commit 211a8374685840fb9bf39956c5deef94239a0a10 Author: zekexiao Date: Tue Jan 13 22:52:55 2026 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..09e5455 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Build directories +build/ +cmake-build-*/ + +# IDE directories +.idea/ +.vscode/ +.vs/ + +# Compiled files +*.o +*.a +*.so +*.dylib +*.exe + +# CMake files +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +Makefile + +# Generated files +*.log +*.out + +# macOS +.DS_Store + +# Backup files +*~ +*.bak +*.swp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..184c923 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,56 @@ +cmake_minimum_required(VERSION 3.30) +project(camellya) + +set(CMAKE_CXX_STANDARD 23) + +# Library sources +set(LIB_SOURCES + library.cpp + lexer.cpp + parser.cpp + value.cpp + interpreter.cpp + state.cpp +) + +set(LIB_HEADERS + library.h + lexer.h + parser.h + ast.h + value.h + interpreter.h + state.h +) + +# Build static library +add_library(camellya STATIC ${LIB_SOURCES} ${LIB_HEADERS}) + +include(FetchContent) + +FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.6.0 +) + +FetchContent_MakeAvailable(Catch2) + +enable_testing() + +add_executable(camellya_tests + tests/test_basic.cpp +) + +target_include_directories(camellya_tests + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_link_libraries(camellya_tests + PRIVATE + camellya + Catch2::Catch2WithMain +) + +add_test(NAME camellya_tests COMMAND camellya_tests) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..fc22979 --- /dev/null +++ b/README.md @@ -0,0 +1,217 @@ +# Camellya Script Language + +一个类似 Lua 的脚本语言,使用 C++23 实现,具有以下特性: + +## 特性 + +- **0-based 索引**:数组和列表从 0 开始索引 +- **明确的类型系统**:区分 list 和 map +- **类支持**:支持 class 定义,包含字段和方法 +- **静态类型声明**:变量需要类型声明(number, string, bool, list, map) +- **类 Lua 的 API**:提供简单的嵌入式 API + +## 语法示例 + +### 基本类型 + +```javascript +number x = 10; +string name = "Alice"; +bool flag = true; +``` + +### List(0-indexed) + +```javascript +list numbers = [10, 20, 30]; +print(numbers[0]); // 输出: 10 +numbers[1] = 99; +``` + +### Map + +```javascript +map person = {"name": "Bob", "age": "25"}; +print(person["name"]); // 输出: Bob +person["city"] = "New York"; +``` + +### 函数 + +```javascript +func add(number a, number b) -> number { + return a + b; +} + +number result = add(10, 20); +``` + +### 类 + +```javascript +class Person { + number age; + string name; + + func sayHi() -> string { + print(name, "says: I'm", age, "years old"); + return "Done"; + } +} + +Person p; +p.age = 10; +p.name = "Peter"; +p.sayHi(); +``` + +### 控制流 + +```javascript +// if-else +if (x > 10) { + print("x is greater than 10"); +} else { + print("x is less than or equal to 10"); +} + +// while loop +number i = 0; +while (i < 5) { + print("Count:", i); + i = i + 1; +} + +// for loop +for (number j = 0; j < 3; j = j + 1) { + print("For loop:", j); +} +``` + +## 编译和使用 + +### 编译 + +```bash +mkdir build +cd build +cmake .. +make +``` + +### 运行示例 + +```bash +./camellya_test +``` + +### 嵌入到 C++ 项目 + +```cpp +#include "library.h" +#include + +int main() { + camellya::State state; + + // 执行脚本字符串 + const char* script = R"( + class Person { + number age; + string name; + + func sayHi() -> string { + print(name, "says: I'm", age, "years old"); + return "Done"; + } + } + + Person p; + p.age = 10; + p.name = "Peter"; + p.sayHi(); + )"; + + if (!state.do_string(script)) { + std::cerr << "Error: " << state.get_error() << std::endl; + } + + // 或者从文件执行 + state.do_file("script.chun"); + + // 注册 C++ 函数 + state.register_function("my_func", + [](const std::vector& args) -> camellya::ValuePtr { + // 你的实现 + return std::make_shared(); + }); + + return 0; +} +``` + +## API 参考 + +### State 类 + +主要 API 类,类似于 `lua_State`: + +- `bool do_string(const std::string& script)` - 执行脚本字符串 +- `bool do_file(const std::string& filename)` - 执行脚本文件 +- `void register_function(const std::string& name, NativeFunction func)` - 注册 C++ 函数 +- `ValuePtr get_global(const std::string& name)` - 获取全局变量 +- `void set_global(const std::string& name, ValuePtr value)` - 设置全局变量 +- `const std::string& get_error()` - 获取最后的错误信息 + +### 栈操作(类似 Lua) + +- `void push_number(double value)` +- `void push_string(const std::string& value)` +- `void push_bool(bool value)` +- `void push_nil()` +- `double to_number(int index)` +- `std::string to_string(int index)` +- `bool to_bool(int index)` +- `int get_top()` +- `void pop(int n = 1)` + +## 内置函数 + +- `print(...)` - 打印多个参数到标准输出 +- `len(container)` - 返回 list、map 或 string 的长度 + +## 项目结构 + +``` +camellya/ +├── library.h # 主头文件 +├── library.cpp # 主实现 +├── lexer.h/cpp # 词法分析器 +├── parser.h/cpp # 语法分析器 +├── ast.h # 抽象语法树定义 +├── value.h/cpp # 值类型系统 +├── interpreter.h/cpp # 解释器 +├── state.h/cpp # 主 API 接口 +├── main.cpp # 示例程序 +├── example.cml # 示例脚本 +└── CMakeLists.txt # 构建配置 +``` + +## 特性对比 + +| 特性 | Lua | Camellya | +|------|-----|----------| +| 索引起始 | 1 | 0 | +| Table | 统一的 table | 区分 list 和 map | +| 类型 | 动态 | 静态声明 | +| 类 | 通过 metatable | 原生支持 | +| 语法 | `function` | `func` | +| 类型注解 | 无 | 必须声明 | + +## 许可证 + +MIT License + +## 版本 + +当前版本:0.1.0 diff --git a/ast.h b/ast.h new file mode 100644 index 0000000..81d9d49 --- /dev/null +++ b/ast.h @@ -0,0 +1,217 @@ +#ifndef CAMELLYA_AST_H +#define CAMELLYA_AST_H + +#include +#include +#include +#include + +namespace camellya { + +// Forward declarations +struct Expr; +struct Stmt; + +using ExprPtr = std::unique_ptr; +using StmtPtr = std::unique_ptr; + +// Value types +enum class ValueType { + NUMBER, STRING, BOOL, LIST, MAP, CLASS, NIL +}; + +// Expressions +struct Expr { + virtual ~Expr() = default; +}; + +struct BinaryExpr : public Expr { + ExprPtr left; + std::string op; + ExprPtr right; + + BinaryExpr(ExprPtr left, std::string op, ExprPtr right) + : left(std::move(left)), op(std::move(op)), right(std::move(right)) {} +}; + +struct UnaryExpr : public Expr { + std::string op; + ExprPtr operand; + + UnaryExpr(std::string op, ExprPtr operand) + : op(std::move(op)), operand(std::move(operand)) {} +}; + +struct LiteralExpr : public Expr { + std::variant value; + + explicit LiteralExpr(double value) : value(value) {} + explicit LiteralExpr(std::string value) : value(std::move(value)) {} + explicit LiteralExpr(bool value) : value(value) {} + LiteralExpr() : value(std::monostate{}) {} // nil +}; + +struct VariableExpr : public Expr { + std::string name; + + explicit VariableExpr(std::string name) : name(std::move(name)) {} +}; + +struct AssignExpr : public Expr { + std::string name; + ExprPtr value; + + AssignExpr(std::string name, ExprPtr value) + : name(std::move(name)), value(std::move(value)) {} +}; + +struct CallExpr : public Expr { + ExprPtr callee; + std::vector arguments; + + CallExpr(ExprPtr callee, std::vector arguments) + : callee(std::move(callee)), arguments(std::move(arguments)) {} +}; + +struct GetExpr : public Expr { + ExprPtr object; + std::string name; + + GetExpr(ExprPtr object, std::string name) + : object(std::move(object)), name(std::move(name)) {} +}; + +struct SetExpr : public Expr { + ExprPtr object; + std::string name; + ExprPtr value; + + SetExpr(ExprPtr object, std::string name, ExprPtr value) + : object(std::move(object)), name(std::move(name)), value(std::move(value)) {} +}; + +struct IndexExpr : public Expr { + ExprPtr object; + ExprPtr index; + + IndexExpr(ExprPtr object, ExprPtr index) + : object(std::move(object)), index(std::move(index)) {} +}; + +struct IndexSetExpr : public Expr { + ExprPtr object; + ExprPtr index; + ExprPtr value; + + IndexSetExpr(ExprPtr object, ExprPtr index, ExprPtr value) + : object(std::move(object)), index(std::move(index)), value(std::move(value)) {} +}; + +struct ListExpr : public Expr { + std::vector elements; + + explicit ListExpr(std::vector elements) + : elements(std::move(elements)) {} +}; + +struct MapExpr : public Expr { + std::vector> pairs; + + explicit MapExpr(std::vector> pairs) + : pairs(std::move(pairs)) {} +}; + +// Statements +struct Stmt { + virtual ~Stmt() = default; +}; + +struct ExprStmt : public Stmt { + ExprPtr expression; + + explicit ExprStmt(ExprPtr expression) : expression(std::move(expression)) {} +}; + +struct VarDecl : public Stmt { + std::string type_name; + std::string name; + ExprPtr initializer; + + VarDecl(std::string type_name, std::string name, ExprPtr initializer = nullptr) + : type_name(std::move(type_name)), name(std::move(name)), initializer(std::move(initializer)) {} +}; + +struct BlockStmt : public Stmt { + std::vector statements; + + explicit BlockStmt(std::vector statements) + : statements(std::move(statements)) {} +}; + +struct IfStmt : public Stmt { + ExprPtr condition; + StmtPtr then_branch; + StmtPtr else_branch; + + IfStmt(ExprPtr condition, StmtPtr then_branch, StmtPtr else_branch = nullptr) + : condition(std::move(condition)), then_branch(std::move(then_branch)), + else_branch(std::move(else_branch)) {} +}; + +struct WhileStmt : public Stmt { + ExprPtr condition; + StmtPtr body; + + WhileStmt(ExprPtr condition, StmtPtr body) + : condition(std::move(condition)), body(std::move(body)) {} +}; + +struct ForStmt : public Stmt { + StmtPtr initializer; + ExprPtr condition; + ExprPtr increment; + StmtPtr body; + + ForStmt(StmtPtr initializer, ExprPtr condition, ExprPtr increment, StmtPtr body) + : initializer(std::move(initializer)), condition(std::move(condition)), + increment(std::move(increment)), body(std::move(body)) {} +}; + +struct ReturnStmt : public Stmt { + ExprPtr value; + + explicit ReturnStmt(ExprPtr value = nullptr) : value(std::move(value)) {} +}; + +struct FunctionDecl : public Stmt { + std::string name; + std::vector> parameters; // (type, name) + std::string return_type; + std::shared_ptr body; + + FunctionDecl(std::string name, + std::vector> parameters, + std::string return_type, + std::shared_ptr body) + : name(std::move(name)), parameters(std::move(parameters)), + return_type(std::move(return_type)), body(std::move(body)) {} +}; + +struct ClassDecl : public Stmt { + std::string name; + std::vector members; // VarDecl and FunctionDecl + + ClassDecl(std::string name, std::vector members) + : name(std::move(name)), members(std::move(members)) {} +}; + +struct Program { + std::vector statements; + + explicit Program(std::vector statements) + : statements(std::move(statements)) {} +}; + +} // namespace camellya + +#endif // CAMELLYA_AST_H diff --git a/example.chun b/example.chun new file mode 100644 index 0000000..c79cef8 --- /dev/null +++ b/example.chun @@ -0,0 +1,52 @@ +// Example Camellya script +// This demonstrates the Person class from the specification + +class Person { + number age; + string name; + + func sayHi() -> string { + print(name, "says: I'm", age, "years old"); + return "Done"; + } + + func birthday() -> number { + age = age + 1; + print(name, "is now", age, "years old!"); + return age; + } +} + +// Create an instance +Person p; +p.age = 10; +p.name = "Peter"; + +// Call methods +p.sayHi(); +p.birthday(); +p.sayHi(); + +// Test lists (0-indexed) +print("\n=== List Demo ==="); +list numbers = [1, 2, 3, 4, 5]; +print("List:", numbers); +print("First element (index 0):", numbers[0]); +print("Third element (index 2):", numbers[2]); + +// Test maps +print("\n=== Map Demo ==="); +map config = {"host": "localhost", "port": "8080"}; +print("Config:", config); +print("Host:", config["host"]); + +// Functions +print("\n=== Function Demo ==="); +func fibonacci(number n) -> number { + if (n <= 1) { + return n; + } + return fibonacci(n - 1) + fibonacci(n - 2); +} + +print("Fibonacci(10) =", fibonacci(10)); diff --git a/interpreter.cpp b/interpreter.cpp new file mode 100644 index 0000000..0caad4c --- /dev/null +++ b/interpreter.cpp @@ -0,0 +1,524 @@ +#include "interpreter.h" +#include +#include +#include + +namespace camellya { + +Interpreter::Interpreter() { + global_environment = std::make_shared(); + environment = global_environment; + register_native_functions(); +} + +void Interpreter::register_native_functions() { + // print function - supports format strings + auto print_func = std::make_shared("print", + [](const std::vector& args) -> ValuePtr { + if (args.empty()) { + std::cout << std::endl; + return std::make_shared(); + } + + // Simple print: just concatenate all arguments + for (size_t i = 0; i < args.size(); ++i) { + if (i > 0) std::cout << " "; + std::cout << args[i]->to_string(); + } + std::cout << std::endl; + + return std::make_shared(); + }); + global_environment->define("print", print_func); + + // len function + auto len_func = std::make_shared("len", + [](const std::vector& args) -> ValuePtr { + if (args.size() != 1) { + throw RuntimeError("len() expects 1 argument."); + } + + auto& arg = args[0]; + if (arg->type() == Type::LIST) { + auto list = std::dynamic_pointer_cast(arg); + return std::make_shared(static_cast(list->size())); + } else if (arg->type() == Type::STRING) { + auto str = std::dynamic_pointer_cast(arg); + return std::make_shared(static_cast(str->value.length())); + } else if (arg->type() == Type::MAP) { + auto map = std::dynamic_pointer_cast(arg); + return std::make_shared(static_cast(map->pairs.size())); + } + + throw RuntimeError("len() expects list, string, or map."); + }); + global_environment->define("len", len_func); +} + +void Interpreter::execute(const Program& program) { + for (const auto& stmt : program.statements) { + execute_statement(*stmt); + } +} + +ValuePtr Interpreter::evaluate(const Expr& expr) { + if (auto* binary = dynamic_cast(&expr)) { + return eval_binary(*binary); + } else if (auto* unary = dynamic_cast(&expr)) { + return eval_unary(*unary); + } else if (auto* literal = dynamic_cast(&expr)) { + return eval_literal(*literal); + } else if (auto* variable = dynamic_cast(&expr)) { + return eval_variable(*variable); + } else if (auto* assign = dynamic_cast(&expr)) { + return eval_assign(*assign); + } else if (auto* call = dynamic_cast(&expr)) { + return eval_call(*call); + } else if (auto* get = dynamic_cast(&expr)) { + return eval_get(*get); + } else if (auto* set = dynamic_cast(&expr)) { + return eval_set(*set); + } else if (auto* index = dynamic_cast(&expr)) { + return eval_index(*index); + } else if (auto* index_set = dynamic_cast(&expr)) { + return eval_index_set(*index_set); + } else if (auto* list = dynamic_cast(&expr)) { + return eval_list(*list); + } else if (auto* map = dynamic_cast(&expr)) { + return eval_map(*map); + } + + throw RuntimeError("Unknown expression type."); +} + +void Interpreter::execute_statement(const Stmt& stmt) { + if (auto* expr_stmt = dynamic_cast(&stmt)) { + exec_expr_stmt(*expr_stmt); + } else if (auto* var_decl = dynamic_cast(&stmt)) { + exec_var_decl(*var_decl); + } else if (auto* block = dynamic_cast(&stmt)) { + exec_block(*block); + } else if (auto* if_stmt = dynamic_cast(&stmt)) { + exec_if(*if_stmt); + } else if (auto* while_stmt = dynamic_cast(&stmt)) { + exec_while(*while_stmt); + } else if (auto* for_stmt = dynamic_cast(&stmt)) { + exec_for(*for_stmt); + } else if (auto* return_stmt = dynamic_cast(&stmt)) { + exec_return(*return_stmt); + } else if (auto* func_decl = dynamic_cast(&stmt)) { + exec_function_decl(*func_decl); + } else if (auto* class_decl = dynamic_cast(&stmt)) { + exec_class_decl(*class_decl); + } +} + +ValuePtr Interpreter::eval_binary(const BinaryExpr& expr) { + ValuePtr left = evaluate(*expr.left); + ValuePtr right = evaluate(*expr.right); + + if (expr.op == "+") { + if (left->type() == Type::NUMBER && right->type() == Type::NUMBER) { + double l = std::dynamic_pointer_cast(left)->value; + double r = std::dynamic_pointer_cast(right)->value; + return std::make_shared(l + r); + } + + if (left->type() == Type::STRING && right->type() == Type::STRING) { + const auto& l = std::dynamic_pointer_cast(left)->value; + const auto& r = std::dynamic_pointer_cast(right)->value; + return std::make_shared(l + r); + } + + throw RuntimeError("Operands of '+' must be both numbers or both strings."); + } + + if (expr.op == "-" || expr.op == "*" || + expr.op == "/" || expr.op == "%") { + if (left->type() != Type::NUMBER || right->type() != Type::NUMBER) { + throw RuntimeError("Operands must be numbers."); + } + + double l = std::dynamic_pointer_cast(left)->value; + double r = std::dynamic_pointer_cast(right)->value; + + if (expr.op == "-") return std::make_shared(l - r); + if (expr.op == "*") return std::make_shared(l * r); + if (expr.op == "/") { + if (r == 0) throw RuntimeError("Division by zero."); + return std::make_shared(l / r); + } + if (expr.op == "%") return std::make_shared(std::fmod(l, r)); + } + + if (expr.op == "==" ) { + return std::make_shared(values_equal(left, right)); + } + if (expr.op == "!=") { + return std::make_shared(!values_equal(left, right)); + } + + if (expr.op == "<" || expr.op == "<=" || expr.op == ">" || expr.op == ">=") { + if (left->type() != Type::NUMBER || right->type() != Type::NUMBER) { + throw RuntimeError("Operands must be numbers."); + } + + double l = std::dynamic_pointer_cast(left)->value; + double r = std::dynamic_pointer_cast(right)->value; + + if (expr.op == "<") return std::make_shared(l < r); + if (expr.op == "<=") return std::make_shared(l <= r); + if (expr.op == ">") return std::make_shared(l > r); + if (expr.op == ">=") return std::make_shared(l >= r); + } + + if (expr.op == "and") { + if (!is_truthy(left)) return left; + return right; + } + + if (expr.op == "or") { + if (is_truthy(left)) return left; + return right; + } + + throw RuntimeError("Unknown binary operator: " + expr.op); +} + +ValuePtr Interpreter::eval_unary(const UnaryExpr& expr) { + ValuePtr operand = evaluate(*expr.operand); + + if (expr.op == "-") { + if (operand->type() != Type::NUMBER) { + throw RuntimeError("Operand must be a number."); + } + double value = std::dynamic_pointer_cast(operand)->value; + return std::make_shared(-value); + } + + if (expr.op == "!") { + return std::make_shared(!is_truthy(operand)); + } + + throw RuntimeError("Unknown unary operator: " + expr.op); +} + +ValuePtr Interpreter::eval_literal(const LiteralExpr& expr) { + return std::visit([](auto&& arg) -> ValuePtr { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return std::make_shared(arg); + } else if constexpr (std::is_same_v) { + return std::make_shared(arg); + } else if constexpr (std::is_same_v) { + return std::make_shared(arg); + } else { + return std::make_shared(); + } + }, expr.value); +} + +ValuePtr Interpreter::eval_variable(const VariableExpr& expr) { + return environment->get(expr.name); +} + +ValuePtr Interpreter::eval_assign(const AssignExpr& expr) { + ValuePtr value = evaluate(*expr.value); + environment->set(expr.name, value); + return value; +} + +ValuePtr Interpreter::eval_call(const CallExpr& expr) { + ValuePtr callee = evaluate(*expr.callee); + + std::vector arguments; + for (const auto& arg : expr.arguments) { + arguments.push_back(evaluate(*arg)); + } + + if (callee->type() == Type::FUNCTION) { + auto func = std::dynamic_pointer_cast(callee); + return call_function(*func, arguments); + } else if (callee->type() == Type::CLASS) { + // Class instantiation + auto klass = std::dynamic_pointer_cast(callee); + auto instance = std::make_shared(klass); + + // If there is an init method, call it like a constructor + ValuePtr init_val = instance->get("init"); + if (init_val && init_val->type() == Type::FUNCTION) { + auto init_func = std::dynamic_pointer_cast(init_val); + call_function(*init_func, arguments); + } + + return instance; + } + + throw RuntimeError("Can only call functions and classes."); +} + +ValuePtr Interpreter::eval_get(const GetExpr& expr) { + ValuePtr object = evaluate(*expr.object); + + if (object->type() == Type::INSTANCE) { + auto instance = std::dynamic_pointer_cast(object); + return instance->get(expr.name); + } + + throw RuntimeError("Only instances have properties."); +} + +ValuePtr Interpreter::eval_set(const SetExpr& expr) { + ValuePtr object = evaluate(*expr.object); + + if (object->type() == Type::INSTANCE) { + auto instance = std::dynamic_pointer_cast(object); + ValuePtr value = evaluate(*expr.value); + instance->set(expr.name, value); + return value; + } + + throw RuntimeError("Only instances have fields."); +} + +ValuePtr Interpreter::eval_index(const IndexExpr& expr) { + ValuePtr object = evaluate(*expr.object); + ValuePtr index = evaluate(*expr.index); + + if (object->type() == Type::LIST) { + auto list = std::dynamic_pointer_cast(object); + if (index->type() != Type::NUMBER) { + throw RuntimeError("List index must be a number."); + } + size_t idx = static_cast(std::dynamic_pointer_cast(index)->value); + return list->get(idx); + } else if (object->type() == Type::MAP) { + auto map = std::dynamic_pointer_cast(object); + if (index->type() != Type::STRING) { + throw RuntimeError("Map key must be a string."); + } + std::string key = std::dynamic_pointer_cast(index)->value; + return map->get(key); + } + + throw RuntimeError("Only lists and maps support indexing."); +} + +ValuePtr Interpreter::eval_index_set(const IndexSetExpr& expr) { + ValuePtr object = evaluate(*expr.object); + ValuePtr index = evaluate(*expr.index); + ValuePtr value = evaluate(*expr.value); + + if (object->type() == Type::LIST) { + auto list = std::dynamic_pointer_cast(object); + if (index->type() != Type::NUMBER) { + throw RuntimeError("List index must be a number."); + } + size_t idx = static_cast(std::dynamic_pointer_cast(index)->value); + list->set(idx, value); + return value; + } else if (object->type() == Type::MAP) { + auto map = std::dynamic_pointer_cast(object); + if (index->type() != Type::STRING) { + throw RuntimeError("Map key must be a string."); + } + std::string key = std::dynamic_pointer_cast(index)->value; + map->set(key, value); + return value; + } + + throw RuntimeError("Only lists and maps support index assignment."); +} + +ValuePtr Interpreter::eval_list(const ListExpr& expr) { + auto list = std::make_shared(); + for (const auto& elem : expr.elements) { + list->push(evaluate(*elem)); + } + return list; +} + +ValuePtr Interpreter::eval_map(const MapExpr& expr) { + auto map = std::make_shared(); + for (const auto& [key_expr, value_expr] : expr.pairs) { + ValuePtr key = evaluate(*key_expr); + ValuePtr value = evaluate(*value_expr); + + if (key->type() != Type::STRING) { + throw RuntimeError("Map keys must be strings."); + } + + std::string key_str = std::dynamic_pointer_cast(key)->value; + map->set(key_str, value); + } + return map; +} + +void Interpreter::exec_expr_stmt(const ExprStmt& stmt) { + evaluate(*stmt.expression); +} + +void Interpreter::exec_var_decl(const VarDecl& stmt) { + ValuePtr value; + + if (stmt.initializer) { + value = evaluate(*stmt.initializer); + } else { + value = create_default_value(stmt.type_name); + } + + environment->define(stmt.name, value); +} + +void Interpreter::exec_block(const BlockStmt& stmt) { + auto previous = environment; + environment = std::make_shared(environment); + + try { + for (const auto& statement : stmt.statements) { + execute_statement(*statement); + } + } catch (...) { + environment = previous; + throw; + } + + environment = previous; +} + +void Interpreter::exec_if(const IfStmt& stmt) { + ValuePtr condition = evaluate(*stmt.condition); + + if (is_truthy(condition)) { + execute_statement(*stmt.then_branch); + } else if (stmt.else_branch) { + execute_statement(*stmt.else_branch); + } +} + +void Interpreter::exec_while(const WhileStmt& stmt) { + while (is_truthy(evaluate(*stmt.condition))) { + execute_statement(*stmt.body); + } +} + +void Interpreter::exec_for(const ForStmt& stmt) { + auto previous = environment; + environment = std::make_shared(environment); + + try { + if (stmt.initializer) { + execute_statement(*stmt.initializer); + } + + while (!stmt.condition || is_truthy(evaluate(*stmt.condition))) { + execute_statement(*stmt.body); + + if (stmt.increment) { + evaluate(*stmt.increment); + } + } + } catch (...) { + environment = previous; + throw; + } + + environment = previous; +} + +void Interpreter::exec_return(const ReturnStmt& stmt) { + ValuePtr value = stmt.value ? evaluate(*stmt.value) : std::make_shared(); + throw ReturnException(value); +} + +void Interpreter::exec_function_decl(const FunctionDecl& stmt) { + auto func_decl = std::make_shared(stmt); + auto func = std::make_shared(stmt.name, func_decl); + environment->define(stmt.name, func); +} + +void Interpreter::exec_class_decl(const ClassDecl& stmt) { + auto klass = std::make_shared(stmt.name); + + for (const auto& member : stmt.members) { + if (auto* var_decl = dynamic_cast(member.get())) { + klass->add_field(var_decl->name, var_decl->type_name); + } else if (auto* func_decl = dynamic_cast(member.get())) { + auto func_decl_ptr = std::make_shared(*func_decl); + auto func = std::make_shared(func_decl->name, func_decl_ptr); + klass->add_method(func_decl->name, func); + } + } + + environment->define(stmt.name, klass); +} + +ValuePtr Interpreter::call_function(const FunctionValue& func, const std::vector& arguments) { + if (func.is_native) { + return func.native_func(arguments); + } + + // Bind parameters + if (func.declaration->parameters.size() != arguments.size()) { + throw RuntimeError(std::format("Expected {} arguments but got {}.", + func.declaration->parameters.size(), + arguments.size())); + } + + auto previous = environment; + environment = std::make_shared(global_environment); + + // Bind 'this' for methods + if (func.bound_instance) { + environment->define("this", func.bound_instance); + } + + // Bind parameters to the new environment + for (size_t i = 0; i < arguments.size(); ++i) { + environment->define(func.declaration->parameters[i].second, arguments[i]); + } + + try { + execute_statement(*func.declaration->body); + } catch (const ReturnException& ret) { + environment = previous; + return ret.value; + } + + environment = previous; + return std::make_shared(); +} + +ValuePtr Interpreter::create_default_value(const std::string& type_name) { + if (type_name == "number") { + return std::make_shared(0.0); + } else if (type_name == "string") { + return std::make_shared(""); + } else if (type_name == "bool") { + return std::make_shared(false); + } else if (type_name == "list") { + return std::make_shared(); + } else if (type_name == "map") { + return std::make_shared(); + } else if (environment->has(type_name)) { + // It's a class type + ValuePtr klass = environment->get(type_name); + if (klass->type() == Type::CLASS) { + auto klass_ptr = std::dynamic_pointer_cast(klass); + auto instance = std::make_shared(klass_ptr); + + // If the class defines init(), call it with no arguments + ValuePtr init_val = instance->get("init"); + if (init_val && init_val->type() == Type::FUNCTION) { + auto init_func = std::dynamic_pointer_cast(init_val); + call_function(*init_func, {}); + } + + return instance; + } + } + + return std::make_shared(); +} + +} // namespace camellya diff --git a/interpreter.h b/interpreter.h new file mode 100644 index 0000000..35beaaf --- /dev/null +++ b/interpreter.h @@ -0,0 +1,151 @@ +#ifndef CAMELLYA_INTERPRETER_H +#define CAMELLYA_INTERPRETER_H + +#include "ast.h" +#include "value.h" +#include +#include +#include + +namespace camellya { + +class RuntimeError : public std::runtime_error { +public: + explicit RuntimeError(const std::string& message) : std::runtime_error(message) {} +}; + +class ReturnException : public std::exception { +public: + ValuePtr value; + explicit ReturnException(ValuePtr value) : value(std::move(value)) {} +}; + +class Environment { +public: + std::shared_ptr parent; + std::map values; + + Environment() : parent(nullptr) {} + explicit Environment(std::shared_ptr parent) : parent(std::move(parent)) {} + + void define(const std::string& name, ValuePtr value) { + values[name] = std::move(value); + } + + ValuePtr get(const std::string& name) const { + auto it = values.find(name); + if (it != values.end()) { + return it->second; + } + + if (parent && parent->has(name)) { + return parent->get(name); + } + + // Fallback: resolve as field on 'this' instance if present + const Environment* env = this; + while (env) { + auto this_it = env->values.find("this"); + if (this_it != env->values.end() && + this_it->second && this_it->second->type() == Type::INSTANCE) { + auto instance = std::dynamic_pointer_cast(this_it->second); + auto field_it = instance->fields.find(name); + if (field_it != instance->fields.end()) { + return field_it->second; + } + break; + } + env = env->parent.get(); + } + + throw RuntimeError("Undefined variable '" + name + "'."); + } + + void set(const std::string& name, ValuePtr value) { + auto it = values.find(name); + if (it != values.end()) { + it->second = std::move(value); + return; + } + + if (parent && parent->has(name)) { + parent->set(name, std::move(value)); + return; + } + + // Fallback: assign to field on 'this' instance if present + Environment* env = this; + while (env) { + auto this_it = env->values.find("this"); + if (this_it != env->values.end() && + this_it->second && this_it->second->type() == Type::INSTANCE) { + auto instance = std::dynamic_pointer_cast(this_it->second); + auto field_it = instance->fields.find(name); + if (field_it != instance->fields.end()) { + field_it->second = std::move(value); + return; + } + break; + } + env = env->parent.get(); + } + + throw RuntimeError("Undefined variable '" + name + "'."); + } + + bool has(const std::string& name) const { + if (values.find(name) != values.end()) { + return true; + } + if (parent) { + return parent->has(name); + } + return false; + } +}; + +class Interpreter { +public: + Interpreter(); + + void execute(const Program& program); + ValuePtr evaluate(const Expr& expr); + void execute_statement(const Stmt& stmt); + + std::shared_ptr environment; + std::shared_ptr global_environment; + +private: + void register_native_functions(); + + ValuePtr eval_binary(const BinaryExpr& expr); + ValuePtr eval_unary(const UnaryExpr& expr); + ValuePtr eval_literal(const LiteralExpr& expr); + ValuePtr eval_variable(const VariableExpr& expr); + ValuePtr eval_assign(const AssignExpr& expr); + ValuePtr eval_call(const CallExpr& expr); + ValuePtr eval_get(const GetExpr& expr); + ValuePtr eval_set(const SetExpr& expr); + ValuePtr eval_index(const IndexExpr& expr); + ValuePtr eval_index_set(const IndexSetExpr& expr); + ValuePtr eval_list(const ListExpr& expr); + ValuePtr eval_map(const MapExpr& expr); + + void exec_expr_stmt(const ExprStmt& stmt); + void exec_var_decl(const VarDecl& stmt); + void exec_block(const BlockStmt& stmt); + void exec_if(const IfStmt& stmt); + void exec_while(const WhileStmt& stmt); + void exec_for(const ForStmt& stmt); + void exec_return(const ReturnStmt& stmt); + void exec_function_decl(const FunctionDecl& stmt); + void exec_class_decl(const ClassDecl& stmt); + + ValuePtr call_function(const FunctionValue& func, const std::vector& arguments); + + ValuePtr create_default_value(const std::string& type_name); +}; + +} // namespace camellya + +#endif // CAMELLYA_INTERPRETER_H diff --git a/lexer.cpp b/lexer.cpp new file mode 100644 index 0000000..4acf369 --- /dev/null +++ b/lexer.cpp @@ -0,0 +1,235 @@ +#include "lexer.h" +#include +#include + +namespace camellya { + +Lexer::Lexer(std::string source) : source_(std::move(source)) {} + +std::vector Lexer::tokenize() { + while (!is_at_end()) { + start_ = current_; + scan_token(); + } + + tokens_.emplace_back(TokenType::END_OF_FILE, "", line_, column_); + return tokens_; +} + +char Lexer::advance() { + column_++; + return source_[current_++]; +} + +char Lexer::peek() const { + if (is_at_end()) return '\0'; + return source_[current_]; +} + +char Lexer::peek_next() const { + if (current_ + 1 >= source_.length()) return '\0'; + return source_[current_ + 1]; +} + +bool Lexer::match(char expected) { + if (is_at_end()) return false; + if (source_[current_] != expected) return false; + + current_++; + column_++; + return true; +} + +void Lexer::skip_whitespace() { + while (!is_at_end()) { + char c = peek(); + switch (c) { + case ' ': + case '\r': + case '\t': + advance(); + break; + case '\n': + line_++; + column_ = 0; + advance(); + break; + default: + return; + } + } +} + +void Lexer::skip_comment() { + if (peek() == '/' && peek_next() == '/') { + while (peek() != '\n' && !is_at_end()) { + advance(); + } + } +} + +void Lexer::scan_token() { + skip_whitespace(); + + if (is_at_end()) return; + + start_ = current_; + int start_column = column_; + char c = advance(); + + switch (c) { + case '(': add_token(TokenType::LEFT_PAREN); break; + case ')': add_token(TokenType::RIGHT_PAREN); break; + case '{': add_token(TokenType::LEFT_BRACE); break; + case '}': add_token(TokenType::RIGHT_BRACE); break; + case '[': add_token(TokenType::LEFT_BRACKET); break; + case ']': add_token(TokenType::RIGHT_BRACKET); break; + case ',': add_token(TokenType::COMMA); break; + case '.': add_token(TokenType::DOT); break; + case ';': add_token(TokenType::SEMICOLON); break; + case ':': add_token(TokenType::COLON); break; + case '+': add_token(TokenType::PLUS); break; + case '*': add_token(TokenType::STAR); break; + case '%': add_token(TokenType::PERCENT); break; + case '-': + if (match('>')) { + add_token(TokenType::ARROW); + } else { + add_token(TokenType::MINUS); + } + break; + case '!': + add_token(match('=') ? TokenType::BANG_EQUAL : TokenType::BANG); + break; + case '=': + add_token(match('=') ? TokenType::EQUAL_EQUAL : TokenType::EQUAL); + break; + case '<': + add_token(match('=') ? TokenType::LESS_EQUAL : TokenType::LESS); + break; + case '>': + add_token(match('=') ? TokenType::GREATER_EQUAL : TokenType::GREATER); + break; + case '/': + if (peek() == '/') { + skip_comment(); + } else { + add_token(TokenType::SLASH); + } + break; + case '"': + scan_string(); + break; + default: + if (std::isdigit(c)) { + scan_number(); + } else if (std::isalpha(c) || c == '_') { + scan_identifier(); + } else { + add_token(TokenType::INVALID); + } + break; + } +} + +void Lexer::add_token(TokenType type) { + std::string text = source_.substr(start_, current_ - start_); + tokens_.emplace_back(type, text, line_, column_ - static_cast(text.length())); +} + +void Lexer::add_token(TokenType type, std::variant literal) { + std::string text = source_.substr(start_, current_ - start_); + tokens_.emplace_back(type, text, literal, line_, column_ - static_cast(text.length())); +} + +void Lexer::scan_string() { + std::string value; + + while (peek() != '"' && !is_at_end()) { + if (peek() == '\n') { + line_++; + column_ = 0; + } + if (peek() == '\\' && peek_next() != '\0') { + advance(); // consume backslash + char escaped = advance(); + switch (escaped) { + case 'n': value += '\n'; break; + case 't': value += '\t'; break; + case 'r': value += '\r'; break; + case '\\': value += '\\'; break; + case '"': value += '"'; break; + default: value += escaped; break; + } + } else { + value += advance(); + } + } + + if (is_at_end()) { + add_token(TokenType::INVALID); + return; + } + + advance(); // closing " + add_token(TokenType::STRING_LITERAL, value); +} + +void Lexer::scan_number() { + while (std::isdigit(peek())) { + advance(); + } + + if (peek() == '.' && std::isdigit(peek_next())) { + advance(); // consume '.' + while (std::isdigit(peek())) { + advance(); + } + } + + std::string text = source_.substr(start_, current_ - start_); + double value = std::stod(text); + add_token(TokenType::NUMBER_LITERAL, value); +} + +void Lexer::scan_identifier() { + while (std::isalnum(peek()) || peek() == '_') { + advance(); + } + + std::string text = source_.substr(start_, current_ - start_); + TokenType type = get_keyword_type(text); + add_token(type); +} + +TokenType Lexer::get_keyword_type(const std::string& text) const { + static const std::unordered_map keywords = { + {"class", TokenType::CLASS}, + {"func", TokenType::FUNC}, + {"number", TokenType::NUMBER}, + {"string", TokenType::STRING}, + {"bool", TokenType::BOOL}, + {"list", TokenType::LIST}, + {"map", TokenType::MAP}, + {"if", TokenType::IF}, + {"else", TokenType::ELSE}, + {"while", TokenType::WHILE}, + {"for", TokenType::FOR}, + {"return", TokenType::RETURN}, + {"var", TokenType::VAR}, + {"true", TokenType::TRUE}, + {"false", TokenType::FALSE}, + {"nil", TokenType::NIL}, + {"and", TokenType::AND}, + {"or", TokenType::OR}, + {"this", TokenType::THIS}, + }; + + auto it = keywords.find(text); + if (it != keywords.end()) { + return it->second; + } + return TokenType::IDENTIFIER; +} + +} // namespace camellya diff --git a/lexer.h b/lexer.h new file mode 100644 index 0000000..9fa8811 --- /dev/null +++ b/lexer.h @@ -0,0 +1,88 @@ +#ifndef CAMELLYA_LEXER_H +#define CAMELLYA_LEXER_H + +#include +#include +#include +#include + +namespace camellya { + +enum class TokenType { + // Keywords + CLASS, FUNC, NUMBER, STRING, BOOL, LIST, MAP, + IF, ELSE, WHILE, FOR, RETURN, VAR, + TRUE, FALSE, NIL, THIS, + + // Operators + PLUS, MINUS, STAR, SLASH, PERCENT, + EQUAL, EQUAL_EQUAL, BANG_EQUAL, + LESS, LESS_EQUAL, GREATER, GREATER_EQUAL, + AND, OR, BANG, + + // Delimiters + LEFT_PAREN, RIGHT_PAREN, + LEFT_BRACE, RIGHT_BRACE, + LEFT_BRACKET, RIGHT_BRACKET, + COMMA, DOT, SEMICOLON, COLON, + ARROW, + + // Literals + IDENTIFIER, NUMBER_LITERAL, STRING_LITERAL, + + // Special + END_OF_FILE, INVALID +}; + +struct Token { + TokenType type; + std::string lexeme; + std::variant literal; + int line; + int column; + + Token(TokenType type, std::string lexeme, int line, int column) + : type(type), lexeme(std::move(lexeme)), line(line), column(column) {} + + template + Token(TokenType type, std::string lexeme, T literal, int line, int column) + : type(type), lexeme(std::move(lexeme)), literal(std::move(literal)), line(line), column(column) {} +}; + +class Lexer { +public: + explicit Lexer(std::string source); + + std::vector tokenize(); + +private: + std::string source_; + size_t start_ = 0; + size_t current_ = 0; + int line_ = 1; + int column_ = 1; + std::vector tokens_; + + bool is_at_end() const { return current_ >= source_.length(); } + char advance(); + char peek() const; + char peek_next() const; + bool match(char expected); + + void skip_whitespace(); + void skip_comment(); + + void scan_token(); + void add_token(TokenType type); + void add_token(TokenType type, std::variant literal); + + void scan_string(); + void scan_number(); + void scan_identifier(); + + TokenType get_keyword_type(const std::string& text) const; +}; + +} // namespace camellya + +#endif // CAMELLYA_LEXER_H diff --git a/library.cpp b/library.cpp new file mode 100644 index 0000000..ec9afd2 --- /dev/null +++ b/library.cpp @@ -0,0 +1,4 @@ +#include "library.h" + +// Implementation file for Camellya scripting language +// All implementation is in separate source files \ No newline at end of file diff --git a/library.h b/library.h new file mode 100644 index 0000000..58e3f2a --- /dev/null +++ b/library.h @@ -0,0 +1,23 @@ +#ifndef CAMELLYA_LIBRARY_H +#define CAMELLYA_LIBRARY_H + +// Camellya Scripting Language +// A Lua-like scripting language with 0-based indexing, +// distinct list/map types, and class support + +#include "state.h" +#include "value.h" +#include "lexer.h" +#include "parser.h" +#include "interpreter.h" +#include "ast.h" + +namespace camellya { + +// Version info +constexpr const char* VERSION = "0.1.0"; +constexpr const char* VERSION_NAME = "Camellya"; + +} // namespace camellya + +#endif // CAMELLYA_LIBRARY_H \ No newline at end of file diff --git a/parser.cpp b/parser.cpp new file mode 100644 index 0000000..544f164 --- /dev/null +++ b/parser.cpp @@ -0,0 +1,482 @@ +#include "parser.h" +#include + +namespace camellya { + +Parser::Parser(std::vector tokens) : tokens_(std::move(tokens)) {} + +Program Parser::parse() { + std::vector statements; + + while (!is_at_end()) { + try { + statements.push_back(declaration()); + } catch (const ParseError& error) { + synchronize(); + } + } + + return Program(std::move(statements)); +} + +Token Parser::peek() const { + return tokens_[current_]; +} + +Token Parser::previous() const { + return tokens_[current_ - 1]; +} + +bool Parser::is_at_end() const { + return peek().type == TokenType::END_OF_FILE; +} + +Token Parser::advance() { + if (!is_at_end()) current_++; + return previous(); +} + +bool Parser::check(TokenType type) const { + if (is_at_end()) return false; + return peek().type == type; +} + +bool Parser::match(std::initializer_list types) { + for (TokenType type : types) { + if (check(type)) { + advance(); + return true; + } + } + return false; +} + +Token Parser::consume(TokenType type, const std::string& message) { + if (check(type)) return advance(); + throw error(peek(), message); +} + +ParseError Parser::error(const Token& token, const std::string& message) { + std::string error_msg = std::format("Line {}: Error at '{}': {}", + token.line, token.lexeme, message); + return ParseError(error_msg); +} + +void Parser::synchronize() { + advance(); + + while (!is_at_end()) { + if (previous().type == TokenType::SEMICOLON) return; + + switch (peek().type) { + case TokenType::CLASS: + case TokenType::FUNC: + case TokenType::VAR: + case TokenType::FOR: + case TokenType::IF: + case TokenType::WHILE: + case TokenType::RETURN: + return; + default: + break; + } + + advance(); + } +} + +StmtPtr Parser::declaration() { + if (match({TokenType::CLASS})) { + return class_declaration(); + } + if (match({TokenType::FUNC})) { + return function_declaration(); + } + if (check(TokenType::NUMBER) || check(TokenType::STRING) || + check(TokenType::BOOL) || check(TokenType::LIST) || + check(TokenType::MAP) || check(TokenType::IDENTIFIER)) { + // Type name followed by identifier + Token lookahead = tokens_[current_]; + if (current_ + 1 < tokens_.size() && tokens_[current_ + 1].type == TokenType::IDENTIFIER) { + return var_declaration(); + } + } + + return statement(); +} + +StmtPtr Parser::class_declaration() { + Token name = consume(TokenType::IDENTIFIER, "Expected class name."); + consume(TokenType::LEFT_BRACE, "Expected '{' before class body."); + + std::vector members; + + while (!check(TokenType::RIGHT_BRACE) && !is_at_end()) { + // Parse field or method + if (check(TokenType::FUNC)) { + advance(); + members.push_back(function_declaration()); + } else { + members.push_back(var_declaration()); + consume(TokenType::SEMICOLON, "Expected ';' after field declaration."); + } + } + + consume(TokenType::RIGHT_BRACE, "Expected '}' after class body."); + + return std::make_unique(name.lexeme, std::move(members)); +} + +StmtPtr Parser::function_declaration() { + Token name = consume(TokenType::IDENTIFIER, "Expected function name."); + consume(TokenType::LEFT_PAREN, "Expected '(' after function name."); + + std::vector> parameters; + + if (!check(TokenType::RIGHT_PAREN)) { + do { + Token type_token = advance(); + std::string type_name = type_token.lexeme; + Token param_name = consume(TokenType::IDENTIFIER, "Expected parameter name."); + parameters.emplace_back(type_name, param_name.lexeme); + } while (match({TokenType::COMMA})); + } + + consume(TokenType::RIGHT_PAREN, "Expected ')' after parameters."); + + std::string return_type = "nil"; + if (match({TokenType::ARROW})) { + Token type_token = advance(); + return_type = type_token.lexeme; + } + + consume(TokenType::LEFT_BRACE, "Expected '{' before function body."); + StmtPtr body = block_statement(); + + auto body_block = std::shared_ptr(static_cast(body.release())); + + return std::make_unique(name.lexeme, std::move(parameters), + return_type, std::move(body_block)); +} + +StmtPtr Parser::var_declaration() { + Token type_token = advance(); + std::string type_name = type_token.lexeme; + Token name = consume(TokenType::IDENTIFIER, "Expected variable name."); + + ExprPtr initializer = nullptr; + if (match({TokenType::EQUAL})) { + initializer = expression(); + } + + return std::make_unique(type_name, name.lexeme, std::move(initializer)); +} + +StmtPtr Parser::statement() { + if (match({TokenType::IF})) return if_statement(); + if (match({TokenType::WHILE})) return while_statement(); + if (match({TokenType::FOR})) return for_statement(); + if (match({TokenType::RETURN})) return return_statement(); + if (match({TokenType::LEFT_BRACE})) return block_statement(); + + return expression_statement(); +} + +StmtPtr Parser::if_statement() { + consume(TokenType::LEFT_PAREN, "Expected '(' after 'if'."); + ExprPtr condition = expression(); + consume(TokenType::RIGHT_PAREN, "Expected ')' after if condition."); + + StmtPtr then_branch = statement(); + StmtPtr else_branch = nullptr; + + if (match({TokenType::ELSE})) { + else_branch = statement(); + } + + return std::make_unique(std::move(condition), std::move(then_branch), + std::move(else_branch)); +} + +StmtPtr Parser::while_statement() { + consume(TokenType::LEFT_PAREN, "Expected '(' after 'while'."); + ExprPtr condition = expression(); + consume(TokenType::RIGHT_PAREN, "Expected ')' after while condition."); + + StmtPtr body = statement(); + + return std::make_unique(std::move(condition), std::move(body)); +} + +StmtPtr Parser::for_statement() { + consume(TokenType::LEFT_PAREN, "Expected '(' after 'for'."); + + StmtPtr initializer = nullptr; + if (!match({TokenType::SEMICOLON})) { + initializer = declaration(); + consume(TokenType::SEMICOLON, "Expected ';' after for initializer."); + } + + ExprPtr condition = nullptr; + if (!check(TokenType::SEMICOLON)) { + condition = expression(); + } + consume(TokenType::SEMICOLON, "Expected ';' after for condition."); + + ExprPtr increment = nullptr; + if (!check(TokenType::RIGHT_PAREN)) { + increment = expression(); + } + consume(TokenType::RIGHT_PAREN, "Expected ')' after for clauses."); + + StmtPtr body = statement(); + + return std::make_unique(std::move(initializer), std::move(condition), + std::move(increment), std::move(body)); +} + +StmtPtr Parser::return_statement() { + ExprPtr value = nullptr; + + if (!check(TokenType::SEMICOLON)) { + value = expression(); + } + + consume(TokenType::SEMICOLON, "Expected ';' after return value."); + return std::make_unique(std::move(value)); +} + +StmtPtr Parser::block_statement() { + std::vector statements; + + while (!check(TokenType::RIGHT_BRACE) && !is_at_end()) { + auto stmt = declaration(); + // If declaration returned a VarDecl (not a class/function/statement), consume semicolon + if (dynamic_cast(stmt.get())) { + consume(TokenType::SEMICOLON, "Expected ';' after variable declaration."); + } + statements.push_back(std::move(stmt)); + } + + consume(TokenType::RIGHT_BRACE, "Expected '}' after block."); + return std::make_unique(std::move(statements)); +} + +StmtPtr Parser::expression_statement() { + ExprPtr expr = expression(); + consume(TokenType::SEMICOLON, "Expected ';' after expression."); + return std::make_unique(std::move(expr)); +} + +ExprPtr Parser::expression() { + return assignment(); +} + +ExprPtr Parser::assignment() { + ExprPtr expr = logical_or(); + + if (match({TokenType::EQUAL})) { + Token equals = previous(); + ExprPtr value = assignment(); + + if (auto* var_expr = dynamic_cast(expr.get())) { + return std::make_unique(var_expr->name, std::move(value)); + } else if (auto* get_expr = dynamic_cast(expr.get())) { + return std::make_unique(std::move(get_expr->object), + get_expr->name, std::move(value)); + } else if (auto* index_expr = dynamic_cast(expr.get())) { + return std::make_unique(std::move(index_expr->object), + std::move(index_expr->index), + std::move(value)); + } + + throw error(equals, "Invalid assignment target."); + } + + return expr; +} + +ExprPtr Parser::logical_or() { + ExprPtr expr = logical_and(); + + while (match({TokenType::OR})) { + Token op = previous(); + ExprPtr right = logical_and(); + expr = std::make_unique(std::move(expr), op.lexeme, std::move(right)); + } + + return expr; +} + +ExprPtr Parser::logical_and() { + ExprPtr expr = equality(); + + while (match({TokenType::AND})) { + Token op = previous(); + ExprPtr right = equality(); + expr = std::make_unique(std::move(expr), op.lexeme, std::move(right)); + } + + return expr; +} + +ExprPtr Parser::equality() { + ExprPtr expr = comparison(); + + while (match({TokenType::EQUAL_EQUAL, TokenType::BANG_EQUAL})) { + Token op = previous(); + ExprPtr right = comparison(); + expr = std::make_unique(std::move(expr), op.lexeme, std::move(right)); + } + + return expr; +} + +ExprPtr Parser::comparison() { + ExprPtr expr = term(); + + while (match({TokenType::GREATER, TokenType::GREATER_EQUAL, + TokenType::LESS, TokenType::LESS_EQUAL})) { + Token op = previous(); + ExprPtr right = term(); + expr = std::make_unique(std::move(expr), op.lexeme, std::move(right)); + } + + return expr; +} + +ExprPtr Parser::term() { + ExprPtr expr = factor(); + + while (match({TokenType::MINUS, TokenType::PLUS})) { + Token op = previous(); + ExprPtr right = factor(); + expr = std::make_unique(std::move(expr), op.lexeme, std::move(right)); + } + + return expr; +} + +ExprPtr Parser::factor() { + ExprPtr expr = unary(); + + while (match({TokenType::SLASH, TokenType::STAR, TokenType::PERCENT})) { + Token op = previous(); + ExprPtr right = unary(); + expr = std::make_unique(std::move(expr), op.lexeme, std::move(right)); + } + + return expr; +} + +ExprPtr Parser::unary() { + if (match({TokenType::BANG, TokenType::MINUS})) { + Token op = previous(); + ExprPtr right = unary(); + return std::make_unique(op.lexeme, std::move(right)); + } + + return call(); +} + +ExprPtr Parser::call() { + ExprPtr expr = primary(); + + while (true) { + if (match({TokenType::LEFT_PAREN})) { + expr = finish_call(std::move(expr)); + } else if (match({TokenType::DOT})) { + Token name = consume(TokenType::IDENTIFIER, "Expected property name after '.'."); + expr = std::make_unique(std::move(expr), name.lexeme); + } else if (match({TokenType::LEFT_BRACKET})) { + ExprPtr index = expression(); + consume(TokenType::RIGHT_BRACKET, "Expected ']' after index."); + expr = std::make_unique(std::move(expr), std::move(index)); + } else { + break; + } + } + + return expr; +} + +ExprPtr Parser::primary() { + if (match({TokenType::TRUE})) { + return std::make_unique(true); + } + if (match({TokenType::FALSE})) { + return std::make_unique(false); + } + if (match({TokenType::NIL})) { + return std::make_unique(); + } + + if (match({TokenType::NUMBER_LITERAL})) { + return std::make_unique(std::get(previous().literal)); + } + + if (match({TokenType::STRING_LITERAL})) { + return std::make_unique(std::get(previous().literal)); + } + + if (match({TokenType::THIS})) { + return std::make_unique("this"); + } + + if (match({TokenType::IDENTIFIER})) { + return std::make_unique(previous().lexeme); + } + + if (match({TokenType::LEFT_PAREN})) { + ExprPtr expr = expression(); + consume(TokenType::RIGHT_PAREN, "Expected ')' after expression."); + return expr; + } + + if (match({TokenType::LEFT_BRACKET})) { + std::vector elements; + + if (!check(TokenType::RIGHT_BRACKET)) { + do { + elements.push_back(expression()); + } while (match({TokenType::COMMA})); + } + + consume(TokenType::RIGHT_BRACKET, "Expected ']' after list elements."); + return std::make_unique(std::move(elements)); + } + + if (match({TokenType::LEFT_BRACE})) { + std::vector> pairs; + + if (!check(TokenType::RIGHT_BRACE)) { + do { + ExprPtr key = expression(); + consume(TokenType::COLON, "Expected ':' after map key."); + ExprPtr value = expression(); + pairs.emplace_back(std::move(key), std::move(value)); + } while (match({TokenType::COMMA})); + } + + consume(TokenType::RIGHT_BRACE, "Expected '}' after map pairs."); + return std::make_unique(std::move(pairs)); + } + + throw error(peek(), "Expected expression."); +} + +ExprPtr Parser::finish_call(ExprPtr callee) { + std::vector arguments; + + if (!check(TokenType::RIGHT_PAREN)) { + do { + arguments.push_back(expression()); + } while (match({TokenType::COMMA})); + } + + consume(TokenType::RIGHT_PAREN, "Expected ')' after arguments."); + + return std::make_unique(std::move(callee), std::move(arguments)); +} + +} // namespace camellya diff --git a/parser.h b/parser.h new file mode 100644 index 0000000..d5b8dae --- /dev/null +++ b/parser.h @@ -0,0 +1,66 @@ +#ifndef CAMELLYA_PARSER_H +#define CAMELLYA_PARSER_H + +#include "lexer.h" +#include "ast.h" +#include + +namespace camellya { + +class ParseError : public std::runtime_error { +public: + explicit ParseError(const std::string& message) : std::runtime_error(message) {} +}; + +class Parser { +public: + explicit Parser(std::vector tokens); + + Program parse(); + +private: + std::vector tokens_; + size_t current_ = 0; + + // Utility methods + Token peek() const; + Token previous() const; + bool is_at_end() const; + Token advance(); + bool check(TokenType type) const; + bool match(std::initializer_list types); + Token consume(TokenType type, const std::string& message); + + ParseError error(const Token& token, const std::string& message); + void synchronize(); + + // Parsing methods + StmtPtr declaration(); + StmtPtr class_declaration(); + StmtPtr function_declaration(); + StmtPtr var_declaration(); + StmtPtr statement(); + StmtPtr if_statement(); + StmtPtr while_statement(); + StmtPtr for_statement(); + StmtPtr return_statement(); + StmtPtr block_statement(); + StmtPtr expression_statement(); + + ExprPtr expression(); + ExprPtr assignment(); + ExprPtr logical_or(); + ExprPtr logical_and(); + ExprPtr equality(); + ExprPtr comparison(); + ExprPtr term(); + ExprPtr factor(); + ExprPtr unary(); + ExprPtr call(); + ExprPtr primary(); + ExprPtr finish_call(ExprPtr callee); +}; + +} // namespace camellya + +#endif // CAMELLYA_PARSER_H diff --git a/state.cpp b/state.cpp new file mode 100644 index 0000000..0a6c0bb --- /dev/null +++ b/state.cpp @@ -0,0 +1,135 @@ +#include "state.h" +#include +#include + +namespace camellya { + +State::State() : interpreter_(std::make_unique()) {} + +bool State::do_string(const std::string& script) { + try { + Lexer lexer(script); + auto tokens = lexer.tokenize(); + + Parser parser(std::move(tokens)); + Program program = parser.parse(); + + interpreter_->execute(program); + + last_error_.clear(); + return true; + } catch (const std::exception& e) { + last_error_ = e.what(); + return false; + } +} + +bool State::do_file(const std::string& filename) { + std::ifstream file(filename); + if (!file.is_open()) { + last_error_ = "Failed to open file: " + filename; + return false; + } + + std::stringstream buffer; + buffer << file.rdbuf(); + return do_string(buffer.str()); +} + +void State::register_function(const std::string& name, NativeFunction func) { + auto func_value = std::make_shared(name, func); + interpreter_->global_environment->define(name, func_value); +} + +ValuePtr State::get_global(const std::string& name) { + return interpreter_->global_environment->get(name); +} + +void State::set_global(const std::string& name, ValuePtr value) { + interpreter_->global_environment->define(name, value); +} + +void State::push_number(double value) { + stack_.push_back(std::make_shared(value)); +} + +void State::push_string(const std::string& value) { + stack_.push_back(std::make_shared(value)); +} + +void State::push_bool(bool value) { + stack_.push_back(std::make_shared(value)); +} + +void State::push_nil() { + stack_.push_back(std::make_shared()); +} + +void State::push_value(ValuePtr value) { + stack_.push_back(std::move(value)); +} + +double State::to_number(int index) { + ValuePtr val = get_stack_value(index); + if (val && val->type() == Type::NUMBER) { + return std::dynamic_pointer_cast(val)->value; + } + return 0.0; +} + +std::string State::to_string(int index) { + ValuePtr val = get_stack_value(index); + if (val) { + return val->to_string(); + } + return ""; +} + +bool State::to_bool(int index) { + ValuePtr val = get_stack_value(index); + return is_truthy(val); +} + +ValuePtr State::to_value(int index) { + return get_stack_value(index); +} + +void State::set_top(int index) { + if (index < 0) { + index = static_cast(stack_.size()) + index + 1; + } + + if (index < 0) { + stack_.clear(); + } else if (static_cast(index) < stack_.size()) { + stack_.resize(index); + } else { + while (static_cast(index) > stack_.size()) { + stack_.push_back(std::make_shared()); + } + } +} + +void State::pop(int n) { + if (n > static_cast(stack_.size())) { + stack_.clear(); + } else { + stack_.resize(stack_.size() - n); + } +} + +ValuePtr State::get_stack_value(int index) { + if (index < 0) { + index = static_cast(stack_.size()) + index; + } else { + index -= 1; // Convert to 0-based + } + + if (index < 0 || static_cast(index) >= stack_.size()) { + return nullptr; + } + + return stack_[index]; +} + +} // namespace camellya diff --git a/state.h b/state.h new file mode 100644 index 0000000..75ff4e4 --- /dev/null +++ b/state.h @@ -0,0 +1,63 @@ +#ifndef CAMELLYA_STATE_H +#define CAMELLYA_STATE_H + +#include "lexer.h" +#include "parser.h" +#include "interpreter.h" +#include "value.h" +#include +#include + +namespace camellya { + +// Main state class - similar to lua_State +class State { +public: + State(); + ~State() = default; + + // Execute script from string + bool do_string(const std::string& script); + + // Execute script from file + bool do_file(const std::string& filename); + + // Register native function + void register_function(const std::string& name, NativeFunction func); + + // Get global variable + ValuePtr get_global(const std::string& name); + + // Set global variable + void set_global(const std::string& name, ValuePtr value); + + // Stack operations (Lua-like API) + 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); + + double to_number(int index); + std::string to_string(int index); + bool to_bool(int index); + ValuePtr to_value(int index); + + int get_top() const { return static_cast(stack_.size()); } + void set_top(int index); + void pop(int n = 1); + + // Error handling + const std::string& get_error() const { return last_error_; } + +private: + std::unique_ptr interpreter_; + std::vector stack_; + std::string last_error_; + + ValuePtr get_stack_value(int index); +}; + +} // namespace camellya + +#endif // CAMELLYA_STATE_H diff --git a/tests/test_basic.cpp b/tests/test_basic.cpp new file mode 100644 index 0000000..5d74ed7 --- /dev/null +++ b/tests/test_basic.cpp @@ -0,0 +1,150 @@ +#include +#include +#include "library.h" + +#include + +using namespace camellya; + +TEST_CASE("basic arithmetic", "[script]") { + State state; + const char* script = R"( + number x = 10; + number y = 20; + number z = x + y; + )"; + + REQUIRE(state.do_string(script)); + + auto z = state.get_global("z"); + REQUIRE(z); + REQUIRE(z->type() == Type::NUMBER); + + auto nz = std::dynamic_pointer_cast(z); + REQUIRE(nz); + REQUIRE(nz->value == 30.0); +} + +TEST_CASE("basic function", "[script][func]") { + State state; + const char* script = R"( + func add(number x, number y) -> number { + return x + y; + } + number z = add(10, 20); + )"; + + REQUIRE(state.do_string(script)); + + auto z = state.get_global("z"); + REQUIRE(z); + REQUIRE(z->type() == Type::NUMBER); + + auto nz = std::dynamic_pointer_cast(z); + REQUIRE(nz); + REQUIRE(nz->value == 30.0); +} + +TEST_CASE("list indexing is 0-based", "[list]") { + State state; + const char* script = R"( + list numbers = [10, 20, 30]; + )"; + + REQUIRE(state.do_string(script)); + + auto list_val = state.get_global("numbers"); + REQUIRE(list_val); + REQUIRE(list_val->type() == Type::LIST); + + auto list = std::dynamic_pointer_cast(list_val); + REQUIRE(list); + REQUIRE(list->size() == 3); + + auto first = list->get(0); + auto third = list->get(2); + + REQUIRE(first->type() == Type::NUMBER); + REQUIRE(third->type() == Type::NUMBER); + + auto first_n = std::dynamic_pointer_cast(first); + auto third_n = std::dynamic_pointer_cast(third); + + REQUIRE(first_n->value == 10.0); + REQUIRE(third_n->value == 30.0); +} + +TEST_CASE("class init is called on declaration", "[class][init]") { + State state; + const char* script = R"( + class Person { + number age; + string name; + + func init() -> nil { + age = 18; + name = "Default"; + } + + func getAge() -> number { + return this.age; + } + } + + Person p; + number a = p.getAge(); + )"; + + REQUIRE(state.do_string(script)); + + auto p_val = state.get_global("p"); + REQUIRE(p_val); + REQUIRE(p_val->type() == Type::INSTANCE); + + auto instance = std::dynamic_pointer_cast(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(age_val); + auto name_str = std::dynamic_pointer_cast(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(a_val); + REQUIRE(a_num->value == 18.0); +} + +TEST_CASE("interpreter performance: simple loop", "[perf][script]") { + State state; + const char* script = R"( + func sum_to(number n) -> number { + number s = 0; + for (number i = 0; i < n; i = i + 1) { + s = s + i; + } + return s; + } + number r = sum_to(1000); + )"; + + BENCHMARK("sum_to(1000)") { + if (!state.do_string(script)) { + auto last_error = state.get_error(); + REQUIRE(last_error.empty()); + } + auto r_val = state.get_global("r"); + REQUIRE(r_val); + REQUIRE(r_val->type() == Type::NUMBER); + auto r_num = std::dynamic_pointer_cast(r_val); + REQUIRE(r_num->value == 499500.0); + }; +} diff --git a/value.cpp b/value.cpp new file mode 100644 index 0000000..42e29e4 --- /dev/null +++ b/value.cpp @@ -0,0 +1,124 @@ +#include "value.h" +#include +#include +#include + +namespace camellya { + +std::string NumberValue::to_string() const { + if (std::floor(value) == value) { + return std::to_string(static_cast(value)); + } + std::ostringstream oss; + oss << std::fixed << std::setprecision(6) << value; + std::string str = oss.str(); + str.erase(str.find_last_not_of('0') + 1, std::string::npos); + if (str.back() == '.') str.pop_back(); + return str; +} + +std::string ListValue::to_string() const { + std::ostringstream oss; + oss << "["; + for (size_t i = 0; i < elements.size(); ++i) { + if (i > 0) oss << ", "; + oss << elements[i]->to_string(); + } + oss << "]"; + return oss.str(); +} + +ValuePtr ListValue::clone() const { + std::vector cloned_elements; + cloned_elements.reserve(elements.size()); + for (const auto& elem : elements) { + cloned_elements.push_back(elem->clone()); + } + return std::make_shared(std::move(cloned_elements)); +} + +ValuePtr ListValue::get(size_t index) const { + if (index >= elements.size()) { + return std::make_shared(); + } + return elements[index]; +} + +void ListValue::set(size_t index, ValuePtr value) { + if (index >= elements.size()) { + elements.resize(index + 1, std::make_shared()); + } + elements[index] = std::move(value); +} + +std::string MapValue::to_string() const { + std::ostringstream oss; + oss << "{"; + bool first = true; + for (const auto& [key, value] : pairs) { + if (!first) oss << ", "; + first = false; + oss << key << ": " << value->to_string(); + } + oss << "}"; + return oss.str(); +} + +ValuePtr MapValue::clone() const { + std::map cloned_pairs; + for (const auto& [key, value] : pairs) { + cloned_pairs[key] = value->clone(); + } + return std::make_shared(std::move(cloned_pairs)); +} + +ValuePtr MapValue::get(const std::string& key) const { + auto it = pairs.find(key); + if (it == pairs.end()) { + return std::make_shared(); + } + return it->second; +} + +ValuePtr ClassValue::clone() const { + auto cloned = std::make_shared(name); + cloned->fields = fields; + cloned->methods = methods; + return cloned; +} + +ValuePtr InstanceValue::clone() const { + auto cloned = std::make_shared(klass); + for (const auto& [key, value] : fields) { + cloned->fields[key] = value->clone(); + } + return cloned; +} + +ValuePtr InstanceValue::get(const std::string& name) const { + // First check fields + auto field_it = fields.find(name); + if (field_it != fields.end()) { + return field_it->second; + } + + // Then check methods and bind 'this' + auto method_it = klass->methods.find(name); + if (method_it != klass->methods.end()) { + auto bound = std::make_shared(*method_it->second); + auto self = std::static_pointer_cast( + const_cast(this)->shared_from_this()); + bound->bound_instance = self; + return bound; + } + + return std::make_shared(); +} + +void InstanceValue::set(const std::string& name, ValuePtr value) { + if (fields.find(name) != fields.end()) { + fields[name] = std::move(value); + } +} + +} // namespace camellya diff --git a/value.h b/value.h new file mode 100644 index 0000000..3c2510f --- /dev/null +++ b/value.h @@ -0,0 +1,211 @@ +#ifndef CAMELLYA_VALUE_H +#define CAMELLYA_VALUE_H + +#include +#include +#include +#include +#include +#include + +namespace camellya { + +class Value; + +using ValuePtr = std::shared_ptr; +using NativeFunction = std::function&)>; + +enum class Type { + NIL, + NUMBER, + STRING, + BOOL, + LIST, + MAP, + FUNCTION, + CLASS, + INSTANCE +}; + +class Value { +public: + virtual ~Value() = default; + virtual Type type() const = 0; + virtual std::string to_string() const = 0; + virtual ValuePtr clone() const = 0; +}; + +class NilValue : public Value { +public: + Type type() const override { return Type::NIL; } + std::string to_string() const override { return "nil"; } + ValuePtr clone() const override { return std::make_shared(); } +}; + +class NumberValue : public Value { +public: + double value; + + explicit NumberValue(double value) : value(value) {} + Type type() const override { return Type::NUMBER; } + std::string to_string() const override; + ValuePtr clone() const override { return std::make_shared(value); } +}; + +class StringValue : public Value { +public: + std::string value; + + explicit StringValue(std::string value) : value(std::move(value)) {} + Type type() const override { return Type::STRING; } + std::string to_string() const override { return value; } + ValuePtr clone() const override { return std::make_shared(value); } +}; + +class BoolValue : public Value { +public: + bool value; + + explicit BoolValue(bool value) : value(value) {} + Type type() const override { return Type::BOOL; } + std::string to_string() const override { return value ? "true" : "false"; } + ValuePtr clone() const override { return std::make_shared(value); } +}; + +class ListValue : public Value { +public: + std::vector elements; + + ListValue() = default; + explicit ListValue(std::vector elements) : elements(std::move(elements)) {} + + Type type() const override { return Type::LIST; } + std::string to_string() const override; + ValuePtr clone() const override; + + void push(ValuePtr value) { elements.push_back(std::move(value)); } + ValuePtr get(size_t index) const; + void set(size_t index, ValuePtr value); + size_t size() const { return elements.size(); } +}; + +class MapValue : public Value { +public: + std::map pairs; + + MapValue() = default; + explicit MapValue(std::map pairs) : pairs(std::move(pairs)) {} + + Type type() const override { return Type::MAP; } + std::string to_string() const override; + ValuePtr clone() const override; + + void set(const std::string& key, ValuePtr value) { pairs[key] = std::move(value); } + ValuePtr get(const std::string& key) const; + bool has(const std::string& key) const { return pairs.find(key) != pairs.end(); } +}; + +// Forward declarations +struct FunctionDecl; +class ClassValue; +class InstanceValue; + +class FunctionValue : public Value { +public: + std::string name; + std::vector> parameters; + std::string return_type; + std::shared_ptr declaration; + NativeFunction native_func; + bool is_native; + std::shared_ptr bound_instance; + + // Script function + FunctionValue(std::string name, std::shared_ptr declaration, + std::shared_ptr bound_instance = nullptr) + : name(std::move(name)), declaration(std::move(declaration)), + is_native(false), bound_instance(std::move(bound_instance)) {} + + // Native function + FunctionValue(std::string name, NativeFunction func) + : name(std::move(name)), native_func(std::move(func)), is_native(true) {} + + Type type() const override { return Type::FUNCTION; } + std::string to_string() const override { return ""; } + ValuePtr clone() const override { return std::make_shared(*this); } +}; + +class ClassValue : public Value { +public: + std::string name; + std::map fields; // field_name -> type_name + std::map> methods; + + explicit ClassValue(std::string name) : name(std::move(name)) {} + + Type type() const override { return Type::CLASS; } + std::string to_string() const override { return ""; } + ValuePtr clone() const override; + + void add_field(const std::string& name, const std::string& type) { + fields[name] = type; + } + + void add_method(const std::string& name, std::shared_ptr method) { + methods[name] = std::move(method); + } +}; + +class InstanceValue : public Value, public std::enable_shared_from_this { +public: + std::shared_ptr klass; + std::map fields; + + explicit InstanceValue(std::shared_ptr klass) : klass(std::move(klass)) { + // Initialize fields with nil + for (const auto& [field_name, type_name] : this->klass->fields) { + fields[field_name] = std::make_shared(); + } + } + + Type type() const override { return Type::INSTANCE; } + std::string to_string() const override { return "name + ">"; } + ValuePtr clone() const override; + + ValuePtr get(const std::string& name) const; + void set(const std::string& name, ValuePtr value); +}; + +// Helper functions +inline bool is_truthy(const ValuePtr& value) { + if (!value || value->type() == Type::NIL) return false; + if (value->type() == Type::BOOL) { + return std::dynamic_pointer_cast(value)->value; + } + return true; +} + +inline bool values_equal(const ValuePtr& a, const ValuePtr& b) { + if (!a || !b) return false; + if (a->type() != b->type()) return false; + + switch (a->type()) { + case Type::NIL: + return true; + case Type::NUMBER: + return std::dynamic_pointer_cast(a)->value == + std::dynamic_pointer_cast(b)->value; + case Type::STRING: + return std::dynamic_pointer_cast(a)->value == + std::dynamic_pointer_cast(b)->value; + case Type::BOOL: + return std::dynamic_pointer_cast(a)->value == + std::dynamic_pointer_cast(b)->value; + default: + return false; + } +} + +} // namespace camellya + +#endif // CAMELLYA_VALUE_H