UNPKG

@designliquido/delegua

Version:

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

949 lines 44.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AnalisadorSemanticoPitugues = 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 Pituguês. */ class AnalisadorSemanticoPitugues 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.atual = 0; this.diagnosticos = []; } /** * Marca as variáveis usadas em uma expressão. * Versão estendida da classe base com tratamento adicional de construtos Pituguês. */ marcarVariaveisUsadasEmExpressao(expressao) { if (expressao instanceof construtos_1.Variavel) { this.gerenciadorEscopos.marcarComoUsada(expressao.simbolo.lexema); return; } if (expressao instanceof construtos_1.Atribuir) { // Em atribuições, marca variáveis usadas no valor (lado direito) this.marcarVariaveisUsadasEmExpressao(expressao.valor); 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.Logico) { this.marcarVariaveisUsadasEmExpressao(expressao.esquerda); this.marcarVariaveisUsadasEmExpressao(expressao.direita); return; } if (expressao instanceof construtos_1.Chamada) { // Marca a entidade sendo chamada (pode ser Variavel, AcessoMetodo, etc.) this.marcarVariaveisUsadasEmExpressao(expressao.entidadeChamada); // Marca todos os argumentos for (const argumento of expressao.argumentos) { this.marcarVariaveisUsadasEmExpressao(argumento); } return; } if (expressao instanceof construtos_1.AcessoMetodo || expressao instanceof construtos_1.AcessoMetodoOuPropriedade || expressao instanceof construtos_1.AcessoPropriedade) { this.marcarVariaveisUsadasEmExpressao(expressao.objeto); return; } // Chama a classe base para outros tipos (Unario, AcessoIndiceVariavel, etc.) super.marcarVariaveisUsadasEmExpressao(expressao); } 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 if (declaracao.inicializador instanceof construtos_1.Chamada) { // Chamadas de método/função podem retornar vetores, então não geramos erro // A verificação de tipo será feita em tempo de execução } 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(); } visitarChamadaPorArgumentoReferenciaFuncao(argumentoReferenciaFuncao, argumentos) { var _a; const variavelCorrespondente = (_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); } visitarExpressaoDeChamada(expressao) { // Garante que toda a árvore de argumentos seja validada e marcada como usada. for (const argumento of expressao.argumentos) { this.marcarVariaveisUsadasEmExpressao(argumento); this.verificarExpressao(argumento); } switch (expressao.entidadeChamada.constructor) { 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(); } resolverSimboloAlvoAtribuicao(alvo) { let atual = alvo; while (atual) { if (atual instanceof construtos_1.Variavel) { return atual.simbolo; } if (atual instanceof construtos_1.Agrupamento) { atual = atual.expressao; continue; } if (atual instanceof construtos_1.AcessoIndiceVariavel) { atual = atual.entidadeChamada; continue; } if (atual instanceof construtos_1.AcessoMetodo || atual instanceof construtos_1.AcessoMetodoOuPropriedade || atual instanceof construtos_1.AcessoPropriedade) { atual = atual.objeto; continue; } const simbolo = atual === null || atual === void 0 ? void 0 : atual.simbolo; if (simbolo === null || simbolo === void 0 ? void 0 : simbolo.lexema) { return simbolo; } return undefined; } return undefined; } visitarExpressaoDeAtribuicao(expressao) { const simboloAlvo = this.resolverSimboloAlvoAtribuicao(expressao.alvo); if (!simboloAlvo) { return Promise.resolve(); } // Marca variáveis usadas no valor da atribuição this.marcarVariaveisUsadasEmExpressao(expressao.valor); let variavel = this.gerenciadorEscopos.buscar(simboloAlvo.lexema); if (!variavel) { // Em Pituguês (como Python), atribuições criam variáveis se não existirem let valorInicializador = undefined; if (expressao.valor.hasOwnProperty('valor')) { valorInicializador = expressao.valor.valor; } else { valorInicializador = expressao.valor; } this.gerenciadorEscopos.declarar(simboloAlvo.lexema, { nome: simboloAlvo.lexema, tipo: 'qualquer', imutavel: false, valor: valorInicializador, inicializada: true, usada: false, hashArquivo: simboloAlvo.hashArquivo, linha: simboloAlvo.linha, }); return Promise.resolve(); } // Marca como inicializada após atribuição this.gerenciadorEscopos.marcarComoInicializada(simboloAlvo.lexema, expressao.valor); 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(); } } } } } } 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) { case construtos_1.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 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); } 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(); } 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; } if (expressao instanceof construtos_1.Variavel) { this.verificarVariavel(expressao); return; } } 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']; if (operadoresMatematicos.includes(binario.operador.tipo)) { this.verificarTiposOperandos(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); if (tipoEsquerda && tipoDireita && tipoEsquerda !== tipoDireita) { // Verificar se são tipos numéricos compatíveis const tiposNumericos = ['inteiro', 'número', 'real']; 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 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; } } /** * Obtém o tipo de uma expressão (pode ser Literal, Variavel, ou Binario) */ 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.Agrupamento) { return this.obterTipoExpressao(expressao.expressao); } return null; } /** * Infere o tipo de resultado de uma operação binária */ inferirTipoBinario(binario) { const tipoEsquerda = this.obterTipoExpressao(binario.esquerda); const tipoDireita = this.obterTipoExpressao(binario.direita); if (!tipoEsquerda || !tipoDireita) { return null; } const operadoresMatematicos = ['ADICAO', 'SUBTRACAO', 'MULTIPLICACAO', 'DIVISAO', 'MODULO']; const operadoresComparacao = [ 'MAIOR', 'MAIOR_IGUAL', 'MENOR', 'MENOR_IGUAL', 'IGUAL', 'DIFERENTE', ]; if (operadoresComparacao.includes(binario.operador.tipo)) { return 'lógico'; } 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'; } 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; const funcoesBuiltIn = [ 'inteiro', 'real', 'número', 'texto', 'leia', 'escreva', 'tipo', ]; const pareceSerClasse = nomeFuncao[0] === nomeFuncao[0].toUpperCase(); 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) { let variavel = lado; this.verificarVariavelBinaria(variavel); } } /** * Verifica interpolações de texto e marca variáveis como usadas */ verificarInterpolacaoTexto(texto, literal) { // Regex para encontrar ${identificador} const regexInterpolacao = /\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g; let match; while ((match = regexInterpolacao.exec(texto)) !== null) { const nomeVariavel = match[1]; // Verifica se a variável existe const variavel = this.gerenciadorEscopos.buscar(nomeVariavel); const funcao = this.funcoes[nomeVariavel]; if (!variavel && !funcao) { this.erro({ lexema: nomeVariavel, tipo: 'IDENTIFICADOR', linha: literal.linha, hashArquivo: literal.hashArquivo, literal: null, }, `Variável ou função '${nomeVariavel}' usada em interpolação não existe.`); } else if (variavel) { // Marca como usada this.gerenciadorEscopos.marcarComoUsada(nomeVariavel); // Verifica se foi inicializada if (!variavel.inicializada) { this.aviso({ lexema: nomeVariavel, tipo: 'IDENTIFICADOR', linha: literal.linha, hashArquivo: literal.hashArquivo, literal: null, }, `Variável '${nomeVariavel}' usada em interpolação pode não ter sido inicializada.`); } } } } 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(); } for (const argumento of declaracao.argumentos) { this.marcarVariaveisUsadasEmExpressao(argumento); if (argumento instanceof construtos_1.Literal && argumento.tipo === 'texto') { this.verificarInterpolacaoTexto(String(argumento.valor), argumento); } if (argumento instanceof construtos_1.Variavel) { const possivelVariavel = this.gerenciadorEscopos.buscar(argumento.simbolo.lexema); const possivelFuncao = this.funcoes[argumento.simbolo.lexema]; if (!possivelVariavel && !possivelFuncao) { this.erro(argumento.simbolo, `Variável ou função '${argumento.simbolo.lexema}' não existe.`); continue; } if (possivelVariavel && possivelVariavel.valor === undefined) { this.aviso(argumento.simbolo, `Variável '${argumento.simbolo.lexema}' não foi inicializada.`); } } } return Promise.resolve(); } visitarDeclaracaoConst(declaracao) { var _a; this.verificarTipoAtribuido(declaracao); if (declaracao.inicializador) { this.marcarVariaveisUsadasEmExpressao(declaracao.inicializador); this.verificarExpressao(declaracao.inicializador); } const constanteCorrespondente = this.gerenciadorEscopos.buscarNoEscopoAtual(declaracao.simbolo.lexema); if (constanteCorrespondente) { this.erro(declaracao.simbolo, 'Declaração de constante já feita.'); return Promise.resolve(); } this.gerenciadorEscopos.declarar(declaracao.simbolo.lexema, { nome: declaracao.simbolo.lexema, tipo: declaracao.tipo || 'qualquer', imutavel: true, valor: (_a = declaracao.inicializador) === null || _a === void 0 ? void 0 : _a.valor, inicializada: true, usada: false, hashArquivo: declaracao.simbolo.hashArquivo, linha: declaracao.simbolo.linha, }); // TODO: Verificar inicializador. return Promise.resolve(); } visitarDeclaracaoVar(declaracao) { this.verificarTipoAtribuido(declaracao); if (declaracao.inicializador) { this.marcarVariaveisUsadasEmExpressao(declaracao.inicializador); this.verificarExpressao(declaracao.inicializador); switch (declaracao.inicializador.constructor) { case construtos_1.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.'); } if (funcaoConstruto.tipo) { const tipoRetornoFuncao = funcaoConstruto.tipo; if (!['vazio', 'qualquer'].includes(tipoRetornoFuncao)) { const todosOsCaminhosRetornam = this.todosOsCaminhosRetornam(funcaoConstruto.corpo); if (!todosOsCaminhosRetornam) { this.erro(declaracao.simbolo, `Função '${declaracao.simbolo.lexema}' deve retornar '${tipoRetornoFuncao}' em todos os caminhos de execução.`); } const funcaoContemRetorno = funcaoConstruto.corpo.find((c) => c instanceof declaracoes_1.Retorna); if (funcaoContemRetorno && funcaoContemRetorno.valor) { const tipoValor = typeof funcaoContemRetorno.valor.valor; if (!['qualquer'].includes(tipoRetornoFuncao)) { if (tipoValor === 'string' && tipoRetornoFuncao !== 'texto') { this.erro(declaracao.simbolo, `Esperado retorno do tipo '${tipoRetornoFuncao}' dentro da função.`); } if (tipoValor === 'number' && !['inteiro', 'real', 'número'].includes(tipoRetornoFuncao)) { this.erro(declaracao.simbolo, `Esperado retorno do tipo '${tipoRetornoFuncao}' dentro da função.`); } } } } } break; } } let valorInicializador = undefined; if (declaracao.inicializador) { if (declaracao.inicializador.hasOwnProperty('valor')) { valorInicializador = declaracao.inicializador.valor; } else { valorInicializador = declaracao.inicializador; } } let tipoInferido = declaracao.tipo; // Se o tipo é o padrão implícito 'qualquer', tenta inferir um tipo mais específico // a partir do inicializador. Para 'qualquer' explícito, apenas sugerimos mais abaixo. if (tipoInferido === 'qualquer' && !declaracao.tipoExplicito && declaracao.inicializador) { tipoInferido = this.obterTipoExpressao(declaracao.inicializador); } if (declaracao.tipoExplicito && declaracao.tipoOriginal === 'qualquer' && declaracao.inicializador) { const tipoMelhor = this.obterTipoExpressao(declaracao.inicializador); if (tipoMelhor && tipoMelhor !== 'qualquer') { this.sugestao(declaracao.simbolo, 'Um tipo melhor pode ser inferido.', [ { titulo: `Alterar tipo para '${tipoMelhor}'`, textoOriginal: 'qualquer', textoSubstituto: tipoMelhor, linha: declaracao.simbolo.linha, colunaInicio: declaracao.simbolo.colunaInicio, colunaFim: declaracao.simbolo.colunaFim, }, ]); } } const variavel = { nome: declaracao.simbolo.lexema, tipo: tipoInferido || 'qualquer', imutavel: false, valor: valorInicializador, inicializada: declaracao.inicializador !== null && declaracao.inicializador !== undefined, usada: false, hashArquivo: declaracao.simbolo.hashArquivo, linha: declaracao.simbolo.linha, }; const declaradaComSucesso = this.gerenciadorEscopos.declarar(declaracao.simbolo.lexema, variavel); if (!declaradaComSucesso) { const variavelExistente = this.gerenciadorEscopos.buscarNoEscopoAtual(declaracao.simbolo.lexema); this.aviso(declaracao.simbolo, `Variável '${declaracao.simbolo.lexema}' já foi declarada na linha ${variavelExistente === null || variavelExistente === void 0 ? void 0 : variavelExistente.linha}.`); } return Promise.resolve(); } visitarExpressaoFormatacaoEscrita(expressao) { if (expressao.expressao) { /* for (const expressao of expressao.expressoes) { if (expressao instanceof Variavel) { this.gerenciadorEscopos.marcarComoUsada(expressao.simbolo.lexema); } // Recursively check for variables in more complex expressions this.marcarVariaveisUsadasEmExpressao(expressao); } */ } return Promise.resolve(); } verificarBinarioEmExpressao(expressao) { if (expressao instanceof construtos_1.Agrupamento) { this.verificarBinarioEmExpressao(expressao.expressao); return; } if (expressao instanceof construtos_1.Binario) { this.verificarOperadorBinario(expressao); } } visitarExpressaoRetornar(declaracao) { if (declaracao.valor) { this.verificarBinarioEmExpressao(declaracao.valor); } return Promise.resolve(null); } visitarExpressaoDeVariavel(expressao) { if (expressao instanceof construtos_1.Variavel) { return this.verificarVariavel(expressao); } return Promise.resolve(); } async visitarExpressaoAcessoIntervaloVariavel(expressao) { const expressaoIntervalo = expressao; await expressaoIntervalo.objeto.aceitar(this); if (expressaoIntervalo.inicio) { await expressaoIntervalo.inicio.aceitar(this); } if (expressaoIntervalo.fim) { await expressaoIntervalo.fim.aceitar(this); } return Promise.resolve(); } async visitarExpressaoTuplaN(expressao) { for (let i = 0; i < expressao.elementos.length; i++) { await expressao.elementos[i].aceitar(this); } return Promise.resolve(); } visitarDeclaracaoDefinicaoFuncao(declaracao) { var _a; if (declaracao.funcao.parametros.length >= 255) { this.erro(declaracao.simbolo, 'Função não pode ter mais de 255 parâmetros.'); } const todosRetornos = declaracao.funcao.corpo.flatMap((c) => (0, comum_1.buscarRetornos)(c)); for (const instrucao of todosRetornos) { if (instrucao.valor) { this.verificarBinarioEmExpressao(instrucao.valor); } } let tipoRetornoFuncao = declaracao.funcao.tipo; if (tipoRetornoFuncao) { if (!['vazio', 'qualquer'].includes(tipoRetornoFuncao)) { const todosOsCaminhosRetornam = this.todosOsCaminhosRetornam(declaracao.funcao.corpo); if (!todosOsCaminhosRetornam) { this.erro(declaracao.simbolo, `Função '${declaracao.simbolo.lexema}' deve retornar '${tipoRetornoFuncao}' em todos os caminhos de execução.`); } } const retornos = declaracao.funcao.corpo.flatMap((c) => (0, comum_1.buscarRetornos)(c)); // Filtra retornos com tipo 'qualquer' (não determinado em tempo de análise sintática) const retornosComTipoIndeterminado = retornos.filter((retorno) => retorno.valor !== null && retorno.valor !== undefined && retorno.tipo === 'qualquer'); // Se a função é 'vazio' e há retornos com tipo indeterminado, // tenta inferir o tipo e fornece mensagem útil ao desenvolvedor if (tipoRetornoFuncao === 'vazio' && declaracao.funcao.tipoExplicito && retornosComTipoIndeterminado.length > 0) { const retornoComValor = retornosComTipoIndeterminado[0]; const tipoInferido = this.obterTipoExpressao(retornoComValor.valor); if (tipoInferido && tipoInferido !== 'qualquer') { this.erro(declaracao.simbolo, `A função não pode ter nenhum tipo de retorno. Tipo inferido do retorno: '${tipoInferido}'.`); } else { this.erro(declaracao.simbolo, `A função não pode ter nenhum tipo de retorno.`); } } else { // Verifica tipos de retorno para funções não-vazio const retornoComValor = retornos.find((retorno) => retorno.valor !== null && retorno.valor !== undefined); if (retornoComValor) { const tipoValor = typeof ((_a = retornoComValor.valor) === null || _a === void 0 ? void 0 : _a.valor); if (!['qualquer'].includes(tipoRetornoFuncao)) { if (tipoValor === 'string' && tipoRetornoFuncao !== 'texto') { this.erro(declaracao.simbolo, `Esperado retorno do tipo '${tipoRetornoFuncao}' dentro da função.`); } if (tipoValor === 'number' && !['inteiro', 'real', 'número'].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(); } verificarVariaveisNaoUsadas() { const naoUsadas = this.gerenciadorEscopos.obterVariaveisNaoUsadas(); for (let variavel of naoUsadas) { // Verifica se já existe um erro associado à variável. const temErro = this.diagnosticos.some((d) => d.severidade === erros_1.DiagnosticoSeveridade.ERRO && d.simbolo.lexema === variavel.nome); // Se a variável já tem um erro associado, não emitir aviso de não usada. if (temErro) { continue; } this.aviso({ lexema: variavel.nome, linha: variavel.linha, tipo: variavel.tipo, hashArquivo: variavel.hashArquivo, }, `Variável '${variavel.nome}' foi declarada mas nunca usada.`); } } async analisar(declaracoes) { this.gerenciadorEscopos = new gerenciador_escopos_1.GerenciadorEscopos(); this.funcoes = {}; this.atual = 0; this.diagnosticos = []; while (this.atual < declaracoes.length) { await declaracoes[this.atual].aceitar(this); this.atual++; } // Verifica variáveis não usadas ao final this.verificarVariaveisNaoUsadas(); return { diagnosticos: this.diagnosticos, }; } } exports.AnalisadorSemanticoPitugues = AnalisadorSemanticoPitugues; //# sourceMappingURL=analisador-semantico-pitugues.js.map