MACRO/END MACRO block

Purpose

Define a single or multi-line text substitution block.

Syntax

Single line macro:

MACRO macroname [(prm1, prm2, ...)] = replacementtext

Multi-line macro:

MACRO macroname [(prm1, prm2, ...)]

  [MACROTEMP ident1 [, ident2, ...]]

  DIM ident1 AS type [, ident2 AS type, ...]]

  {replacementtext}

  [EXIT MACRO]

  {replacementtext}

END MACRO

Macro function:

MACRO FUNCTION macroname [(prm1, prm2, ...)]

  [MACROTEMP ident1 [, ident2, ...]

  DIM ident1 AS type [, ident2 AS type, ...]]

  {replacementtext}

  [EXIT MACRO]

  {replacementtext}

END MACRO = returnexpression

Remarks

Macro is a powerful text substitution construct that may take a single-line or multi-line format.  It generates absolutely no executable code unless it is referenced, and effectively allows the programmer to design a part of the PowerBASIC language to his/her own needs and requirements.  For example, a simple single-line macro can allow PowerBASIC to emulate the CONST syntax used in Visual Basic - see the box-out below for more information.

A macro must always be defined before it is referenced, and the parameter count must always match the definition.  When a macro is referenced, the occurrence of the name is replaced by the defined replacement text, expanded with parameter substitution.  The first line of a MACRO definition is termed the macro prototype, and this line may not be split into multiple logical lines with the underscore (_) line continuation character.  Likewise, the END MACRO = returnexpression may not be split with underscores either. A Macro also cannot end with a line continuation character.

Macros may be nested, and may forward-reference other macros.  However, care should be exercised to avoid circular references.

A single-line macro or a macro function may be referenced at any source code position which, when expanded, will be syntactically correct (also see the Restrictions section below).  Consider the following simplistic example:

MACRO concatenate(prm1,prm2) = prm1 & $SPC & prm2

' more code here

A$ = concatenate("Hello","World")

During compilation, PowerBASIC would internally expand this code to become:

A$ = "Hello" & $SPC & "World"

A multi-line macro, while more powerful in terms of coding, may be referenced only in the "statement" position, which is the first position on a line.  That single reference is internally expanded into multiple lines of inline code to perform a complex task.  For example:

MACRO Display6times(prm1)

  CALL Display(prm1) : CALL Display(prm1)

  CALL Display(prm1) : CALL Display(prm1)

  CALL Display(prm1) : CALL Display(prm1)

END MACRO

' more code here

Display6times("This is very cool...")

The single-line MACRO offers a cunning way to retain the CONST syntax used in MSBASIC and Visual Basic in your PowerBASIC code, while maintaining the low overhead advantage of PowerBASIC.  For example:

MACRO CONST = MACRO

' more code here

CONST Version  = 1&

CONST AppTitle = "My Application"

' more code here

a$ = AppTitle & " v" & FORMAT$(Version)

During compilation, the CONST keyword is replaced by the MACRO keyword, dynamically creating a new macro that, in turn, defines a numeric or string literal.  When the real macro name is referenced in the code, the literal is substituted directly.

MACROTEMP

The MACROTEMP statement may be used to specify a list of one or more identifiers, each of which is automatically made unique to each expansion of a multi-line macro.  This is done by internally appending the digits 0001, 0002, etc, to the identifier upon each expansion of the macro.

A text identifier may represent a variable, label, or any other word, which expands appropriately to avoid a duplicate name conflict in your code.

MACROTEMP just creates a symbol name.  If this symbol is a variable name, the variable must still be formally declared with an appropriate DIM (or LOCAL) statement.  For example:

MACRO CopyUntilNul(ptr1,ptr2)

  MACROTEMP LoopPoint, ByteVar

  DIM ByteVar AS BYTE

LoopPoint:

  ByteVar = @ptr1

  @ptr2 = ByteVar

  INCR ptr1

  INCR ptr2

  IF ByteVar <> 0 THEN LoopPoint

END MACRO

Using that MACRO definition, the code "CopyUntilNul(Source, Dest)" would expand to something like this:

DIM ByteVar0001 AS BYTE

LoopPoint0001:

  ByteVar0001 = @Source

  @Dest = ByteVar0001

  INCR Source

  INCR Dest

  IF ByteVar0001 <> 0 THEN LoopPoint0001

If the MACROTEMP statement were not used, serious naming conflicts would occur most any time that a macro was expanded more than once in a program.  MACROTEMP statements may appear 0, 1, or more times in a macro definition, but they must always precede any other text in the macro.

MACROTEMP statements should be used with any label in a macro that may be expanded more than once in a program, and with any variable that should not be shared with any other expansion of the macro.

EXIT MACRO

EXIT MACRO may be used to terminate execution of code in the current macro expansion.  It is functionally identical to the imaginary concept of GOTO END-MACRO.

END MACRO

A macro function block can return a value with the END MACRO = returnexpression statement.

Restrictions

A macro definition may contain replacement text up to approximately 4000 characters.  Macros may specify up to 240 parameters, which may occupy up to approximately 2000 bytes total expanded space per macro.

Macro Function substitutions are limited to an expanded total of approximately 16000 characters per line of original source code.

Macro parameters are substituted directly, so whitespace characters in the passed macro parameters may cause unexpected problems if the expanded code is syntactically incorrect with the additional whitespace.  For example, this can be important when specifying UDT variables as macro parameters.  Consider the following code:

TYPE MyType

  lCount AS LONG

  szText AS ASCIIZ * 256

END TYPE

 

MACRO PresetUDT(u)

  u.lCount = 1

  u.szText = SPACE$(256)

END MACRO

 

FUNCTION PBMAIN

  DIM x AS MyType

  PresetUDT(x)

  PresetUDT(x )  ' This line causes an Error 526

END FUNCTION

In the code above, the second macro expansion fails to compile because the trailing space in the passed macro parameter becomes part of the expanded code.  In this situation, this additional space character breaks the syntax of the UDT variable reference within the expanded macro, triggering a compile-time Error 526 ("Period not allowed").  If we examine how the two expanded macro statements would appear, the problem becomes immediately obvious:

x .lCount = 1

 ^

x .szText = SPACE$(256)

 ^

(Please note that the caret symbols (^) above have been added purely to illustrate the exact position of the problem)

When using single-line macros that contain numeric expressions, use parentheses around the macro body to guard against unexpected order of precedence problems when the macro is used within an expression.  For example, consider the following macro and expansion:

MACRO Calculate(p1, p2, p3) = (p1 * p2) \ p3

' more code here

x = Calculate(a,b,c) ^ 3

When this macro is expanded, the expression would be calculated as follows:

x = (a * b) \ c ^ 3

However, if the macro body was enclosed in parentheses:

MACRO Calculate(p1, p2, p3) = ((p1 * p2) \ p3)

…then the expanded expression would be calculated thus:

x = ((a * b) \ c) ^ 3

MACRO prototypes (those beginning with the MACRO keyword) and END MACRO = returnexpression lines must be constructed on a single line of source code. That is, they may not be split across multiple lines of source code with line continuation characters, since these interfere with the text substitution process.  For example, the following prototype is invalid:

MACRO FUNCTION MyMacro1(sParam1, sParam2, sParam3, sParam4)

If a macro expands directly to a Function call, the macro can be called using the SUB-style syntax, automatically discarding the function return value.  For example:

MACRO sm(Msg) = SendMessage(a, Msg, b, c)

…can be called like this (if the return value is not required):

sm(x)

A macro cannot expand directly to a REMark, because REM and ' are processed before the macro is assigned. So, MACRO hello = REM winds up as an invalid, blank macro.

Finally, it should be noted that the Integrated Debugger appears to step over macro references as if they were conventional BASIC statements.  This occurs because macro expansion takes place during the compilation process and the original source code is not affected or altered by the compile-time expansion.

See also

EXIT, FUNCTION/END FUNCTION, METHOD, PROPERTY, SUB/END SUB

Example

' Single-line macro:

MACRO muldivide(p1, p2, p3) = ((p1 * p2) / p3)

' more code here

x = muldivide(3,3,2) + 10

 

' Multi-line macro and macro function example:

MACRO FUNCTION HowDidIGetHere

  MACROTEMP i, a

  DIM i AS LONG, a$

  FOR i = CALLSTKCOUNT TO 1 STEP -1

    A$ = A$ + CALLSTK$(i) + ", "

  NEXT

END MACRO = RTRIM$(A$, ANY ", ")

 

MACRO DisplayText(txt)

  #IF %DEF(%PB_CC32)

     PRINT txt

  #ELSE

     MSGBOX txt

  #ENDIF

END MACRO

 

SUB Testing2(r AS LONG,z AS ASCIIZ)

  DisplayText(HowDidIGetHere)

END SUB

 

SUB testing1(z AS ASCIIZ)

  DisplayText(HowDidIGetHere)

  CALL Testing2(1,z)

END SUB

 

FUNCTION PBMAIN

  DisplayText(HowDidIGetHere)

  CALL Testing1("This is a test")

END FUNCTION

 

' Useful Macro functions

MACRO Pi = 3.141592653589793##

MACRO DegreesToRadians(dpDegrees) = (dpDegrees * 0.0174532925199433##)

MACRO RadiansToDegrees(dpRadians) = (dpRadians * 57.29577951308232##)