@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.
Files changed (45) hide show
  1. package/dist/cli.js +201 -23
  2. package/dist/cli.js.map +1 -1
  3. package/dist/codegen/ir_generator.d.ts +13 -0
  4. package/dist/codegen/ir_generator.d.ts.map +1 -1
  5. package/dist/codegen/ir_generator.js +167 -6
  6. package/dist/codegen/ir_generator.js.map +1 -1
  7. package/dist/codegen/ir_nodes.d.ts +19 -0
  8. package/dist/codegen/ir_nodes.d.ts.map +1 -1
  9. package/dist/codegen/wasm_generator.d.ts.map +1 -1
  10. package/dist/codegen/wasm_generator.js +0 -1
  11. package/dist/codegen/wasm_generator.js.map +1 -1
  12. package/dist/formatter/formatter.d.ts +48 -0
  13. package/dist/formatter/formatter.d.ts.map +1 -0
  14. package/dist/formatter/formatter.js +280 -0
  15. package/dist/formatter/formatter.js.map +1 -0
  16. package/dist/index.d.ts +14 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +16 -2
  19. package/dist/index.js.map +1 -1
  20. package/dist/lexer/lexer.d.ts.map +1 -1
  21. package/dist/lexer/lexer.js +15 -7
  22. package/dist/lexer/lexer.js.map +1 -1
  23. package/dist/lexer/token_type.d.ts +3 -1
  24. package/dist/lexer/token_type.d.ts.map +1 -1
  25. package/dist/lexer/token_type.js +2 -0
  26. package/dist/lexer/token_type.js.map +1 -1
  27. package/dist/linter/linter.d.ts +46 -0
  28. package/dist/linter/linter.d.ts.map +1 -0
  29. package/dist/linter/linter.js +166 -0
  30. package/dist/linter/linter.js.map +1 -0
  31. package/dist/parser/parse_result.d.ts +2 -0
  32. package/dist/parser/parse_result.d.ts.map +1 -1
  33. package/dist/parser/parser.d.ts.map +1 -1
  34. package/dist/parser/parser.js +117 -21
  35. package/dist/parser/parser.js.map +1 -1
  36. package/dist/semantic/symbol_table.d.ts +1 -0
  37. package/dist/semantic/symbol_table.d.ts.map +1 -1
  38. package/dist/semantic/symbol_table.js +11 -0
  39. package/dist/semantic/symbol_table.js.map +1 -1
  40. package/dist/semantic/type_checker.d.ts +7 -0
  41. package/dist/semantic/type_checker.d.ts.map +1 -1
  42. package/dist/semantic/type_checker.js +187 -72
  43. package/dist/semantic/type_checker.js.map +1 -1
  44. package/dist/tsconfig.tsbuildinfo +1 -0
  45. 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': 'T', // tipo do elemento
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': 'T', // lista.primeiro() -> T
31
- 'ultimo': 'T', // lista.ultimo() -> T
32
- 'vazia': 'booleano' // lista.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
- try {
121
- if (node.alias) {
122
- // importar financeiro como fin → registra 'fin' como namespace do módulo
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
- else if (node.item && !node.wildcard) {
133
- // importar estoque.Produto registra 'Produto' como tipo externo conhecido
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
- catch {
147
- // Silenciar conflito de nome se o tipo já foi declarado localmente.
148
- // A precedência é do símbolo local.
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
- // Verificar o bloco então
437
+ this.tabela.entrarEscopo('regra_entao');
437
438
  this.verificarBloco(node.entao);
438
- // Verificar o bloco senão (se existir)
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
- // Declarar variável de iteração
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.erro(`Variável '${node.nome}' não declarada`, node.line, node.column);
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
- this.erro(`Função '${node.nome}' não encontrada`, node.line, node.column);
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 (esperado === 'decimal' && recebido === 'numero')
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 (esperado === 'id' && recebido === 'numero')
871
+ if (baseEsperado === 'id' && baseRecebido === 'numero')
829
872
  return true;
830
873
  // Verificar herança de classes
831
- if (this.verificarHeranca(esperado, recebido))
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
- const tiposPrimitivos = ['texto', 'numero', 'decimal', 'booleano', 'data', 'hora', 'id'];
900
- if (tiposPrimitivos.includes(nome))
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 (nome.startsWith('lista<') && nome.endsWith('>')) {
904
- const elementoTipo = nome.substring(6, nome.length - 1);
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 (nome.startsWith('mapa<') && nome.endsWith('>')) {
908
- const partes = nome.substring(4, nome.length - 1).split(',');
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(nome);
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