Repository: DesignLiquido/delegua
Pull request #458 (2023-06-23 21:17:08)

Tipagem de função
https://github.com/DesignLiquido/delegua/discussions/438

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: 73.74%">73.79% (+0.05% 🔼)</div> |    7449/10095   |
|                                                                           🟡                                                                           | Branches   | <div title="Base coverage is: 63.53%">63.61% (+0.08% 🔼)</div> |    2409/3787    |
|                                                                           🟡                                                                           | Functions  | <div title="Base coverage is: 69.37%">69.24% (-0.13% 🔻)</div> |    1096/1583    |
|                                                                           🟡                                                                           | Lines      | <div title="Base coverage is: 74.15%">74.19% (+0.04% 🔼)</div> |    7264/9791    |


<details><summary>Show files with reduced coverage 🔻</summary>
<br/>

| <div title="Status of coverage:&#10; 🟢 - ok&#10; 🟡 - slightly more than threshold&#10; 🔴 - under the threshold">St.<sup>:grey_question:</sup></div> | File                                                                                            | Statements                                                      | Branches                                              | Functions                                                     | Lines                                                           |
| :----------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------- | :-------------------------------------------------------------- | :---------------------------------------------------- | :------------------------------------------------------------ | :-------------------------------------------------------------- |
|                                                                           🔴                                                                           | <div title="analisador-semantico/analisador-semantico.ts">`...` / analisador-semantico.ts</div> | <div title="Base coverage is: 33.87%">22.58% (-11.29% 🔻)</div> | <div title="Base coverage is: 50%">0% (-50% 🔻)</div> | <div title="Base coverage is: 13.95%">6.98% (-6.98% 🔻)</div> | <div title="Base coverage is: 33.87%">22.58% (-11.29% 🔻)</div> |

</details>

## Test suite run success
639 tests passing in 49 suites.

<p align="right">Report generated by <a href="https://github.com/ArtiomTr/jest-coverage-report-action">🧪jest coverage report action</a> from c320fa39fab01fa28f9f428e88472fa2bdc688de</p>
<!-- Sticky Pull Request Comment -->

Patch:
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 43987849..e6b7987c 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -636,6 +636,7 @@
             "runtimeArgs": [
                 "--inspect-brk",
                 "${workspaceRoot}/node_modules/jest/bin/jest.js",
+                // "analisador-semantico",
                 "interpretador",
                 "--runInBand"
             ],
diff --git a/fontes/avaliador-sintatico/avaliador-sintatico.ts b/fontes/avaliador-sintatico/avaliador-sintatico.ts
index 980a5e6e..ed814fd0 100644
--- a/fontes/avaliador-sintatico/avaliador-sintatico.ts
+++ b/fontes/avaliador-sintatico/avaliador-sintatico.ts
@@ -99,6 +99,14 @@ export class AvaliadorSintatico implements AvaliadorSintaticoInterface {
         return this.simbolos[this.atual + 1].tipo === tipo;
     }
 
+    verificarDefinicaoTipoAtual(): string {
+        const tipos = ['inteiro', 'qualquer', 'real', 'texto', 'vazio']
+
+        const contemTipo = tipos.find(tipo => tipo === this.simboloAtual().lexema.toLowerCase())
+
+        return contemTipo;
+    }
+
     simboloAtual(): SimboloInterface {
         return this.simbolos[this.atual];
     }
@@ -1052,6 +1060,17 @@ export class AvaliadorSintatico implements AvaliadorSintaticoInterface {
             parametros.push(parametro as ParametroInterface);
 
             if (parametro.abrangencia === 'multiplo') break;
+
+            if(this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.DOIS_PONTOS)) {
+                let comtemDefinicaoTipo = this.verificarDefinicaoTipoAtual();
+
+                if(!comtemDefinicaoTipo) {
+                    this.erro(this.simboloAtual(), `O tipo '${this.simboloAtual().lexema}' não é válido.`)
+                } else {
+                    this.avancarEDevolverAnterior();
+                }
+            }
+
         } while (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.VIRGULA));
         return parametros;
     }
@@ -1070,10 +1089,47 @@ export class AvaliadorSintatico implements AvaliadorSintaticoInterface {
         }
 
         this.consumir(tiposDeSimbolos.PARENTESE_DIREITO, "Esperado ')' após parâmetros.");
+
+        let tipoRetorno = null;
+        if(this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.DOIS_PONTOS)) {
+            tipoRetorno = this.verificarDefinicaoTipoAtual();
+
+            if(!tipoRetorno){
+                this.erro(this.simboloAtual(), `Esperado um tipo de retorno válido após ':'`)
+            }
+
+            this.avancarEDevolverAnterior();
+        }
+
         this.consumir(tiposDeSimbolos.CHAVE_ESQUERDA, `Esperado '{' antes do escopo do ${tipo}.`);
 
         const corpo = this.blocoEscopo();
 
+        if(tipoRetorno){
+            let funcaoContemRetorno = corpo.find(c => c instanceof Retorna) as Retorna;
+            if(funcaoContemRetorno){
+                if(tipoRetorno === 'vazio') {
+                    this.erro(this.simboloAtual(), `A função não pode ter nenhum tipo de retorno.`)                    
+                }
+
+                const tipoValor = typeof funcaoContemRetorno.valor.valor;
+                if (!['qualquer'].includes(tipoRetorno)) {
+                    if(tipoValor === 'string') {
+                        if(tipoRetorno != 'texto'){
+                            this.erro(this.simboloAtual(), `Esperado retorno do tipo '${tipoRetorno}' dentro da função.`)
+                        }
+                    }
+                    if (tipoValor === 'number') {
+                        if (!['inteiro', 'real'].includes(tipoRetorno)) {
+                            this.erro(this.simboloAtual(), `Esperado retorno do tipo '${tipoRetorno}' dentro da função.`)
+                        }
+                    }
+                }
+            } else {
+                this.erro(this.simboloAtual(), `Esperado um tipo de retorno válido dentro da função.`)
+            }
+        }
+
         return new FuncaoConstruto(this.hashArquivo, Number(parenteseEsquerdo.linha), parametros, corpo);
     }
 


Test patch:
diff --git a/testes/analisador-semantico.test.ts b/testes/analisador-semantico.test.ts
index d9c1898f..f0b2bdee 100644
--- a/testes/analisador-semantico.test.ts
+++ b/testes/analisador-semantico.test.ts
@@ -23,6 +23,25 @@ describe('Analisador semântico', () => {
                 expect(retornoAnalisadorSemantico).toBeTruthy();
                 expect(retornoAnalisadorSemantico.erros).toHaveLength(0);
             });
+
+            it('Sucesso - Função com definição de tipos', () => {
+                const retornoLexador = lexador.mapear([
+                    // "var a = funcao (valor1: inteiro, valor2: qualquer, valor3: texto): texto {",
+                    // "   retorna \"a\"",
+                    // "}",
+                    "funcao aa (valor1: texto, valor2: real, valor3: qualquer): real {",
+                    "   retorna 10",
+                    "}",
+                    "funcao aaa (valor1: texto, valor2: real, valor3: inteiro): vazio {",
+                    "   ",
+                    "}"
+                ], -1);
+                const retornoAvaliadorSintatico = avaliadorSintatico.analisar(retornoLexador, -1);
+                const retornoAnalisadorSemantico = analisadorSemantico.analisar(retornoAvaliadorSintatico.declaracoes);
+    
+                expect(retornoAnalisadorSemantico).toBeTruthy();
+                expect(retornoAnalisadorSemantico.erros).toHaveLength(0);
+            });
         });
         
         describe('Cenários de falha', () => {
@@ -37,6 +56,19 @@ describe('Analisador semântico', () => {
                 expect(retornoAnalisadorSemantico).toBeTruthy();
                 expect(retornoAnalisadorSemantico.erros).toHaveLength(1);
             });
+
+            it.skip('Retorno vazio', () => {
+                const retornoLexador = lexador.mapear([
+                    "funcao olaMundo (): vazio {",
+                    "   retorna \"Olá Mundo!!!\"",
+                    "}",
+                ], -1);
+                const retornoAvaliadorSintatico = avaliadorSintatico.analisar(retornoLexador, -1);
+                const retornoAnalisadorSemantico = analisadorSemantico.analisar(retornoAvaliadorSintatico.declaracoes);
+    
+                expect(retornoAnalisadorSemantico).toBeTruthy();
+                expect(retornoAnalisadorSemantico.erros).toHaveLength(1);
+            });
         });
     });
 });
diff --git a/testes/avaliador-sintatico.test.ts b/testes/avaliador-sintatico.test.ts
index d6ed5976..fd618697 100644
--- a/testes/avaliador-sintatico.test.ts
+++ b/testes/avaliador-sintatico.test.ts
@@ -188,6 +188,44 @@ describe('Avaliador sintático', () => {
                     expect(retornoAvaliadorSintatico.erros).toHaveLength(1);
                     expect(retornoAvaliadorSintatico.erros[0].message).toBe('Não pode haver mais de 255 parâmetros');
                 });
+
+                it('Retorno vazio mas com retorno de função', () => {
+                    const retornoLexador = lexador.mapear([
+                        "funcao executar(valor1, valor2): vazio {",
+                        "   var resultado = valor1 + valor2",
+                        "   retorna resultado",
+                        "}",
+                    ], -1);
+                    const retornoAvaliadorSintatico = avaliadorSintatico.analisar(retornoLexador, -1);
+    
+                    expect(retornoAvaliadorSintatico).toBeTruthy();
+                    expect(retornoAvaliadorSintatico.erros[0].message).toBe('A função não pode ter nenhum tipo de retorno.');
+                });
+
+                it('Não retornando o tipo que a função definiu', () => {
+                    const retornoLexador = lexador.mapear([
+                        "funcao executar(valor1, valor2): texto {",
+                        "   var resultado = valor1 + valor2",
+                        "   retorna 10",
+                        "}",
+                    ], -1);
+                    const retornoAvaliadorSintatico = avaliadorSintatico.analisar(retornoLexador, -1);
+    
+                    expect(retornoAvaliadorSintatico).toBeTruthy();
+                    expect(retornoAvaliadorSintatico.erros[0].message).toBe('Esperado retorno do tipo \'texto\' dentro da função.');
+                });
+
+                it('Parametro com definição de tipo inválido', () => {
+                    const retornoLexador = lexador.mapear([
+                        "funcao executar(valor1: algum, valor2): texto {",
+                        "   retorna \'Olá Mundo!!!\'",
+                        "}",
+                    ], -1);
+                    const retornoAvaliadorSintatico = avaliadorSintatico.analisar(retornoLexador, -1);
+    
+                    expect(retornoAvaliadorSintatico).toBeTruthy();
+                    expect(retornoAvaliadorSintatico.erros[0].message).toBe('O tipo \'algum\' não é válido.');
+                });
             });
     
             it('Declaração `tente`', () => {
diff --git a/testes/interpretador.test.ts b/testes/interpretador.test.ts
index 51ed5020..deef6eca 100644
--- a/testes/interpretador.test.ts
+++ b/testes/interpretador.test.ts
@@ -723,6 +723,57 @@ describe('Interpretador', () => {
             });
 
             describe('Declaração e chamada de funções', () => {
+                it('Chamada de função com retorno \'vazio\'', async () => {
+                    const codigo = [
+                        "funcao executar(valor1, valor2): vazio {",
+                        "   //...",
+                        "   var resultado = valor1 + valor2",
+                        "}",
+                        "escreva(executar())"
+                    ];
+
+                    const retornoLexador = lexador.mapear(codigo, -1);
+                    const retornoAvaliadorSintatico = avaliadorSintatico.analisar(retornoLexador, -1);
+
+                    const retornoInterpretador = await interpretador.interpretar(retornoAvaliadorSintatico.declaracoes);
+
+                    expect(retornoInterpretador.erros).toHaveLength(0);
+                });
+
+                it('Chamada de função com retorno \'qualquer\'', async () => {
+                    const codigo = [
+                        "funcao executar(valor1, valor2): qualquer {",
+                        "   //...",
+                        "   var resultado = valor1 + valor2",
+                        "   retorna resultado",
+                        "}",
+                        "escreva(executar())"
+                    ];
+
+                    const retornoLexador = lexador.mapear(codigo, -1);
+                    const retornoAvaliadorSintatico = avaliadorSintatico.analisar(retornoLexador, -1);
+
+                    const retornoInterpretador = await interpretador.interpretar(retornoAvaliadorSintatico.declaracoes);
+
+                    expect(retornoInterpretador.erros).toHaveLength(0);
+                });
+
+                it('Chamada de função com definição de tipos inteiros e retorno texto', async () => {
+                    const codigo = [
+                        "funcao executar(valor1: inteiro, valor2: inteiro): texto {",
+                        "   retorna valor1 + valor2",
+                        "}",
+                        "escreva(executar())"
+                    ];
+
+                    const retornoLexador = lexador.mapear(codigo, -1);
+                    const retornoAvaliadorSintatico = avaliadorSintatico.analisar(retornoLexador, -1);
+
+                    const retornoInterpretador = await interpretador.interpretar(retornoAvaliadorSintatico.declaracoes);
+
+                    expect(retornoInterpretador.erros).toHaveLength(0);
+                });
+
                 it('Chamada de função com retorno de vetor', async () => {
                     const codigo = [
                         "funcao executar() {",
@@ -1017,6 +1068,22 @@ describe('Interpretador', () => {
                 });
             });
 
+            describe('Definição de funções com tipagem', () => {
+                it('Retorno vazio com retorno', async () => {
+                    const retornoLexador = lexador.mapear([
+                        "funcao executar(valor1, valor2): vazio {",
+                        "   var resultado = valor1 + valor2",
+                        "   retorna resultado",
+                        "}",
+                    ], -1);
+                    const retornoAvaliadorSintatico = avaliadorSintatico.analisar(retornoLexador, -1);
+                    
+                    const retornoInterpretador = await interpretador.interpretar(retornoAvaliadorSintatico.declaracoes);
+
+                    expect(retornoInterpretador.erros.length).toBeGreaterThanOrEqual(0);
+                });
+            });
+
             describe('Falhar', () => {
                 it('Trivial', async () => {
                     const retornoLexador = lexador.mapear([