(Ce doc est un travail en cours…)
Remarque: la branche «CPP» contient un port «stupide» de ce projet de C à C ++, ce que j'ai fait pour plusieurs raisons:
J2 est un système qui combine un langage de programmation minimaliste («Edit», pour «Dictionnaire exécutable») avec un système CFLECLE / FFI C. Il vous permet d'importer facilement des bibliothèques partagées et d'utiliser directement les données, les variables et les fonctions (au moins celles de la portée globale), sans avoir besoin d'écrire de code «colle».
Par exemple, si j'ai un fichier «Inc.c» contenant:
typedef struct mystruct { int i; float f; } mystruct;
extern mystruct increment(mystruct x) { x.i+=1; x.f+=1; return x; }
... et j'en fais une bibliothèque partagée:
gcc --shared -g inc.c -o inc.so
... Ensuite, je peux importer cette bibliothèque partagée dans l'interprète édit et aller à l'utilisation:
e> loadlib([inc.so]) @mylib
Finished curating module
e> mylib<mystruct! <2@i 4@f> @x increment(x)>/ stack!
VMRES_STACK
<null>
structure_type "struct mystruct"
member "i"
base_type "int" 0x3 (0x7f361c0121a0)
member "f"
base_type "float" 5 (0x7f361c0121a4)
Cela semble simple, mais il se passe beaucoup de choses ici sous les couvertures ici ...
«Edit» est un langage de programmation minimaliste qui compense sa simplicité en ayant la capacité intégrée à comprendre et à se lier dynamiquement aux bibliothèques C, en fournissant un accès «natif» aux types C, variables et méthodes, de complexité arbitraire, sans écrire emballages ou code de colle. Alternativement, vous pouvez le considérer comme une bibliothèque de réflexion pour les programmes C qui leur permet d'exposer un accès dynamique à leurs propres internes lors de l'exécution.
La langue est construite sur une base de trois éléments:
Ces trois éléments se réunissent au moment de l'exécution dans un environnement programmable polyvalent. Le système se déroule par::
L'évolution de l'édit a été influencée par Forth, Lisp, Joy, Factor, Tcl, Mathematica et autres.
Points clés:
while (call=vm_dispatch(call)); // VM's inner loop
(Remarque: j'ai trouvé cette structure "zigzag" très similaire (et antérieure) mon listree: https://www.nongnu.org/gzz/gi/gi.html)
Le «Listree» est la structure de données de base de l'édit et la machine virtuelle qui les implémente. Une instance d'un listree se compose simplement d'une valeur listree, qui contient:
La possibilité d'une valeur listree à contenir (étiquetée) références à d'autres valeurs de Listree est ce qui fait de la structure de données «hiérarchique» ou «récursive». Le Listree est à la fois au cœur de l'édit et du système qui l'implémente.
* Ce n'est pas explicite dans cette explication simple (et c'est exprès), mais chaque étiquette fait réellement référence à une liste de références à d'autres valeurs de Listree.
Comme mentionné, la machine virtuelle maintient tout son état dans une valeur de Listree, y compris les sous-listes de «pile de données» et de «dictionnaire», entre autres.
Dans l'édit, il existe deux types de valeurs de données, mais les valeurs des deux types sont stockées à l'aide de valeurs Listree qui existent soit sur la pile de données de la machine virtuelle, soit dans son dictionnaire.
Le type de valeur le plus simple est un "littéral". Les littéraux ne sont que du texte, délimité par des crochets:
[This is a literal]
Si vous venez d'un fond LISP, vous pourriez penser que l'interprète décompose les littéraux en s-expressions, mais ce n'est pas le cas. Tout entre les supports est représenté «littéralement» dans le «tampon de données» d'une valeur de Listree.
L'interprète de l'édit garde une trace des supports carrés imbriqués, donc:
[This is a [nested] literal]
est interprété comme un seul littéral avec la valeur "Il s'agit d'un littéral [imbriqué]".
Un caractère "" apparaissant dans la définition d'un "échappe" littéral "le personnage suivant, permettant à l'interprète de créer des littéraux contenant (par exemple) des supports carrés déséquilibrés:
[This is a literal containing an unbalanced [ bracket]
Lorsque l'interprète rencontre un, les valeurs littérales (ou plutôt, les références à la valeur - une distinction importante) sont simplement placées sur la pile de données.
OTOH, l'interprète fait un peu plus de traitement sur tout ce qui n'est pas un littéral. (Il existe d'autres types de valeurs qui ne sont pas des littéraux, dont je vais discuter dans une section ultérieure…)
Le programmeur édit peut attribuer des noms aux valeurs sur la pile, puis se référer à ces valeurs par ces noms. La mission ressemble simplement à ceci:
@mylabel
L'attribution d'un nom à une valeur fait en fait plusieurs choses:
Lorsque l'interprète voit une référence à une étiquette, cette référence est remplacée par une référence à la valeur associée à cette étiquette… en d'autres termes:
L'édit est différent des autres langues d'une manière importante. De nombreuses langues sont «homoiconiques», le code et les données IE sont représentés en utilisant les mêmes structures sous-jacentes. (Lisp est un exemple traditionnel d'une langue homoiconique.) L'édit n'est ni homoiconique ni non homoiconique: il n'a pas du tout de «fonctions». Il dispose simplement d'un opérateur d'évaluation qui peut être appliqué aux valeurs.
Une chose de base «comme une fonction» dans l'édit peut ressembler:
[1@x]
(Attribuez l'étiquette «x» à la valeur «1».)
Notez que ce n'est qu'un littéral .
Pour l' invoquer , l' opérateur d'évaluation est utilisé:
[1@x]!
Le résultat de ceci est exactement la même que si l'interprète venait de lire directement ce qui suit:
1@x
Tout ce que fait l'opérateur d'évaluation est alimenter le contenu du haut du haut de la pile vers l'interprète.
Maintenant: rappelez-vous que les étiquettes peuvent être affectées aux valeurs et que les étiquettes peuvent être invoquées pour rappeler leurs valeurs, et ces valeurs sont poussées vers la pile, et que l'opérateur d'évaluation renforce le contenu de la pile dans l'interpréteur:
[1@x]@f
f!
Cette petite séquence fait ce qui suit:
L'édit peut importer des types de bibliothèque et des informations sur les variables / fonctions globales via la section de débogage (nain) d'une bibliothèque, si elle est disponible. Les informations naines sont traitées et stockées dans le dictionnaire, et la machine virtuelle peut «comprendre» ces informations et les présenter dans l'interprète de l'édit en utilisant la même syntaxe «native» simple qui fonctionne sur des valeurs littérales.
int! @x
MyCGlobalInt @y
xy Multiply!
(WIP ci-dessous…)
Référence | Référence |
-Ref | Référence (queue) |
@ | Affectation à TOS |
@Ref | Affectation à la référence |
/ / | Libérer les tos |
/ Ref | Release Ref |
^ Ref | Métarerence |
Réf ^ | Fusionner la couche de pile supérieure à la réflexion |
^ Ref ^ (^ ref + ^) | MetaReference et fusionnez la couche de pile supérieure à la réflexion |
! | Évaluer les TO |
Tos <...> | «Évaluer dans Dict-Context»: Poussez TOS vers le dict, évaluez les contenus de <...>, Pop top of Dict Stack to TOS. |
Tos (...) | «Évaluer dans Code-Context»: Push Null Stack / Dict Calques, poussez TOS vers la pile de code, évaluez les contenus de parens, évaluez le haut de la pile de code, jetez le haut du dict, le haut en haut de la pile à la couche précédente. |
Un programme réfléchissant simple:
int! [3]@ square! stack!
Panne:
int | Recherche et valeur poussée de «int» (un type C natif) sur la pile |
! | Évaluez le haut de la pile, dans ce cas, allouer un exemple de «int» |
[3] | Poussez le «3» littéral sur la pile |
@ | Affectation; Dans ce cas, la chaîne «3» est automatiquement contraint dans un «int» C «int» |
square | Recherchez et poussez la valeur de «carré» (une méthode C native) sur la pile |
! | Évaluez le haut de la pile, dans ce cas un appel FFI à la méthode C native «carrée» |
stack | Recherchez et poussez la valeur de «Stack» (une méthode C native) sur la pile |
! | Évaluez le haut de la pile ... |
Vous verrez int 0x9, c'est-à-dire 3 carré.
La création et l'attribution explicites d'un entier C sont indiquées uniquement pour l'exemple; Une version plus simple serait:
[3] square! stack!
La même coercition aurait été effectuée automatiquement lors de la réduction des arguments FFI.
Notez que "code" n'évalue pas automatiquement; L'évaluation est invoquée explicitement via "!". Le code n'est que des données jusqu'à ce que vous décidiez de les «exécuter»:
[3] square stack! | données et "code" sur pile, non évalué |
! stack! | Évaluer les TO et observer les résultats |
Factorielle:
[@n int_iszero(n) 1 | int_mul(fact(int_dec(n)) n)]@fact
Dans un Listree, une valeur contient un dictionnaire de paires de clés / CLL, où chaque CLL ("liste de liaison circulaire", implémentant une file d'attente à double extrémité) contient une ou plusieurs références aux valeurs, chacune contenant un dictionnaire de. .. Et ainsi de suite. Le composant clé / DEQ d'un listree est une implémentation BST basée sur la variation «simple» RBTree d'Arne Andersson. (http://user.it.uu.se/~arnea/ps/simp.pdf)
La machine virtuelle est très simple; Il évalue les bytecodes, chacun étant un indice dans un tableau de méthodes C qui implémente le bytecode.
RÉINITIALISER | Clair |
Ext | Décoder la séquence de caractères et régler allumé ("[un deux 3]", "ABC") |
Ext_push | Pousser allumé sur la pile de données |
Référence | Créer une référence de référence du dictionnaire de Lit |
Deref | Résoudre la réf dans le dictionnaire |
ATTRIBUER | Pop haut de la pile de données et insérer dans le dictionnaire à l'emplacement réf ("@") |
RETIRER | Valeur de libération à Ref |
Évaluer | Pop haut de la pile de données et a) Appelez FFI, ou b) Poussez sur la pile de code et donnez VM |
Ctx_push | Pop en haut de la pile de données et les pousser vers la tête de la pile du dictionnaire, pousser une nouvelle couche vers la pile de données |
Ctx_pop | Fuser les deux couches supérieures de la pile de données, la tête pop de la pile de dictionnaire et les pousser vers la pile de données |
Fun_push | Pop en haut de la pile de données et pousser sur la pile Func, ajouter une couche à la pile de données, ajouter une couche nul au dictionnaire |
Fun_eval | Push {fun_pop} à la pile de code, en haut de la pile de func et faire "EVAL" |
Fun_pop | Fusible les deux couches supérieures de pile, jetez la couche de dictionnaire nulle |
LANCER | Jetez une exception * |
ATTRAPER | Attraper une exception * |
* Les exceptions sont la façon dont les erreurs de machine virtuelle et les conditions sont mises en œuvre, et ils jouent un peu plus l'état de la machine virtuelle que l'opération moyenne, méritant leur propre paragraphe.
Cette simple suite d'opérations est suffisante pour interagir avec le dictionnaire, évaluer récursivement les constructions de langage (la machine virtuelle est une implémentation sans pile), et surtout, exploiter les types, les fonctions et les données. Il s'agit d'un cadre à nu qui s'appuie sur son couplage serré à C afin d'étendre ses capacités sans effort.
(Toute distribution GNU / Linux qui vaut son sel en a:
Pour courir: en supposant que le CCC, le CMake et les bibliothèques ne sont pas trop anciens, exécutez simplement "faire" pour construire et entrer dans le REP.