UNPKG

@designliquido/delegua

Version:

Linguagem de programação simples e moderna usando português estruturado.

514 lines 24.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AnalisadorSemantico = void 0; const construtos_1 = require("../construtos"); const declaracoes_1 = require("../declaracoes"); const erros_1 = require("../interfaces/erros"); const analisador_semantico_base_1 = require("./analisador-semantico-base"); const pilha_variaveis_1 = require("./pilha-variaveis"); class AnalisadorSemantico extends analisador_semantico_base_1.AnalisadorSemanticoBase { constructor() { super(); this.pilhaVariaveis = new pilha_variaveis_1.PilhaVariaveis(); this.variaveis = {}; this.funcoes = {}; this.atual = 0; this.diagnosticos = []; } erro(simbolo, mensagem) { this.diagnosticos.push({ simbolo: simbolo, mensagem: mensagem, hashArquivo: simbolo.hashArquivo, linha: simbolo.linha, severidade: erros_1.DiagnosticoSeveridade.ERRO, }); } aviso(simbolo, mensagem) { this.diagnosticos.push({ simbolo: simbolo, mensagem: mensagem, hashArquivo: simbolo.hashArquivo, linha: simbolo.linha, severidade: erros_1.DiagnosticoSeveridade.AVISO, }); } verificarTipoAtribuido(declaracao) { if (declaracao.tipo) { if (['vetor', 'qualquer[]', 'inteiro[]', 'texto[]'].includes(declaracao.tipo)) { if (declaracao.inicializador instanceof construtos_1.Vetor) { const vetor = declaracao.inicializador; if (declaracao.tipo === 'inteiro[]') { const v = vetor.valores.find((v) => typeof (v === null || v === void 0 ? void 0 : v.valor) !== 'number'); if (v) { this.erro(declaracao.simbolo, `Atribuição inválida para '${declaracao.simbolo.lexema}', é esperado um valor do tipo vetor de inteiro ou real. Atual: ${vetor.tipo}.`); } } if (declaracao.tipo === 'texto[]') { const v = vetor.valores.find((v) => typeof (v === null || v === void 0 ? void 0 : v.valor) !== 'string'); if (v) { this.erro(declaracao.simbolo, `Atribuição inválida para '${declaracao.simbolo.lexema}', é esperado um valor do tipo vetor de texto. Atual: ${vetor.tipo}.`); } } } else { this.erro(declaracao.simbolo, `Atribuição inválida para '${declaracao.simbolo.lexema}', é esperado um vetor de elementos.`); } } if (declaracao.inicializador instanceof construtos_1.Literal) { const literal = declaracao.inicializador; if (declaracao.tipo === 'texto' && literal.tipo !== 'texto') { this.erro(declaracao.simbolo, `Atribuição inválida para '${declaracao.simbolo.lexema}', é esperado um valor do tipo texto. Atual: ${literal.tipo}.`); } if (['inteiro', 'número', 'real'].includes(declaracao.tipo) && !['inteiro', 'número', 'real'].includes(literal.tipo)) { this.erro(declaracao.simbolo, `Atribuição inválida para '${declaracao.simbolo.lexema}', é esperado um valor do tipo número. Atual: ${literal.tipo}.`); } } if (declaracao.inicializador instanceof construtos_1.Leia) { if (!['qualquer', 'texto'].includes(declaracao.tipo)) { this.erro(declaracao.simbolo, `Atribuição inválida para '${declaracao.simbolo.lexema}', Função 'leia()' sempre retorna 'texto'.`); } } } } visitarExpressaoTipoDe(expressao) { return this.verificarTipoDe(expressao.valor); } verificarTipoDe(valor) { switch (valor.constructor.name) { case 'Agrupamento': const valorAgrupamento = valor; return this.verificarTipoDe(valorAgrupamento.expressao); case 'Binario': const valorBinario = valor; this.verificarTipoDe(valorBinario.direita); this.verificarTipoDe(valorBinario.esquerda); break; case 'Variavel': const valorVariavel = valor; return this.verificarVariavel(valorVariavel); } return Promise.resolve(); } visitarExpressaoFalhar(expressao) { return this.verificarFalhar(expressao.explicacao); } verificarFalhar(valor) { if (valor instanceof construtos_1.Binario) { this.verificarFalhar(valor.direita); this.verificarFalhar(valor.esquerda); } if (valor instanceof construtos_1.Agrupamento) { return this.verificarFalhar(valor.expressao); } if (valor instanceof construtos_1.Variavel) { return this.verificarVariavel(valor); } return Promise.resolve(); } comparacaoArgumentosContraParametrosFuncao(simboloFuncao, parametros, argumentos) { if (parametros.length !== argumentos.length) { this.erro(simboloFuncao, `Função '${simboloFuncao.lexema}' espera ${parametros.length} parâmetros. Atual: ${argumentos.length}.`); } for (let [indice, parametro] of parametros.entries()) { // TODO: `argumento` pode ser Literal (tipo já resolvido) ou variável (tipo inferido em outra etapa). const argumento = argumentos[indice]; if (argumento) { if (parametro.tipoDado === 'texto' && argumento.tipo !== 'texto') { this.erro(simboloFuncao, `O valor passado para o parâmetro '${parametro.nome.lexema}' (${parametro.tipoDado}) é diferente do esperado pela função (${argumento.tipo}).`); } else if (['inteiro', 'número', 'real'].includes(parametro.tipoDado)) { // Aqui, se houver diferença entre os tipos do parâmetro e do argumento, não há erro, // porque Delégua pode trabalhar com conversões implícitas. // Isso pode ou não mudar no futuro. if (!['inteiro', 'número', 'real'].includes(argumento.tipo)) { this.erro(simboloFuncao, `O valor passado para o parâmetro '${parametro.nome.lexema}' (${parametro.tipoDado}) é diferente do esperado pela função (${argumento.tipo}).`); } } } } } visitarChamadaPorArgumentoReferenciaFuncao(argumentoReferenciaFuncao, argumentos) { const variavelCorrespondente = this.variaveis[argumentoReferenciaFuncao.simboloFuncao.lexema].valor; if (!variavelCorrespondente) { return; } this.comparacaoArgumentosContraParametrosFuncao(argumentoReferenciaFuncao.simboloFuncao, variavelCorrespondente.parametros, argumentos); } visitarChamadaPorReferenciaFuncao(referenciaFuncao, argumentos) { const funcaoCorrespondente = this.funcoes[referenciaFuncao.simboloFuncao.lexema]; if (!funcaoCorrespondente) { return; } this.comparacaoArgumentosContraParametrosFuncao(referenciaFuncao.simboloFuncao, funcaoCorrespondente.valor.parametros, argumentos); } visitarChamadaPorVariavel(entidadeChamadaVariavel, argumentos) { const variavel = entidadeChamadaVariavel; const funcaoChamada = this.variaveis[variavel.simbolo.lexema] || this.funcoes[variavel.simbolo.lexema]; if (!funcaoChamada) { this.erro(entidadeChamadaVariavel.simbolo, `Chamada da função '${entidadeChamadaVariavel.simbolo.lexema}' não existe.`); return Promise.resolve(); } const funcao = funcaoChamada.valor; this.comparacaoArgumentosContraParametrosFuncao(entidadeChamadaVariavel.simbolo, funcao.parametros, argumentos); } visitarExpressaoDeChamada(expressao) { switch (expressao.entidadeChamada.constructor.name) { case 'ArgumentoReferenciaFuncao': const entidadeChamadaArgumentoReferenciaFuncao = expressao.entidadeChamada; this.visitarChamadaPorArgumentoReferenciaFuncao(entidadeChamadaArgumentoReferenciaFuncao, expressao.argumentos); break; case 'ReferenciaFuncao': const entidadeChamadaReferenciaFuncao = expressao.entidadeChamada; this.visitarChamadaPorReferenciaFuncao(entidadeChamadaReferenciaFuncao, expressao.argumentos); break; case 'Variavel': const entidadeChamadaVariavel = expressao.entidadeChamada; this.visitarChamadaPorVariavel(entidadeChamadaVariavel, expressao.argumentos); break; } return Promise.resolve(); } visitarExpressaoDeAtribuicao(expressao) { // TODO: Readaptar para trabalhar com `expressao.alvo` sendo um construto. let simboloAlvo; switch (expressao.alvo.constructor.name) { case 'Variavel': const alvoVariavel = expressao.alvo; simboloAlvo = alvoVariavel.simbolo; break; default: // throw new Error(`Implementar atribuição para ${expressao.alvo.constructor.name}.`); return Promise.resolve(); } let valor = this.variaveis[simboloAlvo.lexema]; if (!valor) { this.erro(simboloAlvo, `Variável ${simboloAlvo.lexema} ainda não foi declarada até este ponto.`); return Promise.resolve(); } if (valor.tipo) { if (expressao.valor instanceof construtos_1.Literal && valor.tipo.includes('[]')) { this.erro(simboloAlvo, `Atribuição inválida, esperado tipo '${valor.tipo}' na atribuição.`); return Promise.resolve(); } if (expressao.valor instanceof construtos_1.Vetor && !valor.tipo.includes('[]')) { this.erro(simboloAlvo, `Atribuição inválida, esperado tipo '${valor.tipo}' na atribuição.`); return Promise.resolve(); } if (expressao.valor instanceof construtos_1.Literal) { let valorLiteral = typeof expressao.valor.valor; if (!['qualquer'].includes(valor.tipo)) { if (valorLiteral === 'string') { if (valor.tipo != 'texto') { this.erro(simboloAlvo, `Esperado tipo '${valor.tipo}' na atribuição.`); return Promise.resolve(); } } if (valorLiteral === 'number') { if (!['inteiro', 'número', 'real'].includes(valor.tipo)) { this.erro(simboloAlvo, `Esperado tipo '${valor.tipo}' na atribuição.`); return Promise.resolve(); } } } } if (expressao.valor instanceof construtos_1.Vetor) { let valores = expressao.valor.valores; if (!['qualquer[]'].includes(valor.tipo)) { if (valor.tipo === 'texto[]') { if (!valores.every((v) => typeof v.valor === 'string')) { this.erro(simboloAlvo, `Esperado tipo '${valor.tipo}' na atribuição.`); return Promise.resolve(); } } if (['inteiro[]', 'numero[]'].includes(valor.tipo)) { if (!valores.every((v) => typeof v.valor === 'number')) { this.erro(simboloAlvo, `Esperado tipo '${valor.tipo}' na atribuição.`); return Promise.resolve(); } } } } } if (valor.imutavel) { this.erro(simboloAlvo, `Constante ${simboloAlvo.lexema} não pode ser modificada.`); return Promise.resolve(); } else { if (this.variaveis[simboloAlvo.lexema]) { this.variaveis[simboloAlvo.lexema].valor = expressao.valor; } } } async visitarDeclaracaoDeExpressao(declaracao) { return await declaracao.expressao.aceitar(this); } visitarDeclaracaoEscolha(declaracao) { const identificadorOuLiteral = declaracao.identificadorOuLiteral; const tipo = identificadorOuLiteral.tipo; for (let caminho of declaracao.caminhos) { for (let condicao of caminho.condicoes) { switch (condicao.constructor.name) { case 'Literal': const condicaoLiteral = condicao; if (condicaoLiteral.tipo !== tipo) { this.erro({ lexema: condicaoLiteral.valor, tipo: condicaoLiteral.tipo, linha: condicaoLiteral.linha, hashArquivo: condicaoLiteral.hashArquivo, }, `'caso ${condicaoLiteral.valor}:' não é do mesmo tipo esperado em 'escolha' (esperado: ${tipo}, atual: ${condicaoLiteral.tipo}).`); } break; case 'Variavel': const condicaoVariavel = condicao; this.verificarVariavel(condicaoVariavel); const variavelHipotetica = this.variaveis[condicaoVariavel.simbolo.lexema]; if (variavelHipotetica && typeof variavelHipotetica.valor !== tipo) { this.erro(condicaoVariavel.simbolo, `'caso ${condicaoVariavel.simbolo.lexema}:' não é do mesmo tipo esperado em 'escolha'`); } break; } } } return Promise.resolve(); } visitarDeclaracaoEnquanto(declaracao) { return this.verificarCondicao(declaracao.condicao); } verificarCondicao(condicao) { if (condicao instanceof construtos_1.Agrupamento) { return this.verificarCondicao(condicao.expressao); } if (condicao instanceof construtos_1.Variavel) { return this.verificarVariavelBinaria(condicao); } if (condicao instanceof construtos_1.Binario) { return this.verificarBinario(condicao); } if (condicao instanceof construtos_1.Logico) { return this.verificarLogico(condicao); } if (condicao instanceof construtos_1.Chamada) { return this.verificarChamada(condicao); } return Promise.resolve(); } verificarVariavelBinaria(variavel) { this.verificarVariavel(variavel); const variavelHipotetica = this.variaveis[variavel.simbolo.lexema]; if (variavelHipotetica && !(variavelHipotetica.valor instanceof construtos_1.Binario) && typeof variavelHipotetica.valor !== 'boolean') { this.erro(variavel.simbolo, `Esperado tipo 'lógico' na condição do 'enquanto'.`); } return Promise.resolve(); } verificarVariavel(variavel) { const variavelHipotetica = this.variaveis[variavel.simbolo.lexema]; if (!variavelHipotetica) { this.erro(variavel.simbolo, `Variável ${variavel.simbolo.lexema} ainda não foi declarada até este ponto.`); } return Promise.resolve(); } verificarBinario(binario) { this.verificarLadoBinario(binario.direita); this.verificarLadoBinario(binario.esquerda); this.verificarOperadorBinario(binario); return Promise.resolve(); } verificarOperadorBinario(binario) { var _a, _b, _c; const operadoresMatematicos = ['ADICAO', 'SUBTRACAO', 'MULTIPLICACAO', 'DIVISAO', 'MODULO']; if (operadoresMatematicos.includes(binario.operador.tipo)) { if (binario.direita instanceof construtos_1.Variavel && binario.esquerda instanceof construtos_1.Variavel) { const tipoDireita = typeof ((_a = this.variaveis[binario.direita.simbolo.lexema]) === null || _a === void 0 ? void 0 : _a.valor); const tipoEsquerda = typeof ((_b = this.variaveis[binario.esquerda.simbolo.lexema]) === null || _b === void 0 ? void 0 : _b.valor); if (tipoDireita !== tipoEsquerda) { this.erro(binario.operador, `Operação inválida, tipos diferentes.`); } } } if (binario.operador.tipo === 'DIVISAO') { switch (binario.direita.constructor.name) { case 'Variavel': const operadorDireitoLiteral = binario.direita; if (((_c = this.variaveis[operadorDireitoLiteral.simbolo.lexema]) === null || _c === void 0 ? void 0 : _c.valor) === 0) { this.erro(binario.operador, `Divisão por zero.`); } break; } } } verificarLadoBinario(lado) { if (lado instanceof construtos_1.Variavel && !this.variaveis[lado.simbolo.lexema]) { this.erro(lado.simbolo, `Variável ${lado.simbolo.lexema} ainda não foi declarada até este ponto.`); return; } if (lado instanceof construtos_1.Binario) { this.verificarBinario(lado); return; } } verificarLogico(logio) { this.verificarLadoLogico(logio.direita); this.verificarLadoLogico(logio.esquerda); return Promise.resolve(); } verificarChamada(chamada) { switch (chamada.entidadeChamada.constructor.name) { case 'Variavel': let entidadeChamadaVariavel = chamada.entidadeChamada; if (!this.funcoes[entidadeChamadaVariavel.simbolo.lexema]) { this.erro(entidadeChamadaVariavel.simbolo, `Chamada da função '${entidadeChamadaVariavel.simbolo.lexema}' não existe.`); } break; } return Promise.resolve(); } verificarLadoLogico(lado) { if (lado instanceof construtos_1.Variavel) { let variavel = lado; this.verificarVariavelBinaria(variavel); } } visitarDeclaracaoEscreva(declaracao) { if (declaracao.argumentos.length === 0) { const { linha, hashArquivo } = declaracao; const simbolo = { literal: '', tipo: '', lexema: 'escreva', linha, hashArquivo, }; this.erro(simbolo, `É preciso ter um ou mais parametros para 'escreva(...)'`); return Promise.resolve(); } const variaveis = declaracao.argumentos.filter((arg) => arg instanceof construtos_1.Variavel); for (let variavel of variaveis) { // TODO: Funções também são consideradas "variáveis" até aqui, mas isso deve mudar futuramente. const possivelVariavel = this.variaveis[variavel.simbolo.lexema]; const possivelFuncao = this.funcoes[variavel.simbolo.lexema]; if (!possivelVariavel && !possivelFuncao) { this.erro(variavel.simbolo, `Variável ou função '${variavel.simbolo.lexema}' não existe.`); continue; } if (possivelVariavel && possivelVariavel.valor === undefined) { this.aviso(variavel.simbolo, `Variável '${variavel.simbolo.lexema}' não foi inicializada.`); } } return Promise.resolve(); } visitarDeclaracaoConst(declaracao) { this.verificarTipoAtribuido(declaracao); if (this.variaveis.hasOwnProperty(declaracao.simbolo.lexema)) { this.erro(declaracao.simbolo, 'Declaração de constante já feita.'); } else { this.variaveis[declaracao.simbolo.lexema] = { imutavel: true, tipo: declaracao.tipo, valor: declaracao.inicializador.valor, }; } this.virificarTipoDeclaracaoConst(declaracao); return Promise.resolve(); } virificarTipoDeclaracaoConst(declaracao) { if (declaracao.inicializador instanceof construtos_1.Binario) { // verificar tipos iguais no lado esquerdo e direito const binario = declaracao.inicializador; this.verificarLadoBinario(binario.direita); this.verificarLadoBinario(binario.esquerda); const tipoDireita = typeof binario.direita.valor; const tipoEsquerda = typeof binario.esquerda.valor; if (tipoDireita !== tipoEsquerda) { this.aviso(declaracao.simbolo, 'Declaração de constante com tipos diferentes.'); } } return Promise.resolve(); } visitarDeclaracaoVar(declaracao) { this.verificarTipoAtribuido(declaracao); if (declaracao.inicializador) { switch (declaracao.inicializador.constructor.name) { case 'FuncaoConstruto': const funcaoConstruto = declaracao.inicializador; if (funcaoConstruto.parametros.length >= 255) { this.erro(declaracao.simbolo, 'Função não pode ter mais de 255 parâmetros.'); } break; } } const valorInicializador = declaracao.inicializador && declaracao.inicializador.valor ? declaracao.inicializador.valor : declaracao.inicializador; this.variaveis[declaracao.simbolo.lexema] = { imutavel: false, tipo: declaracao.tipo, valor: valorInicializador || undefined, }; return Promise.resolve(); } visitarExpressaoRetornar(declaracao) { return Promise.resolve(null); } visitarDeclaracaoDefinicaoFuncao(declaracao) { if (declaracao.funcao.tipo === undefined) { this.erro(declaracao.simbolo, `Declaração de retorno da função é inválido.`); } if (declaracao.funcao.parametros.length >= 255) { this.erro(declaracao.simbolo, 'Função não pode ter mais de 255 parâmetros.'); } let tipoRetornoFuncao = declaracao.funcao.tipo; if (tipoRetornoFuncao) { let funcaoContemRetorno = declaracao.funcao.corpo.find((c) => c instanceof declaracoes_1.Retorna); if (funcaoContemRetorno) { if (tipoRetornoFuncao === 'vazio') { this.erro(declaracao.simbolo, `A função não pode ter nenhum tipo de retorno.`); } const tipoValor = typeof funcaoContemRetorno.valor.valor; if (!['qualquer'].includes(tipoRetornoFuncao)) { if (tipoValor === 'string') { if (tipoRetornoFuncao != 'texto') { this.erro(declaracao.simbolo, `Esperado retorno do tipo '${tipoRetornoFuncao}' dentro da função.`); } } if (tipoValor === 'number') { if (!['inteiro', 'real'].includes(tipoRetornoFuncao)) { this.erro(declaracao.simbolo, `Esperado retorno do tipo '${tipoRetornoFuncao}' dentro da função.`); } } } } else { if (declaracao.funcao.tipoExplicito && !['vazio', 'qualquer'].includes(tipoRetornoFuncao)) { this.erro(declaracao.simbolo, `Esperado retorno do tipo '${tipoRetornoFuncao}' dentro da função.`); } } } this.funcoes[declaracao.simbolo.lexema] = { valor: declaracao.funcao, }; return Promise.resolve(); } analisar(declaracoes) { // this.pilhaVariaveis = new PilhaVariaveis(); // this.pilhaVariaveis.empilhar() this.variaveis = {}; this.atual = 0; this.diagnosticos = []; while (this.atual < declaracoes.length) { declaracoes[this.atual].aceitar(this); this.atual++; } return { diagnosticos: this.diagnosticos, }; } } exports.AnalisadorSemantico = AnalisadorSemantico; //# sourceMappingURL=analisador-semantico.js.map