zasm: big local symbols overhaul

This commit is contained in:
Virgil Dupras 2019-05-15 20:07:21 -04:00
parent e9244b80ee
commit 0ae91e55ec
6 changed files with 175 additions and 70 deletions

View File

@ -46,3 +46,6 @@ ioSeek:
ld ix, (IO_IN_SEEK)
jp (ix)
ioTell:
ld ix, (IO_IN_TELL)
jp (ix)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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