The Basics
The main purpose of macros is to reduce the amount of typing needed in a program, to make that code more readable, more maintainable or more aesthetically pleasing.
Macros are basically substitutions; whatever is written within a macro is expanded in full whenever that macro is invoked, or "used". So -
Code:
#Macro SayHello
SerTxd( "Hello", CR, LF )
#EndMacro
Do
SayHello
Loop
Is exactly the same as -
Code:
Do
SerTxd( "Hello", CR, LF )
Loop
Macro Parameters
Macros can have parameters specified so you can pass in values to be used when doing the macro expanded substitution so -
Code:
#Macro Say( text )
SerTxd( text, CR, LF )
#EndMacro
Do
Say( "Hello" )
Say( "And Goodbye" )
Loop
Is exactly the same as -
Code:
Do
SerTxd( "Hello", CR, LF )
SerTxd( "And Goodbye", CR, LF )
Loop
Parameters do not have to be specified for macros, as in our first example above, but one or more parameters can be used for each macro. Those parameters can be variables, numbers or text strings, even expressions. Whatever is specified as a parameter will be substituted wherever the parameter name appears within the macro -
Code:
#Macro Assign( var, expression )
Let var = expression
#EndMacro
Assign( w0, 1 * 2 + 3 )
Is the equivalent of -
Note that although one can pass in expressions, that does not always mean expressions can be passed in if the macro is not written to expect them. For example -
Code:
#Macro PrintResult( expression )
SerTxd( "Result = ", #expression )
#EndMacro
PrintResult( 1 * 2 + 3 )
Creates a Syntax error on the "SerTxd( "Result = ", #1 * 2 + 3 )" which the macro creates, because expressions cannot be used within a SERTXD command.
That can however be resolved by having the macro assign the expression to a variable which can then be used within a SERTXD statement, for example -
Code:
#Macro PrintResult( expression )
w0 = expression
SerTxd( "Result = ", #w0, CR, LF )
#EndMacro
PrintResult( 1 * 2 + 3 )
PrintResult( 99 )
PrintResult( w1 )
PrintResult( "A" )
Remember of course that if you assign or alter a variable within a macro it will change that variable's value outside the macro so you need to be careful in choosing which variables are used.
More Advanced Parameter Passing
As well as using variable names, numbers and text strings as parameters, even command names can be used -
Code:
#Macro SetOutput( command )
command C.1
#EndMacro
SetOutput( High )
SetOutput( Low )
Also subroutine or label names and conditions can be passed as parameters. For example -
Code:
#Macro Repeat( subroutineName, var, condition )
Do
Gosub subroutineName
var = var + 1
Loop While condition
#EndMacro
Repeat( PrintValueOfW0, w0, w0 < 10 )
Is the equivalent of -
Code:
Do
Gosub PrintValueOfW0
w0 = w0 + 1
Loop While w0 < 10
As with passing in an expression earlier; the parameters specified have to match how those parameters are used within the macro. If they are not then a Syntax Error will be generated.
Macros Without Parameters
When a macro does not have any parameters it can be specified in one of two ways, with or without parenthesis, round brackets -
Code:
#Macro MyFirstMacro()
#Macro MyOtherMacro
When a macro without parameters is invoked however it must always be without parenthesis -
Code:
MyFirstMacro
MyOtherMacro
If the parenthesis are included that will generate a Syntax Error.
Parameter Naming
Parameter names can be whatever one wishes to use. However care should be taken in choice of names because the parameter name will override any other use of that name elsewhere. For example -
Code:
#Macro Assign( b10, equals, b11 )
b10 = b11
#EndMacro
b10 = 10
b11 = 11
b12 = 12
b13 = 13
Assign( b12 , = , b13 )
SerTxd( "b10 = ", #b10, CR, LF )
SerTxd( "b11 = ", #b11, CR, LF )
SerTxd( "b12 = ", #b12, CR, LF )
SerTxd( "b13 = ", #b13, CR, LF )
This does not set the value of variable 'b10' to the value of variable 'b11' as the macro may at first glance appears to.
In fact 'b10' and 'b11' are simply substituted with whatever is passed into those parameters when the macro was invoked.
This can be prove particularly problematic and difficult to debug when actual variable names ( not used as parameter names ) are also used within the macro or where a parameter name may override a SYMBOL defined name.
It is advisable not to use variable names or any globally defined SYMBOL names as parameter names. If there does appear to be an issue it is always a good idea to look at the code the macro is expanding to as described earlier.
Care should also be taken not to give parameter names which can be misinterpreted. In our earlier example we had a "PrintResult(expression)" macro where 'expression' was used in a SERTXD command which isn't allowed. The 'expression' name gave the impression that an expression could be used where that was not the case.
It would have been better to have named the parameter as 'variableOrNumber' which reflected what could be used.
Unused Parameters
It is possible to include parameters in a macro definition which are not actually used within the macro itself.
It is not common to have unused parameters, or "dummy parameters" as they are also called, but they can sometimes be handy where one wishes to include something within the macro rather than in a comment along side it. For example -
Code:
#Macro Fibonacci( n, dummy )
w0 = 1
For w1 = 1 To n
w0 = w0 * w1
Next
SerTxd( #n, "! = ", #w0, CR, LF )
#EndMacro
Fibonacci( 3 , "The answer should be 6" )
Fibonacci( 5 , "And this should be 120" )
Fibonacci( 3 , 3*2*1 = 6 )
Fibonacci( 5 , 5*4*3*2*1 = 120 )
Or perhaps -
Code:
#Macro Assign( var, equals, expression )
var = expression
#EndMacro
Assign( w0 , = , w1 + 1 )
Unused parameters are completely ignored, do not appear in any macro expansion, and do not use up any program memory.
Macros Are Not Subroutines
While macros can be used like subroutines it is important to note they are not subroutines, and macros are not a replacement for subroutines.
Whether to use macros or subroutines is a question which can arise and there is no simple or single answer. It very much depends on what you are doing, hoping to achieve, and what the macro or subroutine would be.
Perhaps the best way to think of it is that subroutines allow for common functionality to be handled in a single place, while macros allows code to be more cleanly written.
The main thing to remember is that macros will substitute all they contain whenever the macro is invoked. This means that complicated macros can generate a huge amount of code when they are used. This obviously becomes greater the more frequently those macros are invoked within the code.
It is usually desirable to keep macro generated code as short as possible, which can be achieved by moving common code into a subroutine as one normally would when not using macros.
It is entirely possible to use both macros and subroutines, have macros which make code easier to write which call subroutines to do the work required. For example -
Code:
#Macro DoSomethingComplex( actionNumber )
b0 = actionNumber : SerTxd( "Doing ", #b0 ) : Gosub DoIt
#EndMacro
DoSomethingComplex( ACTION_1 )
DoSomethingComplex( ACTION_2 )
Is exactly equivalent to -
Code:
b0 = ACTION_1 : SerTxd( "Doing ", #b0 ) : Gosub DoIt
b0 = ACTION_2 : SerTxd( "Doing ", #b0 ) : Gosub DoIt
In the above example it would make sense to move the "SerTxd("Doing ",#b0)" command into the "DoIt:" subroutine, so there is only one SERTXD in the code which is in that subroutine, rather a SERTXD being generated every time the macro is invoked.
Labels Within Macros
It is possible to include labels within macros -
Code:
#Macro WaitForCommand
Waiting:
SerRxd #w0
If w0 = 0 Or w0 > 9 Then Waiting
#EndMacro
Do
WaitForCommand
SerTxd( "Command was ", #w0, CR, LF )
Loop
The huge caveat there is; if the macro is is used twice it will generate two labels with the "Waiting:" name which will be reported as a syntax error.
It is however possible to pass label names into a macro so if one wanted to use the above macro it could be rewritten as -
Code:
#Macro WaitForCommand( labelName )
labelName:
SerRxd #w0
If w0 = 0 Or w0 > 9 Then labelName
#EndMacro
Do
WaitForCommand( Wait1 )
SerTxd( "First command was ", #w0, CR, LF )
WaitForCommand( Wait2 )
SerTxd( "Second command was ", #w0, CR, LF )
Loop