Remove interpreter

This commit is contained in:
2026-01-22 20:33:43 +08:00
parent 2839c0daff
commit 8c5197c760
5 changed files with 375 additions and 507 deletions

View File

@@ -13,7 +13,6 @@ set(LIB_SOURCES
src/lexer.cpp src/lexer.cpp
src/parser.cpp src/parser.cpp
src/value.cpp src/value.cpp
src/interpreter.cpp
src/state.cpp src/state.cpp
src/chunk.cpp src/chunk.cpp
src/compiler.cpp src/compiler.cpp
@@ -27,7 +26,6 @@ set(LIB_HEADERS
src/parser.h src/parser.h
src/ast.h src/ast.h
src/value.h src/value.h
src/interpreter.h
src/state.h src/state.h
src/chunk.h src/chunk.h
src/compiler.h src/compiler.h

View File

@@ -4,171 +4,143 @@
namespace camellya { namespace camellya {
State::State(ExecutionMode mode) State::State()
: execution_mode_(mode), : vm_(std::make_unique<VM>()), compiler_(std::make_unique<Compiler>()) {}
interpreter_(std::make_unique<Interpreter>()),
vm_(std::make_unique<VM>()),
compiler_(std::make_unique<Compiler>()) {}
bool State::do_string(const std::string& script) { bool State::do_string(const std::string &script) {
try { try {
Lexer lexer(script); Lexer lexer(script);
auto tokens = lexer.tokenize(); auto tokens = lexer.tokenize();
Parser parser(std::move(tokens)); Parser parser(std::move(tokens));
Program program = parser.parse(); Program program = parser.parse();
bool success = execute_program(program); bool success = execute_program(program);
if (success) { if (success) {
last_error_.clear(); last_error_.clear();
}
return success;
} catch (const std::exception& e) {
last_error_ = e.what();
return false;
} }
return success;
} catch (const std::exception &e) {
last_error_ = e.what();
return false;
}
} }
bool State::do_file(const std::string& filename) { bool State::do_file(const std::string &filename) {
std::ifstream file(filename); std::ifstream file(filename);
if (!file.is_open()) { if (!file.is_open()) {
last_error_ = "Failed to open file: " + filename; last_error_ = "Failed to open file: " + filename;
return false; return false;
} }
std::stringstream buffer; std::stringstream buffer;
buffer << file.rdbuf(); buffer << file.rdbuf();
return do_string(buffer.str()); return do_string(buffer.str());
} }
void State::register_function(const std::string& name, NativeFunction func) { void State::register_function(const std::string &name, NativeFunction func) {
auto func_value = std::make_shared<FunctionValue>(name, func); auto func_value = std::make_shared<FunctionValue>(name, func);
vm_->register_native_function(name, func);
if (execution_mode_ == ExecutionMode::INTERPRETER) {
interpreter_->global_environment->define(name, func_value);
} else {
vm_->register_native_function(name, func);
}
} }
ValuePtr State::get_global(const std::string& name) { ValuePtr State::get_global(const std::string &name) {
if (execution_mode_ == ExecutionMode::INTERPRETER) { return vm_->get_global(name);
return interpreter_->global_environment->get(name);
} else {
return vm_->get_global(name);
}
} }
void State::set_global(const std::string& name, ValuePtr value) { void State::set_global(const std::string &name, ValuePtr value) {
if (execution_mode_ == ExecutionMode::INTERPRETER) { vm_->set_global(name, value);
interpreter_->global_environment->define(name, value);
} else {
vm_->set_global(name, value);
}
} }
void State::push_number(double value) { void State::push_number(double value) {
stack_.push_back(std::make_shared<NumberValue>(value)); stack_.push_back(std::make_shared<NumberValue>(value));
} }
void State::push_string(const std::string& value) { void State::push_string(const std::string &value) {
stack_.push_back(std::make_shared<StringValue>(value)); stack_.push_back(std::make_shared<StringValue>(value));
} }
void State::push_bool(bool value) { void State::push_bool(bool value) {
stack_.push_back(std::make_shared<BoolValue>(value)); stack_.push_back(std::make_shared<BoolValue>(value));
} }
void State::push_nil() { void State::push_nil() { stack_.push_back(std::make_shared<NilValue>()); }
stack_.push_back(std::make_shared<NilValue>());
}
void State::push_value(ValuePtr value) { void State::push_value(ValuePtr value) { stack_.push_back(std::move(value)); }
stack_.push_back(std::move(value));
}
double State::to_number(int index) { double State::to_number(int index) {
ValuePtr val = get_stack_value(index); ValuePtr val = get_stack_value(index);
if (val && val->type() == Type::NUMBER) { if (val && val->type() == Type::NUMBER) {
return std::dynamic_pointer_cast<NumberValue>(val)->value; return std::dynamic_pointer_cast<NumberValue>(val)->value;
} }
return 0.0; return 0.0;
} }
std::string State::to_string(int index) { std::string State::to_string(int index) {
ValuePtr val = get_stack_value(index); ValuePtr val = get_stack_value(index);
if (val) { if (val) {
return val->to_string(); return val->to_string();
} }
return ""; return "";
} }
bool State::to_bool(int index) { bool State::to_bool(int index) {
ValuePtr val = get_stack_value(index); ValuePtr val = get_stack_value(index);
return is_truthy(val); return is_truthy(val);
} }
ValuePtr State::to_value(int index) { ValuePtr State::to_value(int index) { return get_stack_value(index); }
return get_stack_value(index);
}
void State::set_top(int index) { void State::set_top(int index) {
if (index < 0) { if (index < 0) {
index = static_cast<int>(stack_.size()) + index + 1; index = static_cast<int>(stack_.size()) + index + 1;
} }
if (index < 0) { if (index < 0) {
stack_.clear(); stack_.clear();
} else if (static_cast<size_t>(index) < stack_.size()) { } else if (static_cast<size_t>(index) < stack_.size()) {
stack_.resize(index); stack_.resize(index);
} else { } else {
while (static_cast<size_t>(index) > stack_.size()) { while (static_cast<size_t>(index) > stack_.size()) {
stack_.push_back(std::make_shared<NilValue>()); stack_.push_back(std::make_shared<NilValue>());
}
} }
}
} }
void State::pop(int n) { void State::pop(int n) {
if (n > static_cast<int>(stack_.size())) { if (n > static_cast<int>(stack_.size())) {
stack_.clear(); stack_.clear();
} else { } else {
stack_.resize(stack_.size() - n); stack_.resize(stack_.size() - n);
} }
} }
ValuePtr State::get_stack_value(int index) { ValuePtr State::get_stack_value(int index) {
if (index < 0) { if (index < 0) {
index = static_cast<int>(stack_.size()) + index; index = static_cast<int>(stack_.size()) + index;
} else { } else {
index -= 1; // Convert to 0-based index -= 1; // Convert to 0-based
} }
if (index < 0 || static_cast<size_t>(index) >= stack_.size()) { if (index < 0 || static_cast<size_t>(index) >= stack_.size()) {
return nullptr; return nullptr;
} }
return stack_[index]; return stack_[index];
} }
bool State::execute_program(const Program& program) { bool State::execute_program(const Program &program) {
if (execution_mode_ == ExecutionMode::INTERPRETER) { // Use VM
// Use tree-walking interpreter auto chunk = compiler_->compile(program);
interpreter_->execute(program); if (!chunk) {
return true; last_error_ = compiler_->get_error();
} else { return false;
// Use VM }
auto chunk = compiler_->compile(program);
if (!chunk) {
last_error_ = compiler_->get_error();
return false;
}
bool success = vm_->execute(chunk); bool success = vm_->execute(chunk);
if (!success) { if (!success) {
last_error_ = vm_->get_error(); last_error_ = vm_->get_error();
} }
return success; return success;
}
} }
} // namespace camellya } // namespace camellya

View File

@@ -1,79 +1,66 @@
#ifndef CAMELLYA_STATE_H #ifndef CAMELLYA_STATE_H
#define CAMELLYA_STATE_H #define CAMELLYA_STATE_H
#include "compiler.h"
#include "lexer.h" #include "lexer.h"
#include "parser.h" #include "parser.h"
#include "interpreter.h"
#include "vm.h"
#include "compiler.h"
#include "value.h" #include "value.h"
#include <string> #include "vm.h"
#include <memory> #include <memory>
#include <string>
namespace camellya { namespace camellya {
// Execution mode
enum class ExecutionMode {
INTERPRETER, // Tree-walking interpreter
VM // Bytecode VM
};
// Main state class - similar to lua_State // Main state class - similar to lua_State
class State { class State {
public: public:
State(ExecutionMode mode = ExecutionMode::VM); State();
~State() = default; ~State() = default;
// Set execution mode // Execute script from string
void set_execution_mode(ExecutionMode mode) { execution_mode_ = mode; } bool do_string(const std::string &script);
ExecutionMode get_execution_mode() const { return execution_mode_; }
// Execute script from string // Execute script from file
bool do_string(const std::string& script); bool do_file(const std::string &filename);
// Execute script from file // Register native function
bool do_file(const std::string& filename); void register_function(const std::string &name, NativeFunction func);
// Register native function // Get global variable
void register_function(const std::string& name, NativeFunction func); ValuePtr get_global(const std::string &name);
// Get global variable // Set global variable
ValuePtr get_global(const std::string& name); void set_global(const std::string &name, ValuePtr value);
// Set global variable // Stack operations (Lua-like API)
void set_global(const std::string& name, ValuePtr value); void push_number(double value);
void push_string(const std::string &value);
void push_bool(bool value);
void push_nil();
void push_value(ValuePtr value);
// Stack operations (Lua-like API) double to_number(int index);
void push_number(double value); std::string to_string(int index);
void push_string(const std::string& value); bool to_bool(int index);
void push_bool(bool value); ValuePtr to_value(int index);
void push_nil();
void push_value(ValuePtr value);
double to_number(int index); int get_top() const { return static_cast<int>(stack_.size()); }
std::string to_string(int index); void set_top(int index);
bool to_bool(int index); void pop(int n = 1);
ValuePtr to_value(int index);
int get_top() const { return static_cast<int>(stack_.size()); } // Error handling
void set_top(int index); const std::string &get_error() const { return last_error_; }
void pop(int n = 1);
// Error handling
const std::string& get_error() const { return last_error_; }
private: private:
ExecutionMode execution_mode_; std::unique_ptr<VM> vm_;
std::unique_ptr<Interpreter> interpreter_; std::unique_ptr<Compiler> compiler_;
std::unique_ptr<VM> vm_; std::vector<ValuePtr> stack_;
std::unique_ptr<Compiler> compiler_; std::string last_error_;
std::vector<ValuePtr> stack_;
std::string last_error_;
ValuePtr get_stack_value(int index); ValuePtr get_stack_value(int index);
// Helper for execution // Helper for execution
bool execute_program(const Program& program); bool execute_program(const Program &program);
}; };
} // namespace camellya } // namespace camellya

View File

@@ -1,82 +1,82 @@
#include <catch2/catch_test_macros.hpp>
#include <catch2/benchmark/catch_benchmark.hpp>
#include "camellya.h" #include "camellya.h"
#include <catch2/benchmark/catch_benchmark.hpp>
#include <catch2/catch_test_macros.hpp>
#include <memory> #include <memory>
using namespace camellya; using namespace camellya;
TEST_CASE("basic arithmetic", "[script]") { TEST_CASE("basic arithmetic", "[script]") {
State state; State state;
const char* script = R"( const char *script = R"(
var x = 10; var x = 10;
var y = 20; var y = 20;
var z = x + y; var z = x + y;
)"; )";
REQUIRE(state.do_string(script)); REQUIRE(state.do_string(script));
auto z = state.get_global("z"); auto z = state.get_global("z");
REQUIRE(z); REQUIRE(z);
REQUIRE(z->type() == Type::NUMBER); REQUIRE(z->type() == Type::NUMBER);
auto nz = std::dynamic_pointer_cast<NumberValue>(z); auto nz = std::dynamic_pointer_cast<NumberValue>(z);
REQUIRE(nz); REQUIRE(nz);
REQUIRE(nz->value == 30.0); REQUIRE(nz->value == 30.0);
} }
TEST_CASE("basic function", "[script][func]") { TEST_CASE("basic function", "[script][func]") {
State state; State state;
const char* script = R"( const char *script = R"(
func add(number x, number y) -> number { func add(number x, number y) -> number {
return x + y; return x + y;
} }
var z = add(10, 20); var z = add(10, 20);
)"; )";
REQUIRE(state.do_string(script)); REQUIRE(state.do_string(script));
auto z = state.get_global("z"); auto z = state.get_global("z");
REQUIRE(z); REQUIRE(z);
REQUIRE(z->type() == Type::NUMBER); REQUIRE(z->type() == Type::NUMBER);
auto nz = std::dynamic_pointer_cast<NumberValue>(z); auto nz = std::dynamic_pointer_cast<NumberValue>(z);
REQUIRE(nz); REQUIRE(nz);
REQUIRE(nz->value == 30.0); REQUIRE(nz->value == 30.0);
} }
TEST_CASE("list indexing is 0-based", "[list]") { TEST_CASE("list indexing is 0-based", "[list]") {
State state; State state;
const char* script = R"( const char *script = R"(
var numbers = [10, 20, 30]; var numbers = [10, 20, 30];
)"; )";
REQUIRE(state.do_string(script)); REQUIRE(state.do_string(script));
auto list_val = state.get_global("numbers"); auto list_val = state.get_global("numbers");
REQUIRE(list_val); REQUIRE(list_val);
REQUIRE(list_val->type() == Type::LIST); REQUIRE(list_val->type() == Type::LIST);
auto list = std::dynamic_pointer_cast<ListValue>(list_val); auto list = std::dynamic_pointer_cast<ListValue>(list_val);
REQUIRE(list); REQUIRE(list);
REQUIRE(list->size() == 3); REQUIRE(list->size() == 3);
auto first = list->get(0); auto first = list->get(0);
auto third = list->get(2); auto third = list->get(2);
REQUIRE(first->type() == Type::NUMBER); REQUIRE(first->type() == Type::NUMBER);
REQUIRE(third->type() == Type::NUMBER); REQUIRE(third->type() == Type::NUMBER);
auto first_n = std::dynamic_pointer_cast<NumberValue>(first); auto first_n = std::dynamic_pointer_cast<NumberValue>(first);
auto third_n = std::dynamic_pointer_cast<NumberValue>(third); auto third_n = std::dynamic_pointer_cast<NumberValue>(third);
REQUIRE(first_n->value == 10.0); REQUIRE(first_n->value == 10.0);
REQUIRE(third_n->value == 30.0); REQUIRE(third_n->value == 30.0);
} }
TEST_CASE("class init is called on declaration", "[class][init]") { TEST_CASE("class init is called on declaration", "[class][init]") {
State state; State state;
const char* script = R"( const char *script = R"(
class Person { class Person {
var age : number; var age : number;
var name : string; var name : string;
@@ -95,38 +95,37 @@ TEST_CASE("class init is called on declaration", "[class][init]") {
var a = p.getAge(); var a = p.getAge();
)"; )";
REQUIRE(state.do_string(script)); REQUIRE(state.do_string(script));
auto p_val = state.get_global("p"); auto p_val = state.get_global("p");
REQUIRE(p_val); REQUIRE(p_val);
REQUIRE(p_val->type() == Type::INSTANCE); REQUIRE(p_val->type() == Type::INSTANCE);
auto instance = std::dynamic_pointer_cast<InstanceValue>(p_val); auto instance = std::dynamic_pointer_cast<InstanceValue>(p_val);
REQUIRE(instance); REQUIRE(instance);
auto age_val = instance->get("age"); auto age_val = instance->get("age");
auto name_val = instance->get("name"); auto name_val = instance->get("name");
REQUIRE(age_val->type() == Type::NUMBER); REQUIRE(age_val->type() == Type::NUMBER);
REQUIRE(name_val->type() == Type::STRING); REQUIRE(name_val->type() == Type::STRING);
auto age_num = std::dynamic_pointer_cast<NumberValue>(age_val); auto age_num = std::dynamic_pointer_cast<NumberValue>(age_val);
auto name_str = std::dynamic_pointer_cast<StringValue>(name_val); auto name_str = std::dynamic_pointer_cast<StringValue>(name_val);
REQUIRE(age_num->value == 18.0); REQUIRE(age_num->value == 18.0);
REQUIRE(name_str->value == "Default"); REQUIRE(name_str->value == "Default");
auto a_val = state.get_global("a"); auto a_val = state.get_global("a");
REQUIRE(a_val); REQUIRE(a_val);
REQUIRE(a_val->type() == Type::NUMBER); REQUIRE(a_val->type() == Type::NUMBER);
auto a_num = std::dynamic_pointer_cast<NumberValue>(a_val); auto a_num = std::dynamic_pointer_cast<NumberValue>(a_val);
REQUIRE(a_num->value == 18.0); REQUIRE(a_num->value == 18.0);
} }
TEST_CASE("interpreter performance: simple loop", "[perf][script]") { TEST_CASE("interpreter performance: simple loop", "[perf][script]") {
State state; State state;
State state_vm(ExecutionMode::VM); const char *script = R"(
const char* script = R"(
func sum_to(number n) -> number { func sum_to(number n) -> number {
var s = 0; var s = 0;
for (var i = 0; i < n; i = i + 1) { for (var i = 0; i < n; i = i + 1) {
@@ -137,35 +136,23 @@ TEST_CASE("interpreter performance: simple loop", "[perf][script]") {
var r = sum_to(1000); var r = sum_to(1000);
)"; )";
BENCHMARK("sum_to(1000)") { BENCHMARK("sum_to(1000)") {
if (!state.do_string(script)) { if (!state.do_string(script)) {
auto last_error = state.get_error(); auto last_error = state.get_error();
REQUIRE(last_error.empty()); REQUIRE(last_error.empty());
} }
auto r_val = state.get_global("r"); auto r_val = state.get_global("r");
REQUIRE(r_val); REQUIRE(r_val);
REQUIRE(r_val->type() == Type::NUMBER); REQUIRE(r_val->type() == Type::NUMBER);
auto r_num = std::dynamic_pointer_cast<NumberValue>(r_val); auto r_num = std::dynamic_pointer_cast<NumberValue>(r_val);
REQUIRE(r_num->value == 499500.0); REQUIRE(r_num->value == 499500.0);
}; };
BENCHMARK("state_vm sum_to(1000)") {
if (!state_vm.do_string(script)) {
auto last_error = state_vm.get_error();
REQUIRE(last_error.empty());
}
auto r_val = state_vm.get_global("r");
REQUIRE(r_val);
REQUIRE(r_val->type() == Type::NUMBER);
auto r_num = std::dynamic_pointer_cast<NumberValue>(r_val);
REQUIRE(r_num->value == 499500.0);
};
} }
TEST_CASE("loop break", "[script][loop]") { TEST_CASE("loop break", "[script][loop]") {
State state; State state;
const char* script = R"( const char *script = R"(
var sum = 0; var sum = 0;
for (var i = 0; i < 10; i = i + 1) { for (var i = 0; i < 10; i = i + 1) {
if (i == 5) { if (i == 5) {
@@ -175,16 +162,16 @@ TEST_CASE("loop break", "[script][loop]") {
} }
)"; )";
REQUIRE(state.do_string(script)); REQUIRE(state.do_string(script));
auto sum_val = state.get_global("sum"); auto sum_val = state.get_global("sum");
REQUIRE(sum_val); REQUIRE(sum_val);
auto sum_num = std::dynamic_pointer_cast<NumberValue>(sum_val); auto sum_num = std::dynamic_pointer_cast<NumberValue>(sum_val);
REQUIRE(sum_num->value == 10.0); // 0+1+2+3+4 = 10 REQUIRE(sum_num->value == 10.0); // 0+1+2+3+4 = 10
} }
TEST_CASE("loop continue", "[script][loop]") { TEST_CASE("loop continue", "[script][loop]") {
State state; State state;
const char* script = R"( const char *script = R"(
var sum = 0; var sum = 0;
for (var i = 0; i < 5; i = i + 1) { for (var i = 0; i < 5; i = i + 1) {
if (i == 2) { if (i == 2) {
@@ -194,16 +181,16 @@ TEST_CASE("loop continue", "[script][loop]") {
} }
)"; )";
REQUIRE(state.do_string(script)); REQUIRE(state.do_string(script));
auto sum_val = state.get_global("sum"); auto sum_val = state.get_global("sum");
REQUIRE(sum_val); REQUIRE(sum_val);
auto sum_num = std::dynamic_pointer_cast<NumberValue>(sum_val); auto sum_num = std::dynamic_pointer_cast<NumberValue>(sum_val);
REQUIRE(sum_num->value == 8.0); // 0+1+3+4 = 8 REQUIRE(sum_num->value == 8.0); // 0+1+3+4 = 8
} }
TEST_CASE("while break and continue", "[script][loop]") { TEST_CASE("while break and continue", "[script][loop]") {
State state; State state;
const char* script = R"( const char *script = R"(
var i = 0; var i = 0;
var sum = 0; var sum = 0;
while (i < 10) { while (i < 10) {
@@ -218,16 +205,16 @@ TEST_CASE("while break and continue", "[script][loop]") {
} }
)"; )";
REQUIRE(state.do_string(script)); REQUIRE(state.do_string(script));
auto sum_val = state.get_global("sum"); auto sum_val = state.get_global("sum");
REQUIRE(sum_val); REQUIRE(sum_val);
auto sum_num = std::dynamic_pointer_cast<NumberValue>(sum_val); auto sum_num = std::dynamic_pointer_cast<NumberValue>(sum_val);
REQUIRE(sum_num->value == 12.0); REQUIRE(sum_num->value == 12.0);
// 1st iter: i=1, sum=1 // 1st iter: i=1, sum=1
// 2nd iter: i=2, sum=1+2=3 // 2nd iter: i=2, sum=1+2=3
// 3rd iter: i=3, continue // 3rd iter: i=3, continue
// 4th iter: i=4, sum=3+4=7 // 4th iter: i=4, sum=3+4=7
// 5th iter: i=5, sum=7+5=12 // 5th iter: i=5, sum=7+5=12
// 6th iter: i=6, break // 6th iter: i=6, break
// Result should be 12.0 // Result should be 12.0
} }

View File

@@ -1,154 +1,155 @@
#include <catch2/catch_test_macros.hpp>
#include "src/camellya.h" #include "src/camellya.h"
#include <catch2/catch_test_macros.hpp>
using namespace camellya; using namespace camellya;
TEST_CASE("VM - Basic arithmetic", "[vm]") { TEST_CASE("VM - Basic arithmetic", "[vm]") {
State state(ExecutionMode::VM); State state;
SECTION("Addition") { SECTION("Addition") {
REQUIRE(state.do_string("var x = 10 + 20;")); REQUIRE(state.do_string("var x = 10 + 20;"));
auto x = state.get_global("x"); auto x = state.get_global("x");
REQUIRE(x != nullptr); REQUIRE(x != nullptr);
REQUIRE(x->type() == Type::NUMBER); REQUIRE(x->type() == Type::NUMBER);
REQUIRE(std::dynamic_pointer_cast<NumberValue>(x)->value == 30.0); REQUIRE(std::dynamic_pointer_cast<NumberValue>(x)->value == 30.0);
} }
SECTION("Subtraction") { SECTION("Subtraction") {
REQUIRE(state.do_string("var y = 50 - 15;")); REQUIRE(state.do_string("var y = 50 - 15;"));
auto y = state.get_global("y"); auto y = state.get_global("y");
REQUIRE(y != nullptr); REQUIRE(y != nullptr);
REQUIRE(std::dynamic_pointer_cast<NumberValue>(y)->value == 35.0); REQUIRE(std::dynamic_pointer_cast<NumberValue>(y)->value == 35.0);
} }
SECTION("Multiplication") { SECTION("Multiplication") {
REQUIRE(state.do_string("var z = 7 * 8;")); REQUIRE(state.do_string("var z = 7 * 8;"));
auto z = state.get_global("z"); auto z = state.get_global("z");
REQUIRE(z != nullptr); REQUIRE(z != nullptr);
REQUIRE(std::dynamic_pointer_cast<NumberValue>(z)->value == 56.0); REQUIRE(std::dynamic_pointer_cast<NumberValue>(z)->value == 56.0);
} }
SECTION("Division") { SECTION("Division") {
REQUIRE(state.do_string("var w = 100 / 4;")); REQUIRE(state.do_string("var w = 100 / 4;"));
auto w = state.get_global("w"); auto w = state.get_global("w");
REQUIRE(w != nullptr); REQUIRE(w != nullptr);
REQUIRE(std::dynamic_pointer_cast<NumberValue>(w)->value == 25.0); REQUIRE(std::dynamic_pointer_cast<NumberValue>(w)->value == 25.0);
} }
} }
TEST_CASE("VM - Variables and assignment", "[vm]") { TEST_CASE("VM - Variables and assignment", "[vm]") {
State state(ExecutionMode::VM); State state;
SECTION("Variable declaration and initialization") { SECTION("Variable declaration and initialization") {
REQUIRE(state.do_string("var a = 42;")); REQUIRE(state.do_string("var a = 42;"));
auto a = state.get_global("a"); auto a = state.get_global("a");
REQUIRE(a != nullptr); REQUIRE(a != nullptr);
REQUIRE(std::dynamic_pointer_cast<NumberValue>(a)->value == 42.0); REQUIRE(std::dynamic_pointer_cast<NumberValue>(a)->value == 42.0);
} }
SECTION("Variable assignment") { SECTION("Variable assignment") {
REQUIRE(state.do_string(R"( REQUIRE(state.do_string(R"(
var b = 10; var b = 10;
b = 20; b = 20;
)")); )"));
auto b = state.get_global("b"); auto b = state.get_global("b");
REQUIRE(std::dynamic_pointer_cast<NumberValue>(b)->value == 20.0); REQUIRE(std::dynamic_pointer_cast<NumberValue>(b)->value == 20.0);
} }
} }
TEST_CASE("VM - String operations", "[vm]") { TEST_CASE("VM - String operations", "[vm]") {
State state(ExecutionMode::VM); State state;
SECTION("String concatenation") { SECTION("String concatenation") {
REQUIRE(state.do_string(R"(var greeting = "Hello" + " " + "World";)")); REQUIRE(state.do_string(R"(var greeting = "Hello" + " " + "World";)"));
auto greeting = state.get_global("greeting"); auto greeting = state.get_global("greeting");
REQUIRE(greeting != nullptr); REQUIRE(greeting != nullptr);
REQUIRE(greeting->type() == Type::STRING); REQUIRE(greeting->type() == Type::STRING);
REQUIRE(std::dynamic_pointer_cast<StringValue>(greeting)->value == "Hello World"); REQUIRE(std::dynamic_pointer_cast<StringValue>(greeting)->value ==
} "Hello World");
}
} }
TEST_CASE("VM - Comparison operators", "[vm]") { TEST_CASE("VM - Comparison operators", "[vm]") {
State state(ExecutionMode::VM); State state;
SECTION("Equality") { SECTION("Equality") {
REQUIRE(state.do_string("var eq = 10 == 10;")); REQUIRE(state.do_string("var eq = 10 == 10;"));
auto eq = state.get_global("eq"); auto eq = state.get_global("eq");
REQUIRE(std::dynamic_pointer_cast<BoolValue>(eq)->value == true); REQUIRE(std::dynamic_pointer_cast<BoolValue>(eq)->value == true);
} }
SECTION("Greater than") { SECTION("Greater than") {
REQUIRE(state.do_string("var gt = 20 > 10;")); REQUIRE(state.do_string("var gt = 20 > 10;"));
auto gt = state.get_global("gt"); auto gt = state.get_global("gt");
REQUIRE(std::dynamic_pointer_cast<BoolValue>(gt)->value == true); REQUIRE(std::dynamic_pointer_cast<BoolValue>(gt)->value == true);
} }
SECTION("Less than") { SECTION("Less than") {
REQUIRE(state.do_string("var lt = 5 < 10;")); REQUIRE(state.do_string("var lt = 5 < 10;"));
auto lt = state.get_global("lt"); auto lt = state.get_global("lt");
REQUIRE(std::dynamic_pointer_cast<BoolValue>(lt)->value == true); REQUIRE(std::dynamic_pointer_cast<BoolValue>(lt)->value == true);
} }
} }
TEST_CASE("VM - Lists", "[vm]") { TEST_CASE("VM - Lists", "[vm]") {
State state(ExecutionMode::VM); State state;
SECTION("Create list") { SECTION("Create list") {
REQUIRE(state.do_string("var numbers = [1, 2, 3, 4, 5];")); REQUIRE(state.do_string("var numbers = [1, 2, 3, 4, 5];"));
auto numbers = state.get_global("numbers"); auto numbers = state.get_global("numbers");
REQUIRE(numbers != nullptr); REQUIRE(numbers != nullptr);
REQUIRE(numbers->type() == Type::LIST); REQUIRE(numbers->type() == Type::LIST);
auto list = std::dynamic_pointer_cast<ListValue>(numbers); auto list = std::dynamic_pointer_cast<ListValue>(numbers);
REQUIRE(list->size() == 5); REQUIRE(list->size() == 5);
} }
SECTION("List indexing") { SECTION("List indexing") {
REQUIRE(state.do_string(R"( REQUIRE(state.do_string(R"(
var arr = [10, 20, 30]; var arr = [10, 20, 30];
var item = arr[1]; var item = arr[1];
)")); )"));
auto item = state.get_global("item"); auto item = state.get_global("item");
REQUIRE(std::dynamic_pointer_cast<NumberValue>(item)->value == 20.0); REQUIRE(std::dynamic_pointer_cast<NumberValue>(item)->value == 20.0);
} }
} }
TEST_CASE("VM - Maps", "[vm]") { TEST_CASE("VM - Maps", "[vm]") {
State state(ExecutionMode::VM); State state;
SECTION("Create map") { SECTION("Create map") {
REQUIRE(state.do_string(R"(var person = {"name": "Alice", "age": "30"};)")); REQUIRE(state.do_string(R"(var person = {"name": "Alice", "age": "30"};)"));
auto person = state.get_global("person"); auto person = state.get_global("person");
REQUIRE(person != nullptr); REQUIRE(person != nullptr);
REQUIRE(person->type() == Type::MAP); REQUIRE(person->type() == Type::MAP);
} }
SECTION("Map access") { SECTION("Map access") {
REQUIRE(state.do_string(R"( REQUIRE(state.do_string(R"(
var data = {"key": "value"}; var data = {"key": "value"};
var val = data["key"]; var val = data["key"];
)")); )"));
auto val = state.get_global("val"); auto val = state.get_global("val");
REQUIRE(val->type() == Type::STRING); REQUIRE(val->type() == Type::STRING);
REQUIRE(std::dynamic_pointer_cast<StringValue>(val)->value == "value"); REQUIRE(std::dynamic_pointer_cast<StringValue>(val)->value == "value");
} }
} }
TEST_CASE("VM - If statements", "[vm]") { TEST_CASE("VM - If statements", "[vm]") {
State state(ExecutionMode::VM); State state;
SECTION("If branch taken") { SECTION("If branch taken") {
REQUIRE(state.do_string(R"( REQUIRE(state.do_string(R"(
var x = 10; var x = 10;
if (x > 5) { if (x > 5) {
x = 100; x = 100;
} }
)")); )"));
auto x = state.get_global("x"); auto x = state.get_global("x");
REQUIRE(std::dynamic_pointer_cast<NumberValue>(x)->value == 100.0); REQUIRE(std::dynamic_pointer_cast<NumberValue>(x)->value == 100.0);
} }
SECTION("Else branch taken") { SECTION("Else branch taken") {
REQUIRE(state.do_string(R"( REQUIRE(state.do_string(R"(
var y = 3; var y = 3;
if (y > 5) { if (y > 5) {
y = 100; y = 100;
@@ -156,16 +157,16 @@ TEST_CASE("VM - If statements", "[vm]") {
y = 200; y = 200;
} }
)")); )"));
auto y = state.get_global("y"); auto y = state.get_global("y");
REQUIRE(std::dynamic_pointer_cast<NumberValue>(y)->value == 200.0); REQUIRE(std::dynamic_pointer_cast<NumberValue>(y)->value == 200.0);
} }
} }
TEST_CASE("VM - While loops", "[vm]") { TEST_CASE("VM - While loops", "[vm]") {
State state(ExecutionMode::VM); State state;
SECTION("While loop") { SECTION("While loop") {
REQUIRE(state.do_string(R"( REQUIRE(state.do_string(R"(
var counter = 0; var counter = 0;
var sum = 0; var sum = 0;
while (counter < 5) { while (counter < 5) {
@@ -173,97 +174,20 @@ TEST_CASE("VM - While loops", "[vm]") {
counter = counter + 1; counter = counter + 1;
} }
)")); )"));
auto sum = state.get_global("sum"); auto sum = state.get_global("sum");
REQUIRE(std::dynamic_pointer_cast<NumberValue>(sum)->value == 10.0); REQUIRE(std::dynamic_pointer_cast<NumberValue>(sum)->value == 10.0);
} }
} }
TEST_CASE("VM - Native functions", "[vm]") { TEST_CASE("VM - Native functions", "[vm]") {
State state(ExecutionMode::VM); State state;
SECTION("len function") { SECTION("len function") {
REQUIRE(state.do_string(R"( REQUIRE(state.do_string(R"(
var arr = [1, 2, 3, 4]; var arr = [1, 2, 3, 4];
var size = len(arr); var size = len(arr);
)")); )"));
auto size = state.get_global("size"); auto size = state.get_global("size");
REQUIRE(std::dynamic_pointer_cast<NumberValue>(size)->value == 4.0); REQUIRE(std::dynamic_pointer_cast<NumberValue>(size)->value == 4.0);
} }
}
TEST_CASE("VM vs Interpreter - Same results", "[vm][interpreter]") {
const char* script = R"(
var x = 10;
var y = 20;
var sum = x + y;
var product = x * y;
)";
State vm_state(ExecutionMode::VM);
State interp_state(ExecutionMode::INTERPRETER);
REQUIRE(vm_state.do_string(script));
REQUIRE(interp_state.do_string(script));
auto vm_sum = vm_state.get_global("sum");
auto interp_sum = interp_state.get_global("sum");
REQUIRE(std::dynamic_pointer_cast<NumberValue>(vm_sum)->value ==
std::dynamic_pointer_cast<NumberValue>(interp_sum)->value);
auto vm_product = vm_state.get_global("product");
auto interp_product = interp_state.get_global("product");
REQUIRE(std::dynamic_pointer_cast<NumberValue>(vm_product)->value ==
std::dynamic_pointer_cast<NumberValue>(interp_product)->value);
}
TEST_CASE("class init is called on declaration", "[vm][class][init]") {
State state(ExecutionMode::VM);
const char* script = R"(
class Person {
var age : number;
var name : string;
func init() -> nil {
age = 18;
name = "Default";
}
func getAge() -> number {
return this.age;
}
}
var p : Person;
var a = p.getAge();
)";
auto ret = state.do_string(script);
if(!ret) {
REQUIRE(state.get_error() == "");
}
auto p_val = state.get_global("p");
REQUIRE(p_val);
REQUIRE(p_val->type() == Type::INSTANCE);
auto instance = std::dynamic_pointer_cast<InstanceValue>(p_val);
REQUIRE(instance);
auto age_val = instance->get("age");
auto name_val = instance->get("name");
REQUIRE(age_val->type() == Type::NUMBER);
REQUIRE(name_val->type() == Type::STRING);
auto age_num = std::dynamic_pointer_cast<NumberValue>(age_val);
auto name_str = std::dynamic_pointer_cast<StringValue>(name_val);
REQUIRE(age_num->value == 18.0);
REQUIRE(name_str->value == "Default");
auto a_val = state.get_global("a");
REQUIRE(a_val);
REQUIRE(a_val->type() == Type::NUMBER);
auto a_num = std::dynamic_pointer_cast<NumberValue>(a_val);
REQUIRE(a_num->value == 18.0);
} }