Análise Semântica
"Porque se perdoarem as ofensas uns dos outros, o Pai celestial também lhes perdoará. Mas se não perdoarem uns aos outros, o Pai celestial não lhes perdoará as ofensas."
Etapas da Compilação
Análise Léxica Análise Sintática Analise Semântica Geração de Código Intermediário Geração de Código FinalSumário da Aula
Conceitos Básicos
Regras de Escopo
Tabela de Símbolos
Verificação de Tipos
Estático X Dinâmico
Estático
É quando acontece durante a compilação
Dinâmico
É quando acontece durante a execução Ou o interpretador faz
Exemplos
Estático
Alocação/desalocação de variáveis locais (C, Java) Descoberta de tipos em expressões numéricas (C,
Java)
Dinâmico
Alocação na heap com a função malloc (em C)
Verificação de cast de uma classe para outra classe
“ancestral” dela (em Java)
Análise Semântica
Faz verificações estáticas sobre o código fonte
Exemplo: Se as variáveis usadas foram declaradas
Prepara para etapas posteriores, acrescentando
informações
Exemplo: Para cada nome de variável usado,
Atenção: Semântica X Análise
Semântica
Semântica da Linguagem
Parte conceitual da linguagem
Envolve tudo que diz respeito ao significado de um programa
Envolve características estáticas e dinâmicas
Interfere no desenvolvimento de todas as fases posteriores à análise sintática
Análise Semântica
Parte do compilador
Verifica apenas algumas das regras da semântica da linguagem Outro nome usado (mais adequado) é "Análise Contextual"
Análise Semântica
Dois papéis principais de um analisador semântico:
Verificar (ou tratar) escopo (ou o acesso a nomes) Verificar (ou tratar) tipos
Observações:
1. Esta divisão tem propósitos didáticos apenas. As duas etapas são interligadas.
2. Estas etapas não apenas verificam se está certo ou errado. Elas também coletam informações e armazenam na árvore (se ela for criada).
Análise Semântica
Em essência, o papel de verificar escopo
consiste principalmente em:
1. Associar as ocorrências de nomes com as suas
declarações respectivas
Por exemplo: Uma ocorrência do nome “temp” é uma
variável local ou global? De que tipo?
Exemplos
int alfa; int main() { int beta; [...] x = 10*beta + alfa; }Análise Semântica
Porém, como consequência, a verificação de
escopo também pode envolver outras ações:
2. Identificar erros como: nomes não declarados,
declarações duplicadas, etc.
3. Verificar se o acesso obedece restrições impostas
pelos modificadores de acessos
Exemplos
Verificações de Escopo
Nomes (ou identificadores) – são strings
definidas pelo usuário para identificar
Variáveis Funções Classes Etc.
Escopo – partes do código onde um nome (ou
Regras de Escopo
As verificações dependem das regras de
escopo da linguagem
Regras de escopo
Dizem em que pontos do programa um nome é válido Dizem como cada uso de um nome deve ser ligado à
Tipos de Escopo
As regras de escopo das linguagens podem ser
de dois tipos
Escopo estático
Escopo Estático
O escopo de um nome está implicitamente
definido pelo lugar onde o nome foi declarado
Escopo Estático
Em escopo estático, pode haver uma estrutura
hierárquica de escopos
Exemplo: blocos aninhados
Variáveis de um escopo mais interno acessam
variáveis do escopo externo, mas não o
contrário
Nome escondido
Quando é definido um nome igual a outro de um
Escopo Estático
Blocos aninhados (C ou Java)
Qual o valor de b ? int a = 1; int b = 10; { int a = 2; b += a; } b += a;
Escopo Dinâmico
Um nome local dentro de um procedimento só
será associado a alguma declaração quando o
procedimento for chamado
Muito pouco usado
Pode ser simulado com macros em C ou com
Simulando Escopo Dinâmico
em C
A variável inc na macro INC() é tratada parecido com
o que acontece em escopo dinâmico
#define INC(x) (x + inc) int main() { int inc = 1; printf("%d\n", INC(1)); } void func() { float inc = 1.2; printf("%f\n", INC(1)); }
Simulando Escopo Dinâmico
em Python
A avaliação de expressao_str dá diferentes valores porque o “x”
que ela referencia funciona como uma variável de escopo dinâmico
def funcao1(): x = 5
print "funcao1:", eval(expressao_str) x = 0
expressao_str = "x+10"
print "global:", eval(expressao_str) funcao1()
Escopo Estático e OO
Classes introduzem um novo escopo para os seus
membros
Acesso interno normal
Porém, esses membros podem ser acessados por
escopos externos
Sintaxe em Java: <obj>.<membro>
Modificadores podem restringir o acesso externo
Lembrando que verificar se o acesso é válido é uma possível ação da Análise Semântica
Resumo dos Tipos de Escopo
Dos tipos de escopo, o escopo estático exige
mais ações da análise semântica
Justamente por ser estático (em tempo de
compilação)...
Já o escopo dinâmico não requer muitas
verificações da análise semântica
Porém, o código final tem que ser mais complexo (a
Análise Semântica
Como manter as informações sobre as
Tabela de Símbolos
Guarda informações importantes relacionadas a
cada nome (identificador)
Operações básicas
get ou lookup – recebe um nome e retorna as
informações
put – recebe um nome e a informação relacionada à
Tabela de Símbolos
A informação a ser guardada depende da
necessidade
Exemplos
Associar uma variável com o lugar onde foi declarado Associar uma classe com outra tabela de símbolos
(para guardar os membros da classe)
Associar um procedimento com o seu tipo de retorno
Tabela de Símbolos
Implementação
Lista
Ineficiente
Hash table
Eficiente, mas difícil de implementar
Java oferece as classes Hashtable e HashMap Python oferece dicionários
Tabela de Símbolos
Implementação de escopos aninhados
Cada escopo terá uma tabela de símbolos própria
A tabela geral será uma pilha destas tabelas
A cada início de um novo bloco, cria uma nova tabela
para o escopo e empilha
Tabela de Símbolos
Implementação de escopos aninhados (cont.)
A operação put irá adicionar na tabela que está no
topo da pilha
A operação de get deverá procurar, primeiramente,
na tabela do topo; se não encontrar, continua a busca na tabela imediatamente abaixo e assim sucessivamente...
Verificações de Tipo
O analisador semântico cumpre o papel de
verificador de tipos quando realiza ações para
garantir que os dados estão sendo manipulados
de maneira coerente com o tipo deles
Essas ações dependem das regras de tipo da
linguagem
Veremos a seguir várias possíveis das ações que
uma linguagem pode exigir do analisador semântico...
Verificações de Tipo
1) Testa se as operações recebem operandos dos
tipos válidos
Também infere o tipo resultante da operação
Exemplo: O projeto
O operador “+" só pode ser aplicado a dois operandos
numéricos de mesmo tipo (int com int ou float com float)
A expressão 1 + 2 é válida e é do tipo int A expressão 2.0 + 1 é inválida
Verificações de Tipo
2) Cria conversões automáticas, onde for
aplicável
São chamadas coerções
Exemplo: C ou Java
Em uma multiplicação "1 * 3.13" (entre um inteiro e um
float), o operando 1 é convertido para o valor float 1.0 (internamente, na árvore)
Verificações de Tipo
3) Decide qual a verdadeira operação a ser executada em caso de overloading de operadores
Overloading (sobrecarga): múltiplas definições (ou declarações) para um mesmo operador (ou para um procedimento de mesmo nome)
A Análise Semântica pode “anotar” na árvore qual a declaração específica a ser considerada
Exemplo: Java
O token "+" é usado para soma de inteiros, soma de floats, etc e para concatenação de strings
A escolha da operação certa depende dos operandos
Parâmetros Formais e Reais
Parâmetros formais – são especificados na definição
da função
No exemplo: a e b
Parâmetros reais – são passados na chamada à função
No exemplo: 2*x e 10
void func(int a, char b) ... int main() {
func(2*x, 10); }
Verificações de Tipo
4) Verifica parâmetros em chamadas de
procedimentos
Verifica se a quantidade de parâmetros está correta
Se o tipo de cada parâmetro real corresponde ao
tipo esperado pelo respectivo parâmetro formal
Verifica a ordem
Obs.: As informações dos parâmetros podem ser
guardadas na tabela de símbolos, associadas ao nome do procedimento
Verificações de Tipo
5) Verifica se o lado esquerdo de uma atribuição
pode guardar valor
Em algumas linguagens (como C), a atribuição é um
operador como outro qualquer (como +, -, etc.)
Porém, a expressão do lado esquerdo, precisa
representar locais de memória que guardam valores (ex.: variáveis)
Verificações de Tipo
5) (cont.)
Exemplos: linguagem C Correto: Incorreto: int array[10]; array[0] = 3; *(array + 1) = 3; (1020) = 3;Verificações de Tipo
6) Verifica expressões nos comandos
Se a expressão de teste em um if é do tipo booleano
(em Java)
Se a expressão usada em um return é do mesmo
tipo que a função/método (em C e Java)
Se a expressão em um switch é de um tipo primitivo
e se os cases são para literais desse mesmo tipo primitivo (em C)
Verificações de Tipo
7) Tratar novos tipos de dados definidos pelo usuário (especialmente em linguagens OO)
Quando um usuário define novos tipos (no código fonte de uma linguagem X), a Análise Semântica (da linguagem X) deve
adaptar-se para lidar com esse tipo
Exemplo 1: Em Java, você pode criar um classe C. Depois disso, o compilador permite definir variáveis do tipo C.
Exemplo 2: C++
O usuário pode definir um novo tipo Racional (para números racionais) e definir (overload) o operador + para este tipo
O compilador, então, passará a aceitar variáveis do tipo Racional e irá aceitar o + nestas variáveis
Verificações
Estes sete casos são apenas exemplos de verificações
de tipo
Tirados das linguagens mais comuns (C e Java, em especial)
Dependendo da linguagem pode haver outras
verificações distintas
O importante é entender o conceito geral de verificação
de tipo
Outras Verificações
Além das verificações de acesso a nomes e
verificações de tipo, a Análise Semântica pode:
Verificar se uma variável declarada não foi usada Verificar se um comando é inatingível Verificar se falta retornar valor em algum ponto de
um procedimento
Análise Semântica
A Análise Semântica não só identifica erros
No decorrer dela, o compilador também obtém
mais informações sobre o código
Tipos de cada expressão, associação entre os
nomes e suas declarações, etc.
Estas informações são necessárias para as
Análise Semântica
Se esta etapa for realizada sobre a árvore
sintática, as informações podem ser
acrescentadas na árvore
A saída seria uma árvore sintática anotada
A tabela de símbolos é auxiliar nesse processo
de obtenção de informações e anotação da
árvore
Análise Semântica – Resumo
Objetivos
Verifica estaticamente a consistência da árvore
sintática de acordo com as regras de escopo e de tipo da linguagem
Acrescenta à arvore informações sobre a
organização lógica código
Entrada/Saída
Entrada: árvore sintática