diff --git a/apps/zasm/io.asm b/apps/zasm/io.asm index f620fc5..0b61546 100644 --- a/apps/zasm/io.asm +++ b/apps/zasm/io.asm @@ -22,6 +22,11 @@ ioPutC: ld ix, (IO_OUT_PUTC) jp (ix) +ioRewind: + ld hl, 0 + ld ix, (IO_IN_SEEK) + jp (ix) + ; Sets Z is A is CR, LF, or null. isLineEnd: or a ; same as cp 0 diff --git a/apps/zasm/main.asm b/apps/zasm/main.asm index 20c3a3e..d604857 100644 --- a/apps/zasm/main.asm +++ b/apps/zasm/main.asm @@ -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 *** ; blockdev ; JUMP_STRNCMP @@ -10,9 +29,20 @@ ; JUMP_BLKSEL ; 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" -.equ IO_RAMSTART RAMSTART +.equ IO_RAMSTART ZASM_RAMEND #include "io.asm" #include "parse.asm" #include "literal.asm" @@ -21,26 +51,30 @@ jp main .equ SYM_RAMSTART IO_RAMEND #include "symbol.asm" -; *** Code *** ; Read file through blockdev ID in H and outputs its upcodes through blockdev ; ID in L. -main: +zasmMain: ld a, h ld de, IO_IN_GETC call JUMP_BLKSEL ld a, l ld de, IO_OUT_GETC call JUMP_BLKSEL - ld hl, 0 - ld (curOutputOffset), hl -.loop: - call ioReadLine - or a ; is A 0? - jr z, .stop ; We have EOF - call parseLine - jr nz, .stop - jr .loop -.stop: + ; First pass + ld a, 1 + ld (ZASM_FIRST_PASS), a + call zasmParseFile + ; Second pass + call ioRewind + xor a + ld (ZASM_FIRST_PASS), a + call zasmParseFile + ret + +; Sets Z according to whether we're in first pass. +zasmIsFirstPass: + ld a, (ZASM_FIRST_PASS) + cp 1 ret ; Increase (curOutputOffset) by A @@ -52,6 +86,17 @@ incOutputOffset: pop de 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 ; (curOutputOffset) by the number of bytes written. Advances HL where ; tokenization stopped and DE to where we should write the next upcode. @@ -75,8 +120,10 @@ parseLine: call parseInstruction or a ; is zero? jr z, .error + ld b, a ; save output byte count call incOutputOffset - ld b, a + call zasmIsFirstPass + jr z, .success ; first pass, nothing to write ld hl, instrUpcode .loopInstr: ld a, (hl) @@ -87,8 +134,10 @@ parseLine: .direc: ld a, c ; D_* call parseDirective + ld b, a ; save output byte count call incOutputOffset - ld b, a + call zasmIsFirstPass + jr z, .success ; first pass, nothing to write ld hl, direcData .loopDirec: ld a, (hl) @@ -97,6 +146,8 @@ parseLine: djnz .loopDirec jr .success .label: + call zasmIsFirstPass + jr nz, .success ; not in first pass? nothing to do ; The string in (scratchpad) is a label with its trailing ':' removed. ld hl, scratchpad ld de, (curOutputOffset) diff --git a/tools/emul/zasm.c b/tools/emul/zasm.c index adaca66..14b833b 100644 --- a/tools/emul/zasm.c +++ b/tools/emul/zasm.c @@ -4,9 +4,12 @@ #include "zasm-kernel.h" #include "zasm-user.h" -/* zasm is a "pure memory" application. It starts up being told memory location - * to read and memory location to write. +/* zasm reads from a specified blkdev, assemble the file and writes the result + * 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: * diff --git a/tools/emul/zasm_glue.asm b/tools/emul/zasm_glue.asm index d8bbc64..5dce484 100644 --- a/tools/emul/zasm_glue.asm +++ b/tools/emul/zasm_glue.asm @@ -2,6 +2,7 @@ .equ RAMSTART 0x4000 .equ USER_CODE 0x4800 .equ STDIO_PORT 0x00 +.equ STDIN_REWIND 0x01 jr init ; 2 bytes ; *** JUMP TABLE *** @@ -40,10 +41,14 @@ emulPutC: out (STDIO_PORT), a ret +emulSeek: + out (STDIN_REWIND), a + ret + #include "core.asm" .equ BLOCKDEV_RAMSTART RAMSTART .equ BLOCKDEV_COUNT 2 #include "blockdev.asm" ; List of devices -.dw emulGetC, 0, 0, 0 +.dw emulGetC, 0, emulSeek, 0 .dw 0, emulPutC, 0, 0