"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AvaliadorSintaticoBase = void 0;
const construtos_1 = require("../construtos");
const declaracoes_1 = require("../declaracoes");
const erro_avaliador_sintatico_1 = require("./erro-avaliador-sintatico");
const comum_1 = __importDefault(require("../tipos-de-simbolos/comum"));
/**
 * O Avaliador Sintático Base é uma tentativa de mapear métodos em comum
 * entre todos os outros Avaliadores Sintáticos. Depende de um dicionário
 * de tipos de símbolos comuns entre todos os dialetos.
 */
class AvaliadorSintaticoBase {
    erro(simbolo, mensagemDeErro) {
        const excecao = new erro_avaliador_sintatico_1.ErroAvaliadorSintatico(simbolo, mensagemDeErro);
        return excecao;
    }
    consumir(tipo, mensagemDeErro) {
        if (this.verificarTipoSimboloAtual(tipo))
            return this.avancarEDevolverAnterior();
        let simboloErro = this.simbolos[this.atual];
        if (this.simbolos.length === 0) {
            simboloErro = {
                hashArquivo: this.hashArquivo,
                linha: 1,
            };
        }
        else if (this.atual >= this.simbolos.length) {
            simboloErro = this.simbolos[this.simbolos.length - 1];
        }
        throw this.erro(simboloErro, mensagemDeErro);
    }
    simboloAnterior() {
        if (this.atual === 0) {
            throw new Error('Este é o primeiro símbolo da sequência vinda do Lexador.');
        }
        return this.simbolos[this.atual - 1];
    }
    verificarTipoSimboloAtual(tipo) {
        if (this.estaNoFinal())
            return false;
        return this.simbolos[this.atual].tipo === tipo;
    }
    verificarTipoProximoSimbolo(tipo) {
        return this.simbolos[this.atual + 1].tipo === tipo;
    }
    estaNoFinal() {
        return this.atual === this.simbolos.length;
    }
    avancarEDevolverAnterior() {
        if (!this.estaNoFinal())
            this.atual += 1;
        return this.simbolos[this.atual - 1];
    }
    verificarSeSimboloAtualEIgualA(...argumentos) {
        for (let i = 0; i < argumentos.length; i++) {
            const tipoAtual = argumentos[i];
            if (this.verificarTipoSimboloAtual(tipoAtual)) {
                this.avancarEDevolverAnterior();
                return true;
            }
        }
        return false;
    }
    async finalizarChamada(entidadeChamada) {
        const argumentos = [];
        if (!this.verificarTipoSimboloAtual(comum_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(comum_1.default.VIRGULA));
        }
        this.consumir(comum_1.default.PARENTESE_DIREITO, "Esperado ')' após os argumentos.");
        return new construtos_1.Chamada(this.hashArquivo, entidadeChamada, argumentos);
    }
    async unario() {
        if (this.verificarSeSimboloAtualEIgualA(comum_1.default.NEGACAO, comum_1.default.SUBTRACAO)) {
            const operador = this.simbolos[this.atual - 1];
            const direito = await this.unario();
            return Promise.resolve(new construtos_1.Unario(this.hashArquivo, operador, direito, 'ANTES'));
        }
        return await this.chamar();
    }
    /**
     * A exponenciacão é uma exceção na ordem de avaliação (resolve primeiro à direita).
     * Por isso `direito` chama `exponenciacao()`, e não `unario()`.
     * @returns {Binario} A expressão binária na forma do construto `Binario`.
     */
    async exponenciacao() {
        let expressao = await this.unario();
        while (this.verificarSeSimboloAtualEIgualA(comum_1.default.EXPONENCIACAO)) {
            const operador = this.simbolos[this.atual - 1];
            const direito = await this.exponenciacao();
            expressao = new construtos_1.Binario(this.hashArquivo, expressao, operador, direito);
        }
        return expressao;
    }
    async multiplicar() {
        let expressao = await this.exponenciacao();
        while (this.verificarSeSimboloAtualEIgualA(comum_1.default.DIVISAO, comum_1.default.DIVISAO_INTEIRA, comum_1.default.MULTIPLICACAO, comum_1.default.MODULO)) {
            const operador = this.simbolos[this.atual - 1];
            const direito = await this.exponenciacao();
            expressao = new construtos_1.Binario(this.hashArquivo, expressao, operador, direito);
        }
        return expressao;
    }
    async adicaoOuSubtracao() {
        let expressao = await this.multiplicar();
        while (this.verificarSeSimboloAtualEIgualA(comum_1.default.SUBTRACAO, comum_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;
    }
    /**
     * Este método é usado por Delégua e alguns dialetos de Portugol que possuem declarações
     * de múltiplas variáveis na mesma linha.
     */
    declaracaoDeVariaveis() {
        throw new Error('Método não implementado.');
    }
    async comparar() {
        let expressao = await this.adicaoOuSubtracao();
        while (this.verificarSeSimboloAtualEIgualA(comum_1.default.MAIOR, comum_1.default.MAIOR_IGUAL, comum_1.default.MENOR, comum_1.default.MENOR_IGUAL)) {
            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 comparacaoIgualdade() {
        let expressao = await this.comparar();
        while (this.verificarSeSimboloAtualEIgualA(comum_1.default.DIFERENTE, comum_1.default.IGUAL, comum_1.default.IGUAL_IGUAL)) {
            const operador = this.simbolos[this.atual - 1];
            const direito = await this.comparar();
            expressao = new construtos_1.Binario(this.hashArquivo, expressao, operador, direito);
        }
        return expressao;
    }
    async e() {
        let expressao = await this.comparacaoIgualdade();
        while (this.verificarSeSimboloAtualEIgualA(comum_1.default.E)) {
            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 ou() {
        let expressao = await this.e();
        while (this.verificarSeSimboloAtualEIgualA(comum_1.default.OU)) {
            const operador = this.simbolos[this.atual - 1];
            const direito = await this.e();
            expressao = new construtos_1.Logico(this.hashArquivo, expressao, operador, direito);
        }
        return expressao;
    }
    /**
     * Processa tuplas, que são expressões separadas por vírgula entre parênteses.
     * Se não houver vírgula, retorna apenas a expressão simples.
     */
    async tupla() {
        let expressao = await this.ou();
        // Se não há vírgula, retorna a expressão simples
        if (!this.verificarSeSimboloAtualEIgualA(comum_1.default.VIRGULA)) {
            return expressao;
        }
        // Se há vírgula, então é uma tupla
        const elementos = [expressao];
        do {
            if (this.verificarTipoSimboloAtual(comum_1.default.PARENTESE_DIREITO)) {
                break;
            }
            elementos.push(await this.ou());
        } while (this.verificarSeSimboloAtualEIgualA(comum_1.default.VIRGULA));
        return new construtos_1.TuplaN(this.hashArquivo, expressao.linha, elementos);
    }
    async expressao() {
        return await this.atribuir();
    }
    async funcao(tipo) {
        // Avançar `função` ou `funcao`.
        this.avancarEDevolverAnterior();
        const nomeFuncao = this.consumir(comum_1.default.IDENTIFICADOR, `Esperado nome ${tipo}.`);
        const corpo = await this.corpoDaFuncao(tipo);
        return new declaracoes_1.FuncaoDeclaracao(nomeFuncao, corpo);
    }
    async logicaComumParametros() {
        const parametros = [];
        do {
            if (parametros.length >= 255) {
                throw this.erro(this.simbolos[this.atual], 'Função não pode ter mais de 255 parâmetros.');
            }
            const parametro = {};
            if (this.simbolos[this.atual].tipo === comum_1.default.MULTIPLICACAO) {
                this.consumir(comum_1.default.MULTIPLICACAO, null);
                parametro.abrangencia = 'multiplo';
            }
            else {
                parametro.abrangencia = 'padrao';
            }
            parametro.nome = this.consumir(comum_1.default.IDENTIFICADOR, 'Esperado nome do parâmetro.');
            if (this.verificarSeSimboloAtualEIgualA(comum_1.default.IGUAL)) {
                parametro.valorPadrao = await this.primario();
            }
            parametros.push(parametro);
            if (parametro.abrangencia === 'multiplo')
                break;
        } while (this.verificarSeSimboloAtualEIgualA(comum_1.default.VIRGULA));
        return parametros;
    }
    /**
     * Os métodos a seguir só devem ser implementados se o dialeto
     * em questão realmente possui a funcionalidade, e devem levantar
     * erro em caso contrário.
     */
    bitShift() {
        throw new Error('Método não implementado.');
    }
    bitE() {
        throw new Error('Método não implementado.');
    }
    bitOu() {
        throw new Error('Método não implementado.');
    }
    declaracaoContinua() {
        throw new Error('Método não implementado.');
    }
    declaracaoDeClasse() {
        throw new Error('Método não implementado.');
    }
    declaracaoDeVariavel() {
        throw new Error('Método não implementado.');
    }
    declaracaoExpressao(simboloAnterior) {
        throw new Error('Método não implementado.');
    }
    declaracaoRetorna() {
        throw new Error('Método não implementado.');
    }
    declaracaoSustar() {
        throw new Error('Método não implementado.');
    }
    declaracaoTente() {
        throw new Error('Método não implementado.');
    }
    em() {
        throw new Error('Método não implementado.');
    }
    resolverDeclaracao() {
        throw new Error('Método não implementado.');
    }
}
exports.AvaliadorSintaticoBase = AvaliadorSintaticoBase;
//# sourceMappingURL=avaliador-sintatico-base.js.map