diff --git a/apps/zasm/io.asm b/apps/zasm/io.asm index 00d6b41..eceb719 100644 --- a/apps/zasm/io.asm +++ b/apps/zasm/io.asm @@ -46,3 +46,6 @@ ioSeek: ld ix, (IO_IN_SEEK) jp (ix) +ioTell: + ld ix, (IO_IN_TELL) + jp (ix) diff --git a/apps/zasm/main.asm b/apps/zasm/main.asm index d9960b3..5172f1c 100644 --- a/apps/zasm/main.asm +++ b/apps/zasm/main.asm @@ -36,10 +36,19 @@ ; 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_FIRST_PASS RAMSTART ; The offset where we currently are with regards to outputting opcodes -.equ ZASM_PC ZASM_FIRST_PASS+1 -.equ ZASM_RAMEND ZASM_PC+2 +.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 @@ -66,7 +75,10 @@ zasmMain: ld a, l ld de, IO_OUT_GETC call JUMP_BLKSEL + ; Init modules + xor a + ld (ZASM_LOCAL_PASS), a call ioInit call symInit @@ -89,6 +101,12 @@ zasmIsFirstPass: 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 @@ -105,13 +123,21 @@ zasmParseFile: ld de, 0 ld (ZASM_PC), de .loop: - inc de call parseLine ret nz ; error ld a, b ; TOK_* cp TOK_EOF - ret z ; if EOF, return now with success + 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 @@ -178,18 +204,32 @@ _parseDirec: _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 - ; When we're not in the first pass, we set the context (if label is not - ; local) to that label. + ; 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, .success ; local? don't set context - call symSetContext - jr z, .success - ; NZ? this means that (HL) couldn't be found in symbol list. Weird - jr .error + 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 @@ -201,3 +241,38 @@ _parseLabel: .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 diff --git a/apps/zasm/parse.asm b/apps/zasm/parse.asm index 89f9de3..275c2ec 100644 --- a/apps/zasm/parse.asm +++ b/apps/zasm/parse.asm @@ -162,6 +162,7 @@ parseNumberOrSymbol: ret z ; first pass? we don't care about the value, ; return success. ; Not a number. Try symbol + call symSelect call symFind ret nz ; not found ; Found! index in A, let's fetch value diff --git a/apps/zasm/symbol.asm b/apps/zasm/symbol.asm index c72662a..a6a1dd4 100644 --- a/apps/zasm/symbol.asm +++ b/apps/zasm/symbol.asm @@ -1,11 +1,14 @@ ; Manages both constants and labels within a same namespace and registry. ; -; About local labels: They are treated as regular labels except they start with -; a dot (example: ".foo"). Because labels are registered in order and because -; constants are registered in the second pass, they end up at the end of the -; symbol list and don't mix with labels. Therefore, we easily iterate through -; local labels of a context by starting from that context's index and iterating -; as long as symbol name start with a '.' +; Local Labels +; +; Local labels during the "official" first pass are ignored. To register them +; in the global registry during that pass would be wasteful in terms of memory. +; +; What we don instead is set up a separate register for them and have a "second +; first pass" whenever we encounter a new context. That is, we wipe the local +; registry, parse the code until the next global symbol (or EOF), then rewind +; and continue second pass as usual. ; *** Constants *** ; Duplicate symbol in registry @@ -14,30 +17,30 @@ .equ SYM_ERR_FULLBUF 0x02 ; Maximum number of symbols we can have in the registry -.equ SYM_MAXCOUNT 0x100 +.equ SYM_MAXCOUNT 0x100 ; Size of the symbol name buffer size. This is a pool. There is no maximum name ; length for a single symbol, just a maximum size for the whole pool. -.equ SYM_BUFSIZE 0x1000 +.equ SYM_BUFSIZE 0x1000 + +; Size of the names buffer for the local context registry +.equ SYM_LOC_BUFSIZE 0x200 ; *** Variables *** ; Each symbol is mapped to a word value saved here. -.equ SYM_VALUES SYM_RAMSTART +.equ SYM_VALUES SYM_RAMSTART ; A list of symbol names separated by null characters. When we encounter a ; symbol name and want to get its value, we search the name here, retrieve the ; index of the name, then go get the value at that index in SYM_VALUES. -.equ SYM_NAMES SYM_VALUES+(SYM_MAXCOUNT*2) +.equ SYM_NAMES SYM_VALUES+(SYM_MAXCOUNT*2) -; Index of the symbol found during the last symSetContext call -.equ SYM_CONTEXT_IDX SYM_NAMES+SYM_BUFSIZE - -; Pointer, in the SYM_NAMES buffer, of the string found during the last -; symSetContext call -.equ SYM_CONTEXT_PTR SYM_CONTEXT_IDX+1 +; Registry for local labels. Wiped out after each context change. +.equ SYM_LOC_VALUES SYM_NAMES+SYM_BUFSIZE +.equ SYM_LOC_NAMES SYM_LOC_VALUES+(SYM_MAXCOUNT*2) ; Pointer to the currently selected registry -.equ SYM_CTX_NAMES SYM_CONTEXT_PTR+2 +.equ SYM_CTX_NAMES SYM_LOC_NAMES+SYM_LOC_BUFSIZE .equ SYM_CTX_NAMESEND SYM_CTX_NAMES+2 .equ SYM_CTX_VALUES SYM_CTX_NAMESEND+2 @@ -67,15 +70,35 @@ _symNext: symInit: xor a ld (SYM_NAMES), a - ld (SYM_CONTEXT_IDX), a - ld hl, SYM_CONTEXT_PTR - ld (SYM_CONTEXT_PTR), hl + ld (SYM_LOC_NAMES), a + ; Continue to symSelectGlobalRegistry + +symSelectGlobalRegistry: + push af + push hl ld hl, SYM_NAMES ld (SYM_CTX_NAMES), hl ld hl, SYM_NAMES+SYM_BUFSIZE ld (SYM_CTX_NAMESEND), hl ld hl, SYM_VALUES ld (SYM_CTX_VALUES), hl + pop hl + pop af + ret + +symSelectLocalRegistry: + push af + push hl + ld hl, SYM_LOC_NAMES + ld (SYM_CTX_NAMES), hl + ld hl, SYM_LOC_NAMES+SYM_LOC_BUFSIZE + ld (SYM_CTX_NAMESEND), hl + ld hl, SYM_LOC_VALUES + ld (SYM_CTX_VALUES), hl + ld a, h + ld a, l + pop hl + pop af ret ; Sets Z according to whether label in (HL) is local (starts with a dot) @@ -153,6 +176,11 @@ symRegister: ld b, 0 ldir ; copy C chars from HL to DE + ; We need to add a second null char to indicate the end of the name + ; list. DE is already correctly placed. + xor a + ld (de), a + ; I'd say we're pretty good just about now. What we need to do is to ; save the value in our original DE that is just on top of the stack ; into the proper index in (SYM_CTX_VALUES). Our index, remember, is @@ -176,16 +204,16 @@ symRegister: pop hl ret +; Select global or local registry according to label name in (HL) +symSelect: + call symIsLabelLocal + jp z, symSelectLocalRegistry + jp symSelectGlobalRegistry + ; Find name (HL) in (SYM_CTX_NAMES) and returns matching index in A. ; If we find something, Z is set, otherwise unset. symFind: push hl - call _symFind - pop hl - ret - -; Same as symFind, but leaks HL -_symFind: push bc push de @@ -193,21 +221,10 @@ _symFind: call strlen ld c, a ; let's save that - call symIsLabelLocal ; save Z for after the 3 next lines, which - ; doesn't touch flags. We need to call this now - ; before we lose HL. ex hl, de ; it's easier if HL is haystack and DE is ; needle. ld b, 0 ld hl, (SYM_CTX_NAMES) - jr nz, .loop ; not local? jump right to loop - ; local? then we need to adjust B and HL - ld hl, (SYM_CONTEXT_PTR) - ld a, (SYM_CONTEXT_IDX) - ld b, a - xor a - sub b - ld b, a .loop: ld a, c ; recall strlen call JUMP_STRNCMP @@ -218,6 +235,7 @@ _symFind: djnz .loop ; exhausted djnz? no match .nomatch: + out (99), a call JUMP_UNSETZ jr .end .match: @@ -228,13 +246,14 @@ _symFind: .end: pop de pop bc + pop hl ret ; Return value associated with symbol index A into DE symGetVal: ; our index is in A. Let's fetch the proper value push hl - ld hl, SYM_VALUES + ld hl, (SYM_CTX_VALUES) call JUMP_ADDHL call JUMP_ADDHL ; twice because our values are words ld e, (hl) @@ -242,18 +261,3 @@ symGetVal: ld d, (hl) pop hl ret - -; Find symbol name (HL) in the symbol list and set SYM_CONTEXT_* accordingly. -; When symFind will be called with a symbol name starting with a '.', the search -; will begin at that context instead of the beginning of the register. -; Sets Z if symbol is found, unsets it if not. -symSetContext: - push hl - call _symFind - jr nz, .end ; Z already unset - ld (SYM_CONTEXT_IDX), a - ld (SYM_CONTEXT_PTR), hl - ; Z already set -.end: - pop hl - ret diff --git a/tools/emul/zasm.c b/tools/emul/zasm.c index 2c1ee1c..a74d2fa 100644 --- a/tools/emul/zasm.c +++ b/tools/emul/zasm.c @@ -41,7 +41,7 @@ static uint8_t mem[0x10000]; static uint8_t inpt[STDIN_BUFSIZE]; static int inpt_size; static int inpt_ptr; -static uint8_t received_first_seek_byte = 0; +static uint8_t middle_of_seek_tell = 0; static uint8_t io_read(int unused, uint16_t addr) { @@ -52,6 +52,17 @@ static uint8_t io_read(int unused, uint16_t addr) } else { return 0; } + } else if (addr == STDIN_SEEK) { + if (middle_of_seek_tell) { + middle_of_seek_tell = 0; + return inpt_ptr & 0xff; + } else { +#ifdef DEBUG + fprintf(stderr, "tell %d\n", inpt_ptr); +#endif + middle_of_seek_tell = 1; + return inpt_ptr >> 8; + } } else { fprintf(stderr, "Out of bounds I/O read: %d\n", addr); return 0; @@ -67,12 +78,15 @@ static void io_write(int unused, uint16_t addr, uint8_t val) putchar(val); #endif } else if (addr == STDIN_SEEK) { - if (received_first_seek_byte) { + if (middle_of_seek_tell) { inpt_ptr |= val; - received_first_seek_byte = 0; + middle_of_seek_tell = 0; +#ifdef DEBUG + fprintf(stderr, "seek %d\n", inpt_ptr); +#endif } else { inpt_ptr = (val << 8) & 0xff00; - received_first_seek_byte = 1; + middle_of_seek_tell = 1; } } else { fprintf(stderr, "Out of bounds I/O write: %d / %d\n", addr, val); diff --git a/tools/emul/zasm_glue.asm b/tools/emul/zasm_glue.asm index 8a897bf..befa8e1 100644 --- a/tools/emul/zasm_glue.asm +++ b/tools/emul/zasm_glue.asm @@ -51,10 +51,18 @@ emulSeek: out (STDIN_SEEK), a ret +emulTell: + ; same principle as STDIN_SEEK + in a, (STDIN_SEEK) + ld h, a + in a, (STDIN_SEEK) + ld l, a + ret + #include "core.asm" .equ BLOCKDEV_RAMSTART RAMSTART .equ BLOCKDEV_COUNT 2 #include "blockdev.asm" ; List of devices -.dw emulGetC, 0, emulSeek, 0 +.dw emulGetC, 0, emulSeek, emulTell .dw 0, emulPutC, 0, 0