UNPKG

@designliquido/delegua

Version:

Linguagem de programação simples e moderna usando português estruturado.

1,394 lines 66.6 kB
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnalisadorSemantico = void 0;
const construtos_1 = require("../construtos");
const declaracoes_1 = require("../declaracoes");
const erros_1 = require("../interfaces/erros");
const comum_1 = require("../avaliador-sintatico/comum");
const micro_avaliador_sintatico_1 = require("../avaliador-sintatico/micro-avaliador-sintatico");
const micro_lexador_1 = require("../lexador/micro-lexador");
const analisador_semantico_base_1 = require("./analisador-semantico-base");
const gerenciador_escopos_1 = require("./gerenciador-escopos");
const pilha_variaveis_1 = require("./pilha-variaveis");
/**
 * O Analisador Semântico de Delégua.
 */
class AnalisadorSemantico extends analisador_semantico_base_1.AnalisadorSemanticoBase {
    constructor() {
        super();
        this.microLexador = new micro_lexador_1.MicroLexador();
        this.microAvaliadorSintatico = new micro_avaliador_sintatico_1.MicroAvaliadorSintatico();
        this.pilhaVariaveis = new pilha_variaveis_1.PilhaVariaveis();
        this.gerenciadorEscopos = new gerenciador_escopos_1.GerenciadorEscopos();
        this.funcoes = {};
        this.classesDeclaradas = new Set();
        this.classesRegistradas = new Map();
        this.classesExternasRegistradas = new Map();
        this.classeAtualEmAnalise = null;
        this.atual = 0;
        this.diagnosticos = [];
    }
    registrarClassesExternas(classes) {
        for (const classe of classes) {
            this.classesExternasRegistradas.set(classe.simbolo.lexema, classe);
        }
    }
    verificarTipoAtribuido(declaracao) {
        if (declaracao.tipo) {
            if (['vetor', 'qualquer[]', 'inteiro[]', 'texto[]'].includes(declaracao.tipo)) {
                if (declaracao.inicializador instanceof construtos_1.Vetor) {
                    const vetor = declaracao.inicializador;
                    const vetorSemSeparadores = vetor.elementos;
                    if (declaracao.tipo === 'inteiro[]') {
                        const apenasValores = vetorSemSeparadores.find((v) => typeof v?.valor !== 'number');
                        if (apenasValores) {
                            this.erro(declaracao.simbolo, `Atribuição inválida para '${declaracao.simbolo.lexema}': é esperado um valor do tipo vetor de inteiro ou real. Atual: ${vetor.tipo}.`);
                        }
                    }
                    if (declaracao.tipo === 'texto[]') {
                        const apenasValores = vetorSemSeparadores.find((v) => typeof v?.valor !== 'string');
                        if (apenasValores) {
                            this.erro(declaracao.simbolo, `Atribuição inválida para '${declaracao.simbolo.lexema}': é esperado um valor do tipo vetor de texto. Atual: ${vetor.tipo}.`);
                        }
                    }
                }
                else if (declaracao.inicializador instanceof construtos_1.Chamada) {
                    // Chamadas de método/função podem retornar vetores.
                    // A validação detalhada é feita em tempo de execução.
                }
                else {
                    this.erro(declaracao.simbolo, `Atribuição inválida para '${declaracao.simbolo.lexema}': é esperado um vetor de elementos.`);
                }
            }
            if (declaracao.inicializador instanceof construtos_1.Literal) {
                const literal = declaracao.inicializador;
                if (declaracao.tipo === 'texto' && literal.tipo !== 'texto') {
                    this.erro(declaracao.simbolo, `Atribuição inválida para '${declaracao.simbolo.lexema}': é esperado um valor do tipo texto. Atual: ${literal.tipo}.`);
                }
                if (['inteiro', 'número', 'real'].includes(declaracao.tipo) &&
                    !['inteiro', 'número', 'real'].includes(literal.tipo)) {
                    this.erro(declaracao.simbolo, `Atribuição inválida para '${declaracao.simbolo.lexema}': é esperado um valor do tipo número. Atual: ${literal.tipo}.`);
                }
            }
            if (declaracao.inicializador instanceof construtos_1.Leia) {
                if (!['qualquer', 'texto'].includes(declaracao.tipo)) {
                    this.erro(declaracao.simbolo, `Atribuição inválida para '${declaracao.simbolo.lexema}', Função 'leia()' sempre retorna 'texto'.`);
                }
            }
        }
    }
    visitarExpressaoTipoDe(expressao) {
        return this.verificarTipoDe(expressao.valor);
    }
    /**
     * Método recursivo para verificar o tipo de um construto, usado principalmente para validar
     * o uso de `tipoDe` e `falhar()`.
     * @param {ConstrutoInterface} valor O construto a ser avaliado.
     * @returns {Promise<any>} O tipo do construto, ou `Promise.resolve()` se o tipo não puder ser
     * determinado neste estágio da análise.
     */
    verificarTipoDe(valor) {
        switch (valor.constructor) {
            case construtos_1.Agrupamento:
                const valorAgrupamento = valor;
                return this.verificarTipoDe(valorAgrupamento.expressao);
            case construtos_1.Binario:
                const valorBinario = valor;
                this.verificarTipoDe(valorBinario.direita);
                this.verificarTipoDe(valorBinario.esquerda);
                break;
            case construtos_1.Variavel:
                const valorVariavel = valor;
                return this.verificarVariavel(valorVariavel);
        }
        return Promise.resolve();
    }
    visitarExpressaoFalhar(expressao) {
        return this.verificarFalhar(expressao.explicacao);
    }
    /**
     * Método recursivo para verificar se um construto passado para `falhar()` é válido, ou seja, se é
     * do tipo texto ou pode ser avaliado como texto.
     * @param {ConstrutoInterface} valor O construto a ser avaliado.
     * @returns {Promise<any>} O tipo do construto, ou `Promise.resolve()` se o tipo não puder ser
     * determinado neste estágio da análise.
     */
    verificarFalhar(valor) {
        if (valor instanceof construtos_1.Binario) {
            this.verificarFalhar(valor.direita);
            this.verificarFalhar(valor.esquerda);
        }
        if (valor instanceof construtos_1.Agrupamento) {
            return this.verificarFalhar(valor.expressao);
        }
        if (valor instanceof construtos_1.Variavel) {
            return this.verificarVariavel(valor);
        }
        return Promise.resolve();
    }
    comparacaoArgumentosContraParametrosFuncao(simboloFuncao, parametros, argumentos) {
        if (parametros.length !== argumentos.length) {
            this.erro(simboloFuncao, `Função '${simboloFuncao.lexema}' espera ${parametros.length} parâmetros. Atual: ${argumentos.length}.`);
        }
        for (let [indice, parametro] of parametros.entries()) {
            const argumento = argumentos[indice];
            if (!argumento)
                continue;
            const tipoArgumento = argumento.tipo ?? 'qualquer';
            if (tipoArgumento === 'qualquer')
                continue;
            if (parametro.tipoDado === 'texto' && tipoArgumento !== 'texto') {
                this.erro(simboloFuncao, `O valor passado para o parâmetro '${parametro.nome.lexema}' (${parametro.tipoDado}) é diferente do esperado pela função (${tipoArgumento}).`);
            }
            else if (['inteiro', 'número', 'real'].includes(parametro.tipoDado)) {
                // Delégua pode trabalhar com conversões implícitas entre tipos numéricos.
                if (!['inteiro', 'número', 'real'].includes(tipoArgumento)) {
                    this.erro(simboloFuncao, `O valor passado para o parâmetro '${parametro.nome.lexema}' (${parametro.tipoDado}) é diferente do esperado pela função (${tipoArgumento}).`);
                }
            }
        }
    }
    visitarChamadaPorArgumentoReferenciaFuncao(argumentoReferenciaFuncao, argumentos) {
        const variavelCorrespondente = this.gerenciadorEscopos.buscar(argumentoReferenciaFuncao.simboloFuncao.lexema)?.valor;
        if (!variavelCorrespondente) {
            return;
        }
        this.comparacaoArgumentosContraParametrosFuncao(argumentoReferenciaFuncao.simboloFuncao, variavelCorrespondente.parametros, argumentos);
    }
    visitarChamadaPorReferenciaFuncao(referenciaFuncao, argumentos) {
        const funcaoCorrespondente = this.funcoes[referenciaFuncao.simboloFuncao.lexema];
        if (!funcaoCorrespondente) {
            return;
        }
        this.comparacaoArgumentosContraParametrosFuncao(referenciaFuncao.simboloFuncao, funcaoCorrespondente.valor.parametros, argumentos);
    }
    visitarChamadaPorVariavel(entidadeChamadaVariavel, argumentos) {
        const variavel = entidadeChamadaVariavel;
        const nomeFuncao = variavel.simbolo.lexema;
        const funcoesNativas = [
            'aleatorio',
            'aleatorioEntre',
            'algum',
            'arredondar',
            'clonar',
            'encontrar',
            'encontrarIndice',
            'encontrarUltimo',
            'encontrarUltimoIndice',
            'escreva',
            'filtrarPor',
            'incluido',
            'inteiro',
            'intervalo',
            'leia',
            'longo',
            'mapear',
            'maximo',
            'minimo',
            'numero',
            'número',
            'ordenar',
            'paraCada',
            'primeiroEmCondicao',
            'real',
            'reduzir',
            'somar',
            'tamanho',
            'texto',
            'tipo',
            'todos',
            'todosEmCondicao',
            'tupla',
            'vetor',
        ];
        const pareceSerClasse = nomeFuncao[0] === nomeFuncao[0].toUpperCase();
        if (funcoesNativas.includes(nomeFuncao) || pareceSerClasse) {
            return Promise.resolve();
        }
        const funcaoChamada = this.gerenciadorEscopos.buscar(nomeFuncao) || this.funcoes[nomeFuncao];
        if (!funcaoChamada) {
            this.erro(entidadeChamadaVariavel.simbolo, `Chamada da função '${nomeFuncao}' não existe.`);
            return Promise.resolve();
        }
        const funcao = funcaoChamada.valor;
        this.comparacaoArgumentosContraParametrosFuncao(entidadeChamadaVariavel.simbolo, funcao.parametros, argumentos);
    }
    async visitarExpressaoDeChamada(expressao) {
        for (const argumento of expressao.argumentos) {
            if (argumento instanceof construtos_1.Variavel) {
                this.gerenciadorEscopos.marcarComoUsada(argumento.simbolo.lexema);
            }
        }
        switch (expressao.entidadeChamada.constructor) {
            case construtos_1.AcessoMetodo:
                // Marca o objeto como usado quando seus métodos são chamados (ex: thor.corre())
                const entidadeChamadaAcessoMetodo = expressao.entidadeChamada;
                this.marcarVariaveisUsadasEmExpressao(entidadeChamadaAcessoMetodo.objeto);
                break;
            case construtos_1.AcessoMetodoOuPropriedade:
                // Marca o objeto como usado quando seus métodos/propriedades são acessados (ex: thor.corre())
                // e verifica acesso a membros privados/protegidos
                const entidadeChamadaAcessoMetodoOuPropriedade = expressao.entidadeChamada;
                this.marcarVariaveisUsadasEmExpressao(entidadeChamadaAcessoMetodoOuPropriedade.objeto);
                await expressao.entidadeChamada.aceitar(this);
                break;
            case construtos_1.ArgumentoReferenciaFuncao:
                const entidadeChamadaArgumentoReferenciaFuncao = expressao.entidadeChamada;
                this.visitarChamadaPorArgumentoReferenciaFuncao(entidadeChamadaArgumentoReferenciaFuncao, expressao.argumentos);
                break;
            case construtos_1.ReferenciaFuncao:
                const entidadeChamadaReferenciaFuncao = expressao.entidadeChamada;
                this.visitarChamadaPorReferenciaFuncao(entidadeChamadaReferenciaFuncao, expressao.argumentos);
                break;
            case construtos_1.Variavel:
                const entidadeChamadaVariavel = expressao.entidadeChamada;
                this.visitarChamadaPorVariavel(entidadeChamadaVariavel, expressao.argumentos);
                break;
        }
        return Promise.resolve();
    }
    visitarExpressaoDeAtribuicao(expressao) {
        let simboloAlvo;
        switch (expressao.alvo.constructor) {
            case construtos_1.Variavel:
                const alvoVariavel = expressao.alvo;
                simboloAlvo = alvoVariavel.simbolo;
                break;
            default:
                return Promise.resolve();
        }
        const variavel = this.gerenciadorEscopos.buscar(simboloAlvo.lexema);
        if (!variavel) {
            this.erro(simboloAlvo, `Variável '${simboloAlvo.lexema}' ainda não foi declarada até este ponto.`);
            return Promise.resolve();
        }
        if (variavel.imutavel) {
            this.erro(simboloAlvo, `Constante '${simboloAlvo.lexema}' não pode ser modificada.`);
            return Promise.resolve();
        }
        // Marca como inicializada após atribuição
        this.gerenciadorEscopos.marcarComoInicializada(simboloAlvo.lexema, expressao.valor);
        // Marca variáveis usadas no valor atribuído (ex: idade = ano - inteiro(leia(...))).
        this.marcarVariaveisUsadasEmExpressao(expressao.valor);
        // Atualiza tipo se a variável não foi tipada explicitamente
        if (variavel.tipo === 'qualquer') {
            const tipoInferido = this.obterTipoExpressao(expressao.valor);
            if (tipoInferido && tipoInferido !== 'qualquer') {
                variavel.tipo = tipoInferido;
            }
        }
        // TODO: Readaptar para trabalhar com `expressao.alvo` sendo um construto.
        switch (expressao.alvo.constructor) {
            case construtos_1.Variavel:
                const alvoVariavel = expressao.alvo;
                simboloAlvo = alvoVariavel.simbolo;
                break;
            default:
                // throw new Error(`Implementar atribuição para ${expressao.alvo.constructor}.`);
                return Promise.resolve();
        }
        let valor = this.gerenciadorEscopos.buscar(simboloAlvo.lexema);
        if (!valor) {
            this.erro(simboloAlvo, `Variável ${simboloAlvo.lexema} ainda não foi declarada até este ponto.`);
            return Promise.resolve();
        }
        if (valor.tipo) {
            if (expressao.valor instanceof construtos_1.Literal && valor.tipo.includes('[]')) {
                this.erro(simboloAlvo, `Atribuição inválida, esperado tipo '${valor.tipo}' na atribuição.`);
                return Promise.resolve();
            }
            if (expressao.valor instanceof construtos_1.Vetor && !valor.tipo.includes('[]')) {
                this.erro(simboloAlvo, `Atribuição inválida, esperado tipo '${valor.tipo}' na atribuição.`);
                return Promise.resolve();
            }
            if (expressao.valor instanceof construtos_1.Literal) {
                let valorLiteral = typeof expressao.valor.valor;
                if (!['qualquer'].includes(valor.tipo)) {
                    if (valorLiteral === 'string') {
                        if (valor.tipo != 'texto') {
                            this.erro(simboloAlvo, `Esperado tipo '${valor.tipo}' na atribuição.`);
                            return Promise.resolve();
                        }
                    }
                    if (valorLiteral === 'number') {
                        if (!['inteiro', 'número', 'real'].includes(valor.tipo)) {
                            this.erro(simboloAlvo, `Esperado tipo '${valor.tipo}' na atribuição.`);
                            return Promise.resolve();
                        }
                    }
                }
            }
            if (expressao.valor instanceof construtos_1.Vetor) {
                let valoresSemSeparador = expressao.valor.elementos;
                if (!['qualquer[]'].includes(valor.tipo)) {
                    if (valor.tipo === 'texto[]') {
                        if (!valoresSemSeparador.every((v) => typeof v.valor === 'string')) {
                            this.erro(simboloAlvo, `Esperado tipo '${valor.tipo}' na atribuição.`);
                            return Promise.resolve();
                        }
                    }
                    if (['inteiro[]', 'numero[]'].includes(valor.tipo)) {
                        if (!valoresSemSeparador.every((v) => typeof v.valor === 'number')) {
                            this.erro(simboloAlvo, `Esperado tipo '${valor.tipo}' na atribuição.`);
                            return Promise.resolve();
                        }
                    }
                }
            }
        }
        return Promise.resolve();
    }
    async visitarDeclaracaoDeExpressao(declaracao) {
        return await declaracao.expressao.aceitar(this);
    }
    async visitarExpressaoBloco(declaracao) {
        this.gerenciadorEscopos.empilharEscopo();
        try {
            for (const declaracaoBloco of declaracao.declaracoes) {
                await declaracaoBloco.aceitar(this);
            }
        }
        finally {
            this.gerenciadorEscopos.desempilharEscopo();
        }
        return Promise.resolve();
    }
    visitarExpressaoAcessoIndiceVariavel(expressao) {
        this.marcarVariaveisUsadasEmExpressao(expressao.entidadeChamada);
        this.marcarVariaveisUsadasEmExpressao(expressao.indice);
        return Promise.resolve();
    }
    visitarExpressaoAtribuicaoPorIndice(expressao) {
        this.marcarVariaveisUsadasEmExpressao(expressao.objeto);
        this.marcarVariaveisUsadasEmExpressao(expressao.indice);
        this.marcarVariaveisUsadasEmExpressao(expressao.valor);
        return Promise.resolve();
    }
    visitarDeclaracaoAjuda(declaracao) {
        if (declaracao.elemento) {
            this.marcarVariaveisUsadasEmExpressao(declaracao.elemento);
        }
        return Promise.resolve();
    }
    visitarExpressaoAjuda(expressao) {
        if (expressao.valor) {
            this.marcarVariaveisUsadasEmExpressao(expressao.valor);
        }
        return Promise.resolve();
    }
    async visitarDeclaracaoEscolha(declaracao) {
        const identificadorOuLiteral = declaracao.identificadorOuLiteral;
        const tipo = identificadorOuLiteral.tipo || 'qualquer';
        const tiposLiteraisCasos = [];
        this.marcarVariaveisUsadasEmExpressao(identificadorOuLiteral);
        for (let caminho of declaracao.caminhos) {
            for (const declaracao of caminho.declaracoes) {
                await declaracao.aceitar(this);
            }
            for (let condicao of caminho.condicoes) {
                switch (condicao.constructor) {
                    case construtos_1.Literal:
                        const condicaoLiteral = condicao;
                        const tiposNumericos = ['inteiro', 'número', 'real'];
                        const ambosSaoNumericos = tiposNumericos.includes(condicaoLiteral.tipo) &&
                            tiposNumericos.includes(tipo);
                        if (condicaoLiteral.tipo !== tipo &&
                            !ambosSaoNumericos &&
                            tipo !== 'qualquer') {
                            this.erro({
                                lexema: condicaoLiteral.valor,
                                tipo: condicaoLiteral.tipo,
                                linha: condicaoLiteral.linha,
                                hashArquivo: condicaoLiteral.hashArquivo,
                            }, `'caso ${condicaoLiteral.valor}:' não é do mesmo tipo esperado em 'escolha' (esperado: ${tipo}, atual: ${condicaoLiteral.tipo}).`);
                        }
                        tiposLiteraisCasos.push(condicaoLiteral.tipo);
                        break;
                    case construtos_1.Variavel:
                        const condicaoVariavel = condicao;
                        this.verificarVariavel(condicaoVariavel);
                        const variavelHipotetica = this.gerenciadorEscopos.buscar(condicaoVariavel.simbolo.lexema);
                        if (variavelHipotetica && typeof variavelHipotetica.valor !== tipo) {
                            this.erro(condicaoVariavel.simbolo, `'caso ${condicaoVariavel.simbolo.lexema}:' não é do mesmo tipo esperado em 'escolha'`);
                        }
                        break;
                }
            }
        }
        if (tipo === 'qualquer' &&
            tiposLiteraisCasos.length > 0 &&
            identificadorOuLiteral instanceof construtos_1.Variavel) {
            const tiposUnicos = [...new Set(tiposLiteraisCasos)];
            if (tiposUnicos.length === 1) {
                const tipoInferido = tiposUnicos[0];
                const variavelEscopo = this.gerenciadorEscopos.buscar(identificadorOuLiteral.simbolo.lexema);
                if (variavelEscopo) {
                    this.sugestao(identificadorOuLiteral.simbolo, `Um tipo melhor pode ser inferido para '${identificadorOuLiteral.simbolo.lexema}': '${tipoInferido}'`, [
                        {
                            titulo: `Alterar tipo de '${identificadorOuLiteral.simbolo.lexema}' para '${tipoInferido}'`,
                            textoOriginal: 'qualquer',
                            textoSubstituto: tipoInferido,
                            linha: variavelEscopo.linha,
                            colunaInicio: 0,
                            colunaFim: 0,
                        },
                    ]);
                }
            }
        }
        if (declaracao.caminhoPadrao) {
            for (const decl of declaracao.caminhoPadrao.declaracoes) {
                await decl.aceitar(this);
            }
        }
        return Promise.resolve();
    }
    async visitarDeclaracaoEnquanto(declaracao) {
        // Marca variáveis usadas na condição.
        this.marcarVariaveisUsadasEmExpressao(declaracao.condicao);
        // Verifica a condição (incluindo validações de tipo para operadores lógicos).
        await this.verificarCondicao(declaracao.condicao);
        // Visita corpo para que usos/atribuições dentro do laço sejam analisados.
        for (const declaracaoCorpo of declaracao.corpo.declaracoes) {
            await declaracaoCorpo.aceitar(this);
        }
        return Promise.resolve();
    }
    async visitarDeclaracaoFazer(declaracao) {
        // Visita corpo para que usos/atribuições dentro do bloco sejam analisados.
        for (const declaracaoCorpo of declaracao.caminhoFazer.declaracoes) {
            await declaracaoCorpo.aceitar(this);
        }
        // Marca variáveis usadas na condição
        this.marcarVariaveisUsadasEmExpressao(declaracao.condicaoEnquanto);
        // Verifica a condição
        return this.verificarCondicao(declaracao.condicaoEnquanto);
    }
    async visitarDeclaracaoPara(declaracao) {
        this.gerenciadorEscopos.empilharEscopo();
        try {
            if (Array.isArray(declaracao.inicializador)) {
                for (const inicializador of declaracao.inicializador) {
                    await inicializador.aceitar(this);
                }
            }
            else if (declaracao.inicializador) {
                await declaracao.inicializador.aceitar(this);
            }
            // O laço precisa visitar condição/incremento/corpo para registrar usos de variáveis.
            if (declaracao.condicao) {
                this.marcarVariaveisUsadasEmExpressao(declaracao.condicao);
                await this.verificarCondicao(declaracao.condicao);
            }
            if (declaracao.incrementar) {
                this.marcarVariaveisUsadasEmExpressao(declaracao.incrementar);
                this.verificarExpressao(declaracao.incrementar);
            }
            for (const declaracaoCorpo of declaracao.corpo.declaracoes) {
                await declaracaoCorpo.aceitar(this);
            }
        }
        finally {
            this.gerenciadorEscopos.desempilharEscopo();
        }
        return Promise.resolve();
    }
    async visitarDeclaracaoParaCada(declaracao) {
        this.marcarVariaveisUsadasEmExpressao(declaracao.vetorOuDicionario);
        this.gerenciadorEscopos.empilharEscopo();
        try {
            if (declaracao.variavelIteracao instanceof construtos_1.Variavel) {
                const nome = declaracao.variavelIteracao.simbolo.lexema;
                this.gerenciadorEscopos.declarar(nome, {
                    nome,
                    tipo: 'qualquer',
                    imutavel: false,
                    valor: null,
                    inicializada: true,
                    usada: false,
                    hashArquivo: declaracao.hashArquivo,
                    linha: declaracao.linha,
                });
            }
            else if (declaracao.variavelIteracao instanceof construtos_1.Dupla) {
                const dupla = declaracao.variavelIteracao;
                for (const parte of [dupla.primeiro, dupla.segundo]) {
                    if (parte instanceof construtos_1.Variavel) {
                        const nome = parte.simbolo.lexema;
                        this.gerenciadorEscopos.declarar(nome, {
                            nome,
                            tipo: 'qualquer',
                            imutavel: false,
                            valor: null,
                            inicializada: true,
                            usada: false,
                            hashArquivo: declaracao.hashArquivo,
                            linha: declaracao.linha,
                        });
                    }
                }
            }
            if (declaracao.corpo) {
                await declaracao.corpo.aceitar(this);
            }
        }
        finally {
            this.gerenciadorEscopos.desempilharEscopo();
        }
        return Promise.resolve();
    }
    async visitarDeclaracaoSe(declaracao) {
        // Marca variáveis usadas na condição
        this.marcarVariaveisUsadasEmExpressao(declaracao.condicao);
        // Verifica a condição (incluindo validação de tipos para operadores lógicos)
        await this.verificarCondicao(declaracao.condicao);
        if (declaracao.caminhoEntao) {
            await declaracao.caminhoEntao.aceitar(this);
        }
        if (declaracao.caminhosSeSenao && declaracao.caminhosSeSenao.length > 0) {
            for (const caminhoSeSenao of declaracao.caminhosSeSenao) {
                this.marcarVariaveisUsadasEmExpressao(caminhoSeSenao.condicao);
                await this.verificarCondicao(caminhoSeSenao.condicao);
                await caminhoSeSenao.caminho.aceitar(this);
            }
        }
        if (declaracao.caminhoSenao) {
            await declaracao.caminhoSenao.aceitar(this);
        }
        return Promise.resolve();
    }
    /**
     * Verifica uma expressão recursivamente, incluindo operações binárias
     */
    verificarExpressao(expressao) {
        if (expressao instanceof construtos_1.Agrupamento) {
            this.verificarExpressao(expressao.expressao);
            return;
        }
        if (expressao instanceof construtos_1.Binario) {
            this.verificarBinario(expressao);
            return;
        }
        if (expressao instanceof construtos_1.Logico) {
            this.verificarLogico(expressao);
            return;
        }
        if (expressao instanceof construtos_1.Chamada) {
            this.verificarChamada(expressao);
            return;
        }
    }
    verificarCondicao(condicao) {
        if (condicao instanceof construtos_1.Agrupamento) {
            return this.verificarCondicao(condicao.expressao);
        }
        if (condicao instanceof construtos_1.Variavel) {
            return this.verificarVariavelBinaria(condicao);
        }
        if (condicao instanceof construtos_1.Binario) {
            return this.verificarBinario(condicao);
        }
        if (condicao instanceof construtos_1.Logico) {
            return this.verificarLogico(condicao);
        }
        if (condicao instanceof construtos_1.Chamada) {
            return this.verificarChamada(condicao);
        }
        return Promise.resolve();
    }
    verificarVariavelBinaria(variavel) {
        this.verificarVariavel(variavel);
        const variavelHipotetica = this.gerenciadorEscopos.buscar(variavel.simbolo.lexema);
        if (variavelHipotetica &&
            !(variavelHipotetica.valor instanceof construtos_1.Binario) &&
            typeof variavelHipotetica.valor !== 'boolean') {
            this.erro(variavel.simbolo, `Esperado tipo 'lógico' na condição do 'enquanto'.`);
        }
        return Promise.resolve();
    }
    verificarVariavel(variavel) {
        const variavelEscopo = this.gerenciadorEscopos.buscar(variavel.simbolo.lexema);
        if (!variavelEscopo) {
            if (this.funcoes[variavel.simbolo.lexema]) {
                return Promise.resolve();
            }
            this.erro(variavel.simbolo, `Variável '${variavel.simbolo.lexema}' ainda não foi declarada até este ponto.`);
            return Promise.resolve();
        }
        // Marca como usada
        this.gerenciadorEscopos.marcarComoUsada(variavel.simbolo.lexema);
        // Verifica se foi inicializada
        if (!variavelEscopo.inicializada) {
            this.aviso(variavel.simbolo, `Variável '${variavel.simbolo.lexema}' pode não ter sido inicializada antes do uso.`);
        }
        return Promise.resolve();
    }
    verificarBinario(binario) {
        this.verificarExistenciaConstruto(binario.direita);
        this.verificarExistenciaConstruto(binario.esquerda);
        this.verificarOperadorBinario(binario);
        return Promise.resolve();
    }
    verificarOperadorBinario(binario) {
        if (binario.esquerda instanceof construtos_1.Binario) {
            this.verificarOperadorBinario(binario.esquerda);
        }
        if (binario.direita instanceof construtos_1.Binario) {
            this.verificarOperadorBinario(binario.direita);
        }
        const operadoresMatematicos = ['ADICAO', 'SUBTRACAO', 'MULTIPLICACAO', 'DIVISAO', 'MODULO'];
        const operadoresComparacao = [
            'MAIOR',
            'MAIOR_IGUAL',
            'MENOR',
            'MENOR_IGUAL',
            'IGUAL',
            'DIFERENTE',
        ];
        if (operadoresMatematicos.includes(binario.operador.tipo)) {
            this.verificarTiposOperandos(binario);
        }
        if (operadoresComparacao.includes(binario.operador.tipo)) {
            this.verificarTiposComparacao(binario);
        }
        if (binario.operador.tipo === 'DIVISAO') {
            this.verificarDivisaoPorZero(binario);
        }
    }
    /**
     * Verifica se os tipos dos operandos são compatíveis
     */
    verificarTiposOperandos(binario) {
        const tipoEsquerda = this.obterTipoExpressao(binario.esquerda);
        const tipoDireita = this.obterTipoExpressao(binario.direita);
        const tiposNumericos = ['inteiro', 'número', 'real'];
        // Verifica se algum operando é do tipo texto em operação aritmética
        if (tipoEsquerda === 'texto' || tipoDireita === 'texto') {
            // Verifica se é uma operação que precisa de números (não concatenação)
            const operadoresAritmeticos = ['SUBTRACAO', 'MULTIPLICACAO', 'DIVISAO', 'MODULO'];
            if (operadoresAritmeticos.includes(binario.operador.tipo)) {
                const ladoProblematico = tipoEsquerda === 'texto' ? 'esquerdo' : 'direito';
                const expressaoProblematica = tipoEsquerda === 'texto' ? binario.esquerda : binario.direita;
                // Verifica se a expressão problemática é um Leia ou uma variável inicializada com Leia
                let mensagemAdicional = '';
                if (expressaoProblematica instanceof construtos_1.Leia) {
                    mensagemAdicional =
                        " Função 'leia()' retorna texto. Use 'inteiro(leia(...))' ou 'real(leia(...))' para converter.";
                }
                else if (expressaoProblematica instanceof construtos_1.Variavel) {
                    const variavel = this.gerenciadorEscopos.buscar(expressaoProblematica.simbolo.lexema);
                    if (variavel && variavel.valor instanceof construtos_1.Leia) {
                        mensagemAdicional =
                            " A variável foi inicializada com 'leia()' que retorna texto. Use 'inteiro(leia(...))' ou 'real(leia(...))' para converter.";
                    }
                    else {
                        mensagemAdicional =
                            " Use 'inteiro(...)' ou 'real(...)' para converter texto em número.";
                    }
                }
                this.erro(binario.operador, `Operação aritmética com tipo incompatível: operando ${ladoProblematico} é do tipo 'texto', mas a operação requer número.${mensagemAdicional}`);
                return;
            }
        }
        if (tipoEsquerda && tipoDireita && tipoEsquerda !== tipoDireita) {
            // Verificar se são tipos numéricos compatíveis
            const ambosNumericos = tiposNumericos.includes(tipoEsquerda) && tiposNumericos.includes(tipoDireita);
            if (!ambosNumericos) {
                this.aviso(binario.operador, `Operação entre tipos diferentes: tipo esquerdo '${tipoEsquerda}' e tipo direito '${tipoDireita}'. O resultado será resolvido implicitamente.`);
            }
        }
    }
    /**
     * Verifica se os tipos dos operandos em uma comparação são compatíveis
     */
    verificarTiposComparacao(binario) {
        const tipoEsquerda = this.obterTipoExpressao(binario.esquerda);
        const tipoDireita = this.obterTipoExpressao(binario.direita);
        const tiposNumericos = ['inteiro', 'número', 'real'];
        if (!tipoEsquerda || !tipoDireita) {
            return;
        }
        if ((tipoEsquerda === 'texto' && tiposNumericos.includes(tipoDireita)) ||
            (tiposNumericos.includes(tipoEsquerda) && tipoDireita === 'texto')) {
            this.aviso(binario.operador, `Esta comparação ocorre entre tipos ${tipoEsquerda} e ${tipoDireita}, e o resultado pode não ser o desejado.`);
        }
    }
    /**
     * Verifica divisão por zero recursivamente
     */
    verificarDivisaoPorZero(binario) {
        const valorDireita = this.avaliarExpressaoConstante(binario.direita);
        if (valorDireita === 0) {
            this.erro(binario.operador, `Divisão por zero.`);
        }
    }
    /**
     * Tenta avaliar uma expressão em tempo de compilação para detectar valores constantes
     * Retorna o valor se puder ser determinado, ou null caso contrário
     */
    avaliarExpressaoConstante(expressao) {
        if (expressao instanceof construtos_1.Literal) {
            return expressao.valor;
        }
        if (expressao instanceof construtos_1.Variavel) {
            const variavel = this.gerenciadorEscopos.buscar(expressao.simbolo.lexema);
            if (!variavel) {
                return null;
            }
            if (variavel.imutavel && variavel.inicializada) {
                return variavel.valor;
            }
            if (variavel.inicializada && variavel.valor !== undefined) {
                return variavel.valor;
            }
            return null;
        }
        if (expressao instanceof construtos_1.Binario) {
            const esquerda = this.avaliarExpressaoConstante(expressao.esquerda);
            const direita = this.avaliarExpressaoConstante(expressao.direita);
            if (esquerda !== null && direita !== null) {
                return this.calcularOperacaoBinaria(expressao.operador.tipo, esquerda, direita);
            }
        }
        if (expressao instanceof construtos_1.Agrupamento) {
            return this.avaliarExpressaoConstante(expressao.expressao);
        }
        return null;
    }
    /**
     * Calcula o resultado de uma operação binária em tempo de compilação
     */
    calcularOperacaoBinaria(operador, esquerda, direita) {
        try {
            switch (operador) {
                case 'ADICAO':
                    return esquerda + direita;
                case 'SUBTRACAO':
                    return esquerda - direita;
                case 'MULTIPLICACAO':
                    return esquerda * direita;
                case 'DIVISAO':
                    return esquerda / direita;
                case 'MODULO':
                    return esquerda % direita;
                case 'MAIOR':
                    return esquerda > direita;
                case 'MAIOR_IGUAL':
                    return esquerda >= direita;
                case 'MENOR':
                    return esquerda < direita;
                case 'MENOR_IGUAL':
                    return esquerda <= direita;
                case 'IGUAL':
                    return esquerda === direita;
                case 'DIFERENTE':
                    return esquerda !== direita;
                default:
                    return null;
            }
        }
        catch (e) {
            return null;
        }
    }
    verificarExistenciaConstruto(construto) {
        if (construto instanceof construtos_1.Variavel) {
            if (!this.gerenciadorEscopos.buscar(construto.simbolo.lexema)) {
                this.erro(construto.simbolo, `Variável ${construto.simbolo.lexema} ainda não foi declarada até este ponto.`);
                return;
            }
            this.gerenciadorEscopos.marcarComoUsada(construto.simbolo.lexema);
            return;
        }
        if (construto instanceof construtos_1.Binario) {
            this.verificarBinario(construto);
        }
    }
    verificarLogico(logico) {
        this.verificarLadoLogico(logico.direita);
        this.verificarLadoLogico(logico.esquerda);
        return Promise.resolve();
    }
    async verificarChamada(chamada) {
        switch (chamada.entidadeChamada.constructor) {
            case construtos_1.AcessoMetodoOuPropriedade:
                await chamada.entidadeChamada.aceitar(this);
                break;
            case construtos_1.Variavel:
                let entidadeChamadaVariavel = chamada.entidadeChamada;
                const nomeFuncao = entidadeChamadaVariavel.simbolo.lexema;
                // Lista de funções embutidas que não precisam ser declaradas
                const funcoesEmbutidas = [
                    'aleatorio',
                    'aleatorioEntre',
                    'algum',
                    'arredondar',
                    'clonar',
                    'encontrar',
                    'encontrarIndice',
                    'encontrarUltimo',
                    'encontrarUltimoIndice',
                    'escreva',
                    'filtrarPor',
                    'incluido',
                    'inteiro',
                    'intervalo',
                    'leia',
                    'longo',
                    'mapear',
                    'maximo',
                    'minimo',
                    'numero',
                    'número',
                    'ordenar',
                    'paraCada',
                    'primeiroEmCondicao',
                    'real',
                    'reduzir',
                    'somar',
                    'tamanho',
                    'texto',
                    'tipo',
                    'todos',
                    'todosEmCondicao',
                    'tupla',
                    'vetor',
                ];
                // Classes/construtores geralmente começam com letra maiúscula
                const pareceSerClasse = nomeFuncao[0] === nomeFuncao[0].toUpperCase();
                if (pareceSerClasse) {
                    this.gerenciadorEscopos.marcarComoUsada(nomeFuncao);
                    break;
                }
                // Só verifica se a função existe se não for embutidas e não parecer ser classe
                if (!funcoesEmbutidas.includes(nomeFuncao) &&
                    !this.funcoes[nomeFuncao] &&
                    !this.gerenciadorEscopos.buscar(nomeFuncao)) {
                    this.erro(entidadeChamadaVariavel.simbolo, `Chamada da função '${nomeFuncao}' não existe.`);
                }
                break;
        }
        return Promise.resolve();
    }
    verificarLadoLogico(lado) {
        if (lado instanceof construtos_1.Variavel) {
            const variavel = lado;
            const variavelEscopo = this.gerenciadorEscopos.buscar(variavel.simbolo.lexema);
            if (variavelEscopo) {
                // Verifica se a variável tem um tipo definido
                if (variavelEscopo.tipo &&
                    variavelEscopo.tipo !== 'lógico' &&
                    variavelEscopo.tipo !== 'qualquer') {
                    // Se a variável foi inicializada com um binário que resulta em 'lógico',
                    // consideramos que o lado é válido (caso como: var x = 5 > 2; enquanto (x) {...}).
                    if (variavelEscopo.valor instanceof construtos_1.Binario &&
                        this.inferirTipoBinario(variavelEscopo.valor) === 'lógico') {
                        return;
                    }
                    this.erro(variavel.simbolo, `Operador lógico requer operandos do tipo 'lógico', mas recebeu '${variavelEscopo.tipo}'.`);
                }
            }
        }
        if (lado instanceof construtos_1.Logico) {
            this.verificarLogico(lado);
        }
    }
    /**
     * Verifica interpolações de texto e marca variáveis como usadas,
     * compreendendo cada expressão interpolada com MicroLexador e MicroAvaliadorSintatico.
     */
    verificarInterpolacaoTexto(texto, literal) {
        const regexInterpolacao = /\$\{(.*?)\}/g;
        let match;
        while ((match = regexInterpolacao.exec(texto)) !== null) {
            const expressaoInterpolacao = match[1].trim();
            try {
                const retornoMicroLexador = this.microLexador.mapear(expressaoInterpolacao);
                const retornoMicro = this.microAvaliadorSintatico.analisar(retornoMicroLexador, literal.linha);
                for (const construto of retornoMicro.declaracoes) {
                    this.marcarVariaveisUsadasEmExpressao(construto);
                }
            }
            catch (_) {
                // Erros de sintaxe na interpolação são tratados em tempo de execução
            }
        }
    }
    async visitarDeclaracaoEscreva(declaracao) {
        if (declaracao.argumentos.length === 0) {
            const { linha, hashArquivo } = declaracao;
            const simbolo = {
                literal: '',
                tipo: '',
                lexema: 'escreva',
                linha,
                hashArquivo,
            };
            this.erro(simbolo, `É preciso ter um ou mais parametros para 'escreva(...)'`);
            return Promise.resolve();
        }
        for (const argumento of declaracao.argumentos) {
            this.marcarVariaveisUsadasEmExpressao(argumento);
            if (argumento instanceof construtos_1.Literal && argumento.tipo === 'texto') {
                this.verificarInterpolacaoTexto(String(argumento.valor), argumento);
            }
            if (argumento instanceof construtos_1.Variavel) {
                const possivelVariavel = this.gerenciadorEscopos.buscar(argumento.simbolo.lexema);
                const possivelFuncao = this.funcoes[argumento.simbolo.lexema];
                if (!possivelVariavel && !possivelFuncao) {
                    this.erro(argumento.simbolo, `Variável ou função '${argumento.simbolo.lexema}' não existe.`);
                    continue;
                }
                if (possivelVariavel && !possivelVariavel.inicializada) {
                    this.aviso(argumento.simbolo, `Variável '${argumento.simbolo.lexema}' não foi inicializada.`);
                }
            }
            else if (!(argumento instanceof construtos_1.Literal)) {
                // Para expressões complexas (ex: AcessoMetodoOuPropriedade), delega ao visitante
                await argumento.aceitar(this);
            }
        }
        return Promise.resolve();
    }
    visitarDeclaracaoConst(declaracao) {
        this.verificarTipoAtribuido(declaracao);
        if (declaracao.tipoExplicito && declaracao.tipo) {
            const tipoBase = declaracao.tipo.replace('[]', '');
            this.gerenciadorEscopos.marcarComoUsada(tipoBase);
        }
        if (declaracao.inicializador) {
            this.marcarVariaveisUsadasEmExpressao(declaracao.inicializador);
            // Verifica operações binárias no inicializador
            this.verificarExpressao(declaracao.inicializador);
        }
        const constanteCorrespondente = this.gerenciadorEscopos.buscarNoEscopoAtual(declaracao.simbolo.lexema);
        if (constanteCorrespondente) {
            this.erro(declaracao.simbolo, 'Declaração de constante já feita.');
            return Promise.resolve();
        }
        this.gerenciadorEscopos.declarar(declaracao.simbolo.lexema, {
            nome: declaracao.simbolo.lexema,
            tipo: declaracao.tipo || 'qualquer',
            imutavel: true,
            valor: declaracao.inicializador?.valor,
            inicializada: true,
            usada: false,
            hashArquivo: declaracao.simbolo.hashArquivo,
            linha: declaracao.simbolo.linha,
        });
        // TODO: Verificar inicializador.
        return Promise.resolve();
    }
    visitarDeclaracaoVar(declaracao) {
        this.verificarTipoAtribuido(declaracao);
        if (declaracao.tipoExplicito && declaracao.tipoOriginal) {
            const tipoBase = declaracao.tipoOriginal.replace('[]', '');
            this.gerenciadorEscopos.marcarComoUsada(tipoBase);
        }
        if (declaracao.inicializador) {
            this.marcarVariaveisUsadasEmExpressao(declaracao.inicializador);
            // Verifica operações binárias no inicializador
            this.verificarExpressao(declaracao.inicializador);
            switch (declaracao.inicializador.constructor) {
                case construtos_1.FuncaoConstruto:
                    const funcaoConstruto = declaracao.inicializador;
                    if (funcaoConstruto.parametros.length >= 255) {
                        this.erro(declaracao.simbolo, 'Função não pode ter mais de 255 parâmetros.');
                    }
                    // Valida tipo de retorno para funções anônimas atribuídas a variáveis
                    if (funcaoConstruto.tipo) {
                        const tipoRetornoFuncao = funcaoConstruto.tipo;
                        if (!['vazio', 'qualquer'].includes(tipoRetornoFuncao)) {
                            const todosOsCaminhosRetornam = this.todosOsCaminhosRetornam(funcaoConstruto.corpo);
                            if (!todosOsCaminhosRetornam) {
                                this.erro(declaracao.simbolo, `Função '${declaracao.simbolo.lexema}' deve retornar '${tipoRetornoFuncao}' em todos os caminhos de execução.`);
                            }
                            const funcaoContemRetorno = funcaoConstruto.corpo.find((c) => c instanceof declaracoes_1.Retorna);
                            if (funcaoContemRetorno && funcaoContemRetorno.valor) {
                                const tipoValor = typeof funcaoContemRetorno.valor.valor;
                                if (!['qualquer'].includes(tipoRetornoFuncao)) {
                                    if (tipoValor === 'string' && tipoRetornoFuncao !== 'texto') {
                                        this.erro(declaracao.simbolo, `Esperado retorno do tipo '${tipoRetornoFuncao}' dentro da função.`);
                                    }
                                    if (tipoValor === 'number' &&
                                        !['inteiro', 'real', 'número'].includes(tipoRetornoFuncao)) {
                                        this.erro(declaracao.simbolo, `Esperado retorno do tipo '${tipoRetornoFuncao}' dentro da função.`);
                                    }
                                }
                            }
                        }
                    }
                    break;
            }
        }
        let valorInicializador = undefined;
        if (declaracao.inicializador) {
            if (declaracao.inicializador.hasOwnProperty('valor')) {
                valorInicializador = declaracao.inicializador.valor;
            }
            else {
                valorInicializador = declaracao.inicializador;
            }
        }
        // Inferir tipo do inicializador se não foi especificado explicitamente
        let tipoInferido = declaracao.tipo;
        if (!tipoInferido && declaracao.inicializador) {
            const tipoInicializador = this.obterTipoExpressao(declaracao.inicializador);
            if (tipoInicializador) {
                tipoInferido = tipoInicializador;
            }
        }
        // Sugestão de tipo melhor quando 'qualquer' é usado explicitamente
        if (declaracao.tipoExplicito &&
            declaracao.tipoOriginal === 'qualquer' &&
            declaracao.inicializador) {
            const tipoMelhor = this.obterTipoExpressao(declaracao.inicializador);
            if (tipoMelhor && tipoMelhor !== 'qualquer') {
                this.sugestao(declaracao.simbolo, 'Um tipo melhor pode ser inferido.', [
                    {
                        titulo: `Alterar tipo para '${tipoMelhor}'`,
                        textoOriginal: 'qualquer',
                        textoSubstituto: tipoMelhor,
                        linha: declaracao.simbolo.linha,
                        colunaInicio: declaracao.simbolo.colunaInicio || 0,
                        colunaFim: declaracao.simbolo.colunaFim || 0,
                    },
                ]);
            }
        }
        const variavel = {
            nome: declaracao.simbolo.lexema,
            tipo: tipoInferido || 'qualquer',
            imutavel: false,
            valor: valorInicializador,
            inicializada: declaracao.inicializador !== null && declaracao.inicializador !== undefined,
            usada: false,
            hashArquivo: declaracao.simbolo.hashArquivo,
            linha: declaracao.simbolo.linha,
        };
        const declaradaComSucesso = this.gerenciadorEscopos.declarar(declaracao.simbolo.lexema, variavel);
        if (!declaradaComSucesso) {
            const variavelExistente = this.gerenciadorEscopos.buscarNoEscopoAtual(declaracao.simbolo.lexema);
            this.aviso(declaracao.simbolo, `Variável '${declaracao.simbolo.lexema}' já foi declarada na linha ${variavelExistente?.linha}.`);
        }
        return Promise.resolve();
    }
    visitarExpressaoFormatacaoEscrita(expressao) {
        if (expressao.expressao) {
            /* for (const expressao of expressao.expressoes) {
                if (expressao instanceof Variavel) {
                    this.gerenciadorEscopos.marcarComoUsada(expressao.simbolo.lexema);
                }
                // Recursively check for variables in more complex expressions
                this.marcarVariaveisUsadasEmExpressao(expressao);
            } */
        }
        return Promise.resolve();
    }
    visitarExpressaoRetornar(declaracao) {
        if (declaracao.valor) {
            this.marcarVariaveisUsadasEmExpressao(declaracao.valor);
        }
        return Promise.resolve(undefined);
    }
    visitarExpressaoDeVariavel(expressao) {
        if (expressao instanceof construtos_1.Variavel) {
            return this.verificarVariavel(expressao);
        }
        return Promise.resolve();
    }
    obterTipoExpressao(expressao) {
        const tipoBase = super.obterTipoExpressao(expressao);
        if (tipoBase)
            return tipoBase;
        if (expressao instanceof construtos_1.Chamada && expressao.entidadeChamada instanceof construtos_1.Variavel) {
            const nomeCallee = expressao.entidadeChamada.simbolo.lexema;
            if (this.classesRegistradas.has(nomeCallee))
                return nomeCallee;
        }
        return null;
    }
    resolverTipoObjeto(objeto) {
        if (objeto instanceof construtos_1.Variavel) {
            if (objeto.simbolo.lexema === 'isto' && this.classeAtualEmAnalise) {
                return this.classeAtualEmAnalise.simbolo.lexema;
            }
            return this.gerenciadorEscopos.buscar(objeto.simbolo.lexema)?.tipo ?? null;
        }
        if (objeto instanceof construtos_1.Chamada && objeto.entidadeChamada instanceof construtos_1.Variavel) {
            const nomeCallee = objeto.entidadeChamada.simbolo.lexema;
            if (this.classesRegistradas.has(nomeCallee))
                return nomeCallee;
        }
        return null;
    }
    estaEmClasseOuSubclasse(nomeClasse) {
        const visitados = new Set();
        const pilha = [this.classeAtualEmAnalise];
        while (pilha.length > 0) {
            const atual = pilha.pop();
            if (!atual)
                continue;
            if (visitados.has(atual.simbolo.lexema))
                continue;
            visitados.add(atual.simbolo.lexema);
            if (atual.simbolo.lexema === nomeClasse)
                return true;
            for (const sc of atual.superClasses) {
                const pai = this.classesRegistradas.get(sc.simbolo.lexema) ?? null;
                if (pai)
                    pilha.push(pai);
            }
        }
        return false;
    }
    async visitarExpressaoAcessoMetodoOuPropriedade(expressao) {
        const nomeMembro = expressao.simbolo.lexema;
        const tipoObjeto = this.resolverTipoObjeto(expressao.objeto);
        if (!tipoObjeto)
            return;
        const classeDef = this.classesRegistradas.get(tipoObjeto);
        if (!classeDef)
            return;
        // Busca o membro na classe e em toda a hierarquia de superclasses
        const encontrarMembroNaHierarquia = (classe) => {
            const encontrado = classe.metodos.find((m) => m.simbolo.lexema === nomeMembro) ??
                classe.propriedades.find((p) => p.nome.lexema === nomeMembro);
            if (encontrado)
                return encontrado;
            for (const sc of classe.superClasses) {
                const pai = this.classesRegistradas.get(sc.simbolo.lexema);
                if (pai) {
                    const membroPai = encontrarMembroNaHierarquia(pai);
                    if (membroPai)
                        return membroPai;
                }
            }
            return undefined;
        };
        const membro = encontrarMembroNaHierarquia(classeDef);
        if (!membro) {
            const mensagemErro = `Método não encontrado na classe '${tipoObjeto}': ${nomeMembro}.`;
            this.erro(expressao.simbolo, mensagemErro, 'SEMANTICO_METODO_NAO_ENCONTRADO');
            const diagnostico = this.diagnosticos.find((d) => d.linha === expressao.simbolo.linha && d.mensagem === mensagemErro);
            if (diagnostico) {
                diagnostico.correcaoMetodo = {
                    tipo: 'implementar-metodo',
                    nomeClasse: tipoObjeto,
                    nomeMetodo: nomeMembro,
                    linhaDeclaracaoClasse: classeDef.linha,
                    hashArquivoClasse: classeDef.hashArquivo,
                };
            }
            return;
        }
        if (membro.acesso === 'privado') {
            if (this.classeAtualEmAnalise?.simbolo.lexema !== tipoObjeto) {
                this.erro(expressao.simbolo, `Membro '${nomeMembro}' é privado e não pode ser acessado fora da classe '${tipoObjeto}'.`);
            }
        }
        else if (membro.acesso === 'protegido') {
            if (!this.estaEmClasseOuSubclasse(tipoObjeto)) {
                this.erro(expressao.simbolo, `Membro '${nomeMembro}' é protegido e não pode ser acessado fora da hierarquia da classe '${tipoObjeto}'.`);
            }
        }
    }
    async visitarDeclaracaoClasse(declaracao) {
        for (const superClasseVariavel of declaracao.superClasses) {
            const nomeSuperclasse = superClasseVariavel.simbolo.lexema;
            if (nomeSuperclasse === declaracao.simbolo.lexema) {
                this.erro(superClasseVariavel.simbolo, `A classe '${declaracao.simbolo.lexema}' não pode herdar de si mesma.`);
            }
            else if (!this.classesDeclaradas.has(nomeSuperclasse) &&
                !this.classesRegistradas.has(nomeSuperclasse)) {
                this.erro(superClasseVariavel.simbolo, `Superclasse '${nomeSuperclasse}' não foi declarada.`);
            }
        }
        this.classesDeclaradas.add(declaracao.simbolo.lexema);
        this.classesRegistradas.set(declaracao.simbolo.lexema, declaracao);
        // Marca tipos usados em anotações de propriedades e parâmetros de métodos como usados.
        for (const propriedade of declaracao.propriedades) {
            const tipoBase = propriedade.tipo?.replace('[]', '');
            if (tipoBase)
                this.gerenciadorEscopos.marcarComoUsada(tipoBase);
        }
        for (const metodo of declaracao.metodos) {
            const tipoRetorno = metodo.funcao.tipo?.replace('[]', '');
            if (tipoRetorno)
                this.gerenciadorEscopos.marcarComoUsada(tipoRetorno);
            for (const parametro of metodo.funcao.parametros) {
                const tipoBase = parametro.tipoDado?.replace('[]', '');
                if (tipoBase)
                    this.gerenciadorEscopos.marcarComoUsada(tipoBase);
            }
        }
        // Visita corpos dos métodos com contexto de classe ativo.
        // Métodos abstratos implícitos e métodos de classes estrangeiras têm corpo vazio —
        // não há declarações para visitar.
        const classeAnterior = this.classeAtualEmAnalise;
        this.classeAtualEmAnalise = declaracao;
        if (!declaracao.estrangeira) {
            for (const metodo of declaracao.metodos) {
                if (metodo.abstrato)
                    continue;
                const tipoRetornoMetodo = metodo.funcao.tipo;
                if (tipoRetornoMetodo &&
                    !['vazio', 'qualquer'].includes(tipoRetornoMetodo) &&
                    metodo.funcao.corpo.length === 0) {
                    this.aviso(metodo.simbolo, `Método especifica tipo de retorno '${tipoRetornoMetodo}', mas não há qualquer retorno correspondente no corpo do método.`);
                }
                this.gerenciadorEscopos.empilharEscopo();
                try {
                    for (const parametro of metodo.funcao.parametros) {
                        this.gerenciadorEscopos.declarar(parametro.nome.lexema, {
                            nome: parametro.nome.lexema,
                            tipo: parametro.tipoDado || 'qualquer',
                            imutavel: false,
                            valor: undefined,
                            inicializada: true,
                            usada: true,
                            hashArquivo: parametro.nome.hashArquivo,
                            linha: parametro.nome.linha,
                        });
                    }
                    for (const declaracao of metodo.funcao.corpo) {
                        await declaracao.aceitar(this);
                    }
                }
                finally {
                    this.gerenciadorEscopos.desempilharEscopo();
                }
            }
        }
        this.classeAtualEmAnalise = classeAnterior;
    }
    async visitarDeclaracaoDefinicaoFuncao(declaracao) {
        if (declaracao.funcao.tipo === undefined) {
            this.erro(declaracao.simbolo, `Declaração de retorno da função é inválido.`);
        }
        if (declaracao.funcao.parametros.length >= 255) {
            this.erro(declaracao.simbolo, 'Função não pode ter mais de 255 parâmetros.');
        }
        let tipoRetornoFuncao = declaracao.funcao.tipo;
        if (tipoRetornoFuncao) {
            this.gerenciadorEscopos.marcarComoUsada(tipoRetornoFuncao);
            if (!['vazio', 'qualquer'].includes(tipoRetornoFuncao)) {
                if (declaracao.funcao.corpo.length === 0) {
                    this.aviso(declaracao.simbolo, `Método especifica tipo de retorno '${tipoRetornoFuncao}', mas não há qualquer retorno correspondente no corpo do método.`);
                }
                else {
                    const todosOsCaminhosRetornam = this.todosOsCaminhosRetornam(declaracao.funcao.corpo);
                    if (!todosOsCaminhosRetornam) {
                        this.erro(declaracao.simbolo, `Função '${declaracao.simbolo.lexema}' deve retornar '${tipoRetornoFuncao}' em todos os caminhos de execução.`);
                    }
                }
            }
            let retornos = [];
            for (const declaracaoCorpo of declaracao.funcao.corpo) {
                retornos = retornos.concat((0, comum_1.buscarRetornos)(declaracaoCorpo));
            }
            // Filtra retornos com tipo 'qualquer' (não determinado em tempo de análise sintática)
            const retornosComTipoIndeterminado = retornos.filter((retorno) => retorno.valor !== null &&
                retorno.valor !== undefined &&
                retorno.tipo === 'qualquer');
            // Se a função é 'vazio' e há retornos com tipo indeterminado,
            // tenta inferir o tipo e fornece mensagem útil ao desenvolvedor
            if (tipoRetornoFuncao === 'vazio' &&
                declaracao.funcao.tipoExplicito &&
                retornosComTipoIndeterminado.length > 0) {
                const retornoComValor = retornosComTipoIndeterminado[0];
                const valorRetorno = retornoComValor.valor;
                const tipoInferido = this.obterTipoExpressao(valorRetorno);
                if (tipoInferido && tipoInferido !== 'qualquer') {
                    this.erro(declaracao.simbolo, `A função não pode ter nenhum tipo de retorno. Tipo inferido do retorno: '${tipoInferido}'.`);
                }
                else {
                    this.erro(declaracao.simbolo, `A função não pode ter nenhum tipo de retorno.`);
                }
            }
            else {
                // Verifica tipos de retorno para funções não-vazio
                const retornoComValor = retornos.find((retorno) => retorno.valor !== null && retorno.valor !== undefined);
                if (retornoComValor) {
                    const tipoValor = typeof retornoComValor.valor?.valor;
                    if (!['qualquer'].includes(tipoRetornoFuncao)) {
                        if (tipoValor === 'string' && tipoRetornoFuncao !== 'texto') {
                            this.erro(declaracao.simbolo, `Esperado retorno do tipo '${tipoRetornoFuncao}' dentro da função.`);
                        }
                        if (tipoValor === 'number' &&
                            !['inteiro', 'real', 'número'].includes(tipoRetornoFuncao)) {
                            this.erro(declaracao.simbolo, `Esperado retorno do tipo '${tipoRetornoFuncao}' dentro da função.`);
                        }
                    }
                }
            }
        }
        this.funcoes[declaracao.simbolo.lexema] = {
            valor: declaracao.funcao,
        };
        this.gerenciadorEscopos.empilharEscopo();
        try {
            for (const parametro of declaracao.funcao.parametros) {
                this.gerenciadorEscopos.declarar(parametro.nome.lexema, {
                    nome: parametro.nome.lexema,
                    tipo: parametro.tipoDado || 'qualquer',
                    imutavel: false,
                    valor: undefined,
                    inicializada: true,
                    usada: false,
                    hashArquivo: parametro.nome.hashArquivo,
                    linha: parametro.nome.linha,
                });
            }
            for (const declaracaoCorpo of declaracao.funcao.corpo) {
                await declaracaoCorpo.aceitar(this);
            }
        }
        finally {
            this.gerenciadorEscopos.desempilharEscopo();
        }
        return Promise.resolve();
    }
    verificarVariaveisNaoUsadas() {
        const naoUsadas = this.gerenciadorEscopos.obterVariaveisNaoUsadas();
        for (let variavel of naoUsadas) {
            // Verifica se já existe um erro associado à variável.
            const temErro = this.diagnosticos.some((d) => d.severidade === erros_1.DiagnosticoSeveridade.ERRO &&
                d.simbolo?.lexema === variavel.nome);
            // Se a variável já tem um erro associado, não emitir aviso de não usada.
            if (temErro) {
                continue;
            }
            this.aviso({
                lexema: variavel.nome,
                linha: variavel.linha,
                tipo: variavel.tipo,
                hashArquivo: variavel.hashArquivo,
            }, `Variável '${variavel.nome}' foi declarada mas nunca usada.`);
        }
    }
    async analisar(declaracoes) {
        this.gerenciadorEscopos = new gerenciador_escopos_1.GerenciadorEscopos();
        this.classesDeclaradas = new Set(this.classesExternasRegistradas.keys());
        this.classesRegistradas = new Map(this.classesExternasRegistradas);
        this.classeAtualEmAnalise = null;
        this.atual = 0;
        this.diagnosticos = [];
        try {
            while (this.atual < declaracoes.length) {
                await declaracoes[this.atual].aceitar(this);
                this.atual++;
            }
        }
        catch (erro) {
            console.error('Erro durante análise semântica:', erro);
        }
        // Verifica variáveis não usadas ao final
        this.verificarVariaveisNaoUsadas();
        return {
            diagnosticos: this.diagnosticos,
        };
    }
}
exports.AnalisadorSemantico = AnalisadorSemantico;
//# sourceMappingURL=analisador-semantico.js.map