Capítulo 5 Análise sintática

Documentos relacionados
Compiladores Analisador Sintático. Prof. Antonio Felicio Netto Ciência da Computação

Compiladores - Análise Ascendente

Compiladores - Análise Ascendente

INE5318 Construção de Compiladores. AULA 4: Análise Sintática

Reduce: reduz o que está imediatamente à esquerda do foco usando uma produção

Construção de Compiladores Aula 16 - Análise Sintática

Análise sintática. Prof. Thiago A. S. Pardo. Análise sintática ascendente

V Análise Sintática. V.1.1 Gramáticas Livres de Contexto Definições de GLC

COMPILADORES. Análise sintática. Prof. Geovane Griesang Universidade de Santa Cruz do Sul UNISC Departamento de informática

Compiladores. Exemplo. Caraterísticas de Gramáticas. A αβ 1 αβ 2. A αx X β 1 β 2. Lembrando... Gramáticas Livres de Contexto

Compiladores. Top-Down x Bottom Up. Plano da aula. Redução exemplo 1. Redução exemplo 1. Lembrando: construir a tabela de análise LL(1) A Abc b B d

Analisadores Sintáticos LR

Análise Sintática (Cap. 04) Análise Sintática Ascendente Analisador Sintático LR

Análise Sintática Bottom-up

Compiladores - Análise SLR

Análise Sintática I. Eduardo Ferreira dos Santos. Abril, Ciência da Computação Centro Universitário de Brasília UniCEUB 1 / 42

Compiladores Aula 6. Celso Olivete Júnior.

Compiladores. Análise Sintática

INE5317 Linguagens Formais e Compiladores AULA 9: Propriedades e Reconhecimento das Linguagens Livres do Contexto

BNF (Backus-Naur Form) Gramática Livres de Contexto / Estruturas Recursivas

Análise Sintática II. Eduardo Ferreira dos Santos. Outubro, Ciência da Computação Centro Universitário de Brasília UniCEUB 1 / 34

Análise sintática. Análise sintática ascendente. Parte-se dos símbolos terminais em direção ao símbolo inicial da gramática. Derivação mais à direita

Análise Sintática LL(1)

Análise Sintática - Final

Compiladores - Análise LL(1)

Análise Sintática (Cap. 04) Análise Sintática Descendente

Análise sintática. Análise sintática. Top-down ou descendente. Com retrocesso: por tentativa e erro. Preditiva: para gramáticas LL(1) 09/04/2012

Problemas decidíveis para LICs

Linguagens Formais e Autômatos P. Blauth Menezes

Compilação: Erros. Detecção de Erros: * Analisadores Top-Down - Preditivo Tabular (LL) - Feito a mão. * Analisadores Botton-Up: - Shift-Reduce (SLR)

COMPILADORES. Análise sintática. Prof. Geovane Griesang Universidade de Santa Cruz do Sul UNISC Departamento de informática

Tokens, Padroes e Lexemas

Construção de Compiladores Aula 18 - Análise Sintática Ascendente

Análise sintática. Análise sintática ascendente. Bottom-up, ascendente ou redutiva. Analisadores de precedência de operadores Analisadores LR

Análise Sintática. Fabiano Baldo

Linguagens Livres de Contexto

Concurso Público para provimento de cargo efetivo de Docentes. Edital 20/2015 CIÊNCIA DA COMPUTAÇÃO II Campus Rio Pomba

Parsing Preditivo. Antes de ser abordado o Parsing Preditivo, será apresentado o Analisador Sintático Descendente Recursivo.

Compiladores. Análise lexical. Plano da aula. Motivação para análise lexical. Vocabulário básico. Estrutura de um compilador

Linguagens e Programação Gramáticas. Paulo Proença

Análise Sintática II: Analisadores Descendentes Preditivos

COMPILADORES. Análise sintática. Prof. Geovane Griesang Universidade de Santa Cruz do Sul UNISC Departamento de informática

Gramáticas Livres de Contexto Parte 1

IV Gramáticas Livres de Contexto

Capítulo 9: Linguagens sensíveis ao contexto e autômatos linearmente limitados.

Linguagens Livres do Contexto. Adaptado de H. Brandão

Compiladores Aula 4. Celso Olivete Júnior.

COMPILADORES. Revisão Linguagens formais Parte 02. Prof. Geovane Griesang

Análise Bottom-Up. Compiladores. Parsing LR. Tabela Ações/Transições. Análise LR. Construindo tabelas LR

Analisadores Ascendentes ou Empilha-Reduz. Mais um exemplo... Mais um exemplo... Top-Down x Bottom Up. Conteúdo da aula. Analisadores Ascendentes

Teoria da Computação Gramáticas, Linguagens Algébricas e Autómatos de Pilha

Análise Sintática. Análise Sintática. Tipos de Analisadores Gramáticais: PARSERS

Vantagens de uma Gramática. Sintaxe de uma Linguagem. Analisador Sintático - Parser. Papel do Analisador Sintático. Tiposde Parsers para Gramáticas

Compiladores. Bruno Lopes. Bruno Lopes Compiladores 1 / 12. Instituto de C

UNIVERSIDADE FEDERAL RURAL DO SEMI-ÁRIDO CURSO: CIÊNCIA DA COMPUTAÇÃO. Prof.ª Danielle Casillo

Linguagens Formais. Aula 01 - Conceitos Básicos. Prof. Othon Batista Mestre em Informática

Apresentação. !! Familiarização com os métodos de construção de compiladores de linguagens e com as técnicas de compilação mais habituais.

Linguagens Livres de Contexto

Análise Sintática. Eduardo Ferreira dos Santos. Outubro, Ciência da Computação Centro Universitário de Brasília UniCEUB 1 / 18

LINGUAGENS FORMAIS E AUTÔMATOS. Prova 2-10/06/ Prof. Marcus Ramos

LINGUAGEM LIVRE DE CONTEXTO GRAMÁTICA LIVRE DE CONTEXTO

COMPILADORES. Revisão Linguagens formais Parte 01. Geovane Griesang

Curso de Engenharia de Computação - UTFPR Teoria da Computação - Prof. Celso Kaestner Lista de exercícios

Como construir um compilador utilizando ferramentas Java

Como construir um compilador utilizando ferramentas Java

Linguagens Livres de Contexto

Um Compilador Simples. Definição de uma Linguagem. Estrutura de Vanguarda. Gramática Livre de Contexto. Exemplo 1

Folha 4.1 Análise sintática descendente

Conceitos de Análise Sintática

COMPILADORES. Análise sintática. Prof. Geovane Griesang Universidade de Santa Cruz do Sul UNISC Departamento de informática

A. (Autómatos finitos determinísticos e não determinísticos AFD e AFND)

Função, interação com o compilador Especificação e reconhecimento de tokens Implementação Tratamento de erros. Prof. Thiago A. S.

Linguagens Formais e Problemas de Decisão

Compiladores. Capítulo 3: Análise Sintática Introdução

Transcrição:

Capítulo 5 1. Objetivo 2. Estratégias gerais de parsing 3. descendente (top-down) 3.1. Analisador sintático com retrocesso (backtracking) 3.2. Analisador sintático predicativo recursivo 3.3. Analisador sintático predicativo não recursivo Parser LL 3.4. Construção de tabelas sintáticas predicativas (Tabelas Oráculo) 4. ascendente (bottom-up) 4.1. Parser por transição-redução (método geral) 4.2. Parser LR 4.3. Parser SLR 4.4. Parser LALR 1/183

Objetivo Objetivo 2/183

Objetivo Gramática da linguagem árvore de sintaxe Sequência de símbolos PARSER Aceitação ou Erro Dado - uma gramática livre do contexto G - uma sequência a de símbolos terminais (frase) pretende-se - verificar se a é uma frase válida de L(G) - reconhecer a forma da frase (identificar as produções de G). 3/183

Objetivo Em cada momento do processo, a a a a em que: a sequência já percorrida a próximo símbolo a analisar (token) a sequência por analisar a é uma frase de L(G) se e só se * S a 4/183

Objetivo Em cada momento do processo, - ou existe uma derivação * S m a d, onde m com m T* e d (S T)* a. { Fase de possível aceitação } - ou não existe nenhuma derivação que aceite a a. { ERRO: não é frase de L(G) } 5/183

Estratégias gerais de parsing Estratégias gerais de parsing 6/183

Estratégias gerais de parsing Existem duas estratégias gerais de parsing: - reconhecimento descendente ( top-down ) - reconhecimento ascendente ( bottom-up ). Reconhecimento descendente ( Top-Down ) - A árvore é construída da raiz para as folhas - Em cada vértice (com um símbolo não terminal A): - selecionar uma produção com A à esquerda e construir os vértices filhos de A com os símbolos à direita nessa produção; - selecionar o vértice onde continuar. - Termina quando todas as folhas são símbolos terminais. - A aceitação é obtida se a sequência a for esgotada. - As decisões são tomadas por observação de a, chamado lookahead símbolo. 7/183

Estratégias gerais de parsing Reconhecimento ascendente ( Bottom-up ) - A árvore é construída das folhas para a raiz. - Os símbolos de a são associados até se reconhecer o lado direito de uma produção. - A aceitação é obtida se, esgotada a sequência a, o símbolo inicial estiver na raiz da árvore. 8/183

descendente (top-down) descendente (top-down) 9/183

descendente (top-down) Dada uma frase a, a análise descendente pode ser vista como uma tentativa de se, - encontrar uma derivação mais à esquerda para a, - construir uma árvore gramatical para a a partir da raiz, criando os nós em pré-ordem. Desta forma, - a frase a é percorrida da esquerda para a direita, - vão-se identificando derivações esquerdas, - enquanto a árvore de sintaxe vai sendo construída. 10/183

descendente (top-down) 1. descendente com retrocesso (backtracking) O reconhecimento é feito por expansão de regras sintáticas, substituindo - símbolos não-terminais do lado esquerdo de produções, - pelos símbolos do lado direito das produções. Depois, a expansão das regras sintáticas é confirmada - por emparelhamento - dos símbolos da frase de entrada (a) - com os símbolos terminais das regras sintáticas. - se não emparelhar, então terá que haver retrocesso no processo ( backtracking ). O retrocesso ( backtrack ) é resultado da existência de várias produções com o mesmo símbolo no lado esquerdo. 11/183

descendente (top-down) Algoritmo: Entrada: Uma gramática G e uma frase a Saída: Árvore de derivação 1. Criar um nó com o símbolo inicial da gramática. 2. Substituir o símbolo não-terminal situado mais à esquerda da árvore de derivação pelos símbolos do lado direito da produção cujo lado esquerdo é o símbolo substituído. Este passo termina quando o primeiro símbolo da produção for um símbolo terminal. 3. Se o primeiro símbolo ainda não processado de a não emparelhar com o primeiro símbolo da produção selecionada no passo anterior, recuar para o passo 2 e pesquisar outra alternativa ( backtrack ); se não existir outra alternativa, o algoritmo termina sem sucesso. 4. Emparelhar, da esquerda para a direita, os símbolos de a com os símbolos terminais nas folhas da árvore de derivação, até que aconteça uma das três hipóteses: - o emparelhamento de símbolos falha terminar sem sucesso; - é detetada na folha da árvore um símbolo não-terminal regressar ao passo 2; - todos os símbolos de a foram emparelhados e todas as folhas da árvore contêm apenas símbolos terminais que foram emparelhados com símbolos de a terminar com sucesso. 12/183

descendente (top-down) Vantagens: - simplicidade de implementação do algoritmo que lhe está associado. 13/183

descendente (top-down) Desvantagens: - Não aceita gramáticas recursivas à esquerda; por ex., numa entrada com, o analisador entra em ciclo infinito de expansão duma produção recursiva à esquerda - A ordem da escolha das alternativas determina a linguagem aceite pelo analisador; por ex., a frase aa é aceite pelo analisador se for expandida em primeiro a produção S B, o que não acontece se a primeira produção a ser expandida for S aa, pois o primeiro símbolo da entrada, a, emparelha com o primeiro símbolo terminal da produção S aa, mas o segundo símbolo de entrada, a, não pode ser derivado a partir de A. - Em caso de entrada com, não é possível identificar as causas de, pois não há distinção entre retrocesso por e por necessidade de pesquisa de alternativas - A operação de retrocesso é muito demorada, degradando o desempenho ( performance ) da análise sintática. 14/183

descendente (top-down) 2. Analisador sintático predicativo recursivo Definição: Um Parser recursivo-descendente diz-se profético (ou predicativo) quando: - a análise do a, ou lookahead, determina univocamente a produção a utilizar; - a execução de um procedimento simula a sequência de símbolos do lado direito dessa produção. Um Parser predicativo é caracterizado por tomar sempre decisões irrevogáveis, isto é, sem backtracking. Para se construir um analisador predicativo, é preciso: - dado o lookahead a (a = a) e o símbolo não terminal A a ser expandido, - conhecer qual das alternativas da produção A b1 b2 bn é a única que deriva uma cadeia que começa por a. 15/183

descendente (top-down) Ou seja, a alternativa adequada precisa ser detetável examinando-se apenas o primeiro símbolo da cadeia que a mesma deriva. Algoritmo: {XÎT} Reconhecer_X: Se (X = a ) Então avançar (a) { reconhecimento parcial } Senão ERRO {XÎS} Reconhecer_X: { em função de a, escolher uma produção p : X X1 X2... Xn } Reconhecer_X1 Reconhecer_X2... Reconhecer_Xn 16/183

descendente (top-down) Partindo do símbolo inicial (S) e do início de a: Parser_RD: Avançar (a); Reconhecer_S ; Se ( a = e) Então RECONHECIMENTO Senão ERRO Para a construção de um Parser predicativo é necessário estabelecer um método que, - conhecidos o lookahead (a ) e o símbolo a reconhecer, - determine univocamente a produção a utilizar. Uma das formas é utilizar uma tabela sintática (Tabela Oráculo), M[X,a] = p, que significa: a produção p será aplicada se pretender-se expandir o símbolo não terminal X e o lookahead for um símbolo terminal a (a = a). 17/183

descendente (top-down) Pretende-se elaborar um Parser predicativo que seja: - independente da gramática; - constituído por uma única função, onde o símbolo é passado como parâmetro; - eficiente; Reconhecer(X): Se (X Î T) Então Se (X = a ) Então Da_Simbolo (a ) Senão ERRO Senão np Oráculo(X, a ) Se (np ¹ ERRO) Então Para cada Y Î Produção[np] Reconhecer (Y) Senão ERRO 18/183

descendente (top-down) - ainda é recursivo; - mas é mais cómodo e mais independente da linguagem: a informação relativa à linguagem está guardada em duas estruturas de dados. Otimização da Função Oráculo: ORÁCULO : (S È T) x T (X, a ) { skip, np, } ação Se (X Î T e X = a ) Então ação = skip { o reconhecedor léxico reconhece um símbolo terminal } * Se (X Î T e X ¹ a ) ou (X Î S e $ p : X a ) Então ação = * Se (X Î S e $1 p : X a ) Então p ação = np 19/183

descendente (top-down) Nota: * * Se (X Î S e $ p', p'' : X a e X a ) Então p' p'' Situação de conflito Reconhecer (X): ação ORÁCULO (X, a ) Caso (ação) Seja : ERRO skip : Da_Simbolo(a ) np : Para (" Y Î Produção[np]) Fazer Reconhecer (Y) Em cada momento continua a haver apenas dois casos possíveis: - ERRO - Possível reconhecimento. 20/183

Analisador sintático predicativo não recursivo Analisador sintático predicativo não recursivo 21/183

Analisador sintático predicativo não recursivo Pode-se construir um analisador predicativo usando uma pilha (sem recursividade). Um analisador sintático predicativo não recursivo é composto por: - uma frase de entrada, - uma pilha, - uma tabela sintática, e - um fluxo de saída. Frase de entrada Pilha X Y Z $ a + b $ Programa de Análise Sintática Predicativa Saída Tabela Sintática M 22/183

Analisador sintático predicativo não recursivo A frase de entrada é seguida por um $ à direita (indica o fim da sequência de símbolos) A pilha Z: - contém uma sequência de símbolos gramaticais, - com $ a indicar o fundo da pilha; - inicialmente, a pilha contém o símbolo inicial da gramática acima de $. A tabela sintática é um array bidimensional M[A, a] onde, - A é um símbolo não terminal, terminal ou $, - a é um símbolo terminal ou $. O analisador sintático é um programa que se comporta da seguinte forma: - considera o símbolo do topo da pilha X e o lookahead a (estes dois símbolos determinam a ação do analisador). 23/183

Analisador sintático predicativo não recursivo Existem três possibilidades: 1. Se X = $, o analisador pára a análise com sucesso, se a = $. 2. Se X = a (a é terminal), o analisador sintático remove X da pilha e avança o apontador da entrada para o próximo símbolo (próximo lookahead), a = a. 3. Se X é um não terminal, o programa consulta a entrada M[X, a] da tabela sintática M, que será uma produção-x (X a) da gramática ou ERRO. Por ex., se M[X,a] = { X U V W } substitui-se X no topo da pilha por W V U (com U no topo); se M[X, a] = chama-se uma rotina de recuperação de s. Algoritmo: predicativa não recursiva Entrada: Uma frase a e uma tabela sintática M para a gramática G = (S, T, P, S). Saída: Uma derivação mais à esquerda (se a L(G)) ou uma indicação de. Método: Introduzir os símbolos $ e S na pilha Z (S é o símbolo inicial de G) ¾ Topo(Z) = S a = primeiro símbolo de a 24/183

Analisador sintático predicativo não recursivo Repetir Se (Topo(Z) Î T) Então Se (Topo(Z) = a ) Então Pop(Z) Da_Simbolo(a ) Senão ERRO Senão Se (Topo(Z) Î S) Então Se M[Topo(Z), a ] = { X Y1Y2 Yn } Então Pop(Z) Para i desde n até 1 Fazer Push(Z, Yi) Senão ERRO Até (Topo(Z) = $) 25/183

Analisador sintático predicativo não recursivo Resumindo, temos que sendo a : sequência de símbolos terminais já analisados Z : sequência de símbolos terminais e não terminais a reconhecer, mantém-se invariante: * S a Z no início : a = e e Z = S e no fim : a = a e Z = e { se tudo correr bem } Deste modo: - não é necessário construir a árvore de derivação - nem guardar a - Z funciona como uma pilha, - portanto, é possível construir um algoritmo iterativo. 26/183

Analisador sintático predicativo não recursivo A este analisador sintático dá-se o nome de Parser LL (scan input from Left to right and construct Leftmost derivation). Falta apenas incluir uma ação de aceitação: Ação = { skip, np,, ac }. A aceitação ocorre quando: (Z = $) e (a = $) pois não há símbolo para reconhecer nem falta analisar nenhum símbolo. 27/183

Analisador sintático predicativo não recursivo ParserLL (algoritmo LL): Push(Z, $) Push(Z, S) Da_Simbolo(a ) Repetir Acção Oráculo(Topo(Z), a ); Pop(Z); Caso Acção Seja : Erro; ac : ; skip : Da_Simbolo(a ); np : Para i desde n até 1 Fazer { X Y1Y2 Yn } Push(Z, Yi); Até Ação = ac; 28/183

Construção de tabelas sintáticas predicativas (Tabelas Oráculo) Construção de tabelas sintáticas predicativas (Tabelas Oráculo) 29/183

Construção de tabelas sintáticas predicativas (Tabelas Oráculo) A ideia subjacente ao algoritmo associado à construção destas tabelas é a seguinte: - suponha-se que A a é uma produção e que a Î First(a); - logo, o analisador sintático irá expandir A através de a quando o símbolo de entrada corrente (lookahead) for a (a = a). A única complicação ocorre quando a = e ou a e. Neste caso, deve-se expandir A de novo através de a se: - a Î Follow(A) ou - $ (na entrada) foi atingido e $ Î Follow(A). 30/183

Construção de tabelas sintáticas predicativas (Tabelas Oráculo) Algoritmo: Entrada : Gramática G Saída : Tabela Oráculo M[X, Y] em que, X (S T { $ }) e Y (T { $ }) Descrição do método: 1. Para cada produção A a de G, executar os passos 2 e 3. 2. Para cada símbolo terminal a Î First(a), fazer M[A, a] = A a. 3. Se e Î First(a), então para cada b Î Follow(A), fazer M[A, b] = A a. Se e Î First(a) e $ Î Follow(A), fazer M[A, $] = A a. 4. Fazer a cada entrada da tabela M do tipo M[x, x] = SKIP, em que x T. 5. Fazer à entrada da tabela M, M[$, $] = AC. 6. Fazer a cada entrada indefinida da tabela M, M[k, j] = ERRO. Os passos 2 e 3 podem ser substituídos pelo seguinte único passo: 2-3. Para cada símbolo terminal a Î Lookhead(A a), fazer M[A, a] = A a. 31/183

Exemplo de um analisador sintático predicativo não recursivo Exemplo de um analisador sintático predicativo não recursivo 32/183

Exemplo de um analisador sintático predicativo não recursivo Considere a seguinte gramática G = (Σ, T, P, S), em que Σ = { S, A, B, C, D }, T = { a, b, c, d, e, f }, P= { 1) S DC; 2) A a; 3) A Cb; 4) B e; 5) B c; 6) B da; 7) C e; 8) C f; 9) D AB } 33/183

Exemplo de um analisador sintático predicativo não recursivo First (a) = { a } Follow (S) = { $ } First (b) = { b } Follow (A) = { c, d, e, f } First (c) = { c } Follow (B) = { e, f } First (d) = { d } Follow (C) = { $, b } First (e) = { e } Follow (D) = { e, f } First (f) = { f } First (S) = { a, e, f } First (A) = { a, e, f } First (B) = { ε, c, d } First (C) = { e, f } First (D) = { a, e, f } 34/183

Exemplo de um analisador sintático predicativo não recursivo 1) S DC First(S DC) = First(D) = { a, e, f } (e First(D)) M[S, a] = M[S, e] = M[S, f] = 1 Oráculo S $ a b 1 c d e f 1 1 A B C D $ a b c d e f 35/183

Exemplo de um analisador sintático predicativo não recursivo 2) A a First(A a) = First(a) = { a } M[A, a] = 2 Oráculo $ a S 1 A 2 b c d e f 1 1 B C D $ a b c d e f 36/183

Exemplo de um analisador sintático predicativo não recursivo 3) A Cb First(A Cb) = First(C) = { e, f } (e First(C)) M[A, e] = M[A, f] = 3 Oráculo $ a b c d e f S 1 1 1 A 2 3 3 B C D $ a b c d e f 37/183

Exemplo de um analisador sintático predicativo não recursivo 4) B e Como e First(e), M[B, x] = 4, para x Follow(B) = { e, f } M[B, e] = M[B, f] = 4 Oráculo $ a b c d e f S 1 1 1 A 2 3 3 4 4 B C D $ a b c d e f 38/183

Exemplo de um analisador sintático predicativo não recursivo 5) B c First(B c) = First(c) = { c } M[B, c] = 5 Oráculo $ a b c d e f S 1 1 1 A 2 3 3 4 4 B 5 C D $ a b c d e f 39/183

Exemplo de um analisador sintático predicativo não recursivo 6) B da First(B da) = First(d) = { d } M[B, d] = 6 Oráculo $ a b c d e f S 1 1 1 A 2 3 3 4 4 B 5 6 C D $ a b c d e f 40/183

Exemplo de um analisador sintático predicativo não recursivo 7) C e First(C e) = First(e) = { e } M[C, e] = 7 Oráculo $ a b c d e f S 1 1 1 A 2 3 3 4 4 B C 5 6 7 D $ a b c d e f 41/183

Exemplo de um analisador sintático predicativo não recursivo 8) C f First(C f) = First(f) = { f } M[C, f] = 8 Oráculo $ a b c d e f S 1 1 1 A 2 3 3 4 4 7 8 B C 5 6 D $ a b c d e f 42/183

Exemplo de um analisador sintático predicativo não recursivo 9) D AB First(D AB) = First(A) = { a, e, f } (e First(A)) M[D, a] = M[D, e] = M[D, f] = 9 Oráculo $ a b c d e f S 1 1 1 A 2 3 3 4 4 7 8 9 9 B 5 C D 9 6 $ a b c d e f 43/183

Exemplo de um analisador sintático predicativo não recursivo M[x, x] = SKIP, para x T Oráculo $ a M[$, $] = AC b c d e f S 1 1 1 A 2 3 3 4 4 7 8 9 9 B 5 6 C D $ a b c 9 AC SKIP SKIP SKIP d e f SKIP SKIP SKIP 44/183

Exemplo de um analisador sintático predicativo não recursivo M[X, Y] = E (Erro), para as entradas vazias Oráculo $ a b c d e f S E 1 E E E 1 1 A E 2 E E E 3 3 B E E E 5 6 4 4 C E E E E E 7 8 D E 9 E E E 9 9 $ AC E E E E E E a E SKIP E E E E E b E E SKIP E E E E c E E E SKIP E E E d E E E E SKIP E E e E E E E E SKIP E f E E E E E E SKIP 45/183

Exemplo de um analisador sintático predicativo não recursivo ebdfbe$ Push(Z, $) Push(Z, S) S $ Oraculo(Topo(Z), a ) = Oraculo(S, e) = 1 46/183

Exemplo de um analisador sintático predicativo não recursivo ebdfbe$ Pop(Z) Push(Z, C) Push(Z, D) D C $ Oraculo(Topo(Z), a ) = Oraculo(D, e) = 9 47/183

Exemplo de um analisador sintático predicativo não recursivo ebdfbe$ Pop(Z) Push(Z, B) Push(Z, A) A B C $ Oraculo(Topo(Z), a ) = Oraculo(A, e) = 3 48/183

Exemplo de um analisador sintático predicativo não recursivo ebdfbe$ Pop(Z) Push(Z, b) Push(Z, C) C b B C $ Oraculo(Topo(Z), a ) = Oraculo(C, e) = 7 49/183

Exemplo de um analisador sintático predicativo não recursivo ebdfbe$ Pop(Z) Push(Z, e) e b B C $ Oraculo(Topo(Z), a ) = Oraculo(e, e) = SKIP 50/183

Exemplo de um analisador sintático predicativo não recursivo ebdfbe$ Pop(Z) Da_Simbolo(a ) b B C $ Oraculo(Topo(Z), a ) = Oraculo(b, b) = SKIP 51/183

Exemplo de um analisador sintático predicativo não recursivo ebdfbe$ Pop(Z) Push(Z, e) Da_Simbolo(a ) B C $ Oraculo(Topo(Z), a ) = Oraculo(B, d) = 6 52/183

Exemplo de um analisador sintático predicativo não recursivo ebdfbe$ Pop(Z) Push(Z, A) Push(Z, d) d A C $ Oraculo(Topo(Z), a ) = Oraculo(d, d) = SKIP 53/183

Exemplo de um analisador sintático predicativo não recursivo ebdfbe$ Pop(Z) Da_Simbolo(a ) A C $ Oraculo(Topo(Z), a ) = Oraculo(A, f) = 3 54/183

Exemplo de um analisador sintático predicativo não recursivo ebdfbe$ Pop(Z) Push(Z, b) Push(Z, C) C b C $ Oraculo(Topo(Z), a ) = Oraculo(C, f) = 8 55/183

Exemplo de um analisador sintático predicativo não recursivo ebdfbe$ Pop(Z) Push(Z, f) f b C $ Oraculo(Topo(Z), a ) = Oraculo(f, f) = SKIP 56/183

Exemplo de um analisador sintático predicativo não recursivo ebdfbe$ Pop(Z) Da_Simbolo(a ) b C $ Oraculo(Topo(Z), a ) = Oraculo(b, b) = SKIP 57/183

Exemplo de um analisador sintático predicativo não recursivo ebdfbe$ Pop(Z) Da_Simbolo(a ) C $ Oraculo(Topo(Z), a ) = Oraculo(C, e) = 7 58/183

Exemplo de um analisador sintático predicativo não recursivo ebdfbe$ Pop(Z) Push(Z, e) e $ Oraculo(Topo(Z), a ) = Oraculo(e, e) = SKIP 59/183

Exemplo de um analisador sintático predicativo não recursivo ebdfbe$ Pop(Z) Da_Simbolo(a ) $ Oraculo(Topo(Z), a ) = Oraculo($, $) = AC FIM. A frase pertence à linguagem gerada pela gramática G, L(G). 60/183

Análise Sintática Ascendente (bottom-up) 61/183

1. Princípios gerais O objetivo da análise sintática ascendente é a construção da árvore de derivação, a partir das folhas, em direção à raiz. A árvore de derivação é construída percorrendo a sequência a de símbolos de entrada (frase) da esquerda para a direita (e armazenada numa string s): esta operação é designada por deslocamento ( shift ) da posição do analisador sintático. Quando é detetada uma sequência de símbolos gramaticais idêntica ao lado direito de uma produção, esses símbolos são substituídos pelo símbolo não-terminal do lado esquerdo da produção: esta operação é designada por redução ( reduce ). 62/183

Exemplo: Considere-se a gramática com as seguintes 6 produções : 1) S A B c 2) S B A c 3) A a 4) A a A 5) B b 6) B b B A frase abbc pode ser reduzida até ao símbolo inicial da gramática S em 4 etapas: SÞABcÞAbBcÞAbbcÞabbc Etapa Sequência de Símbolos Gramaticais Regra Gramatical Reduzida 1 abbc 3 2 Abbc 5 3 AbBc 6 4 ABc 1 5 S O procedimento corresponde a uma sequência de derivação à direita. 63/183

2. Método geral: parser por transição-redução s = m b e p : A b s=ma Redução de b em A pela produção p Cada redução corresponde a uma subida de nível na árvore de derivação. p:a XY Z A m X Y Z depois disso, se : s = m A = f d A e p : B d A s=fb B A f d X Y Z Sequência reconhecida: ao atingir a raiz da árvore (s = S) não restam símbolos em a. 64/183

Um parser bottom-up é caracterizado por dois tipos de operações básicas: Redução: s=mb s=ma Transição: s = m a e Avançar( a ) No início do processo: s = e e a = a No fim do processo: s = S e a = e { Aceitação } Num parser top-down : No início do processo: s = S e a = a No fim do processo: s = e e a = e { Aceitação } 65/183

Em qualquer momento de parsing bottom-up : - Ou existe uma derivação: S s a b { estado possível de aceitação } - Ou não existe nenhuma derivação a efetuar: estado de. Assumindo (por enquanto) que todas as decisões podem ser tomadas apenas em função dos valores de S e de a, define-se: ORÁCULO : (T È S)* x T { shift, reduce, ac, } (s, a ) ação 66/183

Parser_Trans_Red: Inicializar(s) ; Da_Simbolo(a ) ; Repetir ação ORÁCULO(s, a ) ; Caso ação Seja : ERRO ; ac: ; shift: s s a ; Da_Simbolo(a ) ; reduce: s s - b ; { p : A b } s sa; Até ac ; 67/183

Exemplo: Dada a gramática G com as seguintes produções: p1 : E E + E p2 : E E * E p3 : E ( E ) p4 : E id analisar a expressão: a + (b * c) $ 68/183

p1 : E E + E p2 : E E * E s a a a +(b*c)$ p3 : E ( E ) p4 : E id Ação Shift E 69/183

p1 : E E + E p2 : E E * E s a a a a +(b*c)$ + (b*c)$ p3 : E ( E ) p4 : E id Ação Shift Reduce p4 70/183

p1 : E E + E p2 : E E * E s a a p3 : E ( E ) Ação a +(b*c)$ a + (b*c)$ Reduce p4 E + (b*c)$ Shift p4 : E id Shift 71/183

p1 : E E + E p2 : E E * E s a a p3 : E ( E ) Ação a +(b*c)$ a + (b*c)$ Reduce p4 E + (b*c)$ Shift E+ ( b*c)$ Shift p4 : E id Shift 72/183

p1 : E E + E p2 : E E * E s a a p3 : E ( E ) Ação a +(b*c)$ a + (b*c)$ Reduce p4 E + (b*c)$ Shift E+ ( b*c)$ Shift E+( b *c)$ Shift p4 : E id Shift 73/183

p1 : E E + E p2 : E E * E s a a p3 : E ( E ) p4 : E id Ação a +(b*c)$ a + (b*c)$ Reduce p4 E + (b*c)$ Shift E+ ( b*c)$ Shift E+( b *c)$ Shift E+(b * c)$ Shift Reduce p4 74/183

p1 : E E + E p2 : E E * E s a a p3 : E ( E ) p4 : E id Ação a +(b*c)$ a + (b*c)$ Reduce p4 E + (b*c)$ Shift E+ ( b*c)$ Shift E+( b *c)$ Shift E+(b * c)$ Reduce p4 E+(E * c)$ Shift Shift 75/183

p1 : E E + E p2 : E E * E s a p3 : E ( E ) a p4 : E id Ação a +(b*c)$ a + (b*c)$ Reduce p4 E + (b*c)$ Shift E+ ( b*c)$ Shift E+( b *c)$ Shift E+(b * c)$ Reduce p4 E+(E * c)$ Shift E+(E* c )$ Shift Shift 76/183

p1 : E E + E p2 : E E * E s a p3 : E ( E ) a p4 : E id Ação a +(b*c)$ a + (b*c)$ Reduce p4 E + (b*c)$ Shift E+ ( b*c)$ Shift E+( b *c)$ Shift E+(b * c)$ Reduce p4 E+(E * c)$ Shift E+(E* c )$ Shift E+(E*c ) $ Shift Reduce p4 77/183

p1 : E E + E p2 : E E * E s a p3 : E ( E ) a p4 : E id Ação a +(b*c)$ a + (b*c)$ Reduce p4 E + (b*c)$ Shift E+ ( b*c)$ Shift E+( b *c)$ Shift E+(b * c)$ Reduce p4 E+(E * c)$ Shift E+(E* c )$ Shift E+(E*c ) $ Reduce p4 E + (E * E ) $ Reduce p2 Shift 78/183

p1 : E E + E p2 : E E * E s a p3 : E ( E ) a p4 : E id Ação a +(b*c)$ a + (b*c)$ Reduce p4 E + (b*c)$ Shift E+ ( b*c)$ Shift E+( b *c)$ Shift E+(b * c)$ Reduce p4 E+(E * c)$ Shift E+(E* c )$ Shift E+(E*c ) $ Reduce p4 E + (E * E ) $ Reduce p2 E+(E ) $ Shift Shift 79/183

p1 : E E + E p2 : E E * E s a p3 : E ( E ) a p4 : E id Ação a +(b*c)$ a + (b*c)$ Reduce p4 E + (b*c)$ Shift E+ ( b*c)$ Shift E+( b *c)$ Shift E+(b * c)$ Reduce p4 E+(E * c)$ Shift E+(E* c )$ Shift E+(E*c ) $ Reduce p4 E + (E * E ) $ Reduce p2 E+(E ) $ Shift E+(E) $ e Reduce p3 Shift 80/183

p1 : E E + E p2 : E E * E s a p3 : E ( E ) a p4 : E id Ação a +(b*c)$ a + (b*c)$ Reduce p4 E + (b*c)$ Shift E+ ( b*c)$ Shift E+( b *c)$ Shift E+(b * c)$ Reduce p4 E+(E * c)$ Shift E+(E* c )$ Shift E+(E*c ) $ Reduce p4 E + (E * E ) $ Reduce p2 E+(E ) $ Shift E+(E) $ e Reduce p3 E+E $ e Reduce p1 Shift 81/183

p1 : E E + E p2 : E E * E s a p3 : E ( E ) a p4 : E id Ação a +(b*c)$ a + (b*c)$ Reduce p4 E + (b*c)$ Shift E+ ( b*c)$ Shift E+( b *c)$ Shift E+(b * c)$ Reduce p4 E+(E * c)$ Shift E+(E* c )$ Shift E+(E*c ) $ Reduce p4 E + (E * E ) $ Reduce p2 E+(E ) $ Shift E+(E) $ e Reduce p3 E+E $ e Reduce p1 E $ e ac Shift 82/183

3. Parser LR Os analisadores sintáticos LR apresentam diversas vantagens: - Reconhecem praticamente todos os construtores de linguagens de programação estruturadas em blocos, expressas em gramáticas independentes do contexto. - São mais gerais que outros analisadores sintáticos e revelam o mesmo grau de eficiência. - Podem detetar cedo s sintáticos, quando tal é possível, ao pesquisar a entrada da esquerda para a direita. Maior inconveniente destes analisadores: - a complexidade da sua implementação. No entanto, a existência de ferramentas que geram automaticamente analisadores sintáticos (tal como o YACC) eliminou este problema. 83/183

O analisador sintático LR: Left-scan, leitura da frase da esquerda para a direita, Rightmost derivation, derivação da direita para a esquerda, - pesquisa os elementos da frase da esquerda para a direita (tal como os analisadores descendentes) e - constrói uma derivação à direita, invertida. 84/183

Arquitetura do analisador sintático LR Entrada Pilha Frase$ Reconhecedor Saída Ações Saltos 85/183

Tabelas do analisador sintático LR As duas tabelas do analisador sintático ascendente LR são designadas por: - tabela de ações ( actions ) e - tabela de saltos ( goto ). A tabela de ações A[estado, entrada] é indexada pelo estado e pelo símbolo de entrada (terminal), e possui um dos quatro valores : 1. Desloca s ( shift ), na qual s é um estado. 2. Reduz n ( reduce ), na qual n é a referência a uma produção X a. 3. Aceita ( accept ). 4. Erro. 86/183

A tabela de saltos G[estado, símbolo]: - é indexada pelo estado e pelo símbolo não-terminal, - possui, nalgumas células, uma indicação da referência a um estado. A construção das tabelas pode ser efetuada utilizando três metodologias distintas: 1. SLR ( Simple LR ) é o método mais simples, mas também o menos poderoso, porque não é possível construir as tabelas para algumas gramáticas. 2. LR canónico é o método mais poderoso e, em simultâneo, o mais complexo, sendo aquele que mais memória necessita para armazenar a tabela. 3. LALR ( LookAhead LR ) este método é um compromisso entre os métodos SLR e LR canónico, e funciona para a maior parte das linguagens de programação. 87/183

Reconhecimento de uma frase Um parser LR implementa o autómato determinístico de pilha, capaz de reconhecer uma dada gramática do tipo LR(k), onde: - os estados do autómato representam valores da string s - cada transição: s s a corresponde à determinação do próximo estado, em função do atual e de a T - cada redução: s s b determina o estado anterior quando do atual se retira b (S T)*, s sa determina o próximo estado em função do atual e de A S - transições são implementadas por uma tabela chamada Oráculo_T ou Ação: Oráculo_T : Q T { shift, reduce, ac, } (Q P) (q, a ) ( ação, q np ) 88/183

- os estados anteriores são recuperados se, numa pilha F de estados forem efetuados Pop s em número igual ao dos símbolos em β - mudanças de estado por redução são implementadas por um Oráculo_S ou Goto: Oráculo_S : Q S Q (q, A) q 89/183

Algoritmo: Proc Parser_LR: Inicializar(F) ; Da_Simbolo(a ) ; Repetir acção Oráculo_T(Topo(F), a ) ; Caso ação Seja : ERRO ; ac: ; shift: q ação.estado ; Push(F, q) ; Da_Simbolo(a ) ; reduce: np ação.num_prod ; (A, nsd) Prod[np] ; Para i desde 1 até nsd Fazer Pop(F) ; q Oráculo_S(Topo(F), A) ; Push(F, q) ; Até ac ; 90/183

- Como associar estados a valores de s? - Como construir os Oráculos? - Será sempre possível? Cada passo do processo LR é caracterizado por : ( s, a ) CONFIGURAÇÃO LR Cada decisão é tomada em função de: A d.b produção em reconhecimento e ponto (.) de decisão; w o prefixo de s, com s = w d; m o contexto direito de A. 91/183

Ao conjunto destes três elementos chama-se: Item LR [ w, A d. b, m ] Uma configuração ( s, a ) satisfaz a condição não- LR se : * bm a A cada configuração não-, está associado um e um só item LR. No início : (e, a) [ e, Z. S $, e ] No fim : (S, $) [ e, Z S. $, e ] 92/183

Só existem três formas de itens LR (a que correspondem três tipos de ações): 1) [ ω, A d. a b, m ] a que corresponde a transição: (wd, a r ) (wd a, r) s 2) [ ω, A d. B b, m ] a ação a tomar depende de B, e virá a ser tomada em função de [ wd, B.g, bm ] 3) [ w, A d. e, m ] a que corresponde a redução (wd, m) (w A, m) r A cada configuração (s, a ) está associado um item [ w, A d. b, m ] que determina de forma única a ação a executar, sendo necessário verificar que a é derivável de b m. 93/183

Construção do autómato Para um dado item LR [ w, A d. b, m ] chama-se item LR(k) a [ A d. b, First(k, m) ] Os estados de um autómato LR(k) são constituídos por itens LR(k). Num autómato LR(0) os estados são [ A d. b ]. Exemplo: Construir o autómato não determinístico LR(0) para a gramática G com as seguintes produções: 1) Z S $ 2) S a 3) S b A A 4) A ε 5) A A a 94/183

95/183

96/183

97/183

98/183

99/183

100/183

101/183

102/183

103/183

104/183

105/183

106/183

107/183

108/183

109/183

110/183

Algoritmo para construir o autómato determinístico: Alfabeto : S T Estado inicial : [ Z. S $, e ] Cada estado é formado por : - Um item LR(k) : [ A d. b, n ] - Pelo seu fecho, isto é, todos os itens da forma [ B. g, m ] sempre que b = B r, com m = First(k, r n) - E pelo fecho de cada um desses itens. A função de transição : D = Q (T S) Q para cada item LR(k) associado ao estado q [ A d. B r, n ] B T S D (q, B) = q com q = [ A d B. r, n ] Exemplo: construir o autómato determinístico LR(0) para a gramática G anterior. 111/183

112/183

113/183

114/183

115/183

116/183

117/183

118/183

119/183

120/183

121/183

122/183

123/183

124/183

125/183

126/183

NOTAS: - Uma redução corresponde a recuar no grafo (com o conhecimento da produção). { redução por S baa } - Num parser LR(0), a redução é feita qualquer que seja o a ; basta que tenha acabado o reconhecimento da produção atual. 127/183

- Neste parser LR(0) podem ocorrer duas situações de conflito : { redução por A e } { redução por S baa } { redução por A Aa } Por exemplo, ao estado 5 estão associados: [ A e, m ] e [ A A.a, a ] Se a First(m) é impossível decidir: redução por A e, ou transição para [ A Aa., a ]. Para fazer o reconhecimento basta conhecer: - A identificação (número) de cada estado; - Transições ou reduções definidas para esse estado. 128/183

A partir do autómato determinístico A constroem-se as Tabelas Oráculo, da seguinte forma: - q Q, X S Oráculo_S (q, X) = sq', se (q, q')x A Oráculo_S (q, X) =, caso contrário - q Q, t T Oráculo_T (q, t) = sq', se (q, q')t A Oráculo_T (q, t) = rp, se [ A a., n ] : t First(k, n) Oráculo_T (q, t) =, caso contrário q [ Z S.$, e ] e t = $ Oráculo_T (q, t) = AC 129/183

Tabela Oráculo (exemplo anterior): a a b $ S A 1 s3 s4 s2 2 AC t 3 r2 r2 r2 a 4 r4 r4 r4 s5 d 5 s7/r4 r4 r4 s6 6 s7/r3 r3 r3 7 r5 r5 r5 e s o s Oráculo_T Oráculo_S { 2 conflitos redução/transição não é LR(0) } 130/183

Construir o autómato determinístico LR(1) para a mesma gramática: First(S) = { a, b } First(A) = { a, e } Follow(S) = { $ } Follow(A) = { a, $ } 131/183

1 [ Z.S$, ] 132/183

1 [ Z.S$, ] [ S.a, $ ] First($e) = { $ } [ S. b A A, $ ] First($e) = { $ } 133/183

1 [ Z.S$, ] S 2 [ Z S. $, ] AC ( = $) [ S.a, $ ] [ S.bAA, $ ] 134/183

1 [ Z.S$, ] S 2 a 3 [ Z S. $, ] AC ( = $) [ S.a, $ ] [ S.bAA, $ ] [ S a., $ ] red#2 ( = $) 135/183

1 [ Z.S$, ] S 2 a 3 [ Z S. $, ] AC [ S.a, $ ] [ S.bAA, $ ] [ S a., $ ] red#2 ( = $) b 4 [ S b.aa, $ ] 136/183

1 [ Z.S$, ] S 2 a 3 [ Z S. $, ] AC [ S.a, $ ] [ S.bAA, $ ] [ S a., $ ] red#2 ( = $) b 4 [ S b.aa, $ ] [ A., a/$ ] First(A$) = { a, $ } [ A. A a, a/$ ] First(A$) = { a, $ } red#4 ( = a/$) 137/183

1 [ Z.S$, ] S 2 a 3 [ Z S. $, ] AC [ S.a, $ ] [ S.bAA, $ ] [ S a., $ ] red#2 ( = $) b 5 4 [ S b.aa, $ ] A [ S ba.a, $ ] [ A A. a, a/$ ] [ A., a/$ ] [ A. A a, a/$ ] red#4 ( = a/$) 138/183

1 [ Z.S$, ] S 2 a 3 [ Z S. $, ] AC [ S.a, $ ] [ S.bAA, $ ] [ S a., $ ] red#2 ( = $) b 5 4 [ S b.aa, $ ] [ A., a/$ ] [ A. A a, a/$ ] A [ S ba.a, $ ] [ A A. a, a/$ ] red#4 [ A., $ ] ( = $) [ A. A a, $ ] First($) = { $ } First($) = { $ } red#4 ( = a/$) 139/183

1 [ Z.S$, ] S 2 a 3 [ Z S. $, ] AC [ S.a, $ ] [ S.bAA, $ ] [ S a., $ ] red#2 ( = $) b 5 4 [ S b.aa, $ ] [ A., a/$ ] [ A. A a, a/$ ] red#4 ( = a/$) A [ S ba.a, $ ] [ A A. a, a/$ ] red#4 [ A., $ ] ( = $) [ A. A a, $ ] First($) = { $ } red#4 ( = a) [ A., a ] First(a$) = { a } [ A.Aa, a ] First(a$) = { a } First($) = { $ } 140/183

1 [ Z.S$, ] S 2 a 3 [ Z S. $, ] AC [ S.a, $ ] [ S.bAA, $ ] [ S a., $ ] red#2 ( = $) b 5 4 [ S b.aa, $ ] [ A., a/$ ] [ A. A a, a/$ ] A [ S ba.a, $ ] [ A A. a, a/$ ] red#4 [ A., a/$ ] ( = a/$) [ A. A a, a/$ ] red#4 ( = a/$) 141/183

1 [ Z.S$, ] S 2 a 3 [ Z S. $, ] AC [ S.a, $ ] [ S.bAA, $ ] [ S a., $ ] red#2 ( = $) b 5 4 [ S b.aa, $ ] [ A., a/$ ] [ A. A a, a/$ ] A [ S ba.a, $ ] [ A A. a, a/$ ] red#4 [ A., a/$ ] ( = a/$) [ A. A a, a/$ ] red#4 ( = a/$) a 6 [ A A a., a/$ ] red#5 ( = a/$) 142/183

1 [ Z.S$, ] S 2 a 3 [ Z S. $, ] AC [ S.a, $ ] [ S.bAA, $ ] [ S a., $ ] red#2 ( = $) b 5 4 [ S b.aa, $ ] [ A., a/$ ] [ A. A a, a/$ ] A [ S ba.a, $ ] [ A A. a, a/$ ] red#4 [ A., a/$ ] ( = a/$) [ A. A a, a/$ ] red#4 ( = a/$) A 7 [ S b A A., $ ] [ A A. a, a/$ ] red#3 ( = $) a 6 [ A A a., a/$ ] red#5 ( = a/$) 143/183

1 [ Z.S$, ] S 2 a 3 [ Z S. $, ] AC [ S.a, $ ] [ S.bAA, $ ] [ S a., $ ] red#2 ( = $) b 5 4 [ S b.aa, $ ] [ A., a/$ ] [ A. A a, a/$ ] A [ S ba.a, $ ] 7 A [ A A. a, a/$ ] red#4 [ A., a/$ ] ( = a/$) [ A. A a, a/$ ] red#4 ( = a/$) [ S b A A., $ ] [ A A. a, a/$ ] red#3 ( = $) a a 6 [ A A a., a/$ ] red#5 ( = a/$) 144/183

1 [ Z.S$, ] S 2 a 3 [ Z S. $, ] AC [ S.a, $ ] [ S.bAA, $ ] [ S a., $ ] red#2 ( = $) b 5 4 [ S b.aa, $ ] [ A., a/$ ] [ A. A a, a/$ ] A [ S ba.a, $ ] 7 A [ A A. a, a/$ ] red#4 [ A., a/$ ] ( = a/$) [ A. A a, a/$ ] red#4 ( = a/$) [ S b A A., $ ] [ A A. a, a/$ ] red#3 ( = $) a a 6 [ A A a., a/$ ] red#5 ( = a/$) Os Follow's são apenas utilizados para decidir quando devem ser feitas reduções. 145/183

Tabela Oráculo LR(1) (exemplo anterior): a a b $ S A 1 s3 s4 s2 e 2 AC s 3 r2 4 r4 r4 s5 5 s6 r4 s7 o 6 r5 r5 s 7 s8 r3 8 r5 t a d Oráculo_T Oráculo_S { não existem conflitos é LR(1) } 146/183

Conflito redução/transição Resumindo e concluindo: - Um item LR(k) da forma [ A a.xb, m ] determina uma transição por X - Um item LR(k) da forma [ A a., m ] determina uma redução por A a para todo o a First(k, m). As possíveis situações de conflito são - Conflito redução-transição ao mesmo estado estão associados 2 itens LR(k) da forma: [ A a.ab, m ] e [ B d., n ], com a First(k, n) 147/183

- Conflito redução-redução ao mesmo estado estão associados 2 itens LR(k) da forma: [ A a., m ] e [ B b., n ] com First(k, m) First(k, n). - Uma dada gramática satisfaz a condição LR(k) se não provocar conflitos em nenhum dos estados. 148/183

Exercício: Verificar se a gramática não-ambígua de expressões é LR(0) ou LR(1). Produções: p1: Z S $ p2: S E p3: E E + T p4: E T p5: T ( E ) p6: T id 149/183

{ conflito redução/transição no estado 5 não é LR(0) } 150/183

Construção do autómato LR(1): First(S) = First(E) = First(T) = { (, id } Follow(S) = { $ } Follow(E) = Follow(T) = { +, ), $ } 151/183

- As transições possíveis a partir do estado 1 são: - (Action) símbolos terminais :First(S) ou First(E) ou First(T) = { (, id } - (Goto) símbolos não-terminais: S, E, T - Não ocorrem reduções no estado 1 - First(k, rn) servem apenas para identificar reduções 152/183

153/183

Observações - O autómato LR(0) contem informação insuficiente para resolver os conflitos que surgem na maioria das linguagens. - As exigências de memória destes autómatos são aceitáveis. - O autómato LR(1) contem informação suficiente para resolver os conflitos na maioria das linguagens. - A memória necessária neste autómatos é impraticável. - No entanto, existem soluções de compromisso, como sejam: SLR(1) e LALR(1). 154/183

4. Parser SLR(1) Simple LR(1) - Os seus estados são basicamente os mesmos que os do autómato LR(0) - A certos items é associada informação necessária para identificar reduções - A cada item LR(0) da forma: [ A a. ] é associado o Follow(A) Haverá redução por A a se a Follow(A) - O correspondente item LR(1) seria: [ A a., m ], com m Follow(A), mas não todo o Follow(A). 155/183

Exemplo: Verificar se a gramática G = ( { S, A }, { a, b, c }, S, P ) é SLR(1), em que P contém as seguintes produções: p0: Z S$ p1: S Sb p2: S baa p3: A asc p4: A asb p5: A a Follow(S) = { $, b, c } Follow(A) = { a } Serão resolvidos os dois conflitos LR(0): No estado 6 : conflito redução/transição No estado 10: conflito redução/redução 156/183

157/183

158/183

Tabela Oráculo SLR(1): a a b c $ S A 1 s3 s2 2 s4 AC e 3 s6 s5 s 4 r1 r1 r1 5 s7 6 r5 s3 s8 o 7 r2 r2 r2 s 8 s10 s9 9 r3 10 r4 r1 r1 r1 t a d Oráculo_T Oráculo_S 159/183

Conclusão: O método SLR(1) resolve conflitos LR(0) quando Conflito redução-transição: quando a Ï Follow(B) [ A a.ab ] [ B d. ] Conflito redução-redução: quando Follow(A) Follow(B) = Æ [ A a. ] [ B b. ] 160/183

5. Parser LALR(1) LookAhead LR(1) - Ainda os mesmos estados que os autómatos LR(0). - Com mais informação associada a cada item - Cada item LR(0) da forma [ A a. ] do estado q é substituído por [ A a., n ], onde n = L (q, A a. ) - Cada item LR(0) da forma [ A a. X b ] do estado q é substituído por [ A a. X b, n ], onde n = L (q, A a. X b ) - Deste modo, a redução num estado com o item [ A a., n ] só será efetuada se a Î n. 161/183

- O método LALR(1) resolve conflitos LR(0) quando: Conflito redução-transição: [ A a.ab ] [ B d. ] resolvido quando a Ï L(q, B d) Conflito redução-redução: [ A a. ] [ B b. ] resolvido quando L(q, A a) Ç L(q, B b) = Æ 162/183

Cálculo dos L() LALR(1): Regra 1: Propagação por transição de estados : L(q, A ax. b ) = È L(q, A a. X b ) [A a. X b ] Î q Uma transição de q para q pelo símbolo X, propaga o L() do item [A a. X b ] para o item [A a X. b ]. Regra 2: Concatenação por cálculo do fecho [A a. B r ] ------------------------------- [B.d ] L(q, B. d ) = È First(r L(q, A a. B r )) [A a. B r ] Î q O L() de cada item do fecho de [A a. B r ] obtém-se concatenando r com L(q, A a. B r ) e calculando o First da cadeia obtida. 163/183

Regra 3: Estado inicial q0 e estado final qf L(q0, Z. S $ ) = { e } L(qf, Z S. $ ) = { $ } As regras são aplicadas por travessias iterativas do autómato. 164/183

Exemplo: Verificar se a gramática G = ( { S, L, R }, { id, =, * }, S, P ) é LALR(1), em que P contém as seguintes produções: p0: Z S$ (não faz parte de G) p1: S L=R p2: S R p3: L *R p4: L id p5: R L O método LALR(1) vai eliminar o conflito redução/transição no estado 4, que ocorreria por LR(0) e SLR(1). 165/183

LR(0) com conflito redução/transição no estado 4: 166/183

SLR(1) com conflito redução/transição no estado 4: 167/183

LALR(1): 168/183

LALR(1): 169/183

LALR(1): 170/183

LALR(1): 171/183

LALR(1): 172/183

LALR(1): 173/183

LALR(1): 174/183

LALR(1): 175/183

LALR(1): 176/183

LALR(1): 177/183

Tabela Oráculo LALR(1): a id = * $ S L R 1 s5 s6 s2 s4 s3 2 AC e 3 r2 s 4 s7 r5 5 r4 r4 6 s5 s6 s9 s10 o 7 s5 s6 s9 s8 s 8 r1 9 r5 r5 10 r3 r3 t a d Oráculo_T Oráculo_S 178/183

Relação entre LL(k), LR(k), SLR(k) e LALR(k): - LL(k) é o parser mais simples e, consequentemente, o com mais problemas, pos nem todas as gramáticas podem ser tratadas com este parser (as recursivas) - As fraquezas do parser LL(k) são superadas pelo parser LR(k) - O parser SLR(1) LR(1) simples; aumenta o poder de análise do LR(0) - O parser LR(1) aumenta o poder de análise do SLR(1), pois ajuda a resolver conflitos redução-redução e redução-transição - O parser LALR(1) aumenta o poder de análise de LR(1); diminui o AFD e a tabela de parsing do LR(1) 179/183

Construção do AFD LALR(1) a partir do AFD LR(1): Seja a gramática com as seguintes produções: A (A) A a 180/183

LR(1): 181/183

LALR(1): (Fazer também o LALR(1) a partir do LR(0) e verificar se são iguais) 182/183

Otimização das Tabelas de Parsing: - São geralmente matrizes esparsas - Tentativas de compactação.... 183/183