mirror of
https://github.com/hsoft/collapseos.git
synced 2024-12-24 14:28:06 +11:00
zasm: read input in two passes
This commit is contained in:
parent
6a804a9c64
commit
c239ec7dea
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
*
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user