From 87e9f4775b963a22f9a7ef38ed3a112ae59466bd Mon Sep 17 00:00:00 2001 From: cozis Date: Tue, 2 Nov 2021 13:19:06 +0000 Subject: [PATCH] added function calls, native function objects and runtime builtins --- build.sh | 6 ++- samples/func.noja | 6 +-- src/common/executable.c | 2 +- src/common/executable.h | 1 + src/compiler/ASTi.h | 8 +++ src/compiler/compile.c | 26 ++++++++++ src/compiler/parse.c | 97 +++++++++++++++++++++++++++++++++++- src/main.c | 15 ++++-- src/runtime/builtins.c | 49 ++++++++++++++++++ src/runtime/o_nfunc.c | 108 ++++++++++++++++++++++++++++++++++++++++ src/runtime/runtime.c | 83 +++++++++++++++++++++++++++--- src/runtime/runtime.h | 6 ++- 12 files changed, 389 insertions(+), 18 deletions(-) create mode 100644 src/runtime/builtins.c create mode 100644 src/runtime/o_nfunc.c diff --git a/build.sh b/build.sh index 2130553..89cfb2a 100755 --- a/build.sh +++ b/build.sh @@ -1,6 +1,6 @@ USING_VALGRIND=0 -FLAGS="-L3p/libs/ -I3p/include/ -Wall -Wextra -g -DUSING_VALGRIND=$USING_VALGRIND -DTRACE_SOURCE_COPIES=$TRACE_SOURCE_COPIES" +FLAGS="-L3p/libs/ -I3p/include/ -Wall -Wextra -g -DUSING_VALGRIND=$USING_VALGRIND" mkdir temp @@ -34,6 +34,8 @@ gcc -c src/common/executable.c -o temp/common/executable.o $FLAGS mkdir temp/runtime gcc -c src/runtime/runtime_error.c -o temp/runtime/runtime_error.o $FLAGS gcc -c src/runtime/runtime.c -o temp/runtime/runtime.o $FLAGS +gcc -c src/runtime/builtins.c -o temp/runtime/builtins.o $FLAGS +gcc -c src/runtime/o_nfunc.c -o temp/runtime/o_nfunc.o $FLAGS gcc -c src/runtime/o_func.c -o temp/runtime/o_func.o $FLAGS rm -rf build @@ -76,6 +78,8 @@ gcc src/main.c src/debug.c \ temp/objects/o_string.o \ temp/runtime/runtime.o \ temp/runtime/runtime_error.o \ + temp/runtime/builtins.o \ + temp/runtime/o_nfunc.o \ temp/runtime/o_func.o \ temp/common/executable.o \ -o build/noja $FLAGS -Lbuild/ -lnoja-compile -lnoja-objects -lxjson diff --git a/samples/func.noja b/samples/func.noja index 6b0595c..55cc24a 100644 --- a/samples/func.noja +++ b/samples/func.noja @@ -1,7 +1,5 @@ fun hello() - { - print;('hello'); - } + print('hel\nlo', 2); -return hello; \ No newline at end of file +return hello(); \ No newline at end of file diff --git a/src/common/executable.c b/src/common/executable.c index 0ea4510..c6a74a1 100644 --- a/src/common/executable.c +++ b/src/common/executable.c @@ -49,7 +49,7 @@ static const InstrInfo instr_table[] = { [OPCODE_DIV] = {"DIV", 0, NULL}, [OPCODE_ASS] = {"ASS", 1, (OperandType[]) {OPTP_STRING}}, [OPCODE_POP] = {"POP", 1, (OperandType[]) {OPTP_INT}}, - + [OPCODE_CALL] = {"CALL", 1, (OperandType[]) {OPTP_INT}}, [OPCODE_PUSHINT] = {"PUSHINT", 1, (OperandType[]) {OPTP_INT}}, [OPCODE_PUSHFLT] = {"PUSHFLT", 1, (OperandType[]) {OPTP_FLOAT}}, [OPCODE_PUSHSTR] = {"PUSHSTR", 1, (OperandType[]) {OPTP_STRING}}, diff --git a/src/common/executable.h b/src/common/executable.h index fa49ed9..f9a69a5 100644 --- a/src/common/executable.h +++ b/src/common/executable.h @@ -30,6 +30,7 @@ typedef enum { OPCODE_DIV, OPCODE_ASS, OPCODE_POP, + OPCODE_CALL, OPCODE_PUSHINT, OPCODE_PUSHFLT, OPCODE_PUSHSTR, diff --git a/src/compiler/ASTi.h b/src/compiler/ASTi.h index 79e7f36..837c2d4 100644 --- a/src/compiler/ASTi.h +++ b/src/compiler/ASTi.h @@ -22,6 +22,7 @@ typedef enum { EXPR_DIV, EXPR_ASS, EXPR_INT, + EXPR_CALL, EXPR_NONE, EXPR_TRUE, EXPR_FALSE, @@ -79,6 +80,13 @@ typedef struct { int count; } OperExprNode; +typedef struct { + ExprNode base; + Node *func; + Node *argv; + int argc; +} CallExprNode; + typedef struct { Node base; Node *condition; diff --git a/src/compiler/compile.c b/src/compiler/compile.c index 206d470..7a71aa0 100644 --- a/src/compiler/compile.c +++ b/src/compiler/compile.c @@ -101,6 +101,27 @@ static _Bool emit_instr_for_node(ExeBuilder *exeb, Node *node, Error *error) return ExeBuilder_Append(exeb, error, OPCODE_PUSHVAR, &op, 1, node->offset, node->length); } + case EXPR_CALL: + { + CallExprNode *p = (CallExprNode*) expr; + + Node *arg = p->argv; + + while(arg) + { + if(!emit_instr_for_node(exeb, arg, error)) + return 0; + + arg = arg->next; + } + + if(!emit_instr_for_node(exeb, p->func, error)) + return 0; + + Operand op = { .type = OPTP_INT, .as_int = p->argc }; + return ExeBuilder_Append(exeb, error, OPCODE_CALL, &op, 1, node->offset, node->length); + } + case EXPR_NONE: return ExeBuilder_Append(exeb, error, OPCODE_PUSHNNE, NULL, 0, node->offset, node->length); @@ -310,6 +331,11 @@ static _Bool emit_instr_for_node(ExeBuilder *exeb, Node *node, Error *error) if(!emit_instr_for_node(exeb, func->body, error)) return 0; + + // Write a return instruction, just + // in case it didn't already return. + if(!ExeBuilder_Append(exeb, error, OPCODE_RETURN, NULL, 0, func->body->offset, 0)) + return 0; } // This is the first index after the function code. diff --git a/src/compiler/parse.c b/src/compiler/parse.c index 67b45c2..a7f07f5 100644 --- a/src/compiler/parse.c +++ b/src/compiler/parse.c @@ -53,6 +53,7 @@ static Node *parse_expression_statement(Context *ctx); static Node *parse_ifelse_statement(Context *ctx); static Node *parse_compound_statement(Context *ctx, TokenKind end); static Node *parse_function_definition(Context *ctx); +static Node *parse_postfix_expression(Context *ctx); static inline _Bool isoper(char c) { @@ -859,6 +860,98 @@ static Node *parse_primary_expresion(Context *ctx) return NULL; } +static Node *parse_postfix_expression(Context *ctx) +{ + assert(ctx != NULL); + + Node *node = parse_primary_expresion(ctx); + + if(node == NULL) + return NULL; + + while(1) + { + switch(current(ctx)) + { + case '(': + { + int offset = current_token(ctx)->offset; + + Node *argv = NULL; + int argc = 0; + + next(ctx); // Skip the '('. + + if(current(ctx) != ')') + while(1) + { + // Parse. + Node *arg = parse_expression(ctx); + + if(arg == NULL) + return NULL; + + // Append. + arg->next = argv; + argv = arg; + argc += 1; + + // Get ',' or ')'. + + if(current(ctx) == ')') + break; + + if(current(ctx) != ',') + { + if(current(ctx) == TDONE) + Error_Report(ctx->error, 0, "Source ended inside a function argument list"); + else + Error_Report(ctx->error, 0, "Got unexpected token \"%.*s\" where ',' or ')' were expected", ctx->token->length, ctx->src + ctx->token->offset); + return NULL; + } + + next(ctx); // Skip the ','. + } + + int length = current_token(ctx)->offset + + current_token(ctx)->length + - offset; + + next(ctx); // Skip the ')'. + + CallExprNode *call; + { + call = BPAlloc_Malloc(ctx->alloc, sizeof(CallExprNode)); + + if(call == NULL) + { + Error_Report(ctx->error, 1, "No memory"); + return NULL; + } + + call->base.base.kind = NODE_EXPR; + call->base.base.next = NULL; + call->base.base.offset = offset; + call->base.base.length = length; + call->base.kind = EXPR_CALL; + call->func = node; + call->argv = argv; + call->argc = argc; + } + + node = (Node*) call; + break; + } + + default: + goto done; + } // End switch. + } // End loop. +done: + + return node; +} + static inline _Bool isbinop(Token *tok) { assert(tok != NULL); @@ -911,7 +1004,7 @@ static Node *parse_expression_2(Context *ctx, Node *left_expr, int min_prec) ctx->token = ctx->token->next; assert(ctx->token != NULL); - Node *right_expr = parse_primary_expresion(ctx); + Node *right_expr = parse_postfix_expression(ctx); if(right_expr == NULL) return NULL; @@ -966,7 +1059,7 @@ static Node *parse_expression_2(Context *ctx, Node *left_expr, int min_prec) static Node *parse_expression(Context *ctx) { - Node *left_expr = parse_primary_expresion(ctx); + Node *left_expr = parse_postfix_expression(ctx); if(left_expr == NULL) return NULL; diff --git a/src/main.c b/src/main.c index 71b19bd..ffac6d1 100644 --- a/src/main.c +++ b/src/main.c @@ -220,9 +220,19 @@ int main(int argc, char **argv) RuntimeError error; RuntimeError_Init(&error, runtime); + if(!add_builtins(runtime, (Error*) &error)) + { + fprintf(stderr, "Couldn't load builtins: %s\n", error.base.message); + Debug_Free(dbg); + Source_Free(src); + Executable_Free(exe); + RuntimeError_Free(&error); + return 1; + } + Object *result = run(runtime, (Error*) &error, exe, 0, NULL, 0); - if(result == NULL || !Object_Print(result, stderr, (Error*) &error)) + if(result == NULL) { fprintf(stderr, "RUNTIME ERROR: %s.\n", error.base.message); @@ -238,8 +248,7 @@ int main(int argc, char **argv) Runtime_Free(runtime); return 1; } - - fprintf(stderr, "\n"); + Runtime_Free(runtime); Debug_Free(dbg); } diff --git a/src/runtime/builtins.c b/src/runtime/builtins.c new file mode 100644 index 0000000..88bede3 --- /dev/null +++ b/src/runtime/builtins.c @@ -0,0 +1,49 @@ +#include +#include "runtime.h" + +static Object *bin_print(Runtime *runtime, Object **argv, unsigned int argc, Error *error) +{ + for(int i = 0; i < (int) argc; i += 1) + Object_Print(argv[i], stdout, error); + + return Object_NewNone(Runtime_GetHeap(runtime), error); +} + +typedef struct { + const char *name; + int argc; + Object *(*callback)(Runtime*, Object**, unsigned int, Error*); +} BuiltinNativeFunctions; + +BuiltinNativeFunctions builtins[] = { + {"print", -1, bin_print}, +}; + +_Bool add_builtins(Runtime *runtime, Error *error) +{ + Heap *heap = Runtime_GetHeap(runtime); + assert(heap != NULL); + + Object *dest = Runtime_GetBuiltins(runtime, error); + + if(dest == NULL) + return 0; + + for(int i = 0; i < (int) (sizeof(builtins) / sizeof(builtins[0])); i += 1) + { + Object *name = Object_FromString(builtins[i].name, -1, heap, error); + + if(name == NULL) + return 0; + + Object *func = Object_FromNativeFunction(runtime, builtins[i].callback, builtins[i].argc, heap, error); + + if(func == NULL) + return NULL; + + if(!Object_Insert(dest, name, func, heap, error)) + return 0; + } + + return 1; +} \ No newline at end of file diff --git a/src/runtime/o_nfunc.c b/src/runtime/o_nfunc.c new file mode 100644 index 0000000..18f591a --- /dev/null +++ b/src/runtime/o_nfunc.c @@ -0,0 +1,108 @@ +#include +#include +#include "../utils/defs.h" +#include "../objects/objects.h" +#include "runtime.h" + +typedef struct { + Object base; + Runtime *runtime; + Object *(*callback)(Runtime *runtime, Object **argv, unsigned int argc, Error *error); + int argc; +} NativeFunctionObject; + +static Object *call(Object *self, Object **argv, unsigned int argc, Heap *heap, Error *error); + +static const Type t_nfunc = { + .base = (Object) { .type = &t_type, .flags = Object_STATIC }, + .name = "native function", + .size = sizeof (NativeFunctionObject), + .call = call, +}; + +static Object *call(Object *self, Object **argv, unsigned int argc, 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); + + if(argv2 == NULL) + { + Error_Report(error, 1, "No memory"); + return NULL; + } + + // 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 0; + } + } + else UNREACHABLE; + + assert(func->callback != NULL); + Object *result = func->callback(func->runtime, argv2, argc2, error); + + if(result == NULL && error->occurred == 0) + Error_Report(error, 1, "Native callback returned NULL but didn't report errors"); + + if(argv2 != argv) + free(argv2); + + return result; +} + +Object *Object_FromNativeFunction(Runtime *runtime, Object *(*callback)(Runtime*, Object**, unsigned int, Error*), int argc, Heap *heap, Error *error) +{ + assert(callback != NULL); + assert(heap != NULL); + assert(error != 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/runtime/runtime.c b/src/runtime/runtime.c index 9d668f0..6c0cf42 100644 --- a/src/runtime/runtime.c +++ b/src/runtime/runtime.c @@ -17,12 +17,18 @@ struct Frame { struct xRuntime { void *callback_userp; _Bool (*callback_addr)(Runtime*, void*); + Object *builtins; int depth; Frame *frame; Stack *stack; Heap *heap; }; +Heap *Runtime_GetHeap(Runtime *runtime) +{ + return runtime->heap; +} + int Runtime_GetCurrentIndex(Runtime *runtime) { if(runtime->depth == 0) @@ -73,8 +79,10 @@ Runtime *Runtime_New(int stack_size, int heap_size, void *callback_userp, _Bool runtime->callback_userp = callback_userp; runtime->callback_addr = callback_addr; + runtime->builtins = NULL; runtime->frame = NULL; runtime->depth = 0; + } return runtime; @@ -87,6 +95,14 @@ void Runtime_Free(Runtime *runtime) free(runtime); } +Object *Runtime_GetBuiltins(Runtime *runtime, Error *error) +{ + if(runtime->builtins == NULL) + runtime->builtins = Object_NewMap(-1, runtime->heap, error); + + return runtime->builtins; +} + _Bool Runtime_Push(Runtime *runtime, Error *error, Object *obj) { assert(runtime != NULL); @@ -418,6 +434,51 @@ static _Bool step(Runtime *runtime, Error *error) return 1; } + case OPCODE_CALL: + { + assert(opc == 1); + assert(ops[0].type == OPTP_INT); + + int argc = ops[0].as_int; + assert(argc >= 0); + + if(runtime->frame->used < argc + 1) + { + Error_Report(error, 1, "Frame doesn't own enough objects to execute call"); + return 0; + } + + Object *callable = Stack_Top(runtime->stack, 0); + assert(callable != NULL); + + Object *argv[8]; + + int max_argc = sizeof(argv) / sizeof(argv[0]); + if(argc > max_argc) + { + Error_Report(error, 1, "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); + } + + (void) Runtime_Pop(runtime, error, argc+1); + assert(error->occurred == 0); + + Object *obj = Object_Call(callable, argv, argc, runtime->heap, error); + + if(obj == NULL) + return 0; + + if(!Runtime_Push(runtime, error, obj)) + return 0; + return 1; + } + case OPCODE_PUSHINT: { assert(opc == 1); @@ -475,13 +536,23 @@ static _Bool step(Runtime *runtime, Error *error) Object *obj = Object_Select(runtime->frame->vars, key, runtime->heap, error); - if(obj == NULL) + if(obj == NULL && error->occurred == 0) { - if(error->occurred == 0) - // There's no such variable. - Error_Report(error, 1, "Reference to undefined variable \"%s\"", ops[0].as_string); - return 0; + // Variable not defined locally. + + if(runtime->builtins) + obj = Object_Select(runtime->builtins, key, runtime->heap, error); + + if(obj == NULL) + { + if(error->occurred == 0) + // There's no such variable. + Error_Report(error, 1, "Reference to undefined variable \"%s\"", ops[0].as_string); + return 0; + } } + else if(obj == NULL) + return 0; if(!Runtime_Push(runtime, error, obj)) return 0; @@ -538,7 +609,7 @@ static _Bool step(Runtime *runtime, Error *error) assert(ops[1].type == OPTP_INT); Object *obj = Object_FromNojaFunction(runtime, runtime->frame->exe, ops[0].as_int, ops[1].as_int, runtime->heap, error); - + if(obj == NULL) return 0; diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index 1c4a92e..e70d4c4 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -10,6 +10,8 @@ Runtime* Runtime_New(int stack_size, int heap_size, void *callback_userp, _Bool void Runtime_Free(Runtime *runtime); _Bool Runtime_Pop(Runtime *runtime, Error *error, unsigned int n); _Bool Runtime_Push(Runtime *runtime, Error *error, Object *obj); +Heap* Runtime_GetHeap(Runtime *runtime); +Object* Runtime_GetBuiltins(Runtime *runtime, Error *error); int Runtime_GetCurrentIndex(Runtime *runtime); Executable *Runtime_GetCurrentExecutable(Runtime *runtime); @@ -18,7 +20,9 @@ Snapshot *Snapshot_New(Runtime *runtime); void Snapshot_Free(Snapshot *snapshot); void Snapshot_Print(Snapshot *snapshot, FILE *fp); +_Bool add_builtins(Runtime *runtime, Error *error); Object *run(Runtime *runtime, Error *error, Executable *exe, int index, Object **argv, int argc); -Object *Object_FromNojaFunction(Runtime *runtime, Executable *exe, int index, int argc, Heap *heap, Error *error); +Object* Object_FromNativeFunction(Runtime *runtime, Object *(*callback)(Runtime*, Object**, unsigned int, Error*), int argc, Heap *heap, Error *error); +Object* Object_FromNojaFunction(Runtime *runtime, Executable *exe, int index, int argc, Heap *heap, Error *error); #endif \ No newline at end of file