zasm: read input in two passes

This commit is contained in:
Virgil Dupras 2019-05-10 20:32:05 -04:00
parent 6a804a9c64
commit c239ec7dea
4 changed files with 83 additions and 19 deletions

View File

@ -22,6 +22,11 @@ ioPutC:
ld ix, (IO_OUT_PUTC) ld ix, (IO_OUT_PUTC)
jp (ix) jp (ix)
ioRewind:
ld hl, 0
ld ix, (IO_IN_SEEK)
jp (ix)
; Sets Z is A is CR, LF, or null. ; Sets Z is A is CR, LF, or null.
isLineEnd: isLineEnd:
or a ; same as cp 0 or a ; same as cp 0

View File

@ -1,3 +1,22 @@
; zasm
;
; Reads input from specified blkdev ID, assemble the binary in two passes and
; spit the result in another specified blkdev ID.
;
; We don't buffer the whole source in memory, so we need our input blkdev to
; support Seek so we can read the file a second time. So, for input, we need
; GetC and Seek.
;
; For output, we only need PutC. Output doesn't start until the second pass.
;
; The goal of the second pass is to assign values to all symbols so that we
; can have forward references (instructions referencing a label that happens
; later).
;
; Labels and constants are both treated the same way, that is, they can be
; forward-referenced in instructions. ".equ" directives, however, are evaluated
; during the first pass so forward references are not allowed.
;
; *** Requirements *** ; *** Requirements ***
; blockdev ; blockdev
; JUMP_STRNCMP ; JUMP_STRNCMP
@ -10,9 +29,20 @@
; JUMP_BLKSEL ; JUMP_BLKSEL
; RAMSTART (where we put our variables in RAM) ; RAMSTART (where we put our variables in RAM)
jp main ; *** Variables ***
; A bool flag indicating that we're on first pass. When we are, we don't care
; about actual output, but only about the length of each upcode. This means
; that when we parse instructions and directive that error out because of a
; missing symbol, we don't error out and just write down a dummy value.
.equ ZASM_FIRST_PASS RAMSTART
.equ ZASM_RAMEND ZASM_FIRST_PASS+1
; *** Code ***
jp zasmMain
#include "util.asm" #include "util.asm"
.equ IO_RAMSTART RAMSTART .equ IO_RAMSTART ZASM_RAMEND
#include "io.asm" #include "io.asm"
#include "parse.asm" #include "parse.asm"
#include "literal.asm" #include "literal.asm"
@ -21,26 +51,30 @@ jp main
.equ SYM_RAMSTART IO_RAMEND .equ SYM_RAMSTART IO_RAMEND
#include "symbol.asm" #include "symbol.asm"
; *** Code ***
; Read file through blockdev ID in H and outputs its upcodes through blockdev ; Read file through blockdev ID in H and outputs its upcodes through blockdev
; ID in L. ; ID in L.
main: zasmMain:
ld a, h ld a, h
ld de, IO_IN_GETC ld de, IO_IN_GETC
call JUMP_BLKSEL call JUMP_BLKSEL
ld a, l ld a, l
ld de, IO_OUT_GETC ld de, IO_OUT_GETC
call JUMP_BLKSEL call JUMP_BLKSEL
ld hl, 0 ; First pass
ld (curOutputOffset), hl ld a, 1
.loop: ld (ZASM_FIRST_PASS), a
call ioReadLine call zasmParseFile
or a ; is A 0? ; Second pass
jr z, .stop ; We have EOF call ioRewind
call parseLine xor a
jr nz, .stop ld (ZASM_FIRST_PASS), a
jr .loop call zasmParseFile
.stop: ret
; Sets Z according to whether we're in first pass.
zasmIsFirstPass:
ld a, (ZASM_FIRST_PASS)
cp 1
ret ret
; Increase (curOutputOffset) by A ; Increase (curOutputOffset) by A
@ -52,6 +86,17 @@ incOutputOffset:
pop de pop de
ret ret
zasmParseFile:
ld hl, 0
ld (curOutputOffset), hl
.loop:
call ioReadLine
or a ; is A 0?
ret z ; We have EOF
call parseLine
ret nz
jr .loop
; Parse line in (HL), write the resulting opcode(s) in (DE) and increases ; Parse line in (HL), write the resulting opcode(s) in (DE) and increases
; (curOutputOffset) by the number of bytes written. Advances HL where ; (curOutputOffset) by the number of bytes written. Advances HL where
; tokenization stopped and DE to where we should write the next upcode. ; tokenization stopped and DE to where we should write the next upcode.
@ -75,8 +120,10 @@ parseLine:
call parseInstruction call parseInstruction
or a ; is zero? or a ; is zero?
jr z, .error jr z, .error
ld b, a ; save output byte count
call incOutputOffset call incOutputOffset
ld b, a call zasmIsFirstPass
jr z, .success ; first pass, nothing to write
ld hl, instrUpcode ld hl, instrUpcode
.loopInstr: .loopInstr:
ld a, (hl) ld a, (hl)
@ -87,8 +134,10 @@ parseLine:
.direc: .direc:
ld a, c ; D_* ld a, c ; D_*
call parseDirective call parseDirective
ld b, a ; save output byte count
call incOutputOffset call incOutputOffset
ld b, a call zasmIsFirstPass
jr z, .success ; first pass, nothing to write
ld hl, direcData ld hl, direcData
.loopDirec: .loopDirec:
ld a, (hl) ld a, (hl)
@ -97,6 +146,8 @@ parseLine:
djnz .loopDirec djnz .loopDirec
jr .success jr .success
.label: .label:
call zasmIsFirstPass
jr nz, .success ; not in first pass? nothing to do
; The string in (scratchpad) is a label with its trailing ':' removed. ; The string in (scratchpad) is a label with its trailing ':' removed.
ld hl, scratchpad ld hl, scratchpad
ld de, (curOutputOffset) ld de, (curOutputOffset)

View File

@ -4,9 +4,12 @@
#include "zasm-kernel.h" #include "zasm-kernel.h"
#include "zasm-user.h" #include "zasm-user.h"
/* zasm is a "pure memory" application. It starts up being told memory location /* zasm reads from a specified blkdev, assemble the file and writes the result
* to read and memory location to write. * in another specified blkdev. In our emulator layer, we use stdin and stdout
* as those specified blkdevs.
* *
* Because the input blkdev needs support for Seek, we buffer it in the emulator
* layer.
* *
* Memory layout: * Memory layout:
* *

View File

@ -2,6 +2,7 @@
.equ RAMSTART 0x4000 .equ RAMSTART 0x4000
.equ USER_CODE 0x4800 .equ USER_CODE 0x4800
.equ STDIO_PORT 0x00 .equ STDIO_PORT 0x00
.equ STDIN_REWIND 0x01
jr init ; 2 bytes jr init ; 2 bytes
; *** JUMP TABLE *** ; *** JUMP TABLE ***
@ -40,10 +41,14 @@ emulPutC:
out (STDIO_PORT), a out (STDIO_PORT), a
ret ret
emulSeek:
out (STDIN_REWIND), a
ret
#include "core.asm" #include "core.asm"
.equ BLOCKDEV_RAMSTART RAMSTART .equ BLOCKDEV_RAMSTART RAMSTART
.equ BLOCKDEV_COUNT 2 .equ BLOCKDEV_COUNT 2
#include "blockdev.asm" #include "blockdev.asm"
; List of devices ; List of devices
.dw emulGetC, 0, 0, 0 .dw emulGetC, 0, emulSeek, 0
.dw 0, emulPutC, 0, 0 .dw 0, emulPutC, 0, 0