Macros
Macros are named code segments that are substituted to their names each time they are encountered in a program.
Macros offer a powerful way to extend the language and create reusable code.
One reason macros are used is performance.
They are a way of eliminating procedure call overhead because they are always expanded in-line.
There is no alternative for this in FreeBASIC because it does not support inline procedures.
Macros definition
The preprocessor can, during the text replacement mechanism, use parameters supplied to the identifier to be replaced.
These parameters are then replaced without modification in the replacement text.
The replacement text is then called macro.
The syntax to define a macro is as follows (see 'Preprocessor commands'):
- one-line macro:
`#define identifier([parameters]) body`
`parameters` turn a define into a function-like macro, allowing text arguments to be passed to the macro.
`identifier` should be followed by the opening parentheses (`(`) immediately without any white-space in between, otherwise the compiler will treat it as part of the `body`.
- multi-line macro:
`#macro identifier([parameters])`
`body`
`#endmacro`
`#macro` is the multi-line version of `#define`.
The `# Stringize` operator can be used on macro parameters to turn them into string literals, and the `## Concatenate` operator can be used to merge tokens together.
(see 'Preprocessor Operators')
Defines and macros are scoped (they are only visible in the scope they were defined in).
Namespaces on the other hand do not have any effect on the visibility of the defines and macros.
The mechanism of macros allows to do the equivalent of general procedures, which work for all types.
However, care must be taken that parameters passed to a macro are evaluated by it each time they are used in the macro definition. This can cause performance issues or, worse, cause unwanted edge effects.
Parentheses should always be placed around the parameters of the macro:
- Indeed, these parameters can be compound expressions, which must be computed completely before being used in the macro.
- Parentheses force this calculation.
- If they are not set, priority rules can generate a logic error in the macro itself.
Similarly, macros that return a value should be surrounded by parentheses, in order to force their complete evaluation before using them in another expression.
Example of incorrect and correct macros:
`
#define MUL1(x, y) x * y '' incorect macro (parameters must be enclosed in parentheses)
#define MUL2(x, y) ( x ) * ( y ) '' incorrect macro (and returned result must also be in parentheses)
#define MUL3(x, y) ( ( x ) * ( y ) ) '' correct macro
Print MUL1(5-3, 1+2)^2 '' 6 (incorrect result)
Print MUL2(5-3, 1+2)^2 '' 18 (incorrect result)
Print MUL3(5-3, 1+2)^2 '' 36 (correct result)
Sleep
`
Thus, the parentheses ensure a consistent behavior of the macro (parentheses can add to macro definitions, but they are absolutely necessary).
Macros debug
Using macros can be extremely unsafe and they hide a lot of pitfalls which are very hard to find.
Procedures give type checking and scoping, but macros just substitute the passed argument.
Another disadvantage of the macro is the size of the program. The reason is, the pre-processor will replace all the macros in the program by its real definition prior to the compilation process of the program.
Looking only at the source code file, the only way to find out what the problem is to look at the definition of the macro and try to understand what happened.
The most common error when using macros is the unbalanced open parentheses (inducing error at compile-time).
Another is to forget putting parentheses around arguments (or returned result if exists) in macro definitions. That can cause some pretty nasty side effects because of operator precedence (inducing error at compile-time or bug at run-time).
When the compiler detects an error inside a macro (after expanding), it provides a rustic error message containing only:
- the line number where is the call of the macro,
- the error type,
- the text of the call (of the macro).
When the error is not obvious (type of error reported blurred), the only solution (with only the call line number) is presently to iteratively execute the following 5 steps until correction successful:
`Do`
`1.` call fbc on the source file, but with the '-pp' compile option (fbc command emitting only the pre-processed input file, without compiling),
`2.` recover the pre-processed file,
`3.` edit and compile directly from this pre-processed file,
`4.` analyze the error, understand it, correct it, and compile again the pre-processed file thus modified,
`5.` postpone the equivalent correction in the concerned macro body of the original source file.
`Loop`
Example of error on a short code:

- Source file (*.bas):

`
#macro FIRST(array, Operator)
For index As Integer = LBound(array) To UBound(array) - 1
Print " " Operator array(index) Operator ",";
Next index
Print array(UBound(array))
#endmacro
#macro Second(array)
Print #array + ":"
FIRST(array, +)
#endmacro
Dim As String test1(0 To ... ) => {"First", "Second", "Third"}
Dim As Integer test2(0 To ...) => {1, 2 ,3}
Second(test1)
Print
Second(test2)
Sleep
`
Compiler output:
...\FBIDETEMP.bas(18) error 20: Type mismatch, found '+' in 'SECOND(test2)'

- Pre-processed file (*.pp.bas):

`
Dim As String test1(0 To ... ) => {"First", "Second", "Third"}
Dim As Integer test2(0 To ...) => {1, 2 ,3}
Print $"test1" + ":"
For index As Integer =LBound(test1) To UBound(test1) -1
Print " " + test1(index) + ",";
Next index
Print test1(UBound(test1))
Print
Print $"test2" + ":"
For index As Integer =LBound(test2) To UBound(test2) -1
Print " " + test2(index) + ",";
Next index
Print test2(UBound(test2))
Sleep
`
Compiler output:
...\FBIDETEMP.bas(14) error 20: Type mismatch, found '+' in 'Print " " + test2(index) + ",";'

- Example macro correction in source code (*.bas):

`
#macro FIRST(array, Operator)
For index As Integer = LBound(array) To UBound(array) - 1
Print " " Operator array(index) Operator ",";
Next index
Print array(UBound(array))
#endmacro
#macro Second(array)
Print #array + ":"
FIRST(array, &) ''corrected line ("&" instead of "+")
#endmacro
Dim As String test1(0 To ... ) => {"First", "Second", "Third"}
Dim As Integer test2(0 To ...) => {1, 2 ,3}
Second(test1)
Print
Second(test2)
Sleep
`
Output:

    test1:
     First, Second,Third

    test2:
     1, 2, 3

Note: Another solution could be a more detailed error message from compiler, relating to the call of the macro (proposal of improved error message already filled in in a feature request).
Variadic macros
Using an ellipsis `"..."` (3 dots) behind the last parameter in a `#macro` or `#define` declaration allows creation of a variadic macro:
`#macro identifier([parameters,] variadic_parameter...)`
`body`
`#endmacro`
or:
`#define identifier([parameters,] variadic_parameter...) body`
So, it is possible to pass any number of arguments through the `variadic_parameter`, which can be used in the body as if it was a normal macro parameter.
During macro expansion each occurrence of the `variadic_parameter` in the macro replacement list is replaced by the passed arguments. The `variadic_parameter` will expand to the full list of arguments passed to it, including commas, and can also be completely empty.
No direct means is provided to recursively access individual arguments in the variable argument list.
To distinguish between the different arguments passed by the `variadic_parameter`, one can first convert the `variadic_parameter` to a string literal using the `# Stringize` operator, then differentiate in this string literal (`#variadic_parameter`) each passed argument by locating the separators (a comma).
Examples
Example with one-line and multi-line macros:
`
#define MIN(x, y) IIf( ( x ) < ( y ), x, y ) '' maximum function-like macro
#define MAX(x, y) IIf( ( x ) > ( y ), x, y ) '' minimum function-like macro
#define MUL(x, y) ( ( x ) * ( y ) ) '' multiply function-like macro
#macro REV_STR(str_dest, str_src) '' reverse-string sub-like macro
For i As Integer = Len(str_src) To 1 Step -1
str_dest &= Mid(str_src, i, 1)
Next I
#endmacro
Print MIN(5 - 3, 1 + 2) '' 2
Print MAX(5 - 3, 1 + 2) '' 3
Print MUL(5 - 3, 1 + 2)^2 '' 36
Dim As String s
REV_STR(s, "CISABeerF") '' FreeBASIC
Print s
Sleep
`
Example with variadic macro:
`
' macro with a variadic parameter which can contain several sub-parameters:
' To distinguish between the different arguments passed by variadic_parameter,
' you can first convert variadic_parameter to a string using the Operator # (Preprocessor Stringize),
' then differentiate in this string (#variadic_parameter) each passed argument by locating the separators (usually a comma).
#macro average(result, arg...)
Scope
Dim As String s = #arg
If s <> "" Then
result = 0
Dim As Integer n
Do
Dim As Integer k = InStr(1, s, ",")
If k = 0 Then
result += Val(s)
result /= n + 1
Exit Do
End If
result += Val(Left(s, k - 1))
n += 1
s = Mid(s, k + 1)
Loop
End If
End Scope
#endmacro
Dim As Double result
average(result, 1, 2, 3, 4, 5, 6)
Print result
' Output : 3.5
`
See also

- Preprocessor Overview
- Conditional Compilation