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