@yakuzaa/jade-compiler 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ast/nodes.d.ts +219 -0
- package/dist/ast/nodes.d.ts.map +1 -0
- package/dist/ast/nodes.js +2 -0
- package/dist/ast/nodes.js.map +1 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +108 -0
- package/dist/cli.js.map +1 -0
- package/dist/codegen/ir_generator.d.ts +42 -0
- package/dist/codegen/ir_generator.d.ts.map +1 -0
- package/dist/codegen/ir_generator.js +691 -0
- package/dist/codegen/ir_generator.js.map +1 -0
- package/dist/codegen/ir_nodes.d.ts +143 -0
- package/dist/codegen/ir_nodes.d.ts.map +1 -0
- package/dist/codegen/ir_nodes.js +3 -0
- package/dist/codegen/ir_nodes.js.map +1 -0
- package/dist/codegen/ir_printer.d.ts +10 -0
- package/dist/codegen/ir_printer.d.ts.map +1 -0
- package/dist/codegen/ir_printer.js +131 -0
- package/dist/codegen/ir_printer.js.map +1 -0
- package/dist/codegen/wasm_generator.d.ts +12 -0
- package/dist/codegen/wasm_generator.d.ts.map +1 -0
- package/dist/codegen/wasm_generator.js +48 -0
- package/dist/codegen/wasm_generator.js.map +1 -0
- package/dist/codegen/wat_generator.d.ts +29 -0
- package/dist/codegen/wat_generator.d.ts.map +1 -0
- package/dist/codegen/wat_generator.js +345 -0
- package/dist/codegen/wat_generator.js.map +1 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +79 -0
- package/dist/index.js.map +1 -0
- package/dist/lexer/lexer.d.ts +31 -0
- package/dist/lexer/lexer.d.ts.map +1 -0
- package/dist/lexer/lexer.js +475 -0
- package/dist/lexer/lexer.js.map +1 -0
- package/dist/lexer/token.d.ts +8 -0
- package/dist/lexer/token.d.ts.map +1 -0
- package/dist/lexer/token.js +2 -0
- package/dist/lexer/token.js.map +1 -0
- package/dist/lexer/token_type.d.ts +80 -0
- package/dist/lexer/token_type.d.ts.map +1 -0
- package/dist/lexer/token_type.js +98 -0
- package/dist/lexer/token_type.js.map +1 -0
- package/dist/parser/parse_result.d.ts +12 -0
- package/dist/parser/parse_result.d.ts.map +1 -0
- package/dist/parser/parse_result.js +2 -0
- package/dist/parser/parse_result.js.map +1 -0
- package/dist/parser/parser.d.ts +57 -0
- package/dist/parser/parser.d.ts.map +1 -0
- package/dist/parser/parser.js +1094 -0
- package/dist/parser/parser.js.map +1 -0
- package/dist/semantic/semantic_analyzer.d.ts +10 -0
- package/dist/semantic/semantic_analyzer.d.ts.map +1 -0
- package/dist/semantic/semantic_analyzer.js +16 -0
- package/dist/semantic/semantic_analyzer.js.map +1 -0
- package/dist/semantic/symbol_table.d.ts +33 -0
- package/dist/semantic/symbol_table.d.ts.map +1 -0
- package/dist/semantic/symbol_table.js +105 -0
- package/dist/semantic/symbol_table.js.map +1 -0
- package/dist/semantic/type_checker.d.ts +66 -0
- package/dist/semantic/type_checker.d.ts.map +1 -0
- package/dist/semantic/type_checker.js +963 -0
- package/dist/semantic/type_checker.js.map +1 -0
- package/package.json +84 -0
|
@@ -0,0 +1,963 @@
|
|
|
1
|
+
export class TypeChecker {
|
|
2
|
+
tabela;
|
|
3
|
+
erros = [];
|
|
4
|
+
funcaoAtual; // para verificar retorno
|
|
5
|
+
grafoEventos = new Map();
|
|
6
|
+
constructor(tabela) {
|
|
7
|
+
this.tabela = tabela;
|
|
8
|
+
}
|
|
9
|
+
// ── Built-in methods tables ──────────────────────────────
|
|
10
|
+
metodosTexto = {
|
|
11
|
+
'maiusculo': 'texto',
|
|
12
|
+
'minusculo': 'texto',
|
|
13
|
+
'aparar': 'texto',
|
|
14
|
+
'tamanho': 'numero',
|
|
15
|
+
'contem': 'booleano',
|
|
16
|
+
'comecaCom': 'booleano',
|
|
17
|
+
'terminaCom': 'booleano',
|
|
18
|
+
'substituir': 'texto',
|
|
19
|
+
'dividir': 'lista<texto>',
|
|
20
|
+
'normalizar': 'texto'
|
|
21
|
+
};
|
|
22
|
+
metodosLista = {
|
|
23
|
+
'tamanho': 'numero',
|
|
24
|
+
'contem': 'booleano',
|
|
25
|
+
'adicionar': 'vazio',
|
|
26
|
+
'remover': 'vazio',
|
|
27
|
+
'obter': 'T', // tipo do elemento
|
|
28
|
+
'filtrar': 'lista', // lista.filtrar(condicao) -> lista<T>
|
|
29
|
+
'ordenar': 'lista', // lista.ordenar(campo) -> lista<T>
|
|
30
|
+
'primeiro': 'T', // lista.primeiro() -> T
|
|
31
|
+
'ultimo': 'T', // lista.ultimo() -> T
|
|
32
|
+
'vazia': 'booleano' // lista.vazia() -> booleano
|
|
33
|
+
};
|
|
34
|
+
// ── Verificações principais ──────────────────────────────
|
|
35
|
+
verificarPrograma(node) {
|
|
36
|
+
// Passo 1: registrar todas as declarações de topo
|
|
37
|
+
this.registrarDeclaracoesTopo(node.declaracoes);
|
|
38
|
+
// Passo 2: verificar os corpos
|
|
39
|
+
for (const declaracao of node.declaracoes) {
|
|
40
|
+
this.verificarDeclaracao(declaracao);
|
|
41
|
+
}
|
|
42
|
+
// Passo 3: detectar ciclos de eventos
|
|
43
|
+
this.detectarCiclosEventos();
|
|
44
|
+
return this.erros;
|
|
45
|
+
}
|
|
46
|
+
// Adicionar ao final da classe TypeChecker
|
|
47
|
+
// Grafo de dependências de eventos
|
|
48
|
+
// chave: nome do evento emitido
|
|
49
|
+
// valor: lista de eventos que podem ser emitidos em resposta
|
|
50
|
+
registrarDependenciaEvento(eventoEscutado, eventoEmitido) {
|
|
51
|
+
if (!this.grafoEventos.has(eventoEscutado)) {
|
|
52
|
+
this.grafoEventos.set(eventoEscutado, new Set());
|
|
53
|
+
}
|
|
54
|
+
this.grafoEventos.get(eventoEscutado).add(eventoEmitido);
|
|
55
|
+
}
|
|
56
|
+
// DFS para detectar ciclos
|
|
57
|
+
detectarCiclosEventos() {
|
|
58
|
+
const visitados = new Set();
|
|
59
|
+
const emPilha = new Set();
|
|
60
|
+
const dfs = (evento, caminho) => {
|
|
61
|
+
visitados.add(evento);
|
|
62
|
+
emPilha.add(evento);
|
|
63
|
+
const vizinhos = this.grafoEventos.get(evento) ?? new Set();
|
|
64
|
+
for (const vizinho of vizinhos) {
|
|
65
|
+
if (!visitados.has(vizinho)) {
|
|
66
|
+
if (dfs(vizinho, [...caminho, vizinho]))
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
else if (emPilha.has(vizinho)) {
|
|
70
|
+
// Ciclo encontrado
|
|
71
|
+
const ciclo = [...caminho, vizinho].join(' → ');
|
|
72
|
+
this.erro(`Ciclo de eventos detectado: ${ciclo}. ` +
|
|
73
|
+
`Isso causaria loop infinito em runtime.`, 0, 0);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
emPilha.delete(evento);
|
|
78
|
+
return false;
|
|
79
|
+
};
|
|
80
|
+
for (const evento of this.grafoEventos.keys()) {
|
|
81
|
+
if (!visitados.has(evento)) {
|
|
82
|
+
dfs(evento, [evento]);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
registrarDeclaracoesTopo(declaracoes) {
|
|
87
|
+
for (const declaracao of declaracoes) {
|
|
88
|
+
switch (declaracao.kind) {
|
|
89
|
+
case 'Classe':
|
|
90
|
+
this.registrarClasse(declaracao);
|
|
91
|
+
break;
|
|
92
|
+
case 'Entidade':
|
|
93
|
+
this.registrarEntidade(declaracao);
|
|
94
|
+
break;
|
|
95
|
+
case 'Servico':
|
|
96
|
+
this.registrarServico(declaracao);
|
|
97
|
+
break;
|
|
98
|
+
case 'Evento':
|
|
99
|
+
this.registrarEvento(declaracao);
|
|
100
|
+
break;
|
|
101
|
+
case 'Enum':
|
|
102
|
+
this.registrarEnum(declaracao);
|
|
103
|
+
break;
|
|
104
|
+
case 'Interface':
|
|
105
|
+
this.registrarInterface(declaracao);
|
|
106
|
+
break;
|
|
107
|
+
case 'Funcao':
|
|
108
|
+
this.registrarFuncao(declaracao);
|
|
109
|
+
break;
|
|
110
|
+
case 'Importacao':
|
|
111
|
+
this.registrarImportacao(declaracao);
|
|
112
|
+
break;
|
|
113
|
+
case 'Tela':
|
|
114
|
+
this.registrarTela(declaracao);
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
registrarImportacao(node) {
|
|
120
|
+
try {
|
|
121
|
+
if (node.alias) {
|
|
122
|
+
// importar financeiro como fin → registra 'fin' como namespace do módulo
|
|
123
|
+
this.tabela.declarar({
|
|
124
|
+
nome: node.alias,
|
|
125
|
+
kind: 'entidade',
|
|
126
|
+
tipo: node.modulo,
|
|
127
|
+
linha: node.line,
|
|
128
|
+
coluna: node.column,
|
|
129
|
+
escopo: this.tabela.escopoAtual()
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
else if (node.item && !node.wildcard) {
|
|
133
|
+
// importar estoque.Produto → registra 'Produto' como tipo externo conhecido
|
|
134
|
+
this.tabela.declarar({
|
|
135
|
+
nome: node.item,
|
|
136
|
+
kind: 'classe',
|
|
137
|
+
tipo: node.item,
|
|
138
|
+
linha: node.line,
|
|
139
|
+
coluna: node.column,
|
|
140
|
+
escopo: this.tabela.escopoAtual()
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
// Wildcard (importar vendas.*) — não é possível registrar símbolos
|
|
144
|
+
// específicos sem resolver o módulo; aceito silenciosamente por enquanto.
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// Silenciar conflito de nome se o tipo já foi declarado localmente.
|
|
148
|
+
// A precedência é do símbolo local.
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
registrarClasse(node) {
|
|
152
|
+
const simbolo = {
|
|
153
|
+
nome: node.nome,
|
|
154
|
+
kind: 'classe',
|
|
155
|
+
tipo: node.nome,
|
|
156
|
+
linha: node.line,
|
|
157
|
+
coluna: node.column,
|
|
158
|
+
escopo: this.tabela.escopoAtual()
|
|
159
|
+
};
|
|
160
|
+
this.tabela.declarar(simbolo);
|
|
161
|
+
}
|
|
162
|
+
registrarEntidade(node) {
|
|
163
|
+
const simbolo = {
|
|
164
|
+
nome: node.nome,
|
|
165
|
+
kind: 'entidade',
|
|
166
|
+
tipo: node.nome,
|
|
167
|
+
linha: node.line,
|
|
168
|
+
coluna: node.column,
|
|
169
|
+
escopo: this.tabela.escopoAtual()
|
|
170
|
+
};
|
|
171
|
+
this.tabela.declarar(simbolo);
|
|
172
|
+
}
|
|
173
|
+
registrarServico(node) {
|
|
174
|
+
const simbolo = {
|
|
175
|
+
nome: node.nome,
|
|
176
|
+
kind: 'servico',
|
|
177
|
+
tipo: node.nome,
|
|
178
|
+
linha: node.line,
|
|
179
|
+
coluna: node.column,
|
|
180
|
+
escopo: this.tabela.escopoAtual()
|
|
181
|
+
};
|
|
182
|
+
this.tabela.declarar(simbolo);
|
|
183
|
+
}
|
|
184
|
+
registrarEvento(node) {
|
|
185
|
+
const simbolo = {
|
|
186
|
+
nome: node.nome,
|
|
187
|
+
kind: 'evento',
|
|
188
|
+
tipo: node.nome,
|
|
189
|
+
linha: node.line,
|
|
190
|
+
coluna: node.column,
|
|
191
|
+
escopo: this.tabela.escopoAtual()
|
|
192
|
+
};
|
|
193
|
+
this.tabela.declarar(simbolo);
|
|
194
|
+
}
|
|
195
|
+
registrarEnum(node) {
|
|
196
|
+
const simbolo = {
|
|
197
|
+
nome: node.nome,
|
|
198
|
+
kind: 'enum',
|
|
199
|
+
tipo: node.nome,
|
|
200
|
+
linha: node.line,
|
|
201
|
+
coluna: node.column,
|
|
202
|
+
escopo: this.tabela.escopoAtual()
|
|
203
|
+
};
|
|
204
|
+
this.tabela.declarar(simbolo);
|
|
205
|
+
// Registrar valores do enum
|
|
206
|
+
for (const valor of node.valores) {
|
|
207
|
+
const simboloValor = {
|
|
208
|
+
nome: valor,
|
|
209
|
+
kind: 'enum_valor',
|
|
210
|
+
tipo: node.nome,
|
|
211
|
+
linha: node.line,
|
|
212
|
+
coluna: node.column,
|
|
213
|
+
escopo: this.tabela.escopoAtual()
|
|
214
|
+
};
|
|
215
|
+
this.tabela.declarar(simboloValor);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
registrarInterface(node) {
|
|
219
|
+
const simbolo = {
|
|
220
|
+
nome: node.nome,
|
|
221
|
+
kind: 'interface',
|
|
222
|
+
tipo: node.nome,
|
|
223
|
+
linha: node.line,
|
|
224
|
+
coluna: node.column,
|
|
225
|
+
escopo: this.tabela.escopoAtual()
|
|
226
|
+
};
|
|
227
|
+
this.tabela.declarar(simbolo);
|
|
228
|
+
}
|
|
229
|
+
registrarFuncao(node) {
|
|
230
|
+
const simbolo = {
|
|
231
|
+
nome: node.nome,
|
|
232
|
+
kind: 'funcao',
|
|
233
|
+
tipo: node.tipoRetorno ? this.tipoParaString(node.tipoRetorno) : 'vazio',
|
|
234
|
+
linha: node.line,
|
|
235
|
+
coluna: node.column,
|
|
236
|
+
escopo: this.tabela.escopoAtual()
|
|
237
|
+
};
|
|
238
|
+
this.tabela.declarar(simbolo);
|
|
239
|
+
}
|
|
240
|
+
// Declarações
|
|
241
|
+
verificarClasse(node) {
|
|
242
|
+
this.tabela.entrarEscopo(node.nome);
|
|
243
|
+
// Verificar superclasse
|
|
244
|
+
if (node.superClasse) {
|
|
245
|
+
if (!this.tipoExiste(node.superClasse)) {
|
|
246
|
+
this.erro(`Superclasse '${node.superClasse}' não encontrada`, node.line, node.column);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
// Registrar superclasse para verificação de herança
|
|
250
|
+
this.tabela.registrarSuperClasse(node.nome, node.superClasse);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// Verificar interfaces
|
|
254
|
+
for (const interfaceNome of node.interfaces) {
|
|
255
|
+
if (!this.tipoExiste(interfaceNome)) {
|
|
256
|
+
this.erro(`Interface '${interfaceNome}' não encontrada`, node.line, node.column);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// Registrar campos e métodos
|
|
260
|
+
for (const campo of node.campos) {
|
|
261
|
+
const tipoCampo = this.tipoParaString(campo.tipo);
|
|
262
|
+
const simbolo = {
|
|
263
|
+
nome: campo.nome,
|
|
264
|
+
kind: 'variavel',
|
|
265
|
+
tipo: tipoCampo,
|
|
266
|
+
linha: campo.line,
|
|
267
|
+
coluna: campo.column,
|
|
268
|
+
escopo: this.tabela.escopoAtual()
|
|
269
|
+
};
|
|
270
|
+
this.tabela.declarar(simbolo);
|
|
271
|
+
this.tabela.registrarCampo(node.nome, campo.nome, tipoCampo);
|
|
272
|
+
}
|
|
273
|
+
for (const metodo of node.metodos) {
|
|
274
|
+
this.verificarFuncao(metodo);
|
|
275
|
+
}
|
|
276
|
+
this.tabela.sairEscopo();
|
|
277
|
+
}
|
|
278
|
+
verificarEntidade(node) {
|
|
279
|
+
this.tabela.entrarEscopo(node.nome);
|
|
280
|
+
let temId = false;
|
|
281
|
+
for (const campo of node.campos) {
|
|
282
|
+
const tipoCampo = this.tipoParaString(campo.tipo);
|
|
283
|
+
if (tipoCampo === 'id') {
|
|
284
|
+
temId = true;
|
|
285
|
+
}
|
|
286
|
+
if (!this.tipoExiste(tipoCampo)) {
|
|
287
|
+
this.erro(`Tipo '${tipoCampo}' não existe`, campo.line, campo.column);
|
|
288
|
+
}
|
|
289
|
+
const simbolo = {
|
|
290
|
+
nome: campo.nome,
|
|
291
|
+
kind: 'variavel',
|
|
292
|
+
tipo: tipoCampo,
|
|
293
|
+
linha: campo.line,
|
|
294
|
+
coluna: campo.column,
|
|
295
|
+
escopo: this.tabela.escopoAtual()
|
|
296
|
+
};
|
|
297
|
+
this.tabela.declarar(simbolo);
|
|
298
|
+
// Registra permanentemente para acesso a membro
|
|
299
|
+
this.tabela.registrarCampo(node.nome, campo.nome, tipoCampo);
|
|
300
|
+
}
|
|
301
|
+
if (!temId) {
|
|
302
|
+
this.erro(`Entidade '${node.nome}' deve ter exatamente um campo do tipo 'id'`, node.line, node.column);
|
|
303
|
+
}
|
|
304
|
+
this.tabela.sairEscopo();
|
|
305
|
+
}
|
|
306
|
+
verificarServico(node) {
|
|
307
|
+
this.tabela.entrarEscopo(node.nome);
|
|
308
|
+
for (const metodo of node.metodos) {
|
|
309
|
+
this.verificarFuncao(metodo);
|
|
310
|
+
}
|
|
311
|
+
for (const ouvinte of node.ouvintes) {
|
|
312
|
+
this.verificarOuvinte(ouvinte);
|
|
313
|
+
}
|
|
314
|
+
this.tabela.sairEscopo();
|
|
315
|
+
}
|
|
316
|
+
verificarFuncao(node) {
|
|
317
|
+
const funcaoAnterior = this.funcaoAtual;
|
|
318
|
+
this.funcaoAtual = node;
|
|
319
|
+
this.tabela.entrarEscopo(node.nome);
|
|
320
|
+
// Registrar parâmetros
|
|
321
|
+
const tiposParams = [];
|
|
322
|
+
for (const parametro of node.parametros) {
|
|
323
|
+
const tipoParam = this.tipoParaString(parametro.tipo);
|
|
324
|
+
if (!this.tipoExiste(tipoParam)) {
|
|
325
|
+
this.erro(`Tipo '${tipoParam}' não existe`, parametro.line, parametro.column);
|
|
326
|
+
}
|
|
327
|
+
tiposParams.push(tipoParam);
|
|
328
|
+
const simbolo = {
|
|
329
|
+
nome: parametro.nome,
|
|
330
|
+
kind: 'parametro',
|
|
331
|
+
tipo: tipoParam,
|
|
332
|
+
linha: parametro.line,
|
|
333
|
+
coluna: parametro.column,
|
|
334
|
+
escopo: this.tabela.escopoAtual()
|
|
335
|
+
};
|
|
336
|
+
this.tabela.declarar(simbolo);
|
|
337
|
+
}
|
|
338
|
+
// Guarda parâmetros permanentemente para verificação de chamadas
|
|
339
|
+
this.tabela.registrarParametrosFuncao(node.nome, tiposParams);
|
|
340
|
+
// Verificar corpo
|
|
341
|
+
this.verificarBloco(node.corpo);
|
|
342
|
+
// Verificar retorno
|
|
343
|
+
if (node.tipoRetorno) {
|
|
344
|
+
const tipoRetorno = this.tipoParaString(node.tipoRetorno);
|
|
345
|
+
// Se tipoRetorno != void, verificar se bloco termina com Retorno
|
|
346
|
+
if (tipoRetorno !== 'vazio') {
|
|
347
|
+
if (!this.verificarRetornoEmTodosCaminhos(node.corpo)) {
|
|
348
|
+
this.erro(`Função '${node.nome}' deve retornar valor em todos os caminhos`, node.line, node.column);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
this.tabela.sairEscopo();
|
|
353
|
+
this.funcaoAtual = funcaoAnterior;
|
|
354
|
+
}
|
|
355
|
+
verificarOuvinte(node) {
|
|
356
|
+
// Verificar se evento existe
|
|
357
|
+
const evento = this.tabela.buscar(node.evento);
|
|
358
|
+
if (!evento || evento.kind !== 'evento') {
|
|
359
|
+
this.erro(`Evento '${node.evento}' não encontrado`, node.line, node.column);
|
|
360
|
+
return; // Retorna para não continuar com um evento inválido
|
|
361
|
+
}
|
|
362
|
+
// Entrar no escopo do ouvinte
|
|
363
|
+
this.tabela.entrarEscopo(`ouvinte_${node.evento}`);
|
|
364
|
+
// Declarar os campos do evento como variáveis disponíveis no escopo
|
|
365
|
+
const camposEvento = this.tabela.buscarCamposEvento(node.evento);
|
|
366
|
+
if (camposEvento) {
|
|
367
|
+
for (const [nomeCampo, tipoCampo] of camposEvento.entries()) {
|
|
368
|
+
const simboloCampo = {
|
|
369
|
+
nome: nomeCampo,
|
|
370
|
+
kind: 'variavel',
|
|
371
|
+
tipo: tipoCampo,
|
|
372
|
+
linha: node.line,
|
|
373
|
+
coluna: node.column,
|
|
374
|
+
escopo: this.tabela.escopoAtual()
|
|
375
|
+
};
|
|
376
|
+
this.tabela.declarar(simboloCampo);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
// Registrar dependências de eventos para detecção de ciclo
|
|
380
|
+
const eventoEscutado = node.evento;
|
|
381
|
+
for (const instrucao of node.corpo.instrucoes) {
|
|
382
|
+
if (instrucao.kind === 'EmissaoEvento') {
|
|
383
|
+
const emissao = instrucao;
|
|
384
|
+
this.registrarDependenciaEvento(eventoEscutado, emissao.evento);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
this.verificarBloco(node.corpo);
|
|
388
|
+
// Sair do escopo do ouvinte
|
|
389
|
+
this.tabela.sairEscopo();
|
|
390
|
+
}
|
|
391
|
+
verificarEvento(node) {
|
|
392
|
+
this.tabela.entrarEscopo(node.nome);
|
|
393
|
+
for (const campo of node.campos) {
|
|
394
|
+
const tipoCampo = this.tipoParaString(campo.tipo);
|
|
395
|
+
if (!this.tipoExiste(tipoCampo)) {
|
|
396
|
+
this.erro(`Tipo '${tipoCampo}' não existe`, campo.line, campo.column);
|
|
397
|
+
}
|
|
398
|
+
const simbolo = {
|
|
399
|
+
nome: campo.nome,
|
|
400
|
+
kind: 'variavel',
|
|
401
|
+
tipo: tipoCampo,
|
|
402
|
+
linha: campo.line,
|
|
403
|
+
coluna: campo.column,
|
|
404
|
+
escopo: this.tabela.escopoAtual()
|
|
405
|
+
};
|
|
406
|
+
this.tabela.declarar(simbolo);
|
|
407
|
+
// Registrar campo permanentemente para verificação de emissão de eventos
|
|
408
|
+
this.tabela.registrarCampo(node.nome, campo.nome, tipoCampo);
|
|
409
|
+
}
|
|
410
|
+
this.tabela.sairEscopo();
|
|
411
|
+
}
|
|
412
|
+
verificarEnum(node) {
|
|
413
|
+
// Nada a verificar além do registro já feito
|
|
414
|
+
}
|
|
415
|
+
verificarInterface(node) {
|
|
416
|
+
this.tabela.entrarEscopo(node.nome);
|
|
417
|
+
for (const assinatura of node.assinaturas) {
|
|
418
|
+
const simbolo = {
|
|
419
|
+
nome: assinatura.nome,
|
|
420
|
+
kind: 'funcao',
|
|
421
|
+
tipo: this.tipoParaString(assinatura.tipoRetorno),
|
|
422
|
+
linha: assinatura.line,
|
|
423
|
+
coluna: assinatura.column,
|
|
424
|
+
escopo: this.tabela.escopoAtual()
|
|
425
|
+
};
|
|
426
|
+
this.tabela.declarar(simbolo);
|
|
427
|
+
}
|
|
428
|
+
this.tabela.sairEscopo();
|
|
429
|
+
}
|
|
430
|
+
verificarRegra(node) {
|
|
431
|
+
// Verificar se a condição é booleana
|
|
432
|
+
const tipoCondicao = this.resolverTipo(node.condicao);
|
|
433
|
+
if (tipoCondicao !== 'booleano') {
|
|
434
|
+
this.erro(`Condição da regra '${node.nome}' deve ser booleana, recebido '${tipoCondicao}'`, node.condicao.line, node.condicao.column);
|
|
435
|
+
}
|
|
436
|
+
// Verificar o bloco então
|
|
437
|
+
this.verificarBloco(node.entao);
|
|
438
|
+
// Verificar o bloco senão (se existir)
|
|
439
|
+
if (node.senao) {
|
|
440
|
+
this.verificarBloco(node.senao);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
verificarImportacao(_node) {
|
|
444
|
+
// O registro do símbolo importado já é feito em registrarDeclaracoesTopo.
|
|
445
|
+
// A verificação completa (se o módulo existe, se o item exportado existe)
|
|
446
|
+
// requer o sistema de resolução de módulos — implementado na v0.2.0.
|
|
447
|
+
}
|
|
448
|
+
// Instruções
|
|
449
|
+
verificarDeclaracao(declaracao) {
|
|
450
|
+
switch (declaracao.kind) {
|
|
451
|
+
case 'Classe':
|
|
452
|
+
this.verificarClasse(declaracao);
|
|
453
|
+
break;
|
|
454
|
+
case 'Entidade':
|
|
455
|
+
this.verificarEntidade(declaracao);
|
|
456
|
+
break;
|
|
457
|
+
case 'Servico':
|
|
458
|
+
this.verificarServico(declaracao);
|
|
459
|
+
break;
|
|
460
|
+
case 'Funcao':
|
|
461
|
+
this.verificarFuncao(declaracao);
|
|
462
|
+
break;
|
|
463
|
+
case 'Evento':
|
|
464
|
+
this.verificarEvento(declaracao);
|
|
465
|
+
break;
|
|
466
|
+
case 'Enum':
|
|
467
|
+
this.verificarEnum(declaracao);
|
|
468
|
+
break;
|
|
469
|
+
case 'Interface':
|
|
470
|
+
this.verificarInterface(declaracao);
|
|
471
|
+
break;
|
|
472
|
+
case 'Importacao':
|
|
473
|
+
this.verificarImportacao(declaracao);
|
|
474
|
+
break;
|
|
475
|
+
case 'Variavel':
|
|
476
|
+
this.verificarVariavel(declaracao);
|
|
477
|
+
break;
|
|
478
|
+
case 'Regra':
|
|
479
|
+
this.verificarRegra(declaracao);
|
|
480
|
+
break;
|
|
481
|
+
case 'Tela':
|
|
482
|
+
this.verificarTela(declaracao);
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
verificarBloco(node) {
|
|
487
|
+
for (const instrucao of node.instrucoes) {
|
|
488
|
+
this.verificarInstrucao(instrucao);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
verificarVariavel(node) {
|
|
492
|
+
let tipoDeclarado;
|
|
493
|
+
if (node.tipo) {
|
|
494
|
+
tipoDeclarado = this.tipoParaString(node.tipo);
|
|
495
|
+
if (!this.tipoExiste(tipoDeclarado)) {
|
|
496
|
+
this.erro(`Tipo '${tipoDeclarado}' não existe`, node.line, node.column);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if (node.inicializador) {
|
|
500
|
+
const tipoInicializador = this.resolverTipo(node.inicializador);
|
|
501
|
+
if (tipoDeclarado && !this.tiposCompatíveis(tipoDeclarado, tipoInicializador)) {
|
|
502
|
+
this.erro(`Tipo incompatível: esperado '${tipoDeclarado}', recebido '${tipoInicializador}'`, node.line, node.column);
|
|
503
|
+
}
|
|
504
|
+
if (!tipoDeclarado) {
|
|
505
|
+
tipoDeclarado = tipoInicializador;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
else if (!tipoDeclarado) {
|
|
509
|
+
this.erro(`Variável '${node.nome}' precisa de tipo ou inicializador`, node.line, node.column);
|
|
510
|
+
}
|
|
511
|
+
const simbolo = {
|
|
512
|
+
nome: node.nome,
|
|
513
|
+
kind: 'variavel',
|
|
514
|
+
tipo: tipoDeclarado || 'desconhecido',
|
|
515
|
+
linha: node.line,
|
|
516
|
+
coluna: node.column,
|
|
517
|
+
escopo: this.tabela.escopoAtual()
|
|
518
|
+
};
|
|
519
|
+
try {
|
|
520
|
+
this.tabela.declarar(simbolo);
|
|
521
|
+
}
|
|
522
|
+
catch (e) {
|
|
523
|
+
this.erro(e.message, node.line, node.column);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
verificarAtribuicao(node) {
|
|
527
|
+
const tipoValor = this.resolverTipo(node.valor);
|
|
528
|
+
if (typeof node.alvo === 'string') {
|
|
529
|
+
const simbolo = this.tabela.buscar(node.alvo);
|
|
530
|
+
if (!simbolo) {
|
|
531
|
+
this.erro(`Variável '${node.alvo}' não declarada`, node.line, node.column);
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
if (!this.tiposCompatíveis(simbolo.tipo, tipoValor)) {
|
|
535
|
+
this.erro(`Tipo incompatível: esperado '${simbolo.tipo}', recebido '${tipoValor}'`, node.line, node.column);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
// Acesso a membro: produto.estoque = x
|
|
540
|
+
const tipoObjeto = this.resolverTipo(node.alvo.objeto);
|
|
541
|
+
const objetoSimbolo = this.tabela.buscar(tipoObjeto);
|
|
542
|
+
if (!objetoSimbolo || (objetoSimbolo.kind !== 'classe' && objetoSimbolo.kind !== 'entidade')) {
|
|
543
|
+
this.erro(`'${tipoObjeto}' não é uma classe ou entidade`, node.alvo.line, node.alvo.column);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
// Verificar se campo existe e tipo compatível
|
|
547
|
+
const tipoCampo = this.tabela.buscarCampo(tipoObjeto, node.alvo.membro);
|
|
548
|
+
if (tipoCampo === null) {
|
|
549
|
+
this.erro(`'${tipoObjeto}' não possui campo '${node.alvo.membro}'`, node.alvo.line, node.alvo.column);
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
if (!this.tiposCompatíveis(tipoCampo, tipoValor)) {
|
|
553
|
+
this.erro(`Tipo incompatível: campo '${node.alvo.membro}' espera '${tipoCampo}', recebido '${tipoValor}'`, node.line, node.column);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
verificarCondicional(node) {
|
|
558
|
+
const tipoCondicao = this.resolverTipo(node.condicao);
|
|
559
|
+
if (tipoCondicao !== 'booleano') {
|
|
560
|
+
this.erro(`Condição do 'se' deve ser booleano, recebeu '${tipoCondicao}'`, node.condicao.line, node.condicao.column);
|
|
561
|
+
}
|
|
562
|
+
this.verificarBloco(node.entao);
|
|
563
|
+
if (node.senao) {
|
|
564
|
+
this.verificarBloco(node.senao);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
verificarEnquanto(node) {
|
|
568
|
+
const tipoCondicao = this.resolverTipo(node.condicao);
|
|
569
|
+
if (tipoCondicao !== 'booleano') {
|
|
570
|
+
this.erro(`Condição do 'enquanto' deve ser booleano, recebeu '${tipoCondicao}'`, node.condicao.line, node.condicao.column);
|
|
571
|
+
}
|
|
572
|
+
this.verificarBloco(node.corpo);
|
|
573
|
+
}
|
|
574
|
+
verificarPara(node) {
|
|
575
|
+
const tipoIteravel = this.resolverTipo(node.iteravel);
|
|
576
|
+
if (!tipoIteravel.startsWith('lista<')) {
|
|
577
|
+
this.erro(`Iterável do 'para' deve ser do tipo 'lista<T>', recebeu '${tipoIteravel}'`, node.iteravel.line, node.iteravel.column);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
// Extrair tipo do elemento: lista<Tipo> -> Tipo
|
|
581
|
+
const tipoElemento = tipoIteravel.substring(6, tipoIteravel.length - 1);
|
|
582
|
+
// Declarar variável de iteração
|
|
583
|
+
const simbolo = {
|
|
584
|
+
nome: node.variavel,
|
|
585
|
+
kind: 'variavel',
|
|
586
|
+
tipo: tipoElemento,
|
|
587
|
+
linha: node.line,
|
|
588
|
+
coluna: node.column,
|
|
589
|
+
escopo: this.tabela.escopoAtual()
|
|
590
|
+
};
|
|
591
|
+
try {
|
|
592
|
+
this.tabela.declarar(simbolo);
|
|
593
|
+
}
|
|
594
|
+
catch (e) {
|
|
595
|
+
this.erro(e.message, node.line, node.column);
|
|
596
|
+
}
|
|
597
|
+
this.verificarBloco(node.corpo);
|
|
598
|
+
}
|
|
599
|
+
verificarRetorno(node) {
|
|
600
|
+
if (!this.funcaoAtual) {
|
|
601
|
+
this.erro(`'retornar' só pode ser usado dentro de uma função`, node.line, node.column);
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (node.valor) {
|
|
605
|
+
if (!this.funcaoAtual.tipoRetorno) {
|
|
606
|
+
this.erro(`Função '${this.funcaoAtual.nome}' não deve retornar valor`, node.line, node.column);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
const tipoRetorno = this.tipoParaString(this.funcaoAtual.tipoRetorno);
|
|
610
|
+
const tipoValor = this.resolverTipo(node.valor);
|
|
611
|
+
if (!this.tiposCompatíveis(tipoRetorno, tipoValor)) {
|
|
612
|
+
this.erro(`Tipo incompatível: esperado '${tipoRetorno}', recebido '${tipoValor}'`, node.line, node.column);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
else if (this.funcaoAtual.tipoRetorno) {
|
|
616
|
+
this.erro(`Função '${this.funcaoAtual.nome}' deve retornar valor`, node.line, node.column);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
verificarEmissaoEvento(node) {
|
|
620
|
+
const evento = this.tabela.buscar(node.evento);
|
|
621
|
+
if (!evento || evento.kind !== 'evento') {
|
|
622
|
+
this.erro(`Evento '${node.evento}' não encontrado`, node.line, node.column);
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
// Verificar argumentos contra campos do evento
|
|
626
|
+
const camposEvento = this.tabela.buscarCamposEvento(node.evento);
|
|
627
|
+
if (camposEvento) {
|
|
628
|
+
// Verificar quantidade de argumentos
|
|
629
|
+
if (node.argumentos.length !== camposEvento.size) {
|
|
630
|
+
this.erro(`Evento '${node.evento}' espera ${camposEvento.size} argumentos, recebeu ${node.argumentos.length}`, node.line, node.column);
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
// Verificar tipo de cada argumento
|
|
634
|
+
// NOTA: Precisaríamos da ordem dos campos no evento para verificar corretamente
|
|
635
|
+
// Por ora, vamos verificar apenas se a quantidade bate
|
|
636
|
+
const camposArray = Array.from(camposEvento.entries());
|
|
637
|
+
for (let i = 0; i < node.argumentos.length; i++) {
|
|
638
|
+
const tipoArgumento = this.resolverTipo(node.argumentos[i]);
|
|
639
|
+
const [nomeCampo, tipoCampo] = camposArray[i];
|
|
640
|
+
if (!this.tiposCompatíveis(tipoCampo, tipoArgumento)) {
|
|
641
|
+
this.erro(`Argumento '${nomeCampo}' do evento '${node.evento}' deve ser '${tipoCampo}', recebido '${tipoArgumento}'`, node.argumentos[i].line || node.line, node.argumentos[i].column || node.column);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
verificarErro(node) {
|
|
647
|
+
const tipoMensagem = this.resolverTipo(node.mensagem);
|
|
648
|
+
if (tipoMensagem !== 'texto') {
|
|
649
|
+
this.erro(`Mensagem de erro deve ser do tipo 'texto', recebeu '${tipoMensagem}'`, node.line, node.column);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
verificarInstrucao(instrucao) {
|
|
653
|
+
switch (instrucao.kind) {
|
|
654
|
+
case 'Variavel':
|
|
655
|
+
this.verificarVariavel(instrucao);
|
|
656
|
+
break;
|
|
657
|
+
case 'Atribuicao':
|
|
658
|
+
this.verificarAtribuicao(instrucao);
|
|
659
|
+
break;
|
|
660
|
+
case 'ChamadaFuncao':
|
|
661
|
+
this.resolverTipo(instrucao);
|
|
662
|
+
break;
|
|
663
|
+
case 'Retorno':
|
|
664
|
+
this.verificarRetorno(instrucao);
|
|
665
|
+
break;
|
|
666
|
+
case 'Condicional':
|
|
667
|
+
this.verificarCondicional(instrucao);
|
|
668
|
+
break;
|
|
669
|
+
case 'Enquanto':
|
|
670
|
+
this.verificarEnquanto(instrucao);
|
|
671
|
+
break;
|
|
672
|
+
case 'Para':
|
|
673
|
+
this.verificarPara(instrucao);
|
|
674
|
+
break;
|
|
675
|
+
case 'EmissaoEvento':
|
|
676
|
+
this.verificarEmissaoEvento(instrucao);
|
|
677
|
+
break;
|
|
678
|
+
case 'Erro':
|
|
679
|
+
this.verificarErro(instrucao);
|
|
680
|
+
break;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
// Expressões — retornam o tipo resolvido (string)
|
|
684
|
+
resolverTipo(node) {
|
|
685
|
+
switch (node.kind) {
|
|
686
|
+
case 'Literal':
|
|
687
|
+
return this.resolverTipoLiteral(node);
|
|
688
|
+
case 'Identificador':
|
|
689
|
+
return this.resolverTipoIdentificador(node);
|
|
690
|
+
case 'Binario':
|
|
691
|
+
return this.resolverTipoBinario(node);
|
|
692
|
+
case 'Unario':
|
|
693
|
+
return this.resolverTipoUnario(node);
|
|
694
|
+
case 'ChamadaFuncao':
|
|
695
|
+
return this.resolverTipoChamada(node);
|
|
696
|
+
case 'AcessoMembro':
|
|
697
|
+
return this.resolverTipoAcessoMembro(node);
|
|
698
|
+
case 'Atribuicao':
|
|
699
|
+
this.verificarAtribuicao(node);
|
|
700
|
+
return 'vazio';
|
|
701
|
+
default:
|
|
702
|
+
return 'desconhecido';
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
resolverTipoLiteral(node) {
|
|
706
|
+
return node.tipoLiteral;
|
|
707
|
+
}
|
|
708
|
+
resolverTipoIdentificador(node) {
|
|
709
|
+
const simbolo = this.tabela.buscar(node.nome);
|
|
710
|
+
if (!simbolo) {
|
|
711
|
+
this.erro(`Variável '${node.nome}' não declarada`, node.line, node.column);
|
|
712
|
+
return 'desconhecido';
|
|
713
|
+
}
|
|
714
|
+
return simbolo.tipo;
|
|
715
|
+
}
|
|
716
|
+
resolverTipoBinario(node) {
|
|
717
|
+
const tipoEsquerda = this.resolverTipo(node.esquerda);
|
|
718
|
+
const tipoDireita = this.resolverTipo(node.direita);
|
|
719
|
+
switch (node.operador) {
|
|
720
|
+
case '+':
|
|
721
|
+
case '-':
|
|
722
|
+
case '*':
|
|
723
|
+
case '/':
|
|
724
|
+
if (tipoEsquerda === 'numero' && tipoDireita === 'numero')
|
|
725
|
+
return 'numero';
|
|
726
|
+
if (tipoEsquerda === 'decimal' && tipoDireita === 'decimal')
|
|
727
|
+
return 'decimal';
|
|
728
|
+
if (tipoEsquerda === 'decimal' && tipoDireita === 'numero')
|
|
729
|
+
return 'decimal';
|
|
730
|
+
if (tipoEsquerda === 'numero' && tipoDireita === 'decimal')
|
|
731
|
+
return 'decimal';
|
|
732
|
+
if (node.operador === '+' && tipoEsquerda === 'texto' && tipoDireita === 'texto')
|
|
733
|
+
return 'texto';
|
|
734
|
+
break;
|
|
735
|
+
case '==':
|
|
736
|
+
case '!=':
|
|
737
|
+
if (this.tiposCompatíveis(tipoEsquerda, tipoDireita))
|
|
738
|
+
return 'booleano';
|
|
739
|
+
break;
|
|
740
|
+
case '<':
|
|
741
|
+
case '<=':
|
|
742
|
+
case '>':
|
|
743
|
+
case '>=':
|
|
744
|
+
if ((tipoEsquerda === 'numero' || tipoEsquerda === 'decimal') &&
|
|
745
|
+
(tipoDireita === 'numero' || tipoDireita === 'decimal'))
|
|
746
|
+
return 'booleano';
|
|
747
|
+
if (tipoEsquerda === 'data' && tipoDireita === 'data')
|
|
748
|
+
return 'booleano';
|
|
749
|
+
if (tipoEsquerda === 'hora' && tipoDireita === 'hora')
|
|
750
|
+
return 'booleano';
|
|
751
|
+
break;
|
|
752
|
+
case 'e':
|
|
753
|
+
case 'ou':
|
|
754
|
+
if (tipoEsquerda === 'booleano' && tipoDireita === 'booleano')
|
|
755
|
+
return 'booleano';
|
|
756
|
+
break;
|
|
757
|
+
}
|
|
758
|
+
this.erro(`Operador '${node.operador}' não pode ser aplicado entre '${tipoEsquerda}' e '${tipoDireita}'`, node.line, node.column);
|
|
759
|
+
return 'desconhecido';
|
|
760
|
+
}
|
|
761
|
+
resolverTipoUnario(node) {
|
|
762
|
+
const tipoOperando = this.resolverTipo(node.operando);
|
|
763
|
+
switch (node.operador) {
|
|
764
|
+
case '-':
|
|
765
|
+
if (tipoOperando === 'numero' || tipoOperando === 'decimal')
|
|
766
|
+
return tipoOperando;
|
|
767
|
+
break;
|
|
768
|
+
case 'nao':
|
|
769
|
+
if (tipoOperando === 'booleano')
|
|
770
|
+
return 'booleano';
|
|
771
|
+
break;
|
|
772
|
+
}
|
|
773
|
+
this.erro(`Operador unário '${node.operador}' não pode ser aplicado ao tipo '${tipoOperando}'`, node.line, node.column);
|
|
774
|
+
return 'desconhecido';
|
|
775
|
+
}
|
|
776
|
+
resolverTipoChamada(node) {
|
|
777
|
+
const funcao = this.tabela.buscar(node.nome);
|
|
778
|
+
if (!funcao || funcao.kind !== 'funcao') {
|
|
779
|
+
this.erro(`Função '${node.nome}' não encontrada`, node.line, node.column);
|
|
780
|
+
return 'desconhecido';
|
|
781
|
+
}
|
|
782
|
+
// Verificar número de argumentos
|
|
783
|
+
const params = this.tabela.buscarParametrosFuncao(node.nome);
|
|
784
|
+
if (params !== null && node.argumentos.length !== params.length) {
|
|
785
|
+
this.erro(`Função '${node.nome}' espera ${params.length} argumentos, recebeu ${node.argumentos.length}`, node.line, node.column);
|
|
786
|
+
}
|
|
787
|
+
return funcao.tipo === 'vazio' ? 'vazio' : funcao.tipo;
|
|
788
|
+
}
|
|
789
|
+
resolverTipoAcessoMembro(node) {
|
|
790
|
+
const tipoObjeto = this.resolverTipo(node.objeto);
|
|
791
|
+
if (tipoObjeto === 'desconhecido')
|
|
792
|
+
return 'desconhecido';
|
|
793
|
+
// Check for built-in methods first
|
|
794
|
+
if (tipoObjeto === 'texto') {
|
|
795
|
+
const metodoTipo = this.metodosTexto[node.membro];
|
|
796
|
+
if (metodoTipo) {
|
|
797
|
+
return metodoTipo;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
if (tipoObjeto.startsWith('lista<')) {
|
|
801
|
+
const metodoTipo = this.metodosLista[node.membro];
|
|
802
|
+
if (metodoTipo) {
|
|
803
|
+
if (metodoTipo === 'T') {
|
|
804
|
+
// Extract element type from lista<Tipo>
|
|
805
|
+
const elementoTipo = tipoObjeto.substring(6, tipoObjeto.length - 1);
|
|
806
|
+
return elementoTipo;
|
|
807
|
+
}
|
|
808
|
+
return metodoTipo;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
const tipoCampo = this.tabela.buscarCampo(tipoObjeto, node.membro);
|
|
812
|
+
if (tipoCampo === null) {
|
|
813
|
+
this.erro(`'${tipoObjeto}' não possui campo '${node.membro}'`, node.line, node.column);
|
|
814
|
+
return 'desconhecido';
|
|
815
|
+
}
|
|
816
|
+
return tipoCampo;
|
|
817
|
+
}
|
|
818
|
+
// ── Utilitários ──────────────────────────────────────────
|
|
819
|
+
// Verifica se dois tipos são compatíveis para atribuição
|
|
820
|
+
// Ex: 'numero' é compatível com 'numero', mas não com 'texto'
|
|
821
|
+
tiposCompatíveis(esperado, recebido) {
|
|
822
|
+
if (esperado === recebido)
|
|
823
|
+
return true;
|
|
824
|
+
// decimal aceita numero
|
|
825
|
+
if (esperado === 'decimal' && recebido === 'numero')
|
|
826
|
+
return true;
|
|
827
|
+
// id aceita numero (IDs podem ser representados como números)
|
|
828
|
+
if (esperado === 'id' && recebido === 'numero')
|
|
829
|
+
return true;
|
|
830
|
+
// Verificar herança de classes
|
|
831
|
+
if (this.verificarHeranca(esperado, recebido))
|
|
832
|
+
return true;
|
|
833
|
+
return false;
|
|
834
|
+
}
|
|
835
|
+
// Verifica se recebido é subclasse de esperado (herança)
|
|
836
|
+
verificarHeranca(esperado, recebido) {
|
|
837
|
+
// Buscar símbolos dos tipos
|
|
838
|
+
const simboloEsperado = this.tabela.buscar(esperado);
|
|
839
|
+
const simboloRecebido = this.tabela.buscar(recebido);
|
|
840
|
+
// Ambos devem ser classes ou entidades
|
|
841
|
+
if (!simboloEsperado || !simboloRecebido)
|
|
842
|
+
return false;
|
|
843
|
+
if (!['classe', 'entidade'].includes(simboloEsperado.kind))
|
|
844
|
+
return false;
|
|
845
|
+
if (!['classe', 'entidade'].includes(simboloRecebido.kind))
|
|
846
|
+
return false;
|
|
847
|
+
// Verificar cadeia de herança recursivamente
|
|
848
|
+
let tipoAtual = recebido;
|
|
849
|
+
const visitados = new Set();
|
|
850
|
+
while (tipoAtual && !visitados.has(tipoAtual)) {
|
|
851
|
+
visitados.add(tipoAtual);
|
|
852
|
+
if (tipoAtual === esperado) {
|
|
853
|
+
return true;
|
|
854
|
+
}
|
|
855
|
+
// Buscar superclasse do tipo atual
|
|
856
|
+
const simboloAtual = this.tabela.buscar(tipoAtual);
|
|
857
|
+
if (!simboloAtual)
|
|
858
|
+
break;
|
|
859
|
+
// Encontrar definição da classe/entidade para verificar superclasse
|
|
860
|
+
const superClasse = this.buscarSuperClasse(tipoAtual);
|
|
861
|
+
if (!superClasse)
|
|
862
|
+
break;
|
|
863
|
+
tipoAtual = superClasse;
|
|
864
|
+
}
|
|
865
|
+
return false;
|
|
866
|
+
}
|
|
867
|
+
// Busca superclasse de uma classe/entidade na tabela de símbolos
|
|
868
|
+
buscarSuperClasse(nomeTipo) {
|
|
869
|
+
return this.tabela.buscarSuperClasse(nomeTipo);
|
|
870
|
+
}
|
|
871
|
+
// Verifica se todos os caminhos de um bloco terminam com retorno
|
|
872
|
+
verificarRetornoEmTodosCaminhos(bloco) {
|
|
873
|
+
if (bloco.instrucoes.length === 0) {
|
|
874
|
+
return false;
|
|
875
|
+
}
|
|
876
|
+
// Verificar última instrução do bloco
|
|
877
|
+
const ultimaInstrucao = bloco.instrucoes[bloco.instrucoes.length - 1];
|
|
878
|
+
if (ultimaInstrucao.kind === 'Retorno') {
|
|
879
|
+
return true;
|
|
880
|
+
}
|
|
881
|
+
if (ultimaInstrucao.kind === 'Condicional') {
|
|
882
|
+
const condicional = ultimaInstrucao;
|
|
883
|
+
// Para se/senão: ambos os branches devem retornar
|
|
884
|
+
if (condicional.senao) {
|
|
885
|
+
const entaoRetorna = this.verificarRetornoEmTodosCaminhos(condicional.entao);
|
|
886
|
+
const senaoRetorna = this.verificarRetornoEmTodosCaminhos(condicional.senao);
|
|
887
|
+
return entaoRetorna && senaoRetorna;
|
|
888
|
+
}
|
|
889
|
+
else {
|
|
890
|
+
// se sem senão: não garante retorno em todos os caminhos
|
|
891
|
+
return false;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
// Para outros tipos de instrução, não garante retorno
|
|
895
|
+
return false;
|
|
896
|
+
}
|
|
897
|
+
// Verifica se um tipo existe (primitivo, classe, entidade, enum declarado)
|
|
898
|
+
tipoExiste(nome) {
|
|
899
|
+
const tiposPrimitivos = ['texto', 'numero', 'decimal', 'booleano', 'data', 'hora', 'id'];
|
|
900
|
+
if (tiposPrimitivos.includes(nome))
|
|
901
|
+
return true;
|
|
902
|
+
// Verificar tipos genéricos
|
|
903
|
+
if (nome.startsWith('lista<') && nome.endsWith('>')) {
|
|
904
|
+
const elementoTipo = nome.substring(6, nome.length - 1);
|
|
905
|
+
return this.tipoExiste(elementoTipo);
|
|
906
|
+
}
|
|
907
|
+
if (nome.startsWith('mapa<') && nome.endsWith('>')) {
|
|
908
|
+
const partes = nome.substring(4, nome.length - 1).split(',');
|
|
909
|
+
if (partes.length !== 2)
|
|
910
|
+
return false;
|
|
911
|
+
return this.tipoExiste(partes[0].trim()) && this.tipoExiste(partes[1].trim());
|
|
912
|
+
}
|
|
913
|
+
const simbolo = this.tabela.buscar(nome);
|
|
914
|
+
return simbolo !== null && ['classe', 'entidade', 'enum', 'interface'].includes(simbolo.kind);
|
|
915
|
+
}
|
|
916
|
+
// Converte TipoNode em string para comparação
|
|
917
|
+
// Ex: TipoLista<Produto> → 'lista<Produto>'
|
|
918
|
+
tipoParaString(tipo) {
|
|
919
|
+
switch (tipo.kind) {
|
|
920
|
+
case 'TipoSimples':
|
|
921
|
+
let resultado = tipo.nome;
|
|
922
|
+
if (tipo.opcional)
|
|
923
|
+
resultado += '?';
|
|
924
|
+
if (tipo.obrigatorio)
|
|
925
|
+
resultado += '!';
|
|
926
|
+
return resultado;
|
|
927
|
+
case 'TipoLista':
|
|
928
|
+
return `lista<${this.tipoParaString(tipo.elementoTipo)}>`;
|
|
929
|
+
case 'TipoMapa':
|
|
930
|
+
return `mapa<${this.tipoParaString(tipo.chaveTipo)},${this.tipoParaString(tipo.valorTipo)}>`;
|
|
931
|
+
case 'TipoObjeto':
|
|
932
|
+
return 'objeto';
|
|
933
|
+
default:
|
|
934
|
+
return 'desconhecido';
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
registrarTela(node) {
|
|
938
|
+
const simbolo = {
|
|
939
|
+
nome: node.nome,
|
|
940
|
+
kind: 'tela',
|
|
941
|
+
tipo: 'Tela',
|
|
942
|
+
linha: node.line,
|
|
943
|
+
coluna: node.column,
|
|
944
|
+
escopo: this.tabela.escopoAtual()
|
|
945
|
+
};
|
|
946
|
+
this.tabela.declarar(simbolo);
|
|
947
|
+
}
|
|
948
|
+
verificarTela(node) {
|
|
949
|
+
const tiposElementosValidos = ['tabela', 'formulario', 'botao', 'card', 'modal', 'grafico'];
|
|
950
|
+
for (const elem of node.elementos) {
|
|
951
|
+
if (!tiposElementosValidos.includes(elem.tipo)) {
|
|
952
|
+
this.erro(`Tipo de elemento '${elem.tipo}' inválido. Use: tabela, formulario, botao ou card`, elem.line, elem.column);
|
|
953
|
+
}
|
|
954
|
+
// Referências a entidades em tela podem vir de outros módulos —
|
|
955
|
+
// resolução completa está prevista para v0.2.0 (sistema de módulos).
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
// Adiciona erro sem lançar exceção (continua verificando o resto)
|
|
959
|
+
erro(mensagem, linha, coluna) {
|
|
960
|
+
this.erros.push({ mensagem, linha, coluna });
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
//# sourceMappingURL=type_checker.js.map
|