Get new drop: current VB9 Customer Technology Preview (CTP). Copy the database Saturn5_002.mdf and Saturn5_002_log.LDF to c:\. Zipped VB9 project directory tree.
The Hewlett-Packard manual exhibits the following command strings (last blog I said there were 256 possible and 236 used, but here there are 250 – the explanation is that I fold 19 command strings to uppercase, e.g., LBLa = LBLA, and there are five extension command strings, 19-5+236=250; this is admittedly a tiny bit of cruft in my program). This is taken right out of the database table “InstructionSet”:
| 0 | CLX | ENG | GSBA | GTO9 | LBL2 | LSTX | RCL1 | S | ST/8 | ST+8 | STO4 | X<0? |
| 1 | COS | ENT^ | GSBB | GTOA | LBL3 | - | RCL2 | SCI | ST/9 | ST+9 | STO5 | X<>Y? |
| 2 | COS-1 | e^x | GSBC | GTOB | LBL4 | MRG | RCL3 | SF0 | ST-0 | ST*0 | STO6 | X=Y? |
| 3 | DEG | F0? | GSBD | GTOC | LBL5 | N! | RCL4 | SF1 | ST-1 | ST*1 | STO7 | X>Y? |
| 4 | / | F1? | GSBE | GTOD | LBL6 | ->P | RCL5 | SF2 | ST-2 | ST*2 | STO8 | X<=Y? |
| 5 | D->R | F2? | GSBa | GTOE | LBL7 | % | RCL6 | SF3 | ST-3 | ST*3 | STO9 | <X> |
| 6 | DSP0 | F3? | GSBb | GTOa | LBL8 | %CH | RCL7 | SUM+ | ST-4 | ST*4 | STOA | X^2 |
| 7 | DSP1 | FRC | GSBc | GTOb | LBL9 | Pi | RCL8 | SUM- | ST-5 | ST*5 | STOB | X<->I |
| 8 | DSP2 | FIX | GSBd | GTOc | LBLA | + | RCL9 | SIN | ST-6 | ST*6 | STOC | X<->Y |
| 9 | DSP3 | GRAD | GSBe | GTOd | LBLB | PREG | RCLA | SIN-1 | ST-7 | ST*7 | STOD | Y^x |
| . | DSP4 | GSB0 | GSBi | GTOe | LBLC | PRST | RCLB | SPC | ST-8 | ST*8 | STOE | |
| 1/X | DSP5 | GSB1 | GTO0 | GTOi | LBLD | PRTX | RCLC | SQRT | ST-9 | ST*9 | STOI | |
| 10^x | DSP6 | GSB2 | GTO1 | ->HMS | LBLE | P<->S | RCLD | ST/0 | ST+0 | ST/i | STOi | |
| ABS | DSP7 | GSB3 | GTO2 | HMS-> | LBLa | PSE | RCLE | ST/1 | ST+1 | ST-i | TAN-1 | |
| CF0 | DSP8 | GSB4 | GTO3 | HMS+ | LBLb | ->R | RCLi | ST/2 | ST+2 | ST+i | TAN | |
| CF1 | DSP9 | GSB5 | GTO4 | INT | LBLc | Rv | RCLi | ST/3 | ST+3 | ST*i | * | |
| CF2 | DSPi | GSB6 | GTO5 | ISZI | LBLd | R^ | RCLS | ST/4 | ST+4 | STO0 | WDTA | |
| CF3 | DSZI | GSB7 | GTO6 | ISZi | LBLe | RAD | RND | ST/5 | ST+5 | STO1 | X<>0? | |
| CHS | DSZi | GSB8 | GTO7 | LBL0 | LN | R->D | R/S | ST/6 | ST+6 | STO2 | X=0? | |
| CLRG | EEX | GSB9 | GTO8 | LBL1 | LOG | RCL0 | RTN | ST/7 | ST+7 | STO3 | X>0? | |
The first task is to fill the dictionary ‘gas tank’ of static delegates with pairs of command strings and delegates, preparatory to going into a loop reading command strings and executing delegates. Do this in DelExec.Init. The first ten commands are DSP0, DSP1, …, DSP9 for setting up the number of digits to display on the calculator faceplate and in console-mode printouts. In the ironic fashion presented last time, these will all dispatch to a single command delegate, dspcmd:
Sub Init()
Dim digits = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}
Dim labels = {"A", "B", "C", "D", "E"} REM folded to uppercase
Dim ops = {"+", "-", "*", "/"}
For Each i In digits
staticDelegates.Add("DSP" + i, AddressOf dspcmd)
Next
Here, we use + to stitch together strings; & would work just as well (I’m not sure if there is any difference). Let’s look at dspcmd:
Function dspcmd(ByVal c As String, ByVal m As Integer) As Integer
Savex()
Dim q = c(3) REM fish out the digit from the command string
Dim r As Integer = dpyPrecision
If q = "i" Then
r = reg(25) REM DSPI takes precision from Indirect register
Else
r = Val(q)
End If
If r >= 0 And r <= 9 Then
dpyPrecision = r
End If
updateWholeUI()
Return m + 1
End Function
Sure enough, we fish out the digit pseudo-argument from the input string. The only interesting thing is the DSPI case, where the digit isn’t a digit at all, but an uncial i. The DSPi command—pronounced “Disp-Eye” or “Display Indirect”—takes the precision from the I, or Indirect, register. Cool! So we need to gas up the dictionary with one more command string for dspcmd:
staticDelegates.Add("DSPI", AddressOf dspcmd)
for eleven so far (the command string is folded to uppercase). Now, let’s put in all the GTOx, GSBx, LBLx, STOx, and RCLx commands, where x can be any digit or any alphabetic label from A-E:
For Each i In digits.Concat(labels)
staticDelegates.Add("GTO" + i, AddressOf gtocmd)
staticDelegates.Add("GSB" + i, AddressOf gsbcmd)
staticDelegates.Add("LBL" + i, AddressOf lblcmd)
staticDelegates.Add("RCL" + i, AddressOf rclcmd)
staticDelegates.Add("STO" + i, AddressOf stocmd)
Next
That takes care of 15 x 5=75 command strings for 86 so far. You can see, here, why I fold the labels to uppercase: it’s so I can clump together GTOa (uncial a) and GTOA (capital A), for instance, with RCLA (capital A), despite the fact that there is no RCLa command. This is appropriate since the pseudo-argument gets fished out of the actual command string, and all we have to do is remember to fold to uppercase for lookup—but not for copy of the command string passed to the delegate. Another way of saying this is that ONLY the insides of command functions are case-sensitive. Another excuse for this is that it helped me avoid useless ambiguities like 10^X versus 10^x. But I already admitted it was cruft; get over it.
Did you spot the opportunity for dynamic identifiers? Here is what I wanted to write:
For Each cstr In {"GTO", "GSB", "LBL", "RCL", "STO"}
For Each i In digits.Concat(labels)
staticDelegates.Add(cstr + i, AddressOf (cstr + "cmd"))
Next
Next
In other words, I wanted to compute not just the command string, but the name of the delegate, too, to prevent the copy-and-paste error of, say, writing
staticDelegates.Add("GTO" + i, AddressOf gtocmd)
staticDelegates.Add("GSB" + i, AddressOf gtocmd)
Now this is the kind of annoying, stupid coding mistake that I know you never make, but, alas, I am not immune. It shows up like a land mine, much later, during calculator execution as subroutines mysteriously fail to return. But it is completely avoidable if we can write the desired code. So, what would that take? For one thing, the argument of AddressOf is not known at compile time, so the delegate can’t be constructed at compile time. So, isn’t it natural to call them late-bound delegates? We don’t have them yet, but scenarios like this point out the need, and we’re prototyping them for later development in VB.
Next, we need to fill up the tank with the storage-register arithmetic commands, STO+0, STO+1, and so on. These are a lot like the += , -=, *=, and /= operators in VB: they cumulatively side-effect the indicated registers by the indicated arithmetic operation taking the other argument from the X slot of the data stack:
For Each i In digits
For Each j In ops
staticDelegates.Add("ST" + j + i, AddressOf stocmd)
Next
Next
For Each j In ops
staticDelegates.Add("ST" + j + "I", AddressOf stocmd)
Next
These loops add 44 command strings, for 130 so far. We need four more case-folded indirect commands:
staticDelegates.Add("GTOI", AddressOf gtocmd)
staticDelegates.Add("GSBI", AddressOf gsbcmd)
staticDelegates.Add("RCLI", AddressOf rclcmd)
staticDelegates.Add("STOI", AddressOf stocmd)
for 134. There are exactly 100 more commands, accounting for the 236 required minus the two unimplemented (MRG and WDATA). The program does a quick check of this at the end of DelExec.Init, counting the command strings in the database and the command strings in the Dictionary, and diffing them both ways. If a string is in the database and not the dictionary, it’s unimplemented. Vice-versa, it’s an extension. See the source for all that.
Ok, now, sit down. Take a deep breath, because I am about to put on my best Halloween face and try to scare the pants off you. Remember dspcmd? Have you noticed that it is never called statically? Go ahead, to the source, find dspcmd in DelExec, highlight it with the mouse, and press Alt-F12. That will find all references to the symbol. Just its definition and its insertion into staticDelegates at command strings DSP+i and DSPI. No calls. So what’s the use of it? Answer: the REPL loop or the UI faceplate sends one of those 11 strings into the system, which looks up the delegate in the dictionary and dispatches to it through the delegate.
Ok, great, but since it’s not used directly in the simulator program, why should it be stored directly in the simulator program? Why not just read it out of the database, just like the calculator program that uses it, not to mention along with all the state variables and what not? Well, the answer is, of course we should. Just a matter of finding a way to store code in the database, that is, a way to convert code to data. Now, what I want to do is just cut the VB code and paste it into the database, and read it back in at dictionary-creation time. We call that code literals, and this scenario points out the need for it, and we’re prototyping it for later development in VB. In the mean time, we have to use more grotesque means. The only way of saving code as data in the current CLR is to disassemble it into IL, and the only ways to restore code from data is Reflection.Emit and Lightweight CodeGen (the last two links are superb; thanks, Joel Pobar). So, I wrote a little disassembler, and write back all the command functions into the database table “Disassemblies,” with foreign keys to another table called “CmdImplementations,” which stores byte-code streams for safety and redundancy. Oh, and, of course, I have a little assembler that writes the code back out into dynamic delegates in module DynExec. If you’ll forgive the length, dspcmd ends up, in “Disassemblies,” as
| 0 | Nop | |
| 1 | Call | Savex |
| 6 | Nop | |
| 7 | ldarg.0 | |
| 8 | ldc.i4.3 | |
| 9 | callvirt | get_Chars |
| 14 | stloc.1 | |
| 15 | Ldsfld | dpyPrecision |
| 20 | stloc.2 | |
| 21 | ldloc.1 | |
| 22 | Call | ToString |
| 27 | Ldstr | "i" |
| 32 | ldc.i4.0 | |
| 33 | Call | CompareString |
| 38 | ldc.i4.0 | |
| 39 | Ceq | |
| 41 | stloc.3 | |
| 42 | ldloc.3 | |
| 43 | brfalse.s | 17 |
| 45 | Ldsfld | reg |
| 50 | ldc.i4.s | 25 |
| 52 | ldelem.r8 | |
| 53 | call | Round |
| 58 | conv.ovf.i4 | |
| 59 | stloc.2 | |
| 60 | br.s | 8 |
| 62 | nop | |
| 63 | ldloc.1 | |
| 64 | call | Val |
| 69 | stloc.2 | |
| 70 | nop | |
| 71 | ldloc.2 | |
| 72 | ldc.i4.0 | |
| 73 | clt | |
| 75 | ldc.i4.0 | |
| 76 | ceq | |
| 78 | ldloc.2 | |
| 79 | ldc.i4.s | 9 |
| 81 | cgt | |
| 83 | ldc.i4.0 | |
| 84 | ceq | |
| 86 | and | |
| 87 | stloc.3 | |
| 88 | ldloc.3 | |
| 89 | brfalse.s | 6 |
| 91 | ldloc.2 | |
| 92 | stsfld | dpyPrecision |
| 97 | nop | |
| 98 | call | updateWholeUI |
| 103 | nop | |
| 104 | ldarg.1 | |
| 105 | ldc.i4.1 | |
| 106 | add.ovf | |
| 107 | stloc.0 | |
| 108 | br.s | 0 |
| 110 | ldloc.0 | |
| 111 | ret | |
To execute all calculator commands in dynamic-delegate mode, enter the “R” command at the console prompt, then execute the Diagnostics or Primes programs as before. And don’t blame me if it goes into the weeds: it’s only proof-of-concept code.