UNPKG

@designliquido/delegua

Version:

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

869 lines 45.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.avaliar = avaliar; exports.visitarExpressaoReferenciaFuncao = visitarExpressaoReferenciaFuncao; exports.visitarExpressaoArgumentoReferenciaFuncao = visitarExpressaoArgumentoReferenciaFuncao; exports.visitarExpressaoDeChamada = visitarExpressaoDeChamada; exports.visitarDeclaracaoEnquanto = visitarDeclaracaoEnquanto; exports.visitarDeclaracaoEscreva = visitarDeclaracaoEscreva; exports.visitarDeclaracaoPara = visitarDeclaracaoPara; exports.visitarDeclaracaoFazer = visitarDeclaracaoFazer; exports.visitarDeclaracaoTente = visitarDeclaracaoTente; exports.visitarExpressaoRetornar = visitarExpressaoRetornar; exports.visitarExpressaoBinaria = visitarExpressaoBinaria; exports.executarBloco = executarBloco; exports.executarUltimoEscopoComandoContinuar = executarUltimoEscopoComandoContinuar; exports.instrucaoContinuarInterpretacao = instrucaoContinuarInterpretacao; exports.instrucaoPasso = instrucaoPasso; exports.abrirNovoBlocoEscopo = abrirNovoBlocoEscopo; exports.executarUltimoEscopo = executarUltimoEscopo; exports.obterVariavel = obterVariavel; const lodash_1 = __importDefault(require("lodash")); const construtos_1 = require("../../construtos"); const declaracoes_1 = require("../../declaracoes"); const quebras_1 = require("../../quebras"); const inferenciador_1 = require("../../inferenciador"); const espaco_memoria_1 = require("../espaco-memoria"); const delegua_1 = __importDefault(require("../../tipos-de-simbolos/delegua")); const delegua_2 = __importDefault(require("../../tipos-de-dados/delegua")); const ITERACOES_PARA_CEDER_CONTROLE = 1000; async function cederControle(iteracoes) { if (iteracoes % ITERACOES_PARA_CEDER_CONTROLE === 0) { await new Promise((resolve) => { if (typeof setImmediate !== 'undefined') { setImmediate(resolve); } else { setTimeout(resolve, 0); } }); } } async function avaliarArgumentosEscreva(interpretador, argumentos) { let formatoTexto = ''; for (const argumento of argumentos) { const resultadoAvaliacao = await interpretador.avaliar(argumento); let valor = interpretador.resolverValor(resultadoAvaliacao); formatoTexto += `${interpretador.paraTexto(valor)} `; } return formatoTexto.trimEnd(); } /** * Resolve problema de literais que tenham vírgulas, e confundem a resolução de chamadas. * @param valor O valor do argumento, que pode ser um literal com virgulas. * @returns Uma string com vírgulas escapadas. */ function escaparVirgulas(valor) { return String(valor).replace(/,/i, ','); } /** * Gera um identificador para resolução de chamadas. * Usado para não chamar funções repetidamente quando usando instruções * de passo, como "próximo" ou "adentrar-escopo". * @param expressao A expressão de chamada. * @returns Uma `Promise` que resolve como `string`. */ async function gerarIdResolucaoChamada(interpretador, expressao) { const argumentosResolvidos = []; if (expressao.argumentos && expressao.argumentos.length > 0) { for (let argumento of expressao.argumentos) { if (argumento instanceof construtos_1.Leia) { argumentosResolvidos.push(`leia_${argumento.id}`); } else { argumentosResolvidos.push(await interpretador.avaliar(argumento)); } } } return argumentosResolvidos.reduce((acumulador, argumento) => (acumulador += `,${escaparVirgulas(argumento.hasOwnProperty('valor') ? argumento.valor : argumento)}`), expressao.id); } /** * Para fins de depuração, verifica se há ponto de parada no mesmo pragma da declaração. * @param declaracao A declaração a ser executada. * @returns `true` quando execução deve parar. `false` caso contrário. */ function verificarPontoParada(interpretador, declaracao) { const buscaPontoParada = interpretador.pontosParada.filter((p) => p.hashArquivo === declaracao.hashArquivo && p.linha === declaracao.linha); if (buscaPontoParada.length > 0) { console.log(`Ponto de parada encontrado. Linha: ${declaracao.linha}.`); return true; } return false; } /** * Quando um construto ou declaração possui id, significa que o interpretador * deve resolver a avaliação e guardar seu valor até o final do escopo. * Isso serve para quando a linguagem está em modo de depuração, e o contexto * da execução deixa de existir com um ponto de parada, por exemplo. * @param expressao A expressão a ser avaliada. * @returns O resultado da avaliação. */ async function avaliar(interpretador, expressao) { if (expressao.hasOwnProperty('id')) { const escopoAtual = interpretador.pilhaEscoposExecucao.topoDaPilha(); const idChamadaComArgumentos = await gerarIdResolucaoChamada(interpretador, expressao); if (escopoAtual.espacoMemoria.resolucoesChamadas.hasOwnProperty(idChamadaComArgumentos)) { return escopoAtual.espacoMemoria.resolucoesChamadas[idChamadaComArgumentos]; } } return await expressao.aceitar(interpretador); } async function visitarExpressaoReferenciaFuncao(interpretador, visitarExpressaoReferenciaFuncaoAncestral, expressao) { return await visitarExpressaoReferenciaFuncaoAncestral(expressao); } async function visitarExpressaoArgumentoReferenciaFuncao(interpretador, visitarExpressaoArgumentoReferenciaFuncaoAncestral, expressao) { return await visitarExpressaoArgumentoReferenciaFuncaoAncestral(expressao); } async function visitarExpressaoDeChamada(interpretador, visitarExpressaoDeChamadaAncestral, expressao) { const _idChamadaComArgumentos = await gerarIdResolucaoChamada(interpretador, expressao); // Usado na abertura do bloco de escopo da chamada. interpretador.idChamadaAtual = _idChamadaComArgumentos; interpretador.executandoChamada = true; interpretador.proximoEscopo = 'funcao'; const retorno = await visitarExpressaoDeChamadaAncestral(expressao); interpretador.executandoChamada = false; const escopoAtual = interpretador.pilhaEscoposExecucao.topoDaPilha(); escopoAtual.espacoMemoria.resolucoesChamadas[_idChamadaComArgumentos] = retorno; return retorno; } async function visitarDeclaracaoEnquanto(interpretador, declaracao) { const escopoAtual = interpretador.pilhaEscoposExecucao.topoDaPilha(); switch (interpretador.comando) { case 'proximo': if (interpretador.eVerdadeiro(await interpretador.avaliar(declaracao.condicao))) { escopoAtual.emLacoRepeticao = true; return interpretador.executarBloco(declaracao.corpo.declaracoes); } escopoAtual.emLacoRepeticao = false; return null; default: let retornoExecucao; let iteracoes = 0; while (!(retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.Quebra) && !interpretador.pontoDeParadaAtivo && interpretador.comando !== 'pausar' && interpretador.eVerdadeiro(await interpretador.avaliar(declaracao.condicao))) { escopoAtual.emLacoRepeticao = true; try { await cederControle(++iteracoes); retornoExecucao = await interpretador.executar(declaracao.corpo); if (retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.SustarQuebra) { return null; } if (retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.ContinuarQuebra) { retornoExecucao = null; } } catch (erro) { return Promise.reject(erro); } } escopoAtual.emLacoRepeticao = false; return retornoExecucao; } } /** * Execução de uma escrita na saída configurada, que pode ser `console` (padrão) ou * alguma função para escrever numa página Web. * Se ponto de parada foi ativado durante a avaliação de argumentos, não escreve. * @param declaracao A declaração. * @returns Sempre nulo, por convenção de visita. */ async function visitarDeclaracaoEscreva(interpretador, declaracao) { try { const formatoTexto = await avaliarArgumentosEscreva(interpretador, declaracao.argumentos); if (interpretador.pontoDeParadaAtivo) { return null; } interpretador.funcaoDeRetorno(formatoTexto); return null; } catch (erro) { interpretador.erros.push({ erroInterno: erro, linha: declaracao.linha, hashArquivo: declaracao.hashArquivo, }); } } async function visitarDeclaracaoPara(interpretador, declaracao) { // Aqui precisamos clonar a declaração porque modificamos // algumas propriedades que indicam o estado da execução dela. // Por exemplo, se chamamos uma função que tem dentro dela um bloco Para, // cada execução do bloco precisa de uma inicialização diferente. const cloneDeclaracao = lodash_1.default.cloneDeep(declaracao); const corpoExecucao = cloneDeclaracao.corpo; const declaracaoInicializador = Array.isArray(cloneDeclaracao.inicializador) ? declaracao.inicializador[0] : declaracao.inicializador; if (declaracaoInicializador !== null) { await interpretador.avaliar(declaracaoInicializador); // O incremento vai ao final do bloco de escopo. if (cloneDeclaracao.incrementar !== null) { corpoExecucao.declaracoes.push(new declaracoes_1.Expressao(cloneDeclaracao.incrementar)); } } cloneDeclaracao.inicializada = true; const escopoAtual = interpretador.pilhaEscoposExecucao.topoDaPilha(); switch (interpretador.comando) { case 'proximo': if (cloneDeclaracao.condicao !== null && interpretador.eVerdadeiro(await interpretador.avaliar(cloneDeclaracao.condicao))) { escopoAtual.emLacoRepeticao = true; const resultadoBloco = interpretador.executarBloco(corpoExecucao.declaracoes); return resultadoBloco; } escopoAtual.emLacoRepeticao = false; return null; default: let retornoExecucao; let iteracoes = 0; while (!(retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.Quebra) && !interpretador.pontoDeParadaAtivo && interpretador.comando !== 'pausar') { if (cloneDeclaracao.condicao !== null && !interpretador.eVerdadeiro(await interpretador.avaliar(cloneDeclaracao.condicao))) { break; } try { await cederControle(++iteracoes); retornoExecucao = await interpretador.executar(corpoExecucao); if (retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.SustarQuebra) { return null; } if (retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.ContinuarQuebra) { retornoExecucao = null; } } catch (erro) { return Promise.reject(erro); } } // escopoAtual.emLacoRepeticao = false; return retornoExecucao; } } async function visitarDeclaracaoFazer(interpretador, declaracao) { let retornoExecucao; let iteracoes = 0; do { try { await cederControle(++iteracoes); retornoExecucao = await interpretador.executar(declaracao.caminhoFazer); if (retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.SustarQuebra) { return null; } if (retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.ContinuarQuebra) { retornoExecucao = null; } } catch (erro) { return Promise.reject(erro); } } while (!(retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.Quebra) && !interpretador.pontoDeParadaAtivo && interpretador.comando !== 'pausar' && interpretador.eVerdadeiro(await interpretador.avaliar(declaracao.condicaoEnquanto))); return retornoExecucao; } /** * Implementação de try-catch-finally para modo de depuração. * Garante que o bloco finally só é executado após o bloco try ser completado. * Em modo de passo, detecta quando um novo escopo foi criado e está incompleto, * evitando que o finally seja empilhado prematuramente. * @param interpretador O interpretador com depuração. * @param declaracao A declaração tente-pegue-finalmente. * @returns O valor retornado pela execução. */ async function visitarDeclaracaoTente(interpretador, declaracao) { let valorRetorno; interpretador.emDeclaracaoTente = true; // Captura o número de escopos antes de executar o bloco try const escoposAntes = interpretador.pilhaEscoposExecucao.elementos(); try { try { valorRetorno = await interpretador.executarBloco(declaracao.caminhoTente); } catch (erro) { if (declaracao.caminhoPegue !== null) { if (Array.isArray(declaracao.caminhoPegue)) { valorRetorno = await interpretador.executarBloco(declaracao.caminhoPegue); } else { const literalErro = new construtos_1.Literal(declaracao.hashArquivo, Number(declaracao.linha), erro.mensagem); const chamadaPegue = new construtos_1.Chamada(declaracao.caminhoPegue.hashArquivo, declaracao.caminhoPegue, [literalErro]); valorRetorno = await chamadaPegue.aceitar(interpretador); } } } } finally { // Verifica se um novo escopo foi criado e ainda está ativo // Se sim, NÃO executa o finally ainda (o escopo try não foi completado) const escoposDepois = interpretador.pilhaEscoposExecucao.elementos(); const novoEscopoCriado = escoposDepois > escoposAntes; // Só executa finally se: // 1. Existe um bloco finally // 2. Não há um novo escopo criado OU não estamos em modo de passo/adentrar if (declaracao.caminhoFinalmente !== null && (!novoEscopoCriado || (interpretador.comando !== 'proximo' && interpretador.comando !== 'adentrarEscopo'))) { valorRetorno = await interpretador.executarBloco(declaracao.caminhoFinalmente); } interpretador.emDeclaracaoTente = false; } return valorRetorno; } /** * Ao executar um retorno, manter o valor retornado no Interpretador para * uso por linhas que foram executadas com o comando `próximo` do depurador. * @param declaracao Uma declaracao Retorna * @returns O resultado da execução da visita. */ async function visitarExpressaoRetornar(interpretador, visitarExpressaoRetornarAncestral, declaracao) { // Captura o escopo atual ANTES de avaliar a expressão, // pois a avaliação pode abrir novos escopos const escopoAtual = interpretador.pilhaEscoposExecucao.topoDaPilha(); const escoposAntes = interpretador.pilhaEscoposExecucao.elementos(); const retorno = await visitarExpressaoRetornarAncestral(declaracao); // Verifica se novos escopos foram criados durante a avaliação const escoposDepois = interpretador.pilhaEscoposExecucao.elementos(); const novoEscopoFoiCriado = escoposDepois > escoposAntes; // Se o retorno é null ou RetornoQuebra com valor null (porque pausamos durante avaliação de expressão, // como ao entrar em uma função em modo adentrarEscopo), não marcar como finalizado ainda const valorRetorno = retorno && retorno.hasOwnProperty('valor') ? retorno.valor : retorno; // Em modo adentrarEscopo, se um novo escopo foi criado (entramos em uma função aninhada), // NÃO marcar o escopo atual como finalizado ainda if (interpretador.comando === 'adentrarEscopo' && novoEscopoFoiCriado) { return retorno; } // O escopo atual é marcado como finalizado, para notificar a // instrução de que deve ser descartado. escopoAtual.finalizado = true; // Acha o primeiro escopo de função. const escopoFuncao = interpretador.pilhaEscoposExecucao.obterEscopoPorTipo('funcao'); if (escopoFuncao === undefined) { return Promise.reject('retorna() chamado fora de execução de função.'); } if (escopoFuncao.idChamada !== undefined) { escopoAtual.espacoMemoria.resolucoesChamadas[escopoFuncao.idChamada] = retorno && retorno.hasOwnProperty('valor') ? retorno.valor : retorno; } return retorno; } // Contador global para gerar IDs únicos de expressões binárias let contadorBinarioDebug = 0; /** * Gera um identificador único para uma expressão binária. * Usado para armazenar estado de avaliação parcial durante step-into. * Cada expressão binária recebe um ID único na primeira vez que é encontrada, * evitando colisões entre expressões na mesma linha. * @param expressao A expressão binária. * @returns Um identificador único para esta instância de expressão. */ function gerarIdExpressaoBinaria(expressao) { // Se a expressão já tem um ID de depuração, reutiliza if (!expressao._debugId) { // Gera um ID único usando um contador incremental expressao._debugId = `binario_${++contadorBinarioDebug}`; } return expressao._debugId; } /** * Sobrescreve a visita de expressão binária para permitir step-into em cada lado da expressão. * Quando em modo de depuração com step-into, avalia o lado esquerdo primeiro e pausa. * Na próxima execução, avalia o lado direito e completa a operação. * @param interpretador O interpretador com depuração. * @param visitarExpressaoBinariaAncestral Método ancestral para executar a lógica da operação. * @param expressao A expressão binária. * @returns O resultado da operação binária. */ async function visitarExpressaoBinaria(interpretador, expressao) { const escopoAtual = interpretador.pilhaEscoposExecucao.topoDaPilha(); const idExpressao = gerarIdExpressaoBinaria(expressao); const chaveEsquerda = `${idExpressao}_esquerda`; // Verifica se já avaliamos o lado esquerdo if (escopoAtual.espacoMemoria.resolucoesChamadas.hasOwnProperty(chaveEsquerda)) { // Lado esquerdo já foi avaliado, agora avaliar o lado direito const esquerda = escopoAtual.espacoMemoria.resolucoesChamadas[chaveEsquerda]; // Avalia o lado direito const direita = await interpretador.avaliar(expressao.direita); // Limpa o cache do lado esquerdo pois já usamos delete escopoAtual.espacoMemoria.resolucoesChamadas[chaveEsquerda]; // Resolve os valores const valorEsquerdo = interpretador.resolverValor(esquerda); const valorDireito = interpretador.resolverValor(direita); const tipoEsquerdo = (esquerda === null || esquerda === void 0 ? void 0 : esquerda.hasOwnProperty('tipo')) ? esquerda.tipo : (0, inferenciador_1.inferirTipoVariavel)(esquerda); const tipoDireito = (direita === null || direita === void 0 ? void 0 : direita.hasOwnProperty('tipo')) ? direita.tipo : (0, inferenciador_1.inferirTipoVariavel)(direita); // Executa a operação binária usando a lógica ancestral // Criamos um objeto temporário com os valores já avaliados para evitar re-avaliação const expressaoTemp = Object.assign(Object.assign({}, expressao), { esquerda: { valor: valorEsquerdo, tipo: tipoEsquerdo }, direita: { valor: valorDireito, tipo: tipoDireito } }); // Não podemos chamar o ancestral diretamente porque ele vai tentar avaliar novamente // Em vez disso, precisamos executar a operação diretamente return await executarOperacaoBinaria(interpretador, expressao, esquerda, direita, valorEsquerdo, valorDireito, tipoEsquerdo, tipoDireito); } else { // Rastreia o número de escopos antes de avaliar o lado esquerdo const escoposAntes = interpretador.pilhaEscoposExecucao.elementos(); // Primeira avaliação - avaliar apenas o lado esquerdo const esquerda = await interpretador.avaliar(expressao.esquerda); // Armazena o resultado do lado esquerdo escopoAtual.espacoMemoria.resolucoesChamadas[chaveEsquerda] = esquerda; // Verifica se um novo escopo foi criado durante a avaliação do lado esquerdo const escoposDepois = interpretador.pilhaEscoposExecucao.elementos(); const novoEscopoCriado = escoposDepois > escoposAntes; // Se estamos em modo adentrarEscopo e um novo escopo foi aberto, // a execução deve pausar aqui para permitir que o usuário adentre na função do lado esquerdo // A próxima chamada a visitarExpressaoBinaria irá continuar do lado direito if (interpretador.comando === 'adentrarEscopo' && novoEscopoCriado) { // Retorna um valor temporário, a avaliação será completada na próxima execução return null; } // Verifica se devemos continuar ou pausar // Se pontoDeParadaAtivo foi ativado durante a avaliação do lado esquerdo, devemos retornar if (interpretador.pontoDeParadaAtivo) { // Retorna um valor temporário, a avaliação será completada na próxima execução return null; } // Se não há pausa e não entramos em novo escopo, continuar com a avaliação do lado direito const direita = await interpretador.avaliar(expressao.direita); // Limpa o cache já que completamos a avaliação delete escopoAtual.espacoMemoria.resolucoesChamadas[chaveEsquerda]; const valorEsquerdo = interpretador.resolverValor(esquerda); const valorDireito = interpretador.resolverValor(direita); const tipoEsquerdo = (esquerda === null || esquerda === void 0 ? void 0 : esquerda.hasOwnProperty('tipo')) ? esquerda.tipo : (0, inferenciador_1.inferirTipoVariavel)(esquerda); const tipoDireito = (direita === null || direita === void 0 ? void 0 : direita.hasOwnProperty('tipo')) ? direita.tipo : (0, inferenciador_1.inferirTipoVariavel)(direita); return await executarOperacaoBinaria(interpretador, expressao, esquerda, direita, valorEsquerdo, valorDireito, tipoEsquerdo, tipoDireito); } } /** * Executa a operação binária após ambos os lados terem sido avaliados. * Contém a lógica de todas as operações binárias suportadas. */ async function executarOperacaoBinaria(interpretador, expressao, esquerda, direita, valorEsquerdo, valorDireito, tipoEsquerdo, tipoDireito) { switch (expressao.operador.tipo) { case delegua_1.default.EXPONENCIACAO: interpretador.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Math.pow(valorEsquerdo, valorDireito); case delegua_1.default.MAIOR: if (interpretador.tiposNumericos.includes(tipoEsquerdo) && interpretador.tiposNumericos.includes(tipoDireito)) { return Number(valorEsquerdo) > Number(valorDireito); } return String(valorEsquerdo) > String(valorDireito); case delegua_1.default.MAIOR_IGUAL: interpretador.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Number(valorEsquerdo) >= Number(valorDireito); case delegua_1.default.MENOR: if (interpretador.tiposNumericos.includes(tipoEsquerdo) && interpretador.tiposNumericos.includes(tipoDireito)) { return Number(valorEsquerdo) < Number(valorDireito); } return String(valorEsquerdo) < String(valorDireito); case delegua_1.default.MENOR_IGUAL: interpretador.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Number(valorEsquerdo) <= Number(valorDireito); case delegua_1.default.SUBTRACAO: case delegua_1.default.MENOS_IGUAL: interpretador.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Number(valorEsquerdo) - Number(valorDireito); case delegua_1.default.ADICAO: case delegua_1.default.MAIS_IGUAL: if (Array.isArray(valorEsquerdo) && Array.isArray(valorDireito)) { return valorEsquerdo.concat(valorDireito); } if (interpretador.tiposNumericos.includes(tipoEsquerdo) && interpretador.tiposNumericos.includes(tipoDireito)) { return Number(valorEsquerdo) + Number(valorDireito); } if (tipoEsquerdo === 'qualquer' || tipoDireito === 'qualquer') { return valorEsquerdo + valorDireito; } return interpretador.paraTexto(valorEsquerdo) + interpretador.paraTexto(valorDireito); case delegua_1.default.DIVISAO: case delegua_1.default.DIVISAO_IGUAL: interpretador.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Number(valorEsquerdo) / Number(valorDireito); case delegua_1.default.DIVISAO_INTEIRA: case delegua_1.default.DIVISAO_INTEIRA_IGUAL: interpretador.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Math.floor(Number(valorEsquerdo) / Number(valorDireito)); case delegua_1.default.MULTIPLICACAO: case delegua_1.default.MULTIPLICACAO_IGUAL: if (delegua_2.default && (tipoEsquerdo === delegua_2.default.TEXTO || tipoDireito === delegua_2.default.TEXTO)) { if (tipoEsquerdo === delegua_2.default.TEXTO && tipoDireito === delegua_2.default.TEXTO) { return Number(valorEsquerdo) * Number(valorDireito); } if (tipoEsquerdo === delegua_2.default.TEXTO) { return valorEsquerdo.repeat(Number(valorDireito)); } return valorDireito.repeat(Number(valorEsquerdo)); } return Number(valorEsquerdo) * Number(valorDireito); case delegua_1.default.MODULO: case delegua_1.default.MODULO_IGUAL: interpretador.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Number(valorEsquerdo) % Number(valorDireito); case delegua_1.default.BIT_AND: interpretador.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Number(valorEsquerdo) & Number(valorDireito); case delegua_1.default.CIRCUMFLEXO: interpretador.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Number(valorEsquerdo) ^ Number(valorDireito); case delegua_1.default.BIT_OR: interpretador.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Number(valorEsquerdo) | Number(valorDireito); case delegua_1.default.MENOR_MENOR: interpretador.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Number(valorEsquerdo) << Number(valorDireito); case delegua_1.default.MAIOR_MAIOR: interpretador.verificarOperandosNumeros(expressao.operador, esquerda, direita); return Number(valorEsquerdo) >> Number(valorDireito); case delegua_1.default.DIFERENTE: return !interpretador.eIgual(valorEsquerdo, valorDireito); case delegua_1.default.IGUAL_IGUAL: return interpretador.eIgual(valorEsquerdo, valorDireito); } } /** * Se bloco de execução já foi instanciado antes (por exemplo, quando há um ponto de parada e a * execução do código é retomada pelo depurador), retoma a execução do bloco do ponto em que havia parado. * Se bloco de execução ainda não foi instanciado, empilha declarações na pilha de escopos de execução, * cria um novo espacoMemoria e executa as declarações empilhadas. * Se depurador comandou uma instrução 'adentrar-escopo', execução do bloco não ocorre, mas * ponteiros de escopo e execução são atualizados. * @param declaracoes Um vetor de declaracoes a ser executado. * @param espacoMemoria O espacoMemoria de execução quando houver, como parâmetros, argumentos, etc. */ async function executarBloco(interpretador, declaracoes, espacoMemoria) { // Se o escopo atual não é o último. if (interpretador.escopoAtual < interpretador.pilhaEscoposExecucao.elementos() - 1) { interpretador.escopoAtual++; const proximoEscopo = interpretador.pilhaEscoposExecucao.naPosicao(interpretador.escopoAtual); let retornoExecucao; // Sempre executa a próxima instrução, mesmo que haja ponto de parada. retornoExecucao = await interpretador.executar(proximoEscopo.declaracoes[proximoEscopo.declaracaoAtual]); proximoEscopo.declaracaoAtual++; for (; !(retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.Quebra) && proximoEscopo.declaracaoAtual < proximoEscopo.declaracoes.length; proximoEscopo.declaracaoAtual++) { const declaracaoAtual = proximoEscopo.declaracoes[proximoEscopo.declaracaoAtual]; interpretador.linhaDeclaracaoAtual = declaracaoAtual.linha; interpretador.hashArquivoDeclaracaoAtual = declaracaoAtual.hashArquivo; interpretador.pontoDeParadaAtivo = verificarPontoParada(interpretador, declaracaoAtual); if (interpretador.pontoDeParadaAtivo) { interpretador.avisoPontoParadaAtivado(); break; } retornoExecucao = await interpretador.executar(declaracaoAtual); // Um ponto de parada ativo pode ter vindo de um escopo mais interno. // Por isso verificamos outra parada aqui para evitar que // `interpretador.declaracaoAtual` seja incrementado. if (interpretador.pontoDeParadaAtivo) { interpretador.avisoPontoParadaAtivado(); break; } } interpretador.pilhaEscoposExecucao.removerUltimo(); interpretador.escopoAtual--; return retornoExecucao; } else { abrirNovoBlocoEscopo(interpretador, declaracoes, espacoMemoria, interpretador.proximoEscopo || 'outro'); const ultimoEscopo = interpretador.pilhaEscoposExecucao.topoDaPilha(); if (interpretador.idChamadaAtual) { ultimoEscopo.idChamada = interpretador.idChamadaAtual; interpretador.idChamadaAtual = undefined; } interpretador.proximoEscopo = undefined; if (interpretador.comando !== 'adentrarEscopo') { return await executarUltimoEscopo(interpretador); } } } function descartarEscopoPorRetornoFuncao(interpretador) { let ultimoEscopo = interpretador.pilhaEscoposExecucao.topoDaPilha(); while (ultimoEscopo.tipo !== 'funcao') { interpretador.pilhaEscoposExecucao.removerUltimo(); const escopoAnterior = interpretador.pilhaEscoposExecucao.topoDaPilha(); escopoAnterior.espacoMemoria.resolucoesChamadas = Object.assign(escopoAnterior.espacoMemoria.resolucoesChamadas, ultimoEscopo.espacoMemoria.resolucoesChamadas); interpretador.escopoAtual--; ultimoEscopo = interpretador.pilhaEscoposExecucao.topoDaPilha(); } interpretador.pilhaEscoposExecucao.removerUltimo(); const escopoAnterior = interpretador.pilhaEscoposExecucao.topoDaPilha(); escopoAnterior.espacoMemoria.resolucoesChamadas = Object.assign(escopoAnterior.espacoMemoria.resolucoesChamadas, ultimoEscopo.espacoMemoria.resolucoesChamadas); interpretador.escopoAtual--; } function descartarTodosEscoposFinalizados(interpretador) { let i = interpretador.pilhaEscoposExecucao.pilha.length - 1; while (i > 0) { let ultimoEscopo = interpretador.pilhaEscoposExecucao.topoDaPilha(); if (ultimoEscopo.declaracaoAtual >= ultimoEscopo.declaracoes.length || ultimoEscopo.finalizado) { interpretador.pilhaEscoposExecucao.removerUltimo(); const escopoAnterior = interpretador.pilhaEscoposExecucao.topoDaPilha(); escopoAnterior.espacoMemoria.resolucoesChamadas = Object.assign(escopoAnterior.espacoMemoria.resolucoesChamadas, ultimoEscopo.espacoMemoria.resolucoesChamadas); interpretador.escopoAtual--; } else { break; } i--; } } async function executarUmPassoNoEscopo(interpretador) { const ultimoEscopo = interpretador.pilhaEscoposExecucao.topoDaPilha(); // Rastreia quantos escopos existem antes da execução para detectar entrada em novo escopo const escoposAntes = interpretador.pilhaEscoposExecucao.elementos(); let retornoExecucao; if (interpretador.passos > 0) { interpretador.passos--; // Executa a declaração atual const declaracaoAtual = ultimoEscopo.declaracoes[ultimoEscopo.declaracaoAtual]; retornoExecucao = await interpretador.executar(declaracaoAtual); // Verifica se entramos em um novo escopo durante a execução const escoposDepois = interpretador.pilhaEscoposExecucao.elementos(); const entroEmNovoEscopo = escoposDepois > escoposAntes; // Não incrementa se: // - Há um ponto de parada ativo (de escopo interno) // - Estamos em um laço de repetição (o laço gerencia a iteração) // - Entramos em um novo escopo (precisamos executar o novo escopo antes de avançar) if (!interpretador.pontoDeParadaAtivo && !ultimoEscopo.emLacoRepeticao && !entroEmNovoEscopo) { ultimoEscopo.declaracaoAtual++; } // Após executar e avançar, verifica se há ponto de parada na PRÓXIMA declaração if (!interpretador.pontoDeParadaAtivo && ultimoEscopo.declaracaoAtual < ultimoEscopo.declaracoes.length) { const proximaDeclaracao = ultimoEscopo.declaracoes[ultimoEscopo.declaracaoAtual]; interpretador.linhaDeclaracaoAtual = proximaDeclaracao.linha; interpretador.hashArquivoDeclaracaoAtual = proximaDeclaracao.hashArquivo; interpretador.pontoDeParadaAtivo = verificarPontoParada(interpretador, proximaDeclaracao); if (interpretador.pontoDeParadaAtivo) { interpretador.avisoPontoParadaAtivado(); } } if (ultimoEscopo.declaracaoAtual >= ultimoEscopo.declaracoes.length || ultimoEscopo.finalizado) { if (retornoExecucao instanceof quebras_1.RetornoQuebra) { descartarEscopoPorRetornoFuncao(interpretador); } else { descartarTodosEscoposFinalizados(interpretador); // Se escopos foram descartados, precisamos incrementar o contador do escopo pai // para avançar além da declaração que criou o escopo aninhado const escoposAposLimpeza = interpretador.pilhaEscoposExecucao.elementos(); if (escoposAposLimpeza < escoposAntes && escoposAposLimpeza > 0) { // Escopos foram removidos, avançar o contador do escopo pai const escopoAtual = interpretador.pilhaEscoposExecucao.topoDaPilha(); // Só incrementa se ainda há declarações para executar neste escopo // e não estamos em um laço de repetição if (!escopoAtual.emLacoRepeticao && escopoAtual.declaracaoAtual < escopoAtual.declaracoes.length) { escopoAtual.declaracaoAtual++; } } } } if (interpretador.pilhaEscoposExecucao.elementos() === 1) { interpretador.finalizacaoDaExecucao(); } } return retornoExecucao; } /** * Continua a interpretação parcial do último ponto em que parou. * Pode ser tanto o começo da execução inteira, ou pós comando do depurador * quando há um ponto de parada. * @param manterEspacoMemoria Se verdadeiro, junta elementos do último escopo com o escopo * imediatamente abaixo. * @param naoVerificarPrimeiraExecucao Booleano que pede ao Interpretador para não * verificar o ponto de parada na primeira execução. * Normalmente usado pelo Servidor de Depuração para continuar uma linha. * @returns Um objeto de retorno, com erros encontrados se houverem. */ async function executarUltimoEscopoComandoContinuar(interpretador, manterEspacoMemoria = false, naoVerificarPrimeiraExecucao = false) { const ultimoEscopo = interpretador.pilhaEscoposExecucao.topoDaPilha(); let retornoExecucao; try { for (; !(retornoExecucao && retornoExecucao.valorRetornado instanceof quebras_1.Quebra) && ultimoEscopo.declaracaoAtual < ultimoEscopo.declaracoes.length; ultimoEscopo.declaracaoAtual++) { const declaracaoAtual = ultimoEscopo.declaracoes[ultimoEscopo.declaracaoAtual]; interpretador.linhaDeclaracaoAtual = declaracaoAtual.linha; interpretador.hashArquivoDeclaracaoAtual = declaracaoAtual.hashArquivo; if (naoVerificarPrimeiraExecucao) { naoVerificarPrimeiraExecucao = false; } else { interpretador.pontoDeParadaAtivo = verificarPontoParada(interpretador, declaracaoAtual); if (interpretador.pontoDeParadaAtivo) { interpretador.avisoPontoParadaAtivado(); break; } } retornoExecucao = await interpretador.executar(declaracaoAtual); // Um ponto de parada ativo pode ter vindo de um escopo mais interno. // Por isso verificamos outra parada aqui para evitar que // `interpretador.declaracaoAtual` seja incrementado. if (interpretador.pontoDeParadaAtivo) { interpretador.avisoPontoParadaAtivado(); break; } } return retornoExecucao; } catch (erro) { // Se estamos dentro de uma declaração tente, re-lança o erro // para que o bloco pegue possa capturá-lo if (interpretador.emDeclaracaoTente) { throw erro; } interpretador.erros.push(erro); } finally { if (!interpretador.pontoDeParadaAtivo && interpretador.comando !== 'adentrarEscopo') { interpretador.pilhaEscoposExecucao.removerUltimo(); const escopoAnterior = interpretador.pilhaEscoposExecucao.topoDaPilha(); escopoAnterior.espacoMemoria.resolucoesChamadas = Object.assign(escopoAnterior.espacoMemoria.resolucoesChamadas, ultimoEscopo.espacoMemoria.resolucoesChamadas); if (manterEspacoMemoria) { escopoAnterior.espacoMemoria.valores = Object.assign(escopoAnterior.espacoMemoria.valores, ultimoEscopo.espacoMemoria.valores); } interpretador.escopoAtual--; } if (interpretador.pilhaEscoposExecucao.elementos() === 1) { interpretador.finalizacaoDaExecucao(); } } } /** * Continua a interpretação, conforme comando do depurador. * Quando um ponto de parada é ativado, a pilha de execução do TypeScript é perdida. * Esse método cria uma nova pilha de execução do lado do JS, começando do último elemento executado do * primeiro escopo, subindo até o último elemento executado do último escopo. * Se entre escopos houver ponto de parada ativo, a execução é suspensa até o próximo comando * do desenvolvedor. * @see executarUltimoEscopo */ async function instrucaoContinuarInterpretacao(interpretador, escopo = 1) { if (escopo < interpretador.escopoAtual) { await instrucaoContinuarInterpretacao(interpretador, escopo + 1); } if (interpretador.pontoDeParadaAtivo) { return; } await executarUltimoEscopoComandoContinuar(interpretador, false, true); } /** * Interpreta apenas uma instrução a partir do ponto de parada ativo, conforme comando do depurador. * Esse método cria uma nova pilha de execução do lado do JS, começando do último elemento executado do * primeiro escopo, subindo até o último elemento executado do último escopo. * @param escopo Indica o escopo a ser visitado. Usado para construir uma pilha de chamadas do lado JS. */ async function instrucaoPasso(interpretador, escopo = 1) { // Limpa o ponto de parada para permitir a execução interpretador.pontoDeParadaAtivo = false; // Define comando como 'proximo' se não estiver definido (ex: chamado diretamente por instrucaoPasso) // Preserva se já estiver definido (ex: 'adentrarEscopo' definido por adentrarEscopo()) if (!interpretador.comando) { interpretador.comando = 'proximo'; } interpretador.passos = 1; const escopoVisitado = interpretador.pilhaEscoposExecucao.naPosicao(escopo); if (escopo < interpretador.escopoAtual) { await instrucaoPasso(interpretador, escopo + 1); } else { if (escopoVisitado.declaracaoAtual >= escopoVisitado.declaracoes.length || escopoVisitado.finalizado) { interpretador.pilhaEscoposExecucao.removerUltimo(); } if (interpretador.pilhaEscoposExecucao.elementos() === 1) { return interpretador.finalizacaoDaExecucao(); } await executarUmPassoNoEscopo(interpretador); } } /** * Interpreta restante do bloco de execução em que o ponto de parada está, conforme comando do depurador. * Se houver outros pontos de parada no mesmo escopo à frente da instrução atual, todos são ignorados. * @param escopo Indica o escopo a ser visitado. Usado para construir uma pilha de chamadas do lado JS. */ async function instrucaoProximoESair(interpretador) { executarUltimoEscopoComandoContinuar(interpretador, false, true); } function abrirNovoBlocoEscopo(interpretador, declaracoes, espacoMemoria, tipoEscopo = 'outro') { const escopoExecucao = { declaracoes: declaracoes, declaracaoAtual: 0, espacoMemoria: espacoMemoria || new espaco_memoria_1.EspacoMemoria(), finalizado: false, tipo: tipoEscopo, emLacoRepeticao: false, }; interpretador.pilhaEscoposExecucao.empilhar(escopoExecucao); interpretador.escopoAtual++; } /** * No interpretador com depuração, este método é dividido em dois outros métodos privados: * - `executarUmPassoNoEscopo`, que executa apenas uma instrução e nada mais; * - `executarUltimoEscopoComandoContinuar`, que é a execução trivial de um escopo inteiro, * ou com todas as instruções, ou até encontrar um ponto de parada. * @param manterespacoMemoria Se verdadeiro, junta elementos do último escopo com o escopo * imediatamente abaixo. * @param naoVerificarPrimeiraExecucao Booleano que pede ao Interpretador para não * verificar o ponto de parada na primeira execução. * Normalmente usado pelo Servidor de Depuração para continuar uma linha. * @returns O retorno da execução. */ async function executarUltimoEscopo(interpretador, manterespacoMemoria = false, naoVerificarPrimeiraExecucao = false) { switch (interpretador.comando) { case 'adentrarEscopo': case 'proximo': if (!interpretador.executandoChamada) { return executarUmPassoNoEscopo(interpretador); } return executarUltimoEscopoComandoContinuar(interpretador, manterespacoMemoria, naoVerificarPrimeiraExecucao); default: return executarUltimoEscopoComandoContinuar(interpretador, manterespacoMemoria, naoVerificarPrimeiraExecucao); } } /** * Obtém o valor de uma variável por nome. * Em versões anteriores, o mecanismo de avaliação fazia toda a avaliação tradicional, * passando por Lexador, Avaliador Sintático e Interpretador. * Isso tem sua cota de problemas, sobretudo porque a avaliação insere e descarta escopos, * entrando em condição de corrida com a interpretação com depuração. * Método usado principalmente pela [extensão do Visual Studio Code](https://github.com/DesignLiquido/vscode) * e pelo mecanismo de depuração remota, implementado em [`delegua-node`](https://github.com/DesignLiquido/delegua-node). * @param nome O nome da variável. */ function obterVariavel(interpretador, nome) { const valorOuVariavel = interpretador.pilhaEscoposExecucao.obterValorVariavel({ lexema: nome, }); if (valorOuVariavel.hasOwnProperty('valor')) { return valorOuVariavel; } return { valor: valorOuVariavel, tipo: (0, inferenciador_1.inferirTipoVariavel)(valorOuVariavel), }; } //# sourceMappingURL=comum.js.map