370 likes | 535 Views
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.
E N D
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 numaGLC’s • 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 lado-direito
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ão-terminais 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ão-terminais
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} 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 β α γ é também uma forma sentencial S S ( A ) ( A ) ( A ) ( ) ( A ) ( ) ( S ) ( ) ( ( A ) ) ( ) ( ( ) )
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 os símbolos , * e + podem ser aplicados a - Não-terminais diferentes do símbolo inicial - Sub-cadeias de sentenças - Sub-cadeias de formas sentenciais Exemplos: A S ( A ) ( ) A +( A ) A *( ) S S ( A ) ( A ) ( A ) ( ) ( A ) ( ) ( S ) ( ) ( ( 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
Seja o programa Exemplo: program Exemplo; var int i, j; real x; { i = 2; j = 3; x = 5.5–(i+j); } Produções da Gramáticapara LAtrib: Programa → Cabeçalho Declarações Comandos Cabeçalho → program ID ; Declarações → ε | varListDecl ListDecl → Declaração | ListDecl Declaração Declaração → Tipo ListId; Tipo → int | real ListId → ID | ListId, ID Comandos→ {ListCmd} ListCmd → ε | ListCmdCmdAtrib CmdAtrib → ID =Expressão; Expressão → Termo | Expressão+Termo | Expressão-Termo Termo → ID | CTINT | CTREAL | (Expressão) Seja a construção do programa Exemplo
program Exemplo; var int i, j; real x; {i = 2; j = 3; x = 5.5 – (i + j);} ProgramaCabeçalho Declarações Comandos +program ID(Exemplo) ; varListDecl{ListCmd} +program ID(Exemplo) ; var ListDecl Declaração {ListCmdCmdAtrib} +program ID(Exemplo) ; var Declaração Declaração {ListCmdCmdAtribCmdAtrib} +program ID(Exemplo) ; var Declaração Declaração {ListCmdCmdAtribCmdAtribCmdAtrib}
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 {CmdAtribCmdAtribCmdAtrib} +program ID(Exemplo) ; var Tipo ListId;Tipo ListId; { ID(i)=Expressão; ID(j)=Expressão; ID(x)=Expressão;} +program ID(Exemplo) ; var intListId, 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 S b S (recursividade imediata à esquerda)
Exemplo: Na gramáticada linguagem LAtrib, as seguintes produções são recursivas à esquerda: ListDecl → ListDecl Declaração ListId → ListId, ID ListCmd → ListCmdCmdAtrib 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 A a S c a
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