@stevenvo780/st-lang 2.6.1 → 2.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/ast/nodes.d.ts +18 -1
  2. package/dist/ast/nodes.d.ts.map +1 -1
  3. package/dist/lexer/lexer.d.ts +2 -1
  4. package/dist/lexer/lexer.d.ts.map +1 -1
  5. package/dist/lexer/lexer.js +4 -2
  6. package/dist/lexer/lexer.js.map +1 -1
  7. package/dist/lexer/tokens.d.ts +17 -0
  8. package/dist/lexer/tokens.d.ts.map +1 -1
  9. package/dist/lexer/tokens.js +58 -21
  10. package/dist/lexer/tokens.js.map +1 -1
  11. package/dist/parser/parser.d.ts +4 -1
  12. package/dist/parser/parser.d.ts.map +1 -1
  13. package/dist/parser/parser.js +86 -2
  14. package/dist/parser/parser.js.map +1 -1
  15. package/dist/profiles/classical/dpll.d.ts +10 -0
  16. package/dist/profiles/classical/dpll.d.ts.map +1 -0
  17. package/dist/profiles/classical/dpll.js +446 -0
  18. package/dist/profiles/classical/dpll.js.map +1 -0
  19. package/dist/profiles/classical/first-order.d.ts +1 -0
  20. package/dist/profiles/classical/first-order.d.ts.map +1 -1
  21. package/dist/profiles/classical/first-order.js +35 -0
  22. package/dist/profiles/classical/first-order.js.map +1 -1
  23. package/dist/profiles/classical/propositional.d.ts +6 -1
  24. package/dist/profiles/classical/propositional.d.ts.map +1 -1
  25. package/dist/profiles/classical/propositional.js +434 -38
  26. package/dist/profiles/classical/propositional.js.map +1 -1
  27. package/dist/profiles/paraconsistent/belnap.d.ts.map +1 -1
  28. package/dist/profiles/paraconsistent/belnap.js +151 -3
  29. package/dist/profiles/paraconsistent/belnap.js.map +1 -1
  30. package/dist/profiles/shared/tableau-engine.d.ts.map +1 -1
  31. package/dist/profiles/shared/tableau-engine.js +49 -22
  32. package/dist/profiles/shared/tableau-engine.js.map +1 -1
  33. package/dist/runtime/educational-notes.d.ts +27 -0
  34. package/dist/runtime/educational-notes.d.ts.map +1 -0
  35. package/dist/runtime/educational-notes.js +100 -0
  36. package/dist/runtime/educational-notes.js.map +1 -0
  37. package/dist/runtime/format.d.ts.map +1 -1
  38. package/dist/runtime/format.js +4 -0
  39. package/dist/runtime/format.js.map +1 -1
  40. package/dist/runtime/formula-factory.d.ts +26 -0
  41. package/dist/runtime/formula-factory.d.ts.map +1 -0
  42. package/dist/runtime/formula-factory.js +67 -0
  43. package/dist/runtime/formula-factory.js.map +1 -0
  44. package/dist/runtime/interpreter.d.ts +41 -0
  45. package/dist/runtime/interpreter.d.ts.map +1 -1
  46. package/dist/runtime/interpreter.js +1039 -246
  47. package/dist/runtime/interpreter.js.map +1 -1
  48. package/dist/tests/arithmetic.test.js +2 -1
  49. package/dist/tests/arithmetic.test.js.map +1 -1
  50. package/dist/tests/examples.test.js +12 -1
  51. package/dist/tests/examples.test.js.map +1 -1
  52. package/dist/tests/exhaustive-matrix.test.js +1 -1
  53. package/dist/tests/exhaustive-matrix.test.js.map +1 -1
  54. package/dist/tests/limits.test.js +17 -4
  55. package/dist/tests/limits.test.js.map +1 -1
  56. package/dist/tests/result-bindings.test.d.ts +2 -0
  57. package/dist/tests/result-bindings.test.d.ts.map +1 -0
  58. package/dist/tests/result-bindings.test.js +59 -0
  59. package/dist/tests/result-bindings.test.js.map +1 -0
  60. package/dist/tests/stress-hardware.test.d.ts +2 -0
  61. package/dist/tests/stress-hardware.test.d.ts.map +1 -0
  62. package/dist/tests/stress-hardware.test.js +673 -0
  63. package/dist/tests/stress-hardware.test.js.map +1 -0
  64. package/dist/tests/v1-features.test.js +1 -1
  65. package/dist/tests/v1-features.test.js.map +1 -1
  66. package/dist/types/index.d.ts +1 -1
  67. package/dist/types/index.d.ts.map +1 -1
  68. package/dist/utils/memo.d.ts +3 -0
  69. package/dist/utils/memo.d.ts.map +1 -1
  70. package/dist/utils/memo.js +30 -0
  71. package/dist/utils/memo.js.map +1 -1
  72. 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,12 +113,133 @@ 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 = [];
101
- const parser = new parser_1.Parser(file);
231
+ this.runtimeStepCount = 0;
232
+ this.runtimeCallCount = 0;
233
+ // Pre-scan for profile declarations to enable profile-aware lexing.
234
+ // Only restrict keywords when there's exactly one profile in the source.
235
+ // Multi-profile files use all keywords for safety.
236
+ const profileMatches = source.match(/(?:^|\n)\s*(?:logic|logica)\s+([\w.]+)/g);
237
+ let detectedProfile;
238
+ if (profileMatches && profileMatches.length === 1) {
239
+ const m = profileMatches[0].match(/(?:logic|logica)\s+([\w.]+)/);
240
+ detectedProfile = m ? m[1] : undefined;
241
+ }
242
+ const parser = new parser_1.Parser(file, detectedProfile);
102
243
  const program = parser.parse(source);
103
244
  this.diagnostics.push(...parser.diagnostics);
104
245
  if (parser.diagnostics.some((d) => d.severity === 'error')) {
@@ -113,21 +254,7 @@ class Interpreter {
113
254
  results: [],
114
255
  };
115
256
  }
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
- }
257
+ this.executeStatementsIterative(program.statements, file);
131
258
  const hasErrors = this.diagnostics.some((d) => d.severity === 'error');
132
259
  return {
133
260
  stdout: this.stdoutLines.join('\n'),
@@ -148,6 +275,8 @@ class Interpreter {
148
275
  this.diagnostics = [...parser.diagnostics];
149
276
  this.results = [];
150
277
  this.stdoutLines = [];
278
+ this.runtimeStepCount = 0;
279
+ this.runtimeCallCount = 0;
151
280
  if (parser.diagnostics.some((d) => d.severity === 'error')) {
152
281
  return {
153
282
  stdout: '',
@@ -157,18 +286,7 @@ class Interpreter {
157
286
  results: [],
158
287
  };
159
288
  }
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
- }
289
+ this.executeStatementsIterative(program.statements, '<repl>');
172
290
  return {
173
291
  stdout: this.stdoutLines.join('\n'),
174
292
  stderr: this.diagnostics
@@ -264,9 +382,268 @@ class Interpreter {
264
382
  return this.execExportDecl(stmt);
265
383
  }
266
384
  }
385
+ executeStatementsIterative(statements, fallbackFile) {
386
+ const runtimeStack = [{ kind: 'statements', statements, index: 0 }];
387
+ while (runtimeStack.length > 0) {
388
+ if (this.returnSignal)
389
+ break;
390
+ const frame = runtimeStack[runtimeStack.length - 1];
391
+ if (frame.kind === 'statements') {
392
+ if (frame.index >= frame.statements.length) {
393
+ runtimeStack.pop();
394
+ continue;
395
+ }
396
+ const stmt = frame.statements[frame.index++];
397
+ try {
398
+ this.dispatchRuntimeStatement(stmt, runtimeStack);
399
+ }
400
+ catch (e) {
401
+ const message = e instanceof Error ? e.message : String(e);
402
+ this.diagnostics.push({
403
+ severity: this.isSafetyLimitMessage(message || '') ? 'warning' : 'error',
404
+ message: message || 'Error de runtime',
405
+ file: stmt.source.file || fallbackFile,
406
+ line: stmt.source.line,
407
+ column: stmt.source.column,
408
+ });
409
+ }
410
+ continue;
411
+ }
412
+ if (frame.kind === 'for') {
413
+ if (this.returnSignal || frame.index >= frame.stmt.items.length) {
414
+ this.restoreBindingSnapshot(frame.stmt.variable, frame.savedBinding);
415
+ runtimeStack.pop();
416
+ continue;
417
+ }
418
+ const resolved = this.evaluateFormulaValue(frame.stmt.items[frame.index]);
419
+ this.setBinding(frame.stmt.variable, resolved);
420
+ frame.index++;
421
+ runtimeStack.push({ kind: 'statements', statements: frame.stmt.body, index: 0 });
422
+ continue;
423
+ }
424
+ if (this.returnSignal) {
425
+ runtimeStack.pop();
426
+ continue;
427
+ }
428
+ const profile = this.requireProfile();
429
+ const maxIter = frame.stmt.maxIterations || 1000;
430
+ if (frame.iterations >= maxIter) {
431
+ this.diagnostics.push({
432
+ severity: 'warning',
433
+ message: `while: se alcanzó el límite de ${maxIter} iteraciones`,
434
+ file: frame.stmt.source.file,
435
+ line: frame.stmt.source.line,
436
+ column: frame.stmt.source.column,
437
+ });
438
+ runtimeStack.pop();
439
+ continue;
440
+ }
441
+ const resolved = this.evaluateFormulaValue(frame.stmt.formula);
442
+ const matched = this.matchesRuntimeCondition(profile, frame.stmt.condition, resolved);
443
+ if (!matched) {
444
+ runtimeStack.pop();
445
+ continue;
446
+ }
447
+ frame.iterations++;
448
+ runtimeStack.push({ kind: 'statements', statements: frame.stmt.body, index: 0 });
449
+ }
450
+ }
451
+ dispatchRuntimeStatement(stmt, runtimeStack) {
452
+ if (this.returnSignal)
453
+ return;
454
+ this.tickRuntimeStep();
455
+ const directCall = this.getDirectFunctionCall(stmt);
456
+ if (directCall) {
457
+ const result = this.executeFnCall(directCall.call);
458
+ this.applyFunctionContinuation({ type: directCall.type, stmt: directCall.stmt }, result);
459
+ return;
460
+ }
461
+ if (this.isImporting) {
462
+ const sideEffects = [
463
+ 'derive_cmd',
464
+ 'check_valid_cmd',
465
+ 'check_satisfiable_cmd',
466
+ 'check_equivalent_cmd',
467
+ 'prove_cmd',
468
+ 'countermodel_cmd',
469
+ 'truth_table_cmd',
470
+ 'print_cmd',
471
+ 'analyze_cmd',
472
+ 'explain_cmd',
473
+ 'render_cmd',
474
+ ];
475
+ if (sideEffects.includes(stmt.kind) || stmt.kind === 'logic_decl')
476
+ return;
477
+ }
478
+ switch (stmt.kind) {
479
+ case 'if_stmt': {
480
+ const selectedBody = this.selectIfBody(stmt);
481
+ if (selectedBody)
482
+ runtimeStack.push({ kind: 'statements', statements: selectedBody, index: 0 });
483
+ return;
484
+ }
485
+ case 'for_stmt':
486
+ runtimeStack.push({
487
+ kind: 'for',
488
+ stmt,
489
+ index: 0,
490
+ savedBinding: this.captureBindingSnapshot(stmt.variable),
491
+ });
492
+ return;
493
+ case 'while_stmt':
494
+ runtimeStack.push({ kind: 'while', stmt, iterations: 0 });
495
+ return;
496
+ case 'logic_decl':
497
+ return this.execLogicDecl(stmt);
498
+ case 'axiom_decl':
499
+ return this.execAxiomDecl(stmt);
500
+ case 'theorem_decl':
501
+ return this.execTheoremDecl(stmt);
502
+ case 'derive_cmd':
503
+ return this.execDeriveCmd(stmt);
504
+ case 'check_valid_cmd':
505
+ return this.execCheckValidCmd(stmt);
506
+ case 'check_satisfiable_cmd':
507
+ return this.execCheckSatisfiableCmd(stmt);
508
+ case 'check_equivalent_cmd':
509
+ return this.execCheckEquivalentCmd(stmt);
510
+ case 'prove_cmd':
511
+ return this.execProveCmd(stmt);
512
+ case 'countermodel_cmd':
513
+ return this.execCountermodelCmd(stmt);
514
+ case 'truth_table_cmd':
515
+ return this.execTruthTableCmd(stmt);
516
+ case 'let_decl':
517
+ return this.execLetDecl(stmt);
518
+ case 'claim_decl':
519
+ return this.execClaimDecl(stmt);
520
+ case 'support_decl':
521
+ return this.execSupportDecl(stmt);
522
+ case 'confidence_decl':
523
+ return this.execConfidenceDecl(stmt);
524
+ case 'context_decl':
525
+ return this.execContextDecl(stmt);
526
+ case 'render_cmd':
527
+ return this.execRenderCmd(stmt);
528
+ case 'analyze_cmd':
529
+ return this.execAnalyzeCmd(stmt);
530
+ case 'explain_cmd':
531
+ return this.execExplainCmd(stmt);
532
+ case 'import_decl':
533
+ return this.execImportDecl(stmt);
534
+ case 'proof_block':
535
+ return this.execProofBlock(stmt);
536
+ case 'theory_decl':
537
+ return this.execTheoryDecl(stmt);
538
+ case 'print_cmd':
539
+ return this.execPrintCmd(stmt);
540
+ case 'set_cmd':
541
+ return this.execSetCmd(stmt);
542
+ case 'fn_decl':
543
+ return this.execFnDecl(stmt);
544
+ case 'return_stmt':
545
+ return this.execReturnStmt(stmt);
546
+ case 'fn_call':
547
+ this.executeFnCall(stmt);
548
+ return;
549
+ case 'export_decl':
550
+ return this.execExportDecl(stmt);
551
+ }
552
+ }
553
+ selectIfBody(stmt) {
554
+ const profile = this.requireProfile();
555
+ for (const branch of stmt.branches) {
556
+ const resolved = this.evaluateFormulaValue(branch.formula);
557
+ const matched = this.matchesRuntimeCondition(profile, branch.condition, resolved);
558
+ if (matched)
559
+ return branch.body;
560
+ }
561
+ return stmt.elseBranch;
562
+ }
563
+ matchesRuntimeCondition(profile, condition, formula) {
564
+ if (profile.name === 'arithmetic') {
565
+ const numeric = (0, arithmetic_1.evalNumeric)(formula);
566
+ const truthy = !Number.isNaN(numeric) && numeric !== 0;
567
+ if (condition === 'valid' || condition === 'satisfiable')
568
+ return truthy;
569
+ return !truthy;
570
+ }
571
+ if (condition === 'valid' || condition === 'invalid') {
572
+ const result = profile.checkValid(formula);
573
+ return condition === 'valid' ? result.status === 'valid' : result.status !== 'valid';
574
+ }
575
+ const result = profile.checkSatisfiable(formula);
576
+ return condition === 'satisfiable'
577
+ ? result.status === 'satisfiable' || result.status === 'valid'
578
+ : result.status === 'unsatisfiable';
579
+ }
580
+ evaluateFormulaValue(formula) {
581
+ if (formula.kind === 'fn_call' && formula.name) {
582
+ const result = this.executeFnCall({ name: formula.name, args: formula.args || [] });
583
+ return result || { kind: 'atom', name: 'undefined', source: formula.source };
584
+ }
585
+ return this.resolveFormula(formula);
586
+ }
587
+ getDirectFunctionCall(stmt) {
588
+ if (stmt.kind === 'fn_call') {
589
+ return { call: { name: stmt.name, args: stmt.args }, type: 'discard' };
590
+ }
591
+ if (stmt.kind === 'let_decl' && stmt.letType === 'formula' && stmt.formula.kind === 'fn_call') {
592
+ return {
593
+ call: { name: stmt.formula.name || '', args: stmt.formula.args || [] },
594
+ type: 'let_decl',
595
+ stmt,
596
+ };
597
+ }
598
+ if (stmt.kind === 'set_cmd' && stmt.formula.kind === 'fn_call') {
599
+ return {
600
+ call: { name: stmt.formula.name || '', args: stmt.formula.args || [] },
601
+ type: 'set_cmd',
602
+ stmt,
603
+ };
604
+ }
605
+ if (stmt.kind === 'return_stmt' && stmt.formula?.kind === 'fn_call') {
606
+ return {
607
+ call: { name: stmt.formula.name || '', args: stmt.formula.args || [] },
608
+ type: 'return_stmt',
609
+ stmt,
610
+ };
611
+ }
612
+ if (stmt.kind === 'print_cmd' && stmt.formula?.kind === 'fn_call') {
613
+ return {
614
+ call: { name: stmt.formula.name || '', args: stmt.formula.args || [] },
615
+ type: 'print_cmd',
616
+ stmt,
617
+ };
618
+ }
619
+ return undefined;
620
+ }
621
+ applyFunctionContinuation(continuation, result) {
622
+ const normalized = result || { kind: 'atom', name: 'undefined' };
623
+ switch (continuation.type) {
624
+ case 'discard':
625
+ return;
626
+ case 'let_decl': {
627
+ const letStmt = continuation.stmt;
628
+ if (letStmt.letType === 'formula') {
629
+ return this.execLetDecl({ ...letStmt, formula: normalized });
630
+ }
631
+ return;
632
+ }
633
+ case 'set_cmd':
634
+ return this.execSetCmd({ ...continuation.stmt, formula: normalized });
635
+ case 'return_stmt':
636
+ return this.execReturnStmt({
637
+ ...continuation.stmt,
638
+ formula: normalized,
639
+ });
640
+ case 'print_cmd':
641
+ return this.execPrintCmd({ ...continuation.stmt, formula: normalized });
642
+ }
643
+ }
267
644
  execExportDecl(stmt) {
268
645
  // Ejecutar la declaración interna
269
- this.executeStatement(stmt.statement);
646
+ this.executeStatementsIterative([stmt.statement], stmt.source.file);
270
647
  // Registrarla como exportada
271
648
  const s = stmt.statement;
272
649
  switch (s.kind) {
@@ -302,21 +679,43 @@ class Interpreter {
302
679
  * por sus fórmulas definidas. Detecta ciclos para evitar recursión infinita.
303
680
  * Soporta notación con punto: Theory.member resuelve desde el scope de la teoría.
304
681
  */
682
+ invalidateResolveCache() {
683
+ this.resolveCache = new WeakMap();
684
+ this.resolveCacheGeneration++;
685
+ }
305
686
  resolveFormula(f, visited = new Set()) {
687
+ // Check resolution cache first
688
+ const cached = this.resolveCache.get(f);
689
+ if (cached !== undefined)
690
+ return cached;
306
691
  const resolved = this.resolveFormulaRecursive(f, visited);
307
- return this.tryConstantFold(resolved);
692
+ // Constant-fold pure arithmetic (add, subtract, etc.) but NOT comparisons
693
+ // so profiles can still see the structural form of comparisons.
694
+ const folded = this.tryConstantFold(resolved);
695
+ // Cache the result and deduplicate via FormulaFactory
696
+ const result = formula_factory_1.FormulaFactory.create(folded);
697
+ this.resolveCache.set(f, result);
698
+ return result;
308
699
  }
309
- resolveFormulaRecursive(f, visited = new Set()) {
700
+ resolveFormulaRecursive(f, visited) {
310
701
  if (!f)
311
702
  return f;
312
703
  // Si es un átomo, intentar resolver
313
704
  if (f.kind === 'atom' && f.name) {
705
+ if (this.hasBinding(f.name)) {
706
+ if (visited.has(f.name))
707
+ return f;
708
+ visited.add(f.name);
709
+ const result = this.resolveFormulaRecursive(this.getBinding(f.name), visited);
710
+ visited.delete(f.name);
711
+ return result;
712
+ }
314
713
  // Dot notation: Theory.member o instance.member
315
714
  if (f.name.includes('.')) {
316
715
  const [prefix, memberName] = f.name.split('.', 2);
317
716
  // 1. Intentar resolver el prefijo como una variable local (instancia)
318
- if (this.letBindings.has(prefix)) {
319
- const resolvedPrefix = this.letBindings.get(prefix);
717
+ if (this.hasBinding(prefix)) {
718
+ const resolvedPrefix = this.getBinding(prefix);
320
719
  if (resolvedPrefix.kind === 'atom' && resolvedPrefix.name) {
321
720
  const actualInstanceName = resolvedPrefix.name;
322
721
  const scope = this.theories.get(actualInstanceName);
@@ -324,12 +723,12 @@ class Interpreter {
324
723
  if (scope.privateMembers.has(memberName) &&
325
724
  this.currentTheoryName !== actualInstanceName)
326
725
  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));
726
+ const member = scope.letBindings.get(memberName) ??
727
+ scope.axioms.get(memberName) ??
728
+ scope.theorems.get(memberName);
729
+ if (member) {
730
+ return this.resolveFormulaRecursive(member, visited);
731
+ }
333
732
  }
334
733
  }
335
734
  }
@@ -338,37 +737,31 @@ class Interpreter {
338
737
  if (scope) {
339
738
  if (scope.privateMembers.has(memberName) && this.currentTheoryName !== prefix)
340
739
  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));
740
+ const member = scope.letBindings.get(memberName) ??
741
+ scope.axioms.get(memberName) ??
742
+ scope.theorems.get(memberName);
743
+ if (member) {
744
+ return this.resolveFormulaRecursive(member, visited);
745
+ }
347
746
  }
348
747
  return f;
349
748
  }
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
749
  // También resolver axiomas/teoremas del theory actual por nombre
359
750
  if (this.theory.axioms.has(f.name)) {
360
751
  if (visited.has(f.name))
361
752
  return f;
362
- const newVisited = new Set(visited);
363
- newVisited.add(f.name);
364
- return this.resolveFormulaRecursive(this.theory.axioms.get(f.name), newVisited);
753
+ visited.add(f.name);
754
+ const result = this.resolveFormulaRecursive(this.theory.axioms.get(f.name), visited);
755
+ visited.delete(f.name);
756
+ return result;
365
757
  }
366
758
  if (this.theory.theorems.has(f.name)) {
367
759
  if (visited.has(f.name))
368
760
  return f;
369
- const newVisited = new Set(visited);
370
- newVisited.add(f.name);
371
- return this.resolveFormulaRecursive(this.theory.theorems.get(f.name), newVisited);
761
+ visited.add(f.name);
762
+ const result = this.resolveFormulaRecursive(this.theory.theorems.get(f.name), visited);
763
+ visited.delete(f.name);
764
+ return result;
372
765
  }
373
766
  }
374
767
  // Llamada a función como expresión
@@ -378,8 +771,16 @@ class Interpreter {
378
771
  }
379
772
  // Recorrer hijos recursivamente
380
773
  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 };
774
+ const newArgs = f.args.map((a) => (a ? this.resolveFormulaRecursive(a, visited) : a));
775
+ // Check if args actually changed to avoid unnecessary object creation
776
+ let changed = false;
777
+ for (let i = 0; i < f.args.length; i++) {
778
+ if (f.args[i] !== newArgs[i]) {
779
+ changed = true;
780
+ break;
781
+ }
782
+ }
783
+ return changed ? { ...f, args: newArgs } : f;
383
784
  }
384
785
  return f;
385
786
  }
@@ -456,10 +857,60 @@ class Interpreter {
456
857
  }
457
858
  execTruthTableCmd(stmt) {
458
859
  const profile = this.requireProfile();
860
+ const formula = this.resolveFormula(stmt.formula);
861
+ if (profile.name === 'classical.propositional') {
862
+ const atoms = Array.from((0, propositional_1.collectAtoms)(formula)).sort();
863
+ // Streaming de tabla de verdad para evitar OOM
864
+ this.emit(`Tabla de verdad para ${(0, propositional_1.formulaToString)(formula)}:`);
865
+ this.emit(` ${atoms.join(' | ')} | Resultado`);
866
+ this.emit(` ${atoms.map(() => '---').join('-|-')}-|----------`);
867
+ let isTautology = true;
868
+ let isSatisfiable = false;
869
+ let count = 0;
870
+ let satCount = 0;
871
+ for (const v of (0, propositional_1.generateValuationsLazy)(atoms)) {
872
+ const res = (0, propositional_1.evaluateClassical)(formula, v);
873
+ if (res) {
874
+ isSatisfiable = true;
875
+ satCount++;
876
+ }
877
+ else {
878
+ isTautology = false;
879
+ }
880
+ count++;
881
+ // Solo imprimir las primeras 64 filas para no saturar el stdout en tablas masivas
882
+ if (count <= 64) {
883
+ const row = atoms.map((a) => (v[a] ? 'V' : 'F')).join(' | ');
884
+ this.emit(` ${row} | ${res ? 'V' : 'F'}`);
885
+ }
886
+ else if (count === 65) {
887
+ this.emit(' ... (tabla masiva, ocultando filas intermedias) ...');
888
+ }
889
+ }
890
+ this.emit(`\nResumen: ${count} valuaciones analizadas.`);
891
+ this.emit(`Estatus: ${isTautology ? 'Tautología ✓' : isSatisfiable ? 'Satisfacible' : 'Contradicción ✗'}`);
892
+ this.results.push({
893
+ status: isTautology ? 'valid' : isSatisfiable ? 'satisfiable' : 'unsatisfiable',
894
+ output: `Tabla de verdad de ${count} filas procesada.`,
895
+ truthTable: {
896
+ variables: atoms,
897
+ rows: [],
898
+ subFormulas: [],
899
+ subFormulaValues: [],
900
+ isTautology,
901
+ isContradiction: !isSatisfiable,
902
+ isSatisfiable,
903
+ satisfyingCount: satCount,
904
+ },
905
+ diagnostics: [],
906
+ formula: formula,
907
+ });
908
+ return;
909
+ }
910
+ // Fallback para otros perfiles
459
911
  if (!profile.truthTable) {
460
912
  throw new Error('Este perfil no soporta truth_table');
461
913
  }
462
- const formula = this.resolveFormula(stmt.formula);
463
914
  const tt = profile.truthTable(formula);
464
915
  const result = {
465
916
  status: tt.isTautology ? 'valid' : tt.isSatisfiable ? 'satisfiable' : 'unsatisfiable',
@@ -485,19 +936,37 @@ class Interpreter {
485
936
  this.emit(`Formalizacion ${stmt.name}: ${stmt.passageName} -> ${(0, propositional_1.formulaToString)(formula)}`);
486
937
  }
487
938
  else if (stmt.letType === 'description') {
488
- this.letDescriptions.set(stmt.name, stmt.description);
489
- this.emit(`Let ${stmt.name} = "${stmt.description}"`);
939
+ if (this.currentBindingFrame)
940
+ this.currentBindingFrame.descriptions.set(stmt.name, stmt.description);
941
+ else
942
+ this.letDescriptions.set(stmt.name, stmt.description);
943
+ if (this.shouldEmitLocalBindings())
944
+ this.emit(`Let ${stmt.name} = "${stmt.description}"`);
945
+ }
946
+ else if (stmt.letType === 'action') {
947
+ const actionStmt = stmt;
948
+ const captured = this.executeActionExpr(actionStmt.action);
949
+ this.results.push(captured.result);
950
+ this.bindCapturedAction(actionStmt.name, actionStmt.action.action, captured.primary, captured.result, captured.formulas, captured.extraBindings);
951
+ if (this.shouldEmitLocalBindings()) {
952
+ this.emit(`Let ${actionStmt.name} = ${(0, format_1.formulaToUnicode)(captured.primary)}`);
953
+ }
490
954
  }
491
955
  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);
956
+ // Resolve bindings but preserve symbolic structure (no constant-folding)
957
+ const resolved = this.resolveFormulaRecursive(stmt.formula, new Set());
958
+ this.defineBinding(stmt.name, resolved, 'description' in stmt ? stmt.description : undefined);
959
+ if (!this.currentBindingFrame)
960
+ this.theory.axioms.set(stmt.name, resolved);
495
961
  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)}`);
962
+ if (!this.currentBindingFrame)
963
+ this.letDescriptions.set(stmt.name, stmt.description);
964
+ if (this.shouldEmitLocalBindings())
965
+ this.emit(`Let ${stmt.name} = "${stmt.description}" : ${(0, format_1.formulaToUnicode)(resolved)}`);
498
966
  }
499
967
  else {
500
- this.emit(`Let ${stmt.name} = ${(0, format_1.formulaToUnicode)(resolved)}`);
968
+ if (this.shouldEmitLocalBindings())
969
+ this.emit(`Let ${stmt.name} = ${(0, format_1.formulaToUnicode)(resolved)}`);
501
970
  }
502
971
  }
503
972
  }
@@ -637,21 +1106,7 @@ class Interpreter {
637
1106
  }
638
1107
  const resolvedGoal = this.resolveFormula(stmt.goal);
639
1108
  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
- }
1109
+ this.executeStatementsIterative(stmt.body, stmt.source.file);
655
1110
  const premiseNames = stmt.assumptions.map((a) => a.name);
656
1111
  const result = profile.derive(resolvedGoal, premiseNames, this.theory);
657
1112
  this.results.push(result);
@@ -670,6 +1125,7 @@ class Interpreter {
670
1125
  this.theory.axioms = savedAxioms;
671
1126
  this.letBindings = savedLetBindings;
672
1127
  this.letDescriptions = savedLetDescriptions;
1128
+ this.invalidateResolveCache();
673
1129
  this.emit('── End Proof Block ──');
674
1130
  }
675
1131
  execTheoryDecl(stmt) {
@@ -768,6 +1224,7 @@ class Interpreter {
768
1224
  this.theory.axioms = savedAxioms;
769
1225
  this.theory.theorems = savedTheorems;
770
1226
  this.currentTheoryName = savedTheoryName;
1227
+ this.invalidateResolveCache();
771
1228
  this.theories.set(theoryName, scope);
772
1229
  this.emit(`── End Theory Instance ${theoryName} ──`);
773
1230
  return theoryName;
@@ -776,107 +1233,29 @@ class Interpreter {
776
1233
  if (stmt.value !== null)
777
1234
  this.emit(stmt.value);
778
1235
  else if (stmt.formula) {
779
- const resolved = this.resolveFormula(stmt.formula);
1236
+ // Resolve bindings but skip constant-folding so print shows symbolic form
1237
+ const resolved = this.resolveFormulaRecursive(stmt.formula, new Set());
780
1238
  this.emit((0, format_1.formulaToUnicode)(resolved));
781
1239
  }
782
1240
  }
783
1241
  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)}`);
1242
+ const resolved = this.evaluateFormulaValue(stmt.formula);
1243
+ this.setBinding(stmt.name, resolved);
1244
+ if (!this.currentBindingFrame)
1245
+ this.theory.axioms.set(stmt.name, resolved);
1246
+ if (this.shouldEmitLocalBindings())
1247
+ this.emit(`Set ${stmt.name} = ${(0, format_1.formulaToUnicode)(resolved)}`);
788
1248
  }
789
1249
  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
- }
1250
+ const selectedBody = this.selectIfBody(stmt);
1251
+ if (selectedBody)
1252
+ this.executeStatementsIterative(selectedBody, stmt.source.file);
822
1253
  }
823
1254
  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);
1255
+ this.executeStatementsIterative([stmt], stmt.source.file);
840
1256
  }
841
1257
  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
- }
1258
+ this.executeStatementsIterative([stmt], stmt.source.file);
880
1259
  }
881
1260
  execFnDecl(stmt) {
882
1261
  const name = this.currentTheoryName ? `${this.currentTheoryName}.${stmt.name}` : stmt.name;
@@ -888,77 +1267,249 @@ class Interpreter {
888
1267
  }
889
1268
  execReturnStmt(stmt) {
890
1269
  if (stmt.formula)
891
- this.returnValue = this.resolveFormula(stmt.formula);
1270
+ this.returnValue = this.evaluateFormulaValue(stmt.formula);
892
1271
  else
893
1272
  this.returnValue = undefined;
894
1273
  this.returnSignal = true;
895
1274
  }
896
1275
  executeFnCall(stmt) {
1276
+ const initial = this.startFunctionFrame(stmt, undefined);
1277
+ if ('result' in initial)
1278
+ return initial.result;
1279
+ const callStack = [initial.frame];
1280
+ let finalResult = undefined;
1281
+ while (callStack.length > 0) {
1282
+ const frame = callStack[callStack.length - 1];
1283
+ if (this.returnSignal) {
1284
+ const result = this.finishFunctionFrame(frame);
1285
+ callStack.pop();
1286
+ if (frame.continuation)
1287
+ this.applyFunctionContinuation(frame.continuation, result);
1288
+ else
1289
+ finalResult = result;
1290
+ continue;
1291
+ }
1292
+ const nextStmt = this.nextRuntimeStatement(frame.runtimeStack);
1293
+ if (!nextStmt) {
1294
+ const result = this.finishFunctionFrame(frame);
1295
+ callStack.pop();
1296
+ if (frame.continuation)
1297
+ this.applyFunctionContinuation(frame.continuation, result);
1298
+ else
1299
+ finalResult = result;
1300
+ continue;
1301
+ }
1302
+ const nestedCall = this.getDirectFunctionCall(nextStmt);
1303
+ if (nestedCall) {
1304
+ if (nestedCall.type === 'return_stmt') {
1305
+ const replaced = this.replaceFunctionFrame(frame, nestedCall.call);
1306
+ if ('result' in replaced) {
1307
+ this.returnValue = replaced.result || { kind: 'atom', name: 'undefined' };
1308
+ this.returnSignal = true;
1309
+ }
1310
+ else {
1311
+ callStack[callStack.length - 1] = replaced.frame;
1312
+ }
1313
+ continue;
1314
+ }
1315
+ const started = this.startFunctionFrame(nestedCall.call, {
1316
+ type: nestedCall.type,
1317
+ stmt: nestedCall.stmt,
1318
+ });
1319
+ if ('result' in started)
1320
+ this.applyFunctionContinuation(started.resultContinuation, started.result);
1321
+ else
1322
+ callStack.push(started.frame);
1323
+ continue;
1324
+ }
1325
+ this.dispatchRuntimeStatement(nextStmt, frame.runtimeStack);
1326
+ }
1327
+ return finalResult;
1328
+ }
1329
+ startFunctionFrame(stmt, continuation, inheritedReturnState) {
897
1330
  this.callDepth++;
898
1331
  if (this.callDepth > MAX_CALL_DEPTH) {
899
1332
  this.callDepth--;
900
1333
  throw new Error(`Límite de recursión excedido (${MAX_CALL_DEPTH}).`);
901
1334
  }
902
- try {
903
- if (['typeof', 'is_valid', 'is_satisfiable', 'get_atoms', 'input'].includes(stmt.name)) {
904
- return this.executeBuiltin(stmt.name, stmt.args);
1335
+ this.runtimeCallCount++;
1336
+ if (this.runtimeCallCount > this.getRuntimeCallLimit()) {
1337
+ this.callDepth--;
1338
+ throw new Error(`Límite de llamadas ST excedido (${this.getRuntimeCallLimit()}).`);
1339
+ }
1340
+ if ([
1341
+ 'typeof',
1342
+ 'is_valid',
1343
+ 'is_satisfiable',
1344
+ 'get_atoms',
1345
+ 'atoms_of',
1346
+ 'len',
1347
+ 'at',
1348
+ 'formula_eq',
1349
+ 'input',
1350
+ ].includes(stmt.name)) {
1351
+ const result = this.executeBuiltin(stmt.name, stmt.args);
1352
+ this.callDepth--;
1353
+ return { result, resultContinuation: continuation || { type: 'discard' } };
1354
+ }
1355
+ if (stmt.name.includes('.')) {
1356
+ const [prefix, methodName] = stmt.name.split('.', 2);
1357
+ let actualInstanceName = prefix;
1358
+ if (this.hasBinding(prefix)) {
1359
+ const resolved = this.getBinding(prefix);
1360
+ if (resolved.kind === 'atom' && resolved.name)
1361
+ actualInstanceName = resolved.name;
905
1362
  }
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;
1363
+ const scope = this.theories.get(actualInstanceName);
1364
+ if (scope) {
1365
+ const internalFnName = `${actualInstanceName}.${methodName}`;
1366
+ const fn = this.functions.get(internalFnName);
1367
+ if (fn)
1368
+ return {
1369
+ frame: this.createFunctionFrame(internalFnName, fn, stmt.args, continuation, scope, inheritedReturnState),
1370
+ };
1371
+ }
1372
+ }
1373
+ const template = this.theoryTemplates.get(stmt.name);
1374
+ if (template) {
1375
+ const instanceId = `inst_${stmt.name}_${this.theories.size}`;
1376
+ this.instantiateTheory(template.node, instanceId, stmt.args);
1377
+ this.callDepth--;
1378
+ return {
1379
+ result: { kind: 'atom', name: instanceId },
1380
+ resultContinuation: continuation || { type: 'discard' },
1381
+ };
1382
+ }
1383
+ const fn = this.functions.get(stmt.name);
1384
+ if (!fn) {
1385
+ this.callDepth--;
1386
+ throw new Error(`Función o Teoría '${stmt.name}' no declarada`);
1387
+ }
1388
+ return {
1389
+ frame: this.createFunctionFrame(stmt.name, fn, stmt.args, continuation, undefined, inheritedReturnState),
1390
+ };
1391
+ }
1392
+ createFunctionFrame(name, fn, args, continuation, scope, inheritedReturnState) {
1393
+ if (args.length !== fn.params.length) {
1394
+ this.callDepth--;
1395
+ throw new Error(`Argumentos incorrectos.`);
1396
+ }
1397
+ let scopeSnapshot;
1398
+ if (scope) {
1399
+ scopeSnapshot = {
1400
+ letBindings: new Map(this.letBindings),
1401
+ axioms: new Map(this.theory.axioms),
1402
+ theorems: new Map(this.theory.theorems),
1403
+ theoryName: this.currentTheoryName,
1404
+ };
1405
+ for (const [key, value] of scope.letBindings)
1406
+ this.letBindings.set(key, value);
1407
+ for (const [key, value] of scope.axioms)
1408
+ this.theory.axioms.set(key, value);
1409
+ for (const [key, value] of scope.theorems)
1410
+ this.theory.theorems.set(key, value);
1411
+ this.currentTheoryName = scope.name;
1412
+ }
1413
+ const bindingFrame = this.createBindingFrame();
1414
+ this.currentBindingFrame = bindingFrame;
1415
+ for (let i = 0; i < fn.params.length; i++) {
1416
+ this.currentBindingFrame.bindings.set(fn.params[i], this.evaluateFormulaValue(args[i]));
1417
+ }
1418
+ const savedReturnSignal = inheritedReturnState?.signal ?? this.returnSignal;
1419
+ const savedReturnValue = inheritedReturnState?.value ?? this.returnValue;
1420
+ this.returnSignal = false;
1421
+ this.returnValue = undefined;
1422
+ return {
1423
+ name,
1424
+ runtimeStack: [{ kind: 'statements', statements: fn.body, index: 0 }],
1425
+ bindingFrame,
1426
+ savedReturnSignal,
1427
+ savedReturnValue,
1428
+ scopeSnapshot,
1429
+ continuation,
1430
+ };
1431
+ }
1432
+ finishFunctionFrame(frame) {
1433
+ const result = this.returnValue;
1434
+ this.returnSignal = frame.savedReturnSignal;
1435
+ this.returnValue = frame.savedReturnValue;
1436
+ this.discardFunctionFrameState(frame);
1437
+ this.callDepth--;
1438
+ return result;
1439
+ }
1440
+ discardFunctionFrameState(frame) {
1441
+ this.currentBindingFrame = frame.bindingFrame.parent;
1442
+ if (frame.scopeSnapshot) {
1443
+ this.letBindings = frame.scopeSnapshot.letBindings;
1444
+ this.theory.axioms = frame.scopeSnapshot.axioms;
1445
+ this.theory.theorems = frame.scopeSnapshot.theorems;
1446
+ this.currentTheoryName = frame.scopeSnapshot.theoryName;
1447
+ this.invalidateResolveCache();
1448
+ }
1449
+ }
1450
+ replaceFunctionFrame(frame, stmt) {
1451
+ const inheritedReturnState = {
1452
+ signal: frame.savedReturnSignal,
1453
+ value: frame.savedReturnValue,
1454
+ };
1455
+ const inheritedContinuation = frame.continuation;
1456
+ const evaluatedArgs = stmt.args.map((arg) => this.evaluateFormulaValue(arg));
1457
+ this.discardFunctionFrameState(frame);
1458
+ this.callDepth--;
1459
+ const replaced = this.startFunctionFrame({ name: stmt.name, args: evaluatedArgs }, inheritedContinuation, inheritedReturnState);
1460
+ if ('frame' in replaced)
1461
+ return replaced;
1462
+ return { result: replaced.result };
1463
+ }
1464
+ nextRuntimeStatement(runtimeStack) {
1465
+ while (runtimeStack.length > 0) {
1466
+ const frame = runtimeStack[runtimeStack.length - 1];
1467
+ if (frame.kind === 'statements') {
1468
+ if (frame.index >= frame.statements.length) {
1469
+ runtimeStack.pop();
1470
+ continue;
913
1471
  }
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);
1472
+ return frame.statements[frame.index++];
1473
+ }
1474
+ if (frame.kind === 'for') {
1475
+ if (this.returnSignal || frame.index >= frame.stmt.items.length) {
1476
+ this.restoreBindingSnapshot(frame.stmt.variable, frame.savedBinding);
1477
+ runtimeStack.pop();
1478
+ continue;
920
1479
  }
1480
+ const resolved = this.evaluateFormulaValue(frame.stmt.items[frame.index]);
1481
+ this.setBinding(frame.stmt.variable, resolved);
1482
+ frame.index++;
1483
+ runtimeStack.push({ kind: 'statements', statements: frame.stmt.body, index: 0 });
1484
+ continue;
921
1485
  }
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 };
1486
+ if (this.returnSignal) {
1487
+ runtimeStack.pop();
1488
+ continue;
927
1489
  }
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);
1490
+ const profile = this.requireProfile();
1491
+ const maxIter = frame.stmt.maxIterations || 1000;
1492
+ if (frame.iterations >= maxIter) {
1493
+ this.diagnostics.push({
1494
+ severity: 'warning',
1495
+ message: `while: se alcanzó el límite de ${maxIter} iteraciones`,
1496
+ file: frame.stmt.source.file,
1497
+ line: frame.stmt.source.line,
1498
+ column: frame.stmt.source.column,
1499
+ });
1500
+ runtimeStack.pop();
1501
+ continue;
946
1502
  }
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);
1503
+ const resolved = this.evaluateFormulaValue(frame.stmt.formula);
1504
+ const matched = this.matchesRuntimeCondition(profile, frame.stmt.condition, resolved);
1505
+ if (!matched) {
1506
+ runtimeStack.pop();
1507
+ continue;
956
1508
  }
957
- return result;
958
- }
959
- finally {
960
- this.callDepth--;
1509
+ frame.iterations++;
1510
+ runtimeStack.push({ kind: 'statements', statements: frame.stmt.body, index: 0 });
961
1511
  }
1512
+ return undefined;
962
1513
  }
963
1514
  executeFunctionInScope(fn, args, scope) {
964
1515
  this.callDepth++;
@@ -1009,38 +1560,42 @@ class Interpreter {
1009
1560
  add: (a, b) => a + b,
1010
1561
  subtract: (a, b) => a - b,
1011
1562
  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),
1563
+ divide: (a, b) => a / b,
1564
+ modulo: (a, b) => a % b,
1020
1565
  };
1566
+ // Note: comparison operators (less, greater, etc.) are NOT folded here.
1567
+ // They must retain their structural form so profiles can evaluate them properly.
1021
1568
  const newArgs = f.args.map((a) => this.tryConstantFold(a));
1022
1569
  if (newArgs.length === 2 &&
1023
1570
  newArgs[0].kind === 'number' &&
1024
1571
  newArgs[1].kind === 'number' &&
1025
- (ARITH_OPS[f.kind] || CMP_OPS[f.kind])) {
1026
- const op = ARITH_OPS[f.kind] || CMP_OPS[f.kind];
1572
+ ARITH_OPS[f.kind]) {
1573
+ // Don't fold division/modulo by zero — preserve structure for checkWellFormed
1574
+ if ((f.kind === 'divide' || f.kind === 'modulo') && (newArgs[1].value ?? 0) === 0) {
1575
+ return { ...f, args: newArgs };
1576
+ }
1577
+ const op = ARITH_OPS[f.kind];
1027
1578
  const res = op(newArgs[0].value ?? 0, newArgs[1].value ?? 0);
1028
1579
  return { kind: 'number', value: res, source: f.source };
1029
1580
  }
1030
1581
  return { ...f, args: newArgs };
1031
1582
  }
1032
1583
  executeBuiltin(name, args) {
1033
- const arg = this.resolveFormula(args[0]);
1584
+ const arg = args[0] ? this.resolveFormula(args[0]) : undefined;
1034
1585
  if (name === 'typeof') {
1035
1586
  let typeStr = 'Formula';
1036
- if (arg.kind === 'number')
1587
+ if (arg?.kind === 'number')
1037
1588
  typeStr = 'Number';
1038
- if (arg.kind === 'atom' && arg.name?.startsWith('"'))
1589
+ if (arg?.kind === 'list')
1590
+ typeStr = 'List';
1591
+ if (arg?.kind === 'atom' && arg.name?.startsWith('"'))
1039
1592
  typeStr = 'String';
1040
- return { kind: 'atom', name: `"${typeStr}"`, source: arg.source };
1593
+ return { kind: 'atom', name: `"${typeStr}"`, source: arg?.source };
1041
1594
  }
1042
1595
  if (name === 'is_valid' || name === 'is_satisfiable') {
1043
1596
  const profile = this.requireProfile();
1597
+ if (!arg)
1598
+ return { kind: 'atom', name: '"Error"' };
1044
1599
  try {
1045
1600
  const result = name === 'is_valid' ? profile.checkValid(arg) : profile.checkSatisfiable(arg);
1046
1601
  const isTrue = result.status === 'valid' || result.status === 'satisfiable';
@@ -1051,10 +1606,55 @@ class Interpreter {
1051
1606
  }
1052
1607
  }
1053
1608
  if (name === 'get_atoms') {
1609
+ if (!arg)
1610
+ return { kind: 'atom', name: '"{ }"' };
1054
1611
  const atoms = this.collectAtoms(arg);
1055
1612
  return { kind: 'atom', name: `"{ ${atoms.join(', ')} }"`, source: arg.source };
1056
1613
  }
1614
+ if (name === 'atoms_of') {
1615
+ const source = arg?.source;
1616
+ const items = (arg ? this.collectValueAtoms(arg) : []).map((atomName) => ({
1617
+ kind: 'atom',
1618
+ name: atomName,
1619
+ source,
1620
+ }));
1621
+ return { kind: 'list', args: items, source };
1622
+ }
1623
+ if (name === 'len') {
1624
+ if (arg?.kind === 'list') {
1625
+ return { kind: 'number', value: arg.args?.length ?? 0, source: arg.source };
1626
+ }
1627
+ if (arg?.kind === 'atom' && arg.name?.startsWith('"')) {
1628
+ return {
1629
+ kind: 'number',
1630
+ value: arg.name.replace(/(^"|"$)/g, '').length,
1631
+ source: arg.source,
1632
+ };
1633
+ }
1634
+ return { kind: 'number', value: 0, source: arg?.source };
1635
+ }
1636
+ if (name === 'at') {
1637
+ const listArg = arg;
1638
+ const indexArg = args[1] ? this.resolveFormula(args[1]) : undefined;
1639
+ if (listArg?.kind === 'list' && indexArg?.kind === 'number') {
1640
+ const index = Math.floor(indexArg.value ?? -1);
1641
+ return listArg.args?.[index] || { kind: 'atom', name: 'undefined', source: listArg.source };
1642
+ }
1643
+ return { kind: 'atom', name: 'undefined', source: listArg?.source || indexArg?.source };
1644
+ }
1645
+ if (name === 'formula_eq') {
1646
+ const left = arg;
1647
+ const right = args[1] ? this.resolveFormula(args[1]) : undefined;
1648
+ const equal = !!left && !!right && (0, propositional_1.formulaToString)(left) === (0, propositional_1.formulaToString)(right);
1649
+ return {
1650
+ kind: 'number',
1651
+ value: equal ? 1 : 0,
1652
+ source: left?.source || right?.source,
1653
+ };
1654
+ }
1057
1655
  if (name === 'input') {
1656
+ if (!arg)
1657
+ return { kind: 'atom', name: '""' };
1058
1658
  const prompt = arg.kind === 'atom' && arg.name?.startsWith('"')
1059
1659
  ? arg.name.replace(/(^"|"$)/g, '')
1060
1660
  : (0, propositional_1.formulaToString)(arg);
@@ -1075,6 +1675,197 @@ class Interpreter {
1075
1675
  }
1076
1676
  return undefined;
1077
1677
  }
1678
+ executeActionExpr(action) {
1679
+ const profile = this.requireProfile();
1680
+ switch (action.action) {
1681
+ case 'check_valid': {
1682
+ const formula = this.resolveFormula(action.formula);
1683
+ return {
1684
+ result: profile.checkValid(formula),
1685
+ primary: formula,
1686
+ formulas: [formula],
1687
+ extraBindings: [],
1688
+ };
1689
+ }
1690
+ case 'check_satisfiable': {
1691
+ const formula = this.resolveFormula(action.formula);
1692
+ return {
1693
+ result: profile.checkSatisfiable(formula),
1694
+ primary: formula,
1695
+ formulas: [formula],
1696
+ extraBindings: [],
1697
+ };
1698
+ }
1699
+ case 'check_equivalent': {
1700
+ if (!profile.checkEquivalent) {
1701
+ throw new Error('Este perfil no soporta check equivalent');
1702
+ }
1703
+ const left = this.resolveFormula(action.left);
1704
+ const right = this.resolveFormula(action.right);
1705
+ const comparison = {
1706
+ kind: 'biconditional',
1707
+ args: [left, right],
1708
+ source: action.source,
1709
+ };
1710
+ return {
1711
+ result: profile.checkEquivalent(left, right),
1712
+ primary: comparison,
1713
+ formulas: [left, right],
1714
+ extraBindings: [
1715
+ ['left', left],
1716
+ ['right', right],
1717
+ [
1718
+ 'equivalent',
1719
+ {
1720
+ kind: 'number',
1721
+ value: (0, propositional_1.formulaToString)(left) === (0, propositional_1.formulaToString)(right) ? 1 : 0,
1722
+ source: action.source,
1723
+ },
1724
+ ],
1725
+ ],
1726
+ };
1727
+ }
1728
+ case 'derive': {
1729
+ const goal = this.resolveFormula(action.goal);
1730
+ const premises = action.premises || [];
1731
+ const result = profile.derive(goal, premises, this.theory);
1732
+ const premiseFormulas = premises
1733
+ .map((premise) => this.theory.axioms.get(premise) || this.theory.theorems.get(premise))
1734
+ .filter((value) => !!value)
1735
+ .map((formula) => this.resolveFormula(formula));
1736
+ return {
1737
+ result,
1738
+ primary: result.formula || goal,
1739
+ formulas: [goal, ...premiseFormulas],
1740
+ extraBindings: [
1741
+ ['goal', goal],
1742
+ ['premises', this.createListFormula(premiseFormulas, action.source)],
1743
+ ['premise_names', this.createStringListFormula(premises, action.source)],
1744
+ ],
1745
+ };
1746
+ }
1747
+ case 'prove': {
1748
+ const goal = this.resolveFormula(action.goal);
1749
+ const premises = action.premises || [];
1750
+ const result = profile.prove(goal, this.theory);
1751
+ const premiseFormulas = premises
1752
+ .map((premise) => this.theory.axioms.get(premise) || this.theory.theorems.get(premise))
1753
+ .filter((value) => !!value)
1754
+ .map((formula) => this.resolveFormula(formula));
1755
+ return {
1756
+ result,
1757
+ primary: result.formula || goal,
1758
+ formulas: [goal, ...premiseFormulas],
1759
+ extraBindings: [
1760
+ ['goal', goal],
1761
+ ['premises', this.createListFormula(premiseFormulas, action.source)],
1762
+ ['premise_names', this.createStringListFormula(premises, action.source)],
1763
+ ],
1764
+ };
1765
+ }
1766
+ case 'countermodel': {
1767
+ const formula = this.resolveFormula(action.formula);
1768
+ const result = profile.countermodel(formula);
1769
+ return {
1770
+ result,
1771
+ primary: result.formula || formula,
1772
+ formulas: [formula],
1773
+ extraBindings: [
1774
+ [
1775
+ 'has_countermodel',
1776
+ { kind: 'number', value: result.model ? 1 : 0, source: action.source },
1777
+ ],
1778
+ ],
1779
+ };
1780
+ }
1781
+ case 'truth_table': {
1782
+ if (!profile.truthTable) {
1783
+ throw new Error('Este perfil no soporta truth_table');
1784
+ }
1785
+ const formula = this.resolveFormula(action.formula);
1786
+ const tt = profile.truthTable(formula);
1787
+ const result = {
1788
+ status: tt.isTautology ? 'valid' : tt.isSatisfiable ? 'satisfiable' : 'unsatisfiable',
1789
+ output: this.formatTruthTable(formula, tt),
1790
+ truthTable: tt,
1791
+ diagnostics: [],
1792
+ formula,
1793
+ };
1794
+ return {
1795
+ result,
1796
+ primary: formula,
1797
+ formulas: [formula],
1798
+ extraBindings: [
1799
+ ['variables', this.createAtomListFormula(tt.variables, action.source)],
1800
+ ['rows_count', { kind: 'number', value: tt.rows.length, source: action.source }],
1801
+ [
1802
+ 'satisfying_count',
1803
+ { kind: 'number', value: tt.satisfyingCount ?? 0, source: action.source },
1804
+ ],
1805
+ ],
1806
+ };
1807
+ }
1808
+ case 'explain': {
1809
+ const formula = this.resolveFormula(action.formula);
1810
+ return {
1811
+ result: profile.explain(formula),
1812
+ primary: formula,
1813
+ formulas: [formula],
1814
+ extraBindings: [],
1815
+ };
1816
+ }
1817
+ }
1818
+ }
1819
+ bindCapturedAction(baseName, actionName, primary, result, formulas, extraBindings) {
1820
+ this.defineBinding(baseName, primary);
1821
+ this.defineBinding(`${baseName}.formula`, primary);
1822
+ this.defineBinding(`${baseName}.status`, this.createStringFormula(result.status, primary.source));
1823
+ this.defineBinding(`${baseName}.output`, this.createStringFormula(result.output || '', primary.source));
1824
+ this.defineBinding(`${baseName}.ok`, {
1825
+ kind: 'number',
1826
+ value: this.isSuccessfulStatus(result.status) ? 1 : 0,
1827
+ source: primary.source,
1828
+ });
1829
+ this.defineBinding(`${baseName}.command`, this.createStringFormula(actionName, primary.source));
1830
+ this.defineBinding(`${baseName}.formulas`, this.createListFormula(formulas, primary.source));
1831
+ this.defineBinding(`${baseName}.diagnostics`, this.createStringListFormula(result.diagnostics.map((diag) => diag.message), primary.source));
1832
+ for (const [suffix, value] of extraBindings) {
1833
+ this.defineBinding(`${baseName}.${suffix}`, value);
1834
+ }
1835
+ }
1836
+ createStringFormula(value, source) {
1837
+ return {
1838
+ kind: 'atom',
1839
+ name: `"${value.replace(/"/g, '\\"')}"`,
1840
+ source,
1841
+ };
1842
+ }
1843
+ createListFormula(items, source) {
1844
+ return { kind: 'list', args: items, source };
1845
+ }
1846
+ createAtomListFormula(items, source) {
1847
+ return this.createListFormula(items.map((item) => ({ kind: 'atom', name: item, source })), source);
1848
+ }
1849
+ createStringListFormula(items, source) {
1850
+ return this.createListFormula(items.map((item) => this.createStringFormula(item, source)), source);
1851
+ }
1852
+ isSuccessfulStatus(status) {
1853
+ return status === 'valid' || status === 'satisfiable' || status === 'provable';
1854
+ }
1855
+ collectValueAtoms(formula) {
1856
+ const seen = new Set();
1857
+ const walk = (value) => {
1858
+ if (value.kind === 'atom' && value.name && !value.name.startsWith('"')) {
1859
+ seen.add(value.name);
1860
+ return;
1861
+ }
1862
+ for (const arg of value.args || []) {
1863
+ walk(arg);
1864
+ }
1865
+ };
1866
+ walk(formula);
1867
+ return Array.from(seen);
1868
+ }
1078
1869
  execImportDecl(stmt) {
1079
1870
  let filePath = stmt.path;
1080
1871
  if (!filePath.endsWith('.st'))
@@ -1152,7 +1943,7 @@ class Interpreter {
1152
1943
  this.stdoutLines.push(msg);
1153
1944
  }
1154
1945
  getVerbosity() {
1155
- const v = this.letBindings.get('verbose');
1946
+ const v = this.getBinding('verbose');
1156
1947
  if (v && v.kind === 'atom' && v.name)
1157
1948
  return v.name.toLowerCase().replace(/(^"|"$)/g, '');
1158
1949
  return 'off';
@@ -1197,8 +1988,10 @@ class Interpreter {
1197
1988
  statusIcon(status) {
1198
1989
  switch (status) {
1199
1990
  case 'valid':
1991
+ case 'provable':
1200
1992
  return '✓';
1201
1993
  case 'invalid':
1994
+ case 'refutable':
1202
1995
  return '✗';
1203
1996
  case 'satisfiable':
1204
1997
  return '◎';