diff --git a/cheatsheet.md b/cheatsheet.md index 5835c36b..994fbd9d 100644 --- a/cheatsheet.md +++ b/cheatsheet.md @@ -593,6 +593,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 diff --git a/include/chaiscript/language/chaiscript_prelude_docs.hpp b/include/chaiscript/language/chaiscript_prelude_docs.hpp index c7fe40c7..6dc13797 100644 --- a/include/chaiscript/language/chaiscript_prelude_docs.hpp +++ b/include/chaiscript/language/chaiscript_prelude_docs.hpp @@ -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); diff --git a/unittests/reflection_documentation.chai b/unittests/reflection_documentation.chai new file mode 100644 index 00000000..db89dc2c --- /dev/null +++ b/unittests/reflection_documentation.chai @@ -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())