// px_compiler.cpp
// ```````````````

#define PX_INTERNAL_SOURCE
#include "px_compiler.h"
#include "px_ir.h"

// ===========================================================================


// class scope_t
// All ints are relative to EBP only.
// Multiply by -4 in the emitter to get a useful value for x86 target
//
// This is ONLY for stack-local variables! 
class scope_t {
	typedef std::vector<int> heap_storage_t;
	typedef std::map<std::string, unsigned int> heap_lookup_t;

	heap_storage_t heap;
	heap_lookup_t  heapLookup;
	scope_t        *parent;
	int            next; // keep separately from heap.size() to support roll()
	std::string    identifier;

public:
	static const int PXVM_ERROR = 0;
	scope_t(scope_t *parent) : parent(parent), next(1) {
		if (parent == NULL) {
			identifier = make_uniqid();
		}
	};
	
	// Returns the ebp-relative position of the variable, or PXVM_ERROR
	//   if the variable was not found (usually a fatal error).
	int find(std::string& varname) {
		heap_lookup_t::iterator hi = heapLookup.find(varname);
		if (hi != heapLookup.end()) {
			return heap[hi->second];

		} else {
			return (hasParent() ? parent->find(varname) : PXVM_ERROR); // Not found
		}
	}

	// Returns the position of the variable.
	// Returns PXVM_ERROR if the variable was already declared in the current
	//   scope (usually a fatal error)
	int push(std::string& varname) {
		if (!varname.length()) return PXVM_ERROR;

		if (varname.length() && (heapLookup.find(varname) != heapLookup.end())) {
			return PXVM_ERROR; // Key already exists
		}
		
		int new_pos = next++ + (this->depth() - this->top());

		heap.push_back( new_pos );
		heapLookup[varname] = heap.size() - 1;

		return new_pos;
	}

	int typeheap_size() {
		return heap.size() + ((parent == NULL) ? 0 : parent->typeheap_size());
	}

	std::string typeheap_comment() {
		std::string ret;
		ret += "Variables: ";
		for (heap_lookup_t::iterator i = heapLookup.begin(), e = heapLookup.end(); i != e; ++i) {
			ret += strf("{%d:%s}", i->second, i->first.c_str());
		}
		ret += " Typeheap: ";
		for (int i = 0, e = heap.size(); i < e; i++) {
			ret += strf("[%d:%d]", i, heap[i]); // std::string::operator+
		}
		return ret;
	};

	// Move ebp to the top of the stack, leaving two spaces for values not
	//   handled by the scope object (for x86asm: return address, and old ebp).
	// This is required for function arguments, which our calling convention
	//   leaves at negative offsets from ebp. At present this is the only
	//   situation => roll() only called once for every scope_t object
	void roll() {
		next++; // leave one space to not collide with the return address
		for (int i = 0, e = heap.size(); i < e; i++) {
			heap[i] -= next;
		}
		next = 1;
	}
	
	int top() {
		return next - 1; // don't collide with PXVM_ERROR(!)
	}

	int depth() {
		return top() + ((parent == NULL) ? 0 : parent->depth());
	}

	bool hasParent() {
		return (parent != NULL);
	}

	std::string name() {
		return (hasParent() ? parent->name() : identifier);
	};

};

// ===========================================================================

#define PXASSERT(cond, err, ...) \
	if ( ! ( cond )) { \
		e->op(PXIT_FATAL, strf(err, __VA_ARGS__)); \
		return false; \
	};

struct AstCompiler : public IAstMapper {
private:
	PXProgram* e;
	scope_t& scope;

	std::string break_to;
	std::string continue_to;

public:
	//std::string data;
	AstCompiler(PXProgram* e, scope_t& scope) : e(e), scope(scope) {};

	bool action(token* t) {
		switch(t->type) {

			case TOK_BLOCK: {
				// Create a new scope for locals, but don't change ebp/create a
				//   new stack frame - parent variables are still relative to it
				scope_t childscope(&scope);
				AstCompiler childcompiler(e, childscope);
				childcompiler.break_to = this->break_to;
				childcompiler.continue_to = this->continue_to;

				e->op(PXIT_NOP, "{");

				childcompiler.walk( t->children[1] );
				// GC: Decrement references for all locals?
				//  How would this work alongside return statements?

				// Drop all local variables.
				// Explicitly account for extra return address and base pointer
				//   when this is the first block returning from an inner function!
				//   Otherwise we trash the stack when falling off the end of a
				//   void function
				if (true /*childscope.hasParent()*/) {
					e->op(PXIT_NOP, "scope has parent");
					e->op(PXIT_ADD_REG_IMM, "esp", 4*childscope.top());
				} else {
					e->op(PXIT_NOP, "scope has no parent");
				}
				e->op(PXIT_NOP, "}");

				// Any locals in this block will just be overwritten.
				// Actually - we do need to realign the stack ptr so that any
				//   new local variables are properly aligned with a single `push`

				//e->op(PXIT_ADD_REG_IMM, "esp", )
				e->op(PXIT_NOP, "Dropping local variables");
				e->op(PXIT_NOP, strf("Dropped local type-heap: %s\n", childscope.typeheap_comment().c_str()));

				return false;
			} break;

			case TOK_DIM_VAR: {
				// dim-var := type variable-name [ = rvalue ]
				
				// Reserve stack space for variable
				int scope_pos = scope.push( t->children[1]->data );

				PXASSERT(scope_pos != scope_t::PXVM_ERROR,
					"NameError: Redeclared identifier '%s'!", t->children[1]->data.c_str()
				);

				if (t->children.size() > 2) {
					// Initialiser is present
					walk(t->children[3]);

					e->op(PXIT_NOP, strf(
						"assign stack #%d to '%s'",
						scope_pos,
						t->children[1]->data.c_str()
					));

				} else {
					PXType* vt = & t->vartype;

					if (vt->equals(PXType::make_scalar("int")) ||
						vt->type == "function" || // FIXME, shouldn't be allowed(?)
						vt->type == "var" // FIXME - happens if variable is never assigned to, display warning?
						) {

						e->op(PXIT_NOP, strf(
							"reserve stack #%d for '%s' (using default value 0)",
							scope_pos,
							t->children[1]->data.c_str()
							));

						e->op(PXIT_PUSH, "0");

					} else if (vt->type == "string") {

						e->op(PXIT_NOP, strf(
							"reserve stack #%d for '%s' (using blank new string)",
							scope_pos,
							t->children[1]->data.c_str()
							));

						e->op(PXIT_CALL, "pxo_string_new");
						e->op(PXIT_PUSH, "eax");

					} else if (vt->type == "vector") {

						e->op(PXIT_NOP, strf(
							"reserve stack #%d for '%s' (using blank new vector)",
							scope_pos,
							t->children[1]->data.c_str()
							));

						e->op(PXIT_CALL, "pxl_vec_init");
						e->op(PXIT_PUSH, "eax");

					} else {
						PXASSERT(false, "No default initialiser for type '%s'", vt->serialize().c_str() );

					}

				}

				
				return false;
			} break;

			case TOK_FNCALL: {
				// fncall := identifier, lparen, arglist, rparen
			
				// resolve each argument to stack
				// resolve function identifier to stack
				// jump to function identifier

				e->op(PXIT_NOP, "fncall: pushing arguments");
				walk(t->children[2]);
				
				e->op(PXIT_NOP, "fncall: pushing fptr");
				walk(t->children[0]);
				e->op(PXIT_NOP, "fncall: calling function");
				e->op(PXIT_POP, "ebx");
				e->op(PXIT_CALL, "ebx");

				// Push return value onto stack.
				// (If the function doesn't have a return value, then this is
				//     garbage.)
				e->op(PXIT_PUSH, "eax");
				// Push intermediate (return value) and it's type onto the 
				//     scope stack

				// FIXME: What if it's a void function?..!
				// There's always an eax, so void functions return int at least
				
				return false; // handled
			} break;

			case TOK_FNDEF_ARGLIST: {
				// fndef-arglist := type ident [ L, fndef-arglist ]
				
				// We need to know these names to resolve their stack positions
				// This is called only by TOK_FNDEF, which has given us a new scope
				//    to be passed to the block body
				// So, just allocate stack space for each variable.

				// might be zero since an empty arglist is created when there
				//    are no function parameters
				if (t->children.size()) {
					
					int scpos = scope.push( t->children[1]->data );

					e->op(PXIT_NOP, strf(
						"stack #%d reserved for '%s' by arglist",
						scpos,
						t->children[1]->data.c_str()
					));
				}

				// Walk a successive fndef-arglist if one exists, but don't
				//   walk down into resolving the variable name
				if (t->children.size() > 2) {
					walk(t->children[3]);
				}

				return false; // handled
			} break;

			case TOK_FNDEF: {
				// Create a new scope to pass to tok_fndef_arglist
				// Eval in the new scope

				// fndef := L[ arglist L] block
				// block := L{ body L}

				// NOT A CLOSURE - the only variables available are those that
				//    are passed directly to the function.
				// All free variables should have been bound by the previous
				//    lambda lifting pass
				
				// Use the SCOPE name for labels - here we're using the memory
				//    address but we could just be generating these instead.
				// Using the scope name means we can emit jmps to the epilogue
				//    when compiling return statements. But since the scope is
				//    on the stack, we might end up with duplicates!
				// FIXME FIXME

				scope_t function_scope(NULL);
				AstCompiler function_compiler(e, function_scope);
				std::string fn_name = function_scope.name();

				// Function prologue
				e->op(PXIT_JMP, strf("_pf_%s_end", fn_name.c_str()) );
				e->op(PXIT_LABEL, strf("_pf_%s", fn_name.c_str()) );
				
				// scope.push probably not necessary? it would get in the way
				//    of copying the types of all passed arguments
				e->op(PXIT_PUSH, "ebp");
				e->op(PXIT_MOV, "ebp", "esp");

				// Move variables into scope
				e->op(PXIT_NOP, "fndef: preparing argument scope");
				function_compiler.walk(t->children[1]);
				
				// Move arguments below ebp and locals above
				int number_of_func_arguments = function_scope.top();
				function_scope.roll();

				// Compile function
				e->op(PXIT_NOP, "fndef: function body begins");
				function_compiler.walk(t->children[3]);
				
				// All functions need a return value, to make sure the stack
				//  is balanced throughout the epilogue. A separate pass should
				//  be done to ensure that for all functions, either all or no
				//  codepaths return a value, to prevent e.g. returning null when
				//  a GC-able object is expected

				// Function epilogue
				e->op(PXIT_LABEL, strf("_pf_%s_epilogue", fn_name.c_str()) );				

				e->op(PXIT_LEAVE); 
				e->op(PXIT_RET, 4 * number_of_func_arguments ); // callee-cleanup arguments

				// Post-definition:
				e->op(PXIT_LABEL, strf("_pf_%s_end", fn_name.c_str()) );

				// Add the function to the top of the stack (this is an rvalue,
				//   so we must be about to use it for something...)
				e->op(PXIT_PUSH, strf("_pf_%s", fn_name.c_str()) );
				
				return false; // handled

			} break;

			case TOK_IF_STATEMENT: {
				// if_statement := "if" "(" rvalue ")" block ["else" block]

				walk(t->children[2]);

				// Pop result and check for truthiness.
				// At present, we compare 0, so any non-zero int or any non-null
				//    function pointer is true.
				e->op(PXIT_POP, "eax");
				e->op(PXIT_CMP_ZERO, "eax");
				
				std::string condId = make_uniqid();

				bool has_else_clause = ( t->children.size() > 5 );

				// if false goto else|end
				if (has_else_clause) {
					e->op(PXIT_JE, strf("_pf_%s_else", condId.c_str()));
				} else {
					e->op(PXIT_JE, strf("_pf_%s_end", condId.c_str()));
				}
				
				// true-condition
				walk(t->children[4]);
				e->op(PXIT_JMP, strf("_pf_%s_end", condId.c_str())); // optimiser will elide potentially redundant jmp

				if (has_else_clause) {
					e->op(PXIT_LABEL, strf("_pf_%s_else", condId.c_str()));
					walk(t->children[6]);
					e->op(PXIT_JMP, strf("_pf_%s_end", condId.c_str()));
				}

				e->op(PXIT_LABEL, strf("_pf_%s_end", condId.c_str()));
				
				return false; // handled
			} break;

			case TOK_FOR_STATEMENT: {
				// for_statement := "for" "(" statement rvalue ";" statement_inner ")" block
				std::string
					loopId     = make_uniqid(),
					lblLoop    = strf("_pf_%s_forloop", loopId.c_str()),
					lblLoopStmt= strf("_pf_%s_nextloop", loopId.c_str()),
					lblEndLoop = strf("_pf_%s_endfor",  loopId.c_str());

				walk(t->children[2]); // statement (1st for-clause)

				e->op(PXIT_LABEL, lblLoop);

				walk(t->children[3]); // rvalue (2nd for-clause)
				// If the value isn't truthy, exit the loop
				e->op(PXIT_POP, "eax");

				e->op(PXIT_CMP_ZERO, "eax"); // see discussion for TOK_IF_STATEMENT
				e->op(PXIT_JE, lblEndLoop); // didn't meet condition

				e->op(PXIT_NOP, "forloop inner block");

				std::string stash_break_to = this->break_to;
				std::string stash_continue_to = this->continue_to;
				this->break_to = lblEndLoop;
				this->continue_to = lblLoopStmt;

				walk(t->children[7]); // block

				this->break_to = stash_break_to;
				this->continue_to = stash_continue_to;

				e->op(PXIT_NOP, "forloop loop statement");
				e->op(PXIT_LABEL, lblLoopStmt);
				walk(t->children[5]); // statement (3rd for-clause)

				e->op(PXIT_JMP, lblLoop);
				e->op(PXIT_LABEL, lblEndLoop);

				return false; // handled
			} break;

			case TOK_RWORD_BREAK: {
				PXASSERT(this->break_to.length(), "Nowhere to BREAK to!");

				e->op(PXIT_JMP, this->break_to);
			} break;

			case TOK_RWORD_CONTINUE: {
				PXASSERT(this->continue_to.length(), "Nowhere to CONTINUE to!");

				e->op(PXIT_JMP, this->continue_to);
			} break;

			case TOK_RETURN_STATEMENT: {
				// rstatement := "return" rvalue
				walk(t->children[1]);

				e->op(PXIT_NOP, strf(
					"return (type: %s)",
					t->children[1]->vartype.serialize().c_str()
				));

				// Store return value in eax
				e->op(PXIT_POP, "eax");

				// Drop all local variables, out to the depth of the containing
				//     function
				e->op(PXIT_JMP, strf("_pf_%s_epilogue", scope.name().c_str()) );

				return false; // handled
			} break;

			// Nothing for TOK_IDENTIFIER
			
			case TOK_QUOTED_STRING: {
				// data stored directly
				
				// Register string literal
				std::string ctstr_name = e->register_string_literal( t->data );

				// Push immutable string to stack
				e->op(PXIT_PUSH, ctstr_name);

				// Copy to eax
				e->op(PXIT_CALL, "pxo_string_dup");

				// Push eax to stack
				e->op(PXIT_PUSH, "eax");

				// Done

			} break;

			case TOK_LVALUE: {
				// lvalue := identifier [ sbrace rvalue sbrace ]
				// Push the value onto the stack.
				
				// LVALUE is a child of both RVALUE_INNER or ASSIGNMENT - but
				//   ASSIGNMENT does its own thing to push a ptr onto the stack,
				//   so this block only needs to push the actual data

				// isolate identifier
				int element = scope.find( t->children[0]->data );

				if (element == scope_t::PXVM_ERROR) {
					e->op(PXIT_FATAL, strf(
						"CompileError: Couldn't resolve identifier '%s'!",
						t->children[0]->data.c_str()
					));
				}
				
				if (t->children.size() == 1) {
					// Get the value onto the stack. Direct access, type is
					//    assumed to be intptr or compatible (also used for
					//    function pointers)
					e->op(PXIT_PUSH, x86nasm_stack_address(element) );

				} else {

					PXType* vartype = & t->children[0]->vartype;
					std::string vartype_str = vartype->serialize(); // type of the identifier, not the result
					tok_types innerType = t->children[2]->children[0]->type;

					if (vartype_str == "string" && innerType == TOK_RVALUE) {

						// Int-type of offset is asserted in typechecker pass.
						
						walk(t->children[2]);

						// Emit call to substr:
						e->op(PXIT_POP, "eax");
						e->op(PXIT_PUSH, "eax"); // start
						e->op(PXIT_ADD_REG_IMM, "eax", 1);
						e->op(PXIT_PUSH, "eax"); // end
						e->op(PXIT_PUSH, x86nasm_stack_address(element)); // string
						e->op(PXIT_CALL, "pxo_string_substr");
						e->op(PXIT_PUSH, "eax"); // and save

						// Result is a new string slice on top of stack

					} else if (vartype_str == "string" && innerType == TOK_SLICE_INNER) {

						token* sliceInner = t->children[2]->children[0];
						walk(sliceInner->children[0]); // start
						walk(sliceInner->children[2]); // end
						e->op(PXIT_PUSH, x86nasm_stack_address(element)); // string
						e->op(PXIT_CALL, "pxo_string_substr");
						e->op(PXIT_PUSH, "eax"); // and save

					} else if (vartype->type == "vector" && innerType == TOK_RVALUE) { // n.b. NOT vartype_str
						
						// push fallback functionpointer to call on bounds violation
						if (vartype->children[0].serialize() == "int") {
							e->op(PXIT_PUSH, "0");
						} else if (vartype->children[0].serialize() == "string") {
							e->op(PXIT_PUSH, "pxo_string_new");
						} else if (vartype->children[0].type == "vector") {
							e->op(PXIT_PUSH, "pxl_vec_init");
						} else {
							PXASSERT(
								false,
								"No known fallback for bounds check for '%s' contained within vector",
								vartype->children[0].serialize().c_str()
							);
						}

						walk(t->children[2]); // int offset
						e->op(PXIT_PUSH, x86nasm_stack_address(element)); // vector
						e->op(PXIT_CALL, "pxl_vec_at");
						e->op(PXIT_PUSH, "eax");

					} else {
						e->op(PXIT_FATAL, strf(
							"CompileError: %s not supported for type %s",
							(innerType == TOK_RVALUE ? "operator[]" : "slice operator"),
							vartype_str.c_str()
						));
					}

				}
				
				return false; // handled
			} break;

			case TOK_NUMERIC_LITERAL: {
				e->op(PXIT_PUSH, strf("%d", t->data_int) );
				return false; // handled (but this doesn't have children anyway)
			} break;

			case TOK_VECTOR_LITERAL: {
				
				// Mildly complex to handle adding a variable number of elements.
				//   We could (A) use a local, but we don't know esp at compiletime;
				//   (B) reserve a register, but then we couldn't nest vector
				//   literals; so this must use the stack only.


				// walk VECTOR_ELEMENTS, push each one onto stack IN REVERSE, remember stack ct

				std::vector<token*> elements;
				token* vec_element = t->children[1];


				for(;;) {
					if (vec_element->children.size()) {
						elements.push_back(vec_element);
					}

					if (vec_element->children.size() > 1) {
						// walk next
						vec_element = vec_element->children[2];
					} else {
						// no more vector elements
						break;
					}
				}

				// Push each element onto stack IN REVERSE

				for (int i = elements.size(); i-- > 0;) {
					walk(elements[i]->children[0]);
				}

				// call vec_new
				e->op(PXIT_CALL, "pxl_vec_init");

				// copy eax to a register that vec_add doesn't clobber (e.g. esi?)
				e->op(PXIT_MOV, "esi", "eax");

				for (unsigned int i = 0; i < elements.size(); ++i) {

					// push esi
					e->op(PXIT_PUSH, "esi");

					// call vec_add
					e->op(PXIT_CALL, "pxl_vec_push");

				}

				// push esi
				e->op(PXIT_PUSH, "esi");

				// done
				return false; // handled
			} break;

			case TOK_IMPORT_CLAUSE: {

				std::string to_import = t->children[2]->data;
				if (t->vartype.type == "function") {
					// Apply stdcall decoration to imported functions
					to_import = strf("_%s@%d", to_import.c_str(), (t->vartype.children.size() - 1) * 4);
				}

				e->op(PXIT_IMPORT_EXTERN, to_import);
				e->op(PXIT_PUSH, to_import);

				return false; // handled
			} break;

			case TOK_ASSIGNMENT: {
				// compile the right-hand side to get a single value on the
				//   top of the stack.
				
				// assignment := lvalue "=" rvalue

				walk(t->children[2]);
				
				// Look up stack position for assignee
				// TODO: support checking whether the lvalue has an array index
				//    and write assignment cases for all instances of operator[]
				//    overloaded for types (string, vector, ...)

				//                             lvalue       identifier
				int assignee = scope.find( t->children[0]->children[0]->data );

				PXASSERT(
					assignee != scope_t::PXVM_ERROR,
					"CompileError: couldn't resolve identifier '%s'!", t->children[0]->data.c_str()
				);

				e->op(PXIT_NOP, strf(
					"assign to %s'%s'",
					(t->children[0]->children.size() > 1 ? "child of " : ""),
					t->children[0]->children[0]->data.c_str()
				));

				// Emit copy
				e->op(PXIT_POP, "ebx");
				e->op(PXIT_MOV, x86nasm_stack_address(assignee), "ebx");

				return false; // handled
			} break;
		
			case TOK_BINARY_OPERATOR: {

				// left and right operands are the top two stack elements
				
				PXType ebx_type = t->parent->children[2]->vartype; // TOK_RVALUE_INNER
				PXType eax_type = t->parent->children[0]->vartype; // TOK_RVALUE

				if (! eax_type.equals(ebx_type)) {
					e->op(PXIT_FATAL, strf(
						"TypeError: Tried to use binary operator between types '%s' and '%s'",
						eax_type.serialize().c_str(),
						ebx_type.serialize().c_str()
					));
				}

				enum tok_types binop = t->children[0]->type;

				if (eax_type.is_scalar("int")) {

					// pop operands into eax/ebx. Pop EBX first as it's the right-most
					//     element (important for comparisons and division)

					e->op(PXIT_POP, "ebx");
					e->op(PXIT_POP, "eax");

					if (binop == TOK_OP_PLUS) {
						e->op(PXIT_ADD_REG_REG, "eax", "ebx"); // eax += ebx

					} else if (binop == TOK_OP_MINUS) {
						e->op(PXIT_SUB_REG_REG, "eax", "ebx");

					} else if (binop == TOK_OP_STAR) {
						e->op(PXIT_MUL_REG_REG, "eax", "ebx");

					//} else if (t->children[0]->type == TOK_OP_FORWARDSLASH) {
						//e->op(PXIT_DIV

					} else if (binop == TOK_OP_GREATERTHAN) {
						e->op(PXIT_GREATERTHAN, "eax", "ebx"); // work done in emitter to put 0/1 in eax

					} else if (binop == TOK_OP_LESSTHAN) {
						e->op(PXIT_LESSTHAN, "eax", "ebx");

					} else if (binop == TOK_OP_EQUALS_EQUALS) {
						e->op(PXIT_EQUAL, "eax", "ebx");

					} else if (binop == TOK_OP_EXCLAMATION_EQUALS) {
						e->op(PXIT_NOTEQUAL, "eax", "ebx");

					} else {
						// unhandled binary operator? shouldn't happen
						e->op(PXIT_FATAL, strf(
							"no handler written for binary operator '%s'",
							pexl_pprint_typename(binop)
						));
						return false;
					}

					e->op(PXIT_PUSH, "eax"); // push result onto stack

				} else if (eax_type.is_scalar("string")) {

					if (binop == TOK_OP_PLUS) {
						// don't need pop the top two stack elements, just
						//   call pxo_string_concat immediately						
						e->op(PXIT_CALL, "pxo_string_concat");

					} else if (binop == TOK_OP_EQUALS_EQUALS) {
						e->op(PXIT_CALL, "pxo_string_cmp");
						e->op(PXIT_EQUAL, "eax", "0");

					} else if (binop == TOK_OP_EXCLAMATION_EQUALS) {
						e->op(PXIT_CALL, "pxo_string_cmp");
						e->op(PXIT_NOTEQUAL, "eax", "0");

					} else if (binop == TOK_OP_GREATERTHAN) {
						e->op(PXIT_CALL, "pxo_string_cmp");
						e->op(PXIT_EQUAL, "eax", "1");

					} else if (binop == TOK_OP_LESSTHAN) {
						e->op(PXIT_CALL, "pxo_string_cmp");
						e->op(PXIT_EQUAL, "eax", "-1");

					} else {

						e->op(PXIT_FATAL, strf(
							"binary operation '%s' not defined for type string",
							pexl_pprint_typename(binop)
						));
						return false;
					}

					e->op(PXIT_PUSH, "eax");

				} else {

					e->op(PXIT_FATAL, strf(
						"no handlers for binary operations on the type '%s'",
						eax_type.serialize().c_str()
					));

				}

				// make sure you remembered to push the result onto the stack
			
				return false; // handled

			} break;
			
			case TOK_RVALUE: {
			
				// rvalue := rv-inner [ binary-operator rvalue ]

				// compile left onto stack
				walk(t->children[0]);

				if (t->children.size() > 1) {
					walk(t->children[2]); // compile right onto stack
					walk(t->children[1]); // compile binary operator

					// the result is the top stack element
				}

				return false; // handled

			} break;

			case TOK_STATEMENT_INNER: {
				// Ordinarily this would be totally fine - but in the case
				//   of a single fncall() statement, we need to drop the resulting
				//   stack item immediately afterward (and i guess call dtors, etc.)

				walk(t->children[0]);
				if (t->children[0]->type == TOK_FNCALL) {
					e->op(PXIT_POP_NULL);
				}
				return false; // handled
			} break;
		}
		return true; // unhandled
	}
	
};

std::string compile_easy(token* t, bool should_optimise) {
	PXProgram pxp;
	pxp.should_optimise = should_optimise;

	scope_t initial_scope(NULL);
	
	AstCompiler compiler(&pxp, initial_scope);
	compiler.walk(t);
	
	pxp.op(PXIT_NOP, strf("Remaining type scope: %s", initial_scope.typeheap_comment().c_str()));

	// This is here to simulate the pexl function call convention in order to
	//  support returning from top-level function
	pxp.op(PXIT_JMP, strf("_pf_%s_end", initial_scope.name().c_str()));
	pxp.op(PXIT_LABEL, strf("_pf_%s_epilogue", initial_scope.name().c_str())); // allow return from main
	pxp.op(PXIT_LEAVE);
	pxp.op(PXIT_RET, 0);
	pxp.op(PXIT_LABEL, strf("_pf_%s_end", initial_scope.name().c_str()));
	
	pxp.optimise();

	return pxp.render_x86nasm();
}