EnglishFranceseSpagnolo

Favicon di OnWorks

perlinterp: online nel cloud

Esegui perlinterp nel provider di hosting gratuito OnWorks su Ubuntu Online, Fedora Online, emulatore online Windows o emulatore online MAC OS

Questo è il comando perlinterp che può essere eseguito nel provider di hosting gratuito OnWorks utilizzando una delle nostre molteplici workstation online gratuite come Ubuntu Online, Fedora Online, emulatore online Windows o emulatore online MAC OS

PROGRAMMA:

NOME


perlinterp - Una panoramica dell'interprete Perl

DESCRIZIONE


Questo documento fornisce una panoramica di come funziona l'interprete Perl a livello di C
codice, insieme ai puntatori ai file di codice sorgente C pertinenti.

ELEMENTI OF freschi INTERPRETE


Il lavoro dell'interprete ha due fasi principali: compilazione del codice all'interno
rappresentazione, o bytecode, e quindi eseguirlo. Spiega "codice compilato" in perlguts
esattamente come avviene la fase di compilazione.

Ecco una breve descrizione del funzionamento di Perl:

Startup
L'azione inizia tra perlmain.c. (o miniperlmain.c per miniperl) Questo è di altissimo livello
codice, abbastanza da stare su un unico schermo, e assomiglia al codice trovato in perlembed; maggior parte
dell'azione reale si svolge in perl.c

perlmain.c è generato da "ExtUtils::Miniperl" da miniperlmain.c a tempo debito, quindi tu
dovrebbe fare in modo che Perl segua questo processo.

In primo luogo, perlmain.c alloca della memoria e costruisce un interprete Perl, insieme a questa
Linee:

1 PERL_SYS_INIT3(&argc,&argv,&env);
2
3 se (!PL_do_undump) {
4 mio_perl = perl_alloc();
5 se (!mio_perl)
6 exit(1);
7 perl_costrutto(mio_perl);
8 PL_perl_destruct_level = 0;
9}

La riga 1 è una macro e la sua definizione dipende dal sistema operativo. Linea 3
fa riferimento a "PL_do_undump", una variabile globale - tutte le variabili globali in Perl iniziano con
"PL_". Questo ti dice se il programma attualmente in esecuzione è stato creato con il flag "-u".
perl e poi scaricare, il che significa che sarà falso in qualsiasi contesto sano.

La riga 4 richiama una funzione perl.c per allocare memoria per un interprete Perl. È piuttosto un
funzione semplice, e il suo aspetto assomiglia a questo:

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

Qui vedi un esempio di astrazione del sistema Perl, che vedremo più avanti:
"PerlMem_malloc" è il "malloc" del tuo sistema o il "malloc" di Perl come definito in
malloc.c se hai selezionato questa opzione al momento della configurazione.

Successivamente, nella riga 7, costruiamo l'interprete utilizzando perl_construct, anch'esso in perl.c; Questo
imposta tutte le variabili speciali di cui Perl ha bisogno, gli stack e così via.

Ora passiamo a Perl le opzioni della riga di comando e gli diciamo di andare:

exitstatus = perl_parse(my_perl, xs_init, argc, argv, (char **)NULL);
se (!stato di uscita)
perl_run(mio_perl);

stato di uscita = perl_destruct(mio_perl);

perl_free(mio_perl);

"perl_parse" è in realtà un wrapper attorno a "S_parse_body", come definito in perl.c, quale
elabora le opzioni della riga di comando, configura eventuali moduli XS collegati staticamente, apre il file
programma e chiama "yyparse" per analizzarlo.

parsing
Lo scopo di questa fase è prendere il sorgente Perl e trasformarlo in un albero operativo. Vedremo
come apparirà uno di questi più tardi. A rigor di termini, ci sono tre cose che succedono qui.

"yyparse", il parser, vive lì perly.c, anche se faresti meglio a leggere l'originale
Ingresso YACC perly.y. (Sì, Virginia, ecco is una grammatica YACC per Perl!) Il lavoro di
parser è prendere il tuo codice e "comprenderlo", dividendolo in frasi, decidendo
quali operandi vanno con quali operatori e così via.

Il parser è nobilmente assistito dal lexer, che suddivide il tuo input in token, e
decide che tipo di cosa è ciascun token: un nome di variabile, un operatore, una bareword, a
subroutine, una funzione principale e così via. Il principale punto di accesso al lexer è "yylex",
e questo e le routine ad esso associate possono essere trovate in toke.c. Perl non è molto simile agli altri
linguaggi informatici; a volte è altamente sensibile al contesto e può essere difficile da capire
che tipo di token è qualcosa o dove finisce un token. In quanto tale, ce ne sono molti
interazione tra il tokenizzatore e il parser, che può diventare piuttosto spaventosa se lo sei
non ci sono abituato.

Quando il parser comprende un programma Perl, costruisce un albero di operazioni per il programma
interprete da eseguire durante l'esecuzione. Le routine che costruiscono e collegano insieme
le varie operazioni si trovano in op.c, e verrà esaminato in seguito.

OTTIMIZZAZIONE
Ora la fase di analisi è completa e l'albero finito rappresenta le operazioni che
l'interprete Perl deve eseguire per eseguire il nostro programma. Successivamente, Perl esegue un giro di prova
sopra l'albero alla ricerca di ottimizzazioni: saranno espressioni costanti come "3 + 4".
calcolato ora e l'ottimizzatore vedrà anche se è possibile sostituire più operazioni
con uno solo. Ad esempio, per recuperare la variabile $foo, invece di prendere il glob
*foo e osservando il componente scalare, l'ottimizzatore manipola l'albero operativo per utilizzare a
funzione che cerca direttamente lo scalare in questione. L'ottimizzatore principale è "peep" in
op.ce molte operazioni hanno le proprie funzioni di ottimizzazione.

corsa
Ora siamo finalmente pronti a partire: abbiamo compilato il bytecode Perl e tutto ciò che resta da fare
è eseguirlo. L'esecuzione effettiva viene eseguita dalla funzione "runops_standard" in eseguire.c; Di più
nello specifico, è fatto da queste tre linee dall'aspetto innocente:

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

Potresti sentirti più a tuo agio con la versione Perl di questo:

PERL_ASYNC_CHECK() mentre $Perl::op = &{$Perl::op->{funzione}};

Beh, forse no. Ad ogni modo, ogni operazione contiene un puntatore a funzione, che stabilisce il
funzione che effettuerà effettivamente l’operazione. Questa funzione restituirà il successivo
op in sequenza: ciò consente cose come "if" che scelgono dinamicamente l'operazione successiva
in fase di esecuzione. Il "PERL_ASYNC_CHECK" si assicura che cose come i segnali si interrompano
esecuzione se richiesta.

Le funzioni effettivamente richiamate sono note come codice PP e sono distribuite in quattro file:
pp_hot.c contiene il codice "caldo", che viene utilizzato più spesso e altamente ottimizzato, pp_sys.c
contiene tutte le funzioni specifiche del sistema, pp_ctl.c contiene le funzioni che
implementare strutture di controllo ("se", "mentre" e simili) e ppc contiene tutto
altro. Se preferite, questi sono il codice C per le funzioni e gli operatori incorporati in Perl.

Si noti che è previsto che ciascuna funzione "pp_" restituisca un puntatore all'operazione successiva. Chiama a
i sottotitoli perl (e i blocchi eval) vengono gestiti all'interno dello stesso ciclo runops e non consumano
spazio extra nello stack C. Ad esempio, "pp_entersub" e "pp_entertry" basta premere a
Struttura del blocco "CxSUB" o "CxEVAL" sullo stack di contesto che contiene l'indirizzo del file
op dopo la sottochiamata o eval. Quindi restituiscono la prima operazione di quel sub o eval
blocco, e così l'esecuzione di quel sottotitolo o blocco continua. Successivamente, un "pp_leavesub" o
L'operazione "pp_leavetry" apre "CxSUB" o "CxEVAL", recupera l'operazione di ritorno da esso e
lo restituisce.

Eccezione passaggio
La gestione delle eccezioni di Perl (ad esempio "die" ecc.) è costruita sul livello basso
Funzioni della libreria C "setjmp()"/"longjmp()". Questi forniscono fondamentalmente un modo per catturare i file
i registri attuali del PC e dell'SP e successivamente ripristinarli; cioè un "longjmp()" continua al
punto nel codice in cui è stato eseguito un precedente "setjmp()", con qualsiasi cosa più in alto nel C
pila andata perduta. Questo è il motivo per cui il codice dovrebbe sempre salvare i valori utilizzando "SAVE_FOO" anziché
nelle variabili automatiche.

Il core perl racchiude "setjmp()" ecc. nelle macro "JMPENV_PUSH" e "JMPENV_JUMP". IL
La regola base delle eccezioni Perl è che "exit" e "die" (in assenza di "eval") vengono eseguiti
a JMPENV_JUMP(2), mentre "die" all'interno di "eval" fa a JMPENV_JUMP(3).

Nei punti di ingresso a perl, come "perl_parse()", "perl_run()" e "call_sv(cv, G_EVAL)"
ognuno esegue un "JMPENV_PUSH", quindi inserisce un ciclo runops o qualsiasi altra cosa e gestisce il possibile
ritorni di eccezione. Per un ritorno di 2, viene eseguita la pulizia finale, come l'estrazione degli stack e
richiamando i blocchi "CHECK" o "END". Tra le altre cose, questo è ancora il modo in cui viene effettuata la pulizia dell'ambito
avviene durante una "uscita".

Se un "dado" riesce a trovare un blocco "CxEVAL" nello stack di contesto, allora lo stack viene estratto
quel livello e l'operazione di restituzione in quel blocco sono assegnati a "PL_restartop"; poi un
JMPENV_JUMP(3) viene eseguito. Questo normalmente restituisce il controllo alla guardia. Nel caso
di "perl_run" e "call_sv", un "PL_restartop" non nullo attiva il rientro nei runops
ciclo continuo. Questo è il modo normale in cui "morire" o "gracchiare" viene gestito all'interno di una "eval".

A volte le operazioni vengono eseguite all'interno di un ciclo runops interno, come pareggio, ordinamento o sovraccarico
codice. In questo caso, qualcosa del genere

sub FETCH { eval { die } }

causerebbe un longjmp direttamente alla guardia in "perl_run", facendo scattare entrambi i cicli runops,
il che è chiaramente errato. Un modo per evitare ciò è che il codice di parità esegua a
"JMPENV_PUSH" prima di eseguire "FETCH" nel ciclo runops interno, ma per efficienza
per ragioni, perl in effetti imposta semplicemente un flag, usando "CATCH_SET(TRUE)". Il "pp_require",
Gli operatori "pp_entereval" e "pp_entertry" controllano questo flag e, se vero, chiamano "docatch",
che esegue un "JMPENV_PUSH" e avvia un nuovo livello runops per eseguire il codice, anziché
farlo sul ciclo corrente.

Come ulteriore ottimizzazione, all'uscita dal blocco eval nel "FETCH", esecuzione del file
il codice che segue il blocco viene ancora portato avanti nel ciclo interno. Quando un'eccezione è
sollevato, "docatch" confronta il livello "JMPENV" di "CxEVAL" con "PL_top_env" e se
differiscono, rilancia semplicemente l'eccezione. In questo modo eventuali anelli interni vengono scoppiati.

Ecco un esempio.

1: eval { pareggio @a, 'A' };
2: sottotitolo A::TIEARRAY {
3: eval { morire };
4: morire;
5: }

Per eseguire questo codice, viene chiamato "perl_run", che esegue un "JMPENV_PUSH" quindi inserisce un runops
ciclo continuo. Questo ciclo esegue le operazioni di valutazione e pareggio sulla riga 1, con la valutazione che spinge un "CxEVAL"
sullo stack di contesto.

Il "pp_tie" esegue un "CATCH_SET(TRUE)", quindi avvia un secondo ciclo runops per eseguire il
corpo di "TIEARRAY". Quando esegue l'operazione di immissione sulla riga 3, "CATCH_GET" è vero, quindi
"pp_entertry" chiama "docatch" che esegue un "JMPENV_PUSH" e avvia un terzo ciclo runops,
che poi esegue il dado op. A questo punto lo stack di chiamate C si presenta così:

Perl_pp_die
Perl_runops # terzo ciclo
S_docach_body
S_docatch
Perl_pp_entertry
Perl_runops # secondo ciclo
S_call_body
Perl_call_sv
Perl_pp_tie
Perl_runops # primo ciclo
S_run_body
perl_run
principale

e il contesto e gli stack di dati, come mostrato da "-Dstv", assomigliano a:

PILA 0: PRINCIPALE
CX 0: BLOCCO =>
CX 1: EVAL => AV() PV("A"\0)
retop=andare
PILA 1: MAGIA
CX 0: SUB =>
retop=(null)
CX 1: VALUTAZIONE => *
retop=stato successivo

Il dado estrae il primo "CxEVAL" dallo stack di contesto, imposta "PL_restartop" da esso, esegue un
JMPENV_JUMP(3) e il controllo ritorna al "docatch" superiore. Questo poi inizia un altro terzo-
level runops level, che esegue le operazioni nextstate, pushmark e die sulla riga 4. Al
punto in cui viene chiamato il secondo "pp_die", lo stack di chiamate C appare esattamente come quello sopra,
anche se non siamo più all'interno di una valutazione interiore; questo è dovuto all'ottimizzazione
menzionato prima. Tuttavia, lo stack di contesto ora appare così, ovvero con il CxEVAL in alto
spuntato:

PILA 0: PRINCIPALE
CX 0: BLOCCO =>
CX 1: EVAL => AV() PV("A"\0)
retop=andare
PILA 1: MAGIA
CX 0: SUB =>
retop=(null)

Il dado alla riga 4 riporta lo stack di contesto al CxEVAL, lasciandolo come:

PILA 0: PRINCIPALE
CX 0: BLOCCO =>

Come al solito, "PL_restartop" viene estratto da "CxEVAL" e a JMPENV_JUMP(3) fatto, che
riporta lo stack C al docatch:

S_docatch
Perl_pp_entertry
Perl_runops # secondo ciclo
S_call_body
Perl_call_sv
Perl_pp_tie
Perl_runops # primo ciclo
S_run_body
perl_run
principale

In questo caso, poiché il livello "JMPENV" registrato in "CxEVAL" differisce da
quello attuale, "docatch" fa solo a JMPENV_JUMP(3) e lo stack C si svolge in:

perl_run
principale

Poiché "PL_restartop" non è nullo, "run_body" avvia un nuovo ciclo ed esecuzione runops
Continua.

INTERNO VARIABILE TIPI
A questo punto dovresti aver dato un'occhiata a perlguts, che ti parla delle parti interne di Perl
tipi di variabili: SV, HV, AV e il resto. In caso contrario, fallo adesso.

Queste variabili vengono utilizzate non solo per rappresentare le variabili dello spazio Perl, ma anche qualsiasi
costanti nel codice, nonché alcune strutture completamente interne a Perl. Il simbolo
table, ad esempio, è un normale hash Perl. Il tuo codice è rappresentato da un SV così com'è
leggere nel parser; tutti i file di programma richiamati vengono aperti tramite normali filehandle Perl,
e così via.

Il modulo principale Devel::Peek ci consente di esaminare gli SV di un programma Perl. Vediamo, per
ad esempio, come Perl tratta la costante "ciao".

% perl -MDevel::Peek -e 'Dump("ciao")'
1VV = PV(0xa041450) su 0xa04ecbc
2 RIF.NT = 1
3 BANDIERE = (POK,READONLY,pPOK)
4 PV = 0xa0484e0 "ciao"\0
5 CUR = 5
6 LUNGHEZZA = 6

Leggere l'output di "Devel::Peek" richiede un po' di pratica, quindi esaminiamolo riga per riga.

La riga 1 ci dice che stiamo guardando un SV che vive in memoria a 0xa04ecbc. Gli stessi SV
sono strutture molto semplici, ma contengono un puntatore a una struttura più complessa. In
in questo caso si tratta di un PV, una struttura che contiene un valore stringa, nella posizione 0xa041450. Linea
2 è il conteggio di riferimento; non ci sono altri riferimenti a questo dato, quindi è 1.

La riga 3 sono i flag per questo SV: va bene usarlo come PV, è un SV di sola lettura (perché
è una costante) e i dati sono un PV internamente. Successivamente abbiamo il contenuto del file
stringa, a partire dalla posizione 0xa0484e0.

La riga 5 ci fornisce la lunghezza attuale della stringa: nota che è così non è un includono l'
terminatore nullo. La riga 6 non è la lunghezza della stringa, ma la lunghezza del file current
buffer allocato; man mano che la stringa cresce, Perl estende automaticamente lo spazio di archiviazione disponibile
tramite una routine chiamata "SvGROW".

Puoi ottenere qualsiasi di queste quantità da C molto facilmente; basta aggiungere "Sv" al nome di
il campo mostrato nello snippet e hai una macro che restituirà il valore:
"SvCUR(sv)" restituisce la lunghezza corrente della stringa, "SvREFCOUNT(sv)" restituisce la
conteggio dei riferimenti, "SvPV(sv, len)" restituisce la stringa stessa con la sua lunghezza e così via.
Altre macro per manipolare queste proprietà possono essere trovate in perlguts.

Facciamo un esempio di manipolazione di un PV, da "sv_catpvn", in sv.c

1 nulla
2 Perl_sv_catpvn(pTHX_ SV *sv, const char *ptr, STRLEN len)
3 {
4 STRLEN tlen;
5 caratteri *spazzatura;

6 spazzatura = SvPV_force(sv, tlen);
7 SvGROW(sv, tlen + len + 1);
8 se (ptr == spazzatura)
9 ptr = SvPVX(sv);
10 Sposta(ptr,SvPVX(sv)+tlen,len,char);
11 SvCUR(sv) += len;
12 *SvEND(sv) = '\0';
13 (void)SvPOK_only_UTF8(sv); /* convalida il puntatore */
14 SvTAINT(sv);
15}

Questa è una funzione che aggiunge una stringa, "ptr", di lunghezza "len" alla fine del PV
memorizzato in "sv". La prima cosa che facciamo nella riga 6 è assicurarci che SV ha un PV valido,
richiamando la macro "SvPV_force" per forzare un PV. Come effetto collaterale, "tlen" viene impostato su
valore corrente del PV e il PV stesso viene restituito alla "spazzatura".

Nella riga 7, ci assicuriamo che l'SV abbia abbastanza spazio per accogliere la vecchia corda,
la nuova stringa e il terminatore null. Se "LEN" non è abbastanza grande, "SvGROW" lo farà
riallocare lo spazio per noi.

Ora, se "junk" è uguale alla stringa che stiamo cercando di aggiungere, possiamo prendere la stringa
direttamente dalla SV; "SvPVX" è l'indirizzo del PV nella SV.

La riga 10 esegue l'effettiva concatenazione: la macro "Sposta" sposta una porzione di memoria: we
sposta la stringa "ptr" alla fine del PV: questo è l'inizio del PV più la sua corrente
lunghezza. Stiamo spostando "len" byte di tipo "char". Dopo averlo fatto, dobbiamo dirlo a Perl
abbiamo esteso la stringa, alterando "CUR" per riflettere la nuova lunghezza. "SvEND" è una macro
che ci dà la fine della stringa, quindi deve essere un "\0".

La linea 13 manipola le flag; poiché abbiamo modificato il PV, qualsiasi valore IV o NV non sarà più
non sarà più valido: se abbiamo "$a=10; $a.="6";" non vogliamo utilizzare la vecchia IV di 10.
"SvPOK_only_utf8" è una versione speciale compatibile con UTF-8 di "SvPOK_only", una macro che trasforma
disattiva i flag IOK e NOK e attiva POK. L'ultimo "SvTAINT" è una macro che ricicla
dati contaminati se la modalità contaminazione è attivata.

AV e HV sono più complicati, ma gli SV sono di gran lunga il tipo di variabile più comune
gettato in giro. Avendo visto qualcosa su come li manipoliamo, andiamo avanti e guardiamo
come è costruito l'albero operativo.

OP ALBERI


Innanzitutto, qual è l'albero operativo? L'albero delle operazioni è la rappresentazione analizzata del tuo
programma, come abbiamo visto nella nostra sezione sull'analisi, ed è la sequenza di operazioni che
Perl esegue il programma, come abbiamo visto in "Running".

Un op è un'operazione fondamentale che Perl può eseguire: tutte le funzioni integrate e
gli operatori sono op e ci sono una serie di op che trattano i concetti dell'interprete
esigenze interne: entrare e uscire da un blocco, terminare un'istruzione, recuperare una variabile,
e così via.

L'albero delle operazioni è collegato in due modi: puoi immaginare che ci siano due "percorsi" da attraversare
it, due ordini in cui puoi attraversare l'albero. Innanzitutto, l'ordine di analisi riflette il modo in cui
il parser ha capito il codice e, in secondo luogo, l'ordine di esecuzione dice a perl quale ordine eseguire
le operazioni dentro.

Il modo più semplice per esaminare l'albero delle operazioni è fermare Perl dopo che ha terminato l'analisi, e
fallo scaricare dall'albero. Questo è esattamente ciò che il backend del compilatore B::Terse,
B::Concise e B::Debug lo fanno.

Diamo un'occhiata a come Perl vede "$a = $b + $c":

% perl -MO=Conciso -e '$a=$b+$c'
1 LISTOP (0x8179888) lascia
2 OP (0x81798b0) entrare
3 COP (0x8179850) stato successivo
4 BINOP (0x8179828) assegna
5 BINOP (0x8179800) aggiungi [1]
6 UNOP (0x81796e0) nullo [15]
7 SVOP (0x80fafe0) gvsv GV (0x80fa4cc) *b
8 UNOP (0x81797e0) nullo [15]
9 SVOP (0x8179700) gvsv GV (0x80efeb0) *c
10 UNOP (0x816b4f0) nullo [15]
11 SVOP (0x816dcf0) gvsv GV (0x80fa460) *a

Iniziamo dal centro, dalla riga 4. Questo è un BINOP, un operatore binario, che si trova in
posizione 0x8179828. L'operatore specifico in questione è "sassign" - assegnazione scalare -
e puoi trovare il codice che lo implementa nella funzione "pp_sassign" in pp_hot.c. I
un operatore binario, ha due figli: l'operatore add, che fornisce il risultato di "$b+$c",
è in alto sulla linea 5 e il lato sinistro è sulla linea 10.

La riga 10 è l'operazione nulla: non fa esattamente nulla. Cosa ci fa lì? Se tu vedi
l'operazione nulla, è un segno che qualcosa è stato ottimizzato dopo l'analisi. Come noi
menzionata in "Ottimizzazione", la fase di ottimizzazione a volte converte due operazioni in
uno, ad esempio quando si recupera una variabile scalare. Quando ciò accade, invece di riscrivere
l'albero delle operazioni e ripulire i puntatori penzolanti, è più semplice semplicemente sostituire il
operazione ridondante con l'op null. Originariamente l'albero avrebbe avuto questo aspetto:

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

Cioè, prendi la voce "a" dalla tabella dei simboli principale e poi guarda lo scalare
componente di esso: "gvsv" ("pp_gvsv" in pp_hot.c) sembra fare entrambe le cose.

Il lato destro, a partire dalla riga 5, è simile a quello che abbiamo appena visto: abbiamo il
Opzione "aggiungi" ("pp_add" anche in pp_hot.c) sommare due "gvsv".

Ora, di cosa si tratta?

1 LISTOP (0x8179888) lascia
2 OP (0x81798b0) entrare
3 COP (0x8179850) stato successivo

"entrare" e "uscire" sono operazioni di ricerca e il loro compito è eseguire ogni operazione di pulizia
volta che entri e esci da un blocco: le variabili lessicali vengono riordinate, variabili senza riferimenti
vengono distrutti e così via. Ogni programma avrà quelle prime tre righe: "leave" è a
list, e i suoi figli sono tutte le istruzioni nel blocco. Le dichiarazioni sono delimitate da
"nextstate", quindi un blocco è una raccolta di operazioni "nextstate", con le operazioni da eseguire
per ogni affermazione sono i figli di "nextstate". "enter" è una singola operazione che
funziona come un marcatore.

Ecco come Perl ha analizzato il programma, dall'alto al basso:

Programma
|
dichiarazione
|
=
/ \
/ \
$a+
/ \
$ b $ c

Tuttavia, è impossibile eseguire le operazioni in questo ordine: devi trovare il
valori di $b e $c prima di sommarli insieme, ad esempio. Quindi, l'altro thread quello
scorre attraverso l'albero delle operazioni è l'ordine di esecuzione: ogni operazione ha un campo "op_next" che
punta alla prossima operazione da eseguire, quindi seguire questi puntatori ci dice come viene eseguito perl
il codice. Possiamo attraversare l'albero in questo ordine usando l'opzione "exec" su "B::Terse":

% perl -MO=Conciso,exec -e '$a=$b+$c'
1 OP (0x8179928) inserire
2 COP (0x81798c8) stato successivo
3 SVOP (0x81796c8) gvsv GV (0x80fa4d4) *b
4 SVOP (0x8179798) gvsv GV (0x80efeb0) *c
5 BINOP (0x8179878) aggiungi [1]
6 SVOP (0x816dd38) gvsv GV (0x80fa468) *a
7 BINOP (0x81798a0) assegna
8 LISTOP (0x8179900) lascia

Questo probabilmente ha più senso per un essere umano: inserisci un blocco, inizia un'istruzione. Ottenere il
valori di $b e $c e sommarli insieme. Trova $a e assegna l'uno all'altro. Poi
partire.

Il modo in cui Perl costruisce questi alberi operativi nel processo di analisi può essere chiarito
l'esame di perly.y, la grammatica YACC. Prendiamo il pezzo che ci serve per costruire l'albero
per "$a = $b + $c"

1 termine: termine ASSIGNOP termine
2 { $$ = nuovo ASSIGNOP(OPf_STACKED, $1, $2, $3); }
3| termine ADDOP termine
4 { $$ = nuovoBINOP($2, 0, scalare($1), scalare($3)); }

Se non sei abituato a leggere le grammatiche BNF, funziona così: ne hai la certezza
cose dal tokeniser, che generalmente finiscono in maiuscolo. Qui viene fornito "ADDOP".
quando il tokenizzatore vede "+" nel tuo codice. "ASSIGNOP" viene fornito quando viene utilizzato "=" per
assegnare. Questi sono "simboli terminali", perché non c'è niente di più semplice di loro.

La grammatica, righe uno e tre dello snippet sopra, ti dice come accumularne di più
forme complesse. Queste forme complesse, i "simboli non terminali", sono generalmente posizionati in basso
caso. "termine" qui è un simbolo non terminale, che rappresenta una singola espressione.

La grammatica ti dà la seguente regola: puoi fare la cosa a sinistra dei due punti
se vedi tutte le cose a destra in sequenza. Questa è chiamata "riduzione" e il
lo scopo dell'analisi è ridurre completamente l'input. Esistono diversi modi per farlo
eseguire una riduzione, separata da barre verticali: quindi, "termine" seguito da "=" seguito da
"term" crea un "termine" e "termine" seguito da "+" seguito da "term" può anche formare a
"termine".

Quindi, se vedi due termini con un "=" o "+" tra di loro, puoi trasformarli in uno solo
espressione. Quando lo fai, esegui il codice nel blocco sulla riga successiva: if you
vedi "=", eseguirai il codice alla riga 2. Se vedi "+", eseguirai il codice alla riga 4. È
questo codice che contribuisce all'albero operativo.

| termine ADDOP termine
{ $$ = nuovoBINOP($2, 0, scalare($1), scalare($3)); }

Ciò che fa è creare una nuova operazione binaria e alimentarla con una serie di variabili. IL
le variabili si riferiscono ai token: $1 è il primo token nell'input, $2 il secondo e così via
on - pensa ai riferimenti alle espressioni regolari. $$ è l'operazione restituita da questa riduzione.
Quindi chiamiamo "newBINOP" per creare un nuovo operatore binario. Il primo parametro di "newBINOP",
una funzione in op.c, è il tipo op. È un operatore di addizione, quindi vogliamo che il tipo lo sia
"AGGIUNGI". Potremmo specificarlo direttamente, ma è proprio lì come secondo token nel file
input, quindi utilizziamo $ 2. Il secondo parametro sono i flag dell'operazione: 0 significa "niente di speciale".
Poi le cose da aggiungere: il lato sinistro e destro della nostra espressione, in contesto scalare.

PILE


Quando perl esegue qualcosa come "addop", come trasmette i suoi risultati all'operazione successiva?
La risposta è, attraverso l'uso degli stack. Perl ha un numero di stack per memorizzare le cose
attualmente in lavorazione e qui esamineremo i tre più importanti.

Argomento pila
Gli argomenti vengono passati al codice PP e restituiti dal codice PP utilizzando lo stack di argomenti "ST".
Il modo tipico di gestire gli argomenti è estrarli dallo stack, gestirli come preferisci
desideri, e poi rimetti il ​​risultato in pila. Ecco come, ad esempio, il coseno
l'operatore lavora:

Valore NV;
valore = POPn;
valore = Perl_cos(valore);
XPUSHn(valore);

Vedremo un esempio più complicato di questo quando considereremo le macro di Perl di seguito. "POPn" dà
tu il NV (valore in virgola mobile) del primo SV nello stack: $x in "cos($x)". Allora noi
calcola il coseno e restituisci il risultato come NV. La "X" in "XPUSHn" significa che il
stack dovrebbe essere esteso se necessario - non può essere necessario qui, perché lo sappiamo
c'è spazio per un altro oggetto in pila, dato che ne abbiamo appena rimosso uno! Il "XPUSH*"
le macro almeno garantiscono la sicurezza.

In alternativa, puoi giocare direttamente con lo stack: "SP" ti dà il primo elemento
la tua porzione dello stack e "TOP*" ti dà il primo SV/IV/NV/ecc. sulla pila. COSÌ,
ad esempio, per eseguire la negazione unaria di un numero intero:

SETi(-TOPi);

Basta impostare il valore intero della voce in cima allo stack sulla sua negazione.

La manipolazione dello stack degli argomenti nel core è esattamente la stessa degli XSUB - vedi
perlxstut, perlxs e perlguts per una descrizione più lunga delle macro utilizzate nello stack
manipolazione.

Mark pila
Dico "la tua parte dello stack" sopra perché il codice PP non ottiene necessariamente il tutto
stack a se stesso: se la tua funzione chiama un'altra funzione, vorrai esporre solo il file
argomenti miravano alla funzione chiamata e non (necessariamente) lasciavano che arrivasse a te stesso
dati. Il modo in cui lo facciamo è avere un fondo dello stack "virtuale", esposto a ciascuno
funzione. Lo stack dei segni mantiene i segnalibri nelle posizioni nello stack degli argomenti utilizzabili da ciascuno
funzione. Ad esempio, quando si ha a che fare con una variabile legata, (internamente, qualcosa con "P"
magia) Perl deve chiamare metodi per accedere alle variabili legate. Tuttavia, dobbiamo farlo
separare gli argomenti esposti al metodo dall'argomento esposto all'originale
funzione: il negozio o il recupero o qualunque cosa possa essere. Ecco più o meno come funziona la "spinta" legata
è implementato; vedere "av_push" in avc:

1 PUSHMARK(SP);
2 ESTENDERE(SP,2);
3 PUSH(SvTIED_obj((SV*)av, mg));
4 PUSH(val);
5 RITIRO;
6 INVIO;
7 call_method("PUSH", G_SCALAR|G_DISCARD);
8 PARTENZA;

Esaminiamo l'intera implementazione, per fare pratica:

1 PUSHMARK(SP);

Spingere lo stato corrente del puntatore dello stack sullo stack dei segni. Questo è così quando
abbiamo finito di aggiungere elementi allo stack degli argomenti, Perl sa quante cose abbiamo aggiunto
da poco tempo.

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

Aggiungeremo altri due elementi allo stack degli argomenti: quando hai un array legato, il
La subroutine "PUSH" riceve l'oggetto e il valore da spingere, e questo è esattamente ciò
abbiamo qui - l'oggetto legato, recuperato con "SvTIED_obj", e il valore, SV "val".

5 RITIRO;

Successivamente diciamo a Perl di aggiornare il puntatore dello stack globale dalla nostra variabile interna: "dSP"
ci ha fornito solo una copia locale, non un riferimento a quella globale.

6 INVIO;
7 call_method("PUSH", G_SCALAR|G_DISCARD);
8 PARTENZA;

"ENTER" e "LEAVE" localizzano un blocco di codice: si assicurano che tutte le variabili lo siano
riordinato, tutto ciò che è stato localizzato ottiene restituito il valore precedente e così via.
Pensateli come "{" e "}" di un blocco Perl.

Per eseguire effettivamente la chiamata del metodo magico, dobbiamo chiamare una subroutine nello spazio Perl:
"call_method" si occupa di questo ed è descritto in perlcall. Chiamiamo "PUSH"
metodo in un contesto scalare e scarteremo il suo valore restituito. IL metodo_chiamata()
La funzione rimuove l'elemento superiore dello stack dei segni, quindi non c'è nulla da fare per il chiamante
pulire.

Risparmi pila
Il C non ha il concetto di ambito locale, quindi Perl ne fornisce uno. Abbiamo visto che "ENTER" e
"LEAVE" vengono utilizzati come parentesi graffe; lo stack di salvataggio implementa l'equivalente C di, for
esempio:

{
locale $foo = 42;
...
}

Vedi "Localizzazione delle modifiche" in perlguts per come utilizzare lo stack di salvataggio.

MILIONI OF MACRO


Una cosa che noterai del sorgente Perl è che è pieno di macro. Alcuni hanno
ha definito l'uso pervasivo delle macro la cosa più difficile da comprendere, altri ritengono che sia un valore aggiunto
chiarezza. Prendiamo ad esempio il codice che implementa l'operatore di addizione:

1 PP(pp_aggiungi)
2 {
3 dSP; daTARGET; tryAMAGICbin(aggiungi,opASSIGN);
4 {
5 dPOPTOPnnrl_ul;
6 SETn(sinistra + destra);
7 RITORNO;
8}
9}

Ogni riga qui (a parte le parentesi graffe, ovviamente) contiene una macro. La prima riga viene impostata
aggiornare la dichiarazione della funzione come Perl si aspetta per il codice PP; la riga 3 imposta la variabile
dichiarazioni per lo stack degli argomenti e la destinazione, il valore restituito dell'operazione.
Infine, prova a vedere se l'operazione di addizione è sovraccarica; se è così, quello appropriato
viene chiamata la subroutine.

La riga 5 è un'altra dichiarazione di variabile - tutte le dichiarazioni di variabile iniziano con "d" - which
si apre dall'inizio dell'argomento impila due NV (quindi "nn") e li inserisce nel file
variabili "destra" e "sinistra", da qui la "rl". Questi sono i due operandi dell'addizione
operatore. Successivamente, chiamiamo "SETn" per impostare il NV del valore restituito sul risultato dell'addizione
i due valori. Fatto questo, restituiamo: la macro "RETURN" si assicura che il nostro valore venga restituito
viene gestito correttamente e passiamo all'operatore successivo per tornare al ciclo di esecuzione principale.

La maggior parte di queste macro sono spiegate in perlapi, e alcune di quelle più importanti lo sono
spiegato anche in Perlxs. Prestare particolare attenzione a "Sfondo e
PERL_IMPLICIT_CONTEXT" in perlguts per informazioni sulle macro "[pad]THX_?".

ULTERIORE LETTURA


Per ulteriori informazioni sugli interni di Perl, consultare i documenti elencati in "Internals
e interfaccia in linguaggio C" in perl.

Utilizza perlinterp online utilizzando i servizi onworks.net


Server e workstation gratuiti

Scarica app per Windows e Linux

  • 1
    libjpeg-turbo
    libjpeg-turbo
    libjpeg-turbo è un codec di immagine JPEG
    che utilizza istruzioni SIMD (MMX, SSE2,
    NEON, AltiVec) per accelerare la linea di base
    Compressione e decompressione JPEG attiva
    x86, x8...
    Scarica libjpeg-turbo
  • 2
    Xtreme Download Manager
    Xtreme Download Manager
    Il progetto ha ora una nuova casa:
    https://xtremedownloadmanager.com/ For
    sviluppatori:
    https://github.com/subhra74/xdm Xtreme
    Download Manager è un potente strumento per...
    Scarica Xtreme Download Manager
  • 3
    TTGO VGA32 Lite
    TTGO VGA32 Lite
    Caratteristiche:4:3 e 16:9 a bassa risoluzione
    Uscita VGATastiera e mouse PS/2
    interfaccia utente basata su inputText (TUI)
    con dialog manager Partial Unicode
    supportoSlave dis...
    Scarica TTGO VGA32 Lite
  • 4
    Bootloader EFI Clover
    Bootloader EFI Clover
    Il progetto si è spostato in
    https://github.com/CloverHackyColor/CloverBootloader..
    Caratteristiche: Avvia macOS, Windows e Linux
    in modalità UEFI o legacy su Mac o PC con
    UE...
    Scarica il bootloader Clover EFI
  • 5
    rpm uniti
    rpm uniti
    Unisciti a noi in Gitter!
    https://gitter.im/unitedrpms-people/Lobby
    Abilita il repository URPMS nel tuo
    sistema -
    https://github.com/UnitedRPMs/unitedrpms.github.io/bl...
    Scarica unitedrpms
  • 6
    Potenzia le librerie C++
    Potenzia le librerie C++
    Boost fornisce portatile gratuito
    librerie C++ sottoposte a revisione paritaria. Il
    l'accento è posto sulle librerie portatili che
    funzionano bene con la libreria standard C++.
    Vedi http://www.bo...
    Scarica le librerie Boost C++
  • Di Più "

Comandi Linux

Ad