Repository: DesignLiquido/delegua
Pull request #686 (2024-06-29 18:29:52)

685 erro ao usar o operador += em arrays
Nesta PR modifico a lógica para atender ao caso de atribuição em itens de vetor.

Essa é uma versão simples, creio que de para melhorar isso.


Patch:
diff --git a/fontes/analisador-semantico/analisador-semantico-base.ts b/fontes/analisador-semantico/analisador-semantico-base.ts
index da0ad313..c22d5d0a 100644
--- a/fontes/analisador-semantico/analisador-semantico-base.ts
+++ b/fontes/analisador-semantico/analisador-semantico-base.ts
@@ -55,6 +55,7 @@ import {
     Retorna,
     Sustar,
 } from '../declaracoes';
+import { DiagnosticoAnalisadorSemantico, DiagnosticoSeveridade, SimboloInterface } from '../interfaces';
 import { AnalisadorSemanticoInterface } from '../interfaces/analisador-semantico-interface';
 import { RetornoAnalisadorSemantico } from '../interfaces/retornos/retorno-analisador-semantico';
 import { ContinuarQuebra, RetornoQuebra, SustarQuebra } from '../quebras';
@@ -65,9 +66,24 @@ import { ContinuarQuebra, RetornoQuebra, SustarQuebra } from '../quebras';
  * simplesmente passa por ele (`return Promise.resolve()`).
  */
 export abstract class AnalisadorSemanticoBase implements AnalisadorSemanticoInterface {
+    diagnosticos: DiagnosticoAnalisadorSemantico[];
     
     abstract analisar(declaracoes: Declaracao[]): RetornoAnalisadorSemantico;
 
+    adicionarDiagnostico(
+        simbolo: SimboloInterface, 
+        mensagem: string, 
+        severidade: DiagnosticoSeveridade = DiagnosticoSeveridade.ERRO
+    ): void {
+        this.diagnosticos.push({
+            simbolo: simbolo,
+            mensagem: mensagem,
+            hashArquivo: simbolo.hashArquivo,
+            linha: simbolo.linha,
+            severidade: severidade,
+        });
+    }
+
     visitarDeclaracaoAleatorio(declaracao: Aleatorio): Promise<any> {
         return Promise.resolve();
     }
diff --git a/fontes/avaliador-sintatico/avaliador-sintatico.ts b/fontes/avaliador-sintatico/avaliador-sintatico.ts
index b3da413b..2b634a28 100644
--- a/fontes/avaliador-sintatico/avaliador-sintatico.ts
+++ b/fontes/avaliador-sintatico/avaliador-sintatico.ts
@@ -1,66 +1,66 @@
-import tiposDeSimbolos from '../tipos-de-simbolos/delegua';
-import tipoDeDadosDelegua from '../tipos-de-dados/delegua';
 import hrtime from 'browser-process-hrtime';
+import tipoDeDadosDelegua from '../tipos-de-dados/delegua';
+import tiposDeSimbolos from '../tipos-de-simbolos/delegua';
 
-import { AvaliadorSintaticoInterface, ParametroInterface, SimboloInterface } from '../interfaces';
 import {
-    AcessoMetodoOuPropriedade as AcessoMetodoOuPropriedade,
+    AcessoIndiceVariavel,
+    AcessoMetodoOuPropriedade,
     Agrupamento,
     AtribuicaoPorIndice,
     Atribuir,
     Binario,
     Chamada,
+    Comentario,
     Construto,
-    Dicionario,
+    Decorador,
     DefinirValor,
+    Dicionario,
+    ExpressaoRegular,
     FuncaoConstruto,
+    Isto,
     Literal,
     Logico,
-    AcessoIndiceVariavel,
     Super,
     TipoDe,
     Unario,
     Variavel,
     Vetor,
-    Isto,
-    ExpressaoRegular,
-    Decorador,
-    Comentario,
 } from '../construtos';
+import { AvaliadorSintaticoInterface, ParametroInterface, SimboloInterface } from '../interfaces';
 
 import { ErroAvaliadorSintatico } from './erro-avaliador-sintatico';
 
+import { SeletorTuplas, Tupla } from '../construtos/tuplas';
 import {
     Bloco,
     Classe,
+    Const,
     Continua,
     Declaracao,
     Enquanto,
     Escolha,
     Escreva,
     Expressao,
+    Falhar,
     Fazer,
-    FuncaoDeclaracao as FuncaoDeclaracao,
+    FuncaoDeclaracao,
     Importar,
+    Leia,
     Para,
-    Sustar,
+    ParaCada,
+    PropriedadeClasse,
     Retorna,
     Se,
+    Sustar,
+    TendoComo,
     Tente,
     Var,
-    Leia,
-    Const,
-    ParaCada,
-    Falhar,
-    PropriedadeClasse,
-    TendoComo,
 } from '../declaracoes';
 import { RetornoAvaliadorSintatico } from '../interfaces/retornos/retorno-avaliador-sintatico';
 import { RetornoLexador } from '../interfaces/retornos/retorno-lexador';
-import { RetornoDeclaracao } from './retornos';
-import { TipoDadosElementar } from '../tipo-dados-elementar';
 import { Simbolo } from '../lexador';
-import { SeletorTuplas, Tupla } from '../construtos/tuplas';
+import { TipoDadosElementar } from '../tipo-dados-elementar';
+import { RetornoDeclaracao } from './retornos';
 
 // Será usado para forçar tipagem em construtos e em algumas funções internas.
 type TipoDeSimboloDelegua = (typeof tiposDeSimbolos)[keyof typeof tiposDeSimbolos];
@@ -388,7 +388,7 @@ export class AvaliadorSintatico implements AvaliadorSintaticoInterface<SimboloIn
             if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.PARENTESE_ESQUERDO)) {
                 expressao = this.finalizarChamada(expressao);
             } else if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.PONTO)) {
-                const nome = this.consumir(tiposDeSimbolos.IDENTIFICADOR, "Esperado nome do método após '.'.");
+                const nome = this.consumir(tiposDeSimbolos.IDENTIFICADOR, "Esperado nome de método ou propriedade após '.'.");
                 expressao = new AcessoMetodoOuPropriedade(this.hashArquivo, expressao, nome);
             } else if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.COLCHETE_ESQUERDO)) {
                 const indice = this.expressao();
@@ -603,7 +603,24 @@ export class AvaliadorSintatico implements AvaliadorSintaticoInterface<SimboloIn
                 tiposDeSimbolos.MODULO_IGUAL,
             ].includes(expressao.operador.tipo)
         ) {
-            return new Atribuir(this.hashArquivo, (expressao.esquerda as Variavel).simbolo, expressao);
+
+            let simbolo = (expressao.esquerda as Variavel).simbolo
+            if (expressao.esquerda instanceof AcessoIndiceVariavel) {
+                simbolo = (expressao.esquerda.entidadeChamada as Variavel).simbolo
+                return new Atribuir(
+                    this.hashArquivo,
+                    simbolo,
+                    expressao,
+                    (expressao.esquerda as AcessoIndiceVariavel).indice
+                );
+            }
+
+            return new Atribuir(
+                this.hashArquivo,
+                simbolo,
+                expressao,
+            );
+
         } else if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.IGUAL)) {
             const igual = this.simbolos[this.atual - 1];
             const valor = this.expressao();
@@ -704,9 +721,9 @@ export class AvaliadorSintatico implements AvaliadorSintaticoInterface<SimboloIn
         } while (simboloComentario.tipo === tiposDeSimbolos.LINHA_COMENTARIO);
 
         return new Comentario(
-            simboloComentario.hashArquivo, 
-            simboloComentario.linha, 
-            conteudos, 
+            simboloComentario.hashArquivo,
+            simboloComentario.linha,
+            conteudos,
             true
         );
     }
@@ -714,9 +731,9 @@ export class AvaliadorSintatico implements AvaliadorSintaticoInterface<SimboloIn
     declaracaoComentarioUmaLinha(): Comentario {
         const simboloComentario = this.avancarEDevolverAnterior();
         return new Comentario(
-            simboloComentario.hashArquivo, 
-            simboloComentario.linha, 
-            simboloComentario.literal, 
+            simboloComentario.hashArquivo,
+            simboloComentario.linha,
+            simboloComentario.literal,
             false
         );
     }
@@ -729,7 +746,7 @@ export class AvaliadorSintatico implements AvaliadorSintaticoInterface<SimboloIn
         // Ponto-e-vírgula é opcional aqui.
         this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.PONTO_E_VIRGULA);
         return new Continua(this.simbolos[this.atual - 1]);
-    }   
+    }
 
     declaracaoEscolha(): Escolha {
         try {
diff --git a/fontes/construtos/atribuir.ts b/fontes/construtos/atribuir.ts
index 80596d05..0cd260e3 100644
--- a/fontes/construtos/atribuir.ts
+++ b/fontes/construtos/atribuir.ts
@@ -1,5 +1,6 @@
 import { VisitanteComumInterface, SimboloInterface } from '../interfaces';
 import { Construto } from './construto';
+import { Literal } from './literal';
 
 /**
  * Construto de atribuição de um valor a um símbolo.
@@ -8,19 +9,28 @@ export class Atribuir<TTipoSimbolo extends string = string> implements Construto
     linha: number;
     hashArquivo: number;
 
+    indice?: Literal;
+
     simbolo: SimboloInterface<TTipoSimbolo>;
     valor: any;
 
     constructor(
-        hashArquivo: number, 
-        simbolo: SimboloInterface<TTipoSimbolo>, 
-        valor: any
+        hashArquivo: number,
+        simbolo: SimboloInterface<TTipoSimbolo>,
+        valor: any,
+        // indice so é usado para variaveis de vetores
+        // TODO: criar alguma validaçao para garantir que indice so seja passado para variaveis de vetores
+        indice?: Literal
     ) {
         this.linha = Number(simbolo.linha);
         this.hashArquivo = hashArquivo;
 
         this.simbolo = simbolo;
         this.valor = valor;
+
+        if (indice !== undefined) {
+            this.indice = indice;
+        }
     }
 
     async aceitar(visitante: VisitanteComumInterface): Promise<any> {
diff --git a/fontes/estruturas/delegua-funcao.ts b/fontes/estruturas/delegua-funcao.ts
index 58f82f4a..00dd8652 100644
--- a/fontes/estruturas/delegua-funcao.ts
+++ b/fontes/estruturas/delegua-funcao.ts
@@ -84,7 +84,7 @@ export class DeleguaFuncao extends Chamavel {
 
     private resolverParametrosEspalhados(argumentos: Array<ArgumentoInterface>, indiceArgumentoAtual: number) {
         const argumentosResolvidos = [];
-        for (let i = indiceArgumentoAtual; i < argumentos.length; indiceArgumentoAtual++) {
+        for (let i = indiceArgumentoAtual; i < argumentos.length; i++) {
             const argumentoAtual = argumentos[i];
             argumentosResolvidos.push(
                 argumentoAtual && argumentoAtual.hasOwnProperty('valor') ? argumentoAtual.valor : argumentoAtual
diff --git a/fontes/interfaces/pilha-escopos-execucao-interface.ts b/fontes/interfaces/pilha-escopos-execucao-interface.ts
index b79e2cae..7ac9e4a1 100644
--- a/fontes/interfaces/pilha-escopos-execucao-interface.ts
+++ b/fontes/interfaces/pilha-escopos-execucao-interface.ts
@@ -5,7 +5,7 @@ import { SimboloInterface } from './simbolo-interface';
 import { VariavelInterface } from './variavel-interface';
 
 export interface PilhaEscoposExecucaoInterface extends PilhaInterface<EscopoExecucao> {
-    atribuirVariavel(simbolo: SimboloInterface, valor: any): void;
+    atribuirVariavel(simbolo: SimboloInterface, valor: any, indice?: number): void;
     atribuirVariavelEm(distancia: number, simbolo: SimboloInterface, valor: any): void;
     definirConstante(nomeConstante: string, valor: any, tipo?: string): void;
     definirVariavel(nomeVariavel: string, valor: any, tipo?: string): void;
diff --git a/fontes/interpretador/interpretador-base.ts b/fontes/interpretador/interpretador-base.ts
index 8a47040d..bce02c19 100644
--- a/fontes/interpretador/interpretador-base.ts
+++ b/fontes/interpretador/interpretador-base.ts
@@ -551,7 +551,7 @@ export class InterpretadorBase implements InterpretadorInterface {
                 return Math.pow(valorEsquerdo, valorDireito);
 
             case tiposDeSimbolos.MAIOR:
-                
+
                 if (this.tiposNumericos.includes(tipoEsquerdo) && this.tiposNumericos.includes(tipoDireito)) {
                     return Number(valorEsquerdo) > Number(valorDireito);
                 }
@@ -696,14 +696,14 @@ export class InterpretadorBase implements InterpretadorInterface {
     protected resolverParametrosChamada(entidadeChamada: Chamavel): ParametroInterface[] {
         if (entidadeChamada instanceof DeleguaFuncao) {
             return entidadeChamada.declaracao.parametros;
-        } 
-        
+        }
+
         if (entidadeChamada instanceof DeleguaClasse) {
             return entidadeChamada.metodos.construtor
                 ? entidadeChamada.metodos.construtor.declaracao.parametros
                 : [];
-        } 
-        
+        }
+
         return [];
     }
 
@@ -793,7 +793,7 @@ export class InterpretadorBase implements InterpretadorInterface {
                 }
             }
 
-            // Por algum motivo misterioso, `entidadeChamada instanceof Chamavel` dá `false` em Liquido, 
+            // Por algum motivo misterioso, `entidadeChamada instanceof Chamavel` dá `false` em Liquido,
             // mesmo que esteja tudo certo com `DeleguaFuncao`,
             // então precisamos testar o nome do construtor também.
             if (entidadeChamada instanceof Chamavel || entidadeChamada.constructor.name === 'DeleguaFuncao') {
@@ -832,7 +832,13 @@ export class InterpretadorBase implements InterpretadorInterface {
     async visitarExpressaoDeAtribuicao(expressao: Atribuir): Promise<any> {
         const valor = await this.avaliar(expressao.valor);
         const valorResolvido = valor !== undefined && valor.hasOwnProperty('valor') ? valor.valor : valor;
-        this.pilhaEscoposExecucao.atribuirVariavel(expressao.simbolo, valorResolvido);
+        let indice: any = null;
+
+        if (expressao.indice) {
+            indice =  await this.avaliar(expressao.indice);
+        }
+
+        this.pilhaEscoposExecucao.atribuirVariavel(expressao.simbolo, valorResolvido, indice);
 
         return valorResolvido;
     }
@@ -1359,8 +1365,15 @@ export class InterpretadorBase implements InterpretadorInterface {
                     )
                 );
             }
+
             return objeto[valorIndice];
-        } else if (
+        } 
+
+        if (objeto instanceof Vetor) {
+            return objeto.valores[valorIndice];
+        }
+        
+        if (
             objeto.constructor === Object ||
             objeto instanceof ObjetoDeleguaClasse ||
             objeto instanceof DeleguaFuncao ||
@@ -1368,7 +1381,9 @@ export class InterpretadorBase implements InterpretadorInterface {
             objeto instanceof DeleguaModulo
         ) {
             return objeto[valorIndice] || null;
-        } else if (typeof objeto === tipoDeDadosPrimitivos.TEXTO) {
+        } 
+        
+        if (typeof objeto === tipoDeDadosPrimitivos.TEXTO) {
             if (!Number.isInteger(valorIndice)) {
                 return Promise.reject(
                     new ErroEmTempoDeExecucao(
@@ -1390,16 +1405,17 @@ export class InterpretadorBase implements InterpretadorInterface {
                     new ErroEmTempoDeExecucao(expressao.simboloFechamento, 'Índice fora do tamanho.', expressao.linha)
                 );
             }
+
             return objeto.charAt(valorIndice);
-        } else {
-            return Promise.reject(
-                new ErroEmTempoDeExecucao(
-                    expressao.entidadeChamada.nome,
-                    'Somente listas, dicionários, classes e objetos podem ter seus valores indexados.',
-                    expressao.linha
-                )
-            );
         }
+
+        return Promise.reject(
+            new ErroEmTempoDeExecucao(
+                expressao.entidadeChamada.nome,
+                'Somente listas, dicionários, classes e objetos podem ter seus valores indexados.',
+                expressao.linha
+            )
+        );
     }
 
     async visitarExpressaoDefinirValor(expressao: DefinirValor): Promise<any> {
@@ -1421,7 +1437,7 @@ export class InterpretadorBase implements InterpretadorInterface {
             objeto.definir(expressao.nome, valor);
             return valor;
         }
-        
+
         if (objeto.constructor === Object) {
             objeto[expressao.nome.lexema] = valor;
         }
@@ -1434,6 +1450,7 @@ export class InterpretadorBase implements InterpretadorInterface {
 
     /**
      * Executa uma declaração de classe.
+     * Uma variável do tipo `DeleguaClasse` é adicionada à pilha de escopos de execução.
      * @param declaracao A declaração de classe.
      * @returns Sempre retorna nulo, por ser requerido pelo contrato de visita.
      */
@@ -1495,9 +1512,9 @@ export class InterpretadorBase implements InterpretadorInterface {
     async visitarExpressaoAcessoMetodo(expressao: AcessoMetodoOuPropriedade): Promise<any> {
         let variavelObjeto: VariavelInterface = await this.avaliar(expressao.objeto);
 
-        // Este caso acontece quando há encadeamento de métodos. 
+        // Este caso acontece quando há encadeamento de métodos.
         // Por exemplo, `objeto1.metodo1().metodo2()`.
-        // Como `RetornoQuebra` também possui `valor`, precisamos extrair o 
+        // Como `RetornoQuebra` também possui `valor`, precisamos extrair o
         // valor dele primeiro.
         if (variavelObjeto.constructor.name === 'RetornoQuebra') {
             variavelObjeto = variavelObjeto.valor;
@@ -1505,7 +1522,7 @@ export class InterpretadorBase implements InterpretadorInterface {
 
         const objeto = variavelObjeto.hasOwnProperty('valor') ? variavelObjeto.valor : variavelObjeto;
 
-        // Outro caso que `instanceof` simplesmente não funciona para casos em Liquido, 
+        // Outro caso que `instanceof` simplesmente não funciona para casos em Liquido,
         // então testamos também o nome do construtor.
         if (objeto instanceof ObjetoDeleguaClasse || objeto.constructor.name === 'ObjetoDeleguaClasse') {
             return objeto.obter(expressao.simbolo) || null;
diff --git a/fontes/interpretador/pilha-escopos-execucao.ts b/fontes/interpretador/pilha-escopos-execucao.ts
index 7c29b766..f7db386b 100644
--- a/fontes/interpretador/pilha-escopos-execucao.ts
+++ b/fontes/interpretador/pilha-escopos-execucao.ts
@@ -140,7 +140,7 @@ export class PilhaEscoposExecucao implements PilhaEscoposExecucaoInterface {
         };
     }
 
-    atribuirVariavel(simbolo: SimboloInterface, valor: any) {
+    atribuirVariavel(simbolo: SimboloInterface, valor: any, indice?: number) {
         for (let i = 1; i <= this.pilha.length; i++) {
             const ambiente = this.pilha[this.pilha.length - i].ambiente;
             if (ambiente.valores[simbolo.lexema] !== undefined) {
@@ -154,11 +154,21 @@ export class PilhaEscoposExecucao implements PilhaEscoposExecucaoInterface {
                 const tipo = (variavel && variavel.hasOwnProperty('tipo') ? variavel.tipo : inferirTipoVariavel(valor)).toLowerCase() as TipoInferencia;
 
                 const valorResolvido = this.converterValor(tipo, valor);
-                ambiente.valores[simbolo.lexema] = {
-                    valor: valorResolvido,
-                    tipo,
-                    imutavel: false,
-                };
+
+                if (indice !== undefined && indice !== null) {
+                    if (variavel.valor instanceof Array) {
+                        variavel.valor[indice] = valorResolvido;
+                    } else {
+                        throw new ErroEmTempoDeExecucao(simbolo, "Variável não é um vetor.");
+                    }
+                } else {
+                    ambiente.valores[simbolo.lexema] = {
+                        valor: valorResolvido,
+                        tipo,
+                        imutavel: false,
+                    };
+                }
+
                 return;
             }
         }


Test patch:
diff --git a/testes/interpretador/interpretador.test.ts b/testes/interpretador/interpretador.test.ts
index fe3a2be8..2f576001 100644
--- a/testes/interpretador/interpretador.test.ts
+++ b/testes/interpretador/interpretador.test.ts
@@ -15,6 +15,24 @@ describe('Interpretador', () => {
         });
 
         describe('Cenários de sucesso', () => {
+            describe('Acesso a operações matemáticas em posições de array', () => {
+                it('Espera-se que atribuição com acumulador seja bem sucedida', async () => {
+                    const retornoLexador = lexador.mapear(
+                        ['var pilha = [1, 2, 3, 4]', 'pilha[0] += 8', 'escreva(pilha[0])'],
+                        -1
+                    );
+                    const retornoAvaliadorSintatico = avaliadorSintatico.analisar(retornoLexador, -1);
+
+                    interpretador.funcaoDeRetorno = (saida: any) => {
+                        expect(saida).toEqual('9');
+                    };
+
+                    const retornoInterpretador = await interpretador.interpretar(retornoAvaliadorSintatico.declaracoes);
+
+                    expect(retornoInterpretador.erros).toHaveLength(0);
+                })
+            })
+
             describe('Acesso a variáveis e objetos', () => {
                 it('Acesso a elementos de vetor', async () => {
                     const retornoLexador = lexador.mapear(['var a = [1, 2, 3]', 'escreva(a[1])'], -1);