diff --git a/fontes/analisador-semantico/dialetos/analisador-semantico-visualg.ts b/fontes/analisador-semantico/dialetos/analisador-semantico-visualg.ts new file mode 100644 index 00000000..b4c6540a --- /dev/null +++ b/fontes/analisador-semantico/dialetos/analisador-semantico-visualg.ts @@ -0,0 +1,394 @@ +import { Atribuir, Chamada, ExpressaoRegular, FimPara, FormatacaoEscrita, FuncaoConstruto, Literal, Super, TipoDe, Variavel, Vetor } from "../../construtos"; +import { + Bloco, + Classe, + Const, + ConstMultiplo, + Continua, + Declaracao, + Enquanto, + Escolha, + Escreva, + EscrevaMesmaLinha, + Expressao, + Fazer, + FuncaoDeclaracao, + Importar, + Leia, + LeiaMultiplo, + Para, + ParaCada, + Retorna, + Se, + Sustar, + Tente, + Var, + VarMultiplo +} from "../../declaracoes"; +import { SimboloInterface } from "../../interfaces"; +import { AnalisadorSemanticoInterface } from "../../interfaces/analisador-semantico-interface"; +import { DiagnosticoAnalisadorSemantico, DiagnosticoSeveridade } from "../../interfaces/erros"; +import { RetornoAnalisadorSemantico } from "../../interfaces/retornos/retorno-analisador-semantico"; +import { TiposDadosInterface } from "../../interfaces/tipos-dados-interface"; +import { ContinuarQuebra, RetornoQuebra, SustarQuebra } from "../../quebras"; +import { PilhaVariaveis } from "../pilha-variaveis"; + +interface VariavelHipoteticaInterface { + tipo: TiposDadosInterface; + subtipo?: 'texto' | 'número' | 'inteiro' | 'longo' | 'lógico'; + imutavel: boolean; + valor?: any +} + +interface FuncaoHipoteticaInterface { + valor: any +} + +export class AnalisadorSemanticoVisualg implements AnalisadorSemanticoInterface { + pilhaVariaveis: PilhaVariaveis; + variaveis: { [nomeVariavel: string]: VariavelHipoteticaInterface }; + funcoes: { [nomeFuncao: string]: FuncaoHipoteticaInterface } + atual: number; + diagnosticos: DiagnosticoAnalisadorSemantico[]; + + constructor() { + this.pilhaVariaveis = new PilhaVariaveis(); + this.variaveis = {}; + this.funcoes = {}; + this.atual = 0; + this.diagnosticos = []; + } + + erro(simbolo: SimboloInterface, mensagem: string): void { + this.diagnosticos.push({ + simbolo: simbolo, + mensagem: mensagem, + hashArquivo: simbolo.hashArquivo, + linha: simbolo.linha, + severidade: DiagnosticoSeveridade.ERRO + }); + } + + aviso(simbolo: SimboloInterface, mensagem: string): void { + this.diagnosticos.push({ + simbolo: simbolo, + mensagem: mensagem, + hashArquivo: simbolo.hashArquivo, + linha: simbolo.linha, + severidade: DiagnosticoSeveridade.AVISO + }); + } + + visitarDeclaracaoDeAtribuicao(expressao: Atribuir) { + const { simbolo, valor } = expressao; + let variavel = this.variaveis[simbolo.lexema]; + if (!variavel) { + this.erro( + simbolo, + `Variável ${simbolo.lexema} ainda não foi declarada.` + ); + return Promise.resolve(); + } + + + if (variavel.tipo) { + if (valor instanceof Literal && variavel.tipo.includes('[]')) { + this.erro(simbolo, `Atribuição inválida, esperado tipo '${variavel.tipo}' na atribuição.`); + return Promise.resolve(); + } + if (valor instanceof Vetor && !variavel.tipo.includes('[]')) { + this.erro(simbolo, `Atribuição inválida, esperado tipo '${variavel.tipo}' na atribuição.`); + return Promise.resolve(); + } + + if (valor instanceof Literal) { + let valorLiteral = typeof (valor as Literal).valor; + if (!['qualquer'].includes(variavel.tipo)) { + if (valorLiteral === 'string') { + if (variavel.tipo != 'texto') { + this.erro(simbolo, `Esperado tipo '${variavel.tipo}' na atribuição.`); + return Promise.resolve(); + } + } + if (valorLiteral === 'number') { + if (!['inteiro', 'real'].includes(variavel.tipo)) { + this.erro(simbolo, `Esperado tipo '${variavel.tipo}' na atribuição.`); + return Promise.resolve(); + } + } + } + } + } + + if (variavel) { + this.variaveis[simbolo.lexema].valor = valor; + } + } + + visitarDeclaracaoVar(declaracao: Var): Promise { + this.variaveis[declaracao.simbolo.lexema] = { + imutavel: false, + tipo: declaracao.tipo, + valor: declaracao.inicializador !== null ? declaracao.inicializador.valor !== undefined ? declaracao.inicializador.valor : declaracao.inicializador : undefined + } + return Promise.resolve(); + } + + visitarDeclaracaoClasse(declaracao: Classe) { + return Promise.resolve(); + } + + visitarDeclaracaoConst(declaracao: Const): Promise { + return Promise.resolve(); + } + + visitarDeclaracaoConstMultiplo(declaracao: ConstMultiplo): Promise { + return Promise.resolve(); + } + + visitarDeclaracaoDeExpressao(declaracao: Expressao) { + return Promise.resolve(); + } + + visitarDeclaracaoDefinicaoFuncao(declaracao: FuncaoDeclaracao) { + for (let parametro of declaracao.funcao.parametros) { + if (parametro.hasOwnProperty('tipoDado') && !parametro.tipoDado.tipo) { + this.erro(declaracao.simbolo, `O tipo '${parametro.tipoDado.tipoInvalido}' não é valido`); + } + } + + if (declaracao.funcao.tipoRetorno === undefined) { + this.erro(declaracao.simbolo, `Declaração de retorno da função é inválida`); + } + + if (declaracao.funcao.parametros.length >= 255) { + this.erro(declaracao.simbolo, 'Não pode haver mais de 255 parâmetros'); + } + + this.funcoes[declaracao.simbolo.lexema] = { + valor: declaracao.funcao + } + + return Promise.resolve(); + } + + visitarDeclaracaoEnquanto(declaracao: Enquanto) { + return Promise.resolve(); + } + + visitarDeclaracaoEscolha(declaracao: Escolha) { + return Promise.resolve(); + } + + visitarDeclaracaoEscreva(declaracao: Escreva) { + return Promise.resolve(); + } + + visitarDeclaracaoEscrevaMesmaLinha(declaracao: EscrevaMesmaLinha) { + declaracao.argumentos.forEach((argumento: FormatacaoEscrita) => { + if (argumento.expressao instanceof Variavel) { + if (!this.variaveis[argumento.expressao.simbolo.lexema]) { + this.erro(argumento.expressao.simbolo, `Variável '${argumento.expressao.simbolo.lexema}' não existe.`) + return Promise.resolve(); + } + if (this.variaveis[argumento.expressao.simbolo.lexema]?.valor === undefined) { + this.aviso(argumento.expressao.simbolo, `Variável '${argumento.expressao.simbolo.lexema}' não foi inicializada.`) + return Promise.resolve(); + } + } + }) + + return Promise.resolve(); + } + + visitarDeclaracaoFazer(declaracao: Fazer) { + return Promise.resolve(); + } + + visitarDeclaracaoImportar(declaracao: Importar) { + return Promise.resolve(); + } + + visitarDeclaracaoPara(declaracao: Para): Promise { + return Promise.resolve(); + } + + visitarDeclaracaoParaCada(declaracao: ParaCada): Promise { + return Promise.resolve(); + } + + visitarDeclaracaoSe(declaracao: Se) { + return Promise.resolve(); + } + + visitarDeclaracaoTente(declaracao: Tente) { + return Promise.resolve(); + } + + visitarDeclaracaoVarMultiplo(declaracao: VarMultiplo): Promise { + return Promise.resolve(); + } + + visitarExpressaoAcessoIndiceVariavel(expressao: any) { + return Promise.resolve(); + } + + visitarExpressaoVetor(expressao: any) { + return Promise.resolve(); + } + visitarExpressaoAcessoMetodo(expressao: any) { + return Promise.resolve(); + } + + visitarExpressaoAgrupamento(expressao: any): Promise { + return Promise.resolve(); + } + + visitarExpressaoAtribuicaoPorIndice(expressao: any): Promise { + return Promise.resolve(); + } + + visitarExpressaoBinaria(expressao: any) { + return Promise.resolve(); + } + + visitarExpressaoBloco(declaracao: Bloco): Promise { + return Promise.resolve(); + } + + visitarExpressaoContinua(declaracao?: Continua): ContinuarQuebra { + return Promise.resolve(); + } + + visitarExpressaoDeChamada(expressao: Chamada) { + if (expressao.entidadeChamada instanceof Variavel) { + const variavel = expressao.entidadeChamada as Variavel; + const funcaoChamada = this.variaveis[variavel.simbolo.lexema] || this.funcoes[variavel.simbolo.lexema] + if (!funcaoChamada) { + this.erro( + variavel.simbolo, + `Função '${variavel.simbolo.lexema} não foi declarada.'` + ) + return Promise.resolve(); + } + const funcao = funcaoChamada.valor as FuncaoConstruto; + if (funcao.parametros.length != expressao.argumentos.length) { + this.erro( + variavel.simbolo, + `Esperava ${funcao.parametros.length} ${funcao.parametros.length > 1 ? "argumentos" : "argumento"}, mas obteve ${expressao.argumentos.length}.` + ) + } + + for (let [indice, arg0] of funcao.parametros.entries()) { + const arg1 = expressao.argumentos[indice]; + if (arg1) { + if (arg0.tipoDado?.tipo.toLowerCase() === 'caracter' && typeof arg1.valor !== 'string') { + this.erro( + variavel.simbolo, + `O valor passado para o parâmetro '${arg0.tipoDado.nome}' é diferente do esperado pela função.` + ); + } + else if (['inteiro', 'real'].includes(arg0.tipoDado?.tipo.toLowerCase()) + && typeof arg1.valor !== 'number') { + this.erro( + variavel.simbolo, + `O valor passado para o parâmetro '${arg0.tipoDado.nome}' é diferente do esperado pela função.` + ); + } + } + } + } + return Promise.resolve() + } + + visitarExpressaoDeVariavel(expressao: any) { + return Promise.resolve(); + } + + visitarExpressaoDefinirValor(expressao: any) { + return Promise.resolve(); + } + + visitarExpressaoDeleguaFuncao(expressao: any) { + return Promise.resolve(); + } + + visitarExpressaoDicionario(expressao: any) { + return Promise.resolve(); + } + + visitarExpressaoExpressaoRegular(expressao: ExpressaoRegular): Promise { + return Promise.resolve(); + } + + visitarExpressaoFalhar(expressao: any): Promise { + return Promise.resolve(); + } + + visitarExpressaoFimPara(declaracao: FimPara) { + return Promise.resolve(); + } + visitarExpressaoFormatacaoEscrita(declaracao: FormatacaoEscrita) { + return Promise.resolve(); + } + + visitarExpressaoIsto(expressao: any) { + return Promise.resolve(); + } + + visitarExpressaoLeia(expressao: Leia): Promise { + return Promise.resolve(); + } + + visitarExpressaoLeiaMultiplo(expressao: LeiaMultiplo): Promise { + return Promise.resolve(); + } + + visitarExpressaoLiteral(expressao: Literal): Promise { + return Promise.resolve(); + } + + visitarExpressaoLogica(expressao: any) { + return Promise.resolve(); + } + + visitarExpressaoRetornar(declaracao: Retorna): Promise { + return Promise.resolve(null); + } + + visitarExpressaoSuper(expressao: Super) { + return Promise.resolve(); + } + + visitarExpressaoSustar(declaracao?: Sustar): SustarQuebra { + return Promise.resolve(); + } + + visitarExpressaoTipoDe(expressao: TipoDe): Promise { + return Promise.resolve(); + } + + visitarExpressaoUnaria(expressao: any) { + return Promise.resolve(); + } + visitarExpressaoAcessoElementoMatriz(expressao: any) { + return Promise.resolve() + } + + visitarExpressaoAtribuicaoPorIndicesMatriz(expressao: any): Promise { + return Promise.resolve() + } + analisar(declaracoes: Declaracao[]): RetornoAnalisadorSemantico { + this.variaveis = {}; + this.atual = 0; + this.diagnosticos = []; + while (this.atual < declaracoes.length) { + declaracoes[this.atual].aceitar(this); + this.atual++; + } + + return { + diagnosticos: this.diagnosticos + } as RetornoAnalisadorSemantico + } +} diff --git a/fontes/analisador-semantico/dialetos/index.ts b/fontes/analisador-semantico/dialetos/index.ts index 6df675a9..2bead003 100644 --- a/fontes/analisador-semantico/dialetos/index.ts +++ b/fontes/analisador-semantico/dialetos/index.ts @@ -1,2 +1,3 @@ export * from './analisador-semantico-birl'; export * from './analisador-semantico-mapler'; +export * from './analisador-semantico-visualg'; diff --git a/fontes/avaliador-sintatico/dialetos/visualg/avaliador-sintatico-visualg.ts b/fontes/avaliador-sintatico/dialetos/visualg/avaliador-sintatico-visualg.ts index d0baa3ef..525054e0 100644 --- a/fontes/avaliador-sintatico/dialetos/visualg/avaliador-sintatico-visualg.ts +++ b/fontes/avaliador-sintatico/dialetos/visualg/avaliador-sintatico-visualg.ts @@ -40,6 +40,7 @@ import { Simbolo } from '../../../lexador'; import tiposDeSimbolos from '../../../tipos-de-simbolos/visualg'; import { ParametroVisuAlg } from './parametro-visualg'; +import { TiposDadosInterface } from '../../../interfaces/tipos-dados-interface'; export class AvaliadorSintaticoVisuAlg extends AvaliadorSintaticoBase { blocoPrincipalIniciado: boolean; @@ -226,13 +227,15 @@ export class AvaliadorSintaticoVisuAlg extends AvaliadorSintaticoBase { this.atual++; } else { for (let identificador of dadosVariaveis.identificadores) { + const tipo = dadosVariaveis.tipo as TiposDadosInterface switch (dadosVariaveis.tipo) { case tiposDeSimbolos.CARACTER: case tiposDeSimbolos.CARACTERE: inicializacoes.push( new Var( identificador, - new Literal(this.hashArquivo, Number(dadosVariaveis.simbolo.linha), '') + new Literal(this.hashArquivo, Number(dadosVariaveis.simbolo.linha), ''), + tipo ) ); break; @@ -241,7 +244,8 @@ export class AvaliadorSintaticoVisuAlg extends AvaliadorSintaticoBase { inicializacoes.push( new Var( identificador, - new Literal(this.hashArquivo, Number(dadosVariaveis.simbolo.linha), 0) + new Literal(this.hashArquivo, Number(dadosVariaveis.simbolo.linha), 0), + tipo ) ); break; @@ -249,7 +253,8 @@ export class AvaliadorSintaticoVisuAlg extends AvaliadorSintaticoBase { inicializacoes.push( new Var( identificador, - new Literal(this.hashArquivo, Number(dadosVariaveis.simbolo.linha), false) + new Literal(this.hashArquivo, Number(dadosVariaveis.simbolo.linha), false), + tipo ) ); break; @@ -455,6 +460,35 @@ export class AvaliadorSintaticoVisuAlg extends AvaliadorSintaticoBase { return expressao; } + simboloAtual(): SimboloInterface { + return this.simbolos[this.atual - 2]; + } + + verificarDefinicaoTipoAtual(): TiposDadosInterface { + const tipos = ['inteiro', 'qualquer', 'real', 'texto', 'vazio', 'vetor', 'caracter']; + + const lexema = this.simboloAtual().lexema.toLowerCase(); + + const contemTipo = tipos.find((tipo) => tipo === lexema); + + if (contemTipo && this.verificarTipoProximoSimbolo(tiposDeSimbolos.COLCHETE_ESQUERDO)) { + const tiposVetores = ['inteiro[]', 'qualquer[]', 'real[]', 'texto[]', 'caracter[]']; + this.avancarEDevolverAnterior(); + + if (!this.verificarTipoProximoSimbolo(tiposDeSimbolos.COLCHETE_DIREITO)) { + throw this.erro(this.simbolos[this.atual - 1], "Esperado símbolo de fechamento do vetor ']'."); + } + + const contemTipoVetor = tiposVetores.find((tipo) => tipo === `${lexema}[]`); + + this.avancarEDevolverAnterior(); + + return contemTipoVetor as TiposDadosInterface; + } + + return contemTipo as TiposDadosInterface; + } + corpoDaFuncao(tipo: any): FuncaoConstruto { const simboloAnterior = this.simbolos[this.atual - 1]; @@ -463,6 +497,7 @@ export class AvaliadorSintaticoVisuAlg extends AvaliadorSintaticoBase { this.consumir(tiposDeSimbolos.DOIS_PONTOS, 'Esperado dois-pontos após nome de função.'); // Tipo retornado pela função. + let tipoRetorno = null if ( !this.verificarSeSimboloAtualEIgualA( tiposDeSimbolos.INTEIRO, @@ -476,7 +511,7 @@ export class AvaliadorSintaticoVisuAlg extends AvaliadorSintaticoBase { } this.consumir(tiposDeSimbolos.QUEBRA_LINHA, "Esperado quebra de linha após tipo retornado por 'funcao'."); - + tipoRetorno = this.verificarDefinicaoTipoAtual(); const inicializacoes = this.validarSegmentoVar(); this.validarSegmentoInicio('função'); @@ -486,7 +521,8 @@ export class AvaliadorSintaticoVisuAlg extends AvaliadorSintaticoBase { this.hashArquivo, Number(simboloAnterior.linha), parametros, - corpo.filter((d) => d) + corpo.filter((d) => d), + tipoRetorno ); } @@ -944,11 +980,18 @@ export class AvaliadorSintaticoVisuAlg extends AvaliadorSintaticoBase { if (this.verificarSeSimboloAtualEIgualA(tiposDeSimbolos.PARENTESE_ESQUERDO)) { while (!this.verificarTipoSimboloAtual(tiposDeSimbolos.PARENTESE_DIREITO)) { const dadosParametros = this.logicaComumParametroVisuAlg(); + const tipoDadoParametro = { + nome: dadosParametros.simbolo.lexema, + tipo: dadosParametros.tipo as TiposDadosInterface, + tipoInvalido: !dadosParametros.tipo ? this.simboloAtual().lexema : null + } + for (let parametro of dadosParametros.identificadores) { parametros.push({ abrangencia: 'padrao', nome: parametro, referencia: dadosParametros.referencia, + tipoDado: tipoDadoParametro, }); } } diff --git a/fontes/interfaces/tipos-dados-interface.ts b/fontes/interfaces/tipos-dados-interface.ts index a95581e2..a1161011 100644 --- a/fontes/interfaces/tipos-dados-interface.ts +++ b/fontes/interfaces/tipos-dados-interface.ts @@ -15,5 +15,7 @@ export type TiposDadosInterface = | 'símbolo' | 'texto' | 'texto[]' + | 'caracter' + | 'caracter[]' | 'vetor' | undefined; diff --git a/testes/visualg/analisador-semantico.test.ts b/testes/visualg/analisador-semantico.test.ts new file mode 100644 index 00000000..88961e4c --- /dev/null +++ b/testes/visualg/analisador-semantico.test.ts @@ -0,0 +1,102 @@ +import { AnalisadorSemanticoVisualg } from '../../fontes/analisador-semantico/dialetos/analisador-semantico-visualg' +import { AvaliadorSintaticoVisuAlg } from "../../fontes/avaliador-sintatico/dialetos"; +import { LexadorVisuAlg } from "../../fontes/lexador/dialetos"; + +describe('Analisador sêmantico (Visualg)', () => { + describe('analisar()', () => { + let lexador: LexadorVisuAlg; + let avaliadorSintaticoVisuAlg: AvaliadorSintaticoVisuAlg; + let analisadorSemanticoVisualg: AnalisadorSemanticoVisualg + + beforeEach(() => { + lexador = new LexadorVisuAlg(); + avaliadorSintaticoVisuAlg = new AvaliadorSintaticoVisuAlg(); + analisadorSemanticoVisualg = new AnalisadorSemanticoVisualg(); + }); + + + describe('Cenários de falha', () => { + it('Variável indefinida, não declarada(escreva)', () => { + const retornoLexador = lexador.mapear([ + 'algoritmo "Declaração de variável"', + 'var', + 'inicio', + 'escreva(idade, "teste");', + 'fimalgoritmo' + ], -1); + const retornoAvaliadorSintatico = avaliadorSintaticoVisuAlg.analisar(retornoLexador, -1); + const retornoAnalisadorSemantico = analisadorSemanticoVisualg.analisar(retornoAvaliadorSintatico.declaracoes); + expect(retornoAnalisadorSemantico).toBeTruthy(); + expect(retornoAnalisadorSemantico.diagnosticos).toHaveLength(1); + }); + + it('Variável indefinida, não declarada(atribuição)', () => { + const retornoLexador = lexador.mapear([ + 'algoritmo "Atribuição de valor"', + 'var', + 'inicio', + 'idade <- 2', + 'fimalgoritmo' + ], -1); + + const retornoAvaliadorSintatico = avaliadorSintaticoVisuAlg.analisar(retornoLexador, -1); + const retornoAnalisadorSemantico = analisadorSemanticoVisualg.analisar(retornoAvaliadorSintatico.declaracoes); + expect(retornoAnalisadorSemantico).toBeTruthy(); + expect(retornoAnalisadorSemantico.diagnosticos).toHaveLength(1); + }); + + it('Atribuição inválida', () => { + const retornoLexador = lexador.mapear([ + 'algoritmo "Atribuição de valor"', + 'var', + 'idade: real', + 'inicio', + 'idade <- "2"', + 'fimalgoritmo' + ], -1); + + const retornoAvaliadorSintatico = avaliadorSintaticoVisuAlg.analisar(retornoLexador, -1); + const retornoAnalisadorSemantico = analisadorSemanticoVisualg.analisar(retornoAvaliadorSintatico.declaracoes); + expect(retornoAnalisadorSemantico).toBeTruthy(); + expect(retornoAnalisadorSemantico.diagnosticos).toHaveLength(1); + }); + + it("Chamada de função inexistente", () => { + const retornoLexador = lexador.mapear([ + 'algoritmo "definindo função"', + 'var', + 'resultado: caracter', + 'inicio', + 'saudacao(4);', + 'fimalgoritmo' + ], -1) + + const retornoAvaliadorSintatico = avaliadorSintaticoVisuAlg.analisar(retornoLexador, -1); + const retornoAnalisadorSemantico = analisadorSemanticoVisualg.analisar(retornoAvaliadorSintatico.declaracoes); + expect(retornoAnalisadorSemantico).toBeTruthy(); + expect(retornoAnalisadorSemantico.diagnosticos).toHaveLength(1); + }) + + it("Chamada de função com número de parametro diferentes", () => { + const retornoLexador = lexador.mapear([ + 'algoritmo "definindo função"', + 'var', + 'resultado: caracter', + 'função saudacao(nome: caracter): caracter', + 'var', + 'inicio', + 'retorna "Bem vindo " + nome', + 'fimfunção', + 'inicio', + 'saudacao(4);', + 'fimalgoritmo' + ], -1) + + const retornoAvaliadorSintatico = avaliadorSintaticoVisuAlg.analisar(retornoLexador, -1); + const retornoAnalisadorSemantico = analisadorSemanticoVisualg.analisar(retornoAvaliadorSintatico.declaracoes); + expect(retornoAnalisadorSemantico).toBeTruthy(); + expect(retornoAnalisadorSemantico.diagnosticos).toHaveLength(1); + }) + }) + }) +}) \ No newline at end of file