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
的檔案一般定義了一些公共的函式和類。