Repository: DesignLiquido/delegua
Pull request #562 (2023-12-22 21:40:25)

Decoradores para classes e métodos
Implementa https://github.com/DesignLiquido/delegua/discussions/559


Patch:
diff --git a/fontes/avaliador-sintatico/avaliador-sintatico.ts b/fontes/avaliador-sintatico/avaliador-sintatico.ts
index 7c6111c3..6b9105f6 100644
--- a/fontes/avaliador-sintatico/avaliador-sintatico.ts
+++ b/fontes/avaliador-sintatico/avaliador-sintatico.ts
@@ -23,6 +23,7 @@ import {
     Vetor,
     Isto,
     ExpressaoRegular,
+    Decorador,
 } from '../construtos';
 
 import { ErroAvaliadorSintatico } from './erro-avaliador-sintatico';
@@ -62,6 +63,7 @@ import { Simbolo } from '../lexador';
  * Há dois grupos de estruturas de alto nível: Construtos e Declarações.
  */
 export class AvaliadorSintatico implements AvaliadorSintaticoInterface<SimboloInterface, Declaracao> {
+    pilhaDecoradores: Decorador[];
     simbolos: SimboloInterface[];
     erros: ErroAvaliadorSintatico[];
 
@@ -935,6 +937,20 @@ export class AvaliadorSintatico implements AvaliadorSintaticoInterface<SimboloIn
         }
     }
 
+    resolverDecorador(): void {
+        this.pilhaDecoradores = []
+        while (this.verificarTipoSimboloAtual(tiposDeSimbolos.ARROBA)) {
+            let nomeDecorador: string = '';
+            let linha = this.simbolos[this.atual].linha;
+            let simbolosLinhaAtual = this.simbolos.filter((l) => l.linha === linha);
+            nomeDecorador += simbolosLinhaAtual.map(l => {
+                this.avancarEDevolverAnterior()
+                return l.lexema || '.'
+            }).join('')
+            this.pilhaDecoradores.push(new Decorador(this.hashArquivo, linha, nomeDecorador));
+        }
+    }
+
     /**
      * Todas as resoluções triviais da linguagem, ou seja, todas as
      * resoluções que podem ocorrer dentro ou fora de um bloco.
@@ -1171,6 +1187,7 @@ export class AvaliadorSintatico implements AvaliadorSintaticoInterface<SimboloIn
 
     funcao(tipo: string): FuncaoDeclaracao {
         let simbolo: SimboloInterface;
+        this.resolverDecorador();
         switch (this.simbolos[this.atual].tipo) {
             case tiposDeSimbolos.CONSTRUTOR:
                 simbolo = this.avancarEDevolverAnterior();
@@ -1179,7 +1196,7 @@ export class AvaliadorSintatico implements AvaliadorSintaticoInterface<SimboloIn
                 simbolo = this.consumir(tiposDeSimbolos.IDENTIFICADOR, `Esperado nome de ${tipo}.`);
                 break;
         }
-        return new FuncaoDeclaracao(simbolo, this.corpoDaFuncao(tipo));
+        return new FuncaoDeclaracao(simbolo, this.corpoDaFuncao(tipo), null, this.pilhaDecoradores);
     }
 
     logicaComumParametros(): ParametroInterface[] {
@@ -1248,6 +1265,7 @@ export class AvaliadorSintatico implements AvaliadorSintaticoInterface<SimboloIn
 
     declaracaoDeClasse(): Classe {
         const simbolo: SimboloInterface = this.consumir(tiposDeSimbolos.IDENTIFICADOR, 'Esperado nome da classe.');
+        const pilhaDecoradoresClasse = this.pilhaDecoradores;
 
         let superClasse = null;
         if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.HERDA)) {
@@ -1263,7 +1281,7 @@ export class AvaliadorSintatico implements AvaliadorSintaticoInterface<SimboloIn
         }
 
         this.consumir(tiposDeSimbolos.CHAVE_DIREITA, "Esperado '}' após o escopo da classe.");
-        return new Classe(simbolo, superClasse, metodos);
+        return new Classe(simbolo, superClasse, metodos, null, pilhaDecoradoresClasse);
     }
 
     /**
@@ -1285,7 +1303,9 @@ export class AvaliadorSintatico implements AvaliadorSintaticoInterface<SimboloIn
                 return this.funcao('funcao');
             }
 
-            if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.CLASSE)) return this.declaracaoDeClasse();
+            if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.CLASSE)) {
+                return this.declaracaoDeClasse();
+            }
 
             return this.resolverDeclaracao();
         } catch (erro: any) {
@@ -1334,9 +1354,11 @@ export class AvaliadorSintatico implements AvaliadorSintaticoInterface<SimboloIn
 
         this.hashArquivo = hashArquivo || 0;
         this.simbolos = retornoLexador?.simbolos || [];
+        this.pilhaDecoradores = []
 
         let declaracoes: Declaracao[] = [];
         while (!this.estaNoFinal()) {
+            this.resolverDecorador()
             const retornoDeclaracao = this.resolverDeclaracaoForaDeBloco();
             if (Array.isArray(retornoDeclaracao)) {
                 declaracoes = declaracoes.concat(retornoDeclaracao);
diff --git a/fontes/construtos/decorador.ts b/fontes/construtos/decorador.ts
new file mode 100644
index 00000000..814ecac5
--- /dev/null
+++ b/fontes/construtos/decorador.ts
@@ -0,0 +1,24 @@
+import { VisitanteComumInterface } from '../interfaces';
+import { Construto } from './construto';
+
+export class Decorador implements Construto {
+    linha: number;
+    hashArquivo: number;
+
+    nome: string;
+
+    constructor(hashArquivo: number, linha: number, nome: string) {
+        this.linha = linha;
+        this.hashArquivo = hashArquivo;
+
+        this.nome = nome;
+    }
+
+    async aceitar(visitante: VisitanteComumInterface): Promise<any> {
+        return Promise.reject(
+            new Error(
+                'Este método não deveria ser chamado.'
+            )
+        );
+    }
+}
diff --git a/fontes/construtos/index.ts b/fontes/construtos/index.ts
index 2b0dc271..ecd38474 100644
--- a/fontes/construtos/index.ts
+++ b/fontes/construtos/index.ts
@@ -8,6 +8,7 @@ export * from './chamada';
 export * from './constante';
 export * from './constante-ou-variavel';
 export * from './construto';
+export * from './decorador';
 export * from './definir-valor';
 export * from './dicionario';
 export * from './expressao-regular';
diff --git a/fontes/declaracoes/classe.ts b/fontes/declaracoes/classe.ts
index cb5f4d3d..d16efede 100644
--- a/fontes/declaracoes/classe.ts
+++ b/fontes/declaracoes/classe.ts
@@ -1,3 +1,4 @@
+import { Decorador } from '../construtos';
 import { VisitanteComumInterface, SimboloInterface } from '../interfaces';
 import { Declaracao } from './declaracao';
 import { FuncaoDeclaracao } from './funcao';
@@ -8,18 +9,21 @@ export class Classe extends Declaracao {
     superClasse: any;
     metodos: FuncaoDeclaracao[];
     propriedades: PropriedadeClasse[];
+    decoradores: Decorador[];
 
     constructor(
         simbolo: SimboloInterface,
         superClasse: any,
         metodos: FuncaoDeclaracao[],
-        propriedades: PropriedadeClasse[] = []
+        propriedades: PropriedadeClasse[] = [],
+        decoradores: Decorador[] = []
     ) {
         super(Number(simbolo.linha), simbolo.hashArquivo);
         this.simbolo = simbolo;
         this.superClasse = superClasse;
         this.metodos = metodos;
         this.propriedades = propriedades;
+        this.decoradores = decoradores
     }
 
     async aceitar(visitante: VisitanteComumInterface): Promise<any> {
diff --git a/fontes/declaracoes/funcao.ts b/fontes/declaracoes/funcao.ts
index f0391a78..2437efec 100644
--- a/fontes/declaracoes/funcao.ts
+++ b/fontes/declaracoes/funcao.ts
@@ -1,17 +1,23 @@
 import { VisitanteComumInterface, SimboloInterface } from '../interfaces';
 import { Declaracao } from './declaracao';
-import { FuncaoConstruto } from '../construtos';
+import { Decorador, FuncaoConstruto } from '../construtos';
 
 export class FuncaoDeclaracao extends Declaracao {
     simbolo: SimboloInterface;
     funcao: FuncaoConstruto;
     tipoRetorno?: SimboloInterface;
+    decoradores: Decorador[];
 
-    constructor(simbolo: SimboloInterface, funcao: FuncaoConstruto, tipoRetorno?: SimboloInterface) {
+    constructor(
+        simbolo: SimboloInterface, 
+        funcao: FuncaoConstruto, 
+        tipoRetorno?: SimboloInterface, 
+        decoradores: Decorador[] = []) {
         super(Number(simbolo.linha), simbolo.hashArquivo);
         this.simbolo = simbolo;
         this.funcao = funcao;
         this.tipoRetorno = tipoRetorno;
+        this.decoradores = decoradores
     }
 
     async aceitar(visitante: VisitanteComumInterface): Promise<any> {
diff --git a/fontes/lexador/lexador.ts b/fontes/lexador/lexador.ts
index 0ebf7b9d..38e60fda 100644
--- a/fontes/lexador/lexador.ts
+++ b/fontes/lexador/lexador.ts
@@ -194,6 +194,10 @@ export class Lexador implements LexadorInterface<SimboloInterface> {
         const caractere = this.simboloAtual();
 
         switch (caractere) {
+            case '@':
+                this.adicionarSimbolo(tiposDeSimbolos.ARROBA, '@');
+                this.avancar();
+                break;
             case '[':
                 this.adicionarSimbolo(tiposDeSimbolos.COLCHETE_ESQUERDO);
                 this.avancar();
diff --git a/fontes/tipos-de-simbolos/delegua.ts b/fontes/tipos-de-simbolos/delegua.ts
index 618d0db3..531c046c 100644
--- a/fontes/tipos-de-simbolos/delegua.ts
+++ b/fontes/tipos-de-simbolos/delegua.ts
@@ -1,4 +1,5 @@
 export default {
+    ARROBA: 'ARROBA',
     ADICAO: 'ADICAO',
     BIT_AND: 'BIT_AND',
     BIT_OR: 'BIT_OR',


Test patch:
diff --git a/testes/avaliador-sintatico.test.ts b/testes/avaliador-sintatico.test.ts
index 35ad2602..3666e48a 100644
--- a/testes/avaliador-sintatico.test.ts
+++ b/testes/avaliador-sintatico.test.ts
@@ -144,6 +144,38 @@ describe('Avaliador sintático', () => {
     
                 expect(retornoAvaliadorSintatico.erros).toHaveLength(0);
             });
+
+            it('Sucesso - decorador classe', () => {
+                const retornoLexador = lexador.mapear([
+                    '@meu.decorador1',
+                    '@meu.decorador2',
+                    'classe Teste {',
+                        'testeFuncao() {',
+                          'escreva("olá")',
+                        '}',
+                    '}',
+                ], -1);
+
+                const retornoAvaliadorSintatico = avaliadorSintatico.analisar(retornoLexador, -1);
+    
+                expect(retornoAvaliadorSintatico.erros).toHaveLength(0);
+            });
+
+            it('Sucesso - decorador função', () => {
+                const retornoLexador = lexador.mapear([
+                    '@meu.decorador1',
+                    'classe Teste {',
+                        '@meu.decorador2',
+                        'testeFuncao() {',
+                          'escreva("olá")',
+                        '}',
+                    '}',
+                ], -1);
+
+                const retornoAvaliadorSintatico = avaliadorSintatico.analisar(retornoLexador, -1);
+    
+                expect(retornoAvaliadorSintatico.erros).toHaveLength(0);
+            });
         });
 
         describe('Cenários de falha', () => {