@designliquido/delegua
Version:
Linguagem de programação simples e moderna usando português estruturado.
976 lines (975 loc) • 114 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 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"));
const primitivas_vetor_1 = __importDefault(require("../bibliotecas/primitivas-vetor"));
/**
* 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.classeAtualEmExecucao = null;
this.funcaoDeRetorno = null;
this.funcaoDeRetornoMesmaLinha = null;
this.interfaceDeEntrada = null; // Originalmente é `readline.Interface`
this.interfaceEntradaSaida = null;
this.emDeclaracaoTente = false;
// typeName → methodName → DeleguaFuncao
this.extensoesGlobais = new Map();
// hashArquivo → typeName → methodName → DeleguaFuncao
this.extensoesModulo = new Map();
this.microLexador = new lexador_1.MicroLexador();
this.microAvaliadorSintatico = new avaliador_sintatico_1.MicroAvaliadorSintatico();
this.regexInterpolacao = /\${(.*?)}/g;
// Número de iterações entre cada cessão de controle ao loop de eventos do JavaScript.
// Isso permite que laços de repetição longos ou infinitos não bloqueiem o loop de eventos.
this.iteracoesParaCederControle = 1000;
this.tiposNumericos = [
delegua_2.default.INTEIRO,
delegua_2.default.LONGO,
delegua_2.default.NUMERO,
delegua_2.default.NÚMERO,
delegua_2.default.REAL,
];
this.lancarErroPorDivisaoPorZero = false;
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);
// Registrar a classe base `Objeto` no escopo global.
this.pilhaEscoposExecucao.definirVariavel('Objeto', estruturas_1.OBJETO_BASE);
}
/**
* Cede o controle ao loop de eventos do JavaScript.
* Usado em laços de repetição para evitar bloqueio do loop de eventos
* em iterações longas ou infinitas.
*/
async cederControle(iteracoes) {
if (iteracoes % this.iteracoesParaCederControle === 0) {
await new Promise((resolve) => {
if (typeof setImmediate !== 'undefined') {
setImmediate(resolve);
}
else {
setTimeout(resolve, 0);
}
});
}
}
visitarDeclaracaoTextoDocumentacao(declaracao) {
throw new Error('Método não implementado.');
}
visitarExpressaoSeparador(expressao) {
throw new Error('Método não implementado.');
}
visitarExpressaoComentario(expressao) {
throw new Error('Método não implementado.');
}
/**
* Usado para chamadas de métodos de primitiva.
* Sendo uma variável ou constante, a primitiva precisa atualizar a referência
* para o objeto que está sendo acessado.
* @param {Construto} objetoAcessado O objeto que está sendo acessado.
* @returns O nome desse objeto, se ele for uma variável ou constante.
* @see resolverValor
*/
resolverNomeObjectoAcessado(objetoAcessado) {
switch (objetoAcessado.constructor) {
// TODO: Não habilitar isso até que vetores sejam repassados para o montão.
/* case AcessoMetodoOuPropriedade:
return (objetoAcessado as AcessoMetodoOuPropriedade).simbolo.lexema;
case AcessoIndiceVariavel:
return this.resolverNomeObjectoAcessado((objetoAcessado as AcessoIndiceVariavel).entidadeChamada); */
case construtos_1.Chamada:
return this.resolverNomeObjectoAcessado(objetoAcessado.entidadeChamada);
case construtos_1.Constante:
return objetoAcessado.simbolo.lexema;
case construtos_1.AcessoMetodoOuPropriedade:
case construtos_1.AcessoIndiceVariavel:
case construtos_1.Dicionario:
case construtos_1.Literal:
case construtos_1.Vetor:
return '';
case construtos_1.Isto:
return objetoAcessado.simboloChave.lexema;
case construtos_1.Super:
return objetoAcessado.simboloChave.lexema;
case construtos_1.Variavel:
return objetoAcessado.simbolo.lexema;
}
throw new excecoes_1.ErroEmTempoDeExecucao(objetoAcessado.simbolo, `Construto ${objetoAcessado.constructor.name} não possui resolução de nome apropriada.`);
}
resolverValor(objeto) {
if (objeto === null || objeto === undefined) {
return objeto;
}
if (objeto.hasOwnProperty('valor')) {
return objeto.valor;
}
return objeto;
}
/**
* Resolve valores recursivamente, incluindo valores aninhados em arrays e dicionários.
* Remove metadados que não devem ser serializados.
* @param objeto O objeto a ser resolvido
* @returns O valor resolvido sem metadados
*/
resolverValorRecursivo(objeto) {
// Null, undefined, ou tipos primitivos
if (objeto === null || objeto === undefined || typeof objeto !== 'object') {
return objeto;
}
// Resolve metadados primeiro (valorRetornado ou valor)
if (objeto.hasOwnProperty && objeto.hasOwnProperty('valorRetornado')) {
return this.resolverValorRecursivo(objeto.valorRetornado);
}
if (objeto.hasOwnProperty && objeto.hasOwnProperty('valor')) {
return this.resolverValorRecursivo(objeto.valor);
}
// Se é array, resolve recursivamente todos os elementos
if (Array.isArray(objeto)) {
return objeto.map((elemento) => this.resolverValorRecursivo(elemento));
}
// Se é objeto plano, resolve recursivamente todas as propriedades
if (objeto && objeto.constructor && objeto.constructor === Object) {
const objetoResolvido = {};
for (const chave in objeto) {
objetoResolvido[chave] = this.resolverValorRecursivo(objeto[chave]);
}
return objetoResolvido;
}
// Outros tipos de objetos (Date, classes customizadas, etc.)
return objeto;
}
async visitarExpressaoArgumentoReferenciaFuncao(expressao) {
const deleguaFuncao = this.pilhaEscoposExecucao.obterVariavelPorNome(expressao.simboloFuncao.lexema);
return deleguaFuncao;
}
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) {
throw new Error('Método não implementado.');
}
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) {
// Lista de propriedades válidas para tuplas (ignorar propriedades de controle)
const propriedadesValidas = [
'primeiro',
'segundo',
'terceiro',
'quarto',
'quinto',
'sexto',
'sétimo',
'setimo',
'oitavo',
'nono',
'décimo',
'decimo',
];
const valores = [];
for (let propriedade of propriedadesValidas) {
if (expressao.hasOwnProperty(propriedade) && expressao[propriedade] !== undefined) {
const valor = await this.avaliar(expressao[propriedade]);
valores.push(valor);
}
}
return valores;
}
async visitarExpressaoTuplaN(expressao) {
const elementos = [];
for (let i = 0; i < expressao.elementos.length; i++) {
const res = await this.avaliar(expressao.elementos[i]);
elementos.push(this.resolverValor(res));
}
const elementosComoConstrutos = elementos.map((valor) => new construtos_1.Literal(expressao.hashArquivo, expressao.linha, valor));
return new construtos_1.TuplaN(expressao.hashArquivo, expressao.linha, elementosComoConstrutos);
}
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 visitarExpressaoReferenciaFuncao(expressao) {
const deleguaFuncao = this.pilhaEscoposExecucao.obterReferenciaFuncao(expressao.idFuncao);
return deleguaFuncao;
}
/**
* Chama o método `aceitar` de um construto ou declaração, passando o
* próprio interpretador como parâmetro.
*
* Isto é usado para saber qual método do próprio interpretador chamar
* na sequência.
* @param expressao A expressão, que pode ser um construto ou declaração.
* @returns O retorno da execução do método de visita chamado.
*/
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[]} interpolacoes A lista de interpolações a serem resolvidas.
* @returns O texto com o valor das variáveis.
*/
retirarInterpolacao(texto, interpolacoes) {
let textoFinal = texto;
for (const elemento of interpolacoes) {
// TODO: Há alguma chance de `elemento` ser `undefined` aqui?
let valor = elemento === null || elemento === void 0 ? void 0 : elemento.valor;
if (valor.hasOwnProperty && valor.hasOwnProperty('valorRetornado')) {
valor = valor.valorRetornado;
}
if (valor.tipo === delegua_2.default.LOGICO) {
textoFinal = textoFinal.replace('${' + elemento.expressaoInterpolacao + '}', this.paraTexto(valor));
}
else {
valor = this.resolverValor(valor);
const valorResolvidoComoTexto = this.paraTexto(valor);
textoFinal = textoFinal.replace('${' + elemento.expressaoInterpolacao + '}', valorResolvidoComoTexto.replace(/"/g, ''));
}
}
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);
return await Promise.all(variaveis.map(async (s) => {
var _a, _b, _c, _d;
const expressaoInterpolacao = s.replace(/[\$\{\}]*/gm, '');
const microLexador = this.microLexador.mapear(expressaoInterpolacao);
let declaracoes = [];
try {
const resultadoMicroAvaliadorSintatico = this.microAvaliadorSintatico.analisar(microLexador, linha);
for (const erro of resultadoMicroAvaliadorSintatico.erros) {
this.erros.push({
erroInterno: erro,
linha: (_a = erro.linha) !== null && _a !== void 0 ? _a : linha,
hashArquivo: (_b = erro.hashArquivo) !== null && _b !== void 0 ? _b : -1,
});
}
declaracoes = resultadoMicroAvaliadorSintatico.declaracoes;
}
catch (erroAvaliador) {
this.erros.push({
erroInterno: erroAvaliador,
linha: (_c = erroAvaliador.linha) !== null && _c !== void 0 ? _c : linha,
hashArquivo: (_d = erroAvaliador.hashArquivo) !== null && _d !== void 0 ? _d : -1,
});
}
let valor = declaracoes.length > 0 ? await this.avaliar(declaracoes[0]) : '';
const instancia = valor instanceof estruturas_1.ObjetoDeleguaClasse
? valor
: (valor === null || valor === void 0 ? void 0 : valor.valor) instanceof estruturas_1.ObjetoDeleguaClasse
? valor.valor
: null;
if (instancia) {
const metodoParaTexto = instancia.classe.encontrarMetodo('paraTexto');
if (metodoParaTexto) {
const funcaoBound = metodoParaTexto.funcaoPorMetodoDeClasse(instancia);
valor = await funcaoBound.chamar(this, []);
}
}
return { expressaoInterpolacao, valor };
}));
}
async visitarExpressaoLiteral(expressao) {
if (this.regexInterpolacao.test(String(expressao.valor))) {
const valorComoTexto = String(expressao.valor);
const interpolacoes = await this.resolverInterpolacoes(valorComoTexto, expressao.linha);
return this.retirarInterpolacao(valorComoTexto, interpolacoes);
}
return expressao.valor;
}
/**
* Avaliação de agrupamento. Se resultado da avaliação é uma declaração de
* função (por exemplo, funções anônimas), a declaração é retornada. Este
* retorno é utilizado, entre outros lugares, por `visitarExpressaoDeChamada`.
* @param {Agrupamento} expressao O construto de agrupamento.
* @returns O resultado da avaliação.
* @see this.visitarExpressaoDeChamada
*/
async visitarExpressaoAgrupamento(expressao) {
const avaliacaoAgrupamento = await this.avaliar(expressao.expressao);
if (avaliacaoAgrupamento.declaracao !== undefined) {
return avaliacaoAgrupamento.declaracao;
}
return avaliacaoAgrupamento;
}
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.ADICAO:
this.verificarOperandoNumero(expressao.operador, valor);
return +valor;
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:
// Mantém BigInt como BigInt, converte outros para Number
if (typeof valor === 'bigint') {
return ~valor;
}
return ~Number(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;
// TODO: Provavelmente isso está incorreto. Descobrir se operando resolve para
// `Construto` ou para `Simbolo`.
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;
// TODO: Provavelmente isso está incorreto. Descobrir se operando resolve para
// `Construto` ou para `Simbolo`.
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;
// Handle BigInt/Number comparison
if (typeof esquerda === 'bigint' && typeof direita === 'number') {
return esquerda == BigInt(direita);
}
if (typeof esquerda === 'number' && typeof direita === 'bigint') {
return BigInt(esquerda) == direita;
}
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
: typeof direita === 'bigint'
? delegua_2.default.LONGO
: String(NaN);
const tipoEsquerda = esquerda.tipo
? esquerda.tipo
: typeof esquerda === primitivos_1.default.NUMERO
? delegua_2.default.NUMERO
: typeof esquerda === 'bigint'
? delegua_2.default.LONGO
: 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;
if (this.tiposNumericos.includes(tipoEsquerda) && tipoDireita === 'nulo')
return;
if (this.tiposNumericos.includes(tipoDireita) && tipoEsquerda === 'nulo')
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);
// Verificar sobrecarga de operador: se o operando esquerdo é uma instância de classe,
// procurar método `operador<símbolo>` (ex: `operador+`, `operador==`).
if (valorEsquerdo instanceof estruturas_1.ObjetoDeleguaClasse) {
const nomeOperador = 'operador' + expressao.operador.lexema;
const metodoOperador = valorEsquerdo.classe.encontrarMetodo(nomeOperador);
if (metodoOperador) {
const metodoBound = metodoOperador.funcaoPorMetodoDeClasse(valorEsquerdo);
const argumentoOperador = direita && Object.prototype.hasOwnProperty.call(direita, 'tipo')
? direita
: {
tipo: (0, inferenciador_1.inferirTipoVariavel)(valorDireito),
valor: valorDireito,
imutavel: false,
};
return await metodoBound.chamar(this, [{ nome: null, valor: argumentoOperador }]);
}
}
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);
// Auto-promove para BigInt se qualquer operando for BigInt
if (typeof valorEsquerdo === 'bigint' || typeof valorDireito === 'bigint') {
const esq = typeof valorEsquerdo === 'bigint'
? valorEsquerdo
: BigInt(Math.floor(Number(valorEsquerdo)));
const dir = typeof valorDireito === 'bigint'
? valorDireito
: BigInt(Math.floor(Number(valorDireito)));
return esq ** dir;
}
const resultadoExponenciacao = Math.pow(valorEsquerdo, valorDireito);
return resultadoExponenciacao;
case delegua_1.default.MAIOR:
if (this.tiposNumericos.includes(tipoEsquerdo) &&
this.tiposNumericos.includes(tipoDireito)) {
return valorEsquerdo > valorDireito;
}
return String(valorEsquerdo) > String(valorDireito);
case delegua_1.default.MAIOR_IGUAL:
this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
return valorEsquerdo >= valorDireito;
case delegua_1.default.MENOR:
if (this.tiposNumericos.includes(tipoEsquerdo) &&
this.tiposNumericos.includes(tipoDireito)) {
return valorEsquerdo < valorDireito;
}
return String(valorEsquerdo) < String(valorDireito);
case delegua_1.default.MENOR_IGUAL:
this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
return valorEsquerdo <= valorDireito;
case delegua_1.default.SUBTRACAO:
case delegua_1.default.MENOS_IGUAL:
this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
// Auto-promove para BigInt se qualquer operando for BigInt
if (typeof valorEsquerdo === 'bigint' || typeof valorDireito === 'bigint') {
const esq = typeof valorEsquerdo === 'bigint'
? valorEsquerdo
: BigInt(Math.floor(Number(valorEsquerdo)));
const dir = typeof valorDireito === 'bigint'
? valorDireito
: BigInt(Math.floor(Number(valorDireito)));
return esq - dir;
}
return Number(valorEsquerdo) - Number(valorDireito);
case delegua_1.default.ADICAO:
case delegua_1.default.MAIS_IGUAL:
// Se ambos os operandos são vetores, concatená-los
if (Array.isArray(valorEsquerdo) && Array.isArray(valorDireito)) {
return valorEsquerdo.concat(valorDireito);
}
// Auto-promove para BigInt se qualquer operando for BigInt
if (typeof valorEsquerdo === 'bigint' || typeof valorDireito === 'bigint') {
const esq = typeof valorEsquerdo === 'bigint'
? valorEsquerdo
: BigInt(Math.floor(Number(valorEsquerdo)));
const dir = typeof valorDireito === 'bigint'
? valorDireito
: BigInt(Math.floor(Number(valorDireito)));
return esq + dir;
}
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' ||
tipoEsquerdo === 'nulo' || tipoDireito === 'nulo') {
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);
if (this.lancarErroPorDivisaoPorZero && Number(valorDireito) === 0) {
throw new excecoes_1.ErroEmTempoDeExecucao(expressao.operador, 'Divisão por zero não é permitida.', expressao.operador.linha);
}
// SEMPRE retorna Number para precisão decimal (preferência do usuário)
// Mesmo se operandos forem BigInt, converte para Number
return Number(valorEsquerdo) / Number(valorDireito);
case delegua_1.default.DIVISAO_INTEIRA:
case delegua_1.default.DIVISAO_INTEIRA_IGUAL:
this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
if (this.lancarErroPorDivisaoPorZero && valorDireito === 0) {
throw new excecoes_1.ErroEmTempoDeExecucao(expressao.operador, 'Divisão por zero não é permitida.', expressao.operador.linha);
}
// Retorna BigInt se qualquer operando for BigInt
if (typeof valorEsquerdo === 'bigint' || typeof valorDireito === 'bigint') {
const esq = typeof valorEsquerdo === 'bigint'
? valorEsquerdo
: BigInt(Math.floor(Number(valorEsquerdo)));
const dir = typeof valorDireito === 'bigint'
? valorDireito
: BigInt(Math.floor(Number(valorDireito)));
return esq / dir; // Trunca automaticamente
}
return Math.floor(Number(valorEsquerdo) / Number(valorDireito));
case delegua_1.default.MULTIPLICACAO:
case delegua_1.default.MULTIPLICACAO_IGUAL:
// Auto-promove para BigInt se qualquer operando for BigInt (e não for texto)
if ((typeof valorEsquerdo === 'bigint' || typeof valorDireito === 'bigint') &&
tipoEsquerdo !== delegua_2.default.TEXTO &&
tipoDireito !== delegua_2.default.TEXTO) {
const esq = typeof valorEsquerdo === 'bigint'
? valorEsquerdo
: BigInt(Math.floor(Number(valorEsquerdo)));
const dir = typeof valorDireito === 'bigint'
? valorDireito
: BigInt(Math.floor(Number(valorDireito)));
return esq * dir;
}
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) {
throw new excecoes_1.ErroEmTempoDeExecucao(expressao.operador, 'Não é possível multiplicar dois textos.', expressao.linha);
}
const valorTexto = tipoEsquerdo === delegua_2.default.TEXTO ? valorEsquerdo : valorDireito;
const valorQuantidade = tipoEsquerdo === delegua_2.default.TEXTO ? valorDireito : valorEsquerdo;
if (typeof valorQuantidade !== 'number') {
throw new excecoes_1.ErroEmTempoDeExecucao(expressao.operador, 'Para multiplicar um texto, o outro operando deve ser um número.', expressao.linha);
}
const textoParaNumero = Number(valorTexto);
if (!isNaN(textoParaNumero)) {
return textoParaNumero * valorQuantidade;
}
if (!Number.isInteger(valorQuantidade)) {
throw new excecoes_1.ErroEmTempoDeExecucao(expressao.operador, 'A multiplicação de texto exige um número inteiro.', expressao.linha);
}
if (valorQuantidade < 0) {
throw new excecoes_1.ErroEmTempoDeExecucao(expressao.operador, 'Não é possível multiplicar texto por número negativo.', expressao.linha);
}
return valorTexto.repeat(valorQuantidade);
}
return Number(valorEsquerdo) * Number(valorDireito);
case delegua_1.default.MODULO:
case delegua_1.default.MODULO_IGUAL:
// Se o operando esquerdo é uma string, usar formatação de string
if (tipoEsquerdo === delegua_2.default.TEXTO ||
typeof valorEsquerdo === 'string') {
return this.formatarStringComOperadorPorcentagem(String(valorEsquerdo), direita, // Passar 'direita' ao invés de 'valorDireito' para preservar arrays de tuplas
expressao.operador);
}
// Caso contrário, operação matemática normal
this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
return Number(valorEsquerdo) % Number(valorDireito);
case delegua_1.default.BIT_AND:
if (typeof valorEsquerdo === 'boolean' && typeof valorDireito === 'boolean') {
return valorEsquerdo && valorDireito;
}
this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
// Auto-promove para BigInt se qualquer operando for BigInt
if (typeof valorEsquerdo === 'bigint' || typeof valorDireito === 'bigint') {
const esq = typeof valorEsquerdo === 'bigint'
? valorEsquerdo
: BigInt(Math.floor(Number(valorEsquerdo)));
const dir = typeof valorDireito === 'bigint'
? valorDireito
: BigInt(Math.floor(Number(valorDireito)));
return esq & dir;
}
return Number(valorEsquerdo) & Number(valorDireito);
case delegua_1.default.CIRCUMFLEXO:
if (typeof valorEsquerdo === 'boolean' && typeof valorDireito === 'boolean') {
return valorEsquerdo !== valorDireito;
}
this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
// Auto-promove para BigInt se qualquer operando for BigInt
if (typeof valorEsquerdo === 'bigint' || typeof valorDireito === 'bigint') {
const esq = typeof valorEsquerdo === 'bigint'
? valorEsquerdo
: BigInt(Math.floor(Number(valorEsquerdo)));
const dir = typeof valorDireito === 'bigint'
? valorDireito
: BigInt(Math.floor(Number(valorDireito)));
return esq ^ dir;
}
return Number(valorEsquerdo) ^ Number(valorDireito);
case delegua_1.default.BIT_OR:
if (typeof valorEsquerdo === 'boolean' && typeof valorDireito === 'boolean') {
return valorEsquerdo || valorDireito;
}
this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
// Auto-promove para BigInt se qualquer operando for BigInt
if (typeof valorEsquerdo === 'bigint' || typeof valorDireito === 'bigint') {
const esq = typeof valorEsquerdo === 'bigint'
? valorEsquerdo
: BigInt(Math.floor(Number(valorEsquerdo)));
const dir = typeof valorDireito === 'bigint'
? valorDireito
: BigInt(Math.floor(Number(valorDireito)));
return esq | dir;
}
return Number(valorEsquerdo) | Number(valorDireito);
case delegua_1.default.MENOR_MENOR:
this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
// Auto-promove para BigInt (interno para longo) se qualquer operando for BigInt ou se deslocamento >= 32
const tamanhoDeslocamentoEsquerda = Number(valorDireito);
if (typeof valorEsquerdo === 'bigint' ||
typeof valorDireito === 'bigint' ||
tamanhoDeslocamentoEsquerda >= 32) {
const esq = typeof valorEsquerdo === 'bigint'
? valorEsquerdo
: BigInt(Math.floor(Number(valorEsquerdo)));
const dir = typeof valorDireito === 'bigint'
? valorDireito
: BigInt(Math.floor(Number(valorDireito)));
return esq << dir;
}
return Number(valorEsquerdo) << Number(valorDireito);
case delegua_1.default.MAIOR_MAIOR:
this.verificarOperandosNumeros(expressao.operador, esquerda, direita);
// Auto-promove para BigInt (interno para longo) se qualquer operando for BigInt ou se deslocamento >= 32
const tamanhoDeslocamentoDireita = Number(valorDireito);
if (typeof valorEsquerdo === 'bigint' ||
typeof valorDireito === 'bigint' ||
tamanhoDeslocamentoDireita >= 32) {
const esq = typeof valorEsquerdo === 'bigint'
? valorEsquerdo
: BigInt(Math.floor(Number(valorEsquerdo)));
const dir = typeof valorDireito === 'bigint'
? valorDireito
: BigInt(Math.floor(Number(valorDireito)));
return esq >> dir;
}
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} metodoPrimitivaChamado O método da primitiva chamado.
* @returns O resultado da chamada do método da primitiva.
*/
async chamarMetodoPrimitiva(expressao, metodoPrimitivaChamado) {
const argumentosResolvidos = [];
for (const argumento of expressao.argumentos) {
const valorResolvido = await this.avaliar(argumento);
argumentosResolvidos.push(this.resolverValor(valorResolvido));
}
return await metodoPrimitivaChamado.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;
let valor = await this.avaliar(variavelArgumento);
argumentos.push({
nome: nomeArgumento,
valor,
});
}
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 {
let 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));
}
if (variavelEntidadeChamada.hasOwnProperty('valorRetornado')) {
variavelEntidadeChamada = variavelEntidadeChamada.valorRetornado;
}
let entidadeChamada = this.resolverValor(variavelEntidadeChamada);
// Funções anônimas
if (entidadeChamada instanceof construtos_1.FuncaoConstruto) {
entidadeChamada = new estruturas_1.DeleguaFuncao(null, entidadeChamada);
}
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.
// Para métodos polimórficos e classes com construtores polimórficos,
// a quantidade original de argumentos é necessária para o despacho
// correto da sobrecarga.
const ehPolimorfico = entidadeChamada instanceof estruturas_1.MetodoPolimorfico ||
(entidadeChamada instanceof estruturas_1.DescritorTipoClasse &&
entidadeChamada.encontrarMetodo('construtor') instanceof estruturas_1.MetodoPolimorfico);
if (!ehPolimorfico && 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 && this.resolverValor(a.valor)), expressao.entidadeChamada.simbolo // TODO: O que exatamente pode ser aqui?
);
}
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' ||
entidadeChamada.constructor.name === 'MetodoPolimorfico') {
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) {
// TODO: Qual o tipo certo aqui?
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 = this.resolverValor(await this.avaliar(expressao.indice));
}
switch (expressao.alvo.constructor) {
case construtos_1.Variavel:
const alvoVariavel = expressao.alvo;
this.pilhaEscoposExecucao.atribuirVariavel(alvoVariavel.simbolo, valorResolvido, indice);
break;
case construtos_1.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 === estruturas_1.ObjetoDeleguaClasse) {