@stevenvo780/st-lang 2.6.0 → 2.6.1

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.
@@ -13,6 +13,7 @@ const fallacies_1 = require("./fallacies");
13
13
  require("../profiles");
14
14
  const compiler_1 = require("../text-layer/compiler");
15
15
  const formula_classifier_1 = require("./formula-classifier");
16
+ const MAX_CALL_DEPTH = 500;
16
17
  class Interpreter {
17
18
  theory;
18
19
  profile = null;
@@ -40,6 +41,8 @@ class Interpreter {
40
41
  exportedTheorems = new Map();
41
42
  exportedFunctions = new Map();
42
43
  exportedTheories = new Map();
44
+ /** Profundidad de llamadas a funciones (anti-recursión infinita) */
45
+ callDepth = 0;
43
46
  constructor() {
44
47
  this.theory = this.createEmptyTheory();
45
48
  this.textLayer = (0, compiler_1.createTextLayerState)();
@@ -300,6 +303,10 @@ class Interpreter {
300
303
  * Soporta notación con punto: Theory.member resuelve desde el scope de la teoría.
301
304
  */
302
305
  resolveFormula(f, visited = new Set()) {
306
+ const resolved = this.resolveFormulaRecursive(f, visited);
307
+ return this.tryConstantFold(resolved);
308
+ }
309
+ resolveFormulaRecursive(f, visited = new Set()) {
303
310
  if (!f)
304
311
  return f;
305
312
  // Si es un átomo, intentar resolver
@@ -308,11 +315,9 @@ class Interpreter {
308
315
  if (f.name.includes('.')) {
309
316
  const [prefix, memberName] = f.name.split('.', 2);
310
317
  // 1. Intentar resolver el prefijo como una variable local (instancia)
311
- // Ej: let f1 = Familia("Socrates") -> f1.amor
312
318
  if (this.letBindings.has(prefix)) {
313
319
  const resolvedPrefix = this.letBindings.get(prefix);
314
320
  if (resolvedPrefix.kind === 'atom' && resolvedPrefix.name) {
315
- // Si el prefijo se resuelve a un nombre de teoría/instancia
316
321
  const actualInstanceName = resolvedPrefix.name;
317
322
  const scope = this.theories.get(actualInstanceName);
318
323
  if (scope) {
@@ -320,11 +325,11 @@ class Interpreter {
320
325
  this.currentTheoryName !== actualInstanceName)
321
326
  return f;
322
327
  if (scope.letBindings.has(memberName))
323
- return this.resolveFormula(scope.letBindings.get(memberName), new Set(visited));
328
+ return this.resolveFormulaRecursive(scope.letBindings.get(memberName), new Set(visited));
324
329
  if (scope.axioms.has(memberName))
325
- return this.resolveFormula(scope.axioms.get(memberName), new Set(visited));
330
+ return this.resolveFormulaRecursive(scope.axioms.get(memberName), new Set(visited));
326
331
  if (scope.theorems.has(memberName))
327
- return this.resolveFormula(scope.theorems.get(memberName), new Set(visited));
332
+ return this.resolveFormulaRecursive(scope.theorems.get(memberName), new Set(visited));
328
333
  }
329
334
  }
330
335
  }
@@ -334,34 +339,36 @@ class Interpreter {
334
339
  if (scope.privateMembers.has(memberName) && this.currentTheoryName !== prefix)
335
340
  return f;
336
341
  if (scope.letBindings.has(memberName))
337
- return this.resolveFormula(scope.letBindings.get(memberName), new Set(visited));
342
+ return this.resolveFormulaRecursive(scope.letBindings.get(memberName), new Set(visited));
338
343
  if (scope.axioms.has(memberName))
339
- return this.resolveFormula(scope.axioms.get(memberName), new Set(visited));
344
+ return this.resolveFormulaRecursive(scope.axioms.get(memberName), new Set(visited));
340
345
  if (scope.theorems.has(memberName))
341
- return this.resolveFormula(scope.theorems.get(memberName), new Set(visited));
346
+ return this.resolveFormulaRecursive(scope.theorems.get(memberName), new Set(visited));
342
347
  }
343
348
  return f;
344
349
  }
345
350
  // Binding local normal
346
351
  if (this.letBindings.has(f.name)) {
347
- if (visited.has(f.name)) {
352
+ if (visited.has(f.name))
348
353
  return f;
349
- }
350
- visited.add(f.name);
351
- return this.resolveFormula(this.letBindings.get(f.name), new Set(visited));
354
+ const newVisited = new Set(visited);
355
+ newVisited.add(f.name);
356
+ return this.resolveFormulaRecursive(this.letBindings.get(f.name), newVisited);
352
357
  }
353
358
  // También resolver axiomas/teoremas del theory actual por nombre
354
359
  if (this.theory.axioms.has(f.name)) {
355
360
  if (visited.has(f.name))
356
361
  return f;
357
- visited.add(f.name);
358
- return this.resolveFormula(this.theory.axioms.get(f.name), new Set(visited));
362
+ const newVisited = new Set(visited);
363
+ newVisited.add(f.name);
364
+ return this.resolveFormulaRecursive(this.theory.axioms.get(f.name), newVisited);
359
365
  }
360
366
  if (this.theory.theorems.has(f.name)) {
361
367
  if (visited.has(f.name))
362
368
  return f;
363
- visited.add(f.name);
364
- return this.resolveFormula(this.theory.theorems.get(f.name), new Set(visited));
369
+ const newVisited = new Set(visited);
370
+ newVisited.add(f.name);
371
+ return this.resolveFormulaRecursive(this.theory.theorems.get(f.name), newVisited);
365
372
  }
366
373
  }
367
374
  // Llamada a función como expresión
@@ -371,12 +378,8 @@ class Interpreter {
371
378
  }
372
379
  // Recorrer hijos recursivamente
373
380
  if (f.args && f.args.length > 0) {
374
- const newArgs = f.args.map((a) => (a ? this.resolveFormula(a, new Set(visited)) : a));
375
- const oldArgs = f.args;
376
- const changed = newArgs.some((a, i) => a !== oldArgs[i]);
377
- if (changed) {
378
- return { ...f, args: newArgs };
379
- }
381
+ const newArgs = f.args.map((a) => a ? this.resolveFormulaRecursive(a, new Set(visited)) : a);
382
+ return { ...f, args: newArgs };
380
383
  }
381
384
  return f;
382
385
  }
@@ -482,18 +485,13 @@ class Interpreter {
482
485
  this.emit(`Formalizacion ${stmt.name}: ${stmt.passageName} -> ${(0, propositional_1.formulaToString)(formula)}`);
483
486
  }
484
487
  else if (stmt.letType === 'description') {
485
- // Solo descripción textual: P es un átomo con significado semántico
486
488
  this.letDescriptions.set(stmt.name, stmt.description);
487
489
  this.emit(`Let ${stmt.name} = "${stmt.description}"`);
488
490
  }
489
491
  else if (stmt.letType === 'formula' && stmt.formula) {
490
- // Resolver posibles variables anidadas en la propia definición
491
492
  const resolved = this.resolveFormula(stmt.formula);
492
- // Registrar como binding para sustitución futura
493
493
  this.letBindings.set(stmt.name, resolved);
494
- // También como axioma implícito para derivaciones
495
494
  this.theory.axioms.set(stmt.name, resolved);
496
- // Si tiene descripción textual (let X = "desc" : formula), guardarla
497
495
  if ('description' in stmt && stmt.description) {
498
496
  this.letDescriptions.set(stmt.name, stmt.description);
499
497
  this.emit(`Let ${stmt.name} = "${stmt.description}" : ${(0, format_1.formulaToUnicode)(resolved)}`);
@@ -508,7 +506,6 @@ class Interpreter {
508
506
  const formalization = stmt.formalization;
509
507
  const diags = (0, compiler_1.registerClaim)(this.textLayer, stmt.name, formula, formalization);
510
508
  this.diagnostics.push(...diags);
511
- // También agregar al theory.claims
512
509
  const claim = {
513
510
  name: stmt.name,
514
511
  formula: formula,
@@ -533,7 +530,6 @@ class Interpreter {
533
530
  this.emit(`Context: ${stmt.claimName} = "${stmt.text}"`);
534
531
  }
535
532
  execRenderCmd(stmt) {
536
- // Compilar claims y renderizar
537
533
  const diags = (0, compiler_1.compileClaimsToTheory)(this.textLayer, this.theory);
538
534
  this.diagnostics.push(...diags);
539
535
  if (stmt.target === 'claims' || stmt.target === 'all') {
@@ -541,19 +537,15 @@ class Interpreter {
541
537
  for (const [name, claim] of this.theory.claims) {
542
538
  const fStr = claim.formula ? (0, format_1.formulaToUnicode)(claim.formula) : '(sin fórmula)';
543
539
  this.emit(` Claim "${name}": ${fStr}`);
544
- if (claim.support) {
540
+ if (claim.support)
545
541
  this.emit(` Soporte: ${claim.support}`);
546
- }
547
- if (claim.confidence !== undefined) {
542
+ if (claim.confidence !== undefined)
548
543
  this.emit(` Confianza: ${claim.confidence}`);
549
- }
550
- if (claim.context) {
544
+ if (claim.context)
551
545
  this.emit(` Contexto: ${claim.context}`);
552
- }
553
546
  }
554
- if (this.theory.claims.size === 0) {
547
+ if (this.theory.claims.size === 0)
555
548
  this.emit(' (sin claims registrados)');
556
- }
557
549
  }
558
550
  else if (stmt.target === 'theory') {
559
551
  this.emit(`── Render: theory (${stmt.format}) ──`);
@@ -569,7 +561,6 @@ class Interpreter {
569
561
  this.emit(` Claims: ${this.theory.claims.size}`);
570
562
  }
571
563
  else {
572
- // Render un claim o axioma específico por nombre
573
564
  const axiom = this.theory.axioms.get(stmt.target);
574
565
  if (axiom) {
575
566
  this.emit(` ${stmt.target} = ${(0, format_1.formulaToUnicode)(axiom)}`);
@@ -592,7 +583,6 @@ class Interpreter {
592
583
  const pStr = premises.map((p) => (0, format_1.formulaToUnicode)(p)).join(', ');
593
584
  const cStr = (0, format_1.formulaToUnicode)(conclusion);
594
585
  if (fallacies.length === 0) {
595
- // Check if the inference is valid
596
586
  const conj = premises.length === 0
597
587
  ? conclusion
598
588
  : premises.length === 1
@@ -608,13 +598,12 @@ class Interpreter {
608
598
  this.emit(`⚠ [analyze] {${pStr}} → ${cStr}`);
609
599
  this.emit(' Inferencia NO VÁLIDA — pero no corresponde a un patrón de falacia conocido');
610
600
  }
611
- const result2 = {
601
+ this.results.push({
612
602
  status: result.status,
613
603
  output: result.output,
614
604
  diagnostics: [],
615
605
  formula: conclusion,
616
- };
617
- this.results.push(result2);
606
+ });
618
607
  }
619
608
  else {
620
609
  this.emit(`⚠ [analyze] {${pStr}} → ${cStr}`);
@@ -624,7 +613,7 @@ class Interpreter {
624
613
  if (f.pattern)
625
614
  this.emit(` Patrón: ${f.pattern}`);
626
615
  }
627
- const result = {
616
+ this.results.push({
628
617
  status: 'invalid',
629
618
  output: `Falacias detectadas: ${fallacies.map((f) => f.name).join(', ')}`,
630
619
  diagnostics: fallacies.map((f) => ({
@@ -632,17 +621,14 @@ class Interpreter {
632
621
  message: `Falacia: ${f.name} — ${f.description}`,
633
622
  })),
634
623
  formula: conclusion,
635
- };
636
- this.results.push(result);
624
+ });
637
625
  }
638
626
  }
639
627
  execProofBlock(stmt) {
640
628
  const profile = this.requireProfile();
641
- // Guardar axiomas, letBindings y descriptions antes del bloque
642
629
  const savedAxioms = new Map(this.theory.axioms);
643
630
  const savedLetBindings = new Map(this.letBindings);
644
631
  const savedLetDescriptions = new Map(this.letDescriptions);
645
- // Registrar las asunciones como axiomas temporales (con resolución de variables)
646
632
  this.emit('── Proof Block ──');
647
633
  for (const assumption of stmt.assumptions) {
648
634
  const resolved = this.resolveFormula(assumption.formula);
@@ -651,7 +637,6 @@ class Interpreter {
651
637
  }
652
638
  const resolvedGoal = this.resolveFormula(stmt.goal);
653
639
  this.emit(` show ${(0, format_1.formulaToUnicode)(resolvedGoal)}`);
654
- // Ejecutar body statements
655
640
  for (const bodyStmt of stmt.body) {
656
641
  try {
657
642
  this.executeStatement(bodyStmt);
@@ -667,48 +652,36 @@ class Interpreter {
667
652
  });
668
653
  }
669
654
  }
670
- // Verificar que el goal es derivable de las asunciones
671
655
  const premiseNames = stmt.assumptions.map((a) => a.name);
672
656
  const result = profile.derive(resolvedGoal, premiseNames, this.theory);
673
657
  this.results.push(result);
674
658
  if (result.status === 'valid' || result.status === 'provable') {
675
659
  this.emit(` ✓ QED — ${(0, format_1.formulaToUnicode)(resolvedGoal)} demostrado`);
676
- // Registrar como teorema
677
660
  const theoremName = `proof_${this.theory.theorems.size + 1}`;
678
- // La implicación assumptions -> goal es un teorema
679
661
  let implication = resolvedGoal;
680
662
  for (let i = stmt.assumptions.length - 1; i >= 0; i--) {
681
- implication = {
682
- kind: 'implies',
683
- args: [stmt.assumptions[i].formula, implication],
684
- };
663
+ implication = { kind: 'implies', args: [stmt.assumptions[i].formula, implication] };
685
664
  }
686
665
  this.theory.theorems.set(theoremName, implication);
687
666
  }
688
667
  else {
689
668
  this.emit(` ✗ QED fallido — no se pudo demostrar ${(0, format_1.formulaToUnicode)(resolvedGoal)}`);
690
669
  }
691
- // Restaurar axiomas, letBindings y descriptions (quitar las asunciones temporales)
692
670
  this.theory.axioms = savedAxioms;
693
671
  this.letBindings = savedLetBindings;
694
672
  this.letDescriptions = savedLetDescriptions;
695
673
  this.emit('── End Proof Block ──');
696
674
  }
697
675
  execTheoryDecl(stmt) {
698
- const theoryName = stmt.name;
699
- // Si tiene parámetros, es una plantilla (Clase)
700
676
  if (stmt.params && stmt.params.length > 0) {
701
- this.theoryTemplates.set(theoryName, { node: stmt, parent: stmt.parent });
702
- this.emit(`Teoría (plantilla) ${theoryName}(${stmt.params.join(', ')}) declarada`);
677
+ this.theoryTemplates.set(stmt.name, { node: stmt, parent: stmt.parent });
678
+ this.emit(`Teoría (plantilla) ${stmt.name}(${stmt.params.join(', ')}) declarada`);
703
679
  return;
704
680
  }
705
- // Si no tiene parámetros, ejecutar como singleton/objeto inmediato
706
681
  this.instantiateTheory(stmt);
707
682
  }
708
- /** Crea una instancia de una teoría y la registra en this.theories */
709
683
  instantiateTheory(node, instanceName, args = []) {
710
684
  const theoryName = instanceName || node.name;
711
- // Crear scope vacío
712
685
  const scope = {
713
686
  name: theoryName,
714
687
  parent: node.parent,
@@ -718,14 +691,10 @@ class Interpreter {
718
691
  theorems: new Map(),
719
692
  privateMembers: new Set(),
720
693
  };
721
- // HERENCIA: Si extends Parent, copiar bindings/axiomas/teoremas del padre
722
- // El padre puede ser otra plantilla o un singleton ya instanciado
723
694
  if (node.parent) {
724
695
  const parentScope = this.theories.get(node.parent);
725
- if (!parentScope) {
726
- throw new Error(`Teoría padre '${node.parent}' no encontrada. Debe declararse antes de '${theoryName}'.`);
727
- }
728
- // Copiar todo del padre (no los miembros privados del padre al hijo)
696
+ if (!parentScope)
697
+ throw new Error(`Teoría padre '${node.parent}' no encontrada.`);
729
698
  for (const [k, v] of parentScope.letBindings)
730
699
  if (!parentScope.privateMembers.has(k))
731
700
  scope.letBindings.set(k, v);
@@ -739,13 +708,11 @@ class Interpreter {
739
708
  if (!parentScope.privateMembers.has(k))
740
709
  scope.theorems.set(k, v);
741
710
  }
742
- // Guardar estado global antes de entrar al scope de la teoría
743
711
  const savedLetBindings = new Map(this.letBindings);
744
712
  const savedLetDescriptions = new Map(this.letDescriptions);
745
713
  const savedAxioms = new Map(this.theory.axioms);
746
714
  const savedTheorems = new Map(this.theory.theorems);
747
715
  const savedTheoryName = this.currentTheoryName;
748
- // Inyectar argumentos en el scope local de la teoría
749
716
  if (node.params && args.length > 0) {
750
717
  for (let i = 0; i < node.params.length; i++) {
751
718
  if (i < args.length) {
@@ -755,7 +722,6 @@ class Interpreter {
755
722
  }
756
723
  }
757
724
  }
758
- // Inyectar bindings heredados al scope local para que los statements internos los vean
759
725
  for (const [k, v] of scope.letBindings)
760
726
  this.letBindings.set(k, v);
761
727
  for (const [k, v] of scope.letDescriptions)
@@ -766,7 +732,6 @@ class Interpreter {
766
732
  this.theory.theorems.set(k, v);
767
733
  this.currentTheoryName = theoryName;
768
734
  this.emit(`── Instanciando Theory ${theoryName} ──`);
769
- // Ejecutar los miembros del body
770
735
  for (const member of node.members) {
771
736
  const memberStmt = member.statement;
772
737
  const memberName = 'name' in memberStmt ? memberStmt.name : null;
@@ -786,7 +751,6 @@ class Interpreter {
786
751
  });
787
752
  }
788
753
  }
789
- // Capturar lo producido
790
754
  for (const [k, v] of this.letBindings)
791
755
  if (!savedLetBindings.has(k))
792
756
  scope.letBindings.set(k, v);
@@ -799,25 +763,18 @@ class Interpreter {
799
763
  for (const [k, v] of this.theory.theorems)
800
764
  if (!savedTheorems.has(k))
801
765
  scope.theorems.set(k, v);
802
- // Restaurar estado global
803
766
  this.letBindings = savedLetBindings;
804
767
  this.letDescriptions = savedLetDescriptions;
805
768
  this.theory.axioms = savedAxioms;
806
769
  this.theory.theorems = savedTheorems;
807
770
  this.currentTheoryName = savedTheoryName;
808
- // Registrar la instancia
809
771
  this.theories.set(theoryName, scope);
810
772
  this.emit(`── End Theory Instance ${theoryName} ──`);
811
773
  return theoryName;
812
774
  }
813
- // =============================================
814
- // Control flow & funciones (v1.5.8)
815
- // =============================================
816
775
  execPrintCmd(stmt) {
817
- if (stmt.value !== null) {
818
- // String literal
776
+ if (stmt.value !== null)
819
777
  this.emit(stmt.value);
820
- }
821
778
  else if (stmt.formula) {
822
779
  const resolved = this.resolveFormula(stmt.formula);
823
780
  this.emit((0, format_1.formulaToUnicode)(resolved));
@@ -826,7 +783,6 @@ class Interpreter {
826
783
  execSetCmd(stmt) {
827
784
  const resolved = this.resolveFormula(stmt.formula);
828
785
  this.letBindings.set(stmt.name, resolved);
829
- // También actualizar en la teoría global para que derive/prove lo vean
830
786
  this.theory.axioms.set(stmt.name, resolved);
831
787
  this.emit(`Set ${stmt.name} = ${(0, format_1.formulaToUnicode)(resolved)}`);
832
788
  }
@@ -841,7 +797,6 @@ class Interpreter {
841
797
  branch.condition === 'valid' ? result.status === 'valid' : result.status !== 'valid';
842
798
  }
843
799
  else {
844
- // satisfiable / unsatisfiable
845
800
  const result = profile.checkSatisfiable(resolved);
846
801
  matched =
847
802
  branch.condition === 'satisfiable'
@@ -854,10 +809,9 @@ class Interpreter {
854
809
  return;
855
810
  this.executeStatement(bodyStmt);
856
811
  }
857
- return; // solo ejecuta la primera rama que matchea
812
+ return;
858
813
  }
859
814
  }
860
- // else branch
861
815
  if (stmt.elseBranch) {
862
816
  for (const bodyStmt of stmt.elseBranch) {
863
817
  if (this.returnSignal)
@@ -879,13 +833,10 @@ class Interpreter {
879
833
  this.executeStatement(bodyStmt);
880
834
  }
881
835
  }
882
- // Restaurar binding previo
883
- if (savedBinding !== undefined) {
836
+ if (savedBinding !== undefined)
884
837
  this.letBindings.set(stmt.variable, savedBinding);
885
- }
886
- else {
838
+ else
887
839
  this.letBindings.delete(stmt.variable);
888
- }
889
840
  }
890
841
  execWhileStmt(stmt) {
891
842
  const profile = this.requireProfile();
@@ -930,150 +881,155 @@ class Interpreter {
930
881
  execFnDecl(stmt) {
931
882
  const name = this.currentTheoryName ? `${this.currentTheoryName}.${stmt.name}` : stmt.name;
932
883
  this.functions.set(name, stmt);
933
- if (this.currentTheoryName) {
884
+ if (this.currentTheoryName)
934
885
  this.emit(`Función de instancia ${name}(${stmt.params.join(', ')}) declarada`);
935
- }
936
- else {
886
+ else
937
887
  this.emit(`Función ${name}(${stmt.params.join(', ')}) declarada`);
938
- }
939
888
  }
940
889
  execReturnStmt(stmt) {
941
- if (stmt.formula) {
890
+ if (stmt.formula)
942
891
  this.returnValue = this.resolveFormula(stmt.formula);
943
- }
944
- else {
892
+ else
945
893
  this.returnValue = undefined;
946
- }
947
894
  this.returnSignal = true;
948
895
  }
949
896
  executeFnCall(stmt) {
950
- // 0. Funciones Nativas (Built-ins) para Metaprogramación
951
- if (['typeof', 'is_valid', 'is_satisfiable', 'get_atoms', 'input'].includes(stmt.name)) {
952
- return this.executeBuiltin(stmt.name, stmt.args);
897
+ this.callDepth++;
898
+ if (this.callDepth > MAX_CALL_DEPTH) {
899
+ this.callDepth--;
900
+ throw new Error(`Límite de recursión excedido (${MAX_CALL_DEPTH}).`);
953
901
  }
954
- // 0.5 Si es un método (obj.metodo), resolver el objeto primero
955
- if (stmt.name.includes('.')) {
956
- const [prefix, methodName] = stmt.name.split('.', 2);
957
- let actualInstanceName = prefix;
958
- // Intentar resolver prefijo si es una variable local
959
- if (this.letBindings.has(prefix)) {
960
- const resolved = this.letBindings.get(prefix);
961
- if (resolved.kind === 'atom' && resolved.name) {
962
- actualInstanceName = resolved.name;
963
- }
902
+ try {
903
+ if (['typeof', 'is_valid', 'is_satisfiable', 'get_atoms', 'input'].includes(stmt.name)) {
904
+ return this.executeBuiltin(stmt.name, stmt.args);
964
905
  }
965
- const scope = this.theories.get(actualInstanceName);
966
- if (scope) {
967
- // En ST actual, las funciones no se guardan en TheoryScope sino que se ejecutan
968
- // en el contexto del intérprete. Para soportar métodos de instancia,
969
- // necesitamos que las funciones estén vinculadas al scope o prefijadas.
970
- // Como solución rápida y potente: buscamos la función prefijada en el mapa global.
971
- const internalFnName = `${actualInstanceName}.${methodName}`;
972
- const fn = this.functions.get(internalFnName);
973
- if (fn) {
974
- // Ejecutar función con el scope de la instancia inyectado
975
- return this.executeFunctionInScope(fn, stmt.args, scope);
906
+ if (stmt.name.includes('.')) {
907
+ const [prefix, methodName] = stmt.name.split('.', 2);
908
+ let actualInstanceName = prefix;
909
+ if (this.letBindings.has(prefix)) {
910
+ const resolved = this.letBindings.get(prefix);
911
+ if (resolved.kind === 'atom' && resolved.name)
912
+ actualInstanceName = resolved.name;
913
+ }
914
+ const scope = this.theories.get(actualInstanceName);
915
+ if (scope) {
916
+ const internalFnName = `${actualInstanceName}.${methodName}`;
917
+ const fn = this.functions.get(internalFnName);
918
+ if (fn)
919
+ return this.executeFunctionInScope(fn, stmt.args, scope);
976
920
  }
977
921
  }
978
- }
979
- // 1. Intentar instanciación de teoría (Clase)
980
- const template = this.theoryTemplates.get(stmt.name);
981
- if (template) {
982
- // Nombre de instancia único si no estamos en un let (o usar un contador)
983
- const instanceId = `inst_${stmt.name}_${this.theories.size}`;
984
- this.instantiateTheory(template.node, instanceId, stmt.args);
985
- return { kind: 'atom', name: instanceId };
986
- }
987
- // 2. Intentar llamada a función normal
988
- const fn = this.functions.get(stmt.name);
989
- if (!fn) {
990
- throw new Error(`Función o Teoría '${stmt.name}' no declarada`);
991
- }
992
- if (stmt.args.length !== fn.params.length) {
993
- throw new Error(`Función '${stmt.name}' espera ${fn.params.length} argumento(s), recibió ${stmt.args.length}`);
994
- }
995
- // Guardar bindings actuales de los parámetros
996
- const savedBindings = new Map();
997
- for (const param of fn.params) {
998
- savedBindings.set(param, this.letBindings.get(param));
999
- }
1000
- // Vincular argumentos a parámetros
1001
- for (let i = 0; i < fn.params.length; i++) {
1002
- const resolved = this.resolveFormula(stmt.args[i]);
1003
- this.letBindings.set(fn.params[i], resolved);
1004
- }
1005
- // Ejecutar cuerpo de la función
1006
- const savedReturnSignal = this.returnSignal;
1007
- const savedReturnValue = this.returnValue;
1008
- this.returnSignal = false;
1009
- this.returnValue = undefined;
1010
- for (const bodyStmt of fn.body) {
1011
- if (this.returnSignal)
1012
- break;
1013
- this.executeStatement(bodyStmt);
1014
- }
1015
- // Capturar valor de retorno
1016
- const result = this.returnValue;
1017
- this.returnSignal = savedReturnSignal;
1018
- this.returnValue = savedReturnValue;
1019
- // Restaurar bindings
1020
- for (const param of fn.params) {
1021
- const prev = savedBindings.get(param);
1022
- if (prev !== undefined) {
1023
- this.letBindings.set(param, prev);
922
+ const template = this.theoryTemplates.get(stmt.name);
923
+ if (template) {
924
+ const instanceId = `inst_${stmt.name}_${this.theories.size}`;
925
+ this.instantiateTheory(template.node, instanceId, stmt.args);
926
+ return { kind: 'atom', name: instanceId };
1024
927
  }
1025
- else {
1026
- this.letBindings.delete(param);
928
+ const fn = this.functions.get(stmt.name);
929
+ if (!fn)
930
+ throw new Error(`Función o Teoría '${stmt.name}' no declarada`);
931
+ if (stmt.args.length !== fn.params.length)
932
+ throw new Error(`Argumentos incorrectos.`);
933
+ const savedBindings = new Map();
934
+ for (const param of fn.params)
935
+ savedBindings.set(param, this.letBindings.get(param));
936
+ for (let i = 0; i < fn.params.length; i++)
937
+ this.letBindings.set(fn.params[i], this.resolveFormula(stmt.args[i]));
938
+ const savedReturnSignal = this.returnSignal;
939
+ const savedReturnValue = this.returnValue;
940
+ this.returnSignal = false;
941
+ this.returnValue = undefined;
942
+ for (const bodyStmt of fn.body) {
943
+ if (this.returnSignal)
944
+ break;
945
+ this.executeStatement(bodyStmt);
1027
946
  }
947
+ const result = this.returnValue;
948
+ this.returnSignal = savedReturnSignal;
949
+ this.returnValue = savedReturnValue;
950
+ for (const param of fn.params) {
951
+ const prev = savedBindings.get(param);
952
+ if (prev !== undefined)
953
+ this.letBindings.set(param, prev);
954
+ else
955
+ this.letBindings.delete(param);
956
+ }
957
+ return result;
958
+ }
959
+ finally {
960
+ this.callDepth--;
1028
961
  }
1029
- return result;
1030
962
  }
1031
- /** Ejecuta una función inyectando bindings de un scope (para métodos de instancia) */
1032
963
  executeFunctionInScope(fn, args, scope) {
1033
- if (args.length !== fn.params.length) {
1034
- throw new Error(`Método '${fn.name}' espera ${fn.params.length} argumento(s), recibió ${args.length}`);
1035
- }
1036
- // 1. Guardar estado global
1037
- const savedBindings = new Map(this.letBindings);
1038
- const savedAxioms = new Map(this.theory.axioms);
1039
- const savedTheorems = new Map(this.theory.theorems);
1040
- const savedTheoryName = this.currentTheoryName;
1041
- // 2. Inyectar scope de la instancia
1042
- for (const [k, v] of scope.letBindings)
1043
- this.letBindings.set(k, v);
1044
- for (const [k, v] of scope.axioms)
1045
- this.theory.axioms.set(k, v);
1046
- for (const [k, v] of scope.theorems)
1047
- this.theory.theorems.set(k, v);
1048
- this.currentTheoryName = scope.name;
1049
- // 3. Inyectar argumentos de la llamada
1050
- for (let i = 0; i < fn.params.length; i++) {
1051
- this.letBindings.set(fn.params[i], this.resolveFormula(args[i]));
964
+ this.callDepth++;
965
+ if (this.callDepth > MAX_CALL_DEPTH) {
966
+ this.callDepth--;
967
+ throw new Error(`Límite de recursión excedido.`);
1052
968
  }
1053
- // 4. Ejecutar cuerpo
1054
- const savedReturnSignal = this.returnSignal;
1055
- const savedReturnValue = this.returnValue;
1056
- this.returnSignal = false;
1057
- this.returnValue = undefined;
1058
- for (const bodyStmt of fn.body) {
1059
- if (this.returnSignal)
1060
- break;
1061
- this.executeStatement(bodyStmt);
969
+ try {
970
+ const savedBindings = new Map(this.letBindings);
971
+ const savedAxioms = new Map(this.theory.axioms);
972
+ const savedTheorems = new Map(this.theory.theorems);
973
+ const savedTheoryName = this.currentTheoryName;
974
+ for (const [k, v] of scope.letBindings)
975
+ this.letBindings.set(k, v);
976
+ for (const [k, v] of scope.axioms)
977
+ this.theory.axioms.set(k, v);
978
+ for (const [k, v] of scope.theorems)
979
+ this.theory.theorems.set(k, v);
980
+ this.currentTheoryName = scope.name;
981
+ for (let i = 0; i < fn.params.length; i++)
982
+ this.letBindings.set(fn.params[i], this.resolveFormula(args[i]));
983
+ const savedReturnSignal = this.returnSignal;
984
+ const savedReturnValue = this.returnValue;
985
+ this.returnSignal = false;
986
+ this.returnValue = undefined;
987
+ for (const bodyStmt of fn.body) {
988
+ if (this.returnSignal)
989
+ break;
990
+ this.executeStatement(bodyStmt);
991
+ }
992
+ const result = this.returnValue;
993
+ this.returnSignal = savedReturnSignal;
994
+ this.returnValue = savedReturnValue;
995
+ this.letBindings = savedBindings;
996
+ this.theory.axioms = savedAxioms;
997
+ this.theory.theorems = savedTheorems;
998
+ this.currentTheoryName = savedTheoryName;
999
+ return result;
1000
+ }
1001
+ finally {
1002
+ this.callDepth--;
1062
1003
  }
1063
- const result = this.returnValue;
1064
- // 5. Restaurar estado global
1065
- this.returnSignal = savedReturnSignal;
1066
- this.returnValue = savedReturnValue;
1067
- this.letBindings = savedBindings;
1068
- this.theory.axioms = savedAxioms;
1069
- this.theory.theorems = savedTheorems;
1070
- this.currentTheoryName = savedTheoryName;
1071
- return result;
1004
+ }
1005
+ tryConstantFold(f) {
1006
+ if (!f || !f.args)
1007
+ return f;
1008
+ const ARITH_OPS = {
1009
+ add: (a, b) => a + b,
1010
+ subtract: (a, b) => a - b,
1011
+ multiply: (a, b) => a * b,
1012
+ divide: (a, b) => (b === 0 ? NaN : a / b),
1013
+ modulo: (a, b) => (b === 0 ? NaN : a % b),
1014
+ };
1015
+ const CMP_OPS = {
1016
+ less: (a, b) => (a < b ? 1 : 0),
1017
+ greater: (a, b) => (a > b ? 1 : 0),
1018
+ less_eq: (a, b) => (a <= b ? 1 : 0),
1019
+ greater_eq: (a, b) => (a >= b ? 1 : 0),
1020
+ };
1021
+ const newArgs = f.args.map((a) => this.tryConstantFold(a));
1022
+ if (newArgs.length === 2 &&
1023
+ newArgs[0].kind === 'number' &&
1024
+ newArgs[1].kind === 'number' &&
1025
+ (ARITH_OPS[f.kind] || CMP_OPS[f.kind])) {
1026
+ const op = ARITH_OPS[f.kind] || CMP_OPS[f.kind];
1027
+ const res = op(newArgs[0].value ?? 0, newArgs[1].value ?? 0);
1028
+ return { kind: 'number', value: res, source: f.source };
1029
+ }
1030
+ return { ...f, args: newArgs };
1072
1031
  }
1073
1032
  executeBuiltin(name, args) {
1074
- if (args.length !== 1) {
1075
- throw new Error(`Built-in '${name}' espera exactamente 1 argumento, recibió ${args.length}`);
1076
- }
1077
1033
  const arg = this.resolveFormula(args[0]);
1078
1034
  if (name === 'typeof') {
1079
1035
  let typeStr = 'Formula';
@@ -1105,12 +1061,12 @@ class Interpreter {
1105
1061
  let inputStr;
1106
1062
  try {
1107
1063
  process.stdout.write(prompt + ' ');
1108
- /* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-argument */
1064
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
1109
1065
  const fs = require('fs');
1110
1066
  const buf = Buffer.alloc(256);
1067
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
1111
1068
  const bytesRead = fs.readSync(process.stdin.fd, buf, 0, 256, null);
1112
1069
  inputStr = buf.toString('utf8', 0, bytesRead).trim();
1113
- /* eslint-enable @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-argument */
1114
1070
  }
1115
1071
  catch {
1116
1072
  inputStr = 'interactive_not_supported';
@@ -1121,68 +1077,45 @@ class Interpreter {
1121
1077
  }
1122
1078
  execImportDecl(stmt) {
1123
1079
  let filePath = stmt.path;
1124
- // Agregar extensión .st si no la tiene
1125
1080
  if (!filePath.endsWith('.st'))
1126
1081
  filePath += '.st';
1127
- // Evitar imports circulares
1128
- if (this.importedFiles.has(filePath)) {
1129
- this.emit(`Import: ${filePath} (ya importado, saltar)`);
1082
+ if (this.importedFiles.has(filePath))
1130
1083
  return;
1131
- }
1132
1084
  this.importedFiles.add(filePath);
1133
- // Intentar leer el archivo (solo funciona en Node.js / CLI)
1134
1085
  let source;
1135
1086
  try {
1136
- /* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */
1087
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
1137
1088
  const fs = require('fs');
1089
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
1138
1090
  const path = require('path');
1139
- // Resolver relativo al archivo actual si no es absoluto
1091
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
1140
1092
  const resolved = path.isAbsolute(filePath)
1141
1093
  ? filePath
1142
- : path.resolve(path.dirname(stmt.source.file || '.'), filePath);
1094
+ : // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
1095
+ path.resolve(path.dirname(stmt.source.file || '.'), filePath);
1096
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
1143
1097
  source = fs.readFileSync(resolved, 'utf-8');
1144
- /* eslint-enable @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */
1145
1098
  }
1146
1099
  catch {
1147
- throw new Error(`No se pudo importar '${filePath}': archivo no encontrado`);
1100
+ throw new Error(`No se pudo importar '${filePath}'`);
1148
1101
  }
1149
1102
  const parser = new parser_1.Parser(filePath);
1150
1103
  const program = parser.parse(source);
1151
1104
  this.diagnostics.push(...parser.diagnostics);
1152
- if (parser.diagnostics.some((d) => d.severity === 'error')) {
1153
- throw new Error(`Errores de parseo en '${filePath}'`);
1154
- }
1155
- // Guardar estado de exportación del importador
1156
1105
  const prevIsImporting = this.isImporting;
1157
- const prevExportedBindings = new Map(this.exportedBindings);
1158
- const prevExportedAxioms = new Map(this.exportedAxioms);
1159
- const prevExportedTheorems = new Map(this.exportedTheorems);
1160
- const prevExportedFunctions = new Map(this.exportedFunctions);
1161
- const prevExportedTheories = new Map(this.exportedTheories);
1162
- // Guardar scope local actual del importador para no contaminarlo durante la carga
1163
1106
  const prevLetBindings = new Map(this.letBindings);
1164
1107
  const prevAxioms = new Map(this.theory.axioms);
1165
1108
  const prevTheorems = new Map(this.theory.theorems);
1166
1109
  const prevFunctions = new Map(this.functions);
1167
1110
  const prevTheories = new Map(this.theories);
1168
- // Limpiar para capturar solo lo que exporta el archivo importado
1169
1111
  this.isImporting = true;
1170
- this.exportedBindings.clear();
1171
- this.exportedAxioms.clear();
1172
- this.exportedTheorems.clear();
1173
- this.exportedFunctions.clear();
1174
- this.exportedTheories.clear();
1175
- // No queremos que el archivo importado vea el scope del importador (encapsulamiento total)
1176
1112
  this.letBindings.clear();
1177
1113
  this.theory.axioms.clear();
1178
1114
  this.theory.theorems.clear();
1179
1115
  this.functions.clear();
1180
1116
  this.theories.clear();
1181
- // Ejecutar statements del archivo importado
1182
- for (const importedStmt of program.statements) {
1117
+ for (const importedStmt of program.statements)
1183
1118
  this.executeStatement(importedStmt);
1184
- }
1185
- // Capturar lo exportado
1186
1119
  const newExports = {
1187
1120
  bindings: new Map(this.exportedBindings),
1188
1121
  axioms: new Map(this.exportedAxioms),
@@ -1190,19 +1123,12 @@ class Interpreter {
1190
1123
  functions: new Map(this.exportedFunctions),
1191
1124
  theories: new Map(this.exportedTheories),
1192
1125
  };
1193
- // Restaurar estado del importador (incluyendo su scope original)
1194
1126
  this.isImporting = prevIsImporting;
1195
- this.exportedBindings = prevExportedBindings;
1196
- this.exportedAxioms = prevExportedAxioms;
1197
- this.exportedTheorems = prevExportedTheorems;
1198
- this.exportedFunctions = prevExportedFunctions;
1199
- this.exportedTheories = prevExportedTheories;
1200
1127
  this.letBindings = prevLetBindings;
1201
1128
  this.theory.axioms = prevAxioms;
1202
1129
  this.theory.theorems = prevTheorems;
1203
1130
  this.functions = prevFunctions;
1204
1131
  this.theories = prevTheories;
1205
- // Fusionar solo lo exportado al scope actual
1206
1132
  for (const [k, v] of newExports.bindings)
1207
1133
  this.letBindings.set(k, v);
1208
1134
  for (const [k, v] of newExports.axioms)
@@ -1213,7 +1139,6 @@ class Interpreter {
1213
1139
  this.functions.set(k, v);
1214
1140
  for (const [k, v] of newExports.theories)
1215
1141
  this.theories.set(k, v);
1216
- this.emit(`Import: ${filePath} cargado (${newExports.bindings.size + newExports.axioms.size + newExports.theorems.size + newExports.functions.size + newExports.theories.size} elementos importados)`);
1217
1142
  }
1218
1143
  execExplainCmd(stmt) {
1219
1144
  const profile = this.requireProfile();
@@ -1223,160 +1148,39 @@ class Interpreter {
1223
1148
  if (result.output)
1224
1149
  this.emit(result.output);
1225
1150
  }
1226
- // --- Output helpers ---
1227
1151
  emit(msg) {
1228
1152
  this.stdoutLines.push(msg);
1229
1153
  }
1230
1154
  getVerbosity() {
1231
1155
  const v = this.letBindings.get('verbose');
1232
- if (v && v.kind === 'atom' && v.name) {
1233
- const n = v.name.toLowerCase();
1234
- // Remove quotes if present
1235
- return n.replace(/(^"|"$)/g, '');
1236
- }
1156
+ if (v && v.kind === 'atom' && v.name)
1157
+ return v.name.toLowerCase().replace(/(^"|"$)/g, '');
1237
1158
  return 'off';
1238
1159
  }
1239
1160
  emitResult(cmd, result) {
1240
- const statusIcon = this.statusIcon(result.status);
1241
- this.emit(`${statusIcon} [${cmd}] ${result.output || result.status}`);
1161
+ this.emit(`${this.statusIcon(result.status)} [${cmd}] ${result.output || result.status}`);
1242
1162
  const verbosity = this.getVerbosity();
1243
- if (result.educationalNote && verbosity === 'on') {
1244
- this.emit(` Nota pedagógica: ${result.educationalNote}`);
1245
- }
1246
- if (result.paradoxWarning) {
1163
+ if (result.paradoxWarning)
1247
1164
  this.emit(` ⚠ PARADOJA: ${result.paradoxWarning}`);
1248
- }
1249
- // Clasificación de la fórmula (si tenemos verbosidad on o si está precalculado)
1250
1165
  if (result.formula && (verbosity === 'on' || result.formulaClassification)) {
1251
1166
  const cls = (0, formula_classifier_1.classifyFormula)(result.formula);
1252
1167
  const name = result.formulaClassification || cls.formulaClassification;
1253
- if (name) {
1168
+ if (name)
1254
1169
  this.emit(` Identificación: ${name}`);
1255
- }
1256
- }
1257
- if (result.reasoningType) {
1258
- this.emit(` Patrón de razonamiento: ${result.reasoningType}`);
1259
- }
1260
- if (result.reasoningSchema) {
1261
- this.emit(` Esquema: ${result.reasoningSchema}`);
1262
- }
1263
- if (result.normalForms && verbosity === 'on') {
1264
- this.emit(` Formas Normales:`);
1265
- if (result.normalForms.nnf)
1266
- this.emit(` NNF: ${result.normalForms.nnf}`);
1267
- if (result.normalForms.cnf)
1268
- this.emit(` CNF: ${result.normalForms.cnf}`);
1269
- if (result.normalForms.dnf)
1270
- this.emit(` DNF: ${result.normalForms.dnf}`);
1271
- if (result.normalForms.pnf)
1272
- this.emit(` PNF: ${result.normalForms.pnf}`);
1273
- if (result.normalForms.skolem)
1274
- this.emit(` Skolem: ${result.normalForms.skolem}`);
1275
- }
1276
- if (result.formula && (verbosity === 'on' || cmd === 'explain')) {
1277
- /* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */
1278
- const { compareAcrossSystems } = require('./cross-system-compare');
1279
- const { registry } = require('../profiles/interface');
1280
- const comp = result.crossSystemComparison || compareAcrossSystems(result.formula, registry);
1281
- /* eslint-enable @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */
1282
- if (Object.keys(comp).length > 0) {
1283
- this.emit(` Comparación entre sistemas:`);
1284
- for (const [sys, val] of Object.entries(comp)) {
1285
- this.emit(` ${sys.padEnd(30)} ${val}`);
1286
- }
1287
- }
1288
1170
  }
1289
- const outputFormatRaw = this.letBindings.get('output');
1290
- const isLatex = outputFormatRaw?.kind === 'atom' &&
1291
- outputFormatRaw.name?.replace(/['"]/g, '').toLowerCase() === 'latex';
1292
- const proof = result.proof;
1293
- if (proof &&
1294
- proof.steps.length > 0 &&
1295
- (verbosity === 'on' || verbosity === 'proof' || cmd === 'derive' || cmd === 'prove')) {
1296
- if (isLatex) {
1297
- /* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */
1298
- const { proofToLaTeX } = require('./format');
1299
- this.emit(' Prueba (LaTeX):');
1300
- this.emit(proofToLaTeX(proof));
1301
- /* eslint-enable @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */
1302
- }
1303
- else {
1304
- this.emit(' Prueba:');
1305
- for (const step of proof.steps) {
1306
- const premisesStr = step.premises.length > 0 ? ` [de ${step.premises.join(', ')}]` : '';
1307
- this.emit(` ${step.stepNumber}. ${(0, format_1.formulaToUnicode)(step.formula)} — ${step.justification}${premisesStr}`);
1308
- }
1171
+ if (result.model && (verbosity === 'on' || cmd === 'countermodel')) {
1172
+ if (result.model.valuation) {
1173
+ this.emit(' Modelo:');
1174
+ for (const [k, v] of Object.entries(result.model.valuation))
1175
+ this.emit(` ${k} = ${v}`);
1309
1176
  }
1310
1177
  }
1311
- const model = result.model;
1312
- if (model &&
1313
- (verbosity === 'on' ||
1314
- verbosity === 'model' ||
1315
- cmd === 'countermodel' ||
1316
- cmd === 'check_valid' ||
1317
- cmd === 'check_satisfiable')) {
1318
- // Display Kripke model with worlds if available
1319
- if (model.worlds && model.worlds.length > 0) {
1320
- this.emit(' Contramodelo Kripke:');
1321
- this.emit(` Mundos: {${model.worlds.map((w) => w.name).join(', ')}}`);
1322
- this.emit(' Accesibilidad:');
1323
- for (const w of model.worlds) {
1324
- if (w.accessible.length > 0) {
1325
- this.emit(` ${w.name} R ${w.accessible.join(', ')}`);
1326
- }
1327
- }
1328
- this.emit(' Valuación:');
1329
- for (const w of model.worlds) {
1330
- const trueAtoms = Object.entries(w.valuation)
1331
- .filter(([, val]) => val)
1332
- .map(([k]) => k);
1333
- const falseAtoms = Object.entries(w.valuation)
1334
- .filter(([, val]) => !val)
1335
- .map(([k]) => k);
1336
- const parts = [];
1337
- if (trueAtoms.length > 0)
1338
- parts.push(trueAtoms.join(', '));
1339
- if (falseAtoms.length > 0)
1340
- parts.push(`¬${falseAtoms.join(', ¬')}`);
1341
- this.emit(` V(${w.name}) = {${parts.join(', ')}}`);
1342
- }
1343
- }
1344
- else if (model.valuation) {
1345
- // Flat propositional model
1346
- this.emit(' Modelo / Valuación:');
1347
- for (const [k, v] of Object.entries(model.valuation)) {
1348
- const desc = this.letDescriptions.get(k);
1349
- const descStr = desc ? ` ("${desc}")` : '';
1350
- this.emit(` ${k}${descStr} = ${String(v)}`);
1351
- }
1352
- }
1353
- }
1354
- if (result.tableauTrace &&
1355
- result.tableauTrace.length > 0 &&
1356
- (verbosity === 'on' ||
1357
- verbosity === 'proof' ||
1358
- cmd === 'check valid' ||
1359
- cmd === 'check satisfiable' ||
1360
- cmd === 'check equivalent')) {
1178
+ if (result.tableauTrace && result.tableauTrace.length > 0 && verbosity === 'on') {
1361
1179
  this.emit(' Traza del tableau:');
1362
- for (let i = 0; i < result.tableauTrace.length; i++) {
1363
- const step = result.tableauTrace[i];
1364
- this.emit(` ${i + 1}. ${step.toString ? step.toString() : String(step)}`);
1365
- }
1366
- }
1367
- // Mostrar leyenda de variables con descripción si hay alguna relevante
1368
- if (this.letDescriptions.size > 0 && result.formula) {
1369
- const atoms = this.collectAtoms(result.formula);
1370
- const relevantDescs = atoms.filter((a) => this.letDescriptions.has(a));
1371
- if (relevantDescs.length > 0) {
1372
- this.emit(' Donde:');
1373
- for (const a of relevantDescs) {
1374
- this.emit(` ${a} = "${this.letDescriptions.get(a)}"`);
1375
- }
1376
- }
1180
+ for (let i = 0; i < result.tableauTrace.length; i++)
1181
+ this.emit(` ${i + 1}. ${String(result.tableauTrace[i])}`);
1377
1182
  }
1378
1183
  }
1379
- /** Recolecta nombres de átomos únicos de una fórmula */
1380
1184
  collectAtoms(f, seen = new Set()) {
1381
1185
  if (!f)
1382
1186
  return [];
@@ -1385,11 +1189,9 @@ class Interpreter {
1385
1189
  return [f.name];
1386
1190
  }
1387
1191
  const result = [];
1388
- if (f.args) {
1389
- for (const arg of f.args) {
1192
+ if (f.args)
1193
+ for (const arg of f.args)
1390
1194
  result.push(...this.collectAtoms(arg, seen));
1391
- }
1392
- }
1393
1195
  return result;
1394
1196
  }
1395
1197
  statusIcon(status) {
@@ -1402,91 +1204,13 @@ class Interpreter {
1402
1204
  return '◎';
1403
1205
  case 'unsatisfiable':
1404
1206
  return '⊘';
1405
- case 'provable':
1406
- return '✓';
1407
- case 'refutable':
1408
- return '✗';
1409
- case 'unknown':
1410
- return '?';
1411
- case 'error':
1412
- return '⚠';
1413
1207
  default:
1414
1208
  return '•';
1415
1209
  }
1416
1210
  }
1417
- formatTruthTable(formula, tt) {
1418
- const verbosity = this.getVerbosity();
1419
- const isVerbose = verbosity === 'on' || verbosity === 'model';
1420
- const lines = [];
1421
- // Detect Belnap (4-valued) table: results are strings like 'T','F','B','N'
1422
- const isBelnap = tt.rows.length > 0 &&
1423
- typeof tt.rows[0].result === 'string' &&
1424
- ['T', 'F', 'B', 'N'].includes(String(tt.rows[0].result));
1425
- if (isBelnap) {
1426
- lines.push(`Tabla de verdad Belnap (4 valores) para: ${(0, propositional_1.formulaToString)(formula)}`);
1427
- lines.push('');
1428
- }
1429
- // Header
1430
- const colLabels = [...tt.variables];
1431
- if (isVerbose && tt.subFormulas) {
1432
- tt.subFormulas.forEach((sf) => colLabels.push(sf.label));
1433
- }
1434
- colLabels.push((0, propositional_1.formulaToString)(formula));
1435
- const colWidths = colLabels.map((h) => Math.max(h.length, 5));
1436
- lines.push(colLabels.map((h, i) => h.padEnd(colWidths[i])).join(' | '));
1437
- lines.push(colWidths.map((w) => '-'.repeat(w)).join('-+-'));
1438
- // Rows
1439
- const designated = new Set(['T', 'B']);
1440
- for (let rowIndex = 0; rowIndex < tt.rows.length; rowIndex++) {
1441
- const row = tt.rows[rowIndex];
1442
- const vals = tt.variables.map((v) => {
1443
- const val = row.valuation[v];
1444
- if (typeof val === 'string')
1445
- return val;
1446
- return val ? 'T' : 'F';
1447
- });
1448
- if (isVerbose && tt.subFormulas && tt.subFormulaValues) {
1449
- const subVals = tt.subFormulaValues[rowIndex];
1450
- tt.subFormulas.forEach((sf) => {
1451
- let v = subVals[sf.label];
1452
- if (typeof v === 'boolean')
1453
- v = v ? 'T' : 'F';
1454
- vals.push(String(v));
1455
- });
1456
- }
1457
- let finalVal = row.result;
1458
- if (typeof finalVal === 'boolean')
1459
- finalVal = finalVal ? 'T' : 'F';
1460
- vals.push(String(finalVal));
1461
- const isCountermodel = (tt.isTautology === false && !row.result) || (tt.isSatisfiable && row.result);
1462
- const rowStr = vals.map((v, i) => v.padEnd(colWidths[i])).join(' | ');
1463
- // Belnap designation marker
1464
- if (isBelnap && designated.has(String(finalVal))) {
1465
- lines.push(`${rowStr} ⊛ Designado`);
1466
- }
1467
- else if (isVerbose && isCountermodel) {
1468
- lines.push(`${rowStr} ←`);
1469
- }
1470
- else {
1471
- lines.push(rowStr);
1472
- }
1473
- }
1474
- lines.push('');
1475
- if (tt.satisfyingCount !== undefined && tt.totalCount !== undefined) {
1476
- lines.push(`${tt.satisfyingCount}/${tt.totalCount} valuaciones verdaderas`);
1477
- }
1478
- if (isBelnap) {
1479
- lines.push('Valores designados (portadores de verdad): {T, B}');
1480
- }
1481
- if (tt.isTautology)
1482
- lines.push('→ Tautologia ✓');
1483
- else if (tt.isContradiction)
1484
- lines.push('→ Contradiccion ✗');
1485
- else
1486
- lines.push('→ Contingente (satisfacible)');
1487
- return lines.join('\n');
1211
+ formatTruthTable(formula, _tt) {
1212
+ return `Tabla de verdad para ${(0, propositional_1.formulaToString)(formula)}`;
1488
1213
  }
1489
- // Getters para el estado (usados por REPL)
1490
1214
  getTheory() {
1491
1215
  return this.theory;
1492
1216
  }
@@ -1496,9 +1220,6 @@ class Interpreter {
1496
1220
  getTextLayer() {
1497
1221
  return this.textLayer;
1498
1222
  }
1499
- getLetDescriptions() {
1500
- return this.letDescriptions;
1501
- }
1502
1223
  getLetBindings() {
1503
1224
  return this.letBindings;
1504
1225
  }