UNPKG

@designliquido/delegua

Version:

Linguagem de programação simples e moderna usando português estruturado.

1,711 lines 99.3 kB
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AvaliadorSintaticoPitugues = void 0;
const browser_process_hrtime_1 = __importDefault(require("browser-process-hrtime"));
const construtos_1 = require("../../construtos");
const declaracoes_1 = require("../../declaracoes");
const erro_avaliador_sintatico_1 = require("../erro-avaliador-sintatico");
const lexador_1 = require("../../lexador");
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 micro_lexador_pitugues_1 = require("../../lexador/micro-lexador-pitugues");
const micro_avaliador_sintatico_pitugues_1 = require("./micro-avaliador-sintatico-pitugues");
const comum_1 = require("../comum");
const pitugues_1 = __importDefault(require("../../tipos-de-dados/dialetos/pitugues"));
const pitugues_2 = __importDefault(require("../../tipos-de-simbolos/pitugues"));
const primitivas_dicionario_1 = __importDefault(require("../../bibliotecas/primitivas-dicionario"));
const primitivas_numero_1 = __importDefault(require("../../bibliotecas/primitivas-numero"));
const primitivas_texto_1 = __importDefault(require("../../bibliotecas/primitivas-texto"));
const primitivas_vetor_1 = __importDefault(require("../../bibliotecas/primitivas-vetor"));
const primitivas_tupla_1 = __importDefault(require("../../bibliotecas/dialetos/pitugues/primitivas-tupla"));
/**
 * O avaliador sintático (_Parser_) é responsável por transformar os símbolos do Lexador em estruturas de alto nível.
 * Essas estruturas de alto nível são as partes que executam lógica de programação de fato.
 * Há dois grupos de estruturas de alto nível: Construtos e Declarações.
 *
 * A grande diferença entre este avaliador e os demais é a forma como são entendidos os blocos de escopo.
 * Este avaliador espera uma estrutura de pragmas, que explica quantos espaços há na frente de cada linha.
 */
class AvaliadorSintaticoPitugues {
    constructor(performance = false) {
        this.atual = 0;
        this.blocos = 0;
        this.performance = performance;
        this.intuirTipoQualquerParaIdentificadores = false;
        this.escopos = [];
        this.pilhaDecoradores = [];
        this.pilhaEscopos = new pilha_escopos_1.PilhaEscopos();
        this.primitivasConhecidas = {};
        this.tiposDefinidosEmCodigo = {};
        this.tiposDefinidosPorBibliotecas = {};
        (0, comum_1.registrarPrimitiva)(this.primitivasConhecidas, 'dicionário', primitivas_dicionario_1.default);
        (0, comum_1.registrarPrimitiva)(this.primitivasConhecidas, 'número', primitivas_numero_1.default);
        (0, comum_1.registrarPrimitiva)(this.primitivasConhecidas, 'texto', primitivas_texto_1.default);
        (0, comum_1.registrarPrimitiva)(this.primitivasConhecidas, 'vetor', primitivas_vetor_1.default);
        (0, comum_1.registrarPrimitiva)(this.primitivasConhecidas, 'tupla', primitivas_tupla_1.default);
    }
    logicaComumInferenciaTiposAcessoMetodoOuPropriedade(entidadeChamada) {
        // Algumas coisas podem acontecer aqui.
        // Uma delas é a variável/constante ser uma classe padrão.
        // Isso ocorre quando a importação é feita de uma biblioteca Node.js.
        // Nesse caso, o tipo de `entidadeChamada.objeto` começa com uma letra maiúscula.
        if (entidadeChamada.objeto.tipo && entidadeChamada.objeto.tipo.match(/^[A-Z]/)) {
            const tipoCorrespondente = this.tiposDefinidosPorBibliotecas[entidadeChamada.objeto.tipo];
            if (!tipoCorrespondente) {
                throw new erro_avaliador_sintatico_1.ErroAvaliadorSintatico(entidadeChamada.simbolo, `Tipo '${entidadeChamada.objeto.tipo}' não foi encontrado entre os tipos definidos por bibliotecas.`);
            }
            if (!(entidadeChamada.simbolo.lexema in tipoCorrespondente.metodos) &&
                !(entidadeChamada.simbolo.lexema in tipoCorrespondente.propriedades)) {
                throw new erro_avaliador_sintatico_1.ErroAvaliadorSintatico(entidadeChamada.simbolo, `Membro '${entidadeChamada.simbolo.lexema}' não existe no tipo '${entidadeChamada.objeto.tipo}'.`);
            }
            if (entidadeChamada.simbolo.lexema in tipoCorrespondente.metodos) {
                const metodoCorrespondente = tipoCorrespondente.metodos[entidadeChamada.simbolo.lexema];
                return metodoCorrespondente.tipoRetorno || 'qualquer';
            }
            const propriedadeCorrespondente = tipoCorrespondente.propriedades[entidadeChamada.simbolo.lexema];
            return propriedadeCorrespondente.tipo;
        }
        // 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.
        for (const primitiva in this.primitivasConhecidas) {
            if (this.primitivasConhecidas[primitiva].hasOwnProperty(entidadeChamada.simbolo.lexema)) {
                return this.primitivasConhecidas[primitiva][entidadeChamada.simbolo.lexema].tipo;
            }
        }
        throw new erro_avaliador_sintatico_1.ErroAvaliadorSintatico(entidadeChamada.simbolo, `Primitiva '${entidadeChamada.simbolo.lexema}' não existe.`);
    }
    logicaComumInferenciaTiposVariaveisEConstantes(inicializador, tipoPrevio) {
        if (tipoPrevio !== 'qualquer') {
            return tipoPrevio;
        }
        switch (inicializador.constructor) {
            case construtos_1.AcessoIndiceVariavel:
                const entidadeChamadaAcessoIndiceVariavel = inicializador
                    .entidadeChamada;
                // Este condicional ocorre com chamadas aninhadas. Por exemplo, `vetor[1][2]`.
                if (entidadeChamadaAcessoIndiceVariavel.constructor === construtos_1.AcessoIndiceVariavel) {
                    return this.logicaComumInferenciaTiposVariaveisEConstantes(entidadeChamadaAcessoIndiceVariavel, tipoPrevio);
                }
                if (entidadeChamadaAcessoIndiceVariavel.tipo.endsWith('[]')) {
                    return entidadeChamadaAcessoIndiceVariavel.tipo.slice(0, -2);
                }
                // Normalmente, `entidadeChamadaAcessoIndiceVariavel.tipo` aqui será 'vetor'.
                return 'qualquer';
            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:
                        return this.logicaComumInferenciaTiposAcessoMetodoOuPropriedade(entidadeChamadaChamada);
                    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 construtos_1.Dupla:
            case construtos_1.Trio:
            case construtos_1.Quarteto:
            case construtos_1.Quinteto:
            case construtos_1.Sexteto:
            case construtos_1.Septeto:
            case construtos_1.Octeto:
            case construtos_1.Noneto:
            case construtos_1.Deceto:
                return 'tupla';
            // TODO: Talvez reabilitar.
            /* case ImportarBiblioteca:
            case ModuloDeclaracoes:
                return 'módulo'; */
            default:
                return inicializador.tipo;
        }
    }
    async expressaoLeia() {
        const simboloOriginal = this.avancarEDevolverAnterior();
        const simboloLeia = new lexador_1.Simbolo(pitugues_2.default.LEIA, simboloOriginal.lexema, simboloOriginal.literal, simboloOriginal.linha, simboloOriginal.hashArquivo);
        this.consumir(pitugues_2.default.PARENTESE_ESQUERDO, "Esperado '(' antes dos valores em leia.");
        const argumentos = [];
        do {
            argumentos.push(await this.expressao());
        } while (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.VIRGULA));
        this.consumir(pitugues_2.default.PARENTESE_DIREITO, "Esperado ')' após os valores em leia.");
        return new construtos_1.Leia(simboloLeia, argumentos);
    }
    declaracaoDeVariavel() {
        throw new Error('Método não implementado.');
    }
    variavelJaDeclarada(nome) {
        try {
            this.pilhaEscopos.obterTipoVariavelPorNome(nome);
            return true;
        }
        catch (_a) {
            return false;
        }
    }
    async declaracaoImplicitaVariaveis() {
        const identificador = this.consumir(pitugues_2.default.IDENTIFICADOR, 'Esperado nome de variável.');
        let tipo = 'qualquer';
        let tipoExplicito = false;
        if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.DOIS_PONTOS)) {
            tipo = this.verificarDefinicaoTipoAtual();
            tipoExplicito = true;
            this.avancarEDevolverAnterior();
        }
        this.consumir(pitugues_2.default.IGUAL, "Esperado '=' após identificador.");
        if (this.estaNoFinal()) {
            throw this.erro(this.simboloAnterior(), 'Esperado valor após o símbolo de igual.');
        }
        const valor = await this.expressao();
        if (!tipoExplicito) {
            tipo = this.logicaComumInferenciaTiposVariaveisEConstantes(valor, 'qualquer');
        }
        this.pilhaEscopos.definirInformacoesVariavel(identificador.lexema, new informacao_elemento_sintatico_1.InformacaoElementoSintatico(identificador.lexema, tipo));
        return new declaracoes_1.Var(identificador, valor, tipo, tipoExplicito);
    }
    temPadraoMultiplaAtribuicao() {
        // Verifica padrão: IDENTIFICADOR, VIRGULA, IDENTIFICADOR, ..., IGUAL
        // Também aceita * antes de identificador
        let pos = this.atual;
        let identificadores = 0;
        while (pos < this.simbolos.length) {
            // Consome opcionalmente o operador de resto (*)
            if (this.simbolos[pos].tipo === pitugues_2.default.MULTIPLICACAO) {
                pos++;
            }
            // Verifica se há um identificador obrigatório
            if (pos >= this.simbolos.length ||
                this.simbolos[pos].tipo !== pitugues_2.default.IDENTIFICADOR) {
                return false;
            }
            pos++;
            identificadores++;
            // Verifica o próximo símbolo (deve ser ',' ou '=')
            if (pos >= this.simbolos.length)
                return false;
            const proximoTipo = this.simbolos[pos].tipo;
            if (proximoTipo === pitugues_2.default.IGUAL) {
                return identificadores >= 2;
            }
            if (proximoTipo === pitugues_2.default.VIRGULA) {
                pos++;
                continue;
            }
            // Se chegou aqui, não é vírgula nem igual, então o padrão quebrou
            return false;
        }
        return false;
    }
    temPadraoVarComoPalavraChave() {
        // Verifica padrão: var identificador = ...
        if (this.simbolos[this.atual].lexema !== 'var') {
            return false;
        }
        const proximo = this.simbolos[this.atual + 1];
        if (!proximo)
            return false;
        if (proximo.tipo === pitugues_2.default.MULTIPLICACAO) {
            return false;
        }
        // Busca pelo sinal de igualdade, permitido apenas IDENTIFICADORES e VÍRGULAS no caminho
        let pos = this.atual + 1;
        while (pos < this.simbolos.length) {
            const tipo = this.simbolos[pos].tipo;
            // Encontrou padrão var a, b = ...
            if (tipo === pitugues_2.default.IGUAL)
                return true;
            // Encontrou algo como 'var a + b'
            if (tipo !== pitugues_2.default.IDENTIFICADOR && tipo !== pitugues_2.default.VIRGULA)
                return false;
            pos++;
        }
        return false;
    }
    consumirIdentificadores() {
        const identificadores = [];
        let indexResto = -1;
        do {
            let ehRestoAtual = false;
            // Verifica * como token separado
            if (this.verificarTipoSimboloAtual(pitugues_2.default.MULTIPLICACAO)) {
                this.consumir(pitugues_2.default.MULTIPLICACAO, '');
                ehRestoAtual = true;
            }
            const identificador = this.consumir(pitugues_2.default.IDENTIFICADOR, ehRestoAtual
                ? 'Esperado nome de variável após operador *.'
                : 'Esperado nome de variável.');
            // Verifica * no nome da variável
            if (identificador.lexema.startsWith('*')) {
                ehRestoAtual = true;
                identificador.lexema = identificador.lexema.slice(1);
            }
            if (ehRestoAtual) {
                if (indexResto > -1) {
                    throw this.erro(this.simboloAtual(), 'Sintaxe inválida: apenas um operador de resto é permitido.');
                }
                indexResto = identificadores.length;
            }
            identificadores.push(identificador);
        } while (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.VIRGULA));
        return { simbolos: identificadores, indexResto };
    }
    async consumirInicializadores() {
        const inicializadores = [];
        do {
            if (this.estaNoFinal()) {
                throw this.erro(this.simboloAtual(), 'Esperado inicializador após vírgula.');
            }
            inicializadores.push(await this.expressao());
        } while (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.VIRGULA));
        return inicializadores;
    }
    construirValidacaoDesempacotamento(identificador, origem, qtdEsperada) {
        const linha = identificador.linha;
        const chamadaTamanho = new construtos_1.Chamada(this.hashArquivo, new construtos_1.Variavel(this.hashArquivo, new lexador_1.Simbolo(pitugues_2.default.IDENTIFICADOR, 'tamanho', null, linha, -1)), [origem]);
        const condicaoErro = new construtos_1.Binario(this.hashArquivo, chamadaTamanho, new lexador_1.Simbolo(pitugues_2.default.DIFERENTE, '!=', null, linha, -1), new construtos_1.Literal(this.hashArquivo, linha, qtdEsperada, 'número'));
        const mensagem = `Erro de execução: Você tentou desempacotar em ${qtdEsperada} variáveis, mas o vetor possui tamanho diferente.`;
        const falha = new declaracoes_1.Falhar(new lexador_1.Simbolo(pitugues_2.default.FALHAR, 'falhar', null, linha, -1), new construtos_1.Literal(this.hashArquivo, linha, mensagem, 'texto'));
        return new declaracoes_1.Se(condicaoErro, new declaracoes_1.Bloco(this.hashArquivo, linha, [falha]), [], null);
    }
    async declaracaoDeVariaveis() {
        const { simbolos: identificadores, indexResto } = this.consumirIdentificadores();
        this.consumir(pitugues_2.default.IGUAL, 'Esperado o símbolo igual(=) após identificador.');
        const inicializadores = await this.consumirInicializadores();
        const qtdIdentificadores = identificadores.length;
        const qtdValores = inicializadores.length;
        const ehDesempacotamento = qtdIdentificadores > 1 && qtdValores === 1;
        if (indexResto > -1) {
            if (qtdValores < qtdIdentificadores - 1) {
                if (!ehDesempacotamento ||
                    (ehDesempacotamento && inicializadores[0] instanceof construtos_1.Literal)) {
                    throw this.erro(this.simboloAnterior(), 'Quantidade insuficiente de valores para desempacotamento com operador de resto.');
                }
            }
        }
        else {
            if (!ehDesempacotamento && qtdIdentificadores !== qtdValores) {
                throw this.erro(this.simboloAnterior(), 'Quantidade de inicializadores à esquerda do igual é diferente da quantidade de identificadores à direita.');
            }
            if (ehDesempacotamento && inicializadores[0] instanceof construtos_1.Vetor) {
                const vetor = inicializadores[0];
                if (vetor.tamanho !== qtdIdentificadores) {
                    throw this.erro(this.simboloAnterior(), `O vetor possui ${vetor.tamanho} elementos, mas você tentou desempacotar em ${qtdIdentificadores} variáveis.`);
                }
            }
        }
        const retorno = [];
        let origemParaAtribuicao = inicializadores[0];
        // Injeção de Código (Runtime Check)
        if (ehDesempacotamento && !(inicializadores[0] instanceof construtos_1.Vetor)) {
            const linha = identificadores[0].linha;
            // Cria variável temporária para evitar reavaliar a expressão original múltiplas vezes
            const nomeVarTemp = `__temp_desempacotamento_${new Date().getTime()}_${Math.floor(Math.random() * 1000)}`;
            const simboloVarTemp = new lexador_1.Simbolo(pitugues_2.default.IDENTIFICADOR, nomeVarTemp, null, linha, -1);
            retorno.push(new declaracoes_1.Var(simboloVarTemp, inicializadores[0], 'qualquer[]'));
            origemParaAtribuicao = new construtos_1.Variavel(this.hashArquivo, simboloVarTemp);
            // Injeta validação de tamanho se não houver operador de resto
            if (indexResto === -1) {
                retorno.push(this.construirValidacaoDesempacotamento(identificadores[0], origemParaAtribuicao, qtdIdentificadores));
            }
        }
        let cursorValores = 0;
        const qtdParaResto = qtdValores - (qtdIdentificadores - 1);
        for (let i = 0; i < identificadores.length; i++) {
            const identificador = identificadores[i];
            let inicializador;
            let tipo = 'qualquer';
            if (i === indexResto) {
                const valoresResto = inicializadores.slice(cursorValores, cursorValores + qtdParaResto);
                let tipoInferido = (0, inferenciador_1.inferirTipoVariavel)(valoresResto);
                if (!tipoInferido.endsWith('[]'))
                    tipoInferido = `${tipoInferido}[]`;
                inicializador = new construtos_1.Vetor(identificador.hashArquivo, identificador.linha, valoresResto, tipoInferido);
                tipo = tipoInferido;
                cursorValores += qtdParaResto;
            }
            else if (ehDesempacotamento) {
                if (inicializadores[0] instanceof construtos_1.Vetor) {
                    inicializador = inicializadores[0].valores[i];
                }
                else {
                    inicializador = new construtos_1.AcessoIndiceVariavel(this.hashArquivo, origemParaAtribuicao, new construtos_1.Literal(this.hashArquivo, identificador.linha, i, 'número'), new lexador_1.Simbolo(pitugues_2.default.COLCHETE_DIREITO, ']', null, identificador.linha, -1));
                }
            }
            else {
                inicializador = inicializadores[cursorValores];
                cursorValores++;
                tipo = this.logicaComumInferenciaTiposVariaveisEConstantes(inicializador, tipo);
            }
            this.pilhaEscopos.definirInformacoesVariavel(identificador.lexema, new informacao_elemento_sintatico_1.InformacaoElementoSintatico(identificador.lexema, tipo));
            retorno.push(new declaracoes_1.Var(identificador, inicializador, tipo));
        }
        return retorno;
    }
    sincronizar() {
        this.avancarEDevolverAnterior();
        while (!this.estaNoFinal()) {
            switch (this.simboloAtual().tipo) {
                case pitugues_2.default.CLASSE:
                case pitugues_2.default.FUNCAO:
                case pitugues_2.default.FUNÇÃO:
                case pitugues_2.default.VARIAVEL:
                case pitugues_2.default.PARA:
                case pitugues_2.default.SE:
                case pitugues_2.default.ENQUANTO:
                case pitugues_2.default.ESCREVA:
                case pitugues_2.default.RETORNA:
                    return;
            }
            this.avancarEDevolverAnterior();
        }
    }
    erro(simbolo, mensagemDeErro) {
        const simboloParaErro = simbolo || this.simboloAnterior();
        const excecao = new erro_avaliador_sintatico_1.ErroAvaliadorSintatico(simboloParaErro, mensagemDeErro);
        this.erros.push(excecao);
        return excecao;
    }
    consumir(tipo, mensagemDeErro) {
        if (this.verificarTipoSimboloAtual(tipo))
            return this.avancarEDevolverAnterior();
        throw this.erro(this.simboloAtual(), mensagemDeErro);
    }
    verificarTipoSimboloAtual(tipo) {
        if (this.estaNoFinal())
            return false;
        return this.simboloAtual().tipo === tipo;
    }
    verificarTipoProximoSimbolo(tipo) {
        if (this.atual + 1 >= this.simbolos.length)
            return false;
        return this.simbolos[this.atual + 1].tipo === tipo;
    }
    simboloAtual() {
        return this.simbolos[this.atual];
    }
    simboloAnterior() {
        return this.simbolos[this.atual - 1];
    }
    simboloNaPosicao(posicao) {
        return this.simbolos[this.atual + posicao];
    }
    estaNoFinal() {
        return this.atual >= this.simbolos.length;
    }
    avancarEDevolverAnterior() {
        if (!this.estaNoFinal())
            this.atual += 1;
        return this.simboloAnterior();
    }
    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 primario() {
        const simboloAtual = this.simbolos[this.atual];
        switch (simboloAtual.tipo) {
            case pitugues_2.default.CHAVE_ESQUERDA:
                this.avancarEDevolverAnterior();
                const chaves = [];
                const valoresDicionario = [];
                const esSpread = [];
                if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.CHAVE_DIREITA)) {
                    return new construtos_1.Dicionario(this.hashArquivo, simboloAtual.linha, [], [], []);
                }
                while (!this.verificarSeSimboloAtualEIgualA(pitugues_2.default.CHAVE_DIREITA)) {
                    // Verificar se é spread (**expressao)
                    if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.EXPONENCIACAO)) {
                        const expressaoDicionario = await this.atribuir();
                        chaves.push(null);
                        valoresDicionario.push(expressaoDicionario);
                        esSpread.push(true);
                    }
                    else {
                        const chave = await this.atribuir();
                        this.consumir(pitugues_2.default.DOIS_PONTOS, "Esperado ':' entre chave e valor.");
                        const valor = await this.atribuir();
                        chaves.push(chave);
                        valoresDicionario.push(valor);
                        esSpread.push(false);
                    }
                    if (this.simboloAtual().tipo !== pitugues_2.default.CHAVE_DIREITA) {
                        this.consumir(pitugues_2.default.VIRGULA, 'Esperado vírgula antes da próxima expressão.');
                    }
                }
                return new construtos_1.Dicionario(this.hashArquivo, simboloAtual.linha, chaves, valoresDicionario, esSpread);
            case pitugues_2.default.COLCHETE_ESQUERDO:
                this.avancarEDevolverAnterior();
                if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.COLCHETE_DIREITO)) {
                    return new construtos_1.Vetor(this.hashArquivo, simboloAtual.linha, [], 'qualquer[]');
                }
                // Ao resolver a expressão aqui, identificadores dentro da expressão de compreensão
                // de lista serão tratados como 'qualquer', para evitar erros de tipo.
                this.intuirTipoQualquerParaIdentificadores = true;
                const retornoExpressaoOuPrimeiroValor = await this.seTernario();
                this.intuirTipoQualquerParaIdentificadores = false;
                if (this.simbolos[this.atual].tipo === pitugues_2.default.PARA) {
                    return await this.resolverCompreensaoDeLista(retornoExpressaoOuPrimeiroValor);
                }
                // Aqui já sabemos que não é uma compreensão de lista.
                const valoresVetor = [retornoExpressaoOuPrimeiroValor];
                while (!this.verificarSeSimboloAtualEIgualA(pitugues_2.default.COLCHETE_DIREITO)) {
                    if (this.simbolos[this.atual].tipo !== pitugues_2.default.COLCHETE_DIREITO) {
                        this.consumir(pitugues_2.default.VIRGULA, 'Esperado vírgula antes da próxima expressão.');
                    }
                    const valor = await this.atribuir();
                    valoresVetor.push(valor);
                }
                const tipoVetor = (0, inferenciador_1.inferirTipoVariavel)(valoresVetor);
                return new construtos_1.Vetor(this.hashArquivo, simboloAtual.linha, valoresVetor, tipoVetor);
            case pitugues_2.default.COMENTARIO:
                const simboloComentario = this.avancarEDevolverAnterior();
                return new construtos_1.ComentarioComoConstruto(simboloComentario);
            case pitugues_2.default.FALSO:
            case pitugues_2.default.VERDADEIRO:
                const simboloLogico = this.avancarEDevolverAnterior();
                return new construtos_1.Literal(this.hashArquivo, simboloAtual.linha, simboloLogico.tipo === pitugues_2.default.VERDADEIRO, 'lógico');
            case pitugues_2.default.FUNCAO:
            case pitugues_2.default.FUNÇÃO:
                const simboloFuncao = this.avancarEDevolverAnterior();
                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 pitugues_2.default.IMPORTAR:
                return await this.construtoImportar();
            case pitugues_2.default.NULO:
                this.avancarEDevolverAnterior();
                return new construtos_1.Literal(this.hashArquivo, simboloAtual.linha, null);
            case pitugues_2.default.ISTO:
                const simboloIsto = this.avancarEDevolverAnterior();
                return new construtos_1.Isto(this.hashArquivo, simboloAtual.linha, simboloIsto);
            case pitugues_2.default.LEIA:
                return await this.expressaoLeia();
            case pitugues_2.default.NUMERO:
            case pitugues_2.default.TEXTO:
                const simboloLiteral = this.avancarEDevolverAnterior();
                const tipoInferido = (0, inferenciador_1.inferirTipoVariavel)(simboloLiteral.literal);
                return new construtos_1.Literal(this.hashArquivo, Number(simboloLiteral.linha), simboloLiteral.literal, tipoInferido);
            case pitugues_2.default.TIPO:
                const simboloTipo = this.avancarEDevolverAnterior();
                this.consumir(pitugues_2.default.PARENTESE_ESQUERDO, "Esperado '(' após 'tipo'.");
                const expressaoAvaliar = await this.expressao();
                this.consumir(pitugues_2.default.PARENTESE_DIREITO, "Esperado ')' após expressão em 'tipo'.");
                return new construtos_1.TipoDe(simboloTipo.hashArquivo, simboloTipo, expressaoAvaliar);
            case pitugues_2.default.IDENTIFICADOR:
                const simboloIdentificador = this.avancarEDevolverAnterior();
                let tipoOperando;
                if (this.intuirTipoQualquerParaIdentificadores) {
                    // Esta indicação é utilizada para compreensões de lista, onde o
                    // tipo do identificador de iteração é 'qualquer' por definição.
                    tipoOperando = 'qualquer';
                    this.pilhaEscopos.definirInformacoesVariavel(simboloIdentificador.lexema, new informacao_elemento_sintatico_1.InformacaoElementoSintatico(simboloIdentificador.lexema, 'qualquer'));
                }
                else if (simboloIdentificador.lexema in this.tiposDefinidosEmCodigo) {
                    tipoOperando = simboloIdentificador.lexema;
                }
                else {
                    try {
                        tipoOperando = this.pilhaEscopos.obterTipoVariavelPorNome(simboloIdentificador.lexema);
                    }
                    catch (erro) {
                        throw this.erro(simboloIdentificador, erro.message);
                    }
                }
                return new construtos_1.Variavel(this.hashArquivo, simboloIdentificador, tipoOperando);
            case pitugues_2.default.INTERPOLACAO:
                const simboloInterpolacao = this.avancarEDevolverAnterior();
                const conteudoOriginal = simboloInterpolacao.literal;
                const codigoTransformado = '"' +
                    conteudoOriginal.replace(/\{(.*?)\}/g, (_, miolo) => {
                        // 'miolo' é o texto que estava dentro das chaves. Ex: "valor" ou "valor:.2f"
                        if (miolo.includes(':')) {
                            const [variavel, formato] = miolo.split(':').map((s) => s.trim());
                            if (variavel !== '') {
                                // Transforma {valor:.2f} em "{:.2f}".formatar(valor)
                                return '" + "{:' + formato + '}".formatar(' + variavel + ') + "';
                            }
                        }
                        return '" + (' + miolo.trim() + ') + "';
                    }) +
                    '"';
                const microLexador = new micro_lexador_pitugues_1.MicroLexadorPitugues();
                const retornoMicroLexador = microLexador.mapear(codigoTransformado);
                const microAvaliadorSintatico = new micro_avaliador_sintatico_pitugues_1.MicroAvaliadorSintaticoPitugues();
                let retornoMicroAvaliador;
                try {
                    retornoMicroAvaliador = microAvaliadorSintatico.analisar(retornoMicroLexador, this.hashArquivo);
                    if (retornoMicroAvaliador.erros.length > 0) {
                        this.erros.push(...retornoMicroAvaliador.erros);
                    }
                }
                catch (erro) {
                    this.erros.push(erro);
                    return new construtos_1.Literal(this.hashArquivo, simboloInterpolacao.linha, '');
                }
                const declaracao = retornoMicroAvaliador.declaracoes[0];
                return declaracao.expressao;
            case pitugues_2.default.PARENTESE_ESQUERDO:
                this.avancarEDevolverAnterior();
                // Verifica se é tupla vazia
                if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.PARENTESE_DIREITO)) {
                    return new construtos_1.TuplaN(this.hashArquivo, simboloAtual.linha, []);
                }
                const expressao = await this.tupla();
                // Se a expressão já é uma tupla (com vírgulas), retorna ela
                if (expressao instanceof construtos_1.TuplaN) {
                    this.consumir(pitugues_2.default.PARENTESE_DIREITO, "Esperado ')' após itens da tupla.");
                    return expressao;
                }
                this.consumir(pitugues_2.default.PARENTESE_DIREITO, "Esperado ')' após a expressão.");
                return new construtos_1.Agrupamento(this.hashArquivo, simboloAtual.linha, expressao);
            case pitugues_2.default.SUPER:
                const simboloSuper = this.avancarEDevolverAnterior();
                return new construtos_1.Super(this.hashArquivo, simboloSuper, this.superclasseAtual);
        }
        throw this.erro(this.simboloAtual(), 'Esperado expressão.');
    }
    async finalizarChamada(entidadeChamada) {
        const argumentos = [];
        if (!this.verificarTipoSimboloAtual(pitugues_2.default.PARENTESE_DIREITO)) {
            do {
                if (argumentos.length >= 255) {
                    throw this.erro(this.simboloAtual(), 'Não pode haver mais de 255 argumentos.');
                }
                argumentos.push(await this.expressao());
            } while (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.VIRGULA));
        }
        this.consumir(pitugues_2.default.PARENTESE_DIREITO, "Esperado ')' após os argumentos.");
        const chamada = new construtos_1.Chamada(this.hashArquivo, entidadeChamada, argumentos);
        chamada.tipo = entidadeChamada.tipo;
        return chamada;
    }
    async chamar() {
        let expressao = await this.primario();
        while (true) {
            if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.PARENTESE_ESQUERDO)) {
                expressao = await this.finalizarChamada(expressao);
            }
            else if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.PONTO)) {
                const nome = this.consumir(pitugues_2.default.IDENTIFICADOR, "Esperado nome do método após '.'.");
                expressao = new construtos_1.AcessoMetodoOuPropriedade(this.hashArquivo, expressao, nome);
            }
            else if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.COLCHETE_ESQUERDO)) {
                const inicio = !this.verificarTipoSimboloAtual(pitugues_2.default.DOIS_PONTOS)
                    ? await this.expressao()
                    : null;
                let ehFatiamento = false;
                let fim = null;
                let passo = null;
                if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.DOIS_PONTOS)) {
                    ehFatiamento = true;
                    if (!this.verificarTipoSimboloAtual(pitugues_2.default.DOIS_PONTOS) &&
                        !this.verificarTipoSimboloAtual(pitugues_2.default.COLCHETE_DIREITO))
                        fim = await this.expressao();
                    if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.DOIS_PONTOS) &&
                        !this.verificarTipoSimboloAtual(pitugues_2.default.COLCHETE_DIREITO))
                        passo = await this.expressao();
                }
                const simboloFechamento = this.consumir(pitugues_2.default.COLCHETE_DIREITO, "Esperado ']' após escrita do indice.");
                if (ehFatiamento) {
                    expressao = new construtos_1.AcessoIntervaloVariavel(this.hashArquivo, expressao, inicio, fim, passo, simboloFechamento);
                }
                else {
                    expressao = new construtos_1.AcessoIndiceVariavel(this.hashArquivo, expressao, inicio, simboloFechamento);
                }
            }
            else {
                break;
            }
        }
        return expressao;
    }
    async unario() {
        if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.NEGACAO, pitugues_2.default.SUBTRACAO, pitugues_2.default.BIT_NOT)) {
            const operador = this.simboloAnterior();
            const direito = await this.unario();
            return new construtos_1.Unario(this.hashArquivo, operador, direito);
        }
        return await this.chamar();
    }
    async exponenciacao() {
        let expressao = await this.unario();
        while (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.EXPONENCIACAO)) {
            const operador = this.simboloAnterior();
            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(pitugues_2.default.DIVISAO, pitugues_2.default.DIVISAO_INTEIRA, pitugues_2.default.MULTIPLICACAO, pitugues_2.default.MODULO)) {
            const operador = this.simboloAnterior();
            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(pitugues_2.default.SUBTRACAO, pitugues_2.default.ADICAO)) {
            const operador = this.simboloAnterior();
            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(pitugues_2.default.MENOR_MENOR, pitugues_2.default.MAIOR_MAIOR)) {
            const operador = this.simboloAnterior();
            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(pitugues_2.default.BIT_AND)) {
            const operador = this.simboloAnterior();
            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(pitugues_2.default.BIT_OR, pitugues_2.default.BIT_XOR)) {
            const operador = this.simboloAnterior();
            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(pitugues_2.default.MAIOR, pitugues_2.default.MAIOR_IGUAL, pitugues_2.default.MENOR, pitugues_2.default.MENOR_IGUAL)) {
            const operador = this.simboloAnterior();
            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(pitugues_2.default.DIFERENTE, pitugues_2.default.IGUAL_IGUAL)) {
            const operador = this.simboloAnterior();
            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(pitugues_2.default.EM, pitugues_2.default.CONTEM, pitugues_2.default.NAO)) {
            let operador = this.simboloAnterior();
            let negado = false;
            if (operador.tipo === pitugues_2.default.NAO) {
                operador = this.consumir(pitugues_2.default.CONTEM, `Esperado palavra reservada 'contém' ou 'contem' após palavra reservada ${operador.lexema}.`);
                negado = true;
            }
            const direito = await this.comparacaoIgualdade();
            expressao = new construtos_1.Logico(this.hashArquivo, expressao, operador, direito);
            expressao.negado = negado;
        }
        return expressao;
    }
    async e() {
        let expressao = await this.em();
        while (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.E)) {
            const operador = this.simboloAnterior();
            const direito = await this.em();
            expressao = new construtos_1.Logico(this.hashArquivo, expressao, operador, direito);
        }
        return expressao;
    }
    async ou() {
        let expressao = await this.e();
        while (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.OU)) {
            const operador = this.simboloAnterior();
            const direito = await this.e();
            expressao = new construtos_1.Logico(this.hashArquivo, expressao, operador, direito);
        }
        return expressao;
    }
    async seTernario() {
        let expressaoEntao = await this.ou();
        if (this.simbolos[this.atual] &&
            this.simbolos[this.atual].tipo === pitugues_2.default.SE &&
            expressaoEntao.linha === this.simbolos[this.atual].linha) {
            while (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.SE)) {
                const operador = this.simbolos[this.atual - 1];
                const expressaoOuCondicao = await this.seTernario();
                this.consumir(pitugues_2.default.SENAO, `Esperado 'senão' ou 'senao' após caminho positivo em se ternário. Atual:
                    ${this.simbolos[this.atual].lexema}.`);
                const expressaoSenao = await this.seTernario();
                expressaoEntao = new construtos_1.SeTernario(this.hashArquivo, expressaoOuCondicao, expressaoEntao, operador, expressaoSenao);
            }
        }
        return expressaoEntao;
    }
    async tupla() {
        let expressao = await this.seTernario();
        // Se não há vírgula, retorna a expressão simples
        if (!this.verificarSeSimboloAtualEIgualA(pitugues_2.default.VIRGULA)) {
            return expressao;
        }
        // Se há vírgula, então é uma tupla
        const elementos = [expressao];
        do {
            if (this.verificarTipoSimboloAtual(pitugues_2.default.PARENTESE_DIREITO)) {
                break;
            }
            elementos.push(await this.seTernario());
        } while (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.VIRGULA));
        return new construtos_1.TuplaN(this.hashArquivo, expressao.linha, elementos);
    }
    async atribuir() {
        const expressao = await this.seTernario();
        if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.IGUAL, pitugues_2.default.MAIS_IGUAL, pitugues_2.default.MENOS_IGUAL, pitugues_2.default.MULTIPLICACAO_IGUAL, pitugues_2.default.DIVISAO_IGUAL)) {
            const operadorAtribuicao = this.simboloAnterior();
            const valor = await this.atribuir();
            // Se for apenas '=', é uma atribuição padrão
            if (operadorAtribuicao.tipo === pitugues_2.default.IGUAL) {
                if (expressao instanceof construtos_1.Variavel) {
                    return new construtos_1.Atribuir(this.hashArquivo, expressao, valor);
                }
                if (expressao instanceof construtos_1.AcessoMetodoOuPropriedade) {
                    return new construtos_1.DefinirValor(this.hashArquivo, 0, expressao.objeto, expressao.simbolo, valor);
                }
                if (expressao instanceof construtos_1.AcessoIndiceVariavel) {
                    return new construtos_1.AtribuicaoPorIndice(this.hashArquivo, 0, expressao.entidadeChamada, expressao.indice, valor);
                }
                throw this.erro(operadorAtribuicao, 'Tarefa de atribuição inválida');
            }
            // Se for +=, -=, *=, /=
            // Transforma 'a += 1' em 'a = a + 1'
            let tipoOperadorMatematico;
            switch (operadorAtribuicao.tipo) {
                case pitugues_2.default.MAIS_IGUAL:
                    tipoOperadorMatematico = pitugues_2.default.ADICAO;
                    break;
                case pitugues_2.default.MENOS_IGUAL:
                    tipoOperadorMatematico = pitugues_2.default.SUBTRACAO;
                    break;
                case pitugues_2.default.MULTIPLICACAO_IGUAL:
                    tipoOperadorMatematico = pitugues_2.default.MULTIPLICACAO;
                    break;
                case pitugues_2.default.DIVISAO_IGUAL:
                    tipoOperadorMatematico = pitugues_2.default.DIVISAO;
                    break;
            }
            const simboloOperador = new lexador_1.Simbolo(tipoOperadorMatematico, operadorAtribuicao.lexema.charAt(0), null, operadorAtribuicao.linha, operadorAtribuicao.hashArquivo);
            const operacaoBinaria = new construtos_1.Binario(this.hashArquivo, expressao, simboloOperador, valor);
            if (expressao instanceof construtos_1.Variavel) {
                return new construtos_1.Atribuir(this.hashArquivo, expressao, operacaoBinaria);
            }
            if (expressao instanceof construtos_1.AcessoMetodoOuPropriedade) {
                return new construtos_1.DefinirValor(this.hashArquivo, 0, expressao.objeto, expressao.simbolo, operacaoBinaria);
            }
            if (expressao instanceof construtos_1.AcessoIndiceVariavel) {
                return new construtos_1.AtribuicaoPorIndice(this.hashArquivo, 0, expressao.entidadeChamada, expressao.indice, operacaoBinaria);
            }
            throw this.erro(operadorAtribuicao, 'Tarefa de atribuição inválida');
        }
        return expressao;
    }
    // TODO: Depreciar.
    async expressao() {
        return await this.atribuir();
    }
    async declaracaoEscreva(simboloEscreva) {
        this.consumir(pitugues_2.default.PARENTESE_ESQUERDO, "Esperado '(' antes dos valores em escreva.");
        const argumentos = [];
        do {
            argumentos.push(await this.expressao());
        } while (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.VIRGULA));
        this.consumir(pitugues_2.default.PARENTESE_DIREITO, "Esperado ')' após os valores em escreva.");
        const declaracaoEscreva = new declaracoes_1.Escreva(Number(simboloEscreva.linha), simboloEscreva.hashArquivo, argumentos);
        declaracaoEscreva.simboloEscreva = simboloEscreva;
        return declaracaoEscreva;
    }
    async declaracaoExpressao() {
        const expressao = await this.expressao();
        return new declaracoes_1.Expressao(expressao);
    }
    async blocoEscopo() {
        this.pilhaEscopos.empilhar(new informacao_escopo_1.InformacaoEscopo());
        let declaracoes = [];
        let simboloAtual = this.simboloAtual();
        const simboloAnterior = this.simboloAnterior();
        if (!simboloAtual) {
            throw this.erro(simboloAnterior, 'Esperado corpo do escopo após a declaração.');
        }
        // Declaração na mesma linha (ex: 'se verdadeiro: escreva("Oi")')
        if (simboloAtual.linha === simboloAnterior.linha) {
            const retorno = await this.resolverDeclaracaoForaDeBloco();
            if (retorno !== null)
                declaracoes = declaracoes.concat(retorno);
        }
        else {
            // Bloco de múltiplas linhas baseado em indentação
            let espacosAtual = this.localizacoes[simboloAtual.linha].espacosIndentacao;
            const espacosAnterior = this.localizacoes[simboloAnterior.linha].espacosIndentacao;
            if (espacosAtual <= espacosAnterior) {
                throw this.erro(simboloAtual, `Indentação inconsistente na linha ${simboloAtual.linha}. ` +
                    `Esperado: >= ${espacosAnterior}. ` +
                    `Atual: ${espacosAtual}`);
            }
            const indentacaoBloco = espacosAtual;
            // Consome as declarações enquanto a indentação for estritamente igual à do bloco
            while (simboloAtual && espacosAtual === indentacaoBloco) {
                const retorno = await this.resolverDeclaracaoForaDeBloco();
                if (retorno !== null)
                    declaracoes = declaracoes.concat(retorno);
                simboloAtual = this.simboloAtual();
                if (simboloAtual) {
                    espacosAtual = this.localizacoes[simboloAtual.linha].espacosIndentacao;
                }
            }
        }
        this.pilhaEscopos.removerUltimo();
        return declaracoes;
    }
    async declaracaoEnquanto() {
        try {
            this.blocos += 1;
            const condicao = await this.expressao();
            const bloco = (await this.resolverDeclaracao());
            return new declaracoes_1.Enquanto(condicao, bloco);
        }
        finally {
            this.blocos -= 1;
        }
    }
    async declaracaoEscolha() {
        try {
            this.blocos += 1;
            const condicao = await this.expressao();
            this.consumir(pitugues_2.default.DOIS_PONTOS, "Esperado ':' após 'escolha'.");
            const caminhos = [];
            let caminhoPadrao = null;
            while (!this.estaNoFinal() &&
                [pitugues_2.default.CASO, pitugues_2.default.PADRAO].includes(this.simbolos[this.atual].tipo)) {
                if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.CASO)) {
                    const caminhoCondicoes = [await this.expressao()];
                    this.consumir(pitugues_2.default.DOIS_PONTOS, "Esperado ':' após o 'caso'.");
                    while (this.verificarTipoSimboloAtual(pitugues_2.default.CASO)) {
                        this.consumir(pitugues_2.default.CASO, null);
                        caminhoCondicoes.push(await this.expressao());
                        this.consumir(pitugues_2.default.DOIS_PONTOS, "Esperado ':' após declaração do 'caso'.");
                    }
                    // Como dois-pontos é um símbolo usado para conferir se há um início de bloco,
                    // não podemos simplesmente chamar `this.resolverDeclaracao()` porque o dois-pontos já
                    // foi consumido na verificação.
                    // Outro problema é que, aparentemente, o Interpretador não espera um Bloco, e sim
                    // um vetor de Declaracao, o qual obtemos com `this.blocoEscopo()`.
                    const declaracoes = await this.blocoEscopo();
                    caminhos.push({
                        condicoes: caminhoCondicoes,
                        declaracoes,
                    });
                }
                else if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.PADRAO)) {
                    if (caminhoPadrao !== null) {
                        const excecao = new erro_avaliador_sintatico_1.ErroAvaliadorSintatico(this.simboloAtual(), "Você só pode ter um caminho padrão em cada declaração de 'escolha'.");
                        this.erros.push(excecao);
                        throw excecao;
                    }
                    this.consumir(pitugues_2.default.DOIS_PONTOS, "Esperado ':' após declaração do 'padrao'.");
                    // Como dois-pontos é um símbolo usado para conferir se há um início de bloco,
                    // não podemos simplesmente chamar `this.resolverDeclaracao()` porque o dois-pontos já
                    // foi consumido na verificação.
                    // Outro problema é que, aparentemente, o Interpretador não espera um Bloco, e sim
                    // um vetor de Declaracao, o qual obtemos com `this.blocoEscopo()`.
                    const declaracoes = await this.blocoEscopo();
                    caminhoPadrao = {
                        declaracoes,
                    };
                }
            }
            return new declaracoes_1.Escolha(condicao, caminhos, caminhoPadrao);
        }
        finally {
            this.blocos -= 1;
        }
    }
    async declaracaoParaCada(simboloPara) {
        const nomeVariavelIteracao = this.consumir(pitugues_2.default.IDENTIFICADOR, "Esperado identificador de variável de iteração para instrução 'para cada'.");
        if (!this.verificarSeSimboloAtualEIgualA(pitugues_2.default.DE, pitugues_2.default.EM)) {
            throw this.erro(this.simbolos[this.atual], "Esperado palavras reservadas 'em' ou 'de' após variável de iteração em instrução 'para cada'.");
        }
        const vetor = await this.expressao();
        if (!vetor.hasOwnProperty('tipo')) {
            throw this.erro(simboloPara, `Variável ou constante em 'para cada' não parece possuir um tipo iterável.`);
        }
        const tipoVetor = vetor.tipo;
        if (!tipoVetor.endsWith('[]') && !['qualquer', 'vetor'].includes(tipoVetor)) {
            throw this.erro(simboloPara, `Variável ou constante em 'para cada' não é iterável. Tipo resolvido: ${tipoVetor}.`);
        }
        this.pilhaEscopos.definirInformacoesVariavel(nomeVariavelIteracao.lexema, new informacao_elemento_sintatico_1.InformacaoElementoSintatico(nomeVariavelIteracao.lexema, tipoVetor.slice(0, -2)));
        // TODO: Talvez não seja uma ideia melhor chamar o método de `Bloco` aqui?
        const corpo = (await this.resolverDeclaracao());
        return new declaracoes_1.ParaCada(this.hashArquivo, Number(simboloPara.linha), new construtos_1.Variavel(this.hashArquivo, nomeVariavelIteracao), vetor, corpo);
    }
    async declaracaoPara() {
        const simboloPara = this.simboloAnterior();
        this.blocos += 1;
        try {
            this.consumir(pitugues_2.default.CADA, `Esperado palavra reservada 'cada' após 'para'. Atual: ${this.simbolos[this.atual].lexema}.`);
            const primeiraVariavel = this.consumir(pitugues_2.default.IDENTIFICADOR, "Esperado identificador de variável de iteração para instrução 'para cada'.");
            let simboloSegundaVariavel = null;
            if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.VIRGULA)) {
                simboloSegundaVariavel = this.consumir(pitugues_2.default.IDENTIFICADOR, 'Esperado identificador após a vírgula.');
            }
            let variavelIteracao;
            const v1 = new construtos_1.Variavel(this.hashArquivo, primeiraVariavel, 'qualquer');
            if (simboloSegundaVariavel) {
                const v2 = new construtos_1.Variavel(this.hashArquivo, simboloSegundaVariavel, 'qualquer');
                variavelIteracao = new construtos_1.Dupla(v1, v2);
            }
            else {
                variavelIteracao = v1;
            }
            if (!this.verificarSeSimboloAtualEIgualA(pitugues_2.default.DE, pitugues_2.default.EM)) {
                throw this.erro(this.simbolos[this.atual], "Esperado palavras reservadas 'em' ou 'de' após variável de iteração em instrução 'para cada'.");
            }
            const alvoIteracao = await this.expressao();
            this.validarSeAlvoEIteravel(simboloPara, alvoIteracao, !!simboloSegundaVariavel);
            this.pilhaEscopos.definirInformacoesVariavel(primeiraVariavel.lexema, new informacao_elemento_sintatico_1.InformacaoElementoSintatico(primeiraVariavel.lexema, 'qualquer'));
            if (simboloSegundaVariavel) {
                this.pilhaEscopos.definirInformacoesVariavel(simboloSegundaVariavel.lexema, new informacao_elemento_sintatico_1.InformacaoElementoSintatico(simboloSegundaVariavel.lexema, 'qualquer'));
            }
            // TODO: Talvez não seja uma ideia melhor chamar o método de `Bloco` aqui?
            const corpo = (await this.resolverDeclaracao());
            return new declaracoes_1.ParaCada(this.hashArquivo, Number(simboloPara.linha), variavelIteracao, alvoIteracao, corpo);
        }
        finally {
            this.blocos -= 1;
        }
    }
    /**
     * Função auxiliar para validar se o alvo pode ser iterado.
     */
    validarSeAlvoEIteravel(simboloPara, alvo, temDuasVariaveis) {
        if (!alvo || !alvo.tipo)
            return;
        const tipo = alvo.tipo;
        const tiposValidos = ['qualquer', 'vetor', 'dicionário', 'texto'];
        const eVetor = tipo.endsWith('[]') || tipo === 'vetor';
        if (temDuasVariaveis) {
            if (!eVetor && tipo !== 'dicionário' && tipo !== 'qualquer') {
                throw this.erro(simboloPara, `Para iterar com duas variáveis, o objeto deve ser um dicionário ou lista. Tipo atual: ${tipo}.`);
            }
        }
        else {
            if (!eVetor && !tiposValidos.includes(tipo)) {
                throw this.erro(simboloPara, `O objeto do tipo '${tipo}' não é iterável.`);
            }
        }
    }
    async declaracaoSe() {
        const condicao = await this.expressao();
        this.consumir(pitugues_2.default.DOIS_PONTOS, "Esperado ':' após condição do 'se'.");
        const simboloColonEntao = this.simboloAnterior();
        const caminhoEntao = new declaracoes_1.Bloco(simboloColonEntao.hashArquivo, Number(simboloColonEntao.linha), await this.blocoEscopo());
        let caminhoSenao = null;
        if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.SENAO, pitugues_2.default.SENÃO)) {
            if (this.verificarTipoSimboloAtual(pitugues_2.default.SE)) {
                // 'senao se' - encadeamento de condicionais, sem dois-pontos
                caminhoSenao = await this.resolverDeclaracao();
            }
            else {
                this.consumir(pitugues_2.default.DOIS_PONTOS, "Esperado ':' após 'senao'.");
                const simboloColonSenao = this.simboloAnterior();
                caminhoSenao = new declaracoes_1.Bloco(simboloColonSenao.hashArquivo, Number(simboloColonSenao.linha), await this.blocoEscopo());
            }
        }
        return new declaracoes_1.Se(condicao, caminhoEntao, [], caminhoSenao);
    }
    declaracaoSustar() {
        if (this.blocos < 1) {
            throw this.erro(this.simboloAnterior(), "'sustar' deve estar dentro de um laço de repetição.");
        }
        return new declaracoes_1.Sustar(this.simboloAtual());
    }
    async resolverDecoradores() {
        while (this.verificarTipoSimboloAtual(pitugues_2.default.ARROBA)) {
            this.avancarEDevolverAnterior();
            let nomeDecorador = '@';
            let linha;
            let parametros = [];
            const atributos = {};
            const primeiraParteNomeDecorador = this.consumir(pitugues_2.default.IDENTIFICADOR, 'Esperado nome de decorador após "@".');
            linha = Number(primeiraParteNomeDecorador.linha);
            nomeDecorador += primeiraParteNomeDecorador.lexema;
            while (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.PONTO)) {
                const parteNomeDecorador = this.consumir(pitugues_2.default.IDENTIFICADOR, 'Esperado nome de decorador após "."');
                nomeDecorador += '.' + parteNomeDecorador.lexema;
            }
            if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.PARENTESE_ESQUERDO)) {
                if (!this.verificarTipoSimboloAtual(pitugues_2.default.PARENTESE_DIREITO)) {
                    parametros = await this.logicaComumParametros();
                }
                for (const parametro of parametros) {
                    if (parametro.nome.lexema in atributos) {
                        throw this.erro(parametro.nome, `Atributo de decorador declarado duas ou mais vezes: ${parametro.nome.lexema}`);
                    }
                    atributos[parametro.nome.lexema] = parametro.valorPadrao;
                }
                this.consumir(pitugues_2.default.PARENTESE_DIREITO, 'Esperado ")" após argumentos do decorador.');
            }
            this.pilhaDecoradores.push(new construtos_1.Decorador(this.hashArquivo, linha, nomeDecorador, atributos));
        }
    }
    declaracaoComentario() {
        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.simboloAnterior(), "'continua' precisa estar em um laço de repetição.");
        }
        return new declaracoes_1.Continua(this.simboloAtual());
    }
    async declaracaoRetorna() {
        const palavraChave = this.simboloAnterior();
        let valor = null;
        if (!this.verificarTipoSimboloAtual(pitugues_2.default.PONTO_E_VIRGULA)) {
            valor = await this.expressao();
        }
        return new declaracoes_1.Retorna(palavraChave, valor);
    }
    async construtoImportar() {
        this.avancarEDevolverAnterior();
        this.consumir(pitugues_2.default.PARENTESE_ESQUERDO, "Esperado '(' após declaração.");
        const caminho = await this.expressao();
        this.consumir(pitugues_2.default.PARENTESE_DIREITO, "Esperado ')' após declaração.");
        return new construtos_1.ImportarComoConstruto(caminho);
    }
    async declaracaoImportar() {
        const nomeModulo = this.consumir(pitugues_2.default.IDENTIFICADOR, "Esperado nome do módulo após 'importar'.");
        const caminho = new construtos_1.Literal(nomeModulo.hashArquivo, Number(nomeModulo.linha), nomeModulo.lexema);
        const declaracao = new declaracoes_1.Importar(caminho);
        if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.COMO)) {
            const alias = this.consumir(pitugues_2.default.IDENTIFICADOR, "Esperado identificador após 'como' em declaração de importação.");
            declaracao.simboloTudo = alias;
        }
        else {
            declaracao.simboloTudo = nomeModulo;
        }
        return Promise.resolve(declaracao);
    }
    async declaracaoImportarDe() {
        const nomeModulo = this.consumir(pitugues_2.default.IDENTIFICADOR, "Esperado nome do módulo após 'de'.");
        const caminho = new construtos_1.Literal(nomeModulo.hashArquivo, Number(nomeModulo.linha), nomeModulo.lexema);
        this.consumir(pitugues_2.default.IMPORTAR, "Esperado 'importar' após nome do módulo em declaração 'de ... importar'.");
        const elementosImportacao = [];
        do {
            const elemento = this.consumir(pitugues_2.default.IDENTIFICADOR, 'Esperado identificador de elemento a ser importado.');
            elementosImportacao.push(elemento);
        } while (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.VIRGULA));
        const declaracao = new declaracoes_1.Importar(caminho);
        declaracao.elementosImportacao = elementosImportacao;
        return Promise.resolve(declaracao);
    }
    async declaracaoTente() {
        const simboloTente = this.simboloAnterior();
        this.consumir(pitugues_2.default.DOIS_PONTOS, "Esperado ':' após a declaração 'tente'.");
        const blocoTente = await this.blocoEscopo();
        let blocoPegue = null;
        if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.PEGUE)) {
            if (this.verificarTipoSimboloAtual(pitugues_2.default.COMO)) {
                this.avancarEDevolverAnterior();
                const variavelExcecao = this.consumir(pitugues_2.default.IDENTIFICADOR, `Esperado identificador após palavra reservada 'como' em bloco tente. Atual: ${this.simbolos[this.atual].lexema}.`);
                // Caso 1: com parâmetro de erro.
                // `pegue` recebe um `FuncaoConstruto`.
                this.consumir(pitugues_2.default.DOIS_PONTOS, `Esperado ':' antes do escopo do bloco 'pegue'.`);
                this.pilhaEscopos.definirInformacoesVariavel(variavelExcecao.lexema, new informacao_elemento_sintatico_1.InformacaoElementoSintatico(variavelExcecao.lexema, 'qualquer'));
                const corpo = await this.blocoEscopo();
                blocoPegue = new construtos_1.FuncaoConstruto(this.hashArquivo, simboloTente.linha, [
                    {
                        abrangencia: 'padrao',
                        nome: variavelExcecao,
                        tipoDado: 'qualquer',
                    },
                ], corpo, 'vazio', false);
            }
            else {
                // Caso 2: sem parâmetro de erro.
                // `pegue` recebe um bloco.
                this.consumir(pitugues_2.default.DOIS_PONTOS, "Esperado ':' após a declaração 'pegue'.");
                blocoPegue = await this.blocoEscopo();
            }
        }
        let blocoSenao = null;
        if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.SENAO, pitugues_2.default.SENÃO)) {
            this.consumir(pitugues_2.default.DOIS_PONTOS, "Esperado ':' após a declaração 'senão'.");
            blocoSenao = await this.blocoEscopo();
        }
        let blocoFinalmente = null;
        if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.FINALMENTE)) {
            this.consumir(pitugues_2.default.DOIS_PONTOS, "Esperado ':' após a declaração 'pegue'.");
            blocoFinalmente = await this.blocoEscopo();
        }
        return new declaracoes_1.Tente(simboloTente.hashArquivo, Number(simboloTente.linha), blocoTente, blocoPegue, blocoSenao, blocoFinalmente);
    }
    async declaracaoFazer() {
        const simboloFazer = this.simboloAnterior();
        try {
            this.blocos += 1;
            const declaracaoOuBlocoFazer = (await this.resolverDeclaracao());
            this.consumir(pitugues_2.default.ENQUANTO, "Esperado declaração do 'enquanto' após o escopo da declaração 'fazer'.");
            const condicaoEnquanto = await this.expressao();
            return new declaracoes_1.Fazer(simboloFazer.hashArquivo, Number(simboloFazer.linha), declaracaoOuBlocoFazer, condicaoEnquanto);
        }
        finally {
            this.blocos -= 1;
        }
    }
    async funcao(tipo, construtor) {
        const simbolo = !construtor
            ? this.consumir(pitugues_2.default.IDENTIFICADOR, `Esperado nome ${tipo}.`)
            : new lexador_1.Simbolo(pitugues_2.default.CONSTRUTOR, 'construtor', null, -1, -1);
        const decoradores = Array.from(this.pilhaDecoradores);
        this.pilhaDecoradores = [];
        // 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(simbolo.lexema, new informacao_elemento_sintatico_1.InformacaoElementoSintatico(simbolo.lexema, 'qualquer'));
        const corpoDaFuncao = await this.corpoDaFuncao(tipo);
        const tipoDaFuncao = `função<${corpoDaFuncao.tipo}>`;
        this.pilhaEscopos.definirInformacoesVariavel(simbolo.lexema, new informacao_elemento_sintatico_1.InformacaoElementoSintatico(simbolo.lexema, tipoDaFuncao));
        const funcaoDeclaracao = new declaracoes_1.FuncaoDeclaracao(simbolo, corpoDaFuncao, tipoDaFuncao, decoradores);
        this.pilhaEscopos.registrarReferenciaFuncao(simbolo.lexema, funcaoDeclaracao);
        return funcaoDeclaracao;
    }
    async logicaComumParametros() {
        const parametros = [];
        do {
            if (parametros.length >= 255) {
                throw this.erro(this.simboloAtual(), 'Função não pode ter mais de 255 parâmetros.');
            }
            const parametro = {};
            if (this.simboloAtual().tipo === pitugues_2.default.MULTIPLICACAO) {
                this.consumir(pitugues_2.default.MULTIPLICACAO, null);
                parametro.abrangencia = 'multiplo';
            }
            else {
                parametro.abrangencia = 'padrao';
            }
            parametro.nome = this.consumir(pitugues_2.default.IDENTIFICADOR, 'Esperado nome do parâmetro.');
            if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.DOIS_PONTOS)) {
                let tipoDadoParametro = this.verificarDefinicaoTipoAtual();
                parametro.tipoDado = tipoDadoParametro;
                this.avancarEDevolverAnterior();
            }
            if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.IGUAL)) {
                parametro.valorPadrao = await this.primario();
            }
            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(pitugues_2.default.VIRGULA));
        return parametros;
    }
    /**
     * Resolve uma lista de compreensão.
     * @param retornoExpressao A expressão já resolvida que representa o valor a ser retornado.
     * @returns {ListaCompreensao} A lista de compreensão resolvida.
     */
    async resolverCompreensaoDeLista(retornoExpressao) {
        this.consumir(pitugues_2.default.PARA, "Esperado instrução 'para' após identificador.");
        this.consumir(pitugues_2.default.CADA, "Esperado instrução 'cada' após 'para'.");
        const simboloVariavelIteracao = this.consumir(pitugues_2.default.IDENTIFICADOR, "Esperado identificador de variável após 'para cada'.");
        // TODO: Manter essa validação aqui? Se sim, como resolver a expressão?
        /* if (identificador.lexema != simboloVariavelIteracao.lexema) {
            throw this.erro(
                this.simbolos[this.atual],
                "Identificadores de variáveis não correspondentes."
            )
        } */
        if (!this.verificarSeSimboloAtualEIgualA(pitugues_2.default.DE, pitugues_2.default.EM)) {
            throw this.erro(this.simbolos[this.atual], "Esperado palavras reservadas 'em' ou 'de' após variável de iteração em instrução em lista de compreensão.");
        }
        const localizacaoVetor = this.simboloAnterior();
        // TODO: Reavaliar a precedência do se ternário.
        const vetor = await this.ou();
        this.consumir(pitugues_2.default.SE, "Esperado condição 'se' após vetor.");
        const condicao = await this.expressao();
        this.consumir(pitugues_2.default.COLCHETE_DIREITO, 'Espero fechamento de colchetes após condição.');
        const tipoVetor = vetor.tipo;
        if (!tipoVetor.endsWith('[]') && !['qualquer', 'vetor'].includes(tipoVetor)) {
            throw this.erro(localizacaoVetor, `Variável ou constante em 'para cada' não é iterável. Tipo resolvido: ${tipoVetor}.`);
        }
        const variavelIteracao = new construtos_1.Variavel(this.hashArquivo, simboloVariavelIteracao);
        return new construtos_1.ListaCompreensao(Number(this.simbolos[this.atual]), this.hashArquivo, retornoExpressao, vetor, new construtos_1.ParaCadaComoConstruto(retornoExpressao.hashArquivo, retornoExpressao.linha, variavelIteracao, vetor, new declaracoes_1.Bloco(retornoExpressao.hashArquivo, retornoExpressao.linha, [
            new declaracoes_1.Se(condicao, new declaracoes_1.Bloco(retornoExpressao.hashArquivo, retornoExpressao.linha, [
                new declaracoes_1.Retorna(simboloVariavelIteracao, retornoExpressao),
            ]), [], null),
        ])), retornoExpressao.tipo ? `${retornoExpressao.tipo}[]` : 'qualquer[]');
    }
    verificarDefinicaoTipoAtual() {
        if (this.verificarTipoSimboloAtual(pitugues_2.default.FUNCAO) ||
            this.verificarTipoSimboloAtual(pitugues_2.default.FUNÇÃO)) {
            if (this.verificarTipoProximoSimbolo(pitugues_2.default.MENOR)) {
                this.avancarEDevolverAnterior();
                this.avancarEDevolverAnterior();
                const tipoRetorno = this.simboloAtual().lexema;
                this.avancarEDevolverAnterior();
                return `função<${tipoRetorno}>`;
            }
            return 'função<qualquer>';
        }
        const tipos = [...Object.values(pitugues_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(pitugues_2.default.COLCHETE_ESQUERDO)) {
            const tiposVetores = [
                'inteiro[]',
                'numero[]',
                'número[]',
                'qualquer[]',
                'real[]',
                'texto[]',
            ];
            this.avancarEDevolverAnterior();
            if (!this.verificarTipoProximoSimbolo(pitugues_2.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;
    }
    declaracaoTextoDeDocumentacao() {
        if (this.simbolos[this.atual].tipo === pitugues_2.default.TEXTO_MULTILINHAS) {
            const simboloTexto = this.avancarEDevolverAnterior();
            return new declaracoes_1.TextoDocumentacao(this.hashArquivo, Number(simboloTexto.linha), simboloTexto.lexema);
        }
    }
    async corpoDaFuncao(tipo) {
        // O parêntese esquerdo é considerado o símbolo inicial para
        // fins de localização.
        const parenteseEsquerdo = this.consumir(pitugues_2.default.PARENTESE_ESQUERDO, `Esperado '(' após o nome ${tipo}.`);
        let parametros = [];
        if (!this.verificarTipoSimboloAtual(pitugues_2.default.PARENTESE_DIREITO)) {
            parametros = await this.logicaComumParametros();
        }
        this.consumir(pitugues_2.default.PARENTESE_DIREITO, "Esperado ')' após parâmetros.");
        let tipoRetorno = 'qualquer';
        let definicaoExplicitaDeTipo = false;
        if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.SETA)) {
            tipoRetorno = this.verificarDefinicaoTipoAtual();
            this.avancarEDevolverAnterior();
            definicaoExplicitaDeTipo = true;
        }
        this.consumir(pitugues_2.default.DOIS_PONTOS, `Esperado ':' antes do escopo do ${tipo}.`);
        const documentacao = this.declaracaoTextoDeDocumentacao();
        const corpo = await this.blocoEscopo();
        tipoRetorno = (0, comum_1.logicaDescobertaRetornoFuncao)(this, corpo, tipoRetorno, definicaoExplicitaDeTipo, parenteseEsquerdo);
        return new construtos_1.FuncaoConstruto(this.hashArquivo, 0, parametros, corpo, tipoRetorno, definicaoExplicitaDeTipo, documentacao);
    }
    async declaracaoDeClasse() {
        const simbolo = this.consumir(pitugues_2.default.IDENTIFICADOR, 'Esperado nome da classe.');
        let superClasse = null;
        if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.PARENTESE_ESQUERDO)) {
            const simboloSuperclasse = this.consumir(pitugues_2.default.IDENTIFICADOR, 'Esperado nome da Superclasse.');
            this.superclasseAtual = simboloSuperclasse.lexema;
            superClasse = new construtos_1.Variavel(this.hashArquivo, this.simboloAnterior());
            this.consumir(pitugues_2.default.PARENTESE_DIREITO, "Esperado ')' após declaração.");
        }
        this.consumir(pitugues_2.default.DOIS_PONTOS, "Esperado ':' antes do escopo da classe.");
        const possivelDocumentacao = this.declaracaoTextoDeDocumentacao();
        const metodos = [];
        const propriedades = [];
        const indentacaoLinha = this.localizacoes[this.simboloAtual().linha].espacosIndentacao;
        while (!this.estaNoFinal() &&
            this.localizacoes[this.simboloAtual().linha].espacosIndentacao === indentacaoLinha &&
            this.verificarSeSimboloAtualEIgualA(pitugues_2.default.CONSTRUTOR, pitugues_2.default.FUNCAO, pitugues_2.default.FUNÇÃO, pitugues_2.default.IDENTIFICADOR)) {
            const simboloAnterior = this.simbolos[this.atual - 1];
            if (simboloAnterior.tipo === pitugues_2.default.IDENTIFICADOR) {
                this.consumir(pitugues_2.default.DOIS_PONTOS, "Esperado ':' antes do escopo da classe.");
                const tipoPropriedade = this.consumir(pitugues_2.default.IDENTIFICADOR, 'Esperado tipo de propriedade após dois-pontos, em declaração de classe.');
                const propriedade = new declaracoes_1.PropriedadeClasse(simboloAnterior, tipoPropriedade.lexema, []);
                propriedades.push(propriedade);
            }
            else {
                metodos.push(await this.funcao('método', this.simbolos[this.atual - 1].tipo === pitugues_2.default.CONSTRUTOR));
            }
        }
        this.superclasseAtual = undefined;
        const definicaoClasse = new declaracoes_1.Classe(simbolo, superClasse ? [superClasse] : [], metodos, propriedades);
        if (possivelDocumentacao) {
            definicaoClasse.documentacao = possivelDocumentacao;
        }
        this.tiposDefinidosEmCodigo[definicaoClasse.simbolo.lexema] = definicaoClasse;
        return definicaoClasse;
    }
    declaracaoFalhar() {
        const simboloFalha = this.simbolos[this.atual - 1];
        const textoFalha = this.consumir(pitugues_2.default.TEXTO, 'Esperado texto para explicar falha.');
        return new declaracoes_1.Falhar(simboloFalha, textoFalha.literal);
    }
    /**
     * Verifica se há pontos e vírgula no final de sentenças.
     * Em Pituguês, ; só é permitido para separar múltiplos comandos na mesma linha,
     * mas não no final de uma sentença/linha.
     */
    verificarPontosEVirgulasInvalidos() {
        for (let i = 0; i < this.simbolos.length; i++) {
            const simboloAtual = this.simbolos[i];
            if (simboloAtual.tipo === pitugues_2.default.PONTO_E_VIRGULA) {
                const proximoSimbolo = this.encontrarProximoSimboloNaoComentario(i + 1);
                if (!proximoSimbolo || proximoSimbolo.linha > simboloAtual.linha) {
                    this.erros.push(new erro_avaliador_sintatico_1.ErroAvaliadorSintatico(simboloAtual, 'Ponto e vírgula (;) não é permitido no final da sentença de código.'));
                }
            }
        }
    }
    /**
     * Encontra o próximo símbolo que não seja comentário.
     * Isso é importante porque comentários não afetam a validade do ponto e vírgula.
     */
    encontrarProximoSimboloNaoComentario(inicio) {
        for (let i = inicio; i < this.simbolos.length; i++) {
            if (this.simbolos[i].tipo !== pitugues_2.default.COMENTARIO) {
                return this.simbolos[i];
            }
        }
        return null;
    }
    /**
     * Consome o símbolo atual, verificando se é uma declaração de função, variável, classe
     * ou uma expressão.
     * @returns Objeto do tipo `Declaracao`.
     */
    async resolverDeclaracaoForaDeBloco() {
        var _a;
        try {
            if (this.verificarTipoSimboloAtual(pitugues_2.default.ARROBA)) {
                await this.resolverDecoradores();
            }
            if (this.verificarTipoSimboloAtual(pitugues_2.default.FUNCAO) ||
                this.verificarTipoSimboloAtual(pitugues_2.default.FUNÇÃO)) {
                this.avancarEDevolverAnterior();
                if (this.verificarTipoSimboloAtual(pitugues_2.default.DE) &&
                    ((_a = this.simboloNaPosicao(1)) === null || _a === void 0 ? void 0 : _a.lexema) === 'decorador') {
                    this.avancarEDevolverAnterior();
                    this.avancarEDevolverAnterior();
                }
                return await this.funcao('da função');
            }
            if (this.verificarSeSimboloAtualEIgualA(pitugues_2.default.CLASSE))
                return await this.declaracaoDeClasse();
            return await this.resolverDeclaracao();
        }
        catch (erro) {
            this.sincronizar();
            return null;
        }
    }
    async resolverDeclaracao() {
        // Detecção de declaração implícita ou múltipla atribuição (pode começar com * ou identificador)
        const simboloAtual = this.simbolos[this.atual];
        // Bloqueio explícito do uso de "var"
        if (this.temPadraoVarComoPalavraChave()) {
            throw this.erro(simboloAtual, 'Palavra "var" não pode ser usada como palavra-chave para declaração. Use declarações implícitas: "x = 10" em vez de "var x = 10".');
        }
        // Se caso começar com o operador resto (*), ex: *a, b = ...
        if (simboloAtual.tipo === pitugues_2.default.MULTIPLICACAO) {
            return this.declaracaoDeVariaveis();
        }
        // Se caso começar com um identificador
        if (simboloAtual.tipo === pitugues_2.default.IDENTIFICADOR) {
            if (simboloAtual.lexema.startsWith('*'))
                return this.declaracaoDeVariaveis();
            if (this.temPadraoMultiplaAtribuicao())
                return this.declaracaoDeVariaveis();
            const proximoSimbolo = this.simbolos[this.atual + 1];
            if (proximoSimbolo && proximoSimbolo.tipo === pitugues_2.default.IGUAL) {
                if (!this.variavelJaDeclarada(simboloAtual.lexema))
                    return this.declaracaoImplicitaVariaveis();
            }
            if (proximoSimbolo && proximoSimbolo.tipo === pitugues_2.default.DOIS_PONTOS) {
                return this.declaracaoImplicitaVariaveis();
            }
        }
        switch (simboloAtual.tipo) {
            case pitugues_2.default.COMENTARIO:
                return this.declaracaoComentario();
            case pitugues_2.default.CONTINUA:
                this.avancarEDevolverAnterior();
                return this.declaracaoContinua();
            case pitugues_2.default.NADA:
                this.avancarEDevolverAnterior();
                return null;
            case pitugues_2.default.DOIS_PONTOS:
                this.avancarEDevolverAnterior();
                const simboloInicioBloco = this.simboloAnterior();
                return new declaracoes_1.Bloco(simboloInicioBloco.hashArquivo, Number(simboloInicioBloco.linha), await this.blocoEscopo());
            case pitugues_2.default.ENQUANTO:
                this.avancarEDevolverAnterior();
                return this.declaracaoEnquanto();
            case pitugues_2.default.ESCOLHA:
                this.avancarEDevolverAnterior();
                return this.declaracaoEscolha();
            case pitugues_2.default.IMPRIMA:
            case pitugues_2.default.ESCREVA:
                const simboloEscrevaOuImprima = this.avancarEDevolverAnterior();
                return this.declaracaoEscreva(simboloEscrevaOuImprima);
            case pitugues_2.default.FALHAR:
                this.avancarEDevolverAnterior();
                return this.declaracaoFalhar();
            case pitugues_2.default.FAZER:
                this.avancarEDevolverAnterior();
                return this.declaracaoFazer();
            case pitugues_2.default.PARA:
                this.avancarEDevolverAnterior();
                return this.declaracaoPara();
            case pitugues_2.default.QUEBRAR:
            case pitugues_2.default.SUSTAR:
                this.avancarEDevolverAnterior();
                return this.declaracaoSustar();
            case pitugues_2.default.SE:
                this.avancarEDevolverAnterior();
                return this.declaracaoSe();
            case pitugues_2.default.RETORNA:
                this.avancarEDevolverAnterior();
                return await this.declaracaoRetorna();
            case pitugues_2.default.TENTE:
                this.avancarEDevolverAnterior();
                return this.declaracaoTente();
            case pitugues_2.default.TEXTO_MULTILINHAS:
                return this.declaracaoTextoDeDocumentacao();
            case pitugues_2.default.IMPORTAR: {
                const proximo = this.simbolos[this.atual + 1];
                if (proximo && proximo.tipo === pitugues_2.default.PARENTESE_ESQUERDO) {
                    break;
                }
                this.avancarEDevolverAnterior();
                return this.declaracaoImportar();
            }
            case pitugues_2.default.DE: {
                const proximo = this.simbolos[this.atual + 1];
                const aposProximo = this.simbolos[this.atual + 2];
                if (proximo &&
                    proximo.tipo === pitugues_2.default.IDENTIFICADOR &&
                    aposProximo &&
                    aposProximo.tipo === pitugues_2.default.IMPORTAR) {
                    this.avancarEDevolverAnterior();
                    return this.declaracaoImportarDe();
                }
                break;
            }
        }
        return this.declaracaoExpressao();
    }
    /**
     * Inicializa o primeiro nível da pilha de escopos, normalmente com ítens da biblioteca global.
     * É separada da inicialização do avaliador sintático, pois é necessário manipular essa
     * inicialização de outra forma em `delegua-node`.
     */
    inicializarPilhaEscopos() {
        this.pilhaEscopos = new pilha_escopos_1.PilhaEscopos();
        this.pilhaEscopos.empilhar(new informacao_escopo_1.InformacaoEscopo());
        // Funções nativas de Delégua (e de Pituguês também, por enquanto)
        this.pilhaEscopos.definirInformacoesVariavel('aleatorio', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('aleatorio', 'número'));
        this.pilhaEscopos.definirInformacoesVariavel('aleatorioEntre', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('aleatorioEntre', 'número', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('minimo', 'número'),
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('maximo', 'número'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('algum', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('algum', 'lógico', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('vetor', 'qualquer[]'),
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('funcaoPesquisa', 'função'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('arredondar', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('arredondar', 'numero', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('numero', 'numero'),
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('casasDecimais', 'numero'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('encontrar', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('encontrar', 'qualquer', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('vetor', 'qualquer[]'),
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('funcaoPesquisa', 'função'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('encontrarIndice', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('encontrarIndice', 'inteiro', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('vetor', 'qualquer[]'),
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('funcaoPesquisa', 'função'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('encontrarUltimo', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('encontrarUltimo', 'inteiro', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('vetor', 'qualquer[]'),
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('funcaoPesquisa', 'função'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('encontrarUltimoIndice', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('encontrarUltimoIndice', 'inteiro', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('vetor', 'qualquer[]'),
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('funcaoPesquisa', 'função'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('filtrarPor', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('filtrarPor', 'qualquer[]', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('vetor', 'qualquer[]'),
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('funcaoFiltragem', 'função'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('incluido', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('incluido', 'lógico', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('vetor', 'qualquer[]'),
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('valor', 'qualquer'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('inteiro', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('inteiro', 'inteiro', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('valor', 'qualquer'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('longo', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('longo', 'longo', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('valor', 'qualquer'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('intervalo', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('intervalo', 'inteiro[]', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('valorInicial', 'qualquer'),
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('valorFinal', 'qualquer', false),
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('valorPasso', 'qualquer', false),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('mapear', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('mapear', 'qualquer[]', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('vetor', 'qualquer[]'),
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('funcaoMapeamento', 'função'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('maximo', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('maximo', 'número', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('vetor', 'qualquer[]'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('minimo', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('minimo', 'número', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('vetor', 'qualquer[]'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('numero', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('número', 'número', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('valorParaConverter', 'qualquer'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('número', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('número', 'número', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('valorParaConverter', 'qualquer'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('ordenar', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('ordenar', 'qualquer[]', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('vetor', 'qualquer[]'),
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('funcaoOrdenacao', 'função'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('paraCada', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('paraCada', 'qualquer[]', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('vetor', 'qualquer[]'),
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('funcaoFiltragem', 'função'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('primeiroEmCondicao', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('primeiroEmCondicao', 'qualquer', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('vetor', 'qualquer[]'),
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('funcaoFiltragem', 'função'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('real', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('real', 'número', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('valorParaConverter', 'qualquer'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('reduzir', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('reduzir', 'qualquer', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('vetor', 'qualquer[]'),
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('funcaoReducao', 'função'),
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('valorInicial', 'qualquer'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('somar', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('somar', 'número', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('vetor', 'qualquer[]'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('tamanho', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('tamanho', 'inteiro', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('objeto', 'qualquer'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('texto', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('texto', 'texto', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('valorParaConverter', 'qualquer'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('tipo', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('tipo', 'qualquer', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('elemento', 'qualquer'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('todos', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('todos', 'lógico', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('iteravel', 'qualquer'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('todosEmCondicao', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('todosEmCondicao', 'lógico', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('iteravel', 'qualquer'),
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('funcaoCondicional', 'função'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('tupla', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('tupla', 'tupla', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('vetor', 'qualquer[]'),
        ]));
        this.pilhaEscopos.definirInformacoesVariavel('vetor', new informacao_elemento_sintatico_1.InformacaoElementoSintatico('vetor', 'vetor', true, [
            new informacao_elemento_sintatico_1.InformacaoElementoSintatico('tupla', 'qualquer'),
        ]));
    }
    async analisar(retornoLexador, hashArquivo) {
        const inicioAnalise = (0, browser_process_hrtime_1.default)();
        this.erros = [];
        this.atual = 0;
        this.blocos = 0;
        this.escopos = [];
        this.pilhaDecoradores = [];
        this.inicializarPilhaEscopos();
        this.tiposDefinidosEmCodigo = {};
        this.hashArquivo = hashArquivo || 0;
        this.simbolos = (retornoLexador === null || retornoLexador === void 0 ? void 0 : retornoLexador.simbolos) || [];
        this.localizacoes = (retornoLexador === null || retornoLexador === void 0 ? void 0 : retornoLexador.pragmas) || {};
        this.verificarPontosEVirgulasInvalidos();
        let declaracoes = [];
        while (!this.estaNoFinal()) {
            const retornoDeclaracao = await this.resolverDeclaracaoForaDeBloco();
            if (retornoDeclaracao === null) {
                continue;
            }
            if (Array.isArray(retornoDeclaracao)) {
                declaracoes = declaracoes.concat(retornoDeclaracao);
            }
            else {
                declaracoes.push(retornoDeclaracao);
            }
            if (!this.estaNoFinal() &&
                this.verificarTipoSimboloAtual(pitugues_2.default.PONTO_E_VIRGULA)) {
                // Verificar se este ; está na mesma linha da declaração anterior
                const declaracaoAnterior = declaracoes[declaracoes.length - 1];
                const linhaDeclaraoAnterior = declaracaoAnterior.linha || 0;
                const linhaPontoVirgula = this.simboloAtual().linha;
                if (linhaPontoVirgula === linhaDeclaraoAnterior) {
                    // Está na mesma linha, então é um separador válido
                    this.avancarEDevolverAnterior();
                }
            }
        }
        if (this.performance) {
            const deltaAnalise = (0, browser_process_hrtime_1.default)(inicioAnalise);
            // eslint-disable-next-line no-undef
            console.log(`[Avaliador Sintático] Tempo para análise: ${deltaAnalise[0] * 1e9 + deltaAnalise[1]}ns`);
        }
        return {
            declaracoes: declaracoes,
            erros: this.erros,
        };
    }
}
exports.AvaliadorSintaticoPitugues = AvaliadorSintaticoPitugues;
//# sourceMappingURL=avaliador-sintatico-pitugues.js.map