: \ `\n parse drop ; immediate

\ This file defines the core non-native functions (mainly used for
\ parsing words, i.e. not part of the generated output). The line above
\ defines the syntax for comments.

\ Define parenthesis comments.
\ : ( `) parse drop ; immediate

: else postpone ahead 1 cs-roll postpone then ; immediate
: while postpone if 1 cs-roll ; immediate
: repeat postpone again postpone then ; immediate

: ['] ' ; immediate
: [compile] compile ; immediate

: 2drop drop drop ;
: dup2 over over ;

\ Local variables are defined with the native word '(local)'. We define
\ a helper construction that mimics what is found in Apple's Open Firmware
\ implementation. The syntax is: { a b ... ; c d ... }
\ I.e. there is an opening brace, then some names. Names appearing before
\ the semicolon are locals that are both defined and then filled with the
\ values on stack (in stack order: { a b } fills 'b' with the top-of-stack,
\ and 'a' with the value immediately below). Names appearing after the
\ semicolon are not initialized.
: __deflocal ( from_stack name -- )
	dup (local) swap if
		compile-local-write
	else
		drop
	then ;
: __deflocals ( from_stack -- )
	next-word
	dup "}" eqstr if
		2drop ret
	then
	dup ";" eqstr if
		2drop 0 __deflocals ret
	then
	over __deflocals
	__deflocal ;
: {
	-1 __deflocals ; immediate

\ Data building words.
: data:
	new-data-block next-word define-data-word ;
: hexb|
	0 0 { acc z }
	begin
		char
		dup `| = if
			z if "Truncated hexadecimal byte" puts cr exitvm then
			ret
		then
		dup 0x20 > if
			hexval
			z if acc 4 << + data-add8 else >acc then
			z not >z
		then
	again ;

\ Convert hexadecimal character to number. Complain loudly if conversion
\ is not possible.
: hexval ( char -- x )
	hexval-nf dup 0 < if "Not an hex digit: " puts . cr exitvm then ;

\ Convert hexadecimal character to number. If not an hexadecimal digit,
\ return -1.
: hexval-nf ( char -- x )
	dup dup `0 >= swap `9 <= and if `0 - ret then
	dup dup `A >= swap `F <= and if `A - 10 + ret then
	dup dup `a >= swap `f <= and if `a - 10 + ret then
	drop -1 ;

\ Convert decimal character to number. Complain loudly if conversion
\ is not possible.
: decval ( char -- x )
	decval-nf dup 0 < if "Not a decimal digit: " puts . cr exitvm then ;

\ Convert decimal character to number. If not a decimal digit,
\ return -1.
: decval-nf ( char -- x )
	dup dup `0 >= swap `9 <= and if `0 - ret then
	drop -1 ;

\ Commonly used shorthands.
: 1+ 1 + ;
: 2+ 2 + ;
: 1- 1 - ;
: 2- 2 - ;
: 0= 0 = ;
: 0<> 0 <> ;
: 0< 0 < ;
: 0> 0 > ;

\ Get a 16-bit value from the constant data block. This uses big-endian
\ encoding.
: data-get16 ( addr -- x )
	dup data-get8 8 << swap 1+ data-get8 + ;

\ The case..endcase construction is the equivalent of 'switch' is C.
\ Usage:
\     case
\         E1 of C1 endof
\         E2 of C2 endof
\         ...
\         CN
\     endcase
\
\ Upon entry, it considers the TOS (let's call it X). It will then evaluate
\ E1, which should yield a single value Y1; at that point, the X value is
\ still on the stack, just below Y1, and must remain untouched. The 'of'
\ word compares X with Y1; if they are equal, C1 is executed, and then
\ control jumps to after the 'endcase'. The X value is popped from the
\ stack immediately before evaluating C1.
\
\ If X and Y1 are not equal, flow proceeds to E2, to obtain a value Y2 to
\ compare with X. And so on.
\
\ If none of the 'of' clauses found a match, then CN is evaluated. When CN
\ is evaluated, the X value is on the TOS, and CN must either leave it on
\ the stack, or replace it with exactly one value; the 'endcase' word
\ expects (and drops) one value.
\
\ Implementation: this is mostly copied from ANS Forth specification,
\ although simplified a bit because we know that our control-flow stack
\ is independent of the data stack. During compilation, the number of
\ clauses is maintained on the stack; each of..endof clause really is
\ an 'if..else' that must be terminated with a matching 'then' in 'endcase'.

: case 0 ; immediate
: of 1+ postpone over postpone = postpone if postpone drop ; immediate
: endof postpone else ; immediate
: endcase
	postpone drop
	begin dup while 1- postpone then repeat drop ; immediate

\ A simpler and more generic "case": there is no management for a value
\ on the stack, and each test is supposed to come up with its own boolean
\ value.
: choice 0 ; immediate
: uf 1+ postpone if ; immediate
: ufnot 1+ postpone ifnot ; immediate
: enduf postpone else ; immediate
: endchoice begin dup while 1- postpone then repeat drop ; immediate

\ C implementations for native words that can be used in generated code.
add-cc: co { T0_CO(); }
add-cc: execute { T0_ENTER(ip, rp, T0_POP()); }
add-cc: drop { (void)T0_POP(); }
add-cc: dup { T0_PUSH(T0_PEEK(0)); }
add-cc: swap { T0_SWAP(); }
add-cc: over { T0_PUSH(T0_PEEK(1)); }
add-cc: rot { T0_ROT(); }
add-cc: -rot { T0_NROT(); }
add-cc: roll { T0_ROLL(T0_POP()); }
add-cc: pick { T0_PICK(T0_POP()); }
add-cc: + {
	uint32_t b = T0_POP();
	uint32_t a = T0_POP();
	T0_PUSH(a + b);
}
add-cc: - {
	uint32_t b = T0_POP();
	uint32_t a = T0_POP();
	T0_PUSH(a - b);
}
add-cc: neg {
	uint32_t a = T0_POP();
	T0_PUSH(-a);
}
add-cc: * {
	uint32_t b = T0_POP();
	uint32_t a = T0_POP();
	T0_PUSH(a * b);
}
add-cc: / {
	int32_t b = T0_POPi();
	int32_t a = T0_POPi();
	T0_PUSHi(a / b);
}
add-cc: u/ {
	uint32_t b = T0_POP();
	uint32_t a = T0_POP();
	T0_PUSH(a / b);
}
add-cc: % {
	int32_t b = T0_POPi();
	int32_t a = T0_POPi();
	T0_PUSHi(a % b);
}
add-cc: u% {
	uint32_t b = T0_POP();
	uint32_t a = T0_POP();
	T0_PUSH(a % b);
}
add-cc: < {
	int32_t b = T0_POPi();
	int32_t a = T0_POPi();
	T0_PUSH(-(uint32_t)(a < b));
}
add-cc: <= {
	int32_t b = T0_POPi();
	int32_t a = T0_POPi();
	T0_PUSH(-(uint32_t)(a <= b));
}
add-cc: > {
	int32_t b = T0_POPi();
	int32_t a = T0_POPi();
	T0_PUSH(-(uint32_t)(a > b));
}
add-cc: >= {
	int32_t b = T0_POPi();
	int32_t a = T0_POPi();
	T0_PUSH(-(uint32_t)(a >= b));
}
add-cc: = {
	uint32_t b = T0_POP();
	uint32_t a = T0_POP();
	T0_PUSH(-(uint32_t)(a == b));
}
add-cc: <> {
	uint32_t b = T0_POP();
	uint32_t a = T0_POP();
	T0_PUSH(-(uint32_t)(a != b));
}
add-cc: u< {
	uint32_t b = T0_POP();
	uint32_t a = T0_POP();
	T0_PUSH(-(uint32_t)(a < b));
}
add-cc: u<= {
	uint32_t b = T0_POP();
	uint32_t a = T0_POP();
	T0_PUSH(-(uint32_t)(a <= b));
}
add-cc: u> {
	uint32_t b = T0_POP();
	uint32_t a = T0_POP();
	T0_PUSH(-(uint32_t)(a > b));
}
add-cc: u>= {
	uint32_t b = T0_POP();
	uint32_t a = T0_POP();
	T0_PUSH(-(uint32_t)(a >= b));
}
add-cc: and {
	uint32_t b = T0_POP();
	uint32_t a = T0_POP();
	T0_PUSH(a & b);
}
add-cc: or {
	uint32_t b = T0_POP();
	uint32_t a = T0_POP();
	T0_PUSH(a | b);
}
add-cc: xor {
	uint32_t b = T0_POP();
	uint32_t a = T0_POP();
	T0_PUSH(a ^ b);
}
add-cc: not {
	uint32_t a = T0_POP();
	T0_PUSH(~a);
}
add-cc: << {
	int c = (int)T0_POPi();
	uint32_t x = T0_POP();
	T0_PUSH(x << c);
}
add-cc: >> {
	int c = (int)T0_POPi();
	int32_t x = T0_POPi();
	T0_PUSHi(x >> c);
}
add-cc: u>> {
	int c = (int)T0_POPi();
	uint32_t x = T0_POP();
	T0_PUSH(x >> c);
}
add-cc: data-get8 {
	size_t addr = T0_POP();
	T0_PUSH(pgm_read_byte(&t0_datablock[addr]));
}

add-cc: . {
	extern int printf(const char *fmt, ...);
	printf(" %ld", (long)T0_POPi());
}
add-cc: putc {
	extern int printf(const char *fmt, ...);
	printf("%c", (char)T0_POPi());
}
add-cc: puts {
	extern int printf(const char *fmt, ...);
	printf("%s", &t0_datablock[T0_POPi()]);
}
add-cc: cr {
	extern int printf(const char *fmt, ...);
	printf("\n");
}
add-cc: eqstr {
	const void *b = &t0_datablock[T0_POPi()];
	const void *a = &t0_datablock[T0_POPi()];
	T0_PUSH(-(int32_t)(strcmp_P(a, b) == 0));
}
