perlinterp - En ligne dans le cloud

Il s'agit de la commande perlinterp qui peut être exécutée dans le fournisseur d'hébergement gratuit OnWorks en utilisant l'un de nos multiples postes de travail en ligne gratuits tels que Ubuntu Online, Fedora Online, l'émulateur en ligne Windows ou l'émulateur en ligne MAC OS.

PROGRAMME:

Nom


perlinterp - Un aperçu de l'interpréteur Perl

DESCRIPTION


Ce document donne un aperçu du fonctionnement de l'interpréteur Perl au niveau C.
code, ainsi que des pointeurs vers les fichiers de code source C pertinents.

ÉLÉMENTS OF L' INTERPRÈTE


Le travail de l'interprète comporte deux étapes principales : la compilation du code dans le
représentation, ou bytecode, puis son exécution. "Code compilé" dans perlguts explique
exactement comment se déroule la phase de compilation.

Voici un bref aperçu du fonctionnement de Perl :

Démarrage
L'action commence dans perlmain.c. (ou alors miniperlmain.c pour miniperl) C'est un niveau très élevé
code, suffisant pour tenir sur un seul écran, et il ressemble au code trouvé dans perlembed ; la plupart
de l'action réelle se déroule dans perl.c

perlmain.c est généré par "ExtUtils::Miniperl" à partir de miniperlmain.c à l'heure du travail, alors tu
devrait faire en sorte que Perl suive cela.

Tout d'abord, perlmain.c alloue de la mémoire et construit un interpréteur Perl, parallèlement à ces
lignes:

1 PERL_SYS_INIT3(&argc,&argv,&env);
2
3 si (!PL_do_undump) {
4 mon_perl = perl_alloc();
5 si (!my_perl)
6 sortie(1);
7 perl_construct(mon_perl);
8 PL_perl_destruct_level = 0;
9}

La ligne 1 est une macro et sa définition dépend de votre système d'exploitation. Ligne 3
fait référence à "PL_do_undump", une variable globale - toutes les variables globales en Perl commencent par
"PL_". Ceci vous indique si le programme en cours d'exécution a été créé avec l'indicateur "-u".
en perl et ensuite décharger, ce qui signifie que ça va être faux dans n'importe quel contexte sain.

La ligne 4 appelle une fonction dans perl.c pour allouer de la mémoire à un interpréteur Perl. C'est tout à fait un
fonction simple, et l'essentiel ressemble à ceci :

mon_perl = (PerlInterpreter*)PerlMem_malloc(sizeof(PerlInterpreter));

Vous voyez ici un exemple d'abstraction du système Perl, que nous verrons plus tard :
"PerlMem_malloc" est soit le "malloc" de votre système, soit le "malloc" de Perl tel que défini dans
malloc.c si vous avez sélectionné cette option au moment de la configuration.

Ensuite, à la ligne 7, nous construisons l'interpréteur en utilisant perl_construct, également dans perl.c; ce
configure toutes les variables spéciales dont Perl a besoin, les piles, etc.

Maintenant, nous passons à Perl les options de ligne de commande et lui disons de partir :

exitstatus = perl_parse(my_perl, xs_init, argc, argv, (char **)NULL);
si (!exitstatus)
perl_run(mon_perl);

état de sortie = perl_destruct(mon_perl);

perl_free(mon_perl);

"perl_parse" est en fait un wrapper autour de "S_parse_body", tel que défini dans perl.c, Qui
traite les options de ligne de commande, configure tous les modules XS liés statiquement, ouvre le
programme et appelle "yyparse" pour l'analyser.

Analyse
Le but de cette étape est de prendre le source Perl et de le transformer en arbre opérationnel. Nous verrons
à quoi ressemblera l'un d'eux plus tard. À proprement parler, il se passe trois choses ici.

"yyparse", l'analyseur, vit dans perly.c, même s'il vaut mieux lire l'original
Entrée YACC dans perly.y. (Oui, Virginie, là is une grammaire YACC pour Perl !) Le travail du
l'analyseur consiste à prendre votre code et à le "comprendre", en le divisant en phrases, en décidant
quels opérandes vont avec quels opérateurs et ainsi de suite.

L'analyseur est noblement assisté par le lexer, qui décompose votre entrée en jetons, et
décide quel type de chose est chaque jeton : un nom de variable, un opérateur, un mot nu, un
sous-programme, une fonction principale, etc. Le principal point d'entrée du lexer est "yylex",
et cela et ses routines associées peuvent être trouvés dans toke.c. Perl n'est pas vraiment comme les autres
langages informatiques; c'est parfois très sensible au contexte, cela peut être difficile à comprendre
quelle sorte de jeton est quelque chose, ou où se termine un jeton. En tant que tel, il y a beaucoup de
interaction entre le tokeniser et l'analyseur, ce qui peut devenir assez effrayant si vous êtes
pas habitué.

Lorsque l'analyseur comprend un programme Perl, il construit une arborescence d'opérations pour le
interprète à effectuer pendant l’exécution. Les routines qui construisent et relient entre elles
les différentes opérations se trouvent dans op.c, et sera examiné plus tard.


L'étape d'analyse est désormais terminée et l'arborescence terminée représente les opérations qui
l'interpréteur Perl doit effectuer pour exécuter notre programme. Ensuite, Perl effectue un essai à sec
sur l'arbre à la recherche d'optimisations : des expressions constantes telles que "3 + 4" seront
calculé maintenant, et l'optimiseur verra également si plusieurs opérations peuvent être remplacées
avec un seul. Par exemple, pour récupérer la variable $foo, au lieu de récupérer le glob
*foo et en regardant le composant scalaire, l'optimiseur manipule l'arborescence des opérations pour utiliser un
fonction qui recherche directement le scalaire en question. L'optimiseur principal est "peep" dans
op.c, et de nombreuses opérations ont leurs propres fonctions d'optimisation.

Fonctionnement
Maintenant, nous sommes enfin prêts à partir : nous avons compilé le code d'octet Perl, et tout ce qu'il reste à faire
c'est l'exécuter. L'exécution proprement dite est effectuée par la fonction "runops_standard" dans exécuter.c; Suite
plus précisément, cela se fait par ces trois lignes à l'apparence innocente :

tandis que ((PL_op = PL_op->op_ppaddr(aTHX))) {
PERL_ASYNC_CHECK();
}

Vous serez peut-être plus à l'aise avec la version Perl :

PERL_ASYNC_CHECK() while $Perl::op = &{$Perl::op->{function}};

Eh bien, peut-être pas. Quoi qu'il en soit, chaque opération contient un pointeur de fonction, qui stipule le
fonction qui réalisera réellement l’opération. Cette fonction renverra le prochain
op dans la séquence - cela permet des choses comme "if" qui choisissent dynamiquement l'opération suivante
lors de l'exécution. Le "PERL_ASYNC_CHECK" garantit que des éléments tels que les signaux s'interrompent
exécution si nécessaire.

Les fonctions réellement appelées sont connues sous le nom de code PP et sont réparties entre quatre fichiers :
pp_hot.c contient le code "chaud", le plus souvent utilisé et hautement optimisé, pp_sys.c
contient toutes les fonctions spécifiques au système, pp_ctl.c contient les fonctions qui
mettre en œuvre des structures de contrôle (« si », « pendant » et autres) et pp.c contient tout
autre. Il s'agit, si vous le souhaitez, du code C pour les fonctions et opérateurs intégrés de Perl.

Notez que chaque fonction "pp_" est censée renvoyer un pointeur vers l'opération suivante. Appels à
les sous-marins Perl (et les blocs d'évaluation) sont gérés dans la même boucle runops et ne consomment pas
espace supplémentaire sur la pile C. Par exemple, "pp_entersub" et "pp_entertry" poussent simplement un
Structure de bloc "CxSUB" ou "CxEVAL" sur la pile de contexte qui contient l'adresse du
op après le sous-appel ou l'évaluation. Ils renvoient ensuite la première opération de ce sous ou de cette évaluation
bloc, et ainsi l’exécution continue de ce sous ou bloc. Plus tard, un "pp_leavesub" ou
L'opération "pp_leavetry" fait apparaître le "CxSUB" ou "CxEVAL", en récupère l'opération de retour et
le renvoie.

Exception remise
La gestion des exceptions de Perl (c'est-à-dire "mourir", etc.) est construite au-dessus du système de bas niveau.
Fonctions de la bibliothèque C "setjmp()"/"longjmp()". Ceux-ci fournissent essentiellement un moyen de capturer le
registres PC et SP actuels et les restaurer ultérieurement ; c'est-à-dire qu'un "longjmp()" continue au
point dans le code où un "setjmp()" précédent a été effectué, avec quoi que ce soit plus haut sur le C
la pile est perdue. C'est pourquoi le code doit toujours sauvegarder les valeurs en utilisant "SAVE_FOO" plutôt que
dans les variables automatiques.

Le noyau Perl enveloppe "setjmp()" etc. dans les macros "JMPENV_PUSH" et "JMPENV_JUMP". Le
La règle de base des exceptions Perl est que "exit" et "die" (en l'absence de "eval") effectuent
a JMPENV_JUMP(2), alors que "die" dans "eval" fait un JMPENV_JUMP (3).

Aux points d'entrée de Perl, tels que "perl_parse()", "perl_run()" et "call_sv(cv, G_EVAL)"
chacun fait un "JMPENV_PUSH", puis entre dans une boucle runops ou autre, et gère possible
l'exception revient. Pour un retour de 2, le nettoyage final est effectué, tel que l'éclatement des piles et
appeler des blocs "CHECK" ou "END". Entre autres choses, c'est ainsi que le nettoyage de la portée continue
se produit lors d'une "sortie".

Si un "dé" peut trouver un bloc "CxEVAL" sur la pile de contexte, alors la pile est déplacée vers
ce niveau et l'opération de retour dans ce bloc sont attribués à "PL_restartop" ; puis un
JMPENV_JUMP(3) est effectué. Cela redonne normalement le contrôle au garde. Dans le cas
de "perl_run" et "call_sv", un "PL_restartop" non nul déclenche la rentrée dans les runops
boucle. C'est la manière normale dont "mourir" ou "croasser" est traité dans une "évaluation".

Parfois, les opérations sont exécutées dans une boucle runops interne, comme l'attachement, le tri ou la surcharge.
code. Dans ce cas, quelque chose comme

sous FETCH { eval { die } }

provoquerait un longjmp directement au garde dans "perl_run", faisant apparaître les deux boucles runops,
ce qui est clairement incorrect. Une façon d'éviter cela est que le code d'égalité fasse un
"JMPENV_PUSH" avant d'exécuter "FETCH" dans la boucle runops interne, mais pour plus d'efficacité
raisons, Perl définit en fait simplement un indicateur, en utilisant "CATCH_SET(TRUE)". Le "pp_require",
Les opérations "pp_entereval" et "pp_entertry" vérifient cet indicateur, et si c'est vrai, elles appellent "docatch",
qui fait un "JMPENV_PUSH" et démarre un nouveau niveau runops pour exécuter le code, plutôt que
le faire sur la boucle actuelle.

Comme optimisation supplémentaire, à la sortie du bloc eval dans le "FETCH", exécution du
le code qui suit le bloc est toujours exécuté dans la boucle interne. Lorsqu'une exception est
levé, "docatch" compare le niveau "JMPENV" du "CxEVAL" avec "PL_top_env" et si
ils diffèrent, il suffit de relancer l'exception. De cette façon, toutes les boucles internes sont supprimées.

Voici un exemple.

1 : eval { cravate @a, 'A' } ;
2 : sous A ::TIEARRAY {
3 : eval { mourir } ;
4 : mourir ;
5 : }

Pour exécuter ce code, "perl_run" est appelé, qui fait un "JMPENV_PUSH" puis entre dans un runops
boucle. Cette boucle exécute les opérations d'évaluation et de liaison sur la ligne 1, l'évaluation poussant un "CxEVAL"
sur la pile de contexte.

Le "pp_tie" effectue un "CATCH_SET(TRUE)", puis démarre une deuxième boucle runops pour exécuter le
corps de "TIEARRAY". Lorsqu'il exécute l'opération d'entrée sur la ligne 3, "CATCH_GET" est vrai, donc
"pp_entertry" appelle "docatch" qui fait un "JMPENV_PUSH" et démarre une troisième boucle runops,
qui exécute ensuite l'opération de dé. À ce stade, la pile d’appels C ressemble à ceci :

Perl_pp_die
Perl_runops # troisième boucle
S_docatch_body
S_docatch
Perl_pp_entertry
Perl_runops # deuxième boucle
S_call_body
Perl_call_sv
Perl_pp_tie
Perl_runops # première boucle
S_run_body
perl_run
principal

et le contexte et les piles de données, comme indiqué par "-Dstv", ressemblent à :

PILE 0 : PRINCIPAL
CX 0 : BLOC =>
CX 1 : EVAL => AV() PV("A"\0)
retop = quitter
PILE 1 : MAGIE
CX 0 : SOUS =>
retop=(nul)
CX 1 : ÉVAL => *
retop = état suivant

Le dé extrait le premier "CxEVAL" de la pile de contexte, définit "PL_restartop" à partir de celui-ci, effectue un
JMPENV_JUMP(3), et le contrôle revient au sommet "docatch". Cela commence alors un autre troisième-
niveau runops, qui exécute les opérations nextstate, pushmark et die sur la ligne 4. Au niveau
point que le deuxième "pp_die" est appelé, la pile d'appels C ressemble exactement à celle ci-dessus,
même si nous ne sommes plus dans une évaluation intérieure ; c'est à cause de l'optimisation
mentionné plus tôt. Cependant, la pile de contexte ressemble maintenant à ceci, c'est à dire avec le CxEVAL supérieur
sauté:

PILE 0 : PRINCIPAL
CX 0 : BLOC =>
CX 1 : EVAL => AV() PV("A"\0)
retop = quitter
PILE 1 : MAGIE
CX 0 : SOUS =>
retop=(nul)

Le dé de la ligne 4 ramène la pile de contexte au CxEVAL, la laissant ainsi :

PILE 0 : PRINCIPAL
CX 0 : BLOC =>

Comme d'habitude, "PL_restartop" est extrait du "CxEVAL", et un JMPENV_JUMP(3) fait, ce qui
renvoie la pile C dans le docatch :

S_docatch
Perl_pp_entertry
Perl_runops # deuxième boucle
S_call_body
Perl_call_sv
Perl_pp_tie
Perl_runops # première boucle
S_run_body
perl_run
principal

Dans ce cas, comme le niveau "JMPENV" enregistré dans le "CxEVAL" diffère du niveau "JMPENV" enregistré dans le "CxEVAL"
l'actuel, "docatch" fait juste un JMPENV_JUMP(3) et la pile C se déroule comme suit :

perl_run
principal

Parce que "PL_restartop" n'est pas nul, "run_body" démarre une nouvelle boucle et une nouvelle exécution runops
Poursuit.

INTERNE VARIABLE TYPES
Vous devriez déjà avoir jeté un œil à perlguts, qui vous renseigne sur le fonctionnement interne de Perl.
types de variables : SV, HV, AV et le reste. Sinon, faites-le maintenant.

Ces variables sont utilisées non seulement pour représenter les variables de l'espace Perl, mais aussi tout
constantes dans le code, ainsi que certaines structures complètement internes à Perl. Le symbole
table, par exemple, est un hachage Perl ordinaire. Votre code est représenté par un SV tel qu'il est
lire dans l'analyseur ; tous les fichiers de programme que vous appelez sont ouverts via des descripteurs de fichiers Perl ordinaires,
etc.

Le module principal Devel::Peek nous permet d'examiner les SV d'un programme Perl. Voyons, pour
par exemple, comment Perl traite la constante "bonjour".

% perl -MDevel::Peek -e 'Dump("bonjour")'
1 VS = PV(0xa041450) à 0xa04ecbc
2 REFCNT = 1
3 DRAPEAUX = (POK, LECTURE SEULEMENT, pPOK)
4 PV = 0xa0484e0 "bonjour"\0
5 CUR = 5
6 LEN = 6

La lecture du résultat "Devel::Peek" demande un peu de pratique, alors passons en revue ligne par ligne.

La ligne 1 nous indique que nous examinons un SV qui réside à 0xa04ecbc en mémoire. Les SV eux-mêmes
sont des structures très simples, mais elles contiennent un pointeur vers une structure plus complexe. Dans
dans ce cas, il s'agit d'un PV, une structure qui contient une valeur de chaîne, à l'emplacement 0xa041450. Doubler
2 est le décompte de référence ; il n'y a pas d'autres références à ces données, c'est donc 1.

La ligne 3 sont les drapeaux de ce SV - vous pouvez l'utiliser comme PV, c'est un SV en lecture seule (car
c'est une constante) et les données sont un PV en interne. Ensuite, nous avons le contenu du
chaîne, commençant à l’emplacement 0xa0484e0.

La ligne 5 nous donne la longueur actuelle de la chaîne - notez que cela ne ne sauraient inclure
terminateur nul. La ligne 6 n'est pas la longueur de la chaîne, mais la longueur du
tampon alloué ; à mesure que la chaîne grandit, Perl étend automatiquement le stockage disponible
via une routine appelée "SvGROW".

Vous pouvez obtenir n'importe laquelle de ces quantités à partir de C très facilement ; ajoutez simplement "Sv" au nom de
le champ affiché dans l'extrait, et vous avez une macro qui renverra la valeur :
"SvCUR(sv)" renvoie la longueur actuelle de la chaîne, "SvREFCOUNT(sv)" renvoie la
nombre de références, "SvPV(sv, len)" renvoie la chaîne elle-même avec sa longueur, et ainsi de suite.
D'autres macros permettant de manipuler ces propriétés peuvent être trouvées dans perlguts.

Prenons un exemple de manipulation d'un PV, depuis "sv_catpvn", dans sv.c

1 nul
2 Perl_sv_catpvn(pTHX_ SV *sv, const char *ptr, STRLEN len)
3 {
4 STRLEN tlen;
5 caractères *indésirables ;

6 indésirables = SvPV_force(sv, tlen);
7 SvGROW(sv, tlen + len + 1);
8 si (ptr == indésirable)
9 points = SvPVX(sv);
10 Move(ptr,SvPVX(sv)+tlen,len,char);
11 SvCUR(sv) += len;
12 *SvEND(sv) = '\0';
13 (vide)SvPOK_only_UTF8(sv); /* validation du pointeur */
14 SvTAINT(sv);
15}

C'est une fonction qui ajoute une chaîne, "ptr", de longueur "len" à la fin du PV
stocké dans "sv". La première chose que nous faisons à la ligne 6 est de nous assurer que le SV a un PV valide,
en appelant la macro "SvPV_force" pour forcer un PV. Comme effet secondaire, "tlen" est réglé sur
valeur actuelle du PV, et le PV lui-même est renvoyé dans la catégorie « indésirable ».

A la ligne 7, on s'assure que le SV aura suffisamment de place pour accueillir l'ancienne chaîne,
la nouvelle chaîne et le terminateur nul. Si "LEN" n'est pas assez grand, "SvGROW" le fera
réattribuer de l'espace pour nous.

Maintenant, si « indésirable » est identique à la chaîne que nous essayons d’ajouter, nous pouvons récupérer la chaîne
directement du SV; "SvPVX" est l'adresse du PV dans le SV.

La ligne 10 effectue la caténation proprement dite : la macro "Move" déplace une partie de la mémoire : nous
déplacez la chaîne "ptr" à la fin du PV - c'est le début du PV plus son courant
longueur. Nous déplaçons les octets "len" de type "char". Après cela, nous devons dire à Perl
nous avons étendu la chaîne en modifiant "CUR" pour refléter la nouvelle longueur. "SvEND" est une macro
ce qui nous donne la fin de la chaîne, donc cela doit être un "\0".

La ligne 13 manipule les drapeaux ; puisque nous avons modifié le PV, les valeurs IV ou NV ne seront plus
ne sera plus valide : si nous avons "$a=10; $a.="6";" nous ne voulons pas utiliser l'ancien IV de 10.
"SvPOK_only_utf8" est une version spéciale compatible UTF-8 de "SvPOK_only", une macro qui transforme
désactive les drapeaux IOK et NOK et active POK. Le "SvTAINT" final est une macro qui blanchit
données contaminées si le mode contamination est activé.

Les AV et les HV sont plus compliqués, mais les SV sont de loin le type de variable le plus courant étant
jeté autour. Après avoir vu un peu comment nous les manipulons, continuons et regardons
comment l'arbre opérationnel est construit.

OP DES ARBRES


Tout d’abord, qu’est-ce que l’arbre op, de toute façon ? L'arbre op est la représentation analysée de votre
programme, comme nous l'avons vu dans notre section sur l'analyse syntaxique, et c'est la séquence d'opérations qui
Perl exécute votre programme, comme nous l'avons vu dans "Exécution".

Une opération est une opération fondamentale que Perl peut effectuer : toutes les fonctions intégrées et
les opérateurs sont des opérations, et il existe une série d'opérations qui traitent des concepts que l'interprète
besoins en interne - entrer et sortir d'un bloc, terminer une instruction, récupérer une variable,
etc.

L'arbre op est connecté de deux manières : vous pouvez imaginer qu'il existe deux "routes" passant par
il, deux ordres dans lesquels vous pouvez parcourir l'arbre. Premièrement, l'ordre d'analyse reflète la façon dont le
l'analyseur a compris le code, et deuxièmement, l'ordre d'exécution indique à Perl quel ordre exécuter
les opérations dans.

La manière la plus simple d'examiner l'arborescence des opérations est d'arrêter Perl une fois l'analyse terminée, et
faites-lui jeter l'arbre. C'est exactement ce que le backend du compilateur B::Terse,
B :: Concise et B :: Debug le font.

Voyons comment Perl voit "$a = $b + $c" :

% perl -MO=Laconique -e '$a=$b+$c'
1 LISTOP (0x8179888) part
2 OP (0x81798b0) entrez
3 COP (0x8179850) état suivant
4 attribution de BINOP (0x8179828)
5 BINOP (0x8179800) ajouter [1]
6 UNOP (0x81796e0) nul [15]
7 SVOP (0x80fafe0) gvsv GV (0x80fa4cc) *b
8 UNOP (0x81797e0) nul [15]
9 SVOP (0x8179700) gvsv GV (0x80efeb0) *c
10 UNOP (0x816b4f0) nul [15]
11 SVOP (0x816dcf0) gvsv GV (0x80fa460) *a

Commençons par le milieu, à la ligne 4. Il s'agit d'un BINOP, un opérateur binaire, qui se trouve à la ligne XNUMX.
emplacement 0x8179828. L'opérateur spécifique en question est "sassign" - affectation scalaire -
et vous pouvez retrouver le code qui l'implémente dans la fonction "pp_sassign" dans pp_hot.c. Comme
un opérateur binaire, il a deux enfants : l'opérateur add, fournissant le résultat de "$b+$c",
est en haut sur la ligne 5 et le côté gauche est sur la ligne 10.

La ligne 10 est l'opération nulle : cela ne fait exactement rien. Qu'est-ce que ça fait là ? Si tu vois
l'opération nulle, c'est le signe que quelque chose a été optimisé après l'analyse. Comme nous
mentionnée dans "Optimisation", l'étape d'optimisation convertit parfois deux opérations en
un, par exemple lors de la récupération d'une variable scalaire. Quand cela arrive, au lieu de réécrire
l'arborescence op et en nettoyant les pointeurs pendants, il est plus facile de simplement remplacer le
opération redondante avec l'opération nulle. A l'origine, l'arbre aurait ressemblé à ceci :

10 SVOP (0x816b4f0) rv2sv [15]
11 SVOP (0x816dcf0) gv GV (0x80fa460) *a

Autrement dit, récupérez l'entrée "a" dans la table des symboles principale, puis regardez le scalaire
composant de celui-ci : "gvsv" ("pp_gvsv" dans pp_hot.c) arrive à faire ces deux choses.

Le membre de droite, commençant à la ligne 5, est similaire à ce que nous venons de voir : nous avons le
"ajouter" op ("pp_add" également dans pp_hot.c) additionnez deux "gvsv".

Maintenant, de quoi s'agit-il ?

1 LISTOP (0x8179888) part
2 OP (0x81798b0) entrez
3 COP (0x8179850) état suivant

« entrer » et « quitter » sont des opérations de cadrage, et leur travail consiste à effectuer tout entretien ménager à chaque fois.
heure d'entrée et de sortie d'un bloc : les variables lexicales sont rangées, variables non référencées
sont détruits, et ainsi de suite. Chaque programme aura ces trois premières lignes : "leave" est un
list, et ses enfants sont toutes les instructions du bloc. Les instructions sont délimitées par
"nextstate", donc un bloc est une collection d'opérations "nextstate", avec les opérations à effectuer
pour chaque instruction étant les enfants de "nextstate". "enter" est une seule opération qui
fonctionne comme un marqueur.

C'est ainsi que Perl a analysé le programme, de haut en bas :

Programme
|
Déclaration
|
=
/
/
$a +
/
$b $c

Cependant, il est impossible de effectuer les opérations dans cet ordre : il faut trouver le
les valeurs de $b et $c avant de les additionner, par exemple. Donc, l'autre fil qui
parcourt l'arborescence des opérations est l'ordre d'exécution : chaque opération a un champ "op_next" qui
pointe vers la prochaine opération à exécuter, donc suivre ces pointeurs nous indique comment Perl s'exécute
le code. Nous pouvons parcourir l'arborescence dans cet ordre en utilisant l'option "exec" de "B::Terse":

% perl -MO=Laconique,exec -e '$a=$b+$c'
1 OP (0x8179928) entrez
2 COP (0x81798c8) état suivant
3 SVOP (0x81796c8) gvsv GV (0x80fa4d4) *b
4 SVOP (0x8179798) gvsv GV (0x80efeb0) *c
5 BINOP (0x8179878) ajouter [1]
6 SVOP (0x816dd38) gvsv GV (0x80fa468) *a
7 BINOP (0x81798a0) attribution
8 LISTOP (0x8179900) part

Cela a probablement plus de sens pour un humain : entrez un bloc, démarrez une instruction. Obtenir le
valeurs de $b et $c, et additionnez-les ensemble. Trouvez $a et attribuez-en un à l'autre. Alors
laisser.

La façon dont Perl construit ces arbres d'opérations dans le processus d'analyse peut être décryptée par
l'examen perly.y, la grammaire YACC. Prenons la pièce dont nous avons besoin pour construire l'arbre
pour "$a = $b + $c"

1 terme : terme ASSIGNOP terme
2 { $$ = nouveauASSIGNOP(OPf_STACKED, $1, $2, $3); }
3 | terme ADDOP terme
4 { $$ = nouveauBINOP($2, 0, scalaire($1), scalaire($3)); }

Si vous n'avez pas l'habitude de lire les grammaires BNF, voici comment ça marche : Vous êtes nourri de certaines
les choses par le tokeniser, qui finissent généralement en majuscules. Ici, "ADDOP" est fourni
lorsque le tokeniser voit "+" dans votre code. "ASSIGNOP" est fourni lorsque "=" est utilisé pour
attribution. Ce sont des "symboles terminaux", car il n'y a rien de plus simple qu'eux.

La grammaire, lignes un et trois de l'extrait ci-dessus, vous indique comment développer davantage
formes complexes. Ces formes complexes, « symboles non terminaux » sont généralement placées en bas.
cas. "terme" est ici un symbole non terminal, représentant une expression unique.

La grammaire vous donne la règle suivante : vous pouvez faire la chose à gauche des deux points
si vous voyez toutes les choses à droite dans l'ordre. C'est ce qu'on appelle une « réduction », et le
le but de l’analyse est de réduire complètement l’entrée. Il existe plusieurs manières différentes de
effectuer une réduction, séparée par des barres verticales : donc, "terme" suivi de "=" suivi de
"terme" fait un "terme", et "terme" suivi de "+" suivi de "terme" peut aussi faire un
"terme".

Ainsi, si vous voyez deux termes avec un "=" ou un "+" entre eux, vous pouvez les transformer en un seul.
expression. Lorsque vous faites cela, vous exécutez le code dans le bloc de la ligne suivante : si vous
voir "=", vous ferez le code à la ligne 2. Si vous voyez "+", vous ferez le code à la ligne 4. C'est
ce code qui contribue à l'arbre op.

| terme ADDOP terme
{ $$ = nouveauBINOP($2, 0, scalaire($1), scalaire($3)); }

Cela crée une nouvelle opération binaire et lui alimente un certain nombre de variables. Le
les variables font référence aux jetons : $1 est le premier jeton de l'entrée, $2 le deuxième, et ainsi de suite.
on - pensez aux références arrière d'expressions régulières. $$ est l'opération renvoyée par cette réduction.
Nous appelons donc « newBINOP » pour créer un nouvel opérateur binaire. Le premier paramètre de "newBINOP",
une fonction dans op.c, est le type d'opération. C'est un opérateur d'addition, nous voulons donc que le type soit
"AJOUTER". Nous pourrions le spécifier directement, mais il est là comme deuxième jeton dans le
entrée, nous utilisons donc 2 $. Le deuxième paramètre concerne les drapeaux de l'opération : 0 signifie "rien de spécial".
Ensuite les choses à ajouter : les côtés gauche et droit de notre expression, dans un contexte scalaire.

Piles


Lorsque Perl exécute quelque chose comme "addop", comment transmet-il ses résultats à l'opération suivante ?
La réponse est grâce à l’utilisation de piles. Perl a un certain nombre de piles pour stocker les éléments
nous travaillons actuellement sur ce sujet, et nous examinerons ici les trois plus importants.

Argument empiler
Les arguments sont transmis au code PP et renvoyés par le code PP à l'aide de la pile d'arguments "ST".
La manière typique de gérer les arguments est de les retirer de la pile, de les traiter comme vous le souhaitez.
souhaitez, puis repoussez le résultat sur la pile. C'est ainsi que, par exemple, le cosinus
l'opérateur travaille :

Valeur NV ;
valeur = POPn ;
valeur = Perl_cos(valeur);
XPUSHn(valeur);

Nous en verrons un exemple plus délicat lorsque nous considérerons les macros Perl ci-dessous. "POPn" donne
vous le NV (valeur à virgule flottante) du SV supérieur sur la pile : le $x dans "cos($x)". Ensuite nous
calculez le cosinus et repoussez le résultat sous forme de NV. Le « X » dans « XPUSHn » signifie que le
la pile doit être étendue si nécessaire - cela ne peut pas être nécessaire ici, car nous savons
il y a de la place pour un élément supplémentaire sur la pile, puisque nous venons d'en supprimer un ! Le "XPUSH*"
les macros garantissent au moins la sécurité.

Alternativement, vous pouvez manipuler directement la pile : "SP" vous donne le premier élément de
votre partie de la pile, et "TOP*" vous donne le meilleur SV/IV/NV/etc. sur la pile. Donc,
par exemple, pour faire la négation unaire d'un entier :

SETi(-TOPi);

Définissez simplement la valeur entière de l’entrée supérieure de la pile sur sa négation.

La manipulation de la pile d'arguments dans le noyau est exactement la même que dans les XSUB - voir
perlxstut, perlxs et perlguts pour une description plus longue des macros utilisées dans la pile
manipulation.

Marquez empiler
Je dis "votre partie de la pile" ci-dessus car le code PP n'obtient pas nécessairement la totalité
pile sur elle-même : si votre fonction appelle une autre fonction, vous souhaiterez uniquement exposer le
arguments destinés à la fonction appelée, et ne pas (nécessairement) la laisser prendre le dessus sur vous-même
données. La façon dont nous procédons est d'avoir un bas de pile "virtuel", exposé à chaque
fonction. La pile de marques conserve les signets à des emplacements de la pile d'arguments utilisables par chacun.
fonction. Par exemple, lorsqu'il s'agit d'une variable liée (en interne, quelque chose avec "P"
magique) Perl doit appeler des méthodes pour accéder aux variables liées. Cependant, nous devons
séparer les arguments exposés à la méthode de l'argument exposé à l'original
fonction - le magasin ou la récupération ou quoi que ce soit. Voici à peu près comment se déroule le "push" lié
est implémenté; voir "av_push" dans av.c:

1 POUSSOIR (SP);
2 PROLONGER(SP,2);
3 PUSH(SvTIED_obj((SV*)av, mg));
4 PUSH (val);
5 REPORT;
6 ENTRER ;
7 call_method("PUSH", G_SCALAR|G_DISCARD);
8 PARTIR ;

Examinons l'ensemble de l'implémentation, pour nous entraîner :

1 POUSSOIR (SP);

Poussez l'état actuel du pointeur de pile sur la pile de marques. C'est pour que quand
nous avons fini d'ajouter des éléments à la pile d'arguments, Perl sait combien de choses nous avons ajoutées
récemment.

2 PROLONGER(SP,2);
3 PUSH(SvTIED_obj((SV*)av, mg));
4 PUSH (val);

Nous allons ajouter deux éléments supplémentaires à la pile d'arguments : lorsque vous avez un tableau lié, le
Le sous-programme "PUSH" reçoit l'objet et la valeur à pousser, et c'est exactement ce que
nous avons ici - l'objet lié, récupéré avec "SvTIED_obj", et la valeur, le SV "val".

5 REPORT;

Ensuite, nous disons à Perl de mettre à jour le pointeur de pile global à partir de notre variable interne : "dSP".
nous a seulement donné une copie locale, pas une référence au global.

6 ENTRER ;
7 call_method("PUSH", G_SCALAR|G_DISCARD);
8 PARTIR ;

"ENTER" et "LEAVE" localisent un bloc de code - ils s'assurent que toutes les variables sont
rangé, tout ce qui a été localisé retrouve sa valeur précédente, et ainsi de suite.
Considérez-les comme les "{" et "}" d'un bloc Perl.

Pour effectuer réellement l'appel de la méthode magique, nous devons appeler un sous-programme dans l'espace Perl :
"call_method" s'en charge, et c'est décrit dans perlcall. Nous appelons le "PUSH"
méthode dans un contexte scalaire, et nous allons supprimer sa valeur de retour. Le méthode_appel()
La fonction supprime l'élément supérieur de la pile de marques, donc l'appelant n'a rien à faire.
nettoyer.

Enregistré empiler
C n'a pas de concept de portée locale, donc Perl en fournit un. Nous avons vu que "ENTER" et
« LEAVE » sont utilisés comme accolades de portée ; la pile de sauvegarde implémente l'équivalent C de, par exemple
Exemple:

{
local $foo = 42 ;

}

Voir "Localisation des modifications" dans perlguts pour savoir comment utiliser la pile de sauvegarde.

DES MILLIONS OF MACRO


Une chose que vous remarquerez à propos des sources Perl est qu'elles regorgent de macros. Certains ont
ont qualifié l'utilisation omniprésente des macros de chose la plus difficile à comprendre, d'autres trouvent que cela ajoute à
clarté. Prenons un exemple, le code qui implémente l'opérateur d'addition :

1 PP(pp_add)
2 {
3 dSP ; dATARGET; tryAMAGICbin(ajouter,opASSIGN);
4 {
5 dPOPTOPnnrl_ul ;
6 SETn(gauche + droite);
7 RETOUR ;
8}
9}

Chaque ligne ici (à l'exception des accolades, bien sûr) contient une macro. Les ensembles de première ligne
établir la déclaration de fonction comme Perl l'attend pour le code PP ; la ligne 3 définit la variable
déclarations pour la pile d'arguments et la cible, la valeur de retour de l'opération.
Enfin, il essaie de voir si l’opération d’addition est surchargée ; si oui, le approprié
le sous-programme est appelé.

La ligne 5 est une autre déclaration de variable - toutes les déclarations de variables commencent par "d" - qui
apparaît du haut de l'argument, empile deux NV (d'où "nn") et les place dans le
variables "right" et "left", d'où le "rl". Ce sont les deux opérandes de l'addition
opérateur. Ensuite, nous appelons "SETn" pour définir le NV de la valeur de retour sur le résultat de l'ajout
les deux valeurs. Ceci fait, nous revenons - la macro "RETURN" s'assure que notre valeur de retour
est correctement géré et nous transmettons à l'opérateur suivant le retour à la boucle d'exécution principale.

La plupart de ces macros sont expliquées en perlapi, et certaines des plus importantes sont
expliqué également dans Perlxs. Portez une attention particulière à « Contexte et
PERL_IMPLICIT_CONTEXT" dans perlguts pour plus d'informations sur les macros "[pad]THX_?".

PLUS LOIN LECTURE


Pour plus d'informations sur les composants internes de Perl, veuillez consulter les documents répertoriés dans « Internes
et interface en langage C" en perl.

Utilisez Perlinterp en ligne à l'aide des services onworks.net



Derniers programmes en ligne Linux et Windows