Repository: DesignLiquido/delegua
Pull request #283 (2023-02-18 02:41:12)

Mais ajustes no VisuAlg.
- `escreval()` sem argumentos;
- Correções na forma de interpretar `escolha` no VisuAlg;
- Se variável já foi instanciada, reatribuição de valores converte para o tipo inferenciado na primeira vez.


Patch:
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 69431e84..b0279047 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -419,7 +419,7 @@
             "runtimeArgs": [
                 "--inspect-brk",
                 "${workspaceRoot}/node_modules/jest/bin/jest.js",
-                "portugol-studio",
+                "portugol-ipt",
                 "--runInBand"
             ],
             "skipFiles": ["<node_internals>/**", "node_modules/**"],
diff --git a/fontes/avaliador-sintatico/dialetos/avaliador-sintatico-visualg.ts b/fontes/avaliador-sintatico/dialetos/avaliador-sintatico-visualg.ts
index 33f33c2b..8b459ed8 100644
--- a/fontes/avaliador-sintatico/dialetos/avaliador-sintatico-visualg.ts
+++ b/fontes/avaliador-sintatico/dialetos/avaliador-sintatico-visualg.ts
@@ -411,11 +411,12 @@ export class AvaliadorSintaticoVisuAlg extends AvaliadorSintaticoBase {
 
     private logicaCasosEscolha(): any {
         const literais = [];
-        let simboloAtualCaso: SimboloInterface = this.avancarEDevolverAnterior();
+        // let simboloAtualCaso: SimboloInterface = this.avancarEDevolverAnterior();
+        let simboloAtualCaso: SimboloInterface = this.simbolos[this.atual];
         while (simboloAtualCaso.tipo !== tiposDeSimbolos.QUEBRA_LINHA) {
             literais.push(this.primario());
             this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.VIRGULA);
-            simboloAtualCaso = this.simbolos[this.atual];
+            simboloAtualCaso = this.avancarEDevolverAnterior();
         }
 
         return literais;
@@ -424,17 +425,21 @@ export class AvaliadorSintaticoVisuAlg extends AvaliadorSintaticoBase {
     declaracaoEscolha(): Escolha {
         const simboloAtual = this.avancarEDevolverAnterior();
 
+        // Parênteses são opcionais para delimitar o identificador.
+        this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.PARENTESE_ESQUERDO);
         const identificador = this.primario();
+        this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.PARENTESE_DIREITO);
+        this.consumir(tiposDeSimbolos.QUEBRA_LINHA, "Esperado quebra de linha após variável ou literal de declaração 'caso'.")
 
         // Blocos de caso
         const caminhos = [];
         let simboloAtualBlocoCaso: SimboloInterface = this.avancarEDevolverAnterior();
         while (![tiposDeSimbolos.OUTRO_CASO, tiposDeSimbolos.FIM_ESCOLHA].includes(simboloAtualBlocoCaso.tipo)) {
             const caminhoCondicoes = this.logicaCasosEscolha();
-            this.consumir(
+            /* this.consumir(
                 tiposDeSimbolos.QUEBRA_LINHA,
                 "Esperado quebra de linha após último valor de declaração 'caso'."
-            );
+            ); */
 
             const declaracoes = [];
             do {
@@ -463,12 +468,14 @@ export class AvaliadorSintaticoVisuAlg extends AvaliadorSintaticoBase {
             caminhoPadrao = {
                 declaracoes: declaracoes.filter((d) => d),
             };
+
+            simboloAtualBlocoCaso = this.avancarEDevolverAnterior();
         }
 
-        this.consumir(
-            tiposDeSimbolos.FIM_ESCOLHA,
-            "Esperado palavra-chave 'fimescolha' para fechamento de declaração 'escolha'."
-        );
+        if (simboloAtualBlocoCaso.tipo !== tiposDeSimbolos.FIM_ESCOLHA) {
+            throw this.erro(this.simbolos[this.atual], 
+                "Esperado palavra-chave 'fimescolha' para fechamento de declaração 'escolha'.");
+        }
 
         this.consumir(tiposDeSimbolos.QUEBRA_LINHA, "Esperado quebra de linha após palavra-chave 'fimescolha'.");
 
@@ -482,8 +489,19 @@ export class AvaliadorSintaticoVisuAlg extends AvaliadorSintaticoBase {
         );
         const argumentos: FormatacaoEscrita[] = [];
 
+        // Sem não houver parâmetros, retorna vetor com literal vazio.
+        if (this.simbolos[this.atual].tipo === tiposDeSimbolos.PARENTESE_DIREITO) {
+            this.avancarEDevolverAnterior();
+            return [new FormatacaoEscrita(
+                this.hashArquivo, 
+                Number(simboloParenteses.linha), 
+                new Literal(this.hashArquivo, Number(simboloParenteses.linha), ''))
+            ]
+        }
+
         do {
             const valor = this.declaracao();
+
             let espacos = 0;
             let casasDecimais = 0;
             if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.DOIS_PONTOS)) {
diff --git a/fontes/interpretador/interpretador-base.ts b/fontes/interpretador/interpretador-base.ts
index 4eb1f485..9c8d2e5b 100644
--- a/fontes/interpretador/interpretador-base.ts
+++ b/fontes/interpretador/interpretador-base.ts
@@ -612,6 +612,10 @@ export class InterpretadorBase implements InterpretadorInterface {
 
     async visitarDeclaracaoEscolha(declaracao: Escolha): Promise<any> {
         const condicaoEscolha = await this.avaliar(declaracao.identificadorOuLiteral);
+        const valorCondicaoEscolha = condicaoEscolha.hasOwnProperty('valor') ? 
+            condicaoEscolha.valor :
+            condicaoEscolha;
+
         const caminhos = declaracao.caminhos;
         const caminhoPadrao = declaracao.caminhoPadrao;
 
@@ -621,7 +625,8 @@ export class InterpretadorBase implements InterpretadorInterface {
                 const caminho = caminhos[i];
 
                 for (let j = 0; j < caminho.condicoes.length; j++) {
-                    if ((await this.avaliar(caminho.condicoes[j])) === condicaoEscolha) {
+                    const condicaoAvaliada = await this.avaliar(caminho.condicoes[j]);
+                    if (condicaoAvaliada === valorCondicaoEscolha) {
                         encontrado = true;
 
                         try {
diff --git a/fontes/interpretador/pilha-escopos-execucao.ts b/fontes/interpretador/pilha-escopos-execucao.ts
index 3d4bdaf9..575ee2dd 100644
--- a/fontes/interpretador/pilha-escopos-execucao.ts
+++ b/fontes/interpretador/pilha-escopos-execucao.ts
@@ -39,6 +39,19 @@ export class PilhaEscoposExecucao implements PilhaEscoposExecucaoInterface {
         return this.pilha.pop();
     }
 
+    private converterValor(tipo: string, valor: any) {
+        switch (tipo) {
+            case 'texto':
+                return String(valor);
+            case 'número':
+                return Number(valor);
+            case 'lógico':
+                return Boolean(valor);
+            default:
+                return valor;
+        }
+    }
+
     definirVariavel(nomeVariavel: string, valor: any) {
         const variavel = this.pilha[this.pilha.length - 1].ambiente.valores[nomeVariavel];
         const tipo = variavel && variavel.hasOwnProperty('tipo') ? 
@@ -68,8 +81,9 @@ export class PilhaEscoposExecucao implements PilhaEscoposExecucaoInterface {
                     variavel.tipo : 
                     inferirTipoVariavel(valor);
 
+                const valorResolvido = this.converterValor(tipo, valor);
                 ambiente.valores[simbolo.lexema] = {
-                    valor,
+                    valor: valorResolvido,
                     tipo
                 };
                 return;
diff --git a/fontes/lexador/dialetos/lexador-portugol-ipt.ts b/fontes/lexador/dialetos/lexador-portugol-ipt.ts
new file mode 100644
index 00000000..24db8e22
--- /dev/null
+++ b/fontes/lexador/dialetos/lexador-portugol-ipt.ts
@@ -0,0 +1,247 @@
+import { LexadorInterface, SimboloInterface } from "../../interfaces";
+import { RetornoLexador } from "../../interfaces/retornos";
+import { ErroLexador } from "../erro-lexador";
+import { Simbolo } from "../simbolo";
+
+import palavrasReservadas from "./palavras-reservadas/portugol-ipt";
+import tiposDeSimbolos from "../../tipos-de-simbolos/portugol-ipt";
+
+export class LexadorPortugolIpt implements LexadorInterface {
+    simbolos: SimboloInterface[];
+    erros: ErroLexador[];
+    hashArquivo: number;
+    
+    codigo: string | string[];
+    inicioSimbolo: number;
+    atual: number;
+    linha: number;
+
+    eDigito(caractere: string): boolean {
+        return caractere >= '0' && caractere <= '9';
+    }
+
+    eAlfabeto(caractere: string): boolean {
+        const acentuacoes = [
+            'á',
+            'Á',
+            'ã',
+            'Ã',
+            'â',
+            'Â',
+            'à',
+            'À',
+            'é',
+            'É',
+            'ê',
+            'Ê',
+            'í',
+            'Í',
+            'ó',
+            'Ó',
+            'õ',
+            'Õ',
+            'ô',
+            'Ô',
+            'ú',
+            'Ú',
+            'ç',
+            'Ç',
+            '_',
+        ];
+
+        return (
+            (caractere >= 'a' && caractere <= 'z') ||
+            (caractere >= 'A' && caractere <= 'Z') ||
+            acentuacoes.includes(caractere)
+        );
+    }
+    eAlfabetoOuDigito(caractere: string): boolean {
+        return this.eDigito(caractere) || this.eAlfabeto(caractere);
+    }
+
+    eFinalDoCodigo(): boolean {
+        if (this.linha > this.codigo.length - 1) 
+            return true;
+
+        return this.linha == this.codigo.length - 1 && 
+            this.codigo[this.codigo.length - 1].length <= this.atual;
+    }
+
+    /**
+     * Indica se o código está na última linha.
+     * @returns Verdadeiro se contador de linhas está na última linha.
+     *          Falso caso contrário.
+     */
+    private eUltimaLinha(): boolean {
+        return this.linha >= this.codigo.length - 1;
+    }
+
+    avancar(): string | void {
+        this.atual += 1;
+
+        if (this.eFinalDaLinha() && !this.eUltimaLinha()) {
+            this.linha++;
+            this.atual = 0;
+        }
+    }
+
+    adicionarSimbolo(tipo: any, literal: any): void {
+        throw new Error("Método não implementado.");
+    }
+
+    private eFinalDaLinha(): boolean {
+        return this.atual >= this.codigo[this.linha].length;
+    }
+
+    simboloAtual(): string {
+        if (this.eFinalDaLinha()) return '\0';
+        if (this.linha > this.codigo.length - 1) return '\0';
+        return this.codigo[this.linha].charAt(this.atual);
+    }
+
+    proximoSimbolo(): string {
+        if (this.atual + 1 >= this.codigo[this.linha].length) 
+            return '\0';
+        return this.codigo[this.linha].charAt(this.atual + 1);
+    }
+
+    simboloAnterior(): string {
+        return this.codigo[this.linha].charAt(this.atual - 1);
+    }
+
+    analisarTexto(delimitador: string): void {
+        const linhaPrimeiroCaracter: number = this.linha;
+        while (this.simboloAtual() !== delimitador && !this.eFinalDoCodigo()) {
+            this.avancar();
+        }
+
+        if (this.eFinalDoCodigo()) {
+            this.erros.push({
+                linha: this.linha + 1,
+                caractere: this.simboloAnterior(),
+                mensagem: 'Texto não finalizado.',
+            } as ErroLexador);
+            return;
+        }
+
+        const textoCompleto = this.codigo[this.linha].substring(this.inicioSimbolo + 1, this.atual);
+
+        this.simbolos.push(
+            new Simbolo(
+                tiposDeSimbolos.TEXTO,
+                textoCompleto,
+                textoCompleto,
+                linhaPrimeiroCaracter + 1,
+                this.hashArquivo
+            )
+        );
+    }
+    
+    analisarNumero(): void {
+        const linhaPrimeiroDigito: number = this.linha;
+        while (this.eDigito(this.simboloAtual()) && this.linha === linhaPrimeiroDigito) {
+            this.avancar();
+        }
+
+        if (this.simboloAtual() == '.' && this.eDigito(this.proximoSimbolo())) {
+            this.avancar();
+
+            while (this.eDigito(this.simboloAtual())) {
+                this.avancar();
+            }
+        }
+
+        let numeroCompleto: string;
+        if (linhaPrimeiroDigito < this.linha) {
+            const linhaNumero: string = this.codigo[linhaPrimeiroDigito];
+            numeroCompleto = linhaNumero.substring(this.inicioSimbolo, linhaNumero.length);
+        } else {
+            numeroCompleto = this.codigo[this.linha].substring(this.inicioSimbolo, this.atual);
+        }
+
+        this.simbolos.push(
+            new Simbolo(
+                tiposDeSimbolos.NUMERO,
+                numeroCompleto,
+                parseFloat(numeroCompleto),
+                linhaPrimeiroDigito + 1,
+                this.hashArquivo
+            )
+        );
+    }
+
+    identificarPalavraChave(): void {
+        const linhaPrimeiroCaracter: number = this.linha;
+        while (this.eAlfabetoOuDigito(this.simboloAtual()) && this.linha === linhaPrimeiroCaracter) {
+            this.avancar();
+        }
+
+        let textoPalavraChave: string;
+        if (linhaPrimeiroCaracter < this.linha) {
+            const linhaPalavraChave: string = this.codigo[linhaPrimeiroCaracter];
+            textoPalavraChave = linhaPalavraChave.substring(this.inicioSimbolo, linhaPalavraChave.length);
+        } else {
+            textoPalavraChave = this.codigo[this.linha].substring(this.inicioSimbolo, this.atual);
+        }
+
+        const tipo: string =
+            textoPalavraChave in palavrasReservadas
+                ? palavrasReservadas[textoPalavraChave]
+                : tiposDeSimbolos.IDENTIFICADOR;
+
+        this.simbolos.push(new Simbolo(tipo, textoPalavraChave, null, linhaPrimeiroCaracter + 1, this.hashArquivo));
+    }
+
+    analisarToken(): void {
+        const caractere = this.simboloAtual();
+
+        switch (caractere) {
+            case ' ':
+            case '\t':
+            case '\r':
+            case '\n':
+            case '\0':
+            case ';':
+                this.avancar();
+                break;
+            case '"':
+                this.avancar();
+                this.analisarTexto('"');
+                this.avancar();
+                break;
+            default:
+                if (this.eDigito(caractere)) this.analisarNumero();
+                else if (this.eAlfabeto(caractere)) this.identificarPalavraChave();
+                else {
+                    this.erros.push({
+                        linha: this.linha + 1,
+                        caractere: caractere,
+                        mensagem: 'Caractere inesperado.',
+                    } as ErroLexador);
+                    this.avancar();
+                }
+        }
+    }
+
+    mapear(codigo: string[], hashArquivo: number): RetornoLexador {
+        this.simbolos = [];
+        this.erros = [];
+
+        this.inicioSimbolo = 0;
+        this.atual = 0;
+        this.linha = 0;
+
+        this.codigo = codigo || [''];
+        this.hashArquivo = hashArquivo;
+
+        while (!this.eFinalDoCodigo()) {
+            this.inicioSimbolo = this.atual;
+            this.analisarToken();
+        }
+
+        return {
+            simbolos: this.simbolos,
+            erros: this.erros
+        } as RetornoLexador;
+    }
+}
\ No newline at end of file
diff --git a/fontes/lexador/dialetos/palavras-reservadas/portugol-ipt.ts b/fontes/lexador/dialetos/palavras-reservadas/portugol-ipt.ts
new file mode 100644
index 00000000..c8785c53
--- /dev/null
+++ b/fontes/lexador/dialetos/palavras-reservadas/portugol-ipt.ts
@@ -0,0 +1,7 @@
+import tiposDeSimbolos from '../../../tipos-de-simbolos/portugol-ipt';
+
+export default {
+    escrever: tiposDeSimbolos.ESCREVER,
+    fim: tiposDeSimbolos.FIM,
+    inicio: tiposDeSimbolos.INICIO
+};
\ No newline at end of file
diff --git a/fontes/tipos-de-simbolos/portugol-ipt.ts b/fontes/tipos-de-simbolos/portugol-ipt.ts
new file mode 100644
index 00000000..09ee81b5
--- /dev/null
+++ b/fontes/tipos-de-simbolos/portugol-ipt.ts
@@ -0,0 +1,27 @@
+export default {
+    ADICAO: 'ADICAO',
+    DIFERENTE: 'DIFERENTE',
+    DIVISAO: 'DIVISAO',
+    DIVISAO_INTEIRA: 'DIVISAO_INTEIRA',
+    E: 'E',
+    ESCREVER: 'ESCREVER',
+    EXPONENCIACAO: 'EXPONENCIACAO',
+    FIM: 'FIM',
+    IDENTIFICADOR: 'IDENTIFICADOR',
+    IGUAL: 'IGUAL',
+    INICIO: 'INICIO',
+    MAIOR: 'MAIOR',
+    MAIOR_IGUAL: 'MAIOR_IGUAL',
+    MENOR: 'MENOR',
+    MENOR_IGUAL: 'MENOR_IGUAL',
+    MODULO: 'MODULO',
+    MULTIPLICACAO: 'MULTIPLICACAO',
+    NEGACAO: 'NEGACAO',
+    NUMERO: 'NUMERO',
+    OU: 'OU',
+    PARENTESE_ESQUERDO: 'PARENTESE_ESQUERDO',
+    PARENTESE_DIREITO: 'PARENTESE_DIREITO',
+    SUBTRACAO: 'SUBTRACAO',
+    TEXTO: 'TEXTO',
+    VIRGULA: 'VIRGULA',
+};
\ No newline at end of file


Test patch:
diff --git a/testes/portugol-ipt/lexador.test.ts b/testes/portugol-ipt/lexador.test.ts
new file mode 100644
index 00000000..8a1a9220
--- /dev/null
+++ b/testes/portugol-ipt/lexador.test.ts
@@ -0,0 +1,44 @@
+import { LexadorPortugolIpt } from '../../fontes/lexador/dialetos/lexador-portugol-ipt';
+
+import tiposDeSimbolos from '../../fontes/tipos-de-simbolos/portugol-ipt';
+
+describe('Lexador (Portugol IPT)', () => {
+    describe('mapear()', () => {
+        let lexador: LexadorPortugolIpt;
+
+        beforeEach(() => {
+            lexador = new LexadorPortugolIpt();
+        });
+
+        describe('Cenários de sucesso', () => {
+            it('Sucesso - Código vazio', () => {
+                const resultado = lexador.mapear([''], -1);
+
+                expect(resultado).toBeTruthy();
+                expect(resultado.simbolos).toHaveLength(0);
+            });
+        });
+
+        describe('Cenários de sucesso', () => {
+            it('Sucesso - Código apenas com `inicio` e `fim`', () => {
+                const resultado = lexador.mapear(['inicio', 'fim'], -1);
+
+                expect(resultado).toBeTruthy();
+                expect(resultado.simbolos).toHaveLength(2);
+            });
+        });
+
+        describe('Cenários de sucesso', () => {
+            it('Sucesso - Olá Mundo', () => {
+                const resultado = lexador.mapear([
+                    'inicio', 
+                    'escrever "Olá mundo"',
+                    'fim'
+                ], -1);
+
+                expect(resultado).toBeTruthy();
+                expect(resultado.simbolos).toHaveLength(4);
+            });
+        });
+    });
+});