"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AvaliadorSintaticoTenda = void 0;
const browser_process_hrtime_1 = __importDefault(require("browser-process-hrtime"));
const construtos_1 = require("../../construtos");
const erro_avaliador_sintatico_1 = require("./../erro-avaliador-sintatico");
const tuplas_1 = require("../../construtos/tuplas");
const declaracoes_1 = require("../../declaracoes");
const avaliador_sintatico_base_1 = require("../avaliador-sintatico-base");
const inferenciador_1 = require("../../inferenciador");
const pilha_escopos_1 = require("./../pilha-escopos");
const informacao_escopo_1 = require("./../informacao-escopo");
const informacao_elemento_sintatico_1 = require("../../informacao-elemento-sintatico");
const simbolo_1 = require("../../lexador/simbolo");
const delegua_1 = __importDefault(require("../../tipos-de-dados/delegua"));
const tenda_1 = __importDefault(require("../../tipos-de-simbolos/tenda"));
const primitivas_dicionario_1 = __importDefault(require("../../bibliotecas/primitivas-dicionario"));
const primitivas_numero_1 = __importDefault(require("../../bibliotecas/primitivas-numero"));
const primitivas_texto_1 = __importDefault(require("../../bibliotecas/primitivas-texto"));
const primitivas_vetor_1 = __importDefault(require("../../bibliotecas/primitivas-vetor"));
/**
 * Este avaliador sintático emite todos os símbolos de Tenda. No entanto, nem todo
 * símbolo emitido aqui pode ser interpretado por este núcleo, já que Tenda tem
 * Rust como base, e várias de suas funções requerem interface com um sistema operacional.
 * Outros pacotes do ecossistema de Delégua, como `delegua-node` poderão lidar com
 * todas as funcionalidades de Tenda.
 */
class AvaliadorSintaticoTenda extends avaliador_sintatico_base_1.AvaliadorSintaticoBase {
    constructor(performance = false) {
        super();
        this.hashArquivo = 0;
        this.atual = 0;
        this.blocos = 0;
        this.erros = [];
        this.performance = performance;
        this.tiposDefinidosEmCodigo = {};
        this.tiposDeFerramentasExternas = {};
        this.primitivasConhecidas = {};
        for (const [nomePrimitivaDicionario, dadosPrimitiva] of Object.entries(primitivas_dicionario_1.default)) {
            this.primitivasConhecidas[nomePrimitivaDicionario] = new informacao_elemento_sintatico_1.InformacaoElementoSintatico(nomePrimitivaDicionario, 'dicionário', true, dadosPrimitiva.argumentos);
        }
        for (const [nomePrimitivaNumero, dadosPrimitiva] of Object.entries(primitivas_numero_1.default)) {
            this.primitivasConhecidas[nomePrimitivaNumero] = new informacao_elemento_sintatico_1.InformacaoElementoSintatico(nomePrimitivaNumero, 'número', true, dadosPrimitiva.argumentos);
        }
        for (const [nomePrimitivaTexto, dadosPrimitiva] of Object.entries(primitivas_texto_1.default)) {
            this.primitivasConhecidas[nomePrimitivaTexto] = new informacao_elemento_sintatico_1.InformacaoElementoSintatico(nomePrimitivaTexto, 'texto', true, dadosPrimitiva.argumentos);
        }
        for (const [nomePrimitivaVetor, dadosPrimitiva] of Object.entries(primitivas_vetor_1.default)) {
            this.primitivasConhecidas[nomePrimitivaVetor] = new informacao_elemento_sintatico_1.InformacaoElementoSintatico(nomePrimitivaVetor, 'vetor', true, dadosPrimitiva.argumentos);
        }
        // TODO: Por enquanto não há necessidade de validar argumentos aqui, mas isso pode mudar no futuro.
        this.primitivasConhecidas['inteiro'] = new informacao_elemento_sintatico_1.InformacaoElementoSintatico('inteiro', 'inteiro');
        this.primitivasConhecidas['numero'] = new informacao_elemento_sintatico_1.InformacaoElementoSintatico('numero', 'número');
        this.primitivasConhecidas['número'] = new informacao_elemento_sintatico_1.InformacaoElementoSintatico('número', 'número');
        this.primitivasConhecidas['texto'] = new informacao_elemento_sintatico_1.InformacaoElementoSintatico('texto', 'texto');
        this.pilhaEscopos = new pilha_escopos_1.PilhaEscopos();
    }
    verificarDefinicaoTipoAtual() {
        const tipos = [...Object.values(delegua_1.default)];
        if (this.simbolos[this.atual].lexema in this.tiposDefinidosEmCodigo) {
            return this.simbolos[this.atual].lexema;
        }
        const lexemaElementar = this.simbolos[this.atual].lexema.toLowerCase();
        const tipoElementarResolvido = tipos.find((tipo) => tipo === lexemaElementar);
        if (!tipoElementarResolvido) {
            throw this.erro(this.simbolos[this.atual], `Tipo de dados desconhecido: '${this.simbolos[this.atual].lexema}'.`);
        }
        if (this.verificarTipoProximoSimbolo(tenda_1.default.COLCHETE_ESQUERDO)) {
            const tiposVetores = [
                'inteiro[]',
                'numero[]',
                'número[]',
                'qualquer[]',
                'real[]',
                'texto[]',
            ];
            this.avancarEDevolverAnterior();
            if (!this.verificarTipoProximoSimbolo(tenda_1.default.COLCHETE_DIREITO)) {
                throw this.erro(this.simbolos[this.atual], `Esperado símbolo de fechamento do vetor: ']'. Atual: ${this.simbolos[this.atual].lexema}`);
            }
            const tipoVetor = tiposVetores.find((tipo) => tipo === `${lexemaElementar}[]`);
            this.avancarEDevolverAnterior();
            return tipoVetor;
        }
        return tipoElementarResolvido;
    }
    async obterChaveDicionario() {
        switch (this.simbolos[this.atual].tipo) {
            case tenda_1.default.NUMERO:
            case tenda_1.default.TEXTO:
            case tenda_1.default.FALSO:
            case tenda_1.default.VERDADEIRO:
                return await this.primario();
            case tenda_1.default.IDENTIFICADOR:
                const simboloIdentificador = this.avancarEDevolverAnterior();
                let tipoOperando;
                if (simboloIdentificador.lexema in this.tiposDefinidosEmCodigo) {
                    tipoOperando = simboloIdentificador.lexema;
                }
                else {
                    tipoOperando = this.pilhaEscopos.obterTipoVariavelPorNome(simboloIdentificador.lexema);
                }
                if (!['numero', 'número', 'texto', 'lógico'].includes(tipoOperando)) {
                    throw this.erro(simboloIdentificador, `Tipo ${tipoOperando} de identificador ${simboloIdentificador.lexema} não é válido como chave de dicionário.`);
                }
                return new construtos_1.Variavel(this.hashArquivo, simboloIdentificador, tipoOperando);
            case tenda_1.default.COLCHETE_ESQUERDO:
                this.avancarEDevolverAnterior();
                if (this.verificarSeSimboloAtualEIgualA(tenda_1.default.PARENTESE_ESQUERDO)) {
                    return this.construtoTupla();
                }
                throw this.erro(this.simbolos[this.atual], `Esperado parêntese esquerdo após colchete esquerdo para definição de chave de dicionário. Atual: ${this.simbolos[this.atual].tipo}.`);
            default:
                throw this.erro(this.simbolos[this.atual], `Símbolo ${this.simbolos[this.atual].tipo} inesperado ou inválido como chave de dicionário.`);
        }
    }
    async construtoDicionario(simboloChaveEsquerda) {
        this.avancarEDevolverAnterior();
        const chaves = [];
        const valores = [];
        if (this.verificarSeSimboloAtualEIgualA(tenda_1.default.CHAVE_DIREITA)) {
            return new construtos_1.Dicionario(this.hashArquivo, Number(simboloChaveEsquerda.linha), [], []);
        }
        while (!this.verificarSeSimboloAtualEIgualA(tenda_1.default.CHAVE_DIREITA)) {
            const chave = await this.obterChaveDicionario();
            this.consumir(tenda_1.default.DOIS_PONTOS, "Esperado ':' entre chave e valor.");
            const valor = await this.atribuir();
            chaves.push(chave);
            valores.push(valor);
            if (this.simbolos[this.atual].tipo !== tenda_1.default.CHAVE_DIREITA) {
                this.consumir(tenda_1.default.VIRGULA, 'Esperado vírgula antes da próxima expressão.');
            }
        }
        return new construtos_1.Dicionario(this.hashArquivo, Number(simboloChaveEsquerda.linha), chaves, valores);
    }
    async construtoTupla() {
        const expressao = await this.expressao();
        const argumentos = [expressao];
        while (this.simbolos[this.atual].tipo === tenda_1.default.VIRGULA) {
            this.avancarEDevolverAnterior();
            argumentos.push(await this.expressao());
        }
        this.consumir(tenda_1.default.PARENTESE_DIREITO, "Esperado ')' após a expressão.");
        this.consumir(tenda_1.default.COLCHETE_DIREITO, "Esperado ']' após a expressão.");
        return new tuplas_1.SeletorTuplas(...argumentos);
    }
    async primario() {
        const simboloAtual = this.simbolos[this.atual];
        let valores = [];
        switch (simboloAtual.tipo) {
            case tenda_1.default.CHAVE_ESQUERDA:
                return await this.construtoDicionario(simboloAtual);
            case tenda_1.default.COLCHETE_ESQUERDO:
                this.avancarEDevolverAnterior();
                valores = [];
                if (this.verificarSeSimboloAtualEIgualA(tenda_1.default.COLCHETE_DIREITO)) {
                    return new construtos_1.Vetor(this.hashArquivo, Number(simboloAtual.linha), [], 0, 'qualquer[]');
                }
                while (!this.verificarSeSimboloAtualEIgualA(tenda_1.default.COLCHETE_DIREITO)) {
                    if (this.verificarSeSimboloAtualEIgualA(tenda_1.default.PARENTESE_ESQUERDO)) {
                        return await this.construtoTupla();
                    }
                    const valor = await this.atribuir();
                    valores.push(valor);
                    if (this.simbolos[this.atual].tipo !== tenda_1.default.COLCHETE_DIREITO) {
                        this.consumir(tenda_1.default.VIRGULA, 'Esperado vírgula antes da próxima expressão.');
                    }
                }
                const tipoVetor = (0, inferenciador_1.inferirTipoVariavel)(valores);
                return new construtos_1.Vetor(this.hashArquivo, Number(simboloAtual.linha), valores, valores.length, tipoVetor);
            case tenda_1.default.EXPRESSAO_REGULAR:
                let valor = '';
                let linhaAtual = this.simbolos[this.atual].linha;
                let eParExpressaoRegular = this.simbolos.filter((l) => l.linha === linhaAtual && l.tipo === tenda_1.default.EXPRESSAO_REGULAR).length %
                    2 ===
                    0;
                if (eParExpressaoRegular) {
                    this.avancarEDevolverAnterior();
                    while (!this.verificarTipoSimboloAtual(tenda_1.default.EXPRESSAO_REGULAR)) {
                        valor += this.simbolos[this.atual].lexema || '';
                        this.avancarEDevolverAnterior();
                    }
                    this.avancarEDevolverAnterior();
                    return new construtos_1.ExpressaoRegular(this.hashArquivo, simboloAtual, valor);
                }
            case tenda_1.default.FALSO:
                this.avancarEDevolverAnterior();
                return new construtos_1.Literal(this.hashArquivo, Number(simboloAtual.linha), false, 'lógico');
            case tenda_1.default.FUNÇÃO:
                const simboloFuncao = this.avancarEDevolverAnterior();
                // Avançamos o parêntese esquerdo aqui, porque `corpoDaFuncao`
                // espera que esse parêntese esquerdo já foi consumido.
                this.consumir(tenda_1.default.PARENTESE_ESQUERDO, "Esperado parêntese esquerdo após palavra reservada 'função'.");
                const corpoDaFuncao = await this.corpoDaFuncao(simboloFuncao.lexema);
                this.pilhaEscopos.definirInformacoesVariavel(simboloFuncao.lexema, new informacao_elemento_sintatico_1.InformacaoElementoSintatico(simboloFuncao.lexema, 'função'));
                return corpoDaFuncao;
            case tenda_1.default.IDENTIFICADOR:
                const simboloIdentificador = this.avancarEDevolverAnterior();
                let tipoOperando;
                if (simboloIdentificador.lexema in this.tiposDefinidosEmCodigo) {
                    tipoOperando = simboloIdentificador.lexema;
                }
                else {
                    tipoOperando = this.pilhaEscopos.obterTipoVariavelPorNome(simboloIdentificador.lexema);
                }
                // Se o próximo símbolo é um incremento ou um decremento,
                // aqui deve retornar um unário correspondente.
                // Caso contrário, apenas retornar um construto de variável.
                if (this.simbolos[this.atual] &&
                    [tenda_1.default.INCREMENTAR, tenda_1.default.DECREMENTAR].includes(this.simbolos[this.atual].tipo)) {
                    const simboloIncrementoDecremento = this.avancarEDevolverAnterior();
                    return new construtos_1.Unario(this.hashArquivo, simboloIncrementoDecremento, new construtos_1.Variavel(this.hashArquivo, simboloIdentificador, tipoOperando || 'qualquer'), 'DEPOIS');
                }
                return new construtos_1.Variavel(this.hashArquivo, simboloIdentificador, tipoOperando || 'qualquer');
            case tenda_1.default.LEIA:
                return this.expressaoLeia();
            case tenda_1.default.NADA:
                this.avancarEDevolverAnterior();
                return new construtos_1.Literal(this.hashArquivo, Number(simboloAtual.linha), null, 'nulo');
            case tenda_1.default.NUMERO:
            case tenda_1.default.NÚMERO:
            case tenda_1.default.TEXTO:
                const simboloNumeroTexto = this.avancarEDevolverAnterior();
                const tipoInferido = (0, inferenciador_1.inferirTipoVariavel)(simboloNumeroTexto.literal);
                const tipoDadosElementar = (0, inferenciador_1.tipoInferenciaParaTipoDadosElementar)(tipoInferido);
                return new construtos_1.Literal(this.hashArquivo, Number(simboloNumeroTexto.linha), simboloNumeroTexto.literal, tipoDadosElementar);
            case tenda_1.default.PARENTESE_ESQUERDO:
                this.avancarEDevolverAnterior();
                const expressao = await this.expressao();
                this.consumir(tenda_1.default.PARENTESE_DIREITO, "Esperado ')' após a expressão.");
                return new construtos_1.Agrupamento(this.hashArquivo, Number(simboloAtual.linha), expressao);
            case tenda_1.default.SUPER:
                const simboloSuper = this.avancarEDevolverAnterior();
                if (!this.superclasseAtual) {
                    throw this.erro(this.simbolos[this.atual], "'Super' usado fora de declaração de classe com herança.");
                }
                return new construtos_1.Super(this.hashArquivo, simboloSuper, this.superclasseAtual);
            case tenda_1.default.VERDADEIRO:
                this.avancarEDevolverAnterior();
                return new construtos_1.Literal(this.hashArquivo, Number(simboloAtual.linha), true, 'lógico');
        }
        throw this.erro(this.simbolos[this.atual], 'Esperado expressão.');
    }
    async chamar() {
        let expressao = await this.primario();
        while (true) {
            let tipoPrimitiva = undefined;
            if (this.verificarSeSimboloAtualEIgualA(tenda_1.default.PARENTESE_ESQUERDO)) {
                expressao = await this.finalizarChamada(expressao, tipoPrimitiva);
            }
            else if (this.verificarSeSimboloAtualEIgualA(tenda_1.default.PONTO)) {
                const nome = this.consumir(tenda_1.default.IDENTIFICADOR, "Esperado nome de método ou propriedade após '.'.");
                tipoPrimitiva = expressao.tipo;
                expressao = new construtos_1.AcessoMetodoOuPropriedade(this.hashArquivo, expressao, nome);
            }
            else if (this.verificarSeSimboloAtualEIgualA(tenda_1.default.COLCHETE_ESQUERDO)) {
                const indice = await this.expressao();
                const simboloFechamento = this.consumir(tenda_1.default.COLCHETE_DIREITO, "Esperado ']' após escrita do indice.");
                expressao = new construtos_1.AcessoIndiceVariavel(this.hashArquivo, expressao, indice, simboloFechamento);
            }
            else {
                break;
            }
        }
        return expressao;
    }
    /**
     * `AcessoMetodoOuPropriedade` é um construto intermediário em Delégua, e deve ser resolvido como outro
     * construto antes de qualquer outra próxima etapa. Algumas validações adicionais também ocorrem aqui.
     * @param {AcessoMetodoOuPropriedade} entidadeChamadaResolvida O construto original.
     * @returns {Construto} O construto resolvido como um tipo mais específico.
     * @see finalizarChamada
     */
    resolverEntidadeChamadaAcessoMetodoOuPropriedade(entidadeChamadaResolvida) {
        const construtoTipado = entidadeChamadaResolvida;
        switch (entidadeChamadaResolvida.tipo) {
            case delegua_1.default.DICIONARIO:
            case delegua_1.default.DICIONÁRIO:
                if (!(construtoTipado.simbolo.lexema in primitivas_dicionario_1.default)) {
                    throw this.erro(construtoTipado.simbolo, `${construtoTipado.simbolo.lexema} não é uma primitiva de dicionário.`);
                }
                const primitivaDicionarioSelecionada = primitivas_dicionario_1.default[construtoTipado.simbolo.lexema];
                return new construtos_1.AcessoMetodo(construtoTipado.hashArquivo, construtoTipado.objeto, construtoTipado.simbolo.lexema, primitivaDicionarioSelecionada.tipoRetorno);
            case delegua_1.default.INTEIRO:
            case delegua_1.default.NUMERO:
            case delegua_1.default.NÚMERO:
                if (!(construtoTipado.simbolo.lexema in primitivas_numero_1.default)) {
                    throw this.erro(construtoTipado.simbolo, `${construtoTipado.simbolo.lexema} não é uma primitiva de número.`);
                }
                const primitivaNumeroSelecionada = primitivas_numero_1.default[construtoTipado.simbolo.lexema];
                return new construtos_1.AcessoMetodo(construtoTipado.hashArquivo, construtoTipado.objeto, construtoTipado.simbolo.lexema, primitivaNumeroSelecionada.tipoRetorno);
            case delegua_1.default.MODULO:
            case delegua_1.default.MÓDULO:
                if (construtoTipado.simbolo.lexema in this.tiposDefinidosEmCodigo) {
                    // Construtor de classe.
                    return new construtos_1.Variavel(construtoTipado.hashArquivo, construtoTipado.simbolo, construtoTipado.objeto.tipo);
                }
                return new construtos_1.AcessoMetodo(construtoTipado.hashArquivo, construtoTipado.objeto, construtoTipado.simbolo.lexema);
            case delegua_1.default.TEXTO:
                if (!(construtoTipado.simbolo.lexema in primitivas_texto_1.default)) {
                    throw this.erro(construtoTipado.simbolo, `${construtoTipado.simbolo.lexema} não é uma primitiva de texto.`);
                }
                const primitivaTextoSelecionada = primitivas_texto_1.default[construtoTipado.simbolo.lexema];
                return new construtos_1.AcessoMetodo(construtoTipado.hashArquivo, construtoTipado.objeto, construtoTipado.simbolo.lexema, primitivaTextoSelecionada.tipoRetorno);
            case delegua_1.default.VETOR:
            case delegua_1.default.VETOR_NUMERO:
            case delegua_1.default.VETOR_NÚMERO:
            case delegua_1.default.VETOR_TEXTO:
                if (!(construtoTipado.simbolo.lexema in primitivas_vetor_1.default)) {
                    throw this.erro(construtoTipado.simbolo, `${construtoTipado.simbolo.lexema} não é uma primitiva de vetor.`);
                }
                const primitivaVetorSelecionada = primitivas_vetor_1.default[construtoTipado.simbolo.lexema];
                return new construtos_1.AcessoMetodo(construtoTipado.hashArquivo, construtoTipado.objeto, construtoTipado.simbolo.lexema, primitivaVetorSelecionada.tipoRetorno);
        }
        return entidadeChamadaResolvida;
    }
    validarArgumentosEntidadeChamada(argumentosEntidadeChamada, argumentosUtilizados) {
        if (argumentosEntidadeChamada.length === 0) {
            return [];
        }
        const possiveisErros = [];
        for (const [indice, argumentoEntidadeChamada] of argumentosEntidadeChamada.entries()) {
            const argumentoUtilizado = argumentosUtilizados[indice];
            if (argumentoUtilizado.tipo === null || argumentoUtilizado.tipo === undefined) {
                continue;
            }
            const argumentoEntidadeChamadaVetor = argumentoEntidadeChamada.tipo.endsWith('[]');
            const argumentoUtilizadoVetor = argumentoUtilizado.tipo.endsWith('[]');
            if (argumentoEntidadeChamadaVetor !== argumentoUtilizadoVetor) {
                possiveisErros.push(`Argumento: ${argumentoEntidadeChamada.nome}. Tipo esperado: ${argumentoEntidadeChamada.tipo}; Tipo utilizado: ${argumentoUtilizado.tipo}`);
                continue;
            }
            const argumentoEntidadeChamadaQualquer = argumentoEntidadeChamada.tipo.startsWith('qualquer');
            if (argumentoEntidadeChamadaQualquer) {
                continue;
            }
            if (argumentoUtilizado.tipo !== argumentoEntidadeChamada.tipo) {
                possiveisErros.push(`Argumento: ${argumentoEntidadeChamada.nome}. Tipo esperado: ${argumentoEntidadeChamada.tipo}; Tipo utilizado: ${argumentoUtilizado.tipo}`);
            }
        }
        return possiveisErros;
    }
    /**
     * Diversas verificações de resolução de entidade chamada, como resolver chamada da pilha ou usar referência, argumentos, etc.
     * @param entidadeChamada O construto da entidade chamada.
     * @param argumentos Os argumentos utilizados na chamada.
     * @param tipoPrimitiva Se for uma primitiva, o tipo dela. Senão, `undefined`.
     * @returns A entidade chamada resolvida, se as validações passarem.
     */
    resolverEntidadeChamada(entidadeChamada, argumentos, tipoPrimitiva = undefined) {
        if (entidadeChamada.constructor === construtos_1.Variavel) {
            const entidadeChamadaResolvidaVariavel = entidadeChamada;
            if (tipoPrimitiva === undefined) {
                // Provavelmente uma chamada a alguma função da biblioteca global.
                const informacoesPossivelFuncaoBibliotecaGlobal = this.pilhaEscopos.obterBibliotecaGlobal(entidadeChamadaResolvidaVariavel.simbolo.lexema);
                if (informacoesPossivelFuncaoBibliotecaGlobal !== undefined) {
                    const erros = this.validarArgumentosEntidadeChamada(informacoesPossivelFuncaoBibliotecaGlobal.subElementos, argumentos);
                    if (erros.length > 0) {
                        throw new erro_avaliador_sintatico_1.ErroAvaliadorSintatico(entidadeChamadaResolvidaVariavel.simbolo, `Erros ao resolver argumentos de chamada a ${entidadeChamadaResolvidaVariavel.simbolo.lexema}: \n${erros.reduce((mensagem, erro) => (mensagem += `${erro}\n`), '')}`);
                    }
                    return entidadeChamadaResolvidaVariavel;
                }
            }
            if (tipoPrimitiva !== undefined &&
                this.primitivasConhecidas.hasOwnProperty(entidadeChamadaResolvidaVariavel.simbolo.lexema)) {
                var informacoesPrimitiva = this.primitivasConhecidas[entidadeChamadaResolvidaVariavel.simbolo.lexema];
                const erros = this.validarArgumentosEntidadeChamada(informacoesPrimitiva.subElementos, argumentos);
                if (erros.length > 0) {
                    throw new erro_avaliador_sintatico_1.ErroAvaliadorSintatico(entidadeChamadaResolvidaVariavel.simbolo, `Erros ao resolver argumentos de chamada a ${entidadeChamadaResolvidaVariavel.simbolo.lexema}: \n${erros.reduce((mensagem, erro) => (mensagem += `${erro}\n`), '')}`);
                }
                return entidadeChamadaResolvidaVariavel;
            }
            if (entidadeChamadaResolvidaVariavel.simbolo.lexema in this.tiposDefinidosEmCodigo) {
                return entidadeChamadaResolvidaVariavel;
            }
            const possivelReferencia = this.pilhaEscopos.obterReferenciaFuncao(entidadeChamadaResolvidaVariavel.simbolo.lexema);
            if (possivelReferencia !== null) {
                return new construtos_1.ReferenciaFuncao(entidadeChamada.hashArquivo, entidadeChamada.linha, entidadeChamadaResolvidaVariavel.simbolo, entidadeChamadaResolvidaVariavel.tipo, possivelReferencia.id);
            }
            return new construtos_1.ArgumentoReferenciaFuncao(entidadeChamada.hashArquivo, entidadeChamada.linha, entidadeChamadaResolvidaVariavel.simbolo);
        }
        if (entidadeChamada.constructor === construtos_1.AcessoMetodoOuPropriedade) {
            return this.resolverEntidadeChamadaAcessoMetodoOuPropriedade(entidadeChamada);
        }
        return entidadeChamada;
    }
    async declaracaoDeFuncao(identificador) {
        // Se houver chamadas recursivas à função, precisamos definir um tipo
        // para ela. Vai ser atualizado após avaliação do corpo da função.
        this.pilhaEscopos.definirInformacoesVariavel(identificador.lexema, new informacao_elemento_sintatico_1.InformacaoElementoSintatico(identificador.lexema, 'qualquer'));
        const corpoDaFuncao = await this.corpoDaFuncao('implícita');
        this.pilhaEscopos.definirInformacoesVariavel(identificador.lexema, new informacao_elemento_sintatico_1.InformacaoElementoSintatico(identificador.lexema, corpoDaFuncao.tipo));
        const funcaoDeclaracao = new declaracoes_1.FuncaoDeclaracao(identificador, corpoDaFuncao, corpoDaFuncao.tipo);
        this.pilhaEscopos.registrarReferenciaFuncao(identificador.lexema, funcaoDeclaracao);
        return funcaoDeclaracao;
    }
    async finalizarChamada(entidadeChamada, tipoPrimitiva = undefined) {
        const argumentos = [];
        if (!this.verificarTipoSimboloAtual(tenda_1.default.PARENTESE_DIREITO)) {
            do {
                // `apply()` em JavaScript aceita até 255 parâmetros.
                if (argumentos.length >= 255) {
                    throw this.erro(this.simbolos[this.atual], 'Não pode haver mais de 255 argumentos.');
                }
                argumentos.push(await this.expressao());
            } while (this.verificarSeSimboloAtualEIgualA(tenda_1.default.VIRGULA));
        }
        this.consumir(tenda_1.default.PARENTESE_DIREITO, "Esperado ')' após os argumentos.");
        // Toda chamada precisa saber de antemão qual o tipo resolvido.
        const entidadeChamadaResolvida = this.resolverEntidadeChamada(entidadeChamada, argumentos, tipoPrimitiva);
        // TODO: Criar forma de validar tipos dos argumentos da entidade chamada.
        const construtoChamada = new construtos_1.Chamada(this.hashArquivo, entidadeChamadaResolvida, argumentos);
        construtoChamada.tipo = 'qualquer';
        return construtoChamada;
    }
    async unario() {
        if (this.verificarSeSimboloAtualEIgualA(tenda_1.default.NÃO, tenda_1.default.SUBTRACAO, tenda_1.default.BIT_NOT, tenda_1.default.INCREMENTAR, tenda_1.default.DECREMENTAR)) {
            const operador = this.simbolos[this.atual - 1];
            const direito = await this.unario();
            return new construtos_1.Unario(this.hashArquivo, operador, direito, 'ANTES');
        }
        return await this.chamar();
    }
    async multiplicar() {
        let expressao = await this.exponenciacao();
        while (this.verificarSeSimboloAtualEIgualA(tenda_1.default.DIVISAO, tenda_1.default.DIVISAO_INTEIRA, tenda_1.default.MODULO, tenda_1.default.MULTIPLICACAO)) {
            const operador = this.simbolos[this.atual - 1];
            const direito = await this.exponenciacao();
            expressao = new construtos_1.Binario(this.hashArquivo, expressao, operador, direito);
        }
        return expressao;
    }
    /**
     * Se símbolo de operação é `+`, `-`, `+=` ou `-=`, monta objeto `Binario` para
     * ser avaliado pelo Interpretador.
     * @returns Um Construto, normalmente um `Binario`, ou `Unario` se houver alguma operação unária para ser avaliada.
     */
    async adicaoOuSubtracao() {
        let expressao = await this.multiplicar();
        while (this.verificarSeSimboloAtualEIgualA(tenda_1.default.SUBTRACAO, tenda_1.default.ADICAO)) {
            const operador = this.simbolos[this.atual - 1];
            const direito = await this.multiplicar();
            expressao = new construtos_1.Binario(this.hashArquivo, expressao, operador, direito);
        }
        return expressao;
    }
    async bitShift() {
        let expressao = await this.adicaoOuSubtracao();
        while (this.verificarSeSimboloAtualEIgualA(tenda_1.default.MENOR_MENOR, tenda_1.default.MAIOR_MAIOR)) {
            const operador = this.simbolos[this.atual - 1];
            const direito = await this.adicaoOuSubtracao();
            expressao = new construtos_1.Binario(this.hashArquivo, expressao, operador, direito);
        }
        return expressao;
    }
    async bitE() {
        let expressao = await this.bitShift();
        while (this.verificarSeSimboloAtualEIgualA(tenda_1.default.BIT_AND)) {
            const operador = this.simbolos[this.atual - 1];
            const direito = await this.bitShift();
            expressao = new construtos_1.Binario(this.hashArquivo, expressao, operador, direito);
        }
        return expressao;
    }
    async bitOu() {
        let expressao = await this.bitE();
        while (this.verificarSeSimboloAtualEIgualA(tenda_1.default.BIT_OR, tenda_1.default.BIT_XOR)) {
            const operador = this.simbolos[this.atual - 1];
            const direito = await this.bitE();
            expressao = new construtos_1.Binario(this.hashArquivo, expressao, operador, direito);
        }
        return expressao;
    }
    async comparar() {
        let expressao = await this.bitOu();
        while (this.verificarSeSimboloAtualEIgualA(tenda_1.default.MAIOR, tenda_1.default.MAIOR_IGUAL, tenda_1.default.MENOR, tenda_1.default.MENOR_IGUAL)) {
            const operador = this.simbolos[this.atual - 1];
            const direito = await this.bitOu();
            expressao = new construtos_1.Binario(this.hashArquivo, expressao, operador, direito);
        }
        return expressao;
    }
    async comparacaoIgualdade() {
        let expressao = await this.comparar();
        while (this.verificarSeSimboloAtualEIgualA(tenda_1.default.NÃO, tenda_1.default.É)) {
            const operador = this.simbolos[this.atual - 1];
            if (operador.tipo === tenda_1.default.NÃO) {
                this.consumir(tenda_1.default.É, `Esperado 'é' após 'não'.`);
                operador.tipo = tenda_1.default.DIFERENTE;
                operador.lexema = 'não é';
                operador.literal = 'não é';
            }
            const direito = await this.comparar();
            expressao = new construtos_1.Binario(this.hashArquivo, expressao, operador, direito);
        }
        return expressao;
    }
    async em() {
        let expressao = await this.comparacaoIgualdade();
        while (this.verificarSeSimboloAtualEIgualA(tenda_1.default.TEM)) {
            const operador = this.simbolos[this.atual - 1];
            const direito = await this.comparacaoIgualdade();
            expressao = new construtos_1.Logico(this.hashArquivo, expressao, operador, direito);
        }
        return expressao;
    }
    async e() {
        let expressao = await this.em();
        while (this.verificarSeSimboloAtualEIgualA(tenda_1.default.E)) {
            const operador = this.simbolos[this.atual - 1];
            const direito = await this.em();
            expressao = new construtos_1.Logico(this.hashArquivo, expressao, operador, direito);
        }
        return expressao;
    }
    /**
     * Método que resolve atribuições.
     * @returns Um construto do tipo `Atribuir`, `Conjunto` ou `AtribuicaoPorIndice`.
     */
    async atribuir() {
        const expressao = await this.ou();
        if (this.verificarSeSimboloAtualEIgualA(tenda_1.default.IGUAL)) {
            const igual = this.simbolos[this.atual - 1];
            const valor = await this.atribuir();
            switch (expressao.constructor) {
                case construtos_1.Variavel:
                    return new construtos_1.Atribuir(this.hashArquivo, expressao, valor);
                case construtos_1.AcessoMetodoOuPropriedade:
                    const expressaoAcessoMetodoOuPropriedade = expressao;
                    return new construtos_1.DefinirValor(this.hashArquivo, igual.linha, expressaoAcessoMetodoOuPropriedade.objeto, expressaoAcessoMetodoOuPropriedade.simbolo, valor);
                case construtos_1.AcessoIndiceVariavel:
                    const expressaoAcessoIndiceVariavel = expressao;
                    return new construtos_1.AtribuicaoPorIndice(this.hashArquivo, expressaoAcessoIndiceVariavel.linha, expressaoAcessoIndiceVariavel.entidadeChamada, expressaoAcessoIndiceVariavel.indice, valor);
            }
            throw this.erro(igual, 'Tarefa de atribuição inválida');
        }
        return expressao;
    }
    /**
     * Declaração para comando `leia`, para ler dados de entrada do usuário.
     * @returns Um objeto da classe `Leia`.
     */
    async expressaoLeia() {
        const simboloLeia = this.avancarEDevolverAnterior();
        this.consumir(tenda_1.default.PARENTESE_ESQUERDO, "Esperado '(' antes dos argumentos em instrução `leia`.");
        const argumentos = [];
        if (this.simbolos[this.atual].tipo !== tenda_1.default.PARENTESE_DIREITO) {
            do {
                argumentos.push(await this.expressao());
            } while (this.verificarSeSimboloAtualEIgualA(tenda_1.default.VIRGULA));
        }
        this.consumir(tenda_1.default.PARENTESE_DIREITO, "Esperado ')' após os argumentos em instrução `leia`.");
        return new construtos_1.Leia(simboloLeia, argumentos);
    }
    // TODO: Depreciar.
    async expressao() {
        return await this.atribuir();
    }
    async blocoEscopo(tipo) {
        this.pilhaEscopos.empilhar(new informacao_escopo_1.InformacaoEscopo());
        let declaracoes = [];
        while (!this.verificarTipoSimboloAtual(tenda_1.default.FIM) && !this.estaNoFinal()) {
            const retornoDeclaracao = await this.resolverDeclaracaoForaDeBloco();
            if (Array.isArray(retornoDeclaracao)) {
                declaracoes = declaracoes.concat(retornoDeclaracao);
            }
            else {
                declaracoes.push(retornoDeclaracao);
            }
        }
        this.consumir(tenda_1.default.FIM, `Esperado 'fim' para concluir bloco de escopo ${tipo ? 'de ' + tipo : ''}.`);
        this.pilhaEscopos.removerUltimo();
        return declaracoes;
    }
    declaracaoComentarioUmaLinha() {
        const simboloComentario = this.avancarEDevolverAnterior();
        return new declaracoes_1.Comentario(simboloComentario.hashArquivo, simboloComentario.linha, simboloComentario.literal, false);
    }
    declaracaoContinua() {
        if (this.blocos < 1) {
            throw this.erro(this.simbolos[this.atual - 1], "'continua' precisa estar em um laço de repetição.");
        }
        // Ponto-e-vírgula é opcional aqui.
        this.verificarSeSimboloAtualEIgualA(tenda_1.default.PONTO_E_VIRGULA);
        return new declaracoes_1.Continua(this.simbolos[this.atual - 1]);
    }
    async declaracaoEnquanto() {
        try {
            const simboloEnquanto = this.simbolos[this.atual - 1];
            this.blocos += 1;
            const condicao = await this.expressao();
            this.consumir(tenda_1.default.FAÇA, "Esperado 'faça' depois da condição.");
            const blocoCorpo = await this.blocoEscopo('enquanto');
            return new declaracoes_1.Enquanto(condicao, new declaracoes_1.Bloco(simboloEnquanto.linha, simboloEnquanto.hashArquivo, blocoCorpo));
        }
        finally {
            this.blocos -= 1;
        }
    }
    async declaracaoEscreva() {
        const simboloAtual = this.simbolos[this.atual];
        this.consumir(tenda_1.default.PARENTESE_ESQUERDO, "Esperado '(' antes dos valores em escreva.");
        const argumentos = [];
        if (!this.verificarTipoSimboloAtual(tenda_1.default.PARENTESE_DIREITO)) {
            do {
                argumentos.push(await this.expressao());
            } while (this.verificarSeSimboloAtualEIgualA(tenda_1.default.VIRGULA));
        }
        this.consumir(tenda_1.default.PARENTESE_DIREITO, "Esperado ')' após os valores em escreva.");
        // Ponto-e-vírgula é opcional aqui.
        // this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.PONTO_E_VIRGULA);
        return new declaracoes_1.Escreva(Number(simboloAtual.linha), simboloAtual.hashArquivo, argumentos);
    }
    async declaracaoExpressao() {
        const expressao = await this.expressao();
        // Ponto-e-vírgula é opcional aqui.
        this.verificarSeSimboloAtualEIgualA(tenda_1.default.PONTO_E_VIRGULA);
        return new declaracoes_1.Expressao(expressao);
    }
    async declaracaoPara() {
        const simboloPara = this.simbolos[this.atual - 1];
        this.consumir(tenda_1.default.CADA, `Esperado palavra reservada 'cada' após 'para'.`);
        this.blocos += 1;
        const nomeVariavelIteracao = this.consumir(tenda_1.default.IDENTIFICADOR, "Esperado identificador de variável de iteração para instrução 'para cada'.");
        this.consumir(tenda_1.default.EM, "Esperado palavra reservada 'em' após variável de iteração em instrução 'para cada'.");
        // Se for um literal ou identificador numérico, segue um `para`
        // tradicional de Delégua, com variável de controle e passo positivo, incrementado em 1.
        const literalOuVariavelInicio = await this.adicaoOuSubtracao();
        this.blocos -= 1;
        switch (literalOuVariavelInicio.constructor) {
            case construtos_1.Literal:
                return await this.declaracaoParaTradicional(simboloPara, nomeVariavelIteracao, literalOuVariavelInicio);
            // TODO: Terminar
            default:
                return await this.declaracaoParaCada(simboloPara, nomeVariavelIteracao, literalOuVariavelInicio);
        }
    }
    async declaracaoParaCada(simboloParaCada, simboloVariavelIteracao, literalOuVariavelIteravel) {
        const tipoVetor = literalOuVariavelIteravel.tipo;
        // TODO: Permitir 'qualquer' aqui é bastante frágil. Criar uma forma de validar o tipo
        // antes dessa avaliação.
        if (!tipoVetor.endsWith('[]') && !['qualquer', 'vetor'].includes(tipoVetor)) {
            throw this.erro(simboloParaCada, `Variável ou constante em 'para cada' não é iterável. Tipo resolvido: ${tipoVetor}.`);
        }
        let tipoVariavelIteracao = 'qualquer';
        if (tipoVetor.endsWith('[]')) {
            tipoVariavelIteracao = tipoVetor.slice(0, -2);
        }
        this.pilhaEscopos.definirInformacoesVariavel(simboloVariavelIteracao.lexema, new informacao_elemento_sintatico_1.InformacaoElementoSintatico(simboloVariavelIteracao.lexema, tipoVariavelIteracao));
        this.consumir(tenda_1.default.FAÇA, "Esperado palavra reservada 'faça' após literal ou variável de iteração em declaração 'para cada'.");
        const corpo = await this.blocoEscopo();
        return new declaracoes_1.ParaCada(this.hashArquivo, Number(simboloParaCada.linha), new construtos_1.Variavel(this.hashArquivo, simboloVariavelIteracao), literalOuVariavelIteravel, new declaracoes_1.Bloco(simboloParaCada.hashArquivo, simboloParaCada.linha, corpo));
    }
    async declaracaoParaTradicional(simboloPara, simboloVariavelIteracao, literalOuVariavelInicio) {
        this.consumir(tenda_1.default.ATÉ, "Esperado palavra reservada 'até' após literal ou identificador de início de declaração 'para cada'.");
        const literalOuVariavelFim = await this.adicaoOuSubtracao();
        this.consumir(tenda_1.default.FAÇA, "Esperado palavra reservada 'faça' após literal ou variável de passo final em declaração 'para cada'.");
        // A variável de iteração precisa ser definida aqui, para que o corpo de `para cada` (um escopo)
        // seja capaz de reconhecer a variável e seu tipo.
        this.pilhaEscopos.definirInformacoesVariavel(simboloVariavelIteracao.lexema, new informacao_elemento_sintatico_1.InformacaoElementoSintatico(simboloVariavelIteracao.lexema, 'inteiro'));
        const corpo = await this.blocoEscopo();
        // `variavelIteracao <= valorFinal`
        let operadorCondicao = new simbolo_1.Simbolo(tenda_1.default.MENOR_IGUAL, '<=', null, Number(simboloPara.linha), this.hashArquivo);
        let operadorCondicaoIncremento = new simbolo_1.Simbolo(tenda_1.default.MENOR, '<', null, Number(simboloPara.linha), this.hashArquivo);
        return new declaracoes_1.Para(this.hashArquivo, Number(simboloPara.linha), 
        // Inicialização.
        new declaracoes_1.Expressao(new construtos_1.Atribuir(this.hashArquivo, new construtos_1.Variavel(this.hashArquivo, simboloVariavelIteracao, 'inteiro'), literalOuVariavelInicio)), 
        // Condição.
        new construtos_1.Binario(this.hashArquivo, new construtos_1.Variavel(this.hashArquivo, simboloVariavelIteracao, 'inteiro'), operadorCondicao, literalOuVariavelFim), 
        // Incremento, feito em construto especial `FimPara`.
        new construtos_1.FimPara(this.hashArquivo, Number(simboloPara.linha), new construtos_1.Binario(this.hashArquivo, new construtos_1.Variavel(this.hashArquivo, simboloVariavelIteracao, 'inteiro'), operadorCondicaoIncremento, literalOuVariavelFim), new declaracoes_1.Expressao(new construtos_1.Atribuir(this.hashArquivo, new construtos_1.Variavel(this.hashArquivo, simboloVariavelIteracao, 'inteiro'), new construtos_1.Binario(this.hashArquivo, new construtos_1.Variavel(this.hashArquivo, simboloVariavelIteracao, 'inteiro'), new simbolo_1.Simbolo(tenda_1.default.ADICAO, '+', null, Number(simboloPara.linha), this.hashArquivo), new construtos_1.Literal(this.hashArquivo, Number(simboloPara.linha), 1))))), new declaracoes_1.Bloco(simboloPara.hashArquivo, simboloPara.linha, corpo));
    }
    async declaracaoRetorna() {
        const simboloChave = this.simbolos[this.atual - 1];
        let valor = null;
        if ([
            tenda_1.default.CHAVE_ESQUERDA,
            tenda_1.default.COLCHETE_ESQUERDO,
            tenda_1.default.FALSO,
            tenda_1.default.FUNÇÃO,
            tenda_1.default.IDENTIFICADOR,
            tenda_1.default.ISTO,
            tenda_1.default.NÃO,
            tenda_1.default.NUMERO,
            tenda_1.default.NADA,
            tenda_1.default.PARENTESE_ESQUERDO,
            tenda_1.default.SUPER,
            tenda_1.default.TEXTO,
            tenda_1.default.VERDADEIRO,
        ].includes(this.simbolos[this.atual].tipo)) {
            valor = await this.expressao();
        }
        // Ponto-e-vírgula é opcional aqui.
        this.verificarSeSimboloAtualEIgualA(tenda_1.default.PONTO_E_VIRGULA);
        return new declaracoes_1.Retorna(simboloChave, valor);
    }
    async declaracaoSe() {
        const condicao = await this.expressao();
        this.consumir(tenda_1.default.ENTÃO, "Esperado 'então' após a condição.");
        const caminhoEntao = await this.resolverDeclaracao();
        let caminhoSenao = null;
        if (this.verificarSeSimboloAtualEIgualA(tenda_1.default.SENÃO)) {
            caminhoSenao = await this.resolverDeclaracao();
        }
        return new declaracoes_1.Se(condicao, caminhoEntao, [], caminhoSenao);
    }
    declaracaoSustar() {
        if (this.blocos < 1) {
            throw this.erro(this.simbolos[this.atual - 1], "'sustar' ou 'pausa' deve estar dentro de um laço de repetição.");
        }
        // Ponto-e-vírgula é opcional aqui.
        this.verificarSeSimboloAtualEIgualA(tenda_1.default.PONTO_E_VIRGULA);
        return new declaracoes_1.Sustar(this.simbolos[this.atual - 1]);
    }
    /**
     * Todas as resoluções triviais da linguagem, ou seja, todas as
     * resoluções que podem ocorrer dentro ou fora de um bloco.
     * @returns Normalmente uma `Declaracao`, mas há casos em que
     * outros objetos podem ser retornados.
     * @see resolverDeclaracaoForaDeBloco para as declarações que não podem
     * ocorrer em blocos de escopo elementares.
     */
    async resolverDeclaracao() {
        switch (this.simbolos[this.atual].tipo) {
            case tenda_1.default.COMENTARIO:
                return this.declaracaoComentarioUmaLinha();
            case tenda_1.default.CONTINUA:
                this.avancarEDevolverAnterior();
                return this.declaracaoContinua();
            case tenda_1.default.ENQUANTO:
                this.avancarEDevolverAnterior();
                return this.declaracaoEnquanto();
            case tenda_1.default.EXIBA:
                this.avancarEDevolverAnterior();
                return this.declaracaoEscreva();
            case tenda_1.default.FAÇA:
                this.avancarEDevolverAnterior();
                return await this.blocoEscopo();
            case tenda_1.default.PARA:
                this.avancarEDevolverAnterior();
                return this.declaracaoPara();
            case tenda_1.default.PAUSA:
            case tenda_1.default.SUSTAR:
                this.avancarEDevolverAnterior();
                return this.declaracaoSustar();
            case tenda_1.default.SE:
                this.avancarEDevolverAnterior();
                return this.declaracaoSe();
            case tenda_1.default.RETORNA:
                this.avancarEDevolverAnterior();
                return this.declaracaoRetorna();
            case tenda_1.default.TENTE:
                this.avancarEDevolverAnterior();
                return await this.declaracaoTente();
            case tenda_1.default.SEJA:
                this.avancarEDevolverAnterior();
                return this.declaracaoDeVariaveisOuFuncoes();
        }
        const simboloAtual = this.simbolos[this.atual];
        if (simboloAtual.tipo === tenda_1.default.IDENTIFICADOR) {
            // Pela gramática, a seguinte situação não pode ocorrer:
            // 1. O símbolo anterior ser um identificador; e
            // 2. O símbolo anterior estar na mesma linha do identificador atual.
            const simboloAnterior = this.simbolos[this.atual - 1];
            if (!!simboloAnterior &&
                simboloAnterior.tipo === tenda_1.default.IDENTIFICADOR &&
                simboloAnterior.linha === simboloAtual.linha) {
                throw this.erro(this.simbolos[this.atual], 'Não é permitido ter dois identificadores seguidos na mesma linha.');
            }
        }
        return await this.declaracaoExpressao();
    }
    logicaComumInferenciaTiposVariaveis(inicializador) {
        switch (inicializador.constructor) {
            case construtos_1.AcessoIndiceVariavel:
                const entidadeChamadaAcessoIndiceVariavel = inicializador
                    .entidadeChamada;
                return entidadeChamadaAcessoIndiceVariavel.tipo.slice(0, -2);
            case construtos_1.Chamada:
                const entidadeChamadaChamada = inicializador.entidadeChamada;
                switch (entidadeChamadaChamada.constructor) {
                    case construtos_1.AcessoMetodo:
                        const entidadeChamadaAcessoMetodo = entidadeChamadaChamada;
                        return entidadeChamadaAcessoMetodo.tipoRetornoMetodo;
                    case construtos_1.AcessoMetodoOuPropriedade:
                        // Este caso ocorre quando a variável/constante é do tipo 'qualquer',
                        // e a chamada normalmente é feita para uma primitiva.
                        // A inferência, portanto, ocorre pelo uso da primitiva.
                        const entidadeChamadaAcessoMetodoOuPropriedade = entidadeChamadaChamada;
                        if (this.primitivasConhecidas.hasOwnProperty(entidadeChamadaAcessoMetodoOuPropriedade.simbolo.lexema)) {
                            return this.primitivasConhecidas[entidadeChamadaAcessoMetodoOuPropriedade.simbolo.lexema].tipo;
                        }
                        throw new erro_avaliador_sintatico_1.ErroAvaliadorSintatico(entidadeChamadaAcessoMetodoOuPropriedade.simbolo, `Primitiva '${entidadeChamadaAcessoMetodoOuPropriedade.simbolo.lexema}' não existe.`);
                    case construtos_1.AcessoPropriedade:
                        const entidadeChamadaAcessoPropriedade = entidadeChamadaChamada;
                        return entidadeChamadaAcessoPropriedade.tipoRetornoPropriedade;
                    case construtos_1.ArgumentoReferenciaFuncao:
                        // TODO: Voltar aqui se necessário.
                        return 'qualquer';
                    case construtos_1.ReferenciaFuncao:
                        const entidadeChamadaReferenciaFuncao = entidadeChamadaChamada;
                        return entidadeChamadaReferenciaFuncao.tipo;
                    case construtos_1.Variavel:
                        const entidadeChamadaVariavel = entidadeChamadaChamada;
                        return entidadeChamadaVariavel.tipo;
                }
                break;
            case construtos_1.FuncaoConstruto:
                const funcaoConstruto = inicializador;
                return `função<${funcaoConstruto.tipo}>`;
            case construtos_1.Leia:
                return 'texto';
            case tuplas_1.Dupla:
            case tuplas_1.Trio:
            case tuplas_1.Quarteto:
            case tuplas_1.Quinteto:
            case tuplas_1.Sexteto:
            case tuplas_1.Septeto:
            case tuplas_1.Octeto:
            case tuplas_1.Noneto:
            case tuplas_1.Deceto:
                return delegua_1.default.TUPLA;
            default:
                return inicializador.tipo;
        }
    }
    /**
     * Após palavra reservada `seja`, é esperado ou uma variável, ou uma função.
     * @returns Um Construto, ou do tipo `Var` para variável, ou do tipo `FuncaoDeclaracao` se for
     *          declaração de função.
     */
    async declaracaoDeVariaveisOuFuncoes() {
        const identificador = this.consumir(tenda_1.default.IDENTIFICADOR, 'Esperado nome da variável ou função.');
        if (this.verificarSeSimboloAtualEIgualA(tenda_1.default.PARENTESE_ESQUERDO)) {
            return this.declaracaoDeFuncao(identificador);
        }
        this.consumir(tenda_1.default.IGUAL, "Esperado símbolo de igual após nome de identificador em declaração 'seja'.");
        const inicializador = await this.expressao();
        const tipo = this.logicaComumInferenciaTiposVariaveis(inicializador);
        this.pilhaEscopos.definirInformacoesVariavel(identificador.lexema, new informacao_elemento_sintatico_1.InformacaoElementoSintatico(identificador.lexema, tipo));
        return new declaracoes_1.Var(identificador, inicializador, tipo);
    }
    async logicaComumParametros() {
        const parametros = [];
        do {
            const parametro = {};
            parametro.nome = this.consumir(tenda_1.default.IDENTIFICADOR, 'Esperado nome do parâmetro.');
            if (this.verificarSeSimboloAtualEIgualA(tenda_1.default.IGUAL)) {
                const valorPadrao = await this.primario();
                parametro.valorPadrao = valorPadrao;
            }
            if (this.verificarSeSimboloAtualEIgualA(tenda_1.default.DOIS_PONTOS)) {
                let tipoDadoParametro = this.verificarDefinicaoTipoAtual();
                parametro.tipoDado = tipoDadoParametro;
                this.avancarEDevolverAnterior();
            }
            this.pilhaEscopos.definirInformacoesVariavel(parametro.nome.lexema, new informacao_elemento_sintatico_1.InformacaoElementoSintatico(parametro.nome.lexema, parametro.tipoDado || 'qualquer'));
            parametros.push(parametro);
            if (parametro.abrangencia === 'multiplo')
                break;
        } while (this.verificarSeSimboloAtualEIgualA(tenda_1.default.VIRGULA));
        return parametros;
    }
    async corpoDaFuncao(tipo) {
        // O parêntese esquerdo aqui é o símbolo atual.
        // Ele já foi lido neste ponto.
        const parenteseEsquerdo = this.simbolos[this.atual];
        let parametros = [];
        if (!this.verificarTipoSimboloAtual(tenda_1.default.PARENTESE_DIREITO)) {
            parametros = await this.logicaComumParametros();
        }
        this.consumir(tenda_1.default.PARENTESE_DIREITO, "Esperado ')' após parâmetros.");
        switch (tipo) {
            case 'função':
                this.consumir(tenda_1.default.SUBTRACAO, 'Esperado seta após fechamento de parênteses para declaração de função.');
                this.consumir(tenda_1.default.MAIOR, 'Esperado seta após fechamento de parênteses para declaração de função.');
                break;
            case 'implícita':
                this.consumir(tenda_1.default.IGUAL, 'Esperado sinal de igual após fechamento de parênteses para declaração de função.');
                break;
        }
        const corpo = (await this.resolverDeclaracao());
        // Se o corpo for uma `Expressao`, corpo é convertido para `Retorna`.
        // Tenda trabalha com retornos implícitos.
        let corpoResolvido = [];
        if (corpo.constructor === declaracoes_1.Expressao) {
            const expressaoComoRetorna = new declaracoes_1.Retorna(new simbolo_1.Simbolo(tenda_1.default.RETORNA, 'retorna', 'retorna', parenteseEsquerdo.linha, this.hashArquivo), corpo.expressao);
            corpoResolvido.push(expressaoComoRetorna);
        }
        else {
            // TODO: Verificar se `corpo` é sempre um Array aqui.
            corpoResolvido = corpo;
        }
        // TODO: Inferir o tipo de retorno corretamente.
        return new construtos_1.FuncaoConstruto(this.hashArquivo, Number(parenteseEsquerdo.linha), parametros, corpoResolvido, 'qualquer');
    }
    /**
     * Até então, Tenda não tem casos de declarações fora de blocos.
     * Isso pode mudar futuramente. Portanto, esta seção será mantida.
     * @returns Uma `Declaracao` ou várias, dependendo do retorno de `resolverDeclaracao`.
     * @see resolverDeclaracao
     */
    async resolverDeclaracaoForaDeBloco() {
        try {
            return await this.resolverDeclaracao();
        }
        catch (erro) {
            this.sincronizar();
            this.erros.push(erro);
            return null;
        }
    }
    /**
     * Usado quando há erros na avaliação sintática.
     * Garante que o código não entre em loop infinito.
     * @returns Sempre retorna `void`.
     */
    sincronizar() {
        this.avancarEDevolverAnterior();
        while (!this.estaNoFinal()) {
            const tipoSimboloAtual = this.simbolos[this.atual - 1].tipo;
            switch (tipoSimboloAtual) {
                case tenda_1.default.FUNÇÃO:
                case tenda_1.default.VARIAVEL:
                case tenda_1.default.PARA:
                case tenda_1.default.SE:
                case tenda_1.default.ENQUANTO:
                case tenda_1.default.EXIBA:
                case tenda_1.default.RETORNA:
                    return;
            }
            this.avancarEDevolverAnterior();
        }
    }
    /**
     * Inicializa o primeiro nível da pilha de escopos, normalmente com ítens da biblioteca global.
     */
    inicializarPilhaEscopos() {
        this.pilhaEscopos = new pilha_escopos_1.PilhaEscopos();
        this.pilhaEscopos.empilhar(new informacao_escopo_1.InformacaoEscopo());
        // TODO: Escrever algum tipo de validação aqui.
        for (const tipos of Object.values(this.tiposDeFerramentasExternas)) {
            for (const [nomeTipo, tipo] of Object.entries(tipos)) {
                this.pilhaEscopos.definirInformacoesVariavel(nomeTipo, new informacao_elemento_sintatico_1.InformacaoElementoSintatico(nomeTipo, tipo));
            }
        }
    }
    async analisar(retornoLexador, hashArquivo) {
        const inicioAnalise = (0, browser_process_hrtime_1.default)();
        this.erros = [];
        this.atual = 0;
        this.blocos = 0;
        this.hashArquivo = hashArquivo || 0;
        this.simbolos = (retornoLexador === null || retornoLexador === void 0 ? void 0 : retornoLexador.simbolos) || [];
        this.tiposDefinidosEmCodigo = {};
        this.inicializarPilhaEscopos();
        let declaracoes = [];
        while (!this.estaNoFinal()) {
            const retornoDeclaracao = await this.resolverDeclaracaoForaDeBloco();
            if (Array.isArray(retornoDeclaracao)) {
                declaracoes = declaracoes.concat(retornoDeclaracao);
            }
            else if (retornoDeclaracao !== null) {
                declaracoes.push(retornoDeclaracao);
            }
        }
        if (this.performance) {
            const deltaAnalise = (0, browser_process_hrtime_1.default)(inicioAnalise);
            // eslint-disable-next-line no-undef
            console.log(`[Avaliador Sintático Tenda] Tempo para análise: ${deltaAnalise[0] * 1e9 + deltaAnalise[1]}ns`);
        }
        return {
            declaracoes: declaracoes,
            erros: this.erros,
        };
    }
}
exports.AvaliadorSintaticoTenda = AvaliadorSintaticoTenda;
//# sourceMappingURL=avaliador-sintatico-tenda.js.map