Skip to content

Commit

Permalink
Add local scopes to compiler
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuanunn committed Dec 30, 2023
1 parent 50eb60f commit 39921cc
Show file tree
Hide file tree
Showing 2 changed files with 162 additions and 2 deletions.
18 changes: 16 additions & 2 deletions compiler/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,22 @@ std::shared_ptr<Error> Compiler::compile(std::shared_ptr<Node> node)
return err;
}
auto symbol = symbol_table->define(l->name->value);
emit(OpType::OpSetGlobal, symbol.index);
if (symbol.scope == SymbolScope::GlobalScope) {
emit(OpType::OpSetGlobal, symbol.index);
} else {
emit(OpType::OpSetLocal, symbol.index);
}
// Identifier
} else if (auto id = std::dynamic_pointer_cast<Identifier>(node)) {
auto [symbol, ok] = symbol_table->resolve(id->value);
if (!ok) {
return new_error("undefined variable " + id->value);
}
emit(OpType::OpGetGlobal, symbol.index);
if (symbol.scope == SymbolScope::GlobalScope) {
emit(OpType::OpGetGlobal, symbol.index);
} else {
emit(OpType::OpGetLocal, symbol.index);
}
// String Literal
} else if (auto sl = std::dynamic_pointer_cast<StringLiteral>(node)) {
auto str = std::make_shared<String>(String{sl->value});
Expand Down Expand Up @@ -363,6 +371,9 @@ void Compiler::change_operand(int op_pos, int first_operand) {
void Compiler::enter_scope() {
scopes.push_back(CompilationScope{});
scope_index++;

// Create new enclosed symbol table when entering new compiler scope
symbol_table = new_enclosed_symbol_table(symbol_table);
}

Instructions Compiler::leave_scope() {
Expand All @@ -371,5 +382,8 @@ Instructions Compiler::leave_scope() {
scopes.pop_back();
scope_index--;

// When leaving compiler scope, drop current symbol table and reset to existing outer one
symbol_table = symbol_table->outer;

return instructions;
}
146 changes: 146 additions & 0 deletions compiler/compiler.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,8 @@ TEST_CASE("Test Compiler Scopes") {
}
REQUIRE(compiler->scope_index == 0);

auto global_symbol_table = compiler->symbol_table;

compiler->emit(OpType::OpMul);

compiler->enter_scope();
Expand All @@ -570,13 +572,28 @@ TEST_CASE("Test Compiler Scopes") {
}
REQUIRE(last.opcode == OpType::OpSub);

if (compiler->symbol_table->outer != global_symbol_table) {
std::cerr << "compiler did not enclose symbol table" << std::endl;
}
REQUIRE(compiler->symbol_table->outer == global_symbol_table);

compiler->leave_scope();

if (compiler->scope_index != 0) {
std::cerr << "scope_index wrong. got=" << compiler->scope_index << ", want=" << 0 << std::endl;
}
REQUIRE(compiler->scope_index == 0);

if (compiler->symbol_table != global_symbol_table) {
std::cerr << "compiler did not restore global symbol table" << std::endl;
}
REQUIRE(compiler->symbol_table == global_symbol_table);

if (compiler->symbol_table->outer) {
std::cerr << "compiler modified global symbol table incorrectly" << std::endl;
}
REQUIRE(!compiler->symbol_table->outer);

compiler->emit(OpType::OpAdd);

if (compiler->scopes.at(compiler->scope_index).instructions.size() != 2) {
Expand Down Expand Up @@ -816,3 +833,132 @@ noArg();
auto fn_constants = bytecode->constants.back();
REQUIRE(test_function_constants(expected_fn_instructions, fn_constants));
}

TEST_CASE("Test Let Statement Scopes 1") {
std::string input = R"(
let num = 55;
fn() { num };
)";
std::vector<int> expected_integer_constants = std::vector<int>{55};
std::vector<Instructions> expected_fn_instructions = {
make(OpType::OpGetGlobal, 0),
make(OpType::OpReturnValue),
};
std::vector<Instructions> expected_instructions = {
make(OpType::OpConstant, 0),
make(OpType::OpSetGlobal, 0),
make(OpType::OpConstant, 1),
make(OpType::OpPop),
};

auto program = parse(input);

auto compiler = new_compiler();

auto err = compiler->compile(program);
if (err) {
std::cerr << "compiler error: " << err->message << std::endl;
}
REQUIRE(!err);

auto bytecode = compiler->bytecode();

REQUIRE(test_instructions(expected_instructions, bytecode->instructions));

// Test integer constants
auto int_constants = std::vector<std::shared_ptr<Object>>(bytecode->constants.begin(), bytecode->constants.end() - 1);
REQUIRE(test_integer_constants(expected_integer_constants, int_constants));

// Test compiled function constants
auto fn_constants = bytecode->constants.back();
REQUIRE(test_function_constants(expected_fn_instructions, fn_constants));
}

TEST_CASE("Test Let Statement Scopes 2") {
std::string input = R"(
fn() {
let num = 55;
num;
}
)";
std::vector<int> expected_integer_constants = std::vector<int>{55};
std::vector<Instructions> expected_fn_instructions = {
make(OpType::OpConstant, 0),
make(OpType::OpSetLocal, 0),
make(OpType::OpGetLocal, 0),
make(OpType::OpReturnValue),
};
std::vector<Instructions> expected_instructions = {
make(OpType::OpConstant, 1),
make(OpType::OpPop),
};

auto program = parse(input);

auto compiler = new_compiler();

auto err = compiler->compile(program);
if (err) {
std::cerr << "compiler error: " << err->message << std::endl;
}
REQUIRE(!err);

auto bytecode = compiler->bytecode();

REQUIRE(test_instructions(expected_instructions, bytecode->instructions));

// Test integer constants
auto int_constants = std::vector<std::shared_ptr<Object>>(bytecode->constants.begin(), bytecode->constants.end() - 1);
REQUIRE(test_integer_constants(expected_integer_constants, int_constants));

// Test compiled function constants
auto fn_constants = bytecode->constants.back();
REQUIRE(test_function_constants(expected_fn_instructions, fn_constants));
}

TEST_CASE("Test Let Statement Scopes 3") {
std::string input = R"(
fn() {
let a = 55;
let b = 77;
a + b;
}
)";
std::vector<int> expected_integer_constants = std::vector<int>{55, 77};
std::vector<Instructions> expected_fn_instructions = {
make(OpType::OpConstant, 0),
make(OpType::OpSetLocal, 0),
make(OpType::OpConstant, 1),
make(OpType::OpSetLocal, 1),
make(OpType::OpGetLocal, 0),
make(OpType::OpGetLocal, 1),
make(OpType::OpAdd),
make(OpType::OpReturnValue),
};
std::vector<Instructions> expected_instructions = {
make(OpType::OpConstant, 2),
make(OpType::OpPop),
};

auto program = parse(input);

auto compiler = new_compiler();

auto err = compiler->compile(program);
if (err) {
std::cerr << "compiler error: " << err->message << std::endl;
}
REQUIRE(!err);

auto bytecode = compiler->bytecode();

REQUIRE(test_instructions(expected_instructions, bytecode->instructions));

// Test integer constants
auto int_constants = std::vector<std::shared_ptr<Object>>(bytecode->constants.begin(), bytecode->constants.end() - 1);
REQUIRE(test_integer_constants(expected_integer_constants, int_constants));

// Test compiled function constants
auto fn_constants = bytecode->constants.back();
REQUIRE(test_function_constants(expected_fn_instructions, fn_constants));
}

0 comments on commit 39921cc

Please sign in to comment.