essence-os/util/script.c

5318 lines
189 KiB
C

// TODO Basic missing features:
// - Other list operations: insert, insert_many, delete, delete_many, delete_last, delete_all.
// - 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.
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#define T_ERROR (0)
#define T_EOF (1)
#define T_IDENTIFIER (2)
#define T_STRING_LITERAL (3)
#define T_NUMERIC_LITERAL (4)
#define T_ADD (40)
#define T_MINUS (41)
#define T_ASTERISK (42)
#define T_SLASH (43)
#define T_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_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;
uintptr_t instructionPointer;
uintptr_t variableBase;
} 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;
} 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 <assert.h>
#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);
// --------------------------------- 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;"
"int StringToByte(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;"
"}"
"bool CharacterIsAlnum(str c) {"
" int b = StringToByte(c);"
" return (b >= StringToByte(\"A\") && b <= StringToByte(\"Z\")) || (b >= StringToByte(\"a\") && b <= StringToByte(\"z\"))"
" || (b >= StringToByte(\"0\") && b <= StringToByte(\"9\"));"
"}"
// Miscellaneous:
"int SystemGetProcessorCount() #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;"
};
// --------------------------------- External function calls.
int ExternalPrintInt(ExecutionContext *context, Value *returnValue);
int ExternalPrintString(ExecutionContext *context, Value *returnValue);
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 ExternalStringToByte(ExecutionContext *context, Value *returnValue);
int ExternalSystemShellExecute(ExecutionContext *context, Value *returnValue);
int ExternalSystemShellExecuteWithWorkingDirectory(ExecutionContext *context, Value *returnValue);
int ExternalSystemShellEvaluate(ExecutionContext *context, Value *returnValue);
int ExternalSystemGetProcessorCount(ExecutionContext *context, Value *returnValue);
int ExternalSystemGetEnvironmentVariable(ExecutionContext *context, Value *returnValue);
int ExternalSystemSetEnvironmentVariable(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 = "StringToByte", .callback = ExternalStringToByte },
{ .cName = "SystemShellExecute", .callback = ExternalSystemShellExecute },
{ .cName = "SystemShellExecuteWithWorkingDirectory", .callback = ExternalSystemShellExecuteWithWorkingDirectory },
{ .cName = "SystemShellEvaluate", .callback = ExternalSystemShellEvaluate },
{ .cName = "SystemGetProcessorCount", .callback = ExternalSystemGetProcessorCount },
{ .cName = "SystemGetEnvironmentVariable", .callback = ExternalSystemGetEnvironmentVariable },
{ .cName = "SystemSetEnvironmentVariable", .callback = ExternalSystemSetEnvironmentVariable },
{ .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(&copy);
}
Node *ParseType(Tokenizer *tokenizer, bool maybe, bool allowVoid) {
Node *node = (Node *) AllocateFixed(sizeof(Node));
node->token = TokenNext(tokenizer);
if (node->token.type == T_INT
|| node->token.type == T_FLOAT
|| node->token.type == T_STR
|| node->token.type == T_BOOL
|| node->token.type == T_VOID
|| node->token.type == T_IDENTIFIER) {
node->type = node->token.type;
if (!allowVoid && node->type == T_VOID) {
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, &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->expressionType, 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(argument1->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) {
uint8_t b = node->firstChild->sibling->expressionType->type == T_STR ? T_INTERPOLATE_STR
: node->firstChild->sibling->expressionType->type == T_FLOAT ? T_INTERPOLATE_FLOAT
: node->firstChild->sibling->expressionType->type == T_INT ? T_INTERPOLATE_INT
: node->firstChild->sibling->expressionType->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;
}
uint8_t zero = 0; // Make sure no function can start at 0.
FunctionBuilderAppend(context->functionData, &zero, sizeof(zero));
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, %d\n", command, 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) {
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;
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);
}
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;
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_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) {
if (context->c->stackPointer < 1) return -1;
// PrintDebug("== AWAIT from %ld\n", context->c->id);
Assert(!context->c->nextUnblockedCoroutine && !context->c->previousUnblockedCoroutineLink);
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;
}
}
CoroutineState *next = context->unblockedCoroutines;
if (!next) {
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;
}
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) {
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--;
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 == 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;
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 <direct.h>
#include <windows.h>
#define getcwd _getcwd
#define popen _popen
#define pclose _pclose
#define setenv(x, y, z) !SetEnvironmentVariable(x, y)
#else
#include <dirent.h>
#include <unistd.h>
#endif
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
void **fixedAllocationBlocks;
uint8_t *fixedAllocationCurrentBlock;
uintptr_t fixedAllocationCurrentPosition;
size_t fixedAllocationCurrentSize;
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 ExternalStringToByte(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;
}
int ExternalSystemShellExecute(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;
PrintDebug("\033[0;32m%s\033[0m\n", temporary);
returnValue->i = system(temporary) == 0;
free(temporary);
} else {
fprintf(stderr, "Error in ExternalSystemShellExecute: Out of memory.\n");
returnValue->i = 0;
}
return 2;
}
int ExternalSystemShellExecuteWithWorkingDirectory(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;
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;
}
chdir(temporary);
PrintDebug("\033[0;32m(%s) %s\033[0m\n", temporary, temporary2);
returnValue->i = system(temporary2) == 0;
chdir(data);
free(temporary);
free(temporary2);
free(data);
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 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;
}
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 <path to script> <script options...>\n", argv[0]);
return 1;
}
options = argv + 2;
optionCount = argc - 2;
optionsMatched = (bool *) calloc(argc - 2, sizeof(bool));
scriptSourceDirectory = (char *) malloc(strlen(argv[1]) + 2);
strcpy(scriptSourceDirectory, argv[1]);
char *lastSlash = strrchr(scriptSourceDirectory, '/');
if (lastSlash) *lastSlash = 0;
else strcpy(scriptSourceDirectory, ".");
Tokenizer tokenizer = { 0 };
ImportData importData = { 0 };
importData.path = argv[1];
importData.pathBytes = strlen(argv[1]);
importData.fileData = FileLoad(argv[1], &tokenizer.inputBytes);
importData.fileDataBytes = tokenizer.inputBytes;
tokenizer.module = &importData;
tokenizer.line = 1;
tokenizer.input = importData.fileData;
if (!tokenizer.input) {
fprintf(stderr, "Error: Could not load the input file '%s'.\n", argv[1]);
return 1;
}
FunctionBuilder builder = { 0 };
ExecutionContext context = { 0 };
context.functionData = &builder;
context.mainModule = &importData;
context.heapEntriesAllocated = 2;
context.heap = (HeapEntry *) AllocateResize(NULL, sizeof(HeapEntry) * context.heapEntriesAllocated);
context.heap[0].type = T_EOF;
context.heap[1].type = T_ERROR;
context.heap[1].nextUnusedEntry = 0;
context.heapFirstUnusedEntry = 1;
context.c = (CoroutineState *) AllocateResize(0, sizeof(CoroutineState));
CoroutineState empty = { 0 };
*context.c = empty;
context.c->stackEntriesAllocated = sizeof(context.c->stack) / sizeof(context.c->stack[0]);
context.c->previousCoroutineLink = &context.allCoroutines;
context.allCoroutines = context.c;
int result = ScriptLoad(tokenizer, &context, &importData) ? ScriptExecute(&context, &importData) : 1;
ScriptFree(&context);
while (fixedAllocationBlocks) {
void *block = fixedAllocationBlocks;
fixedAllocationBlocks = (void **) *fixedAllocationBlocks;
free(block);
}
free(scriptSourceDirectory);
free(optionsMatched);
return result;
}