; *** Code ***

; Parse the hex char at A and extract it's 0-15 numerical value. Put the result
; in A.
;
; On success, the carry flag is reset. On error, it is set.
parseHex:
	; First, let's see if we have an easy 0-9 case

	add 	a, 0xc6	; maps '0'-'9' onto 0xf6-0xff
	sub 	0xf6	; maps to 0-9 and carries if not a digit
	ret	nc

	and 	0xdf		; converts lowercase to uppercase
	add	a, 0xe9		; map 0x11-x017 onto 0xFA - 0xFF
	sub 	0xfa		; map onto 0-6
	ret 	c
	; we have an A-F digit
	add	a, 10		; C is clear, map back to 0xA-0xF
	ret


; Parses 2 characters of the string pointed to by HL and returns the numerical
; value in A. If the second character is a "special" character (<0x21) we don't
; error out: the result will be the one from the first char only.
; HL is set to point to the last char of the pair.
;
; On success, the carry flag is reset. On error, it is set.
parseHexPair:
	push	bc

	ld	a, (hl)
	call	parseHex
	jr	c, .end		; error? goto end, keeping the C flag on
	rla \ rla \ rla \ rla	; let's push this in MSB
	ld	b, a
	inc	hl
	ld	a, (hl)
	cp	0x21
	jr	c, .single	; special char? single digit
	call	parseHex
	jr	c, .end		; error?
	or	b		; join left-shifted + new. we're done!
	; C flag was set on parseHex and is necessarily clear at this point
	jr	.end

.single:
	; If we have a single digit, our result is already stored in B, but
	; we have to right-shift it back.
	ld	a, b
	and	0xf0
	rra \ rra \ rra \ rra
	dec	hl

.end:
	pop	bc
	ret

; Parse the decimal char at A and extract it's 0-9 numerical value. Put the
; result in A.
;
; On success, the carry flag is reset. On error, it is set.
; Also, zero flag set if '0'
; parseDecimalDigit has been replaced with the following code inline:
;	add	a, 0xff-'9'	; maps '0'-'9' onto 0xf6-0xff
;	sub	0xff-9		; maps to 0-9 and carries if not a digit

; Parse string at (HL) as a decimal value and return value in IX under the
; same conditions as parseLiteral.
; Sets Z on success, unset on error.
; To parse successfully, all characters following HL must be digits and those
; digits must form a number that fits in 16 bits. To end the number, both \0
; and whitespaces (0x20 and 0x09) are accepted. There must be at least one
; digit in the string.

parseDecimal:
	push 	hl

	ld	a, (hl)
	add	a, 0xff-'9'	; maps '0'-'9' onto 0xf6-0xff
	sub	0xff-9		; maps to 0-9 and carries if not a digit
	jr	c, .error	; not a digit on first char? error
	exx		; preserve bc, hl, de
	ld	h, 0
	ld	l, a	; load first digit in without multiplying
	ld	b, 3	; Carries can only occur for decimals >=5 in length

.loop:
	exx
	inc hl
	ld a, (hl)
	exx

	; inline parseDecimalDigit
	add	a, 0xff-'9'	; maps '0'-'9' onto 0xf6-0xff
	sub	0xff-9		; maps to 0-9 and carries if not a digit
	jr	c, .end

	add	hl, hl	; x2
	ld	d, h
	ld	e, l		; de is x2
	add	hl, hl	; x4
	add	hl, hl	; x8
	add	hl, de	; x10
	ld	d, 0
	ld	e, a
	add	hl, de
	jr	c, .end	; if hl was 0x1999, it may carry here
	djnz	.loop


	inc 	b	; so loop only executes once more
	; only numbers >0x1999 can carry when multiplied by 10.
	ld	de, 0xE666
	ex	de, hl
	add	hl, de
	ex	de, hl
	jr	nc, .loop	; if it doesn't carry, it's small enough

	exx
	inc 	hl
	ld 	a, (hl)
	exx
	add 	a, 0xd0	; the next line expects a null to be mapped to 0xd0
.end:
	; Because of the add and sub in parseDecimalDigit, null is mapped
	; to 0x00+(0xff-'9')-(0xff-9)=-0x30=0xd0
	sub 	0xd0	; if a is null, set Z
			; a is checked for null before any errors
	push	hl \ pop ix
	exx	; restore original de and bc
	pop	hl
	ret	z
	; A is not 0? Ok, but if it's a space, we're happy too.
	jp	isWS
.error:
	pop	hl
	jp	unsetZ

; 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