The source code for MACRO SPITBOL contains extensive documentation. I have extracted the specification of the MINIMAL (Machine Independent Macro Assembly Language) and the specification of the OSINT (Operatint System INterface) and converted the plain text to HTML, resulting in what is now the “MINIMAL Reference Manual.
As part of this effort I wrote an introduction in order to give a sense of the flavor of the code. Here is that introduction:
Introduction
The implementation of MACRO SPITBOL is written in three languages: MINIMAL, C, and assembler.
The SPITBOL compiler and runtime is written in MINIMAL, a machine-independent portable assembly language.
The runtime is augmented by procedures written in C that collectively comprise OSINT (Operating System INTerface). These procedures provides such functions as input and output, system initialization and termination, management of UNIX pipes, the loading of external functions, the writing and reading of save files and load modules, and so forth.
The implementation also includes assembly code. This size of this code varies according to the target machine. About 1500 lines are needed for the x86 architecture running UNIX.
This code provides such functions as macros that define the translation of MINIMAL instructions that take more than a few machine-level instructions, support for calling C procedures from MINIMAL, for calling MINIMAL procedures from C, for creating save files and load modules, and for resuming execution from save files or load modules.
To give some idea of the flavor of the code, consider the following simple SPITBOL program that copies standard input to standard output.
loop output = input :s(loop) end
By default, the variable input is input-associated to standard input, so each attempt to get its value results in reading in a line from standard input and returning the line as a string. The read fails if there are no more lines, and succeeds otherwise.
Similarly, the variable output is output-associated with standard output, so each assignment to output causes the assigned value to be written to the standard output file.
The osint procedure for writing a line is SYSOU. It is called from within SPITBOL as part of assignment, as shown in the follwing excerpt from the MINIMAL source:
* here for output association asg10 bze kvoup,asg07 ignore output assoc if output off asg1b mov xl,xr copy trblk pointer mov xr,trnxt(xr) point to next trblk beq (xr),=b_trt,asg1b loop back if another trblk mov xr,xl else point back to last trblk .if .cnbf mov -(xs),trval(xr) stack value to output .else mov xr,trval(xr) get value to output beq (xr),=b_bct,asg11 branch if buffer mov -(xs),xr stack value to output .fi jsr gtstg convert to string ppm asg12 get datatype name if unconvertible * merge with string or buffer to output in xr asg11 mov wa,trfpt(xl) fcblk ptr bze wa,asg13 jump if standard output file * here for output to file asg1a jsr sysou call system output routine err 206,output caused file overflow err 207,output caused non-recoverable error exi else all done, return to caller
From the OSINT C code (the C procedure name starts with ‘z’ since there is intermediate code (shown below) to call from MINIMAL to C at runtime):
zysou() { REGISTER struct fcblk *fcb = WA(struct fcblk *); REGISTER union block *blk = XR(union block *); int result; if (blk->scb.typ == type_scl) { /* called with string, get length from SCBLK */ SET_WA(blk->scb.len); } else { /* called with buffer, get length from BCBLK, and treat BSBLK * like an SCBLK */ SET_WA(blk->bcb.len); SET_XR(blk->bcb.bcbuf); } if (fcb == (struct fcblk *) 0 || fcb == (struct fcblk *) 1) { if (!fcb) result = zyspi(); else result = zyspr(); if (result == EXI_0) return EXI_0; else return EXI_2; } /* ensure iob is open, fail if unsuccessful */ if (!(MK_MP(fcb->iob, struct ioblk *)->flg1 & IO_OPN)) { return EXI_1; } /* write the data, fail if unsuccessful */ if (oswrite (fcb->mode, fcb->rsz, WA(word), MK_MP(fcb->iob, struct ioblk *), XR(struct scblk *)) != 0) return EXI_2; /* normal return */ return EXI_0; }
Here is the assembly code that is used to call a C procedure from MINIMAL. The code is for 32-bit X86
and is written using NASM (Netwide Assembler) syntax.
%macro mtoc 1 extern %1 ; save minimal registers to make their values available to called procedure mov dword [reg_wa],ecx mov dword [reg_wb],ebx mov dword [reg_wc],edx ; (also reg_ia) mov dword [reg_xr],edi mov dword [reg_xl],esi mov dword [reg_cp],ebp ; Needed in image saved by sysxi call %1 ; call c interface function ; restore minimal registers since called procedure may have changed them mov ecx, dword [reg_wa] ; restore registers mov ebx, dword [reg_wb] mov edx, dword [reg_wc] ; (also reg_ia) mov edi, dword [reg_xr] mov esi, dword [reg_xl] mov ebp, dword [reg_cp] ; restore direction flag in (the unlikely) case that it was changed cld ; note that the called procedure must return exi action in eax ret %endmacro ... global sysou ; output record sysou: mtoc zysou
9 Comments
I don’t understand why NASM? I’d have thought the idea was to convert MINIMAL into the target machine’s native assembler, using a Spitbol program.
I also wonder if there could be a “portable” version made by having C as a translation target. It would look like a lot of calls to macros or inline functions, with a suitable set of #define’s.
The program is small enough that a manual translation to C or C++ is probably still of reasonable scope for a recreational project.
Paul,
Indeed, the goal is to convert MINIMAL source to native assembler, whatever ‘native’ means. In the present case, NASM is far superior to GAS, notably in its use of uniform notation for memory references, all of which must be enclosed in square brackets.
Also, the NASM macro processor is far superior to the one in GAS.
Though assembly languages are indeed “low level,” I have found that in the ten or so assemblers I have used over the years, macro processing in assembler is quite powerful. Usually an assembler processor is much more powerful than that provided by the C preprocessor.
By native assembler I just meant the one that comes on the target OS, e.g. GAS. I would have thought all asm-level macro expansion would be handled by the MINIMAL-to-asm Spitbol program. I’d have thought the Spitbol program could handle all issues like square brackets.
I wonder if you know about Parsec, the Haskell parser combinator library. It reminds me in some ways of Spitbol pattern matching:
* http://legacy.cs.uu.nl/daan/parsec.html
* http://www.haskell.org/haskellwiki/Parsec
Paul,
Re Haskell parser library, I still put my faith in Philippe’s JikesPG, arguably the best parser generator around.
thanks,dae
I live your energy! I hope you have a blast!!
Great post. I loved this: “Knowing that I am starting from zero, any progress will be great news, and it will be fun to see how far I can get. I had a similar experience working with Philippe Charles on Jikes. We started from nothing, and were able to build something that achieved some success. I hope to repeat that, and I know I will have fun trying to do so, even if I don’t succeed.”
Hi Dave, is there a link to the doc somewhere? Thank you.
What doc?
Jack,
Ah, I see you want the MINIMAL Reference Manual.
To get it, do
$ git clone http://github.com/hardbol/spitbol
and use your browser to open the file
minimal-reference-manual.html
The SPITBOL manual as well as “The Green Book” can be found in subdirectory spitbol/docs.
thanks,dave
thanks.dave