mirror of https://gitlab.com/nakst/essence
8001 lines
288 KiB
C
8001 lines
288 KiB
C
// TODO Basic missing features:
|
|
// - Maps: T[int], T[str].
|
|
// - Control flow: break, continue.
|
|
// - Other operators: integer modulo.
|
|
// - Named optional arguments with default values.
|
|
// - Using declared types from imported modules.
|
|
// - struct inheritance.
|
|
|
|
// TODO Syntax sugar: (ideas)
|
|
// - Pipe operator? e.g. <e := expression> | <f := function pointer> (...) ==> f(e, ...)
|
|
// - Dot operator for functions? e.g. <f := function pointer> . ==> f()
|
|
|
|
// 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.
|
|
// - Saving and showing the stack trace of where T_ERR values were created in assertion failure messages.
|
|
|
|
// TODO Cleanup:
|
|
// - Cleanup the code in External- functions and ScriptExecuteFunction using macros for common stack and heap operations.
|
|
// - Cleanup the ImportData/ExecutionContext/FunctionBuilder structures and their relationships.
|
|
// - Cleanup the variables/stack arrays.
|
|
// - Cleanup the platform layer.
|
|
|
|
// TODO Safety:
|
|
// - Safety against extremely large scripts?
|
|
// - Loading untrusted bytecode files?
|
|
|
|
// TODO Miscellaneous:
|
|
// - Inlining small strings.
|
|
// - Exponent notation in numeric literals.
|
|
// - Block comments.
|
|
// - More escape sequences in string literals.
|
|
// - Setting the initial values of global variables.
|
|
// - Better handling of memory allocation failures.
|
|
// - Operation arguments are evaluated in the opposite order to functions?
|
|
// - Shrink lists during garbage collection.
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <stdbool.h>
|
|
|
|
#define FUNCTION_MAX_ARGUMENTS (20) // Also the maximum number of return values in a tuple.
|
|
|
|
#define EXTCALL_NO_RETURN (1)
|
|
#define EXTCALL_RETURN_UNMANAGED (2)
|
|
#define EXTCALL_RETURN_MANAGED (3)
|
|
#define EXTCALL_START_COROUTINE (4)
|
|
#define EXTCALL_RETURN_ERR_UNMANAGED (5)
|
|
#define EXTCALL_RETURN_ERR_MANAGED (6)
|
|
#define EXTCALL_RETURN_ERR_ERROR (7)
|
|
|
|
#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 (20)
|
|
#define T_MINUS (21)
|
|
#define T_ASTERISK (22)
|
|
#define T_SLASH (23)
|
|
#define T_NEGATE (24)
|
|
#define T_LEFT_ROUND (25)
|
|
#define T_RIGHT_ROUND (26)
|
|
#define T_LEFT_SQUARE (27)
|
|
#define T_RIGHT_SQUARE (28)
|
|
#define T_LEFT_FANCY (29)
|
|
#define T_RIGHT_FANCY (30)
|
|
#define T_COMMA (31)
|
|
#define T_EQUALS (32)
|
|
#define T_SEMICOLON (33)
|
|
#define T_GREATER_THAN (34)
|
|
#define T_LESS_THAN (35)
|
|
#define T_GT_OR_EQUAL (36)
|
|
#define T_LT_OR_EQUAL (37)
|
|
#define T_DOUBLE_EQUALS (38)
|
|
#define T_NOT_EQUALS (39)
|
|
#define T_LOGICAL_AND (40)
|
|
#define T_LOGICAL_OR (41)
|
|
#define T_ADD_EQUALS (42)
|
|
#define T_MINUS_EQUALS (43)
|
|
#define T_ASTERISK_EQUALS (44)
|
|
#define T_SLASH_EQUALS (45)
|
|
#define T_DOT (46)
|
|
#define T_COLON (47)
|
|
#define T_LOGICAL_NOT (48)
|
|
#define T_BIT_SHIFT_LEFT (49)
|
|
#define T_BIT_SHIFT_RIGHT (50)
|
|
#define T_BITWISE_OR (51)
|
|
#define T_BITWISE_AND (52)
|
|
#define T_BITWISE_NOT (53)
|
|
#define T_BITWISE_XOR (54)
|
|
|
|
#define T_ROOT (70)
|
|
#define T_FUNCBODY (71)
|
|
#define T_ARGUMENTS (72)
|
|
#define T_ARGUMENT (73)
|
|
#define T_FUNCTION (74)
|
|
#define T_BLOCK (75)
|
|
#define T_VARIABLE (76)
|
|
#define T_CALL (77)
|
|
#define T_DECLARE (78)
|
|
#define T_FUNCPTR (79)
|
|
#define T_STR_INTERPOLATE (80)
|
|
#define T_INDEX (81)
|
|
#define T_LIST (82)
|
|
#define T_IMPORT_PATH (83)
|
|
#define T_LIST_LITERAL (84)
|
|
#define T_REPL_RESULT (85)
|
|
#define T_RETURN_TUPLE (86)
|
|
#define T_DECLARE_GROUP (87)
|
|
#define T_DECL_GROUP_AND_SET (88)
|
|
#define T_SET_GROUP (89)
|
|
#define T_FOR_EACH (90)
|
|
#define T_ERR_CAST (91)
|
|
#define T_IF_ERR (92)
|
|
#define T_INTTYPE_CONSTANT (93)
|
|
#define T_ANYTYPE_CAST (94)
|
|
#define T_CAST_TYPE_WRAPPER (95)
|
|
#define T_ZERO (96)
|
|
#define T_PLACEHOLDER (97)
|
|
|
|
#define T_EXIT_SCOPE (100)
|
|
#define T_END_FUNCTION (101)
|
|
#define T_POP (102)
|
|
#define T_BRANCH (103)
|
|
#define T_CONCAT (104)
|
|
#define T_INTERPOLATE_STR (105)
|
|
#define T_INTERPOLATE_BOOL (106)
|
|
#define T_INTERPOLATE_INT (107)
|
|
#define T_INTERPOLATE_FLOAT (108)
|
|
#define T_DUP (109)
|
|
#define T_SWAP (110)
|
|
#define T_INTERPOLATE_ILIST (111)
|
|
#define T_ROT3 (112)
|
|
#define T_LIBCALL (113)
|
|
#define T_END_CALLBACK (114)
|
|
|
|
#define T_FLOAT_ADD (120)
|
|
#define T_FLOAT_MINUS (121)
|
|
#define T_FLOAT_ASTERISK (122)
|
|
#define T_FLOAT_SLASH (123)
|
|
#define T_FLOAT_NEGATE (124)
|
|
#define T_FLOAT_GREATER_THAN (125)
|
|
#define T_FLOAT_LESS_THAN (126)
|
|
#define T_FLOAT_GT_OR_EQUAL (127)
|
|
#define T_FLOAT_LT_OR_EQUAL (128)
|
|
#define T_FLOAT_DOUBLE_EQUALS (129)
|
|
#define T_FLOAT_NOT_EQUALS (130)
|
|
#define T_STR_DOUBLE_EQUALS (131)
|
|
#define T_STR_NOT_EQUALS (132)
|
|
#define T_EQUALS_DOT (133)
|
|
#define T_EQUALS_LIST (134)
|
|
#define T_INDEX_LIST (135)
|
|
|
|
#define T_OP_RESIZE (140)
|
|
#define T_OP_ADD (141)
|
|
#define T_OP_INSERT (142)
|
|
#define T_OP_INSERT_MANY (143)
|
|
#define T_OP_DELETE (144)
|
|
#define T_OP_DELETE_MANY (145)
|
|
#define T_OP_DELETE_LAST (146)
|
|
#define T_OP_DELETE_ALL (147)
|
|
#define T_OP_FIRST (148)
|
|
#define T_OP_LAST (149)
|
|
#define T_OP_LEN (150)
|
|
#define T_OP_DISCARD (151)
|
|
#define T_OP_ASSERT (152)
|
|
#define T_OP_CURRY (153)
|
|
#define T_OP_ASYNC (154)
|
|
#define T_OP_FIND_AND_DELETE (155)
|
|
#define T_OP_FIND (156)
|
|
#define T_OP_FIND_AND_DEL_STR (157)
|
|
#define T_OP_FIND_STR (158)
|
|
#define T_OP_ASSERT_ERR (159)
|
|
#define T_OP_SUCCESS (160)
|
|
#define T_OP_ERROR (161)
|
|
#define T_OP_DEFAULT (162)
|
|
#define T_OP_INT_TO_FLOAT (163)
|
|
#define T_OP_FLOAT_TRUNCATE (164)
|
|
#define T_OP_CAST (165)
|
|
|
|
#define T_IF (170)
|
|
#define T_WHILE (171)
|
|
#define T_FOR (172)
|
|
#define T_INT (173)
|
|
#define T_FLOAT (174)
|
|
#define T_BOOL (175)
|
|
#define T_VOID (176)
|
|
#define T_RETURN (177)
|
|
#define T_ELSE (178)
|
|
#define T_EXTCALL (179)
|
|
#define T_STR (180)
|
|
#define T_FUNCTYPE (181)
|
|
#define T_NULL (182)
|
|
#define T_FALSE (183)
|
|
#define T_TRUE (184)
|
|
#define T_ASSERT (185)
|
|
#define T_PERSIST (186)
|
|
#define T_STRUCT (187)
|
|
#define T_NEW (188)
|
|
#define T_OPTION (189)
|
|
#define T_IMPORT (190)
|
|
#define T_INLINE (191)
|
|
#define T_AWAIT (192)
|
|
#define T_TUPLE (193)
|
|
#define T_IN (194)
|
|
#define T_ERR (195)
|
|
#define T_SUCCESS (196)
|
|
#define T_RETERR (197)
|
|
#define T_INTTYPE (198)
|
|
#define T_HANDLETYPE (199)
|
|
#define T_ANYTYPE (200)
|
|
#define T_LIBRARY (201)
|
|
|
|
#define STACK_READ_STRING(textVariable, bytesVariable, stackIndex) \
|
|
if (context->c->stackPointer < stackIndex) return -1; \
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - stackIndex]) return -1; \
|
|
uint64_t _index ## stackIndex = context->c->stack[context->c->stackPointer - stackIndex].i; \
|
|
if (context->heapEntriesAllocated <= _index ## stackIndex) return -1; \
|
|
HeapEntry *_entry ## stackIndex = &context->heap[_index ## stackIndex]; \
|
|
if (_entry ## stackIndex->type != T_EOF && _entry ## stackIndex->type != T_STR && _entry ## stackIndex->type != T_CONCAT) return -1; \
|
|
const char *textVariable; \
|
|
size_t bytesVariable; \
|
|
ScriptHeapEntryToString(context, _entry ## stackIndex, &textVariable, &bytesVariable);
|
|
#define STACK_POP_STRING(textVariable, bytesVariable) \
|
|
STACK_READ_STRING(textVariable, bytesVariable, 1); \
|
|
context->c->stackPointer--;
|
|
#define STACK_POP_STRING_2(textVariable1, bytesVariable1, textVariable2, bytesVariable2) \
|
|
STACK_READ_STRING(textVariable1, bytesVariable1, 1); \
|
|
STACK_READ_STRING(textVariable2, bytesVariable2, 2); \
|
|
context->c->stackPointer -= 2;
|
|
#define RETURN_STRING_COPY(_text, _bytes) \
|
|
returnValue->i = HeapAllocate(context); \
|
|
context->heap[returnValue->i].type = T_STR; \
|
|
context->heap[returnValue->i].bytes = _bytes; \
|
|
context->heap[returnValue->i].text = (char *) AllocateResize(NULL, context->heap[returnValue->i].bytes); \
|
|
MemoryCopy(context->heap[returnValue->i].text, _text, context->heap[returnValue->i].bytes);
|
|
#define RETURN_STRING_NO_COPY(_text, _bytes) \
|
|
returnValue->i = HeapAllocate(context); \
|
|
context->heap[returnValue->i].type = T_STR; \
|
|
context->heap[returnValue->i].bytes = _bytes; \
|
|
context->heap[returnValue->i].text = _text;
|
|
|
|
typedef struct Token {
|
|
struct ImportData *module;
|
|
const char *text;
|
|
size_t textBytes;
|
|
uint32_t line;
|
|
uint8_t type;
|
|
} Token;
|
|
|
|
typedef struct Tokenizer {
|
|
struct ImportData *module;
|
|
const char *input;
|
|
size_t inputBytes;
|
|
uintptr_t position;
|
|
uintptr_t line;
|
|
bool error, isBaseModule;
|
|
} Tokenizer;
|
|
|
|
typedef struct Scope {
|
|
struct Node **entries;
|
|
size_t entryCount;
|
|
size_t variableEntryCount;
|
|
size_t entriesAllocated;
|
|
bool isRoot;
|
|
} Scope;
|
|
|
|
typedef struct Node {
|
|
uint8_t type;
|
|
bool referencesRootScope, isExternalCall, isPersistentVariable, isOptionVariable, cycleCheck, hasTypeInheritanceParent;
|
|
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; // The module being imported by this node.
|
|
} Node;
|
|
|
|
typedef struct Value {
|
|
union {
|
|
int64_t i;
|
|
double f;
|
|
};
|
|
} Value;
|
|
|
|
typedef struct LineNumber {
|
|
struct ImportData *importData;
|
|
uint32_t instructionPointer;
|
|
uint32_t lineNumber;
|
|
Token *function;
|
|
} LineNumber;
|
|
|
|
typedef struct FunctionBuilder {
|
|
uint8_t *data;
|
|
size_t dataBytes;
|
|
size_t dataAllocated;
|
|
LineNumber *lineNumbers;
|
|
size_t lineNumberCount;
|
|
size_t lineNumbersAllocated;
|
|
int32_t scopeIndex;
|
|
bool isPersistentVariable, isDotAssignment, isListAssignment;
|
|
uintptr_t globalVariableOffset;
|
|
struct ImportData *importData; // Only valid during script loading.
|
|
Node *replResultType;
|
|
} FunctionBuilder;
|
|
|
|
typedef struct BackTraceItem {
|
|
uint32_t instructionPointer : 30,
|
|
popResult : 1,
|
|
assertResult : 1;
|
|
int32_t variableBase : 30;
|
|
} BackTraceItem;
|
|
|
|
typedef struct HeapEntry {
|
|
uint8_t type;
|
|
bool gcMark;
|
|
bool internalValuesAreManaged;
|
|
uint32_t externalReferenceCount;
|
|
|
|
union {
|
|
struct { // T_STR
|
|
// TODO Inlining small strings.
|
|
size_t bytes;
|
|
char *text;
|
|
};
|
|
|
|
struct { // T_STRUCT
|
|
uint16_t fieldCount;
|
|
Value *fields; // Managed bools placed before this.
|
|
};
|
|
|
|
struct { // T_LIST
|
|
uint32_t length, allocated;
|
|
Value *list;
|
|
};
|
|
|
|
struct { // Unused entry.
|
|
uintptr_t nextUnusedEntry;
|
|
};
|
|
|
|
struct { // Various function pointers.
|
|
int64_t lambdaID;
|
|
Value curryValue;
|
|
};
|
|
|
|
struct { // T_CONCAT
|
|
uint32_t concat1, concat2;
|
|
size_t concatBytes;
|
|
};
|
|
|
|
struct { // T_ERR
|
|
bool success;
|
|
Value errorValue;
|
|
};
|
|
|
|
struct { // T_ANYTYPE
|
|
Node *anyType;
|
|
Value anyValue;
|
|
};
|
|
};
|
|
} HeapEntry;
|
|
|
|
typedef struct CoroutineState {
|
|
Value *localVariables;
|
|
bool *localVariableIsManaged;
|
|
size_t localVariableCount;
|
|
size_t localVariablesAllocated;
|
|
Value stack[50]; // TODO Merge with variables?
|
|
bool stackIsManaged[50];
|
|
uintptr_t stackPointer;
|
|
size_t stackEntriesAllocated;
|
|
BackTraceItem backTrace[300];
|
|
uintptr_t backTracePointer;
|
|
uint64_t id;
|
|
int64_t unblockedBy;
|
|
struct CoroutineState *nextCoroutine;
|
|
struct CoroutineState **previousCoroutineLink;
|
|
struct CoroutineState *nextUnblockedCoroutine;
|
|
struct CoroutineState **previousUnblockedCoroutineLink;
|
|
struct CoroutineState *nextExternalCoroutine;
|
|
struct CoroutineState **waiters;
|
|
size_t waiterCount;
|
|
size_t waitersAllocated;
|
|
struct CoroutineState ***waitingOn;
|
|
size_t waitingOnCount;
|
|
bool awaiting, startedByAsync, externalCoroutine;
|
|
uintptr_t instructionPointer;
|
|
uintptr_t variableBase;
|
|
Value externalCoroutineData;
|
|
void *externalCoroutineData2;
|
|
|
|
// Data stored for library calls:
|
|
uintptr_t parameterCount;
|
|
Value returnValue;
|
|
int returnValueType;
|
|
} CoroutineState;
|
|
|
|
typedef struct ExecutionContext {
|
|
Value *globalVariables;
|
|
bool *globalVariableIsManaged;
|
|
size_t globalVariableCount;
|
|
|
|
HeapEntry *heap;
|
|
uintptr_t heapFirstUnusedEntry;
|
|
size_t heapEntriesAllocated;
|
|
|
|
FunctionBuilder *functionData; // Cleanup the relations between ExecutionContext, FunctionBuilder, Tokenizer and ImportData.
|
|
Node *rootNode; // Only valid during script loading.
|
|
char *scriptPersistFile;
|
|
struct ImportData *mainModule;
|
|
|
|
CoroutineState *c; // Active coroutine.
|
|
CoroutineState *allCoroutines;
|
|
CoroutineState *unblockedCoroutines;
|
|
uint64_t lastCoroutineID;
|
|
uint32_t externalCoroutineCount;
|
|
} ExecutionContext;
|
|
|
|
typedef struct ExternalFunction {
|
|
const char *cName;
|
|
int (*callback)(ExecutionContext *context, Value *returnValue);
|
|
} ExternalFunction;
|
|
|
|
typedef struct ImportData {
|
|
char *path;
|
|
size_t pathBytes;
|
|
void *fileData;
|
|
size_t fileDataBytes;
|
|
uintptr_t globalVariableOffset;
|
|
struct ImportData *nextImport;
|
|
struct ImportData *parentImport;
|
|
Node *rootNode;
|
|
void *library;
|
|
} 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 };
|
|
Node globalExpressionTypeErrVoid = { .type = T_ERR, .firstChild = &globalExpressionTypeVoid };
|
|
|
|
// Global variables:
|
|
const char *startFunction = "Start";
|
|
size_t startFunctionBytes = 5;
|
|
char **options;
|
|
bool *optionsMatched;
|
|
size_t optionCount;
|
|
int debugBytecodeLevel;
|
|
ImportData *importedModules;
|
|
ImportData **importedModulesLink = &importedModules;
|
|
|
|
// Forward declarations:
|
|
Node *ParseBlock(Tokenizer *tokenizer, bool replMode);
|
|
Node *ParseExpression(Tokenizer *tokenizer, bool allowAssignment, uint8_t precedence);
|
|
void ScriptPrintNode(Node *node, int indent);
|
|
bool ScriptLoad(Tokenizer tokenizer, ExecutionContext *context, ImportData *importData, bool replMode);
|
|
void ScriptFreeCoroutine(CoroutineState *c);
|
|
uintptr_t HeapAllocate(ExecutionContext *context);
|
|
|
|
// --------------------------------- Platform layer definitions.
|
|
|
|
#if defined(_WIN32) || defined(__linux__) || defined(__APPLE__)
|
|
#include <assert.h>
|
|
#define Assert assert
|
|
#endif
|
|
|
|
void *AllocateFixed(size_t bytes);
|
|
void *AllocateResize(void *old, size_t bytes);
|
|
int MemoryCompare(const void *a, const void *b, size_t bytes);
|
|
void MemoryCopy(void *a, const void *b, size_t bytes);
|
|
size_t PrintIntegerToBuffer(char *buffer, size_t bufferBytes, int64_t i); // TODO This shouldn't be in the platform layer.
|
|
size_t PrintFloatToBuffer(char *buffer, size_t bufferBytes, double f); // TODO This shouldn't be in the platform layer.
|
|
void PrintDebug(const char *format, ...);
|
|
void PrintError(Tokenizer *tokenizer, const char *format, ...);
|
|
void PrintError2(Tokenizer *tokenizer, Node *node, const char *format, ...);
|
|
void PrintError3(const char *format, ...);
|
|
void PrintError4(ExecutionContext *context, uint32_t instructionPointer, const char *format, ...);
|
|
void PrintBackTrace(ExecutionContext *context, uint32_t instructionPointer, CoroutineState *c, const char *prefix);
|
|
void *FileLoad(const char *path, size_t *length);
|
|
CoroutineState *ExternalCoroutineWaitAny(ExecutionContext *context);
|
|
void ExternalPassREPLResult(ExecutionContext *context, Value value);
|
|
void *LibraryLoad(const char *name);
|
|
void *LibraryGetAddress(void *library, const char *name);
|
|
|
|
// --------------------------------- Base module.
|
|
|
|
char baseModuleSource[] = {
|
|
// TODO Temporary.
|
|
"struct DirectoryChild { str name; bool isDirectory; int size; };"
|
|
"DirectoryChild[] Dir() { str[] names = DirectoryEnumerate(\"0:\"):assert(); DirectoryChild[] result = new DirectoryChild[]; result:resize(names:len()); for int i = 0; i < names:len(); i += 1 { result[i] = new DirectoryChild; result[i].name = names[i]; result[i].isDirectory = PathIsDirectory(\"0:/\"+names[i]); result[i].size = FileGetSize(\"0:/\"+names[i]):default(-1); } return result;}"
|
|
"str[] OpenDocumentEnumerate() #extcall;"
|
|
|
|
// Logging:
|
|
|
|
"void Log(str x) #extcall;"
|
|
"void LogInfo(str x) { Log(TextColorHighlight() + x); }"
|
|
"void LogError(str x) { Log(TextColorError() + \"Error: \" + x); }"
|
|
|
|
"void LogOpenGroup(str title) #extcall;"
|
|
// TODO LogOpenList, LogOpenTable, etc.
|
|
"void LogClose() #extcall;"
|
|
|
|
"str TextColorError() #extcall;"
|
|
"str TextColorHighlight() #extcall;"
|
|
"str TextWeight(int i) #extcall;"
|
|
"str TextMonospaced() #extcall;"
|
|
"str TextPlain() #extcall;"
|
|
"str TextBold() { return TextWeight(700); }"
|
|
|
|
// String operations:
|
|
|
|
"str StringSlice(str x, int start, int end) #extcall;"
|
|
"int CharacterToByte(str x) #extcall;"
|
|
"bool StringContains(str haystack, str needle) {"
|
|
" for int i = 0; i <= haystack:len() - needle:len(); i += 1 {"
|
|
" bool match = true;"
|
|
" for int j = 0; j < needle:len(); j += 1 { if haystack[i + j] != needle[j] match = false; }"
|
|
" if match { return true; }"
|
|
" }"
|
|
" return false;"
|
|
"}"
|
|
"str StringTrim(str string) {"
|
|
" int start = 0;"
|
|
" int end = string:len();"
|
|
" while start != end && (string[start] == \" \" || string[start] == \"\\t\" || string[start] == \"\\r\" || string[start] == \"\\n\") { start += 1; }"
|
|
" while start != end && (string[end - 1] == \" \" || string[end - 1] == \"\\t\" || string[end - 1] == \"\\r\" || string[end - 1] == \"\\n\") { end -= 1; }"
|
|
" return StringSlice(string, start, end);"
|
|
"}"
|
|
"str[] StringSplitByCharacter(str string, str character, bool includeEmptyString) {"
|
|
" assert character:len() == 1;"
|
|
" str[] list = new str[];"
|
|
" int x = 0;"
|
|
" for int i = 0; i < string:len(); i += 1 {"
|
|
" if string[i] == character {"
|
|
" if (includeEmptyString || x != i) { list:add(StringSlice(string, x, i)); }"
|
|
" x = i + 1;"
|
|
" }"
|
|
" }"
|
|
" if (includeEmptyString || x != string:len()) { list:add(StringSlice(string, x, string:len())); }"
|
|
" return list;"
|
|
"}"
|
|
"str StringJoin(str[] strings, str k, bool trailing) {"
|
|
" str c = \"\";"
|
|
" for int i = 0; i < strings:len(); i += 1 {"
|
|
" if i < strings:len() - 1 || trailing { c = c + strings[i] + k; }"
|
|
" else { c = c + strings[i]; }"
|
|
" }"
|
|
" return c;"
|
|
"}"
|
|
"bool StringEndsWith(str s, str x) { return s:len() >= x:len() && StringSlice(s, s:len() - x:len(), s:len()) == x; }"
|
|
"bool StringStartsWith(str s, str x) { return s:len() >= x:len() && StringSlice(s, 0, x:len()) == x; }"
|
|
"bool CharacterIsAlnum(str c) {"
|
|
" int b = CharacterToByte(c);"
|
|
" return (b >= CharacterToByte(\"A\") && b <= CharacterToByte(\"Z\")) || (b >= CharacterToByte(\"a\") && b <= CharacterToByte(\"z\"))"
|
|
" || (b >= CharacterToByte(\"0\") && b <= CharacterToByte(\"9\"));"
|
|
"}"
|
|
|
|
// Miscellaneous:
|
|
|
|
"int SystemGetProcessorCount() #extcall;"
|
|
"bool SystemRunningAsAdministrator() #extcall;"
|
|
"str SystemGetHostName() #extcall;"
|
|
"void SystemSleepMs(int ms) #extcall;"
|
|
"int RandomInt(int min, int max) #extcall;"
|
|
|
|
"str UUIDGenerate() {"
|
|
" str hexChars = \"0123456789abcdef\";"
|
|
" str result;"
|
|
" for int i = 0; i < 8; i += 1 { result += hexChars[RandomInt(0, 15)]; }"
|
|
" result += \"-\";"
|
|
" for int i = 0; i < 4; i += 1 { result += hexChars[RandomInt(0, 15)]; }"
|
|
" result += \"-4\";"
|
|
" for int i = 0; i < 3; i += 1 { result += hexChars[RandomInt(0, 15)]; }"
|
|
" result += \"-\" + hexChars[RandomInt(8, 11)];"
|
|
" for int i = 0; i < 3; i += 1 { result += hexChars[RandomInt(0, 15)]; }"
|
|
" result += \"-\";"
|
|
" for int i = 0; i < 12; i += 1 { result += hexChars[RandomInt(0, 15)]; }"
|
|
" return result;"
|
|
"}"
|
|
|
|
// File system access:
|
|
|
|
"bool PathExists(str x) #extcall;"
|
|
"bool PathIsFile(str source) #extcall;"
|
|
"bool PathIsDirectory(str source) #extcall;"
|
|
"bool PathIsLink(str source) #extcall;"
|
|
"err[void] PathCreateDirectory(str x) #extcall;"
|
|
"err[void] PathDelete(str x) #extcall;"
|
|
"err[void] PathMove(str source, str destination) #extcall;"
|
|
"str PathGetDefaultPrefix() #extcall;"
|
|
"err[void] PathSetDefaultPrefixToScriptSourceDirectory() #extcall;"
|
|
"err[str] FileReadAll(str path) #extcall;"
|
|
"err[void] FileWriteAll(str path, str x) #extcall;"
|
|
"err[void] FileAppend(str path, str x) #extcall;"
|
|
"err[void] FileCopy(str source, str destination) #extcall;"
|
|
"err[int] FileGetSize(str path) #extcall;"
|
|
|
|
"err[void] _DirectoryInternalStartIteration(str path) #extcall;"
|
|
"str _DirectoryInternalNextIteration() #extcall;"
|
|
"void _DirectoryInternalEndIteration() #extcall;"
|
|
"err[void] _DirectoryInternalEnumerateChildren(str path, str prefix, str[] result, bool recurse) {"
|
|
" reterr _DirectoryInternalStartIteration(path);"
|
|
" str child = _DirectoryInternalNextIteration();"
|
|
" int start = result:len();"
|
|
" while child != \"\" { result:add(child); child = _DirectoryInternalNextIteration(); }"
|
|
" _DirectoryInternalEndIteration();"
|
|
" int end = result:len();"
|
|
" if recurse {"
|
|
" for int i = start; i < end; i += 1 {"
|
|
" str actual = path + \"/\" + result[i];"
|
|
" if PathIsDirectory(actual) {"
|
|
" _DirectoryInternalEnumerateChildren(actual, prefix + result[i] + \"/\", result, true);"
|
|
" }"
|
|
" }"
|
|
" }"
|
|
" for int i = start; i < end; i += 1 {"
|
|
" result[i] = prefix + result[i];"
|
|
" }"
|
|
" return #success;"
|
|
"}"
|
|
"err[str[]] DirectoryEnumerate(str path) {"
|
|
" str[] result = new str[];"
|
|
" err[void] e = _DirectoryInternalEnumerateChildren(path, \"\", result, false);"
|
|
" reterr e; return result;"
|
|
"}"
|
|
"err[str[]] DirectoryEnumerateRecursively(str path) {"
|
|
" str[] result = new str[];"
|
|
" err[void] e = _DirectoryInternalEnumerateChildren(PathTrimTrailingSlash(path), \"\", result, true);"
|
|
" reterr e; return result;"
|
|
"}"
|
|
|
|
"str PathTrimTrailingSlash(str x) { if x:len() > 0 && x[x:len() - 1] == \"/\" { return StringSlice(x, 0, x:len() - 1); } return x; }"
|
|
|
|
"err[void] PathDeleteRecursively(str path) {"
|
|
" err[str[]] e = DirectoryEnumerateRecursively(path);"
|
|
" if str[] all in e {"
|
|
" for int i = 0; i < all:len(); i += 1 {"
|
|
" str p = path + \"/\" + all[i];"
|
|
" if PathIsFile(p) || PathIsLink(p) {"
|
|
" PathDelete(p);"
|
|
" }"
|
|
" }"
|
|
" for int i = all:len(); i > 0; i -= 1 {"
|
|
" str p = path + \"/\" + all[i - 1];"
|
|
" if PathIsDirectory(p) {"
|
|
" PathDelete(p);"
|
|
" }"
|
|
" }"
|
|
" return PathDelete(path);"
|
|
" } else {"
|
|
" return new err[void] e:error();"
|
|
" }"
|
|
"}"
|
|
"err[void] PathCopyRecursively(str source, str destination) {"
|
|
" err[str[]] e = DirectoryEnumerateRecursively(source);"
|
|
" if str[] all in e {"
|
|
" reterr PathCreateDirectory(destination);"
|
|
" for int i = 0; i < all:len(); i += 1 {"
|
|
" str sourceItem = source + \"/\" + all[i];"
|
|
" str destinationItem = destination + \"/\" + all[i];"
|
|
" if PathIsDirectory(sourceItem) {"
|
|
" PathCreateDirectory(destinationItem);"
|
|
" } else {"
|
|
" reterr FileCopy(sourceItem, destinationItem);"
|
|
" }"
|
|
" }"
|
|
" return #success;"
|
|
" } else {"
|
|
" return new err[void] e:error();"
|
|
" }"
|
|
"}"
|
|
"err[void] PathCopyInto(str sourceDirectory, str item, str destinationDirectory) {"
|
|
" str sourceItem = sourceDirectory + \"/\"+ item;"
|
|
" str destinationItem = destinationDirectory + \"/\"+ item;"
|
|
" PathCreateLeadingDirectories(PathGetLeadingDirectories(destinationItem));"
|
|
" if PathIsDirectory(sourceItem) {"
|
|
" return PathCreateDirectory(destinationItem);"
|
|
" } else {"
|
|
" return FileCopy(sourceItem, destinationItem);"
|
|
" }"
|
|
"}"
|
|
"err[void] PathCopyAllInto(str sourceDirectory, str[] items, str destinationDirectory) {"
|
|
" for int i = 0; i < items:len(); i += 1 {"
|
|
" reterr PathCopyInto(sourceDirectory, items[i], destinationDirectory);"
|
|
" }"
|
|
" return #success;"
|
|
"}"
|
|
"err[void] PathCopyFilteredInto(str sourceDirectory, str[] filter, int filterWildcard, str destinationDirectory) {"
|
|
" assert filter:len() > 0;"
|
|
" err[str[]] e;"
|
|
" if filterWildcard != -1 || filter:len() > 1 { e = DirectoryEnumerateRecursively(sourceDirectory); }"
|
|
" else { e = DirectoryEnumerate(sourceDirectory); }"
|
|
" if str[] items in e {"
|
|
" for int i = 0; i < items:len(); i += 1 {"
|
|
" if PathMatchesFilter(items[i], filter, filterWildcard) {"
|
|
" reterr PathCopyInto(sourceDirectory, items[i], destinationDirectory);"
|
|
" }"
|
|
" }"
|
|
" return #success;"
|
|
" } else {"
|
|
" return new err[void] e:error();"
|
|
" }"
|
|
"}"
|
|
|
|
"bool PathMatchesFilter(str path, str[] filterComponents, int _filterWildcard) {"
|
|
" str[] pathComponents = StringSplitByCharacter(path, \"/\", false);"
|
|
" int filterWildcard = _filterWildcard;"
|
|
" if filterWildcard == -1 { "
|
|
" filterWildcard = pathComponents:len(); "
|
|
" }"
|
|
" if filterComponents:len() > pathComponents:len() { "
|
|
" return false; "
|
|
" }"
|
|
" for int i = 0; i < filterComponents:len(); i += 1 {"
|
|
" str filterComponent = filterComponents[i];"
|
|
" str pathComponent;"
|
|
" int wildcard = filterComponent:len();"
|
|
" if i < filterWildcard { "
|
|
" pathComponent = pathComponents[i]; "
|
|
" } else { "
|
|
" pathComponent = pathComponents[pathComponents:len() - filterComponents:len() + i]; "
|
|
" }"
|
|
" for int j = 0; j < filterComponent:len(); j += 1 { "
|
|
" if filterComponent[j] == \"*\" { "
|
|
" wildcard = j; "
|
|
" if pathComponent:len() < filterComponent:len() - 1 {"
|
|
" return false;"
|
|
" }"
|
|
" } "
|
|
" }"
|
|
" for int j = 0; j < filterComponent:len(); j += 1 {"
|
|
" if j < wildcard {"
|
|
" if filterComponent[j] != pathComponent[j] { return false; }"
|
|
" } else if j > wildcard {"
|
|
" if filterComponent[j] != pathComponent[pathComponent:len() - filterComponent:len() + j] { return false; }"
|
|
" }"
|
|
" }"
|
|
" }"
|
|
" return true;"
|
|
"}"
|
|
"str PathGetLeadingDirectories(str path) {"
|
|
" for int i = path:len() - 1; i >= 0; i -= 1 {"
|
|
" if path[i] == \"/\" {"
|
|
" return StringSlice(path, 0, i);"
|
|
" }"
|
|
" }"
|
|
" return \"\";"
|
|
"}"
|
|
"err[void] PathCreateLeadingDirectories(str path) {"
|
|
" for int i = 0; i < path:len(); i += 1 {"
|
|
" if path[i] == \"/\" {"
|
|
" PathCreateDirectory(StringSlice(path, 0, i));"
|
|
" }"
|
|
" }"
|
|
" return PathCreateDirectory(path);"
|
|
"}"
|
|
|
|
// Persistent variables:
|
|
|
|
"bool PersistRead(str path) #extcall;"
|
|
|
|
// Command line:
|
|
|
|
"str ConsoleGetLine() #extcall;"
|
|
"void ConsoleWriteStdout(str x) #extcall;"
|
|
"void ConsoleWriteStderr(str x) #extcall;"
|
|
"err[str] SystemGetEnvironmentVariable(str name) #extcall;"
|
|
"err[void] 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;" // Captures stdout.
|
|
"void SystemShellEnableLogging(bool x) #extcall;"
|
|
};
|
|
|
|
// --------------------------------- External function calls.
|
|
|
|
int ExternalLog(ExecutionContext *context, Value *returnValue);
|
|
int ExternalLogOpenGroup(ExecutionContext *context, Value *returnValue);
|
|
int ExternalLogClose(ExecutionContext *context, Value *returnValue);
|
|
int ExternalTextColorError(ExecutionContext *context, Value *returnValue);
|
|
int ExternalTextColorHighlight(ExecutionContext *context, Value *returnValue);
|
|
int ExternalTextWeight(ExecutionContext *context, Value *returnValue);
|
|
int ExternalTextMonospaced(ExecutionContext *context, Value *returnValue);
|
|
int ExternalTextPlain(ExecutionContext *context, Value *returnValue);
|
|
int ExternalConsoleGetLine(ExecutionContext *context, Value *returnValue);
|
|
int ExternalConsoleWriteStdout(ExecutionContext *context, Value *returnValue);
|
|
int ExternalConsoleWriteStderr(ExecutionContext *context, Value *returnValue);
|
|
int ExternalStringSlice(ExecutionContext *context, Value *returnValue);
|
|
int ExternalCharacterToByte(ExecutionContext *context, Value *returnValue);
|
|
int ExternalSystemShellExecute(ExecutionContext *context, Value *returnValue);
|
|
int ExternalSystemShellExecuteWithWorkingDirectory(ExecutionContext *context, Value *returnValue);
|
|
int ExternalSystemShellEvaluate(ExecutionContext *context, Value *returnValue);
|
|
int ExternalSystemShellEnableLogging(ExecutionContext *context, Value *returnValue);
|
|
int ExternalSystemGetProcessorCount(ExecutionContext *context, Value *returnValue);
|
|
int ExternalSystemGetEnvironmentVariable(ExecutionContext *context, Value *returnValue);
|
|
int ExternalSystemSetEnvironmentVariable(ExecutionContext *context, Value *returnValue);
|
|
int ExternalSystemRunningAsAdministrator(ExecutionContext *context, Value *returnValue);
|
|
int ExternalSystemGetHostName(ExecutionContext *context, Value *returnValue);
|
|
int ExternalSystemSleepMs(ExecutionContext *context, Value *returnValue);
|
|
int ExternalPathCreateDirectory(ExecutionContext *context, Value *returnValue);
|
|
int ExternalPathDelete(ExecutionContext *context, Value *returnValue);
|
|
int ExternalPathExists(ExecutionContext *context, Value *returnValue);
|
|
int ExternalPathIsFile(ExecutionContext *context, Value *returnValue);
|
|
int ExternalPathIsDirectory(ExecutionContext *context, Value *returnValue);
|
|
int ExternalPathIsLink(ExecutionContext *context, Value *returnValue);
|
|
int ExternalPathMove(ExecutionContext *context, Value *returnValue);
|
|
int ExternalPathGetDefaultPrefix(ExecutionContext *context, Value *returnValue);
|
|
int ExternalPathSetDefaultPrefixToScriptSourceDirectory(ExecutionContext *context, Value *returnValue);
|
|
int ExternalFileReadAll(ExecutionContext *context, Value *returnValue);
|
|
int ExternalFileWriteAll(ExecutionContext *context, Value *returnValue);
|
|
int ExternalFileAppend(ExecutionContext *context, Value *returnValue);
|
|
int ExternalFileCopy(ExecutionContext *context, Value *returnValue);
|
|
int ExternalFileGetSize(ExecutionContext *context, Value *returnValue);
|
|
int ExternalPersistRead(ExecutionContext *context, Value *returnValue);
|
|
int ExternalPersistWrite(ExecutionContext *context, Value *returnValue);
|
|
int ExternalRandomInt(ExecutionContext *context, Value *returnValue); // TODO This shouldn't be in the platform layer.
|
|
int External_DirectoryInternalStartIteration(ExecutionContext *context, Value *returnValue);
|
|
int External_DirectoryInternalNextIteration(ExecutionContext *context, Value *returnValue);
|
|
int External_DirectoryInternalEndIteration(ExecutionContext *context, Value *returnValue);
|
|
int ExternalOpenDocumentEnumerate(ExecutionContext *context, Value *returnValue);
|
|
|
|
ExternalFunction externalFunctions[] = {
|
|
{ .cName = "Log", .callback = ExternalLog },
|
|
{ .cName = "LogOpenGroup", .callback = ExternalLogOpenGroup },
|
|
{ .cName = "LogClose", .callback = ExternalLogClose },
|
|
{ .cName = "TextColorError", .callback = ExternalTextColorError },
|
|
{ .cName = "TextColorHighlight", .callback = ExternalTextColorHighlight },
|
|
{ .cName = "TextWeight", .callback = ExternalTextWeight },
|
|
{ .cName = "TextMonospaced", .callback = ExternalTextMonospaced },
|
|
{ .cName = "TextPlain", .callback = ExternalTextPlain },
|
|
{ .cName = "ConsoleGetLine", .callback = ExternalConsoleGetLine },
|
|
{ .cName = "ConsoleWriteStdout", .callback = ExternalConsoleWriteStdout },
|
|
{ .cName = "ConsoleWriteStderr", .callback = ExternalConsoleWriteStderr },
|
|
{ .cName = "StringSlice", .callback = ExternalStringSlice },
|
|
{ .cName = "CharacterToByte", .callback = ExternalCharacterToByte },
|
|
{ .cName = "SystemShellExecute", .callback = ExternalSystemShellExecute },
|
|
{ .cName = "SystemShellExecuteWithWorkingDirectory", .callback = ExternalSystemShellExecuteWithWorkingDirectory },
|
|
{ .cName = "SystemShellEvaluate", .callback = ExternalSystemShellEvaluate },
|
|
{ .cName = "SystemShellEnableLogging", .callback = ExternalSystemShellEnableLogging },
|
|
{ .cName = "SystemGetProcessorCount", .callback = ExternalSystemGetProcessorCount },
|
|
{ .cName = "SystemGetEnvironmentVariable", .callback = ExternalSystemGetEnvironmentVariable },
|
|
{ .cName = "SystemSetEnvironmentVariable", .callback = ExternalSystemSetEnvironmentVariable },
|
|
{ .cName = "SystemRunningAsAdministrator", .callback = ExternalSystemRunningAsAdministrator },
|
|
{ .cName = "SystemGetHostName", .callback = ExternalSystemGetHostName },
|
|
{ .cName = "SystemSleepMs", .callback = ExternalSystemSleepMs },
|
|
{ .cName = "PathExists", .callback = ExternalPathExists },
|
|
{ .cName = "PathIsFile", .callback = ExternalPathIsFile },
|
|
{ .cName = "PathIsDirectory", .callback = ExternalPathIsDirectory },
|
|
{ .cName = "PathIsLink", .callback = ExternalPathIsLink },
|
|
{ .cName = "PathCreateDirectory", .callback = ExternalPathCreateDirectory },
|
|
{ .cName = "PathDelete", .callback = ExternalPathDelete },
|
|
{ .cName = "PathMove", .callback = ExternalPathMove },
|
|
{ .cName = "PathGetDefaultPrefix", .callback = ExternalPathGetDefaultPrefix },
|
|
{ .cName = "PathSetDefaultPrefixToScriptSourceDirectory", .callback = ExternalPathSetDefaultPrefixToScriptSourceDirectory },
|
|
{ .cName = "FileReadAll", .callback = ExternalFileReadAll },
|
|
{ .cName = "FileWriteAll", .callback = ExternalFileWriteAll },
|
|
{ .cName = "FileAppend", .callback = ExternalFileAppend },
|
|
{ .cName = "FileCopy", .callback = ExternalFileCopy },
|
|
{ .cName = "FileGetSize", .callback = ExternalFileGetSize },
|
|
{ .cName = "PersistRead", .callback = ExternalPersistRead },
|
|
{ .cName = "PersistWrite", .callback = ExternalPersistWrite },
|
|
{ .cName = "RandomInt", .callback = ExternalRandomInt },
|
|
{ .cName = "_DirectoryInternalStartIteration", .callback = External_DirectoryInternalStartIteration },
|
|
{ .cName = "_DirectoryInternalNextIteration", .callback = External_DirectoryInternalNextIteration },
|
|
{ .cName = "_DirectoryInternalEndIteration", .callback = External_DirectoryInternalEndIteration },
|
|
{ .cName = "OpenDocumentEnumerate", .callback = ExternalOpenDocumentEnumerate },
|
|
};
|
|
|
|
// --------------------------------- Tokenization and parsing.
|
|
|
|
uint8_t TokenLookupPrecedence(uint8_t t) {
|
|
if (t == T_EQUALS) return 10;
|
|
if (t == T_ADD_EQUALS) return 10;
|
|
if (t == T_MINUS_EQUALS) return 10;
|
|
if (t == T_ASTERISK_EQUALS) return 10;
|
|
if (t == T_SLASH_EQUALS) return 10;
|
|
if (t == T_COMMA) return 12;
|
|
if (t == T_LOGICAL_OR) return 14;
|
|
if (t == T_LOGICAL_AND) return 15;
|
|
if (t == T_BITWISE_OR) return 16;
|
|
if (t == T_BITWISE_XOR) return 17;
|
|
if (t == T_BITWISE_AND) return 18;
|
|
if (t == T_DOUBLE_EQUALS) return 20;
|
|
if (t == T_NOT_EQUALS) return 20;
|
|
if (t == T_GREATER_THAN) return 25;
|
|
if (t == T_LESS_THAN) return 25;
|
|
if (t == T_GT_OR_EQUAL) return 25;
|
|
if (t == T_LT_OR_EQUAL) return 25;
|
|
if (t == T_BIT_SHIFT_LEFT) return 30;
|
|
if (t == T_BIT_SHIFT_RIGHT) return 30;
|
|
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_BITWISE_NOT) return 70;
|
|
if (t == T_NEGATE) return 70;
|
|
if (t == T_NEW) return 75;
|
|
if (t == T_DOT) return 80;
|
|
if (t == T_COLON) return 80;
|
|
if (t == T_AWAIT) return 90;
|
|
if (t == T_LEFT_ROUND) return 100;
|
|
Assert(false);
|
|
return 0;
|
|
}
|
|
|
|
Token TokenNext(Tokenizer *tokenizer) {
|
|
// TODO Block comments.
|
|
|
|
Token token = { 0 };
|
|
token.type = T_ERROR;
|
|
token.module = tokenizer->module;
|
|
|
|
while (true) {
|
|
if (tokenizer->position == tokenizer->inputBytes) {
|
|
token.type = T_EOF;
|
|
break;
|
|
}
|
|
|
|
uint8_t c = tokenizer->input[tokenizer->position];
|
|
uint8_t c1 = tokenizer->position + 1 == tokenizer->inputBytes ? 0 : tokenizer->input[tokenizer->position + 1];
|
|
token.text = &tokenizer->input[tokenizer->position];
|
|
token.textBytes = 1;
|
|
token.line = tokenizer->line;
|
|
|
|
if (c == ' ' || c == '\t' || c == '\r') {
|
|
tokenizer->position++;
|
|
continue;
|
|
} else if (c == '\n') {
|
|
tokenizer->position++;
|
|
tokenizer->line++;
|
|
continue;
|
|
} else if (c == '/' && c1 == '/') {
|
|
while (tokenizer->position != tokenizer->inputBytes && tokenizer->input[tokenizer->position] != '\n') {
|
|
tokenizer->position++;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
else if (c == '<' && c1 == '=' && (tokenizer->position += 2)) token.type = T_LT_OR_EQUAL;
|
|
else if (c == '>' && c1 == '=' && (tokenizer->position += 2)) token.type = T_GT_OR_EQUAL;
|
|
else if (c == '=' && c1 == '=' && (tokenizer->position += 2)) token.type = T_DOUBLE_EQUALS;
|
|
else if (c == '!' && c1 == '=' && (tokenizer->position += 2)) token.type = T_NOT_EQUALS;
|
|
else if (c == '&' && c1 == '&' && (tokenizer->position += 2)) token.type = T_LOGICAL_AND;
|
|
else if (c == '|' && c1 == '|' && (tokenizer->position += 2)) token.type = T_LOGICAL_OR;
|
|
else if (c == '+' && c1 == '=' && (tokenizer->position += 2)) token.type = T_ADD_EQUALS;
|
|
else if (c == '-' && c1 == '=' && (tokenizer->position += 2)) token.type = T_MINUS_EQUALS;
|
|
else if (c == '*' && c1 == '=' && (tokenizer->position += 2)) token.type = T_ASTERISK_EQUALS;
|
|
else if (c == '/' && c1 == '=' && (tokenizer->position += 2)) token.type = T_SLASH_EQUALS;
|
|
else if (c == '<' && c1 == '<' && (tokenizer->position += 2)) token.type = T_BIT_SHIFT_LEFT;
|
|
else if (c == '>' && c1 == '>' && (tokenizer->position += 2)) token.type = T_BIT_SHIFT_RIGHT;
|
|
else if (c == '~' && c1 == '|' && (tokenizer->position += 2)) token.type = T_BITWISE_XOR;
|
|
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 == '|' && ++tokenizer->position) token.type = T_BITWISE_OR;
|
|
else if (c == '&' && ++tokenizer->position) token.type = T_BITWISE_AND;
|
|
else if (c == '~' && ++tokenizer->position) token.type = T_BITWISE_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("#library") token.type = T_LIBRARY;
|
|
else if KEYWORD("#option") token.type = T_OPTION;
|
|
else if KEYWORD("#persist") token.type = T_PERSIST;
|
|
else if KEYWORD("#success") token.type = T_SUCCESS;
|
|
else if KEYWORD("anytype") token.type = T_ANYTYPE;
|
|
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("err") token.type = T_ERR;
|
|
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("handletype") token.type = T_HANDLETYPE;
|
|
else if KEYWORD("if") token.type = T_IF;
|
|
else if KEYWORD("in") token.type = T_IN;
|
|
else if KEYWORD("int") token.type = T_INT;
|
|
else if KEYWORD("inttype") token.type = T_INTTYPE;
|
|
else if KEYWORD("new") token.type = T_NEW;
|
|
else if KEYWORD("null") token.type = T_NULL;
|
|
else if KEYWORD("reterr") token.type = T_RETERR;
|
|
else if KEYWORD("return") token.type = T_RETURN;
|
|
else if KEYWORD("str") token.type = T_STR;
|
|
else if KEYWORD("struct") token.type = T_STRUCT;
|
|
else if KEYWORD("true") token.type = T_TRUE;
|
|
else if KEYWORD("tuple") token.type = T_TUPLE;
|
|
else if KEYWORD("void") token.type = T_VOID;
|
|
else if KEYWORD("while") token.type = T_WHILE;
|
|
|
|
else if (token.text[0] == '#') {
|
|
PrintError(tokenizer, "Unrecognised #-token '%.*s'.\n", token.textBytes, token.text);
|
|
tokenizer->error = true;
|
|
token.type = T_ERROR;
|
|
break;
|
|
}
|
|
} else if (c >= '0' && c <= '9') {
|
|
// TODO Exponent notation.
|
|
|
|
token.textBytes = 0;
|
|
token.type = T_NUMERIC_LITERAL;
|
|
token.text = tokenizer->input + tokenizer->position;
|
|
|
|
while ((c >= '0' && c <= '9') || (c == '.')) {
|
|
tokenizer->position++;
|
|
token.textBytes++;
|
|
if (tokenizer->position == tokenizer->inputBytes) break;
|
|
c = tokenizer->input[tokenizer->position];
|
|
}
|
|
|
|
if (token.textBytes == 1 && token.text[0] == '0') {
|
|
token.type = T_ZERO;
|
|
}
|
|
} else if (c == '"') {
|
|
// TODO Escape sequence to insert an arbitrary codepoint.
|
|
|
|
bool inInterpolation = false;
|
|
intptr_t startPosition = ++tokenizer->position;
|
|
intptr_t endPosition = -1;
|
|
|
|
for (uintptr_t i = tokenizer->position; true; i++) {
|
|
if (inInterpolation) {
|
|
if (tokenizer->input[i] == '%') {
|
|
inInterpolation = false;
|
|
} else if (tokenizer->input[i] == '"') {
|
|
PrintError(tokenizer, "Strings are not allowed within a string interpolation expression.\n");
|
|
tokenizer->error = true;
|
|
break;
|
|
} else if (tokenizer->input[i] == '\n') {
|
|
PrintError(tokenizer, "String interpolation expressions must stay on a single line.\n");
|
|
tokenizer->error = true;
|
|
break;
|
|
} else {
|
|
token.textBytes++;
|
|
}
|
|
} else if (i == tokenizer->inputBytes || tokenizer->input[i] == '\n') {
|
|
PrintError(tokenizer, "String does not end before the end of the line.\n");
|
|
tokenizer->error = true;
|
|
break;
|
|
} else if (tokenizer->input[i] == '"') {
|
|
endPosition = i;
|
|
break;
|
|
} else if (tokenizer->input[i] == '%') {
|
|
inInterpolation = true;
|
|
} else if (tokenizer->input[i] == '\\') {
|
|
if (i + 1 == tokenizer->inputBytes
|
|
|| (tokenizer->input[i + 1] != 'n' && tokenizer->input[i + 1] != 't'
|
|
&& tokenizer->input[i + 1] != 'r'
|
|
&& tokenizer->input[i + 1] != '%'
|
|
&& tokenizer->input[i + 1] != '"' && tokenizer->input[i + 1] != '\\')) {
|
|
PrintError(tokenizer, "String contains unrecognized escape sequence '\\%c'. "
|
|
"Possibilities are: '\\\\', '\\%%', '\\n', '\\r', '\\t' and '\\\"'\n", tokenizer->input[i + 1]);
|
|
tokenizer->error = true;
|
|
break;
|
|
} else {
|
|
i++;
|
|
token.textBytes++;
|
|
}
|
|
} else {
|
|
token.textBytes++;
|
|
}
|
|
}
|
|
|
|
if (endPosition != -1) {
|
|
token.text = tokenizer->input + startPosition;
|
|
token.textBytes = endPosition - startPosition;
|
|
token.type = T_STRING_LITERAL;
|
|
tokenizer->position = endPosition + 1;
|
|
}
|
|
} else {
|
|
PrintError(tokenizer, "Unexpected character '%c'.\n", c);
|
|
tokenizer->error = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return token;
|
|
}
|
|
|
|
Token TokenPeek(Tokenizer *tokenizer) {
|
|
Tokenizer copy = *tokenizer;
|
|
return TokenNext(©);
|
|
}
|
|
|
|
Node *ParseType(Tokenizer *tokenizer, bool maybe, bool allowVoid, bool allowTuple) {
|
|
Node *node = (Node *) AllocateFixed(sizeof(Node));
|
|
node->token = TokenNext(tokenizer);
|
|
|
|
if (node->token.type == T_INT
|
|
|| node->token.type == T_FLOAT
|
|
|| node->token.type == T_STR
|
|
|| node->token.type == T_BOOL
|
|
|| node->token.type == T_VOID
|
|
|| node->token.type == T_TUPLE
|
|
|| node->token.type == T_ERR
|
|
|| node->token.type == T_ANYTYPE
|
|
|| node->token.type == T_IDENTIFIER) {
|
|
node->type = node->token.type;
|
|
|
|
if (!allowVoid && node->type == T_VOID) {
|
|
if (!maybe) {
|
|
PrintError2(tokenizer, node, "The 'void' type is not allowed here.\n");
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if (!allowTuple && node->type == T_TUPLE) {
|
|
if (!maybe) {
|
|
PrintError2(tokenizer, node, "The 'tuple' type is not allowed here.\n");
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if (node->type == T_TUPLE) {
|
|
Token token = TokenNext(tokenizer);
|
|
|
|
if (token.type != T_LEFT_SQUARE) {
|
|
if (!maybe) PrintError2(tokenizer, node, "Expected a '[' after 'tuple'.\n");
|
|
return NULL;
|
|
}
|
|
|
|
Node **link = &node->firstChild;
|
|
bool needComma = false;
|
|
int count = 0;
|
|
|
|
while (true) {
|
|
token = TokenPeek(tokenizer);
|
|
|
|
if (token.type == T_RIGHT_SQUARE) {
|
|
TokenNext(tokenizer);
|
|
break;
|
|
} else if (needComma) {
|
|
if (token.type == T_COMMA) {
|
|
TokenNext(tokenizer);
|
|
} else {
|
|
if (!maybe) PrintError2(tokenizer, node, "Expected a ',' or ']' in the type list for 'tuple'.\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (count == FUNCTION_MAX_ARGUMENTS) {
|
|
if (!maybe) PrintError2(tokenizer, node, "Too many values in the tuple (maximum is %d).\n", FUNCTION_MAX_ARGUMENTS);
|
|
return NULL;
|
|
}
|
|
|
|
Node *n = ParseType(tokenizer, maybe, false, false);
|
|
if (!n) return NULL;
|
|
*link = n;
|
|
link = &n->sibling;
|
|
needComma = true;
|
|
count++;
|
|
}
|
|
|
|
if (!node->firstChild || !node->firstChild->sibling) {
|
|
if (!maybe) PrintError2(tokenizer, node, "A tuple must have at least two types in its list.\n");
|
|
return NULL;
|
|
}
|
|
} else if (node->type == T_ERR) {
|
|
Token token = TokenNext(tokenizer);
|
|
|
|
if (token.type != T_LEFT_SQUARE) {
|
|
if (!maybe) PrintError2(tokenizer, node, "Expected a '[' after 'err'.\n");
|
|
return NULL;
|
|
}
|
|
|
|
Node *n = ParseType(tokenizer, maybe, true /* allow void */, false);
|
|
if (!n) return NULL;
|
|
node->firstChild = n;
|
|
|
|
token = TokenNext(tokenizer);
|
|
|
|
if (token.type != T_RIGHT_SQUARE) {
|
|
if (!maybe) PrintError2(tokenizer, node, "Expected a ']' after the type of 'err'.\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (n->type == T_ERR) {
|
|
if (!maybe) PrintError2(tokenizer, node, "Error types cannot be nested.\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 a builtin type or an identifier.\n");
|
|
return NULL;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
Node *ParseCall(Tokenizer *tokenizer, Node *function) {
|
|
Node *call = (Node *) AllocateFixed(sizeof(Node));
|
|
call->token = TokenNext(tokenizer);
|
|
call->type = T_CALL;
|
|
call->firstChild = function;
|
|
Node *arguments = (Node *) AllocateFixed(sizeof(Node));
|
|
arguments->type = T_ARGUMENTS;
|
|
function->sibling = arguments;
|
|
Node **link = &arguments->firstChild;
|
|
|
|
if (call->token.type != T_LEFT_ROUND) {
|
|
PrintError2(tokenizer, call, "Expected a '(' to start the list of arguments.\n");
|
|
return NULL;
|
|
}
|
|
|
|
while (true) {
|
|
Token token = TokenPeek(tokenizer);
|
|
|
|
if (token.type == T_RIGHT_ROUND) {
|
|
TokenNext(tokenizer);
|
|
break;
|
|
} else if (token.type == T_ERROR) {
|
|
return NULL;
|
|
}
|
|
|
|
if (arguments->firstChild) {
|
|
Token comma = TokenNext(tokenizer);
|
|
|
|
if (comma.type != T_COMMA) {
|
|
Node n = { .token = comma };
|
|
PrintError2(tokenizer, &n, "Expected a comma to separate function arguments.\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
Node *argument = ParseExpression(tokenizer, false, 0);
|
|
if (!argument) return NULL;
|
|
*link = argument;
|
|
link = &argument->sibling;
|
|
}
|
|
|
|
return call;
|
|
}
|
|
|
|
Node *ParseExpression(Tokenizer *tokenizer, bool allowAssignment, uint8_t precedence) {
|
|
Node *node = (Node *) AllocateFixed(sizeof(Node));
|
|
node->token = TokenNext(tokenizer);
|
|
if (node->token.type == T_ERROR) return NULL;
|
|
|
|
if (node->token.type == T_IDENTIFIER) {
|
|
node->type = T_VARIABLE;
|
|
} else if (node->token.type == T_STRING_LITERAL) {
|
|
Node *string = node;
|
|
string->type = string->token.type;
|
|
|
|
const char *raw = string->token.text;
|
|
size_t rawBytes = string->token.textBytes;
|
|
|
|
// It's impossible for size of the string to increase.
|
|
char *output = (char *) AllocateFixed(rawBytes);
|
|
size_t outputPosition = 0;
|
|
|
|
string->token.text = output;
|
|
string->token.textBytes = 0;
|
|
|
|
for (uintptr_t i = 0; i < rawBytes; i++) {
|
|
char c = raw[i];
|
|
|
|
if (c == '\\') {
|
|
Assert(i != rawBytes - 1);
|
|
c = raw[++i];
|
|
Assert(outputPosition != rawBytes);
|
|
if (c == '\\') c = '\\';
|
|
else if (c == 'n') c = '\n';
|
|
else if (c == 'r') c = '\r';
|
|
else if (c == 't') c = '\t';
|
|
else if (c == '%') c = '%';
|
|
else if (c == '"') c = '"';
|
|
else Assert(false);
|
|
output[outputPosition++] = c;
|
|
string->token.textBytes++;
|
|
} else if (c == '%') {
|
|
Node *stringInterpolate = (Node *) AllocateFixed(sizeof(Node));
|
|
stringInterpolate->type = T_STR_INTERPOLATE;
|
|
stringInterpolate->firstChild = node;
|
|
Tokenizer t = *tokenizer;
|
|
t.position = raw - t.input + i + 1;
|
|
stringInterpolate->firstChild->sibling = ParseExpression(&t, false, 0);
|
|
if (!stringInterpolate->firstChild->sibling) return NULL;
|
|
i = t.position - (raw - t.input);
|
|
string = (Node *) AllocateFixed(sizeof(Node));
|
|
string->type = T_STRING_LITERAL;
|
|
string->token.text = output + outputPosition;
|
|
string->token.textBytes = 0;
|
|
stringInterpolate->firstChild->sibling->sibling = string;
|
|
node = stringInterpolate;
|
|
} else {
|
|
Assert(outputPosition != rawBytes);
|
|
output[outputPosition++] = c;
|
|
string->token.textBytes++;
|
|
}
|
|
}
|
|
} else if (node->token.type == T_NUMERIC_LITERAL || node->token.type == T_SUCCESS || node->token.type == T_ZERO
|
|
|| node->token.type == T_TRUE || node->token.type == T_FALSE || node->token.type == T_NULL) {
|
|
node->type = node->token.type;
|
|
} else if (node->token.type == T_LOGICAL_NOT || node->token.type == T_MINUS || node->token.type == T_BITWISE_NOT) {
|
|
node->type = node->token.type == T_MINUS ? T_NEGATE : node->token.type;
|
|
node->firstChild = ParseExpression(tokenizer, false, TokenLookupPrecedence(node->type));
|
|
} else if (node->token.type == T_LEFT_ROUND) {
|
|
node = ParseExpression(tokenizer, false, 0);
|
|
if (!node) return NULL;
|
|
|
|
Token token = TokenNext(tokenizer);
|
|
|
|
if (token.type != T_RIGHT_ROUND) {
|
|
Node n = { .token = token };
|
|
PrintError2(tokenizer, &n, "Expected a matching closing bracket.\n");
|
|
return NULL;
|
|
}
|
|
} else if (node->token.type == T_NEW) {
|
|
node->type = T_NEW;
|
|
node->firstChild = ParseType(tokenizer, false, false /* allow void */, false /* allow tuple */);
|
|
node->expressionType = node->firstChild;
|
|
if (!node->firstChild) return NULL;
|
|
|
|
if (node->firstChild->type == T_ERR) {
|
|
node->firstChild->sibling = ParseExpression(tokenizer, false, TokenLookupPrecedence(node->type));
|
|
if (!node->firstChild->sibling) return NULL;
|
|
}
|
|
} else if (node->token.type == T_LEFT_SQUARE) {
|
|
node->type = T_LIST_LITERAL;
|
|
Node **link = &node->firstChild;
|
|
bool needComma = false;
|
|
|
|
while (true) {
|
|
Token peek = TokenPeek(tokenizer);
|
|
|
|
if (peek.type == T_ERROR) {
|
|
return NULL;
|
|
} else if (peek.type == T_RIGHT_SQUARE) {
|
|
TokenNext(tokenizer);
|
|
break;
|
|
}
|
|
|
|
if (needComma) {
|
|
if (peek.type != T_COMMA) {
|
|
PrintError(tokenizer, "Expected a comma between list literal items.\n");
|
|
return NULL;
|
|
} else {
|
|
TokenNext(tokenizer);
|
|
}
|
|
}
|
|
|
|
Node *item = ParseExpression(tokenizer, false, 0);
|
|
if (!item) return NULL;
|
|
*link = item;
|
|
link = &item->sibling;
|
|
needComma = true;
|
|
}
|
|
} else if (node->token.type == T_AWAIT) {
|
|
node->type = T_AWAIT;
|
|
node->firstChild = ParseExpression(tokenizer, false, TokenLookupPrecedence(node->token.type));
|
|
} else {
|
|
PrintError2(tokenizer, node, "Expected an expression. "
|
|
"Expressions can start with a variable identifier, a string literal, a number, 'await', 'new', '-', '!', '[' or '('.\n");
|
|
return NULL;
|
|
}
|
|
|
|
while (true) {
|
|
Token token = TokenPeek(tokenizer);
|
|
|
|
if (token.type == T_ERROR) {
|
|
return NULL;
|
|
} else if ((token.type == T_EQUALS || token.type == T_ADD_EQUALS || token.type == T_MINUS_EQUALS
|
|
|| token.type == T_ASTERISK_EQUALS || token.type == T_SLASH_EQUALS) && !allowAssignment) {
|
|
PrintError2(tokenizer, node, "Variable assignment is not allowed within an expression.\n");
|
|
return NULL;
|
|
} else if (token.type == T_DOT && TokenLookupPrecedence(token.type) > precedence) {
|
|
TokenNext(tokenizer);
|
|
Node *operation = (Node *) AllocateFixed(sizeof(Node));
|
|
operation->token = TokenNext(tokenizer);
|
|
operation->type = T_DOT;
|
|
operation->firstChild = node;
|
|
node = operation;
|
|
|
|
if (operation->token.type != T_IDENTIFIER) {
|
|
PrintError2(tokenizer, node, "Expected an identifier for the struct field after '.'.\n");
|
|
return NULL;
|
|
}
|
|
} else if (token.type == T_COLON && TokenLookupPrecedence(token.type) > precedence) {
|
|
TokenNext(tokenizer);
|
|
Token operationName = TokenNext(tokenizer);
|
|
|
|
if (operationName.type != T_IDENTIFIER && operationName.type != T_ASSERT && operationName.type != T_FLOAT) {
|
|
PrintError2(tokenizer, node, "Expected an identifier for the operation name after ':'.\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (operationName.textBytes == 4 && 0 == MemoryCompare(operationName.text, "cast", 4)) {
|
|
Node *n = node;
|
|
node = (Node *) AllocateFixed(sizeof(Node));
|
|
node->token = TokenNext(tokenizer);
|
|
|
|
if (node->token.type == T_ERROR) {
|
|
return NULL;
|
|
} else if (node->token.type != T_LEFT_ROUND) {
|
|
PrintError2(tokenizer, node, "Expected a '(' before the type to cast to.\n");
|
|
return NULL;
|
|
}
|
|
|
|
node->firstChild = n;
|
|
Node *type = ParseType(tokenizer, false, false, false);
|
|
if (!type) return NULL;
|
|
node->firstChild->sibling = (Node *) AllocateFixed(sizeof(Node));
|
|
node->firstChild->sibling->type = T_CAST_TYPE_WRAPPER;
|
|
node->firstChild->sibling->firstChild = type;
|
|
|
|
Token token = TokenNext(tokenizer);
|
|
|
|
if (token.type == T_ERROR) {
|
|
return NULL;
|
|
} else if (token.type != T_RIGHT_ROUND) {
|
|
PrintError2(tokenizer, node, "Expected a ')' after the type to cast to.\n");
|
|
return NULL;
|
|
}
|
|
} else {
|
|
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_BITWISE_XOR
|
|
|| token.type == T_ASTERISK || token.type == T_SLASH
|
|
|| token.type == T_GREATER_THAN || token.type == T_LESS_THAN
|
|
|| token.type == T_BIT_SHIFT_LEFT || token.type == T_BIT_SHIFT_RIGHT
|
|
|| token.type == T_BITWISE_OR || token.type == T_BITWISE_AND
|
|
|| token.type == T_LT_OR_EQUAL || token.type == T_GT_OR_EQUAL
|
|
|| token.type == T_DOUBLE_EQUALS || token.type == T_NOT_EQUALS
|
|
|| token.type == T_LOGICAL_AND || token.type == T_LOGICAL_OR)
|
|
&& TokenLookupPrecedence(token.type) > precedence) {
|
|
Node *operation = (Node *) AllocateFixed(sizeof(Node));
|
|
operation->token = TokenNext(tokenizer);
|
|
operation->type = operation->token.type;
|
|
operation->firstChild = node;
|
|
node->sibling = ParseExpression(tokenizer, false, TokenLookupPrecedence(token.type));
|
|
if (!node->sibling) return NULL;
|
|
node = operation;
|
|
} else if (token.type == T_ADD_EQUALS || token.type == T_MINUS_EQUALS
|
|
|| token.type == T_ASTERISK_EQUALS || token.type == T_SLASH_EQUALS) {
|
|
Node *operation = (Node *) AllocateFixed(sizeof(Node));
|
|
operation->token = TokenNext(tokenizer);
|
|
operation->type = operation->token.type - T_ADD_EQUALS + T_ADD;
|
|
operation->firstChild = node;
|
|
node->sibling = ParseExpression(tokenizer, false, TokenLookupPrecedence(token.type));
|
|
if (!node->sibling) return NULL;
|
|
Node *nodeCopy = (Node *) AllocateFixed(sizeof(Node));
|
|
*nodeCopy = *node;
|
|
node = operation;
|
|
operation = (Node *) AllocateFixed(sizeof(Node));
|
|
operation->token = token;
|
|
operation->type = T_EQUALS;
|
|
operation->firstChild = nodeCopy;
|
|
operation->firstChild->sibling = node;
|
|
node = operation;
|
|
} else if (token.type == T_LEFT_ROUND && TokenLookupPrecedence(token.type) > precedence) {
|
|
node = ParseCall(tokenizer, node);
|
|
if (!node) return NULL;
|
|
} else if (token.type == T_LEFT_SQUARE) {
|
|
TokenNext(tokenizer);
|
|
Node *index = (Node *) AllocateFixed(sizeof(Node));
|
|
index->type = T_INDEX;
|
|
index->token = token;
|
|
index->firstChild = node;
|
|
index->firstChild->sibling = ParseExpression(tokenizer, false, 0);
|
|
if (!index->firstChild->sibling) return NULL;
|
|
node = index;
|
|
Token token = TokenNext(tokenizer);
|
|
|
|
if (token.type != T_RIGHT_SQUARE) {
|
|
Node n = { .token = token };
|
|
PrintError2(tokenizer, &n, "Expected a matching closing bracket.\n");
|
|
return NULL;
|
|
}
|
|
} else if (token.type == T_COMMA && TokenLookupPrecedence(token.type) >= precedence && allowAssignment) {
|
|
Node *operation = (Node *) AllocateFixed(sizeof(Node));
|
|
operation->token = TokenNext(tokenizer);
|
|
operation->type = T_SET_GROUP;
|
|
operation->firstChild = node;
|
|
node->sibling = ParseExpression(tokenizer, true, TokenLookupPrecedence(token.type));
|
|
if (!node->sibling) return NULL;
|
|
node = operation;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
Node *ParseIf(Tokenizer *tokenizer) {
|
|
Node *node = (Node *) AllocateFixed(sizeof(Node));
|
|
node->type = T_IF;
|
|
node->token = TokenNext(tokenizer);
|
|
Node **link = &node->firstChild;
|
|
|
|
{
|
|
Tokenizer copy = *tokenizer;
|
|
Node *type = ParseType(tokenizer, true /* maybe */, false, false);
|
|
|
|
if (tokenizer->error) {
|
|
return NULL;
|
|
} else if (type) {
|
|
Token name = TokenNext(tokenizer);
|
|
|
|
if (tokenizer->error) {
|
|
return NULL;
|
|
} else if (name.type == T_IDENTIFIER) {
|
|
Token in = TokenNext(tokenizer);
|
|
|
|
if (tokenizer->error) {
|
|
return NULL;
|
|
} else if (in.type == T_IN) {
|
|
node->type = T_IF_ERR;
|
|
|
|
Node *declaration = (Node *) AllocateFixed(sizeof(Node));
|
|
declaration->type = T_DECLARE;
|
|
declaration->token = name;
|
|
declaration->firstChild = type;
|
|
node->firstChild = declaration;
|
|
link = &declaration->sibling;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (node->type != T_IF_ERR) {
|
|
*tokenizer = copy;
|
|
}
|
|
}
|
|
|
|
*link = ParseExpression(tokenizer, false, 0);
|
|
if (!(*link)) return NULL;
|
|
Token token = TokenPeek(tokenizer);
|
|
|
|
if (token.type == T_LEFT_FANCY) {
|
|
TokenNext(tokenizer);
|
|
(*link)->sibling = ParseBlock(tokenizer, false);
|
|
if (!(*link)->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;
|
|
(*link)->sibling = wrapper;
|
|
|
|
Token semicolon = TokenNext(tokenizer);
|
|
|
|
if (semicolon.type != T_SEMICOLON) {
|
|
PrintError2(tokenizer, wrapper->firstChild, "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) {
|
|
(*link)->sibling->sibling = ParseIf(tokenizer);
|
|
if (!(*link)->sibling->sibling) return NULL;
|
|
} else if (token.type == T_LEFT_FANCY) {
|
|
TokenNext(tokenizer);
|
|
(*link)->sibling->sibling = ParseBlock(tokenizer, false);
|
|
if (!(*link)->sibling->sibling) return NULL;
|
|
} else {
|
|
(*link)->sibling->sibling = ParseExpression(tokenizer, true, 0);
|
|
if (!(*link)->sibling->sibling) return NULL;
|
|
|
|
Token semicolon = TokenNext(tokenizer);
|
|
|
|
if (semicolon.type != T_SEMICOLON) {
|
|
PrintError2(tokenizer, (*link)->sibling, "Expected a semicolon at the end of the expression.\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make sure that the if-err variable has its own scope.
|
|
Node *wrapper = (Node *) AllocateFixed(sizeof(Node));
|
|
wrapper->type = T_BLOCK;
|
|
wrapper->token = node->token;
|
|
wrapper->firstChild = node;
|
|
return wrapper;
|
|
}
|
|
|
|
Node *ParseVariableDeclarationOrExpression(Tokenizer *tokenizer, bool replMode, bool allowIn) {
|
|
Tokenizer copy = *tokenizer;
|
|
bool isVariableDeclaration = false;
|
|
|
|
if (ParseType(tokenizer, true, false /* allow void */, false /* allow tuple */) && TokenNext(tokenizer).type == T_IDENTIFIER) {
|
|
Token token = TokenNext(tokenizer);
|
|
isVariableDeclaration = token.type == T_EQUALS || token.type == T_SEMICOLON || token.type == T_COMMA || token.type == T_IN;
|
|
}
|
|
|
|
if (tokenizer->error) {
|
|
return NULL;
|
|
}
|
|
|
|
*tokenizer = copy;
|
|
|
|
if (isVariableDeclaration) {
|
|
// TODO Variable support in replMode.
|
|
if (replMode) {
|
|
PrintError(tokenizer, "Variables are not yet supported in the console.\n");
|
|
return NULL;
|
|
}
|
|
|
|
Node *declaration = (Node *) AllocateFixed(sizeof(Node));
|
|
declaration->type = T_DECLARE;
|
|
declaration->firstChild = ParseType(tokenizer, false, false /* allow void */, false /* allow tuple */);
|
|
Assert(declaration->firstChild);
|
|
declaration->token = TokenNext(tokenizer);
|
|
Assert(declaration->token.type == T_IDENTIFIER);
|
|
|
|
Tokenizer copy = *tokenizer;
|
|
Token token = TokenNext(tokenizer);
|
|
Assert(token.type == T_EQUALS || token.type == T_SEMICOLON || token.type == T_COMMA || token.type == T_IN);
|
|
|
|
if (token.type == T_IN && allowIn) {
|
|
*tokenizer = copy;
|
|
} else if (token.type == T_EQUALS) {
|
|
declaration->firstChild->sibling = ParseExpression(tokenizer, false, 0);
|
|
if (!declaration->firstChild->sibling) return NULL;
|
|
Token semicolon = TokenNext(tokenizer);
|
|
|
|
if (semicolon.type != T_SEMICOLON) {
|
|
PrintError2(tokenizer, declaration, "Expected a semicolon at the end of the variable declaration.\n");
|
|
return NULL;
|
|
}
|
|
} else if (token.type == T_COMMA) {
|
|
Node *group = (Node *) AllocateFixed(sizeof(Node));
|
|
group->type = T_DECLARE_GROUP;
|
|
group->firstChild = declaration;
|
|
group->token = declaration->token;
|
|
Node **link = &declaration->sibling;
|
|
declaration = group;
|
|
|
|
while (true) {
|
|
Node *declaration = (Node *) AllocateFixed(sizeof(Node));
|
|
*link = declaration;
|
|
link = &declaration->sibling;
|
|
declaration->type = T_DECLARE;
|
|
declaration->firstChild = ParseType(tokenizer, false, false /* allow void */, false /* allow tuple */);
|
|
if (!declaration->firstChild) return NULL;
|
|
declaration->token = TokenNext(tokenizer);
|
|
|
|
if (declaration->token.type != T_IDENTIFIER) {
|
|
PrintError2(tokenizer, declaration, "Expected an identifier for the variable name.\n");
|
|
return NULL;
|
|
}
|
|
|
|
Token token = TokenNext(tokenizer);
|
|
|
|
if (token.type == T_ERROR) {
|
|
return NULL;
|
|
} else if (token.type == T_COMMA) {
|
|
// Keep going...
|
|
} else if (token.type == T_SEMICOLON) {
|
|
break;
|
|
} else if (token.type == T_EQUALS) {
|
|
group->type = T_DECL_GROUP_AND_SET;
|
|
*link = ParseExpression(tokenizer, false, 0);
|
|
if (!(*link)) return NULL;
|
|
Token semicolon = TokenNext(tokenizer);
|
|
|
|
if (semicolon.type != T_SEMICOLON) {
|
|
PrintError2(tokenizer, declaration, "Expected a semicolon at the end of the variable declaration group.\n");
|
|
return NULL;
|
|
}
|
|
|
|
break;
|
|
} else {
|
|
PrintError2(tokenizer, declaration, "Expected one of ',', '=' or ';' in the variable declaration group.\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
return declaration;
|
|
} else {
|
|
Node *expression = ParseExpression(tokenizer, true, 0);
|
|
if (!expression) return NULL;
|
|
|
|
if (replMode) {
|
|
Token end = TokenPeek(tokenizer);
|
|
|
|
if (end.type == T_ERROR) {
|
|
return NULL;
|
|
} else if (end.type == T_EOF) {
|
|
Node *replResult = (Node *) AllocateFixed(sizeof(Node));
|
|
replResult->type = T_REPL_RESULT;
|
|
replResult->firstChild = expression;
|
|
return replResult;
|
|
}
|
|
}
|
|
|
|
Token semicolon = TokenNext(tokenizer);
|
|
|
|
if (semicolon.type != T_SEMICOLON) {
|
|
PrintError2(tokenizer, expression, "Expected a semicolon at the end of the expression.\n");
|
|
return NULL;
|
|
}
|
|
|
|
return expression;
|
|
}
|
|
}
|
|
|
|
Node *ParseBlock(Tokenizer *tokenizer, bool replMode) {
|
|
Node *node = (Node *) AllocateFixed(sizeof(Node));
|
|
Node **link = &node->firstChild;
|
|
node->type = T_BLOCK;
|
|
node->token.line = tokenizer->line;
|
|
node->token.module = tokenizer->module;
|
|
|
|
while (true) {
|
|
Token token = TokenPeek(tokenizer);
|
|
|
|
if (token.type == T_ERROR) {
|
|
return NULL;
|
|
} else if (token.type == (replMode ? T_EOF : T_RIGHT_FANCY)) {
|
|
TokenNext(tokenizer);
|
|
return node;
|
|
} else if (token.type == T_IF) {
|
|
Node *node = ParseIf(tokenizer);
|
|
if (!node) return NULL;
|
|
*link = node;
|
|
link = &node->sibling;
|
|
} else if (token.type == T_WHILE) {
|
|
Node *node = (Node *) AllocateFixed(sizeof(Node));
|
|
node->type = T_WHILE;
|
|
node->token = TokenNext(tokenizer);
|
|
node->firstChild = ParseExpression(tokenizer, false, 0);
|
|
if (!node->firstChild) return NULL;
|
|
Token token = TokenNext(tokenizer);
|
|
|
|
if (token.type != T_LEFT_FANCY && token.type != T_SEMICOLON) {
|
|
PrintError2(tokenizer, node, "Expected a block or semicolon after the while condition.\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (token.type == T_LEFT_FANCY) {
|
|
node->firstChild->sibling = ParseBlock(tokenizer, false);
|
|
if (!node->firstChild->sibling) return NULL;
|
|
} else {
|
|
node->firstChild->sibling = (Node *) AllocateFixed(sizeof(Node));
|
|
node->firstChild->sibling->type = T_BLOCK;
|
|
}
|
|
|
|
*link = node;
|
|
link = &node->sibling;
|
|
} else if (token.type == T_FOR) {
|
|
// TODO Optional components.
|
|
|
|
Node *node = (Node *) AllocateFixed(sizeof(Node));
|
|
node->type = T_FOR;
|
|
node->token = TokenNext(tokenizer);
|
|
node->firstChild = ParseVariableDeclarationOrExpression(tokenizer, false /* replMode */, true /* allowIn */);
|
|
if (!node->firstChild) return NULL;
|
|
Node **siblingLink = NULL;
|
|
Token peek = TokenPeek(tokenizer);
|
|
|
|
if (tokenizer->error) {
|
|
return NULL;
|
|
} else if (peek.type == T_IN) {
|
|
node->type = T_FOR_EACH;
|
|
TokenNext(tokenizer);
|
|
node->firstChild->sibling = ParseExpression(tokenizer, false, 0);
|
|
if (!node->firstChild->sibling) return NULL;
|
|
siblingLink = &node->firstChild->sibling->sibling;
|
|
} else {
|
|
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;
|
|
|
|
siblingLink = &node->firstChild->sibling->sibling->sibling;
|
|
}
|
|
|
|
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) {
|
|
*siblingLink = ParseBlock(tokenizer, false);
|
|
if (!(*siblingLink)) return NULL;
|
|
} else {
|
|
*siblingLink = (Node *) AllocateFixed(sizeof(Node));
|
|
(*siblingLink)->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 || token.type == T_RETERR) {
|
|
if (replMode) {
|
|
PrintError2(tokenizer, node, "%s statements cannot be used in the console.\n",
|
|
token.type == T_RETURN || token.type == T_RETERR ? "Return" : "Assert");
|
|
return NULL;
|
|
}
|
|
|
|
Node *node = (Node *) AllocateFixed(sizeof(Node));
|
|
node->type = token.type;
|
|
node->token = TokenNext(tokenizer);
|
|
|
|
if (token.type == T_ASSERT || token.type == T_RETERR || TokenPeek(tokenizer).type != T_SEMICOLON) {
|
|
node->firstChild = ParseExpression(tokenizer, false, 0);
|
|
if (!node->firstChild) return NULL;
|
|
|
|
if (token.type == T_RETURN && TokenPeek(tokenizer).type == T_COMMA) {
|
|
Node *n = node;
|
|
node = (Node *) AllocateFixed(sizeof(Node));
|
|
node->type = T_RETURN_TUPLE;
|
|
node->firstChild = n->firstChild;
|
|
Node **argumentLink = &n->firstChild->sibling;
|
|
|
|
while (TokenPeek(tokenizer).type == T_COMMA) {
|
|
TokenNext(tokenizer);
|
|
n = ParseExpression(tokenizer, false, 0);
|
|
if (!n) return NULL;
|
|
*argumentLink = n;
|
|
argumentLink = &n->sibling;
|
|
}
|
|
}
|
|
}
|
|
|
|
*link = node;
|
|
link = &node->sibling;
|
|
|
|
Token semicolon = TokenNext(tokenizer);
|
|
|
|
if (semicolon.type != T_SEMICOLON) {
|
|
PrintError2(tokenizer, node, "Expected a semicolon at the end of the statement.\n");
|
|
return NULL;
|
|
}
|
|
} else if (token.type == T_LEFT_FANCY) {
|
|
TokenNext(tokenizer);
|
|
Node *block = ParseBlock(tokenizer, false);
|
|
if (!block) return NULL;
|
|
*link = block;
|
|
link = &block->sibling;
|
|
} else {
|
|
Node *node = ParseVariableDeclarationOrExpression(tokenizer, replMode, false /* allowIn */);
|
|
if (!node) return NULL;
|
|
*link = node;
|
|
link = &node->sibling;
|
|
}
|
|
}
|
|
}
|
|
|
|
Node *ParseGlobalVariableOrFunctionDefinition(Tokenizer *tokenizer, bool allowGlobalVariables, bool parseFunctionBody) {
|
|
Node *type = ParseType(tokenizer, false, true /* allow void */, true /* allow tuple */);
|
|
|
|
if (!type) {
|
|
return NULL;
|
|
}
|
|
|
|
Node *node = (Node *) AllocateFixed(sizeof(Node));
|
|
node->token = TokenNext(tokenizer);
|
|
|
|
if (node->token.type != T_IDENTIFIER) {
|
|
if (allowGlobalVariables) {
|
|
PrintError2(tokenizer, node, "Expected an identifier for the name of the function or global variable.\n");
|
|
} else {
|
|
PrintError2(tokenizer, node, "Expected an identifier for the name of the function pointer type.\n");
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
Token bracket = TokenPeek(tokenizer);
|
|
|
|
if (bracket.type == T_ERROR) {
|
|
return NULL;
|
|
} else if (bracket.type == T_LEFT_ROUND) {
|
|
TokenNext(tokenizer);
|
|
node->type = T_FUNCTION;
|
|
Node *functionPointerType = (Node *) AllocateFixed(sizeof(Node));
|
|
functionPointerType->type = T_FUNCPTR;
|
|
node->firstChild = functionPointerType;
|
|
Node *arguments = (Node *) AllocateFixed(sizeof(Node));
|
|
arguments->type = T_ARGUMENTS;
|
|
functionPointerType->firstChild = arguments;
|
|
arguments->sibling = type;
|
|
Node **link = &arguments->firstChild;
|
|
int argumentCount = 0;
|
|
|
|
while (true) {
|
|
Token token = TokenPeek(tokenizer);
|
|
|
|
if (token.type == T_RIGHT_ROUND) {
|
|
TokenNext(tokenizer);
|
|
break;
|
|
}
|
|
|
|
if (arguments->firstChild) {
|
|
Token comma = TokenNext(tokenizer);
|
|
|
|
if (comma.type != T_COMMA) {
|
|
Node n = { .token = comma };
|
|
PrintError2(tokenizer, &n, "Expected a comma to separate function arguments.\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
Node *argument = (Node *) AllocateFixed(sizeof(Node));
|
|
argument->type = T_ARGUMENT;
|
|
argument->firstChild = ParseType(tokenizer, false, false /* allow void */, false /* allow tuple */);
|
|
if (!argument->firstChild) return NULL;
|
|
argument->token = TokenNext(tokenizer);
|
|
*link = argument;
|
|
link = &argument->sibling;
|
|
argumentCount++;
|
|
|
|
if (argumentCount > FUNCTION_MAX_ARGUMENTS) {
|
|
PrintError2(tokenizer, argument, "Functions cannot have more than %d arguments.\n", FUNCTION_MAX_ARGUMENTS);
|
|
return NULL;
|
|
}
|
|
|
|
if (argument->token.type != T_IDENTIFIER) {
|
|
PrintError2(tokenizer, argument, "Expected an identifier for the name of the function argument.\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (parseFunctionBody) {
|
|
Token token = TokenNext(tokenizer);
|
|
|
|
if (token.type == T_LEFT_FANCY) {
|
|
Node *body = (Node *) AllocateFixed(sizeof(Node));
|
|
body->type = T_FUNCBODY;
|
|
functionPointerType->sibling = body;
|
|
body->firstChild = ParseBlock(tokenizer, false);
|
|
|
|
if (!body->firstChild) {
|
|
return NULL;
|
|
}
|
|
} else if (token.type == T_EXTCALL) {
|
|
Token semicolon = TokenNext(tokenizer);
|
|
|
|
if (semicolon.type != T_SEMICOLON) {
|
|
PrintError(tokenizer, "Expected a semicolon after 'extcall'.\n");
|
|
return NULL;
|
|
}
|
|
|
|
node->isExternalCall = true;
|
|
} else {
|
|
Node n = { .token = token };
|
|
PrintError2(tokenizer, &n, "Expected a '{' to start the function body.\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
} else if (allowGlobalVariables) {
|
|
Token semicolon = TokenNext(tokenizer);
|
|
|
|
if (semicolon.type == T_PERSIST) {
|
|
node->isPersistentVariable = true;
|
|
semicolon = TokenNext(tokenizer);
|
|
}
|
|
|
|
if (semicolon.type == T_OPTION) {
|
|
node->isOptionVariable = true;
|
|
semicolon = TokenNext(tokenizer);
|
|
}
|
|
|
|
if (semicolon.type != T_SEMICOLON) {
|
|
PrintError(tokenizer, "Expected a semicolon after the global variable definition.\n");
|
|
return NULL;
|
|
}
|
|
|
|
node->type = T_DECLARE;
|
|
node->firstChild = type;
|
|
} else {
|
|
PrintError(tokenizer, "Expected a '(' to start the argument list.\n");
|
|
return NULL;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
Node *ParseRootREPL(Tokenizer *tokenizer) {
|
|
// TODO Importing modules.
|
|
|
|
Node *root = (Node *) AllocateFixed(sizeof(Node));
|
|
root->type = T_ROOT;
|
|
Node **link = &root->firstChild;
|
|
|
|
Node *node = (Node *) AllocateFixed(sizeof(Node));
|
|
node->type = T_IMPORT;
|
|
node->token.type = T_INLINE;
|
|
node->token.text = "#inline";
|
|
node->token.textBytes = 7;
|
|
node->firstChild = (Node *) AllocateFixed(sizeof(Node));
|
|
node->firstChild->type = T_IMPORT_PATH;
|
|
node->firstChild->token.type = T_STRING_LITERAL;
|
|
node->firstChild->token.text = "__base_module__";
|
|
node->firstChild->token.textBytes = 15;
|
|
*link = node;
|
|
link = &node->sibling;
|
|
|
|
Node *function = (Node *) AllocateFixed(sizeof(Node));
|
|
*link = function;
|
|
link = &function->sibling;
|
|
function->type = T_FUNCTION;
|
|
function->token.text = startFunction;
|
|
function->token.textBytes = startFunctionBytes;
|
|
Node *functionPointerType = (Node *) AllocateFixed(sizeof(Node));
|
|
function->firstChild = functionPointerType;
|
|
functionPointerType->type = T_FUNCPTR;
|
|
Node *functionArguments = (Node *) AllocateFixed(sizeof(Node));
|
|
functionPointerType->firstChild = functionArguments;
|
|
functionArguments->type = T_ARGUMENTS;
|
|
Node *functionReturnType = (Node *) AllocateFixed(sizeof(Node));
|
|
functionArguments->sibling = functionReturnType;
|
|
functionReturnType->type = T_VOID;
|
|
Node *functionBody = (Node *) AllocateFixed(sizeof(Node));
|
|
functionPointerType->sibling = functionBody;
|
|
functionBody->type = T_FUNCBODY;
|
|
functionBody->firstChild = ParseBlock(tokenizer, true);
|
|
|
|
if (!functionBody->firstChild) {
|
|
return NULL;
|
|
}
|
|
|
|
return root;
|
|
}
|
|
|
|
Node *ParseRoot(Tokenizer *tokenizer) {
|
|
Node *root = (Node *) AllocateFixed(sizeof(Node));
|
|
root->type = T_ROOT;
|
|
Node **link = &root->firstChild;
|
|
|
|
if (!tokenizer->isBaseModule) {
|
|
Node *node = (Node *) AllocateFixed(sizeof(Node));
|
|
node->type = T_IMPORT;
|
|
node->token.type = T_INLINE;
|
|
node->token.text = "#inline";
|
|
node->token.textBytes = 7;
|
|
node->firstChild = (Node *) AllocateFixed(sizeof(Node));
|
|
node->firstChild->type = T_IMPORT_PATH;
|
|
node->firstChild->token.type = T_STRING_LITERAL;
|
|
node->firstChild->token.text = "__base_module__";
|
|
node->firstChild->token.textBytes = 15;
|
|
*link = node;
|
|
link = &node->sibling;
|
|
}
|
|
|
|
while (true) {
|
|
Token token = TokenPeek(tokenizer);
|
|
|
|
if (token.type == T_ERROR) {
|
|
return NULL;
|
|
} else if (token.type == T_EOF) {
|
|
// ScriptPrintNode(root, 0);
|
|
return root;
|
|
} else if (token.type == T_FUNCTYPE) {
|
|
TokenNext(tokenizer);
|
|
Node *node = ParseGlobalVariableOrFunctionDefinition(tokenizer, false, false);
|
|
if (!node) return NULL;
|
|
node->type = T_FUNCTYPE;
|
|
*link = node;
|
|
link = &node->sibling;
|
|
|
|
Token semicolon = TokenNext(tokenizer);
|
|
|
|
if (semicolon.type != T_SEMICOLON) {
|
|
PrintError2(tokenizer, node->firstChild->sibling, "Expected a semicolon after the argument list.\n");
|
|
return NULL;
|
|
}
|
|
} else if (token.type == T_HANDLETYPE) {
|
|
TokenNext(tokenizer);
|
|
Node *node = (Node *) AllocateFixed(sizeof(Node));
|
|
node->type = T_HANDLETYPE;
|
|
node->token = TokenNext(tokenizer);
|
|
*link = node;
|
|
link = &node->sibling;
|
|
|
|
if (node->token.type != T_IDENTIFIER) {
|
|
PrintError2(tokenizer, node, "Expected the name of the handletype.\n");
|
|
return NULL;
|
|
}
|
|
|
|
Token semicolon = TokenNext(tokenizer);
|
|
|
|
if (semicolon.type == T_COLON) {
|
|
node->firstChild = ParseType(tokenizer, false, false, false);
|
|
if (!node->firstChild) return NULL;
|
|
semicolon = TokenNext(tokenizer);
|
|
node->hasTypeInheritanceParent = true;
|
|
}
|
|
|
|
if (semicolon.type != T_SEMICOLON) {
|
|
PrintError2(tokenizer, node, "Expected a semicolon after the handletype identifier.\n");
|
|
return NULL;
|
|
}
|
|
} else if (token.type == T_INTTYPE) {
|
|
TokenNext(tokenizer);
|
|
Node *node = (Node *) AllocateFixed(sizeof(Node));
|
|
node->type = T_INTTYPE;
|
|
node->token = TokenNext(tokenizer);
|
|
*link = node;
|
|
link = &node->sibling;
|
|
Node **contentLink = &node->firstChild;
|
|
|
|
if (node->token.type != T_IDENTIFIER) {
|
|
PrintError2(tokenizer, node, "Expected the name of the inttype.\n");
|
|
return NULL;
|
|
}
|
|
|
|
Token token = TokenNext(tokenizer);
|
|
|
|
if (token.type == T_ERROR) {
|
|
return NULL;
|
|
} else if (token.type == T_COLON) {
|
|
Node *parent = ParseType(tokenizer, false, false, false);
|
|
if (!parent) return NULL;
|
|
*contentLink = parent;
|
|
contentLink = &parent->sibling;
|
|
token = TokenNext(tokenizer);
|
|
node->hasTypeInheritanceParent = true;
|
|
}
|
|
|
|
if (token.type == T_ERROR) {
|
|
return NULL;
|
|
} else if (token.type != T_LEFT_FANCY) {
|
|
PrintError2(tokenizer, node, "Expected a '{' for the inttype contents after the name.\n");
|
|
return NULL;
|
|
}
|
|
|
|
while (true) {
|
|
Token peek = TokenPeek(tokenizer);
|
|
if (peek.type == T_ERROR) return NULL;
|
|
if (peek.type == T_RIGHT_FANCY) break;
|
|
Node *constant = (Node *) AllocateFixed(sizeof(Node));
|
|
constant->token = TokenNext(tokenizer);
|
|
constant->type = T_INTTYPE_CONSTANT;
|
|
*contentLink = constant;
|
|
contentLink = &constant->sibling;
|
|
|
|
if (constant->token.type != T_IDENTIFIER) {
|
|
PrintError2(tokenizer, constant, "Expected an identifier for the constant.\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (TokenNext(tokenizer).type != T_EQUALS) {
|
|
PrintError2(tokenizer, constant, "Expected '=' between the constant name and the value.\n");
|
|
return NULL;
|
|
}
|
|
|
|
constant->firstChild = ParseExpression(tokenizer, false, 0);
|
|
|
|
if (TokenNext(tokenizer).type != T_SEMICOLON) {
|
|
PrintError2(tokenizer, constant, "Expected a semicolon after the constant value.\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
TokenNext(tokenizer);
|
|
|
|
if (TokenNext(tokenizer).type != T_SEMICOLON) {
|
|
PrintError2(tokenizer, node, "Expected a semicolon after the inttype body.\n");
|
|
return NULL;
|
|
}
|
|
} else if (token.type == T_STRUCT) {
|
|
TokenNext(tokenizer);
|
|
Node *node = (Node *) AllocateFixed(sizeof(Node));
|
|
node->type = T_STRUCT;
|
|
node->token = TokenNext(tokenizer);
|
|
*link = node;
|
|
link = &node->sibling;
|
|
|
|
if (node->token.type != T_IDENTIFIER) {
|
|
PrintError2(tokenizer, node, "Expected the name of the struct.\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (TokenNext(tokenizer).type != T_LEFT_FANCY) {
|
|
PrintError2(tokenizer, node, "Expected a '{' for the struct contents after the name.\n");
|
|
return NULL;
|
|
}
|
|
|
|
Node **fieldLink = &node->firstChild;
|
|
|
|
while (true) {
|
|
Token peek = TokenPeek(tokenizer);
|
|
if (peek.type == T_ERROR) return NULL;
|
|
if (peek.type == T_RIGHT_FANCY) break;
|
|
Node *type = ParseType(tokenizer, false, false /* allow void */, false /* allow tuple */);
|
|
if (!type) return NULL;
|
|
Node *field = (Node *) AllocateFixed(sizeof(Node));
|
|
field->token = TokenNext(tokenizer);
|
|
field->type = T_DECLARE;
|
|
field->firstChild = type;
|
|
*fieldLink = field;
|
|
fieldLink = &field->sibling;
|
|
|
|
if (field->token.type != T_IDENTIFIER) {
|
|
PrintError2(tokenizer, field, "Expected an identifier for the field name.\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (TokenNext(tokenizer).type != T_SEMICOLON) {
|
|
PrintError2(tokenizer, field, "Expected a semicolon after the field name.\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
TokenNext(tokenizer);
|
|
|
|
if (TokenNext(tokenizer).type != T_SEMICOLON) {
|
|
PrintError2(tokenizer, node, "Expected a semicolon after the struct body.\n");
|
|
return NULL;
|
|
}
|
|
} else if (token.type == T_IMPORT) {
|
|
Node *node = (Node *) AllocateFixed(sizeof(Node));
|
|
node->type = T_IMPORT;
|
|
TokenNext(tokenizer);
|
|
node->firstChild = ParseExpression(tokenizer, false, 0);
|
|
if (!node->firstChild) return NULL;
|
|
node->token = TokenNext(tokenizer);
|
|
|
|
if (node->firstChild->type != T_STRING_LITERAL) {
|
|
PrintError2(tokenizer, node, "The path to the script file to import must be a string literal.\n");
|
|
return NULL;
|
|
} else if (node->token.type != T_IDENTIFIER && node->token.type != T_INLINE) {
|
|
PrintError2(tokenizer, node, "Expected an identifier for the name to import the module as.\n");
|
|
return NULL;
|
|
}
|
|
|
|
node->firstChild->type = T_IMPORT_PATH;
|
|
*link = node;
|
|
link = &node->sibling;
|
|
|
|
if (TokenNext(tokenizer).type != T_SEMICOLON) {
|
|
PrintError2(tokenizer, node, "Expected a semicolon after the #import statement.\n");
|
|
return NULL;
|
|
}
|
|
} else if (token.type == T_LIBRARY) {
|
|
if (tokenizer->module->library) {
|
|
PrintError(tokenizer, "The library has already been set for this module.\n");
|
|
return NULL;
|
|
} else {
|
|
TokenNext(tokenizer);
|
|
Token token = TokenNext(tokenizer);
|
|
|
|
if (token.type != T_STRING_LITERAL) {
|
|
if (token.type != T_ERROR) PrintError(tokenizer, "Expected a string literal for the library name.\n");
|
|
return NULL;
|
|
}
|
|
|
|
char name[256];
|
|
|
|
if (token.textBytes > sizeof(name) - 1) {
|
|
PrintError(tokenizer, "The library name is too long.\n");
|
|
return NULL;
|
|
}
|
|
|
|
MemoryCopy(name, token.text, token.textBytes);
|
|
name[token.textBytes] = 0;
|
|
tokenizer->module->library = LibraryLoad(name);
|
|
|
|
if (!tokenizer->module->library) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (TokenNext(tokenizer).type != T_SEMICOLON) {
|
|
PrintError(tokenizer, "Expected a semicolon after the #library 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 || node->type == T_PLACEHOLDER;
|
|
}
|
|
|
|
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)
|
|
&& scope->entries[i]->type != T_PLACEHOLDER) {
|
|
PrintError2(tokenizer, node, "The identifier '%.*s' was already used in this scope.\n",
|
|
node->token.textBytes, node->token.text);
|
|
|
|
if (scope->entries[i]->type == T_INLINE) {
|
|
if (scope->entries[i]->importData->pathBytes == 15
|
|
&& 0 == MemoryCompare(scope->entries[i]->importData->path, "__base_module__", scope->entries[i]->importData->pathBytes)) {
|
|
PrintDebug("It was declared in base library module.\n",
|
|
scope->entries[i]->importData->path);
|
|
} else {
|
|
PrintDebug("It was imported inline from the module '%s'.\n",
|
|
scope->entries[i]->importData->path);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
ancestor = ancestor->parent;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ScopeAddEntry(Tokenizer *tokenizer, Scope *scope, Node *node) {
|
|
if (node->type == T_IMPORT && node->token.type == T_INLINE) {
|
|
return true;
|
|
}
|
|
|
|
node->scope = scope;
|
|
|
|
if (scope->entryCount == scope->entriesAllocated) {
|
|
scope->entriesAllocated = scope->entriesAllocated ? scope->entriesAllocated * 2 : 4;
|
|
scope->entries = (Node **) AllocateResize(scope->entries, sizeof(Node *) * scope->entriesAllocated);
|
|
}
|
|
|
|
if (!ScopeCheckNotAlreadyUsed(tokenizer, node)) {
|
|
return false;
|
|
}
|
|
|
|
scope->entries[scope->entryCount++] = node;
|
|
|
|
if (ScopeIsVariableType(node)) {
|
|
scope->variableEntryCount++;
|
|
}
|
|
|
|
// Set this here before type checking occurs,
|
|
// so that all the declarations in the scope already have their expression type set.
|
|
node->expressionType = node->firstChild;
|
|
|
|
return true;
|
|
}
|
|
|
|
void ASTFreeScopes(Node *node) {
|
|
if (node && node->scope) {
|
|
node->scope->entries = (Node **) AllocateResize(node->scope->entries, 0);
|
|
|
|
Node *child = node->firstChild;
|
|
|
|
while (child) {
|
|
ASTFreeScopes(child);
|
|
child = child->sibling;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ASTSetScopes(Tokenizer *tokenizer, ExecutionContext *context, Node *node, Scope *scope) {
|
|
Node *child = node->firstChild;
|
|
|
|
while (child) {
|
|
child->parent = node;
|
|
child = child->sibling;
|
|
}
|
|
|
|
if (node->type == T_ROOT || node->type == T_BLOCK || node->type == T_FUNCBODY) {
|
|
scope = node->scope = (Scope *) AllocateFixed(sizeof(Scope));
|
|
scope->isRoot = node->type == T_ROOT;
|
|
} else {
|
|
node->scope = scope;
|
|
}
|
|
|
|
if (node->type == T_FUNCBODY) {
|
|
Node *argument = node->parent->firstChild->firstChild->firstChild;
|
|
|
|
while (argument) {
|
|
if (!ScopeAddEntry(tokenizer, scope, argument)) {
|
|
return false;
|
|
}
|
|
|
|
argument = argument->sibling;
|
|
}
|
|
}
|
|
|
|
if (node->type == T_VARIABLE) {
|
|
Node *referenced = ScopeLookup(tokenizer, node, true);
|
|
|
|
if (!referenced || referenced->parent->type == T_ROOT) {
|
|
// (If the lookup fails, then it must be a forward declaration, which is only allowed at the root scope level.)
|
|
node->referencesRootScope = true;
|
|
}
|
|
}
|
|
|
|
{
|
|
child = node->firstChild;
|
|
|
|
while (child) {
|
|
if (!ASTSetScopes(tokenizer, context, child, scope)) {
|
|
return false;
|
|
}
|
|
|
|
child = child->sibling;
|
|
}
|
|
}
|
|
|
|
if ((node->type == T_DECLARE || node->type == T_FUNCTION || node->type == T_FUNCTYPE || node->type == T_INTTYPE || node->type == T_INTTYPE_CONSTANT
|
|
|| node->type == T_STRUCT || node->type == T_IMPORT || node->type == T_HANDLETYPE) && node->parent->type != T_STRUCT) {
|
|
if (!ScopeAddEntry(tokenizer, scope, node)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (node->type == T_FOR_EACH) {
|
|
Node *placeholder;
|
|
placeholder = (Node *) AllocateFixed(sizeof(Node));
|
|
placeholder->type = T_PLACEHOLDER;
|
|
Assert(ScopeAddEntry(tokenizer, scope, placeholder));
|
|
placeholder = (Node *) AllocateFixed(sizeof(Node));
|
|
placeholder->type = T_PLACEHOLDER;
|
|
Assert(ScopeAddEntry(tokenizer, scope, placeholder));
|
|
}
|
|
|
|
if (node->type == T_IMPORT) {
|
|
ImportData *alreadyImportedModule = importedModules;
|
|
|
|
while (alreadyImportedModule) {
|
|
if (alreadyImportedModule->pathBytes == node->firstChild->token.textBytes
|
|
&& 0 == MemoryCompare(alreadyImportedModule->path, node->firstChild->token.text, alreadyImportedModule->pathBytes)) {
|
|
break;
|
|
}
|
|
|
|
alreadyImportedModule = alreadyImportedModule->nextImport;
|
|
}
|
|
|
|
if (alreadyImportedModule) {
|
|
#if 0
|
|
PrintError2(tokenizer, node, "The script at path '%.*s' has already been imported as a module.\n",
|
|
node->firstChild->token.textBytes, node->firstChild->token.text);
|
|
return false;
|
|
#else
|
|
node->importData = alreadyImportedModule;
|
|
#endif
|
|
} else {
|
|
char *path = (char *) AllocateFixed(node->firstChild->token.textBytes + 1);
|
|
MemoryCopy(path, node->firstChild->token.text, node->firstChild->token.textBytes);
|
|
path[node->firstChild->token.textBytes] = 0;
|
|
|
|
Tokenizer t = { 0 };
|
|
void *fileData;
|
|
|
|
if (node->firstChild->token.textBytes == 15
|
|
&& 0 == MemoryCompare(node->firstChild->token.text, "__base_module__", node->firstChild->token.textBytes)) {
|
|
fileData = baseModuleSource;
|
|
t.inputBytes = sizeof(baseModuleSource) - 1;
|
|
t.isBaseModule = true;
|
|
} else {
|
|
fileData = FileLoad(path, &t.inputBytes);
|
|
}
|
|
|
|
if (!fileData) {
|
|
PrintError2(tokenizer, node, "The script at path '%.*s' could not be loaded.\n",
|
|
node->firstChild->token.textBytes, node->firstChild->token.text);
|
|
return false;
|
|
}
|
|
|
|
node->importData = (ImportData *) AllocateFixed(sizeof(ImportData));
|
|
node->importData->fileDataBytes = t.inputBytes;
|
|
node->importData->fileData = fileData;
|
|
node->importData->path = path;
|
|
node->importData->pathBytes = node->firstChild->token.textBytes;
|
|
node->importData->parentImport = tokenizer->module;
|
|
|
|
ImportData *parentImport = tokenizer->module;
|
|
|
|
while (parentImport) {
|
|
if (parentImport->pathBytes == node->firstChild->token.textBytes
|
|
&& 0 == MemoryCompare(parentImport->path, node->firstChild->token.text, parentImport->pathBytes)) {
|
|
PrintError3("There is a cyclic import dependency.\n");
|
|
ImportData *data = node->importData;
|
|
|
|
while (data->parentImport) {
|
|
PrintDebug("- '%s' is imported by '%s'\n", data->path, data->parentImport->path);
|
|
data = data->parentImport;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
parentImport = parentImport->parentImport;
|
|
}
|
|
|
|
t.module = node->importData;
|
|
t.input = (const char *) fileData;
|
|
t.line = 1;
|
|
|
|
if (!ScriptLoad(t, context, node->importData, false)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (node->token.type == T_INLINE) {
|
|
uintptr_t j = 0;
|
|
|
|
for (uintptr_t i = 0; i < node->importData->rootNode->scope->entryCount; i++) {
|
|
if (node->importData->rootNode->scope->entries[i]->type == T_INLINE) {
|
|
continue;
|
|
}
|
|
|
|
Node *inlineNode = (Node *) AllocateFixed(sizeof(Node));
|
|
inlineNode->token = node->importData->rootNode->scope->entries[i]->token;
|
|
inlineNode->type = T_INLINE;
|
|
inlineNode->parent = node->parent;
|
|
inlineNode->firstChild = node->importData->rootNode->scope->entries[i]->expressionType;
|
|
inlineNode->importData = node->importData;
|
|
|
|
if (ScopeIsVariableType(node->importData->rootNode->scope->entries[i])) {
|
|
inlineNode->inlineImportVariableIndex = j++;
|
|
} else {
|
|
inlineNode->inlineImportVariableIndex = -1;
|
|
}
|
|
|
|
if (!ScopeAddEntry(tokenizer, scope, inlineNode)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// --------------------------------- Type checking.
|
|
|
|
bool ASTMatching(Node *left, Node *right) {
|
|
if (!left && !right) {
|
|
return true;
|
|
} else if (!left || !right) {
|
|
return false;
|
|
} else if (left->type == T_NULL && (right->type == T_STRUCT || right->type == T_LIST || right->type == T_FUNCTYPE)) {
|
|
return true;
|
|
} else if (right->type == T_NULL && (left->type == T_STRUCT || left->type == T_LIST || left->type == T_FUNCTYPE)) {
|
|
return true;
|
|
} else if (left->type == T_ZERO && (right->type == T_INT || right->type == T_INTTYPE || right->type == T_HANDLETYPE)) {
|
|
return true;
|
|
} else if (right->type == T_ZERO && (left->type == T_INT || left->type == T_INTTYPE || left->type == T_HANDLETYPE)) {
|
|
return true;
|
|
} else if (left->type != right->type) {
|
|
return false;
|
|
} else if ((left->type == T_IDENTIFIER || left->type == T_STRUCT || left->type == T_HANDLETYPE || left->type == T_INTTYPE)
|
|
&& (left->token.module != right->token.module || 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 ASTIsIntType(Node *node) {
|
|
return node && node->type == T_INTTYPE;
|
|
}
|
|
|
|
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 || node->type == T_ERR || node->type == T_ANYTYPE;
|
|
}
|
|
|
|
int ASTGetTypePopCount(Node *node) {
|
|
if (node->type == T_TUPLE) {
|
|
int count = 0;
|
|
Node *child = node->firstChild;
|
|
while (child) { count++; child = child->sibling; }
|
|
return count;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
bool ASTLookupTypeIdentifiers(Tokenizer *tokenizer, Node *node) {
|
|
Node *child = node->firstChild;
|
|
|
|
while (child) {
|
|
if (!ASTLookupTypeIdentifiers(tokenizer, child)) return false;
|
|
child = child->sibling;
|
|
}
|
|
|
|
if (node->type == T_FUNCPTR) {
|
|
Node *type = node->firstChild->sibling;
|
|
|
|
if (type->type == T_IDENTIFIER) {
|
|
Node *lookup = ScopeLookup(tokenizer, type, false);
|
|
if (!lookup) return false;
|
|
Node *copy = (Node *) AllocateFixed(sizeof(Node));
|
|
*copy = *lookup;
|
|
copy->sibling = NULL;
|
|
node->firstChild->sibling = copy;
|
|
}
|
|
}
|
|
|
|
if (node->type == T_DECLARE || node->type == T_ARGUMENT || node->type == T_NEW || node->type == T_LIST
|
|
|| node->type == T_CAST_TYPE_WRAPPER || node->hasTypeInheritanceParent) {
|
|
Node *type = node->firstChild;
|
|
|
|
if (node->hasTypeInheritanceParent && type && type->type != T_IDENTIFIER && type->type != node->type) {
|
|
PrintError2(tokenizer, node, "Types can only inherit from similar types.\n");
|
|
return false;
|
|
}
|
|
|
|
if (type && type->type == T_IDENTIFIER) {
|
|
Node *lookup = ScopeLookup(tokenizer, type, false);
|
|
|
|
if (!lookup) {
|
|
return false;
|
|
}
|
|
|
|
if (node->hasTypeInheritanceParent) {
|
|
if (lookup->type != node->type) {
|
|
PrintError2(tokenizer, node, "Types can only inherit from similar types.\n");
|
|
return false;
|
|
}
|
|
} else 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 || lookup->type == T_INTTYPE || lookup->type == T_HANDLETYPE) {
|
|
if (node->hasTypeInheritanceParent) {
|
|
if (lookup->cycleCheck) {
|
|
PrintError2(tokenizer, lookup, "Cyclic type inheritance.\n");
|
|
return false;
|
|
}
|
|
|
|
Node *copy = (Node *) AllocateFixed(sizeof(Node));
|
|
*copy = *lookup;
|
|
copy->sibling = node->firstChild->sibling;
|
|
node->firstChild = copy;
|
|
node->cycleCheck = true;
|
|
if (!ASTLookupTypeIdentifiers(tokenizer, copy)) return false;
|
|
node->cycleCheck = false;
|
|
} else {
|
|
MemoryCopy(node->expressionType, lookup, sizeof(Node));
|
|
}
|
|
} else {
|
|
PrintError2(tokenizer, lookup, "The identifier did not resolve to a type.\n");
|
|
return false;
|
|
}
|
|
|
|
node->expressionType->sibling = previousSibling;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ASTImplicitCastIsPossible(Node *targetType, Node *expressionType) {
|
|
if (!expressionType || !targetType) {
|
|
return false;
|
|
} else if (targetType->type == T_ERR && ASTMatching(targetType->firstChild, expressionType)) {
|
|
return true;
|
|
} else if (targetType->type == T_HANDLETYPE && expressionType->type == T_HANDLETYPE) {
|
|
// Only allow implicit casts to the inherited type.
|
|
Node *inherit = expressionType->firstChild;
|
|
return inherit && (ASTMatching(targetType, inherit) || ASTImplicitCastIsPossible(targetType, inherit));
|
|
} else if (targetType->type == T_INTTYPE && expressionType->type == T_INTTYPE) {
|
|
// Only allow implicit casts to a type that inherits from this.
|
|
Node *inherit = targetType->firstChild;
|
|
return inherit && inherit->type != T_INTTYPE_CONSTANT && (ASTMatching(inherit, expressionType) || ASTImplicitCastIsPossible(inherit, expressionType));
|
|
} else if (targetType->type == T_ANYTYPE && expressionType->type != T_VOID) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Node *ASTImplicitCastApply(Tokenizer *tokenizer, Node *parent, Node *target, Node *expression) {
|
|
Node *cast = NULL;
|
|
|
|
if (target->type == T_ERR && ASTMatching(target->firstChild, expression->expressionType)) {
|
|
if (!expression->expressionType || ASTMatching(expression->expressionType, &globalExpressionTypeVoid)) {
|
|
PrintError2(tokenizer, parent, "You cannot assign a value of 'void' to 'err[void]'.\nInstead, use the constant '#success'.\n");
|
|
return NULL;
|
|
}
|
|
|
|
cast = (Node *) AllocateFixed(sizeof(Node));
|
|
cast->type = T_ERR_CAST;
|
|
} else if ((target->type == T_HANDLETYPE && expression->expressionType->type == T_HANDLETYPE)
|
|
|| (target->type == T_INTTYPE && expression->expressionType->type == T_INTTYPE)) {
|
|
return expression; // Nothing needs to be done to cast between handletypes or inttypes.
|
|
} else if (target->type == T_ANYTYPE) {
|
|
cast = (Node *) AllocateFixed(sizeof(Node));
|
|
cast->type = T_ANYTYPE_CAST;
|
|
} else {
|
|
Assert(false);
|
|
return NULL;
|
|
}
|
|
|
|
cast->scope = parent->scope;
|
|
cast->parent = parent;
|
|
cast->firstChild = expression;
|
|
cast->expressionType = target;
|
|
return cast;
|
|
}
|
|
|
|
Value ASTNumericLiteralToValue(Node *node) {
|
|
// TODO Overflow checking.
|
|
|
|
Value v = { 0 };
|
|
|
|
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';
|
|
}
|
|
}
|
|
} else {
|
|
Assert(false);
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
int64_t ASTEvaluateIntConstant(Tokenizer *tokenizer, Node *node, bool *error) {
|
|
Assert(ASTMatching(node->expressionType, &globalExpressionTypeInt));
|
|
|
|
if (node->type == T_NUMERIC_LITERAL) {
|
|
return ASTNumericLiteralToValue(node).i;
|
|
} else if (node->type == T_ZERO) {
|
|
return 0;
|
|
} else if (node->type == T_ADD) {
|
|
return ASTEvaluateIntConstant(tokenizer, node->firstChild, error) + ASTEvaluateIntConstant(tokenizer, node->firstChild->sibling, error);
|
|
} else if (node->type == T_MINUS) {
|
|
return ASTEvaluateIntConstant(tokenizer, node->firstChild, error) - ASTEvaluateIntConstant(tokenizer, node->firstChild->sibling, error);
|
|
} else if (node->type == T_ASTERISK) {
|
|
return ASTEvaluateIntConstant(tokenizer, node->firstChild, error) * ASTEvaluateIntConstant(tokenizer, node->firstChild->sibling, error);
|
|
} else if (node->type == T_BITWISE_OR) {
|
|
return ASTEvaluateIntConstant(tokenizer, node->firstChild, error) | ASTEvaluateIntConstant(tokenizer, node->firstChild->sibling, error);
|
|
} else if (node->type == T_BITWISE_AND) {
|
|
return ASTEvaluateIntConstant(tokenizer, node->firstChild, error) & ASTEvaluateIntConstant(tokenizer, node->firstChild->sibling, error);
|
|
} else if (node->type == T_BITWISE_XOR) {
|
|
return ASTEvaluateIntConstant(tokenizer, node->firstChild, error) ^ ASTEvaluateIntConstant(tokenizer, node->firstChild->sibling, error);
|
|
} else if (node->type == T_SLASH) {
|
|
int64_t divisor = ASTEvaluateIntConstant(tokenizer, node->firstChild->sibling, error);
|
|
|
|
if (*error) {
|
|
return 0;
|
|
} else if (divisor == 0) {
|
|
*error = true;
|
|
PrintError2(tokenizer, node, "Integer division by zero.\n");
|
|
return 0;
|
|
} else {
|
|
return ASTEvaluateIntConstant(tokenizer, node->firstChild, error) / divisor;
|
|
}
|
|
} else if (node->type == T_BIT_SHIFT_LEFT || node->type == T_BIT_SHIFT_RIGHT) {
|
|
int64_t shift = ASTEvaluateIntConstant(tokenizer, node->firstChild->sibling, error);
|
|
|
|
if (*error) {
|
|
return 0;
|
|
} else if (shift < 0 || shift > 63) {
|
|
*error = true;
|
|
PrintError2(tokenizer, node, "Shift %d is out of range (0..63).\n", shift);
|
|
return 0;
|
|
} else if (node->type == T_BIT_SHIFT_LEFT) {
|
|
return ASTEvaluateIntConstant(tokenizer, node->firstChild, error) << shift;
|
|
} else {
|
|
return ASTEvaluateIntConstant(tokenizer, node->firstChild, error) >> shift;
|
|
}
|
|
} else if (node->type == T_NEGATE) {
|
|
return -ASTEvaluateIntConstant(tokenizer, node->firstChild, error);
|
|
} else if (node->type == T_BITWISE_NOT) {
|
|
return ~ASTEvaluateIntConstant(tokenizer, node->firstChild, error);
|
|
} else {
|
|
Assert(false);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
bool ASTIsIntegerConstant(Node *node) {
|
|
if (node->type != T_NUMERIC_LITERAL
|
|
&& node->type != T_ZERO
|
|
&& node->type != T_ADD
|
|
&& node->type != T_MINUS
|
|
&& node->type != T_ASTERISK
|
|
&& node->type != T_SLASH
|
|
&& node->type != T_NEGATE
|
|
&& node->type != T_BIT_SHIFT_LEFT
|
|
&& node->type != T_BIT_SHIFT_RIGHT
|
|
&& node->type != T_BITWISE_OR
|
|
&& node->type != T_BITWISE_AND
|
|
&& node->type != T_BITWISE_XOR
|
|
&& node->type != T_BITWISE_NOT) {
|
|
return false;
|
|
}
|
|
|
|
if (!ASTMatching(node->expressionType, &globalExpressionTypeInt)) {
|
|
return false;
|
|
}
|
|
|
|
Node *child = node->firstChild;
|
|
|
|
while (child) {
|
|
if (!ASTIsIntegerConstant(child)) return false;
|
|
child = child->sibling;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ASTSetTypes(Tokenizer *tokenizer, Node *node) {
|
|
Node *child = node->firstChild;
|
|
|
|
while (child) {
|
|
if (child == node) Assert(false);
|
|
if (!ASTSetTypes(tokenizer, child)) return false;
|
|
child->parent = node;
|
|
child = child->sibling;
|
|
}
|
|
|
|
if (node->type == T_ROOT || node->type == T_BLOCK
|
|
|| node->type == T_INT || node->type == T_FLOAT || node->type == T_STR
|
|
|| node->type == T_LIST || node->type == T_TUPLE || node->type == T_ERR || node->type == T_ANYTYPE
|
|
|| 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_INTTYPE || node->type == T_HANDLETYPE
|
|
|| node->type == T_FUNCPTR || node->type == T_FUNCBODY || node->type == T_FUNCTION
|
|
|| node->type == T_REPL_RESULT || node->type == T_DECLARE_GROUP || node->type == T_CAST_TYPE_WRAPPER) {
|
|
} 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_SUCCESS) {
|
|
node->expressionType = &globalExpressionTypeErrVoid;
|
|
} else if (node->type == T_NULL || node->type == T_ZERO) {
|
|
node->expressionType = node;
|
|
} else if (node->type == T_STRING_LITERAL) {
|
|
node->expressionType = &globalExpressionTypeStr;
|
|
} else if (node->type == T_VARIABLE) {
|
|
Node *lookup = ScopeLookup(tokenizer, node, false);
|
|
if (!lookup) return false;
|
|
node->expressionType = lookup->expressionType;
|
|
} else if (node->type == T_STR_INTERPOLATE) {
|
|
Node *left = node->firstChild;
|
|
Node *expression = node->firstChild->sibling;
|
|
Node *right = node->firstChild->sibling->sibling;
|
|
Assert(left->type == T_STRING_LITERAL || left->type == T_STR_INTERPOLATE);
|
|
Assert(right->type == T_STRING_LITERAL);
|
|
Assert(left->expressionType->type == T_STR);
|
|
Assert(right->expressionType->type == T_STR);
|
|
node->expressionType = &globalExpressionTypeStr;
|
|
|
|
// TODO Converting more types to strings.
|
|
|
|
if (!ASTMatching(expression->expressionType, &globalExpressionTypeInt)
|
|
&& !ASTMatching(expression->expressionType, &globalExpressionTypeIntList)
|
|
&& !ASTMatching(expression->expressionType, &globalExpressionTypeStr)
|
|
&& !ASTMatching(expression->expressionType, &globalExpressionTypeFloat)
|
|
&& !ASTMatching(expression->expressionType, &globalExpressionTypeBool)
|
|
&& !ASTIsIntType(expression->expressionType)) {
|
|
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
|
|
|| node->type == T_BIT_SHIFT_LEFT || node->type == T_BIT_SHIFT_RIGHT
|
|
|| node->type == T_BITWISE_OR || node->type == T_BITWISE_AND || node->type == T_BITWISE_XOR) {
|
|
Node *leftType = node->firstChild->expressionType;
|
|
Node *rightType = node->firstChild->sibling->expressionType;
|
|
bool useRightType = false;
|
|
|
|
if (!ASTMatching(leftType, rightType)) {
|
|
if (leftType && rightType && leftType->type == T_INTTYPE && rightType->type == T_INTTYPE
|
|
&& (ASTImplicitCastIsPossible(leftType, rightType) || ASTImplicitCastIsPossible(rightType, leftType))) {
|
|
// If both expressions are inttypes with one inheriting from the other, allow an implicit cast.
|
|
// Nothing needs to be done to cast between inttypes.
|
|
useRightType = ASTImplicitCastIsPossible(rightType, leftType);
|
|
} else {
|
|
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(leftType, &globalExpressionTypeInt)
|
|
&& !ASTIsIntType(leftType)
|
|
&& !ASTMatching(leftType, &globalExpressionTypeFloat)
|
|
&& !ASTMatching(leftType, &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(leftType, &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(leftType, &globalExpressionTypeInt)
|
|
&& !ASTMatching(leftType, &globalExpressionTypeFloat)
|
|
&& !ASTMatching(leftType, &globalExpressionTypeStr)
|
|
&& !ASTMatching(leftType, &globalExpressionTypeBool)
|
|
&& !ASTIsIntType(leftType)
|
|
&& (!leftType || leftType->type != T_LIST)
|
|
&& (!leftType || leftType->type != T_STRUCT)) {
|
|
PrintError2(tokenizer, node, "These types cannot be compared.\n");
|
|
return false;
|
|
}
|
|
} else {
|
|
if (!ASTMatching(leftType, &globalExpressionTypeInt)
|
|
&& !ASTIsIntType(leftType)
|
|
&& !ASTMatching(leftType, &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 = useRightType ? rightType : leftType;
|
|
}
|
|
} else if (node->type == T_DECLARE) {
|
|
if (node->firstChild->sibling && ASTImplicitCastIsPossible(node->firstChild, node->firstChild->sibling->expressionType)) {
|
|
Node *cast = ASTImplicitCastApply(tokenizer, node, node->firstChild, node->firstChild->sibling);
|
|
if (!cast) return false;
|
|
node->firstChild->sibling = cast;
|
|
} else if (node->firstChild->sibling && !ASTMatching(node->firstChild, node->firstChild->sibling->expressionType)) {
|
|
PrintError2(tokenizer, node, "The type of the variable being assigned does not match the expression.\n");
|
|
return false;
|
|
}
|
|
} else if (node->type == T_DECL_GROUP_AND_SET) {
|
|
Node *lastChild = node->firstChild;
|
|
while (lastChild->sibling) lastChild = lastChild->sibling;
|
|
Node *tuple = lastChild->expressionType;
|
|
|
|
if (tuple->type != T_TUPLE) {
|
|
PrintError2(tokenizer, node, "You can only use '=' in the declaration group with a function that returns a tuple.\n");
|
|
return false;
|
|
}
|
|
|
|
Node *child = node->firstChild;
|
|
Node *item = tuple->firstChild;
|
|
int index = 1;
|
|
|
|
while (child->sibling && item) {
|
|
if (!ASTMatching(child->expressionType, item)) {
|
|
PrintError2(tokenizer, node, "The type of value %d in the tuple does not match the declaration type.\n", index);
|
|
return false;
|
|
}
|
|
|
|
child = child->sibling;
|
|
item = item->sibling;
|
|
index++;
|
|
}
|
|
|
|
if ((!item && child->sibling) || (item && !child->sibling)) {
|
|
PrintError2(tokenizer, node, "The number of declarations in the group does not match the number of values in the tuple.\n");
|
|
return false;
|
|
}
|
|
} else if (node->type == T_SET_GROUP) {
|
|
if (node->parent->type == T_SET_GROUP) {
|
|
// Reattach our children at the end of the parent's children.
|
|
Assert(!node->sibling);
|
|
Node *child = node->parent->firstChild;
|
|
while (child->sibling != node) child = child->sibling;
|
|
child->sibling = node->firstChild;
|
|
} else if (node->parent->type == T_EQUALS) {
|
|
Node *expressionType = (Node *) AllocateFixed(sizeof(Node));
|
|
expressionType->type = T_TUPLE;
|
|
node->expressionType = expressionType;
|
|
|
|
Node *child = node->firstChild;
|
|
Node **link = &expressionType->firstChild;
|
|
|
|
while (child) {
|
|
Node *copy = (Node *) AllocateFixed(sizeof(Node));
|
|
*copy = *child->expressionType;
|
|
*link = copy;
|
|
link = &(*link)->sibling;
|
|
child = child->sibling;
|
|
}
|
|
|
|
*link = NULL;
|
|
} else {
|
|
PrintError2(tokenizer, node, "A comma separated list of expressions can only be used to assign a tuple return value to variables.\n");
|
|
return false;
|
|
}
|
|
} else if (node->type == T_EQUALS) {
|
|
if (ASTImplicitCastIsPossible(node->firstChild->expressionType, node->firstChild->sibling->expressionType)) {
|
|
Node *cast = ASTImplicitCastApply(tokenizer, node, node->firstChild->expressionType, node->firstChild->sibling);
|
|
if (!cast) return false;
|
|
node->firstChild->sibling = cast;
|
|
} else 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 **argumentLink = &node->firstChild->sibling->firstChild;
|
|
Node *argument = *argumentLink;
|
|
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 (ASTImplicitCastIsPossible(match->firstChild, argument->expressionType)) {
|
|
Node *cast = ASTImplicitCastApply(tokenizer, node, match->firstChild, argument);
|
|
if (!cast) return false;
|
|
*argumentLink = cast;
|
|
} 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;
|
|
argumentLink = &argument->sibling;
|
|
argument = *argumentLink;
|
|
index++;
|
|
}
|
|
}
|
|
} else if (node->type == T_ASSERT) {
|
|
if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeBool)
|
|
&& !(node->firstChild->expressionType && node->firstChild->expressionType->type == T_ERR)) {
|
|
PrintError2(tokenizer, node, "The asserted expression must evaluate to a boolean or error value.\n");
|
|
return false;
|
|
}
|
|
} else if (node->type == T_RETURN || node->type == T_RETERR) {
|
|
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 (node->type == T_RETURN) {
|
|
if (!ASTMatching(expressionType, returnType)) {
|
|
if (ASTImplicitCastIsPossible(returnType, node->firstChild->expressionType)) {
|
|
Node *cast = ASTImplicitCastApply(tokenizer, node, returnType, node->firstChild);
|
|
if (!cast) return false;
|
|
node->firstChild = cast;
|
|
} else {
|
|
PrintError2(tokenizer, node, "The type of the expression does not match the return type of the function.\n");
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
if (!returnType || returnType->type != T_ERR) {
|
|
PrintError2(tokenizer, node, "'reterr' can only be used in functions that return an error value.\n");
|
|
return false;
|
|
}
|
|
|
|
if (!expressionType || expressionType->type != T_ERR) {
|
|
PrintError2(tokenizer, node, "The expression must be an error value.\n");
|
|
return false;
|
|
}
|
|
}
|
|
} else if (node->type == T_RETURN_TUPLE) {
|
|
Node *expressionType = (Node *) AllocateFixed(sizeof(Node));
|
|
expressionType->type = T_TUPLE;
|
|
|
|
Node *child = node->firstChild;
|
|
Node **link = &expressionType->firstChild;
|
|
|
|
while (child) {
|
|
Node *copy = (Node *) AllocateFixed(sizeof(Node));
|
|
*copy = *child->expressionType;
|
|
*link = copy;
|
|
link = &(*link)->sibling;
|
|
child = child->sibling;
|
|
}
|
|
|
|
*link = NULL;
|
|
|
|
Node *function = node->parent;
|
|
|
|
while (function->type != T_FUNCTION) {
|
|
function = function->parent;
|
|
}
|
|
|
|
Node *returnType = function->firstChild->firstChild->sibling;
|
|
|
|
if (ASTMatching(returnType, &globalExpressionTypeVoid)) {
|
|
PrintError2(tokenizer, node, "The function does not return a value ('void'), but the return statement has a return value.\n");
|
|
return false;
|
|
}
|
|
|
|
if (!ASTMatching(expressionType, returnType)) {
|
|
PrintError2(tokenizer, node, "The type of the expression does not match the declared return type of the function.\n");
|
|
return false;
|
|
}
|
|
} else if (node->type == T_IF) {
|
|
if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeBool)
|
|
&& !(node->firstChild->expressionType && node->firstChild->expressionType->type == T_ERR)) {
|
|
PrintError2(tokenizer, node, "The expression used for the condition must evaluate to a boolean or error value.\n");
|
|
return false;
|
|
}
|
|
} else 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_IF_ERR) {
|
|
if (!node->firstChild->sibling->expressionType || node->firstChild->sibling->expressionType->type != T_ERR) {
|
|
PrintError2(tokenizer, node, "The expression used for the condition must evaluate to a error type.\n");
|
|
return false;
|
|
}
|
|
|
|
if (!ASTMatching(node->firstChild->firstChild, node->firstChild->sibling->expressionType->firstChild)) {
|
|
PrintError2(tokenizer, node, "The variable declaration type must match the error value.\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_FOR_EACH) {
|
|
Node *listType = node->firstChild->sibling->expressionType;
|
|
bool isStr = listType && ASTMatching(listType, &globalExpressionTypeStr);
|
|
|
|
if (!listType || (listType->type != T_LIST && !isStr)) {
|
|
PrintError2(tokenizer, node, "The expression on the right of 'in' must be a list or string.\n");
|
|
return false;
|
|
}
|
|
|
|
if (isStr) {
|
|
if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeStr)) {
|
|
PrintError2(tokenizer, node, "The variable on the left of 'in' must be a 'str' when iterating over a string.\n");
|
|
return false;
|
|
}
|
|
} else {
|
|
if (!ASTMatching(node->firstChild->expressionType, listType->firstChild)) {
|
|
PrintError2(tokenizer, node, "The variable on the left of 'in' must match the type of the items in the list on the right.\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Assert(node->scope->entryCount == 3 && node->scope->variableEntryCount == 3
|
|
&& node->scope->entries[1]->type == T_PLACEHOLDER && node->scope->entries[2]->type == T_PLACEHOLDER);
|
|
node->scope->entries[1]->expressionType = listType;
|
|
node->scope->entries[2]->expressionType = &globalExpressionTypeInt;
|
|
} 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 && node->firstChild->type != T_ERR) {
|
|
PrintError2(tokenizer, node, "This type is not a struct, list or error. 'new' is used to create new instances of structs, lists and errors.\n");
|
|
return false;
|
|
}
|
|
|
|
if (node->firstChild->type == T_ERR && !ASTMatching(node->firstChild->sibling->expressionType, &globalExpressionTypeStr)) {
|
|
PrintError2(tokenizer, node, "The error message should be a string.\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;
|
|
|
|
if (!expressionType) {
|
|
PrintError2(tokenizer, node, "This is not an expression.\n");
|
|
return false;
|
|
}
|
|
|
|
bool isList = expressionType->type == T_LIST;
|
|
bool isStr = expressionType->type == T_STR;
|
|
bool isFuncPtr = expressionType->type == T_FUNCPTR;
|
|
bool isErr = expressionType->type == T_ERR;
|
|
bool isInt = expressionType->type == T_INT;
|
|
bool isFloat = expressionType->type == T_FLOAT;
|
|
bool isAnyType = expressionType->type == T_ANYTYPE;
|
|
|
|
if (!isList && !isStr & !isFuncPtr && !isErr && !isInt && !isFloat && !isAnyType) {
|
|
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, returnsStr = false, returnsFloat = false, simple = true;
|
|
uint8_t op;
|
|
|
|
if (isList && KEYWORD("resize")) arguments[0] = &globalExpressionTypeInt, op = T_OP_RESIZE;
|
|
else if (isList && KEYWORD("add")) arguments[0] = expressionType->firstChild, op = T_OP_ADD;
|
|
else if (isList && KEYWORD("insert")) arguments[0] = expressionType->firstChild, arguments[1] = &globalExpressionTypeInt, op = T_OP_INSERT;
|
|
else if (isList && KEYWORD("insert_many")) arguments[0] = &globalExpressionTypeInt, arguments[1] = &globalExpressionTypeInt, op = T_OP_INSERT_MANY;
|
|
else if (isList && KEYWORD("delete")) arguments[0] = &globalExpressionTypeInt, op = T_OP_DELETE;
|
|
else if (isList && KEYWORD("find_and_delete")) arguments[0] = expressionType->firstChild, op = T_OP_FIND_AND_DELETE, returnsBool = true;
|
|
else if (isList && KEYWORD("find")) arguments[0] = expressionType->firstChild, op = T_OP_FIND, returnsInt = true;
|
|
else if (isList && KEYWORD("delete_many")) arguments[0] = &globalExpressionTypeInt, arguments[1] = &globalExpressionTypeInt, op = T_OP_DELETE_MANY;
|
|
else if (isList && KEYWORD("delete_last")) op = T_OP_DELETE_LAST;
|
|
else if (isList && KEYWORD("delete_all")) op = T_OP_DELETE_ALL;
|
|
else if (isList && KEYWORD("first")) returnsItem = true, op = T_OP_FIRST;
|
|
else if (isList && KEYWORD("last")) returnsItem = true, op = T_OP_LAST;
|
|
else if ((isList || isStr) && KEYWORD("len")) returnsInt = true, op = T_OP_LEN;
|
|
else if (isInt && KEYWORD("float")) returnsFloat = true, op = T_OP_INT_TO_FLOAT;
|
|
else if (isFloat && KEYWORD("truncate")) returnsInt = true, op = T_OP_FLOAT_TRUNCATE;
|
|
else if (isErr && KEYWORD("success")) returnsBool = true, op = T_OP_SUCCESS;
|
|
else if (isErr && KEYWORD("assert")) returnsItem = true, op = T_OP_ASSERT_ERR;
|
|
else if (isErr && KEYWORD("error")) returnsStr = true, op = T_OP_ERROR;
|
|
else if (isErr && KEYWORD("default")) arguments[0] = expressionType->firstChild, returnsItem = true, op = T_OP_DEFAULT; // TODO Warn if the expression has side effects.
|
|
|
|
else if (isFuncPtr && KEYWORD("async")) {
|
|
if (expressionType->firstChild->firstChild) {
|
|
PrintError2(tokenizer, node, "The function pointer should take no arguments. Use ':curry(...)' to set them before ':async()'.\n");
|
|
return false;
|
|
} else if (!ASTMatching(expressionType->firstChild->sibling, &globalExpressionTypeVoid)) {
|
|
PrintError2(tokenizer, node, "The function pointer should not return anything. Use ':discard()' or ':assert()' before ':async()'.\n");
|
|
return false;
|
|
}
|
|
|
|
// TODO Allow currying here, for convenience.
|
|
op = T_OP_ASYNC;
|
|
returnsInt = true;
|
|
}
|
|
|
|
else if (isFuncPtr && (KEYWORD("assert") || KEYWORD("discard"))) {
|
|
if (KEYWORD("assert")) {
|
|
op = T_OP_ASSERT;
|
|
|
|
if (!ASTMatching(expressionType->firstChild->sibling, &globalExpressionTypeBool)) {
|
|
PrintError2(tokenizer, node, "The return value of the function must be a bool to assert it.\n");
|
|
return false;
|
|
}
|
|
} else {
|
|
op = T_OP_DISCARD;
|
|
|
|
if (ASTMatching(expressionType->firstChild->sibling, &globalExpressionTypeVoid)) {
|
|
PrintError2(tokenizer, node, "The return value cannot be discarded from a function that already returns void.\n");
|
|
return false;
|
|
}
|
|
|
|
if (expressionType->firstChild->sibling && expressionType->firstChild->sibling->type == T_TUPLE) {
|
|
// TODO Remove this restriction?
|
|
PrintError2(tokenizer, node, "The discard operation cannot be used on a function returning a tuple.\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
node->expressionType = (Node *) AllocateFixed(sizeof(Node));
|
|
node->expressionType->type = T_FUNCPTR;
|
|
node->expressionType->firstChild = (Node *) AllocateFixed(sizeof(Node));
|
|
MemoryCopy(node->expressionType->firstChild, expressionType->firstChild, sizeof(Node));
|
|
node->expressionType->firstChild->sibling = &globalExpressionTypeVoid;
|
|
simple = false;
|
|
}
|
|
|
|
else if (isFuncPtr && KEYWORD("curry")) {
|
|
if (!expressionType->firstChild->firstChild) {
|
|
PrintError2(tokenizer, node, "The function pointer doesn't take any arguments.\n");
|
|
return false;
|
|
} else if (!ASTMatching(expressionType->firstChild->firstChild->firstChild, node->firstChild->sibling->firstChild->expressionType)) {
|
|
PrintError2(tokenizer, node, "The curried argument does not match the type of the first argument.\n");
|
|
return false;
|
|
} else if (node->firstChild->sibling->firstChild->sibling) {
|
|
// TODO Allow currying multiple arguments together.
|
|
PrintError2(tokenizer, node, "You can only curry one argument at a time.\n");
|
|
return false;
|
|
}
|
|
|
|
node->expressionType = (Node *) AllocateFixed(sizeof(Node));
|
|
node->expressionType->type = T_FUNCPTR;
|
|
node->expressionType->firstChild = (Node *) AllocateFixed(sizeof(Node));
|
|
MemoryCopy(node->expressionType->firstChild, expressionType->firstChild, sizeof(Node));
|
|
node->expressionType->firstChild->firstChild = node->expressionType->firstChild->firstChild->sibling;
|
|
node->expressionType->firstChild->sibling = expressionType->firstChild->sibling;
|
|
op = T_OP_CURRY;
|
|
simple = false;
|
|
}
|
|
|
|
else if (isAnyType && KEYWORD("cast")) {
|
|
Assert(node->firstChild->sibling->type == T_CAST_TYPE_WRAPPER);
|
|
node->expressionType = node->firstChild->sibling->firstChild;
|
|
op = T_OP_CAST;
|
|
simple = false;
|
|
}
|
|
|
|
else {
|
|
PrintError2(tokenizer, node, "This type does not have an operation called '%.*s'.\n", token.textBytes, token.text);
|
|
return false;
|
|
}
|
|
|
|
if (op == T_OP_FIND_AND_DELETE && ASTMatching(arguments[0], &globalExpressionTypeFloat)) {
|
|
PrintError2(tokenizer, node, "The 'find_and_delete' operation cannot be used with floats.\n");
|
|
return false;
|
|
} else if (op == T_OP_FIND && ASTMatching(arguments[0], &globalExpressionTypeFloat)) {
|
|
PrintError2(tokenizer, node, "The 'find' operation cannot be used with floats.\n");
|
|
return false;
|
|
}
|
|
|
|
if (op == T_OP_DEFAULT && ASTMatching(arguments[0], &globalExpressionTypeVoid)) {
|
|
PrintError2(tokenizer, node, "The 'default' operation cannot be used with error values of type void.\n");
|
|
return false;
|
|
} else if (op == T_OP_ASSERT_ERR && ASTMatching(expressionType->firstChild, &globalExpressionTypeVoid)) {
|
|
PrintError2(tokenizer, node, "The 'assert' operation cannot be used with error values of type void.\n");
|
|
return false;
|
|
}
|
|
|
|
if (op == T_OP_FIND_AND_DELETE && ASTMatching(arguments[0], &globalExpressionTypeStr)) {
|
|
op = T_OP_FIND_AND_DEL_STR;
|
|
} else if (op == T_OP_FIND && ASTMatching(arguments[0], &globalExpressionTypeStr)) {
|
|
op = T_OP_FIND_STR;
|
|
}
|
|
|
|
if (simple) {
|
|
Node *argument1 = node->firstChild->sibling->firstChild;
|
|
Node *argument2 = argument1 ? argument1->sibling : NULL;
|
|
Node *argument3 = argument2 ? argument2->sibling : NULL;
|
|
|
|
if (argument3 || (argument2 && !arguments[1]) || (argument1 && !arguments[0])
|
|
|| (!argument2 && arguments[1]) || (!argument1 && arguments[0])) {
|
|
PrintError2(tokenizer, node, "Incorrect number of arguments for the operation '%.*s'.\n", token.textBytes, token.text);
|
|
return false;
|
|
}
|
|
|
|
if (argument1 && !ASTMatching(argument1->expressionType, arguments[0])) {
|
|
PrintError2(tokenizer, node, "Incorrect first argument type for the operation '%.*s'.\n", token.textBytes, token.text);
|
|
return false;
|
|
}
|
|
|
|
if (argument2 && !ASTMatching(argument2->expressionType, arguments[1])) {
|
|
PrintError2(tokenizer, node, "Incorrect second argument type for the operation '%.*s'.\n", token.textBytes, token.text);
|
|
return false;
|
|
}
|
|
|
|
node->expressionType = returnsItem ? expressionType->firstChild
|
|
: returnsInt ? &globalExpressionTypeInt
|
|
: returnsStr ? &globalExpressionTypeStr
|
|
: returnsFloat ? &globalExpressionTypeFloat
|
|
: returnsBool ? &globalExpressionTypeBool : NULL;
|
|
}
|
|
|
|
node->operationType = op;
|
|
} else if (node->type == T_LOGICAL_NOT) {
|
|
if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeBool)) {
|
|
PrintError2(tokenizer, node, "Expected a bool for the logical not '!' operator.\n");
|
|
return false;
|
|
}
|
|
|
|
node->expressionType = &globalExpressionTypeBool;
|
|
} else if (node->type == T_NEGATE || node->type == T_BITWISE_NOT) {
|
|
if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeInt)
|
|
&& !ASTMatching(node->firstChild->expressionType, &globalExpressionTypeFloat)) {
|
|
PrintError2(tokenizer, node, "Expected a int or float for the %s operator.\n", node->type == T_NEGATE ? "unary negate '-'" : "bitwise not '~'");
|
|
return false;
|
|
}
|
|
|
|
node->expressionType = node->firstChild->expressionType;
|
|
} else if (node->type == T_LIST_LITERAL) {
|
|
if (!node->firstChild) {
|
|
// TODO Support empty list literals?
|
|
PrintError2(tokenizer, node, "Empty list literals are not allowed. Instead, put 'new T[]' where 'T' is the item type.\n");
|
|
return false;
|
|
}
|
|
|
|
Node *item = node->firstChild;
|
|
node->expressionType = (Node *) AllocateFixed(sizeof(Node));
|
|
node->expressionType->type = T_LIST;
|
|
Node *copy = (Node *) AllocateFixed(sizeof(Node));
|
|
*copy = *item->expressionType;
|
|
copy->sibling = NULL;
|
|
node->expressionType->firstChild = copy;
|
|
|
|
while (item) {
|
|
if (!ASTMatching(node->expressionType->firstChild, item->expressionType)) {
|
|
PrintError2(tokenizer, item, "The type of this item is different to the ones before it in the list.\n");
|
|
return false;
|
|
}
|
|
|
|
item = item->sibling;
|
|
}
|
|
} else if (node->type == T_AWAIT) {
|
|
if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeIntList)) {
|
|
PrintError2(tokenizer, node, "Expected a list of task IDs to wait on.\n");
|
|
return false;
|
|
}
|
|
|
|
node->expressionType = &globalExpressionTypeInt;
|
|
} else if (node->type == T_INTTYPE_CONSTANT) {
|
|
if (!ASTIsIntegerConstant(node->firstChild)) {
|
|
PrintError2(tokenizer, node, "The expression is not a constant integer.\n");
|
|
return false;
|
|
}
|
|
|
|
node->expressionType = node->parent;
|
|
} else {
|
|
PrintDebug("ASTSetTypes %d\n", node->type);
|
|
Assert(false);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ASTCheckForReturnStatements(Tokenizer *tokenizer, Node *node) {
|
|
if (node->type == T_ROOT) {
|
|
Node *child = node->firstChild;
|
|
|
|
while (child) {
|
|
if (!ASTCheckForReturnStatements(tokenizer, child)) return false;
|
|
child->parent = node;
|
|
child = child->sibling;
|
|
}
|
|
} else if (node->type == T_FUNCTION) {
|
|
if (node->firstChild->sibling && node->firstChild->firstChild->sibling
|
|
&& node->firstChild->firstChild->sibling->type != T_VOID) {
|
|
Assert(node->firstChild->sibling->type == T_FUNCBODY);
|
|
return ASTCheckForReturnStatements(tokenizer, node->firstChild->sibling);
|
|
}
|
|
} else if (node->type == T_BLOCK || node->type == T_FUNCBODY) {
|
|
Node *lastStatement = node->firstChild;
|
|
|
|
while (lastStatement && lastStatement->sibling) {
|
|
lastStatement = lastStatement->sibling;
|
|
}
|
|
|
|
if (lastStatement && (lastStatement->type == T_RETURN || lastStatement->type == T_RETURN_TUPLE)) {
|
|
return true;
|
|
} else if (lastStatement && (lastStatement->type == T_IF || lastStatement->type == T_IF_ERR || 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);
|
|
} else if (node->type == T_IF_ERR) {
|
|
if (!node->firstChild->sibling->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->sibling)
|
|
&& ASTCheckForReturnStatements(tokenizer, node->firstChild->sibling->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;
|
|
bool isIntConstant = false;
|
|
Value intConstantValue;
|
|
|
|
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;
|
|
} else if (scope->entries[i]->type == T_INTTYPE_CONSTANT) {
|
|
isIntConstant = true;
|
|
bool error = false;
|
|
intConstantValue.i = ASTEvaluateIntConstant(tokenizer, scope->entries[i]->firstChild, &error);
|
|
if (error) return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
|
|
if (isIntConstant) {
|
|
uint8_t b = T_NUMERIC_LITERAL;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
FunctionBuilderAppend(builder, &intConstantValue, sizeof(intConstantValue));
|
|
} else {
|
|
if (index >= (int32_t) rootScope->variableEntryCount && !inlineImport) {
|
|
index = rootScope->variableEntryCount - index - 1;
|
|
} else {
|
|
index += globalVariableOffset;
|
|
}
|
|
|
|
if (forAssignment) {
|
|
builder->scopeIndex = index;
|
|
builder->isDotAssignment = false;
|
|
builder->isListAssignment = false;
|
|
} else {
|
|
FunctionBuilderAppend(builder, &node->type, sizeof(node->type));
|
|
FunctionBuilderAppend(builder, &index, sizeof(index));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FunctionBuilderRecurse(Tokenizer *tokenizer, Node *node, FunctionBuilder *builder, bool forAssignment) {
|
|
if (forAssignment) {
|
|
if (node->type == T_VARIABLE || node->type == T_DOT || node->type == T_INDEX) {
|
|
// Supported.
|
|
} else {
|
|
PrintError2(tokenizer, node, "A value cannot be assigned to this expression. Try putting a variable name here.\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (node->type == T_FUNCBODY || node->type == T_BLOCK) {
|
|
if (node->scope->variableEntryCount > 10000) {
|
|
PrintError2(tokenizer, node, "There are too many variables in this scope (the maximum is 10000).\n");
|
|
return false;
|
|
}
|
|
|
|
uint16_t entryCount = node->scope->variableEntryCount;
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
FunctionBuilderAppend(builder, &node->type, sizeof(node->type));
|
|
FunctionBuilderAppend(builder, &entryCount, sizeof(entryCount));
|
|
|
|
for (uintptr_t i = 0; i < node->scope->entryCount; i++) {
|
|
Node *entry = node->scope->entries[i];
|
|
|
|
if (ScopeIsVariableType(entry)) {
|
|
bool isManaged = ASTIsManagedType(entry->expressionType);
|
|
FunctionBuilderAppend(builder, &isManaged, sizeof(isManaged));
|
|
}
|
|
}
|
|
} else if (node->type == T_EQUALS || node->type == T_DECLARE || node->type == T_DECL_GROUP_AND_SET) {
|
|
if (node->firstChild->sibling) {
|
|
Node *variables[FUNCTION_MAX_ARGUMENTS];
|
|
uintptr_t variableCount = 0;
|
|
bool setGroup = node->type == T_EQUALS && node->firstChild->type == T_SET_GROUP;
|
|
Node *lastChild = setGroup ? node->firstChild->firstChild : node->firstChild;
|
|
|
|
while (setGroup ? lastChild : lastChild->sibling) {
|
|
Assert(variableCount != FUNCTION_MAX_ARGUMENTS);
|
|
variables[variableCount++] = lastChild;
|
|
lastChild = lastChild->sibling;
|
|
}
|
|
|
|
if (!FunctionBuilderRecurse(tokenizer, setGroup ? node->firstChild->sibling : lastChild, builder, false)) {
|
|
return false;
|
|
}
|
|
|
|
while (variableCount) {
|
|
Node *child = variables[--variableCount];
|
|
builder->isPersistentVariable = false;
|
|
|
|
if (node->type == T_DECLARE || node->type == T_DECL_GROUP_AND_SET) {
|
|
if (!FunctionBuilderVariable(tokenizer, builder, node->type == T_DECL_GROUP_AND_SET ? child : node, true)) {
|
|
return false;
|
|
}
|
|
} else if (!FunctionBuilderRecurse(tokenizer, child, builder, true)) {
|
|
return false;
|
|
}
|
|
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
uint8_t b = builder->isListAssignment ? T_EQUALS_LIST : builder->isDotAssignment ? T_EQUALS_DOT : T_EQUALS;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
|
|
if (!builder->isListAssignment) {
|
|
FunctionBuilderAppend(builder, &builder->scopeIndex, sizeof(builder->scopeIndex));
|
|
}
|
|
|
|
if (builder->isPersistentVariable) {
|
|
b = T_PERSIST;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
}
|
|
|
|
child = child->sibling;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
} else if (node->type == T_CALL) {
|
|
Node *argument = node->firstChild->sibling->firstChild;
|
|
Node *arguments[FUNCTION_MAX_ARGUMENTS];
|
|
size_t argumentCount = 0;
|
|
|
|
while (argument) {
|
|
arguments[argumentCount++] = argument;
|
|
argument = argument->sibling;
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < argumentCount; i++) {
|
|
if (!FunctionBuilderRecurse(tokenizer, arguments[argumentCount - i - 1], builder, false)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!FunctionBuilderRecurse(tokenizer, node->firstChild, builder, false)) return false;
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
FunctionBuilderAppend(builder, &node->type, sizeof(node->type));
|
|
return true;
|
|
} else if (node->type == T_WHILE) {
|
|
int32_t start = builder->dataBytes;
|
|
if (!FunctionBuilderRecurse(tokenizer, node->firstChild, builder, false)) return false;
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
uint8_t b = T_IF;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
uintptr_t writeOffset = builder->dataBytes;
|
|
uint32_t zero = 0;
|
|
FunctionBuilderAppend(builder, &zero, sizeof(zero));
|
|
if (!FunctionBuilderRecurse(tokenizer, node->firstChild->sibling, builder, false)) return false;
|
|
b = T_BRANCH;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
int32_t delta = start - builder->dataBytes;
|
|
FunctionBuilderAppend(builder, &delta, sizeof(delta));
|
|
delta = builder->dataBytes - writeOffset;
|
|
MemoryCopy(builder->data + writeOffset, &delta, sizeof(delta));
|
|
return true;
|
|
} else if (node->type == T_FOR) {
|
|
Node *declare = node->firstChild;
|
|
Node *condition = node->firstChild->sibling;
|
|
Node *increment = node->firstChild->sibling->sibling;
|
|
Node *body = node->firstChild->sibling->sibling->sibling;
|
|
|
|
if (declare->type != T_DECLARE && declare->type != T_EQUALS) {
|
|
PrintError2(tokenizer, node, "The first section of a for statement must be a variable declaration or an assignment.\n");
|
|
return false;
|
|
}
|
|
|
|
if (!FunctionBuilderRecurse(tokenizer, declare, builder, false)) return false;
|
|
int32_t start = builder->dataBytes;
|
|
if (!FunctionBuilderRecurse(tokenizer, condition, builder, false)) return false;
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
uint8_t b = T_IF;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
uintptr_t writeOffset = builder->dataBytes;
|
|
uint32_t zero = 0;
|
|
FunctionBuilderAppend(builder, &zero, sizeof(zero));
|
|
if (!FunctionBuilderRecurse(tokenizer, body, builder, false)) return false;
|
|
if (!FunctionBuilderRecurse(tokenizer, increment, builder, false)) return false;
|
|
|
|
if (increment->expressionType && increment->expressionType->type != T_VOID) {
|
|
if (increment->type == T_CALL) {
|
|
uint8_t b = T_POP;
|
|
|
|
for (int i = 0; i < ASTGetTypePopCount(increment->expressionType); i++) {
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
}
|
|
} else {
|
|
PrintError2(tokenizer, increment, "The result of the expression is unused.\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
b = T_BRANCH;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
int32_t delta = start - builder->dataBytes;
|
|
FunctionBuilderAppend(builder, &delta, sizeof(delta));
|
|
delta = builder->dataBytes - writeOffset;
|
|
MemoryCopy(builder->data + writeOffset, &delta, sizeof(delta));
|
|
return true;
|
|
} else if (node->type == T_FOR_EACH) {
|
|
Node *declare = node->firstChild;
|
|
Node *list = node->firstChild->sibling;
|
|
Node *body = node->firstChild->sibling->sibling;
|
|
bool isStr = ASTMatching(list->expressionType, &globalExpressionTypeStr);
|
|
|
|
if (declare->type != T_DECLARE) {
|
|
PrintError2(tokenizer, node, "The left of a for-in statement must be a variable declaration.\n");
|
|
return false;
|
|
}
|
|
|
|
// Get the scope index base.
|
|
Node *variableNode = (Node *) AllocateFixed(sizeof(Node));
|
|
variableNode->type = T_IDENTIFIER;
|
|
variableNode->token = declare->token;
|
|
variableNode->scope = node->scope;
|
|
variableNode->parent = node;
|
|
size_t oldDataBytes = builder->dataBytes;
|
|
FunctionBuilderVariable(tokenizer, builder, variableNode, true);
|
|
Assert(oldDataBytes == builder->dataBytes);
|
|
int32_t scopeIndexBase = builder->scopeIndex;
|
|
|
|
// Declare the iteration variable.
|
|
if (!FunctionBuilderRecurse(tokenizer, declare, builder, false)) return false;
|
|
|
|
// Push the starting index of 0.
|
|
uint8_t b = T_ZERO;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
|
|
// Push the list.
|
|
if (!FunctionBuilderRecurse(tokenizer, list, builder, false)) return false;
|
|
|
|
int32_t start = builder->dataBytes;
|
|
|
|
// Check whether the index is less than the list length.
|
|
// Stack: list, index, ...
|
|
b = T_SWAP;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
// Stack: index, list, ...
|
|
b = T_DUP;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
// Stack: index, index, list, ...
|
|
b = T_ROT3;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
// Stack: list, index, index, ...
|
|
b = T_DUP;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
// Stack: list, list, index, index, ...
|
|
b = T_OP_LEN;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
// Stack: length, list, index, index, ...
|
|
b = T_ROT3;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
// Stack: index, length, list, index, ...
|
|
b = T_GREATER_THAN;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
// Stack: comparison, list, index, ...
|
|
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
b = T_IF;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
uintptr_t writeOffset = builder->dataBytes;
|
|
uint32_t zero = 0;
|
|
FunctionBuilderAppend(builder, &zero, sizeof(zero));
|
|
|
|
// Get the value out of the list.
|
|
// Stack: list, index, ...
|
|
b = T_SWAP;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
// Stack: index, list, ...
|
|
b = T_DUP;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
// Stack: index, index, list, ...
|
|
b = T_ROT3;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
// Stack: list, index, index, ...
|
|
b = T_DUP;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
// Stack: list, list, index, index, ...
|
|
b = T_ROT3;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
// Stack: index, list, list, index, ...
|
|
b = isStr ? T_INDEX : T_INDEX_LIST;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
// Stack: list, index, ...
|
|
|
|
// Set the iteration variable.
|
|
b = T_EQUALS;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
FunctionBuilderAppend(builder, &scopeIndexBase, sizeof(scopeIndexBase));
|
|
|
|
// Save the list and index.
|
|
int32_t scopeIndexList = scopeIndexBase - 1;
|
|
int32_t scopeIndexIndex = scopeIndexBase - 2;
|
|
b = T_EQUALS;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
FunctionBuilderAppend(builder, &scopeIndexList, sizeof(scopeIndexList));
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
FunctionBuilderAppend(builder, &scopeIndexIndex, sizeof(scopeIndexIndex));
|
|
|
|
// Output the body.
|
|
if (!FunctionBuilderRecurse(tokenizer, body, builder, false)) return false;
|
|
|
|
// Load the list and index.
|
|
b = T_VARIABLE;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
FunctionBuilderAppend(builder, &scopeIndexIndex, sizeof(scopeIndexIndex));
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
FunctionBuilderAppend(builder, &scopeIndexList, sizeof(scopeIndexList));
|
|
|
|
// Increment the index.
|
|
// Stack: list, index, ...
|
|
b = T_SWAP;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
// Stack: index, list, ...
|
|
b = T_NUMERIC_LITERAL;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
Value v;
|
|
v.i = 1;
|
|
FunctionBuilderAppend(builder, &v, sizeof(v));
|
|
// Stack: 1, index, list, ...
|
|
b = T_ADD;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
// Stack: new index, list, ...
|
|
b = T_SWAP;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
// Stack: list, new index, ...
|
|
|
|
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));
|
|
|
|
// Pop the list and index.
|
|
b = T_POP;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
|
|
return true;
|
|
} else if (node->type == T_IF) {
|
|
if (!FunctionBuilderRecurse(tokenizer, node->firstChild, builder, false)) return false;
|
|
|
|
if (node->firstChild->expressionType->type == T_ERR) {
|
|
uint8_t b = T_OP_SUCCESS;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
}
|
|
|
|
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_IF_ERR) {
|
|
uint8_t b;
|
|
if (!FunctionBuilderRecurse(tokenizer, node->firstChild->sibling, builder, false)) return false;
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
|
|
b = T_DUP;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
b = T_OP_SUCCESS;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
b = T_IF;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
uintptr_t writeOffset = builder->dataBytes, writeOffsetElse = 0;
|
|
uint32_t zero = 0;
|
|
FunctionBuilderAppend(builder, &zero, sizeof(zero));
|
|
b = T_OP_ASSERT_ERR;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
|
|
Node *variableNode = (Node *) AllocateFixed(sizeof(Node));
|
|
variableNode->type = T_IDENTIFIER;
|
|
variableNode->token = node->firstChild->token;
|
|
variableNode->scope = node->scope;
|
|
variableNode->parent = node;
|
|
FunctionBuilderVariable(tokenizer, builder, variableNode, true);
|
|
b = T_EQUALS;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
FunctionBuilderAppend(builder, &builder->scopeIndex, sizeof(builder->scopeIndex));
|
|
|
|
if (!FunctionBuilderRecurse(tokenizer, node->firstChild->sibling->sibling, builder, false)) return false;
|
|
|
|
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));
|
|
|
|
b = T_POP;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
|
|
if (node->firstChild->sibling->sibling->sibling) {
|
|
if (!FunctionBuilderRecurse(tokenizer, node->firstChild->sibling->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) {
|
|
if (node->firstChild->type == T_ERR) {
|
|
FunctionBuilderRecurse(tokenizer, node->firstChild->sibling, builder, false);
|
|
}
|
|
|
|
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 if (node->firstChild->type == T_ERR) {
|
|
fieldCount = ASTIsManagedType(node->firstChild->firstChild) ? -4 : -3;
|
|
} 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);
|
|
|
|
if (node->operationType != T_OP_CAST) {
|
|
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));
|
|
|
|
if (node->operationType == T_OP_CAST) {
|
|
FunctionBuilderAppend(builder, &node->expressionType, sizeof(node->expressionType));
|
|
}
|
|
|
|
return true;
|
|
} else if (node->type == T_LIST_LITERAL) {
|
|
// Step 1: Create the list.
|
|
uint8_t b = T_NEW;
|
|
int16_t isManaged = ASTIsManagedType(node->expressionType->firstChild) ? -2 : -1;
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
FunctionBuilderAppend(builder, &isManaged, sizeof(isManaged));
|
|
|
|
// Step 2: Resize the list.
|
|
uint32_t size = 0;
|
|
Node *item = node->firstChild;
|
|
while (item) { size++; item = item->sibling; }
|
|
b = T_DUP;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
b = T_NUMERIC_LITERAL;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
Value v;
|
|
v.i = size;
|
|
FunctionBuilderAppend(builder, &v, sizeof(v));
|
|
b = T_OP_RESIZE;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
|
|
// Step 3: Populate the list.
|
|
item = node->firstChild;
|
|
uint32_t i = 0;
|
|
|
|
while (item) {
|
|
b = T_DUP;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
FunctionBuilderRecurse(tokenizer, item, builder, false);
|
|
b = T_SWAP;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
b = T_NUMERIC_LITERAL;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
Value v;
|
|
v.i = i++;
|
|
FunctionBuilderAppend(builder, &v, sizeof(v));
|
|
b = T_EQUALS_LIST;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
item = item->sibling;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Node *child = node->firstChild;
|
|
|
|
while (child) {
|
|
if (!FunctionBuilderRecurse(tokenizer, child, builder, false)) {
|
|
return false;
|
|
}
|
|
|
|
if (node->type == T_BLOCK && child->expressionType && child->expressionType->type != T_VOID) {
|
|
if (child->type == T_CALL || child->type == T_AWAIT
|
|
|| (child->type == T_COLON && child->operationType == T_OP_FIND_AND_DELETE)
|
|
|| (child->type == T_COLON && child->operationType == T_OP_FIND_AND_DEL_STR)) {
|
|
uint8_t b = T_POP;
|
|
|
|
for (int i = 0; i < ASTGetTypePopCount(child->expressionType); i++) {
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
}
|
|
} else if (child->type == T_DECLARE || child->type == T_EQUALS) {
|
|
} else {
|
|
PrintError2(tokenizer, child, "The result of the expression is unused.\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
child = child->sibling;
|
|
}
|
|
|
|
if (node->type == T_FUNCBODY || node->type == T_BLOCK) {
|
|
uint8_t b = T_EXIT_SCOPE;
|
|
uint16_t entryCount = node->scope->variableEntryCount;
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
FunctionBuilderAppend(builder, &entryCount, sizeof(entryCount));
|
|
b = T_END_FUNCTION;
|
|
if (node->type == T_FUNCBODY) FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
} else if (node->type == T_DECLARE_GROUP) {
|
|
} else if (node->type == T_RETURN || node->type == T_RETURN_TUPLE) {
|
|
uint8_t b = T_END_FUNCTION;
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
} else if (node->type == T_RETERR) {
|
|
uint8_t b;
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
b = T_DUP;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
b = T_OP_SUCCESS;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
b = T_LOGICAL_NOT;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
b = T_IF;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
int32_t delta = 9;
|
|
FunctionBuilderAppend(builder, &delta, sizeof(delta));
|
|
b = T_OP_ERROR;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
b = T_NEW;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
int16_t newType = -3;
|
|
FunctionBuilderAppend(builder, &newType, sizeof(newType));
|
|
b = T_END_FUNCTION;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
b = T_POP;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
} else if (node->type == T_NULL || node->type == T_LOGICAL_NOT || node->type == T_AWAIT || node->type == T_ERR_CAST || node->type == T_ZERO) {
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
FunctionBuilderAppend(builder, &node->type, sizeof(node->type));
|
|
} else if (node->type == T_ANYTYPE_CAST) {
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
FunctionBuilderAppend(builder, &node->type, sizeof(node->type));
|
|
FunctionBuilderAppend(builder, &node->firstChild->expressionType, sizeof(node->firstChild->expressionType));
|
|
} else if (node->type == T_ASSERT) {
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
|
|
if (node->firstChild->expressionType->type == T_ERR) {
|
|
uint8_t b = T_OP_ASSERT_ERR;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
b = T_POP;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
} else {
|
|
FunctionBuilderAppend(builder, &node->type, sizeof(node->type));
|
|
}
|
|
} else if (node->type == T_ADD || node->type == T_MINUS || node->type == T_ASTERISK || node->type == T_SLASH || node->type == T_NEGATE || node->type == T_BITWISE_NOT
|
|
|| node->type == T_BIT_SHIFT_LEFT || node->type == T_BIT_SHIFT_RIGHT
|
|
|| node->type == T_BITWISE_OR || node->type == T_BITWISE_AND || node->type == T_BITWISE_XOR) {
|
|
uint8_t b = node->expressionType->type == T_FLOAT ? node->type - T_ADD + T_FLOAT_ADD
|
|
: node->expressionType->type == T_STR ? T_CONCAT : node->type;
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
} else if (node->type == T_STR_INTERPOLATE) {
|
|
Node *type = node->firstChild->sibling->expressionType;
|
|
uint8_t b = type->type == T_STR ? T_INTERPOLATE_STR
|
|
: type->type == T_FLOAT ? T_INTERPOLATE_FLOAT
|
|
: (type->type == T_INT || ASTIsIntType(type)) ? T_INTERPOLATE_INT
|
|
: (type->type == T_LIST && type->firstChild->type == T_INT) ? T_INTERPOLATE_ILIST
|
|
: type->type == T_BOOL ? T_INTERPOLATE_BOOL : T_ERROR;
|
|
Assert(b != T_ERROR);
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
} else if (node->type == T_LESS_THAN || node->type == T_GREATER_THAN || node->type == T_LT_OR_EQUAL || node->type == T_GT_OR_EQUAL
|
|
|| node->type == T_DOUBLE_EQUALS || node->type == T_NOT_EQUALS) {
|
|
uint8_t b = node->firstChild->expressionType->type == T_STR ? node->type - T_DOUBLE_EQUALS + T_STR_DOUBLE_EQUALS
|
|
: node->firstChild->expressionType->type == T_FLOAT ? node->type - T_GREATER_THAN + T_FLOAT_GREATER_THAN : node->type;
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
} else if (node->type == T_VARIABLE) {
|
|
if (!FunctionBuilderVariable(tokenizer, builder, node, forAssignment)) {
|
|
return false;
|
|
}
|
|
} else if (node->type == T_INDEX) {
|
|
if (forAssignment) {
|
|
if (node->firstChild->expressionType->type == T_STR) {
|
|
PrintError2(tokenizer, node->firstChild, "Strings cannot be modified.\n");
|
|
return false;
|
|
} else {
|
|
builder->isListAssignment = true;
|
|
builder->isDotAssignment = false;
|
|
}
|
|
} else {
|
|
uint8_t b = node->firstChild->expressionType->type == T_STR ? T_INDEX : T_INDEX_LIST;
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
}
|
|
} else if (node->type == T_DOT) {
|
|
bool isStruct = node->firstChild->expressionType->type == T_STRUCT;
|
|
|
|
if (isStruct) {
|
|
Node *field = node->firstChild->expressionType->firstChild;
|
|
int16_t fieldIndex = 0;
|
|
|
|
while (field) {
|
|
if (field->token.textBytes == node->token.textBytes &&
|
|
0 == MemoryCompare(field->token.text, node->token.text, node->token.textBytes)) {
|
|
break;
|
|
}
|
|
|
|
field = field->sibling;
|
|
fieldIndex++;
|
|
}
|
|
|
|
if (ASTIsManagedType(field->firstChild)) {
|
|
fieldIndex = -1 - fieldIndex;
|
|
}
|
|
|
|
if (forAssignment) {
|
|
builder->scopeIndex = fieldIndex;
|
|
builder->isDotAssignment = true;
|
|
builder->isListAssignment = false;
|
|
} else {
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
FunctionBuilderAppend(builder, &node->type, sizeof(node->type));
|
|
FunctionBuilderAppend(builder, &fieldIndex, sizeof(fieldIndex));
|
|
}
|
|
} else {
|
|
if (forAssignment) {
|
|
PrintError2(tokenizer, node, "You cannot directly modify a variable from an imported module.\n");
|
|
return false;
|
|
} else {
|
|
Node *importStatement = node->firstChild->expressionType->parent;
|
|
Assert(importStatement->type == T_IMPORT);
|
|
uint32_t realIndex = ScopeLookupIndex(node, importStatement->importData->rootNode->scope, false, true);
|
|
Node *declarationNode = importStatement->importData->rootNode->scope->entries[realIndex];
|
|
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
uint8_t b = T_POP;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
|
|
if (declarationNode->type == T_INTTYPE_CONSTANT) {
|
|
bool error = false;
|
|
Value intConstantValue;
|
|
intConstantValue.i = ASTEvaluateIntConstant(tokenizer, declarationNode->firstChild, &error);
|
|
if (error) return false;
|
|
uint8_t b = T_NUMERIC_LITERAL;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
FunctionBuilderAppend(builder, &intConstantValue, sizeof(intConstantValue));
|
|
} else {
|
|
uint32_t index = ScopeLookupIndex(node, importStatement->importData->rootNode->scope, false, false);
|
|
index += importStatement->importData->globalVariableOffset;
|
|
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 = ASTNumericLiteralToValue(node);
|
|
FunctionBuilderAppend(builder, &v, sizeof(v));
|
|
} else if (node->type == T_STRING_LITERAL) {
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
FunctionBuilderAppend(builder, &node->type, sizeof(node->type));
|
|
uint32_t textBytes = node->token.textBytes;
|
|
FunctionBuilderAppend(builder, &textBytes, sizeof(textBytes));
|
|
FunctionBuilderAppend(builder, node->token.text, textBytes);
|
|
} else if (node->type == T_TRUE || node->type == T_FALSE) {
|
|
FunctionBuilderAddLineNumber(builder, node);
|
|
uint8_t b = T_NUMERIC_LITERAL;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
Value v;
|
|
v.i = node->type == T_TRUE ? 1 : 0;
|
|
FunctionBuilderAppend(builder, &v, sizeof(v));
|
|
} else if (node->type == T_SUCCESS) {
|
|
uint8_t b = T_NUMERIC_LITERAL;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
Value v;
|
|
v.i = 0;
|
|
FunctionBuilderAppend(builder, &v, sizeof(v));
|
|
b = T_ERR_CAST;
|
|
FunctionBuilderAppend(builder, &b, sizeof(b));
|
|
} else if (node->type == T_REPL_RESULT) {
|
|
if (!ASTMatching(node->firstChild->expressionType, &globalExpressionTypeVoid)) {
|
|
FunctionBuilderAppend(builder, &node->type, sizeof(node->type));
|
|
}
|
|
|
|
if (builder->replResultType) {
|
|
PrintError2(tokenizer, node, "Multiple REPL results are not allowed.\n");
|
|
return false;
|
|
}
|
|
|
|
builder->replResultType = node->firstChild->expressionType;
|
|
} else {
|
|
PrintDebug("FunctionBuilderRecurse %d\n", node->type);
|
|
Assert(false);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ASTGenerate(Tokenizer *tokenizer, Node *root, ExecutionContext *context) {
|
|
Node *child = root->firstChild;
|
|
|
|
context->functionData->globalVariableOffset = context->globalVariableCount;
|
|
context->globalVariableCount += root->scope->variableEntryCount;
|
|
context->globalVariables = (Value *) AllocateResize(context->globalVariables, sizeof(Value) * context->globalVariableCount);
|
|
context->globalVariableIsManaged = (bool *) AllocateResize(context->globalVariableIsManaged, sizeof(Value) * context->globalVariableCount);
|
|
|
|
for (uintptr_t i = 0; i < root->scope->variableEntryCount; i++) {
|
|
context->globalVariables[context->functionData->globalVariableOffset + i].i = 0;
|
|
context->globalVariableIsManaged[context->functionData->globalVariableOffset + i] = false;
|
|
}
|
|
|
|
while (child) {
|
|
if (child->type == T_FUNCTION) {
|
|
uintptr_t variableIndex = context->functionData->globalVariableOffset + ScopeLookupIndex(child, root->scope, false, false);
|
|
uintptr_t heapIndex = HeapAllocate(context);
|
|
context->globalVariableIsManaged[variableIndex] = true;
|
|
context->heap[heapIndex].type = T_FUNCPTR;
|
|
context->heap[heapIndex].lambdaID = context->functionData->dataBytes;
|
|
context->globalVariables[variableIndex].i = heapIndex;
|
|
|
|
if (child->isExternalCall && child->token.module->library) {
|
|
char name[256];
|
|
|
|
if (child->token.textBytes > sizeof(name) - 1) {
|
|
PrintError2(tokenizer, child, "The function name is too long to be loaded from a library.\n");
|
|
return NULL;
|
|
}
|
|
|
|
MemoryCopy(name, child->token.text, child->token.textBytes);
|
|
name[child->token.textBytes] = 0;
|
|
|
|
void *address = LibraryGetAddress(child->token.module->library, name);
|
|
if (!address) return false;
|
|
uint8_t b = T_LIBCALL;
|
|
FunctionBuilderAppend(context->functionData, &b, sizeof(b));
|
|
FunctionBuilderAppend(context->functionData, &address, sizeof(address));
|
|
} else if (child->isExternalCall) {
|
|
uint8_t b = T_EXTCALL;
|
|
|
|
uint16_t index = 0xFFFF;
|
|
|
|
for (uintptr_t i = 0; i < sizeof(externalFunctions) / sizeof(externalFunctions[0]); i++) {
|
|
bool match = true;
|
|
|
|
for (uintptr_t j = 0; j <= child->token.textBytes; j++) {
|
|
if (externalFunctions[i].cName[j] != (j == child->token.textBytes ? 0 : child->token.text[j])) {
|
|
match = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (match) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (index == 0xFFFF) {
|
|
PrintError2(tokenizer, child, "No such external function '%.*s'.\n", child->token.textBytes, child->token.text);
|
|
return false;
|
|
}
|
|
|
|
FunctionBuilderAppend(context->functionData, &b, sizeof(b));
|
|
FunctionBuilderAppend(context->functionData, &index, sizeof(index));
|
|
} else {
|
|
if (!FunctionBuilderRecurse(tokenizer, child->firstChild->sibling, context->functionData, false)) return false;
|
|
}
|
|
} else if (child->type == T_DECLARE) {
|
|
if (child->isPersistentVariable && context->mainModule != tokenizer->module) {
|
|
PrintError2(tokenizer, child, "Persistent variables are not allowed in imported modules.\n");
|
|
return false;
|
|
}
|
|
|
|
context->globalVariables[context->functionData->globalVariableOffset + ScopeLookupIndex(child, root->scope, false, false)].i = 0;
|
|
context->globalVariableIsManaged[context->functionData->globalVariableOffset + ScopeLookupIndex(child, root->scope, false, false)] = ASTIsManagedType(child->expressionType);
|
|
}
|
|
|
|
child = child->sibling;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// --------------------------------- Main script execution.
|
|
|
|
void HeapGarbageCollectMark(ExecutionContext *context, uintptr_t index) {
|
|
start:;
|
|
Assert(index < context->heapEntriesAllocated);
|
|
if (context->heap[index].gcMark) return;
|
|
context->heap[index].gcMark = true;
|
|
|
|
if (context->heap[index].type == T_EOF || context->heap[index].type == T_STR || context->heap[index].type == T_FUNCPTR) {
|
|
// Nothing else to mark.
|
|
} else if (context->heap[index].type == T_STRUCT) {
|
|
for (uintptr_t i = 0; i < context->heap[index].fieldCount; i++) {
|
|
if (((uint8_t *) context->heap[index].fields)[-1 - i]) {
|
|
HeapGarbageCollectMark(context, context->heap[index].fields[i].i);
|
|
}
|
|
}
|
|
} else if (context->heap[index].type == T_LIST) {
|
|
if (context->heap[index].internalValuesAreManaged) {
|
|
for (uintptr_t i = 0; i < context->heap[index].length; i++) {
|
|
HeapGarbageCollectMark(context, context->heap[index].list[i].i);
|
|
}
|
|
}
|
|
} else if (context->heap[index].type == T_CONCAT) {
|
|
uintptr_t index1 = context->heap[index].concat1;
|
|
uintptr_t index2 = context->heap[index].concat2;
|
|
|
|
if (context->heap[index1].type == T_CONCAT) {
|
|
HeapGarbageCollectMark(context, index2);
|
|
index = index1;
|
|
} else {
|
|
HeapGarbageCollectMark(context, index1);
|
|
index = index2;
|
|
}
|
|
|
|
goto start;
|
|
} else if (context->heap[index].type == T_OP_DISCARD || context->heap[index].type == T_OP_ASSERT) {
|
|
HeapGarbageCollectMark(context, context->heap[index].lambdaID);
|
|
} else if (context->heap[index].type == T_OP_CURRY) {
|
|
HeapGarbageCollectMark(context, context->heap[index].lambdaID);
|
|
|
|
if (context->heap[index].internalValuesAreManaged) {
|
|
HeapGarbageCollectMark(context, context->heap[index].curryValue.i);
|
|
}
|
|
} else if (context->heap[index].type == T_ERR) {
|
|
if (context->heap[index].internalValuesAreManaged) {
|
|
HeapGarbageCollectMark(context, context->heap[index].errorValue.i);
|
|
}
|
|
} else if (context->heap[index].type == T_ANYTYPE) {
|
|
if (context->heap[index].internalValuesAreManaged) {
|
|
HeapGarbageCollectMark(context, context->heap[index].anyValue.i);
|
|
}
|
|
} else {
|
|
Assert(false);
|
|
}
|
|
}
|
|
|
|
void HeapFreeEntry(ExecutionContext *context, uintptr_t i) {
|
|
if (context->heap[i].type == T_STR) {
|
|
AllocateResize(context->heap[i].text, 0);
|
|
} else if (context->heap[i].type == T_STRUCT) {
|
|
AllocateResize((uint8_t *) context->heap[i].fields - context->heap[i].fieldCount, 0);
|
|
} else if (context->heap[i].type == T_LIST) {
|
|
AllocateResize(context->heap[i].list, 0);
|
|
} else if (context->heap[i].type == T_OP_DISCARD || context->heap[i].type == T_OP_ASSERT
|
|
|| context->heap[i].type == T_FUNCPTR || context->heap[i].type == T_OP_CURRY
|
|
|| context->heap[i].type == T_CONCAT || context->heap[i].type == T_ERR
|
|
|| context->heap[i].type == T_ANYTYPE) {
|
|
} 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 && !context->heap[i].externalReferenceCount) {
|
|
// PrintDebug("\033[0;32mFreeing index %d...\033[0m\n", i);
|
|
HeapFreeEntry(context, i);
|
|
*link = i;
|
|
link = &context->heap[i].nextUnusedEntry;
|
|
reclaimed++;
|
|
}
|
|
}
|
|
|
|
if (reclaimed <= context->heapEntriesAllocated / 5) {
|
|
// PrintDebug("\033[0;32mFreed only %d/%d entries. Doubling heap size...\033[0m\n", reclaimed, context->heapEntriesAllocated);
|
|
|
|
intptr_t linkIndex = link == &context->heapFirstUnusedEntry ? -1
|
|
: ((intptr_t) link - (intptr_t) context->heap) / (intptr_t) sizeof(HeapEntry);
|
|
uintptr_t oldSize = context->heapEntriesAllocated;
|
|
context->heapEntriesAllocated *= 2;
|
|
context->heap = (HeapEntry *) AllocateResize(context->heap, context->heapEntriesAllocated * sizeof(HeapEntry));
|
|
link = linkIndex == -1 ? &context->heapFirstUnusedEntry : &context->heap[linkIndex].nextUnusedEntry;
|
|
|
|
for (uintptr_t i = oldSize; i < context->heapEntriesAllocated; i++) {
|
|
context->heap[i].type = T_ERROR;
|
|
*link = i;
|
|
link = &context->heap[i].nextUnusedEntry;
|
|
}
|
|
} else {
|
|
// PrintDebug("\033[0;32mFreed %d/%d entries.\033[0m\n", reclaimed, context->heapEntriesAllocated);
|
|
}
|
|
|
|
*link = 0;
|
|
}
|
|
|
|
uintptr_t index = context->heapFirstUnusedEntry;
|
|
Assert(index);
|
|
context->heapFirstUnusedEntry = context->heap[index].nextUnusedEntry;
|
|
return index;
|
|
}
|
|
|
|
void ScriptPrintNode(Node *node, int indent) {
|
|
for (int i = 0; i < indent; i++) {
|
|
PrintDebug("\t");
|
|
}
|
|
|
|
PrintDebug("%d l%d '%.*s'\n", node->type, node->token.line, node->token.textBytes, node->token.text);
|
|
|
|
Node *child = node->firstChild;
|
|
|
|
while (child) {
|
|
ScriptPrintNode(child, indent + 1);
|
|
child = child->sibling;
|
|
}
|
|
}
|
|
|
|
size_t ScriptHeapEntryGetStringBytes(HeapEntry *entry) {
|
|
if (entry->type == T_STR) {
|
|
return entry->bytes;
|
|
} else if (entry->type == T_EOF) {
|
|
return 0;
|
|
} else if (entry->type == T_CONCAT) {
|
|
return entry->concatBytes;
|
|
} else {
|
|
Assert(false);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void ScriptHeapEntryConcatConvertToStringWrite(ExecutionContext *context, HeapEntry *entry, char *buffer) {
|
|
while (true) {
|
|
if (entry->type == T_STR) {
|
|
MemoryCopy(buffer, entry->text, entry->bytes);
|
|
} else if (entry->type == T_EOF) {
|
|
} else if (entry->type == T_CONCAT) {
|
|
HeapEntry *part1 = &context->heap[entry->concat1], *part2 = &context->heap[entry->concat2];
|
|
size_t part1Bytes = ScriptHeapEntryGetStringBytes(part1);
|
|
|
|
if (part1->type == T_CONCAT) {
|
|
ScriptHeapEntryConcatConvertToStringWrite(context, part2, buffer + part1Bytes);
|
|
Assert(part2->type != T_CONCAT);
|
|
entry = part1;
|
|
continue;
|
|
} else if (part2->type == T_CONCAT) {
|
|
ScriptHeapEntryConcatConvertToStringWrite(context, part1, buffer);
|
|
Assert(part1->type != T_CONCAT);
|
|
entry = part2;
|
|
buffer += part1Bytes;
|
|
continue;
|
|
} else {
|
|
ScriptHeapEntryConcatConvertToStringWrite(context, part1, buffer);
|
|
ScriptHeapEntryConcatConvertToStringWrite(context, part2, buffer + part1Bytes);
|
|
}
|
|
} else {
|
|
Assert(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ScriptHeapEntryConcatConvertToString(ExecutionContext *context, HeapEntry *entry) {
|
|
// TODO Efficient concatenation of many strings.
|
|
// TODO Preventing stack overflow.
|
|
Assert(entry->type == T_CONCAT);
|
|
HeapEntry *part1 = &context->heap[entry->concat1], *part2 = &context->heap[entry->concat2];
|
|
size_t part1Bytes = ScriptHeapEntryGetStringBytes(part1), part2Bytes = ScriptHeapEntryGetStringBytes(part2);
|
|
Assert(entry->concatBytes == part1Bytes + part2Bytes);
|
|
entry->type = T_STR;
|
|
entry->bytes = part1Bytes + part2Bytes;
|
|
entry->text = (char *) AllocateResize(NULL, entry->bytes);
|
|
ScriptHeapEntryConcatConvertToStringWrite(context, part1, entry->text);
|
|
ScriptHeapEntryConcatConvertToStringWrite(context, part2, entry->text + part1Bytes);
|
|
}
|
|
|
|
void ScriptHeapEntryToString(ExecutionContext *context, HeapEntry *entry, const char **text, size_t *bytes) {
|
|
if (entry->type == T_STR) {
|
|
*text = entry->text;
|
|
*bytes = entry->bytes;
|
|
} else if (entry->type == T_EOF) {
|
|
*text = "";
|
|
*bytes = 0;
|
|
} else if (entry->type == T_CONCAT) {
|
|
ScriptHeapEntryConcatConvertToString(context, entry);
|
|
ScriptHeapEntryToString(context, entry, text, bytes);
|
|
} else {
|
|
Assert(false);
|
|
*text = "";
|
|
*bytes = 0;
|
|
}
|
|
}
|
|
|
|
int ScriptExecuteFunction(uintptr_t instructionPointer, ExecutionContext *context) {
|
|
// TODO Things to verify if loading untrusted scripts -- is this a feature we will need?
|
|
// Checking we don't go off the end of the function body.
|
|
// Checking that this is actually a valid function body pointer.
|
|
// Checking various integer overflows.
|
|
|
|
uintptr_t variableBase = context->c->localVariableCount - 1;
|
|
uint8_t *functionData = context->functionData->data;
|
|
|
|
while (true) {
|
|
uint8_t command = functionData[instructionPointer++];
|
|
|
|
if (debugBytecodeLevel >= 1) {
|
|
PrintDebug("--> %d, %ld, %ld, %ld\n", command, instructionPointer - 1, context->c->id, context->c->stackPointer);
|
|
if (debugBytecodeLevel >= 2) PrintBackTrace(context, instructionPointer - 1, context->c, "");
|
|
}
|
|
|
|
if (command == T_BLOCK || command == T_FUNCBODY) {
|
|
uint16_t newVariableCount = functionData[instructionPointer + 0] + (functionData[instructionPointer + 1] << 8);
|
|
instructionPointer += 2;
|
|
|
|
if (context->c->localVariableCount + newVariableCount > context->c->localVariablesAllocated) {
|
|
// TODO Handling memory errors here.
|
|
context->c->localVariablesAllocated = context->c->localVariableCount + newVariableCount;
|
|
context->c->localVariables = (Value *) AllocateResize(context->c->localVariables, context->c->localVariablesAllocated * sizeof(Value));
|
|
context->c->localVariableIsManaged = (bool *) AllocateResize(context->c->localVariableIsManaged, context->c->localVariablesAllocated * sizeof(bool));
|
|
}
|
|
|
|
MemoryCopy(context->c->localVariableIsManaged + context->c->localVariableCount, functionData + instructionPointer, newVariableCount);
|
|
instructionPointer += newVariableCount;
|
|
|
|
for (uintptr_t i = context->c->localVariableCount; i < context->c->localVariableCount + newVariableCount; i++) {
|
|
if (command == T_FUNCBODY) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
context->c->localVariables[i] = context->c->stack[--context->c->stackPointer];
|
|
} else {
|
|
Value zero = { 0 };
|
|
context->c->localVariables[i] = zero;
|
|
}
|
|
}
|
|
|
|
context->c->localVariableCount += newVariableCount;
|
|
} else if (command == T_EXIT_SCOPE) {
|
|
uint16_t count = functionData[instructionPointer + 0] + (functionData[instructionPointer + 1] << 8);
|
|
instructionPointer += 2;
|
|
if (context->c->localVariableCount < count) return -1;
|
|
context->c->localVariableCount -= count;
|
|
} else if (command == T_NUMERIC_LITERAL) {
|
|
if (context->c->stackPointer == context->c->stackEntriesAllocated) {
|
|
PrintError4(context, instructionPointer - 1, "Stack overflow.\n");
|
|
return 0;
|
|
}
|
|
|
|
context->c->stackIsManaged[context->c->stackPointer] = false;
|
|
MemoryCopy(&context->c->stack[context->c->stackPointer++], &functionData[instructionPointer], sizeof(Value));
|
|
instructionPointer += sizeof(Value);
|
|
} else if (command == T_NULL || command == T_ZERO) {
|
|
if (context->c->stackPointer == context->c->stackEntriesAllocated) {
|
|
PrintError4(context, instructionPointer - 1, "Stack overflow.\n");
|
|
return 0;
|
|
}
|
|
|
|
context->c->stackIsManaged[context->c->stackPointer] = command == T_NULL;
|
|
context->c->stack[context->c->stackPointer++].i = 0;
|
|
} else if (command == T_STRING_LITERAL) {
|
|
if (context->c->stackPointer == context->c->stackEntriesAllocated) {
|
|
PrintError4(context, instructionPointer - 1, "Stack overflow.\n");
|
|
return 0;
|
|
}
|
|
|
|
uint32_t textBytes;
|
|
MemoryCopy(&textBytes, &functionData[instructionPointer], sizeof(textBytes));
|
|
instructionPointer += sizeof(textBytes);
|
|
|
|
// TODO Handle memory allocation failures here.
|
|
uintptr_t index = HeapAllocate(context);
|
|
context->heap[index].type = T_STR;
|
|
context->heap[index].text = (char *) AllocateResize(NULL, textBytes);
|
|
context->heap[index].bytes = textBytes;
|
|
MemoryCopy(context->heap[index].text, &functionData[instructionPointer], textBytes);
|
|
instructionPointer += textBytes;
|
|
|
|
Value v;
|
|
v.i = index;
|
|
context->c->stackIsManaged[context->c->stackPointer] = true;
|
|
context->c->stack[context->c->stackPointer++] = v;
|
|
} else if (command == T_CONCAT) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
uint64_t index1 = context->c->stack[context->c->stackPointer - 2].i;
|
|
uint64_t index2 = context->c->stack[context->c->stackPointer - 1].i;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 2]) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
if (context->heapEntriesAllocated <= index1) return -1;
|
|
if (context->heapEntriesAllocated <= index2) return -1;
|
|
Assert(index1 <= 0xFFFFFFFF && index2 <= 0xFFFFFFFF);
|
|
size_t bytes1 = ScriptHeapEntryGetStringBytes(&context->heap[index1]);
|
|
size_t bytes2 = ScriptHeapEntryGetStringBytes(&context->heap[index2]);
|
|
uintptr_t index = HeapAllocate(context); // TODO Handle memory allocation failures here.
|
|
context->heap[index].type = T_CONCAT;
|
|
context->heap[index].concat1 = index1;
|
|
context->heap[index].concat2 = index2;
|
|
context->heap[index].concatBytes = bytes1 + bytes2;
|
|
|
|
// At most one argument can be a T_CONCAT (ohterwise converting to a string could stack overflow).
|
|
if (context->heap[index1].type == T_CONCAT && context->heap[index2].type == T_CONCAT) {
|
|
ScriptHeapEntryConcatConvertToString(context, bytes1 < bytes2 ? &context->heap[index1] : &context->heap[index2]);
|
|
}
|
|
|
|
context->c->stack[context->c->stackPointer - 2].i = index;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_INTERPOLATE_STR || command == T_INTERPOLATE_BOOL
|
|
|| command == T_INTERPOLATE_INT || command == T_INTERPOLATE_FLOAT
|
|
|| command == T_INTERPOLATE_ILIST) {
|
|
STACK_READ_STRING(text1, bytes1, 3);
|
|
STACK_READ_STRING(text3, bytes3, 1);
|
|
|
|
char *freeText = NULL;
|
|
const char *text2 = "";
|
|
size_t bytes2 = 0;
|
|
char temp[30];
|
|
|
|
if (command == T_INTERPOLATE_STR) {
|
|
STACK_READ_STRING(entryText2, entryBytes2, 2);
|
|
text2 = entryText2, bytes2 = entryBytes2;
|
|
} else if (command == T_INTERPOLATE_BOOL) {
|
|
text2 = context->c->stack[context->c->stackPointer - 2].i ? "true" : "false";
|
|
bytes2 = context->c->stack[context->c->stackPointer - 2].i ? 4 : 5;
|
|
} else if (command == T_INTERPOLATE_INT) {
|
|
text2 = temp;
|
|
bytes2 = PrintIntegerToBuffer(temp, sizeof(temp), context->c->stack[context->c->stackPointer - 2].i);
|
|
} else if (command == T_INTERPOLATE_FLOAT) {
|
|
text2 = temp;
|
|
bytes2 = PrintFloatToBuffer(temp, sizeof(temp), context->c->stack[context->c->stackPointer - 2].f);
|
|
} else if (command == T_INTERPOLATE_ILIST) {
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 2]) return -1;
|
|
uint64_t index2 = context->c->stack[context->c->stackPointer - 2].i;
|
|
if (context->heapEntriesAllocated <= index2) return -1;
|
|
HeapEntry *entry2 = &context->heap[index2];
|
|
if (entry2->type != T_EOF && entry2->type != T_LIST) return -1;
|
|
|
|
if (entry2->type == T_EOF) {
|
|
text2 = "null";
|
|
bytes2 = 4;
|
|
} else if (entry2->length == 0) {
|
|
text2 = "[]";
|
|
bytes2 = 2;
|
|
} else {
|
|
if (entry2->internalValuesAreManaged) return -1;
|
|
bytes2 = 4;
|
|
|
|
for (uintptr_t i = 0; i < entry2->length; i++) {
|
|
bytes2 += PrintIntegerToBuffer(temp, sizeof(temp), entry2->list[i].i) + 2;
|
|
}
|
|
|
|
freeText = (char *) AllocateResize(freeText, bytes2);
|
|
text2 = freeText;
|
|
bytes2 = 0;
|
|
freeText[bytes2++] = '[';
|
|
freeText[bytes2++] = ' ';
|
|
|
|
for (uintptr_t i = 0; i < entry2->length; i++) {
|
|
bytes2 += PrintIntegerToBuffer(freeText + bytes2, sizeof(temp) /* enough space */, entry2->list[i].i);
|
|
freeText[bytes2++] = ',';
|
|
freeText[bytes2++] = ' ';
|
|
}
|
|
|
|
bytes2 -= 2;
|
|
freeText[bytes2++] = ' ';
|
|
freeText[bytes2++] = ']';
|
|
}
|
|
}
|
|
|
|
// TODO Handle memory allocation failures here.
|
|
uintptr_t index = HeapAllocate(context);
|
|
context->heap[index].type = T_STR;
|
|
context->heap[index].bytes = bytes1 + bytes2 + bytes3;
|
|
context->heap[index].text = (char *) AllocateResize(NULL, context->heap[index].bytes);
|
|
if (bytes1) MemoryCopy(context->heap[index].text + 0, text1, bytes1);
|
|
if (bytes2) MemoryCopy(context->heap[index].text + bytes1, text2, bytes2);
|
|
if (bytes3) MemoryCopy(context->heap[index].text + bytes1 + bytes2, text3, bytes3);
|
|
context->c->stack[context->c->stackPointer - 3].i = index;
|
|
|
|
if (freeText) AllocateResize(freeText, 0);
|
|
|
|
context->c->stackPointer -= 2;
|
|
} else if (command == T_VARIABLE) {
|
|
if (context->c->stackPointer == context->c->stackEntriesAllocated) {
|
|
PrintDebug("Stack overflow.\n");
|
|
return -1;
|
|
}
|
|
|
|
int32_t scopeIndex;
|
|
MemoryCopy(&scopeIndex, &functionData[instructionPointer], sizeof(scopeIndex));
|
|
instructionPointer += sizeof(scopeIndex);
|
|
|
|
if (scopeIndex >= 0) {
|
|
if ((uintptr_t) scopeIndex >= context->globalVariableCount) return -1;
|
|
context->c->stackIsManaged[context->c->stackPointer] = context->globalVariableIsManaged[scopeIndex];
|
|
context->c->stack[context->c->stackPointer++] = context->globalVariables[scopeIndex];
|
|
} else {
|
|
scopeIndex = variableBase - scopeIndex;
|
|
if ((uintptr_t) scopeIndex >= context->c->localVariableCount) return -1;
|
|
context->c->stackIsManaged[context->c->stackPointer] = context->c->localVariableIsManaged[scopeIndex];
|
|
context->c->stack[context->c->stackPointer++] = context->c->localVariables[scopeIndex];
|
|
}
|
|
} else if (command == T_EQUALS) {
|
|
if (!context->c->stackPointer) return -1;
|
|
int32_t scopeIndex;
|
|
MemoryCopy(&scopeIndex, &functionData[instructionPointer], sizeof(scopeIndex));
|
|
instructionPointer += sizeof(scopeIndex);
|
|
|
|
if (scopeIndex >= 0) {
|
|
if ((uintptr_t) scopeIndex >= context->globalVariableCount) return -1;
|
|
if (context->globalVariableIsManaged[scopeIndex] != context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
context->globalVariables[scopeIndex] = context->c->stack[--context->c->stackPointer];
|
|
} else {
|
|
scopeIndex = variableBase - scopeIndex;
|
|
if ((uintptr_t) scopeIndex >= context->c->localVariableCount) return -1;
|
|
if (context->c->localVariableIsManaged[scopeIndex] != context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
context->c->localVariables[scopeIndex] = context->c->stack[--context->c->stackPointer];
|
|
}
|
|
} else if (command == T_EQUALS_DOT) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
|
|
uint64_t index = context->c->stack[context->c->stackPointer - 1].i;
|
|
|
|
if (!index) {
|
|
PrintError4(context, instructionPointer - 1, "The struct is null.\n");
|
|
return 0;
|
|
}
|
|
|
|
if (context->heapEntriesAllocated <= index) return -1;
|
|
HeapEntry *entry = &context->heap[index];
|
|
if (entry->type != T_STRUCT) return -1;
|
|
|
|
int32_t fieldIndex;
|
|
MemoryCopy(&fieldIndex, &functionData[instructionPointer], sizeof(fieldIndex));
|
|
instructionPointer += sizeof(fieldIndex);
|
|
bool isManaged = fieldIndex < 0;
|
|
if (isManaged) fieldIndex = -fieldIndex - 1;
|
|
if (fieldIndex < 0 || fieldIndex >= entry->fieldCount) return -1;
|
|
|
|
entry->fields[fieldIndex] = context->c->stack[context->c->stackPointer - 2];
|
|
if (isManaged != context->c->stackIsManaged[context->c->stackPointer - 2]) return -1;
|
|
((uint8_t *) entry->fields - 1)[-fieldIndex] = isManaged;
|
|
|
|
context->c->stackPointer -= 2;
|
|
} else if (command == T_EQUALS_LIST) {
|
|
if (context->c->stackPointer < 3) return -1;
|
|
if (context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 2]) return -1;
|
|
|
|
uint64_t index = context->c->stack[context->c->stackPointer - 2].i;
|
|
|
|
if (!index) {
|
|
PrintError4(context, instructionPointer - 1, "The list is null.\n");
|
|
return 0;
|
|
}
|
|
|
|
if (context->heapEntriesAllocated <= index) return -1;
|
|
HeapEntry *entry = &context->heap[index];
|
|
if (entry->type != T_LIST) return -1;
|
|
|
|
index = context->c->stack[context->c->stackPointer - 1].i;
|
|
|
|
if (index >= entry->length) {
|
|
PrintError4(context, instructionPointer - 1, "The index %ld is not valid for the list, which has length %d.\n", index, entry->length);
|
|
return 0;
|
|
}
|
|
|
|
entry->list[index] = context->c->stack[context->c->stackPointer - 3];
|
|
if (entry->internalValuesAreManaged != context->c->stackIsManaged[context->c->stackPointer - 3]) return -1;
|
|
|
|
context->c->stackPointer -= 3;
|
|
} else if (command == T_INDEX_LIST) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
if (context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 2]) return -1;
|
|
|
|
uint64_t index = context->c->stack[context->c->stackPointer - 2].i;
|
|
|
|
if (!index) {
|
|
PrintError4(context, instructionPointer - 1, "The list is null.\n");
|
|
return 0;
|
|
}
|
|
|
|
if (context->heapEntriesAllocated <= index) return -1;
|
|
HeapEntry *entry = &context->heap[index];
|
|
if (entry->type != T_LIST) return -1;
|
|
|
|
index = context->c->stack[context->c->stackPointer - 1].i;
|
|
|
|
if (index >= entry->length) {
|
|
PrintError4(context, instructionPointer - 1, "The index %ld is not valid for the list, which has length %d.\n", index, entry->length);
|
|
return 0;
|
|
}
|
|
|
|
context->c->stack[context->c->stackPointer - 2] = entry->list[index];
|
|
context->c->stackIsManaged[context->c->stackPointer - 2] = entry->internalValuesAreManaged;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_OP_FIRST || command == T_OP_LAST) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
|
|
uint64_t index = context->c->stack[context->c->stackPointer - 1].i;
|
|
|
|
if (!index) {
|
|
PrintError4(context, instructionPointer - 1, "The list is null.\n");
|
|
return 0;
|
|
}
|
|
|
|
if (context->heapEntriesAllocated <= index) return -1;
|
|
HeapEntry *entry = &context->heap[index];
|
|
if (entry->type != T_LIST) return -1;
|
|
|
|
if (!entry->length) {
|
|
PrintError4(context, instructionPointer - 1, "The list is empty.\n");
|
|
return 0;
|
|
}
|
|
|
|
context->c->stack[context->c->stackPointer - 1] = entry->list[command == T_OP_FIRST ? 0 : entry->length - 1];
|
|
context->c->stackIsManaged[context->c->stackPointer - 1] = entry->internalValuesAreManaged;
|
|
} else if (command == T_DOT) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
|
|
uint64_t index = context->c->stack[context->c->stackPointer - 1].i;
|
|
|
|
if (!index) {
|
|
PrintError4(context, instructionPointer - 1, "The struct is null.\n");
|
|
return 0;
|
|
}
|
|
|
|
if (context->heapEntriesAllocated <= index) return -1;
|
|
HeapEntry *entry = &context->heap[index];
|
|
if (entry->type != T_STRUCT) return -1;
|
|
|
|
int16_t fieldIndex;
|
|
MemoryCopy(&fieldIndex, &functionData[instructionPointer], sizeof(fieldIndex));
|
|
instructionPointer += sizeof(fieldIndex);
|
|
bool isManaged = fieldIndex < 0;
|
|
if (isManaged) fieldIndex = -fieldIndex - 1;
|
|
if (fieldIndex < 0 || fieldIndex >= entry->fieldCount) return -1;
|
|
|
|
// Only allow the isManaged bool to be incorrect if it's a null managed variable.
|
|
if (isManaged != ((uint8_t *) entry->fields - 1)[-fieldIndex] && (entry->fields[fieldIndex].i || !isManaged)) return -1;
|
|
|
|
context->c->stack[context->c->stackPointer - 1] = entry->fields[fieldIndex];
|
|
context->c->stackIsManaged[context->c->stackPointer - 1] = isManaged;
|
|
} else if (command == T_BIT_SHIFT_LEFT) {
|
|
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_BIT_SHIFT_RIGHT) {
|
|
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_BITWISE_OR) {
|
|
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_BITWISE_AND) {
|
|
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_BITWISE_XOR) {
|
|
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_ADD) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].i + context->c->stack[context->c->stackPointer - 1].i;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_MINUS) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].i - context->c->stack[context->c->stackPointer - 1].i;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_ASTERISK) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].i * context->c->stack[context->c->stackPointer - 1].i;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_SLASH) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
|
|
if (0 == context->c->stack[context->c->stackPointer - 1].i) {
|
|
PrintError4(context, instructionPointer - 1, "Attempted division by zero.\n");
|
|
return 0;
|
|
}
|
|
|
|
context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].f / context->c->stack[context->c->stackPointer - 1].f;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_NEGATE) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
context->c->stack[context->c->stackPointer - 1].i = -context->c->stack[context->c->stackPointer - 1].i;
|
|
} else if (command == T_BITWISE_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_ADD) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
context->c->stack[context->c->stackPointer - 2].f = context->c->stack[context->c->stackPointer - 2].f + context->c->stack[context->c->stackPointer - 1].f;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_FLOAT_MINUS) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
context->c->stack[context->c->stackPointer - 2].f = context->c->stack[context->c->stackPointer - 2].f - context->c->stack[context->c->stackPointer - 1].f;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_FLOAT_ASTERISK) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
context->c->stack[context->c->stackPointer - 2].f = context->c->stack[context->c->stackPointer - 2].f * context->c->stack[context->c->stackPointer - 1].f;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_FLOAT_SLASH) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
context->c->stack[context->c->stackPointer - 2].f = context->c->stack[context->c->stackPointer - 2].f / context->c->stack[context->c->stackPointer - 1].f;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_FLOAT_NEGATE) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
context->c->stack[context->c->stackPointer - 1].f = -context->c->stack[context->c->stackPointer - 1].f;
|
|
} else if (command == T_LESS_THAN) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].i < context->c->stack[context->c->stackPointer - 1].i;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_GREATER_THAN) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].i > context->c->stack[context->c->stackPointer - 1].i;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_LT_OR_EQUAL) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].i <= context->c->stack[context->c->stackPointer - 1].i;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_GT_OR_EQUAL) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].i >= context->c->stack[context->c->stackPointer - 1].i;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_DOUBLE_EQUALS) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].i == context->c->stack[context->c->stackPointer - 1].i;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_NOT_EQUALS) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].i != context->c->stack[context->c->stackPointer - 1].i;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_LOGICAL_NOT) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
context->c->stack[context->c->stackPointer - 1].i = !context->c->stack[context->c->stackPointer - 1].i;
|
|
} else if (command == T_FLOAT_LESS_THAN) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].f < context->c->stack[context->c->stackPointer - 1].f;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_FLOAT_GREATER_THAN) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].f > context->c->stack[context->c->stackPointer - 1].f;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_FLOAT_LT_OR_EQUAL) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].f <= context->c->stack[context->c->stackPointer - 1].f;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_FLOAT_GT_OR_EQUAL) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].f >= context->c->stack[context->c->stackPointer - 1].f;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_FLOAT_DOUBLE_EQUALS) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].f == context->c->stack[context->c->stackPointer - 1].f;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_FLOAT_NOT_EQUALS) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
context->c->stack[context->c->stackPointer - 2].i = context->c->stack[context->c->stackPointer - 2].f != context->c->stack[context->c->stackPointer - 1].f;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_STR_DOUBLE_EQUALS || command == T_STR_NOT_EQUALS) {
|
|
STACK_READ_STRING(text1, bytes1, 2);
|
|
STACK_READ_STRING(text2, bytes2, 1);
|
|
bool equal = bytes1 == bytes2 && 0 == MemoryCompare(text1, text2, bytes1);
|
|
context->c->stack[context->c->stackPointer - 2].i = command == T_STR_NOT_EQUALS ? !equal : equal;
|
|
context->c->stackIsManaged[context->c->stackPointer - 2] = false;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_OP_LEN) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
uint64_t index = context->c->stack[context->c->stackPointer - 1].i;
|
|
if (context->heapEntriesAllocated <= index) return -1;
|
|
HeapEntry *entry = &context->heap[index];
|
|
|
|
if (entry->type == T_LIST) {
|
|
context->c->stack[context->c->stackPointer - 1].i = entry->length;
|
|
} else {
|
|
STACK_READ_STRING(stringText, stringBytes, 1);
|
|
context->c->stack[context->c->stackPointer - 1].i = stringBytes;
|
|
}
|
|
|
|
context->c->stackIsManaged[context->c->stackPointer - 1] = false;
|
|
} else if (command == T_INDEX) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
STACK_READ_STRING(text, bytes, 2);
|
|
if (context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
uintptr_t index = context->c->stack[context->c->stackPointer - 1].i;
|
|
|
|
if (index >= bytes) {
|
|
PrintError4(context, instructionPointer - 1, "Index %ld out of bounds in string '%.*s' of length %ld.\n",
|
|
index, bytes, text, bytes);
|
|
return 0;
|
|
}
|
|
|
|
char c = text[index];
|
|
index = HeapAllocate(context);
|
|
context->heap[index].type = T_STR;
|
|
context->heap[index].bytes = 1;
|
|
context->heap[index].text = (char *) AllocateResize(NULL, 1); // TODO Handling allocation failure.
|
|
context->heap[index].text[0] = c;
|
|
context->c->stack[context->c->stackPointer - 2].i = index;
|
|
context->c->stackIsManaged[context->c->stackPointer - 2] = true;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_CALL) {
|
|
callCommand:;
|
|
if (context->c->stackPointer < 1) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
Value newBody = context->c->stack[--context->c->stackPointer];
|
|
|
|
if (newBody.i == 0) {
|
|
PrintError4(context, instructionPointer - 1, "Function pointer was null.\n");
|
|
return 0;
|
|
}
|
|
|
|
bool popResult = false;
|
|
bool assertResult = false;
|
|
|
|
while (true) {
|
|
uint64_t index = newBody.i;
|
|
if (context->heapEntriesAllocated <= index) return -1;
|
|
HeapEntry *entry = &context->heap[index];
|
|
newBody.i = entry->lambdaID;
|
|
|
|
if (entry->type == T_OP_DISCARD) {
|
|
popResult = true;
|
|
} else if (entry->type == T_OP_ASSERT) {
|
|
assertResult = true;
|
|
} else if (entry->type == T_OP_CURRY) {
|
|
if (context->c->stackPointer == context->c->stackEntriesAllocated) {
|
|
PrintError4(context, instructionPointer - 1, "Stack overflow.\n");
|
|
return 0;
|
|
}
|
|
|
|
context->c->stack[context->c->stackPointer] = entry->curryValue;
|
|
context->c->stackIsManaged[context->c->stackPointer] = entry->internalValuesAreManaged;
|
|
context->c->stackPointer++;
|
|
} else if (entry->type == T_FUNCPTR) {
|
|
break;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (context->c->backTracePointer == sizeof(context->c->backTrace) / sizeof(context->c->backTrace[0])) {
|
|
PrintError4(context, instructionPointer - 1, "Back trace overflow.\n");
|
|
return 0;
|
|
}
|
|
|
|
BackTraceItem *link = &context->c->backTrace[context->c->backTracePointer];
|
|
context->c->backTracePointer++;
|
|
link->instructionPointer = instructionPointer;
|
|
link->variableBase = variableBase;
|
|
link->popResult = popResult;
|
|
link->assertResult = assertResult;
|
|
instructionPointer = newBody.i;
|
|
variableBase = context->c->localVariableCount - 1;
|
|
} else if (command == T_IF) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
Value condition = context->c->stack[--context->c->stackPointer];
|
|
int32_t delta;
|
|
MemoryCopy(&delta, &functionData[instructionPointer], sizeof(delta));
|
|
instructionPointer += condition.i ? (int32_t) sizeof(delta) : delta;
|
|
} else if (command == T_LOGICAL_OR) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
Value condition = context->c->stack[context->c->stackPointer - 1];
|
|
int32_t delta;
|
|
MemoryCopy(&delta, &functionData[instructionPointer], sizeof(delta));
|
|
instructionPointer += condition.i ? delta : (int32_t) sizeof(delta);
|
|
if (!condition.i) context->c->stackPointer--;
|
|
} else if (command == T_LOGICAL_AND) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
Value condition = context->c->stack[context->c->stackPointer - 1];
|
|
int32_t delta;
|
|
MemoryCopy(&delta, &functionData[instructionPointer], sizeof(delta));
|
|
instructionPointer += condition.i ? (int32_t) sizeof(delta) : delta;
|
|
if (condition.i) context->c->stackPointer--;
|
|
} else if (command == T_BRANCH) {
|
|
int32_t delta;
|
|
MemoryCopy(&delta, &functionData[instructionPointer], sizeof(delta));
|
|
instructionPointer += delta;
|
|
} else if (command == T_POP) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_DUP) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
|
|
if (context->c->stackPointer == context->c->stackEntriesAllocated) {
|
|
PrintError4(context, instructionPointer - 1, "Stack overflow.\n");
|
|
return 0;
|
|
}
|
|
|
|
context->c->stack[context->c->stackPointer] = context->c->stack[context->c->stackPointer - 1];
|
|
context->c->stackIsManaged[context->c->stackPointer] = context->c->stackIsManaged[context->c->stackPointer - 1];
|
|
context->c->stackPointer++;
|
|
} else if (command == T_SWAP) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
Value v1 = context->c->stack[context->c->stackPointer - 1];
|
|
Value v2 = context->c->stack[context->c->stackPointer - 2];
|
|
bool m1 = context->c->stackIsManaged[context->c->stackPointer - 1];
|
|
bool m2 = context->c->stackIsManaged[context->c->stackPointer - 2];
|
|
context->c->stack[context->c->stackPointer - 1] = v2;
|
|
context->c->stack[context->c->stackPointer - 2] = v1;
|
|
context->c->stackIsManaged[context->c->stackPointer - 1] = m2;
|
|
context->c->stackIsManaged[context->c->stackPointer - 2] = m1;
|
|
} else if (command == T_ROT3) {
|
|
if (context->c->stackPointer < 3) return -1;
|
|
Value v1 = context->c->stack[context->c->stackPointer - 1];
|
|
Value v2 = context->c->stack[context->c->stackPointer - 2];
|
|
Value v3 = context->c->stack[context->c->stackPointer - 3];
|
|
bool m1 = context->c->stackIsManaged[context->c->stackPointer - 1];
|
|
bool m2 = context->c->stackIsManaged[context->c->stackPointer - 2];
|
|
bool m3 = context->c->stackIsManaged[context->c->stackPointer - 3];
|
|
context->c->stack[context->c->stackPointer - 1] = v3;
|
|
context->c->stack[context->c->stackPointer - 2] = v1;
|
|
context->c->stack[context->c->stackPointer - 3] = v2;
|
|
context->c->stackIsManaged[context->c->stackPointer - 1] = m3;
|
|
context->c->stackIsManaged[context->c->stackPointer - 2] = m1;
|
|
context->c->stackIsManaged[context->c->stackPointer - 3] = m2;
|
|
} 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_ERR_CAST) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
|
|
// TODO Handle memory allocation failures here.
|
|
uintptr_t index = HeapAllocate(context);
|
|
context->heap[index].type = T_ERR;
|
|
context->heap[index].success = true;
|
|
context->heap[index].internalValuesAreManaged = context->c->stackIsManaged[context->c->stackPointer - 1];;
|
|
context->heap[index].errorValue = context->c->stack[context->c->stackPointer - 1];
|
|
|
|
Value v;
|
|
v.i = index;
|
|
context->c->stackIsManaged[context->c->stackPointer - 1] = true;
|
|
context->c->stack[context->c->stackPointer - 1] = v;
|
|
} else if (command == T_ANYTYPE_CAST) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
|
|
// TODO Handle memory allocation failures here.
|
|
uintptr_t index = HeapAllocate(context);
|
|
context->heap[index].type = T_ANYTYPE;
|
|
MemoryCopy(&context->heap[index].anyType, &functionData[instructionPointer], sizeof(context->heap[index].anyType));
|
|
context->heap[index].internalValuesAreManaged = ASTIsManagedType(context->heap[index].anyType);
|
|
context->heap[index].anyValue = context->c->stack[context->c->stackPointer - 1];
|
|
|
|
Value v;
|
|
v.i = index;
|
|
context->c->stackIsManaged[context->c->stackPointer - 1] = true;
|
|
context->c->stack[context->c->stackPointer - 1] = v;
|
|
|
|
instructionPointer += sizeof(context->heap[index].anyType);
|
|
} else if (command == T_OP_CAST) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
uintptr_t index = context->c->stack[context->c->stackPointer - 1].i;
|
|
|
|
if (index == 0) {
|
|
PrintError4(context, instructionPointer - 1, "The object is null.\n");
|
|
return 0;
|
|
}
|
|
|
|
if (context->heapEntriesAllocated <= index) return -1;
|
|
HeapEntry *entry = &context->heap[index];
|
|
if (entry->type != T_ANYTYPE) return -1;
|
|
Node *expressionType;
|
|
MemoryCopy(&expressionType, &functionData[instructionPointer], sizeof(expressionType));
|
|
|
|
if (!ASTMatching(expressionType, entry->anyType)) {
|
|
PrintError4(context, instructionPointer - 1, "Invalid cast.\n");
|
|
return 0;
|
|
}
|
|
|
|
Assert(ASTIsManagedType(expressionType) == entry->internalValuesAreManaged);
|
|
context->c->stackIsManaged[context->c->stackPointer - 1] = entry->internalValuesAreManaged;
|
|
context->c->stack[context->c->stackPointer - 1] = entry->anyValue;
|
|
instructionPointer += sizeof(expressionType);
|
|
} else if (command == T_OP_SUCCESS) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
uintptr_t index = context->c->stack[context->c->stackPointer - 1].i;
|
|
bool success = false;
|
|
|
|
if (index) {
|
|
if (context->heapEntriesAllocated <= index) return -1;
|
|
HeapEntry *entry = &context->heap[index];
|
|
if (entry->type != T_ERR) return -1;
|
|
success = entry->success;
|
|
}
|
|
|
|
context->c->stack[context->c->stackPointer - 1].i = success;
|
|
context->c->stackIsManaged[context->c->stackPointer - 1] = false;
|
|
} else if (command == T_OP_ASSERT_ERR) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
uintptr_t index = context->c->stack[context->c->stackPointer - 1].i;
|
|
|
|
if (index == 0) {
|
|
PrintError4(context, instructionPointer - 1, "Assertion failed. Unknown error.\n");
|
|
return 0;
|
|
} else {
|
|
if (context->heapEntriesAllocated <= index) return -1;
|
|
HeapEntry *entry = &context->heap[index];
|
|
if (entry->type != T_ERR) return -1;
|
|
|
|
if (!entry->success) {
|
|
if (!entry->internalValuesAreManaged) return -1;
|
|
size_t textBytes;
|
|
const char *text;
|
|
ScriptHeapEntryToString(context, &context->heap[entry->errorValue.i], &text, &textBytes);
|
|
PrintError4(context, instructionPointer - 1, "Assertion failed.\nThe error code is: '%.*s'.\n", textBytes, text);
|
|
return 0;
|
|
}
|
|
|
|
context->c->stack[context->c->stackPointer - 1] = entry->errorValue;
|
|
context->c->stackIsManaged[context->c->stackPointer - 1] = entry->internalValuesAreManaged;
|
|
}
|
|
} else if (command == T_OP_ERROR) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
uintptr_t index = context->c->stack[context->c->stackPointer - 1].i;
|
|
|
|
if (index == 0) {
|
|
index = HeapAllocate(context);
|
|
context->heap[index].type = T_STR;
|
|
context->heap[index].text = (char *) AllocateResize(NULL, 7);
|
|
context->heap[index].bytes = 7;
|
|
MemoryCopy(context->heap[index].text, "UNKNOWN", 7);
|
|
} else {
|
|
if (context->heapEntriesAllocated <= index) return -1;
|
|
HeapEntry *entry = &context->heap[index];
|
|
if (entry->type != T_ERR) return -1;
|
|
if (!entry->success && !entry->internalValuesAreManaged) return -1;
|
|
index = entry->success ? 0 : entry->errorValue.i;
|
|
}
|
|
|
|
context->c->stack[context->c->stackPointer - 1].i = index;
|
|
context->c->stackIsManaged[context->c->stackPointer - 1] = true;
|
|
} else if (command == T_OP_DEFAULT) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 2]) return -1;
|
|
uintptr_t index = context->c->stack[context->c->stackPointer - 2].i;
|
|
|
|
if (index) {
|
|
if (context->heapEntriesAllocated <= index) return -1;
|
|
HeapEntry *entry = &context->heap[index];
|
|
if (entry->type != T_ERR) return -1;
|
|
if (!entry->success && !entry->internalValuesAreManaged) return -1;
|
|
|
|
if (entry->success) {
|
|
context->c->stack[context->c->stackPointer - 1] = entry->errorValue;
|
|
context->c->stackIsManaged[context->c->stackPointer - 1] = entry->internalValuesAreManaged;
|
|
}
|
|
}
|
|
|
|
context->c->stack[context->c->stackPointer - 2] = context->c->stack[context->c->stackPointer - 1];
|
|
context->c->stackIsManaged[context->c->stackPointer - 2] = context->c->stackIsManaged[context->c->stackPointer - 1];
|
|
context->c->stackPointer--;
|
|
} else if (command == T_OP_INT_TO_FLOAT) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
if (context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
context->c->stack[context->c->stackPointer - 1].f = context->c->stack[context->c->stackPointer - 1].i;
|
|
} else if (command == T_OP_FLOAT_TRUNCATE) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
if (context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
context->c->stack[context->c->stackPointer - 1].i = context->c->stack[context->c->stackPointer - 1].f;
|
|
} 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_STRUCT : fieldCount >= -2 ? T_LIST : T_ERR;
|
|
|
|
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 if (fieldCount >= -2) {
|
|
context->heap[index].internalValuesAreManaged = fieldCount == -2;
|
|
context->heap[index].length = context->heap[index].allocated = 0;
|
|
context->heap[index].list = NULL;
|
|
} else {
|
|
context->heap[index].internalValuesAreManaged = true;
|
|
context->heap[index].success = false;
|
|
|
|
if (context->c->stackPointer < 1) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
context->heap[index].errorValue = context->c->stack[context->c->stackPointer - 1];
|
|
context->c->stackPointer--;
|
|
}
|
|
|
|
Value v;
|
|
v.i = index;
|
|
context->c->stackIsManaged[context->c->stackPointer] = true;
|
|
context->c->stack[context->c->stackPointer++] = v;
|
|
} else if (command == T_OP_RESIZE) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 2]) return -1;
|
|
|
|
uint64_t index = context->c->stack[context->c->stackPointer - 2].i;
|
|
|
|
if (!index) {
|
|
PrintError4(context, instructionPointer - 1, "The list is null.\n");
|
|
return 0;
|
|
}
|
|
|
|
if (context->heapEntriesAllocated <= index) return -1;
|
|
HeapEntry *entry = &context->heap[index];
|
|
if (entry->type != T_LIST) return -1;
|
|
|
|
int64_t newLength = context->c->stack[context->c->stackPointer - 1].i;
|
|
|
|
if (newLength < 0 || newLength >= 1000000000) {
|
|
PrintError4(context, instructionPointer - 1, "The new length of the list is out of the supported range (0..1000000000).\n");
|
|
return 0;
|
|
}
|
|
|
|
uint32_t oldLength = context->heap[index].length;
|
|
context->heap[index].length = newLength;
|
|
context->heap[index].allocated = newLength;
|
|
|
|
// TODO Handling out of memory errors.
|
|
context->heap[index].list = (Value *) AllocateResize(context->heap[index].list, newLength * sizeof(Value));
|
|
|
|
for (uintptr_t i = oldLength; i < (size_t) newLength; i++) {
|
|
context->heap[index].list[i].i = 0;
|
|
}
|
|
|
|
context->c->stackPointer -= 2;
|
|
} else if (command == T_OP_ADD) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 2]) return -1;
|
|
|
|
uint64_t index = context->c->stack[context->c->stackPointer - 2].i;
|
|
|
|
if (!index) {
|
|
PrintError4(context, instructionPointer - 1, "The list is null.\n");
|
|
return 0;
|
|
}
|
|
|
|
if (context->heapEntriesAllocated <= index) return -1;
|
|
HeapEntry *entry = &context->heap[index];
|
|
if (entry->type != T_LIST) return -1;
|
|
|
|
int64_t newLength = entry->length + 1;
|
|
|
|
if (newLength < 0 || newLength >= 1000000000) {
|
|
PrintError4(context, instructionPointer - 1, "The new length of the list is out of the supported range (0..1000000000).\n");
|
|
return 0;
|
|
}
|
|
|
|
uint32_t oldLength = context->heap[index].length;
|
|
entry->length = newLength;
|
|
|
|
if (entry->length > entry->allocated) {
|
|
// TODO Handling out of memory errors.
|
|
entry->allocated = entry->allocated ? entry->allocated * 2 : 4;
|
|
entry->list = (Value *) AllocateResize(entry->list, entry->allocated * sizeof(Value));
|
|
Assert(entry->length <= entry->allocated);
|
|
}
|
|
|
|
if (entry->internalValuesAreManaged != context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
entry->list[oldLength] = context->c->stack[context->c->stackPointer - 1];
|
|
|
|
context->c->stackPointer -= 2;
|
|
} else if (command == T_OP_INSERT) {
|
|
if (context->c->stackPointer < 3) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 3]) return -1;
|
|
|
|
uint64_t index = context->c->stack[context->c->stackPointer - 3].i;
|
|
|
|
if (!index) {
|
|
PrintError4(context, instructionPointer - 1, "The list is null.\n");
|
|
return 0;
|
|
}
|
|
|
|
if (context->heapEntriesAllocated <= index) return -1;
|
|
HeapEntry *entry = &context->heap[index];
|
|
if (entry->type != T_LIST) return -1;
|
|
|
|
int64_t newLength = entry->length + 1;
|
|
|
|
if (newLength < 0 || newLength >= 1000000000) {
|
|
PrintError4(context, instructionPointer - 1, "The new length of the list is out of the supported range (0..1000000000).\n");
|
|
return 0;
|
|
}
|
|
|
|
uint32_t oldLength = context->heap[index].length;
|
|
entry->length = newLength;
|
|
|
|
if (entry->length > entry->allocated) {
|
|
// TODO Handling out of memory errors.
|
|
entry->allocated = entry->allocated ? entry->allocated * 2 : 4;
|
|
entry->list = (Value *) AllocateResize(entry->list, entry->allocated * sizeof(Value));
|
|
Assert(entry->length <= entry->allocated);
|
|
}
|
|
|
|
if (context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
int64_t insertIndex = context->c->stack[context->c->stackPointer - 1].i;
|
|
|
|
if (insertIndex < 0 || insertIndex > oldLength) {
|
|
PrintError4(context, instructionPointer - 1, "Cannot insert at index %ld. The list has length %ld.\n",
|
|
insertIndex, oldLength);
|
|
return 0;
|
|
}
|
|
|
|
for (int64_t i = oldLength - 1; i >= insertIndex; i--) {
|
|
entry->list[i + 1] = entry->list[i];
|
|
}
|
|
|
|
if (entry->internalValuesAreManaged != context->c->stackIsManaged[context->c->stackPointer - 2]) return -1;
|
|
entry->list[insertIndex] = context->c->stack[context->c->stackPointer - 2];
|
|
|
|
context->c->stackPointer -= 3;
|
|
} else if (command == T_OP_INSERT_MANY) {
|
|
if (context->c->stackPointer < 3) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 3]) return -1;
|
|
|
|
uint64_t index = context->c->stack[context->c->stackPointer - 3].i;
|
|
|
|
if (!index) {
|
|
PrintError4(context, instructionPointer - 1, "The list is null.\n");
|
|
return 0;
|
|
}
|
|
|
|
if (context->heapEntriesAllocated <= index) return -1;
|
|
HeapEntry *entry = &context->heap[index];
|
|
if (entry->type != T_LIST) return -1;
|
|
|
|
if (context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
int64_t insertCount = context->c->stack[context->c->stackPointer - 1].i;
|
|
int64_t newLength = (int64_t) entry->length + insertCount;
|
|
|
|
if (insertCount < 0) {
|
|
PrintError4(context, instructionPointer - 1, "The number of items to insert is negative (%ld).\n", insertCount);
|
|
return 0;
|
|
} else if (newLength < 0 || newLength >= 1000000000) {
|
|
PrintError4(context, instructionPointer - 1, "The new length of the list (%ld + %ld = %ld) "
|
|
"is out of the supported range (0..1000000000).\n", (int64_t) entry->length, insertCount, newLength);
|
|
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;
|
|
if (entry->length > entry->allocated) entry->allocated = entry->length + 5;
|
|
entry->list = (Value *) AllocateResize(entry->list, entry->allocated * sizeof(Value));
|
|
Assert(entry->length <= entry->allocated);
|
|
}
|
|
|
|
if (context->c->stackIsManaged[context->c->stackPointer - 2]) return -1;
|
|
int64_t insertIndex = context->c->stack[context->c->stackPointer - 2].i;
|
|
|
|
if (insertIndex < 0 || insertIndex > oldLength) {
|
|
PrintError4(context, instructionPointer - 1, "Cannot insert at index %ld. The list has length %ld.\n",
|
|
insertIndex, oldLength);
|
|
return 0;
|
|
}
|
|
|
|
for (int64_t i = oldLength - 1; i >= insertIndex; i--) {
|
|
entry->list[i + insertCount] = entry->list[i];
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < (uintptr_t) insertCount; i++) {
|
|
entry->list[i + insertIndex].i = 0;
|
|
}
|
|
|
|
context->c->stackPointer -= 3;
|
|
} else if (command == T_OP_DELETE || command == T_OP_DELETE_MANY) {
|
|
int stackIndexList = command == T_OP_DELETE ? 2 : 3;
|
|
int stackIndexIndex = command == T_OP_DELETE ? 1 : 2;
|
|
if (context->c->stackPointer < (uintptr_t) stackIndexList) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - stackIndexList]) return -1;
|
|
uint64_t index = context->c->stack[context->c->stackPointer - stackIndexList].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 (context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
int64_t deleteCount = command == T_OP_DELETE ? 1 : context->c->stack[context->c->stackPointer - 1].i;
|
|
int64_t newLength = (int64_t) entry->length - deleteCount;
|
|
|
|
if (deleteCount < 0) {
|
|
PrintError4(context, instructionPointer - 1, "The number of items to delete is negative (%ld).\n", deleteCount);
|
|
return 0;
|
|
} else if (newLength < 0 || newLength >= 1000000000) {
|
|
PrintError4(context, instructionPointer - 1, "The new length of the list (%ld - %ld = %ld) "
|
|
"is out of the supported range (0..1000000000).\n", (int64_t) entry->length, deleteCount, newLength);
|
|
return 0;
|
|
}
|
|
|
|
// TODO Maybe shrink the list storage, if it will save a lot of memory.
|
|
|
|
if (context->c->stackIsManaged[context->c->stackPointer - stackIndexIndex]) return -1;
|
|
int64_t deleteIndex = context->c->stack[context->c->stackPointer - stackIndexIndex].i;
|
|
|
|
if (deleteIndex < 0 || deleteIndex > newLength) {
|
|
PrintError4(context, instructionPointer - 1, "Cannot delete %ld items starting at index %ld. The list has length %ld.\n",
|
|
deleteCount, deleteIndex, (int64_t) entry->length);
|
|
return 0;
|
|
}
|
|
|
|
for (int64_t i = deleteIndex; i < newLength; i++) {
|
|
entry->list[i] = entry->list[i + deleteCount];
|
|
}
|
|
|
|
entry->length = newLength;
|
|
context->c->stackPointer -= command == T_OP_DELETE ? 2 : 3;
|
|
} else if (command == T_OP_DELETE_ALL) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
|
|
uint64_t index = context->c->stack[context->c->stackPointer - 1].i;
|
|
|
|
if (!index) {
|
|
PrintError4(context, instructionPointer - 1, "The list is null.\n");
|
|
return 0;
|
|
}
|
|
|
|
if (context->heapEntriesAllocated <= index) return -1;
|
|
HeapEntry *entry = &context->heap[index];
|
|
if (entry->type != T_LIST) return -1;
|
|
|
|
context->heap[index].length = context->heap[index].allocated = 0;
|
|
context->heap[index].list = (Value *) AllocateResize(context->heap[index].list, 0);
|
|
context->c->stackPointer--;
|
|
} else if (command == T_OP_FIND_AND_DELETE || command == T_OP_FIND
|
|
|| command == T_OP_FIND_AND_DEL_STR || command == T_OP_FIND_STR) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 2]) return -1;
|
|
|
|
uint64_t index = context->c->stack[context->c->stackPointer - 2].i;
|
|
|
|
if (!index) {
|
|
PrintError4(context, instructionPointer - 1, "The list is null.\n");
|
|
return 0;
|
|
}
|
|
|
|
if (context->heapEntriesAllocated <= index) return -1;
|
|
HeapEntry *entry = &context->heap[index];
|
|
if (entry->type != T_LIST) return -1;
|
|
if (entry->internalValuesAreManaged != context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
if ((command == T_OP_FIND_STR || command == T_OP_FIND_AND_DEL_STR) && !entry->internalValuesAreManaged) return -1;
|
|
context->c->stack[context->c->stackPointer - 2].i = command == T_OP_FIND || command == T_OP_FIND_STR ? -1 : 0;
|
|
|
|
for (uintptr_t i = 0; i < entry->length; i++) {
|
|
if (command == T_OP_FIND_STR || command == T_OP_FIND_AND_DEL_STR) {
|
|
const char *text1, *text2;
|
|
size_t bytes1, bytes2;
|
|
ScriptHeapEntryToString(context, &context->heap[entry->list[i].i], &text1, &bytes1);
|
|
ScriptHeapEntryToString(context, &context->heap[context->c->stack[context->c->stackPointer - 1].i], &text2, &bytes2);
|
|
bool equal = bytes1 == bytes2 && 0 == MemoryCompare(text1, text2, bytes1);
|
|
if (!equal) continue;
|
|
} else {
|
|
bool equal = entry->list[i].i == context->c->stack[context->c->stackPointer - 1].i;
|
|
if (!equal) continue;
|
|
}
|
|
|
|
if (command == T_OP_FIND || command == T_OP_FIND_STR) {
|
|
context->c->stack[context->c->stackPointer - 2].i = i;
|
|
} else {
|
|
context->c->stack[context->c->stackPointer - 2].i = 1;
|
|
entry->length--;
|
|
|
|
for (uintptr_t j = i; j < entry->length; j++) {
|
|
entry->list[j] = entry->list[j + 1];
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
context->c->stackIsManaged[context->c->stackPointer - 2] = false;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_OP_DISCARD || command == T_OP_ASSERT) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
int64_t id = context->c->stack[context->c->stackPointer - 1].i;
|
|
uintptr_t index = HeapAllocate(context);
|
|
context->heap[index].type = command;
|
|
context->heap[index].lambdaID = id;
|
|
context->c->stackIsManaged[context->c->stackPointer - 1] = true;
|
|
context->c->stack[context->c->stackPointer - 1].i = index;
|
|
} else if (command == T_OP_CURRY) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 2]) return -1;
|
|
bool valueIsManaged = context->c->stackIsManaged[context->c->stackPointer - 1];
|
|
Value value = context->c->stack[context->c->stackPointer - 1];
|
|
int64_t id = context->c->stack[context->c->stackPointer - 2].i;
|
|
uintptr_t index = HeapAllocate(context);
|
|
context->heap[index].type = command;
|
|
context->heap[index].lambdaID = id;
|
|
context->heap[index].curryValue = value;
|
|
context->heap[index].internalValuesAreManaged = valueIsManaged;
|
|
context->c->stackIsManaged[context->c->stackPointer - 2] = true;
|
|
context->c->stack[context->c->stackPointer - 2].i = index;
|
|
context->c->stackPointer--;
|
|
} else if (command == T_OP_ASYNC) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
CoroutineState *c = (CoroutineState *) AllocateResize(NULL, sizeof(CoroutineState)); // TODO Handle allocation failure.
|
|
CoroutineState empty = { 0 };
|
|
*c = empty;
|
|
c->id = ++context->lastCoroutineID;
|
|
c->startedByAsync = true;
|
|
c->stackEntriesAllocated = sizeof(context->c->stack) / sizeof(context->c->stack[0]);
|
|
c->stackPointer = 2;
|
|
c->stack[0].i = -1; // Indicates to T_AWAIT to remove the coroutine.
|
|
c->stackIsManaged[0] = false;
|
|
c->stack[1] = context->c->stack[context->c->stackPointer - 1];
|
|
c->stackIsManaged[1] = true;
|
|
c->nextCoroutine = context->allCoroutines;
|
|
if (c->nextCoroutine) c->nextCoroutine->previousCoroutineLink = &c->nextCoroutine;
|
|
c->previousCoroutineLink = &context->allCoroutines;
|
|
context->allCoroutines = c;
|
|
c->nextUnblockedCoroutine = context->unblockedCoroutines;
|
|
if (c->nextUnblockedCoroutine) c->nextUnblockedCoroutine->previousUnblockedCoroutineLink = &c->nextUnblockedCoroutine;
|
|
c->previousUnblockedCoroutineLink = &context->unblockedCoroutines;
|
|
context->unblockedCoroutines = c;
|
|
context->c->stackIsManaged[context->c->stackPointer - 1] = false;
|
|
context->c->stack[context->c->stackPointer - 1].i = c->id;
|
|
} else if (command == T_AWAIT) {
|
|
awaitCommand:;
|
|
if (context->c->stackPointer < 1 && !context->c->externalCoroutine) return -1;
|
|
|
|
// PrintDebug("== AWAIT from %ld\n", context->c->id);
|
|
Assert(!context->c->nextUnblockedCoroutine && !context->c->previousUnblockedCoroutineLink);
|
|
bool unblockImmediately = false;
|
|
|
|
if (context->c->externalCoroutine) {
|
|
// PrintDebug("== external coroutine\n");
|
|
context->c->unblockedBy = -1;
|
|
context->c->awaiting = true;
|
|
context->c->instructionPointer = instructionPointer;
|
|
context->c->variableBase = variableBase;
|
|
context->c->waitingOnCount = 0;
|
|
} else if (context->c->stack[context->c->stackPointer - 1].i == -1) {
|
|
if (context->c->stackPointer != 1) return -1;
|
|
// The coroutine has finished. Remove it from the list of all coroutines.
|
|
*context->c->previousCoroutineLink = context->c->nextCoroutine;
|
|
if (context->c->nextCoroutine) context->c->nextCoroutine->previousCoroutineLink = context->c->previousCoroutineLink;
|
|
|
|
// PrintDebug("== finished\n");
|
|
|
|
for (uintptr_t i = 0; i < context->c->waiterCount; i++) {
|
|
CoroutineState *c = context->c->waiters[i];
|
|
if (!c) continue;
|
|
Assert(!c->nextUnblockedCoroutine && !c->previousUnblockedCoroutineLink);
|
|
c->unblockedBy = context->c->id;
|
|
c->nextUnblockedCoroutine = context->unblockedCoroutines;
|
|
if (c->nextUnblockedCoroutine) c->nextUnblockedCoroutine->previousUnblockedCoroutineLink = &c->nextUnblockedCoroutine;
|
|
c->previousUnblockedCoroutineLink = &context->unblockedCoroutines;
|
|
context->unblockedCoroutines = c;
|
|
|
|
for (uintptr_t j = 0; j < c->waitingOnCount; j++) {
|
|
Assert(*(c->waitingOn[j]) == c);
|
|
*(c->waitingOn[j]) = NULL;
|
|
}
|
|
|
|
c->waitingOnCount = 0;
|
|
// PrintDebug("== unblocked %ld\n", c->id);
|
|
}
|
|
|
|
ScriptFreeCoroutine(context->c);
|
|
} else {
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - 1]) return -1;
|
|
// The coroutine is waiting.
|
|
context->c->unblockedBy = -1;
|
|
context->c->awaiting = true;
|
|
context->c->instructionPointer = instructionPointer;
|
|
context->c->variableBase = variableBase;
|
|
uint64_t index = context->c->stack[context->c->stackPointer - 1].i;
|
|
if (context->heapEntriesAllocated <= index) return -1;
|
|
HeapEntry *entry = &context->heap[index];
|
|
if (entry->internalValuesAreManaged || entry->type != T_LIST) return -1;
|
|
|
|
context->c->waitingOn = (CoroutineState ***) AllocateResize(context->c->waitingOn, sizeof(CoroutineState **) * entry->length);
|
|
Assert(context->c->waitingOnCount == 0);
|
|
CoroutineState *c = context->allCoroutines;
|
|
|
|
while (c) {
|
|
for (uintptr_t i = 0; i < entry->length; i++) {
|
|
if (c->id == (uint64_t) entry->list[i].i) {
|
|
if (c->waiterCount == c->waitersAllocated) {
|
|
c->waitersAllocated = c->waitersAllocated ? c->waitersAllocated * 2 : 4;
|
|
c->waiters = (CoroutineState **) AllocateResize(c->waiters, sizeof(CoroutineState *) * c->waitersAllocated);
|
|
}
|
|
|
|
c->waiters[c->waiterCount] = context->c;
|
|
context->c->waitingOn[context->c->waitingOnCount++] = &c->waiters[c->waiterCount];
|
|
c->waiterCount++;
|
|
}
|
|
}
|
|
|
|
c = c->nextCoroutine;
|
|
}
|
|
|
|
// PrintDebug("== waiting on %d...\n", context->c->waitingOnCount);
|
|
|
|
if (!context->c->waitingOnCount) {
|
|
// PrintDebug("== immediately unblocking\n");
|
|
context->c->unblockedBy = entry->length ? entry->list[0].i : -1;
|
|
unblockImmediately = true;
|
|
}
|
|
}
|
|
|
|
CoroutineState *next = unblockImmediately ? context->c : context->unblockedCoroutines;
|
|
|
|
if (!next) {
|
|
if (context->externalCoroutineCount) {
|
|
// PrintDebug("== wait for an external coroutine\n");
|
|
next = ExternalCoroutineWaitAny(context);
|
|
Assert(next->externalCoroutine);
|
|
unblockImmediately = true;
|
|
} else {
|
|
// TODO Earlier deadlock detection.
|
|
PrintError4(context, instructionPointer - 1, "No tasks can run if this task (ID %ld) starts waiting.\n", context->c->id);
|
|
PrintDebug("All tasks:\n");
|
|
CoroutineState *c = context->allCoroutines;
|
|
|
|
while (c) {
|
|
PrintDebug("\t%ld blocks ", c->id);
|
|
bool first = true;
|
|
|
|
for (uintptr_t i = 0; i < c->waiterCount; i++) {
|
|
if (!c->waiters[i]) continue;
|
|
PrintDebug("%s%ld", first ? "" : ", ", c->waiters[i]->id);
|
|
first = false;
|
|
}
|
|
|
|
PrintDebug("\n");
|
|
PrintBackTrace(context, c->instructionPointer - 1, c, "\t");
|
|
c = c->nextCoroutine;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!unblockImmediately) {
|
|
Assert(next->previousUnblockedCoroutineLink);
|
|
*next->previousUnblockedCoroutineLink = next->nextUnblockedCoroutine;
|
|
if (next->nextUnblockedCoroutine) next->nextUnblockedCoroutine->previousUnblockedCoroutineLink = next->previousUnblockedCoroutineLink;
|
|
}
|
|
|
|
next->nextUnblockedCoroutine = NULL;
|
|
next->previousUnblockedCoroutineLink = NULL;
|
|
context->c = next;
|
|
// PrintDebug("== switch to %ld\n", next->id);
|
|
|
|
if (context->c->awaiting) {
|
|
if (!context->c->externalCoroutine) {
|
|
context->c->stackIsManaged[context->c->stackPointer - 1] = false;
|
|
context->c->stack[context->c->stackPointer - 1].i = context->c->unblockedBy;
|
|
}
|
|
|
|
instructionPointer = context->c->instructionPointer;
|
|
variableBase = context->c->variableBase;
|
|
// PrintDebug("== unblocked by %ld\n", context->c->unblockedBy);
|
|
} else {
|
|
// PrintDebug("== just started\n");
|
|
instructionPointer = 1; // There is a T_AWAIT command at address 1.
|
|
goto callCommand;
|
|
}
|
|
} else if (command == T_REPL_RESULT) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
ExternalPassREPLResult(context, context->c->stack[--context->c->stackPointer]);
|
|
} else if (command == T_END_FUNCTION || command == T_EXTCALL || command == T_LIBCALL) {
|
|
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 == EXTCALL_START_COROUTINE) {
|
|
context->externalCoroutineCount++;
|
|
context->c->externalCoroutine = true;
|
|
instructionPointer -= 3;
|
|
// PrintDebug("start external coroutine %ld\n", context->c->id);
|
|
goto awaitCommand;
|
|
} else if (context->c->externalCoroutine) {
|
|
context->externalCoroutineCount--;
|
|
context->c->externalCoroutine = false;
|
|
// PrintDebug("end external coroutine %ld\n", context->c->id);
|
|
}
|
|
|
|
bool isErr = result == EXTCALL_RETURN_ERR_ERROR
|
|
|| result == EXTCALL_RETURN_ERR_MANAGED
|
|
|| result == EXTCALL_RETURN_ERR_UNMANAGED;
|
|
|
|
if (result == EXTCALL_RETURN_UNMANAGED || result == EXTCALL_RETURN_MANAGED || isErr) {
|
|
if (context->c->stackPointer == context->c->stackEntriesAllocated) {
|
|
PrintDebug("Evaluation stack overflow.\n");
|
|
return -1;
|
|
}
|
|
|
|
if (isErr) {
|
|
if (result != EXTCALL_RETURN_ERR_ERROR || returnValue.i) {
|
|
// Temporarily put the return value on the stack in case garbage collection occurs
|
|
// in the following HeapAllocate (i.e. before the return value has been wrapped).
|
|
context->c->stackIsManaged[context->c->stackPointer] = result != EXTCALL_RETURN_ERR_UNMANAGED;
|
|
context->c->stack[context->c->stackPointer++] = returnValue;
|
|
|
|
// TODO Handle memory allocation failures here.
|
|
uintptr_t index = HeapAllocate(context);
|
|
context->heap[index].type = T_ERR;
|
|
context->heap[index].success = result != EXTCALL_RETURN_ERR_ERROR;
|
|
context->heap[index].internalValuesAreManaged = result != EXTCALL_RETURN_ERR_UNMANAGED;
|
|
context->heap[index].errorValue = returnValue;
|
|
returnValue.i = index;
|
|
|
|
context->c->stackPointer--;
|
|
} else {
|
|
// Unknown error.
|
|
returnValue.i = 0;
|
|
}
|
|
|
|
result = EXTCALL_RETURN_MANAGED;
|
|
}
|
|
|
|
context->c->stackIsManaged[context->c->stackPointer] = result == EXTCALL_RETURN_MANAGED;
|
|
context->c->stack[context->c->stackPointer++] = returnValue;
|
|
}
|
|
} else {
|
|
return -1;
|
|
}
|
|
} else if (command == T_LIBCALL) {
|
|
context->c->parameterCount = 0;
|
|
context->c->returnValueType = EXTCALL_NO_RETURN;
|
|
|
|
void *address;
|
|
MemoryCopy(&address, &functionData[instructionPointer], sizeof(address));
|
|
instructionPointer += sizeof(address);
|
|
|
|
if (!((bool (*)(void *)) address)(context)) {
|
|
return 0;
|
|
}
|
|
|
|
context->c->stackPointer -= context->c->parameterCount;
|
|
|
|
if (context->c->returnValueType != EXTCALL_NO_RETURN) {
|
|
if (context->c->stackPointer == context->c->stackEntriesAllocated) {
|
|
PrintDebug("Evaluation stack overflow.\n");
|
|
return -1;
|
|
}
|
|
|
|
context->c->stackIsManaged[context->c->stackPointer] = context->c->returnValueType == EXTCALL_RETURN_MANAGED;
|
|
context->c->stack[context->c->stackPointer++] = context->c->returnValue;
|
|
}
|
|
}
|
|
|
|
context->c->localVariableCount = variableBase + 1;
|
|
|
|
if (context->c->backTracePointer) {
|
|
BackTraceItem *item = &context->c->backTrace[context->c->backTracePointer - 1];
|
|
|
|
if (command == T_EXTCALL || command == T_LIBCALL) {
|
|
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 && command != T_LIBCALL) {
|
|
context->c->backTracePointer--;
|
|
instructionPointer = item->instructionPointer;
|
|
variableBase = item->variableBase;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
} else if (command == T_END_CALLBACK) {
|
|
break;
|
|
} else {
|
|
PrintDebug("Unknown command %d.\n", command);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (context->allCoroutines->nextCoroutine || context->allCoroutines->startedByAsync) {
|
|
PrintError3("Script ended with unfinished tasks.\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ScriptParseOptions(ExecutionContext *context) {
|
|
for (uintptr_t i = 0; i < optionCount; i++) {
|
|
uintptr_t equalsPosition = 0;
|
|
uintptr_t optionLength = 0;
|
|
|
|
for (uintptr_t j = 0; options[i][j]; j++) {
|
|
if (options[i][j] == '=') {
|
|
equalsPosition = j;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (uintptr_t j = 0; options[i][j]; j++) {
|
|
optionLength++;
|
|
}
|
|
|
|
if (!equalsPosition) {
|
|
PrintError3("Invalid script option passed on command line '%s'.\n", options[i]);
|
|
return false;
|
|
}
|
|
|
|
uintptr_t index = 0;
|
|
Node *node = NULL;
|
|
|
|
for (uintptr_t j = 0; j < context->rootNode->scope->entryCount; j++) {
|
|
if (context->rootNode->scope->entries[j]->token.textBytes == equalsPosition
|
|
&& 0 == MemoryCompare(context->rootNode->scope->entries[j]->token.text, options[i], equalsPosition)
|
|
&& context->rootNode->scope->entries[j]->type == T_DECLARE) {
|
|
node = context->rootNode->scope->entries[j];
|
|
break;
|
|
}
|
|
|
|
if (ScopeIsVariableType(context->rootNode->scope->entries[j])) {
|
|
index++;
|
|
}
|
|
}
|
|
|
|
if (!node) {
|
|
continue;
|
|
}
|
|
|
|
index += context->functionData->globalVariableOffset;
|
|
|
|
if (node->expressionType->type == T_STR) {
|
|
uintptr_t heapIndex = HeapAllocate(context);
|
|
context->heap[heapIndex].type = T_STR;
|
|
context->heap[heapIndex].bytes = optionLength - equalsPosition - 1;
|
|
context->heap[heapIndex].text = (char *) AllocateResize(NULL, context->heap[heapIndex].bytes);
|
|
context->globalVariables[index].i = heapIndex;
|
|
MemoryCopy(context->heap[heapIndex].text, options[i] + equalsPosition + 1, context->heap[heapIndex].bytes);
|
|
} else if (node->expressionType->type == T_INT) {
|
|
// TODO Overflow checking.
|
|
|
|
Value v;
|
|
v.i = 0;
|
|
|
|
for (uintptr_t j = 0; options[i][j + equalsPosition + 1]; j++) {
|
|
char c = options[i][j + equalsPosition + 1];
|
|
|
|
if (c >= '0' && c <= '9') {
|
|
v.i *= 10;
|
|
v.i += c - '0';
|
|
} else {
|
|
PrintError3("Option '%s' should be an integer.\n", options[i]);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
context->globalVariables[index] = v;
|
|
} else if (node->expressionType->type == T_BOOL) {
|
|
char c = options[i][equalsPosition + 1];
|
|
bool truthy = c == 't' || c == 'y' || c == '1';
|
|
bool falsey = c == 'f' || c == 'n' || c == '0';
|
|
|
|
if (!truthy && !falsey) {
|
|
PrintError3("#option variable '%.*s' should be a boolean value 'true' or 'false'.\n", node->token.textBytes, node->token.text);
|
|
return false;
|
|
}
|
|
|
|
context->globalVariables[index].i = truthy ? 1 : 0;
|
|
} else {
|
|
PrintError3("#option variable '%.*s' is not of string, boolean or integer type.\n", node->token.textBytes, node->token.text);
|
|
return false;
|
|
}
|
|
|
|
if (optionsMatched[i]) {
|
|
PrintError3("Script option passed on command line '%s' matches multiple #option variables in different modules.\n", options[i]);
|
|
return false;
|
|
}
|
|
|
|
optionsMatched[i] = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ScriptParameterCString(void *engine, char **output) {
|
|
ExecutionContext *context = (ExecutionContext *) engine;
|
|
context->c->parameterCount++;
|
|
if (context->c->stackPointer < context->c->parameterCount) return false;
|
|
if (!context->c->stackIsManaged[context->c->stackPointer - context->c->parameterCount]) return false;
|
|
uint64_t index = context->c->stack[context->c->stackPointer - context->c->parameterCount].i;
|
|
if (context->heapEntriesAllocated <= index) return false;
|
|
const char *text;
|
|
size_t bytes;
|
|
HeapEntry *entry = &context->heap[index];
|
|
ScriptHeapEntryToString(context, entry, &text, &bytes);
|
|
*output = (char *) AllocateResize(NULL, bytes + 1);
|
|
MemoryCopy(*output, text, bytes);
|
|
(*output)[bytes] = 0;
|
|
return true;
|
|
}
|
|
|
|
void ScriptHeapRefClose(void *engine, intptr_t index) {
|
|
ExecutionContext *context = (ExecutionContext *) engine;
|
|
Assert(index >= 0 || index < (intptr_t) context->heapEntriesAllocated);
|
|
Assert(context->heap[index].externalReferenceCount);
|
|
context->heap[index].externalReferenceCount--;
|
|
}
|
|
|
|
bool ScriptParameterHeapRef(void *engine, intptr_t *output) {
|
|
ExecutionContext *context = (ExecutionContext *) engine;
|
|
context->c->parameterCount++;
|
|
if (context->c->stackPointer < context->c->parameterCount) return false;
|
|
intptr_t index = context->c->stack[context->c->stackPointer - context->c->parameterCount].i;
|
|
if (index < 0 || index >= (intptr_t) context->heapEntriesAllocated) return false;
|
|
if (context->heap[index].externalReferenceCount == 0xFFFFFFFF) return false;
|
|
context->heap[index].externalReferenceCount++;
|
|
*output = index;
|
|
return true;
|
|
}
|
|
|
|
bool ScriptParameterInt64(void *engine, int64_t *output) {
|
|
ExecutionContext *context = (ExecutionContext *) engine;
|
|
context->c->parameterCount++;
|
|
if (context->c->stackPointer < context->c->parameterCount) return false;
|
|
*output = context->c->stack[context->c->stackPointer - context->c->parameterCount].i;
|
|
return true;
|
|
}
|
|
|
|
bool ScriptParameterDouble(void *engine, double *output) {
|
|
ExecutionContext *context = (ExecutionContext *) engine;
|
|
context->c->parameterCount++;
|
|
if (context->c->stackPointer < context->c->parameterCount) return false;
|
|
*output = context->c->stack[context->c->stackPointer - context->c->parameterCount].f;
|
|
return true;
|
|
}
|
|
|
|
bool ScriptParameterBool (void *engine, bool *output) { int64_t i; if (!ScriptParameterInt64(engine, &i)) return false; *output = i; return true; }
|
|
bool ScriptParameterUint32(void *engine, uint32_t *output) { int64_t i; if (!ScriptParameterInt64(engine, &i)) return false; *output = i; return true; }
|
|
bool ScriptParameterUint64(void *engine, uint64_t *output) { int64_t i; if (!ScriptParameterInt64(engine, &i)) return false; *output = i; return true; }
|
|
bool ScriptParameterInt32 (void *engine, int32_t *output) { int64_t i; if (!ScriptParameterInt64(engine, &i)) return false; *output = i; return true; }
|
|
|
|
void ScriptReturnInt(void *engine, int64_t input) {
|
|
ExecutionContext *context = (ExecutionContext *) engine;
|
|
Assert(context->c->returnValueType == EXTCALL_NO_RETURN);
|
|
context->c->returnValueType = EXTCALL_RETURN_UNMANAGED;
|
|
context->c->returnValue.i = input;
|
|
}
|
|
|
|
bool ScriptParameterPointer(void *engine, void **output) {
|
|
int64_t i;
|
|
if (!ScriptParameterInt64(engine, &i)) return false;
|
|
*output = (void *) (intptr_t) i;
|
|
return true;
|
|
}
|
|
|
|
void ScriptReturnPointer(void *engine, void *input) {
|
|
ScriptReturnInt(engine, (int64_t) (intptr_t) input);
|
|
}
|
|
|
|
bool ScriptRunCallback(void *engine, intptr_t functionPointer, int64_t *parameters, bool *managedParameters, size_t parameterCount) {
|
|
// TODO Do this in a separate coroutine?
|
|
|
|
ExecutionContext *context = (ExecutionContext *) engine;
|
|
|
|
for (intptr_t i = parameterCount - 1; i >= -1; i--) {
|
|
if (context->c->stackPointer == context->c->stackEntriesAllocated) {
|
|
PrintError4(context, 0, "Stack overflow.\n");
|
|
return false;
|
|
}
|
|
|
|
if (i == -1) {
|
|
context->c->stack[context->c->stackPointer].i = functionPointer;
|
|
context->c->stackIsManaged[context->c->stackPointer] = true;
|
|
} else {
|
|
context->c->stack[context->c->stackPointer].i = parameters[i];
|
|
context->c->stackIsManaged[context->c->stackPointer] = managedParameters[i];
|
|
}
|
|
|
|
context->c->stackPointer++;
|
|
}
|
|
|
|
int result = ScriptExecuteFunction(2, context);
|
|
if (result == -1) PrintError3("The script was malformed.\n");
|
|
return result > 0;
|
|
}
|
|
|
|
bool ScriptLoad(Tokenizer tokenizer, ExecutionContext *context, ImportData *importData, bool replMode) {
|
|
Node *previousRootNode = context->rootNode;
|
|
ImportData *previousImportData = context->functionData->importData;
|
|
|
|
context->rootNode = replMode ? ParseRootREPL(&tokenizer) : ParseRoot(&tokenizer);
|
|
context->functionData->importData = tokenizer.module;
|
|
|
|
uint8_t b = 0; // Make sure no function can start at 0.
|
|
FunctionBuilderAppend(context->functionData, &b, sizeof(b));
|
|
b = T_AWAIT; // Put a T_AWAIT command at address 1.
|
|
FunctionBuilderAppend(context->functionData, &b, sizeof(b));
|
|
b = T_CALL; // Put a T_CALL command at address 2 (for ScriptRunCallback).
|
|
FunctionBuilderAppend(context->functionData, &b, sizeof(b));
|
|
b = T_END_CALLBACK; // Put a T_END_CALLBACK command at address 3 (for ScriptRunCallback).
|
|
FunctionBuilderAppend(context->functionData, &b, sizeof(b));
|
|
|
|
bool success = context->rootNode
|
|
&& ASTSetScopes(&tokenizer, context, context->rootNode, NULL)
|
|
&& ASTLookupTypeIdentifiers(&tokenizer, context->rootNode)
|
|
&& ASTSetTypes(&tokenizer, context->rootNode)
|
|
&& ASTCheckForReturnStatements(&tokenizer, context->rootNode)
|
|
&& ASTGenerate(&tokenizer, context->rootNode, context)
|
|
&& ScriptParseOptions(context);
|
|
|
|
importData->globalVariableOffset = context->functionData->globalVariableOffset;
|
|
importData->rootNode = context->rootNode;
|
|
*importedModulesLink = importData;
|
|
importedModulesLink = &importData->nextImport;
|
|
|
|
context->rootNode = previousRootNode;
|
|
context->functionData->importData = previousImportData;
|
|
|
|
return success;
|
|
}
|
|
|
|
int ScriptExecute(ExecutionContext *context, ImportData *mainModule) {
|
|
bool optionMatchingError = false;
|
|
|
|
for (uintptr_t i = 0; i < optionCount; i++) {
|
|
if (!optionsMatched[i]) {
|
|
PrintError3("Script option passed on command line '%s' does not match any #option variable.\n", options[i]);
|
|
optionMatchingError = true;
|
|
}
|
|
}
|
|
|
|
if (optionMatchingError) {
|
|
return 1;
|
|
}
|
|
|
|
Node n;
|
|
n.token.textBytes = startFunctionBytes;
|
|
n.token.text = startFunction;
|
|
intptr_t startIndex = ScopeLookupIndex(&n, mainModule->rootNode->scope, true, false);
|
|
|
|
if (startIndex == -1) {
|
|
PrintError3("The script does not have a start function '%.*s'.\n", startFunctionBytes, startFunction);
|
|
return 1;
|
|
}
|
|
|
|
Node mainFunctionArguments = { 0 };
|
|
mainFunctionArguments.type = T_ARGUMENTS;
|
|
Node mainFunctionReturn = { 0 };
|
|
mainFunctionReturn.type = T_VOID;
|
|
Node mainFunctionType = { 0 };
|
|
mainFunctionType.type = T_FUNCPTR;
|
|
mainFunctionType.firstChild = &mainFunctionArguments;
|
|
mainFunctionArguments.sibling = &mainFunctionReturn;
|
|
|
|
if (!ASTMatching(&mainFunctionType, mainModule->rootNode->scope->entries[ScopeLookupIndex(&n, mainModule->rootNode->scope, false, true)]->expressionType)) {
|
|
PrintError3("The start function '%.*s' should take no arguments and return 'void'.\n", startFunctionBytes, startFunction);
|
|
return 1;
|
|
}
|
|
|
|
ImportData *module = importedModules;
|
|
|
|
while (module) {
|
|
Node n;
|
|
n.token.textBytes = 10;
|
|
n.token.text = "Initialise";
|
|
intptr_t index = ScopeLookupIndex(&n, module->rootNode->scope, true, false);
|
|
|
|
if (index != -1) {
|
|
if (!ASTMatching(&mainFunctionType, module->rootNode->scope->entries[ScopeLookupIndex(&n, module->rootNode->scope, false, true)]->expressionType)) {
|
|
PrintError3("The 'Initialise' function in the module '%s' should take no arguments and return 'void'.\n", module->path);
|
|
return 1;
|
|
}
|
|
|
|
int result = ScriptExecuteFunction(context->heap[context->globalVariables[index + module->globalVariableOffset].i].lambdaID, context);
|
|
|
|
if (result == 0) {
|
|
// A runtime error occurred.
|
|
return 1;
|
|
} else if (result == -1 || context->c->stackPointer != 0) {
|
|
PrintError3("The script was malformed.\n");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
module = module->nextImport;
|
|
}
|
|
|
|
int result = ScriptExecuteFunction(context->heap[context->globalVariables[context->functionData->globalVariableOffset + startIndex].i].lambdaID, context);
|
|
|
|
if (result == 0) {
|
|
// A runtime error occurred.
|
|
return 1;
|
|
} else if (result == -1 || context->c->stackPointer != 0) {
|
|
PrintError3("The script was malformed.\n");
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ScriptFreeCoroutine(CoroutineState *c) {
|
|
AllocateResize(c->waiters, 0);
|
|
AllocateResize(c->waitingOn, 0);
|
|
AllocateResize(c->localVariables, 0);
|
|
AllocateResize(c->localVariableIsManaged, 0);
|
|
AllocateResize(c, 0);
|
|
}
|
|
|
|
void ScriptFree(ExecutionContext *context) {
|
|
ImportData *module = importedModules;
|
|
|
|
while (module) {
|
|
if (module->pathBytes != 15 || 0 != MemoryCompare(module->path, "__base_module__", module->pathBytes)) {
|
|
AllocateResize(module->fileData, 0);
|
|
}
|
|
|
|
ASTFreeScopes(module->rootNode);
|
|
module = module->nextImport;
|
|
}
|
|
|
|
for (uintptr_t i = 1; i < context->heapEntriesAllocated; i++) {
|
|
if (context->heap[i].type != T_ERROR) {
|
|
HeapFreeEntry(context, i);
|
|
}
|
|
}
|
|
|
|
CoroutineState *coroutine = context->allCoroutines;
|
|
|
|
while (coroutine) {
|
|
CoroutineState *next = coroutine->nextCoroutine;
|
|
ScriptFreeCoroutine(coroutine);
|
|
coroutine = next;
|
|
}
|
|
|
|
AllocateResize(context->heap, 0);
|
|
AllocateResize(context->globalVariables, 0);
|
|
AllocateResize(context->globalVariableIsManaged, 0);
|
|
AllocateResize(context->functionData->lineNumbers, 0);
|
|
AllocateResize(context->functionData->data, 0);
|
|
AllocateResize(context->scriptPersistFile, 0);
|
|
}
|
|
|
|
// --------------------------------- Helpers.
|
|
|
|
void LineNumberLookup(ExecutionContext *context, uint32_t instructionPointer, LineNumber *output) {
|
|
for (uintptr_t i = 0; i < context->functionData->lineNumberCount; i++) {
|
|
if (context->functionData->lineNumbers[i].instructionPointer == instructionPointer) {
|
|
*output = context->functionData->lineNumbers[i];
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PrintBackTrace(ExecutionContext *context, uint32_t instructionPointer, CoroutineState *c, const char *prefix) {
|
|
LineNumber lineNumber = { 0 };
|
|
LineNumberLookup(context, instructionPointer, &lineNumber);
|
|
|
|
if (lineNumber.importData) {
|
|
PrintDebug("%s\t%s:%d %s %.*s\n", prefix, lineNumber.importData->path, lineNumber.lineNumber, lineNumber.function ? "in" : "",
|
|
lineNumber.function ? (int) lineNumber.function->textBytes : 0, lineNumber.function ? lineNumber.function->text : "");
|
|
}
|
|
|
|
uintptr_t btp = c->backTracePointer;
|
|
uintptr_t minimum = c->startedByAsync ? 1 : 0;
|
|
|
|
while (btp > minimum) {
|
|
BackTraceItem *link = &c->backTrace[--btp];
|
|
LineNumberLookup(context, link->instructionPointer - 1, &lineNumber);
|
|
PrintDebug("%s\t%s:%d %s %.*s\n", prefix, lineNumber.importData->path ? lineNumber.importData->path : "??",
|
|
lineNumber.lineNumber, lineNumber.function ? "in" : "",
|
|
lineNumber.function ? (int) lineNumber.function->textBytes : 0, lineNumber.function ? lineNumber.function->text : "");
|
|
}
|
|
}
|
|
|
|
void PrintLine(ImportData *importData, uintptr_t line) {
|
|
if (!importData) {
|
|
return;
|
|
}
|
|
|
|
uintptr_t position = 0;
|
|
|
|
for (uintptr_t i = 1; i < line; i++) {
|
|
while (position < importData->fileDataBytes) {
|
|
if (((char *) importData->fileData)[position] == '\n') {
|
|
position++;
|
|
break;
|
|
}
|
|
|
|
position++;
|
|
}
|
|
}
|
|
|
|
uintptr_t length = 0;
|
|
|
|
for (uintptr_t i = position; i <= importData->fileDataBytes; i++) {
|
|
if (i == importData->fileDataBytes || ((char *) importData->fileData)[i] == '\n') {
|
|
length = i - position;
|
|
break;
|
|
}
|
|
}
|
|
|
|
PrintDebug(">> %.*s\n", (int) length, &((char *) importData->fileData)[position]);
|
|
}
|
|
|
|
int ScriptExecuteFromFile(char *scriptPath, size_t scriptPathBytes, char *fileData, size_t fileDataBytes, bool replMode) {
|
|
Tokenizer tokenizer = { 0 };
|
|
ImportData importData = { 0 };
|
|
importData.path = scriptPath;
|
|
importData.pathBytes = scriptPathBytes;
|
|
importData.fileData = fileData;
|
|
importData.fileDataBytes = fileDataBytes;
|
|
tokenizer.module = &importData;
|
|
tokenizer.line = 1;
|
|
tokenizer.input = fileData;
|
|
tokenizer.inputBytes = fileDataBytes;
|
|
|
|
FunctionBuilder builder = { 0 };
|
|
ExecutionContext context = { 0 };
|
|
context.functionData = &builder;
|
|
context.mainModule = &importData;
|
|
|
|
context.heapEntriesAllocated = 2;
|
|
context.heap = (HeapEntry *) AllocateResize(NULL, sizeof(HeapEntry) * context.heapEntriesAllocated);
|
|
context.heap[0].type = T_EOF;
|
|
context.heap[1].type = T_ERROR;
|
|
context.heap[1].nextUnusedEntry = 0;
|
|
context.heapFirstUnusedEntry = 1;
|
|
context.c = (CoroutineState *) AllocateResize(0, sizeof(CoroutineState));
|
|
CoroutineState empty = { 0 };
|
|
*context.c = empty;
|
|
context.c->stackEntriesAllocated = sizeof(context.c->stack) / sizeof(context.c->stack[0]);
|
|
context.c->previousCoroutineLink = &context.allCoroutines;
|
|
context.allCoroutines = context.c;
|
|
|
|
int result = ScriptLoad(tokenizer, &context, &importData, replMode) ? ScriptExecute(&context, &importData) : 1;
|
|
ScriptFree(&context);
|
|
|
|
importedModules = NULL;
|
|
importedModulesLink = &importedModules;
|
|
|
|
return result;
|
|
}
|
|
|
|
int ExternalStringSlice(ExecutionContext *context, Value *returnValue) {
|
|
(void) returnValue;
|
|
if (context->c->stackPointer < 3) return -1;
|
|
STACK_POP_STRING(string, bytes);
|
|
uint64_t start = context->c->stack[--context->c->stackPointer].i;
|
|
if (context->c->stackIsManaged[context->c->stackPointer]) return -1;
|
|
uint64_t end = context->c->stack[--context->c->stackPointer].i;
|
|
if (context->c->stackIsManaged[context->c->stackPointer]) return -1;
|
|
|
|
if (start > bytes || end > bytes || end < start) {
|
|
PrintError4(context, 0, "The slice range (%ld..%ld) is invalid for the string of length %ld.\n",
|
|
start, end, bytes);
|
|
return 0;
|
|
}
|
|
|
|
RETURN_STRING_COPY(string + start, end - start);
|
|
return EXTCALL_RETURN_MANAGED;
|
|
}
|
|
|
|
int ExternalCharacterToByte(ExecutionContext *context, Value *returnValue) {
|
|
(void) returnValue;
|
|
STACK_POP_STRING(entryText, entryBytes);
|
|
returnValue->i = entryBytes ? entryText[0] : -1;
|
|
return EXTCALL_RETURN_UNMANAGED;
|
|
}
|
|
|
|
// --------------------------------- Platform layer.
|
|
|
|
#if defined(_WIN32) || defined(__linux__) || defined(__APPLE__)
|
|
|
|
#ifdef _WIN32
|
|
#include <direct.h>
|
|
#include <windows.h>
|
|
#define getcwd _getcwd
|
|
#define popen _popen
|
|
#define pclose _pclose
|
|
#define setenv(x, y, z) !SetEnvironmentVariable(x, y)
|
|
#else
|
|
#include <dlfcn.h>
|
|
#include <dirent.h>
|
|
#include <unistd.h>
|
|
#include <sys/utsname.h>
|
|
#include <sys/wait.h>
|
|
#include <pthread.h>
|
|
#include <semaphore.h>
|
|
#endif
|
|
#include <errno.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
|
|
void **fixedAllocationBlocks;
|
|
uint8_t *fixedAllocationCurrentBlock;
|
|
uintptr_t fixedAllocationCurrentPosition;
|
|
size_t fixedAllocationCurrentSize;
|
|
|
|
sem_t externalCoroutineSemaphore;
|
|
pthread_mutex_t externalCoroutineMutex;
|
|
CoroutineState *externalCoroutineUnblockedList;
|
|
|
|
bool systemShellLoggingEnabled = true;
|
|
bool coloredOutput;
|
|
|
|
char *scriptSourceDirectory;
|
|
|
|
DIR *directoryIterator;
|
|
|
|
char *StringZeroTerminate(const char *text, size_t bytes) {
|
|
char *buffer = malloc(bytes + 1);
|
|
if (!buffer) return NULL;
|
|
memcpy(buffer, text, bytes);
|
|
buffer[bytes] = 0;
|
|
return buffer;
|
|
}
|
|
|
|
#define RETURN_ERROR(error) do { MakeError(context, returnValue, error); return EXTCALL_RETURN_ERR_ERROR; } while (0)
|
|
|
|
void MakeError(ExecutionContext *context, Value *returnValue, int error) {
|
|
const char *text = NULL;
|
|
|
|
if (error == EAGAIN || error == EBUSY) text = "OPERATION_BLOCKED";
|
|
if (error == ENOTEMPTY) text = "DIRECTORY_NOT_EMPTY";
|
|
if (error == EFBIG) text = "FILE_TOO_LARGE";
|
|
if (error == ENOSPC || error == EDQUOT) text = "DRIVE_FULL";
|
|
if (error == EROFS) text = "FILE_ON_READ_ONLY_VOLUME";
|
|
if (error == ENOTDIR || error == EISDIR) text = "INCORRECT_NODE_TYPE";
|
|
if (error == ENOENT) text = "FILE_DOES_NOT_EXIST";
|
|
if (error == EEXIST) text = "ALREADY_EXISTS";
|
|
if (error == ENOMEM) text = "INSUFFICIENT_RESOURCES";
|
|
if (error == EPERM || error == EACCES) text = "PERMISSION_NOT_GRANTED";
|
|
if (error == EXDEV) text = "VOLUME_MISMATCH";
|
|
if (error == EIO) text = "HARDWARE_FAILURE";
|
|
|
|
if (text) {
|
|
RETURN_STRING_COPY(text, strlen(text));
|
|
} else {
|
|
returnValue->i = 0;
|
|
}
|
|
}
|
|
|
|
void ExternalCoroutineDone(CoroutineState *coroutine) {
|
|
#ifdef __linux__
|
|
pthread_mutex_lock(&externalCoroutineMutex);
|
|
coroutine->nextExternalCoroutine = externalCoroutineUnblockedList;
|
|
externalCoroutineUnblockedList = coroutine;
|
|
pthread_mutex_unlock(&externalCoroutineMutex);
|
|
sem_post(&externalCoroutineSemaphore);
|
|
#else
|
|
(void) coroutine;
|
|
#endif
|
|
}
|
|
|
|
void *SystemShellExecuteThread(void *_coroutine) {
|
|
CoroutineState *coroutine = (CoroutineState *) _coroutine;
|
|
coroutine->externalCoroutineData.i = system((char *) coroutine->externalCoroutineData2) == 0;
|
|
free((char *) coroutine->externalCoroutineData2);
|
|
ExternalCoroutineDone(coroutine);
|
|
return NULL;
|
|
}
|
|
|
|
int ExternalSystemShellExecute(ExecutionContext *context, Value *returnValue) {
|
|
if (context->c->externalCoroutine) {
|
|
*returnValue = context->c->externalCoroutineData;
|
|
return EXTCALL_RETURN_UNMANAGED;
|
|
}
|
|
|
|
STACK_POP_STRING(text, bytes);
|
|
char *temporary = StringZeroTerminate(text, bytes);
|
|
|
|
if (temporary) {
|
|
if (systemShellLoggingEnabled) PrintDebug("\033[0;32m%s\033[0m\n", temporary);
|
|
context->c->externalCoroutineData2 = temporary;
|
|
#ifdef __linux__
|
|
pthread_t thread;
|
|
pthread_create(&thread, NULL, SystemShellExecuteThread, context->c);
|
|
pthread_detach(thread);
|
|
return EXTCALL_START_COROUTINE;
|
|
#else
|
|
SystemShellExecuteThread(context->c);
|
|
*returnValue = context->c->externalCoroutineData;
|
|
return EXTCALL_RETURN_UNMANAGED;
|
|
#endif
|
|
} else {
|
|
fprintf(stderr, "Error in ExternalSystemShellExecute: Out of memory.\n");
|
|
returnValue->i = 0;
|
|
return EXTCALL_RETURN_UNMANAGED;
|
|
}
|
|
}
|
|
|
|
void *SystemShellExecuteWithWorkingDirectoryThread(void *_coroutine) {
|
|
CoroutineState *coroutine = (CoroutineState *) _coroutine;
|
|
int status;
|
|
pid_t p = waitpid(coroutine->externalCoroutineData.i, &status, 0);
|
|
|
|
if (p != coroutine->externalCoroutineData.i) {
|
|
fprintf(stderr, "waitpid returned %d\n", p);
|
|
perror("waitpid failed");
|
|
coroutine->externalCoroutineData.i = 0;
|
|
} else {
|
|
coroutine->externalCoroutineData.i = WEXITSTATUS(status) == 0;
|
|
}
|
|
|
|
ExternalCoroutineDone(coroutine);
|
|
return NULL;
|
|
}
|
|
|
|
int ExternalSystemShellExecuteWithWorkingDirectory(ExecutionContext *context, Value *returnValue) {
|
|
if (context->c->externalCoroutine) {
|
|
*returnValue = context->c->externalCoroutineData;
|
|
return EXTCALL_RETURN_UNMANAGED;
|
|
}
|
|
|
|
STACK_POP_STRING_2(entryText, entryBytes, entry2Text, entry2Bytes);
|
|
returnValue->i = 0;
|
|
char *temporary = StringZeroTerminate(entryText, entryBytes);
|
|
if (!temporary) return EXTCALL_RETURN_MANAGED;
|
|
char *temporary2 = StringZeroTerminate(entry2Text, entry2Bytes);
|
|
if (!temporary2) return EXTCALL_RETURN_MANAGED;
|
|
|
|
if (systemShellLoggingEnabled) PrintDebug("\033[0;32m(%s) %s\033[0m\n", temporary, temporary2);
|
|
|
|
#ifdef __linux__
|
|
pid_t pid = fork();
|
|
|
|
if (pid == 0) {
|
|
Assert(chdir(temporary) == 0);
|
|
exit(system(temporary2));
|
|
} else if (pid < 0) {
|
|
PrintDebug("Unable to fork(), got pid = %d, errno = %d.\n", pid, errno);
|
|
returnValue->i = 0;
|
|
}
|
|
|
|
free(temporary);
|
|
free(temporary2);
|
|
|
|
if (pid > 0) {
|
|
context->c->externalCoroutineData.i = pid;
|
|
pthread_t thread;
|
|
pthread_create(&thread, NULL, SystemShellExecuteWithWorkingDirectoryThread, context->c);
|
|
pthread_detach(thread);
|
|
return EXTCALL_START_COROUTINE;
|
|
}
|
|
#else
|
|
char *data = (char *) malloc(10000);
|
|
|
|
if (!data || data != getcwd(data, 10000)) {
|
|
PrintError4(context, 0, "Could not get the working directory.\n");
|
|
free(data);
|
|
return 0;
|
|
}
|
|
|
|
Assert(chdir(temporary) == 0);
|
|
returnValue->i = system(temporary2) == 0;
|
|
chdir(data);
|
|
free(temporary);
|
|
free(temporary2);
|
|
free(data);
|
|
#endif
|
|
|
|
return EXTCALL_RETURN_UNMANAGED;
|
|
}
|
|
|
|
int ExternalSystemShellEvaluate(ExecutionContext *context, Value *returnValue) {
|
|
STACK_POP_STRING(text, bytes);
|
|
char *temporary = StringZeroTerminate(text, bytes);
|
|
|
|
if (temporary) {
|
|
FILE *f = popen(temporary, "r");
|
|
|
|
if (f) {
|
|
char *buffer = NULL;
|
|
size_t position = 0;
|
|
size_t bufferAllocated = 0;
|
|
|
|
while (true) {
|
|
if (position == bufferAllocated) {
|
|
bufferAllocated = bufferAllocated ? bufferAllocated * 2 : 64;
|
|
char *reallocated = (char *) realloc(buffer, bufferAllocated);
|
|
if (!reallocated) break;
|
|
buffer = reallocated;
|
|
}
|
|
|
|
intptr_t bytesRead = fread(buffer + position, 1, bufferAllocated - position, f);
|
|
|
|
if (bytesRead <= 0) {
|
|
break;
|
|
}
|
|
|
|
position += bytesRead;
|
|
}
|
|
|
|
buffer = (char *) realloc(buffer, position); // Shrink to match the size exactly.
|
|
pclose(f);
|
|
|
|
RETURN_STRING_NO_COPY(buffer, position);
|
|
} else {
|
|
returnValue->i = 0;
|
|
}
|
|
|
|
free(temporary);
|
|
} else {
|
|
fprintf(stderr, "Error in ExternalSystemShellEvaluate: Out of memory.\n");
|
|
returnValue->i = 0;
|
|
}
|
|
|
|
return EXTCALL_RETURN_MANAGED;
|
|
}
|
|
|
|
int ExternalSystemShellEnableLogging(ExecutionContext *context, Value *returnValue) {
|
|
(void) returnValue;
|
|
if (context->c->stackPointer < 1) return -1;
|
|
systemShellLoggingEnabled = context->c->stack[--context->c->stackPointer].i;
|
|
if (context->c->stackIsManaged[context->c->stackPointer]) return -1;
|
|
return EXTCALL_NO_RETURN;
|
|
}
|
|
|
|
int ExternalLog(ExecutionContext *context, Value *returnValue) {
|
|
(void) returnValue;
|
|
STACK_POP_STRING(entryText, entryBytes);
|
|
fprintf(stderr, "%.*s", (int) entryBytes, (char *) entryText);
|
|
fprintf(stderr, coloredOutput ? "\033[0;m\n" : "\n");
|
|
return EXTCALL_NO_RETURN;
|
|
}
|
|
|
|
int ExternalConsoleWriteStdout(ExecutionContext *context, Value *returnValue) {
|
|
(void) returnValue;
|
|
STACK_POP_STRING(entryText, entryBytes);
|
|
printf("%.*s", (int) entryBytes, (char *) entryText);
|
|
return EXTCALL_NO_RETURN;
|
|
}
|
|
|
|
int ExternalConsoleWriteStderr(ExecutionContext *context, Value *returnValue) {
|
|
(void) returnValue;
|
|
STACK_POP_STRING(entryText, entryBytes);
|
|
fprintf(stderr, "%.*s", (int) entryBytes, (char *) entryText);
|
|
return EXTCALL_NO_RETURN;
|
|
}
|
|
|
|
int ExternalLogOpenGroup(ExecutionContext *context, Value *returnValue) {
|
|
(void) returnValue;
|
|
if (context->c->stackPointer < 1) return -1;
|
|
context->c->stackPointer--;
|
|
return EXTCALL_NO_RETURN;
|
|
}
|
|
|
|
int ExternalLogClose(ExecutionContext *context, Value *returnValue) {
|
|
(void) context;
|
|
(void) returnValue;
|
|
return EXTCALL_NO_RETURN;
|
|
}
|
|
|
|
int ExternalTextFormat(ExecutionContext *context, Value *returnValue, const char *mode) {
|
|
if (coloredOutput) {
|
|
char *buffer = malloc(32);
|
|
uintptr_t index = HeapAllocate(context);
|
|
context->heap[index].type = T_STR;
|
|
context->heap[index].bytes = sprintf(buffer, "\033[0;%sm", mode);
|
|
context->heap[index].text = buffer;
|
|
returnValue->i = index;
|
|
} else {
|
|
returnValue->i = 0;
|
|
}
|
|
|
|
return EXTCALL_RETURN_MANAGED;
|
|
}
|
|
|
|
int ExternalTextColorError (ExecutionContext *context, Value *returnValue) { return ExternalTextFormat(context, returnValue, "31"); }
|
|
int ExternalTextColorHighlight(ExecutionContext *context, Value *returnValue) { return ExternalTextFormat(context, returnValue, "36"); }
|
|
int ExternalTextMonospaced (ExecutionContext *context, Value *returnValue) { return ExternalTextFormat(context, returnValue, ""); }
|
|
int ExternalTextPlain (ExecutionContext *context, Value *returnValue) { return ExternalTextFormat(context, returnValue, ""); }
|
|
|
|
int ExternalTextWeight(ExecutionContext *context, Value *returnValue) {
|
|
if (context->c->stackPointer < 1) return -1;
|
|
return ExternalTextFormat(context, returnValue, context->c->stack[--context->c->stackPointer].i > 500 ? "1" : "");
|
|
}
|
|
|
|
int ExternalPathCreateDirectory(ExecutionContext *context, Value *returnValue) {
|
|
STACK_POP_STRING(entryText, entryBytes);
|
|
char *temporary = StringZeroTerminate(entryText, entryBytes);
|
|
if (!temporary) RETURN_ERROR(ENOMEM);
|
|
#ifdef _WIN32
|
|
#pragma message ("ExternalPathCreateDirectory unimplemented")
|
|
#else
|
|
returnValue->i = 1;
|
|
if (mkdir(temporary, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) returnValue->i = errno == EEXIST;
|
|
#endif
|
|
free(temporary);
|
|
if (!returnValue->i) RETURN_ERROR(errno);
|
|
return EXTCALL_RETURN_ERR_UNMANAGED;
|
|
}
|
|
|
|
int ExternalPathDelete(ExecutionContext *context, Value *returnValue) {
|
|
(void) returnValue;
|
|
STACK_POP_STRING(entryText, entryBytes);
|
|
returnValue->i = 0;
|
|
char *temporary = StringZeroTerminate(entryText, entryBytes);
|
|
if (!temporary) RETURN_ERROR(ENOMEM);
|
|
struct stat s = { 0 };
|
|
bool isDirectory = lstat(temporary, &s) == 0 && S_ISDIR(s.st_mode);
|
|
returnValue->i = isDirectory ? (rmdir(temporary) == 0) : (unlink(temporary) == 0);
|
|
free(temporary);
|
|
if (!returnValue->i) RETURN_ERROR(errno);
|
|
return EXTCALL_RETURN_ERR_UNMANAGED;
|
|
}
|
|
|
|
int ExternalPathExists(ExecutionContext *context, Value *returnValue) {
|
|
STACK_POP_STRING(entryText, entryBytes);
|
|
returnValue->i = 0;
|
|
if (entryBytes == 0) return EXTCALL_RETURN_UNMANAGED;
|
|
char *temporary = StringZeroTerminate(entryText, entryBytes);
|
|
if (!temporary) return EXTCALL_RETURN_UNMANAGED;
|
|
struct stat s = { 0 };
|
|
returnValue->i = stat(temporary, &s) == 0;
|
|
free(temporary);
|
|
return EXTCALL_RETURN_UNMANAGED;
|
|
}
|
|
|
|
int ExternalPathIsFile(ExecutionContext *context, Value *returnValue) {
|
|
STACK_POP_STRING(entryText, entryBytes);
|
|
returnValue->i = 0;
|
|
if (entryBytes == 0) return EXTCALL_RETURN_UNMANAGED;
|
|
char *temporary = StringZeroTerminate(entryText, entryBytes);
|
|
if (!temporary) return EXTCALL_RETURN_UNMANAGED;
|
|
struct stat s = { 0 };
|
|
returnValue->i = lstat(temporary, &s) == 0 && S_ISREG(s.st_mode);
|
|
free(temporary);
|
|
return EXTCALL_RETURN_UNMANAGED;
|
|
}
|
|
|
|
int ExternalPathIsDirectory(ExecutionContext *context, Value *returnValue) {
|
|
STACK_POP_STRING(entryText, entryBytes);
|
|
returnValue->i = 0;
|
|
if (entryBytes == 0) return EXTCALL_RETURN_UNMANAGED;
|
|
char *temporary = StringZeroTerminate(entryText, entryBytes);
|
|
if (!temporary) return EXTCALL_RETURN_UNMANAGED;
|
|
struct stat s = { 0 };
|
|
returnValue->i = lstat(temporary, &s) == 0 && S_ISDIR(s.st_mode);
|
|
free(temporary);
|
|
return EXTCALL_RETURN_UNMANAGED;
|
|
}
|
|
|
|
int ExternalPathIsLink(ExecutionContext *context, Value *returnValue) {
|
|
STACK_POP_STRING(entryText, entryBytes);
|
|
returnValue->i = 0;
|
|
if (entryBytes == 0) return EXTCALL_RETURN_UNMANAGED;
|
|
char *temporary = StringZeroTerminate(entryText, entryBytes);
|
|
if (!temporary) return EXTCALL_RETURN_UNMANAGED;
|
|
struct stat s = { 0 };
|
|
returnValue->i = lstat(temporary, &s) == 0 && S_ISLNK(s.st_mode);
|
|
free(temporary);
|
|
return EXTCALL_RETURN_UNMANAGED;
|
|
}
|
|
|
|
int ExternalPathMove(ExecutionContext *context, Value *returnValue) {
|
|
STACK_POP_STRING_2(entryText, entryBytes, entry2Text, entry2Bytes);
|
|
returnValue->i = 0;
|
|
char *temporary = StringZeroTerminate(entryText, entryBytes);
|
|
char *temporary2 = StringZeroTerminate(entry2Text, entry2Bytes);
|
|
bool success = temporary && temporary2 && rename(temporary, temporary2) == 0;
|
|
free(temporary);
|
|
free(temporary2);
|
|
if (!temporary || !temporary2) RETURN_ERROR(ENOMEM);
|
|
if (!success) RETURN_ERROR(errno);
|
|
return EXTCALL_RETURN_ERR_UNMANAGED;
|
|
}
|
|
|
|
int ExternalFileCopy(ExecutionContext *context, Value *returnValue) {
|
|
STACK_POP_STRING_2(entryText, entryBytes, entry2Text, entry2Bytes);
|
|
returnValue->i = 0;
|
|
char *temporary = StringZeroTerminate(entryText, entryBytes);
|
|
char *temporary2 = StringZeroTerminate(entry2Text, entry2Bytes);
|
|
|
|
if (!temporary || !temporary2) {
|
|
free(temporary);
|
|
free(temporary2);
|
|
RETURN_ERROR(ENOMEM);
|
|
}
|
|
|
|
if (0 == strcmp(temporary, temporary2)) {
|
|
free(temporary);
|
|
free(temporary2);
|
|
PrintError4(context, 0, "FileCopy called with the source and destination paths identical. Attempting to copy a file to itself is undefined behaviour!\n");
|
|
return 0;
|
|
}
|
|
|
|
FILE *f = fopen(temporary, "rb");
|
|
off_t inputFileSize = 0;
|
|
|
|
if (f) {
|
|
fseek(f, 0, SEEK_END);
|
|
inputFileSize = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
}
|
|
|
|
FILE *f2 = fopen(temporary2, "wb");
|
|
free(temporary);
|
|
free(temporary2);
|
|
bool okay = true;
|
|
bool modifiedDuringCopy = false;
|
|
|
|
if (f && f2) {
|
|
char buffer[4096];
|
|
off_t totalBytesRead = 0;
|
|
|
|
while (okay) {
|
|
intptr_t bytesRead = fread(buffer, 1, sizeof(buffer), f);
|
|
totalBytesRead += bytesRead;
|
|
if (totalBytesRead > inputFileSize) { modifiedDuringCopy = true; break; }
|
|
if (bytesRead < 0) okay = false;
|
|
if (bytesRead <= 0) break;
|
|
intptr_t bytesWritten = fwrite(buffer, 1, bytesRead, f2);
|
|
if (bytesWritten != bytesRead) okay = false;
|
|
}
|
|
|
|
if (totalBytesRead != inputFileSize) {
|
|
modifiedDuringCopy = true;
|
|
}
|
|
} else {
|
|
okay = false;
|
|
}
|
|
|
|
if (f && fclose(f)) okay = false;
|
|
if (f2 && fclose(f2)) okay = false;
|
|
|
|
if (modifiedDuringCopy) {
|
|
RETURN_ERROR(-1);
|
|
} else if (okay) {
|
|
returnValue->i = 0;
|
|
return EXTCALL_RETURN_ERR_UNMANAGED;
|
|
} else {
|
|
RETURN_ERROR(errno);
|
|
}
|
|
}
|
|
|
|
int ExternalSystemGetEnvironmentVariable(ExecutionContext *context, Value *returnValue) {
|
|
STACK_POP_STRING(entryText, entryBytes);
|
|
returnValue->i = 0;
|
|
char *temporary = StringZeroTerminate(entryText, entryBytes);
|
|
if (!temporary) RETURN_ERROR(ENOMEM);
|
|
char *data = getenv(temporary);
|
|
free(temporary);
|
|
if (!data) RETURN_ERROR(-1);
|
|
RETURN_STRING_COPY(data, data ? strlen(data) : 0);
|
|
return EXTCALL_RETURN_ERR_MANAGED;
|
|
}
|
|
|
|
int ExternalSystemSetEnvironmentVariable(ExecutionContext *context, Value *returnValue) {
|
|
STACK_POP_STRING_2(entryText, entryBytes, entry2Text, entry2Bytes);
|
|
char *temporary = StringZeroTerminate(entryText, entryBytes);
|
|
char *temporary2 = StringZeroTerminate(entry2Text, entry2Bytes);
|
|
|
|
bool success = temporary && temporary2 && setenv(temporary, temporary2, true) == 0;
|
|
free(temporary);
|
|
free(temporary2);
|
|
|
|
if (!success) {
|
|
if (!temporary || !temporary2) RETURN_ERROR(ENOMEM);
|
|
RETURN_ERROR(errno);
|
|
}
|
|
|
|
return EXTCALL_RETURN_ERR_UNMANAGED;
|
|
}
|
|
|
|
int External_DirectoryInternalStartIteration(ExecutionContext *context, Value *returnValue) {
|
|
STACK_POP_STRING(entryText, entryBytes);
|
|
returnValue->i = 0;
|
|
char *temporary = StringZeroTerminate(entryText, entryBytes);
|
|
if (!temporary) RETURN_ERROR(ENOMEM);
|
|
if (directoryIterator) RETURN_ERROR(-1);
|
|
directoryIterator = opendir(temporary);
|
|
free(temporary);
|
|
if (!directoryIterator) RETURN_ERROR(errno);
|
|
return EXTCALL_RETURN_ERR_UNMANAGED;
|
|
}
|
|
|
|
int External_DirectoryInternalEndIteration(ExecutionContext *context, Value *returnValue) {
|
|
(void) context;
|
|
(void) returnValue;
|
|
if (!directoryIterator) return 0;
|
|
closedir(directoryIterator);
|
|
directoryIterator = NULL;
|
|
return EXTCALL_NO_RETURN;
|
|
}
|
|
|
|
int External_DirectoryInternalNextIteration(ExecutionContext *context, Value *returnValue) {
|
|
(void) context;
|
|
if (!directoryIterator) return 0;
|
|
struct dirent *entry = readdir(directoryIterator);
|
|
while (entry && (0 == strcmp(entry->d_name, ".") || 0 == strcmp(entry->d_name, ".."))) entry = readdir(directoryIterator);
|
|
if (!entry) { returnValue->i = 0; return EXTCALL_RETURN_MANAGED; }
|
|
RETURN_STRING_COPY(entry->d_name, strlen(entry->d_name));
|
|
return EXTCALL_RETURN_MANAGED;
|
|
}
|
|
|
|
int ExternalFileReadAll(ExecutionContext *context, Value *returnValue) {
|
|
STACK_POP_STRING(entryText, entryBytes);
|
|
returnValue->i = 0;
|
|
char *temporary = StringZeroTerminate(entryText, entryBytes);
|
|
if (!temporary) RETURN_ERROR(ENOMEM);
|
|
size_t length = 0;
|
|
void *data = FileLoad(temporary, &length);
|
|
free(temporary);
|
|
if (!data) RETURN_ERROR(errno);
|
|
RETURN_STRING_NO_COPY(data, length);
|
|
return EXTCALL_RETURN_ERR_MANAGED;
|
|
}
|
|
|
|
int ExternalFileGetSize(ExecutionContext *context, Value *returnValue) {
|
|
STACK_POP_STRING(entryText, entryBytes);
|
|
returnValue->i = 0;
|
|
char *temporary = StringZeroTerminate(entryText, entryBytes);
|
|
if (!temporary) RETURN_ERROR(ENOMEM);
|
|
FILE *file = fopen(temporary, "rb");
|
|
free(temporary);
|
|
|
|
if (file) {
|
|
fseek(file, 0, SEEK_END);
|
|
returnValue->i = ftell(file);
|
|
fclose(file);
|
|
return EXTCALL_RETURN_ERR_UNMANAGED;
|
|
} else {
|
|
RETURN_ERROR(errno);
|
|
}
|
|
}
|
|
|
|
int ExternalFileWriteAll(ExecutionContext *context, Value *returnValue) {
|
|
STACK_POP_STRING_2(entryText, entryBytes, entry2Text, entry2Bytes);
|
|
returnValue->i = 0;
|
|
char *temporary = StringZeroTerminate(entryText, entryBytes);
|
|
if (!temporary) RETURN_ERROR(ENOMEM);
|
|
FILE *f = fopen(temporary, "wb");
|
|
free(temporary);
|
|
|
|
if (f) {
|
|
returnValue->i = entry2Bytes == fwrite(entry2Text, 1, entry2Bytes, f);
|
|
if (fclose(f)) RETURN_ERROR(errno);
|
|
if (!returnValue->i) RETURN_ERROR(errno);
|
|
return EXTCALL_RETURN_ERR_UNMANAGED;
|
|
} else {
|
|
RETURN_ERROR(errno);
|
|
}
|
|
}
|
|
|
|
int ExternalFileAppend(ExecutionContext *context, Value *returnValue) {
|
|
STACK_POP_STRING_2(entryText, entryBytes, entry2Text, entry2Bytes);
|
|
returnValue->i = 0;
|
|
char *temporary = StringZeroTerminate(entryText, entryBytes);
|
|
if (!temporary) RETURN_ERROR(ENOMEM);
|
|
FILE *f = fopen(temporary, "ab");
|
|
free(temporary);
|
|
|
|
if (f) {
|
|
returnValue->i = entry2Bytes == fwrite(entry2Text, 1, entry2Bytes, f);
|
|
if (fclose(f)) RETURN_ERROR(errno);
|
|
if (!returnValue->i) RETURN_ERROR(errno);
|
|
return EXTCALL_RETURN_ERR_UNMANAGED;
|
|
} else {
|
|
RETURN_ERROR(errno);
|
|
}
|
|
}
|
|
|
|
int ExternalPathGetDefaultPrefix(ExecutionContext *context, Value *returnValue) {
|
|
(void) returnValue;
|
|
char *data = (char *) malloc(10000);
|
|
|
|
if (!data || data != getcwd(data, 10000)) {
|
|
PrintError4(context, 0, "Could not get the working directory.\n");
|
|
free(data);
|
|
return 0;
|
|
}
|
|
|
|
RETURN_STRING_NO_COPY(realloc(data, strlen(data) + 1), strlen(data));
|
|
return EXTCALL_RETURN_MANAGED;
|
|
}
|
|
|
|
int ExternalPathSetDefaultPrefixToScriptSourceDirectory(ExecutionContext *context, Value *returnValue) {
|
|
(void) context;
|
|
returnValue->i = chdir(scriptSourceDirectory) == 0;
|
|
if (!returnValue->i) RETURN_ERROR(errno);
|
|
return EXTCALL_RETURN_ERR_UNMANAGED;
|
|
}
|
|
|
|
int ExternalPersistRead(ExecutionContext *context, Value *returnValue) {
|
|
(void) returnValue;
|
|
STACK_POP_STRING(entryText, entryBytes);
|
|
returnValue->i = 0;
|
|
if (entryBytes == 0) return EXTCALL_RETURN_UNMANAGED;
|
|
char *temporary = StringZeroTerminate(entryText, entryBytes);
|
|
if (!temporary) return EXTCALL_RETURN_UNMANAGED;
|
|
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 EXTCALL_RETURN_UNMANAGED;
|
|
}
|
|
|
|
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 EXTCALL_NO_RETURN;
|
|
}
|
|
|
|
uintptr_t k = context->mainModule->globalVariableOffset;
|
|
Scope *scope = context->mainModule->rootNode->scope;
|
|
|
|
for (uintptr_t j = 0; j < scope->entryCount; j++) {
|
|
if (scope->entries[j]->type == T_DECLARE && scope->entries[j]->isPersistentVariable) {
|
|
uint32_t variableNameLength = scope->entries[j]->token.textBytes;
|
|
fwrite(&variableNameLength, 1, sizeof(uint32_t), f);
|
|
|
|
if (scope->entries[j]->expressionType->type == T_STR) {
|
|
HeapEntry *entry = &context->heap[context->globalVariables[k].i];
|
|
const char *text;
|
|
size_t bytes;
|
|
ScriptHeapEntryToString(context, entry, &text, &bytes);
|
|
uint32_t variableDataLength = bytes;
|
|
fwrite(&variableDataLength, 1, sizeof(uint32_t), f);
|
|
fwrite(scope->entries[j]->token.text, 1, variableNameLength, f);
|
|
if (bytes) fwrite(text, 1, variableDataLength, f);
|
|
} else if (scope->entries[j]->expressionType->type == T_INT) {
|
|
uint32_t variableDataLength = sizeof(int64_t);
|
|
fwrite(&variableDataLength, 1, sizeof(uint32_t), f);
|
|
fwrite(scope->entries[j]->token.text, 1, variableNameLength, f);
|
|
fwrite(&context->globalVariables[k].i, 1, sizeof(int64_t), f);
|
|
} else if (scope->entries[j]->expressionType->type == T_FLOAT) {
|
|
uint32_t variableDataLength = sizeof(double);
|
|
fwrite(&variableDataLength, 1, sizeof(uint32_t), f);
|
|
fwrite(scope->entries[j]->token.text, 1, variableNameLength, f);
|
|
fwrite(&context->globalVariables[k].f, 1, sizeof(double), f);
|
|
} else if (scope->entries[j]->expressionType->type == T_BOOL) {
|
|
uint32_t variableDataLength = 1;
|
|
fwrite(&variableDataLength, 1, sizeof(uint32_t), f);
|
|
fwrite(scope->entries[j]->token.text, 1, variableNameLength, f);
|
|
uint8_t b = context->globalVariables[k].i == 1;
|
|
fwrite(&b, 1, sizeof(uint8_t), f);
|
|
} else {
|
|
PrintDebug("\033[0;32mWarning: The persistent variable %.*s could not be written, because it had an unsupported type.\033[0m\n",
|
|
scope->entries[j]->token.textBytes, scope->entries[j]->token.text);
|
|
}
|
|
}
|
|
|
|
if (ScopeIsVariableType(scope->entries[j])) {
|
|
k++;
|
|
}
|
|
}
|
|
|
|
if (fclose(f)) {
|
|
PrintDebug("\033[0;32mWarning: Persistent variables could not written. The file could not be closed.\033[0m\n");
|
|
return EXTCALL_NO_RETURN;
|
|
}
|
|
|
|
return EXTCALL_NO_RETURN;
|
|
}
|
|
|
|
int ExternalConsoleGetLine(ExecutionContext *context, Value *returnValue) {
|
|
#ifdef _WIN32
|
|
#pragma message ("ExternalConsoleGetLine unimplemented")
|
|
return -1;
|
|
#else
|
|
char *line = NULL;
|
|
size_t pos;
|
|
size_t unused = getline(&line, &pos, stdin);
|
|
(void) unused;
|
|
uintptr_t index = HeapAllocate(context);
|
|
context->heap[index].type = T_STR;
|
|
context->heap[index].bytes = strlen(line) - 1;
|
|
context->heap[index].text = line;
|
|
returnValue->i = index;
|
|
return EXTCALL_RETURN_MANAGED;
|
|
#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 EXTCALL_RETURN_UNMANAGED;
|
|
}
|
|
|
|
int ExternalSystemRunningAsAdministrator(ExecutionContext *context, Value *returnValue) {
|
|
(void) context;
|
|
#ifdef _WIN32
|
|
#pragma message ("ExternalSystemRunningAsAdministrator unimplemented")
|
|
returnValue->i = 0;
|
|
#else
|
|
returnValue->i = geteuid() == 0;
|
|
#endif
|
|
return EXTCALL_RETURN_UNMANAGED;
|
|
}
|
|
|
|
int ExternalSystemGetHostName(ExecutionContext *context, Value *returnValue) {
|
|
(void) context;
|
|
const char *name;
|
|
#ifdef _WIN32
|
|
name = "Windows";
|
|
#else
|
|
struct utsname buffer;
|
|
uname(&buffer);
|
|
name = buffer.sysname;
|
|
#endif
|
|
RETURN_STRING_COPY(name, strlen(name));
|
|
return EXTCALL_RETURN_MANAGED;
|
|
}
|
|
|
|
int ExternalRandomInt(ExecutionContext *context, Value *returnValue) {
|
|
if (context->c->stackPointer < 2) return -1;
|
|
int64_t min = context->c->stack[context->c->stackPointer - 1].i;
|
|
int64_t max = context->c->stack[context->c->stackPointer - 2].i;
|
|
if (max < min) { PrintError4(context, 0, "RandomInt() called with maximum limit (%ld) less than the minimum limit (%ld).\n", max, min); return 0; }
|
|
returnValue->i = rand() % (max - min + 1) + min;
|
|
context->c->stackPointer -= 2;
|
|
return EXTCALL_RETURN_UNMANAGED;
|
|
}
|
|
|
|
void *SystemSleepThread(void *_coroutine) {
|
|
CoroutineState *coroutine = (CoroutineState *) _coroutine;
|
|
struct timespec sleepTime;
|
|
uint64_t x = 1000000 * coroutine->externalCoroutineData.i;
|
|
sleepTime.tv_sec = x / 1000000000;
|
|
sleepTime.tv_nsec = x % 1000000000;
|
|
nanosleep(&sleepTime, NULL);
|
|
ExternalCoroutineDone(coroutine);
|
|
return NULL;
|
|
}
|
|
|
|
int ExternalSystemSleepMs(ExecutionContext *context, Value *returnValue) {
|
|
(void) returnValue;
|
|
if (context->c->externalCoroutine) return EXTCALL_NO_RETURN;
|
|
if (context->c->stackPointer < 1) return -1;
|
|
context->c->externalCoroutineData = context->c->stack[--context->c->stackPointer];
|
|
#ifdef __linux__
|
|
pthread_t thread;
|
|
pthread_create(&thread, NULL, SystemSleepThread, context->c);
|
|
pthread_detach(thread);
|
|
return EXTCALL_START_COROUTINE;
|
|
#else
|
|
struct timespec sleepTime;
|
|
uint64_t x = 1000000 * context->c->externalCoroutineData.i;
|
|
sleepTime.tv_sec = x / 1000000000;
|
|
sleepTime.tv_nsec = x % 1000000000;
|
|
nanosleep(&sleepTime, NULL);
|
|
return EXTCALL_NO_RETURN;
|
|
#endif
|
|
}
|
|
|
|
int ExternalOpenDocumentEnumerate(ExecutionContext *context, Value *returnValue) {
|
|
(void) context;
|
|
returnValue->i = 0;
|
|
return EXTCALL_RETURN_MANAGED;
|
|
}
|
|
|
|
CoroutineState *ExternalCoroutineWaitAny(ExecutionContext *context) {
|
|
(void) context;
|
|
while (sem_wait(&externalCoroutineSemaphore) == -1);
|
|
pthread_mutex_lock(&externalCoroutineMutex);
|
|
CoroutineState *unblocked = externalCoroutineUnblockedList;
|
|
Assert(unblocked);
|
|
externalCoroutineUnblockedList = unblocked->nextExternalCoroutine;
|
|
unblocked->nextExternalCoroutine = NULL;
|
|
pthread_mutex_unlock(&externalCoroutineMutex);
|
|
return unblocked;
|
|
}
|
|
|
|
void PrintREPLResult(ExecutionContext *context, Node *type, Value value) {
|
|
if (type->type == T_INT) {
|
|
printf("%ld", value.i);
|
|
} else if (type->type == T_BOOL) {
|
|
printf("%s", value.i ? "true" : "false");
|
|
} else if (type->type == T_NULL) {
|
|
printf("null");
|
|
} else if (type->type == T_FLOAT) {
|
|
printf("%f", value.f);
|
|
} else if (type->type == T_STR) {
|
|
Assert(context->heapEntriesAllocated > (uint64_t) value.i);
|
|
HeapEntry *entry = &context->heap[value.i];
|
|
const char *valueText;
|
|
size_t valueBytes;
|
|
ScriptHeapEntryToString(context, entry, &valueText, &valueBytes);
|
|
printf("%.*s", (int) valueBytes, valueText);
|
|
} else if (type->type == T_ERR) {
|
|
if (value.i) {
|
|
Assert(context->heapEntriesAllocated > (uint64_t) value.i);
|
|
HeapEntry *entry = &context->heap[value.i];
|
|
|
|
if (entry->success) {
|
|
if (type->firstChild->type == T_VOID) {
|
|
printf("Success.");
|
|
} else {
|
|
PrintREPLResult(context, type->firstChild, entry->errorValue);
|
|
}
|
|
} else {
|
|
Assert(context->heapEntriesAllocated > (uint64_t) entry->errorValue.i);
|
|
const char *valueText;
|
|
size_t valueBytes;
|
|
ScriptHeapEntryToString(context, &context->heap[entry->errorValue.i], &valueText, &valueBytes);
|
|
printf("Error: %.*s.", (int) valueBytes, valueText);
|
|
}
|
|
} else {
|
|
printf("Error: UNKNOWN.");
|
|
}
|
|
} else if (type->type == T_LIST) {
|
|
Assert(context->heapEntriesAllocated > (uint64_t) value.i);
|
|
HeapEntry *entry = &context->heap[value.i];
|
|
Assert(entry->type == T_LIST);
|
|
|
|
if (!entry->length) {
|
|
printf("Empty list.\n");
|
|
} else {
|
|
printf("[ ");
|
|
|
|
for (uintptr_t i = 0; i < entry->length; i++) {
|
|
if (i) printf(", ");
|
|
PrintREPLResult(context, type->firstChild, entry->list[i]);
|
|
}
|
|
|
|
printf(" ]");
|
|
}
|
|
} else if (type->type == T_STRUCT) {
|
|
Assert(context->heapEntriesAllocated > (uint64_t) value.i);
|
|
HeapEntry *entry = &context->heap[value.i];
|
|
Assert(entry->type == T_STRUCT);
|
|
uintptr_t i = 0;
|
|
printf("{ ");
|
|
|
|
Node *field = type->firstChild;
|
|
|
|
while (field) {
|
|
if (i) printf(", ");
|
|
Assert(i != entry->fieldCount);
|
|
PrintREPLResult(context, field->firstChild, entry->fields[i]);
|
|
field = field->sibling;
|
|
i++;
|
|
}
|
|
|
|
printf(" }");
|
|
} else if (type->type == T_FUNCPTR) {
|
|
printf("Function pointer.");
|
|
} else {
|
|
printf("The type of the result was not recognized.");
|
|
}
|
|
}
|
|
|
|
void ExternalPassREPLResult(ExecutionContext *context, Value value) {
|
|
PrintREPLResult(context, context->functionData->replResultType, value);
|
|
printf("\n");
|
|
}
|
|
|
|
void *AllocateFixed(size_t bytes) {
|
|
if (!bytes) {
|
|
return NULL;
|
|
}
|
|
|
|
bytes = (bytes + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1);
|
|
|
|
if (bytes >= fixedAllocationCurrentSize || fixedAllocationCurrentPosition >= fixedAllocationCurrentSize - bytes) {
|
|
#if 1
|
|
fixedAllocationCurrentSize = bytes > 1048576 ? bytes : 1048576;
|
|
#else
|
|
fixedAllocationCurrentSize = bytes;
|
|
#endif
|
|
fixedAllocationCurrentPosition = 0;
|
|
fixedAllocationCurrentBlock = calloc(1, fixedAllocationCurrentSize + sizeof(void *));
|
|
|
|
if (!fixedAllocationCurrentBlock) {
|
|
fprintf(stderr, "Internal error: not enough memory to run the script.\n");
|
|
exit(1);
|
|
}
|
|
|
|
*(void **) fixedAllocationCurrentBlock = fixedAllocationBlocks;
|
|
fixedAllocationBlocks = (void **) fixedAllocationCurrentBlock;
|
|
fixedAllocationCurrentBlock += sizeof(void *);
|
|
}
|
|
|
|
void *p = fixedAllocationCurrentBlock + fixedAllocationCurrentPosition;
|
|
fixedAllocationCurrentPosition += bytes;
|
|
return p;
|
|
}
|
|
|
|
void *AllocateResize(void *old, size_t bytes) {
|
|
if (bytes == 0) {
|
|
free(old);
|
|
return NULL;
|
|
}
|
|
|
|
void *p = realloc(old, bytes);
|
|
|
|
if (!p && bytes) {
|
|
fprintf(stderr, "Internal error: not enough memory to run the script.\n");
|
|
exit(1);
|
|
// TODO Better error handling.
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
int MemoryCompare(const void *a, const void *b, size_t bytes) {
|
|
return memcmp(a, b, bytes);
|
|
}
|
|
|
|
void MemoryCopy(void *a, const void *b, size_t bytes) {
|
|
memcpy(a, b, bytes);
|
|
}
|
|
|
|
size_t PrintIntegerToBuffer(char *buffer, size_t bufferBytes, int64_t i) {
|
|
snprintf(buffer, bufferBytes, "%ld", i);
|
|
return strlen(buffer);
|
|
}
|
|
|
|
size_t PrintFloatToBuffer(char *buffer, size_t bufferBytes, double f) {
|
|
snprintf(buffer, bufferBytes, "%f", f);
|
|
return strlen(buffer);
|
|
}
|
|
|
|
void PrintDebug(const char *format, ...) {
|
|
va_list arguments;
|
|
va_start(arguments, format);
|
|
vfprintf(stderr, format, arguments);
|
|
va_end(arguments);
|
|
}
|
|
|
|
void PrintError(Tokenizer *tokenizer, const char *format, ...) {
|
|
fprintf(stderr, "\033[0;33mError on line %d of '%s':\033[0m\n", (int) tokenizer->line, tokenizer->module->path);
|
|
va_list arguments;
|
|
va_start(arguments, format);
|
|
vfprintf(stderr, format, arguments);
|
|
va_end(arguments);
|
|
PrintLine(tokenizer->module, tokenizer->line);
|
|
}
|
|
|
|
void PrintError2(Tokenizer *tokenizer, Node *node, const char *format, ...) {
|
|
fprintf(stderr, "\033[0;33mError on line %d of '%s':\033[0m\n", (int) node->token.line, tokenizer->module->path);
|
|
va_list arguments;
|
|
va_start(arguments, format);
|
|
vfprintf(stderr, format, arguments);
|
|
va_end(arguments);
|
|
PrintLine(tokenizer->module, node->token.line);
|
|
}
|
|
|
|
void PrintError3(const char *format, ...) {
|
|
fprintf(stderr, "\033[0;33mGeneral error:\033[0m\n");
|
|
va_list arguments;
|
|
va_start(arguments, format);
|
|
vfprintf(stderr, format, arguments);
|
|
va_end(arguments);
|
|
}
|
|
|
|
void PrintError4(ExecutionContext *context, uint32_t instructionPointer, const char *format, ...) {
|
|
LineNumber lineNumber = { 0 };
|
|
LineNumberLookup(context, instructionPointer, &lineNumber);
|
|
fprintf(stderr, "\033[0;33mRuntime error on line %d of '%s'\033[0m:\n", lineNumber.lineNumber,
|
|
lineNumber.importData ? lineNumber.importData->path : "??");
|
|
va_list arguments;
|
|
va_start(arguments, format);
|
|
vfprintf(stderr, format, arguments);
|
|
va_end(arguments);
|
|
PrintLine(lineNumber.importData, lineNumber.lineNumber);
|
|
fprintf(stderr, "Back trace:\n");
|
|
PrintBackTrace(context, instructionPointer, context->c, "");
|
|
}
|
|
|
|
void *LibraryLoad(const char *name) {
|
|
Assert(strlen(name) < 256);
|
|
char name2[256 + 20];
|
|
strcpy(name2, "l");
|
|
strcat(name2, name);
|
|
strcat(name2, ".so");
|
|
|
|
void *library = dlopen(name2, RTLD_LAZY);
|
|
|
|
if (!library) {
|
|
PrintError3("The library \"%s\" could not be found or loaded.\n", name2);
|
|
}
|
|
|
|
return library;
|
|
}
|
|
|
|
void *LibraryGetAddress(void *library, const char *name) {
|
|
Assert(strlen(name) < 256);
|
|
Assert(library);
|
|
char name2[256 + 20];
|
|
strcpy(name2, "ScriptExt");
|
|
strcat(name2, name);
|
|
|
|
void *address = dlsym(library, name2);
|
|
|
|
if (!address) {
|
|
PrintError3("The library symbol \"%s\" could not be found.\n", name2);
|
|
}
|
|
|
|
return address;
|
|
}
|
|
|
|
void *FileLoad(const char *path, size_t *length) {
|
|
FILE *file = fopen(path, "rb");
|
|
if (!file) return NULL;
|
|
struct stat s;
|
|
fstat(fileno(file), &s);
|
|
if (!S_ISREG(s.st_mode)) { fclose(file); errno = EISDIR; return NULL; }
|
|
fseek(file, 0, SEEK_END);
|
|
size_t fileSize = ftell(file);
|
|
fseek(file, 0, SEEK_SET);
|
|
char *buffer = (char *) malloc(fileSize + 1);
|
|
buffer[fileSize] = 0;
|
|
size_t readBytes = fread(buffer, 1, fileSize, file);
|
|
fclose(file);
|
|
if (readBytes != fileSize) { free(buffer); return NULL; }
|
|
if (length) *length = fileSize;
|
|
return buffer;
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
if (argc < 2) {
|
|
fprintf(stderr, "Usage: %s <engine options...> <path to script> <script options...>\n", argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
coloredOutput = isatty(STDERR_FILENO);
|
|
srand(time(NULL));
|
|
sem_init(&externalCoroutineSemaphore, 0, 0);
|
|
|
|
char *scriptPath = NULL;
|
|
char *evaluateString = NULL;
|
|
bool evaluateMode = false;
|
|
|
|
for (int i = 1; i < argc; i++) {
|
|
if (argv[i][0] != '-') {
|
|
if (evaluateMode) {
|
|
evaluateString = argv[i];
|
|
} else {
|
|
scriptPath = argv[i];
|
|
}
|
|
|
|
options = argv + i + 1;
|
|
optionCount = argc - i - 1;
|
|
optionsMatched = (bool *) calloc(argc, sizeof(bool));
|
|
break;
|
|
} else if (0 == memcmp(argv[i], "--start=", 8)) {
|
|
startFunction = argv[i] + 8;
|
|
startFunctionBytes = strlen(argv[i]) - 8;
|
|
} else if (0 == memcmp(argv[i], "--debug-bytecode=", 17)) {
|
|
debugBytecodeLevel = atoi(argv[i] + 17);
|
|
} else if (0 == strcmp(argv[i], "--evaluate") || 0 == strcmp(argv[i], "-e")) {
|
|
evaluateMode = true;
|
|
} else {
|
|
fprintf(stderr, "Unrecognised engine option: '%s'.\n", argv[i]);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (!scriptPath && !evaluateString) {
|
|
fprintf(stderr, "Error: %s\n", evaluateMode ? "String to evaluate not specified." : "Path to script not specified.");
|
|
return 1;
|
|
}
|
|
|
|
if (scriptPath) {
|
|
scriptSourceDirectory = (char *) malloc(strlen(scriptPath) + 2);
|
|
strcpy(scriptSourceDirectory, scriptPath);
|
|
char *lastSlash = strrchr(scriptSourceDirectory, '/');
|
|
if (lastSlash) *lastSlash = 0;
|
|
else strcpy(scriptSourceDirectory, ".");
|
|
} else {
|
|
scriptSourceDirectory = (char *) malloc(2);
|
|
scriptSourceDirectory[0] = '.';
|
|
scriptSourceDirectory[1] = 0;
|
|
}
|
|
|
|
size_t dataBytes = evaluateMode ? strlen(evaluateString) : 0;
|
|
void *data = evaluateMode ? malloc(strlen(evaluateString)) : FileLoad(scriptPath, &dataBytes);
|
|
if (evaluateMode) memcpy(data, evaluateString, dataBytes);
|
|
|
|
if (!data) {
|
|
PrintDebug("Error: Could not load the input file '%s'.\n", scriptPath);
|
|
return 1;
|
|
}
|
|
|
|
if (evaluateMode) scriptPath = "[input]";
|
|
int result = ScriptExecuteFromFile(scriptPath, strlen(scriptPath), data, dataBytes, evaluateMode);
|
|
|
|
while (fixedAllocationBlocks) {
|
|
void *block = fixedAllocationBlocks;
|
|
fixedAllocationBlocks = (void **) *fixedAllocationBlocks;
|
|
free(block);
|
|
}
|
|
|
|
free(scriptSourceDirectory);
|
|
free(optionsMatched);
|
|
|
|
return result;
|
|
}
|
|
|
|
#endif
|