When I say
variables for the assembler, I am not talking about reserving storage for the
assembly language program but I am instead talking about setting up some
special labels that get replaced with other values defined within the assembly
language file. These tend to be constants, but having the ability to change the
value of an assembly variable on the fly doesn’t hurt. These are often set up
as an include file (something that my assembler will not have) that contains the
special addresses that the machine you are assembling code for uses. For
instance, you would have variables that define the different TIA register
addresses so that when you want to access one of the TIA registers you simply
need to use the label for that register instead of remembering a more obscure
number.
The
existing label mechanism goes a long way towards supporting variables, so all
we would need to do is have a declarative add a label with the assigned value.
I opted not to do this wanting to insert the value right into the code as the
first target machine is the 2600. If I just used labels, the assembler assumes
a full address not a zero page address while if I am dealing with the TIA then
all the register addresses are zero page addresses as is all the memory (the
2600 only has 128 bytes of RAM). By having the variable macro check a variable
list and replace that token with a number token the assembler will optimize
instructions that can use zero page addressing. When the processDirective function
runs into a label, it simply checks to see if it is a declared variable and if
so replaces that label token with the stored token as follows:
if
(variableList.containsKey(token.contents)) {
tokens[indx]
= variableList.get(token.contents)!!
}
Processing
variables is one thing, but they need to be added before we they can be used.
This is very simple to do as we simply make sure that the declarive is in the
format .EQU label token with the token being what to replace the label with
when it is discovered in our code. I am thinking that for extra safety this
should be limited to a number token but for now will leave it open ended. Restricting
it to just a number requires just an extra check but having it open lets you
have labels as a replacement which may be a desired feature.
"EQU" -> {
if
(indx >= (tokens.size + 1)) {
throw
AssemblyException("Missing parameters for .EQU statement line
$assemblyLine")
}
if
(tokens[indx].type != AssemblerTokenTypes.LABEL_LINK) {
throw
AssemblyException("Missing EQU variable name on line $assemblyLine")
}
val
varName = tokens[indx].contents
tokens.removeAt(indx)
variableList.put(varName,
tokens[indx])
tokens.removeAt(indx)
}
This is
tested easily enough with the following assembly language file:
.EQU ONE 1
.EQU TWO $2
.EQU THREE %11
LDA
#ONE
LDA
#TWO
STA
THREE
BRK
Related to
variables is .HIGH and .LOW directives which are used for getting the high
order byte or low order byte of a variable or label. This comes in handy if you
are setting up some type of branch in zero page and need the address. You would
load the low byte of the address and store it then load the high byte of the
address and store it. The test assembly language does the LDA portion of both a
variable and a label.
.BANK 0 $1000 1024
.EQU hilow $1234
LDA #.HIGH hilow
hllabel: LDA
#.LOW hilow
LDA
#.HIGH hllabel
LDA
#.LOW hllabel
BRK
Actually
implementing the .HIGH directive is simply the matter of determining if the
token after the directive is a number, label or variable. For numbers and
variables we replace the token with the high byte of that number. For labels we
set it to the number 0 and add a label link telling the linker to add the high
byte of the label to this location. The code may seem strange to those of you
not use to Kotlin, but one of the nice features of Kotlin is that if and when
statements can return a value which can then be assigned to a variable which is
quite convenient.
"HIGH" -> {
if
(indx >= tokens.size) {
throw
AssemblyException("Missing parameters for .HIGH statement line
$assemblyLine")
}
val
num = when (tokens[indx].type) {
AssemblerTokenTypes.NUMBER
-> tokens[indx].num
AssemblerTokenTypes.LABEL_LINK
-> {
val
rep = variableList.get(tokens[indx].contents)
if
(rep == null) {
addLabel(AssemblerLabel(tokens[indx].contents,
AssemblerLabelTypes.HIGH_BYTE, currentBank.curAddress+1, currentBank.number))
0
}
else {
rep.num
}
}
else
-> throw AssemblyException("Missing HIGH variable name on line
$assemblyLine")
}
tokens[indx]
= AssemblerToken(AssemblerTokenTypes.NUMBER, "high", (num / 256) and
255)
}
The
implementation of .LOW is fairly similar except using a low label link and
storing the low order byte (ANDing the number by 255) in the number.