Tabela de Simbolos

Propaganda
Departamento de Estatística e Informática
Universidade Federal de Sergipe
Compiladores
Ambientes
Giovanny Lucero
[email protected]
1
Ambientes
• Também conhecidos como tabela de símbolos
• Mapeamento de Símbolos a atributos (tipicamente
tipos e endereço de memória)
– Não confundir símbolos com tokens
• Implementam os bindings (amarrações / vínculos)
• Dois tipos de ocorrência dos identificadores
– definição (declaração) – inserção na tabela
– Uso -- lookup
• Escopo de um identificador
2
Ambientes
1. function f(a: int, b:int ,
2.
c:int) =
3. ( print_int(a+c);
4.
let var j:= a+b;
5.
var a:= “hello”;
6.
in print(a);
7.
print_int(j);
8.
end;
9.
print_int(b);
10. )
Suponha ambiente inicial 0
Linha 2:
1 = 0 + {aint, bint, cint}
Linha 4: 2 = 1 + {jint}
Linha 5: 3 = 2 + {aString}
sobrepondo o binding dado para
a anteriormente (em 1)
Linha 8: Descartamos 3 e
voltamos para 1
Linha 10: Voltamos para 0
3
Como implementar “descartar-voltar”
• Estilo imperativo
– Modificamos 1 até virar 2
– Problema: destruímos 1, como “volto”?
– Solução: pilha de ambientes
• Estilo funcional
– Implementamos a função +
– Não há problema
• um ambiente novo não é construído “destrutivamente”
a partir de um anterior
• Implementações eficientes para ambos estilos
– Crítico: um programa pode ter milhares de símbolos
4
Ambientes múltiplos
structure M = struct
package M;
structure E = struct
class E {
val a = 5
static int a = 5;
end
}
structrure N = struct
class N {
val b = 10;
static int b = 10;
val a = E.a + b
static int a = E.a + b;
end
}
structure D = struct
class D {
val d = E.a + N.a
static int d = E.a + N.a
end
}
end
Vários ambientes ativos ao mesmo tempo
5
Implementação imperativa eficiente
Tabela de Hash com external chaining
– inserimos novos bindings no começo das listas,
– implementação eficiente de “deleção”
a int
b int
y real
a int
b int
y real
b real
Adicionamos o binding {breal}
6
Tabela de Hash com external chaining
class Bucket {String key; Object binding; Bucket next;
Bucket(String k, Object b, Bucket n) {
key = k; binding = b; next = n; } }
class HashT {
final int SIZE = 256;
Bucket table = new Bucket[SIZE]
int hash(String s) {
int h=0;
for (int i = 0; i < s.length(), i++)
h= h*65599 + s.charAt(i);
return h
}
7
void insert(String s, Bind b) {
int index = hash(s) % SIZE;
table[index] = new Bucket(s,b,table[index]);
}
void lookup(String s) {
int index = hash(s) % SIZE;
for (Binding b = table[index]; b!null; b = b.next)
if (s.equals(b.key)) return b.binding;
return null;
}
void pop(String s) {
int index = hash(s) % SIZE;
table[index]=table[index].next;
}
8
Implementação funcional eficiente
Tabela de Hash não serve (trabalha destrutivamente)
Árvores balanceadas
– Inserção “cria” uma árvore nova
– Não há o problema descartar-voltar
data Tree a = ConsT (String,a) Tree Tree | EmptyTree
insert :: Tree a  (String,a)  Tree a
insert EmptyTree (s,v) = ConsT (s,v) EmptyTree EmptyTree
insert (ConsT (s,v) left right) (s1,v1)
| s1 < s = ConsT (s,v) (insert s1 left) right
| s1 > s = ConsT (s,v) left (insert s1 right)
| s1 = s = ConsT (s,v1) left right
• Qual é a complexidade de insert?
• insert “cria uma nova árvore”, no entanto:
Quantos novos nós são criados realmente?
A interface de uma tabela imperativa
package lps.simbolo;
beginScope e endScope
precisam de uma pilha
public class Symbol {
public String toString();
public static Symbol symbol(String s); }
public class Table {
public Table();
public void put(Symbol key, Object value)
throws AlreadyBound;
public Object get(Symbol key);
public void beginScope();
public void endScope();
public java.util.Enumeration keys(); }
• Ver exercício 5.1 para melhorar a implementação
10
Interface para tabela funcional
public class Table {
public Table();
public Table put(Symbol key, Object value);
public Object get(Symbol key);
public java.util.Enumeration keys();
}
• Ver exercício 1.1 para fazer uma implementação eficiente
11
Exercício (para o projeto)
• Implementar eficientemente a tabela de
símbolos
– Escolher um estilo
12
Implementação eficiente de Symbol
• Implementação de tabela de símbolos é crítica
• Operações eficientes necessárias
– Comparar dois símbolos por igualdade
– Extrair o código de hash (quando se usa hash)
– Comparar por “<=” (quando se usam árvores)
• Numa implementação ingênua, cada caractere de
um string é analisado a cada
– Operação hash
– Cada comparação com outro string (put e get)
• Como evitar comparações caractere por caractere?
– Que padrão uso?
13
Padrão Flyweight
• Por uniformidade, podemos querer tratar valores
“leves” como objetos. Exemplo
– Os caracteres de um texo (num editor)
– Implementação ingênua traz problemas de espaço
– Solução: criamos um pool de objetos leves
• No nosso caso podemos tratar símbolos como
objetos leves, assim teremos
– igualdade por referência
14
Symbol: Padrão Flyweight
package lps.simbolo;
public class Symbol {
private String name;
private Symbol (String n) {name=n;}
private static java.util.Map dict = new java.util.Hashtable();
public String toString() {return n; }
public static Symbol symbol(String n) {
String u = n.intern(); // Um string único – precisa?
Symbol s = (Symbol) dict.get(u);
if (s==null) { s = new Symbol(u); dict.put(u,s); }
return s; } }
Agora só é preciso comparar referências
15
Download