.
This commit is contained in:
17
.zed/debug.json
Normal file
17
.zed/debug.json
Normal 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",
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -69,6 +69,9 @@ if(CAMELLYA_BUILD_TESTS)
|
|||||||
tests/test_basic.cpp
|
tests/test_basic.cpp
|
||||||
tests/test_utf8.cpp
|
tests/test_utf8.cpp
|
||||||
tests/test_vm.cpp
|
tests/test_vm.cpp
|
||||||
|
tests/test_list.cpp
|
||||||
|
tests/test_map_builtin.cpp
|
||||||
|
tests/test_generic_builtin.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(camellya_tests
|
target_include_directories(camellya_tests
|
||||||
|
|||||||
@@ -71,10 +71,13 @@ bool Camellya::do_file(const std::string &filename) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Camellya::register_function(const std::string &name, NativeFunction func) {
|
void Camellya::register_function(const std::string &name, NativeFunction func) {
|
||||||
auto func_value = std::make_shared<FunctionValue>(name, func);
|
|
||||||
d->vm->register_native_function(name, 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()); }
|
int Camellya::get_top() const { return static_cast<int>(d->stack.size()); }
|
||||||
ValuePtr Camellya::get_global(const std::string &name) {
|
ValuePtr Camellya::get_global(const std::string &name) {
|
||||||
return d->vm->get_global(name);
|
return d->vm->get_global(name);
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ public:
|
|||||||
// Register native function
|
// Register native function
|
||||||
void register_function(const std::string &name, NativeFunction func);
|
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
|
// Get global variable
|
||||||
ValuePtr get_global(const std::string &name);
|
ValuePtr get_global(const std::string &name);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "value.h"
|
#include "value.h"
|
||||||
#include <sstream>
|
|
||||||
#include <iomanip>
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
namespace camellya {
|
namespace camellya {
|
||||||
|
|
||||||
@@ -13,7 +13,8 @@ std::string NumberValue::to_string() const {
|
|||||||
oss << std::fixed << std::setprecision(6) << value;
|
oss << std::fixed << std::setprecision(6) << value;
|
||||||
std::string str = oss.str();
|
std::string str = oss.str();
|
||||||
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
|
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;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,7 +22,8 @@ std::string ListValue::to_string() const {
|
|||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << "[";
|
oss << "[";
|
||||||
for (size_t i = 0; i < elements.size(); ++i) {
|
for (size_t i = 0; i < elements.size(); ++i) {
|
||||||
if (i > 0) oss << ", ";
|
if (i > 0)
|
||||||
|
oss << ", ";
|
||||||
oss << elements[i]->to_string();
|
oss << elements[i]->to_string();
|
||||||
}
|
}
|
||||||
oss << "]";
|
oss << "]";
|
||||||
@@ -56,7 +58,8 @@ std::string MapValue::to_string() const {
|
|||||||
oss << "{";
|
oss << "{";
|
||||||
bool first = true;
|
bool first = true;
|
||||||
for (const auto &[key, value] : pairs) {
|
for (const auto &[key, value] : pairs) {
|
||||||
if (!first) oss << ", ";
|
if (!first)
|
||||||
|
oss << ", ";
|
||||||
first = false;
|
first = false;
|
||||||
oss << key << ": " << value->to_string();
|
oss << key << ": " << value->to_string();
|
||||||
}
|
}
|
||||||
@@ -106,9 +109,7 @@ ValuePtr InstanceValue::get(const std::string& name) const {
|
|||||||
auto method_it = klass->methods.find(name);
|
auto method_it = klass->methods.find(name);
|
||||||
if (method_it != klass->methods.end()) {
|
if (method_it != klass->methods.end()) {
|
||||||
auto bound = std::make_shared<FunctionValue>(*method_it->second);
|
auto bound = std::make_shared<FunctionValue>(*method_it->second);
|
||||||
auto self = std::static_pointer_cast<InstanceValue>(
|
bound->bound_instance = const_cast<InstanceValue *>(this)->shared_from_this();
|
||||||
const_cast<InstanceValue*>(this)->shared_from_this());
|
|
||||||
bound->bound_instance = self;
|
|
||||||
return bound;
|
return bound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
33
src/value.h
33
src/value.h
@@ -24,13 +24,15 @@ enum class Type {
|
|||||||
MAP,
|
MAP,
|
||||||
FUNCTION,
|
FUNCTION,
|
||||||
CLASS,
|
CLASS,
|
||||||
INSTANCE
|
INSTANCE,
|
||||||
|
NATIVE
|
||||||
};
|
};
|
||||||
|
|
||||||
class Value {
|
class Value : public std::enable_shared_from_this<Value> {
|
||||||
public:
|
public:
|
||||||
virtual ~Value() = default;
|
virtual ~Value() = default;
|
||||||
virtual Type type() const = 0;
|
virtual Type type() const = 0;
|
||||||
|
virtual std::string type_name() const = 0;
|
||||||
virtual std::string to_string() const = 0;
|
virtual std::string to_string() const = 0;
|
||||||
virtual ValuePtr clone() const = 0;
|
virtual ValuePtr clone() const = 0;
|
||||||
};
|
};
|
||||||
@@ -38,6 +40,7 @@ public:
|
|||||||
class NilValue : public Value {
|
class NilValue : public Value {
|
||||||
public:
|
public:
|
||||||
Type type() const override { return Type::NIL; }
|
Type type() const override { return Type::NIL; }
|
||||||
|
std::string type_name() const override { return "nil"; }
|
||||||
std::string to_string() const override { return "nil"; }
|
std::string to_string() const override { return "nil"; }
|
||||||
ValuePtr clone() const override { return std::make_shared<NilValue>(); }
|
ValuePtr clone() const override { return std::make_shared<NilValue>(); }
|
||||||
};
|
};
|
||||||
@@ -48,6 +51,7 @@ public:
|
|||||||
|
|
||||||
explicit NumberValue(double value) : value(value) {}
|
explicit NumberValue(double value) : value(value) {}
|
||||||
Type type() const override { return Type::NUMBER; }
|
Type type() const override { return Type::NUMBER; }
|
||||||
|
std::string type_name() const override { return "number"; }
|
||||||
std::string to_string() const override;
|
std::string to_string() const override;
|
||||||
ValuePtr clone() const override { return std::make_shared<NumberValue>(value); }
|
ValuePtr clone() const override { return std::make_shared<NumberValue>(value); }
|
||||||
};
|
};
|
||||||
@@ -58,6 +62,7 @@ public:
|
|||||||
|
|
||||||
explicit StringValue(std::string value) : value(std::move(value)) {}
|
explicit StringValue(std::string value) : value(std::move(value)) {}
|
||||||
Type type() const override { return Type::STRING; }
|
Type type() const override { return Type::STRING; }
|
||||||
|
std::string type_name() const override { return "string"; }
|
||||||
std::string to_string() const override { return value; }
|
std::string to_string() const override { return value; }
|
||||||
ValuePtr clone() const override { return std::make_shared<StringValue>(value); }
|
ValuePtr clone() const override { return std::make_shared<StringValue>(value); }
|
||||||
};
|
};
|
||||||
@@ -68,6 +73,7 @@ public:
|
|||||||
|
|
||||||
explicit BoolValue(bool value) : value(value) {}
|
explicit BoolValue(bool value) : value(value) {}
|
||||||
Type type() const override { return Type::BOOL; }
|
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"; }
|
std::string to_string() const override { return value ? "true" : "false"; }
|
||||||
ValuePtr clone() const override { return std::make_shared<BoolValue>(value); }
|
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)) {}
|
explicit ListValue(std::vector<ValuePtr> elements) : elements(std::move(elements)) {}
|
||||||
|
|
||||||
Type type() const override { return Type::LIST; }
|
Type type() const override { return Type::LIST; }
|
||||||
|
std::string type_name() const override { return "list"; }
|
||||||
std::string to_string() const override;
|
std::string to_string() const override;
|
||||||
ValuePtr clone() const override;
|
ValuePtr clone() const override;
|
||||||
|
|
||||||
@@ -97,6 +104,7 @@ public:
|
|||||||
explicit MapValue(std::map<std::string, ValuePtr> pairs) : pairs(std::move(pairs)) {}
|
explicit MapValue(std::map<std::string, ValuePtr> pairs) : pairs(std::move(pairs)) {}
|
||||||
|
|
||||||
Type type() const override { return Type::MAP; }
|
Type type() const override { return Type::MAP; }
|
||||||
|
std::string type_name() const override { return "map"; }
|
||||||
std::string to_string() const override;
|
std::string to_string() const override;
|
||||||
ValuePtr clone() const override;
|
ValuePtr clone() const override;
|
||||||
|
|
||||||
@@ -120,12 +128,12 @@ public:
|
|||||||
std::shared_ptr<Chunk> chunk;
|
std::shared_ptr<Chunk> chunk;
|
||||||
NativeFunction native_func;
|
NativeFunction native_func;
|
||||||
bool is_native;
|
bool is_native;
|
||||||
std::shared_ptr<InstanceValue> bound_instance;
|
ValuePtr bound_instance;
|
||||||
|
|
||||||
// Script function
|
// Script function
|
||||||
FunctionValue(std::string name, std::shared_ptr<FunctionDecl> declaration,
|
FunctionValue(std::string name, std::shared_ptr<FunctionDecl> declaration,
|
||||||
std::shared_ptr<Chunk> chunk = nullptr,
|
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)),
|
: name(std::move(name)), declaration(std::move(declaration)),
|
||||||
chunk(std::move(chunk)), is_native(false),
|
chunk(std::move(chunk)), is_native(false),
|
||||||
bound_instance(std::move(bound_instance)) {}
|
bound_instance(std::move(bound_instance)) {}
|
||||||
@@ -135,6 +143,7 @@ public:
|
|||||||
: name(std::move(name)), native_func(std::move(func)), is_native(true) {}
|
: name(std::move(name)), native_func(std::move(func)), is_native(true) {}
|
||||||
|
|
||||||
Type type() const override { return Type::FUNCTION; }
|
Type type() const override { return Type::FUNCTION; }
|
||||||
|
std::string type_name() const override { return "function"; }
|
||||||
std::string to_string() const override { return "<function " + name + ">"; }
|
std::string to_string() const override { return "<function " + name + ">"; }
|
||||||
ValuePtr clone() const override { return std::make_shared<FunctionValue>(*this); }
|
ValuePtr clone() const override { return std::make_shared<FunctionValue>(*this); }
|
||||||
};
|
};
|
||||||
@@ -148,6 +157,7 @@ public:
|
|||||||
explicit ClassValue(std::string name) : name(std::move(name)) {}
|
explicit ClassValue(std::string name) : name(std::move(name)) {}
|
||||||
|
|
||||||
Type type() const override { return Type::CLASS; }
|
Type type() const override { return Type::CLASS; }
|
||||||
|
std::string type_name() const override { return "class"; }
|
||||||
std::string to_string() const override { return "<class " + name + ">"; }
|
std::string to_string() const override { return "<class " + name + ">"; }
|
||||||
ValuePtr clone() const override;
|
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:
|
public:
|
||||||
std::shared_ptr<ClassValue> klass;
|
std::shared_ptr<ClassValue> klass;
|
||||||
std::map<std::string, ValuePtr> fields;
|
std::map<std::string, ValuePtr> fields;
|
||||||
@@ -173,6 +183,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
Type type() const override { return Type::INSTANCE; }
|
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 + ">"; }
|
std::string to_string() const override { return "<instance of " + klass->name + ">"; }
|
||||||
ValuePtr clone() const override;
|
ValuePtr clone() const override;
|
||||||
|
|
||||||
@@ -180,6 +191,18 @@ public:
|
|||||||
void set(const std::string& name, ValuePtr value);
|
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
|
// Helper functions
|
||||||
inline bool is_truthy(const ValuePtr& value) {
|
inline bool is_truthy(const ValuePtr& value) {
|
||||||
if (!value || value->type() == Type::NIL) return false;
|
if (!value || value->type() == Type::NIL) return false;
|
||||||
|
|||||||
83
src/vm.cpp
83
src/vm.cpp
@@ -1,5 +1,4 @@
|
|||||||
#include "vm.h"
|
#include "vm.h"
|
||||||
#include "interpreter.h"
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <format>
|
#include <format>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@@ -212,6 +211,9 @@ bool VM::run() {
|
|||||||
if (func->is_native) {
|
if (func->is_native) {
|
||||||
// Native function call
|
// Native function call
|
||||||
std::vector<ValuePtr> args;
|
std::vector<ValuePtr> args;
|
||||||
|
if (func->bound_instance) {
|
||||||
|
args.push_back(func->bound_instance);
|
||||||
|
}
|
||||||
for (int i = arg_count - 1; i >= 0; i--) {
|
for (int i = arg_count - 1; i >= 0; i--) {
|
||||||
args.push_back(peek(i));
|
args.push_back(peek(i));
|
||||||
}
|
}
|
||||||
@@ -424,7 +426,18 @@ bool VM::run() {
|
|||||||
auto instance = std::dynamic_pointer_cast<InstanceValue>(object);
|
auto instance = std::dynamic_pointer_cast<InstanceValue>(object);
|
||||||
push(instance->get(name));
|
push(instance->get(name));
|
||||||
} else {
|
} 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;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -613,6 +626,67 @@ void VM::register_builtin_functions() {
|
|||||||
throw RuntimeError("len() expects list, string, or map.");
|
throw RuntimeError("len() expects list, string, or map.");
|
||||||
});
|
});
|
||||||
globals["len"] = len_func;
|
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) {
|
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;
|
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) {
|
void VM::set_global(const std::string& name, ValuePtr value) {
|
||||||
globals[name] = value;
|
globals[name] = value;
|
||||||
}
|
}
|
||||||
|
|||||||
4
src/vm.h
4
src/vm.h
@@ -34,6 +34,9 @@ public:
|
|||||||
// Register native functions
|
// Register native functions
|
||||||
void register_native_function(const std::string& name, NativeFunction func);
|
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
|
// Global variable access
|
||||||
void set_global(const std::string& name, ValuePtr value);
|
void set_global(const std::string& name, ValuePtr value);
|
||||||
ValuePtr get_global(const std::string& name);
|
ValuePtr get_global(const std::string& name);
|
||||||
@@ -48,6 +51,7 @@ private:
|
|||||||
std::vector<CallFrame> frames;
|
std::vector<CallFrame> frames;
|
||||||
CallFrame* current_frame;
|
CallFrame* current_frame;
|
||||||
std::map<std::string, ValuePtr> globals;
|
std::map<std::string, ValuePtr> globals;
|
||||||
|
std::map<std::string, std::shared_ptr<ClassValue>> builtin_classes;
|
||||||
std::string error_message;
|
std::string error_message;
|
||||||
|
|
||||||
// Main execution loop
|
// Main execution loop
|
||||||
|
|||||||
59
tests/test_generic_builtin.cpp
Normal file
59
tests/test_generic_builtin.cpp
Normal 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
18
tests/test_list.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
32
tests/test_map_builtin.cpp
Normal file
32
tests/test_map_builtin.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user