Sie sind auf Seite 1von 5

Compiladores

Analise Semantica
versao 0.001
Simao Melo de Sousa
Este documento e uma traducao adaptada do captulo Analyse Semantique da sebenta Cours de Compilation de Christine Paulin-Morhing e Marc Pouzet
(http://www.lri.fr/~paulin).

Introduc
ao

A analise semantica trata a entrada sintactica e transforma-a numa representacao mais


simples e mais adaptada a` geracao de codigo. Esta camada do compilador fica igualmente
encarregue de analisar a utilizacao dos identificadores e de ligar cada uma delas a sua
declaracao. Nesta situacao verificar-se-a que o programa respeita as regras de visibilidade
tambem esperado que esta fase da compilacao verifie de porte dos identificadores. E
que que cada expressao definida tenha um tipo adequado conforme as regras proprias a`
linguagem.
Neste parte da licao iremos estudar a gestao da tabela dos smbolos que serve para a
ligacao dos nomes manipulados aos objectos que estes de facto designam. Estudaremos
igualmente a tipificacao dos programas. Por fim definiremos as nocoes de gramaticas de
atributos que permitam associar valores aos nodos da arvore de derivacao sintactica.

2
2.1

Tabela dos smbolos


Introdu
c
ao

As linguagens de programacao manipulam identificadores que sao essencialmente smbolos que servem para designar objectos conte
udo dum endereco memoria, no caso por
exemplo duma variavel, pedacos de codigo no caso de nomes de procedimentos, tipos,
etc...
A tabela de smbolos arquiva as informacoes sobre os objectos designados por nomes
na linguagem em questao. Esta e actualizada de cada vez que e analisada uma declaracao
dum novo identificador. De forma semelhante, a tabela e consultada de cada vez que e
utilizado um identificador no programa analisado.
A tabela de smbolos permite para cada identificador o arquivo das informacoes associadas ao objecto identificado. Estas podem ser de natureza diversa, como o tipo do

objecto, uma posicao na lista das variaveis declaradas (com a finalidade de calcular o
endereco relativo aquando da geracao de codigo), um valor...
igualmente possvel coexistirem varias tabelas de smbolos, por exemplo quando
E
existem varios espacos de nomes, como e o caso para linguagens orientadas a objecto
(e.g. os packages do java). Classicamente, encontraremos nestas linguagens uma tabela
de smbolos para cada espaco de nome. Por exemplo arquivaremos nos diferentes espacos
o nome das classes e o nome dos metodos associados. Em linguagens de tipo ML, os
tipos, os modulos e os valores podem ser agrupados em tabelas de smbolos diferentes.
Um mesmo identificador pode ser utilizado para representar diferentes objectos. Este
objectos podem estar arquivados ou referenciados em diferentes tabelas de smbolos. Este
situacao obriga conhecer a natureza do objecto para determinar em que tabela procurar
os seus dados. Este conhecimento e em geral adquirido. Assim este aparente conflito pode
ser facilmente resolvido. Um mesmo identificador (variavel, procedimento) pode igualmente estar declarado mais do que uma vez na mesma tabela. Aquando da compilacao
sera necessario conhecer precisamente o objecto referenciado por cada identificador. As
regras de porte (scope em ingles) dos identificadores que permitira resolver os conflitos
subjacentes.

2.2

Porte dos identificadores

Os programas manipulam varios identificadores. Por razoes de eficacia, e com a finalidade


de melhorara a robustez do codigo, as linguagens permitam indicar sintacticamente que a
utilizacao de certas variaveis sera confinada a uma parte bem determinada do programa,
designado de bloco.
Assim, fora deste bloco, nao sera necessario alocar espaco para as variaveis atribudas
ao bloco em questao.
Durante a analise semantica, o compilador assegurar-se-a que todas as variaveis utilizadas foram declaradas de forma adequada e sao bem visveis durante as suas utilizacoes
(i.e. as suas utilizacoes tem lugar no bloco ao qual pertencem).
As regras de porte/alcance dos identificadores sao especficas a cada linguagem. Por
exemplo em C, uma variavel e local ao procedimento em que foi declarada, ou e global a
todo o programa. Em Pascal, qualquer identificador tem de ser declarado de forma centralizada antes da sua utilizacao (da a necessidade da instrucao forward para as funcoes
mutuamente recursivas). O corpo dum procedimento pode utilizar variaveis declaradas
em qualquer procedimento que o engloba (ele proprio includo).
Em ML, qualquer identificador deve ter sido previamente declarado numa expressao
let id = ... in ... ou let id = .... O porte de tal declaracao esta restrito a expressao associada a expressao a direita do in no caso da declaracao local, ou ao resto do
programa/modulo em que esta definido no segundo caso (ficheiro = modulo em OCaml).
Fora do modulo um identificador exportado (i.e. cuja visibilidade fora do modulo
e permitida) pode ser acedido atraves do que se designa por nome qualificado ou seja
Nome_do_m
odulo.identificador. Se existirem directivas de abertura de modulos (por
exemplo open em OCaml, import em Java) a qualificacao pode ser omitida.
Se um identificador e declarado mais do que uma vez o objecto acedido pelo nome
no entanto possvel aceder a um objecto via o seu
qualificado e o ultimo declarado. E
nome completamente qualificado. No caso de um nome completamente qualificado poder
2

referir dois objectos diferentes, entao este fica por designar o u


ltimo declarado.
Uma declaracao de funcao em ML nao e por defeito recursiva. Para tal e preciso
juntar a palavra chave rec ao let.
Em Java, o corpo dum metodo pode utilizar outros metodos da mesma classe mesmo
se a definicao destes metodos ocorre posteriormente. Isto obriga ao processamento em
bloco das definicoes de classes. Mais, podemos definir numa classe varios metodos com
o mesmo nome. O tipo dos parametros permite determinar de forma estatica (i.e. em
tempo de compilacao) o metodo por aplicar. De uma forma similar, varias classes podem
redefinir os mesmos metodos. Se as classes sao disjuntas, esta ambiguidade de nome
pode ser resolvida por uma verificacao dos tipos (i.e. type checking). Se um mesmo
identificador esta definido numa classe e redefinido numa das suas sub-classes entao devese impor coerencia entre os tipos dos objectos assim definidos, como no caso dos metodos
por exemplos. A escolha do metodo por executar e geralmente feita de forma dinamica
(na altura da execucao). Em algumas situacoes e possvel indicar sintacticamente que
metodo se pretende chamar, como no caso do uso das palavras chaves super ou this.
No primeiro caso e assim indicado explicitamente que se pretende invocar o metodo da
classe mae mesmo se essa foi redefinida na classe activa.

2.3

Representa
c
ao da tabela de smbolos

Deve ser possvel apos a analise dum programa encontrar toda a informacao associada
a um identificador. Isto pode ser feito enfeitando (analogia feita ao enfeito da arvore
de natal que fica apos este trabalho com todo o significado a semantica de natal) a
possvel associar a cada utilizacao dum identificador
arvore de sintaxe abstracta (ASA). E
um apontador para parte da ASA que corresponde a declaracao do identificador. Esta
associacao pode (costuma) igualmente incluir outras informacoes u
teis como, por exemplo,
o tipo do objecto referenciado. De facto o cuidado por ter aqui e o compromisso entre
utilidade da informacao arquivada, o seu tamanho e a frequencia da sua utilizacao. Sem
cuidado, a gestao duma tabela de smbolo pode se tornar pesada.
Uma outra solucao consiste em arquivar as informacoes sobre um objecto numa tabela
e associar um endereco para esta tabela a cada utilizacao do identificador. Este endereco
pode ser um apontador, um inteiro ou um nome u
nico.
Ao lado desta estrutura persistente, e necessario gerir uma tabela para a verificacao do
porte de cada identificador. Esta tabela deve poder informar em qualquer instante desta
fase de analise dos dados dos identificadores visveis. deve ser igualmente possvel juntar
novos identificadores, determinar rapidamente e facilmente se um dado identificador e
visvel. De forma semelhante deve ser possvel retirar identificadores desta estrutura
quando o objecto referenciado deixa de existir ou de ser acessvel (um identificador de
uma variavel local quando se atinge o fim do bloco em que esta definido).
Consideremos a analise do seguinte programa:
1

let x = ( let y = 2 in y * y + 2* y +1) in x + y

Quando comecamos a analisar esta expressao, construmos uma tabela dos smbolos
visveis T . Primeiro analisa-se a construcao let x = e in e que declara o novo identificador x.
Para encontrar informacao sobre este identificador, analisa-se o corpo e da definicao (aqui let y = 2 in y*y + 2*y +1). Nesta situacao devemos entao analisar o corpo da
3

definicao de y ou seja 2.
Este processo leva assim a actualizacao da tabela T em T 0 na qual se acrescentou a
entrada y com a informacao de que este e, por exemplo, inteiro. Esta declaracao torna
qualquer informacao previa sobre y em T invisvel. Utilizando a tabela T 0 analisamos a
expressao y*y + 2*y +1 que e determinada como sendo do tipo inteiro. Ao sair do bloco
y*y + 2*y +1, a entrada y na tabela de smbolos deve desaparecer e assim voltamos de
T 0 para T . Neste ponto preciso sabe-se que x e inteiro e podemos entao juntar esta
informacao a tabela de smbolos T e proceder a analise de x+y. Com se ve na expressao
por analisar e pelas regras de porte em OCaml a ocorrencia de y nao faz referencia
a` definicao interna a` definicao de x mas sim a uma declaracao previa. No fim desta
analise, as diferentes utilizacoes dos identificadores na ASA devem estar associadas a` boa
declaracao.
Estas tabelas devem ser optimizadas porque o n
umero de identificadores pode ser
importante e o acesso a informacao deve ser rapido. Podem ser implementadas de forma
imperativa ou funcional No caso duma representacao funcional a analise de visilibilidade
poderia se escrever da seguinte forma:
function visivel : tabela * asa -> asa_tipada
visivel (T,let(x,e,e)) =
seja f = visivel(T,e),
tip = tipo_de(T,f),
T = add ((x,tip),T),
f = visivel(T,e),
let(x:tip,f,f)
Este processo funciona se a representacao da tabela e funcional, ou seja se a construcao de T 0 (juncao de (x, tip)) nao altere de facto a tabela T . Nao e o caso, por exemplo
se T for uma tabela de Hash (como e o caso em OCaml). A copia e arquivo da copia da
tabela de hash com o objectivo de a repor caso necessario seria aqui altamente ineficiente. Se optarmos por uma tabela de hash, e assim necessario retirar explicitamente os
identificadores dos quais pretendemos apagar o registo. Assim sendo esta tabela por ser
global:
function visible_imp : asa -> asa_tipada
visival_imp (let(x,e,e )) =
seja f = visivel_imp(e),
tip = tipo_de(f),
add x;
seja f = visivel_imp(e),
del (x); let(x:tip,f,f)
As tabelas de hash com ligacoes para listas de entradas (onde os identificadores pertencendo a mesma lista correspondem aos valores com a mesma chave de dispersao (hash
key)) sao em regra geral boas estruturas para implementar tabelas de smbolos visveis.
De facto quando identificadores com o mesmo nome estao declarados o u
ltimo esconde
naturalmente os anteriores. Isto porque a lista de entradas e gerida como uma pilha. Para
retirar um identificador basta eliminar a primeira ocorrencia encontrada na tabela (que
corresponde ao u
ltimo inserido).
4

possvel introduzir em cada bloco do programa analisado um n


E
umero arbitrario de
identificadores. Na sada de cada bloco devemos poder identificar e remover todos os

identificadores da tabela que nele foram introduzidos (estes deixam de ser visveis). E
assim necessario conservar uma pilha dos blocos abertos com a informacao, para cada
bloco aberto, dos identificadores que este introduz. Este processo pode ser feito de forma
funcional arquivando uma pilha de listas de identificadores ou de forma mais imperativa
interligando o conjunto de identificadores introduzidos num mesmo bloco na tabela dos
smbolos visveis e guardando num estrutura (uma pilha por exemplo) o endereco na
tabela do u
ltimo identificador introduzido no bloco activo.

2.4

Representa
c
ao dos smbolos

Quando se analisa os smbolos do programa, e necessario fazer numerosas comparacoes


(igualdades, desigualdades se se utiliza arvores binarias de pesquisa). Para tal e pertinente
utilizar uma representacao eficiente dos identificadores com por exemplo uma codificacao
com base em inteiros. Neste caso e interessante dispor duma tabela de dispersao (hash
table) onde se arquiva a associacao entre inteiro - identificador. Em termos de requisitos,
(a) esta associacao deve ser u
nica, isto e, um inteiro representa um u
nico identificador;
(b) esta tabela deve poder devolver o identificador representado por um inteiro e viceversa; (c) a gestao desta tabela nao deve sobrecarregar computacionalmente o processo
de analise.

Das könnte Ihnen auch gefallen