C++ 的腳本語言:ChaiScript
ChaiScript 是一個可以方便的嵌在 C++ 程序里的腳本語言,相比于 V8(Google JavaScript)和 Lua 來說,它的用法要簡單得多。
ChaiScript 和 STL 一樣只有頭文件,缺點是編譯慢,而且因為大量使用模板,編譯就更慢。
說明:
-
本文示例代碼一律假定已經 using namespace chaiscript 。
-
本文已經有些年頭了,不代表 ChaiScript 最新特性。
函數
導出(expose)函數到 ChaiScript:
int echo (int i) {
return i;
}
ChaiScript chai;
chai.add(fun(&echo), "echo"); // 導出到ChaiScript
int i = chai.eval<int>("echo(1)"); // 在ChaiScript里調用這個函數
導出重載函數時,需要指定具體類型:
string echo(const string& s) {
return s;
}
ChaiScript chai;
chai.add(fun<int (int)>(&echo), "echo");
chai.add(fun<string (const string&)>(&echo), "echo");
int i = chai.eval<int>("echo(1)");
string s = chai.eval<string>("echo(\"string\")");
強類型
ChaiScript is very strongly typed and does not do any int to unsigned int conversions automatically.
http://chaiscript.com/node/126
給定:
unsigned int fac(unsigned int n) {
return n == 0 ? 1 : n * fac(n - 1);
}
chai.add(fun(&fac), "fac");
這樣調用是不行的:
unsigned int fac = chai.eval<unsigned int>("fac(3)"); // 錯!
會有 bad_boxed_cast 異常。應該在調用時顯式的轉成 unsigned int :
unsigned int fac = chai.eval<unsigned int>("fac(unsigned_int(3))");
或者,直接以 signed int 來導出這個函數:
chai.add(fun<int (int)>(&fac), "fac");
int fac = chai.eval<int>("fac(3)");
C++ 雖然也是強類型,但是允許 signed 和 unsigned 之間隱式轉換。如果 fac 函數只有 unsigned 實現, fac(3) 也能調用, 3 是 signed ( 3u 表示 unsigned ),但是 C++ 可以將它隱式轉換成 unsigned 。ChaiScript 則不行, signed 就是 signed , unsigned 就是 unsigned ,需要顯式轉換( 3u 這種寫法 ChaiScript 也不支持)。
對 ChaiScript 來說, int 和 double 都是 POD(plain object data)類型, bool 和 std::string 也是,完整的列表詳見 ChaiScript bootstrap(自舉,引導)的那段代碼。
數組
ChaiScript 提供的 Vector 類似于 STL 的 vector :
var v := Vector()
v.push_back(1)
v.push_back(2)
print(v) // [1, 2]
Vector 的構造和初始化可以簡化(類似于 Python 的列表):
var v := [1, 2]
在 STL 的 vector 和 ChaiScript 的 Vector 之間,是不可以“直接”轉換的:
vector<int> ints = chai.eval<vector<int> >("[1, 2, 3]"); // 錯!
這樣會有 bad_boxed_cast 異常。右邊 eval 返回的是 vector<Boxed_Value> ,不能自動轉成 vector<int> 。也可以簡單理解成:自動 Box 和 Unbox 只對 POD 類型有效。這是一種設計上的折中吧,作者是這樣解釋的:
There's no built in way to do conversions between typed vectors and vectors of Boxed_Value. We tried to implement that, but it introduced too much overhead in the code.
http://chaiscript.com/node/118
下面說說細節。首先是 eval 的結果可以引用:
vector<Boxed_Value>& ints = chai.eval<vector<Boxed_Value> >("[1, 2, 3]");
這就避免了一次拷貝構造。但是注意, Boxed_Value 里具體的數據是引用計數的:
class Boxed_Value {
private:
boost::shared_ptr<Data> m_data;
};
所以vector本身引用與否,不影響內部的數據。假如想在C++這邊改ChaiScript里的這個數組,也可以:
vector<Boxed_Value>& ints = chai.eval<vector<Boxed_Value> >("var a := [1, 2, 3]; a;"); // 聲明變量a方便后續訪問
ints[0].assign(Boxed_Value(4));
chai.eval("print(a[0])"); // 4
這里有兩點值得注意。首先,變量 ints 不用引用也可以達到相同效果,因為如前所述, Boxed_Value 內部的數據有引用計數。其次, assign 不能替換成 = :
ints[0] = Boxed_Value(4);
用 = ,之前的那個 Boxed_Value 就被替換掉了。
引用
下面這兩種寫法的差別在于,第一種多一次拷貝構造:
var a = Vector()
var a := Vector()
看兩個例子。
例一:
var a := Vector()
var b = a // 拷貝構造了b
b.push_back(1) // b改了,a仍為[]
例二:
var a := Vector()
var b := a // b引用a
b.push_back(1) // a和b都改了
發現一個 bug,用快捷方式創建的 Vector ,不能再被其他變量引用:
var a := [1, 2, 3] // 用快捷方式創建
var b := a
b.push_back(1) // Crash!
可見 ChaiScript 還有很多問題。不夠成熟是我對它最大的顧慮。
類
class Buffer {
public:
size_t LineCount() const { return lines_.size(); }
string& GetLine(size_t line_index) {
return lines_[line_index];
}
const string& GetLine(size_t line_index) const {
return lines_[line_index];
}
void AddLine(const string& line) {
lines_.push_back(line);
}
private:
vector<string> lines_;
};
chai.add(user_type<Buffer>(), "Buffer");
// Default constructor
chai.add(constructor<Buffer ()>(), "Buffer");
// Copy constructor
chai.add(constructor<Buffer (const Buffer &)>(), "Buffer");
chai.add(fun(&Buffer::AddLine), "AddLine");
chai.add(fun(&Buffer::LineCount), "LineCount");
成員變量
成員變量的導出方法和成員函數相同。
class Option {
public:
std::string cjk;
std::string file_encoding;
bool show_space;
bool show_number;
};
chai_->add(user_type<Option>(), "Option");
chai_->add(fun(&Option::cjk), "cjk");
chai_->add(fun(&Option::file_encoding), "file_encoding");
chai_->add(fun(&Option::show_number), "show_number");
chai_->add(fun(&Option::show_space), "show_space");
一些補充
傳值,傳引用
-
Basic data types are passed by value.
-
Class data types are passed by reference.
-
There is not specific syntax for one or the other.
-
Use wrapper classes for basic data types, if you want to pass them by reference (like Java.)
關于 use
如果導出一個變量,然后 eval 一個文件,這個文件又 use 了另一個文件,那么被 use 的那個文件是看不到這個變量的。
use 就相當于 C++ 的 include ,被 use 的文件一般定義了一些公共的函數和類。
來自: https://segmentfault.com/a/1190000005722500