Ampliation of macro definitions for Assembler to be able to use sentences as SWITCH / CASE / [BREAK] / [DEFAULT] and ENDSWITCH
Introduction
As mentioned in the previous article, the power of high level languages resides on the capability of writing human readable syntax that the compiler translates to machine code. In the previous article, I commented the way of writing macros such as "IF
/THEN
/[ELSE
]/ENDIF
", "FOR
..NEXT
", "WHILE
..WEND
" or "REPEAT
..UNTIL
". In this article, I explain how to build macros for "SWITCH
/CASE
/[BREAK
]/[DEFAULT
]/ENDSWITCH
" although I think, SWITCH
/ CASE
in High Level Assembler syntax shouldn't be implemented because, in structured programming, this high level syntax is a sequence of blocks of 'if
condition(s) then /*actions*/' statements.
Anyway, they can try to develop these macros for those who want to use them.
Background
MacroAssembler is not a friendly language. With the use of these macros, you can improve the reading of your code and write a more comprehensible text. Have you tried to modify a program you had written in the past?. (You'll know what I mean). What about 'debugging'? (The same). Have you listed an .ASM source code?. (So on.)
Using the Code
In this article, I post the definition of the previous macros ("IF
/THEN
/[ELSE
]/ENDIF
", "FOR
..NEXT
", "WHILE
..WEND
" and "REPEAT
..UNTIL
") as a file to include in your source code (macros.inc) and the definition of new macros ("SWITCH
, CASE
, [BREAK
], [DEFAULT
] and ENDSWITCH
") as a file to include (switch.inc) after the previous one. Also is included a little source program to test the macros (Test.asm).
Explanation of Switch, Case, Break, Default and EndSwitch
Studying high level languages syntax, we can observe little differences between languages:
VB Syntax | VC Syntax |
select case n
case 0: ...
case 6 to 9, 11 to 12 : ...
[case else] ...
end select
|
switch(n) {
case 0: ... ; break;
case 6,7,8,9,11,12: ... ; break;
[default] ...
}
|
The great difference between VB and VC is that VB doesn't have a token as 'break
', so in each 'case
' and after the last action, VB jumps outside the 'Select Case
' (that means that 'break
' is implicit in each 'case
' in VB). By the other side, if we include 'break
' as the last token in all VC 'cases', then the similarity to VB in its functionality is evident, but ... 'break
' is an optional token in VC. So, if it is included after the last token in a 'case
', it will 'jump_out_of_switch
' after the execution of the actions, but if it's not included, then the compiler will do a little jump to the first action of the following 'case
' statement, ignoring the next 'case
' test conditions.
To be a little more clear, let's examine a function in VC:
void function(int i)
{
int value = -1;
switch(i)
{
case 1:
case 2:
case 3: value=0;
break; case 4: value=1; case 5:
case 6:
error(value);
break; default:
DoSomething(value);
}
}
In other words, VC is more flexible and powerful than VB in SWITCH
/CASE
statements, but increases the complexity of compilation.
In both languages, a Switch
starts with a variable (n in the sample) to test against constants. In Assembler, we'll give the possibility of using a variable or register to test against the constants for each 'switch
'. So, the first thing we have to do is remember which variable or register has been assigned to a 'switch
' and which number of 'switch
' we are processing, because these kind of blocks could be nested too. This variable will be $switchcount
, and starts at zero at the beginning of our program.
$SWITCH Macro VarOrReg
The SWITCH
macro consists basically of two jumps:
- a
jmp
to Start_of_Cases
and - a
jmp Out_Of_Switch
that will be assigned by $ENDSWITCH
. All cases with 'break
' will jump here, and then, Out_Of_Switch
We could create labels in our program as Out_Of_Switch_1
, Out_Of_Switch_2
... after every $ENDSWITCH
and assign the jmp Out_Of_Switch
in a way like $SWITCH i
, Out_of_this_switchLabel
, but this doesn't seem to me a beautiful solution.
As said, we have to remember info about every 'switch
' as which variable or register to use in each case and the address of its jump out_the_switch_block
. So, we will define two helpers for the $SWITCH
macro: $SetSwitchInfo
and $GetSwitchInfo
(see the companion code for these macros as well as for $SWITCH
)
$CASE Macros
The syntax of a 'case
' statement is quite complex to implement, so we are going to create two different syntaxes to process a 'case
', that will be explained later:
$CASE <argList>
to process a list of constant values, i.e., $CASE <1,2,3,5,18>
$CASEFROMTO const1, const2
to process a range of values, i.e., $CASEFROMTO 5,15
To start with, remember that when a VC compiler finds a 'case
', the internal processing is something like:
if none of the conditions is meet {
jump_to_next_case;
}
So, here starts the complications:
- The actions provided for this case are outside the macros
$CASE
or $CASEFROMTO
and aren't assembled yet Next_Case
, start_of_actions_of_the_next_case
and somewhere are undefined because they haven't been assembled yet $BREAK
, if provided after this 'case
' macro, is outside the macro too.
Fortunately, jump_to_somewhere
is the last instruction in a 'case
' block being placed after the actions of the block. So, when we start processing the following 'case
', we know that the address of the preceding jump_to_somewhere
is just the address where assembler is assembling just now. At this point, we know too if the preceding 'case
' has used 'break
' or not.
Quote:
We can create the jump_to_somewhere of the preceding 'case' at the start of the current one.
To do so, we need to keep track of which number is this 'case
' into the current 'switch
'. When we start a 'case
', we increment the 'cases
' counter of the 'switch
', and when this counter is greater than one,
If the previous 'case
' as used 'break
', we will assemble a jmp out_of_switch
else we will assemble a jmp start_of_actions
in this 'case
'.
To keep control over each 'case
', we'll define helper macros such as $GetCaseInfo
, $SetCaseInfo
. In each 'case
', we'll assign control variables that respectively mean:
$PrevSomewhere
: is the address of the previous 'case
' jump_to_somewhere
, followed by three nops
that will became a jump out of switch (if break
is used in the previous case) or a jump to 1st action of the next case (if break
is not used)
$This_Start_Of_Case
: Address of the real logic starting address of this case
$This_JmpToNextCase
: At runtime, when none of the constants is equal to the test value, we have to jump to the next_case_Start_Of_case
. This is the address to put the jump (known at the evaluation of the next case).
$This_1stAction:
Address of first action of this case that will be assembled a bit later
The helper $AdjustPrevCase
assigns the previous 'case
' jump_to_next_case
to $This_Start_Of_Case
and its jump_to_somewhe
(this $PrevSomewhere
) as mentioned previously. $SetCaseInfo
will save information about the current 'case
'.
$CASE Macro argList
Local TestOK
$PrevSomewhere = this near
$reservejump
$IncSwitchCases %$switchcount
$This_Start_Of_Case = this near
IRP arg,<argList>
cmp $switchname, arg
jz TestOK
endm
$This_JmpToNextCase = this near,
$reservejump
$This_1stAction = this near
TestOK:
$AdjustPrevCase
$SetCaseInfo %$switchcount, %$switchcase
endm
$CASERANGE Macro FromValue, ToValue
The structure or this syntax is equal to the $CASE
. Only change the way of testing the constants passed to the macro.
[$DEFAULT Macro]
[$DEFAULT]
is a special 'case
' that has no test values but only actions. The internal structure and process of previous 'cases' is the same as $CASE
and $CASERANGE
.
[$BREAK Macro]
[$BREAK]
does nothing, since the required jumps are processed at the start of $CASE
, $CASERANGE
or $DEFAULT
. It only identifies if the previous $CASE
, $CASERANGE
or $DEFAULT
uses 'break
'.
$ENDSWITCH Macro
When a switch
block is ended with $ENDSWITH
, we have to replace its last 'case
' jump_to_the_next_case
nops with a jump outside the block. Also, we have to set the address $switchquit
of the block with a jump to this AddressOfOutOfSwith
.
AddressOfOutOfSwitch = this near
$PrevSomewhere = this near
$This_JmpToNextCase = this near
$This_Start_Of_Case = this near
$This_1stAction = this near
if $switchcase gt 0
$SetJump2NextCase %$switchcount, %$switchcase
endif
$ReplaceNops4Jmp $switchquit, AddressOfOutOfSwitch
$switchcount = $switchcount-1
if $switchcount gt 0
$GetSwitchInfo %$switchcount
endif
endm
Points of Interest
And that's all folks! You can play the companion sample program to test this macros.
Once the mechanism of the 'cases' has been understood, you could implement other syntaxes for them. By the way, I've changed the name of the previous $makejums
macro @ macros.inc to $reservejump
, because this name is more intuitive and I've changed the definition of the first (3 consecutive nops) to db 90h, 90h, 90h in the second, because the expansion of the macro is shorter in the listings.
History
- 19th September, 2021: First version of
SWITCH
/CASE
/[BREAK
]/[DEFAULT
]/ENDSWITCH
macros. Few changes are being included in the first version of macros.inc.