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.