e corrigir o erro.


🧪 Implementando Análise Semântica no Projeto Integrador

Arquitetura da Análise Semântica

A análise semântica opera sobre a AST produzida pelo parser. A arquitetura típica usa o padrão Visitor para percorrer a árvore sistematicamente, realizando verificações em cada nodo.

Vamos construir um analisador semântico completo passo a passo, começando com a estrutura básica:

/// Representa um erro semântico detectado
class ErroSemantico {
  final String mensagem;
  final int linha;
  final int? coluna;
  final String? sugestao;
  
  ErroSemantico(this.mensagem, this.linha, {this.coluna, this.sugestao});
  
  @override
  String toString() {
    var resultado = 'Erro semântico na linha $linha';
    if (coluna != null) resultado += ', coluna $coluna';
    resultado += ':\n  $mensagem';
    if (sugestao != null) resultado += '\n💡 Sugestão: $sugestao';
    return resultado;
  }
}

/// Analisador semântico que percorre AST e realiza verificações
class AnalisadorSemantico {
  TabelaSimbolos tabelaSimbolos = TabelaSimbolos();
  VerificadorTipos verificadorTipos = VerificadorTipos();
  List<ErroSemantico> erros = [];
  
  // Contexto para rastrear se estamos dentro de um loop
  bool _dentroDeLoop = false;
  
  // Contexto para rastrear função atual (para verificar retornos)
  DeclaracaoFuncaoNode? _funcaoAtual;
  
  /// Analisa um programa completo
  void analisar(ProgramaNode programa) {
    try {
      _analisarPrograma(programa);
    } catch (e) {
      print('Erro fatal durante análise semântica: $e');
    }
    
    if (erros.isNotEmpty) {
      print('\n=== Erros Semânticos Encontrados ===');
      for (var erro in erros) {
        print(erro);
        print('');  // Linha em branco entre erros
      }
    } else {
      print('\n✓ Análise semântica concluída sem erros');
    }
  }
  
  void _analisarPrograma(ProgramaNode programa) {
    // Primeira passada: coleta assinaturas de funções
    // (permite referências forward - usar função antes de declarar)
    for (var declaracao in programa.declaracoes) {
      if (declaracao is DeclaracaoFuncaoNode) {
        _coletarAssinaturaFuncao(declaracao);
      }
    }
    
    // Segunda passada: analisa corpos de funções e variáveis globais
    for (var declaracao in programa.declaracoes) {
      _analisarDeclaracao(declaracao);
    }
    
    // Verifica variáveis globais não utilizadas
    _verificarVariaveisNaoUtilizadas();
  }
  
  void _coletarAssinaturaFuncao(DeclaracaoFuncaoNode funcao) {
    // Constrói tipo da função: (tipo_param1, tipo_param2, ...) -> tipo_retorno
    var tiposParametros = funcao.parametros
        .map((p) => p.tipo)
        .toList();
    var tipoFuncao = TipoFuncao(tiposParametros, funcao.tipoRetorno);
    
    // Adiciona função à tabela de símbolos global
    if (!tabelaSimbolos.declarar(funcao.nome, tipoFuncao.mostrar(), 
                                  funcao.linha, categoria: 'funcao')) {
      var simboloExistente = tabelaSimbolos.buscar(funcao.nome)!;
      erros.add(ErroSemantico(
        'Função "${funcao.nome}" já foi declarada na linha ${simboloExistente.linha}',
        funcao.linha,
        sugestao: 'Escolha um nome diferente ou remova a declaração duplicada'
      ));
    }
  }
  
  void _analisarDeclaracao(DeclaracaoNode declaracao) {
    if (declaracao is DeclaracaoVariavelNode) {
      _analisarDeclaracaoVariavel(declaracao);
    } else if (declaracao is DeclaracaoFuncaoNode) {
      _analisarDeclaracaoFuncao(declaracao);
    }
  }
  
  void _analisarDeclaracaoVariavel(DeclaracaoVariavelNode decl) {
    // Analisa expressão de inicialização se presente
    Tipo? tipoInicial;
    if (decl.inicializador != null) {
      tipoInicial = _inferirTipo(decl.inicializador!);
    }
    
    // Verifica compatibilidade de tipos
    if (tipoInicial != null && decl.tipo != null) {
      if (!_tiposCompativeis(decl.tipo!, tipoInicial)) {
        erros.add(ErroSemantico(
          'Tipo do inicializador "${tipoInicial.mostrar()}" incompatível com tipo declarado "${decl.tipo!.mostrar()}"',
          decl.linha,
          sugestao: 'Mude o tipo da variável para "${tipoInicial.mostrar()}" ou converta o valor explicitamente'
        ));
      }
    }
    
    // Adiciona variável à tabela
    var tipo = decl.tipo ?? tipoInicial;
    if (tipo != null) {
      if (!tabelaSimbolos.declarar(decl.nome, tipo.mostrar(), decl.linha,
                                    inicializado: decl.inicializador != null)) {
        var simboloExistente = tabelaSimbolos.buscarNoEscopoAtual(decl.nome)!;
        erros.add(ErroSemantico(
          'Variável "${decl.nome}" já foi declarada neste escopo (linha ${simboloExistente.linha})',
          decl.linha,
          sugestao: 'Use um nome diferente ou remova a declaração duplicada'
        ));
      }
    } else {
      erros.add(ErroSemantico(
        'Não foi possível determinar o tipo da variável "${decl.nome}"',
        decl.linha,
        sugestao: 'Adicione uma anotação de tipo explícita ou forneça um inicializador'
      ));
    }
  }
  
  void _analisarDeclaracaoFuncao(DeclaracaoFuncaoNode funcao) {
    // Guarda função atual para verificações de retorno
    var funcaoAnterior = _funcaoAtual;
    _funcaoAtual = funcao;
    
    // Entra em novo escopo para a função
    tabelaSimbolos.entrarEscopo();
    
    // Adiciona parâmetros ao escopo da função
    for (var param in funcao.parametros) {
      if (!tabelaSimbolos.declarar(param.nome, param.tipo.mostrar(), 
                                    param.linha, categoria: 'parametro',
                                    inicializado: true)) {
        erros.add(ErroSemantico(
          'Parâmetro "${param.nome}" duplicado na função "${funcao.nome}"',
          param.linha,
          sugestao: 'Cada parâmetro deve ter um nome único'
        ));
      }
    }
    
    // Analisa corpo da função
    for (var comando in funcao.corpo) {
      _analisarComando(comando);
    }
    
    // Verifica se função retorna em todos os caminhos
    if (!_todosOsCaminhosRetornam(funcao.corpo) && 
        funcao.tipoRetorno.mostrar() != 'void') {
      erros.add(ErroSemantico(
        'Função "${funcao.nome}" pode não retornar valor em todos os caminhos de execução',
        funcao.linha,
        sugestao: 'Adicione um comando "retorna" no final da função ou em todos os ramos condicionais'
      ));
    }
    
    // Sai do escopo da função
    tabelaSimbolos.sairEscopo();
    
    // Restaura função anterior
    _funcaoAtual = funcaoAnterior;
  }
  
  void _analisarComando(ComandoNode comando) {
    if (comando is AtribuicaoNode) {
      _analisarAtribuicao(comando);
    } else if (comando is SeNode) {
      _analisarSe(comando);
    } else if (comando is EnquantoNode) {
      _analisarEnquanto(comando);
    } else if (comando is ParaNode) {
      _analisarPara(comando);
    } else if (comando is RetornaNode) {
      _analisarRetorna(comando);
    } else if (comando is PararNode) {
      _analisarParar(comando);
    } else if (comando is ContinuarNode) {
      _analisarContinuar(comando);
    } else if (comando is ChamadaFuncaoNode) {
      _inferirTipo(comando);  // Chamada de função como comando
    }
  }
  
  void _analisarAtribuicao(AtribuicaoNode atrib) {
    // Busca variável na tabela de símbolos
    var simbolo = tabelaSimbolos.buscar(atrib.variavel);
    
    if (simbolo == null) {
      // Variável não declarada - sugere nomes similares
      var sugestoes = _buscarNomesSimilares(atrib.variavel);
      var mensagemSugestao = sugestoes.isEmpty 
          ? 'Declare a variável antes de usá-la'
          : 'Você quis dizer: ${sugestoes.join(", ")}?';
      
      erros.add(ErroSemantico(
        'Variável "${atrib.variavel}" não foi declarada',
        atrib.linha,
        sugestao: mensagemSugestao
      ));
      return;
    }
    
    // Verifica se não é uma constante
    if (simbolo.categoria == 'constante') {
      erros.add(ErroSemantico(
        'Não é possível atribuir valor a constante "${atrib.variavel}"',
        atrib.linha,
        sugestao: 'Constantes não podem ser modificadas após a declaração'
      ));
      return;
    }
    
    // Infere tipo da expressão
    var tipoExpressao = _inferirTipo(atrib.valor);
    
    // Converte string de tipo para objeto Tipo
    var tipoVariavel = _stringParaTipo(simbolo.tipo);
    
    // Verifica compatibilidade de tipos
    if (!_tiposCompativeis(tipoVariavel, tipoExpressao)) {
      erros.add(ErroSemantico(
        'Não é possível atribuir valor de tipo "${tipoExpressao.mostrar()}" a variável de tipo "${tipoVariavel.mostrar()}"',
        atrib.linha,
        sugestao: 'Converta o valor para o tipo correto ou mude o tipo da variável'
      ));
    }
    
    // Marca variável como inicializada
    simbolo.inicializado = true;
  }
  
  void _analisarSe(SeNode se) {
    // Verifica condição
    var tipoCondicao = _inferirTipo(se.condicao);
    var booleano = TipoPrimitivo('booleano');
    
    if (!_tiposCompativeis(booleano, tipoCondicao)) {
      erros.add(ErroSemantico(
        'Condição do "se" deve ser do tipo booleano, mas tem tipo "${tipoCondicao.mostrar()}"',
        se.linha,
        sugestao: 'Use uma expressão de comparação (>, <, ==, etc.) ou lógica (e, ou, nao)'
      ));
    }
    
    // Analisa bloco "então"
    tabelaSimbolos.entrarEscopo();
    for (var comando in se.blocoEntao) {
      _analisarComando(comando);
    }
    tabelaSimbolos.sairEscopo();
    
    // Analisa bloco "senão" se existir
    if (se.blocoSenao != null) {
      tabelaSimbolos.entrarEscopo();
      for (var comando in se.blocoSenao!) {
        _analisarComando(comando);
      }
      tabelaSimbolos.sairEscopo();
    }
  }
  
  void _analisarEnquanto(EnquantoNode enquanto) {
    // Verifica condição
    var tipoCondicao = _inferirTipo(enquanto.condicao);
    var booleano = TipoPrimitivo('booleano');
    
    if (!_tiposCompativeis(booleano, tipoCondicao)) {
      erros.add(ErroSemantico(
        'Condição do "enquanto" deve ser do tipo booleano, mas tem tipo "${tipoCondicao.mostrar()}"',
        enquanto.linha,
        sugestao: 'Use uma expressão que resulte em verdadeiro ou falso'
      ));
    }
    
    // Marca que estamos dentro de um loop
    var dentroDeLoopAnterior = _dentroDeLoop;
    _dentroDeLoop = true;
    
    // Analisa corpo do loop
    tabelaSimbolos.entrarEscopo();
    for (var comando in enquanto.corpo) {
      _analisarComando(comando);
    }
    tabelaSimbolos.sairEscopo();
    
    // Restaura estado anterior
    _dentroDeLoop = dentroDeLoopAnterior;
  }
  
  void _analisarPara(ParaNode para) {
    tabelaSimbolos.entrarEscopo();
    
    // Declara variável de controle do loop
    tabelaSimbolos.declarar(para.variavel, 'inteiro', para.linha,
                            categoria: 'variavel', inicializado: true);
    
    // Verifica expressões inicial e final
    var tipoInicial = _inferirTipo(para.inicio);
    var tipoFinal = _inferirTipo(para.fim);
    var inteiro = TipoPrimitivo('inteiro');
    
    if (!_tiposCompativeis(inteiro, tipoInicial)) {
      erros.add(ErroSemantico(
        'Valor inicial do "para" deve ser inteiro, mas tem tipo "${tipoInicial.mostrar()}"',
        para.linha
      ));
    }
    
    if (!_tiposCompativeis(inteiro, tipoFinal)) {
      erros.add(ErroSemantico(
        'Valor final do "para" deve ser inteiro, mas tem tipo "${tipoFinal.mostrar()}"',
        para.linha
      ));
    }
    
    // Marca que estamos dentro de um loop
    var dentroDeLoopAnterior = _dentroDeLoop;
    _dentroDeLoop = true;
    
    // Analisa corpo do loop
    for (var comando in para.corpo) {
      _analisarComando(comando);
    }
    
    // Restaura estado anterior
    _dentroDeLoop = dentroDeLoopAnterior;
    
    tabelaSimbolos.sairEscopo();
  }
  
  void _analisarRetorna(RetornaNode retorna) {
    if (_funcaoAtual == null) {
      erros.add(ErroSemantico(
        'Comando "retorna" fora de função',
        retorna.linha,
        sugestao: 'Comandos "retorna" só podem aparecer dentro de funções'
      ));
      return;
    }
    
    // Verifica tipo do valor retornado
    var tipoRetorno = _funcaoAtual!.tipoRetorno;
    
    if (retorna.valor == null) {
      // "retorna" sem valor
      if (tipoRetorno.mostrar() != 'void') {
        erros.add(ErroSemantico(
          'Função "${_funcaoAtual!.nome}" deve retornar valor de tipo "${tipoRetorno.mostrar()}"',
          retorna.linha,
          sugestao: 'Adicione uma expressão após "retorna"'
        ));
      }
    } else {
      // "retorna" com valor
      if (tipoRetorno.mostrar() == 'void') {
        erros.add(ErroSemantico(
          'Função "${_funcaoAtual!.nome}" não deve retornar valor (tipo void)',
          retorna.linha,
          sugestao: 'Remova o valor após "retorna" ou mude o tipo de retorno da função'
        ));
      } else {
        var tipoValor = _inferirTipo(retorna.valor!);
        if (!_tiposCompativeis(tipoRetorno, tipoValor)) {
          erros.add(ErroSemantico(
            'Tipo do valor retornado "${tipoValor.mostrar()}" incompatível com tipo de retorno declarado "${tipoRetorno.mostrar()}"',
            retorna.linha,
            sugestao: 'Converta o valor para o tipo correto ou mude o tipo de retorno da função'
          ));
        }
      }
    }
  }
  
  void _analisarParar(PararNode parar) {
    if (!_dentroDeLoop) {
      erros.add(ErroSemantico(
        'Comando "parar" fora de loop',
        parar.linha,
        sugestao: 'Comandos "parar" só podem aparecer dentro de loops (enquanto, para)'
      ));
    }
  }
  
  void _analisarContinuar(ContinuarNode continuar) {
    if (!_dentroDeLoop) {
      erros.add(ErroSemantico(
        'Comando "continuar" fora de loop',
        continuar.linha,
        sugestao: 'Comandos "continuar" só podem aparecer dentro de loops (enquanto, para)'
      ));
    }
  }
  
  /// Infere o tipo de uma expressão
  Tipo _inferirTipo(ExpressaoNode expressao) {
    if (expressao is LiteralInteiroNode) {
      return TipoPrimitivo('inteiro');
    } else if (expressao is LiteralRealNode) {
      return TipoPrimitivo('real');
    } else if (expressao is LiteralTextoNode) {
      return TipoPrimitivo('texto');
    } else if (expressao is LiteralBooleanoNode) {
      return TipoPrimitivo('booleano');
    } else if (expressao is IdentificadorNode) {
      return _inferirTipoIdentificador(expressao);
    } else if (expressao is ExpressaoBinariaNode) {
      return _inferirTipoBinaria(expressao);
    } else if (expressao is ChamadaFuncaoNode) {
      return _inferirTipoChamada(expressao);
    } else if (expressao is ListaLiteralNode) {
      return _inferirTipoLista(expressao);
    }
    
    throw ErroSemantico(
      'Não foi possível inferir tipo da expressão',
      expressao.linha
    );
  }
  
  Tipo _inferirTipoIdentificador(IdentificadorNode id) {
    var simbolo = tabelaSimbolos.buscar(id.nome);
    
    if (simbolo == null) {
      var sugestoes = _buscarNomesSimilares(id.nome);
      var mensagemSugestao = sugestoes.isEmpty 
          ? 'Declare a variável antes de usá-la'
          : 'Você quis dizer: ${sugestoes.join(", ")}?';
      
      erros.add(ErroSemantico(
        'Variável "${id.nome}" não foi declarada',
        id.linha,
        sugestao: mensagemSugestao
      ));
      
      // Retorna tipo erro para continuar análise
      return TipoPrimitivo('erro');
    }
    
    // Verifica se variável foi inicializada
    if (!simbolo.inicializado && simbolo.categoria == 'variavel') {
      erros.add(ErroSemantico(
        'Variável "${id.nome}" pode não ter sido inicializada',
        id.linha,
        sugestao: 'Atribua um valor à variável antes de usá-la'
      ));
    }
    
    return _stringParaTipo(simbolo.tipo);
  }
  
  Tipo _inferirTipoBinaria(ExpressaoBinariaNode binaria) {
    var tipoEsq = _inferirTipo(binaria.esquerda);
    var tipoDir = _inferirTipo(binaria.direita);
    
    var tipoResultado = verificadorTipos.verificarOperacaoBinaria(
      binaria.operador,
      tipoEsq,
      tipoDir
    );
    
    if (tipoResultado == null) {
      erros.add(ErroSemantico(
        'Operador "${binaria.operador}" não pode ser aplicado a tipos "${tipoEsq.mostrar()}" e "${tipoDir.mostrar()}"',
        binaria.linha,
        sugestao: 'Verifique os tipos dos operandos e use um operador compatível'
      ));
      
      return TipoPrimitivo('erro');
    }
    
    return tipoResultado;
  }
  
  Tipo _inferirTipoChamada(ChamadaFuncaoNode chamada) {
    var simbolo = tabelaSimbolos.buscar(chamada.nome);
    
    if (simbolo == null) {
      var sugestoes = _buscarNomesSimilares(chamada.nome);
      var mensagemSugestao = sugestoes.isEmpty 
          ? 'Declare a função antes de usá-la'
          : 'Você quis dizer: ${sugestoes.join(", ")}?';
      
      erros.add(ErroSemantico(
        'Função "${chamada.nome}" não foi declarada',
        chamada.linha,
        sugestao: mensagemSugestao
      ));
      
      return TipoPrimitivo('erro');
    }
    
    if (simbolo.categoria != 'funcao') {
      erros.add(ErroSemantico(
        '"${chamada.nome}" não é uma função (é ${simbolo.categoria})',
        chamada.linha,
        sugestao: 'Use parênteses apenas para chamar funções'
      ));
      
      return TipoPrimitivo('erro');
    }
    
    // Parse tipo da função para verificar parâmetros
    var tipoFuncao = _stringParaTipo(simbolo.tipo) as TipoFuncao;
    
    // Verifica número de argumentos
    if (chamada.argumentos.length != tipoFuncao.parametros.length) {
      erros.add(ErroSemantico(
        'Função "${chamada.nome}" espera ${tipoFuncao.parametros.length} argumento(s), mas recebeu ${chamada.argumentos.length}',
        chamada.linha,
        sugestao: 'Verifique a declaração da função e ajuste o número de argumentos'
      ));
    } else {
      // Verifica tipos dos argumentos
      for (int i = 0; i < chamada.argumentos.length; i++) {
        var tipoArg = _inferirTipo(chamada.argumentos[i]);
        var tipoParam = tipoFuncao.parametros[i];
        
        if (!_tiposCompativeis(tipoParam, tipoArg)) {
          erros.add(ErroSemantico(
            'Argumento ${i + 1} da função "${chamada.nome}": esperado "${tipoParam.mostrar()}", mas recebeu "${tipoArg.mostrar()}"',
            chamada.linha,
            sugestao: 'Converta o argumento para o tipo correto'
          ));
        }
      }
    }
    
    return tipoFuncao.retorno;
  }
  
  Tipo _inferirTipoLista(ListaLiteralNode lista) {
    if (lista.elementos.isEmpty) {
      // Lista vazia - não podemos inferir o tipo do elemento
      erros.add(ErroSemantico(
        'Não é possível inferir tipo de lista vazia',
        lista.linha,
        sugestao: 'Adicione elementos à lista ou especifique o tipo explicitamente'
      ));
      return TipoLista(TipoPrimitivo('erro'));
    }
    
    // Infere tipo do primeiro elemento
    var tipoPrimeiroElemento = _inferirTipo(lista.elementos[0]);
    
    // Verifica se todos os elementos têm o mesmo tipo
    for (int i = 1; i < lista.elementos.length; i++) {
      var tipoElemento = _inferirTipo(lista.elementos[i]);
      if (!_tiposCompativeis(tipoPrimeiroElemento, tipoElemento)) {
        erros.add(ErroSemantico(
          'Todos os elementos da lista devem ter o mesmo tipo. Elemento ${i + 1} tem tipo "${tipoElemento.mostrar()}", mas esperava-se "${tipoPrimeiroElemento.mostrar()}"',
          lista.linha,
          sugestao: 'Converta todos os elementos para o mesmo tipo'
        ));
      }
    }
    
    return TipoLista(tipoPrimeiroElemento);
  }
  
  /// Verifica se dois tipos são compatíveis (igualdade ou conversão implícita)
  bool _tiposCompativeis(Tipo esperado, Tipo recebido) {
    // Tipos exatamente iguais
    if (esperado.igual(recebido)) return true;
    
    // Tipos erro são compatíveis com tudo (para continuar análise)
    if (esperado is TipoPrimitivo && esperado.nome == 'erro') return true;
    if (recebido is TipoPrimitivo && recebido.nome == 'erro') return true;
    
    // Promoção automática: inteiro → real
    if (esperado is TipoPrimitivo && esperado.nome == 'real' &&
        recebido is TipoPrimitivo && recebido.nome == 'inteiro') {
      return true;
    }
    
    return false;
  }
  
  /// Converte string de tipo para objeto Tipo
  Tipo _stringParaTipo(String tipoStr) {
    // Remove espaços
    tipoStr = tipoStr.trim();
    
    // Tipos primitivos
    if (['inteiro', 'real', 'texto', 'booleano', 'void'].contains(tipoStr)) {
      return TipoPrimitivo(tipoStr);
    }
    
    // Tipo lista: lista<tipo>
    if (tipoStr.startsWith('lista<') && tipoStr.endsWith('>')) {
      var tipoElementoStr = tipoStr.substring(6, tipoStr.length - 1);
      var tipoElemento = _stringParaTipo(tipoElementoStr);
      return TipoLista(tipoElemento);
    }
    
    // Tipo função: (tipo1, tipo2, ...) -> tipoRetorno
    if (tipoStr.contains('->')) {
      var partes = tipoStr.split('->');
      var paramsStr = partes[0].trim();
      var retornoStr = partes[1].trim();
      
      // Remove parênteses dos parâmetros
      paramsStr = paramsStr.substring(1, paramsStr.length - 1);
      
      // Parse parâmetros
      var tiposParams = <Tipo>[];
      if (paramsStr.isNotEmpty) {
        for (var paramStr in paramsStr.split(',')) {
          tiposParams.add(_stringParaTipo(paramStr.trim()));
        }
      }
      
      var tipoRetorno = _stringParaTipo(retornoStr);
      
      return TipoFuncao(tiposParams, tipoRetorno);
    }
    
    // Tipo desconhecido
    return TipoPrimitivo('erro');
  }
  
  /// Busca nomes similares na tabela de símbolos (para sugestões)
  List<String> _buscarNomesSimilares(String nome) {
    // Implementação simples: busca nomes com distância de edição <= 2
    var similares = <String>[];
    
    // Percorre todos os escopos
    for (int i = _pilhaEscopos.length - 1; i >= 0; i--) {
      for (var simbolo in _pilhaEscopos[i].obterTodosSimbolos()) {
        if (_distanciaEdicao(nome, simbolo.nome) <= 2) {
          similares.add(simbolo.nome);
        }
      }
    }
    
    return similares;
  }
  
  /// Calcula distância de edição entre duas strings (algoritmo de Levenshtein)
  int _distanciaEdicao(String s1, String s2) {
    var len1 = s1.length;
    var len2 = s2.length;
    
    var matriz = List.generate(len1 + 1, (_) => List.filled(len2 + 1, 0));
    
    for (int i = 0; i <= len1; i++) matriz[i][0] = i;
    for (int j = 0; j <= len2; j++) matriz[0][j] = j;
    
    for (int i = 1; i <= len1; i++) {
      for (int j = 1; j <= len2; j++) {
        var custo = s1[i - 1] == s2[j - 1] ? 0 : 1;
        
        matriz[i][j] = [
          matriz[i - 1][j] + 1,      // Deleção
          matriz[i][j - 1] + 1,      // Inserção
          matriz[i - 1][j - 1] + custo  // Substituição
        ].reduce((a, b) => a < b ? a : b);
      }
    }
    
    return matriz[len1][len2];
  }
  
  /// Verifica se todos os caminhos de execução retornam valor
  bool _todosOsCaminhosRetornam(List<ComandoNode> comandos) {
    for (var comando in comandos) {
      if (comando is RetornaNode) {
        return true;
      }
      
      if (comando is SeNode) {
        // Se tem senão e ambos retornam, então todos caminhos retornam
        if (comando.blocoSenao != null) {
          if (_todosOsCaminhosRetornam(comando.blocoEntao) &&
              _todosOsCaminhosRetornam(comando.blocoSenao!)) {
            return true;
          }
        }
      }
    }
    
    return false;
  }
  
  /// Verifica variáveis não utilizadas (chamado no final da análise)
  void _verificarVariaveisNaoUtilizadas() {
    // Esta é uma verificação simplificada
    // Em um compilador real, rastreamos cada uso de variável durante a análise
    // Aqui apenas mostramos o conceito
  }
}

/// Nodos da AST (simplificados para o exemplo)
abstract class ASTNode {
  int get linha;
}

class ProgramaNode extends ASTNode {
  final List<DeclaracaoNode> declaracoes;
  @override
  final int linha;
  
  ProgramaNode(this.declaracoes, this.linha);
}

abstract class DeclaracaoNode extends ASTNode {}

class DeclaracaoVariavelNode extends DeclaracaoNode {
  final String nome;
  final Tipo? tipo;
  final ExpressaoNode? inicializador;
  @override
  final int linha;
  
  DeclaracaoVariavelNode(this.nome, this.tipo, this.inicializador, this.linha);
}

class DeclaracaoFuncaoNode extends DeclaracaoNode {
  final String nome;
  final List<ParametroNode> parametros;
  final Tipo tipoRetorno;
  final List<ComandoNode> corpo;
  @override
  final int linha;
  
  DeclaracaoFuncaoNode(this.nome, this.parametros, this.tipoRetorno, 
                       this.corpo, this.linha);
}

class ParametroNode extends ASTNode {
  final String nome;
  final Tipo tipo;
  @override
  final int linha;
  
  ParametroNode(this.nome, this.tipo, this.linha);
}

abstract class ComandoNode extends ASTNode {}

class AtribuicaoNode extends ComandoNode {
  final String variavel;
  final ExpressaoNode valor;
  @override
  final int linha;
  
  AtribuicaoNode(this.variavel, this.valor, this.linha);
}

class SeNode extends ComandoNode {
  final ExpressaoNode condicao;
  final List<ComandoNode> blocoEntao;
  final List<ComandoNode>? blocoSenao;
  @override
  final int linha;
  
  SeNode(this.condicao, this.blocoEntao, this.blocoSenao, this.linha);
}

class EnquantoNode extends ComandoNode {
  final ExpressaoNode condicao;
  final List<ComandoNode> corpo;
  @override
  final int linha;
  
  EnquantoNode(this.condicao, this.corpo, this.linha);
}

class ParaNode extends ComandoNode {
  final String variavel;
  final ExpressaoNode inicio;
  final ExpressaoNode fim;
  final List<ComandoNode> corpo;
  @override
  final int linha;
  
  ParaNode(this.variavel, this.inicio, this.fim, this.corpo, this.linha);
}

class RetornaNode extends ComandoNode {
  final ExpressaoNode? valor;
  @override
  final int linha;
  
  RetornaNode(this.valor, this.linha);
}

class PararNode extends ComandoNode {
  @override
  final int linha;
  
  PararNode(this.linha);
}

class ContinuarNode extends ComandoNode {
  @override
  final int linha;
  
  ContinuarNode(this.linha);
}

abstract class ExpressaoNode extends ASTNode {}

class LiteralInteiroNode extends ExpressaoNode {
  final int valor;
  @override
  final int linha;
  
  LiteralInteiroNode(this.valor, this.linha);
}

class LiteralRealNode extends ExpressaoNode {
  final double valor;
  @override
  final int linha;
  
  LiteralRealNode(this.valor, this.linha);
}

class LiteralTextoNode extends ExpressaoNode {
  final String valor;
  @override
  final int linha;
  
  LiteralTextoNode(this.valor, this.linha);
}

class LiteralBooleanoNode extends ExpressaoNode {
  final bool valor;
  @override
  final int linha;
  
  LiteralBooleanoNode(this.valor, this.linha);
}

class IdentificadorNode extends ExpressaoNode {
  final String nome;
  @override
  final int linha;
  
  IdentificadorNode(this.nome, this.linha);
}

class ExpressaoBinariaNode extends ExpressaoNode {
  final ExpressaoNode esquerda;
  final String operador;
  final ExpressaoNode direita;
  @override
  final int linha;
  
  ExpressaoBinariaNode(this.esquerda, this.operador, this.direita, this.linha);
}

class ChamadaFuncaoNode extends ExpressaoNode {
  final String nome;
  final List<ExpressaoNode> argumentos;
  @override
  final int linha;
  
  ChamadaFuncaoNode(this.nome, this.argumentos, this.linha);
}

class ListaLiteralNode extends ExpressaoNode {
  final List<ExpressaoNode> elementos;
  @override
  final int linha;
  
  ListaLiteralNode(this.elementos, this.linha);
}

Esta implementação demonstra a estrutura básica de um analisador semântico completo. Programas reais requerem muito mais complexidade, incluindo análise de fluxo de dados para rastrear inicialização de variáveis, análise de alcance para código morto, e verificações específicas da linguagem para construções particulares.

Integração no Pipeline do Compilador

A análise semântica se encaixa no pipeline entre parsing e geração de código:

graph LR
    A[Código Fonte] --> B[Análise Léxica]
    B --> C[Tokens]
    C --> D[Análise Sintática]
    D --> E[AST]
    E --> F[Análise Semântica]
    F --> G[AST Anotada com Tipos]
    G --> H[Tabela de Símbolos]
    F --> H
    G --> I[Geração de Código]
    H --> I
    
    style F fill:#fff3e0
    style G fill:#e8f5e9
    style H fill:#e3f2fd

A análise semântica consome a AST “pura” produzida pelo parser e produz:

  1. AST Anotada: Onde cada nodo de expressão tem informação de tipo associada
  2. Tabela de Símbolos Completa: Com todos os símbolos declarados e seus tipos
  3. Lista de Erros Semânticos: Todos os problemas detectados com mensagens detalhadas

Estas estruturas de dados enriquecidas são então usadas pela fase de geração de código para produzir código executável correto e eficiente.


🌟 Reflexões Finais: Da Teoria à Prática

Esta semana você deu um dos passos mais importantes da disciplina: implementar análise semântica que transforma seu compilador de um simples verificador de sintaxe em uma ferramenta verdadeiramente inteligente que compreende significado.

A jornada de tabelas de símbolos simples, através de sistemas de tipos sofisticados, até inferência automática de tipos, ilustra perfeitamente como teoria e prática se reforçam mutuamente na ciência da computação. A teoria fornece algoritmos elegantes e garantias formais. A prática revela desafios específicos que teoria abstrata não antecipa, como qualidade de mensagens de erro e performance em programas grandes.

🎓 Lições Fundamentais Desta Semana

Implementar análise semântica desenvolveu múltiplas competências essenciais:

Capacidade de raciocinar sobre correção de programas: Você aprendeu a pensar sistematicamente sobre o que torna um programa correto além de sua sintaxe, desenvolvendo intuição sobre tipos, escopos, e fluxo de controle que se aplica a qualquer linguagem de programação. Esta habilidade transcende compiladores - você agora pensa em programas de forma mais profunda e estruturada.

Compreensão profunda de sistemas de tipos: Trabalhar com tipos e inferência desenvolve apreciação por como sistemas de tipos são simultaneamente ferramentas práticas para prevenir erros e objetos matemáticos elegantes com propriedades profundas. Você vê agora por que TypeScript adiciona tipos ao JavaScript, por que Rust tem seu sistema de ownership, e por que Haskell consegue inferir tipos tão complexos.

Habilidade de design de estruturas de dados complexas: Projetar tabelas de símbolos hierárquicas e implementar algoritmos de unificação requer pensar cuidadosamente sobre invariantes, eficiência, e correção - habilidades fundamentais de engenharia de software. Estas estruturas não são apenas para compiladores; aparecem em IDEs, linters, formatadores, e ferramentas de análise estática.

Experiência com análise de programas: Análise semântica é uma forma de análise estática de programas. As técnicas que você aprendeu - análise de fluxo de controle, análise de fluxo de dados, resolução de nomes - são aplicáveis a muitos outros contextos além de compiladores, incluindo ferramentas de verificação formal, refatoração automática, análise de segurança, e detecção de bugs.

Desenvolvimento de empatia com usuários: Criar mensagens de erro de qualidade requer você se colocar no lugar do programador que cometeu o erro. Esta empatia é essencial para qualquer ferramenta de desenvolvedor. Você aprendeu que tecnologia não é apenas algoritmos corretos, mas também experiência do usuário.

As capacidades que você desenvolveu transcendem compiladores especificamente. Você praticou raciocínio formal ao aplicar regras de tipagem e algoritmos de inferência rigorosamente. Você experimentou design de algoritmos ao implementar unificação e verificações semânticas. Você aplicou engenharia de software ao estruturar código complexo de forma modular e testável. Você desenvolveu pensamento sistêmico ao ver como diferentes fases do compilador se conectam.

A conexão com as próximas semanas é direta e emocionante. A AST anotada com tipos que você construiu esta semana será a entrada para geração de código na Semana 14, onde você transformará programas verificados em código executável eficiente. A tabela de símbolos que você construiu informará decisões sobre alocação de memória e gerenciamento de variáveis. As verificações semânticas garantirão que apenas programas corretos sejam compilados, prevenindo erros em tempo de execução.

Você está agora em posição de apreciar plenamente a complexidade e elegância de compiladores modernos. O que começou como simples análise léxica (reconhecer palavras) floresceu em um sistema sofisticado que verifica programas de formas profundas, implementando a “inteligência” que torna linguagens de programação usáveis e seguras. Você construiu um sistema que realmente “entende” código!

Aplicações Práticas do que Você Aprendeu:

Quando você usar um IDE moderno como VS Code, IntelliJ ou PyCharm, reconhecerá que os recursos de autocompletar, destacar erros em tempo real, sugerir correções, e refatorar código dependem exatamente das técnicas que você implementou: tabelas de símbolos para saber quais nomes estão disponíveis, inferência de tipos para sugerir métodos apropriados, análise de fluxo para detectar variáveis não inicializadas.

Quando você ler sobre linguagens modernas como Rust (com seu sistema de tipos que previne race conditions), TypeScript (que adiciona tipos ao JavaScript), ou Swift (com suas guarantias de segurança de memória), você entenderá os fundamentos teóricos por trás desses recursos.

Continue explorando, experimentando, e aprofundando sua compreensão. A análise semântica que você implementou é apenas o começo - há todo um mundo de técnicas avançadas de verificação e análise esperando para ser descoberto: análise de fluxo interprocedural, verificação formal, análise simbólica, type systems dependentes, e muito mais!


Próxima Semana: Geração de Código com LLVM IR ⚡

Na próxima semana, você transformará programas verificados semanticamente em código executável, seja através de compilação para LLVM IR ou interpretação direta. Esta é a fase culminante onde todo o trabalho de análise finalmente produz programas que podem ser executados! Você verá como a informação de tipos coletada esta semana guia decisões de geração de código, como registros são alocados, como funções são chamadas, e como otimizações são aplicadas.

Prepare-se para a jornada final do seu compilador: da análise à execução! 🚀