mirror of
https://github.com/hsoft/collapseos.git
synced 2024-12-24 14:28:06 +11:00
zasm: big local symbols overhaul
This commit is contained in:
parent
e9244b80ee
commit
0ae91e55ec
@ -46,3 +46,6 @@ ioSeek:
|
||||
ld ix, (IO_IN_SEEK)
|
||||
jp (ix)
|
||||
|
||||
ioTell:
|
||||
ld ix, (IO_IN_TELL)
|
||||
jp (ix)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user