ChaiScript-C ++脚本语言

当需要在C ++项目中嵌入脚本语言时,大多数人记得的第一件事就是Lua。 在本文中,我不会再谈论另一种同样便捷且易学的语言,称为ChaiScript。

图片

简短介绍


当我观看语言的创建者之一Jason Turner的其中一场演讲时,我自己偶然发现了ChaiScript。 它引起了我的兴趣,当需要在项目中选择脚本语言时,我决定-为什么不尝试ChaiScript? 结果使我感到惊喜(我的个人经历将在本文结尾处写),但是,无论听起来多么奇怪,枢纽中都没有哪一篇文章以某种方式提到了这种语言,因此我决定写关于他的事会很高兴。 当然,该语言有文档和一个官方网站 ,但是并非所有人都会从观察中阅读该语言,并且文章的格式更接近于许多人(包括我)。

首先,我们将讨论该语言的语法及其所有功能,然后讨论如何在您的C ++项目中实现该语言,最后,我会谈一些我的经验。 如果您的某些部分不感兴趣,或者您想以不同的顺序阅读文章,则可以使用目录:



语言语法


ChaiScript的语法与C ++和JS非常相似。 首先,它像绝大多数脚本语言一样是动态类型的,但是,与JavaScript不同,它具有严格的类型(没有1 + "2" )。 还有一个内置的垃圾收集器,该语言是完全可解释的,允许您逐行执行代码,而无需编译为字节码。 它支持异常(此外,联合,允许您在脚本和C ++中捕获它们),lambda函数,运算符重载。 它对空格不敏感,允许您通过分号或python样式以单行形式编写,并用新行分隔表达式。

原始类型


默认情况下,ChaiScript将整数变量存储为int,实数作为double以及带有std :: string的字符串。 这样做主要是为了确保与调用代码的兼容性。 该语言甚至都有数字后缀,因此我们可以明确指出变量的类型:

 /*   chaiscript    js    ,  var / auto `;`      */ var myInt = 1 // int var myLongLong = 1ll // long long int var myFloating = 3.3 // double var myBoolean = false // bool var myString = "hello world!\n" // std::string 

更改变量的类型实际上是行不通的,很可能您需要为这些类型定义自己的`=`运算符,否则,您可能会抛出异常(稍后再讨论)或成为舍入的受害者,如下所示:

 var integer = 3 integer = 5.433 print(integer) //  5    double    int! integer = true //   -   `=`  (int, bool) 

但是,您可以在不给变量赋值的情况下声明变量,在这种情况下,变量将包含一种未定义的变量,直到为其分配了值。

内联容器


该语言有两个容器-矢量和地图。 它们的工作方式与C ++中的类似物非常相似(分别为std :: vector和std :: map),但是它们不需要类型,因为它们可以存储任何类型。 可以像往常一样使用int进行索引,但是Map需要带有字符串的键。 显然是受python启发的,作者还添加了使用以下语法在代码中快速声明容器的功能:

 var v = [ 1, 2, 3u, 4ll, "16", `+` ] //      var m = [ "key1" : 1, "key2": "Bob" ]; //    - var M = Map() //    var V = Vector() //    //        C++ : v.push_back(123) //    ,     v.push_back_ref(m); // m -   //      m["key"] = 3 //       (reference assignment): m["key"] := m //       

除了迭代器外,这两个类几乎都完全重复了C ++中的对应类,因为除了它们之外,还有特殊的类Range和Const_Range。 顺便说一句,即使您通过=使用分配,所有容器也都通过引用传递,这对我来说很奇怪,因为对于所有其他类型,都会发生按值复制。

条件构造


条件和周期的几乎所有构造都可以用字面上的一个示例代码来描述:

 var a = 5 var b = -1 //  if-else if (a > b) { print("a > b") } else if (a == b){ print("a == b") } else { print("a < b") } // switch -    if- //      //  break    ,    C++ var str = "hello" switch(str) { case("hi") { print("hi!"); break; } case("hello") { print("hello!" break; } case("bye") { print("bye-bye!") break; } default { print("what have you said?") } } var x = true //     ,       while (x) { print("x was true") x = false; } //    C.        ,    ,    ,    for (var i = 0; i < 10; ++i) //   -,    { print(i); //  0 ... 9  10  } // ranged-for loop for(element : [1, 2, 3, 4, 5]) { puts(element) //   12345 } //  :   C++17 if-init statements: if(var x = get_value(); x < 10) { print(x) // x     if } 

我认为熟悉C ++的人们没有发现任何新东西。 这并不奇怪,因为ChaiScript被定位为“学习者”学习的一种简便语言,因此借鉴了所有著名的古典设计。 如果您真的很喜欢auto的优点,作者决定甚至突出显示两个用于声明变量的关键字var和auto。

执行上下文


ChaiScript具有本地和全局上下文。 该代码从上到下逐行执行,但是可以在函数中取出并稍后调用(但不能更早!)。 默认情况下,从外部看不到在函数或条件/循环内部声明的变量,但是您可以使用global标识符而不是var更改此行为。 全局变量与普通变量的不同之处在于,首先,它们在局部上下文之外可见,其次,可以重新声明(如果在重新声明期间未设置该值,则它保持不变)

 //     chaiscript def foo(x) { global G = 2 print(x) } foo(0) //  foo(x), G = 2 print(G) //  2 global G = 3 //  G = 3,   global -  ! 

顺便说一句,如果您有一个变量,并且需要检查是否为其分配了值,请使用is_var_undef内置函数,如果未定义该变量,则该函数返回true。

字符串插值


可以使用${object}语法将to_string()to_string()方法的基础对象或用户对象放入字符串中。 这样可以避免不必要的字符串连接,并且通常看起来更加整洁:

 var x = 3 var y = 4 //  sum of 3 + 4 = 7 print("sum of ${x} + ${y} = ${x + y}") 

Vector,Map,MapPair和所有原语也支持此功能。 向量以[o1, o2, ...]格式显示,映射为[<key1, val1>, <key2, val2>, ...]和MapPair: <key, val>

功能及其细微差别


ChaiScript函数是与其他所有对象一样的对象。 它们可以被捕获,分配给变量,嵌套在其他函数中并作为参数传递。 同样,对于它们,您可以指定输入值的类型(这是动态键入的语言所缺少的!),为此,您需要在声明函数参数之前指定类型。 如果在调用时可以将参数转换为指定的参数,则将根据C ++规则进行转换,否则将引发异常:

 def adder(int x, int y) { return x + y } def adder(bool x, bool y) { return x || y } adder(1, 2) // ,  3 adder(1.22, -3.7) // ,  1 + (-3) = 2 adder(true, true) // ,  true adder(true, 3) // ,    adder(bool, int) 

语言功能也可以设置呼叫条件(呼叫保护)。 如果不遵守,则会引发异常,否则将进行调用。 我还注意到,如果函数的末尾没有return语句,则将返回最后一个表达式。 小例程非常方便:

 def div(x, y) : y != 0 { x / y } //  `y`    -    `x`  `y` print(div(2, 0.5)) //  4.0 print(div(2, 0)) // , `y`  0! 

类和Dynamic_Object


ChaiScript具有OOP的基础知识,如果您需要操作复杂的对象,这是绝对的优势。 该语言具有特殊类型-Dynamic_Object。 实际上,所有类和名称空间的实例都是具有预定义属性的Dynamic_Object。 动态对象允许您在脚本执行期间向其添加字段,然后访问它们:

 var obj = Dynamic_Object(); obj.x = 3; obj.f = fun(arg) { print(this.x + arg); } //  obj   f (     `x` obj.f(-3); //  0 

类的定义非常简单。 可以将它们设置为字段,方法,构造函数。 从有趣的set_explicit(object, value)通过特殊功能set_explicit(object, value)可以通过在类声明后禁止添加新方法或属性来“固定”对象的字段(通常在构造函数中完成):

 class Widget { var id; //  id def Widget() { this.id= 0 } //    def Widget(id) { this.id = id } //   1  def get_id() { id } //   } var w = Widget(10) print(w.get_id()) //  10 (w.id) print(w.get_id) //   10,        set_explicit(w, true) //    wx = 3 //      Widget   x 

重要的一点-实际上,类方法只是函数,其第一个参数是具有明确指定类型的类的对象。 因此,以下代码等效于向现有类添加方法:

 def set_id(Widget w, id) { w.id = id } w.set_id(9) // w.id = 9 set_id(w, 9) //  , w.id = 9 

熟悉C#的任何人都可以痛苦地替换看起来像扩展方法的东西,并且会接近事实。 因此,使用该语言,您甚至可以为内置类(例如,字符串或int)添加新功能。 作者还提供了一种使运算符重载的棘手方法:要做到这一点,您需要用波浪号(`)包围运算符,如下例所示:

 //   +     Widget def `+`(Widget w1, Widget w2) { print("merging two widgets!") } var widget1 = Widget() var widget2 = Widget() widget1 + widget2 //      //        : var plus = `+` print(plus(1, 7)) //  8 

命名空间


说到ChaiScript中的名称空间,应该牢记这些本质上是始终在全局上下文中的类。 您可以使用namespace(name)函数创建它们,然后添加必要的函数和类。 默认情况下,没有该语言的库,但是您可以使用扩展来安装它们,我们将在稍后讨论。 通常,名称空间初始化可能看起来像这样:

 namespace("math") //    math //   math.square = fun(x) { x * x } math.hypot_squared= fun(x, y) { math.square(x) + math.square(y) } print(math.square(4)) //  16 print(math.hypot_squared(3, 4)) //  25 

Lambda表达式和其他功能


ChaiScript中的Lambda表达式类似于我们从C ++知道的表达式。 他们使用fun关键字,它们还需要显式指定捕获的变量,但是它们始终通过引用来执行。 该语言还具有绑定函数,可让您将值绑定到函数参数:

 var func_object = fun(x) { x * x } func_object(9) //  81 var name = "John" var greet = fun[name]() { "Hello, " + name } print(greet()) //  Hello, John name = "Bob" print(greet()) //  Hello, Bob var message = bind(fun(msg, name) { msg + " from " + name }, _, "ChaiScript"); print(message("Hello")) //  Hello from ChaiScript 

例外情况


在脚本执行过程中可能会发生异常。 它们可以在ChaiScript本身(我们将在这里讨论)和C ++中进行拦截。 语法与加号绝对相同,甚至可以抛出数字或字符串:

 try { eval(x + 1) // x   } catch (e) { print("Error during evaluation")) } //   C++   ChaiScript //   Vector -   std::vector,    std::exception      try { var vec = [1, 2] var val = vec[3] //     } catch (e) { print("index out of range: " + e.what()); // e.what    ChaiScript } //  atch   guard     ,    `:` try { throw(5.2) } catch(e) : is_type(e, "int") { print("Int: ${e}"); //   `e`  int } catch(e) : is_type(e, "double") { print("Double: ${e}"); //  `e`  double } 

以一种好的方式,您应该定义异常类并将其抛出。 在第二部分中,我们将讨论如何在C ++中对其进行拦截。 对于解释器异常,ChaiScript抛出其异常,例如eval_error,bad_boxed_cast等。

解释器常量


令我惊讶的是,该语言竟然是某种编译器宏-它们只有4个,它们全部用于标识上下文,并且主要用于错误处理:
__LINE__当前行,如果未从文件执行代码,则为“ 1”
__FILE__当前文件,如果未从文件中调用代码,则为“ __EVAL__”
__CLASS__当前课程或“ NOT_IN_CLASS”
__功能__当前函数或“ NOT_IN_FUNCTION”

错误陷阱


如果尚未声明要调用的函数,则会引发异常。 如果这对您来说不可接受,则可以定义一个特殊函数method_missing(object, func_name, params) ,如果发生错误,将使用相应的参数调用该函数:

 def method_missing(Widget w, string name, Vector v) { print("widget method ${name} with params {v} was not found") } w = Widget() w.invoke_error(1, 2, 3) //  widget method invoke_error with params [1, 2, 3] was not found 

内建功能


ChaiScript定义了许多内置函数,在本文中,我想谈一谈特别有用的函数。 其中: eval(str)eval_file(filename)to_json(object)from_json(str)

 var x = 3 var y = 5 var res = eval("x * y") // res = 15,  eval     //     : //  eval_file eval_file("source.chai") //   use,  ,         use("source.chai") // to_json    Map    var w = Widget(0) var j = to_json(w) // j = "{ "id" : 0 }" // from_json    Map ( ,   ) var m = from_json(" { "x": 0, "y": 3, "z": 2 }") print(m) //  Map  [<x, 0>, <y, 3>, <z, 2>] 


用C ++实现


安装方式


ChaiScript是一个基于C ++模板的仅标头库。 因此,对于安装,您只需要制作一个克隆存储库或将所有来自此文件夹的文件放入您的项目中。 由于根据IDE的不同,所有这些操作都有不同的方式,并且已经在论坛上进行了很长时间的详细描述,因此,我们假定您已成功连接该库,并编译了包含#include <chaiscript/chaiscript.hpp>的代码。

C ++代码调用和脚本加载


使用ChaiScript的最小示例代码如下所示。 我们在C ++中定义一个简单的函数,该函数采用std :: string并返回更改后的字符串,然后在ChaiScript对象中添加指向它的链接以进行调用。 编译可能要花费大量时间,但这主要是因为为编译器实例化大量模板并不容易:

 #include <string> #include <chaiscript/chaiscript.hpp> std::string greet_name(const std::string& name) { return "hello, " + name; } int main() { chaiscript::ChaiScript chai; //  chaiscript chai.add(chaiscript::fun(&greet_name), "greet"); //    greet //  eval      chai.eval(R"( print(greet("John")); )"); } 

希望您成功了,并且您看到了函数的结果。 我想立即注意一个细微差别-如果您将ChaiScript对象声明为静态对象,则会遇到令人不愉快的运行时错误。 这是因为该语言默认情况下支持多线程,并存储在其析构函数中访问的本地流变量。 但是,它们在调用静态实例的析构函数之前已被销毁,因此,我们遇到访问冲突或分段错误错误。 基于github上的问题 ,最简单的解决方案是将#define CHAISCRIPT_NO_THREADS放在编译器设置中或在包含库文件之前,从而禁用多线程。 据我了解,无法修复此错误。

现在,我们将详细分析C ++和ChaiScript之间的交互作用。 该库定义了一个特殊的模板函数fun ,可以使用一个指向函数,函子或类变量的指针,然后返回一个存储状态的特殊对象。 例如,让我们用C ++代码定义Widget类,并尝试以不同的方式将其与ChaiScript关联:

 class Widget { int Id; public: Widget(int id) : Id(id) { } int GetId() const { return this->Id; } }; std::string ToString(const Widget& w) { return "widget #" + std::to_string(w.GetId()); } int main() { chaiscript::ChaiScript chai; Widget w(2); //  Widget  C++  chai.add(chaiscript::fun([&w] { return w; }), "get_widget"); //         chai.add(chaiscript::fun(ToString), "to_string"); //   chai.add(chaiscript::fun(&Widget::GetId), "get_id"); //   //    ,   Widget    GetId,    to_string,    chai.eval(R"( var w = get_widget() print(w.get_id) //  2 print(w) //  widget #2 )"); } 

如您所见,ChaiScript在它未知的C ++类中可以完全平静地工作,并且可以调用其方法。 如果您在代码中的某个地方犯了一个错误,则脚本很可能会error in function dispatch抛出异常类型的异常,这一点都不重要。 但是,不仅可以导入函数,而且让我们看看如何使用该库向脚本添加变量。 为此,请更难选择任务-import std :: vector <Widget>。 chaiscript::var函数和add_global方法将帮助我们解决这一问题。 我们还将Data public字段添加到Widget中,以了解如何导入class字段:

 class Widget { int Id; public: int Data = 0; Widget(int id) noexcept : Id(id) { } int GetId() const { return this->Id; } }; std::string ToString(const Widget& w) { return "widget #" + std::to_string(w.GetId()) + " with data: " + std::to_string(w.Data); int main() { chaiscript::ChaiScript chai; std::vector<Widget> W; //    Widget W.emplace_back(1); W.emplace_back(2); W.emplace_back(3); chai.add(chaiscript::fund(ToString), "to_string"); chai.add(chaiscript::fun(&Widget::Data), "data"); //     //     ChaiScript chai.add_global(chaiscript::var(std::ref(W)), "widgets"); //     std::ref chai.add(chaiscript::fun(&std::vector<Widget>::size), "size"); //   // .        using IndexFuncType = Widget& (std::vector<Widget>::*)(const size_t); chai.add(chaiscript::fun(IndexFuncType(&std::vector<Widget>::operator[])), "[]"); chai.eval(R"( for(var i = 0; i < vec.size; ++i) { vec[i].data = i * 2; print(vec[i]) } )"); } 

上面的代码显示: widget #1 with data: 0 widget #2 with data: 2 widget #3 with data: 4 widget #2 with data: 2widget #3 with data: 4 。 我们在ChaiScript中添加了一个指向class字段的指针,并且由于该字段原来是原始类型,因此我们更改其值。 此外,还添加了几种方法来使用std::vector ,包括operator[] 。 那些熟悉STL的人知道std::vector两种索引方法-一种返回常量链接,另一种返回简单链接。 这就是为什么对于重载的函数,必须显式指示其类型-否则会引起歧义,并且编译器将引发错误。

该库提供了更多的添加对象的方法,但是它们几乎都是相同的,因此我看不到详细考虑它们的意义。 作为一个小提示,下面是下面的代码:

 chai.add(chaiscript::var(x), "x"); // x   ChaiScript chai.add(chaiscript::var(std::ref(x), "x"); //  ,    C++  ChaiScript auto shared_x = std::make_shared<int>(5); chai.add(chaiscript::var(shared_x), "x"); // shared_ptr      C++  ChaiScript chai.add(chaiscript::const_var(x), "x"); //   ChaiScript    chai.add_global_const(chaiscript::const_var(x), "x"); // global const . ,  x   chai.add_global(chaiscript::var(x), "x"); // global , .  x   chai.set_global(chaiscript::var(x), "x"); //   global ,    const 

使用STL容器


如果要将包含原始类型的STL容器传递给ChaiScript,可以将模板容器实例化添加到脚本中,这样就不必为每种类型导入方法。

 using MyVector = std::vector<std::pair<int, std::string>>; MyVector V; V.emplace_back(1, "John"); V.emplace_back(3, "Bob"); //    - vector  pair chai.add(chaiscript::bootstrap::standard_library::vector_type<MyVector>("MyVec")); chai.add(chaiscript::bootstrap::standard_library::pair_type<MyVector::value_type>("MyVecData")); chai.add(chaiscript::var(std::ref(V)), "vec"); chai.eval(R"( for(var i = 0; i < vec.size; ++i) { print(to_string(vec[i].first) + " " + vec[i].second) } )"); 

ChaiScript, . , STL-, . c std::vector<Widget> , , , ChaiScript vector_type , Widget .

++ ChaiScript


ChaiScript, . , . Widget WindowWidget, , :

 class Widget { int Id; public: Widget(int id) : Id(id) { } int GetId() const { return this->Id; } }; class WindowWidget : public Widget { std::pair<int, int> Size; public: WindowWidget(int id, int width, int height) : Widget(id), Size(width, height) { } int GetWidth() const { return this->Size.first; } int GetHeight() const { return this->Size.second; } }; int main() { chaiscript::ChaiScript chai; //   Widget    chai.add(chaiscript::user_type<Widget>(), "Widget"); chai.add(chaiscript::constructor<Widget(int)>(), "Widget"); //   WindowWidget    chai.add(chaiscript::user_type<WindowWidget>(), "WindowWidget"); chai.add(chaiscript::constructor<WindowWidget(int, int, int)>(), "WindowWidget"); // ,  Widget -    WindowWidget chai.add(chaiscript::base_class<Widget, WindowWidget>()); //   Widget  WindowWidget chai.add(chaiscript::fun(&Widget::GetId), "get_id"); chai.add(chaiscript::fun(&WindowWidget::GetWidth), "width"); chai.add(chaiscript::fun(&WindowWidget::GetHeight), "height"); //  WindowWidget     chai.eval(R"( var window = WindowWidget(1, 800, 600) print("${window.width} * ${window.height}") print("widget.id is ${window.get_id}") )"); } 

ChaiScript , C++ , . - (, ), , std::vector<Widget> .


. ChaiScript , , :

 Widget w(3); w.Data = 4444; //  Widget w chai.add(chaiscript::fun(&Widget::GetId, &w), "widget_id"); chai.add(chaiscript::fun(&Widget::Data, &w), "widget_data"); chai.eval(R"( print(widget_id) print(widget_data) )"); 

«» C++ ChaiScript ( , vec3, complex, matrix) . ChaiScript type_conversion . Complex int double :

 class Complex { public: float Re, Im; Complex(float re, float im = 0.0f) : Re(re), Im(im) { } }; int main() { chaiscript::ChaiScript chai; //  Complex,   re, im,    `=` chai.add(chaiscript::user_type<Complex>(), "Complex"); chai.add(chaiscript::bootstrap::standard_library::assignable_type<Complex>("Complex")); chai.add(chaiscript::constructor<Complex(float, float)>(), "Complex"); chai.add(chaiscript::fun(&Complex::Re), "re"); chai.add(chaiscript::fun(&Complex::Im), "im"); //     double  int  Complex chai.add(chaiscript::type_conversion<int, Complex>()); chai.add(chaiscript::type_conversion<double, Complex>()); //     `+`    chai.eval(R"( def `+`(Complex c, x) { var res = Complex(0, 0) res.re = c.re + x.re res.im = c.im + x.im return res } var c = Complex(1, 2) c = c + 3 print("${c.re} + ${c.im}i") )"); // : `4 + 2i` } 

因此,没有必要用C ++本身编写转换函数,只需将其导出到ChaiScript。您可以添加转换,并且已经在脚本代码本身中描述了新功能。如果两种类型的转换很简单,则可以将lambda作为参数传递给function type_conversion投射时会被调用。

使用类似的原理将Vector或Map ChaiScript转换为您的自定义类型。为此,在库vector_conversion定义了map_conversion

解压缩ChaiScript返回值


eval eval_file Boxed_Value . C++, , boxed_cast<T> . , , bad_boxed_cast :

 //       double d = chai.eval<double>("5.3 + 2.1"); //     Boxed_Value,     auto v = chai.eval("5.3 + 2.1"); double d = chai.boxed_cast<double>(v); 

ChaiScript shared_ptr, . shared_ptr :

 auto x = chai.eval<std::shared_ptr<double>>("var x = 3.2"); 

, shared_ptr, access violation , .

, ChaiScript , ChaiScript. , Complex :

 auto printComplex = chai.eval<std::function<void(Complex)>>(R"( fun(Complex c) { print("${c.re} + ${c.im}i"); } )"); //  ,   ,      C++ printComplex(Complex(2, 3)); //  chaiscript,  `2 + 3i` 

ChaiScript


, . eval_error , bad_boxed_cast , std::exception . , ++:

 class MyException : public std::exception { public: int Data; MyException(int data) : std::exception("MyException"), Data(data) { } }; int main() { chaiscript::ChaiScript chai; //      chaiscript chai.add(chaiscript::user_type<MyException>(), "MyException"); chai.add(chaiscript::constructor<MyException(int)>(), "MyException"); try { //          chai.eval("throw(MyException(11111))", chaiscript::exception_specification<MyException, std::exception>()); } catch (MyException& e) { std::cerr << e.Data; //   `11111` } catch (chaiscript::exception::eval_error& e) { std::cerr << e.pretty_print(); } catch(std::exception& e) { std::cerr << e.what(); } } 

, C++. pretty_print , eval_error , , , , .

ChaiScript


, ChaiScript . , , -, . - ChaiScript Extras , . math acos(x):

 #include <chaiscript/chaiscript.hpp> #include <chaiscript/extras/math.hpp> int main() { chaiscript::ChaiScript chai; //   auto mathlib = chaiscript::extras::math::bootstrap(); chai.add(mathlib); std::cout << chai.eval<double>("acos(0.5)"); // ~1.047 } 

. , math . , C++ , , .


3D- OpenGL , , . , , , « », .

, ChaiScript , Lua. , , : , C++ C, - C-style . , , .

, . ImGui, chaiscript. , :

 //      3D-: // rotation CHAI_IMPORT(&GLInstance::RotateX, rotate_x); CHAI_IMPORT(&GLInstance::RotateY, rotate_y); CHAI_IMPORT(&GLInstance::RotateZ, rotate_z); // scale CHAI_IMPORT((GLInstance&(GLInstance::*)(float))&GLInstance::Scale, scale); CHAI_IMPORT((GLInstance&(GLInstance::*)(float, float, float))&GLInstance::Scale, scale); // translation CHAI_IMPORT(&GLInstance::Translate, translate); CHAI_IMPORT(&GLInstance::TranslateX, translate_x); CHAI_IMPORT(&GLInstance::TranslateY, translate_y); CHAI_IMPORT(&GLInstance::TranslateZ, translate_z); // hide / show CHAI_IMPORT(&GLInstance::Hide, hide); CHAI_IMPORT(&GLInstance::Show, show); // getters CHAI_IMPORT(&GLInstance::GetTranslation, translation); CHAI_IMPORT(&GLInstance::GetRotation, rotation); CHAI_IMPORT(&GLInstance::GetScale, scale); 

, -, . :

图片
chaiscript ImGui:

, . , Lua , , (JIT ), ChaiScript . , , .

. C++ ( Lua ), ChaiScript . . .


Source: https://habr.com/ru/post/zh-CN483540/


All Articles