@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.
- package/dist/ast/nodes.d.ts +35 -2
- package/dist/ast/nodes.d.ts.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/lexer/lexer.d.ts.map +1 -1
- package/dist/lexer/lexer.js +4 -0
- package/dist/lexer/lexer.js.map +1 -1
- package/dist/lexer/tokens.d.ts +8 -0
- package/dist/lexer/tokens.d.ts.map +1 -1
- package/dist/lexer/tokens.js +23 -0
- package/dist/lexer/tokens.js.map +1 -1
- package/dist/parser/parser.d.ts +6 -0
- package/dist/parser/parser.d.ts.map +1 -1
- package/dist/parser/parser.js +171 -6
- package/dist/parser/parser.js.map +1 -1
- package/dist/profiles/classical/cdcl.d.ts +34 -0
- package/dist/profiles/classical/cdcl.d.ts.map +1 -0
- package/dist/profiles/classical/cdcl.js +843 -0
- package/dist/profiles/classical/cdcl.js.map +1 -0
- package/dist/profiles/classical/dpll.d.ts +11 -1
- package/dist/profiles/classical/dpll.d.ts.map +1 -1
- package/dist/profiles/classical/dpll.js +54 -17
- package/dist/profiles/classical/dpll.js.map +1 -1
- package/dist/profiles/classical/first-order.d.ts.map +1 -1
- package/dist/profiles/classical/first-order.js +20 -10
- package/dist/profiles/classical/first-order.js.map +1 -1
- package/dist/profiles/classical/parallel-sat.d.ts +62 -0
- package/dist/profiles/classical/parallel-sat.d.ts.map +1 -0
- package/dist/profiles/classical/parallel-sat.js +630 -0
- package/dist/profiles/classical/parallel-sat.js.map +1 -0
- package/dist/profiles/classical/propositional.d.ts.map +1 -1
- package/dist/profiles/classical/propositional.js +35 -19
- package/dist/profiles/classical/propositional.js.map +1 -1
- package/dist/profiles/classical/sat-preprocess.d.ts +17 -0
- package/dist/profiles/classical/sat-preprocess.d.ts.map +1 -0
- package/dist/profiles/classical/sat-preprocess.js +372 -0
- package/dist/profiles/classical/sat-preprocess.js.map +1 -0
- package/dist/profiles/classical/undecidability-detector.d.ts +13 -0
- package/dist/profiles/classical/undecidability-detector.d.ts.map +1 -0
- package/dist/profiles/classical/undecidability-detector.js +277 -0
- package/dist/profiles/classical/undecidability-detector.js.map +1 -0
- package/dist/profiles/paraconsistent/belnap.d.ts.map +1 -1
- package/dist/profiles/paraconsistent/belnap.js +4 -2
- package/dist/profiles/paraconsistent/belnap.js.map +1 -1
- package/dist/profiles/shared/tableau-engine.d.ts.map +1 -1
- package/dist/profiles/shared/tableau-engine.js +3 -1
- package/dist/profiles/shared/tableau-engine.js.map +1 -1
- package/dist/protocol/handler.d.ts.map +1 -1
- package/dist/protocol/handler.js +327 -88
- package/dist/protocol/handler.js.map +1 -1
- package/dist/runtime/formula-factory.d.ts.map +1 -1
- package/dist/runtime/formula-factory.js +3 -2
- package/dist/runtime/formula-factory.js.map +1 -1
- package/dist/runtime/interpreter.d.ts +33 -0
- package/dist/runtime/interpreter.d.ts.map +1 -1
- package/dist/runtime/interpreter.js +516 -5
- package/dist/runtime/interpreter.js.map +1 -1
- package/dist/tests/benchmark-cdcl.test.d.ts +2 -0
- package/dist/tests/benchmark-cdcl.test.d.ts.map +1 -0
- package/dist/tests/benchmark-cdcl.test.js +172 -0
- package/dist/tests/benchmark-cdcl.test.js.map +1 -0
- package/dist/tests/limits.test.js +11 -4
- package/dist/tests/limits.test.js.map +1 -1
- package/dist/tests/parallel-sat.test.d.ts +2 -0
- package/dist/tests/parallel-sat.test.d.ts.map +1 -0
- package/dist/tests/parallel-sat.test.js +351 -0
- package/dist/tests/parallel-sat.test.js.map +1 -0
- package/dist/tests/stress-cdcl.test.d.ts +2 -0
- package/dist/tests/stress-cdcl.test.d.ts.map +1 -0
- package/dist/tests/stress-cdcl.test.js +792 -0
- package/dist/tests/stress-cdcl.test.js.map +1 -0
- package/dist/tests/stress-extreme.test.d.ts +2 -0
- package/dist/tests/stress-extreme.test.d.ts.map +1 -0
- package/dist/tests/stress-extreme.test.js +1005 -0
- package/dist/tests/stress-extreme.test.js.map +1 -0
- package/dist/tests/v3-features.test.d.ts +2 -0
- package/dist/tests/v3-features.test.d.ts.map +1 -0
- package/dist/tests/v3-features.test.js +529 -0
- package/dist/tests/v3-features.test.js.map +1 -0
- package/dist/tests/v3-stress.test.d.ts +2 -0
- package/dist/tests/v3-stress.test.d.ts.map +1 -0
- package/dist/tests/v3-stress.test.js +755 -0
- package/dist/tests/v3-stress.test.js.map +1 -0
- package/dist/text-layer/compiler.d.ts +4 -1
- package/dist/text-layer/compiler.d.ts.map +1 -1
- package/dist/text-layer/compiler.js +35 -0
- package/dist/text-layer/compiler.js.map +1 -1
- package/dist/types/index.d.ts +27 -1
- package/dist/types/index.d.ts.map +1 -1
- 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 =
|
|
19
|
-
const DEFAULT_MAX_RUNTIME_STEPS =
|
|
20
|
-
const DEFAULT_MAX_RUNTIME_CALLS =
|
|
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
|
-
|
|
1277
|
-
|
|
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;
|