Definição de uma Linguagem n n n Um Compilador Simples Linguagem = sintaxe + semântica Especificação da sintaxe: gramática livre de contexto, BNF (Backus-Naur Form) Especificação Semântica: informal (textual), operacional, denotacional, de ações. n n n Objetivo: traduzir expressões infixas em posfixas. 9 – 5 + 2 Æ9 5 –2+ Aplicabilidade: computação baseda em pilha. Idéias claras → construções mais gerais das linguagens. 1 2 Estrutura de Vanguarda Gramática Livre de Contexto n n fluxo de caracteres de entrada Analisador Léxico fluxo de tokens Tradutor Dirigido pela Sintaxe n representação intermediária n Um conjunto de tokens, símbolos terminais. Um conjunto de símbolos não-terminais. Um conjunto de produções, cada produção consiste de um não-terminal, uma seta, e uma seqüência de tokens e/ou nãoterminais. Um não-terminal designado como símbolo inicial (de partida). 3 Gramática Livre de Contexto n n n n n Exemplo 1 Especificação de gramáticas → produções. Terminais: n dígitos sinais cadeias de caracteres em negrito lista à lista + dígito lista à lista – dígito lista à dígito dígito à 0|1|2|3|4|5|6|7|8|9 lista à lista + dígito | lista dígito | dígito Não-terminais: n 4 nome em itálico 5 • Cada nó da árvore → símbolo da gramática. • Nó interior e seus filhos → produção. • Nó interior → lado esquerdo, os filhos → lado direito. 6 1 Exemplo 2 n Árvores Gramaticais Tipo diferente de lista → seqüência de comandos. bloco à { cmds_opcs } cmds_opcs à lista_cmds | ∈ lista_cmds à lista_cmds cmd; | cmd; n n Mostra graficamente como o símbolo inicial de uma gramática deriva uma string da linguagem. Para uma produção A à XYZ A X Y Z 7 8 Parsing n Ambigüidade Processo de procurar uma árvore gramatical para uma dada string de tokens → análise gramatical ou análise sintática. n n Parse-tree gera uma string de tokens, mas uma string pode possuir várias parsetrees, se a gramática for ambígua. Solução: usar sempre gramáticas nãoambíguas, ou gramáticas ambíguas com informações adicionais sobre como resolver ambigüidades. 9 10 Ambigüidade - Exemplo n n n Exemplo: Duas Parse Trees Mostrar que uma gramática é ambígua → encontrar cadeia de tokens que tenha mais de uma árvore gramatical. Suponha que não fizéssemos distinção entre dígitos e listas. string à string + string | string - string |0|1|2|3|4|5|6|7|8|9 11 9 – 5 + 2 string string string 9 - string + string string 5 2 string - string string + string 9 5 2 12 2 Associatividade de Operadores n n Associatividade à Direita +, –, * e / são associativos à esquerda, na maioria das linguagens de programação: 9+5+2 Atribuição em C e exponenciação são associativos à direita: a=b=c n Cadeias, como a = b = c, com um operador associativo à direita são geradas pela seguinte gramática: direita à letra = direita | letra letra à a | b | … | z 13 14 Precedência de Operadores Precedência de Operadores n 9 + 5 * 2 n * tem maior precedência do que + Usaremos dois não-terminais (expr e termo) para representar os dois níveis de precedência em expressões e um outro (fator) para gerar as unidades básicas de expressões. n expr à expr + termo | expr – termo | termo termo à termo * fator | termo / fator | fator fator à dígito | ( expr ) dígito à 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 15 16 Sintaxe dos Comandos Tradução Dirigida pela Sintaxe n cmd → id = expr | if (expr) cmd | if (expr) cmd else cmd | while (expr) cmd | { cmds_opcs } n n 17 Definição dirigida pela sintaxe usa uma gramática livre de contexto para especificar a estrutura sintática da entrada. Associa a cada símbolo um conjunto de atributos e a cada produção associa regras semânticas para computar os valores dos atributos. Gramática e regras semânticas constituem a definição dirigida pela sintaxe. 18 3 Exemplo – Definição Dirigida pela Sintaxe Notação Posfixa 1. 2. 3. Se E for uma variável ou uma constante, então a notação posfixa para E será o próprio E. Se E for uma expressão da forma E1 op E2, onde op é qualquer operador binário, então a forma posfixa para E será E1 E2op. Se E for uma expressão da forma (E1), então a notação posfixa para E1 será também a notação posfixa para E. Ex: (9 – 5) + 2 ? 9 – (5 + 2) ? n Produção expr à expr1 + termo expr à expr1 – termo expr à termo termo à 0 … termo à 9 Regra Semântica expr.t = expr1.t || termo.t || ‘+’ expr.t = expr1 .t || termo.t || ‘–’ expr.t = termo.t termo.t = ‘0’ … termo.t = ‘9’ 19 20 Valores dos Atributos nos Nós de uma Parse Tree Esquemas de Tradução n expr.t = 95-2+ expr.t = 95expr.t = 9 termo.t = 2 n termo.t = 5 termo.t = 9 9 - 5 + Gramática livre de contexto com fragmentos de programas (ações semânticas) embutidos no lado direito das produções. Semelhante à definição dirigida por sintaxe, mas com a ordem de avaliação das regras semânticas explicitada. 2 21 Exemplo 1 n Exemplo 2 resto à + termo {imprimir (‘+’) } resto1 expr à expr + termo {imprimir (‘+’) } expr à expr - termo {imprimir (‘–’) } expr à termo termo à 0 {imprimir (‘0’) } termo à 1 {imprimir (‘1’) } … termo à 9 {imprimir (‘9’) } resto + termo 22 {imprimir (‘+’)} resto1 23 24 4 Ações Explícitas em Parse-Tree Análise Gramatical n expr expr expr - {imprimir (‘+’)} + termo {imprimir (‘-’)} 2 {imprimir (‘2’)} termo n 5 {imprimir (‘5’)} termo 9 n n {imprimir (‘9’)} Processo de se determinar se uma cadeia de tokens pode ser gerada por uma gramática. Método de análise gramatical para construir tradutores dirigidos pela sintaxe. Na prática, o parsing de linguagens pode ser feito por algoritmos lineares. Travessia linear da esquerda para a direita, olhando um token de cada vez. 25 Parsers Top-Down e Bottom-Up n n n Refere-se à ordem em que os nós da parse tree são criados. Top-down: mais fáceis de escrever “à mão”. Bottom-up: suportam uma classe maior de gramáticas e de esquemas de tradução, e é usado pelas ferramentas de geração de parsers. 27 Construindo Parser Top-Down 1. 2. 26 Exemplo: Tipos em Pascal tipo à tipo_simples | ↑ id | array [ tipo_simples ] of tipo tipo_simples à integer | char | num pontoponto num 28 Passos na Construção Top-Down Para cada nó n, com um não-terminal A, selecione uma das produções de A e construa os filhos de n para os símbolos à direita da produção. Encontre o próximo nó para o qual uma sub-árvore deve ser construída. 29 30 5 Construindo Parser Top-Down n n n Construindo Parser Top-Down n Para algumas gramáticas basta uma única travessia da esquerda para a direita da string de entrada. Token corrente é chamado de símbolo lookahead. Exemplo: array [num pontoponto num ] of integer Escaneando a entrada da esquerda para a direita. 31 32 Backtracking n n Parsing Descendente Recursivo A escolha de uma produção pode exigir tentativa-e-erro, voltando para tentar novas alternativas possíveis. Parsing Preditivo: parsing em que não ocorre backtracking (retrocesso). n n n Método de análise sintática top-down em que um conjunto de procedimentos recursivos é usado para processar a entrada. Cada procedimento está associado a um símbolo não-terminal da gramática. Parsing Preditivo é um caso especial de parsing descendente recursivo em que o símbolo lookahead determina sem ambiguidades o procedimento a ser chamado para cada nãoterminal. 33 Exemplo – Parsing Preditivo 34 Exemplo – Parsing Preditivo Entry: array[num pontoponto num] of integer void reconhecer (token t) { if (lookahead = = t) lookahead = proximo_token; else error ( ); } 35 void tipo ( ) { if (lookahead está em { integer, char, num }) tipo_simples ( ); else if (lookahead = = ‘↑’) { reconhecer (‘↑’); reconhecer (id); } else if (lookahead = = array) { reconhecer (array); reconhecer (‘[’); tipo_simples ( ); reconhecer (‘]’); reconhecer (of); tipo ( ); } else error ( ); } 36 6 Exemplo – Parsing Preditivo Recursão à Esquerda void tipo_simples ( ) { if (lookahead = = integer) reconhecer (integer) else if (lookahead = = char) reconhecer (char) else if (lookahead = = num) { reconhecer (num); reconhecer (pontoponto); reconhecer (num); } else error ( ); } n n n É possível um analisador gramatical descendente recursivo rodar para sempre. O problema emerge em produções recursivas à esquerda. Problema: A → Aα | β Solução: A → βR R → αR | ∈ 37 38 Um Tradutor para Expressões Simples n n n Um Tradutor para Expressões Simples Utilizando-se as técnicas apresentadas: construção de um tradutor dirigido pela sintaxe. Traduz expressões aritméticas para a forma posfixa. Necessário adaptar o esquema de tradução. 39 40 Análise Léxica n n n n 41 Lê e converte a entrada para um fluxo de tokens. Uma seqüência de caracteres de entrada compõem um único token ⇒ lexema. Um scanner isola o parser do lexema dos tokens. Quando um lexema é examinado, algum mecanismo é necessário para verificação. 42 7 Usando um Analisador Léxico n Analisador Léxico Funções que um analisador léxico realiza: n Tratamento de espaços em branco: n n n n n passa token e seus atributos lê caracter brancos tabulações avanço de linha analisador léxico entrada analisador gramatical empilha caracter de volta Tratamento de números maiores que 9 (const). Reconhecimento de identificadores e palavraschave. Produtor-Consumidor 43 44 Analisador Léxico Código do Analisador Léxico retorna token ao chamador getchar( ) ungetc(c,stdin) analisador léxico lexan( ) tokenval variável global 45 int lexan ( ) { int t; while(TRUE) { t = getchar( ); if (t = = ‘ ’ || t = =‘\t’) ; else if (t = =‘\n’) clinha++; else if (isdigit(t)) { tokenval = t – ‘0’; t = getchar(); while (isdigit(t)) { { tokenval = tokenval * 10 + t – ‘0’; t = getchar(); } ungetc(t,stdin); return NUM; } else … 46 A Tabela de Símbolos n Estrutura de dados → armazena informações. n n lexema, tipo, argumentos e passagem (se função) n Não é reservado quantidade fixa de espaço para os lexemas. Interface: armazenamento e recuperação. n n n Implementação da Tabela inserir(s, t) – retorna o índice da nova entrada buscar(s) – retorna o índice de uma entrada Palavras-chave reservadas: n n inserir(“div”,div) inserir(“mod”, mod) 47 48 8 Pseudocódigo para um Scanner Descrição dos Tokens 49 50 Máquina de Pilha Abstrata Interface de vanguarda → interface de retaguarda. Forma popular de representação intermediária. n n instruções 1 2 3 4 5 6 Valores-L e Valores-R empilhar 5 valor-r 2 + valor-r 3 * … pilha 16 7 Distinção entre o significado dos identificadores à esquerda e à direita de uma atribuição. n dados ← topo ← pc 0 11 7 … 1 2 3 4 i = 5; i = i + 1; p↑ = q ↑; Valores-L são localizações, e Valores-R são valores. n 51 Operações sobre a Pilha n n n n n n 52 Exemplo v – coloca o valor v no topo da pilha v a l o r -r l – coloca o conteúdo do local l no topo da pilha v a l o r -l l – coloca o endereço do local l no topo da pilha p o p – remove o valor do topo da pilha := atribui o valor-r no topo da pilha ao valor-l abaixo, e os dois são removidos da pilha c o p y – copia valor do topo da pilha push 53 dia := (1461*y) div 4 + (153*m + 2) div 5 + d v a l o r- l d i a push 1461 v a l o r- r y * push 4 div push 153 v a l o r- r m push 2 + push 5 div + v a l o r- r d + := * 54 9 Fluxo de Controle n n Fluxo de Controle - Instruções Máquina de pilha executa instruções sequenciais, a não ser: desvios condicionais ou incondicionais. O destino dos desvios pode ser especificado: n n n rótulo n goto l n pelo operando da instrução. o operando pode especificar um desvio relativo. o destino pode ser especificado simbolicamente, através de rótulos. n n 55 l – alvo dos desvios – próxima instrução g o f a l s e l – desempilha o valor ao topo da pilha; desvia se for zero g o t r u e l – desempilha o valor ao topo da pilha; desvia se não for zero p a r a r – para a execução n 56 Fluxo de Controle - Instruções 57 10