Você está na página 1de 43

Aula 4 – Análise Sintática

Prof. Guilherme Galante


Análise Sintática (parsing)
● A análise sintática é a etapa do compilador que
ocorre após a análise léxica

● Verifica a corretude gramatical da sequência de


palavras e categorias sintáticas produzida pela
análise léxica (scanner)
● Determina se a entrada está sintaticamente bem
formada
● Constrói uma representação IR do código (árvore)
Estrutura Sintática (parsing)
● A estrutura sintática de um programa relaciona
cada parte do programa com suas sub-partes
componentes
● Por exemplo, um comando if completo tem três
partes que o compõem:
● uma condição de teste
● um comando para executar caso a condição seja
verdadeira, e
● um comando para excutar caso a condição seja falsa
Estrutura Sintática (parsing)
● Quando o compilador identifica um if em um programa, é
importante que ele possa acessar esses componentes
para poder gerar código executável para o if
● Por isso, não basta apenas agrupar os caracteres em
tokens, é preciso também agrupar os tokens em
estruturas sintáticas
Erros de Sintaxe
● Bom compilador deve detectar e localizar erros de
sintaxe
● Boa parte da detecção e recuperação de erros é
feita na fase sintática
● Regras gramaticais da linguagem são desobedecidas
● Metas:
● Relatar a presença de erros de modo claro e acurado
● Recuperar-se de cada erro rapidamente a fim de
detectar erros subsequentes
● Não deve retardar significativamente o processamento
de programas corretos
Erros de Sintaxe
● Felizmente, a maioria dos erros são simples
● Pesquisa com estudantes de Pascal
● 80% dos enunciados contém apenas um erro
● 13% tem dois
● 90% são erros em um único token
● 60% são erros de pontuação: p.ex., uso do ponto e
vírgula (;)
● 20% são erros de operadores e operandos: p.ex.,
omissão de : no símbolo :=
● 15% são erros de palavras-chave: p. ex., erros
ortográficos (wrteln)
Erros de Sintaxe
● Em muitos compiladores, ao encontrar uma construção
mal formada o erro é reportado e a tarefa da Análise
Sintática é dada como concluída
● Mas na prática o compilador pode e até deve reportar o
erroe tentar continuar a Análise Sintática para detectar
outros erros, se houver, e assim diminuir a necessidade
de recomeçar a compilação a cada relato de erro
● A realização efetiva do tratamento de erros pode ser uma
tarefa difícil
● O tratamento inadequado de erros pode introduzir uma
avalanche de erros espúrios, que não foram cometidos
pelo programador, mas pelo tratamento de erros realizado
Erros de Sintaxe
● Três processos, geralmente, são realizados nesse ponto:
1) Notificação
2) Recuperação (modo de pânico):
● pula-se parte subseqüente da entrada até encontrar um
token de sincronização (porto seguro para continuar a
análise)
3) Reparo (recuperação local):
● faz-se algumas suposições sobre a natureza do erro e a
intenção do escritor do programa.
● Altera-se 1 símbolo apenas: despreza o símbolo, ou
substitui ele por outro ou ainda insere um novo token.
● A opção mais comum é inserir 1 símbolo (comum para";"
faltantes)
Reconhecimento de Linguagem
● Toda linguagem tem de ter regras que descrevem sua estrutura
sintática (ou sintaxe)
● Uma gramática livre de contexto (GLC) define uma linguagem:
● Esta linguagem é um conjunto de strings de k tokens
● Cada string de tokens é derivada de uma regra de produção
● Vantagens de se utilizar uma gramática:
● Fornece uma especificação sintática precisa e fácil de entender
● Para certas classes de gramáticas, podemos construir
automaticamente um analisador sintático
● Novas construções que surgem com a evolução da linguagem
podem facilmente ser incorporadas a um compilador se este tem
sua implementação baseada em descrições gramaticais
Gramáticas Livres de Contexto
● Uma Gramática Livre de Contexto é construída utilizando símbolos
terminais e não-terminais, um símbolo de partida e regras de
produções, onde:
● Os terminais são os símbolos básicos a partir dos quais as cadeias
são formadas. Na fase de análise gramatical os tokens da linguagem
representam os símbolos terminais. Ex: if, then, else, num, id, etc.
● Os não-terminais as variáveis sintáticas que denotam cadeias de
caracteres. Impõem uma estrutura hierárquica que auxilia na análise
sintática e influencia a tradução. Ex: cmd, expr.
● Numa gramática um não terminal é distinguido como símbolo de
partida, e o conjunto que o mesmo denota é a linguagem definida pela
linguagem. Ex: program
● As produções de uma gramática especificam como os terminais e não-
terminais podem se combinar para formas as cadeias da linguagem.
Cada produção consiste em um não terminal seguido por uma seta
(ou ::=), seguido por uma cadeia de não terminais e terminais
Exemplo1
expr ::= expr op expr
expr ::= (expr)
expr ::= - expr
expr ::= id
op ::= +
op ::= -
op ::= *
op ::= /

- Simbolos terminais
id + - * / ( )

- Símbolos não-terminais
expr e op , sendo expr o símbolo de partida
Exemplo 2

Quais são aceitas???


Exemplo 2
Convenções Notacionais
Símbolos Terminais:
● Letras minúsculas do inicio do alfabeto, tais como a, b c
● Símbolos de operadores, tais como +, -, etc
● Símbolos de pontuação, tais como parênteses e vírgulas
● Dígitos 0, 1, ..., 9
● Cadeias em negritos como id ou if
Símbolos não-terminais
● Letras maiúsculas do início do alfabeto, tais como A, B, C
● A letra S, quando aparecer é usualmente símbolo de partida
● Nomes em itálico formados por letras minúsculas, como expr ou
cmd
● A menos que seja explicitamente estabelecido, o lado esquerdo da
primeira produção é o símbolo de partida
Convenções Notacionais
● Produções para o mesmo símbolo não terminal a esquerda podem
ser agrupadas utilizando “|”.
● Ex: A::= + | - | ...

● Exemplo:

expr ::= expr op expr


expr ::= (expr)
expr ::= - expr
expr ::= id E ::= E A E|(E)|-E| id
op ::= + A ::= +|-|*|/
op ::= -
op ::= *
op ::= /
Grafos de Sintaxe
Grafos de Sintaxe
<line> ::= [<line> <term>]
<term> ::= <expr> newline
<expr> ::= integer | -<expr>
<expr> ::= <expr> (+|-) <expr>
<expr> ::= <expr> (*|/) <expr>
Derivações
● Processo através do qual as regras de produções da
gramática são aplicadas para formar uma palavra ou
verificar se esta pertence a linguagem

● Verificar se a sequência de tokens é reconhecida:


● Inicia‐se com o símbolo não-terminal chamado raiz
● Aplica‐se produções, substituindo não‐terminais, até
somente restarem terminais
● Uma derivação substitui um não-terminal pelo lado
direito de uma de suas produções
● O símbolo → denota um passo de derivação
Derivações
Derivações Mais à Esquerda ou Direita
● Derivação mais à Esquerda:
● Sequência de formas sentenciais que se obtém
derivando sempre o símbolo não-terminal mais à
esquerda;
● Derivação mais à Direita:
● Sequência de formas sentenciais que se obtém
derivando sempre o símbolo não terminal mais à
direita;
● Considera-se equivalentes todas as derivações
que correspondem à mesma árvore de
derivação
Derivações Mais à Esquerda
Derivações Mais à Direita
Árvores Gramaticais
● Representação gráfica de uma derivação
● Cada nó interior: Não-terminal
● Filhos de cada nó são rotulados pelos símbolos do
lado direito da produção pelos quais foram
substituídos
● Folhas: não terminal ou terminal, lidos da esquerda
para a direita
Árvores Gramaticais
Exemplo: -(id + id)
E

- E E::=-E

( E ) E::=(E)

E + E E::=E+E

Id id E::=id E::=id

* Optar por derivação mais à esquerda ou mais à direira


Árvores Sintáticas Abstratas
● Árvores de análise sintática contém muito mais
informação que o necessário

exp

exp op exp

número + número

● Há forma mais simples: árvores sintáticas


abstratas
Árvores Sintáticas Abstratas

3 4

● Nó rotulado pela operação que ele representa, e


as folhas são rotuladas pelos valores
Gramáticas Ambíguas
● São gramáticas que permitem construir mais de
uma árvore de derivação para uma mesma
sentença
● Isto representa um problema para o analisador
sintático, pois ela não especifica com precisão a
estrutura sintática do um programa
● Este problema pode ser comparado ao dos
autômatos não‐determinísticos, onde dois
caminhos podem aceitar a mesma cadeia
Gramáticas Ambíguas

Ambas produzem x - 2 * y
Gramáticas Ambíguas
● A eliminação de ambiguidade não é tão simples
como a eliminação de não‐determinismo em
autômatos

● Existem duas maneiras:


● Estabelecer uma regra que especifique a cada caso
ambíguo qual é o caminho correto (Sem alterar a
gramática)
● Alterar a gramática para forçar a construção da árvore
sintática correta, removendo a ambiguidade
Reescrita da Gramática
● Considere o exemplo:
id + id * id

● Tratamento de precedência:
● * deve ser derivado primeiro
● Precedência de Operadores
expr ::= expr op expr expr ::= term | term op1 term
expr ::= id term ::= fator | fator op2 fator
op ::= + fator ::= id | (expr)
op ::= - op1 ::= +
op ::= * op1 ::= -
op ::= / op2 ::= *
op2 ::= /

● Essa gramática tem três símbolos variáveis expr, term e fator
● Cada um representa um nível de precedência:
● expr representa a precedência mais baixa, onde estão os 
operadores de soma e subtração
● term representa o próximo nível de precedência, com os operadores 
de multiplicação e divisão
● fator representa o nível mais alto de precedência, onde ficam os 
números isolados e as expressões entre parênteses; isso significa 
que o uso de parênteses se sobrepõe à precedência de qualquer 
operador, como esperado
Reescrita da Gramática
● If then else

cmd ::= if expr then cmd


|if expr then cmd else cmd
|outro

if E1 then S1 else if E2 then S2 else S3


if E1 then S1 else if E2 then S2 else S3

cmd cmd ::= if expr then cmd


|if expr then cmd else cmd
|outro
if expr then cmd
E1

if expr then cmd else cmd


E2 S1 S2

cmd

if expr then cmd else cmd


E1 S2

if expr then cmd Regra geral: associar cada


E2 S1
else ao then anterior mais
próximo
Reescrevendo a gramática
● Todo enunciado entre um then e um else precisa ser
“associado”, isto é não pode terminar com um then ainda
não “associado”
● Um enunciado associado ou é um enunciado if-then-else
contendo somente enunciados associados ou é qualquer
outro tipo de enunciado incondicional

cmd ::= cmd_associado


|cmd_não_associado
cmd_associado ::= if expr then cmd_associado else cmd_associado
|outro
cmd_não_associado ::= if expr then cmd
| if expr then cmd_associado else cmd_não_associado
Gramáticas Recursivas à Esquerda
● Uma gramática é recursiva à esquerda se possui
um não-terminal A, tal que, exista uma derivação
A → Aα
para alguma cadeia α

● É importante para permitir o processamento top-


down
● Não processam gramáticas com essa característica
Eliminação de Recursões à Esquerda
● Agrupamos os produções recursivas
A → Aα1 | Aα2 | ... | Aαn | β1 | β2 | ... | βn
● Onde nenhum β começa com um A

● Substituímos as produções-A por


A → β1A' | β2A' | ... | βnA'
A' → α1A' | α2A' | ... | αnA' | ε
Exemplo:

E → TE’
E → E + T|T E’ → +TE’ | ε
T → T * F|F T → FT’
F → (E)|id T’ → *FT’ | ε
F → (E) | id
Exemplo 2

A → Ac | Aad | bd | ε cad
{

{
{
{
α1 α2 β1 β2 A

A → bdA' | A' A ad

A'→ cA' | adA' | ε


A c

Como fica a árvore sintática para a nova gramática???


Fatoração à Esquerda
● Transformação que facilita a análise sintática
● Preditiva (lookahead)
● Deve ser realizada quando a escolha entre duas
opções começa com uma cadeia comum
● Indecisão sobre qual produção usar
● Neste caso deve se adiar a escolha
● Postergar a escolha até que tenhamos visto o suficiente
da entrada
cmd ::= if expr then cmd else cmd
|if expr then cmd
Cadeia comum
Fatoração à Esquerda: Regra Geral

● Se A → αβ1 | αβ2 forem duas produções e a entrada


começar com uma cadeia não vazia derivada de α, não
sabemos se A deve ser expandida para αβ1 ou αβ2
● Devemos, então, adiar a decisão expandido A para αA' e
após ler a entrada derivada de α, expandir A' para β 1 ou
β2.
A → αA'
A' → β1 | β2
Algoritmo
● Entrada: Gramática G
● Saída: Gramática Fatorada à Esquerda

● Método:
1) Para cada não-terminal A, encontrar o mais longo prefixo α
comum a duas ou mais alternativas
2) Se α ≠ ε, substituir todas as produções de A
A → αβ1 | αβ2 | ...| γ
onde γ são as alternativas que não começam com α
por:
A → αA' | γ
A' → β1| β2 |...| βn
Exemplo
S → iEtS | iEtSeS | a
{
{
{
{
α α β1 γ

S → iEtSS' | a
S' → ε | eS

Você também pode gostar