From 211a8374685840fb9bf39956c5deef94239a0a10 Mon Sep 17 00:00:00 2001 From: zekexiao Date: Tue, 13 Jan 2026 22:52:55 +0800 Subject: [PATCH] init --- .gitignore | 33 +++ CMakeLists.txt | 56 +++++ README.md | 217 ++++++++++++++++++ ast.h | 217 ++++++++++++++++++ example.chun | 52 +++++ interpreter.cpp | 524 +++++++++++++++++++++++++++++++++++++++++++ interpreter.h | 151 +++++++++++++ lexer.cpp | 235 +++++++++++++++++++ lexer.h | 88 ++++++++ library.cpp | 4 + library.h | 23 ++ parser.cpp | 482 +++++++++++++++++++++++++++++++++++++++ parser.h | 66 ++++++ state.cpp | 135 +++++++++++ state.h | 63 ++++++ tests/test_basic.cpp | 150 +++++++++++++ value.cpp | 124 ++++++++++ value.h | 211 +++++++++++++++++ 18 files changed, 2831 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 ast.h create mode 100644 example.chun create mode 100644 interpreter.cpp create mode 100644 interpreter.h create mode 100644 lexer.cpp create mode 100644 lexer.h create mode 100644 library.cpp create mode 100644 library.h create mode 100644 parser.cpp create mode 100644 parser.h create mode 100644 state.cpp create mode 100644 state.h create mode 100644 tests/test_basic.cpp create mode 100644 value.cpp create mode 100644 value.h 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