"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.InterpretadorEguaClassico = void 0;
const espaco_memoria_1 = require("../../espaco-memoria");
const chamavel_1 = require("../../estruturas/chamavel");
const descritor_tipo_classe_1 = require("../../estruturas/descritor-tipo-classe");
const delegua_funcao_1 = require("../../estruturas/delegua-funcao");
const funcao_padrao_1 = require("../../estruturas/funcao-padrao");
const modulo_1 = require("../../estruturas/modulo");
const objeto_delegua_classe_1 = require("../../estruturas/objeto-delegua-classe");
const excecoes_1 = require("../../../excecoes");
const quebras_1 = require("../../../quebras");
const inferenciador_1 = require("../../../inferenciador");
const pilha_escopos_execucao_1 = require("../../pilha-escopos-execucao");
const egua_classico_1 = __importDefault(require("../../../tipos-de-simbolos/egua-classico"));
const biblioteca_global_1 = __importDefault(require("../../../bibliotecas/dialetos/egua-classico/biblioteca-global"));
const resolvedor_1 = require("./resolvedor/resolvedor");
/**
 * O Interpretador visita todos os elementos complexos gerados pelo analisador sintático (_Parser_)
 * e de fato executa a lógica de programação descrita no código.
 */
class InterpretadorEguaClassico {
    constructor(diretorioBase) {
        // Esta variável indica que propriedades de classes precisam ser
        // declaradas para serem válidas.
        // Égua não requer isso, mas precisamos ter o valor
        // aqui por questões de compatibilidade com outros elementos.
        this.requerDeclaracaoPropriedades = false;
        this.interfaceEntradaSaida = null;
        this.emDeclaracaoTente = false;
        this.resolvedor = new resolvedor_1.ResolvedorEguaClassico();
        this.diretorioBase = diretorioBase;
        this.funcaoDeRetorno = console.log;
        this.locais = new Map();
        this.erros = [];
        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);
        (0, biblioteca_global_1.default)(this, this.pilhaEscoposExecucao);
    }
    /* istanbul ignore next */
    visitarExpressaoTuplaN(expressao) {
        throw new Error('Método não implementado.');
    }
    /* istanbul ignore next */
    visitarExpressaoAcessoIntervaloVariavel(expressao) {
        throw new Error('Método não implementado.');
    }
    /* istanbul ignore next */
    visitarDeclaracaoTextoDocumentacao(declaracao) {
        throw new Error('Método não implementado.');
    }
    /* istanbul ignore next */
    visitarExpressaoSeparador(expressao) {
        throw new Error('Método não implementado.');
    }
    /* istanbul ignore next */
    visitarExpressaoComentario(expressao) {
        throw new Error('Método não implementado.');
    }
    resolverValor(objeto) {
        if (objeto === null || objeto === undefined) {
            return objeto;
        }
        if (objeto.hasOwnProperty('valor')) {
            return objeto.valor;
        }
        return objeto;
    }
    /* istanbul ignore next */
    visitarExpressaoArgumentoReferenciaFuncao(expressao) {
        throw new Error('Método não implementado.');
    }
    /* istanbul ignore next */
    visitarExpressaoReferenciaFuncao(expressao) {
        throw new Error('Método não implementado.');
    }
    /* istanbul ignore next */
    visitarExpressaoAcessoMetodo(expressao) {
        throw new Error('Método não implementado.');
    }
    /* istanbul ignore next */
    visitarExpressaoAcessoPropriedade(expressao) {
        throw new Error('Método não implementado.');
    }
    visitarDeclaracaoComentario(declaracao) {
        return Promise.resolve();
    }
    /* istanbul ignore next */
    visitarDeclaracaoTendoComo(declaracao) {
        throw new Error('Método não implementado.');
    }
    /* istanbul ignore next */
    visitarDeclaracaoInicioAlgoritmo(declaracao) {
        throw new Error('Método não implementado.');
    }
    /* istanbul ignore next */
    visitarDeclaracaoCabecalhoPrograma(declaracao) {
        throw new Error('Método não implementado.');
    }
    /* istanbul ignore next */
    visitarExpressaoTupla(expressao) {
        throw new Error('Método não implementado.');
    }
    /* istanbul ignore next */
    visitarExpressaoAcessoElementoMatriz(expressao) {
        throw new Error('Método não implementado.');
    }
    /* istanbul ignore next */
    visitarExpressaoAtribuicaoPorIndicesMatriz(expressao) {
        throw new Error('Método não implementado.');
    }
    /* istanbul ignore next */
    visitarExpressaoExpressaoRegular(expressao) {
        throw new Error('Método não implementado.');
    }
    /* istanbul ignore next */
    visitarExpressaoTipoDe(expressao) {
        throw new Error('Método não implementado.');
    }
    /* istanbul ignore next */
    visitarExpressaoFalhar(expressao) {
        throw new Error('Método não implementado.');
    }
    /* istanbul ignore next */
    visitarDeclaracaoParaCada(declaracao) {
        throw new Error('Método não implementado.');
    }
    /* istanbul ignore next */
    visitarDeclaracaoConst(declaracao) {
        throw new Error('Método não implementado.');
    }
    /* istanbul ignore next */
    visitarDeclaracaoConstMultiplo(declaracao) {
        throw new Error('Método não implementado.');
    }
    /* istanbul ignore next */
    visitarExpressaoFimPara(declaracao) {
        throw new Error('Método não implementado.');
    }
    /* istanbul ignore next */
    visitarExpressaoFormatacaoEscrita(declaracao) {
        throw new Error('Método não implementado.');
    }
    /* istanbul ignore next */
    visitarDeclaracaoEscrevaMesmaLinha(declaracao) {
        throw new Error('Método não implementado.');
    }
    /* istanbul ignore next */
    visitarExpressaoLeia(expressao) {
        throw new Error('Método não implementado.');
    }
    visitarExpressaoLiteral(expressao) {
        return Promise.resolve(expressao.valor);
    }
    avaliar(expressao) {
        return expressao.aceitar(this);
    }
    visitarExpressaoAgrupamento(expressao) {
        return this.avaliar(expressao.expressao);
    }
    eVerdadeiro(objeto) {
        if (objeto === null)
            return false;
        if (typeof objeto === 'boolean')
            return Boolean(objeto);
        if (objeto.hasOwnProperty('valor')) {
            return Boolean(objeto.valor);
        }
        return true;
    }
    verificarOperandoNumero(operador, operando) {
        if (typeof operando === 'number' || operando.tipo === 'número')
            return;
        throw new excecoes_1.ErroEmTempoDeExecucao(operador, 'Operador precisa ser um número.', operador.linha);
    }
    async visitarExpressaoUnaria(expr) {
        const direita = await this.avaliar(expr.direita);
        const valor = this.resolverValor(direita);
        switch (expr.operador.tipo) {
            case egua_classico_1.default.SUBTRACAO:
                this.verificarOperandoNumero(expr.operador, valor);
                return -valor;
            case egua_classico_1.default.NEGACAO:
                return !this.eVerdadeiro(valor);
            case egua_classico_1.default.BIT_NOT:
                return ~valor;
        }
        return null;
    }
    eIgual(esquerda, direita) {
        if (esquerda && esquerda.tipo) {
            if (esquerda.tipo === 'nulo' && direita.tipo && direita.tipo === 'nulo')
                return true;
            if (esquerda.tipo === 'nulo')
                return false;
            return esquerda.valor === direita.valor;
        }
        if (esquerda === null && direita === null)
            return true;
        if (esquerda === null)
            return false;
        return esquerda === direita;
    }
    verificarOperandosNumeros(operador, direita, esquerda) {
        const tipoDireita = direita.tipo
            ? direita.tipo
            : typeof direita === 'number'
                ? 'número'
                : String(NaN);
        const tipoEsquerda = esquerda.tipo
            ? esquerda.tipo
            : typeof esquerda === 'number'
                ? 'número'
                : String(NaN);
        const tiposNumericos = ['inteiro', 'numero', 'número', 'real'];
        if (tiposNumericos.includes(tipoDireita.toLowerCase()) &&
            tiposNumericos.includes(tipoEsquerda.toLowerCase()))
            return;
        throw new excecoes_1.ErroEmTempoDeExecucao(operador, 'Operadores precisam ser números.', operador.linha);
    }
    async visitarExpressaoBinaria(expressao) {
        try {
            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 && esquerda.hasOwnProperty('tipo')
                ? esquerda.tipo
                : (0, inferenciador_1.inferirTipoVariavel)(esquerda);
            const tipoDireito = direita && direita.hasOwnProperty('tipo')
                ? direita.tipo
                : (0, inferenciador_1.inferirTipoVariavel)(direita);
            switch (expressao.operador.tipo) {
                case egua_classico_1.default.EXPONENCIACAO:
                    this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                    return Math.pow(valorEsquerdo, valorDireito);
                case egua_classico_1.default.MAIOR:
                    this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                    return Number(valorEsquerdo) > Number(valorDireito);
                case egua_classico_1.default.MAIOR_IGUAL:
                    this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                    return Number(valorEsquerdo) >= Number(valorDireito);
                case egua_classico_1.default.MENOR:
                    this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                    return Number(valorEsquerdo) < Number(valorDireito);
                case egua_classico_1.default.MENOR_IGUAL:
                    this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                    return Number(valorEsquerdo) <= Number(valorDireito);
                case egua_classico_1.default.SUBTRACAO:
                    this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                    return Number(valorEsquerdo) - Number(valorDireito);
                case egua_classico_1.default.ADICAO:
                    if (tipoEsquerdo === 'número' && tipoDireito === 'número') {
                        return Number(valorEsquerdo) + Number(valorDireito);
                    }
                    else if (tipoEsquerdo === 'texto' && tipoDireito === 'texto') {
                        return String(valorEsquerdo) + String(valorDireito);
                    }
                    throw new excecoes_1.ErroEmTempoDeExecucao(expressao.operador, 'Operadores precisam ser dois números ou duas strings.');
                case egua_classico_1.default.DIVISAO:
                    this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                    return Number(valorEsquerdo) / Number(valorDireito);
                case egua_classico_1.default.MULTIPLICACAO:
                    this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                    return Number(valorEsquerdo) * Number(valorDireito);
                case egua_classico_1.default.MODULO:
                    this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                    return Number(valorEsquerdo) % Number(valorDireito);
                case egua_classico_1.default.BIT_AND:
                    this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                    return Number(valorEsquerdo) & Number(valorDireito);
                case egua_classico_1.default.BIT_XOR:
                    this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                    return Number(valorEsquerdo) ^ Number(valorDireito);
                case egua_classico_1.default.BIT_OR:
                    this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                    return Number(valorEsquerdo) | Number(valorDireito);
                case egua_classico_1.default.MENOR_MENOR:
                    this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                    return Number(valorEsquerdo) << Number(valorDireito);
                case egua_classico_1.default.MAIOR_MAIOR:
                    this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
                    return Number(valorEsquerdo) >> Number(valorDireito);
                case egua_classico_1.default.DIFERENTE:
                    return !this.eIgual(valorEsquerdo, valorDireito);
                case egua_classico_1.default.IGUAL_IGUAL:
                    return this.eIgual(valorEsquerdo, valorDireito);
            }
            return null;
        }
        catch (erro) {
            return Promise.reject(erro);
        }
    }
    async visitarExpressaoDeChamada(expressao) {
        const 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));
        }
        const entidadeChamada = this.resolverValor(variavelEntidadeChamada);
        let argumentos = [];
        for (let i = 0; i < expressao.argumentos.length; i++) {
            const variavelArgumento = expressao.argumentos[i];
            argumentos.push({
                nome: variavelArgumento,
                valor: await this.avaliar(variavelArgumento),
            });
        }
        if (!(entidadeChamada instanceof chamavel_1.Chamavel)) {
            throw new excecoes_1.ErroEmTempoDeExecucao(expressao.parentese, 'Só pode chamar função ou classe.', expressao.linha);
        }
        let parametros;
        if (entidadeChamada instanceof delegua_funcao_1.DeleguaFuncao) {
            parametros = entidadeChamada.declaracao.parametros;
        }
        else if (entidadeChamada instanceof descritor_tipo_classe_1.DescritorTipoClasse) {
            parametros = entidadeChamada.metodos.inicializacao
                ? entidadeChamada.metodos.inicializacao.declaracao.parametros
                : [];
        }
        else {
            parametros = [];
        }
        // Isso aqui completa os parâmetros não preenchidos com nulos.
        if (argumentos.length < entidadeChamada.aridade()) {
            const diferenca = entidadeChamada.aridade() - argumentos.length;
            for (let i = 0; i < diferenca; i++) {
                argumentos.push(null);
            }
        }
        else {
            if (parametros &&
                parametros.length > 0 &&
                parametros[parametros.length - 1]['tipo'] === 'multiplo') {
                let novosArgumentos = argumentos.slice(0, parametros.length - 1);
                novosArgumentos = novosArgumentos.concat(argumentos.slice(parametros.length - 1, argumentos.length));
                argumentos = novosArgumentos;
            }
        }
        if (entidadeChamada instanceof funcao_padrao_1.FuncaoPadrao) {
            return entidadeChamada.chamar(undefined, argumentos.map((a) => this.resolverValor(a)), expressao.entidadeChamada.simbolo);
        }
        return entidadeChamada.chamar(this, argumentos);
    }
    async visitarExpressaoDeAtribuicao(expressao) {
        const valor = await this.avaliar(expressao.valor);
        const alvoVariavel = expressao.alvo;
        this.pilhaEscoposExecucao.atribuirVariavel(alvoVariavel.simbolo, valor);
        return valor;
    }
    procurarVariavel(simbolo, expressao) {
        return this.pilhaEscoposExecucao.obterValorVariavel(simbolo);
    }
    visitarExpressaoDeVariavel(expressao) {
        return Promise.resolve(this.procurarVariavel(expressao.simbolo, expressao));
    }
    async visitarDeclaracaoDeExpressao(declaracao) {
        return await this.avaliar(declaracao.expressao);
    }
    async visitarExpressaoLogica(expressao) {
        const esquerda = await this.avaliar(expressao.esquerda);
        if (expressao.operador.tipo === egua_classico_1.default.EM) {
            const direita = await this.avaliar(expressao.direita);
            if (Array.isArray(direita) || typeof direita === 'string') {
                return direita.includes(esquerda);
            }
            else if (direita.constructor === Object) {
                return esquerda in direita;
            }
            else {
                throw new excecoes_1.ErroEmTempoDeExecucao(esquerda, "Tipo de chamada inválida com 'em'.", expressao.linha);
            }
        }
        // se um estado for verdadeiro, retorna verdadeiro
        if (expressao.operador.tipo === egua_classico_1.default.OU) {
            if (this.eVerdadeiro(esquerda))
                return esquerda;
        }
        // se um estado for falso, retorna falso
        if (expressao.operador.tipo === egua_classico_1.default.E) {
            if (!this.eVerdadeiro(esquerda))
                return esquerda;
        }
        return await this.avaliar(expressao.direita);
    }
    async visitarDeclaracaoSe(declaracao) {
        if (this.eVerdadeiro(await this.avaliar(declaracao.condicao))) {
            return await this.executar(declaracao.caminhoEntao);
        }
        for (let i = 0; i < declaracao.caminhosSeSenao.length; i++) {
            const atual = declaracao.caminhosSeSenao[i];
            // TODO: Qual seria o tipo certo aqui?
            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 visitarDeclaracaoPara(declaracao) {
        if (declaracao.inicializador !== null) {
            await this.avaliar(declaracao.inicializador);
        }
        while (true) {
            if (declaracao.condicao !== null) {
                if (!this.eVerdadeiro(await this.avaliar(declaracao.condicao))) {
                    break;
                }
            }
            try {
                await this.executar(declaracao.corpo);
            }
            catch (erro) {
                throw erro;
            }
            if (declaracao.incrementar !== null) {
                await this.avaliar(declaracao.incrementar);
            }
        }
        return null;
    }
    async visitarDeclaracaoFazer(declaracao) {
        do {
            try {
                await this.executar(declaracao.caminhoFazer);
            }
            catch (erro) {
                throw erro;
            }
        } while (this.eVerdadeiro(await this.avaliar(declaracao.condicaoEnquanto)));
    }
    async visitarDeclaracaoEscolha(declaracao) {
        const condicaoEscolha = await this.avaliar(declaracao.identificadorOuLiteral);
        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++) {
                    if ((await this.avaliar(caminho.condicoes[j])) === condicaoEscolha) {
                        encontrado = true;
                        try {
                            for (let k = 0; k < caminho.declaracoes.length; k++) {
                                await this.executar(caminho.declaracoes[k]);
                            }
                        }
                        catch (erro) {
                            throw erro;
                        }
                    }
                }
            }
            if (caminhoPadrao !== null && encontrado === false) {
                for (let i = 0; i < caminhoPadrao.declaracoes.length; i++) {
                    await this.executar(caminhoPadrao['declaracoes'][i]);
                }
            }
        }
        catch (erro) {
            throw erro;
        }
    }
    async visitarDeclaracaoTente(declaracao) {
        let valorRetorno;
        try {
            let sucesso = true;
            this.emDeclaracaoTente = true;
            try {
                valorRetorno = await this.executarBloco(declaracao.caminhoTente);
            }
            catch (erro) {
                sucesso = false;
                if (declaracao.caminhoPegue !== null) {
                    valorRetorno = await this.executarBloco(declaracao.caminhoPegue);
                }
                else {
                    this.erros.push(erro);
                }
            }
            if (sucesso && declaracao.caminhoSenao !== null) {
                valorRetorno = await this.executarBloco(declaracao.caminhoSenao);
            }
        }
        finally {
            if (declaracao.caminhoFinalmente !== null)
                valorRetorno = await this.executarBloco(declaracao.caminhoFinalmente);
            this.emDeclaracaoTente = false;
        }
        return valorRetorno;
    }
    async visitarDeclaracaoEnquanto(declaracao) {
        while (this.eVerdadeiro(await this.avaliar(declaracao.condicao))) {
            try {
                await this.executar(declaracao.corpo);
            }
            catch (erro) {
                throw erro;
            }
        }
        return null;
    }
    // TODO: Implementar em `delegua-node`.
    async visitarDeclaracaoImportar(declaracao) {
        throw new excecoes_1.ErroEmTempoDeExecucao({
            lexema: declaracao.caminho.valor,
            linha: declaracao.linha,
            hashArquivo: declaracao.caminho.hashArquivo,
        }, 'Importação não suportada em núcleo da linguagem puro. Favor executar a aplicação usando o pacote NPM `delegua-node`.', declaracao.linha);
    }
    async visitarDeclaracaoEscreva(declaracao) {
        try {
            const resultadoAvaliacao = await this.avaliar(declaracao.argumentos[0]);
            let valor = this.resolverValor(resultadoAvaliacao);
            this.funcaoDeRetorno(this.paraTexto(valor));
            return null;
        }
        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) {
            throw retornoUltimoEscopo;
        }
        return retornoUltimoEscopo;
    }
    async visitarExpressaoBloco(declaracao) {
        return await this.executarBloco(declaracao.declaracoes);
    }
    /**
     * Executa expressão de definição de variável.
     * @param declaracao A declaração Var
     * @returns Sempre retorna nulo.
     */
    async visitarDeclaracaoVar(declaracao) {
        let valorOuOutraVariavel = null;
        if (declaracao.inicializador !== null) {
            valorOuOutraVariavel = await this.avaliar(declaracao.inicializador);
        }
        this.pilhaEscoposExecucao.definirVariavel(declaracao.simbolo.lexema, this.resolverValor(valorOuOutraVariavel));
        return null;
    }
    async visitarDeclaracaoVarMultiplo(declaracao) {
        throw new Error('Método não implementado.');
    }
    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(expressao) {
        return new delegua_funcao_1.DeleguaFuncao(null, expressao);
    }
    async visitarExpressaoAtribuicaoPorIndice(expressao) {
        const objeto = await this.avaliar(expressao.objeto);
        let indice = await this.avaliar(expressao.indice);
        const valor = await this.avaliar(expressao.valor);
        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 objeto_delegua_classe_1.ObjetoDeleguaClasse ||
            objeto instanceof delegua_funcao_1.DeleguaFuncao ||
            objeto instanceof descritor_tipo_classe_1.DescritorTipoClasse ||
            objeto instanceof modulo_1.DeleguaModulo) {
            objeto[indice] = valor;
        }
        else {
            throw new excecoes_1.ErroEmTempoDeExecucao(expressao.objeto.nome, 'Somente listas, dicionários, classes e objetos podem ser mudados por sobrescrita.', expressao.linha);
        }
    }
    async visitarExpressaoAcessoIndiceVariavel(expressao) {
        const variavelObjeto = await this.avaliar(expressao.entidadeChamada);
        const objeto = this.resolverValor(variavelObjeto);
        let indice = await this.avaliar(expressao.indice);
        const valorIndice = this.resolverValor(indice);
        if (Array.isArray(objeto)) {
            if (!Number.isInteger(valorIndice)) {
                throw new excecoes_1.ErroEmTempoDeExecucao(expressao.simboloFechamento, 'Somente inteiros podem ser usados para indexar um vetor.', expressao.linha);
            }
            if (indice < 0 && objeto.length !== 0) {
                while (indice < 0) {
                    indice += objeto.length;
                }
            }
            if (indice >= objeto.length) {
                throw new excecoes_1.ErroEmTempoDeExecucao(expressao.simboloFechamento, 'Índice do vetor fora do intervalo.', expressao.linha);
            }
            return objeto[indice];
        }
        else if (objeto.constructor === Object ||
            objeto instanceof objeto_delegua_classe_1.ObjetoDeleguaClasse ||
            objeto instanceof delegua_funcao_1.DeleguaFuncao ||
            objeto instanceof descritor_tipo_classe_1.DescritorTipoClasse ||
            objeto instanceof modulo_1.DeleguaModulo) {
            return objeto[indice] || null;
        }
        else if (typeof objeto === 'string') {
            if (!Number.isInteger(indice)) {
                throw new excecoes_1.ErroEmTempoDeExecucao(expressao.simboloFechamento, 'Somente inteiros podem ser usados para indexar um vetor.', expressao.linha);
            }
            if (indice < 0 && objeto.length !== 0) {
                while (indice < 0) {
                    indice += objeto.length;
                }
            }
            if (indice >= objeto.length) {
                throw new excecoes_1.ErroEmTempoDeExecucao(expressao.simboloFechamento, 'Índice fora do tamanho.', expressao.linha);
            }
            return objeto.charAt(indice);
        }
        else {
            throw new excecoes_1.ErroEmTempoDeExecucao(expressao.entidadeChamada.nome, 'Somente listas, dicionários, classes e objetos podem ser mudados por sobrescrita.', expressao.linha);
        }
    }
    async visitarExpressaoDefinirValor(expressao) {
        const objeto = await this.avaliar(expressao.objeto);
        if (!(objeto instanceof objeto_delegua_classe_1.ObjetoDeleguaClasse) && objeto.constructor !== Object) {
            throw new excecoes_1.ErroEmTempoDeExecucao(expressao.objeto.nome, 'Somente instâncias e dicionários podem possuir campos.', expressao.linha);
        }
        const valor = await this.avaliar(expressao.valor);
        if (objeto instanceof objeto_delegua_classe_1.ObjetoDeleguaClasse) {
            objeto.definir(expressao.nome, valor);
            return valor;
        }
        else if (objeto.constructor === Object) {
            objeto[expressao.simbolo.lexema] = valor;
        }
    }
    visitarDeclaracaoDefinicaoFuncao(declaracao) {
        const funcao = new delegua_funcao_1.DeleguaFuncao(declaracao.simbolo.lexema, declaracao.funcao);
        this.pilhaEscoposExecucao.definirVariavel(declaracao.simbolo.lexema, funcao);
    }
    async visitarDeclaracaoClasse(declaracao) {
        let superClasse = null;
        if (declaracao.superClasse !== null) {
            const variavelSuperClasse = await this.avaliar(declaracao.superClasse);
            superClasse = variavelSuperClasse.valor;
            if (!(superClasse instanceof descritor_tipo_classe_1.DescritorTipoClasse)) {
                throw new excecoes_1.ErroEmTempoDeExecucao(declaracao.superClasse.nome, 'Superclasse precisa ser uma classe.', declaracao.linha);
            }
        }
        // TODO: Estudar retirar isso.
        // this.pilhaEscoposExecucao.definirVariavel(declaracao.simbolo.lexema, null);
        if (declaracao.superClasse !== null) {
            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 delegua_funcao_1.DeleguaFuncao(metodoAtual.simbolo.lexema, metodoAtual.funcao, undefined, eInicializador);
            metodos[metodoAtual.simbolo.lexema] = funcao;
        }
        const deleguaClasse = new descritor_tipo_classe_1.DescritorTipoClasse(declaracao.simbolo, superClasse, metodos);
        // TODO: Depreciar na próxima versão.
        deleguaClasse.dialetoRequerExpansaoPropriedadesEspacoMemoria = false;
        deleguaClasse.dialetoRequerDeclaracaoPropriedades = false;
        // TODO: Recolocar isso se for necessário.
        /* if (superClasse !== null) {
            this.ambiente = this.ambiente.enclosing;
        } */
        this.pilhaEscoposExecucao.definirVariavel(declaracao.simbolo.lexema, deleguaClasse);
        return null;
    }
    async visitarExpressaoAcessoMetodoOuPropriedade(expressao) {
        const variavelObjeto = await this.avaliar(expressao.objeto);
        const objeto = variavelObjeto === null || variavelObjeto === void 0 ? void 0 : variavelObjeto.valor;
        if (objeto instanceof objeto_delegua_classe_1.ObjetoDeleguaClasse) {
            return objeto.obter(expressao.simbolo) || null;
        }
        else if (objeto.constructor === Object) {
            return objeto[expressao.simbolo.lexema] || null;
        }
        else if (objeto instanceof modulo_1.DeleguaModulo) {
            return objeto[expressao.simbolo.lexema] || null;
        }
        throw new excecoes_1.ErroEmTempoDeExecucao(expressao.nome, 'Você só pode acessar métodos do objeto e dicionários.', expressao.linha);
    }
    async visitarExpressaoIsto(expressao) {
        return Promise.resolve(this.procurarVariavel(expressao.simboloChave, expressao));
    }
    async visitarExpressaoDicionario(expressao) {
        const dicionario = {};
        for (let i = 0; i < expressao.chaves.length; i++) {
            dicionario[await this.avaliar(expressao.chaves[i])] = await this.avaliar(expressao.valores[i]);
        }
        return dicionario;
    }
    async visitarExpressaoVetor(expressao) {
        const valores = [];
        for (let i = 0; i < expressao.valores.length; i++) {
            valores.push(await this.avaliar(expressao.valores[i]));
        }
        return valores;
    }
    visitarExpressaoSuper(expressao) {
        const objeto = this.pilhaEscoposExecucao.obterVariavelPorNome('isto');
        return objeto.valor;
    }
    paraTexto(objeto) {
        if (objeto === null)
            return 'nulo';
        if (typeof objeto === 'boolean') {
            return objeto ? 'verdadeiro' : 'falso';
        }
        if (objeto instanceof Date) {
            const formato = Intl.DateTimeFormat('pt', {
                dateStyle: 'full',
                timeStyle: 'full',
            });
            return formato.format(objeto);
        }
        if (Array.isArray(objeto))
            return objeto;
        if (typeof objeto === 'object')
            return JSON.stringify(objeto);
        if (objeto === undefined) {
            return 'nulo';
        }
        return objeto.toString();
    }
    async executar(declaracao, mostrarResultado = false) {
        return await declaracao.aceitar(this);
    }
    /**
     * Executa o último escopo empilhado no topo na pilha de escopos do Interpretador.
     * Originalmente, Égua não trabalha com uma pilha de escopos.
     *
     * 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.
     *
     * Essa implementação é derivada do Interpretador de Delégua, mas simulando todos os
     * comportamos do interpretador Égua original.
     * Interpretador Égua: https://github.com/eguatech/egua/blob/master/src/interpreter.js
     * @returns O resultado da execução do escopo, se houver.
     */
    async executarUltimoEscopo() {
        const ultimoEscopo = this.pilhaEscoposExecucao.topoDaPilha();
        try {
            let retornoExecucao;
            for (; !(retornoExecucao instanceof quebras_1.Quebra) &&
                ultimoEscopo.declaracaoAtual < ultimoEscopo.declaracoes.length; ultimoEscopo.declaracaoAtual++) {
                retornoExecucao = await this.executar(ultimoEscopo.declaracoes[ultimoEscopo.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();
        }
    }
    async interpretar(declaracoes) {
        this.erros = [];
        const retornoResolvedor = await this.resolvedor.resolver(declaracoes);
        this.locais = retornoResolvedor.locais;
        const escopoExecucao = {
            declaracoes: declaracoes,
            declaracaoAtual: 0,
            espacoMemoria: new espaco_memoria_1.EspacoMemoria(),
            finalizado: false,
            tipo: 'outro',
            emLacoRepeticao: false,
        };
        this.pilhaEscoposExecucao.empilhar(escopoExecucao);
        await this.executarUltimoEscopo();
        return {
            erros: this.erros,
        };
    }
    finalizacao() {
        this.funcaoDeRetorno();
    }
}
exports.InterpretadorEguaClassico = InterpretadorEguaClassico;
//# sourceMappingURL=interpretador-egua-classico.js.map