December 1986
This is one of the oldest examples of my programming that I still have. I wrote it for a college class on assembly-language programming.
The assignment was to write an assembler, in VAX 11/750 assembly language, for a subset of VAX 11/750 assembly language. I have mercifully forgotten some of the details. I think I preserved this code by downloading it to a floppy disc using a terminal program on a Macintosh, perhaps using Kermit? My memory is a little hazy.
The macros to access the file system were provided for us, by a teaching assistant named Elvis, hence the reference to “Elvis” in the comments.
Not having a VAX 11/750 on hand, I have not tested this code. I’m also not sure I still have the original input data file I used to test it. If you want to experiment with an emulator or something, go for it. I hereby place this code in the public domain.
I remember with a mix of horror and fondness the alternate feelings of exhaustion and elation I felt during the many all-nighters I spent in The College of Wooster’s Taylor Hall, in my sophomore year, writing and debugging this code, spreading out yards of tractor-feed paper printouts across the floor and crawling up and down the code like a spider, trying to get the “big picture” before going back to my terminal and staring at it with bleary eyes, marking up the code, refueling myself occasionally with soda and subs from Pizza Express, aka “Pizza Distress,” working next to- my classmates and friends John, Ken, and Bill, who were each working through this programming boot camp/hazing experience in their own way.
I’m grateful for the experience, and this was one of the programs I wrote that helped “make my bones” as a programmer. It didn’t do anything good for my eyes or my back or my wrists, though. So I’m also grateful I every day that I wake up and don’t have an assignment due for CS253!
Note that the web version of the code is a bit ugly, because my web page template imposes a strict width restriction on preformatted text, which results in the ends of some comments getting cut off (although you can scroll right to read them). Until I can fix that, I recommend reading the PDF version of this file.
; Original VAX filename: assembler.mar.paul;
; Original modification date: 18 Dec 1986
;
; X253 ASSEMBLER
; Version 3.1415926535
; By Paul Potts
;
; Notes added in 2025:
;
; The original version of this file contained tab characters and was written
; assuming 8 character tab stops. In this version of the file, I have converted
; all the tabs to the appropriate numbers of spaces using BBEdit's "Convert
; Spaces to Tabs" feature. This might render this version unreadable by the
; actual VAX assembler program it was written for; I'm not sure.
;
; I have made some additional minor fixes:
; - cleaned up spaces that should have been tabs
; - removed trailing whitespace
; - fixed a line that exceeded 80 characters
; - made the horizontal bars all 78 characters
; - made subroutine names in header comments all capitalized like This,
; not THIS or this.
;
; Note that comments are sometimes aligned inconsistently. This is because
; the DEC VT220 terminal had an 80-column display, so it was important to
; keep lines of text under this limit.
; ===========================================================================
;Pass one of the assembler
passnum: .blkb 1 ;which pass are we on?
pass1er: .blkb 1 ;was there an error in pass 1?
pass2er: .blkb 1 ;was there an error in pass 2?
crlf: .byte 13, 10 ;character codes for CR and LF
blank: .byte ^a/ / ;blank character
lc: .blkw 1 ;a word is all that is necessary
oldlc: .blkw 1 ;lc from the previous line
linenum: .blkb 1 ;the line number (maximum 255)
instr: .blkl 1 ;the instruction read
operand: .blkl 1 ;the current operand to evaluate
opcode: .blkb 1 ;byte holds the opcode
maxoperands: .blkb 1 ;maximum operands for this instr.
numoperands: .blkb 1 ;number of operands for this instr.
symboltype: .blkb 1 ;holds ascii type of symbol (a/r)
label: .blkl 1 ;entry for symbol table
lastsymbol: .blkl 1 ;used to hold last symbol for entry
decnum: .blkl 1 ;decimal number stored in ascii
inform: .blkb 1 ;internal form of a number
outnum: .blkw 1 ;to hold hex output in ascii
errorreturn: .blkb 1 ;set to 1 if error occurs
illegal: .blkb 1 ;used to flag illegal operands
legal: .blkb 1 ;flag for checkoperands
eolnflag: .blkb 1 ;set to 1 if eoln reached (char80)
counter1: .blkl 1 ;counter for searching
counter2: .blkl 1 ;another counter for searching
counter3: .blkl 1 ;and another
pointer1: .blkl 1 ;pointers for the insertion sort
pointer2: .blkl 1 ;another
pointer3: .blkl 1 ;and yet another
inline: .BYTE ^a/ /[80] ;the input buffer
outline: ;the output buffer
outcode: .blkb 18 ;locations for 7 bytes machine code
outlc: .blkb 3 ;location counter
2 ;blanks
.blkb outlinenum: .blkb 3 ;space for line number
3 ;blanks
.blkb outsymbol: .blkb 3 ;the symbol (if any)
3 ;blanks
.blkb outinstr: .blkb 3 ;the instruction read
80 ;space for the rest
.blkb
symboltable: .blkq 20 ;reserve 20 quadwords for symbols
endsymbol: .blkq 1 ;null symbol to check for overflow
database: .ascii /cmp tst gtr eql lss brb bsb rsb ret /
/mov moa add sub .by .eq .en /
.ascii
opcodetable: .byte ^x91, ^x95, ^x14, ^x13, ^x19, ^x11, ^x10
byte ^x05, ^x04, ^x90, ^x9e, ^x80, ^x82
.byte ^xfd, ^xfe, ^xff
.
maxoperdbase: .byte 2, 1, 1, 1, 1, 1, 1, 0, 0, 2, 2, 2, 2, 6, 1, 1
machine: .blkb 500 ;the storage area for machine code
entry: .blkw 1 ;the entry point for machine code
;it is an offset
message1: .ascii /WARNING: duplicate symbol found in pass one!! /
message2: .ascii /ERROR: An illegal operand was in the line below!! /
message3: .ascii /ERROR: An illegal instruction in the line below!! /
message4: .ascii /ERROR: Reference to an undefined symbol below!! /
message5: .ascii /ERROR: Decimal constant out of range below!! /
message6: .ascii /ERROR: Attempted branch to constant symbol below!!/
message7: .ascii /- The Symbol Table - /
message8: .ascii /WARNING: Symbol Table Overflow in pass one!! /
message9: .ascii /ERROR: Need a label for the .eq statement below!! /
message10: .ascii /ERROR: Symbol is referencing nothing below!! /
message11: .ascii /ERROR: Error occured in pass 1, will not run. /
message12: .ascii /ERROR: Error occured in pass 2, will not run. /
message13: .ascii /Code in memory now executing. . . /
message14: .ascii /Code in memory has returned control legally! Done./
message15: .ascii /INFO: line below doesn't display all code produced/
message16: .ascii /ERROR! Entry point following .en is illegal! /
message17: .ascii /ERROR! Too many operands for instruction below! /
1
.blkb
;============================================================================
MACRO FILE$SERVER
.JMP FR$START
$PUT: $RAB_STORE RAB=FR$RAB_OUT$, RBF=(R5), RSZ=R6
FILE=FR$RAB_OUT$
$PUT RAB
RSB$GET: $RAB_STORE RAB=FR$RAB_IN$, UBF=(R5), USZ=R6
FILE=FR$RAB_IN$
$GET RAB
RSB$ERR_OPEN: $RAB_STORE RAB=FR$OUTRAB, RBF=FR$WARNING1, RSZ=FR$LEN_P
FR=FR$OUTRAB
$PUT RABJMP FC$NMASK
$WARNING1: .ASCII /?? File not found <retry>:/
FRBYTE 10,13
.$PRMPT_OUT: .ASCII /Enter the output file name: /
FR$PRMPT_IN: .ASCII /Enter the input file name: /
FR$NAM_OUT$: .BYTE ^A/ /[16]
FR$NAM_IN$: .BYTE ^A/ /[16]
FR$LEN_P: .LONG 28
FR$LEN_I: .LONG 16
FR$OUTFAB: $FAB FAC = PUT, FNM = <SYS$OUTPUT>
FR$OUTRAB: $RAB FAB = FR$OUTFAB
FR$INFAB: $FAB FAC = GET, FNM = <SYS$INPUT>
FR$INRAB: $RAB FAB = FR$INFAB
FR$FAB_OUT$: $FAB FAC=PUT, FNA=FR$NAM_OUT$, FNS=16, ORG=SEQ, rat=cr
FR$RAB_OUT$: $RAB FAB=FR$FAB_OUT$
FR$FAB_IN$: $FAB FAC=GET, FNA=FR$NAM_IN$, FNS=16
FR$RAB_IN$: $RAB FAB=FR$FAB_IN$
FR$START: $OPEN FAB=FR$INFAB ;SYS INPUT
FR=FR$INRAB
$CONNECT RAB=FR$OUTFAB ;SYS OUTPUT
$OPEN FAB=FR$OUTRAB
$CONNECT RAB$SERVER
.ENDM FILE
;============================================================================
MACRO FILE$CREATE_FILE
.=FR$OUTRAB, RBF=FR$PRMPT_OUT, RSZ=FR$LEN_P
$RAB_STORE RAB=FR$OUTRAB
$PUT RAB=FR$INRAB, UBF=FR$NAM_OUT$, USZ=FR$LEN_I
$RAB_STORE RAB=FR$INRAB
$GET RAB=FR$FAB_OUT$
$CREATE FAB=FR$RAB_OUT$
$CONNECT RAB$CREATE_FILE
.ENDM FILE
;============================================================================
MACRO FILE$OPEN_FILE
.$NMASK: $RAB_STORE RAB=FR$OUTRAB, RBF=FR$PRMPT_IN RSZ=FR$LEN_P
FC=FR$OUTRAB
$PUT RAB=FR$INRAB, UBF=FR$NAM_IN$, USZ=FR$LEN_I
$RAB_STORE RAB=FR$INRAB
$GET RAB=FR$FAB_IN$
$OPEN FAB,FR$ESCAPE
BLBS R0JMP FR$ERR_OPEN
$ESCAPE: $CONNECT RAB=FR$RAB_IN$
FR$OPEN_FILE
.ENDM FILE
;============================================================================
READ: $RAB_STORE RAB = FR$INRAB, UBF=(R5), USZ=R6
= FR$INRAB
$GET RAB
RSB
WRITE: $RAB_STORE RAB = FR$OUTRAB, RBF=(R5), RSZ=R6
= FR$OUTRAB
$PUT RAB
RSB
;============================================================================
;THE MAIN PROGRAM LOOP
begin: .word
;set up everything
jsb initialize
#1, passnum ;pass number one
movb pass1loop: jsb getline ;fetch a line
,counter1 ;reset counter1
moval inline, #^a/;/ ;is the whole line a comment?
cmpb inline;if not, go do stuff with it
bneq checksymbol ;cycle to next line
brb pass1loop checksymbol: movl inline, label ;take 4 characters
;move past the label
jsb scanforblank ;skipblanks
jsb scanforchar , #1 ;has end of line been reached?
cmpb eolnflag;the line was blank
beql pass1loop ;call the instruction subroutine
jsb instruction ;put symbol in symboltable
jsb insertsymbol ;calculate their legality
jsb checkoperands , #^xff ;is it ".en"
cmpb opcode;if not, get another line
bneq pass1loop
=FR$RAB_IN$ ;reset to begining of file
$REWIND RAB#2, passnum ;pass number two
movb
;set up stuff for pass 2
jsb initialize2 pass2loop: jsb getline ;read line of code
,counter1 ;set up pointer
moval inline, #^a/;/ ;is the whole line a comment?
cmpb inline;if not, go do stuff with it
bneq checksymbol2 ;print the whole line
jsb bigcomment ;cycle to next line
brb pass2loop checksymbol2: movl inline, label ;take 4 characters
;move past the label
jsb scanforblank ;skipblanks
jsb scanforchar , #1 ;has end of line been reached?
cmpb eolnflag;the line was blank
beql pass2loop ;call the instruction subroutine
jsb instruction ;print the operands
jsb dumpoperands ;calculate their legality
jsb checkoperands ;was there a legal num of operands
jsb checknumoper ;put the machine code in outline
jsb dumpcode ;fill fields in line & print it
jsb dumpline , #^xff ;is it ".en"
cmpb opcode;if so, quit program
beql endloop ;repeat for another line
brw pass2loop endloop: jsb printtable ;print symbol table
, #1 ;error in pass 1?
cmpb pass1er;if so, don't execute
beql noexecute1 , #1 ;was there an error in pass 2?
cmpb pass2er;if so, don't run code
beql noexecute2 ;set entrypoint
jsb setentry , #1 ;error in the entrypoint?
cmpb pass2er;if so, don't execute
beql noexecute2 ;put code for return in memory
jsb setreturn , r5 ;print "code now executing. . ."
moval message13;go print it
jsb printmessage ;clear out offset
clrl r5 , r5 ;entry offset
movw entry, r6 ;start of machine code
moval machine, r6 ;calculate the address
addl2 r5(r6) ;go execute the code
jsb jmp finishup ;executed on successful return
noexecute1: moval message11, r5 ;message for pass1
;go print it
jsb printmessage jmp badfinishup ;quit without message
noexecute2: moval message12, r5 ;message for bad pass2
;go print it
jsb printmessage jmp badfinishup ;quit without message
;============================================================================
;This subroutine simply initializes everything that needs it
initialize: file$server ;Call the macros Elvis wrote
$create_file ;Set up an input file
file$open_file ;Get the filename and open it
file#0, blank, #0, #168, symboltable ;erase it
movc5 #0, pass1er ;default
movb initialize2: movw #0, lc ;location counter start at 0
#0, oldlc ;oldlc start at zero
movw #0, linenum ;first line
movw #0, pass2er ;default
movb
rsb
;============================================================================
;This is a generic routine for scanning through the inline. It walks
;through until it finds a character, with counter1. The second routine
;walks through until it finds a blank.
scanforblank: movb #0, eolnflag ;reset flag
, r5
moval outline, r5, r6 ;see above
subl3 counter1#1, r6
cmpl
beql eoln, r7
movl counter1;next character position
incl counter1 (r7), blank
cmpb ;repeat until found a blank
bneq scanforblank #1, counter1 ;cancel last increment
subl2 ;return
rsb
scanforparenth: movb #0, eolnflag ;reset flag
, r5
moval outliner10, r5, r6 ;use r10 as pointer now
subl3 #1, r6 ;see above
cmpl
beql eolnr10
incl (r10), #^a/(/ ;find left parenth
cmpb ;loop
bneq scanforparenth
rsb
eoln: movb #1, eolnflag ;set flag
rsb
scanforchar: movb #0, eolnflag ;reset flag
, r5 ;want the address
moval outline, r5, r6 ;how many chars over?
subl3 counter1#1, r6 ;is it the 80th char?
cmpl ;end of inline
beql eoln , r7 ;the address of inline+n
movl counter1;next character position
incl counter1 (r7), blank ;found a blank?
cmpb ;if not, we are done
beql scanforchar #1, counter1 ;cancel last increment
subl2 ;go back
rsb
scanforcomma: movb #0, eolnflag ;reset flag
, r5
moval outline, r5, r6 ;see above
subl3 counter1#1, r6
cmpl
beql eoln, r7
movl counter1;next character position
incl counter1 (r7), #^a/,/ ;compare to a comma
cmpb
bneq scanforcomma
rsb
;============================================================================
;Subroutine: Getline
;This will read in a line from a file into inline, and echo comments to the
;screen. That's all it does.
getline: movb #0, eolnflag ;eoln not found
#1, blank, blank, #80, inline ;blank inline
movc5 #^x2020, label ;blank label
movl ,r5 ;buffer
moval inline#80,r6 ;set length
movl $get ;read the line
jsb file#1, blank, #32, #80, outline ;clear outline
movc5 ;return
rsb
;============================================================================
;This routine prints out an entire-line comment from infile
bigcomment: moval inline, r5 ;print the whole line
#80, r6 ;all 80 chars
movl $put ;print comment line
jsb file;return
rsb
;============================================================================
;Subroutine: Setentry
;This code will take the value of the last symbol evaluated and set it
;as the entrypoint (the offset stored in entry.)
;search through the symboltable until you find the value of the last
;string, then move it into entry
setentry: moval lastsymbol, r10 ;search for symbol in r10
, r5 ;start of symboltable
moval symboltable, r6 ;end of symboltable
moval endsymboleloop: cmpl (r5), (r10) ;did we find symbol?
;branch if we did
beql foundit #8, r5 ;increment pointer
addl2 , r6 ;did we hit end?
cmpl r5;if not, loop
bneq eloop #1, pass2er ;set error status
movb , r5 ;illegal end entrypoint
moval message16;go print it
jsb printmessage ;return
rsb foundit: cmpb 7(r5), #^a/a/ ;is it absolute symbol
;if not, okay
bneq oksymbol #1, pass2er ;set error status
movb , r5 ;or trigger error
moval message16;go print it
jsb printmessage ;and return
rsb oksymbol: movw 4(r5), entry ;the entry offset
rsb
;============================================================================
;Subroutine: Setreturn
;this code will move an absolute jump to finishup to the point where the
;.en occurs in the code in memory. That way, if the code is permitted to
;run through until the .en, it will be able to complete execution without
;crashing.
setreturn: subw2 #1, lc ;don't ask me why it works
, r5 ;address of start of machine code
moval machine;clear out temporary storage
clrl r6 , r6 ;this is the offset
movw lc, r5 ;this is the place to put the code
addl2 r6, (r5) ;move first four bytes
movb code;return to execute code
rsb code: rsb ;this code will be copied directly
;============================================================================
;Subroutine: Instruction
;This subroutine searches through the database until it finds an instruction
;that matches the one read from the file or runs out of intructions. If
;the instruction is found to be ".eq" then the symboltype is set to
;absolute. If a symbol has not been read in an instruction error will be
;generated.
;
;Input: Nothing is passed through registers. The routines uses the global
;variables counter1 and counter2 to do its work.
;
;Output: the opcode stored in "opcode"
instruction: movl counter1, r9 ;use it as an address
(r9), instr ;move the instruction to instr
movl ;counter to find maxoperands
clrl counter2 ;counter to find insruction
clrl counter3 , r8 ;address of database start
moval database, r7 ;maxoperands database
moval maxoperdbasescandatabase: mull3 #4, counter3, r6 ;4 bytes for each entry
, r8, r5 ;calculate offset
addl3 r6, r7, counter2 ;the position of maxoperands
addl3 counter3;next position
incl counter3 , #17 ;are we out of instructions?
cmpl counter3;yes, go there
beql notfound , (r5) ;is this the instruction?
cmpl instr;haven't found it yet
bneq scandatabase , r5 ;start of opcodes
moval opcodetable#1, counter3 ;cancel the last increment
subl2 , r5 ;add in the offset
addl2 counter3(r5), opcode ;this is the opcode
movb , r7 ;the address
movl counter2(r7), maxoperands ;max operands for this instr.
movb , #^xfe ;is it the ".eq" instruction
cmpb opcode;if so, symbol is absolute
beql absolute #^a/r/, symboltype ;or mark it as relative
movb ;do skipchars
jsb scanforblank ;do skipblank
jsb scanforchar
rsbnotfound: movb #1, pass2er ;an error for pass2 also
, r5 ;illegal instruction message
moval message3;go print it
jsb printmessage #0, maxoperands ;no operands allowed
movb #0, opcode ;error code
movb ;do skipchars
jsb scanforblank ;do skipblank
jsb scanforchar
rsbabsolute: cmpw #^x2020, label ;is it blank
;illegal use of ".eq"
bneq legal2 , r5 ;call message 9
moval message9;go print it
jsb printmessage legal2: movb #^a/a/, symboltype ;symbol is absolute
;do skipchars
jsb scanforblank ;do skipblank
jsb scanforchar ;return to main
rsb
;============================================================================
;Subroutine: Checkoperands
;
;This subroutine checks the operand for type and branches to one of the
;following routines to evaluate it: literal, register, deferred,
;displacement, or relative. (or byte, if it is a constant with no #)
;
;Input: the inline buffer, and the pointer to it counter1. This points
;to the next character examined after the instruction has been read
;
;Output: none
checkoperands: movb #0, numoperands ;no operands found yet
, oldlc ;hold the previous lc
movw lc;clear it
clrl r7 , r7 ;temporary for overflow
movb opcode, #^xfc ;is opcode assembly directive?
cmpl r7;if so, don't increment
bgtr no_opcode , #0 ;is opcode an error?
cmpb opcode;if so, don't increment
beql no_opcode , #2 ;are we on pass 2
cmpb passnum;put it in memory
beql jpoke_opcode ;or else increment lc
incw lc no_opcode: cmpb eolnflag, #1 ;has end of line been reached?
;no operands
beql jnomore , r10 ;temporary pointer
movl counter1(r10), #^a/;/ ;reached a comment?
cmpb ;if so, no more operands
beql jnomore ;clear decimal representation
clrl decnum #0, errorreturn ;no error yet
movb ;don't execute these branches
brb j_operandloop jpoke_opcode: brw poke_opcode ;go put opcode in memory
jnext: brw next ;indirect branch
jnoneofthem: brw noneofthem ;another one
jnomore: brw nomore ;another one
jregister: brw register ;yet another
j_operandloop: movw lc, r9 ;temporary storage
operandloop: movl counter1, r10 ;temporary pointer
#0, legal ;preset flag
movb (r10), #^a/#/ ;is it a pound sign?
cmpb ;means literal mode
beql jliteral ;reenter code here
brb reenter1 jliteral: jsb literal ;indirect jump
reenter1: cmpb illegal, #1 ;was this a good operand?
;if so, find next one
beql jnoneofthem , r9 ;test lc
cmpw lc;loop if just found good
bgtr jnext , #1 ;did it fall through
cmpb legal;branch if it did
beql jnomore
(r10), #^a/a/ ;is it >= a?
cmpb ;jump down
blss testregdefer (r10), #^a/z/ ;is it <= z?
cmpb ;jump down
bgtr testregdefer
(r10), #^a/r/ ;does it begin with 'r'
cmpb ;go test register mode
jsb jregister , #1 ;was this a good operand?
cmpb illegal;if so, find next one
beql symbol , r9 ;test lc
cmpw lc;loop if just found good
bgtr jnext
symbol: jsb byterelative ;go test byte relative mode
, #1 ;was this a good operand?
cmpb illegal;if so, find next one
beql jnoneofthem , r9 ;test lc
cmpw lc;loop if just found good
bgtr next
testregdefer: cmpb (r10), #^a/(/ ;is it left parenthesis
;if not check the last one
bneq testbytedisp ;go check it out
jsb regdefer , #1 ;was this a good operand?
cmpb illegal;if so, find next one
beql noneofthem , r9 ;test lc
cmpw lc;loop if just found good
bgtr next
testbytedisp: cmpb (r10), #^a/0/ ;see if byte>=0
;no
blss noneofthem (r10), #^a/9/ ;see if byte<=9
cmpb ;no
bgtr noneofthem ;go check for this mode
jsb bytedisp , #1 ;was this a good operand?
cmpb illegal;if so, find next one
beql noneofthem , r9 ;test lc
cmpw lc;loop if just found good
bgtr next
testbyteconst: movb #0, legal ;preset flag
;go test for a byte
jsb byteconst , #1 ;was this a good operand?
cmpb illegal;if so, find next one
beql noneofthem , r9 ;test lc
cmpw lc;loop if just found good
bgtr next #1, legal ;did it fall through
cmpb ;branch if so
beql next ;or else crash with error
brb noneofthem
next: addb2 #1, numoperands ;we have found another operand
, r9 ;reset the location counter
movw lc;move to next operand
jsb scanforcomma ;find it
jsb scanforchar , #1 ;was eoln reached
cmpb eolnflag;if it was, no more on line
beql nomore , r10 ;update pointer
movl counter1(r10), #^a/;/ ;or a comment found? Then
cmpb ;no more on this line.
beql nomore ;or else go look for more
brw operandloop
noneofthem: movb #1, pass2er ;or else set error flag
, r5 ;illegal operand message
moval message2;print it
jsb printmessage nomore: rsb ;no more operands
joperandloop2: brw operandloop ;way station
;============================================================================
;Subroutine: Checknumoper
;This routine compares the numoperands against maxoperands for this
;instruction and calls an error if numoperands is greater than maxoperands.
checknumoper: cmpb pass2er, #1 ;already flagged
;an error?
beql ignore , maxoperands ;check it
cmpb numoperands;call error
bgtr opnumer ;else return
rsb opnumer: movb #1, pass2er ;pass 2 error occurred
, r5 ;opnum error
moval message17;go print it
jsb printmessage ignore: rsb ;return
;============================================================================
;This subroutine moves a variable-length comment from the input line to
;the next available field in the output line
comment: moval outline, r8 ;use to calculate eoln
, r9 ;use to calculate eoln
moval symboltablecommentloop: movb (r5), (r6) ;move comment field
, r8, r7 ;has eoln been reached
subl3 r5#1, r7 ;if it has, go down
cmpl ;to outofroom
beql outofroom , r9, r7 ;has eoln been reached
subl3 r6#1, r7 ;if it has, go down
cmpl ;to outofroom
beql outofroom ;move over in inline
incl r5 ;move over in outline
incl r6 ;loop around
brb commentloop outofroom: rsb ;return from the dumpoperands call
;============================================================================
;Subroutine: Dumpoperands
;This will dump operands pointed to by counter1 into the next available
;position in the outline pointed to by counter 2. These counters are
;not updated. Instead, r5 and r6 are used as local variables.
;The routine loops until EOLN.
dumpoperands: moval outinstr, r10 ;hold address temporarily
r10, #6, counter2 ;pointer to outline
addl3 , r5 ;index to inline
movl counter1, r6 ;index to outline
movl counter2nextoperand: cmpb (r5), #^a/;/ ;rest of line a comment?
;if so, go deal with it
beql comment copyloop: movb (r5), (r6) ;copy char from inline to
;outline
incl r5 ;next available space
incl r6 , r8 ;eoln for inline
moval outline, r9 ;eoln for outline
moval symboltable, r8, r7 ;has eoln been reached
subl3 r5#1, r7 ;if it has, go down
cmpl ;to done
beql done , r9, r7 ;check eoln for outline
subl3 r6#1, r7 ;also, branch if it
cmpl ;has been reached.
beql done (r5), #^a/,/ ;or a comma?
cmpb ;include comma in operand
beql includecomma (r5), #^a/ / ;is there a blank reached?
cmpb ;if not, next char
bneq copyloop ;jump down
brb skipcomma includecomma: movb (r5), (r6) ;move last character
skipcomma: brw nextoperand ;do more
done: rsb ;return to mainloop
;============================================================================
;Subroutine: Poke_opcode
;this subroutine will place the opcode in memory
;it is not called by a jump subroutine but a branch,
;and also returns by a branch.
poke_opcode: moval machine, r6 ;start of machine code
;clear out temporary
clrl r7 , r7 ;store offset
movw lc, r6 ;add in the offset
addl r7, (r6) ;move it into machine code
movb opcode;increment for pass 2
incw lc ;return to checkoperands
brw no_opcode
;============================================================================
;subroutine to handle literal mode
;this routine has been debugged
literal: ;this will handle literal mode
#0, illegal ;operand legal
movb r10 ;move past number sign
incl (r10), decnum ;move entire literal
movl ;convert ascii to inform
jsb convertd #1, errorreturn ;was there an error?
cmpb ;if not, continue
bneq constantok #1, illegal ;flag for illegal operand
movb #1, r10 ;restore pointer
subl2 ;and die
rsb constantok: cmpb passnum, #2 ;are we on pass 2?
;if so, go handle value
beql pass2lit , #^xfb ;is it a ".by" or ".eq"
cmpb opcode;if so, takes no space
bgtr notliteral ;takes at least one byte
incw lc ;must compare as longwords
clrl r5 , r5 ;put inform in r5
movb inform, #63 ;check the literal
cmpl r5;too big to fit in one byte
bgtr toobig #1, r10 ;restore pointer
subl2 ;return
rsb notliteral: cmpb opcode, #^xfe ;is it the ".eq"
;if so, don't increment
beql not_eq ;takes at least one byte
incw lc not_eq: subl2 #1, r10 ;restore pointer
;return
rsb toobig: subl2 #1, r10 ;restore pointer
;takes another byte
incw lc ;go back to checkoperand
rsb pass2lit: cmpb opcode, #^xfe ;is it the '.eq' instr
;if so, branch
beql liteq ;must compare longword
clrl r5 , r5 ;hold for a second
movb inform, #63 ;too big to fit in one byte?
cmpl r5;if so, branch down
bgtr pass2toobig , r6 ;start of machine code
moval machine;clear out temporary
clrl r7 , r7 ;store offset
movw lc, r6 ;add in the offset
addl r7, (r6) ;move it into machine code
movb inform;increment for pass 2
incw lc ;return
rsb pass2toobig: moval machine, r6 ;start of machine code
;clear out temporary
clrl r7 , r7 ;store offset
movw lc, r6 ;add in offset
addl2 r7#^x8f, (r6) ;this is immediate mode
movb ;next byte
incl r6 , (r6) ;this is the literal value
movb inform#2, lc ;update lc
addw2 ;return for more
rsb liteq: moval symboltable, r5 ;start of symboltable
, r6 ;end of symboltable
moval endsymbollloop: cmpl (r5), label ;did we find symbol?
;if so, found it
beql lfoundsym #8, r5 ;increment pointer
addl2 , r6 ;did we hit end?
cmpl r5;loop around
bneq lloop #1, pass2er ;pass 2 error occured
movb #1, illegal ;tell checkoperands
movb , r5 ;undefined symbol reference
moval message4;go tell user
jsb printmessage ;or else return
rsb lfoundsym: addl2 #4, r5 ;value of label
, (r5) ;zero for first byte
movb inform;increment to middle position
incl r5 #0, (r5) ;middle byte to zero also
movb ;most significant byte
incl r5 #0, r5 ;msb set to zero
movb #1, legal ;flag routine to fall through
movb ;return without changing lc
rsb
;============================================================================
;Subroutine to handle byte relative mode
byterelative: ;In pass 2 this routine will look up the symbol to see
;if it exists. In pass 1 this routine will allow two
;bytes for this addressing mode for a word offset, since
;the program can be a maximum of 500 bytes long.
;make sure that r10 points to the start of the symbol!
(r10), lastsymbol ;the last symbol of
movl ;the program
#0, illegal ;reset flag
movb , r10 ;work with this
moval lastsymbol#^a/ /, 3(r10) ;set rightmost char to blank
movb , #2 ;are we on pass 2?
cmpb passnum;if so, branch down
beql pass2byterel , #1 ;is it a branch?
cmpb maxoperands;if no, add 2 to lc
bgtr notbranch #1, lc ;branch takes one byte
addw2 ;return
rsb notbranch: addw2 #2, lc ;requires 2 more bytes
;short, wasn't it!
rsb pass2byterel: moval symboltable, r5 ;start of symboltable
, r6 ;end of symboltable
moval endsymbolsloop: cmpl (r5), (r10) ;did we find symbol?
;if so, found it
beql foundsym #8, r5 ;increment pointer
addl2 , r6 ;did we hit end?
cmpl r5;loop around
bneq sloop #1, pass2er ;pass 2 error occured
movb #1, illegal ;tell checkoperands
movb , r5 ;undefined symbol reference
moval message4;go tell user
jsb printmessage ;or else return
rsb foundsym: cmpb maxoperands, #1 ;is it a branch instruction
;skip check for absolute
beql noabsolute 7(r5), #^a/a/ ;is it an absolute symbol?
cmpb ;if so, treat it differently
beql jabsymbol noabsolute: cmpb 7(r5), #^a/r/ ;is it an relative symbol?
;if so, not legal branch
beql goodsymbol , r5 ;call error message
moval message6;go print it
jsb printmessage #1, illegal ;alert checkoperands
movb ;go back to checkoperands
rsb jabsymbol: brw absymbol ;way station
goodsymbol: ;this will always be a branch displacement to calculate
, #1 ;is it a branch
cmpb maxoperands;if not, use different mode
bgtr goodsymbol2 #1, lc ;update lc first
addw #4, r5 ;point to value of symbol
addl2 r8 ;clear it out
clrl (r5), r8 ;the symbol location
movw ;storage for lc
clrl r6 , r6 ;move it in
movw lc, r8, r7 ;r7 holds the offset
subl3 r6;now put it in memory at the proper location
, r6 ;start of machine code
moval machine#1, lc ;restore value of lc
subw2 ;clear it out
clrl r5 , r5 ;r6 holds offset
movw lc, r6 ;r6 points to spot in code
addl2 r5, (r6) ;put the offset in memory
movb r7#1, lc
addw2 ;return for more
rsb goodsymbol2: ;this will always be a branch displacement to calculate
#1, lc ;update lc first
addw #4, r5 ;point to value of symbol
addl2 r8 ;clear it out
clrl (r5), r8 ;the symbol location
movw ;storage for lc
clrl r6 , r6 ;move it in
movw lc, r8, r7 ;r7 holds the offset
subl3 r6;now put it in memory at the proper location
, r6 ;start of machine code
moval machine#1, lc ;restore value of lc
subw2 ;clear it out
clrl r5 , r5 ;r6 holds offset
movw lc, r6 ;r6 points to spot in code
addl2 r5#^xaf, (r6) ;put in addressing mode
movb #1, r6 ;next byte
addl2 , (r6) ;put the offset in memory
movb r7#2, lc ;2 bytes for location
addw2 ;return for more
rsb absymbol: clrl r6 ;temporary
, r7 ;the start of machine code
moval machine, r6 ;the offset
movw lc, r7 ;r7 is the actual address
addl2 r6#4, r5 ;update pointer
addl2 #^x8f, (r7) ;move addressing mode
movb #1, r7 ;next memory location
addl2 (r5), (r7) ;throw thing in as longword
movb #2, lc ;now update
addw2 ;back for more
rsb
;============================================================================
;Subroutine to handle register mode
;If the convert routine returns an error, then the string must have
;been alphabetical (byte relative mode) and it will return.
;This routine has been debugged
register: incl r10 ;move past the "r"
#0, illegal ;default is legal
movb ;first two bytes
clrl decnum (r10), decnum ;move the register number
movw ;go convert it
jsb convertd , #1 ;did an error occur
cmpb errorreturn;if so, must not have been
beql illegalreg ;register mode
, #9 ;not allowed registers>9
cmpb inform;illegal register reference
bgtr illegalreg , #2 ;are we at pass 2?
cmpb passnum;if so, write code
beql pass2reg #1, lc ;register mode takes 1 byte
addw2 ;or else return
rsb pass2reg: clrb r5 ;clear out to put code in
#^x50, r5 ;set high nibble to 5
addb2 , r5 ;add in register number
addb2 inform, r6 ;store address
moval machine;clear temporary
clrl r7 , r7 ;hold the offset
movw lc, r6 ;add in the offset
addl2 r7, (r6) ;move it into machine code
movb r5;next available byte
incl lc ;finished register mode
rsb illegalreg: movb #1, illegal ;set for illegal
#1, r10 ;return pointer to previous
subl2 ;return to main
rsb
;============================================================================
;Subroutine to test register defered mode
;since they both check the format Rx, and both take up the same space, I
;simply call register mode to verify the correctness of the register
regdefer: incl r10 ;skip over the left parenth
;go check register
jsb register , #1 ;are we on pass 1
cmpb passnum;if so, don't write code
beql regdeferer , #1 ;was register number legal
cmpb illegal;if not, return
beql regdeferer #1, lc ;decrement lc for a moment
subw2 ;to put code in
clrb r5 #^X60, r5 ;set high nibble to 6
addb2 , r5 ;add in the register number
addb2 inform, r6 ;store address
moval machine;clear out temporary register
clrl r7 , r7 ;hold the offset
movw lc, r6 ;add in the offset
addl2 r7, (r6) ;move it into machine code
movb r5;reset lc to updated state
incw lc regdeferer: rsb ;go back to main
;lc was already incremented
;for pass one
;============================================================================
;Subroutine to test byte displacement mode
;since they both check the constant, I first call literal mode and then
;call register mode to see if it is okay.
jbad_disp: brw bad_disp ;relative branch
jbad_disp2: brw bad_disp2 ;another
bytedisp: movb #0, illegal ;call it legal for now
(r10), decnum ;move entire literal
movl ;convert ascii to inform
jsb convertd r10, pointer3 ;hold place in line
movl , #1 ;was there an error
cmpb errorreturn;illegal displacement
beql jbad_disp ;go skip to left parenth
jsb scanforparenth , #1 ;did we hit eoln
cmpb eolnflag;if so, bad mode
beql jbad_disp ;clear for storage
clrl r5 r8 ;clear for storage
clrl , r8 ;value of displacement
movb informr10 ;skip over the left parenth
incl r10 ;move past the "r"
incl ;first two bytes
clrl decnum (r10), decnum ;move the register number
movw ;go convert it
jsb convertd , #1 ;did an error occur
cmpb errorreturn;if so, must not have been
beql jbad_disp2 ;good mode
, #9 ;not allowed registers>9
cmpb inform;illegal register reference
bgtr jbad_disp2 , #1 ;was register number legal
cmpb illegal;if not, return
beql bad_disp , #1 ;are we on pass 1
cmpb passnum;if so, don't write code
beql pass1bytedisp r8, #64 ;is it in range 1-63
cmpl ;if 1-63, can use byte
blss bytedispok ;or else use word
brw worddisp bytedispok: clrl r7 ;to put code in
#^xa0, r7 ;set high nibble to a
addb2 , r7 ;add in the register number
addb2 inform, r5 ;store address
moval machine;clear out temporary register
clrl r6 , r6 ;hold the offset
movw lc, r5 ;add in the offset
addl2 r6, (r5) ;move it into machine code
movb r7;now move to next byte
incl r5 r8, (r5) ;the byte offset value
movb ;update the lc now
brw pass1bytedisp worddisp: clrl r7 ;to put code in
#^xc0, r7 ;set high nibble to c
addb2 , r7 ;add in the register number
addb2 inform, r5 ;store address
moval machine;clear out temporary register
clrl r6 , r6 ;hold the offset
movw lc, r5 ;add in the offset
addl2 r6, (r5) ;move it into machine code
movb r7;now move to next byte
incl r5 r8, (r5) ;the byte offset value
movb ;move to next byte in code
incl r5 #0, (r5) ;high byte of word offset
movb pass1bytedisp: addw2 #2, lc ;increment lc by two
r8, #64 ;is it too big for 1 byte
cmpl ;if 1-63, okay
blss notbigdisp #1, lc ;or else reserve more space
addw2 notbigdisp: movl pointer3, r10 ;restore old line pointer
;return to checkoperands
rsb bad_disp: movl pointer3, r10 ;restore old line pointer
;go back
rsb bad_disp2: moval message2, r5 ;illegal operand message
;go print it
jsb printmessage #1, illegal ;might have been literal
movb , r10 ;restore old line pointer
movl pointer3#1, legal ;flag for legal but no lc+
movb ;return
rsb
;============================================================================
;Subroutine: Byteconst
;Subroutine to test if it is a byte without a # before it, used for
;the opcode .by. It checks for a valid number.
;this form can also be used for the opcode .eq.
byteconst: movb #0, illegal ;default is legal
, #^xfd ;is it a ".by" or ".eq"
cmpb opcode;if not, return
blss notbyte (r10), decnum ;move the ascii number
movl ;convert to inform
jsb convertd #1, errorreturn ;error occured?
cmpb ;return if so
beql notbyte , #2 ;are we on pass 2?
cmpb passnum;if so, store the byte
beql storebyte , #^xfe ;is it ".eq"
cmpb opcode;if so, don't increment
beql noincrement ;else reserve 1 byte
incw lc noincrement: rsb
storebyte: cmpb opcode, #^xfe ;is it ".eq"
;if so, branch
beql bceq ;use as offset
clrl r6 , r6 ;hold offset
movw lc, r5 ;pointer to machine code
moval machine, r5 ;put the byte here
addl2 r6, (r5) ;move it
movb inform;update to next location
incw lc notbyte: rsb ;go back
bceq: moval symboltable, r5 ;start of symboltable
, r6 ;end of symboltable
moval endsymbolbcloop: cmpl (r5), label ;did we find symbol?
;if so, found it
beql bcfoundsym #8, r5 ;increment pointer
addl2 , r6 ;did we hit end?
cmpl r5;loop around
bneq bcloop #1, pass2er ;pass 2 error occured
movb #1, illegal ;tell checkoperands
movb , r5 ;undefined symbol reference
moval message4;go tell user
jsb printmessage ;or else return
rsb bcfoundsym: addl2 #4, r5 ;value of label
, (r5) ;zero for first byte
movb inform;increment to middle position
incl r5 #0, (r5) ;middle byte to zero also
movb ;most significant byte
incl r5 #0, (r5) ;msb set to zero
movb #1, legal ;set legality flag
movb ;return without changing lc
rsb
;============================================================================
; Subroutine: Insertsymbol
; This subroutine will insert a symbol into the symbol table, or call an
; error if one of the following conditions occurs:
; -a duplicate symbol is found
; -symbol table overlow occurs
;
; Input:
; -The symbol stored in label, with the high byte
; set to blank.
; -The current location counter or value to store in r6
; -the type (absolute or relative) stored in symboltype
;
quitnow: rsb ;return if no symbol exists
insertsymbol: cmpb label, #^a/a/ ;is there a symbol?
;if not, return
blss quitnow , blank ;is it a blank?
cmpb label;this should filter bad symbols
beql quitnow , #^a/z/ ;is it greater than z
cmpb label;ignore it if so
bgtr quitnow , r5 ;address of endsymbol
moval endsymbol#0, symboltable ;is the first symbol empty
cmpl ;no?
bneq case2 , symboltable ;otherwise throw it in
movl label, #^a/a/ ;is it absolute?
cmpb symboltype;if so, jump
beql absolutesym1 , symboltable+4 ;into next word
movw lc;skip it
brb skipdown1 absolutesym1: movb #0, symboltable+4 ;absolute value
skipdown1: movb symboltype, symboltable+7 ;highest byte
;done
rsb case2: moval symboltable, pointer1 ;pointer is index
searchloop: movl pointer1, r6 ;temporary
#4, label, (r6) ;compare character string
cmpc ;a duplicate symbol found
beql duplicate (r6), #0 ;empty spot found?
cmpl ;add to end of table
beql addtoend #4, label, (r6) ;compare character string
cmpc ;insert behind this one
blss insert #8, pointer1 ;add to the end
addl ;return to keep looking
brb searchloop
insert: movl pointer1, r6 ;need the address
movetoend: addl2 #8, r6 ;move to next symbol
(r6), #0 ;is it empty?
cmpb ;move to end of table
bneq movetoend
, r5 ;is it end of symboltable
cmpl r6;if so, print overflow message
beql overflow #8, r6, r7 ;r7 points to last symbol
subl3 movq (r7), (r6) ;move whole symbol down
movedown: subl2 #8, r7 ;back up and do it again
#8, r6 ;move to this point
subl2 , r10 ;user temporarily
moval symboltable, r10 ;have we moved above top?
cmpl r7;if so, insert here
blss addtoend movq (r7), (r6) ;move the next one
, pointer1 ;until there are no more
cmpl r6;repeat if not done
bgtr movedown addtoend: cmpl r6, r5 ;is it end of symboltable
;if so, print overflow message
beql overflow , (r6) ;otherwise throw it in
movl label#4, r6 ;count over 4 bytes
addl2 , #^a/a/ ;is it absolute?
cmpb symboltype;if so, jump
beql absolutesym2 , (r6) ;into next word
movw lc;go down
brb skipdown2 absolutesym2: movb #0, (r6) ;absolute value
skipdown2: addl2 #3, r6 ;highest byte of record
, (r6) ;highest byte
movb symboltype;procedure is done
rsb duplicate: moval message1, r5 ;flag for an error
;print the error
jsb printmessage ;return to main procedure
rsb overflow: moval message8, r5 ;message 8 to call
;go print the message
jsb printmessage ;return to main program
rsb
;============================================================================
;Subroutine: Printmessage
;
;This routine prints an appropriate error/informative message
;
;Input: the address of the message to print stored in r5
;Output: the message to the output file
printmessage: cmpb passnum, #1 ;are we on pass 1?
;if so, go down
beql filterprint #50, r6 ;messages 50 chars long
movl $put ;write to file
jsb file;return from this routine
rsb filterprint: ;this portion will print certain messages
;that may have occured in pass one
, r6 ;redefined symbol
moval message1, r6 ;was it this one?
cmpl r5;if so, okay
beql printit , r6 ;symbol overflow
moval message8, r6 ;was it this one
cmpl r5;if so, okay
beql printit ;or else return
rsb printit: movb #1, pass1er ;pass 1 error
#50, r6 ;50 chars long
movl $put ;print line
jsb file;return
rsb
;============================================================================
;Subroutine: Dumpline
;
;this subroutine fills all the fields in the outline for the listing file
;and sends it to be printed.
dumpline: incb linenum ;increment linenum
, inform ;the line number
movb linenum;convert linenum
jsb dconvert , outlinenum ;print linenum
movl outnum+1, inform ;hi byte
movb oldlc;convert hi byte
jsb hconvert , outlc ;hi bytes' ascii
movw outnum, inform ;treat in 2 parts
movb oldlc;convert to hex
jsb hconvert , outlc+2 ;high bytes' ascii
movw outnum, outsymbol ;the symbol
movl label, outinstr ;the instruction
movl instr, r5 ;buffer
moval outline#80, r6 ;set length
movl $put ;write line to file
jsb file;return
rsb
;============================================================================
;Subroutine: Dumpcode
;This routine will print the machine code in hex into the outline.
dumpcode: clrl pointer2 ;this will hold number of bytes
, lc, pointer2 ;produced by this line
subw3 oldlc, #6 ;were there more than six?
cmpb pointer2;return if too many
bgtr toomany , #0 ;was any code produced?
cmpb pointer2;if no, do nothing
beql donothing +15, r7 ;rightmost code byte
moval outcode#0, pointer3 ;count number of bytes done
movl , r8 ;the start of machine code
moval machiner9 ;the offset
clrl , r9 ;where does the code start
movw oldlcr8, r9 ;the position of the code
addl2 cloop: addl2 #1, pointer3 ;do the next byte
(r9), inform ;move binary byte to inform
movb ;convert it to hex
jsb hconvert , (r7) ;move ascii bytes to outline
movw outnum#3, r7 ;move to the left 3 characters
subl2 , r5 ;temporary storage
movl pointer2, r6 ;temporary storage
movl pointer3r9 ;next byte of code
incl , r6 ;are we done
cmpl r5;if not, loop
bneq cloop
rsbtoomany: moval message15, r5 ;call too many bytes message
;go tell the user
jsb printmessage donothing: rsb ;return to pass 2
;============================================================================
;Subroutine: Printtable
;This subroutine prints the symbol table in columns. The table is already
;in sorted order. It uses input and output to the file macros.
printtable: moval message7, r5 ;print message 7
;-symbol-table-
jsb printmessage , r7 ;r7 indexes table
moval symboltable(r7), #0 ;any symbols?
cmpb ;if so, start looping
bneq repeat ;or else return
rsb
repeat: moval outline, r8 ;pointer to outline
(r7), #0 ;is the symbol null
cmpl ;quit if so
beql exitloop #1, blank, #32, #80, outline ;clear outline
movc5 (r7), outline ;put symbol in outline
movl #4, r7 ;next table entry
addl2 #8, r8 ;position in outline
addl2
2(r7), inform ;send byte to convert
movb ;convert it
jsb hconvert , (r8) ;put the hex digits
movw outnum#2, r8 ;move 2 spaces over
addl2
1(r7), inform ;repeat for the
movb ;second byte of
jsb hconvert , (r8) ;location counter/ value
movw outnum#2, r8
addl2
(r7), inform ;do for the third
movb ;byte--don't have to
jsb hconvert , (r8) ;increment after
movw outnum#6, r8 ;move over 4 blanks
addl2 #3, r7 ;move over 3 in inline
addl2
(r7), (r8) ;the symboltype
movb , r5 ;buffer
moval outline#20, r6 ;set length
movl $put ;write line to file
jsb file#1, r7 ;next symbol
addl2
, r9 ;need the address
moval endsymbol, r9 ;are we done?
cmpl r7;no, go continue looping
bneq jrepeat exitloop: rsb ;yes, we are done
jrepeat: brw repeat ;this is indirect jump
;============================================================================
;Subroutine: Convertd
;This subroutine converts a decimal number stored as up to 3 ascii numbers
;to internal unsigned binary number, in the range 0-255. This rountine
;uses registers r5 and r6 internally
;
; Input: the number stored in ascii in decnum
;
; Output: the number in binary stored in inform
;
; Sample call: jsb convertd
convertd: clrl r6
;clear storage
clrl r5 +1, #^a/0/ ;test right digits
cmpb decnum;if punctuation, reset to zero
blss reset1 test3: cmpb decnum+2, #^a/0/ ;test 3rd digit
;if punctuation, reset it too
blss reset2 ;or don't reset values
brb no_reset reset1: movb #0, decnum+1 ;reset the middle digit
;test the third digit
brb test3 reset2: movb #0, decnum+2 ;reset the right digit
no_reset: clrb errorreturn ;clear error status
+1, #0 ;is there a second digit?
cmpb decnum;if not, shift digit right 2
beql shift2 +2, #0 ;is there a third digit?
cmpb decnum;if not, shift digits right one
beql shift1 ;skip down
brb reduce shift2: movb decnum, decnum+2 ;move left digit to middle
#^x30, decnum ;fill with ascii zeroes
movb #^x30, decnum+1 ;prevents subtraction underflow
movb ;reduce the digit
brb reduce shift1: movb decnum+1, decnum+2 ;move middle digit right
, decnum+1 ;move left digit right
movb decnum#^x30, decnum ;erase empty digit
movb reduce: subb2 #^x30, decnum+2 ;convert ascii to digit
#^x30, decnum+1
subb2 #^x30, decnum
subb2 +2, #9 ;test the value
cmpb decnum;go print an error
bgtr er1 , #100, r6 ;use r6 as accumulator
mull3 decnum#^xffffff00, r6 ;get rid of extraneous
bicl2 +1, #10, r5 ;temporary
mulb3 decnum,r6 ;put tens digit in
addb2 r5
clrl r5+2, r5 ;extract ones digit
movb decnum, r6 ;add it in
addl2 r5, inform
movb r6;go back to main program
rsb er1: movb #1, errorreturn ;set error flag
;return
rsb
;============================================================================
;Subroutine: hconvert
;This subroutine takes an internal number and converts it to two ascii
;codes of its hexadecimal representation.
;
;Input: The number stored in binary in iform
;
;Output: outnum contains two bytes - hex ascii characters
;
;Sample call: jsb hconvert
hconvert: clrl r6 ;hi nibble
;low nibble
clrl r5 ;blank this thing
clrl outnum #-4, inform, r5 ;put left nibble into r5
ashl #^xfffffff0, r5 ;blank out extraneous
bicl , #9 ;is digit in range 0..9?
cmpb r5;must be a letter
bgtr hcase2 #^x30, r5 ;or else convert it
addb2 ;skip to low byte evaluation
brb lobyte
hcase2: addb2 #87, r5 ;add 87 to convert
lobyte: bicb3 #^xf0, inform, r6 ;mask high bits
, #9 ;is digit in range 0..9?
cmpb r6;must be a letter
bgtr hcase4 #^x30, r6 ;ascii value in r6
addb2 ;skip over next command
brb notascii
hcase4: addb2 #87, r6 ;convert to an ascii letter
notascii: movb r6, outnum+1 ;output ascii number
, outnum ;the high order nibble
movb r5;return
rsb
;============================================================================
;
;This subroutine takes an internal representation and converts it into three
;decimal ascii digits with a leading zero.
;
;
;input: the byte in stored in inform
;output: the ascii codes stored in outnum
;Sample call: jsb dconvert
dconvert: clrl r5 ;clear temporary storage
;the output field
clrl outnum ;temporary storage for inform
clrl r6 , r6
movb inform#100, r6, r5 ;divide inform by 100
divl3 , #0 ;is there a hundreds digit?
cmpl r5;if not, go to tens
beql tens , outnum+1 ;the hundreds digit
movb r5#100, r5 ;temporary number
mull2 , r6 ;reduce the internal form
subl2 r5tens: divb3 #10, r6, r5 ;divide inform by 10
, #0 ;is there a tens digit?
cmpl r5;if not, go to ones
beql ones , outnum+2 ;the tens digit
movb r5#10, r5 ;temporary number
mulb2 , r6 ;reduce internal form
subb2 r5ones: cmpb r6, #0 ;is there a ones digit left?
;go change to ascii
beql dectoascii , outnum+3 ;the third digit
movb r6dectoascii: addb2 #^x30, outnum ;change numbers to ascii
#^x30, outnum+1
addb2 #^x30, outnum+2
addb2 #^x30, outnum+3 ;all four digits
addb2 ;return to calling procedure
rsb
;============================================================================
finishup: moval message14, r5 ;message for successful completion
;go print it
jsb printmessage
badfinishup: $CLOSE FAB=FR$OUTFAB
=FR$INFAB
$CLOSE FAB=FR$FAB_OUT$
$CLOSE FAB=FR$FAB_IN$
$CLOSE FAB
$EXIT_SEND BEGIN .