ChaiScript - Skriptsprache für C ++

Wenn eine Skriptsprache in ein C ++ - Projekt eingebettet werden muss, erinnern sich die meisten Menschen zunächst an Lua. In diesem Artikel wird es nicht darum gehen, über eine andere, nicht weniger bequeme und leicht zu erlernende Sprache namens ChaiScript zu sprechen.

Bild

Kurze Einführung


Ich selbst bin aus Versehen auf ChaiScript gestoßen, als ich eine der Vorlesungen von Jason Turner, einem der Schöpfer der Sprache, sah. Es hat mich interessiert, und in dem Moment, in dem es notwendig war, eine Skriptsprache im Projekt auszuwählen, habe ich mich entschieden - warum nicht ChaiScript ausprobieren? Das Ergebnis hat mich positiv überrascht (meine persönliche Erfahrung wird näher am Ende des Artikels geschrieben), aber egal wie seltsam es klingt, es gab keinen einzigen Artikel im Hub, in dem diese Sprache überhaupt erwähnt wurde, und ich habe das entschieden Es wäre schön, über ihn zu schreiben. Natürlich verfügt die Sprache über eine Dokumentation und eine offizielle Website , aber nicht jeder wird sie anhand von Beobachtungen lesen, und das Format des Artikels kommt vielen (einschließlich mir) näher.

Zuerst werden wir über die Syntax der Sprache und all ihre Funktionen sprechen, dann darüber, wie sie in Ihrem C ++ - Projekt implementiert wird, und zum Schluss werde ich ein wenig über meine Erfahrungen sprechen. Wenn ein Teil von Ihnen nicht interessiert ist oder Sie den Artikel in einer anderen Reihenfolge lesen möchten, können Sie das Inhaltsverzeichnis verwenden:



Sprachsyntax


ChaiScript ist in seiner Syntax C ++ und JS sehr ähnlich. Zunächst wird es, wie die meisten Skriptsprachen, dynamisch getippt, hat jedoch im Gegensatz zu JavaScript eine strikte Typisierung (Nr. 1 + "2" ). Es gibt auch einen integrierten Garbage Collector. Die Sprache ist vollständig interpretierbar, sodass Sie Code Zeile für Zeile ausführen können, ohne ihn in Bytecode zu kompilieren. Es unterstützt Ausnahmen (außerdem joint, so dass Sie sie sowohl innerhalb des Skripts als auch in C ++ abfangen können), Lambda-Funktionen und das Überladen von Operatoren. Es reagiert nicht auf Leerzeichen, sodass Sie als einzelne Zeile durch ein Semikolon oder im Python-Stil schreiben und Ausdrücke durch eine neue Zeile trennen können.

Primitive Typen


ChaiScript speichert Integer-Variablen standardmäßig als int, real als double und Strings mit std :: string. Dies geschieht hauptsächlich, um die Kompatibilität mit dem aufrufenden Code sicherzustellen. Die Sprache hat sogar Suffixe für Zahlen, sodass wir explizit angeben können, um welchen Typ es sich bei unserer Variablen handelt:

 /*   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 

Das Ändern des Variablentyps funktioniert einfach nicht. Wahrscheinlich müssen Sie für diese Typen einen eigenen "=" - Operator definieren. Andernfalls besteht die Gefahr, dass Sie entweder eine Ausnahme auslösen (wir werden später darauf eingehen) oder gerundet werden:

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

Sie können eine Variable jedoch deklarieren, ohne ihr einen Wert zuzuweisen. In diesem Fall enthält sie eine Art undefiniertes Element, bis ihr ein Wert zugewiesen wird.

Inline-Container


Die Sprache hat zwei Container - Vector und Map. Sie arbeiten sehr ähnlich wie ihre Gegenstücke in C ++ (std :: vector bzw. std :: map), benötigen jedoch keinen Typ, da sie jeden speichern können. Die Indizierung kann wie gewohnt mit ints erfolgen, für Map ist jedoch ein Schlüssel mit einer Zeichenfolge erforderlich. Anscheinend von Python inspiriert, haben die Autoren auch die Möglichkeit hinzugefügt, Container in Code mit der folgenden Syntax schnell zu deklarieren:

 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 //       

Beide Klassen wiederholen ihre Entsprechungen in C ++ fast vollständig, mit Ausnahme von Iteratoren, da es stattdessen spezielle Klassen Range und Const_Range gibt. Übrigens werden alle Container als Referenz übergeben, auch wenn Sie die Zuweisung durch = verwenden, was für mich sehr seltsam ist, da bei allen anderen Typen das Kopieren nach Wert erfolgt.

Bedingte Konstruktionen


Fast alle Konstruktionen von Bedingungen und Zyklen lassen sich in einem Beispielcode buchstäblich beschreiben:

 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 } 

Ich denke, die mit C ++ vertrauten Personen haben nichts Neues gefunden. Dies ist nicht verwunderlich, da ChaiScript als leicht zu erlernende Sprache positioniert ist und daher alle bekannten klassischen Designs übernimmt. Die Autoren haben beschlossen, sogar zwei Schlüsselwörter für die Deklaration von Variablen hervorzuheben - var und auto , falls Sie die Vorteile von auto wirklich mögen.

Ausführungskontext


ChaiScript hat einen lokalen und globalen Kontext. Der Code wird zeilenweise von oben nach unten ausgeführt, kann aber in Funktionen herausgenommen und später (aber nicht früher!) Aufgerufen werden. Innerhalb von Funktionen oder Bedingungen / Schleifen deklarierte Variablen sind von außen standardmäßig nicht sichtbar. Sie können dieses Verhalten jedoch ändern, indem Sie den global Bezeichner anstelle von var . Globale Variablen unterscheiden sich von gewöhnlichen insofern, als sie erstens außerhalb des lokalen Kontexts sichtbar sind und zweitens neu deklariert werden können (wenn der Wert während der Neudeklaration nicht festgelegt wird, bleibt er gleich).

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

Wenn Sie eine Variable haben und prüfen müssen, ob ihr ein Wert zugewiesen wurde, verwenden Sie die is_var_undef Funktion is_var_undef , die true zurückgibt, wenn die Variable nicht definiert ist.

String-Interpolation


to_string() oder Benutzerobjekte, für die eine to_string() -Methode to_string() können mit der Syntax ${object} in eine Zeichenfolge to_string() . Dies vermeidet unnötige Verkettungen von Zeichenfolgen und sieht im Allgemeinen viel aufgeräumter aus:

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

Vector, Map, MapPair und alle Grundelemente unterstützen diese Funktion ebenfalls. Der Vektor wird im Format [o1, o2, ...] , Karte als [<key1, val1>, <key2, val2>, ...] und MapPair: <key, val> angezeigt.

Funktionen und ihre Nuancen


ChaiScript-Funktionen sind Objekte wie alles andere. Sie können erfasst, Variablen zugewiesen, in anderen Funktionen verschachtelt und als Argument übergeben werden. Auch für sie können Sie den Typ der Eingabewerte angeben (was den dynamisch typisierten Sprachen fehlte!). Hierzu müssen Sie den Typ angeben, bevor Sie den Funktionsparameter deklarieren. Wenn der Parameter beim Aufrufen in den angegebenen Wert konvertiert werden kann, erfolgt die Konvertierung gemäß den C ++ - Regeln. Andernfalls wird eine Ausnahme ausgelöst:

 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) 

Funktionen in der Sprache können auch über Anrufbedingungen (Call Guard) eingestellt werden. Wird dies nicht beachtet, wird eine Ausnahme ausgelöst, andernfalls wird ein Anruf getätigt. Ich beachte auch, dass, wenn die Funktion am Ende keine return-Anweisung hat, der letzte Ausdruck zurückgegeben wird. Sehr praktisch für kleine Routinen:

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

Klassen und Dynamic_Object


ChaiScript hat die Grundlagen von OOP, was ein klares Plus ist, wenn Sie komplexe Objekte bearbeiten müssen. Die Sprache hat einen speziellen Typ - Dynamic_Object. Tatsächlich sind alle Instanzen von Klassen und Namespaces genau Dynamic_Object mit vordefinierten Eigenschaften. Mit einem dynamischen Objekt können Sie ihm während der Ausführung des Skripts Felder hinzufügen und dann darauf zugreifen:

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

Klassen werden ganz einfach definiert. Sie können auf Felder, Methoden und Konstruktoren gesetzt werden. Ab dem interessanten set_explicit(object, value) mit der Sonderfunktion set_explicit(object, value) die Felder des Objekts "reparieren", indem Sie das Hinzufügen neuer Methoden oder Attribute nach der Klassendeklaration verbieten (dies erfolgt normalerweise im Konstruktor):

 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 

Ein wichtiger Punkt: Tatsächlich sind Klassenmethoden nur Funktionen, deren erstes Argument ein Objekt einer Klasse mit einem explizit angegebenen Typ ist. Daher entspricht der folgende Code dem Hinzufügen einer Methode zu einer vorhandenen Klasse:

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

Jeder, der mit C # vertraut ist, kann eine schmerzhafte Erweiterungsmethode ersetzen und ist der Wahrheit sehr nahe. Auf diese Weise können Sie in der Sprache neue Funktionen auch für integrierte Klassen hinzufügen, z. B. für einen String oder eine Ganzzahl. Die Autoren bieten auch eine knifflige Möglichkeit, Operatoren zu überladen: Dazu müssen Sie das Operator-Symbol wie im folgenden Beispiel mit einer Tilde (`) umgeben:

 //   +     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 

Namespaces


In Bezug auf den Namespace in ChaiScript sollte beachtet werden, dass es sich im Wesentlichen um Klassen handelt, die sich immer in einem globalen Kontext befinden. Sie können sie mit der namespace(name) Funktion namespace(name) erstellen und anschließend die erforderlichen Funktionen und Klassen hinzufügen. Standardmäßig gibt es keine Bibliotheken in der Sprache. Sie können sie jedoch mithilfe von Erweiterungen installieren, auf die wir später noch eingehen werden. Im Allgemeinen könnte die Namespace-Initialisierung folgendermaßen aussehen:

 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-Ausdrücke und andere Merkmale


Lambda-Ausdrücke in ChaiScript ähneln dem, was wir aus C ++ kennen. Für sie wird das Schlüsselwort fun verwendet. Außerdem müssen die erfassten Variablen explizit angegeben werden. Dies geschieht jedoch immer anhand von Verweisen. Die Sprache verfügt auch über eine Bindefunktion, mit der Sie Werte an Funktionsparameter binden können:

 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 

Ausnahmen


Während der Skriptausführung können Ausnahmen auftreten. Sie können sowohl in ChaiScript selbst (das wir hier diskutieren) als auch in C ++ abgefangen werden. Die Syntax ist absolut identisch mit Pluszeichen. Sie können sogar eine Zahl oder eine Zeichenfolge ausgeben:

 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 } 

Auf eine gute Weise sollten Sie Ihre Klasse von Ausnahmen definieren und sie auslösen. Wir werden im zweiten Abschnitt darüber sprechen, wie man es in C ++ abfängt. Für Interpreter-Ausnahmen gibt ChaiScript Ausnahmen aus, wie eval_error, bad_boxed_cast usw.

Interpreter-Konstanten


Zu meiner Überraschung stellte sich heraus, dass es sich bei der Sprache um eine Art Compiler-Makro handelte - es gibt nur vier davon und alle dienen zur Identifizierung des Kontexts und werden hauptsächlich zur Fehlerbehandlung verwendet:
__LINE__aktuelle Zeile, wenn der Code nicht aus einer Datei ausgeführt wird, dann '1'
__FILE__Aktuelle Datei, wenn der Code nicht aus einer Datei aufgerufen wird, dann "__EVAL__"
__CLASS__aktuelle Klasse oder "NOT_IN_CLASS"
__FUNC__aktuelle Funktion oder "NOT_IN_FUNCTION"

Fehler beim Überfüllen


Wenn die aufgerufene Funktion nicht deklariert wurde, wird eine Ausnahme ausgelöst. Wenn dies für Sie nicht akzeptabel ist, können Sie eine spezielle Funktion definieren - method_missing(object, func_name, params) , die im Fehlerfall mit den entsprechenden Argumenten aufgerufen wird:

 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 

Eingebaute Funktionen


ChaiScript definiert viele integrierte Funktionen, und in dem Artikel möchte ich auf besonders nützliche Funktionen eingehen. Darunter: 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>] 


Implementierung in C ++


Installation


ChaiScript ist eine auf C ++ - Vorlagen basierende Bibliothek, die nur auf Headern basiert. Dementsprechend müssen Sie für die Installation nur ein Klon- Repository erstellen oder alle Dateien aus diesem Ordner in Ihr Projekt einfügen. Da dies alles je nach IDE unterschiedlich #include <chaiscript/chaiscript.hpp> und in den Foren lange Zeit ausführlich beschrieben wurde, gehen wir davon aus, dass Sie die Verbindung zur Bibliothek hergestellt haben und der Code mit include: #include <chaiscript/chaiscript.hpp> kompiliert wurde.

Aufruf von C ++ - Code und Laden von Skripten


Der kleinste Beispielcode, der ChaiScript verwendet, ist wie folgt. Wir definieren eine einfache Funktion in C ++, die std :: string verwendet und den geänderten String zurückgibt, und fügen dann im ChaiScript-Objekt einen Link hinzu, um ihn aufzurufen. Das Kompilieren kann viel Zeit in Anspruch nehmen, ist jedoch in erster Linie darauf zurückzuführen, dass das Instanziieren einer großen Anzahl von Vorlagen für den Compiler nicht einfach ist:

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

Ich hoffe es ist Ihnen gelungen und Sie haben das Ergebnis der Veranstaltung gesehen. Ich möchte gleich eine Nuance notieren - wenn Sie ein ChaiScript-Objekt als statisch deklarieren, erhalten Sie einen unangenehmen Laufzeitfehler. Dies liegt an der Tatsache, dass die Sprache standardmäßig Multithreading unterstützt und lokale Ablaufvariablen speichert, auf die in ihrem Destruktor zugegriffen wird. Sie werden jedoch zerstört, bevor der Destruktor der statischen Instanz aufgerufen wird. Infolgedessen liegt eine Zugriffsverletzung oder ein Segmentierungsfehler vor. Basierend auf dem Problem bei Github besteht die einfachste Lösung darin, " #define CHAISCRIPT_NO_THREADS in den Compilereinstellungen oder vor dem Einschließen der Bibliotheksdatei zu definieren und dadurch das Multithreading zu deaktivieren. Soweit ich weiß, konnte dieser Fehler nicht behoben werden.

Nun werden wir im Detail analysieren, wie die Interaktion zwischen C ++ und ChaiScript abläuft. Die Bibliothek definiert eine spezielle Schablonenfunktion fun , die einen Zeiger auf eine Funktion, einen Funktor oder einen Zeiger auf eine Klassenvariable nehmen und dann ein spezielles Objekt zurückgeben kann, das den Status speichert. Als Beispiel definieren wir die Widget-Klasse in C ++ und versuchen, sie auf verschiedene Arten mit ChaiScript zu verknüpfen:

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

Wie Sie sehen, arbeitet ChaiScript absolut ruhig mit C ++ - Klassen, die ihm unbekannt sind, und kann deren Methoden aufrufen. Wenn Sie irgendwo im Code einen Fehler machen, wird das Skript höchstwahrscheinlich eine Ausnahme von der Art error in function dispatch , die überhaupt nicht kritisch ist. Es können jedoch nicht nur Funktionen importiert werden, sondern es wird auch gezeigt, wie Sie einem Skript mithilfe der Bibliothek eine Variable hinzufügen. Wählen Sie dazu die Aufgabe etwas schwieriger aus - importieren Sie std :: vector <Widget>. Die Funktion chaiscript::var und die Methode add_global helfen uns dabei. Wir werden auch das öffentliche Data zu unserem Widget hinzufügen, um zu sehen, wie das Klassenfeld importiert wird:

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

Der obige Code zeigt widget #1 with data: 0 : widget #1 with data: 0 , widget #2 with data: 2 , widget #3 with data: 4 . Wir haben dem Klassenfeld in ChaiScript einen Zeiger hinzugefügt, und da sich herausstellte, dass das Feld ein primitiver Typ ist, ändern wir seinen Wert. Es wurden auch mehrere Methoden hinzugefügt, um mit std::vector , einschließlich operator[] . Diejenigen, die mit STL vertraut sind, wissen, dass std::vector zwei Indizierungsmethoden hat - eine gibt eine konstante Verknüpfung zurück, die andere eine einfache Verknüpfung. Aus diesem Grund müssen Sie für überladene Funktionen deren Typ explizit angeben, da sonst Mehrdeutigkeiten auftreten und der Compiler einen Fehler auslöst.

Die Bibliothek bietet mehrere weitere Methoden zum Hinzufügen von Objekten, die jedoch fast alle identisch sind. Daher sehe ich keinen Sinn darin, sie im Detail zu betrachten. Als kleinen Hinweis ist hier der folgende Code:

 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 

Verwendung von AWL-Containern


Wenn Sie STL-Container mit primitiven Typen an ChaiScript übergeben möchten, können Sie Ihrem Skript eine Instanziierung für Vorlagencontainer hinzufügen, damit Sie nicht für jeden Typ Methoden importieren.

 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. , . , 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); 

, -, . :

Bild
chaiscript ImGui:

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

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


Source: https://habr.com/ru/post/de483540/


All Articles