The power of high level languages resides on the capability of writing human readable syntax that the compiler translates to machine code. All these high level languages, such as C, VB, Python, ... include structured programming blocks. A structured programming block, is then, a block of instructions that are executed (or not) depending on conditions. MacroAssembler doesn't allow to program blocks such as IF .. THEN .. ELSE, WHILE .. WEND, REPEAT .. UNTIL or FOR ..NEXT, but with the definition of macros for these keywords, we can mimic a high level language sintax.
Introduction
Programmers that use MacroAssembler
, often would like to have the same flexibility that high level languages offer in structured blocks such as "if
.. then
.. else
.. endif
", "while
.. wend
", "repeat
.. until
" or "for
..next
". Why has this class of syntax not been included in MacroAssembler
? Probably because the power of the MacroAssembler
language are the macros itself, so we can mimic the above mentioned blocks and write a more clear code.
I'll use VB naming convention because it is nearer to human readable syntax. (When you, as "C" programmer, reads "}
" in your code, you have to go back in your source to know if it closes a "if
", "else
", "while
" or "do
" block.)
As said, all these blocks are executed (or not) on conditions. A single condition can be read as the result of Operand1 Operator Operand2
where Operand1
and Operand2
are variables or constant values in your code and operator
is an arithmetic one such as =
, >
, <
, >=
, <=
, ...
So, a high level language can process a syntax such as:
IF age > 50 THEN
ENDIF
-or said in another way-
IF Condition THEN
ENDIF
In this case, if we think about what a VB program does when it finds this block in the code is:
- Evaluate the condition.
- If result is
true
, then process all the actions existing from 'THEN
' up to 'ENDIF
' keywords.
A little more elaborated block could be:
IF Condition THEN
ELSE
ENDIF
In this case, the process is:
- Evaluate the condition
- If result is
true
, then process all the actions from 'THEN
' to 'ELSE
' labels, else process all the actions from 'ELSE
' to the 'ENDIF
' labels
Before going deeper into the creation of structured macros, we have to take into account that MacroAssembler
doesn't have the syntax of arithmetic operators. In their place, pnemonics are used. Let's say: E (Equal), L (Less), LE (Less or Equal), B (Below), BE (Below or Equal), G (Greater), GE (Greater or Equal), A (Above), AE (Above or Equal)
or their contraries:
NE (No equal), NL (No less), NLE (No less equal), NB (no below), NBE (no below equal), NG (no greater), NGE (no greater equal), NA (no above), NAE (no Above equal).
What Is a Macro?
For those who use variants of the 'C' language, a macro is the equivalent to #define
a result based on parameters. When MacroAssembler
finds in your code a macro that has previously been defined, replaces the parameters and expands the macro.
Once arrived at this point, we can write (and study) our first very basic macro with the following syntax: (Text between []
means optional from so on):
$iif macro op1, oper, op2, label1, label2
cmp &op1, &op2
j&oper &label1
ifnb <&label2>
(the 'label2' parameter exists) then MacroAssembler assembles then next line
jmp &label2
endif
endm
If we place the following text in our code ...
$iif ax,e,5,IsFive
...MacroAssemble will expand the macro to ...
cmp ax, 5
je IsFive
if we place the following text in our code ...
$iif ax,e,5,IsFive,NoIsFive
...MacroAssemble will expand the macro to ...
cmp ax, 5
je IsFive
jmp NoIsFive
So, by the use of this little macro, we can reduce all the source code in comparisons from two or three lines to only one, making our code very much readable.
The next thing we can study are the high level language Iterating blocks:
-
Repeat
Until Condition (is true)
In this case, what a compiler does when it finds the 'Repeat
' tag is to save the address of the first action to do. When it finds the 'Until
' tag, the program has to evaluate the condition. If condition evaluates to false
, then the program has to jump to the address of the first action in the block saved before (else the work has done).
-
While Condition (is true)
Wend
In this case, when the compiler finds the 'While
' tag, it saves the starting position of the actions. The runtime evaluates 'condition
'. If it's true
, the execution will continue in the first action if the condition evaluates to false
, then execution will continue after 'Wend
' tag.
-
For variable,Initial_Value,Final_Value,Step
Next
This is a bit complex. The function is to execute 'Actions
' with 'variable
' values from 'Initial_Value
' to 'Final_Value
', increasing or decreasing 'variable
' in each iteration by 'Step
'. When 'variable
' exceeds 'final_value
', the iteration is finished.
First, the runtime assigns 'Initial_Value
' to 'variable
'. Second evaluates if 'variable
' exceeds 'Final_Value
'. If Final_Value
is not exceeded, then all the actions are executed, variable is increased or decreased and the process continues at point 2. If final_value
is exceeded, then the iteration is finished.
Putting All Together for MacroAssembler
MacroAssembler assembles top-down in your code, as all high level languages. That means that when assembler tries to assembly an instruction (or macro) it knows where is this instruction in your code (its address). First of all, we have a counter of symbolic places in the code. Each place name for macroassemble will be $sim_
plus the counter value.
As iterating blocks require to know where they start and/or end to allow jumps to this places, some internal macros are provided:
$pushaddr
: When found, it increases the counter of symbolic names and generates a $sim_counter
name to identify the start of a block equal to the place where the macro has been found. $popaddr
: When found, it recovers the $sim_counter
value as $jmp
variable and decreases the counter of symbolic names. $makenops
: Allocates a 3 byte code for a jmp
'xxxx' that $filljmp
will set later $filljmp
: Fills the preallocated 3 bytes space with a jmp
'xxxx'.
NOTE: These macros were created for MacroAssembler 5.1. In assembling code for flat memory, perhaps you should add more nops
in $makenops
to allow offsets in code to be greater than a signed word. To do so, you have to write a little program such as:
jmp veryfar
db 100000 dup(0)
veryfar:
... when assembled, you can see how many bytes "jmp veryfar
" uses to store the instruction. This number of bytes are the nops you have to reserve in $makenops
. Remember to revise the keyword 'near
' in the code provided if needed.
Explanations of every macro are detailed in the included text. This macros can be nested, that means that from now on, you can write MacroAssembler code as:
$IF value,le,100
$THEN
$ELSE
$ENDIF
$FOR x,0,1366-1,1
$FOR y,0,768-1,1
$NEXT
$NEXT
$REPEAT
$UNTIL key,e,Escape
$WHILE value,l,100
$WEND
Using the Code
Just include the text provided at the beginning of your code or #include
it.
Code
.XLIST
.XCREF
$simcount = 0
$pushaddr macro
$simcount = $simcount + 1
$newsim %$simcount
endm
$newsim macro $n
$sim_&$n = this near
endm
$popaddr macro
$getsim %$simcount
$simcount = $simcount - 1
endm
$getsim macro $n
ifndef $sim_&$n
%out fail in structure !!!
endif
$jmp = $sim_&$n
endm
$makenops macro
nop
nop
nop
endm
$makejump macro towhere
here = this near
$popaddr
org $jmp ;we go there ...
jmp &towhere
org here ;we come back to our position in the source code
endm
$iif macro op1, oper, op2, label1, label2
cmp &op1, &op2
j&cond &label1
ifnb <&label2>
jmp &label2
endif
endm
$IF macro op1, oper, op2
local block1
$iif <op1>, oper, <op2>, block1
$pushaddr
$makenops
block1:
endm
$THEN macro
endm
$ELSE macro
local block2
$makejump block2
$pushaddr
$makenops
block2:
endm
$ENDIF macro
local exitif
$makejump exitif
exitif:
endm
$WHILE macro op1, oper, op2
local istrue
$pushaddr
$iif <op1>, oper, <op2>, istrue
$pushaddr
$makenops
istrue:
endm
$WEND macro
local quitloop
$makejump quitloop
$popaddr
jmp $jmp
quitloop:
endm
$REPEAT macro
$pushaddr
endm
$UNTIL macro op1, oper, op2
local quitloop
$iif <op1>, oper, <op2>, quitloop
$popaddr
jmp $jmp
quitloop:
endm
$FOR macro index,initial_value,final_value,step,register
local compare, inrange
ifnb <®ister>
mov ®ister, &initial_value
mov &index, ®ister
else
mov &index, &initial_value
endif
jmp short compare
$pushaddr
if step eq 1
inc index
else
if step eq -1
dec index
else
add index, step
endif
endif
compare:
ifnb <®ister>
mov ®ister, &final_value
cmp &index, ®ister
else
cmp &index, &final_value
endif
if step gt 0
jle inrange
else
jge inrange
endif
$pushaddr
$makenops
inrange:
endm
$NEXT macro
local quitloop
$makejump quitloop
$popaddr
jmp $jmp
quitloop:
endm
$LOOP equ <$NEXT>
.CREF
.LIST
Points of Interest
After decompiling "C" code snippet, I was able to determine what a compiler does, so the macros mimic the same process.
History
These macros haven't ever been modified.