@stevenvo780/st-lang 2.6.1 → 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 (56) hide show
  1. package/dist/ast/nodes.d.ts +18 -1
  2. package/dist/ast/nodes.d.ts.map +1 -1
  3. package/dist/parser/parser.d.ts +2 -0
  4. package/dist/parser/parser.d.ts.map +1 -1
  5. package/dist/parser/parser.js +82 -0
  6. package/dist/parser/parser.js.map +1 -1
  7. package/dist/profiles/classical/first-order.d.ts +1 -0
  8. package/dist/profiles/classical/first-order.d.ts.map +1 -1
  9. package/dist/profiles/classical/first-order.js +35 -0
  10. package/dist/profiles/classical/first-order.js.map +1 -1
  11. package/dist/profiles/classical/propositional.d.ts +6 -1
  12. package/dist/profiles/classical/propositional.d.ts.map +1 -1
  13. package/dist/profiles/classical/propositional.js +334 -35
  14. package/dist/profiles/classical/propositional.js.map +1 -1
  15. package/dist/profiles/paraconsistent/belnap.d.ts.map +1 -1
  16. package/dist/profiles/paraconsistent/belnap.js +151 -3
  17. package/dist/profiles/paraconsistent/belnap.js.map +1 -1
  18. package/dist/profiles/shared/tableau-engine.d.ts.map +1 -1
  19. package/dist/profiles/shared/tableau-engine.js +49 -22
  20. package/dist/profiles/shared/tableau-engine.js.map +1 -1
  21. package/dist/runtime/format.d.ts.map +1 -1
  22. package/dist/runtime/format.js +4 -0
  23. package/dist/runtime/format.js.map +1 -1
  24. package/dist/runtime/formula-factory.d.ts +26 -0
  25. package/dist/runtime/formula-factory.d.ts.map +1 -0
  26. package/dist/runtime/formula-factory.js +67 -0
  27. package/dist/runtime/formula-factory.js.map +1 -0
  28. package/dist/runtime/interpreter.d.ts +41 -0
  29. package/dist/runtime/interpreter.d.ts.map +1 -1
  30. package/dist/runtime/interpreter.js +1029 -245
  31. package/dist/runtime/interpreter.js.map +1 -1
  32. package/dist/tests/arithmetic.test.js +2 -1
  33. package/dist/tests/arithmetic.test.js.map +1 -1
  34. package/dist/tests/examples.test.js +12 -1
  35. package/dist/tests/examples.test.js.map +1 -1
  36. package/dist/tests/exhaustive-matrix.test.js +1 -1
  37. package/dist/tests/exhaustive-matrix.test.js.map +1 -1
  38. package/dist/tests/limits.test.js +15 -3
  39. package/dist/tests/limits.test.js.map +1 -1
  40. package/dist/tests/result-bindings.test.d.ts +2 -0
  41. package/dist/tests/result-bindings.test.d.ts.map +1 -0
  42. package/dist/tests/result-bindings.test.js +59 -0
  43. package/dist/tests/result-bindings.test.js.map +1 -0
  44. package/dist/tests/stress-hardware.test.d.ts +2 -0
  45. package/dist/tests/stress-hardware.test.d.ts.map +1 -0
  46. package/dist/tests/stress-hardware.test.js +516 -0
  47. package/dist/tests/stress-hardware.test.js.map +1 -0
  48. package/dist/tests/v1-features.test.js +1 -1
  49. package/dist/tests/v1-features.test.js.map +1 -1
  50. package/dist/types/index.d.ts +1 -1
  51. package/dist/types/index.d.ts.map +1 -1
  52. package/dist/utils/memo.d.ts +3 -0
  53. package/dist/utils/memo.d.ts.map +1 -1
  54. package/dist/utils/memo.js +30 -0
  55. package/dist/utils/memo.js.map +1 -1
  56. package/package.json +2 -1
@@ -7,13 +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");
16
- const MAX_CALL_DEPTH = 500;
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;
17
21
  class Interpreter {
18
22
  theory;
19
23
  profile = null;
@@ -43,6 +47,12 @@ class Interpreter {
43
47
  exportedTheories = new Map();
44
48
  /** Profundidad de llamadas a funciones (anti-recursión infinita) */
45
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;
46
56
  constructor() {
47
57
  this.theory = this.createEmptyTheory();
48
58
  this.textLayer = (0, compiler_1.createTextLayerState)();
@@ -50,7 +60,17 @@ class Interpreter {
50
60
  }
51
61
  /** Registra funciones nativas (Built-ins) para metaprogramación e interactividad */
52
62
  registerBuiltins() {
53
- 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
+ ];
54
74
  for (const name of builtins) {
55
75
  this.functions.set(name, {
56
76
  kind: 'fn_decl',
@@ -93,11 +113,123 @@ class Interpreter {
93
113
  this.exportedTheorems.clear();
94
114
  this.exportedFunctions.clear();
95
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;
96
226
  }
97
227
  execute(source, file = '<stdin>') {
98
228
  this.diagnostics = [];
99
229
  this.results = [];
100
230
  this.stdoutLines = [];
231
+ this.runtimeStepCount = 0;
232
+ this.runtimeCallCount = 0;
101
233
  const parser = new parser_1.Parser(file);
102
234
  const program = parser.parse(source);
103
235
  this.diagnostics.push(...parser.diagnostics);
@@ -113,21 +245,7 @@ class Interpreter {
113
245
  results: [],
114
246
  };
115
247
  }
116
- for (const stmt of program.statements) {
117
- try {
118
- this.executeStatement(stmt);
119
- }
120
- catch (e) {
121
- const message = e instanceof Error ? e.message : String(e);
122
- this.diagnostics.push({
123
- severity: 'error',
124
- message: message || 'Error de runtime',
125
- file,
126
- line: stmt.source.line,
127
- column: stmt.source.column,
128
- });
129
- }
130
- }
248
+ this.executeStatementsIterative(program.statements, file);
131
249
  const hasErrors = this.diagnostics.some((d) => d.severity === 'error');
132
250
  return {
133
251
  stdout: this.stdoutLines.join('\n'),
@@ -148,6 +266,8 @@ class Interpreter {
148
266
  this.diagnostics = [...parser.diagnostics];
149
267
  this.results = [];
150
268
  this.stdoutLines = [];
269
+ this.runtimeStepCount = 0;
270
+ this.runtimeCallCount = 0;
151
271
  if (parser.diagnostics.some((d) => d.severity === 'error')) {
152
272
  return {
153
273
  stdout: '',
@@ -157,18 +277,7 @@ class Interpreter {
157
277
  results: [],
158
278
  };
159
279
  }
160
- for (const stmt of program.statements) {
161
- try {
162
- this.executeStatement(stmt);
163
- }
164
- catch (e) {
165
- const message = e instanceof Error ? e.message : String(e);
166
- this.diagnostics.push({
167
- severity: 'error',
168
- message: message || 'Error de runtime',
169
- });
170
- }
171
- }
280
+ this.executeStatementsIterative(program.statements, '<repl>');
172
281
  return {
173
282
  stdout: this.stdoutLines.join('\n'),
174
283
  stderr: this.diagnostics
@@ -264,9 +373,268 @@ class Interpreter {
264
373
  return this.execExportDecl(stmt);
265
374
  }
266
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
+ }
267
635
  execExportDecl(stmt) {
268
636
  // Ejecutar la declaración interna
269
- this.executeStatement(stmt.statement);
637
+ this.executeStatementsIterative([stmt.statement], stmt.source.file);
270
638
  // Registrarla como exportada
271
639
  const s = stmt.statement;
272
640
  switch (s.kind) {
@@ -302,21 +670,43 @@ class Interpreter {
302
670
  * por sus fórmulas definidas. Detecta ciclos para evitar recursión infinita.
303
671
  * Soporta notación con punto: Theory.member resuelve desde el scope de la teoría.
304
672
  */
673
+ invalidateResolveCache() {
674
+ this.resolveCache = new WeakMap();
675
+ this.resolveCacheGeneration++;
676
+ }
305
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;
306
682
  const resolved = this.resolveFormulaRecursive(f, visited);
307
- return this.tryConstantFold(resolved);
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;
308
690
  }
309
- resolveFormulaRecursive(f, visited = new Set()) {
691
+ resolveFormulaRecursive(f, visited) {
310
692
  if (!f)
311
693
  return f;
312
694
  // Si es un átomo, intentar resolver
313
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
+ }
314
704
  // Dot notation: Theory.member o instance.member
315
705
  if (f.name.includes('.')) {
316
706
  const [prefix, memberName] = f.name.split('.', 2);
317
707
  // 1. Intentar resolver el prefijo como una variable local (instancia)
318
- if (this.letBindings.has(prefix)) {
319
- const resolvedPrefix = this.letBindings.get(prefix);
708
+ if (this.hasBinding(prefix)) {
709
+ const resolvedPrefix = this.getBinding(prefix);
320
710
  if (resolvedPrefix.kind === 'atom' && resolvedPrefix.name) {
321
711
  const actualInstanceName = resolvedPrefix.name;
322
712
  const scope = this.theories.get(actualInstanceName);
@@ -324,12 +714,12 @@ class Interpreter {
324
714
  if (scope.privateMembers.has(memberName) &&
325
715
  this.currentTheoryName !== actualInstanceName)
326
716
  return f;
327
- if (scope.letBindings.has(memberName))
328
- return this.resolveFormulaRecursive(scope.letBindings.get(memberName), new Set(visited));
329
- if (scope.axioms.has(memberName))
330
- return this.resolveFormulaRecursive(scope.axioms.get(memberName), new Set(visited));
331
- if (scope.theorems.has(memberName))
332
- return this.resolveFormulaRecursive(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
+ }
333
723
  }
334
724
  }
335
725
  }
@@ -338,37 +728,31 @@ class Interpreter {
338
728
  if (scope) {
339
729
  if (scope.privateMembers.has(memberName) && this.currentTheoryName !== prefix)
340
730
  return f;
341
- if (scope.letBindings.has(memberName))
342
- return this.resolveFormulaRecursive(scope.letBindings.get(memberName), new Set(visited));
343
- if (scope.axioms.has(memberName))
344
- return this.resolveFormulaRecursive(scope.axioms.get(memberName), new Set(visited));
345
- if (scope.theorems.has(memberName))
346
- return this.resolveFormulaRecursive(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
+ }
347
737
  }
348
738
  return f;
349
739
  }
350
- // Binding local normal
351
- if (this.letBindings.has(f.name)) {
352
- if (visited.has(f.name))
353
- return f;
354
- const newVisited = new Set(visited);
355
- newVisited.add(f.name);
356
- return this.resolveFormulaRecursive(this.letBindings.get(f.name), newVisited);
357
- }
358
740
  // También resolver axiomas/teoremas del theory actual por nombre
359
741
  if (this.theory.axioms.has(f.name)) {
360
742
  if (visited.has(f.name))
361
743
  return f;
362
- const newVisited = new Set(visited);
363
- newVisited.add(f.name);
364
- return this.resolveFormulaRecursive(this.theory.axioms.get(f.name), newVisited);
744
+ visited.add(f.name);
745
+ const result = this.resolveFormulaRecursive(this.theory.axioms.get(f.name), visited);
746
+ visited.delete(f.name);
747
+ return result;
365
748
  }
366
749
  if (this.theory.theorems.has(f.name)) {
367
750
  if (visited.has(f.name))
368
751
  return f;
369
- const newVisited = new Set(visited);
370
- newVisited.add(f.name);
371
- return this.resolveFormulaRecursive(this.theory.theorems.get(f.name), newVisited);
752
+ visited.add(f.name);
753
+ const result = this.resolveFormulaRecursive(this.theory.theorems.get(f.name), visited);
754
+ visited.delete(f.name);
755
+ return result;
372
756
  }
373
757
  }
374
758
  // Llamada a función como expresión
@@ -378,8 +762,16 @@ class Interpreter {
378
762
  }
379
763
  // Recorrer hijos recursivamente
380
764
  if (f.args && f.args.length > 0) {
381
- const newArgs = f.args.map((a) => a ? this.resolveFormulaRecursive(a, new Set(visited)) : a);
382
- 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
+ }
773
+ }
774
+ return changed ? { ...f, args: newArgs } : f;
383
775
  }
384
776
  return f;
385
777
  }
@@ -456,10 +848,60 @@ class Interpreter {
456
848
  }
457
849
  execTruthTableCmd(stmt) {
458
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
459
902
  if (!profile.truthTable) {
460
903
  throw new Error('Este perfil no soporta truth_table');
461
904
  }
462
- const formula = this.resolveFormula(stmt.formula);
463
905
  const tt = profile.truthTable(formula);
464
906
  const result = {
465
907
  status: tt.isTautology ? 'valid' : tt.isSatisfiable ? 'satisfiable' : 'unsatisfiable',
@@ -485,19 +927,37 @@ class Interpreter {
485
927
  this.emit(`Formalizacion ${stmt.name}: ${stmt.passageName} -> ${(0, propositional_1.formulaToString)(formula)}`);
486
928
  }
487
929
  else if (stmt.letType === 'description') {
488
- this.letDescriptions.set(stmt.name, stmt.description);
489
- 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
+ }
490
945
  }
491
946
  else if (stmt.letType === 'formula' && stmt.formula) {
492
- const resolved = this.resolveFormula(stmt.formula);
493
- this.letBindings.set(stmt.name, resolved);
494
- this.theory.axioms.set(stmt.name, resolved);
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);
495
952
  if ('description' in stmt && stmt.description) {
496
- this.letDescriptions.set(stmt.name, stmt.description);
497
- 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)}`);
498
957
  }
499
958
  else {
500
- 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)}`);
501
961
  }
502
962
  }
503
963
  }
@@ -637,21 +1097,7 @@ class Interpreter {
637
1097
  }
638
1098
  const resolvedGoal = this.resolveFormula(stmt.goal);
639
1099
  this.emit(` show ${(0, format_1.formulaToUnicode)(resolvedGoal)}`);
640
- for (const bodyStmt of stmt.body) {
641
- try {
642
- this.executeStatement(bodyStmt);
643
- }
644
- catch (e) {
645
- const message = e instanceof Error ? e.message : String(e);
646
- this.diagnostics.push({
647
- severity: 'error',
648
- message,
649
- file: stmt.source.file,
650
- line: bodyStmt.source.line,
651
- column: bodyStmt.source.column,
652
- });
653
- }
654
- }
1100
+ this.executeStatementsIterative(stmt.body, stmt.source.file);
655
1101
  const premiseNames = stmt.assumptions.map((a) => a.name);
656
1102
  const result = profile.derive(resolvedGoal, premiseNames, this.theory);
657
1103
  this.results.push(result);
@@ -670,6 +1116,7 @@ class Interpreter {
670
1116
  this.theory.axioms = savedAxioms;
671
1117
  this.letBindings = savedLetBindings;
672
1118
  this.letDescriptions = savedLetDescriptions;
1119
+ this.invalidateResolveCache();
673
1120
  this.emit('── End Proof Block ──');
674
1121
  }
675
1122
  execTheoryDecl(stmt) {
@@ -768,6 +1215,7 @@ class Interpreter {
768
1215
  this.theory.axioms = savedAxioms;
769
1216
  this.theory.theorems = savedTheorems;
770
1217
  this.currentTheoryName = savedTheoryName;
1218
+ this.invalidateResolveCache();
771
1219
  this.theories.set(theoryName, scope);
772
1220
  this.emit(`── End Theory Instance ${theoryName} ──`);
773
1221
  return theoryName;
@@ -776,107 +1224,29 @@ class Interpreter {
776
1224
  if (stmt.value !== null)
777
1225
  this.emit(stmt.value);
778
1226
  else if (stmt.formula) {
779
- 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());
780
1229
  this.emit((0, format_1.formulaToUnicode)(resolved));
781
1230
  }
782
1231
  }
783
1232
  execSetCmd(stmt) {
784
- const resolved = this.resolveFormula(stmt.formula);
785
- this.letBindings.set(stmt.name, resolved);
786
- this.theory.axioms.set(stmt.name, resolved);
787
- 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)}`);
788
1239
  }
789
1240
  execIfStmt(stmt) {
790
- const profile = this.requireProfile();
791
- for (const branch of stmt.branches) {
792
- const resolved = this.resolveFormula(branch.formula);
793
- let matched;
794
- if (branch.condition === 'valid' || branch.condition === 'invalid') {
795
- const result = profile.checkValid(resolved);
796
- matched =
797
- branch.condition === 'valid' ? result.status === 'valid' : result.status !== 'valid';
798
- }
799
- else {
800
- const result = profile.checkSatisfiable(resolved);
801
- matched =
802
- branch.condition === 'satisfiable'
803
- ? result.status === 'satisfiable' || result.status === 'valid'
804
- : result.status === 'unsatisfiable';
805
- }
806
- if (matched) {
807
- for (const bodyStmt of branch.body) {
808
- if (this.returnSignal)
809
- return;
810
- this.executeStatement(bodyStmt);
811
- }
812
- return;
813
- }
814
- }
815
- if (stmt.elseBranch) {
816
- for (const bodyStmt of stmt.elseBranch) {
817
- if (this.returnSignal)
818
- return;
819
- this.executeStatement(bodyStmt);
820
- }
821
- }
1241
+ const selectedBody = this.selectIfBody(stmt);
1242
+ if (selectedBody)
1243
+ this.executeStatementsIterative(selectedBody, stmt.source.file);
822
1244
  }
823
1245
  execForStmt(stmt) {
824
- const savedBinding = this.letBindings.get(stmt.variable);
825
- for (const item of stmt.items) {
826
- if (this.returnSignal)
827
- break;
828
- const resolved = this.resolveFormula(item);
829
- this.letBindings.set(stmt.variable, resolved);
830
- for (const bodyStmt of stmt.body) {
831
- if (this.returnSignal)
832
- break;
833
- this.executeStatement(bodyStmt);
834
- }
835
- }
836
- if (savedBinding !== undefined)
837
- this.letBindings.set(stmt.variable, savedBinding);
838
- else
839
- this.letBindings.delete(stmt.variable);
1246
+ this.executeStatementsIterative([stmt], stmt.source.file);
840
1247
  }
841
1248
  execWhileStmt(stmt) {
842
- const profile = this.requireProfile();
843
- const maxIter = stmt.maxIterations || 1000;
844
- let iter = 0;
845
- while (iter < maxIter) {
846
- if (this.returnSignal)
847
- break;
848
- iter++;
849
- const resolved = this.resolveFormula(stmt.formula);
850
- let matched;
851
- if (stmt.condition === 'valid' || stmt.condition === 'invalid') {
852
- const result = profile.checkValid(resolved);
853
- matched =
854
- stmt.condition === 'valid' ? result.status === 'valid' : result.status !== 'valid';
855
- }
856
- else {
857
- const result = profile.checkSatisfiable(resolved);
858
- matched =
859
- stmt.condition === 'satisfiable'
860
- ? result.status === 'satisfiable' || result.status === 'valid'
861
- : result.status === 'unsatisfiable';
862
- }
863
- if (!matched)
864
- break;
865
- for (const bodyStmt of stmt.body) {
866
- if (this.returnSignal)
867
- break;
868
- this.executeStatement(bodyStmt);
869
- }
870
- }
871
- if (iter >= maxIter) {
872
- this.diagnostics.push({
873
- severity: 'warning',
874
- message: `while: se alcanzó el límite de ${maxIter} iteraciones`,
875
- file: stmt.source.file,
876
- line: stmt.source.line,
877
- column: stmt.source.column,
878
- });
879
- }
1249
+ this.executeStatementsIterative([stmt], stmt.source.file);
880
1250
  }
881
1251
  execFnDecl(stmt) {
882
1252
  const name = this.currentTheoryName ? `${this.currentTheoryName}.${stmt.name}` : stmt.name;
@@ -888,77 +1258,249 @@ class Interpreter {
888
1258
  }
889
1259
  execReturnStmt(stmt) {
890
1260
  if (stmt.formula)
891
- this.returnValue = this.resolveFormula(stmt.formula);
1261
+ this.returnValue = this.evaluateFormulaValue(stmt.formula);
892
1262
  else
893
1263
  this.returnValue = undefined;
894
1264
  this.returnSignal = true;
895
1265
  }
896
1266
  executeFnCall(stmt) {
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) {
897
1321
  this.callDepth++;
898
1322
  if (this.callDepth > MAX_CALL_DEPTH) {
899
1323
  this.callDepth--;
900
1324
  throw new Error(`Límite de recursión excedido (${MAX_CALL_DEPTH}).`);
901
1325
  }
902
- try {
903
- if (['typeof', 'is_valid', 'is_satisfiable', 'get_atoms', 'input'].includes(stmt.name)) {
904
- return this.executeBuiltin(stmt.name, stmt.args);
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' } };
1345
+ }
1346
+ if (stmt.name.includes('.')) {
1347
+ const [prefix, methodName] = stmt.name.split('.', 2);
1348
+ let actualInstanceName = prefix;
1349
+ if (this.hasBinding(prefix)) {
1350
+ const resolved = this.getBinding(prefix);
1351
+ if (resolved.kind === 'atom' && resolved.name)
1352
+ actualInstanceName = resolved.name;
905
1353
  }
906
- if (stmt.name.includes('.')) {
907
- const [prefix, methodName] = stmt.name.split('.', 2);
908
- let actualInstanceName = prefix;
909
- if (this.letBindings.has(prefix)) {
910
- const resolved = this.letBindings.get(prefix);
911
- if (resolved.kind === 'atom' && resolved.name)
912
- actualInstanceName = resolved.name;
1354
+ const scope = this.theories.get(actualInstanceName);
1355
+ if (scope) {
1356
+ const internalFnName = `${actualInstanceName}.${methodName}`;
1357
+ const fn = this.functions.get(internalFnName);
1358
+ if (fn)
1359
+ return {
1360
+ frame: this.createFunctionFrame(internalFnName, fn, stmt.args, continuation, scope, inheritedReturnState),
1361
+ };
1362
+ }
1363
+ }
1364
+ const template = this.theoryTemplates.get(stmt.name);
1365
+ if (template) {
1366
+ const instanceId = `inst_${stmt.name}_${this.theories.size}`;
1367
+ this.instantiateTheory(template.node, instanceId, stmt.args);
1368
+ this.callDepth--;
1369
+ return {
1370
+ result: { kind: 'atom', name: instanceId },
1371
+ resultContinuation: continuation || { type: 'discard' },
1372
+ };
1373
+ }
1374
+ const fn = this.functions.get(stmt.name);
1375
+ if (!fn) {
1376
+ this.callDepth--;
1377
+ throw new Error(`Función o Teoría '${stmt.name}' no declarada`);
1378
+ }
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;
1406
+ for (let i = 0; i < fn.params.length; i++) {
1407
+ this.currentBindingFrame.bindings.set(fn.params[i], this.evaluateFormulaValue(args[i]));
1408
+ }
1409
+ const savedReturnSignal = inheritedReturnState?.signal ?? this.returnSignal;
1410
+ const savedReturnValue = inheritedReturnState?.value ?? this.returnValue;
1411
+ this.returnSignal = false;
1412
+ this.returnValue = undefined;
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) {
1424
+ const result = this.returnValue;
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;
913
1462
  }
914
- const scope = this.theories.get(actualInstanceName);
915
- if (scope) {
916
- const internalFnName = `${actualInstanceName}.${methodName}`;
917
- const fn = this.functions.get(internalFnName);
918
- if (fn)
919
- return this.executeFunctionInScope(fn, stmt.args, scope);
1463
+ return frame.statements[frame.index++];
1464
+ }
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;
920
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;
921
1476
  }
922
- const template = this.theoryTemplates.get(stmt.name);
923
- if (template) {
924
- const instanceId = `inst_${stmt.name}_${this.theories.size}`;
925
- this.instantiateTheory(template.node, instanceId, stmt.args);
926
- return { kind: 'atom', name: instanceId };
1477
+ if (this.returnSignal) {
1478
+ runtimeStack.pop();
1479
+ continue;
927
1480
  }
928
- const fn = this.functions.get(stmt.name);
929
- if (!fn)
930
- throw new Error(`Función o Teoría '${stmt.name}' no declarada`);
931
- if (stmt.args.length !== fn.params.length)
932
- throw new Error(`Argumentos incorrectos.`);
933
- const savedBindings = new Map();
934
- for (const param of fn.params)
935
- savedBindings.set(param, this.letBindings.get(param));
936
- for (let i = 0; i < fn.params.length; i++)
937
- this.letBindings.set(fn.params[i], this.resolveFormula(stmt.args[i]));
938
- const savedReturnSignal = this.returnSignal;
939
- const savedReturnValue = this.returnValue;
940
- this.returnSignal = false;
941
- this.returnValue = undefined;
942
- for (const bodyStmt of fn.body) {
943
- if (this.returnSignal)
944
- break;
945
- this.executeStatement(bodyStmt);
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;
946
1493
  }
947
- const result = this.returnValue;
948
- this.returnSignal = savedReturnSignal;
949
- this.returnValue = savedReturnValue;
950
- for (const param of fn.params) {
951
- const prev = savedBindings.get(param);
952
- if (prev !== undefined)
953
- this.letBindings.set(param, prev);
954
- else
955
- this.letBindings.delete(param);
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;
956
1499
  }
957
- return result;
958
- }
959
- finally {
960
- this.callDepth--;
1500
+ frame.iterations++;
1501
+ runtimeStack.push({ kind: 'statements', statements: frame.stmt.body, index: 0 });
961
1502
  }
1503
+ return undefined;
962
1504
  }
963
1505
  executeFunctionInScope(fn, args, scope) {
964
1506
  this.callDepth++;
@@ -1009,38 +1551,42 @@ class Interpreter {
1009
1551
  add: (a, b) => a + b,
1010
1552
  subtract: (a, b) => a - b,
1011
1553
  multiply: (a, b) => a * b,
1012
- divide: (a, b) => (b === 0 ? NaN : a / b),
1013
- modulo: (a, b) => (b === 0 ? NaN : a % b),
1014
- };
1015
- const CMP_OPS = {
1016
- less: (a, b) => (a < b ? 1 : 0),
1017
- greater: (a, b) => (a > b ? 1 : 0),
1018
- less_eq: (a, b) => (a <= b ? 1 : 0),
1019
- greater_eq: (a, b) => (a >= b ? 1 : 0),
1554
+ divide: (a, b) => a / b,
1555
+ modulo: (a, b) => a % b,
1020
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.
1021
1559
  const newArgs = f.args.map((a) => this.tryConstantFold(a));
1022
1560
  if (newArgs.length === 2 &&
1023
1561
  newArgs[0].kind === 'number' &&
1024
1562
  newArgs[1].kind === 'number' &&
1025
- (ARITH_OPS[f.kind] || CMP_OPS[f.kind])) {
1026
- const op = ARITH_OPS[f.kind] || CMP_OPS[f.kind];
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];
1027
1569
  const res = op(newArgs[0].value ?? 0, newArgs[1].value ?? 0);
1028
1570
  return { kind: 'number', value: res, source: f.source };
1029
1571
  }
1030
1572
  return { ...f, args: newArgs };
1031
1573
  }
1032
1574
  executeBuiltin(name, args) {
1033
- const arg = this.resolveFormula(args[0]);
1575
+ const arg = args[0] ? this.resolveFormula(args[0]) : undefined;
1034
1576
  if (name === 'typeof') {
1035
1577
  let typeStr = 'Formula';
1036
- if (arg.kind === 'number')
1578
+ if (arg?.kind === 'number')
1037
1579
  typeStr = 'Number';
1038
- if (arg.kind === 'atom' && arg.name?.startsWith('"'))
1580
+ if (arg?.kind === 'list')
1581
+ typeStr = 'List';
1582
+ if (arg?.kind === 'atom' && arg.name?.startsWith('"'))
1039
1583
  typeStr = 'String';
1040
- return { kind: 'atom', name: `"${typeStr}"`, source: arg.source };
1584
+ return { kind: 'atom', name: `"${typeStr}"`, source: arg?.source };
1041
1585
  }
1042
1586
  if (name === 'is_valid' || name === 'is_satisfiable') {
1043
1587
  const profile = this.requireProfile();
1588
+ if (!arg)
1589
+ return { kind: 'atom', name: '"Error"' };
1044
1590
  try {
1045
1591
  const result = name === 'is_valid' ? profile.checkValid(arg) : profile.checkSatisfiable(arg);
1046
1592
  const isTrue = result.status === 'valid' || result.status === 'satisfiable';
@@ -1051,10 +1597,55 @@ class Interpreter {
1051
1597
  }
1052
1598
  }
1053
1599
  if (name === 'get_atoms') {
1600
+ if (!arg)
1601
+ return { kind: 'atom', name: '"{ }"' };
1054
1602
  const atoms = this.collectAtoms(arg);
1055
1603
  return { kind: 'atom', name: `"{ ${atoms.join(', ')} }"`, source: arg.source };
1056
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
+ }
1057
1646
  if (name === 'input') {
1647
+ if (!arg)
1648
+ return { kind: 'atom', name: '""' };
1058
1649
  const prompt = arg.kind === 'atom' && arg.name?.startsWith('"')
1059
1650
  ? arg.name.replace(/(^"|"$)/g, '')
1060
1651
  : (0, propositional_1.formulaToString)(arg);
@@ -1075,6 +1666,197 @@ class Interpreter {
1075
1666
  }
1076
1667
  return undefined;
1077
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
+ }
1078
1860
  execImportDecl(stmt) {
1079
1861
  let filePath = stmt.path;
1080
1862
  if (!filePath.endsWith('.st'))
@@ -1152,7 +1934,7 @@ class Interpreter {
1152
1934
  this.stdoutLines.push(msg);
1153
1935
  }
1154
1936
  getVerbosity() {
1155
- const v = this.letBindings.get('verbose');
1937
+ const v = this.getBinding('verbose');
1156
1938
  if (v && v.kind === 'atom' && v.name)
1157
1939
  return v.name.toLowerCase().replace(/(^"|"$)/g, '');
1158
1940
  return 'off';
@@ -1197,8 +1979,10 @@ class Interpreter {
1197
1979
  statusIcon(status) {
1198
1980
  switch (status) {
1199
1981
  case 'valid':
1982
+ case 'provable':
1200
1983
  return '✓';
1201
1984
  case 'invalid':
1985
+ case 'refutable':
1202
1986
  return '✗';
1203
1987
  case 'satisfiable':
1204
1988
  return '◎';