Alguém pode me dar um exemplo simples de análise LL versus análise LR?
Alguém pode me dar um exemplo simples de análise LL versus análise LR?
Respostas:
Em um nível alto, a diferença entre a análise LL e a análise LR é que os analisadores LL começam no símbolo de início e tentam aplicar produções para chegar à sequência de destino, enquanto os analisadores LR começam na sequência de destino e tentam retornar ao início. símbolo.
Uma análise LL é uma derivação da esquerda para a direita, mais à esquerda. Ou seja, consideramos os símbolos de entrada da esquerda para a direita e tentamos construir uma derivação mais à esquerda. Isso é feito começando no símbolo de início e expandindo repetidamente o não terminal mais à esquerda até chegarmos à sequência de destino. Uma análise LR é uma derivação da esquerda para a direita, à direita, o que significa que digitalizamos da esquerda para a direita e tentamos construir uma derivação à direita. O analisador seleciona continuamente uma substring da entrada e tenta revertê-la de volta para um não-terminal.
Durante uma análise de LL, o analisador escolhe continuamente entre duas ações:
Como exemplo, dada esta gramática:
int
Em seguida, dada a string int + int + int
, um analisador LL (2) (que usa dois tokens de lookahead) analisaria a string da seguinte maneira:
Production Input Action
---------------------------------------------------------
S int + int + int Predict S -> E
E int + int + int Predict E -> T + E
T + E int + int + int Predict T -> int
int + E int + int + int Match int
+ E + int + int Match +
E int + int Predict E -> T + E
T + E int + int Predict T -> int
int + E int + int Match int
+ E + int Match +
E int Predict E -> T
T int Predict T -> int
int int Match int
Accept
Observe que, em cada etapa, observamos o símbolo mais à esquerda em nossa produção. Se é um terminal, combinamos com ele e se não é um terminal, prevemos o que será escolhendo uma das regras.
Em um analisador de LR, há duas ações:
Como exemplo, um analisador LR (1) (com um token de lookahead) pode analisar a mesma string da seguinte maneira:
Workspace Input Action
---------------------------------------------------------
int + int + int Shift
int + int + int Reduce T -> int
T + int + int Shift
T + int + int Shift
T + int + int Reduce T -> int
T + T + int Shift
T + T + int Shift
T + T + int Reduce T -> int
T + T + T Reduce E -> T
T + T + E Reduce E -> T + E
T + E Reduce E -> T + E
E Reduce S -> E
S Accept
Os dois algoritmos de análise mencionados (LL e LR) são conhecidos por terem características diferentes. Os analisadores LL tendem a ser mais fáceis de escrever manualmente, mas são menos poderosos que os analisadores LR e aceitam um conjunto de gramáticas muito menor do que os analisadores LR. Os analisadores LR são oferecidos em vários tipos (LR (0), SLR (1), LALR (1), LR (1), IELR (1), GLR (0) etc.) e são muito mais poderosos. Eles também tendem a ser muito mais complexos e quase sempre são gerados por ferramentas como yacc
ou bison
. Os analisadores de LL também têm muitos sabores (incluindo LL (*), que é usado pela ANTLR
ferramenta), embora na prática LL (1) seja o mais amplamente usado.
Como um plug-in vergonhoso, se você quiser saber mais sobre a análise de LL e LR, acabei de ministrar um curso de compiladores e tenho alguns folhetos e slides de palestras sobre análise no site do curso. Ficaria feliz em elaborar algum deles, se você acha que seria útil.
Josh Haberman em seu artigo LL e LR Parsing Demystified afirma que a análise de LL corresponde diretamente à Notação Polonesa , enquanto LR corresponde à Notação Polonesa Reversa . A diferença entre PN e RPN é a ordem de atravessar a árvore binária da equação:
+ 1 * 2 3 // Polish (prefix) expression; pre-order traversal.
1 2 3 * + // Reverse Polish (postfix) expression; post-order traversal.
Segundo Haberman, isso ilustra a principal diferença entre os analisadores LL e LR:
A principal diferença entre como os analisadores LL e LR operam é que um analisador LL produz uma passagem de pré-ordem da árvore de análise e um analisador LR produz uma travessia de pós-ordem.
Para uma explicação detalhada, exemplos e conclusões, consulte o artigo de Haberman .
O LL usa de cima para baixo, enquanto o LR usa a abordagem de baixo para cima.
Se você analisar uma linguagem de programação:
A análise de LL é deficiente, quando comparada à LR. Aqui está uma gramática que é um pesadelo para um gerador de analisador LL:
Goal -> (FunctionDef | FunctionDecl)* <eof>
FunctionDef -> TypeSpec FuncName '(' [Arg/','+] ')' '{' '}'
FunctionDecl -> TypeSpec FuncName '(' [Arg/','+] ')' ';'
TypeSpec -> int
-> char '*' '*'
-> long
-> short
FuncName -> IDENTIFIER
Arg -> TypeSpec ArgName
ArgName -> IDENTIFIER
Um FunctionDef se parece exatamente com um FunctionDecl até o ';' ou '{' for encontrado.
Um analisador LL não pode manipular duas regras ao mesmo tempo, portanto, deve escolher FunctionDef ou FunctionDecl. Mas, para saber qual é o correto, é preciso procurar um ';' ou '{'. No momento da análise gramatical, o lookahead (k) parece ser infinito. No momento da análise, é finito, mas pode ser grande.
Um analisador LR não precisa procurar, pois pode lidar com duas regras ao mesmo tempo. Portanto, os geradores de analisador LALR (1) podem lidar com essa gramática com facilidade.
Dado o código de entrada:
int main (int na, char** arg);
int main (int na, char** arg)
{
}
Um analisador LR pode analisar o
int main (int na, char** arg)
sem se importar com qual regra está sendo reconhecida até encontrar um ';' ou um '{'.
Um analisador LL é desligado no 'int' porque precisa saber qual regra está sendo reconhecida. Portanto, ele deve procurar por um ';' ou '{'.
O outro pesadelo para os analisadores LL é a recursão deixada na gramática. A recursão à esquerda é uma coisa normal nas gramáticas, não há problema para um gerador de analisador LR, mas o LL não pode lidar com isso.
Então você tem que escrever suas gramáticas de uma maneira não natural com LL.
Esquerda Mais derivação Exemplo: Uma gramática G livre de contexto possui as produções
z → xXY (Regra: 1) X → Ybx (Regra: 2) Y → bY (Regra: 3) Y → c (Regra: 4)
Calcule a String w = 'xcbxbc' com a derivação mais à esquerda.
z ⇒ xXY (Regra: 1) ⇒ xYbxY (Regra: 2) ⇒ xcbxY (Regra: 4) ⇒ xcbxbY (Regra: 3) ⇒ xcbxbc (Regra: 4)
Direita Maior derivação Exemplo: K → aKK (Regra: 1) A → b (Regra: 2)
Calcule a String w = 'aababbb' com a derivação mais correta.
K ⇒ aKK (Regra: 1) ⇒ aKb (Regra: 2) ⇒ aaKKb (Regra: 1) ⇒ aaKaKKb (Regra: 1) ⇒ aaKaKbb (Regra: 2) ⇒ aaKabbb (Regra: 2) ⇒ aababb (Regra: 2)