# ChaiScript Versioning ChaiScript tries to follow the [Semantic Versioning](http://semver.org/) scheme. This basically means: * Major Version Number: API changes / breaking changes * Minor Version Number: New Features * Patch Version Number: Minor changes / enhancements # Initializing ChaiScript ``` chaiscript::ChaiScript chai; // initializes ChaiScript, adding the standard ChaiScript types (map, string, ...) ``` Note that ChaiScript cannot be used as a global / static object unless it is being compiled with `CHAISCRIPT_NO_THREADS`. # Adding Things To The Engine ## Adding a Function / Method / Member ### General ```cpp chai.add(chaiscript::fun(&function_name), "function_name"); chai.add(chaiscript::fun(&Class::method_name), "method_name"); chai.add(chaiscript::fun(&Class::member_name), "member_name"); ``` ### Bound Member Functions ```cpp chai.add(chaiscript::fun(&Class::method_name, Class_instance_ptr), "method_name"); chai.add(chaiscript::fun(&Class::member_name, Class_instance_ptr), "member_name"); ``` ### With Overloads #### Preferred ```cpp chai.add(chaiscript::fun(&function_with_overloads), "function_name"); ``` #### Alternative ```cpp chai.add(chaiscript::fun(static_cast(&function_with_overloads)), "function_name"); ``` This overload technique is also used when exposing base members using derived type ```cpp struct Base { int data; }; struct Derived : public Base {}; chai.add(chaiscript::fun(static_cast(&Derived::data)), "data"); ``` ### Lambda ```cpp chai.add( chaiscript::fun>( [](bool type) { if (type) { return "x"; } else { return "y"; } }), "function_name"); ``` ### Constructors ```cpp chai.add(chaiscript::constructor(), "MyType"); chai.add(chaiscript::constructor(), "MyType"); ``` ## Adding Types It's not strictly necessary to add types, but it helps with many things. Cloning, better errors, etc. ```cpp chai.add(chaiscript::user_type(), "MyClass"); ``` ## Adding Type Conversions User-defined type conversions are possible, defined in either script or in C++. ### ChaiScript Defined Conversions Function objects (including lambdas) can be used to add type conversions from inside of ChaiScript: ``` add_type_conversion(type("string"), type("Type_Info"), fun(s) { return type(s); }); ``` ### C++ Defined Conversions Invoking a C++ type conversion possible with `static_cast` ```cpp chai.add(chaiscript::type_conversion()); ``` Calling a user-defined type conversion that takes a lambda ```cpp chai.add(chaiscript::type_conversion([](const TestBaseType &t_bt) { /* return converted thing */ })); ``` ### Class Hierarchies If you want objects to be convertable between base and derived classes, you must tell ChaiScript about the relationship. ```cpp chai.add(chaiscript::base_class()); ``` If you have multiple classes in your inheritance graph, you will probably want to tell ChaiScript about all relationships. ```cpp chai.add(chaiscript::base_class()); chai.add(chaiscript::base_class()); chai.add(chaiscript::base_class()); ``` ### Helpers A helper function exists for strongly typed and ChaiScript `Vector` function conversion definition: ``` chai.add(chaiscript::vector_conversion>()); ``` A helper function also exists for strongly typed and ChaiScript `Map` function conversion definition: ``` chai.add(chaiscript::map_conversion>()); ``` This allows you to pass a ChaiScript function to a function requiring `std::vector` ## Adding Objects ``` chai.add(chaiscript::var(somevar), "somevar"); // copied in chai.add(chaiscript::var(std::ref(somevar)), "somevar"); // by reference, shared between C++ and chai auto shareddouble = std::make_shared(4.3); chai.add(chaiscript::var(shareddouble), "shareddouble"); // by shared_ptr, shared between c++ and chai chai.add(chaiscript::const_var(somevar), "somevar"); // copied in and made const chai.add_global_const(chaiscript::const_var(somevar), "somevar"); // global const. Throws if value is non-const, throws if object exists chai.add_global(chaiscript::var(somevar), "somevar"); // global non-const, throws if object exists chai.set_global(chaiscript::var(somevar), "somevar"); // global non-const, overwrites existing object ``` ## Adding Namespaces Namespaces will not be populated until `import` is called. This saves memory and computing costs if a namespace is not imported into every ChaiScript instance. ```cpp chai.register_namespace([](chaiscript::Namespace& math) { math["pi"] = chaiscript::const_var(3.14159); math["sin"] = chaiscript::var(chaiscript::fun([](const double x) { return sin(x); })); }, "math"); ``` Import namespace in ChaiScript ``` import("math") print(math.pi) // prints 3.14159 ``` # Using STL ChaiScript recognizes many types from STL, but you have to add specific instantiation yourself. ```cpp typedef std::vector> data_list; data_list my_list{ make_pair(0, "Hello"), make_pair(1, "World") }; chai.add(chaiscript::bootstrap::standard_library::vector_type("DataList")); chai.add(chaiscript::bootstrap::standard_library::pair_type("DataElement")); chai.add(chaiscript::var(&my_list), "data_list"); chai.eval(R"_( for(var i=0; i("5.3 + 2.1"); // returns 7.4 as a C++ double ``` ### Alternative ```cpp auto v = chai.eval("5.3 + 2.1"); chai.boxed_cast(v); // extracts double value from boxed_value and applies known conversions chaiscript::boxed_cast(v); // free function version, does not know about conversions ``` ### Converting Between Algebraic Types ```cpp chaiscript::Boxed_Number(chai.eval("5.3 + 2.1")).get_as(); // works with any number type // which is equivalent to, but much more automatic than: static_cast(chai.eval("5.3+2.1")); // this version only works if we know that it's a double ``` ### Conversion Caveats Conversion to `std::shared_ptr &` is supported for function calls, but if you attempt to keep a reference to a `shared_ptr<>` you might invoke undefined behavior ```cpp // ok this is supported, you can register it with chaiscript engine void nullify_shared_ptr(std::shared_ptr &t) { t = nullptr } ``` ```cpp int main() { // do some stuff and create a chaiscript instance std::shared_ptr &ptr = chai.eval &>(somevalue); // DO NOT do this. Taking a non-const reference to a shared_ptr is not // supported and causes undefined behavior in the chaiscript engine } ``` ## Sharing Values ```cpp double &d = chai.eval("var i = 5.2; i"); // d is now a reference to i in the script std::shared_ptr d = chai.eval("var i = 5.2; i"); // same result but reference counted d = 3; chai.eval("print(i)"); // prints 3 ``` ## Catching Eval Errors ```cpp try { chai.eval("2.3 + \"String\""); } catch (const chaiscript::exception::eval_error &e) { std::cout << "Error\n" << e.pretty_print() << '\n'; } ``` ## Catching Errors Thrown From Script ```cpp try { chai.eval("throw(runtime_error(\"error\"))", chaiscript::exception_specification()); } catch (const double e) { } catch (int) { } catch (float) { } catch (const std::string &) { } catch (const std::exception &e) { // This is the one that will be called in the specific throw() above } ``` ## Sharing Functions ```cpp auto p = chai.eval>("to_string"); p(5); // calls chaiscript's 'to_string' function, returning std::string("5") ``` Note: backtick treats operators as normal functions ```cpp auto p = chai.eval>("`+`"); p(5, 6); // calls chaiscript's '+' function, returning 11 ``` ```cpp auto p = chai.eval>("fun(x,y) { to_string(x) + to_string(y); }"); p(3,4.2); // evaluates the lambda function, returning the string "34.2" to C++ ``` # Language Reference ## Variables ``` var i; // uninitialized variable, can take any value on first assignment; auto j; // equiv to var var k = 5; // initialized to 5 (integer) var l := k; // reference to k auto &m = k; // reference to k global g = 5; // creates a global variable. If global already exists, it is not re-added global g = 2; // global 'g' now equals 2 global g2; if (g2.is_var_undef()) { g2 = 4; } // only initialize g2 once, if global decl hit more than once GLOBAL g3; // all upper case version also accepted ``` ## Looping ``` // c-style for loops for (var i = 0; i < 100; ++i) { print(i); } ``` ``` // while while (some_condition()) { /* do something */ } ``` ``` // ranged for for (i : [1, 2, 3]) { print(i); } ``` Each of the loop styles can be broken using the `break` statement. For example: ``` while (some_condition()) { /* do something */ if (another_condition()) { break; } } ``` ## Conditionals ``` if (expression) { } ``` ``` // C++17-style init-if blocks // Value of 'statement' is scoped for entire `if` block if (statement; expression) { } ``` ## Switch Statements ``` chaiscript var myvalue = 2 switch (myvalue) { case (1) { print("My Value is 1"); break; } case (2) { print("My Value is 2"); break; } default { print("My Value is something else."; } } ``` ## Built-in Types There are a number of built-in types that are part of ChaiScript. ### Vectors and Maps ``` var v = [1,2,3u,4ll,"16", `+`]; // creates vector of heterogenous values var m = ["a":1, "b":2]; // map of string:value pairs // Add a value to the vector by value. v.push_back(123); // Add an object to the vector by reference. v.push_back_ref(m); ``` ### Numbers Floating point values default to `double` type and integers default to `int` type. All C++ suffixes such as `f`, `ll`, `u` as well as scientific notation are supported ``` 1.0 // double 1.0f // float 1.0l // long double 1 // int 1u // unsigned int 1ul // unsigned long 1ull // unsigned long long ``` Literals are automatically sized, just as in C++. For example: `10000000000` is > 32bits and the appropriate type is used to hold it on your platform. ## Functions Note that any type of ChaiScript function can be passed freely to C++ and automatically converted into a `std::function` object. ### General ``` def myfun(x, y) { x + y; } // last statement in body is the return value def myfun(x, y) { return x + y; } // equiv ``` ### Optionally Typed ``` def myfun(x, int y) { x + y; } // requires y to be an int ``` ### With Guards ``` def myfun(x, int y) : y > 5 { x - y; } // only called if y > 5 ``` ### Methods Methods and functions are mostly equivalent ``` def string::add(int y) { this + to_string(y); } def add(string s, int y) { s + to_string(y); } //equiv functionality // calling new function/method "a".add(1); // returns a1 add("a", 1); // returns a1, either calling syntax works with either def above ``` ### Lambdas ``` var l = fun(x) { x * 15; } l(2) // returns 30 var a = 13 var m = fun[a](x) { x * a; } m(3); // a was captured (by reference), returns 39 var n = bind(fun(x,y) { x * y; }, _, 10); n(2); // returns 20 ``` ## ChaiScript Defined Types (Classes) 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). ### 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 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 ``` ### Class Definition (Open Syntax) Equivalently, attributes and methods can be defined outside a block using the `TypeName::` prefix. ``` 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 = 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 All ChaiScript defined types and generic Dynamic_Object support dynamic parameters ``` var o = Dynamic_Object(); o.f = fun(x) { print(x); } o.f(3); // prints "3" ``` Implicit 'this' is allowed: ``` var o = Dynamic_Object(); o.x = 3; o.f = fun(y) { print(this.x + y); } o.f(10); // prints 13 ``` ## Namespaces Namespaces in ChaiScript are Dynamic Objects with global scope ``` namespace("math") // create a new namespace math.square = fun(x) { x * x } // add a function to the "math" namespace math.sum_squares = fun(x, y) { math.square(x) + math.square(y) } print(math.square(4)) // prints 16 print(math.sum_squares(2, 5)) // prints 29 ``` ### Option Explicit If you want to disable dynamic parameter definitions, you can `set_explicit`. ``` class My_Class { def My_Class() { this.set_explicit(true); this.x = 2; // this would fail with explicit set to true } }; ``` ## method_missing A function of the signature `method_missing(object, name, param1, param2, param3)` will be called if an appropriate method cannot be found ``` def method_missing(int i, string name, Vector v) { print("method_missing(${i}, ${name}), ${v.size()} params"); } 5.bob(1,2,3); // prints "method_missing(5, bob, 3 params)" ``` `method_missing` signature can be either 2 parameters or 3 parameters. If the signature contains two parameters it is treated as a property. If the property contains a function then additional parameters are passed to the contained function. If both a 2 parameter and a 3 parameter signature match, the 3 parameter function always wins. ## Context * `__LINE__` Current file line number * `__FILE__` Full path of current file * `__CLASS__` Name of current class * `__FUNC__` Name of current function # Built-in Functions ## Evaluation ``` eval("4 + 5") // dynamically eval script string and returns value of last statement eval_file("filename") // evals file and returns value of last statement use("filename") // evals file exactly once and returns value of last statement // if the file had already been 'used' nothing happens and undefined is returned ``` 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 * `to_json` converts a ChaiScript object (either a `Object` or one of map, vector, int, double, string) tree into its JSON string representation ## Extras ChaiScript itself does not provide a link to the math functions defined in ``. You can either add them yourself, or use the [ChaiScript_Extras](https://github.com/ChaiScript/ChaiScript_Extras) helper library. (Which also provides some additional string functions.)