diff --git a/build.sh b/build.sh index 12075ed..2130553 100755 --- a/build.sh +++ b/build.sh @@ -76,6 +76,7 @@ gcc src/main.c src/debug.c \ temp/objects/o_string.o \ temp/runtime/runtime.o \ temp/runtime/runtime_error.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 new file mode 100644 index 0000000..6b0595c --- /dev/null +++ b/samples/func.noja @@ -0,0 +1,7 @@ + +fun hello() + { + print;('hello'); + } + +return hello; \ No newline at end of file diff --git a/src/common/executable.c b/src/common/executable.c index d6bbb66..0ea4510 100644 --- a/src/common/executable.c +++ b/src/common/executable.c @@ -57,6 +57,7 @@ static const InstrInfo instr_table[] = { [OPCODE_PUSHTRU] = {"PUSHTRU", 0, NULL}, [OPCODE_PUSHFLS] = {"PUSHFLS", 0, NULL}, [OPCODE_PUSHNNE] = {"PUSHNNE", 0, NULL}, + [OPCODE_PUSHFUN] = {"PUSHFUN", 2, (OperandType[]) {OPTP_INT, OPTP_INT}}, [OPCODE_RETURN] = {"RETURN", 0, NULL}, @@ -319,7 +320,7 @@ _Bool ExeBuilder_Append(ExeBuilder *exeb, Error *error, Opcode opcode, Operand * // ERROR: Too many operands were provided. Error_Report(error, 0, "Instruction %s expects %d operands, but %d were provided", - info->name, opc, info->opcount); + info->name, info->opcount, opc); return 0; } diff --git a/src/common/executable.h b/src/common/executable.h index 83079e6..fa49ed9 100644 --- a/src/common/executable.h +++ b/src/common/executable.h @@ -37,6 +37,7 @@ typedef enum { OPCODE_PUSHTRU, OPCODE_PUSHFLS, OPCODE_PUSHNNE, + OPCODE_PUSHFUN, OPCODE_RETURN, OPCODE_JUMPIFNOTANDPOP, OPCODE_JUMP, diff --git a/src/compiler/ASTi.h b/src/compiler/ASTi.h index 840c6bf..79e7f36 100644 --- a/src/compiler/ASTi.h +++ b/src/compiler/ASTi.h @@ -9,6 +9,8 @@ typedef enum { NODE_IFELSE, NODE_COMP, NODE_RETURN, + NODE_FUNC, + NODE_ARG, } NodeKind; typedef enum { @@ -93,4 +95,17 @@ typedef struct { Node base; Node *val; } ReturnNode; + +typedef struct { + Node base; + char *name; + Node *argv; + int argc; + Node *body; +} FunctionNode; + +typedef struct { + Node base; + char *name; +} ArgumentNode; #endif \ No newline at end of file diff --git a/src/compiler/compile.c b/src/compiler/compile.c index b6b7c31..206d470 100644 --- a/src/compiler/compile.c +++ b/src/compiler/compile.c @@ -241,6 +241,86 @@ static _Bool emit_instr_for_node(ExeBuilder *exeb, Node *node, Error *error) return 1; } + case NODE_FUNC: + { + FunctionNode *func = (FunctionNode*) node; + + Promise *func_index = Promise_New(ExeBuilder_GetAlloc(exeb), sizeof(long long int)); + Promise *jump_index = Promise_New(ExeBuilder_GetAlloc(exeb), sizeof(long long int)); + + if(func_index == NULL || jump_index == NULL) + { + Error_Report(error, 1, "No memory"); + return 0; + } + + // Push function. + { + Operand ops[2] = { + { .type = OPTP_PROMISE, .as_promise = func_index }, + { .type = OPTP_INT, .as_int = func->argc }, + }; + + if(!ExeBuilder_Append(exeb, error, OPCODE_PUSHFUN, ops, 2, func->base.offset, func->base.length)) + return 0; + } + + // Assign variable. + Operand op = (Operand) { .type = OPTP_STRING, .as_string = func->name }; + if(!ExeBuilder_Append(exeb, error, OPCODE_ASS, &op, 1, func->base.offset, func->base.length)) + return 0; + + // Pop function object. + op = (Operand) { .type = OPTP_INT, .as_int = 1 }; + if(!ExeBuilder_Append(exeb, error, OPCODE_POP, &op, 1, func->base.offset, func->base.length)) + return 0; + + // Jump after the function code. + op = (Operand) { .type = OPTP_PROMISE, .as_promise = jump_index }; + if(!ExeBuilder_Append(exeb, error, OPCODE_JUMP, &op, 1, func->base.offset, func->base.length)) + return 0; + + // This is the function code index. + long long int temp = ExeBuilder_InstrCount(exeb); + Promise_Resolve(func_index, &temp, sizeof(temp)); + + // Compile the function body. + { + // Assign the arguments. + + if(func->argv) + assert(func->argv->kind == NODE_ARG); + + ArgumentNode *arg = (ArgumentNode*) func->argv; + + while(arg) + { + op = (Operand) { .type = OPTP_STRING, .as_string = arg->name }; + if(!ExeBuilder_Append(exeb, error, OPCODE_ASS, &op, 1, arg->base.offset, arg->base.length)) + return 0; + + op = (Operand) { .type = OPTP_INT, .as_int = 1 }; + if(!ExeBuilder_Append(exeb, error, OPCODE_POP, &op, 1, arg->base.offset, arg->base.length)) + return 0; + + assert(arg->base.next->kind == NODE_ARG); + + arg = (ArgumentNode*) arg->base.next; + } + + if(!emit_instr_for_node(exeb, func->body, error)) + return 0; + } + + // This is the first index after the function code. + temp = ExeBuilder_InstrCount(exeb); + Promise_Resolve(jump_index, &temp, sizeof(temp)); + + Promise_Free(func_index); + Promise_Free(jump_index); + return 1; + } + default: UNREACHABLE; return 0; diff --git a/src/compiler/parse.c b/src/compiler/parse.c index 0fe9640..67b45c2 100644 --- a/src/compiler/parse.c +++ b/src/compiler/parse.c @@ -25,6 +25,7 @@ typedef enum { TIDENT, TKWIF, + TKWFUN, TKWELSE, TKWNONE, TKWTRUE, @@ -51,6 +52,7 @@ static Node *parse_expression(Context *ctx); 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 inline _Bool isoper(char c) { @@ -125,6 +127,10 @@ AST *parse(Source *src, BPAlloc *alloc, Error *error) { tok->kind = TKWIF; } + else if(matchstr(str + tok->offset, tok->length, "fun")) + { + tok->kind = TKWFUN; + } else if(matchstr(str + tok->offset, tok->length, "else")) { tok->kind = TKWELSE; @@ -397,6 +403,9 @@ static Node *parse_statement(Context *ctx) node->val = val; return (Node*) node; } + + case TKWFUN: + return parse_function_definition(ctx); } Error_Report(ctx->error, 0, "Got token \"%.*s\" where the start of a statement was expected", @@ -551,6 +560,20 @@ static Node *parse_string_primary_expression(Context *ctx) return (Node*) node; } +static char *copy_token_text(Context *ctx) +{ + char *copy = BPAlloc_Malloc(ctx->alloc, ctx->token->length + 1); + + if(copy == NULL) + return NULL; + + memcpy(copy, ctx->src + ctx->token->offset, ctx->token->length); + + copy[ctx->token->length] = '\0'; + + return copy; +} + static Node *parse_primary_expresion(Context *ctx) { assert(ctx != NULL); @@ -789,23 +812,14 @@ static Node *parse_primary_expresion(Context *ctx) case TIDENT: { - char *copy; - int copyl; + char *copy = copy_token_text(ctx); + int copyl = ctx->token->length; - { - copy = BPAlloc_Malloc(ctx->alloc, ctx->token->length + 1); - - if(copy == NULL) - { - Error_Report(ctx->error, 1, "No memory"); - return NULL; - } - - copyl = ctx->token->length; - memcpy(copy, ctx->src + ctx->token->offset, copyl); - copy[copyl] = '\0'; - - } + if(copy == NULL) + { + Error_Report(ctx->error, 1, "No memory"); + return NULL; + } IdentExprNode *node; { @@ -966,7 +980,6 @@ static Node *parse_expression(Context *ctx) static Node *parse_ifelse_statement(Context *ctx) { assert(ctx != NULL); - assert(ctx->token != NULL); if(done(ctx)) { @@ -1076,4 +1089,159 @@ static Node *parse_compound_statement(Context *ctx, TokenKind end) } return (Node*) node; +} + +static Node *parse_function_definition(Context *ctx) +{ + assert(ctx != NULL); + + if(done(ctx)) + { + Error_Report(ctx->error, 0, "Source ended where a function definition was expected"); + return NULL; + } + + if(current(ctx) != TKWFUN) + { + Error_Report(ctx->error, 0, "Got unexpected token \"%.*s\" where a function definition was expected", ctx->token->length, ctx->src + ctx->token->offset); + return NULL; + } + + int offset = current_token(ctx)->offset; + + if(next(ctx) != TIDENT) + { + if(done(ctx)) + Error_Report(ctx->error, 0, "Source ended where an identifier was expected as function name"); + else + Error_Report(ctx->error, 0, "Got unexpected token \"%.*s\" where an identifier was expected as function name", ctx->token->length, ctx->src + ctx->token->offset); + return NULL; + } + + char *name = copy_token_text(ctx); + + if(name == NULL) + { + Error_Report(ctx->error, 1, "No memory"); + return NULL; + } + + if(next(ctx) != '(') + { + if(done(ctx)) + Error_Report(ctx->error, 0, "Source ended where a function argument list was expected"); + else + Error_Report(ctx->error, 0, "Got unexpected token \"%.*s\" where a function argument list was expected", ctx->token->length, ctx->src + ctx->token->offset); + return NULL; + } + + Node *argv = NULL; + int argc = 0; + + if(next(ctx) != ')') + { + // Parse arguments. + + while(1) + { + if(done(ctx)) + { + Error_Report(ctx->error, 0, "Source ended inside a function argument list"); + return NULL; + } + + if(current(ctx) != TIDENT) + { + Error_Report(ctx->error, 0, "Got unexpected token \"%.*s\" where a function argument name was expected", ctx->token->length, ctx->src + ctx->token->offset); + return NULL; + } + + char *arg_name = copy_token_text(ctx); + + if(arg_name == NULL) + { + Error_Report(ctx->error, 1, "No memory"); + return NULL; + } + + ArgumentNode *arg; + { + // Make argument node. + arg = BPAlloc_Malloc(ctx->alloc, sizeof(ArgumentNode)); + + if(arg == NULL) + { + Error_Report(ctx->error, 1, "No memory"); + return NULL; + } + + arg->base.kind = NODE_ARG; + arg->base.next = NULL; + arg->base.offset = current_token(ctx)->offset; + arg->base.length = current_token(ctx)->length; + arg->name = arg_name; + } + + // Add it to the list. + argc += 1; + arg->base.next = argv; + argv = (Node*) arg; + + // Get either ',' or ')'. + + if(next(ctx) == ')') + break; + + if(done(ctx)) + { + Error_Report(ctx->error, 0, "Source ended inside a function argument list"); + return NULL; + } + + if(current(ctx) != ',') + { + Error_Report(ctx->error, 0, "Got unexpected token \"%.*s\" where inside function argument list, where either ',' or ')' were expected", ctx->token->length, ctx->src + ctx->token->offset); + return NULL; + } + + // Now prepare for the next identifier. + next(ctx); + } + } + + next(ctx); // Consume the ')'. + + if(done(ctx)) + { + Error_Report(ctx->error, 0, "Source ended before function body"); + return NULL; + } + + Node *body = parse_statement(ctx); + + if(body == NULL) + return NULL; + + FunctionNode *func; + { + // Make argument node. + func = BPAlloc_Malloc(ctx->alloc, sizeof(FunctionNode)); + + if(func == NULL) + { + Error_Report(ctx->error, 1, "No memory"); + return NULL; + } + + func->base.kind = NODE_FUNC; + func->base.next = NULL; + func->base.offset = offset; + func->base.length = body->offset + body->length - offset; + func->name = name; + func->argv = argv; + func->argc = argc; + func->body = body; + } + + return (Node*) func; } \ No newline at end of file diff --git a/src/runtime/o_func.c b/src/runtime/o_func.c index b739b7a..704680f 100644 --- a/src/runtime/o_func.c +++ b/src/runtime/o_func.c @@ -1,4 +1,5 @@ #include +#include #include "../utils/defs.h" #include "../objects/objects.h" #include "runtime.h" @@ -7,7 +8,7 @@ typedef struct { Object base; Runtime *runtime; Executable *exe; - int index; + int index, argc; } FunctionObject; static Object *call(Object *self, Object **argv, unsigned int argc, Heap *heap, Error *error); @@ -28,16 +29,64 @@ static Object *call(Object *self, Object **argv, unsigned int argc, Heap *heap, FunctionObject *func = (FunctionObject*) self; assert(func->exe != NULL); + assert(func->argc >= 0); assert(func->index >= 0); - return run(func->runtime, error, func->exe, func->index, argv, argc); + // 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, 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 + // The right amount of arguments was provided. + argv2 = argv; + + Object *result = run(func->runtime, error, func->exe, func->index, argv2, expected_argc); + + if(argv2 != argv) + free(argv2); + + return result; } -Object *Object_FromNojaFunction(Runtime *runtime, Executable *exe, int index, Heap *heap, Error *error) +Object *Object_FromNojaFunction(Runtime *runtime, Executable *exe, int index, int argc, Heap *heap, Error *error) { assert(runtime != NULL); assert(exe != NULL); assert(index >= 0); + assert(argc >= 0); assert(heap != NULL); assert(error != NULL); @@ -57,5 +106,7 @@ Object *Object_FromNojaFunction(Runtime *runtime, Executable *exe, int index, He func->runtime = runtime; func->exe = exe_copy; func->index = index; + 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 cabe980..9d668f0 100644 --- a/src/runtime/runtime.c +++ b/src/runtime/runtime.c @@ -531,6 +531,22 @@ static _Bool step(Runtime *runtime, Error *error) return 1; } + case OPCODE_PUSHFUN: + { + assert(opc == 2); + assert(ops[0].type == OPTP_INT); + 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; + + if(!Runtime_Push(runtime, error, obj)) + return 0; + return 1; + } + case OPCODE_RETURN: return 0; diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index 2c1cec6..1c4a92e 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -20,5 +20,5 @@ void Snapshot_Print(Snapshot *snapshot, FILE *fp); Object *run(Runtime *runtime, Error *error, Executable *exe, int index, Object **argv, int argc); -Object *Object_FromNojaFunction(Runtime *runtime, Executable *exe, int offset, 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