; Parse string at (HL) as a hexadecimal value and return value in IX under the
; same conditions as parseLiteral.
parseHexadecimal:
	call	hasHexPrefix
	ret	nz
	push	hl
	push	de
	ld	d, 0
	inc	hl	; get rid of "0x"
	inc	hl
	call	strlen
	cp	3
	jr	c, .single
	cp	4
	jr	c, .doubleShort	; 0x123
	cp	5
	jr	c, .double	; 0x1234
	; too long, error
	jr	.error
.double:
	call	parseHexPair
	jr	c, .error
	inc	hl			; now HL is on first char of next pair
	ld	d, a
	jr	.single
.doubleShort:
	ld	a, (hl)
	call	parseHex
	jr	c, .error
	inc	hl			; now HL is on first char of next pair
	ld	d, a
.single:
	call	parseHexPair
	jr	c, .error
	ld	e, a
	cp	a			; ensure Z
	jr	.end
.error:
	call	unsetZ
.end:
	push	de \ pop ix
	pop	de
	pop	hl
	ret

; Sets Z if (HL) has a '0x' prefix.
hasHexPrefix:
	ld	a, (hl)
	cp	'0'
	ret	nz
	push	hl
	inc	hl
	ld	a, (hl)
	cp	'x'
	pop	hl
	ret

; Parse string at (HL) as a binary value (0b010101) and return value in IX.
; High IX byte is always clear.
; Sets Z on success.
parseBinaryLiteral:
	call	hasBinPrefix
	ret	nz
	push	bc
	push	hl
	push	de
	ld	d, 0
	inc	hl	; get rid of "0b"
	inc	hl
	call	strlen
	or	a
	jr	z, .error	; empty, error
	cp	9
	jr	nc, .error	; >= 9, too long
	; We have a string of 8 or less chars. What we'll do is that for each
	; char, we rotate left and set the LSB according to whether we have '0'
	; or '1'. Error out on anything else. C is our stored result.
	ld	b, a		; we loop for "strlen" times
	ld	c, 0		; our stored result
.loop:
	rlc	c
	ld	a, (hl)
	inc	hl
	cp	'0'
	jr	z, .nobit	; no bit to set
	cp	'1'
	jr	nz, .error	; not 0 or 1
	; We have a bit to set
	inc	c
.nobit:
	djnz	.loop
	ld	e, c
	cp	a		; ensure Z
	jr	.end
.error:
	call	unsetZ
.end:
	push	de \ pop ix
	pop	de
	pop	hl
	pop	bc
	ret

; Sets Z if (HL) has a '0b' prefix.
hasBinPrefix:
	ld	a, (hl)
	cp	'0'
	ret	nz
	push	hl
	inc	hl
	ld	a, (hl)
	cp	'b'
	pop	hl
	ret

; Parse string at (HL) and, if it is a char literal, sets Z and return
; corresponding value in IX. High IX byte is always clear.
;
; A valid char literal starts with ', ends with ' and has one character in the
; middle. No escape sequence are accepted, but ''' will return the apostrophe
; character.
parseCharLiteral:
	ld	a, 0x27		; apostrophe (') char
	cp	(hl)
	ret	nz

	push	hl
	push	de
	inc	hl
	inc	hl
	cp	(hl)
	jr	nz, .end	; not ending with an apostrophe
	inc	hl
	ld	a, (hl)
	or	a		; cp 0
	jr	nz, .end	; string has to end there
	; Valid char, good
	ld	d, a		; A is zero, take advantage of that
	dec	hl
	dec	hl
	ld	a, (hl)
	ld	e, a
	cp	a		; ensure Z
.end:
	push	de \ pop ix
	pop	de
	pop	hl
	ret

; Parses the string at (HL) and returns the 16-bit value in IX. The string
; can be a decimal literal (1234), a hexadecimal literal (0x1234) or a char
; literal ('X').
;
; As soon as the number doesn't fit 16-bit any more, parsing stops and the
; number is invalid. If the number is valid, Z is set, otherwise, unset.
parseLiteral:
	call	parseCharLiteral
	ret	z
	call	parseHexadecimal
	ret	z
	call	parseBinaryLiteral
	ret	z
	jp	parseDecimal

; Parse string in (HL) and return its numerical value whether its a number
; literal or a symbol. Returns value in IX.
; Sets Z if number or symbol is valid, unset otherwise.
parseNumberOrSymbol:
	call	parseLiteral
	ret	z
	; Not a number.
	; Is str a single char? If yes, maybe it's a special symbol.
	call	strIs1L
	jr	nz, .symbol	; nope
	ld	a, (hl)
	cp	'$'
	jr	z, .returnPC
	cp	'@'
	jr	nz, .symbol
	; last val
	ld	ix, (DIREC_LASTVAL)
	ret
.symbol:
	push	de		; --> lvl 1
	call	symFindVal	; --> DE
	jr	nz, .notfound
	; value in DE. We need it in IX
	push	de \ pop ix
	pop	de		; <-- lvl 1
	cp	a		; ensure Z
	ret
.notfound:
	pop	de		; <-- lvl 1
	; If not found, check if we're in first pass. If we are, it doesn't
	; matter that we didn't find our symbol. Return success anyhow.
	; Otherwise return error. Z is already unset, so in fact, this is the
	; same as jumping to zasmIsFirstPass
	jp	zasmIsFirstPass

.returnPC:
	push	hl
	call	zasmGetPC
	push	hl \ pop ix
	pop	hl
	ret
