Skip to content
Closed
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
64 changes: 56 additions & 8 deletions src/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,56 @@
#include "vm.h"

#ifdef __CC65__
enum { kIdentifierNameLength = 16, kSymbolTableSize = 16 };
enum {
kIdentifierNameLength = 16,
kSymbolTableSize = 16,
kJumpPatchStackSize = 4
};
#else
static constexpr int kIdentifierNameLength = 16;
static constexpr int kSymbolTableSize = 16;
static constexpr int kJumpPatchStackSize = 4;
#endif

/// Pushes a value onto the jump patch stack.
/// Defined as a macro since cc65 doesn't support passing structs to functions
/// by value for regular functions.
#define PushJumpPatch(jump_patch) \
do { \
if (kJumpPatchStackSize <= jump_patch_stack_index) { \
puts("Error: Too many nested conditionals."); \
} else { \
jump_patch_stack[jump_patch_stack_index++] = (jump_patch); \
} \
} while (0)

/// Pops a value from the jump patch stack.
/// Defined as a macro since cc65 doesn't support passing structs to functions
/// by value for regular functions.
#define PopJumpPatch() \
(0 == jump_patch_stack_index \
? (puts("Error: Jump patch stack underflow."), kEmptyPendingJumpPatch) \
: jump_patch_stack[--jump_patch_stack_index])

typedef struct SymbolTableEntry {
char name[kIdentifierNameLength];
VariableType type;
size_t index;
} SymbolTableEntry;

typedef struct PendingJumpPatch {
size_t condition_patch_slot;
size_t exit_patch_slot;
} PendingJumpPatch;

static const PendingJumpPatch kEmptyPendingJumpPatch = {};

// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables)
static SymbolTableEntry symbol_table[kSymbolTableSize];

static PendingJumpPatch jump_patch_stack[kJumpPatchStackSize];
static size_t jump_patch_stack_index = 0;

static char local_names[kSymbolTableSize][kIdentifierNameLength];
static size_t local_count = 0;

Expand Down Expand Up @@ -377,8 +412,9 @@ static void ParsePrintStatement() {

// NOLINTNEXTLINE(misc-no-recursion)
static void ParseIfStatement() {
size_t if_jump_address = 0;
size_t else_jump_address = 0;
size_t condition_patch_slot = 0;
size_t exit_patch_slot = 0;
PendingJumpPatch pending_jump_patch = {};

if (!ExpectToken(1, kTokenLeftParenthesis)) {
return;
Expand All @@ -392,38 +428,50 @@ static void ParseIfStatement() {

EmitByte(kOpJumpIfFalse);

if_jump_address = instruction_address;
condition_patch_slot = instruction_address;

EmitByte(0);

pending_jump_patch.condition_patch_slot = condition_patch_slot;

PushJumpPatch(pending_jump_patch);

while (kTokenEndif != token.type && kTokenElse != token.type &&
kTokenEof != token.type) {
ParseStatement();
}

// No else
if (kTokenElse != token.type) {
instructions[if_jump_address] = instruction_address;
pending_jump_patch = PopJumpPatch();

instructions[pending_jump_patch.condition_patch_slot] = instruction_address;

ExpectToken(1, kTokenEndif);

return;
}

// If-else
EmitByte(kOpJump);

else_jump_address = instruction_address;
exit_patch_slot = instruction_address;

EmitByte(0);

instructions[if_jump_address] = instruction_address;
pending_jump_patch = PopJumpPatch();

instructions[pending_jump_patch.condition_patch_slot] = instruction_address;
pending_jump_patch.exit_patch_slot = exit_patch_slot;

// Parse else body.
ConsumeNextToken();

while (kTokenEndif != token.type && kTokenEof != token.type) {
ParseStatement();
}

instructions[else_jump_address] = instruction_address;
instructions[pending_jump_patch.exit_patch_slot] = instruction_address;

ExpectToken(1, kTokenEndif);
}
Expand Down
30 changes: 23 additions & 7 deletions src/vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,23 @@ static constexpr int kFunctionPoolSize = 16;
#endif

/// Pushes a value onto the stack.
/// Is defined as a macro since cc65 doesn't support passing structs to
/// functions by value for regular functions.
#define Push(value) (stack[stack_index++] = (value))
/// Defined as a macro since cc65 doesn't support passing structs to functions
/// by value for regular functions.
#define Push(value) \
do { \
if (kStackSize <= stack_index) { \
puts("Error: Stack overflow."); \
} else { \
stack[stack_index++] = (value); \
} \
} while (0)

/// Pops a value from the stack.
/// Is defined as a macro since cc65 doesn't support passing structs to
/// functions by value for regular functions.
#define Pop() (stack[--stack_index])
/// Defined as a macro since cc65 doesn't support passing structs to functions
/// by value for regular functions.
#define Pop() \
(0 == stack_index ? (puts("Error: Stack underflow."), kEmptyStackValue) \
: stack[--stack_index])

typedef struct Constants {
const void* pointer[kConstantsSize];
Expand All @@ -49,14 +58,21 @@ typedef struct Function {
} Function;

typedef struct StackValue {
VariableType type;
union as {
int number;
char* string;
Function* function;
} as;
VariableType type;
#ifdef __CC65__
unsigned char padding; ///< Used to add 1 byte padding to the struct, so that
///< the whole struct has a size of exactly 4 bytes.
///< See https://cc65.github.io/doc/cc65.html#s4
#endif
} StackValue;

static const StackValue kEmptyStackValue = {};

typedef struct CallFrame {
size_t return_address[kCallFrameTableSize];
size_t stack_offset[kCallFrameTableSize];
Expand Down
70 changes: 70 additions & 0 deletions tests/lexer_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,76 @@ void TestIf() {
TEST_ASSERT_EQUAL_INT(kTokenEndif, token.type);
}

void TestNestedIf() {
FillProgramBuffer(
"if(true)\nif(false)\nprint(\"foo\")\nelse\nprint(\"bar\")"
"\nendif\nendif");

ConsumeNextToken(); // if
TEST_ASSERT_EQUAL_INT(kTokenIf, token.type);

ConsumeNextToken(); // (
TEST_ASSERT_EQUAL_INT(kTokenLeftParenthesis, token.type);

ConsumeNextToken(); // true
TEST_ASSERT_EQUAL_INT(kTokenBoolean, token.type);
TEST_ASSERT_EQUAL_INT(1, token.value.number);

ConsumeNextToken(); // )
TEST_ASSERT_EQUAL_INT(kTokenRightParenthesis, token.type);

ConsumeNextToken(); // if
TEST_ASSERT_EQUAL_INT(kTokenIf, token.type);

ConsumeNextToken(); // (
TEST_ASSERT_EQUAL_INT(kTokenLeftParenthesis, token.type);

ConsumeNextToken(); // false
TEST_ASSERT_EQUAL_INT(kTokenBoolean, token.type);
TEST_ASSERT_EQUAL_INT(0, token.value.number);

ConsumeNextToken(); // )
TEST_ASSERT_EQUAL_INT(kTokenRightParenthesis, token.type);

ConsumeNextToken(); // print
TEST_ASSERT_EQUAL_INT(kTokenPrint, token.type);

ConsumeNextToken(); // (
TEST_ASSERT_EQUAL_INT(kTokenLeftParenthesis, token.type);

ConsumeNextToken(); // foo
TEST_ASSERT_EQUAL_INT(kTokenString, token.type);
TEST_ASSERT_EQUAL_STRING("foo", token.value.text);

ConsumeNextToken(); // )
TEST_ASSERT_EQUAL_INT(kTokenRightParenthesis, token.type);

ConsumeNextToken(); // else
TEST_ASSERT_EQUAL_INT(kTokenElse, token.type);

ConsumeNextToken(); // print
TEST_ASSERT_EQUAL_INT(kTokenPrint, token.type);

ConsumeNextToken(); // (
TEST_ASSERT_EQUAL_INT(kTokenLeftParenthesis, token.type);

ConsumeNextToken(); // bar
TEST_ASSERT_EQUAL_INT(kTokenString, token.type);
TEST_ASSERT_EQUAL_STRING("bar", token.value.text);

ConsumeNextToken(); // )
TEST_ASSERT_EQUAL_INT(kTokenRightParenthesis, token.type);

ConsumeNextToken(); // endif
TEST_ASSERT_EQUAL_INT(kTokenEndif, token.type);

ConsumeNextToken(); // endif
TEST_ASSERT_EQUAL_INT(kTokenEndif, token.type);

ConsumeNextToken(); // EOF
TEST_ASSERT_EQUAL_INT(kTokenEof, token.type);
}

void TestFor() {
FillProgramBuffer("for(true)print(\"Hello, world!\")endfor");

Expand Down
1 change: 1 addition & 0 deletions tests/lexer_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ void TestGreaterThanOrEqualTo();
void TestLessThanOrEqualTo();
void TestBooleanLiteral();
void TestIf();
void TestNestedIf();
void TestFor();
void TestNot();
void TestIntVariableDeclaration();
Expand Down
1 change: 1 addition & 0 deletions tests/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ int main() {
RUN_TEST(TestLessThanOrEqualTo);
RUN_TEST(TestBooleanLiteral);
RUN_TEST(TestIf);
RUN_TEST(TestNestedIf);
RUN_TEST(TestFor);
RUN_TEST(TestNot);
RUN_TEST(TestIntVariableDeclaration);
Expand Down