From 411388755f8d715357ecbe4e19f12da406e47090 Mon Sep 17 00:00:00 2001 From: Francesco Cozzuto Date: Fri, 20 Jan 2023 20:52:20 +0100 Subject: [PATCH] major restructuring --- src/cli/main.c | 116 +- src/lib/assembler/assemble.c | 19 +- src/lib/assembler/assemble.h | 2 +- src/lib/builtins/basic.c | 53 +- src/lib/compiler/codegen.c | 17 +- src/lib/compiler/codegen.h | 2 +- src/lib/compiler/codegenctx.c | 9 +- src/lib/compiler/codegenctx.h | 4 +- src/lib/compiler/compile.c | 9 +- src/lib/compiler/compile.h | 2 +- src/lib/compiler/parse.c | 104 +- src/lib/compiler/parse.h | 2 +- src/lib/noja.c | 412 ------ src/lib/noja.h | 13 - src/lib/runtime/builtins_api.c | 54 + src/lib/runtime/builtins_api.h | 6 + src/lib/runtime/o_func.c | 220 --- src/lib/runtime/o_nfunc.c | 173 --- src/lib/runtime/path.c | 73 + src/lib/runtime/path.h | 4 + src/lib/runtime/run.c | 1280 ++++++++++++++++++ src/lib/runtime/run.h | 12 + src/lib/runtime/runtime.c | 1788 +++++++------------------ src/lib/runtime/runtime.h | 36 +- src/lib/runtime/runtime_error.c | 59 - src/lib/runtime/serialize_profiling.c | 31 + src/lib/runtime/serialize_profiling.h | 4 + src/lib/utils/error.c | 12 +- src/lib/utils/error.h | 4 +- src/test/main.c | 17 +- 30 files changed, 2175 insertions(+), 2362 deletions(-) delete mode 100644 src/lib/noja.c delete mode 100644 src/lib/noja.h create mode 100644 src/lib/runtime/builtins_api.c create mode 100644 src/lib/runtime/builtins_api.h delete mode 100644 src/lib/runtime/o_func.c delete mode 100644 src/lib/runtime/o_nfunc.c create mode 100644 src/lib/runtime/path.c create mode 100644 src/lib/runtime/path.h create mode 100644 src/lib/runtime/run.c create mode 100644 src/lib/runtime/run.h delete mode 100644 src/lib/runtime/runtime_error.c create mode 100644 src/lib/runtime/serialize_profiling.c create mode 100644 src/lib/runtime/serialize_profiling.h diff --git a/src/cli/main.c b/src/cli/main.c index 4cf18d9..62bae10 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -31,8 +31,12 @@ #include #include #include +#include #include -#include "../lib/noja.h" +#include "../lib/runtime/run.h" +#include "../lib/runtime/runtime.h" +#include "../lib/runtime/builtins_api.h" +#include "../lib/runtime/serialize_profiling.h" static void usage(FILE *stream, const char *name) { @@ -57,12 +61,21 @@ static void help(FILE *stream, const char *name) } typedef enum { - Mode_DISASSEMBLy, + Mode_DISASSEMBLY, Mode_ASSEMBLY, Mode_DEFAULT, Mode_HELP, } Mode; +Runtime *runtime = NULL; + +static void signalHandler(int signo) +{ + (void) signo; + if (runtime != NULL) + Runtime_Interrupt(runtime); +} + int main(int argc, char **argv) { Mode mode = Mode_DEFAULT; @@ -80,7 +93,7 @@ int main(int argc, char **argv) } else if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--disassembly")) { - mode = Mode_DISASSEMBLy; + mode = Mode_DISASSEMBLY; } else if (!strcmp(argv[i], "-i") || !strcmp(argv[i], "--inline")) { @@ -131,47 +144,78 @@ int main(int argc, char **argv) code = 0; break; - case Mode_DEFAULT: - if (input == NULL) { - fprintf(stderr, "No input file"); - code = -1; - break; - } - if (profile) { - if (no_file) code = NOJA_profileString(input, heap) ? 0 : -1; - else code = NOJA_profileFile(input, heap) ? 0 : -1; - } else { - if (output != NULL) - fprintf(stderr, "Ignoring option -o\n"); - if (no_file) code = NOJA_runString(input, heap) ? 0 : -1; - else code = NOJA_runFile(input, heap) ? 0 : -1; - } - break; - + case Mode_DEFAULT: case Mode_ASSEMBLY: - if (input == NULL) { - fprintf(stderr, "No assembly input file"); - code = -1; + { + if (input == NULL) { + fprintf(stderr, "No input file"); + code = -1; + break; + } + + RuntimeConfig config = Runtime_GetDefaultConfigs(); + config.time = profile; + config.heap = heap; + + runtime = Runtime_New(config); + if (runtime == NULL) { + fprintf(stderr, "Failed to initialize runtime"); + code = -1; + break; + } + signal(SIGINT, signalHandler); + signal(SIGTERM, signalHandler); + + Error error; + Error_Init(&error); + + if (!Runtime_plugDefaultBuiltins(runtime, (Error*) &error)) { + Error_Print(&error, ErrorType_RUNTIME, stderr); + Error_Free(&error); + Runtime_PrintStackTrace(runtime, stderr); + Runtime_Free(runtime); + code = -1; + break; + } + + bool ok; + if (mode == Mode_ASSEMBLY) { + if (no_file) + ok = runBytecodeString(runtime, input, (Error*) &error); + else + ok = runBytecodeFile(runtime, input, (Error*) &error); + } else { + if (no_file) + ok = runString(runtime, input, (Error*) &error); + else + ok = runFile(runtime, input, (Error*) &error); + } + + if (ok == false) { + Error_Print(&error, ErrorType_RUNTIME, stderr); + Error_Free(&error); + Runtime_PrintStackTrace(runtime, stderr); + Runtime_Free(runtime); + code = -1; + break; + } + + code = 0; + if (output == NULL) + Runtime_SerializeProfilingResultsToStream(runtime, stdout); + else + Runtime_SerializeProfilingResultsToFile(runtime, output); + Runtime_Free(runtime); break; } - if (profile) { - if (no_file) code = NOJA_profileAssemblyString(input, heap) ? 0 : -1; - else code = NOJA_profileAssemblyFile(input, heap) ? 0 : -1; - } else { - if (output != NULL) - fprintf(stderr, "Ignoring option -o\n"); - if (no_file) code = NOJA_runAssemblyString(input, heap) ? 0 : -1; - else code = NOJA_runAssemblyFile(input, heap) ? 0 : -1; - } - break; - case Mode_DISASSEMBLy: + + case Mode_DISASSEMBLY: if (input == NULL) { fprintf(stderr, "No disassembly input file"); code = -1; break; } - if (no_file) code = NOJA_dumpStringBytecode(input) ? 0 : -1; - else code = NOJA_dumpFileBytecode(input) ? 0 : -1; + /* .. */ break; } diff --git a/src/lib/assembler/assemble.c b/src/lib/assembler/assemble.c index 95c7c8c..9fb732e 100644 --- a/src/lib/assembler/assemble.c +++ b/src/lib/assembler/assemble.c @@ -11,6 +11,7 @@ typedef struct { const char *str; size_t len; size_t cur; + int *error_offset; } Context; static void skipIdentifier(Context *ctx) @@ -56,6 +57,7 @@ static bool parseLabelAndOpcode(Context *ctx, bool *no_label, char c = ctx->str[ctx->cur]; if(!isalpha(c) && c != '_') { // ERROR: Missing opcode + *ctx->error_offset = ctx->cur; Error_Report(error, ErrorType_SYNTAX, "Missing opcode"); #warning "should there be a return here?" } @@ -78,6 +80,7 @@ static bool parseLabelAndOpcode(Context *ctx, bool *no_label, skipSpaces(ctx); if(ctx->cur == ctx->len || (!isalpha(ctx->str[ctx->cur]) && ctx->str[ctx->cur] != '_')) { + *ctx->error_offset = ctx->cur; Error_Report(error, ErrorType_SYNTAX, "Missing opcode after label"); return false; } @@ -112,6 +115,7 @@ static bool parseStringOperand(Context *ctx, Error *error, BPAlloc *alloc, Opera ctx->cur += 1; if(ctx->cur == ctx->len) { + *ctx->error_offset = ctx->cur; Error_Report(error, ErrorType_SYNTAX, "End of source inside a string literal"); return false; } @@ -124,6 +128,7 @@ static bool parseStringOperand(Context *ctx, Error *error, BPAlloc *alloc, Opera char *copy = BPAlloc_Malloc(alloc, literal_length+1); if(copy == NULL) { + *ctx->error_offset = ctx->cur; Error_Report(error, ErrorType_INTERNAL, "No memory"); return false; } @@ -152,6 +157,7 @@ static bool parseIntegerOperand(Context *ctx, Error *error, Operand *op) // Will this overflow? if(buffer > (LLONG_MAX - d) / 10) { + *ctx->error_offset = ctx->cur; Error_Report(error, ErrorType_SEMANTIC, "Integer literal is too big to be represented in %d bits", 8*sizeof(buffer)); return false; } @@ -255,6 +261,7 @@ static bool parseOperands(Context *ctx, BPAlloc *alloc, Error *error, Promise *promise = LabelList_GetLabel(list, ctx->str + offset, length); if(promise == NULL) { + *ctx->error_offset = ctx->cur; Error_Report(error, ErrorType_INTERNAL, "No memory"); return false; } @@ -264,11 +271,13 @@ static bool parseOperands(Context *ctx, BPAlloc *alloc, Error *error, } else { // ERROR: Unexpected character + *ctx->error_offset = ctx->cur; Error_Report(error, ErrorType_SYNTAX, "Unexpected character '%c'", c); return false; } if(*opc == opc_max) { + *ctx->error_offset = ctx->cur; Error_Report(error, ErrorType_SEMANTIC, "Too many operands"); return false; } @@ -282,6 +291,7 @@ static bool parseOperands(Context *ctx, BPAlloc *alloc, Error *error, c = ctx->str[ctx->cur]; if(c != ',') { + *ctx->error_offset = ctx->cur; Error_Report(error, ErrorType_SYNTAX, "Unexpected character '%c' (',' or ';' were expected)", c); return false; } @@ -296,18 +306,20 @@ static bool parseOperands(Context *ctx, BPAlloc *alloc, Error *error, return true; } -Executable *assemble(Source *src, Error *error) +Executable *assemble(Source *src, Error *error, int *error_offset) { Executable *exe = NULL; BPAlloc *alloc = BPAlloc_Init(-1); if(alloc == NULL) { + *error_offset = -1; Error_Report(error, ErrorType_INTERNAL, "No memory"); return NULL; } LabelList *list = LabelList_New(alloc); if(list == NULL) { + *error_offset = -1; Error_Report(error, ErrorType_INTERNAL, "No memory"); BPAlloc_Free(alloc); return NULL; @@ -315,6 +327,7 @@ Executable *assemble(Source *src, Error *error) ExeBuilder *builder = ExeBuilder_New(alloc); if(builder == NULL) { + *error_offset = -1; Error_Report(error, ErrorType_INTERNAL, "No memory"); LabelList_Free(list); BPAlloc_Free(alloc); @@ -325,6 +338,7 @@ Executable *assemble(Source *src, Error *error) .str = Source_GetBody(src), .len = Source_GetSize(src), .cur = 0, + .error_offset = error_offset, }; while(1) { @@ -343,6 +357,7 @@ Executable *assemble(Source *src, Error *error) if(no_label == false) { long long int value = ExeBuilder_InstrCount(builder); if(!LabelList_SetLabel(list, ctx.str + label.offset, label.length, value)) { + *error_offset = ctx.cur; Error_Report(error, ErrorType_INTERNAL, "Out of memory"); goto done; } @@ -353,6 +368,7 @@ Executable *assemble(Source *src, Error *error) Opcode opcode; const char *name = ctx.str + opcode_name.offset; if(!Executable_GetOpcodeBinaryFromName(name, opcode_name.length, &opcode)) { + *error_offset = ctx.cur; Error_Report(error, ErrorType_SEMANTIC, "Opcode %.*s doesn't exist", (int) opcode_name.length, name); goto done; } @@ -378,6 +394,7 @@ Executable *assemble(Source *src, Error *error) size_t unresolved_count = LabelList_GetUnresolvedCount(list); if(unresolved_count > 0) { + *error_offset = -1; Error_Report(error, ErrorType_SEMANTIC, "%d unresolved labels", unresolved_count); goto done; } diff --git a/src/lib/assembler/assemble.h b/src/lib/assembler/assemble.h index 6170966..32baeb3 100644 --- a/src/lib/assembler/assemble.h +++ b/src/lib/assembler/assemble.h @@ -3,5 +3,5 @@ #include "../utils/error.h" #include "../utils/source.h" #include "../common/executable.h" -Executable *assemble(Source *src, Error *error); +Executable *assemble(Source *src, Error *error, int *error_offset); #endif /* ASSEMBLE_H */ \ No newline at end of file diff --git a/src/lib/builtins/basic.c b/src/lib/builtins/basic.c index 10ee89d..fa2f530 100644 --- a/src/lib/builtins/basic.c +++ b/src/lib/builtins/basic.c @@ -44,7 +44,7 @@ #include "../common/defs.h" #include "../objects/objects.h" #include "../runtime/runtime.h" -#include "../compiler/compile.h" +#include "../runtime/run.h" static int bin_getCurrentWorkingDirectory(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error) { @@ -133,56 +133,7 @@ static int bin_import(Runtime *runtime, return -1; const char *path = pargs[0].as_string.data; - size_t path_len = pargs[0].as_string.size; - - char full_path[1024]; - - if(path[0] == '/') { - if(path_len >= sizeof(full_path)) { - Error_Report(error, ErrorType_INTERNAL, "Internal buffer is too small"); - return -1; - } - strcpy(full_path, path); - } else { - size_t written = Runtime_GetCurrentScriptFolder(runtime, full_path, sizeof(full_path)); - if(written == 0) { - Error_Report(error, ErrorType_INTERNAL, "Internal buffer is too small"); - return -1; - } - - if(written + path_len >= sizeof(full_path)) { - Error_Report(error, ErrorType_INTERNAL, "Internal buffer is too small"); - return -1; - } - - memcpy(full_path + written, path, path_len); - full_path[written + path_len] = '\0'; - } - - Source *src = Source_FromFile(full_path, error); - if(src == NULL) - return -1; - - Executable *exe = compile(src, error); - if(exe == NULL) { - Source_Free(src); - return -1; - } - - Object *sub_rets[8]; - int retc = run(runtime, error, exe, 0, NULL, NULL, 0, sub_rets); - if(retc < 0) - { - Source_Free(src); - Executable_Free(exe); - return -1; - } - ASSERT(retc == 1); - - Source_Free(src); - Executable_Free(exe); - rets[0] = sub_rets[0]; - return 1; + return runFileRelativeToScript(runtime, path, rets, error); } static int bin_type(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error) diff --git a/src/lib/compiler/codegen.c b/src/lib/compiler/codegen.c index 7c02beb..586650c 100644 --- a/src/lib/compiler/codegen.c +++ b/src/lib/compiler/codegen.c @@ -320,7 +320,7 @@ static void flattenTupleTree(CodegenContext *ctx, ExprNode *root, ExprNode *tupl if(max == *count) { - CodegenContext_ReportErrorAndJump(ctx, ErrorType_INTERNAL, "Static buffer is too small"); + CodegenContext_ReportErrorAndJump(ctx, root->base.offset, ErrorType_INTERNAL, "Static buffer is too small"); UNREACHABLE; } @@ -348,7 +348,7 @@ static void emitInstrForAssignmentNode(CodegenContext *ctx, OperExprNode *asgn, if(((ExprNode*) rop)->kind == EXPR_CALL) emitInstrForFuncCallNode(ctx, (CallExprNode*) rop, label_break, count); else { - CodegenContext_ReportErrorAndJump(ctx, ErrorType_SEMANTIC, "Assigning to %d variables only 1 value", count); + CodegenContext_ReportErrorAndJump(ctx, rop->offset, ErrorType_SEMANTIC, "Assigning to %d variables only 1 value", count); UNREACHABLE; } } @@ -376,7 +376,7 @@ static void emitInstrForAssignmentNode(CodegenContext *ctx, OperExprNode *asgn, } default: - CodegenContext_ReportErrorAndJump(ctx, ErrorType_SEMANTIC, "Assigning to something that it can't be assigned to"); + CodegenContext_ReportErrorAndJump(ctx, tuple_item->base.offset, ErrorType_SEMANTIC, "Assigning to something that it can't be assigned to"); UNREACHABLE; } @@ -392,8 +392,8 @@ static void emitInstrForExprNode(CodegenContext *ctx, ExprNode *expr, { case EXPR_PAIR: CodegenContext_ReportErrorAndJump( - ctx, 0, "Tuple outside of " - "assignment or return statement"); + ctx, expr->base.offset, 0, + "Tuple outside of assignment or return statement"); UNREACHABLE; return; // For the compiler warning. @@ -415,7 +415,7 @@ static void emitInstrForExprNode(CodegenContext *ctx, ExprNode *expr, } case EXPR_ARW: - CodegenContext_ReportErrorAndJump(ctx, ErrorType_SEMANTIC, "Operator -> out of a function call"); + CodegenContext_ReportErrorAndJump(ctx, expr->base.offset, ErrorType_SEMANTIC, "Operator -> out of a function call"); UNREACHABLE; return; @@ -683,7 +683,7 @@ static void emitInstrForNode(CodegenContext *ctx, Node *node, Label *label_break case NODE_BREAK: if(label_break == NULL) - CodegenContext_ReportErrorAndJump(ctx, ErrorType_SEMANTIC, "Break not inside a loop"); + CodegenContext_ReportErrorAndJump(ctx, node->offset, ErrorType_SEMANTIC, "Break not inside a loop"); emitInstr_JUMP(ctx, label_break, node->offset, node->length); return; @@ -761,7 +761,7 @@ static void emitInstrForNode(CodegenContext *ctx, Node *node, Label *label_break * returned and the `error` structure is filled out. * */ -Executable *codegen(AST *ast, BPAlloc *alloc, Error *error) +Executable *codegen(AST *ast, BPAlloc *alloc, Error *error, int *error_offset) { assert(ast != NULL); assert(error != NULL); @@ -770,6 +770,7 @@ Executable *codegen(AST *ast, BPAlloc *alloc, Error *error) CodegenContext *ctx = CodegenContext_New(error, alloc); if(ctx == NULL) { + *error_offset = 0; Error_Report(error, ErrorType_INTERNAL, "No memory"); return NULL; } diff --git a/src/lib/compiler/codegen.h b/src/lib/compiler/codegen.h index c2089f8..cec25e8 100644 --- a/src/lib/compiler/codegen.h +++ b/src/lib/compiler/codegen.h @@ -34,5 +34,5 @@ #include "../utils/bpalloc.h" #include "../common/executable.h" #include "AST.h" -Executable *codegen(AST *ast, BPAlloc *alloc, Error *error); +Executable *codegen(AST *ast, BPAlloc *alloc, Error *error, int *error_offset); #endif /* CODEGEN_H */ \ No newline at end of file diff --git a/src/lib/compiler/codegenctx.c b/src/lib/compiler/codegenctx.c index 409768a..dfb3a03 100644 --- a/src/lib/compiler/codegenctx.c +++ b/src/lib/compiler/codegenctx.c @@ -9,6 +9,7 @@ struct CodegenContext { bool own_alloc; bool env_set; jmp_buf *env; + int *error_offset; }; Label *Label_New(CodegenContext *ctx) @@ -17,7 +18,7 @@ Label *Label_New(CodegenContext *ctx) if(promise != NULL) return (Label*) promise; - CodegenContext_ReportErrorAndJump(ctx, ErrorType_INTERNAL, "No memory"); + CodegenContext_ReportErrorAndJump(ctx, -1, ErrorType_INTERNAL, "No memory"); UNREACHABLE; return NULL; // For the compiler warning. } @@ -54,14 +55,14 @@ static void okNowJump(CodegenContext *ctx) } void CodegenContext_ReportErrorAndJump_(CodegenContext *ctx, const char *file, - const char *func, int line, ErrorType type, - const char *format, ...) + const char *func, int line, int error_offset, + ErrorType type, const char *format, ...) { va_list args; va_start(args, format); _Error_Report2(ctx->error, type, file, func, line, format, args); va_end(args); - + *ctx->error_offset = error_offset; okNowJump(ctx); UNREACHABLE; } diff --git a/src/lib/compiler/codegenctx.h b/src/lib/compiler/codegenctx.h index 3656de7..bc2a4f1 100644 --- a/src/lib/compiler/codegenctx.h +++ b/src/lib/compiler/codegenctx.h @@ -9,8 +9,8 @@ void CodegenContext_EmitInstr(CodegenContext *ctx, Opcode opcode, Ope void CodegenContext_SetJumpDest(CodegenContext *ctx, jmp_buf *env); void CodegenContext_Free(CodegenContext *ctx); Executable *CodegenContext_MakeExecutableAndFree(CodegenContext *ctx, Source *src); -void CodegenContext_ReportErrorAndJump_(CodegenContext *ctx, const char *file, const char *func, int line, ErrorType type, const char *format, ...); -#define CodegenContext_ReportErrorAndJump(ctx, int, fmt, ...) CodegenContext_ReportErrorAndJump_(ctx, __FILE__, __func__, __LINE__, int, fmt, ## __VA_ARGS__) +void CodegenContext_ReportErrorAndJump_(CodegenContext *ctx, const char *file, const char *func, int line, int error_offset, ErrorType type, const char *format, ...); +#define CodegenContext_ReportErrorAndJump(ctx, error_offset, typ, fmt, ...) CodegenContext_ReportErrorAndJump_(ctx, __FILE__, __func__, __LINE__, error_offset, typ, fmt, ## __VA_ARGS__) int CodegenContext_InstrCount(CodegenContext *ctx); typedef struct Label Label; diff --git a/src/lib/compiler/compile.c b/src/lib/compiler/compile.c index c30913f..0620f48 100644 --- a/src/lib/compiler/compile.c +++ b/src/lib/compiler/compile.c @@ -5,20 +5,21 @@ #include "codegen.h" #include "compile.h" -Executable *compile(Source *src, Error *error) -{ +Executable *compile(Source *src, Error *error, int *error_offset) +{ // Create a bump-pointer allocator to hold the AST. BPAlloc *alloc = BPAlloc_Init(-1); if(alloc == NULL) { + *error_offset = -1; Error_Report(error, ErrorType_INTERNAL, "No memory"); return NULL; } // NOTE: The AST is stored in the BPAlloc. Its // lifetime is the same as the pool. - AST *ast = parse(src, alloc, error); + AST *ast = parse(src, alloc, error, error_offset); if(ast == NULL) { @@ -28,7 +29,7 @@ Executable *compile(Source *src, Error *error) } // Transform the AST into bytecode. - Executable *exe = codegen(ast, alloc, error); + Executable *exe = codegen(ast, alloc, error, error_offset); // We're done with the AST. BPAlloc_Free(alloc); diff --git a/src/lib/compiler/compile.h b/src/lib/compiler/compile.h index d62fef9..27fa25f 100644 --- a/src/lib/compiler/compile.h +++ b/src/lib/compiler/compile.h @@ -3,5 +3,5 @@ #include "../utils/error.h" #include "../utils/source.h" #include "../common/executable.h" -Executable *compile(Source *src, Error *error); +Executable *compile(Source *src, Error *error, int *error_offset); #endif /* COMPILE_H */ \ No newline at end of file diff --git a/src/lib/compiler/parse.c b/src/lib/compiler/parse.c index dbb33ee..bd5be45 100644 --- a/src/lib/compiler/parse.c +++ b/src/lib/compiler/parse.c @@ -129,6 +129,7 @@ typedef struct { Token *token; BPAlloc *alloc; Error *error; + int *error_offset; } Context; static Node *parse_statement(Context *ctx); @@ -152,6 +153,7 @@ static inline _Bool isoper(char c) c == '%'; } +#warning "update doc arguments" /* Symbol: tokenize * * Build a list of tokens that represents the @@ -175,7 +177,7 @@ static inline _Bool isoper(char c) * structure is filled out. * */ -static Token *tokenize(Source *src, BPAlloc *alloc, Error *error) +static Token *tokenize(Source *src, BPAlloc *alloc, Error *error, int *error_offset) { const char *str = Source_GetBody(src); int len = Source_GetSize(src); @@ -209,6 +211,7 @@ static Token *tokenize(Source *src, BPAlloc *alloc, Error *error) if(tok == NULL) { // Error: No memory. + *error_offset = i; Error_Report(error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -295,6 +298,7 @@ static Token *tokenize(Source *src, BPAlloc *alloc, Error *error) if(i == len) { + *error_offset = i; Error_Report(error, 0, "Source ended inside string literal"); return NULL; } @@ -377,6 +381,7 @@ static Token *tokenize(Source *src, BPAlloc *alloc, Error *error) if(tok == NULL) { // Error: No memory. + *error_offset = i; Error_Report(error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -427,7 +432,7 @@ static Token *tokenize(Source *src, BPAlloc *alloc, Error *error) * is freed before the AST. * */ -AST *parse(Source *src, BPAlloc *alloc, Error *error) +AST *parse(Source *src, BPAlloc *alloc, Error *error, int *error_offset) { assert(src != NULL); assert(alloc != NULL); @@ -436,9 +441,10 @@ AST *parse(Source *src, BPAlloc *alloc, Error *error) AST *ast = BPAlloc_Malloc(alloc, sizeof(AST)); if(ast == NULL) + #warning "should an error be reported here?" return NULL; - Token *tokens = tokenize(src, alloc, error); + Token *tokens = tokenize(src, alloc, error, error_offset); if(tokens == NULL) return NULL; @@ -448,7 +454,7 @@ AST *parse(Source *src, BPAlloc *alloc, Error *error) ctx.token = tokens; ctx.alloc = alloc; ctx.error = error; - + ctx.error_offset = error_offset; Node *root = parse_compound_statement(&ctx, TDONE); if(root == NULL) @@ -599,6 +605,7 @@ static Node *parse_statement(Context *ctx) if(current(ctx) != ';') { + *ctx->error_offset = token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Got token \"%.*s\" where \";\" was expected", ctx->token->length, ctx->src + ctx->token->offset); return NULL; } @@ -609,6 +616,7 @@ static Node *parse_statement(Context *ctx) if(node == NULL) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -633,6 +641,7 @@ static Node *parse_statement(Context *ctx) if(current(ctx) != ';') { + *ctx->error_offset = current_token(ctx)->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Got token \"%.*s\" where \";\" was expected", ctx->token->length, ctx->src + ctx->token->offset); @@ -645,6 +654,7 @@ static Node *parse_statement(Context *ctx) if(node == NULL) { + *ctx->error_offset = current_token(ctx)->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -667,6 +677,7 @@ static Node *parse_statement(Context *ctx) return parse_dowhile_statement(ctx); } + *ctx->error_offset = current_token(ctx)->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Got token \"%.*s\" where the start of a statement was expected", ctx->token->length, ctx->src + ctx->token->offset); return NULL; @@ -683,15 +694,14 @@ static Node *parse_expression_statement(Context *ctx) if(done(ctx)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended right after an expression, where a ';' was expected"); return NULL; } if(current(ctx) != ';') { - // ERROR: Got something other than a semicolon at the end - // of statement. - + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Got token \"%.*s\" where \";\" was expected", ctx->token->length, ctx->src + ctx->token->offset); return NULL; } @@ -715,6 +725,7 @@ static Node *makeStringExprNode(Context *ctx, const char *str, int len) if(copy == NULL) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -730,6 +741,7 @@ static Node *makeStringExprNode(Context *ctx, const char *str, int len) if(node == NULL) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -752,12 +764,14 @@ static Node *parse_string_primary_expression(Context *ctx) if(done(ctx)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended where a string literal was expected"); return NULL; } if(current(ctx) != TSTRING) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Got token \"%.*s\" where a string literal was expected", ctx->token->length, ctx->src + ctx->token->offset); return NULL; } @@ -784,6 +798,7 @@ static Node *parse_string_primary_expression(Context *ctx) if(temp_used + segm_len >= (int) sizeof(temp)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "String is too big to be rendered inside the fixed size buffer"); return NULL; } @@ -797,6 +812,7 @@ static Node *parse_string_primary_expression(Context *ctx) if(temp_used + 1 >= (int) sizeof(temp)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "String is too big to be rendered inside the fixed size buffer"); return NULL; } @@ -818,6 +834,7 @@ static Node *parse_string_primary_expression(Context *ctx) case '\'': temp[temp_used++] = '\''; break; default: + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Invalid escape sequence \\%c", src[i]); return NULL; } @@ -843,12 +860,14 @@ static Node *parse_list_primary_expression(Context *ctx) if(done(ctx)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended where a list literal was expected"); return NULL; } if(current(ctx) != '[') { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Got token \"%.*s\" where a list literal was expected", ctx->token->length, ctx->src + ctx->token->offset); return NULL; } @@ -887,6 +906,7 @@ static Node *parse_list_primary_expression(Context *ctx) if(current(ctx) != ',') { + *ctx->error_offset = ctx->token->offset; if(current(ctx) == TDONE) Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended inside a list literal"); else @@ -910,6 +930,7 @@ static Node *parse_list_primary_expression(Context *ctx) if(list == NULL) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -933,6 +954,7 @@ static Node *parse_map_primary_expression(Context *ctx) if(done(ctx)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended where a map literal was expected"); return NULL; @@ -940,6 +962,7 @@ static Node *parse_map_primary_expression(Context *ctx) if(current(ctx) != '{') { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Got token \"%.*s\" where a map literal was expected", ctx->token->length, ctx->src + ctx->token->offset); @@ -956,6 +979,7 @@ static Node *parse_map_primary_expression(Context *ctx) if(done(ctx)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended where a map child item's key was expected"); return NULL; } @@ -1007,6 +1031,7 @@ static Node *parse_map_primary_expression(Context *ctx) if(done(ctx)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended where a map key-value " "separator ':' was expected"); @@ -1015,6 +1040,7 @@ static Node *parse_map_primary_expression(Context *ctx) if(current(ctx) != ':') { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Got token \"%.*s\" where a map key-value " "separator ':' was expected", @@ -1054,6 +1080,7 @@ static Node *parse_map_primary_expression(Context *ctx) if (!item_is_func_decl) { if(current(ctx) != ',') { + *ctx->error_offset = ctx->token->offset; if(current(ctx) == TDONE) Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended inside a map literal"); else @@ -1080,6 +1107,7 @@ static Node *parse_map_primary_expression(Context *ctx) if(map == NULL) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -1119,6 +1147,7 @@ static Node *makeIdentExprNode(Context *ctx) if(copy == NULL) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -1129,6 +1158,7 @@ static Node *makeIdentExprNode(Context *ctx) if(node == NULL) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -1151,6 +1181,7 @@ static Node *parse_primary_expresion(Context *ctx) if(done(ctx)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended where a primary expression was expected"); return NULL; } @@ -1175,12 +1206,14 @@ static Node *parse_primary_expresion(Context *ctx) if(done(ctx)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended before \")\", after sub-expression"); return NULL; } if(current(ctx) != ')') { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Missing \")\", after sub-expression"); return NULL; } @@ -1194,6 +1227,7 @@ static Node *parse_primary_expresion(Context *ctx) if(ctx->token->length >= (int) sizeof(buffer)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "Integer is too big"); return NULL; } @@ -1207,6 +1241,7 @@ static Node *parse_primary_expresion(Context *ctx) if(errno == ERANGE) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "Integer is too big"); return NULL; } @@ -1218,6 +1253,7 @@ static Node *parse_primary_expresion(Context *ctx) if(node == NULL) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -1241,6 +1277,7 @@ static Node *parse_primary_expresion(Context *ctx) if(ctx->token->length >= (int) sizeof(buffer)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "Floating is too big"); return NULL; } @@ -1254,6 +1291,7 @@ static Node *parse_primary_expresion(Context *ctx) if(errno == ERANGE) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "Floating is too big"); return NULL; } @@ -1265,6 +1303,7 @@ static Node *parse_primary_expresion(Context *ctx) if(node == NULL) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -1295,6 +1334,7 @@ static Node *parse_primary_expresion(Context *ctx) if(node == NULL) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -1319,6 +1359,7 @@ static Node *parse_primary_expresion(Context *ctx) if(node == NULL) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -1343,6 +1384,7 @@ static Node *parse_primary_expresion(Context *ctx) if(node == NULL) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -1369,10 +1411,12 @@ static Node *parse_primary_expresion(Context *ctx) } case TDONE: + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "Unexpected end of source where a primary expression was expected"); return NULL; default: + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "Unexpected token \"%.*s\" where a primary expression was expected", ctx->token->length, ctx->src + ctx->token->offset); return NULL; } @@ -1387,6 +1431,7 @@ static Node *makeIndexOrArrowSelectionExprNode(Context *ctx, bool arrow, Node *s if(sel == NULL) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -1425,12 +1470,14 @@ static Node *parse_postfix_expression(Context *ctx) if(done(ctx)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended after dot or arrow"); return NULL; } if(current(ctx) != TIDENT) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Got unexpected token \"%.*s\" after dot or arrow, where an identifier was expected", ctx->token->length, ctx->src + ctx->token->offset); return NULL; } @@ -1462,6 +1509,7 @@ static Node *parse_postfix_expression(Context *ctx) if(ls->itemc == 0) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Missing index in index selection expression"); return NULL; } @@ -1507,6 +1555,7 @@ static Node *parse_postfix_expression(Context *ctx) if(current(ctx) != ',') { + *ctx->error_offset = ctx->token->offset; if(current(ctx) == TDONE) Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended inside a function argument list"); else @@ -1529,6 +1578,7 @@ static Node *parse_postfix_expression(Context *ctx) if(call == NULL) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -1559,6 +1609,7 @@ static Node *parse_prefix_expression(Context *ctx) { if(done(ctx)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended where a prefix expression was expected"); return NULL; } @@ -1566,6 +1617,7 @@ static Node *parse_prefix_expression(Context *ctx) switch(current(ctx)) { case TDONE: + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "Unexpected end of source where a prefix expression was expected"); return NULL; @@ -1779,12 +1831,14 @@ static Node *parse_ifelse_statement(Context *ctx) if(done(ctx)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended where an if-else statement was expected"); return NULL; } if(current(ctx) != TKWIF) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Got unexpected token \"%.*s\" where an if-else statement was expected", ctx->token->length, ctx->src + ctx->token->offset); return NULL; } @@ -1801,12 +1855,14 @@ static Node *parse_ifelse_statement(Context *ctx) if(done(ctx)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended right after an if-else condition, where a ':' was expected"); return NULL; } if(current(ctx) != ':') { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Got unexpected token \"%.*s\" after an if-else condition, where a ':' was expected", ctx->token->length, ctx->src + ctx->token->offset); return NULL; } @@ -1836,7 +1892,7 @@ static Node *parse_ifelse_statement(Context *ctx) if(ifelse == NULL) { - // ERROR: No memory. + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -1876,6 +1932,7 @@ static Node *parse_compound_statement(Context *ctx, TokenKind end) if(current(ctx) != end) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended inside compound statement"); return NULL; } @@ -1886,7 +1943,7 @@ static Node *parse_compound_statement(Context *ctx, TokenKind end) if(node == NULL) { - // ERROR: No memory. + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -1907,6 +1964,7 @@ static _Bool parse_function_arguments(Context *ctx, int *argc_, Node **argv_) if(next(ctx) != '(') { + *ctx->error_offset = ctx->token->offset; if(done(ctx)) Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended where a function argument list was expected"); else @@ -1925,12 +1983,14 @@ static _Bool parse_function_arguments(Context *ctx, int *argc_, Node **argv_) { if(done(ctx)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended inside a function argument list"); return 0; } if(current(ctx) != TIDENT) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Got unexpected token \"%.*s\" where a function argument name was expected", ctx->token->length, ctx->src + ctx->token->offset); return 0; } @@ -1939,6 +1999,7 @@ static _Bool parse_function_arguments(Context *ctx, int *argc_, Node **argv_) if(arg_name == NULL) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return 0; } @@ -1968,6 +2029,7 @@ static _Bool parse_function_arguments(Context *ctx, int *argc_, Node **argv_) if(arg == NULL) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return 0; } @@ -1990,6 +2052,7 @@ static _Bool parse_function_arguments(Context *ctx, int *argc_, Node **argv_) if(done(ctx)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended inside a function argument list"); return 0; } @@ -1999,6 +2062,7 @@ static _Bool parse_function_arguments(Context *ctx, int *argc_, Node **argv_) if(current(ctx) != ',') { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Got unexpected token \"%.*s\" inside function argument list, where either ',' or ')' were expected", ctx->token->length, ctx->src + ctx->token->offset); return 0; } @@ -2021,12 +2085,14 @@ static FuncDeclNode *parse_function_definition(Context *ctx) if(done(ctx)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended where a function definition was expected"); return NULL; } if(current(ctx) != TKWFUN) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Got unexpected token \"%.*s\" where a function definition was expected", ctx->token->length, ctx->src + ctx->token->offset); return NULL; } @@ -2035,6 +2101,7 @@ static FuncDeclNode *parse_function_definition(Context *ctx) if(next(ctx) != TIDENT) { + *ctx->error_offset = ctx->token->offset; if(done(ctx)) Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended where an identifier was expected as function name"); else @@ -2045,6 +2112,7 @@ static FuncDeclNode *parse_function_definition(Context *ctx) char *name_val = copy_token_text(ctx); if(name_val == NULL) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -2059,6 +2127,7 @@ static FuncDeclNode *parse_function_definition(Context *ctx) if(done(ctx)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended before function body"); return NULL; } @@ -2077,6 +2146,7 @@ static FuncDeclNode *parse_function_definition(Context *ctx) FuncExprNode *expr = BPAlloc_Malloc(ctx->alloc, sizeof(FuncExprNode)); if (expr == NULL) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -2092,6 +2162,7 @@ static FuncDeclNode *parse_function_definition(Context *ctx) StringExprNode *name = BPAlloc_Malloc(ctx->alloc, sizeof(StringExprNode)); if (name == NULL) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -2106,6 +2177,7 @@ static FuncDeclNode *parse_function_definition(Context *ctx) func = BPAlloc_Malloc(ctx->alloc, sizeof(FuncDeclNode)); if(func == NULL) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -2126,12 +2198,14 @@ static Node *parse_while_statement(Context *ctx) if(done(ctx)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended where a while statement was expected"); return NULL; } if(current(ctx) != TKWWHILE) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Got unexpected token \"%.*s\" where a while statement was expected", ctx->token->length, ctx->src + ctx->token->offset); return NULL; } @@ -2148,12 +2222,14 @@ static Node *parse_while_statement(Context *ctx) if(done(ctx)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended right after a while loop condition, where a ':' was expected"); return NULL; } if(current(ctx) != ':') { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Got unexpected token \"%.*s\" after a while loop condition, where a ':' was expected", ctx->token->length, ctx->src + ctx->token->offset); return NULL; } @@ -2171,7 +2247,7 @@ static Node *parse_while_statement(Context *ctx) if(whl == NULL) { - // ERROR: No memory. + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } @@ -2193,12 +2269,14 @@ static Node *parse_dowhile_statement(Context *ctx) if(done(ctx)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended where a do-while statement was expected"); return NULL; } if(current(ctx) != TKWDO) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Got unexpected token \"%.*s\" where a do-while statement was expected", ctx->token->length, ctx->src + ctx->token->offset); return NULL; } @@ -2215,12 +2293,14 @@ static Node *parse_dowhile_statement(Context *ctx) if(done(ctx)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended right after a do-while body, where the \"while\" keyword was expected"); return NULL; } if(current(ctx) != TKWWHILE) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Got unexpected token \"%.*s\" after a do-while body, where the \"while\" keyword was expected", ctx->token->length, ctx->src + ctx->token->offset); return NULL; } @@ -2234,12 +2314,14 @@ static Node *parse_dowhile_statement(Context *ctx) if(done(ctx)) { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Source ended right after a do-while condition, where a ';' was expected"); return NULL; } if(current(ctx) != ';') { + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_SYNTAX, "Got unexpected token \"%.*s\" after a do-while conditnion, where a ';' was expected", ctx->token->length, ctx->src + ctx->token->offset); return NULL; } @@ -2252,7 +2334,7 @@ static Node *parse_dowhile_statement(Context *ctx) if(dowhl == NULL) { - // ERROR: No memory. + *ctx->error_offset = ctx->token->offset; Error_Report(ctx->error, ErrorType_INTERNAL, "No memory"); return NULL; } diff --git a/src/lib/compiler/parse.h b/src/lib/compiler/parse.h index 544e3a8..d014f6f 100644 --- a/src/lib/compiler/parse.h +++ b/src/lib/compiler/parse.h @@ -34,5 +34,5 @@ #include "../utils/source.h" #include "../utils/error.h" #include "AST.h" -AST *parse(Source *src, BPAlloc *alloc, Error *error); +AST *parse(Source *src, BPAlloc *alloc, Error *error, int *error_offset); #endif \ No newline at end of file diff --git a/src/lib/noja.c b/src/lib/noja.c deleted file mode 100644 index f598b14..0000000 --- a/src/lib/noja.c +++ /dev/null @@ -1,412 +0,0 @@ -#include -#include -#include "utils/error.h" -#include "utils/source.h" -#include "compiler/compile.h" -#include "assembler/assemble.h" -#include "builtins/basic.h" -#include "runtime/timing.h" -#include "noja.h" - -static void serializeProfilingResults(Runtime *runtime, const char *file) -{ - TimingTable *table = Runtime_GetTimingTable(runtime); - if (table == NULL) - return; - - FILE *stream = fopen(file, "wb"); - if (stream == NULL) { - fprintf(stderr, "Failed to serialize profiling results\n"); - return; - } - - const FunctionExecutionSummary *summary; - size_t count; - - summary = TimingTable_getSummary(table, &count); - assert(summary != NULL); - - for (size_t i = 0; i < count; i++) { - if (summary[i].calls > 0) { - fprintf(stream, "%20s - %s - %ld calls - %.2lfus\n", - summary[i].name, - Source_GetName(summary[i].src), - summary[i].calls, - summary[i].time * 1000000); - } - } - - fclose(stream); - fprintf(stderr, "Wrote profiling result to %s\n", file); -} - - -#include - -Runtime *runtime = NULL; - -static void signalHandler(int signo) -{ - (void) signo; - if (runtime != NULL) - Runtime_Interrupt(runtime); -} - -static _Bool interpret(Executable *exe, bool time, size_t heap) -{ - RuntimeConfig config = Runtime_GetDefaultConfigs(); - config.time = time; - - runtime = Runtime_New(heap, config); - if(runtime == NULL) - { - Error error; - Error_Init(&error); - Error_Report(&error, ErrorType_INTERNAL, "Couldn't initialize runtime"); - Error_Print(&error, ErrorType_UNSPECIFIED); - Error_Free(&error); - return 0; - } - signal(SIGINT, signalHandler); - signal(SIGTERM, signalHandler); - - // We use a [RuntimeError] instead of a simple [Error] - // because the [RuntimeError] makes a snapshot of the - // runtime state when an error is reported. Other than - // this fact they are interchangable. Any function that - // expects a pointer to [Error] can receive a [RuntimeError] - // upcasted to [Error]. - RuntimeError error; - RuntimeError_Init(&error, runtime); // Here we specify the runtime to snapshot in case of failure. - - { - Object *native_bins = Object_NewStaticMap(bins_basic, bins_basic_init, runtime, (Error*) &error); - if(native_bins == NULL) - { - assert(error.base.occurred == 1); - Error_Print((Error*) &error, ErrorType_RUNTIME); - RuntimeError_Free(&error); - Runtime_Free(runtime); - return 0; - } - - // Just to execute the prelude - Runtime_SetBuiltins(runtime, native_bins); - - extern char start_noja[]; - Source *prelude = Source_FromString("", start_noja, -1, (Error*) &error); - if (prelude == NULL) { - assert(error.base.occurred == 1); - Error_Print((Error*) &error, ErrorType_RUNTIME); - RuntimeError_Free(&error); - Runtime_Free(runtime); - return 0; - } - - Executable *prelude_exe = compile(prelude, (Error*) &error); - if(prelude_exe == NULL) { - Error_Print((Error*) &error, ErrorType_RUNTIME); - RuntimeError_Free(&error); - Runtime_Free(runtime); - Source_Free(prelude); - return 0; - } - - Object *rets[8]; - int retc = run(runtime, (Error*) &error, prelude_exe, 0, NULL, NULL, 0, rets); - if(retc < 0) { - Error_Print((Error*) &error, ErrorType_RUNTIME); - RuntimeError_Free(&error); - Runtime_Free(runtime); - Source_Free(prelude); - Executable_Free(prelude_exe); - return 0; - } - Object *noja_bins = rets[0]; - - // Need to remake the native built-ins because - // running the script invalidated the previous - // pointer. - native_bins = Object_NewStaticMap(bins_basic, bins_basic_init, runtime, (Error*) &error); - if(native_bins == NULL) - { - assert(error.base.occurred == 1); - Error_Print((Error*) &error, ErrorType_RUNTIME); - RuntimeError_Free(&error); - Runtime_Free(runtime); - Source_Free(prelude); - Executable_Free(prelude_exe); - return 0; - } - - Object *all_bins = Object_NewClosure(native_bins, noja_bins, Runtime_GetHeap(runtime), (Error*) &error); - if (all_bins == NULL) { - Error_Print((Error*) &error, ErrorType_RUNTIME); - RuntimeError_Free(&error); - Runtime_Free(runtime); - Source_Free(prelude); - Executable_Free(prelude_exe); - return 0; - } - - Runtime_SetBuiltins(runtime, all_bins); - - Source_Free(prelude); - Executable_Free(prelude_exe); - } - - Object *rets[8]; - int retc = run(runtime, (Error*) &error, exe, 0, NULL, NULL, 0, rets); - - // NOTE: The pointer to the builtins object is invalidated - // now because it may be moved by the garbage collector. - - if(retc < 0) - { - Error_Print((Error*) &error, ErrorType_RUNTIME); - - if(error.snapshot == NULL) - fprintf(stderr, "No snapshot available.\n"); - else - Snapshot_Print(error.snapshot, stderr); - - RuntimeError_Free(&error); - } - - serializeProfilingResults(runtime, "profiling-results.txt"); - - Runtime_Free(runtime); - return retc > -1; -} - -static Executable *compile_source_and_print_error_on_failure(Source *src) -{ - Error error; - Error_Init(&error); - Executable *exe = compile(src, &error); - if(exe == NULL) { - Error_Print(&error, ErrorType_UNSPECIFIED); - Error_Free(&error); - return NULL; - } - Error_Free(&error); - return exe; -} - -static _Bool disassemble(Source *src) -{ - Executable *exe = compile_source_and_print_error_on_failure(src); - if(exe == NULL) return 0; - Executable_Dump(exe, stdout); - Executable_Free(exe); - return 1; -} - -static _Bool interpret_file(const char *file, bool time, size_t heap) -{ - Error error; - Error_Init(&error); - - Source *src = Source_FromFile(file, &error); - if(src == NULL) - { - assert(error.occurred == 1); - Error_Print(&error, ErrorType_UNSPECIFIED); - Error_Free(&error); - return 0; - } - - Executable *exe = compile_source_and_print_error_on_failure(src); - if(exe == NULL) { - Source_Free(src); - return 0; - } - - _Bool r = interpret(exe, time, heap); - - Executable_Free(exe); - Source_Free(src); - return r; -} - -static _Bool interpret_code(const char *code, bool time, size_t heap) -{ - Error error; - Error_Init(&error); - - Source *src = Source_FromString(NULL, code, -1, &error); - - if(src == NULL) - { - assert(error.occurred); - Error_Print(&error, ErrorType_UNSPECIFIED); - Error_Free(&error); - return 0; - } - - Executable *exe = compile_source_and_print_error_on_failure(src); - if(exe == NULL) { - Source_Free(src); - return 0; - } - - _Bool r = interpret(exe, time, heap); - - Executable_Free(exe); - Source_Free(src); - return r; -} - -static _Bool interpret_asm_file(const char *file, bool time, size_t heap) -{ - Error error; - Error_Init(&error); - - Source *src = Source_FromFile(file, &error); - - if(src == NULL) - { - assert(error.occurred == 1); - Error_Print(&error, ErrorType_UNSPECIFIED); - Error_Free(&error); - return 0; - } - - Executable *exe = assemble(src, &error); - if(exe == NULL) { - Error_Print(&error, ErrorType_UNSPECIFIED); - Source_Free(src); - Error_Free(&error); - return 0; - } - - _Bool r = interpret(exe, time, heap); - - Executable_Free(exe); - Source_Free(src); - Error_Free(&error); - return r; -} - -static _Bool interpret_asm_code(const char *code, bool time, size_t heap) -{ - Error error; - Error_Init(&error); - - Source *src = Source_FromString(NULL, code, -1, &error); - - if(src == NULL) - { - assert(error.occurred); - Error_Print(&error, ErrorType_UNSPECIFIED); - Error_Free(&error); - return 0; - } - - Executable *exe = assemble(src, &error); - if(exe == NULL) { - Error_Print(&error, ErrorType_UNSPECIFIED); - Source_Free(src); - Error_Free(&error); - return 0; - } - - _Bool r = interpret(exe, time, heap); - - Executable_Free(exe); - Source_Free(src); - Error_Free(&error); - return r; -} - -static _Bool disassemble_file(const char *file) -{ - Error error; - Error_Init(&error); - - Source *src = Source_FromFile(file, &error); - - if(src == NULL) - { - assert(error.occurred == 1); - Error_Print(&error, ErrorType_UNSPECIFIED); - Error_Free(&error); - return 0; - } - - _Bool r = disassemble(src); - - Source_Free(src); - return r; -} - -static _Bool disassemble_code(const char *code) -{ - Error error; - Error_Init(&error); - - Source *src = Source_FromString(NULL, code, -1, &error); - - if(src == NULL) - { - assert(error.occurred); - Error_Print(&error, ErrorType_UNSPECIFIED); - Error_Free(&error); - return 0; - } - - _Bool r = disassemble(src); - - Source_Free(src); - return r; -} - -_Bool NOJA_runString(const char *str, size_t heap) -{ - return interpret_code(str, false, heap); -} - -_Bool NOJA_runFile(const char *file, size_t heap) -{ - return interpret_file(file, false, heap); -} - -_Bool NOJA_dumpFileBytecode(const char *file) -{ - return disassemble_file(file); -} - -_Bool NOJA_dumpStringBytecode(const char *str) -{ - return disassemble_code(str); -} - -_Bool NOJA_runAssemblyFile(const char *file, size_t heap) -{ - return interpret_asm_file(file, false, heap); -} - -_Bool NOJA_runAssemblyString(const char *str, size_t heap) -{ - return interpret_asm_code(str, false, heap); -} - -_Bool NOJA_profileString(const char *str, size_t heap) -{ - return interpret_code(str, true, heap); -} - -_Bool NOJA_profileFile(const char *file, size_t heap) -{ - return interpret_file(file, true, heap); -} - -_Bool NOJA_profileAssemblyFile(const char *file, size_t heap) -{ - return interpret_asm_file(file, true, heap); -} - -_Bool NOJA_profileAssemblyString(const char *str, size_t heap) -{ - return interpret_asm_code(str, true, heap); -} diff --git a/src/lib/noja.h b/src/lib/noja.h deleted file mode 100644 index ad99f90..0000000 --- a/src/lib/noja.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef NOJA_H -#define NOJA_H -_Bool NOJA_runFile (const char *file, size_t heap); -_Bool NOJA_runString(const char *str, size_t heap); -_Bool NOJA_runAssemblyFile(const char *file, size_t heap); -_Bool NOJA_runAssemblyString(const char *str, size_t heap); -_Bool NOJA_dumpFileBytecode(const char *file); -_Bool NOJA_dumpStringBytecode(const char *str); -_Bool NOJA_profileFile(const char *file, size_t heap); -_Bool NOJA_profileString(const char *str, size_t heap); -_Bool NOJA_profileAssemblyFile(const char *file, size_t heap); -_Bool NOJA_profileAssemblyString(const char *str, size_t heap); -#endif /* NOJA_H */ diff --git a/src/lib/runtime/builtins_api.c b/src/lib/runtime/builtins_api.c new file mode 100644 index 0000000..a8cdad7 --- /dev/null +++ b/src/lib/runtime/builtins_api.c @@ -0,0 +1,54 @@ +#include "run.h" +#include "builtins_api.h" +#include "../builtins/basic.h" + +bool Runtime_plugBuiltinsFromStaticMap(Runtime *runtime, StaticMapSlot *bin_table, void (*bin_table_constructor)(StaticMapSlot*), Error *error) +{ + Object *object = Object_NewStaticMap(bin_table, bin_table_constructor, runtime, error); + if (object == NULL) + return false; + return Runtime_plugBuiltins(runtime, object, error); +} + +bool Runtime_plugBuiltinsFromSource(Runtime *runtime, Source *source, Error *error) +{ + Object *rets[8]; + int retc = runSource(runtime, source, rets, error); + if(retc < 0) + return false; + if (retc == 0) + return true; + return Runtime_plugBuiltins(runtime, rets[0], error); +} + +bool Runtime_plugBuiltinsFromFile(Runtime *runtime, const char *file, Error *error) +{ + Source *source = Source_FromFile(file, error); + if (source == NULL) + return false; + + bool result = Runtime_plugBuiltinsFromSource(runtime, source, error); + + Source_Free(source); + return result; +} + +bool Runtime_plugBuiltinsFromString(Runtime *runtime, const char *string, Error *error) +{ + Source *source = Source_FromString("", string, -1, error); + if (source == NULL) + return false; + + bool result = Runtime_plugBuiltinsFromSource(runtime, source, error); + + Source_Free(source); + return result; +} + +bool Runtime_plugDefaultBuiltins(Runtime *runtime, Error *error) +{ + extern char start_noja[]; + return Runtime_plugBuiltinsFromStaticMap(runtime, bins_basic, bins_basic_init, error) + && Runtime_plugBuiltinsFromString(runtime, start_noja, error) + && Runtime_plugBuiltinsFromString(runtime, "XXX=999;", error); +} \ No newline at end of file diff --git a/src/lib/runtime/builtins_api.h b/src/lib/runtime/builtins_api.h new file mode 100644 index 0000000..6d42237 --- /dev/null +++ b/src/lib/runtime/builtins_api.h @@ -0,0 +1,6 @@ +#include "runtime.h" +bool Runtime_plugDefaultBuiltins(Runtime *runtime, Error *error); +bool Runtime_plugBuiltinsFromString(Runtime *runtime, const char *string, Error *error); +bool Runtime_plugBuiltinsFromFile(Runtime *runtime, const char *file, Error *error); +bool Runtime_plugBuiltinsFromStaticMap(Runtime *runtime, StaticMapSlot *bin_table, void (*bin_table_constructor)(StaticMapSlot*), Error *error); +bool Runtime_plugBuiltinsFromSource(Runtime *runtime, Source *source, Error *error); diff --git a/src/lib/runtime/o_func.c b/src/lib/runtime/o_func.c deleted file mode 100644 index 6b60062..0000000 --- a/src/lib/runtime/o_func.c +++ /dev/null @@ -1,220 +0,0 @@ - -/* +--------------------------------------------------------------------------+ -** | _ _ _ | -** | | \ | | (_) | -** | | \| | ___ _ __ _ | -** | | . ` |/ _ \| |/ _` | | -** | | |\ | (_) | | (_| | | -** | |_| \_|\___/| |\__,_| | -** | _/ | | -** | |__/ | -** +--------------------------------------------------------------------------+ -** | Copyright (c) 2022 Francesco Cozzuto | -** +--------------------------------------------------------------------------+ -** | This file is part of The Noja Interpreter. | -** | | -** | The Noja Interpreter is free software: you can redistribute it and/or | -** | modify it under the terms of the GNU General Public License as published | -** | by the Free Software Foundation, either version 3 of the License, or (at | -** | your option) any later version. | -** | | -** | The Noja Interpreter is distributed in the hope that it will be useful, | -** | but WITHOUT ANY WARRANTY; without even the implied warranty of | -** | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General | -** | Public License for more details. | -** | | -** | You should have received a copy of the GNU General Public License along | -** | with The Noja Interpreter. If not, see . | -** +--------------------------------------------------------------------------+ -*/ - -#include -#include -#include -#include "../utils/defs.h" -#include "../objects/objects.h" -#include "timing.h" -#include "runtime.h" - -typedef struct { - Object base; - const char *name; - Runtime *runtime; - Executable *exe; - int index, argc; - Object *closure; - TimingID timing_id; -} FunctionObject; - -static _Bool free_(Object *self, Error *error) -{ - (void) error; - - FunctionObject *func = (FunctionObject*) self; - Executable_Free(func->exe); - return 1; -} - -static void walk(Object *self, void (*callback)(Object **referer, void *userp), void *userp) -{ - FunctionObject *func = (FunctionObject*) self; - callback(&func->closure, userp); -} - -static int call(Object *self, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Heap *heap, Error *error) -{ - assert(self != NULL && heap != NULL && error != NULL); - - FunctionObject *func = (FunctionObject*) self; - - assert(func->exe != NULL); - assert(func->argc >= 0); - assert(func->index >= 0); - - // Make sure the right amount of arguments is provided. - - Object **argv2; - - int expected_argc = func->argc; - - if(expected_argc < (int) argc) - { - // Nothing to be done. By using - // the right argc the additional - // arguments are ignored implicitly. - argv2 = argv; - } - else if(expected_argc > (int) argc) - { - // Some arguments are missing. - argv2 = malloc(sizeof(Object*) * expected_argc); - - if(argv2 == NULL) - { - Error_Report(error, ErrorType_INTERNAL, "No memory"); - return -1; - } - - // Copy the provided arguments. - for(int i = 0; i < (int) argc; i += 1) - argv2[i] = argv[i]; - - // Set the unspecified arguments to none. - for(int i = argc; i < expected_argc; i += 1) - { - argv2[i] = Object_NewNone(heap, error); - - if(argv2[i] == NULL) - return -1; - } - } - else - // The right amount of arguments was provided. - argv2 = argv; - - clock_t begin; - TimingID timing_id; - TimingTable *timing_table = Runtime_GetTimingTable(func->runtime); - if (timing_table != NULL) { - begin = clock(); - - // Need to save the object's member - // before the run function since it - // may trigger a GC cycle invalidating - // the object pointer. - timing_id = func->timing_id; - } - - int retc = run(func->runtime, error, func->exe, func->index, func->closure, argv2, expected_argc, rets); - - if (timing_table != NULL) { - double time = (double) (clock() - begin) / CLOCKS_PER_SEC; - TimingTable_sumCallTime(timing_table, timing_id, time); - } - - // NOTE: Every object reference is invalidated from here. - - if(argv2 != argv) - free(argv2); - - return retc; -} - -static TypeObject t_func = { - .base = (Object) { .type = &t_type, .flags = Object_STATIC }, - .name = "function", - .size = sizeof (FunctionObject), - .call = call, - .walk = walk, - .free = free_, -}; - -/* Symbol: Object_FromNojaFunction - * - * Creates an object from a noja executable structure. - * - * Args: - * - runtime: The reference to an instanciated Runtime. - * - * - exe: A noja executable. - * - * - index: The index of the first bytecode instruction - * of the noja function within the executable. - * - * - argc: The number of arguments the function expects. - * It must be positive (unlike [Object_FromNativeFunction], - * where -1 means variadic). - * - * - closure: An object containing variables that will be - * accessible from the noja function other than - * the ones that will be defined inside it. - * - * - heap: The heap that will be used to allocate the object. - * It can't be NULL. - * - * - error: Output parameter where error information is stored. - * It can't be NULL. - * - * Returns: - * The newly created object. If an error occurred, NULL is returned - * and information about the error is stored in the [error] argument. - */ -Object *Object_FromNojaFunction(Runtime *runtime, const char *name, Executable *exe, int index, int argc, Object *closure, Heap *heap, Error *error) -{ - assert(runtime != NULL); - assert(exe != NULL); - assert(index >= 0); - assert(argc >= 0); - assert(heap != NULL); - assert(error != NULL); - - FunctionObject *func = (FunctionObject*) Heap_Malloc(heap, &t_func, error); - - if(func == NULL) - return NULL; - - Executable *exe_copy = Executable_Copy(exe); - - if(exe_copy == NULL) - { - Error_Report(error, ErrorType_INTERNAL, "Failed to copy executable"); - return NULL; - } - - func->runtime = runtime; - func->name = name; // Should this be copied? - func->exe = exe_copy; - func->index = index; - func->argc = argc; - func->closure = closure; - - TimingTable *table = Runtime_GetTimingTable(runtime); - if (table != NULL) { - #warning "TODO: Calculate line number" - size_t line = 0; - Source *src = Executable_GetSource(exe); - func->timing_id = TimingTable_newEntry(table, src, line, name); - } - - return (Object*) func; -} \ No newline at end of file diff --git a/src/lib/runtime/o_nfunc.c b/src/lib/runtime/o_nfunc.c deleted file mode 100644 index f5dc8cf..0000000 --- a/src/lib/runtime/o_nfunc.c +++ /dev/null @@ -1,173 +0,0 @@ - -/* +--------------------------------------------------------------------------+ -** | _ _ _ | -** | | \ | | (_) | -** | | \| | ___ _ __ _ | -** | | . ` |/ _ \| |/ _` | | -** | | |\ | (_) | | (_| | | -** | |_| \_|\___/| |\__,_| | -** | _/ | | -** | |__/ | -** +--------------------------------------------------------------------------+ -** | Copyright (c) 2022 Francesco Cozzuto | -** +--------------------------------------------------------------------------+ -** | This file is part of The Noja Interpreter. | -** | | -** | The Noja Interpreter is free software: you can redistribute it and/or | -** | modify it under the terms of the GNU General Public License as published | -** | by the Free Software Foundation, either version 3 of the License, or (at | -** | your option) any later version. | -** | | -** | The Noja Interpreter is distributed in the hope that it will be useful, | -** | but WITHOUT ANY WARRANTY; without even the implied warranty of | -** | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General | -** | Public License for more details. | -** | | -** | You should have received a copy of the GNU General Public License along | -** | with The Noja Interpreter. If not, see . | -** +--------------------------------------------------------------------------+ -*/ - -/* - * WHAT IS THIS FILE? - * This file implements an object that makes it possible - * to call native functions from within noja code. - * - */ -#include -#include -#include "../utils/defs.h" -#include "../objects/objects.h" -#include "runtime.h" - -typedef struct { - Object base; - Runtime *runtime; - int (*callback)(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[MAX_RETS], Error *error); - int argc; -} NativeFunctionObject; - -static int call(Object *self, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Heap *heap, Error *error) -{ - assert(self != NULL); - assert(heap != NULL); - assert(error != NULL); - - NativeFunctionObject *func = (NativeFunctionObject*) self; - - // If the function isn't variadic, make sure - // the right amount of arguments is provided. - - Object **argv2; - int argc2; - - int expected_argc = func->argc; - - if(expected_argc < 0 || expected_argc == (int) argc) - { - // The function is variadic or the right - // amount of arguments was provided. - argv2 = argv; - argc2 = argc; - } - else if(expected_argc < (int) argc) - { - // Nothing to be done. By using - // the right argc the additional - // arguments are ignored implicitly. - argv2 = argv; - argc2 = expected_argc; - } - else if(expected_argc > (int) argc) - { - // Some arguments are missing. - argv2 = malloc(sizeof(Object*) * expected_argc); - argc2 = expected_argc; - - if(argv2 == NULL) - { - Error_Report(error, 1, "No memory"); - return -1; - } - - // Copy the provided arguments. - for(int i = 0; i < (int) argc; i += 1) - argv2[i] = argv[i]; - - // Set the unspecified arguments to none. - for(int i = argc; i < expected_argc; i += 1) - { - argv2[i] = Object_NewNone(heap, error); - - if(argv2[i] == NULL) - { - free(argv2); - return -1; - } - } - } else { - UNREACHABLE; - argv2 = NULL; - argc2 = -1; - } - - assert(func->callback != NULL); - int retc = func->callback(func->runtime, argv2, argc2, rets, error); - - // NOTE: Since the callback may have executed some bytecode, a GC - // cycle may have been triggered, therefore we must assume - // every object reference that was locally saved is invalidated - // from here (the returned object is good tho). - - if(argv2 != argv) - free(argv2); - - return retc; -} - -static TypeObject t_nfunc = { - .base = (Object) { .type = &t_type, .flags = Object_STATIC }, - .name = "native function", - .size = sizeof (NativeFunctionObject), - .call = call, -}; - -/* Symbol: Object_FromNativeFunction - * - * Creates an object from a function pointer. - * - * Args: - * - runtime: The reference to an instanciated Runtime. This must be - * provided so that the callback can also access it. - * - * - callback: The native function to be executed when this object - * is called. - * - * - argc: The number of arguments the function expects. If -1 is - * provided, then the function is considered to be variadic. - * - * - heap: The heap that will be used to allocate the object. - * It can't be NULL. - * - * - error: Output parameter where error information is stored. - * It can't be NULL. - * - * Returns: - * The newly created object. If an error occurred, NULL is returned - * and information about the error is stored in the [error] argument. - */ -Object *Object_FromNativeFunction(Runtime *runtime, int (*callback)(Runtime*, Object**, unsigned int, Object*[static MAX_RETS], Error*), int argc, Heap *heap, Error *error) -{ - assert(callback != NULL); - - NativeFunctionObject *func = (NativeFunctionObject*) Heap_Malloc(heap, &t_nfunc, error); - - if(func == NULL) - return NULL; - - func->runtime = runtime; - func->callback = callback; - func->argc = argc; - - return (Object*) func; -} \ No newline at end of file diff --git a/src/lib/runtime/path.c b/src/lib/runtime/path.c new file mode 100644 index 0000000..d1cb9ee --- /dev/null +++ b/src/lib/runtime/path.c @@ -0,0 +1,73 @@ +#include +#include +#include "path.h" +#include "../utils/defs.h" +#include "../utils/path.h" + +// Returns the length written in buff (not considering the zero byte) +size_t Runtime_GetCurrentScriptFolder(Runtime *runtime, char *buff, size_t buffsize) +{ + const char *path = Runtime_GetCurrentScriptAbsolutePath(runtime); + if(path == NULL) { + if(getcwd(buff, buffsize) == NULL) + return 0; + size_t cwdlen = strlen(buff); + if (buff[cwdlen-1] == '/') + return cwdlen; + else { + if (cwdlen+1 >= buffsize) + return 0; + buff[cwdlen] = '/'; + return cwdlen+1; + } + } + + // This following block is a custom implementation + // of [dirname], which doesn't write into the input + // string and is way buggier. It will for sure give + // problems in the future!! + size_t dir_len; + { + // This is buggy code!! + size_t path_len = strlen(path); + ASSERT(path_len > 0); // Not empty + ASSERT(Path_IsAbsolute(path)); // Is absolute + ASSERT(path[path_len-1] != '/'); // Doesn't end with a slash. + + size_t popped = 0; + while(path[path_len-1-popped] != '/') + popped += 1; + + ASSERT(path_len > popped); + + dir_len = path_len - popped; + + ASSERT(dir_len < path_len); + ASSERT(path[dir_len-1] == '/'); + } + + if(dir_len >= buffsize) + return 0; + + memcpy(buff, path, dir_len); + buff[dir_len] = '\0'; + return dir_len; +} + +const char *Runtime_GetCurrentScriptAbsolutePath(Runtime *runtime) +{ + Executable *exe = Runtime_GetMostRecentExecutable(runtime); + if (exe == NULL) + return NULL; + + Source *src = Executable_GetSource(exe); + if(src == NULL) + return NULL; + + const char *path = Source_GetAbsolutePath(src); + if(path == NULL) + return NULL; + + ASSERT(path[0] != '\0'); + return path; +} diff --git a/src/lib/runtime/path.h b/src/lib/runtime/path.h new file mode 100644 index 0000000..4e0de34 --- /dev/null +++ b/src/lib/runtime/path.h @@ -0,0 +1,4 @@ +#include +#include "runtime.h" +const char *Runtime_GetCurrentScriptAbsolutePath(Runtime *runtime); +size_t Runtime_GetCurrentScriptFolder(Runtime *runtime, char *buff, size_t buffsize); diff --git a/src/lib/runtime/run.c b/src/lib/runtime/run.c new file mode 100644 index 0000000..2adc04e --- /dev/null +++ b/src/lib/runtime/run.c @@ -0,0 +1,1280 @@ +#include +#include +#include +#include "runtime.h" +#include "../utils/defs.h" +#include "../utils/path.h" +#include "../compiler/compile.h" +#include "../assembler/assemble.h" + +static int runExecutableAtIndex(Runtime *runtime, Error *error, + Executable *exe, int index, + Object *closure, + Object *rets[static MAX_RETS], + Object *argv[], int argc); + +typedef struct { + Object base; + const char *name; + Runtime *runtime; + Executable *exe; + int index, argc; + Object *closure; + TimingID timing_id; +} FunctionObject; + +static _Bool func_free(Object *self, Error *error) +{ + (void) error; + + FunctionObject *func = (FunctionObject*) self; + Executable_Free(func->exe); + return 1; +} + +static void func_walk(Object *self, void (*callback)(Object **referer, void *userp), void *userp) +{ + FunctionObject *func = (FunctionObject*) self; + callback(&func->closure, userp); +} + +static int func_call(Object *self, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Heap *heap, Error *error) +{ + ASSERT(self != NULL && heap != NULL && error != NULL); + + FunctionObject *func = (FunctionObject*) self; + + ASSERT(func->exe != NULL); + ASSERT(func->argc >= 0); + ASSERT(func->index >= 0); + + // Make sure the right amount of arguments is provided. + + Object **argv2; + + int expected_argc = func->argc; + + if(expected_argc < (int) argc) + { + // Nothing to be done. By using + // the right argc the additional + // arguments are ignored implicitly. + argv2 = argv; + } + else if(expected_argc > (int) argc) + { + // Some arguments are missing. + argv2 = malloc(sizeof(Object*) * expected_argc); + + if(argv2 == NULL) + { + Error_Report(error, ErrorType_INTERNAL, "No memory"); + return -1; + } + + // Copy the provided arguments. + for(int i = 0; i < (int) argc; i += 1) + argv2[i] = argv[i]; + + // Set the unspecified arguments to none. + for(int i = argc; i < expected_argc; i += 1) + { + argv2[i] = Object_NewNone(heap, error); + + if(argv2[i] == NULL) + return -1; + } + } + else + // The right amount of arguments was provided. + argv2 = argv; + + clock_t begin; + TimingID timing_id; + TimingTable *timing_table = Runtime_GetTimingTable(func->runtime); + if (timing_table != NULL) { + begin = clock(); + + // Need to save the object's member + // before the run function since it + // may trigger a GC cycle invalidating + // the object pointer. + timing_id = func->timing_id; + } + + int retc = runExecutableAtIndex(func->runtime, error, func->exe, func->index, func->closure, rets, argv2, expected_argc); + + if (timing_table != NULL) { + double time = (double) (clock() - begin) / CLOCKS_PER_SEC; + TimingTable_sumCallTime(timing_table, timing_id, time); + } + + // NOTE: Every object reference is invalidated from here. + + if(argv2 != argv) + free(argv2); + + return retc; +} + +static TypeObject t_func = { + .base = (Object) { .type = &t_type, .flags = Object_STATIC }, + .name = "function", + .size = sizeof (FunctionObject), + .call = func_call, + .walk = func_walk, + .free = func_free, +}; + +/* Symbol: Object_FromNojaFunction + * + * Creates an object from a noja executable structure. + * + * Args: + * - runtime: The reference to an instanciated Runtime. + * + * - exe: A noja executable. + * + * - index: The index of the first bytecode instruction + * of the noja function within the executable. + * + * - argc: The number of arguments the function expects. + * It must be positive (unlike [Object_FromNativeFunction], + * where -1 means variadic). + * + * - closure: An object containing variables that will be + * accessible from the noja function other than + * the ones that will be defined inside it. + * + * - heap: The heap that will be used to allocate the object. + * It can't be NULL. + * + * - error: Output parameter where error information is stored. + * It can't be NULL. + * + * Returns: + * The newly created object. If an error occurred, NULL is returned + * and information about the error is stored in the [error] argument. + */ +Object *Object_FromNojaFunction(Runtime *runtime, const char *name, Executable *exe, int index, int argc, Object *closure, Heap *heap, Error *error) +{ + ASSERT(runtime != NULL); + ASSERT(exe != NULL); + ASSERT(index >= 0); + ASSERT(argc >= 0); + ASSERT(heap != NULL); + ASSERT(error != NULL); + + FunctionObject *func = (FunctionObject*) Heap_Malloc(heap, &t_func, error); + + if(func == NULL) + return NULL; + + Executable *exe_copy = Executable_Copy(exe); + + if(exe_copy == NULL) + { + Error_Report(error, ErrorType_INTERNAL, "Failed to copy executable"); + return NULL; + } + + func->runtime = runtime; + func->name = name; // Should this be copied? + func->exe = exe_copy; + func->index = index; + func->argc = argc; + func->closure = closure; + + TimingTable *table = Runtime_GetTimingTable(runtime); + if (table != NULL) { + #warning "TODO: Calculate line number" + size_t line = 0; + Source *src = Executable_GetSource(exe); + func->timing_id = TimingTable_newEntry(table, src, line, name); + } + + return (Object*) func; +} + + +typedef struct { + Object base; + Runtime *runtime; + int (*callback)(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[MAX_RETS], Error *error); + int argc; +} NativeFunctionObject; + +static int native_func_call(Object *self, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Heap *heap, Error *error) +{ + ASSERT(self != NULL); + ASSERT(heap != NULL); + ASSERT(error != NULL); + + NativeFunctionObject *func = (NativeFunctionObject*) self; + + // If the function isn't variadic, make sure + // the right amount of arguments is provided. + + Object **argv2; + int argc2; + + int expected_argc = func->argc; + + if(expected_argc < 0 || expected_argc == (int) argc) + { + // The function is variadic or the right + // amount of arguments was provided. + argv2 = argv; + argc2 = argc; + } + else if(expected_argc < (int) argc) + { + // Nothing to be done. By using + // the right argc the additional + // arguments are ignored implicitly. + argv2 = argv; + argc2 = expected_argc; + } + else if(expected_argc > (int) argc) + { + // Some arguments are missing. + argv2 = malloc(sizeof(Object*) * expected_argc); + argc2 = expected_argc; + + if(argv2 == NULL) + { + Error_Report(error, 1, "No memory"); + return -1; + } + + // Copy the provided arguments. + for(int i = 0; i < (int) argc; i += 1) + argv2[i] = argv[i]; + + // Set the unspecified arguments to none. + for(int i = argc; i < expected_argc; i += 1) + { + argv2[i] = Object_NewNone(heap, error); + + if(argv2[i] == NULL) + { + free(argv2); + return -1; + } + } + } else { + UNREACHABLE; + argv2 = NULL; + argc2 = -1; + } + + if (!Runtime_PushNativeFrame(func->runtime, error)) + return -1; + + ASSERT(func->callback != NULL); + int retc = func->callback(func->runtime, argv2, argc2, rets, error); + + // NOTE: Since the callback may have executed some bytecode, a GC + // cycle may have been triggered, therefore we must assume + // every object reference that was locally saved is invalidated + // from here (the returned object is good tho). + + if(argv2 != argv) + free(argv2); + + if (retc >= 0 && !Runtime_PopFrame(func->runtime)) + return -1; + + return retc; +} + +static TypeObject t_nfunc = { + .base = (Object) { .type = &t_type, .flags = Object_STATIC }, + .name = "native function", + .size = sizeof (NativeFunctionObject), + .call = native_func_call, +}; + +/* Symbol: Object_FromNativeFunction + * + * Creates an object from a function pointer. + * + * Args: + * - runtime: The reference to an instanciated Runtime. This must be + * provided so that the callback can also access it. + * + * - callback: The native function to be executed when this object + * is called. + * + * - argc: The number of arguments the function expects. If -1 is + * provided, then the function is considered to be variadic. + * + * - heap: The heap that will be used to allocate the object. + * It can't be NULL. + * + * - error: Output parameter where error information is stored. + * It can't be NULL. + * + * Returns: + * The newly created object. If an error occurred, NULL is returned + * and information about the error is stored in the [error] argument. + */ +Object *Object_FromNativeFunction(Runtime *runtime, int (*callback)(Runtime*, Object**, unsigned int, Object*[static MAX_RETS], Error*), int argc, Heap *heap, Error *error) +{ + ASSERT(callback != NULL); + + NativeFunctionObject *func = (NativeFunctionObject*) Heap_Malloc(heap, &t_nfunc, error); + + if(func == NULL) + return NULL; + + func->runtime = runtime; + func->callback = callback; + func->argc = argc; + + return (Object*) func; +} + +static Object *do_math_op(Object *lop, Object *rop, Opcode opcode, Heap *heap, Error *error) +{ + ASSERT(lop != NULL); + ASSERT(rop != NULL); + + #define APPLY(x, y, z, id) \ + switch(opcode) \ + { \ + case OPCODE_ADD: (z) = (x) + (y); break; \ + case OPCODE_SUB: (z) = (x) - (y); break; \ + case OPCODE_MUL: (z) = (x) * (y); break; \ + case OPCODE_DIV: \ + if((y) == 0) \ + { \ + Error_Report(error, ErrorType_RUNTIME, "Division by zero"); \ + return NULL; \ + } \ + (z) = (x) / (y); \ + break; \ + default: UNREACHABLE; break; \ + } + + Object *res; + + if(Object_IsInt(lop)) + { + long long int raw_lop = Object_GetInt(lop); + + if(Object_IsInt(rop)) + { + // int + int + long long int raw_rop = Object_GetInt(rop); + long long int raw_res = 0; + APPLY(raw_lop, raw_rop, raw_res, id) + res = Object_FromInt(raw_res, heap, error); + } + else if(Object_IsFloat(rop)) + { + // int + float + double raw_rop = Object_GetFloat(rop); + double raw_res = 0; + APPLY((double) raw_lop, raw_rop, raw_res, id) + res = Object_FromFloat(raw_res, heap, error); + } + else + { + Error_Report(error, ErrorType_RUNTIME, "Arithmetic operation on a non-numeric object"); + return NULL; + } + } + else if(Object_IsFloat(lop)) + { + double raw_lop = Object_GetFloat(lop); + + if(Object_IsInt(rop)) + { + // float + int + long long int raw_rop = Object_GetInt(rop); + double raw_res = 0; + APPLY(raw_lop, (double) raw_rop, raw_res, id) + res = Object_FromFloat(raw_res, heap, error); + } + else if(Object_IsFloat(rop)) + { + // float + float + double raw_rop = Object_GetFloat(rop); + double raw_res = 0; + APPLY(raw_lop, raw_rop, raw_res, id) + res = Object_FromFloat(raw_res, heap, error); + } + else + { + Error_Report(error, ErrorType_RUNTIME, "Arithmetic operation on a non-numeric object"); + return NULL; + } + } + else + { + Error_Report(error, ErrorType_RUNTIME, "Arithmetic operation on a non-numeric object"); + return NULL; + } + + #undef APPLY + + return res; +} + +static Object *do_relational_op(Object *lop, Object *rop, Opcode opcode, Heap *heap, Error *error) +{ + ASSERT(lop != NULL); + ASSERT(rop != NULL); + + #define APPLY(x, y, z, id) \ + switch(opcode) \ + { \ + case OPCODE_LSS: (z) = (x) < (y); break; \ + case OPCODE_GRT: (z) = (x) > (y); break; \ + case OPCODE_LEQ: (z) = (x) <= (y); break; \ + case OPCODE_GEQ: (z) = (x) >= (y); break; \ + default: UNREACHABLE; break; \ + } + + _Bool res = 0; + + if(Object_IsInt(lop)) + { + long long int raw_lop = Object_GetInt(lop); + if(Object_IsInt(rop)) + { + // int + int + long long int raw_rop = Object_GetInt(rop); + APPLY(raw_lop, raw_rop, res, id) + } + else if(Object_IsFloat(rop)) + { + // int + float + double raw_rop = Object_GetFloat(rop); + APPLY((double) raw_lop, raw_rop, res, id) + } + else + { + Error_Report(error, ErrorType_RUNTIME, "Relational operation on a non-numeric object"); + return NULL; + } + } + else if(Object_IsFloat(lop)) + { + double raw_lop = Object_GetFloat(lop); + if(Object_IsInt(rop)) + { + // float + int + long long int raw_rop = Object_GetInt(rop); + APPLY(raw_lop, (double) raw_rop, res, id) + } + else if(Object_IsFloat(rop)) + { + // float + float + double raw_rop = Object_GetFloat(rop); + APPLY(raw_lop, raw_rop, res, id) + } + else + { + Error_Report(error, ErrorType_RUNTIME, "Relational operation on a non-numeric object"); + return NULL; + } + } + else + { + Error_Report(error, ErrorType_RUNTIME, "Relational operation on a non-numeric object"); + return NULL; + } + + #undef APPLY + + return Object_FromBool(res, heap, error); +} + +static _Bool runInstruction(Runtime *runtime, Error *error) +{ + ASSERT(runtime != NULL); + ASSERT(error->occurred == 0); + + Stack *stack = Runtime_GetStack(runtime); + Heap *heap = Runtime_GetHeap(runtime); + Executable *exe = Runtime_GetCurrentExecutable(runtime); + ASSERT(exe != NULL); + int index = Runtime_GetCurrentIndex(runtime); + Opcode opcode; + Operand ops[3]; + int opc = sizeof(ops) / sizeof(ops[0]); + + if(!Executable_Fetch(exe, index, &opcode, ops, &opc)) + { + Error_Report(error, ErrorType_INTERNAL, "Invalid instruction index %d", index); + return 0; + } + + Runtime_SetInstructionIndex(runtime, index+1); + + switch(opcode) + { + case OPCODE_NOPE: + // Do nothing. + return 1; + + case OPCODE_POS: + { + ASSERT(opc == 0); + + if(Runtime_Top(runtime, 0) == NULL) + { + Error_Report(error, ErrorType_INTERNAL, "Frame doesn't have enough items on the stack to execute POS"); + return 0; + } + + /* Do nothing */ + return 1; + } + + case OPCODE_NEG: + { + ASSERT(opc == 0); + Object *top; + if(!Runtime_Pop(runtime, error, &top, 1)) + return 0; + + if(Object_IsInt(top)) + { + long long n = Object_GetInt(top); + top = Object_FromInt(-n, heap, error); + } + else if(Object_IsFloat(top)) + { + double f = Object_GetFloat(top); + top = Object_FromFloat(-f, heap, error); + } + else + { + Error_Report(error, ErrorType_RUNTIME, "Negation operand on a non-numeric object"); + return 0; + } + + if(top == NULL) + return 0; + + return Runtime_Push(runtime, error, top); + } + + case OPCODE_NOT: + { + ASSERT(opc == 0); + + Object *top; + if(!Runtime_Pop(runtime, error, &top, 1)) + return 0; + + if(!Object_IsBool(top)) + { + Error_Report(error, ErrorType_RUNTIME, "NOT operand isn't a boolean"); + return 0; + } + + _Bool v = Object_GetBool(top); + + Object *negated = Object_FromBool(!v, heap, error); + if(negated == NULL) + return 0; + + return Runtime_Push(runtime, error, negated); + } + + case OPCODE_NLB: + { + ASSERT(opc == 0); + + Object *top; + if(!Runtime_Pop(runtime, error, &top, 1)) + return 0; + + Object *nullable = Object_NewNullable(top, heap, error); + if(nullable == NULL) + return 0; + + return Runtime_Push(runtime, error, nullable); + } + + case OPCODE_STP: + { + ASSERT(opc == 0); + Object *objs[2]; + if(!Runtime_Pop(runtime, error, objs, 2)) + return 0; + Object *res = Object_NewSum(objs[1], objs[0], heap, error); + if(res == NULL) + return 0; + return Runtime_Push(runtime, error, res); + } + + case OPCODE_ADD: + case OPCODE_SUB: + case OPCODE_MUL: + case OPCODE_DIV: + { + ASSERT(opc == 0); + Object *objs[2]; + if(!Runtime_Pop(runtime, error, objs, 2)) + return 0; + Object *res = do_math_op(objs[1], objs[0], opcode, heap, error); + if(res == NULL) + return 0; + return Runtime_Push(runtime, error, res); + } + + case OPCODE_MOD: + { + ASSERT(opc == 0); + Object *objs[2]; + if(!Runtime_Pop(runtime, error, objs, 2)) + return 0; + if (!Object_IsInt(objs[0]) || !Object_IsInt(objs[1])) { + Error_Report(error, ErrorType_RUNTIME, "Arithmetic operation on a non-numeric object"); + return 0; + } + + long long int x, y, z; + y = Object_GetInt(objs[0]); + x = Object_GetInt(objs[1]); + z = x % y; + + Object *res = Object_FromInt(z, heap, error); + if(res == NULL) + return 0; + + return Runtime_Push(runtime, error, res); + } + + case OPCODE_EQL: + case OPCODE_NQL: + { + ASSERT(opc == 0); + Object *objs[2]; + if(!Runtime_Pop(runtime, error, objs, 2)) + return 0; + + _Bool rawres = Object_Compare(objs[1], objs[0], error); + if(error->occurred == 1) + return 0; + + if(opcode == OPCODE_NQL) + rawres = !rawres; + + Object *res = Object_FromBool(rawres, heap, error); + if(res == NULL) + return 0; + + return Runtime_Push(runtime, error, res); + } + + case OPCODE_LSS: + case OPCODE_GRT: + case OPCODE_LEQ: + case OPCODE_GEQ: + { + ASSERT(opc == 0); + + Object *objs[2]; + if(!Runtime_Pop(runtime, error, objs, 2)) + return 0; + + Object *res = do_relational_op(objs[1], objs[0], opcode, heap, error); + if(res == NULL) + return 0; + + return Runtime_Push(runtime, error, res); + } + + case OPCODE_ASS: + { + ASSERT(opc == 1); + ASSERT(ops[0].type == OPTP_STRING); + const char *name = ops[0].as_string; + + Object *value = Runtime_Top(runtime, 0); + if(value == NULL) { + Error_Report(error, ErrorType_INTERNAL, "Frame has not enough values on the stack"); + return 0; + } + return Runtime_SetVariable(runtime, error, name, value); + } + + case OPCODE_POP: + { + ASSERT(opc == 1); + return Runtime_Pop(runtime, error, NULL, ops[0].as_int); + } + + case OPCODE_CHECKTYPE: + { + ASSERT(opc == 2); + ASSERT(ops[0].type == OPTP_INT); + ASSERT(ops[1].type == OPTP_STRING); + + const char *arg_name; + int arg_index; + + arg_index = ops[0].as_int; + arg_name = ops[1].as_string; + ASSERT(arg_name != NULL); + + Object *typ = Runtime_Top(runtime, 0); + Object *arg = Runtime_Top(runtime, -1); + if(typ == NULL || arg == NULL) + { + Error_Report(error, ErrorType_INTERNAL, "Frame doesn't own enough objects to execute CHECKTYPE"); + return 0; + } + + // Pop type + if(!Runtime_Pop(runtime, error, NULL, 1)) + return 0; + + if (!Object_IsTypeOf(typ, arg, heap, error)) { + char provided[512]; + char allowed[512]; + FILE *provided_fp = fmemopen(provided, sizeof(provided), "wb"); + FILE *allowed_fp = fmemopen(allowed, sizeof(allowed), "wb"); + // TODO: Check for errors from [fmemopen] + Object_Print(typ, allowed_fp); + Object_Print(arg, provided_fp); + fclose(allowed_fp); + fclose(provided_fp); + Error_Report(error, ErrorType_RUNTIME, "Argument %d \"%s\" has an unallowed type. Was expected something with type %s but was provided %s", + arg_index+1, arg_name, allowed, provided); + return 0; + } + return 1; + } + + case OPCODE_CALL: + { + ASSERT(opc == 2); + ASSERT(ops[0].type == OPTP_INT); + ASSERT(ops[1].type == OPTP_INT); + + int argc = ops[0].as_int; + int retc = ops[1].as_int; + ASSERT(argc >= 0 && retc > 0); + + Object *callable; + if (!Runtime_Pop(runtime, error, &callable, 1)) { + Error_Report(error, ErrorType_INTERNAL, "Frame doesn't own enough objects to execute call"); + return 0; + } + + Object *argv[32]; + + int max_argc = sizeof(argv) / sizeof(argv[0]); + if(argc > max_argc) { + Error_Report(error, ErrorType_INTERNAL, "Static buffer only allows function calls with up to %d arguments", max_argc); + return 0; + } + if (!Runtime_Pop(runtime, error, argv, argc)) + return 0; + + Object *rets[8]; + int num_rets = Object_Call(callable, argv, argc, rets, heap, error); + if(num_rets < 0) + return 0; + + // NOTE: Every local object reference is invalidated from here. + + ASSERT(error->occurred == 0); + + for(int g = 0; g < MIN(num_rets, retc); g += 1) + if(!Runtime_Push(runtime, error, rets[g])) + return 0; + + for(int g = 0; g < retc - num_rets; g += 1) + { + Object *temp = Object_NewNone(Runtime_GetHeap(runtime), error); + if(temp == NULL) + return NULL; + + if(!Runtime_Push(runtime, error, temp)) + return 0; + } + return 1; + } + + case OPCODE_SELECT: + case OPCODE_SELECT2: + { + ASSERT(opc == 0); + + int to_be_popped = (opcode == OPCODE_SELECT) ? 2 : 1; + + Object *col = Runtime_Top(runtime, -1); + Object *key = Runtime_Top(runtime, 0); + if (col == NULL || key == NULL) { + const char *name = "SELECT"; + if (opcode == OPCODE_SELECT2) + name = "SELECT2"; + Error_Report(error, ErrorType_INTERNAL, "Frame has not enough values on the stack to run %s instruction", name); + return 0; + } + if(!Runtime_Pop(runtime, error, NULL, to_be_popped)) + return 0; + + Error dummy; + Error_Init(&dummy); // We want to catch the error reported by this Object_Select. + + Object *val = Object_Select(col, key, heap, &dummy); + + if(val == NULL) { + Error_Free(&dummy); + + val = Object_NewNone(heap, error); + if(val == NULL) + return 0; + } + + return Runtime_Push(runtime, error, val); + } + + case OPCODE_INSERT: + { + ASSERT(opc == 0); + + Object *col = Runtime_Top(runtime, -2); + Object *key = Runtime_Top(runtime, -1); + Object *val = Runtime_Top(runtime, 0); + if (col == NULL || key == NULL || val == NULL) { + Error_Report(error, ErrorType_INTERNAL, "Frame has not enough values on the stack to run INSERT instruction"); + return 0; + } + if(!Runtime_Pop(runtime, error, NULL, 2)) + return 0; + + return Object_Insert(col, key, val, heap, error); + } + + case OPCODE_INSERT2: + { + ASSERT(opc == 0); + + Object *val = Stack_Top(stack, -2); + Object *col = Stack_Top(stack, -1); + Object *key = Stack_Top(stack, 0); + if (val == NULL || col == NULL || key == NULL) { + Error_Report(error, ErrorType_INTERNAL, "Frame has not enough values on the stack to run INSERT2 instruction"); + return 0; + } + if(!Runtime_Pop(runtime, error, NULL, 2)) + return 0; + + return Object_Insert(col, key, val, heap, error); + } + + case OPCODE_PUSHINT: + { + ASSERT(opc == 1); + ASSERT(ops[0].type == OPTP_INT); + + Object *obj = Object_FromInt(ops[0].as_int, heap, error); + if(obj == NULL) + return 0; + + return Runtime_Push(runtime, error, obj); + } + + case OPCODE_PUSHFLT: + { + ASSERT(opc == 1); + ASSERT(ops[0].type == OPTP_FLOAT); + + Object *obj = Object_FromFloat(ops[0].as_float, heap, error); + if(obj == NULL) + return 0; + + return Runtime_Push(runtime, error, obj); + } + + case OPCODE_PUSHSTR: + { + ASSERT(opc == 1); + ASSERT(ops[0].type == OPTP_STRING); + + Object *obj = Object_FromString(ops[0].as_string, -1, heap, error); + if(obj == NULL) + return 0; + + return Runtime_Push(runtime, error, obj); + } + + case OPCODE_PUSHVAR: + { + ASSERT(opc == 1); + ASSERT(ops[0].type == OPTP_STRING); + + Object *value; + if (!Runtime_GetVariable(runtime, error, ops[0].as_string, &value)) + return 0; + + if (value == NULL) { + Error_Report(error, ErrorType_RUNTIME, "Reference to undefined variable \"%s\"", ops[0].as_string); + return 0; + } + + return Runtime_Push(runtime, error, value); + } + + case OPCODE_PUSHNNE: + { + ASSERT(opc == 0); + Object *obj = Object_NewNone(heap, error); + if(obj == NULL) + return 0; + return Runtime_Push(runtime, error, obj); + } + + case OPCODE_PUSHTRU: + { + ASSERT(opc == 0); + Object *obj = Object_FromBool(1, heap, error); + if(obj == NULL) + return 0; + return Runtime_Push(runtime, error, obj); + } + + case OPCODE_PUSHFLS: + { + ASSERT(opc == 0); + Object *obj = Object_FromBool(0, heap, error); + if(obj == NULL) + return 0; + return Runtime_Push(runtime, error, obj); + } + + case OPCODE_PUSHFUN: + { + ASSERT(opc == 3); + ASSERT(ops[0].type == OPTP_IDX); + ASSERT(ops[1].type == OPTP_INT); + ASSERT(ops[2].type == OPTP_STRING); + + Object *locals = Runtime_GetLocals(runtime); + Object *old_closure = Runtime_GetClosure(runtime); + Object *new_closure = Object_NewClosure(old_closure, locals, heap, error); // Should old_closure and locals be in the reverse order? + if(new_closure == NULL) + return 0; + + Object *func = Object_FromNojaFunction(runtime, ops[2].as_string, exe, ops[0].as_int, ops[1].as_int, new_closure, heap, error); + if(func == NULL) + return 0; + + return Runtime_Push(runtime, error, func); + } + + case OPCODE_PUSHLST: + { + ASSERT(opc == 1); + ASSERT(ops[0].type == OPTP_INT); + + Object *obj = Object_NewList(ops[0].as_int, heap, error); + if(obj == NULL) + return 0; + + return Runtime_Push(runtime, error, obj); + } + + case OPCODE_PUSHMAP: + { + ASSERT(opc == 1); + ASSERT(ops[0].type == OPTP_INT); + + Object *obj = Object_NewMap(ops[0].as_int, heap, error); + if(obj == NULL) + return 0; + + return Runtime_Push(runtime, error, obj); + } + + case OPCODE_PUSHNNETYP: + { + ASSERT(opc == 0); + + Object *obj = (Object*) Object_GetNoneType(); + ASSERT(obj != NULL); + + return Runtime_Push(runtime, error, obj); + } + + case OPCODE_PUSHTYP: + { + ASSERT(opc == 0); + + Object *top = Runtime_Top(runtime, 0); + if (top == NULL) { + Error_Report(error, ErrorType_INTERNAL, "Frame has not enough values on the stack to run PUSHTYP instruction"); + return 0; + } + + Object *typ = (Object*) Object_GetType(top); + ASSERT(typ != NULL); + + return Runtime_Push(runtime, error, typ); + } + + case OPCODE_EXIT: + { + ASSERT(opc == 0); + Object *vars = Runtime_GetLocals(runtime); + ASSERT(vars != NULL); + Runtime_Push(runtime, error, vars); + return 0; + } + + case OPCODE_RETURN: + { + ASSERT(opc == 1); + ASSERT(ops[0].type == OPTP_INT); + int retc = ops[0].as_int; + UNUSED(retc); + ASSERT(retc >= 0); + ASSERT(retc <= MAX_RETS); + ASSERT(retc == Runtime_GetFrameStackUsage(runtime)); + return 0; + } + + case OPCODE_JUMP: + ASSERT(opc == 1); + ASSERT(ops[0].type == OPTP_IDX); + Runtime_SetInstructionIndex(runtime, ops[0].as_int); + return 1; + + case OPCODE_JUMPIFANDPOP: + case OPCODE_JUMPIFNOTANDPOP: + { + ASSERT(opc == 1); + ASSERT(ops[0].type == OPTP_IDX); + long long int target = ops[0].as_int; + + Object *top; + if(!Runtime_Pop(runtime, error, &top, 1)) + return 0; + + if(!Object_IsBool(top)) { + Error_Report(error, ErrorType_RUNTIME, "Not a boolean"); + return 0; + } + + if(( Object_GetBool(top) && opcode == OPCODE_JUMPIFANDPOP) + || (!Object_GetBool(top) && opcode == OPCODE_JUMPIFNOTANDPOP)) + Runtime_SetInstructionIndex(runtime, target); + return 1; + } + + default: + UNREACHABLE; + return 0; + } + + return 1; +} + +static bool runInstructionsUntilSomethingHappens(Runtime *runtime, Error *error) +{ + Heap *heap = Runtime_GetHeap(runtime); + RuntimeCallback callback = Runtime_GetCallback(runtime); + + if(Runtime_WasInterrupted(runtime) || (callback.func != NULL && !callback.func(runtime, callback.data))) + Error_Report(error, ErrorType_RUNTIME, "Forced abortion"); + else + while(runInstruction(runtime, error)) + { + if(Runtime_WasInterrupted(runtime) || (callback.func != NULL && !callback.func(runtime, callback.data))) + { + Error_Report(error, ErrorType_RUNTIME, "Forced abortion"); + break; + } + + if(Heap_GetUsagePercentage(heap) > 100) + if(!Runtime_CollectGarbage(runtime, error)) + break; + } + + // If an error occurred, we want to return NULL. + return !error->occurred; +} + +static int runExecutableAtIndex(Runtime *runtime, Error *error, + Executable *exe, int index, + Object *closure, + Object *rets[static MAX_RETS], + Object *argv[], int argc) +{ + if (!Runtime_PushFrame(runtime, error, closure, exe, index)) + return -1; + + for (int i = 0; i < argc; i++) + if (!Runtime_Push(runtime, error, argv[i])) + return -1; + + if (!runInstructionsUntilSomethingHappens(runtime, error)) + return -1; + + // Get return values + int retc = 0; + while (retc < MAX_RETS && (rets[retc] = Runtime_Top(runtime, -retc))) + retc++; + + if (!Runtime_PopFrame(runtime)) + return -1; + + return retc; +} + +int runSource(Runtime *runtime, Source *source, Object *rets[static MAX_RETS], Error *error) +{ + int error_offset; + Executable *exe = compile(source, error, &error_offset); + if(exe == NULL) { + Runtime_PushFailedFrame(runtime, error, source, error_offset); // If this fails, there's nothing we can do + return -1; + } + + int retc = runExecutableAtIndex(runtime, error, exe, 0, NULL, rets, NULL, 0); + + Executable_Free(exe); + return retc; +} + +int runBytecodeSource(Runtime *runtime, Source *source, Object *rets[static MAX_RETS], Error *error) +{ + int error_offset; + Executable *exe = assemble(source, error, &error_offset); + if(exe == NULL) { + Runtime_PushFailedFrame(runtime, error, source, error_offset); // If this fails, there's nothing we can do + return -1; + } + + int retc = runExecutableAtIndex(runtime, error, exe, 0, NULL, rets, NULL, 0); + + Executable_Free(exe); + return retc; +} + +int runFileEx(Runtime *runtime, const char *file, Object *rets[static MAX_RETS], Error *error) +{ + Source *source = Source_FromFile(file, error); + if (source == NULL) + return -1; + + int retc = runSource(runtime, source, rets, error); + + Source_Free(source); + return retc; +} + +int runStringEx(Runtime *runtime, const char *name, const char *string, Object *rets[static MAX_RETS], Error *error) +{ + Source *source = Source_FromString(name, string, -1, error); + if (source == NULL) + return -1; + + int retc = runSource(runtime, source, rets, error); + + Source_Free(source); + return retc; +} + +int runBytecodeFileEx(Runtime *runtime, const char *file, Object *rets[static MAX_RETS], Error *error) +{ + Source *source = Source_FromFile(file, error); + if (source == NULL) + return -1; + + int retc = runBytecodeSource(runtime, source, rets, error); + + Source_Free(source); + return retc; +} + +int runBytecodeStringEx(Runtime *runtime, const char *name, const char *string, Object *rets[static MAX_RETS], Error *error) +{ + Source *source = Source_FromString(name, string, -1, error); + if (source == NULL) + return -1; + + int retc = runBytecodeSource(runtime, source, rets, error); + + Source_Free(source); + return retc; +} + +bool runFile(Runtime *runtime, const char *file, Error *error) +{ + Object *rets[MAX_RETS]; + return runFileEx(runtime, file, rets, error) >= 0; +} + +bool runString(Runtime *runtime, const char *string, Error *error) +{ + Object *rets[MAX_RETS]; + return runStringEx(runtime, "(unnamed)", string, rets, error) >= 0; +} + +bool runBytecodeFile(Runtime *runtime, const char *file, Error *error) +{ + Object *rets[MAX_RETS]; + return runBytecodeFileEx(runtime, file, rets, error) >= 0; +} + +bool runBytecodeString(Runtime *runtime, const char *string, Error *error) +{ + Object *rets[MAX_RETS]; + return runBytecodeStringEx(runtime, "(unnamed)", string, rets, error) >= 0; +} + +static bool makePathRelativeToScript(Runtime *runtime, const char *src_path, char *dst_path, size_t dst_size) +{ + size_t src_size = strlen(src_path); + + if(Path_IsAbsolute(src_path)) { + + if(src_size >= dst_size) + return false; + + strcpy(dst_path, src_path); + + } else { + + size_t written = Runtime_GetCurrentScriptFolder(runtime, dst_path, dst_size); + if(written == 0) + return false; + + if(written + src_size >= dst_size) + return false; + + memcpy(dst_path + written, src_path, src_size); + dst_path[written + src_size] = '\0'; + } + + return true; +} + +int runFileRelativeToScript(Runtime *runtime, const char *file, Object *rets[static MAX_RETS], Error *error) +{ + char full[1024]; + if (!makePathRelativeToScript(runtime, file, full, sizeof(full))) { + Error_Report(error, ErrorType_INTERNAL, "Internal buffer is too small"); + return -1; + } + + Source *source = Source_FromFile(full, error); + if (source == NULL) + return -1; + + int retc = runSource(runtime, source, rets, error); + + Source_Free(source); + return retc; +} diff --git a/src/lib/runtime/run.h b/src/lib/runtime/run.h new file mode 100644 index 0000000..eac2fdc --- /dev/null +++ b/src/lib/runtime/run.h @@ -0,0 +1,12 @@ +#include "runtime.h" +int runSource(Runtime *runtime, Source *source, Object *rets[static MAX_RETS], Error *error); +int runBytecodeSource(Runtime *runtime, Source *source, Object *rets[static MAX_RETS], Error *error); +bool runFile(Runtime *runtime, const char *file, Error *error); +bool runString(Runtime *runtime, const char *string, Error *error); +bool runBytecodeFile(Runtime *runtime, const char *file, Error *error); +bool runBytecodeString(Runtime *runtime, const char *string, Error *error); +int runFileEx(Runtime *runtime, const char *file, Object *rets[static MAX_RETS], Error *error); +int runStringEx(Runtime *runtime, const char *name, const char *string, Object *rets[static MAX_RETS], Error *error); +int runBytecodeFileEx(Runtime *runtime, const char *file, Object *rets[static MAX_RETS], Error *error); +int runBytecodeStringEx(Runtime *runtime, const char *name, const char *string, Object *rets[static MAX_RETS], Error *error); +int runFileRelativeToScript(Runtime *runtime, const char *file, Object *rets[static MAX_RETS], Error *error); diff --git a/src/lib/runtime/runtime.c b/src/lib/runtime/runtime.c index e2eced7..c5f3901 100644 --- a/src/lib/runtime/runtime.c +++ b/src/lib/runtime/runtime.c @@ -28,9 +28,6 @@ ** +--------------------------------------------------------------------------+ */ -#include -#include -#include #include #include "runtime.h" #include "../utils/path.h" @@ -40,14 +37,35 @@ #define MAX_FRAME_STACK 16 #define MAX_FRAMES 16 -typedef struct xFrame Frame; -struct xFrame { - Frame *prev; +typedef enum { + FrameType_NATIVE, + FrameType_NORMAL, + FrameType_FAILED, +} FrameType; + +typedef struct Frame Frame; +struct Frame { + Frame *prev; + FrameType type; +}; + +typedef struct { + Frame base; Object *locals; Object *closure; Executable *exe; int index, used; -}; +} NormalFrame; + +typedef struct { + Frame base; +} NativeFrame; + +typedef struct { + Frame base; + Source *source; + size_t offset; +} FailedFrame; struct xRuntime { bool interrupt; @@ -59,84 +77,40 @@ struct xRuntime { Stack *stack; Heap *heap; TimingTable *timing; + + FailedFrame failed_frame; }; +bool Runtime_plugBuiltins(Runtime *runtime, Object *object, Error *error) +{ + if (runtime->builtins == NULL) { + runtime->builtins = object; + } else { + Heap *heap = Runtime_GetHeap(runtime); + Object *old_builtins; + Object *new_builtins; + + old_builtins = runtime->builtins; + new_builtins = Object_NewClosure(object, old_builtins, heap, error); + if (new_builtins == NULL) + return false; + + runtime->builtins = new_builtins; + } + + return true; +} + RuntimeConfig Runtime_GetDefaultConfigs() { return (RuntimeConfig) { + .heap = 1024*1024, .stack = 1024, .callback = { .func = NULL, .data = NULL }, .time = false, }; } -// Returns the length written in buff (not considering the zero byte) -size_t Runtime_GetCurrentScriptFolder(Runtime *runtime, char *buff, size_t buffsize) -{ - const char *path = Runtime_GetCurrentScriptAbsolutePath(runtime); - if(path == NULL) { - if(getcwd(buff, buffsize) == NULL) - return 0; - size_t cwdlen = strlen(buff); - if (buff[cwdlen-1] == '/') - return cwdlen; - else { - if (cwdlen+1 >= buffsize) - return 0; - buff[cwdlen] = '/'; - return cwdlen+1; - } - } - - // This following block is a custom implementation - // of [dirname], which doesn't write into the input - // string and is way buggier. It will for sure give - // problems in the future!! - size_t dir_len; - { - // This is buggy code!! - size_t path_len = strlen(path); - ASSERT(path_len > 0); // Not empty - ASSERT(Path_IsAbsolute(path)); // Is absolute - ASSERT(path[path_len-1] != '/'); // Doesn't end with a slash. - - size_t popped = 0; - while(path[path_len-1-popped] != '/') - popped += 1; - - ASSERT(path_len > popped); - - dir_len = path_len - popped; - - ASSERT(dir_len < path_len); - ASSERT(path[dir_len-1] == '/'); - } - - if(dir_len >= buffsize) - return 0; - - memcpy(buff, path, dir_len); - buff[dir_len] = '\0'; - return dir_len; -} - -const char *Runtime_GetCurrentScriptAbsolutePath(Runtime *runtime) -{ - Executable *exe = Runtime_GetCurrentExecutable(runtime); - ASSERT(exe != NULL); - - Source *src = Executable_GetSource(exe); - if(src == NULL) - return NULL; - - const char *path = Source_GetAbsolutePath(src); - if(path == NULL) - return NULL; - - ASSERT(path[0] != '\0'); - return path; -} - Stack *Runtime_GetStack(Runtime *runtime) { return Stack_Copy(runtime->stack, 1); @@ -149,18 +123,115 @@ Heap *Runtime_GetHeap(Runtime *runtime) int Runtime_GetCurrentIndex(Runtime *runtime) { - if(runtime->depth == 0) + Frame *frame = runtime->frame; + if(frame == NULL || frame->type != FrameType_NORMAL) return -1; + return ((NormalFrame*) frame)->index; +} + +static Source *getFrameSource(Frame *frame) +{ + switch (frame->type) { + case FrameType_NORMAL: return Executable_GetSource(((NormalFrame*) frame)->exe); + case FrameType_NATIVE: return NULL; + case FrameType_FAILED: return ((FailedFrame*) frame)->source; + } + UNREACHABLE; + return NULL; +} + +static int getFrameOffset(Frame *frame) +{ + switch (frame->type) { + case FrameType_NORMAL: + { + NormalFrame *normal_frame = (NormalFrame*) frame; + return Executable_GetInstrOffset(normal_frame->exe, normal_frame->index); + } + + case FrameType_NATIVE: return -1; + case FrameType_FAILED: return ((FailedFrame*) frame)->offset; + } + UNREACHABLE; + return -1; +} + +static void printFrame(Frame *frame, int depth, FILE *stream) +{ + Source *source = getFrameSource(frame); + int offset = getFrameOffset(frame); + + int line; + const char *name; + + + if (source == NULL) + // Executable has no associate source object + name = "(no source)"; + else { + name = Source_GetName(source); + if (name == NULL) + // Executable has a source but the source + // doesn't have a name. + name = "(unnamed)"; + } + + if(source == NULL || offset < 0) + line = 0; + else { + line = 1; + + const char *body = Source_GetBody(source); + + int i = 0; + + while(i < offset) + { + if(body[i] == '\n') + line += 1; + + i += 1; + } + } + + if(line == 0) + fprintf(stream, "\t#%d %s\n", depth, name); else - return runtime->frame->index; + fprintf(stream, "\t#%d %s:%d\n", depth, name, line); +} + +void Runtime_PrintStackTrace(Runtime *runtime, FILE *stream) +{ + fprintf(stream, "Stack trace:\n"); + Frame *frame = runtime->frame; + if (frame == NULL) + fprintf(stderr, "\t(No active frames)\n"); + else { + size_t depth = 0; + do { + printFrame(frame, depth, stream); + frame = frame->prev; + depth++; + } while (frame != NULL); + } +} + +Executable *Runtime_GetMostRecentExecutable(Runtime *runtime) +{ + Frame *frame = runtime->frame; + while (frame != NULL && frame->type != FrameType_NORMAL) + frame = frame->prev; + if (frame == NULL) + return NULL; + return ((NormalFrame*) frame)->exe; } Executable *Runtime_GetCurrentExecutable(Runtime *runtime) { - if(runtime->depth == 0) + if(runtime->depth == 0 || runtime->frame->type != FrameType_NORMAL) return NULL; - else - return runtime->frame->exe; + + return ((NormalFrame*) runtime->frame)->exe; } TimingTable *Runtime_GetTimingTable(Runtime *runtime) @@ -173,16 +244,21 @@ void Runtime_Interrupt(Runtime *runtime) runtime->interrupt = true; } -Runtime *Runtime_New2(Heap *heap, _Bool free_it, RuntimeConfig config) +Runtime *Runtime_New(RuntimeConfig config) { Runtime *runtime = malloc(sizeof(Runtime)); if (runtime == NULL) return NULL; + runtime->heap = Heap_New(config.heap); + if(runtime->heap == NULL) { + free(runtime); + return NULL; + } + runtime->stack = Stack_New(config.stack); if(runtime->stack == NULL) { - if (free_it) - Heap_Free(heap); + Heap_Free(runtime->heap); free(runtime); return NULL; } @@ -191,18 +267,15 @@ Runtime *Runtime_New2(Heap *heap, _Bool free_it, RuntimeConfig config) if (config.time) { timing_table = TimingTable_new(); if (timing_table == NULL) { - if (free_it) - Heap_Free(heap); + Stack_Free(runtime->stack); + Heap_Free(runtime->heap); free(runtime); return NULL; } } - runtime->interrupt = false; runtime->timing = timing_table; - runtime->heap = heap; - runtime->free_heap = free_it; runtime->callback = config.callback; runtime->builtins = NULL; runtime->frame = NULL; @@ -211,52 +284,42 @@ Runtime *Runtime_New2(Heap *heap, _Bool free_it, RuntimeConfig config) return runtime; } -Runtime *Runtime_New(int heap_size, RuntimeConfig config) -{ - if(heap_size < 0) - heap_size = 65536; - - Heap *heap = Heap_New(heap_size); - if(heap == NULL) return NULL; - - return Runtime_New2(heap, 1, config); -} - void Runtime_Free(Runtime *runtime) { + while (runtime->frame != NULL) + Runtime_PopFrame(runtime); if (runtime->timing != NULL) TimingTable_free(runtime->timing); - if(runtime->free_heap) - Heap_Free(runtime->heap); Stack_Free(runtime->stack); + Heap_Free(runtime->heap); free(runtime); } -Object *Runtime_GetBuiltins(Runtime *runtime) -{ - return runtime->builtins; -} - -void Runtime_SetBuiltins(Runtime *runtime, Object *builtins) -{ - runtime->builtins = builtins; -} - _Bool Runtime_Push(Runtime *runtime, Error *error, Object *obj) { ASSERT(runtime != NULL); ASSERT(error != NULL); ASSERT(obj != NULL); - if(runtime->depth == 0) + Frame *frame = runtime->frame; + if(frame == NULL) { Error_Report(error, ErrorType_RUNTIME, "There are no frames on the stack"); return 0; } + if (frame->type == FrameType_NATIVE) { + Error_Report(error, ErrorType_INTERNAL, "Can't push on the stack from a native function"); + return 0; + } + if (frame->type == FrameType_FAILED) { + Error_Report(error, ErrorType_INTERNAL, "Can't push on the stack after a compilation error"); + return 0; + } - ASSERT(runtime->frame->used <= MAX_FRAME_STACK); - - if(runtime->frame->used == MAX_FRAME_STACK) + ASSERT(frame->type == FrameType_NORMAL); + NormalFrame *normal_frame = (NormalFrame*) frame; + + if(normal_frame->used == MAX_FRAME_STACK) { Error_Report(error, ErrorType_RUNTIME, "Frame stack limit of %d reached", MAX_FRAME_STACK); return 0; @@ -265,1288 +328,333 @@ _Bool Runtime_Push(Runtime *runtime, Error *error, Object *obj) if(!Stack_Push(runtime->stack, obj)) { Error_Report(error, ErrorType_RUNTIME, "Out of stack"); - return 0; + return 0; } - runtime->frame->used += 1; + normal_frame->used++; return 1; } -_Bool Runtime_Pop(Runtime *runtime, Error *error, unsigned int n) +_Bool Runtime_Pop(Runtime *runtime, Error *error, Object **p, unsigned int n) { ASSERT(runtime != NULL); ASSERT(error != NULL); - if(runtime->depth == 0) + Frame *frame = runtime->frame; + + if (frame == NULL) { Error_Report(error, ErrorType_RUNTIME, "There are no frames on the stack"); return 0; } + if (frame->type == FrameType_NATIVE) { + Error_Report(error, ErrorType_INTERNAL, "Can't pop from the stack from a native function"); + return 0; + } + if (frame->type == FrameType_FAILED) { + Error_Report(error, ErrorType_INTERNAL, "Can't pop from the stack after a compilation error"); + return 0; + } - ASSERT(runtime->frame->used >= 0); + ASSERT(frame->type == FrameType_NORMAL); + NormalFrame *normal_frame = (NormalFrame*) frame; - if((unsigned int) runtime->frame->used < n) + ASSERT(normal_frame->used >= 0); + if((unsigned int) normal_frame->used < n) { Error_Report(error, ErrorType_RUNTIME, "Frame has not enough values on the stack"); return 0; } + if (p != NULL) + for (unsigned int i = 0; i < n; i++) + p[i] = Stack_Top(runtime->stack, -i); + // The frame has something on the stack, // this means that the stack isn't empty // and popping won't fail. (void) Stack_Pop(runtime->stack, n); - runtime->frame->used -= n; + normal_frame->used -= n; - ASSERT(runtime->frame->used >= 0); + ASSERT(normal_frame->used >= 0); return 1; } -typedef struct { - Executable *exe; - int index; -} SnapshotNode; - -struct xSnapshot { - int depth; - SnapshotNode nodes[]; -}; - -Snapshot *Snapshot_New(Runtime *runtime) +Object *Runtime_Top(Runtime *runtime, int n) { - ASSERT(runtime->depth >= 0); - - Snapshot *snapshot = malloc(sizeof(Snapshot) + sizeof(SnapshotNode) * runtime->depth); - - if(snapshot == NULL) + if (runtime->frame->type != FrameType_NORMAL) return NULL; - { - Frame *f = runtime->frame; + NormalFrame *normal_frame = (NormalFrame*) runtime->frame; - snapshot->depth = 0; - - while(snapshot->depth < runtime->depth) - { - ASSERT(f != NULL); - - SnapshotNode *node = snapshot->nodes + snapshot->depth; - - node->exe = Executable_Copy(f->exe); - node->index = f->index; - - if(node->exe == NULL) - goto abort; - - f = f->prev; - snapshot->depth += 1; - } - - ASSERT(f == NULL); - } - - return snapshot; - -abort: - Snapshot_Free(snapshot); - return NULL; -} - -void Snapshot_Free(Snapshot *snapshot) -{ - for(int i = 0; i < snapshot->depth; i += 1) - { - Executable *exe = snapshot->nodes[i].exe; - Executable_Free(exe); - } - free(snapshot); -} - -void Snapshot_Print(Snapshot *snapshot, FILE *fp) -{ - ASSERT(snapshot != NULL); - ASSERT(fp != NULL); - - fprintf(fp, "Stack trace:\n"); - - for(int i = 0; i < snapshot->depth; i += 1) - { - SnapshotNode node = snapshot->nodes[i]; - - Executable *exe = node.exe; - Source *src = Executable_GetSource(exe); - - const char *name; - { - name = NULL; - - if(src != NULL) - name = Source_GetName(src); - - if(name == NULL) - name = "(unnamed)"; - } - - int line; - { - if(src == NULL) - line = 0; - else - { - line = 1; - - const char *body = Source_GetBody(src); - int offset = Executable_GetInstrOffset(exe, node.index); - - int i = 0; - - while(i < offset) - { - if(body[i] == '\n') - line += 1; - - i += 1; - } - } - } - - if(line == 0) - fprintf(fp, "\t#%d %s\n", i, name); - else - fprintf(fp, "\t#%d %s:%d\n", i, name, line); - } - - //fprintf(fp, " (Snapshot can't be printed yet)\n"); -} - -static Object *do_math_op(Object *lop, Object *rop, Opcode opcode, Heap *heap, Error *error) -{ - ASSERT(lop != NULL); - ASSERT(rop != NULL); - - #define APPLY(x, y, z, id) \ - switch(opcode) \ - { \ - case OPCODE_ADD: (z) = (x) + (y); break; \ - case OPCODE_SUB: (z) = (x) - (y); break; \ - case OPCODE_MUL: (z) = (x) * (y); break; \ - case OPCODE_DIV: \ - if((y) == 0) \ - { \ - Error_Report(error, ErrorType_RUNTIME, "Division by zero"); \ - return NULL; \ - } \ - (z) = (x) / (y); \ - break; \ - default: UNREACHABLE; break; \ - } - - Object *res; - - if(Object_IsInt(lop)) - { - long long int raw_lop = Object_GetInt(lop); - - if(Object_IsInt(rop)) - { - // int + int - long long int raw_rop = Object_GetInt(rop); - long long int raw_res = 0; - APPLY(raw_lop, raw_rop, raw_res, id) - res = Object_FromInt(raw_res, heap, error); - } - else if(Object_IsFloat(rop)) - { - // int + float - double raw_rop = Object_GetFloat(rop); - double raw_res = 0; - APPLY((double) raw_lop, raw_rop, raw_res, id) - res = Object_FromFloat(raw_res, heap, error); - } - else - { - Error_Report(error, ErrorType_RUNTIME, "Arithmetic operation on a non-numeric object"); - return NULL; - } - } - else if(Object_IsFloat(lop)) - { - double raw_lop = Object_GetFloat(lop); - - if(Object_IsInt(rop)) - { - // float + int - long long int raw_rop = Object_GetInt(rop); - double raw_res = 0; - APPLY(raw_lop, (double) raw_rop, raw_res, id) - res = Object_FromFloat(raw_res, heap, error); - } - else if(Object_IsFloat(rop)) - { - // float + float - double raw_rop = Object_GetFloat(rop); - double raw_res = 0; - APPLY(raw_lop, raw_rop, raw_res, id) - res = Object_FromFloat(raw_res, heap, error); - } - else - { - Error_Report(error, ErrorType_RUNTIME, "Arithmetic operation on a non-numeric object"); - return NULL; - } - } - else - { - Error_Report(error, ErrorType_RUNTIME, "Arithmetic operation on a non-numeric object"); + if (normal_frame->used + n <= 0) return NULL; - } - #undef APPLY - - return res; -} - -static Object *do_relational_op(Object *lop, Object *rop, Opcode opcode, Heap *heap, Error *error) -{ - ASSERT(lop != NULL); - ASSERT(rop != NULL); - - #define APPLY(x, y, z, id) \ - switch(opcode) \ - { \ - case OPCODE_LSS: (z) = (x) < (y); break; \ - case OPCODE_GRT: (z) = (x) > (y); break; \ - case OPCODE_LEQ: (z) = (x) <= (y); break; \ - case OPCODE_GEQ: (z) = (x) >= (y); break; \ - default: UNREACHABLE; break; \ - } - - _Bool res = 0; - - if(Object_IsInt(lop)) - { - long long int raw_lop = Object_GetInt(lop); - if(Object_IsInt(rop)) - { - // int + int - long long int raw_rop = Object_GetInt(rop); - APPLY(raw_lop, raw_rop, res, id) - } - else if(Object_IsFloat(rop)) - { - // int + float - double raw_rop = Object_GetFloat(rop); - APPLY((double) raw_lop, raw_rop, res, id) - } - else - { - Error_Report(error, ErrorType_RUNTIME, "Relational operation on a non-numeric object"); - return NULL; - } - } - else if(Object_IsFloat(lop)) - { - double raw_lop = Object_GetFloat(lop); - if(Object_IsInt(rop)) - { - // float + int - long long int raw_rop = Object_GetInt(rop); - APPLY(raw_lop, (double) raw_rop, res, id) - } - else if(Object_IsFloat(rop)) - { - // float + float - double raw_rop = Object_GetFloat(rop); - APPLY(raw_lop, raw_rop, res, id) - } - else - { - Error_Report(error, ErrorType_RUNTIME, "Relational operation on a non-numeric object"); - return NULL; - } - } - else - { - Error_Report(error, ErrorType_RUNTIME, "Relational operation on a non-numeric object"); - return NULL; - } - - #undef APPLY - - return Object_FromBool(res, heap, error); -} - -static _Bool step(Runtime *runtime, Error *error) -{ - ASSERT(runtime != NULL); - ASSERT(error->occurred == 0); - Opcode opcode; - Operand ops[3]; - int opc = sizeof(ops) / sizeof(ops[0]); + Object *top = Stack_Top(runtime->stack, n); + ASSERT(top != NULL); - if(!Executable_Fetch(runtime->frame->exe, runtime->frame->index, &opcode, ops, &opc)) - { - Error_Report(error, ErrorType_INTERNAL, "Invalid instruction index"); - return 0; - } - - runtime->frame->index += 1; - - switch(opcode) - { - case OPCODE_NOPE: - // Do nothing. - return 1; - - case OPCODE_POS: - { - ASSERT(opc == 0); - - if(runtime->frame->used == 0) - { - Error_Report(error, ErrorType_INTERNAL, "Frame doesn't have enough items on the stack to execute POS"); - return 0; - } - - /* Do nothing */ - return 1; - } - - case OPCODE_NEG: - { - ASSERT(opc == 0); - - if(runtime->frame->used == 0) - { - Error_Report(error, ErrorType_INTERNAL, "Frame doesn't have enough items on the stack to execute NEG"); - return 0; - } - - Object *top = Stack_Top(runtime->stack, 0); - ASSERT(top != NULL); - - if(!Runtime_Pop(runtime, error, 1)) - return 0; - - Heap *heap = Runtime_GetHeap(runtime); - ASSERT(heap != NULL); - - if(Object_IsInt(top)) - { - long long n = Object_GetInt(top); - top = Object_FromInt(-n, heap, error); - } - else if(Object_IsFloat(top)) - { - double f = Object_GetFloat(top); - top = Object_FromFloat(-f, heap, error); - } - else - { - Error_Report(error, ErrorType_RUNTIME, "Negation operand on a non-numeric object"); - return 0; - } - - if(top == NULL) - return 0; - - if(!Runtime_Push(runtime, error, top)) - return 0; - return 1; - } - - case OPCODE_NOT: - { - ASSERT(opc == 0); - - if(runtime->frame->used == 0) - { - Error_Report(error, ErrorType_INTERNAL, "Frame doesn't have enough items on the stack to execute NOT"); - return 0; - } - - Object *top = Stack_Top(runtime->stack, 0); - - if(!Runtime_Pop(runtime, error, 1)) - return 0; - - ASSERT(top != NULL); - - if(!Object_IsBool(top)) - { - Error_Report(error, ErrorType_RUNTIME, "NOT operand isn't a boolean"); - return 0; - } - - _Bool v = Object_GetBool(top); - - Object *negated = Object_FromBool(!v, runtime->heap, error); - if(negated == NULL) - return 0; - - if(!Runtime_Push(runtime, error, negated)) - return 0; - return 1; - } - - case OPCODE_NLB: - { - ASSERT(opc == 0); - - if(runtime->frame->used == 0) - { - Error_Report(error, ErrorType_INTERNAL, "Frame doesn't have enough items on the stack to execute NLB"); - return 0; - } - - Object *top = Stack_Top(runtime->stack, 0); - - if(!Runtime_Pop(runtime, error, 1)) - return 0; - - ASSERT(top != NULL); - - Object *nullable = Object_NewNullable(top, runtime->heap, error); - if(nullable == NULL) - return 0; - - if(!Runtime_Push(runtime, error, nullable)) - return 0; - return 1; - } - - case OPCODE_STP: - { - ASSERT(opc == 0); - - Object *rop = Stack_Top(runtime->stack, 0); - Object *lop = Stack_Top(runtime->stack, -1); - - if(!Runtime_Pop(runtime, error, 2)) - return 0; - - // We managed to pop rop and lop, - // so we know they're not NULL. - ASSERT(rop != NULL); - ASSERT(lop != NULL); - - Object *res = Object_NewSum(lop, rop, runtime->heap, error); - if(res == NULL) - return 0; - - if(!Runtime_Push(runtime, error, res)) - return 0; - return 1; - } - - case OPCODE_ADD: - case OPCODE_SUB: - case OPCODE_MUL: - case OPCODE_DIV: - { - ASSERT(opc == 0); - - Object *rop = Stack_Top(runtime->stack, 0); - Object *lop = Stack_Top(runtime->stack, -1); - - if(!Runtime_Pop(runtime, error, 2)) - return 0; - - // We managed to pop rop and lop, - // so we know they're not NULL. - ASSERT(rop != NULL); - ASSERT(lop != NULL); - - Object *res = do_math_op(lop, rop, opcode, runtime->heap, error); - - if(res == NULL) - return 0; - - if(!Runtime_Push(runtime, error, res)) - return 0; - return 1; - } - - case OPCODE_MOD: - { - ASSERT(opc == 0); - - Object *rop = Stack_Top(runtime->stack, 0); - Object *lop = Stack_Top(runtime->stack, -1); - - if(!Runtime_Pop(runtime, error, 2)) - return 0; - - // We managed to pop rop and lop, - // so we know they're not NULL. - ASSERT(rop != NULL); - ASSERT(lop != NULL); - - if (!Object_IsInt(rop) || !Object_IsInt(lop)) { - Error_Report(error, ErrorType_RUNTIME, "Arithmetic operation on a non-numeric object"); - return 0; - } - - long long int x, y, z; - y = Object_GetInt(rop); - x = Object_GetInt(lop); - z = x % y; - - Object *res = Object_FromInt(z, runtime->heap, error); - if(res == NULL) - return 0; - - if(!Runtime_Push(runtime, error, res)) - return 0; - return 1; - } - - case OPCODE_EQL: - case OPCODE_NQL: - { - ASSERT(opc == 0); - - Object *rop = Stack_Top(runtime->stack, 0); - Object *lop = Stack_Top(runtime->stack, -1); - - if(!Runtime_Pop(runtime, error, 2)) - return 0; - - // We managed to pop rop and lop, - // so we know they're not NULL. - ASSERT(rop != NULL); - ASSERT(lop != NULL); - - _Bool rawres = Object_Compare(lop, rop, error); - - if(error->occurred == 1) - return 0; - - if(opcode == OPCODE_NQL) - rawres = !rawres; - - Object *res = Object_FromBool(rawres, runtime->heap, error); - - if(res == NULL) - return 0; - - if(!Runtime_Push(runtime, error, res)) - return 0; - return 1; - } - - case OPCODE_LSS: - case OPCODE_GRT: - case OPCODE_LEQ: - case OPCODE_GEQ: - { - ASSERT(opc == 0); - - Object *rop = Stack_Top(runtime->stack, 0); - Object *lop = Stack_Top(runtime->stack, -1); - - if(!Runtime_Pop(runtime, error, 2)) - return 0; - - // We managed to pop rop and lop, - // so we know they're not NULL. - ASSERT(rop != NULL); - ASSERT(lop != NULL); - - Object *res = do_relational_op(lop, rop, opcode, runtime->heap, error); - - if(res == NULL) - return 0; - - if(!Runtime_Push(runtime, error, res)) - return 0; - return 1; - } - - case OPCODE_ASS: - { - ASSERT(opc == 1); - ASSERT(ops[0].type == OPTP_STRING); - const char *name = ops[0].as_string; - - if(runtime->frame->used == 0) - { - Error_Report(error, ErrorType_RUNTIME, "Frame has not enough values on the stack"); - return 0; - } - - Object *val = Stack_Top(runtime->stack, 0); - ASSERT(val != NULL); - - Object *key = Object_FromString(name, -1, runtime->heap, error); - - if(key == NULL) - return 0; - - if(!Object_Insert(runtime->frame->locals, key, val, runtime->heap, error)) - return 0; - return 1; - } - - case OPCODE_POP: - { - ASSERT(opc == 1); - - if(!Runtime_Pop(runtime, error, ops[0].as_int)) - return 0; - return 1; - } - - case OPCODE_CHECKTYPE: - { - ASSERT(opc == 2); - ASSERT(ops[0].type == OPTP_INT); - ASSERT(ops[1].type == OPTP_STRING); - - const char *arg_name; - int arg_index; - - arg_index = ops[0].as_int; - arg_name = ops[1].as_string; - ASSERT(arg_name != NULL); - - if(runtime->frame->used < 2) - { - Error_Report(error, ErrorType_INTERNAL, "Frame doesn't own enough objects to execute CHECKTYPE"); - return 0; - } - - Object *typ = Stack_Top(runtime->stack, 0); - Object *arg = Stack_Top(runtime->stack, -1); - ASSERT(typ != NULL); - ASSERT(arg != NULL); - - // Pop type - if(!Runtime_Pop(runtime, error, 1)) - return 0; - - if (!Object_IsTypeOf(typ, arg, runtime->heap, error)) { - char provided[512]; - char allowed[512]; - FILE *provided_fp = fmemopen(provided, sizeof(provided), "wb"); - FILE *allowed_fp = fmemopen(allowed, sizeof(allowed), "wb"); - // TODO: Check for errors from [fmemopen] - Object_Print(typ, allowed_fp); - Object_Print(arg, provided_fp); - fclose(allowed_fp); - fclose(provided_fp); - Error_Report(error, ErrorType_RUNTIME, "Argument %d \"%s\" has an unallowed type. Was expected something with type %s but was provided %s", - arg_index+1, arg_name, allowed, provided); - return 0; - } - return 1; - } - - case OPCODE_CALL: - { - ASSERT(opc == 2); - ASSERT(ops[0].type == OPTP_INT); - ASSERT(ops[1].type == OPTP_INT); - - int argc = ops[0].as_int; - int retc = ops[1].as_int; - ASSERT(argc >= 0 && retc > 0); - - if(runtime->frame->used < argc + 1) - { - Error_Report(error, ErrorType_INTERNAL, "Frame doesn't own enough objects to execute call"); - return 0; - } - - Object *callable = Stack_Top(runtime->stack, 0); - ASSERT(callable != NULL); - - Object *argv[32]; - - int max_argc = sizeof(argv) / sizeof(argv[0]); - if(argc > max_argc) - { - Error_Report(error, ErrorType_INTERNAL, "Static buffer only allows function calls with up to %d arguments", max_argc); - return 0; - } - - for(int i = 0; i < argc; i += 1) - { - argv[i] = Stack_Top(runtime->stack, -(i+1)); - ASSERT(argv[i] != NULL); - } - - ASSERT(error->occurred == 0); - (void) Runtime_Pop(runtime, error, argc+1); - ASSERT(error->occurred == 0); - - Object *rets[8]; - int num_rets = Object_Call(callable, argv, argc, rets, runtime->heap, error); - - if(num_rets < 0) - return 0; - - // NOTE: Every local object reference is invalidated from here. - - ASSERT(error->occurred == 0); - - for(int g = 0; g < MIN(num_rets, retc); g += 1) - if(!Runtime_Push(runtime, error, rets[g])) - return 0; - - for(int g = 0; g < retc - num_rets; g += 1) - { - Object *temp = Object_NewNone(Runtime_GetHeap(runtime), error); - - if(temp == NULL) - return NULL; - - if(!Runtime_Push(runtime, error, temp)) - return 0; - } - return 1; - } - - case OPCODE_SELECT: - case OPCODE_SELECT2: - { - ASSERT(opc == 0); - - int to_be_popped = (opcode == OPCODE_SELECT) ? 2 : 1; - - if(runtime->frame->used < to_be_popped) - { - const char *name = "SELECT"; - if (opcode == OPCODE_SELECT2) - name = "SELECT2"; - Error_Report(error, ErrorType_INTERNAL, "Frame has not enough values on the stack to run %s instruction", name); - return 0; - } - - Object *col = Stack_Top(runtime->stack, -1); - Object *key = Stack_Top(runtime->stack, 0); - - ASSERT(col != NULL && key != NULL); - - if(!Runtime_Pop(runtime, error, to_be_popped)) - return 0; - - ASSERT(error->occurred == 0); - - Error dummy; - Error_Init(&dummy); // We want to catch the error reported by this Object_Select. - - Object *val = Object_Select(col, key, runtime->heap, &dummy); - - if(val == NULL) - { - Error_Free(&dummy); - - val = Object_NewNone(runtime->heap, error); - if(val == NULL) - return 0; - } - - ASSERT(error->occurred == 0); - - if(!Runtime_Push(runtime, error, val)) - return 0; - - ASSERT(error->occurred == 0); - return 1; - } - - case OPCODE_INSERT: - { - ASSERT(opc == 0); - - if(runtime->frame->used < 3) - { - Error_Report(error, ErrorType_INTERNAL, "Frame has not enough values on the stack to run INSERT instruction"); - return 0; - } - - Object *col = Stack_Top(runtime->stack, -2); - Object *key = Stack_Top(runtime->stack, -1); - Object *val = Stack_Top(runtime->stack, 0); - - ASSERT(col != NULL && key != NULL && val != NULL); - - if(!Runtime_Pop(runtime, error, 2)) - return 0; - - if(!Object_Insert(col, key, val, runtime->heap, error)) - return 0; - return 1; - } - - case OPCODE_INSERT2: - { - ASSERT(opc == 0); - - if(runtime->frame->used < 3) - { - Error_Report(error, ErrorType_INTERNAL, "Frame has not enough values on the stack to run INSERT2 instruction"); - return 0; - } - - Object *val = Stack_Top(runtime->stack, -2); - Object *col = Stack_Top(runtime->stack, -1); - Object *key = Stack_Top(runtime->stack, 0); - - ASSERT(col != NULL && key != NULL && val != NULL); - - if(!Runtime_Pop(runtime, error, 2)) - return 0; - - if(!Object_Insert(col, key, val, runtime->heap, error)) - return 0; - return 1; - } - - case OPCODE_PUSHINT: - { - ASSERT(opc == 1); - ASSERT(ops[0].type == OPTP_INT); - - Object *obj = Object_FromInt(ops[0].as_int, runtime->heap, error); - - if(obj == NULL) - return 0; - - if(!Runtime_Push(runtime, error, obj)) - return 0; - return 1; - } - - case OPCODE_PUSHFLT: - { - ASSERT(opc == 1); - ASSERT(ops[0].type == OPTP_FLOAT); - - Object *obj = Object_FromFloat(ops[0].as_float, runtime->heap, error); - - if(obj == NULL) - return 0; - - if(!Runtime_Push(runtime, error, obj)) - return 0; - return 1; - } - - case OPCODE_PUSHSTR: - { - ASSERT(opc == 1); - ASSERT(ops[0].type == OPTP_STRING); - - Object *obj = Object_FromString(ops[0].as_string, -1, runtime->heap, error); - - if(obj == NULL) - return 0; - - if(!Runtime_Push(runtime, error, obj)) - return 0; - return 1; - } - - case OPCODE_PUSHVAR: - { - ASSERT(opc == 1); - ASSERT(ops[0].type == OPTP_STRING); - - Object *key = Object_FromString(ops[0].as_string, -1, runtime->heap, error); - - if(key == NULL) - return 0; - - Object *locations[] = { - runtime->frame->locals, - runtime->frame->closure, - Runtime_GetBuiltins(runtime), - }; - - Object *obj = NULL; - - for(int p = 0; obj == NULL && (unsigned int) p < sizeof(locations)/sizeof(locations[0]); p += 1) - { - if(locations[p] == NULL) - continue; - - obj = Object_Select(locations[p], key, Runtime_GetHeap(runtime), error); - } - - if(obj == NULL) - { - if(error->occurred == 0) - // There's no such variable. - Error_Report(error, ErrorType_RUNTIME, "Reference to undefined variable \"%s\"", ops[0].as_string); - return 0; - } - - if(!Runtime_Push(runtime, error, obj)) - return 0; - - return 1; - } - - case OPCODE_PUSHNNE: - { - ASSERT(opc == 0); - - Object *obj = Object_NewNone(runtime->heap, error); - - if(obj == NULL) - return 0; - - if(!Runtime_Push(runtime, error, obj)) - return 0; - return 1; - } - - case OPCODE_PUSHTRU: - { - ASSERT(opc == 0); - - Object *obj = Object_FromBool(1, runtime->heap, error); - - if(obj == NULL) - return 0; - - if(!Runtime_Push(runtime, error, obj)) - return 0; - return 1; - } - - case OPCODE_PUSHFLS: - { - ASSERT(opc == 0); - - Object *obj = Object_FromBool(0, runtime->heap, error); - - if(obj == NULL) - return 0; - - if(!Runtime_Push(runtime, error, obj)) - return 0; - return 1; - } - - case OPCODE_PUSHFUN: - { - ASSERT(opc == 3); - ASSERT(ops[0].type == OPTP_IDX); - ASSERT(ops[1].type == OPTP_INT); - ASSERT(ops[2].type == OPTP_STRING); - - Object *closure = Object_NewClosure(runtime->frame->closure, runtime->frame->locals, Runtime_GetHeap(runtime), error); - - if(closure == NULL) - return 0; - - Object *obj = Object_FromNojaFunction(runtime, ops[2].as_string, runtime->frame->exe, ops[0].as_int, ops[1].as_int, closure, runtime->heap, error); - - if(obj == NULL) - return 0; - - if(!Runtime_Push(runtime, error, obj)) - return 0; - return 1; - } - - case OPCODE_PUSHLST: - { - ASSERT(opc == 1); - ASSERT(ops[0].type == OPTP_INT); - - Object *obj = Object_NewList(ops[0].as_int, runtime->heap, error); - - if(obj == NULL) - return 0; - - if(!Runtime_Push(runtime, error, obj)) - return 0; - return 1; - } - - case OPCODE_PUSHMAP: - { - ASSERT(opc == 1); - ASSERT(ops[0].type == OPTP_INT); - - Object *obj = Object_NewMap(ops[0].as_int, runtime->heap, error); - - if(obj == NULL) - return 0; - - if(!Runtime_Push(runtime, error, obj)) - return 0; - return 1; - } - - case OPCODE_PUSHNNETYP: - { - ASSERT(opc == 0); - - Object *obj = (Object*) Object_GetNoneType(); - ASSERT(obj != NULL); - - if(!Runtime_Push(runtime, error, obj)) - return 0; - return 1; - } - - case OPCODE_PUSHTYP: - { - ASSERT(opc == 0); - - if(runtime->frame->used < 1) - { - Error_Report(error, ErrorType_INTERNAL, "Frame has not enough values on the stack to run PUSHTYP instruction"); - return 0; - } - - Object *top = Stack_Top(runtime->stack, 0); - ASSERT(top != NULL); - - Object *typ = (Object*) Object_GetType(top); - ASSERT(typ != NULL); - - if(!Runtime_Push(runtime, error, typ)) - return 0; - return 1; - } - - case OPCODE_EXIT: - { - ASSERT(opc == 0); - - Object *vars = runtime->frame->locals; - ASSERT(vars != NULL); - - if(!Runtime_Push(runtime, error, vars)) - return 0; - return 0; - } - - case OPCODE_RETURN: - { - ASSERT(opc == 1); - ASSERT(ops[0].type == OPTP_INT); - int retc = ops[0].as_int; - UNUSED(retc); - ASSERT(retc >= 0); - ASSERT(retc <= MAX_RETS); - ASSERT(retc == runtime->frame->used); - return 0; - } - - case OPCODE_JUMP: - ASSERT(opc == 1); - ASSERT(ops[0].type == OPTP_IDX); - runtime->frame->index = ops[0].as_int; - return 1; - - case OPCODE_JUMPIFANDPOP: - { - ASSERT(opc == 1); - ASSERT(ops[0].type == OPTP_IDX); - - long long int target = ops[0].as_int; - - if(runtime->frame->used == 0) - { - Error_Report(error, ErrorType_INTERNAL, "Frame doesn't have enough items on the stack to execute JUMPIFNOTANDPOP"); - return 0; - } - - Object *top = Stack_Top(runtime->stack, 0); - - if(!Runtime_Pop(runtime, error, 1)) - return 0; - - ASSERT(top != NULL); - - if(!Object_IsBool(top)) - { - Error_Report(error, ErrorType_RUNTIME, "Not a boolean"); - return 0; - } - - if(Object_GetBool(top)) - runtime->frame->index = target; - - return 1; - } - - case OPCODE_JUMPIFNOTANDPOP: - { - ASSERT(opc == 1); - ASSERT(ops[0].type == OPTP_IDX); - - long long int target = ops[0].as_int; - - if(runtime->frame->used == 0) - { - Error_Report(error, ErrorType_INTERNAL, "Frame doesn't have enough items on the stack to execute JUMPIFNOTANDPOP"); - return 0; - } - - Object *top = Stack_Top(runtime->stack, 0); - - if(!Runtime_Pop(runtime, error, 1)) - return 0; - - ASSERT(top != NULL); - - if(!Object_IsBool(top)) - { - Error_Report(error, ErrorType_RUNTIME, "Not a boolean"); - return 0; - } - - if(!Object_GetBool(top)) - runtime->frame->index = target; - - return 1; - } - - default: - UNREACHABLE; - return 0; - } - - return 1; + return top; } -static _Bool collect(Runtime *runtime, Error *error) +void Runtime_SetInstructionIndex(Runtime *runtime, int index) +{ + ASSERT(runtime->frame->type == FrameType_NORMAL); + ((NormalFrame*) runtime->frame)->index = index; +} + +bool Runtime_SetVariable(Runtime *runtime, Error *error, const char *name, Object *value) { Frame *frame = runtime->frame; + if (frame == NULL) { + Error_Report(error, ErrorType_INTERNAL, "Can't assign a variable while there's no active frame"); + return false; + } + if (frame->type == FrameType_NATIVE) { + Error_Report(error, ErrorType_INTERNAL, "Can't set a variable from a native function"); + return 0; + } + if (frame->type == FrameType_FAILED) { + Error_Report(error, ErrorType_INTERNAL, "Can't set a variable after a compilation error"); + return 0; + } - if(!Heap_StartCollection(runtime->heap, error)) + ASSERT(frame->type == FrameType_NORMAL); + NormalFrame *normal_frame = (NormalFrame*) frame; + + Heap *heap = Runtime_GetHeap(runtime); + Object *key = Object_FromString(name, -1, heap, error); + if(key == NULL) + return false; + + return Object_Insert(normal_frame->locals, key, value, heap, error); +} + +bool Runtime_GetVariable(Runtime *runtime, Error *error, const char *name, Object **value) +{ + ASSERT(value != NULL); + + Frame *frame = runtime->frame; + if (frame == NULL) { + Error_Report(error, ErrorType_INTERNAL, "Can't load a variable while there is no active frame"); + return 0; + } + if (frame->type == FrameType_NATIVE) { + Error_Report(error, ErrorType_INTERNAL, "Can't load a variable from a native function"); + return 0; + } + if (frame->type == FrameType_FAILED) { + Error_Report(error, ErrorType_INTERNAL, "Can't load a variable after a compilation error"); + return 0; + } + + ASSERT(frame->type == FrameType_NORMAL); + NormalFrame *normal_frame = (NormalFrame*) frame; + + Heap *heap = Runtime_GetHeap(runtime); + Object *key = Object_FromString(name, -1, heap, error); + if(key == NULL) + return false; + + Object *locations[] = { + normal_frame->locals, + normal_frame->closure, + runtime->builtins, + }; + + Object *obj = NULL; + + for(int p = 0; obj == NULL && (unsigned int) p < sizeof(locations)/sizeof(locations[0]); p += 1) + { + if(locations[p] == NULL) + continue; + + obj = Object_Select(locations[p], key, heap, error); + } + + *value = obj; + return true; +} + +Object *Runtime_GetClosure(Runtime *runtime) +{ + ASSERT(runtime->frame != NULL && runtime->frame->type == FrameType_NORMAL); + NormalFrame *normal_frame = (NormalFrame*) runtime->frame; + return normal_frame->closure; +} + +Object *Runtime_GetLocals(Runtime *runtime) +{ + ASSERT(runtime->frame != NULL && runtime->frame->type == FrameType_NORMAL); + NormalFrame *normal_frame = (NormalFrame*) runtime->frame; + return normal_frame->locals; +} + +size_t Runtime_GetFrameStackUsage(Runtime *runtime) +{ + if (runtime->frame->type != FrameType_NORMAL) + return 0; + return ((NormalFrame*) runtime->frame)->used; +} + +static bool appendFrame(Runtime *runtime, Error *error, Frame *frame) +{ + if(runtime->depth == MAX_FRAMES) { + Error_Report(error, ErrorType_INTERNAL, "Maximum nested call limit of %d was reached", MAX_FRAMES); + return false; + } + frame->prev = runtime->frame; + runtime->frame = frame; + runtime->depth++; + return true; +} + +bool Runtime_PushFailedFrame(Runtime *runtime, Error *error, Source *source, int offset) +{ + FailedFrame *failed_frame = &runtime->failed_frame; + + Source *source_copy = Source_Copy(source); + if (source_copy == NULL) { + Error_Report(error, ErrorType_INTERNAL, "Failed to copy source object"); + return false; + } + + failed_frame->base.type = FrameType_FAILED; + failed_frame->base.prev = NULL; + failed_frame->source = source; + failed_frame->offset = offset; + + if (!appendFrame(runtime, error, (Frame*) failed_frame)) { + Source_Free(source_copy); + return false; + } + return true; +} + +bool Runtime_PushNativeFrame(Runtime *runtime, Error *error) +{ + NativeFrame *native_frame = malloc(sizeof(NativeFrame)); + if (native_frame == NULL) { + Error_Report(error, ErrorType_INTERNAL, "Out of memory"); + return false; + } + + native_frame->base.type = FrameType_NATIVE; + native_frame->base.prev = NULL; + + if (!appendFrame(runtime, error, (Frame*) native_frame)) { + free(native_frame); + return false; + } + return true; +} + +bool Runtime_PushFrame(Runtime *runtime, Error *error, Object *closure, Executable *exe, int index) +{ + NormalFrame *frame = malloc(sizeof(NormalFrame)); + if (frame == NULL) { + Error_Report(error, ErrorType_INTERNAL, "Out of memory"); + return false; + } + + Object *locals = Object_NewMap(-1, runtime->heap, error); + if (locals == NULL) { + free(frame); + return false; + } + + Executable *exe_copy = Executable_Copy(exe); + if (exe_copy == NULL) { + free(frame); + Error_Report(error, ErrorType_INTERNAL, "Failed to copy executable"); + return false; + } + + frame->base.type = FrameType_NORMAL; + frame->base.prev = NULL; + frame->closure = closure; + frame->exe = exe_copy; + frame->index = index; + frame->used = 0; + frame->locals = locals; + + if (!appendFrame(runtime, error, (Frame*) frame)) { + free(frame); + Executable_Free(exe_copy); + return false; + } + return true; +} + +bool Runtime_PopFrame(Runtime *runtime) +{ + Frame *frame = runtime->frame; + if (frame == NULL) + return false; + runtime->frame = frame->prev; + runtime->depth--; + switch(frame->type) { + case FrameType_NORMAL: { + NormalFrame *normal_frame = (NormalFrame*) frame; + Stack_Pop(runtime->stack, normal_frame->used); + Executable_Free(normal_frame->exe); + free(frame); + break; + } + case FrameType_NATIVE: { + NativeFrame *native_frame = (NativeFrame*) frame; + UNUSED(native_frame); + free(frame); + break; + } + case FrameType_FAILED: { + FailedFrame *failed_frame = (FailedFrame*) frame; + Source_Free(failed_frame->source); + break; + } + } + return true; +} + +RuntimeCallback Runtime_GetCallback(Runtime *runtime) +{ + return runtime->callback; +} + +bool Runtime_WasInterrupted(Runtime *runtime) +{ + return runtime->interrupt; +} + +bool Runtime_CollectGarbage(Runtime *runtime, Error *error) +{ + Frame *frame = runtime->frame; + Heap *heap = runtime->heap; + + if(!Heap_StartCollection(heap, error)) return 0; Heap_CollectReference(&runtime->builtins, runtime->heap); while(frame) { - Heap_CollectReference(&frame->locals, runtime->heap); - Heap_CollectReference(&frame->closure, runtime->heap); + if (frame->type == FrameType_NORMAL) { + NormalFrame *normal_frame = (NormalFrame*) frame; + Heap_CollectReference(&normal_frame->locals, heap); + Heap_CollectReference(&normal_frame->closure, heap); + } frame = frame->prev; } - for(unsigned int i = 0; i < Stack_Size(runtime->stack); i += 1) + Stack *stack = Runtime_GetStack(runtime); + for(unsigned int i = 0; i < Stack_Size(stack); i += 1) { - Object **ref = (Object**) Stack_TopRef(runtime->stack, -i); + Object **ref = (Object**) Stack_TopRef(stack, -i); ASSERT(ref != NULL); Heap_CollectReference(ref, runtime->heap); } return Heap_StopCollection(runtime->heap); -} - -int run(Runtime *runtime, Error *error, - Executable *exe, int index, - Object *closure, - Object **argv, int argc, - Object *rets[static MAX_RETS]) -{ - ASSERT(runtime != NULL); - ASSERT(error != NULL); - ASSERT(exe != NULL); - ASSERT(index >= 0); - ASSERT(argc >= 0); - - if(runtime->depth == MAX_FRAMES) - { - Error_Report(error, ErrorType_INTERNAL, "Maximum nested call limit of %d was reached", MAX_FRAMES); - return -1; - } - - ASSERT(runtime->depth < MAX_FRAMES); - - // Initialize the frame. - Frame frame; - { - frame.prev = NULL; - frame.closure = closure; - frame.locals = Object_NewMap(-1, runtime->heap, error); - frame.exe = Executable_Copy(exe); - frame.index = index; - frame.used = 0; - - if(frame.locals == NULL) - return -1; - - if(frame.exe == NULL) - { - Error_Report(error, ErrorType_INTERNAL, "Failed to copy executable"); - return -1; - } - - // Add the frame to the runtime. - frame.prev = runtime->frame; - runtime->frame = &frame; - runtime->depth += 1; - } - - // This is what the function will return. - int retc = -1; - - // Push the initial values of the frame. - for(int i = 0; i < argc; i += 1) - if(!Runtime_Push(runtime, error, argv[i])) - goto cleanup; - - // Run the code. - - - if(runtime->interrupt || (runtime->callback.func != NULL && !runtime->callback.func(runtime, runtime->callback.data))) - Error_Report(error, ErrorType_RUNTIME, "Forced abortion"); - else - while(step(runtime, error)) - { - if(runtime->interrupt || (runtime->callback.func != NULL && !runtime->callback.func(runtime, runtime->callback.data))) - { - Error_Report(error, ErrorType_RUNTIME, "Forced abortion"); - break; - } - - //printf("%2.2f%% percent.\n", Heap_GetUsagePercentage(runtime->heap)); - - if(Heap_GetUsagePercentage(runtime->heap) > 100) - if(!collect(runtime, error)) - break; - } - - // If an error occurred, we want to return NULL. - if(error->occurred == 0) - { - retc = frame.used; - ASSERT(retc <= MAX_RETS); - - for(int i = 0; i < retc; i += 1) - { - rets[i] = Stack_Top(runtime->stack, i - retc + 1); - ASSERT(rets[i] != NULL); - } - } - -cleanup: - // Remove the frame-owned items from the stack. - // This can't fail. - (void) Stack_Pop(runtime->stack, frame.used); - - // Deinitialize the frame. - { - // Remove the frame from the runtime. - runtime->frame = runtime->frame->prev; - runtime->depth -= 1; - - // Deallocate the fields. - Executable_Free(frame.exe); - } - - return retc; } \ No newline at end of file diff --git a/src/lib/runtime/runtime.h b/src/lib/runtime/runtime.h index 284ba13..5133301 100644 --- a/src/lib/runtime/runtime.h +++ b/src/lib/runtime/runtime.h @@ -40,6 +40,7 @@ typedef struct xRuntime Runtime; typedef struct xSnapshot Snapshot; +typedef struct StaticMapSlot StaticMapSlot; typedef struct { bool (*func)(Runtime*, void*); @@ -48,31 +49,49 @@ typedef struct { typedef struct { bool time; + size_t heap; size_t stack; RuntimeCallback callback; } RuntimeConfig; -Runtime* Runtime_New(int heap_size, RuntimeConfig config); -Runtime* Runtime_New2(Heap *heap, bool free_it, RuntimeConfig config); +Runtime* Runtime_New(RuntimeConfig config); void Runtime_Free(Runtime *runtime); -_Bool Runtime_Pop(Runtime *runtime, Error *error, unsigned int n); -_Bool Runtime_Push(Runtime *runtime, Error *error, Object *obj); + +Object *Runtime_Top(Runtime *runtime, int n); +bool Runtime_Pop (Runtime *runtime, Error *error, Object **p, unsigned int n); +bool Runtime_Push(Runtime *runtime, Error *error, Object *obj); +bool Runtime_PushFrame(Runtime *runtime, Error *error, Object *closure, Executable *exe, int index); +bool Runtime_PushNativeFrame(Runtime *runtime, Error *error); +bool Runtime_PushFailedFrame(Runtime *runtime, Error *error, Source *source, int offset); +bool Runtime_PopFrame(Runtime *runtime); +void Runtime_SetInstructionIndex(Runtime *runtime, int index); +bool Runtime_SetVariable(Runtime *runtime, Error *error, const char *name, Object *value); +bool Runtime_GetVariable(Runtime *runtime, Error *error, const char *name, Object **value); +Object *Runtime_GetLocals(Runtime *runtime); +Object *Runtime_GetClosure(Runtime *runtime); +size_t Runtime_GetFrameStackUsage(Runtime *runtime); +bool Runtime_WasInterrupted(Runtime *runtime); +RuntimeCallback Runtime_GetCallback(Runtime *runtime); +bool Runtime_CollectGarbage(Runtime *runtime, Error *error); +void Runtime_PrintStackTrace(Runtime *runtime, FILE *stream); void Runtime_Interrupt(Runtime *runtime); Heap* Runtime_GetHeap(Runtime *runtime); Stack* Runtime_GetStack(Runtime *runtime); -Object* Runtime_GetBuiltins(Runtime *runtime); -void Runtime_SetBuiltins(Runtime *runtime, Object *builtins); int Runtime_GetCurrentIndex(Runtime *runtime); Executable *Runtime_GetCurrentExecutable(Runtime *runtime); +Executable *Runtime_GetMostRecentExecutable(Runtime *runtime); size_t Runtime_GetCurrentScriptFolder(Runtime *runtime, char *buff, size_t buffsize); const char *Runtime_GetCurrentScriptAbsolutePath(Runtime *runtime); TimingTable *Runtime_GetTimingTable(Runtime *runtime); RuntimeConfig Runtime_GetDefaultConfigs(); +bool Runtime_plugBuiltins(Runtime *runtime, Object *object, Error *error); +int Runtime_runSource(Runtime *runtime, Source *source, Object *rets[static MAX_RETS], Error *error); +int Runtime_runBytecodeSource(Runtime *runtime, Source *source, Object *rets[static MAX_RETS], Error *error); + Snapshot *Snapshot_New(Runtime *runtime); void Snapshot_Free(Snapshot *snapshot); void Snapshot_Print(Snapshot *snapshot, FILE *fp); -int run(Runtime *runtime, Error *error, Executable *exe, int index, Object *closure, Object **argv, int argc, Object *rets[static MAX_RETS]); typedef enum { SM_END, @@ -87,8 +106,6 @@ typedef enum { SM_OBJECT, } StaticMapSlotKind; -typedef struct StaticMapSlot StaticMapSlot; - struct StaticMapSlot { const char *name; StaticMapSlotKind kind; @@ -117,5 +134,6 @@ typedef struct { void RuntimeError_Init(RuntimeError *error, Runtime *runtime); void RuntimeError_Free(RuntimeError *error); +void RuntimeError_Print(RuntimeError *error, ErrorType type_if_unspecified, FILE *stream); #endif \ No newline at end of file diff --git a/src/lib/runtime/runtime_error.c b/src/lib/runtime/runtime_error.c deleted file mode 100644 index ff96401..0000000 --- a/src/lib/runtime/runtime_error.c +++ /dev/null @@ -1,59 +0,0 @@ - -/* +--------------------------------------------------------------------------+ -** | _ _ _ | -** | | \ | | (_) | -** | | \| | ___ _ __ _ | -** | | . ` |/ _ \| |/ _` | | -** | | |\ | (_) | | (_| | | -** | |_| \_|\___/| |\__,_| | -** | _/ | | -** | |__/ | -** +--------------------------------------------------------------------------+ -** | Copyright (c) 2022 Francesco Cozzuto | -** +--------------------------------------------------------------------------+ -** | This file is part of The Noja Interpreter. | -** | | -** | The Noja Interpreter is free software: you can redistribute it and/or | -** | modify it under the terms of the GNU General Public License as published | -** | by the Free Software Foundation, either version 3 of the License, or (at | -** | your option) any later version. | -** | | -** | The Noja Interpreter is distributed in the hope that it will be useful, | -** | but WITHOUT ANY WARRANTY; without even the implied warranty of | -** | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General | -** | Public License for more details. | -** | | -** | You should have received a copy of the GNU General Public License along | -** | with The Noja Interpreter. If not, see . | -** +--------------------------------------------------------------------------+ -*/ - -#include -#include "runtime.h" - -static void on_report(Error *error) -{ - assert(error != NULL); - - RuntimeError *error2 = (RuntimeError*) error; - - if(error2->runtime != NULL) - error2->snapshot = Snapshot_New(error2->runtime); -} - -void RuntimeError_Init(RuntimeError *error, Runtime *runtime) -{ - assert(error != NULL); - - Error_Init2(&error->base, on_report); - - error->runtime = runtime; - error->snapshot = NULL; -} - -void RuntimeError_Free(RuntimeError *error) -{ - if(error->snapshot) - Snapshot_Free(error->snapshot); - Error_Free(&error->base); -} \ No newline at end of file diff --git a/src/lib/runtime/serialize_profiling.c b/src/lib/runtime/serialize_profiling.c new file mode 100644 index 0000000..d2890d8 --- /dev/null +++ b/src/lib/runtime/serialize_profiling.c @@ -0,0 +1,31 @@ +#include "serialize_profiling.h" + +void Runtime_SerializeProfilingResultsToStream(Runtime *runtime, FILE *stream) +{ + TimingTable *table = Runtime_GetTimingTable(runtime); + if (table == NULL) + return; // Runtime wasn't profiling + + size_t count; + const FunctionExecutionSummary *summary = TimingTable_getSummary(table, &count); + + for (size_t i = 0; i < count; i++) { + if (summary[i].calls > 0) { + fprintf(stream, "%20s - %s - %ld calls - %.2lfus\n", + summary[i].name, + Source_GetName(summary[i].src), + summary[i].calls, + summary[i].time * 1000000); + } + } +} + +bool Runtime_SerializeProfilingResultsToFile(Runtime *runtime, const char *file) +{ + FILE *stream = fopen(file, "wb"); + if (stream == NULL) + return false; + Runtime_SerializeProfilingResultsToStream(runtime, stream); + fclose(stream); + return true; +} \ No newline at end of file diff --git a/src/lib/runtime/serialize_profiling.h b/src/lib/runtime/serialize_profiling.h new file mode 100644 index 0000000..1ca940d --- /dev/null +++ b/src/lib/runtime/serialize_profiling.h @@ -0,0 +1,4 @@ +#include "runtime.h" + +void Runtime_SerializeProfilingResultsToStream(Runtime *runtime, FILE *stream); +bool Runtime_SerializeProfilingResultsToFile(Runtime *runtime, const char *file); diff --git a/src/lib/utils/error.c b/src/lib/utils/error.c index 439f003..4feb7f5 100644 --- a/src/lib/utils/error.c +++ b/src/lib/utils/error.c @@ -137,7 +137,7 @@ void Error_Panic_(const char *file, int line, abort(); } -void Error_Print(Error *error, ErrorType type_if_unspecified) +void Error_Print(Error *error, ErrorType type_if_unspecified, FILE *stream) { ErrorType type = error->type; if (type == ErrorType_UNSPECIFIED) @@ -152,19 +152,19 @@ void Error_Print(Error *error, ErrorType type_if_unspecified) default: name = "Error"; break; } - fprintf(stderr, "%s: %s.", name, error->message); + fprintf(stream, "%s: %s.", name, error->message); #ifdef DEBUG if(error->file != NULL) { if(error->line > 0 && error->func != NULL) - fprintf(stderr, " (Reported in %s:%d in %s)", error->file, error->line, error->func); + fprintf(stream, " (Reported in %s:%d in %s)", error->file, error->line, error->func); else if(error->line > 0 && error->func == NULL) - fprintf(stderr, " (Reported in %s:%d)", error->file, error->line); + fprintf(stream, " (Reported in %s:%d)", error->file, error->line); else if(error->line < 1 && error->func != NULL) - fprintf(stderr, " (Reported in %s in %s)", error->file, error->func); + fprintf(stream, " (Reported in %s in %s)", error->file, error->func); } #endif - fprintf(stderr, "\n"); + fprintf(stream, "\n"); } \ No newline at end of file diff --git a/src/lib/utils/error.h b/src/lib/utils/error.h index b8cdf61..4a5ddaf 100644 --- a/src/lib/utils/error.h +++ b/src/lib/utils/error.h @@ -30,6 +30,8 @@ #ifndef ERROR_H #define ERROR_H + +#include #include typedef enum { @@ -63,5 +65,5 @@ void _Error_Report (Error *err, ErrorType typ, const char *file, const char *f void _Error_Report2(Error *err, ErrorType typ, const char *file, const char *func, int line, const char *fmt, va_list va); void Error_Panic_(const char *file, int line, const char *fmt, ...); #define Error_Panic(fmt, ...) Error_Panic_(__FILE__, __LINE__, fmt, ## __VA_ARGS__) -void Error_Print(Error *error, ErrorType type_if_unspecified); +void Error_Print(Error *error, ErrorType type_if_unspecified, FILE *stream); #endif \ No newline at end of file diff --git a/src/test/main.c b/src/test/main.c index f1231e6..41f136c 100644 --- a/src/test/main.c +++ b/src/test/main.c @@ -284,14 +284,14 @@ static bool parseTestCaseSource(Source *source, TestCase *testcase) testcase->source = Source_FromString("", str + source_offset, source_length, &error); if(testcase->source == NULL) { - Error_Print(&error, ErrorType_UNSPECIFIED); + Error_Print(&error, ErrorType_UNSPECIFIED, stderr); Error_Free(&error); return false; } testcase->bytecode = Source_FromString("", str + bytecode_offset, bytecode_length, &error); if(testcase->bytecode == NULL) { - Error_Print(&error, ErrorType_UNSPECIFIED); + Error_Print(&error, ErrorType_UNSPECIFIED, stderr); Source_Free(testcase->source); Error_Free(&error); return false; @@ -303,11 +303,12 @@ static bool parseTestCaseSource(Source *source, TestCase *testcase) static Executable *compile_source_and_print_error_on_failure(Source *src) { + int error_offset; Error error; Error_Init(&error); - Executable *exe = compile(src, &error); + Executable *exe = compile(src, &error, &error_offset); if(exe == NULL) { - Error_Print(&error, ErrorType_UNSPECIFIED); + Error_Print(&error, ErrorType_UNSPECIFIED, stderr); Error_Free(&error); return NULL; } @@ -330,7 +331,7 @@ static TestResult runTest(const char *file) source = Source_FromFile(file, &error); if(source == NULL) { - Error_Print(&error, ErrorType_UNSPECIFIED); + Error_Print(&error, ErrorType_UNSPECIFIED, stderr); Error_Free(&error); return TestResult_ABORT; } @@ -358,10 +359,10 @@ static TestResult runTest(const char *file) { Error error; Error_Init(&error); - - exe2 = assemble(testcase.bytecode, &error); + int error_offset; + exe2 = assemble(testcase.bytecode, &error, &error_offset); if(exe2 == NULL) { - Error_Print(&error, ErrorType_UNSPECIFIED); + Error_Print(&error, ErrorType_UNSPECIFIED, stderr); Error_Free(&error); Executable_Free(exe1); Source_Free(testcase.source);