"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Interpretador = void 0;
const construtos_1 = require("../construtos");
const estruturas_1 = require("./estruturas");
const interpretador_base_1 = require("./interpretador-base");
const inferenciador_1 = require("../inferenciador");
const excecoes_1 = require("../excecoes");
const declaracoes_1 = require("../declaracoes");
const quebras_1 = require("../quebras");
const montao_1 = require("./montao");
const comum_1 = require("./comum");
const primitivas_dicionario_1 = __importDefault(require("../bibliotecas/primitivas-dicionario"));
const primitivas_numero_1 = __importDefault(require("../bibliotecas/primitivas-numero"));
const primitivas_texto_1 = __importDefault(require("../bibliotecas/primitivas-texto"));
const primitivas_vetor_1 = __importDefault(require("../bibliotecas/primitivas-vetor"));
const primitivas_tupla_1 = __importDefault(require("../bibliotecas/dialetos/pitugues/primitivas-tupla"));
const primitivos_1 = __importDefault(require("../tipos-de-dados/primitivos"));
const delegua_1 = __importDefault(require("../tipos-de-dados/delegua"));
const delegua_2 = __importDefault(require("../tipos-de-simbolos/delegua"));
/**
 * O interpretador de Delégua, usado também por Pituguês usando herança.
 * @see InterpretadorPitugues
 */
class Interpretador extends interpretador_base_1.InterpretadorBase {
    constructor(diretorioBase, performance = false, funcaoDeRetorno = null, funcaoDeRetornoMesmaLinha = null) {
        super(diretorioBase, performance, funcaoDeRetorno, funcaoDeRetornoMesmaLinha);
        this.montao = new montao_1.Montao();
        this.pontoInicializacaoBibliotecasGlobais();
    }
    /**
     * Cada dialeto que deriva deste interpretador conhece este ponto de inicialização.
     * A partir daqui, cada dialeto pode carregar as bibliotecas globais específicas do seu dialeto.
     */
    pontoInicializacaoBibliotecasGlobais() {
        (0, comum_1.carregarBibliotecasGlobais)(this.pilhaEscoposExecucao);
    }
    resolverReferenciaMontao(referenciaMontao) {
        const valorMontao = this.montao.obterReferencia(this.hashArquivoDeclaracaoAtual, this.linhaDeclaracaoAtual, referenciaMontao.endereco);
        return valorMontao;
    }
    resolverValor(objeto, referencia = false) {
        if (objeto === null || objeto === undefined) {
            return objeto;
        }
        if (Array.isArray(objeto)) {
            return objeto;
        }
        if (objeto instanceof estruturas_1.ReferenciaMontao) {
            return this.resolverReferenciaMontao(objeto);
        }
        if (objeto instanceof quebras_1.RetornoQuebra) {
            return this.resolverValor(objeto.valor);
        }
        if (objeto.hasOwnProperty) {
            if (objeto.hasOwnProperty('valorRetornado')) {
                return this.resolverValor(objeto.valorRetornado);
            }
            if (objeto.hasOwnProperty('valor')) {
                if (Array.isArray(objeto.valor)) {
                    return this.resolverValor(objeto.valor);
                }
                if (objeto.valor instanceof estruturas_1.ReferenciaMontao) {
                    return this.resolverReferenciaMontao(objeto.valor);
                }
                return objeto.valor;
            }
        }
        return objeto;
    }
    serializarSemEspacos(objeto) {
        return JSON
            .stringify(objeto)
            .replace(/,\s+/g, ',')
            .replace(/:\s+/g, ':');
    }
    paraTexto(objeto) {
        if (objeto === null || objeto === undefined)
            return delegua_1.default.NULO;
        if (typeof objeto === primitivos_1.default.BOOLEANO) {
            return objeto ? 'verdadeiro' : 'falso';
        }
        if (objeto.valor instanceof estruturas_1.ObjetoPadrao)
            return objeto.valor.paraTexto();
        if (objeto instanceof construtos_1.Literal || objeto instanceof construtos_1.Tupla)
            return objeto.paraTextoSaida();
        if (objeto instanceof estruturas_1.ObjetoDeleguaClasse || objeto instanceof estruturas_1.DeleguaFuncao)
            return objeto.paraTexto();
        if (objeto instanceof quebras_1.RetornoQuebra) {
            if (typeof objeto.valor === 'boolean')
                return objeto.valor ? 'verdadeiro' : 'falso';
        }
        if (objeto instanceof estruturas_1.MetodoPrimitiva) {
            return objeto.paraTexto();
        }
        if (objeto instanceof Date) {
            const formato = Intl.DateTimeFormat('pt', {
                dateStyle: 'full',
                timeStyle: 'full',
            });
            return formato.format(objeto);
        }
        if (Array.isArray(objeto)) {
            let retornoVetor = '[';
            for (let elemento of objeto) {
                // Resolve referências ao montão antes de processar
                if (elemento instanceof estruturas_1.ReferenciaMontao) {
                    elemento = this.resolverValor(elemento);
                }
                if (elemento instanceof construtos_1.Tupla) {
                    retornoVetor += elemento.paraTextoSaida() + ', ';
                    continue;
                }
                // Se o elemento é um array (incluindo arrays resolvidos de referências),
                // chama paraTexto recursivamente para processá-lo corretamente
                if (Array.isArray(elemento)) {
                    retornoVetor += this.paraTexto(elemento) + ', ';
                    continue;
                }
                if (typeof elemento === 'object') {
                    retornoVetor += `${this.serializarSemEspacos(elemento)}, `;
                    continue;
                }
                retornoVetor +=
                    typeof elemento === 'string'
                        ? `'${elemento}', `
                        : `${this.paraTexto(elemento)}, `;
            }
            if (retornoVetor.length > 1) {
                retornoVetor = retornoVetor.slice(0, -2);
            }
            retornoVetor += ']';
            return retornoVetor;
        }
        if (typeof objeto === primitivos_1.default.OBJETO) {
            const objetoEscrita = {};
            for (const propriedade in objeto) {
                let valor = objeto[propriedade];
                if (typeof valor === primitivos_1.default.BOOLEANO) {
                    valor = valor ? 'verdadeiro' : 'falso';
                }
                if (valor instanceof estruturas_1.ReferenciaMontao ||
                    ((valor === null || valor === void 0 ? void 0 : valor.hasOwnProperty) && (valor === null || valor === void 0 ? void 0 : valor.hasOwnProperty('tipo')))) {
                    valor = this.resolverValor(valor);
                }
                objetoEscrita[propriedade] = valor;
            }
            return JSON.stringify(objetoEscrita);
        }
        switch (objeto.constructor) {
            case Object:
                if ('tipo' in objeto) {
                    switch (objeto.tipo) {
                        case 'dicionário':
                            return JSON.stringify(objeto.valor);
                        default:
                            return objeto.valor;
                    }
                }
        }
        return objeto.toString();
    }
    async avaliacaoDeclaracaoVarOuConst(declaracao) {
        let valorOuOutraVariavel = null;
        if (declaracao.inicializador !== null) {
            valorOuOutraVariavel = await this.avaliar(declaracao.inicializador);
        }
        let valorFinal = null;
        if (valorOuOutraVariavel !== null && valorOuOutraVariavel !== undefined) {
            valorFinal = this.resolverValor(valorOuOutraVariavel);
        }
        return valorFinal;
    }
    /**
     * Declaração de ajuda.
     * Neste interpretador básico, a ajuda apenas retorna texto sobre um determinado
     * tópico, ou ainda sobre a ajuda em si.
     * Outros ambientes implementam mecanismos mais sofisticados, como o modo de ajuda.
     * @param declaracao A declaração de ajuda.
     */
    async visitarDeclaracaoAjuda(declaracao) {
        return Promise.resolve((0, comum_1.pontoEntradaAjuda)(declaracao.funcao, declaracao.elemento));
    }
    visitarDeclaracaoDefinicaoFuncao(declaracao) {
        const funcao = new estruturas_1.DeleguaFuncao(declaracao.simbolo.lexema, declaracao.funcao);
        // TODO: Depreciar essa abordagem a favor do uso por referências?
        this.pilhaEscoposExecucao.definirVariavel(declaracao.simbolo.lexema, funcao);
        this.pilhaEscoposExecucao.registrarReferenciaFuncao(declaracao.id, funcao);
        return Promise.resolve({
            tipo: `função<${funcao.declaracao.tipo || 'qualquer'}>`,
            tipoExplicito: funcao.declaracao.tipoExplicito,
            declaracao: funcao,
        });
    }
    async logicaComumExecucaoEnquanto(enquanto, acumularRetornos) {
        let retornoExecucao;
        const retornos = [];
        while ((acumularRetornos ||
            !(retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.Quebra)) &&
            this.eVerdadeiro(await this.avaliar(enquanto.condicao))) {
            try {
                retornoExecucao = await this.executar(enquanto.corpo);
                if (retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.SustarQuebra) {
                    if (acumularRetornos) {
                        return {
                            valorRetornado: retornos,
                            tipo: 'vetor',
                        };
                    }
                    return null;
                }
                if (retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.ContinuarQuebra) {
                    retornoExecucao = null;
                }
                if (acumularRetornos) {
                    retornos.push(this.resolverValor(retornoExecucao));
                }
            }
            catch (erro) {
                this.erros.push({
                    erroInterno: erro,
                    linha: enquanto.linha,
                    hashArquivo: enquanto.hashArquivo,
                });
                return Promise.reject(erro);
            }
        }
        if (acumularRetornos) {
            return {
                valorRetornado: retornos,
                tipo: 'vetor',
            };
        }
        return retornoExecucao;
    }
    async visitarDeclaracaoEnquanto(declaracao) {
        return this.logicaComumExecucaoEnquanto(declaracao, false);
    }
    async logicaComumExecucaoFazer(fazer, acumularRetornos) {
        let retornoExecucao;
        const retornos = [];
        do {
            try {
                retornoExecucao = await this.executar(fazer.caminhoFazer);
                if (retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.SustarQuebra) {
                    if (acumularRetornos) {
                        return {
                            valorRetornado: retornos,
                            tipo: 'vetor',
                        };
                    }
                    return null;
                }
                if (retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.ContinuarQuebra) {
                    retornoExecucao = null;
                }
                if (acumularRetornos) {
                    retornos.push(this.resolverValor(retornoExecucao));
                }
            }
            catch (erro) {
                this.erros.push({
                    erroInterno: erro,
                    linha: fazer.linha,
                    hashArquivo: fazer.hashArquivo,
                });
                return Promise.reject(erro);
            }
        } while ((acumularRetornos ||
            !(retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.Quebra)) &&
            this.eVerdadeiro(await this.avaliar(fazer.condicaoEnquanto)));
        if (acumularRetornos) {
            return {
                valorRetornado: retornos,
                tipo: 'vetor',
            };
        }
    }
    async visitarDeclaracaoFazer(declaracao) {
        return this.logicaComumExecucaoFazer(declaracao, false);
    }
    async logicaComumExecucaoPara(para, acumularRetornos) {
        const declaracaoInicializador = Array.isArray(para.inicializador)
            ? para.inicializador[0]
            : para.inicializador;
        if (declaracaoInicializador !== null) {
            await this.avaliar(declaracaoInicializador);
        }
        let retornoExecucao;
        const retornos = [];
        while (acumularRetornos ||
            !(retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.Quebra)) {
            if (para.condicao !== null && !this.eVerdadeiro(await this.avaliar(para.condicao))) {
                break;
            }
            retornoExecucao = await this.executar(para.corpo);
            if (retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.SustarQuebra) {
                if (acumularRetornos) {
                    return {
                        valorRetornado: retornos,
                        tipo: 'vetor',
                    };
                }
                return null;
            }
            if (retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.ContinuarQuebra) {
                retornoExecucao = null;
            }
            if (acumularRetornos) {
                retornos.push(this.resolverValor(retornoExecucao));
            }
            if (para.incrementar !== null) {
                await this.avaliar(para.incrementar);
            }
        }
        if (acumularRetornos) {
            return {
                valorRetornado: retornos,
                tipo: 'vetor',
            };
        }
        return retornoExecucao;
    }
    async visitarDeclaracaoPara(declaracao) {
        return this.logicaComumExecucaoPara(declaracao, false);
    }
    async logicaComumExecucaoParaCada(paraCada, acumularRetornos) {
        let retornoExecucao;
        // Posição atual precisa ser reiniciada, pois pode estar dentro de outro
        // laço de repetição.
        paraCada.posicaoAtual = 0;
        const vetorOuDicionarioResolvido = await this.avaliar(paraCada.vetorOuDicionario);
        let valorVetorOuDicionarioResolvido = this.resolverValor(vetorOuDicionarioResolvido);
        // Se até aqui vetor resolvido é um dicionário, converte dicionário
        // para vetor de duplas.
        if (paraCada.vetorOuDicionario.tipo === 'dicionário') {
            valorVetorOuDicionarioResolvido = Object.entries(valorVetorOuDicionarioResolvido).map((v) => new construtos_1.Dupla(new construtos_1.Literal(paraCada.hashArquivo, paraCada.linha, v[0], 'texto'), new construtos_1.Literal(paraCada.hashArquivo, paraCada.linha, v[1], (0, inferenciador_1.inferirTipoVariavel)(v[1]))));
        }
        if (paraCada.vetorOuDicionario.tipo === 'texto' ||
            typeof valorVetorOuDicionarioResolvido === 'string') {
            valorVetorOuDicionarioResolvido = valorVetorOuDicionarioResolvido.split('');
        }
        if (!Array.isArray(valorVetorOuDicionarioResolvido)) {
            return Promise.reject("Variável ou literal provida em instrução 'para cada' não é um vetor.");
        }
        const retornos = [];
        while ((acumularRetornos ||
            !(retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.Quebra)) &&
            paraCada.posicaoAtual < valorVetorOuDicionarioResolvido.length) {
            try {
                if (paraCada.variavelIteracao instanceof construtos_1.Variavel) {
                    this.pilhaEscoposExecucao.definirVariavel(paraCada.variavelIteracao.simbolo.lexema, valorVetorOuDicionarioResolvido[paraCada.posicaoAtual]);
                }
                if (paraCada.variavelIteracao instanceof construtos_1.Dupla) {
                    const valorComoDupla = valorVetorOuDicionarioResolvido[paraCada.posicaoAtual];
                    const nomesVariaveis = await Promise.all([
                        this.avaliar(paraCada.variavelIteracao.primeiro),
                        this.avaliar(paraCada.variavelIteracao.segundo)
                    ]);
                    const valoresDupla = await Promise.all([
                        this.avaliar(valorComoDupla.primeiro),
                        this.avaliar(valorComoDupla.segundo)
                    ]);
                    // nomesVariaveis são strings (nomes das variáveis)
                    this.pilhaEscoposExecucao.definirVariavel(String(nomesVariaveis[0]), valoresDupla[0]);
                    this.pilhaEscoposExecucao.definirVariavel(String(nomesVariaveis[1]), valoresDupla[1]);
                }
                retornoExecucao = await this.executar(paraCada.corpo);
                if (retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.SustarQuebra) {
                    if (acumularRetornos) {
                        return {
                            valorRetornado: retornos,
                            tipo: 'vetor',
                        };
                    }
                    return null;
                }
                if (retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.ContinuarQuebra) {
                    retornoExecucao = null;
                }
                if (acumularRetornos) {
                    retornos.push(this.resolverValor(retornoExecucao));
                }
                paraCada.posicaoAtual++;
            }
            catch (erro) {
                this.erros.push({
                    erroInterno: erro,
                    linha: paraCada.linha,
                    hashArquivo: paraCada.hashArquivo,
                });
                return Promise.reject(erro);
            }
        }
        if (acumularRetornos) {
            return {
                valorRetornado: retornos,
                tipo: 'vetor',
            };
        }
        return retornoExecucao;
    }
    async visitarDeclaracaoParaCada(declaracao) {
        return this.logicaComumExecucaoParaCada(declaracao, false);
    }
    async visitarDeclaracaoTendoComo(declaracao) {
        const retornoInicializacao = await this.avaliar(declaracao.inicializacaoVariavel);
        const retornoInicializacaoResolvido = this.resolverValor(retornoInicializacao);
        this.pilhaEscoposExecucao.definirConstante(declaracao.simboloVariavel.lexema, retornoInicializacaoResolvido);
        await this.executar(declaracao.corpo);
        if (retornoInicializacao instanceof estruturas_1.ObjetoDeleguaClasse) {
            const metodoFinalizar = retornoInicializacaoResolvido.classe.metodos['finalizar'];
            if (metodoFinalizar) {
                const chamavel = metodoFinalizar.funcaoPorMetodoDeClasse(retornoInicializacaoResolvido);
                chamavel.chamar(this, []);
            }
        }
        return null;
    }
    async visitarExpressaoAcessoIndiceVariavel(expressao) {
        const promises = await Promise.all([
            this.avaliar(expressao.entidadeChamada),
            this.avaliar(expressao.indice),
        ]);
        const variavelObjeto = promises[0];
        const indice = promises[1];
        const objeto = this.resolverValor(variavelObjeto);
        let valorIndice = this.resolverValor(indice);
        if (Array.isArray(objeto)) {
            if (!Number.isInteger(valorIndice)) {
                return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.simboloFechamento, 'Somente inteiros podem ser usados para indexar um vetor.', expressao.linha));
            }
            if (valorIndice < 0 && objeto.length !== 0) {
                valorIndice += objeto.length;
            }
            if (valorIndice >= objeto.length || valorIndice < 0) {
                return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.simboloFechamento, 'Índice do vetor fora do intervalo.', expressao.linha));
            }
            return objeto[valorIndice];
        }
        if (objeto instanceof construtos_1.TuplaN || objeto.constructor.name === 'TuplaN') {
            if (!Number.isInteger(valorIndice)) {
                return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.simboloFechamento, 'Somente inteiros podem ser usados para indexar uma tupla.', expressao.linha));
            }
            if (valorIndice < 0 && objeto.elementos.length !== 0) {
                valorIndice += objeto.elementos.length;
            }
            if (valorIndice >= objeto.elementos.length || valorIndice < 0) {
                return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.simboloFechamento, 'Índice da tupla fora de intervalo.', expressao.linha));
            }
            const elemento = objeto.elementos[valorIndice];
            if (elemento && elemento.constructor && elemento.constructor.name === 'Literal') {
                return elemento.valor;
            }
            return elemento;
        }
        if (objeto instanceof construtos_1.Vetor) {
            return objeto.valores[valorIndice];
        }
        if (objeto.constructor === Object ||
            objeto instanceof estruturas_1.ObjetoDeleguaClasse ||
            objeto instanceof estruturas_1.DeleguaFuncao ||
            objeto instanceof estruturas_1.DescritorTipoClasse ||
            objeto instanceof estruturas_1.DeleguaModulo) {
            if (objeto[valorIndice] === 0)
                return 0;
            return objeto[valorIndice] || null;
        }
        if (typeof objeto === primitivos_1.default.TEXTO) {
            if (!Number.isInteger(valorIndice)) {
                return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.simboloFechamento, 'Somente inteiros podem ser usados para indexar um vetor.', expressao.linha));
            }
            if (valorIndice < 0 && objeto.length !== 0) {
                valorIndice += objeto.length;
            }
            if (valorIndice >= objeto.length || valorIndice < 0) {
                return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.simboloFechamento, 'Índice fora do tamanho.', expressao.linha));
            }
            return objeto.charAt(valorIndice);
        }
        return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao({
            hashArquivo: this.hashArquivoDeclaracaoAtual,
            linha: this.linhaDeclaracaoAtual,
        }, 'Somente listas, dicionários, classes e objetos podem ter seus valores indexados.', expressao.linha));
    }
    async visitarExpressaoAcessoMetodo(expressao) {
        const nomeObjeto = this.resolverNomeObjectoAcessado(expressao.objeto);
        let variavelObjeto = await this.avaliar(expressao.objeto);
        // Este caso acontece quando há encadeamento de métodos.
        // Por exemplo, `objeto1.metodo1().metodo2()`.
        // Como `RetornoQuebra` também possui `valor`, precisamos extrair o
        // valor dele primeiro.
        if (variavelObjeto.constructor === quebras_1.RetornoQuebra) {
            variavelObjeto = variavelObjeto.valor;
        }
        const objeto = this.resolverValor(variavelObjeto);
        if (objeto.constructor && objeto.constructor === estruturas_1.ObjetoDeleguaClasse) {
            return objeto.obterMetodo(expressao.nomeMetodo) || null;
        }
        if (objeto instanceof construtos_1.TuplaN || objeto.constructor.name === 'TuplaN') {
            const metodoDePrimitivaTupla = primitivas_tupla_1.default[expressao.nomeMetodo];
            if (metodoDePrimitivaTupla) {
                return new estruturas_1.MetodoPrimitiva(nomeObjeto, objeto, metodoDePrimitivaTupla.implementacao, expressao.nomeMetodo, 'tupla');
            }
        }
        // Objeto simples do JavaScript, ou dicionário de Delégua.
        if (objeto.constructor === Object) {
            if (expressao.nomeMetodo in primitivas_dicionario_1.default) {
                const metodoDePrimitivaDicionario = primitivas_dicionario_1.default[expressao.nomeMetodo].implementacao;
                return new estruturas_1.MetodoPrimitiva(nomeObjeto, objeto, metodoDePrimitivaDicionario, expressao.nomeMetodo, 'dicionário');
            }
            return objeto[expressao.nomeMetodo] || null;
        }
        // Casos em que o objeto possui algum outro tipo que não o de objeto simples.
        // Normalmente executam quando uma biblioteca é importada, e estamos tentando
        // obter alguma propriedade ou método desse objeto.
        // Caso 1: Função tradicional do JavaScript.
        if (typeof objeto[expressao.nomeMetodo] === primitivos_1.default.FUNCAO) {
            return objeto[expressao.nomeMetodo];
        }
        // Caso 2: Objeto tradicional do JavaScript.
        if (typeof objeto[expressao.nomeMetodo] === primitivos_1.default.OBJETO) {
            return objeto[expressao.nomeMetodo];
        }
        // A partir daqui, presume-se que o objeto é uma das estruturas
        // de Delégua.
        if (objeto instanceof estruturas_1.DeleguaModulo) {
            return objeto.componentes[expressao.nomeMetodo] || null;
        }
        let tipoObjeto = variavelObjeto.tipo;
        if (tipoObjeto === null || tipoObjeto === undefined) {
            tipoObjeto = (0, inferenciador_1.inferirTipoVariavel)(objeto);
        }
        // Como internamente um dicionário de Delégua é simplesmente um objeto de
        // JavaScript, as primitivas de dicionário, especificamente, são tratadas
        // mais acima.
        switch (tipoObjeto) {
            case delegua_1.default.INTEIRO:
            case delegua_1.default.NUMERO:
            case delegua_1.default.NÚMERO:
                const metodoDePrimitivaNumero = primitivas_numero_1.default[expressao.nomeMetodo].implementacao;
                if (metodoDePrimitivaNumero) {
                    return new estruturas_1.MetodoPrimitiva(nomeObjeto, objeto, metodoDePrimitivaNumero, expressao.nomeMetodo, tipoObjeto);
                }
                break;
            case delegua_1.default.TEXTO:
                const metodoDePrimitivaTexto = primitivas_texto_1.default[expressao.nomeMetodo].implementacao;
                if (metodoDePrimitivaTexto) {
                    return new estruturas_1.MetodoPrimitiva(nomeObjeto, objeto, metodoDePrimitivaTexto, expressao.nomeMetodo, 'texto');
                }
                break;
            case delegua_1.default.VETOR:
            case delegua_1.default.VETOR_NUMERO:
            case delegua_1.default.VETOR_NÚMERO:
            case delegua_1.default.VETOR_TEXTO:
                const metodoDePrimitivaVetor = primitivas_vetor_1.default[expressao.nomeMetodo].implementacao;
                if (metodoDePrimitivaVetor) {
                    return new estruturas_1.MetodoPrimitiva(nomeObjeto, objeto, metodoDePrimitivaVetor, expressao.nomeMetodo, tipoObjeto);
                }
                break;
        }
        return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao({
            hashArquivo: this.hashArquivoDeclaracaoAtual,
            linha: this.linhaDeclaracaoAtual,
        }, `Método para objeto ou primitiva não encontrado: ${expressao.nomeMetodo}.`, expressao.linha));
    }
    /**
     * Casos que ocorrem aqui:
     *
     * - Quando o método ou propriedade é ou 'qualquer', ou vetor
     *   de 'qualquer' ('qualquer[]'), e uma primitiva é usada.
     * - Quando o objeto é uma classe definida em código.
     * @param {AcessoMetodoOuPropriedade} expressao A expressão de acesso a método ou propriedade.
     * @returns A primitiva encontrada.
     */
    async visitarExpressaoAcessoMetodoOuPropriedade(expressao) {
        const nomeObjeto = this.resolverNomeObjectoAcessado(expressao.objeto);
        let variavelObjeto = await this.avaliar(expressao.objeto);
        // Este caso acontece quando há encadeamento de métodos.
        // Por exemplo, `objeto1.metodo1().metodo2()`.
        // Como `RetornoQuebra` também possui `valor`, precisamos extrair o
        // valor dele primeiro.
        if (variavelObjeto.constructor === quebras_1.RetornoQuebra) {
            const retornoQuebra = variavelObjeto;
            variavelObjeto = retornoQuebra.valor;
        }
        const objeto = this.resolverValor(variavelObjeto, true);
        if (objeto.constructor === estruturas_1.ObjetoDeleguaClasse) {
            return objeto.obter(expressao.simbolo);
        }
        if (objeto instanceof construtos_1.TuplaN || objeto.constructor.name === 'TuplaN') {
            const metodoDePrimitivaTupla = primitivas_tupla_1.default[expressao.simbolo.lexema];
            if (metodoDePrimitivaTupla) {
                return new estruturas_1.MetodoPrimitiva(nomeObjeto, objeto, metodoDePrimitivaTupla.implementacao, expressao.simbolo.lexema, 'tupla');
            }
        }
        // Objeto simples do JavaScript, ou dicionário de Delégua.
        if (objeto.constructor === Object) {
            if (expressao.simbolo.lexema in primitivas_dicionario_1.default) {
                const metodoDePrimitivaDicionario = primitivas_dicionario_1.default[expressao.simbolo.lexema].implementacao;
                return new estruturas_1.MetodoPrimitiva(nomeObjeto, objeto, metodoDePrimitivaDicionario, expressao.simbolo.lexema, 'dicionário');
            }
            return objeto[expressao.simbolo.lexema];
        }
        // String do JavaScript, ou seja, primitiva de texto.
        if (objeto.constructor === String) {
            if (!(expressao.simbolo.lexema in primitivas_texto_1.default)) {
                throw new excecoes_1.ErroEmTempoDeExecucao(expressao.simbolo, `Método de primitiva '${expressao.simbolo.lexema}' não existe para o tipo texto.`);
            }
            const metodoDePrimitivaTexto = primitivas_texto_1.default[expressao.simbolo.lexema].implementacao;
            return new estruturas_1.MetodoPrimitiva(nomeObjeto, objeto, metodoDePrimitivaTexto, expressao.simbolo.lexema, 'texto');
        }
        // A partir daqui, presume-se que o objeto é uma das estruturas
        // de Delégua.
        if (objeto instanceof estruturas_1.DeleguaModulo) {
            return objeto.componentes[expressao.simbolo.lexema] || null;
        }
        let tipoObjeto = variavelObjeto.tipo;
        if (tipoObjeto === null || tipoObjeto === undefined) {
            tipoObjeto = (0, inferenciador_1.inferirTipoVariavel)(objeto);
        }
        // Como internamente um dicionário de Delégua é simplesmente um objeto de
        // JavaScript, as primitivas de dicionário, especificamente, são tratadas
        // mais acima.
        switch (tipoObjeto) {
            case delegua_1.default.INTEIRO:
            case delegua_1.default.NUMERO:
            case delegua_1.default.NÚMERO:
                if (!(expressao.simbolo.lexema in primitivas_numero_1.default)) {
                    throw new excecoes_1.ErroEmTempoDeExecucao(expressao.simbolo, `Método de primitiva '${expressao.simbolo.lexema}' não existe para o tipo ${tipoObjeto}.`);
                }
                const metodoDePrimitivaNumero = primitivas_numero_1.default[expressao.simbolo.lexema].implementacao;
                if (metodoDePrimitivaNumero) {
                    return new estruturas_1.MetodoPrimitiva(nomeObjeto, objeto, metodoDePrimitivaNumero, expressao.simbolo.lexema, 'número');
                }
                break;
            case delegua_1.default.TEXTO:
                if (!(expressao.simbolo.lexema in primitivas_texto_1.default)) {
                    throw new excecoes_1.ErroEmTempoDeExecucao(expressao.simbolo, `Método de primitiva '${expressao.simbolo.lexema}' não existe para o tipo ${tipoObjeto}.`);
                }
                const metodoDePrimitivaTexto = primitivas_texto_1.default[expressao.simbolo.lexema].implementacao;
                if (metodoDePrimitivaTexto) {
                    return new estruturas_1.MetodoPrimitiva(nomeObjeto, objeto, metodoDePrimitivaTexto, expressao.simbolo.lexema, 'texto');
                }
                break;
            case delegua_1.default.VETOR:
            case delegua_1.default.VETOR_INTEIRO:
            case delegua_1.default.VETOR_LOGICO:
            case delegua_1.default.VETOR_LÓGICO:
            case delegua_1.default.VETOR_NUMERO:
            case delegua_1.default.VETOR_NÚMERO:
            case delegua_1.default.VETOR_QUALQUER:
            case delegua_1.default.VETOR_TEXTO:
                if (!(expressao.simbolo.lexema in primitivas_vetor_1.default)) {
                    throw new excecoes_1.ErroEmTempoDeExecucao(expressao.simbolo, `Método de primitiva '${expressao.simbolo.lexema}' não existe para o tipo ${tipoObjeto}.`);
                }
                const metodoDePrimitivaVetor = primitivas_vetor_1.default[expressao.simbolo.lexema].implementacao;
                if (metodoDePrimitivaVetor) {
                    return new estruturas_1.MetodoPrimitiva(nomeObjeto, objeto, metodoDePrimitivaVetor, expressao.simbolo.lexema, tipoObjeto);
                }
                break;
        }
        // Objeto de uma classe JavaScript regular (ou seja, com construtor e propriedades)
        // que possua a propriedade.
        // Exemplos: classes de LinConEs, como `RetornoComando`, ou bibliotecas globais com objetos próprios.
        if (objeto.hasOwnProperty && objeto.hasOwnProperty(expressao.simbolo.lexema)) {
            return objeto[expressao.simbolo.lexema];
        }
        // Último caso: objeto simples, sem construtor, sem protótipo. Exemplo: {'a': 1, 'b': 2}
        if (typeof objeto[expressao.simbolo.lexema] !== 'undefined') {
            return objeto[expressao.simbolo.lexema];
        }
        return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(null, `Método ou propriedade para objeto ou primitiva não encontrado: ${expressao.simbolo.lexema}.`, expressao.linha));
    }
    async visitarExpressaoAcessoPropriedade(expressao) {
        const nomeObjeto = this.resolverNomeObjectoAcessado(expressao.objeto);
        let variavelObjeto = await this.avaliar(expressao.objeto);
        // Este caso acontece quando há encadeamento de métodos.
        // Por exemplo, `objeto1.metodo1().metodo2()`.
        // Como `RetornoQuebra` também possui `valor`, precisamos extrair o
        // valor dele primeiro.
        if (variavelObjeto.constructor === quebras_1.RetornoQuebra) {
            variavelObjeto = variavelObjeto.valor;
        }
        const objeto = this.resolverValor(variavelObjeto);
        // Outro caso que `instanceof` simplesmente não funciona para casos em Liquido,
        // então testamos também o nome do construtor.
        if (objeto.constructor === estruturas_1.ObjetoDeleguaClasse) {
            return objeto.obterMetodo(expressao.nomePropriedade) || null;
        }
        if (objeto instanceof construtos_1.TuplaN || objeto.constructor.name === 'TuplaN') {
            const metodoPrimitivaTupla = primitivas_tupla_1.default[expressao.nomePropriedade];
            if (metodoPrimitivaTupla) {
                return new estruturas_1.MetodoPrimitiva(nomeObjeto, objeto, metodoPrimitivaTupla.implementacao, expressao.nomePropriedade, 'tupla');
            }
        }
        // Objeto simples do JavaScript, ou dicionário de Delégua.
        if (objeto.constructor === Object) {
            if (expressao.nomePropriedade in primitivas_dicionario_1.default) {
                const metodoDePrimitivaDicionario = primitivas_dicionario_1.default[expressao.nomePropriedade].implementacao;
                return new estruturas_1.MetodoPrimitiva(nomeObjeto, objeto, metodoDePrimitivaDicionario, expressao.nomePropriedade, 'dicionário');
            }
            return objeto[expressao.nomePropriedade] || null;
        }
        // Casos em que o objeto possui algum outro tipo que não o de objeto simples.
        // Normalmente executam quando uma biblioteca é importada, e estamos tentando
        // obter alguma propriedade ou método desse objeto.
        // Caso 1: Função tradicional do JavaScript.
        if (typeof objeto[expressao.nomePropriedade] === primitivos_1.default.FUNCAO) {
            return objeto[expressao.nomePropriedade];
        }
        // Caso 2: Objeto tradicional do JavaScript.
        if (typeof objeto[expressao.nomePropriedade] === primitivos_1.default.OBJETO) {
            return objeto[expressao.nomePropriedade];
        }
        // A partir daqui, presume-se que o objeto é uma das estruturas
        // de Delégua.
        if (objeto instanceof estruturas_1.DeleguaModulo) {
            return objeto.componentes[expressao.nomePropriedade] || null;
        }
        let tipoObjeto = variavelObjeto.tipo;
        if (tipoObjeto === null || tipoObjeto === undefined) {
            tipoObjeto = (0, inferenciador_1.inferirTipoVariavel)(objeto);
        }
        return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(null, `Propriedade para objeto ou primitiva não encontrado: ${expressao.nomePropriedade}.`, expressao.linha));
    }
    async visitarExpressaoAjuda(expressao) {
        return Promise.resolve((0, comum_1.pontoEntradaAjuda)(expressao.funcao, expressao.valor));
    }
    async visitarExpressaoAtribuicaoPorIndice(expressao) {
        const promises = await Promise.all([
            this.avaliar(expressao.objeto),
            this.avaliar(expressao.indice),
            this.avaliar(expressao.valor),
        ]);
        let objeto = promises[0];
        let indice = promises[1];
        const valor = promises[2];
        if (objeto.tipo === delegua_1.default.TUPLA) {
            return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.objeto.simbolo, 'Não é possível modificar uma tupla. As tuplas são estruturas de dados imutáveis.', expressao.linha));
        }
        objeto = this.resolverValor(objeto);
        indice = this.resolverValor(indice);
        // Se o valor é uma referência ao montão, e o índice que a recebe é
        // de uma variável/constante que vive num escopo superior, a referência
        // precisa ser transferida para o escopo correspondente.
        if (valor instanceof estruturas_1.ReferenciaMontao) {
            // TODO: Terminar
            const nomeVariavel = expressao.objeto.simbolo.lexema;
            if (this.pilhaEscoposExecucao.obterVariavelEm(1, nomeVariavel) === undefined) {
                this.pilhaEscoposExecucao.migrarReferenciaMontaoParaEscopoDeVariavel(nomeVariavel, valor.endereco);
            }
        }
        if (Array.isArray(objeto)) {
            if (indice < 0 && objeto.length !== 0) {
                while (indice < 0) {
                    indice += objeto.length;
                }
            }
            while (objeto.length < indice) {
                objeto.push(null);
            }
            objeto[indice] = valor;
            // this.pilhaEscoposExecucao.atribuirVariavel((expressao.objeto as any).simbolo, objeto);
        }
        else if ((objeto && objeto.constructor === Object) ||
            objeto instanceof estruturas_1.ObjetoDeleguaClasse ||
            objeto instanceof estruturas_1.DeleguaFuncao ||
            objeto instanceof estruturas_1.DescritorTipoClasse ||
            objeto instanceof estruturas_1.DeleguaModulo) {
            objeto[indice] = valor;
        }
        else {
            return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.objeto.nome, 'Somente listas, dicionários, classes e objetos podem ser mudados por índice.', expressao.linha));
        }
    }
    /**
     * Em Delégua e Pituguês, comentários não são importantes para a interpretação.
     * @param expressao Uma `Promise` sempre resolvida.
     */
    async visitarExpressaoComentario(expressao) {
        return Promise.resolve();
    }
    /**
     * Execução de uma expressão de atribuição.
     * @param expressao A expressão.
     * @returns O valor atribuído.
     */
    async visitarExpressaoDeAtribuicao(expressao) {
        let valor = await this.avaliar(expressao.valor);
        if (valor && valor.hasOwnProperty('valorRetornado')) {
            valor = valor.valorRetornado;
        }
        const valorResolvido = this.resolverValor(valor);
        let indice = null;
        if (expressao.indice) {
            indice = await this.avaliar(expressao.indice);
        }
        switch (expressao.alvo.constructor) {
            case construtos_1.Variavel:
                const alvoVariavel = expressao.alvo;
                const variavelResolvida = this.pilhaEscoposExecucao.obterValorVariavel(alvoVariavel.simbolo);
                if (variavelResolvida.valor instanceof estruturas_1.ReferenciaMontao) {
                    const referenciaMontao = this.montao.obterReferencia(this.hashArquivoDeclaracaoAtual, this.linhaDeclaracaoAtual, variavelResolvida.valor.endereco);
                    referenciaMontao[indice] = valorResolvido;
                }
                else {
                    this.pilhaEscoposExecucao.atribuirVariavel(alvoVariavel.simbolo, valorResolvido, indice);
                }
                break;
            case construtos_1.AcessoMetodoOuPropriedade:
                // Nunca será método aqui: apenas propriedade.
                const alvoPropriedade = expressao.alvo;
                const variavelObjeto = await this.avaliar(alvoPropriedade.objeto);
                const objeto = this.resolverValor(variavelObjeto);
                const valor = await this.avaliar(expressao.valor);
                if (objeto.constructor === estruturas_1.ObjetoDeleguaClasse) {
                    const objetoDeleguaClasse = objeto;
                    objetoDeleguaClasse.definir(alvoPropriedade.simbolo, valor);
                }
                else {
                    // Se cair aqui, provavelmente `objeto.constructor.name` é 'Object'.
                    objeto[alvoPropriedade.simbolo.lexema] = valor;
                }
                break;
            default:
                throw new excecoes_1.ErroEmTempoDeExecucao(null, `Atribuição com caso faltante: ${JSON.stringify(expressao)}.`);
        }
        return valorResolvido;
    }
    async visitarExpressaoDefinirValor(expressao) {
        const variavelObjeto = await this.avaliar(expressao.objeto);
        const objeto = this.resolverValor(variavelObjeto);
        if (objeto.constructor !== estruturas_1.ObjetoDeleguaClasse && objeto.constructor !== Object) {
            return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.nome, 'Somente instâncias e dicionários podem possuir campos.', expressao.linha));
        }
        const valor = await this.avaliar(expressao.valor);
        const valorResolvido = this.resolverValor(valor);
        if (objeto.constructor === estruturas_1.ObjetoDeleguaClasse) {
            objeto.definir(expressao.nome, valorResolvido);
            return valorResolvido;
        }
        if (objeto.constructor === Object) {
            objeto[expressao.nome.lexema] = valorResolvido;
        }
    }
    /**
     * Dicionários em Delégua são passados por referência, portanto, são
     * armazenados no montão.
     */
    async visitarExpressaoDicionario(expressao) {
        // Delega ao interpretador base para processar o dicionário (incluindo spread)
        const dicionario = await super.visitarExpressaoDicionario(expressao);
        // Adiciona referência no montão (comportamento específico deste interpretador)
        const enderecoDicionarioMontao = this.montao.adicionarReferencia(dicionario);
        this.pilhaEscoposExecucao.registrarReferenciaMontao(enderecoDicionarioMontao);
        return new estruturas_1.ReferenciaMontao(enderecoDicionarioMontao);
    }
    async visitarExpressaoElvis(expressao) {
        const esquerda = await this.avaliar(expressao.esquerda);
        const direita = await this.avaliar(expressao.direita);
        const valorEsquerdo = this.resolverValor(esquerda);
        const valorDireito = this.resolverValor(direita);
        if (valorEsquerdo === null || valorEsquerdo === undefined) {
            return valorDireito;
        }
        return valorEsquerdo;
    }
    visitarExpressaoEnquanto(expressao) {
        return this.logicaComumExecucaoEnquanto(expressao, true);
    }
    visitarExpressaoFazer(expressao) {
        return this.logicaComumExecucaoFazer(expressao, true);
    }
    visitarExpressaoImportar(expressao) {
        throw new Error('Importações não são suportadas neste interpretador.');
    }
    async visitarExpressaoListaCompreensao(listaCompreensao) {
        const vetorVariavelIteracao = await this.avaliar(listaCompreensao.referenciaVariavelIteracao);
        let valorVetorVariavelIteracao = this.resolverValor(vetorVariavelIteracao);
        if (!Array.isArray(valorVetorVariavelIteracao)) {
            return Promise.reject("Variável ou literal provida em instrução 'para cada' não é um vetor.");
        }
        const resultadoCompreensao = await this.avaliar(listaCompreensao.paraCada);
        const resultadoCompreensaoResolvido = resultadoCompreensao.valorRetornado
            .filter((r) => r !== null)
            .map((r) => this.resolverValor(r));
        return resultadoCompreensaoResolvido;
    }
    visitarExpressaoPara(expressao) {
        return this.logicaComumExecucaoPara(expressao, true);
    }
    visitarExpressaoParaCada(expressao) {
        return this.logicaComumExecucaoParaCada(expressao, true);
    }
    async visitarExpressaoRetornar(declaracao) {
        let valor = null;
        if (declaracao.valor !== null && declaracao.valor !== undefined) {
            valor = await this.avaliar(declaracao.valor);
        }
        const retornoQuebra = new quebras_1.RetornoQuebra(valor, declaracao.tipo);
        // Se o retorno for uma função anônima ou referência ao montão, o escopo precisa ser preservado.
        // Como quebras matam o topo da pilha de escopos, precisamos dizer
        // para a finalização para copiar valores importantes para o escopo de baixo.
        if (retornoQuebra.valor) {
            const construtorRetorno = retornoQuebra.valor.constructor.name.replaceAll('_', '');
            if (['DeleguaFuncao', 'ReferenciaMontao'].includes(construtorRetorno)) {
                retornoQuebra.preservarEscopo = true; // TODO: Ver se é mesmo o caso de manter isso para referências ao montão.
            }
        }
        return retornoQuebra;
    }
    /**
     * Para Delégua e Pituguês, o separador é apenas um elemento de sintaxe.
     * Não há qualquer avaliação a ser feita.
     * @param expressao
     */
    async visitarExpressaoSeparador(expressao) {
        return Promise.resolve(null);
    }
    async visitarExpressaoSeTernario(expressao) {
        const avaliacaoCondicao = await this.avaliar(expressao.condicao);
        const valorAvaliacaoCondicao = this.resolverValor(avaliacaoCondicao);
        if (valorAvaliacaoCondicao) {
            return this.avaliar(expressao.expressaoSe);
        }
        return this.avaliar(expressao.expressaoSenao);
    }
    async visitarExpressaoTipoDe(expressao) {
        let valorTipoDe = expressao.valor;
        switch (valorTipoDe.constructor) {
            case construtos_1.AcessoIndiceVariavel:
            case construtos_1.Agrupamento:
            case construtos_1.Binario:
            case construtos_1.Chamada:
            case construtos_1.Dicionario:
            case construtos_1.Unario:
                valorTipoDe = await this.avaliar(valorTipoDe);
                if (valorTipoDe instanceof estruturas_1.ReferenciaMontao) {
                    valorTipoDe = this.montao.obterReferencia(this.hashArquivoDeclaracaoAtual, this.linhaDeclaracaoAtual, valorTipoDe.endereco);
                }
                return valorTipoDe.tipo || (0, inferenciador_1.inferirTipoVariavel)(valorTipoDe);
            case construtos_1.AcessoMetodo:
                const acessoMetodo = valorTipoDe;
                const tipoRetornoMetodoResolvido = acessoMetodo.tipoRetornoMetodo.replace('<T>', acessoMetodo.objeto.tipo);
                return `método<${tipoRetornoMetodoResolvido}>`;
            case construtos_1.AcessoPropriedade:
                const acessoPropriedade = valorTipoDe;
                return acessoPropriedade.tipoRetornoPropriedade;
            case construtos_1.AcessoMetodoOuPropriedade:
                // TODO: Deve ser removido mais futuramente.
                // Apenas `AcessoMetodo` e `AcessoPropriedade` devem funcionar aqui.
                throw new excecoes_1.ErroEmTempoDeExecucao(expressao.simbolo, 'Não deveria cair aqui.');
            case declaracoes_1.Escreva:
                return 'função<vazio>';
            case construtos_1.Leia:
                return 'função<texto>';
            case construtos_1.Literal:
                const tipoLiteral = valorTipoDe;
                return tipoLiteral.tipo;
            case construtos_1.TipoDe:
                const alvoTipoDe = await this.avaliar(valorTipoDe);
                return `tipo de<${alvoTipoDe}>`;
            case construtos_1.Variavel:
                return valorTipoDe.tipo;
            case construtos_1.Vetor:
                const vetor = valorTipoDe;
                const apenasValores = vetor.valores.filter((v) => !['ComentarioComoConstruto', 'Separador'].includes(v.constructor.name));
                return (0, inferenciador_1.inferirTipoVariavel)(apenasValores);
            default:
                return (0, inferenciador_1.inferirTipoVariavel)(valorTipoDe);
        }
    }
    async visitarExpressaoUnaria(expressao) {
        // Tratamento especial para expressões unárias aplicadas a chamadas de método em literais numéricos.
        // Por exemplo: -5.absoluto() deve ser avaliado como (-5).absoluto(), não como -(5.absoluto())
        // Isso garante que o operador unário seja aplicado ao literal antes de chamar o método.
        if ((expressao.operador.tipo === delegua_2.default.SUBTRACAO || expressao.operador.tipo === delegua_2.default.ADICAO) &&
            expressao.operando instanceof construtos_1.Chamada) {
            const entidadeChamada = expressao.operando.entidadeChamada;
            // Verifica se é AcessoMetodo ou AcessoMetodoOuPropriedade
            if (entidadeChamada instanceof construtos_1.AcessoMetodo || entidadeChamada instanceof construtos_1.AcessoMetodoOuPropriedade) {
                const objetoAcesso = entidadeChamada.objeto;
                // Verifica se o objeto do método é um literal numérico
                if (objetoAcesso instanceof construtos_1.Literal && typeof objetoAcesso.valor === 'number') {
                    // Cria um novo literal com o sinal aplicado
                    const novoLiteral = new construtos_1.Literal(objetoAcesso.hashArquivo, objetoAcesso.linha, expressao.operador.tipo === delegua_2.default.SUBTRACAO ?
                        -objetoAcesso.valor :
                        +objetoAcesso.valor);
                    // Cria um novo acesso com o literal modificado
                    let novoAcesso;
                    if (entidadeChamada instanceof construtos_1.AcessoMetodo) {
                        novoAcesso = new construtos_1.AcessoMetodo(entidadeChamada.hashArquivo, novoLiteral, entidadeChamada.nomeMetodo, entidadeChamada.tipoRetornoMetodo);
                    }
                    else {
                        novoAcesso = new construtos_1.AcessoMetodoOuPropriedade(entidadeChamada.hashArquivo, novoLiteral, entidadeChamada.simbolo);
                    }
                    // Cria uma nova Chamada com o acesso modificado
                    const novaChamada = new construtos_1.Chamada(expressao.operando.hashArquivo, novoAcesso, expressao.operando.argumentos);
                    // Avalia a nova chamada
                    return await this.avaliar(novaChamada);
                }
            }
        }
        // Para outros casos, usa o comportamento padrão
        return await super.visitarExpressaoUnaria(expressao);
    }
    async visitarExpressaoVetor(expressao) {
        // Delega ao interpretador base para processar o vetor
        const vetor = await super.visitarExpressaoVetor(expressao);
        // Adiciona referência no montão (comportamento específico deste interpretador)                                                                             
        const enderecoVetorMontao = this.montao.adicionarReferencia(vetor);
        this.pilhaEscoposExecucao.registrarReferenciaMontao(enderecoVetorMontao);
        return new estruturas_1.ReferenciaMontao(enderecoVetorMontao);
    }
    /**
     * Executa o último escopo empilhado no topo na pilha de escopos do interpretador.
     * Esse método pega exceções, mas apenas as devolve.
     *
     * O tratamento das exceções é feito de acordo com o bloco chamador.
     * Por exemplo, em `tente ... pegue ... finalmente`, a exceção é capturada e tratada.
     * Em outros blocos, pode ser desejável ter o erro em tela.
     * @param manterAmbiente Se verdadeiro, ambiente do topo da pilha de escopo é copiado para o ambiente imediatamente abaixo.
     * @returns O resultado da execução do escopo, se houver.
     */
    async executarUltimoEscopo(manterAmbiente = false) {
        const ultimoEscopo = this.pilhaEscoposExecucao.topoDaPilha();
        let retornoExecucao;
        try {
            for (; !(retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.Quebra) &&
                ultimoEscopo.declaracaoAtual < ultimoEscopo.declaracoes.length; ultimoEscopo.declaracaoAtual++) {
                const declaracaoAtual = ultimoEscopo.declaracoes[ultimoEscopo.declaracaoAtual];
                this.linhaDeclaracaoAtual = declaracaoAtual.linha;
                this.hashArquivoDeclaracaoAtual = declaracaoAtual.hashArquivo;
                retornoExecucao = await this.executar(declaracaoAtual);
            }
            return retornoExecucao;
        }
        catch (erro) {
            const declaracaoAtual = ultimoEscopo.declaracoes[ultimoEscopo.declaracaoAtual];
            if (!this.emDeclaracaoTente) {
                this.erros.push({
                    erroInterno: erro,
                    linha: declaracaoAtual.linha,
                    hashArquivo: declaracaoAtual.hashArquivo,
                });
            }
            else {
                return Promise.reject(erro);
            }
        }
        finally {
            const escopoFinalizado = this.pilhaEscoposExecucao.removerUltimo();
            const escopoAnterior = this.pilhaEscoposExecucao.topoDaPilha();
            if (manterAmbiente ||
                (retornoExecucao && retornoExecucao.valorRetornado.preservarEscopo === true)) {
                escopoAnterior.espacoMemoria.valores = Object.assign(escopoAnterior.espacoMemoria.valores, ultimoEscopo.espacoMemoria.valores);
                escopoAnterior.espacoMemoria.enderecosMontao = new Set([
                    ...escopoAnterior.espacoMemoria.enderecosMontao,
                    ...ultimoEscopo.espacoMemoria.enderecosMontao,
                ]);
            }
            else {
                this.montao.excluirReferencias(...escopoFinalizado.espacoMemoria.enderecosMontao);
            }
        }
    }
    async visitarExpressaoTuplaN(expressao) {
        return super.visitarExpressaoTuplaN(expressao);
    }
    /**
     * Método que efetivamente inicia o processo de interpretação.
     * @param declaracoes Um vetor de declarações gerado pelo Avaliador Sintático.
     * @param manterAmbiente Se ambiente de execução (variáveis, classes, etc.) deve ser mantido. Normalmente usado
     *                       pelo modo REPL (LAIR).
     * @returns Um objeto com o resultado da interpretação.
     */
    async interpretar(declaracoes, manterAmbiente) {
        this.montao = new montao_1.Montao();
        const resultados = await super.interpretar(declaracoes, manterAmbiente);
        if (resultados.resultado.length > 0) {
            const ultimoResultado = resultados.resultado[resultados.resultado.length - 1];
            if (ultimoResultado &&
                ultimoResultado.valorRetornado instanceof quebras_1.RetornoQuebra &&
                ultimoResultado.valorRetornado.valor instanceof estruturas_1.ReferenciaMontao) {
                const ultimaDeclaracao = declaracoes[declaracoes.length - 1];
                ultimoResultado.valorRetornado.valor = this.montao.obterReferencia(ultimaDeclaracao.hashArquivo, ultimaDeclaracao.linha, ultimoResultado.valorRetornado.valor.endereco);
            }
        }
        return resultados;
    }
}
exports.Interpretador = Interpretador;
//# sourceMappingURL=interpretador.js.map