// TODO Basic missing features:
// 	- Other list operations: insert_many, delete, delete_many, delete_last.
// 	- Maps: T[int], T[str].
// 	- Control flow: break, continue.
// 	- Other operators: remainder, bitwise shifts, bitwise AND/OR/XOR/NOT, ternary.
// 	- Enums, bitsets.
// 	- Named optional arguments with default values.
// 	- Accessing structs and functypes from inline modules.

// TODO Larger missing features:
// 	- Serialization.
// 	- Debugging.
// 	- Verbose mode, where every external call is logged, every variable modification is logged, every line is logged, etc? Saving output to file.

// TODO Cleanup:
// 	- Cleanup the code in External- functions and ScriptExecuteFunction using macros for common stack and heap operations.
// 	- Cleanup the ImportData/ExecutionContext/FunctionBuilder structures and their relationships.
// 	- Cleanup the variables/stack arrays.
// 	- Cleanup the platform layer.

// TODO Safety:
// 	- Safety against extremely large scripts?
// 	- Loading untrusted bytecode files?

// TODO Miscellaneous:
// 	- Inlining small strings.
// 	- Exponent notation in numeric literals.
// 	- Block comments.
// 	- More escape sequences in string literals.
// 	- Setting the initial values of global variables.
// 	- Better handling of memory allocation failures.
// 	- Operation arguments are evaluated in the opposite order to functions?

#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>

#define T_ERROR               (0)
#define T_EOF                 (1)
#define T_IDENTIFIER          (2)
#define T_STRING_LITERAL      (3)
#define T_NUMERIC_LITERAL     (4)

#define T_ADD                 (40)
#define T_MINUS               (41)
#define T_ASTERISK            (42)
#define T_SLASH               (43)
#define T_NEGATE              (44)
#define T_LEFT_ROUND          (45)
#define T_RIGHT_ROUND         (46)
#define T_LEFT_SQUARE         (47)
#define T_RIGHT_SQUARE        (48)
#define T_LEFT_FANCY          (49)
#define T_RIGHT_FANCY         (50)
#define T_COMMA               (51)
#define T_EQUALS              (52)
#define T_SEMICOLON           (53)
#define T_GREATER_THAN        (54)
#define T_LESS_THAN           (55)
#define T_GT_OR_EQUAL         (56)
#define T_LT_OR_EQUAL         (57)
#define T_DOUBLE_EQUALS       (58)
#define T_NOT_EQUALS          (59)
#define T_LOGICAL_AND         (60)
#define T_LOGICAL_OR          (61)
#define T_ADD_EQUALS          (62)
#define T_MINUS_EQUALS        (63)
#define T_ASTERISK_EQUALS     (64)
#define T_SLASH_EQUALS        (65)
#define T_DOT                 (66)
#define T_COLON               (67)
#define T_LOGICAL_NOT         (68)

#define T_ROOT                (80)
#define T_FUNCBODY            (81)
#define T_ARGUMENTS           (82)
#define T_ARGUMENT            (83)
#define T_FUNCTION            (84)
#define T_BLOCK               (85)
#define T_VARIABLE            (86)
#define T_CALL                (87)
#define T_DECLARE             (88)
#define T_FUNCPTR             (89)
#define T_STR_INTERPOLATE     (90)
#define T_INDEX               (91)
#define T_LIST                (92)
#define T_IMPORT_PATH         (93)
#define T_LIST_LITERAL        (94)
#define T_REPL_RESULT         (95)

#define T_EXIT_SCOPE          (100)
#define T_END_FUNCTION        (101)
#define T_POP                 (102)
#define T_BRANCH              (103)
#define T_CONCAT              (104)
#define T_INTERPOLATE_STR     (105)
#define T_INTERPOLATE_BOOL    (106)
#define T_INTERPOLATE_INT     (107)
#define T_INTERPOLATE_FLOAT   (108)
#define T_DUP                 (109)
#define T_SWAP                (110)
#define T_INTERPOLATE_ILIST   (111)

#define T_FLOAT_ADD           (120)
#define T_FLOAT_MINUS         (121)
#define T_FLOAT_ASTERISK      (122)
#define T_FLOAT_SLASH         (123)
#define T_FLOAT_NEGATE        (124)
#define T_FLOAT_GREATER_THAN  (125)
#define T_FLOAT_LESS_THAN     (126)
#define T_FLOAT_GT_OR_EQUAL   (127)
#define T_FLOAT_LT_OR_EQUAL   (128)
#define T_FLOAT_DOUBLE_EQUALS (129)
#define T_FLOAT_NOT_EQUALS    (130)
#define T_STR_DOUBLE_EQUALS   (131)
#define T_STR_NOT_EQUALS      (132)
#define T_EQUALS_DOT          (133)
#define T_EQUALS_LIST         (134)
#define T_INDEX_LIST          (135)

#define T_OP_RESIZE           (140)
#define T_OP_ADD              (141)
#define T_OP_INSERT           (142)
#define T_OP_INSERT_MANY      (143)
#define T_OP_DELETE           (144)
#define T_OP_DELETE_MANY      (145)
#define T_OP_DELETE_LAST      (146)
#define T_OP_DELETE_ALL       (147)
#define T_OP_FIRST            (148)
#define T_OP_LAST             (149)
#define T_OP_LEN              (150)
#define T_OP_DISCARD          (151)
#define T_OP_ASSERT           (152)
#define T_OP_CURRY            (153)
#define T_OP_ASYNC            (154)
#define T_OP_FIND_AND_DELETE  (155)

#define T_IF                  (160)
#define T_WHILE               (161)
#define T_FOR                 (162)
#define T_INT                 (163)
#define T_FLOAT               (164)
#define T_BOOL                (165)
#define T_VOID                (166)
#define T_RETURN              (167)
#define T_ELSE                (168)
#define T_EXTCALL             (169)
#define T_STR                 (170)
#define T_FUNCTYPE            (171)
#define T_NULL                (172)
#define T_FALSE               (173)
#define T_TRUE                (174)
#define T_ASSERT              (175)
#define T_PERSIST             (176)
#define T_STRUCT              (177)
#define T_NEW                 (178)
#define T_OPTION              (179)
#define T_IMPORT              (180)
#define T_INLINE              (181)
#define T_AWAIT               (182)

#define STACK_READ_STRING(textVariable, bytesVariable, stackIndex) \
	if (context->c->stackPointer < stackIndex) return -1; \
	if (!context->c->stackIsManaged[context->c->stackPointer - stackIndex]) return -1; \
	uint64_t _index ## stackIndex = context->c->stack[context->c->stackPointer - stackIndex].i; \
	if (context->heapEntriesAllocated <= _index ## stackIndex) return -1; \
	HeapEntry *_entry ## stackIndex = &context->heap[_index ## stackIndex]; \
	if (_entry ## stackIndex->type != T_EOF && _entry ## stackIndex->type != T_STR && _entry ## stackIndex->type != T_CONCAT) return -1; \
	const char *textVariable; \
	size_t bytesVariable; \
	ScriptHeapEntryToString(context, _entry ## stackIndex, &textVariable, &bytesVariable);
#define STACK_POP_STRING(textVariable, bytesVariable) \
	STACK_READ_STRING(textVariable, bytesVariable, 1); \
	context->c->stackPointer--;
#define STACK_POP_STRING_2(textVariable1, bytesVariable1, textVariable2, bytesVariable2) \
	STACK_READ_STRING(textVariable1, bytesVariable1, 1); \
	STACK_READ_STRING(textVariable2, bytesVariable2, 2); \
	context->c->stackPointer -= 2;
#define RETURN_STRING_COPY(_text, _bytes) \
	returnValue->i = HeapAllocate(context); \
	context->heap[returnValue->i].type = T_STR; \
	context->heap[returnValue->i].bytes = _bytes; \
	context->heap[returnValue->i].text = (char *) AllocateResize(NULL, context->heap[returnValue->i].bytes); \
	MemoryCopy(context->heap[returnValue->i].text, _text, context->heap[returnValue->i].bytes);
#define RETURN_STRING_NO_COPY(_text, _bytes) \
	returnValue->i = HeapAllocate(context); \
	context->heap[returnValue->i].type = T_STR; \
	context->heap[returnValue->i].bytes = _bytes; \
	context->heap[returnValue->i].text = _text;

typedef struct Token {
	struct ImportData *module;
	const char *text;
	size_t textBytes;
	uint32_t line;
	uint8_t type;
} Token;

typedef struct Tokenizer {
	struct ImportData *module;
	const char *input;
	size_t inputBytes;
	uintptr_t position;
	uintptr_t line;
	bool error, isBaseModule;
} Tokenizer;

typedef struct Scope {
	struct Node **entries;
	size_t entryCount;
	size_t variableEntryCount;
	size_t entriesAllocated;
	bool isRoot;
} Scope;

typedef struct Node {
	uint8_t type;
	bool referencesRootScope, isExternalCall, isPersistentVariable, isOptionVariable;
	uint8_t operationType;
	int32_t inlineImportVariableIndex;
	Token token;
	struct Node *firstChild;
	struct Node *sibling;
	struct Node *parent; // Set in ASTSetScopes.
	Scope *scope; // Set in ASTSetScopes.
	struct Node *expressionType; // Set in ASTSetTypes.
	struct ImportData *importData;
} Node;

typedef struct Value {
	union {
		int64_t i;
		double f;
	};
} Value;

typedef struct LineNumber {
	struct ImportData *importData;
	uint32_t instructionPointer;
	uint32_t lineNumber;
	Token *function;
} LineNumber;

typedef struct FunctionBuilder {
	uint8_t *data;
	size_t dataBytes;
	size_t dataAllocated;
	LineNumber *lineNumbers;
	size_t lineNumberCount;
	size_t lineNumbersAllocated;
	int32_t scopeIndex;
	bool isPersistentVariable, isDotAssignment, isListAssignment;
	uintptr_t globalVariableOffset;
	struct ImportData *importData; // Only valid during script loading.
	Node *replResultType;
} FunctionBuilder;

typedef struct BackTraceItem {
	uint32_t instructionPointer : 30,
		 popResult : 1,
		 assertResult : 1;
	int32_t variableBase : 30;
} BackTraceItem;

typedef struct HeapEntry {
	uint8_t type;
	bool gcMark;
	bool internalValuesAreManaged;

	union {
		struct {
			// TODO Inlining small strings.
			size_t bytes;
			char *text;
		};

		struct {
			uint16_t fieldCount;
			Value *fields; // Managed bools placed before this.
		};

		struct {
			uint32_t length, allocated;
			Value *list;
		};

		struct {
			uintptr_t nextUnusedEntry;
		};

		struct {
			int64_t lambdaID;
			Value curryValue;
		};

		struct {
			uint32_t concat1, concat2;
			size_t concatBytes;
		};
	};
} HeapEntry;

typedef struct CoroutineState {
	Value *localVariables;
	bool *localVariableIsManaged;
	size_t localVariableCount;
	size_t localVariablesAllocated;
	Value stack[50]; // TODO Merge with variables?
	bool stackIsManaged[50];
	uintptr_t stackPointer;
	size_t stackEntriesAllocated;
	BackTraceItem backTrace[300];
	uintptr_t backTracePointer;
	uint64_t id;
	int64_t unblockedBy;
	struct CoroutineState *nextCoroutine;
	struct CoroutineState **previousCoroutineLink;
	struct CoroutineState *nextUnblockedCoroutine;
	struct CoroutineState **previousUnblockedCoroutineLink;
	struct CoroutineState *nextExternalCoroutine;
	struct CoroutineState **waiters;
	size_t waiterCount;
	size_t waitersAllocated;
	struct CoroutineState ***waitingOn;
	size_t waitingOnCount;
	bool awaiting, startedByAsync, externalCoroutine;
	uintptr_t instructionPointer;
	uintptr_t variableBase;
	Value externalCoroutineData;
	void *externalCoroutineData2;
} CoroutineState;

typedef struct ExecutionContext {
	Value *globalVariables;
	bool *globalVariableIsManaged;
	size_t globalVariableCount;

	HeapEntry *heap;
	uintptr_t heapFirstUnusedEntry;
	size_t heapEntriesAllocated;

	FunctionBuilder *functionData; // Cleanup the relations between ExecutionContext, FunctionBuilder, Tokenizer and ImportData.
	Node *rootNode; // Only valid during script loading.
	char *scriptPersistFile;
	struct ImportData *mainModule;

	CoroutineState *c; // Active coroutine.
	CoroutineState *allCoroutines;
	CoroutineState *unblockedCoroutines;
	uint64_t lastCoroutineID;
	uint32_t externalCoroutineCount;
} ExecutionContext;

typedef struct ExternalFunction {
	const char *cName;
	int (*callback)(ExecutionContext *context, Value *returnValue);
} ExternalFunction;

typedef struct ImportData {
	char *path;
	size_t pathBytes;
	void *fileData;
	size_t fileDataBytes;
	uintptr_t globalVariableOffset;
	struct ImportData *nextImport;
	struct ImportData *parentImport;
	Node *rootNode;
} ImportData;

Node globalExpressionTypeVoid = { .type = T_VOID };
Node globalExpressionTypeInt = { .type = T_INT };
Node globalExpressionTypeFloat = { .type = T_FLOAT };
Node globalExpressionTypeBool = { .type = T_BOOL };
Node globalExpressionTypeStr = { .type = T_STR };
Node globalExpressionTypeIntList = { .type = T_LIST, .firstChild = &globalExpressionTypeInt };

// Global variables:
const char *startFunction = "Start";
size_t startFunctionBytes = 5;
char **options;
bool *optionsMatched;
size_t optionCount;
ImportData *importedModules;
ImportData **importedModulesLink = &importedModules;

// Forward declarations:
Node *ParseBlock(Tokenizer *tokenizer, bool replMode);
Node *ParseExpression(Tokenizer *tokenizer, bool allowAssignment, uint8_t precedence);
void ScriptPrintNode(Node *node, int indent);
bool ScriptLoad(Tokenizer tokenizer, ExecutionContext *context, ImportData *importData, bool replMode);
void ScriptFreeCoroutine(CoroutineState *c);
uintptr_t HeapAllocate(ExecutionContext *context);

// --------------------------------- Platform layer definitions.

#if defined(_WIN32) || defined(__linux__) || defined(__APPLE__)
#include <assert.h>
#define Assert assert
#endif

void *AllocateFixed(size_t bytes);
void *AllocateResize(void *old, size_t bytes);
int MemoryCompare(const void *a, const void *b, size_t bytes);
void MemoryCopy(void *a, const void *b, size_t bytes);
size_t PrintIntegerToBuffer(char *buffer, size_t bufferBytes, int64_t i); // TODO This shouldn't be in the platform layer.
size_t PrintFloatToBuffer(char *buffer, size_t bufferBytes, double f); // TODO This shouldn't be in the platform layer.
void PrintDebug(const char *format, ...);
void PrintError(Tokenizer *tokenizer, const char *format, ...);
void PrintError2(Tokenizer *tokenizer, Node *node, const char *format, ...);
void PrintError3(const char *format, ...);
void PrintError4(ExecutionContext *context, uint32_t instructionPointer, const char *format, ...);
void PrintBackTrace(ExecutionContext *context, uint32_t instructionPointer, CoroutineState *c, const char *prefix);
void *FileLoad(const char *path, size_t *length);
CoroutineState *ExternalCoroutineWaitAny(ExecutionContext *context);
void ExternalPassREPLResult(ExecutionContext *context, Value value);

// --------------------------------- Base module.

char baseModuleSource[] = {
	// TODO Temporary.
	"struct DirectoryChild { str name; bool isDirectory; int size; };"
	"DirectoryChild[] Dir() { str[] names = DirectoryEnumerateChildren(\"0:\"); DirectoryChild[] result = new DirectoryChild[]; result:resize(names:len()); for int i = 0; i < names:len(); i += 1 { result[i] = new DirectoryChild; result[i].name = names[i]; result[i].isDirectory = PathIsDirectory(\"0:/\"+names[i]); result[i].size = FileGetSize(\"0:/\"+names[i]); } return result;}"
	"str[] OpenDocumentEnumerate() #extcall;"

	// Logging:

	"void PrintStdErr(str x) #extcall;"
	"void PrintStdErrWarning(str x) #extcall;"
	"void PrintStdErrHighlight(str x) #extcall;"

	// String operations:

	"str StringSlice(str x, int start, int end) #extcall;"
	"int CharacterToByte(str x) #extcall;"
	"bool StringContains(str haystack, str needle) {"
	"	for int i = 0; i <= haystack:len() - needle:len(); i += 1 {"
	"		bool match = true;"
	"		for int j = 0; j < needle:len(); j += 1 { if haystack[i + j] != needle[j] match = false; }"
	"		if match { return true; }"
	"	}"
	"	return false;"
	"}"
	"str StringTrim(str string) {"
	"	int start = 0;"
	"	int end = string:len();"
	"	while start != end && (string[start] == \" \" || string[start] == \"\\t\" || string[start] == \"\\r\" || string[start] == \"\\n\") { start += 1; }"
	"	while start != end && (string[end - 1] == \" \" || string[end - 1] == \"\\t\" || string[end - 1] == \"\\r\" || string[end - 1] == \"\\n\") { end -= 1; }"
	"	return StringSlice(string, start, end);"
	"}"
	"str[] StringSplitByCharacter(str string, str character, bool includeEmptyString) {"
	"	str[] list = new str[];"
	"	int x = 0;"
	"	for int i = 0; i < string:len(); i += 1 {"
	"		if string[i] == character {"
	"			if (includeEmptyString || x != i) { list:add(StringSlice(string, x, i)); }"
	"			x = i + 1;"
	"		}"
	"	}"
	"	if (includeEmptyString || x != string:len()) { list:add(StringSlice(string, x, string:len())); }"
	"	return list;"
	"}"
	"str StringJoin(str[] strings, str k, bool trailing) {"
	"	str c = \"\";"
	"	for int i = 0; i < strings:len(); i += 1 {"
	"		if i < strings:len() - 1 || trailing { c = c + strings[i] + k; }"
	"		else { c = c + strings[i]; }"
	"	}"
	"	return c;"
	"}"
	"bool CharacterIsAlnum(str c) {"
	"	int b = CharacterToByte(c);"
	"	return (b >= CharacterToByte(\"A\") && b <= CharacterToByte(\"Z\")) || (b >= CharacterToByte(\"a\") && b <= CharacterToByte(\"z\"))"
	"		|| (b >= CharacterToByte(\"0\") && b <= CharacterToByte(\"9\"));"
	"}"

	// Miscellaneous:

	"int SystemGetProcessorCount() #extcall;"
	"bool SystemRunningAsAdministrator() #extcall;"
	"str SystemGetHostName() #extcall;"
	"void SystemSleepMs(int ms) #extcall;"
	"int RandomInt(int min, int max) #extcall;"

	"str UUIDGenerate() {"
	"	str hexChars = \"0123456789abcdef\";"
	"	str result;"
	"	for int i = 0; i <  8; i += 1 { result += hexChars[RandomInt(0, 15)]; }"
	"	result += \"-\";"
	"	for int i = 0; i <  4; i += 1 { result += hexChars[RandomInt(0, 15)]; }"
	"	result += \"-4\";"
	"	for int i = 0; i <  3; i += 1 { result += hexChars[RandomInt(0, 15)]; }"
	"	result += \"-\" + hexChars[RandomInt(8, 11)];"
	"	for int i = 0; i <  3; i += 1 { result += hexChars[RandomInt(0, 15)]; }"
	"	result += \"-\";"
	"	for int i = 0; i < 12; i += 1 { result += hexChars[RandomInt(0, 15)]; }"
	"	return result;"
	"}"

	// File system access:

	"bool PathExists(str x) #extcall;"
	"bool PathIsFile(str source) #extcall;"
	"bool PathIsDirectory(str source) #extcall;"
	"bool PathIsLink(str source) #extcall;"
	"bool PathCreateDirectory(str x) #extcall;" // TODO Replace the return value with a enum.
	"bool PathDelete(str x) #extcall;" // TODO Replace the return value with a enum.
	"bool PathMove(str source, str destination) #extcall;"
	"str PathGetDefaultPrefix() #extcall;"
	"bool PathSetDefaultPrefixToScriptSourceDirectory() #extcall;"
	"str FileReadAll(str path) #extcall;" // TODO Returning an error?
	"bool FileWriteAll(str path, str x) #extcall;" // TODO Returning an error?
	"bool FileCopy(str source, str destination) #extcall;"
	"int FileGetSize(str path) #extcall;" // Returns -1 on error. TODO Returning an error code.
					      
	"bool _DirectoryInternalStartIteration(str path) #extcall;"
	"str _DirectoryInternalNextIteration() #extcall;"
	"void _DirectoryInternalEndIteration() #extcall;"
	"bool _DirectoryInternalEnumerateChildren(str path, str prefix, str[] result, bool recurse) {"
	"	if !_DirectoryInternalStartIteration(path) { return false; }"
	"	str child = _DirectoryInternalNextIteration();"
	"	int start = result:len();"
	"	while child != \"\" { result:add(child); child = _DirectoryInternalNextIteration(); }"
	"	_DirectoryInternalEndIteration();"
	"	int end = result:len();"
	"	if recurse {"
	"		for int i = start; i < end; i += 1 {"
	"			str actual = path + \"/\" + result[i];"
	"			if PathIsDirectory(actual) {"
	"				_DirectoryInternalEnumerateChildren(actual, prefix + result[i] + \"/\", result, true);"
	"			}"
	"		}"
	"	}"
	"	for int i = start; i < end; i += 1 {"
	"		result[i] = prefix + result[i];"
	"	}"
	"	return true;"
	"}"
	"str[] DirectoryEnumerateChildren(str path) {" // TODO Returning an error code.
	"	str[] result = new str[];"
	"	if _DirectoryInternalEnumerateChildren(path, \"\", result, false) { return result; }"
	"	return null;"
	"}"
	"str[] DirectoryEnumerateChildrenRecursively(str path) {" // TODO Returning an error code.
	"	str[] result = new str[];"
	"	if _DirectoryInternalEnumerateChildren(path, \"\", result, true) { return result; }"
	"	return null;"
	"}"

	"bool PathDeleteRecursively(str path) {"
	"	str[] all = DirectoryEnumerateChildrenRecursively(path);"
	"	if all == null { return false; }"
	"	for int i = 0; i < all:len(); i += 1 {"
	"		str p = path + \"/\" + all[i];"
	"		if PathIsFile(p) || PathIsLink(p) {"
	"			PathDelete(p);"
	"		}"
	"	}"
	"	for int i = all:len(); i > 0; i -= 1 {"
	"		str p = path + \"/\" + all[i - 1];"
	"		if PathIsDirectory(p) {"
	"			PathDelete(p);"
	"		}"
	"	}"
	"	return PathDelete(path);"
	"}"
	"bool PathCopyRecursively(str source, str destination) {"
	"	str[] all = DirectoryEnumerateChildrenRecursively(source);"
	"	if all == null { return false; }"
	"	if !PathCreateDirectory(destination) { return false; }"
	"	for int i = 0; i < all:len(); i += 1 {"
	"		str sourceItem = source + \"/\" + all[i];"
	"		str destinationItem = destination + \"/\" + all[i];"
	"		if PathIsDirectory(sourceItem) {"
	"			PathCreateDirectory(destinationItem);"
	"		} else {"
	"			if !FileCopy(sourceItem, destinationItem) { return false; }"
	"		}"
	"	}"
	"	return true;"
	"}"
	"bool PathCopyInto(str sourceDirectory, str item, str destinationDirectory) {"
	"	str sourceItem = sourceDirectory + \"/\"+ item;"
	"	str destinationItem = destinationDirectory + \"/\"+ item;"
	"	PathCreateLeadingDirectories(PathGetLeadingDirectories(destinationItem));"
	"	if PathIsDirectory(sourceItem) {"
	"		return PathCreateDirectory(destinationItem);"
	"	} else {"
	"		return FileCopy(sourceItem, destinationItem);"
	"	}"
	"}"
	"bool PathCopyAllInto(str sourceDirectory, str[] items, str destinationDirectory) {"
	"	for int i = 0; i < items:len(); i += 1 {"
	"		if !PathCopyInto(sourceDirectory, items[i], destinationDirectory) { return false; }"
	"	}"
	"	return true;"
	"}"
	"bool PathCopyFilteredInto(str sourceDirectory, str[] filter, int filterWildcard, str destinationDirectory) {"
	"	str[] items;"
	"	assert filter:len() > 0;"
	"	if filterWildcard != -1 || filter:len() > 1 { items = DirectoryEnumerateChildrenRecursively(sourceDirectory); }"
	"	else { items = DirectoryEnumerateChildren(sourceDirectory); }"
	"	for int i = 0; i < items:len(); i += 1 {"
	"		if PathMatchesFilter(items[i], filter, filterWildcard) {"
	"			if !PathCopyInto(sourceDirectory, items[i], destinationDirectory) { return false; }"
	"		}"
	"	}"
	"	return true;"
	"}"

	"bool PathMatchesFilter(str path, str[] filterComponents, int _filterWildcard) {"
	"	str[] pathComponents = StringSplitByCharacter(path, \"/\", false);"
	"	int filterWildcard = _filterWildcard;"
	"	if filterWildcard == -1 { "
	"		filterWildcard = pathComponents:len(); "
	"	}"
	"	if filterComponents:len() > pathComponents:len() { "
	"		return false; "
	"	}"
	"	for int i = 0; i < filterComponents:len(); i += 1 {"
	"		str filterComponent = filterComponents[i];"
	"		str pathComponent;"
	"		int wildcard = filterComponent:len();"
	"		if i < filterWildcard { "
	"			pathComponent = pathComponents[i]; "
	"		} else { "
	"			pathComponent = pathComponents[pathComponents:len() - filterComponents:len() + i]; "
	"		}"
	"		for int j = 0; j < filterComponent:len(); j += 1 { "
	"			if filterComponent[j] == \"*\" { "
	"				wildcard = j; "
	"				if pathComponent:len() < filterComponent:len() - 1 {"
	"					return false;"
	"				}"
	"			} "
	"		}"
	"		for int j = 0; j < filterComponent:len(); j += 1 {"
	"			if j < wildcard {"
	"				if filterComponent[j] != pathComponent[j] { return false; }"
	"			} else if j > wildcard {"
	"				if filterComponent[j] != pathComponent[pathComponent:len() - filterComponent:len() + j] { return false; }"
	"			}"
	"		}"
	"	}"
	"	return true;"
	"}"
	"str PathGetLeadingDirectories(str path) {"
	"	for int i = path:len() - 1; i >= 0; i -= 1 {"
	"		if path[i] == \"/\" {"
	"			return StringSlice(path, 0, i);"
	"		}"
	"	}"
	"	return \"\";"
	"}"
	"bool PathCreateLeadingDirectories(str path) {"
	"	for int i = 0; i < path:len(); i += 1 {"
	"		if path[i] == \"/\" {"
	"			PathCreateDirectory(StringSlice(path, 0, i));"
	"		}"
	"	}"
	"	return PathCreateDirectory(path);"
	"}"

	// Persistent variables:

	"bool PersistRead(str path) #extcall;"

	// Command line:

	"str ConsoleGetLine() #extcall;"
	"str SystemGetEnvironmentVariable(str name) #extcall;"
	"bool SystemSetEnvironmentVariable(str name, str value) #extcall;"
	"bool SystemShellExecute(str x) #extcall;" // Returns true on success.
	"bool SystemShellExecuteWithWorkingDirectory(str wd, str x) #extcall;" // Returns true on success.
	"str SystemShellEvaluate(str x) #extcall;"
	"void SystemShellEnableLogging(bool x) #extcall;"
};

// --------------------------------- External function calls.

int ExternalPrintStdErr(ExecutionContext *context, Value *returnValue);
int ExternalPrintStdErrWarning(ExecutionContext *context, Value *returnValue);
int ExternalPrintStdErrHighlight(ExecutionContext *context, Value *returnValue);
int ExternalConsoleGetLine(ExecutionContext *context, Value *returnValue);
int ExternalStringSlice(ExecutionContext *context, Value *returnValue);
int ExternalCharacterToByte(ExecutionContext *context, Value *returnValue);
int ExternalSystemShellExecute(ExecutionContext *context, Value *returnValue);
int ExternalSystemShellExecuteWithWorkingDirectory(ExecutionContext *context, Value *returnValue);
int ExternalSystemShellEvaluate(ExecutionContext *context, Value *returnValue);
int ExternalSystemShellEnableLogging(ExecutionContext *context, Value *returnValue);
int ExternalSystemGetProcessorCount(ExecutionContext *context, Value *returnValue);
int ExternalSystemGetEnvironmentVariable(ExecutionContext *context, Value *returnValue);
int ExternalSystemSetEnvironmentVariable(ExecutionContext *context, Value *returnValue);
int ExternalSystemRunningAsAdministrator(ExecutionContext *context, Value *returnValue);
int ExternalSystemGetHostName(ExecutionContext *context, Value *returnValue);
int ExternalSystemSleepMs(ExecutionContext *context, Value *returnValue);
int ExternalPathCreateDirectory(ExecutionContext *context, Value *returnValue);
int ExternalPathDelete(ExecutionContext *context, Value *returnValue);
int ExternalPathExists(ExecutionContext *context, Value *returnValue);
int ExternalPathIsFile(ExecutionContext *context, Value *returnValue);
int ExternalPathIsDirectory(ExecutionContext *context, Value *returnValue);
int ExternalPathIsLink(ExecutionContext *context, Value *returnValue);
int ExternalPathMove(ExecutionContext *context, Value *returnValue);
int ExternalPathGetDefaultPrefix(ExecutionContext *context, Value *returnValue);
int ExternalPathSetDefaultPrefixToScriptSourceDirectory(ExecutionContext *context, Value *returnValue);
int ExternalFileReadAll(ExecutionContext *context, Value *returnValue);
int ExternalFileWriteAll(ExecutionContext *context, Value *returnValue);
int ExternalFileCopy(ExecutionContext *context, Value *returnValue);
int ExternalFileGetSize(ExecutionContext *context, Value *returnValue);
int ExternalPersistRead(ExecutionContext *context, Value *returnValue);
int ExternalPersistWrite(ExecutionContext *context, Value *returnValue);
int ExternalRandomInt(ExecutionContext *context, Value *returnValue); // TODO This shouldn't be in the platform layer.
int External_DirectoryInternalStartIteration(ExecutionContext *context, Value *returnValue);
int External_DirectoryInternalNextIteration(ExecutionContext *context, Value *returnValue);
int External_DirectoryInternalEndIteration(ExecutionContext *context, Value *returnValue);
int ExternalOpenDocumentEnumerate(ExecutionContext *context, Value *returnValue);

ExternalFunction externalFunctions[] = {
	{ .cName = "PrintStdErr", .callback = ExternalPrintStdErr },
	{ .cName = "PrintStdErrWarning", .callback = ExternalPrintStdErrWarning },
	{ .cName = "PrintStdErrHighlight", .callback = ExternalPrintStdErrHighlight },
	{ .cName = "ConsoleGetLine", .callback = ExternalConsoleGetLine },
	{ .cName = "StringSlice", .callback = ExternalStringSlice },
	{ .cName = "CharacterToByte", .callback = ExternalCharacterToByte },
	{ .cName = "SystemShellExecute", .callback = ExternalSystemShellExecute },
	{ .cName = "SystemShellExecuteWithWorkingDirectory", .callback = ExternalSystemShellExecuteWithWorkingDirectory },
	{ .cName = "SystemShellEvaluate", .callback = ExternalSystemShellEvaluate },
	{ .cName = "SystemShellEnableLogging", .callback = ExternalSystemShellEnableLogging },
	{ .cName = "SystemGetProcessorCount", .callback = ExternalSystemGetProcessorCount },
	{ .cName = "SystemGetEnvironmentVariable", .callback = ExternalSystemGetEnvironmentVariable },
	{ .cName = "SystemSetEnvironmentVariable", .callback = ExternalSystemSetEnvironmentVariable },
	{ .cName = "SystemRunningAsAdministrator", .callback = ExternalSystemRunningAsAdministrator },
	{ .cName = "SystemGetHostName", .callback = ExternalSystemGetHostName },
	{ .cName = "SystemSleepMs", .callback = ExternalSystemSleepMs },
	{ .cName = "PathExists", .callback = ExternalPathExists },
	{ .cName = "PathIsFile", .callback = ExternalPathIsFile },
	{ .cName = "PathIsDirectory", .callback = ExternalPathIsDirectory },
	{ .cName = "PathIsLink", .callback = ExternalPathIsLink },
	{ .cName = "PathCreateDirectory", .callback = ExternalPathCreateDirectory },
	{ .cName = "PathDelete", .callback = ExternalPathDelete },
	{ .cName = "PathMove", .callback = ExternalPathMove },
	{ .cName = "PathGetDefaultPrefix", .callback = ExternalPathGetDefaultPrefix },
	{ .cName = "PathSetDefaultPrefixToScriptSourceDirectory", .callback = ExternalPathSetDefaultPrefixToScriptSourceDirectory },
	{ .cName = "FileReadAll", .callback = ExternalFileReadAll },
	{ .cName = "FileWriteAll", .callback = ExternalFileWriteAll },
	{ .cName = "FileCopy", .callback = ExternalFileCopy },
	{ .cName = "FileGetSize", .callback = ExternalFileGetSize },
	{ .cName = "PersistRead", .callback = ExternalPersistRead },
	{ .cName = "PersistWrite", .callback = ExternalPersistWrite },
	{ .cName = "RandomInt", .callback = ExternalRandomInt },
	{ .cName = "_DirectoryInternalStartIteration", .callback = External_DirectoryInternalStartIteration },
	{ .cName = "_DirectoryInternalNextIteration", .callback = External_DirectoryInternalNextIteration },
	{ .cName = "_DirectoryInternalEndIteration", .callback = External_DirectoryInternalEndIteration },
	{ .cName = "OpenDocumentEnumerate", .callback = ExternalOpenDocumentEnumerate },
};

// --------------------------------- Tokenization and parsing.

uint8_t TokenLookupPrecedence(uint8_t t) {
	if (t == T_EQUALS)          return 10;
	if (t == T_ADD_EQUALS)      return 10;
	if (t == T_MINUS_EQUALS)    return 10;
	if (t == T_ASTERISK_EQUALS) return 10;
	if (t == T_SLASH_EQUALS)    return 10;
	if (t == T_LOGICAL_OR)      return 14;
	if (t == T_LOGICAL_AND)     return 15;
	if (t == T_GREATER_THAN)    return 20;
	if (t == T_LESS_THAN)       return 20;
	if (t == T_GT_OR_EQUAL)     return 20;
	if (t == T_LT_OR_EQUAL)     return 20;
	if (t == T_DOUBLE_EQUALS)   return 20;
	if (t == T_NOT_EQUALS)      return 20;
	if (t == T_ADD)             return 50;
	if (t == T_MINUS)           return 50;
	if (t == T_ASTERISK)        return 60;
	if (t == T_SLASH)           return 60;
	if (t == T_LOGICAL_NOT)     return 70;
	if (t == T_NEGATE)          return 70;
	if (t == T_DOT)             return 80;
	if (t == T_COLON)           return 80;
	if (t == T_AWAIT)           return 90;
	if (t == T_LEFT_ROUND)      return 100;
	Assert(false);
	return 0;
}

Token TokenNext(Tokenizer *tokenizer) {
	// TODO Block comments.

	Token token = { 0 };
	token.type = T_ERROR;
	token.module = tokenizer->module;

	while (true) {
		if (tokenizer->position == tokenizer->inputBytes) {
			token.type = T_EOF;
			break;
		}

		uint8_t c = tokenizer->input[tokenizer->position];
		uint8_t c1 = tokenizer->position + 1 == tokenizer->inputBytes ? 0 : tokenizer->input[tokenizer->position + 1];
		token.text = &tokenizer->input[tokenizer->position];
		token.textBytes = 1;
		token.line = tokenizer->line;

		if (c == ' ' || c == '\t' || c == '\r') { 
			tokenizer->position++; 
			continue; 
		} else if (c == '\n') {
			tokenizer->position++; 
			tokenizer->line++;
			continue; 
		} else if (c == '/' && c1 == '/') {
			while (tokenizer->position != tokenizer->inputBytes && tokenizer->input[tokenizer->position] != '\n') {
				tokenizer->position++;
			}

			continue;
		}

		else if (c == '<' && c1 == '=' && (tokenizer->position += 2)) token.type = T_LT_OR_EQUAL;
		else if (c == '>' && c1 == '=' && (tokenizer->position += 2)) token.type = T_GT_OR_EQUAL;
		else if (c == '=' && c1 == '=' && (tokenizer->position += 2)) token.type = T_DOUBLE_EQUALS;
		else if (c == '!' && c1 == '=' && (tokenizer->position += 2)) token.type = T_NOT_EQUALS;
		else if (c == '&' && c1 == '&' && (tokenizer->position += 2)) token.type = T_LOGICAL_AND;
		else if (c == '|' && c1 == '|' && (tokenizer->position += 2)) token.type = T_LOGICAL_OR;
		else if (c == '+' && c1 == '=' && (tokenizer->position += 2)) token.type = T_ADD_EQUALS;
		else if (c == '-' && c1 == '=' && (tokenizer->position += 2)) token.type = T_MINUS_EQUALS;
		else if (c == '*' && c1 == '=' && (tokenizer->position += 2)) token.type = T_ASTERISK_EQUALS;
		else if (c == '/' && c1 == '=' && (tokenizer->position += 2)) token.type = T_SLASH_EQUALS;
		else if (c == '+' && ++tokenizer->position) token.type = T_ADD;
		else if (c == '-' && ++tokenizer->position) token.type = T_MINUS;
		else if (c == '*' && ++tokenizer->position) token.type = T_ASTERISK;
		else if (c == '/' && ++tokenizer->position) token.type = T_SLASH;
		else if (c == '(' && ++tokenizer->position) token.type = T_LEFT_ROUND;
		else if (c == ')' && ++tokenizer->position) token.type = T_RIGHT_ROUND;
		else if (c == '[' && ++tokenizer->position) token.type = T_LEFT_SQUARE;
		else if (c == ']' && ++tokenizer->position) token.type = T_RIGHT_SQUARE;
		else if (c == '{' && ++tokenizer->position) token.type = T_LEFT_FANCY;
		else if (c == '}' && ++tokenizer->position) token.type = T_RIGHT_FANCY;
		else if (c == ',' && ++tokenizer->position) token.type = T_COMMA;
		else if (c == ';' && ++tokenizer->position) token.type = T_SEMICOLON;
		else if (c == '=' && ++tokenizer->position) token.type = T_EQUALS;
		else if (c == '<' && ++tokenizer->position) token.type = T_LESS_THAN;
		else if (c == '>' && ++tokenizer->position) token.type = T_GREATER_THAN;
		else if (c == '%' && ++tokenizer->position) token.type = T_STR_INTERPOLATE;
		else if (c == '.' && ++tokenizer->position) token.type = T_DOT;
		else if (c == ':' && ++tokenizer->position) token.type = T_COLON;
		else if (c == '!' && ++tokenizer->position) token.type = T_LOGICAL_NOT;

		else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_') || (c >= 0x80) || c == '#') {
			token.textBytes = 0;
			token.type = T_IDENTIFIER;
			token.text = tokenizer->input + tokenizer->position;

			while ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') 
					|| (c == '_') || (c >= 0x80) || (c == '#' && !token.textBytes)) {
				tokenizer->position++;
				token.textBytes++;
				if (tokenizer->position == tokenizer->inputBytes) break;
				c = tokenizer->input[tokenizer->position];
			}

#define KEYWORD(x) (token.textBytes == sizeof(x) - 1 && 0 == MemoryCompare(x, token.text, token.textBytes))
			if (false) {}
			else if KEYWORD("#extcall") token.type = T_EXTCALL;
			else if KEYWORD("#import") token.type = T_IMPORT;
			else if KEYWORD("#inline") token.type = T_INLINE;
			else if KEYWORD("#option") token.type = T_OPTION;
			else if KEYWORD("#persist") token.type = T_PERSIST;
			else if KEYWORD("assert") token.type = T_ASSERT;
			else if KEYWORD("await") token.type = T_AWAIT;
			else if KEYWORD("bool") token.type = T_BOOL;
			else if KEYWORD("else") token.type = T_ELSE;
			else if KEYWORD("false") token.type = T_FALSE;
			else if KEYWORD("float") token.type = T_FLOAT;
			else if KEYWORD("for") token.type = T_FOR;
			else if KEYWORD("functype") token.type = T_FUNCTYPE;
			else if KEYWORD("if") token.type = T_IF;
			else if KEYWORD("int") token.type = T_INT;
			else if KEYWORD("new") token.type = T_NEW;
			else if KEYWORD("null") token.type = T_NULL;
			else if KEYWORD("return") token.type = T_RETURN;
			else if KEYWORD("str") token.type = T_STR;
			else if KEYWORD("struct") token.type = T_STRUCT;
			else if KEYWORD("true") token.type = T_TRUE;
			else if KEYWORD("void") token.type = T_VOID;
			else if KEYWORD("while") token.type = T_WHILE;

			else if (token.text[0] == '#') {
				PrintError(tokenizer, "Unrecognised #-token '%.*s'.\n", token.textBytes, token.text);
				tokenizer->error = true;
				token.type = T_ERROR;
				break;
			}
		} else if (c >= '0' && c <= '9') {
			// TODO Exponent notation.

			token.textBytes = 0;
			token.type = T_NUMERIC_LITERAL;
			token.text = tokenizer->input + tokenizer->position;

			while ((c >= '0' && c <= '9') || (c == '.')) {
				tokenizer->position++;
				token.textBytes++;
				if (tokenizer->position == tokenizer->inputBytes) break;
				c = tokenizer->input[tokenizer->position];
			}
		} else if (c == '"') {
			// TODO Escape sequence to insert an arbitrary codepoint.

			bool inInterpolation = false;
			intptr_t startPosition = ++tokenizer->position;
			intptr_t endPosition = -1;
			
			for (uintptr_t i = tokenizer->position; true; i++) {
				if (inInterpolation) {
					if (tokenizer->input[i] == '%') {
						inInterpolation = false;
					} else if (tokenizer->input[i] == '"') {
						PrintError(tokenizer, "Strings are not allowed within a string interpolation expression.\n");
						tokenizer->error = true;
						break;
					} else if (tokenizer->input[i] == '\n') {
						PrintError(tokenizer, "String interpolation expressions must stay on a single line.\n");
						tokenizer->error = true;
						break;
					} else {
						token.textBytes++;
					}
				} else if (i == tokenizer->inputBytes || tokenizer->input[i] == '\n') {
					PrintError(tokenizer, "String does not end before the end of the line.\n");
					tokenizer->error = true;
					break;
				} else if (tokenizer->input[i] == '"') {
					endPosition = i;
					break;
				} else if (tokenizer->input[i] == '%') {
					inInterpolation = true;
				} else if (tokenizer->input[i] == '\\') {
					if (i + 1 == tokenizer->inputBytes 
							|| (tokenizer->input[i + 1] != 'n' && tokenizer->input[i + 1] != 't' 
								&& tokenizer->input[i + 1] != 'r' 
								&& tokenizer->input[i + 1] != '%' 
								&& tokenizer->input[i + 1] != '"' && tokenizer->input[i + 1] != '\\')) {
						PrintError(tokenizer, "String contains unrecognized escape sequence '\\%c'. "
								"Possibilities are: '\\\\', '\\%%', '\\n', '\\r', '\\t' and '\\\"'\n", tokenizer->input[i + 1]);
						tokenizer->error = true;
						break;
					} else {
						i++;
						token.textBytes++;
					}
				} else {
					token.textBytes++;
				}
			}

			if (endPosition != -1) {
				token.text = tokenizer->input + startPosition;
				token.textBytes = endPosition - startPosition;
				token.type = T_STRING_LITERAL;
				tokenizer->position = endPosition + 1;
			}
		} else {
			PrintError(tokenizer, "Unexpected character '%c'.\n", c);
			tokenizer->error = true;
		}

		break;
	}

	return token;
}

Token TokenPeek(Tokenizer *tokenizer) {
	Tokenizer copy = *tokenizer;
	return TokenNext(&copy);
}

Node *ParseType(Tokenizer *tokenizer, bool maybe, bool allowVoid) {
	Node *node = (Node *) AllocateFixed(sizeof(Node));
	node->token = TokenNext(tokenizer);

	if (node->token.type == T_INT 
			|| node->token.type == T_FLOAT 
			|| node->token.type == T_STR 
			|| node->token.type == T_BOOL 
			|| node->token.type == T_VOID 
			|| node->token.type == T_IDENTIFIER) {
		node->type = node->token.type;

		if (!allowVoid && node->type == T_VOID) {
			if (!maybe) {
				PrintError2(tokenizer, node, "The 'void' type is not allowed here.\n");
			}

			return NULL;
		}

		bool first = true;

		while (true) {
			Token token = TokenPeek(tokenizer);

			if (token.type == T_ERROR) {
				return NULL;
			} else if (token.type == T_LEFT_SQUARE) {
				Node *list = (Node *) AllocateFixed(sizeof(Node));
				list->type = T_LIST;
				list->token = TokenNext(tokenizer);

				if (first) {
					list->firstChild = node;
					first = false;
					node = list;
				} else {
					Node *end = node;
					while (end->firstChild->firstChild) end = end->firstChild;
					list->firstChild = end->firstChild;
					end->firstChild = list;
				}

				token = TokenNext(tokenizer);

				if (token.type == T_ERROR) {
					return NULL;
				} else if (token.type != T_RIGHT_SQUARE) {
					if (!maybe) {
						PrintError2(tokenizer, node, "Expected a ']' after the '[' in an list type.\n");
					}

					return NULL;
				}
			} else {
				break;
			}
		}

		return node;
	} else if (!maybe) {
		PrintError2(tokenizer, node, "Expected a type. This can be 'int', 'float', 'bool', 'void', 'str', or an identifier.\n");
		return NULL;
	} else {
		return NULL;
	}
}

Node *ParseCall(Tokenizer *tokenizer, Node *function) {
	Node *call = (Node *) AllocateFixed(sizeof(Node));
	call->token = TokenNext(tokenizer);
	call->type = T_CALL;
	call->firstChild = function;
	Node *arguments = (Node *) AllocateFixed(sizeof(Node));
	arguments->type = T_ARGUMENTS;
	function->sibling = arguments;
	Node **link = &arguments->firstChild;

	if (call->token.type != T_LEFT_ROUND) {
		PrintError2(tokenizer, call, "Expected a '(' to start the list of arguments.\n");
		return NULL;
	}

	while (true) {
		Token token = TokenPeek(tokenizer);

		if (token.type == T_RIGHT_ROUND) {
			TokenNext(tokenizer);
			break;
		} else if (token.type == T_ERROR) {
			return NULL;
		}

		if (arguments->firstChild) {
			Token comma = TokenNext(tokenizer);

			if (comma.type != T_COMMA) {
				Node n = { .token = comma };
				PrintError2(tokenizer, &n, "Expected a comma to separate function arguments.\n");
				return NULL;
			}
		}

		Node *argument = ParseExpression(tokenizer, false, 0);
		if (!argument) return NULL;
		*link = argument;
		link = &argument->sibling;
	}

	return call;
}

Node *ParseExpression(Tokenizer *tokenizer, bool allowAssignment, uint8_t precedence) {
	Node *node = (Node *) AllocateFixed(sizeof(Node));
	node->token = TokenNext(tokenizer);
	if (node->token.type == T_ERROR) return NULL;

	if (node->token.type == T_IDENTIFIER) {
		node->type = T_VARIABLE;
	} else if (node->token.type == T_STRING_LITERAL) {
		Node *string = node;
		string->type = string->token.type;

		const char *raw = string->token.text;
		size_t rawBytes = string->token.textBytes;

		// It's impossible for size of the string to increase.
		char *output = (char *) AllocateFixed(rawBytes);
		size_t outputPosition = 0;

		string->token.text = output;
		string->token.textBytes = 0;

		for (uintptr_t i = 0; i < rawBytes; i++) {
			char c = raw[i];

			if (c == '\\') {
				Assert(i != rawBytes - 1);
				c = raw[++i];
				Assert(outputPosition != rawBytes);
				if (c == '\\') c = '\\';
				else if (c == 'n') c = '\n';
				else if (c == 'r') c = '\r';
				else if (c == 't') c = '\t';
				else if (c == '%') c = '%';
				else if (c == '"') c = '"';
				else Assert(false);
				output[outputPosition++] = c;
				string->token.textBytes++;
			} else if (c == '%') {
				Node *stringInterpolate = (Node *) AllocateFixed(sizeof(Node));
				stringInterpolate->type = T_STR_INTERPOLATE;
				stringInterpolate->firstChild = node;
				Tokenizer t = *tokenizer;
				t.position = raw - t.input + i + 1;
				stringInterpolate->firstChild->sibling = ParseExpression(&t, false, 0);
				if (!stringInterpolate->firstChild->sibling) return NULL;
				i = t.position - (raw - t.input);
				string = (Node *) AllocateFixed(sizeof(Node));
				string->type = T_STRING_LITERAL;
				string->token.text = output + outputPosition;
				string->token.textBytes = 0;
				stringInterpolate->firstChild->sibling->sibling = string;
				node = stringInterpolate;
			} else {
				Assert(outputPosition != rawBytes);
				output[outputPosition++] = c;
				string->token.textBytes++;
			}
		}
	} else if (node->token.type == T_NUMERIC_LITERAL
			|| node->token.type == T_TRUE || node->token.type == T_FALSE || node->token.type == T_NULL) {
		node->type = node->token.type;
	} else if (node->token.type == T_LOGICAL_NOT || node->token.type == T_MINUS) {
		node->type = node->token.type == T_MINUS ? T_NEGATE : T_LOGICAL_NOT;
		node->firstChild = ParseExpression(tokenizer, false, TokenLookupPrecedence(node->type));
	} else if (node->token.type == T_LEFT_ROUND) {
		node = ParseExpression(tokenizer, false, 0);
		if (!node) return NULL;

		Token token = TokenNext(tokenizer);

		if (token.type != T_RIGHT_ROUND) {
			Node n = { .token = token };
			PrintError2(tokenizer, &n, "Expected a matching closing bracket.\n");
			return NULL;
		}
	} else if (node->token.type == T_NEW) {
		node->type = T_NEW;
		node->firstChild = ParseType(tokenizer, false, false);
		node->expressionType = node->firstChild;
		if (!node->firstChild) return NULL;
	} else if (node->token.type == T_LEFT_SQUARE) {
		node->type = T_LIST_LITERAL;
		Node **link = &node->firstChild;
		bool needComma = false;

		while (true) {
			Token peek = TokenPeek(tokenizer);

			if (peek.type == T_ERROR) {
				return NULL;
			} else if (peek.type == T_RIGHT_SQUARE) {
				TokenNext(tokenizer);
				break;
			}

			if (needComma) {
				if (peek.type != T_COMMA) {
					PrintError(tokenizer, "Expected a comma between list literal items.\n");
					return NULL;
				} else {
					TokenNext(tokenizer);
				}
			}

			Node *item = ParseExpression(tokenizer, false, 0);
			if (!item) return NULL;
			*link = item;
			link = &item->sibling;
			needComma = true;
		}
	} else if (node->token.type == T_AWAIT) {
		node->type = T_AWAIT;
		node->firstChild = ParseExpression(tokenizer, false, TokenLookupPrecedence(node->token.type));
	} else {
		PrintError2(tokenizer, node, "Expected an expression. "
				"Expressions can start with a variable identifier, a string literal, a number, 'await', 'new', '-', '!', '[' or '('.\n");
		return NULL;
	}

	while (true) {
		Token token = TokenPeek(tokenizer);

		if (token.type == T_ERROR) {
			return NULL;
		} else if ((token.type == T_EQUALS || token.type == T_ADD_EQUALS || token.type == T_MINUS_EQUALS 
					|| token.type == T_ASTERISK_EQUALS || token.type == T_SLASH_EQUALS) && !allowAssignment) {
			PrintError2(tokenizer, node, "Variable assignment is not allowed within an expression.\n");
			return NULL;
		} else if (token.type == T_DOT && TokenLookupPrecedence(token.type) > precedence) {
			TokenNext(tokenizer);
			Node *operation = (Node *) AllocateFixed(sizeof(Node));
			operation->token = TokenNext(tokenizer);
			operation->type = T_DOT;
			operation->firstChild = node;
			node = operation;

			if (operation->token.type != T_IDENTIFIER) {
				PrintError2(tokenizer, node, "Expected an identifier for the struct field after '.'.\n");
				return NULL;
			}
		} else if (token.type == T_COLON && TokenLookupPrecedence(token.type) > precedence) {
			TokenNext(tokenizer);
			Token operationName = TokenNext(tokenizer);

			if (operationName.type != T_IDENTIFIER && operationName.type != T_ASSERT) {
				PrintError2(tokenizer, node, "Expected an identifier for the operation name after ':'.\n");
				return NULL;
			}

			node = ParseCall(tokenizer, node);
			if (!node) return NULL;
			node->type = T_COLON;
			node->token = operationName;
		} else if ((token.type == T_EQUALS || token.type == T_ADD || token.type == T_MINUS
					|| token.type == T_ASTERISK || token.type == T_SLASH
					|| token.type == T_GREATER_THAN || token.type == T_LESS_THAN
					|| token.type == T_LT_OR_EQUAL || token.type == T_GT_OR_EQUAL
					|| token.type == T_DOUBLE_EQUALS || token.type == T_NOT_EQUALS
					|| token.type == T_LOGICAL_AND || token.type == T_LOGICAL_OR) 
				&& TokenLookupPrecedence(token.type) > precedence) {
			Node *operation = (Node *) AllocateFixed(sizeof(Node));
			operation->token = TokenNext(tokenizer);
			operation->type = operation->token.type;
			operation->firstChild = node;
			node->sibling = ParseExpression(tokenizer, false, TokenLookupPrecedence(token.type));
			if (!node->sibling) return NULL;
			node = operation;
		} else if (token.type == T_ADD_EQUALS || token.type == T_MINUS_EQUALS
				|| token.type == T_ASTERISK_EQUALS || token.type == T_SLASH_EQUALS) {
			Node *operation = (Node *) AllocateFixed(sizeof(Node));
			operation->token = TokenNext(tokenizer);
			operation->type = operation->token.type - T_ADD_EQUALS + T_ADD;
			operation->firstChild = node;
			node->sibling = ParseExpression(tokenizer, false, TokenLookupPrecedence(token.type));
			if (!node->sibling) return NULL;
			Node *nodeCopy = (Node *) AllocateFixed(sizeof(Node));
			*nodeCopy = *node;
			node = operation;
			operation = (Node *) AllocateFixed(sizeof(Node));
			operation->token = token;
			operation->type = T_EQUALS;
			operation->firstChild = nodeCopy;
			operation->firstChild->sibling = node;
			node = operation;
		} else if (token.type == T_LEFT_ROUND && TokenLookupPrecedence(token.type) > precedence) {
			node = ParseCall(tokenizer, node);
			if (!node) return NULL;
		} else if (token.type == T_LEFT_SQUARE) {
			TokenNext(tokenizer);
			Node *index = (Node *) AllocateFixed(sizeof(Node));
			index->type = T_INDEX;
			index->token = token;
			index->firstChild = node;
			index->firstChild->sibling = ParseExpression(tokenizer, false, 0);
			if (!index->firstChild->sibling) return NULL;
			node = index;
			Token token = TokenNext(tokenizer);

			if (token.type != T_RIGHT_SQUARE) {
				Node n = { .token = token };
				PrintError2(tokenizer, &n, "Expected a matching closing bracket.\n");
				return NULL;
			}
		} else {
			break;
		}
	}

	return node;
}

Node *ParseIf(Tokenizer *tokenizer) {
	Node *node = (Node *) AllocateFixed(sizeof(Node));
	node->type = T_IF;
	node->token = TokenNext(tokenizer);
	node->firstChild = ParseExpression(tokenizer, false, 0);
	if (!node->firstChild) return NULL;
	Token token = TokenPeek(tokenizer);

	if (token.type == T_LEFT_FANCY) {
		TokenNext(tokenizer);
		node->firstChild->sibling = ParseBlock(tokenizer, false);
		if (!node->firstChild->sibling) return NULL;
	} else {
		Node *wrapper = (Node *) AllocateFixed(sizeof(Node));
		wrapper->type = T_BLOCK;
		wrapper->firstChild = ParseExpression(tokenizer, true, 0);
		if (!wrapper->firstChild) return NULL;
		node->firstChild->sibling = wrapper;

		Token semicolon = TokenNext(tokenizer);

		if (semicolon.type != T_SEMICOLON) {
			PrintError2(tokenizer, node->firstChild->sibling, "Expected a semicolon at the end of the expression.\n");
			return NULL;
		}
	}

	token = TokenPeek(tokenizer);

	if (token.type == T_ELSE) {
		TokenNext(tokenizer);
		token = TokenPeek(tokenizer);

		if (token.type == T_IF) {
			node->firstChild->sibling->sibling = ParseIf(tokenizer);
			if (!node->firstChild->sibling->sibling) return NULL;
		} else if (token.type == T_LEFT_FANCY) {
			TokenNext(tokenizer);
			node->firstChild->sibling->sibling = ParseBlock(tokenizer, false);
			if (!node->firstChild->sibling->sibling) return NULL;
		} else {
			node->firstChild->sibling->sibling = ParseExpression(tokenizer, true, 0);
			if (!node->firstChild->sibling->sibling) return NULL;

			Token semicolon = TokenNext(tokenizer);

			if (semicolon.type != T_SEMICOLON) {
				PrintError2(tokenizer, node->firstChild->sibling, "Expected a semicolon at the end of the expression.\n");
				return NULL;
			}
		}
	}

	return node;
}

Node *ParseVariableDeclarationOrExpression(Tokenizer *tokenizer, bool replMode) {
	Tokenizer copy = *tokenizer;
	bool isVariableDeclaration = false;

	if (ParseType(tokenizer, true, false) && TokenNext(tokenizer).type == T_IDENTIFIER) {
		Token equalsOrSemicolon = TokenNext(tokenizer);

		if (equalsOrSemicolon.type == T_EQUALS || equalsOrSemicolon.type == T_SEMICOLON) {
			isVariableDeclaration = true;
		}
	}

	if (tokenizer->error) {
		return NULL;
	}

	*tokenizer = copy;

	if (isVariableDeclaration) {
		// TODO Variable support in replMode.
		if (replMode) {
			PrintError(tokenizer, "Variables are not yet supported in the console.\n");
			return NULL;
		}

		Node *declaration = (Node *) AllocateFixed(sizeof(Node));
		declaration->type = T_DECLARE;
		declaration->firstChild = ParseType(tokenizer, false, false);
		Assert(declaration->firstChild);
		declaration->token = TokenNext(tokenizer);
		Assert(declaration->token.type == T_IDENTIFIER);
		Token equalsOrSemicolon = TokenNext(tokenizer);
		Assert(equalsOrSemicolon.type == T_EQUALS || equalsOrSemicolon.type == T_SEMICOLON);

		if (equalsOrSemicolon.type == T_EQUALS) {
			declaration->firstChild->sibling = ParseExpression(tokenizer, false, 0);
			if (!declaration->firstChild->sibling) return NULL;
			Token semicolon = TokenNext(tokenizer);

			if (semicolon.type != T_SEMICOLON) {
				PrintError2(tokenizer, declaration, "Expected a semicolon at the end of the variable declaration.\n");
				return NULL;
			}
		}

		return declaration;
	} else {
		Node *expression = ParseExpression(tokenizer, true, 0);
		if (!expression) return NULL;

		if (replMode) {
			Token end = TokenPeek(tokenizer);

			if (end.type == T_ERROR) {
				return NULL;
			} else if (end.type == T_EOF) {
				Node *replResult = (Node *) AllocateFixed(sizeof(Node));
				replResult->type = T_REPL_RESULT;
				replResult->firstChild = expression;
				return replResult;
			}
		}

		Token semicolon = TokenNext(tokenizer);

		if (semicolon.type != T_SEMICOLON) {
			PrintError2(tokenizer, expression, "Expected a semicolon at the end of the expression.\n");
			return NULL;
		}

		return expression;
	}
}

Node *ParseBlock(Tokenizer *tokenizer, bool replMode) {
	Node *node = (Node *) AllocateFixed(sizeof(Node));
	Node **link = &node->firstChild;
	node->type = T_BLOCK;
	node->token.line = tokenizer->line;
	node->token.module = tokenizer->module;

	while (true) {
		Token token = TokenPeek(tokenizer);

		if (token.type == T_ERROR) {
			return NULL;
		} else if (token.type == (replMode ? T_EOF : T_RIGHT_FANCY)) {
			TokenNext(tokenizer);
			return node;
		} else if (token.type == T_IF) {
			Node *node = ParseIf(tokenizer);
			if (!node) return NULL;
			*link = node;
			link = &node->sibling;
		} else if (token.type == T_WHILE) {
			Node *node = (Node *) AllocateFixed(sizeof(Node));
			node->type = T_WHILE;
			node->token = TokenNext(tokenizer);
			node->firstChild = ParseExpression(tokenizer, false, 0);
			if (!node->firstChild) return NULL;
			Token token = TokenNext(tokenizer);

			if (token.type != T_LEFT_FANCY && token.type != T_SEMICOLON) {
				PrintError2(tokenizer, node, "Expected a block or semicolon after the while condition.\n");
				return NULL;
			}

			if (token.type == T_LEFT_FANCY) {
				node->firstChild->sibling = ParseBlock(tokenizer, false);
				if (!node->firstChild->sibling) return NULL;
			} else {
				node->firstChild->sibling = (Node *) AllocateFixed(sizeof(Node));
				node->firstChild->sibling->type = T_BLOCK;
			}

			*link = node;
			link = &node->sibling;
		} else if (token.type == T_FOR) {
			// TODO Optional components.

			Node *node = (Node *) AllocateFixed(sizeof(Node));
			node->type = T_FOR;
			node->token = TokenNext(tokenizer);
			node->firstChild = ParseVariableDeclarationOrExpression(tokenizer, false);
			if (!node->firstChild) return NULL;
			node->firstChild->sibling = ParseExpression(tokenizer, false, 0);
			if (!node->firstChild->sibling) return NULL;

			Token token = TokenNext(tokenizer);

			if (token.type != T_SEMICOLON) {
				PrintError2(tokenizer, node, "Expected a semicolon.\n");
				return NULL;
			}

			node->firstChild->sibling->sibling = ParseExpression(tokenizer, true, 0);
			if (!node->firstChild->sibling->sibling) return NULL;

			token = TokenNext(tokenizer);

			if (token.type != T_LEFT_FANCY && token.type != T_SEMICOLON) {
				PrintError2(tokenizer, node, "Expected a block or semicolon to complete the for statement.\n");
				return NULL;
			}

			if (token.type == T_LEFT_FANCY) {
				node->firstChild->sibling->sibling->sibling = ParseBlock(tokenizer, false);
				if (!node->firstChild->sibling->sibling->sibling) return NULL;
			} else {
				node->firstChild->sibling->sibling->sibling = (Node *) AllocateFixed(sizeof(Node));
				node->firstChild->sibling->sibling->sibling->type = T_BLOCK;
			}

			// Make sure that the for variable has its own scope.
			Node *wrapper = (Node *) AllocateFixed(sizeof(Node));
			wrapper->type = T_BLOCK;
			wrapper->token = node->token;
			wrapper->firstChild = node;

			*link = wrapper;
			link = &wrapper->sibling;
		} else if (token.type == T_RETURN || token.type == T_ASSERT) {
			if (replMode) {
				PrintError2(tokenizer, node, "%s statements cannot be used in the console.\n", 
						token.type == T_RETURN ? "Return" : "Assert");
				return NULL;
			}

			Node *node = (Node *) AllocateFixed(sizeof(Node));
			node->type = token.type;
			node->token = TokenNext(tokenizer);
			*link = node;
			link = &node->sibling;

			if (token.type == T_ASSERT || TokenPeek(tokenizer).type != T_SEMICOLON) {
				node->firstChild = ParseExpression(tokenizer, false, 0);
				if (!node->firstChild) return NULL;
			}

			Token semicolon = TokenNext(tokenizer);

			if (semicolon.type != T_SEMICOLON) {
				PrintError2(tokenizer, node, "Expected a semicolon at the end of the statement.\n");
				return NULL;
			}
		} else if (token.type == T_LEFT_FANCY) {
			TokenNext(tokenizer);
			Node *block = ParseBlock(tokenizer, false);
			if (!block) return NULL;
			*link = block;
			link = &block->sibling;
		} else {
			Node *node = ParseVariableDeclarationOrExpression(tokenizer, replMode);
			if (!node) return NULL;
			*link = node;
			link = &node->sibling;
		}
	}
}

Node *ParseGlobalVariableOrFunctionDefinition(Tokenizer *tokenizer, bool allowGlobalVariables, bool parseFunctionBody) {
	Node *type = ParseType(tokenizer, false, true);

	if (!type) {
		return NULL;
	}

	Node *node = (Node *) AllocateFixed(sizeof(Node));
	node->token = TokenNext(tokenizer);

	if (node->token.type != T_IDENTIFIER) {
		if (allowGlobalVariables) {
			PrintError2(tokenizer, node, "Expected an identifier for the name of the function or global variable.\n");
		} else {
			PrintError2(tokenizer, node, "Expected an identifier for the name of the function pointer type.\n");
		}

		return NULL;
	}

	Token bracket = TokenPeek(tokenizer);

	if (bracket.type == T_ERROR) {
		return NULL;
	} else if (bracket.type == T_LEFT_ROUND) {
		TokenNext(tokenizer);
		node->type = T_FUNCTION;
		Node *functionPointerType = (Node *) AllocateFixed(sizeof(Node));
		functionPointerType->type = T_FUNCPTR;
		node->firstChild = functionPointerType;
		Node *arguments = (Node *) AllocateFixed(sizeof(Node));
		arguments->type = T_ARGUMENTS;
		functionPointerType->firstChild = arguments;
		arguments->sibling = type;
		Node **link = &arguments->firstChild;
		int argumentCount = 0;

		while (true) {
			Token token = TokenPeek(tokenizer);

			if (token.type == T_RIGHT_ROUND) {
				TokenNext(tokenizer);
				break;
			}

			if (arguments->firstChild) {
				Token comma = TokenNext(tokenizer);

				if (comma.type != T_COMMA) {
					Node n = { .token = comma };
					PrintError2(tokenizer, &n, "Expected a comma to separate function arguments.\n");
					return NULL;
				}
			}

			Node *argument = (Node *) AllocateFixed(sizeof(Node));
			argument->type = T_ARGUMENT;
			argument->firstChild = ParseType(tokenizer, false, false);
			if (!argument->firstChild) return NULL;
			argument->token = TokenNext(tokenizer);
			*link = argument;
			link = &argument->sibling;
			argumentCount++;

#define FUNCTION_MAX_ARGUMENTS (20)
			if (argumentCount > FUNCTION_MAX_ARGUMENTS) {
				PrintError2(tokenizer, argument, "Functions cannot have more than %d arguments.\n", FUNCTION_MAX_ARGUMENTS);
				return NULL;
			}

			if (argument->token.type != T_IDENTIFIER) {
				PrintError2(tokenizer, argument, "Expected an identifier for the name of the function argument.\n");
				return NULL;
			}
		}

		if (parseFunctionBody) {
			Token token = TokenNext(tokenizer);

			if (token.type == T_LEFT_FANCY) {
				Node *body = (Node *) AllocateFixed(sizeof(Node));
				body->type = T_FUNCBODY;
				functionPointerType->sibling = body;
				body->firstChild = ParseBlock(tokenizer, false);

				if (!body->firstChild) {
					return NULL;
				}
			} else if (token.type == T_EXTCALL) {
				Token semicolon = TokenNext(tokenizer);

				if (semicolon.type != T_SEMICOLON) {
					PrintError(tokenizer, "Expected a semicolon after 'extcall'.\n");
					return NULL;
				}

				node->isExternalCall = true;
			} else {
				Node n = { .token = token };
				PrintError2(tokenizer, &n, "Expected a '{' to start the function body.\n");
				return NULL;
			}
		}
	} else if (allowGlobalVariables) {
		Token semicolon = TokenNext(tokenizer);

		if (semicolon.type == T_PERSIST) {
			node->isPersistentVariable = true;
			semicolon = TokenNext(tokenizer);
		}

		if (semicolon.type == T_OPTION) {
			node->isOptionVariable = true;
			semicolon = TokenNext(tokenizer);
		}

		if (semicolon.type != T_SEMICOLON) {
			PrintError(tokenizer, "Expected a semicolon after the global variable definition.\n");
			return NULL;
		}

		node->type = T_DECLARE;
		node->firstChild = type;
	} else {
		PrintError(tokenizer, "Expected a '(' to start the argument list.\n");
		return NULL;
	}

	return node;
}

Node *ParseRootREPL(Tokenizer *tokenizer) {
	// TODO Importing modules.

	Node *root = (Node *) AllocateFixed(sizeof(Node));
	root->type = T_ROOT;
	Node **link = &root->firstChild;

	Node *node = (Node *) AllocateFixed(sizeof(Node));
	node->type = T_IMPORT;
	node->token.type = T_INLINE;
	node->token.text = "#inline";
	node->token.textBytes = 7;
	node->firstChild = (Node *) AllocateFixed(sizeof(Node));
	node->firstChild->type = T_IMPORT_PATH;
	node->firstChild->token.type = T_STRING_LITERAL;
	node->firstChild->token.text = "__base_module__";
	node->firstChild->token.textBytes = 15;
	*link = node;
	link = &node->sibling;

	Node *function = (Node *) AllocateFixed(sizeof(Node));
	*link = function;
	link = &function->sibling;
	function->type = T_FUNCTION;
	function->token.text = startFunction;
	function->token.textBytes = startFunctionBytes;
	Node *functionPointerType = (Node *) AllocateFixed(sizeof(Node));
	function->firstChild = functionPointerType;
	functionPointerType->type = T_FUNCPTR;
	Node *functionArguments = (Node *) AllocateFixed(sizeof(Node));
	functionPointerType->firstChild = functionArguments;
	functionArguments->type = T_ARGUMENTS;
	Node *functionReturnType = (Node *) AllocateFixed(sizeof(Node));
	functionArguments->sibling = functionReturnType;
	functionReturnType->type = T_VOID;
	Node *functionBody = (Node *) AllocateFixed(sizeof(Node));
	functionPointerType->sibling = functionBody;
	functionBody->type = T_FUNCBODY;
	functionBody->firstChild = ParseBlock(tokenizer, true);

	if (!functionBody->firstChild) {
		return NULL;
	}

	return root;
}

Node *ParseRoot(Tokenizer *tokenizer) {
	Node *root = (Node *) AllocateFixed(sizeof(Node));
	root->type = T_ROOT;
	Node **link = &root->firstChild;

	if (!tokenizer->isBaseModule) {
		Node *node = (Node *) AllocateFixed(sizeof(Node));
		node->type = T_IMPORT;
		node->token.type = T_INLINE;
		node->token.text = "#inline";
		node->token.textBytes = 7;
		node->firstChild = (Node *) AllocateFixed(sizeof(Node));
		node->firstChild->type = T_IMPORT_PATH;
		node->firstChild->token.type = T_STRING_LITERAL;
		node->firstChild->token.text = "__base_module__";
		node->firstChild->token.textBytes = 15;
		*link = node;
		link = &node->sibling;
	}

	while (true) {
		Token token = TokenPeek(tokenizer);

		if (token.type == T_ERROR) {
			return NULL;
		} else if (token.type == T_EOF) {
			return root;
		} else if (token.type == T_FUNCTYPE) {
			TokenNext(tokenizer);
			Node *node = ParseGlobalVariableOrFunctionDefinition(tokenizer, false, false);
			if (!node) return NULL;
			node->type = T_FUNCTYPE;
			*link = node;
			link = &node->sibling;

			Token semicolon = TokenNext(tokenizer);

			if (semicolon.type != T_SEMICOLON) {
				PrintError2(tokenizer, node->firstChild->sibling, "Expected a semicolon after the argument list.\n");
				return NULL;
			}
		} else if (token.type == T_STRUCT) {
			TokenNext(tokenizer);
			Node *node = (Node *) AllocateFixed(sizeof(Node));
			node->type = T_STRUCT;
			node->token = TokenNext(tokenizer);
			*link = node;
			link = &node->sibling;

			if (node->token.type != T_IDENTIFIER) {
				PrintError2(tokenizer, node, "Expected the name of the struct.\n");
				return NULL;
			}

			if (TokenNext(tokenizer).type != T_LEFT_FANCY) {
				PrintError2(tokenizer, node, "Expected a '{' for the struct contents after the name.\n");
				return NULL;
			}

			Node **fieldLink = &node->firstChild;

			while (true) {
				Token peek = TokenPeek(tokenizer);
				if (peek.type == T_ERROR) return NULL;
				if (peek.type == T_RIGHT_FANCY) break;
				Node *type = ParseType(tokenizer, false, true);
				if (!type) return NULL;
				Node *field = (Node *) AllocateFixed(sizeof(Node));
				field->token = TokenNext(tokenizer);
				field->type = T_DECLARE;
				field->firstChild = type;
				*fieldLink = field;
				fieldLink = &field->sibling;

				if (field->token.type != T_IDENTIFIER) {
					PrintError2(tokenizer, field, "Expected an identifier for the field name.\n");
					return NULL;
				}

				if (TokenNext(tokenizer).type != T_SEMICOLON) {
					PrintError2(tokenizer, field, "Expected a semicolon after the field name.\n");
					return NULL;
				}
			}

			TokenNext(tokenizer);

			if (TokenNext(tokenizer).type != T_SEMICOLON) {
				PrintError2(tokenizer, node, "Expected a semicolon after the struct body.\n");
				return NULL;
			}
		} else if (token.type == T_IMPORT) {
			Node *node = (Node *) AllocateFixed(sizeof(Node));
			node->type = T_IMPORT;
			TokenNext(tokenizer);
			node->firstChild = ParseExpression(tokenizer, false, 0);
			if (!node->firstChild) return NULL;
			node->token = TokenNext(tokenizer);

			if (node->firstChild->type != T_STRING_LITERAL) {
				PrintError2(tokenizer, node, "The path to the script file to import must be a string literal.\n");
				return NULL;
			} else if (node->token.type != T_IDENTIFIER && node->token.type != T_INLINE) {
				PrintError2(tokenizer, node, "Expected an identifier for the name to import the module as.\n");
				return NULL;
			}

			node->firstChild->type = T_IMPORT_PATH;
			*link = node;
			link = &node->sibling;

			if (TokenNext(tokenizer).type != T_SEMICOLON) {
				PrintError2(tokenizer, node, "Expected a semicolon after the import statement.\n");
				return NULL;
			}
		} else {
			Node *node = ParseGlobalVariableOrFunctionDefinition(tokenizer, true, true);
			if (!node) return NULL;
			*link = node;
			link = &node->sibling;
		}
	}
}

// --------------------------------- Scope management.

bool ScopeIsVariableType(Node *node) {
	return node->type == T_DECLARE || node->type == T_FUNCTION || node->type == T_ARGUMENT;
}

intptr_t ScopeLookupIndex(Node *node, Scope *scope, bool maybe, bool real /* if false, the variable index is returned */) {
	uintptr_t j = 0;

	for (uintptr_t i = 0; i < scope->entryCount; i++) {
		if (scope->entries[i]->token.textBytes == node->token.textBytes
				&& 0 == MemoryCompare(scope->entries[i]->token.text, node->token.text, node->token.textBytes)) {
			if (!ScopeIsVariableType(scope->entries[i]) && !real) {
				break;
			}

			return j;
		}

		if (ScopeIsVariableType(scope->entries[i]) || real) {
			j++;
		}
	}

	if (!maybe) {
		Assert(false);
	}

	return -1;
}

Node *ScopeLookup(Tokenizer *tokenizer, Node *node, bool maybe) {
	Node *ancestor = node;
	Scope *scope = NULL;

	while (ancestor) {
		if (ancestor->scope != scope) {
			scope = ancestor->scope;

			for (uintptr_t i = 0; i < scope->entryCount; i++) {
				if (scope->entries[i]->token.textBytes == node->token.textBytes
						&& 0 == MemoryCompare(scope->entries[i]->token.text, node->token.text, node->token.textBytes)) {
					if (node->referencesRootScope && scope->entries[i]->parent->type != T_ROOT) {
						PrintError2(tokenizer, node, "The identifier '%.*s' is used before it is declared in this scope.\n", 
								node->token.textBytes, node->token.text);
						return NULL;
					}

					return scope->entries[i];
				}
			}
		}

		ancestor = ancestor->parent;
	}

	if (!maybe) {
		PrintError2(tokenizer, node, "Could not find identifier '%.*s' inside its scope.\n", node->token.textBytes, node->token.text);
	}

	return NULL;
}

bool ScopeCheckNotAlreadyUsed(Tokenizer *tokenizer, Node *node) {
	Node *ancestor = node;
	Scope *scope = NULL;

	while (ancestor) {
		if (ancestor->scope != scope) {
			scope = ancestor->scope;

			for (uintptr_t i = 0; i < scope->entryCount; i++) {
				if (scope->entries[i]->token.textBytes == node->token.textBytes
						&& 0 == MemoryCompare(scope->entries[i]->token.text, node->token.text, node->token.textBytes)
						&& (!scope->isRoot || node->scope == scope)) {
					PrintError2(tokenizer, node, "The identifier '%.*s' was already used in this scope.\n", 
							node->token.textBytes, node->token.text);

					if (scope->entries[i]->type == T_INLINE) {
						if (scope->entries[i]->importData->pathBytes == 15 
								&& 0 == MemoryCompare(scope->entries[i]->importData->path, "__base_module__", scope->entries[i]->importData->pathBytes)) {
							PrintDebug("It was declared in base library module.\n", 
									scope->entries[i]->importData->path);
						} else {
							PrintDebug("It was imported inline from the module '%s'.\n", 
									scope->entries[i]->importData->path);
						}
					}

					return false;
				}
			}
		}

		ancestor = ancestor->parent;
	}

	return true;
}

bool ScopeAddEntry(Tokenizer *tokenizer, Scope *scope, Node *node) {
	if (node->type == T_IMPORT && node->token.type == T_INLINE) {
		return true;
	}

	node->scope = scope;

	if (scope->entryCount == scope->entriesAllocated) {
		scope->entriesAllocated = scope->entriesAllocated ? scope->entriesAllocated * 2 : 4;
		scope->entries = (Node **) AllocateResize(scope->entries, sizeof(Node *) * scope->entriesAllocated);
	}

	if (!ScopeCheckNotAlreadyUsed(tokenizer, node)) {
		return false;
	}

	scope->entries[scope->entryCount++] = node;

	if (ScopeIsVariableType(node)) {
		scope->variableEntryCount++;
	}

	// Set this here before type checking occurs, 
	// so that all the declarations in the scope already have their expression type set.
	node->expressionType = node->firstChild;

	return true;
}

void ASTFreeScopes(Node *node) {
	if (node && node->scope) {
		node->scope->entries = (Node **) AllocateResize(node->scope->entries, 0);

		Node *child = node->firstChild;

		while (child) {
			ASTFreeScopes(child);
			child = child->sibling;
		}
	}
}

bool ASTSetScopes(Tokenizer *tokenizer, ExecutionContext *context, Node *node, Scope *scope) {
	Node *child = node->firstChild;

	while (child) {
		child->parent = node;
		child = child->sibling;
	}

	if (node->type == T_ROOT || node->type == T_BLOCK || node->type == T_FUNCBODY) {
		scope = node->scope = (Scope *) AllocateFixed(sizeof(Scope));
		scope->isRoot = node->type == T_ROOT;
	} else {
		node->scope = scope;
	}

	if (node->type == T_FUNCBODY) {
		Node *argument = node->parent->firstChild->firstChild->firstChild;

		while (argument) {
			if (!ScopeAddEntry(tokenizer, scope, argument)) {
				return false;
			}
			
			argument = argument->sibling;
		}
	}

	if (node->type == T_VARIABLE) {
		Node *referenced = ScopeLookup(tokenizer, node, true);

		if (!referenced || referenced->parent->type == T_ROOT) {
			// (If the lookup fails, then it must be a forward declaration, which is only allowed at the root scope level.)
			node->referencesRootScope = true;
		}
	}

	{
		child = node->firstChild;

		while (child) {
			if (!ASTSetScopes(tokenizer, context, child, scope)) {
				return false;
			}

			child = child->sibling;
		}
	}

	if (node->type == T_DECLARE || node->type == T_FUNCTION || node->type == T_FUNCTYPE 
			|| node->type == T_STRUCT || node->type == T_IMPORT) {
		if (!ScopeAddEntry(tokenizer, scope, node)) {
			return false;
		}
	}

	if (node->type == T_IMPORT) {
		ImportData *alreadyImportedModule = importedModules;

		while (alreadyImportedModule) {
			if (alreadyImportedModule->pathBytes == node->firstChild->token.textBytes
					&& 0 == MemoryCompare(alreadyImportedModule->path, node->firstChild->token.text, alreadyImportedModule->pathBytes)) {
				break;
			}

			alreadyImportedModule = alreadyImportedModule->nextImport;
		}

		if (alreadyImportedModule) {
#if 0
			PrintError2(tokenizer, node, "The script at path '%.*s' has already been imported as a module.\n",
					node->firstChild->token.textBytes, node->firstChild->token.text);
			return false;
#else
			node->importData = alreadyImportedModule;
#endif
		} else {
			char *path = (char *) AllocateFixed(node->firstChild->token.textBytes + 1);
			MemoryCopy(path, node->firstChild->token.text, node->firstChild->token.textBytes);
			path[node->firstChild->token.textBytes] = 0;

			Tokenizer t = { 0 };
			void *fileData;

			if (node->firstChild->token.textBytes == 15 
					&& 0 == MemoryCompare(node->firstChild->token.text, "__base_module__", node->firstChild->token.textBytes)) {
				fileData = baseModuleSource;
				t.inputBytes = sizeof(baseModuleSource) - 1;
				t.isBaseModule = true;
			} else {
				fileData = FileLoad(path, &t.inputBytes);
			}

			if (!fileData) {
				PrintError2(tokenizer, node, "The script at path '%.*s' could not be loaded.\n",
						node->firstChild->token.textBytes, node->firstChild->token.text);
				return false;
			}

			node->importData = (ImportData *) AllocateFixed(sizeof(ImportData));
			node->importData->fileDataBytes = t.inputBytes;
			node->importData->fileData = fileData;
			node->importData->path = path;
			node->importData->pathBytes = node->firstChild->token.textBytes;
			node->importData->parentImport = tokenizer->module;

			ImportData *parentImport = tokenizer->module;

			while (parentImport) {
				if (parentImport->pathBytes == node->firstChild->token.textBytes
						&& 0 == MemoryCompare(parentImport->path, node->firstChild->token.text, parentImport->pathBytes)) {
					PrintError3("There is a cyclic import dependency.\n");
					ImportData *data = node->importData;

					while (data->parentImport) {
						PrintDebug("- '%s' is imported by '%s'\n", data->path, data->parentImport->path);
						data = data->parentImport;
					}

					return false;
				}

				parentImport = parentImport->parentImport;
			}

			t.module = node->importData;
			t.input = (const char *) fileData;
			t.line = 1;

			if (!ScriptLoad(t, context, node->importData, false)) {
				return false;
			}
		}

		if (node->token.type == T_INLINE) {
			uintptr_t j = 0;

			for (uintptr_t i = 0; i < node->importData->rootNode->scope->entryCount; i++) {
				if (node->importData->rootNode->scope->entries[i]->type == T_INLINE) {
					continue;
				}

				Node *inlineNode = (Node *) AllocateFixed(sizeof(Node));
				inlineNode->token = node->importData->rootNode->scope->entries[i]->token;
				inlineNode->type = T_INLINE;
				inlineNode->parent = node->parent;
				inlineNode->firstChild = node->importData->rootNode->scope->entries[i]->expressionType;
				inlineNode->importData = node->importData;

				if (ScopeIsVariableType(node->importData->rootNode->scope->entries[i])) {
					inlineNode->inlineImportVariableIndex = j++;
				} else {
					inlineNode->inlineImportVariableIndex = -1;
				}

				if (!ScopeAddEntry(tokenizer, scope, inlineNode)) {
					return false;
				}
			}
		}
	}

	return true;
}

// --------------------------------- Type checking.

bool ASTMatching(Node *left, Node *right) {
	if (!left && !right) {
		return true;
	} else if (!left || !right) {
		return false;
	} else if (left->type == T_NULL && (right->type == T_STRUCT || right->type == T_LIST)) {
		return true;
	} else if (right->type == T_NULL && (left->type == T_STRUCT || left->type == T_LIST)) {
		return true;
	} else if (left->type != right->type) {
		return false;
	} else if ((left->type == T_IDENTIFIER || left->type == T_STRUCT) 
			&& (left->token.textBytes != right->token.textBytes 
				|| MemoryCompare(left->token.text, right->token.text, right->token.textBytes))) {
		return false;
	} else {
		Node *childLeft = left->firstChild;
		Node *childRight = right->firstChild;

		while (true) {
			if (!childLeft && !childRight) {
				return true;
			} else if (!childLeft || !childRight) {
				return false;
			} else if (!ASTMatching(childLeft, childRight)) {
				return false;
			} else {
				childLeft = childLeft->sibling;
				childRight = childRight->sibling;
			}
		}
	}
}

bool ASTIsManagedType(Node *node) {
	return node->type == T_STR || node->type == T_LIST || node->type == T_STRUCT || node->type == T_FUNCPTR || node->type == T_FUNCPTR;
}

bool ASTLookupTypeIdentifiers(Tokenizer *tokenizer, Node *node) {
	Node *child = node->firstChild;

	while (child) {
		if (!ASTLookupTypeIdentifiers(tokenizer, child)) return false;
		child = child->sibling;
	}

	if (node->type == T_FUNCPTR) {
		Node *type = node->firstChild->sibling;

		if (type->type == T_IDENTIFIER) {
			Node *lookup = ScopeLookup(tokenizer, type, false);
			if (!lookup) return false;
			Node *copy = (Node *) AllocateFixed(sizeof(Node));
			*copy = *lookup;
			copy->sibling = NULL;
			node->firstChild->sibling = copy;
		}
	}

	if (node->type == T_DECLARE || node->type == T_ARGUMENT || node->type == T_NEW || node->type == T_LIST) {
		Node *type = node->firstChild;

		if (type->type == T_IDENTIFIER) {
			Node *lookup = ScopeLookup(tokenizer, type, false);

			if (!lookup) {
				return false;
			}

			if (!node->expressionType) {
				node->expressionType = node->firstChild;
			}

			Node *previousSibling = node->expressionType->sibling;

			if (!lookup) {
				return false;
			} else if (lookup->type == T_FUNCTYPE) {
				MemoryCopy(node->expressionType, lookup->firstChild, sizeof(Node));
			} else if (lookup->type == T_STRUCT) {
				MemoryCopy(node->expressionType, lookup, sizeof(Node));
			} else {
				PrintError2(tokenizer, node, "The identifier did not resolve to a type.\n");
				return false;
			}

			node->expressionType->sibling = previousSibling;
		}
	}

	return true;
}

bool ASTSetTypes(Tokenizer *tokenizer, Node *node) {
	Node *child = node->firstChild;

	while (child) {
		if (!ASTSetTypes(tokenizer, child)) return false;
		child->parent = node;
		child = child->sibling;
	}

	if (node->type == T_ROOT || node->type == T_BLOCK
			|| node->type == T_INT || node->type == T_FLOAT || node->type == T_STR || node->type == T_LIST
			|| node->type == T_BOOL || node->type == T_VOID || node->type == T_IDENTIFIER
			|| node->type == T_ARGUMENTS || node->type == T_ARGUMENT
			|| node->type == T_STRUCT || node->type == T_FUNCTYPE || node->type == T_IMPORT || node->type == T_IMPORT_PATH
			|| node->type == T_FUNCPTR || node->type == T_FUNCBODY || node->type == T_FUNCTION
			|| node->type == T_REPL_RESULT) {
	} else if (node->type == T_NUMERIC_LITERAL) {
		size_t dotCount = 0;

		for (uintptr_t i = 0; i < node->token.textBytes; i++) {
			if (node->token.text[i] == '.') {
				dotCount++;
			}
		}

		if (dotCount == 0) {
			node->expressionType = &globalExpressionTypeInt;
		} else if (dotCount == 1) {
			node->expressionType = &globalExpressionTypeFloat;
		} else {
			PrintError2(tokenizer, node, "Invalid number. There should either be one decimal place (for a float), or none (for an integer).\n");
			return false;
		}
	} else if (node->type == T_TRUE || node->type == T_FALSE) {
		node->expressionType = &globalExpressionTypeBool;
	} else if (node->type == T_NULL) {
		node->expressionType = node;
	} else if (node->type == T_STRING_LITERAL) {
		node->expressionType = &globalExpressionTypeStr;
	} else if (node->type == T_VARIABLE) {
		Node *lookup = ScopeLookup(tokenizer, node, false);
		if (!lookup) return false;
		node->expressionType = lookup->expressionType;
	} else if (node->type == T_STR_INTERPOLATE) {
		Node *left = node->firstChild;
		Node *expression = node->firstChild->sibling;
		Node *right = node->firstChild->sibling->sibling;
		Assert(left->type == T_STRING_LITERAL || left->type == T_STR_INTERPOLATE);
		Assert(right->type == T_STRING_LITERAL);
		Assert(left->expressionType->type == T_STR);
		Assert(right->expressionType->type == T_STR);
		node->expressionType = &globalExpressionTypeStr;

		// TODO Converting more types to strings.

		if (!ASTMatching(expression->expressionType, &globalExpressionTypeInt)
				&& !ASTMatching(expression->expressionType, &globalExpressionTypeIntList)
				&& !ASTMatching(expression->expressionType, &globalExpressionTypeStr)
				&& !ASTMatching(expression->expressionType, &globalExpressionTypeFloat)
				&& !ASTMatching(expression->expressionType, &globalExpressionTypeBool)) {
			PrintError2(tokenizer, expression, "The expression cannot be converted to a string.\n");
			return false;
		}
	} else if (node->type == T_ADD || node->type == T_MINUS || node->type == T_ASTERISK || node->type == T_SLASH
			|| node->type == T_GREATER_THAN || node->type == T_LESS_THAN || node->type == T_LT_OR_EQUAL || node->type == T_GT_OR_EQUAL
			|| node->type == T_DOUBLE_EQUALS || node->type == T_NOT_EQUALS || node->type == T_LOGICAL_AND || node->type == T_LOGICAL_OR) {
		if (!ASTMatching(node->firstChild->expressionType, node->firstChild->sibling->expressionType)) {
			PrintError2(tokenizer, node, "The expression on the left and right side of a binary operator must have the same type.\n");
			return false;
		}

		if (node->type == T_ADD) {
			if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeInt)
					&& !ASTMatching(node->firstChild->expressionType, &globalExpressionTypeFloat)
					&& !ASTMatching(node->firstChild->expressionType, &globalExpressionTypeStr)) {
				PrintError2(tokenizer, node, "The add operator expects integers, floats or strings.\n");
				return false;
			}
		} else if (node->type == T_LOGICAL_AND || node->type == T_LOGICAL_OR) {
			if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeBool)) {
				PrintError2(tokenizer, node, "This operator expects boolean expressions.\n");
				return false;
			}
		} else if (node->type == T_DOUBLE_EQUALS || node->type == T_NOT_EQUALS) {
			if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeInt)
					&& !ASTMatching(node->firstChild->expressionType, &globalExpressionTypeFloat)
					&& !ASTMatching(node->firstChild->expressionType, &globalExpressionTypeStr)
					&& !ASTMatching(node->firstChild->expressionType, &globalExpressionTypeBool)
					&& (!node->firstChild->expressionType || node->firstChild->expressionType->type != T_LIST)
					&& (!node->firstChild->expressionType || node->firstChild->expressionType->type != T_STRUCT)) {
				PrintError2(tokenizer, node, "These types cannot be compared.\n");
				return false;
			}
		} else {
			if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeInt)
					&& !ASTMatching(node->firstChild->expressionType, &globalExpressionTypeFloat)) {
				PrintError2(tokenizer, node, "This operator expects either integers or floats.\n");
				return false;
			}
		}

		if (node->type == T_GREATER_THAN || node->type == T_LESS_THAN || node->type == T_LT_OR_EQUAL || node->type == T_GT_OR_EQUAL
				|| node->type == T_DOUBLE_EQUALS || node->type == T_NOT_EQUALS) {
			node->expressionType = &globalExpressionTypeBool;
		} else {
			node->expressionType = node->firstChild->expressionType;
		}
	} else if (node->type == T_DECLARE) {
		if (node->firstChild->sibling && !ASTMatching(node->firstChild, node->firstChild->sibling->expressionType)) {
			ScriptPrintNode(node->firstChild, 0);
			ScriptPrintNode(node->firstChild->sibling->expressionType, 0);

			PrintError2(tokenizer, node, "The type of the variable being assigned does not match the expression.\n");
			return false;
		}
	} else if (node->type == T_EQUALS) {
		if (!ASTMatching(node->firstChild->expressionType, node->firstChild->sibling->expressionType)) {
			PrintError2(tokenizer, node, "The type of the variable being assigned does not match the expression.\n");
			return false;
		}
	} else if (node->type == T_CALL) {
		Node *functionPointer = node->firstChild;
		Node *expressionType = functionPointer->expressionType;

		if (!expressionType || expressionType->type != T_FUNCPTR) {
			PrintError2(tokenizer, functionPointer, "The expression being called is not a function.\n");
			return false;
		}

		node->expressionType = expressionType->firstChild->sibling;
		Node *match = expressionType->firstChild->firstChild;
		Node *argument = node->firstChild->sibling->firstChild;
		size_t index = 1;

		while (true) {
			if (!argument && !match) {
				break;
			} else if (!argument || !match) {
				PrintError2(tokenizer, node, "The function has a different number of arguments to this.\n");
				return false;
			} else {
				if (!ASTMatching(argument->expressionType, match->firstChild)) {
					PrintError2(tokenizer, node, "The types for argument %d do not match.\n", index);
					return false;
				}

				match = match->sibling;
				argument = argument->sibling;
				index++;
			}
		}
	} else if (node->type == T_ASSERT) {
		if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeBool)) {
			PrintError2(tokenizer, node, "The asserted expression must evaluate to a boolean.\n");
			return false;
		}
	} else if (node->type == T_RETURN) {
		Node *expressionType = node->firstChild ? node->firstChild->expressionType : &globalExpressionTypeVoid;

		Node *function = node->parent;

		while (function->type != T_FUNCTION) {
			function = function->parent;
		}

		Node *returnType = function->firstChild->firstChild->sibling;

		if (node->firstChild && ASTMatching(returnType, &globalExpressionTypeVoid)) {
			PrintError2(tokenizer, node, "The function does not return a value ('void'), but the return statement has a return value.\n");
			return false;
		}

		if (!ASTMatching(expressionType, returnType)) {
			PrintError2(tokenizer, node, "The type of the expression does not match the declared return type of the function.\n");
			return false;
		}
	} else if (node->type == T_IF || node->type == T_WHILE) {
		if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeBool)) {
			PrintError2(tokenizer, node, "The expression used for the condition must evaluate to a boolean.\n");
			return false;
		}
	} else if (node->type == T_FOR) {
		if (!ASTMatching(node->firstChild->sibling->expressionType, &globalExpressionTypeBool)) {
			PrintError2(tokenizer, node, "The expression used for the condition must evaluate to a boolean.\n");
			return false;
		}
	} else if (node->type == T_INDEX) {
		if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeStr)
				&& node->firstChild->expressionType->type != T_LIST) {
			PrintError2(tokenizer, node, "The expression being indexed must be a string or list.\n");
			return false;
		}

		if (!ASTMatching(node->firstChild->sibling->expressionType, &globalExpressionTypeInt)) {
			PrintError2(tokenizer, node, "The index must be a integer.\n");
			return false;
		}

		if (ASTMatching(node->firstChild->expressionType, &globalExpressionTypeStr)) {
			node->expressionType = &globalExpressionTypeStr;
		} else {
			node->expressionType = node->firstChild->expressionType->firstChild;
		}
	} else if (node->type == T_NEW) {
		if (node->firstChild->type != T_STRUCT && node->firstChild->type != T_LIST) {
			PrintError2(tokenizer, node, "This type is not a struct or list. 'new' is used to create new instances of structs or lists.\n");
			return false;
		}
	} else if (node->type == T_DOT) {
		bool isStruct = node->firstChild->expressionType->type == T_STRUCT;

		if (!isStruct && node->firstChild->expressionType->type != T_IMPORT_PATH) {
			PrintError2(tokenizer, node, "This expression is not a struct or an imported module. "
					"You cannot use the '.' operator on it.\n");
			return false;
		}

		if (isStruct) {
			Node *structure = node->firstChild->expressionType;
			Node *field = structure->firstChild;

			while (field) {
				if (field->token.textBytes == node->token.textBytes && 
						0 == MemoryCompare(field->token.text, node->token.text, node->token.textBytes)) {
					break;
				}

				field = field->sibling;
			}

			if (!field) {
				PrintError2(tokenizer, node, "The field '%.*s' is not in the struct '%.*s'.\n",
						node->token.textBytes, node->token.text, structure->token.textBytes, structure->token.text);
				return false;
			}

			node->expressionType = field->firstChild;
		} else {
			ImportData *importData = node->firstChild->expressionType->parent->importData;
			intptr_t index = ScopeLookupIndex(node, importData->rootNode->scope, true, true);

			if (index == -1) {
				PrintError2(tokenizer, node, "The variable or function '%.*s' is not in the imported module '%s'.\n",
						node->token.textBytes, node->token.text, importData->path);
				return false;
			}

			node->expressionType = importData->rootNode->scope->entries[index]->expressionType;
		}
	} else if (node->type == T_COLON) {
		Node *expressionType = node->firstChild->expressionType;

		bool isList = expressionType->type == T_LIST;
		bool isStr = expressionType->type == T_STR;
		bool isFuncPtr = expressionType->type == T_FUNCPTR;

		if (!isList && !isStr & !isFuncPtr) {
			PrintError2(tokenizer, node, "This type does not have any ':' operations.\n");
			return false;
		}

		Token token = node->token;
		Node *arguments[2] = { 0 };
		bool returnsItem = false, returnsInt = false, returnsBool = false, simple = true;
		uint8_t op;

		if (isList && KEYWORD("resize")) arguments[0] = &globalExpressionTypeInt, op = T_OP_RESIZE;
		else if (isList && KEYWORD("add")) arguments[0] = expressionType->firstChild, op = T_OP_ADD;
		else if (isList && KEYWORD("insert")) arguments[0] = expressionType->firstChild, arguments[1] = &globalExpressionTypeInt, op = T_OP_INSERT;
		else if (isList && KEYWORD("insert_many")) arguments[0] = &globalExpressionTypeInt, arguments[1] = &globalExpressionTypeInt, op = T_OP_INSERT_MANY;
		else if (isList && KEYWORD("delete")) arguments[0] = &globalExpressionTypeInt, op = T_OP_DELETE;
		else if (isList && KEYWORD("find_and_delete")) arguments[0] = expressionType->firstChild, op = T_OP_FIND_AND_DELETE, returnsBool = true;
		else if (isList && KEYWORD("delete_many")) arguments[0] = &globalExpressionTypeInt, arguments[1] = &globalExpressionTypeInt, op = T_OP_DELETE_MANY;
		else if (isList && KEYWORD("delete_last")) op = T_OP_DELETE_LAST;
		else if (isList && KEYWORD("delete_all")) op = T_OP_DELETE_ALL;
		else if (isList && KEYWORD("first")) returnsItem = true, op = T_OP_FIRST;
		else if (isList && KEYWORD("last")) returnsItem = true, op = T_OP_LAST;
		else if ((isList || isStr) && KEYWORD("len")) returnsInt = true, op = T_OP_LEN;

		else if (isFuncPtr && KEYWORD("async")) {
			if (expressionType->firstChild->firstChild) {
				PrintError2(tokenizer, node, "The function pointer should take no arguments. Use ':curry(...)' to set them before ':async()'.\n");
				return false;
			} else if (!ASTMatching(expressionType->firstChild->sibling, &globalExpressionTypeVoid)) {
				PrintError2(tokenizer, node, "The function pointer should not return anything. Use ':discard()' or ':assert()' before ':async()'.\n");
				return false;
			}

			// TODO Allow currying here, for convenience.
			op = T_OP_ASYNC;
			returnsInt = true;
		}

		else if (isFuncPtr && (KEYWORD("assert") || KEYWORD("discard"))) {
			if (KEYWORD("assert")) {
				op = T_OP_ASSERT;

				if (!ASTMatching(expressionType->firstChild->sibling, &globalExpressionTypeBool)) {
					PrintError2(tokenizer, node, "The return value of the function must be a bool to assert it.\n");
					return false;
				}
			} else {
				op = T_OP_DISCARD;

				if (ASTMatching(expressionType->firstChild->sibling, &globalExpressionTypeVoid)) {
					PrintError2(tokenizer, node, "The return value cannot be discarded from a function that already returns void.\n");
					return false;
				}
			}

			node->expressionType = (Node *) AllocateFixed(sizeof(Node));
			node->expressionType->type = T_FUNCPTR;
			node->expressionType->firstChild = (Node *) AllocateFixed(sizeof(Node));
			MemoryCopy(node->expressionType->firstChild, expressionType->firstChild, sizeof(Node));
			node->expressionType->firstChild->sibling = &globalExpressionTypeVoid;
			simple = false;
		}

		else if (isFuncPtr && KEYWORD("curry")) {
			if (!expressionType->firstChild->firstChild) {
				PrintError2(tokenizer, node, "The function pointer doesn't take any arguments.\n");
				return false;
			} else if (!ASTMatching(expressionType->firstChild->firstChild->firstChild, node->firstChild->sibling->firstChild->expressionType)) {
				PrintError2(tokenizer, node, "The curried argument does not match the type of the first argument.\n");
				return false;
			} else if (node->firstChild->sibling->firstChild->sibling) {
				// TODO Allow currying multiple arguments together.
				PrintError2(tokenizer, node, "You can only curry one argument at a time.\n");
				return false;
			}

			node->expressionType = (Node *) AllocateFixed(sizeof(Node));
			node->expressionType->type = T_FUNCPTR;
			node->expressionType->firstChild = (Node *) AllocateFixed(sizeof(Node));
			MemoryCopy(node->expressionType->firstChild, expressionType->firstChild, sizeof(Node));
			node->expressionType->firstChild->firstChild = node->expressionType->firstChild->firstChild->sibling;
			node->expressionType->firstChild->sibling = expressionType->firstChild->sibling;
			op = T_OP_CURRY;
			simple = false;
		}

		else {
			PrintError2(tokenizer, node, "This type does not have an operation called '%.*s'.\n", token.textBytes, token.text);
			return false;
		}

		if (op == T_OP_FIND_AND_DELETE && ASTMatching(arguments[0], &globalExpressionTypeFloat)) {
			PrintError2(tokenizer, node, "The find_and_delete operation cannot be used with floats.\n");
			return false;
		}

		if (simple) {
			Node *argument1 = node->firstChild->sibling->firstChild;
			Node *argument2 = argument1 ? argument1->sibling : NULL;
			Node *argument3 = argument2 ? argument2->sibling : NULL;

			if (argument3 || (argument2 && !arguments[1]) || (argument1 && !arguments[0])
					|| (!argument2 && arguments[1]) || (!argument1 && arguments[0])) {
				PrintError2(tokenizer, node, "Incorrect number of arguments for the operation '%.*s'.\n", token.textBytes, token.text);
				return false;
			}

			if (argument1 && !ASTMatching(argument1->expressionType, arguments[0])) {
				PrintError2(tokenizer, node, "Incorrect first argument type for the operation '%.*s'.\n", token.textBytes, token.text);
				return false;
			}

			if (argument2 && !ASTMatching(argument2->expressionType, arguments[1])) {
				PrintError2(tokenizer, node, "Incorrect second argument type for the operation '%.*s'.\n", token.textBytes, token.text);
				return false;
			}

			node->expressionType = returnsItem ? expressionType->firstChild 
				: returnsInt ? &globalExpressionTypeInt 
				: returnsBool ? &globalExpressionTypeBool : NULL;
		}

		node->operationType = op;
	} else if (node->type == T_LOGICAL_NOT) {
		if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeBool)) {
			PrintError2(tokenizer, node, "Expected a bool for the logical not '!' operator.\n");
			return false;
		}

		node->expressionType = &globalExpressionTypeBool;
	} else if (node->type == T_NEGATE) {
		if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeInt)
				&& !ASTMatching(node->firstChild->expressionType, &globalExpressionTypeFloat)) {
			PrintError2(tokenizer, node, "Expected a int or float for the unary negate '-' operator.\n");
			return false;
		}

		node->expressionType = node->firstChild->expressionType;
	} else if (node->type == T_LIST_LITERAL) {
		if (!node->firstChild) {
			// TODO Support empty list literals?
			PrintError2(tokenizer, node, "Empty list literals are not allowed. Instead, put 'new T[]' where 'T' is the item type.\n");
			return false;
		}

		Node *item = node->firstChild;
		node->expressionType = (Node *) AllocateFixed(sizeof(Node));
		node->expressionType->type = T_LIST;
		Node *copy = (Node *) AllocateFixed(sizeof(Node));
		*copy = *item->expressionType;
		copy->sibling = NULL;
		node->expressionType->firstChild = copy;

		while (item) {
			if (!ASTMatching(node->expressionType->firstChild, item->expressionType)) {
				PrintError2(tokenizer, item, "The type of this item is different to the ones before it in the list.\n");
				return false;
			}

			item = item->sibling;
		}
	} else if (node->type == T_AWAIT) {
		if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeIntList)) {
			PrintError2(tokenizer, node, "Expected a list of task IDs to wait on.\n");
			return false;
		}

		node->expressionType = &globalExpressionTypeInt;
	} else {
		PrintDebug("ASTSetTypes %d\n", node->type);
		Assert(false);
	}

	return true;
}

bool ASTCheckForReturnStatements(Tokenizer *tokenizer, Node *node) {
	if (node->type == T_ROOT) {
		Node *child = node->firstChild;

		while (child) {
			if (!ASTCheckForReturnStatements(tokenizer, child)) return false;
			child->parent = node;
			child = child->sibling;
		}
	} else if (node->type == T_FUNCTION) {
		if (node->firstChild->sibling && node->firstChild->firstChild->sibling
				&& node->firstChild->firstChild->sibling->type != T_VOID) {
			Assert(node->firstChild->sibling->type == T_FUNCBODY);
			return ASTCheckForReturnStatements(tokenizer, node->firstChild->sibling);
		}
	} else if (node->type == T_BLOCK || node->type == T_FUNCBODY) {
		Node *lastStatement = node->firstChild;

		while (lastStatement && lastStatement->sibling) {
			lastStatement = lastStatement->sibling;
		}

		if (lastStatement && lastStatement->type == T_RETURN) {
			return true;
		} else if (lastStatement && (lastStatement->type == T_IF || lastStatement->type == T_BLOCK)) {
			return ASTCheckForReturnStatements(tokenizer, lastStatement);
		} else {
			PrintError2(tokenizer, node, "This block needs to end with a return statement.\n");
			return false;
		}
	} else if (node->type == T_IF) {
		if (!node->firstChild->sibling->sibling) {
			PrintError2(tokenizer, node, "This function returns a value, so this if statement needs an else block which ends with a return statement.\n");
			return false;
		}

		return ASTCheckForReturnStatements(tokenizer, node->firstChild->sibling)
			&& ASTCheckForReturnStatements(tokenizer, node->firstChild->sibling->sibling);
	}

	return true;
}

// --------------------------------- Code generation.

void FunctionBuilderAppend(FunctionBuilder *builder, const void *buffer, size_t bytes) {
	if (builder->dataBytes + bytes > builder->dataAllocated) {
		builder->dataAllocated = 2 * builder->dataAllocated + bytes;
		builder->data = (uint8_t *) AllocateResize(builder->data, builder->dataAllocated);
	}

	for (uintptr_t i = 0; i < bytes; i++) {
		builder->data[builder->dataBytes + i] = ((const uint8_t *) buffer)[i];
	}

	builder->dataBytes += bytes;
}

void FunctionBuilderAddLineNumber(FunctionBuilder *builder, Node *node) {
	if (builder->lineNumberCount == builder->lineNumbersAllocated) {
		builder->lineNumbersAllocated = 2 * builder->lineNumbersAllocated + 4;
		builder->lineNumbers = (LineNumber *) AllocateResize(builder->lineNumbers, builder->lineNumbersAllocated * sizeof(LineNumber));
	}

	Node *ancestor = node;

	while (ancestor) {
		if (ancestor->type == T_FUNCTION) {
			builder->lineNumbers[builder->lineNumberCount].function = &ancestor->token;
		}

		ancestor = ancestor->parent;
	}

	builder->lineNumbers[builder->lineNumberCount].importData = builder->importData;
	builder->lineNumbers[builder->lineNumberCount].instructionPointer = builder->dataBytes;
	builder->lineNumbers[builder->lineNumberCount].lineNumber = node->token.line;
	builder->lineNumberCount++;
}

bool FunctionBuilderVariable(Tokenizer *tokenizer, FunctionBuilder *builder, Node *node, bool forAssignment) {
	Node *ancestor = node;
	Scope *scope = NULL;
	int32_t index = -1;
	Scope *rootScope = NULL;
	uintptr_t globalVariableOffset = builder->globalVariableOffset;
	bool inlineImport = false;

	while (ancestor) {
		if (ancestor->scope != scope) {
			scope = ancestor->scope;

			if (scope->isRoot) {
				rootScope = scope;
			}

			if (index != -1) {
				index += scope->variableEntryCount;
			}

			uintptr_t j = 0;

			for (uintptr_t i = 0; i < scope->entryCount; i++) {
				if (scope->entries[i]->token.textBytes == node->token.textBytes
						&& 0 == MemoryCompare(scope->entries[i]->token.text, node->token.text, node->token.textBytes)
						&& index == -1) {
					index = j;
					builder->isPersistentVariable = scope->entries[i]->isPersistentVariable;

					if (scope->entries[i]->type == T_INLINE) {
						index = scope->entries[i]->inlineImportVariableIndex;
						Assert(index != -1);
						globalVariableOffset = scope->entries[i]->importData->globalVariableOffset;
						inlineImport = true;
					}

					if (scope->entries[i]->type != T_DECLARE && forAssignment) {
						PrintError2(tokenizer, node, "A value cannot be assigned to this. "
								"Try putting a variable name here.\n");
						return false;
					}
				}

				if (ScopeIsVariableType(scope->entries[i])) {
					j++;
				}
			}
		}

		ancestor = ancestor->parent;
	}

	if (index >= (int32_t) rootScope->variableEntryCount && !inlineImport) {
		index = rootScope->variableEntryCount - index - 1;
	} else {
		index += globalVariableOffset;
	}

	FunctionBuilderAddLineNumber(builder, node);

	if (forAssignment) {
		builder->scopeIndex = index;
		builder->isDotAssignment = false;
		builder->isListAssignment = false;
	} else {
		FunctionBuilderAppend(builder, &node->type, sizeof(node->type));
		FunctionBuilderAppend(builder, &index, sizeof(index));
	}

	return true;
}

bool FunctionBuilderRecurse(Tokenizer *tokenizer, Node *node, FunctionBuilder *builder, bool forAssignment) {
	if (forAssignment) {
		if (node->type == T_VARIABLE || node->type == T_DOT || node->type == T_INDEX) {
			// Supported.
		} else {
			PrintError2(tokenizer, node, "A value cannot be assigned to this expression. Try putting a variable name here.\n");
			return false;
		}
	}

	if (node->type == T_FUNCBODY || node->type == T_BLOCK) {
		if (node->scope->variableEntryCount > 10000) {
			PrintError2(tokenizer, node, "There are too many variables in this scope (the maximum is 10000).\n");
			return false;
		}

		uint16_t entryCount = node->scope->variableEntryCount;
		FunctionBuilderAddLineNumber(builder, node);
		FunctionBuilderAppend(builder, &node->type, sizeof(node->type));
		FunctionBuilderAppend(builder, &entryCount, sizeof(entryCount));

		for (uintptr_t i = 0; i < node->scope->entryCount; i++) {
			Node *entry = node->scope->entries[i];

			if (ScopeIsVariableType(entry)) {
				bool isManaged = ASTIsManagedType(entry->expressionType);
				FunctionBuilderAppend(builder, &isManaged, sizeof(isManaged));
			}
		}
	} else if (node->type == T_EQUALS || node->type == T_DECLARE) {
		if (node->firstChild->sibling) {
			if (!FunctionBuilderRecurse(tokenizer, node->firstChild->sibling, builder, false)) return false;
			builder->isPersistentVariable = false;

			if (node->type == T_DECLARE) {
				if (!FunctionBuilderVariable(tokenizer, builder, node, true)) return false;
			}

			else if (!FunctionBuilderRecurse(tokenizer, node->firstChild, builder, true)) return false;
			FunctionBuilderAddLineNumber(builder, node);
			uint8_t b = builder->isListAssignment ? T_EQUALS_LIST : builder->isDotAssignment ? T_EQUALS_DOT : T_EQUALS;
			FunctionBuilderAppend(builder, &b, sizeof(b));
			if (!builder->isListAssignment) FunctionBuilderAppend(builder, &builder->scopeIndex, sizeof(builder->scopeIndex));
			b = T_PERSIST;
			if (builder->isPersistentVariable) FunctionBuilderAppend(builder, &b, sizeof(b));
		}

		return true;
	} else if (node->type == T_CALL) {
		Node *argument = node->firstChild->sibling->firstChild;
		Node *arguments[FUNCTION_MAX_ARGUMENTS];
		size_t argumentCount = 0;

		while (argument) {
			arguments[argumentCount++] = argument;
			argument = argument->sibling;
		}

		for (uintptr_t i = 0; i < argumentCount; i++) {
			if (!FunctionBuilderRecurse(tokenizer, arguments[argumentCount - i - 1], builder, false)) {
				return false;
			}
		}

		if (!FunctionBuilderRecurse(tokenizer, node->firstChild, builder, false)) return false;
		FunctionBuilderAddLineNumber(builder, node);
		FunctionBuilderAppend(builder, &node->type, sizeof(node->type));
		return true;
	} else if (node->type == T_WHILE) {
		int32_t start = builder->dataBytes;
		if (!FunctionBuilderRecurse(tokenizer, node->firstChild, builder, false)) return false;
		FunctionBuilderAddLineNumber(builder, node);
		uint8_t b = T_IF;
		FunctionBuilderAppend(builder, &b, sizeof(b));
		uintptr_t writeOffset = builder->dataBytes;
		uint32_t zero = 0;
		FunctionBuilderAppend(builder, &zero, sizeof(zero));
		if (!FunctionBuilderRecurse(tokenizer, node->firstChild->sibling, builder, false)) return false;
		b = T_BRANCH;
		FunctionBuilderAppend(builder, &b, sizeof(b));
		int32_t delta = start - builder->dataBytes;
		FunctionBuilderAppend(builder, &delta, sizeof(delta));
		delta = builder->dataBytes - writeOffset;
		MemoryCopy(builder->data + writeOffset, &delta, sizeof(delta));
		return true;
	} else if (node->type == T_FOR) {
		Node *declare = node->firstChild;
		Node *condition = node->firstChild->sibling;
		Node *increment = node->firstChild->sibling->sibling;
		Node *body = node->firstChild->sibling->sibling->sibling;

		if (declare->type != T_DECLARE && declare->type != T_EQUALS) {
			PrintError2(tokenizer, node, "The first section of a for statement must be a variable declaration or an assignment.\n");
			return false;
		}

		if (!FunctionBuilderRecurse(tokenizer, declare, builder, false)) return false;
		int32_t start = builder->dataBytes;
		if (!FunctionBuilderRecurse(tokenizer, condition, builder, false)) return false;
		FunctionBuilderAddLineNumber(builder, node);
		uint8_t b = T_IF;
		FunctionBuilderAppend(builder, &b, sizeof(b));
		uintptr_t writeOffset = builder->dataBytes;
		uint32_t zero = 0;
		FunctionBuilderAppend(builder, &zero, sizeof(zero));
		if (!FunctionBuilderRecurse(tokenizer, body, builder, false)) return false;
		if (!FunctionBuilderRecurse(tokenizer, increment, builder, false)) return false;

		if (increment->expressionType && increment->expressionType->type != T_VOID) {
			if (increment->type == T_CALL) {
				uint8_t b = T_POP;
				FunctionBuilderAppend(builder, &b, sizeof(b));
			} else {
				PrintError2(tokenizer, increment, "The result of the expression is unused.\n");
				return false;
			}
		}

		b = T_BRANCH;
		FunctionBuilderAppend(builder, &b, sizeof(b));
		int32_t delta = start - builder->dataBytes;
		FunctionBuilderAppend(builder, &delta, sizeof(delta));
		delta = builder->dataBytes - writeOffset;
		MemoryCopy(builder->data + writeOffset, &delta, sizeof(delta));
		return true;
	} else if (node->type == T_IF) {
		if (!FunctionBuilderRecurse(tokenizer, node->firstChild, builder, false)) return false;
		FunctionBuilderAddLineNumber(builder, node);
		FunctionBuilderAppend(builder, &node->type, sizeof(node->type));
		uintptr_t writeOffset = builder->dataBytes, writeOffsetElse = 0;
		uint32_t zero = 0;
		FunctionBuilderAppend(builder, &zero, sizeof(zero));
		if (!FunctionBuilderRecurse(tokenizer, node->firstChild->sibling, builder, false)) return false;

		if (node->firstChild->sibling->sibling) {
			uint8_t b = T_BRANCH;
			FunctionBuilderAppend(builder, &b, sizeof(b));
			writeOffsetElse = builder->dataBytes;
			FunctionBuilderAppend(builder, &zero, sizeof(zero));
		}

		int32_t delta = builder->dataBytes - writeOffset;
		MemoryCopy(builder->data + writeOffset, &delta, sizeof(delta));

		if (node->firstChild->sibling->sibling) {
			if (!FunctionBuilderRecurse(tokenizer, node->firstChild->sibling->sibling, builder, false)) return false;
			delta = builder->dataBytes - writeOffsetElse;
			MemoryCopy(builder->data + writeOffsetElse, &delta, sizeof(delta));
		}

		return true;
	} else if (node->type == T_LOGICAL_OR || node->type == T_LOGICAL_AND) {
		if (!FunctionBuilderRecurse(tokenizer, node->firstChild, builder, false)) return false;
		FunctionBuilderAppend(builder, &node->type, sizeof(node->type));
		uintptr_t writeOffset = builder->dataBytes;
		uint32_t zero = 0;
		FunctionBuilderAppend(builder, &zero, sizeof(zero));
		if (!FunctionBuilderRecurse(tokenizer, node->firstChild->sibling, builder, false)) return false;
		int32_t delta = builder->dataBytes - writeOffset;
		MemoryCopy(builder->data + writeOffset, &delta, sizeof(delta));
		return true;
	} else if (node->type == T_NEW) {
		FunctionBuilderAddLineNumber(builder, node);
		FunctionBuilderAppend(builder, &node->type, sizeof(node->type));
		int16_t fieldCount = 0;

		if (node->firstChild->type == T_LIST) {
			fieldCount = ASTIsManagedType(node->firstChild->firstChild) ? -2 : -1;
		} else {
			Node *child = node->firstChild->firstChild;

			while (child) { 
				if (fieldCount == 1000) {
					PrintError2(tokenizer, child, "The struct exceeds the maximum number of fields (1000).\n");
					return false;
				}

				fieldCount++; 
				child = child->sibling; 
			}
		}

		FunctionBuilderAppend(builder, &fieldCount, sizeof(fieldCount));
		return true;
	} else if (node->type == T_COLON) {
		FunctionBuilderRecurse(tokenizer, node->firstChild, builder, false);

		Node *argument = node->firstChild->sibling->firstChild;

		while (argument) {
			FunctionBuilderRecurse(tokenizer, argument, builder, false);
			argument = argument->sibling;
		}

		FunctionBuilderAddLineNumber(builder, node);
		FunctionBuilderAppend(builder, &node->operationType, sizeof(node->operationType));
		return true;
	} else if (node->type == T_LIST_LITERAL) {
		// Step 1: Create the list.
		uint8_t b = T_NEW;
		int16_t isManaged = ASTIsManagedType(node->expressionType->firstChild) ? -2 : -1;
		FunctionBuilderAddLineNumber(builder, node);
		FunctionBuilderAppend(builder, &b, sizeof(b));
		FunctionBuilderAppend(builder, &isManaged, sizeof(isManaged));

		// Step 2: Resize the list.
		uint32_t size = 0;
		Node *item = node->firstChild;
		while (item) { size++; item = item->sibling; }
		b = T_DUP;
		FunctionBuilderAppend(builder, &b, sizeof(b));
		b = T_NUMERIC_LITERAL;
		FunctionBuilderAppend(builder, &b, sizeof(b));
		Value v;
		v.i = size;
		FunctionBuilderAppend(builder, &v, sizeof(v));
		b = T_OP_RESIZE;
		FunctionBuilderAppend(builder, &b, sizeof(b));

		// Step 3: Populate the list.
		item = node->firstChild;
		uint32_t i = 0;

		while (item) {
			b = T_DUP;
			FunctionBuilderAppend(builder, &b, sizeof(b));
			FunctionBuilderRecurse(tokenizer, item, builder, false);
			b = T_SWAP;
			FunctionBuilderAppend(builder, &b, sizeof(b));
			b = T_NUMERIC_LITERAL;
			FunctionBuilderAppend(builder, &b, sizeof(b));
			Value v;
			v.i = i++;
			FunctionBuilderAppend(builder, &v, sizeof(v));
			b = T_EQUALS_LIST;
			FunctionBuilderAppend(builder, &b, sizeof(b));
			item = item->sibling;
		}

		return true;
	}

	Node *child = node->firstChild;

	while (child) {
		if (!FunctionBuilderRecurse(tokenizer, child, builder, false)) {
			return false;
		}

		if (node->type == T_BLOCK && child->expressionType && child->expressionType->type != T_VOID) {
			if (child->type == T_CALL || child->type == T_AWAIT 
					|| (child->type == T_COLON && child->operationType == T_OP_FIND_AND_DELETE)) {
				uint8_t b = T_POP;
				FunctionBuilderAppend(builder, &b, sizeof(b));
			} else if (child->type == T_DECLARE || child->type == T_EQUALS) {
			} else {
				PrintError2(tokenizer, child, "The result of the expression is unused.\n");
				return false;
			}
		}

		child = child->sibling;
	}

	if (node->type == T_FUNCBODY || node->type == T_BLOCK) {
		uint8_t b = T_EXIT_SCOPE;
		uint16_t entryCount = node->scope->variableEntryCount;
		FunctionBuilderAddLineNumber(builder, node);
		FunctionBuilderAppend(builder, &b, sizeof(b));
		FunctionBuilderAppend(builder, &entryCount, sizeof(entryCount));
		b = T_END_FUNCTION;
		if (node->type == T_FUNCBODY) FunctionBuilderAppend(builder, &b, sizeof(b));
	} else if (node->type == T_RETURN) {
		uint8_t b = T_END_FUNCTION;
		FunctionBuilderAddLineNumber(builder, node);
		FunctionBuilderAppend(builder, &b, sizeof(b));
	} else if (node->type == T_ASSERT || node->type == T_NULL || node->type == T_LOGICAL_NOT || node->type == T_AWAIT) {
		FunctionBuilderAddLineNumber(builder, node);
		FunctionBuilderAppend(builder, &node->type, sizeof(node->type));
	} else if (node->type == T_ADD || node->type == T_MINUS || node->type == T_ASTERISK || node->type == T_SLASH || node->type == T_NEGATE) {
		uint8_t b = node->expressionType->type == T_FLOAT ? node->type - T_ADD + T_FLOAT_ADD 
			: node->expressionType->type == T_STR ? T_CONCAT : node->type;
		FunctionBuilderAddLineNumber(builder, node);
		FunctionBuilderAppend(builder, &b, sizeof(b));
	} else if (node->type == T_STR_INTERPOLATE) {
		Node *type = node->firstChild->sibling->expressionType;
		uint8_t b = type->type == T_STR ? T_INTERPOLATE_STR
			: type->type == T_FLOAT ? T_INTERPOLATE_FLOAT
			: type->type == T_INT ? T_INTERPOLATE_INT
			: (type->type == T_LIST && type->firstChild->type == T_INT) ? T_INTERPOLATE_ILIST
			: type->type == T_BOOL ? T_INTERPOLATE_BOOL : T_ERROR;
		Assert(b != T_ERROR);
		FunctionBuilderAddLineNumber(builder, node);
		FunctionBuilderAppend(builder, &b, sizeof(b));
	} else if (node->type == T_LESS_THAN || node->type == T_GREATER_THAN || node->type == T_LT_OR_EQUAL || node->type == T_GT_OR_EQUAL
			|| node->type == T_DOUBLE_EQUALS || node->type == T_NOT_EQUALS) {
		uint8_t b = node->firstChild->expressionType->type == T_STR ? node->type - T_DOUBLE_EQUALS + T_STR_DOUBLE_EQUALS 
			: node->firstChild->expressionType->type == T_FLOAT ? node->type - T_GREATER_THAN + T_FLOAT_GREATER_THAN : node->type;
		FunctionBuilderAddLineNumber(builder, node);
		FunctionBuilderAppend(builder, &b, sizeof(b));
	} else if (node->type == T_VARIABLE) {
		if (!FunctionBuilderVariable(tokenizer, builder, node, forAssignment)) {
			return false;
		}
	} else if (node->type == T_INDEX) {
		if (forAssignment) {
			if (node->firstChild->expressionType->type == T_STR) {
				PrintError2(tokenizer, node->firstChild, "Strings cannot be modified.\n");
				return false;
			} else {
				builder->isListAssignment = true;
				builder->isDotAssignment = false;
			}
		} else {
			uint8_t b = node->firstChild->expressionType->type == T_STR ? T_INDEX : T_INDEX_LIST;
			FunctionBuilderAddLineNumber(builder, node);
			FunctionBuilderAppend(builder, &b, sizeof(b));
		}
	} else if (node->type == T_DOT) {
		bool isStruct = node->firstChild->expressionType->type == T_STRUCT;

		if (isStruct) {
			Node *field = node->firstChild->expressionType->firstChild;
			int16_t fieldIndex = 0;

			while (field) {
				if (field->token.textBytes == node->token.textBytes && 
						0 == MemoryCompare(field->token.text, node->token.text, node->token.textBytes)) {
					break;
				}

				field = field->sibling;
				fieldIndex++;
			}

			if (ASTIsManagedType(field->firstChild)) {
				fieldIndex = -1 - fieldIndex;
			}

			if (forAssignment) {
				builder->scopeIndex = fieldIndex;
				builder->isDotAssignment = true;
				builder->isListAssignment = false;
			} else {
				FunctionBuilderAddLineNumber(builder, node);
				FunctionBuilderAppend(builder, &node->type, sizeof(node->type));
				FunctionBuilderAppend(builder, &fieldIndex, sizeof(fieldIndex));
			}
		} else {
			if (forAssignment) {
				PrintError2(tokenizer, node, "You cannot directly modify a variable from an imported module.\n");
				return false;
			} else {
				Node *importStatement = node->firstChild->expressionType->parent;
				Assert(importStatement->type == T_IMPORT);
				uint32_t index = ScopeLookupIndex(node, importStatement->importData->rootNode->scope, false, false);
				index += importStatement->importData->globalVariableOffset;
				FunctionBuilderAddLineNumber(builder, node);
				uint8_t b = T_POP;
				FunctionBuilderAppend(builder, &b, sizeof(b));
				b = T_VARIABLE;
				FunctionBuilderAppend(builder, &b, sizeof(b));
				FunctionBuilderAppend(builder, &index, sizeof(index));
			}
		}
	} else if (node->type == T_NUMERIC_LITERAL) {
		FunctionBuilderAddLineNumber(builder, node);
		FunctionBuilderAppend(builder, &node->type, sizeof(node->type));
		Value v;

		// TODO Overflow checking.

		if (node->expressionType == &globalExpressionTypeInt) {
			v.i = 0;

			for (uintptr_t i = 0; i < node->token.textBytes; i++) {
				v.i *= 10;
				v.i += node->token.text[i] - '0';
			}
		} else if (node->expressionType == &globalExpressionTypeFloat) {
			bool dot = false;
			v.f = 0;
			double m = 0.1;

			for (uintptr_t i = 0; i < node->token.textBytes; i++) {
				if (node->token.text[i] == '.') {
					dot = true;
				} else if (dot) {
					v.f += (node->token.text[i] - '0') * m;
					m /= 10;
				} else {
					v.f *= 10;
					v.f += node->token.text[i] - '0';
				}
			}
		}

		FunctionBuilderAppend(builder, &v, sizeof(v));
	} else if (node->type == T_STRING_LITERAL) {
		FunctionBuilderAddLineNumber(builder, node);
		FunctionBuilderAppend(builder, &node->type, sizeof(node->type));
		uint32_t textBytes = node->token.textBytes;
		FunctionBuilderAppend(builder, &textBytes, sizeof(textBytes));
		FunctionBuilderAppend(builder, node->token.text, textBytes);
	} else if (node->type == T_TRUE || node->type == T_FALSE) {
		FunctionBuilderAddLineNumber(builder, node);
		uint8_t b = T_NUMERIC_LITERAL;
		FunctionBuilderAppend(builder, &b, sizeof(b));
		Value v;
		v.i = node->type == T_TRUE ? 1 : 0;
		FunctionBuilderAppend(builder, &v, sizeof(v));
	} else if (node->type == T_REPL_RESULT) {
		if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeVoid)) {
			FunctionBuilderAppend(builder, &node->type, sizeof(node->type));
		}

		if (builder->replResultType) {
			PrintError2(tokenizer, node, "Multiple REPL results are not allowed.\n");
			return false;
		}

		builder->replResultType = node->firstChild->expressionType;
	} else {
		PrintDebug("FunctionBuilderRecurse %d\n", node->type);
		Assert(false);
	}

	return true;
}

bool ASTGenerate(Tokenizer *tokenizer, Node *root, ExecutionContext *context) {
	Node *child = root->firstChild;

	context->functionData->globalVariableOffset = context->globalVariableCount;
	context->globalVariableCount += root->scope->variableEntryCount;
	context->globalVariables = (Value *) AllocateResize(context->globalVariables, sizeof(Value) * context->globalVariableCount);
	context->globalVariableIsManaged = (bool *) AllocateResize(context->globalVariableIsManaged, sizeof(Value) * context->globalVariableCount);

	for (uintptr_t i = 0; i < root->scope->variableEntryCount; i++) {
		context->globalVariables[context->functionData->globalVariableOffset + i].i = 0;
		context->globalVariableIsManaged[context->functionData->globalVariableOffset + i] = false;
	}

	while (child) {
		if (child->type == T_FUNCTION) {
			uintptr_t variableIndex = context->functionData->globalVariableOffset + ScopeLookupIndex(child, root->scope, false, false);
			uintptr_t heapIndex = HeapAllocate(context);
			context->globalVariableIsManaged[variableIndex] = true;
			context->heap[heapIndex].type = T_FUNCPTR;
			context->heap[heapIndex].lambdaID = context->functionData->dataBytes;
			context->globalVariables[variableIndex].i = heapIndex;

			if (child->isExternalCall) {
				uint8_t b = T_EXTCALL;

				uint16_t index = 0xFFFF;
				
				for (uintptr_t i = 0; i < sizeof(externalFunctions) / sizeof(externalFunctions[0]); i++) {
					bool match = true;

					for (uintptr_t j = 0; j <= child->token.textBytes; j++) {
						if (externalFunctions[i].cName[j] != (j == child->token.textBytes ? 0 : child->token.text[j])) {
							match = false;
							break;
						}
					}

					if (match) {
						index = i;
						break;
					}
				}

				if (index == 0xFFFF) {
					PrintError2(tokenizer, child, "No such external function '%.*s'.\n", child->token.textBytes, child->token.text);
					return false;
				}

				FunctionBuilderAppend(context->functionData, &b, sizeof(b));
				FunctionBuilderAppend(context->functionData, &index, sizeof(index));
			} else {
				if (!FunctionBuilderRecurse(tokenizer, child->firstChild->sibling, context->functionData, false)) return false;
			}
		} else if (child->type == T_DECLARE) {
			if (child->isPersistentVariable && context->mainModule != tokenizer->module) {
				PrintError2(tokenizer, child, "Persistent variables are not allowed in imported modules.\n");
				return false;
			}

			context->globalVariables[context->functionData->globalVariableOffset + ScopeLookupIndex(child, root->scope, false, false)].i = 0;
			context->globalVariableIsManaged[context->functionData->globalVariableOffset + ScopeLookupIndex(child, root->scope, false, false)] = ASTIsManagedType(child->expressionType);
		}

		child = child->sibling;
	}

	return true;
}

// --------------------------------- Main script execution.

void HeapGarbageCollectMark(ExecutionContext *context, uintptr_t index) {
	start:;
	Assert(index < context->heapEntriesAllocated);
	if (context->heap[index].gcMark) return;
	context->heap[index].gcMark = true;

	if (context->heap[index].type == T_EOF || context->heap[index].type == T_STR || context->heap[index].type == T_FUNCPTR) {
		// Nothing else to mark.
	} else if (context->heap[index].type == T_STRUCT) {
		for (uintptr_t i = 0; i < context->heap[index].fieldCount; i++) {
			if (((uint8_t *) context->heap[index].fields)[-1 - i]) {
				HeapGarbageCollectMark(context, context->heap[index].fields[i].i);
			}
		}
	} else if (context->heap[index].type == T_LIST) {
		if (context->heap[index].internalValuesAreManaged) {
			for (uintptr_t i = 0; i < context->heap[index].length; i++) {
				HeapGarbageCollectMark(context, context->heap[index].list[i].i);
			}
		}
	} else if (context->heap[index].type == T_CONCAT) {
		uintptr_t index1 = context->heap[index].concat1;
		uintptr_t index2 = context->heap[index].concat2;

		if (context->heap[index1].type == T_CONCAT) {
			HeapGarbageCollectMark(context, index2);
			index = index1;
		} else {
			HeapGarbageCollectMark(context, index1);
			index = index2;
		}

		goto start;
	} else if (context->heap[index].type == T_OP_DISCARD || context->heap[index].type == T_OP_ASSERT) {
		HeapGarbageCollectMark(context, context->heap[index].lambdaID);
	} else if (context->heap[index].type == T_OP_CURRY) {
		HeapGarbageCollectMark(context, context->heap[index].lambdaID);

		if (context->heap[index].internalValuesAreManaged) {
			HeapGarbageCollectMark(context, context->heap[index].curryValue.i);
		}
	} else {
		Assert(false);
	}
}

void HeapFreeEntry(ExecutionContext *context, uintptr_t i) {
	if (context->heap[i].type == T_STR) {
		AllocateResize(context->heap[i].text, 0);
	} else if (context->heap[i].type == T_STRUCT) {
		AllocateResize((uint8_t *) context->heap[i].fields - context->heap[i].fieldCount, 0);
	} else if (context->heap[i].type == T_LIST) {
		AllocateResize(context->heap[i].list, 0);
	} else if (context->heap[i].type == T_OP_DISCARD || context->heap[i].type == T_OP_ASSERT 
			|| context->heap[i].type == T_FUNCPTR || context->heap[i].type == T_OP_CURRY
			|| context->heap[i].type == T_CONCAT) {
	} else {
		Assert(false);
	}

	context->heap[i].type = T_ERROR;
}

uintptr_t HeapAllocate(ExecutionContext *context) {
	if (!context->heapFirstUnusedEntry) {
		// All heapEntriesAllocated entries are in use.

		for (uintptr_t i = 0; i < context->heapEntriesAllocated; i++) {
			context->heap[i].gcMark = false;
		}

		for (uintptr_t i = 0; i < context->globalVariableCount; i++) {
			if (context->globalVariableIsManaged[i]) {
				HeapGarbageCollectMark(context, context->globalVariables[i].i);
			}
		}

		CoroutineState *c = context->allCoroutines;

		while (c) {
			for (uintptr_t i = 0; i < c->localVariableCount; i++) {
				if (c->localVariableIsManaged[i]) {
					HeapGarbageCollectMark(context, c->localVariables[i].i);
				}
			}

			for (uintptr_t i = 0; i < c->stackPointer; i++) {
				if (c->stackIsManaged[i]) {
					HeapGarbageCollectMark(context, c->stack[i].i);
				}
			}

			c = c->nextCoroutine;
		}
		
		uintptr_t *link = &context->heapFirstUnusedEntry;
		uintptr_t reclaimed = 0;

		for (uintptr_t i = 1; i < context->heapEntriesAllocated; i++) {
			if (!context->heap[i].gcMark) {
				HeapFreeEntry(context, i);
				*link = i;
				link = &context->heap[i].nextUnusedEntry;
				reclaimed++;
			}
		}

		if (reclaimed <= context->heapEntriesAllocated / 5) {
			// PrintDebug("\033[0;32mFreed only %d/%d entries. Doubling heap size...\033[0m\n", reclaimed, context->heapEntriesAllocated);

			intptr_t linkIndex = link == &context->heapFirstUnusedEntry ? -1 
				: ((intptr_t) link - (intptr_t) context->heap) / (intptr_t) sizeof(HeapEntry);
			uintptr_t oldSize = context->heapEntriesAllocated;
			context->heapEntriesAllocated *= 2;
			context->heap = (HeapEntry *) AllocateResize(context->heap, context->heapEntriesAllocated * sizeof(HeapEntry));
			link = linkIndex == -1 ? &context->heapFirstUnusedEntry : &context->heap[linkIndex].nextUnusedEntry;

			for (uintptr_t i = oldSize; i < context->heapEntriesAllocated; i++) {
				context->heap[i].type = T_ERROR;
				*link = i;
				link = &context->heap[i].nextUnusedEntry;
			}
		} else {
			// PrintDebug("\033[0;32mFreed %d/%d entries.\033[0m\n", reclaimed, context->heapEntriesAllocated);
		}

		*link = 0;
	}

	uintptr_t index = context->heapFirstUnusedEntry;
	Assert(index);
	context->heapFirstUnusedEntry = context->heap[index].nextUnusedEntry;
	return index;
}

void ScriptPrintNode(Node *node, int indent) {
	for (int i = 0; i < indent; i++) {
		PrintDebug("\t");
	}

	PrintDebug("%d l%d '%.*s'\n", node->type, node->token.line, node->token.textBytes, node->token.text);

	Node *child = node->firstChild;

	while (child) {
		ScriptPrintNode(child, indent + 1);
		child = child->sibling;
	}
}

size_t ScriptHeapEntryGetStringBytes(HeapEntry *entry) {
	if (entry->type == T_STR) {
		return entry->bytes;
	} else if (entry->type == T_EOF) {
		return 0;
	} else if (entry->type == T_CONCAT) {
		return entry->concatBytes;
	} else {
		Assert(false);
		return 0;
	}
}

void ScriptHeapEntryConcatConvertToStringWrite(ExecutionContext *context, HeapEntry *entry, char *buffer) {
	while (true) {
		if (entry->type == T_STR) {
			MemoryCopy(buffer, entry->text, entry->bytes);
		} else if (entry->type == T_EOF) {
		} else if (entry->type == T_CONCAT) {
			HeapEntry *part1 = &context->heap[entry->concat1], *part2 = &context->heap[entry->concat2];
			size_t part1Bytes = ScriptHeapEntryGetStringBytes(part1);

			if (part1->type == T_CONCAT) {
				ScriptHeapEntryConcatConvertToStringWrite(context, part2, buffer + part1Bytes);
				Assert(part2->type != T_CONCAT);
				entry = part1;
				continue;
			} else if (part2->type == T_CONCAT) {
				ScriptHeapEntryConcatConvertToStringWrite(context, part1, buffer);
				Assert(part1->type != T_CONCAT);
				entry = part2;
				buffer += part1Bytes;
				continue;
			} else {
				ScriptHeapEntryConcatConvertToStringWrite(context, part1, buffer);
				ScriptHeapEntryConcatConvertToStringWrite(context, part2, buffer + part1Bytes);
			}
		} else {
			Assert(false);
		}

		break;
	}
}

void ScriptHeapEntryConcatConvertToString(ExecutionContext *context, HeapEntry *entry) {
	// TODO Efficient concatenation of many strings.
	// TODO Preventing stack overflow.
	Assert(entry->type == T_CONCAT);
	HeapEntry *part1 = &context->heap[entry->concat1], *part2 = &context->heap[entry->concat2];
	size_t part1Bytes = ScriptHeapEntryGetStringBytes(part1), part2Bytes = ScriptHeapEntryGetStringBytes(part2);
	Assert(entry->concatBytes == part1Bytes + part2Bytes);
	entry->type = T_STR;
	entry->bytes = part1Bytes + part2Bytes;
	entry->text = (char *) AllocateResize(NULL, entry->bytes);
	ScriptHeapEntryConcatConvertToStringWrite(context, part1, entry->text);
	ScriptHeapEntryConcatConvertToStringWrite(context, part2, entry->text + part1Bytes);
}

void ScriptHeapEntryToString(ExecutionContext *context, HeapEntry *entry, const char **text, size_t *bytes) {
	if (entry->type == T_STR) {
		*text = entry->text;
		*bytes = entry->bytes;
	} else if (entry->type == T_EOF) {
		*text = "";
		*bytes = 0;
	} else if (entry->type == T_CONCAT) {
		ScriptHeapEntryConcatConvertToString(context, entry);
		ScriptHeapEntryToString(context, entry, text, bytes);
	} else {
		Assert(false);
		*text = "";
		*bytes = 0;
	}
}

int ScriptExecuteFunction(uintptr_t instructionPointer, ExecutionContext *context) {
	// TODO Things to verify if loading untrusted scripts -- is this a feature we will need?
	// 	Checking we don't go off the end of the function body.
	// 	Checking that this is actually a valid function body pointer.
	// 	Checking various integer overflows.

	uintptr_t variableBase = context->c->localVariableCount - 1;
	uint8_t *functionData = context->functionData->data;

	while (true) {
		uint8_t command = functionData[instructionPointer++];
		// PrintDebug("--> %d, %ld, %ld\n", command, instructionPointer - 1, context->c->id);

		if (command == T_BLOCK || command == T_FUNCBODY) {
			uint16_t newVariableCount = functionData[instructionPointer + 0] + (functionData[instructionPointer + 1] << 8); 
			instructionPointer += 2;

			if (context->c->localVariableCount + newVariableCount > context->c->localVariablesAllocated) {
				// TODO Handling memory errors here.
				context->c->localVariablesAllocated = context->c->localVariableCount + newVariableCount;
				context->c->localVariables = (Value *) AllocateResize(context->c->localVariables, context->c->localVariablesAllocated * sizeof(Value)); 
				context->c->localVariableIsManaged = (bool *) AllocateResize(context->c->localVariableIsManaged, context->c->localVariablesAllocated * sizeof(bool)); 
			}

			MemoryCopy(context->c->localVariableIsManaged + context->c->localVariableCount, functionData + instructionPointer, newVariableCount);
			instructionPointer += newVariableCount;

			for (uintptr_t i = context->c->localVariableCount; i < context->c->localVariableCount + newVariableCount; i++) {
				if (command == T_FUNCBODY) {
					if (context->c->stackPointer < 1) return -1;
					context->c->localVariables[i] = context->c->stack[--context->c->stackPointer];
				} else {
					Value zero = { 0 };
					context->c->localVariables[i] = zero;
				}
			}

			context->c->localVariableCount += newVariableCount;
		} else if (command == T_EXIT_SCOPE) {
			uint16_t count = functionData[instructionPointer + 0] + (functionData[instructionPointer + 1] << 8); 
			instructionPointer += 2;
			if (context->c->localVariableCount < count) return -1;
			context->c->localVariableCount -= count;
		} else if (command == T_NUMERIC_LITERAL) {
			if (context->c->stackPointer == context->c->stackEntriesAllocated) {
				PrintError4(context, instructionPointer - 1, "Stack overflow.\n");
				return 0;
			}

			context->c->stackIsManaged[context->c->stackPointer] = false;
			MemoryCopy(&context->c->stack[context->c->stackPointer++], &functionData[instructionPointer], sizeof(Value));
			instructionPointer += sizeof(Value);
		} else if (command == T_NULL) {
			if (context->c->stackPointer == context->c->stackEntriesAllocated) {
				PrintError4(context, instructionPointer - 1, "Stack overflow.\n");
				return 0;
			}

			context->c->stackIsManaged[context->c->stackPointer] = true;
			context->c->stack[context->c->stackPointer++].i = 0;
		} else if (command == T_STRING_LITERAL) {
			if (context->c->stackPointer == context->c->stackEntriesAllocated) {
				PrintError4(context, instructionPointer - 1, "Stack overflow.\n");
				return 0;
			}

			uint32_t textBytes;
			MemoryCopy(&textBytes, &functionData[instructionPointer], sizeof(textBytes));
			instructionPointer += sizeof(textBytes);

			// TODO Handle memory allocation failures here.
			uintptr_t index = HeapAllocate(context);
			context->heap[index].type = T_STR;
			context->heap[index].text = (char *) AllocateResize(NULL, textBytes);
			context->heap[index].bytes = textBytes;
			MemoryCopy(context->heap[index].text, &functionData[instructionPointer], textBytes);
			instructionPointer += textBytes;

			Value v;
			v.i = index;
			context->c->stackIsManaged[context->c->stackPointer] = true;
			context->c->stack[context->c->stackPointer++] = v;
		} else if (command == T_CONCAT) {
			if (context->c->stackPointer < 2) return -1;
			uint64_t index1 = context->c->stack[context->c->stackPointer - 2].i;
			uint64_t index2 = context->c->stack[context->c->stackPointer - 1].i;
			if (!context->c->stackIsManaged[context->c->stackPointer - 2]) return -1;
			if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
			if (context->heapEntriesAllocated <= index1) return -1;
			if (context->heapEntriesAllocated <= index2) return -1;
			Assert(index1 <= 0xFFFFFFFF && index2 <= 0xFFFFFFFF);
			size_t bytes1 = ScriptHeapEntryGetStringBytes(&context->heap[index1]);
			size_t bytes2 = ScriptHeapEntryGetStringBytes(&context->heap[index2]);
			uintptr_t index = HeapAllocate(context); // TODO Handle memory allocation failures here.
			context->heap[index].type = T_CONCAT;
			context->heap[index].concat1 = index1;
			context->heap[index].concat2 = index2;
			context->heap[index].concatBytes = bytes1 + bytes2;

			// At most one argument can be a T_CONCAT (ohterwise converting to a string could stack overflow).
			if (context->heap[index1].type == T_CONCAT && context->heap[index2].type == T_CONCAT) {
				ScriptHeapEntryConcatConvertToString(context, bytes1 < bytes2 ? &context->heap[index1] : &context->heap[index2]);
			}

			context->c->stack[context->c->stackPointer - 2].i = index;
			context->c->stackPointer--;
		} else if (command == T_INTERPOLATE_STR || command == T_INTERPOLATE_BOOL 
				|| command == T_INTERPOLATE_INT || command == T_INTERPOLATE_FLOAT
				|| command == T_INTERPOLATE_ILIST) {
			STACK_READ_STRING(text1, bytes1, 3);
			STACK_READ_STRING(text3, bytes3, 1);

			char *freeText = NULL;
			const char *text2 = "";
			size_t bytes2 = 0;
			char temp[30];

			if (command == T_INTERPOLATE_STR) {
				STACK_READ_STRING(entryText2, entryBytes2, 2);
				text2 = entryText2, bytes2 = entryBytes2;
			} else if (command == T_INTERPOLATE_BOOL) {
				text2 = context->c->stack[context->c->stackPointer - 2].i ? "true" : "false";
				bytes2 = context->c->stack[context->c->stackPointer - 2].i ? 4 : 5;
			} else if (command == T_INTERPOLATE_INT) {
				text2 = temp;
				bytes2 = PrintIntegerToBuffer(temp, sizeof(temp), context->c->stack[context->c->stackPointer - 2].i);
			} else if (command == T_INTERPOLATE_FLOAT) {
				text2 = temp;
				bytes2 = PrintFloatToBuffer(temp, sizeof(temp), context->c->stack[context->c->stackPointer - 2].f);
			} else if (command == T_INTERPOLATE_ILIST) {
				if (!context->c->stackIsManaged[context->c->stackPointer - 2]) return -1;
				uint64_t index2 = context->c->stack[context->c->stackPointer - 2].i;
				if (context->heapEntriesAllocated <= index2) return -1;
				HeapEntry *entry2 = &context->heap[index2];
				if (entry2->type != T_EOF && entry2->type != T_LIST) return -1;

				if (entry2->type == T_EOF) {
					text2 = "null";
					bytes2 = 4;
				} else if (entry2->length == 0) {
					text2 = "[]";
					bytes2 = 2;
				} else {
					if (entry2->internalValuesAreManaged) return -1;
					bytes2 = 4;

					for (uintptr_t i = 0; i < entry2->length; i++) {
						bytes2 += PrintIntegerToBuffer(temp, sizeof(temp), entry2->list[i].i) + 2;
					}

					freeText = (char *) AllocateResize(freeText, bytes2);
					text2 = freeText;
					bytes2 = 0;
					freeText[bytes2++] = '[';
					freeText[bytes2++] = ' ';

					for (uintptr_t i = 0; i < entry2->length; i++) {
						bytes2 += PrintIntegerToBuffer(freeText + bytes2, sizeof(temp) /* enough space */, entry2->list[i].i);
						freeText[bytes2++] = ',';
						freeText[bytes2++] = ' ';
					}

					bytes2 -= 2;
					freeText[bytes2++] = ' ';
					freeText[bytes2++] = ']';
				}
			}

			// TODO Handle memory allocation failures here.
			uintptr_t index = HeapAllocate(context);
			context->heap[index].type = T_STR;
			context->heap[index].bytes = bytes1 + bytes2 + bytes3;
			context->heap[index].text = (char *) AllocateResize(NULL, context->heap[index].bytes);
			if (bytes1) MemoryCopy(context->heap[index].text + 0,               text1, bytes1);
			if (bytes2) MemoryCopy(context->heap[index].text + bytes1,          text2, bytes2);
			if (bytes3) MemoryCopy(context->heap[index].text + bytes1 + bytes2, text3, bytes3);
			context->c->stack[context->c->stackPointer - 3].i = index;

			if (freeText) AllocateResize(freeText, 0);

			context->c->stackPointer -= 2;
		} else if (command == T_VARIABLE) {
			if (context->c->stackPointer == context->c->stackEntriesAllocated) {
				PrintDebug("Stack overflow.\n");
				return -1;
			}

			int32_t scopeIndex;
			MemoryCopy(&scopeIndex, &functionData[instructionPointer], sizeof(scopeIndex));
			instructionPointer += sizeof(scopeIndex);

			if (scopeIndex >= 0) {
				if ((uintptr_t) scopeIndex >= context->globalVariableCount) return -1;
				context->c->stackIsManaged[context->c->stackPointer] = context->globalVariableIsManaged[scopeIndex];
				context->c->stack[context->c->stackPointer++] = context->globalVariables[scopeIndex];
			} else {
				scopeIndex = variableBase - scopeIndex;
				if ((uintptr_t) scopeIndex >= context->c->localVariableCount) return -1;
				context->c->stackIsManaged[context->c->stackPointer] = context->c->localVariableIsManaged[scopeIndex];
				context->c->stack[context->c->stackPointer++] = context->c->localVariables[scopeIndex];
			}
		} else if (command == T_EQUALS) {
			if (!context->c->stackPointer) return -1;
			int32_t scopeIndex;
			MemoryCopy(&scopeIndex, &functionData[instructionPointer], sizeof(scopeIndex));
			instructionPointer += sizeof(scopeIndex);

			if (scopeIndex >= 0) {
				if ((uintptr_t) scopeIndex >= context->globalVariableCount) return -1;
				if (context->globalVariableIsManaged[scopeIndex] != context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
				context->globalVariables[scopeIndex] = context->c->stack[--context->c->stackPointer];
			} else {
				scopeIndex = variableBase - scopeIndex;
				if ((uintptr_t) scopeIndex >= context->c->localVariableCount) return -1;
				if (context->c->localVariableIsManaged[scopeIndex] != context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
				context->c->localVariables[scopeIndex] = context->c->stack[--context->c->stackPointer];
			}
		} else if (command == T_EQUALS_DOT) {
			if (context->c->stackPointer < 2) return -1;
			if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;

			uint64_t index = context->c->stack[context->c->stackPointer - 1].i;

			if (!index) {
				PrintError4(context, instructionPointer - 1, "The struct is null.\n");
				return 0;
			}

			if (context->heapEntriesAllocated <= index) return -1;
			HeapEntry *entry = &context->heap[index];
			if (entry->type != T_STRUCT) return -1;

			int32_t fieldIndex;
			MemoryCopy(&fieldIndex, &functionData[instructionPointer], sizeof(fieldIndex));
			instructionPointer += sizeof(fieldIndex);
			bool isManaged = fieldIndex < 0;
			if (isManaged) fieldIndex = -fieldIndex - 1;
			if (fieldIndex < 0 || fieldIndex >= entry->fieldCount) return -1;

			entry->fields[fieldIndex] = context->c->stack[context->c->stackPointer - 2];
			if (isManaged != context->c->stackIsManaged[context->c->stackPointer - 2]) return -1;
			((uint8_t *) entry->fields - 1)[-fieldIndex] = isManaged;

			context->c->stackPointer -= 2;
		} else if (command == T_EQUALS_LIST) {
			if (context->c->stackPointer < 3) return -1;
			if (context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
			if (!context->c->stackIsManaged[context->c->stackPointer - 2]) return -1;

			uint64_t index = context->c->stack[context->c->stackPointer - 2].i;

			if (!index) {
				PrintError4(context, instructionPointer - 1, "The list is null.\n");
				return 0;
			}

			if (context->heapEntriesAllocated <= index) return -1;
			HeapEntry *entry = &context->heap[index];
			if (entry->type != T_LIST) return -1;

			index = context->c->stack[context->c->stackPointer - 1].i;

			if (index >= entry->length) {
				PrintError4(context, instructionPointer - 1, "The index %ld is not valid for the list, which has length %d.\n", index, entry->length);
				return 0;
			}

			entry->list[index] = context->c->stack[context->c->stackPointer - 3];
			if (entry->internalValuesAreManaged != context->c->stackIsManaged[context->c->stackPointer - 3]) return -1;

			context->c->stackPointer -= 3;
		} else if (command == T_INDEX_LIST) {
			if (context->c->stackPointer < 2) return -1;
			if (context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
			if (!context->c->stackIsManaged[context->c->stackPointer - 2]) return -1;

			uint64_t index = context->c->stack[context->c->stackPointer - 2].i;

			if (!index) {
				PrintError4(context, instructionPointer - 1, "The list is null.\n");
				return 0;
			}

			if (context->heapEntriesAllocated <= index) return -1;
			HeapEntry *entry = &context->heap[index];
			if (entry->type != T_LIST) return -1;

			index = context->c->stack[context->c->stackPointer - 1].i;

			if (index >= entry->length) {
				PrintError4(context, instructionPointer - 1, "The index %ld is not valid for the list, which has length %d.\n", index, entry->length);
				return 0;
			}

			context->c->stack[context->c->stackPointer - 2] = entry->list[index];
			context->c->stackIsManaged[context->c->stackPointer - 2] = entry->internalValuesAreManaged;
			context->c->stackPointer--;
		} else if (command == T_OP_FIRST || command == T_OP_LAST) {
			if (context->c->stackPointer < 1) return -1;
			if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;

			uint64_t index = context->c->stack[context->c->stackPointer - 1].i;

			if (!index) {
				PrintError4(context, instructionPointer - 1, "The list is null.\n");
				return 0;
			}

			if (context->heapEntriesAllocated <= index) return -1;
			HeapEntry *entry = &context->heap[index];
			if (entry->type != T_LIST) return -1;

			if (!entry->length) {
				PrintError4(context, instructionPointer - 1, "The list is empty.\n");
				return 0;
			}

			context->c->stack[context->c->stackPointer - 1] = entry->list[command == T_OP_FIRST ? 0 : entry->length - 1];
			context->c->stackIsManaged[context->c->stackPointer - 1] = entry->internalValuesAreManaged;
		} else if (command == T_DOT) {
			if (context->c->stackPointer < 1) return -1;
			if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;

			uint64_t index = context->c->stack[context->c->stackPointer - 1].i;

			if (!index) {
				PrintError4(context, instructionPointer - 1, "The struct is null.\n");
				return 0;
			}

			if (context->heapEntriesAllocated <= index) return -1;
			HeapEntry *entry = &context->heap[index];
			if (entry->type != T_STRUCT) return -1;

			int16_t fieldIndex;
			MemoryCopy(&fieldIndex, &functionData[instructionPointer], sizeof(fieldIndex));
			instructionPointer += sizeof(fieldIndex);
			bool isManaged = fieldIndex < 0;
			if (isManaged) fieldIndex = -fieldIndex - 1;
			if (fieldIndex < 0 || fieldIndex >= entry->fieldCount) return -1;

			// Only allow the isManaged bool to be incorrect if it's a null managed variable.
			if (isManaged != ((uint8_t *) entry->fields - 1)[-fieldIndex] && (entry->fields[fieldIndex].i || !isManaged)) return -1;

			context->c->stack[context->c->stackPointer - 1] = entry->fields[fieldIndex];
			context->c->stackIsManaged[context->c->stackPointer - 1] = isManaged;
		} else if (command == T_ADD) {
			if (context->c->stackPointer < 2) return -1;
			context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].i + context->c->stack[context->c->stackPointer - 1].i;
			context->c->stackPointer--;
		} else if (command == T_MINUS) {
			if (context->c->stackPointer < 2) return -1;
			context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].i - context->c->stack[context->c->stackPointer - 1].i;
			context->c->stackPointer--;
		} else if (command == T_ASTERISK) {
			if (context->c->stackPointer < 2) return -1;
			context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].i * context->c->stack[context->c->stackPointer - 1].i;
			context->c->stackPointer--;
		} else if (command == T_SLASH) {
			if (context->c->stackPointer < 2) return -1;

			if (0 == context->c->stack[context->c->stackPointer - 1].i) {
				PrintError4(context, instructionPointer - 1, "Attempted division by zero.\n");
				return 0;
			}

			context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].f / context->c->stack[context->c->stackPointer - 1].f;
			context->c->stackPointer--;
		} else if (command == T_NEGATE) {
			if (context->c->stackPointer < 1) return -1;
			context->c->stack[context->c->stackPointer - 1].i = -context->c->stack[context->c->stackPointer - 1].i;
		} else if (command == T_FLOAT_ADD) {
			if (context->c->stackPointer < 2) return -1;
			context->c->stack[context->c->stackPointer - 2].f = context->c->stack[context->c->stackPointer - 2].f + context->c->stack[context->c->stackPointer - 1].f;
			context->c->stackPointer--;
		} else if (command == T_FLOAT_MINUS) {
			if (context->c->stackPointer < 2) return -1;
			context->c->stack[context->c->stackPointer - 2].f = context->c->stack[context->c->stackPointer - 2].f - context->c->stack[context->c->stackPointer - 1].f;
			context->c->stackPointer--;
		} else if (command == T_FLOAT_ASTERISK) {
			if (context->c->stackPointer < 2) return -1;
			context->c->stack[context->c->stackPointer - 2].f = context->c->stack[context->c->stackPointer - 2].f * context->c->stack[context->c->stackPointer - 1].f;
			context->c->stackPointer--;
		} else if (command == T_FLOAT_SLASH) {
			if (context->c->stackPointer < 2) return -1;
			context->c->stack[context->c->stackPointer - 2].f = context->c->stack[context->c->stackPointer - 2].f / context->c->stack[context->c->stackPointer - 1].f;
			context->c->stackPointer--;
		} else if (command == T_FLOAT_NEGATE) {
			if (context->c->stackPointer < 1) return -1;
			context->c->stack[context->c->stackPointer - 1].f = -context->c->stack[context->c->stackPointer - 1].f;
		} else if (command == T_LESS_THAN) {
			if (context->c->stackPointer < 2) return -1;
			context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].i < context->c->stack[context->c->stackPointer - 1].i;
			context->c->stackPointer--;
		} else if (command == T_GREATER_THAN) {
			if (context->c->stackPointer < 2) return -1;
			context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].i > context->c->stack[context->c->stackPointer - 1].i;
			context->c->stackPointer--;
		} else if (command == T_LT_OR_EQUAL) {
			if (context->c->stackPointer < 2) return -1;
			context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].i <= context->c->stack[context->c->stackPointer - 1].i;
			context->c->stackPointer--;
		} else if (command == T_GT_OR_EQUAL) {
			if (context->c->stackPointer < 2) return -1;
			context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].i >= context->c->stack[context->c->stackPointer - 1].i;
			context->c->stackPointer--;
		} else if (command == T_DOUBLE_EQUALS) {
			if (context->c->stackPointer < 2) return -1;
			context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].i == context->c->stack[context->c->stackPointer - 1].i;
			context->c->stackPointer--;
		} else if (command == T_NOT_EQUALS) {
			if (context->c->stackPointer < 2) return -1;
			context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].i != context->c->stack[context->c->stackPointer - 1].i;
			context->c->stackPointer--;
		} else if (command == T_LOGICAL_NOT) {
			if (context->c->stackPointer < 1) return -1;
			context->c->stack[context->c->stackPointer - 1].i = !context->c->stack[context->c->stackPointer - 1].i;
		} else if (command == T_FLOAT_LESS_THAN) {
			if (context->c->stackPointer < 2) return -1;
			context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].f < context->c->stack[context->c->stackPointer - 1].f;
			context->c->stackPointer--;
		} else if (command == T_FLOAT_GREATER_THAN) {
			if (context->c->stackPointer < 2) return -1;
			context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].f > context->c->stack[context->c->stackPointer - 1].f;
			context->c->stackPointer--;
		} else if (command == T_FLOAT_LT_OR_EQUAL) {
			if (context->c->stackPointer < 2) return -1;
			context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].f <= context->c->stack[context->c->stackPointer - 1].f;
			context->c->stackPointer--;
		} else if (command == T_FLOAT_GT_OR_EQUAL) {
			if (context->c->stackPointer < 2) return -1;
			context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].f >= context->c->stack[context->c->stackPointer - 1].f;
			context->c->stackPointer--;
		} else if (command == T_FLOAT_DOUBLE_EQUALS) {
			if (context->c->stackPointer < 2) return -1;
			context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].f == context->c->stack[context->c->stackPointer - 1].f;
			context->c->stackPointer--;
		} else if (command == T_FLOAT_NOT_EQUALS) {
			if (context->c->stackPointer < 2) return -1;
			context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].f != context->c->stack[context->c->stackPointer - 1].f;
			context->c->stackPointer--;
		} else if (command == T_STR_DOUBLE_EQUALS || command == T_STR_NOT_EQUALS) {
			STACK_READ_STRING(text1, bytes1, 2);
			STACK_READ_STRING(text2, bytes2, 1);
			bool equal = bytes1 == bytes2 && 0 == MemoryCompare(text1, text2, bytes1);
			context->c->stack[context->c->stackPointer - 2].i = command == T_STR_NOT_EQUALS ? !equal : equal;
			context->c->stackIsManaged[context->c->stackPointer - 2] = false;
			context->c->stackPointer--;
		} else if (command == T_OP_LEN) {
			if (context->c->stackPointer < 1) return -1;
			if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
			uint64_t index = context->c->stack[context->c->stackPointer - 1].i;
			if (context->heapEntriesAllocated <= index) return -1;
			HeapEntry *entry = &context->heap[index];

			if (entry->type == T_LIST) {
				context->c->stack[context->c->stackPointer - 1].i = entry->length;
			} else {
				STACK_READ_STRING(stringText, stringBytes, 1);
				context->c->stack[context->c->stackPointer - 1].i = stringBytes;
			}

			context->c->stackIsManaged[context->c->stackPointer - 1] = false;
		} else if (command == T_INDEX) {
			if (context->c->stackPointer < 2) return -1;
			STACK_READ_STRING(text, bytes, 2);
			if (context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
			uintptr_t index = context->c->stack[context->c->stackPointer - 1].i;

			if (index >= bytes) {
				PrintError4(context, instructionPointer - 1, "Index %ld out of bounds in string '%.*s' of length %ld.\n", 
						index, bytes, text, bytes);
				return 0;
			}

			char c = text[index];
			index = HeapAllocate(context);
			context->heap[index].type = T_STR;
			context->heap[index].bytes = 1;
			context->heap[index].text = (char *) AllocateResize(NULL, 1); // TODO Handling allocation failure.
			context->heap[index].text[0] = c;
			context->c->stack[context->c->stackPointer - 2].i = index;
			context->c->stackIsManaged[context->c->stackPointer - 2] = true;
			context->c->stackPointer--;
		} else if (command == T_CALL) {
			callCommand:;
			if (context->c->stackPointer < 1) return -1;
			if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
			Value newBody = context->c->stack[--context->c->stackPointer];

			if (newBody.i == 0) {
				PrintError4(context, instructionPointer - 1, "Function pointer was null.\n");
				return 0;
			}

			bool popResult = false;
			bool assertResult = false;

			while (true) {
				uint64_t index = newBody.i;
				if (context->heapEntriesAllocated <= index) return -1;
				HeapEntry *entry = &context->heap[index];
				newBody.i = entry->lambdaID;

				if (entry->type == T_OP_DISCARD) {
					popResult = true;
				} else if (entry->type == T_OP_ASSERT) {
					assertResult = true;
				} else if (entry->type == T_OP_CURRY) {
					if (context->c->stackPointer == context->c->stackEntriesAllocated) {
						PrintError4(context, instructionPointer - 1, "Stack overflow.\n");
						return 0;
					}

					context->c->stack[context->c->stackPointer] = entry->curryValue;
					context->c->stackIsManaged[context->c->stackPointer] = entry->internalValuesAreManaged;
					context->c->stackPointer++;
				} else if (entry->type == T_FUNCPTR) {
					break;
				} else {
					return -1;
				}
			} 

			if (context->c->backTracePointer == sizeof(context->c->backTrace) / sizeof(context->c->backTrace[0])) {
				PrintError4(context, instructionPointer - 1, "Back trace overflow.\n");
				return 0;
			}
			
			BackTraceItem *link = &context->c->backTrace[context->c->backTracePointer];
			context->c->backTracePointer++;
			link->instructionPointer = instructionPointer;
			link->variableBase = variableBase;
			link->popResult = popResult;
			link->assertResult = assertResult;
			instructionPointer = newBody.i;
			variableBase = context->c->localVariableCount - 1;
		} else if (command == T_IF) {
			if (context->c->stackPointer < 1) return -1;
			Value condition = context->c->stack[--context->c->stackPointer];
			int32_t delta;
			MemoryCopy(&delta, &functionData[instructionPointer], sizeof(delta));
			instructionPointer += condition.i ? (int32_t) sizeof(delta) : delta; 
		} else if (command == T_LOGICAL_OR) {
			if (context->c->stackPointer < 1) return -1;
			Value condition = context->c->stack[context->c->stackPointer - 1];
			int32_t delta;
			MemoryCopy(&delta, &functionData[instructionPointer], sizeof(delta));
			instructionPointer += condition.i ? delta : (int32_t) sizeof(delta); 
			if (!condition.i) context->c->stackPointer--;
		} else if (command == T_LOGICAL_AND) {
			if (context->c->stackPointer < 1) return -1;
			Value condition = context->c->stack[context->c->stackPointer - 1];
			int32_t delta;
			MemoryCopy(&delta, &functionData[instructionPointer], sizeof(delta));
			instructionPointer += condition.i ? (int32_t) sizeof(delta) : delta; 
			if (condition.i) context->c->stackPointer--;
		} else if (command == T_BRANCH) {
			int32_t delta;
			MemoryCopy(&delta, &functionData[instructionPointer], sizeof(delta));
			instructionPointer += delta; 
		} else if (command == T_POP) {
			if (context->c->stackPointer < 1) return -1;
			context->c->stackPointer--;
		} else if (command == T_DUP) {
			if (context->c->stackPointer < 1) return -1;

			if (context->c->stackPointer == context->c->stackEntriesAllocated) {
				PrintError4(context, instructionPointer - 1, "Stack overflow.\n");
				return 0;
			}

			context->c->stack[context->c->stackPointer] = context->c->stack[context->c->stackPointer - 1];
			context->c->stackIsManaged[context->c->stackPointer] = context->c->stackIsManaged[context->c->stackPointer - 1];
			context->c->stackPointer++;
		} else if (command == T_SWAP) {
			if (context->c->stackPointer < 2) return -1;
			Value v1 = context->c->stack[context->c->stackPointer - 1];
			Value v2 = context->c->stack[context->c->stackPointer - 2];
			bool m1 = context->c->stackIsManaged[context->c->stackPointer - 1];
			bool m2 = context->c->stackIsManaged[context->c->stackPointer - 2];
			context->c->stack[context->c->stackPointer - 1] = v2;
			context->c->stack[context->c->stackPointer - 2] = v1;
			context->c->stackIsManaged[context->c->stackPointer - 1] = m2;
			context->c->stackIsManaged[context->c->stackPointer - 2] = m1;
		} else if (command == T_ASSERT) {
			if (context->c->stackPointer < 1) return -1;
			Value condition = context->c->stack[--context->c->stackPointer];

			if (condition.i == 0) {
				PrintError4(context, instructionPointer - 1, "Assertion failed.\n");
				return 0;
			}
		} else if (command == T_PERSIST) {
			if (!ExternalPersistWrite(context, NULL)) {
				return 0;
			}
		} else if (command == T_NEW) {
			if (context->c->stackPointer == context->c->stackEntriesAllocated) {
				PrintError4(context, instructionPointer - 1, "Stack overflow.\n");
				return 0;
			}

			int16_t fieldCount = functionData[instructionPointer + 0] + (functionData[instructionPointer + 1] << 8); 
			instructionPointer += 2;
			uintptr_t index = HeapAllocate(context);
			context->heap[index].type = fieldCount < 0 ? T_LIST : T_STRUCT;

			if (fieldCount >= 0) {
				context->heap[index].fields = (Value *) ((uint8_t *) AllocateResize(NULL, fieldCount * (1 + sizeof(Value))) + fieldCount);
				context->heap[index].fieldCount = fieldCount;

				for (intptr_t i = 0; i < fieldCount; i++) {
					context->heap[index].fields[i].i = 0;

					// Default all fields to being unmanaged.
					// The first type they are set this will be updated.
					((uint8_t *) context->heap[index].fields)[-1 - i] = false;
				}
			} else {
				context->heap[index].internalValuesAreManaged = fieldCount == -2;
				context->heap[index].length = context->heap[index].allocated = 0;
				context->heap[index].list = NULL;
			}

			Value v;
			v.i = index;
			context->c->stackIsManaged[context->c->stackPointer] = true;
			context->c->stack[context->c->stackPointer++] = v;
		} else if (command == T_OP_RESIZE) {
			if (context->c->stackPointer < 2) return -1;
			if (!context->c->stackIsManaged[context->c->stackPointer - 2]) return -1;

			uint64_t index = context->c->stack[context->c->stackPointer - 2].i;

			if (!index) {
				PrintError4(context, instructionPointer - 1, "The list is null.\n");
				return 0;
			}

			if (context->heapEntriesAllocated <= index) return -1;
			HeapEntry *entry = &context->heap[index];
			if (entry->type != T_LIST) return -1;

			int64_t newLength = context->c->stack[context->c->stackPointer - 1].i;

			if (newLength < 0 || newLength >= 1000000000) {
				PrintError4(context, instructionPointer - 1, "The new length of the list is out of the supported range (0..1000000000).\n");
				return 0;
			}

			uint32_t oldLength = context->heap[index].length;
			context->heap[index].length = newLength;
			context->heap[index].allocated = newLength;

			// TODO Handling out of memory errors.
			context->heap[index].list = (Value *) AllocateResize(context->heap[index].list, newLength * sizeof(Value));

			for (uintptr_t i = oldLength; i < (size_t) newLength; i++) {
				context->heap[index].list[i].i = 0;
			}

			context->c->stackPointer -= 2;
		} else if (command == T_OP_ADD) {
			if (context->c->stackPointer < 2) return -1;
			if (!context->c->stackIsManaged[context->c->stackPointer - 2]) return -1;

			uint64_t index = context->c->stack[context->c->stackPointer - 2].i;

			if (!index) {
				PrintError4(context, instructionPointer - 1, "The list is null.\n");
				return 0;
			}

			if (context->heapEntriesAllocated <= index) return -1;
			HeapEntry *entry = &context->heap[index];
			if (entry->type != T_LIST) return -1;

			int64_t newLength = entry->length + 1;

			if (newLength < 0 || newLength >= 1000000000) {
				PrintError4(context, instructionPointer - 1, "The new length of the list is out of the supported range (0..1000000000).\n");
				return 0;
			}

			uint32_t oldLength = context->heap[index].length;
			entry->length = newLength;

			if (entry->length > entry->allocated) {
				// TODO Handling out of memory errors.
				entry->allocated = entry->allocated ? entry->allocated * 2 : 4;
				entry->list = (Value *) AllocateResize(entry->list, entry->allocated * sizeof(Value));
				Assert(entry->length <= entry->allocated);
			}

			if (entry->internalValuesAreManaged != context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
			entry->list[oldLength] = context->c->stack[context->c->stackPointer - 1];

			context->c->stackPointer -= 2;
		} else if (command == T_OP_INSERT) {
			if (context->c->stackPointer < 3) return -1;
			if (!context->c->stackIsManaged[context->c->stackPointer - 3]) return -1;

			uint64_t index = context->c->stack[context->c->stackPointer - 3].i;

			if (!index) {
				PrintError4(context, instructionPointer - 1, "The list is null.\n");
				return 0;
			}

			if (context->heapEntriesAllocated <= index) return -1;
			HeapEntry *entry = &context->heap[index];
			if (entry->type != T_LIST) return -1;

			int64_t newLength = entry->length + 1;

			if (newLength < 0 || newLength >= 1000000000) {
				PrintError4(context, instructionPointer - 1, "The new length of the list is out of the supported range (0..1000000000).\n");
				return 0;
			}

			uint32_t oldLength = context->heap[index].length;
			entry->length = newLength;

			if (entry->length > entry->allocated) {
				// TODO Handling out of memory errors.
				entry->allocated = entry->allocated ? entry->allocated * 2 : 4;
				entry->list = (Value *) AllocateResize(entry->list, entry->allocated * sizeof(Value));
				Assert(entry->length <= entry->allocated);
			}

			if (context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
			int64_t insertIndex = context->c->stack[context->c->stackPointer - 1].i;

			if (insertIndex < 0 || insertIndex > oldLength) {
				PrintError4(context, instructionPointer - 1, "Cannot insert at index %ld. The list has length %ld.\n",
						insertIndex, oldLength);
				return 0;
			}

			for (int64_t i = oldLength - 1; i >= insertIndex; i--) {
				entry->list[i + 1] = entry->list[i];
			}

			if (entry->internalValuesAreManaged != context->c->stackIsManaged[context->c->stackPointer - 2]) return -1;
			entry->list[insertIndex] = context->c->stack[context->c->stackPointer - 2];

			context->c->stackPointer -= 3;
		} else if (command == T_OP_DELETE_ALL) {
			if (context->c->stackPointer < 1) return -1;
			if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;

			uint64_t index = context->c->stack[context->c->stackPointer - 1].i;

			if (!index) {
				PrintError4(context, instructionPointer - 1, "The list is null.\n");
				return 0;
			}

			if (context->heapEntriesAllocated <= index) return -1;
			HeapEntry *entry = &context->heap[index];
			if (entry->type != T_LIST) return -1;

			context->heap[index].length = context->heap[index].allocated = 0;
			context->heap[index].list = (Value *) AllocateResize(context->heap[index].list, 0);
			context->c->stackPointer--;
		} else if (command == T_OP_FIND_AND_DELETE) {
			if (context->c->stackPointer < 2) return -1;
			if (!context->c->stackIsManaged[context->c->stackPointer - 2]) return -1;

			uint64_t index = context->c->stack[context->c->stackPointer - 2].i;

			if (!index) {
				PrintError4(context, instructionPointer - 1, "The list is null.\n");
				return 0;
			}

			if (context->heapEntriesAllocated <= index) return -1;
			HeapEntry *entry = &context->heap[index];
			if (entry->type != T_LIST) return -1;
			if (entry->internalValuesAreManaged != context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
			bool found = false;

			for (uintptr_t i = 0; i < entry->length; i++) {
				if (entry->list[i].i == context->c->stack[context->c->stackPointer - 1].i) {
					entry->length--;
					found = true;

					for (uintptr_t j = i; j < entry->length; j++) {
						entry->list[j] = entry->list[j + 1];
					}

					break;
				}
			}

			context->c->stack[context->c->stackPointer - 2].i = found;
			context->c->stackIsManaged[context->c->stackPointer - 2] = false;
			context->c->stackPointer--;
		} else if (command == T_OP_DISCARD || command == T_OP_ASSERT) {
			if (context->c->stackPointer < 1) return -1;
			if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
			int64_t id = context->c->stack[context->c->stackPointer - 1].i;
			uintptr_t index = HeapAllocate(context);
			context->heap[index].type = command;
			context->heap[index].lambdaID = id;
			context->c->stackIsManaged[context->c->stackPointer - 1] = true;
			context->c->stack[context->c->stackPointer - 1].i = index;
		} else if (command == T_OP_CURRY) {
			if (context->c->stackPointer < 2) return -1;
			if (!context->c->stackIsManaged[context->c->stackPointer - 2]) return -1;
			bool valueIsManaged = context->c->stackIsManaged[context->c->stackPointer - 1];
			Value value = context->c->stack[context->c->stackPointer - 1];
			int64_t id = context->c->stack[context->c->stackPointer - 2].i;
			uintptr_t index = HeapAllocate(context);
			context->heap[index].type = command;
			context->heap[index].lambdaID = id;
			context->heap[index].curryValue = value;
			context->heap[index].internalValuesAreManaged = valueIsManaged;
			context->c->stackIsManaged[context->c->stackPointer - 2] = true;
			context->c->stack[context->c->stackPointer - 2].i = index;
			context->c->stackPointer--;
		} else if (command == T_OP_ASYNC) {
			if (context->c->stackPointer < 1) return -1;
			if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
			CoroutineState *c = (CoroutineState *) AllocateResize(NULL, sizeof(CoroutineState)); // TODO Handle allocation failure.
			CoroutineState empty = { 0 };
			*c = empty;
			c->id = ++context->lastCoroutineID;
			c->startedByAsync = true;
			c->stackEntriesAllocated = sizeof(context->c->stack) / sizeof(context->c->stack[0]);
			c->stackPointer = 2;
			c->stack[0].i = -1; // Indicates to T_AWAIT to remove the coroutine.
			c->stackIsManaged[0] = false;
			c->stack[1] = context->c->stack[context->c->stackPointer - 1];
			c->stackIsManaged[1] = true;
			c->nextCoroutine = context->allCoroutines;
			if (c->nextCoroutine) c->nextCoroutine->previousCoroutineLink = &c->nextCoroutine;
			c->previousCoroutineLink = &context->allCoroutines;
			context->allCoroutines = c;
			c->nextUnblockedCoroutine = context->unblockedCoroutines;
			if (c->nextUnblockedCoroutine) c->nextUnblockedCoroutine->previousUnblockedCoroutineLink = &c->nextUnblockedCoroutine;
			c->previousUnblockedCoroutineLink = &context->unblockedCoroutines;
			context->unblockedCoroutines = c;
			context->c->stackIsManaged[context->c->stackPointer - 1] = false;
			context->c->stack[context->c->stackPointer - 1].i = c->id;
		} else if (command == T_AWAIT) {
			awaitCommand:;
			if (context->c->stackPointer < 1 && !context->c->externalCoroutine) return -1;

			// PrintDebug("== AWAIT from %ld\n", context->c->id);
			Assert(!context->c->nextUnblockedCoroutine && !context->c->previousUnblockedCoroutineLink);
			bool unblockImmediately = false;

			if (context->c->externalCoroutine) {
				// PrintDebug("== external coroutine\n");
				context->c->unblockedBy = -1;
				context->c->awaiting = true;
				context->c->instructionPointer = instructionPointer;
				context->c->variableBase = variableBase;
				context->c->waitingOnCount = 0;
			} else if (context->c->stack[context->c->stackPointer - 1].i == -1) {
				if (context->c->stackPointer != 1) return -1;
				// The coroutine has finished. Remove it from the list of all coroutines.
				*context->c->previousCoroutineLink = context->c->nextCoroutine;
				if (context->c->nextCoroutine) context->c->nextCoroutine->previousCoroutineLink = context->c->previousCoroutineLink;

				// PrintDebug("== finished\n");

				for (uintptr_t i = 0; i < context->c->waiterCount; i++) {
					CoroutineState *c = context->c->waiters[i];
					if (!c) continue;
					Assert(!c->nextUnblockedCoroutine && !c->previousUnblockedCoroutineLink);
					c->unblockedBy = context->c->id;
					c->nextUnblockedCoroutine = context->unblockedCoroutines;
					if (c->nextUnblockedCoroutine) c->nextUnblockedCoroutine->previousUnblockedCoroutineLink = &c->nextUnblockedCoroutine;
					c->previousUnblockedCoroutineLink = &context->unblockedCoroutines;
					context->unblockedCoroutines = c;

					for (uintptr_t j = 0; j < c->waitingOnCount; j++) {
						Assert(*(c->waitingOn[j]) == c);
						*(c->waitingOn[j]) = NULL;
					}

					c->waitingOnCount = 0;
					// PrintDebug("== unblocked %ld\n", c->id);
				}

				ScriptFreeCoroutine(context->c);
			} else {
				if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
				// The coroutine is waiting.
				context->c->unblockedBy = -1;
				context->c->awaiting = true;
				context->c->instructionPointer = instructionPointer;
				context->c->variableBase = variableBase;
				uint64_t index = context->c->stack[context->c->stackPointer - 1].i;
				if (context->heapEntriesAllocated <= index) return -1;
				HeapEntry *entry = &context->heap[index];
				if (entry->internalValuesAreManaged || entry->type != T_LIST) return -1;
				
				context->c->waitingOn = (CoroutineState ***) AllocateResize(context->c->waitingOn, sizeof(CoroutineState **) * entry->length);
				Assert(context->c->waitingOnCount == 0);
				CoroutineState *c = context->allCoroutines;

				while (c) {
					for (uintptr_t i = 0; i < entry->length; i++) {
						if (c->id == (uint64_t) entry->list[i].i) {
							if (c->waiterCount == c->waitersAllocated) {
								c->waitersAllocated = c->waitersAllocated ? c->waitersAllocated * 2 : 4;
								c->waiters = (CoroutineState **) AllocateResize(c->waiters, sizeof(CoroutineState *) * c->waitersAllocated);
							}

							c->waiters[c->waiterCount] = context->c;
							context->c->waitingOn[context->c->waitingOnCount++] = &c->waiters[c->waiterCount];
							c->waiterCount++;
						}
					}

					c = c->nextCoroutine;
				}

				// PrintDebug("== waiting on %d...\n", context->c->waitingOnCount);

				if (!context->c->waitingOnCount) {
					// PrintDebug("== immediately unblocking\n");
					context->c->unblockedBy = entry->length ? entry->list[0].i : -1;
					unblockImmediately = true;
				}
			}

			CoroutineState *next = unblockImmediately ? context->c : context->unblockedCoroutines;

			if (!next) {
				if (context->externalCoroutineCount) {
					// PrintDebug("== wait for an external coroutine\n");
					next = ExternalCoroutineWaitAny(context);
					Assert(next->externalCoroutine);
					unblockImmediately = true;
				} else {
					// TODO Earlier deadlock detection.
					PrintError4(context, instructionPointer - 1, "No tasks can run if this task (ID %ld) starts waiting.\n", context->c->id);
					PrintDebug("All tasks:\n");
					CoroutineState *c = context->allCoroutines;

					while (c) {
						PrintDebug("\t%ld blocks ", c->id);
						bool first = true;

						for (uintptr_t i = 0; i < c->waiterCount; i++) {
							if (!c->waiters[i]) continue;
							PrintDebug("%s%ld", first ? "" : ", ", c->waiters[i]->id);
							first = false;
						}

						PrintDebug("\n");
						PrintBackTrace(context, c->instructionPointer - 1, c, "\t");
						c = c->nextCoroutine;
					}

					return 0;
				}
			}

			if (!unblockImmediately) {
				Assert(next->previousUnblockedCoroutineLink);
				*next->previousUnblockedCoroutineLink = next->nextUnblockedCoroutine;
				if (next->nextUnblockedCoroutine) next->nextUnblockedCoroutine->previousUnblockedCoroutineLink = next->previousUnblockedCoroutineLink;
			}

			next->nextUnblockedCoroutine = NULL;
			next->previousUnblockedCoroutineLink = NULL;
			context->c = next;
			// PrintDebug("== switch to %ld\n", next->id);

			if (context->c->awaiting) {
				if (!context->c->externalCoroutine) {
					context->c->stackIsManaged[context->c->stackPointer - 1] = false;
					context->c->stack[context->c->stackPointer - 1].i = context->c->unblockedBy;
				}

				instructionPointer = context->c->instructionPointer;
				variableBase = context->c->variableBase;
				// PrintDebug("== unblocked by %ld\n", context->c->unblockedBy);
			} else {
				// PrintDebug("== just started\n");
				instructionPointer = 1; // There is a T_AWAIT command at address 1.
				goto callCommand;
			}
		} else if (command == T_REPL_RESULT) {
			if (context->c->stackPointer < 1) return -1;
			ExternalPassREPLResult(context, context->c->stack[--context->c->stackPointer]);
		} else if (command == T_END_FUNCTION || command == T_EXTCALL) {
			if (command == T_EXTCALL) {
				uint16_t index = functionData[instructionPointer + 0] + (functionData[instructionPointer + 1] << 8); 
				instructionPointer += 2;

				if (index < sizeof(externalFunctions) / sizeof(externalFunctions[0])) {
					Value returnValue;
					int result = externalFunctions[index].callback(context, &returnValue);
					if (result <= 0) return result;

					if (result == 4) {
						context->externalCoroutineCount++;
						context->c->externalCoroutine = true;
						instructionPointer -= 3;
						// PrintDebug("start external coroutine %ld\n", context->c->id);
						goto awaitCommand;
					} else if (context->c->externalCoroutine) {
						context->externalCoroutineCount--;
						context->c->externalCoroutine = false;
						// PrintDebug("end external coroutine %ld\n", context->c->id);
					}

					if (result == 2 || result == 3) {
						if (context->c->stackPointer == context->c->stackEntriesAllocated) {
							PrintDebug("Evaluation stack overflow.\n");
							return -1;
						}

						context->c->stackIsManaged[context->c->stackPointer] = result == 3;
						context->c->stack[context->c->stackPointer++] = returnValue;
					}
				} else {
					return -1;
				}
			}

			context->c->localVariableCount = variableBase + 1;

			if (context->c->backTracePointer) {
				BackTraceItem *item = &context->c->backTrace[context->c->backTracePointer - 1];

				if (command == T_EXTCALL) {
					context->c->backTracePointer--;
					instructionPointer = item->instructionPointer;
					variableBase = item->variableBase;
				}

				if (item->popResult) {
					if (context->c->stackPointer < 1) return -1;
					context->c->stackPointer--;
				} else if (item->assertResult) {
					if (context->c->stackPointer < 1) return -1;
					Value condition = context->c->stack[--context->c->stackPointer];

					if (condition.i == 0) {
						PrintError4(context, instructionPointer - 1, "Return value was false on an asserting function pointer.\n");
						return 0;
					}
				}

				if (command != T_EXTCALL) {
					context->c->backTracePointer--;
					instructionPointer = item->instructionPointer;
					variableBase = item->variableBase;
				}
			} else {
				break;
			}
		} else {
			PrintDebug("Unknown command %d.\n", command);
			return -1;
		}
	}

	if (context->allCoroutines->nextCoroutine || context->allCoroutines->startedByAsync) {
		PrintError3("Script ended with unfinished tasks.\n");
		return false;
	}

	return true;
}

bool ScriptParseOptions(ExecutionContext *context) {
	for (uintptr_t i = 0; i < optionCount; i++) {
		uintptr_t equalsPosition = 0;
		uintptr_t optionLength = 0;

		for (uintptr_t j = 0; options[i][j]; j++) {
			if (options[i][j] == '=') {
				equalsPosition = j;
				break;
			}
		}

		for (uintptr_t j = 0; options[i][j]; j++) {
			optionLength++;
		}

		if (!equalsPosition) {
			PrintError3("Invalid script option passed on command line '%s'.\n", options[i]);
			return false;
		}

		uintptr_t index = 0;
		Node *node = NULL;

		for (uintptr_t j = 0; j < context->rootNode->scope->entryCount; j++) {
			if (context->rootNode->scope->entries[j]->token.textBytes == equalsPosition
					&& 0 == MemoryCompare(context->rootNode->scope->entries[j]->token.text, options[i], equalsPosition)
					&& context->rootNode->scope->entries[j]->type == T_DECLARE) {
				node = context->rootNode->scope->entries[j];
				break;
			}

			if (ScopeIsVariableType(context->rootNode->scope->entries[j])) {
				index++;
			}
		}

		if (!node) {
			continue;
		}

		index += context->functionData->globalVariableOffset;

		if (node->expressionType->type == T_STR) {
			uintptr_t heapIndex = HeapAllocate(context);
			context->heap[heapIndex].type = T_STR;
			context->heap[heapIndex].bytes = optionLength - equalsPosition - 1;
			context->heap[heapIndex].text = (char *) AllocateResize(NULL, context->heap[heapIndex].bytes);
			context->globalVariables[index].i = heapIndex;
			MemoryCopy(context->heap[heapIndex].text, options[i] + equalsPosition + 1, context->heap[heapIndex].bytes);
		} else if (node->expressionType->type == T_INT) {
			// TODO Overflow checking.

			Value v;
			v.i = 0;

			for (uintptr_t j = 0; options[i][j + equalsPosition + 1]; j++) {
				char c = options[i][j + equalsPosition + 1];

				if (c >= '0' && c <= '9') {
					v.i *= 10;
					v.i += c - '0';
				} else {
					PrintError3("Option '%s' should be an integer.\n", options[i]);
					return false;
				}
			}

			context->globalVariables[index] = v;
		} else if (node->expressionType->type == T_BOOL) {
			char c = options[i][equalsPosition + 1];
			bool truthy = c == 't' || c == 'y' || c == '1';
			bool falsey = c == 'f' || c == 'n' || c == '0';

			if (!truthy && !falsey) {
				PrintError3("#option variable '%.*s' should be a boolean value 'true' or 'false'.\n", node->token.textBytes, node->token.text);
				return false;
			}

			context->globalVariables[index].i = truthy ? 1 : 0;
		} else {
			PrintError3("#option variable '%.*s' is not of string, boolean or integer type.\n", node->token.textBytes, node->token.text);
			return false;
		}

		if (optionsMatched[i]) {
			PrintError3("Script option passed on command line '%s' matches multiple #option variables in different modules.\n", options[i]);
			return false;
		}

		optionsMatched[i] = true;
	}

	return true;
}

bool ScriptLoad(Tokenizer tokenizer, ExecutionContext *context, ImportData *importData, bool replMode) {
	Node *previousRootNode = context->rootNode;
	ImportData *previousImportData = context->functionData->importData;

	context->rootNode = replMode ? ParseRootREPL(&tokenizer) : ParseRoot(&tokenizer); 
	context->functionData->importData = tokenizer.module;

	uint8_t b = 0; // Make sure no function can start at 0.
	FunctionBuilderAppend(context->functionData, &b, sizeof(b));
	b = T_AWAIT; // Put a T_AWAIT command at address 1.
	FunctionBuilderAppend(context->functionData, &b, sizeof(b));

	bool success = context->rootNode 
		&& ASTSetScopes(&tokenizer, context, context->rootNode, NULL)
		&& ASTLookupTypeIdentifiers(&tokenizer, context->rootNode)
		&& ASTSetTypes(&tokenizer, context->rootNode)
		&& ASTCheckForReturnStatements(&tokenizer, context->rootNode)
		&& ASTGenerate(&tokenizer, context->rootNode, context)
		&& ScriptParseOptions(context);

	importData->globalVariableOffset = context->functionData->globalVariableOffset;
	importData->rootNode = context->rootNode;
	*importedModulesLink = importData;
	importedModulesLink = &importData->nextImport;

	context->rootNode = previousRootNode;
	context->functionData->importData = previousImportData;

	return success;
}

int ScriptExecute(ExecutionContext *context, ImportData *mainModule) {
	bool optionMatchingError = false;

	for (uintptr_t i = 0; i < optionCount; i++) {
		if (!optionsMatched[i]) {
			PrintError3("Script option passed on command line '%s' does not match any #option variable.\n", options[i]);
			optionMatchingError = true;
		}
	}

	if (optionMatchingError) {
		return 1;
	}

	Node n;
	n.token.textBytes = startFunctionBytes;
	n.token.text = startFunction;
	intptr_t startIndex = ScopeLookupIndex(&n, mainModule->rootNode->scope, true, false);

	if (startIndex == -1) {
		PrintError3("The script does not have a start function '%.*s'.\n", startFunctionBytes, startFunction);
		return 1;
	}

	Node mainFunctionArguments = { 0 };
	mainFunctionArguments.type = T_ARGUMENTS;
	Node mainFunctionReturn = { 0 };
	mainFunctionReturn.type = T_VOID;
	Node mainFunctionType = { 0 };
	mainFunctionType.type = T_FUNCPTR;
	mainFunctionType.firstChild = &mainFunctionArguments;
	mainFunctionArguments.sibling = &mainFunctionReturn;

	if (!ASTMatching(&mainFunctionType, mainModule->rootNode->scope->entries[ScopeLookupIndex(&n, mainModule->rootNode->scope, false, true)]->expressionType)) {
		PrintError3("The start function '%.*s' should take no arguments and return 'void'.\n", startFunctionBytes, startFunction);
		return 1;
	}

	ImportData *module = importedModules;

	while (module) {
		Node n;
		n.token.textBytes = 10;
		n.token.text = "Initialise";
		intptr_t index = ScopeLookupIndex(&n, module->rootNode->scope, true, false);

		if (index != -1) {
			if (!ASTMatching(&mainFunctionType, module->rootNode->scope->entries[ScopeLookupIndex(&n, module->rootNode->scope, false, true)]->expressionType)) {
				PrintError3("The 'Initialise' function in the module '%s' should take no arguments and return 'void'.\n", module->path);
				return 1;
			}

			int result = ScriptExecuteFunction(context->heap[context->globalVariables[index + module->globalVariableOffset].i].lambdaID, context);

			if (result == 0) {
				// A runtime error occurred.
				return 1;
			} else if (result == -1 || context->c->stackPointer != 0) {
				PrintError3("The script was malformed.\n");
				return 1;
			}
		}

		module = module->nextImport;
	}

	int result = ScriptExecuteFunction(context->heap[context->globalVariables[context->functionData->globalVariableOffset + startIndex].i].lambdaID, context);

	if (result == 0) {
		// A runtime error occurred.
		return 1;
	} else if (result == -1 || context->c->stackPointer != 0) {
		PrintError3("The script was malformed.\n");
		return 1;
	}

	return 0;
}

void ScriptFreeCoroutine(CoroutineState *c) {
	AllocateResize(c->waiters, 0);
	AllocateResize(c->waitingOn, 0);
	AllocateResize(c->localVariables, 0);
	AllocateResize(c->localVariableIsManaged, 0);
	AllocateResize(c, 0);
}

void ScriptFree(ExecutionContext *context) {
	ImportData *module = importedModules;

	while (module) {
		if (module->pathBytes != 15 || 0 != MemoryCompare(module->path, "__base_module__", module->pathBytes)) {
			AllocateResize(module->fileData, 0);
		}

		ASTFreeScopes(module->rootNode);
		module = module->nextImport;
	}

	for (uintptr_t i = 1; i < context->heapEntriesAllocated; i++) {
		if (context->heap[i].type != T_ERROR) {
			HeapFreeEntry(context, i);
		}
	}

	CoroutineState *coroutine = context->allCoroutines;

	while (coroutine) {
		CoroutineState *next = coroutine->nextCoroutine;
		ScriptFreeCoroutine(coroutine);
		coroutine = next;
	}

	AllocateResize(context->heap, 0);
	AllocateResize(context->globalVariables, 0);
	AllocateResize(context->globalVariableIsManaged, 0);
	AllocateResize(context->functionData->lineNumbers, 0);
	AllocateResize(context->functionData->data, 0);
	AllocateResize(context->scriptPersistFile, 0);
}

// --------------------------------- Helpers.

void LineNumberLookup(ExecutionContext *context, uint32_t instructionPointer, LineNumber *output) {
	for (uintptr_t i = 0; i < context->functionData->lineNumberCount; i++) {
		if (context->functionData->lineNumbers[i].instructionPointer == instructionPointer) {
			*output = context->functionData->lineNumbers[i];
			return;
		}
	}
}

void PrintBackTrace(ExecutionContext *context, uint32_t instructionPointer, CoroutineState *c, const char *prefix) {
	LineNumber lineNumber = { 0 };
	LineNumberLookup(context, instructionPointer, &lineNumber);

	if (lineNumber.importData) {
		PrintDebug("%s\t%s:%d %s %.*s\n", prefix, lineNumber.importData->path, lineNumber.lineNumber, lineNumber.function ? "in" : "",
				lineNumber.function ? (int) lineNumber.function->textBytes : 0, lineNumber.function ? lineNumber.function->text : "");
	}

	uintptr_t btp = c->backTracePointer;
	uintptr_t minimum = c->startedByAsync ? 1 : 0;

	while (btp > minimum) {
		BackTraceItem *link = &c->backTrace[--btp];
		LineNumberLookup(context, link->instructionPointer - 1, &lineNumber);
		PrintDebug("%s\t%s:%d %s %.*s\n", prefix, lineNumber.importData->path ? lineNumber.importData->path : "??", 
				lineNumber.lineNumber, lineNumber.function ? "in" : "",
				lineNumber.function ? (int) lineNumber.function->textBytes : 0, lineNumber.function ? lineNumber.function->text : "");
	}
}

void PrintLine(ImportData *importData, uintptr_t line) {
	if (!importData) {
		return;
	}
	
	uintptr_t position = 0;

	for (uintptr_t i = 1; i < line; i++) {
		while (position < importData->fileDataBytes) {
			if (((char *) importData->fileData)[position] == '\n') {
				position++;
				break;
			}

			position++;
		}
	}

	uintptr_t length = 0;

	for (uintptr_t i = position; i <= importData->fileDataBytes; i++) {
		if (i == importData->fileDataBytes || ((char *) importData->fileData)[i] == '\n') {
			length = i - position;
			break;
		}
	}

	PrintDebug(">> %.*s\n", (int) length, &((char *) importData->fileData)[position]);
}

int ScriptExecuteFromFile(char *scriptPath, size_t scriptPathBytes, char *fileData, size_t fileDataBytes, bool replMode) {
	Tokenizer tokenizer = { 0 };
	ImportData importData = { 0 };
	importData.path = scriptPath;
	importData.pathBytes = scriptPathBytes;
	importData.fileData = fileData;
	importData.fileDataBytes = fileDataBytes;
	tokenizer.module = &importData;
	tokenizer.line = 1;
	tokenizer.input = fileData;
	tokenizer.inputBytes = fileDataBytes;

	FunctionBuilder builder = { 0 };
	ExecutionContext context = { 0 };
	context.functionData = &builder;
	context.mainModule = &importData;

	context.heapEntriesAllocated = 2;
	context.heap = (HeapEntry *) AllocateResize(NULL, sizeof(HeapEntry) * context.heapEntriesAllocated);
	context.heap[0].type = T_EOF;
	context.heap[1].type = T_ERROR;
	context.heap[1].nextUnusedEntry = 0;
	context.heapFirstUnusedEntry = 1;
	context.c = (CoroutineState *) AllocateResize(0, sizeof(CoroutineState));
	CoroutineState empty = { 0 };
	*context.c = empty;
	context.c->stackEntriesAllocated = sizeof(context.c->stack) / sizeof(context.c->stack[0]);
	context.c->previousCoroutineLink = &context.allCoroutines;
	context.allCoroutines = context.c;

	int result = ScriptLoad(tokenizer, &context, &importData, replMode) ? ScriptExecute(&context, &importData) : 1;
	ScriptFree(&context);

	importedModules = NULL;
	importedModulesLink = &importedModules;

	return result;
}

int ExternalStringSlice(ExecutionContext *context, Value *returnValue) {
	(void) returnValue;
	if (context->c->stackPointer < 3) return -1;
	STACK_POP_STRING(string, bytes);
	uint64_t start = context->c->stack[--context->c->stackPointer].i;
	if (context->c->stackIsManaged[context->c->stackPointer]) return -1;
	uint64_t end = context->c->stack[--context->c->stackPointer].i;
	if (context->c->stackIsManaged[context->c->stackPointer]) return -1;

	if (start > bytes || end > bytes || end < start) {
		PrintError4(context, 0, "The slice range (%ld..%ld) is invalid for the string of length %ld.\n",
				start, end, bytes);
		return 0;
	}

	RETURN_STRING_COPY(string + start, end - start);
	return 3;
}

int ExternalCharacterToByte(ExecutionContext *context, Value *returnValue) {
	(void) returnValue;
	STACK_POP_STRING(entryText, entryBytes);
	returnValue->i = entryBytes ? entryText[0] : -1;
	return 2;
}

// --------------------------------- Platform layer.

#if defined(_WIN32) || defined(__linux__) || defined(__APPLE__)

#ifdef _WIN32
#include <direct.h>
#include <windows.h>
#define getcwd _getcwd
#define popen _popen
#define pclose _pclose
#define setenv(x, y, z) !SetEnvironmentVariable(x, y)
#else
#include <dirent.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <pthread.h>
#include <semaphore.h>
#endif
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

void **fixedAllocationBlocks;
uint8_t *fixedAllocationCurrentBlock;
uintptr_t fixedAllocationCurrentPosition;
size_t fixedAllocationCurrentSize;

sem_t externalCoroutineSemaphore;
pthread_mutex_t externalCoroutineMutex;
CoroutineState *externalCoroutineUnblockedList;

bool systemShellLoggingEnabled = true;

char *scriptSourceDirectory;

DIR *directoryIterator;

char *StringZeroTerminate(const char *text, size_t bytes) {
	char *buffer = malloc(bytes + 1);
	if (!buffer) return NULL;
	memcpy(buffer, text, bytes);
	buffer[bytes] = 0;
	return buffer;
}

void ExternalCoroutineDone(CoroutineState *coroutine) {
#ifdef __linux__
	pthread_mutex_lock(&externalCoroutineMutex);
	coroutine->nextExternalCoroutine = externalCoroutineUnblockedList;
	externalCoroutineUnblockedList = coroutine;
	pthread_mutex_unlock(&externalCoroutineMutex);
	sem_post(&externalCoroutineSemaphore);
#else
	(void) coroutine;
#endif
}

void *SystemShellExecuteThread(void *_coroutine) {
	CoroutineState *coroutine = (CoroutineState *) _coroutine;
	coroutine->externalCoroutineData.i = system((char *) coroutine->externalCoroutineData2) == 0;
	free((char *) coroutine->externalCoroutineData2);
	ExternalCoroutineDone(coroutine);
	return NULL;
}

int ExternalSystemShellExecute(ExecutionContext *context, Value *returnValue) {
	if (context->c->externalCoroutine) {
		*returnValue = context->c->externalCoroutineData;
		return 2;
	}

	STACK_POP_STRING(text, bytes);
	char *temporary = StringZeroTerminate(text, bytes);

	if (temporary) {
		if (systemShellLoggingEnabled) PrintDebug("\033[0;32m%s\033[0m\n", temporary);
		context->c->externalCoroutineData2 = temporary;
#ifdef __linux__
		pthread_t thread;
		pthread_create(&thread, NULL, SystemShellExecuteThread, context->c);
		pthread_detach(thread);
		return 4;
#else
		SystemShellExecuteThread(context->c);
		*returnValue = context->c->externalCoroutineData;
		return 2;
#endif
	} else {
		fprintf(stderr, "Error in ExternalSystemShellExecute: Out of memory.\n");
		returnValue->i = 0;
		return 2;
	}
}

void *SystemShellExecuteWithWorkingDirectoryThread(void *_coroutine) {
	CoroutineState *coroutine = (CoroutineState *) _coroutine;
	int status;
	pid_t p = waitpid(coroutine->externalCoroutineData.i, &status, 0);

	if (p != coroutine->externalCoroutineData.i) {
		fprintf(stderr, "waitpid returned %d\n", p);
		perror("waitpid failed");
		coroutine->externalCoroutineData.i = 0;
	} else {
		coroutine->externalCoroutineData.i = WEXITSTATUS(status) == 0;
	}

	ExternalCoroutineDone(coroutine);
	return NULL;
}

int ExternalSystemShellExecuteWithWorkingDirectory(ExecutionContext *context, Value *returnValue) {
	if (context->c->externalCoroutine) {
		*returnValue = context->c->externalCoroutineData;
		return 2;
	}

	STACK_POP_STRING_2(entryText, entryBytes, entry2Text, entry2Bytes);
	returnValue->i = 0;
	char *temporary = StringZeroTerminate(entryText, entryBytes);
	if (!temporary) return 3;
	char *temporary2 = StringZeroTerminate(entry2Text, entry2Bytes);
	if (!temporary2) return 3;

	if (systemShellLoggingEnabled) PrintDebug("\033[0;32m(%s) %s\033[0m\n", temporary, temporary2);
	
#ifdef __linux__
	pid_t pid = fork();

	if (pid == 0) {
		Assert(chdir(temporary) == 0);
		exit(system(temporary2));
	} else if (pid < 0) {
		PrintDebug("Unable to fork(), got pid = %d, errno = %d.\n", pid, errno);
		returnValue->i = 0;
	} 

	free(temporary);
	free(temporary2);

	if (pid > 0) {
		context->c->externalCoroutineData.i = pid;
		pthread_t thread;
		pthread_create(&thread, NULL, SystemShellExecuteWithWorkingDirectoryThread, context->c);
		pthread_detach(thread);
		return 4;
	}
#else
	char *data = (char *) malloc(10000);

	if (!data || data != getcwd(data, 10000)) {
		PrintError4(context, 0, "Could not get the working directory.\n");
		free(data);
		return 0;
	}

	Assert(chdir(temporary) == 0);
	returnValue->i = system(temporary2) == 0;
	chdir(data);
	free(temporary);
	free(temporary2);
	free(data);
#endif

	return 2;
}

int ExternalSystemShellEvaluate(ExecutionContext *context, Value *returnValue) {
	STACK_POP_STRING(text, bytes);
	char *temporary = StringZeroTerminate(text, bytes);

	if (temporary) {
		FILE *f = popen(temporary, "r");
		
		if (f) {
			char *buffer = NULL;
			size_t position = 0;
			size_t bufferAllocated = 0;

			while (true) {
				if (position == bufferAllocated) {
					bufferAllocated = bufferAllocated ? bufferAllocated * 2 : 64;
					char *reallocated = (char *) realloc(buffer, bufferAllocated);
					if (!reallocated) break;
					buffer = reallocated;
				}

				intptr_t bytesRead = fread(buffer + position, 1, bufferAllocated - position, f);

				if (bytesRead <= 0) {
					break;
				}

				position += bytesRead;
			}

			buffer = (char *) realloc(buffer, position); // Shrink to match the size exactly.
			pclose(f);

			RETURN_STRING_NO_COPY(buffer, position);
		} else {
			returnValue->i = 0;
		}

		free(temporary);
	} else {
		fprintf(stderr, "Error in ExternalSystemShellEvaluate: Out of memory.\n");
		returnValue->i = 0;
	}

	return 3;
}

int ExternalSystemShellEnableLogging(ExecutionContext *context, Value *returnValue) {
	(void) returnValue;
	if (context->c->stackPointer < 1) return -1;
	systemShellLoggingEnabled = context->c->stack[--context->c->stackPointer].i;
	if (context->c->stackIsManaged[context->c->stackPointer]) return -1;
	return 1;
}

int ExternalPrintStdErr(ExecutionContext *context, Value *returnValue) {
	(void) returnValue;
	STACK_POP_STRING(entryText, entryBytes);
	fprintf(stderr, "%.*s", (int) entryBytes, (char *) entryText);
	return 1;
}

int ExternalPrintStdErrWarning(ExecutionContext *context, Value *returnValue) {
	(void) returnValue;
	STACK_POP_STRING(entryText, entryBytes);
	static int coloredOutput = 0;
#ifndef _WIN32
	if (!coloredOutput) coloredOutput = isatty(STDERR_FILENO) ? 2 : 1;
#endif
	fprintf(stderr, coloredOutput == 2 ? "\033[0;33m%.*s\033[0;m" : "%.*s", (int) entryBytes, (char *) entryText);
	return 1;
}

int ExternalPrintStdErrHighlight(ExecutionContext *context, Value *returnValue) {
	(void) returnValue;
	STACK_POP_STRING(entryText, entryBytes);
	static int coloredOutput = 0;
#ifndef _WIN32
	if (!coloredOutput) coloredOutput = isatty(STDERR_FILENO) ? 2 : 1;
#endif
	fprintf(stderr, coloredOutput == 2 ? "\033[0;36m%.*s\033[0;m" : "%.*s", (int) entryBytes, (char *) entryText);
	return 1;
}

int ExternalPathCreateDirectory(ExecutionContext *context, Value *returnValue) {
	(void) returnValue;
	STACK_POP_STRING(entryText, entryBytes);
	returnValue->i = 0;
	if (entryBytes == 0) return 2;
	char *temporary = StringZeroTerminate(entryText, entryBytes);
	if (!temporary) return 2;
#ifdef _WIN32
#pragma message ("ExternalPathCreateDirectory unimplemented")
#else
	returnValue->i = 1;
	if (mkdir(temporary, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) returnValue->i = errno == EEXIST;
#endif
	free(temporary);
	return 2;
}

int ExternalPathDelete(ExecutionContext *context, Value *returnValue) {
	(void) returnValue;
	STACK_POP_STRING(entryText, entryBytes);
	returnValue->i = 0;
	if (entryBytes == 0) return 2;
	char *temporary = StringZeroTerminate(entryText, entryBytes);
	if (!temporary) return 2;
	struct stat s = { 0 };
	bool isDirectory = lstat(temporary, &s) == 0 && S_ISDIR(s.st_mode);
	returnValue->i = isDirectory ? (rmdir(temporary) == 0) : (unlink(temporary) == 0);
	free(temporary);
	return 2;
}

int ExternalPathExists(ExecutionContext *context, Value *returnValue) {
	(void) returnValue;
	STACK_POP_STRING(entryText, entryBytes);
	returnValue->i = 0;
	if (entryBytes == 0) return 2;
	char *temporary = StringZeroTerminate(entryText, entryBytes);
	if (!temporary) return 2;
	struct stat s = { 0 };
	returnValue->i = stat(temporary, &s) == 0;
	free(temporary);
	return 2;
}

int ExternalPathIsFile(ExecutionContext *context, Value *returnValue) {
	(void) returnValue;
	STACK_POP_STRING(entryText, entryBytes);
	returnValue->i = 0;
	if (entryBytes == 0) return 2;
	char *temporary = StringZeroTerminate(entryText, entryBytes);
	if (!temporary) return 2;
	struct stat s = { 0 };
	returnValue->i = lstat(temporary, &s) == 0 && S_ISREG(s.st_mode);
	free(temporary);
	return 2;
}

int ExternalPathIsDirectory(ExecutionContext *context, Value *returnValue) {
	(void) returnValue;
	STACK_POP_STRING(entryText, entryBytes);
	returnValue->i = 0;
	if (entryBytes == 0) return 2;
	char *temporary = StringZeroTerminate(entryText, entryBytes);
	if (!temporary) return 2;
	struct stat s = { 0 };
	returnValue->i = lstat(temporary, &s) == 0 && S_ISDIR(s.st_mode);
	free(temporary);
	return 2;
}

int ExternalPathIsLink(ExecutionContext *context, Value *returnValue) {
	(void) returnValue;
	STACK_POP_STRING(entryText, entryBytes);
	returnValue->i = 0;
	if (entryBytes == 0) return 2;
	char *temporary = StringZeroTerminate(entryText, entryBytes);
	if (!temporary) return 2;
	struct stat s = { 0 };
	returnValue->i = lstat(temporary, &s) == 0 && S_ISLNK(s.st_mode);
	free(temporary);
	return 2;
}

int ExternalPathMove(ExecutionContext *context, Value *returnValue) {
	(void) returnValue;
	STACK_POP_STRING_2(entryText, entryBytes, entry2Text, entry2Bytes);
	returnValue->i = 0;
	if (entryBytes == 0 || entry2Bytes == 0) return 2;
	char *temporary = StringZeroTerminate(entryText, entryBytes);
	if (!temporary) return 2;
	char *temporary2 = StringZeroTerminate(entry2Text, entry2Bytes);
	if (!temporary2) return 2;
	returnValue->i = rename(temporary, temporary2) == 0;
	free(temporary);
	free(temporary2);
	return 2;
}

int ExternalFileCopy(ExecutionContext *context, Value *returnValue) {
	STACK_POP_STRING_2(entryText, entryBytes, entry2Text, entry2Bytes);
	returnValue->i = 0;
	if (entryBytes == 0 || entry2Bytes == 0) return 2;
	char *temporary = StringZeroTerminate(entryText, entryBytes);
	if (!temporary) return 2;
	char *temporary2 = StringZeroTerminate(entry2Text, entry2Bytes);
	if (!temporary2) return 2;
	FILE *f = fopen(temporary, "rb");
	FILE *f2 = fopen(temporary2, "wb");
	free(temporary);
	free(temporary2);
	bool okay = true;

	if (f && f2) {
		char buffer[4096];

		while (true) {
			intptr_t bytesRead = fread(buffer, 1, sizeof(buffer), f);
			if (bytesRead < 0) okay = false;
			if (bytesRead <= 0) break;
			intptr_t bytesWritten = fwrite(buffer, 1, bytesRead, f2);
			if (bytesWritten != bytesRead) okay = false;
		}
	} else okay = false;
	
	if (f && fclose(f)) okay = false;
	if (f2 && fclose(f2)) okay = false;
	returnValue->i = okay;
	return 2;
}

int ExternalSystemGetEnvironmentVariable(ExecutionContext *context, Value *returnValue) {
	STACK_POP_STRING(entryText, entryBytes);
	returnValue->i = 0;
	if (entryBytes == 0) return 3;
	char *temporary = StringZeroTerminate(entryText, entryBytes);
	if (!temporary) return 3;
	char *data = getenv(temporary);
	RETURN_STRING_COPY(data, data ? strlen(data) : 0);
	free(temporary);
	return 3;
}

int ExternalSystemSetEnvironmentVariable(ExecutionContext *context, Value *returnValue) {
	STACK_POP_STRING_2(entryText, entryBytes, entry2Text, entry2Bytes);
	returnValue->i = 0;
	if (entryBytes == 0 || entry2Bytes == 0) return 2;
	char *temporary = StringZeroTerminate(entryText, entryBytes);
	if (!temporary) return 3;
	char *temporary2 = StringZeroTerminate(entry2Text, entry2Bytes);
	if (!temporary2) return 3;
	returnValue->i = setenv(temporary, temporary2, true) == 0;
	free(temporary);
	free(temporary2);
	return 2;
}

int External_DirectoryInternalStartIteration(ExecutionContext *context, Value *returnValue) {
	STACK_POP_STRING(entryText, entryBytes);
	returnValue->i = 0;
	if (entryBytes == 0) return 2;
	char *temporary = StringZeroTerminate(entryText, entryBytes);
	if (!temporary) return 2;
	if (directoryIterator) return 2;
	directoryIterator = opendir(temporary);
	free(temporary);
	returnValue->i = directoryIterator != NULL;
	return 2;
}

int External_DirectoryInternalEndIteration(ExecutionContext *context, Value *returnValue) {
	(void) context;
	(void) returnValue;
	if (!directoryIterator) return 0;
	closedir(directoryIterator);
	directoryIterator = NULL;
	return 1;
}

int External_DirectoryInternalNextIteration(ExecutionContext *context, Value *returnValue) {
	(void) context;
	if (!directoryIterator) return 0;
	struct dirent *entry = readdir(directoryIterator);
	while (entry && (0 == strcmp(entry->d_name, ".") || 0 == strcmp(entry->d_name, ".."))) entry = readdir(directoryIterator);
	if (!entry) { returnValue->i = 0; return 3; }
	RETURN_STRING_COPY(entry->d_name, strlen(entry->d_name));
	return 3;
}

int ExternalFileReadAll(ExecutionContext *context, Value *returnValue) {
	STACK_POP_STRING(entryText, entryBytes);
	returnValue->i = 0;
	if (entryBytes == 0) return 3;
	char *temporary = StringZeroTerminate(entryText, entryBytes);
	if (!temporary) return 3;
	size_t length = 0;
	void *data = FileLoad(temporary, &length);
	RETURN_STRING_NO_COPY(data, length);
	free(temporary);
	return 3;
}

int ExternalFileGetSize(ExecutionContext *context, Value *returnValue) {
	STACK_POP_STRING(entryText, entryBytes);
	returnValue->i = -1;
	char *temporary = StringZeroTerminate(entryText, entryBytes);
	if (!temporary) return 2;
	FILE *file = fopen(temporary, "rb");

	if (file) {
		fseek(file, 0, SEEK_END);
		returnValue->i = ftell(file);
		fclose(file);
	}

	free(temporary);
	return 2;
}

int ExternalFileWriteAll(ExecutionContext *context, Value *returnValue) {
	STACK_POP_STRING_2(entryText, entryBytes, entry2Text, entry2Bytes);
	returnValue->i = 0;
	if (entryBytes == 0) return 2;
	char *temporary = StringZeroTerminate(entryText, entryBytes);
	if (!temporary) return 3;
	FILE *f = fopen(temporary, "wb");

	if (f) {
		returnValue->i = entry2Bytes == fwrite(entry2Text, 1, entry2Bytes, f);
		if (fclose(f)) returnValue->i = 0;
	}

	free(temporary);
	return 2;
}

int ExternalPathGetDefaultPrefix(ExecutionContext *context, Value *returnValue) {
	(void) returnValue;
	char *data = (char *) malloc(10000);

	if (!data || data != getcwd(data, 10000)) {
		PrintError4(context, 0, "Could not get the working directory.\n");
		free(data);
		return 0;
	}

	RETURN_STRING_NO_COPY(realloc(data, strlen(data) + 1), strlen(data));
	return 3;
}

int ExternalPathSetDefaultPrefixToScriptSourceDirectory(ExecutionContext *context, Value *returnValue) {
	(void) context;
	returnValue->i = chdir(scriptSourceDirectory) == 0;
	return 2;
}

int ExternalPersistRead(ExecutionContext *context, Value *returnValue) {
	(void) returnValue;
	STACK_POP_STRING(entryText, entryBytes);
	returnValue->i = 0;
	if (entryBytes == 0) return 2;
	char *temporary = StringZeroTerminate(entryText, entryBytes);
	if (!temporary) return 2;
	free(context->scriptPersistFile);
	context->scriptPersistFile = temporary;
	size_t length = 0;
	uint8_t *data = (uint8_t *) FileLoad(temporary, &length);
	returnValue->i = 1;

	for (uintptr_t i = 0; i < length; ) {
		uint32_t variableNameLength, variableDataLength;
		char variableName[256];
		if (length < sizeof(uint32_t) * 2) break;
		if (i > length - sizeof(uint32_t) * 2) break;
		memcpy(&variableNameLength, &data[i], sizeof(uint32_t)); i += sizeof(uint32_t);
		memcpy(&variableDataLength, &data[i], sizeof(uint32_t)); i += sizeof(uint32_t);
		if (variableNameLength > 256) break;
		if (length < variableNameLength + variableDataLength) break;
		if (i > length - variableNameLength - variableDataLength) break;
		memcpy(variableName, &data[i], variableNameLength); i += variableNameLength;
		uintptr_t k = context->mainModule->globalVariableOffset;
		Scope *scope = context->mainModule->rootNode->scope;

		for (uintptr_t j = 0; j < scope->entryCount; j++) {
			if (scope->entries[j]->token.textBytes == variableNameLength
					&& 0 == MemoryCompare(scope->entries[j]->token.text, variableName, variableNameLength)
					&& scope->entries[j]->type == T_DECLARE
					&& scope->entries[j]->isPersistentVariable) {
				if (scope->entries[j]->expressionType->type == T_STR) {
					// TODO Handling allocation failures.
					context->globalVariables[k].i = HeapAllocate(context);
					context->heap[context->globalVariables[k].i].type = T_STR;
					context->heap[context->globalVariables[k].i].bytes = variableDataLength;
					context->heap[context->globalVariables[k].i].text = AllocateResize(NULL, variableDataLength);
					memcpy(context->heap[context->globalVariables[k].i].text, &data[i], variableDataLength);
				} else if (scope->entries[j]->expressionType->type == T_INT) {
					if (variableDataLength == sizeof(int64_t)) memcpy(&context->globalVariables[k].i, &data[i], sizeof(int64_t));
				} else if (scope->entries[j]->expressionType->type == T_FLOAT) {
					if (variableDataLength == sizeof(double)) memcpy(&context->globalVariables[k].f, &data[i], sizeof(double));
				} else if (scope->entries[j]->expressionType->type == T_BOOL) {
					if (variableDataLength == 1) context->globalVariables[k].i = data[i] == 1;
				} else {
					// TODO What should happen here?
				}

				break;
			}

			if (ScopeIsVariableType(scope->entries[j])) {
				k++;
			}
		}

		i += variableDataLength;
	}

	free(data);
	return 2;
}

int ExternalPersistWrite(ExecutionContext *context, Value *returnValue) {
	(void) returnValue;

	if (!context->scriptPersistFile) {
		// TODO Report the file/line number.
		PrintError3("Attempted to modify a persistent variable before calling PersistRead.\n");
		return 0;
	}

	FILE *f = fopen(context->scriptPersistFile, "wb");

	if (!f) {
		PrintDebug("\033[0;32mWarning: Persistent variables could not written. The file could not be opened.\033[0m\n");
		return 1;
	}

	uintptr_t k = context->mainModule->globalVariableOffset;
	Scope *scope = context->mainModule->rootNode->scope;

	for (uintptr_t j = 0; j < scope->entryCount; j++) {
		if (scope->entries[j]->type == T_DECLARE && scope->entries[j]->isPersistentVariable) {
			uint32_t variableNameLength = scope->entries[j]->token.textBytes;
			fwrite(&variableNameLength, 1, sizeof(uint32_t), f);

			if (scope->entries[j]->expressionType->type == T_STR) {
				HeapEntry *entry = &context->heap[context->globalVariables[k].i];
				const char *text;
				size_t bytes;
				ScriptHeapEntryToString(context, entry, &text, &bytes);
				uint32_t variableDataLength = bytes;
				fwrite(&variableDataLength, 1, sizeof(uint32_t), f);
				fwrite(scope->entries[j]->token.text, 1, variableNameLength, f);
				if (bytes) fwrite(text, 1, variableDataLength, f);
			} else if (scope->entries[j]->expressionType->type == T_INT) {
				uint32_t variableDataLength = sizeof(int64_t);
				fwrite(&variableDataLength, 1, sizeof(uint32_t), f);
				fwrite(scope->entries[j]->token.text, 1, variableNameLength, f);
				fwrite(&context->globalVariables[k].i, 1, sizeof(int64_t), f);
			} else if (scope->entries[j]->expressionType->type == T_FLOAT) {
				uint32_t variableDataLength = sizeof(double);
				fwrite(&variableDataLength, 1, sizeof(uint32_t), f);
				fwrite(scope->entries[j]->token.text, 1, variableNameLength, f);
				fwrite(&context->globalVariables[k].f, 1, sizeof(double), f);
			} else if (scope->entries[j]->expressionType->type == T_BOOL) {
				uint32_t variableDataLength = 1;
				fwrite(&variableDataLength, 1, sizeof(uint32_t), f);
				fwrite(scope->entries[j]->token.text, 1, variableNameLength, f);
				uint8_t b = context->globalVariables[k].i == 1;
				fwrite(&b, 1, sizeof(uint8_t), f);
			} else {
				PrintDebug("\033[0;32mWarning: The persistent variable %.*s could not be written, because it had an unsupported type.\033[0m\n",
						scope->entries[j]->token.textBytes, scope->entries[j]->token.text);
			}
		}

		if (ScopeIsVariableType(scope->entries[j])) {
			k++;
		}
	}

	if (fclose(f)) {
		PrintDebug("\033[0;32mWarning: Persistent variables could not written. The file could not be closed.\033[0m\n");
		return 1;
	}

	return 1;
}

int ExternalConsoleGetLine(ExecutionContext *context, Value *returnValue) {
#ifdef _WIN32
#pragma message ("ExternalConsoleGetLine unimplemented")
	return -1;
#else
	char *line = NULL;
	size_t pos;
	size_t unused = getline(&line, &pos, stdin);
	(void) unused;
	uintptr_t index = HeapAllocate(context);
	context->heap[index].type = T_STR;
	context->heap[index].bytes = strlen(line) - 1;
	context->heap[index].text = line;
	returnValue->i = index;
	return 3;
#endif
}

int ExternalSystemGetProcessorCount(ExecutionContext *context, Value *returnValue) {
	(void) context;
#ifdef _WIN32
#pragma message ("ExternalSystemGetProcessorCount unimplemented")
	returnValue->i = 1;
#else
	returnValue->i = sysconf(_SC_NPROCESSORS_CONF);
#endif
	if (returnValue->i < 1) returnValue->i = 1;
	if (returnValue->i > 10000) returnValue->i = 1; // Values this large are obviously wrong.
	return 2;
}

int ExternalSystemRunningAsAdministrator(ExecutionContext *context, Value *returnValue) {
	(void) context;
#ifdef _WIN32
#pragma message ("ExternalSystemRunningAsAdministrator unimplemented")
	returnValue->i = 0;
#else
	returnValue->i = geteuid() == 0;
#endif
	return 2;
}

int ExternalSystemGetHostName(ExecutionContext *context, Value *returnValue) {
	(void) context;
	const char *name;
#ifdef _WIN32
	name = "Windows";
#else
	struct utsname buffer;
	uname(&buffer);
	name = buffer.sysname;
#endif
	RETURN_STRING_COPY(name, strlen(name));
	return 3;
}

int ExternalRandomInt(ExecutionContext *context, Value *returnValue) {
	if (context->c->stackPointer < 2) return -1;
	int64_t min = context->c->stack[context->c->stackPointer - 1].i;
	int64_t max = context->c->stack[context->c->stackPointer - 2].i;
	if (max < min) { PrintError4(context, 0, "RandomInt() called with maximum limit (%ld) less than the minimum limit (%ld).\n", max, min); return 0; }
	returnValue->i = rand() % (max - min + 1) + min;
	context->c->stackPointer -= 2;
	return 2;
}

void *SystemSleepThread(void *_coroutine) {
	CoroutineState *coroutine = (CoroutineState *) _coroutine;
	struct timespec sleepTime;
	uint64_t x = 1000000 * coroutine->externalCoroutineData.i;
	sleepTime.tv_sec = x / 1000000000;
	sleepTime.tv_nsec = x % 1000000000;
	nanosleep(&sleepTime, NULL);
	ExternalCoroutineDone(coroutine);
	return NULL;
}

int ExternalSystemSleepMs(ExecutionContext *context, Value *returnValue) {
	(void) returnValue;
	if (context->c->externalCoroutine) return 1;
	if (context->c->stackPointer < 1) return -1;
	context->c->externalCoroutineData = context->c->stack[--context->c->stackPointer];
#ifdef __linux__
	pthread_t thread;
	pthread_create(&thread, NULL, SystemSleepThread, context->c);
	pthread_detach(thread);
	return 4;
#else
	struct timespec sleepTime;
	uint64_t x = 1000000 * context->c->externalCoroutineData.i;
	sleepTime.tv_sec = x / 1000000000;
	sleepTime.tv_nsec = x % 1000000000;
	nanosleep(&sleepTime, NULL);
	return 1;
#endif
}

int ExternalOpenDocumentEnumerate(ExecutionContext *context, Value *returnValue) {
	(void) context;
	returnValue->i = 0;
	return 3;
}

CoroutineState *ExternalCoroutineWaitAny(ExecutionContext *context) {
	(void) context;
	while (sem_wait(&externalCoroutineSemaphore) == -1);
	pthread_mutex_lock(&externalCoroutineMutex);
	CoroutineState *unblocked = externalCoroutineUnblockedList;
	Assert(unblocked);
	externalCoroutineUnblockedList = unblocked->nextExternalCoroutine;
	unblocked->nextExternalCoroutine = NULL;
	pthread_mutex_unlock(&externalCoroutineMutex);
	return unblocked;
}

void ExternalPassREPLResult(ExecutionContext *context, Value value) {
	(void) context;
	(void) value;
	Assert(false);
}

void *AllocateFixed(size_t bytes) {
	if (!bytes) {
		return NULL;
	}

	bytes = (bytes + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1);

	if (bytes >= fixedAllocationCurrentSize || fixedAllocationCurrentPosition >= fixedAllocationCurrentSize - bytes) {
#if 1
		fixedAllocationCurrentSize = bytes > 1048576 ? bytes : 1048576;
#else
		fixedAllocationCurrentSize = bytes;
#endif
		fixedAllocationCurrentPosition = 0;
		fixedAllocationCurrentBlock = calloc(1, fixedAllocationCurrentSize + sizeof(void *));

		if (!fixedAllocationCurrentBlock) {
			fprintf(stderr, "Internal error: not enough memory to run the script.\n");
			exit(1);
		}

		*(void **) fixedAllocationCurrentBlock = fixedAllocationBlocks;
		fixedAllocationBlocks = (void **) fixedAllocationCurrentBlock;
		fixedAllocationCurrentBlock += sizeof(void *);
	}

	void *p = fixedAllocationCurrentBlock + fixedAllocationCurrentPosition;
	fixedAllocationCurrentPosition += bytes;
	return p;
}

void *AllocateResize(void *old, size_t bytes) {
	if (bytes == 0) {
		free(old);
		return NULL;
	}

	void *p = realloc(old, bytes);

	if (!p && bytes) {
		fprintf(stderr, "Internal error: not enough memory to run the script.\n");
		exit(1);
		// TODO Better error handling.
	}

	return p;
}

int MemoryCompare(const void *a, const void *b, size_t bytes) {
	return memcmp(a, b, bytes);
}

void MemoryCopy(void *a, const void *b, size_t bytes) {
	memcpy(a, b, bytes);
}

size_t PrintIntegerToBuffer(char *buffer, size_t bufferBytes, int64_t i) {
	snprintf(buffer, bufferBytes, "%ld", i);
	return strlen(buffer);
}

size_t PrintFloatToBuffer(char *buffer, size_t bufferBytes, double f) {
	snprintf(buffer, bufferBytes, "%f", f);
	return strlen(buffer);
}

void PrintDebug(const char *format, ...) {
	va_list arguments;
	va_start(arguments, format);
	vfprintf(stderr, format, arguments);
	va_end(arguments);
}

void PrintError(Tokenizer *tokenizer, const char *format, ...) {
	fprintf(stderr, "\033[0;33mError on line %d of '%s':\033[0m\n", (int) tokenizer->line, tokenizer->module->path);
	va_list arguments;
	va_start(arguments, format);
	vfprintf(stderr, format, arguments);
	va_end(arguments);
	PrintLine(tokenizer->module, tokenizer->line);
}

void PrintError2(Tokenizer *tokenizer, Node *node, const char *format, ...) {
	fprintf(stderr, "\033[0;33mError on line %d of '%s':\033[0m\n", (int) node->token.line, tokenizer->module->path);
	va_list arguments;
	va_start(arguments, format);
	vfprintf(stderr, format, arguments);
	va_end(arguments);
	PrintLine(tokenizer->module, node->token.line);
}

void PrintError3(const char *format, ...) {
	fprintf(stderr, "\033[0;33mGeneral error:\033[0m\n");
	va_list arguments;
	va_start(arguments, format);
	vfprintf(stderr, format, arguments);
	va_end(arguments);
}

void PrintError4(ExecutionContext *context, uint32_t instructionPointer, const char *format, ...) {
	LineNumber lineNumber = { 0 };
	LineNumberLookup(context, instructionPointer, &lineNumber);
	fprintf(stderr, "\033[0;33mRuntime error on line %d of '%s'\033[0m:\n", lineNumber.lineNumber, 
			lineNumber.importData ? lineNumber.importData->path : "??");
	va_list arguments;
	va_start(arguments, format);
	vfprintf(stderr, format, arguments);
	va_end(arguments);
	PrintLine(lineNumber.importData, lineNumber.lineNumber);
	fprintf(stderr, "Back trace:\n");
	PrintBackTrace(context, instructionPointer, context->c, "");
}

void *FileLoad(const char *path, size_t *length) {
	FILE *file = fopen(path, "rb");
	if (!file) return NULL;
	fseek(file, 0, SEEK_END);
	size_t fileSize = ftell(file);
	fseek(file, 0, SEEK_SET);
	char *buffer = (char *) malloc(fileSize + 1);
	buffer[fileSize] = 0;
	size_t readBytes = fread(buffer, 1, fileSize, file);
	fclose(file);
	if (readBytes != fileSize) { free(buffer); return NULL; }
	if (length) *length = fileSize;
	return buffer;
}

int main(int argc, char **argv) {
	if (argc < 2) {
		fprintf(stderr, "Usage: %s <engine options...> <path to script> <script options...>\n", argv[0]);
		return 1;
	}

	srand(time(NULL));

	sem_init(&externalCoroutineSemaphore, 0, 0);

	char *scriptPath = NULL;

	for (int i = 1; i < argc; i++) {
		if (argv[i][0] != '-') {
			scriptPath = argv[i];
			options = argv + i + 1;
			optionCount = argc - i - 1;
			optionsMatched = (bool *) calloc(argc, sizeof(bool));
			break;
		} else if (0 == memcmp(argv[i], "--start=", 8)) {
			startFunction = argv[i] + 8;
			startFunctionBytes = strlen(argv[i]) - 8;
		}
	}

	scriptSourceDirectory = (char *) malloc(strlen(scriptPath) + 2);
	strcpy(scriptSourceDirectory, scriptPath);
	char *lastSlash = strrchr(scriptSourceDirectory, '/');
	if (lastSlash) *lastSlash = 0;
	else strcpy(scriptSourceDirectory, ".");

	size_t dataBytes;
	void *data = FileLoad(scriptPath, &dataBytes);

	if (!data) {
		PrintDebug("Error: Could not load the input file '%s'.\n", scriptPath);
		return 1;
	}

	int result = ScriptExecuteFromFile(scriptPath, strlen(scriptPath), data, dataBytes, false);

	while (fixedAllocationBlocks) {
		void *block = fixedAllocationBlocks;
		fixedAllocationBlocks = (void **) *fixedAllocationBlocks;
		free(block);
	}

	free(scriptSourceDirectory);
	free(optionsMatched);

	return result;
}

#endif