1
0
mirror of https://github.com/hsoft/collapseos.git synced 2024-11-02 20:30:56 +11:00

Compare commits

..

No commits in common. "15628da7de8703e9e6d9873e25289c50ab65bb14" and "7410891ad1bc1189874404e694fae2ea10f373ab" have entirely different histories.

8 changed files with 251 additions and 341 deletions

View File

@ -98,9 +98,12 @@ parseLiteralOrVar:
push hl ; --> lvl 1
ld hl, VAR_TBL
call addHL
push de ; --> lvl 2
ld e, (hl)
inc hl
ld d, (hl)
push de \ pop ix
pop de ; <-- lvl 2
pop hl ; <-- lvl 1
cp a ; ensure Z
ret

View File

@ -1,141 +1,177 @@
; *** Requirements ***
; ari
; findchar
; multDEBC
; callIXI
;
; *** Defines ***
;
; EXPR_PARSE: routine to call to parse literals or symbols that are part of
; the expression. Routine's signature:
; String in (HL), returns its parsed value to DE. Z for success.
; String in (HL), returns its parsed value to IX. Z for success.
;
; *** Code ***
;
; Parse expression in string at (HL) and returns the result in DE.
; This routine needs to be able to mutate (HL), but it takes care of restoring
; the string to its original value before returning.
; **This routine mutates (HL).**
; We expect (HL) to be disposable: we mutate it to avoid having to make a copy.
; Sets Z on success, unset on error.
parseExpr:
push iy
push ix
push hl
call _parseAddSubst
call _parseExpr
push ix \ pop de
pop hl
pop ix
pop iy
ret
; *** Op signature ***
; The signature of "operators routines" (.plus, .mult, etc) below is this:
; Combine HL and DE with an operator (+, -, *, etc) and put the result in DE.
; Destroys HL and A. Never fails. Yes, that's a problem for division by zero.
; Don't divide by zero. All other registers are protected.
; Given a running result in DE, a rest-of-expression in (HL), a parse routine
; in IY and an apply "operator routine" in IX, (HL/DE --> DE)
; With that, parse the rest of (HL) and apply the operation on it, then place
; HL at the end of the parsed string, with A containing the last char of it,
; which can be either an operator or a null char.
; Z for success.
;
_parseApply:
push de ; --> lvl 1, left result
push ix ; --> lvl 2, routine to apply
inc hl ; after op char
call callIY ; --> DE
pop ix ; <-- lvl 2, routine to apply
; Here we do some stack kung fu. We have, in HL, a string pointer we
; want to keep. We have, in (SP), our left result we want to use.
ex (sp), hl ; <-> lvl 1
jr nz, .end
push af ; --> lvl 2, save ending operator
call callIX
pop af ; <-- lvl 2, restore operator.
.end:
pop hl ; <-- lvl 1, restore str pointer
ret
; Unless there's an error, this routine completely resolves any valid expression
; from (HL) and puts the result in DE.
; Destroys HL
; Z for success.
_parseAddSubst:
call _parseMultDiv
ret nz
_parseExpr:
ld de, exprTbl
.loop:
; do we have an operator?
ld a, (de)
or a
ret z ; null char, we're done
; We have an operator. Resolve the rest of the expr then apply it.
ld ix, .plus
cp '+'
jp z, EXPR_PARSE ; no operator, just parse the literal
push de ; --> lvl 1. save operator row
call _findAndSplit
jr z, .found
ld ix, .minus
cp '-'
ret nz ; unknown char, error
.found:
ld iy, _parseMultDiv
call _parseApply
ret nz
pop de ; <-- lvl 1
inc de \ inc de \ inc de
jr .loop
.found:
; Operator found, string splitted. Left in (HL), right in (DE)
call _resolveLeftAndRight
; Whether _resolveLeftAndRight was a success, we pop our lvl 1 stack
; out, which contains our operator row. We pop it in IX.
; L-R numbers are parsed in HL (left) and DE (right).
pop ix ; <-- lvl 1
ret nz
; Resolving left and right succeeded, proceed!
inc ix ; point to routine pointer
call callIXI
push de \ pop ix
cp a ; ensure Z
ret
; Given a string in (HL) and a separator char in A, return a splitted string,
; that is, the same (HL) string but with the found A char replaced by a null
; char. DE points to the second part of the split.
; Sets Z if found, unset if not found.
_findAndSplit:
push hl
call .skipCharLiteral
call findchar
jr nz, .end ; nothing found
; Alright, we have our char and we're pointing at it. Let's replace it
; with a null char.
xor a
ld (hl), a ; + changed to \0
inc hl
ex de, hl ; DE now points to the second part of the split
cp a ; ensure Z
.end:
pop hl ; HL is back to the start
ret
.skipCharLiteral:
; special case: if our first char is ', skip the first 3 characters
; so that we don't mistake a literal for an iterator
push af
ld a, (hl)
cp 0x27 ; '
jr nz, .skipCharLiteralEnd ; not a '
xor a ; check for null char during skipping
; skip 3
inc hl
cp (hl)
jr z, .skipCharLiteralEnd
inc hl
cp (hl)
jr z, .skipCharLiteralEnd
inc hl
.skipCharLiteralEnd:
pop af
ret
.find:
; parse expression on the left (HL) and the right (DE) and put the results in
; HL (left) and DE (right)
_resolveLeftAndRight:
ld a, (hl)
or a
jr z, .noleft
; Parse left operand in (HL)
push de ; --> lvl 1
call parseExpr
pop hl ; <-- lvl 1, orig DE
ret nz ; return immediately if error
.parseright:
; Now we have parsed everything to the left and we have its result in
; DE. What we need to do now is the same thing on (DE) and then apply
; the + operator. Let's save DE somewhere and parse this.
push de ; --> lvl 1
; right expr in (HL)
call parseExpr ; DE is set
pop hl ; <-- lvl 1. left value
ret ; Z is parseExpr's result
.noleft:
; special case: is (HL) zero? If yes, it means that our left operand
; is empty. consider it as 0
ex de, hl ; (DE) goes in (HL) for .parseright
ld de, 0
jr .parseright
; Routines in here all have the same signature: they take two numbers, DE (left)
; and IX (right), apply the operator and put the resulting number in DE.
; The table has 3 bytes per row: 1 byte for operator and 2 bytes for routine
; pointer.
exprTbl:
.db '+'
.dw .plus
.db '-'
.dw .minus
.db '*'
.dw .mult
.db '/'
.dw .div
.db '%'
.dw .mod
.db '&'
.dw .and
.db 0x7c ; '|'
.dw .or
.db '^'
.dw .xor
.db '}'
.dw .rshift
.db '{'
.dw .lshift
.db 0 ; end of table
.plus:
add hl, de
ex de, hl
ret
.minus:
or a ; clear carry
sbc hl, de
ex de, hl
ret
; Parse (HL) as far as it can, that is, resolving expressions at its level or
; lower (anything but + and -).
; A is set to the last op it encountered. Unless there's an error, this can only
; be +, - or null. Null if we're done parsing, + and - if there's still work to
; do.
; (HL) points to last op encountered.
; DE is set to the numerical value of everything that was parsed left of (HL).
_parseMultDiv:
call _parseBitShift
ret nz
.loop:
; do we have an operator?
or a
ret z ; null char, we're done
; We have an operator. Resolve the rest of the expr then apply it.
ld ix, .mult
cp '*'
jr z, .found
ld ix, .div
cp '/'
jr z, .found
ld ix, .mod
cp '%'
jr z, .found
; might not be an error, return success
cp a
ret
.found:
ld iy, _parseBitShift
call _parseApply
ret nz
jr .loop
.mult:
push bc ; --> lvl 1
ld b, h
ld c, l
call multDEBC ; --> HL
pop bc ; <-- lvl 1
ex de, hl
ret
.div:
; divide takes HL/DE
ld a, l
push bc ; --> lvl 1
push bc
call divide
ld e, c
ld d, b
pop bc ; <-- lvl 1
pop bc
ret
.mod:
@ -143,39 +179,6 @@ _parseMultDiv:
ex de, hl
ret
; Same as _parseMultDiv, but a layer lower.
_parseBitShift:
call _parseNumber
ret nz
.loop:
; do we have an operator?
or a
ret z ; null char, we're done
; We have an operator. Resolve the rest of the expr then apply it.
ld ix, .and
cp '&'
jr z, .found
ld ix, .or
cp 0x7c ; '|'
jr z, .found
ld ix, .xor
cp '^'
jr z, .found
ld ix, .rshift
cp '}'
jr z, .found
ld ix, .lshift
cp '{'
jr z, .found
; might not be an error, return success
cp a
ret
.found:
ld iy, _parseNumber
call _parseApply
ret nz
jr .loop
.and:
ld a, h
and d
@ -206,126 +209,26 @@ _parseBitShift:
ld a, e
and 0xf
ret z
push bc ; --> lvl 1
push bc
ld b, a
.rshiftLoop:
srl h
rr l
djnz .rshiftLoop
ex de, hl
pop bc ; <-- lvl 1
pop bc
ret
.lshift:
ld a, e
and 0xf
ret z
push bc ; --> lvl 1
push bc
ld b, a
.lshiftLoop:
sla l
rl h
djnz .lshiftLoop
ex de, hl
pop bc ; <-- lvl 1
pop bc
ret
; Parse first number of expression at (HL). A valid number is anything that can
; be parsed by EXPR_PARSE and is followed either by a null char or by any of the
; operator chars. This routines takes care of replacing an operator char with
; the null char before calling EXPR_PARSE and then replace the operator back
; afterwards.
; HL is moved to the char following the number having been parsed.
; DE contains the numerical result.
; A contains the operator char following the number (or null). Only on success.
; Z for success.
_parseNumber:
; Special case 1: number starts with '-'
ld a, (hl)
cp '-'
jr nz, .skip1
; We have a negative number. Parse normally, then subst from zero
inc hl
call _parseNumber
push hl ; --> lvl 1
ex af, af' ; preserve flags
or a ; clear carry
ld hl, 0
sbc hl, de
ex de, hl
ex af, af' ; restore flags
pop hl ; <-- lvl 1
ret
.skip1:
; End of special case 1
; Copy beginning of string to DE, we'll need it later
ld d, h
ld e, l
; Special case 2: we have a char literal. If we have a char literal, we
; don't want to go through the "_isOp" loop below because if that char
; is one of our operators, we're messing up our processing. So, set
; ourselves 3 chars further and continue from there. EXPR_PARSE will
; take care of validating those 3 chars.
cp 0x27 ; apostrophe (') char
jr nz, .skip2
; "'". advance HL by 3
inc hl \ inc hl \ inc hl
; End of special case 2
.skip2:
dec hl ; offset "inc-hl-before" in loop
.loop:
inc hl
ld a, (hl)
call _isOp
jr nz, .loop
; (HL) and A is an op or a null
push af ; --> lvl 1 save op
push hl ; --> lvl 2 save end of string
; temporarily put a null char instead of the op
xor a
ld (hl), a
ex de, hl ; rewind to beginning of number
call EXPR_PARSE ; --> DE
ex af, af' ; keep result flags away while we restore (HL)
pop hl ; <-- lvl 2, end of string
pop af ; <-- lvl 1, saved op
ld (hl), a
ex af, af' ; restore Z from EXPR_PARSE
ret nz
; HL is currently at the end of the number's string
; On success, have A be the operator char following the number
ex af, af'
ret
; Sets Z if A contains a valid operator char or a null char.
_isOp:
or a
ret z
push hl ; --> lvl 1
; Set A' to zero for quick end-of-table checks
ex af, af'
xor a
ex af, af'
ld hl, .exprChars
.loop:
cp (hl)
jr z, .found
ex af, af'
cp (hl)
jr z, .notFound ; end of table
ex af, af'
inc hl ; next char
jr .loop
.notFound:
ex af, af' ; restore orig A
inc a ; unset Z
.found:
; Z already set
pop hl ; <-- lvl 1
ret
.exprChars:
.db "+-*/%&|^{}", 0

View File

@ -67,7 +67,7 @@ parseHexPair:
; add a, 0xff-'9' ; maps '0'-'9' onto 0xf6-0xff
; sub 0xff-9 ; maps to 0-9 and carries if not a digit
; Parse string at (HL) as a decimal value and return value in DE under the
; Parse string at (HL) as a decimal value and return value in IX under the
; same conditions as parseLiteral.
; Sets Z on success, unset on error.
; To parse successfully, all characters following HL must be digits and those
@ -76,7 +76,7 @@ parseHexPair:
; digit in the string.
parseDecimal:
push hl ; --> lvl 1
push hl
ld a, (hl)
add a, 0xff-'9' ; maps '0'-'9' onto 0xf6-0xff
@ -129,23 +129,23 @@ parseDecimal:
; to 0x00+(0xff-'9')-(0xff-9)=-0x30=0xd0
sub 0xd0 ; if a is null, set Z
; a is checked for null before any errors
push hl ; --> lvl 2, result
exx ; restore original bc
pop de ; <-- lvl 2, result
pop hl ; <-- lvl 1, orig
push hl \ pop ix
exx ; restore original de and bc
pop hl
ret z
; A is not 0? Ok, but if it's a space, we're happy too.
jp isWS
.error:
pop hl ; <-- lvl 1, orig
pop hl
jp unsetZ
; Parse string at (HL) as a hexadecimal value and return value in DE under the
; Parse string at (HL) as a hexadecimal value and return value in IX under the
; same conditions as parseLiteral.
parseHexadecimal:
call hasHexPrefix
ret nz
push hl
push de
ld d, 0
inc hl ; get rid of "0x"
inc hl
@ -179,6 +179,8 @@ parseHexadecimal:
.error:
call unsetZ
.end:
push de \ pop ix
pop de
pop hl
ret
@ -194,14 +196,15 @@ hasHexPrefix:
pop hl
ret
; Parse string at (HL) as a binary value (0b010101) and return value in E.
; D is always zero.
; Parse string at (HL) as a binary value (0b010101) and return value in IX.
; High IX byte is always clear.
; Sets Z on success.
parseBinaryLiteral:
call hasBinPrefix
ret nz
push bc
push hl
push de
ld d, 0
inc hl ; get rid of "0b"
inc hl
@ -233,6 +236,8 @@ parseBinaryLiteral:
.error:
call unsetZ
.end:
push de \ pop ix
pop de
pop hl
pop bc
ret
@ -250,7 +255,7 @@ hasBinPrefix:
ret
; Parse string at (HL) and, if it is a char literal, sets Z and return
; corresponding value in E. D is always zero.
; corresponding value in IX. High IX byte is always clear.
;
; A valid char literal starts with ', ends with ' and has one character in the
; middle. No escape sequence are accepted, but ''' will return the apostrophe
@ -261,6 +266,7 @@ parseCharLiteral:
ret nz
push hl
push de
inc hl
inc hl
cp (hl)
@ -277,10 +283,12 @@ parseCharLiteral:
ld e, a
cp a ; ensure Z
.end:
push de \ pop ix
pop de
pop hl
ret
; Parses the string at (HL) and returns the 16-bit value in DE. The string
; Parses the string at (HL) and returns the 16-bit value in IX. The string
; can be a decimal literal (1234), a hexadecimal literal (0x1234) or a char
; literal ('X').
;

View File

@ -657,6 +657,7 @@ _readDouble:
_readk7:
push hl
push de
push ix
call parseExpr
jr nz, .end
; If we're in first pass, stop now. The value of HL doesn't matter and
@ -685,6 +686,7 @@ _readk7:
ld a, l
cp a ; ensure Z
.end:
pop ix
pop de
pop hl
ret
@ -705,6 +707,7 @@ _readR4:
; Set Z for success.
_readR5:
push de
push ix
ld a, (hl)
call upcase
cp 'R'
@ -712,9 +715,11 @@ _readR5:
inc hl
call parseDecimal
jr nz, .end
push ix \ pop de
ld a, 31
call _DE2A
.end:
pop ix
pop de
ret

View File

@ -1,5 +1,5 @@
; Parse string in (HL) and return its numerical value whether its a number
; literal or a symbol. Returns value in DE.
; literal or a symbol. Returns value in IX.
; Sets Z if number or symbol is valid, unset otherwise.
parseNumberOrSymbol:
call parseLiteral
@ -14,25 +14,31 @@ parseNumberOrSymbol:
cp '@'
jr nz, .symbol
; last val
ld de, (DIREC_LASTVAL)
ld ix, (DIREC_LASTVAL)
ret
.symbol:
push de ; --> lvl 1
call symFindVal ; --> DE
jr nz, .notfound
; value in DE. We need it in IX
push de \ pop ix
pop de ; <-- lvl 1
cp a ; ensure Z
ret
.notfound:
pop de ; <-- lvl 1
; If not found, check if we're in first pass. If we are, it doesn't
; matter that we didn't find our symbol. Return success anyhow.
; Otherwise return error. Z is already unset, so in fact, this is the
; same as jumping to zasmIsFirstPass
; however, before we do, load DE with zero. Returning dummy non-zero
; however, before we do, load IX with zero. Returning dummy non-zero
; values can have weird consequence (such as false overflow errors).
ld de, 0
ld ix, 0
jp zasmIsFirstPass
.returnPC:
push hl
call zasmGetPC
ex de, hl ; result in DE
push hl \ pop ix
pop hl
ret

View File

@ -6,12 +6,6 @@
; lib/fmt
testNum: .db 1
; Each time we call assertSP, we verify that our stack isn't imbalanced by
; comparing SP to its saved value. Whenever your "base" SP value change,
; generally at the beginning of a test routine, run "ld (testSP), sp" to have
; proper value saved to heap.
testSP: .dw 0xffff
STDIO_PUTC:
out (0), a
@ -57,38 +51,6 @@ assertEQW:
.msg:
.db "HL != DE", CR, LF, 0
; Given a list of pointer to test data structures in HL and a pointer to a test
; routine in IX, call (IX) with HL pointing to the test structure until the list
; points to a zero. See testParseExpr in test_expr for an example usage.
testList:
push hl ; --> lvl 1
call intoHL
ld a, h
or l
jr z, .end
call callIX
call nexttest
pop hl ; <-- lvl 1
inc hl \ inc hl
jr testList
.end:
pop hl ; <-- lvl 1
ret
; test that SP == testSP
assertSP:
ld hl, (testSP)
; offset the fact that we call assertSP
dec hl \ dec hl
or a ; reset carry
sbc hl, sp
ret z
ld hl, .msg
call printstr
jr fail
.msg:
.db "Wrong SP", CR, LF, 0
nexttest:
ld a, (testNum)
inc a

View File

@ -34,6 +34,12 @@ zasmIsFirstPass:
zasmGetPC:
ret
s1: .db "2+2", 0
s2: .db "0x4001+0x22", 0
s3: .db "FOO+BAR", 0
s4: .db "BAR*3", 0
s5: .db "FOO-3", 0
s6: .db "FOO+BAR*4", 0
sFOO: .db "FOO", 0
sBAR: .db "BAR", 0
@ -41,7 +47,25 @@ sBAR: .db "BAR", 0
test:
ld sp, 0xffff
; before testing begins, let's set up FOO and BAR symbols
; New-style tests
call testParseExpr
; Old-style tests, not touching them now.
ld hl, s1
call parseExpr
call assertZ
ld hl, 4
call assertEQW
call nexttest
ld hl, s2
call parseExpr
call assertZ
ld hl, 0x4023
call assertEQW
call nexttest
; before the next test, let's set up FOO and BAR symbols
call symInit
ld hl, sFOO
ld de, 0x4000
@ -52,26 +76,68 @@ test:
call symRegisterGlobal
jp nz, fail
call testParseExpr
call testSPOnFail
ld hl, s3
call parseExpr
call assertZ
ld hl, 0x4020
call assertEQW
call nexttest
ld hl, s4
call parseExpr
call assertZ
ld hl, 0x60
call assertEQW
call nexttest
ld hl, s5
call parseExpr
call assertZ
ld hl, 0x3ffd
call assertEQW
call nexttest
ld hl, s6
call parseExpr
call assertZ
ld hl, 0x4080
call assertEQW
call nexttest
; success
xor a
halt
testParseExpr:
ld hl, .alltests
ld ix, .test
jp testList
ld iy, .t1
call .testEQ
ld iy, .t2
call .testEQ
ld iy, .t3
call .testEQ
ld iy, .t4
call .testEQ
ld iy, .t5
call .testEQ
ld iy, .t6
call .testEQ
ld iy, .t7
call .testEQ
ld iy, .t8
call .testEQ
ld iy, .t9
call .testEQ
ret
.test:
push hl \ pop iy
.testEQ:
push iy \ pop hl
inc hl \ inc hl
call parseExpr
call assertZ
ld l, (iy)
ld h, (iy+1)
jp assertEQW
call assertEQW
jp nexttest
.t1:
.dw 7
@ -100,46 +166,3 @@ testParseExpr:
.t9:
.dw 10
.db "2*3+4", 0
; There was this untested regression during the replacement of find-and-subst
; parseExpr to the recursive descent one. It was time consuming to find. Here
; it goes, here it stays.
.t10:
.dw '-'+1
.db "'-'+1", 0
.t11:
.dw 0x4023
.db "0x4001+0x22", 0
.t12:
.dw 0x4020
.db "FOO+BAR", 0
.t13:
.dw 0x60
.db "BAR*3", 0
.t14:
.dw 0x3ffd
.db "FOO-3", 0
.t15:
.dw 0x4080
.db "FOO+BAR*4", 0
.alltests:
.dw .t1, .t2, .t3, .t4, .t5, .t6, .t7, .t8, .t9, .t10, .t11, .t12
.dw .t13, .t14, .t15, 0
; Ensure that stack is balanced on failure
testSPOnFail:
ld (testSP), sp
ld hl, .sFail
call parseExpr
call assertNZ
call assertSP
jp nexttest
.sFail: .db "1+abc123", 0

View File

@ -13,9 +13,9 @@ cmpas() {
echo ok
else
echo actual
echo "$ACTUAL"
echo $ACTUAL
echo expected
echo "$EXPECTED"
echo $EXPECTED
exit 1
fi
}