ChaiScript - langage de script pour C ++

Lorsqu'il est nécessaire d'incorporer un langage de script dans un projet C ++, la première chose dont la plupart des gens se souviennent est Lua. Dans cet article ça ne sera pas, je vais en parler d'un autre, non moins pratique et facile à apprendre, le langage appelé ChaiScript.

image

Brève introduction


Je suis moi-même tombé sur ChaiScript par hasard en regardant l' une des conférences de Jason Turner, l'un des créateurs du langage. Cela m'a intéressé, et à ce moment où il a fallu choisir un langage de script dans le projet, j'ai décidé - pourquoi ne pas essayer ChaiScript? Le résultat m'a agréablement surpris (mon expérience personnelle sera écrite plus près de la fin de l'article), cependant, aussi étrange que cela puisse paraître, il n'y avait pas un seul article sur le hub qui mentionnait cette langue au moins d'une manière ou d'une autre, et j'ai décidé que ce serait bien d'écrire sur lui. Bien sûr, la langue a une documentation et un site officiel , mais tout le monde ne la lira pas à partir des observations, et le format de l'article est plus proche de beaucoup (y compris moi).

Tout d'abord, nous parlerons de la syntaxe du langage et de toutes ses fonctionnalités, puis de la façon de l'implémenter dans votre projet C ++, et à la fin je parlerai un peu de mon expérience. Si une partie de vous n'est pas intéressée, ou si vous souhaitez lire l'article dans un ordre différent, vous pouvez utiliser la table des matières:



Syntaxe du langage


ChaiScript est très similaire à C ++ et JS dans sa syntaxe. Tout d'abord, comme la grande majorité des langages de script, il est typé dynamiquement, mais contrairement à JavaScript, il a un typage strict (non 1 + "2" ). Il y a aussi un garbage collector intégré, le langage est entièrement interprétable, vous permettant d'exécuter du code ligne par ligne, sans compilation en bytecode. Il prend en charge les exceptions (en plus, joint, vous permettant de les intercepter à la fois dans le script et en C ++), les fonctions lambda, la surcharge de l'opérateur. Il n'est pas sensible aux espaces, vous permettant d'écrire sur une seule ligne à travers un point-virgule ou en style python, en séparant les expressions par une nouvelle ligne.

Types primitifs


ChaiScript par défaut stocke les variables entières comme int, réelles comme double et les chaînes avec std :: string. Cela se fait principalement afin d'assurer la compatibilité avec le code appelant. Le langage a même des suffixes pour les nombres, afin que nous puissions indiquer explicitement de quel type est notre variable:

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

Changer le type de variables ne fonctionne tout simplement pas, vous devrez probablement définir votre propre opérateur `=` pour ces types, sinon vous courez le risque de lever une exception (nous en parlerons plus tard) ou de devenir victime d'arrondi, comme ceci:

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

Cependant, vous pouvez déclarer une variable sans lui affecter de valeur, auquel cas elle contiendra une sorte d'indéfini jusqu'à ce qu'une valeur lui soit affectée.

Conteneurs en ligne


La langue a deux conteneurs - Vector et Map. Ils fonctionnent de manière très similaire à leurs homologues en C ++ (std :: vector et std :: map, respectivement), mais ils ne nécessitent pas de type, car ils peuvent en stocker n'importe quel. L'indexation peut être effectuée comme d'habitude avec des entiers, mais Map nécessite une clé avec une chaîne. Apparemment inspiré par python, les auteurs ont également ajouté la possibilité de déclarer rapidement des conteneurs en code en utilisant la syntaxe suivante:

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

Ces deux classes répètent presque complètement leurs homologues en C ++, à l'exception des itérateurs, car à leur place, il existe des classes spéciales Range et Const_Range. Soit dit en passant, tous les conteneurs sont passés par référence même si vous utilisez l'affectation via =, ce qui est très étrange pour moi, car pour tous les autres types, la copie par valeur se produit.

Constructions conditionnelles


Presque toutes les constructions de conditions et de cycles peuvent être décrites dans un seul exemple de code:

 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 } 

Je pense que les gens familiers avec C ++ n’ont rien trouvé de nouveau. Cela n'est pas surprenant, car ChaiScript est positionné comme un langage facile à apprendre pour les "apprenants", et emprunte donc tous les modèles classiques bien connus. Les auteurs ont décidé de mettre en évidence même deux mots clés pour déclarer des variables - var et auto , au cas où vous aimeriez vraiment les avantages avec auto.

Contexte d'exécution


ChaiScript a un contexte local et global. Le code est exécuté de haut en bas ligne par ligne, mais il peut être retiré dans les fonctions et appelé plus tard (mais pas plus tôt!). Les variables déclarées à l'intérieur des fonctions ou des conditions / boucles ne sont pas visibles par défaut de l'extérieur, mais vous pouvez changer ce comportement en utilisant l'identifiant global au lieu de var . Les variables globales diffèrent des variables ordinaires en ce que, d'une part, elles sont visibles en dehors du contexte local, et d'autre part, elles peuvent être re-déclarées (si la valeur n'est pas définie lors de la re-déclaration, alors elle reste la même)

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

Soit dit en passant, si vous avez une variable et que vous devez vérifier si une valeur lui est affectée, utilisez la is_var_undef intégrée is_var_undef , qui renvoie true si la variable n'est pas définie.

Interpolation de chaînes


Les objets de base ou les objets utilisateur qui ont une méthode to_string() peuvent être placés dans une chaîne en utilisant la syntaxe ${object} . Cela évite les concaténations de chaînes inutiles et semble généralement beaucoup plus ordonné:

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

Vector, Map, MapPair et toutes les primitives prennent également en charge cette fonctionnalité. Le vecteur est affiché au format [o1, o2, ...] , Map as [<key1, val1>, <key2, val2>, ...] et MapPair: <key, val> .

Fonctions et leurs nuances


Les fonctions ChaiScript sont des objets comme tout le reste. Ils peuvent être capturés, affectés à des variables, rendus imbriqués dans d'autres fonctions et passés en argument. Pour eux, vous pouvez également spécifier le type de valeurs d'entrée (ce qui manquait aux langues typées dynamiquement!). Pour cela, vous devez spécifier le type avant de déclarer le paramètre de fonction. Si, une fois appelé, le paramètre peut être converti en celui spécifié, la conversion se produira selon les règles C ++, sinon une exception sera levée:

 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) 

Des fonctions dans la langue peuvent également être définies comme conditions d'appel (garde d'appel). S'il n'est pas respecté, une exception est levée, sinon un appel est effectué. Je note également que si la fonction n'a pas de déclaration de retour à la fin, la dernière expression sera retournée. Très pratique pour les petites routines:

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

Classes et Dynamic_Object


ChaiScript a les rudiments de la POO, ce qui est un avantage certain si vous devez manipuler des objets complexes. Le langage a un type spécial - Dynamic_Object. En fait, toutes les instances de classes et d'espaces de noms sont exactement Dynamic_Object avec des propriétés prédéfinies. Un objet dynamique vous permet d'y ajouter des champs lors de l'exécution du script, puis d'y accéder:

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

Les classes sont définies tout simplement. Ils peuvent être définis sur des champs, des méthodes, des constructeurs. Du set_explicit(object, value) intéressant set_explicit(object, value) grâce à la fonction spéciale set_explicit(object, value) vous pouvez «corriger» les champs de l'objet en interdisant l'ajout de nouvelles méthodes ou attributs après la déclaration de la classe (cela se fait généralement dans le constructeur):

 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 

Un point important - en fait, les méthodes de classe ne sont que des fonctions dont le premier argument est un objet d'une classe avec un type explicitement spécifié. Par conséquent, le code suivant équivaut à ajouter une méthode à une classe existante:

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

Toute personne familiarisée avec C # peut remplacer ce qui ressemble douloureusement à une méthode d'extension et sera proche de la vérité. Ainsi, dans le langage, vous pouvez ajouter de nouvelles fonctionnalités même pour les classes intégrées, par exemple, pour une chaîne ou un entier. Les auteurs proposent également un moyen délicat de surcharger les opérateurs: pour ce faire, vous devez entourer le symbole de l'opérateur d'un tilde (`) comme dans l'exemple ci-dessous:

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

Espaces de noms


En parlant de l'espace de noms dans ChaiScript, il faut garder à l'esprit que ce sont essentiellement des classes qui sont toujours dans un contexte global. Vous pouvez les créer à l'aide de la fonction namespace(name) , puis ajouter les fonctions et classes nécessaires. Par défaut, il n'y a pas de bibliothèques dans la langue, mais vous pouvez les installer à l'aide d'extensions, dont nous parlerons un peu plus tard. En général, l'initialisation de l'espace de noms peut ressembler à ceci:

 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 

Expressions lambda et autres fonctionnalités


Les expressions lambda dans ChaiScript sont similaires à ce que nous savons du C ++. Le mot-clé fun est utilisé pour eux, et ils nécessitent également de spécifier explicitement les variables capturées, mais ils le font toujours par référence. Le langage possède également une fonction de liaison qui vous permet de lier des valeurs aux paramètres de fonction:

 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 

Exceptions


Des exceptions peuvent se produire lors de l'exécution du script. Ils peuvent être interceptés à la fois dans ChaiScript lui-même (dont nous parlerons ici) et en C ++. La syntaxe est absolument identique aux plus, vous pouvez même jeter un nombre ou une chaîne:

 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 } 

Dans le bon sens, vous devez définir votre classe d'exceptions et la lever. Nous parlerons de la façon de l'intercepter en C ++ dans la deuxième section. Pour les exceptions d'interpréteur, ChaiScript lève ses exceptions, telles que eval_error, bad_boxed_cast, etc.

Constantes d'interpréteur


À ma grande surprise, le langage s'est avéré être une sorte de macros de compilation - il n'y en a que 4 et elles servent toutes à identifier le contexte et sont principalement utilisées pour la gestion des erreurs:
__LINE__ligne actuelle, si le code n'est pas exécuté à partir d'un fichier, alors '1'
__FILE__fichier actuel, si le code n'est pas appelé à partir d'un fichier, alors "__EVAL__"
__CLASS__classe actuelle ou "NOT_IN_CLASS"
__FUNC__fonction actuelle ou "NOT_IN_FUNCTION"

Erreur de récupération


Si la fonction que vous appelez n'a pas été déclarée, une exception est levée. Si cela ne vous convient pas, vous pouvez définir une fonction spéciale - method_missing(object, func_name, params) , qui sera appelée avec les arguments correspondants en cas d'erreur:

 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 

Fonctions intégrées


ChaiScript définit de nombreuses fonctions intégrées, et dans l'article, j'aimerais parler de fonctions particulièrement utiles. Parmi eux: 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>] 


Implémentation en C ++


L'installation


ChaiScript est une bibliothèque en-tête uniquement basée sur un modèle C ++. Par conséquent, pour l'installation, il vous suffit de créer un référentiel de clone ou de simplement mettre tous les fichiers de ce dossier dans votre projet. Étant donné que, selon l'IDE, tout cela se fait différemment et a été décrit en détail sur les forums depuis longtemps, nous supposerons que vous avez réussi à connecter la bibliothèque, et le code avec include: #include <chaiscript/chaiscript.hpp> compilé.

Invocation de code C ++ et chargement de script


Le plus petit exemple de code utilisant ChaiScript est le suivant. Nous définissons une fonction simple en C ++ qui prend std :: string et renvoie la chaîne modifiée, puis nous y ajoutons un lien dans l'objet ChaiScript pour l'appeler. La compilation peut prendre un temps considérable, mais cela est principalement dû au fait qu'il n'est pas facile d'instancier un grand nombre de modèles pour le compilateur:

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

J'espère que vous avez réussi et que vous avez vu le résultat de la fonction. Je veux noter une nuance tout de suite - si vous déclarez un objet ChaiScript statique, vous obtenez une erreur d'exécution désagréable. Cela est dû au fait que le langage prend en charge le multithreading par défaut et stocke les variables de flux locales accessibles dans son destructeur. Cependant, ils sont détruits avant l'appel du destructeur de l'instance statique et, par conséquent, nous avons une violation d'accès ou une erreur de segmentation. En fonction du problème sur github , la solution la plus simple serait de simplement mettre #define CHAISCRIPT_NO_THREADS dans les paramètres du compilateur ou avant d'inclure le fichier de bibliothèque, désactivant ainsi le multithreading. Si je comprends bien, il n'a pas été possible de corriger cette erreur.

Nous allons maintenant analyser en détail comment se déroule l'interaction entre C ++ et ChaiScript. La bibliothèque définit une fonction de modèle spéciale fun , qui peut prendre un pointeur vers une fonction, un foncteur ou un pointeur vers une variable de classe, puis renvoyer un objet spécial qui stocke l'état. Par exemple, définissons la classe Widget dans le code C ++ et essayons de l'associer à ChaiScript de différentes manières:

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

Comme vous pouvez le voir, ChaiScript fonctionne de manière absolument calme avec des classes C ++ inconnues et peut appeler leurs méthodes. Si vous faites une erreur quelque part dans le code, le script lèvera très probablement une exception à l' error in function dispatch type error in function dispatch , qui n'est pas critique du tout. Cependant, non seulement les fonctions peuvent être importées, voyons comment ajouter une variable à un script à l'aide de la bibliothèque. Pour ce faire, sélectionnez un peu plus la tâche - import std :: vector <Widget>. La fonction chaiscript::var et la méthode add_global nous y aideront. Nous ajouterons également le champ public Data à notre widget pour voir comment importer le champ de classe:

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

Le code ci-dessus affiche: widget #1 with data: 0 , widget #2 with data: 2 , widget #3 with data: 4 . Nous avons ajouté un pointeur sur le champ de classe dans ChaiScript, et puisque le champ s'est avéré être un type primitif, nous changeons sa valeur. De plus, plusieurs méthodes ont été ajoutées pour fonctionner avec std::vector , y compris l' operator[] . Ceux qui connaissent STL savent que std::vector deux méthodes d'indexation - l'une renvoie un lien constant, l'autre un simple lien. C'est pourquoi pour les fonctions surchargées, vous devez indiquer explicitement leur type - sinon une ambiguïté survient et le compilateur lèvera une erreur.

La bibliothèque fournit plusieurs autres méthodes pour ajouter des objets, mais ils sont tous presque identiques, donc je ne vois pas l'intérêt de les examiner en détail. À titre indicatif, voici le code ci-dessous:

 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 

Utilisation de conteneurs STL


Si vous souhaitez transmettre des conteneurs STL contenant des types primitifs à ChaiScript, vous pouvez ajouter une instanciation de conteneur de modèle à votre script afin de ne pas importer de méthodes pour chaque type.

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

De plus, lors de l'exportation de plusieurs classes de «bibliothèque» de C ++ vers ChaiScript (par exemple, vec3, complexe, matrice), la possibilité de conversion implicite d'un type à un autre est souvent requise. Dans ChaiScript, ce problème est résolu en ajoutant type_conversionun script à l'objet. Par exemple, considérez la classe Complex et l'implémentation de la conversion int et double en elle pendant l'ajout:

 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` } 

Ainsi, il n'est pas nécessaire d'écrire une fonction de conversion en C ++ lui-même, puis de l'exporter vers ChaiScript. Vous pouvez ajouter des transformations et déjà décrire la nouvelle fonctionnalité dans le code de script lui-même. Si la conversion pour les deux types n'est pas triviale, vous pouvez passer le lambda comme argument à une fonction type_conversion. Il sera appelé lors du casting.

Un principe similaire est utilisé pour convertir Vector ou Map ChaiScript en votre type personnalisé. Pour cela, vector_conversionet sont définis dans la bibliothèque map_conversion.

Décompression des valeurs de retour ChaiScript


Méthodes evalet eval_fileretourne la valeur de la dernière expression exécutée en tant qu'objet Boxed_Value. Pour le décompresser et utiliser le résultat dans le code C ++, vous pouvez soit spécifier explicitement le type de la valeur de retour, soit utiliser une fonction boxed_cast<T>. Si la conversion entre types existe, elle sera exécutée, sinon une exception sera levée 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); 

Étant donné que tous les objets à l'intérieur de ChaiScript sont stockés à l'aide de shared_ptr, vous pouvez obtenir l'objet en tant que pointeur pour un travail ultérieur avec lui. Pour ce faire, spécifiez explicitement le type shared_ptr lors de la conversion de la valeur de retour:

 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


Malheureusement, par défaut, ChaiScript ne fournit pas de fonctionnalités supplémentaires en termes de bibliothèques. Par exemple, il manque des fonctions mathématiques, des tables de hachage et la plupart des algorithmes. Vous pouvez télécharger certains d'entre eux sous forme de bibliothèques de modules à partir du référentiel officiel ChaiScript Extras , puis les importer dans votre script. Par exemple, prenez la bibliothèque mathématique et la fonction 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 } 

Vous pouvez également écrire votre bibliothèque pour la langue, puis l'importer. Cela se fait tout simplement, je vous conseille donc de vous familiariser avec les mathématiques open source ou toute autre source dans le référentiel. En principe, dans le cadre de l'intégration avec C ++, nous avons examiné presque tout, donc je pense que la section peut être complétée à ce sujet.

Expérience personnelle


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

, -, . :

image
chaiscript ImGui:

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

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


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


All Articles