diff --git a/src/lexer.c b/src/lexer.c index 8b557f3..1a79a20 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -24,11 +24,15 @@ typedef struct KeywordEntry { } KeywordEntry; static const KeywordEntry kKeywordMap[] = { - {"print", kTokenPrint}, {"if", kTokenIf}, {"else", kTokenElse}, - {"endif", kTokenEndif}, {"for", kTokenFor}, {"endfor", kTokenEndfor}, - {"local", kTokenLocal}, {"func", kTokenFunc}, {"endfunc", kTokenEndfunc}, - {"ret", kTokenRet}, {"int", kTokenInt}, {"float", kTokenFloat}, - {"str", kTokenStr}, {"bool", kTokenBool}, {"array", kTokenArray}}; + {"print", kTokenPrint}, {"if", kTokenIf}, + {"else", kTokenElse}, {"endif", kTokenEndif}, + {"for", kTokenFor}, {"endfor", kTokenEndfor}, + {"local", kTokenLocal}, {"func", kTokenFunc}, + {"endfunc", kTokenEndfunc}, {"ret", kTokenRet}, + {"int", kTokenInt}, {"float", kTokenFloat}, + {"str", kTokenStr}, {"bool", kTokenBool}, + {"array", kTokenArray}, {"while", kTokenWhile}, + {"endwhile", kTokenEndwhile}}; #ifdef __CC65__ static const size_t kKeywordCount = sizeof(kKeywordMap) / sizeof(KeywordEntry); @@ -192,6 +196,10 @@ static bool IsCharacter() { case ':': token.type = kTokenColon; + break; + case ';': + token.type = kTokenSemicolon; + break; case '>': IncrementProgramBufferIndex(); diff --git a/src/lexer.h b/src/lexer.h index cb650a3..808672c 100644 --- a/src/lexer.h +++ b/src/lexer.h @@ -47,6 +47,7 @@ typedef enum TokenType { kTokenDot, kTokenComma, kTokenColon, + kTokenSemicolon, kTokenAssign, kTokenEquals, kTokenNot, @@ -63,6 +64,8 @@ typedef enum TokenType { kTokenEndif, kTokenFor, kTokenEndfor, + kTokenWhile, + kTokenEndwhile, kTokenPrint, kTokenLocal, kTokenBoolean, diff --git a/src/parser.c b/src/parser.c index 720659a..1a80b9d 100644 --- a/src/parser.c +++ b/src/parser.c @@ -862,6 +862,138 @@ static void ParseIdentifierStatement(const char* const identifier_name) { puts("Error: Unexpected token after identifier."); } +// NOLINTNEXTLINE(misc-no-recursion) +static void ParseForStatement() { + size_t jump_if_false_patch = 0; + size_t loop_condition_address = 0; + size_t increment_start_address = 0; + size_t jump_after_increment_patch = 0; + size_t after_loop_address = 0; + char identifier_name[kIdentifierNameLength]; + + if (!ExpectToken(1, kTokenLeftParenthesis)) { + return; + } + + // Parse variable declaration + // Initializer example: i: int = 0 + ExtractIdentifierName(identifier_name); + if (!ExpectToken(1, kTokenIdentifier)) { + return; + } + + if (!ExpectToken(1, kTokenColon)) { + return; + } + + ParseVariableDeclaration(identifier_name); + + if (!ExpectToken(1, kTokenSemicolon)) { + puts("That's yap!: You're missing a semicolon after the initializer."); + return; + } + + // Parse condition address + loop_condition_address = instruction_address; + ParseExpression(); // Condition example: i < 3 + + EmitByte(kOpJumpIfFalse); + jump_if_false_patch = instruction_address; + EmitByte(0); // patched after loop body + + if (!ExpectToken(1, kTokenSemicolon)) { + puts("That's yap!: You're missing a semicolon after the condition."); + return; + } + + // Parse increment & store increment skip address + // Increment example: i = i + 1 + EmitByte(kOpJump); + jump_after_increment_patch = instruction_address; + EmitByte(0); // patched to skip increment on false condition + + increment_start_address = instruction_address; + + ExtractIdentifierName(identifier_name); + if (!ExpectToken(1, kTokenIdentifier)) { + return; + } + + if (!ExpectToken(1, kTokenAssign)) { + return; + } + + ParseVariableAssignment(identifier_name); + + if (!ExpectToken(1, kTokenRightParenthesis)) { + return; + } + + // Jump from end of increment back to condition + EmitByte(kOpJump); + EmitByte(loop_condition_address); + + // Patch earlier jump to skip increment on first entry + instructions[jump_after_increment_patch] = instruction_address; + + // Parse loop body + while (token.type != kTokenEndfor && token.type != kTokenEof) { + ParseStatement(); // Body example: print(i) + } + + // Jump from body to increment + EmitByte(kOpJump); + EmitByte(increment_start_address); + + // === PATCHING === + after_loop_address = instruction_address; + instructions[jump_if_false_patch] = after_loop_address; + + ExpectToken(1, kTokenEndfor); + + EmitHalt(); +} + +// NOLINTNEXTLINE(misc-no-recursion) +static void ParseWhileStatement() { + size_t loop_start_index = 0; + size_t pending_loop_exit_slot = 0; + + // Mark where the loop starts + loop_start_index = instruction_address; + + // Parse condition + // condition example: i < 3 + if (!ExpectToken(1, kTokenLeftParenthesis)) { + return; + } + + ParseExpression(); + + if (!ExpectToken(1, kTokenRightParenthesis)) { + return; + } + + // Emit conditional jump to exit if false + EmitByte(kOpJumpIfFalse); + pending_loop_exit_slot = instruction_address; + EmitByte(0); // placeholder to be patched + + // Parse loop body + while (token.type != kTokenEndwhile && token.type != kTokenEof) { + ParseStatement(); + } + + // Jump back to start of loop + EmitByte(kOpJump); + EmitByte(loop_start_index); + + // Waiting for instruction jump to jump to loop exit + instructions[pending_loop_exit_slot] = instruction_address; + + ExpectToken(1, kTokenEndwhile); +} + // NOLINTNEXTLINE(misc-no-recursion) static void ParseStatement() { char identifier_name[kIdentifierNameLength]; @@ -884,6 +1016,18 @@ static void ParseStatement() { return; } + if (AcceptToken(1, kTokenFor)) { + ParseForStatement(); + + return; + } + + if (AcceptToken(1, kTokenWhile)) { + ParseWhileStatement(); + + return; + } + ExtractIdentifierName(identifier_name); if (AcceptToken(1, kTokenIdentifier)) { diff --git a/src/vm.c b/src/vm.c index 4223c08..f56b479 100644 --- a/src/vm.c +++ b/src/vm.c @@ -149,9 +149,10 @@ void EmitByte(const unsigned char byte) { } void EmitHalt() { - if (kOpHalt != instructions[instruction_address - 1]) { - EmitByte(kOpHalt); - } + /* if (kOpHalt != instructions[instruction_address - 1]) { + EmitByte(kOpHalt); + }*/ + EmitByte(kOpHalt); } void RemoveHalt() { @@ -758,4 +759,4 @@ void RunVm() { } } } -} +} \ No newline at end of file diff --git a/tests/main.c b/tests/main.c index 76fda3b..9b2625a 100644 --- a/tests/main.c +++ b/tests/main.c @@ -117,5 +117,9 @@ int main() { // RUN_TEST(Example9); // RUN_TEST(Example10); + // Loops + RUN_TEST(TestForLoopExecutesThreeTimes); + RUN_TEST(TestWhileLoopExecutesThreeTimes); + RUN_TEST(TestNestedWhileLoops); return UNITY_END(); } diff --git a/tests/vm_test.c b/tests/vm_test.c index 31d9be2..a2728ee 100644 --- a/tests/vm_test.c +++ b/tests/vm_test.c @@ -329,3 +329,93 @@ void Example10() { ResetInterpreterState(); } + +// Loops testing + +void TestForLoopExecutesThreeTimes(void) { + FillProgramBufferAndParse( + "for(i: int = 0; i < 3; i = i + 1)\n" + " print(i)\n" + "endfor"); + + int print_count = 0; + int jump_count = 0; + bool saw_jump_if_false = false; + + for (size_t i = 0; i < kInstructionsSize; ++i) { + if (instructions[i] == kOpPrint) { + print_count++; + } + if (instructions[i] == kOpJumpIfFalse) { + saw_jump_if_false = true; + } + if (instructions[i] == kOpJump) { + jump_count++; + } + } + + TEST_ASSERT_TRUE_MESSAGE(saw_jump_if_false, + "Missing kOpJumpIfFalse for for-loop condition"); + TEST_ASSERT_TRUE_MESSAGE(jump_count >= 2, + "Expected at least two kOpJump instructions (one to " + "body, one to increment)"); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, print_count, + "Expected 1 print instruction in bytecode"); + + RunVm(); +} + +void TestWhileLoopExecutesThreeTimes() { + FillProgramBufferAndParse( + "i: int = 0\n" + "while(i < 3)\n" + " print(i)\n" + " i = i + 1\n" + "endwhile"); + + int print_count = 0; + bool saw_jump_if_false = false; + bool saw_jump = false; + + for (size_t i = 0; i < kInstructionsSize; ++i) { + if (instructions[i] == kOpPrint) { + print_count++; + } + if (instructions[i] == kOpJumpIfFalse) { + saw_jump_if_false = true; + } + if (instructions[i] == kOpJump) { + saw_jump = true; + } + } + + TEST_ASSERT_TRUE_MESSAGE(saw_jump_if_false, + "Missing kOpJumpIfFalse for while"); + TEST_ASSERT_TRUE_MESSAGE(saw_jump, "Missing kOpJump to repeat loop"); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, print_count, + "Expected 1 print instruction in bytecode"); + + RunVm(); +} + +void TestNestedWhileLoops() { + // Expected output should be: + // 0 + // 1 + // 0 + // 1 + + FillProgramBufferAndParse( + "i: int = 0\n" + "j: int = 0\n" + "while(i < 2)\n" + " j = 0\n" + " while(j < 2)\n" + " print(j)\n" + " j = j + 1\n" + " endwhile\n" + " i = i + 1\n" + "endwhile"); + + RunVm(); +} diff --git a/tests/vm_test.h b/tests/vm_test.h index 09dcda5..b34a3c3 100644 --- a/tests/vm_test.h +++ b/tests/vm_test.h @@ -31,4 +31,9 @@ void Example8(); void Example9(); void Example10(); +// Loops +void TestForLoopExecutesThreeTimes(void); +void TestWhileLoopExecutesThreeTimes(); +void TestNestedWhileLoops(); + #endif // CONDITIONALS_TEST_H