lexer vs parser


308

Lexer e parser sono davvero così diversi in teoria?

Sembra di moda odiare le espressioni regolari: coding horror , un altro post sul blog .

Tuttavia, i popolari strumenti basati sul lexing: pygments , geshi o prettify , usano tutti espressioni regolari. Sembrano perdere tutto ...

Quando è abbastanza lexing, quando hai bisogno di EBNF?

Qualcuno ha usato i token prodotti da questi lexer con generatori di parser bisonte o antlr?


2
sì. Sto cercando di analizzare il tasto automatico. Sono stato in grado di costruire un evidenziatore della sintassi usando i pigmenti molto velocemente. Ma antlr sta impiegando molto più tempo ... Non ho visto molta impollinazione incrociata tra i due strumenti.
Naveen,

67
È di moda odiare le espressioni regolari quando vengono usate in modo improprio. Molte persone cercano di usare espressioni regolari quando è necessaria un'analisi senza contesto. Falliscono sempre. E danno la colpa alla tecnologia delle espressioni regolari. È molto come lamentarsi del fatto che il tuo martello sia una sega scadente. È vero, ma non otterrai molta simpatia.
Ira Baxter,

2
Sto cominciando a guadagnare un po 'di velocità con antlr, per fortuna. Un sacco di lexing è senza contesto e talvolta dipende anche dal contesto.
Naveen,

1
Un aspetto fondamentale della questione lexer vs parser è che i lexer si basano su automi finiti (FSA) o più precisamente trasduttori finiti (FST). La maggior parte dei formalismi di analisi (non solo Context-Free) sono chiusi all'intersezione con FSA o all'applicazione di FST. Quindi l'uso del formalismo basato su espressioni regolari più semplici per lexer non aumenta la complessità delle strutture sintattiche dei più complessi formalismi del parser. Questa è una questione di modularità assolutamente importante quando si definiscono la struttura e la semantica delle lingue, felicemente ignorate dalle risposte votate.
babou,

Va notato che i lexer e i parser non devono essere diversi, ad esempio LLLPG e le versioni precedenti di ANTLR usano lo stesso sistema di analisi LL (k) sia per i lexer che per i parser. La differenza principale è che le regex sono generalmente sufficienti per i lexer ma non per i parser.
Qwertie

Risposte:


475

Cosa hanno in comune parser e lexer:

  1. Leggono i simboli di qualche alfabeto dal loro input.

    • Suggerimento: l'alfabeto non deve necessariamente contenere lettere. Ma deve essere di simboli atomici per il linguaggio compreso da parser / lexer.
    • Simboli per il lexer: caratteri ASCII.
    • Simboli per il parser: i token particolari, che sono simboli terminali della loro grammatica.
  2. Analizzano questi simboli e cercano di abbinarli alla grammatica della lingua che hanno capito.

    • Ecco dove sta la vera differenza. Vedi sotto per di più.
    • Grammatica compresa dai lexer: grammatica regolare (livello 3 di Chomsky).
    • Grammatica compresa dai parser: grammatica senza contesto (livello 2 di Chomsky).
  3. Associano la semantica (significato) alle parti linguistiche che trovano.

    • I Lexer attribuiscono significato classificando i lessemi (stringhe di simboli dall'input) come token particolari . Ad esempio, tutti questi lessemi: *, ==, <=, ^saranno classificati come "operatore" gettone dal C / C ++ lexer.
    • I parser attribuiscono significato classificando stringhe di token dall'input (frasi) come particolari non terminali e costruendo l' albero di analisi . Ad esempio tutte queste stringhe token: [number][operator][number], [id][operator][id], [id][operator][number][operator][number]saranno classificati come "espressione" non terminale dal C / C ++ parser.
  4. Possono associare un significato aggiuntivo (dati) agli elementi riconosciuti.

    • Quando un lexer riconosce una sequenza di caratteri che costituisce un numero appropriato, può convertirlo nel suo valore binario e memorizzarlo con il token "numero".
    • Allo stesso modo, quando un parser riconosce un'espressione, può calcolarne il valore e memorizzarlo con il nodo "espressione" dell'albero della sintassi.
  5. Tutti producono sul loro output una frase corretta della lingua che riconoscono.

    • I Lexer producono token , che sono frasi della lingua normale che riconoscono. Ogni token può avere una sintassi interna (anche se livello 3, non livello 2), ma questo non ha importanza per i dati di output e per quello che li legge.
    • I parser producono alberi di sintassi , che sono rappresentazioni di frasi del linguaggio privo di contesto che riconoscono. Di solito è solo un grande albero per l'intero documento / file sorgente, perché l'intero documento / file sorgente è una frase corretta per loro. Ma non ci sono ragioni per cui il parser non possa produrre una serie di alberi di sintassi sul suo output. Ad esempio, potrebbe essere un parser che riconosce i tag SGML attaccati al testo normale. Così sarà tokenize il documento SGML in una serie di gettoni: [TXT][TAG][TAG][TXT][TAG][TXT]....

Come puoi vedere, parser e tokenizer hanno molto in comune. Un parser può essere un tokenizer per altri parser, che legge i suoi token di input come simboli dal proprio alfabeto (i token sono semplicemente simboli di un certo alfabeto) allo stesso modo in cui le frasi di una lingua possono essere simboli alfabetici di altri, di livello superiore linguaggio. Ad esempio, se *e -sono i simboli dell'alfabeto M(come "simboli di codice Morse"), è possibile creare un parser che riconosca le stringhe di questi punti e linee come lettere codificate nel codice Morse. Le frasi nella lingua "Codice Morse" potrebbero essere token per alcuni altri parser, per i quali questi tokensono simboli atomici della sua lingua (ad esempio la lingua "parole inglesi"). E queste "parole inglesi" potrebbero essere dei token (simboli dell'alfabeto) per alcuni parser di livello superiore che capiscono la lingua delle "frasi inglesi". E tutte queste lingue differiscono solo per la complessità della grammatica . Niente di più.

Allora, cosa c'è in questi "livelli grammaticali di Chomsky"? Bene, Noam Chomsky ha classificato le grammatiche in quattro livelli a seconda della loro complessità:

  • Livello 3: grammatiche regolari

    Essi utilizzano le espressioni regolari, che è, che può consistere solo dei simboli dell'alfabeto ( a, b), le loro concatenazioni ( ab, aba, bbbETD.), O alternative (ad esempio a|b).
    Possono essere implementati come automi a stati finiti (FSA), come NFA (Nondeterministic Finite Automaton) o migliore DFA (Deterministic Finite Automaton).
    Le grammatiche regolari non possono essere gestite con sintassi nidificata , ad es. Parentesi nidificate / abbinate correttamente (()()(()())), tag HTML / BBcode nidificati, blocchi nidificati ecc. È perché gli automi di stato da gestire dovrebbero avere infiniti stati per gestire infiniti livelli di annidamento.
  • Livello 2: grammatiche senza contesto

    Possono avere rami nidificati, ricorsivi e auto-simili nei loro alberi di sintassi, quindi possono gestire bene con strutture nidificate.
    Possono essere implementati come automi di stato con stack. Questo stack viene utilizzato per rappresentare il livello di nidificazione della sintassi. In pratica, di solito sono implementati come un parser discendente dall'alto verso il basso che utilizza lo stack di chiamate di procedura della macchina per tenere traccia del livello di annidamento e utilizzano procedure / funzioni chiamate ricorsivamente per ogni simbolo non terminale nella loro sintassi.
    Ma non possono gestire con una sintassi sensibile al contesto . Ad esempio, quando hai un'espressione x+3e in un contesto questo xpotrebbe essere il nome di una variabile, e in un altro contesto potrebbe essere il nome di una funzione, ecc.
  • Livello 1: grammatiche sensibili al contesto

  • Livello 0: grammatiche senza restrizioni Chiamate
    anche grammatiche ricorsivamente enumerabili.


70
O si? Quindi quali sono quelle "parole o token"? Sono solo frasi nella lingua normale, composte da lettere dell'alfabeto. E quali sono questi "costrutti" o "alberi" nel parser? Sono anche frasi , ma in una lingua diversa, di livello superiore, per cui i token particolari sono simboli alfabetici. La differenza non è ciò che hai detto, ma nella COMPLESSITÀ DELLA LINGUA USATA . Confronta il tuo -1 con qualsiasi manuale sulla teoria dell'analisi.
SasQ,

3
@SasQ Sarebbe giusto dire che sia Lexer che Parser prendono come input una grammatica e una serie di token?
Parag,

4
Proprio così. Entrambi prendono una serie di simboli dall'alfabeto che riconoscono. Per lexer, questo alfabeto è composto solo da caratteri semplici. Per parser, l'alfabeto è costituito da simboli terminali, qualunque essi siano definiti. Potrebbero anche essere personaggi, se non usi lexer e usi identificatori di un carattere e numeri di una cifra ecc. (Abbastanza utili nelle prime fasi di sviluppo). Ma di solito sono token (classi lessicali) perché i token sono una buona astrazione: puoi cambiare i lexemi (stringhe) reali che rappresentano e il parser non vede il cambiamento.
SasQ,

6
Ad esempio, è possibile utilizzare un simbolo terminale STMT_ENDnella sintassi (per il parser) per indicare la fine delle istruzioni. Ora puoi avere un token con lo stesso nome associato ad esso, generato dal lexer. Ma puoi cambiare il lessico attuale che rappresenta. Per esempio. puoi definire STMT_ENDcome ;avere un codice sorgente simile a C / C ++. Oppure puoi definirlo come endse fosse in qualche modo simile allo stile Pascal. Oppure puoi definirlo come solo '\n'per terminare l'istruzione con la fine della linea, come in Python. Ma la sintassi dell'istruzione (e del parser) rimane invariata :-) Solo il lexer deve essere cambiato.
SasQ,

24
Le ore su Wikipedia e Google non hanno aiutato, ma hai spiegato le grammatiche di Chomsky in 3 minuti. Grazie.
enrey,

107

Sì, sono molto diversi in teoria e in attuazione.

I Lexer sono usati per riconoscere le "parole" che compongono gli elementi del linguaggio, poiché la struttura di tali parole è generalmente semplice. Le espressioni regolari sono estremamente brave a gestire questa struttura più semplice e ci sono motori di corrispondenza delle espressioni regolari ad altissime prestazioni utilizzati per implementare i lexer.

I parser sono usati per riconoscere la "struttura" delle frasi linguistiche. Tale struttura è generalmente ben al di là di ciò che le "espressioni regolari" possono riconoscere, quindi per estrarre tale struttura sono necessari parser "sensibili al contesto". I parser sensibili al contesto sono difficili da costruire, quindi il compromesso ingegneristico consiste nell'utilizzare grammatiche "senza contesto" e aggiungere hack ai parser ("tabelle dei simboli", ecc.) Per gestire la parte sensibile al contesto.

Né la tecnologia di lexing né di analisi probabilmente scomparirà presto.

Essi possono essere unificate decidendo di usare "parsing" tecnologia per riconoscere "parole", come è attualmente esplorata dai cosiddetti parser GLR scannerless. Ciò ha un costo di runtime, poiché stai applicando macchinari più generali a quello che spesso è un problema che non ne ha bisogno, e di solito lo paghi in overhead. Dove hai un sacco di cicli liberi, quel sovraccarico potrebbe non avere importanza. Se si elabora molto testo, l'overhead è importante e i parser di espressioni regolari classiche continueranno a essere utilizzati.


40
Bella spiegazione, Ira. Aggiungendo alla tua analogia: mentre i lexer si occupano di ottenere le parole giuste, i parser si occupano di ottenere le frasi giuste. "See run run" e "spot run See" sono entrambi validi per quanto riguarda un lexer. Ci vuole un parser per determinare che la struttura della frase è sbagliata (nella grammatica inglese).
Alan,

Immagino che un parser sia per un lexer come un camminatore di alberi è per un parser. Non sono convinto che la teoria sia così diversa: antlr.org/wiki/display/~admin/ANTLR+v4+lexers ma sto iniziando a capire le differenze di convenzione tra di loro ...
Naveen

4
La teoria è molto diversa. La maggior parte delle tecnologie di analisi sta cercando di gestire in una certa misura linguaggi senza contesto (alcuni fanno solo una parte, ad esempio LALR, altri fanno tutto, ad esempio GLR). La maggior parte delle tecnologie lexer cerca solo di fare espressioni regolari.
Ira Baxter,

3
La teoria è diversa, perché è stata proposta da molte persone diverse e utilizza una terminologia e algoritmi diversi. Ma se li guardi da vicino, puoi individuare le somiglianze. Ad esempio, il problema della ricorsione sinistra è molto simile al problema del non determinismo negli NFA e la rimozione della ricorsione sinistra è simile alla rimozione del non determinismo e alla conversione dell'NFA in DFA. I token sono frasi per il tokenizer (output), ma simboli alfabetici per il parser (input). Non nego le differenze (livelli di Chomsky), ma le somiglianze aiutano molto nella progettazione.
SasQ,

1
Il mio officemate era nella teoria delle categorie. Ha mostrato come la nozione di teoria categorica delle pulegge coprisse tutti i tipi di pattern matching e sia stata in grado di derivare l'analisi LR da una specifica categorica astratta. Quindi, in effetti, se diventi abbastanza astratto, puoi trovare tali elementi comuni. Il punto della teoria delle categorie è che puoi spesso astrarre "fino in fondo"; Sono sicuro che potresti costruire un parser di teoria delle categorie che ha cancellato le differenze. Ma qualsiasi uso pratico di esso deve istanziare fino al dominio specifico del problema, e quindi le differenze si presentano come reali.
Ira Baxter,

32

Quando è abbastanza lexing, quando hai bisogno di EBNF?

EBNF in realtà non aggiunge molto al potere delle grammatiche. È solo una comodità / notazione di scorciatoia / "zucchero sintattico" rispetto alle regole grammaticali standard di forma normale (CNF) di Chomsky. Ad esempio, l'alternativa EBNF:

S --> A | B

puoi ottenere in CNF elencando separatamente ciascuna produzione alternativa:

S --> A      // `S` can be `A`,
S --> B      // or it can be `B`.

L'elemento opzionale da EBNF:

S --> X?

puoi ottenere in CNF usando una produzione nullable , cioè quella che può essere sostituita da una stringa vuota (indicata qui da una produzione vuota; altri usano epsilon o lambda o cerchio incrociato):

S --> B       // `S` can be `B`,
B --> X       // and `B` can be just `X`,
B -->         // or it can be empty.

Una produzione in una forma come l'ultima Bsopra è chiamata "cancellazione", perché può cancellare qualunque cosa rappresenti in altre produzioni (prodotto una stringa vuota invece di qualcos'altro).

Zero o più ripetizioni da EBNF:

S --> A*

puoi ottenere usando la produzione ricorsiva , cioè quella che si incorpora da qualche parte in essa. Può essere fatto in due modi. Il primo è la ricorsione sinistra (che di solito dovrebbe essere evitata, perché i parser Discesa ricorsiva dall'alto verso il basso non possono analizzarla):

S --> S A    // `S` is just itself ended with `A` (which can be done many times),
S -->        // or it can begin with empty-string, which stops the recursion.

Sapendo che genera solo una stringa vuota (in definitiva) seguita da zero o più As, la stessa stringa ( ma non la stessa lingua! ) Può essere espressa usando la ricorsione a destra :

S --> A S    // `S` can be `A` followed by itself (which can be done many times),
S -->        // or it can be just empty-string end, which stops the recursion.

E quando si tratta di +una o più ripetizioni da EBNF:

S --> A+

può essere fatto prendendo in considerazione uno Ae usando *come prima:

S --> A A*

che puoi esprimere in CNF in quanto tale (io uso la giusta ricorsione qui; prova a capire l'altro come esercizio):

S --> A S   // `S` can be one `A` followed by `S` (which stands for more `A`s),
S --> A     // or it could be just one single `A`.

Sapendo ciò, ora puoi probabilmente riconoscere una grammatica per un'espressione regolare (cioè grammatica regolare ) come quella che può essere espressa in una singola produzione EBNF costituita solo da simboli terminali. Più in generale, puoi riconoscere grammatiche regolari quando vedi produzioni simili a queste:

A -->        // Empty (nullable) production (AKA erasure).
B --> x      // Single terminal symbol.
C --> y D    // Simple state change from `C` to `D` when seeing input `y`.
E --> F z    // Simple state change from `E` to `F` when seeing input `z`.
G --> G u    // Left recursion.
H --> v H    // Right recursion.

Cioè, usando solo stringhe vuote, simboli di terminale, semplici non terminali per sostituzioni e cambiamenti di stato e usando la ricorsione solo per ottenere la ripetizione (iterazione, che è solo ricorsione lineare - quella che non si ramifica come un albero). Niente di più avanzato di questi, quindi sei sicuro che si tratti di una sintassi regolare e puoi farlo solo con Lexer.

Ma quando la tua sintassi usa la ricorsione in un modo non banale, per produrre strutture annidate simili ad alberi, auto-simili, come la seguente:

S --> a S b    // `S` can be itself "parenthesized" by `a` and `b` on both sides.
S -->          // or it could be (ultimately) empty, which ends recursion.

allora puoi facilmente vedere che questo non può essere fatto con un'espressione regolare, perché non puoi risolverlo in una sola produzione EBNF in alcun modo; vi ritroverete con sostituendo Sa tempo indeterminato, che sarà sempre aggiungere un altro as e bs su entrambi i lati. I Lexer (più specificamente: gli automi a stati finiti usati dai lexer) non possono contare su un numero arbitrario (sono finiti, ricordi?), Quindi non sanno quanti as erano lì per abbinarli uniformemente con così tanti bs. Grammatiche come questa sono chiamate grammatiche senza contesto (per lo meno) e richiedono un parser.

Le grammatiche senza contesto sono ben note per l'analisi, quindi sono ampiamente utilizzate per descrivere la sintassi dei linguaggi di programmazione. Ma c'è di più. A volte è necessaria una grammatica più generale, quando hai più cose da contare contemporaneamente, indipendentemente. Ad esempio, quando si desidera descrivere una lingua in cui è possibile utilizzare parentesi tonde e parentesi quadre interlacciate, ma devono essere accoppiate correttamente tra loro (parentesi graffe con parentesi graffe, arrotondate con arrotondate). Questo tipo di grammatica si chiama sensibile al contesto . Puoi riconoscerlo dal fatto che ha più di un simbolo a sinistra (prima della freccia). Per esempio:

A R B --> A S B

Puoi considerare questi simboli aggiuntivi a sinistra come un "contesto" per applicare la regola. Ci potrebbero essere alcune precondizioni, postcondizioni ecc Ad esempio, la regola precedente sostituirà Rin S, ma solo quando è in mezzo Ae B, lasciando quelli Ae Bsi invariato. Questo tipo di sintassi è davvero difficile da analizzare, perché ha bisogno di una macchina di Turing in piena regola. È tutta un'altra storia, quindi finirò qui.


1
Affermate che EBNF è "solo una convenienza / notazione di scorciatoia /" zucchero sintattico "rispetto alle regole grammaticali standard della forma normale (CNF) di Chomsky". Ma CNF non ha quasi nulla a che fare con l'argomento in questione. L'EBNF può essere facilmente trasformato in BNF standard. Periodo. È zucchero sintattico per BNF standard.
babou,

11

Per rispondere alla domanda come posta (senza ripetere indebitamente ciò che appare nelle altre risposte)

Lexer e parser non sono molto diversi, come suggerito dalla risposta accettata. Entrambi si basano su semplici formalismi linguistici: linguaggi regolari per i lexer e, quasi sempre, linguaggi senza contesto (CF) per i parser. Entrambi sono associati a modelli computazionali abbastanza semplici, l'automa a stati finiti e l'automa a pila push-down. Le lingue regolari sono un caso speciale di lingue senza contesto, in modo che i lexer possano essere prodotti con la tecnologia CF un po 'più complessa. Ma non è una buona idea per almeno due motivi.

Un punto fondamentale nella programmazione è che un componente di sistema dovrebbe essere acquistato con la tecnologia più appropriata, in modo che sia facile da produrre, capire e mantenere. La tecnologia non dovrebbe essere eccessiva (utilizzando tecniche molto più complesse e costose del necessario), né dovrebbe essere al limite della sua potenza, richiedendo quindi contorsioni tecniche per raggiungere l'obiettivo desiderato.

Ecco perché "Sembra di moda odiare le espressioni regolari". Sebbene possano fare molto, a volte richiedono una codifica molto illeggibile per raggiungerlo, per non parlare del fatto che varie estensioni e restrizioni nell'attuazione riducono in qualche modo la loro semplicità teorica. I Lexer di solito non lo fanno e di solito sono una tecnologia semplice, efficiente e appropriata per analizzare il token. L'uso di parser CF come token sarebbe eccessivo, sebbene sia possibile.

Un altro motivo per non usare il formalismo CF per i lexer è che potrebbe quindi essere allettante utilizzare la piena potenza CF. Ma ciò potrebbe sollevare problemi strutturali riguardo alla lettura dei programmi.

Fondamentalmente, la maggior parte della struttura del testo del programma, da cui viene estratto il significato, è una struttura ad albero. Esprime il modo in cui la frase di analisi (programma) viene generata dalle regole di sintassi. La semantica deriva da tecniche compositive (omomorfismo orientato matematicamente) dal modo in cui le regole di sintassi sono composte per costruire l'albero di analisi. Quindi la struttura ad albero è essenziale. Il fatto che i token siano identificati con un lexer basato su un set regolare non cambia la situazione, perché la CF composta da regolare dà ancora CF (sto parlando in modo molto approssimativo dei trasduttori regolari, che trasformano un flusso di caratteri in un flusso di token).

Tuttavia, la CF composta da CF (tramite trasduttori CF ... scusate la matematica), non fornisce necessariamente CF e potrebbe rendere le cose più generali, ma meno trattabili nella pratica. Quindi CF non è lo strumento appropriato per i lexer, anche se può essere utilizzato.

Una delle principali differenze tra il normale e il CF è che i linguaggi regolari (e i trasduttori) si compongono molto bene con quasi ogni formalismo in vari modi, mentre i linguaggi CF (e i trasduttori) non lo fanno, nemmeno con se stessi (con poche eccezioni).

(Notare che i trasduttori regolari possono avere altri usi, come la formalizzazione di alcune tecniche di gestione degli errori di sintassi.)

BNF è solo una sintassi specifica per la presentazione delle grammatiche CF.

EBNF è uno zucchero sintattico per BNF , che utilizza le funzionalità della notazione regolare per fornire una versione più terser delle grammatiche BNF. Può sempre essere trasformato in un equivalente BNF puro.

Tuttavia, la notazione regolare viene spesso utilizzata in EBNF solo per enfatizzare queste parti della sintassi che corrispondono alla struttura degli elementi lessicali e dovrebbero essere riconosciute con il lexer, mentre il resto deve essere piuttosto presentato in BNF dritto. Ma non è una regola assoluta.

Riassumendo, la struttura più semplice del token viene analizzata meglio con la tecnologia più semplice delle lingue normali, mentre la struttura orientata agli alberi della lingua (della sintassi del programma) viene gestita meglio dalle grammatiche CF.

Suggerirei anche di guardare la risposta di AHR .

Ma questo lascia una domanda aperta: perché gli alberi?

Gli alberi sono una buona base per specificare la sintassi perché

  • danno una struttura semplice al testo

  • ci sono molto convenienti per associare la semantica al testo sulla base di quella struttura, con una tecnologia matematicamente ben compresa (composizionalità tramite omomorfismi), come indicato sopra. È uno strumento algebrico fondamentale per definire la semantica dei formalismi matematici.

Quindi è una buona rappresentazione intermedia, come dimostra il successo di Abstract Syntax Trees (AST). Si noti che le AST sono spesso diverse dall'albero di analisi perché la tecnologia di analisi utilizzata da molti professionisti (come LL o LR) si applica solo a un sottoinsieme di grammatiche CF, forzando così le distorsioni grammaticali che vengono successivamente corrette in AST. Questo può essere evitato con una tecnologia di analisi più generale (basata sulla programmazione dinamica) che accetta qualsiasi grammatica CF.

Dichiarazione sul fatto che i linguaggi di programmazione sono sensibili al contesto (CS) anziché CF sono arbitrari e discutibili.

Il problema è che la separazione di sintassi e semantica è arbitraria. Il controllo delle dichiarazioni o del tipo di accordo può essere visto come parte della sintassi o parte della semantica. Lo stesso vale per l'accordo di genere e numero nelle lingue naturali. Ma ci sono linguaggi naturali in cui l'accordo plurale dipende dall'effettivo significato semantico delle parole, in modo che non si adatti bene alla sintassi.

Molte definizioni dei linguaggi di programmazione nella semantica denotazionale collocano dichiarazioni e verifica del tipo nella semantica. Così come affermato da Ira Baxter che i parser CF vengono hackerati per ottenere una sensibilità al contesto richiesta dalla sintassi, nella migliore delle ipotesi è una visione arbitraria della situazione. Può essere organizzato come un hack in alcuni compilatori, ma non deve esserlo.

Inoltre non è solo che i parser CS (nel senso usato in altre risposte qui) sono difficili da costruire e meno efficienti. Sono inoltre inadeguati per esprimere in modo evidente il tipo di sensibilità al contesto che potrebbe essere necessario. E non producono naturalmente una struttura sintattica (come alberi di analisi) che sia conveniente per derivare la semantica del programma, cioè per generare il codice compilato.


Sì, gli alberi di analisi e le AST sono diversi, ma praticamente non in modo molto utile. Vedi la mia discussione su questo: stackoverflow.com/a/1916687/120163
Ira Baxter,

@IraBaxter Non sono d'accordo con te, ma non ho davvero il tempo di redigere una risposta chiara al tuo post. Fondamentalmente, stai prendendo un punto di vista pragmatico (e anche difendendo il tuo sistema, credo). Questo è ancora più facile perché stai usando parser CF generali (tuttavia il GLR potrebbe non essere il più efficiente), piuttosto che quelli deterministici come in alcuni sistemi. Considero AST come la rappresentazione di riferimento, che si presta a un trattamento formalmente definito, trasformazioni dimostrabilmente corrette, prove matematiche, non analizzabili a molteplici rappresentazioni concrete, ecc.
Babou,

La visione "pragmatica" è il motivo per cui sostengo che non sono molto diversi in modo utile. E semplicemente non credo che l'uso di un (AST ad hoc) ti dia "trasformazioni dimostrabilmente corrette"; il tuo AST ad hoc non ha alcuna relazione ovvia con la grammatica effettiva della lingua in corso di elaborazione (e qui sì, il mio sistema è difendibile in quanto il nostro "AST" è di fatto un equivalente isomorfo del BNF). Gli AST ad hoc non ti danno alcuna possibilità aggiuntiva di analizzare le "molteplici rappresentazioni concrete". La tua obiezione al GLR (non molto efficiente) sembra piuttosto inutile. Né sono non deterministici.
Ira Baxter,

Quindi in realtà non capisco nessuna parte della tua obiezione al mio commento. Dovrai scrivere quella "risposta pulita".
Ira Baxter,

@IraBaxter I commenti sono troppo limitati per una risposta corretta (suggerimento?). "Ad hoc" non è un valido qualificatore per AST I sostenitore, che dovrebbe essere (a volte è) la sintassi di riferimento. Questo è storicamente vero, guardando sia alla storia del concetto di AST nell'informatica, sia alla storia dei sistemi formali come termini (alberi) in un'algebra ordinata, insieme all'interpretazione. AST è la forma di riferimento, non derivata. Vedi anche sistemi di prova moderni e generazione automatica di programmi. Potresti essere influenzato dal fatto che devi lavorare su una sintassi concreta progettata da altri.
babou,

7

Esistono diversi motivi per cui la parte di analisi di un compilatore è normalmente suddivisa in fasi di analisi lessicale e analisi (analisi di sintassi).

  1. La semplicità del design è la considerazione più importante. La separazione dell'analisi lessicale e sintattica spesso ci consente di semplificare almeno uno di questi compiti. Ad esempio, sarebbe un parser che doveva trattare i commenti e lo spazio bianco come unità sintattiche. Molto più complesso di uno che può assumere commenti e spazi bianchi sono già stati rimossi dall'analizzatore lessicale. Se stiamo progettando un nuovo linguaggio, separare le preoccupazioni lessicali e sintattiche può portare a un design del linguaggio complessivo più pulito.
  2. L'efficienza del compilatore è migliorata. Un analizzatore lessicale separato ci consente di applicare tecniche specializzate che servono solo al compito lessicale, non al lavoro di analisi. Inoltre, tecniche di buffering specializzate per la lettura dei caratteri di input possono velocizzare significativamente il compilatore.
  3. La portabilità del compilatore è stata migliorata. Le peculiarità specifiche del dispositivo di input possono essere limitate all'analizzatore lessicale.

resource___ Compilers (2nd Edition) scritto da- Alfred V. Abo Columbia University Monica S. Lam Stanford University Ravi Sethi Avaya Jeffrey D. Ullman Stanford University

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.