UNPKG

@designliquido/delegua

Version:

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

1,111 lines (1,109 loc) 3.37 MB
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Delegua = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AnalisadorSemanticoBase = void 0; const construtos_1 = require("../construtos"); const declaracoes_1 = require("../declaracoes"); const interfaces_1 = require("../interfaces"); /** * Essa classe só existe para eliminar redundância entre todos os analisadores * semânticos. Por padrão, quando um método não é implementado, ao invés de dar erro, * simplesmente passa por ele (`return Promise.resolve()`). */ class AnalisadorSemanticoBase { diagnosticoJaExiste(simbolo, mensagem) { return this.diagnosticos.some((d) => d.linha === simbolo.linha && d.mensagem === mensagem && d.simbolo.lexema === simbolo.lexema); } erro(simbolo, mensagem) { if (this.diagnosticoJaExiste(simbolo, mensagem)) { return; } this.diagnosticos.push({ simbolo: simbolo, mensagem: mensagem, hashArquivo: simbolo.hashArquivo, linha: simbolo.linha, severidade: interfaces_1.DiagnosticoSeveridade.ERRO, }); } aviso(simbolo, mensagem) { if (this.diagnosticoJaExiste(simbolo, mensagem)) { return; } this.diagnosticos.push({ simbolo: simbolo, mensagem: mensagem, hashArquivo: simbolo.hashArquivo, linha: simbolo.linha, severidade: interfaces_1.DiagnosticoSeveridade.AVISO, }); } sugestao(simbolo, mensagem, correcoes) { var _a, _b; if (this.diagnosticoJaExiste(simbolo, mensagem)) { return; } this.diagnosticos.push({ simbolo: simbolo, mensagem: mensagem, hashArquivo: simbolo.hashArquivo, linha: simbolo.linha, severidade: interfaces_1.DiagnosticoSeveridade.SUGESTAO, colunaInicio: (_a = correcoes[0]) === null || _a === void 0 ? void 0 : _a.colunaInicio, colunaFim: (_b = correcoes[0]) === null || _b === void 0 ? void 0 : _b.colunaFim, correcoes: correcoes, }); } 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()) { const argumento = argumentos[indice]; if (argumento) { // Usando `obterTipoExpressao` para resolver adequadamente o tipo do argumento, // independentemente de ser um `Literal` (tipo já resolvido), `Variavel` (tipo inferido do // escopo), `Binario`, `Agrupamento`, ou qualquer outro construto (retorna `null` quando // o tipo não pode ser determinado em tempo de compilação). const tipoArgumento = this.obterTipoExpressao(argumento); // Validar apenas quando ambos os lados têm um tipo específico e determinável. // Ignorar quando `tipoArgumento` é nulo (por exemplo, resultado de `Chamada`) ou `qualquer`, // evitando falsos positivos para expressões cujo tipo é desconhecido em tempo de compilação. if (tipoArgumento && tipoArgumento !== 'qualquer' && parametro.tipoDado) { if (parametro.tipoDado === 'texto' && tipoArgumento !== 'texto') { this.erro(simboloFuncao, `O valor passado para o parâmetro '${parametro.nome.lexema}' (${parametro.tipoDado}) é diferente do esperado pela função (${tipoArgumento}).`); } else if (['inteiro', 'número', 'real'].includes(parametro.tipoDado)) { // Delegua suporta conversões implícitas entre tipos numéricos, mas não // entre texto e número. if (!['inteiro', 'número', 'real'].includes(tipoArgumento)) { this.erro(simboloFuncao, `O valor passado para o parâmetro '${parametro.nome.lexema}' (${parametro.tipoDado}) é diferente do esperado pela função (${tipoArgumento}).`); } } } } } } /** * Obtém o tipo de uma expressão (pode ser Literal, Variavel, Binario, Leia, etc) */ obterTipoExpressao(expressao) { if (expressao instanceof construtos_1.Literal) { return expressao.tipo; } if (expressao instanceof construtos_1.Variavel) { const variavel = this.gerenciadorEscopos.buscar(expressao.simbolo.lexema); return (variavel === null || variavel === void 0 ? void 0 : variavel.tipo) || null; } if (expressao instanceof construtos_1.Binario) { // Para binários, tentamos inferir o tipo baseado nos operandos return this.inferirTipoBinario(expressao); } if (expressao instanceof construtos_1.Logico) { // Operadores lógicos sempre retornam tipo lógico return 'lógico'; } if (expressao instanceof construtos_1.Agrupamento) { return this.obterTipoExpressao(expressao.expressao); } if (expressao instanceof construtos_1.Leia) { // leia() sempre retorna texto return 'texto'; } return null; } /** * Infere o tipo de resultado de uma operação binária */ inferirTipoBinario(binario) { const operadoresMatematicos = ['ADICAO', 'SUBTRACAO', 'MULTIPLICACAO', 'DIVISAO', 'MODULO']; const operadoresComparacao = [ 'MAIOR', 'MAIOR_IGUAL', 'MENOR', 'MENOR_IGUAL', 'IGUAL', 'DIFERENTE', ]; // Operadores de comparação sempre retornam lógico if (operadoresComparacao.includes(binario.operador.tipo)) { return 'lógico'; } const tipoEsquerda = this.obterTipoExpressao(binario.esquerda); const tipoDireita = this.obterTipoExpressao(binario.direita); if (!tipoEsquerda || !tipoDireita) { return null; } if (operadoresMatematicos.includes(binario.operador.tipo)) { const tiposNumericos = ['inteiro', 'número', 'real']; if (tiposNumericos.includes(tipoEsquerda) && tiposNumericos.includes(tipoDireita)) { // Se um dos lados é 'real', o resultado é 'real' if (tipoEsquerda === 'real' || tipoDireita === 'real') { return 'real'; } return 'número'; } // Concatenação de textos if (tipoEsquerda === 'texto' || tipoDireita === 'texto') { return 'texto'; } } return 'qualquer'; } /** * Marca as variáveis usadas em uma expressão. */ marcarVariaveisUsadasEmExpressao(expressao) { if (expressao instanceof construtos_1.Variavel) { this.gerenciadorEscopos.marcarComoUsada(expressao.simbolo.lexema); return; } if (expressao instanceof construtos_1.Binario) { this.marcarVariaveisUsadasEmExpressao(expressao.esquerda); this.marcarVariaveisUsadasEmExpressao(expressao.direita); return; } if (expressao instanceof construtos_1.Agrupamento) { this.marcarVariaveisUsadasEmExpressao(expressao.expressao); return; } if (expressao instanceof construtos_1.Chamada) { this.marcarVariaveisUsadasEmExpressao(expressao.entidadeChamada); for (const arg of expressao.argumentos) { this.marcarVariaveisUsadasEmExpressao(arg); } return; } if (expressao instanceof construtos_1.AcessoMetodo || expressao instanceof construtos_1.AcessoMetodoOuPropriedade || expressao instanceof construtos_1.AcessoPropriedade) { this.marcarVariaveisUsadasEmExpressao(expressao.objeto); return; } if (expressao instanceof construtos_1.Logico) { this.marcarVariaveisUsadasEmExpressao(expressao.esquerda); this.marcarVariaveisUsadasEmExpressao(expressao.direita); return; } if (expressao instanceof construtos_1.Unario) { this.marcarVariaveisUsadasEmExpressao(expressao.operando); return; } if (expressao instanceof construtos_1.AcessoIndiceVariavel) { this.marcarVariaveisUsadasEmExpressao(expressao.entidadeChamada); this.marcarVariaveisUsadasEmExpressao(expressao.indice); return; } if (expressao instanceof construtos_1.AjudaComoConstruto) { if (expressao.valor) { this.marcarVariaveisUsadasEmExpressao(expressao.valor); } return; } // TODO: Adicionar outros tipos de expressões conforme necessário. } /** * Analisa se todos os caminhos retornam * @returns true se todos os caminhos retornam, false caso contrário */ todosOsCaminhosRetornam(declaracoes) { return this.verificarBlocoRetorna(declaracoes); } verificarBlocoRetorna(declaracoes) { for (let i = 0; i < declaracoes.length; i++) { const declaracao = declaracoes[i]; if (declaracao instanceof declaracoes_1.Retorna) { return true; } if (declaracao instanceof declaracoes_1.Se) { const todosOsCaminhosSe = this.verificarSeRetorna(declaracao); if (todosOsCaminhosSe) { return true; } } if (declaracao instanceof declaracoes_1.Escolha) { const todosOsCaminhosEscolha = this.verificarEscolhaRetorna(declaracao); if (todosOsCaminhosEscolha) { return true; } } } return false; } verificarSeRetorna(declaracaoSe) { var _a, _b; const caminhoEntaoResolvido = declaracaoSe.caminhoEntao; const entaoRetorna = this.verificarBlocoRetorna(caminhoEntaoResolvido.declaracoes); const caminhoSenaoResolvido = declaracaoSe.caminhoSenao; if (!caminhoSenaoResolvido || ((_a = caminhoSenaoResolvido.declaracoes) === null || _a === void 0 ? void 0 : _a.length) === 0) { return false; } if (caminhoSenaoResolvido instanceof declaracoes_1.Se && ((_b = caminhoSenaoResolvido.caminhoEntao.declaracoes) === null || _b === void 0 ? void 0 : _b.length) === 1) { const senaoSeRetorna = this.verificarSeRetorna(caminhoSenaoResolvido); return entaoRetorna && senaoSeRetorna; } const senaoRetorna = this.verificarBlocoRetorna(declaracaoSe.caminhoSenao.declaracoes); return entaoRetorna && senaoRetorna; } verificarEscolhaRetorna(declaracaoEscolha) { let temPadrao = false; // Verifica se todos os caminhos retornam for (let caminho of declaracaoEscolha.caminhos) { const caminhoRetorna = this.verificarBlocoRetorna(caminho.declaracoes); if (!caminhoRetorna) { return false; } // Verifica se há um caso padrão if (caminho.condicoes.length === 0) { temPadrao = true; } } // Se não há caso padrão, não podemos garantir que todos os caminhos retornam return temPadrao; } visitarDeclaracaoTextoDocumentacao(declaracao) { return Promise.resolve(); } visitarExpressaoAcessoIntervaloVariavel(expressao) { return Promise.resolve(); } visitarExpressaoTuplaN(expressao) { return Promise.resolve(); } visitarExpressaoComentario(expressao) { // Comentários não afetam a análise semântica, então não faz nada. return Promise.resolve(); } visitarExpressaoSeparador(expressao) { // Separadores não afetam a análise semântica, então não faz nada. return Promise.resolve(); } adicionarDiagnostico(simbolo, mensagem, severidade = interfaces_1.DiagnosticoSeveridade.AVISO) { this.diagnosticos.push({ simbolo: simbolo, mensagem: mensagem, hashArquivo: simbolo.hashArquivo, linha: simbolo.linha, severidade: severidade, }); } visitarExpressaoArgumentoReferenciaFuncao(expressao) { return Promise.resolve(); } visitarExpressaoReferenciaFuncao(expressao) { return Promise.resolve(); } visitarExpressaoAcessoMetodo(expressao) { return Promise.resolve(); } visitarExpressaoAcessoPropriedade(expressao) { return Promise.resolve(); } visitarDeclaracaoCabecalhoPrograma(declaracao) { return Promise.resolve(); } visitarDeclaracaoClasse(declaracao) { return Promise.resolve(); } visitarDeclaracaoComentario(declaracao) { return Promise.resolve(); } visitarDeclaracaoConst(declaracao) { return Promise.resolve(); } visitarDeclaracaoConstMultiplo(declaracao) { return Promise.resolve(); } visitarExpressaoDeAtribuicao(expressao) { return Promise.resolve(); } visitarDeclaracaoDeExpressao(declaracao) { return Promise.resolve(); } visitarDeclaracaoDefinicaoFuncao(declaracao) { return Promise.resolve(); } visitarDeclaracaoEnquanto(declaracao) { return Promise.resolve(); } visitarDeclaracaoEscolha(declaracao) { return Promise.resolve(); } visitarDeclaracaoEscreva(declaracao) { return Promise.resolve(); } visitarDeclaracaoFazer(declaracao) { return Promise.resolve(); } visitarDeclaracaoImportar(declaracao) { return Promise.resolve(); } visitarDeclaracaoInicioAlgoritmo(declaracao) { return Promise.resolve(); } visitarDeclaracaoPara(declaracao) { return Promise.resolve(); } visitarDeclaracaoParaCada(declaracao) { return Promise.resolve(); } visitarDeclaracaoSe(declaracao) { return Promise.resolve(); } visitarDeclaracaoTendoComo(declaracao) { return Promise.resolve(); } visitarDeclaracaoTente(declaracao) { return Promise.resolve(); } visitarDeclaracaoVar(declaracao) { return Promise.resolve(); } visitarDeclaracaoVarMultiplo(declaracao) { return Promise.resolve(); } visitarExpressaoAcessoIndiceVariavel(expressao) { return Promise.resolve(); } visitarExpressaoAcessoElementoMatriz(expressao) { return Promise.resolve(); } visitarExpressaoAcessoMetodoOuPropriedade(expressao) { return Promise.resolve(); } visitarExpressaoAgrupamento(expressao) { return Promise.resolve(); } visitarExpressaoAtribuicaoPorIndice(expressao) { return Promise.resolve(); } visitarExpressaoAtribuicaoPorIndicesMatriz(expressao) { return Promise.resolve(); } visitarExpressaoBinaria(expressao) { return Promise.resolve(); } visitarExpressaoBloco(declaracao) { return Promise.resolve(); } visitarExpressaoContinua(declaracao) { return null; } visitarExpressaoDeChamada(expressao) { return Promise.resolve(); } visitarExpressaoDefinirValor(expressao) { return Promise.resolve(); } visitarExpressaoFuncaoConstruto(expressao) { return Promise.resolve(); } visitarExpressaoDeVariavel(expressao) { return Promise.resolve(); } visitarExpressaoDicionario(expressao) { return Promise.resolve(); } visitarExpressaoExpressaoRegular(expressao) { return; } visitarDeclaracaoEscrevaMesmaLinha(declaracao) { return Promise.resolve(); } visitarExpressaoFalhar(expressao) { return Promise.resolve(); } visitarExpressaoFimPara(declaracao) { return Promise.resolve(); } visitarExpressaoFormatacaoEscrita(declaracao) { return Promise.resolve(); } visitarExpressaoIsto(expressao) { return Promise.resolve(); } visitarExpressaoLeia(expressao) { return Promise.resolve(); } visitarExpressaoLiteral(expressao) { return Promise.resolve(); } visitarExpressaoLogica(expressao) { return Promise.resolve(); } visitarExpressaoRetornar(declaracao) { return; } visitarExpressaoSuper(expressao) { return Promise.resolve(); } visitarExpressaoSustar(declaracao) { return null; } visitarExpressaoTupla(expressao) { return Promise.resolve(); } visitarExpressaoTipoDe(expressao) { return Promise.resolve(); } visitarExpressaoUnaria(expressao) { return Promise.resolve(); } visitarExpressaoVetor(expressao) { return Promise.resolve(); } } exports.AnalisadorSemanticoBase = AnalisadorSemanticoBase; },{"../construtos":62,"../declaracoes":110,"../interfaces":149}],2:[function(require,module,exports){ "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 comum_1 = require("../avaliador-sintatico/comum"); const analisador_semantico_base_1 = require("./analisador-semantico-base"); const gerenciador_escopos_1 = require("./gerenciador-escopos"); const pilha_variaveis_1 = require("./pilha-variaveis"); /** * O Analisador Semântico de Delégua. */ class AnalisadorSemantico extends analisador_semantico_base_1.AnalisadorSemanticoBase { constructor() { super(); this.pilhaVariaveis = new pilha_variaveis_1.PilhaVariaveis(); this.gerenciadorEscopos = new gerenciador_escopos_1.GerenciadorEscopos(); this.funcoes = {}; this.classesDeclararadas = new Set(); this.classesRegistradas = new Map(); this.classeAtualEmAnalise = null; this.atual = 0; this.diagnosticos = []; } verificarTipoAtribuido(declaracao) { if (declaracao.tipo) { if (['vetor', 'qualquer[]', 'inteiro[]', 'texto[]'].includes(declaracao.tipo)) { if (declaracao.inicializador instanceof construtos_1.Vetor) { const vetor = declaracao.inicializador; const vetorSemSeparadores = vetor.elementos; if (declaracao.tipo === 'inteiro[]') { const apenasValores = vetorSemSeparadores.find((v) => typeof (v === null || v === void 0 ? void 0 : v.valor) !== 'number'); if (apenasValores) { 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 apenasValores = vetorSemSeparadores.find((v) => typeof (v === null || v === void 0 ? void 0 : v.valor) !== 'string'); if (apenasValores) { 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) { case construtos_1.Agrupamento: const valorAgrupamento = valor; return this.verificarTipoDe(valorAgrupamento.expressao); case construtos_1.Binario: const valorBinario = valor; this.verificarTipoDe(valorBinario.direita); this.verificarTipoDe(valorBinario.esquerda); break; case construtos_1.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) { var _a; const variavelCorrespondente = // this.variaveis[argumentoReferenciaFuncao.simboloFuncao.lexema].valor; (_a = this.gerenciadorEscopos.buscar(argumentoReferenciaFuncao.simboloFuncao.lexema)) === null || _a === void 0 ? void 0 : _a.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.gerenciadorEscopos.buscar(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); } async visitarExpressaoDeChamada(expressao) { for (const argumento of expressao.argumentos) { if (argumento instanceof construtos_1.Variavel) { this.gerenciadorEscopos.marcarComoUsada(argumento.simbolo.lexema); } } switch (expressao.entidadeChamada.constructor) { case construtos_1.AcessoMetodo: // Marca o objeto como usado quando seus métodos são chamados (ex: thor.corre()) const entidadeChamadaAcessoMetodo = expressao.entidadeChamada; this.marcarVariaveisUsadasEmExpressao(entidadeChamadaAcessoMetodo.objeto); break; case construtos_1.AcessoMetodoOuPropriedade: // Marca o objeto como usado quando seus métodos/propriedades são acessados (ex: thor.corre()) // e verifica acesso a membros privados/protegidos const entidadeChamadaAcessoMetodoOuPropriedade = expressao.entidadeChamada; this.marcarVariaveisUsadasEmExpressao(entidadeChamadaAcessoMetodoOuPropriedade.objeto); await expressao.entidadeChamada.aceitar(this); break; case construtos_1.ArgumentoReferenciaFuncao: const entidadeChamadaArgumentoReferenciaFuncao = expressao.entidadeChamada; this.visitarChamadaPorArgumentoReferenciaFuncao(entidadeChamadaArgumentoReferenciaFuncao, expressao.argumentos); break; case construtos_1.ReferenciaFuncao: const entidadeChamadaReferenciaFuncao = expressao.entidadeChamada; this.visitarChamadaPorReferenciaFuncao(entidadeChamadaReferenciaFuncao, expressao.argumentos); break; case construtos_1.Variavel: const entidadeChamadaVariavel = expressao.entidadeChamada; this.visitarChamadaPorVariavel(entidadeChamadaVariavel, expressao.argumentos); break; } return Promise.resolve(); } visitarExpressaoDeAtribuicao(expressao) { let simboloAlvo; switch (expressao.alvo.constructor) { case construtos_1.Variavel: const alvoVariavel = expressao.alvo; simboloAlvo = alvoVariavel.simbolo; break; default: return Promise.resolve(); } const variavel = this.gerenciadorEscopos.buscar(simboloAlvo.lexema); if (!variavel) { this.erro(simboloAlvo, `Variável '${simboloAlvo.lexema}' ainda não foi declarada até este ponto.`); return Promise.resolve(); } if (variavel.imutavel) { this.erro(simboloAlvo, `Constante '${simboloAlvo.lexema}' não pode ser modificada.`); return Promise.resolve(); } // Marca como inicializada após atribuição this.gerenciadorEscopos.marcarComoInicializada(simboloAlvo.lexema, expressao.valor); // Atualiza tipo se a variável não foi tipada explicitamente if (variavel.tipo === 'qualquer') { const tipoInferido = this.obterTipoExpressao(expressao.valor); if (tipoInferido && tipoInferido !== 'qualquer') { variavel.tipo = tipoInferido; } } // TODO: Readaptar para trabalhar com `expressao.alvo` sendo um construto. switch (expressao.alvo.constructor) { case construtos_1.Variavel: const alvoVariavel = expressao.alvo; simboloAlvo = alvoVariavel.simbolo; break; default: // throw new Error(`Implementar atribuição para ${expressao.alvo.constructor}.`); return Promise.resolve(); } let valor = this.gerenciadorEscopos.buscar(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 valoresSemSeparador = expressao.valor.elementos; if (!['qualquer[]'].includes(valor.tipo)) { if (valor.tipo === 'texto[]') { if (!valoresSemSeparador.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 (!valoresSemSeparador.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); } visitarDeclaracaoAjuda(declaracao) { if (declaracao.elemento) { this.marcarVariaveisUsadasEmExpressao(declaracao.elemento); } return Promise.resolve(); } visitarExpressaoAjuda(expressao) { if (expressao.valor) { this.marcarVariaveisUsadasEmExpressao(expressao.valor); } return Promise.resolve(); } 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) { case construtos_1.Literal: const condicaoLiteral = condicao; const tiposNumericos = ['inteiro', 'número', 'real']; const ambosSaoNumericos = tiposNumericos.includes(condicaoLiteral.tipo) && tiposNumericos.includes(tipo); if (condicaoLiteral.tipo !== tipo && !ambosSaoNumericos) { 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 construtos_1.Variavel: const condicaoVariavel = condicao; this.verificarVariavel(condicaoVariavel); const variavelHipotetica = this.gerenciadorEscopos.buscar(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); } visitarDeclaracaoFazer(declaracao) { // Marca variáveis usadas na condição this.marcarVariaveisUsadasEmExpressao(declaracao.condicaoEnquanto); // Verifica a condição return this.verificarCondicao(declaracao.condicaoEnquanto); } visitarDeclaracaoParaCada(declaracao) { // Marca o vetor/dicionário como usado this.marcarVariaveisUsadasEmExpressao(declaracao.vetorOuDicionario); return Promise.resolve(); } visitarDeclaracaoSe(declaracao) { // Marca variáveis usadas na condição this.marcarVariaveisUsadasEmExpressao(declaracao.condicao); // Verifica a condição (incluindo validação de tipos para operadores lógicos) return this.verificarCondicao(declaracao.condicao); } /** * Verifica uma expressão recursivamente, incluindo operações binárias */ verificarExpressao(expressao) { if (expressao instanceof construtos_1.Agrupamento) { this.verificarExpressao(expressao.expressao); return; } if (expressao instanceof construtos_1.Binario) { this.verificarBinario(expressao); return; } if (expressao instanceof construtos_1.Logico) { this.verificarLogico(expressao); return; } if (expressao instanceof construtos_1.Chamada) { this.verificarChamada(expressao); return; } } 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.gerenciadorEscopos.buscar(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 variavelEscopo = this.gerenciadorEscopos.buscar(variavel.simbolo.lexema); if (!variavelEscopo) { this.erro(variavel.simbolo, `Variável '${variavel.simbolo.lexema}' ainda não foi declarada até este ponto.`); return Promise.resolve(); } // Marca como usada this.gerenciadorEscopos.marcarComoUsada(variavel.simbolo.lexema); // Verifica se foi inicializada if (!variavelEscopo.inicializada) { this.aviso(variavel.simbolo, `Variável '${variavel.simbolo.lexema}' pode não ter sido inicializada antes do uso.`); } return Promise.resolve(); } verificarBinario(binario) { this.verificarExistenciaConstruto(binario.direita); this.verificarExistenciaConstruto(binario.esquerda); this.verificarOperadorBinario(binario); return Promise.resolve(); } verificarOperadorBinario(binario) { if (binario.esquerda instanceof construtos_1.Binario) { this.verificarOperadorBinario(binario.esquerda); } if (binario.direita instanceof construtos_1.Binario) { this.verificarOperadorBinario(binario.direita); } const operadoresMatematicos = ['ADICAO', 'SUBTRACAO', 'MULTIPLICACAO', 'DIVISAO', 'MODULO']; const operadoresComparacao = [ 'MAIOR', 'MAIOR_IGUAL', 'MENOR', 'MENOR_IGUAL', 'IGUAL', 'DIFERENTE', ]; if (operadoresMatematicos.includes(binario.operador.tipo)) { this.verificarTiposOperandos(binario); } if (operadoresComparacao.includes(binario.operador.tipo)) { this.verificarTiposComparacao(binario); } if (binario.operador.tipo === 'DIVISAO') { this.verificarDivisaoPorZero(binario); } } /** * Verifica se os tipos dos operandos são compatíveis */ verificarTiposOperandos(binario) { const tipoEsquerda = this.obterTipoExpressao(binario.esquerda); const tipoDireita = this.obterTipoExpressao(binario.direita); const tiposNumericos = ['inteiro', 'número', 'real']; // Verifica se algum operando é do tipo texto em operação aritmética if (tipoEsquerda === 'texto' || tipoDireita === 'texto') { // Verifica se é uma operação que precisa de números (não concatenação) const operadoresAritmeticos = ['SUBTRACAO', 'MULTIPLICACAO', 'DIVISAO', 'MODULO']; if (operadoresAritmeticos.includes(binario.operador.tipo)) { const ladoProblematico = tipoEsquerda === 'texto' ? 'esquerdo' : 'direito'; const expressaoProblematica = tipoEsquerda === 'texto' ? binario.esquerda : binario.direita; // Verifica se a expressão problemática é um Leia ou uma variável inicializada com Leia let mensagemAdicional = ''; if (expressaoProblematica instanceof construtos_1.Leia) { mensagemAdicional = " Função 'leia()' retorna texto. Use 'inteiro(leia(...))' ou 'real(leia(...))' para converter."; } else if (expressaoProblematica instanceof construtos_1.Variavel) { const variavel = this.gerenciadorEscopos.buscar(expressaoProblematica.simbolo.lexema); if (variavel && variavel.valor instanceof construtos_1.Leia) { mensagemAdicional = " A variável foi inicializada com 'leia()' que retorna texto. Use 'inteiro(leia(...))' ou 'real(leia(...))' para converter."; } else { mensagemAdicional = " Use 'inteiro(...)' ou 'real(...)' para converter texto em número."; } } this.erro(binario.operador, `Operação aritmética com tipo incompatível: operando ${ladoProblematico} é do tipo 'texto', mas a operação requer número.${mensagemAdicional}`); return; } } if (tipoEsquerda && tipoDireita && tipoEsquerda !== tipoDireita) { // Verificar se são tipos numéricos compatíveis const ambosNumericos = tiposNumericos.includes(tipoEsquerda) && tiposNumericos.includes(tipoDireita); if (!ambosNumericos) { this.aviso(binario.operador, `Operação entre tipos diferentes: tipo esquerdo '${tipoEsquerda}' e tipo direito '${tipoDireita}'. O resultado será resolvido implicitamente.`); } } } /** * Verifica se os tipos dos operandos em uma comparação são compatíveis */ verificarTiposComparacao(binario) { const tipoEsquerda = this.obterTipoExpressao(binario.esquerda); const tipoDireita = this.obterTipoExpressao(binario.direita); const tiposNumericos = ['inteiro', 'número', 'real']; if ((tipoEsquerda === 'texto' && tiposNumericos.includes(tipoDireita)) || (tiposNumericos.includes(tipoEsquerda) && tipoDireita === 'texto')) { this.aviso(binario.operador, `Esta comparação ocorre entre tipos ${tipoEsquerda} e ${tipoDireita}, e o resultado pode não ser o desejado.`); } } /** * Verifica divisão por zero recursivamente */ verificarDivisaoPorZero(binario) { const valorDireita = this.avaliarExpressaoConstante(binario.direita); if (valorDireita === 0) { this.erro(binario.operador, `Divisão por zero.`); } } /** * Tenta avaliar uma expressão em tempo de compilação para detectar valores constantes * Retorna o valor se puder ser determinado, ou null caso contrário */ avaliarExpressaoConstante(expressao) { if (expressao instanceof construtos_1.Literal) { return expressao.valor; } if (expressao instanceof construtos_1.Variavel) { const variavel = this.gerenciadorEscopos.buscar(expressao.simbolo.lexema); if (!variavel) { return null; } if (variavel.imutavel && variavel.inicializada) { return variavel.valor; } if (variavel.inicializada && variavel.valor !== undefined) { return variavel.valor; } return null; } if (expressao instanceof construtos_1.Binario) { const esquerda = this.avaliarExpressaoConstante(expressao.esquerda); const direita = this.avaliarExpressaoConstante(expressao.direita); if (esquerda !== null && direita !== null) { return this.calcularOperacaoBinaria(expressao.operador.tipo, esquerda, direita); } } if (expressao instanceof construtos_1.Agrupamento) { return this.avaliarExpressaoConstante(expressao.expressao); } return null; } /** * Calcula o resultado de uma operação binária em tempo de compilação */ calcularOperacaoBinaria(operador, esquerda, direita) { try { switch (operador) { case 'ADICAO': return esquerda + direita; case 'SUBTRACAO': return esquerda - direita; case 'MULTIPLICACAO': return esquerda * direita; case 'DIVISAO': return esquerda / direita; case 'MODULO': return esquerda % direita; case 'MAIOR': return esquerda > direita; case 'MAIOR_IGUAL': return esquerda >= direita; case 'MENOR': return esquerda < direita; case 'MENOR_IGUAL': return esquerda <= direita; case 'IGUAL': return esquerda === direita; case 'DIFERENTE': return esquerda !== direita; default: return null; } } catch (e) { return null; } } verificarExistenciaConstruto(construto) { if (construto instanceof construtos_1.Variavel) { if (!this.gerenciadorEscopos.buscar(construto.simbolo.lexema)) { this.erro(construto.simbolo, `Variável ${construto.simbolo.lexema} ainda não foi declarada até este ponto.`); return; } this.gerenciadorEscopos.marcarComoUsada(construto.simbolo.lexema); return; } if (construto instanceof construtos_1.Binario) { this.verificarBinario(construto); } } verificarLogico(logico) { this.verificarLadoLogico(logico.direita); this.verificarLadoLogico(logico.esquerda); return Promise.resolve(); } verificarChamada(chamada) { switch (chamada.entidadeChamada.constructor) { case construtos_1.Variavel: let entidadeChamadaVariavel = chamada.entidadeChamada; const nomeFuncao = entidadeChamadaVariavel.simbolo.lexema; // Lista de funções built-in que não precisam ser declaradas const funcoesBuiltIn = [ 'inteiro', 'real', 'número', 'texto', 'leia', 'escreva', 'tipo', ]; // Classes/construtores geralmente começam com letra maiúscula const pareceSerClasse = nomeFuncao[0] === nomeFuncao[0].toUpperCase(); // Só verifica se a função existe se não for built-in e não parecer ser classe if (!funcoesBuiltIn.includes(nomeFuncao) && !pareceSerClasse && !this.funcoes[nomeFuncao] && !this.gerenciadorEscopos.buscar(nomeFuncao)) { this.erro(entidadeChamadaVariavel.simbolo, `Chamada da função '${nomeFuncao}' não existe.`); } break; } return Promise.resolve(); } verificarLadoLogico(lado) { if (lado instanceof construtos_1.Variavel) { const variavel = lado; const variavelEscopo = this.gerenciadorEscopos.buscar(variavel.simbolo.lexema); if (variavelEscopo) { // Verifica se a variável tem um tipo definido if (variavelEscopo.tipo && variavelEscopo.tipo !== 'lógico' && variavelEscopo.tipo !== 'qualquer') { // Se a variável foi inicializada com um binário que resulta em 'lógico', // consideramos que o lado é válido (caso como: var x = 5 > 2; enquanto (x) {...}). if (variavelEscopo.valor instanceof construtos_1.Binario && this.inferirTipoBinario(variavelEscopo.valor) === 'lógico') { return; } this.erro(variavel.simbolo, `Operador lógico requer operandos do tipo 'lógico', mas recebeu '${variavelEscopo.tipo}'.`); } } } if (lado instanceof construtos_1.Logico) { this.verificarLogico(lado); } } /** * Verifica interpolações de texto e marca variáveis como usadas */ verificarInterpolacaoTexto(texto, literal) {