Compare commits

...

3 Commits

Author SHA1 Message Date
36d273e09f . 2026-02-04 19:37:03 +08:00
5aa4efede7 Rename files 2026-01-22 20:56:02 +08:00
8c5197c760 Remove interpreter 2026-01-22 20:33:43 +08:00
20 changed files with 1420 additions and 2042 deletions

17
.zed/debug.json Normal file
View File

@@ -0,0 +1,17 @@
// Project-local debug tasks
//
// For more documentation on how to configure debug tasks,
// see: https://zed.dev/docs/debugger
[
{
"label": "Debug Test",
"build": {
"command": "make",
"args": ["-j8"],
"cwd": "$ZED_WORKTREE_ROOT/build",
},
"program": "$ZED_WORKTREE_ROOT/build/camellya_tests",
"request": "launch",
"adapter": "GDB",
},
]

View File

@@ -10,11 +10,10 @@ option(CAMELLYA_BUILD_CLI "Build command line interface" ON)
# Library sources
set(LIB_SOURCES
src/camellya.cpp
src/lexer.cpp
src/parser.cpp
src/value.cpp
src/interpreter.cpp
src/state.cpp
src/chunk.cpp
src/compiler.cpp
src/vm.cpp
@@ -27,8 +26,6 @@ set(LIB_HEADERS
src/parser.h
src/ast.h
src/value.h
src/interpreter.h
src/state.h
src/chunk.h
src/compiler.h
src/vm.h
@@ -72,6 +69,9 @@ if(CAMELLYA_BUILD_TESTS)
tests/test_basic.cpp
tests/test_utf8.cpp
tests/test_vm.cpp
tests/test_list.cpp
tests/test_map_builtin.cpp
tests/test_generic_builtin.cpp
)
target_include_directories(camellya_tests

View File

@@ -1,22 +1,26 @@
#include "camellya.h"
#include <iostream>
#include <format>
#include <chrono>
#include <format>
#include <iostream>
int main(int argc, char **argv) {
if (argc < 2) {
std::cout << std::format("Usage: chun <script> \n") << std::endl;
return 0;
}
std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();
camellya::State state;
bool success = state.do_file(argv[1]);
std::chrono::high_resolution_clock::time_point start =
std::chrono::high_resolution_clock::now();
camellya::Camellya c;
bool success = c.do_file(argv[1]);
if (!success) {
std::cerr << "Error: " << state.get_error() << std::endl;
std::cerr << "Error: " << c.get_error() << std::endl;
return 1;
}
std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now();
std::chrono::high_resolution_clock::time_point end =
std::chrono::high_resolution_clock::now();
std::chrono::duration<double> duration = end - start;
std::cout << std::format("Execution completed in {} seconds. \n", duration.count()) << std::endl;
std::cout << std::format("Execution completed in {} seconds. \n",
duration.count())
<< std::endl;
return 0;
}

171
src/camellya.cpp Normal file
View File

@@ -0,0 +1,171 @@
#include "camellya.h"
#include <fstream>
#include <sstream>
#include "compiler.h"
#include "lexer.h"
#include "parser.h"
#include "vm.h"
namespace camellya {
class CamellyaImpl {
public:
CamellyaImpl()
: vm(std::make_unique<VM>()), compiler(std::make_unique<Compiler>()),
stack(), last_error() {}
std::unique_ptr<VM> vm;
std::unique_ptr<Compiler> compiler;
std::vector<ValuePtr> stack;
std::string last_error;
bool execute_program(const Program &program) {
// Use VM
auto chunk = compiler->compile(program);
if (!chunk) {
last_error = compiler->get_error();
return false;
}
bool success = vm->execute(chunk);
if (!success) {
last_error = vm->get_error();
}
return success;
}
};
Camellya::Camellya() : d(std::make_unique<CamellyaImpl>()) {}
Camellya::~Camellya() = default;
bool Camellya::do_string(const std::string &script) {
try {
Lexer lexer(script);
auto tokens = lexer.tokenize();
Parser parser(std::move(tokens));
Program program = parser.parse();
bool success = d->execute_program(program);
if (success) {
d->last_error.clear();
}
return success;
} catch (const std::exception &e) {
d->last_error = e.what();
return false;
}
}
bool Camellya::do_file(const std::string &filename) {
std::ifstream file(filename);
if (!file.is_open()) {
d->last_error = "Failed to open file: " + filename;
return false;
}
std::stringstream buffer;
buffer << file.rdbuf();
return do_string(buffer.str());
}
void Camellya::register_function(const std::string &name, NativeFunction func) {
d->vm->register_native_function(name, func);
}
void Camellya::register_builtin_class(const std::string &type_name, std::shared_ptr<ClassValue> klass) {
d->vm->register_builtin_class(type_name, klass);
}
int Camellya::get_top() const { return static_cast<int>(d->stack.size()); }
ValuePtr Camellya::get_global(const std::string &name) {
return d->vm->get_global(name);
}
void Camellya::set_global(const std::string &name, ValuePtr value) {
d->vm->set_global(name, value);
}
void Camellya::push_number(double value) {
d->stack.push_back(std::make_shared<NumberValue>(value));
}
void Camellya::push_string(const std::string &value) {
d->stack.push_back(std::make_shared<StringValue>(value));
}
void Camellya::push_bool(bool value) {
d->stack.push_back(std::make_shared<BoolValue>(value));
}
void Camellya::push_nil() { d->stack.push_back(std::make_shared<NilValue>()); }
void Camellya::push_value(ValuePtr value) {
d->stack.push_back(std::move(value));
}
double Camellya::to_number(int index) {
ValuePtr val = get_stack_value(index);
if (val && val->type() == Type::NUMBER) {
return std::dynamic_pointer_cast<NumberValue>(val)->value;
}
return 0.0;
}
std::string Camellya::to_string(int index) {
ValuePtr val = get_stack_value(index);
if (val) {
return val->to_string();
}
return "";
}
bool Camellya::to_bool(int index) {
ValuePtr val = get_stack_value(index);
return is_truthy(val);
}
ValuePtr Camellya::to_value(int index) { return get_stack_value(index); }
void Camellya::set_top(int index) {
if (index < 0) {
index = static_cast<int>(d->stack.size()) + index + 1;
}
if (index < 0) {
d->stack.clear();
} else if (static_cast<size_t>(index) < d->stack.size()) {
d->stack.resize(index);
} else {
while (static_cast<size_t>(index) > d->stack.size()) {
d->stack.push_back(std::make_shared<NilValue>());
}
}
}
void Camellya::pop(int n) {
if (n > static_cast<int>(d->stack.size())) {
d->stack.clear();
} else {
d->stack.resize(d->stack.size() - n);
}
}
ValuePtr Camellya::get_stack_value(int index) {
if (index < 0) {
index = static_cast<int>(d->stack.size()) + index;
} else {
index -= 1; // Convert to 0-based
}
if (index < 0 || static_cast<size_t>(index) >= d->stack.size()) {
return nullptr;
}
return d->stack[index];
}
const std::string &Camellya::get_error() const { return d->last_error; }
} // namespace camellya

View File

@@ -1,28 +1,62 @@
#ifndef CAMELLYA_LIBRARY_H
#define CAMELLYA_LIBRARY_H
#ifndef CAMELLYA_STATE_H
#define CAMELLYA_STATE_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"
#include "vm.h"
#include "compiler.h"
#include "chunk.h"
#include "opcode.h"
#include "exceptions.h"
#include <memory>
#include <string>
namespace camellya {
// Version info
constexpr const char* VERSION = "0.1.0";
constexpr const char* VERSION_NAME = "Camellya";
// Main state class - similar to lua_State
class CamellyaImpl;
class Camellya {
public:
Camellya();
~Camellya();
// 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);
// Register built-in class for any type (including user native types)
void register_builtin_class(const std::string &type_name, std::shared_ptr<ClassValue> klass);
// 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;
void set_top(int index);
void pop(int n = 1);
// Error handling
const std::string &get_error() const;
private:
std::unique_ptr<CamellyaImpl> d;
ValuePtr get_stack_value(int index);
};
} // namespace camellya
#endif // CAMELLYA_LIBRARY_H
#endif // CAMELLYA_STATE_H

View File

@@ -1,12 +1,10 @@
#include "compiler.h"
#include <iostream>
#include <format>
#include "exceptions.h"
namespace camellya {
Compiler::Compiler()
: current_chunk(nullptr), scope_depth(0), had_error(false) {
}
: current_chunk(nullptr), scope_depth(0), had_error(false) {}
std::shared_ptr<Chunk> Compiler::compile(const Program &program) {
current_chunk = std::make_shared<Chunk>();
@@ -128,7 +126,8 @@ void Compiler::compile_unary(const UnaryExpr& expr) {
}
void Compiler::compile_literal(const LiteralExpr &expr) {
std::visit([this](auto&& arg) {
std::visit(
[this](auto &&arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, double>) {
emit_constant(std::make_shared<NumberValue>(arg));
@@ -143,15 +142,18 @@ void Compiler::compile_literal(const LiteralExpr& expr) {
} else {
emit_opcode(OpCode::OP_NIL);
}
}, expr.value);
},
expr.value);
}
void Compiler::compile_variable(const VariableExpr &expr) {
int local = resolve_local(expr.name);
if (local != -1) {
emit_bytes(static_cast<uint8_t>(OpCode::OP_GET_LOCAL), static_cast<uint8_t>(local));
emit_bytes(static_cast<uint8_t>(OpCode::OP_GET_LOCAL),
static_cast<uint8_t>(local));
} else {
emit_bytes(static_cast<uint8_t>(OpCode::OP_GET_GLOBAL), identifier_constant(expr.name));
emit_bytes(static_cast<uint8_t>(OpCode::OP_GET_GLOBAL),
identifier_constant(expr.name));
}
}
@@ -160,9 +162,11 @@ void Compiler::compile_assign(const AssignExpr& expr) {
int local = resolve_local(expr.name);
if (local != -1) {
emit_bytes(static_cast<uint8_t>(OpCode::OP_SET_LOCAL), static_cast<uint8_t>(local));
emit_bytes(static_cast<uint8_t>(OpCode::OP_SET_LOCAL),
static_cast<uint8_t>(local));
} else {
emit_bytes(static_cast<uint8_t>(OpCode::OP_SET_GLOBAL), identifier_constant(expr.name));
emit_bytes(static_cast<uint8_t>(OpCode::OP_SET_GLOBAL),
identifier_constant(expr.name));
}
}
@@ -262,7 +266,8 @@ void Compiler::compile_var_decl(const VarDecl& stmt) {
// It's a class type - emit code to get the class and call it
emit_bytes(static_cast<uint8_t>(OpCode::OP_GET_GLOBAL),
identifier_constant(stmt.type_name));
emit_bytes(static_cast<uint8_t>(OpCode::OP_CALL), 0); // Call with 0 arguments
emit_bytes(static_cast<uint8_t>(OpCode::OP_CALL),
0); // Call with 0 arguments
} else {
emit_opcode(OpCode::OP_NIL);
}
@@ -528,7 +533,8 @@ void Compiler::compile_class_decl(const ClassDecl& stmt) {
loops = std::move(prev_loops);
auto func_decl_ptr = std::make_shared<FunctionDecl>(*func_decl);
auto func = std::make_shared<FunctionValue>(func_decl->name, func_decl_ptr, method_chunk);
auto func = std::make_shared<FunctionValue>(func_decl->name,
func_decl_ptr, method_chunk);
klass->add_method(func_decl->name, func);
}
}
@@ -545,9 +551,7 @@ void Compiler::emit_byte(uint8_t byte) {
current_chunk->write(byte, 0); // Line number tracking could be improved
}
void Compiler::emit_opcode(OpCode op) {
emit_byte(static_cast<uint8_t>(op));
}
void Compiler::emit_opcode(OpCode op) { emit_byte(static_cast<uint8_t>(op)); }
void Compiler::emit_bytes(uint8_t byte1, uint8_t byte2) {
emit_byte(byte1);
@@ -565,9 +569,7 @@ size_t Compiler::emit_jump(OpCode op) {
return current_chunk->current_offset() - 2;
}
void Compiler::patch_jump(size_t offset) {
current_chunk->patch_jump(offset);
}
void Compiler::patch_jump(size_t offset) { current_chunk->patch_jump(offset); }
void Compiler::emit_loop(size_t loop_start) {
emit_opcode(OpCode::OP_LOOP);
@@ -582,9 +584,7 @@ void Compiler::emit_loop(size_t loop_start) {
emit_byte(offset & 0xff);
}
void Compiler::begin_scope() {
scope_depth++;
}
void Compiler::begin_scope() { scope_depth++; }
void Compiler::end_scope() {
scope_depth--;

View File

@@ -4,9 +4,7 @@
#include "ast.h"
#include "chunk.h"
#include "value.h"
#include "exceptions.h"
#include <memory>
#include <map>
#include <vector>
namespace camellya {

View File

@@ -1,567 +0,0 @@
#include "interpreter.h"
#include <iostream>
#include <format>
#include <cmath>
namespace camellya {
Interpreter::Interpreter() {
global_environment = std::make_shared<Environment>();
environment = global_environment;
register_native_functions();
}
void Interpreter::register_native_functions() {
// print function - supports format strings
auto print_func = std::make_shared<FunctionValue>("print",
[](const std::vector<ValuePtr>& args) -> ValuePtr {
if (args.empty()) {
std::cout << std::endl;
return std::make_shared<NilValue>();
}
// 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<NilValue>();
});
global_environment->define("print", print_func);
// len function
auto len_func = std::make_shared<FunctionValue>("len",
[](const std::vector<ValuePtr>& 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<ListValue>(arg);
return std::make_shared<NumberValue>(static_cast<double>(list->size()));
} else if (arg->type() == Type::STRING) {
auto str = std::dynamic_pointer_cast<StringValue>(arg);
return std::make_shared<NumberValue>(static_cast<double>(str->value.length()));
} else if (arg->type() == Type::MAP) {
auto map = std::dynamic_pointer_cast<MapValue>(arg);
return std::make_shared<NumberValue>(static_cast<double>(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<const BinaryExpr*>(&expr)) {
return eval_binary(*binary);
} else if (auto* unary = dynamic_cast<const UnaryExpr*>(&expr)) {
return eval_unary(*unary);
} else if (auto* literal = dynamic_cast<const LiteralExpr*>(&expr)) {
return eval_literal(*literal);
} else if (auto* variable = dynamic_cast<const VariableExpr*>(&expr)) {
return eval_variable(*variable);
} else if (auto* assign = dynamic_cast<const AssignExpr*>(&expr)) {
return eval_assign(*assign);
} else if (auto* call = dynamic_cast<const CallExpr*>(&expr)) {
return eval_call(*call);
} else if (auto* get = dynamic_cast<const GetExpr*>(&expr)) {
return eval_get(*get);
} else if (auto* set = dynamic_cast<const SetExpr*>(&expr)) {
return eval_set(*set);
} else if (auto* index = dynamic_cast<const IndexExpr*>(&expr)) {
return eval_index(*index);
} else if (auto* index_set = dynamic_cast<const IndexSetExpr*>(&expr)) {
return eval_index_set(*index_set);
} else if (auto* list = dynamic_cast<const ListExpr*>(&expr)) {
return eval_list(*list);
} else if (auto* map = dynamic_cast<const MapExpr*>(&expr)) {
return eval_map(*map);
}
throw RuntimeError("Unknown expression type.");
}
void Interpreter::execute_statement(const Stmt& stmt) {
if (auto* expr_stmt = dynamic_cast<const ExprStmt*>(&stmt)) {
exec_expr_stmt(*expr_stmt);
} else if (auto* var_decl = dynamic_cast<const VarDecl*>(&stmt)) {
exec_var_decl(*var_decl);
} else if (auto* block = dynamic_cast<const BlockStmt*>(&stmt)) {
exec_block(*block);
} else if (auto* if_stmt = dynamic_cast<const IfStmt*>(&stmt)) {
exec_if(*if_stmt);
} else if (auto* while_stmt = dynamic_cast<const WhileStmt*>(&stmt)) {
exec_while(*while_stmt);
} else if (auto* for_stmt = dynamic_cast<const ForStmt*>(&stmt)) {
exec_for(*for_stmt);
} else if (auto* return_stmt = dynamic_cast<const ReturnStmt*>(&stmt)) {
exec_return(*return_stmt);
} else if (auto* break_stmt = dynamic_cast<const BreakStmt*>(&stmt)) {
exec_break(*break_stmt);
} else if (auto* continue_stmt = dynamic_cast<const ContinueStmt*>(&stmt)) {
exec_continue(*continue_stmt);
} else if (auto* func_decl = dynamic_cast<const FunctionDecl*>(&stmt)) {
exec_function_decl(*func_decl);
} else if (auto* class_decl = dynamic_cast<const ClassDecl*>(&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<NumberValue>(left)->value;
double r = std::dynamic_pointer_cast<NumberValue>(right)->value;
return std::make_shared<NumberValue>(l + r);
}
if (left->type() == Type::STRING && right->type() == Type::STRING) {
const auto& l = std::dynamic_pointer_cast<StringValue>(left)->value;
const auto& r = std::dynamic_pointer_cast<StringValue>(right)->value;
return std::make_shared<StringValue>(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<NumberValue>(left)->value;
double r = std::dynamic_pointer_cast<NumberValue>(right)->value;
if (expr.op == "-") return std::make_shared<NumberValue>(l - r);
if (expr.op == "*") return std::make_shared<NumberValue>(l * r);
if (expr.op == "/") {
if (r == 0) throw RuntimeError("Division by zero.");
return std::make_shared<NumberValue>(l / r);
}
if (expr.op == "%") return std::make_shared<NumberValue>(std::fmod(l, r));
}
if (expr.op == "==" ) {
return std::make_shared<BoolValue>(values_equal(left, right));
}
if (expr.op == "!=") {
return std::make_shared<BoolValue>(!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<NumberValue>(left)->value;
double r = std::dynamic_pointer_cast<NumberValue>(right)->value;
if (expr.op == "<") return std::make_shared<BoolValue>(l < r);
if (expr.op == "<=") return std::make_shared<BoolValue>(l <= r);
if (expr.op == ">") return std::make_shared<BoolValue>(l > r);
if (expr.op == ">=") return std::make_shared<BoolValue>(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<NumberValue>(operand)->value;
return std::make_shared<NumberValue>(-value);
}
if (expr.op == "!") {
return std::make_shared<BoolValue>(!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<decltype(arg)>;
if constexpr (std::is_same_v<T, double>) {
return std::make_shared<NumberValue>(arg);
} else if constexpr (std::is_same_v<T, std::string>) {
return std::make_shared<StringValue>(arg);
} else if constexpr (std::is_same_v<T, bool>) {
return std::make_shared<BoolValue>(arg);
} else {
return std::make_shared<NilValue>();
}
}, 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<ValuePtr> arguments;
for (const auto& arg : expr.arguments) {
arguments.push_back(evaluate(*arg));
}
if (callee->type() == Type::FUNCTION) {
auto func = std::dynamic_pointer_cast<FunctionValue>(callee);
return call_function(*func, arguments);
} else if (callee->type() == Type::CLASS) {
// Class instantiation
auto klass = std::dynamic_pointer_cast<ClassValue>(callee);
auto instance = std::make_shared<InstanceValue>(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<FunctionValue>(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<InstanceValue>(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<InstanceValue>(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<ListValue>(object);
if (index->type() != Type::NUMBER) {
throw RuntimeError("List index must be a number.");
}
size_t idx = static_cast<size_t>(std::dynamic_pointer_cast<NumberValue>(index)->value);
return list->get(idx);
} else if (object->type() == Type::MAP) {
auto map = std::dynamic_pointer_cast<MapValue>(object);
if (index->type() != Type::STRING) {
throw RuntimeError("Map key must be a string.");
}
std::string key = std::dynamic_pointer_cast<StringValue>(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<ListValue>(object);
if (index->type() != Type::NUMBER) {
throw RuntimeError("List index must be a number.");
}
size_t idx = static_cast<size_t>(std::dynamic_pointer_cast<NumberValue>(index)->value);
list->set(idx, value);
return value;
} else if (object->type() == Type::MAP) {
auto map = std::dynamic_pointer_cast<MapValue>(object);
if (index->type() != Type::STRING) {
throw RuntimeError("Map key must be a string.");
}
std::string key = std::dynamic_pointer_cast<StringValue>(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<ListValue>();
for (const auto& elem : expr.elements) {
list->push(evaluate(*elem));
}
return list;
}
ValuePtr Interpreter::eval_map(const MapExpr& expr) {
auto map = std::make_shared<MapValue>();
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<StringValue>(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);
// If type inference is needed, we use the value directly
if (stmt.infer_type) {
// Type is automatically inferred from the initializer value
environment->define(stmt.name, value);
return;
}
// If explicit type is provided, verify the value matches
// (This is a simple check, could be more sophisticated)
if (!stmt.type_name.empty()) {
// Just use the value; type checking could be added here
environment->define(stmt.name, value);
return;
}
} else {
// No initializer, use default value based on type
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>(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) {
try {
while (is_truthy(evaluate(*stmt.condition))) {
try {
execute_statement(*stmt.body);
} catch (const ContinueException&) {
// Continue: just proceed to the next iteration (check condition again)
continue;
}
}
} catch (const BreakException&) {
// Break: exit the loop
}
}
void Interpreter::exec_for(const ForStmt& stmt) {
auto previous = environment;
environment = std::make_shared<Environment>(environment);
try {
if (stmt.initializer) {
execute_statement(*stmt.initializer);
}
while (!stmt.condition || is_truthy(evaluate(*stmt.condition))) {
try {
execute_statement(*stmt.body);
} catch (const ContinueException&) {
// Continue: proceed to increment
}
if (stmt.increment) {
evaluate(*stmt.increment);
}
}
} catch (const BreakException&) {
// Break: exit the loop
} catch (...) {
environment = previous;
throw;
}
environment = previous;
}
void Interpreter::exec_return(const ReturnStmt& stmt) {
ValuePtr value = stmt.value ? evaluate(*stmt.value) : std::make_shared<NilValue>();
throw ReturnException(value);
}
void Interpreter::exec_break(const BreakStmt& stmt) {
throw BreakException();
}
void Interpreter::exec_continue(const ContinueStmt& stmt) {
throw ContinueException();
}
void Interpreter::exec_function_decl(const FunctionDecl& stmt) {
auto func_decl = std::make_shared<FunctionDecl>(stmt);
auto func = std::make_shared<FunctionValue>(stmt.name, func_decl);
environment->define(stmt.name, func);
}
void Interpreter::exec_class_decl(const ClassDecl& stmt) {
auto klass = std::make_shared<ClassValue>(stmt.name);
for (const auto& member : stmt.members) {
if (auto* var_decl = dynamic_cast<VarDecl*>(member.get())) {
klass->add_field(var_decl->name, var_decl->type_name);
} else if (auto* func_decl = dynamic_cast<FunctionDecl*>(member.get())) {
auto func_decl_ptr = std::make_shared<FunctionDecl>(*func_decl);
auto func = std::make_shared<FunctionValue>(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<ValuePtr>& 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<Environment>(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<NilValue>();
}
ValuePtr Interpreter::create_default_value(const std::string& type_name) {
if (type_name == "number") {
return std::make_shared<NumberValue>(0.0);
} else if (type_name == "string") {
return std::make_shared<StringValue>("");
} else if (type_name == "bool") {
return std::make_shared<BoolValue>(false);
} else if (type_name == "list") {
return std::make_shared<ListValue>();
} else if (type_name == "map") {
return std::make_shared<MapValue>();
} 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<ClassValue>(klass);
auto instance = std::make_shared<InstanceValue>(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<FunctionValue>(init_val);
call_function(*init_func, {});
}
return instance;
}
}
return std::make_shared<NilValue>();
}
} // namespace camellya

View File

@@ -1,151 +0,0 @@
#ifndef CAMELLYA_INTERPRETER_H
#define CAMELLYA_INTERPRETER_H
#include "ast.h"
#include "value.h"
#include "exceptions.h"
#include <map>
#include <memory>
namespace camellya {
class ReturnException : public std::exception {
public:
ValuePtr value;
explicit ReturnException(ValuePtr value) : value(std::move(value)) {}
};
class BreakException : public std::exception {};
class ContinueException : public std::exception {};
class Environment {
public:
std::shared_ptr<Environment> parent;
std::map<std::string, ValuePtr> values;
Environment() : parent(nullptr) {}
explicit Environment(std::shared_ptr<Environment> 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<InstanceValue>(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<InstanceValue>(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> environment;
std::shared_ptr<Environment> 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_break(const BreakStmt& stmt);
void exec_continue(const ContinueStmt& stmt);
void exec_function_decl(const FunctionDecl& stmt);
void exec_class_decl(const ClassDecl& stmt);
ValuePtr call_function(const FunctionValue& func, const std::vector<ValuePtr>& arguments);
ValuePtr create_default_value(const std::string& type_name);
};
} // namespace camellya
#endif // CAMELLYA_INTERPRETER_H

View File

@@ -1,174 +0,0 @@
#include "state.h"
#include <fstream>
#include <sstream>
namespace camellya {
State::State(ExecutionMode mode)
: execution_mode_(mode),
interpreter_(std::make_unique<Interpreter>()),
vm_(std::make_unique<VM>()),
compiler_(std::make_unique<Compiler>()) {}
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();
bool success = execute_program(program);
if (success) {
last_error_.clear();
}
return success;
} 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<FunctionValue>(name, func);
if (execution_mode_ == ExecutionMode::INTERPRETER) {
interpreter_->global_environment->define(name, func_value);
} else {
vm_->register_native_function(name, func);
}
}
ValuePtr State::get_global(const std::string& name) {
if (execution_mode_ == ExecutionMode::INTERPRETER) {
return interpreter_->global_environment->get(name);
} else {
return vm_->get_global(name);
}
}
void State::set_global(const std::string& name, ValuePtr value) {
if (execution_mode_ == ExecutionMode::INTERPRETER) {
interpreter_->global_environment->define(name, value);
} else {
vm_->set_global(name, value);
}
}
void State::push_number(double value) {
stack_.push_back(std::make_shared<NumberValue>(value));
}
void State::push_string(const std::string& value) {
stack_.push_back(std::make_shared<StringValue>(value));
}
void State::push_bool(bool value) {
stack_.push_back(std::make_shared<BoolValue>(value));
}
void State::push_nil() {
stack_.push_back(std::make_shared<NilValue>());
}
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<NumberValue>(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<int>(stack_.size()) + index + 1;
}
if (index < 0) {
stack_.clear();
} else if (static_cast<size_t>(index) < stack_.size()) {
stack_.resize(index);
} else {
while (static_cast<size_t>(index) > stack_.size()) {
stack_.push_back(std::make_shared<NilValue>());
}
}
}
void State::pop(int n) {
if (n > static_cast<int>(stack_.size())) {
stack_.clear();
} else {
stack_.resize(stack_.size() - n);
}
}
ValuePtr State::get_stack_value(int index) {
if (index < 0) {
index = static_cast<int>(stack_.size()) + index;
} else {
index -= 1; // Convert to 0-based
}
if (index < 0 || static_cast<size_t>(index) >= stack_.size()) {
return nullptr;
}
return stack_[index];
}
bool State::execute_program(const Program& program) {
if (execution_mode_ == ExecutionMode::INTERPRETER) {
// Use tree-walking interpreter
interpreter_->execute(program);
return true;
} else {
// Use VM
auto chunk = compiler_->compile(program);
if (!chunk) {
last_error_ = compiler_->get_error();
return false;
}
bool success = vm_->execute(chunk);
if (!success) {
last_error_ = vm_->get_error();
}
return success;
}
}
} // namespace camellya

View File

@@ -1,81 +0,0 @@
#ifndef CAMELLYA_STATE_H
#define CAMELLYA_STATE_H
#include "lexer.h"
#include "parser.h"
#include "interpreter.h"
#include "vm.h"
#include "compiler.h"
#include "value.h"
#include <string>
#include <memory>
namespace camellya {
// Execution mode
enum class ExecutionMode {
INTERPRETER, // Tree-walking interpreter
VM // Bytecode VM
};
// Main state class - similar to lua_State
class State {
public:
State(ExecutionMode mode = ExecutionMode::VM);
~State() = default;
// Set execution mode
void set_execution_mode(ExecutionMode mode) { execution_mode_ = mode; }
ExecutionMode get_execution_mode() const { return execution_mode_; }
// 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<int>(stack_.size()); }
void set_top(int index);
void pop(int n = 1);
// Error handling
const std::string& get_error() const { return last_error_; }
private:
ExecutionMode execution_mode_;
std::unique_ptr<Interpreter> interpreter_;
std::unique_ptr<VM> vm_;
std::unique_ptr<Compiler> compiler_;
std::vector<ValuePtr> stack_;
std::string last_error_;
ValuePtr get_stack_value(int index);
// Helper for execution
bool execute_program(const Program& program);
};
} // namespace camellya
#endif // CAMELLYA_STATE_H

View File

@@ -1,7 +1,7 @@
#include "value.h"
#include <sstream>
#include <iomanip>
#include <cmath>
#include <iomanip>
#include <sstream>
namespace camellya {
@@ -13,7 +13,8 @@ std::string NumberValue::to_string() const {
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();
if (str.back() == '.')
str.pop_back();
return str;
}
@@ -21,7 +22,8 @@ std::string ListValue::to_string() const {
std::ostringstream oss;
oss << "[";
for (size_t i = 0; i < elements.size(); ++i) {
if (i > 0) oss << ", ";
if (i > 0)
oss << ", ";
oss << elements[i]->to_string();
}
oss << "]";
@@ -56,7 +58,8 @@ std::string MapValue::to_string() const {
oss << "{";
bool first = true;
for (const auto &[key, value] : pairs) {
if (!first) oss << ", ";
if (!first)
oss << ", ";
first = false;
oss << key << ": " << value->to_string();
}
@@ -106,9 +109,7 @@ ValuePtr InstanceValue::get(const std::string& name) const {
auto method_it = klass->methods.find(name);
if (method_it != klass->methods.end()) {
auto bound = std::make_shared<FunctionValue>(*method_it->second);
auto self = std::static_pointer_cast<InstanceValue>(
const_cast<InstanceValue*>(this)->shared_from_this());
bound->bound_instance = self;
bound->bound_instance = const_cast<InstanceValue *>(this)->shared_from_this();
return bound;
}

View File

@@ -24,13 +24,15 @@ enum class Type {
MAP,
FUNCTION,
CLASS,
INSTANCE
INSTANCE,
NATIVE
};
class Value {
class Value : public std::enable_shared_from_this<Value> {
public:
virtual ~Value() = default;
virtual Type type() const = 0;
virtual std::string type_name() const = 0;
virtual std::string to_string() const = 0;
virtual ValuePtr clone() const = 0;
};
@@ -38,6 +40,7 @@ public:
class NilValue : public Value {
public:
Type type() const override { return Type::NIL; }
std::string type_name() const override { return "nil"; }
std::string to_string() const override { return "nil"; }
ValuePtr clone() const override { return std::make_shared<NilValue>(); }
};
@@ -48,6 +51,7 @@ public:
explicit NumberValue(double value) : value(value) {}
Type type() const override { return Type::NUMBER; }
std::string type_name() const override { return "number"; }
std::string to_string() const override;
ValuePtr clone() const override { return std::make_shared<NumberValue>(value); }
};
@@ -58,6 +62,7 @@ public:
explicit StringValue(std::string value) : value(std::move(value)) {}
Type type() const override { return Type::STRING; }
std::string type_name() const override { return "string"; }
std::string to_string() const override { return value; }
ValuePtr clone() const override { return std::make_shared<StringValue>(value); }
};
@@ -68,6 +73,7 @@ public:
explicit BoolValue(bool value) : value(value) {}
Type type() const override { return Type::BOOL; }
std::string type_name() const override { return "bool"; }
std::string to_string() const override { return value ? "true" : "false"; }
ValuePtr clone() const override { return std::make_shared<BoolValue>(value); }
};
@@ -80,6 +86,7 @@ public:
explicit ListValue(std::vector<ValuePtr> elements) : elements(std::move(elements)) {}
Type type() const override { return Type::LIST; }
std::string type_name() const override { return "list"; }
std::string to_string() const override;
ValuePtr clone() const override;
@@ -97,6 +104,7 @@ public:
explicit MapValue(std::map<std::string, ValuePtr> pairs) : pairs(std::move(pairs)) {}
Type type() const override { return Type::MAP; }
std::string type_name() const override { return "map"; }
std::string to_string() const override;
ValuePtr clone() const override;
@@ -120,12 +128,12 @@ public:
std::shared_ptr<Chunk> chunk;
NativeFunction native_func;
bool is_native;
std::shared_ptr<InstanceValue> bound_instance;
ValuePtr bound_instance;
// Script function
FunctionValue(std::string name, std::shared_ptr<FunctionDecl> declaration,
std::shared_ptr<Chunk> chunk = nullptr,
std::shared_ptr<InstanceValue> bound_instance = nullptr)
ValuePtr bound_instance = nullptr)
: name(std::move(name)), declaration(std::move(declaration)),
chunk(std::move(chunk)), is_native(false),
bound_instance(std::move(bound_instance)) {}
@@ -135,6 +143,7 @@ public:
: name(std::move(name)), native_func(std::move(func)), is_native(true) {}
Type type() const override { return Type::FUNCTION; }
std::string type_name() const override { return "function"; }
std::string to_string() const override { return "<function " + name + ">"; }
ValuePtr clone() const override { return std::make_shared<FunctionValue>(*this); }
};
@@ -148,6 +157,7 @@ public:
explicit ClassValue(std::string name) : name(std::move(name)) {}
Type type() const override { return Type::CLASS; }
std::string type_name() const override { return "class"; }
std::string to_string() const override { return "<class " + name + ">"; }
ValuePtr clone() const override;
@@ -160,7 +170,7 @@ public:
}
};
class InstanceValue : public Value, public std::enable_shared_from_this<InstanceValue> {
class InstanceValue : public Value {
public:
std::shared_ptr<ClassValue> klass;
std::map<std::string, ValuePtr> fields;
@@ -173,6 +183,7 @@ public:
}
Type type() const override { return Type::INSTANCE; }
std::string type_name() const override { return klass->name; }
std::string to_string() const override { return "<instance of " + klass->name + ">"; }
ValuePtr clone() const override;
@@ -180,6 +191,18 @@ public:
void set(const std::string& name, ValuePtr value);
};
class NativeValue : public Value {
public:
std::string _type_name;
explicit NativeValue(std::string type_name) : _type_name(std::move(type_name)) {}
Type type() const override { return Type::NATIVE; }
std::string type_name() const override { return _type_name; }
std::string to_string() const override { return "<native " + _type_name + ">"; }
ValuePtr clone() const override { return std::make_shared<NativeValue>(_type_name); }
};
// Helper functions
inline bool is_truthy(const ValuePtr& value) {
if (!value || value->type() == Type::NIL) return false;

View File

@@ -1,5 +1,4 @@
#include "vm.h"
#include "interpreter.h"
#include <iostream>
#include <format>
#include <cmath>
@@ -212,6 +211,9 @@ bool VM::run() {
if (func->is_native) {
// Native function call
std::vector<ValuePtr> args;
if (func->bound_instance) {
args.push_back(func->bound_instance);
}
for (int i = arg_count - 1; i >= 0; i--) {
args.push_back(peek(i));
}
@@ -424,7 +426,18 @@ bool VM::run() {
auto instance = std::dynamic_pointer_cast<InstanceValue>(object);
push(instance->get(name));
} else {
runtime_error("Only instances have properties.");
auto it = builtin_classes.find(object->type_name());
if (it != builtin_classes.end()) {
auto klass = it->second;
auto method_it = klass->methods.find(name);
if (method_it != klass->methods.end()) {
auto bound = std::make_shared<FunctionValue>(*method_it->second);
bound->bound_instance = object;
push(bound);
break;
}
}
runtime_error("Only instances and types with registered built-in classes have properties/methods.");
return false;
}
break;
@@ -613,6 +626,67 @@ void VM::register_builtin_functions() {
throw RuntimeError("len() expects list, string, or map.");
});
globals["len"] = len_func;
// Define list class
auto list_klass = std::make_shared<ClassValue>("list");
list_klass->add_method("push", std::make_shared<FunctionValue>("push",
[](const std::vector<ValuePtr>& args) -> ValuePtr {
if (args.size() != 2) throw RuntimeError("list.push() expects 1 argument.");
auto list = std::dynamic_pointer_cast<ListValue>(args[0]);
list->push(args[1]);
return args[1];
}));
list_klass->add_method("pop", std::make_shared<FunctionValue>("pop",
[](const std::vector<ValuePtr>& args) -> ValuePtr {
if (args.size() != 1) throw RuntimeError("list.pop() expects 0 arguments.");
auto list = std::dynamic_pointer_cast<ListValue>(args[0]);
if (list->elements.empty()) return std::make_shared<NilValue>();
ValuePtr val = list->elements.back();
list->elements.pop_back();
return val;
}));
list_klass->add_method("len", std::make_shared<FunctionValue>("len",
[](const std::vector<ValuePtr>& args) -> ValuePtr {
if (args.size() != 1) throw RuntimeError("list.len() expects 0 arguments.");
auto list = std::dynamic_pointer_cast<ListValue>(args[0]);
return std::make_shared<NumberValue>(static_cast<double>(list->size()));
}));
register_builtin_class("list", list_klass);
// Define map class
auto map_klass = std::make_shared<ClassValue>("map");
map_klass->add_method("set", std::make_shared<FunctionValue>("set",
[](const std::vector<ValuePtr>& args) -> ValuePtr {
if (args.size() != 3) throw RuntimeError("map.set() expects 2 arguments.");
auto map = std::dynamic_pointer_cast<MapValue>(args[0]);
if (args[1]->type() != Type::STRING) throw RuntimeError("Map key must be a string.");
std::string key = std::dynamic_pointer_cast<StringValue>(args[1])->value;
map->set(key, args[2]);
return args[2];
}));
map_klass->add_method("get", std::make_shared<FunctionValue>("get",
[](const std::vector<ValuePtr>& args) -> ValuePtr {
if (args.size() != 2) throw RuntimeError("map.get() expects 1 argument.");
auto map = std::dynamic_pointer_cast<MapValue>(args[0]);
if (args[1]->type() != Type::STRING) throw RuntimeError("Map key must be a string.");
std::string key = std::dynamic_pointer_cast<StringValue>(args[1])->value;
return map->get(key);
}));
map_klass->add_method("has", std::make_shared<FunctionValue>("has",
[](const std::vector<ValuePtr>& args) -> ValuePtr {
if (args.size() != 2) throw RuntimeError("map.has() expects 1 argument.");
auto map = std::dynamic_pointer_cast<MapValue>(args[0]);
if (args[1]->type() != Type::STRING) throw RuntimeError("Map key must be a string.");
std::string key = std::dynamic_pointer_cast<StringValue>(args[1])->value;
return std::make_shared<BoolValue>(map->has(key));
}));
map_klass->add_method("len", std::make_shared<FunctionValue>("len",
[](const std::vector<ValuePtr>& args) -> ValuePtr {
if (args.size() != 1) throw RuntimeError("map.len() expects 0 arguments.");
auto map = std::dynamic_pointer_cast<MapValue>(args[0]);
return std::make_shared<NumberValue>(static_cast<double>(map->pairs.size()));
}));
register_builtin_class("map", map_klass);
}
void VM::register_native_function(const std::string& name, NativeFunction func) {
@@ -620,6 +694,11 @@ void VM::register_native_function(const std::string& name, NativeFunction func)
globals[name] = func_value;
}
void VM::register_builtin_class(const std::string& type_name, std::shared_ptr<ClassValue> klass) {
builtin_classes[type_name] = klass;
globals[klass->name] = klass;
}
void VM::set_global(const std::string& name, ValuePtr value) {
globals[name] = value;
}

View File

@@ -34,6 +34,9 @@ public:
// Register native functions
void register_native_function(const std::string& name, NativeFunction func);
// Register built-in classes for types (like list, map)
void register_builtin_class(const std::string& type_name, std::shared_ptr<ClassValue> klass);
// Global variable access
void set_global(const std::string& name, ValuePtr value);
ValuePtr get_global(const std::string& name);
@@ -48,6 +51,7 @@ private:
std::vector<CallFrame> frames;
CallFrame* current_frame;
std::map<std::string, ValuePtr> globals;
std::map<std::string, std::shared_ptr<ClassValue>> builtin_classes;
std::string error_message;
// Main execution loop

View File

@@ -1,13 +1,13 @@
#include <catch2/catch_test_macros.hpp>
#include <catch2/benchmark/catch_benchmark.hpp>
#include "camellya.h"
#include <catch2/benchmark/catch_benchmark.hpp>
#include <catch2/catch_test_macros.hpp>
#include <memory>
using namespace camellya;
TEST_CASE("basic arithmetic", "[script]") {
State state;
Camellya state;
const char *script = R"(
var x = 10;
var y = 20;
@@ -26,7 +26,7 @@ TEST_CASE("basic arithmetic", "[script]") {
}
TEST_CASE("basic function", "[script][func]") {
State state;
Camellya state;
const char *script = R"(
func add(number x, number y) -> number {
return x + y;
@@ -46,7 +46,7 @@ TEST_CASE("basic function", "[script][func]") {
}
TEST_CASE("list indexing is 0-based", "[list]") {
State state;
Camellya state;
const char *script = R"(
var numbers = [10, 20, 30];
)";
@@ -75,7 +75,7 @@ TEST_CASE("list indexing is 0-based", "[list]") {
}
TEST_CASE("class init is called on declaration", "[class][init]") {
State state;
Camellya state;
const char *script = R"(
class Person {
var age : number;
@@ -124,8 +124,7 @@ TEST_CASE("class init is called on declaration", "[class][init]") {
}
TEST_CASE("interpreter performance: simple loop", "[perf][script]") {
State state;
State state_vm(ExecutionMode::VM);
Camellya state;
const char *script = R"(
func sum_to(number n) -> number {
var s = 0;
@@ -149,22 +148,10 @@ TEST_CASE("interpreter performance: simple loop", "[perf][script]") {
auto r_num = std::dynamic_pointer_cast<NumberValue>(r_val);
REQUIRE(r_num->value == 499500.0);
};
BENCHMARK("state_vm sum_to(1000)") {
if (!state_vm.do_string(script)) {
auto last_error = state_vm.get_error();
REQUIRE(last_error.empty());
}
auto r_val = state_vm.get_global("r");
REQUIRE(r_val);
REQUIRE(r_val->type() == Type::NUMBER);
auto r_num = std::dynamic_pointer_cast<NumberValue>(r_val);
REQUIRE(r_num->value == 499500.0);
};
}
TEST_CASE("loop break", "[script][loop]") {
State state;
Camellya state;
const char *script = R"(
var sum = 0;
for (var i = 0; i < 10; i = i + 1) {
@@ -183,7 +170,7 @@ TEST_CASE("loop break", "[script][loop]") {
}
TEST_CASE("loop continue", "[script][loop]") {
State state;
Camellya state;
const char *script = R"(
var sum = 0;
for (var i = 0; i < 5; i = i + 1) {
@@ -202,7 +189,7 @@ TEST_CASE("loop continue", "[script][loop]") {
}
TEST_CASE("while break and continue", "[script][loop]") {
State state;
Camellya state;
const char *script = R"(
var i = 0;
var sum = 0;

View File

@@ -0,0 +1,59 @@
#include "camellya.h"
#include "exceptions.h"
#include <catch2/catch_all.hpp>
#include <cmath>
using namespace camellya;
// Simple Vector3D wrapper for testing
class Vector3DValue : public NativeValue {
public:
double x, y, z;
Vector3DValue(double x, double y, double z) : NativeValue("Vector3D"), x(x), y(y), z(z) {}
std::string to_string() const override {
return "Vector3D(" + std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(z) + ")";
}
ValuePtr clone() const override {
return std::make_shared<Vector3DValue>(x, y, z);
}
};
TEST_CASE("Generic built-in class (Vector3D) test", "[generic]") {
Camellya c;
// 1. Create the ClassValue for Vector3D
auto v3d_klass = std::make_shared<ClassValue>("Vector3D");
// 2. Add a native method 'length'
v3d_klass->add_method("length", std::make_shared<FunctionValue>("length",
[](const std::vector<ValuePtr>& args) -> ValuePtr {
if (args.size() != 1) throw RuntimeError("Vector3D.length() expects 0 arguments.");
auto vec = std::dynamic_pointer_cast<Vector3DValue>(args[0]);
double len = std::sqrt(vec->x * vec->x + vec->y * vec->y + vec->z * vec->z);
return std::make_shared<NumberValue>(len);
}));
// 3. Register it as a built-in class for type "Vector3D"
c.register_builtin_class("Vector3D", v3d_klass);
// 4. Register a factory function to create Vector3D instances from script
c.register_function("Vector3D", [](const std::vector<ValuePtr>& args) -> ValuePtr {
if (args.size() != 3) throw RuntimeError("Vector3D() expects 3 arguments.");
double x = std::dynamic_pointer_cast<NumberValue>(args[0])->value;
double y = std::dynamic_pointer_cast<NumberValue>(args[1])->value;
double z = std::dynamic_pointer_cast<NumberValue>(args[2])->value;
return std::make_shared<Vector3DValue>(x, y, z);
});
SECTION("vector3d methods") {
REQUIRE(c.do_string(R"(
var v = Vector3D(3, 4, 0);
var len = v.length();
)"));
auto len = c.get_global("len");
REQUIRE(std::dynamic_pointer_cast<NumberValue>(len)->value == 5.0);
}
}

18
tests/test_list.cpp Normal file
View File

@@ -0,0 +1,18 @@
#include "camellya.h"
#include <catch2/catch_all.hpp>
using namespace camellya;
TEST_CASE("Basic List test", "[list]") {
Camellya c;
SECTION("len function") {
REQUIRE(c.do_string(R"(
var arr = [1, 2, 3, 4];
var size = len(arr);
arr.push(5);
size = arr.len();
)"));
auto size = c.get_global("size");
REQUIRE(std::dynamic_pointer_cast<NumberValue>(size)->value == 5.0);
}
}

View File

@@ -0,0 +1,32 @@
#include "camellya.h"
#include <catch2/catch_all.hpp>
using namespace camellya;
TEST_CASE("Map built-in class test", "[map]") {
Camellya c;
SECTION("map methods") {
REQUIRE(c.do_string(R"(
var m = {"a": 1, "b": 2};
var s1 = m.len();
m.set("c", 3);
var s2 = m.len();
var has_a = m.has("a");
var has_z = m.has("z");
var val_b = m.get("b");
)"));
auto s1 = c.get_global("s1");
auto s2 = c.get_global("s2");
auto has_a = c.get_global("has_a");
auto has_z = c.get_global("has_z");
auto val_b = c.get_global("val_b");
REQUIRE(std::dynamic_pointer_cast<NumberValue>(s1)->value == 2.0);
REQUIRE(std::dynamic_pointer_cast<NumberValue>(s2)->value == 3.0);
REQUIRE(std::dynamic_pointer_cast<BoolValue>(has_a)->value == true);
REQUIRE(std::dynamic_pointer_cast<BoolValue>(has_z)->value == false);
REQUIRE(std::dynamic_pointer_cast<NumberValue>(val_b)->value == 2.0);
}
}

View File

@@ -1,10 +1,10 @@
#include <catch2/catch_test_macros.hpp>
#include "src/camellya.h"
#include <catch2/catch_test_macros.hpp>
using namespace camellya;
TEST_CASE("VM - Basic arithmetic", "[vm]") {
State state(ExecutionMode::VM);
Camellya state;
SECTION("Addition") {
REQUIRE(state.do_string("var x = 10 + 20;"));
@@ -37,7 +37,7 @@ TEST_CASE("VM - Basic arithmetic", "[vm]") {
}
TEST_CASE("VM - Variables and assignment", "[vm]") {
State state(ExecutionMode::VM);
Camellya state;
SECTION("Variable declaration and initialization") {
REQUIRE(state.do_string("var a = 42;"));
@@ -57,19 +57,20 @@ TEST_CASE("VM - Variables and assignment", "[vm]") {
}
TEST_CASE("VM - String operations", "[vm]") {
State state(ExecutionMode::VM);
Camellya state;
SECTION("String concatenation") {
REQUIRE(state.do_string(R"(var greeting = "Hello" + " " + "World";)"));
auto greeting = state.get_global("greeting");
REQUIRE(greeting != nullptr);
REQUIRE(greeting->type() == Type::STRING);
REQUIRE(std::dynamic_pointer_cast<StringValue>(greeting)->value == "Hello World");
REQUIRE(std::dynamic_pointer_cast<StringValue>(greeting)->value ==
"Hello World");
}
}
TEST_CASE("VM - Comparison operators", "[vm]") {
State state(ExecutionMode::VM);
Camellya state;
SECTION("Equality") {
REQUIRE(state.do_string("var eq = 10 == 10;"));
@@ -91,7 +92,7 @@ TEST_CASE("VM - Comparison operators", "[vm]") {
}
TEST_CASE("VM - Lists", "[vm]") {
State state(ExecutionMode::VM);
Camellya state;
SECTION("Create list") {
REQUIRE(state.do_string("var numbers = [1, 2, 3, 4, 5];"));
@@ -113,7 +114,7 @@ TEST_CASE("VM - Lists", "[vm]") {
}
TEST_CASE("VM - Maps", "[vm]") {
State state(ExecutionMode::VM);
Camellya state;
SECTION("Create map") {
REQUIRE(state.do_string(R"(var person = {"name": "Alice", "age": "30"};)"));
@@ -134,7 +135,7 @@ TEST_CASE("VM - Maps", "[vm]") {
}
TEST_CASE("VM - If statements", "[vm]") {
State state(ExecutionMode::VM);
Camellya state;
SECTION("If branch taken") {
REQUIRE(state.do_string(R"(
@@ -162,7 +163,7 @@ TEST_CASE("VM - If statements", "[vm]") {
}
TEST_CASE("VM - While loops", "[vm]") {
State state(ExecutionMode::VM);
Camellya state;
SECTION("While loop") {
REQUIRE(state.do_string(R"(
@@ -179,7 +180,7 @@ TEST_CASE("VM - While loops", "[vm]") {
}
TEST_CASE("VM - Native functions", "[vm]") {
State state(ExecutionMode::VM);
Camellya state;
SECTION("len function") {
REQUIRE(state.do_string(R"(
@@ -190,80 +191,3 @@ TEST_CASE("VM - Native functions", "[vm]") {
REQUIRE(std::dynamic_pointer_cast<NumberValue>(size)->value == 4.0);
}
}
TEST_CASE("VM vs Interpreter - Same results", "[vm][interpreter]") {
const char* script = R"(
var x = 10;
var y = 20;
var sum = x + y;
var product = x * y;
)";
State vm_state(ExecutionMode::VM);
State interp_state(ExecutionMode::INTERPRETER);
REQUIRE(vm_state.do_string(script));
REQUIRE(interp_state.do_string(script));
auto vm_sum = vm_state.get_global("sum");
auto interp_sum = interp_state.get_global("sum");
REQUIRE(std::dynamic_pointer_cast<NumberValue>(vm_sum)->value ==
std::dynamic_pointer_cast<NumberValue>(interp_sum)->value);
auto vm_product = vm_state.get_global("product");
auto interp_product = interp_state.get_global("product");
REQUIRE(std::dynamic_pointer_cast<NumberValue>(vm_product)->value ==
std::dynamic_pointer_cast<NumberValue>(interp_product)->value);
}
TEST_CASE("class init is called on declaration", "[vm][class][init]") {
State state(ExecutionMode::VM);
const char* script = R"(
class Person {
var age : number;
var name : string;
func init() -> nil {
age = 18;
name = "Default";
}
func getAge() -> number {
return this.age;
}
}
var p : Person;
var a = p.getAge();
)";
auto ret = state.do_string(script);
if(!ret) {
REQUIRE(state.get_error() == "");
}
auto p_val = state.get_global("p");
REQUIRE(p_val);
REQUIRE(p_val->type() == Type::INSTANCE);
auto instance = std::dynamic_pointer_cast<InstanceValue>(p_val);
REQUIRE(instance);
auto age_val = instance->get("age");
auto name_val = instance->get("name");
REQUIRE(age_val->type() == Type::NUMBER);
REQUIRE(name_val->type() == Type::STRING);
auto age_num = std::dynamic_pointer_cast<NumberValue>(age_val);
auto name_str = std::dynamic_pointer_cast<StringValue>(name_val);
REQUIRE(age_num->value == 18.0);
REQUIRE(name_str->value == "Default");
auto a_val = state.get_global("a");
REQUIRE(a_val);
REQUIRE(a_val->type() == Type::NUMBER);
auto a_num = std::dynamic_pointer_cast<NumberValue>(a_val);
REQUIRE(a_num->value == 18.0);
}