ChaiScript is a scripting language that easily embeds into your existing C++ applications. It's built to be flexible and dynamic, yet still maintain the type-safety you expect as a C++ user. It can natively use classes, methods, and attributes, even if the class inherits functionality from a parent class.
As of 2.0, it also supports exceptions and automatic thread-safety.
The latest source (as well as the latest Subversion) is hosted at Google code. To grab the latest release, go here. For the more adventurous, you can get the latest source code here.
ChaiScript requires Boost (at least 1.34), which you can get here.
In GCC:
cd chaiscript ./g++ -Iinclude -I<path to Boost> src/main.cpp -o chaiscript_eval
In Visual Studio:
Open the msvc\chaiscript\chaiscript.sln VC++ solution file. Use the property manager to modify the "C++/General/Additional Include Directories" and "Linker/General/Additional Library Directories" to reflect your local configuration.
Right-click on "chaiscript" and hit "compile" or just hit the "Start Debugging" "play" button to start the evaluator.
In Windows:
chaiscript_eval.exe eval>
In Linux and OS X:
./chaiscript_eval eval>
The evaluator allows you to evaluate ChaiScript expressions. This is a great way to get a feel for the language. You can also pass ChaiScript source files to the evaluator from the commandline to run them as standalone scripts.
The minimal ChaiScript project requires only a few lines:
#include <chaiscript/chaiscript.hpp> int main() { using namespace chaiscript; ChaiScript chai; chai.eval("print(\"Hello, world\")"); return 0; }
You can also grab values returned by executing ChaiScript. For example, if you wanted to compute a value, and return its answer, you can replace the above eval line with one like this:
int answer = chai.eval<int>("5+5");
Notice that we use the templated version so that what comes out of the script is type-safe in C++.
It's also possible to register your own functions so that your script can see them. For example:
#include <chaiscript/chaiscript.hpp> int add(int x, int y) { return x+y; } int main() { using namespace chaiscript; ChaiScript chai; chai.add(fun(&add), "add"); //register our 'add' function under the name "add" chai.eval("print(add(3, 4))"); return 0; }
For more a more involved example project, see example.cpp.
Some simple examples:
//Creating a vector of mixed types: var v = [1, 4, "Hi", 5.0] //And print them one per line by passing the 'print' function v.for_each(print);
//Creating a Map var m = ["a":1, "b":2, "c":"Hello World"] //Print the value referenced with the key "b" print(m["b"]) //Replacing the value with another value m["b"] = 5 //Print the key again print(m["b"])
For more examples, see the samples directory.
Continue on to the ChaiScript Reference or the C++ API Reference.
Assignment
Assignment in ChaiScript occurs by default as a copy operation.
var i = 5; var j = i; // Copy i into j
You can explicitly choose a reference assignment with the := operator.
var i = 5; var j := i; // Share the same value between i and j
Conditional Blocks
"If" blocks are supported, but be sure to note that "else if" is a single keyword instead of two separate words as it is in C++:
var i = 2 if (i < 2) { print("less than 2") } else if (i == 2) { print("equal to 2") } else { print("more than 2") }
Functions
ChaiScript allows two kinds of function definitions. The first, static definitions, use the keyword "def" and are globally visible:
def add_elems(x, y) { x + y } print(add_elems(5, 10))
Function may also have guards, expressions which prevent or allow execution. For example, let's say we want to print the proper pluralization next to the number of items we have in a vector:
def print_count(x) : x.size() == 1 { print("You have " + x.size().to_string() + " item") } def print_count(x) { print("You have " + x.size().to_string() + " items") } print_count(["bob"]) // prints "You have 1 item" print_count(["bob", "fred", "sally"]) //prints "You have 3 items"
Anonymous functions (aka lambdas) create a function as a value in your code, that can be passed in a variable.
var add_elems = fun(x, y) { x + y } print(add_elems(5, 10))
Return values
ChaiScript return values come in two types: explicit and inferred. Explicit return values work identically to C++:
def five() { return 5 } print(five())
When there is no explicit return statement, the last value referenced in a function is used. This code sample produces the same result as the above:
def five() { 5 } print(five())
Looping
ChaiScript allows C++ style loops in two flavors:
For loops:
for (var i = 0; i < 10; ++i) { print("i: " + i.to_string()) }
While loops:
var i = 0 while (i < 10) { print("i: " + i.to_string()) ++i }
Loops can be broken with the 'break' keyword, which works similarly to C++, where flow breaks out of the inner-most loop to the next outer loop.
Methods
ChaiScript uses a "syntactic sugar" technique for methods which allow functions to be called as methods. For example, the following are equivalent:
5.to_string() to_string(5)
The value on the left of the dot is used as the first parameter to the named function.
Containers
ChaiScript supports a handful of container types, allowing shortcuts for creating Vectors and Maps.
Vectors:
var x = [1, 2, 3] print(x[1])
Maps:
var x = ["bob":1, "fred":2] print(x["fred"])
Also available is a shortcut notation for creating a Vector from a range of values:
var x = [1..10] print(x)
Exception Blocks
ChaiScript offers the ability to capture and throw exceptions.
try { //dangerous block of code } catch (e) { //do something with 'e', our exception } finally { //do something after everything else has been done }
Throwing an exception is just like any other function call, but we use 'throw' as the name of the function:
throw(3)
Objects
For more information on how ChaiScript can extend C++ classes and build new types, read here.
Introduction
The Vector, in some languages also called an 'array', is a simple container of values of any type. In ChaiScript, "Vector" is an exposing of the std::vector<>.
Creating Vectors
There are two main ways to create a Vector. The first is to call the "Vector()" function, like so:
var v = Vector()
You may also use a shorter syntax, which is probably familiar to users of other scripting languages:
var v = []
Using the shorter syntax, you can also give initial values for the Vector:
var v = [4, "bob", 3.5]
Using Vectors
The easiest way to read or write to Vectors is through direct access:
var v = [4, 5, 6] v[1] = 10 print(v[1])
Additional function/methods are also available:
For an example usage of these techniques see the file example.cpp
chaiscript::ChaiScript chai;
To Utilize the "add" member, you need to specify the type of object you want to add:
Given the following code:
class MyClass { public: MyClass(int t_value) : m_value(t_value) {} int get_value() { return m_value; } void multiply_value(int v) { m_value *= v; } std::string data; private: int m_value; }; std::string concat(const std::string &first, const std::string &second) { return first + second; }
We can fully expose this set of functions to ChaiScript with just a few lines of C++:
chai.add(fun(&MyClass::get_value), "get_value"); chai.add(fun(&MyClass::multiply_value), "multiply_value"); // the data member can be added like other class members chai.add(fun(&MyClass::data), "data"); //"fun"'s can also be implicitly created using the "constructor" function //for exposing class constructors chai.add(constructor<MyClass (int)>(), "MyClass"); //Exposing a free function is the same: chai.add(fun(&concat), "my_concat"); //Also, a boost::function can be added chai.add(fun(boost::function<std::string (const std::string)>(boost::bind(&concat, "Hello ", _1))), "my_bound_concat");
Additionally, "fun" overloads exist which automatically bind in up to 2 parameters in at function creation time. These overloads are helpful when wanting to add bound object methods.
class MyClass { public: MyClass(int t_val) : m_value(t_val) {} int add_value(int i) { return m_val + i; } private: int t_val }; MyClass object(10); chai.add(fun(&MyClass::add_value, &object), "add_value"); // Add "add_value" which is bound to the "object" variable.
boost::shared_ptr<>, boost::ref, pointer or data. Note, if you add a variable as data, the value is copied into the system, and the variable is not shared with ChaiScript.
Examples:
int i = 5; chai.add(var(i), "i"); // Add a copy of i, and call it "i"
The above example adds a copy of a local variable to the ChaiScript system. The safest way to share data (as opposed to copying) is with a boost::shared_ptr<>.
boost::shared_ptr<MyClass> myclass(new MyClass(5)); chai.add(var(myclass), "myclass"); // Add our shared MyClass object to the system
Options that are not as safe for sharing data include the use of pointers or references, seen below:
// This option is a little dangerous, if the local i were to go out of scope, // the pointer would become invalidated int i = 5; chai.add(var(&i), "i"); // Add a pointer to i.
// This option is dangerous in the same way that the pointer version is std::string s = "hello world"; chai.add(var(boost::ref(s)), "str"); // Add a boost::reference to our string
int i = 5; chai.add(const_var(&i), "consti"); // Add a const pointer to i
User_type is required if you want your type to be support by the "clone" and "new" functions in ChaiScript. "Clone" and "new" do a lookup of the typename of an object to determine how to call its constructor.
That is, if you want your type to be fully supported by the ChaiScript system, you need to add a default constructor, a copy constructor and a user_type entry all with the same exact name.
chai.add(user_type<MyClass>(), "MyClass");
The two most notable examples are for a constructor adding helper and for bootstrapping of STL types.
//Add default and copy constructors for our type chai.add(bootstrap::basic_constructors<MyClass>("MyClass"));
//Add a new type, a vector of our MyClass chai.add(bootstrap::vector_type<std::vector<MyClass> >("MyClassVec"));
//Add a new global const value chai.add(const_var(1), "myconstint");
chai.eval("var j = 5+7; print(j)");
We can also type-safely handle C++ return values from our ChaiScript:
int i = chai.eval<int>("5+7");
Building on the previous example, we see that we can call our MyClass constructor, and share the result with C++:
boost::shared_ptr<MyClass> obj = chai.eval<boost::shared_ptr<MyClass> >("var obj = MyClass(2)"); obj.multiply_value(3); chai.eval("print(obj.get_value())"); chai.eval("obj.multiply_value(2)"); std::cout << obj.get_value() << std::endl;
A shortcut to using "eval" is the () operator, which does not perform any runtime type checking:
chai("obj.multiply_value(18)");
Also, we can use the eval (or the () shortcut) to handle return values in a generic fashion using Boxed_Value.
Boxed_Value bv = chai("MyClass()"); // construct a MyClass in a generic factory-style way
boost::function<void ()> print_value = chai.functor<void ()>("fun() { print(obj.get_value()) }"); print_value(); boost::function<int (int, int)> int_max = chai.functor<int (int, int)>("fun(x, y) { if (x>y) {x} else {y} }"); int m = int_max(6,7); //Note, the returned boost::function need not necessarily be stored in a local //variable. An interesting shortcut would be to do: int s = chai.functor<void (int, int)>("fun(x, y) { x+y }")(4,5); //The above technique is particularly handy when you need to substitute //in C++ variables into your executed ChaiScript code.
Get_state and set_state allow the user to save and restore the current state of the ChaiScript system. The currently loaded set of of functions, variables, types nad modules is saved and restored, so the developer can revert to a previous system state. The functions operate on chaiscript::ChaiScript::State objects.
ChaiScript::State state = chai.get_state(); chai.set_state(state);
Boxed_Value bv = chai("MyClass()"); //Various ways of extracting type safe data from a Boxed_Value boost::shared_ptr<MyClass> o1 = boxed_cast<boost::shared_ptr<MyClass> >(bv); MyClass &o2 = boxed_cast<MyClass &>(bv); MyClass o3 = boxed_cast<MyClass>(bv); // This version makes a copy MyClass *o4 = boxed_cast<MyClass *>(bv); //If we try a boxed_cast that is invalid a bad_boxed_cast is thrown try { int i = boxed_cast<int>(bv); } catch (const bad_boxed_cast &) { // failure in boxed_cast }
The functor free function differs from ChaiScript::functor because it expects a chaiscript::Proxy_Function object. This can be used for generating C++ callbacks from ChaiScript code.
struct Callback_Handler { std::vector<boost::function<int (const std::string &)> > m_funcs; void add(const chaiscript::Proxy_Function &f) { //Create a boost::function<> from the passed in Proxy_Function //and add it to our vector of chaiscript m_funcs.push_back(chaiscript::functor<int (const std::string &)>(f)); } }; int main() { chaiscript::ChaiScript chai; chai.add(fun(&Callback_Handler::add), "add"); Callback_Handler ch; chai.add(var(&ch), "callback_handler"); //Create a chaiscript lambda function and add it to the list //list of callbacks chai.eval("challback_handler.add(fun(x) { x.size(); })"); }
example.cpp has a more robust example of using functor to support ChaiScript callbacks.
C++ operators are just like any other function pointer, so adding them can be as simple as
chai.add(fun(&MyClass::operator+), "+");
Helper functions for adding conanical forms of opreators are provided that optionally take a ModulePtr and return a ModulePtr.
chai.add(operators::less_than<MyClass>());
The full list of supported operators:
assign<>() =assign_bitwise_and<>() &=assign_xor<>() ^=assign_bitwise_or<>() |=assign_difference<>() -=assign_left_shift<>() <<=assign_product<>() *=assign_quotient<>() /=assign_remainder<>() %=assign_right_shift<>() >>=assign_sum<>() +=prefix_decrement<>() --prefix_increment<>() ++equal<>() ==greater_than<>() >greater_than_equal<>() >=less_than<>() <less_than_equal<>() <=logical_compliment<>() !not_equal<>() !=addition<>() +unary_plus<>() +subtraction<>() -unary_minus<>() -bitwise_and<>() &bitwise_compliment<>() ~bitwise_xor<>() ^bitwise_or<>() |division<>() /left_shift<>() <<multiplication<>() *remainder<>() %right_shift<>() >>ChaiScript, once parsed, is executed top to bottom in a single pass. Definitions and statements have the same precedence.
Recommendation: ChaiScript source files have a .chai extension.
Comments follow the same format as C++ comments.
/* This is a multi-line comment. */ //This is a single line comment.
If Block ::= "if" "(" condition ")" block
Else If Block ::= "else if" "(" condition ")" block
Else Block ::= "else" block
While Block ::= "while" "(" condition ")" block
This loop can be broken using the break command.
For Block ::= "for" "(" [initial] ";" stop_condition ";" loop_expression ")" block
This loop can be broken using the break command.
Try Block ::= "try" block ("catch" ["(" variable ")"] [":" guards] block)+ ["finally" block]
Function Definition ::= [annotation + CR/LF] "def" identifier "(" [arg ("," arg)*] ")" [":" guard] block
annotation: meta-annotation on function, currently used as documentation. Optional.
identifier: name of function. Required.
args: comma-delimited list of parameter names. Optional.
guards: guarding statement that act as a prerequisite for the function. Optional.
{ }: scoped block as function body. Required.
Functions return values in one of two ways:
return call, optionally passing the value to be returned.Lambda ::= "fun" "(" [variable] ("," variable)* ")" block
Creates an anonymous function (sometimes called a lambda).
In-place Vector ::= "[" [expression ("," expression)*] "]"
In-place Ranged Vector ::= "[" value ".." value "]"
Creates a vector over a range (eg. 1..10)
In-place Map ::= "[" (string ":" expression)+ "]"
You may extend C++ classes that you have previously registered with the ChaiScript engine using the method definition syntax:
Method Definition ::= class_name "::" method_name "(" [arg ("," arg)*] ")" block
Using the same method definition syntax you can create a constructor and add methods to a new type. For example:
def MyClass::MyClass() { }
This creates a constructor for the 'MyClass' type, which, if it doesn't already exist, will now be visible to your script. To instantiate a value of 'MyClass', call its constructor directly:
var myobject = MyClass()
Attribute Definition ::= "attr" class_name "::" attribute_name
In addition to extending your new type with methods, you may also add attributes.
Extended example:
attr Rectangle::height attr Rectangle::width def Rectangle::Rectangle() { this.height = 10; this.width = 20 } def Rectangle::area() { this.height * this.width } var rect = Rectangle() rect.height = 30 print(rect.area())
ChaiScript, at its core, has some very functional programming-inspired habits. Few places show this off as clearly as the prelude, itself a name taken as a nod to the popular functional language Haskell. This prelude is available to all standard ChaiScript applications, and provides a simple foundation for using numbers, strings, and ranges (the general category of containers and their iteration).
to_string(x): Converts x into a string.
eval> to_string(3).is_type("string") true
puts(x): Prints x to the terminal, without a trailing carriage return.
eval> puts("hi, "); puts("there") hi, there
print(x): Prints x to the terminal, with a trailing carriage return.
eval> print("hello") hello
find(str, substr): Finds the first instance of substr in str.
eval> find("abab", "ab") 0
rfind(str, substr): Finds the last instance of substr in str.
eval> rfind("abab", "ab") 2
find_first_of(str, list): Finds the first of characters in list in the str string.
eval> find_first_of("abab", "bec") 1
find_last_of(str, list): Finds the last of characters in list in the str string.
eval> find_last_of("abab", "bec") 3
find_first_not_of(str, list): Finds the first non-matching character to list in the str string.
eval> find_first_not_of("abcd", "fec") 0
find_last_not_of(str, list): Finds the last non-matching character to list in the str string.
eval> find_last_not_of("abcd", "fec") 3
ltrim(str): Removes whitespace from the front of the string.
eval> ltrim(" bob") bob
rtrim(str): Removes whitespace from the back of the string.
eval> rtrim("bob ") + "|" bob|
trim(str): Removes whitespace from the front and back of the string.
eval> trim(" bob ") + "|" bob|
max(a, b): Returns the maximum value of a or b.
eval> max(4, 10) 10
min(a, b): Returns the minimum value of a or b.
eval> min(4, 10) 4
even(x): Returns true if x is even, otherwise returns false.
eval> even(4) true
odd(x): Returns true if x is odd, otherwise returns false.
eval> odd(4) false
for_each(container, f): Applies the function f over each element in the container.
eval> for_each([1, 2, 3], print) 1 2 3
map(container, f): Applies f over each element in the container, joining all the results.
eval> map([1, 2, 3], odd) [true, false, true]
foldl(container, f, initial): Starts with the initial value and applies the function f to it and the first element of the container. The result is then applied to the second element, and so on until the elements are exhausted.
eval> foldl([1, 2, 3, 4], `+`, 0) 10
sum(container): Returns the sum total of the values in the container.
eval> sum([1, 2, 3, 4]) 10
product(container): Returns the product of the value in the container.
eval> product([1, 2, 3, 4]) 24
take(container, num): Takes num elements from the container, returning them.
eval> take([1, 2, 3, 4], 2) [1, 2]
take_while(container, f): Takes elements from the container that match function f, stopping at the first non-match, returning them as a new Vector.
eval> take_while([1, 2, 3], odd) [1]
drop(container, num): Drops num elements from the container, returning the remainder.
eval> drop([1, 2, 3, 4], 2) [3, 4]
drop_while(container, f): Drops elements from the container that match f, stopping at the first non-match, returning the remainder.
eval> drop_while([1, 2, 3], odd) [2, 3]
reduce(container, f): Similar to foldl, this takes the first two elements as its starting values for f. This assumes container has at least 2 elements.
eval> reduce([1, 2, 3, 4], `+`) 10
filter(container, f): Takes elements from container that match function f, return them.
eval> filter([1, 2, 3, 4], odd) [1, 3]
join(container, delim): Joins the elements of the container into a string, delimiting each with the delim string.
eval> join([1, 2, 3], "*") 1*2*3
reverse(container): Returns the contents of the container in reversed order.
eval> reverse([1, 2, 3, 4, 5, 6, 7]) [7, 6, 5, 4, 3, 2, 1]
generate_range(x, y): Generates a new Vector filled with values starting at x and ending with y.
eval> generate_range(1, 10) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
concat(x, y): Returns a new Vector with x and y concatenated.
eval> concat([1, 2, 3], [4, 5, 6]) [1, 2, 3, 4, 5, 6]
collate(x, y): Returns a new Vector with x and y as its values.
eval> collate(1, 2) [1, 2]
zip_with(f, x, y): Applies f to elements of x and y, returning a new Vector with the result of each application.
eval> zip_with(`+`, [1, 2, 3], [4, 5, 6]) [5, 7, 9]
zip(x, y): Collates elements of x and y, returning a new Vector with the result.
eval> zip([1, 2, 3], [4, 5, 6]) [[1, 4], [2, 5], [3, 6]]
Statements are formed by combining one or more of the following:
ChaiScript supports both ints and doubles.
Example int:
10Example doubles:
3.14, NaN, Infinity
Example booleans:
true, false
Any variable identifier that follows the C naming convention.
Example variable:
bob
Quoted string values.
Example string:
"test me"Strings accept the following escaped sequences:
| Sequence | Description |
|---|---|
| \b | Backspace |
| \f | Form feed |
| \n | Newline |
| \r | Carriage return |
| \t | Tab |
| \' | Single quote |
| \" | Double quote |
| \\ | Single backslash |
Strings also allow in-string evaluation, which allows you to evaluate simple expressions inside of the string. For example:
var five = 5 "3 + 5 = ${3 + five}"
Single-Quoted char values.
Example char:
'b'Chars also accept the following escaped sequences:
| Sequence | Description |
|---|---|
| \b | Backspace |
| \f | Form feed |
| \n | Newline |
| \r | Carriage return |
| \t | Tab |
| \' | Single quote |
| \" | Double quote |
| \\ | Single backslash |
A special form of the operator that allows you to reference it as an identifier.
Example operator literal:
`+`Note: These may be used as first-class functions.
Example expression:
3 + 4 * 5
Mathematical expression following the order of precedence mentioned in "Operators".
Variable Declaration ::= "var" identifier
A variable name preceded by the keyword var, which declares the variable as an anonymous type. Once a variable has been given a type by assigning it to a value, the variable must maintain this type while it's in scope.
Equation ::= lvalue "=" rvalue
Takes a copy of what is on the right hand side and assigns it to the identifier named on the left hand side. For complex types this calls the "clone" method for that type.
Function Call ::= fun_name "(" [arg ("," arg)*] ")"
Calls the function pointed to by the lhs value.
Method Call ::= obj_name "." fun_name "(" [arg ("," arg)*] ")"
In ChaiScript the above notation is identical to:
fun_name "(" obj_name ("," arg)* ")"
Array Call ::= obj_name "[" key "]"
An array value lookup. In maps, 'key' is a string value, in vectors it is a numeric index.
Throw ::= "throw" "(" value ")"
The following are natively supported types in ChaiScript:
| Type | Example |
|---|---|
| int | 10 |
| double | 1.2 |
| bool | true |
| string | "hello" |
| char | 'b' |
| Vector | [1, 2] and [1..10] |
| Map | ["key":100] |
All C++ types are support (value, reference, and pointer types included). Additionally, values of boost::shared_ptr are also supported.