mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-07-31 00:37:02 +08:00
Merge develop into feature branch to resolve conflicts
Resolved conflict in unittests/compiled_tests.cpp where both branches added tests at the end of file (issue #146 tests and issue #421 tests). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
a9fa6657c0
225
cheatsheet.md
225
cheatsheet.md
@ -477,34 +477,109 @@ n(2); // returns 20
|
||||
|
||||
|
||||
|
||||
## ChaiScript Defined Types
|
||||
## ChaiScript Defined Types (Classes)
|
||||
|
||||
Define a type called "MyType" with one member value "a" and a getter
|
||||
ChaiScript supports user-defined types using the `class` keyword. Classes can have attributes,
|
||||
constructors, methods, guards, and operator overloads. There is no inheritance between
|
||||
ChaiScript-defined types, but C++ class hierarchies can be exposed (see *Class Hierarchies* above).
|
||||
|
||||
### Preferred
|
||||
### Class Definition (Block Syntax)
|
||||
|
||||
Define a type with attributes, a constructor, and methods inside a `class` block.
|
||||
The keywords `var`, `attr`, and `auto` are interchangeable for declaring attributes.
|
||||
|
||||
```
|
||||
class MyType {
|
||||
var value;
|
||||
def MyType() { this.value = "a"; }
|
||||
def get_value() { "Value Is: " + this.value; }
|
||||
};
|
||||
class Rectangle {
|
||||
var width
|
||||
var height
|
||||
def Rectangle(w, h) { this.width = w; this.height = h; }
|
||||
def Rectangle() { this.width = 0; this.height = 0; }
|
||||
def area() { this.width * this.height; }
|
||||
}
|
||||
|
||||
var r = Rectangle(3, 4)
|
||||
print(r.area()) // prints 12
|
||||
```
|
||||
|
||||
### Alternative
|
||||
### Class Definition (Open Syntax)
|
||||
|
||||
Equivalently, attributes and methods can be defined outside a block using the `TypeName::` prefix.
|
||||
|
||||
```
|
||||
attr MyType::value;
|
||||
def MyType::MyType() { this.value = "a"; }
|
||||
def MyType::get_value() { "Value Is: " + this.value; }
|
||||
attr Circle::radius
|
||||
def Circle::Circle(r) { this.radius = r; }
|
||||
def Circle::circumference() { 2.0 * 3.14159 * this.radius; }
|
||||
```
|
||||
|
||||
Methods can also be added to an existing class after its initial definition:
|
||||
|
||||
```
|
||||
def Rectangle::perimeter() { 2 * (this.width + this.height); }
|
||||
```
|
||||
|
||||
### Using
|
||||
|
||||
```
|
||||
var m = MyType(); // calls constructor
|
||||
print(m.get_value()); // prints "Value Is: a"
|
||||
print(get_value(m)); // prints "Value Is: a"
|
||||
var m = Rectangle(5, 10)
|
||||
print(m.area()) // prints 50 — method call syntax
|
||||
print(area(m)) // prints 50 — function call syntax (equivalent)
|
||||
```
|
||||
|
||||
### Constructor and Method Guards
|
||||
|
||||
Constructors and methods can have guard expressions (after `:`) that control which
|
||||
overload is selected at call time.
|
||||
|
||||
```
|
||||
class Clamped {
|
||||
var value
|
||||
def Clamped(x) : x >= 0 { this.value = x; }
|
||||
def Clamped(x) { this.value = 0; } // fallback when guard fails
|
||||
}
|
||||
|
||||
Clamped(5).value // 5
|
||||
Clamped(-3).value // 0
|
||||
|
||||
class Abs {
|
||||
var x
|
||||
def Abs(v) { this.x = v; }
|
||||
def get() : this.x >= 0 { this.x; }
|
||||
def get() { -this.x; }
|
||||
}
|
||||
```
|
||||
|
||||
### Operator Overloading
|
||||
|
||||
Operators can be overloaded on user-defined types using backtick-quoted operator names.
|
||||
|
||||
```
|
||||
class Vec2 {
|
||||
var x
|
||||
var y
|
||||
def Vec2(x, y) { this.x = x; this.y = y; }
|
||||
def `+`(other) { Vec2(this.x + other.x, this.y + other.y); }
|
||||
}
|
||||
|
||||
var v = Vec2(1, 2) + Vec2(3, 4) // v.x == 4, v.y == 6
|
||||
```
|
||||
|
||||
Operators can also be overloaded as free functions with guards:
|
||||
|
||||
```
|
||||
def `-`(a, b) : is_type(a, "Vec2") && is_type(b, "Vec2") {
|
||||
Vec2(a.x - b.x, a.y - b.y)
|
||||
}
|
||||
```
|
||||
|
||||
### Cloning Objects
|
||||
|
||||
Use `clone()` to create a deep copy of a ChaiScript-defined object.
|
||||
|
||||
```
|
||||
var original = Rectangle(10, 20)
|
||||
var copy = clone(original)
|
||||
copy.width = 99
|
||||
print(original.width) // still 10
|
||||
```
|
||||
|
||||
## Dynamic Objects
|
||||
@ -593,6 +668,126 @@ use("filename") // evals file exactly once and returns value of last statement
|
||||
|
||||
Both `use` and `eval_file` search the 'usepaths' passed to the ChaiScript constructor
|
||||
|
||||
## Reflection and Introspection
|
||||
|
||||
ChaiScript provides built-in reflection capabilities for inspecting types, functions, and objects at runtime.
|
||||
|
||||
### Type Inspection
|
||||
|
||||
```
|
||||
type_name(x) // returns the type name of a value as a string
|
||||
is_type(x, "typename") // returns true if x is of the named type
|
||||
type("typename") // returns a Type_Info object for the named type
|
||||
|
||||
// Examples
|
||||
type_name(1) // "int"
|
||||
type_name("hello") // "string"
|
||||
is_type(1, "int") // true
|
||||
is_type(1, "string") // false
|
||||
```
|
||||
|
||||
### Object Inspection Methods
|
||||
|
||||
Every object in ChaiScript supports these methods:
|
||||
|
||||
```
|
||||
x.get_type_info() // returns a Type_Info object for the value
|
||||
x.is_type("string") // returns true if x is of the named type
|
||||
x.is_type(string_type) // returns true if x matches the Type_Info
|
||||
x.is_var_const() // returns true if x is immutable
|
||||
x.is_var_null() // returns true if x is a null pointer
|
||||
x.is_var_pointer() // returns true if x is stored as a pointer
|
||||
x.is_var_reference() // returns true if x is stored as a reference
|
||||
x.is_var_undef() // returns true if x is undefined
|
||||
```
|
||||
|
||||
### Type_Info
|
||||
|
||||
`Type_Info` objects describe a type. You can get them via `type("typename")` or `x.get_type_info()`.
|
||||
|
||||
```
|
||||
var ti = type("int")
|
||||
ti.name() // ChaiScript registered name, e.g. "int"
|
||||
ti.cpp_name() // mangled C++ type name
|
||||
ti.cpp_bare_name() // C++ name without const/pointer/reference
|
||||
ti.bare_equal(other) // true if types match ignoring const/ptr/ref
|
||||
ti.is_type_const() // true if type is const
|
||||
ti.is_type_reference() // true if type is a reference
|
||||
ti.is_type_void() // true if type is void
|
||||
ti.is_type_undef() // true if type is undefined
|
||||
ti.is_type_pointer() // true if type is a pointer
|
||||
ti.is_type_arithmetic() // true if type is arithmetic (int, double, etc.)
|
||||
```
|
||||
|
||||
Built-in type constants are available: `int_type`, `double_type`, `string_type`, `bool_type`, `Object_type`, `Function_type`, `vector_type`, `map_type`.
|
||||
|
||||
### Function Introspection
|
||||
|
||||
Function objects support these introspection methods:
|
||||
|
||||
```
|
||||
f.get_arity() // number of parameters (-1 for variadic)
|
||||
f.get_param_types() // Vector of Type_Info (first element is return type)
|
||||
f.get_contained_functions() // Vector of overloaded functions (empty if not a conglomerate)
|
||||
f.has_guard() // true if the function has a guard condition
|
||||
f.get_guard() // returns the guard function (throws if none)
|
||||
f.get_annotation() // returns the annotation description
|
||||
f.call([param1, param2]) // call the function with a vector of parameters
|
||||
|
||||
// Examples
|
||||
def my_func(a, b) { return a + b; }
|
||||
my_func.get_arity() // 2
|
||||
my_func.has_guard() // false
|
||||
|
||||
def guarded(x) : x > 0 { return x; }
|
||||
guarded.has_guard() // true
|
||||
guarded.get_guard().get_arity() // 1
|
||||
|
||||
// Calling functions dynamically
|
||||
`+`.call([1, 2]) // 3
|
||||
```
|
||||
|
||||
### System Introspection
|
||||
|
||||
```
|
||||
get_functions() // returns a Map of all registered functions (name -> function)
|
||||
get_objects() // returns a Map of all scripting objects (name -> value)
|
||||
function_exists("f") // returns true if a function named "f" is registered
|
||||
call_exists(`f`, args) // returns true if f can be called with the given args
|
||||
dump_system() // prints all registered functions to stdout
|
||||
dump_object(x) // prints information about a value to stdout
|
||||
|
||||
// Examples
|
||||
var funcs = get_functions()
|
||||
funcs["print"] // the print function object
|
||||
function_exists("print") // true
|
||||
call_exists(`+`, 1, 2) // true
|
||||
```
|
||||
|
||||
### Dynamic_Object Reflection
|
||||
|
||||
ChaiScript-defined classes are Dynamic_Objects internally. They support:
|
||||
|
||||
```
|
||||
obj.get_type_name() // returns the ChaiScript class name (e.g. "MyClass")
|
||||
obj.get_attrs() // returns a Map of all attributes
|
||||
obj.has_attr("name") // returns true if the attribute exists
|
||||
obj.get_attr("name") // returns the value of the attribute
|
||||
obj.set_explicit(true) // disables dynamic attribute creation
|
||||
obj.is_explicit() // returns true if explicit mode is enabled
|
||||
|
||||
// Example
|
||||
class MyClass {
|
||||
var x
|
||||
def MyClass() { this.x = 10; }
|
||||
}
|
||||
var m = MyClass()
|
||||
m.get_type_name() // "MyClass"
|
||||
m.get_attrs() // map containing "x" -> 10
|
||||
type_name(m) // "Dynamic_Object" (the underlying C++ type)
|
||||
m.is_type("MyClass") // true (checks the ChaiScript class name)
|
||||
```
|
||||
|
||||
## JSON
|
||||
|
||||
* `from_json` converts a JSON string into its strongly typed (map, vector, int, double, string) representations
|
||||
|
||||
@ -1060,6 +1060,19 @@ namespace chaiscript {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sort more-derived Dynamic_Object types before base types so that
|
||||
// overridden methods in derived classes are tried first during dispatch
|
||||
const auto &lhs_dotn = lhs->dynamic_object_type_name();
|
||||
const auto &rhs_dotn = rhs->dynamic_object_type_name();
|
||||
if (!lhs_dotn.empty() && !rhs_dotn.empty() && lhs_dotn != rhs_dotn) {
|
||||
if (dispatch::Dynamic_Object::type_matches(lhs_dotn, rhs_dotn)) {
|
||||
return true; // lhs is derived from rhs, so lhs is more specific
|
||||
}
|
||||
if (dispatch::Dynamic_Object::type_matches(rhs_dotn, lhs_dotn)) {
|
||||
return false; // rhs is derived from lhs, so rhs is more specific
|
||||
}
|
||||
}
|
||||
|
||||
const auto &lhsparamtypes = lhs->get_param_types();
|
||||
const auto &rhsparamtypes = rhs->get_param_types();
|
||||
|
||||
|
||||
@ -44,6 +44,28 @@ namespace chaiscript {
|
||||
|
||||
Dynamic_Object() = default;
|
||||
|
||||
/// Register that derived_name inherits from base_name
|
||||
static void register_inheritance(const std::string &derived_name, const std::string &base_name) {
|
||||
inheritance_map()[derived_name] = base_name;
|
||||
}
|
||||
|
||||
/// Check if type_name is, or inherits from, base_name
|
||||
static bool type_matches(const std::string &type_name, const std::string &base_name) noexcept {
|
||||
if (type_name == base_name) {
|
||||
return true;
|
||||
}
|
||||
const auto &m = inheritance_map();
|
||||
auto it = m.find(type_name);
|
||||
while (it != m.end()) {
|
||||
if (it->second == base_name) {
|
||||
return true;
|
||||
}
|
||||
it = m.find(it->second);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool is_explicit() const noexcept { return m_option_explicit; }
|
||||
|
||||
void set_explicit(const bool t_explicit) noexcept { m_option_explicit = t_explicit; }
|
||||
@ -87,6 +109,11 @@ namespace chaiscript {
|
||||
std::map<std::string, Boxed_Value> get_attrs() const { return m_attrs; }
|
||||
|
||||
private:
|
||||
static std::map<std::string, std::string> &inheritance_map() {
|
||||
static std::map<std::string, std::string> s_map;
|
||||
return s_map;
|
||||
}
|
||||
|
||||
const std::string m_type_name = "";
|
||||
bool m_option_explicit = false;
|
||||
|
||||
|
||||
@ -72,6 +72,8 @@ namespace chaiscript {
|
||||
|
||||
bool is_attribute_function() const noexcept override { return m_is_attribute; }
|
||||
|
||||
const std::string &dynamic_object_type_name() const noexcept override { return m_type_name; }
|
||||
|
||||
bool call_match(const chaiscript::Function_Params &vals, const Type_Conversions_State &t_conversions) const noexcept override {
|
||||
if (dynamic_object_typename_match(vals, m_type_name, m_ti, t_conversions)) {
|
||||
return m_func->call_match(vals, t_conversions);
|
||||
@ -112,7 +114,7 @@ namespace chaiscript {
|
||||
if (bv.get_type_info().bare_equal(m_doti)) {
|
||||
try {
|
||||
const Dynamic_Object &d = boxed_cast<const Dynamic_Object &>(bv, &t_conversions);
|
||||
return name == "Dynamic_Object" || d.get_type_name() == name;
|
||||
return name == "Dynamic_Object" || Dynamic_Object::type_matches(d.get_type_name(), name);
|
||||
} catch (const std::bad_cast &) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -120,7 +120,7 @@ namespace chaiscript {
|
||||
if (bv.get_type_info().bare_equal(dynamic_object_type_info)) {
|
||||
try {
|
||||
const Dynamic_Object &d = boxed_cast<const Dynamic_Object &>(bv, &t_conversions);
|
||||
if (!(name == "Dynamic_Object" || d.get_type_name() == name)) {
|
||||
if (!(name == "Dynamic_Object" || Dynamic_Object::type_matches(d.get_type_name(), name))) {
|
||||
return std::make_pair(false, false);
|
||||
}
|
||||
} catch (const std::bad_cast &) {
|
||||
@ -233,6 +233,12 @@ namespace chaiscript {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the Dynamic_Object type name this function is bound to, or empty string if not a Dynamic_Object function
|
||||
virtual const std::string &dynamic_object_type_name() const noexcept {
|
||||
static const std::string empty;
|
||||
return empty;
|
||||
}
|
||||
|
||||
virtual bool compare_first_type(const Boxed_Value &bv, const Type_Conversions_State &t_conversions) const noexcept {
|
||||
/// TODO is m_types guaranteed to be at least 2??
|
||||
return compare_type_to_param(m_types[1], bv, t_conversions);
|
||||
|
||||
@ -534,6 +534,24 @@ namespace chaiscript {
|
||||
return chaiscript::make_shared<detail::Type_Conversion_Base, detail::Type_Conversion_Impl<decltype(func)>>(
|
||||
user_type<std::map<std::string, Boxed_Value>>(), user_type<To>(), func);
|
||||
}
|
||||
|
||||
template<typename Left, typename Right>
|
||||
Type_Conversion pair_conversion() {
|
||||
auto func = [](const Boxed_Value &t_bv) -> Boxed_Value {
|
||||
const std::pair<Boxed_Value, Boxed_Value> &from_pair
|
||||
= detail::Cast_Helper<const std::pair<Boxed_Value, Boxed_Value> &>::cast(t_bv, nullptr);
|
||||
|
||||
auto pair = std::make_pair(
|
||||
detail::Cast_Helper<Left>::cast(from_pair.first, nullptr),
|
||||
detail::Cast_Helper<Right>::cast(from_pair.second, nullptr)
|
||||
);
|
||||
|
||||
return Boxed_Value(std::move(pair));
|
||||
};
|
||||
|
||||
return chaiscript::make_shared<detail::Type_Conversion_Base, detail::Type_Conversion_Impl<decltype(func)>>(
|
||||
user_type<std::pair<Boxed_Value, Boxed_Value>>(), user_type<std::pair<Left, Right>>(), func);
|
||||
}
|
||||
} // namespace chaiscript
|
||||
|
||||
#endif
|
||||
|
||||
@ -220,15 +220,16 @@ namespace chaiscript {
|
||||
Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override {
|
||||
auto lhs = this->children[0]->eval(t_ss);
|
||||
auto rhs = this->children[1]->eval(t_ss);
|
||||
return do_oper(t_ss, m_oper, this->text, lhs, rhs);
|
||||
return do_oper(t_ss, m_oper, this->text, lhs, rhs, m_loc);
|
||||
}
|
||||
|
||||
protected:
|
||||
Boxed_Value do_oper(const chaiscript::detail::Dispatch_State &t_ss,
|
||||
// static and public so we can use this to process Switch_AST_Node case equality
|
||||
static Boxed_Value do_oper(const chaiscript::detail::Dispatch_State &t_ss,
|
||||
Operators::Opers t_oper,
|
||||
const std::string &t_oper_string,
|
||||
const Boxed_Value &t_lhs,
|
||||
const Boxed_Value &t_rhs) const {
|
||||
const Boxed_Value &t_rhs,
|
||||
std::atomic_uint_fast32_t &t_loc) {
|
||||
try {
|
||||
if (t_oper != Operators::Opers::invalid && t_lhs.get_type_info().is_arithmetic() && t_rhs.get_type_info().is_arithmetic()) {
|
||||
// If it's an arithmetic operation we want to short circuit dispatch
|
||||
@ -243,7 +244,7 @@ namespace chaiscript {
|
||||
chaiscript::eval::detail::Function_Push_Pop fpp(t_ss);
|
||||
std::array<Boxed_Value, 2> params{t_lhs, t_rhs};
|
||||
fpp.save_params(Function_Params(params));
|
||||
return t_ss->call_function(t_oper_string, m_loc, Function_Params(params), t_ss.conversions());
|
||||
return t_ss->call_function(t_oper_string, t_loc, Function_Params(params), t_ss.conversions());
|
||||
}
|
||||
} catch (const exception::dispatch_error &e) {
|
||||
throw exception::eval_error("Can not find appropriate '" + t_oper_string + "' operator.", e.parameters, e.functions, false, *t_ss);
|
||||
@ -827,11 +828,23 @@ namespace chaiscript {
|
||||
Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override {
|
||||
chaiscript::eval::detail::Scope_Push_Pop spp(t_ss);
|
||||
|
||||
const auto &class_name = this->children[0]->text;
|
||||
|
||||
/// \todo do this better
|
||||
// put class name in current scope so it can be looked up by the attrs and methods
|
||||
t_ss.add_object("_current_class_name", const_var(this->children[0]->text));
|
||||
t_ss.add_object("_current_class_name", const_var(class_name));
|
||||
|
||||
this->children[1]->eval(t_ss);
|
||||
const bool has_base_class = (this->children.size() == 3);
|
||||
const auto &block = has_base_class ? this->children[2] : this->children[1];
|
||||
|
||||
// Register inheritance before evaluating the class body so that
|
||||
// function dispatch ordering can account for the relationship
|
||||
if (has_base_class) {
|
||||
const auto &base_name = this->children[1]->text;
|
||||
dispatch::Dynamic_Object::register_inheritance(class_name, base_name);
|
||||
}
|
||||
|
||||
block->eval(t_ss);
|
||||
|
||||
return void_var();
|
||||
}
|
||||
@ -985,8 +998,7 @@ namespace chaiscript {
|
||||
if (this->children[currentCase]->identifier == AST_Node_Type::Case) {
|
||||
// This is a little odd, but because want to see both the switch and the case simultaneously, I do a downcast here.
|
||||
try {
|
||||
std::array<Boxed_Value, 2> p{match_value, this->children[currentCase]->children[0]->eval(t_ss)};
|
||||
if (hasMatched || boxed_cast<bool>(t_ss->call_function("==", m_loc, Function_Params{p}, t_ss.conversions()))) {
|
||||
if (hasMatched || boxed_cast<bool>(Binary_Operator_AST_Node<T>::do_oper(t_ss, Operators::Opers::equals, "==", match_value, this->children[currentCase]->children[0]->eval(t_ss), m_loc))) {
|
||||
this->children[currentCase]->eval(t_ss);
|
||||
hasMatched = true;
|
||||
}
|
||||
|
||||
@ -1908,6 +1908,13 @@ namespace chaiscript {
|
||||
|
||||
const auto class_name = m_match_stack.back()->text;
|
||||
|
||||
// Optionally parse ': BaseClassName' for inheritance
|
||||
if (Char(':')) {
|
||||
if (!Id(true)) {
|
||||
throw exception::eval_error("Missing base class name in definition", File_Position(m_position.line, m_position.col), *m_filename);
|
||||
}
|
||||
}
|
||||
|
||||
while (Eol()) {
|
||||
}
|
||||
|
||||
|
||||
@ -426,6 +426,9 @@ namespace ChaiScript_Language {
|
||||
/// \brief Returns true if the type is "void"
|
||||
bool is_type_void() const;
|
||||
|
||||
/// \brief Returns true if the type is an arithmetic type (int, double, etc.)
|
||||
bool is_type_arithmetic() const;
|
||||
|
||||
/// \brief Returns the ChaiScript registered name for the type if one exists.
|
||||
string name() const;
|
||||
};
|
||||
@ -764,6 +767,90 @@ namespace ChaiScript_Language {
|
||||
/// \endcode
|
||||
bool call_exists(Function f, ...);
|
||||
|
||||
/// \brief Returns the type name of the given object as a string
|
||||
///
|
||||
/// Example:
|
||||
/// \code
|
||||
/// eval> type_name(1)
|
||||
/// int
|
||||
/// eval> type_name("hello")
|
||||
/// string
|
||||
/// \endcode
|
||||
///
|
||||
/// \sa Type_Info::name()
|
||||
/// \sa Object::get_type_info()
|
||||
string type_name(Object o);
|
||||
|
||||
/// \brief Returns true if the object is of the named type
|
||||
///
|
||||
/// Example:
|
||||
/// \code
|
||||
/// eval> is_type(1, "int")
|
||||
/// true
|
||||
/// eval> is_type(1, "string")
|
||||
/// false
|
||||
/// \endcode
|
||||
///
|
||||
/// \sa Object::is_type()
|
||||
bool is_type(Object o, string type_name);
|
||||
|
||||
/// \brief Returns true if a function with the given name is registered
|
||||
///
|
||||
/// Example:
|
||||
/// \code
|
||||
/// eval> function_exists("print")
|
||||
/// true
|
||||
/// eval> function_exists("nonexistent")
|
||||
/// false
|
||||
/// \endcode
|
||||
bool function_exists(string name);
|
||||
|
||||
/// \brief Returns a Map of all registered functions, keyed by function name
|
||||
///
|
||||
/// Example:
|
||||
/// \code
|
||||
/// eval> var funcs = get_functions()
|
||||
/// eval> funcs["print"].get_arity()
|
||||
/// 1
|
||||
/// \endcode
|
||||
///
|
||||
/// \sa Function
|
||||
Map get_functions();
|
||||
|
||||
/// \brief Returns a Map of all scripting objects (variables), keyed by name
|
||||
///
|
||||
/// Example:
|
||||
/// \code
|
||||
/// eval> var x = 42
|
||||
/// eval> var objs = get_objects()
|
||||
/// eval> objs.count("x")
|
||||
/// 1
|
||||
/// \endcode
|
||||
Map get_objects();
|
||||
|
||||
/// \brief Returns a Type_Info for the named type
|
||||
///
|
||||
/// Example:
|
||||
/// \code
|
||||
/// eval> type("int").name()
|
||||
/// int
|
||||
/// \endcode
|
||||
///
|
||||
/// \param type_name The name of the type to look up
|
||||
/// \param throw_on_fail If true (default), throws if the type is not found
|
||||
/// \sa Type_Info
|
||||
Type_Info type(string type_name, bool throw_on_fail = true);
|
||||
|
||||
/// \brief Prints all registered functions to stdout
|
||||
///
|
||||
/// Useful for debugging. Outputs a list of all functions registered in the system.
|
||||
void dump_system();
|
||||
|
||||
/// \brief Prints information about the given object to stdout
|
||||
///
|
||||
/// Useful for debugging. Outputs the type and value of the object.
|
||||
void dump_object(Object o);
|
||||
|
||||
/// \brief Reverses a Range object so that the elements are accessed in reverse
|
||||
Range retro(Range);
|
||||
|
||||
|
||||
103
unittests/class_cheatsheet.chai
Normal file
103
unittests/class_cheatsheet.chai
Normal file
@ -0,0 +1,103 @@
|
||||
// Test for class features documented in the cheatsheet
|
||||
|
||||
// --- Class definition (preferred block syntax) ---
|
||||
class Rectangle {
|
||||
var width
|
||||
var height
|
||||
def Rectangle(w, h) { this.width = w; this.height = h; }
|
||||
def Rectangle() { this.width = 0; this.height = 0; }
|
||||
def area() { this.width * this.height; }
|
||||
}
|
||||
|
||||
auto r = Rectangle(3, 4)
|
||||
assert_equal(12, r.area())
|
||||
|
||||
// --- Default constructor ---
|
||||
auto r2 = Rectangle()
|
||||
assert_equal(0, r2.area())
|
||||
|
||||
// --- Alternative open syntax ---
|
||||
attr Circle::radius
|
||||
def Circle::Circle(r) { this.radius = r; }
|
||||
def Circle::circumference() { 2.0 * 3.14159 * this.radius; }
|
||||
|
||||
auto c = Circle(5.0)
|
||||
assert_equal(5.0, c.radius)
|
||||
|
||||
// --- attr, auto, var all work for attributes ---
|
||||
class AttrTest {
|
||||
attr a
|
||||
auto b
|
||||
var c
|
||||
def AttrTest() { this.a = 1; this.b = 2; this.c = 3; }
|
||||
}
|
||||
|
||||
auto at = AttrTest()
|
||||
assert_equal(1, at.a)
|
||||
assert_equal(2, at.b)
|
||||
assert_equal(3, at.c)
|
||||
|
||||
// --- Constructor guards ---
|
||||
class Clamped {
|
||||
var value
|
||||
def Clamped(x) : x >= 0 { this.value = x; }
|
||||
def Clamped(x) { this.value = 0; }
|
||||
}
|
||||
|
||||
assert_equal(5, Clamped(5).value)
|
||||
assert_equal(0, Clamped(-3).value)
|
||||
|
||||
// --- Method guards ---
|
||||
class Abs {
|
||||
var x
|
||||
def Abs(v) { this.x = v; }
|
||||
def get() : this.x >= 0 { this.x; }
|
||||
def get() { -this.x; }
|
||||
}
|
||||
|
||||
assert_equal(5, Abs(5).get())
|
||||
assert_equal(3, Abs(-3).get())
|
||||
|
||||
// --- Operator overloading ---
|
||||
class Vec2 {
|
||||
var x
|
||||
var y
|
||||
def Vec2(x, y) { this.x = x; this.y = y; }
|
||||
def `+`(other) { Vec2(this.x + other.x, this.y + other.y); }
|
||||
}
|
||||
|
||||
auto v = Vec2(1, 2) + Vec2(3, 4)
|
||||
assert_equal(4, v.x)
|
||||
assert_equal(6, v.y)
|
||||
|
||||
// --- Cloning objects ---
|
||||
auto r3 = Rectangle(10, 20)
|
||||
auto r4 = clone(r3)
|
||||
r4.width = 99
|
||||
assert_equal(10, r3.width)
|
||||
assert_equal(99, r4.width)
|
||||
|
||||
// --- Dynamic attributes ---
|
||||
auto r5 = Rectangle(1, 1)
|
||||
r5.color = "red"
|
||||
assert_equal("red", r5.color)
|
||||
|
||||
// --- Explicit mode ---
|
||||
class Strict {
|
||||
var x
|
||||
def Strict() {
|
||||
this.x = 0
|
||||
this.set_explicit(true)
|
||||
}
|
||||
}
|
||||
|
||||
auto s = Strict()
|
||||
assert_equal(0, s.x)
|
||||
assert_equal(true, s.is_explicit())
|
||||
|
||||
try {
|
||||
s.y = 10
|
||||
assert_equal(true, false) // should not reach here
|
||||
} catch(e) {
|
||||
// expected: cannot add dynamic attribute in explicit mode
|
||||
}
|
||||
137
unittests/class_inheritance.chai
Normal file
137
unittests/class_inheritance.chai
Normal file
@ -0,0 +1,137 @@
|
||||
|
||||
class Base
|
||||
{
|
||||
attr x
|
||||
|
||||
def Base()
|
||||
{
|
||||
this.x = 10
|
||||
}
|
||||
|
||||
def do_something()
|
||||
{
|
||||
return this.x * 2
|
||||
}
|
||||
}
|
||||
|
||||
class Derived : Base
|
||||
{
|
||||
attr y
|
||||
|
||||
def Derived()
|
||||
{
|
||||
this.x = 10
|
||||
this.y = 20
|
||||
}
|
||||
|
||||
def do_other()
|
||||
{
|
||||
return this.y * 3
|
||||
}
|
||||
}
|
||||
|
||||
// Test basic inheritance - derived can call base methods
|
||||
auto d = Derived()
|
||||
assert_equal(10, d.x)
|
||||
assert_equal(20, d.y)
|
||||
assert_equal(20, d.do_something())
|
||||
assert_equal(60, d.do_other())
|
||||
|
||||
// Test base class still works independently
|
||||
auto b = Base()
|
||||
assert_equal(10, b.x)
|
||||
assert_equal(20, b.do_something())
|
||||
|
||||
// Test method override
|
||||
class Derived2 : Base
|
||||
{
|
||||
def Derived2()
|
||||
{
|
||||
this.x = 5
|
||||
}
|
||||
|
||||
def do_something()
|
||||
{
|
||||
return this.x * 100
|
||||
}
|
||||
}
|
||||
|
||||
auto d2 = Derived2()
|
||||
assert_equal(500, d2.do_something())
|
||||
|
||||
// Test passing a derived object to an untyped free function
|
||||
def call_do_something_untyped(obj) {
|
||||
return obj.do_something()
|
||||
}
|
||||
|
||||
auto d3 = Derived()
|
||||
assert_equal(20, call_do_something_untyped(d3))
|
||||
assert_equal(20, call_do_something_untyped(Base()))
|
||||
|
||||
// Test typed functions: parameter declared as Base, accepts derived objects
|
||||
def call_do_something(Base obj) {
|
||||
return obj.do_something()
|
||||
}
|
||||
|
||||
assert_equal(20, call_do_something(Base()))
|
||||
assert_equal(20, call_do_something(d3))
|
||||
|
||||
// Test typed function accessing base attributes on a derived object
|
||||
def get_x(Base obj) {
|
||||
return obj.x
|
||||
}
|
||||
|
||||
assert_equal(10, get_x(d3))
|
||||
assert_equal(10, get_x(Base()))
|
||||
|
||||
// Test polymorphic dispatch through typed function: derived override is called
|
||||
auto d4 = Derived2()
|
||||
assert_equal(500, call_do_something(d4))
|
||||
|
||||
// Test mixing base and derived in a container, calling base methods
|
||||
var objects = [Base(), Derived(), Derived2()]
|
||||
assert_equal(20, objects[0].do_something())
|
||||
assert_equal(20, objects[1].do_something())
|
||||
assert_equal(500, objects[2].do_something())
|
||||
|
||||
// Test that derived objects still report correct type
|
||||
auto d5 = Derived()
|
||||
assert_true(d5.is_type("Derived"))
|
||||
|
||||
// Test multi-level inheritance
|
||||
class GrandChild : Derived
|
||||
{
|
||||
attr z
|
||||
|
||||
def GrandChild()
|
||||
{
|
||||
this.x = 1
|
||||
this.y = 2
|
||||
this.z = 3
|
||||
}
|
||||
|
||||
def do_grandchild()
|
||||
{
|
||||
return this.z * 4
|
||||
}
|
||||
}
|
||||
|
||||
auto gc = GrandChild()
|
||||
assert_equal(1, gc.x)
|
||||
assert_equal(2, gc.y)
|
||||
assert_equal(3, gc.z)
|
||||
assert_equal(2, gc.do_something()) // Base method
|
||||
assert_equal(6, gc.do_other()) // Derived method
|
||||
assert_equal(12, gc.do_grandchild()) // Own method
|
||||
|
||||
// Test passing grandchild to typed Base function (multi-level inheritance)
|
||||
assert_equal(2, call_do_something(gc))
|
||||
assert_equal(1, get_x(gc))
|
||||
|
||||
// Test typed function expecting mid-level type
|
||||
def call_do_other(Derived obj) {
|
||||
return obj.do_other()
|
||||
}
|
||||
|
||||
assert_equal(6, call_do_other(gc))
|
||||
assert_equal(60, call_do_other(Derived()))
|
||||
@ -866,6 +866,27 @@ TEST_CASE("Map conversions") {
|
||||
CHECK(c == 42);
|
||||
}
|
||||
|
||||
TEST_CASE("Pair conversions") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
chai.add(chaiscript::pair_conversion<std::string, std::string>());
|
||||
chai.add(chaiscript::pair_conversion<int, double>());
|
||||
|
||||
{
|
||||
const auto p = chai.eval<std::pair<std::string, std::string>>(R"cs(
|
||||
Pair("chai", "script");
|
||||
)cs");
|
||||
CHECK(p.first == std::string{ "chai" });
|
||||
CHECK(p.second == "script");
|
||||
}
|
||||
{
|
||||
const auto p = chai.eval<std::pair<int, double>>(R"cs(
|
||||
Pair(5, 3.14);
|
||||
)cs");
|
||||
CHECK(p.first == 5);
|
||||
CHECK(p.second == Approx(3.14));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Parse floats with non-posix locale") {
|
||||
#ifdef CHAISCRIPT_MSVC
|
||||
std::setlocale(LC_ALL, "en-ZA");
|
||||
@ -1409,3 +1430,97 @@ TEST_CASE("ChaiScript default has all functions") {
|
||||
CHECK_NOTHROW(chai.eval("var v = Vector()"));
|
||||
CHECK(chai.eval<int>("5 + 3") == 8);
|
||||
}
|
||||
|
||||
// Issue #421: Class with type_conversion from int and "==" operator
|
||||
// causes switch statement to compare destroyed objects.
|
||||
// The switch case comparison must use Function_Push_Pop to properly
|
||||
// manage the lifetime of temporaries created by type conversions.
|
||||
TEST_CASE("Issue #421 - Switch with type_conversion does not compare destroyed objects") {
|
||||
struct MyType {
|
||||
int value;
|
||||
explicit MyType(int v) : value(v) {}
|
||||
};
|
||||
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
chai.add(chaiscript::user_type<MyType>(), "MyType");
|
||||
chai.add(chaiscript::constructor<MyType(int)>(), "MyType");
|
||||
chai.add(chaiscript::fun(&MyType::value), "value");
|
||||
chai.add(chaiscript::fun([](const MyType &a, const MyType &b) { return a.value == b.value; }), "==");
|
||||
chai.add(chaiscript::type_conversion<int, MyType>([](const int &i) { return MyType(i); }));
|
||||
|
||||
// Test switch with type conversion - the case integer literals must be
|
||||
// converted to MyType via the registered conversion before comparison.
|
||||
// Without Function_Push_Pop, the converted temporaries may be destroyed
|
||||
// before the == operator can compare them.
|
||||
CHECK(chai.eval<int>(R"({
|
||||
var result = 0
|
||||
var obj = MyType(2)
|
||||
switch(obj) {
|
||||
case (1) {
|
||||
result = 1
|
||||
break
|
||||
}
|
||||
case (2) {
|
||||
result = 2
|
||||
break
|
||||
}
|
||||
case (3) {
|
||||
result = 3
|
||||
break
|
||||
}
|
||||
}
|
||||
result
|
||||
})") == 2);
|
||||
|
||||
// Test fall-through with type conversion
|
||||
CHECK(chai.eval<int>(R"({
|
||||
var total = 0
|
||||
var obj = MyType(2)
|
||||
switch(obj) {
|
||||
case (1) {
|
||||
total += 1
|
||||
}
|
||||
case (2) {
|
||||
total += 2
|
||||
}
|
||||
case (3) {
|
||||
total += 4
|
||||
}
|
||||
}
|
||||
total
|
||||
})") == 6);
|
||||
|
||||
// Test matching the first case
|
||||
CHECK(chai.eval<int>(R"({
|
||||
var result = 0
|
||||
var obj = MyType(1)
|
||||
switch(obj) {
|
||||
case (1) {
|
||||
result = 10
|
||||
break
|
||||
}
|
||||
case (2) {
|
||||
result = 20
|
||||
break
|
||||
}
|
||||
}
|
||||
result
|
||||
})") == 10);
|
||||
|
||||
// Test no match
|
||||
CHECK(chai.eval<int>(R"({
|
||||
var result = 0
|
||||
var obj = MyType(5)
|
||||
switch(obj) {
|
||||
case (1) {
|
||||
result = 1
|
||||
break
|
||||
}
|
||||
case (2) {
|
||||
result = 2
|
||||
break
|
||||
}
|
||||
}
|
||||
result
|
||||
})") == 0);
|
||||
}
|
||||
|
||||
123
unittests/reflection_documentation.chai
Normal file
123
unittests/reflection_documentation.chai
Normal file
@ -0,0 +1,123 @@
|
||||
|
||||
// Tests for ChaiScript reflection / introspection capabilities
|
||||
// Ensures all documented reflection functions work correctly
|
||||
|
||||
// --- Global reflection functions ---
|
||||
|
||||
// type_name: returns the type name of a value
|
||||
assert_equal("int", type_name(1))
|
||||
assert_equal("string", type_name("hello"))
|
||||
assert_equal("bool", type_name(true))
|
||||
assert_equal("double", type_name(1.0))
|
||||
|
||||
// is_type: checks if a value is of the given type
|
||||
assert_true(is_type(1, "int"))
|
||||
assert_true(is_type("hello", "string"))
|
||||
assert_false(is_type(1, "string"))
|
||||
|
||||
// function_exists: checks if a named function is registered
|
||||
assert_true(function_exists("print"))
|
||||
assert_true(function_exists("type_name"))
|
||||
assert_true(function_exists("function_exists"))
|
||||
assert_false(function_exists("this_function_does_not_exist_xyz"))
|
||||
|
||||
// get_functions: returns a Map of all registered functions
|
||||
var funcs = get_functions()
|
||||
assert_true(funcs.size() > 0)
|
||||
assert_true(funcs.count("print") > 0)
|
||||
assert_true(funcs.count("type_name") > 0)
|
||||
|
||||
// get_objects: returns a Map of all scripting objects
|
||||
var my_test_var = 42
|
||||
var objs = get_objects()
|
||||
assert_true(objs.size() > 0)
|
||||
assert_true(objs.count("my_test_var") > 0)
|
||||
|
||||
// type: returns a Type_Info for a named type
|
||||
var ti = type("int")
|
||||
assert_equal("int", ti.name())
|
||||
|
||||
// call_exists: checks if a function call with given params exists
|
||||
assert_true(call_exists(`+`, 1, 2))
|
||||
assert_true(call_exists(`+`, "a", "b"))
|
||||
|
||||
// --- Object methods ---
|
||||
|
||||
// get_type_info: returns Type_Info for a value
|
||||
var s = "hello"
|
||||
assert_equal("string", s.get_type_info().name())
|
||||
assert_equal("int", 1.get_type_info().name())
|
||||
|
||||
// is_type on objects
|
||||
assert_true("hello".is_type("string"))
|
||||
assert_true(1.is_type("int"))
|
||||
assert_false(1.is_type("string"))
|
||||
|
||||
// is_type with Type_Info
|
||||
assert_true("hello".is_type(string_type))
|
||||
assert_true(1.is_type(int_type))
|
||||
|
||||
// is_var_* methods
|
||||
var x = 5
|
||||
assert_false(x.is_var_const())
|
||||
assert_false(x.is_var_null())
|
||||
assert_false(x.is_var_undef())
|
||||
|
||||
// --- Type_Info methods ---
|
||||
var int_ti = type("int")
|
||||
assert_equal("int", int_ti.name())
|
||||
assert_false(int_ti.is_type_const())
|
||||
assert_false(int_ti.is_type_void())
|
||||
assert_false(int_ti.is_type_undef())
|
||||
assert_false(int_ti.is_type_reference())
|
||||
assert_false(int_ti.is_type_pointer())
|
||||
|
||||
// bare_equal: compares types ignoring const/pointer/reference
|
||||
assert_true(int_ti.bare_equal(1.get_type_info()))
|
||||
|
||||
// --- Function introspection ---
|
||||
|
||||
def my_reflection_test_func(a, b) { return a + b; }
|
||||
|
||||
// get_arity
|
||||
assert_equal(2, my_reflection_test_func.get_arity())
|
||||
|
||||
// get_param_types
|
||||
var param_types = my_reflection_test_func.get_param_types()
|
||||
assert_true(param_types.size() > 0)
|
||||
|
||||
// get_contained_functions
|
||||
assert_equal(0, my_reflection_test_func.get_contained_functions().size())
|
||||
|
||||
// has_guard
|
||||
assert_false(my_reflection_test_func.has_guard())
|
||||
|
||||
// Guarded function
|
||||
def my_guarded_func(x) : x > 0 { return x; }
|
||||
assert_true(my_guarded_func.has_guard())
|
||||
var g = my_guarded_func.get_guard()
|
||||
assert_equal(1, g.get_arity())
|
||||
|
||||
// call: invoke a function with a vector of parameters
|
||||
assert_equal(3, `+`.call([1, 2]))
|
||||
|
||||
// --- Dynamic_Object reflection ---
|
||||
var obj = Dynamic_Object()
|
||||
obj.name = "test"
|
||||
obj.value = 42
|
||||
var attrs = obj.get_attrs()
|
||||
assert_true(attrs.count("name") > 0)
|
||||
assert_true(attrs.count("value") > 0)
|
||||
|
||||
// --- Class reflection ---
|
||||
class ReflectionTestClass {
|
||||
var x
|
||||
def ReflectionTestClass() { this.x = 10; }
|
||||
def get_x() { return this.x; }
|
||||
}
|
||||
|
||||
var rtc = ReflectionTestClass()
|
||||
assert_equal("ReflectionTestClass", rtc.get_type_name())
|
||||
assert_true(rtc.is_type("ReflectionTestClass"))
|
||||
assert_equal("Dynamic_Object", type_name(rtc))
|
||||
assert_equal("ReflectionTestClass", rtc.get_type_name())
|
||||
50
unittests/switch_type_conversion.chai
Normal file
50
unittests/switch_type_conversion.chai
Normal file
@ -0,0 +1,50 @@
|
||||
// Test for issue #421: switch statement with custom == operator on
|
||||
// dynamic objects must properly manage object lifetimes during
|
||||
// case comparisons.
|
||||
|
||||
class MyType {
|
||||
var value
|
||||
def MyType(v) { this.value = v }
|
||||
}
|
||||
|
||||
def `==`(a, b) : a.is_type("MyType") && b.is_type("MyType") {
|
||||
return a.value == b.value
|
||||
}
|
||||
|
||||
var result = 0
|
||||
var obj = MyType(2)
|
||||
|
||||
switch(obj) {
|
||||
case (MyType(1)) {
|
||||
result = 1
|
||||
break
|
||||
}
|
||||
case (MyType(2)) {
|
||||
result = 2
|
||||
break
|
||||
}
|
||||
case (MyType(3)) {
|
||||
result = 3
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
assert_equal(result, 2)
|
||||
|
||||
// Also test fall-through with custom == operator
|
||||
var total = 0
|
||||
var obj2 = MyType(2)
|
||||
|
||||
switch(obj2) {
|
||||
case (MyType(1)) {
|
||||
total += 1
|
||||
}
|
||||
case (MyType(2)) {
|
||||
total += 2
|
||||
}
|
||||
case (MyType(3)) {
|
||||
total += 4
|
||||
}
|
||||
}
|
||||
|
||||
assert_equal(total, 6)
|
||||
Loading…
x
Reference in New Issue
Block a user