This commit is contained in:
2026-02-04 19:37:03 +08:00
parent 5aa4efede7
commit 36d273e09f
11 changed files with 335 additions and 93 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

@@ -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

View File

@@ -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);

View File

@@ -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);

View File

@@ -1,124 +1,125 @@
#include "value.h" #include "value.h"
#include <sstream>
#include <iomanip>
#include <cmath> #include <cmath>
#include <iomanip>
#include <sstream>
namespace camellya { namespace camellya {
std::string NumberValue::to_string() const { std::string NumberValue::to_string() const {
if (std::floor(value) == value) { if (std::floor(value) == value) {
return std::to_string(static_cast<int64_t>(value)); return std::to_string(static_cast<int64_t>(value));
} }
std::ostringstream oss; std::ostringstream oss;
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() == '.')
return str; str.pop_back();
return str;
} }
std::string ListValue::to_string() const { 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 << elements[i]->to_string(); oss << ", ";
} oss << elements[i]->to_string();
oss << "]"; }
return oss.str(); oss << "]";
return oss.str();
} }
ValuePtr ListValue::clone() const { ValuePtr ListValue::clone() const {
std::vector<ValuePtr> cloned_elements; std::vector<ValuePtr> cloned_elements;
cloned_elements.reserve(elements.size()); cloned_elements.reserve(elements.size());
for (const auto& elem : elements) { for (const auto &elem : elements) {
cloned_elements.push_back(elem->clone()); cloned_elements.push_back(elem->clone());
} }
return std::make_shared<ListValue>(std::move(cloned_elements)); return std::make_shared<ListValue>(std::move(cloned_elements));
} }
ValuePtr ListValue::get(size_t index) const { ValuePtr ListValue::get(size_t index) const {
if (index >= elements.size()) { if (index >= elements.size()) {
return std::make_shared<NilValue>(); return std::make_shared<NilValue>();
} }
return elements[index]; return elements[index];
} }
void ListValue::set(size_t index, ValuePtr value) { void ListValue::set(size_t index, ValuePtr value) {
if (index >= elements.size()) { if (index >= elements.size()) {
elements.resize(index + 1, std::make_shared<NilValue>()); elements.resize(index + 1, std::make_shared<NilValue>());
} }
elements[index] = std::move(value); elements[index] = std::move(value);
} }
std::string MapValue::to_string() const { std::string MapValue::to_string() const {
std::ostringstream oss; std::ostringstream oss;
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)
first = false; oss << ", ";
oss << key << ": " << value->to_string(); first = false;
} oss << key << ": " << value->to_string();
oss << "}"; }
return oss.str(); oss << "}";
return oss.str();
} }
ValuePtr MapValue::clone() const { ValuePtr MapValue::clone() const {
std::map<std::string, ValuePtr> cloned_pairs; std::map<std::string, ValuePtr> cloned_pairs;
for (const auto& [key, value] : pairs) { for (const auto &[key, value] : pairs) {
cloned_pairs[key] = value->clone(); cloned_pairs[key] = value->clone();
} }
return std::make_shared<MapValue>(std::move(cloned_pairs)); return std::make_shared<MapValue>(std::move(cloned_pairs));
} }
ValuePtr MapValue::get(const std::string& key) const { ValuePtr MapValue::get(const std::string &key) const {
auto it = pairs.find(key); auto it = pairs.find(key);
if (it == pairs.end()) { if (it == pairs.end()) {
return std::make_shared<NilValue>(); return std::make_shared<NilValue>();
} }
return it->second; return it->second;
} }
ValuePtr ClassValue::clone() const { ValuePtr ClassValue::clone() const {
auto cloned = std::make_shared<ClassValue>(name); auto cloned = std::make_shared<ClassValue>(name);
cloned->fields = fields; cloned->fields = fields;
cloned->methods = methods; cloned->methods = methods;
return cloned; return cloned;
} }
ValuePtr InstanceValue::clone() const { ValuePtr InstanceValue::clone() const {
auto cloned = std::make_shared<InstanceValue>(klass); auto cloned = std::make_shared<InstanceValue>(klass);
for (const auto& [key, value] : fields) { for (const auto &[key, value] : fields) {
cloned->fields[key] = value->clone(); cloned->fields[key] = value->clone();
} }
return cloned; return cloned;
} }
ValuePtr InstanceValue::get(const std::string& name) const { ValuePtr InstanceValue::get(const std::string &name) const {
// First check fields // First check fields
auto field_it = fields.find(name); auto field_it = fields.find(name);
if (field_it != fields.end()) { if (field_it != fields.end()) {
return field_it->second; return field_it->second;
} }
// Then check methods and bind 'this' // Then check methods and bind 'this'
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()); return bound;
bound->bound_instance = self; }
return bound;
} return std::make_shared<NilValue>();
return std::make_shared<NilValue>();
} }
void InstanceValue::set(const std::string& name, ValuePtr value) { void InstanceValue::set(const std::string &name, ValuePtr value) {
if (fields.find(name) != fields.end()) { if (fields.find(name) != fields.end()) {
fields[name] = std::move(value); fields[name] = std::move(value);
} }
} }
} // namespace camellya } // namespace camellya

View File

@@ -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;

View File

@@ -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;
} }

View File

@@ -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

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);
}
}