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:
SS(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
Download

CES-41 Teoria Cap 2