Tradução dirigida por sintaxe
André Luis Meneses Silva
andreLuis.ms@gmail.com
Departamento de Estatística e Informática
Universidade Federal de Sergipe
Definições dirigidas por sintaxe
• Gramática + regras (ações) semânticas
– Cada símbolo possui um atributo
– Regras definem os valores dos atributos
– Cada produção tem sua(s) regra(s)
• Regras são avaliadas quando a produção é aplicada
• Gramática de atributos
– Defn. dirigida por sintaxe onde as regras não
têm efeitos colaterais (paradigma funcional)
terminal Integer NUM;
non terminal Integer expr; ...
expr ::= expr:a PLUS expr:b
{: RESULT = new Integer(a.intValue() + b.intValue()); :}
| expr:a TIMES expr:b
{: RESULT = new Integer(a.intValue() * b.intValue()); :}
| LPAREN expr:a RPAREN
{: RESULT = a :}
| NUM : i
{: RESULT = i :} ;
Atributos sintetizados e herdados
• Considere a árvore de derivação:
– O valor de um atributo sintetizado é definido
em função dos valores dos atributos dos filhos
• Exemplo anterior
– Atributos herdados dependem dos irmãos e/ou
o pai
• Cup só trabalha com atributos sintetizados
D T L { L.type = T.type }
L id , L1 { L1.type = L.type; id.type=L.type }
N N1 D { N.v = N1.v*10 + D.v } N D { N.v = D.v } D 0 { D.v = 0 } D 1 { D.v = 1 } ... D 9 { D.v = 9 }
Exemplo: números simples
N N D N D N D N N D
N N1 D { N.v = N1.v*10 + D.v } N D { N.v = D.v } D 0 { D.v = 0 } D 1 { D.v = 1 } ... D 9 { D.v = 9 } S N { N.h = 0; } N D N1 { N.s = N1.s ; N1.h = N.h*10 + D.s } N D { N.s = N.h*10 + D.s; } D 0 { D.s = 0 }
Exemplo: números simples
N N D N D N D N N D
Qual é mais adequada para definir o valor semântico de N
somente com atributos sintetizados?
Árvore de derivação
D
N
N
D
N
0
D
1
9
• 901 árvore
de derivação
Árvore de derivação
D
N
N
D
N
0
D
1
• 901 árvore
de derivação
N N1 D { N.v = N1.v*10+D.v } N D { N.v = D.v } D 0 { D.v = 0 } D 1 { D.v = 1 } ... D 9 { D.v = 9 }Árvore de derivação
D
N
N
D
N
0
D
1
9
D.v = 9• 901 árvore
de derivação
N N1 D { N.v = N1.v*10+D.v } N D { N.v = D.v } D 0 { D.v = 0 } D 1 { D.v = 1 } ... D 9 { D.v = 9 }Árvore de derivação
D
N
N
D
N
0
D
1
D.v = 9 N.v = 9• 901 árvore
de derivação
N N1 D { N.v = N1.v*10+D.v } N D { N.v = D.v } D 0 { D.v = 0 } D 1 { D.v = 1 } ... D 9 { D.v = 9 }Árvore de derivação
D
N
N
D
N
0
D
1
9
D.v = 9 N.v = 9 N.v = 90 D.v = 0• 901 árvore
de derivação
N N1 D { N.v = N1.v*10+D.v } N D { N.v = D.v } D 0 { D.v = 0 } D 1 { D.v = 1 } ... D 9 { D.v = 9 }Árvore de derivação
• 901 árvore
de derivação
N N1 D { N.v = N1.v*10+D.v } N D { N.v = D.v } D 0 { D.v = 0 } D 1 { D.v = 1 } ... D 9 { D.v = 9 }D
N
N
D
N
0
D
1
D.v = 9 N.v = 9 N.v = 90 D.v = 0 N.v = 901 D.v = 1Implementando Atributos Sintetizados – LR
• Empilha-se o valor do atributo junto com o símbolo/estado • Quando há redução, a respectiva regra semântica é avaliada ( , 2 + 4 * 8 $) ├ (<NUM,2>, + 4 * 8 $) ├ (<expr,2>, + 4 * 8 $) ├ ( <expr,2><PLUS>, 4 * 8 $) ├ (<expr,2><PLUS><NUM,4>, * 8 $) ├ (<expr,2><PLUS><expr,4>, * 8 $) ├ (<expr,2><PLUS><expr,4><TIMES>, 8 $) ├ (<expr,2><PLUS><expr,4><TIMES><NUM,8>, $) ├ (<expr,2><PLUS><expr,4><TIMES><expr,8>, $) ├ (<expr,2><PLUS><expr,32>, $)
4+9 ; <ENTER>
13
5*(4+2); <ENTER>
30
Ações com efeitos colaterais
• Gramáticas de atributos são declarativas
• As vezes é interessante escrever ações com efeitos colaterais
– Maior cuidado pois a execução das ações depende da ordem em que as produções são reduzidas
terminal SEMI, PLUS, TIMES, LPAREN, RPAREN;
terminal Integer NUMBER; // com atributo
non terminal Integer expr; // com atributo
non terminal expr_, calc;
precedence left PLUS;
precedence left TIMES;
calc ::= expr_ SEMI calc
| expr_ SEMI {: System.out.println("BYE"); :} ;
expr_ ::= expr:e {: System.out.println(e.toString()); :} ;
expr ::= expr:a PLUS expr:b
{: RESULT = new Integer(a.intValue() + b.intValue()); :}
| expr:a TIMES expr:b
{: RESULT = new Integer(a.intValue() * b.intValue()); :}
| LPAREN expr:a RPAREN
Ações imperativas exigem cuidado
calc ::= expr:e SEMI calc
{: System.out.println(e.toString()); :}
| expr:e SEMI
{: System.out.println(e.toString()); System.out.println("BYE"); :} ; expr ::= ...
Ações imperativas exigem cuidado
calc ::= expr:e SEMI calc
{: System.out.println(e.toString()); :}
| expr:e SEMI
{: System.out.println(e.toString()); System.out.println("BYE"); :} ; expr ::= ...
Como se comportam os parsers das seguinte gramáticas?
calc ::= calc expr:e SEMI
{: System.out.println(e.toString()); :}
| expr:e SEMI
s ::= calc {: System.out.println("BYE"); :} ;
calc ::= calc expr:e SEMI
{: System.out.println(e.toString()); :}
| expr:e SEMI
{: System.out.println(e.toString()); :} ;
Ações embutidas
calc ::= calc expr:e {: System.out.println(e.toString()); :}
SEMI
| expr:e SEMI {: System.out.println(e.toString()); :} ; expr ::= ...
Quando são executadas as ações embutidas?
– São tratadas como não terminais (um novo para c/ação) – Uma produção vazia é adicionada para c/ação
– Conflitos podem ser introduzidos
calc ::= calc expr:e $N_1 SEMI | ...
Implementação totalmente imperativa
import java.util.*;
action code {:
List pilha = new Vector();
void push(int a) { pilha.add(pilha.size(), new Integer(a)); } int pop() { Integer i = (Integer) pilha.remove(pilha.size()-1);
return i.intValue(); } :}
terminal PLUS, TIMES, SEMI, LPAREN, RPAREN;
terminal Integer NUM;
non terminal expr, expr_, calc;
precedence left PLUS;
calc ::= expr_ SEMI calc
| expr_ SEMI {: System.out.println("BYE"); :}
;
expr_ ::= expr {: System.out.println(pop()); :} ;
expr ::= expr PLUS expr
{: int a = pop(); int b = pop(); push(a+b); :}
| expr TIMES expr
{: int a = pop(); int b = pop(); push(a*b); :}
Exemplo estendido:
Todo um interpretador em Ações semânticas
Prog → Stm
Stm → Stm ; Stm
| id := Exp
| print ( ExpList )
Exp → Exp BinOp Exp
| Stm , Exp
| ( Exp )
| id
| num
ExpList → Exp , ExpList
| Exp
BinOp → + | *
Sintaxe de
Straight-line
terminal Integer NUM;
terminal String ID;
terminal PLUS, TIMES, SEMI, COMMA, LPAREN, RPAREN, ASSIGN, PRINT ;
non terminal exp, stm, exps, prog;
precedence right SEMI;
precedence left PLUS;
precedence left TIMES;
action code {:
List pilha = new Vector(); void push(int a) {
pilha.add(pilha.size(), new Integer(a)); } int pop() {
Integer i = (Integer) pilha.remove(pilha.size()-1); return i.intValue(); }
Map mem = new HashMap(); void store(int v, String id) {
mem.put(id, new Integer(v)); } int get(String id) {
Integer v = (Integer) mem.get(id); return v.intValue(); }
prog ::= stm ;
stm ::= stm SEMI stm
| ID:id ASSIGN exp {: store(id,pop()); :}
| PRINT LPAREN exps RPAREN
{: System.out.println(); :}
;
exps ::= exp {: System.out.println(pop()); :}
| exps COMMA exp
{: System.out.println(pop()); :}
;
exp ::= exp PLUS exp
{: int a = pop(); int b = pop(); push(a+b); :}
| exp TIMES exp
{: int a = pop(); int b = pop(); push(a*b); :}
| LPAREN exp RPAREN | stm COMMA exp
Misturando estilos
// declarações de símbolos e precedências como antes, salvo:
non terminal Integer exp ;
action code {: ... // só definições de get e store:} prog ::= stm ;
stm ::= stm SEMI stm
| ID:id ASSIGN exp:e {: store(id,e.intValue()); :}
| PRINT LPAREN exps RPAREN
{: System.out.println(); :}
;
exps ::= exp:e {: System.out.println(e.intValue()); :}
| exps COMMA exp:e
{: System.out.println(e.intValue()); :}
;
exp ::= exp:e1 PLUS exp:e2
{: RESULT = new Integer(e1.intValue()+ e2.intValue(); :}
| exp TIMES exp
{: RESULT = new Integer(e1.intValue()* e2.intValue(); :}
| LPAREN exp:e RPAREN {: RESULT = e; :}
| stm COMMA exp:e {: RESULT = e; :}
| NUM : i {: RESULT = i; :}
Ações Semânticas em LL
• Ações semânticas são misturadas com o
fluxo de controle das ações do parser
• Ordem de avaliação é fácil predizer
• Atributos sintetizados
– Valor de retorno da função
• Atributos herdados
Atributos sintetizados em LL
E → num + E
1{ E.v = num.v + E
1.v }
| num
{ E.v = num.v }
int E() {
if (tok1.code == NUM && tok2.code == PLUS ) {
int i = tok1.value();
reconhece(NUM ); reconhece (PLUS); int j = E( ); return(i+j); }
else if (tok1.code == NUM && tok2.code == EOF) {
int i = tok1.value; reconhece(NUM); return i; }
else erro();
LL(2)
Atributos sintetizados são passados de filhos para pai através do retorno da função
Atributos herdados em LL
E → num E’
E’ → + num E
1’
| - num E’
|
Se aumentarmos a operação “-”, o parser começa a complicar Podemos fatorar à esq. para termos LL(1)
No entanto a gramáticade atributos precisa de atributos herdados
O computo do + e – associa a esq. ou a dir.?
{ E’.h = num.v ;
E.v = E’.v}
{ E
1’.h = E’.h + num.v;
E’.v = E
1’.v }
{ ... // similar }
{ E’.v = E’.h }
Atributos herdados em LL
Atributos herdados são passados
• de pai para filho através de argumentos da função
• de irmão (à esquerda) para irmão na avaliação da produção
int E() {switch(tok.code) {
case NUM: int v = tok.value(); reconhece(NUM); return E’(v); default: erro(); } }
int E’(int h) {switch(tok.code) { // h atributo herdado
case PLUS: reconhece(PLUS); int v = reconhece(NUM);
return E’(h+v); case MINUS: ... // similar
Interprete de Expressões LL
E → T E’
E’ → + T E’ |
T → F T’
T’ → * F T’ |
F → num | ( E )
Gramática de expressões por camadas e fatoradaPrimeiro(E) = { id, num, ( } Primeiro(E’) = { ,+, $ } ...
Seguinte(E) = { ), $ } Seguinte(E’) = { ), $ } ....
Exercício tipo prova:
• Calcular a tabela de parser LL(1)
• Dar uma gramática de atributos para um interpretador desta gramática
• Implementar na mão
Ações com efeitos colaterâis
public class InterpretadorSL {
Map mem = ...;
void SimpleStm() {switch(tok.code) {
case PRINT: reconhece(PRINT); reconhece(LPAREN); ExpList(); reconhece (RPAREN)
case ID: String id = (String) tok.value();
reconhece (ID); reconhece(ASSIGN);
int v = Exp( ); mem.store(id,v); break;
default: erro();
void compoundStm { ... }
void ExpList( ) {switch(tok.code) {
case ....: int v = Exp( );
System.out.print(v+" "); reconhece(COMMA); ExpList();
break;
case RPAREN: System.out.println(); break;
default: erro(); }
Ações semânticas no JavaCC
void SimpleStm( ) : { Token t;
int v; }
{ “print” “(” ExpList() “)”
| t=<ID> <ASSIGN> v = Exp( ); { mem.store(t.image(),v); }
} ... void ExpList( ) : { int v; } { v = Exp( ) { System.out.print(v+" "); }
Exercício opcional
• Completar o interpretador de straight-line
na mão ou usando JavaCC
Exercício opcional
• Utilizando uma gramática de atributos escrita em JavaCC, dar uma implementação em estilo funcional do
interpretador de straight-line
– Definir o tipo memória com funções
• int get(String), Memoria store(int,String) – Atributos herdados são necessários?
– Atributo sintetizado de Stm
• Memória e Lista de valores (impressos), ou • Memória (aqui a impressão será imperativa) – Atributo de Exp
• ?