May 2006 - Posts
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.
Apologies ... the previously posted versions of IQ97 were missing a source file, specifically, {lambda}Exec, where {lambda} is the UNICODE greek character. Turns out that Winzip silently refused to copy the file, so it was just absent from the source project. The workaround was simply to comment out the three references to it that appeared in Module1.vb and delete the file from the Visual Studio project, as, no doubt, some readers discovered. The lambda execution mode is work-in-progress at this time, anyway, so there is no real harm in deleting the file. However, to prevent this kind of thing in the future, I've learned my lesson: Don't Try to be Cute When Naming Files. The drop above fixes the problem.
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.
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 nice thing about this project is that we can explore technologies quite fully, but in the context of a concrete, small, completely understandable application: a programmable calculator. Anyone who can understand a Hewlett-Packard calculator can understand VB9, LINQ, ADO.NET, comprehensions, lambda expressions, and all the rest of these beautiful new technologies. Many ‘package’ samples are too large, offering no way to understand them piece-by-piece; or too artificial or abstract, leaving too big a gap to what one wants to learn to do; or too mundane to keep you awake. IQ97 is just big enough, just small enough, concrete, and fun.
Because people reason better from the concrete to the abstract rather than the other way around, many generalizations to other kinds of projects will be quite obvious, some so obvious that they don’t need pointing out, others I’ll point out when I notice them. Here’s one right off the bat: every technique we employ to simulate the HP97 can carry over to implementing any other virtual-machine (VM) specification. Any important VMs come to mind? How about the CLR itself? IQ97 will give you lots of ideas toward creating your own CLR / CLI implementation. Later on, we will talk about a virtual machine for executing LINQ comprehensions: a VM that can be implemented over a relational database, over XML, over objects in memory, over any kind of collections.
Because IQ97 is small and constrained, it’s practical to do some crazy things with it. Now, you’re not supposed to resist, saying “why would anyone ever do THAT?” You’re supposed to say “wow, I never thought of that!” then let your imagination go, trying to think of ways to use them in your own work. For instance, I now have planned out the following FIVE execution modes
1. Delegates to static functions
a. Development Mode: Includes disassembly and dehydration into the database
b. Deployment Mode: When development is completely done, the static functions can be erased from the simulator, relying entirely on 2b
2. Delegates to dynamic functions via Lightweight Code Gen (LCG)
a. generated in parallel with disassembly and dehydration of the static functions
b. or, in a completely separate phase, dynamically rehydrated, re-assembled, and generated from the database
3. Dynamically generated lambda expressions
4. Rehydrated lambda expressions
5. Stored procedures in VB through SQLCLR in the database
The first mode is the easiest to understand and implement, and is the basis for all the other modes. In the current drop, 1a and 2a are implemented. 2B just takes courage, but will be the last thing I ever do with this. Some scaffolding for 3 is in place, and the rest are just dreams at present. Let’s dig into mode 1.
The first task is to establish some variables to hold the durable state—that is, persisted to the database—of the machine, in a module named DelExec, short for Delegate Execution. There is a 4-position data stack, a 4-position address stack for GSB (go subroutine) instructions, 4 flags, 224 program-memory slots, 26 registers, and a special last-x register for storing whatever was last in the X slot of the stack. VB9 requires the largest-index value in the declaration of the array bounds, not the length of the array, which would be one more. So an array of length 4 is declared with a 3 in round brackets, and the index will range from 0 to 3, inclusive.
Public Module DelExec
Public dStk(3) As Double ' Data Stack
Public aStk(3) As Integer ' Address Stack
Public flags(3) As Boolean
Public pgm(224) As String ' Program Memory
Public reg(25) As Double
Public lastX As Double
We’re using just ordinary VB Doubles for values in the calculator. This is not precisely correct, as the actual calculator uses binary-coded decimal (BCD) and it’s easy to detect the difference, for instance, in different overflow behavior. However, VB Doubles are good enough for ordinary circumstances, in particular, for the Diagnostics and Primes programs. Furthermore, our goal is to simulate the calculator software, not its hardware. It would be a different project to precisely simulate the calculator numerics, and even the exact microcode (someone else has written such a simulation of the microcode for the HP45). But that would take us away from the point of the exercise: to show off fancy language and programming techniques.
Other aspects of durable state include the current display precision, trig mode and display style, which are loaded with the defaults below when the calculator powers up:
Public dpyPrecision As Integer = 2
Public dpyStyle As String = "FIX"
Public trigMode As String = "RAD"
For executing stored programs, we need an instruction pointer (ip), and we shall add a couple more instrumentation variables to count the total number of instructions executed and the real time taken. These are not in the original calculator, of course, but are just too good not to add to our sim. Since we can execute the sim out of the database, these will need to be durable, too:
Public ip As Integer
Public ic As Integer
Public it As New Stopwatch REM from System.Diagnostics
We’ll need exactly these things in the database. Use “Open Table Definition” and “Show Table Data” in Visual Studio’s Server Explorer under the connection to Saturn5_002.mdf and look over the tables “Stack,” “Registers,” “Flags,” and “Controls” (I don’t see a way to copy-paste the database schema here, so just take a look). We interact with these tables using LINQ / Dataset as follows:
Sub initVmdb()
dInstructionSet = New DataPackage("InstructionSet")
dFlags = New DataPackage("Flags")
dControls = New DataPackage("Controls")
dStack = New DataPackage("Stack")
dRegisters = New DataPackage("Registers")
End Sub
DataPackage is my abstraction over the LINQ / Dataset stuff, capturing the patterns I use over and over:
Public Class DataPackage
Implements IDisposable
Public ds As DataSet
Public da As SqlDataAdapter
Public dt As DataTable
Public dq As IQueryable
Public cb As SqlCommandBuilder
Every one of my data packages will need all five of these things. I make them public for my own convenience. Yeah, I know the conventional wisdom of private fields, public properties, yadda yadda, but this is for me, only, and I don’t need to overdo that discipline. A couple of trivial constructors:
Sub New()
'Nothing interesting here, but must exist
End Sub
Sub New(ByVal nym As String)
Me.create(nym)
End Sub
The following is where the real stuff occurs: this is how we handle each table in the database. Given a name, first we make a DataSet (I frequently use the pseudonym “nym” for variables containing names, because there are too many other names of names named name in a typical system). We also must make a data adapter, then just have it query up the whole table into the DataSet, which I always want to do. We also must have a SQL command builder, even though it’s not directly used. Since each of my DataSets contains just one table, for simplicity, I can set a variable of type DataTable always to the 0-th element of ds.Tables:
Public Sub create(ByVal nym As String)
ds = New DataSet(nym)
da = New SqlDataAdapter("SELECT * FROM " + nym, conn)
cb = New SqlCommandBuilder(da) REM must exist, but not used
da.Fill(ds)
dt = ds.Tables(0)
Now, we have another pattern: In every one of my tables (with one exception, later), the first column and only the first column contains the primary key, which sometimes happens to be one of those SQL autoincrement is-identity kinds. I found the following patterns to work, partly by trial-and-error and partly by asking around. I don’t understand the TableMappings, but I need them:
Dim fudge(1) As DataColumn
fudge(0) = dt.Columns.Item(0)
dt.PrimaryKey = fudge
dq = dt.ToQueryable()
da.TableMappings.Add(nym, "Table")
End Sub
Public Function update(ByVal nym As String) As Integer
Return da.Update(ds, nym)
End Function
The rest of the class is just standard IDisposable boilerplate support for releasing non-garbage-collected resources:
Protected Overrides Sub finalize()
Me.Dispose()
MyBase.Finalize()
End Sub
Private disposedValue As Boolean = False ' To detect redundant calls
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
cb.Dispose()
ds.Dispose()
da.Dispose()
dt.Dispose()
Me.disposedValue = True
End If
' TODO: free shared unmanaged resources
End If
Me.disposedValue = True
End Sub
End Class
The next seven variables are NON-durable, and track modes in the simulator rather than any documented modes in the calculator. Numeric entry, for instance, requires us to track whether the decimal or EEX key has been pressed. This isn’t the time to get into those vagaries, the point here being that just a handful of extra variables take care of them.
Public eex(1) As Integer
Public currexp As Double = 0
Public liftP As Boolean = True ' Will next entry lift the stack?
Public traceP As Boolean = True
Public eexmode As Boolean = False
Public efrac As Boolean = False ' Entering a fractional part?
Public progP As Boolean = False
Notice the Public access for most everything. Since this application is standalone, I break things up into modules and classes just for design modularity, and access control is not something I need to worry much about, so I am completely whimsical about it.
I’ll use the term command to mean a calculator function, whether executed by clicking a button on the faceplate, or by typing a shortcut key with the faceplate up, or by typing a calculator command in the console window, or by running a stored calculator program. Every command has a static function in the DelExec module (all functions in a VB module happen to be static functions). Executing a command works by first presenting a command string to the simulator. IQ97 commands do not take arguments, but variable information is often encoded in the command string. For instance, the STO4 command means “Store a copy of the value in the X slot of the data stack into register 4.” Likewise, STO3 means “Store a copy of the value in the X slot into register 3.” Today, it would be considered a more mainstream design, perhaps, to give the destination register as an argument to a generic STO command. However, that was not quite the way Hewlett-Packard did it back then.
There is a bit of irony to the story: the way to key in the STO4 command on the calculator faceplate is first to press the STO key and then to press the 4 key. However, internally, this combination is stored as a single byte in program memory. In fact, this merged keystroke encoding was a big improvement over the ancestral HP65, which, in fact, stored commands and arguments sequentially. However, that led to twisted code as programmers strove to save precious memory.
We need a CLR type, cmdel, for command delegates and a dictionary, staticDelegates, to look up command delegates given command strings:
Delegate Function cmdel(ByVal cmd As String, ByVal ip As Integer) _
As Integer
Private staticDelegates As New Dictionary(Of String, cmdel)
So, in our sim, we pick up the STO key, then a numeric key, and build a combined command string, “STO4,” and the static function for all STO commands then fishes the register back out of the string, again. This explains why every command function takes the actual command string as an argument—it often needs to fish out hidden pseudo-argument information.
Every command function also takes an integer representing the current instruction pointer, and is responsible for returning the next value of the instruction pointer, which is almost always just one more than the current ip, exceptions including branch and subroutine-call instructions. This is another advantage of the merged keystroke encoding: since all instructions are the same length, all command functions can increment ip the same way.
So, as you can probably guess, there are 256 possible opcodes. The calculator defines 236 of them, and I implemented all but WDATA and MRG, which are for writing data to mag cards. You can see all the command strings and their key scan codes in the table “InstructionSet.” The numeric opcodes are not used in IQ97, so we don’t record or store them anywhere.
It might seem most straightforward to start off by showing the numeric-entry commands. However, they are a bit messy due to internal modalities concerning the decimal point and EEX key, so let’s delay presenting them for a bit. The numeric operation commands are a good place to start. Consider the following, which implements the plus command, whenever the + key is pressed:
Function plus(ByVal c As String, ByVal m As Integer) As Integer
Savex()
dStk(0) += dStk(1)
dStk(1) = dStk(2)
dStk(2) = dStk(3)
updateStackUI()
Return m + 1
End Function
So, clearly, dStk(0) represents the X slot of the data stack, dStk(1) the Y slot, and so on. Savex is a ubiquitous utility; most of the command functions call it:
Sub Savex()
lastX = dStk(0)
EndInput()
End Sub
which just saves the last value of the X slot of the data stack in the special register lastX, where it can be fetched with the LSTX command. EndInput interacts with the numeric input modes, so we’ll talk about it later.
UpdateStackUI is one of a bunch of functions for operating the blinking lights. We get to all that later.
So there you go: we have laid out the endoskeleton of the calculator implementation. Next installment, we’ll fill up the dictionary with command delegates and start executing functions!
I’ll call my simulator of the HP97 the IQ97 (see if you can decipher the pun). Despite my effort at self-restraint, this has bloomed into a full-blown obsessive-compulsive sample work, as I can’t stop thinking of new things to do with it. So, rather than waiting until it’s all done, since there is no way to know when that will be, I’ll blog it out as I go along. A tremendous amount of interesting technology that deserves to be shown off has already gone into it. You’ll forgive the bits that get finished or undone later in the spirit of joyful bodging, and you’ll overlook messy bits that were just hacked together to get to something more interesting. And bugs? Please post or email ‘em when you find ‘em.
You’ll need the current VB9 Customer Technology Preview (CTP). Then you will need to copy the database Saturn5_002.mdf file and Saturn5_002_log.LDF file to c:\ , the root of your C drive. (This pun is too obscure to guess: one version of the chip in the HP calculators was called the ‘Saturn’ and the rocket used to launch the Apollo-Soyuz mission was the mighty Saturn 5). Finally, you will need this zipped VB9 project directory tree. You should be able to open up the solution, hit F5, and follow along these blogs. At this point, if you have things properly installed, you will see quite a lot of console output fly by, ending with the last few lines of the calculator’s diagnostic program:
...
219 SCI
220 PRTX
221 DSP1
222 FIX
223 PRTX
224 R/S
=000:=>
Most of the console output consists in disassembled calculator-key functions, about which we will have more to say later on.
There are lots of things to do in console mode, but, for now, just type m, which stands for ‘monitor,’ and <Enter>, and you should see the faceplate of the IQ97. You have now exited console mode: all keyboard and mouse entry go into the UI until you type q at the faceplate. That will send you back to console mode. For some reason I don’t understand, you must force the UI into focus the first time you type ‘m<Enter>;’ the easiest way to do that is just click anything on the faceplate or just Alt-tab over to its window. After the first time, you can m<Enter> and q back and forth and focus will track correctly.
Hewlett-Packard’s diagnostic program is preloaded, so if you click the ‘A’ button on the extreme left center or just type a (no <Enter> on the faceplate), you should see blinking lights for about 5 to 10 seconds, halting with 0.00 in the main display at top-left; -8.889e-088 twice and 0.00 on the paper tape, right-center top; and, in the machine-state display in green, left-center top, ip = 224, cmd = R/S, ic = 1018, TIME = something reasonable around 5 to 10 seconds, and ips (instructions per second) = something reasonable around 250 or below. If you get all that, you’ve passed the basic diagnostics and you are ready to use the calculator.
The UI mode responds to mouse clicks, of course, but also to several standard computer keyboard presses, like the number keys, the arithmetic keys, <End> for the calculator ENTER key (because the computer <Enter> key automatically mouse clicks whatever button has the focus, it was difficult to use that for the calculator ENTER key), <Backspace> for CLX, A-E for the A-E keys, F for the calculator f-shift key (the glaring yellow one), and R for Rv, which rolls the stack down one.
To get off the ground, type 1 <End> 2 +, and you should see 3.00 in the main display and the X slot of the stack display center-top. The main display is always an echo of the X slot. If you just finished the diagnostics, the rest of the stack will contain Y=0.00, Z=300.00, and T=300.00. For those not familiar with the famous Hewlett-Packard Reverse-Polish Notation (RPN), it’s just postfix immediate. Numeric keys build up inputs, so, when we type the first 1, we start input mode. When we type <End>, we end input mode. Typing 2 causes an automatic push (HP calls it stack lift) of 1 into the Y slot, and puts the machine back into buildup mode . + Terminates buildup mode and immediately executes Y+XàX, dropping the stack down, autocopying T into Z. Most of the rest of the calculator functions operate this way, and a little bit of playing around will get you familiar and at-ease with it if you aren’t already.
While RPN eventually lost out to so-called ‘algebraic mode’ with parentheses in the calculator mass market, presumably because students preferred to slavishly copy formulas from textbooks to their calculators rather than to read and understand the formulas, RPN really is technically superior and much easier to grasp, not to mention much easier to edit and correct. Yes, there is a teeny-tiny learning molehill, but the benefits massively outweigh the cost, especially for anyone who has actually studied computers for a little while. Be that as it may, RPN was introduced by HP for pure expediency: it required less precious, on-board memory than the algebraic style. RPN only requires the value stack because all operations execute immediately. An algebraic-mode calculator must store a complete expression tree as it is built up, and would thus have been intolerably limited in 1976, when 25 memory cells was considered lavishly, luxuriously, many. With algebraic mode, users had to resort to pencil-and-paper to break up big formulas, while, with a little practice, an experienced RPN user could key in the most hairy formula right the first time (hint: start from the innermost subexpressions and build OUT, just as with applicative order or call-by-value in programming languages.
If you want to play with the primes program, go to the drop-down combo box just under the main display and select “Primes” (don’t bother with “Moon Lander” – it’s not working yet). Now TAB out of the combo box to remove focus from it (another UI booger – sorry). Click or type E to enter Prime’s Auto Mode, and type 100 B 200 C D to generate all primes from 100 to 200. This should go for about 20 to 40 seconds, with 4788 instructions executed at 125-250 ips, and leave results on the scrollable paper tape. If you want to heat up your computer for a few hours, type 1000000000 B 1000001000 C D and it will spit out one prime about once every 10 to 20 minutes. I suggest you leave that for overnight, though, and try out the console mode first, without the UI, because console mode has about 1000 times greater throughput.
Type q to quit the UI (you can get it back with another m<Enter>), and enter the following at the console prompt, each line terminated with <Enter> (don’t press <Enter> too early – it single-steps the program in the calculator, but, if you do, I’ll let you guess how to get started again):
| read.prim | an extended command, not really part of the HP97 command set |
| T | Turns of trace mode |
| E | Turns on auto-print mode, lest the calculator stop after each prime |
| 1 | Simulates pressing of the 1 key on the calculator |
| 0 | Yes, we have to enter each digit on its own |
| 0 | As if it were an individual calculator key press |
| 0 | Each followed by the Enter key, so, yes, console mode is clumsy |
| 0 | But it will execute much, much faster, when we run the program |
| 0 | So hang in there for four more zeros, ok? |
| 0 | Three … |
| 0 | Two … |
| 0 | One … |
| 0 | Liftoff! Yay, we’re done (and it sounds like a rocket launch) |
| B | Tells the calculator that we have the starting prime |
| 1 | Oh, no, here we go again! |
| 0 | Ok, go with me for five zeros |
| 0 | Four … |
| 0 | Three … |
| 0 | Two … |
| 0 | One … |
| 1 | A break from the monotony |
| 0 | Just three more and we’re done for good, I promise |
| 0 | But now I’m just funning with you because you got the idea |
| 0 | Way back there |
| C | Tells the calculator that we have the ending prime |
| D | Let ‘er rip! |
This should start producing primes immediately, starting with 1000000007, and ending with 1000000993, at the rate of 1 every few seconds, executing exactly 8,628,918 instructions at the rate of up to 225,000 instructions per second. If you did the same in UI mode, the number of instructions will be exactly the same, but the ips will be more like 225, taking well over one hour.
The primes program has another nifty feature: it will also factor any number in the X register if you click the A key (or type A<Enter> at the console-mode prompt, or just A at the UI). Make sure you’re in auto-print mode, that is, that you have toggled E until 1 shows in the X slot and the main display.
At the console prompt, you can also type preg to print out the registers and prst to print out the stack, or go back into trace mode with t, all followed by <Enter>, of course, which I won’t bother to mention for console mode from now on.
If something goes wrong and the program becomes unresponsive (called ‘going into the weeds’ – a metaphor from automobile racing) just kill it from Visual Studio and start it over. It’s just a blogging sample, don’t you know, and hasn’t been subjected to anything like product-level QA.
Ok, but guess what? You’ve stuck with me through all that and we are ready for our first REAL TREAT of VB9 programming! Look at the code for intercepting computer keystrokes in the UI panel. It’s in Monitor.vb and is called Monitor_KeyPress. This is the ordinary winforms event handler for KeyPress, which gets forwarded to the topmost control—the window itself—from whatever subcontrol happens to have the focus.
Private Sub Monitor_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles Me.KeyPress
Dim k = Char.ToUpperInvariant(e.KeyChar)
If k >= "0"c And k <= "9"c Then
CObj(Me).("Num" & k & "_Click")(Nothing, Nothing)
ElseIf k >= "A"c And k <= "E"c Then
CObj(Me).(k & "Key_Click")(Nothing, Nothing)
ElseIf k = "F"c Then
fShiftKey_Click(Nothing, Nothing)
ElseIf k = "+"c Then
Me.PlusKey_Click(Nothing, Nothing)
ElseIf k = "-"c Then
Me.MinusKey_Click(Nothing, Nothing)
ElseIf k = "*"c Then
Me.TimesKey_Click(Nothing, Nothing)
ElseIf k = "/"c Then
Me.DivKey_Click(Nothing, Nothing)
ElseIf k = "R"c Then
Me.RollDownKey_Click(Nothing, Nothing)
ElseIf k = "Q"c Then
fMonitor.Dispose()
fMonitor = Nothing
End If
End Sub
We’ll convert the key to uppercase and start testing its value. The first branch runs when you press any key in the 0 through 9 range, and … what’s that weirdness? Method call through dynamic Identifiers! The idea is rather than specifying the method to call for each digit key statically, like this
If k = "0"c Then
Me.Num0_Click(Nothing, Nothing)
ElseIf k = "1"c Then
Me.Num1_Click(Nothing, Nothing)
ElseIf k = "2"c Then
Me.Num2_Click(Nothing, Nothing)
...
and so on, let’s just calculate the name of the method to call from the keycode. It only works through late binding (can’t statically bind to a method name that is not known until run time), so we first must convert the method receiver to something of static type Object, that is, convert Me to an object (hopefully, this syntax noise will be eliminated in the next drop of VB9):
CObj(Me)
Then, let’s calculate the name of the button-click event-handler method to call:
"Num" & k & "_Click"
(the ampersand glues strings – and things automatically convertible to strings – together), put that method name inside parentheses to the right of the dot:
CObj(Me).("Num" & k & "_Click")
(that's the magic syntax that means “dynamic identifier!”), supply the arguments to finish our first method-call through dynamic identifier:
CObj(Me).("Num" & k & "_Click")(Nothing, Nothing)
So, pressing a digit key on the computer keyboard just forwards execution to the exact code that process a digit-key button click through the computer mouse, just as we want. There is another method call through dynamic identifiers for handling A through E. Notice we cannot make the all-too-easy typing error of getting the digit wrong in the method name, so the dynamic identifiers also give us prophylaxis against cut-and-paste coding bugs!
We could not conveniently do likewise with the arithmetic keys. While I would have liked to say
Private Sub [+_Click](...) Handles PlusKey.Click
which VB9 accepts, by the way, because of the bracket escape syntax, the CLR balks at a name such as +_Click for a method and it fails to link. But if I had been able to write that, then the four lines for processing arithmetic keyboard input would have collapsed to just one method call through dynamic identifiers.
That’s enough for today, but in future installments, you will see much more use of this feature, as well as calls through dynamic-method delegates and through lambda-expression delegates. Also in the future, I will update the IQ97 source solution as I go along, and each blog will tell you whether there is a new drop to pick up.
More Posts