Analizzatore di discesa ricorsivo - Recursive descent parser
In informatica , un parser a discesa ricorsiva è una sorta di parser top-down costruito da un insieme di procedure reciprocamente ricorsive (o un equivalente non ricorsivo) in cui ciascuna di tali procedure implementa uno dei non terminali della grammatica . Quindi la struttura del programma risultante rispecchia da vicino quella della grammatica che riconosce.
Un parser predittivo è un parser discendente ricorsivo che non richiede il backtracking . L'analisi predittiva è possibile solo per la classe di grammatiche LL ( k ) , che sono le grammatiche prive di contesto per le quali esiste un intero positivo k che consente a un parser discendente ricorsivo di decidere quale produzione usare esaminando solo i successivi k token di ingresso. Le grammatiche LL ( k ) escludono quindi tutte le grammatiche ambigue , nonché tutte le grammatiche che contengono la ricorsione a sinistra . Qualsiasi grammatica libera dal contesto può essere trasformata in una grammatica equivalente che non ha ricorsione a sinistra, ma la rimozione della ricorsione a sinistra non sempre produce una grammatica LL ( k ). Un parser predittivo viene eseguito in tempo lineare .
La discesa ricorsiva con backtracking è una tecnica che determina quale produzione utilizzare provando a turno ciascuna produzione. La discesa ricorsiva con backtracking non è limitata alle grammatiche LL ( k ), ma non è garantito che termini a meno che la grammatica non sia LL ( k ). Anche quando terminano, i parser che utilizzano la discesa ricorsiva con backtracking possono richiedere tempo esponenziale .
Sebbene i parser predittivi siano ampiamente utilizzati e siano spesso scelti se si scrive un parser a mano, i programmatori spesso preferiscono utilizzare un parser basato su tabella prodotto da un generatore di parser , sia per un linguaggio LL ( k ) o utilizzando un parser alternativo, come LALR o LR . Questo è particolarmente vero se una grammatica non è in forma LL ( k ) , poiché è coinvolta la trasformazione della grammatica in LL per renderla adatta all'analisi predittiva. I parser predittivi possono anche essere generati automaticamente, utilizzando strumenti come ANTLR .
I parser predittivi possono essere rappresentati utilizzando diagrammi di transizione per ciascun simbolo non terminale in cui i bordi tra lo stato iniziale e quello finale sono etichettati dai simboli (terminali e non terminali) del lato destro della regola di produzione.
Parser di esempio
Il seguente EBNF -come grammatica (per Niklaus Wirth s' PL / 0 linguaggio di programmazione, dalle strutture Algoritmi + Dati = Programmi ) è in LL (1) modulo:
program = block "." .
block =
["const" ident "=" num {"," ident "=" num} ";"]
["var" ident {"," ident} ";"]
{"procedure" ident ";" block ";"} statement .
statement =
ident ":=" expression
| "call" ident
| "begin" statement {";" statement } "end"
| "if" condition "then" statement
| "while" condition "do" statement .
condition =
"odd" expression
| expression ("="|"#"|"<"|"<="|">"|">=") expression .
expression = ["+"|"-"] term {("+"|"-") term} .
term = factor {("*"|"/") factor} .
factor =
ident
| number
| "(" expression ")" .
I terminali sono espressi tra virgolette. Ogni non terminale è definito da una regola nella grammatica, ad eccezione di ident e number , che si presume siano definiti implicitamente.
Implementazione C.
Quello che segue è l'implementazione di un parser discesa ricorsiva per il linguaggio sopra C . Il parser legge il codice sorgente ed esce con un messaggio di errore se il codice non riesce ad analizzare, chiudendosi silenziosamente se il codice viene analizzato correttamente.
Nota quanto il parser predittivo sotto rispecchi la grammatica sopra. C'è una procedura per ogni non terminale nella grammatica. L'analisi discende dall'alto verso il basso, finché non è stato elaborato il non terminale finale. Il frammento di programma dipende da una variabile globale, sym , che contiene il simbolo corrente dall'input, e dalla funzione nextsym , che aggiorna sym quando viene chiamata.
Le implementazioni delle funzioni nextsym ed error sono omesse per semplicità.
typedef enum {ident, number, lparen, rparen, times, slash, plus,
minus, eql, neq, lss, leq, gtr, geq, callsym, beginsym, semicolon,
endsym, ifsym, whilesym, becomes, thensym, dosym, constsym, comma,
varsym, procsym, period, oddsym} Symbol;
Symbol sym;
void nextsym(void);
void error(const char msg[]);
int accept(Symbol s) {
if (sym == s) {
nextsym();
return 1;
}
return 0;
}
int expect(Symbol s) {
if (accept(s))
return 1;
error("expect: unexpected symbol");
return 0;
}
void factor(void) {
if (accept(ident)) {
;
} else if (accept(number)) {
;
} else if (accept(lparen)) {
expression();
expect(rparen);
} else {
error("factor: syntax error");
nextsym();
}
}
void term(void) {
factor();
while (sym == times || sym == slash) {
nextsym();
factor();
}
}
void expression(void) {
if (sym == plus || sym == minus)
nextsym();
term();
while (sym == plus || sym == minus) {
nextsym();
term();
}
}
void condition(void) {
if (accept(oddsym)) {
expression();
} else {
expression();
if (sym == eql || sym == neq || sym == lss || sym == leq || sym == gtr || sym == geq) {
nextsym();
expression();
} else {
error("condition: invalid operator");
nextsym();
}
}
}
void statement(void) {
if (accept(ident)) {
expect(becomes);
expression();
} else if (accept(callsym)) {
expect(ident);
} else if (accept(beginsym)) {
do {
statement();
} while (accept(semicolon));
expect(endsym);
} else if (accept(ifsym)) {
condition();
expect(thensym);
statement();
} else if (accept(whilesym)) {
condition();
expect(dosym);
statement();
} else {
error("statement: syntax error");
nextsym();
}
}
void block(void) {
if (accept(constsym)) {
do {
expect(ident);
expect(eql);
expect(number);
} while (accept(comma));
expect(semicolon);
}
if (accept(varsym)) {
do {
expect(ident);
} while (accept(comma));
expect(semicolon);
}
while (accept(procsym)) {
expect(ident);
expect(semicolon);
block();
expect(semicolon);
}
statement();
}
void program(void) {
nextsym();
block();
expect(period);
}
Esempi
Alcuni generatori di parser a discesa ricorsiva:
- TMG - un primo compilatore-compilatore utilizzato negli anni '60 e nei primi anni '70
- JavaCC
- Coco / R
- ANTLR
- Spirit Parser Framework - un framework di generatore di parser di discesa ricorsiva C ++ che non richiede alcun passaggio di precompilazione
- parboiled (Java) - una libreria di analisi PEG di discesa ricorsiva per Java
Guarda anche
- Parser combinator : una funzione di ordine superiore utilizzata nell'analisi combinatoria, un metodo per fattorizzare i progetti di parser a discesa ricorsiva
- Parsing expression grammar - un'altra forma che rappresenta la grammatica discendente ricorsiva
- Analizzatore di ascesa ricorsiva
- Parser ricorsivo di coda : una variante del parser di discesa ricorsivo
Riferimenti
Riferimenti generali
- Compilatori: principi, tecniche e strumenti , prima edizione, Alfred V Aho, Ravi Sethi e Jeffrey D Ullman, in particolare la sezione 4.4.
- Implementazione del compilatore moderno in Java, seconda edizione , Andrew Appel, 2002, ISBN 0-521-82060-X .
- Tecniche di programmazione ricorsiva , WH Burge, 1975, ISBN 0-201-14450-6
- Creazione di un compilatore con C , Charles N Fischer e Richard J LeBlanc, Jr, 1991, ISBN 0-8053-2166-7 .
- Compilazione con C # e Java , Pat Terry, 2005, ISBN 0-321-26360-X , 624
- Algoritmi + Strutture dati = Programmi , Niklaus Wirth, 1975, ISBN 0-13-022418-9
- Compiler Construction , Niklaus Wirth, 1996, ISBN 0-201-40353-6
link esterno
- Introduzione all'analisi : un'introduzione di facile lettura all'analisi, con una sezione completa sull'analisi discendente ricorsiva
- Come trasformare una grammatica in codice C : un breve tutorial sull'implementazione del parser discendente ricorsivo
- Analizzatore di espressioni matematiche semplici in Ruby
- Semplice analisi dall'alto verso il basso in Python
- Jack W. Crenshaw: Let's Build A Compiler (1988-1995) , in Pascal , con output in linguaggio assembly , utilizzando un approccio "keep it simple"
- Perle funzionali: analisi monadica in Haskell