Qual é a diferença entre a análise LL e LR?

225

Alguém pode me dar um exemplo simples de análise LL versus análise LR?

Criatividade2345
fonte

Respostas:

483

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:

  1. Prever : Com base nos tokens não terminais mais à esquerda e em algum número de tokens à esquerda, escolha qual produção deve ser aplicada para se aproximar da string de entrada.
  2. Correspondência : combine o símbolo do terminal adivinhado mais à esquerda com o símbolo não consumido mais à esquerda da entrada.

Como exemplo, dada esta gramática:

  • S → E
  • E → T + E
  • E → T
  • T → 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:

  1. Mudança : adicione o próximo token de entrada a um buffer para consideração.
  2. Reduzir : reduza uma coleção de terminais e não terminais neste buffer de volta a alguns não terminais, revertendo uma produção.

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 yaccou bison. Os analisadores de LL também têm muitos sabores (incluindo LL (*), que é usado pela ANTLRferramenta), 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.

templatetypedef
fonte
40
Seus slides de aula são fenomenais, facilmente a explicação mais divertida que eu já vi :) Esse é o tipo de coisa que realmente desperta interesse.
kizzx2
1
Eu tenho que comentar sobre os slides também! Passando por todos eles agora. Ajuda muito! Obrigado!
Kornfridge 29/05
Realmente gostando dos slides também. Suponho que você não poderia postar a versão não Windows dos arquivos do projeto (e o arquivo scanner.l, para pp2)? :)
Erik P.
1
A única coisa em que posso contribuir para a excelente resposta sumária de Matt é que qualquer gramática que possa ser analisada por um analisador LL (k) (ou seja, olhando para os terminais "k" para decidir a próxima ação de análise) pode ser analisada por um LR ( 1) analisador. Isso dá uma dica do incrível poder da análise de LR sobre a análise de LL. Fonte: curso de compilação na UCSC ministrado pelo Dr. F. DeRemer, criador dos analisadores LALR ().
JoGusto
1
Excelente recurso! Obrigado por fornecer slides, folhetos, projetos também.
P. Hinker
58

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:

árvore binária de uma 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 .

msiemens
fonte
9

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:

  • O LL vê um código fonte, que contém funções, que contém expressão.
  • O LR vê a expressão, que pertence às funções, que resulta na fonte completa.
betontalpfa
fonte
6

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.


fonte
0

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)

Anupama Thathsarani
fonte