; *** Requirements *** ; 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 IX. Z for success. ; ; *** Code *** ; ; Parse expression in string at (HL) and returns the result in IX. ; **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. ; TODO: the IX output register is a bit awkward. Nearly everywhere, I need ; to push \ pop that thing. See if we could return the result in DE ; instead. parseExpr: push de push hl call _parseExpr pop hl pop de ret _parseExpr: ld de, exprTbl .loop: ld a, (de) or a jp z, EXPR_PARSE ; no operator, just parse the literal push de ; --> lvl 1. save operator row call _findAndSplit jr z, .found 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: ; special case: is (HL) zero? If yes, it means that our left operand ; is empty. consider it as 0 ld ix, 0 ; pre-set to 0 ld a, (hl) or a jr z, .skip ; Parse left operand in (HL) call parseExpr ret nz ; return immediately if error .skip: ; Now we have parsed everything to the left and we have its result in ; IX. What we need to do now is the same thing on (DE) and then apply ; the + operator. Let's save IX somewhere and parse this. ex de, hl ; right expr now in HL push ix ; --> lvl 1 call parseExpr pop hl ; <-- lvl 1. left push ix \ pop de ; right ret ; Z is parseExpr's result ; 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 .mult: ld b, h ld c, l call multDEBC ; --> HL ex de, hl ret .div: ; divide takes HL/DE push bc call divide ld e, c ld d, b pop bc ret .mod: call .div ex de, hl ret .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 ld b, a .rshiftLoop: srl h rr l djnz .rshiftLoop ex de, hl pop bc ret .lshift: ld a, e and 0xf ret z push bc ld b, a .lshiftLoop: sla l rl h djnz .lshiftLoop ex de, hl pop bc ret