@designliquido/delegua
Version:
Linguagem de programação simples e moderna usando português estruturado.
1,111 lines (1,109 loc) • 3.37 MB
JavaScript
(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) {