C++ 的脚本语言:ChaiScript

a444878151 8年前
   <p><a href="/misc/goto?guid=4959674443556339066" rel="nofollow,noindex">ChaiScript</a> 是一个可以方便的嵌在 C++ 程序里的脚本语言,相比于 V8(Google JavaScript)和 Lua 来说,它的用法要简单得多。</p>    <p>ChaiScript 和 STL 一样只有头文件,缺点是编译慢,而且因为大量使用模板,编译就更慢。</p>    <p>说明:</p>    <ul>     <li> <p>本文示例代码一律假定已经 using namespace chaiscript 。</p> </li>     <li> <p>本文已经有些年头了,不代表 ChaiScript 最新特性。</p> </li>    </ul>    <h2>函数</h2>    <p>导出(expose)函数到 ChaiScript:</p>    <pre>  <code class="language-cpp">int echo (int i) {    return i;  }    ChaiScript chai;  chai.add(fun(&echo), "echo");  // 导出到ChaiScript  int i = chai.eval<int>("echo(1)");  // 在ChaiScript里调用这个函数  </code></pre>    <p>导出重载函数时,需要指定具体类型:</p>    <pre>  <code class="language-cpp">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\")");  </code></pre>    <h2>强类型</h2>    <p>ChaiScript is very strongly typed and does not do any int to unsigned int conversions automatically.</p>    <p><a href="/misc/goto?guid=4959674443636437580" rel="nofollow,noindex">http://chaiscript.com/node/126</a></p>    <p>给定:</p>    <pre>  <code class="language-cpp">unsigned int fac(unsigned int n) {    return n == 0 ? 1 : n * fac(n - 1);  }  chai.add(fun(&fac), "fac");  </code></pre>    <p>这样调用是不行的:</p>    <pre>  <code class="language-cpp">unsigned int fac = chai.eval<unsigned int>("fac(3)"); // 错!  </code></pre>    <p>会有 bad_boxed_cast 异常。应该在调用时显式的转成 unsigned int :</p>    <pre>  <code class="language-cpp">unsigned int fac = chai.eval<unsigned int>("fac(unsigned_int(3))");  </code></pre>    <p>或者,直接以 signed int 来导出这个函数:</p>    <pre>  <code class="language-cpp">chai.add(fun<int (int)>(&fac), "fac");  int fac = chai.eval<int>("fac(3)");  </code></pre>    <p>C++ 虽然也是强类型,但是允许 signed 和 unsigned 之间隐式转换。如果 fac 函数只有 unsigned 实现, fac(3) 也能调用, 3 是 signed ( 3u 表示 unsigned ),但是 C++ 可以将它隐式转换成 unsigned 。ChaiScript 则不行, signed 就是 signed , unsigned 就是 unsigned ,需要显式转换( 3u 这种写法 ChaiScript 也不支持)。</p>    <p>对 ChaiScript 来说, int 和 double 都是 POD(plain object data)类型, bool 和 std::string 也是,完整的列表详见 ChaiScript bootstrap(自举,引导)的那段代码。</p>    <h2>数组</h2>    <p>ChaiScript 提供的 Vector 类似于 STL 的 vector :</p>    <pre>  <code class="language-cpp">var v := Vector()  v.push_back(1)  v.push_back(2)  print(v) // [1, 2]  </code></pre>    <p>Vector 的构造和初始化可以简化(类似于 Python 的列表):</p>    <pre>  <code class="language-cpp">var v := [1, 2]  </code></pre>    <p>在 STL 的 vector 和 ChaiScript 的 Vector 之间,是不可以“直接”转换的:</p>    <pre>  <code class="language-cpp">vector<int> ints = chai.eval<vector<int> >("[1, 2, 3]"); // 错!  </code></pre>    <p>这样会有 bad_boxed_cast 异常。右边 eval 返回的是 vector<Boxed_Value> ,不能自动转成 vector<int> 。也可以简单理解成:自动 Box 和 Unbox 只对 POD 类型有效。这是一种设计上的折中吧,作者是这样解释的:</p>    <p>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.</p>    <p><a href="/misc/goto?guid=4959674443719397166" rel="nofollow,noindex">http://chaiscript.com/node/118</a></p>    <p>下面说说细节。首先是 eval 的结果可以引用:</p>    <pre>  <code class="language-cpp">vector<Boxed_Value>& ints = chai.eval<vector<Boxed_Value> >("[1, 2, 3]");  </code></pre>    <p>这就避免了一次拷贝构造。但是注意, Boxed_Value 里具体的数据是引用计数的:</p>    <pre>  <code class="language-cpp">class Boxed_Value {  private:    boost::shared_ptr<Data> m_data;  };  </code></pre>    <p>所以vector本身引用与否,不影响内部的数据。假如想在C++这边改ChaiScript里的这个数组,也可以:</p>    <pre>  <code class="language-cpp">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  </code></pre>    <p>这里有两点值得注意。首先,变量 ints 不用引用也可以达到相同效果,因为如前所述, Boxed_Value 内部的数据有引用计数。其次, assign 不能替换成 = :</p>    <pre>  <code class="language-cpp">ints[0] = Boxed_Value(4);  </code></pre>    <p>用 = ,之前的那个 Boxed_Value 就被替换掉了。</p>    <h2>引用</h2>    <p>下面这两种写法的差别在于,第一种多一次拷贝构造:</p>    <pre>  <code class="language-cpp">var a = Vector()  var a := Vector()  </code></pre>    <p>看两个例子。</p>    <p>例一:</p>    <pre>  <code class="language-cpp">var a := Vector()  var b = a // 拷贝构造了b  b.push_back(1) // b改了,a仍为[]  </code></pre>    <p>例二:</p>    <pre>  <code class="language-cpp">var a := Vector()  var b := a // b引用a  b.push_back(1) // a和b都改了  </code></pre>    <p>发现一个 bug,用快捷方式创建的 Vector ,不能再被其他变量引用:</p>    <pre>  <code class="language-cpp">var a := [1, 2, 3] // 用快捷方式创建  var b := a  b.push_back(1) // Crash!  </code></pre>    <p>可见 ChaiScript 还有很多问题。不够成熟是我对它最大的顾虑。</p>    <h2>类</h2>    <pre>  <code class="language-cpp">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_;  };</code></pre>    <pre>  <code class="language-cpp">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");</code></pre>    <h2>成员变量</h2>    <p>成员变量的导出方法和成员函数相同。</p>    <pre>  <code class="language-cpp">class Option {  public:    std::string cjk;    std::string file_encoding;    bool show_space;    bool show_number;  };</code></pre>    <pre>  <code class="language-cpp">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");</code></pre>    <h2>一些补充</h2>    <h3>传值,传引用</h3>    <ol>     <li> <p>Basic data types are passed by value.</p> </li>     <li> <p>Class data types are passed by reference.</p> </li>     <li> <p>There is not specific syntax for one or the other.</p> </li>     <li> <p>Use wrapper classes for basic data types, if you want to pass them by reference (like Java.)</p> </li>    </ol>    <h3>关于 use</h3>    <p>如果导出一个变量,然后 eval 一个文件,这个文件又 use 了另一个文件,那么被 use 的那个文件是看不到这个变量的。</p>    <p>use 就相当于 C++ 的 include ,被 use 的文件一般定义了一些公共的函数和类。</p>    <p> </p>    <p>来自: <a href="/misc/goto?guid=4959674443799787455" rel="nofollow">https://segmentfault.com/a/1190000005722500</a></p>    <p> </p>