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

