collapseos/apps/zasm/main.asm
2019-05-16 08:26:00 -04:00

279 lines
6.4 KiB
NASM

; 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
; JUMP_ADDDE
; JUMP_ADDHL
; JUMP_UPCASE
; JUMP_UNSETZ
; JUMP_INTODE
; JUMP_INTOHL
; JUMP_FINDCHAR
; JUMP_BLKSEL
; RAMSTART (where we put our variables in RAM)
; *** 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
; The offset where we currently are with regards to outputting opcodes
.equ ZASM_PC ZASM_FIRST_PASS+1
; whether we're in "local pass", that is, in local label scanning mode. During
; this special pass, ZASM_FIRST_PASS will also be set so that the rest of the
; code behaves as is we were in the first pass.
.equ ZASM_LOCAL_PASS ZASM_PC+2
; I/O position (in terms of ioSeek/ioTell) of the current context. Used to
; rewind to it after having parsed local labels.
.equ ZASM_CTX_POS ZASM_LOCAL_PASS+1
; What ZASM_PC was when we started our context
.equ ZASM_CTX_PC ZASM_CTX_POS+2
.equ ZASM_RAMEND ZASM_CTX_PC+2
; *** Code ***
jp zasmMain
#include "util.asm"
.equ IO_RAMSTART ZASM_RAMEND
#include "io.asm"
#include "tok.asm"
#include "parse.asm"
#include "expr.asm"
#include "instr.asm"
.equ DIREC_RAMSTART IO_RAMEND
#include "directive.asm"
.equ SYM_RAMSTART DIREC_RAMEND
#include "symbol.asm"
; Read file through blockdev ID in H and outputs its upcodes through blockdev
; ID in L.
zasmMain:
; Init I/O
ld a, h
ld de, IO_IN_GETC
call JUMP_BLKSEL
ld a, l
ld de, IO_OUT_GETC
call JUMP_BLKSEL
; Init modules
xor a
ld (ZASM_LOCAL_PASS), a
call ioInit
call symInit
; First pass
ld a, 1
ld (ZASM_FIRST_PASS), a
call zasmParseFile
ret nz
; Second pass
ld hl, 0
call ioSeek
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
; Sets Z according to whether we're in local pass.
zasmIsLocalPass:
ld a, (ZASM_LOCAL_PASS)
cp 1
ret
; Increase (ZASM_PC) by A
incOutputOffset:
push de
ld de, (ZASM_PC)
call JUMP_ADDDE
ld (ZASM_PC), de
pop de
ret
; Repeatedly reads lines from IO, assemble them and spit the binary code in
; IO. Z is set on success, unset on error. DE contains the last line number to
; be read (first line is 1).
zasmParseFile:
ld de, 0
ld (ZASM_PC), de
.loop:
call parseLine
ret nz ; error
ld a, b ; TOK_*
cp TOK_EOF
jr z, .eof
jr .loop
.eof:
call zasmIsLocalPass
jr nz, .end ; EOF and not local pass
; we're in local pass and EOF. Unwind this
call _endLocalPass
jr .loop
.end:
cp a ; ensure Z
ret
; Parse next token and accompanying args (when relevant) in I/O, write the
; resulting opcode(s) through ioPutC and increases (ZASM_PC) by the number of
; bytes written. BC is set to the result of the call to tokenize.
; Sets Z if parse was successful, unset if there was an error. EOF is not an
; error.
parseLine:
call tokenize
ld a, b ; TOK_*
cp TOK_INSTR
jp z, _parseInstr
cp TOK_DIRECTIVE
jp z, _parseDirec
cp TOK_LABEL
jr z, _parseLabel
cp TOK_EOF
ret ; Z is correct. If EOF, Z is set and not an
; error, otherwise, it means bad token and
; errors out.
_parseInstr:
ld a, c ; I_*
call parseInstruction
or a ; is zero?
jr z, .error
ld b, a ; save output byte count
call incOutputOffset
call zasmIsFirstPass
jr z, .success ; first pass, nothing to write
ld hl, instrUpcode
.loopInstr:
ld a, (hl)
call ioPutC
inc hl
djnz .loopInstr
; continue to success
.success:
xor a ; ensure Z
ret
.error:
call JUMP_UNSETZ
ret
_parseDirec:
ld a, c ; D_*
call parseDirective
or a ; cp 0
jr z, .success ; if zero, shortcut through
ld b, a ; save output byte count
call incOutputOffset
call zasmIsFirstPass
jr z, .success ; first pass, nothing to write
ld hl, direcData
.loopDirec:
ld a, (hl)
call ioPutC
inc hl
djnz .loopDirec
; continue to success
.success:
xor a ; ensure Z
ret
_parseLabel:
; The string in (scratchpad) is a label with its trailing ':' removed.
ld hl, scratchpad
call zasmIsLocalPass
jr z, .processLocalPass
; Is this a local label? If yes, we don't process it in the context of
; parseLine, whether it's first or second pass. Local labels are only
; parsed during the Local Pass
call symIsLabelLocal
jr z, .success ; local? don't do anything.
call zasmIsFirstPass
jr z, .registerLabel ; When we encounter a label in the first
; pass, we register it in the symbol
; list
; At this point, we're in second pass, we've encountered a global label
; and we'll soon continue processing our file. However, before we do
; that, we should process our local labels.
call _beginLocalPass
jr .success
.processLocalPass:
call symIsLabelLocal
jr z, .registerLabel ; local label? all good, register it
; normally
; not a local label? Then we need to end local pass
call _endLocalPass
jr .success
.registerLabel:
ld de, (ZASM_PC)
call symRegister
jr nz, .error
; continue to .success
.success:
xor a ; ensure Z
ret
.error:
call JUMP_UNSETZ
ret
_beginLocalPass:
; remember were I/O was
call ioTell
ld (ZASM_CTX_POS), hl
; Remember where PC was
ld hl, (ZASM_PC)
ld (ZASM_CTX_PC), hl
; Fake first pass
ld a, 1
ld (ZASM_FIRST_PASS), a
; Set local pass
ld (ZASM_LOCAL_PASS), a
; Empty local label registry
xor a
ld (SYM_LOC_NAMES), a
call symSelectLocalRegistry
ret
_endLocalPass:
call symSelectGlobalRegistry
; recall I/O pos
ld hl, (ZASM_CTX_POS)
call ioSeek
; recall PC
ld hl, (ZASM_CTX_PC)
ld (ZASM_PC), hl
; unfake first pass
xor a
ld (ZASM_FIRST_PASS), a
; Unset local pass
ld (ZASM_LOCAL_PASS), a
cp a ; ensure Z
ret