@designliquido/delegua
Version:
Linguagem de programação simples e moderna usando português estruturado.
1,394 lines • 66.6 kB
JavaScript
"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 micro_avaliador_sintatico_1 = require("../avaliador-sintatico/micro-avaliador-sintatico");
const micro_lexador_1 = require("../lexador/micro-lexador");
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.microLexador = new micro_lexador_1.MicroLexador();
this.microAvaliadorSintatico = new micro_avaliador_sintatico_1.MicroAvaliadorSintatico();
this.pilhaVariaveis = new pilha_variaveis_1.PilhaVariaveis();
this.gerenciadorEscopos = new gerenciador_escopos_1.GerenciadorEscopos();
this.funcoes = {};
this.classesDeclaradas = new Set();
this.classesRegistradas = new Map();
this.classesExternasRegistradas = new Map();
this.classeAtualEmAnalise = null;
this.atual = 0;
this.diagnosticos = [];
}
registrarClassesExternas(classes) {
for (const classe of classes) {
this.classesExternasRegistradas.set(classe.simbolo.lexema, classe);
}
}
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?.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?.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.
// A validação detalhada é 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);
}
/**
* Método recursivo para verificar o tipo de um construto, usado principalmente para validar
* o uso de `tipoDe` e `falhar()`.
* @param {ConstrutoInterface} valor O construto a ser avaliado.
* @returns {Promise<any>} O tipo do construto, ou `Promise.resolve()` se o tipo não puder ser
* determinado neste estágio da análise.
*/
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);
}
/**
* Método recursivo para verificar se um construto passado para `falhar()` é válido, ou seja, se é
* do tipo texto ou pode ser avaliado como texto.
* @param {ConstrutoInterface} valor O construto a ser avaliado.
* @returns {Promise<any>} O tipo do construto, ou `Promise.resolve()` se o tipo não puder ser
* determinado neste estágio da análise.
*/
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()) {
const argumento = argumentos[indice];
if (!argumento)
continue;
const tipoArgumento = argumento.tipo ?? 'qualquer';
if (tipoArgumento === 'qualquer')
continue;
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)) {
// Delégua pode trabalhar com conversões implícitas entre tipos numéricos.
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}).`);
}
}
}
}
visitarChamadaPorArgumentoReferenciaFuncao(argumentoReferenciaFuncao, argumentos) {
const variavelCorrespondente = this.gerenciadorEscopos.buscar(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 nomeFuncao = variavel.simbolo.lexema;
const funcoesNativas = [
'aleatorio',
'aleatorioEntre',
'algum',
'arredondar',
'clonar',
'encontrar',
'encontrarIndice',
'encontrarUltimo',
'encontrarUltimoIndice',
'escreva',
'filtrarPor',
'incluido',
'inteiro',
'intervalo',
'leia',
'longo',
'mapear',
'maximo',
'minimo',
'numero',
'número',
'ordenar',
'paraCada',
'primeiroEmCondicao',
'real',
'reduzir',
'somar',
'tamanho',
'texto',
'tipo',
'todos',
'todosEmCondicao',
'tupla',
'vetor',
];
const pareceSerClasse = nomeFuncao[0] === nomeFuncao[0].toUpperCase();
if (funcoesNativas.includes(nomeFuncao) || pareceSerClasse) {
return Promise.resolve();
}
const funcaoChamada = this.gerenciadorEscopos.buscar(nomeFuncao) || this.funcoes[nomeFuncao];
if (!funcaoChamada) {
this.erro(entidadeChamadaVariavel.simbolo, `Chamada da função '${nomeFuncao}' 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);
// Marca variáveis usadas no valor atribuído (ex: idade = ano - inteiro(leia(...))).
this.marcarVariaveisUsadasEmExpressao(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();
}
}
}
}
}
return Promise.resolve();
}
async visitarDeclaracaoDeExpressao(declaracao) {
return await declaracao.expressao.aceitar(this);
}
async visitarExpressaoBloco(declaracao) {
this.gerenciadorEscopos.empilharEscopo();
try {
for (const declaracaoBloco of declaracao.declaracoes) {
await declaracaoBloco.aceitar(this);
}
}
finally {
this.gerenciadorEscopos.desempilharEscopo();
}
return Promise.resolve();
}
visitarExpressaoAcessoIndiceVariavel(expressao) {
this.marcarVariaveisUsadasEmExpressao(expressao.entidadeChamada);
this.marcarVariaveisUsadasEmExpressao(expressao.indice);
return Promise.resolve();
}
visitarExpressaoAtribuicaoPorIndice(expressao) {
this.marcarVariaveisUsadasEmExpressao(expressao.objeto);
this.marcarVariaveisUsadasEmExpressao(expressao.indice);
this.marcarVariaveisUsadasEmExpressao(expressao.valor);
return Promise.resolve();
}
visitarDeclaracaoAjuda(declaracao) {
if (declaracao.elemento) {
this.marcarVariaveisUsadasEmExpressao(declaracao.elemento);
}
return Promise.resolve();
}
visitarExpressaoAjuda(expressao) {
if (expressao.valor) {
this.marcarVariaveisUsadasEmExpressao(expressao.valor);
}
return Promise.resolve();
}
async visitarDeclaracaoEscolha(declaracao) {
const identificadorOuLiteral = declaracao.identificadorOuLiteral;
const tipo = identificadorOuLiteral.tipo || 'qualquer';
const tiposLiteraisCasos = [];
this.marcarVariaveisUsadasEmExpressao(identificadorOuLiteral);
for (let caminho of declaracao.caminhos) {
for (const declaracao of caminho.declaracoes) {
await declaracao.aceitar(this);
}
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 &&
tipo !== 'qualquer') {
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}).`);
}
tiposLiteraisCasos.push(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;
}
}
}
if (tipo === 'qualquer' &&
tiposLiteraisCasos.length > 0 &&
identificadorOuLiteral instanceof construtos_1.Variavel) {
const tiposUnicos = [...new Set(tiposLiteraisCasos)];
if (tiposUnicos.length === 1) {
const tipoInferido = tiposUnicos[0];
const variavelEscopo = this.gerenciadorEscopos.buscar(identificadorOuLiteral.simbolo.lexema);
if (variavelEscopo) {
this.sugestao(identificadorOuLiteral.simbolo, `Um tipo melhor pode ser inferido para '${identificadorOuLiteral.simbolo.lexema}': '${tipoInferido}'`, [
{
titulo: `Alterar tipo de '${identificadorOuLiteral.simbolo.lexema}' para '${tipoInferido}'`,
textoOriginal: 'qualquer',
textoSubstituto: tipoInferido,
linha: variavelEscopo.linha,
colunaInicio: 0,
colunaFim: 0,
},
]);
}
}
}
if (declaracao.caminhoPadrao) {
for (const decl of declaracao.caminhoPadrao.declaracoes) {
await decl.aceitar(this);
}
}
return Promise.resolve();
}
async visitarDeclaracaoEnquanto(declaracao) {
// Marca variáveis usadas na condição.
this.marcarVariaveisUsadasEmExpressao(declaracao.condicao);
// Verifica a condição (incluindo validações de tipo para operadores lógicos).
await this.verificarCondicao(declaracao.condicao);
// Visita corpo para que usos/atribuições dentro do laço sejam analisados.
for (const declaracaoCorpo of declaracao.corpo.declaracoes) {
await declaracaoCorpo.aceitar(this);
}
return Promise.resolve();
}
async visitarDeclaracaoFazer(declaracao) {
// Visita corpo para que usos/atribuições dentro do bloco sejam analisados.
for (const declaracaoCorpo of declaracao.caminhoFazer.declaracoes) {
await declaracaoCorpo.aceitar(this);
}
// Marca variáveis usadas na condição
this.marcarVariaveisUsadasEmExpressao(declaracao.condicaoEnquanto);
// Verifica a condição
return this.verificarCondicao(declaracao.condicaoEnquanto);
}
async visitarDeclaracaoPara(declaracao) {
this.gerenciadorEscopos.empilharEscopo();
try {
if (Array.isArray(declaracao.inicializador)) {
for (const inicializador of declaracao.inicializador) {
await inicializador.aceitar(this);
}
}
else if (declaracao.inicializador) {
await declaracao.inicializador.aceitar(this);
}
// O laço precisa visitar condição/incremento/corpo para registrar usos de variáveis.
if (declaracao.condicao) {
this.marcarVariaveisUsadasEmExpressao(declaracao.condicao);
await this.verificarCondicao(declaracao.condicao);
}
if (declaracao.incrementar) {
this.marcarVariaveisUsadasEmExpressao(declaracao.incrementar);
this.verificarExpressao(declaracao.incrementar);
}
for (const declaracaoCorpo of declaracao.corpo.declaracoes) {
await declaracaoCorpo.aceitar(this);
}
}
finally {
this.gerenciadorEscopos.desempilharEscopo();
}
return Promise.resolve();
}
async visitarDeclaracaoParaCada(declaracao) {
this.marcarVariaveisUsadasEmExpressao(declaracao.vetorOuDicionario);
this.gerenciadorEscopos.empilharEscopo();
try {
if (declaracao.variavelIteracao instanceof construtos_1.Variavel) {
const nome = declaracao.variavelIteracao.simbolo.lexema;
this.gerenciadorEscopos.declarar(nome, {
nome,
tipo: 'qualquer',
imutavel: false,
valor: null,
inicializada: true,
usada: false,
hashArquivo: declaracao.hashArquivo,
linha: declaracao.linha,
});
}
else if (declaracao.variavelIteracao instanceof construtos_1.Dupla) {
const dupla = declaracao.variavelIteracao;
for (const parte of [dupla.primeiro, dupla.segundo]) {
if (parte instanceof construtos_1.Variavel) {
const nome = parte.simbolo.lexema;
this.gerenciadorEscopos.declarar(nome, {
nome,
tipo: 'qualquer',
imutavel: false,
valor: null,
inicializada: true,
usada: false,
hashArquivo: declaracao.hashArquivo,
linha: declaracao.linha,
});
}
}
}
if (declaracao.corpo) {
await declaracao.corpo.aceitar(this);
}
}
finally {
this.gerenciadorEscopos.desempilharEscopo();
}
return Promise.resolve();
}
async 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)
await this.verificarCondicao(declaracao.condicao);
if (declaracao.caminhoEntao) {
await declaracao.caminhoEntao.aceitar(this);
}
if (declaracao.caminhosSeSenao && declaracao.caminhosSeSenao.length > 0) {
for (const caminhoSeSenao of declaracao.caminhosSeSenao) {
this.marcarVariaveisUsadasEmExpressao(caminhoSeSenao.condicao);
await this.verificarCondicao(caminhoSeSenao.condicao);
await caminhoSeSenao.caminho.aceitar(this);
}
}
if (declaracao.caminhoSenao) {
await declaracao.caminhoSenao.aceitar(this);
}
return Promise.resolve();
}
/**
* 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) {
if (this.funcoes[variavel.simbolo.lexema]) {
return Promise.resolve();
}
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 || !tipoDireita) {
return;
}
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();
}
async verificarChamada(chamada) {
switch (chamada.entidadeChamada.constructor) {
case construtos_1.AcessoMetodoOuPropriedade:
await chamada.entidadeChamada.aceitar(this);
break;
case construtos_1.Variavel:
let entidadeChamadaVariavel = chamada.entidadeChamada;
const nomeFuncao = entidadeChamadaVariavel.simbolo.lexema;
// Lista de funções embutidas que não precisam ser declaradas
const funcoesEmbutidas = [
'aleatorio',
'aleatorioEntre',
'algum',
'arredondar',
'clonar',
'encontrar',
'encontrarIndice',
'encontrarUltimo',
'encontrarUltimoIndice',
'escreva',
'filtrarPor',
'incluido',
'inteiro',
'intervalo',
'leia',
'longo',
'mapear',
'maximo',
'minimo',
'numero',
'número',
'ordenar',
'paraCada',
'primeiroEmCondicao',
'real',
'reduzir',
'somar',
'tamanho',
'texto',
'tipo',
'todos',
'todosEmCondicao',
'tupla',
'vetor',
];
// Classes/construtores geralmente começam com letra maiúscula
const pareceSerClasse = nomeFuncao[0] === nomeFuncao[0].toUpperCase();
if (pareceSerClasse) {
this.gerenciadorEscopos.marcarComoUsada(nomeFuncao);
break;
}
// Só verifica se a função existe se não for embutidas e não parecer ser classe
if (!funcoesEmbutidas.includes(nomeFuncao) &&
!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,
* compreendendo cada expressão interpolada com MicroLexador e MicroAvaliadorSintatico.
*/
verificarInterpolacaoTexto(texto, literal) {
const regexInterpolacao = /\$\{(.*?)\}/g;
let match;
while ((match = regexInterpolacao.exec(texto)) !== null) {
const expressaoInterpolacao = match[1].trim();
try {
const retornoMicroLexador = this.microLexador.mapear(expressaoInterpolacao);
const retornoMicro = this.microAvaliadorSintatico.analisar(retornoMicroLexador, literal.linha);
for (const construto of retornoMicro.declaracoes) {
this.marcarVariaveisUsadasEmExpressao(construto);
}
}
catch (_) {
// Erros de sintaxe na interpolação são tratados em tempo de execução
}
}
}
async 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.inicializada) {
this.aviso(argumento.simbolo, `Variável '${argumento.simbolo.lexema}' não foi inicializada.`);
}
}
else if (!(argumento instanceof construtos_1.Literal)) {
// Para expressões complexas (ex: AcessoMetodoOuPropriedade), delega ao visitante
await argumento.aceitar(this);
}
}
return Promise.resolve();
}
visitarDeclaracaoConst(declaracao) {
this.verificarTipoAtribuido(declaracao);
if (declaracao.tipoExplicito && declaracao.tipo) {
const tipoBase = declaracao.tipo.replace('[]', '');
this.gerenciadorEscopos.marcarComoUsada(tipoBase);
}
if (declaracao.inicializador) {
this.marcarVariaveisUsadasEmExpressao(declaracao.inicializador);
// Verifica operações binárias no 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: declaracao.inicializador?.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.tipoExplicito && declaracao.tipoOriginal) {
const tipoBase = declaracao.tipoOriginal.replace('[]', '');
this.gerenciadorEscopos.marcarComoUsada(tipoBase);
}
if (declaracao.inicializador) {
this.marcarVariaveisUsadasEmExpressao(declaracao.inicializador);
// Verifica operações binárias no 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.');
}
// Valida tipo de retorno para funções anônimas atribuídas a variáveis
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;
}
}
// Inferir tipo do inicializador se não foi especificado explicitamente
let tipoInferido = declaracao.tipo;
if (!tipoInferido && declaracao.inicializador) {
const tipoInicializador = this.obterTipoExpressao(declaracao.inicializador);
if (tipoInicializador) {
tipoInferido = tipoInicializador;
}
}
// Sugestão de tipo melhor quando 'qualquer' é usado explicitamente
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 || 0,
colunaFim: declaracao.simbolo.colunaFim || 0,
},
]);
}
}
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?.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();
}
visitarExpressaoRetornar(declaracao) {
if (declaracao.valor) {
this.marcarVariaveisUsadasEmExpressao(declaracao.valor);
}
return Promise.resolve(undefined);
}
visitarExpressaoDeVariavel(expressao) {
if (expressao instanceof construtos_1.Variavel) {
return this.verificarVariavel(expressao);
}
return Promise.resolve();
}
obterTipoExpressao(expressao) {
const tipoBase = super.obterTipoExpressao(expressao);
if (tipoBase)
return tipoBase;
if (expressao instanceof construtos_1.Chamada && expressao.entidadeChamada instanceof construtos_1.Variavel) {
const nomeCallee = expressao.entidadeChamada.simbolo.lexema;
if (this.classesRegistradas.has(nomeCallee))
return nomeCallee;
}
return null;
}
resolverTipoObjeto(objeto) {
if (objeto instanceof construtos_1.Variavel) {
if (objeto.simbolo.lexema === 'isto' && this.classeAtualEmAnalise) {
return this.classeAtualEmAnalise.simbolo.lexema;
}
return this.gerenciadorEscopos.buscar(objeto.simbolo.lexema)?.tipo ?? null;
}
if (objeto instanceof construtos_1.Chamada && objeto.entidadeChamada instanceof construtos_1.Variavel) {
const nomeCallee = objeto.entidadeChamada.simbolo.lexema;
if (this.classesRegistradas.has(nomeCallee))
return nomeCallee;
}
return null;
}
estaEmClasseOuSubclasse(nomeClasse) {
const visitados = new Set();
const pilha = [this.classeAtualEmAnalise];
while (pilha.length > 0) {
const atual = pilha.pop();
if (!atual)
continue;
if (visitados.has(atual.simbolo.lexema))
continue;
visitados.add(atual.simbolo.lexema);
if (atual.simbolo.lexema === nomeClasse)
return true;
for (const sc of atual.superClasses) {
const pai = this.classesRegistradas.get(sc.simbolo.lexema) ?? null;
if (pai)
pilha.push(pai);
}
}
return false;
}
async visitarExpressaoAcessoMetodoOuPropriedade(expressao) {
const nomeMembro = expressao.simbolo.lexema;
const tipoObjeto = this.resolverTipoObjeto(expressao.objeto);
if (!tipoObjeto)
return;
const classeDef = this.classesRegistradas.get(tipoObjeto);
if (!classeDef)
return;
// Busca o membro na classe e em toda a hierarquia de superclasses
const encontrarMembroNaHierarquia = (classe) => {
const encontrado = classe.metodos.find((m) => m.simbolo.lexema === nomeMembro) ??
classe.propriedades.find((p) => p.nome.lexema === nomeMembro);
if (encontrado)
return encontrado;
for (const sc of classe.superClasses) {
const pai = this.classesRegistradas.get(sc.simbolo.lexema);
if (pai) {
const membroPai = encontrarMembroNaHierarquia(pai);
if (membroPai)
return membroPai;
}
}
return undefined;
};
const membro = encontrarMembroNaHierarquia(classeDef);
if (!membro) {
const mensagemErro = `Método não encontrado na classe '${tipoObjeto}': ${nomeMembro}.`;
this.erro(expressao.simbolo, mensagemErro, 'SEMANTICO_METODO_NAO_ENCONTRADO');
const diagnostico = this.diagnosticos.find((d) => d.linha === expressao.simbolo.linha && d.mensagem === mensagemErro);
if (diagnostico) {
diagnostico.correcaoMetodo = {
tipo: 'implementar-metodo',
nomeClasse: tipoObjeto,
nomeMetodo: nomeMembro,
linhaDeclaracaoClasse: classeDef.linha,
hashArquivoClasse: classeDef.hashArquivo,
};
}
return;
}
if (membro.acesso === 'privado') {
if (this.classeAtualEmAnalise?.simbolo.lexema !== tipoObjeto) {
this.erro(expressao.simbolo, `Membro '${nomeMembro}' é privado e não pode ser acessado fora da classe '${tipoObjeto}'.`);
}
}
else if (membro.acesso === 'protegido') {
if (!this.estaEmClasseOuSubclasse(tipoObjeto)) {
this.erro(expressao.simbolo, `Membro '${nomeMembro}' é protegido e não pode ser acessado fora da hierarquia da classe '${tipoObjeto}'.`);
}
}
}
async visitarDeclaracaoClasse(declaracao) {
for (const superClasseVariavel of declaracao.superClasses) {
const nomeSuperclasse = superClasseVariavel.simbolo.lexema;
if (nomeSuperclasse === declaracao.simbolo.lexema) {
this.erro(superClasseVariavel.simbolo, `A classe '${declaracao.simbolo.lexema}' não pode herdar de si mesma.`);
}
else if (!this.classesDeclaradas.has(nomeSuperclasse) &&
!this.classesRegistradas.has(nomeSuperclasse)) {
this.erro(superClasseVariavel.simbolo, `Superclasse '${nomeSuperclasse}' não foi declarada.`);
}
}
this.classesDeclaradas.add(declaracao.simbolo.lexema);
this.classesRegistradas.set(declaracao.simbolo.lexema, declaracao);
// Marca tipos usados em anotações de propriedades e parâmetros de métodos como usados.
for (const propriedade of declaracao.propriedades) {
const tipoBase = propriedade.tipo?.replace('[]', '');
if (tipoBase)
this.gerenciadorEscopos.marcarComoUsada(tipoBase);
}
for (const metodo of declaracao.metodos) {
const tipoRetorno = metodo.funcao.tipo?.replace('[]', '');
if (tipoRetorno)
this.gerenciadorEscopos.marcarComoUsada(tipoRetorno);
for (const parametro of metodo.funcao.parametros) {
const tipoBase = parametro.tipoDado?.replace('[]', '');
if (tipoBase)
this.gerenciadorEscopos.marcarComoUsada(tipoBase);
}
}
// Visita corpos dos métodos com contexto de classe ativo.
// Métodos abstratos implícitos e métodos de classes estrangeiras têm corpo vazio —
// não há declarações para visitar.
const classeAnterior = this.classeAtualEmAnalise;
this.classeAtualEmAnalise = declaracao;
if (!declaracao.estrangeira) {
for (const metodo of declaracao.metodos) {
if (metodo.abstrato)
continue;
const tipoRetornoMetodo = metodo.funcao.tipo;
if (tipoRetornoMetodo &&
!['vazio', 'qualquer'].includes(tipoRetornoMetodo) &&
metodo.funcao.corpo.length === 0) {
this.aviso(metodo.simbolo, `Método especifica tipo de retorno '${tipoRetornoMetodo}', mas não há qualquer retorno correspondente no corpo do método.`);
}
this.gerenciadorEscopos.empilharEscopo();
try {
for (const parametro of metodo.funcao.parametros) {
this.gerenciadorEscopos.declarar(parametro.nome.lexema, {
nome: parametro.nome.lexema,
tipo: parametro.tipoDado || 'qualquer',
imutavel: false,
valor: undefined,
inicializada: true,
usada: true,
hashArquivo: parametro.nome.hashArquivo,
linha: parametro.nome.linha,
});
}
for (const declaracao of metodo.funcao.corpo) {
await declaracao.aceitar(this);
}
}
finally {
this.gerenciadorEscopos.desempilharEscopo();
}
}
}
this.classeAtualEmAnalise = classeAnterior;
}
async 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) {
this.gerenciadorEscopos.marcarComoUsada(tipoRetornoFuncao);
if (!['vazio', 'qualquer'].includes(tipoRetornoFuncao)) {
if (declaracao.funcao.corpo.length === 0) {
this.aviso(declaracao.simbolo, `Método especifica tipo de retorno '${tipoRetornoFuncao}', mas não há qualquer retorno correspondente no corpo do método.`);
}
else {
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.`);
}
}
}
let retornos = [];
for (const declaracaoCorpo of declaracao.funcao.corpo) {
retornos = retornos.concat((0, comum_1.buscarRetornos)(declaracaoCorpo));
}
// 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 valorRetorno = retornoComValor.valor;
const tipoInferido = this.obterTipoExpressao(valorRetorno);
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 retornoComValor.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.`);
}
}
}
}
}
this.funcoes[declaracao.simbolo.lexema] = {
valor: declaracao.funcao,
};
this.gerenciadorEscopos.empilharEscopo();
try {
for (const parametro of declaracao.funcao.parametros) {
this.gerenciadorEscopos.declarar(parametro.nome.lexema, {
nome: parametro.nome.lexema,
tipo: parametro.tipoDado || 'qualquer',
imutavel: false,
valor: undefined,
inicializada: true,
usada: false,
hashArquivo: parametro.nome.hashArquivo,
linha: parametro.nome.linha,
});
}
for (const declaracaoCorpo of declaracao.funcao.corpo) {
await declaracaoCorpo.aceitar(this);
}
}
finally {
this.gerenciadorEscopos.desempilharEscopo();
}
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.classesDeclaradas = new Set(this.classesExternasRegistradas.keys());
this.classesRegistradas = new Map(this.classesExternasRegistradas);
this.classeAtualEmAnalise = null;
this.atual = 0;
this.diagnosticos = [];
try {
while (this.atual < declaracoes.length) {
await declaracoes[this.atual].aceitar(this);
this.atual++;
}
}
catch (erro) {
console.error('Erro durante análise semântica:', erro);
}
// Verifica variáveis não usadas ao final
this.verificarVariaveisNaoUsadas();
return {
diagnosticos: this.diagnosticos,
};
}
}
exports.AnalisadorSemantico = AnalisadorSemantico;
//# sourceMappingURL=analisador-semantico.js.map