Repository: DesignLiquido/delegua
Pull request #373 (2023-05-13 19:04:01)

VisuAlg - Passo dinâmico de `para` dentro de um laço de repetição
- Usando Lodash porque JS é um lixo e não tem um método `clone` implementado na linguagem;
- Resolvendo problema do `para` com passo dinâmico dentro de outro bloco de repetição para o VisuAlg.

github-actions[bot]: <!-- jest coverage report action for options with hash 781087021f8e6c43aac1e668f4520603 -->

## Coverage report 



| <div title="Status of coverage:&#10; 🟢 - ok&#10; 🟡 - slightly more than threshold&#10; 🔴 - under the threshold">St.<sup>:grey_question:</sup></div> | Category   | Percentage                                                     | Covered / Total |
| :----------------------------------------------------------------------------------------------------------------------------------------------------: | :--------- | :------------------------------------------------------------- | :-------------: |
|                                                                           🟡                                                                           | Statements | <div title="Base coverage is: 72.53%">73.29% (+0.76% 🔼)</div> |    5905/8057    |
|                                                                           🟡                                                                           | Branches   | <div title="Base coverage is: 61.69%">62.57% (+0.88% 🔼)</div> |    1892/3024    |
|                                                                           🟡                                                                           | Functions  | <div title="Base coverage is: 69.26%">69.53% (+0.27% 🔼)</div> |     867/1247    |
|                                                                           🟡                                                                           | Lines      | <div title="Base coverage is: 73%">73.78% (+0.79% 🔼)</div>    |    5753/7797    |


## Test suite run success
449 tests passing in 37 suites.

<p align="right">Report generated by <a href="https://github.com/ArtiomTr/jest-coverage-report-action">🧪jest coverage report action</a> from b5cd92b04c4854adb2e0bddef202c8c8276909a5</p>
<!-- Sticky Pull Request Comment -->
samuelrvg: Sugestão: https://www.npmjs.com/package/lodash.clone

Patch:
diff --git a/fontes/avaliador-sintatico/dialetos/avaliador-sintatico-visualg.ts b/fontes/avaliador-sintatico/dialetos/avaliador-sintatico-visualg.ts
index 82756e98..15078d11 100644
--- a/fontes/avaliador-sintatico/dialetos/avaliador-sintatico-visualg.ts
+++ b/fontes/avaliador-sintatico/dialetos/avaliador-sintatico-visualg.ts
@@ -725,6 +725,7 @@ export class AvaliadorSintaticoVisuAlg extends AvaliadorSintaticoBase {
         // Quando um dos operandos é uma variável, tanto a condição do laço quanto o
         // passo são considerados indefinidos aqui.
         let passo: Construto;
+        let resolverIncrementoEmExecucao = false;
         if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.PASSO)) {
             passo = this.unario();
         } else {
@@ -751,6 +752,7 @@ export class AvaliadorSintaticoVisuAlg extends AvaliadorSintaticoBase {
                 passo = undefined;
                 operadorCondicao = undefined;
                 operadorCondicaoIncremento = undefined;
+                resolverIncrementoEmExecucao = true;
             }
         }
 
@@ -819,6 +821,7 @@ export class AvaliadorSintaticoVisuAlg extends AvaliadorSintaticoBase {
             corpo
         );
         para.blocoPosExecucao = corpo;
+        para.resolverIncrementoEmExecucao = resolverIncrementoEmExecucao;
         return para;
     }
 
diff --git a/fontes/declaracoes/para.ts b/fontes/declaracoes/para.ts
index 9502923a..748190ca 100644
--- a/fontes/declaracoes/para.ts
+++ b/fontes/declaracoes/para.ts
@@ -14,6 +14,7 @@ export class Para extends Declaracao {
     corpo: Bloco;
     inicializada: boolean;
     blocoPosExecucao?: Bloco;
+    resolverIncrementoEmExecucao: boolean;
 
     constructor(
         hashArquivo: number,
@@ -30,6 +31,7 @@ export class Para extends Declaracao {
         this.corpo = corpo;
         this.inicializada = false;
         this.blocoPosExecucao = undefined;
+        this.resolverIncrementoEmExecucao = false;
     }
 
     async aceitar(visitante: VisitanteComumInterface): Promise<any> {
diff --git a/fontes/interpretador/dialetos/visualg/comum.ts b/fontes/interpretador/dialetos/visualg/comum.ts
index b710f32e..12a02a4c 100644
--- a/fontes/interpretador/dialetos/visualg/comum.ts
+++ b/fontes/interpretador/dialetos/visualg/comum.ts
@@ -1,13 +1,60 @@
-import { Binario, Construto, FimPara, Literal, Logico, Unario } from "../../../construtos";
+import { AcessoIndiceVariavel, Binario, Construto, FimPara, Literal, Logico, Unario, Variavel } from "../../../construtos";
 import { Expressao, Para } from "../../../declaracoes";
 import { Simbolo } from "../../../lexador";
-import { VisitanteComumInterface, SimboloInterface, VariavelInterface } from "../../../interfaces";
+import { SimboloInterface, VariavelInterface } from "../../../interfaces";
 import { inferirTipoVariavel } from '../../inferenciador';
 
 import tiposDeSimbolos from '../../../tipos-de-simbolos/visualg';
 import { ErroEmTempoDeExecucao } from "../../../excecoes";
+import { InterpretadorBase } from "../../interpretador-base";
 
-async function avaliar(interpretador: VisitanteComumInterface, expressao: Construto): Promise<any> {
+export async function atribuirVariavel(interpretador: InterpretadorBase, expressao: Construto, valor: any): Promise<any> {
+    if (expressao instanceof Variavel) {
+        interpretador.pilhaEscoposExecucao.atribuirVariavel(expressao.simbolo, valor);
+        return;
+    }
+
+    if (expressao instanceof AcessoIndiceVariavel) {
+        const promises = await Promise.all([
+            interpretador.avaliar(expressao.entidadeChamada),
+            interpretador.avaliar(expressao.indice)
+        ]);
+
+        let alvo = promises[0];
+        let indice = promises[1];
+        const subtipo = alvo.hasOwnProperty('subtipo') ? 
+            alvo.subtipo :
+            undefined;
+
+        if (alvo.hasOwnProperty('valor')) {
+            alvo = alvo.valor;
+        }
+
+        if (indice.hasOwnProperty('valor')) {
+            indice = indice.valor;
+        }
+
+        let valorResolvido;
+        switch (subtipo) {
+            case 'texto':
+                valorResolvido = String(valor);
+                break;
+            case 'número':
+                valorResolvido = Number(valor);
+                break;
+            case 'lógico':
+                valorResolvido = Boolean(valor);
+                break;
+            default:
+                valorResolvido = valor;
+                break;
+        }
+
+        alvo[indice] = valorResolvido;
+    }
+}
+
+async function avaliar(interpretador: InterpretadorBase, expressao: Construto): Promise<any> {
     return await expressao.aceitar(interpretador);
 }
 
@@ -48,10 +95,15 @@ function verificarOperandosNumeros(
  * @param expressao A expressão binária.
  * @returns O resultado da resolução da expressão.
  */
-export async function visitarExpressaoBinaria(interpretador: VisitanteComumInterface, expressao: Binario | any): Promise<any> {
+export async function visitarExpressaoBinaria(interpretador: InterpretadorBase, expressao: Binario | any): Promise<any> {
     try {
-        const esquerda: VariavelInterface | any = await avaliar(interpretador, expressao.esquerda);
-        const direita: VariavelInterface | any = await avaliar(interpretador, expressao.direita);
+        const promises = await Promise.all([
+            avaliar(interpretador, expressao.esquerda),
+            avaliar(interpretador, expressao.direita)
+        ])
+
+        const esquerda: VariavelInterface | any = promises[0];
+        const direita: VariavelInterface | any = promises[1];
 
         let valorEsquerdo: any = esquerda?.hasOwnProperty('valor') ? esquerda.valor : esquerda;
         let valorDireito: any = direita?.hasOwnProperty('valor') ? direita.valor : direita;
@@ -144,7 +196,7 @@ export async function visitarExpressaoBinaria(interpretador: VisitanteComumInter
     }
 }
 
-export async function visitarExpressaoLogica(interpretador: VisitanteComumInterface, expressao: Logico): Promise<any> {
+export async function visitarExpressaoLogica(interpretador: InterpretadorBase, expressao: Logico): Promise<any> {
     const esquerda = await avaliar(interpretador, expressao.esquerda);
 
     // se um estado for verdadeiro, retorna verdadeiro
@@ -172,10 +224,14 @@ export async function visitarExpressaoLogica(interpretador: VisitanteComumInterf
  * Quando um dos operandos é uma variável, tanto a condição do laço quanto o
  * passo são considerados indefinidos aqui.
  */
-export async function resolverIncrementoPara(interpretador: VisitanteComumInterface, declaracao: Para): Promise<any> {
-    if (declaracao.condicao.operador === undefined) {
-        const operandoEsquerdo = await avaliar(interpretador, declaracao.condicao.esquerda);
-        const operandoDireito = await avaliar(interpretador, declaracao.condicao.direita);
+export async function resolverIncrementoPara(interpretador: InterpretadorBase, declaracao: Para): Promise<any> {
+    if (declaracao.resolverIncrementoEmExecucao) {
+        const promises = await Promise.all([
+            avaliar(interpretador, declaracao.condicao.esquerda),
+            avaliar(interpretador, declaracao.condicao.direita)
+        ]);
+        const operandoEsquerdo = promises[0];
+        const operandoDireito = promises[1];
         const valorAtualEsquerdo = operandoEsquerdo.hasOwnProperty('valor') ?
             operandoEsquerdo.valor :
             operandoEsquerdo;
diff --git a/fontes/interpretador/dialetos/visualg/interpretador-visualg-com-depuracao.ts b/fontes/interpretador/dialetos/visualg/interpretador-visualg-com-depuracao.ts
index fd30dccf..dc4e5b29 100644
--- a/fontes/interpretador/dialetos/visualg/interpretador-visualg-com-depuracao.ts
+++ b/fontes/interpretador/dialetos/visualg/interpretador-visualg-com-depuracao.ts
@@ -1,3 +1,5 @@
+import * as _ from 'lodash';
+
 import {
     registrarBibliotecaNumericaVisuAlg,
     registrarBibliotecaCaracteresVisuAlg,
@@ -119,52 +121,6 @@ export class InterpretadorVisuAlgComDepuracao extends InterpretadorComDepuracao
         await this.executar(declaracao.incremento);
     }
 
-    async atribuirVariavel(expressao: Construto, valor: any): Promise<any> {
-        if (expressao instanceof Variavel) {
-            this.pilhaEscoposExecucao.atribuirVariavel(expressao.simbolo, valor);
-            return;
-        }
-
-        if (expressao instanceof AcessoIndiceVariavel) {
-            const promises = await Promise.all([
-                this.avaliar(expressao.entidadeChamada),
-                this.avaliar(expressao.indice)
-            ]);
-
-            let alvo = promises[0];
-            let indice = promises[1];
-            const subtipo = alvo.hasOwnProperty('subtipo') ? 
-                alvo.subtipo :
-                undefined;
-
-            if (alvo.hasOwnProperty('valor')) {
-                alvo = alvo.valor;
-            }
-
-            if (indice.hasOwnProperty('valor')) {
-                indice = indice.valor;
-            }
-
-            let valorResolvido;
-            switch (subtipo) {
-                case 'texto':
-                    valorResolvido = String(valor);
-                    break;
-                case 'número':
-                    valorResolvido = Number(valor);
-                    break;
-                case 'lógico':
-                    valorResolvido = Boolean(valor);
-                    break;
-                default:
-                    valorResolvido = valor;
-                    break;
-            }
-
-            alvo[indice] = valorResolvido;
-        }
-    }
-
     /**
      * Execução da leitura de valores da entrada configurada no
      * início da aplicação.
@@ -182,26 +138,28 @@ export class InterpretadorVisuAlgComDepuracao extends InterpretadorComDepuracao
                 );
 
             const valorLido = await promessaLeitura();
-            await this.atribuirVariavel(argumento, valorLido);
+            await comum.atribuirVariavel(this, argumento, valorLido);
         }
     }
 
     async visitarDeclaracaoPara(declaracao: Para): Promise<any> {
-        const corpoExecucao = declaracao.corpo as Bloco;
-        if (declaracao.inicializador !== null && !declaracao.inicializada) {
-            await this.avaliar(declaracao.inicializador);
+        // const cloneDeclaracao = JSON.parse(JSON.stringify(declaracao)) as Para;
+        const cloneDeclaracao = _.cloneDeep(declaracao) as Para;
+        const corpoExecucao = cloneDeclaracao.corpo as Bloco;
+        if (cloneDeclaracao.inicializador !== null) {
+            await this.avaliar(cloneDeclaracao.inicializador);
             // O incremento vai ao final do bloco de escopo.
-            if (declaracao.incrementar !== null) {
-                await comum.resolverIncrementoPara(this, declaracao);
-                corpoExecucao.declaracoes.push(declaracao.incrementar);
+            if (cloneDeclaracao.incrementar !== null) {
+                await comum.resolverIncrementoPara(this, cloneDeclaracao);
+                corpoExecucao.declaracoes.push(cloneDeclaracao.incrementar);
             }
         }
 
-        declaracao.inicializada = true;
+        // cloneDeclaracao.inicializada = true;
         const escopoAtual = this.pilhaEscoposExecucao.topoDaPilha();
         switch (this.comando) {
             case "proximo":
-                if (declaracao.condicao !== null && this.eVerdadeiro(await this.avaliar(declaracao.condicao))) {
+                if (cloneDeclaracao.condicao !== null && this.eVerdadeiro(await this.avaliar(cloneDeclaracao.condicao))) {
                     escopoAtual.emLacoRepeticao = true;
                     
                     const resultadoBloco = this.executarBloco(corpoExecucao.declaracoes);
@@ -209,11 +167,12 @@ export class InterpretadorVisuAlgComDepuracao extends InterpretadorComDepuracao
                 }
 
                 escopoAtual.emLacoRepeticao = false;
+                // declaracao.inicializada = false;
                 return null;
             default: 
                 let retornoExecucao: any;
                 while (!(retornoExecucao instanceof Quebra) && !this.pontoDeParadaAtivo) {
-                    if (declaracao.condicao !== null && !this.eVerdadeiro(await this.avaliar(declaracao.condicao))) {
+                    if (cloneDeclaracao.condicao !== null && !this.eVerdadeiro(await this.avaliar(cloneDeclaracao.condicao))) {
                         break;
                     }
 
@@ -231,6 +190,7 @@ export class InterpretadorVisuAlgComDepuracao extends InterpretadorComDepuracao
                     }
                 }
                 // escopoAtual.emLacoRepeticao = false;
+                // declaracao.inicializada = false;
                 return retornoExecucao;
         }
     }
diff --git a/fontes/interpretador/dialetos/visualg/interpretador-visualg.ts b/fontes/interpretador/dialetos/visualg/interpretador-visualg.ts
index a63192b3..1de02ad8 100644
--- a/fontes/interpretador/dialetos/visualg/interpretador-visualg.ts
+++ b/fontes/interpretador/dialetos/visualg/interpretador-visualg.ts
@@ -1,4 +1,4 @@
-import { Binario, Construto, Logico, Variavel } from '../../../construtos';
+import { Binario, Construto, FimPara, Logico, Variavel } from '../../../construtos';
 import { Const, Escreva, EscrevaMesmaLinha, Fazer, Leia, Para } from '../../../declaracoes';
 import { InterpretadorBase } from '../..';
 import { ContinuarQuebra, Quebra, SustarQuebra } from '../../../quebras';
@@ -107,6 +107,22 @@ export class InterpretadorVisuAlg extends InterpretadorBase {
         }
     }
 
+    async visitarExpressaoFimPara(declaracao: FimPara): Promise<any> {
+        if (!this.eVerdadeiro(await this.avaliar(declaracao.condicaoPara))) {
+            const escopoPara = this.pilhaEscoposExecucao.pilha[this.pilhaEscoposExecucao.pilha.length - 2];
+            escopoPara.declaracaoAtual++;
+            
+            escopoPara.emLacoRepeticao = false;
+            return new SustarQuebra();
+        }
+
+        if (declaracao.incremento === null || declaracao.incremento === undefined) {
+            return;
+        }
+
+        await this.executar(declaracao.incremento);
+    }
+
     /**
      * Execução da leitura de valores da entrada configurada no
      * início da aplicação.
@@ -123,7 +139,7 @@ export class InterpretadorVisuAlg extends InterpretadorBase {
             );
 
             const valorLido = await promessaLeitura();
-            this.pilhaEscoposExecucao.atribuirVariavel((<Variavel>argumento).simbolo, valorLido);
+            await comum.atribuirVariavel(this, argumento, valorLido);
         }
     }
 
@@ -134,7 +150,8 @@ export class InterpretadorVisuAlg extends InterpretadorBase {
         }
 
         let retornoExecucao: any;
-        while (!(retornoExecucao instanceof Quebra)) {
+        let retornoIncremento: any;
+        while (!(retornoExecucao instanceof Quebra) && !(retornoIncremento instanceof Quebra)) {
             if (declaracao.condicao !== null && !this.eVerdadeiro(await this.avaliar(declaracao.condicao))) {
                 break;
             }
@@ -158,7 +175,7 @@ export class InterpretadorVisuAlg extends InterpretadorBase {
             }
 
             if (declaracao.incrementar !== null) {
-                await this.avaliar(declaracao.incrementar);
+                retornoIncremento = await this.avaliar(declaracao.incrementar);
             }
         }
 
diff --git a/package.json b/package.json
index 58ebfadc..08c88f50 100644
--- a/package.json
+++ b/package.json
@@ -61,6 +61,7 @@
         "@types/esprima": "^4.0.3",
         "@types/estree": "^1.0.0",
         "@types/jest": "^29.4.0",
+        "@types/lodash": "^4.14.194",
         "@typescript-eslint/eslint-plugin": "^5.46.1",
         "@typescript-eslint/parser": "^5.46.1",
         "antlr4": "^4.12.0",
@@ -83,6 +84,7 @@
     "dependencies": {
         "antlr4ts": "^0.5.0-alpha.4",
         "browser-process-hrtime": "^1.0.0",
-        "esprima": "^4.0.1"
+        "esprima": "^4.0.1",
+        "lodash.clonedeep": "^4.5.0"
     }
 }
diff --git a/yarn.lock b/yarn.lock
index 1dd4e66a..bb78cf22 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1564,6 +1564,11 @@
   resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
   integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
 
+"@types/lodash@^4.14.194":
+  version "4.14.194"
+  resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.194.tgz#b71eb6f7a0ff11bff59fc987134a093029258a76"
+  integrity sha512-r22s9tAS7imvBt2lyHC9B8AGwWnXaYb1tY09oyLkXDs4vArpYJzw09nj8MLx5VfciBPGIb+ZwG0ssYnEPJxn/g==
+
 "@types/node@*":
   version "18.14.0"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.0.tgz#94c47b9217bbac49d4a67a967fdcdeed89ebb7d0"
@@ -4226,6 +4231,11 @@ locate-path@^6.0.0:
   dependencies:
     p-locate "^5.0.0"
 
+lodash.clonedeep@^4.5.0:
+  version "4.5.0"
+  resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
+  integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==
+
 lodash.debounce@^4.0.8:
   version "4.0.8"
   resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"


Test patch:
diff --git a/testes/visualg/interpretador.test.ts b/testes/visualg/interpretador.test.ts
index 97863d9d..1c104943 100644
--- a/testes/visualg/interpretador.test.ts
+++ b/testes/visualg/interpretador.test.ts
@@ -171,6 +171,57 @@ describe('Interpretador', () => {
                 expect(retornoInterpretador.erros).toHaveLength(0);
             });
 
+            it('Sucesso - Para com passo dinâmico', async () => {
+                // Aqui vamos simular a resposta para doze variáveis de `leia()`.
+                const respostas = [
+                    2, 'S', 5, 'S', 6, 'S', 5, 'S', 3, 'S', 5, 'N'
+                ];
+                interpretador.interfaceEntradaSaida = {
+                    question: (mensagem: string, callback: Function) => {
+                        callback(respostas.shift());
+                    }
+                };
+
+                const retornoLexador = lexador.mapear([
+                    'algoritmo "semnome"',
+                    'var',
+                    '    li: vetor[0..9] de inteiro',
+                    '    i, j, k: inteiro',
+                    '    resposta: caractere',
+                    'inicio',
+                    '    i <- 0',
+                    '    k <- 0',
+                    '    enquanto resposta <> "N" faca',
+                    '        escreval("---------------------------")',
+                    '        escreval("Digite o", i + 1, "º numero: ")',
+                    '        leia(li[i])',
+                    '        se (i > 0) entao',
+                    '            para j de 0 ate i - 1 faca',
+                    '                se li[i] = li[j] entao',
+                    '                    escreval("Valores repetidos não serão computados.")',
+                    '                    i <- i - 1',
+                    '                    interrompa',
+                    '                fimse',
+                    '            fimpara',
+                    '        fimse',
+                    '        escreval ("Você deseja inserir mais um número? (S/N)")',
+                    '        leia(resposta)',
+                    '        se (resposta <> "N") entao',
+                    '            i <- i + 1',
+                    '        fimse',
+                    '    fimenquanto',
+                    '    para k de 0 ate i faca',
+                    '        escreva(li[k], " ")',
+                    '    fimPara',
+                    'fimAlgoritmo'
+                ], -1);
+                const retornoAvaliadorSintatico = avaliadorSintatico.analisar(retornoLexador, -1);
+    
+                const retornoInterpretador = await interpretador.interpretar(retornoAvaliadorSintatico.declaracoes);
+
+                expect(retornoInterpretador.erros).toHaveLength(0);
+            });
+
             it('Sucesso - Procedimento', async () => {
                 // Aqui vamos simular a resposta para duas variáveis de `leia()`.
                 const respostas = [