CES-41
COMPILADORES
Capítulo II
Gramáticas e Linguagens
Capítulo II – Gramáticas e
Linguagens
2.1 – Gramáticas e linguagens de programação
2.2 – Gramáticas livres de contexto
2.3 – GLC’s ambíguas
2.4 – GLC’s recursivas à esquerda
2.1 – Gramáticas e Linguagens de
Programação
As linguagens de programação conhecidas não são livres de
contexto
Exemplo: seja a linguagem
L = {w c w | w Є (a | b)*}
onde ∑ = {a, b, c}
Sentença típica: aababcaabab
L = {w c w | w Є (a | b)*}
onde ∑ = {a, b, c}
Sentença típica: aababcaabab
A linguagem é uma simplificação daquelas que exigem a
declaração de todo identificador antes de ser usado
A primeira ocorrência de aabab representa sua declaração
e a segunda, seu uso
A letra c representa a separação entre declarações e
comandos executáveis
L = {w c w | w Є (a | b)*}
onde ∑ = {a, b, c}
Sentença típica: aababcaabab
A Teoria de Linguagens Formais demonstra que tais
linguagens não podem ser geradas por gramáticas livres de
contexto (GLC’s)
Ou seja, apresentam sensibilidade ao contexto
No entanto não se usam gramáticas sensíveis ao contexto
(GSC’s) para analisar programas escritos em linguagens de
programação
Gramáticas regulares são usadas na análise léxica
Gramáticas livres de contexto são usadas na análise sintática
A sensibilidade ao contexto das linguagens é verificada na
análise semântica
Exemplo: declaração de identificadores
Um identificador é colocado na tabela de símbolos
quando de sua declaração
Sua presença nessa tabela é verificada, quando de seu
uso (teste semântico)
2.2 – Gramáticas Livres de Contexto
2.2.1 – Definição
Tipicamente são usadas três especificações para se definir
uma linguagem de programação:
Especificações sintáticas, feitas através de uma GLC
Especificações léxicas, usando expressões regulares
Especificações semânticas, usando restrições às
construções sintáticas
Em compilação, para a maioria das linguagens de
programação, há uma simplificação tal que:
GLC’s são usadas para guiar toda a fase de análise e a
geração do código intermediário (todo o front-end do
compilador)
O analisador sintático é fundamentado numa GLC
Ele tem como escravo o analisador léxico
Ele tem como recheio o analisador semântico e o
gerador do código intermediário
GLC é uma entidade G contendo quatro componentes:
Um conjunto finito N de símbolos não-terminais
Um alfabeto, ou seja, um conjunto finito ∑ de símbolos
terminais, também chamados de átomos
A designação de um dos não-terminais de N para ser o
símbolo inicial, referenciado muitas vezes por S
Um conjunto P de produções
Simbolicamente, G = {N, ∑, S, P}
Forma geral de uma produção: A → α
A é um não-terminal, ou seja, A Є N, e é chamado de
lado-esquerdo
α é um conjunto de zero ou mais terminais e/ou não-
terminais, ou seja, α Є (N ∑)*, e é chamado de ladodireito
De passagem, produção com sensibilidade ao contexto:
βAγ→βαγ
A é um não-terminal, ou seja, A Є N
α, β, γ são conjuntos de zero ou mais terminais e/ou não-
terminais, ou seja, α, β, γ Є (N ∑)*
Pelo menos um entre β e γ não é vazio
A pode ser substituído por α, se estiver entre β e γ
2.2.2 – Construção de um programa ou sentença
■ Seja a seguinte gramática G = {N, ∑, S, P}
N = {S, A}
∑ = {(, )}
P = {S ( A ) | S ( A ) , A ε | S}
Símbolo inicial: S
■ O símbolo inicial S é o embrião
■ A construção se inicia substituindo-se o símbolo inicial, pelo
lado direito de uma de suas produções:
SS(A)
■ Tem início a formação do feto do programa
P = {S ( A ) | S ( A ) , A ε | S}
■ A seguir, lados esquerdos de produções que fazem parte
desse feto vão sendo substituídos pelos lados direitos
S S(A)
(A)(A)
()(A)
()(S)
()((A))
()(( ))
A construção termina quando todos os nãoterminais tiverem sido substituídos
O símbolo inicial e cada estado do feto são
formas sentenciais do programa ou da
sentença
Sentença é uma forma sentencial sem nãoterminais
P = {S ( A ) | S ( A ) , A ε | S}
S S(A)
(A)(A)
()(A)
()(S)
()((A))
()(( ))
A linguagem gerada por G, é o conjunto de
todas as sentenças geradas por G
Simbolicamente
L(G) = { w * S * w }
P = {S ( A ) | S ( A ) , A ε | S}
S S(A)
(A)(A)
()(A)
()(S)
()((A))
()(( ))
Definição recursiva de forma sentencial de uma
gramática:
1) O símbolo inicial S é uma forma sentencial
2) Seja A um não-terminal e sejam α, β, γ
cadeias de símbolos terminais e/ou não
terminais
3) Se βAγ for uma forma sentencial e A → α
uma produção, então β α γ será também uma
forma sentencial
P = {S ( A ) | S ( A ) , A ε | S}
S S(A)
(A)(A)
()(A)
()(S)
()((A))
()(( ))
Derivação direta é a substituição, numa forma
sentencial, de um não-terminal pelo lado
direito de uma de suas produções
O processo ao lado apresenta 6 derivações
diretas
P = {S ( A ) | S ( A ) , A ε | S}
S S(A)
(A)(A)
()(A)
()(S)
()((A))
()(( ))
Derivação de uma forma sentencial é uma
sequência de zero ou mais derivações diretas
para produzir essa forma, começando do
símbolo inicial
Simbolicamente:
S * S,
S * ( ) ( ( ) )
e
S * ( ) ( A )
Outro símbolo: + : sequência de uma ou mais
derivações diretas
P = {S ( A ) | S ( A ) , A ε | S}
As definições de derivação e derivação direta e
S S(A)
os símbolos , * e + podem ser aplicados
(A)(A)
a
()(A)
- Não-terminais diferentes do símbolo inicial
()(S)
- Sub-cadeias de sentenças
()((A))
- Sub-cadeias de formas sentenciais
()(( ))
Exemplos:
A S (A)( )
A + ( A )
A * ( )
Exemplo: produção de um programa na linguagem LAtrib,
com as seguintes características:
■ Programas têm só o módulo principal
■ Esse módulo tem cabeçalho, declarações e comandos de
atribuição
■ As variáveis podem ser inteiras e reais, escalares
■ Operadores de expressões: soma e subtração
■ Expressões podem ter parêntesis
Gramática para LAtrib:
∑ = {program, var, int, real, ID, CTINT, CTREAL, ‘{’, ‘}’,
‘;’, ‘,’ , ‘=’ , ‘+’ , ‘-’ , ‘(’, ‘)’}
Os átomos literais em negrito são palavras reservadas
N = {Programa, Cabeçalho, Declarações, ListDecl, Declaração, Tipo,
ListId, Comandos, ListCmd, CmdAtrib, Expressão, Termo}
O símbolo inicial é Programa
Produções da Gramática para LAtrib: Seja o programa Exemplo:
Programa → Cabeçalho Declarações Comandos
Cabeçalho → program ID ;
program Exemplo;
var int i, j; real x;
{
i = 2; j = 3;
x = 5.5–(i+j);
}
Declarações → ε | var ListDecl
ListDecl → Declaração | ListDecl Declaração
Declaração → Tipo ListId ;
Tipo → int | real
Seja a construção do
ListId → ID | ListId , ID
programa Exemplo
Comandos → { ListCmd }
ListCmd → ε | ListCmd CmdAtrib
CmdAtrib → ID = Expressão ;
Expressão → Termo | Expressão + Termo | Expressão - Termo
Termo → ID | CTINT | CTREAL | ( Expressão )
program Exemplo; var int i, j; real x;
{i = 2; j = 3; x = 5.5 – (i + j);}
Programa Cabeçalho Declarações Comandos
+ program ID(Exemplo) ; var ListDecl { ListCmd }
+ program ID(Exemplo) ; var ListDecl Declaração
{ ListCmd CmdAtrib }
+ program ID(Exemplo) ; var Declaração Declaração
{ ListCmd CmdAtrib CmdAtrib }
+ program ID(Exemplo) ; var Declaração Declaração
{ ListCmd CmdAtrib CmdAtrib CmdAtrib }
program Exemplo; var int i, j; real x;
{i = 2; j = 3; x = 5.5 – (i + j);}
+
program ID(Exemplo) ; var Declaração Declaração
{ CmdAtrib CmdAtrib CmdAtrib }
+
program ID(Exemplo) ; var Tipo ListId ; Tipo ListId ;
{ ID(i) = Expressão ; ID(j) = Expressão ;
ID(x) = Expressão ; }
+
program ID(Exemplo) ;
var int ListId , ID(j) ; real ID(x) ;
{ ID(i) = Termo ; ID(j) = Termo ;
ID(x) = Expressão - Termo ; }
program Exemplo; var int i, j; real x;
{i = 2; j = 3; x = 5.5 – (i + j);}
+
program ID(Exemplo) ;
var int ID(i) , ID(j) ; real ID(x) ;
{ ID(i) = Termo ; ID(j) = Termo ;
ID(x) = Termo - ( Expressão ) ; }
+
program ID(Exemplo) ;
var int ID(i) , ID(j) ; real ID(x) ;
{ ID(i) = Termo ; ID(j) = Termo ;
ID(x) = Termo - ( Expressão + Termo ) ; }
program Exemplo; var int i, j; real x;
{i = 2; j = 3; x = 5.5 – (i + j);}
+
program ID(Exemplo) ;
var
int ID(i) , ID(j) ; real ID(x) ;
{
ID(i) = Termo ; ID(j) = Termo ;
ID(x) = Termo - ( Termo + Termo ) ;
}
program Exemplo; var int i, j; real x;
{i = 2; j = 3; x = 5.5 – (i + j);}
+
program ID(Exemplo) ;
var
int ID(i) , ID(j) ; real ID(x) ;
{
ID(i) = CTINT(2) ; ID(j) = CTINT(3) ;
ID(x) = CTREAL(5.5) - (ID(i) + ID(j) ) ;
}
2.2.3 – Árvores sintáticas
■ Árvore sintática é uma representação gráfica de uma
derivação
■ Exemplo: árvore sintática de S * ( ) ( ( ) )
Árvore sintática do
programa Exemplo
program Exemplo;
var int i, j; real x;
{
i = 2; j = 3;
x = 5.5–(i+j);
}
2.3 – GLC’s Ambíguas
Uma GLC é ambígua, se uma de suas sentenças possuir 2 ou
mais árvores sintáticas distintas
Exemplo: Seja a gramática G = {, N, P, S} tal que:
= {a, b}; N = {S}; P = {S S b S a}
G é ambígua pois a sentença ababa tem duas árvores
sintáticas:
Exemplo: a Língua Portuguesa sem pontuação teria sérias
ambiguidades
A frase
matar o rei não é pecado
teria dois sentidos:
1) matar o rei não
é pecado
2) matar o rei não é pecado
Em compilação, as gramáticas devem ser não-ambíguas, ou
então, deve-se acrescentar regras para resolver ambiguidades
Exemplo: comando if-else da Linguagem C
Produções:
S if B S else S if B S a1 a2
B b1 b2
S if B S else S if B S a1 a2
B b1 b2
A sentença
if b1 if b2 a1 else a2
tem duas árvores sintáticas, a saber:
Regra de solução: fazer
o else corresponder ao
último if
2.4 – GLC’s Recursivas à Esquerda
Uma GLC é recursiva à esquerda, se tiver um não-terminal
A tal que haja uma derivação A + A, onde (N
)*
Exemplo: Na gramática G = {, N, P, S} tal que:
= {a, b}; N = {S}; P = {S S b S a}
tem-se
S SbS
(recursividade imediata à esquerda)
Exemplo: Na gramática da linguagem LAtrib, as seguintes
produções são recursivas à esquerda:
ListDecl → ListDecl Declaração
ListId → ListId , ID
ListCmd → ListCmd CmdAtrib
Expressão → Expressão + Termo | Expressão - Termo
Existe recursividade não-imediata à esquerda
Exemplo: Seja a gramática G = {, N, P, S} na qual:
= {a, b, c, d}; N = {S, A};
P = {S Aa b , A Sc d }
Essa gramática é não-imediatamente recursiva à esquerda
pois:
S Aa Sca
Análise sintática top-down - grande grupo de métodos
muito populares de análise sintática:
Não consegue tratar gramáticas recursivas à esquerda
Tais gramáticas devem então ser transformadas em outras
equivalentes não-recursivas à esquerda
Essa transformação será vista no Capítulo V
Análise sintática bottom-up - outro grande grupo de
métodos muito populares de análise sintática:
Trabalha mais eficientemente com gramáticas recursivas à
esquerda
Yacc usa análise bottom-up