@@ -1,133 +0,0 @@ | |||||
; Parse the decimal char at A and extract it's 0-9 numerical value. Put the | |||||
; result in A. | |||||
; | |||||
; On success, the carry flag is reset. On error, it is set. | |||||
parseDecimalDigit: | |||||
; First, let's see if we have an easy 0-9 case | |||||
cp '0' | |||||
ret c ; if < '0', we have a problem | |||||
sub a, '0' ; our value now is valid if it's < 10 | |||||
cp 10 ; on success, C is set, which is the opposite | |||||
; of what we want | |||||
ccf ; invert C flag | |||||
ret | |||||
; Parse string at (HL) as a decimal value and return value in IX under the | |||||
; same conditions as parseNumber. | |||||
parseDecimal: | |||||
push hl | |||||
push de | |||||
push bc | |||||
ld ix, 0 | |||||
.loop: | |||||
ld a, (hl) | |||||
cp 0 | |||||
jr z, .end ; success! | |||||
call parseDecimalDigit | |||||
jr c, .error | |||||
; Now, let's add A to IX. First, multiply by 10. | |||||
ld d, ixh ; we need a copy of the initial copy for later | |||||
ld e, ixl | |||||
add ix, ix ; x2 | |||||
add ix, ix ; x4 | |||||
add ix, ix ; x8 | |||||
add ix, de ; x9 | |||||
add ix, de ; x10 | |||||
add a, ixl | |||||
jr nc, .nocarry | |||||
inc ixh | |||||
.nocarry: | |||||
ld ixl, a | |||||
; We didn't bother checking for the C flag at each step because we | |||||
; check for overflow afterwards. If ixh < d, we overflowed | |||||
ld a, ixh | |||||
cp d | |||||
jr c, .error ; carry is set? overflow | |||||
inc hl | |||||
jr .loop | |||||
.error: | |||||
call JUMP_UNSETZ | |||||
.end: | |||||
pop bc | |||||
pop de | |||||
pop hl | |||||
ret | |||||
; Parse string at (HL) as a hexadecimal value and return value in IX under the | |||||
; same conditions as parseNumber. | |||||
parseHexadecimal: | |||||
push hl | |||||
xor a | |||||
ld ixh, a | |||||
inc hl ; get rid of "0x" | |||||
inc hl | |||||
call strlen | |||||
cp 3 | |||||
jr c, .single | |||||
cp 5 | |||||
jr c, .double | |||||
; too long, error | |||||
jr .error | |||||
.double: | |||||
call JUMP_PARSEHEXPAIR ; moves HL to last char of pair | |||||
jr c, .error | |||||
inc hl ; now HL is on first char of next pair | |||||
ld ixh, a | |||||
.single: | |||||
call JUMP_PARSEHEXPAIR | |||||
jr c, .error | |||||
ld ixl, a | |||||
cp a ; ensure Z | |||||
jr .end | |||||
.error: | |||||
call JUMP_UNSETZ | |||||
.end: | |||||
pop hl | |||||
ret | |||||
; Sets Z if (HL) has a '0x' or '0X' prefix. | |||||
hasHexPrefix: | |||||
ld a, (hl) | |||||
cp '0' | |||||
ret nz | |||||
push hl | |||||
inc hl | |||||
ld a, (hl) | |||||
cp 'x' | |||||
jr z, .end | |||||
cp 'X' | |||||
.end: | |||||
pop hl | |||||
ret | |||||
; Parses the string at (HL) and returns the 16-bit value in IX. | |||||
; As soon as the number doesn't fit 16-bit any more, parsing stops and the | |||||
; number is invalid. If the number is valid, Z is set, otherwise, unset. | |||||
parseNumber: | |||||
call hasHexPrefix | |||||
jr z, parseHexadecimal | |||||
jr parseDecimal | |||||
; Parse string in (HL) and return its numerical value whether its a number | |||||
; literal or a symbol. Returns value in IX. | |||||
; Sets Z if number or symbol is valid, unset otherwise. | |||||
parseNumberOrSymbol: | |||||
call parseNumber | |||||
ret z | |||||
; Not a number. Try symbol | |||||
push de | |||||
call symGetVal | |||||
jr nz, .notfound ; Z already unset | |||||
; Found! value in DE. We need it in IX | |||||
ld ixh, d | |||||
ld ixl, e | |||||
; Z already set | |||||
.notfound: | |||||
pop de | |||||
ret |
@@ -44,8 +44,8 @@ jp zasmMain | |||||
#include "util.asm" | #include "util.asm" | ||||
.equ IO_RAMSTART ZASM_RAMEND | .equ IO_RAMSTART ZASM_RAMEND | ||||
#include "io.asm" | #include "io.asm" | ||||
#include "tok.asm" | |||||
#include "parse.asm" | #include "parse.asm" | ||||
#include "literal.asm" | |||||
#include "instr.asm" | #include "instr.asm" | ||||
#include "directive.asm" | #include "directive.asm" | ||||
.equ SYM_RAMSTART IO_RAMEND | .equ SYM_RAMSTART IO_RAMEND | ||||
@@ -1,146 +1,133 @@ | |||||
; *** Consts *** | |||||
TOK_INSTR .equ 0x01 | |||||
TOK_DIRECTIVE .equ 0x02 | |||||
TOK_LABEL .equ 0x03 | |||||
TOK_EMPTY .equ 0xfe ; not a bad token, just an empty line | |||||
TOK_BAD .equ 0xff | |||||
; Parse the decimal char at A and extract it's 0-9 numerical value. Put the | |||||
; result in A. | |||||
; | |||||
; On success, the carry flag is reset. On error, it is set. | |||||
parseDecimalDigit: | |||||
; First, let's see if we have an easy 0-9 case | |||||
cp '0' | |||||
ret c ; if < '0', we have a problem | |||||
sub a, '0' ; our value now is valid if it's < 10 | |||||
cp 10 ; on success, C is set, which is the opposite | |||||
; of what we want | |||||
ccf ; invert C flag | |||||
ret | |||||
.equ SCRATCHPAD_SIZE 0x20 | |||||
; *** Variables *** | |||||
scratchpad: | |||||
.fill SCRATCHPAD_SIZE | |||||
; Parse string at (HL) as a decimal value and return value in IX under the | |||||
; same conditions as parseNumber. | |||||
parseDecimal: | |||||
push hl | |||||
push de | |||||
push bc | |||||
; *** Code *** | |||||
ld ix, 0 | |||||
.loop: | |||||
ld a, (hl) | |||||
cp 0 | |||||
jr z, .end ; success! | |||||
call parseDecimalDigit | |||||
jr c, .error | |||||
; Sets Z is A is ';' or null. | |||||
isLineEndOrComment: | |||||
cp ';' | |||||
ret z | |||||
or a ; cp 0 | |||||
ret | |||||
; Now, let's add A to IX. First, multiply by 10. | |||||
ld d, ixh ; we need a copy of the initial copy for later | |||||
ld e, ixl | |||||
add ix, ix ; x2 | |||||
add ix, ix ; x4 | |||||
add ix, ix ; x8 | |||||
add ix, de ; x9 | |||||
add ix, de ; x10 | |||||
add a, ixl | |||||
jr nc, .nocarry | |||||
inc ixh | |||||
.nocarry: | |||||
ld ixl, a | |||||
; Sets Z is A is ' ' '\t' or ',' | |||||
isSep: | |||||
cp ' ' | |||||
ret z | |||||
cp 0x09 | |||||
ret z | |||||
cp ',' | |||||
ret | |||||
; We didn't bother checking for the C flag at each step because we | |||||
; check for overflow afterwards. If ixh < d, we overflowed | |||||
ld a, ixh | |||||
cp d | |||||
jr c, .error ; carry is set? overflow | |||||
; Sets Z is A is ' ', ',', ';', CR, LF, or null. | |||||
isSepOrLineEnd: | |||||
call isSep | |||||
ret z | |||||
call isLineEndOrComment | |||||
inc hl | |||||
jr .loop | |||||
.error: | |||||
call JUMP_UNSETZ | |||||
.end: | |||||
pop bc | |||||
pop de | |||||
pop hl | |||||
ret | ret | ||||
; Checks whether string at (HL) is a label, that is, whether it ends with a ":" | |||||
; Sets Z if yes, unset if no. | |||||
; | |||||
; If it's a label, we change the trailing ':' char with a null char. It's a bit | |||||
; dirty, but it's the easiest way to proceed. | |||||
isLabel: | |||||
; Parse string at (HL) as a hexadecimal value and return value in IX under the | |||||
; same conditions as parseNumber. | |||||
parseHexadecimal: | |||||
push hl | push hl | ||||
ld a, ':' | |||||
call JUMP_FINDCHAR | |||||
ld a, (hl) | |||||
cp ':' | |||||
jr nz, .nomatch | |||||
; We also have to check that it's our last char. | |||||
xor a | |||||
ld ixh, a | |||||
inc hl ; get rid of "0x" | |||||
inc hl | inc hl | ||||
ld a, (hl) | |||||
or a ; cp 0 | |||||
jr nz, .nomatch ; not a null char following the :. no match. | |||||
; We have a match! | |||||
; Remove trailing ':' | |||||
xor a ; Z is set | |||||
ld (hl), a | |||||
call strlen | |||||
cp 3 | |||||
jr c, .single | |||||
cp 5 | |||||
jr c, .double | |||||
; too long, error | |||||
jr .error | |||||
.double: | |||||
call JUMP_PARSEHEXPAIR ; moves HL to last char of pair | |||||
jr c, .error | |||||
inc hl ; now HL is on first char of next pair | |||||
ld ixh, a | |||||
.single: | |||||
call JUMP_PARSEHEXPAIR | |||||
jr c, .error | |||||
ld ixl, a | |||||
cp a ; ensure Z | |||||
jr .end | jr .end | ||||
.nomatch: | |||||
.error: | |||||
call JUMP_UNSETZ | call JUMP_UNSETZ | ||||
.end: | .end: | ||||
pop hl | pop hl | ||||
ret | ret | ||||
; read word in (HL) and put it in (scratchpad), null terminated, for a maximum | |||||
; of SCRATCHPAD_SIZE-1 characters. As a result, A is the read length. HL is | |||||
; advanced to the next separator char. | |||||
readWord: | |||||
push bc | |||||
push de | |||||
ld de, scratchpad | |||||
ld b, SCRATCHPAD_SIZE-1 | |||||
.loop: | |||||
; Sets Z if (HL) has a '0x' or '0X' prefix. | |||||
hasHexPrefix: | |||||
ld a, (hl) | ld a, (hl) | ||||
call isSepOrLineEnd | |||||
jr z, .success | |||||
call JUMP_UPCASE | |||||
ld (de), a | |||||
cp '0' | |||||
ret nz | |||||
push hl | |||||
inc hl | inc hl | ||||
inc de | |||||
djnz .loop | |||||
.success: | |||||
xor a | |||||
ld (de), a | |||||
ld a, SCRATCHPAD_SIZE-1 | |||||
sub a, b | |||||
.end: | |||||
pop de | |||||
pop bc | |||||
ret | |||||
; (HL) being a string, advance it to the next non-sep character. | |||||
; Set Z if we could do it before the line ended, reset Z if we couldn't. | |||||
toWord: | |||||
.loop: | |||||
ld a, (hl) | ld a, (hl) | ||||
call isLineEndOrComment | |||||
jr z, .error | |||||
call isSep | |||||
jr nz, .success | |||||
inc hl | |||||
jr .loop | |||||
.error: | |||||
call JUMP_UNSETZ | |||||
ret | |||||
.success: | |||||
xor a ; ensure Z | |||||
ret | |||||
; Parse line in (HL) and read the next token in BC. The token is written on | |||||
; two bytes (B and C). B is a token type (TOK_* constants) and C is an ID | |||||
; specific to that token type. | |||||
; Advance HL to after the read word. | |||||
; If no token matches, TOK_BAD is written to B | |||||
tokenize: | |||||
call toWord | |||||
jr nz, .emptyline | |||||
call readWord | |||||
push hl ; Save advanced HL for later | |||||
ld hl, scratchpad | |||||
call isLabel | |||||
jr z, .label | |||||
call getInstID | |||||
jr z, .instr | |||||
call getDirectiveID | |||||
jr z, .direc | |||||
; no match | |||||
ld b, TOK_BAD | |||||
jr .end | |||||
.instr: | |||||
ld b, TOK_INSTR | |||||
jr .end | |||||
.direc: | |||||
ld b, TOK_DIRECTIVE | |||||
jr .end | |||||
.label: | |||||
ld b, TOK_LABEL | |||||
cp 'x' | |||||
jr z, .end | |||||
cp 'X' | |||||
.end: | .end: | ||||
ld c, a | |||||
pop hl | pop hl | ||||
ret | ret | ||||
.emptyline: | |||||
ld b, TOK_EMPTY | |||||
; no HL to pop, we jumped before the push | |||||
; Parses the string at (HL) and returns the 16-bit value in IX. | |||||
; As soon as the number doesn't fit 16-bit any more, parsing stops and the | |||||
; number is invalid. If the number is valid, Z is set, otherwise, unset. | |||||
parseNumber: | |||||
call hasHexPrefix | |||||
jr z, parseHexadecimal | |||||
jr parseDecimal | |||||
; Parse string in (HL) and return its numerical value whether its a number | |||||
; literal or a symbol. Returns value in IX. | |||||
; Sets Z if number or symbol is valid, unset otherwise. | |||||
parseNumberOrSymbol: | |||||
call parseNumber | |||||
ret z | |||||
; Not a number. Try symbol | |||||
push de | |||||
call symGetVal | |||||
jr nz, .notfound ; Z already unset | |||||
; Found! value in DE. We need it in IX | |||||
ld ixh, d | |||||
ld ixl, e | |||||
; Z already set | |||||
.notfound: | |||||
pop de | |||||
ret | ret |
@@ -0,0 +1,146 @@ | |||||
; *** Consts *** | |||||
TOK_INSTR .equ 0x01 | |||||
TOK_DIRECTIVE .equ 0x02 | |||||
TOK_LABEL .equ 0x03 | |||||
TOK_EMPTY .equ 0xfe ; not a bad token, just an empty line | |||||
TOK_BAD .equ 0xff | |||||
.equ SCRATCHPAD_SIZE 0x20 | |||||
; *** Variables *** | |||||
scratchpad: | |||||
.fill SCRATCHPAD_SIZE | |||||
; *** Code *** | |||||
; Sets Z is A is ';' or null. | |||||
isLineEndOrComment: | |||||
cp ';' | |||||
ret z | |||||
or a ; cp 0 | |||||
ret | |||||
; Sets Z is A is ' ' '\t' or ',' | |||||
isSep: | |||||
cp ' ' | |||||
ret z | |||||
cp 0x09 | |||||
ret z | |||||
cp ',' | |||||
ret | |||||
; Sets Z is A is ' ', ',', ';', CR, LF, or null. | |||||
isSepOrLineEnd: | |||||
call isSep | |||||
ret z | |||||
call isLineEndOrComment | |||||
ret | |||||
; Checks whether string at (HL) is a label, that is, whether it ends with a ":" | |||||
; Sets Z if yes, unset if no. | |||||
; | |||||
; If it's a label, we change the trailing ':' char with a null char. It's a bit | |||||
; dirty, but it's the easiest way to proceed. | |||||
isLabel: | |||||
push hl | |||||
ld a, ':' | |||||
call JUMP_FINDCHAR | |||||
ld a, (hl) | |||||
cp ':' | |||||
jr nz, .nomatch | |||||
; We also have to check that it's our last char. | |||||
inc hl | |||||
ld a, (hl) | |||||
or a ; cp 0 | |||||
jr nz, .nomatch ; not a null char following the :. no match. | |||||
; We have a match! | |||||
; Remove trailing ':' | |||||
xor a ; Z is set | |||||
ld (hl), a | |||||
jr .end | |||||
.nomatch: | |||||
call JUMP_UNSETZ | |||||
.end: | |||||
pop hl | |||||
ret | |||||
; read word in (HL) and put it in (scratchpad), null terminated, for a maximum | |||||
; of SCRATCHPAD_SIZE-1 characters. As a result, A is the read length. HL is | |||||
; advanced to the next separator char. | |||||
readWord: | |||||
push bc | |||||
push de | |||||
ld de, scratchpad | |||||
ld b, SCRATCHPAD_SIZE-1 | |||||
.loop: | |||||
ld a, (hl) | |||||
call isSepOrLineEnd | |||||
jr z, .success | |||||
call JUMP_UPCASE | |||||
ld (de), a | |||||
inc hl | |||||
inc de | |||||
djnz .loop | |||||
.success: | |||||
xor a | |||||
ld (de), a | |||||
ld a, SCRATCHPAD_SIZE-1 | |||||
sub a, b | |||||
.end: | |||||
pop de | |||||
pop bc | |||||
ret | |||||
; (HL) being a string, advance it to the next non-sep character. | |||||
; Set Z if we could do it before the line ended, reset Z if we couldn't. | |||||
toWord: | |||||
.loop: | |||||
ld a, (hl) | |||||
call isLineEndOrComment | |||||
jr z, .error | |||||
call isSep | |||||
jr nz, .success | |||||
inc hl | |||||
jr .loop | |||||
.error: | |||||
call JUMP_UNSETZ | |||||
ret | |||||
.success: | |||||
xor a ; ensure Z | |||||
ret | |||||
; Parse line in (HL) and read the next token in BC. The token is written on | |||||
; two bytes (B and C). B is a token type (TOK_* constants) and C is an ID | |||||
; specific to that token type. | |||||
; Advance HL to after the read word. | |||||
; If no token matches, TOK_BAD is written to B | |||||
tokenize: | |||||
call toWord | |||||
jr nz, .emptyline | |||||
call readWord | |||||
push hl ; Save advanced HL for later | |||||
ld hl, scratchpad | |||||
call isLabel | |||||
jr z, .label | |||||
call getInstID | |||||
jr z, .instr | |||||
call getDirectiveID | |||||
jr z, .direc | |||||
; no match | |||||
ld b, TOK_BAD | |||||
jr .end | |||||
.instr: | |||||
ld b, TOK_INSTR | |||||
jr .end | |||||
.direc: | |||||
ld b, TOK_DIRECTIVE | |||||
jr .end | |||||
.label: | |||||
ld b, TOK_LABEL | |||||
.end: | |||||
ld c, a | |||||
pop hl | |||||
ret | |||||
.emptyline: | |||||
ld b, TOK_EMPTY | |||||
; no HL to pop, we jumped before the push | |||||
ret |