Definição de uma Linguagem 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. Um Compilador Simples 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 fluxo de caracteres de entrada Estrutura de Vanguarda Analisador Léxico fluxo de tokens Tradutor Dirigido pela Sintaxe representação intermediária Gramática Livre de Contexto 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 4 Gramática Livre de Contexto Especificação de gramáticas produções. Terminais: dígitos sinais cadeias de caracteres em negrito Não-terminais: nome em itálico 5 Exemplo 1 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 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 Tipo diferente de lista seqüência de comandos. bloco { cmds_opcs } cmds_opcs lista_cmds lista_cmds lista_cmds cmd; cmd; Árvores Gramaticais 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 Processo de procurar uma árvore gramatical para uma dada string de tokens análise gramatical ou análise sintática. Ambigüidade 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 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 Exemplo: Duas Parse Trees 9 5 + 2 string string string + string string - string string - string 2 9 string + string 9 5 5 2 11 12 2
Associatividade de Operadores +,, * 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 Associatividade à Direita 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 9 + 5 * 2 * 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. Precedência de Operadores 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 cmd id = expr if (expr) cmd if (expr) cmd else cmd while (expr) cmd { cmds_opcs } Tradução Dirigida pela Sintaxe 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. 17 18 3
Notação Posfixa 1. Se E for uma variável ou uma constante, então a notação posfixa para E será o próprio E. 2. Se E for uma expressão da forma E 1 op E 2, onde op é qualquer operador binário, então a forma posfixa para E será E 1 E 2 op. 3. Se E for uma expressão da forma (E 1 ), então a notação posfixa para E 1 será também a notação posfixa para E. Ex: (9 5) + 2? 9 (5 + 2)? Exemplo Definição Dirigida pela Sintaxe Produção Regra Semântica expr expr 1 + termo expr.t = expr 1.t termo.t + expr expr 1 termo expr.t = expr 1.t termo.t expr termo expr.t = termo.t termo 0 termo.t = 0 termo 9 termo.t = 9 19 20 Valores dos Atributos nos Nós de uma Parse Tree expr.t = 9 termo.t = 9 expr.t = 95-2+ expr.t = 95- termo.t = 2 termo.t = 5 Esquemas de Tradução 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. 9-5 + 2 21 22 Exemplo 1 resto + termo {imprimir ( + ) } resto 1 resto + termo {imprimir ( + )} resto 1 Exemplo 2 expr expr + termo {imprimir ( + ) } expr expr - termo {imprimir ( ) } expr termo termo 0 {imprimir ( 0 ) } termo 1 {imprimir ( 1 ) } termo 9 {imprimir ( 9 ) } 23 24 4
Ações Explícitas em Parse-Tree expr {imprimir ( + )} expr + termo {imprimir ( - )} expr - termo 2 {imprimir ( 2 )} termo 5 {imprimir ( 5 )} 9 {imprimir ( 9 )} Análise Gramatical 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 26 Parsers Top-Down e Bottom-Up 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. Exemplo: Tipos em Pascal tipo tipo_simples id array [ tipo_simples ] of tipo tipo_simples integer char num pontoponto num 27 28 Construindo Parser Top-Down Passos na Construção Top-Down 1. 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. 2. Encontre o próximo nó para o qual uma sub-árvore deve ser construída. 29 30 5
Construindo Parser Top-Down 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 Construindo Parser Top-Down Escaneando a entrada da esquerda para a direita. 31 32 Backtracking 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). Parsing Descendente Recursivo 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 34 Exemplo Parsing Preditivo Entry: array[num pontoponto num] of integer void reconhecer (token t) { if (lookahead = = t) lookahead = proximo_token; else error ( ); } 35 Exemplo Parsing Preditivo 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 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 ( ); } 37 Recursão à Esquerda É 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 38 Um Tradutor para Expressões Simples 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 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. 41 42 7
Usando um Analisador Léxico Analisador Léxico Funções que um analisador léxico realiza: Tratamento de espaços em branco: brancos tabulações avanço de linha Tratamento de números maiores que 9 (const). Reconhecimento de identificadores e palavraschave. entrada lê caracter empilha caracter de volta analisador léxico passa token e seus atributos Produtor-Consumidor analisador gramatical 43 44 Analisador Léxico Código do Analisador Léxico getchar( ) ungetc(c,stdin) analisador léxico lexan( ) retorna token ao chamador 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 Estrutura de dados armazena informações. lexema, tipo, argumentos e passagem (se função) Interface: armazenamento e recuperação. inserir(s, t) retorna o índice da nova entrada buscar(s) retorna o índice de uma entrada Palavras-chave reservadas: inserir( div,div) inserir( mod, mod) 47 Implementação da Tabela Não é reservado quantidade fixa de espaço para os lexemas. 48 8
Descrição dos Tokens Pseudocódigo para um Scanner 49 50 1 2 3 4 5 6 Máquina de Pilha Abstrata Interface de vanguarda interface de retaguarda. Forma popular de representação intermediária. instruções empilhar 5 valor-r 2 + valor-r 3 * pc pilha 16 7 topo dados 0 11 7 1 2 3 4 Valores-L e Valores-R Distinção entre o significado dos identificadores à esquerda e à direita de uma atribuição. i = 5; i = i + 1; p = q ; Valores-L são localizações, e Valores-R são valores. 51 52 Operações sobre a Pilha push v coloca o valor v no topo da pilha valor-r l coloca o conteúdo do local l no topo da pilha valor-l l coloca o endereço do local l no topo da pilha pop 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 copy copia valor do topo da pilha Exemplo dia := (1461*y) div 4 + (153*m + 2) div 5 + d valor-l dia push 1461 valor-r y * push 4 div push 153 valor-r m * push 2 + push 5 div + valor-r d + := 53 54 9
Fluxo de Controle Máquina de pilha executa instruções sequenciais, a não ser: desvios condicionais ou incondicionais. O destino dos desvios pode ser especificado: pelo operando da instrução. o operando pode especificar um desvio relativo. o destino pode ser especificado simbolicamente, através de rótulos. Fluxo de Controle - Instruções rótulo l alvo dos desvios goto l próxima instrução gofalse l desempilha o valor ao topo da pilha; desvia se for zero gotrue l desempilha o valor ao topo da pilha; desvia se não for zero parar para a execução 55 56 Fluxo de Controle - Instruções 57 10