28d5ebdc8a
To straighten out includes and to pave the way into zasm being part of the same "include CFS" as parts, we make zasm includes namespaced.
234 lines
5.4 KiB
NASM
234 lines
5.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
|
|
; strncmp
|
|
; addDE
|
|
; addHL
|
|
; upcase
|
|
; unsetZ
|
|
; intoDE
|
|
; intoHL
|
|
; findchar
|
|
; blkSel
|
|
; fsFindFN
|
|
; fsOpen
|
|
; fsGetC
|
|
; fsSeek
|
|
; fsTell
|
|
; RAMSTART (where we put our variables in RAM)
|
|
; FS_HANDLE_SIZE
|
|
|
|
; *** 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
|
|
; 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_FIRST_PASS+1
|
|
; What IO_PC was when we started our context
|
|
.equ ZASM_CTX_PC ZASM_LOCAL_PASS+1
|
|
.equ ZASM_RAMEND ZASM_CTX_PC+2
|
|
|
|
; *** Code ***
|
|
jp zasmMain
|
|
|
|
#include "zasm/util.asm"
|
|
.equ IO_RAMSTART ZASM_RAMEND
|
|
#include "zasm/io.asm"
|
|
#include "zasm/tok.asm"
|
|
#include "zasm/parse.asm"
|
|
#include "zasm/expr.asm"
|
|
#include "zasm/instr.asm"
|
|
.equ DIREC_RAMSTART IO_RAMEND
|
|
#include "zasm/directive.asm"
|
|
.equ SYM_RAMSTART DIREC_RAMEND
|
|
#include "zasm/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 blkSel
|
|
ld a, l
|
|
ld de, IO_OUT_GETC
|
|
call 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
|
|
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
|
|
|
|
; Sets Z according to whether we're in local pass.
|
|
zasmIsLocalPass:
|
|
ld a, (ZASM_LOCAL_PASS)
|
|
cp 1
|
|
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:
|
|
call ioResetPC
|
|
.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 (IO_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_*
|
|
jp parseInstruction
|
|
|
|
_parseDirec:
|
|
ld a, c ; D_*
|
|
call parseDirective
|
|
cp 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, (IO_PC)
|
|
call symRegister
|
|
jr nz, .error
|
|
; continue to .success
|
|
.success:
|
|
xor a ; ensure Z
|
|
ret
|
|
.error:
|
|
call unsetZ
|
|
ret
|
|
|
|
_beginLocalPass:
|
|
; remember were I/O was
|
|
call ioSavePos
|
|
; Remember where PC was
|
|
ld hl, (IO_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
|
|
call ioRecallPos
|
|
; recall PC
|
|
ld hl, (ZASM_CTX_PC)
|
|
ld (IO_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
|