@yakuzaa/jade-compiler 0.1.2 → 0.1.4
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/cli.js +201 -23
- package/dist/cli.js.map +1 -1
- package/dist/codegen/ir_generator.d.ts +13 -0
- package/dist/codegen/ir_generator.d.ts.map +1 -1
- package/dist/codegen/ir_generator.js +167 -6
- package/dist/codegen/ir_generator.js.map +1 -1
- package/dist/codegen/ir_nodes.d.ts +19 -0
- package/dist/codegen/ir_nodes.d.ts.map +1 -1
- package/dist/codegen/wasm_generator.d.ts.map +1 -1
- package/dist/codegen/wasm_generator.js +0 -1
- package/dist/codegen/wasm_generator.js.map +1 -1
- package/dist/formatter/formatter.d.ts +48 -0
- package/dist/formatter/formatter.d.ts.map +1 -0
- package/dist/formatter/formatter.js +280 -0
- package/dist/formatter/formatter.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/dist/lexer/lexer.d.ts.map +1 -1
- package/dist/lexer/lexer.js +15 -7
- package/dist/lexer/lexer.js.map +1 -1
- package/dist/lexer/token_type.d.ts +3 -1
- package/dist/lexer/token_type.d.ts.map +1 -1
- package/dist/lexer/token_type.js +2 -0
- package/dist/lexer/token_type.js.map +1 -1
- package/dist/linter/linter.d.ts +46 -0
- package/dist/linter/linter.d.ts.map +1 -0
- package/dist/linter/linter.js +166 -0
- package/dist/linter/linter.js.map +1 -0
- package/dist/parser/parse_result.d.ts +2 -0
- package/dist/parser/parse_result.d.ts.map +1 -1
- package/dist/parser/parser.d.ts.map +1 -1
- package/dist/parser/parser.js +117 -21
- package/dist/parser/parser.js.map +1 -1
- package/dist/semantic/symbol_table.d.ts +1 -0
- package/dist/semantic/symbol_table.d.ts.map +1 -1
- package/dist/semantic/symbol_table.js +11 -0
- package/dist/semantic/symbol_table.js.map +1 -1
- package/dist/semantic/type_checker.d.ts +7 -0
- package/dist/semantic/type_checker.d.ts.map +1 -1
- package/dist/semantic/type_checker.js +187 -72
- package/dist/semantic/type_checker.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +1 -1
|
@@ -24,12 +24,12 @@ export class TypeChecker {
|
|
|
24
24
|
'contem': 'booleano',
|
|
25
25
|
'adicionar': 'vazio',
|
|
26
26
|
'remover': 'vazio',
|
|
27
|
-
'obter': '
|
|
27
|
+
'obter': 'qualquer', // tipo do elemento — sem suporte a genéricos ainda
|
|
28
28
|
'filtrar': 'lista', // lista.filtrar(condicao) -> lista<T>
|
|
29
29
|
'ordenar': 'lista', // lista.ordenar(campo) -> lista<T>
|
|
30
|
-
'primeiro': '
|
|
31
|
-
'ultimo': '
|
|
32
|
-
'vazia': 'booleano'
|
|
30
|
+
'primeiro': 'qualquer', // tipo do elemento — sem suporte a genéricos ainda
|
|
31
|
+
'ultimo': 'qualquer', // tipo do elemento — sem suporte a genéricos ainda
|
|
32
|
+
'vazia': 'booleano'
|
|
33
33
|
};
|
|
34
34
|
// ── Verificações principais ──────────────────────────────
|
|
35
35
|
verificarPrograma(node) {
|
|
@@ -70,7 +70,7 @@ export class TypeChecker {
|
|
|
70
70
|
// Ciclo encontrado
|
|
71
71
|
const ciclo = [...caminho, vizinho].join(' → ');
|
|
72
72
|
this.erro(`Ciclo de eventos detectado: ${ciclo}. ` +
|
|
73
|
-
`Isso causaria loop infinito em runtime.`, 0, 0);
|
|
73
|
+
`Isso causaria loop infinito em runtime.`, 0, 0, 'Redesenhe o fluxo de eventos para evitar que um evento dispare outro que eventualmente o re-dispare');
|
|
74
74
|
return true;
|
|
75
75
|
}
|
|
76
76
|
}
|
|
@@ -117,9 +117,9 @@ export class TypeChecker {
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
registrarImportacao(node) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
if (node.alias) {
|
|
121
|
+
// importar financeiro como fin → registra 'fin' como namespace do módulo
|
|
122
|
+
if (!this.tabela.buscar(node.alias)) {
|
|
123
123
|
this.tabela.declarar({
|
|
124
124
|
nome: node.alias,
|
|
125
125
|
kind: 'entidade',
|
|
@@ -129,8 +129,11 @@ export class TypeChecker {
|
|
|
129
129
|
escopo: this.tabela.escopoAtual()
|
|
130
130
|
});
|
|
131
131
|
}
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
}
|
|
133
|
+
else if (node.item && !node.wildcard) {
|
|
134
|
+
// importar estoque.Produto → registra 'Produto' como tipo externo conhecido
|
|
135
|
+
// Se já declarado localmente, o símbolo local tem precedência (não substituir)
|
|
136
|
+
if (!this.tabela.buscar(node.item)) {
|
|
134
137
|
this.tabela.declarar({
|
|
135
138
|
nome: node.item,
|
|
136
139
|
kind: 'classe',
|
|
@@ -140,12 +143,10 @@ export class TypeChecker {
|
|
|
140
143
|
escopo: this.tabela.escopoAtual()
|
|
141
144
|
});
|
|
142
145
|
}
|
|
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
|
}
|
|
146
|
-
|
|
147
|
-
//
|
|
148
|
-
|
|
147
|
+
else if (node.wildcard) {
|
|
148
|
+
// Wildcard bloqueado — ambíguo e inseguro
|
|
149
|
+
this.erro(`Import wildcard 'importar ${node.modulo}.*' não é permitido`, node.line, node.column, `Importe apenas o que precisa: 'importar ${node.modulo}.NomeDoTipo'`);
|
|
149
150
|
}
|
|
150
151
|
}
|
|
151
152
|
registrarClasse(node) {
|
|
@@ -243,7 +244,7 @@ export class TypeChecker {
|
|
|
243
244
|
// Verificar superclasse
|
|
244
245
|
if (node.superClasse) {
|
|
245
246
|
if (!this.tipoExiste(node.superClasse)) {
|
|
246
|
-
this.erro(`Superclasse '${node.superClasse}' não encontrada`, node.line, node.column);
|
|
247
|
+
this.erro(`Superclasse '${node.superClasse}' não encontrada`, node.line, node.column, `Declare a classe '${node.superClasse}' antes de usá-la como superclasse, ou verifique se o nome está correto`);
|
|
247
248
|
}
|
|
248
249
|
else {
|
|
249
250
|
// Registrar superclasse para verificação de herança
|
|
@@ -253,7 +254,7 @@ export class TypeChecker {
|
|
|
253
254
|
// Verificar interfaces
|
|
254
255
|
for (const interfaceNome of node.interfaces) {
|
|
255
256
|
if (!this.tipoExiste(interfaceNome)) {
|
|
256
|
-
this.erro(`Interface '${interfaceNome}' não encontrada`, node.line, node.column);
|
|
257
|
+
this.erro(`Interface '${interfaceNome}' não encontrada`, node.line, node.column, `Declare a interface '${interfaceNome}' antes de usá-la com 'implements'`);
|
|
257
258
|
}
|
|
258
259
|
}
|
|
259
260
|
// Registrar campos e métodos
|
|
@@ -284,7 +285,7 @@ export class TypeChecker {
|
|
|
284
285
|
temId = true;
|
|
285
286
|
}
|
|
286
287
|
if (!this.tipoExiste(tipoCampo)) {
|
|
287
|
-
this.erro(`Tipo '${tipoCampo}' não existe`, campo.line, campo.column);
|
|
288
|
+
this.erro(`Tipo '${tipoCampo}' não existe`, campo.line, campo.column, this.dicaTipoNaoExiste(tipoCampo));
|
|
288
289
|
}
|
|
289
290
|
const simbolo = {
|
|
290
291
|
nome: campo.nome,
|
|
@@ -299,7 +300,7 @@ export class TypeChecker {
|
|
|
299
300
|
this.tabela.registrarCampo(node.nome, campo.nome, tipoCampo);
|
|
300
301
|
}
|
|
301
302
|
if (!temId) {
|
|
302
|
-
this.erro(`Entidade '${node.nome}' deve ter exatamente um campo do tipo 'id'`, node.line, node.column);
|
|
303
|
+
this.erro(`Entidade '${node.nome}' deve ter exatamente um campo do tipo 'id'`, node.line, node.column, `Adicione 'id: id' à entidade '${node.nome}' para que ela possa ser salva e buscada`);
|
|
303
304
|
}
|
|
304
305
|
this.tabela.sairEscopo();
|
|
305
306
|
}
|
|
@@ -322,7 +323,7 @@ export class TypeChecker {
|
|
|
322
323
|
for (const parametro of node.parametros) {
|
|
323
324
|
const tipoParam = this.tipoParaString(parametro.tipo);
|
|
324
325
|
if (!this.tipoExiste(tipoParam)) {
|
|
325
|
-
this.erro(`Tipo '${tipoParam}' não existe`, parametro.line, parametro.column);
|
|
326
|
+
this.erro(`Tipo '${tipoParam}' não existe`, parametro.line, parametro.column, this.dicaTipoNaoExiste(tipoParam));
|
|
326
327
|
}
|
|
327
328
|
tiposParams.push(tipoParam);
|
|
328
329
|
const simbolo = {
|
|
@@ -345,7 +346,7 @@ export class TypeChecker {
|
|
|
345
346
|
// Se tipoRetorno != void, verificar se bloco termina com Retorno
|
|
346
347
|
if (tipoRetorno !== 'vazio') {
|
|
347
348
|
if (!this.verificarRetornoEmTodosCaminhos(node.corpo)) {
|
|
348
|
-
this.erro(`Função '${node.nome}' deve retornar valor em todos os caminhos`, node.line, node.column);
|
|
349
|
+
this.erro(`Função '${node.nome}' deve retornar valor em todos os caminhos`, node.line, node.column, `Certifique-se que todos os ramos do 'se/senao' terminam com 'retornar valor'`);
|
|
349
350
|
}
|
|
350
351
|
}
|
|
351
352
|
}
|
|
@@ -356,7 +357,7 @@ export class TypeChecker {
|
|
|
356
357
|
// Verificar se evento existe
|
|
357
358
|
const evento = this.tabela.buscar(node.evento);
|
|
358
359
|
if (!evento || evento.kind !== 'evento') {
|
|
359
|
-
this.erro(`Evento '${node.evento}' não encontrado`, node.line, node.column);
|
|
360
|
+
this.erro(`Evento '${node.evento}' não encontrado`, node.line, node.column, `Declare o evento antes de escutá-lo: 'evento ${node.evento}'`);
|
|
360
361
|
return; // Retorna para não continuar com um evento inválido
|
|
361
362
|
}
|
|
362
363
|
// Entrar no escopo do ouvinte
|
|
@@ -393,7 +394,7 @@ export class TypeChecker {
|
|
|
393
394
|
for (const campo of node.campos) {
|
|
394
395
|
const tipoCampo = this.tipoParaString(campo.tipo);
|
|
395
396
|
if (!this.tipoExiste(tipoCampo)) {
|
|
396
|
-
this.erro(`Tipo '${tipoCampo}' não existe`, campo.line, campo.column);
|
|
397
|
+
this.erro(`Tipo '${tipoCampo}' não existe`, campo.line, campo.column, 'Use um tipo válido para o campo do evento: texto, numero, decimal, booleano, data, hora ou id');
|
|
397
398
|
}
|
|
398
399
|
const simbolo = {
|
|
399
400
|
nome: campo.nome,
|
|
@@ -431,13 +432,15 @@ export class TypeChecker {
|
|
|
431
432
|
// Verificar se a condição é booleana
|
|
432
433
|
const tipoCondicao = this.resolverTipo(node.condicao);
|
|
433
434
|
if (tipoCondicao !== 'booleano') {
|
|
434
|
-
this.erro(`Condição da regra '${node.nome}' deve ser booleana, recebido '${tipoCondicao}'`, node.condicao.line, node.condicao.column);
|
|
435
|
+
this.erro(`Condição da regra '${node.nome}' deve ser booleana, recebido '${tipoCondicao}'`, node.condicao.line, node.condicao.column, "A condição deve resultar em verdadeiro/falso, ex: `quando produto.estoque < 10` ou `quando cliente.ativo`");
|
|
435
436
|
}
|
|
436
|
-
|
|
437
|
+
this.tabela.entrarEscopo('regra_entao');
|
|
437
438
|
this.verificarBloco(node.entao);
|
|
438
|
-
|
|
439
|
+
this.tabela.sairEscopo();
|
|
439
440
|
if (node.senao) {
|
|
441
|
+
this.tabela.entrarEscopo('regra_senao');
|
|
440
442
|
this.verificarBloco(node.senao);
|
|
443
|
+
this.tabela.sairEscopo();
|
|
441
444
|
}
|
|
442
445
|
}
|
|
443
446
|
verificarImportacao(_node) {
|
|
@@ -493,20 +496,20 @@ export class TypeChecker {
|
|
|
493
496
|
if (node.tipo) {
|
|
494
497
|
tipoDeclarado = this.tipoParaString(node.tipo);
|
|
495
498
|
if (!this.tipoExiste(tipoDeclarado)) {
|
|
496
|
-
this.erro(`Tipo '${tipoDeclarado}' não existe`, node.line, node.column);
|
|
499
|
+
this.erro(`Tipo '${tipoDeclarado}' não existe`, node.line, node.column, this.dicaTipoNaoExiste(tipoDeclarado));
|
|
497
500
|
}
|
|
498
501
|
}
|
|
499
502
|
if (node.inicializador) {
|
|
500
503
|
const tipoInicializador = this.resolverTipo(node.inicializador);
|
|
501
504
|
if (tipoDeclarado && !this.tiposCompatíveis(tipoDeclarado, tipoInicializador)) {
|
|
502
|
-
this.erro(`Tipo incompatível: esperado '${tipoDeclarado}', recebido '${tipoInicializador}'`, node.line, node.column);
|
|
505
|
+
this.erro(`Tipo incompatível: esperado '${tipoDeclarado}', recebido '${tipoInicializador}'`, node.line, node.column, `Converta o valor para '${tipoDeclarado}' ou mude o tipo declarado da variável`);
|
|
503
506
|
}
|
|
504
507
|
if (!tipoDeclarado) {
|
|
505
508
|
tipoDeclarado = tipoInicializador;
|
|
506
509
|
}
|
|
507
510
|
}
|
|
508
511
|
else if (!tipoDeclarado) {
|
|
509
|
-
this.erro(`Variável '${node.nome}' precisa de tipo ou inicializador`, node.line, node.column);
|
|
512
|
+
this.erro(`Variável '${node.nome}' precisa de tipo ou inicializador`, node.line, node.column, `Declare com tipo: 'variavel ${node.nome}: numero', ou com valor: 'variavel ${node.nome} = 0'`);
|
|
510
513
|
}
|
|
511
514
|
const simbolo = {
|
|
512
515
|
nome: node.nome,
|
|
@@ -520,7 +523,7 @@ export class TypeChecker {
|
|
|
520
523
|
this.tabela.declarar(simbolo);
|
|
521
524
|
}
|
|
522
525
|
catch (e) {
|
|
523
|
-
this.erro(e.message, node.line, node.column);
|
|
526
|
+
this.erro(e.message, node.line, node.column, `Renomeie a variável '${node.nome}' ou remova a declaração duplicada`);
|
|
524
527
|
}
|
|
525
528
|
}
|
|
526
529
|
verificarAtribuicao(node) {
|
|
@@ -528,11 +531,11 @@ export class TypeChecker {
|
|
|
528
531
|
if (typeof node.alvo === 'string') {
|
|
529
532
|
const simbolo = this.tabela.buscar(node.alvo);
|
|
530
533
|
if (!simbolo) {
|
|
531
|
-
this.erro(`Variável '${node.alvo}' não declarada`, node.line, node.column);
|
|
534
|
+
this.erro(`Variável '${node.alvo}' não declarada`, node.line, node.column, `Declare a variável antes de usá-la: 'variavel ${node.alvo}: tipo'`);
|
|
532
535
|
return;
|
|
533
536
|
}
|
|
534
537
|
if (!this.tiposCompatíveis(simbolo.tipo, tipoValor)) {
|
|
535
|
-
this.erro(`Tipo incompatível: esperado '${simbolo.tipo}', recebido '${tipoValor}'`, node.line, node.column);
|
|
538
|
+
this.erro(`Tipo incompatível: esperado '${simbolo.tipo}', recebido '${tipoValor}'`, node.line, node.column, `O valor atribuído não é do mesmo tipo da variável '${node.alvo}' (${simbolo.tipo})`);
|
|
536
539
|
}
|
|
537
540
|
}
|
|
538
541
|
else {
|
|
@@ -540,46 +543,55 @@ export class TypeChecker {
|
|
|
540
543
|
const tipoObjeto = this.resolverTipo(node.alvo.objeto);
|
|
541
544
|
const objetoSimbolo = this.tabela.buscar(tipoObjeto);
|
|
542
545
|
if (!objetoSimbolo || (objetoSimbolo.kind !== 'classe' && objetoSimbolo.kind !== 'entidade')) {
|
|
543
|
-
this.erro(`'${tipoObjeto}' não é uma classe ou entidade`, node.alvo.line, node.alvo.column);
|
|
546
|
+
this.erro(`'${tipoObjeto}' não é uma classe ou entidade`, node.alvo.line, node.alvo.column, "Só é possível acessar campos com '.' em entidades ou classes");
|
|
544
547
|
return;
|
|
545
548
|
}
|
|
546
549
|
// Verificar se campo existe e tipo compatível
|
|
547
550
|
const tipoCampo = this.tabela.buscarCampo(tipoObjeto, node.alvo.membro);
|
|
548
551
|
if (tipoCampo === null) {
|
|
549
|
-
this.erro(`'${tipoObjeto}' não possui campo '${node.alvo.membro}'`, node.alvo.line, node.alvo.column);
|
|
552
|
+
this.erro(`'${tipoObjeto}' não possui campo '${node.alvo.membro}'`, node.alvo.line, node.alvo.column, `Verifique o nome do campo — pode estar digitado errado. Os campos disponíveis estão na declaração da entidade '${tipoObjeto}'`);
|
|
550
553
|
return;
|
|
551
554
|
}
|
|
552
555
|
if (!this.tiposCompatíveis(tipoCampo, tipoValor)) {
|
|
553
|
-
this.erro(`Tipo incompatível: campo '${node.alvo.membro}' espera '${tipoCampo}', recebido '${tipoValor}'`, node.line, node.column);
|
|
556
|
+
this.erro(`Tipo incompatível: campo '${node.alvo.membro}' espera '${tipoCampo}', recebido '${tipoValor}'`, node.line, node.column, `Converta o valor para '${tipoCampo}' antes de atribuir ao campo '${node.alvo.membro}'`);
|
|
554
557
|
}
|
|
555
558
|
}
|
|
556
559
|
}
|
|
557
560
|
verificarCondicional(node) {
|
|
558
561
|
const tipoCondicao = this.resolverTipo(node.condicao);
|
|
559
562
|
if (tipoCondicao !== 'booleano') {
|
|
560
|
-
this.erro(`Condição do 'se' deve ser booleano, recebeu '${tipoCondicao}'`, node.condicao.line, node.condicao.column);
|
|
563
|
+
this.erro(`Condição do 'se' deve ser booleano, recebeu '${tipoCondicao}'`, node.condicao.line, node.condicao.column, "Use uma comparação: `se x > 0`, `se produto.ativo`, `se nome == \"João\"`");
|
|
561
564
|
}
|
|
565
|
+
// Cada branch tem seu próprio escopo — evita vazamento de variáveis entre branches
|
|
566
|
+
this.tabela.entrarEscopo('se');
|
|
562
567
|
this.verificarBloco(node.entao);
|
|
568
|
+
this.tabela.sairEscopo();
|
|
563
569
|
if (node.senao) {
|
|
570
|
+
this.tabela.entrarEscopo('senao');
|
|
564
571
|
this.verificarBloco(node.senao);
|
|
572
|
+
this.tabela.sairEscopo();
|
|
565
573
|
}
|
|
566
574
|
}
|
|
567
575
|
verificarEnquanto(node) {
|
|
568
576
|
const tipoCondicao = this.resolverTipo(node.condicao);
|
|
569
577
|
if (tipoCondicao !== 'booleano') {
|
|
570
|
-
this.erro(`Condição do 'enquanto' deve ser booleano, recebeu '${tipoCondicao}'`, node.condicao.line, node.condicao.column);
|
|
578
|
+
this.erro(`Condição do 'enquanto' deve ser booleano, recebeu '${tipoCondicao}'`, node.condicao.line, node.condicao.column, "Use uma condição booleana, ex: `enquanto i < 10` ou `enquanto cliente.ativo`");
|
|
571
579
|
}
|
|
580
|
+
// Corpo do enquanto tem escopo próprio
|
|
581
|
+
this.tabela.entrarEscopo('enquanto');
|
|
572
582
|
this.verificarBloco(node.corpo);
|
|
583
|
+
this.tabela.sairEscopo();
|
|
573
584
|
}
|
|
574
585
|
verificarPara(node) {
|
|
575
586
|
const tipoIteravel = this.resolverTipo(node.iteravel);
|
|
576
587
|
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);
|
|
588
|
+
this.erro(`Iterável do 'para' deve ser do tipo 'lista<T>', recebeu '${tipoIteravel}'`, node.iteravel.line, node.iteravel.column, "O 'para' itera sobre listas, ex: `para produto em listaProdutos` onde listaProdutos é do tipo lista<Produto>");
|
|
578
589
|
return;
|
|
579
590
|
}
|
|
580
591
|
// Extrair tipo do elemento: lista<Tipo> -> Tipo
|
|
581
592
|
const tipoElemento = tipoIteravel.substring(6, tipoIteravel.length - 1);
|
|
582
|
-
//
|
|
593
|
+
// Variável de iteração e corpo do loop têm escopo próprio — não vaza para fora
|
|
594
|
+
this.tabela.entrarEscopo('para');
|
|
583
595
|
const simbolo = {
|
|
584
596
|
nome: node.variavel,
|
|
585
597
|
kind: 'variavel',
|
|
@@ -592,34 +604,35 @@ export class TypeChecker {
|
|
|
592
604
|
this.tabela.declarar(simbolo);
|
|
593
605
|
}
|
|
594
606
|
catch (e) {
|
|
595
|
-
this.erro(e.message, node.line, node.column);
|
|
607
|
+
this.erro(e.message, node.line, node.column, `Renomeie a variável de iteração '${node.variavel}' para evitar conflito com nome existente`);
|
|
596
608
|
}
|
|
597
609
|
this.verificarBloco(node.corpo);
|
|
610
|
+
this.tabela.sairEscopo();
|
|
598
611
|
}
|
|
599
612
|
verificarRetorno(node) {
|
|
600
613
|
if (!this.funcaoAtual) {
|
|
601
|
-
this.erro(`'retornar' só pode ser usado dentro de uma função`, node.line, node.column);
|
|
614
|
+
this.erro(`'retornar' só pode ser usado dentro de uma função`, node.line, node.column, "Mova o 'retornar' para dentro de um bloco 'funcao'");
|
|
602
615
|
return;
|
|
603
616
|
}
|
|
604
617
|
if (node.valor) {
|
|
605
618
|
if (!this.funcaoAtual.tipoRetorno) {
|
|
606
|
-
this.erro(`Função '${this.funcaoAtual.nome}' não deve retornar valor`, node.line, node.column);
|
|
619
|
+
this.erro(`Função '${this.funcaoAtual.nome}' não deve retornar valor`, node.line, node.column, `Remova o valor do 'retornar', ou adicione '-> tipo' na declaração da função '${this.funcaoAtual.nome}'`);
|
|
607
620
|
return;
|
|
608
621
|
}
|
|
609
622
|
const tipoRetorno = this.tipoParaString(this.funcaoAtual.tipoRetorno);
|
|
610
623
|
const tipoValor = this.resolverTipo(node.valor);
|
|
611
624
|
if (!this.tiposCompatíveis(tipoRetorno, tipoValor)) {
|
|
612
|
-
this.erro(`Tipo incompatível: esperado '${tipoRetorno}', recebido '${tipoValor}'`, node.line, node.column);
|
|
625
|
+
this.erro(`Tipo incompatível: esperado '${tipoRetorno}', recebido '${tipoValor}'`, node.line, node.column, `O valor retornado não combina com o tipo de retorno '${tipoRetorno}' declarado na função`);
|
|
613
626
|
}
|
|
614
627
|
}
|
|
615
628
|
else if (this.funcaoAtual.tipoRetorno) {
|
|
616
|
-
this.erro(`Função '${this.funcaoAtual.nome}' deve retornar valor`, node.line, node.column);
|
|
629
|
+
this.erro(`Função '${this.funcaoAtual.nome}' deve retornar valor`, node.line, node.column, `Adicione 'retornar valor' ao final da função, ou remova '-> ${this.tipoParaString(this.funcaoAtual.tipoRetorno)}' da declaração`);
|
|
617
630
|
}
|
|
618
631
|
}
|
|
619
632
|
verificarEmissaoEvento(node) {
|
|
620
633
|
const evento = this.tabela.buscar(node.evento);
|
|
621
634
|
if (!evento || evento.kind !== 'evento') {
|
|
622
|
-
this.erro(`Evento '${node.evento}' não encontrado`, node.line, node.column);
|
|
635
|
+
this.erro(`Evento '${node.evento}' não encontrado`, node.line, node.column, `Declare o evento antes de emiti-lo: 'evento ${node.evento}'`);
|
|
623
636
|
return;
|
|
624
637
|
}
|
|
625
638
|
// Verificar argumentos contra campos do evento
|
|
@@ -627,7 +640,7 @@ export class TypeChecker {
|
|
|
627
640
|
if (camposEvento) {
|
|
628
641
|
// Verificar quantidade de argumentos
|
|
629
642
|
if (node.argumentos.length !== camposEvento.size) {
|
|
630
|
-
this.erro(`Evento '${node.evento}' espera ${camposEvento.size} argumentos, recebeu ${node.argumentos.length}`, node.line, node.column);
|
|
643
|
+
this.erro(`Evento '${node.evento}' espera ${camposEvento.size} argumentos, recebeu ${node.argumentos.length}`, node.line, node.column, `Forneça exatamente um valor para cada campo do evento na ordem declarada: emitir ${node.evento}(${Array.from(camposEvento.keys()).join(', ')})`);
|
|
631
644
|
return;
|
|
632
645
|
}
|
|
633
646
|
// Verificar tipo de cada argumento
|
|
@@ -638,7 +651,7 @@ export class TypeChecker {
|
|
|
638
651
|
const tipoArgumento = this.resolverTipo(node.argumentos[i]);
|
|
639
652
|
const [nomeCampo, tipoCampo] = camposArray[i];
|
|
640
653
|
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);
|
|
654
|
+
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, `Verifique o tipo do valor fornecido para o campo '${nomeCampo}' — esperado '${tipoCampo}'`);
|
|
642
655
|
}
|
|
643
656
|
}
|
|
644
657
|
}
|
|
@@ -646,7 +659,7 @@ export class TypeChecker {
|
|
|
646
659
|
verificarErro(node) {
|
|
647
660
|
const tipoMensagem = this.resolverTipo(node.mensagem);
|
|
648
661
|
if (tipoMensagem !== 'texto') {
|
|
649
|
-
this.erro(`Mensagem de erro deve ser do tipo 'texto', recebeu '${tipoMensagem}'`, node.line, node.column);
|
|
662
|
+
this.erro(`Mensagem de erro deve ser do tipo 'texto', recebeu '${tipoMensagem}'`, node.line, node.column, 'Use uma string entre aspas: `erro "Mensagem de erro aqui"`');
|
|
650
663
|
}
|
|
651
664
|
}
|
|
652
665
|
verificarInstrucao(instrucao) {
|
|
@@ -708,7 +721,11 @@ export class TypeChecker {
|
|
|
708
721
|
resolverTipoIdentificador(node) {
|
|
709
722
|
const simbolo = this.tabela.buscar(node.nome);
|
|
710
723
|
if (!simbolo) {
|
|
711
|
-
this.
|
|
724
|
+
const sug = this.sugestao(node.nome, this.tabela.buscarTodosNomesVisiveis());
|
|
725
|
+
const dica = sug
|
|
726
|
+
? `Você quis dizer '${sug}'? Declare a variável antes de usá-la: 'variavel ${node.nome}: tipo'`
|
|
727
|
+
: `Declare a variável antes de usá-la: 'variavel ${node.nome}: tipo'`;
|
|
728
|
+
this.erro(`Variável '${node.nome}' não declarada`, node.line, node.column, dica);
|
|
712
729
|
return 'desconhecido';
|
|
713
730
|
}
|
|
714
731
|
return simbolo.tipo;
|
|
@@ -729,6 +746,13 @@ export class TypeChecker {
|
|
|
729
746
|
return 'decimal';
|
|
730
747
|
if (tipoEsquerda === 'numero' && tipoDireita === 'decimal')
|
|
731
748
|
return 'decimal';
|
|
749
|
+
// moeda opera com moeda ou numero (escala monetária); sempre retorna moeda
|
|
750
|
+
if (tipoEsquerda === 'moeda' && tipoDireita === 'moeda')
|
|
751
|
+
return 'moeda';
|
|
752
|
+
if (tipoEsquerda === 'moeda' && tipoDireita === 'numero')
|
|
753
|
+
return 'moeda';
|
|
754
|
+
if (tipoEsquerda === 'numero' && tipoDireita === 'moeda')
|
|
755
|
+
return 'moeda';
|
|
732
756
|
if (node.operador === '+' && tipoEsquerda === 'texto' && tipoDireita === 'texto')
|
|
733
757
|
return 'texto';
|
|
734
758
|
break;
|
|
@@ -741,8 +765,8 @@ export class TypeChecker {
|
|
|
741
765
|
case '<=':
|
|
742
766
|
case '>':
|
|
743
767
|
case '>=':
|
|
744
|
-
if ((tipoEsquerda === 'numero' || tipoEsquerda === 'decimal') &&
|
|
745
|
-
(tipoDireita === 'numero' || tipoDireita === 'decimal'))
|
|
768
|
+
if ((tipoEsquerda === 'numero' || tipoEsquerda === 'decimal' || tipoEsquerda === 'moeda') &&
|
|
769
|
+
(tipoDireita === 'numero' || tipoDireita === 'decimal' || tipoDireita === 'moeda'))
|
|
746
770
|
return 'booleano';
|
|
747
771
|
if (tipoEsquerda === 'data' && tipoDireita === 'data')
|
|
748
772
|
return 'booleano';
|
|
@@ -755,14 +779,14 @@ export class TypeChecker {
|
|
|
755
779
|
return 'booleano';
|
|
756
780
|
break;
|
|
757
781
|
}
|
|
758
|
-
this.erro(`Operador '${node.operador}' não pode ser aplicado entre '${tipoEsquerda}' e '${tipoDireita}'`, node.line, node.column);
|
|
782
|
+
this.erro(`Operador '${node.operador}' não pode ser aplicado entre '${tipoEsquerda}' e '${tipoDireita}'`, node.line, node.column, `Operadores aritméticos (+, -, *, /) funcionam entre número/decimal. '+' também concatena dois textos. Compare apenas valores do mesmo tipo`);
|
|
759
783
|
return 'desconhecido';
|
|
760
784
|
}
|
|
761
785
|
resolverTipoUnario(node) {
|
|
762
786
|
const tipoOperando = this.resolverTipo(node.operando);
|
|
763
787
|
switch (node.operador) {
|
|
764
788
|
case '-':
|
|
765
|
-
if (tipoOperando === 'numero' || tipoOperando === 'decimal')
|
|
789
|
+
if (tipoOperando === 'numero' || tipoOperando === 'decimal' || tipoOperando === 'moeda')
|
|
766
790
|
return tipoOperando;
|
|
767
791
|
break;
|
|
768
792
|
case 'nao':
|
|
@@ -770,19 +794,24 @@ export class TypeChecker {
|
|
|
770
794
|
return 'booleano';
|
|
771
795
|
break;
|
|
772
796
|
}
|
|
773
|
-
this.erro(`Operador unário '${node.operador}' não pode ser aplicado ao tipo '${tipoOperando}'`, node.line, node.column);
|
|
797
|
+
this.erro(`Operador unário '${node.operador}' não pode ser aplicado ao tipo '${tipoOperando}'`, node.line, node.column, "O operador 'nao' só funciona com booleanos; o operador '-' (negação) só funciona com numero e decimal");
|
|
774
798
|
return 'desconhecido';
|
|
775
799
|
}
|
|
776
800
|
resolverTipoChamada(node) {
|
|
777
801
|
const funcao = this.tabela.buscar(node.nome);
|
|
778
802
|
if (!funcao || funcao.kind !== 'funcao') {
|
|
779
|
-
|
|
803
|
+
const funcoes = this.tabela.buscarTodosNomesVisiveis();
|
|
804
|
+
const sug = this.sugestao(node.nome, funcoes);
|
|
805
|
+
const dica = sug
|
|
806
|
+
? `Você quis dizer '${sug}'? Verifique o nome ou declare-a: 'funcao ${node.nome}()'`
|
|
807
|
+
: `A função não foi declarada. Verifique o nome ou declare-a: 'funcao ${node.nome}()'`;
|
|
808
|
+
this.erro(`Função '${node.nome}' não encontrada`, node.line, node.column, dica);
|
|
780
809
|
return 'desconhecido';
|
|
781
810
|
}
|
|
782
811
|
// Verificar número de argumentos
|
|
783
812
|
const params = this.tabela.buscarParametrosFuncao(node.nome);
|
|
784
813
|
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);
|
|
814
|
+
this.erro(`Função '${node.nome}' espera ${params.length} argumentos, recebeu ${node.argumentos.length}`, node.line, node.column, `Passe exatamente ${params.length} argumento(s) na chamada de '${node.nome}'`);
|
|
786
815
|
}
|
|
787
816
|
return funcao.tipo === 'vazio' ? 'vazio' : funcao.tipo;
|
|
788
817
|
}
|
|
@@ -810,7 +839,7 @@ export class TypeChecker {
|
|
|
810
839
|
}
|
|
811
840
|
const tipoCampo = this.tabela.buscarCampo(tipoObjeto, node.membro);
|
|
812
841
|
if (tipoCampo === null) {
|
|
813
|
-
this.erro(`'${tipoObjeto}' não possui campo '${node.membro}'`, node.line, node.column);
|
|
842
|
+
this.erro(`'${tipoObjeto}' não possui campo '${node.membro}'`, node.line, node.column, `Verifique o nome do campo — pode estar digitado errado ou não existe na entidade/classe '${tipoObjeto}'`);
|
|
814
843
|
return 'desconhecido';
|
|
815
844
|
}
|
|
816
845
|
return tipoCampo;
|
|
@@ -821,14 +850,28 @@ export class TypeChecker {
|
|
|
821
850
|
tiposCompatíveis(esperado, recebido) {
|
|
822
851
|
if (esperado === recebido)
|
|
823
852
|
return true;
|
|
853
|
+
// 'desconhecido' suprime cascata de erros — erro já foi reportado na origem
|
|
854
|
+
if (esperado === 'desconhecido' || recebido === 'desconhecido')
|
|
855
|
+
return true;
|
|
856
|
+
// Strip modificadores opcionais/obrigatórios para comparação de base
|
|
857
|
+
const baseEsperado = esperado.replace(/[?!]$/, '');
|
|
858
|
+
const baseRecebido = recebido.replace(/[?!]$/, '');
|
|
859
|
+
if (baseEsperado === baseRecebido)
|
|
860
|
+
return true;
|
|
861
|
+
// 'qualquer' é compatível com qualquer tipo (escape hatch)
|
|
862
|
+
if (baseEsperado === 'qualquer' || baseRecebido === 'qualquer')
|
|
863
|
+
return true;
|
|
824
864
|
// decimal aceita numero
|
|
825
|
-
if (
|
|
865
|
+
if (baseEsperado === 'decimal' && baseRecebido === 'numero')
|
|
866
|
+
return true;
|
|
867
|
+
// moeda aceita decimal (interop com valores monetários vindos de APIs)
|
|
868
|
+
if (baseEsperado === 'moeda' && baseRecebido === 'decimal')
|
|
826
869
|
return true;
|
|
827
870
|
// id aceita numero (IDs podem ser representados como números)
|
|
828
|
-
if (
|
|
871
|
+
if (baseEsperado === 'id' && baseRecebido === 'numero')
|
|
829
872
|
return true;
|
|
830
873
|
// Verificar herança de classes
|
|
831
|
-
if (this.verificarHeranca(
|
|
874
|
+
if (this.verificarHeranca(baseEsperado, baseRecebido))
|
|
832
875
|
return true;
|
|
833
876
|
return false;
|
|
834
877
|
}
|
|
@@ -896,21 +939,24 @@ export class TypeChecker {
|
|
|
896
939
|
}
|
|
897
940
|
// Verifica se um tipo existe (primitivo, classe, entidade, enum declarado)
|
|
898
941
|
tipoExiste(nome) {
|
|
899
|
-
|
|
900
|
-
|
|
942
|
+
// Strip modificadores opcionais (?) e obrigatórios (!) antes de validar
|
|
943
|
+
const nomeBase = nome.replace(/[?!]$/, '');
|
|
944
|
+
const tiposPrimitivos = ['texto', 'numero', 'decimal', 'moeda', 'booleano', 'data', 'hora', 'id', 'qualquer', 'vazio', 'objeto'];
|
|
945
|
+
if (tiposPrimitivos.includes(nomeBase))
|
|
901
946
|
return true;
|
|
902
947
|
// Verificar tipos genéricos
|
|
903
|
-
if (
|
|
904
|
-
const elementoTipo =
|
|
948
|
+
if (nomeBase.startsWith('lista<') && nomeBase.endsWith('>')) {
|
|
949
|
+
const elementoTipo = nomeBase.substring(6, nomeBase.length - 1);
|
|
905
950
|
return this.tipoExiste(elementoTipo);
|
|
906
951
|
}
|
|
907
|
-
if (
|
|
908
|
-
|
|
952
|
+
if (nomeBase.startsWith('mapa<') && nomeBase.endsWith('>')) {
|
|
953
|
+
// 'mapa<' tem 5 chars — substring(5) descarta corretamente o prefixo com '<'
|
|
954
|
+
const partes = nomeBase.substring(5, nomeBase.length - 1).split(',');
|
|
909
955
|
if (partes.length !== 2)
|
|
910
956
|
return false;
|
|
911
957
|
return this.tipoExiste(partes[0].trim()) && this.tipoExiste(partes[1].trim());
|
|
912
958
|
}
|
|
913
|
-
const simbolo = this.tabela.buscar(
|
|
959
|
+
const simbolo = this.tabela.buscar(nomeBase);
|
|
914
960
|
return simbolo !== null && ['classe', 'entidade', 'enum', 'interface'].includes(simbolo.kind);
|
|
915
961
|
}
|
|
916
962
|
// Converte TipoNode em string para comparação
|
|
@@ -949,15 +995,84 @@ export class TypeChecker {
|
|
|
949
995
|
const tiposElementosValidos = ['tabela', 'formulario', 'botao', 'card', 'modal', 'grafico'];
|
|
950
996
|
for (const elem of node.elementos) {
|
|
951
997
|
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);
|
|
998
|
+
this.erro(`Tipo de elemento '${elem.tipo}' inválido. Use: tabela, formulario, botao ou card`, elem.line, elem.column, "Tipos válidos de elementos de tela: tabela, formulario, botao, card, modal ou grafico");
|
|
999
|
+
continue;
|
|
1000
|
+
}
|
|
1001
|
+
// grafico requer propriedade 'entidade'
|
|
1002
|
+
if (elem.tipo === 'grafico') {
|
|
1003
|
+
const temEntidade = elem.propriedades.some(p => p.chave === 'entidade');
|
|
1004
|
+
if (!temEntidade) {
|
|
1005
|
+
this.erro(`Elemento grafico '${elem.nome}' deve declarar a propriedade 'entidade' com a fonte de dados`, elem.line, elem.column, "Informe a entidade de dados: entidade: NomeDaEntidade");
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
// Valida referência de entidade declarada na propriedade 'entidade:'
|
|
1009
|
+
const propEntidade = elem.propriedades.find(p => p.chave === 'entidade');
|
|
1010
|
+
if (propEntidade && typeof propEntidade.valor === 'string') {
|
|
1011
|
+
const nomeEntidade = propEntidade.valor;
|
|
1012
|
+
const simbolo = this.tabela.buscar(nomeEntidade);
|
|
1013
|
+
if (!simbolo || simbolo.kind !== 'entidade') {
|
|
1014
|
+
this.erro(`Entidade '${nomeEntidade}' não declarada ou não encontrada`, elem.line, elem.column, `Declare a entidade antes de usá-la: entidade ${nomeEntidade} ... fim`);
|
|
1015
|
+
}
|
|
1016
|
+
else {
|
|
1017
|
+
// Valida campos referenciados na propriedade 'campos:'
|
|
1018
|
+
const propCampos = elem.propriedades.find(p => p.chave === 'campos');
|
|
1019
|
+
if (propCampos) {
|
|
1020
|
+
const listaCampos = Array.isArray(propCampos.valor)
|
|
1021
|
+
? propCampos.valor
|
|
1022
|
+
: [propCampos.valor];
|
|
1023
|
+
for (const nomeCampo of listaCampos) {
|
|
1024
|
+
const tipoCampo = this.tabela.buscarCampo(nomeEntidade, nomeCampo);
|
|
1025
|
+
if (tipoCampo === null) {
|
|
1026
|
+
this.erro(`Campo '${nomeCampo}' não existe na entidade '${nomeEntidade}'`, elem.line, elem.column, `Verifique os campos disponíveis na entidade '${nomeEntidade}' e corrija o nome`);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
953
1031
|
}
|
|
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
1032
|
}
|
|
957
1033
|
}
|
|
958
1034
|
// Adiciona erro sem lançar exceção (continua verificando o resto)
|
|
959
|
-
erro(mensagem, linha, coluna) {
|
|
960
|
-
this.erros.push({ mensagem, linha, coluna });
|
|
1035
|
+
erro(mensagem, linha, coluna, dica) {
|
|
1036
|
+
this.erros.push({ mensagem, linha, coluna, dica });
|
|
1037
|
+
}
|
|
1038
|
+
// ── "Você quis dizer X?" ─────────────────────────────────────────────────
|
|
1039
|
+
levenshtein(a, b) {
|
|
1040
|
+
const m = a.length, n = b.length;
|
|
1041
|
+
const dp = Array.from({ length: m + 1 }, (_, i) => Array.from({ length: n + 1 }, (_, j) => i === 0 ? j : j === 0 ? i : 0));
|
|
1042
|
+
for (let i = 1; i <= m; i++) {
|
|
1043
|
+
for (let j = 1; j <= n; j++) {
|
|
1044
|
+
dp[i][j] = a[i - 1] === b[j - 1]
|
|
1045
|
+
? dp[i - 1][j - 1]
|
|
1046
|
+
: 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
return dp[m][n];
|
|
1050
|
+
}
|
|
1051
|
+
// Retorna o nome mais próximo de `nome` na lista `candidatos` se distância ≤ 2
|
|
1052
|
+
sugestao(nome, candidatos) {
|
|
1053
|
+
let melhor;
|
|
1054
|
+
let menorDist = 3;
|
|
1055
|
+
for (const c of candidatos) {
|
|
1056
|
+
const dist = this.levenshtein(nome.toLowerCase(), c.toLowerCase());
|
|
1057
|
+
if (dist < menorDist) {
|
|
1058
|
+
menorDist = dist;
|
|
1059
|
+
melhor = c;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
return melhor;
|
|
1063
|
+
}
|
|
1064
|
+
TIPOS_BUILTIN = ['texto', 'numero', 'decimal', 'moeda', 'booleano', 'data', 'hora', 'id'];
|
|
1065
|
+
sugestaoTipo(nome) {
|
|
1066
|
+
const tiposConhecidos = [
|
|
1067
|
+
...this.TIPOS_BUILTIN,
|
|
1068
|
+
...this.tabela.buscarTodosNomesVisiveis()
|
|
1069
|
+
];
|
|
1070
|
+
return this.sugestao(nome, tiposConhecidos);
|
|
1071
|
+
}
|
|
1072
|
+
dicaTipoNaoExiste(tipo) {
|
|
1073
|
+
const sug = this.sugestaoTipo(tipo);
|
|
1074
|
+
const base = 'Use um tipo válido: texto, numero, decimal, moeda, booleano, data, hora, id, ou o nome de uma entidade declarada';
|
|
1075
|
+
return sug ? `Você quis dizer '${sug}'? ${base}` : base;
|
|
961
1076
|
}
|
|
962
1077
|
}
|
|
963
1078
|
//# sourceMappingURL=type_checker.js.map
|