@stevenvo780/st-lang 2.6.0 → 2.7.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 (64) hide show
  1. package/bin/st +0 -0
  2. package/dist/ast/nodes.d.ts +18 -1
  3. package/dist/ast/nodes.d.ts.map +1 -1
  4. package/dist/parser/parser.d.ts +2 -0
  5. package/dist/parser/parser.d.ts.map +1 -1
  6. package/dist/parser/parser.js +82 -0
  7. package/dist/parser/parser.js.map +1 -1
  8. package/dist/profiles/arithmetic/index.d.ts.map +1 -1
  9. package/dist/profiles/arithmetic/index.js.map +1 -1
  10. package/dist/profiles/classical/first-order.d.ts +3 -3
  11. package/dist/profiles/classical/first-order.d.ts.map +1 -1
  12. package/dist/profiles/classical/first-order.js +71 -392
  13. package/dist/profiles/classical/first-order.js.map +1 -1
  14. package/dist/profiles/classical/propositional.d.ts +6 -1
  15. package/dist/profiles/classical/propositional.d.ts.map +1 -1
  16. package/dist/profiles/classical/propositional.js +334 -35
  17. package/dist/profiles/classical/propositional.js.map +1 -1
  18. package/dist/profiles/intuitionistic/propositional.d.ts.map +1 -1
  19. package/dist/profiles/intuitionistic/propositional.js +7 -1
  20. package/dist/profiles/intuitionistic/propositional.js.map +1 -1
  21. package/dist/profiles/paraconsistent/belnap.d.ts.map +1 -1
  22. package/dist/profiles/paraconsistent/belnap.js +151 -3
  23. package/dist/profiles/paraconsistent/belnap.js.map +1 -1
  24. package/dist/profiles/shared/tableau-engine.d.ts.map +1 -1
  25. package/dist/profiles/shared/tableau-engine.js +49 -22
  26. package/dist/profiles/shared/tableau-engine.js.map +1 -1
  27. package/dist/runtime/format.d.ts.map +1 -1
  28. package/dist/runtime/format.js +4 -0
  29. package/dist/runtime/format.js.map +1 -1
  30. package/dist/runtime/formula-factory.d.ts +26 -0
  31. package/dist/runtime/formula-factory.d.ts.map +1 -0
  32. package/dist/runtime/formula-factory.js +67 -0
  33. package/dist/runtime/formula-factory.js.map +1 -0
  34. package/dist/runtime/interpreter.d.ts +45 -4
  35. package/dist/runtime/interpreter.d.ts.map +1 -1
  36. package/dist/runtime/interpreter.js +1114 -609
  37. package/dist/runtime/interpreter.js.map +1 -1
  38. package/dist/tests/arithmetic.test.js +2 -1
  39. package/dist/tests/arithmetic.test.js.map +1 -1
  40. package/dist/tests/examples.test.js +12 -1
  41. package/dist/tests/examples.test.js.map +1 -1
  42. package/dist/tests/exhaustive-matrix.test.js +1 -1
  43. package/dist/tests/exhaustive-matrix.test.js.map +1 -1
  44. package/dist/tests/limits.test.js +67 -5
  45. package/dist/tests/limits.test.js.map +1 -1
  46. package/dist/tests/profiles.test.js +14 -6
  47. package/dist/tests/profiles.test.js.map +1 -1
  48. package/dist/tests/result-bindings.test.d.ts +2 -0
  49. package/dist/tests/result-bindings.test.d.ts.map +1 -0
  50. package/dist/tests/result-bindings.test.js +59 -0
  51. package/dist/tests/result-bindings.test.js.map +1 -0
  52. package/dist/tests/stress-hardware.test.d.ts +2 -0
  53. package/dist/tests/stress-hardware.test.d.ts.map +1 -0
  54. package/dist/tests/stress-hardware.test.js +516 -0
  55. package/dist/tests/stress-hardware.test.js.map +1 -0
  56. package/dist/tests/v1-features.test.js +1 -1
  57. package/dist/tests/v1-features.test.js.map +1 -1
  58. package/dist/types/index.d.ts +1 -1
  59. package/dist/types/index.d.ts.map +1 -1
  60. package/dist/utils/memo.d.ts +3 -0
  61. package/dist/utils/memo.d.ts.map +1 -1
  62. package/dist/utils/memo.js +30 -0
  63. package/dist/utils/memo.js.map +1 -1
  64. package/package.json +2 -1
@@ -7,12 +7,17 @@ exports.Interpreter = void 0;
7
7
  const parser_1 = require("../parser/parser");
8
8
  const interface_1 = require("../profiles/interface");
9
9
  const propositional_1 = require("../profiles/classical/propositional");
10
+ const arithmetic_1 = require("../profiles/arithmetic");
10
11
  const format_1 = require("./format");
11
12
  const fallacies_1 = require("./fallacies");
12
13
  // Barrel import: registra todos los perfiles automáticamente
13
14
  require("../profiles");
14
15
  const compiler_1 = require("../text-layer/compiler");
15
16
  const formula_classifier_1 = require("./formula-classifier");
17
+ const formula_factory_1 = require("./formula-factory");
18
+ const MAX_CALL_DEPTH = 10000;
19
+ const DEFAULT_MAX_RUNTIME_STEPS = 100000;
20
+ const DEFAULT_MAX_RUNTIME_CALLS = 7000;
16
21
  class Interpreter {
17
22
  theory;
18
23
  profile = null;
@@ -40,6 +45,14 @@ class Interpreter {
40
45
  exportedTheorems = new Map();
41
46
  exportedFunctions = new Map();
42
47
  exportedTheories = new Map();
48
+ /** Profundidad de llamadas a funciones (anti-recursión infinita) */
49
+ callDepth = 0;
50
+ currentBindingFrame = null;
51
+ runtimeStepCount = 0;
52
+ runtimeCallCount = 0;
53
+ /** Cache de resolución: fórmula original → fórmula resuelta (invalidado al cambiar bindings) */
54
+ resolveCache = new WeakMap();
55
+ resolveCacheGeneration = 0;
43
56
  constructor() {
44
57
  this.theory = this.createEmptyTheory();
45
58
  this.textLayer = (0, compiler_1.createTextLayerState)();
@@ -47,7 +60,17 @@ class Interpreter {
47
60
  }
48
61
  /** Registra funciones nativas (Built-ins) para metaprogramación e interactividad */
49
62
  registerBuiltins() {
50
- const builtins = ['typeof', 'is_valid', 'is_satisfiable', 'get_atoms', 'input'];
63
+ const builtins = [
64
+ 'typeof',
65
+ 'is_valid',
66
+ 'is_satisfiable',
67
+ 'get_atoms',
68
+ 'atoms_of',
69
+ 'len',
70
+ 'at',
71
+ 'formula_eq',
72
+ 'input',
73
+ ];
51
74
  for (const name of builtins) {
52
75
  this.functions.set(name, {
53
76
  kind: 'fn_decl',
@@ -90,11 +113,123 @@ class Interpreter {
90
113
  this.exportedTheorems.clear();
91
114
  this.exportedFunctions.clear();
92
115
  this.exportedTheories.clear();
116
+ this.currentBindingFrame = null;
117
+ this.runtimeStepCount = 0;
118
+ this.runtimeCallCount = 0;
119
+ }
120
+ getRuntimeStepLimit() {
121
+ const configured = this.getBinding('max_steps') || this.getBinding('max_runtime_steps');
122
+ if (configured?.kind === 'number' && configured.value && configured.value > 0) {
123
+ return Math.floor(configured.value);
124
+ }
125
+ return DEFAULT_MAX_RUNTIME_STEPS;
126
+ }
127
+ getRuntimeCallLimit() {
128
+ const configured = this.getBinding('max_calls') || this.getBinding('max_runtime_calls');
129
+ if (configured?.kind === 'number' && configured.value && configured.value > 0) {
130
+ return Math.floor(configured.value);
131
+ }
132
+ return DEFAULT_MAX_RUNTIME_CALLS;
133
+ }
134
+ tickRuntimeStep() {
135
+ this.runtimeStepCount++;
136
+ const limit = this.getRuntimeStepLimit();
137
+ if (this.runtimeStepCount > limit) {
138
+ throw new Error(`Límite de ejecución excedido (${limit} pasos).`);
139
+ }
140
+ }
141
+ isSafetyLimitMessage(message) {
142
+ return /Límite de (recursión|ejecución|llamadas ST)/i.test(message);
143
+ }
144
+ createBindingFrame() {
145
+ return {
146
+ bindings: new Map(),
147
+ descriptions: new Map(),
148
+ parent: this.currentBindingFrame,
149
+ };
150
+ }
151
+ findBindingOwner(name) {
152
+ let frame = this.currentBindingFrame;
153
+ while (frame) {
154
+ if (frame.bindings.has(name))
155
+ return frame;
156
+ frame = frame.parent;
157
+ }
158
+ if (this.letBindings.has(name))
159
+ return 'global';
160
+ return undefined;
161
+ }
162
+ hasBinding(name) {
163
+ return this.findBindingOwner(name) !== undefined;
164
+ }
165
+ getBinding(name) {
166
+ const owner = this.findBindingOwner(name);
167
+ if (!owner)
168
+ return undefined;
169
+ return owner === 'global' ? this.letBindings.get(name) : owner.bindings.get(name);
170
+ }
171
+ defineBinding(name, value, description) {
172
+ this.invalidateResolveCache();
173
+ if (this.currentBindingFrame) {
174
+ this.currentBindingFrame.bindings.set(name, value);
175
+ if (description)
176
+ this.currentBindingFrame.descriptions.set(name, description);
177
+ return;
178
+ }
179
+ this.letBindings.set(name, value);
180
+ if (description)
181
+ this.letDescriptions.set(name, description);
182
+ }
183
+ setBinding(name, value) {
184
+ this.invalidateResolveCache();
185
+ const owner = this.findBindingOwner(name);
186
+ if (owner === 'global') {
187
+ this.letBindings.set(name, value);
188
+ return;
189
+ }
190
+ if (owner) {
191
+ owner.bindings.set(name, value);
192
+ return;
193
+ }
194
+ if (this.currentBindingFrame) {
195
+ this.currentBindingFrame.bindings.set(name, value);
196
+ return;
197
+ }
198
+ this.letBindings.set(name, value);
199
+ }
200
+ captureBindingSnapshot(name) {
201
+ const owner = this.findBindingOwner(name);
202
+ if (!owner)
203
+ return { exists: false, owner: 'global' };
204
+ return {
205
+ exists: true,
206
+ value: owner === 'global' ? this.letBindings.get(name) : owner.bindings.get(name),
207
+ owner,
208
+ };
209
+ }
210
+ restoreBindingSnapshot(name, snapshot) {
211
+ if (!snapshot.exists) {
212
+ if (this.currentBindingFrame && this.currentBindingFrame.bindings.has(name)) {
213
+ this.currentBindingFrame.bindings.delete(name);
214
+ }
215
+ this.letBindings.delete(name);
216
+ return;
217
+ }
218
+ if (snapshot.owner === 'global') {
219
+ this.letBindings.set(name, snapshot.value);
220
+ return;
221
+ }
222
+ snapshot.owner.bindings.set(name, snapshot.value);
223
+ }
224
+ shouldEmitLocalBindings() {
225
+ return this.currentBindingFrame === null;
93
226
  }
94
227
  execute(source, file = '<stdin>') {
95
228
  this.diagnostics = [];
96
229
  this.results = [];
97
230
  this.stdoutLines = [];
231
+ this.runtimeStepCount = 0;
232
+ this.runtimeCallCount = 0;
98
233
  const parser = new parser_1.Parser(file);
99
234
  const program = parser.parse(source);
100
235
  this.diagnostics.push(...parser.diagnostics);
@@ -110,21 +245,7 @@ class Interpreter {
110
245
  results: [],
111
246
  };
112
247
  }
113
- for (const stmt of program.statements) {
114
- try {
115
- this.executeStatement(stmt);
116
- }
117
- catch (e) {
118
- const message = e instanceof Error ? e.message : String(e);
119
- this.diagnostics.push({
120
- severity: 'error',
121
- message: message || 'Error de runtime',
122
- file,
123
- line: stmt.source.line,
124
- column: stmt.source.column,
125
- });
126
- }
127
- }
248
+ this.executeStatementsIterative(program.statements, file);
128
249
  const hasErrors = this.diagnostics.some((d) => d.severity === 'error');
129
250
  return {
130
251
  stdout: this.stdoutLines.join('\n'),
@@ -145,6 +266,8 @@ class Interpreter {
145
266
  this.diagnostics = [...parser.diagnostics];
146
267
  this.results = [];
147
268
  this.stdoutLines = [];
269
+ this.runtimeStepCount = 0;
270
+ this.runtimeCallCount = 0;
148
271
  if (parser.diagnostics.some((d) => d.severity === 'error')) {
149
272
  return {
150
273
  stdout: '',
@@ -154,18 +277,7 @@ class Interpreter {
154
277
  results: [],
155
278
  };
156
279
  }
157
- for (const stmt of program.statements) {
158
- try {
159
- this.executeStatement(stmt);
160
- }
161
- catch (e) {
162
- const message = e instanceof Error ? e.message : String(e);
163
- this.diagnostics.push({
164
- severity: 'error',
165
- message: message || 'Error de runtime',
166
- });
167
- }
168
- }
280
+ this.executeStatementsIterative(program.statements, '<repl>');
169
281
  return {
170
282
  stdout: this.stdoutLines.join('\n'),
171
283
  stderr: this.diagnostics
@@ -261,9 +373,268 @@ class Interpreter {
261
373
  return this.execExportDecl(stmt);
262
374
  }
263
375
  }
376
+ executeStatementsIterative(statements, fallbackFile) {
377
+ const runtimeStack = [{ kind: 'statements', statements, index: 0 }];
378
+ while (runtimeStack.length > 0) {
379
+ if (this.returnSignal)
380
+ break;
381
+ const frame = runtimeStack[runtimeStack.length - 1];
382
+ if (frame.kind === 'statements') {
383
+ if (frame.index >= frame.statements.length) {
384
+ runtimeStack.pop();
385
+ continue;
386
+ }
387
+ const stmt = frame.statements[frame.index++];
388
+ try {
389
+ this.dispatchRuntimeStatement(stmt, runtimeStack);
390
+ }
391
+ catch (e) {
392
+ const message = e instanceof Error ? e.message : String(e);
393
+ this.diagnostics.push({
394
+ severity: this.isSafetyLimitMessage(message || '') ? 'warning' : 'error',
395
+ message: message || 'Error de runtime',
396
+ file: stmt.source.file || fallbackFile,
397
+ line: stmt.source.line,
398
+ column: stmt.source.column,
399
+ });
400
+ }
401
+ continue;
402
+ }
403
+ if (frame.kind === 'for') {
404
+ if (this.returnSignal || frame.index >= frame.stmt.items.length) {
405
+ this.restoreBindingSnapshot(frame.stmt.variable, frame.savedBinding);
406
+ runtimeStack.pop();
407
+ continue;
408
+ }
409
+ const resolved = this.evaluateFormulaValue(frame.stmt.items[frame.index]);
410
+ this.setBinding(frame.stmt.variable, resolved);
411
+ frame.index++;
412
+ runtimeStack.push({ kind: 'statements', statements: frame.stmt.body, index: 0 });
413
+ continue;
414
+ }
415
+ if (this.returnSignal) {
416
+ runtimeStack.pop();
417
+ continue;
418
+ }
419
+ const profile = this.requireProfile();
420
+ const maxIter = frame.stmt.maxIterations || 1000;
421
+ if (frame.iterations >= maxIter) {
422
+ this.diagnostics.push({
423
+ severity: 'warning',
424
+ message: `while: se alcanzó el límite de ${maxIter} iteraciones`,
425
+ file: frame.stmt.source.file,
426
+ line: frame.stmt.source.line,
427
+ column: frame.stmt.source.column,
428
+ });
429
+ runtimeStack.pop();
430
+ continue;
431
+ }
432
+ const resolved = this.evaluateFormulaValue(frame.stmt.formula);
433
+ const matched = this.matchesRuntimeCondition(profile, frame.stmt.condition, resolved);
434
+ if (!matched) {
435
+ runtimeStack.pop();
436
+ continue;
437
+ }
438
+ frame.iterations++;
439
+ runtimeStack.push({ kind: 'statements', statements: frame.stmt.body, index: 0 });
440
+ }
441
+ }
442
+ dispatchRuntimeStatement(stmt, runtimeStack) {
443
+ if (this.returnSignal)
444
+ return;
445
+ this.tickRuntimeStep();
446
+ const directCall = this.getDirectFunctionCall(stmt);
447
+ if (directCall) {
448
+ const result = this.executeFnCall(directCall.call);
449
+ this.applyFunctionContinuation({ type: directCall.type, stmt: directCall.stmt }, result);
450
+ return;
451
+ }
452
+ if (this.isImporting) {
453
+ const sideEffects = [
454
+ 'derive_cmd',
455
+ 'check_valid_cmd',
456
+ 'check_satisfiable_cmd',
457
+ 'check_equivalent_cmd',
458
+ 'prove_cmd',
459
+ 'countermodel_cmd',
460
+ 'truth_table_cmd',
461
+ 'print_cmd',
462
+ 'analyze_cmd',
463
+ 'explain_cmd',
464
+ 'render_cmd',
465
+ ];
466
+ if (sideEffects.includes(stmt.kind) || stmt.kind === 'logic_decl')
467
+ return;
468
+ }
469
+ switch (stmt.kind) {
470
+ case 'if_stmt': {
471
+ const selectedBody = this.selectIfBody(stmt);
472
+ if (selectedBody)
473
+ runtimeStack.push({ kind: 'statements', statements: selectedBody, index: 0 });
474
+ return;
475
+ }
476
+ case 'for_stmt':
477
+ runtimeStack.push({
478
+ kind: 'for',
479
+ stmt,
480
+ index: 0,
481
+ savedBinding: this.captureBindingSnapshot(stmt.variable),
482
+ });
483
+ return;
484
+ case 'while_stmt':
485
+ runtimeStack.push({ kind: 'while', stmt, iterations: 0 });
486
+ return;
487
+ case 'logic_decl':
488
+ return this.execLogicDecl(stmt);
489
+ case 'axiom_decl':
490
+ return this.execAxiomDecl(stmt);
491
+ case 'theorem_decl':
492
+ return this.execTheoremDecl(stmt);
493
+ case 'derive_cmd':
494
+ return this.execDeriveCmd(stmt);
495
+ case 'check_valid_cmd':
496
+ return this.execCheckValidCmd(stmt);
497
+ case 'check_satisfiable_cmd':
498
+ return this.execCheckSatisfiableCmd(stmt);
499
+ case 'check_equivalent_cmd':
500
+ return this.execCheckEquivalentCmd(stmt);
501
+ case 'prove_cmd':
502
+ return this.execProveCmd(stmt);
503
+ case 'countermodel_cmd':
504
+ return this.execCountermodelCmd(stmt);
505
+ case 'truth_table_cmd':
506
+ return this.execTruthTableCmd(stmt);
507
+ case 'let_decl':
508
+ return this.execLetDecl(stmt);
509
+ case 'claim_decl':
510
+ return this.execClaimDecl(stmt);
511
+ case 'support_decl':
512
+ return this.execSupportDecl(stmt);
513
+ case 'confidence_decl':
514
+ return this.execConfidenceDecl(stmt);
515
+ case 'context_decl':
516
+ return this.execContextDecl(stmt);
517
+ case 'render_cmd':
518
+ return this.execRenderCmd(stmt);
519
+ case 'analyze_cmd':
520
+ return this.execAnalyzeCmd(stmt);
521
+ case 'explain_cmd':
522
+ return this.execExplainCmd(stmt);
523
+ case 'import_decl':
524
+ return this.execImportDecl(stmt);
525
+ case 'proof_block':
526
+ return this.execProofBlock(stmt);
527
+ case 'theory_decl':
528
+ return this.execTheoryDecl(stmt);
529
+ case 'print_cmd':
530
+ return this.execPrintCmd(stmt);
531
+ case 'set_cmd':
532
+ return this.execSetCmd(stmt);
533
+ case 'fn_decl':
534
+ return this.execFnDecl(stmt);
535
+ case 'return_stmt':
536
+ return this.execReturnStmt(stmt);
537
+ case 'fn_call':
538
+ this.executeFnCall(stmt);
539
+ return;
540
+ case 'export_decl':
541
+ return this.execExportDecl(stmt);
542
+ }
543
+ }
544
+ selectIfBody(stmt) {
545
+ const profile = this.requireProfile();
546
+ for (const branch of stmt.branches) {
547
+ const resolved = this.evaluateFormulaValue(branch.formula);
548
+ const matched = this.matchesRuntimeCondition(profile, branch.condition, resolved);
549
+ if (matched)
550
+ return branch.body;
551
+ }
552
+ return stmt.elseBranch;
553
+ }
554
+ matchesRuntimeCondition(profile, condition, formula) {
555
+ if (profile.name === 'arithmetic') {
556
+ const numeric = (0, arithmetic_1.evalNumeric)(formula);
557
+ const truthy = !Number.isNaN(numeric) && numeric !== 0;
558
+ if (condition === 'valid' || condition === 'satisfiable')
559
+ return truthy;
560
+ return !truthy;
561
+ }
562
+ if (condition === 'valid' || condition === 'invalid') {
563
+ const result = profile.checkValid(formula);
564
+ return condition === 'valid' ? result.status === 'valid' : result.status !== 'valid';
565
+ }
566
+ const result = profile.checkSatisfiable(formula);
567
+ return condition === 'satisfiable'
568
+ ? result.status === 'satisfiable' || result.status === 'valid'
569
+ : result.status === 'unsatisfiable';
570
+ }
571
+ evaluateFormulaValue(formula) {
572
+ if (formula.kind === 'fn_call' && formula.name) {
573
+ const result = this.executeFnCall({ name: formula.name, args: formula.args || [] });
574
+ return result || { kind: 'atom', name: 'undefined', source: formula.source };
575
+ }
576
+ return this.resolveFormula(formula);
577
+ }
578
+ getDirectFunctionCall(stmt) {
579
+ if (stmt.kind === 'fn_call') {
580
+ return { call: { name: stmt.name, args: stmt.args }, type: 'discard' };
581
+ }
582
+ if (stmt.kind === 'let_decl' && stmt.letType === 'formula' && stmt.formula.kind === 'fn_call') {
583
+ return {
584
+ call: { name: stmt.formula.name || '', args: stmt.formula.args || [] },
585
+ type: 'let_decl',
586
+ stmt,
587
+ };
588
+ }
589
+ if (stmt.kind === 'set_cmd' && stmt.formula.kind === 'fn_call') {
590
+ return {
591
+ call: { name: stmt.formula.name || '', args: stmt.formula.args || [] },
592
+ type: 'set_cmd',
593
+ stmt,
594
+ };
595
+ }
596
+ if (stmt.kind === 'return_stmt' && stmt.formula?.kind === 'fn_call') {
597
+ return {
598
+ call: { name: stmt.formula.name || '', args: stmt.formula.args || [] },
599
+ type: 'return_stmt',
600
+ stmt,
601
+ };
602
+ }
603
+ if (stmt.kind === 'print_cmd' && stmt.formula?.kind === 'fn_call') {
604
+ return {
605
+ call: { name: stmt.formula.name || '', args: stmt.formula.args || [] },
606
+ type: 'print_cmd',
607
+ stmt,
608
+ };
609
+ }
610
+ return undefined;
611
+ }
612
+ applyFunctionContinuation(continuation, result) {
613
+ const normalized = result || { kind: 'atom', name: 'undefined' };
614
+ switch (continuation.type) {
615
+ case 'discard':
616
+ return;
617
+ case 'let_decl': {
618
+ const letStmt = continuation.stmt;
619
+ if (letStmt.letType === 'formula') {
620
+ return this.execLetDecl({ ...letStmt, formula: normalized });
621
+ }
622
+ return;
623
+ }
624
+ case 'set_cmd':
625
+ return this.execSetCmd({ ...continuation.stmt, formula: normalized });
626
+ case 'return_stmt':
627
+ return this.execReturnStmt({
628
+ ...continuation.stmt,
629
+ formula: normalized,
630
+ });
631
+ case 'print_cmd':
632
+ return this.execPrintCmd({ ...continuation.stmt, formula: normalized });
633
+ }
634
+ }
264
635
  execExportDecl(stmt) {
265
636
  // Ejecutar la declaración interna
266
- this.executeStatement(stmt.statement);
637
+ this.executeStatementsIterative([stmt.statement], stmt.source.file);
267
638
  // Registrarla como exportada
268
639
  const s = stmt.statement;
269
640
  switch (s.kind) {
@@ -299,32 +670,56 @@ class Interpreter {
299
670
  * por sus fórmulas definidas. Detecta ciclos para evitar recursión infinita.
300
671
  * Soporta notación con punto: Theory.member resuelve desde el scope de la teoría.
301
672
  */
673
+ invalidateResolveCache() {
674
+ this.resolveCache = new WeakMap();
675
+ this.resolveCacheGeneration++;
676
+ }
302
677
  resolveFormula(f, visited = new Set()) {
678
+ // Check resolution cache first
679
+ const cached = this.resolveCache.get(f);
680
+ if (cached !== undefined)
681
+ return cached;
682
+ const resolved = this.resolveFormulaRecursive(f, visited);
683
+ // Constant-fold pure arithmetic (add, subtract, etc.) but NOT comparisons
684
+ // so profiles can still see the structural form of comparisons.
685
+ const folded = this.tryConstantFold(resolved);
686
+ // Cache the result and deduplicate via FormulaFactory
687
+ const result = formula_factory_1.FormulaFactory.create(folded);
688
+ this.resolveCache.set(f, result);
689
+ return result;
690
+ }
691
+ resolveFormulaRecursive(f, visited) {
303
692
  if (!f)
304
693
  return f;
305
694
  // Si es un átomo, intentar resolver
306
695
  if (f.kind === 'atom' && f.name) {
696
+ if (this.hasBinding(f.name)) {
697
+ if (visited.has(f.name))
698
+ return f;
699
+ visited.add(f.name);
700
+ const result = this.resolveFormulaRecursive(this.getBinding(f.name), visited);
701
+ visited.delete(f.name);
702
+ return result;
703
+ }
307
704
  // Dot notation: Theory.member o instance.member
308
705
  if (f.name.includes('.')) {
309
706
  const [prefix, memberName] = f.name.split('.', 2);
310
707
  // 1. Intentar resolver el prefijo como una variable local (instancia)
311
- // Ej: let f1 = Familia("Socrates") -> f1.amor
312
- if (this.letBindings.has(prefix)) {
313
- const resolvedPrefix = this.letBindings.get(prefix);
708
+ if (this.hasBinding(prefix)) {
709
+ const resolvedPrefix = this.getBinding(prefix);
314
710
  if (resolvedPrefix.kind === 'atom' && resolvedPrefix.name) {
315
- // Si el prefijo se resuelve a un nombre de teoría/instancia
316
711
  const actualInstanceName = resolvedPrefix.name;
317
712
  const scope = this.theories.get(actualInstanceName);
318
713
  if (scope) {
319
714
  if (scope.privateMembers.has(memberName) &&
320
715
  this.currentTheoryName !== actualInstanceName)
321
716
  return f;
322
- if (scope.letBindings.has(memberName))
323
- return this.resolveFormula(scope.letBindings.get(memberName), new Set(visited));
324
- if (scope.axioms.has(memberName))
325
- return this.resolveFormula(scope.axioms.get(memberName), new Set(visited));
326
- if (scope.theorems.has(memberName))
327
- return this.resolveFormula(scope.theorems.get(memberName), new Set(visited));
717
+ const member = scope.letBindings.get(memberName) ??
718
+ scope.axioms.get(memberName) ??
719
+ scope.theorems.get(memberName);
720
+ if (member) {
721
+ return this.resolveFormulaRecursive(member, visited);
722
+ }
328
723
  }
329
724
  }
330
725
  }
@@ -333,35 +728,31 @@ class Interpreter {
333
728
  if (scope) {
334
729
  if (scope.privateMembers.has(memberName) && this.currentTheoryName !== prefix)
335
730
  return f;
336
- if (scope.letBindings.has(memberName))
337
- return this.resolveFormula(scope.letBindings.get(memberName), new Set(visited));
338
- if (scope.axioms.has(memberName))
339
- return this.resolveFormula(scope.axioms.get(memberName), new Set(visited));
340
- if (scope.theorems.has(memberName))
341
- return this.resolveFormula(scope.theorems.get(memberName), new Set(visited));
731
+ const member = scope.letBindings.get(memberName) ??
732
+ scope.axioms.get(memberName) ??
733
+ scope.theorems.get(memberName);
734
+ if (member) {
735
+ return this.resolveFormulaRecursive(member, visited);
736
+ }
342
737
  }
343
738
  return f;
344
739
  }
345
- // Binding local normal
346
- if (this.letBindings.has(f.name)) {
347
- if (visited.has(f.name)) {
348
- return f;
349
- }
350
- visited.add(f.name);
351
- return this.resolveFormula(this.letBindings.get(f.name), new Set(visited));
352
- }
353
740
  // También resolver axiomas/teoremas del theory actual por nombre
354
741
  if (this.theory.axioms.has(f.name)) {
355
742
  if (visited.has(f.name))
356
743
  return f;
357
744
  visited.add(f.name);
358
- return this.resolveFormula(this.theory.axioms.get(f.name), new Set(visited));
745
+ const result = this.resolveFormulaRecursive(this.theory.axioms.get(f.name), visited);
746
+ visited.delete(f.name);
747
+ return result;
359
748
  }
360
749
  if (this.theory.theorems.has(f.name)) {
361
750
  if (visited.has(f.name))
362
751
  return f;
363
752
  visited.add(f.name);
364
- return this.resolveFormula(this.theory.theorems.get(f.name), new Set(visited));
753
+ const result = this.resolveFormulaRecursive(this.theory.theorems.get(f.name), visited);
754
+ visited.delete(f.name);
755
+ return result;
365
756
  }
366
757
  }
367
758
  // Llamada a función como expresión
@@ -371,12 +762,16 @@ class Interpreter {
371
762
  }
372
763
  // Recorrer hijos recursivamente
373
764
  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 };
765
+ const newArgs = f.args.map((a) => (a ? this.resolveFormulaRecursive(a, visited) : a));
766
+ // Check if args actually changed to avoid unnecessary object creation
767
+ let changed = false;
768
+ for (let i = 0; i < f.args.length; i++) {
769
+ if (f.args[i] !== newArgs[i]) {
770
+ changed = true;
771
+ break;
772
+ }
379
773
  }
774
+ return changed ? { ...f, args: newArgs } : f;
380
775
  }
381
776
  return f;
382
777
  }
@@ -453,10 +848,60 @@ class Interpreter {
453
848
  }
454
849
  execTruthTableCmd(stmt) {
455
850
  const profile = this.requireProfile();
851
+ const formula = this.resolveFormula(stmt.formula);
852
+ if (profile.name === 'classical.propositional') {
853
+ const atoms = Array.from((0, propositional_1.collectAtoms)(formula)).sort();
854
+ // Streaming de tabla de verdad para evitar OOM
855
+ this.emit(`Tabla de verdad para ${(0, propositional_1.formulaToString)(formula)}:`);
856
+ this.emit(` ${atoms.join(' | ')} | Resultado`);
857
+ this.emit(` ${atoms.map(() => '---').join('-|-')}-|----------`);
858
+ let isTautology = true;
859
+ let isSatisfiable = false;
860
+ let count = 0;
861
+ let satCount = 0;
862
+ for (const v of (0, propositional_1.generateValuationsLazy)(atoms)) {
863
+ const res = (0, propositional_1.evaluateClassical)(formula, v);
864
+ if (res) {
865
+ isSatisfiable = true;
866
+ satCount++;
867
+ }
868
+ else {
869
+ isTautology = false;
870
+ }
871
+ count++;
872
+ // Solo imprimir las primeras 64 filas para no saturar el stdout en tablas masivas
873
+ if (count <= 64) {
874
+ const row = atoms.map((a) => (v[a] ? 'V' : 'F')).join(' | ');
875
+ this.emit(` ${row} | ${res ? 'V' : 'F'}`);
876
+ }
877
+ else if (count === 65) {
878
+ this.emit(' ... (tabla masiva, ocultando filas intermedias) ...');
879
+ }
880
+ }
881
+ this.emit(`\nResumen: ${count} valuaciones analizadas.`);
882
+ this.emit(`Estatus: ${isTautology ? 'Tautología ✓' : isSatisfiable ? 'Satisfacible' : 'Contradicción ✗'}`);
883
+ this.results.push({
884
+ status: isTautology ? 'valid' : isSatisfiable ? 'satisfiable' : 'unsatisfiable',
885
+ output: `Tabla de verdad de ${count} filas procesada.`,
886
+ truthTable: {
887
+ variables: atoms,
888
+ rows: [],
889
+ subFormulas: [],
890
+ subFormulaValues: [],
891
+ isTautology,
892
+ isContradiction: !isSatisfiable,
893
+ isSatisfiable,
894
+ satisfyingCount: satCount,
895
+ },
896
+ diagnostics: [],
897
+ formula: formula,
898
+ });
899
+ return;
900
+ }
901
+ // Fallback para otros perfiles
456
902
  if (!profile.truthTable) {
457
903
  throw new Error('Este perfil no soporta truth_table');
458
904
  }
459
- const formula = this.resolveFormula(stmt.formula);
460
905
  const tt = profile.truthTable(formula);
461
906
  const result = {
462
907
  status: tt.isTautology ? 'valid' : tt.isSatisfiable ? 'satisfiable' : 'unsatisfiable',
@@ -482,24 +927,37 @@ class Interpreter {
482
927
  this.emit(`Formalizacion ${stmt.name}: ${stmt.passageName} -> ${(0, propositional_1.formulaToString)(formula)}`);
483
928
  }
484
929
  else if (stmt.letType === 'description') {
485
- // Solo descripción textual: P es un átomo con significado semántico
486
- this.letDescriptions.set(stmt.name, stmt.description);
487
- this.emit(`Let ${stmt.name} = "${stmt.description}"`);
930
+ if (this.currentBindingFrame)
931
+ this.currentBindingFrame.descriptions.set(stmt.name, stmt.description);
932
+ else
933
+ this.letDescriptions.set(stmt.name, stmt.description);
934
+ if (this.shouldEmitLocalBindings())
935
+ this.emit(`Let ${stmt.name} = "${stmt.description}"`);
936
+ }
937
+ else if (stmt.letType === 'action') {
938
+ const actionStmt = stmt;
939
+ const captured = this.executeActionExpr(actionStmt.action);
940
+ this.results.push(captured.result);
941
+ this.bindCapturedAction(actionStmt.name, actionStmt.action.action, captured.primary, captured.result, captured.formulas, captured.extraBindings);
942
+ if (this.shouldEmitLocalBindings()) {
943
+ this.emit(`Let ${actionStmt.name} = ${(0, format_1.formulaToUnicode)(captured.primary)}`);
944
+ }
488
945
  }
489
946
  else if (stmt.letType === 'formula' && stmt.formula) {
490
- // Resolver posibles variables anidadas en la propia definición
491
- const resolved = this.resolveFormula(stmt.formula);
492
- // Registrar como binding para sustitución futura
493
- this.letBindings.set(stmt.name, resolved);
494
- // También como axioma implícito para derivaciones
495
- this.theory.axioms.set(stmt.name, resolved);
496
- // Si tiene descripción textual (let X = "desc" : formula), guardarla
947
+ // Resolve bindings but preserve symbolic structure (no constant-folding)
948
+ const resolved = this.resolveFormulaRecursive(stmt.formula, new Set());
949
+ this.defineBinding(stmt.name, resolved, 'description' in stmt ? stmt.description : undefined);
950
+ if (!this.currentBindingFrame)
951
+ this.theory.axioms.set(stmt.name, resolved);
497
952
  if ('description' in stmt && stmt.description) {
498
- this.letDescriptions.set(stmt.name, stmt.description);
499
- this.emit(`Let ${stmt.name} = "${stmt.description}" : ${(0, format_1.formulaToUnicode)(resolved)}`);
953
+ if (!this.currentBindingFrame)
954
+ this.letDescriptions.set(stmt.name, stmt.description);
955
+ if (this.shouldEmitLocalBindings())
956
+ this.emit(`Let ${stmt.name} = "${stmt.description}" : ${(0, format_1.formulaToUnicode)(resolved)}`);
500
957
  }
501
958
  else {
502
- this.emit(`Let ${stmt.name} = ${(0, format_1.formulaToUnicode)(resolved)}`);
959
+ if (this.shouldEmitLocalBindings())
960
+ this.emit(`Let ${stmt.name} = ${(0, format_1.formulaToUnicode)(resolved)}`);
503
961
  }
504
962
  }
505
963
  }
@@ -508,7 +966,6 @@ class Interpreter {
508
966
  const formalization = stmt.formalization;
509
967
  const diags = (0, compiler_1.registerClaim)(this.textLayer, stmt.name, formula, formalization);
510
968
  this.diagnostics.push(...diags);
511
- // También agregar al theory.claims
512
969
  const claim = {
513
970
  name: stmt.name,
514
971
  formula: formula,
@@ -533,7 +990,6 @@ class Interpreter {
533
990
  this.emit(`Context: ${stmt.claimName} = "${stmt.text}"`);
534
991
  }
535
992
  execRenderCmd(stmt) {
536
- // Compilar claims y renderizar
537
993
  const diags = (0, compiler_1.compileClaimsToTheory)(this.textLayer, this.theory);
538
994
  this.diagnostics.push(...diags);
539
995
  if (stmt.target === 'claims' || stmt.target === 'all') {
@@ -541,19 +997,15 @@ class Interpreter {
541
997
  for (const [name, claim] of this.theory.claims) {
542
998
  const fStr = claim.formula ? (0, format_1.formulaToUnicode)(claim.formula) : '(sin fórmula)';
543
999
  this.emit(` Claim "${name}": ${fStr}`);
544
- if (claim.support) {
1000
+ if (claim.support)
545
1001
  this.emit(` Soporte: ${claim.support}`);
546
- }
547
- if (claim.confidence !== undefined) {
1002
+ if (claim.confidence !== undefined)
548
1003
  this.emit(` Confianza: ${claim.confidence}`);
549
- }
550
- if (claim.context) {
1004
+ if (claim.context)
551
1005
  this.emit(` Contexto: ${claim.context}`);
552
- }
553
1006
  }
554
- if (this.theory.claims.size === 0) {
1007
+ if (this.theory.claims.size === 0)
555
1008
  this.emit(' (sin claims registrados)');
556
- }
557
1009
  }
558
1010
  else if (stmt.target === 'theory') {
559
1011
  this.emit(`── Render: theory (${stmt.format}) ──`);
@@ -569,7 +1021,6 @@ class Interpreter {
569
1021
  this.emit(` Claims: ${this.theory.claims.size}`);
570
1022
  }
571
1023
  else {
572
- // Render un claim o axioma específico por nombre
573
1024
  const axiom = this.theory.axioms.get(stmt.target);
574
1025
  if (axiom) {
575
1026
  this.emit(` ${stmt.target} = ${(0, format_1.formulaToUnicode)(axiom)}`);
@@ -592,7 +1043,6 @@ class Interpreter {
592
1043
  const pStr = premises.map((p) => (0, format_1.formulaToUnicode)(p)).join(', ');
593
1044
  const cStr = (0, format_1.formulaToUnicode)(conclusion);
594
1045
  if (fallacies.length === 0) {
595
- // Check if the inference is valid
596
1046
  const conj = premises.length === 0
597
1047
  ? conclusion
598
1048
  : premises.length === 1
@@ -608,13 +1058,12 @@ class Interpreter {
608
1058
  this.emit(`⚠ [analyze] {${pStr}} → ${cStr}`);
609
1059
  this.emit(' Inferencia NO VÁLIDA — pero no corresponde a un patrón de falacia conocido');
610
1060
  }
611
- const result2 = {
1061
+ this.results.push({
612
1062
  status: result.status,
613
1063
  output: result.output,
614
1064
  diagnostics: [],
615
1065
  formula: conclusion,
616
- };
617
- this.results.push(result2);
1066
+ });
618
1067
  }
619
1068
  else {
620
1069
  this.emit(`⚠ [analyze] {${pStr}} → ${cStr}`);
@@ -624,7 +1073,7 @@ class Interpreter {
624
1073
  if (f.pattern)
625
1074
  this.emit(` Patrón: ${f.pattern}`);
626
1075
  }
627
- const result = {
1076
+ this.results.push({
628
1077
  status: 'invalid',
629
1078
  output: `Falacias detectadas: ${fallacies.map((f) => f.name).join(', ')}`,
630
1079
  diagnostics: fallacies.map((f) => ({
@@ -632,17 +1081,14 @@ class Interpreter {
632
1081
  message: `Falacia: ${f.name} — ${f.description}`,
633
1082
  })),
634
1083
  formula: conclusion,
635
- };
636
- this.results.push(result);
1084
+ });
637
1085
  }
638
1086
  }
639
1087
  execProofBlock(stmt) {
640
1088
  const profile = this.requireProfile();
641
- // Guardar axiomas, letBindings y descriptions antes del bloque
642
1089
  const savedAxioms = new Map(this.theory.axioms);
643
1090
  const savedLetBindings = new Map(this.letBindings);
644
1091
  const savedLetDescriptions = new Map(this.letDescriptions);
645
- // Registrar las asunciones como axiomas temporales (con resolución de variables)
646
1092
  this.emit('── Proof Block ──');
647
1093
  for (const assumption of stmt.assumptions) {
648
1094
  const resolved = this.resolveFormula(assumption.formula);
@@ -651,64 +1097,38 @@ class Interpreter {
651
1097
  }
652
1098
  const resolvedGoal = this.resolveFormula(stmt.goal);
653
1099
  this.emit(` show ${(0, format_1.formulaToUnicode)(resolvedGoal)}`);
654
- // Ejecutar body statements
655
- for (const bodyStmt of stmt.body) {
656
- try {
657
- this.executeStatement(bodyStmt);
658
- }
659
- catch (e) {
660
- const message = e instanceof Error ? e.message : String(e);
661
- this.diagnostics.push({
662
- severity: 'error',
663
- message,
664
- file: stmt.source.file,
665
- line: bodyStmt.source.line,
666
- column: bodyStmt.source.column,
667
- });
668
- }
669
- }
670
- // Verificar que el goal es derivable de las asunciones
1100
+ this.executeStatementsIterative(stmt.body, stmt.source.file);
671
1101
  const premiseNames = stmt.assumptions.map((a) => a.name);
672
1102
  const result = profile.derive(resolvedGoal, premiseNames, this.theory);
673
1103
  this.results.push(result);
674
1104
  if (result.status === 'valid' || result.status === 'provable') {
675
1105
  this.emit(` ✓ QED — ${(0, format_1.formulaToUnicode)(resolvedGoal)} demostrado`);
676
- // Registrar como teorema
677
1106
  const theoremName = `proof_${this.theory.theorems.size + 1}`;
678
- // La implicación assumptions -> goal es un teorema
679
1107
  let implication = resolvedGoal;
680
1108
  for (let i = stmt.assumptions.length - 1; i >= 0; i--) {
681
- implication = {
682
- kind: 'implies',
683
- args: [stmt.assumptions[i].formula, implication],
684
- };
1109
+ implication = { kind: 'implies', args: [stmt.assumptions[i].formula, implication] };
685
1110
  }
686
1111
  this.theory.theorems.set(theoremName, implication);
687
1112
  }
688
1113
  else {
689
1114
  this.emit(` ✗ QED fallido — no se pudo demostrar ${(0, format_1.formulaToUnicode)(resolvedGoal)}`);
690
1115
  }
691
- // Restaurar axiomas, letBindings y descriptions (quitar las asunciones temporales)
692
1116
  this.theory.axioms = savedAxioms;
693
1117
  this.letBindings = savedLetBindings;
694
1118
  this.letDescriptions = savedLetDescriptions;
1119
+ this.invalidateResolveCache();
695
1120
  this.emit('── End Proof Block ──');
696
1121
  }
697
1122
  execTheoryDecl(stmt) {
698
- const theoryName = stmt.name;
699
- // Si tiene parámetros, es una plantilla (Clase)
700
1123
  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`);
1124
+ this.theoryTemplates.set(stmt.name, { node: stmt, parent: stmt.parent });
1125
+ this.emit(`Teoría (plantilla) ${stmt.name}(${stmt.params.join(', ')}) declarada`);
703
1126
  return;
704
1127
  }
705
- // Si no tiene parámetros, ejecutar como singleton/objeto inmediato
706
1128
  this.instantiateTheory(stmt);
707
1129
  }
708
- /** Crea una instancia de una teoría y la registra en this.theories */
709
1130
  instantiateTheory(node, instanceName, args = []) {
710
1131
  const theoryName = instanceName || node.name;
711
- // Crear scope vacío
712
1132
  const scope = {
713
1133
  name: theoryName,
714
1134
  parent: node.parent,
@@ -718,14 +1138,10 @@ class Interpreter {
718
1138
  theorems: new Map(),
719
1139
  privateMembers: new Set(),
720
1140
  };
721
- // HERENCIA: Si extends Parent, copiar bindings/axiomas/teoremas del padre
722
- // El padre puede ser otra plantilla o un singleton ya instanciado
723
1141
  if (node.parent) {
724
1142
  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)
1143
+ if (!parentScope)
1144
+ throw new Error(`Teoría padre '${node.parent}' no encontrada.`);
729
1145
  for (const [k, v] of parentScope.letBindings)
730
1146
  if (!parentScope.privateMembers.has(k))
731
1147
  scope.letBindings.set(k, v);
@@ -739,13 +1155,11 @@ class Interpreter {
739
1155
  if (!parentScope.privateMembers.has(k))
740
1156
  scope.theorems.set(k, v);
741
1157
  }
742
- // Guardar estado global antes de entrar al scope de la teoría
743
1158
  const savedLetBindings = new Map(this.letBindings);
744
1159
  const savedLetDescriptions = new Map(this.letDescriptions);
745
1160
  const savedAxioms = new Map(this.theory.axioms);
746
1161
  const savedTheorems = new Map(this.theory.theorems);
747
1162
  const savedTheoryName = this.currentTheoryName;
748
- // Inyectar argumentos en el scope local de la teoría
749
1163
  if (node.params && args.length > 0) {
750
1164
  for (let i = 0; i < node.params.length; i++) {
751
1165
  if (i < args.length) {
@@ -755,7 +1169,6 @@ class Interpreter {
755
1169
  }
756
1170
  }
757
1171
  }
758
- // Inyectar bindings heredados al scope local para que los statements internos los vean
759
1172
  for (const [k, v] of scope.letBindings)
760
1173
  this.letBindings.set(k, v);
761
1174
  for (const [k, v] of scope.letDescriptions)
@@ -766,7 +1179,6 @@ class Interpreter {
766
1179
  this.theory.theorems.set(k, v);
767
1180
  this.currentTheoryName = theoryName;
768
1181
  this.emit(`── Instanciando Theory ${theoryName} ──`);
769
- // Ejecutar los miembros del body
770
1182
  for (const member of node.members) {
771
1183
  const memberStmt = member.statement;
772
1184
  const memberName = 'name' in memberStmt ? memberStmt.name : null;
@@ -786,7 +1198,6 @@ class Interpreter {
786
1198
  });
787
1199
  }
788
1200
  }
789
- // Capturar lo producido
790
1201
  for (const [k, v] of this.letBindings)
791
1202
  if (!savedLetBindings.has(k))
792
1203
  scope.letBindings.set(k, v);
@@ -799,292 +1210,383 @@ class Interpreter {
799
1210
  for (const [k, v] of this.theory.theorems)
800
1211
  if (!savedTheorems.has(k))
801
1212
  scope.theorems.set(k, v);
802
- // Restaurar estado global
803
1213
  this.letBindings = savedLetBindings;
804
1214
  this.letDescriptions = savedLetDescriptions;
805
1215
  this.theory.axioms = savedAxioms;
806
1216
  this.theory.theorems = savedTheorems;
807
1217
  this.currentTheoryName = savedTheoryName;
808
- // Registrar la instancia
1218
+ this.invalidateResolveCache();
809
1219
  this.theories.set(theoryName, scope);
810
1220
  this.emit(`── End Theory Instance ${theoryName} ──`);
811
1221
  return theoryName;
812
1222
  }
813
- // =============================================
814
- // Control flow & funciones (v1.5.8)
815
- // =============================================
816
1223
  execPrintCmd(stmt) {
817
- if (stmt.value !== null) {
818
- // String literal
1224
+ if (stmt.value !== null)
819
1225
  this.emit(stmt.value);
820
- }
821
1226
  else if (stmt.formula) {
822
- const resolved = this.resolveFormula(stmt.formula);
1227
+ // Resolve bindings but skip constant-folding so print shows symbolic form
1228
+ const resolved = this.resolveFormulaRecursive(stmt.formula, new Set());
823
1229
  this.emit((0, format_1.formulaToUnicode)(resolved));
824
1230
  }
825
1231
  }
826
1232
  execSetCmd(stmt) {
827
- const resolved = this.resolveFormula(stmt.formula);
828
- this.letBindings.set(stmt.name, resolved);
829
- // También actualizar en la teoría global para que derive/prove lo vean
830
- this.theory.axioms.set(stmt.name, resolved);
831
- this.emit(`Set ${stmt.name} = ${(0, format_1.formulaToUnicode)(resolved)}`);
1233
+ const resolved = this.evaluateFormulaValue(stmt.formula);
1234
+ this.setBinding(stmt.name, resolved);
1235
+ if (!this.currentBindingFrame)
1236
+ this.theory.axioms.set(stmt.name, resolved);
1237
+ if (this.shouldEmitLocalBindings())
1238
+ this.emit(`Set ${stmt.name} = ${(0, format_1.formulaToUnicode)(resolved)}`);
832
1239
  }
833
1240
  execIfStmt(stmt) {
834
- const profile = this.requireProfile();
835
- for (const branch of stmt.branches) {
836
- const resolved = this.resolveFormula(branch.formula);
837
- let matched;
838
- if (branch.condition === 'valid' || branch.condition === 'invalid') {
839
- const result = profile.checkValid(resolved);
840
- matched =
841
- branch.condition === 'valid' ? result.status === 'valid' : result.status !== 'valid';
842
- }
843
- else {
844
- // satisfiable / unsatisfiable
845
- const result = profile.checkSatisfiable(resolved);
846
- matched =
847
- branch.condition === 'satisfiable'
848
- ? result.status === 'satisfiable' || result.status === 'valid'
849
- : result.status === 'unsatisfiable';
850
- }
851
- if (matched) {
852
- for (const bodyStmt of branch.body) {
853
- if (this.returnSignal)
854
- return;
855
- this.executeStatement(bodyStmt);
856
- }
857
- return; // solo ejecuta la primera rama que matchea
858
- }
859
- }
860
- // else branch
861
- if (stmt.elseBranch) {
862
- for (const bodyStmt of stmt.elseBranch) {
863
- if (this.returnSignal)
864
- return;
865
- this.executeStatement(bodyStmt);
866
- }
867
- }
1241
+ const selectedBody = this.selectIfBody(stmt);
1242
+ if (selectedBody)
1243
+ this.executeStatementsIterative(selectedBody, stmt.source.file);
868
1244
  }
869
1245
  execForStmt(stmt) {
870
- const savedBinding = this.letBindings.get(stmt.variable);
871
- for (const item of stmt.items) {
872
- if (this.returnSignal)
873
- break;
874
- const resolved = this.resolveFormula(item);
875
- this.letBindings.set(stmt.variable, resolved);
876
- for (const bodyStmt of stmt.body) {
877
- if (this.returnSignal)
878
- break;
879
- this.executeStatement(bodyStmt);
880
- }
881
- }
882
- // Restaurar binding previo
883
- if (savedBinding !== undefined) {
884
- this.letBindings.set(stmt.variable, savedBinding);
885
- }
886
- else {
887
- this.letBindings.delete(stmt.variable);
888
- }
1246
+ this.executeStatementsIterative([stmt], stmt.source.file);
889
1247
  }
890
1248
  execWhileStmt(stmt) {
891
- const profile = this.requireProfile();
892
- const maxIter = stmt.maxIterations || 1000;
893
- let iter = 0;
894
- while (iter < maxIter) {
895
- if (this.returnSignal)
896
- break;
897
- iter++;
898
- const resolved = this.resolveFormula(stmt.formula);
899
- let matched;
900
- if (stmt.condition === 'valid' || stmt.condition === 'invalid') {
901
- const result = profile.checkValid(resolved);
902
- matched =
903
- stmt.condition === 'valid' ? result.status === 'valid' : result.status !== 'valid';
904
- }
905
- else {
906
- const result = profile.checkSatisfiable(resolved);
907
- matched =
908
- stmt.condition === 'satisfiable'
909
- ? result.status === 'satisfiable' || result.status === 'valid'
910
- : result.status === 'unsatisfiable';
911
- }
912
- if (!matched)
913
- break;
914
- for (const bodyStmt of stmt.body) {
915
- if (this.returnSignal)
916
- break;
917
- this.executeStatement(bodyStmt);
918
- }
919
- }
920
- if (iter >= maxIter) {
921
- this.diagnostics.push({
922
- severity: 'warning',
923
- message: `while: se alcanzó el límite de ${maxIter} iteraciones`,
924
- file: stmt.source.file,
925
- line: stmt.source.line,
926
- column: stmt.source.column,
927
- });
928
- }
1249
+ this.executeStatementsIterative([stmt], stmt.source.file);
929
1250
  }
930
1251
  execFnDecl(stmt) {
931
1252
  const name = this.currentTheoryName ? `${this.currentTheoryName}.${stmt.name}` : stmt.name;
932
1253
  this.functions.set(name, stmt);
933
- if (this.currentTheoryName) {
1254
+ if (this.currentTheoryName)
934
1255
  this.emit(`Función de instancia ${name}(${stmt.params.join(', ')}) declarada`);
935
- }
936
- else {
1256
+ else
937
1257
  this.emit(`Función ${name}(${stmt.params.join(', ')}) declarada`);
938
- }
939
1258
  }
940
1259
  execReturnStmt(stmt) {
941
- if (stmt.formula) {
942
- this.returnValue = this.resolveFormula(stmt.formula);
943
- }
944
- else {
1260
+ if (stmt.formula)
1261
+ this.returnValue = this.evaluateFormulaValue(stmt.formula);
1262
+ else
945
1263
  this.returnValue = undefined;
946
- }
947
1264
  this.returnSignal = true;
948
1265
  }
949
1266
  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);
1267
+ const initial = this.startFunctionFrame(stmt, undefined);
1268
+ if ('result' in initial)
1269
+ return initial.result;
1270
+ const callStack = [initial.frame];
1271
+ let finalResult = undefined;
1272
+ while (callStack.length > 0) {
1273
+ const frame = callStack[callStack.length - 1];
1274
+ if (this.returnSignal) {
1275
+ const result = this.finishFunctionFrame(frame);
1276
+ callStack.pop();
1277
+ if (frame.continuation)
1278
+ this.applyFunctionContinuation(frame.continuation, result);
1279
+ else
1280
+ finalResult = result;
1281
+ continue;
1282
+ }
1283
+ const nextStmt = this.nextRuntimeStatement(frame.runtimeStack);
1284
+ if (!nextStmt) {
1285
+ const result = this.finishFunctionFrame(frame);
1286
+ callStack.pop();
1287
+ if (frame.continuation)
1288
+ this.applyFunctionContinuation(frame.continuation, result);
1289
+ else
1290
+ finalResult = result;
1291
+ continue;
1292
+ }
1293
+ const nestedCall = this.getDirectFunctionCall(nextStmt);
1294
+ if (nestedCall) {
1295
+ if (nestedCall.type === 'return_stmt') {
1296
+ const replaced = this.replaceFunctionFrame(frame, nestedCall.call);
1297
+ if ('result' in replaced) {
1298
+ this.returnValue = replaced.result || { kind: 'atom', name: 'undefined' };
1299
+ this.returnSignal = true;
1300
+ }
1301
+ else {
1302
+ callStack[callStack.length - 1] = replaced.frame;
1303
+ }
1304
+ continue;
1305
+ }
1306
+ const started = this.startFunctionFrame(nestedCall.call, {
1307
+ type: nestedCall.type,
1308
+ stmt: nestedCall.stmt,
1309
+ });
1310
+ if ('result' in started)
1311
+ this.applyFunctionContinuation(started.resultContinuation, started.result);
1312
+ else
1313
+ callStack.push(started.frame);
1314
+ continue;
1315
+ }
1316
+ this.dispatchRuntimeStatement(nextStmt, frame.runtimeStack);
1317
+ }
1318
+ return finalResult;
1319
+ }
1320
+ startFunctionFrame(stmt, continuation, inheritedReturnState) {
1321
+ this.callDepth++;
1322
+ if (this.callDepth > MAX_CALL_DEPTH) {
1323
+ this.callDepth--;
1324
+ throw new Error(`Límite de recursión excedido (${MAX_CALL_DEPTH}).`);
1325
+ }
1326
+ this.runtimeCallCount++;
1327
+ if (this.runtimeCallCount > this.getRuntimeCallLimit()) {
1328
+ this.callDepth--;
1329
+ throw new Error(`Límite de llamadas ST excedido (${this.getRuntimeCallLimit()}).`);
1330
+ }
1331
+ if ([
1332
+ 'typeof',
1333
+ 'is_valid',
1334
+ 'is_satisfiable',
1335
+ 'get_atoms',
1336
+ 'atoms_of',
1337
+ 'len',
1338
+ 'at',
1339
+ 'formula_eq',
1340
+ 'input',
1341
+ ].includes(stmt.name)) {
1342
+ const result = this.executeBuiltin(stmt.name, stmt.args);
1343
+ this.callDepth--;
1344
+ return { result, resultContinuation: continuation || { type: 'discard' } };
953
1345
  }
954
- // 0.5 Si es un método (obj.metodo), resolver el objeto primero
955
1346
  if (stmt.name.includes('.')) {
956
1347
  const [prefix, methodName] = stmt.name.split('.', 2);
957
1348
  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) {
1349
+ if (this.hasBinding(prefix)) {
1350
+ const resolved = this.getBinding(prefix);
1351
+ if (resolved.kind === 'atom' && resolved.name)
962
1352
  actualInstanceName = resolved.name;
963
- }
964
1353
  }
965
1354
  const scope = this.theories.get(actualInstanceName);
966
1355
  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
1356
  const internalFnName = `${actualInstanceName}.${methodName}`;
972
1357
  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);
976
- }
1358
+ if (fn)
1359
+ return {
1360
+ frame: this.createFunctionFrame(internalFnName, fn, stmt.args, continuation, scope, inheritedReturnState),
1361
+ };
977
1362
  }
978
1363
  }
979
- // 1. Intentar instanciación de teoría (Clase)
980
1364
  const template = this.theoryTemplates.get(stmt.name);
981
1365
  if (template) {
982
- // Nombre de instancia único si no estamos en un let (o usar un contador)
983
1366
  const instanceId = `inst_${stmt.name}_${this.theories.size}`;
984
1367
  this.instantiateTheory(template.node, instanceId, stmt.args);
985
- return { kind: 'atom', name: instanceId };
1368
+ this.callDepth--;
1369
+ return {
1370
+ result: { kind: 'atom', name: instanceId },
1371
+ resultContinuation: continuation || { type: 'discard' },
1372
+ };
986
1373
  }
987
- // 2. Intentar llamada a función normal
988
1374
  const fn = this.functions.get(stmt.name);
989
1375
  if (!fn) {
1376
+ this.callDepth--;
990
1377
  throw new Error(`Función o Teoría '${stmt.name}' no declarada`);
991
1378
  }
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
1379
+ return {
1380
+ frame: this.createFunctionFrame(stmt.name, fn, stmt.args, continuation, undefined, inheritedReturnState),
1381
+ };
1382
+ }
1383
+ createFunctionFrame(name, fn, args, continuation, scope, inheritedReturnState) {
1384
+ if (args.length !== fn.params.length) {
1385
+ this.callDepth--;
1386
+ throw new Error(`Argumentos incorrectos.`);
1387
+ }
1388
+ let scopeSnapshot;
1389
+ if (scope) {
1390
+ scopeSnapshot = {
1391
+ letBindings: new Map(this.letBindings),
1392
+ axioms: new Map(this.theory.axioms),
1393
+ theorems: new Map(this.theory.theorems),
1394
+ theoryName: this.currentTheoryName,
1395
+ };
1396
+ for (const [key, value] of scope.letBindings)
1397
+ this.letBindings.set(key, value);
1398
+ for (const [key, value] of scope.axioms)
1399
+ this.theory.axioms.set(key, value);
1400
+ for (const [key, value] of scope.theorems)
1401
+ this.theory.theorems.set(key, value);
1402
+ this.currentTheoryName = scope.name;
1403
+ }
1404
+ const bindingFrame = this.createBindingFrame();
1405
+ this.currentBindingFrame = bindingFrame;
1001
1406
  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);
1407
+ this.currentBindingFrame.bindings.set(fn.params[i], this.evaluateFormulaValue(args[i]));
1004
1408
  }
1005
- // Ejecutar cuerpo de la función
1006
- const savedReturnSignal = this.returnSignal;
1007
- const savedReturnValue = this.returnValue;
1409
+ const savedReturnSignal = inheritedReturnState?.signal ?? this.returnSignal;
1410
+ const savedReturnValue = inheritedReturnState?.value ?? this.returnValue;
1008
1411
  this.returnSignal = false;
1009
1412
  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
1413
+ return {
1414
+ name,
1415
+ runtimeStack: [{ kind: 'statements', statements: fn.body, index: 0 }],
1416
+ bindingFrame,
1417
+ savedReturnSignal,
1418
+ savedReturnValue,
1419
+ scopeSnapshot,
1420
+ continuation,
1421
+ };
1422
+ }
1423
+ finishFunctionFrame(frame) {
1016
1424
  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);
1425
+ this.returnSignal = frame.savedReturnSignal;
1426
+ this.returnValue = frame.savedReturnValue;
1427
+ this.discardFunctionFrameState(frame);
1428
+ this.callDepth--;
1429
+ return result;
1430
+ }
1431
+ discardFunctionFrameState(frame) {
1432
+ this.currentBindingFrame = frame.bindingFrame.parent;
1433
+ if (frame.scopeSnapshot) {
1434
+ this.letBindings = frame.scopeSnapshot.letBindings;
1435
+ this.theory.axioms = frame.scopeSnapshot.axioms;
1436
+ this.theory.theorems = frame.scopeSnapshot.theorems;
1437
+ this.currentTheoryName = frame.scopeSnapshot.theoryName;
1438
+ this.invalidateResolveCache();
1439
+ }
1440
+ }
1441
+ replaceFunctionFrame(frame, stmt) {
1442
+ const inheritedReturnState = {
1443
+ signal: frame.savedReturnSignal,
1444
+ value: frame.savedReturnValue,
1445
+ };
1446
+ const inheritedContinuation = frame.continuation;
1447
+ const evaluatedArgs = stmt.args.map((arg) => this.evaluateFormulaValue(arg));
1448
+ this.discardFunctionFrameState(frame);
1449
+ this.callDepth--;
1450
+ const replaced = this.startFunctionFrame({ name: stmt.name, args: evaluatedArgs }, inheritedContinuation, inheritedReturnState);
1451
+ if ('frame' in replaced)
1452
+ return replaced;
1453
+ return { result: replaced.result };
1454
+ }
1455
+ nextRuntimeStatement(runtimeStack) {
1456
+ while (runtimeStack.length > 0) {
1457
+ const frame = runtimeStack[runtimeStack.length - 1];
1458
+ if (frame.kind === 'statements') {
1459
+ if (frame.index >= frame.statements.length) {
1460
+ runtimeStack.pop();
1461
+ continue;
1462
+ }
1463
+ return frame.statements[frame.index++];
1024
1464
  }
1025
- else {
1026
- this.letBindings.delete(param);
1465
+ if (frame.kind === 'for') {
1466
+ if (this.returnSignal || frame.index >= frame.stmt.items.length) {
1467
+ this.restoreBindingSnapshot(frame.stmt.variable, frame.savedBinding);
1468
+ runtimeStack.pop();
1469
+ continue;
1470
+ }
1471
+ const resolved = this.evaluateFormulaValue(frame.stmt.items[frame.index]);
1472
+ this.setBinding(frame.stmt.variable, resolved);
1473
+ frame.index++;
1474
+ runtimeStack.push({ kind: 'statements', statements: frame.stmt.body, index: 0 });
1475
+ continue;
1027
1476
  }
1477
+ if (this.returnSignal) {
1478
+ runtimeStack.pop();
1479
+ continue;
1480
+ }
1481
+ const profile = this.requireProfile();
1482
+ const maxIter = frame.stmt.maxIterations || 1000;
1483
+ if (frame.iterations >= maxIter) {
1484
+ this.diagnostics.push({
1485
+ severity: 'warning',
1486
+ message: `while: se alcanzó el límite de ${maxIter} iteraciones`,
1487
+ file: frame.stmt.source.file,
1488
+ line: frame.stmt.source.line,
1489
+ column: frame.stmt.source.column,
1490
+ });
1491
+ runtimeStack.pop();
1492
+ continue;
1493
+ }
1494
+ const resolved = this.evaluateFormulaValue(frame.stmt.formula);
1495
+ const matched = this.matchesRuntimeCondition(profile, frame.stmt.condition, resolved);
1496
+ if (!matched) {
1497
+ runtimeStack.pop();
1498
+ continue;
1499
+ }
1500
+ frame.iterations++;
1501
+ runtimeStack.push({ kind: 'statements', statements: frame.stmt.body, index: 0 });
1028
1502
  }
1029
- return result;
1503
+ return undefined;
1030
1504
  }
1031
- /** Ejecuta una función inyectando bindings de un scope (para métodos de instancia) */
1032
1505
  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}`);
1506
+ this.callDepth++;
1507
+ if (this.callDepth > MAX_CALL_DEPTH) {
1508
+ this.callDepth--;
1509
+ throw new Error(`Límite de recursión excedido.`);
1035
1510
  }
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]));
1511
+ try {
1512
+ const savedBindings = new Map(this.letBindings);
1513
+ const savedAxioms = new Map(this.theory.axioms);
1514
+ const savedTheorems = new Map(this.theory.theorems);
1515
+ const savedTheoryName = this.currentTheoryName;
1516
+ for (const [k, v] of scope.letBindings)
1517
+ this.letBindings.set(k, v);
1518
+ for (const [k, v] of scope.axioms)
1519
+ this.theory.axioms.set(k, v);
1520
+ for (const [k, v] of scope.theorems)
1521
+ this.theory.theorems.set(k, v);
1522
+ this.currentTheoryName = scope.name;
1523
+ for (let i = 0; i < fn.params.length; i++)
1524
+ this.letBindings.set(fn.params[i], this.resolveFormula(args[i]));
1525
+ const savedReturnSignal = this.returnSignal;
1526
+ const savedReturnValue = this.returnValue;
1527
+ this.returnSignal = false;
1528
+ this.returnValue = undefined;
1529
+ for (const bodyStmt of fn.body) {
1530
+ if (this.returnSignal)
1531
+ break;
1532
+ this.executeStatement(bodyStmt);
1533
+ }
1534
+ const result = this.returnValue;
1535
+ this.returnSignal = savedReturnSignal;
1536
+ this.returnValue = savedReturnValue;
1537
+ this.letBindings = savedBindings;
1538
+ this.theory.axioms = savedAxioms;
1539
+ this.theory.theorems = savedTheorems;
1540
+ this.currentTheoryName = savedTheoryName;
1541
+ return result;
1542
+ }
1543
+ finally {
1544
+ this.callDepth--;
1052
1545
  }
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);
1546
+ }
1547
+ tryConstantFold(f) {
1548
+ if (!f || !f.args)
1549
+ return f;
1550
+ const ARITH_OPS = {
1551
+ add: (a, b) => a + b,
1552
+ subtract: (a, b) => a - b,
1553
+ multiply: (a, b) => a * b,
1554
+ divide: (a, b) => a / b,
1555
+ modulo: (a, b) => a % b,
1556
+ };
1557
+ // Note: comparison operators (less, greater, etc.) are NOT folded here.
1558
+ // They must retain their structural form so profiles can evaluate them properly.
1559
+ const newArgs = f.args.map((a) => this.tryConstantFold(a));
1560
+ if (newArgs.length === 2 &&
1561
+ newArgs[0].kind === 'number' &&
1562
+ newArgs[1].kind === 'number' &&
1563
+ ARITH_OPS[f.kind]) {
1564
+ // Don't fold division/modulo by zero — preserve structure for checkWellFormed
1565
+ if ((f.kind === 'divide' || f.kind === 'modulo') && (newArgs[1].value ?? 0) === 0) {
1566
+ return { ...f, args: newArgs };
1567
+ }
1568
+ const op = ARITH_OPS[f.kind];
1569
+ const res = op(newArgs[0].value ?? 0, newArgs[1].value ?? 0);
1570
+ return { kind: 'number', value: res, source: f.source };
1062
1571
  }
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;
1572
+ return { ...f, args: newArgs };
1072
1573
  }
1073
1574
  executeBuiltin(name, args) {
1074
- if (args.length !== 1) {
1075
- throw new Error(`Built-in '${name}' espera exactamente 1 argumento, recibió ${args.length}`);
1076
- }
1077
- const arg = this.resolveFormula(args[0]);
1575
+ const arg = args[0] ? this.resolveFormula(args[0]) : undefined;
1078
1576
  if (name === 'typeof') {
1079
1577
  let typeStr = 'Formula';
1080
- if (arg.kind === 'number')
1578
+ if (arg?.kind === 'number')
1081
1579
  typeStr = 'Number';
1082
- if (arg.kind === 'atom' && arg.name?.startsWith('"'))
1580
+ if (arg?.kind === 'list')
1581
+ typeStr = 'List';
1582
+ if (arg?.kind === 'atom' && arg.name?.startsWith('"'))
1083
1583
  typeStr = 'String';
1084
- return { kind: 'atom', name: `"${typeStr}"`, source: arg.source };
1584
+ return { kind: 'atom', name: `"${typeStr}"`, source: arg?.source };
1085
1585
  }
1086
1586
  if (name === 'is_valid' || name === 'is_satisfiable') {
1087
1587
  const profile = this.requireProfile();
1588
+ if (!arg)
1589
+ return { kind: 'atom', name: '"Error"' };
1088
1590
  try {
1089
1591
  const result = name === 'is_valid' ? profile.checkValid(arg) : profile.checkSatisfiable(arg);
1090
1592
  const isTrue = result.status === 'valid' || result.status === 'satisfiable';
@@ -1095,22 +1597,67 @@ class Interpreter {
1095
1597
  }
1096
1598
  }
1097
1599
  if (name === 'get_atoms') {
1600
+ if (!arg)
1601
+ return { kind: 'atom', name: '"{ }"' };
1098
1602
  const atoms = this.collectAtoms(arg);
1099
1603
  return { kind: 'atom', name: `"{ ${atoms.join(', ')} }"`, source: arg.source };
1100
1604
  }
1605
+ if (name === 'atoms_of') {
1606
+ const source = arg?.source;
1607
+ const items = (arg ? this.collectValueAtoms(arg) : []).map((atomName) => ({
1608
+ kind: 'atom',
1609
+ name: atomName,
1610
+ source,
1611
+ }));
1612
+ return { kind: 'list', args: items, source };
1613
+ }
1614
+ if (name === 'len') {
1615
+ if (arg?.kind === 'list') {
1616
+ return { kind: 'number', value: arg.args?.length ?? 0, source: arg.source };
1617
+ }
1618
+ if (arg?.kind === 'atom' && arg.name?.startsWith('"')) {
1619
+ return {
1620
+ kind: 'number',
1621
+ value: arg.name.replace(/(^"|"$)/g, '').length,
1622
+ source: arg.source,
1623
+ };
1624
+ }
1625
+ return { kind: 'number', value: 0, source: arg?.source };
1626
+ }
1627
+ if (name === 'at') {
1628
+ const listArg = arg;
1629
+ const indexArg = args[1] ? this.resolveFormula(args[1]) : undefined;
1630
+ if (listArg?.kind === 'list' && indexArg?.kind === 'number') {
1631
+ const index = Math.floor(indexArg.value ?? -1);
1632
+ return listArg.args?.[index] || { kind: 'atom', name: 'undefined', source: listArg.source };
1633
+ }
1634
+ return { kind: 'atom', name: 'undefined', source: listArg?.source || indexArg?.source };
1635
+ }
1636
+ if (name === 'formula_eq') {
1637
+ const left = arg;
1638
+ const right = args[1] ? this.resolveFormula(args[1]) : undefined;
1639
+ const equal = !!left && !!right && (0, propositional_1.formulaToString)(left) === (0, propositional_1.formulaToString)(right);
1640
+ return {
1641
+ kind: 'number',
1642
+ value: equal ? 1 : 0,
1643
+ source: left?.source || right?.source,
1644
+ };
1645
+ }
1101
1646
  if (name === 'input') {
1647
+ if (!arg)
1648
+ return { kind: 'atom', name: '""' };
1102
1649
  const prompt = arg.kind === 'atom' && arg.name?.startsWith('"')
1103
1650
  ? arg.name.replace(/(^"|"$)/g, '')
1104
1651
  : (0, propositional_1.formulaToString)(arg);
1105
1652
  let inputStr;
1106
1653
  try {
1107
1654
  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 */
1655
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
1109
1656
  const fs = require('fs');
1110
1657
  const buf = Buffer.alloc(256);
1658
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
1111
1659
  const bytesRead = fs.readSync(process.stdin.fd, buf, 0, 256, null);
1112
1660
  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
1661
  }
1115
1662
  catch {
1116
1663
  inputStr = 'interactive_not_supported';
@@ -1119,70 +1666,238 @@ class Interpreter {
1119
1666
  }
1120
1667
  return undefined;
1121
1668
  }
1669
+ executeActionExpr(action) {
1670
+ const profile = this.requireProfile();
1671
+ switch (action.action) {
1672
+ case 'check_valid': {
1673
+ const formula = this.resolveFormula(action.formula);
1674
+ return {
1675
+ result: profile.checkValid(formula),
1676
+ primary: formula,
1677
+ formulas: [formula],
1678
+ extraBindings: [],
1679
+ };
1680
+ }
1681
+ case 'check_satisfiable': {
1682
+ const formula = this.resolveFormula(action.formula);
1683
+ return {
1684
+ result: profile.checkSatisfiable(formula),
1685
+ primary: formula,
1686
+ formulas: [formula],
1687
+ extraBindings: [],
1688
+ };
1689
+ }
1690
+ case 'check_equivalent': {
1691
+ if (!profile.checkEquivalent) {
1692
+ throw new Error('Este perfil no soporta check equivalent');
1693
+ }
1694
+ const left = this.resolveFormula(action.left);
1695
+ const right = this.resolveFormula(action.right);
1696
+ const comparison = {
1697
+ kind: 'biconditional',
1698
+ args: [left, right],
1699
+ source: action.source,
1700
+ };
1701
+ return {
1702
+ result: profile.checkEquivalent(left, right),
1703
+ primary: comparison,
1704
+ formulas: [left, right],
1705
+ extraBindings: [
1706
+ ['left', left],
1707
+ ['right', right],
1708
+ [
1709
+ 'equivalent',
1710
+ {
1711
+ kind: 'number',
1712
+ value: (0, propositional_1.formulaToString)(left) === (0, propositional_1.formulaToString)(right) ? 1 : 0,
1713
+ source: action.source,
1714
+ },
1715
+ ],
1716
+ ],
1717
+ };
1718
+ }
1719
+ case 'derive': {
1720
+ const goal = this.resolveFormula(action.goal);
1721
+ const premises = action.premises || [];
1722
+ const result = profile.derive(goal, premises, this.theory);
1723
+ const premiseFormulas = premises
1724
+ .map((premise) => this.theory.axioms.get(premise) || this.theory.theorems.get(premise))
1725
+ .filter((value) => !!value)
1726
+ .map((formula) => this.resolveFormula(formula));
1727
+ return {
1728
+ result,
1729
+ primary: result.formula || goal,
1730
+ formulas: [goal, ...premiseFormulas],
1731
+ extraBindings: [
1732
+ ['goal', goal],
1733
+ ['premises', this.createListFormula(premiseFormulas, action.source)],
1734
+ ['premise_names', this.createStringListFormula(premises, action.source)],
1735
+ ],
1736
+ };
1737
+ }
1738
+ case 'prove': {
1739
+ const goal = this.resolveFormula(action.goal);
1740
+ const premises = action.premises || [];
1741
+ const result = profile.prove(goal, this.theory);
1742
+ const premiseFormulas = premises
1743
+ .map((premise) => this.theory.axioms.get(premise) || this.theory.theorems.get(premise))
1744
+ .filter((value) => !!value)
1745
+ .map((formula) => this.resolveFormula(formula));
1746
+ return {
1747
+ result,
1748
+ primary: result.formula || goal,
1749
+ formulas: [goal, ...premiseFormulas],
1750
+ extraBindings: [
1751
+ ['goal', goal],
1752
+ ['premises', this.createListFormula(premiseFormulas, action.source)],
1753
+ ['premise_names', this.createStringListFormula(premises, action.source)],
1754
+ ],
1755
+ };
1756
+ }
1757
+ case 'countermodel': {
1758
+ const formula = this.resolveFormula(action.formula);
1759
+ const result = profile.countermodel(formula);
1760
+ return {
1761
+ result,
1762
+ primary: result.formula || formula,
1763
+ formulas: [formula],
1764
+ extraBindings: [
1765
+ [
1766
+ 'has_countermodel',
1767
+ { kind: 'number', value: result.model ? 1 : 0, source: action.source },
1768
+ ],
1769
+ ],
1770
+ };
1771
+ }
1772
+ case 'truth_table': {
1773
+ if (!profile.truthTable) {
1774
+ throw new Error('Este perfil no soporta truth_table');
1775
+ }
1776
+ const formula = this.resolveFormula(action.formula);
1777
+ const tt = profile.truthTable(formula);
1778
+ const result = {
1779
+ status: tt.isTautology ? 'valid' : tt.isSatisfiable ? 'satisfiable' : 'unsatisfiable',
1780
+ output: this.formatTruthTable(formula, tt),
1781
+ truthTable: tt,
1782
+ diagnostics: [],
1783
+ formula,
1784
+ };
1785
+ return {
1786
+ result,
1787
+ primary: formula,
1788
+ formulas: [formula],
1789
+ extraBindings: [
1790
+ ['variables', this.createAtomListFormula(tt.variables, action.source)],
1791
+ ['rows_count', { kind: 'number', value: tt.rows.length, source: action.source }],
1792
+ [
1793
+ 'satisfying_count',
1794
+ { kind: 'number', value: tt.satisfyingCount ?? 0, source: action.source },
1795
+ ],
1796
+ ],
1797
+ };
1798
+ }
1799
+ case 'explain': {
1800
+ const formula = this.resolveFormula(action.formula);
1801
+ return {
1802
+ result: profile.explain(formula),
1803
+ primary: formula,
1804
+ formulas: [formula],
1805
+ extraBindings: [],
1806
+ };
1807
+ }
1808
+ }
1809
+ }
1810
+ bindCapturedAction(baseName, actionName, primary, result, formulas, extraBindings) {
1811
+ this.defineBinding(baseName, primary);
1812
+ this.defineBinding(`${baseName}.formula`, primary);
1813
+ this.defineBinding(`${baseName}.status`, this.createStringFormula(result.status, primary.source));
1814
+ this.defineBinding(`${baseName}.output`, this.createStringFormula(result.output || '', primary.source));
1815
+ this.defineBinding(`${baseName}.ok`, {
1816
+ kind: 'number',
1817
+ value: this.isSuccessfulStatus(result.status) ? 1 : 0,
1818
+ source: primary.source,
1819
+ });
1820
+ this.defineBinding(`${baseName}.command`, this.createStringFormula(actionName, primary.source));
1821
+ this.defineBinding(`${baseName}.formulas`, this.createListFormula(formulas, primary.source));
1822
+ this.defineBinding(`${baseName}.diagnostics`, this.createStringListFormula(result.diagnostics.map((diag) => diag.message), primary.source));
1823
+ for (const [suffix, value] of extraBindings) {
1824
+ this.defineBinding(`${baseName}.${suffix}`, value);
1825
+ }
1826
+ }
1827
+ createStringFormula(value, source) {
1828
+ return {
1829
+ kind: 'atom',
1830
+ name: `"${value.replace(/"/g, '\\"')}"`,
1831
+ source,
1832
+ };
1833
+ }
1834
+ createListFormula(items, source) {
1835
+ return { kind: 'list', args: items, source };
1836
+ }
1837
+ createAtomListFormula(items, source) {
1838
+ return this.createListFormula(items.map((item) => ({ kind: 'atom', name: item, source })), source);
1839
+ }
1840
+ createStringListFormula(items, source) {
1841
+ return this.createListFormula(items.map((item) => this.createStringFormula(item, source)), source);
1842
+ }
1843
+ isSuccessfulStatus(status) {
1844
+ return status === 'valid' || status === 'satisfiable' || status === 'provable';
1845
+ }
1846
+ collectValueAtoms(formula) {
1847
+ const seen = new Set();
1848
+ const walk = (value) => {
1849
+ if (value.kind === 'atom' && value.name && !value.name.startsWith('"')) {
1850
+ seen.add(value.name);
1851
+ return;
1852
+ }
1853
+ for (const arg of value.args || []) {
1854
+ walk(arg);
1855
+ }
1856
+ };
1857
+ walk(formula);
1858
+ return Array.from(seen);
1859
+ }
1122
1860
  execImportDecl(stmt) {
1123
1861
  let filePath = stmt.path;
1124
- // Agregar extensión .st si no la tiene
1125
1862
  if (!filePath.endsWith('.st'))
1126
1863
  filePath += '.st';
1127
- // Evitar imports circulares
1128
- if (this.importedFiles.has(filePath)) {
1129
- this.emit(`Import: ${filePath} (ya importado, saltar)`);
1864
+ if (this.importedFiles.has(filePath))
1130
1865
  return;
1131
- }
1132
1866
  this.importedFiles.add(filePath);
1133
- // Intentar leer el archivo (solo funciona en Node.js / CLI)
1134
1867
  let source;
1135
1868
  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 */
1869
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
1137
1870
  const fs = require('fs');
1871
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
1138
1872
  const path = require('path');
1139
- // Resolver relativo al archivo actual si no es absoluto
1873
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
1140
1874
  const resolved = path.isAbsolute(filePath)
1141
1875
  ? filePath
1142
- : path.resolve(path.dirname(stmt.source.file || '.'), filePath);
1876
+ : // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
1877
+ path.resolve(path.dirname(stmt.source.file || '.'), filePath);
1878
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
1143
1879
  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
1880
  }
1146
1881
  catch {
1147
- throw new Error(`No se pudo importar '${filePath}': archivo no encontrado`);
1882
+ throw new Error(`No se pudo importar '${filePath}'`);
1148
1883
  }
1149
1884
  const parser = new parser_1.Parser(filePath);
1150
1885
  const program = parser.parse(source);
1151
1886
  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
1887
  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
1888
  const prevLetBindings = new Map(this.letBindings);
1164
1889
  const prevAxioms = new Map(this.theory.axioms);
1165
1890
  const prevTheorems = new Map(this.theory.theorems);
1166
1891
  const prevFunctions = new Map(this.functions);
1167
1892
  const prevTheories = new Map(this.theories);
1168
- // Limpiar para capturar solo lo que exporta el archivo importado
1169
1893
  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
1894
  this.letBindings.clear();
1177
1895
  this.theory.axioms.clear();
1178
1896
  this.theory.theorems.clear();
1179
1897
  this.functions.clear();
1180
1898
  this.theories.clear();
1181
- // Ejecutar statements del archivo importado
1182
- for (const importedStmt of program.statements) {
1899
+ for (const importedStmt of program.statements)
1183
1900
  this.executeStatement(importedStmt);
1184
- }
1185
- // Capturar lo exportado
1186
1901
  const newExports = {
1187
1902
  bindings: new Map(this.exportedBindings),
1188
1903
  axioms: new Map(this.exportedAxioms),
@@ -1190,19 +1905,12 @@ class Interpreter {
1190
1905
  functions: new Map(this.exportedFunctions),
1191
1906
  theories: new Map(this.exportedTheories),
1192
1907
  };
1193
- // Restaurar estado del importador (incluyendo su scope original)
1194
1908
  this.isImporting = prevIsImporting;
1195
- this.exportedBindings = prevExportedBindings;
1196
- this.exportedAxioms = prevExportedAxioms;
1197
- this.exportedTheorems = prevExportedTheorems;
1198
- this.exportedFunctions = prevExportedFunctions;
1199
- this.exportedTheories = prevExportedTheories;
1200
1909
  this.letBindings = prevLetBindings;
1201
1910
  this.theory.axioms = prevAxioms;
1202
1911
  this.theory.theorems = prevTheorems;
1203
1912
  this.functions = prevFunctions;
1204
1913
  this.theories = prevTheories;
1205
- // Fusionar solo lo exportado al scope actual
1206
1914
  for (const [k, v] of newExports.bindings)
1207
1915
  this.letBindings.set(k, v);
1208
1916
  for (const [k, v] of newExports.axioms)
@@ -1213,7 +1921,6 @@ class Interpreter {
1213
1921
  this.functions.set(k, v);
1214
1922
  for (const [k, v] of newExports.theories)
1215
1923
  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
1924
  }
1218
1925
  execExplainCmd(stmt) {
1219
1926
  const profile = this.requireProfile();
@@ -1223,160 +1930,39 @@ class Interpreter {
1223
1930
  if (result.output)
1224
1931
  this.emit(result.output);
1225
1932
  }
1226
- // --- Output helpers ---
1227
1933
  emit(msg) {
1228
1934
  this.stdoutLines.push(msg);
1229
1935
  }
1230
1936
  getVerbosity() {
1231
- 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
- }
1937
+ const v = this.getBinding('verbose');
1938
+ if (v && v.kind === 'atom' && v.name)
1939
+ return v.name.toLowerCase().replace(/(^"|"$)/g, '');
1237
1940
  return 'off';
1238
1941
  }
1239
1942
  emitResult(cmd, result) {
1240
- const statusIcon = this.statusIcon(result.status);
1241
- this.emit(`${statusIcon} [${cmd}] ${result.output || result.status}`);
1943
+ this.emit(`${this.statusIcon(result.status)} [${cmd}] ${result.output || result.status}`);
1242
1944
  const verbosity = this.getVerbosity();
1243
- if (result.educationalNote && verbosity === 'on') {
1244
- this.emit(` Nota pedagógica: ${result.educationalNote}`);
1245
- }
1246
- if (result.paradoxWarning) {
1945
+ if (result.paradoxWarning)
1247
1946
  this.emit(` ⚠ PARADOJA: ${result.paradoxWarning}`);
1248
- }
1249
- // Clasificación de la fórmula (si tenemos verbosidad on o si está precalculado)
1250
1947
  if (result.formula && (verbosity === 'on' || result.formulaClassification)) {
1251
1948
  const cls = (0, formula_classifier_1.classifyFormula)(result.formula);
1252
1949
  const name = result.formulaClassification || cls.formulaClassification;
1253
- if (name) {
1950
+ if (name)
1254
1951
  this.emit(` Identificación: ${name}`);
1255
- }
1256
1952
  }
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
- }
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
- }
1953
+ if (result.model && (verbosity === 'on' || cmd === 'countermodel')) {
1954
+ if (result.model.valuation) {
1955
+ this.emit(' Modelo:');
1956
+ for (const [k, v] of Object.entries(result.model.valuation))
1957
+ this.emit(` ${k} = ${v}`);
1309
1958
  }
1310
1959
  }
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')) {
1960
+ if (result.tableauTrace && result.tableauTrace.length > 0 && verbosity === 'on') {
1361
1961
  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
- }
1962
+ for (let i = 0; i < result.tableauTrace.length; i++)
1963
+ this.emit(` ${i + 1}. ${String(result.tableauTrace[i])}`);
1377
1964
  }
1378
1965
  }
1379
- /** Recolecta nombres de átomos únicos de una fórmula */
1380
1966
  collectAtoms(f, seen = new Set()) {
1381
1967
  if (!f)
1382
1968
  return [];
@@ -1385,108 +1971,30 @@ class Interpreter {
1385
1971
  return [f.name];
1386
1972
  }
1387
1973
  const result = [];
1388
- if (f.args) {
1389
- for (const arg of f.args) {
1974
+ if (f.args)
1975
+ for (const arg of f.args)
1390
1976
  result.push(...this.collectAtoms(arg, seen));
1391
- }
1392
- }
1393
1977
  return result;
1394
1978
  }
1395
1979
  statusIcon(status) {
1396
1980
  switch (status) {
1397
1981
  case 'valid':
1982
+ case 'provable':
1398
1983
  return '✓';
1399
1984
  case 'invalid':
1985
+ case 'refutable':
1400
1986
  return '✗';
1401
1987
  case 'satisfiable':
1402
1988
  return '◎';
1403
1989
  case 'unsatisfiable':
1404
1990
  return '⊘';
1405
- case 'provable':
1406
- return '✓';
1407
- case 'refutable':
1408
- return '✗';
1409
- case 'unknown':
1410
- return '?';
1411
- case 'error':
1412
- return '⚠';
1413
1991
  default:
1414
1992
  return '•';
1415
1993
  }
1416
1994
  }
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');
1995
+ formatTruthTable(formula, _tt) {
1996
+ return `Tabla de verdad para ${(0, propositional_1.formulaToString)(formula)}`;
1488
1997
  }
1489
- // Getters para el estado (usados por REPL)
1490
1998
  getTheory() {
1491
1999
  return this.theory;
1492
2000
  }
@@ -1496,9 +2004,6 @@ class Interpreter {
1496
2004
  getTextLayer() {
1497
2005
  return this.textLayer;
1498
2006
  }
1499
- getLetDescriptions() {
1500
- return this.letDescriptions;
1501
- }
1502
2007
  getLetBindings() {
1503
2008
  return this.letBindings;
1504
2009
  }