Repository: DesignLiquido/delegua
Pull request #265 (2023-02-04 01:33:01)

Esboço do dialeto Portugol Studio

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

## 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: 68.1%">67.67% (-0.44% 🔻)</div>  |    4106/6068    |
|                                                                           🔴                                                                           | Branches   | <div title="Base coverage is: 56.62%">56.28% (-0.34% 🔻)</div> |    1224/2175    |
|                                                                           🟡                                                                           | Functions  | <div title="Base coverage is: 68.43%">68.85% (+0.42% 🔼)</div> |     599/870     |
|                                                                           🟡                                                                           | Lines      | <div title="Base coverage is: 68.44%">68% (-0.44% 🔻)</div>    |    3967/5834    |

<details><summary>Show new covered files 🐣</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="lexador/dialetos/lexador-portugol-studio.ts">`...` / lexador-portugol-studio.ts</div>                                     | 43.97%     | 39.13%   | 80%       | 43.36% |
|                                                                           🟡                                                                           | <div title="lexador/lexador-base.ts">`...` / lexador-base.ts</div>                                                                    | 62.16%     | 68.18%   | 71.43%    | 65.71% |
|                                                                           🟢                                                                           | <div title="lexador/dialetos/palavras-reservadas/portugol-studio.ts">`...` / portugol-studio.ts</div>                                 | 100%       | 100%     | 100%      | 100%   |
|                                                                           🟢                                                                           | <div title="tipos-de-simbolos/portugol-studio.ts">`...` / portugol-studio.ts</div>                                                    | 100%       | 100%     | 100%      | 100%   |
|                                                                           🟡                                                                           | <div title="avaliador-sintatico/dialetos/avaliador-sintatico-portugol-studio.ts">`...` / avaliador-sintatico-portugol-studio.ts</div> | 73.26%     | 58.62%   | 70.59%    | 72.94% |

</details>


<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="avaliador-sintatico/avaliador-sintatico-base.ts">`...` / avaliador-sintatico-base.ts</div> | <div title="Base coverage is: 50.53%">49.55% (-0.98% 🔻)</div> | <div title="Base coverage is: 57.14%">33.33% (-23.81% 🔻)</div> | <div title="Base coverage is: 33.33%">41.18% (+7.84% 🔼)</div> | <div title="Base coverage is: 49.45%">49.06% (-0.39% 🔻)</div> |

</details>

## Test suite run success
283 tests passing in 26 suites.

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

Patch:
diff --git a/.vscode/launch.json b/.vscode/launch.json
index d962c713..3d940d21 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -435,7 +435,7 @@
             "runtimeArgs": [
                 "--inspect-brk",
                 "${workspaceRoot}/node_modules/jest/bin/jest.js",
-                "tradutor-javascript.test.ts",
+                "portugol-studio",
                 "--runInBand"
             ],
             "skipFiles": ["<node_internals>/**", "node_modules/**"],
@@ -860,6 +860,22 @@
             "runtimeExecutable": "node",
             "runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"]
         },
+        {
+            "type": "node",
+            "request": "launch",
+            "name": "Portugol Studio > Olá Mundo",
+            "skipFiles": ["<node_internals>/**", "node_modules/**"],
+            "cwd": "${workspaceRoot}",
+            "console": "integratedTerminal",
+            "args": [
+                "${workspaceFolder}${pathSeparator}execucao.ts",
+                "--dialeto",
+                "portugol-studio",
+                "./exemplos/dialetos/portugol-studio/ola-mundo.por"
+            ],
+            "runtimeExecutable": "node",
+            "runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"]
+        },
         {
             "type": "node",
             "request": "launch",
diff --git a/exemplos/dialetos/portugol-studio/ola-mundo.por b/exemplos/dialetos/portugol-studio/ola-mundo.por
new file mode 100644
index 00000000..4ac0f119
--- /dev/null
+++ b/exemplos/dialetos/portugol-studio/ola-mundo.por
@@ -0,0 +1,7 @@
+programa 
+{ 
+	funcao inicio () 
+	{
+		escreva("Olá Mundo!")
+	} 
+}
diff --git a/fontes/avaliador-sintatico/avaliador-sintatico-base.ts b/fontes/avaliador-sintatico/avaliador-sintatico-base.ts
index 7c362338..1669f42e 100644
--- a/fontes/avaliador-sintatico/avaliador-sintatico-base.ts
+++ b/fontes/avaliador-sintatico/avaliador-sintatico-base.ts
@@ -18,7 +18,7 @@ import {
     Declaracao,
     Leia,
 } from '../declaracoes';
-import { AvaliadorSintaticoInterface, SimboloInterface } from '../interfaces';
+import { AvaliadorSintaticoInterface, ParametroInterface, SimboloInterface } from '../interfaces';
 import { RetornoLexador, RetornoAvaliadorSintatico } from '../interfaces/retornos';
 import { ErroAvaliadorSintatico } from './erro-avaliador-sintatico';
 
@@ -291,6 +291,36 @@ export abstract class AvaliadorSintaticoBase implements AvaliadorSintaticoInterf
         throw new Error('Método não implementado.');
     }
 
+    protected logicaComumParametros(): ParametroInterface[] {
+        const parametros: ParametroInterface[] = [];
+
+        do {
+            if (parametros.length >= 255) {
+                this.erro(this.simbolos[this.atual], 'Não pode haver mais de 255 parâmetros');
+            }
+
+            const parametro: Partial<ParametroInterface> = {};
+
+            if (this.simbolos[this.atual].tipo === tiposDeSimbolos.MULTIPLICACAO) {
+                this.consumir(tiposDeSimbolos.MULTIPLICACAO, null);
+                parametro.tipo = 'estrela';
+            } else {
+                parametro.tipo = 'padrao';
+            }
+
+            parametro.nome = this.consumir(tiposDeSimbolos.IDENTIFICADOR, 'Esperado nome do parâmetro.');
+
+            if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.IGUAL)) {
+                parametro.valorPadrao = this.primario();
+            }
+
+            parametros.push(parametro as ParametroInterface);
+
+            if (parametro.tipo === 'estrela') break;
+        } while (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.VIRGULA));
+        return parametros;
+    }
+
     abstract declaracao(): Declaracao;
 
     abstract analisar(retornoLexador: RetornoLexador, hashArquivo?: number): RetornoAvaliadorSintatico;
diff --git a/fontes/avaliador-sintatico/dialetos/avaliador-sintatico-portugol-studio.ts b/fontes/avaliador-sintatico/dialetos/avaliador-sintatico-portugol-studio.ts
new file mode 100644
index 00000000..d5af9c8c
--- /dev/null
+++ b/fontes/avaliador-sintatico/dialetos/avaliador-sintatico-portugol-studio.ts
@@ -0,0 +1,216 @@
+import { AcessoIndiceVariavel, AtribuicaoSobrescrita, Atribuir, Chamada, Construto, FuncaoConstruto, Literal, Variavel } from "../../construtos";
+import { Escreva, Declaracao, Se, Enquanto, Para, Escolha, Fazer, FuncaoDeclaracao, Expressao } from "../../declaracoes";
+import { RetornoLexador, RetornoAvaliadorSintatico } from "../../interfaces/retornos";
+import { AvaliadorSintaticoBase } from "../avaliador-sintatico-base";
+
+import { SimboloInterface } from "../../interfaces";
+
+import tiposDeSimbolos from '../../tipos-de-simbolos/portugol-studio';
+import { RetornoDeclaracao } from "../retornos";
+import { DeleguaFuncao } from "../../estruturas";
+
+/**
+ * O avaliador sintático (Parser) é responsável por transformar os símbolos do Lexador em estruturas de alto nível.
+ * Essas estruturas de alto nível são as partes que executam lógica de programação de fato.
+ * Há dois grupos de estruturas de alto nível: Construtos e Declarações.
+ */
+export class AvaliadorSintaticoPortugolStudio extends AvaliadorSintaticoBase {
+    private validarEscopoPrograma(declaracoes: Declaracao[]): void {
+        this.consumir(tiposDeSimbolos.PROGRAMA, "Esperada expressão 'programa' para inicializar programa.");
+
+        this.consumir(tiposDeSimbolos.CHAVE_ESQUERDA, "Esperada chave esquerda após expressão 'programa' para inicializar programa.");
+
+        while (!this.estaNoFinal()) {
+            declaracoes.push(this.declaracao() as Declaracao);
+        }
+
+        if (this.simbolos[this.atual - 1].tipo !== tiposDeSimbolos.CHAVE_DIREITA) {
+            throw this.erro(this.simbolos[this.atual - 1], "Esperado chave direita final para término do programa.");
+        }
+
+        const encontrarDeclaracaoInicio = 
+            declaracoes.filter(d => d instanceof FuncaoDeclaracao && d.simbolo.lexema === 'inicio');
+
+        if (encontrarDeclaracaoInicio.length <= 0) {
+            throw this.erro(this.simbolos[0], "Função 'inicio()' para iniciar o programa não foi definida.");
+        }
+
+        // A última declaração do programa deve ser uma chamada a inicio()
+        const declaracaoInicio = encontrarDeclaracaoInicio[0];
+        declaracoes.push(
+            new Expressao(
+                new Chamada(
+                    declaracaoInicio.hashArquivo,
+                    (declaracaoInicio as any).funcao,
+                    null, 
+                    []
+                )
+            )
+        );
+    }
+
+    primario(): Construto {
+        if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.REAL, tiposDeSimbolos.CADEIA)) {
+            const simboloAnterior: SimboloInterface = this.simbolos[this.atual - 1];
+            return new Literal(this.hashArquivo, Number(simboloAnterior.linha), simboloAnterior.literal);
+        }
+    }
+
+    chamar(): Construto {
+        let expressao = this.primario();
+
+        while (true) {
+            if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.PARENTESE_ESQUERDO)) {
+                expressao = this.finalizarChamada(expressao);
+            } else if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.COLCHETE_ESQUERDO)) {
+                const indices = [];
+                do {
+                    indices.push(this.expressao());
+                } while (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.VIRGULA));
+
+                const indice = indices[0];
+                const simboloFechamento = this.consumir(
+                    tiposDeSimbolos.COLCHETE_DIREITO,
+                    "Esperado ']' após escrita do indice."
+                );
+                expressao = new AcessoIndiceVariavel(this.hashArquivo, expressao, indice, simboloFechamento);
+            } else {
+                break;
+            }
+        }
+
+        return expressao;
+    }
+
+    atribuir(): Construto {
+        const expressao = this.ou();
+
+        if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.IGUAL)) {
+            const setaAtribuicao = this.simbolos[this.atual - 1];
+            const valor = this.atribuir();
+
+            if (expressao instanceof Variavel) {
+                const simbolo = expressao.simbolo;
+                return new Atribuir(this.hashArquivo, simbolo, valor);
+            } else if (expressao instanceof AcessoIndiceVariavel) {
+                return new AtribuicaoSobrescrita(
+                    this.hashArquivo,
+                    expressao.linha,
+                    expressao.entidadeChamada,
+                    expressao.indice,
+                    valor
+                );
+            }
+
+            this.erro(setaAtribuicao, 'Tarefa de atribuição inválida');
+        }
+
+        return expressao;
+    }
+
+    declaracaoEscreva(): Escreva {
+        const simboloAtual = this.avancarEDevolverAnterior();
+
+        this.consumir(tiposDeSimbolos.PARENTESE_ESQUERDO, "Esperado '(' antes dos valores em escreva.");
+
+        const argumentos: Construto[] = [];
+
+        do {
+            argumentos.push(this.expressao());
+        } while (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.VIRGULA));
+
+        this.consumir(tiposDeSimbolos.PARENTESE_DIREITO, "Esperado ')' após os valores em escreva.");
+
+        return new Escreva(Number(simboloAtual.linha), simboloAtual.hashArquivo, argumentos);
+    }
+
+    blocoEscopo(): Declaracao[] {
+        const declaracoes: Array<RetornoDeclaracao> = [];
+
+        while (!this.verificarTipoSimboloAtual(tiposDeSimbolos.CHAVE_DIREITA) && !this.estaNoFinal()) {
+            declaracoes.push(this.declaracao());
+        }
+
+        this.consumir(tiposDeSimbolos.CHAVE_DIREITA, "Esperado '}' após o bloco.");
+        return declaracoes;
+    }
+
+    declaracaoSe(): Se {
+        throw new Error("Método não implementado.");
+    }
+
+    declaracaoEnquanto(): Enquanto {
+        throw new Error("Método não implementado.");
+    }
+
+    declaracaoPara(): Para {
+        throw new Error("Método não implementado.");
+    }
+
+    declaracaoEscolha(): Escolha {
+        throw new Error("Método não implementado.");
+    }
+
+    declaracaoFazer(): Fazer {
+        throw new Error("Método não implementado.");
+    }
+
+    corpoDaFuncao(tipo: string): FuncaoConstruto {
+        // O parêntese esquerdo é considerado o símbolo inicial para
+        // fins de pragma.
+        const parenteseEsquerdo = this.consumir(
+            tiposDeSimbolos.PARENTESE_ESQUERDO,
+            `Esperado '(' após o nome ${tipo}.`
+        );
+
+        let parametros = [];
+        if (!this.verificarTipoSimboloAtual(tiposDeSimbolos.PARENTESE_DIREITO)) {
+            parametros = this.logicaComumParametros();
+        }
+
+        this.consumir(tiposDeSimbolos.PARENTESE_DIREITO, "Esperado ')' após parâmetros.");
+        this.consumir(tiposDeSimbolos.CHAVE_ESQUERDA, `Esperado '{' antes do escopo do ${tipo}.`);
+
+        const corpo = this.blocoEscopo();
+
+        return new FuncaoConstruto(this.hashArquivo, Number(parenteseEsquerdo.linha), parametros, corpo);
+    }
+
+    expressao(): Construto {
+        // if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.LEIA)) return this.declaracaoLeia();
+        return this.atribuir();
+    }
+
+    declaracao(): Declaracao | Declaracao[] | Construto | Construto[] | any {
+        const simboloAtual = this.simbolos[this.atual];
+        switch (simboloAtual.tipo) {
+            case tiposDeSimbolos.ESCREVA:
+                return this.declaracaoEscreva();
+            case tiposDeSimbolos.FUNCAO:
+                return this.funcao('funcao');
+            case tiposDeSimbolos.PROGRAMA:
+            case tiposDeSimbolos.CHAVE_DIREITA:
+                this.avancarEDevolverAnterior();
+                return null;
+            default:
+                return this.expressao();
+        }
+    }
+
+    analisar(retornoLexador: RetornoLexador, hashArquivo?: number): RetornoAvaliadorSintatico {
+        this.erros = [];
+        this.atual = 0;
+        this.blocos = 0;
+
+        this.hashArquivo = hashArquivo || 0;
+        this.simbolos = retornoLexador?.simbolos || [];
+
+        const declaracoes: Declaracao[] = [];
+        this.validarEscopoPrograma(declaracoes);
+
+        return {
+            declaracoes: declaracoes.filter(d => d),
+            erros: this.erros,
+        } as RetornoAvaliadorSintatico;
+    }
+}
\ No newline at end of file
diff --git a/fontes/delegua.ts b/fontes/delegua.ts
index a38bb1e7..ff8778a2 100644
--- a/fontes/delegua.ts
+++ b/fontes/delegua.ts
@@ -37,6 +37,8 @@ import { TradutorJavaScript, TradutorReversoJavaScript } from './tradutores';
 import { InterpretadorVisuAlg } from './interpretador/dialetos/visualg';
 import { ErroInterpretador } from './interpretador';
 import { InterpretadorVisuAlgComDepuracao } from './interpretador/dialetos';
+import { LexadorPortugolStudio } from './lexador/dialetos/lexador-portugol-studio';
+import { AvaliadorSintaticoPortugolStudio } from './avaliador-sintatico/dialetos/avaliador-sintatico-portugol-studio';
 
 /**
  * O núcleo da linguagem.
@@ -50,6 +52,7 @@ export class Delegua implements DeleguaInterface {
         delegua: 'padrão',
         egua: 'Égua',
         eguap: 'ÉguaP',
+        'portugol-studio': 'Portugol Studio',
         visualg: 'VisuAlg',
     };
 
@@ -125,6 +128,20 @@ export class Delegua implements DeleguaInterface {
                     ? new InterpretadorComDepuracao(this.importador, process.cwd(), funcaoDeRetorno)
                     : new Interpretador(this.importador, process.cwd(), performance, funcaoDeRetorno);
                 break;
+            case 'portugol-studio':
+                this.lexador = new LexadorPortugolStudio();
+                this.avaliadorSintatico = new AvaliadorSintaticoPortugolStudio();
+                this.importador = new Importador(
+                    this.lexador,
+                    this.avaliadorSintatico,
+                    this.arquivosAbertos,
+                    this.conteudoArquivosAbertos,
+                    depurador
+                );
+                this.interpretador = depurador
+                    ? new InterpretadorComDepuracao(this.importador, process.cwd(), funcaoDeRetorno)
+                    : new Interpretador(this.importador, process.cwd(), performance, funcaoDeRetorno);
+                break;
             case 'visualg':
                 this.lexador = new LexadorVisuAlg();
                 this.avaliadorSintatico = new AvaliadorSintaticoVisuAlg();
diff --git a/fontes/lexador/dialetos/lexador-portugol-studio.ts b/fontes/lexador/dialetos/lexador-portugol-studio.ts
new file mode 100644
index 00000000..f5f1b985
--- /dev/null
+++ b/fontes/lexador/dialetos/lexador-portugol-studio.ts
@@ -0,0 +1,263 @@
+import { RetornoLexador } from "../../interfaces/retornos";
+import { ErroLexador } from "../erro-lexador";
+import { LexadorBase } from "../lexador-base";
+
+import palavrasReservadas from './palavras-reservadas/portugol-studio';
+import tiposDeSimbolos from '../../tipos-de-simbolos/portugol-studio';
+
+/**
+ * O Lexador é responsável por transformar o código em uma coleção de tokens de linguagem.
+ * Cada token de linguagem é representado por um tipo, um lexema e informações da linha de código em que foi expresso.
+ * Também é responsável por mapear as palavras reservadas da linguagem, que não podem ser usadas por outras
+ * estruturas, tais como nomes de variáveis, funções, literais, classes e assim por diante.
+ */
+export class LexadorPortugolStudio extends LexadorBase {
+    analisarTexto(delimitador: string): void {
+        while (this.simboloAtual() !== delimitador && !this.eFinalDoCodigo()) {
+            this.avancar();
+        }
+
+        if (this.eFinalDoCodigo()) {
+            this.erros.push({
+                linha: this.linha + 1,
+                caractere: this.simboloAnterior(),
+                mensagem: 'Cadeia de caracteres não finalizada.',
+            } as ErroLexador);
+            return;
+        }
+
+        const valor = this.codigo[this.linha].substring(this.inicioSimbolo + 1, this.atual);
+        this.adicionarSimbolo(tiposDeSimbolos.CADEIA, valor);
+    }
+
+    analisarNumero(): void {
+        while (this.eDigito(this.simboloAtual())) {
+            this.avancar();
+        }
+
+        if (this.simboloAtual() == '.' && this.eDigito(this.proximoSimbolo())) {
+            this.avancar();
+
+            while (this.eDigito(this.simboloAtual())) {
+                this.avancar();
+            }
+        }
+
+        const numeroCompleto = this.codigo[this.linha].substring(this.inicioSimbolo, this.atual);
+
+        this.adicionarSimbolo(tiposDeSimbolos.REAL, parseFloat(numeroCompleto));
+    }
+
+    identificarPalavraChave(): void {
+        while (this.eAlfabetoOuDigito(this.simboloAtual())) {
+            this.avancar();
+        }
+
+        const codigo: string = this.codigo[this.linha].substring(this.inicioSimbolo, this.atual);
+
+        const tipo: string = codigo in palavrasReservadas ? palavrasReservadas[codigo] : tiposDeSimbolos.IDENTIFICADOR;
+
+        this.adicionarSimbolo(tipo);
+    }
+
+    analisarToken(): void {
+        const caractere = this.simboloAtual();
+
+        switch (caractere) {
+            case '[':
+                this.adicionarSimbolo(tiposDeSimbolos.COLCHETE_ESQUERDO);
+                this.avancar();
+                break;
+            case ']':
+                this.adicionarSimbolo(tiposDeSimbolos.COLCHETE_DIREITO);
+                this.avancar();
+                break;
+            case '(':
+                this.adicionarSimbolo(tiposDeSimbolos.PARENTESE_ESQUERDO);
+                this.avancar();
+                break;
+            case ')':
+                this.adicionarSimbolo(tiposDeSimbolos.PARENTESE_DIREITO);
+                this.avancar();
+                break;
+            case '{':
+                this.adicionarSimbolo(tiposDeSimbolos.CHAVE_ESQUERDA);
+                this.avancar();
+                break;
+            case '}':
+                this.adicionarSimbolo(tiposDeSimbolos.CHAVE_DIREITA);
+                this.avancar();
+                break;
+            case ',':
+                this.adicionarSimbolo(tiposDeSimbolos.VIRGULA);
+                this.avancar();
+                break;
+            case '.':
+                this.adicionarSimbolo(tiposDeSimbolos.PONTO);
+                this.avancar();
+                break;
+            case '-':
+                this.adicionarSimbolo(tiposDeSimbolos.SUBTRACAO);
+                this.avancar();
+                break;
+            case '+':
+                this.adicionarSimbolo(tiposDeSimbolos.ADICAO);
+                this.avancar();
+                break;
+
+            /* case '%':
+                this.inicioSimbolo = this.atual;
+                this.avancar();
+                switch (this.simboloAtual()) {
+                    case '=':
+                        this.avancar();
+                        this.adicionarSimbolo(tiposDeSimbolos.MODULO_IGUAL);
+                        break;
+                    default:
+                        this.adicionarSimbolo(tiposDeSimbolos.MODULO);
+                        break;
+                }
+
+                break; */
+            case '*':
+                this.adicionarSimbolo(tiposDeSimbolos.MULTIPLICACAO);
+                this.avancar();
+                break;
+            /* case '!':
+                this.avancar();
+                if (this.simboloAtual() === '=') {
+                    this.adicionarSimbolo(tiposDeSimbolos.DIFERENTE);
+                    this.avancar();
+                } else {
+                    this.adicionarSimbolo(tiposDeSimbolos.NEGACAO);
+                }
+
+                break; */
+            case '=':
+                this.avancar();
+                if (this.simboloAtual() === '=') {
+                    this.adicionarSimbolo(tiposDeSimbolos.IGUAL_IGUAL);
+                    this.avancar();
+                } else {
+                    this.adicionarSimbolo(tiposDeSimbolos.IGUAL);
+                }
+
+                break;
+
+            /* case '&':
+                this.adicionarSimbolo(tiposDeSimbolos.BIT_AND);
+                this.avancar();
+                break;
+
+            case '~':
+                this.adicionarSimbolo(tiposDeSimbolos.BIT_NOT);
+                this.avancar();
+                break;
+
+            case '|':
+                this.adicionarSimbolo(tiposDeSimbolos.BIT_OR);
+                this.avancar();
+                break;
+
+            case '^':
+                this.adicionarSimbolo(tiposDeSimbolos.BIT_XOR);
+                this.avancar();
+                break; */
+
+            case '<':
+                this.avancar();
+                if (this.simboloAtual() === '=') {
+                    this.adicionarSimbolo(tiposDeSimbolos.MENOR_IGUAL);
+                    this.avancar();
+                } else {
+                    this.adicionarSimbolo(tiposDeSimbolos.MENOR);
+                }
+                break;
+
+            case '>':
+                this.avancar();
+                if (this.simboloAtual() === '=') {
+                    this.adicionarSimbolo(tiposDeSimbolos.MAIOR_IGUAL);
+                    this.avancar();
+                } else {
+                    this.adicionarSimbolo(tiposDeSimbolos.MAIOR);
+                }
+                break;
+
+            case '/':
+                this.avancar();
+                switch (this.simboloAtual()) {
+                    case '/':
+                        this.avancarParaProximaLinha();
+                        break;
+                    case '*':
+                        this.encontrarFimComentarioAsterisco();
+                        break;
+                    default:
+                        this.adicionarSimbolo(tiposDeSimbolos.DIVISAO);
+                        break;
+                }
+
+                break;
+
+            // Esta sessão ignora espaços em branco na tokenização.
+            // Ponto-e-vírgula é opcional em Delégua, então pode apenas ser ignorado.
+            case ' ':
+            case '\0':
+            case '\r':
+            case '\t':
+            case ';':
+                this.avancar();
+                break;
+
+            case '"':
+                this.avancar();
+                this.analisarTexto('"');
+                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.erros = [];
+        this.simbolos = [];
+        this.inicioSimbolo = 0;
+        this.atual = 0;
+        this.linha = 0;
+
+        this.codigo = codigo || [''];
+        this.hashArquivo = hashArquivo;
+
+        for (let iterador = 0; iterador < this.codigo.length; iterador++) {
+            this.codigo[iterador] += '\0';
+        }
+
+        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/index.ts b/fontes/lexador/dialetos/palavras-reservadas/index.ts
index f500f303..fd01421c 100644
--- a/fontes/lexador/dialetos/palavras-reservadas/index.ts
+++ b/fontes/lexador/dialetos/palavras-reservadas/index.ts
@@ -1,2 +1,3 @@
 export * from './birl';
+export * from './portugol-studio';
 export * from './visualg';
diff --git a/fontes/lexador/dialetos/palavras-reservadas/portugol-studio.ts b/fontes/lexador/dialetos/palavras-reservadas/portugol-studio.ts
new file mode 100644
index 00000000..eac8e668
--- /dev/null
+++ b/fontes/lexador/dialetos/palavras-reservadas/portugol-studio.ts
@@ -0,0 +1,7 @@
+import tiposDeSimbolos from '../../../tipos-de-simbolos/portugol-studio';
+
+export default {
+    escreva: tiposDeSimbolos.ESCREVA,
+    funcao: tiposDeSimbolos.FUNCAO,
+    programa: tiposDeSimbolos.PROGRAMA,
+}
\ No newline at end of file
diff --git a/fontes/lexador/lexador-base.ts b/fontes/lexador/lexador-base.ts
new file mode 100644
index 00000000..7edc2b4d
--- /dev/null
+++ b/fontes/lexador/lexador-base.ts
@@ -0,0 +1,146 @@
+import { LexadorInterface, SimboloInterface } from "../interfaces";
+import { RetornoLexador } from "../interfaces/retornos";
+import { ErroLexador } from "./erro-lexador";
+import { Simbolo } from "./simbolo";
+
+/**
+ * Essa versão do Lexador Base é por padrão com comentários multilinha.
+ * Em outras palavras, se o dialeto da linguagem terá comentários multilinha, 
+ * este Lexador Base deverá ser usado.
+ */
+export abstract class LexadorBase implements LexadorInterface {
+    erros: ErroLexador[];
+    hashArquivo: number;
+
+    simbolos: SimboloInterface[];
+    codigo: string[];
+    inicioSimbolo: number;
+    atual: number;
+    linha: number;
+
+    constructor() {
+        this.simbolos = [];
+        this.erros = [];
+
+        this.inicioSimbolo = 0;
+        this.atual = 0;
+        this.linha = 0;
+    }
+
+    protected avancarParaProximaLinha(): void {
+        this.linha++;
+        this.atual = 0;
+    }
+
+    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);
+    }
+
+    /**
+     * Indica se o código está na última linha.
+     * @returns Verdadeiro se contador de linhas está na última linha.
+     *          Falso caso contrário.
+     */
+    protected eUltimaLinha(): boolean {
+        return this.linha >= this.codigo.length - 1;
+    }
+
+    eFinalDoCodigo(): boolean {
+        return this.eUltimaLinha() && this.codigo[this.codigo.length - 1].length <= this.atual;
+    }
+
+    eFinalDaLinha(): boolean {
+        if (this.codigo.length === this.linha) {
+            return true;
+        }
+        return this.atual >= this.codigo[this.linha].length;
+    }
+
+    protected encontrarFimComentarioAsterisco(): void {
+        while (!this.eFinalDoCodigo()) {
+            this.avancar();
+            if (this.simboloAtual() === '*' && this.proximoSimbolo() === '/') {
+                this.avancar();
+                this.avancar();
+                break;
+            }
+        }
+    }
+
+    avancar(): void {
+        this.atual += 1;
+        if (this.eFinalDaLinha() && !this.eUltimaLinha()) {
+            this.linha++;
+            this.atual = 0;
+        }
+    }
+
+    adicionarSimbolo(tipo: any, literal?: any): void {
+        const texto: string = this.codigo[this.linha].substring(this.inicioSimbolo, this.atual);
+        this.simbolos.push(new Simbolo(tipo, literal || texto, literal, this.linha + 1, this.hashArquivo));
+    }
+
+    simboloAtual(): string {
+        if (this.eFinalDoCodigo()) return '\0';
+        return this.codigo[this.linha].charAt(this.atual);
+    }
+
+    proximoSimbolo(): string {
+        if (this.atual + 1 >= this.codigo.length) return '\0';
+        return this.codigo[this.linha].charAt(this.atual + 1);
+    }
+
+    simboloAnterior(): string {
+        return this.codigo[this.linha].charAt(this.atual - 1);
+    }
+
+    abstract analisarTexto(delimitador: string): void;
+
+    abstract analisarNumero(): void;
+
+    abstract identificarPalavraChave(): void;
+
+    abstract analisarToken(): void;
+
+    abstract mapear(codigo: string[], hashArquivo: number): RetornoLexador;
+}
\ No newline at end of file
diff --git a/fontes/tipos-de-simbolos/portugol-studio.ts b/fontes/tipos-de-simbolos/portugol-studio.ts
new file mode 100644
index 00000000..b1079d1d
--- /dev/null
+++ b/fontes/tipos-de-simbolos/portugol-studio.ts
@@ -0,0 +1,32 @@
+export default {
+    ADICAO: 'ADICAO',
+    CADEIA: 'CADEIA',
+    CHAVE_ESQUERDA: 'CHAVE_ESQUERDA',
+    CHAVE_DIREITA: 'CHAVE_DIREITA',
+    COLCHETE_ESQUERDO: 'COLCHETE_ESQUERDO',
+    COLCHETE_DIREITO: 'COLCHETE_DIREITO',
+    DIFERENTE: 'DIFERENTE',
+    DIVISAO: 'DIVISAO',
+    DIVISAO_INTEIRA: 'DIVISAO_INTEIRA',
+    E: 'E',
+    ESCREVA: 'ESCREVA',
+    FUNCAO: 'FUNCAO',
+    IDENTIFICADOR: 'IDENTIFICADOR',
+    IGUAL: 'IGUAL',
+    IGUAL_IGUAL: 'IGUAL_IGUAL',
+    MAIOR: 'MAIOR',
+    MAIOR_IGUAL: 'MAIOR_IGUAL',
+    MENOR: 'MENOR',
+    MENOR_IGUAL: 'MENOR_IGUAL',
+    MODULO: 'MODULO',
+    MULTIPLICACAO: 'MULTIPLICACAO',
+    NEGACAO: 'NEGACAO',
+    OU: 'OU',
+    PARENTESE_ESQUERDO: 'PARENTESE_ESQUERDO',
+    PARENTESE_DIREITO: 'PARENTESE_DIREITO',
+    PONTO: 'PONTO',
+    PROGRAMA: 'PROGRAMA',
+    REAL: 'REAL',
+    SUBTRACAO: 'SUBTRACAO',
+    VIRGULA: 'VIRGULA',
+};


Test patch:
diff --git a/testes/portugol-studio/avaliador-sintatico.test.ts b/testes/portugol-studio/avaliador-sintatico.test.ts
new file mode 100644
index 00000000..c23da2e8
--- /dev/null
+++ b/testes/portugol-studio/avaliador-sintatico.test.ts
@@ -0,0 +1,55 @@
+import { ErroAvaliadorSintatico } from "../../fontes/avaliador-sintatico/erro-avaliador-sintatico";
+import { Delegua } from "../../fontes/delegua";
+
+describe('Avaliador sintático (Portugol Studio)', () => {
+    describe('analisar()', () => {
+        let delegua: Delegua;
+
+        beforeEach(() => {
+            delegua = new Delegua('portugol-studio');
+        });
+
+        it('Sucesso - Olá Mundo', () => {
+            const retornoLexador = delegua.lexador.mapear(
+                [
+                    'programa',
+                    '{',
+                    '   ', 
+                    '    funcao inicio()',
+                    '    {',
+                    '        escreva("Olá Mundo")',
+                    '    }',
+                    '}'
+                ],
+                -1
+            );
+            const retornoAvaliadorSintatico =
+                delegua.avaliadorSintatico.analisar(retornoLexador);
+
+            expect(retornoAvaliadorSintatico).toBeTruthy();
+            expect(retornoAvaliadorSintatico.declaracoes).toHaveLength(2);
+        });
+
+        it('Falha - Função `inicio()` não definida', () => {
+            const retornoLexador = delegua.lexador.mapear(
+                [
+                    'programa',
+                    '{',
+                    '   ', 
+                    '    funcao teste()',
+                    '    {',
+                    '        escreva("Olá Mundo")',
+                    '    }',
+                    '}'
+                ],
+                -1
+            );
+
+            const t = () => {
+                delegua.avaliadorSintatico.analisar(retornoLexador);
+            };
+
+            expect(t).toThrow(ErroAvaliadorSintatico);
+        });
+    });
+});
diff --git a/testes/portugol-studio/interpretador.test.ts b/testes/portugol-studio/interpretador.test.ts
new file mode 100644
index 00000000..ed271442
--- /dev/null
+++ b/testes/portugol-studio/interpretador.test.ts
@@ -0,0 +1,31 @@
+import { Delegua } from '../../fontes/delegua';
+
+describe('Interpretador', () => {
+    describe('interpretar()', () => {
+        let delegua: Delegua;
+
+        beforeEach(() => {
+            delegua = new Delegua('portugol-studio');
+        });
+
+        describe('Cenários de sucesso', () => {
+            it('Trivial', async () => {
+                const retornoLexador = delegua.lexador.mapear([
+                    'programa',
+                    '{',
+                    '   ', 
+                    '    funcao inicio()',
+                    '    {',
+                    '        escreva("Olá Mundo")',
+                    '    }',
+                    '}'
+                ], -1);
+                const retornoAvaliadorSintatico = delegua.avaliadorSintatico.analisar(retornoLexador);
+
+                const retornoInterpretador = await delegua.interpretador.interpretar(retornoAvaliadorSintatico.declaracoes);
+
+                expect(retornoInterpretador.erros).toHaveLength(0);
+            });
+        });
+    });
+});
diff --git a/testes/portugol-studio/lexador.test.ts b/testes/portugol-studio/lexador.test.ts
new file mode 100644
index 00000000..1241457c
--- /dev/null
+++ b/testes/portugol-studio/lexador.test.ts
@@ -0,0 +1,36 @@
+import { Delegua } from '../../fontes/delegua';
+
+describe('Lexador (Portugol Studio)', () => {
+    describe('mapear()', () => {
+        let delegua: Delegua;
+
+        beforeEach(() => {
+            delegua = new Delegua('portugol-studio');
+        });
+
+        describe('Cenário de sucesso', () => {
+            it('Arquivo vazio.', () => {
+                const resultado = delegua.lexador.mapear([''], -1);
+
+                expect(resultado).toBeTruthy();
+                expect(resultado.simbolos).toHaveLength(0);
+            });
+
+            it('Programa vazio.', () => {
+                const resultado = delegua.lexador.mapear([
+                    'programa',
+                    '{',
+                    '   ', 
+                    '    funcao inicio()',
+                    '    {',
+                    '',
+                    '    }',
+                    '}'
+                ], -1);
+
+                expect(resultado).toBeTruthy();
+                expect(resultado.simbolos).toHaveLength(9);
+            });
+        });
+    });
+});