Skip to content

Commit

Permalink
feat: add support for string
Browse files Browse the repository at this point in the history
  • Loading branch information
shejialuo committed Apr 1, 2023
1 parent 1e5601c commit 2d26f20
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 3 deletions.
10 changes: 8 additions & 2 deletions compiler/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ void Compiler::compile(Node *node) {

IntegerLiteral *integerLiteral = dynamic_cast<IntegerLiteral *>(node);
if (integerLiteral != nullptr) {
auto integer = std::make_unique<Integer>(integerLiteral->value);
std::unique_ptr<Object> integer = std::make_unique<Integer>(integerLiteral->value);
// Here, we push the index for the constant, not the number itself.
emit(Ops::OpConstant, {addConstant(integer)});
}
Expand Down Expand Up @@ -159,9 +159,15 @@ void Compiler::compile(Node *node) {

emit(Ops::OpGetGlobal, {symbol.value().get().index});
}

StringLiteral *stringLiteral = dynamic_cast<StringLiteral *>(node);
if (stringLiteral != nullptr) {
std::unique_ptr<Object> string = std::make_unique<String>(stringLiteral->value);
emit(Ops::OpConstant, {addConstant(string)});
}
}

int Compiler::addConstant(std::unique_ptr<Integer> &object) {
int Compiler::addConstant(std::unique_ptr<Object> &object) {
bytecode.constants.emplace_back(std::move(object));
return bytecode.constants.size() - 1;
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/compiler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class Compiler {
* @param object
* @return int
*/
int addConstant(std::unique_ptr<Integer> &object);
int addConstant(std::unique_ptr<Object> &object);

/**
* @brief emit the instruction, return the instruction length
Expand Down
52 changes: 52 additions & 0 deletions compiler/tests/compilerTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ std::unique_ptr<Program> parse(const std::string &input);
Instructions concatInstructions(std::vector<Instructions> &instructions);
bool testInstructions(const Instructions &expected, const Instructions &actual);
bool testIntegerObject(int expected, Object *actual);
bool testStringObject(const std::string &expected, Object *actual);

template <typename T>
bool testConstants(std::vector<T> &expected, std::vector<std::shared_ptr<Object>> &actual);
Expand All @@ -44,6 +45,10 @@ bool testConstants(std::vector<T> &expected, std::vector<std::shared_ptr<Object>
if (!testIntegerObject(expected[i], actual[i].get())) {
return false;
}
} else if constexpr (std::is_same_v<std::string, T>) {
if (!testStringObject(expected[i], actual[i].get())) {
return false;
}
}
}

Expand Down Expand Up @@ -97,6 +102,21 @@ bool testIntegerObject(int expected, Object *actual) {
return true;
}

bool testStringObject(const std::string &expected, Object *actual) {
auto str = dynamic_cast<const String *>(actual);
if (str == nullptr) {
spdlog::error("object is not String. got={}", actual->type());
return false;
}

if (str->value != expected) {
spdlog::error("object has wrong value. want={}, got={}", expected, str->value);
return false;
}

return true;
}

TEST(Compiler, TestIntegerArithmetic) {
std::vector<CompilerTestCase<int>> tests{
{
Expand Down Expand Up @@ -367,3 +387,35 @@ TEST(Compiler, TestGlobalLetStatements) {
EXPECT_TRUE(testConstants(test.expectedConstants, compiler.getBytecode().constants));
}
}

TEST(Compiler, TestStringExpressions) {
std::vector<CompilerTestCase<std::string>> tests{
{
R"("monkey")",
{"monkey"},
{
Code::make(Ops::OpConstant, {0}),
Code::make(Ops::OpPop, {}),
},
},
{
R"("mon" + "key")",
{"mon", "key"},
{
Code::make(Ops::OpConstant, {0}),
Code::make(Ops::OpConstant, {1}),
Code::make(Ops::OpAdd, {}),
Code::make(Ops::OpPop, {}),
},
},
};

for (auto &&test : tests) {
auto program = parse(test.input);
Compiler compiler;
compiler.compile(program.get());
auto instructions = compiler.getBytecode().instructions;
EXPECT_TRUE(testInstructions(test.expectedInstructions, instructions));
EXPECT_TRUE(testConstants(test.expectedConstants, compiler.getBytecode().constants));
}
}
39 changes: 39 additions & 0 deletions vm/tests/vmTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ bool testExpectedObject(const T &expected, Object *actual) {
return testIntegerObject(expected, actual);
} else if constexpr (std::is_same_v<bool, T>) {
return testBooleanObject(expected, actual);
} else if constexpr (std::is_same_v<std::string, T>) {
return testStringObject(expected, actual);
}
return false;
}
Expand Down Expand Up @@ -72,6 +74,21 @@ bool testBooleanObject(bool expected, Object *actual) {
return true;
}

bool testStringObject(const std::string &expected, Object *actual) {
auto string = dynamic_cast<const String *>(actual);
if (string == nullptr) {
spdlog::error("object is not String. got={}", actual->type());
return false;
}

if (string->value != expected) {
spdlog::error("object has wrong value. want={}, got={}", expected, string->value);
return false;
}

return true;
}

TEST(VM, TestIntegerArithmetic) {
std::vector<vmTestCase<int>> tests{
{"1", 1},
Expand Down Expand Up @@ -203,3 +220,25 @@ TEST(VM, TestGlobalLetStatements) {
EXPECT_TRUE(testExpectedObject(test.expected, stackElem.get()));
}
}

TEST(VM, TestStringExpressions) {
std::vector<vmTestCase<std::string>> tests{
{"\"monkey\"", "monkey"},
{"\"mon\" + \"key\"", "monkey"},
{"\"mon\" + \"key\" + \"banana\"", "monkeybanana"},
};

for (auto &&test : tests) {
auto program = parse(test.input);

Compiler compiler;
compiler.compile(program.get());

VM vm{std::move(compiler.getBytecode().constants), std::move(compiler.getBytecode().instructions)};
vm.run();

auto stackElem = vm.lastPoppedStackElem();

EXPECT_TRUE(testExpectedObject(test.expected, stackElem.get()));
}
}
15 changes: 15 additions & 0 deletions vm/vm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ void VM::executeBinaryOperation(const Opcode &op) {

if (leftType == INTEGER_OBJ && rightType == INTEGER_OBJ) {
executeBinaryIntegerOperation(op, left, right);
} else if (leftType == STRING_OBJ && rightType == STRING_OBJ) {
executeBinaryStringOperation(op, left, right);
} else {
spdlog::error("unsupported types for binary operation: {} {}", leftType, rightType);
}
Expand Down Expand Up @@ -133,6 +135,19 @@ void VM::executeBinaryIntegerOperation(const Opcode &op,
push(resultObject);
}

void VM::executeBinaryStringOperation(const Opcode &op, std::shared_ptr<Object> &left, std::shared_ptr<Object> &right) {
String *rightString = dynamic_cast<String *>(right.get());
String *leftString = dynamic_cast<String *>(left.get());

if (op == Ops::OpAdd) {
std::string result = leftString->value + rightString->value;
std::shared_ptr<Object> resultObject = std::make_shared<String>(result);
push(resultObject);
} else {
spdlog::error("unknown operator for strings: {}", op);
}
}

void VM::executeComparision(const Opcode &op) {
auto right = pop();
auto left = pop();
Expand Down
9 changes: 9 additions & 0 deletions vm/vm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,15 @@ class VM {
*/
void executeBinaryIntegerOperation(const Opcode &op, std::shared_ptr<Object> &left, std::shared_ptr<Object> &right);

/**
* @brief execute the string operator such as add
*
* @param op the operator
* @param left the left object
* @param right the right object
*/
void executeBinaryStringOperation(const Opcode &op, std::shared_ptr<Object> &left, std::shared_ptr<Object> &right);

/**
* @brief execute the comparison such as equal, not equal, greater than, less than
*
Expand Down

0 comments on commit 2d26f20

Please sign in to comment.