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