Jump to content

Subleq: Difference between revisions

11,892 bytes added ,  3 years ago
Add 8080 assembly version
(Add 8086 assembly version)
(Add 8080 assembly version)
Line 44:
message: "Hello, world!\n\0"</pre>
<br><br>
 
=={{header|8080 Assembly}}==
 
<lang 8080asm> ;;; ---------------------------------------------------------------
;;; SUBLEQ for CP/M. The word size is 16 bits, and the program
;;; is given 16 Kwords (32 KB) of memory. (If the system doesn't
;;; have enough, the program will not run.)
;;; I/O is via the console; since it cannot normally be redirected,
;;; CR/LF translation is on by default. It can be turned off with
;;; the 'R' switch.
;;; ---------------------------------------------------------------
;;; CP/M system calls
getch: equ 1h
putch: equ 2h
puts: equ 9h
fopen: equ 0Fh
fread: equ 14h
;;; RAM locations
fcb1: equ 5ch ; FCB 1 (automatically preloaded with 1st file name)
fcb2: equ 6ch ; FCB 2 (we're abusing this one for the switch)
dma: equ 80h ; default DMA is located at 80h
bdos: equ 5h ; CP/M entry point
memtop: equ 6h ; First reserved memory address (below this is ours)
;;; Constants
CR: equ 13 ; CR and LF
LF: equ 10
EOF: equ 26 ; EOF marker (as we don't have exact filesizes)
MSTART: equ 2048 ; Reserve 2K of memory for this program + the stack
MSIZE: equ 32768 ; Reserve 32K of memory (16Kwords) for the SUBLEQ code
PB: equ 0C6h ; PUSH B opcode.
org 100h
;;; -- Memory initialization --------------------------------------
;;; The fastest way to zero out a whole bunch of memory on the 8080
;;; is to push zeroes onto the stack. Since we need to do 32K,
;;; and it's slow already to begin with, let's do it that way.
lxi d,MSTART+MSIZE ; Top address we need
lhld memtop ; See if we even have enough memory
call cmp16 ; Compare the two
xchg ; Put top address in HL
lxi d,emem ; Memory error message
jnc die ; If there isn't enough memory, stop.
sphl ; Set the stack pointer to the top of memory
lxi b,0 ; 2 zero bytes to push
xra a ; Zero out A.
;;; Each PUSH pushes 2 zeroes. 256 * 64 * 2 = 32768 zeroes.
;;; In the interests of "speedy" (ha!) execution, let's unroll this
;;; loop a bit. In the interest of the reader, let's not write out
;;; 64 lines of "PUSH B". 'PB' is set to the opcode for PUSH B, and
;;; 4*16=64. This costs some memory, but since we're basically
;;; assuming a baller >48K system anyway to run any non-trivial
;;; SUBLEQ code (ha!), we can spare the 64 bytes.
memini: db PB,PB,PB,PB, PB,PB,PB,PB, PB,PB,PB,PB, PB,PB,PB,PB
db PB,PB,PB,PB, PB,PB,PB,PB, PB,PB,PB,PB, PB,PB,PB,PB
db PB,PB,PB,PB, PB,PB,PB,PB, PB,PB,PB,PB, PB,PB,PB,PB
db PB,PB,PB,PB, PB,PB,PB,PB, PB,PB,PB,PB, PB,PB,PB,PB
inr a ; This will loop around 256 times
jnz memini
push b
;;; This conveniently leaves SP pointing just below SUBLEQ memory.
;;; -- Check the raw switch ---------------------------------------
;;; CP/M conveniently parses the command line for us, under the
;;; assumption that there are two whitespace-separated filenames,
;;; which are also automatically made uppercase.
;;; We only have to see if the second filename starts with 'R'.
lda fcb2+1 ; Filename starts at offset 1 in the FCB
cpi 'R' ; Is it 'R'?
jnz readfl ; If not, go read the file (in FCB1).
lxi h,chiraw ; If so, rewrite the jumps to use the raw fns
shld chin+1
lxi h,choraw
shld chout+1
;;; -- Parse the input file ---------------------------------------
;;; The input file should consist of signed integers written in
;;; decimal, separated by whitespace. (For simplicity, we'll call
;;; all control characters whitespace). CP/M can only read files
;;; 128 bytes at a time, so we'll process it 128 bytes at a time
;;; as well.
readfl: lda fcb1+1 ; See if a file was given
cpi ' ' ; If not, the filename will be empty (spaces)
lxi d,eusage ; Print the usage string if that is the case
jz die
mvi c,fopen ; Otherwise, try to open the file.
lxi d,fcb1
call bdos
inr a ; FF is returned on error
lxi d,efile ; Print 'file error' and stop.
jz die
;;; Start parsing 16-bit numbers
lxi h,MSTART ; Start of SUBLEQ memory
push h ; Keep that on the stack
skipws: call fgetc ; Get character from file
jc rddone ; If EOF, we're done
cpi ' '+1 ; Is it whitespace?
jc skipws ; Then get next character
rdnum: lxi h,0 ; H = accumulator to store the number
mov b,h ; Set B if number should be negative.
cpi '-' ; Did we read a minus sign?
jnz rddgt ; If not, then this should be a digit.
inr b ; But if so, set B,
call fgetc ; and get the next character.
jc rddone
rddgt: sui '0' ; Make ASCII digit
cpi 10 ; Which should now be less than 10
jnc fmterr ; Otherwise, print an error and stop
mov d,h ; Set HL=HL*10
mov e,l ; DE = HL
dad h ; HL *= 2
dad h ; HL *= 4
dad d ; HL *= 5
dad h ; HL *= 10
mvi d,0 ; Add in the digit
mov e,a
dad d
call fgetc ; Get next character
jc rdeof ; EOF while reading number
cpi ' '+1 ; Is it whitespace?
jnc rddgt ; If not, then it should be the next digit
xchg ; If so, write the number to SUBLEQ memory
pop h ; Number in DE and pointer in HL
call wrnum ; Write the number
push h ; Put the pointer back
jmp skipws ; Then skip to next number and parse it
rdeof: xchg ; EOF, but we still have a number to write
pop h ; Number in DE and pointer in HL
call wrnum ; Write the number
push h
rddone: pop h ; We're done, discard pointer
;;; -- Run the SUBLEQ code ----------------------------------------
lxi h,MSTART ; Initialize IP
;;; At the start of step, HL = IP (in system memory)
step: mov e,m ; Load A into DE
inx h
mov d,m
inx h
mov c,m ; Load B into BC
inx h
mov b,m
inx h
mov a,e ; Check if A=-1
ana d
inr a
jz sbin ; If so, read input
mov a,b ; Otherwise, check if B=-1
ana c
inr a
jz sbout ; If so, write output
;;; Perform the SUBLEQ instruction
push h ; Store the IP (-2) on the stack
mov a,d ; Obtain [A] (set DE=[DE])
ani 3Fh ; Make sure address is in 16K words
mov d,a
lxi h,MSTART ; Add to start address twice
dad d ; (SUBLEQ addresses words, we're addressing
dad d ; bytes)
mov e,m ; Load low byte
inx h
mov d,m ; Load high byte
mov a,b ; Obtain [B] (set BC=[BC])
ani 3Fh ; This adress should also be in the 16K words
mov b,a
lxi h,MSTART ; Add to start address twice, again
dad b
dad b
mov c,m ; Load low byte
inx h
mov b,m ; Load high byte
mov a,c ; BC (B) -= DE (A)
sub e ; Subtract low bytes
mov c,a
mov a,b ; Subtract high bytes
sbb d
mov b,a
mov m,b ; HL is still pointing to the high byte of [B]
dcx h
mov m,c ; Store the low byte back too
pop h ; Restore IP
ral ; Check sign bit of [B] (which is still in A)
jc sujmp ; If set, it's negative, and we need to jump
rar
ora c ; If we're still here, it wasn't set. OR with
jz sujmp ; low bit, if zero then we also need to jump
inx h ; We don't need to jump, so we should ignore C;
inx h ; increment the IP to advance past it.
jmp step ; Next step
sujmp: mov c,m ; We do need to jump, load BC=C
inx h
mov a,m ; High byte into A
ral ; See if it is negative
jc quit ; If so, stop
rar
ani 3Fh ; Don't jump outside the address space
mov b,a ; High byte into B
lxi h,MSTART ; Calculate new IP
dad b
dad b
jmp step ; Do next step
;;; Input: A=-1
sbin: inx h ; Advance IP past C
inx h
xchg ; IP in DE
mov a,b ; Calculate address for BC (B)
ani 3Fh
mov b,a
lxi h,MSTART
dad b
dad b
call chin ; Read character
mov m,a ; Store in low byte
inx h
mvi m,0 ; Store zero in high byte
xchg ; IP back in HL
jmp step ; Next step
;;; Output: B=-1
sbout: inx h ; Advance IP past C
inx h
xchg ; IP in DE and A in HL
mov a,h ; Calculate address for A
ani 3Fh
mov h,a
dad h
lxi b,MSTART
dad b
mov a,m ; Retrieve low byte (character)
call chout ; Write character
xchg ; IP back in HL
jmp step ; Next step
quit: rst 0
;;; -- Write number to SUBLEQ memory ------------------------------
;;; Assuming: DE holds the number, B=1 if number should be negated,
;;; HL holds the pointer to SUBLEQ memory.
wrnum: dcr b ; Should the number be negated?
jnz wrpos ; If not, just write it
dcx d ; Otherwise, negate it: decrement,
mov a,e ; Then complement low byte,
cma
mov e,a
mov a,d ; Then complement high byte
cma
mov d,a ; And then write it
wrpos: mov m,e ; Write low byte
inx h ; Advance pointer
mov m,d ; Write high byte
inx h ; Advance pointer
ret
;;; -- Read file byte by byte -------------------------------------
;;; The next byte from the file in FCB1 is returned in A, and all
;;; other registers are preserved. When 128 bytes have been read,
;;; the next record is loaded automatically. Carry set on EOF.
fgetc: push h ; Keep HL registers
lda fgptr ; Where are we in the record?
ana a
jz nxtrec ; If at 0 (rollover), load new record.
frecc: mvi h,0 ; HL = A
mov l,a
inr a ; Next A
sta fgptr ; Write A back
mov a,m ; Retrieve byte
pop h ; Restore HL registers
cpi EOF ; Is it EOF?
rnz ; If not, we're done (ANA clears carry)
stc ; But otherwise, set carry
ret
nxtrec: push d ; Keep the other registers too
push b
mvi c,fread ; Read record from file
lxi d,fcb1
call bdos
dcr a ; A=1 on EOF
jz fgeof
inr a ; A<>0 = error
lxi d,efile
jnz die
mvi a,80h ; If we're still here, record read correctly
sta fgptr ; Set pointer back to beginning of DMA.
pop b ; Restore B and D
pop d
jmp frecc ; Get first character from the record.
fgeof: stc ; On EOF (no more records), set carry
jmp resbdh ; And restore the registers
fgptr: db 0 ; Pointer (80h-FFh) into DMA area. Reload on 0.
;;; -- Compare DE to HL -------------------------------------------
cmp16: mov a,d ; Compare high bytes
cmp h
rnz ; If they are not equal, we know the ordering
mov a,e ; If they are equal, compare lower bytes
cmp l
ret
;;; -- Register-preserving I/O routines ---------------------------
chin: jmp chitr ; These are rewritten to jump to the raw I/O
chout: jmp chotr ; instructions to turn translation off.
;;; -- Read character into A with translation ---------------------
chitr: call chiraw ; Get raw character
cpi CR ; Is it CR?
rnz ; If not, return character unchanged
mvi a,LF ; Otherwise, return LF (terminal sends only CR)
ret
;;; -- Read character into A. -------------------------------------
chiraw: push h ; Save all registers except A
push d
push b
mvi c,getch ; Get character from terminal
call bdos ; Character ends up in A
jmp resbdh ; Restore registers afterwards
;;; -- Write character in A to terminal with translation ----------
chotr: cpi LF ; Is it LF?
jnz choraw ; If not, just print it
mvi a,CR ; Otherwise, print a CR first,
call choraw
mvi a,LF ; And then a LF. (fall through)
;;; -- Write character in A to terminal ---------------------------
choraw: push h ; Store all registers
push d
push b
push psw
mvi c,putch ; Write character to terminal
mov e,a
call bdos
;;; -- Restore registers ------------------------------------------
restor: pop psw ; Restore all registers
resbdh: pop b ; Restore B D H
pop d
pop h
ret
;;; -- Make parse error message and stop --------------------------
;;; A should hold the offending character _after_ '0' has already
;;; been subtracted.
fmterr: adi '0' ; Undo subtraction of ASCII 0
lxi h,eiloc ; Write the characters in the error message
mov m,a
inx h
mvi b,4 ; Max. 4 more characters
fmtelp: call fgetc ; Get next character
jc fmtdne ; If EOF, stop
mov m,a ; If not, store the character
inx h ; Advance pointer
dcr b ; Should we do more characters?
jnz fmtelp ; If so, go get another
fmtdne: lxi d,einv ; Print 'invalid integer' error message.
;;; -- Print an error message and stop ----------------------------
die: mvi c,puts
call bdos
rst 0
;;; -- Error messages ---------------------------------------------
eusage: db 'SUBLEQ <file> [R]: Run the SUBLEQ program in <file>.$'
efile: db 'File error$'
emem: db 'Memory error$'
einv: db 'Invalid integer: '
eiloc: db ' $' </lang>
 
 
=={{header|8086 Assembly}}==
2,121

edits

Cookies help us deliver our services. By using our services, you agree to our use of cookies.