UNPKG

@designliquido/delegua

Version:

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

976 lines (975 loc) 114 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 = 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) {