diff --git a/fontes/avaliador-sintatico/avaliador-sintatico-portugol-studio.ts b/fontes/avaliador-sintatico/avaliador-sintatico-portugol-studio.ts index 1025b0c..cc04ce2 100644 --- a/fontes/avaliador-sintatico/avaliador-sintatico-portugol-studio.ts +++ b/fontes/avaliador-sintatico/avaliador-sintatico-portugol-studio.ts @@ -40,6 +40,7 @@ import { RetornoDeclaracao } from '@designliquido/delegua/avaliador-sintatico/re import { ErroAvaliadorSintatico } from '@designliquido/delegua/avaliador-sintatico/erro-avaliador-sintatico'; import { TipoDadosElementar } from '@designliquido/delegua/tipo-dados-elementar'; +import { Matriz } from '../construtos/matriz'; import tiposDeSimbolos from '../tipos-de-simbolos/lexico-regular'; /** @@ -48,13 +49,13 @@ import tiposDeSimbolos from '../tipos-de-simbolos/lexico-regular'; * Há dois grupos de estruturas de alto nível: Construtos e Declarações. */ export class AvaliadorSintaticoPortugolStudio extends AvaliadorSintaticoBase { - private declaracoes: Declaracao[] = [] + private declaracoes: Declaracao[] = []; declaracaoEscreva(): Escreva { throw new Error('Método não implementado.'); } - private validarEscopoPrograma(): void { + private validarEscopoProgramaEAvaliacaoSintatica(): void { this.consumir(tiposDeSimbolos.PROGRAMA, "Esperada expressão 'programa' para inicializar programa."); this.consumir( @@ -213,7 +214,7 @@ export class AvaliadorSintaticoPortugolStudio extends AvaliadorSintaticoBase { } /** - * Declaração para inclusão de uma biblioteca. + * Declaração para inclusão de uma biblioteca. * Exemplo: `inclua biblioteca Matematica --> mat` seria o mesmo que * `const mat = importar('Matematica')` em Delégua, ou * `inclua biblioteca Matematica` (sem o nome da variável) seria o @@ -223,10 +224,16 @@ export class AvaliadorSintaticoPortugolStudio extends AvaliadorSintaticoBase { declaracaoInclua(): Const { this.avancarEDevolverAnterior(); this.consumir(tiposDeSimbolos.BIBLIOTECA, 'Esperado palavra reservada "biblioteca" após "inclua".'); - const nomeBiblioteca = this.consumir(tiposDeSimbolos.IDENTIFICADOR, 'Esperado identificador com nome de biblioteca após palavra reservada "biblioteca"'); + const nomeBiblioteca = this.consumir( + tiposDeSimbolos.IDENTIFICADOR, + 'Esperado identificador com nome de biblioteca após palavra reservada "biblioteca"' + ); let constanteBiblioteca = nomeBiblioteca; if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.SETA)) { - constanteBiblioteca = this.consumir(tiposDeSimbolos.IDENTIFICADOR, 'Esperado identificador com nome de constante de biblioteca após seta de atribuição em declaração "inclua".'); + constanteBiblioteca = this.consumir( + tiposDeSimbolos.IDENTIFICADOR, + 'Esperado identificador com nome de constante de biblioteca após seta de atribuição em declaração "inclua".' + ); } return new Const( @@ -297,7 +304,6 @@ export class AvaliadorSintaticoPortugolStudio extends AvaliadorSintaticoBase { let caminhoPadrao = null; while (!this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.CHAVE_DIREITA) && !this.estaNoFinal()) { if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.CASO)) { - if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.CONTRARIO)) { if (caminhoPadrao !== null) { const excecao = new ErroAvaliadorSintatico( @@ -314,7 +320,7 @@ export class AvaliadorSintaticoPortugolStudio extends AvaliadorSintaticoBase { do { declaracoes.push(this.resolverDeclaracaoForaDeBloco()); - this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.PARE) + this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.PARE); } while ( !this.verificarTipoSimboloAtual(tiposDeSimbolos.CASO) && !this.verificarTipoSimboloAtual(tiposDeSimbolos.CONTRARIO) && @@ -344,7 +350,7 @@ export class AvaliadorSintaticoPortugolStudio extends AvaliadorSintaticoBase { } else { declaracoes.push(retornoDeclaracao as Declaracao); } - this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.PARE) + this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.PARE); } while ( !this.verificarTipoSimboloAtual(tiposDeSimbolos.CASO) && !this.verificarTipoSimboloAtual(tiposDeSimbolos.CONTRARIO) && @@ -500,7 +506,11 @@ export class AvaliadorSintaticoPortugolStudio extends AvaliadorSintaticoBase { } inicializacoes.push( - new Var(identificador, new Literal(this.hashArquivo, Number(simboloCadeia.linha), valorInicializacao), "caracter") + new Var( + identificador, + new Literal(this.hashArquivo, Number(simboloCadeia.linha), valorInicializacao), + 'caracter' + ) ); } while (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.VIRGULA)); @@ -517,19 +527,15 @@ export class AvaliadorSintaticoPortugolStudio extends AvaliadorSintaticoBase { "Esperado identificador após palavra reservada 'caracter'." ); - // Inicializações de variáveis podem ter valores definidos. - let valorInicializacao = ''; - if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.IGUAL)) { - const literalInicializacao = this.consumir( - tiposDeSimbolos.CARACTER, - 'Esperado literal de caracter após símbolo de igual em declaração de variável.' + const dimensoes = this.logicaComumDimensoesMatrizes(); + + if (dimensoes.length > 0) { + inicializacoes.push( + this.declaracaoVetorOuMatriz(simboloCaracter, identificador, dimensoes, 'caracter') ); - valorInicializacao = literalInicializacao.literal; + } else { + inicializacoes.push(this.declaracaoVariavelSemDimensoes(simboloCaracter, identificador, 'caracter')); } - - inicializacoes.push( - new Var(identificador, new Literal(this.hashArquivo, Number(simboloCaracter.linha), valorInicializacao), "caracter") - ); } while (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.VIRGULA)); return inicializacoes; @@ -546,48 +552,91 @@ export class AvaliadorSintaticoPortugolStudio extends AvaliadorSintaticoBase { return new Expressao(expressao); } - protected declaracaoVetorInteiros( - simboloInteiro: SimboloInterface, - identificador: SimboloInterface, - posicoes: number - ) { - let valorInicializacao: Vetor = new Vetor(this.hashArquivo, Number(simboloInteiro.linha), []); - if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.IGUAL)) { - this.consumir( - tiposDeSimbolos.CHAVE_ESQUERDA, - 'Esperado chave esquerda após sinal de igual em lado direito da atribuição de vetor.' - ); + /** + * Método recursivo que lê os valores de inicialização de uma matriz de N dimensões. + * @param {number[]} dimensoes O número de dimensões faltantes. + * Cada passo recursivo usa o primeiro valor e chama a função passando esse vetor, mas sem + * o primeiro valor. + */ + protected lerValoresAtribuicaoMatriz(dimensoes: number[]) { + this.consumir( + tiposDeSimbolos.CHAVE_ESQUERDA, + 'Esperado chave esquerda após sinal de igual em lado direito da atribuição de vetor.' + ); - const valores = []; - do { + const valores = []; + do { + if (dimensoes.length === 1) { valores.push(this.primario()); - } while (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.VIRGULA)); + } else { + const valoresProximaDimensao = this.lerValoresAtribuicaoMatriz(dimensoes.slice(1)); + valores.push(valoresProximaDimensao); + } + } while (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.VIRGULA)); - this.consumir( - tiposDeSimbolos.CHAVE_DIREITA, - 'Esperado chave direita após valores de vetor em lado direito da atribuição de vetor.' + this.consumir( + tiposDeSimbolos.CHAVE_DIREITA, + 'Esperado chave direita após valores de vetor em lado direito da atribuição de vetor.' + ); + + // TODO: Recolocar. + /* if (dimensoes !== valores.length) { + throw this.erro( + simboloInteiro, + `Esperado ${dimensoes} números, mas foram fornecidos ${valores.length} valores do lado direito da atribuição.` ); + } */ - if (posicoes !== valores.length) { - throw this.erro( - simboloInteiro, - `Esperado ${posicoes} números, mas foram fornecidos ${valores.length} valores do lado direito da atribuição.` - ); - } + return valores; + } - valorInicializacao.valores = valores; + protected declaracaoVetorOuMatriz( + simboloTipo: SimboloInterface, + identificador: SimboloInterface, + dimensoes: number[], + tipoDados: string = 'inteiro' + ) { + let valorInicializacao: Matriz = new Matriz(this.hashArquivo, Number(simboloTipo.linha), dimensoes, null); + if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.IGUAL)) { + valorInicializacao.valores = this.lerValoresAtribuicaoMatriz(dimensoes); } - return new Var(identificador, valorInicializacao, "inteiro[]"); + return new Var(identificador, valorInicializacao, `${tipoDados}[]` as any); } - protected declaracaoTrivialInteiro(simboloInteiro: SimboloInterface, identificador: SimboloInterface) { + protected declaracaoVariavelSemDimensoes( + simboloInteiro: SimboloInterface, + identificador: SimboloInterface, + tipoDados: string = 'inteiro' + ) { // Inicializações de variáveis podem ter valores definidos. let valorInicializacao: Construto = new Literal(this.hashArquivo, Number(simboloInteiro.linha), 0); if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.IGUAL)) { valorInicializacao = this.expressao(); } - return new Var(identificador, valorInicializacao, 'inteiro'); + return new Var(identificador, valorInicializacao, tipoDados as any); + } + + protected logicaComumDimensoesMatrizes(): number[] { + let dimensoes = []; + while (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.COLCHETE_ESQUERDO)) { + let simboloNumeroPosicoes: SimboloInterface; + if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.INTEIRO)) { + simboloNumeroPosicoes = this.simboloAnterior(); + } + + this.consumir( + tiposDeSimbolos.COLCHETE_DIREITO, + 'Esperado fechamento de identificação de número de posições de uma dimensão de vetor ou matriz.' + ); + + // Portugol Studio permite declarar vetores sem posições definidas. + // Quando isso acontece, definimos a quantidade de posições de uma dimensão como -1. + const numeroPosicoes = simboloNumeroPosicoes ? Number(simboloNumeroPosicoes.literal) : -1; + dimensoes.push(numeroPosicoes); + } + + return dimensoes; } declaracaoInteiros(): Var[] { @@ -600,25 +649,15 @@ export class AvaliadorSintaticoPortugolStudio extends AvaliadorSintaticoBase { "Esperado identificador após palavra reservada 'inteiro'." ); - if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.COLCHETE_ESQUERDO)) { - // TODO - const numeroPosicoes = this.consumir( - tiposDeSimbolos.INTEIRO, - 'Esperado número inteiro para definir quantas posições terá o vetor.' - ); + const dimensoes = this.logicaComumDimensoesMatrizes(); - this.consumir( - tiposDeSimbolos.COLCHETE_DIREITO, - 'Esperado fechamento de identificação de número de posições de uma declaração de vetor.' - ); - - inicializacoes.push( - this.declaracaoVetorInteiros(simboloInteiro, identificador, Number(numeroPosicoes.literal)) - ); + if (dimensoes.length > 0) { + inicializacoes.push(this.declaracaoVetorOuMatriz(simboloInteiro, identificador, dimensoes)); } else { - inicializacoes.push(this.declaracaoTrivialInteiro(simboloInteiro, identificador)); + inicializacoes.push(this.declaracaoVariavelSemDimensoes(simboloInteiro, identificador)); } } while (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.VIRGULA)); + return inicializacoes; } @@ -665,7 +704,11 @@ export class AvaliadorSintaticoPortugolStudio extends AvaliadorSintaticoBase { } inicializacoes.push( - new Var(identificador, new Literal(this.hashArquivo, Number(simboloLogico.linha), valorInicializacao), 'lógico') + new Var( + identificador, + new Literal(this.hashArquivo, Number(simboloLogico.linha), valorInicializacao), + 'lógico' + ) ); } while (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.VIRGULA)); @@ -673,7 +716,7 @@ export class AvaliadorSintaticoPortugolStudio extends AvaliadorSintaticoBase { } declaracaoRetorne(): Retorna { - this.avancarEDevolverAnterior() + this.avancarEDevolverAnterior(); const simboloChave = this.simbolos[this.atual]; let valor = null; @@ -719,7 +762,7 @@ export class AvaliadorSintaticoPortugolStudio extends AvaliadorSintaticoBase { let incrementar = null; if (!this.verificarTipoSimboloAtual(tiposDeSimbolos.PARENTESE_DIREITO)) { incrementar = this.expressao(); - this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.INCREMENTAR, tiposDeSimbolos.DECREMENTAR) + this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.INCREMENTAR, tiposDeSimbolos.DECREMENTAR); } this.consumir(tiposDeSimbolos.PARENTESE_DIREITO, "Esperado ')' após cláusulas"); @@ -742,25 +785,18 @@ export class AvaliadorSintaticoPortugolStudio extends AvaliadorSintaticoBase { "Esperado identificador após palavra reservada 'real'." ); - // Inicializações de variáveis podem ter valores definidos. - let valorInicializacao = 0; - if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.IGUAL)) { - const literalInicializacao = this.consumir( - tiposDeSimbolos.REAL, - 'Esperado literal real após símbolo de igual em declaração de variável.' - ); - valorInicializacao = Number(literalInicializacao.literal); - } + const dimensoes = this.logicaComumDimensoesMatrizes(); - inicializacoes.push( - new Var(identificador, new Literal(this.hashArquivo, Number(simboloReal.linha), valorInicializacao), 'real') - ); + if (dimensoes.length > 0) { + inicializacoes.push(this.declaracaoVetorOuMatriz(simboloReal, identificador, dimensoes, 'real')); + } else { + inicializacoes.push(this.declaracaoVariavelSemDimensoes(simboloReal, identificador, 'real')); + } } while (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.VIRGULA)); return inicializacoes; } - expressao(): Construto { return this.atribuir(); } @@ -871,9 +907,9 @@ export class AvaliadorSintaticoPortugolStudio extends AvaliadorSintaticoBase { this.hashArquivo = hashArquivo || 0; this.simbolos = retornoLexador?.simbolos || []; - this.declaracoes = [] + this.declaracoes = []; - this.validarEscopoPrograma(); + this.validarEscopoProgramaEAvaliacaoSintatica(); return { declaracoes: this.declaracoes.filter((d) => d), diff --git a/fontes/construtos/index.ts b/fontes/construtos/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/fontes/construtos/matriz.ts b/fontes/construtos/matriz.ts new file mode 100644 index 0000000..8925d4c --- /dev/null +++ b/fontes/construtos/matriz.ts @@ -0,0 +1,22 @@ +import { VisitanteComumInterface } from "@designliquido/delegua"; +import { Construto } from "@designliquido/delegua/construtos"; + +import { InterpretadorPortugolStudio } from "../interpretador"; + +export class Matriz implements Construto { + linha: number; + hashArquivo: number; + dimensoes: number[]; + valores: any; + + constructor(hashArquivo: number, linha: number, dimensoes: number[], valores: any) { + this.linha = linha; + this.hashArquivo = hashArquivo; + this.dimensoes = dimensoes; + this.valores = valores; + } + + async aceitar(visitante: VisitanteComumInterface): Promise { + return await (visitante as InterpretadorPortugolStudio).visitarExpressaoMatriz(this); + } +} diff --git a/fontes/interfaces/index.ts b/fontes/interfaces/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/fontes/interpretador/comum.ts b/fontes/interpretador/comum.ts index 1b49d96..a186383 100644 --- a/fontes/interpretador/comum.ts +++ b/fontes/interpretador/comum.ts @@ -8,6 +8,8 @@ import * as calendario from '../bibliotecas/calendario'; import * as matematica from '../bibliotecas/matematica'; import * as texto from '../bibliotecas/texto'; import * as util from '../bibliotecas/util'; +import { Matriz } from 'fontes/construtos/matriz'; +import { InterpretadorPortugolStudio } from './interpretador-portugol-studio'; function carregarBibliotecaCalendario(): DeleguaModulo { const metodos: { [nome: string]: FuncaoPadrao } = { @@ -123,3 +125,30 @@ export async function visitarExpressaoLeiaComum( pilhaEscoposExecucao.definirVariavel(simbolo.lexema, valorLido); } } + +export async function visitarExpressaoMatrizComum( + interpretador: InterpretadorPortugolStudio, + expressao: Matriz +): Promise { + return await resolverValoresMatriz(interpretador, expressao.valores); +} + +/** + * Função recursiva que visita todos os valores de uma matriz. + * @param interpretador A instância do interpretador. + * @param valores A matriz de valores das dimensões ainda não resolvidas. + */ +async function resolverValoresMatriz(interpretador: InterpretadorPortugolStudio, valores: any[]) { + const valoresResolvidos = []; + if (valores && valores.length > 0) { + for (let i = 0; i < valores.length; i++) { + if (Array.isArray(valores[i])) { + valoresResolvidos.push(await resolverValoresMatriz(interpretador, valores[i])); + } else { + valoresResolvidos.push(await interpretador.avaliar(valores[i])); + } + } + } + + return valoresResolvidos; +} \ No newline at end of file diff --git a/fontes/interpretador/interpretador-portugol-studio-com-depuracao.ts b/fontes/interpretador/interpretador-portugol-studio-com-depuracao.ts index 984b7c5..bdff142 100644 --- a/fontes/interpretador/interpretador-portugol-studio-com-depuracao.ts +++ b/fontes/interpretador/interpretador-portugol-studio-com-depuracao.ts @@ -3,6 +3,7 @@ import { InterpretadorComDepuracao } from '@designliquido/delegua/interpretador/ import { PilhaEscoposExecucaoPortugolStudio } from './pilha-escopos-execucao-portugol-studio'; import { DeleguaModulo } from '@designliquido/delegua/estruturas'; +import { Matriz } from '../construtos/matriz'; import * as comum from './comum'; export class InterpretadorPortugolStudioComDepuracao extends InterpretadorComDepuracao { @@ -28,6 +29,10 @@ export class InterpretadorPortugolStudioComDepuracao extends InterpretadorComDep return comum.visitarExpressaoLeiaComum(this.interfaceEntradaSaida, this.pilhaEscoposExecucao, expressao); } + async visitarExpressaoMatriz(expressao: Matriz): Promise { + return comum.visitarExpressaoMatrizComum(this, expressao); + } + /** * No Portugol Studio, como o bloco de execução da função `inicio` é criado * pelo avaliador sintático, precisamos ter uma forma aqui de avançar o diff --git a/fontes/interpretador/interpretador-portugol-studio.ts b/fontes/interpretador/interpretador-portugol-studio.ts index e2612b4..6f0fafc 100644 --- a/fontes/interpretador/interpretador-portugol-studio.ts +++ b/fontes/interpretador/interpretador-portugol-studio.ts @@ -4,6 +4,7 @@ import { EscopoExecucao } from '@designliquido/delegua/interfaces/escopo-execuca import { EspacoVariaveis } from '@designliquido/delegua/espaco-variaveis'; import { DeleguaModulo } from '@designliquido/delegua/estruturas'; +import { Matriz } from '../construtos/matriz'; import { PilhaEscoposExecucaoPortugolStudio } from './pilha-escopos-execucao-portugol-studio'; import * as comum from './comum'; @@ -35,4 +36,8 @@ export class InterpretadorPortugolStudio extends InterpretadorBase { async visitarExpressaoLeia(expressao: Leia): Promise { return comum.visitarExpressaoLeiaComum(this.interfaceEntradaSaida, this.pilhaEscoposExecucao, expressao); } + + async visitarExpressaoMatriz(expressao: Matriz): Promise { + return comum.visitarExpressaoMatrizComum(this, expressao); + } } diff --git a/testes/avaliador-sintatico.test.ts b/testes/avaliador-sintatico.test.ts index f92624f..6f033b3 100644 --- a/testes/avaliador-sintatico.test.ts +++ b/testes/avaliador-sintatico.test.ts @@ -323,6 +323,25 @@ describe('Avaliador sintático (Portugol Studio)', () => { expect(retornoAvaliadorSintatico).toBeTruthy(); expect(retornoAvaliadorSintatico.declaracoes.length).toBeGreaterThan(0); }); + + it('Matrizes', () => { + const retornoLexador = lexador.mapear([ + 'programa', + '{', + ' funcao inicio()', + ' {', + ' //Declaração de uma matriz de inteiros', + ' // de duas linhas e duas colunas já inicializado.', + ' inteiro matriz[2][2] = {{15,22},{10,11}}', + ' }', + '}' + ], -1); + + const retornoAvaliadorSintatico = avaliadorSintatico.analisar(retornoLexador, -1); + + expect(retornoAvaliadorSintatico).toBeTruthy(); + expect(retornoAvaliadorSintatico.declaracoes.length).toBeGreaterThan(0); + }); }); describe('Casos de Falha', () => { diff --git a/testes/interpretador.test.ts b/testes/interpretador.test.ts index 030baea..b083a9e 100644 --- a/testes/interpretador.test.ts +++ b/testes/interpretador.test.ts @@ -439,6 +439,55 @@ describe('Interpretador (Portugol Studio)', () => { expect(retornoInterpretador.erros).toHaveLength(0); expect(_saidas).toBe('9'); }); + + it('Matrizes', async () => { + let _saidas = ""; + interpretador.funcaoDeRetornoMesmaLinha = (saida: string) => { + _saidas += saida; + } + + const retornoLexador = lexador.mapear([ + 'programa', + '{', + ' funcao inicio()', + ' {', + ' //Declaração de uma matriz de inteiros', + ' // de duas linhas e duas colunas já inicializado.', + ' inteiro matriz[2][2] = {{15,22},{10,11}}', + + ' //Atribui -1 na primeira linha e segunda', + ' // coluna da matriz.', + ' matriz[0][1] = -1', + + ' //Imprime o valor 15 correspondente ', + ' // a primeira linha e primeira coluna da matriz.', + ' inteiro i = 0', + ' escreva(matriz[i][0])', + ' escreva("\n")', + + ' //Imprime o valor 11 correspondente ', + ' // a última linha e última coluna da matriz.', + ' escreva(matriz[1][1])', + + ' //Declaração de uma matriz de reais de ', + ' // duas linhas e quatro colunas.', + ' real outra_matriz[2][4]', + + ' //Declaração de uma matriz de caracteres onde o tamanho', + ' // de linhas e colunas são definidos pela inicialização', + ` caracter jogo_velha[][] = {{'X','O','X'}`, + ` ,{'O','X','O'}`, + ` ,{' ',' ','X'}}`, + ' }', + '}' + ], -1); + + const retornoAvaliadorSintatico = avaliadorSintatico.analisar(retornoLexador, -1); + const retornoInterpretador = await interpretador.interpretar(retornoAvaliadorSintatico.declaracoes); + + expect(retornoInterpretador.erros).toHaveLength(0); + expect(_saidas.length).toBeGreaterThanOrEqual(4); + }); }); }); });