collapseos/apps/zasm/main.asm

246 lines
5.7 KiB
NASM
Raw Normal View History

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.
.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.
.equ ZASM_LOCAL_PASS @+1
; What IO_PC was when we started our context
.equ ZASM_CTX_PC @+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 @+2
.equ ZASM_RAMEND @+2
2019-05-11 10:32:05 +10:00
; Takes 2 byte arguments, blkdev in and blkdev out, expressed as IDs.
; Can optionally take a 3rd argument which is the high byte of the initial
; .org. For example, passing 0x42 to this 3rd arg is the equivalent of beginning
; the unit with ".org 0x4200".
; Read file through blkdev in and outputs its upcodes through blkdev out.
; HL is set to the last lineno to be read.
; Sets Z on success, unset on error. On error, A contains an error code (ERR_*)
2019-05-11 10:32:05 +10:00
zasmMain:
; Parse args in (HL)
; blkdev in
call parseHexadecimal ; --> DE
jr nz, .badargs
ld a, e
ld de, IO_IN_BLK
call blkSel
; blkdev in
call rdWS
jr nz, .badargs
call parseHexadecimal ; --> DE
jr nz, .badargs
ld a, e
ld de, IO_OUT_BLK
call blkSel
2019-05-16 10:07:21 +10:00
; .org high byte
ld e, 0 ; in case we .skipOrgSet
call rdWS
jr nz, .skipOrgSet ; no org argument
call parseHexadecimal ; --> DE
jr nz, .badargs
.skipOrgSet:
; Init .org with value of E
; Save in "@" too
ld a, e
ld (ZASM_ORG+1), a ; high byte of .org
ld (DIREC_LASTVAL+1), a
2019-05-16 10:07:21 +10:00
xor a
ld (ZASM_ORG), a ; low byte zero in all cases
ld (DIREC_LASTVAL), a
; And then the rest.
2019-05-16 10:07:21 +10:00
ld (ZASM_LOCAL_PASS), a
call ioInit
2019-05-14 10:23:10 +10:00
call symInit
2019-05-11 10:32:05 +10:00
; First pass
ld hl, .sFirstPass
call ioPrintLN
2019-05-11 10:32:05 +10:00
ld a, 1
ld (ZASM_FIRST_PASS), a
call zasmParseFile
jr nz, .end
2019-05-11 10:32:05 +10:00
; Second pass
ld hl, .sSecondPass
call ioPrintLN
2019-05-11 10:32:05 +10:00
xor a
ld (ZASM_FIRST_PASS), a
; 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
.end:
jp ioLineNo ; --> HL, --> DE, returns
2019-05-11 10:32:05 +10:00
.badargs:
; bad args
ld a, SHELL_ERR_BAD_ARGS
ret
.sFirstPass:
.db "First pass", 0
.sSecondPass:
.db "Second pass", 0
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
; 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:
call ioRewind
2019-05-11 10:32:05 +10:00
.loop:
call parseLine
ret nz ; error
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
; Parse next token and accompanying args (when relevant) in I/O, write the
; resulting opcode(s) through ioPutB 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. 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
jr z, _parseLabel
cp TOK_EOF
ret z ; We're finished, no error.
; Bad token
2019-05-28 01:22:38 +10:00
ld a, ERR_UNKNOWN
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_*
jp parseInstruction
2019-05-15 04:32:12 +10:00
_parseDirec:
2019-05-02 01:26:41 +10:00
ld a, c ; D_*
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
.success:
xor a ; ensure Z
2019-05-15 04:32:12 +10:00
ret
2019-05-01 05:51:39 +10:00
.error:
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
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
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)
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