ChaiScript: An Embedded Scripting Language Tailored for C++

In modern software development, embedded scripting languages are widely used to extend the functionality of applications, especially in scenarios requiring dynamic interaction. For C++ developers, ChaiScript is an excellent embedded scripting library that provides a simple and easy-to-use interface while seamlessly integrating with the C++ language.

What is ChaiScript?

ChaiScript is an embedded scripting language specifically designed for C++. It is similar to ECMAScript (i.e., JavaScript), but modified to better fit the features of C++. ChaiScript is a header-only library, meaning it does not require additional compilation steps; you only need to include the header files in your project. It supports C++14 and above, fully complying with modern C++ development requirements.

Main Features of ChaiScript

  • Easy to Use: ChaiScript has a concise and clear syntax, similar to JavaScript, making it easy to get started. It provides a rich set of built-in functions and operators, making script writing more intuitive.
  • Seamless Integration: ChaiScript integrates very closely with C++. It supports advanced features in C++ such as std::shared_ptr, function objects, exceptions, etc. Developers can easily expose C++ functions and objects to scripts, while scripts can also call C++ code.
  • Cross-Platform Support: ChaiScript supports multiple operating systems, including Windows, Linux, FreeBSD, OS X, etc. This means developers can use the same script code across different platforms without worrying about compatibility issues.
  • Thread Safety: By default, ChaiScript is thread-safe, making it very suitable for use in multi-threaded environments.

Code Example

Below is a simple example of using ChaiScript, demonstrating how to expose C++ functions to scripts and execute them:

#include <chaiscript/chaiscript.hpp>
#include <iostream>
#include <string>

// A simple C++ function that we will expose to ChaiScript
int add(int a, int b) {
    return a + b;
}

// Another function to print messages
void print_message(const std::string& message) {
    std::cout << "Message from script: " << message << std::endl;
}

// A simple class
class Person {
public:
    Person(const std::string& name, int age) : name_(name), age_(age) {}
    
    void introduce() const {
        std::cout << "Hello, my name is " << name_ << " and I'm " << age_ << " years old." << std::endl;
    }
    
    void setAge(int age) { age_ = age; }
    int getAge() const { return age_; }
    
    void setName(const std::string& name) { name_ = name; }
    std::string getName() const { return name_; }
    
private:
    std::string name_;
    int age_;
};

int main() {
    // Create ChaiScript engine
    chaiscript::ChaiScript chai;
    
    // Expose C++ functions to ChaiScript
    chai.add(chaiscript::fun(&add), "add");
    chai.add(chaiscript::fun(&print_message), "print_message");
    
    // Expose Person class to ChaiScript
    chai.add(chaiscript::user_type<Person>(), "Person");
    chai.add(chaiscript::constructor<Person(const std::string&, int)>(), "Person");
    chai.add(chaiscript::fun(&Person::introduce), "introduce");
    chai.add(chaiscript::fun(&Person::setAge), "setAge");
    chai.add(chaiscript::fun(&Person::getAge), "getAge");
    chai.add(chaiscript::fun(&Person::setName), "setName");
    chai.add(chaiscript::fun(&Person::getName), "getName");
    
    try {
        // Execute simple ChaiScript code
        chai.eval(R"(
            print_message("Hello from ChaiScript!");
            var result = add(5, 3);
            print_message("5 + 3 = " + to_string(result));
            
            // Create and use C++ object
            var person = Person("Alice", 30);
            person.introduce();
            person.setAge(31);
            print_message("After birthday: " + person.getName() + " is now " + to_string(person.getAge()));
        )");
        
        // Execute script from file
        // chai.eval_file("script.chai");
        
    } catch (const chaiscript::exception::eval_error& e) {
        std::cout << "ChaiScript error: " << e.what() << std::endl;
        std::cout << "Stack trace: " << e.get_call_stack().front() << std::endl;
    } catch (const std::exception& e) {
        std::cout << "Standard exception: " << e.what() << std::endl;
    }
    
    return 0;
}

More Advanced Example: Custom Types and Function Binding

#include <chaiscript/chaiscript.hpp>
#include <vector>
#include <iostream>

// A simple math library
namespace math {
    double multiply(double a, double b) {
        return a * b;
    }
    
    double power(double base, double exponent) {
        return std::pow(base, exponent);
    }
}

// A container class
class NumberContainer {
public:
    void addNumber(double num) {
        numbers.push_back(num);
    }
    
    double sum() const {
        double total = 0;
        for (double num : numbers) {
            total += num;
        }
        return total;
    }
    
    double average() const {
        if (numbers.empty()) return 0;
        return sum() / numbers.size();
    }
    
    void clear() {
        numbers.clear();
    }
    
private:
    std::vector<double> numbers;
};

int main() {
    chaiscript::ChaiScript chai;
    
    // Expose math functions
    chai.add(chaiscript::fun(&math::multiply), "multiply");
    chai.add(chaiscript::fun(&math::power), "power");
    
    // Expose NumberContainer class
    chai.add(chaiscript::user_type<NumberContainer>(), "NumberContainer");
    chai.add(chaiscript::constructor<NumberContainer()>(), "NumberContainer");
    chai.add(chaiscript::fun(&NumberContainer::addNumber), "addNumber");
    chai.add(chaiscript::fun(&NumberContainer::sum), "sum");
    chai.add(chaiscript::fun(&NumberContainer::average), "average");
    chai.add(chaiscript::fun(&NumberContainer::clear), "clear");
    
    try {
        // Execute more complex script
        chai.eval(R"(
            // Use math functions
            var product = multiply(6, 7);
            var power_result = power(2, 8);
            print("6 * 7 = " + to_string(product));
            print("2^8 = " + to_string(power_result));
            
            // Use container class
            var container = NumberContainer();
            container.addNumber(10.5);
            container.addNumber(20.3);
            container.addNumber(30.2);
            
            print("Sum: " + to_string(container.sum()));
            print("Average: " + to_string(container.average()));
            
            // Add more numbers
            for (var i = 0; i < 5; ++i) {
                container.addNumber(i * 2.5);
            }
            
            print("New sum: " + to_string(container.sum()));
            print("New average: " + to_string(container.average()));
            
            // Clear container
            container.clear();
            print("After clear, sum: " + to_string(container.sum()));
        )");
        
    } catch (const chaiscript::exception::eval_error& e) {
        std::cout << "ChaiScript error: " << e.pretty_print() << std::endl;
    }
    
    return 0;
}

Compiling and Running

To compile the above code, you need to install ChaiScript first. The easiest way is to install it via a package manager (like vcpkg or Conan) or directly obtain the header files from the GitHub repository.

Example configuration using CMake:

cmake_minimum_required(VERSION 3.10)
project(ChaiScriptExample)

set(CMAKE_CXX_STANDARD 14)

# Find ChaiScript (assuming it is installed)
find_package(ChaiScript REQUIRED)

add_executable(example main.cpp)
target_link_libraries(example ChaiScript::ChaiScript)

Or directly include the header files (if ChaiScript header files are in the project directory):

g++ -std=c++14 -I/path/to/chaiscript/include main.cpp -o example

Conclusion

ChaiScript provides C++ developers with a powerful and flexible scripting solution, particularly suitable for applications requiring runtime extension and customization. Its simple integration method, seamless interaction with C++, and thread safety features make it an excellent choice for embedded scripting languages.

Whether for game development, scientific computing, or applications requiring user-defined functionality, ChaiScript can provide robust scripting support while maintaining a high degree of consistency with the C++ codebase.

Leave a Comment