2019-11-18 15:52:07 -05:00
|
|
|
; *** Requirements ***
|
2019-12-29 11:42:18 -05:00
|
|
|
; ari
|
2019-11-18 15:52:07 -05:00
|
|
|
;
|
|
|
|
; *** Defines ***
|
|
|
|
;
|
|
|
|
; EXPR_PARSE: routine to call to parse literals or symbols that are part of
|
|
|
|
; the expression. Routine's signature:
|
2019-12-29 17:37:04 -05:00
|
|
|
; String in (HL), returns its parsed value to DE. Z for success.
|
2019-11-18 15:52:07 -05:00
|
|
|
;
|
|
|
|
; *** Code ***
|
|
|
|
;
|
2019-12-23 19:13:44 -05:00
|
|
|
; Parse expression in string at (HL) and returns the result in DE.
|
2019-12-29 11:42:18 -05:00
|
|
|
; This routine needs to be able to mutate (HL), but it takes care of restoring
|
|
|
|
; the string to its original value before returning.
|
2019-05-14 15:26:29 -04:00
|
|
|
; Sets Z on success, unset on error.
|
|
|
|
parseExpr:
|
2019-12-29 11:42:18 -05:00
|
|
|
push iy
|
2019-12-23 15:41:25 -05:00
|
|
|
push ix
|
|
|
|
push hl
|
2019-12-29 11:42:18 -05:00
|
|
|
call _parseAddSubst
|
2019-12-23 15:41:25 -05:00
|
|
|
pop hl
|
|
|
|
pop ix
|
2019-12-29 11:42:18 -05:00
|
|
|
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
|
2019-12-29 16:15:48 -05:00
|
|
|
jr nz, .end
|
2019-12-29 11:42:18 -05:00
|
|
|
push af ; --> lvl 2, save ending operator
|
|
|
|
call callIX
|
|
|
|
pop af ; <-- lvl 2, restore operator.
|
2019-12-29 16:15:48 -05:00
|
|
|
.end:
|
2019-12-29 11:42:18 -05:00
|
|
|
pop hl ; <-- lvl 1, restore str pointer
|
2019-12-23 15:41:25 -05:00
|
|
|
ret
|
|
|
|
|
2019-12-29 11:42:18 -05:00
|
|
|
; 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
|
2019-11-22 14:45:12 -05:00
|
|
|
.loop:
|
2019-12-29 11:42:18 -05:00
|
|
|
; do we have an operator?
|
2019-11-22 14:45:12 -05:00
|
|
|
or a
|
2019-12-29 11:42:18 -05:00
|
|
|
ret z ; null char, we're done
|
|
|
|
; We have an operator. Resolve the rest of the expr then apply it.
|
|
|
|
ld ix, .plus
|
|
|
|
cp '+'
|
2019-11-22 14:45:12 -05:00
|
|
|
jr z, .found
|
2019-12-29 11:42:18 -05:00
|
|
|
ld ix, .minus
|
|
|
|
cp '-'
|
|
|
|
ret nz ; unknown char, error
|
2019-11-22 14:45:12 -05:00
|
|
|
.found:
|
2019-12-29 11:42:18 -05:00
|
|
|
ld iy, _parseMultDiv
|
|
|
|
call _parseApply
|
2019-11-22 14:45:12 -05:00
|
|
|
ret nz
|
2019-12-29 11:42:18 -05:00
|
|
|
jr .loop
|
2019-11-22 14:45:12 -05:00
|
|
|
.plus:
|
2019-11-22 19:56:08 -05:00
|
|
|
add hl, de
|
|
|
|
ex de, hl
|
2019-05-17 22:22:10 -04:00
|
|
|
ret
|
2019-11-22 14:45:12 -05:00
|
|
|
.minus:
|
2019-12-29 11:42:18 -05:00
|
|
|
or a ; clear carry
|
2019-05-14 16:38:19 -04:00
|
|
|
sbc hl, de
|
2019-11-22 19:56:08 -05:00
|
|
|
ex de, hl
|
2019-05-17 22:22:10 -04:00
|
|
|
ret
|
|
|
|
|
2019-12-29 11:42:18 -05:00
|
|
|
; 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
|
|
|
|
|
2019-11-22 14:45:12 -05:00
|
|
|
.mult:
|
2019-12-29 11:42:18 -05:00
|
|
|
push bc ; --> lvl 1
|
2019-11-22 19:56:08 -05:00
|
|
|
ld b, h
|
|
|
|
ld c, l
|
|
|
|
call multDEBC ; --> HL
|
2019-12-29 11:42:18 -05:00
|
|
|
pop bc ; <-- lvl 1
|
2019-11-22 19:56:08 -05:00
|
|
|
ex de, hl
|
2019-05-14 15:26:29 -04:00
|
|
|
ret
|
2019-11-22 15:03:16 -05:00
|
|
|
|
|
|
|
.div:
|
|
|
|
; divide takes HL/DE
|
2019-12-29 11:42:18 -05:00
|
|
|
ld a, l
|
|
|
|
push bc ; --> lvl 1
|
2019-11-22 15:03:16 -05:00
|
|
|
call divide
|
2019-11-22 19:56:08 -05:00
|
|
|
ld e, c
|
|
|
|
ld d, b
|
2019-12-29 11:42:18 -05:00
|
|
|
pop bc ; <-- lvl 1
|
2019-11-22 15:03:16 -05:00
|
|
|
ret
|
|
|
|
|
|
|
|
.mod:
|
|
|
|
call .div
|
2019-11-22 19:56:08 -05:00
|
|
|
ex de, hl
|
2019-11-22 17:16:51 -05:00
|
|
|
ret
|
|
|
|
|
2019-12-29 11:42:18 -05:00
|
|
|
; 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
|
|
|
|
|
2019-11-22 17:16:51 -05:00
|
|
|
.and:
|
|
|
|
ld a, h
|
|
|
|
and d
|
2019-11-22 19:56:08 -05:00
|
|
|
ld d, a
|
2019-11-22 17:16:51 -05:00
|
|
|
ld a, l
|
|
|
|
and e
|
2019-11-22 19:56:08 -05:00
|
|
|
ld e, a
|
2019-11-22 17:16:51 -05:00
|
|
|
ret
|
|
|
|
.or:
|
|
|
|
ld a, h
|
|
|
|
or d
|
2019-11-22 19:56:08 -05:00
|
|
|
ld d, a
|
2019-11-22 17:16:51 -05:00
|
|
|
ld a, l
|
|
|
|
or e
|
2019-11-22 19:56:08 -05:00
|
|
|
ld e, a
|
2019-11-22 17:16:51 -05:00
|
|
|
ret
|
|
|
|
|
|
|
|
.xor:
|
|
|
|
ld a, h
|
|
|
|
xor d
|
2019-11-22 19:56:08 -05:00
|
|
|
ld d, a
|
2019-11-22 17:16:51 -05:00
|
|
|
ld a, l
|
|
|
|
xor e
|
2019-11-22 19:56:08 -05:00
|
|
|
ld e, a
|
2019-11-22 15:03:16 -05:00
|
|
|
ret
|
2019-11-22 18:35:10 -05:00
|
|
|
|
|
|
|
.rshift:
|
2019-11-22 19:56:08 -05:00
|
|
|
ld a, e
|
2019-11-22 18:35:10 -05:00
|
|
|
and 0xf
|
|
|
|
ret z
|
2019-12-29 11:42:18 -05:00
|
|
|
push bc ; --> lvl 1
|
2019-11-22 18:35:10 -05:00
|
|
|
ld b, a
|
|
|
|
.rshiftLoop:
|
2019-11-22 19:56:08 -05:00
|
|
|
srl h
|
|
|
|
rr l
|
2019-11-22 18:35:10 -05:00
|
|
|
djnz .rshiftLoop
|
2019-11-22 19:56:08 -05:00
|
|
|
ex de, hl
|
2019-12-29 11:42:18 -05:00
|
|
|
pop bc ; <-- lvl 1
|
2019-11-22 18:35:10 -05:00
|
|
|
ret
|
|
|
|
|
|
|
|
.lshift:
|
2019-11-22 19:56:08 -05:00
|
|
|
ld a, e
|
2019-11-22 18:35:10 -05:00
|
|
|
and 0xf
|
|
|
|
ret z
|
2019-12-29 11:42:18 -05:00
|
|
|
push bc ; --> lvl 1
|
2019-11-22 18:35:10 -05:00
|
|
|
ld b, a
|
|
|
|
.lshiftLoop:
|
2019-11-22 19:56:08 -05:00
|
|
|
sla l
|
|
|
|
rl h
|
2019-11-22 18:35:10 -05:00
|
|
|
djnz .lshiftLoop
|
2019-11-22 19:56:08 -05:00
|
|
|
ex de, hl
|
2019-12-29 11:42:18 -05:00
|
|
|
pop bc ; <-- lvl 1
|
|
|
|
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
|
2019-12-29 17:37:04 -05:00
|
|
|
call EXPR_PARSE ; --> DE
|
2019-12-29 11:42:18 -05:00
|
|
|
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
|
2019-12-29 17:37:04 -05:00
|
|
|
ret nz
|
2019-12-29 11:42:18 -05:00
|
|
|
; 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
|
2019-11-22 18:35:10 -05:00
|
|
|
ret
|
2019-12-29 11:42:18 -05:00
|
|
|
|
|
|
|
.exprChars:
|
|
|
|
.db "+-*/%&|^{}", 0
|
|
|
|
|