Skip to content
Merged

Loops #196

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions src/lexer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -192,6 +196,10 @@ static bool IsCharacter() {
case ':':
token.type = kTokenColon;

break;
case ';':
token.type = kTokenSemicolon;

break;
case '>':
IncrementProgramBufferIndex();
Expand Down
3 changes: 3 additions & 0 deletions src/lexer.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ typedef enum TokenType {
kTokenDot,
kTokenComma,
kTokenColon,
kTokenSemicolon,
kTokenAssign,
kTokenEquals,
kTokenNot,
Expand All @@ -63,6 +64,8 @@ typedef enum TokenType {
kTokenEndif,
kTokenFor,
kTokenEndfor,
kTokenWhile,
kTokenEndwhile,
kTokenPrint,
kTokenLocal,
kTokenBoolean,
Expand Down
144 changes: 144 additions & 0 deletions src/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -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)) {
Expand Down
9 changes: 5 additions & 4 deletions src/vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -758,4 +759,4 @@ void RunVm() {
}
}
}
}
}
4 changes: 4 additions & 0 deletions tests/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
90 changes: 90 additions & 0 deletions tests/vm_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
5 changes: 5 additions & 0 deletions tests/vm_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,9 @@ void Example8();
void Example9();
void Example10();

// Loops
void TestForLoopExecutesThreeTimes(void);
void TestWhileLoopExecutesThreeTimes();
void TestNestedWhileLoops();

#endif // CONDITIONALS_TEST_H