Um Tradutor Dirigido por Sintaxe Simples Apresenta de

















































































- Slides: 81
Um Tradutor Dirigido por Sintaxe Simples • Apresenta de forma resumida boa parte do assunto que veremos em detalhes no restante do curso • Dá uma visão geral do processo de compilação
Um compilador simples • Objetivo: traduzir expressões infixas em posfixas 9– 5+2 95– 2+ • Aplicabilidade: computação baseda em pilha (por exemplo, gerador de código para. NET e JVM)
Front-end de um compilador Programa fonte Analisador léxico tokens Parser árvore sintática Ger. de código intermediário Three-address code Tabela de Símbolos
Definição de uma linguagem • Linguagem = sintaxe + semântica • Sintaxe = forma • Semântica = significado
Definição de uma linguagem • Especificação da sintaxe: – gramática livre de contexto, BNF (Backus -Naur Form) • Especificação da Semântica: – normalmente informal (textual) – formal: uso de semântica operacional, denotacional, de ações, etc.
Exemplo: if-else if ( expression ) statement else statement
Exemplo: if-else if ( expression ) statement else statement stmt g if ( expr ) stmt else stmt
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 sequencia de tokens e/ou não terminais • Um não terminal designado como símbolo inicial
Exemplo 1 list g list + digit list g list - digit list g digit g 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
Exemplo 1 a list g list + digit | list - digit | digit g 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
Exemplo 2 call g id ( optparams ) optparams g params | params g params , param | param
Derivações • Uma gramática deriva strings a partir do seu símbolo inicial e repetidamente substituindo não-terminais pelo corpo de sua produção • As sequencias de terminais produzidas desta formam a linguagem definida pela gramática • Exemplos: 9, 9 - 5 + 2
Parsing • Problema de pegar uma string de terminais e verificar como derivá-la a partir do símbolo inicial da gramática; caso não seja possível, reportar erros de sintaxe. • Processo de procurar uma parse-tree para uma dada sequencia de terminais.
Parse Trees • Mostra graficamente como o símbolo inicial de uma gramática deriva uma string da linguagem. • Para uma produção A g XYZ A X Y Z
Parse Trees • • A raiz é o símbolo inicial Cada folha é um terminal ou Cada nó interior é um não-terminal Se A é um não-terminal e X 1, X 2, . . . , Xn são labels de filhos deste nó, tem que haver uma produção A g X 1 X 2. . . Xn
Exemplo • 9 – 5 + 2 list digit 9 - 5 + 2
Ambiguidade • Parse-tree gera uma string, mas uma string pode possuir várias parse-trees, 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 ambiguidades.
Ambiguidade - Exemplo • string g string + string | string - string |0|1|2|3|4|5|6|7|8|9
Exemplo: Duas parse trees • 9 – 5 + 2 string 9 - string + string 5 2 string 9 - string 5 + string 2
Associatividade de Operadores • +, –, * e / são associativos à esquerda, na maioria das linguagens de programação: 9 – 5 + 2 é equivalente a (9 -5)+2 • Atribuição em C e exponenciação são associativos à direita: a=b=c 2**3**4
Associatividade à direita right g letter = right | letter g a | b | … | z
Precedência de operadores • 9+5*2 • * tem maior precedência do que + • Usaremos dois não-terminais para representar os dois níveis de precedência em expressões e um outro para gerar as unidades básicas de expressões.
Precedência de operadores • expr g expr + term | expr – term | term g term * factor | term / factor | factor g digit | ( expr ) digit g 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
Exemplo • stmt g id = expression ; | if ( expression ) stmt else stmt | while ( expression ) stmt | do stmt while ( expression ) ; | { stmt } stmts g stmts stmt |
Tradução dirigida por sintaxe • Tradução dirigida associa regras ou trechos de programa às produções de uma gramática.
Exemplo expr g expr 1 + term traduza expr 1; traduza term; trate + ;
Conceito: Atributo • Um valor associado a um construtor do programa. • Exemplos: – Tipo em uma expressão; – Número de instruções geradas; – Localização da primeira instrução gerada por um construtor;
Conceito: Esquema de tradução (dirigida pela sintaxe) • Notação para associar trechos de programa a produções de uma gramática • Os trechos de programa são executados quando a produção é usada durante a análise sintática • O resultado da execução desses trechos de programa, na ordem criada pela análise sintática, produz a tradução desejada do programa fonte
Notação pós-fixada • Se E é uma variável ou constante, sua notação pós-fixada é ela mesma; • Se E é uma expressão da forma E 1 op E 2, então sua notação pós fixada é E 1’ E 2’ op, onde E 1’ e E 2’ são as notações pós-fixadas de E 1 e E 2 respectivamente; • Se E é uma expressão com parênteses ( E ), então sua notação pós-fixada é a mesma notação pós-fixada de E 1.
Exemplo • (9 -5)+2 95 -2+ • 9 -(5+2) 952+- • 9 -(5+2)*3 952+-3*
Definição dirigida por sintaxe • Definição dirigida por sintaxe associa a cada símbolo um conjunto de atributos; • e a cada produção, regras semânticas para computar os valores dos atributos (Gramática de atributos)
Definição dirigida por sintaxe 1 Dada uma string x, construa uma parse tree para ela; 2 Aplique as regras semânticas para avaliar os atributos em cada nó.
Exemplo – valores dos atributos nós de uma parse tree expr. t = 95 -2+ expr. t = 95 expr. t = 9 term. t = 2 term. t = 5 term. t = 9 9 - 5 + 2
Tipos de Atributos • Atributos sintetizados: seus valores são obtidos a partir dos filhos de um determinado nó; Podem ser calculados através de uma travessia bottom-up; • Atributos herdados: têm seus valores definidos a partir do próprio nó, de seus pais ou seus irmãos.
Exemplo – Definição dirigida por sintaxe • Produção expr g expr 1 + term expr g expr 1 – term expr g term g 0 term g 1 … term g 9 Regra semântica expr. t = expr 1. t || term. t || ‘+’ expr. t = expr 1. t || term. t || ‘-’ expr. t = term. t = ‘ 0’ term. t = ‘ 1’ term. t = ‘ 9’
Travessias • Travessia em profundidade (depth-first) • procedure visit (node N) { for (each child C of N, from left to right) { visit(C); } evaluate semantic rules at node N; }
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.
Exemplo 1 • rest g + term {print (‘+’) } rest 1 rest + term {print(‘+’)} rest 1
Exemplo 2 • expr g expr + term { print (‘+’) } expr g expr - term { print (‘-’) } expr g term g 0 { print (‘ 0’) } term g 1 { print (‘ 1’) } … term g 9 { print (‘ 9’) }
Exemplo 2 expr - + term 2 term 5 9
Ações traduzindo 9=5+2 em 95 -2+ expr + term {print(‘-’)} - 2 term 5 9 {print(‘+’)} {print(‘ 9’)} {print(‘ 5’)} {print(‘ 2’)}
Parsing • Processo de determinar como uma string de terminais pode ser gerada por uma gramática • Conceitualmente é a construção da parse tree • Mas a parse tree pode não ser efetivamente construída durante a compilação.
Parsing • Para gramáticas livres de contexto sempre é possível construir um parser complexidade O(n 3) para fazer o parsing de n tokens. • Na prática, o parsing de linguagens de programação normalmente pode ser feito linearmente. • Travessia linear da esquerda para a direita, olhando um token de cada vez.
Top-down ou bottom-up parsers • 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; são frequentemente usados/gerados pelas ferramentas de geração automática de parsers.
Exemplo • stmt g expr ; | if (expr ) stmt | for ( optexpr ; optexpr ) stmt | other optexpr g expr |
Exemplo stmt for ( optexpr ; expr optexpr for ( ; expr ) other stmt
Construindo um parser 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.
Construindo um parser top-down • • • Para algumas gramáticas basta uma única travessia da esquerda para a direita da string de entrada. Token corrente é chamado de lookahead symbol. Exemplo: for ( ; expr ) other
Backtracking • • A escolha de uma produção pode exigir tentativa-e-erro, voltando para tentar novas alternativas possíveis. Predictive-parsing: parsing em que não ocorre backtracking.
Recursive descent parsing • 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. • Predictive parsing é um caso especial de recursive descent parsing em que o símbolo lookahead determina sem ambiguidades o procedimento a ser chamado para cada não-terminal.
Exemplo – predictive parsing stmt g for ( optexpr ; optexpr ) stmt match (for); match (‘(‘); optexpr (); match (‘; ‘); optexpr (); match (‘)‘); stmt ();
Exemplo – predictive parsing void match (terminal t) { if (lookahead == t) lookahead = next. Terminal else report (“syntax error”); }
Exemplo – predictive parsing (cont. ) void optexpr( ) { if (lookahead == expr) match (expr) ; }
Exemplo – predictive parsing (cont. ) void stmt( ) { switch (lookahead) { case expr: match(expr); match(‘; ’); break; case if: match(if); match(‘(‘); match(expr); match(‘)’); stmt(); break; case for: match(for); match(‘(‘); optexpr(); match(‘; ‘); optexpr(); match(‘)‘); stmt(); break; case other: match(other); break; default: report(“syntax error”); } }
Predictive parsing - Problema • Recursão à esquerda leva a loop em predictive parsers: expr g expr + term | term A g Aa |
Predictive parsing - Solução • Reescrever produções tornando-as recursivas à direita: A g Aa | b reescrever para A g b. R R g a. R | • Exemplo: expressão “baaaa”
Sintaxe abstrata x Sintaxe concreta • Sintaxe abstrata: ignora distinções superficiais de forma, irrelevantes para o processo de tradução. list + digit 9 - - digit 5 + 2 2 5
Sintaxe abstrata x Sintaxe concreta (cont. ) • Na árvore sintática abstrata os nós são construções do programa, enquanto que na parse tree os nós são não-terminais. • Idealmente as duas árvores devem ser próximas.
Reescrevendo a gramática de expressões • expr g expr + term expr g expr - term expr g term g 0 … term g 9 {print (‘+’) } {print (‘-’) } {print (‘ 0’) } {print (‘ 9’) }
Reescrevendo a gramática de expressões A g Aa | Ab | c A g c. R R g a. R | b. R |
Reescrevendo a gramática de expressões (cont. ) A = expr a = + term {print(‘+’)} b = - term {print(‘-’)} c = term
Reescrevendo a gramática de expressões (cont. ) expr g term rest g + term {print (‘+’) } rest | - term {print (‘-’) } rest | term g 0 … term g 9 {print (‘ 0’) } {print (‘ 9’) }
Tradutor em C void expr () { term(); rest(); }
Tradutor em C void term () { if (isdigit(lookahead)) { t = lookahead; match(lookahead); print(t); } else report(“syntax error”); }
Tradutor em C (cont. ) void rest() { if (lookahead == ‘+’) { match(‘+’); term(); print(‘+’); rest(); } else if (lookahead ==‘-’) { match(‘-’); term(); print(‘-’); rest(); } else { }; }
Otimizações void rest () { while (true) { if (lookahead == ‘+’) { match(‘+’); term(); print(‘+’); continue; } else if (lookahead ==‘-’) { match(‘-’); term(); print(‘-’); continue; } break; } }
Usando um analisador léxico • Facilita o tratamento de lexemas ao invés de caracteres: – Tratamento de espaços em branco – Tratamento de número maiores que 9 (constantes) – Reconhecimento de identificadores e palavraschave.
Estendendo para identificadore e números maiores que 9 expr g expr + term {print (‘+’) } | expr – term {print (‘-’) } | term g term * factor {print (‘*’) } | term / factor {print (‘/’) } | factor g ( expr ) | num {print (num. value) } | id {print (id. lexeme) }
Class Token class Token int tag class Num class Word int value string lexeme
Código do analisador léxico • token scan() { char t; while(true) { t = getchar(); if (t == ‘ ’ || t ==‘t’) { } ; else if (t ==‘n’) line++; else if (isdigit(t)) { tokenval = t – ‘ 0’; t = getchar(); while (isdigit(t)) { { tokenval = tokenval * 10 + t – ‘ 0’; t = getchar(); } ungetc(t, stdin); return token<num, tokenval>; } else …
A tabela de símbolos • Interface: – insert(s, t) – lookup(s) • Palavras reservadas: insert(“div”, div) insert(“mod”, mod)
Geração de código intermediário • Árvores: e. g. parse trees ou (abstract) syntax trees • Representações lineares: e. g. three-address code
Construção da Syntax tree • Classes para representar a hierarquia de construções da Linguagem: Statements, If. Statements, While. Statements; Expressions, Binary. Expressions, etc.
class AST class Statement class If. Stmt Expr E; Stmt S; class Assignment. Stmt Identifier I; Expr E; class Expression class While. Stmt Expr E; Stmt;
class AST class Statement class Int. Literal Int IL; class Binary. Expr Operator Op; Expr E 1, E 2; class Expression class Unary. Expr Operator Op; Expr E;
Exemplo stmt g expr ; {stmt. n = new Eval(expr. n); } | if (expr ) stmt {stmt. n = new If(expr. n, stmt. n}
Fluxo de controle • Desvios condicionais ou incondicionais • o destino pode ser especificado – pelo operando da instrução – o operando pode especificar um desvio relativo – o destino pode ser especificado simbolicamente, através de labels
Fluxo de controle - instruções • if. False x goto L • if. True x goto L • goto L
Fluxo de controle – exemplo 1 class If. Stmt extends Stmt { Expr E; Stmt S; public If (Expr x, Stmt y) { E = x; S = y; after = newlabel(); } public void gen() { Expr n = E. rvalue(); emit(“if. False ”+n. to. String()+“goto ”+after); S. gen() emit(after +”: “); } }
if. False x goto after Código para calcular expr em x if. False x goto after Código para stmt 1 after
Resumo If ( peek == ‘n’ ) line = line + 1; Lexical Analyser <if> <(> <id, ”peek”> <eq> <const, ’n’> <)> <id, ”line”> <assign> <id, ”line”> <+> <num, 1> <; > Syntax Directed Translator 1: t 1 = (int) ‘n’ 2: if. False peek == t 1 goto 4 3: line = line + 1 4: if eq peek assign (int) ‘n’ line + 1