// TODO StringJoin is extremely slow and uses a lot of memory. // Add a StringBuilder? Or maybe a T_CONCAT heap object? // TODO Basic missing features: // - Other list operations: insert_many, delete, delete_many, delete_last. // - Maps: T[int], T[str]. // - Control flow: break, continue. // - Other operators: remainder, bitwise shifts, unary minus, bitwise AND/OR/XOR/NOT, ternary. // - Enums, bitsets. // - Resolving type identifiers when structs or function pointers contain references to other structs or function pointers. // 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 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_LEFT_ROUND (44) #define T_RIGHT_ROUND (45) #define T_LEFT_SQUARE (46) #define T_RIGHT_SQUARE (47) #define T_LEFT_FANCY (48) #define T_RIGHT_FANCY (49) #define T_COMMA (50) #define T_EQUALS (51) #define T_SEMICOLON (52) #define T_GREATER_THAN (53) #define T_LESS_THAN (54) #define T_GT_OR_EQUAL (55) #define T_LT_OR_EQUAL (56) #define T_DOUBLE_EQUALS (57) #define T_NOT_EQUALS (58) #define T_LOGICAL_AND (59) #define T_LOGICAL_OR (60) #define T_ADD_EQUALS (61) #define T_MINUS_EQUALS (62) #define T_ASTERISK_EQUALS (63) #define T_SLASH_EQUALS (64) #define T_DOT (65) #define T_COLON (66) #define T_LOGICAL_NOT (67) #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_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_GREATER_THAN (124) #define T_FLOAT_LESS_THAN (125) #define T_FLOAT_GT_OR_EQUAL (126) #define T_FLOAT_LT_OR_EQUAL (127) #define T_FLOAT_DOUBLE_EQUALS (128) #define T_FLOAT_NOT_EQUALS (129) #define T_STR_DOUBLE_EQUALS (130) #define T_STR_NOT_EQUALS (131) #define T_EQUALS_DOT (132) #define T_EQUALS_LIST (133) #define T_INDEX_LIST (134) #define T_OP_RESIZE (140) #define T_OP_ADD (141) #define T_OP_INSERT (142) #define T_OP_INSERT_MANY (143) #define T_OP_DELETE (144) #define T_OP_DELETE_MANY (145) #define T_OP_DELETE_LAST (146) #define T_OP_DELETE_ALL (147) #define T_OP_FIRST (148) #define T_OP_LAST (149) #define T_OP_LEN (150) #define T_OP_DISCARD (151) #define T_OP_ASSERT (152) #define T_OP_CURRY (153) #define T_OP_ASYNC (154) #define T_OP_FIND_AND_DELETE (155) #define T_IF (160) #define T_WHILE (161) #define T_FOR (162) #define T_INT (163) #define T_FLOAT (164) #define T_BOOL (165) #define T_VOID (166) #define T_RETURN (167) #define T_ELSE (168) #define T_EXTCALL (169) #define T_STR (170) #define T_FUNCTYPE (171) #define T_NULL (172) #define T_FALSE (173) #define T_TRUE (174) #define T_ASSERT (175) #define T_PERSIST (176) #define T_STRUCT (177) #define T_NEW (178) #define T_OPTION (179) #define T_IMPORT (180) #define T_INLINE (181) #define T_AWAIT (182) 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. } 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; }; }; } 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 **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: char *scriptSourceDirectory; char **options; bool *optionsMatched; size_t optionCount; ImportData *importedModules; ImportData **importedModulesLink = &importedModules; // Forward declarations: Node *ParseBlock(Tokenizer *tokenizer); Node *ParseExpression(Tokenizer *tokenizer, bool allowAssignment, uint8_t precedence); void ScriptPrintNode(Node *node, int indent); bool ScriptLoad(Tokenizer tokenizer, ExecutionContext *context, ImportData *importData); void ScriptFreeCoroutine(CoroutineState *c); uintptr_t HeapAllocate(ExecutionContext *context); // --------------------------------- Platform layer definitions. #include #define Assert assert 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); // --------------------------------- Base module. char baseModuleSource[] = { // Logging: "void PrintStdErr(str x) #extcall;" "void PrintStdErrWarning(str x) #extcall;" "void PrintStdErrHighlight(str x) #extcall;" // String operations: "str StringTrim(str x) #extcall;" "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[] 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;" // File system access: "bool PathExists(str x) #extcall;" "bool PathCreateDirectory(str x) #extcall;" // TODO Replace the return value with a enum. "bool PathCreateLeadingDirectories(str x) #extcall;" "bool PathDelete(str x) #extcall;" // TODO Replace the return value with a enum. "bool PathDeleteRecursively(str x) #extcall;" "bool PathMove(str source, str destination) #extcall;" "str PathGetDefaultPrefix() #extcall;" "bool PathSetDefaultPrefixToScriptSourceDirectory() #extcall;" "str FileReadAll(str path) #extcall;" // TODO Returning an error? "bool FileWriteAll(str path, str x) #extcall;" // TODO Returning an error? "bool FileCopy(str source, str destination) #extcall;" // Persistent variables: "bool PersistRead(str path) #extcall;" // Command line: "str ConsoleGetLine() #extcall;" "str SystemGetEnvironmentVariable(str name) #extcall;" "bool SystemSetEnvironmentVariable(str name, str value) #extcall;" "bool SystemShellExecute(str x) #extcall;" // Returns true on success. "bool SystemShellExecuteWithWorkingDirectory(str wd, str x) #extcall;" // Returns true on success. "str SystemShellEvaluate(str x) #extcall;" "void SystemShellEnableLogging(bool x) #extcall;" }; // --------------------------------- External function calls. int ExternalPrintStdErr(ExecutionContext *context, Value *returnValue); int ExternalPrintStdErrWarning(ExecutionContext *context, Value *returnValue); int ExternalPrintStdErrHighlight(ExecutionContext *context, Value *returnValue); int ExternalConsoleGetLine(ExecutionContext *context, Value *returnValue); int ExternalStringTrim(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 ExternalPathCreateDirectory(ExecutionContext *context, Value *returnValue); int ExternalPathCreateLeadingDirectories(ExecutionContext *context, Value *returnValue); int ExternalPathDelete(ExecutionContext *context, Value *returnValue); int ExternalPathDeleteRecursively(ExecutionContext *context, Value *returnValue); int ExternalPathExists(ExecutionContext *context, Value *returnValue); int ExternalPathMove(ExecutionContext *context, Value *returnValue); int ExternalPathGetDefaultPrefix(ExecutionContext *context, Value *returnValue); int ExternalPathSetDefaultPrefixToScriptSourceDirectory(ExecutionContext *context, Value *returnValue); int ExternalFileReadAll(ExecutionContext *context, Value *returnValue); int ExternalFileWriteAll(ExecutionContext *context, Value *returnValue); int ExternalFileCopy(ExecutionContext *context, Value *returnValue); int ExternalPersistRead(ExecutionContext *context, Value *returnValue); int ExternalPersistWrite(ExecutionContext *context, Value *returnValue); ExternalFunction externalFunctions[] = { { .cName = "PrintStdErr", .callback = ExternalPrintStdErr }, { .cName = "PrintStdErrWarning", .callback = ExternalPrintStdErrWarning }, { .cName = "PrintStdErrHighlight", .callback = ExternalPrintStdErrHighlight }, { .cName = "ConsoleGetLine", .callback = ExternalConsoleGetLine }, { .cName = "StringTrim", .callback = ExternalStringTrim }, { .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 = "PathExists", .callback = ExternalPathExists }, { .cName = "PathCreateDirectory", .callback = ExternalPathCreateDirectory }, { .cName = "PathCreateLeadingDirectories", .callback = ExternalPathCreateLeadingDirectories }, { .cName = "PathDelete", .callback = ExternalPathDelete }, { .cName = "PathDeleteRecursively", .callback = ExternalPathDeleteRecursively }, { .cName = "PathMove", .callback = ExternalPathMove }, { .cName = "PathGetDefaultPrefix", .callback = ExternalPathGetDefaultPrefix }, { .cName = "PathSetDefaultPrefixToScriptSourceDirectory", .callback = ExternalPathSetDefaultPrefixToScriptSourceDirectory }, { .cName = "FileReadAll", .callback = ExternalFileReadAll }, { .cName = "FileWriteAll", .callback = ExternalFileWriteAll }, { .cName = "FileCopy", .callback = ExternalFileCopy }, { .cName = "PersistRead", .callback = ExternalPersistRead }, { .cName = "PersistWrite", .callback = ExternalPersistWrite }, }; // --------------------------------- Tokenization and parsing. uint8_t TokenLookupPrecedence(uint8_t t) { if (t == T_EQUALS) return 10; if (t == T_ADD_EQUALS) return 10; if (t == T_MINUS_EQUALS) return 10; if (t == T_ASTERISK_EQUALS) return 10; if (t == T_SLASH_EQUALS) return 10; if (t == T_LOGICAL_OR) return 14; if (t == T_LOGICAL_AND) return 15; if (t == T_GREATER_THAN) return 20; if (t == T_LESS_THAN) return 20; if (t == T_GT_OR_EQUAL) return 20; if (t == T_LT_OR_EQUAL) return 20; if (t == T_DOUBLE_EQUALS) return 20; if (t == T_NOT_EQUALS) return 20; if (t == T_ADD) return 50; if (t == T_MINUS) return 50; if (t == T_ASTERISK) return 60; if (t == T_SLASH) return 60; if (t == T_LOGICAL_NOT) return 70; if (t == T_DOT) return 80; if (t == T_COLON) return 80; if (t == T_AWAIT) return 90; if (t == T_LEFT_ROUND) return 100; Assert(false); } Token TokenNext(Tokenizer *tokenizer) { // TODO Block comments. Token token = { 0 }; token.type = T_ERROR; token.module = tokenizer->module; while (true) { if (tokenizer->position == tokenizer->inputBytes) { token.type = T_EOF; break; } uint8_t c = tokenizer->input[tokenizer->position]; uint8_t c1 = tokenizer->position + 1 == tokenizer->inputBytes ? 0 : tokenizer->input[tokenizer->position + 1]; token.text = &tokenizer->input[tokenizer->position]; token.textBytes = 1; token.line = tokenizer->line; if (c == ' ' || c == '\t' || c == '\r') { tokenizer->position++; continue; } else if (c == '\n') { tokenizer->position++; tokenizer->line++; continue; } else if (c == '/' && c1 == '/') { while (tokenizer->position != tokenizer->inputBytes && tokenizer->input[tokenizer->position] != '\n') { tokenizer->position++; } continue; } else if (c == '<' && c1 == '=' && (tokenizer->position += 2)) token.type = T_LT_OR_EQUAL; else if (c == '>' && c1 == '=' && (tokenizer->position += 2)) token.type = T_GT_OR_EQUAL; else if (c == '=' && c1 == '=' && (tokenizer->position += 2)) token.type = T_DOUBLE_EQUALS; else if (c == '!' && c1 == '=' && (tokenizer->position += 2)) token.type = T_NOT_EQUALS; else if (c == '&' && c1 == '&' && (tokenizer->position += 2)) token.type = T_LOGICAL_AND; else if (c == '|' && c1 == '|' && (tokenizer->position += 2)) token.type = T_LOGICAL_OR; else if (c == '+' && c1 == '=' && (tokenizer->position += 2)) token.type = T_ADD_EQUALS; else if (c == '-' && c1 == '=' && (tokenizer->position += 2)) token.type = T_MINUS_EQUALS; else if (c == '*' && c1 == '=' && (tokenizer->position += 2)) token.type = T_ASTERISK_EQUALS; else if (c == '/' && c1 == '=' && (tokenizer->position += 2)) token.type = T_SLASH_EQUALS; else if (c == '+' && ++tokenizer->position) token.type = T_ADD; else if (c == '-' && ++tokenizer->position) token.type = T_MINUS; else if (c == '*' && ++tokenizer->position) token.type = T_ASTERISK; else if (c == '/' && ++tokenizer->position) token.type = T_SLASH; else if (c == '(' && ++tokenizer->position) token.type = T_LEFT_ROUND; else if (c == ')' && ++tokenizer->position) token.type = T_RIGHT_ROUND; else if (c == '[' && ++tokenizer->position) token.type = T_LEFT_SQUARE; else if (c == ']' && ++tokenizer->position) token.type = T_RIGHT_SQUARE; else if (c == '{' && ++tokenizer->position) token.type = T_LEFT_FANCY; else if (c == '}' && ++tokenizer->position) token.type = T_RIGHT_FANCY; else if (c == ',' && ++tokenizer->position) token.type = T_COMMA; else if (c == ';' && ++tokenizer->position) token.type = T_SEMICOLON; else if (c == '=' && ++tokenizer->position) token.type = T_EQUALS; else if (c == '<' && ++tokenizer->position) token.type = T_LESS_THAN; else if (c == '>' && ++tokenizer->position) token.type = T_GREATER_THAN; else if (c == '%' && ++tokenizer->position) token.type = T_STR_INTERPOLATE; else if (c == '.' && ++tokenizer->position) token.type = T_DOT; else if (c == ':' && ++tokenizer->position) token.type = T_COLON; else if (c == '!' && ++tokenizer->position) token.type = T_LOGICAL_NOT; else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_') || (c >= 0x80) || c == '#') { token.textBytes = 0; token.type = T_IDENTIFIER; token.text = tokenizer->input + tokenizer->position; while ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '_') || (c >= 0x80) || (c == '#' && !token.textBytes)) { tokenizer->position++; token.textBytes++; if (tokenizer->position == tokenizer->inputBytes) break; c = tokenizer->input[tokenizer->position]; } #define KEYWORD(x) (token.textBytes == sizeof(x) - 1 && 0 == MemoryCompare(x, token.text, token.textBytes)) if (false) {} else if KEYWORD("#extcall") token.type = T_EXTCALL; else if KEYWORD("#import") token.type = T_IMPORT; else if KEYWORD("#inline") token.type = T_INLINE; else if KEYWORD("#option") token.type = T_OPTION; else if KEYWORD("#persist") token.type = T_PERSIST; else if KEYWORD("assert") token.type = T_ASSERT; else if KEYWORD("await") token.type = T_AWAIT; else if KEYWORD("bool") token.type = T_BOOL; else if KEYWORD("else") token.type = T_ELSE; else if KEYWORD("false") token.type = T_FALSE; else if KEYWORD("float") token.type = T_FLOAT; else if KEYWORD("for") token.type = T_FOR; else if KEYWORD("functype") token.type = T_FUNCTYPE; else if KEYWORD("if") token.type = T_IF; else if KEYWORD("int") token.type = T_INT; else if KEYWORD("new") token.type = T_NEW; else if KEYWORD("null") token.type = T_NULL; else if KEYWORD("return") token.type = T_RETURN; else if KEYWORD("str") token.type = T_STR; else if KEYWORD("struct") token.type = T_STRUCT; else if KEYWORD("true") token.type = T_TRUE; else if KEYWORD("void") token.type = T_VOID; else if KEYWORD("while") token.type = T_WHILE; else if (token.text[0] == '#') { PrintError(tokenizer, "Unrecognised #-token '%.*s'.\n", token.textBytes, token.text); tokenizer->error = true; token.type = T_ERROR; break; } } else if (c >= '0' && c <= '9') { // TODO Exponent notation. token.textBytes = 0; token.type = T_NUMERIC_LITERAL; token.text = tokenizer->input + tokenizer->position; while ((c >= '0' && c <= '9') || (c == '.')) { tokenizer->position++; token.textBytes++; if (tokenizer->position == tokenizer->inputBytes) break; c = tokenizer->input[tokenizer->position]; } } else if (c == '"') { // TODO Escape sequence to insert an arbitrary codepoint. bool inInterpolation = false; intptr_t startPosition = ++tokenizer->position; intptr_t endPosition = -1; for (uintptr_t i = tokenizer->position; true; i++) { if (inInterpolation) { if (tokenizer->input[i] == '%') { inInterpolation = false; } else if (tokenizer->input[i] == '"') { PrintError(tokenizer, "Strings are not allowed within a string interpolation expression.\n"); tokenizer->error = true; break; } else if (tokenizer->input[i] == '\n') { PrintError(tokenizer, "String interpolation expressions must stay on a single line.\n"); tokenizer->error = true; break; } else { token.textBytes++; } } else if (i == tokenizer->inputBytes || tokenizer->input[i] == '\n') { PrintError(tokenizer, "String does not end before the end of the line.\n"); tokenizer->error = true; break; } else if (tokenizer->input[i] == '"') { endPosition = i; break; } else if (tokenizer->input[i] == '%') { inInterpolation = true; } else if (tokenizer->input[i] == '\\') { if (i + 1 == tokenizer->inputBytes || (tokenizer->input[i + 1] != 'n' && tokenizer->input[i + 1] != 't' && tokenizer->input[i + 1] != '%' && tokenizer->input[i + 1] != '"' && tokenizer->input[i + 1] != '\\')) { PrintError(tokenizer, "String contains unrecognized escape sequence '\\%c'. " "Possibilities are: '\\\\', '\\%%', '\\n', '\\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) { Node *node = (Node *) AllocateFixed(sizeof(Node)); node->token = TokenNext(tokenizer); if (node->token.type == T_INT || node->token.type == T_FLOAT || node->token.type == T_STR || node->token.type == T_BOOL || node->token.type == T_VOID || node->token.type == T_IDENTIFIER) { node->type = node->token.type; if (!allowVoid && node->type == T_VOID) { PrintError2(tokenizer, node, "The 'void' type is not allowed here.\n"); return NULL; } bool first = true; while (true) { Token token = TokenPeek(tokenizer); if (token.type == T_ERROR) { return NULL; } else if (token.type == T_LEFT_SQUARE) { Node *list = (Node *) AllocateFixed(sizeof(Node)); list->type = T_LIST; list->token = TokenNext(tokenizer); if (first) { list->firstChild = node; first = false; node = list; } else { Node *end = node; while (end->firstChild->firstChild) end = end->firstChild; list->firstChild = end->firstChild; end->firstChild = list; } token = TokenNext(tokenizer); if (token.type == T_ERROR) { return NULL; } else if (token.type != T_RIGHT_SQUARE) { if (!maybe) { PrintError2(tokenizer, node, "Expected a ']' after the '[' in an list type.\n"); } return NULL; } } else { break; } } return node; } else if (!maybe) { PrintError2(tokenizer, node, "Expected a type. This can be 'int', 'float', 'bool', 'void', 'str', or an identifier.\n"); return NULL; } else { return NULL; } } Node *ParseCall(Tokenizer *tokenizer, Node *function) { Node *call = (Node *) AllocateFixed(sizeof(Node)); call->token = TokenNext(tokenizer); call->type = T_CALL; call->firstChild = function; Node *arguments = (Node *) AllocateFixed(sizeof(Node)); arguments->type = T_ARGUMENTS; function->sibling = arguments; Node **link = &arguments->firstChild; if (call->token.type != T_LEFT_ROUND) { PrintError2(tokenizer, call, "Expected a '(' to start the list of arguments.\n"); return NULL; } while (true) { Token token = TokenPeek(tokenizer); if (token.type == T_RIGHT_ROUND) { TokenNext(tokenizer); break; } 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 = 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 == '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->type = node->token.type; node->firstChild = ParseExpression(tokenizer, false, TokenLookupPrecedence(node->token.type)); } else if (node->token.type == T_LEFT_ROUND) { node = ParseExpression(tokenizer, false, 0); if (!node) return NULL; Token token = TokenNext(tokenizer); if (token.type != T_RIGHT_ROUND) { Node n = { .token = token }; PrintError2(tokenizer, &n, "Expected a matching closing bracket.\n"); return NULL; } } else if (node->token.type == T_NEW) { node->type = T_NEW; node->firstChild = ParseType(tokenizer, false, false); node->expressionType = node->firstChild; if (!node->firstChild) return NULL; } else if (node->token.type == T_LEFT_SQUARE) { node->type = T_LIST_LITERAL; Node **link = &node->firstChild; bool needComma = false; while (true) { Token peek = TokenPeek(tokenizer); if (peek.type == T_ERROR) { return NULL; } else if (peek.type == T_RIGHT_SQUARE) { TokenNext(tokenizer); break; } if (needComma) { if (peek.type != T_COMMA) { PrintError(tokenizer, "Expected a comma between list literal items.\n"); return NULL; } else { TokenNext(tokenizer); } } Node *item = ParseExpression(tokenizer, false, 0); if (!item) return NULL; *link = item; link = &item->sibling; needComma = true; } } else if (node->token.type == T_AWAIT) { node->type = T_AWAIT; node->firstChild = ParseExpression(tokenizer, false, TokenLookupPrecedence(node->token.type)); } else { PrintError2(tokenizer, node, "Expected an expression. " "Expressions can start with a variable identifier, a string literal, a number, 'len', '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 = AllocateFixed(sizeof(Node)); *nodeCopy = *node; node = operation; operation = (Node *) AllocateFixed(sizeof(Node)); operation->token = token; operation->type = T_EQUALS; operation->firstChild = nodeCopy; operation->firstChild->sibling = node; node = operation; } else if (token.type == T_LEFT_ROUND && TokenLookupPrecedence(token.type) > precedence) { node = ParseCall(tokenizer, node); if (!node) return NULL; } else if (token.type == T_LEFT_SQUARE) { TokenNext(tokenizer); Node *index = (Node *) AllocateFixed(sizeof(Node)); index->type = T_INDEX; index->token = token; index->firstChild = node; index->firstChild->sibling = ParseExpression(tokenizer, false, 0); if (!index->firstChild->sibling) return NULL; node = index; Token token = TokenNext(tokenizer); if (token.type != T_RIGHT_SQUARE) { Node n = { .token = token }; PrintError2(tokenizer, &n, "Expected a matching closing bracket.\n"); return NULL; } } else { break; } } return node; } Node *ParseIf(Tokenizer *tokenizer) { Node *node = (Node *) AllocateFixed(sizeof(Node)); node->type = T_IF; node->token = TokenNext(tokenizer); node->firstChild = ParseExpression(tokenizer, false, 0); if (!node->firstChild) return NULL; Token token = TokenPeek(tokenizer); if (token.type == T_LEFT_FANCY) { TokenNext(tokenizer); node->firstChild->sibling = ParseBlock(tokenizer); 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); 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) { Tokenizer copy = *tokenizer; bool isVariableDeclaration = false; if (ParseType(tokenizer, true, false) && TokenNext(tokenizer).type == T_IDENTIFIER) { Token equalsOrSemicolon = TokenNext(tokenizer); if (equalsOrSemicolon.type == T_EQUALS || equalsOrSemicolon.type == T_SEMICOLON) { isVariableDeclaration = true; } } if (tokenizer->error) { return NULL; } *tokenizer = copy; if (isVariableDeclaration) { Node *declaration = (Node *) AllocateFixed(sizeof(Node)); declaration->type = T_DECLARE; declaration->firstChild = ParseType(tokenizer, false, false); Assert(declaration->firstChild); declaration->token = TokenNext(tokenizer); Assert(declaration->token.type == T_IDENTIFIER); Token equalsOrSemicolon = TokenNext(tokenizer); Assert(equalsOrSemicolon.type == T_EQUALS || equalsOrSemicolon.type == T_SEMICOLON); if (equalsOrSemicolon.type == T_EQUALS) { declaration->firstChild->sibling = ParseExpression(tokenizer, false, 0); if (!declaration->firstChild->sibling) return NULL; Token semicolon = TokenNext(tokenizer); if (semicolon.type != T_SEMICOLON) { PrintError2(tokenizer, declaration, "Expected a semicolon at the end of the variable declaration.\n"); return NULL; } } return declaration; } else { Node *expression = ParseExpression(tokenizer, true, 0); if (!expression) return NULL; 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) { 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 == 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); 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); 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); 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) { Node *node = (Node *) AllocateFixed(sizeof(Node)); node->type = token.type; node->token = TokenNext(tokenizer); *link = node; link = &node->sibling; if (token.type == T_ASSERT || TokenPeek(tokenizer).type != T_SEMICOLON) { node->firstChild = ParseExpression(tokenizer, false, 0); if (!node->firstChild) return NULL; } Token semicolon = TokenNext(tokenizer); if (semicolon.type != T_SEMICOLON) { PrintError2(tokenizer, node, "Expected a semicolon at the end of the statement.\n"); return NULL; } } else if (token.type == T_LEFT_FANCY) { TokenNext(tokenizer); Node *block = ParseBlock(tokenizer); if (!block) return NULL; *link = block; link = &block->sibling; } else { Node *node = ParseVariableDeclarationOrExpression(tokenizer); if (!node) return NULL; *link = node; link = &node->sibling; } } } Node *ParseGlobalVariableOrFunctionDefinition(Tokenizer *tokenizer, bool allowGlobalVariables, bool parseFunctionBody) { Node *type = ParseType(tokenizer, false, true); if (!type) { return NULL; } Node *node = (Node *) AllocateFixed(sizeof(Node)); node->token = TokenNext(tokenizer); if (node->token.type != T_IDENTIFIER) { if (allowGlobalVariables) { PrintError2(tokenizer, node, "Expected an identifier for the name of the function or global variable.\n"); } else { PrintError2(tokenizer, node, "Expected an identifier for the name of the function pointer type.\n"); } return NULL; } Token bracket = TokenPeek(tokenizer); if (bracket.type == T_ERROR) { return NULL; } else if (bracket.type == T_LEFT_ROUND) { TokenNext(tokenizer); node->type = T_FUNCTION; Node *functionPointerType = (Node *) AllocateFixed(sizeof(Node)); functionPointerType->type = T_FUNCPTR; node->firstChild = functionPointerType; Node *arguments = (Node *) AllocateFixed(sizeof(Node)); arguments->type = T_ARGUMENTS; functionPointerType->firstChild = arguments; arguments->sibling = type; Node **link = &arguments->firstChild; int argumentCount = 0; while (true) { Token token = TokenPeek(tokenizer); if (token.type == T_RIGHT_ROUND) { TokenNext(tokenizer); break; } if (arguments->firstChild) { Token comma = TokenNext(tokenizer); if (comma.type != T_COMMA) { Node n = { .token = comma }; PrintError2(tokenizer, &n, "Expected a comma to separate function arguments.\n"); return NULL; } } Node *argument = (Node *) AllocateFixed(sizeof(Node)); argument->type = T_ARGUMENT; argument->firstChild = ParseType(tokenizer, false, false); if (!argument->firstChild) return NULL; argument->token = TokenNext(tokenizer); *link = argument; link = &argument->sibling; argumentCount++; #define FUNCTION_MAX_ARGUMENTS (20) if (argumentCount > FUNCTION_MAX_ARGUMENTS) { PrintError2(tokenizer, argument, "Functions cannot have more than %d arguments.\n", FUNCTION_MAX_ARGUMENTS); return NULL; } if (argument->token.type != T_IDENTIFIER) { PrintError2(tokenizer, argument, "Expected an identifier for the name of the function argument.\n"); return NULL; } } if (parseFunctionBody) { Token token = TokenNext(tokenizer); if (token.type == T_LEFT_FANCY) { Node *body = (Node *) AllocateFixed(sizeof(Node)); body->type = T_FUNCBODY; functionPointerType->sibling = body; body->firstChild = ParseBlock(tokenizer); 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 *ParseRoot(Tokenizer *tokenizer) { Node *root = (Node *) AllocateFixed(sizeof(Node)); root->type = T_ROOT; Node **link = &root->firstChild; if (!tokenizer->isBaseModule) { Node *node = (Node *) AllocateFixed(sizeof(Node)); node->type = T_IMPORT; node->token.type = T_INLINE; node->token.text = "#inline"; node->token.textBytes = 7; node->firstChild = (Node *) AllocateFixed(sizeof(Node)); node->firstChild->type = T_IMPORT_PATH; node->firstChild->token.type = T_STRING_LITERAL; node->firstChild->token.text = "__base_module__"; node->firstChild->token.textBytes = 15; *link = node; link = &node->sibling; } while (true) { Token token = TokenPeek(tokenizer); if (token.type == T_ERROR) { return NULL; } else if (token.type == T_EOF) { return root; } else if (token.type == T_FUNCTYPE) { TokenNext(tokenizer); Node *node = ParseGlobalVariableOrFunctionDefinition(tokenizer, false, false); if (!node) return NULL; node->type = T_FUNCTYPE; *link = node; link = &node->sibling; Token semicolon = TokenNext(tokenizer); if (semicolon.type != T_SEMICOLON) { PrintError2(tokenizer, node->firstChild->sibling, "Expected a semicolon after the argument list.\n"); return NULL; } } else if (token.type == T_STRUCT) { TokenNext(tokenizer); Node *node = (Node *) AllocateFixed(sizeof(Node)); node->type = T_STRUCT; node->token = TokenNext(tokenizer); *link = node; link = &node->sibling; if (node->token.type != T_IDENTIFIER) { PrintError2(tokenizer, node, "Expected the name of the struct.\n"); return NULL; } if (TokenNext(tokenizer).type != T_LEFT_FANCY) { PrintError2(tokenizer, node, "Expected a '{' for the struct contents after the name.\n"); return NULL; } Node **fieldLink = &node->firstChild; while (true) { Token peek = TokenPeek(tokenizer); if (peek.type == T_ERROR) return NULL; if (peek.type == T_RIGHT_FANCY) break; Node *type = ParseType(tokenizer, false, true); if (!type) return NULL; Node *field = (Node *) AllocateFixed(sizeof(Node)); field->token = TokenNext(tokenizer); field->type = T_DECLARE; field->firstChild = type; *fieldLink = field; fieldLink = &field->sibling; if (field->token.type != T_IDENTIFIER) { PrintError2(tokenizer, field, "Expected an identifier for the field name.\n"); return NULL; } if (TokenNext(tokenizer).type != T_SEMICOLON) { PrintError2(tokenizer, field, "Expected a semicolon after the field name.\n"); return NULL; } } TokenNext(tokenizer); if (TokenNext(tokenizer).type != T_SEMICOLON) { PrintError2(tokenizer, node, "Expected a semicolon after the struct body.\n"); return NULL; } } else if (token.type == T_IMPORT) { Node *node = (Node *) AllocateFixed(sizeof(Node)); node->type = T_IMPORT; TokenNext(tokenizer); node->firstChild = ParseExpression(tokenizer, false, 0); if (!node->firstChild) return NULL; node->token = TokenNext(tokenizer); if (node->firstChild->type != T_STRING_LITERAL) { PrintError2(tokenizer, node, "The path to the script file to import must be a string literal.\n"); return NULL; } else if (node->token.type != T_IDENTIFIER && node->token.type != T_INLINE) { PrintError2(tokenizer, node, "Expected an identifier for the name to import the module as.\n"); return NULL; } node->firstChild->type = T_IMPORT_PATH; *link = node; link = &node->sibling; if (TokenNext(tokenizer).type != T_SEMICOLON) { PrintError2(tokenizer, node, "Expected a semicolon after the import statement.\n"); return NULL; } } else { Node *node = ParseGlobalVariableOrFunctionDefinition(tokenizer, true, true); if (!node) return NULL; *link = node; link = &node->sibling; } } } // --------------------------------- Scope management. bool ScopeIsVariableType(Node *node) { return node->type == T_DECLARE || node->type == T_FUNCTION || node->type == T_ARGUMENT; } intptr_t ScopeLookupIndex(Node *node, Scope *scope, bool maybe, bool real /* if false, the variable index is returned */) { uintptr_t j = 0; for (uintptr_t i = 0; i < scope->entryCount; i++) { if (scope->entries[i]->token.textBytes == node->token.textBytes && 0 == MemoryCompare(scope->entries[i]->token.text, node->token.text, node->token.textBytes)) { if (!ScopeIsVariableType(scope->entries[i]) && !real) { break; } return j; } if (ScopeIsVariableType(scope->entries[i]) || real) { j++; } } if (!maybe) { Assert(false); } return -1; } Node *ScopeLookup(Tokenizer *tokenizer, Node *node, bool maybe) { Node *ancestor = node; Scope *scope = NULL; while (ancestor) { if (ancestor->scope != scope) { scope = ancestor->scope; for (uintptr_t i = 0; i < scope->entryCount; i++) { if (scope->entries[i]->token.textBytes == node->token.textBytes && 0 == MemoryCompare(scope->entries[i]->token.text, node->token.text, node->token.textBytes)) { if (node->referencesRootScope && scope->entries[i]->parent->type != T_ROOT) { PrintError2(tokenizer, node, "The identifier '%.*s' is used before it is declared in this scope.\n", node->token.textBytes, node->token.text); return NULL; } return scope->entries[i]; } } } ancestor = ancestor->parent; } if (!maybe) { PrintError2(tokenizer, node, "Could not find identifier '%.*s' inside its scope.\n", node->token.textBytes, node->token.text); } return NULL; } bool ScopeCheckNotAlreadyUsed(Tokenizer *tokenizer, Node *node) { Node *ancestor = node; Scope *scope = NULL; while (ancestor) { if (ancestor->scope != scope) { scope = ancestor->scope; for (uintptr_t i = 0; i < scope->entryCount; i++) { if (scope->entries[i]->token.textBytes == node->token.textBytes && 0 == MemoryCompare(scope->entries[i]->token.text, node->token.text, node->token.textBytes) && (!scope->isRoot || node->scope == scope)) { PrintError2(tokenizer, node, "The identifier '%.*s' was already used in this scope.\n", node->token.textBytes, node->token.text); if (scope->entries[i]->type == T_INLINE) { if (scope->entries[i]->importData->pathBytes == 15 && 0 == MemoryCompare(scope->entries[i]->importData->path, "__base_module__", scope->entries[i]->importData->pathBytes)) { PrintDebug("It was declared in base library module.\n", scope->entries[i]->importData->path); } else { PrintDebug("It was imported inline from the module '%s'.\n", scope->entries[i]->importData->path); } } return false; } } } ancestor = ancestor->parent; } return true; } bool ScopeAddEntry(Tokenizer *tokenizer, Scope *scope, Node *node) { if (node->type == T_IMPORT && node->token.type == T_INLINE) { return true; } node->scope = scope; if (scope->entryCount == scope->entriesAllocated) { scope->entriesAllocated = scope->entriesAllocated ? scope->entriesAllocated * 2 : 4; scope->entries = (Node **) AllocateResize(scope->entries, sizeof(Node *) * scope->entriesAllocated); } if (!ScopeCheckNotAlreadyUsed(tokenizer, node)) { return false; } scope->entries[scope->entryCount++] = node; if (ScopeIsVariableType(node)) { scope->variableEntryCount++; } // Set this here before type checking occurs, // so that all the declarations in the scope already have their expression type set. node->expressionType = node->firstChild; return true; } void ASTFreeScopes(Node *node) { if (node && node->scope) { node->scope->entries = 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; } } if (node->type != T_STRUCT) { 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 = 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 = fileData; t.line = 1; if (!ScriptLoad(t, context, node->importData)) { 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) { return true; } else if (right->type == T_NULL && left->type == T_STRUCT) { return true; } else if (left->type != right->type) { return false; } else if ((left->type == T_IDENTIFIER || left->type == T_STRUCT) && (left->token.textBytes != right->token.textBytes || MemoryCompare(left->token.text, right->token.text, right->token.textBytes))) { return false; } else { Node *childLeft = left->firstChild; Node *childRight = right->firstChild; while (true) { if (!childLeft && !childRight) { return true; } else if (!childLeft || !childRight) { return false; } else if (!ASTMatching(childLeft, childRight)) { return false; } else { childLeft = childLeft->sibling; childRight = childRight->sibling; } } } } bool ASTIsManagedType(Node *node) { return node->type == T_STR || node->type == T_LIST || node->type == T_STRUCT || node->type == T_FUNCPTR || node->type == T_FUNCPTR; } bool ASTLookupTypeIdentifiers(Tokenizer *tokenizer, Node *node) { Node *child = node->firstChild; while (child) { if (!ASTLookupTypeIdentifiers(tokenizer, child)) return false; child = child->sibling; } if (node->type == T_DECLARE || node->type == T_ARGUMENT || node->type == T_NEW || node->type == T_LIST) { Node *type = node->firstChild; if (type->type == T_IDENTIFIER) { Node *lookup = ScopeLookup(tokenizer, type, false); if (!lookup) { return false; } if (!node->expressionType) { node->expressionType = node->firstChild; } Node *previousSibling = node->expressionType->sibling; if (!lookup) { return false; } else if (lookup->type == T_FUNCTYPE) { MemoryCopy(node->expressionType, lookup->firstChild, sizeof(Node)); } else if (lookup->type == T_STRUCT) { MemoryCopy(node->expressionType, lookup, sizeof(Node)); } else { PrintError2(tokenizer, node, "The identifier did not resolve to a type.\n"); return false; } node->expressionType->sibling = previousSibling; } } return true; } bool ASTSetTypes(Tokenizer *tokenizer, Node *node) { Node *child = node->firstChild; while (child) { if (!ASTSetTypes(tokenizer, child)) return false; child->parent = node; child = child->sibling; } if (node->type == T_ROOT || node->type == T_BLOCK || node->type == T_INT || node->type == T_FLOAT || node->type == T_STR || node->type == T_LIST || node->type == T_BOOL || node->type == T_VOID || node->type == T_IDENTIFIER || node->type == T_ARGUMENTS || node->type == T_ARGUMENT || node->type == T_STRUCT || node->type == T_FUNCTYPE || node->type == T_IMPORT || node->type == T_IMPORT_PATH || node->type == T_FUNCPTR || node->type == T_FUNCBODY || node->type == T_FUNCTION) { } 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)) { PrintError2(tokenizer, node, "This operator expects either integers, floats, strings or booleans.\n"); return false; } } else { if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeInt) && !ASTMatching(node->firstChild->expressionType, &globalExpressionTypeFloat)) { PrintError2(tokenizer, node, "This operator expects either integers or floats.\n"); return false; } } if (node->type == T_GREATER_THAN || node->type == T_LESS_THAN || node->type == T_LT_OR_EQUAL || node->type == T_GT_OR_EQUAL || node->type == T_DOUBLE_EQUALS || node->type == T_NOT_EQUALS) { node->expressionType = &globalExpressionTypeBool; } else { node->expressionType = node->firstChild->expressionType; } } else if (node->type == T_DECLARE) { if (node->firstChild->sibling && !ASTMatching(node->firstChild, node->firstChild->sibling->expressionType)) { ScriptPrintNode(node->firstChild, 0); ScriptPrintNode(node->firstChild->sibling->expressionType, 0); PrintError2(tokenizer, node, "The type of the variable being assigned does not match the expression.\n"); return false; } } else if (node->type == T_EQUALS) { if (!ASTMatching(node->firstChild->expressionType, node->firstChild->sibling->expressionType)) { PrintError2(tokenizer, node, "The type of the variable being assigned does not match the expression.\n"); return false; } } else if (node->type == T_CALL) { Node *functionPointer = node->firstChild; Node *expressionType = functionPointer->expressionType; if (!expressionType || expressionType->type != T_FUNCPTR) { PrintError2(tokenizer, functionPointer, "The expression being called is not a function.\n"); return false; } node->expressionType = expressionType->firstChild->sibling; Node *match = expressionType->firstChild->firstChild; Node *argument = node->firstChild->sibling->firstChild; size_t index = 1; while (true) { if (!argument && !match) { break; } else if (!argument || !match) { PrintError2(tokenizer, node, "The function has a different number of arguments to this.\n"); return false; } else { if (!ASTMatching(argument->expressionType, match->firstChild)) { PrintError2(tokenizer, node, "The types for argument %d do not match.\n", index); return false; } match = match->sibling; argument = argument->sibling; index++; } } } else if (node->type == T_ASSERT) { if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeBool)) { PrintError2(tokenizer, node, "The asserted expression must evaluate to a boolean.\n"); return false; } } else if (node->type == T_RETURN) { Node *expressionType = node->firstChild ? node->firstChild->expressionType : &globalExpressionTypeVoid; Node *function = node->parent; while (function->type != T_FUNCTION) { function = function->parent; } Node *returnType = function->firstChild->firstChild->sibling; if (node->firstChild && ASTMatching(returnType, &globalExpressionTypeVoid)) { PrintError2(tokenizer, node, "The function does not return a value ('void'), but the return statement has a return value.\n"); return false; } if (!ASTMatching(expressionType, returnType)) { PrintError2(tokenizer, node, "The type of the expression does not match the declared return type of the function.\n"); return false; } } else if (node->type == T_IF || node->type == T_WHILE) { if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeBool)) { PrintError2(tokenizer, node, "The expression used for the condition must evaluate to a boolean.\n"); return false; } } else if (node->type == T_FOR) { if (!ASTMatching(node->firstChild->sibling->expressionType, &globalExpressionTypeBool)) { PrintError2(tokenizer, node, "The expression used for the condition must evaluate to a boolean.\n"); return false; } } else if (node->type == T_INDEX) { if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeStr) && node->firstChild->expressionType->type != T_LIST) { PrintError2(tokenizer, node, "The expression being indexed must be a string or list.\n"); return false; } if (!ASTMatching(node->firstChild->sibling->expressionType, &globalExpressionTypeInt)) { PrintError2(tokenizer, node, "The index must be a integer.\n"); return false; } if (ASTMatching(node->firstChild->expressionType, &globalExpressionTypeStr)) { node->expressionType = &globalExpressionTypeStr; } else { node->expressionType = node->firstChild->expressionType->firstChild; } } else if (node->type == T_NEW) { if (node->firstChild->type != T_STRUCT && node->firstChild->type != T_LIST) { PrintError2(tokenizer, node, "This type is not a struct or list. 'new' is used to create new instances of structs or lists.\n"); return false; } } else if (node->type == T_DOT) { bool isStruct = node->firstChild->expressionType->type == T_STRUCT; if (!isStruct && node->firstChild->expressionType->type != T_IMPORT_PATH) { PrintError2(tokenizer, node, "This expression is not a struct or an imported module. " "You cannot use the '.' operator on it.\n"); return false; } if (isStruct) { Node *structure = node->firstChild->expressionType; Node *field = structure->firstChild; while (field) { if (field->token.textBytes == node->token.textBytes && 0 == MemoryCompare(field->token.text, node->token.text, node->token.textBytes)) { break; } field = field->sibling; } if (!field) { PrintError2(tokenizer, node, "The field '%.*s' is not in the struct '%.*s'.\n", node->token.textBytes, node->token.text, structure->token.textBytes, structure->token.text); return false; } node->expressionType = field->firstChild; } else { ImportData *importData = node->firstChild->expressionType->parent->importData; intptr_t index = ScopeLookupIndex(node, importData->rootNode->scope, true, true); if (index == -1) { PrintError2(tokenizer, node, "The variable or function '%.*s' is not in the imported module '%s'.\n", node->token.textBytes, node->token.text, importData->path); return false; } node->expressionType = importData->rootNode->scope->entries[index]->expressionType; } } else if (node->type == T_COLON) { Node *expressionType = node->firstChild->expressionType; bool isList = expressionType->type == T_LIST; bool isStr = expressionType->type == T_STR; bool isFuncPtr = expressionType->type == T_FUNCPTR; if (!isList && !isStr & !isFuncPtr) { PrintError2(tokenizer, node, "This type does not have any ':' operations.\n"); return false; } Token token = node->token; Node *arguments[2] = { 0 }; bool returnsItem = false, returnsInt = false, returnsBool = false, simple = true; uint8_t op; if (isList && KEYWORD("resize")) arguments[0] = &globalExpressionTypeInt, op = T_OP_RESIZE; else if (isList && KEYWORD("add")) arguments[0] = expressionType->firstChild, op = T_OP_ADD; else if (isList && KEYWORD("insert")) arguments[0] = expressionType->firstChild, arguments[1] = &globalExpressionTypeInt, op = T_OP_INSERT; else if (isList && KEYWORD("insert_many")) arguments[0] = &globalExpressionTypeInt, arguments[1] = &globalExpressionTypeInt, op = T_OP_INSERT_MANY; else if (isList && KEYWORD("delete")) arguments[0] = &globalExpressionTypeInt, op = T_OP_DELETE; else if (isList && KEYWORD("find_and_delete")) arguments[0] = expressionType->firstChild, op = T_OP_FIND_AND_DELETE, returnsBool = true; else if (isList && KEYWORD("delete_many")) arguments[0] = &globalExpressionTypeInt, arguments[1] = &globalExpressionTypeInt, op = T_OP_DELETE_MANY; else if (isList && KEYWORD("delete_last")) op = T_OP_DELETE_LAST; else if (isList && KEYWORD("delete_all")) op = T_OP_DELETE_ALL; else if (isList && KEYWORD("first")) returnsItem = true, op = T_OP_FIRST; else if (isList && KEYWORD("last")) returnsItem = true, op = T_OP_LAST; else if ((isList || isStr) && KEYWORD("len")) returnsInt = true, op = T_OP_LEN; else if (isFuncPtr && KEYWORD("async")) { if (expressionType->firstChild->firstChild) { PrintError2(tokenizer, node, "The function pointer should take no arguments. Use ':curry(...)' to set them before ':async()'.\n"); return false; } else if (!ASTMatching(expressionType->firstChild->sibling, &globalExpressionTypeVoid)) { PrintError2(tokenizer, node, "The function pointer should not return anything. Use ':discard()' or ':assert()' before ':async()'.\n"); return false; } // TODO Allow currying here, for convenience. op = T_OP_ASYNC; returnsInt = true; } else if (isFuncPtr && (KEYWORD("assert") || KEYWORD("discard"))) { if (KEYWORD("assert")) { op = T_OP_ASSERT; if (!ASTMatching(expressionType->firstChild->sibling, &globalExpressionTypeBool)) { PrintError2(tokenizer, node, "The return value of the function must be a bool to assert it.\n"); return false; } } else { op = T_OP_DISCARD; if (ASTMatching(expressionType->firstChild->sibling, &globalExpressionTypeVoid)) { PrintError2(tokenizer, node, "The return value cannot be discarded from a function that already returns void.\n"); return false; } } node->expressionType = (Node *) AllocateFixed(sizeof(Node)); node->expressionType->type = T_FUNCPTR; node->expressionType->firstChild = (Node *) AllocateFixed(sizeof(Node)); MemoryCopy(node->expressionType->firstChild, expressionType->firstChild, sizeof(Node)); node->expressionType->firstChild->sibling = &globalExpressionTypeVoid; simple = false; } else if (isFuncPtr && KEYWORD("curry")) { if (!expressionType->firstChild->firstChild) { PrintError2(tokenizer, node, "The function pointer doesn't take any arguments.\n"); return false; } else if (!ASTMatching(expressionType->firstChild->firstChild->firstChild, node->firstChild->sibling->firstChild->expressionType)) { PrintError2(tokenizer, node, "The curried argument does not match the type of the first argument.\n"); return false; } else if (node->firstChild->sibling->firstChild->sibling) { // TODO Allow currying multiple arguments together. PrintError2(tokenizer, node, "You can only curry one argument at a time.\n"); return false; } node->expressionType = (Node *) AllocateFixed(sizeof(Node)); node->expressionType->type = T_FUNCPTR; node->expressionType->firstChild = (Node *) AllocateFixed(sizeof(Node)); MemoryCopy(node->expressionType->firstChild, expressionType->firstChild, sizeof(Node)); node->expressionType->firstChild->firstChild = node->expressionType->firstChild->firstChild->sibling; node->expressionType->firstChild->sibling = expressionType->firstChild->sibling; op = T_OP_CURRY; simple = false; } else { PrintError2(tokenizer, node, "This type does not have an operation called '%.*s'.\n", token.textBytes, token.text); return false; } if (op == T_OP_FIND_AND_DELETE && ASTMatching(arguments[0], &globalExpressionTypeFloat)) { PrintError2(tokenizer, node, "The find_and_delete operation cannot be used with floats.\n"); return false; } if (simple) { Node *argument1 = node->firstChild->sibling->firstChild; Node *argument2 = argument1 ? argument1->sibling : NULL; Node *argument3 = argument2 ? argument2->sibling : NULL; if (argument3 || (argument2 && !arguments[1]) || (argument1 && !arguments[0]) || (!argument2 && arguments[1]) || (!argument1 && arguments[0])) { PrintError2(tokenizer, node, "Incorrect number of arguments for the operation '%.*s'.\n", token.textBytes, token.text); return false; } if (argument1 && !ASTMatching(argument1->expressionType, arguments[0])) { PrintError2(tokenizer, node, "Incorrect first argument type for the operation '%.*s'.\n", token.textBytes, token.text); return false; } if (argument2 && !ASTMatching(argument2->expressionType, arguments[1])) { PrintError2(tokenizer, node, "Incorrect second argument type for the operation '%.*s'.\n", token.textBytes, token.text); return false; } node->expressionType = returnsItem ? expressionType->firstChild : returnsInt ? &globalExpressionTypeInt : returnsBool ? &globalExpressionTypeBool : NULL; } node->operationType = op; } else if (node->type == T_LOGICAL_NOT) { if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeBool)) { PrintError2(tokenizer, node, "Expected a bool for the logical not '!' operator.\n"); return false; } node->expressionType = &globalExpressionTypeBool; } else if (node->type == T_LIST_LITERAL) { if (!node->firstChild) { // TODO Support empty list literals? PrintError2(tokenizer, node, "Empty list literals are not allowed. Instead, put 'new T[]' where 'T' is the item type.\n"); return false; } Node *item = node->firstChild; node->expressionType = (Node *) AllocateFixed(sizeof(Node)); node->expressionType->type = T_LIST; Node *copy = (Node *) AllocateFixed(sizeof(Node)); *copy = *item->expressionType; copy->sibling = NULL; node->expressionType->firstChild = copy; while (item) { if (!ASTMatching(node->expressionType->firstChild, item->expressionType)) { PrintError2(tokenizer, item, "The type of this item is different to the ones before it in the list.\n"); return false; } item = item->sibling; } } else if (node->type == T_AWAIT) { if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeIntList)) { PrintError2(tokenizer, node, "Expected a list of task IDs to wait on.\n"); return false; } node->expressionType = &globalExpressionTypeInt; } else { PrintDebug("ASTSetTypes %d\n", node->type); Assert(false); } return true; } bool ASTCheckForReturnStatements(Tokenizer *tokenizer, Node *node) { if (node->type == T_ROOT) { Node *child = node->firstChild; while (child) { if (!ASTCheckForReturnStatements(tokenizer, child)) return false; child->parent = node; child = child->sibling; } } else if (node->type == T_FUNCTION) { if (node->firstChild->sibling && node->firstChild->firstChild->sibling && node->firstChild->firstChild->sibling->type != T_VOID) { Assert(node->firstChild->sibling->type == T_FUNCBODY); return ASTCheckForReturnStatements(tokenizer, node->firstChild->sibling); } } else if (node->type == T_BLOCK || node->type == T_FUNCBODY) { Node *lastStatement = node->firstChild; while (lastStatement && lastStatement->sibling) { lastStatement = lastStatement->sibling; } if (lastStatement && lastStatement->type == T_RETURN) { return true; } else if (lastStatement && (lastStatement->type == T_IF || lastStatement->type == T_BLOCK)) { return ASTCheckForReturnStatements(tokenizer, lastStatement); } else { PrintError2(tokenizer, node, "This block needs to end with a return statement.\n"); return false; } } else if (node->type == T_IF) { if (!node->firstChild->sibling->sibling) { PrintError2(tokenizer, node, "This function returns a value, so this if statement needs an else block which ends with a return statement.\n"); return false; } return ASTCheckForReturnStatements(tokenizer, node->firstChild->sibling) && ASTCheckForReturnStatements(tokenizer, node->firstChild->sibling->sibling); } return true; } // --------------------------------- Code generation. void FunctionBuilderAppend(FunctionBuilder *builder, const void *buffer, size_t bytes) { if (builder->dataBytes + bytes > builder->dataAllocated) { builder->dataAllocated = 2 * builder->dataAllocated + bytes; builder->data = (uint8_t *) AllocateResize(builder->data, builder->dataAllocated); } for (uintptr_t i = 0; i < bytes; i++) { builder->data[builder->dataBytes + i] = ((const uint8_t *) buffer)[i]; } builder->dataBytes += bytes; } void FunctionBuilderAddLineNumber(FunctionBuilder *builder, Node *node) { if (builder->lineNumberCount == builder->lineNumbersAllocated) { builder->lineNumbersAllocated = 2 * builder->lineNumbersAllocated + 4; builder->lineNumbers = (LineNumber *) AllocateResize(builder->lineNumbers, builder->lineNumbersAllocated * sizeof(LineNumber)); } Node *ancestor = node; while (ancestor) { if (ancestor->type == T_FUNCTION) { builder->lineNumbers[builder->lineNumberCount].function = &ancestor->token; } ancestor = ancestor->parent; } builder->lineNumbers[builder->lineNumberCount].importData = builder->importData; builder->lineNumbers[builder->lineNumberCount].instructionPointer = builder->dataBytes; builder->lineNumbers[builder->lineNumberCount].lineNumber = node->token.line; builder->lineNumberCount++; } bool FunctionBuilderVariable(Tokenizer *tokenizer, FunctionBuilder *builder, Node *node, bool forAssignment) { Node *ancestor = node; Scope *scope = NULL; int32_t index = -1; Scope *rootScope = NULL; uintptr_t globalVariableOffset = builder->globalVariableOffset; bool inlineImport = false; while (ancestor) { if (ancestor->scope != scope) { scope = ancestor->scope; if (scope->isRoot) { rootScope = scope; } if (index != -1) { index += scope->variableEntryCount; } uintptr_t j = 0; for (uintptr_t i = 0; i < scope->entryCount; i++) { if (scope->entries[i]->token.textBytes == node->token.textBytes && 0 == MemoryCompare(scope->entries[i]->token.text, node->token.text, node->token.textBytes) && index == -1) { index = j; builder->isPersistentVariable = scope->entries[i]->isPersistentVariable; if (scope->entries[i]->type == T_INLINE) { index = scope->entries[i]->inlineImportVariableIndex; Assert(index != -1); globalVariableOffset = scope->entries[i]->importData->globalVariableOffset; inlineImport = true; } if (scope->entries[i]->type != T_DECLARE && forAssignment) { PrintError2(tokenizer, node, "A value cannot be assigned to this. " "Try putting a variable name here.\n"); return false; } } if (ScopeIsVariableType(scope->entries[i])) { j++; } } } ancestor = ancestor->parent; } if (index >= (int32_t) rootScope->variableEntryCount && !inlineImport) { index = rootScope->variableEntryCount - index - 1; } else { index += globalVariableOffset; } FunctionBuilderAddLineNumber(builder, node); if (forAssignment) { builder->scopeIndex = index; builder->isDotAssignment = false; builder->isListAssignment = false; } else { FunctionBuilderAppend(builder, &node->type, sizeof(node->type)); FunctionBuilderAppend(builder, &index, sizeof(index)); } return true; } bool FunctionBuilderRecurse(Tokenizer *tokenizer, Node *node, FunctionBuilder *builder, bool forAssignment) { if (forAssignment) { if (node->type == T_VARIABLE || node->type == T_DOT || node->type == T_INDEX) { // Supported. } else { PrintError2(tokenizer, node, "A value cannot be assigned to this expression. Try putting a variable name here.\n"); return false; } } if (node->type == T_FUNCBODY || node->type == T_BLOCK) { if (node->scope->variableEntryCount > 10000) { PrintError2(tokenizer, node, "There are too many variables in this scope (the maximum is 10000).\n"); return false; } uint16_t entryCount = node->scope->variableEntryCount; FunctionBuilderAddLineNumber(builder, node); FunctionBuilderAppend(builder, &node->type, sizeof(node->type)); FunctionBuilderAppend(builder, &entryCount, sizeof(entryCount)); for (uintptr_t i = 0; i < node->scope->entryCount; i++) { Node *entry = node->scope->entries[i]; if (ScopeIsVariableType(entry)) { bool isManaged = ASTIsManagedType(entry->expressionType); FunctionBuilderAppend(builder, &isManaged, sizeof(isManaged)); } } } else if (node->type == T_EQUALS || node->type == T_DECLARE) { if (node->firstChild->sibling) { if (!FunctionBuilderRecurse(tokenizer, node->firstChild->sibling, builder, false)) return false; builder->isPersistentVariable = false; if (node->type == T_DECLARE) { if (!FunctionBuilderVariable(tokenizer, builder, node, true)) return false; } else if (!FunctionBuilderRecurse(tokenizer, node->firstChild, builder, true)) return false; FunctionBuilderAddLineNumber(builder, node); uint8_t b = builder->isListAssignment ? T_EQUALS_LIST : builder->isDotAssignment ? T_EQUALS_DOT : T_EQUALS; FunctionBuilderAppend(builder, &b, sizeof(b)); if (!builder->isListAssignment) FunctionBuilderAppend(builder, &builder->scopeIndex, sizeof(builder->scopeIndex)); b = T_PERSIST; if (builder->isPersistentVariable) FunctionBuilderAppend(builder, &b, sizeof(b)); } return true; } else if (node->type == T_CALL) { Node *argument = node->firstChild->sibling->firstChild; Node *arguments[FUNCTION_MAX_ARGUMENTS]; size_t argumentCount = 0; while (argument) { arguments[argumentCount++] = argument; argument = argument->sibling; } for (uintptr_t i = 0; i < argumentCount; i++) { if (!FunctionBuilderRecurse(tokenizer, arguments[argumentCount - i - 1], builder, false)) { return false; } } if (!FunctionBuilderRecurse(tokenizer, node->firstChild, builder, false)) return false; FunctionBuilderAddLineNumber(builder, node); FunctionBuilderAppend(builder, &node->type, sizeof(node->type)); return true; } else if (node->type == T_WHILE) { int32_t start = builder->dataBytes; if (!FunctionBuilderRecurse(tokenizer, node->firstChild, builder, false)) return false; FunctionBuilderAddLineNumber(builder, node); uint8_t b = T_IF; FunctionBuilderAppend(builder, &b, sizeof(b)); uintptr_t writeOffset = builder->dataBytes; uint32_t zero = 0; FunctionBuilderAppend(builder, &zero, sizeof(zero)); if (!FunctionBuilderRecurse(tokenizer, node->firstChild->sibling, builder, false)) return false; b = T_BRANCH; FunctionBuilderAppend(builder, &b, sizeof(b)); int32_t delta = start - builder->dataBytes; FunctionBuilderAppend(builder, &delta, sizeof(delta)); delta = builder->dataBytes - writeOffset; MemoryCopy(builder->data + writeOffset, &delta, sizeof(delta)); return true; } else if (node->type == T_FOR) { Node *declare = node->firstChild; Node *condition = node->firstChild->sibling; Node *increment = node->firstChild->sibling->sibling; Node *body = node->firstChild->sibling->sibling->sibling; if (declare->type != T_DECLARE && declare->type != T_EQUALS) { PrintError2(tokenizer, node, "The first section of a for statement must be a variable declaration or an assignment.\n"); return false; } if (!FunctionBuilderRecurse(tokenizer, declare, builder, false)) return false; int32_t start = builder->dataBytes; if (!FunctionBuilderRecurse(tokenizer, condition, builder, false)) return false; FunctionBuilderAddLineNumber(builder, node); uint8_t b = T_IF; FunctionBuilderAppend(builder, &b, sizeof(b)); uintptr_t writeOffset = builder->dataBytes; uint32_t zero = 0; FunctionBuilderAppend(builder, &zero, sizeof(zero)); if (!FunctionBuilderRecurse(tokenizer, body, builder, false)) return false; if (!FunctionBuilderRecurse(tokenizer, increment, builder, false)) return false; if (increment->expressionType && increment->expressionType->type != T_VOID) { if (increment->type == T_CALL) { uint8_t b = T_POP; FunctionBuilderAppend(builder, &b, sizeof(b)); } else { PrintError2(tokenizer, increment, "The result of the expression is unused.\n"); return false; } } b = T_BRANCH; FunctionBuilderAppend(builder, &b, sizeof(b)); int32_t delta = start - builder->dataBytes; FunctionBuilderAppend(builder, &delta, sizeof(delta)); delta = builder->dataBytes - writeOffset; MemoryCopy(builder->data + writeOffset, &delta, sizeof(delta)); return true; } else if (node->type == T_IF) { if (!FunctionBuilderRecurse(tokenizer, node->firstChild, builder, false)) return false; FunctionBuilderAddLineNumber(builder, node); FunctionBuilderAppend(builder, &node->type, sizeof(node->type)); uintptr_t writeOffset = builder->dataBytes, writeOffsetElse = 0; uint32_t zero = 0; FunctionBuilderAppend(builder, &zero, sizeof(zero)); if (!FunctionBuilderRecurse(tokenizer, node->firstChild->sibling, builder, false)) return false; if (node->firstChild->sibling->sibling) { uint8_t b = T_BRANCH; FunctionBuilderAppend(builder, &b, sizeof(b)); writeOffsetElse = builder->dataBytes; FunctionBuilderAppend(builder, &zero, sizeof(zero)); } int32_t delta = builder->dataBytes - writeOffset; MemoryCopy(builder->data + writeOffset, &delta, sizeof(delta)); if (node->firstChild->sibling->sibling) { if (!FunctionBuilderRecurse(tokenizer, node->firstChild->sibling->sibling, builder, false)) return false; delta = builder->dataBytes - writeOffsetElse; MemoryCopy(builder->data + writeOffsetElse, &delta, sizeof(delta)); } return true; } else if (node->type == T_LOGICAL_OR || node->type == T_LOGICAL_AND) { if (!FunctionBuilderRecurse(tokenizer, node->firstChild, builder, false)) return false; FunctionBuilderAppend(builder, &node->type, sizeof(node->type)); uintptr_t writeOffset = builder->dataBytes; uint32_t zero = 0; FunctionBuilderAppend(builder, &zero, sizeof(zero)); if (!FunctionBuilderRecurse(tokenizer, node->firstChild->sibling, builder, false)) return false; int32_t delta = builder->dataBytes - writeOffset; MemoryCopy(builder->data + writeOffset, &delta, sizeof(delta)); return true; } else if (node->type == T_NEW) { FunctionBuilderAddLineNumber(builder, node); FunctionBuilderAppend(builder, &node->type, sizeof(node->type)); int16_t fieldCount = 0; if (node->firstChild->type == T_LIST) { fieldCount = ASTIsManagedType(node->firstChild->firstChild) ? -2 : -1; } else { Node *child = node->firstChild->firstChild; while (child) { if (fieldCount == 1000) { PrintError2(tokenizer, child, "The struct exceeds the maximum number of fields (1000).\n"); return false; } fieldCount++; child = child->sibling; } } FunctionBuilderAppend(builder, &fieldCount, sizeof(fieldCount)); return true; } else if (node->type == T_COLON) { FunctionBuilderRecurse(tokenizer, node->firstChild, builder, false); Node *argument = node->firstChild->sibling->firstChild; while (argument) { FunctionBuilderRecurse(tokenizer, argument, builder, false); argument = argument->sibling; } FunctionBuilderAddLineNumber(builder, node); FunctionBuilderAppend(builder, &node->operationType, sizeof(node->operationType)); return true; } else if (node->type == T_LIST_LITERAL) { // Step 1: Create the list. uint8_t b = T_NEW; int16_t isManaged = ASTIsManagedType(node->expressionType->firstChild) ? -2 : -1; FunctionBuilderAddLineNumber(builder, node); FunctionBuilderAppend(builder, &b, sizeof(b)); FunctionBuilderAppend(builder, &isManaged, sizeof(isManaged)); // Step 2: Resize the list. uint32_t size = 0; Node *item = node->firstChild; while (item) { size++; item = item->sibling; } b = T_DUP; FunctionBuilderAppend(builder, &b, sizeof(b)); b = T_NUMERIC_LITERAL; FunctionBuilderAppend(builder, &b, sizeof(b)); Value v; v.i = size; FunctionBuilderAppend(builder, &v, sizeof(v)); b = T_OP_RESIZE; FunctionBuilderAppend(builder, &b, sizeof(b)); // Step 3: Populate the list. item = node->firstChild; uint32_t i = 0; while (item) { b = T_DUP; FunctionBuilderAppend(builder, &b, sizeof(b)); FunctionBuilderRecurse(tokenizer, item, builder, false); b = T_SWAP; FunctionBuilderAppend(builder, &b, sizeof(b)); b = T_NUMERIC_LITERAL; FunctionBuilderAppend(builder, &b, sizeof(b)); Value v; v.i = i++; FunctionBuilderAppend(builder, &v, sizeof(v)); b = T_EQUALS_LIST; FunctionBuilderAppend(builder, &b, sizeof(b)); item = item->sibling; } return true; } Node *child = node->firstChild; while (child) { if (!FunctionBuilderRecurse(tokenizer, child, builder, false)) { return false; } if (node->type == T_BLOCK && child->expressionType && child->expressionType->type != T_VOID) { if (child->type == T_CALL || child->type == T_AWAIT || (child->type == T_COLON && child->operationType == T_OP_FIND_AND_DELETE)) { uint8_t b = T_POP; FunctionBuilderAppend(builder, &b, sizeof(b)); } else if (child->type == T_DECLARE || child->type == T_EQUALS) { } else { PrintError2(tokenizer, child, "The result of the expression is unused.\n"); return false; } } child = child->sibling; } if (node->type == T_FUNCBODY || node->type == T_BLOCK) { uint8_t b = T_EXIT_SCOPE; uint16_t entryCount = node->scope->variableEntryCount; FunctionBuilderAddLineNumber(builder, node); FunctionBuilderAppend(builder, &b, sizeof(b)); FunctionBuilderAppend(builder, &entryCount, sizeof(entryCount)); b = T_END_FUNCTION; if (node->type == T_FUNCBODY) FunctionBuilderAppend(builder, &b, sizeof(b)); } else if (node->type == T_RETURN) { uint8_t b = T_END_FUNCTION; FunctionBuilderAddLineNumber(builder, node); FunctionBuilderAppend(builder, &b, sizeof(b)); } else if (node->type == T_ASSERT || node->type == T_NULL || node->type == T_LOGICAL_NOT || node->type == T_AWAIT) { FunctionBuilderAddLineNumber(builder, node); FunctionBuilderAppend(builder, &node->type, sizeof(node->type)); } else if (node->type == T_ADD || node->type == T_MINUS || node->type == T_ASTERISK || node->type == T_SLASH) { 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 { 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 = AllocateResize(context->globalVariables, sizeof(Value) * context->globalVariableCount); context->globalVariableIsManaged = 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) { 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_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) { } 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; } } 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 = AllocateResize(context->c->localVariables, context->c->localVariablesAllocated * sizeof(Value)); context->c->localVariableIsManaged = 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; if (!context->c->stackIsManaged[context->c->stackPointer - 2]) return -1; if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1; uint64_t index1 = context->c->stack[context->c->stackPointer - 2].i; if (context->heapEntriesAllocated <= index1) return -1; HeapEntry *entry1 = &context->heap[index1]; if (entry1->type != T_EOF && entry1->type != T_STR) return -1; const char *text1 = entry1->type == T_STR ? entry1->text : ""; size_t bytes1 = entry1->type == T_STR ? entry1->bytes : 0; uint64_t index2 = context->c->stack[context->c->stackPointer - 1].i; if (context->heapEntriesAllocated <= index2) return -1; HeapEntry *entry2 = &context->heap[index2]; if (entry2->type != T_EOF && entry2->type != T_STR) return -1; const char *text2 = entry2->type == T_STR ? entry2->text : ""; size_t bytes2 = entry2->type == T_STR ? entry2->bytes : 0; // TODO Handle memory allocation failures here. uintptr_t index = HeapAllocate(context); context->heap[index].type = T_STR; context->heap[index].bytes = bytes1 + bytes2; 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); 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) { if (context->c->stackPointer < 3) return -1; if (!context->c->stackIsManaged[context->c->stackPointer - 3]) return -1; if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1; uint64_t index1 = context->c->stack[context->c->stackPointer - 3].i; if (context->heapEntriesAllocated <= index1) return -1; HeapEntry *entry1 = &context->heap[index1]; if (entry1->type != T_EOF && entry1->type != T_STR) return -1; const char *text1 = entry1->type == T_STR ? entry1->text : ""; size_t bytes1 = entry1->type == T_STR ? entry1->bytes : 0; char *freeText = NULL; const char *text2 = ""; size_t bytes2 = 0; char temp[30]; if (command == T_INTERPOLATE_STR) { 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_STR) return -1; text2 = entry2->type == T_STR ? entry2->text : ""; bytes2 = entry2->type == T_STR ? entry2->bytes : 0; } 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++] = ']'; } } uint64_t index3 = context->c->stack[context->c->stackPointer - 1].i; if (context->heapEntriesAllocated <= index3) return -1; HeapEntry *entry3 = &context->heap[index3]; if (entry3->type != T_EOF && entry3->type != T_STR) return -1; const char *text3 = entry3->type == T_STR ? entry3->text : ""; size_t bytes3 = entry3->type == T_STR ? entry3->bytes : 0; // 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_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_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) { if (context->c->stackPointer < 2) return -1; if (!context->c->stackIsManaged[context->c->stackPointer - 2]) return -1; if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1; uint64_t index1 = context->c->stack[context->c->stackPointer - 2].i; if (context->heapEntriesAllocated <= index1) return -1; HeapEntry *entry1 = &context->heap[index1]; if (entry1->type != T_EOF && entry1->type != T_STR) return -1; const char *text1 = entry1->type == T_STR ? entry1->text : 0; size_t bytes1 = entry1->type == T_STR ? entry1->bytes : 0; uint64_t index2 = context->c->stack[context->c->stackPointer - 1].i; if (context->heapEntriesAllocated <= index2) return -1; HeapEntry *entry2 = &context->heap[index2]; if (entry2->type != T_EOF && entry2->type != T_STR) return -1; const char *text2 = entry2->type == T_STR ? entry2->text : 0; size_t bytes2 = entry2->type == T_STR ? entry2->bytes : 0; 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]; int64_t length; if (entry->type == T_EOF) length = 0; else if (entry->type == T_STR) length = entry->bytes; else if (entry->type == T_LIST) length = entry->length; else return -1; context->c->stack[context->c->stackPointer - 1].i = length; context->c->stackIsManaged[context->c->stackPointer - 1] = false; } else if (command == T_INDEX) { if (context->c->stackPointer < 1) 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 (context->heapEntriesAllocated <= index) return -1; HeapEntry *entry = &context->heap[index]; if (entry->type != T_EOF && entry->type != T_STR) return -1; index = context->c->stack[context->c->stackPointer - 1].i; size_t bytes = entry->type == T_STR ? entry->bytes : 0; if (index >= bytes) { PrintError4(context, instructionPointer - 1, "Index %ld out of bounds in string '%.*s' of length %ld.\n", index, bytes, entry->type == T_STR ? entry->text : "", bytes); return 0; } char c = entry->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 = 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 = 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 = 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 = AllocateResize(context->heap[index].list, 0); context->c->stackPointer--; } else if (command == T_OP_FIND_AND_DELETE) { if (context->c->stackPointer < 2) return -1; if (!context->c->stackIsManaged[context->c->stackPointer - 2]) return -1; uint64_t index = context->c->stack[context->c->stackPointer - 2].i; if (!index) { PrintError4(context, instructionPointer - 1, "The list is null.\n"); return 0; } if (context->heapEntriesAllocated <= index) return -1; HeapEntry *entry = &context->heap[index]; if (entry->type != T_LIST) return -1; if (entry->internalValuesAreManaged != context->c->stackIsManaged[context->c->stackPointer - 1]) return -1; bool found = false; for (uintptr_t i = 0; i < entry->length; i++) { if (entry->list[i].i == context->c->stack[context->c->stackPointer - 1].i) { entry->length--; found = true; for (uintptr_t j = i; j < entry->length; j++) { entry->list[j] = entry->list[j + 1]; } break; } } context->c->stack[context->c->stackPointer - 2].i = found; context->c->stackIsManaged[context->c->stackPointer - 2] = false; context->c->stackPointer--; } else if (command == T_OP_DISCARD || command == T_OP_ASSERT) { if (context->c->stackPointer < 1) return -1; if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1; int64_t id = context->c->stack[context->c->stackPointer - 1].i; uintptr_t index = HeapAllocate(context); context->heap[index].type = command; context->heap[index].lambdaID = id; context->c->stackIsManaged[context->c->stackPointer - 1] = true; context->c->stack[context->c->stackPointer - 1].i = index; } else if (command == T_OP_CURRY) { if (context->c->stackPointer < 2) return -1; if (!context->c->stackIsManaged[context->c->stackPointer - 2]) return -1; bool valueIsManaged = context->c->stackIsManaged[context->c->stackPointer - 1]; Value value = context->c->stack[context->c->stackPointer - 1]; int64_t id = context->c->stack[context->c->stackPointer - 2].i; uintptr_t index = HeapAllocate(context); context->heap[index].type = command; context->heap[index].lambdaID = id; context->heap[index].curryValue = value; context->heap[index].internalValuesAreManaged = valueIsManaged; context->c->stackIsManaged[context->c->stackPointer - 2] = true; context->c->stack[context->c->stackPointer - 2].i = index; context->c->stackPointer--; } else if (command == T_OP_ASYNC) { if (context->c->stackPointer < 1) return -1; if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1; CoroutineState *c = (CoroutineState *) AllocateResize(NULL, sizeof(CoroutineState)); // TODO Handle allocation failure. CoroutineState empty = { 0 }; *c = empty; c->id = ++context->lastCoroutineID; c->startedByAsync = true; c->stackEntriesAllocated = sizeof(context->c->stack) / sizeof(context->c->stack[0]); c->stackPointer = 2; c->stack[0].i = -1; // Indicates to T_AWAIT to remove the coroutine. c->stackIsManaged[0] = false; c->stack[1] = context->c->stack[context->c->stackPointer - 1]; c->stackIsManaged[1] = true; c->nextCoroutine = context->allCoroutines; if (c->nextCoroutine) c->nextCoroutine->previousCoroutineLink = &c->nextCoroutine; c->previousCoroutineLink = &context->allCoroutines; context->allCoroutines = c; c->nextUnblockedCoroutine = context->unblockedCoroutines; if (c->nextUnblockedCoroutine) c->nextUnblockedCoroutine->previousUnblockedCoroutineLink = &c->nextUnblockedCoroutine; c->previousUnblockedCoroutineLink = &context->unblockedCoroutines; context->unblockedCoroutines = c; context->c->stackIsManaged[context->c->stackPointer - 1] = false; context->c->stack[context->c->stackPointer - 1].i = c->id; } else if (command == T_AWAIT) { awaitCommand:; if (context->c->stackPointer < 1 && !context->c->externalCoroutine) return -1; // PrintDebug("== AWAIT from %ld\n", context->c->id); Assert(!context->c->nextUnblockedCoroutine && !context->c->previousUnblockedCoroutineLink); bool unblockImmediately = false; if (context->c->externalCoroutine) { // PrintDebug("== external coroutine\n"); context->c->unblockedBy = -1; context->c->awaiting = true; context->c->instructionPointer = instructionPointer; context->c->variableBase = variableBase; context->c->waitingOnCount = 0; } else if (context->c->stack[context->c->stackPointer - 1].i == -1) { if (context->c->stackPointer != 1) return -1; // The coroutine has finished. Remove it from the list of all coroutines. *context->c->previousCoroutineLink = context->c->nextCoroutine; if (context->c->nextCoroutine) context->c->nextCoroutine->previousCoroutineLink = context->c->previousCoroutineLink; // PrintDebug("== finished\n"); for (uintptr_t i = 0; i < context->c->waiterCount; i++) { CoroutineState *c = context->c->waiters[i]; if (!c) continue; Assert(!c->nextUnblockedCoroutine && !c->previousUnblockedCoroutineLink); c->unblockedBy = context->c->id; c->nextUnblockedCoroutine = context->unblockedCoroutines; if (c->nextUnblockedCoroutine) c->nextUnblockedCoroutine->previousUnblockedCoroutineLink = &c->nextUnblockedCoroutine; c->previousUnblockedCoroutineLink = &context->unblockedCoroutines; context->unblockedCoroutines = c; for (uintptr_t j = 0; j < c->waitingOnCount; j++) { Assert(*(c->waitingOn[j]) == c); *(c->waitingOn[j]) = NULL; } c->waitingOnCount = 0; // PrintDebug("== unblocked %ld\n", c->id); } ScriptFreeCoroutine(context->c); } else { if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1; // The coroutine is waiting. context->c->unblockedBy = -1; context->c->awaiting = true; context->c->instructionPointer = instructionPointer; context->c->variableBase = variableBase; uint64_t index = context->c->stack[context->c->stackPointer - 1].i; if (context->heapEntriesAllocated <= index) return -1; HeapEntry *entry = &context->heap[index]; if (entry->internalValuesAreManaged || entry->type != T_LIST) return -1; context->c->waitingOn = (CoroutineState ***) AllocateResize(context->c->waitingOn, sizeof(CoroutineState **) * entry->length); Assert(context->c->waitingOnCount == 0); CoroutineState *c = context->allCoroutines; while (c) { for (uintptr_t i = 0; i < entry->length; i++) { if (c->id == (uint64_t) entry->list[i].i) { if (c->waiterCount == c->waitersAllocated) { c->waitersAllocated = c->waitersAllocated ? c->waitersAllocated * 2 : 4; c->waiters = (CoroutineState **) AllocateResize(c->waiters, sizeof(CoroutineState *) * c->waitersAllocated); } c->waiters[c->waiterCount] = context->c; context->c->waitingOn[context->c->waitingOnCount++] = &c->waiters[c->waiterCount]; c->waiterCount++; } } c = c->nextCoroutine; } // PrintDebug("== waiting on %d...\n", context->c->waitingOnCount); if (!context->c->waitingOnCount) { // PrintDebug("== immediately unblocking\n"); context->c->unblockedBy = entry->length ? entry->list[0].i : -1; unblockImmediately = true; } } CoroutineState *next = unblockImmediately ? context->c : context->unblockedCoroutines; if (!next) { if (context->externalCoroutineCount) { // PrintDebug("== wait for an external coroutine\n"); next = ExternalCoroutineWaitAny(context); Assert(next->externalCoroutine); unblockImmediately = true; } else { // TODO Earlier deadlock detection. PrintError4(context, instructionPointer - 1, "No tasks can run if this task (ID %ld) starts waiting.\n", context->c->id); PrintDebug("All tasks:\n"); CoroutineState *c = context->allCoroutines; while (c) { PrintDebug("\t%ld blocks ", c->id); bool first = true; for (uintptr_t i = 0; i < c->waiterCount; i++) { if (!c->waiters[i]) continue; PrintDebug("%s%ld", first ? "" : ", ", c->waiters[i]->id); first = false; } PrintDebug("\n"); PrintBackTrace(context, c->instructionPointer - 1, c, "\t"); c = c->nextCoroutine; } return 0; } } if (!unblockImmediately) { Assert(next->previousUnblockedCoroutineLink); *next->previousUnblockedCoroutineLink = next->nextUnblockedCoroutine; if (next->nextUnblockedCoroutine) next->nextUnblockedCoroutine->previousUnblockedCoroutineLink = next->previousUnblockedCoroutineLink; } next->nextUnblockedCoroutine = NULL; next->previousUnblockedCoroutineLink = NULL; context->c = next; // PrintDebug("== switch to %ld\n", next->id); if (context->c->awaiting) { if (!context->c->externalCoroutine) { context->c->stackIsManaged[context->c->stackPointer - 1] = false; context->c->stack[context->c->stackPointer - 1].i = context->c->unblockedBy; } instructionPointer = context->c->instructionPointer; variableBase = context->c->variableBase; // PrintDebug("== unblocked by %ld\n", context->c->unblockedBy); } else { // PrintDebug("== just started\n"); instructionPointer = 1; // There is a T_AWAIT command at address 1. goto callCommand; } } else if (command == T_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 = 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) { Node *previousRootNode = context->rootNode; ImportData *previousImportData = context->functionData->importData; context->rootNode = 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 = 5; n.token.text = "Start"; intptr_t startIndex = ScopeLookupIndex(&n, mainModule->rootNode->scope, true, false); if (startIndex == -1) { PrintError3("The script does not have a 'Start' function.\n"); 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) { 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); } // --------------------------------- Platform layer. #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; int ExternalStringTrim(ExecutionContext *context, Value *returnValue) { (void) returnValue; if (context->c->stackPointer < 1) return -1; uint64_t index = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; if (context->heapEntriesAllocated <= index) return -1; HeapEntry *entry = &context->heap[index]; if (entry->type == T_EOF) { returnValue->i = 0; return 3; } if (entry->type != T_STR) return -1; uintptr_t start = 0, end = entry->bytes; while (start != end) { if (entry->text[start] == ' ' || entry->text[start] == '\t' || entry->text[start] == '\r' || entry->text[start] == '\n') { start++; } else { break; } } while (start != end) { if (entry->text[end - 1] == ' ' || entry->text[end - 1] == '\t' || entry->text[end - 1] == '\r' || entry->text[end - 1] == '\n') { end--; } else { break; } } char *buffer = AllocateResize(NULL, end - start); MemoryCopy(buffer, entry->text + start, end - start); // TODO Handling allocation failures. index = HeapAllocate(context); context->heap[index].type = T_STR; context->heap[index].bytes = end - start; context->heap[index].text = buffer; returnValue->i = index; return 3; } int ExternalStringSlice(ExecutionContext *context, Value *returnValue) { (void) returnValue; if (context->c->stackPointer < 3) return -1; uint64_t index = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; 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 (context->heapEntriesAllocated <= index) return -1; HeapEntry *entry = &context->heap[index]; if (entry->type != T_EOF && entry->type != T_STR) return -1; char *string = entry->type == T_EOF ? "" : entry->text; size_t bytes = entry->type == T_EOF ? 0 : entry->bytes; 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; } char *buffer = AllocateResize(NULL, end - start); MemoryCopy(buffer, string + start, end - start); // TODO Handling allocation failures. index = HeapAllocate(context); context->heap[index].type = T_STR; context->heap[index].bytes = end - start; context->heap[index].text = buffer; returnValue->i = index; return 3; } int ExternalCharacterToByte(ExecutionContext *context, Value *returnValue) { (void) returnValue; if (context->c->stackPointer < 1) return -1; uint64_t index = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; if (context->heapEntriesAllocated <= index) return -1; HeapEntry *entry = &context->heap[index]; if (entry->type == T_EOF) { returnValue->i = -1; return 2; } if (entry->type != T_STR) return -1; returnValue->i = entry->bytes ? entry->text[0] : -1; return 2; } void ExternalCoroutineDone(CoroutineState *coroutine) { sem_post(&externalCoroutineSemaphore); pthread_mutex_lock(&externalCoroutineMutex); coroutine->nextUnblockedCoroutine = externalCoroutineUnblockedList; externalCoroutineUnblockedList = coroutine; pthread_mutex_unlock(&externalCoroutineMutex); } 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; } if (context->c->stackPointer < 1) return -1; uint64_t index = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; if (context->heapEntriesAllocated <= index) return -1; HeapEntry *entry = &context->heap[index]; if (entry->type != T_STR && entry->type != T_EOF) return -1; const char *text = entry->type == T_STR ? entry->text : ""; size_t bytes = entry->type == T_STR ? entry->bytes : 0; char *temporary = malloc(bytes + 1); if (temporary) { memcpy(temporary, text, bytes); temporary[bytes] = 0; if (systemShellLoggingEnabled) PrintDebug("\033[0;32m%s\033[0m\n", temporary); context->c->externalCoroutineData2 = temporary; pthread_t thread; pthread_create(&thread, NULL, SystemShellExecuteThread, context->c); return 4; } 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; Assert(coroutine->externalCoroutineData.i == waitpid(coroutine->externalCoroutineData.i, &status, 0)); 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; } if (context->c->stackPointer < 2) return -1; uint64_t index = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; uint64_t index2 = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; if (context->heapEntriesAllocated <= index) return -1; if (context->heapEntriesAllocated <= index2) return -1; HeapEntry *entry = &context->heap[index]; HeapEntry *entry2 = &context->heap[index2]; returnValue->i = 0; if (entry->type == T_EOF || entry2->type == T_EOF) return 2; if (entry2->type != T_STR) return -1; if (entry->type != T_STR) return -1; char *temporary = malloc(entry->bytes + 1); if (!temporary) return 3; memcpy(temporary, entry->text, entry->bytes); temporary[entry->bytes] = 0; char *temporary2 = malloc(entry2->bytes + 1); memcpy(temporary2, entry2->text, entry2->bytes); temporary2[entry2->bytes] = 0; if (systemShellLoggingEnabled) PrintDebug("\033[0;32m(%s) %s\033[0m\n", temporary, temporary2); pid_t pid = fork(); if (pid == 0) { chdir(temporary); 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); return 4; } return 2; } int ExternalSystemShellEvaluate(ExecutionContext *context, Value *returnValue) { if (context->c->stackPointer < 1) return -1; uint64_t index = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; if (context->heapEntriesAllocated <= index) return -1; HeapEntry *entry = &context->heap[index]; if (entry->type != T_STR && entry->type != T_EOF) return -1; const char *text = entry->type == T_STR ? entry->text : ""; size_t bytes = entry->type == T_STR ? entry->bytes : 0; char *temporary = malloc(bytes + 1); if (temporary) { memcpy(temporary, text, bytes); temporary[bytes] = 0; 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); uintptr_t index = HeapAllocate(context); context->heap[index].type = T_STR; context->heap[index].bytes = position; context->heap[index].text = buffer; returnValue->i = index; } else { returnValue->i = 0; } free(temporary); } else { fprintf(stderr, "Error in ExternalSystemShellEvaluate: Out of memory.\n"); returnValue->i = 0; } return 3; } int ExternalSystemShellEnableLogging(ExecutionContext *context, Value *returnValue) { (void) returnValue; if (context->c->stackPointer < 1) return -1; systemShellLoggingEnabled = context->c->stack[--context->c->stackPointer].i; if (context->c->stackIsManaged[context->c->stackPointer]) return -1; return 1; } int ExternalPrintStdErr(ExecutionContext *context, Value *returnValue) { (void) returnValue; if (context->c->stackPointer < 1) return -1; uint64_t index = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; if (context->heapEntriesAllocated <= index) return -1; HeapEntry *entry = &context->heap[index]; if (entry->type == T_STR) fprintf(stderr, "%.*s", (int) entry->bytes, (char *) entry->text); else if (entry->type != T_EOF) return -1; return 1; } int ExternalPrintStdErrWarning(ExecutionContext *context, Value *returnValue) { (void) returnValue; if (context->c->stackPointer < 1) return -1; uint64_t index = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; if (context->heapEntriesAllocated <= index) return -1; HeapEntry *entry = &context->heap[index]; static int coloredOutput = 0; #ifndef _WIN32 if (!coloredOutput) coloredOutput = isatty(STDERR_FILENO) ? 2 : 1; #endif if (entry->type == T_STR) fprintf(stderr, coloredOutput == 2 ? "\033[0;33m%.*s\033[0;m" : "%.*s", (int) entry->bytes, (char *) entry->text); else if (entry->type != T_EOF) return -1; return 1; } int ExternalPrintStdErrHighlight(ExecutionContext *context, Value *returnValue) { (void) returnValue; if (context->c->stackPointer < 1) return -1; uint64_t index = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; if (context->heapEntriesAllocated <= index) return -1; HeapEntry *entry = &context->heap[index]; static int coloredOutput = 0; #ifndef _WIN32 if (!coloredOutput) coloredOutput = isatty(STDERR_FILENO) ? 2 : 1; #endif if (entry->type == T_STR) fprintf(stderr, coloredOutput == 2 ? "\033[0;36m%.*s\033[0;m" : "%.*s", (int) entry->bytes, (char *) entry->text); else if (entry->type != T_EOF) return -1; return 1; } int ExternalPathCreateDirectory(ExecutionContext *context, Value *returnValue) { (void) returnValue; if (context->c->stackPointer < 1) return -1; uint64_t index = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; if (context->heapEntriesAllocated <= index) return -1; HeapEntry *entry = &context->heap[index]; returnValue->i = 0; if (entry->type == T_EOF) return 2; if (entry->type != T_STR) return -1; char *temporary = malloc(entry->bytes + 1); if (!temporary) return 2; memcpy(temporary, entry->text, entry->bytes); temporary[entry->bytes] = 0; returnValue->i = 1; #ifdef _WIN32 #pragma message ("ExternalPathCreateDirectory unimplemented") returnValue->i = 0; #else if (mkdir(temporary, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) returnValue->i = errno == EEXIST; #endif free(temporary); return 2; } int ExternalPathCreateLeadingDirectories(ExecutionContext *context, Value *returnValue) { (void) returnValue; if (context->c->stackPointer < 1) return -1; uint64_t index = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; if (context->heapEntriesAllocated <= index) return -1; HeapEntry *entry = &context->heap[index]; returnValue->i = 0; if (entry->type == T_EOF) return 2; if (entry->type != T_STR) return -1; char *temporary = malloc(entry->bytes + 1); if (!temporary) return 2; memcpy(temporary, entry->text, entry->bytes); temporary[entry->bytes] = 0; returnValue->i = 1; #ifdef _WIN32 #pragma message ("ExternalPathCreateLeadingDirectories unimplemented") returnValue->i = 0; #else for (uintptr_t i = 1; i < entry->bytes; i++) { if (temporary[i] == '/') { temporary[i] = 0; mkdir(temporary, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); temporary[i] = '/'; } } 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; if (context->c->stackPointer < 1) return -1; uint64_t index = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; if (context->heapEntriesAllocated <= index) return -1; HeapEntry *entry = &context->heap[index]; returnValue->i = 0; if (entry->type == T_EOF) return 2; if (entry->type != T_STR) return -1; char *temporary = malloc(entry->bytes + 1); if (!temporary) return 2; memcpy(temporary, entry->text, entry->bytes); temporary[entry->bytes] = 0; returnValue->i = unlink(temporary) == 0; free(temporary); return 2; } int ExternalPathExists(ExecutionContext *context, Value *returnValue) { (void) returnValue; if (context->c->stackPointer < 1) return -1; uint64_t index = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; if (context->heapEntriesAllocated <= index) return -1; HeapEntry *entry = &context->heap[index]; returnValue->i = 0; if (entry->type == T_EOF) return 2; if (entry->type != T_STR) return -1; char *temporary = malloc(entry->bytes + 1); if (!temporary) return 2; memcpy(temporary, entry->text, entry->bytes); temporary[entry->bytes] = 0; struct stat s = { 0 }; returnValue->i = stat(temporary, &s) == 0; free(temporary); return 2; } bool PathDeleteRecursively(const char *path) { #ifdef _WIN32 #pragma message ("PathDeleteRecursively unimplemented") return false; #else struct stat s = {}; if (lstat(path, &s)) { return true; } if (S_ISDIR(s.st_mode)) { DIR *directory = opendir(path); if (!directory) { return false; } struct dirent *entry; while ((entry = readdir(directory))) { if (0 == strcmp(entry->d_name, ".") || 0 == strcmp(entry->d_name, "..")) { continue; } char *child = (char *) malloc(strlen(path) + strlen(entry->d_name) + 2); sprintf(child, "%s/%s", path, entry->d_name); bool result = PathDeleteRecursively(child); free(child); if (!result) return result; } closedir(directory); return 0 == rmdir(path); } else if (S_ISREG(s.st_mode) || S_ISLNK(s.st_mode)) { return 0 == unlink(path); } else { return false; } #endif } int ExternalPathDeleteRecursively(ExecutionContext *context, Value *returnValue) { (void) returnValue; if (context->c->stackPointer < 1) return -1; uint64_t index = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; if (context->heapEntriesAllocated <= index) return -1; HeapEntry *entry = &context->heap[index]; returnValue->i = 0; if (entry->type == T_EOF) return 2; if (entry->type != T_STR) return -1; char *temporary = malloc(entry->bytes + 1); if (!temporary) return 2; memcpy(temporary, entry->text, entry->bytes); temporary[entry->bytes] = 0; returnValue->i = PathDeleteRecursively(temporary); free(temporary); return 2; } int ExternalPathMove(ExecutionContext *context, Value *returnValue) { (void) returnValue; if (context->c->stackPointer < 2) return -1; uint64_t index = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; if (context->heapEntriesAllocated <= index) return -1; HeapEntry *entry = &context->heap[index]; uint64_t index2 = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; if (context->heapEntriesAllocated <= index2) return -1; HeapEntry *entry2 = &context->heap[index2]; returnValue->i = 0; if (entry->type == T_EOF) return 2; if (entry->type != T_STR) return -1; if (entry2->type == T_EOF) return 2; if (entry2->type != T_STR) return -1; char *temporary = malloc(entry->bytes + 1); if (!temporary) return 2; char *temporary2 = malloc(entry2->bytes + 1); if (!temporary2) return 2; memcpy(temporary, entry->text, entry->bytes); temporary[entry->bytes] = 0; memcpy(temporary2, entry2->text, entry2->bytes); temporary2[entry2->bytes] = 0; returnValue->i = rename(temporary, temporary2) == 0; free(temporary); free(temporary2); return 2; } int ExternalFileCopy(ExecutionContext *context, Value *returnValue) { (void) returnValue; if (context->c->stackPointer < 2) return -1; uint64_t index = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; if (context->heapEntriesAllocated <= index) return -1; HeapEntry *entry = &context->heap[index]; uint64_t index2 = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; if (context->heapEntriesAllocated <= index2) return -1; HeapEntry *entry2 = &context->heap[index2]; returnValue->i = 0; if (entry->type == T_EOF) return 2; if (entry->type != T_STR) return -1; if (entry2->type == T_EOF) return 2; if (entry2->type != T_STR) return -1; char *temporary = malloc(entry->bytes + 1); if (!temporary) return 2; char *temporary2 = malloc(entry2->bytes + 1); if (!temporary2) return 2; memcpy(temporary, entry->text, entry->bytes); temporary[entry->bytes] = 0; memcpy(temporary2, entry2->text, entry2->bytes); temporary2[entry2->bytes] = 0; 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) { if (context->c->stackPointer < 1) return -1; uint64_t index = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; if (context->heapEntriesAllocated <= index) return -1; HeapEntry *entry = &context->heap[index]; returnValue->i = 0; if (entry->type == T_EOF) return 3; if (entry->type != T_STR) return -1; char *temporary = malloc(entry->bytes + 1); if (!temporary) return 3; memcpy(temporary, entry->text, entry->bytes); temporary[entry->bytes] = 0; char *data = getenv(temporary); size_t length = data ? strlen(data) : 0; char *copy = (char *) malloc(length + 1); if (length) strcpy(copy, data); else *copy = 0; index = HeapAllocate(context); context->heap[index].type = T_STR; context->heap[index].bytes = length; context->heap[index].text = copy; returnValue->i = index; free(temporary); return 3; } int ExternalSystemSetEnvironmentVariable(ExecutionContext *context, Value *returnValue) { if (context->c->stackPointer < 2) return -1; uint64_t index = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; uint64_t index2 = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; if (context->heapEntriesAllocated <= index) return -1; if (context->heapEntriesAllocated <= index2) return -1; HeapEntry *entry = &context->heap[index]; HeapEntry *entry2 = &context->heap[index2]; returnValue->i = 0; if (entry->type == T_EOF || entry2->type == T_EOF) return 2; if (entry2->type != T_STR) return -1; if (entry->type != T_STR) return -1; char *temporary = malloc(entry->bytes + 1); if (!temporary) return 3; memcpy(temporary, entry->text, entry->bytes); temporary[entry->bytes] = 0; char *temporary2 = malloc(entry2->bytes + 1); memcpy(temporary2, entry2->text, entry2->bytes); temporary2[entry2->bytes] = 0; returnValue->i = setenv(temporary, temporary2, true) == 0; free(temporary); free(temporary2); return 2; } int ExternalFileReadAll(ExecutionContext *context, Value *returnValue) { if (context->c->stackPointer < 1) return -1; uint64_t index = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; if (context->heapEntriesAllocated <= index) return -1; HeapEntry *entry = &context->heap[index]; returnValue->i = 0; if (entry->type == T_EOF) return 3; if (entry->type != T_STR) return -1; char *temporary = malloc(entry->bytes + 1); if (!temporary) return 3; memcpy(temporary, entry->text, entry->bytes); temporary[entry->bytes] = 0; size_t length = 0; void *data = FileLoad(temporary, &length); index = HeapAllocate(context); context->heap[index].type = T_STR; context->heap[index].bytes = length; context->heap[index].text = data; returnValue->i = index; free(temporary); return 3; } int ExternalFileWriteAll(ExecutionContext *context, Value *returnValue) { if (context->c->stackPointer < 2) return -1; uint64_t index = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; uint64_t index2 = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; if (context->heapEntriesAllocated <= index) return -1; if (context->heapEntriesAllocated <= index2) return -1; HeapEntry *entry = &context->heap[index]; HeapEntry *entry2 = &context->heap[index2]; returnValue->i = 0; if (entry->type == T_EOF) return 2; if (entry2->type != T_STR && entry2->type != T_EOF) return -1; if (entry->type != T_STR) return -1; char *temporary = malloc(entry->bytes + 1); if (!temporary) return 3; memcpy(temporary, entry->text, entry->bytes); temporary[entry->bytes] = 0; FILE *f = fopen(temporary, "wb"); if (f) { if (entry2->type == T_STR) returnValue->i = entry2->bytes == fwrite(entry2->text, 1, entry2->bytes, 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; } uint64_t index = HeapAllocate(context); context->heap[index].type = T_STR; context->heap[index].bytes = strlen(data); context->heap[index].text = realloc(data, strlen(data) + 1); returnValue->i = index; 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; if (context->c->stackPointer < 1) return -1; uint64_t index = context->c->stack[--context->c->stackPointer].i; if (!context->c->stackIsManaged[context->c->stackPointer]) return -1; if (context->heapEntriesAllocated <= index) return -1; HeapEntry *entry = &context->heap[index]; returnValue->i = 0; if (entry->type == T_EOF) return 2; if (entry->type != T_STR) return -1; char *temporary = malloc(entry->bytes + 1); if (!temporary) return 2; memcpy(temporary, entry->text, entry->bytes); temporary[entry->bytes] = 0; 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]; uint32_t variableDataLength = entry->type == T_STR ? entry->bytes : 0; fwrite(&variableDataLength, 1, sizeof(uint32_t), f); fwrite(scope->entries[j]->token.text, 1, variableNameLength, f); if (entry->bytes) fwrite(entry->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; getline(&line, &pos, stdin); 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 returnValue->i = HeapAllocate(context); context->heap[returnValue->i].type = T_STR; context->heap[returnValue->i].bytes = strlen(name); context->heap[returnValue->i].text = AllocateResize(NULL, context->heap[returnValue->i].bytes); memcpy(context->heap[returnValue->i].text, name, context->heap[returnValue->i].bytes); return 3; } CoroutineState *ExternalCoroutineWaitAny(ExecutionContext *context) { (void) context; while (sem_wait(&externalCoroutineSemaphore) == -1); pthread_mutex_lock(&externalCoroutineMutex); CoroutineState *unblocked = externalCoroutineUnblockedList; Assert(unblocked); externalCoroutineUnblockedList = unblocked->nextUnblockedCoroutine; unblocked->nextUnblockedCoroutine = NULL; pthread_mutex_unlock(&externalCoroutineMutex); return unblocked; } 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 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 (((char *) importData->fileData)[i] == '\n') { length = i - position; break; } } fprintf(stderr, ">> %.*s\n", (int) length, &((char *) importData->fileData)[position]); } 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 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) { fprintf(stderr, "%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); fprintf(stderr, "%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 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; fread(buffer, 1, fileSize, file); fclose(file); if (length) *length = fileSize; return buffer; } int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "Usage: %s