UNPKG

@designliquido/delegua

Version:

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

981 lines 111 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 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 = undefined, funcaoDeRetornoMesmaLinha = undefined) { this.resultadoInterpretador = []; this.linhaDeclaracaoAtual = -1; this.hashArquivoDeclaracaoAtual = -1; this.classeAtualEmExecucao = null; this.funcaoDeRetorno = undefined; this.funcaoDeRetornoMesmaLinha = undefined; this.interfaceDeEntrada = undefined; // Originalmente é `readline.Interface` this.interfaceEntradaSaida = undefined; this.funcaoVerificarIteracao = undefined; 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; const _process = globalThis.process; this.funcaoDeRetornoMesmaLinha = funcaoDeRetornoMesmaLinha || (_process?.stdout?.write ? _process.stdout.write.bind(_process.stdout) : (texto) => console.log(texto)); 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) => { const _setImmediate = globalThis.setImmediate; if (_setImmediate) { _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 {ConstrutoInterface} 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.Agrupamento: return this.resolverNomeObjectoAcessado(objetoAcessado.expressao); case construtos_1.Constante: return objetoAcessado.simbolo.lexema; case construtos_1.AcessoMetodo: case construtos_1.AcessoMetodoOuPropriedade: case construtos_1.AcessoIndiceVariavel: case construtos_1.Binario: case construtos_1.Dicionario: case construtos_1.Leia: 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(_) { throw new Error('Método não implementado.'); } async visitarExpressaoAcessoElementoMatriz(_) { 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(_) { throw new Error('Método não implementado.'); } async visitarExpressaoFalhar(expressao) { const textoFalha = expressao.explicacao.valor ?? (await this.avaliar(expressao.explicacao)).valor; throw new excecoes_1.ErroEmTempoDeExecucao(expressao.simbolo, textoFalha, expressao.linha); } async visitarExpressaoFimPara(_) { 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'); } */ if (expressao.hashArquivo >= 0 && expressao.linha >= 0) { this.registrarExpressao?.(expressao.hashArquivo, expressao.linha); } 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) { let valor = 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); if (!variaveis) { return []; } return await Promise.all(variaveis.map(async (s) => { 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: erro.linha ?? linha, hashArquivo: erro.hashArquivo ?? -1, }); } declaracoes = resultadoMicroAvaliadorSintatico.declaracoes; } catch (erroAvaliador) { this.erros.push({ erroInterno: erroAvaliador, linha: erroAvaliador.linha ?? linha, hashArquivo: erroAvaliador.hashArquivo ?? -1, }); } let valor = declaracoes.length > 0 ? await this.avaliar(declaracoes[0]) : ''; const instancia = valor instanceof estruturas_1.ObjetoDeleguaClasse ? valor : 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 !== null && avaliacaoAgrupamento.declaracao) { 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: if (typeof valor === 'bigint') { return ~valor; } this.verificarOperandoNumero(expressao.operador, 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; if (expressao.operando instanceof construtos_1.Variavel) { 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; if (expressao.operando instanceof construtos_1.Variavel) { 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); } converterParaBigInt(valor) { return typeof valor === 'bigint' ? valor : BigInt(Math.floor(Number(valor))); } 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 metodoASerChamado = 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 metodoASerChamado.chamar(this, [ { nome: null, valor: argumentoOperador }, ]); } } const tipoEsquerdo = esquerda?.hasOwnProperty('tipo') ? esquerda.tipo : (0, inferenciador_1.inferirTipoVariavel)(esquerda); const tipoDireito = 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') { return (this.converterParaBigInt(valorEsquerdo) ** this.converterParaBigInt(valorDireito)); } 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') { return (this.converterParaBigInt(valorEsquerdo) - this.converterParaBigInt(valorDireito)); } 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); } // Se ambos os operandos são dicionários, mescla-os. // Em caso de chaves duplicadas, valores à direita sobrescrevem os da esquerda. if (valorEsquerdo && valorDireito && !Array.isArray(valorEsquerdo) && !Array.isArray(valorDireito) && valorEsquerdo.constructor === Object && valorDireito.constructor === Object) { return Object.assign({}, valorEsquerdo, valorDireito); } // Auto-promove para BigInt se qualquer operando for BigInt if (typeof valorEsquerdo === 'bigint' || typeof valorDireito === 'bigint') { const valorResolvidoEsquerdo = typeof valorEsquerdo === 'bigint' ? valorEsquerdo : BigInt(Math.floor(Number(valorEsquerdo))); const valorResolvidoDireito = typeof valorDireito === 'bigint' ? valorDireito : BigInt(Math.floor(Number(valorDireito))); return valorResolvidoEsquerdo + valorResolvidoDireito; } if (this.tiposNumericos.includes(tipoEsquerdo) && this.tiposNumericos.includes(tipoDireito)) { return Number(valorEsquerdo) + Number(valorDireito); } if (valorEsquerdo === null || valorDireito === null) { return this.paraTexto(valorEsquerdo) + this.paraTexto(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); 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 valorResolvidoEsquerdo = typeof valorEsquerdo === 'bigint' ? valorEsquerdo : BigInt(Math.floor(Number(valorEsquerdo))); const valorResolvidoDireito = typeof valorDireito === 'bigint' ? valorDireito : BigInt(Math.floor(Number(valorDireito))); return valorResolvidoEsquerdo / valorResolvidoDireito; // 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 valorResolvidoEsquerdo = typeof valorEsquerdo === 'bigint' ? valorEsquerdo : BigInt(Math.floor(Number(valorEsquerdo))); const valorResolvidoDireito = typeof valorDireito === 'bigint' ? valorDireito : BigInt(Math.floor(Number(valorDireito))); return valorResolvidoEsquerdo * valorResolvidoDireito; } 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') { return (this.converterParaBigInt(valorEsquerdo) & this.converterParaBigInt(valorDireito)); } 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') { return (this.converterParaBigInt(valorEsquerdo) ^ this.converterParaBigInt(valorDireito)); } 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') { return (this.converterParaBigInt(valorEsquerdo) | this.converterParaBigInt(valorDireito)); } 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 if (typeof valorEsquerdo === 'bigint' || typeof valorDireito === 'bigint' || Number(valorDireito) >= 32) { return (this.converterParaBigInt(valorEsquerdo) << this.converterParaBigInt(valorDireito)); } 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 if (typeof valorEsquerdo === 'bigint' || typeof valorDireito === 'bigint' || Number(valorDireito) >= 32) { return (this.converterParaBigInt(valorEsquerdo) >> this.converterParaBigInt(valorDireito)); } 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 ?? null, 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 || variavelEntidadeChamada === undefined) { return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.parentese, 'Chamada de função ou método inexistente: ' + String(expressao.entidadeChamada), expressao.linha)); } if (Object.prototype.hasOwnProperty.call(variavelEntidadeChamada, '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 (entidadeChamada instanceof estruturas_1.DeleguaFuncao && entidadeChamada.declaracao) { // Pega os parâmetros da declaração e filtra apenas os obrigatórios // (ignora os que têm valor padrão ou são rest parameters) const parametros = entidadeChamada.declaracao.parametros || []; const parametrosObrigatorios = parametros.filter((p) => !p.valorPadrao && p.abrangencia !== 'multiplo').length; if (argumentos.length < parametrosObrigatorios) { const nomeFuncao = entidadeChamada.nome || 'anônima'; return Promise.reject(new excecoes_1.ErroEmTempoDeExecucao(expressao.parentese, `A função '${nomeFuncao}' esperava no mínimo ${parametrosObrigatorios} argumento(s), mas recebeu ${argumentos.length}.`, expressao.linha)); } } 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 await entidadeChamada.chamar(this, argumentos.map((a) => a && this.resolverValor(a.valor)), expressao.entidadeChamada.simbolo // TODO: O que exatamente pode ser aqui? ); } catch (erro) { if (this.emDeclaracaoTente) { return Promise.reject(erro); } this.erros.push({ erroInterno: erro, linha: expressao.linha, hashArquivo: expressao.hashArquivo, }); return; } } // 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 === estruturas_1.DeleguaFuncao || entidadeChamada.constructor === estruturas_1.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.AcessoMetodoOuPropriedade: // Nunca será método aqui: apenas propriedade. const alvoPr