UNPKG

@designliquido/delegua

Version:

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

1,071 lines 66.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.InterpretadorBase = void 0; const browser_process_hrtime_1 = __importDefault(require("browser-process-hrtime")); const estruturas_1 = require("./estruturas"); const construtos_1 = require("../construtos"); const pilha_escopos_execucao_1 = require("./pilha-escopos-execucao"); const quebras_1 = require("../quebras"); const inferenciador_1 = require("../inferenciador"); const metodo_primitiva_1 = require("./estruturas/metodo-primitiva"); const lexador_1 = require("../lexador"); const avaliador_sintatico_1 = require("../avaliador-sintatico"); const espaco_memoria_1 = require("./espaco-memoria"); const comum_1 = require("./comum"); const excecoes_1 = require("../excecoes"); const primitivas_dicionario_1 = __importDefault(require("../bibliotecas/primitivas-dicionario")); const delegua_1 = __importDefault(require("../tipos-de-simbolos/delegua")); const primitivos_1 = __importDefault(require("../tipos-de-dados/primitivos")); const delegua_2 = __importDefault(require("../tipos-de-dados/delegua")); /** * O Interpretador visita todos os elementos complexos gerados pelo avaliador sintático (_parser_), * e de fato executa a lógica de programação descrita no código. Este interpretador base é usado * por Delégua e todos os seus dialetos, contendo somente os pontos em comum entre todas as * linguagens. * * O Interpretador Base não contém dependências com o Node.js. É * recomendado para uso em execuções que ocorrem no navegador de internet. */ class InterpretadorBase { constructor(diretorioBase, performance = false, funcaoDeRetorno = null, funcaoDeRetornoMesmaLinha = null) { this.resultadoInterpretador = []; this.funcaoDeRetorno = null; this.funcaoDeRetornoMesmaLinha = null; this.interfaceDeEntrada = null; // Originalmente é `readline.Interface` this.interfaceEntradaSaida = null; this.emDeclaracaoTente = false; this.microLexador = new lexador_1.MicroLexador(); this.microAvaliadorSintatico = new avaliador_sintatico_1.MicroAvaliadorSintatico(); this.regexInterpolacao = /\${(.*?)}/g; this.tiposNumericos = [ delegua_2.default.INTEIRO, delegua_2.default.NUMERO, delegua_2.default.NÚMERO, delegua_2.default.REAL, ]; this.diretorioBase = diretorioBase; this.performance = performance; this.funcaoDeRetorno = funcaoDeRetorno || console.log; this.funcaoDeRetornoMesmaLinha = funcaoDeRetornoMesmaLinha || process.stdout.write.bind(process.stdout); this.erros = []; this.declaracoes = []; this.resultadoInterpretador = []; // Isso existe por causa de Potigol. // Para acessar uma variável de classe, não é preciso a palavra `isto`. this.expandirPropriedadesDeObjetosEmEspacoMemoria = false; // Por padrão é verdadeiro porque Delégua e Pituguês usam // o interpretador base como implementação padrão. this.requerDeclaracaoPropriedades = true; this.pilhaEscoposExecucao = new pilha_escopos_execucao_1.PilhaEscoposExecucao(); const escopoExecucao = { declaracoes: [], declaracaoAtual: 0, espacoMemoria: new espaco_memoria_1.EspacoMemoria(), finalizado: false, tipo: 'outro', emLacoRepeticao: false, }; this.pilhaEscoposExecucao.empilhar(escopoExecucao); (0, comum_1.carregarBibliotecasGlobais)(this.pilhaEscoposExecucao); } resolverValor(objeto) { if (objeto === null || objeto === undefined) { return objeto; } if (objeto.hasOwnProperty('valor')) { return objeto.valor; } return objeto; } visitarExpressaoArgumentoReferenciaFuncao(expressao) { throw new Error('Método não implementado.'); } visitarExpressaoReferenciaFuncao(expressao) { throw new Error('Método não implementado.'); } visitarExpressaoAcessoMetodo(expressao) { throw new Error('Método não implementado.'); } visitarExpressaoAcessoPropriedade(expressao) { throw new Error('Método não implementado.'); } /** * Construtos de comentários não têm utilidade para o Interpretador. * Apenas retornamos `Promise.resolve()` para não termos erros. * @param declaracao A declaração de comentário. */ visitarDeclaracaoComentario(declaracao) { return Promise.resolve(); } async visitarDeclaracaoTendoComo(declaracao) { const retornoInicializacao = await this.avaliar(declaracao.inicializacaoVariavel); this.pilhaEscoposExecucao.definirConstante(declaracao.simboloVariavel.lexema, retornoInicializacao); await this.executar(declaracao.corpo); if (retornoInicializacao instanceof estruturas_1.ObjetoDeleguaClasse) { const metodoFinalizar = retornoInicializacao.classe.metodos['finalizar']; if (metodoFinalizar) { const chamavel = metodoFinalizar.funcaoPorMetodoDeClasse(retornoInicializacao); chamavel.chamar(this, []); } } return null; } async visitarDeclaracaoInicioAlgoritmo(declaracao) { throw new Error('Método não implementado.'); } async visitarDeclaracaoCabecalhoPrograma(declaracao) { throw new Error('Método não implementado.'); } async visitarExpressaoTupla(expressao) { const chaves = Object.keys(expressao); const valores = []; for (let chave of chaves) { const valor = await this.avaliar(expressao[chave]); valores.push(valor); } return valores; } async visitarExpressaoAtribuicaoPorIndicesMatriz(expressao) { throw new Error('Método não implementado.'); } async visitarExpressaoAcessoElementoMatriz(expressao) { throw new Error('Método não implementado.'); } textoParaRegex(texto) { const match = texto.match(/^([\/~@;%#'])(.*?)\1([gimsuy]*)$/); return match ? new RegExp(match[2], match[3] .split('') .filter((char, pos, flagArr) => flagArr.indexOf(char) === pos) .join('')) : new RegExp(texto); } visitarExpressaoExpressaoRegular(expressao) { return Promise.resolve(this.textoParaRegex(expressao.valor)); } visitarExpressaoTipoDe(expressao) { throw new Error('Método não implementado.'); } async visitarExpressaoFalhar(expressao) { var _a; const textoFalha = (_a = expressao.explicacao.valor) !== null && _a !== void 0 ? _a : (await this.avaliar(expressao.explicacao)).valor; throw new excecoes_1.ErroEmTempoDeExecucao(expressao.simbolo, textoFalha, expressao.linha); } async visitarExpressaoFimPara(declaracao) { throw new Error('Método não implementado.'); } async avaliar(expressao) { // Descomente o código abaixo quando precisar detectar expressões undefined ou nulas. // Por algum motivo o depurador do VSCode não funciona direito aqui // com breakpoint condicional. /* if (expressao === null || expressao === undefined) { console.log('Aqui'); } */ return await expressao.aceitar(this); } /** * Execução da leitura de valores da entrada configurada no * início da aplicação. * @param expressao Expressão do tipo Leia * @returns Promise com o resultado da leitura. */ async visitarExpressaoLeia(expressao) { const mensagem = expressao.argumentos && expressao.argumentos[0] ? expressao.argumentos[0].valor : '> '; return new Promise((resolucao) => this.interfaceEntradaSaida.question(mensagem, (resposta) => { resolucao(resposta); })); } /** * Retira a interpolação de um texto. * @param {texto} texto O texto * @param {any[]} variaveis A lista de variaveis interpoladas * @returns O texto com o valor das variaveis. */ retirarInterpolacao(texto, variaveis) { let textoFinal = texto; variaveis.forEach((elemento) => { var _a, _b; if (((_a = elemento === null || elemento === void 0 ? void 0 : elemento.valor) === null || _a === void 0 ? void 0 : _a.tipo) === delegua_2.default.LOGICO) { textoFinal = textoFinal.replace('${' + elemento.variavel + '}', this.paraTexto((_b = elemento === null || elemento === void 0 ? void 0 : elemento.valor) === null || _b === void 0 ? void 0 : _b.valor)); } else { const valor = this.resolverValor(elemento.valor); textoFinal = textoFinal.replace('${' + elemento.variavel + '}', `${this.paraTexto(valor)}`); } }); return textoFinal; } /** * Resolve todas as interpolações em um texto. * @param {texto} textoOriginal O texto original com as variáveis interpoladas. * @returns Uma lista de variáveis interpoladas. */ async resolverInterpolacoes(textoOriginal, linha) { const variaveis = textoOriginal.match(this.regexInterpolacao); let resultadosAvaliacaoSintatica = variaveis.map((s) => { const nomeVariavel = s.replace(/[\$\{\}]*/gm, ''); let microLexador = this.microLexador.mapear(nomeVariavel); const resultadoMicroAvaliadorSintatico = this.microAvaliadorSintatico.analisar(microLexador, linha); return { nomeVariavel, resultadoMicroAvaliadorSintatico, }; }); // TODO: Verificar erros do `resultadosAvaliacaoSintatica`. const resolucoesPromises = await Promise.all(resultadosAvaliacaoSintatica .flatMap((r) => r.resultadoMicroAvaliadorSintatico.declaracoes) .map((d) => this.avaliar(d))); return resolucoesPromises.map((item, indice) => ({ variavel: resultadosAvaliacaoSintatica[indice].nomeVariavel, valor: item, })); } async visitarExpressaoLiteral(expressao) { if (this.regexInterpolacao.test(expressao.valor)) { const variaveis = await this.resolverInterpolacoes(expressao.valor, expressao.linha); return this.retirarInterpolacao(expressao.valor, variaveis); } return expressao.valor; } async visitarExpressaoAgrupamento(expressao) { return await this.avaliar(expressao.expressao); } eVerdadeiro(objeto) { if (objeto === null) return false; if (typeof objeto === primitivos_1.default.BOOLEANO) return Boolean(objeto); if (objeto.hasOwnProperty('valor')) { return Boolean(objeto.valor); } return true; } verificarOperandoNumero(operador, operando) { if (typeof operando === primitivos_1.default.NUMERO || operando.tipo === delegua_2.default.NUMERO) return; throw new excecoes_1.ErroEmTempoDeExecucao(operador, 'Operando precisa ser um número.', Number(operador.linha)); } async visitarExpressaoUnaria(expressao) { const operando = await this.avaliar(expressao.operando); let valor = this.resolverValor(operando); switch (expressao.operador.tipo) { case delegua_1.default.SUBTRACAO: this.verificarOperandoNumero(expressao.operador, valor); return -valor; case delegua_1.default.NEGACAO: return !this.eVerdadeiro(valor); case delegua_1.default.BIT_NOT: return ~valor; // Para incrementar e decrementar, primeiro precisamos saber se o operador // veio antes do literal ou variável. // Se veio antes e o operando é uma variável, precisamos incrementar/decrementar, // armazenar o valor da variável pra só então devolver o valor. case delegua_1.default.INCREMENTAR: if (expressao.incidenciaOperador === 'ANTES') { valor++; if (expressao.operando instanceof construtos_1.Variavel) { this.pilhaEscoposExecucao.atribuirVariavel(expressao.operando.simbolo, valor); } return valor; } const valorAnteriorIncremento = valor; this.pilhaEscoposExecucao.atribuirVariavel(expressao.operando.simbolo, ++valor); return valorAnteriorIncremento; case delegua_1.default.DECREMENTAR: if (expressao.incidenciaOperador === 'ANTES') { valor--; if (expressao.operando instanceof construtos_1.Variavel) { this.pilhaEscoposExecucao.atribuirVariavel(expressao.operando.simbolo, valor); } return valor; } const valorAnteriorDecremento = valor; this.pilhaEscoposExecucao.atribuirVariavel(expressao.operando.simbolo, --valor); return valorAnteriorDecremento; } return null; } /** * Formata uma saída de acordo com o número e espaços e casas decimais solicitados. * @param declaracao A declaração de formatação de escrita. * @returns {string} A saída formatada como texto e os respectivos parâmetros aplicados. */ async visitarExpressaoFormatacaoEscrita(declaracao) { let resultado = ''; const conteudo = await this.avaliar(declaracao.expressao); const valorConteudo = this.resolverValor(conteudo); const tipoConteudo = conteudo.hasOwnProperty('tipo') ? conteudo.tipo : typeof conteudo; resultado = valorConteudo; if ([delegua_2.default.NUMERO, primitivos_1.default.NUMERO].includes(tipoConteudo) && declaracao.casasDecimais > 0) { resultado = valorConteudo.toLocaleString('pt', { maximumFractionDigits: declaracao.casasDecimais, }); } if (declaracao.espacos > 0) { resultado += ' '.repeat(declaracao.espacos); } return resultado; } /** * Lógica para verificação de valores iguais, para Delégua e alguns dialetos. * @param esquerda Uma variável. * @param direita Outra variável. * @returns Verdadeiro se são iguais. Falso em caso contrário. */ eIgual(esquerda, direita) { if (esquerda === null && direita === null) return true; if (esquerda === null) return false; return esquerda === direita; } /** * Verifica se operandos são números, que podem ser tanto variáveis puras do JavaScript * (neste caso, `number`), ou podem ser variáveis de Delégua com inferência (`VariavelInterface`). * @param operador O símbolo do operador. * @param direita O operando direito. * @param esquerda O operando esquerdo. * @returns Se ambos os operandos são números ou não. */ verificarOperandosNumeros(operador, direita, esquerda) { const tipoDireita = direita.tipo ? direita.tipo : typeof direita === primitivos_1.default.NUMERO ? delegua_2.default.NUMERO : String(NaN); const tipoEsquerda = esquerda.tipo ? esquerda.tipo : typeof esquerda === primitivos_1.default.NUMERO ? delegua_2.default.NUMERO : String(NaN); if (this.tiposNumericos.includes(tipoDireita) && this.tiposNumericos.includes(tipoEsquerda)) return; if (this.tiposNumericos.includes(tipoEsquerda) && tipoDireita === 'qualquer') return; if (this.tiposNumericos.includes(tipoDireita) && tipoEsquerda === 'qualquer') return; // Se operador é subtração, os dois tipos são `qualquer`, mas ambos podem ser convertidos // para número, a operação é válida. if (operador.tipo === delegua_1.default.SUBTRACAO) { if (typeof esquerda.valor === 'number' && typeof direita.valor === 'number') { return; } } throw new excecoes_1.ErroEmTempoDeExecucao(operador, 'Operadores precisam ser números.', operador.linha); } async visitarExpressaoBinaria(expressao) { const esquerda = await this.avaliar(expressao.esquerda); const direita = await this.avaliar(expressao.direita); const valorEsquerdo = this.resolverValor(esquerda); const valorDireito = this.resolverValor(direita); const tipoEsquerdo = (esquerda === null || esquerda === void 0 ? void 0 : esquerda.hasOwnProperty('tipo')) ? esquerda.tipo : (0, inferenciador_1.inferirTipoVariavel)(esquerda); const tipoDireito = (direita === null || direita === void 0 ? void 0 : direita.hasOwnProperty('tipo')) ? direita.tipo : (0, inferenciador_1.inferirTipoVariavel)(direita); switch (expressao.operador.tipo) { case delegua_1.default.EXPONENCIACAO: this.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Math.pow(valorEsquerdo, valorDireito); case delegua_1.default.MAIOR: if (this.tiposNumericos.includes(tipoEsquerdo) && this.tiposNumericos.includes(tipoDireito)) { return Number(valorEsquerdo) > Number(valorDireito); } return String(valorEsquerdo) > String(valorDireito); case delegua_1.default.MAIOR_IGUAL: this.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Number(valorEsquerdo) >= Number(valorDireito); case delegua_1.default.MENOR: if (this.tiposNumericos.includes(tipoEsquerdo) && this.tiposNumericos.includes(tipoDireito)) { return Number(valorEsquerdo) < Number(valorDireito); } return String(valorEsquerdo) < String(valorDireito); case delegua_1.default.MENOR_IGUAL: this.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Number(valorEsquerdo) <= Number(valorDireito); case delegua_1.default.SUBTRACAO: case delegua_1.default.MENOS_IGUAL: this.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Number(valorEsquerdo) - Number(valorDireito); case delegua_1.default.ADICAO: case delegua_1.default.MAIS_IGUAL: if (this.tiposNumericos.includes(tipoEsquerdo) && this.tiposNumericos.includes(tipoDireito)) { return Number(valorEsquerdo) + Number(valorDireito); } // TODO: Se tipo for 'qualquer', seria uma boa confiar nos operadores // tradicionais do JavaScript? if (tipoEsquerdo === 'qualquer' || tipoDireito === 'qualquer') { return valorEsquerdo + valorDireito; } return this.paraTexto(valorEsquerdo) + this.paraTexto(valorDireito); case delegua_1.default.DIVISAO: case delegua_1.default.DIVISAO_IGUAL: this.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Number(valorEsquerdo) / Number(valorDireito); case delegua_1.default.DIVISAO_INTEIRA: case delegua_1.default.DIVISAO_INTEIRA_IGUAL: this.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Math.floor(Number(valorEsquerdo) / Number(valorDireito)); case delegua_1.default.MULTIPLICACAO: case delegua_1.default.MULTIPLICACAO_IGUAL: if (tipoEsquerdo === delegua_2.default.TEXTO || tipoDireito === delegua_2.default.TEXTO) { // Sem ambos os valores resolvem como texto, multiplica normal. // Se apenas um resolve como texto, o outro repete o // texto n vezes, sendo n o valor do outro. if (tipoEsquerdo === delegua_2.default.TEXTO && tipoDireito === delegua_2.default.TEXTO) { return Number(valorEsquerdo) * Number(valorDireito); } if (tipoEsquerdo === delegua_2.default.TEXTO) { return valorEsquerdo.repeat(Number(valorDireito)); } return valorDireito.repeat(Number(valorEsquerdo)); } return Number(valorEsquerdo) * Number(valorDireito); case delegua_1.default.MODULO: case delegua_1.default.MODULO_IGUAL: this.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Number(valorEsquerdo) % Number(valorDireito); case delegua_1.default.BIT_AND: this.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Number(valorEsquerdo) & Number(valorDireito); case delegua_1.default.BIT_XOR: this.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Number(valorEsquerdo) ^ Number(valorDireito); case delegua_1.default.BIT_OR: this.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Number(valorEsquerdo) | Number(valorDireito); case delegua_1.default.MENOR_MENOR: this.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Number(valorEsquerdo) << Number(valorDireito); case delegua_1.default.MAIOR_MAIOR: this.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Number(valorEsquerdo) >> Number(valorDireito); case delegua_1.default.DIFERENTE: return !this.eIgual(valorEsquerdo, valorDireito); case delegua_1.default.IGUAL_IGUAL: return this.eIgual(valorEsquerdo, valorDireito); } } /** * Faz a chamada do método de uma primitiva (por exemplo, número, texto, etc.) com seus * respectivos argumentos. * @param {Chamada} expressao A expressão de chamada. * @param {MetodoPrimitiva} entidadeChamada O método da primitiva chamado. * @returns O resultado da chamada do método da primitiva. */ async chamarMetodoPrimitiva(expressao, entidadeChamada) { const argumentosResolvidos = []; for (const argumento of expressao.argumentos) { const valorResolvido = await this.avaliar(argumento); argumentosResolvidos.push(this.resolverValor(valorResolvido)); } return await entidadeChamada.chamar(this, argumentosResolvidos); } async resolverArgumentosChamada(expressao) { const argumentos = []; for (let i = 0; i < expressao.argumentos.length; i++) { const variavelArgumento = expressao.argumentos[i]; const nomeArgumento = variavelArgumento.hasOwnProperty('simbolo') ? variavelArgumento.simbolo.lexema : undefined; argumentos.push({ nome: nomeArgumento, valor: await this.avaliar(variavelArgumento), }); } return argumentos; } /** * Executa uma chamada de função, método ou classe. * @param expressao A expressão chamada. * @returns O resultado da chamada. */ async visitarExpressaoDeChamada(expressao) { try { const variavelEntidadeChamada = await this.avaliar(expressao.entidadeChamada); if (variavelEntidadeChamada === null) { return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.parentese, 'Chamada de função ou método inexistente: ' + String(expressao.entidadeChamada), expressao.linha)); } const entidadeChamada = this.resolverValor(variavelEntidadeChamada); if (entidadeChamada instanceof estruturas_1.DeleguaModulo) { return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.parentese, 'Entidade chamada é um módulo de Delégua. Provavelmente você quer chamar um de seus componentes?', expressao.linha)); } if (entidadeChamada instanceof metodo_primitiva_1.MetodoPrimitiva) { return await this.chamarMetodoPrimitiva(expressao, entidadeChamada); } const argumentos = await this.resolverArgumentosChamada(expressao); const aridade = entidadeChamada.aridade ? entidadeChamada.aridade() : entidadeChamada.length; // Completar os argumentos não preenchidos com valores indefinidos. if (argumentos.length < aridade) { const diferenca = aridade - argumentos.length; for (let i = 0; i < diferenca; i++) { argumentos.push({ nome: null, valor: null, }); } } if (entidadeChamada instanceof estruturas_1.FuncaoPadrao) { try { return entidadeChamada.chamar(this, argumentos.map((a) => a && a.valor && this.resolverValor(a.valor)), expressao.entidadeChamada.simbolo); } catch (erro) { this.erros.push({ erroInterno: erro, linha: expressao.linha, hashArquivo: expressao.hashArquivo, }); } } // Por algum motivo misterioso, `entidadeChamada instanceof Chamavel` dá `false` em Liquido, // mesmo que esteja tudo certo com `DeleguaFuncao`, // então precisamos testar o nome do construtor também. if (entidadeChamada instanceof estruturas_1.Chamavel || entidadeChamada.constructor.name === 'DeleguaFuncao') { const retornoEntidadeChamada = await entidadeChamada.chamar(this, argumentos); return retornoEntidadeChamada; } // Chamadas a `super()`. // Basicamente, chamar o construtor da superclasse. if (expressao.entidadeChamada instanceof construtos_1.Super) { const descritorSuperclasse = variavelEntidadeChamada.classe.superClasse; const metodoConstrutor = descritorSuperclasse.encontrarMetodo('construtor'); await metodoConstrutor.chamar(this, argumentos); return null; } // A função chamada pode ser de uma biblioteca JavaScript. // Neste caso apenas testamos se o tipo é uma função. // Casos que passam aqui: chamadas a métodos de bibliotecas de Delégua. if (typeof entidadeChamada === primitivos_1.default.FUNCAO) { let objeto = null; if (expressao.entidadeChamada.objeto) { objeto = await this.avaliar(expressao.entidadeChamada.objeto); } return entidadeChamada.apply(this.resolverValor(objeto), argumentos); } return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.parentese, 'Só pode chamar função ou classe.', expressao.linha)); } catch (erro) { this.erros.push({ erroInterno: erro, linha: expressao.linha, hashArquivo: expressao.hashArquivo, }); } } /** * Execução de uma expressão de atribuição. * @param expressao A expressão. * @returns O valor atribuído. */ async visitarExpressaoDeAtribuicao(expressao) { const valor = await this.avaliar(expressao.valor); const valorResolvido = this.resolverValor(valor); let indice = null; if (expressao.indice) { indice = await this.avaliar(expressao.indice); } switch (expressao.alvo.constructor.name) { case 'Variavel': const alvoVariavel = expressao.alvo; this.pilhaEscoposExecucao.atribuirVariavel(alvoVariavel.simbolo, valorResolvido, indice); break; case 'AcessoMetodoOuPropriedade': // Nunca será método aqui: apenas propriedade. const alvoPropriedade = expressao.alvo; const variavelObjeto = await this.avaliar(alvoPropriedade.objeto); const objeto = this.resolverValor(variavelObjeto); const valor = await this.avaliar(expressao.valor); if (objeto.constructor.name === 'ObjetoDeleguaClasse') { const objetoDeleguaClasse = objeto; objetoDeleguaClasse.definir(alvoPropriedade.simbolo, valor); } break; default: throw new excecoes_1.ErroEmTempoDeExecucao(null, `Atribuição com caso faltante: ${expressao.alvo.constructor.name}.`); } return valorResolvido; } procurarVariavel(simbolo) { return this.pilhaEscoposExecucao.obterValorVariavel(simbolo); } visitarExpressaoDeVariavel(expressao) { return this.procurarVariavel(expressao.simbolo); } async visitarDeclaracaoDeExpressao(declaracao) { return await this.avaliar(declaracao.expressao); } async visitarExpressaoLogica(expressao) { const esquerda = await this.avaliar(expressao.esquerda); if (expressao.operador.tipo === delegua_1.default.EM) { const direita = await this.avaliar(expressao.direita); if (Array.isArray(direita) || typeof direita === primitivos_1.default.TEXTO) { return direita.includes(esquerda); } else if (direita !== null && typeof direita === 'object') { return (esquerda in direita || (direita.valor !== undefined && esquerda in direita.valor)); } throw new excecoes_1.ErroEmTempoDeExecucao(esquerda, "Tipo de chamada inválida com 'em'.", expressao.linha); } // se um estado for verdadeiro, retorna verdadeiro if (expressao.operador.tipo === delegua_1.default.OU) { if (this.eVerdadeiro(esquerda)) return esquerda; } // se um estado for falso, retorna falso if (expressao.operador.tipo === delegua_1.default.E) { if (!this.eVerdadeiro(esquerda)) return esquerda; } return await this.avaliar(expressao.direita); } async visitarDeclaracaoPara(declaracao) { const declaracaoInicializador = Array.isArray(declaracao.inicializador) ? declaracao.inicializador[0] : declaracao.inicializador; if (declaracaoInicializador !== null) { await this.avaliar(declaracaoInicializador); } let retornoExecucao; while (!(retornoExecucao instanceof quebras_1.Quebra)) { if (declaracao.condicao !== null && !this.eVerdadeiro(await this.avaliar(declaracao.condicao))) { break; } try { retornoExecucao = await this.executar(declaracao.corpo); if (retornoExecucao instanceof quebras_1.SustarQuebra) { return null; } if (retornoExecucao instanceof quebras_1.ContinuarQuebra) { retornoExecucao = null; } } catch (erro) { this.erros.push({ erroInterno: erro, linha: declaracao.linha, hashArquivo: declaracao.hashArquivo, }); return Promise.reject(erro); } if (declaracao.incrementar !== null) { await this.avaliar(declaracao.incrementar); } } return retornoExecucao; } // TODO: Descobrir se mais algum dialeto, fora Delégua e Pituguês, usam isso. async visitarDeclaracaoParaCada(declaracao) { let retornoExecucao; // Posição atual precisa ser reiniciada, pois pode estar dentro de outro // laço de repetição. declaracao.posicaoAtual = 0; const vetorResolvido = await this.avaliar(declaracao.vetor); let valorVetorResolvido = this.resolverValor(vetorResolvido); // Se até aqui vetor resolvido é um dicionário, converte dicionário // para vetor de duplas. if (declaracao.vetor.tipo === 'dicionário') { valorVetorResolvido = Object.entries(valorVetorResolvido).map(v => new construtos_1.Dupla(v[0], v[1])); } if (!Array.isArray(valorVetorResolvido)) { return Promise.reject("Variável ou literal provida em instrução 'para cada' não é um vetor."); } while (!(retornoExecucao instanceof quebras_1.Quebra) && declaracao.posicaoAtual < valorVetorResolvido.length) { try { if (declaracao.variavelIteracao instanceof construtos_1.Variavel) { this.pilhaEscoposExecucao.definirVariavel(declaracao.variavelIteracao.simbolo.lexema, valorVetorResolvido[declaracao.posicaoAtual]); } if (declaracao.variavelIteracao instanceof construtos_1.Dupla) { const valorComoDupla = valorVetorResolvido[declaracao.posicaoAtual]; this.pilhaEscoposExecucao.definirVariavel(declaracao.variavelIteracao.primeiro.valor, valorComoDupla.primeiro); this.pilhaEscoposExecucao.definirVariavel(declaracao.variavelIteracao.segundo.valor, valorComoDupla.segundo); } retornoExecucao = await this.executar(declaracao.corpo); if (retornoExecucao instanceof quebras_1.SustarQuebra) { return null; } if (retornoExecucao instanceof quebras_1.ContinuarQuebra) { retornoExecucao = null; } declaracao.posicaoAtual++; } catch (erro) { this.erros.push({ erroInterno: erro, linha: declaracao.linha, hashArquivo: declaracao.hashArquivo, }); return Promise.reject(erro); } } return retornoExecucao; } /** * Executa uma expressão Se, que tem uma condição, pode ter um bloco * Senão, e múltiplos blocos Senão-se. * @param declaracao A declaração Se. * @returns O resultado da avaliação do bloco cuja condição é verdadeira. */ async visitarDeclaracaoSe(declaracao) { if (this.eVerdadeiro(await this.avaliar(declaracao.condicao))) { return await this.executar(declaracao.caminhoEntao); } for (let i = 0; i < declaracao.caminhosSeSenao.length; i++) { const atual = declaracao.caminhosSeSenao[i]; if (this.eVerdadeiro(await this.avaliar(atual.condicao))) { return await this.executar(atual.caminho); } } if (declaracao.caminhoSenao !== null) { return await this.executar(declaracao.caminhoSenao); } return null; } async visitarDeclaracaoEnquanto(declaracao) { let retornoExecucao; while (!(retornoExecucao instanceof quebras_1.Quebra) && this.eVerdadeiro(await this.avaliar(declaracao.condicao))) { try { retornoExecucao = await this.executar(declaracao.corpo); if (retornoExecucao instanceof quebras_1.SustarQuebra) { return null; } if (retornoExecucao instanceof quebras_1.ContinuarQuebra) { retornoExecucao = null; } } catch (erro) { this.erros.push({ erroInterno: erro, linha: declaracao.linha, hashArquivo: declaracao.hashArquivo, }); return Promise.reject(erro); } } return retornoExecucao; } async visitarDeclaracaoEscolha(declaracao) { const condicaoEscolha = await this.avaliar(declaracao.identificadorOuLiteral); const valorCondicaoEscolha = this.resolverValor(condicaoEscolha); const caminhos = declaracao.caminhos; const caminhoPadrao = declaracao.caminhoPadrao; let encontrado = false; try { for (let i = 0; i < caminhos.length; i++) { const caminho = caminhos[i]; for (let j = 0; j < caminho.condicoes.length; j++) { const condicaoAvaliada = await this.avaliar(caminho.condicoes[j]); if (condicaoAvaliada === valorCondicaoEscolha) { encontrado = true; try { await this.executarBloco(caminho.declaracoes); } catch (erro) { this.erros.push({ erroInterno: erro, linha: declaracao.linha, hashArquivo: declaracao.hashArquivo, }); return Promise.reject(erro); } } } } if (caminhoPadrao !== null && !encontrado) { await this.executarBloco(caminhoPadrao.declaracoes); } } catch (erro) { this.erros.push({ erroInterno: erro, linha: declaracao.linha, hashArquivo: declaracao.hashArquivo, }); throw erro; } } async visitarDeclaracaoFazer(declaracao) { let retornoExecucao; do { try { retornoExecucao = await this.executar(declaracao.caminhoFazer); if (retornoExecucao instanceof quebras_1.SustarQuebra) { return null; } if (retornoExecucao instanceof quebras_1.ContinuarQuebra) { retornoExecucao = null; } } catch (erro) { this.erros.push({ erroInterno: erro, linha: declaracao.linha, hashArquivo: declaracao.hashArquivo, }); return Promise.reject(erro); } } while (!(retornoExecucao instanceof quebras_1.Quebra) && this.eVerdadeiro(await this.avaliar(declaracao.condicaoEnquanto))); } /** * Interpretação de uma declaração `tente`. * @param declaracao O objeto da declaração. */ async visitarDeclaracaoTente(declaracao) { let valorRetorno; try { this.emDeclaracaoTente = true; try { valorRetorno = await this.executarBloco(declaracao.caminhoTente); } catch (erro) { if (declaracao.caminhoPegue !== null) { // `caminhoPegue` aqui pode ser um construto de função (se `pegue` tem parâmetros) // ou um vetor de `Declaracao` (`pegue` sem parâmetros). // As execuções, portanto, são diferentes. if (Array.isArray(declaracao.caminhoPegue)) { valorRetorno = await this.executarBloco(declaracao.caminhoPegue); } else { const literalErro = new construtos_1.Literal(declaracao.hashArquivo, Number(declaracao.linha), erro.mensagem); const chamadaPegue = new construtos_1.Chamada(declaracao.caminhoPegue.hashArquivo, declaracao.caminhoPegue, [literalErro]); valorRetorno = await chamadaPegue.aceitar(this); } } } } finally { if (declaracao.caminhoFinalmente !== null) valorRetorno = await this.executarBloco(declaracao.caminhoFinalmente); this.emDeclaracaoTente = false; } return valorRetorno; } async visitarDeclaracaoImportar(declaracao) { return Promise.reject('Importação de arquivos não suportada por Interpretador Base.'); } async avaliarArgumentosEscreva(argumentos) { let formatoTexto = ''; for (const argumento of argumentos) { const resultadoAvaliacao = await this.avaliar(argumento); let valor = this.resolverValor(resultadoAvaliacao); formatoTexto += `${this.paraTexto(valor)} `; } return formatoTexto.trimEnd(); } /** * Execução de uma escrita na saída padrão, sem quebras de linha. * Implementada para alguns dialetos, como VisuAlg. * @param declaracao A declaração. * @returns Sempre nulo, por convenção de visita. */ async visitarDeclaracaoEscrevaMesmaLinha(declaracao) { try { const formatoTexto = await this.avaliarArgumentosEscreva(declaracao.argumentos); this.funcaoDeRetornoMesmaLinha(formatoTexto); return null; } catch (erro) { this.erros.push({ erroInterno: erro, linha: declaracao.linha, hashArquivo: declaracao.hashArquivo, }); } } /** * Execução de uma escrita na saída configurada, que pode ser `console` (padrão) ou * alguma função para escrever numa página Web. * @param declaracao A declaração. * @returns Sempre nulo, por convenção de visita. */ async visitarDeclaracaoEscreva(declaracao) { try { const formatoTexto = await this.avaliarArgumentosEscreva(declaracao.argumentos); this.funcaoDeRetorno(formatoTexto); return null; } catch (erro) { this.erros.push({ erroInterno: erro, linha: declaracao.linha, hashArquivo: declaracao.hashArquivo, }); } } /** * Empilha declarações na pilha de escopos de execução, cria um novo ambiente e * executa as declarações empilhadas. * Se o retorno do último bloco foi uma exceção (normalmente um erro em tempo de execução), * atira a exceção daqui. * Isso é usado, por exemplo, em blocos tente ... pegue ... finalmente. * @param declaracoes Um vetor de declaracoes a ser executado. * @param ambiente O ambiente de execução quando houver, como parâmetros, argumentos, etc. */ async executarBloco(declaracoes, ambiente) { const escopoExecucao = { declaracoes: declaracoes, declaracaoAtual: 0, espacoMemoria: ambiente || new espaco_memoria_1.EspacoMemoria(), finalizado: false, tipo: 'outro', emLacoRepeticao: false, }; this.pilhaEscoposExecucao.empilhar(escopoExecucao); const retornoUltimoEscopo = await this.executarUltimoEscopo(); if (retornoUltimoEscopo instanceof excecoes_1.ErroEmTempoDeExecucao) { return Promise.reject(retornoUltimoEscopo); } return retornoUltimoEscopo; } async visitarExpressaoBloco(declaracao) { return await this.executarBloco(declaracao.declaracoes); } async avaliacaoDeclaracaoVarOuConst(declaracao) { let valorOuOutraVariavel = null; if (declaracao.inicializador !== null) { valorOuOutraVariavel = await this.avaliar(declaracao.inicializador); } let valorFinal = null; if (valorOuOutraVariavel !== null && valorOuOutraVariavel !== undefined) { valorFinal = this.resolverValor(valorOuOutraVariavel); } return valorFinal; } /** * Executa expressão de definição de constante. * @param declaracao A declaração `Const`. * @returns Sempre retorna nulo. */ async visitarDeclaracaoConst(declaracao) { const valorFinal = await this.avaliacaoDeclaracaoVarOuConst(declaracao); this.pilhaEscoposExecucao.definirConstante(declaracao.simbolo.lexema, valorFinal, declaracao.tipo); return null; } /** * Executa expressão de definição de múltiplas constantes. * @param declaracao A declaração `ConstMultiplo`. * @returns Sempre retorna nulo. */ async visitarDeclaracaoConstMultiplo(declaracao) { const valoresFinais = await this.avaliacaoDeclaracaoVarOuConst(declaracao); const tipoIndividual = declaracao.tipo.replace('[]', ''); for (let [indice, valor] of valoresFinais.entries()) { this.pilhaEscoposExecucao.definirConstante(declaracao.simbolos[indice].lexema, valor, tipoIndividual); } return null; } visitarExpressaoContinua(declaracao) { return new quebras_1.ContinuarQuebra(); } visitarExpressaoSustar(declaracao) { return new quebras_1.SustarQuebra(); } async visitarExpressaoRetornar(declaracao) { let valor = null; if (declaracao.valor != null) valor = await this.avaliar(declaracao.valor); return new quebras_1.RetornoQuebra(valor); } async visitarExpressaoFuncaoConstruto(funcaoConstruto) { return new estruturas_1.DeleguaFuncao(null, funcaoConstruto); } async visitarExpressaoAtribuicaoPorIndice(expressao) { const promises = await Promise.all([ this.avaliar(expressao.objeto), this.avaliar(expressao.indice), this.avaliar(expressao.valor), ]); let objeto = promises[0]; let indice = promises[1]; const valor = promises[2]; if (objeto.tipo === delegua_2.default.TUPLA) { return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.objeto.simbolo.lexema, 'Não é possível modificar uma tupla. As tuplas são estruturas de dados imutáveis.', expressao.linha)); } objeto = this.resolverValor(objeto); indice = this.resolverValor(indice); if (Array.isArray(objeto)) { if (indice < 0 && objeto.length !== 0) { while (indice < 0) { indice += objeto.length; } } while (objeto.length < indice) { objeto.push(null); } objeto[indice] = valor; } else if (objeto.constructor === Object || objeto instanceof estruturas_1.ObjetoDeleguaClasse || objeto instanceof estruturas_1.DeleguaFuncao || objeto instanceof estruturas_1.DescritorTipoClasse || objeto instanceof estruturas_1.DeleguaModulo) { objeto[indice] = valor; } else { return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.objeto.nome, 'Somente listas, dicionários, classes e objetos podem ser mudados por índice.', expressao.linha)); } } async visitarExpressaoAcessoIndiceVariavel(expressao) { const promises = await Promise.all([ this.avaliar(expressao.entidadeChamada), this.avaliar(expressao.indice), ]); const variavelObjeto = promises[0]; const indice = promises[1]; const objeto = this.resolverValor(variavelObjeto); let valorIndice = this.resolverValor(indice); if (Array.isArray(objeto)) { if (!Number.isInteger(valorIndice)) { return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.simboloFechamento, 'Somente inteiros podem ser usados para indexar um vetor.', expressao.linha)); } if (valorIndice < 0 && objeto.length !== 0) { while (valorIndice < 0) { valorIndice += objeto.length; } } if (valorIndice >= objeto.length) { return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.simboloFechamento, 'Índice do vetor fora do intervalo.', expressao.linha)); } return objeto[valorIndice]; } if (objeto instanceof construtos_1.Vetor) { return objeto.valores[valorIndice]; } if (objeto.constructor === Object || objeto instanceof estruturas_1.ObjetoDeleguaClasse || objeto instanceof estruturas_1.DeleguaFuncao || objeto instanceof estruturas_1.DescritorTipoClasse || objeto instanceof estruturas_1.DeleguaModulo) { if (objeto[valorIndice] === 0) return 0; return objeto[valorIndice] || null; } if (typeof objeto === primitivos_1.default.TEXTO) { if (!Number.isInteger(valorIndice)) { return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.simboloFechamento, 'Somente inteiros podem ser usados para indexar um vetor.', expressao.linha)); } if (valorIndice < 0 && objeto.length !== 0) { while (valorIndice < 0) { valorIndice