C++ 的腳本語言:ChaiScript

a444878151 8年前發布 | 32K 次閱讀 C/C++開發 C/C++

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");

一些補充

傳值,傳引用

  1. Basic data types are passed by value.

  2. Class data types are passed by reference.

  3. There is not specific syntax for one or the other.

  4. 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

 

 本文由用戶 a444878151 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!