@designliquido/delegua
Version:
Linguagem de programação simples e moderna usando português estruturado.
1,071 lines • 66.7 kB
JavaScript
"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