1. O que é Tabela de Símbolos?

A tabela de símbolos é uma estrutura de dados (hash table, árvore ou aninhamento dessas) usada pelo compilador para armazenar informações sobre cada identificador do programa:

Ela é criada no início da compilação e é consultada e atualizada em várias fases para:

  1. Verificar declarações e usos de identificadores
  2. Verificar tipos (checagem semântica)
  3. Gerar código intermediário e final
  4. Otimizações dependentes de dados de símbolos

2. Interação com as Fases do Compilador

Fase Ação sobre a Tabela de Símbolos
Análise Léxica Insere novos tokens de identificador (variáveis, literais)
Análise Sintática Recupera tokens, mas não altera a tabela; auxilia na construção da Árvore de Sintaxe (AST)
Análise Semântica Atualiza atributos (tipo de dado, escopo), checa declarações repetidas ou usos não declarados
Geração de Código Intermediário Consulta escopo e endereço para gerar temporários e instruções de referência a variáveis
Otimização Usa informações de tipos e escopos para eliminar código morto, propagar constantes e outras otimizações
Geração de Código Final Utiliza endereços e atributos para emitir instruções de máquina ou bytecode

3. Simulação em Python: Estrutura de Tabela de Símbolos

A seguir, um exemplo completo que simula a criação e o uso de tabelas de símbolos em diferentes escopos.

class Symbol:
    """Representa um identificador."""
    def __init__(self, name, kind, data_type, scope_level):
        self.name = name            *# léxico*
        self.kind = kind            *# 'variable', 'function', 'constant'*
        self.data_type = data_type  *# 'int', 'float', etc.*
        self.scope_level = scope_level

class SymbolTable:
    """Tabela de símbolos com encadeamento de escopos."""
    def __init__(self, parent=None):
        self.table = {}         *# map name -> Symbol*
        self.parent = parent    *# escopo aninhado*

    def insert(self, name, kind, data_type):
        """Insere novo símbolo no escopo atual."""
        if name in self.table:
            raise Exception(f"Símbolo '{name}' já declarado neste escopo.")
        sym = Symbol(name, kind, data_type, self.scope_level())
        self.table[name] = sym
        return sym

    def lookup(self, name):
        """Procura símbolo no escopo atual ou nos ancestrais."""
        if name in self.table:
            return self.table[name]
        if self.parent:
            return self.parent.lookup(name)
        return None

    def scope_level(self):
        """Retorna profundidade do escopo (0 = global)."""
        level = 0
        cur = self.parent
        while cur:
            level += 1
            cur = cur.parent
        return level

    def enter_scope(self):
        """Cria e retorna um novo escopo aninhado."""
        return SymbolTable(parent=self)

    def exit_scope(self):
        """Retorna ao escopo pai."""
        return self.parent

    def __repr__(self):
        syms = ', '.join(self.table.keys())
        return f"<Scope level={self.scope_level()} symbols=[{syms}]>"

*# Exemplo de uso:*
if __name__ == "__main__":
    *# Escopo global*
    global_scope = SymbolTable()
    global_scope.insert('x', 'variable', 'int')
    global_scope.insert('pi', 'constant', 'float')

    print(global_scope)  
    *# <Scope level=0 symbols=[x, pi]># Entrando em função foo*
    foo_scope = global_scope.enter_scope()
    foo_scope.insert('y', 'variable', 'int')
    foo_scope.insert('x', 'variable', 'float')  *# sombreia 'x' global*

    print(foo_scope.lookup('x').data_type)  *# float*
    print(foo_scope.lookup('pi').kind)      *# constant*

    print(foo_scope)  
    *# <Scope level=1 symbols=[y, x]># Saindo para escopo global*
    back = foo_scope.exit_scope()
    print(back)  
    *# <Scope level=0 symbols=[x, pi]>*

Como funciona o código:

  1. Symbol: objeto que armazena atributos de um identificador.
  2. SymbolTable: mantém um dicionário table e ponteiro parent para escopo externo.