Wednesday, February 28, 2018

Variable Directives

I was going to post Video Poker on Spelchan.com on Saturday but ended up too busy with other things so decided to post it today. This is a pretty close port to the original, but in the future I may do an enhanced version of the game. It is actually possible to use Create.JS with Kotlin, though it is a bit of work and does have a few quirks, so I may do a HD version of this game in Kotlin.



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.

This covers most of our variable handling needs, with the only thing missing being way of putting data into the machine language file. This will be covered next time.

No comments:

Post a Comment