// TODO Bug: Structure fields cannot have names the same as global variables or as in other structures. // TODO Basic missing features: // - Other list operations: insert_many, delete_many. // - Maps: map[int, T], map[str, T]. // - 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. // - For each syntax: for type identifier; list block // 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 #include #include #define FUNCTION_MAX_ARGUMENTS (20) // Also the maximum number of return values in a tuple. #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_RETURN_TUPLE (96) #define T_DECLARE_GROUP (97) #define T_DECL_GROUP_AND_SET (98) #define T_SET_GROUP (99) #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_OP_FIND (156) #define T_OP_FIND_AND_DEL_STR (157) #define T_OP_FIND_STR (158) #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 T_TUPLE (183) #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 #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 Log(str x) #extcall;" "void LogInfo(str x) { Log(TextColorHighlight() + x); }" "void LogError(str x) { Log(TextColorError() + \"Error: \" + x); }" "str TextColorError() #extcall;" "str TextColorHighlight() #extcall;" "str TextWeight(int i) #extcall;" "str TextMonospaced() #extcall;" "str TextPlain() #extcall;" "str TextBold() { return TextWeight(700); }" // 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 FileAppend(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 ExternalLog(ExecutionContext *context, Value *returnValue); int ExternalTextColorError(ExecutionContext *context, Value *returnValue); int ExternalTextColorHighlight(ExecutionContext *context, Value *returnValue); int ExternalTextWeight(ExecutionContext *context, Value *returnValue); int ExternalTextMonospaced(ExecutionContext *context, Value *returnValue); int ExternalTextPlain(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 ExternalFileAppend(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 = "Log", .callback = ExternalLog }, { .cName = "TextColorError", .callback = ExternalTextColorError }, { .cName = "TextColorHighlight", .callback = ExternalTextColorHighlight }, { .cName = "TextWeight", .callback = ExternalTextWeight }, { .cName = "TextMonospaced", .callback = ExternalTextMonospaced }, { .cName = "TextPlain", .callback = ExternalTextPlain }, { .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 = "FileAppend", .callback = ExternalFileAppend }, { .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_COMMA) return 12; 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("tuple") token.type = T_TUPLE; 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(©); } Node *ParseType(Tokenizer *tokenizer, bool maybe, bool allowVoid, bool allowTuple) { 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_TUPLE || 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; } if (!allowTuple && node->type == T_TUPLE) { if (!maybe) { PrintError2(tokenizer, node, "The 'tuple' type is not allowed here.\n"); } return NULL; } if (node->type == T_TUPLE) { Token token = TokenNext(tokenizer); if (token.type != T_LEFT_SQUARE) { if (!maybe) PrintError2(tokenizer, node, "Expected a '[' after 'tuple'.\n"); return NULL; } Node **link = &node->firstChild; bool needComma = false; int count = 0; while (true) { token = TokenPeek(tokenizer); if (token.type == T_RIGHT_SQUARE) { TokenNext(tokenizer); break; } else if (needComma) { if (token.type == T_COMMA) { TokenNext(tokenizer); } else { if (!maybe) PrintError2(tokenizer, node, "Expected a ',' or ']' in the type list for 'tuple'.\n"); return NULL; } } if (count == FUNCTION_MAX_ARGUMENTS) { if (!maybe) PrintError2(tokenizer, node, "Too many values in the tuple (maximum is %d).\n", FUNCTION_MAX_ARGUMENTS); return NULL; } Node *n = ParseType(tokenizer, maybe, false, false); if (!n) return NULL; *link = n; link = &n->sibling; needComma = true; count++; } if (!node->firstChild || !node->firstChild->sibling) { if (!maybe) PrintError2(tokenizer, node, "A tuple must have at least two types in its list.\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', 'tuple', 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 /* allow void */, false /* allow tuple */); 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 if (token.type == T_COMMA && TokenLookupPrecedence(token.type) >= precedence && allowAssignment) { Node *operation = (Node *) AllocateFixed(sizeof(Node)); operation->token = TokenNext(tokenizer); operation->type = T_SET_GROUP; operation->firstChild = node; node->sibling = ParseExpression(tokenizer, true, TokenLookupPrecedence(token.type)); if (!node->sibling) return NULL; node = operation; } 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 /* allow void */, false /* allow tuple */) && TokenNext(tokenizer).type == T_IDENTIFIER) { Token token = TokenNext(tokenizer); isVariableDeclaration = token.type == T_EQUALS || token.type == T_SEMICOLON || token.type == T_COMMA; } 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 /* allow void */, false /* allow tuple */); Assert(declaration->firstChild); declaration->token = TokenNext(tokenizer); Assert(declaration->token.type == T_IDENTIFIER); Token token = TokenNext(tokenizer); Assert(token.type == T_EQUALS || token.type == T_SEMICOLON || token.type == T_COMMA); if (token.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; } } else if (token.type == T_COMMA) { Node *group = (Node *) AllocateFixed(sizeof(Node)); group->type = T_DECLARE_GROUP; group->firstChild = declaration; group->token = declaration->token; Node **link = &declaration->sibling; declaration = group; while (true) { Node *declaration = (Node *) AllocateFixed(sizeof(Node)); *link = declaration; link = &declaration->sibling; declaration->type = T_DECLARE; declaration->firstChild = ParseType(tokenizer, false, false /* allow void */, false /* allow tuple */); if (!declaration->firstChild) return NULL; declaration->token = TokenNext(tokenizer); if (declaration->token.type != T_IDENTIFIER) { PrintError2(tokenizer, declaration, "Expected an identifier for the variable name.\n"); return NULL; } Token token = TokenNext(tokenizer); if (token.type == T_ERROR) { return NULL; } else if (token.type == T_COMMA) { // Keep going... } else if (token.type == T_SEMICOLON) { break; } else if (token.type == T_EQUALS) { group->type = T_DECL_GROUP_AND_SET; *link = ParseExpression(tokenizer, false, 0); if (!(*link)) return NULL; Token semicolon = TokenNext(tokenizer); if (semicolon.type != T_SEMICOLON) { PrintError2(tokenizer, declaration, "Expected a semicolon at the end of the variable declaration group.\n"); return NULL; } break; } else { PrintError2(tokenizer, declaration, "Expected one of ',', '=' or ';' in the variable declaration group.\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); if (token.type == T_ASSERT || TokenPeek(tokenizer).type != T_SEMICOLON) { node->firstChild = ParseExpression(tokenizer, false, 0); if (!node->firstChild) return NULL; if (token.type == T_RETURN && TokenPeek(tokenizer).type == T_COMMA) { Node *n = node; node = (Node *) AllocateFixed(sizeof(Node)); node->type = T_RETURN_TUPLE; node->firstChild = n->firstChild; Node **argumentLink = &n->firstChild->sibling; while (TokenPeek(tokenizer).type == T_COMMA) { TokenNext(tokenizer); n = ParseExpression(tokenizer, false, 0); if (!n) return NULL; *argumentLink = n; argumentLink = &n->sibling; } } } *link = node; link = &node->sibling; 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 /* allow void */, true /* allow tuple */); 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 /* allow void */, false /* allow tuple */); if (!argument->firstChild) return NULL; argument->token = TokenNext(tokenizer); *link = argument; link = &argument->sibling; argumentCount++; 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) { // ScriptPrintNode(root, 0); 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, false /* allow void */, false /* allow tuple */); 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; } int ASTGetTypePopCount(Node *node) { if (node->type == T_TUPLE) { int count = 0; Node *child = node->firstChild; while (child) { count++; child = child->sibling; } return count; } else { return 1; } } 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_TUPLE || 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 || node->type == T_DECLARE_GROUP) { } 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)) { PrintError2(tokenizer, node, "The type of the variable being assigned does not match the expression.\n"); return false; } } else if (node->type == T_DECL_GROUP_AND_SET) { Node *lastChild = node->firstChild; while (lastChild->sibling) lastChild = lastChild->sibling; Node *tuple = lastChild->expressionType; if (tuple->type != T_TUPLE) { PrintError2(tokenizer, node, "You can only use '=' in the declaration group with a function that returns a tuple.\n"); return false; } Node *child = node->firstChild; Node *item = tuple->firstChild; int index = 1; while (child->sibling && item) { if (!ASTMatching(child->expressionType, item)) { PrintError2(tokenizer, node, "The type of value %d in the tuple does not match the declaration type.\n", index); return false; } child = child->sibling; item = item->sibling; index++; } if ((!item && child->sibling) || (item && !child->sibling)) { PrintError2(tokenizer, node, "The number of declarations in the group does not match the number of values in the tuple.\n"); return false; } } else if (node->type == T_SET_GROUP) { if (node->parent->type == T_SET_GROUP) { // Reattach our children at the end of the parent's children. Assert(!node->sibling); Node *child = node->parent->firstChild; while (child->sibling != node) child = child->sibling; child->sibling = node->firstChild; } else if (node->parent->type == T_EQUALS) { Node *expressionType = (Node *) AllocateFixed(sizeof(Node)); expressionType->type = T_TUPLE; node->expressionType = expressionType; Node *child = node->firstChild; Node **link = &expressionType->firstChild; while (child) { Node *copy = (Node *) AllocateFixed(sizeof(Node)); *copy = *child->expressionType; *link = copy; link = &(*link)->sibling; child = child->sibling; } *link = NULL; } else { PrintError2(tokenizer, node, "A comma separated list of expressions can only be used to assign a tuple return value to variables.\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_RETURN_TUPLE) { Node *expressionType = (Node *) AllocateFixed(sizeof(Node)); expressionType->type = T_TUPLE; Node *child = node->firstChild; Node **link = &expressionType->firstChild; while (child) { Node *copy = (Node *) AllocateFixed(sizeof(Node)); *copy = *child->expressionType; *link = copy; link = &(*link)->sibling; child = child->sibling; } *link = NULL; Node *function = node->parent; while (function->type != T_FUNCTION) { function = function->parent; } Node *returnType = function->firstChild->firstChild->sibling; if (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("find")) arguments[0] = expressionType->firstChild, op = T_OP_FIND, returnsInt = 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; } if (expressionType->firstChild->sibling && expressionType->firstChild->sibling->type == T_TUPLE) { // TODO Remove this restriction? PrintError2(tokenizer, node, "The discard operation cannot be used on a function returning a tuple.\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; } else if (op == T_OP_FIND && ASTMatching(arguments[0], &globalExpressionTypeFloat)) { PrintError2(tokenizer, node, "The 'find' operation cannot be used with floats.\n"); return false; } if (op == T_OP_FIND_AND_DELETE && ASTMatching(arguments[0], &globalExpressionTypeStr)) { op = T_OP_FIND_AND_DEL_STR; } else if (op == T_OP_FIND && ASTMatching(arguments[0], &globalExpressionTypeStr)) { op = T_OP_FIND_STR; } 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 || lastStatement->type == T_RETURN_TUPLE)) { 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 || node->type == T_DECL_GROUP_AND_SET) { if (node->firstChild->sibling) { Node *variables[FUNCTION_MAX_ARGUMENTS]; uintptr_t variableCount = 0; bool setGroup = node->type == T_EQUALS && node->firstChild->type == T_SET_GROUP; Node *lastChild = setGroup ? node->firstChild->firstChild : node->firstChild; while (setGroup ? lastChild : lastChild->sibling) { Assert(variableCount != FUNCTION_MAX_ARGUMENTS); variables[variableCount++] = lastChild; lastChild = lastChild->sibling; } if (!FunctionBuilderRecurse(tokenizer, setGroup ? node->firstChild->sibling : lastChild, builder, false)) { return false; } while (variableCount) { Node *child = variables[--variableCount]; builder->isPersistentVariable = false; if (node->type == T_DECLARE || node->type == T_DECL_GROUP_AND_SET) { if (!FunctionBuilderVariable(tokenizer, builder, node->type == T_DECL_GROUP_AND_SET ? child : node, true)) { return false; } } else if (!FunctionBuilderRecurse(tokenizer, child, 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)); } if (builder->isPersistentVariable) { b = T_PERSIST; FunctionBuilderAppend(builder, &b, sizeof(b)); } child = child->sibling; } } 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; for (int i = 0; i < ASTGetTypePopCount(increment->expressionType); i++) { 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) || (child->type == T_COLON && child->operationType == T_OP_FIND_AND_DEL_STR)) { uint8_t b = T_POP; for (int i = 0; i < ASTGetTypePopCount(child->expressionType); i++) { 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_DECLARE_GROUP) { } else if (node->type == T_RETURN || node->type == T_RETURN_TUPLE) { 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 || command == T_OP_FIND || command == T_OP_FIND_AND_DEL_STR || command == T_OP_FIND_STR) { 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; if ((command == T_OP_FIND_STR || command == T_OP_FIND_AND_DEL_STR) && !entry->internalValuesAreManaged) return -1; context->c->stack[context->c->stackPointer - 2].i = command == T_OP_FIND || command == T_OP_FIND_STR ? -1 : 0; for (uintptr_t i = 0; i < entry->length; i++) { if (command == T_OP_FIND_STR || command == T_OP_FIND_AND_DEL_STR) { const char *text1, *text2; size_t bytes1, bytes2; ScriptHeapEntryToString(context, &context->heap[entry->list[i].i], &text1, &bytes1); ScriptHeapEntryToString(context, &context->heap[context->c->stack[context->c->stackPointer - 1].i], &text2, &bytes2); bool equal = bytes1 == bytes2 && 0 == MemoryCompare(text1, text2, bytes1); if (!equal) continue; } else { bool equal = entry->list[i].i == context->c->stack[context->c->stackPointer - 1].i; if (!equal) continue; } if (command == T_OP_FIND || command == T_OP_FIND_STR) { context->c->stack[context->c->stackPointer - 2].i = i; } else { context->c->stack[context->c->stackPointer - 2].i = 1; entry->length--; for (uintptr_t j = i; j < entry->length; j++) { entry->list[j] = entry->list[j + 1]; } } break; } 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 #include #define getcwd _getcwd #define popen _popen #define pclose _pclose #define setenv(x, y, z) !SetEnvironmentVariable(x, y) #else #include #include #include #include #include #include #endif #include #include #include #include #include #include void **fixedAllocationBlocks; uint8_t *fixedAllocationCurrentBlock; uintptr_t fixedAllocationCurrentPosition; size_t fixedAllocationCurrentSize; sem_t externalCoroutineSemaphore; pthread_mutex_t externalCoroutineMutex; CoroutineState *externalCoroutineUnblockedList; bool systemShellLoggingEnabled = true; bool coloredOutput; 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 ExternalLog(ExecutionContext *context, Value *returnValue) { STACK_POP_STRING(entryText, entryBytes); fprintf(stderr, "%.*s", (int) entryBytes, (char *) entryText); fprintf(stderr, coloredOutput ? "\033[0;m\n" : "\n"); return 1; } int ExternalTextFormat(ExecutionContext *context, Value *returnValue, const char *mode) { if (coloredOutput) { char *buffer = malloc(32); uintptr_t index = HeapAllocate(context); context->heap[index].type = T_STR; context->heap[index].bytes = sprintf(buffer, "\033[0;%sm", mode); context->heap[index].text = buffer; returnValue->i = index; } else { returnValue->i = 0; } return 3; } int ExternalTextColorError (ExecutionContext *context, Value *returnValue) { return ExternalTextFormat(context, returnValue, "31"); } int ExternalTextColorHighlight(ExecutionContext *context, Value *returnValue) { return ExternalTextFormat(context, returnValue, "36"); } int ExternalTextMonospaced (ExecutionContext *context, Value *returnValue) { return ExternalTextFormat(context, returnValue, ""); } int ExternalTextPlain (ExecutionContext *context, Value *returnValue) { return ExternalTextFormat(context, returnValue, ""); } int ExternalTextWeight(ExecutionContext *context, Value *returnValue) { if (context->c->stackPointer < 1) return -1; return ExternalTextFormat(context, returnValue, context->c->stack[--context->c->stackPointer].i > 500 ? "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 ExternalFileAppend(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, "ab"); 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