@stevenvo780/st-lang 2.7.1 → 3.0.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 (92) hide show
  1. package/dist/ast/nodes.d.ts +35 -2
  2. package/dist/ast/nodes.d.ts.map +1 -1
  3. package/dist/index.d.ts +5 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +14 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/lexer/lexer.d.ts.map +1 -1
  8. package/dist/lexer/lexer.js +4 -0
  9. package/dist/lexer/lexer.js.map +1 -1
  10. package/dist/lexer/tokens.d.ts +8 -0
  11. package/dist/lexer/tokens.d.ts.map +1 -1
  12. package/dist/lexer/tokens.js +23 -0
  13. package/dist/lexer/tokens.js.map +1 -1
  14. package/dist/parser/parser.d.ts +6 -0
  15. package/dist/parser/parser.d.ts.map +1 -1
  16. package/dist/parser/parser.js +171 -6
  17. package/dist/parser/parser.js.map +1 -1
  18. package/dist/profiles/classical/cdcl.d.ts +34 -0
  19. package/dist/profiles/classical/cdcl.d.ts.map +1 -0
  20. package/dist/profiles/classical/cdcl.js +843 -0
  21. package/dist/profiles/classical/cdcl.js.map +1 -0
  22. package/dist/profiles/classical/dpll.d.ts +11 -1
  23. package/dist/profiles/classical/dpll.d.ts.map +1 -1
  24. package/dist/profiles/classical/dpll.js +54 -17
  25. package/dist/profiles/classical/dpll.js.map +1 -1
  26. package/dist/profiles/classical/first-order.d.ts.map +1 -1
  27. package/dist/profiles/classical/first-order.js +20 -10
  28. package/dist/profiles/classical/first-order.js.map +1 -1
  29. package/dist/profiles/classical/parallel-sat.d.ts +62 -0
  30. package/dist/profiles/classical/parallel-sat.d.ts.map +1 -0
  31. package/dist/profiles/classical/parallel-sat.js +630 -0
  32. package/dist/profiles/classical/parallel-sat.js.map +1 -0
  33. package/dist/profiles/classical/propositional.d.ts.map +1 -1
  34. package/dist/profiles/classical/propositional.js +35 -19
  35. package/dist/profiles/classical/propositional.js.map +1 -1
  36. package/dist/profiles/classical/sat-preprocess.d.ts +17 -0
  37. package/dist/profiles/classical/sat-preprocess.d.ts.map +1 -0
  38. package/dist/profiles/classical/sat-preprocess.js +372 -0
  39. package/dist/profiles/classical/sat-preprocess.js.map +1 -0
  40. package/dist/profiles/classical/undecidability-detector.d.ts +13 -0
  41. package/dist/profiles/classical/undecidability-detector.d.ts.map +1 -0
  42. package/dist/profiles/classical/undecidability-detector.js +277 -0
  43. package/dist/profiles/classical/undecidability-detector.js.map +1 -0
  44. package/dist/profiles/paraconsistent/belnap.d.ts.map +1 -1
  45. package/dist/profiles/paraconsistent/belnap.js +4 -2
  46. package/dist/profiles/paraconsistent/belnap.js.map +1 -1
  47. package/dist/profiles/shared/tableau-engine.d.ts.map +1 -1
  48. package/dist/profiles/shared/tableau-engine.js +3 -1
  49. package/dist/profiles/shared/tableau-engine.js.map +1 -1
  50. package/dist/protocol/handler.d.ts.map +1 -1
  51. package/dist/protocol/handler.js +327 -88
  52. package/dist/protocol/handler.js.map +1 -1
  53. package/dist/runtime/formula-factory.d.ts.map +1 -1
  54. package/dist/runtime/formula-factory.js +3 -2
  55. package/dist/runtime/formula-factory.js.map +1 -1
  56. package/dist/runtime/interpreter.d.ts +33 -0
  57. package/dist/runtime/interpreter.d.ts.map +1 -1
  58. package/dist/runtime/interpreter.js +516 -5
  59. package/dist/runtime/interpreter.js.map +1 -1
  60. package/dist/tests/benchmark-cdcl.test.d.ts +2 -0
  61. package/dist/tests/benchmark-cdcl.test.d.ts.map +1 -0
  62. package/dist/tests/benchmark-cdcl.test.js +172 -0
  63. package/dist/tests/benchmark-cdcl.test.js.map +1 -0
  64. package/dist/tests/limits.test.js +11 -4
  65. package/dist/tests/limits.test.js.map +1 -1
  66. package/dist/tests/parallel-sat.test.d.ts +2 -0
  67. package/dist/tests/parallel-sat.test.d.ts.map +1 -0
  68. package/dist/tests/parallel-sat.test.js +351 -0
  69. package/dist/tests/parallel-sat.test.js.map +1 -0
  70. package/dist/tests/stress-cdcl.test.d.ts +2 -0
  71. package/dist/tests/stress-cdcl.test.d.ts.map +1 -0
  72. package/dist/tests/stress-cdcl.test.js +792 -0
  73. package/dist/tests/stress-cdcl.test.js.map +1 -0
  74. package/dist/tests/stress-extreme.test.d.ts +2 -0
  75. package/dist/tests/stress-extreme.test.d.ts.map +1 -0
  76. package/dist/tests/stress-extreme.test.js +1005 -0
  77. package/dist/tests/stress-extreme.test.js.map +1 -0
  78. package/dist/tests/v3-features.test.d.ts +2 -0
  79. package/dist/tests/v3-features.test.d.ts.map +1 -0
  80. package/dist/tests/v3-features.test.js +529 -0
  81. package/dist/tests/v3-features.test.js.map +1 -0
  82. package/dist/tests/v3-stress.test.d.ts +2 -0
  83. package/dist/tests/v3-stress.test.d.ts.map +1 -0
  84. package/dist/tests/v3-stress.test.js +755 -0
  85. package/dist/tests/v3-stress.test.js.map +1 -0
  86. package/dist/text-layer/compiler.d.ts +4 -1
  87. package/dist/text-layer/compiler.d.ts.map +1 -1
  88. package/dist/text-layer/compiler.js +35 -0
  89. package/dist/text-layer/compiler.js.map +1 -1
  90. package/dist/types/index.d.ts +27 -1
  91. package/dist/types/index.d.ts.map +1 -1
  92. package/package.json +1 -1
@@ -15,9 +15,9 @@ require("../profiles");
15
15
  const compiler_1 = require("../text-layer/compiler");
16
16
  const formula_classifier_1 = require("./formula-classifier");
17
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;
18
+ const MAX_CALL_DEPTH = 50000;
19
+ const DEFAULT_MAX_RUNTIME_STEPS = 500000;
20
+ const DEFAULT_MAX_RUNTIME_CALLS = 100000;
21
21
  class Interpreter {
22
22
  theory;
23
23
  profile = null;
@@ -53,6 +53,13 @@ class Interpreter {
53
53
  /** Cache de resolución: fórmula original → fórmula resuelta (invalidado al cambiar bindings) */
54
54
  resolveCache = new WeakMap();
55
55
  resolveCacheGeneration = 0;
56
+ /** Memoización automática para funciones con argumentos numéricos (fibonacci, factorial, etc.) */
57
+ fnMemoCache = new Map();
58
+ static MEMO_CACHE_MAX = 50000;
59
+ /** v3: Formal definitions (define Name(params?) := body) */
60
+ definitions = new Map();
61
+ /** v3: Bibliographic sources */
62
+ sources = new Map();
56
63
  constructor() {
57
64
  this.theory = this.createEmptyTheory();
58
65
  this.textLayer = (0, compiler_1.createTextLayerState)();
@@ -116,7 +123,78 @@ class Interpreter {
116
123
  this.currentBindingFrame = null;
117
124
  this.runtimeStepCount = 0;
118
125
  this.runtimeCallCount = 0;
126
+ this.fnMemoCache.clear();
127
+ this.definitions.clear();
128
+ this.sources.clear();
129
+ }
130
+ // ── Memoización de funciones ──────────────────────────────
131
+ /** Serializa una fórmula a una cadena para usar como clave de cache */
132
+ serializeFormulaKey(f) {
133
+ if (f.kind === 'number')
134
+ return `N:${f.value}`;
135
+ if (f.kind === 'atom')
136
+ return `A:${f.name}`;
137
+ if (f.kind === 'list')
138
+ return `L:[${f.args?.map((a) => this.serializeFormulaKey(a)).join(',') ?? ''}]`;
139
+ return `${f.kind}(${f.args?.map((a) => this.serializeFormulaKey(a)).join(',') ?? ''})`;
140
+ }
141
+ /** Genera la clave de memoización: nombre_función(arg1,arg2,...) */
142
+ memoKey(name, args) {
143
+ // Solo memoizar si todos los argumentos son valores concretos (number, atom sin variables)
144
+ for (const arg of args) {
145
+ if (arg.kind !== 'number' && arg.kind !== 'atom')
146
+ return null;
147
+ }
148
+ return `${name}(${args.map((a) => this.serializeFormulaKey(a)).join(',')})`;
149
+ }
150
+ /** Verifica si una función es pura (sin side effects como print, set, check, axiom) */
151
+ isPureFn(fn) {
152
+ const check = (stmts) => {
153
+ for (const s of stmts) {
154
+ switch (s.kind) {
155
+ case 'print_cmd':
156
+ case 'set_cmd':
157
+ case 'derive_cmd':
158
+ case 'check_valid_cmd':
159
+ case 'check_satisfiable_cmd':
160
+ case 'check_equivalent_cmd':
161
+ case 'prove_cmd':
162
+ case 'axiom_decl':
163
+ case 'theorem_decl':
164
+ case 'theory_decl':
165
+ case 'import_decl':
166
+ case 'export_decl':
167
+ case 'logic_decl':
168
+ case 'glossary_cmd':
169
+ case 'unfold_cmd':
170
+ case 'fold_cmd':
171
+ return false;
172
+ case 'if_stmt': {
173
+ const ifS = s;
174
+ for (const branch of ifS.branches) {
175
+ if (!check(branch.body))
176
+ return false;
177
+ }
178
+ if (ifS.elseBranch && !check(ifS.elseBranch))
179
+ return false;
180
+ break;
181
+ }
182
+ case 'for_stmt':
183
+ if (!check(s.body))
184
+ return false;
185
+ break;
186
+ case 'while_stmt':
187
+ if (!check(s.body))
188
+ return false;
189
+ break;
190
+ // let_decl, return_stmt, fn_call, fn_decl are considered pure
191
+ }
192
+ }
193
+ return true;
194
+ };
195
+ return check(fn.body);
119
196
  }
197
+ // ── Fin memoización ───────────────────────────────────────
120
198
  getRuntimeStepLimit() {
121
199
  const configured = this.getBinding('max_steps') || this.getBinding('max_runtime_steps');
122
200
  if (configured?.kind === 'number' && configured.value && configured.value > 0) {
@@ -380,6 +458,18 @@ class Interpreter {
380
458
  return;
381
459
  case 'export_decl':
382
460
  return this.execExportDecl(stmt);
461
+ case 'define_decl':
462
+ return this.execDefineDecl(stmt);
463
+ case 'unfold_cmd':
464
+ return this.execUnfoldCmd(stmt);
465
+ case 'fold_cmd':
466
+ return this.execFoldCmd(stmt);
467
+ case 'source_decl':
468
+ return this.execSourceDecl(stmt);
469
+ case 'interpret_cmd':
470
+ return this.execInterpretCmd(stmt);
471
+ case 'glossary_cmd':
472
+ return this.execGlossaryCmd(stmt);
383
473
  }
384
474
  }
385
475
  executeStatementsIterative(statements, fallbackFile) {
@@ -548,6 +638,18 @@ class Interpreter {
548
638
  return;
549
639
  case 'export_decl':
550
640
  return this.execExportDecl(stmt);
641
+ case 'define_decl':
642
+ return this.execDefineDecl(stmt);
643
+ case 'unfold_cmd':
644
+ return this.execUnfoldCmd(stmt);
645
+ case 'fold_cmd':
646
+ return this.execFoldCmd(stmt);
647
+ case 'source_decl':
648
+ return this.execSourceDecl(stmt);
649
+ case 'interpret_cmd':
650
+ return this.execInterpretCmd(stmt);
651
+ case 'glossary_cmd':
652
+ return this.execGlossaryCmd(stmt);
551
653
  }
552
654
  }
553
655
  selectIfBody(stmt) {
@@ -665,6 +767,13 @@ class Interpreter {
665
767
  case 'theory_decl':
666
768
  this.exportedTheories.set(s.name, this.theories.get(s.name));
667
769
  break;
770
+ case 'define_decl': {
771
+ const defEntry = this.definitions.get(s.name);
772
+ if (defEntry) {
773
+ this.exportedBindings.set(s.name, defEntry.body);
774
+ }
775
+ break;
776
+ }
668
777
  }
669
778
  }
670
779
  requireProfile() {
@@ -763,6 +872,32 @@ class Interpreter {
763
872
  visited.delete(f.name);
764
873
  return result;
765
874
  }
875
+ // v3: Expand definitions (no-param definitions referenced as atoms)
876
+ const def = this.definitions.get(f.name);
877
+ if (def && (!def.params || def.params.length === 0)) {
878
+ if (visited.has(f.name))
879
+ return f;
880
+ visited.add(f.name);
881
+ const result = this.resolveFormulaRecursive(def.body, visited);
882
+ visited.delete(f.name);
883
+ return result;
884
+ }
885
+ }
886
+ // v3: Expand parametric definitions used as predicates: F(a, b)
887
+ if (f.kind === 'predicate' && f.name) {
888
+ const def = this.definitions.get(f.name);
889
+ if (def && def.params && def.params.length > 0) {
890
+ // Predicates store arguments as params (string[]) or args (Formula[])
891
+ let argFormulas;
892
+ if (f.args && f.args.length > 0) {
893
+ argFormulas = f.args.map((a) => (a ? this.resolveFormulaRecursive(a, visited) : a));
894
+ }
895
+ else {
896
+ argFormulas = (f.params ?? []).map((p) => ({ kind: 'atom', name: p, source: f.source }));
897
+ }
898
+ const expanded = this.substituteParams(def.body, def.params, argFormulas);
899
+ return this.resolveFormulaRecursive(expanded, visited);
900
+ }
766
901
  }
767
902
  // Llamada a función como expresión
768
903
  if (f.kind === 'fn_call' && f.name) {
@@ -1001,6 +1136,12 @@ class Interpreter {
1001
1136
  execRenderCmd(stmt) {
1002
1137
  const diags = (0, compiler_1.compileClaimsToTheory)(this.textLayer, this.theory);
1003
1138
  this.diagnostics.push(...diags);
1139
+ if (stmt.target === 'glossary') {
1140
+ return this.renderGlossary(stmt.format);
1141
+ }
1142
+ if (stmt.target === 'analysis') {
1143
+ return this.renderAnalysis(stmt.format);
1144
+ }
1004
1145
  if (stmt.target === 'claims' || stmt.target === 'all') {
1005
1146
  this.emit(`── Render: ${stmt.target} (${stmt.format}) ──`);
1006
1147
  for (const [name, claim] of this.theory.claims) {
@@ -1044,6 +1185,98 @@ class Interpreter {
1044
1185
  this.emit(`Render: ${stmt.target} (${stmt.format})`);
1045
1186
  }
1046
1187
  }
1188
+ /** Render glossary in the specified format */
1189
+ renderGlossary(format) {
1190
+ if (format === 'json') {
1191
+ const obj = {};
1192
+ for (const [name, def] of this.definitions) {
1193
+ obj[name] = {
1194
+ params: def.params ?? [],
1195
+ body: (0, propositional_1.formulaToString)(def.body),
1196
+ description: def.description ?? null,
1197
+ };
1198
+ }
1199
+ this.emit(JSON.stringify(obj, null, 2));
1200
+ return;
1201
+ }
1202
+ // markdown or default
1203
+ if (this.definitions.size === 0) {
1204
+ this.emit('(sin definiciones)');
1205
+ return;
1206
+ }
1207
+ for (const [name, def] of this.definitions) {
1208
+ const paramsStr = def.params?.length ? `(${def.params.join(', ')})` : '';
1209
+ const bodyStr = format === 'latex' ? (0, format_1.formulaToLaTeX)(def.body) : (0, format_1.formulaToUnicode)(def.body);
1210
+ if (format === 'latex') {
1211
+ this.emit(`\\newcommand{\\${name}}{${bodyStr}} % ${def.description ?? ''}`);
1212
+ }
1213
+ else {
1214
+ this.emit(`- **${name}${paramsStr}** := ${bodyStr}`);
1215
+ if (def.description)
1216
+ this.emit(` > ${def.description}`);
1217
+ }
1218
+ }
1219
+ }
1220
+ /** Render full analysis document */
1221
+ renderAnalysis(format) {
1222
+ const isLatex = format === 'latex';
1223
+ const heading = (level, text) => {
1224
+ if (isLatex)
1225
+ return `${'\\'.repeat(1)}${'sub'.repeat(level - 1)}section{${text}}`;
1226
+ return `${'#'.repeat(level)} ${text}`;
1227
+ };
1228
+ this.emit(heading(1, 'Análisis'));
1229
+ this.emit('');
1230
+ // Sources
1231
+ if (this.sources.size > 0) {
1232
+ this.emit(heading(2, 'Fuentes'));
1233
+ for (const [, src] of this.sources) {
1234
+ const yearStr = src.year ? ` (${src.year})` : '';
1235
+ this.emit(`- ${src.author ?? ''}${yearStr} *${src.work ?? ''}*`);
1236
+ }
1237
+ this.emit('');
1238
+ }
1239
+ // Definitions
1240
+ if (this.definitions.size > 0) {
1241
+ this.emit(heading(2, 'Definiciones'));
1242
+ this.renderGlossary(format);
1243
+ this.emit('');
1244
+ }
1245
+ // Axioms
1246
+ if (this.theory.axioms.size > 0) {
1247
+ this.emit(heading(2, 'Axiomas'));
1248
+ for (const [name, formula] of this.theory.axioms) {
1249
+ this.emit(`- **${name}**: ${(0, format_1.formulaToUnicode)(formula)}`);
1250
+ }
1251
+ this.emit('');
1252
+ }
1253
+ // Theorems
1254
+ if (this.theory.theorems.size > 0) {
1255
+ this.emit(heading(2, 'Teoremas'));
1256
+ for (const [name, formula] of this.theory.theorems) {
1257
+ this.emit(`- **${name}**: ${(0, format_1.formulaToUnicode)(formula)}`);
1258
+ }
1259
+ this.emit('');
1260
+ }
1261
+ // Claims
1262
+ if (this.theory.claims.size > 0) {
1263
+ this.emit(heading(2, 'Claims'));
1264
+ for (const [name, claim] of this.theory.claims) {
1265
+ const fStr = claim.formula ? (0, format_1.formulaToUnicode)(claim.formula) : '(sin fórmula)';
1266
+ this.emit(`- **${name}**: ${fStr}`);
1267
+ }
1268
+ this.emit('');
1269
+ }
1270
+ // Results summary
1271
+ if (this.results.length > 0) {
1272
+ this.emit(heading(2, 'Verificaciones'));
1273
+ for (const r of this.results) {
1274
+ const status = r.status === 'valid' ? '✓' : r.status === 'satisfiable' ? '~' : '✗';
1275
+ const fStr = r.formula ? (0, format_1.formulaToUnicode)(r.formula) : '';
1276
+ this.emit(`- ${fStr}: ${r.status} ${status}`);
1277
+ }
1278
+ }
1279
+ }
1047
1280
  execAnalyzeCmd(stmt) {
1048
1281
  const profile = this.requireProfile();
1049
1282
  const premises = stmt.premises.map((p) => this.resolveFormula(p));
@@ -1272,10 +1505,261 @@ class Interpreter {
1272
1505
  this.returnValue = undefined;
1273
1506
  this.returnSignal = true;
1274
1507
  }
1508
+ // ── v3: Definitions, sources, glossary ──────────────────────────
1509
+ execDefineDecl(stmt) {
1510
+ // Check for circular definitions
1511
+ this.checkCircularDefinition(stmt.name, stmt.body, new Set());
1512
+ const entry = {
1513
+ name: stmt.name,
1514
+ params: stmt.params,
1515
+ body: stmt.body,
1516
+ description: stmt.description,
1517
+ };
1518
+ this.definitions.set(stmt.name, entry);
1519
+ const diags = (0, compiler_1.registerDefinition)(this.textLayer, entry);
1520
+ this.diagnostics.push(...diags);
1521
+ this.invalidateResolveCache();
1522
+ const paramsStr = stmt.params?.length ? `(${stmt.params.join(', ')})` : '';
1523
+ const bodyStr = (0, format_1.formulaToUnicode)(stmt.body);
1524
+ this.emit(`Define ${stmt.name}${paramsStr} := ${bodyStr}`);
1525
+ if (stmt.description) {
1526
+ this.emit(` → "${stmt.description}"`);
1527
+ }
1528
+ }
1529
+ /** Detect circular definitions (A := ... A ...) */
1530
+ checkCircularDefinition(name, body, visited) {
1531
+ if (!body)
1532
+ return;
1533
+ if (body.kind === 'atom' && body.name) {
1534
+ if (body.name === name) {
1535
+ throw new Error(`Definición circular detectada: '${name}' se refiere a sí mismo`);
1536
+ }
1537
+ if (visited.has(body.name))
1538
+ return;
1539
+ visited.add(body.name);
1540
+ const dep = this.definitions.get(body.name);
1541
+ if (dep) {
1542
+ this.checkCircularDefinition(name, dep.body, visited);
1543
+ }
1544
+ }
1545
+ if (body.args) {
1546
+ for (const arg of body.args) {
1547
+ if (arg)
1548
+ this.checkCircularDefinition(name, arg, visited);
1549
+ }
1550
+ }
1551
+ }
1552
+ /** Expand a definition by name with given arguments */
1553
+ expandDefinition(name, args) {
1554
+ const def = this.definitions.get(name);
1555
+ if (!def)
1556
+ return null;
1557
+ // If definition has no params, return body directly
1558
+ if (!def.params || def.params.length === 0) {
1559
+ return def.body;
1560
+ }
1561
+ // Substitute params with args
1562
+ const actualArgs = args ?? [];
1563
+ return this.substituteParams(def.body, def.params, actualArgs);
1564
+ }
1565
+ /** Recursively substitute parameter names with actual argument formulas */
1566
+ substituteParams(formula, params, args) {
1567
+ if (!formula)
1568
+ return formula;
1569
+ if (formula.kind === 'atom' && formula.name) {
1570
+ const idx = params.indexOf(formula.name);
1571
+ if (idx >= 0 && idx < args.length) {
1572
+ return args[idx];
1573
+ }
1574
+ return formula;
1575
+ }
1576
+ if (formula.args && formula.args.length > 0) {
1577
+ const newArgs = formula.args.map((a) => (a ? this.substituteParams(a, params, args) : a));
1578
+ let changed = false;
1579
+ for (let i = 0; i < formula.args.length; i++) {
1580
+ if (formula.args[i] !== newArgs[i]) {
1581
+ changed = true;
1582
+ break;
1583
+ }
1584
+ }
1585
+ return changed ? { ...formula, args: newArgs } : formula;
1586
+ }
1587
+ return formula;
1588
+ }
1589
+ /** Expand all definitions in a formula (one level) */
1590
+ expandDefinitionsInFormula(f) {
1591
+ if (!f)
1592
+ return f;
1593
+ // If atom matches a definition name (no params), expand it
1594
+ if (f.kind === 'atom' && f.name) {
1595
+ const expanded = this.expandDefinition(f.name);
1596
+ if (expanded)
1597
+ return expanded;
1598
+ }
1599
+ // If it's a predicate-like call (name with args), try to expand parametric definition
1600
+ if (f.kind === 'predicate' && f.name) {
1601
+ const def = this.definitions.get(f.name);
1602
+ if (def && def.params && def.params.length > 0 && f.args) {
1603
+ const expanded = this.expandDefinition(f.name, f.args);
1604
+ if (expanded)
1605
+ return expanded;
1606
+ }
1607
+ }
1608
+ // Recurse into sub-formulas
1609
+ if (f.args && f.args.length > 0) {
1610
+ const newArgs = f.args.map((a) => (a ? this.expandDefinitionsInFormula(a) : a));
1611
+ let changed = false;
1612
+ for (let i = 0; i < f.args.length; i++) {
1613
+ if (f.args[i] !== newArgs[i]) {
1614
+ changed = true;
1615
+ break;
1616
+ }
1617
+ }
1618
+ return changed ? { ...f, args: newArgs } : f;
1619
+ }
1620
+ return f;
1621
+ }
1622
+ /** Try to fold a formula back into a definition reference */
1623
+ foldFormula(f) {
1624
+ const fStr = (0, propositional_1.formulaToString)(f);
1625
+ for (const [name, def] of this.definitions) {
1626
+ if (!def.params || def.params.length === 0) {
1627
+ const defStr = (0, propositional_1.formulaToString)(def.body);
1628
+ if (fStr === defStr) {
1629
+ return { kind: 'atom', name, source: f.source };
1630
+ }
1631
+ }
1632
+ }
1633
+ return f;
1634
+ }
1635
+ execUnfoldCmd(stmt) {
1636
+ const resolved = this.resolveFormulaRecursive(stmt.formula, new Set());
1637
+ const expanded = this.expandDefinitionsInFormula(resolved);
1638
+ const expandedStr = (0, format_1.formulaToUnicode)(expanded);
1639
+ this.emit(`Unfold: ${(0, format_1.formulaToUnicode)(resolved)} → ${expandedStr}`);
1640
+ this.results.push({
1641
+ status: 'valid',
1642
+ output: expandedStr,
1643
+ diagnostics: [],
1644
+ formula: expanded,
1645
+ });
1646
+ }
1647
+ execFoldCmd(stmt) {
1648
+ const resolved = this.resolveFormulaRecursive(stmt.formula, new Set());
1649
+ const folded = this.foldFormula(resolved);
1650
+ const foldedStr = (0, format_1.formulaToUnicode)(folded);
1651
+ this.emit(`Fold: ${(0, format_1.formulaToUnicode)(resolved)} → ${foldedStr}`);
1652
+ this.results.push({
1653
+ status: 'valid',
1654
+ output: foldedStr,
1655
+ diagnostics: [],
1656
+ formula: folded,
1657
+ });
1658
+ }
1659
+ execSourceDecl(stmt) {
1660
+ const info = { id: stmt.name };
1661
+ for (const field of stmt.fields) {
1662
+ switch (field.key) {
1663
+ case 'author':
1664
+ info.author = String(field.value);
1665
+ break;
1666
+ case 'work':
1667
+ info.work = String(field.value);
1668
+ break;
1669
+ case 'year':
1670
+ info.year = typeof field.value === 'number' ? field.value : parseInt(String(field.value));
1671
+ break;
1672
+ case 'section':
1673
+ info.section = String(field.value);
1674
+ break;
1675
+ case 'edition':
1676
+ info.edition = String(field.value);
1677
+ break;
1678
+ case 'url':
1679
+ info.url = String(field.value);
1680
+ break;
1681
+ }
1682
+ }
1683
+ this.sources.set(stmt.name, info);
1684
+ const diags = (0, compiler_1.registerSource)(this.textLayer, info);
1685
+ this.diagnostics.push(...diags);
1686
+ const yearStr = info.year ? ` (${info.year})` : '';
1687
+ this.emit(`Source ${stmt.name}: ${info.author ?? ''}${yearStr} — ${info.work ?? ''}`);
1688
+ }
1689
+ execInterpretCmd(stmt) {
1690
+ const formula = this.resolveFormula(stmt.formula);
1691
+ const key = stmt.passageRef ?? stmt.text;
1692
+ const diags = (0, compiler_1.registerInterpretation)(this.textLayer, key, {
1693
+ text: stmt.text,
1694
+ passageRef: stmt.passageRef,
1695
+ formula,
1696
+ });
1697
+ this.diagnostics.push(...diags);
1698
+ // Also register as a let binding so the interpretation is available as a named formula
1699
+ const bindingName = stmt.text.replace(/\s+/g, '_').replace(/[^a-zA-Z0-9_]/g, '');
1700
+ if (bindingName) {
1701
+ this.defineBinding(bindingName, formula);
1702
+ }
1703
+ this.emit(`Interpret: "${stmt.text}" → ${(0, format_1.formulaToUnicode)(formula)}`);
1704
+ }
1705
+ execGlossaryCmd(_stmt) {
1706
+ this.emit('');
1707
+ this.emit('══════════════════════════════════════');
1708
+ this.emit(' GLOSARIO');
1709
+ this.emit('══════════════════════════════════════');
1710
+ if (this.definitions.size === 0) {
1711
+ this.emit(' (sin definiciones registradas)');
1712
+ }
1713
+ else {
1714
+ for (const [name, def] of this.definitions) {
1715
+ const paramsStr = def.params?.length ? `(${def.params.join(', ')})` : '';
1716
+ const bodyStr = (0, format_1.formulaToUnicode)(def.body);
1717
+ this.emit(` ${name}${paramsStr} := ${bodyStr}`);
1718
+ if (def.description) {
1719
+ this.emit(` "${def.description}"`);
1720
+ }
1721
+ }
1722
+ }
1723
+ // Also show sources if any
1724
+ if (this.sources.size > 0) {
1725
+ this.emit('');
1726
+ this.emit('── Fuentes ──');
1727
+ for (const [, src] of this.sources) {
1728
+ const yearStr = src.year ? ` (${src.year})` : '';
1729
+ this.emit(` ${src.id}: ${src.author ?? ''}${yearStr} — ${src.work ?? ''}`);
1730
+ }
1731
+ }
1732
+ // Show interpretations if any
1733
+ if (this.textLayer.interpretations.size > 0) {
1734
+ this.emit('');
1735
+ this.emit('── Interpretaciones ──');
1736
+ for (const [, interp] of this.textLayer.interpretations) {
1737
+ this.emit(` "${interp.text}" → ${(0, format_1.formulaToUnicode)(interp.formula)}`);
1738
+ }
1739
+ }
1740
+ this.emit('══════════════════════════════════════');
1741
+ this.emit('');
1742
+ }
1743
+ // ── End v3 ─────────────────────────────────────────────────
1275
1744
  executeFnCall(stmt) {
1276
- const initial = this.startFunctionFrame(stmt, undefined);
1277
- if ('result' in initial)
1745
+ // ── Memoización: verificar cache antes de ejecutar ──
1746
+ const evaluatedArgs = stmt.args.map((a) => this.evaluateFormulaValue(a));
1747
+ const memoStmt = { name: stmt.name, args: evaluatedArgs };
1748
+ const fn = this.functions.get(stmt.name);
1749
+ let mKey = null;
1750
+ if (fn && this.isPureFn(fn)) {
1751
+ mKey = this.memoKey(stmt.name, evaluatedArgs);
1752
+ if (mKey !== null && this.fnMemoCache.has(mKey)) {
1753
+ return this.fnMemoCache.get(mKey);
1754
+ }
1755
+ }
1756
+ const initial = this.startFunctionFrame(memoStmt, undefined);
1757
+ if ('result' in initial) {
1758
+ if (mKey !== null && this.fnMemoCache.size < Interpreter.MEMO_CACHE_MAX) {
1759
+ this.fnMemoCache.set(mKey, initial.result);
1760
+ }
1278
1761
  return initial.result;
1762
+ }
1279
1763
  const callStack = [initial.frame];
1280
1764
  let finalResult = undefined;
1281
1765
  while (callStack.length > 0) {
@@ -1324,9 +1808,25 @@ class Interpreter {
1324
1808
  }
1325
1809
  this.dispatchRuntimeStatement(nextStmt, frame.runtimeStack);
1326
1810
  }
1811
+ // ── Memoización: guardar resultado ──
1812
+ if (mKey !== null && this.fnMemoCache.size < Interpreter.MEMO_CACHE_MAX) {
1813
+ this.fnMemoCache.set(mKey, finalResult);
1814
+ }
1327
1815
  return finalResult;
1328
1816
  }
1329
1817
  startFunctionFrame(stmt, continuation, inheritedReturnState) {
1818
+ // ── Memoización: comprobar cache antes de crear frame ──
1819
+ const fnDef = this.functions.get(stmt.name);
1820
+ if (fnDef && this.isPureFn(fnDef)) {
1821
+ const evaluatedArgs = stmt.args.map((a) => this.evaluateFormulaValue(a));
1822
+ const mk = this.memoKey(stmt.name, evaluatedArgs);
1823
+ if (mk !== null && this.fnMemoCache.has(mk)) {
1824
+ return {
1825
+ result: this.fnMemoCache.get(mk),
1826
+ resultContinuation: continuation || { type: 'discard' },
1827
+ };
1828
+ }
1829
+ }
1330
1830
  this.callDepth++;
1331
1831
  if (this.callDepth > MAX_CALL_DEPTH) {
1332
1832
  this.callDepth--;
@@ -1419,6 +1919,12 @@ class Interpreter {
1419
1919
  const savedReturnValue = inheritedReturnState?.value ?? this.returnValue;
1420
1920
  this.returnSignal = false;
1421
1921
  this.returnValue = undefined;
1922
+ // Calcular clave de memoización para este frame
1923
+ const evaluatedArgValues = Array.from(this.currentBindingFrame.bindings.values());
1924
+ let frameMemoKey = null;
1925
+ if (this.isPureFn(fn)) {
1926
+ frameMemoKey = this.memoKey(name, evaluatedArgValues);
1927
+ }
1422
1928
  return {
1423
1929
  name,
1424
1930
  runtimeStack: [{ kind: 'statements', statements: fn.body, index: 0 }],
@@ -1427,12 +1933,17 @@ class Interpreter {
1427
1933
  savedReturnValue,
1428
1934
  scopeSnapshot,
1429
1935
  continuation,
1936
+ memoKey: frameMemoKey,
1430
1937
  };
1431
1938
  }
1432
1939
  finishFunctionFrame(frame) {
1433
1940
  const result = this.returnValue;
1434
1941
  this.returnSignal = frame.savedReturnSignal;
1435
1942
  this.returnValue = frame.savedReturnValue;
1943
+ // ── Memoización: guardar resultado al finalizar frame ──
1944
+ if (frame.memoKey && this.fnMemoCache.size < Interpreter.MEMO_CACHE_MAX) {
1945
+ this.fnMemoCache.set(frame.memoKey, result);
1946
+ }
1436
1947
  this.discardFunctionFrameState(frame);
1437
1948
  this.callDepth--;
1438
1949
  return result;