** QuickJS ** est un moteur JavaScript léger qui peut être intégré en C / C ++. La licence est une licence MIT. Cela semble utile lorsque vous souhaitez intégrer du JavaScript, mais que V8 est trop surpuissant.
Cliquez ici pour la page officielle, qui est trop simple dans la conception et a un sens de la fiabilité.
QuickJS is a small and embeddable Javascript engine. It supports the ES2020 specification including modules, asynchronous generators, proxies and BigInt. (Traduction: QuickJS est un petit moteur JavaScript intégrable. Il prend en charge les spécifications ES2020, y compris les modules, les générateurs asynchrones, les proxies et BigInt.)
À propos, l'auteur de QuickJS Fabrice Bellard est également l'auteur de qemu et ffmpeg. L'incarnation du pouvoir comportemental ... (Image omise)
Il existe également un miroir non officiel sur GitHub. Ceci est lié à partir de la page officielle en tant que «miroir git non officiel». Cependant, au moment de la rédaction de l'article, la dernière version (05/01/2020) n'est pas reflétée et reste la version 2019-10-27.
Cet article explique comment incorporer QuickJS dans C / C ++. C'est comme mon mémorandum, pas systématique et exhaustif, alors veuillez comprendre à l'avance.
Vous pouvez décompresser l'archive tar et l'installer comme d'habitude avec make install
. Par défaut, il est installé sous / usr / local
, mais il peut être modifié en spécifiant prefix
. Ce qui suit est écrit en supposant qu'il sera installé dans ~ / .local
, alors lisez-le comme il convient.
#Télécharger la source
curl -LO https://bellard.org/quickjs/quickjs-2020-01-05.tar.xz
#Développer l'archive tar
tar axvf quickjs-2020-01-05.tar.xz
#Construire~/.Exemple d'installation sous local
# -j (--jobs)Est le nombre d'exécutions parallèles, donc ajustez en conséquence
make -C quickjs-2020-01-05 -j 2 prefix="${HOME}/.local" install
Si vous souhaitez utiliser la commande qjs
, transmettez la variable d'environnement PATH
de manière appropriée.
PATH="${HOME}/.local/bin:${PATH}"
export PATH
Il peut également être installé via AUR pour les utilisateurs d'Arch Linux et Homebrew pour les utilisateurs de macOS.
qjs
/ qjsc
L'appel de la commande qjs
sans argument lance REPL.
$ qjs
QuickJS - Type "\h" for help
qjs > \h
\h this help
\x hexadecimal number display
\d *decimal number display
\t toggle timing display
\clear clear the terminal
\q exit
qjs > 3**2 + 4**2
25
qjs > 2n ** 256n
115792089237316195423570985008687907853269984665640564039457584007913129639936n
qjs > const name = "world"
undefined
qjs > `Hello, ${name}`
"Hello, world"
qjs > /^(a)(b*)(c+)(d?)$/.exec("abbbcc")
[ "abbbcc", "a", "bbb", "cc", "" ]
qjs >
Si vous donnez un nom de fichier à la commande qjs
, ce fichier sera exécuté. Vous pouvez également ʻimport / ʻexport
.
greeter.js
export function greet(name) {
console.log(`Hello, ${name}!`);
}
index.js
import { greet } from "./greeter.js";
greet("Alice");
$ qjs index.js
Hello, Alice!
Vous pouvez faire de JavaScript un fichier exécutable en utilisant la commande qjsc
.
$ qjsc index.js
$ strip a.out
$ file a.out
a.out: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=315625503ecf284b44cab3b6f1d3dea6df4dedc7, for GNU/Linux 3.2.0, stripped
$ stat -c '%s' a.out | numfmt --to=iec-i
832Ki
$ ./a.out
Hello, Alice!
Le sujet principal est d'ici.
La documentation officielle de l'API C ne comporte que quelques dizaines de lignes de sections:
Eh bien, le comportement peut être déduit de l'en-tête (quickjs.h
), et cela fonctionne tel quel. Après cela, si vous lisez la source REPL (qjs.c
) et l'implémentation de la bibliothèque elle-même ( quickjs.c
), vous pouvez comprendre comment l'utiliser.
L'ensemble du code source illustré cette fois est placé dans le référentiel suivant.
Pour commencer, appelons la fonction foo
définie dans JS à l'aide de l'API C. Le code est le suivant (En passant, cela correspond à l'exemple de code de Lua (langage de programmation) --C API de Wikipedia (en) ing).
simple.c
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <quickjs.h>
int main(void) {
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
char *const fooCode = "function foo(x, y) { return x + y; }";
if (JS_IsException(JS_Eval(ctx, fooCode, strlen(fooCode), "<input>", JS_EVAL_FLAG_STRICT))) {
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return -1;
}
JSValue global = JS_GetGlobalObject(ctx);
JSValue foo = JS_GetPropertyStr(ctx, global, "foo");
JSValue argv[] = { JS_NewInt32(ctx, 5), JS_NewInt32(ctx, 3) };
JSValue jsResult = JS_Call(ctx, foo, global, sizeof(argv) / sizeof(JSValue), argv);
int32_t result;
JS_ToInt32(ctx, &result, jsResult);
printf("Result: %d\n", result);
JSValue used[] = { jsResult, argv[1], argv[0], foo, global };
for (int i = 0; i < sizeof(used) / sizeof(JSValue); ++i) {
JS_FreeValue(ctx, used[i]);
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return 0;
}
Construisez la source ci-dessus avec la commande suivante.
#compiler(-Optimisé pour la taille avec Os)
gcc -c -Os -Wall -I"${HOME}/.local/include" simple.c
#Lien(-Wl,-bande avec s)
gcc -Wl,-s -L"${HOME}/.local/lib/quickjs" simple.o -l quickjs -l m -o simple
Une fois exécuté, le résultat de «5 + 3» s'affiche.
$ ./simple
Result: 8
Ce code est traité dans le flux suivant.
JS_NewRuntime
JS_NewContext
JS_Eval
JSValue JS_Eval (JSContext * ctx, const char * input, size_t input_len, const char * filename, int eval_flags);
SignatureJS_GetGlobalObject
JS_GetPropertyStr
JS_NewInt32
JS_Call
JSValue JS_Call (JSContext * ctx, JSValueConst func_obj, JSValueConst this_obj, int argc, JSValueConst * argv);
JS_ToInt32
JS_FreeValue
pour libérer des objets JS (réduire les compteurs de référence)JS_FreeValue
car ce que vous générez avec JS_NewInt32
est un type valeur, pas un type référence, mais il est préférable de l'appeler par souci de cohérence.JS_DupValue
JS_FreeValue
D'une manière ou d'une autre, je peux voir la loi qui fait la distinction entre ceux qui doivent et ceux qui ne devraient pas, mais je ne la comprends pas complètement, donc je le fais dans une atmosphère (veuillez l'indiquer)Get
ʻEval nécessite
Free, et que le type
New` doit être jeté immédiatement après </ del> JSValue
, qui est propriétaire ici, est toujours gratuit
. Lorsque vous voulez céder la propriété au côté JS, Dup
(offset Free
) et transmettez-le." Peut-être que ça a l'air bien (utile si vous voulez Free
avec un destructeur C ++)JS_FreeContext
JS_FreeRuntime
Un exemple de création d'une application qui exécute le JavaScript donné comme argument de ligne de commande et affiche le résultat dans la sortie standard. L'image de l'opération est la suivante.
$ ./jseval '3**2 + 4**2'
25
$ ./jseval foo
ReferenceError: foo is not defined
$ ./jseval 'undefined'
$ ./jseval '[3, 4, 5].map(x => x ** 10).forEach(x => console.log(x))'
59049
1048576
9765625
Le code est comme suit. console.log
/ console.error
est implémenté en C pour pouvoir être utilisé à partir de JS.
jseval.c
#include <stdio.h>
#include <string.h>
#include <quickjs.h>
JSValue jsFprint(JSContext *ctx, JSValueConst jsThis, int argc, JSValueConst *argv, FILE *f) {
for (int i = 0; i < argc; ++i) {
if (i != 0) {
fputc(' ', f);
}
const char *str = JS_ToCString(ctx, argv[i]);
if (!str) {
return JS_EXCEPTION;
}
fputs(str, f);
JS_FreeCString(ctx, str);
}
fputc('\n', f);
return JS_UNDEFINED;
}
JSValue jsPrint(JSContext *ctx, JSValueConst jsThis, int argc, JSValueConst *argv) {
return jsFprint(ctx, jsThis, argc, argv, stdout);
}
JSValue jsPrintErr(JSContext *ctx, JSValueConst jsThis, int argc, JSValueConst *argv) {
return jsFprint(ctx, jsThis, argc, argv, stderr);
}
void initContext(JSContext *ctx) {
JSValue global = JS_GetGlobalObject(ctx);
//Ajouter une console à globalThis
JSValue console = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, global, "console", console);
// console.définir le journal
JS_SetPropertyStr(ctx, console, "log", JS_NewCFunction(ctx, jsPrint, "log", 1));
// console.Définir l'erreur
JS_SetPropertyStr(ctx, console, "error", JS_NewCFunction(ctx, jsPrintErr, "error", 1));
JS_FreeValue(ctx, global);
}
int main(int argc, char const *argv[]) {
int exitCode = 0;
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
initContext(ctx);
for (int i = 1; i < argc; ++i) {
JSValue ret = JS_Eval(ctx, argv[i], strlen(argv[i]), "<input>", JS_EVAL_FLAG_STRICT);
if (JS_IsException(ret)) {
JSValue e = JS_GetException(ctx);
jsPrintErr(ctx, JS_NULL, 1, &e);
JS_FreeValue(ctx, e);
exitCode = 1;
break;
} else if (JS_IsUndefined(ret)) {
// nop
} else {
jsPrint(ctx, JS_NULL, 1, &ret);
}
JS_FreeValue(ctx, ret);
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return exitCode;
}
JS_SetPropertyStr
JS_NewCFunction
à traiter les fonctions C comme des fonctions JSJSValue JS_NewCFunction (JSContext * ctx, JSCFunction * func, const char * name, int length)
JSCFunction
est typedef JSValue JSCFunction (JSContext * ctx, JSValueConst this_val, int argc, JSValueConst * argv);
this
et des arguments (nombre et pointeur au début du tableau) et renvoie une valeurJSValueConst
est" JSValue ne doit pas nécessairement être "JS_FreeValue
"JS_IsException (valeur de retour de JS_Eval)
est véridique, JS_GetException
obtient et affiche l'objet de l'exception qui s'est produite dans le contexte.À partir de là, l'exemple de code devient soudainement C ++ au lieu de C. J'ai eu beaucoup de mal ...
Par exemple, vous souhaitez utiliser C ++ std :: mt19937
(générateur de nombres pseudo aléatoires) du côté JavaScript comme suit. Pense.
const mt = new Mt19937();
for (let i = 0; i < 10; ++i) {
console.log(mt.generate()); //nombre aléatoire(BigInt)Pour sortir
}
production
3499211612
581869302
3890346734
3586334585
545404204
4161255391
3922919429
949333985
2715962298
1323567403
Cela peut être écrit comme suit:
mt19937.cc
//ID unique de la classe Mt19937 (initialisé ultérieurement)
//Il est défini globalement pour des raisons de simplicité, mais il se cassera lorsque plusieurs Runtimes sont exécutés en même temps, il est donc nécessaire de le faire de manière appropriée.
static JSClassID jsMt19937ClassID;
// Mt19937.prototype.generate
JSValue jsMt19937Generate(JSContext *ctx, JSValueConst jsThis, int argc, JSValueConst *argv) {
std::mt19937 *p = static_cast<std::mt19937 *>(JS_GetOpaque(jsThis, jsMt19937ClassID));
return JS_NewBigUint64(ctx, (*p)());
}
// Mt19937.prototype
const JSCFunctionListEntry jsMt19937ProtoFuncs[] = {
JS_CFUNC_DEF("generate", 1, jsMt19937Generate),
};
//Constructeur MT19937
JSValue jsMt19937New(JSContext *ctx, JSValueConst jsThis, int argc, JSValueConst *argv) {
//Créer une instance
JSValue obj = JS_NewObjectClass(ctx, jsMt19937ClassID);
bool fail = false;
if (argc == 0) {
JS_SetOpaque(obj, new std::mt19937());
} else if (argc == 1) {
// ... (Si vous souhaitez définir une valeur de départ. Omis car ce n'est pas essentiel)
} else {
fail = true;
}
if (fail) {
JS_FreeValue(ctx, obj); //J'ai tendance à oublier
return JS_EXCEPTION;
}
return obj;
}
//Appelé lorsque l'objet Mt19937 est récupéré par le GC
void jsMt19937Finalizer(JSRuntime *rt, JSValue val) {
std::mt19937 *p = static_cast<std::mt19937 *>(JS_GetOpaque(val, jsMt19937ClassID));
delete p;
}
//Définition de la classe Mt19937
//Lorsqu'il existe une dépendance entre des objets qui ne sont pas exposés côté JS.gc_Il semble que la marque doit également être collectée
JSClassDef jsMt19937Class = {
"Mt19937",
.finalizer = jsMt19937Finalizer,
};
void initContext(JSContext *ctx) {
// ...
//Initialiser l'ID de classe Mt19937
JS_NewClassID(&jsMt19937ClassID);
//Enregistrer la classe au moment de l'exécution
JS_NewClass(JS_GetRuntime(ctx), jsMt19937ClassID, &jsMt19937Class);
//réglage du prototype
JSValue mt19937Proto = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, mt19937Proto, jsMt19937ProtoFuncs, std::extent_v<decltype(jsMt19937ProtoFuncs)>);
JS_SetClassProto(ctx, jsMt19937ClassID, mt19937Proto);
//Ajouté Mt19937 à globalThis
JS_SetPropertyStr(ctx, global, "Mt19937", JS_NewCFunction2(ctx, jsMt19937New, "Mt19937", 1, JS_CFUNC_constructor, 0));
// ...
}
Le cœur est «JS_NewClass» / «JS_NewObjectClass» et «JS_GetOpaque» / «JS_SetOpaque». De cette façon, il est possible de laisser le GC gérer la durée de vie de std :: mt19937 *
.
Si vous voulez faire ʻimport / ʻexport
d'un module, vous avez besoin de l'indicateur JS_EVAL_TYPE_MODULE
au moment de JS_Eval
. Si cet indicateur est activé, la valeur de retour «ret» sera soit «JS_UNDEFINED» ou «JS_EXCEPTION».
JSValue ret = JS_Eval(ctx, argv[i], strlen(argv[i]), "<input>", JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_STRICT);
Si vous voulez charger un fichier JS en tant que module du système de fichiers comme la commande qjs
, vous devez enregistrer une fonction comme js_module_loader
dans quickjs-libc.c
avec JS_SetModuleLoaderFunc
.
Considérez le cas où vous voulez que le Mt19937
dans la section précédente soit ʻimport du module
rand` au lieu d'être donné comme une propriété globale.
import { Mt19937 } from "rand";
Cela peut être écrit comme suit:
rand.cc
//Liste des fonctions du module rand
static const JSCFunctionListEntry randFuncs[] = {
JS_CFUNC_SPECIAL_DEF("Mt19937", 1, constructor, jsMt19937New), // new Mt19937()Ça peut être ainsi
// JS_CFUNC_DEF("Mt19937", 1, jsMt19937New), // Mt19937()Si tu veux
};
//Initialisation du module rand (celui appelé lors de l'importation de JS)
int initRand(JSContext *ctx, JSModuleDef *m) {
JS_NewClassID(&jsMt19937ClassID);
JS_NewClass(JS_GetRuntime(ctx), jsMt19937ClassID, &jsMt19937Class);
//réglage du prototype
JSValue mt19937Proto = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, mt19937Proto, jsMt19937ProtoFuncs, std::extent_v<decltype(jsMt19937ProtoFuncs)>);
JS_SetClassProto(ctx, jsMt19937ClassID, mt19937Proto);
//Le dernier argument est sizeof(randFuncs) / sizeof(JSCFunctionListEntry)Sens
return JS_SetModuleExportList(ctx, m, randFuncs, std::extent_v<decltype(randFuncs)>);
}
//Définition du module rand
JSModuleDef *initRandModule(JSContext *ctx, const char *moduleName) {
JSModuleDef *m = JS_NewCModule(ctx, moduleName, initRand);
if (!m) {
return nullptr;
}
JS_AddModuleExportList(ctx, m, randFuncs, std::extent_v<decltype(randFuncs)>);
return m;
}
void initContext(JSContext *ctx) {
// ...
initRandModule(ctx, "rand");
// ...
}
int main(int argc, char const *argv[]) {
// ...
// JS_EVAL_TYPE_Seul le MODULE peut être importé
JSValue ret = JS_Eval(ctx, argv[i], strlen(argv[i]), "<input>", JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_STRICT);
// ...
}
Vous pouvez enregistrer le module dans le contexte en créant JSCFunctionListEntry []
et en faisant JS_NewCModule
.
qjs
Si vous nommez la fonction d'initialisation du module js_init_module
et la compilez comme une bibliothèque partagée, vous pouvez ʻimport depuis le script exécuté par la commande
qjs. Cette fonctionnalité est implémentée dans la fonction
js_module_loader_so de
quickjs-libc.c`.
Image d'utilisation
import { Mt19937 } from "./librand.so";
rand.cc
//Définition du module rand
extern "C" JSModuleDef *js_init_module(JSContext *ctx, const char *moduleName) {
JSModuleDef *m = JS_NewCModule(ctx, moduleName, initRand);
if (!m) {
return nullptr;
}
JS_AddModuleExportList(ctx, m, randFuncs, std::extent_v<decltype(randFuncs)>);
return m;
}
g++ -c -fPIC -Os -std=c++17 -Wall -I"${HOME}/.local/include/quickjs" rand.cc
#sur macOS-undefined dynamic_Ajouter également une recherche
g++ -shared -Wl,-s -L"${HOME}/.local/lib/quickjs" rand.o -o librand.so
QuickJS est incroyable car le système de traitement JavaScript complet est complété avec seulement quelques fichiers source C (impression). Cependant, il y a des milliers à des dizaines de milliers de lignes par fichier. Le contenu semble être relativement facile à lire, alors j'aimerais également essayer la lecture de code.
Les autres langages légers pouvant être incorporés dans C / C ++ sont Lua, mruby, [Squirrel](http: // squirrel] -lang.org/) etc., mais je pense que l'utilisation de JavaScript (QuickJS) à la place est une option. Par exemple, si vous pouvez écrire des fichiers de paramètres d'application et des scripts de jeu dans JS, vous pouvez utiliser eslint + joli, ou vous pouvez convertir de TypeScript / JSX avec tsc ou babel, donc cela peut être utile et intéressant (approprié).
Après cela, j'ai besoin d'une personne capable de lier facilement C / C ++ ⇆ JavaScript pour une utilisation pratique. Couches équivalentes à tolua ++ et luabind dans Lua (je ne l'ai pas utilisé, mais maintenant Est-ce que sol est le courant dominant?). Personnellement, je veux un gars qui fait du bon travail avec les macros de Rust. Actuellement, il existe un wrapper appelé quick-js
, mais il manque certaines fonctionnalités, donc j'aimerais l'implémenter moi-même ou non.
Recommended Posts