2019-05-11 10:32:05 +10:00
|
|
|
; *** 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.
|
2019-05-19 23:14:40 +10:00
|
|
|
.equ ZASM_FIRST_PASS ZASM_RAMSTART
|
2019-05-16 10:07:21 +10:00
|
|
|
; 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.
|
2019-05-18 04:58:16 +10:00
|
|
|
.equ ZASM_LOCAL_PASS ZASM_FIRST_PASS+1
|
|
|
|
; What IO_PC was when we started our context
|
2019-05-17 11:15:00 +10:00
|
|
|
.equ ZASM_CTX_PC ZASM_LOCAL_PASS+1
|
2019-05-20 03:22:14 +10:00
|
|
|
; current ".org" offset, that is, what we must offset all our label by.
|
|
|
|
.equ ZASM_ORG ZASM_CTX_PC+2
|
|
|
|
.equ ZASM_RAMEND ZASM_ORG+2
|
2019-05-11 10:32:05 +10:00
|
|
|
|
2019-06-03 04:05:20 +10:00
|
|
|
; Takes 2 byte arguments, blkdev in and blkdev out, expressed as IDs.
|
|
|
|
; Read file through blkdev in and outputs its upcodes through blkdev out.
|
|
|
|
; HL is set to the last lineno to be read.
|
2019-05-28 10:52:40 +10:00
|
|
|
; Sets Z on success, unset on error. On error, A contains an error code (ERR_*)
|
2019-05-11 10:32:05 +10:00
|
|
|
zasmMain:
|
2019-06-03 04:05:20 +10:00
|
|
|
; Parse args. HL points to string already
|
|
|
|
; We don't allocate memory just to hold this. Because this happens
|
|
|
|
; before initialization, we don't really care where those args are
|
|
|
|
; parsed.
|
|
|
|
ld de, .argspecs
|
|
|
|
ld ix, ZASM_RAMSTART
|
|
|
|
call parseArgs
|
2019-06-03 22:12:44 +10:00
|
|
|
jr z, .goodargs
|
|
|
|
; bad args
|
2019-06-03 04:46:07 +10:00
|
|
|
ld hl, 0
|
|
|
|
ld de, 0
|
2019-06-03 04:05:20 +10:00
|
|
|
ld a, SHELL_ERR_BAD_ARGS
|
2019-06-03 22:12:44 +10:00
|
|
|
ret
|
2019-06-03 04:05:20 +10:00
|
|
|
|
2019-06-03 22:12:44 +10:00
|
|
|
.goodargs:
|
2019-06-03 04:05:20 +10:00
|
|
|
; HL now points to parsed args
|
2019-05-14 10:23:10 +10:00
|
|
|
; Init I/O
|
2019-06-03 04:05:20 +10:00
|
|
|
ld a, (ZASM_RAMSTART) ; blkdev in ID
|
2019-06-05 01:53:02 +10:00
|
|
|
ld de, IO_IN_BLK
|
2019-05-17 23:50:11 +10:00
|
|
|
call blkSel
|
2019-06-03 04:05:20 +10:00
|
|
|
ld a, (ZASM_RAMSTART+1) ; blkdev out ID
|
2019-06-05 01:53:02 +10:00
|
|
|
ld de, IO_OUT_BLK
|
2019-05-17 23:50:11 +10:00
|
|
|
call blkSel
|
2019-05-16 10:07:21 +10:00
|
|
|
|
2019-05-14 10:23:10 +10:00
|
|
|
; Init modules
|
2019-05-16 10:07:21 +10:00
|
|
|
xor a
|
|
|
|
ld (ZASM_LOCAL_PASS), a
|
2019-05-20 03:22:14 +10:00
|
|
|
ld (ZASM_ORG), a
|
|
|
|
ld (ZASM_ORG+1), a
|
2019-05-16 21:53:42 +10:00
|
|
|
call ioInit
|
2019-05-14 10:23:10 +10:00
|
|
|
call symInit
|
|
|
|
|
2019-05-11 10:32:05 +10:00
|
|
|
; First pass
|
2019-06-20 01:42:39 +10:00
|
|
|
ld hl, .sFirstPass
|
|
|
|
call ioPrintLN
|
2019-05-11 10:32:05 +10:00
|
|
|
ld a, 1
|
|
|
|
ld (ZASM_FIRST_PASS), a
|
|
|
|
call zasmParseFile
|
2019-05-28 10:52:40 +10:00
|
|
|
jr nz, .end
|
2019-05-11 10:32:05 +10:00
|
|
|
; Second pass
|
2019-06-20 01:42:39 +10:00
|
|
|
ld hl, .sSecondPass
|
|
|
|
call ioPrintLN
|
2019-05-11 10:32:05 +10:00
|
|
|
xor a
|
|
|
|
ld (ZASM_FIRST_PASS), a
|
2019-07-23 12:49:43 +10:00
|
|
|
; before parsing the file for the second pass, let's clear the const
|
|
|
|
; registry. See comment in handleEQU.
|
|
|
|
ld ix, SYM_CONST_REGISTRY
|
|
|
|
call symClear
|
2019-05-11 10:32:05 +10:00
|
|
|
call zasmParseFile
|
2019-05-28 10:52:40 +10:00
|
|
|
.end:
|
2019-05-28 23:57:29 +10:00
|
|
|
jp ioLineNo ; --> HL, --> DE, returns
|
2019-05-11 10:32:05 +10:00
|
|
|
|
2019-06-03 04:05:20 +10:00
|
|
|
.argspecs:
|
|
|
|
.db 0b001, 0b001, 0
|
2019-06-20 01:42:39 +10:00
|
|
|
.sFirstPass:
|
|
|
|
.db "First pass", 0
|
|
|
|
.sSecondPass:
|
|
|
|
.db "Second pass", 0
|
2019-06-03 04:05:20 +10:00
|
|
|
|
2019-05-11 10:32:05 +10:00
|
|
|
; Sets Z according to whether we're in first pass.
|
|
|
|
zasmIsFirstPass:
|
|
|
|
ld a, (ZASM_FIRST_PASS)
|
|
|
|
cp 1
|
2019-05-01 05:51:39 +10:00
|
|
|
ret
|
|
|
|
|
2019-05-16 10:07:21 +10:00
|
|
|
; Sets Z according to whether we're in local pass.
|
|
|
|
zasmIsLocalPass:
|
|
|
|
ld a, (ZASM_LOCAL_PASS)
|
|
|
|
cp 1
|
|
|
|
ret
|
|
|
|
|
2019-05-20 03:22:14 +10:00
|
|
|
; Set ZASM_ORG to specified number in HL
|
|
|
|
zasmSetOrg:
|
|
|
|
ld (ZASM_ORG), hl
|
|
|
|
ret
|
|
|
|
|
|
|
|
; Return current PC (properly .org offsetted) in HL
|
|
|
|
zasmGetPC:
|
|
|
|
push de
|
|
|
|
ld hl, (ZASM_ORG)
|
|
|
|
ld de, (IO_PC)
|
|
|
|
add hl, de
|
|
|
|
pop de
|
|
|
|
ret
|
|
|
|
|
2019-05-13 11:44:59 +10:00
|
|
|
; 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).
|
2019-05-11 10:32:05 +10:00
|
|
|
zasmParseFile:
|
2019-05-28 10:52:40 +10:00
|
|
|
call ioRewind
|
2019-05-11 10:32:05 +10:00
|
|
|
.loop:
|
|
|
|
call parseLine
|
2019-05-13 11:31:11 +10:00
|
|
|
ret nz ; error
|
2019-05-16 21:53:42 +10:00
|
|
|
ld a, b ; TOK_*
|
|
|
|
cp TOK_EOF
|
2019-05-16 10:07:21 +10:00
|
|
|
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
|
2019-05-11 10:32:05 +10:00
|
|
|
jr .loop
|
2019-05-16 10:07:21 +10:00
|
|
|
.end:
|
|
|
|
cp a ; ensure Z
|
|
|
|
ret
|
2019-05-11 10:32:05 +10:00
|
|
|
|
2019-05-16 21:53:42 +10:00
|
|
|
; Parse next token and accompanying args (when relevant) in I/O, write the
|
2019-10-31 07:59:35 +11:00
|
|
|
; resulting opcode(s) through ioPutB and increases (IO_PC) by the number of
|
2019-05-16 21:53:42 +10:00
|
|
|
; 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
|
2019-05-28 01:04:31 +10:00
|
|
|
; error. If there is an error, A is set to the corresponding error code (ERR_*).
|
2019-05-01 05:51:39 +10:00
|
|
|
parseLine:
|
|
|
|
call tokenize
|
2019-05-01 12:27:11 +10:00
|
|
|
ld a, b ; TOK_*
|
2019-05-01 11:40:22 +10:00
|
|
|
cp TOK_INSTR
|
2019-05-16 05:16:35 +10:00
|
|
|
jp z, _parseInstr
|
2019-05-02 01:26:41 +10:00
|
|
|
cp TOK_DIRECTIVE
|
2019-05-16 05:16:35 +10:00
|
|
|
jp z, _parseDirec
|
2019-05-10 11:21:08 +10:00
|
|
|
cp TOK_LABEL
|
2019-05-16 21:53:42 +10:00
|
|
|
jr z, _parseLabel
|
|
|
|
cp TOK_EOF
|
2019-05-28 01:04:31 +10:00
|
|
|
ret z ; We're finished, no error.
|
|
|
|
; Bad token
|
2019-05-28 01:22:38 +10:00
|
|
|
ld a, ERR_UNKNOWN
|
2019-05-28 01:04:31 +10:00
|
|
|
jp unsetZ ; return with Z unset
|
2019-05-15 04:32:12 +10:00
|
|
|
|
|
|
|
_parseInstr:
|
2019-05-01 12:27:11 +10:00
|
|
|
ld a, c ; I_*
|
2019-05-18 05:35:49 +10:00
|
|
|
jp parseInstruction
|
2019-05-15 04:32:12 +10:00
|
|
|
|
|
|
|
_parseDirec:
|
2019-05-02 01:26:41 +10:00
|
|
|
ld a, c ; D_*
|
2019-05-28 07:45:05 +10:00
|
|
|
jp parseDirective
|
2019-05-15 04:32:12 +10:00
|
|
|
|
|
|
|
_parseLabel:
|
2019-05-10 11:21:08 +10:00
|
|
|
; The string in (scratchpad) is a label with its trailing ':' removed.
|
|
|
|
ld hl, scratchpad
|
2019-05-16 10:07:21 +10:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
2019-07-23 05:59:47 +10:00
|
|
|
ld ix, SYM_GLOBAL_REGISTRY
|
2019-05-14 10:23:10 +10:00
|
|
|
call zasmIsFirstPass
|
|
|
|
jr z, .registerLabel ; When we encounter a label in the first
|
|
|
|
; pass, we register it in the symbol
|
|
|
|
; list
|
2019-05-16 10:07:21 +10:00
|
|
|
; 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:
|
2019-07-23 05:59:47 +10:00
|
|
|
ld ix, SYM_LOCAL_REGISTRY
|
2019-05-14 10:23:10 +10:00
|
|
|
call symIsLabelLocal
|
2019-05-16 10:07:21 +10:00
|
|
|
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
|
2019-05-14 10:23:10 +10:00
|
|
|
.registerLabel:
|
2019-05-20 03:22:14 +10:00
|
|
|
push hl
|
|
|
|
call zasmGetPC
|
|
|
|
ex de, hl
|
|
|
|
pop hl
|
2019-05-10 11:21:08 +10:00
|
|
|
call symRegister
|
2019-05-14 10:23:10 +10:00
|
|
|
jr nz, .error
|
|
|
|
; continue to .success
|
2019-05-02 00:16:57 +10:00
|
|
|
.success:
|
|
|
|
xor a ; ensure Z
|
2019-05-15 04:32:12 +10:00
|
|
|
ret
|
2019-05-01 05:51:39 +10:00
|
|
|
.error:
|
2019-05-17 23:50:11 +10:00
|
|
|
call unsetZ
|
2019-05-01 05:51:39 +10:00
|
|
|
ret
|
2019-05-16 10:07:21 +10:00
|
|
|
|
|
|
|
_beginLocalPass:
|
|
|
|
; remember were I/O was
|
2019-05-17 11:15:00 +10:00
|
|
|
call ioSavePos
|
2019-05-16 10:07:21 +10:00
|
|
|
; Remember where PC was
|
2019-05-18 04:58:16 +10:00
|
|
|
ld hl, (IO_PC)
|
2019-05-16 10:07:21 +10:00
|
|
|
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
|
2019-07-23 12:49:43 +10:00
|
|
|
ld ix, SYM_LOCAL_REGISTRY
|
|
|
|
jp symClear
|
2019-05-16 10:07:21 +10:00
|
|
|
|
|
|
|
|
|
|
|
_endLocalPass:
|
|
|
|
; recall I/O pos
|
2019-05-17 11:15:00 +10:00
|
|
|
call ioRecallPos
|
2019-05-16 10:07:21 +10:00
|
|
|
; recall PC
|
|
|
|
ld hl, (ZASM_CTX_PC)
|
2019-05-18 04:58:16 +10:00
|
|
|
ld (IO_PC), hl
|
2019-05-16 10:07:21 +10:00
|
|
|
; unfake first pass
|
|
|
|
xor a
|
|
|
|
ld (ZASM_FIRST_PASS), a
|
|
|
|
; Unset local pass
|
|
|
|
ld (ZASM_LOCAL_PASS), a
|
|
|
|
cp a ; ensure Z
|
|
|
|
ret
|