Você está na página 1de 82

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
Tabela de
Parser
Símbolos
árvore sintática

Ger. de código intermediário

Three-address code
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, eBNF
(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
G = (V, T, P, S)
• V é um conj. finito de variáveis ou símbolos
não-terminais
• T é um conj. finito de tokens ou símbolos
terminais
• P é um conj. de produções da forma: A à α,
onde A ∈ V e α ∈ (V ∪ T)*
• S é o símbolo inicial, talque S ∈ V
Exemplo 1

list g list + digit


list g list - digit
list g digit

digit g 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
Exemplo 1a

list g list + digit | list - digit | 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
forma geram a linguagem definida pela
gramática
• Exemplos: 9, 9 - 5, 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 X1, X2,...,Xn são
labels de filhos deste nó, tem que haver uma
produção A g X1 X2 ... Xn
Exemplo

• 9–5+2 list

list digit

list digit

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ão-
ambí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 string

string
+ string string - string

string - string string + string


2 9
9 5 5 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 em
Pascal são associativos à direita:
a=b=c
2**3**4
Associatividade à direita

right g letter = right | letter


letter g a | b | … | z

Qual a gramática para


associatividade à esquerda?
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
term g term * factor | term / factor | factor
factor g digit | ( expr )
digit g 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

• Qual a gramática para inverter a


precedência, i.e., + ter maior
precedência do que * ?
Exemplo
• stmt g id = expression ;
| if ( expression ) stmt
| 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 expr1 + term

traduza expr1;
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;
– Endereço da primeira instrução gerada para
uma construção.
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 E1 op E2, então
sua notação pós fixada é E1’ E2’ op, onde E1’ e
E2’ são as notações pós-fixadas de E1 e E2
respectivamente;
• Se E é uma expressão com parênteses da forma
( E1 ), então sua notação pós-fixada é a mesma
notação pós-fixada de E1.
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
nos nós de uma parse tree
expr.t = 95-2+

expr.t = 95- term.t = 2

expr.t = 9 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 Regra semântica
expr g expr1 + term expr.t = expr1.t || term.t || ‘+’
expr g expr1 – term expr.t = expr1.t || term.t || ‘-’
expr g term expr.t = term.t
term g 0 term.t = ‘0’
term g 1 term.t = ‘1’

term g 9 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
• É uma 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 (‘+’) } rest1

rest

+ term {print(‘+’)} rest1


Exemplo 2

expr

expr term
+

expr - term
2
term
5

9
Ações traduzindo 9-5+2 em 95-2+

expr

expr term
+ {print(‘+’)}

expr {print(‘-’)}
- term
2 {print(‘2’)}
term
5 {print(‘5’)}

9 {print(‘9’)}
Exemplo 2: Ações de tradução
para notação pós-fixada
• expr g expr + term { print (‘+’) }
expr g expr - term { print (‘-’) }
expr g term
term g 0 { print (‘0’) }
term g 1 { print (‘1’) }

term g 9 { print (‘9’) }
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 com
complexidade O(n3) 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 ; optexpr ) stmt
| other
optexpr g expr

Exemplo

stmt

for ( optexpr ; optexpr ; optexpr ) stmt

ε expr expr other

for ( ; expr ; expr ) other


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 ; 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 ; optexpr ) stmt

match (for); match (‘(‘);


optexpr (); match (‘;‘);
optexpr (); match (‘;‘);
optexpr (); match (‘)‘); stmt ();
Exemplo – predictive parsing

void match (terminal t) {


if (lookahead == t) lookahead = nextTerminal
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(‘;‘); 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 Aα | ε
Predictive parsing - Solução
• Reescrever produções tornando-as
recursivas à direita:
A g Aα | β
reescrever para
A g βR
R g αR | ε
• Exemplo: expressão “βαααα”
Sintaxe abstrata x Sintaxe
concreta
• Sintaxe abstrata: ignora distinções
superficiais de forma, irrelevantes para o
processo de tradução.
list +

list -
digit 2

list digit 9 5
digit

9 - 5 + 2
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 {print (‘+’) }
expr g expr - term {print (‘-’) }
expr g term
term g 0 {print (‘0’) }

term g 9 {print (‘9’) }
Reescrevendo a gramática de
expressões

A g Aa | Ab | c | d

A g cR|dR
R g aR | bR | ε
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
rest g + term {print (‘+’) } rest
| - term {print (‘-’) } rest

term g 0 {print (‘0’) }

term g 9 {print (‘9’) }

Exemplo: 9-5+2
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 palavras-
chave.
Estendendo para identificadores e
números maiores que 9
expr g expr + term {print (‘+’) }
| expr – term {print (‘-’) }
| term
term g term * factor {print (‘*’) }
| term / factor {print (‘/’) }
| factor
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, IfStatements, WhileStatements;


Expressions, BinaryExpressions, etc.
class AST

class Statement class Expression

class IfStmt class AssignmentStmt class WhileStmt


Identifier I;
Expr E; Expr E;
Expr E;
Stmt S; Stmt;
class AST

class Statement class Expression

class IntLiteral class BinaryExpr class UnaryExpr


Operator Op; Operator Op;
Int IL; Expr E1, E2; 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
• ifFalse x goto L
• ifTrue x goto L
• goto L
Fluxo de controle – exemplo 1
class IfStmt 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(“ifFalse ”+n.toString()+“goto ”+after);
S.gen()
emit(after +”:“);
}
}
ifFalse x goto after

Código para calcular


expr em x
ifFalse x goto after

Código para stmt1

after
L-values e R-values
• i = 5;
i = i + 1;

• L-values são locais (endereços), e R-values


são valores.
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

if 1: t1 = (int) ‘\n’
2: ifFalse peek == t1 goto 4
eq assign 3: line = line + 1
4:
peek (int) line +

‘\n’ line 1

Você também pode gostar