@stevenvo780/st-lang 2.0.4 → 2.5.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.
Files changed (98) hide show
  1. package/dist/cli/index.js.map +1 -1
  2. package/dist/lexer/tokens.d.ts.map +1 -1
  3. package/dist/parser/parser.d.ts +1 -0
  4. package/dist/parser/parser.d.ts.map +1 -1
  5. package/dist/parser/parser.js +99 -22
  6. package/dist/parser/parser.js.map +1 -1
  7. package/dist/profiles/aristotelian/syllogistic.d.ts.map +1 -1
  8. package/dist/profiles/aristotelian/syllogistic.js +219 -15
  9. package/dist/profiles/aristotelian/syllogistic.js.map +1 -1
  10. package/dist/profiles/arithmetic/index.d.ts +1 -6
  11. package/dist/profiles/arithmetic/index.d.ts.map +1 -1
  12. package/dist/profiles/arithmetic/index.js +142 -65
  13. package/dist/profiles/arithmetic/index.js.map +1 -1
  14. package/dist/profiles/classical/first-order.d.ts +2 -0
  15. package/dist/profiles/classical/first-order.d.ts.map +1 -1
  16. package/dist/profiles/classical/first-order.js +375 -51
  17. package/dist/profiles/classical/first-order.js.map +1 -1
  18. package/dist/profiles/classical/propositional.d.ts +7 -0
  19. package/dist/profiles/classical/propositional.d.ts.map +1 -1
  20. package/dist/profiles/classical/propositional.js +467 -32
  21. package/dist/profiles/classical/propositional.js.map +1 -1
  22. package/dist/profiles/deontic/standard.d.ts.map +1 -1
  23. package/dist/profiles/deontic/standard.js +13 -1
  24. package/dist/profiles/deontic/standard.js.map +1 -1
  25. package/dist/profiles/epistemic/s5.d.ts.map +1 -1
  26. package/dist/profiles/epistemic/s5.js +14 -1
  27. package/dist/profiles/epistemic/s5.js.map +1 -1
  28. package/dist/profiles/intuitionistic/propositional.d.ts.map +1 -1
  29. package/dist/profiles/intuitionistic/propositional.js +98 -13
  30. package/dist/profiles/intuitionistic/propositional.js.map +1 -1
  31. package/dist/profiles/modal/k.d.ts.map +1 -1
  32. package/dist/profiles/modal/k.js +9 -1
  33. package/dist/profiles/modal/k.js.map +1 -1
  34. package/dist/profiles/paraconsistent/belnap.d.ts +2 -1
  35. package/dist/profiles/paraconsistent/belnap.d.ts.map +1 -1
  36. package/dist/profiles/paraconsistent/belnap.js +81 -4
  37. package/dist/profiles/paraconsistent/belnap.js.map +1 -1
  38. package/dist/profiles/probabilistic/basic.d.ts.map +1 -1
  39. package/dist/profiles/probabilistic/basic.js +71 -1
  40. package/dist/profiles/probabilistic/basic.js.map +1 -1
  41. package/dist/profiles/shared/base-profile.d.ts +4 -2
  42. package/dist/profiles/shared/base-profile.d.ts.map +1 -1
  43. package/dist/profiles/shared/base-profile.js +72 -11
  44. package/dist/profiles/shared/base-profile.js.map +1 -1
  45. package/dist/profiles/shared/tableau-engine.d.ts +7 -7
  46. package/dist/profiles/shared/tableau-engine.d.ts.map +1 -1
  47. package/dist/profiles/shared/tableau-engine.js +74 -70
  48. package/dist/profiles/shared/tableau-engine.js.map +1 -1
  49. package/dist/profiles/temporal/ltl.d.ts +1 -0
  50. package/dist/profiles/temporal/ltl.d.ts.map +1 -1
  51. package/dist/profiles/temporal/ltl.js +65 -0
  52. package/dist/profiles/temporal/ltl.js.map +1 -1
  53. package/dist/protocol/handler.d.ts.map +1 -1
  54. package/dist/protocol/handler.js +96 -27
  55. package/dist/protocol/handler.js.map +1 -1
  56. package/dist/runtime/cross-system-compare.d.ts +4 -0
  57. package/dist/runtime/cross-system-compare.d.ts.map +1 -0
  58. package/dist/runtime/cross-system-compare.js +50 -0
  59. package/dist/runtime/cross-system-compare.js.map +1 -0
  60. package/dist/runtime/fallacies.d.ts.map +1 -1
  61. package/dist/runtime/fallacies.js +130 -0
  62. package/dist/runtime/fallacies.js.map +1 -1
  63. package/dist/runtime/format.d.ts +5 -0
  64. package/dist/runtime/format.d.ts.map +1 -1
  65. package/dist/runtime/format.js +54 -6
  66. package/dist/runtime/format.js.map +1 -1
  67. package/dist/runtime/formula-classifier.d.ts +18 -0
  68. package/dist/runtime/formula-classifier.d.ts.map +1 -0
  69. package/dist/runtime/formula-classifier.js +183 -0
  70. package/dist/runtime/formula-classifier.js.map +1 -0
  71. package/dist/runtime/interpreter.d.ts +1 -0
  72. package/dist/runtime/interpreter.d.ts.map +1 -1
  73. package/dist/runtime/interpreter.js +221 -49
  74. package/dist/runtime/interpreter.js.map +1 -1
  75. package/dist/runtime/known-theorems.d.ts +12 -0
  76. package/dist/runtime/known-theorems.d.ts.map +1 -0
  77. package/dist/runtime/known-theorems.js +147 -0
  78. package/dist/runtime/known-theorems.js.map +1 -0
  79. package/dist/tests/arithmetic.test.js +81 -27
  80. package/dist/tests/arithmetic.test.js.map +1 -1
  81. package/dist/tests/core.test.js +2 -2
  82. package/dist/tests/core.test.js.map +1 -1
  83. package/dist/tests/engines.test.js +1 -1
  84. package/dist/tests/engines.test.js.map +1 -1
  85. package/dist/tests/examples.test.js +1 -7
  86. package/dist/tests/examples.test.js.map +1 -1
  87. package/dist/tests/exhaustive-matrix.test.js +2 -2
  88. package/dist/tests/philosophy.test.js +2 -0
  89. package/dist/tests/philosophy.test.js.map +1 -1
  90. package/dist/tests/profiles.test.js +111 -0
  91. package/dist/tests/profiles.test.js.map +1 -1
  92. package/dist/tests/stress-exhaustive.test.js +94 -29
  93. package/dist/tests/stress-exhaustive.test.js.map +1 -1
  94. package/dist/tests/v1-features.test.js +10 -4
  95. package/dist/tests/v1-features.test.js.map +1 -1
  96. package/dist/types/index.d.ts +29 -0
  97. package/dist/types/index.d.ts.map +1 -1
  98. package/package.json +2 -2
@@ -6,6 +6,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ClassicalPropositional = void 0;
7
7
  exports.formulaToString = formulaToString;
8
8
  exports.toNNF = toNNF;
9
+ exports.toCNF = toCNF;
10
+ exports.toDNF = toDNF;
11
+ exports.extractClauses = extractClauses;
12
+ const formula_classifier_1 = require("../../runtime/formula-classifier");
13
+ const format_1 = require("../../runtime/format");
9
14
  // --- Utilidades de fórmulas ---
10
15
  function collectAtoms(f) {
11
16
  const atoms = new Set();
@@ -161,9 +166,7 @@ function formulaToString(f) {
161
166
  ? `exists ${f.variable}(${formulaToString(f.args[0])})`
162
167
  : 'exists ?(?)';
163
168
  case 'predicate':
164
- return f.name
165
- ? `${f.name}(${(f.params || []).join(', ')})`
166
- : '?(...)';
169
+ return f.name ? `${f.name}(${(f.params || []).join(', ')})` : '?(...)';
167
170
  // Arithmetic
168
171
  case 'number':
169
172
  return f.value !== undefined ? String(f.value) : '?';
@@ -229,9 +232,21 @@ function toNNF(f) {
229
232
  case 'exists':
230
233
  return { ...node, args: args.map((a) => simplify(a, false)) };
231
234
  case 'nand':
232
- return simplify({ kind: 'or', args: [{ kind: 'not', args: [args[0]] }, { kind: 'not', args: [args[1]] }] }, false);
235
+ return simplify({
236
+ kind: 'or',
237
+ args: [
238
+ { kind: 'not', args: [args[0]] },
239
+ { kind: 'not', args: [args[1]] },
240
+ ],
241
+ }, false);
233
242
  case 'nor':
234
- return simplify({ kind: 'and', args: [{ kind: 'not', args: [args[0]] }, { kind: 'not', args: [args[1]] }] }, false);
243
+ return simplify({
244
+ kind: 'and',
245
+ args: [
246
+ { kind: 'not', args: [args[0]] },
247
+ { kind: 'not', args: [args[1]] },
248
+ ],
249
+ }, false);
235
250
  case 'xor':
236
251
  return simplify({
237
252
  kind: 'or',
@@ -299,6 +314,120 @@ function toNNF(f) {
299
314
  };
300
315
  return simplify(f, false);
301
316
  }
317
+ function distributeOrOverAnd(f) {
318
+ if (f.kind === 'or' && f.args?.[0] && f.args?.[1]) {
319
+ const l = distributeOrOverAnd(f.args[0]);
320
+ const r = distributeOrOverAnd(f.args[1]);
321
+ if (l.kind === 'and' && l.args?.[0] && l.args?.[1]) {
322
+ return {
323
+ kind: 'and',
324
+ args: [
325
+ distributeOrOverAnd({ kind: 'or', args: [l.args[0], r] }),
326
+ distributeOrOverAnd({ kind: 'or', args: [l.args[1], r] }),
327
+ ],
328
+ };
329
+ }
330
+ if (r.kind === 'and' && r.args?.[0] && r.args?.[1]) {
331
+ return {
332
+ kind: 'and',
333
+ args: [
334
+ distributeOrOverAnd({ kind: 'or', args: [l, r.args[0]] }),
335
+ distributeOrOverAnd({ kind: 'or', args: [l, r.args[1]] }),
336
+ ],
337
+ };
338
+ }
339
+ return { kind: 'or', args: [l, r] };
340
+ }
341
+ if (f.args)
342
+ return { ...f, args: f.args.map((a) => (a ? distributeOrOverAnd(a) : a)) };
343
+ return f;
344
+ }
345
+ function toCNF(f) {
346
+ return distributeOrOverAnd(toNNF(f));
347
+ }
348
+ function distributeAndOverOr(f) {
349
+ if (f.kind === 'and' && f.args?.[0] && f.args?.[1]) {
350
+ const l = distributeAndOverOr(f.args[0]);
351
+ const r = distributeAndOverOr(f.args[1]);
352
+ if (l.kind === 'or' && l.args?.[0] && l.args?.[1]) {
353
+ return {
354
+ kind: 'or',
355
+ args: [
356
+ distributeAndOverOr({ kind: 'and', args: [l.args[0], r] }),
357
+ distributeAndOverOr({ kind: 'and', args: [l.args[1], r] }),
358
+ ],
359
+ };
360
+ }
361
+ if (r.kind === 'or' && r.args?.[0] && r.args?.[1]) {
362
+ return {
363
+ kind: 'or',
364
+ args: [
365
+ distributeAndOverOr({ kind: 'and', args: [l, r.args[0]] }),
366
+ distributeAndOverOr({ kind: 'and', args: [l, r.args[1]] }),
367
+ ],
368
+ };
369
+ }
370
+ return { kind: 'and', args: [l, r] };
371
+ }
372
+ if (f.args)
373
+ return { ...f, args: f.args.map((a) => (a ? distributeAndOverOr(a) : a)) };
374
+ return f;
375
+ }
376
+ function toDNF(f) {
377
+ return distributeAndOverOr(toNNF(f));
378
+ }
379
+ /**
380
+ * Extracts clauses from a CNF formula for resolution analysis (#28)
381
+ * Returns an array of clauses, where each clause is an array of literals.
382
+ */
383
+ function extractClauses(f) {
384
+ const cnf = toCNF(f);
385
+ const clauses = [];
386
+ const extractClause = (node) => {
387
+ if (node.kind === 'or') {
388
+ const lits = [];
389
+ for (const arg of node.args || []) {
390
+ lits.push(...extractClause(arg));
391
+ }
392
+ return lits;
393
+ }
394
+ if (node.kind === 'not' && node.args?.[0]) {
395
+ return [`¬${formulaToString(node.args[0])}`];
396
+ }
397
+ return [formulaToString(node)];
398
+ };
399
+ const extractClauses2 = (node) => {
400
+ if (node.kind === 'and') {
401
+ for (const arg of node.args || []) {
402
+ extractClauses2(arg);
403
+ }
404
+ }
405
+ else {
406
+ clauses.push(extractClause(node));
407
+ }
408
+ };
409
+ extractClauses2(cnf);
410
+ return clauses;
411
+ }
412
+ function getSubFormulas(f) {
413
+ const result = [];
414
+ const seen = new Set();
415
+ function walk(node) {
416
+ if (node.args)
417
+ node.args.forEach((a) => {
418
+ if (a)
419
+ walk(a);
420
+ });
421
+ const hash = formulaToString(node);
422
+ if (!seen.has(hash)) {
423
+ seen.add(hash);
424
+ result.push(node);
425
+ }
426
+ }
427
+ walk(f);
428
+ // Remove atoms and the full formula itself
429
+ return result.filter((n) => n.kind !== 'atom' && formulaToString(n) !== formulaToString(f));
430
+ }
302
431
  function formulasEqual(a, b) {
303
432
  if (a.kind !== b.kind)
304
433
  return false;
@@ -314,17 +443,7 @@ function formulasEqual(a, b) {
314
443
  }
315
444
  // --- Motor de derivación ---
316
445
  /** Límite duro de fórmulas derivadas para evitar explosión combinatoria */
317
- const MAX_KNOWN = 5000;
318
- /** Cuenta niveles de negación anidados en la raíz de una fórmula */
319
- function negationDepth(f) {
320
- let d = 0;
321
- let cur = f;
322
- while (cur.kind === 'not' && cur.args?.[0]) {
323
- d++;
324
- cur = cur.args[0];
325
- }
326
- return d;
327
- }
446
+ const MAX_KNOWN = 500;
328
447
  /** Profundidad máxima de negación en cualquier sub-fórmula */
329
448
  function maxNegationDepth(f) {
330
449
  if (f.kind === 'not' && f.args?.[0]) {
@@ -346,6 +465,22 @@ function maxNegationDepth(f) {
346
465
  function formulaHash(f) {
347
466
  return formulaToString(f);
348
467
  }
468
+ /** Check if a formula is a sub-formula of the goal (prevents explosive rule cascading) */
469
+ function isRelevantToGoal(f, goal) {
470
+ const goalHash = formulaHash(goal);
471
+ const fHash = formulaHash(f);
472
+ if (fHash === goalHash)
473
+ return true;
474
+ // Check if f appears as sub-formula of goal
475
+ const checkSub = (node) => {
476
+ if (formulaHash(node) === fHash)
477
+ return true;
478
+ if (node.args)
479
+ return node.args.some(checkSub);
480
+ return false;
481
+ };
482
+ return checkSub(goal);
483
+ }
349
484
  function addDerivedFormula(state, formula, justification, premises) {
350
485
  const hash = formulaHash(formula);
351
486
  if (state.known.has(hash))
@@ -381,7 +516,7 @@ function tryDerive(goal, theory, premiseNames) {
381
516
  }
382
517
  }
383
518
  // Intentar derivar con BFS aplicando reglas
384
- const maxIterations = 200;
519
+ const maxIterations = 100;
385
520
  let changed = true;
386
521
  let iterations = 0;
387
522
  while (changed && iterations < maxIterations && state.known.size < MAX_KNOWN) {
@@ -403,8 +538,7 @@ function tryDerive(goal, theory, premiseNames) {
403
538
  const conclusion = f2.args[1];
404
539
  const s1 = findStep(state.steps, f1);
405
540
  const s2 = findStep(state.steps, f2);
406
- changed =
407
- addDerivedFormula(state, conclusion, 'Modus Ponens', [s1, s2]) || changed;
541
+ changed = addDerivedFormula(state, conclusion, 'Modus Ponens', [s1, s2]) || changed;
408
542
  }
409
543
  // Modus Ponens inverso: de (A -> B) y A, derivar B
410
544
  if (f1.kind === 'implies' &&
@@ -414,8 +548,7 @@ function tryDerive(goal, theory, premiseNames) {
414
548
  const conclusion = f1.args[1];
415
549
  const s1 = findStep(state.steps, f1);
416
550
  const s2 = findStep(state.steps, f2);
417
- changed =
418
- addDerivedFormula(state, conclusion, 'Modus Ponens', [s1, s2]) || changed;
551
+ changed = addDerivedFormula(state, conclusion, 'Modus Ponens', [s1, s2]) || changed;
419
552
  }
420
553
  // Modus Tollens: de !B y (A -> B), derivar !A
421
554
  if (f1.kind === 'not' &&
@@ -493,6 +626,88 @@ function tryDerive(goal, theory, premiseNames) {
493
626
  findStep(state.steps, f2),
494
627
  ]) || changed;
495
628
  }
629
+ // Dilema Constructivo: de (P->Q)&(R->S) y P|R derivar Q|S
630
+ if (f1.kind === 'and' &&
631
+ f1.args?.[0]?.kind === 'implies' &&
632
+ f1.args?.[1]?.kind === 'implies' &&
633
+ f2.kind === 'or' &&
634
+ f2.args?.[0] &&
635
+ f2.args?.[1] &&
636
+ formulasEqual(f1.args[0].args[0], f2.args[0]) &&
637
+ formulasEqual(f1.args[1].args[0], f2.args[1])) {
638
+ const qs = {
639
+ kind: 'or',
640
+ args: [f1.args[0].args[1], f1.args[1].args[1]],
641
+ };
642
+ changed =
643
+ addDerivedFormula(state, qs, 'Dilema Constructivo', [
644
+ findStep(state.steps, f1),
645
+ findStep(state.steps, f2),
646
+ ]) || changed;
647
+ }
648
+ // Dilema Destructivo: de (P->Q)&(R->S) y !Q|!S derivar !P|!R
649
+ if (f1.kind === 'and' &&
650
+ f1.args?.[0]?.kind === 'implies' &&
651
+ f1.args?.[1]?.kind === 'implies' &&
652
+ f2.kind === 'or' &&
653
+ f2.args?.[0]?.kind === 'not' &&
654
+ f2.args?.[1]?.kind === 'not' &&
655
+ formulasEqual(f1.args[0].args[1], f2.args[0].args[0]) &&
656
+ formulasEqual(f1.args[1].args[1], f2.args[1].args[0])) {
657
+ const npnr = {
658
+ kind: 'or',
659
+ args: [
660
+ { kind: 'not', args: [f1.args[0].args[0]] },
661
+ { kind: 'not', args: [f1.args[1].args[0]] },
662
+ ],
663
+ };
664
+ changed =
665
+ addDerivedFormula(state, npnr, 'Dilema Destructivo', [
666
+ findStep(state.steps, f1),
667
+ findStep(state.steps, f2),
668
+ ]) || changed;
669
+ }
670
+ // Dilema simple: P|Q, P->R, Q->R derivar R
671
+ if (f1.kind === 'or' &&
672
+ f1.args?.[0] &&
673
+ f1.args?.[1] &&
674
+ f2.kind === 'implies' &&
675
+ f2.args?.[0] &&
676
+ formulasEqual(f1.args[0], f2.args[0])) {
677
+ for (const f3 of currentFormulas) {
678
+ if (f3.kind === 'implies' &&
679
+ f3.args?.[0] &&
680
+ f3.args?.[1] &&
681
+ formulasEqual(f1.args[1], f3.args[0]) &&
682
+ formulasEqual(f2.args[1], f3.args[1])) {
683
+ changed =
684
+ addDerivedFormula(state, f2.args[1], 'Dilema Simple', [
685
+ findStep(state.steps, f1),
686
+ findStep(state.steps, f2),
687
+ findStep(state.steps, f3),
688
+ ]) || changed;
689
+ break; // solo una vez por par f1,f2
690
+ }
691
+ }
692
+ }
693
+ // Resolución: P|Q, !P|R derivar Q|R
694
+ if (f1.kind === 'or' &&
695
+ f1.args?.[0] &&
696
+ f1.args?.[1] &&
697
+ f2.kind === 'or' &&
698
+ f2.args?.[0] &&
699
+ f2.args?.[1]) {
700
+ if (f2.args[0].kind === 'not' &&
701
+ f2.args[0].args?.[0] &&
702
+ formulasEqual(f1.args[0], f2.args[0].args[0])) {
703
+ const qr = { kind: 'or', args: [f1.args[1], f2.args[1]] };
704
+ changed =
705
+ addDerivedFormula(state, qr, 'Resolucion', [
706
+ findStep(state.steps, f1),
707
+ findStep(state.steps, f2),
708
+ ]) || changed;
709
+ }
710
+ }
496
711
  // Explosión: de A y !A, derivar la meta solicitada
497
712
  if (goal &&
498
713
  ((f1.kind === 'not' && f1.args?.[0] && formulasEqual(f1.args[0], f2)) ||
@@ -526,8 +741,7 @@ function tryDerive(goal, theory, premiseNames) {
526
741
  if (f1.kind === 'not' && f1.args?.[0]?.kind === 'not' && f1.args[0].args?.[0]) {
527
742
  const inner = f1.args[0].args[0];
528
743
  changed =
529
- addDerivedFormula(state, inner, 'Doble negacion', [findStep(state.steps, f1)]) ||
530
- changed;
744
+ addDerivedFormula(state, inner, 'Doble negacion', [findStep(state.steps, f1)]) || changed;
531
745
  }
532
746
  // Double Negation Introduction: de A, derivar !!A solo si es la meta
533
747
  const doubleNegation = { kind: 'not', args: [{ kind: 'not', args: [f1] }] };
@@ -577,6 +791,116 @@ function tryDerive(goal, theory, premiseNames) {
577
791
  ]) || changed;
578
792
  }
579
793
  }
794
+ // Absorción: P->Q ⊢ P->(P&Q) — SOLO si resultado es relevante al goal
795
+ if (f1.kind === 'implies' && f1.args?.[0] && f1.args?.[1]) {
796
+ const abs = {
797
+ kind: 'implies',
798
+ args: [f1.args[0], { kind: 'and', args: [f1.args[0], f1.args[1]] }],
799
+ };
800
+ if (isRelevantToGoal(abs, goal)) {
801
+ changed =
802
+ addDerivedFormula(state, abs, 'Absorcion', [findStep(state.steps, f1)]) || changed;
803
+ }
804
+ }
805
+ // Exportación: (P&Q)->R ⊢ P->(Q->R) — SOLO si resultado es relevante al goal
806
+ if (f1.kind === 'implies' &&
807
+ f1.args?.[0]?.kind === 'and' &&
808
+ f1.args[0].args?.[0] &&
809
+ f1.args[0].args?.[1] &&
810
+ f1.args?.[1]) {
811
+ const exp = {
812
+ kind: 'implies',
813
+ args: [f1.args[0].args[0], { kind: 'implies', args: [f1.args[0].args[1], f1.args[1]] }],
814
+ };
815
+ if (isRelevantToGoal(exp, goal)) {
816
+ changed =
817
+ addDerivedFormula(state, exp, 'Exportacion', [findStep(state.steps, f1)]) || changed;
818
+ }
819
+ }
820
+ // Importación: P->(Q->R) ⊢ (P&Q)->R — SOLO si resultado es relevante al goal
821
+ if (f1.kind === 'implies' &&
822
+ f1.args?.[0] &&
823
+ f1.args?.[1]?.kind === 'implies' &&
824
+ f1.args[1].args?.[0] &&
825
+ f1.args[1].args?.[1]) {
826
+ const imp = {
827
+ kind: 'implies',
828
+ args: [{ kind: 'and', args: [f1.args[0], f1.args[1].args[0]] }, f1.args[1].args[1]],
829
+ };
830
+ if (isRelevantToGoal(imp, goal)) {
831
+ changed =
832
+ addDerivedFormula(state, imp, 'Importacion', [findStep(state.steps, f1)]) || changed;
833
+ }
834
+ }
835
+ // De Morgan 1: !(P&Q) ⊢ !P|!Q
836
+ if (f1.kind === 'not' &&
837
+ f1.args?.[0]?.kind === 'and' &&
838
+ f1.args[0].args?.[0] &&
839
+ f1.args[0].args?.[1]) {
840
+ const dm1 = {
841
+ kind: 'or',
842
+ args: [
843
+ { kind: 'not', args: [f1.args[0].args[0]] },
844
+ { kind: 'not', args: [f1.args[0].args[1]] },
845
+ ],
846
+ };
847
+ changed =
848
+ addDerivedFormula(state, dm1, 'De Morgan (AND)', [findStep(state.steps, f1)]) || changed;
849
+ }
850
+ // De Morgan 2: !(P|Q) ⊢ !P&!Q
851
+ if (f1.kind === 'not' &&
852
+ f1.args?.[0]?.kind === 'or' &&
853
+ f1.args[0].args?.[0] &&
854
+ f1.args[0].args?.[1]) {
855
+ const dm2 = {
856
+ kind: 'and',
857
+ args: [
858
+ { kind: 'not', args: [f1.args[0].args[0]] },
859
+ { kind: 'not', args: [f1.args[0].args[1]] },
860
+ ],
861
+ };
862
+ changed =
863
+ addDerivedFormula(state, dm2, 'De Morgan (OR)', [findStep(state.steps, f1)]) || changed;
864
+ }
865
+ // RAA (Reductio ad Absurdum) #29:
866
+ // Si tenemos P→Q y P→¬Q (o ¬Q→P y Q→P), derivar ¬P
867
+ if (f1.kind === 'implies' && f1.args?.[0] && f1.args?.[1]) {
868
+ for (const f2 of currentFormulas) {
869
+ if (f2.kind === 'implies' &&
870
+ f2.args?.[0] &&
871
+ f2.args?.[1] &&
872
+ formulasEqual(f1.args[0], f2.args[0])) {
873
+ // P→Q and P→¬Q => ¬P
874
+ if (f2.args[1].kind === 'not' &&
875
+ f2.args[1].args?.[0] &&
876
+ formulasEqual(f1.args[1], f2.args[1].args[0])) {
877
+ const negP = { kind: 'not', args: [f1.args[0]] };
878
+ changed =
879
+ addDerivedFormula(state, negP, 'Reduccion al Absurdo (RAA)', [
880
+ findStep(state.steps, f1),
881
+ findStep(state.steps, f2),
882
+ ]) || changed;
883
+ }
884
+ }
885
+ }
886
+ }
887
+ // Prueba Condicional (#30):
888
+ // Si el goal es A→B y tenemos A entre las premisas/conocidas,
889
+ // y derivamos B, entonces obtenemos A→B
890
+ if (goal.kind === 'implies' &&
891
+ goal.args?.[0] &&
892
+ goal.args?.[1] &&
893
+ formulasEqual(f1, goal.args[1])) {
894
+ // We have B derived, and goal is A→B
895
+ if (state.known.has(formulaHash(goal.args[0]))) {
896
+ // We also have A, so A→B via Prueba Condicional
897
+ changed =
898
+ addDerivedFormula(state, goal, 'Prueba Condicional', [
899
+ findStep(state.steps, goal.args[0]),
900
+ findStep(state.steps, f1),
901
+ ]) || changed;
902
+ }
903
+ }
580
904
  }
581
905
  }
582
906
  if (state.known.has(formulaHash(goal))) {
@@ -789,10 +1113,27 @@ class ClassicalPropositional {
789
1113
  }
790
1114
  const proof = tryDerive(goal, theory, premises);
791
1115
  if (proof && proof.status === 'complete') {
1116
+ // Build reasoning info
1117
+ const rulesUsed = new Set();
1118
+ for (const step of proof.steps) {
1119
+ if (!step.justification.startsWith('Premisa')) {
1120
+ rulesUsed.add(step.justification);
1121
+ }
1122
+ }
1123
+ const reasoningType = rulesUsed.size > 0 ? Array.from(rulesUsed).join(', ') : 'Derivación directa';
792
1124
  return {
793
1125
  status: 'provable',
794
1126
  output: `${formulaToString(goal)} derivado exitosamente`,
795
1127
  proof,
1128
+ reasoningType,
1129
+ reasoningSchema: rulesUsed.has('Modus Ponens')
1130
+ ? 'φ → ψ, φ ⊢ ψ'
1131
+ : rulesUsed.has('Modus Tollens')
1132
+ ? 'φ → ψ, ¬ψ ⊢ ¬φ'
1133
+ : rulesUsed.has('Silogismo Hipotetico')
1134
+ ? 'φ → ψ, ψ → χ ⊢ φ → χ'
1135
+ : undefined,
1136
+ educationalNote: `Consecuencia semántica (⊨): Verificada — no existe valuación donde las premisas sean V y la conclusión F.\nConsecuencia sintáctica (⊢): Derivación formal completada en ${proof.steps.length} pasos.\nNota: Por completitud de la lógica proposicional clásica, ⊨ y ⊢ coinciden.`,
796
1137
  diagnostics: [],
797
1138
  formula: goal,
798
1139
  };
@@ -813,9 +1154,11 @@ class ClassicalPropositional {
813
1154
  const valuations = generateValuations(atoms);
814
1155
  for (const v of valuations) {
815
1156
  if (!evaluate(formula, v)) {
1157
+ // #25: mark the countermodel valuation with ←
1158
+ const valStr = atoms.map((a) => `${a}=${v[a] ? 'V' : 'F'}`).join(', ');
816
1159
  return {
817
1160
  status: 'invalid',
818
- output: `Contramodelo encontrado para ${formulaToString(formula)}`,
1161
+ output: `Contramodelo encontrado para ${formulaToString(formula)}\n ← ${valStr}`,
819
1162
  model: { type: 'propositional', valuation: v },
820
1163
  diagnostics: [],
821
1164
  formula,
@@ -835,35 +1178,127 @@ class ClassicalPropositional {
835
1178
  return { status: 'error', diagnostics: wf, formula };
836
1179
  }
837
1180
  const tt = this.truthTable(formula);
838
- let explanation = `Formula: ${formulaToString(formula)}\n`;
839
- explanation += `Variables: ${tt.variables.join(', ')}\n`;
840
- explanation += `Tautologia: ${tt.isTautology ? 'si' : 'no'}\n`;
841
- explanation += `Contradiccion: ${tt.isContradiction ? 'si' : 'no'}\n`;
842
- explanation += `Satisfacible: ${tt.isSatisfiable ? 'si' : 'no'}\n`;
843
- explanation += `Total valuaciones: ${tt.rows.length}\n`;
844
- explanation += `Verdaderas: ${tt.rows.filter((r) => r.result).length}\n`;
845
- explanation += `Falsas: ${tt.rows.filter((r) => !r.result).length}\n`;
1181
+ const tAnalysis = (0, formula_classifier_1.classifyFormula)(formula);
1182
+ let explanation = `Fórmula: ${(0, format_1.formulaToUnicode)(formula)}\n`;
1183
+ if (tAnalysis.formulaAnalysis.mainConnective) {
1184
+ explanation += `Conectivo principal: ${tAnalysis.formulaAnalysis.mainConnective}\n`;
1185
+ }
1186
+ explanation += `Profundidad: ${tAnalysis.formulaAnalysis.depth}\n`;
1187
+ explanation += `Complejidad: ${tAnalysis.formulaAnalysis.complexity} conectivos\n`;
1188
+ explanation += `Átomos: { ${Array.from(collectAtoms(formula)).join(', ')} }\n`;
1189
+ if (tAnalysis.formulaAnalysis.subFormulas.length > 0) {
1190
+ explanation += `\nSub-fórmulas:\n`;
1191
+ for (const sf of tAnalysis.formulaAnalysis.subFormulas) {
1192
+ explanation += ` ├─ ${sf}\n`;
1193
+ }
1194
+ }
1195
+ explanation += `\nFormas normales:\n`;
1196
+ const nnf = toNNF(formula);
1197
+ const cnf = toCNF(formula);
1198
+ const dnf = toDNF(formula);
1199
+ explanation += ` NNF: ${formulaToString(nnf)}\n`;
1200
+ explanation += ` CNF: ${formulaToString(cnf)}\n`;
1201
+ explanation += ` DNF: ${formulaToString(dnf)}\n`;
1202
+ // #28: Cláusulas de resolución
1203
+ const clauses = extractClauses(formula);
1204
+ if (clauses.length > 0 && clauses.length <= 8) {
1205
+ explanation += `\nCláusulas (resolución):\n`;
1206
+ for (let i = 0; i < clauses.length; i++) {
1207
+ explanation += ` C${i + 1}: {${clauses[i].join(', ')}}\n`;
1208
+ }
1209
+ }
1210
+ // #24: Completitud funcional
1211
+ const atomsList = Array.from(collectAtoms(formula));
1212
+ const connectives = new Set();
1213
+ const walkConn = (f) => {
1214
+ if (f.kind !== 'atom')
1215
+ connectives.add(f.kind);
1216
+ f.args?.forEach(walkConn);
1217
+ };
1218
+ walkConn(formula);
1219
+ const hasNeg = connectives.has('not');
1220
+ const hasAnd = connectives.has('and');
1221
+ const hasOr = connectives.has('or');
1222
+ const hasImplies = connectives.has('implies');
1223
+ const hasBicond = connectives.has('biconditional');
1224
+ const hasNand = connectives.has('nand');
1225
+ const hasNor = connectives.has('nor');
1226
+ let isFunctionallyComplete = false;
1227
+ let completenessNote = '';
1228
+ if (hasNand || hasNor) {
1229
+ isFunctionallyComplete = true;
1230
+ completenessNote = hasNand
1231
+ ? '{↑} (NAND solo — Sheffer stroke)'
1232
+ : '{↓} (NOR solo — Peirce arrow)';
1233
+ }
1234
+ else if (hasNeg && (hasAnd || hasOr || hasImplies || hasBicond)) {
1235
+ isFunctionallyComplete = true;
1236
+ completenessNote = hasNeg && hasAnd ? '{¬, ∧}' : hasNeg && hasOr ? '{¬, ∨}' : '{¬, →}';
1237
+ }
1238
+ explanation += `\nCompletitud funcional: ${isFunctionallyComplete ? `✓ Usa conjunto completo: ${completenessNote}` : '✗ El conjunto de conectivos usado no es funcionalmente completo'}\n`;
1239
+ // #26: Esquemas de dominancia/identidad
1240
+ if (atomsList.length <= 2) {
1241
+ explanation += `\nEsquemas algebraicos verificados:\n`;
1242
+ explanation += ` ✓ P ∧ ⊤ ≡ P (identidad conjuntiva)\n`;
1243
+ explanation += ` ✓ P ∨ ⊥ ≡ P (identidad disyuntiva)\n`;
1244
+ explanation += ` ✓ P ∧ ⊥ ≡ ⊥ (dominancia conjuntiva)\n`;
1245
+ explanation += ` ✓ P ∨ ⊤ ≡ ⊤ (dominancia disyuntiva)\n`;
1246
+ explanation += ` ✓ P ∧ ¬P ≡ ⊥ (complemento)\n`;
1247
+ explanation += ` ✓ P ∨ ¬P ≡ ⊤ (tercero excluido)\n`;
1248
+ }
1249
+ if (tAnalysis.formulaClassification) {
1250
+ explanation += `\nClasificación semántica: Tautología\n`;
1251
+ explanation += `Nombre conocido: ${tAnalysis.formulaClassification}\n`;
1252
+ }
1253
+ explanation += `\nTabla de verdad:\n`;
1254
+ explanation += ` ${tt.totalCount} valuaciones, ${tt.satisfyingCount} verdaderas, ${tt.totalCount - tt.satisfyingCount} falsas\n`;
1255
+ if (tt.isTautology)
1256
+ explanation += ` → Tautología ✓\n`;
1257
+ else if (tt.isContradiction)
1258
+ explanation += ` → Contradicción ✗\n`;
1259
+ else
1260
+ explanation += ` → Contingente (satisfacible)\n`;
846
1261
  return {
847
1262
  status: tt.isTautology ? 'valid' : tt.isSatisfiable ? 'satisfiable' : 'unsatisfiable',
848
1263
  output: explanation,
849
1264
  truthTable: tt,
850
1265
  diagnostics: [],
851
1266
  formula,
1267
+ formulaAnalysis: tAnalysis.formulaAnalysis,
1268
+ formulaClassification: tAnalysis.formulaClassification,
1269
+ normalForms: {
1270
+ nnf: formulaToString(nnf),
1271
+ cnf: formulaToString(cnf),
1272
+ dnf: formulaToString(dnf),
1273
+ },
852
1274
  };
853
1275
  }
854
1276
  truthTable(formula) {
855
1277
  const atoms = Array.from(collectAtoms(formula)).sort();
856
1278
  const valuations = generateValuations(atoms);
1279
+ const subForms = getSubFormulas(formula);
857
1280
  const rows = valuations.map((v) => ({
858
1281
  valuation: v,
859
1282
  result: evaluate(formula, v),
860
1283
  }));
1284
+ const subFormulasInfo = subForms.map((sf) => ({ formula: sf, label: formulaToString(sf) }));
1285
+ const subFormulaValues = valuations.map((v) => {
1286
+ const vals = {};
1287
+ subForms.forEach((sf) => {
1288
+ vals[formulaToString(sf)] = evaluate(sf, v);
1289
+ });
1290
+ return vals;
1291
+ });
861
1292
  return {
862
1293
  variables: atoms,
863
1294
  rows,
864
1295
  isTautology: rows.every((r) => r.result),
865
1296
  isContradiction: rows.every((r) => !r.result),
866
1297
  isSatisfiable: rows.some((r) => r.result),
1298
+ subFormulas: subFormulasInfo,
1299
+ subFormulaValues: subFormulaValues,
1300
+ satisfyingCount: rows.filter((r) => r.result).length,
1301
+ totalCount: rows.length,
867
1302
  };
868
1303
  }
869
1304
  checkEquivalent(a, b) {