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