diff --git a/.gitignore b/.gitignore index c795b05..c510a28 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -build \ No newline at end of file +build +temp \ No newline at end of file diff --git a/samples/if-else.noja b/samples/if-else.noja new file mode 100644 index 0000000..421e7e2 --- /dev/null +++ b/samples/if-else.noja @@ -0,0 +1,2 @@ + +if 1 1; \ No newline at end of file diff --git a/src/common/executable.c b/src/common/executable.c index 449dbd2..5bb04af 100644 --- a/src/common/executable.c +++ b/src/common/executable.c @@ -48,12 +48,15 @@ static const InstrInfo instr_table[] = { [OPCODE_MUL] = {"MUL", 2, 0, NULL}, [OPCODE_DIV] = {"DIV", 2, 0, NULL}, - [OPCODE_PUSHI] = {"PUSHI", 2, 1, (OperandType[]) {OPTP_INT}}, - [OPCODE_PUSHF] = {"PUSHF", 2, 1, (OperandType[]) {OPTP_FLOAT}}, - [OPCODE_PUSHS] = {"PUSHS", 2, 1, (OperandType[]) {OPTP_STRING}}, - [OPCODE_PUSHV] = {"PUSHV", 2, 1, (OperandType[]) {OPTP_STRING}}, + [OPCODE_PUSHI] = {"PUSHI", 1, 1, (OperandType[]) {OPTP_INT}}, + [OPCODE_PUSHF] = {"PUSHF", 1, 1, (OperandType[]) {OPTP_FLOAT}}, + [OPCODE_PUSHS] = {"PUSHS", 1, 1, (OperandType[]) {OPTP_STRING}}, + [OPCODE_PUSHV] = {"PUSHV", 1, 1, (OperandType[]) {OPTP_STRING}}, [OPCODE_RETURN] = {"RETURN", 0, 0, NULL}, + + [OPCODE_JUMPIFNOTANDPOP] = {"JUMPIFNOTANDPOP", 1, 1, (OperandType[]) {OPTP_INT}}, + [OPCODE_JUMP] = {"JUMP", 0, 1, (OperandType[]) {OPTP_INT}}, }; static const char *operand_type_names[] = { @@ -362,4 +365,19 @@ _Bool ExeBuilder_Append(ExeBuilder *exeb, Error *error, Opcode opcode, Operand * } return 1; -} \ No newline at end of file +} + +BPAlloc *ExeBuilder_GetAlloc(ExeBuilder *exeb) +{ + return BucketList_GetAlloc(exeb->code); +} + +int ExeBuilder_InstrCount(ExeBuilder *exeb) +{ + int raw_size = BucketList_Size(exeb->code); + + assert(raw_size % sizeof(Instruction) == 0); + + return raw_size / sizeof(Instruction); +} + diff --git a/src/common/executable.h b/src/common/executable.h index 228cb6c..290f5a7 100644 --- a/src/common/executable.h +++ b/src/common/executable.h @@ -33,6 +33,8 @@ typedef enum { OPCODE_PUSHS, OPCODE_PUSHV, OPCODE_RETURN, + OPCODE_JUMPIFNOTANDPOP, + OPCODE_JUMP, } Opcode; typedef struct xExecutable Executable; @@ -47,4 +49,6 @@ Source *Executable_GetSource(Executable *exe); ExeBuilder *ExeBuilder_New(BPAlloc *alloc); _Bool ExeBuilder_Append(ExeBuilder *exeb, Error *error, Opcode opcode, Operand *opv, int opc, int off, int len); Executable *ExeBuilder_Finalize(ExeBuilder *exeb, Error *error); +BPAlloc *ExeBuilder_GetAlloc(ExeBuilder *exeb); +int ExeBuilder_InstrCount(ExeBuilder *exeb); #endif \ No newline at end of file diff --git a/src/compiler/compile.c b/src/compiler/compile.c index 9544af4..254d115 100644 --- a/src/compiler/compile.c +++ b/src/compiler/compile.c @@ -42,7 +42,8 @@ static _Bool emit_instr_for_node(ExeBuilder *exeb, Node *node, Error *error) OperExprNode *oper = (OperExprNode*) expr; for(Node *operand = oper->head; operand; operand = operand->next) - emit_instr_for_node(exeb, operand, error); + if(!emit_instr_for_node(exeb, operand, error)) + return 0; return ExeBuilder_Append(exeb, error, exprkind_to_opcode(expr->kind), @@ -84,10 +85,78 @@ static _Bool emit_instr_for_node(ExeBuilder *exeb, Node *node, Error *error) break; } + case NODE_IFELSE: + { + IfElseNode *ifelse = (IfElseNode*) node; + + if(!emit_instr_for_node(exeb, ifelse->condition, error)) + return 0; + + if(ifelse->false_branch) + { + Promise *else_offset = Promise_New(ExeBuilder_GetAlloc(exeb), sizeof(long long int)); + Promise *done_offset = Promise_New(ExeBuilder_GetAlloc(exeb), sizeof(long long int)); + + if(else_offset == NULL || done_offset == NULL) + { + Error_Report(error, 1, "No memory"); + return 0; + } + + Operand op = { .type = OPTP_PROMISE, .as_promise = else_offset }; + if(!ExeBuilder_Append(exeb, error, OPCODE_JUMPIFNOTANDPOP, &op, 1, node->offset, node->length)) + return 0; + + if(!emit_instr_for_node(exeb, ifelse->true_branch, error)) + return 0; + + op = (Operand) { .type = OPTP_PROMISE, .as_promise = done_offset }; + if(!ExeBuilder_Append(exeb, error, OPCODE_JUMP, &op, 1, node->offset, node->length)) + return 0; + + long long int temp = ExeBuilder_InstrCount(exeb); + Promise_Resolve(else_offset, &temp, sizeof(temp)); + + if(!emit_instr_for_node(exeb, ifelse->false_branch, error)) + return 0; + + temp = ExeBuilder_InstrCount(exeb); + Promise_Resolve(done_offset, &temp, sizeof(temp)); + + Promise_Free(else_offset); + Promise_Free(done_offset); + } + else + { + Promise *done_offset = Promise_New(ExeBuilder_GetAlloc(exeb), sizeof(long long int)); + + if(done_offset == NULL) + { + Error_Report(error, 1, "No memory"); + return 0; + } + + if(!ExeBuilder_Append(exeb, error, OPCODE_JUMPIFNOTANDPOP, &(Operand) { .type = OPTP_PROMISE, .as_promise = done_offset }, 1, node->offset, node->length)) + return 0; + + if(!emit_instr_for_node(exeb, ifelse->true_branch, error)) + return 0; + + long long int temp = ExeBuilder_InstrCount(exeb); + Promise_Resolve(done_offset, &temp, sizeof(temp)); + + Promise_Free(done_offset); + } + + return 1; + } + default: UNREACHABLE; - break; + return 0; } + UNREACHABLE; + return 0; } Executable *compile(AST *ast, BPAlloc *alloc, Error *error) diff --git a/src/compiler/parse.c b/src/compiler/parse.c index 000b33b..3593fa4 100644 --- a/src/compiler/parse.c +++ b/src/compiler/parse.c @@ -369,7 +369,7 @@ static Node *parse_string_primary_expression(Context *ctx) assert(ctx != NULL); assert(ctx->token->kind == TSTRING); - if(current(ctx) == TDONE) + if(done(ctx)) { Error_Report(ctx->error, 0, "Source ended where a string literal was expected"); return NULL; @@ -493,6 +493,12 @@ static Node *parse_primary_expresion(Context *ctx) { assert(ctx != NULL); + if(done(ctx)) + { + Error_Report(ctx->error, 0, "Source ended where a primary expression was expected"); + return NULL; + } + switch(current(ctx)) { case '+': @@ -532,7 +538,7 @@ static Node *parse_primary_expresion(Context *ctx) if(node == NULL) return NULL; - if(current(ctx) == TDONE) + if(done(ctx)) { Error_Report(ctx->error, 0, "Source ended before \")\", after sub-expression"); return NULL; @@ -823,34 +829,44 @@ static Node *parse_ifelse_statement(Context *ctx) { assert(ctx != NULL); assert(ctx->token != NULL); - assert(ctx->token->kind == TKWIF); - ctx->token = ctx->token->next; - - if(ctx->token->kind == TDONE) + if(done(ctx)) { - // ERROR: Source ended after if keyword. - // An expression was expected. - Error_Report(ctx->error, 0, "Source ended after if keyword. An expression was expected"); + Error_Report(ctx->error, 0, "Source ended where an if-else statement was expected"); + return NULL; + } + + if(current(ctx) != TKWIF) + { + Error_Report(ctx->error, 0, "Got unexpected token \"%.*s\" where an if-else statement was expected", ctx->token->length, ctx->src + ctx->token->offset); return NULL; } Token *if_token = current_token(ctx); + assert(if_token != NULL); + + next(ctx); // Consume the "if" keyword. Node *condition = parse_expression(ctx); - if(condition == NULL) return NULL; + + if(condition == NULL) + return NULL; Node *true_branch = parse_statement(ctx); - if(condition == NULL) return NULL; + + if(condition == NULL) + return NULL; Node *false_branch = NULL; + if(ctx->token->kind == TKWELSE) { - // Consume the "else" token. - ctx->token = ctx->token->next; + next(ctx); // Consume the "else" token. false_branch = parse_statement(ctx); - if(false_branch == NULL) return NULL; + + if(false_branch == NULL) + return NULL; } IfElseNode *ifelse; diff --git a/src/compiler/serialize.c b/src/compiler/serialize.c index 9059713..3c295b7 100644 --- a/src/compiler/serialize.c +++ b/src/compiler/serialize.c @@ -126,6 +126,27 @@ static xj_value *convert_node(Node *node, xj_error *error, xj_alloc **alloc) } break; + case NODE_IFELSE: + { + IfElseNode *ifelse = (IfElseNode*) node; + + xj_value *condition = convert_node(ifelse->condition, error, alloc); + if(condition == NULL) return NULL; + + xj_value *true_branch = convert_node(ifelse->true_branch, error, alloc); + if(true_branch == NULL) return NULL; + + xj_value *false_branch = convert_node(ifelse->false_branch, error, alloc); + if(false_branch == NULL) return NULL; + + return xj_decodef(error, alloc, + "{" + "\t\"node-kind\": \"if-else\", \n" + "\t\"condition\": %v, \n" + "\t\"true-branch\": %v, \n" + "\t\"false-branch\": %v\n" + "}", condition, true_branch, false_branch); + } default: UNREACHABLE; diff --git a/src/objects/o_bool.c b/src/objects/o_bool.c new file mode 100644 index 0000000..1664628 --- /dev/null +++ b/src/objects/o_bool.c @@ -0,0 +1,38 @@ +#include +#include +#include "objects.h" + +static _Bool to_bool(Object *obj, Error *err); + +static const Type t_int = { + .base = (Object) { .type = &t_type, .flags = Object_STATIC }, + .name = "int", + .size = sizeof (Object), + .atomic = ATMTP_BOOL, + .to_bool = to_bool, +}; + +static Object the_true_object = { + .type = t_bool, + .flags = Object_STATIC, +} + +static Object the_false_object = { + .type = t_bool, + .flags = Object_STATIC, +} + +static _Bool to_bool(Object *obj, Error *err) +{ + assert(obj); + assert(err); + assert(Object_GetType(obj) == &t_bool); + return obj == &the_true_object; +} + +Object *Object_FromBool(_Bool val, Heap *heap, Error *error) +{ + (void) heap; + (void) error; + return val ? &the_true_object : &the_false_object; +} \ No newline at end of file diff --git a/src/objects/objects.c b/src/objects/objects.c index 862557d..7f58c65 100644 --- a/src/objects/objects.c +++ b/src/objects/objects.c @@ -218,6 +218,13 @@ _Bool Object_IsInt(Object *obj) return obj->type->atomic == ATMTP_INT; } +_Bool Object_IsBool(Object *obj) +{ + assert(obj != NULL); + assert(obj->type != NULL); + return obj->type->atomic == ATMTP_BOOL; +} + _Bool Object_IsFloat(Object *obj) { assert(obj != NULL); @@ -248,6 +255,29 @@ long long int Object_ToInt(Object *obj, Error *err) return type->to_int(obj, err); } +_Bool Object_ToBool(Object *obj, Error *err) +{ + assert(err); + assert(obj); + + if(!Object_IsBool(obj)) + { + Error_Report(err, 0, "Object is not a boolean"); + return 0; + } + + const Type *type = Object_GetType(obj); + assert(type); + + if(type->to_bool == NULL) + { + Error_Report(err, "Object %s doesn't implement %s", Object_GetName(obj), __func__); + return 0; + } + + return type->to_bool(obj, err); +} + double Object_ToFloat(Object *obj, Error *err) { assert(err); diff --git a/src/objects/objects.h b/src/objects/objects.h index c126aba..89045c7 100644 --- a/src/objects/objects.h +++ b/src/objects/objects.h @@ -15,6 +15,7 @@ struct Object { typedef enum { ATMTP_NOTATOMIC = 0, ATMTP_INT, + ATMTP_BOOL, ATMTP_FLOAT, } AtomicType; @@ -47,6 +48,7 @@ struct Type { // Some. union { long long int (*to_int)(Object *self, Error *err); + _Bool (*to_bool)(Object *self, Error *err); double (*to_float)(Object *self, Error *err); char *(*to_string)(Object *self, int *size, Heap *heap, Error *err); }; @@ -81,13 +83,16 @@ Object* Object_NewMap(int num, Heap *heap, Error *error); Object* Object_NewNone(Heap *heap, Error *error); Object* Object_FromInt (long long int val, Heap *heap, Error *error); +Object* Object_FromBool (_Bool val, Heap *heap, Error *error); Object* Object_FromFloat (double val, Heap *heap, Error *error); Object* Object_FromString(const char *str, int len, Heap *heap, Error *error); _Bool Object_IsInt (Object *obj); +_Bool Object_IsBool (Object *obj); _Bool Object_IsFloat(Object *obj); long long int Object_ToInt (Object *obj, Error *err); +_Bool Object_ToBool (Object *obj, Error *err); double Object_ToFloat(Object *obj, Error *err); _Bool Object_Compare(Object *obj1, Object *obj2, Error *error); diff --git a/src/runtime/runtime.c b/src/runtime/runtime.c index 1121801..3867f2d 100644 --- a/src/runtime/runtime.c +++ b/src/runtime/runtime.c @@ -433,6 +433,40 @@ static _Bool step(Runtime *runtime, Error *error) case OPCODE_RETURN: return 0; + case OPCODE_JUMP: + assert(opc == 1); + assert(ops[0].type == OPTP_INT); + runtime->frame->index = ops[0].as_int; + return 1; + + case OPCODE_JUMPIFNOTANDPOP: + { + assert(opc == 1); + assert(ops[0].type == OPTP_INT); + + long long int target = ops[0].as_int; + + if(runtime->frame->used == 0) + { + Error_Report(error, 1, "Frame doesn't have enough items on the stack to execute JUMPIFNOTANDPOP"); + return 0; + } + + Object *top = Stack_Top(runtime->stack, 0); + assert(top != NULL); + + if(!Object_IsBool(top)) + { + Error_Report(error, 1, "JUMPIFNOTANDPOP expected a boolean on the stack"); + return 0; + } + + if(Object_ToBool(top, error)) // This can't fail because we know it's a bool. + runtime->frame->index = target; + + return 1; + } + default: UNREACHABLE; return 0; diff --git a/src/utils/bucketlist.c b/src/utils/bucketlist.c index 62b1ad7..453dedb 100644 --- a/src/utils/bucketlist.c +++ b/src/utils/bucketlist.c @@ -174,4 +174,9 @@ void BucketList_Copy(BucketList *blist, void *dest, int len) copied += copying; bucket = bucket->next; } +} + +BPAlloc *BucketList_GetAlloc(BucketList *blist) +{ + return blist->alloc; } \ No newline at end of file diff --git a/src/utils/bucketlist.h b/src/utils/bucketlist.h index 66e9a63..d4eda72 100644 --- a/src/utils/bucketlist.h +++ b/src/utils/bucketlist.h @@ -7,4 +7,5 @@ int BucketList_Size(BucketList *blist); void BucketList_Copy(BucketList *blist, void *dest, int len); _Bool BucketList_Append (BucketList *blist, const void *data, int size); void *BucketList_Append2(BucketList *blist, const void *data, int size); +BPAlloc *BucketList_GetAlloc(BucketList *blist); #endif \ No newline at end of file diff --git a/src/utils/promise.c b/src/utils/promise.c index 8720e25..38420e0 100644 --- a/src/utils/promise.c +++ b/src/utils/promise.c @@ -41,7 +41,7 @@ unsigned int Promise_Size(Promise *promise) return promise->size; } -void Promise_Delete(Promise *promise) +void Promise_Free(Promise *promise) { assert(promise->set == 1); } diff --git a/src/utils/promise.h b/src/utils/promise.h index 9d2c64f..32d79a8 100644 --- a/src/utils/promise.h +++ b/src/utils/promise.h @@ -4,7 +4,7 @@ typedef struct xPromise Promise; Promise *Promise_New(BPAlloc *alloc, int size); unsigned int Promise_Size(Promise *promise); -void Promise_Delete(Promise *promise); +void Promise_Free(Promise *promise); void Promise_Resolve(Promise *promise, const void *data, int size); _Bool Promise_Subscribe(Promise *promise, void *dest); _Bool Promise_Subscribe2(Promise *promise, void *dest, void *userp, void (*callback)(void*)); diff --git a/src/utils/source.c b/src/utils/source.c index a7a999b..f6bbf0b 100644 --- a/src/utils/source.c +++ b/src/utils/source.c @@ -80,7 +80,7 @@ Source *Source_FromFile(const char *file, Error *error) return NULL; } - if(fseek(fp, 0, SEEK_END)) + if(fseek(fp, 0, SEEK_SET)) { Error_Report(error, 1, "Call to fseek failed (%s, errno = %d)", strerror(errno), errno); fclose(fp);