From 34577ecfe11641eabb9bf61f76e026336f32dc89 Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Tue, 19 Nov 2024 17:14:39 +1100 Subject: [PATCH] WIP: if-then-else, incomplete --- scripts/cond.toy | 10 +++++++ source/toy_ast.c | 11 +++++++ source/toy_ast.h | 10 +++++++ source/toy_opcodes.h | 14 +++++++++ source/toy_parser.c | 40 +++++++++++++++++++++---- source/toy_routine.c | 66 +++++++++++++++++++++++++++++++++++++++--- source/toy_vm.c | 52 ++++++++++++++++++++++++++++++++- tests/cases/test_ast.c | 3 ++ 8 files changed, 196 insertions(+), 10 deletions(-) create mode 100644 scripts/cond.toy diff --git a/scripts/cond.toy b/scripts/cond.toy new file mode 100644 index 0000000..712350e --- /dev/null +++ b/scripts/cond.toy @@ -0,0 +1,10 @@ + + +if (true) { + print "Success"; +} + +else { + print "Failed"; +} + diff --git a/source/toy_ast.c b/source/toy_ast.c index 521cda1..c576cf8 100644 --- a/source/toy_ast.c +++ b/source/toy_ast.c @@ -105,6 +105,17 @@ void Toy_private_emitAstAssert(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, T (*astHandle) = tmp; } +void Toy_private_emitAstIfThenElse(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Ast* condBranch, Toy_Ast* thenBranch, Toy_Ast* elseBranch) { + Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast)); + + tmp->type = TOY_AST_IF_THEN_ELSE; + tmp->ifThenElse.condBranch = condBranch; + tmp->ifThenElse.thenBranch = thenBranch; + tmp->ifThenElse.elseBranch = elseBranch; + + (*astHandle) = tmp; +} + void Toy_private_emitAstPrint(Toy_Bucket** bucketHandle, Toy_Ast** astHandle) { Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast)); diff --git a/source/toy_ast.h b/source/toy_ast.h index 990a57b..c369ced 100644 --- a/source/toy_ast.h +++ b/source/toy_ast.h @@ -18,6 +18,7 @@ typedef enum Toy_AstType { TOY_AST_COMPOUND, TOY_AST_ASSERT, + TOY_AST_IF_THEN_ELSE, TOY_AST_PRINT, TOY_AST_VAR_DECLARE, @@ -123,6 +124,13 @@ typedef struct Toy_AstAssert { Toy_Ast* message; } Toy_AstAssert; +typedef struct Toy_AstIfThenElse { + Toy_AstType type; + Toy_Ast* condBranch; + Toy_Ast* thenBranch; + Toy_Ast* elseBranch; +} Toy_AstIfThenElse; + typedef struct Toy_AstPrint { Toy_AstType type; Toy_Ast* child; @@ -168,6 +176,7 @@ union Toy_Ast { //32 | 64 BITNESS Toy_AstGroup group; //8 | 16 Toy_AstCompound compound; //16 | 24 Toy_AstAssert assert; //16 | 24 + Toy_AstIfThenElse ifThenElse; //16 | 32 Toy_AstPrint print; //8 | 16 Toy_AstVarDeclare varDeclare; //16 | 24 Toy_AstVarAssign varAssign; //16 | 24 @@ -188,6 +197,7 @@ void Toy_private_emitAstGroup(Toy_Bucket** bucketHandle, Toy_Ast** astHandle); void Toy_private_emitAstCompound(Toy_Bucket** bucketHandle, Toy_Ast** astHandle,Toy_AstFlag flag, Toy_Ast* right); void Toy_private_emitAstAssert(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Ast* child, Toy_Ast* msg); +void Toy_private_emitAstIfThenElse(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Ast* condBranch, Toy_Ast* thenBranch, Toy_Ast* elseBranch); void Toy_private_emitAstPrint(Toy_Bucket** bucketHandle, Toy_Ast** astHandle); void Toy_private_emitAstVariableDeclaration(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_String* name, Toy_Ast* expr); diff --git a/source/toy_opcodes.h b/source/toy_opcodes.h index 641e730..50cdaac 100644 --- a/source/toy_opcodes.h +++ b/source/toy_opcodes.h @@ -32,6 +32,7 @@ typedef enum Toy_OpcodeType { //control instructions TOY_OPCODE_RETURN, + TOY_OPCODE_JUMP, TOY_OPCODE_SCOPE_PUSH, TOY_OPCODE_SCOPE_POP, @@ -48,3 +49,16 @@ typedef enum Toy_OpcodeType { TOY_OPCODE_ERROR, TOY_OPCODE_EOF = 255, } Toy_OpcodeType; + +//specific opcode flags +typedef enum Toy_OpParamJumpType { + TOY_OP_PARAM_JUMP_ABSOLUTE = 0, //from the start of the routine's code section + TOY_OP_PARAM_JUMP_RELATIVE = 1, +} Toy_OpJumpType; + +typedef enum Toy_OpParamJumpConditional { + TOY_OP_PARAM_JUMP_ALWAYS = 0, + TOY_OP_PARAM_JUMP_IF_TRUE = 1, + TOY_OP_PARAM_JUMP_IF_FALSE = 2, +} Toy_OpParamJumpConditional; + diff --git a/source/toy_parser.c b/source/toy_parser.c index 23fbc35..3c6a509 100644 --- a/source/toy_parser.c +++ b/source/toy_parser.c @@ -655,6 +655,7 @@ static void makeExpr(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** ro //forward declarations static void makeBlockStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** rootHandle); +static void makeDeclarationStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** rootHandle); static void makeAssertStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** rootHandle) { Toy_Ast* ast = NULL; //assert's emit function is a bit different @@ -677,6 +678,27 @@ static void makeAssertStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_As consume(parser, TOY_TOKEN_OPERATOR_SEMICOLON, "Expected ';' at the end of assert statement"); } +static void makeIfThenElseStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** rootHandle) { + Toy_Ast* condBranch = NULL; + Toy_Ast* thenBranch = NULL; + Toy_Ast* elseBranch = NULL; + + //if (condBranch) + consume(parser, TOY_TOKEN_OPERATOR_PAREN_LEFT, "Expected '(' after 'if' keyword"); + makeExpr(bucketHandle, parser, &condBranch); + consume(parser, TOY_TOKEN_OPERATOR_PAREN_RIGHT, "Expected ')' after 'if' condition"); + + // { thenBranch } + makeDeclarationStmt(bucketHandle, parser, &thenBranch); + + //else { elseBranch } + if (match(parser, TOY_TOKEN_KEYWORD_ELSE)) { + makeDeclarationStmt(bucketHandle, parser, &elseBranch); + } + + Toy_private_emitAstIfThenElse(bucketHandle, rootHandle, condBranch, thenBranch, elseBranch); +} + static void makePrintStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** rootHandle) { makeExpr(bucketHandle, parser, rootHandle); Toy_private_emitAstPrint(bucketHandle, rootHandle); @@ -737,12 +759,18 @@ static void makeStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** ro return; } + //assert else if (match(parser, TOY_TOKEN_KEYWORD_ASSERT)) { makeAssertStmt(bucketHandle, parser, rootHandle); return; } //if-then-else + else if (match(parser, TOY_TOKEN_KEYWORD_IF)) { + makeIfThenElseStmt(bucketHandle, parser, rootHandle); + return; + } + //while-then //for-pre-clause-post-then //break @@ -750,17 +778,19 @@ static void makeStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** ro //return //import - //check for empty lines - else if (match(parser, TOY_TOKEN_OPERATOR_SEMICOLON)) { - Toy_private_emitAstPass(bucketHandle, rootHandle); + //print + else if (match(parser, TOY_TOKEN_KEYWORD_PRINT)) { + makePrintStmt(bucketHandle, parser, rootHandle); return; } - else if (match(parser, TOY_TOKEN_KEYWORD_PRINT)) { - makePrintStmt(bucketHandle, parser, rootHandle); + //empty lines + else if (match(parser, TOY_TOKEN_OPERATOR_SEMICOLON)) { + Toy_private_emitAstPass(bucketHandle, rootHandle); return; } + //expressions else { //default makeExprStmt(bucketHandle, parser, rootHandle); diff --git a/source/toy_routine.c b/source/toy_routine.c index 4cdfdfe..52ad707 100644 --- a/source/toy_routine.c +++ b/source/toy_routine.c @@ -47,11 +47,22 @@ static void emitFloat(void** handle, unsigned int* capacity, unsigned int* count //write instructions based on the AST types #define EMIT_BYTE(rt, part, byte) \ - emitByte((void**)(&((*rt)->part)), &((*rt)->part##Capacity), &((*rt)->part##Count), byte); + emitByte((void**)(&((*rt)->part)), &((*rt)->part##Capacity), &((*rt)->part##Count), byte) #define EMIT_INT(rt, part, bytes) \ - emitInt((void**)(&((*rt)->part)), &((*rt)->part##Capacity), &((*rt)->part##Count), bytes); + emitInt((void**)(&((*rt)->part)), &((*rt)->part##Capacity), &((*rt)->part##Count), bytes) #define EMIT_FLOAT(rt, part, bytes) \ - emitFloat((void**)(&((*rt)->part)), &((*rt)->part##Capacity), &((*rt)->part##Count), bytes); + emitFloat((void**)(&((*rt)->part)), &((*rt)->part##Capacity), &((*rt)->part##Count), bytes) + +//skip bytes, but return the address +#define SKIP_BYTE(rt, part) (EMIT_BYTE(rt, part, 0), ((*rt)->part##Count - 1)) +#define SKIP_INT(rt, part) (EMIT_INT(rt, part, 0), ((*rt)->part##Count - 4)) + +//overwrite a pre-existing position +#define OVERWRITE_INT(rt, part, addr, bytes) \ + emitInt((void**)(&((*rt)->part)), &((*rt)->part##Capacity), &(addr), bytes); + +//simply get the address (always an integer) +#define CURRENT_ADDRESS(rt, part) ((*rt)->part##Count) static void emitToJumpTable(Toy_Routine** rt, unsigned int startAddr) { EMIT_INT(rt, code, (*rt)->jumpsCount); //mark the jump index in the code @@ -296,6 +307,48 @@ static unsigned int writeInstructionAssert(Toy_Routine** rt, Toy_AstAssert ast) return 0; } +static unsigned int writeInstructionIfThenElse(Toy_Routine** rt, Toy_AstIfThenElse ast) { + //cond-branch + writeRoutineCode(rt, ast.condBranch); + + //emit the jump word (opcode, type, condition, padding) + EMIT_BYTE(rt, code, TOY_OPCODE_JUMP); + EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_RELATIVE); + EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_IF_FALSE); + EMIT_BYTE(rt, code, 0); + + unsigned int thenEndAddr = SKIP_INT(rt, code); //parameter to be written later + + //emit then branch + writeRoutineCode(rt, ast.thenBranch); + + if (ast.elseBranch != NULL) { + //emit the jump-to-end (opcode, type, condition, padding) + EMIT_BYTE(rt, code, TOY_OPCODE_JUMP); + EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_RELATIVE); + EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_ALWAYS); + EMIT_BYTE(rt, code, 0); + + unsigned int elseEndAddr = SKIP_INT(rt, code); //parameter to be written later + + //specify the starting position for the else branch + OVERWRITE_INT(rt, code, thenEndAddr, CURRENT_ADDRESS(rt, code) - thenEndAddr); + + //emit the else branch + writeRoutineCode(rt, ast.elseBranch); + + //specify the ending position for the else branch + OVERWRITE_INT(rt, code, elseEndAddr, CURRENT_ADDRESS(rt, code) - elseEndAddr); + } + + else { + //without an else branch, set the jump destination and move on + OVERWRITE_INT(rt, code, thenEndAddr, CURRENT_ADDRESS(rt, code) - thenEndAddr); + } + + return 0; +} + static unsigned int writeInstructionPrint(Toy_Routine** rt, Toy_AstPrint ast) { //the thing to print writeRoutineCode(rt, ast.child); @@ -494,6 +547,7 @@ static unsigned int writeRoutineCode(Toy_Routine** rt, Toy_Ast* ast) { return 0; } + //NOTE: 'result' is used to in 'writeInstructionCompound()' unsigned int result = 0; //determine how to write each instruction based on the Ast @@ -545,6 +599,10 @@ static unsigned int writeRoutineCode(Toy_Routine** rt, Toy_Ast* ast) { result += writeInstructionAssert(rt, ast->assert); break; + case TOY_AST_IF_THEN_ELSE: + result += writeInstructionIfThenElse(rt, ast->ifThenElse); + break; + case TOY_AST_PRINT: result += writeInstructionPrint(rt, ast->print); break; @@ -596,7 +654,7 @@ static void* writeRoutine(Toy_Routine* rt, Toy_Ast* ast) { //write the header and combine the parts void* buffer = NULL; unsigned int capacity = 0, count = 0; - // int paramAddr = 0, codeAddr = 0, subsAddr = 0; + // int paramAddr = 0, subsAddr = 0; int codeAddr = 0; int jumpsAddr = 0; int dataAddr = 0; diff --git a/source/toy_vm.c b/source/toy_vm.c index e19d781..79de834 100644 --- a/source/toy_vm.c +++ b/source/toy_vm.c @@ -31,7 +31,7 @@ static inline int readPostfixUtil(unsigned int* ptr, int amount) { static inline void fixAlignment(Toy_VM* vm) { //NOTE: It's a tilde, not a negative sign - vm->programCounter = (vm->programCounter + 3) & ~0b11; + vm->programCounter = (vm->programCounter + 3) & ~3; } //instruction handlers @@ -361,6 +361,52 @@ static void processLogical(Toy_VM* vm, Toy_OpcodeType opcode) { } } +static void processJump(Toy_VM* vm) { + Toy_OpJumpType type = READ_BYTE(vm); + Toy_OpParamJumpConditional cond = READ_BYTE(vm); + fixAlignment(vm); + + //assume the param is a signed integer + int param = READ_INT(vm); + + //should we jump? + switch(cond) { + case TOY_OP_PARAM_JUMP_ALWAYS: + break; + + case TOY_OP_PARAM_JUMP_IF_TRUE: { + Toy_Value value = Toy_popStack(&vm->stack); + if (Toy_checkValueIsTruthy(value) == true) { + Toy_freeValue(value); + break; + } + Toy_freeValue(value); + return; + } + + case TOY_OP_PARAM_JUMP_IF_FALSE: { + Toy_Value value = Toy_popStack(&vm->stack); + if (Toy_checkValueIsTruthy(value) != true) { + Toy_freeValue(value); + break; + } + Toy_freeValue(value); + return; + } + } + + //do the jump + switch(type) { + case TOY_OP_PARAM_JUMP_ABSOLUTE: + vm->programCounter = vm->codeAddr + param; + return; + + case TOY_OP_PARAM_JUMP_RELATIVE: + vm->programCounter += param; + return; + } +} + static void processAssert(Toy_VM* vm) { unsigned int count = READ_BYTE(vm); @@ -551,6 +597,10 @@ static void process(Toy_VM* vm) { //temp terminator return; + case TOY_OPCODE_JUMP: + processJump(vm); + break; + case TOY_OPCODE_SCOPE_PUSH: vm->scope = Toy_pushScope(&vm->scopeBucket, vm->scope); break; diff --git a/tests/cases/test_ast.c b/tests/cases/test_ast.c index 5c7c981..8ed9401 100644 --- a/tests/cases/test_ast.c +++ b/tests/cases/test_ast.c @@ -5,6 +5,7 @@ #include int test_sizeof_ast_64bit() { + //NOTE: This could've covered both bitness sizes as TEST_SIZEOF(type, bit32, bit32) #define TEST_SIZEOF(type, size) \ if (sizeof(type) != size) { \ fprintf(stderr, TOY_CC_ERROR "ERROR: sizeof(" #type ") is %d, expected %d\n" TOY_CC_RESET, (int)sizeof(type), size); \ @@ -24,6 +25,7 @@ int test_sizeof_ast_64bit() { TEST_SIZEOF(Toy_AstGroup, 16); TEST_SIZEOF(Toy_AstCompound, 24); TEST_SIZEOF(Toy_AstAssert, 24); + TEST_SIZEOF(Toy_AstIfThenElse, 32); TEST_SIZEOF(Toy_AstPrint, 16); TEST_SIZEOF(Toy_AstVarDeclare, 24); TEST_SIZEOF(Toy_AstVarAssign, 24); @@ -58,6 +60,7 @@ int test_sizeof_ast_32bit() { TEST_SIZEOF(Toy_AstGroup, 8); TEST_SIZEOF(Toy_AstCompound, 16); TEST_SIZEOF(Toy_AstAssert, 12); + TEST_SIZEOF(Toy_AstIfThenElse, 16); TEST_SIZEOF(Toy_AstPrint, 8); TEST_SIZEOF(Toy_AstVarDeclare, 12); TEST_SIZEOF(Toy_AstVarAssign, 16);