"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.InterpretadorBase = void 0;
const browser_process_hrtime_1 = __importDefault(require("browser-process-hrtime"));
const estruturas_1 = require("./estruturas");
const construtos_1 = require("../construtos");
const pilha_escopos_execucao_1 = require("./pilha-escopos-execucao");
const quebras_1 = require("../quebras");
const inferenciador_1 = require("../inferenciador");
const metodo_primitiva_1 = require("./estruturas/metodo-primitiva");
const lexador_1 = require("../lexador");
const avaliador_sintatico_1 = require("../avaliador-sintatico");
const espaco_memoria_1 = require("./espaco-memoria");
const excecoes_1 = require("../excecoes");
const primitivas_dicionario_1 = __importDefault(require("../bibliotecas/primitivas-dicionario"));
const delegua_1 = __importDefault(require("../tipos-de-simbolos/delegua"));
const primitivos_1 = __importDefault(require("../tipos-de-dados/primitivos"));
const delegua_2 = __importDefault(require("../tipos-de-dados/delegua"));
const primitivas_vetor_1 = __importDefault(require("../bibliotecas/primitivas-vetor"));
/**
 * O Interpretador visita todos os elementos complexos gerados pelo avaliador sintático (_parser_),
 * e de fato executa a lógica de programação descrita no código. Este interpretador base é usado
 * por Delégua e todos os seus dialetos, contendo somente os pontos em comum entre todas as
 * linguagens.
 *
 * O Interpretador Base não contém dependências com o Node.js. É
 * recomendado para uso em execuções que ocorrem no navegador de internet.
 */
class InterpretadorBase {
    constructor(diretorioBase, performance = false, funcaoDeRetorno = null, funcaoDeRetornoMesmaLinha = null) {
        this.resultadoInterpretador = [];
        this.funcaoDeRetorno = null;
        this.funcaoDeRetornoMesmaLinha = null;
        this.interfaceDeEntrada = null; // Originalmente é `readline.Interface`
        this.interfaceEntradaSaida = null;
        this.emDeclaracaoTente = false;
        this.microLexador = new lexador_1.MicroLexador();
        this.microAvaliadorSintatico = new avaliador_sintatico_1.MicroAvaliadorSintatico();
        this.regexInterpolacao = /\${(.*?)}/g;
        this.tiposNumericos = [
            delegua_2.default.INTEIRO,
            delegua_2.default.NUMERO,
            delegua_2.default.NÚMERO,
            delegua_2.default.REAL,
        ];
        this.diretorioBase = diretorioBase;
        this.performance = performance;
        this.funcaoDeRetorno = funcaoDeRetorno || console.log;
        this.funcaoDeRetornoMesmaLinha =
            funcaoDeRetornoMesmaLinha || process.stdout.write.bind(process.stdout);
        this.erros = [];
        this.declaracoes = [];
        this.resultadoInterpretador = [];
        // Isso existe por causa de Potigol.
        // Para acessar uma variável de classe, não é preciso a palavra `isto`.
        this.expandirPropriedadesDeObjetosEmEspacoMemoria = false;
        // Por padrão é verdadeiro porque Delégua e Pituguês usam
        // o interpretador base como implementação padrão.
        this.requerDeclaracaoPropriedades = true;
        this.pilhaEscoposExecucao = new pilha_escopos_execucao_1.PilhaEscoposExecucao();
        const escopoExecucao = {
            declaracoes: [],
            declaracaoAtual: 0,
            espacoMemoria: new espaco_memoria_1.EspacoMemoria(),
            finalizado: false,
            tipo: 'outro',
            emLacoRepeticao: false,
        };
        this.pilhaEscoposExecucao.empilhar(escopoExecucao);
    }
    visitarDeclaracaoTextoDocumentacao(declaracao) {
        throw new Error('Método não implementado.');
    }
    visitarExpressaoSeparador(expressao) {
        throw new Error('Método não implementado.');
    }
    visitarExpressaoComentario(expressao) {
        throw new Error('Método não implementado.');
    }
    /**
     * Usado para chamadas de métodos de primitiva.
     * Sendo uma variável ou constante, a primitiva precisa atualizar a referência
     * para o objeto que está sendo acessado.
     * @param {Construto} objetoAcessado O objeto que está sendo acessado.
     * @returns O nome desse objeto, se ele for uma variável ou constante.
     * @see resolverValor
     */
    resolverNomeObjectoAcessado(objetoAcessado) {
        switch (objetoAcessado.constructor) {
            // TODO: Não habilitar isso até que vetores sejam repassados para o montão.
            /* case AcessoMetodoOuPropriedade:
                return (objetoAcessado as AcessoMetodoOuPropriedade).simbolo.lexema;
            case AcessoIndiceVariavel:
                return this.resolverNomeObjectoAcessado((objetoAcessado as AcessoIndiceVariavel).entidadeChamada); */
            case construtos_1.Chamada:
                return this.resolverNomeObjectoAcessado(objetoAcessado.entidadeChamada);
            case construtos_1.Constante:
                return objetoAcessado.simbolo.lexema;
            case construtos_1.AcessoMetodoOuPropriedade:
            case construtos_1.AcessoIndiceVariavel:
            case construtos_1.Dicionario:
            case construtos_1.Literal:
            case construtos_1.Vetor:
                return '';
            case construtos_1.Isto:
                return objetoAcessado.simboloChave.lexema;
            case construtos_1.Super:
                return objetoAcessado.simboloChave.lexema;
            case construtos_1.Variavel:
                return objetoAcessado.simbolo.lexema;
        }
        throw new excecoes_1.ErroEmTempoDeExecucao(objetoAcessado.simbolo, `Construto ${objetoAcessado.constructor.name} não possui resolução de nome apropriada.`);
    }
    resolverValor(objeto) {
        if (objeto === null || objeto === undefined) {
            return objeto;
        }
        if (objeto.hasOwnProperty('valor')) {
            return objeto.valor;
        }
        return objeto;
    }
    /**
     * Resolve valores recursivamente, incluindo valores aninhados em arrays e dicionários.
     * Remove metadados que não devem ser serializados.
     * @param objeto O objeto a ser resolvido
     * @returns O valor resolvido sem metadados
     */
    resolverValorRecursivo(objeto) {
        // Null, undefined, ou tipos primitivos
        if (objeto === null || objeto === undefined || typeof objeto !== 'object') {
            return objeto;
        }
        // Resolve metadados primeiro (valorRetornado ou valor)
        if (objeto.hasOwnProperty && objeto.hasOwnProperty('valorRetornado')) {
            return this.resolverValorRecursivo(objeto.valorRetornado);
        }
        if (objeto.hasOwnProperty && objeto.hasOwnProperty('valor')) {
            return this.resolverValorRecursivo(objeto.valor);
        }
        // Se é array, resolve recursivamente todos os elementos
        if (Array.isArray(objeto)) {
            return objeto.map(elemento => this.resolverValorRecursivo(elemento));
        }
        // Se é objeto plano, resolve recursivamente todas as propriedades
        if (objeto && objeto.constructor && objeto.constructor === Object) {
            const objetoResolvido = {};
            for (const chave in objeto) {
                objetoResolvido[chave] = this.resolverValorRecursivo(objeto[chave]);
            }
            return objetoResolvido;
        }
        // Outros tipos de objetos (Date, classes customizadas, etc.)
        return objeto;
    }
    async visitarExpressaoArgumentoReferenciaFuncao(expressao) {
        const deleguaFuncao = this.pilhaEscoposExecucao.obterVariavelPorNome(expressao.simboloFuncao.lexema);
        return deleguaFuncao;
    }
    visitarExpressaoAcessoMetodo(expressao) {
        throw new Error('Método não implementado.');
    }
    visitarExpressaoAcessoPropriedade(expressao) {
        throw new Error('Método não implementado.');
    }
    /**
     * Construtos de comentários não têm utilidade para o Interpretador.
     * Apenas retornamos `Promise.resolve()` para não termos erros.
     * @param declaracao A declaração de comentário.
     */
    visitarDeclaracaoComentario(declaracao) {
        return Promise.resolve();
    }
    async visitarDeclaracaoTendoComo(declaracao) {
        throw new Error('Método não implementado.');
    }
    async visitarDeclaracaoInicioAlgoritmo(declaracao) {
        throw new Error('Método não implementado.');
    }
    async visitarDeclaracaoCabecalhoPrograma(declaracao) {
        throw new Error('Método não implementado.');
    }
    async visitarExpressaoTupla(expressao) {
        // Lista de propriedades válidas para tuplas (ignorar propriedades de controle)
        const propriedadesValidas = [
            'primeiro', 'segundo', 'terceiro', 'quarto', 'quinto',
            'sexto', 'sétimo', 'setimo', 'oitavo', 'nono', 'décimo', 'decimo'
        ];
        const valores = [];
        for (let propriedade of propriedadesValidas) {
            if (expressao.hasOwnProperty(propriedade) && expressao[propriedade] !== undefined) {
                const valor = await this.avaliar(expressao[propriedade]);
                valores.push(valor);
            }
        }
        return valores;
    }
    async visitarExpressaoTuplaN(expressao) {
        const elementos = [];
        for (let i = 0; i < expressao.elementos.length; i++) {
            const res = await this.avaliar(expressao.elementos[i]);
            elementos.push(this.resolverValor(res));
        }
        const elementosComoConstrutos = elementos.map(valor => new construtos_1.Literal(expressao.hashArquivo, expressao.linha, valor));
        return new construtos_1.TuplaN(expressao.hashArquivo, expressao.linha, elementosComoConstrutos);
    }
    async visitarExpressaoAtribuicaoPorIndicesMatriz(expressao) {
        throw new Error('Método não implementado.');
    }
    async visitarExpressaoAcessoElementoMatriz(expressao) {
        throw new Error('Método não implementado.');
    }
    textoParaRegex(texto) {
        const match = texto.match(/^([\/~@;%#'])(.*?)\1([gimsuy]*)$/);
        return match
            ? new RegExp(match[2], match[3]
                .split('')
                .filter((char, pos, flagArr) => flagArr.indexOf(char) === pos)
                .join(''))
            : new RegExp(texto);
    }
    visitarExpressaoExpressaoRegular(expressao) {
        return Promise.resolve(this.textoParaRegex(expressao.valor));
    }
    visitarExpressaoTipoDe(expressao) {
        throw new Error('Método não implementado.');
    }
    async visitarExpressaoFalhar(expressao) {
        var _a;
        const textoFalha = (_a = expressao.explicacao.valor) !== null && _a !== void 0 ? _a : (await this.avaliar(expressao.explicacao)).valor;
        throw new excecoes_1.ErroEmTempoDeExecucao(expressao.simbolo, textoFalha, expressao.linha);
    }
    async visitarExpressaoFimPara(declaracao) {
        throw new Error('Método não implementado.');
    }
    async visitarExpressaoReferenciaFuncao(expressao) {
        const deleguaFuncao = this.pilhaEscoposExecucao.obterReferenciaFuncao(expressao.idFuncao);
        return deleguaFuncao;
    }
    /**
     * Chama o método `aceitar` de um construto ou declaração, passando o
     * próprio interpretador como parâmetro.
     *
     * Isto é usado para saber qual método do próprio interpretador chamar
     * na sequência.
     * @param expressao A expressão, que pode ser um construto ou declaração.
     * @returns O retorno da execução do método de visita chamado.
     */
    async avaliar(expressao) {
        // Descomente o código abaixo quando precisar detectar expressões undefined ou nulas.
        // Por algum motivo o depurador do VSCode não funciona direito aqui
        // com breakpoint condicional.
        /* if (expressao === null || expressao === undefined) {
            console.log('Aqui');
        } */
        return await expressao.aceitar(this);
    }
    /**
     * Execução da leitura de valores da entrada configurada no
     * início da aplicação.
     * @param expressao Expressão do tipo Leia
     * @returns Promise com o resultado da leitura.
     */
    async visitarExpressaoLeia(expressao) {
        const mensagem = expressao.argumentos && expressao.argumentos[0] ? expressao.argumentos[0].valor : '> ';
        return new Promise((resolucao) => this.interfaceEntradaSaida.question(mensagem, (resposta) => {
            resolucao(resposta);
        }));
    }
    /**
     * Retira a interpolação de um texto.
     * @param {texto} texto O texto
     * @param {any[]} interpolacoes A lista de interpolações a serem resolvidas.
     * @returns O texto com o valor das variáveis.
     */
    retirarInterpolacao(texto, interpolacoes) {
        let textoFinal = texto;
        for (const elemento of interpolacoes) {
            // TODO: Há alguma chance de `elemento` ser `undefined` aqui?
            let valor = elemento === null || elemento === void 0 ? void 0 : elemento.valor;
            if (valor.hasOwnProperty && valor.hasOwnProperty('valorRetornado')) {
                valor = valor.valorRetornado;
            }
            if (valor.tipo === delegua_2.default.LOGICO) {
                textoFinal = textoFinal.replace('${' + elemento.expressaoInterpolacao + '}', this.paraTexto(valor));
            }
            else {
                valor = this.resolverValor(valor);
                const valorResolvidoComoTexto = this.paraTexto(valor);
                textoFinal = textoFinal.replace('${' + elemento.expressaoInterpolacao + '}', valorResolvidoComoTexto.replace(/"/g, ''));
            }
        }
        return textoFinal;
    }
    /**
     * Resolve todas as interpolações em um texto.
     * @param {texto} textoOriginal O texto original com as variáveis interpoladas.
     * @returns Uma lista de variáveis interpoladas.
     */
    async resolverInterpolacoes(textoOriginal, linha) {
        const variaveis = textoOriginal.match(this.regexInterpolacao);
        let resultadosAvaliacaoSintatica = variaveis.map((s) => {
            const expressaoInterpolacao = s.replace(/[\$\{\}]*/gm, '');
            let microLexador = this.microLexador.mapear(expressaoInterpolacao);
            const resultadoMicroAvaliadorSintatico = this.microAvaliadorSintatico.analisar(microLexador, linha);
            return {
                expressaoInterpolacao,
                resultadoMicroAvaliadorSintatico,
            };
        });
        // TODO: Verificar erros do `resultadosAvaliacaoSintatica`.
        const resolucoesPromises = await Promise.all(resultadosAvaliacaoSintatica
            .flatMap((r) => r.resultadoMicroAvaliadorSintatico.declaracoes)
            .map((d) => this.avaliar(d)));
        return resolucoesPromises.map((item, indice) => ({
            expressaoInterpolacao: resultadosAvaliacaoSintatica[indice].expressaoInterpolacao,
            valor: item,
        }));
    }
    async visitarExpressaoLiteral(expressao) {
        if (this.regexInterpolacao.test(String(expressao.valor))) {
            const valorComoTexto = String(expressao.valor);
            const interpolacoes = await this.resolverInterpolacoes(valorComoTexto, expressao.linha);
            return this.retirarInterpolacao(valorComoTexto, interpolacoes);
        }
        return expressao.valor;
    }
    /**
     * Avaliação de agrupamento. Se resultado da avaliação é uma declaração de
     * função (por exemplo, funções anônimas), a declaração é retornada. Este
     * retorno é utilizado, entre outros lugares, por `visitarExpressaoDeChamada`.
     * @param {Agrupamento} expressao O construto de agrupamento.
     * @returns O resultado da avaliação.
     * @see this.visitarExpressaoDeChamada
     */
    async visitarExpressaoAgrupamento(expressao) {
        const avaliacaoAgrupamento = await this.avaliar(expressao.expressao);
        if (avaliacaoAgrupamento.declaracao !== undefined) {
            return avaliacaoAgrupamento.declaracao;
        }
        return avaliacaoAgrupamento;
    }
    eVerdadeiro(objeto) {
        if (objeto === null)
            return false;
        if (typeof objeto === primitivos_1.default.BOOLEANO)
            return Boolean(objeto);
        if (objeto.hasOwnProperty('valor')) {
            return Boolean(objeto.valor);
        }
        return true;
    }
    verificarOperandoNumero(operador, operando) {
        if (typeof operando === primitivos_1.default.NUMERO ||
            operando.tipo === delegua_2.default.NUMERO)
            return;
        throw new excecoes_1.ErroEmTempoDeExecucao(operador, 'Operando precisa ser um número.', Number(operador.linha));
    }
    async visitarExpressaoUnaria(expressao) {
        const operando = await this.avaliar(expressao.operando);
        let valor = this.resolverValor(operando);
        switch (expressao.operador.tipo) {
            case delegua_1.default.ADICAO:
                this.verificarOperandoNumero(expressao.operador, valor);
                return +valor;
            case delegua_1.default.SUBTRACAO:
                this.verificarOperandoNumero(expressao.operador, valor);
                return -valor;
            case delegua_1.default.NEGACAO:
                return !this.eVerdadeiro(valor);
            case delegua_1.default.BIT_NOT:
                return ~valor;
            // Para incrementar e decrementar, primeiro precisamos saber se o operador
            // veio antes do literal ou variável.
            // Se veio antes e o operando é uma variável, precisamos incrementar/decrementar,
            // armazenar o valor da variável pra só então devolver o valor.
            case delegua_1.default.INCREMENTAR:
                if (expressao.incidenciaOperador === 'ANTES') {
                    valor++;
                    if (expressao.operando instanceof construtos_1.Variavel) {
                        this.pilhaEscoposExecucao.atribuirVariavel(expressao.operando.simbolo, valor);
                    }
                    return valor;
                }
                const valorAnteriorIncremento = valor;
                // TODO: Provavelmente isso está incorreto. Descobrir se operando resolve para
                // `Construto` ou para `Simbolo`.
                this.pilhaEscoposExecucao.atribuirVariavel(expressao.operando.simbolo, ++valor);
                return valorAnteriorIncremento;
            case delegua_1.default.DECREMENTAR:
                if (expressao.incidenciaOperador === 'ANTES') {
                    valor--;
                    if (expressao.operando instanceof construtos_1.Variavel) {
                        this.pilhaEscoposExecucao.atribuirVariavel(expressao.operando.simbolo, valor);
                    }
                    return valor;
                }
                const valorAnteriorDecremento = valor;
                // TODO: Provavelmente isso está incorreto. Descobrir se operando resolve para
                // `Construto` ou para `Simbolo`.
                this.pilhaEscoposExecucao.atribuirVariavel(expressao.operando.simbolo, --valor);
                return valorAnteriorDecremento;
        }
        return null;
    }
    /**
     * Formata uma saída de acordo com o número e espaços e casas decimais solicitados.
     * @param declaracao A declaração de formatação de escrita.
     * @returns {string} A saída formatada como texto e os respectivos parâmetros aplicados.
     */
    async visitarExpressaoFormatacaoEscrita(declaracao) {
        let resultado = '';
        const conteudo = await this.avaliar(declaracao.expressao);
        const valorConteudo = this.resolverValor(conteudo);
        const tipoConteudo = conteudo.hasOwnProperty('tipo')
            ? conteudo.tipo
            : typeof conteudo;
        resultado = valorConteudo;
        if ([delegua_2.default.NUMERO, primitivos_1.default.NUMERO].includes(tipoConteudo) &&
            declaracao.casasDecimais > 0) {
            resultado = valorConteudo.toLocaleString('pt', {
                maximumFractionDigits: declaracao.casasDecimais,
            });
        }
        if (declaracao.espacos > 0) {
            resultado += ' '.repeat(declaracao.espacos);
        }
        return resultado;
    }
    /**
     * Lógica para verificação de valores iguais, para Delégua e alguns dialetos.
     * @param esquerda Uma variável.
     * @param direita Outra variável.
     * @returns Verdadeiro se são iguais. Falso em caso contrário.
     */
    eIgual(esquerda, direita) {
        if (esquerda === null && direita === null)
            return true;
        if (esquerda === null)
            return false;
        return esquerda === direita;
    }
    /**
     * Verifica se operandos são números, que podem ser tanto variáveis puras do JavaScript
     * (neste caso, `number`), ou podem ser variáveis de Delégua com inferência (`VariavelInterface`).
     * @param operador O símbolo do operador.
     * @param direita O operando direito.
     * @param esquerda O operando esquerdo.
     * @returns Se ambos os operandos são números ou não.
     */
    verificarOperandosNumeros(operador, direita, esquerda) {
        const tipoDireita = direita.tipo
            ? direita.tipo
            : typeof direita === primitivos_1.default.NUMERO
                ? delegua_2.default.NUMERO
                : String(NaN);
        const tipoEsquerda = esquerda.tipo
            ? esquerda.tipo
            : typeof esquerda === primitivos_1.default.NUMERO
                ? delegua_2.default.NUMERO
                : String(NaN);
        if (this.tiposNumericos.includes(tipoDireita) && this.tiposNumericos.includes(tipoEsquerda))
            return;
        if (this.tiposNumericos.includes(tipoEsquerda) && tipoDireita === 'qualquer')
            return;
        if (this.tiposNumericos.includes(tipoDireita) && tipoEsquerda === 'qualquer')
            return;
        // Se operador é subtração, os dois tipos são `qualquer`, mas ambos podem ser convertidos
        // para número, a operação é válida.
        if (operador.tipo === delegua_1.default.SUBTRACAO) {
            if (typeof esquerda.valor === 'number' && typeof direita.valor === 'number') {
                return;
            }
        }
        throw new excecoes_1.ErroEmTempoDeExecucao(operador, 'Operadores precisam ser números.', operador.linha);
    }
    async visitarExpressaoBinaria(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);
        const tipoEsquerdo = (esquerda === null || esquerda === void 0 ? void 0 : esquerda.hasOwnProperty('tipo'))
            ? esquerda.tipo
            : (0, inferenciador_1.inferirTipoVariavel)(esquerda);
        const tipoDireito = (direita === null || direita === void 0 ? void 0 : direita.hasOwnProperty('tipo'))
            ? direita.tipo
            : (0, inferenciador_1.inferirTipoVariavel)(direita);
        switch (expressao.operador.tipo) {
            case delegua_1.default.EXPONENCIACAO:
                this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                const resultadoExponenciacao = Math.pow(valorEsquerdo, valorDireito);
                return resultadoExponenciacao;
            case delegua_1.default.MAIOR:
                if (this.tiposNumericos.includes(tipoEsquerdo) &&
                    this.tiposNumericos.includes(tipoDireito)) {
                    return Number(valorEsquerdo) > Number(valorDireito);
                }
                return String(valorEsquerdo) > String(valorDireito);
            case delegua_1.default.MAIOR_IGUAL:
                this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                return Number(valorEsquerdo) >= Number(valorDireito);
            case delegua_1.default.MENOR:
                if (this.tiposNumericos.includes(tipoEsquerdo) &&
                    this.tiposNumericos.includes(tipoDireito)) {
                    return Number(valorEsquerdo) < Number(valorDireito);
                }
                return String(valorEsquerdo) < String(valorDireito);
            case delegua_1.default.MENOR_IGUAL:
                this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                return Number(valorEsquerdo) <= Number(valorDireito);
            case delegua_1.default.SUBTRACAO:
            case delegua_1.default.MENOS_IGUAL:
                this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                return Number(valorEsquerdo) - Number(valorDireito);
            case delegua_1.default.ADICAO:
            case delegua_1.default.MAIS_IGUAL:
                // Se ambos os operandos são vetores, concatená-los
                if (Array.isArray(valorEsquerdo) && Array.isArray(valorDireito)) {
                    return valorEsquerdo.concat(valorDireito);
                }
                if (this.tiposNumericos.includes(tipoEsquerdo) &&
                    this.tiposNumericos.includes(tipoDireito)) {
                    return Number(valorEsquerdo) + Number(valorDireito);
                }
                // TODO: Se tipo for 'qualquer', seria uma boa confiar nos operadores
                // tradicionais do JavaScript?
                if (tipoEsquerdo === 'qualquer' || tipoDireito === 'qualquer') {
                    return valorEsquerdo + valorDireito;
                }
                return this.paraTexto(valorEsquerdo) + this.paraTexto(valorDireito);
            case delegua_1.default.DIVISAO:
            case delegua_1.default.DIVISAO_IGUAL:
                this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                return Number(valorEsquerdo) / Number(valorDireito);
            case delegua_1.default.DIVISAO_INTEIRA:
            case delegua_1.default.DIVISAO_INTEIRA_IGUAL:
                this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                return Math.floor(Number(valorEsquerdo) / Number(valorDireito));
            case delegua_1.default.MULTIPLICACAO:
            case delegua_1.default.MULTIPLICACAO_IGUAL:
                if (tipoEsquerdo === delegua_2.default.TEXTO ||
                    tipoDireito === delegua_2.default.TEXTO) {
                    // Sem ambos os valores resolvem como texto, multiplica normal.
                    // Se apenas um resolve como texto, o outro repete o
                    // texto n vezes, sendo n o valor do outro.
                    if (tipoEsquerdo === delegua_2.default.TEXTO &&
                        tipoDireito === delegua_2.default.TEXTO) {
                        throw new excecoes_1.ErroEmTempoDeExecucao(expressao.operador, 'Não é possível multiplicar dois textos.', expressao.linha);
                    }
                    const valorTexto = tipoEsquerdo === delegua_2.default.TEXTO ? valorEsquerdo : valorDireito;
                    const valorQuantidade = tipoEsquerdo === delegua_2.default.TEXTO ? valorDireito : valorEsquerdo;
                    if (typeof valorQuantidade !== 'number') {
                        throw new excecoes_1.ErroEmTempoDeExecucao(expressao.operador, 'Para multiplicar um texto, o outro operando deve ser um número.', expressao.linha);
                    }
                    const textoParaNumero = Number(valorTexto);
                    if (!isNaN(textoParaNumero)) {
                        return textoParaNumero * valorQuantidade;
                    }
                    if (!Number.isInteger(valorQuantidade)) {
                        throw new excecoes_1.ErroEmTempoDeExecucao(expressao.operador, 'A multiplicação de texto exige um número inteiro.', expressao.linha);
                    }
                    if (valorQuantidade < 0) {
                        throw new excecoes_1.ErroEmTempoDeExecucao(expressao.operador, 'Não é possível multiplicar texto por número negativo.', expressao.linha);
                    }
                    return valorTexto.repeat(valorQuantidade);
                }
                return Number(valorEsquerdo) * Number(valorDireito);
            case delegua_1.default.MODULO:
            case delegua_1.default.MODULO_IGUAL:
                // Se o operando esquerdo é uma string, usar formatação de string
                if (tipoEsquerdo === delegua_2.default.TEXTO || typeof valorEsquerdo === 'string') {
                    return this.formatarStringComOperadorPorcentagem(String(valorEsquerdo), direita, // Passar 'direita' ao invés de 'valorDireito' para preservar arrays de tuplas
                    expressao.operador);
                }
                // Caso contrário, operação matemática normal
                this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                return Number(valorEsquerdo) % Number(valorDireito);
            case delegua_1.default.BIT_AND:
                this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                return Number(valorEsquerdo) & Number(valorDireito);
            case delegua_1.default.BIT_XOR:
                this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                return Number(valorEsquerdo) ^ Number(valorDireito);
            case delegua_1.default.BIT_OR:
                this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                return Number(valorEsquerdo) | Number(valorDireito);
            case delegua_1.default.MENOR_MENOR:
                this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                return Number(valorEsquerdo) << Number(valorDireito);
            case delegua_1.default.MAIOR_MAIOR:
                this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                return Number(valorEsquerdo) >> Number(valorDireito);
            case delegua_1.default.DIFERENTE:
                return !this.eIgual(valorEsquerdo, valorDireito);
            case delegua_1.default.IGUAL_IGUAL:
                return this.eIgual(valorEsquerdo, valorDireito);
        }
    }
    /**
     * Faz a chamada do método de uma primitiva (por exemplo, número, texto, etc.) com seus
     * respectivos argumentos.
     * @param {Chamada} expressao A expressão de chamada.
     * @param {MetodoPrimitiva} metodoPrimitivaChamado O método da primitiva chamado.
     * @returns O resultado da chamada do método da primitiva.
     */
    async chamarMetodoPrimitiva(expressao, metodoPrimitivaChamado) {
        const argumentosResolvidos = [];
        for (const argumento of expressao.argumentos) {
            const valorResolvido = await this.avaliar(argumento);
            argumentosResolvidos.push(this.resolverValor(valorResolvido));
        }
        return await metodoPrimitivaChamado.chamar(this, argumentosResolvidos);
    }
    async resolverArgumentosChamada(expressao) {
        const argumentos = [];
        for (let i = 0; i < expressao.argumentos.length; i++) {
            const variavelArgumento = expressao.argumentos[i];
            const nomeArgumento = variavelArgumento.hasOwnProperty('simbolo')
                ? variavelArgumento.simbolo.lexema
                : undefined;
            let valor = await this.avaliar(variavelArgumento);
            argumentos.push({
                nome: nomeArgumento,
                valor,
            });
        }
        return argumentos;
    }
    /**
     * Executa uma chamada de função, método ou classe.
     * @param expressao A expressão chamada.
     * @returns O resultado da chamada.
     */
    async visitarExpressaoDeChamada(expressao) {
        try {
            let variavelEntidadeChamada = await this.avaliar(expressao.entidadeChamada);
            if (variavelEntidadeChamada === null) {
                return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.parentese, 'Chamada de função ou método inexistente: ' +
                    String(expressao.entidadeChamada), expressao.linha));
            }
            if (variavelEntidadeChamada.hasOwnProperty('valorRetornado')) {
                variavelEntidadeChamada = variavelEntidadeChamada.valorRetornado;
            }
            let entidadeChamada = this.resolverValor(variavelEntidadeChamada);
            // Funções anônimas
            if (entidadeChamada instanceof construtos_1.FuncaoConstruto) {
                entidadeChamada = new estruturas_1.DeleguaFuncao(null, entidadeChamada);
            }
            if (entidadeChamada instanceof estruturas_1.DeleguaModulo) {
                return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.parentese, 'Entidade chamada é um módulo de Delégua. Provavelmente você quer chamar um de seus componentes?', expressao.linha));
            }
            if (entidadeChamada instanceof metodo_primitiva_1.MetodoPrimitiva) {
                return await this.chamarMetodoPrimitiva(expressao, entidadeChamada);
            }
            const argumentos = await this.resolverArgumentosChamada(expressao);
            const aridade = entidadeChamada.aridade
                ? entidadeChamada.aridade()
                : entidadeChamada.length;
            // Completar os argumentos não preenchidos com valores indefinidos.
            if (argumentos.length < aridade) {
                const diferenca = aridade - argumentos.length;
                for (let i = 0; i < diferenca; i++) {
                    argumentos.push({
                        nome: null,
                        valor: null,
                    });
                }
            }
            if (entidadeChamada instanceof estruturas_1.FuncaoPadrao) {
                try {
                    return entidadeChamada.chamar(this, argumentos.map((a) => a && this.resolverValor(a.valor)), expressao.entidadeChamada.simbolo // TODO: O que exatamente pode ser aqui?
                    );
                }
                catch (erro) {
                    this.erros.push({
                        erroInterno: erro,
                        linha: expressao.linha,
                        hashArquivo: expressao.hashArquivo,
                    });
                }
            }
            // Por algum motivo misterioso, `entidadeChamada instanceof Chamavel` dá `false` em Liquido,
            // mesmo que esteja tudo certo com `DeleguaFuncao`,
            // então precisamos testar o nome do construtor também.
            if (entidadeChamada instanceof estruturas_1.Chamavel ||
                entidadeChamada.constructor.name === 'DeleguaFuncao') {
                const retornoEntidadeChamada = await entidadeChamada.chamar(this, argumentos);
                return retornoEntidadeChamada;
            }
            // Chamadas a `super()`.
            // Basicamente, chamar o construtor da superclasse.
            if (expressao.entidadeChamada instanceof construtos_1.Super) {
                const descritorSuperclasse = variavelEntidadeChamada.classe.superClasse;
                const metodoConstrutor = descritorSuperclasse.encontrarMetodo('construtor');
                await metodoConstrutor.chamar(this, argumentos);
                return null;
            }
            // A função chamada pode ser de uma biblioteca JavaScript.
            // Neste caso apenas testamos se o tipo é uma função.
            // Casos que passam aqui: chamadas a métodos de bibliotecas de Delégua.
            if (typeof entidadeChamada === primitivos_1.default.FUNCAO) {
                let objeto = null;
                if (expressao.entidadeChamada.objeto) { // TODO: Qual o tipo certo aqui?
                    objeto = await this.avaliar(expressao.entidadeChamada.objeto);
                }
                return entidadeChamada.apply(this.resolverValor(objeto), argumentos);
            }
            return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.parentese, 'Só pode chamar função ou classe.', expressao.linha));
        }
        catch (erro) {
            this.erros.push({
                erroInterno: erro,
                linha: expressao.linha,
                hashArquivo: expressao.hashArquivo,
            });
        }
    }
    /**
     * Execução de uma expressão de atribuição.
     * @param expressao A expressão.
     * @returns O valor atribuído.
     */
    async visitarExpressaoDeAtribuicao(expressao) {
        const valor = await this.avaliar(expressao.valor);
        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;
                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);
                }
                break;
            default:
                throw new excecoes_1.ErroEmTempoDeExecucao(null, `Atribuição com caso faltante: ${JSON.stringify(expressao)}.`);
        }
        return valorResolvido;
    }
    procurarVariavel(simbolo) {
        return this.pilhaEscoposExecucao.obterValorVariavel(simbolo);
    }
    visitarExpressaoDeVariavel(expressao) {
        return this.procurarVariavel(expressao.simbolo);
    }
    async visitarDeclaracaoDeExpressao(declaracao) {
        return await this.avaliar(declaracao.expressao);
    }
    logicaContemOuEm(esquerda, direita, expressao) {
        const valorDireitoResolvido = this.resolverValor(direita);
        if (Array.isArray(valorDireitoResolvido) || typeof valorDireitoResolvido === primitivos_1.default.TEXTO) {
            const avaliacao = valorDireitoResolvido.includes(esquerda);
            return expressao.negado ? !avaliacao : avaliacao;
        }
        if (valorDireitoResolvido !== null && typeof valorDireitoResolvido === 'object') {
            const avaliacao = esquerda in valorDireitoResolvido;
            return expressao.negado ? !avaliacao : avaliacao;
        }
        throw new excecoes_1.ErroEmTempoDeExecucao(esquerda, `Tipo de chamada inválida com '${expressao.operador.tipo}'.`, expressao.linha);
    }
    async visitarExpressaoLogica(expressao) {
        const esquerda = await this.avaliar(expressao.esquerda);
        if ([delegua_1.default.EM, delegua_1.default.CONTEM].includes(expressao.operador.tipo)) {
            const direita = await this.avaliar(expressao.direita);
            // `3 em lista` é igual a `lista contém 3`.
            // Portanto, precisamos inverter os operandos de acordo com a
            // palavra reservada usada.
            switch (expressao.operador.tipo) {
                case delegua_1.default.EM:
                    return this.logicaContemOuEm(esquerda, direita, expressao);
                case delegua_1.default.CONTEM:
                    return this.logicaContemOuEm(direita, esquerda, expressao);
            }
        }
        // se um estado for verdadeiro, retorna verdadeiro
        if (expressao.operador.tipo === delegua_1.default.OU) {
            if (this.eVerdadeiro(esquerda))
                return esquerda;
        }
        // se um estado for falso, retorna falso
        if (expressao.operador.tipo === delegua_1.default.E) {
            if (!this.eVerdadeiro(esquerda))
                return esquerda;
        }
        return await this.avaliar(expressao.direita);
    }
    async visitarDeclaracaoPara(declaracao) {
        const declaracaoInicializador = Array.isArray(declaracao.inicializador)
            ? declaracao.inicializador[0]
            : declaracao.inicializador;
        if (declaracaoInicializador !== null) {
            await this.avaliar(declaracaoInicializador);
        }
        let retornoExecucao;
        while (!(retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.Quebra)) {
            if (declaracao.condicao !== null &&
                !this.eVerdadeiro(await this.avaliar(declaracao.condicao))) {
                break;
            }
            try {
                retornoExecucao = await this.executar(declaracao.corpo);
                if (retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.SustarQuebra) {
                    return null;
                }
                if (retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.ContinuarQuebra) {
                    retornoExecucao = null;
                }
            }
            catch (erro) {
                this.erros.push({
                    erroInterno: erro,
                    linha: declaracao.linha,
                    hashArquivo: declaracao.hashArquivo,
                });
                return Promise.reject(erro);
            }
            if (declaracao.incrementar !== null) {
                await this.avaliar(declaracao.incrementar);
            }
        }
        return retornoExecucao;
    }
    // TODO: Descobrir se mais algum dialeto, fora Delégua e Pituguês, usam isso.
    async visitarDeclaracaoParaCada(declaracao) {
        let retornoExecucao;
        // Posição atual precisa ser reiniciada, pois pode estar dentro de outro
        // laço de repetição.
        declaracao.posicaoAtual = 0;
        const vetorResolvido = await this.avaliar(declaracao.vetorOuDicionario);
        let valorVetorResolvido = this.resolverValor(vetorResolvido);
        // Se até aqui vetor resolvido é um dicionário, converte dicionário
        // para vetor de duplas.
        // TODO: Converter elementos para `Construto` se necessário.
        if (declaracao.vetorOuDicionario.tipo === 'dicionário') {
            valorVetorResolvido = Object.entries(valorVetorResolvido).map((v) => new construtos_1.Dupla(v[0], v[1]));
        }
        if (!Array.isArray(valorVetorResolvido)) {
            return Promise.reject("Variável ou literal provida em instrução 'para cada' não é um vetor.");
        }
        while (!(retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.Quebra) &&
            declaracao.posicaoAtual < valorVetorResolvido.length) {
            try {
                if (declaracao.variavelIteracao instanceof construtos_1.Variavel) {
                    this.pilhaEscoposExecucao.definirVariavel(declaracao.variavelIteracao.simbolo.lexema, valorVetorResolvido[declaracao.posicaoAtual]);
                }
                if (declaracao.variavelIteracao instanceof construtos_1.Dupla) {
                    const valorComoDupla = valorVetorResolvido[declaracao.posicaoAtual];
                    const promises = await Promise.all([
                        this.avaliar(declaracao.variavelIteracao.primeiro),
                        this.avaliar(declaracao.variavelIteracao.segundo)
                    ]);
                    // TODO: O que fazer quando não forem literais?
                    this.pilhaEscoposExecucao.definirVariavel(String(promises[0].valor), valorComoDupla.primeiro);
                    this.pilhaEscoposExecucao.definirVariavel(String(promises[1].valor), valorComoDupla.segundo);
                }
                retornoExecucao = await this.executar(declaracao.corpo);
                if (retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.SustarQuebra) {
                    return null;
                }
                if (retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.ContinuarQuebra) {
                    retornoExecucao = null;
                }
                declaracao.posicaoAtual++;
            }
            catch (erro) {
                this.erros.push({
                    erroInterno: erro,
                    linha: declaracao.linha,
                    hashArquivo: declaracao.hashArquivo,
                });
                return Promise.reject(erro);
            }
        }
        return retornoExecucao;
    }
    /**
     * Executa uma expressão Se, que tem uma condição, pode ter um bloco
     * Senão, e múltiplos blocos Senão-se.
     * @param declaracao A declaração Se.
     * @returns O resultado da avaliação do bloco cuja condição é verdadeira.
     */
    async visitarDeclaracaoSe(declaracao) {
        const avaliacaoCondicaoSe = await this.avaliar(declaracao.condicao);
        if (this.eVerdadeiro(avaliacaoCondicaoSe)) {
            return await this.executar(declaracao.caminhoEntao);
        }
        for (let i = 0; i < declaracao.caminhosSeSenao.length; i++) {
            // TODO: Qual o tipo de `atual`?
            const atual = declaracao.caminhosSeSenao[i];
            if (this.eVerdadeiro(await this.avaliar(atual.condicao))) {
                return await this.executar(atual.caminho);
            }
        }
        if (declaracao.caminhoSenao !== null) {
            return await this.executar(declaracao.caminhoSenao);
        }
        return null;
    }
    async visitarDeclaracaoEnquanto(declaracao) {
        let retornoExecucao;
        while (!(retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.Quebra) &&
            this.eVerdadeiro(await this.avaliar(declaracao.condicao))) {
            try {
                retornoExecucao = await this.executar(declaracao.corpo);
                if (retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.SustarQuebra) {
                    return null;
                }
                if (retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.ContinuarQuebra) {
                    retornoExecucao = null;
                }
            }
            catch (erro) {
                this.erros.push({
                    erroInterno: erro,
                    linha: declaracao.linha,
                    hashArquivo: declaracao.hashArquivo,
                });
                return Promise.reject(erro);
            }
        }
        return retornoExecucao;
    }
    async visitarDeclaracaoEscolha(declaracao) {
        const condicaoEscolha = await this.avaliar(declaracao.identificadorOuLiteral);
        const valorCondicaoEscolha = this.resolverValor(condicaoEscolha);
        const caminhos = declaracao.caminhos;
        const caminhoPadrao = declaracao.caminhoPadrao;
        let encontrado = false;
        try {
            for (let i = 0; i < caminhos.length; i++) {
                const caminho = caminhos[i];
                for (let j = 0; j < caminho.condicoes.length; j++) {
                    const condicaoAvaliada = await this.avaliar(caminho.condicoes[j]);
                    if (condicaoAvaliada === valorCondicaoEscolha) {
                        encontrado = true;
                        try {
                            await this.executarBloco(caminho.declaracoes);
                        }
                        catch (erro) {
                            this.erros.push({
                                erroInterno: erro,
                                linha: declaracao.linha,
                                hashArquivo: declaracao.hashArquivo,
                            });
                            return Promise.reject(erro);
                        }
                    }
                }
            }
            if (caminhoPadrao !== null && !encontrado) {
                await this.executarBloco(caminhoPadrao.declaracoes);
            }
        }
        catch (erro) {
            this.erros.push({
                erroInterno: erro,
                linha: declaracao.linha,
                hashArquivo: declaracao.hashArquivo,
            });
            throw erro;
        }
    }
    async visitarDeclaracaoFazer(declaracao) {
        let retornoExecucao;
        do {
            try {
                retornoExecucao = await this.executar(declaracao.caminhoFazer);
                if (retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.SustarQuebra) {
                    return null;
                }
                if (retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.ContinuarQuebra) {
                    retornoExecucao = null;
                }
            }
            catch (erro) {
                this.erros.push({
                    erroInterno: erro,
                    linha: declaracao.linha,
                    hashArquivo: declaracao.hashArquivo,
                });
                return Promise.reject(erro);
            }
        } while (!(retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.Quebra) &&
            this.eVerdadeiro(await this.avaliar(declaracao.condicaoEnquanto)));
    }
    /**
     * Interpretação de uma declaração `tente`.
     * @param declaracao O objeto da declaração.
     */
    async visitarDeclaracaoTente(declaracao) {
        let valorRetorno;
        try {
            this.emDeclaracaoTente = true;
            try {
                valorRetorno = await this.executarBloco(declaracao.caminhoTente);
            }
            catch (erro) {
                if (declaracao.caminhoPegue !== null) {
                    // `caminhoPegue` aqui pode ser um construto de função (se `pegue` tem parâmetros)
                    // ou um vetor de `Declaracao` (`pegue` sem parâmetros).
                    // As execuções, portanto, são diferentes.
                    if (Array.isArray(declaracao.caminhoPegue)) {
                        valorRetorno = await this.executarBloco(declaracao.caminhoPegue);
                    }
                    else {
                        const literalErro = new construtos_1.Literal(declaracao.hashArquivo, Number(declaracao.linha), erro.mensagem);
                        const chamadaPegue = new construtos_1.Chamada(declaracao.caminhoPegue.hashArquivo, declaracao.caminhoPegue, [literalErro]);
                        valorRetorno = await chamadaPegue.aceitar(this);
                    }
                }
            }
        }
        finally {
            if (declaracao.caminhoFinalmente !== null)
                valorRetorno = await this.executarBloco(declaracao.caminhoFinalmente);
            this.emDeclaracaoTente = false;
        }
        return valorRetorno;
    }
    async visitarDeclaracaoImportar(declaracao) {
        return Promise.reject('Importação de arquivos não suportada por Interpretador Base.');
    }
    async avaliarArgumentosEscreva(argumentos) {
        let formatoTexto = '';
        for (const argumento of argumentos) {
            let resultadoAvaliacao = await this.avaliar(argumento);
            if (resultadoAvaliacao && resultadoAvaliacao.hasOwnProperty && resultadoAvaliacao.hasOwnProperty('valorRetornado')) {
                resultadoAvaliacao = resultadoAvaliacao.valorRetornado;
            }
            let valor = this.resolverValor(resultadoAvaliacao);
            formatoTexto += `${this.paraTexto(valor)} `;
        }
        return formatoTexto.trimEnd();
    }
    /**
     * Execução de uma escrita na saída padrão, sem quebras de linha.
     * Implementada para alguns dialetos, como VisuAlg.
     * @param declaracao A declaração.
     * @returns Sempre nulo, por convenção de visita.
     */
    async visitarDeclaracaoEscrevaMesmaLinha(declaracao) {
        try {
            const formatoTexto = await this.avaliarArgumentosEscreva(declaracao.argumentos);
            this.funcaoDeRetornoMesmaLinha(formatoTexto);
            return {
                tipo: 'vazio',
                tipoExplicito: false,
            };
        }
        catch (erro) {
            this.erros.push({
                erroInterno: erro,
                linha: declaracao.linha,
                hashArquivo: declaracao.hashArquivo,
            });
        }
    }
    /**
     * Execução de uma escrita na saída configurada, que pode ser `console` (padrão) ou
     * alguma função para escrever numa página Web.
     * @param declaracao A declaração.
     * @returns Sempre nulo, por convenção de visita.
     */
    async visitarDeclaracaoEscreva(declaracao) {
        try {
            const formatoTexto = await this.avaliarArgumentosEscreva(declaracao.argumentos);
            this.funcaoDeRetorno(formatoTexto);
            return {
                tipo: 'vazio',
                tipoExplicito: false,
            };
        }
        catch (erro) {
            this.erros.push({
                erroInterno: erro,
                linha: declaracao.linha,
                hashArquivo: declaracao.hashArquivo,
            });
        }
    }
    /**
     * Empilha declarações na pilha de escopos de execução, cria um novo ambiente e
     * executa as declarações empilhadas.
     * Se o retorno do último bloco foi uma exceção (normalmente um erro em tempo de execução),
     * atira a exceção daqui.
     * Isso é usado, por exemplo, em blocos tente ... pegue ... finalmente.
     * @param declaracoes Um vetor de declaracoes a ser executado.
     * @param ambiente O ambiente de execução quando houver, como parâmetros, argumentos, etc.
     */
    async executarBloco(declaracoes, ambiente) {
        const escopoExecucao = {
            declaracoes: declaracoes,
            declaracaoAtual: 0,
            espacoMemoria: ambiente || new espaco_memoria_1.EspacoMemoria(),
            finalizado: false,
            tipo: 'outro',
            emLacoRepeticao: false,
        };
        this.pilhaEscoposExecucao.empilhar(escopoExecucao);
        const retornoUltimoEscopo = await this.executarUltimoEscopo();
        if (retornoUltimoEscopo instanceof excecoes_1.ErroEmTempoDeExecucao) {
            return Promise.reject(retornoUltimoEscopo);
        }
        return retornoUltimoEscopo;
    }
    async visitarExpressaoBloco(declaracao) {
        return await this.executarBloco(declaracao.declaracoes);
    }
    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;
    }
    /**
     * Executa expressão de definição de constante.
     * @param declaracao A declaração `Const`.
     * @returns Um descritor de informações importantes para o retorno externo.
     */
    async visitarDeclaracaoConst(declaracao) {
        const valorFinal = await this.avaliacaoDeclaracaoVarOuConst(declaracao);
        this.pilhaEscoposExecucao.definirConstante(declaracao.simbolo.lexema, valorFinal, declaracao.tipo);
        return {
            tipo: declaracao.tipo,
            tipoExplicito: declaracao.tipoExplicito,
        };
    }
    /**
     * Executa expressão de definição de múltiplas constantes.
     * @param declaracao A declaração `ConstMultiplo`.
     * @returns Sempre retorna nulo.
     */
    async visitarDeclaracaoConstMultiplo(declaracao) {
        const valoresFinais = await this.avaliacaoDeclaracaoVarOuConst(declaracao);
        const tipoIndividual = declaracao.tipo.replace('[]', '');
        for (let [indice, valor] of valoresFinais.entries()) {
            this.pilhaEscoposExecucao.definirConstante(declaracao.simbolos[indice].lexema, valor, tipoIndividual);
        }
        return null;
    }
    visitarExpressaoContinua(declaracao) {
        return new quebras_1.ContinuarQuebra();
    }
    visitarExpressaoSustar(declaracao) {
        return new quebras_1.SustarQuebra();
    }
    async visitarExpressaoRetornar(declaracao) {
        let valor = null;
        if (declaracao.valor != null)
            valor = await this.avaliar(declaracao.valor);
        return new quebras_1.RetornoQuebra(valor);
    }
    async visitarExpressaoFuncaoConstruto(funcaoConstruto) {
        return new estruturas_1.DeleguaFuncao(null, funcaoConstruto);
    }
    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_2.default.TUPLA) {
            return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.objeto.simbolo.lexema, '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);
        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;
        }
        else if (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));
        }
    }
    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) {
                while (valorIndice < 0) {
                    valorIndice += objeto.length;
                }
            }
            if (valorIndice >= objeto.length) {
                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) {
                while (valorIndice < 0) {
                    valorIndice += objeto.length;
                }
            }
            if (valorIndice >= objeto.length) {
                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));
    }
    /**
        * Método base para acesso a intervalo.
        * Por padrão lança erro, pois a maioria dos dialetos (como Delégua padrão)
        * ainda não suporta isso nativamente, apenas Pituguês.
    */
    visitarExpressaoAcessoIntervaloVariavel(expressao) {
        return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.simboloFechamento, "Acesso por intervalo não implementado para este dialeto.", expressao.linha));
    }
    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);
        if (objeto.constructor === estruturas_1.ObjetoDeleguaClasse) {
            objeto.definir(expressao.nome, valor);
            return valor;
        }
        if (objeto.constructor === Object) {
            objeto[expressao.nome.lexema] = valor;
        }
    }
    visitarDeclaracaoDefinicaoFuncao(declaracao) {
        const funcao = new estruturas_1.DeleguaFuncao(declaracao.simbolo.lexema, declaracao.funcao);
        this.pilhaEscoposExecucao.definirVariavel(declaracao.simbolo.lexema, funcao);
        this.pilhaEscoposExecucao.registrarReferenciaFuncao(declaracao.id, funcao);
        return Promise.resolve({
            declaracao: funcao,
        });
    }
    /**
     * Executa uma declaração de classe.
     * Uma variável do tipo `DeleguaClasse` é adicionada à pilha de escopos de execução.
     * @param declaracao A declaração de classe.
     * @returns Sempre retorna nulo, por ser requerido pelo contrato de visita.
     */
    async visitarDeclaracaoClasse(declaracao) {
        let superClasse = null;
        if (declaracao.superClasse !== null && declaracao.superClasse !== undefined) {
            const variavelSuperClasse = await this.avaliar(declaracao.superClasse);
            superClasse = variavelSuperClasse.valor;
            if (!(superClasse instanceof estruturas_1.DescritorTipoClasse)) {
                throw new excecoes_1.ErroEmTempoDeExecucao(declaracao.superClasse.nome, 'Superclasse precisa ser uma classe.', declaracao.linha);
            }
        }
        // TODO: Precisamos disso?
        this.pilhaEscoposExecucao.definirVariavel(declaracao.simbolo.lexema, declaracao);
        if (declaracao.superClasse !== null && declaracao.superClasse !== undefined) {
            this.pilhaEscoposExecucao.definirVariavel('super', superClasse);
        }
        const metodos = {};
        const definirMetodos = declaracao.metodos;
        for (let i = 0; i < declaracao.metodos.length; i++) {
            const metodoAtual = definirMetodos[i];
            const eInicializador = metodoAtual.simbolo.lexema === 'construtor';
            const funcao = new estruturas_1.DeleguaFuncao(metodoAtual.simbolo.lexema, metodoAtual.funcao, undefined, eInicializador);
            metodos[metodoAtual.simbolo.lexema] = funcao;
        }
        const descritorTipoClasse = new estruturas_1.DescritorTipoClasse(declaracao.simbolo, superClasse, metodos, declaracao.propriedades);
        // TODO: Até então, a única exceção a isso é Égua Clássico.
        // Por enquanto, tudo bem deixar isso aqui.
        descritorTipoClasse.dialetoRequerDeclaracaoPropriedades = this.requerDeclaracaoPropriedades;
        this.pilhaEscoposExecucao.atribuirVariavel(declaracao.simbolo, descritorTipoClasse);
        return descritorTipoClasse;
    }
    /**
     * Executa um acesso a método, normalmente de um objeto de classe.
     * @param {AcessoMetodoOuPropriedade} expressao A expressão de acesso.
     * @returns O resultado da execução.
     */
    async visitarExpressaoAcessoMetodoOuPropriedade(expressao) {
        let variavelObjeto = await this.avaliar(expressao.objeto);
        const nomeObjeto = this.resolverNomeObjectoAcessado(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 instanceof estruturas_1.ObjetoDeleguaClasse ||
            objeto.constructor.name === 'ObjetoDeleguaClasse') {
            const valor = objeto.obter(expressao.simbolo);
            if (valor === 0)
                return 0;
            return valor || null;
        }
        // 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 metodo_primitiva_1.MetodoPrimitiva(nomeObjeto, objeto, metodoDePrimitivaDicionario, expressao.simbolo.lexema, 'dicionário');
            }
            return objeto[expressao.simbolo.lexema];
        }
        // 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.simbolo.lexema] === primitivos_1.default.FUNCAO) {
            return objeto[expressao.simbolo.lexema];
        }
        // Caso 2: Objeto tradicional do JavaScript.
        if (typeof objeto[expressao.simbolo.lexema] === primitivos_1.default.OBJETO) {
            return objeto[expressao.simbolo.lexema];
        }
        let tipoObjeto = variavelObjeto.tipo;
        if (tipoObjeto === null || tipoObjeto === undefined) {
            tipoObjeto = (0, inferenciador_1.inferirTipoVariavel)(variavelObjeto);
        }
        // Caso 3: Vetor simples do JavaScript.
        // TODO: Em teoria, isso deve estar no interpretador de Delégua, não aqui.
        if (Array.isArray(objeto)) {
            if (expressao.simbolo.lexema in primitivas_vetor_1.default) {
                const metodoDePrimitivaVetor = primitivas_vetor_1.default[expressao.simbolo.lexema].implementacao;
                // TODO: Um problema a ser resolvido na questão de vetores é quando eles pertencem a outro objeto.
                // Por exemplo, um dicionário.
                // Existe uma lógica nas bibliotecas padrão que, quando a primitiva tem um nome, ela deve ser definida na
                // pilha de escopos, para registrar a mutação do vetor corretamente.
                // Não é uma boa solução. Algo melhor precisa ser feito.
                return new metodo_primitiva_1.MetodoPrimitiva(nomeObjeto, objeto, metodoDePrimitivaVetor, expressao.simbolo.lexema, tipoObjeto);
            }
        }
        // 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;
        }
        return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.simbolo, `Método ou propriedade para objeto não encontrado: ${expressao.simbolo.lexema}.`, expressao.linha));
    }
    visitarExpressaoIsto(expressao) {
        return this.procurarVariavel(expressao.simboloChave);
    }
    async visitarExpressaoDicionario(expressao) {
        const dicionario = {};
        for (let i = 0; i < expressao.chaves.length; i++) {
            if (expressao.esSpread && expressao.esSpread[i]) {
                const dicionarioParaDesempacotar = this.resolverValor(await this.avaliar(expressao.valores[i]));
                // Validação: verificar se é realmente um dicionário
                if (typeof dicionarioParaDesempacotar !== 'object' ||
                    dicionarioParaDesempacotar === null ||
                    Array.isArray(dicionarioParaDesempacotar)) {
                    throw new excecoes_1.ErroEmTempoDeExecucao({ linha: expressao.linha, lexema: '**' }, `Operador '**' só pode ser usado com dicionários. Tipo encontrado: ${dicionarioParaDesempacotar === null
                        ? 'nulo'
                        : Array.isArray(dicionarioParaDesempacotar)
                            ? 'vetor'
                            : typeof dicionarioParaDesempacotar}`, expressao.linha);
                }
                // Desempacotar: copiar todas as propriedades
                Object.assign(dicionario, dicionarioParaDesempacotar);
            }
            else {
                const promises = await Promise.all([
                    this.avaliar(expressao.chaves[i]),
                    this.avaliar(expressao.valores[i]),
                ]);
                if (typeof promises[0] === 'boolean') {
                    const chaveLogico = promises[0] === true ? 'verdadeiro' : 'falso';
                    dicionario[chaveLogico] = this.resolverValor(promises[1]);
                    continue;
                }
                dicionario[promises[0]] = this.resolverValor(promises[1]);
            }
        }
        return dicionario;
    }
    async visitarExpressaoVetor(expressao) {
        const valores = [];
        for (let i = 0; i < expressao.valores.length; i++) {
            valores.push(this.resolverValor(await this.avaliar(expressao.valores[i])));
        }
        return valores.filter((v) => v !== null && v !== undefined);
    }
    visitarExpressaoSuper(expressao) {
        const objeto = this.pilhaEscoposExecucao.obterVariavelPorNome('isto');
        return objeto.valor;
    }
    /**
     * Executa expressão de definição de variável.
     * @param declaracao A declaração Var
     * @returns Um descritor de informações importantes para o retorno externo.
     */
    async visitarDeclaracaoVar(declaracao) {
        const valorFinal = await this.avaliacaoDeclaracaoVarOuConst(declaracao);
        let tipoResolvido = declaracao.tipo;
        if (tipoResolvido.startsWith('função<')) {
            tipoResolvido = tipoResolvido.replace('função<', '').replace('>', '');
        }
        this.pilhaEscoposExecucao.definirVariavel(declaracao.simbolo.lexema, valorFinal, tipoResolvido);
        // TODO: É relevante registrar uma declaração de variável no
        // resultado do interpretador?
        /* return {
            tipo: declaracao.tipo,
            tipoExplicito: declaracao.tipoExplicito
        }; */
        return null;
    }
    /**
     * Executa expressão de definição de múltiplas variáveis.
     * @param declaracao A declaração `VarMultiplo`.
     * @returns Sempre retorna nulo.
     */
    async visitarDeclaracaoVarMultiplo(declaracao) {
        const valoresFinais = await this.avaliacaoDeclaracaoVarOuConst(declaracao);
        const tipoIndividual = declaracao.tipo.replace('[]', '');
        for (let [indice, valor] of valoresFinais.entries()) {
            this.pilhaEscoposExecucao.definirVariavel(declaracao.simbolos[indice].lexema, valor, tipoIndividual);
        }
        return null;
    }
    /**
     * Formata uma string usando o operador % (similar ao Python).
     * @param formato A string de formato com especificadores (ex: "Olá %s").
     * @param valores Os valores para substituir (pode ser um único valor ou uma tupla/vetor).
     * @param operador O símbolo do operador para mensagens de erro.
     * @returns A string formatada.
     */
    formatarStringComOperadorPorcentagem(formato, valores, operador) {
        let valoresArray;
        // Verificar se é uma TuplaN (verifica instanceof OU propriedade elementos/tipo para maior compatibilidade)
        if (valores instanceof construtos_1.TuplaN ||
            (valores && valores.tipo === 'tupla' && valores.elementos && Array.isArray(valores.elementos))) {
            valoresArray = valores.elementos.map((elem) => {
                if (elem instanceof construtos_1.Literal || (elem && typeof elem === 'object' && elem.hasOwnProperty('valor'))) {
                    return this.resolverValor(elem.valor);
                }
                return this.resolverValor(elem);
            });
        }
        else if (Array.isArray(valores)) {
            valoresArray = valores.map(v => this.resolverValor(v));
        }
        else {
            const valorResolvido = this.resolverValor(valores);
            if (Array.isArray(valorResolvido)) {
                valoresArray = valorResolvido.map(v => this.resolverValor(v));
            }
            else {
                valoresArray = [valorResolvido];
            }
        }
        let indiceValor = 0;
        let resultado = '';
        let i = 0;
        while (i < formato.length) {
            if (formato[i] === '%') {
                if (i + 1 >= formato.length) {
                    throw new excecoes_1.ErroEmTempoDeExecucao(operador, 'Formato inválido: % no final da string', operador.linha);
                }
                const proximoChar = formato[i + 1];
                // %% -> % literal
                if (proximoChar === '%') {
                    resultado += '%';
                    i += 2;
                    continue;
                }
                // Verificar se ainda temos valores para substituir
                if (indiceValor >= valoresArray.length) {
                    throw new excecoes_1.ErroEmTempoDeExecucao(operador, 'Argumentos insuficientes para a string de formatação', operador.linha);
                }
                const valor = valoresArray[indiceValor];
                indiceValor++;
                // Processar especificadores de formato
                if (proximoChar === 's') {
                    // %s - string
                    resultado += this.paraTexto(valor);
                    i += 2;
                }
                else if (proximoChar === 'd') {
                    // %d - inteiro
                    const valorNumerico = Number(valor);
                    if (isNaN(valorNumerico)) {
                        throw new excecoes_1.ErroEmTempoDeExecucao(operador, `Esperado número para %d, mas recebeu ${typeof valor}`, operador.linha);
                    }
                    resultado += Math.floor(valorNumerico).toString();
                    i += 2;
                }
                else if (proximoChar === 'f') {
                    // %f - flutuante
                    const valorNumerico = Number(valor);
                    if (isNaN(valorNumerico)) {
                        throw new excecoes_1.ErroEmTempoDeExecucao(operador, `Esperado número para %f, mas recebeu ${typeof valor}`, operador.linha);
                    }
                    resultado += valorNumerico.toString();
                    i += 2;
                }
                else if (proximoChar === '.') {
                    // %.nf - flutuante com n casas decimais
                    let j = i + 2;
                    let casasDecimais = '';
                    while (j < formato.length && formato[j] >= '0' && formato[j] <= '9') {
                        casasDecimais += formato[j];
                        j++;
                    }
                    if (j >= formato.length || formato[j] !== 'f') {
                        throw new excecoes_1.ErroEmTempoDeExecucao(operador, 'Formato inválido: esperado %.nf (ex: %.2f)', operador.linha);
                    }
                    const precisao = parseInt(casasDecimais, 10);
                    const valorNumerico = Number(valor);
                    if (isNaN(valorNumerico)) {
                        throw new excecoes_1.ErroEmTempoDeExecucao(operador, `Esperado número para %.${precisao}f, mas recebeu ${typeof valor}`, operador.linha);
                    }
                    resultado += valorNumerico.toFixed(precisao);
                    i = j + 1;
                }
                else if (proximoChar === 'x') {
                    // %x - hexadecimal (caixa baixa)
                    const valorNumerico = Number(valor);
                    if (isNaN(valorNumerico)) {
                        throw new excecoes_1.ErroEmTempoDeExecucao(operador, `Esperado número para %x, mas recebeu ${typeof valor}`, operador.linha);
                    }
                    resultado += Math.floor(valorNumerico).toString(16);
                    i += 2;
                }
                else if (proximoChar === 'X') {
                    // %X - hexadecimal (caixa alta)
                    const valorNumerico = Number(valor);
                    if (isNaN(valorNumerico)) {
                        throw new excecoes_1.ErroEmTempoDeExecucao(operador, `Esperado número para %X, mas recebeu ${typeof valor}`, operador.linha);
                    }
                    resultado += Math.floor(valorNumerico).toString(16).toUpperCase();
                    i += 2;
                }
                else if (proximoChar === 'o') {
                    // %o - octal
                    const valorNumerico = Number(valor);
                    if (isNaN(valorNumerico)) {
                        throw new excecoes_1.ErroEmTempoDeExecucao(operador, `Esperado número para %o, mas recebeu ${typeof valor}`, operador.linha);
                    }
                    resultado += Math.floor(valorNumerico).toString(8);
                    i += 2;
                }
                else {
                    throw new excecoes_1.ErroEmTempoDeExecucao(operador, `Especificador de formato desconhecido: %${proximoChar}`, operador.linha);
                }
            }
            else {
                resultado += formato[i];
                i++;
            }
        }
        // Verificar se há valores não utilizados
        if (indiceValor < valoresArray.length) {
            throw new excecoes_1.ErroEmTempoDeExecucao(operador, 'Nem todos os argumentos foram convertidos durante a formatação da string.', operador.linha);
        }
        return resultado;
    }
    paraTexto(objeto) {
        if (objeto === null || objeto === undefined)
            return delegua_2.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)
            return this.paraTexto(objeto.valor);
        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 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) {
                if (typeof elemento === 'object') {
                    retornoVetor += `${JSON.stringify(elemento)}, `;
                    continue;
                }
                retornoVetor +=
                    typeof elemento === 'string'
                        ? `'${elemento}', `
                        : `${this.paraTexto(elemento)}, `;
            }
            if (retornoVetor.length > 1) {
                retornoVetor = retornoVetor.slice(0, -2);
            }
            retornoVetor += ']';
            return retornoVetor;
        }
        switch (objeto.constructor) {
            case Object:
                if ('tipo' in objeto) {
                    switch (objeto.tipo) {
                        case 'dicionário':
                            const valorResolvido = this.resolverValorRecursivo(objeto.valor);
                            return JSON.stringify(valorResolvido);
                        default:
                            return objeto.valor;
                    }
                }
        }
        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';
                }
                objetoEscrita[propriedade] = valor;
            }
            return JSON.stringify(objetoEscrita);
        }
        return objeto.toString();
    }
    /**
     * Efetivamente executa uma declaração.
     * @param declaracao A declaração a ser executada.
     * @returns O resultado parcial da execução, normalmente usado por
     *          ferramentas externas.
     */
    async executar(declaracao) {
        const resultado = await declaracao.aceitar(this);
        // Alguns casos não possuem retorno, como declarações `se`, `enquanto`, etc.,
        // que não satisfazem suas respectivas condições.
        if (resultado === null || resultado === undefined) {
            return null;
        }
        // Se o retorno já possui um `valorRetornado`, apenas retorna o resultado.
        if (resultado.hasOwnProperty('valorRetornado')) {
            return resultado;
        }
        let tipoResultado = resultado.tipo;
        switch (resultado.constructor) {
            case estruturas_1.DescritorTipoClasse:
                tipoResultado = resultado.simboloOriginal.lexema;
                break;
            default:
                if (!tipoResultado) {
                    tipoResultado = (0, inferenciador_1.inferirTipoVariavel)(resultado);
                }
                break;
        }
        return {
            hashArquivo: declaracao.hashArquivo,
            linha: declaracao.linha,
            valorRetornado: resultado,
            tipo: tipoResultado,
        };
    }
    /**
     * 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 {
            this.pilhaEscoposExecucao.removerUltimo();
            const escopoAnterior = this.pilhaEscoposExecucao.topoDaPilha();
            if (manterAmbiente || (retornoExecucao && retornoExecucao.preservarEscopo === true)) {
                escopoAnterior.espacoMemoria.valores = Object.assign(escopoAnterior.espacoMemoria.valores, ultimoEscopo.espacoMemoria.valores);
            }
        }
    }
    /**
     * Interpretação sem depurador, com medição de performance.
     * 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 = false) {
        this.erros = [];
        this.emDeclaracaoTente = false;
        this.linhaDeclaracaoAtual = -1;
        this.hashArquivoDeclaracaoAtual = -1;
        const escopoExecucao = {
            declaracoes: declaracoes,
            declaracaoAtual: 0,
            espacoMemoria: new espaco_memoria_1.EspacoMemoria(),
            finalizado: false,
            tipo: 'outro',
            emLacoRepeticao: false,
        };
        this.pilhaEscoposExecucao.empilhar(escopoExecucao);
        const inicioInterpretacao = (0, browser_process_hrtime_1.default)();
        try {
            const retornoOuErro = await this.executarUltimoEscopo(manterAmbiente);
            // TODO: Esta lógica já ocorre em `executarUltimoEscopo`.
            // Estudar remoção.
            if (retornoOuErro instanceof excecoes_1.ErroEmTempoDeExecucao) {
                this.erros.push(retornoOuErro);
            }
            if (retornoOuErro !== undefined) {
                this.resultadoInterpretador.push(retornoOuErro);
            }
        }
        catch (erro) {
            // TODO: Estudar remoção do `catch`.
            throw new Error(`Não deveria estar caindo aqui. Há erros no interpretador que não estão tratados corretamente. Erro atual: ${JSON.stringify(erro)}.`);
        }
        finally {
            if (this.performance) {
                const deltaInterpretacao = (0, browser_process_hrtime_1.default)(inicioInterpretacao);
                console.log(`[Interpretador] Tempo para interpretaçao: ${deltaInterpretacao[0] * 1e9 + deltaInterpretacao[1]}ns`);
            }
            const retorno = {
                erros: this.erros,
                resultado: this.resultadoInterpretador,
            };
            this.resultadoInterpretador = [];
            return retorno;
        }
    }
}
exports.InterpretadorBase = InterpretadorBase;
//# sourceMappingURL=interpretador-base.js.map