; *** 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. ; ; *** 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 ; 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