collapseos/apps/lib/expr.asm

268 lines
5.4 KiB
NASM

; *** Requirements ***
; ari
;
; *** 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.
; HL is advanced to the character following the last successfully
; read char.
;
; *** 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.
; Sets Z on success, unset on error.
parseExpr:
push iy
push ix
push hl
call _parseAddSubst
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
.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, .plus
cp '+'
jr z, .found
ld ix, .minus
cp '-'
ret nz ; unknown char, error
.found:
ld iy, _parseMultDiv
call _parseApply
ret nz
jr .loop
.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
call divide
ld e, c
ld d, b
pop bc ; <-- lvl 1
ret
.mod:
call .div
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
ld d, a
ld a, l
and e
ld e, a
ret
.or:
ld a, h
or d
ld d, a
ld a, l
or e
ld e, a
ret
.xor:
ld a, h
xor d
ld d, a
ld a, l
xor e
ld e, a
ret
.rshift:
ld a, e
and 0xf
ret z
push bc ; --> lvl 1
ld b, a
.rshiftLoop:
srl h
rr l
djnz .rshiftLoop
ex de, hl
pop bc ; <-- lvl 1
ret
.lshift:
ld a, e
and 0xf
ret z
push bc ; --> lvl 1
ld b, a
.lshiftLoop:
sla l
rl h
djnz .lshiftLoop
ex de, hl
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
call EXPR_PARSE ; --> DE
ret nz
; Check if (HL) points to null or op
ld a, (hl)
ret