@stevenvo780/st-lang 2.6.1 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ast/nodes.d.ts +18 -1
- package/dist/ast/nodes.d.ts.map +1 -1
- package/dist/parser/parser.d.ts +2 -0
- package/dist/parser/parser.d.ts.map +1 -1
- package/dist/parser/parser.js +82 -0
- package/dist/parser/parser.js.map +1 -1
- package/dist/profiles/classical/first-order.d.ts +1 -0
- package/dist/profiles/classical/first-order.d.ts.map +1 -1
- package/dist/profiles/classical/first-order.js +35 -0
- package/dist/profiles/classical/first-order.js.map +1 -1
- package/dist/profiles/classical/propositional.d.ts +6 -1
- package/dist/profiles/classical/propositional.d.ts.map +1 -1
- package/dist/profiles/classical/propositional.js +334 -35
- package/dist/profiles/classical/propositional.js.map +1 -1
- package/dist/profiles/paraconsistent/belnap.d.ts.map +1 -1
- package/dist/profiles/paraconsistent/belnap.js +151 -3
- 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 +49 -22
- package/dist/profiles/shared/tableau-engine.js.map +1 -1
- package/dist/runtime/format.d.ts.map +1 -1
- package/dist/runtime/format.js +4 -0
- package/dist/runtime/format.js.map +1 -1
- package/dist/runtime/formula-factory.d.ts +26 -0
- package/dist/runtime/formula-factory.d.ts.map +1 -0
- package/dist/runtime/formula-factory.js +67 -0
- package/dist/runtime/formula-factory.js.map +1 -0
- package/dist/runtime/interpreter.d.ts +41 -0
- package/dist/runtime/interpreter.d.ts.map +1 -1
- package/dist/runtime/interpreter.js +1029 -245
- package/dist/runtime/interpreter.js.map +1 -1
- package/dist/tests/arithmetic.test.js +2 -1
- package/dist/tests/arithmetic.test.js.map +1 -1
- package/dist/tests/examples.test.js +12 -1
- package/dist/tests/examples.test.js.map +1 -1
- package/dist/tests/exhaustive-matrix.test.js +1 -1
- package/dist/tests/exhaustive-matrix.test.js.map +1 -1
- package/dist/tests/limits.test.js +15 -3
- package/dist/tests/limits.test.js.map +1 -1
- package/dist/tests/result-bindings.test.d.ts +2 -0
- package/dist/tests/result-bindings.test.d.ts.map +1 -0
- package/dist/tests/result-bindings.test.js +59 -0
- package/dist/tests/result-bindings.test.js.map +1 -0
- package/dist/tests/stress-hardware.test.d.ts +2 -0
- package/dist/tests/stress-hardware.test.d.ts.map +1 -0
- package/dist/tests/stress-hardware.test.js +516 -0
- package/dist/tests/stress-hardware.test.js.map +1 -0
- package/dist/tests/v1-features.test.js +1 -1
- package/dist/tests/v1-features.test.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/memo.d.ts +3 -0
- package/dist/utils/memo.d.ts.map +1 -1
- package/dist/utils/memo.js +30 -0
- package/dist/utils/memo.js.map +1 -1
- 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
|
|
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 = [
|
|
63
|
+
const builtins = [
|
|
64
|
+
'typeof',
|
|
65
|
+
'is_valid',
|
|
66
|
+
'is_satisfiable',
|
|
67
|
+
'get_atoms',
|
|
68
|
+
'atoms_of',
|
|
69
|
+
'len',
|
|
70
|
+
'at',
|
|
71
|
+
'formula_eq',
|
|
72
|
+
'input',
|
|
73
|
+
];
|
|
54
74
|
for (const name of builtins) {
|
|
55
75
|
this.functions.set(name, {
|
|
56
76
|
kind: 'fn_decl',
|
|
@@ -93,11 +113,123 @@ class Interpreter {
|
|
|
93
113
|
this.exportedTheorems.clear();
|
|
94
114
|
this.exportedFunctions.clear();
|
|
95
115
|
this.exportedTheories.clear();
|
|
116
|
+
this.currentBindingFrame = null;
|
|
117
|
+
this.runtimeStepCount = 0;
|
|
118
|
+
this.runtimeCallCount = 0;
|
|
119
|
+
}
|
|
120
|
+
getRuntimeStepLimit() {
|
|
121
|
+
const configured = this.getBinding('max_steps') || this.getBinding('max_runtime_steps');
|
|
122
|
+
if (configured?.kind === 'number' && configured.value && configured.value > 0) {
|
|
123
|
+
return Math.floor(configured.value);
|
|
124
|
+
}
|
|
125
|
+
return DEFAULT_MAX_RUNTIME_STEPS;
|
|
126
|
+
}
|
|
127
|
+
getRuntimeCallLimit() {
|
|
128
|
+
const configured = this.getBinding('max_calls') || this.getBinding('max_runtime_calls');
|
|
129
|
+
if (configured?.kind === 'number' && configured.value && configured.value > 0) {
|
|
130
|
+
return Math.floor(configured.value);
|
|
131
|
+
}
|
|
132
|
+
return DEFAULT_MAX_RUNTIME_CALLS;
|
|
133
|
+
}
|
|
134
|
+
tickRuntimeStep() {
|
|
135
|
+
this.runtimeStepCount++;
|
|
136
|
+
const limit = this.getRuntimeStepLimit();
|
|
137
|
+
if (this.runtimeStepCount > limit) {
|
|
138
|
+
throw new Error(`Límite de ejecución excedido (${limit} pasos).`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
isSafetyLimitMessage(message) {
|
|
142
|
+
return /Límite de (recursión|ejecución|llamadas ST)/i.test(message);
|
|
143
|
+
}
|
|
144
|
+
createBindingFrame() {
|
|
145
|
+
return {
|
|
146
|
+
bindings: new Map(),
|
|
147
|
+
descriptions: new Map(),
|
|
148
|
+
parent: this.currentBindingFrame,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
findBindingOwner(name) {
|
|
152
|
+
let frame = this.currentBindingFrame;
|
|
153
|
+
while (frame) {
|
|
154
|
+
if (frame.bindings.has(name))
|
|
155
|
+
return frame;
|
|
156
|
+
frame = frame.parent;
|
|
157
|
+
}
|
|
158
|
+
if (this.letBindings.has(name))
|
|
159
|
+
return 'global';
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
hasBinding(name) {
|
|
163
|
+
return this.findBindingOwner(name) !== undefined;
|
|
164
|
+
}
|
|
165
|
+
getBinding(name) {
|
|
166
|
+
const owner = this.findBindingOwner(name);
|
|
167
|
+
if (!owner)
|
|
168
|
+
return undefined;
|
|
169
|
+
return owner === 'global' ? this.letBindings.get(name) : owner.bindings.get(name);
|
|
170
|
+
}
|
|
171
|
+
defineBinding(name, value, description) {
|
|
172
|
+
this.invalidateResolveCache();
|
|
173
|
+
if (this.currentBindingFrame) {
|
|
174
|
+
this.currentBindingFrame.bindings.set(name, value);
|
|
175
|
+
if (description)
|
|
176
|
+
this.currentBindingFrame.descriptions.set(name, description);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
this.letBindings.set(name, value);
|
|
180
|
+
if (description)
|
|
181
|
+
this.letDescriptions.set(name, description);
|
|
182
|
+
}
|
|
183
|
+
setBinding(name, value) {
|
|
184
|
+
this.invalidateResolveCache();
|
|
185
|
+
const owner = this.findBindingOwner(name);
|
|
186
|
+
if (owner === 'global') {
|
|
187
|
+
this.letBindings.set(name, value);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (owner) {
|
|
191
|
+
owner.bindings.set(name, value);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (this.currentBindingFrame) {
|
|
195
|
+
this.currentBindingFrame.bindings.set(name, value);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
this.letBindings.set(name, value);
|
|
199
|
+
}
|
|
200
|
+
captureBindingSnapshot(name) {
|
|
201
|
+
const owner = this.findBindingOwner(name);
|
|
202
|
+
if (!owner)
|
|
203
|
+
return { exists: false, owner: 'global' };
|
|
204
|
+
return {
|
|
205
|
+
exists: true,
|
|
206
|
+
value: owner === 'global' ? this.letBindings.get(name) : owner.bindings.get(name),
|
|
207
|
+
owner,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
restoreBindingSnapshot(name, snapshot) {
|
|
211
|
+
if (!snapshot.exists) {
|
|
212
|
+
if (this.currentBindingFrame && this.currentBindingFrame.bindings.has(name)) {
|
|
213
|
+
this.currentBindingFrame.bindings.delete(name);
|
|
214
|
+
}
|
|
215
|
+
this.letBindings.delete(name);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (snapshot.owner === 'global') {
|
|
219
|
+
this.letBindings.set(name, snapshot.value);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
snapshot.owner.bindings.set(name, snapshot.value);
|
|
223
|
+
}
|
|
224
|
+
shouldEmitLocalBindings() {
|
|
225
|
+
return this.currentBindingFrame === null;
|
|
96
226
|
}
|
|
97
227
|
execute(source, file = '<stdin>') {
|
|
98
228
|
this.diagnostics = [];
|
|
99
229
|
this.results = [];
|
|
100
230
|
this.stdoutLines = [];
|
|
231
|
+
this.runtimeStepCount = 0;
|
|
232
|
+
this.runtimeCallCount = 0;
|
|
101
233
|
const parser = new parser_1.Parser(file);
|
|
102
234
|
const program = parser.parse(source);
|
|
103
235
|
this.diagnostics.push(...parser.diagnostics);
|
|
@@ -113,21 +245,7 @@ class Interpreter {
|
|
|
113
245
|
results: [],
|
|
114
246
|
};
|
|
115
247
|
}
|
|
116
|
-
|
|
117
|
-
try {
|
|
118
|
-
this.executeStatement(stmt);
|
|
119
|
-
}
|
|
120
|
-
catch (e) {
|
|
121
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
122
|
-
this.diagnostics.push({
|
|
123
|
-
severity: 'error',
|
|
124
|
-
message: message || 'Error de runtime',
|
|
125
|
-
file,
|
|
126
|
-
line: stmt.source.line,
|
|
127
|
-
column: stmt.source.column,
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
}
|
|
248
|
+
this.executeStatementsIterative(program.statements, file);
|
|
131
249
|
const hasErrors = this.diagnostics.some((d) => d.severity === 'error');
|
|
132
250
|
return {
|
|
133
251
|
stdout: this.stdoutLines.join('\n'),
|
|
@@ -148,6 +266,8 @@ class Interpreter {
|
|
|
148
266
|
this.diagnostics = [...parser.diagnostics];
|
|
149
267
|
this.results = [];
|
|
150
268
|
this.stdoutLines = [];
|
|
269
|
+
this.runtimeStepCount = 0;
|
|
270
|
+
this.runtimeCallCount = 0;
|
|
151
271
|
if (parser.diagnostics.some((d) => d.severity === 'error')) {
|
|
152
272
|
return {
|
|
153
273
|
stdout: '',
|
|
@@ -157,18 +277,7 @@ class Interpreter {
|
|
|
157
277
|
results: [],
|
|
158
278
|
};
|
|
159
279
|
}
|
|
160
|
-
|
|
161
|
-
try {
|
|
162
|
-
this.executeStatement(stmt);
|
|
163
|
-
}
|
|
164
|
-
catch (e) {
|
|
165
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
166
|
-
this.diagnostics.push({
|
|
167
|
-
severity: 'error',
|
|
168
|
-
message: message || 'Error de runtime',
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
}
|
|
280
|
+
this.executeStatementsIterative(program.statements, '<repl>');
|
|
172
281
|
return {
|
|
173
282
|
stdout: this.stdoutLines.join('\n'),
|
|
174
283
|
stderr: this.diagnostics
|
|
@@ -264,9 +373,268 @@ class Interpreter {
|
|
|
264
373
|
return this.execExportDecl(stmt);
|
|
265
374
|
}
|
|
266
375
|
}
|
|
376
|
+
executeStatementsIterative(statements, fallbackFile) {
|
|
377
|
+
const runtimeStack = [{ kind: 'statements', statements, index: 0 }];
|
|
378
|
+
while (runtimeStack.length > 0) {
|
|
379
|
+
if (this.returnSignal)
|
|
380
|
+
break;
|
|
381
|
+
const frame = runtimeStack[runtimeStack.length - 1];
|
|
382
|
+
if (frame.kind === 'statements') {
|
|
383
|
+
if (frame.index >= frame.statements.length) {
|
|
384
|
+
runtimeStack.pop();
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
const stmt = frame.statements[frame.index++];
|
|
388
|
+
try {
|
|
389
|
+
this.dispatchRuntimeStatement(stmt, runtimeStack);
|
|
390
|
+
}
|
|
391
|
+
catch (e) {
|
|
392
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
393
|
+
this.diagnostics.push({
|
|
394
|
+
severity: this.isSafetyLimitMessage(message || '') ? 'warning' : 'error',
|
|
395
|
+
message: message || 'Error de runtime',
|
|
396
|
+
file: stmt.source.file || fallbackFile,
|
|
397
|
+
line: stmt.source.line,
|
|
398
|
+
column: stmt.source.column,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (frame.kind === 'for') {
|
|
404
|
+
if (this.returnSignal || frame.index >= frame.stmt.items.length) {
|
|
405
|
+
this.restoreBindingSnapshot(frame.stmt.variable, frame.savedBinding);
|
|
406
|
+
runtimeStack.pop();
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
const resolved = this.evaluateFormulaValue(frame.stmt.items[frame.index]);
|
|
410
|
+
this.setBinding(frame.stmt.variable, resolved);
|
|
411
|
+
frame.index++;
|
|
412
|
+
runtimeStack.push({ kind: 'statements', statements: frame.stmt.body, index: 0 });
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (this.returnSignal) {
|
|
416
|
+
runtimeStack.pop();
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
const profile = this.requireProfile();
|
|
420
|
+
const maxIter = frame.stmt.maxIterations || 1000;
|
|
421
|
+
if (frame.iterations >= maxIter) {
|
|
422
|
+
this.diagnostics.push({
|
|
423
|
+
severity: 'warning',
|
|
424
|
+
message: `while: se alcanzó el límite de ${maxIter} iteraciones`,
|
|
425
|
+
file: frame.stmt.source.file,
|
|
426
|
+
line: frame.stmt.source.line,
|
|
427
|
+
column: frame.stmt.source.column,
|
|
428
|
+
});
|
|
429
|
+
runtimeStack.pop();
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
const resolved = this.evaluateFormulaValue(frame.stmt.formula);
|
|
433
|
+
const matched = this.matchesRuntimeCondition(profile, frame.stmt.condition, resolved);
|
|
434
|
+
if (!matched) {
|
|
435
|
+
runtimeStack.pop();
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
frame.iterations++;
|
|
439
|
+
runtimeStack.push({ kind: 'statements', statements: frame.stmt.body, index: 0 });
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
dispatchRuntimeStatement(stmt, runtimeStack) {
|
|
443
|
+
if (this.returnSignal)
|
|
444
|
+
return;
|
|
445
|
+
this.tickRuntimeStep();
|
|
446
|
+
const directCall = this.getDirectFunctionCall(stmt);
|
|
447
|
+
if (directCall) {
|
|
448
|
+
const result = this.executeFnCall(directCall.call);
|
|
449
|
+
this.applyFunctionContinuation({ type: directCall.type, stmt: directCall.stmt }, result);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (this.isImporting) {
|
|
453
|
+
const sideEffects = [
|
|
454
|
+
'derive_cmd',
|
|
455
|
+
'check_valid_cmd',
|
|
456
|
+
'check_satisfiable_cmd',
|
|
457
|
+
'check_equivalent_cmd',
|
|
458
|
+
'prove_cmd',
|
|
459
|
+
'countermodel_cmd',
|
|
460
|
+
'truth_table_cmd',
|
|
461
|
+
'print_cmd',
|
|
462
|
+
'analyze_cmd',
|
|
463
|
+
'explain_cmd',
|
|
464
|
+
'render_cmd',
|
|
465
|
+
];
|
|
466
|
+
if (sideEffects.includes(stmt.kind) || stmt.kind === 'logic_decl')
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
switch (stmt.kind) {
|
|
470
|
+
case 'if_stmt': {
|
|
471
|
+
const selectedBody = this.selectIfBody(stmt);
|
|
472
|
+
if (selectedBody)
|
|
473
|
+
runtimeStack.push({ kind: 'statements', statements: selectedBody, index: 0 });
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
case 'for_stmt':
|
|
477
|
+
runtimeStack.push({
|
|
478
|
+
kind: 'for',
|
|
479
|
+
stmt,
|
|
480
|
+
index: 0,
|
|
481
|
+
savedBinding: this.captureBindingSnapshot(stmt.variable),
|
|
482
|
+
});
|
|
483
|
+
return;
|
|
484
|
+
case 'while_stmt':
|
|
485
|
+
runtimeStack.push({ kind: 'while', stmt, iterations: 0 });
|
|
486
|
+
return;
|
|
487
|
+
case 'logic_decl':
|
|
488
|
+
return this.execLogicDecl(stmt);
|
|
489
|
+
case 'axiom_decl':
|
|
490
|
+
return this.execAxiomDecl(stmt);
|
|
491
|
+
case 'theorem_decl':
|
|
492
|
+
return this.execTheoremDecl(stmt);
|
|
493
|
+
case 'derive_cmd':
|
|
494
|
+
return this.execDeriveCmd(stmt);
|
|
495
|
+
case 'check_valid_cmd':
|
|
496
|
+
return this.execCheckValidCmd(stmt);
|
|
497
|
+
case 'check_satisfiable_cmd':
|
|
498
|
+
return this.execCheckSatisfiableCmd(stmt);
|
|
499
|
+
case 'check_equivalent_cmd':
|
|
500
|
+
return this.execCheckEquivalentCmd(stmt);
|
|
501
|
+
case 'prove_cmd':
|
|
502
|
+
return this.execProveCmd(stmt);
|
|
503
|
+
case 'countermodel_cmd':
|
|
504
|
+
return this.execCountermodelCmd(stmt);
|
|
505
|
+
case 'truth_table_cmd':
|
|
506
|
+
return this.execTruthTableCmd(stmt);
|
|
507
|
+
case 'let_decl':
|
|
508
|
+
return this.execLetDecl(stmt);
|
|
509
|
+
case 'claim_decl':
|
|
510
|
+
return this.execClaimDecl(stmt);
|
|
511
|
+
case 'support_decl':
|
|
512
|
+
return this.execSupportDecl(stmt);
|
|
513
|
+
case 'confidence_decl':
|
|
514
|
+
return this.execConfidenceDecl(stmt);
|
|
515
|
+
case 'context_decl':
|
|
516
|
+
return this.execContextDecl(stmt);
|
|
517
|
+
case 'render_cmd':
|
|
518
|
+
return this.execRenderCmd(stmt);
|
|
519
|
+
case 'analyze_cmd':
|
|
520
|
+
return this.execAnalyzeCmd(stmt);
|
|
521
|
+
case 'explain_cmd':
|
|
522
|
+
return this.execExplainCmd(stmt);
|
|
523
|
+
case 'import_decl':
|
|
524
|
+
return this.execImportDecl(stmt);
|
|
525
|
+
case 'proof_block':
|
|
526
|
+
return this.execProofBlock(stmt);
|
|
527
|
+
case 'theory_decl':
|
|
528
|
+
return this.execTheoryDecl(stmt);
|
|
529
|
+
case 'print_cmd':
|
|
530
|
+
return this.execPrintCmd(stmt);
|
|
531
|
+
case 'set_cmd':
|
|
532
|
+
return this.execSetCmd(stmt);
|
|
533
|
+
case 'fn_decl':
|
|
534
|
+
return this.execFnDecl(stmt);
|
|
535
|
+
case 'return_stmt':
|
|
536
|
+
return this.execReturnStmt(stmt);
|
|
537
|
+
case 'fn_call':
|
|
538
|
+
this.executeFnCall(stmt);
|
|
539
|
+
return;
|
|
540
|
+
case 'export_decl':
|
|
541
|
+
return this.execExportDecl(stmt);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
selectIfBody(stmt) {
|
|
545
|
+
const profile = this.requireProfile();
|
|
546
|
+
for (const branch of stmt.branches) {
|
|
547
|
+
const resolved = this.evaluateFormulaValue(branch.formula);
|
|
548
|
+
const matched = this.matchesRuntimeCondition(profile, branch.condition, resolved);
|
|
549
|
+
if (matched)
|
|
550
|
+
return branch.body;
|
|
551
|
+
}
|
|
552
|
+
return stmt.elseBranch;
|
|
553
|
+
}
|
|
554
|
+
matchesRuntimeCondition(profile, condition, formula) {
|
|
555
|
+
if (profile.name === 'arithmetic') {
|
|
556
|
+
const numeric = (0, arithmetic_1.evalNumeric)(formula);
|
|
557
|
+
const truthy = !Number.isNaN(numeric) && numeric !== 0;
|
|
558
|
+
if (condition === 'valid' || condition === 'satisfiable')
|
|
559
|
+
return truthy;
|
|
560
|
+
return !truthy;
|
|
561
|
+
}
|
|
562
|
+
if (condition === 'valid' || condition === 'invalid') {
|
|
563
|
+
const result = profile.checkValid(formula);
|
|
564
|
+
return condition === 'valid' ? result.status === 'valid' : result.status !== 'valid';
|
|
565
|
+
}
|
|
566
|
+
const result = profile.checkSatisfiable(formula);
|
|
567
|
+
return condition === 'satisfiable'
|
|
568
|
+
? result.status === 'satisfiable' || result.status === 'valid'
|
|
569
|
+
: result.status === 'unsatisfiable';
|
|
570
|
+
}
|
|
571
|
+
evaluateFormulaValue(formula) {
|
|
572
|
+
if (formula.kind === 'fn_call' && formula.name) {
|
|
573
|
+
const result = this.executeFnCall({ name: formula.name, args: formula.args || [] });
|
|
574
|
+
return result || { kind: 'atom', name: 'undefined', source: formula.source };
|
|
575
|
+
}
|
|
576
|
+
return this.resolveFormula(formula);
|
|
577
|
+
}
|
|
578
|
+
getDirectFunctionCall(stmt) {
|
|
579
|
+
if (stmt.kind === 'fn_call') {
|
|
580
|
+
return { call: { name: stmt.name, args: stmt.args }, type: 'discard' };
|
|
581
|
+
}
|
|
582
|
+
if (stmt.kind === 'let_decl' && stmt.letType === 'formula' && stmt.formula.kind === 'fn_call') {
|
|
583
|
+
return {
|
|
584
|
+
call: { name: stmt.formula.name || '', args: stmt.formula.args || [] },
|
|
585
|
+
type: 'let_decl',
|
|
586
|
+
stmt,
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
if (stmt.kind === 'set_cmd' && stmt.formula.kind === 'fn_call') {
|
|
590
|
+
return {
|
|
591
|
+
call: { name: stmt.formula.name || '', args: stmt.formula.args || [] },
|
|
592
|
+
type: 'set_cmd',
|
|
593
|
+
stmt,
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
if (stmt.kind === 'return_stmt' && stmt.formula?.kind === 'fn_call') {
|
|
597
|
+
return {
|
|
598
|
+
call: { name: stmt.formula.name || '', args: stmt.formula.args || [] },
|
|
599
|
+
type: 'return_stmt',
|
|
600
|
+
stmt,
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
if (stmt.kind === 'print_cmd' && stmt.formula?.kind === 'fn_call') {
|
|
604
|
+
return {
|
|
605
|
+
call: { name: stmt.formula.name || '', args: stmt.formula.args || [] },
|
|
606
|
+
type: 'print_cmd',
|
|
607
|
+
stmt,
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
return undefined;
|
|
611
|
+
}
|
|
612
|
+
applyFunctionContinuation(continuation, result) {
|
|
613
|
+
const normalized = result || { kind: 'atom', name: 'undefined' };
|
|
614
|
+
switch (continuation.type) {
|
|
615
|
+
case 'discard':
|
|
616
|
+
return;
|
|
617
|
+
case 'let_decl': {
|
|
618
|
+
const letStmt = continuation.stmt;
|
|
619
|
+
if (letStmt.letType === 'formula') {
|
|
620
|
+
return this.execLetDecl({ ...letStmt, formula: normalized });
|
|
621
|
+
}
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
case 'set_cmd':
|
|
625
|
+
return this.execSetCmd({ ...continuation.stmt, formula: normalized });
|
|
626
|
+
case 'return_stmt':
|
|
627
|
+
return this.execReturnStmt({
|
|
628
|
+
...continuation.stmt,
|
|
629
|
+
formula: normalized,
|
|
630
|
+
});
|
|
631
|
+
case 'print_cmd':
|
|
632
|
+
return this.execPrintCmd({ ...continuation.stmt, formula: normalized });
|
|
633
|
+
}
|
|
634
|
+
}
|
|
267
635
|
execExportDecl(stmt) {
|
|
268
636
|
// Ejecutar la declaración interna
|
|
269
|
-
this.
|
|
637
|
+
this.executeStatementsIterative([stmt.statement], stmt.source.file);
|
|
270
638
|
// Registrarla como exportada
|
|
271
639
|
const s = stmt.statement;
|
|
272
640
|
switch (s.kind) {
|
|
@@ -302,21 +670,43 @@ class Interpreter {
|
|
|
302
670
|
* por sus fórmulas definidas. Detecta ciclos para evitar recursión infinita.
|
|
303
671
|
* Soporta notación con punto: Theory.member resuelve desde el scope de la teoría.
|
|
304
672
|
*/
|
|
673
|
+
invalidateResolveCache() {
|
|
674
|
+
this.resolveCache = new WeakMap();
|
|
675
|
+
this.resolveCacheGeneration++;
|
|
676
|
+
}
|
|
305
677
|
resolveFormula(f, visited = new Set()) {
|
|
678
|
+
// Check resolution cache first
|
|
679
|
+
const cached = this.resolveCache.get(f);
|
|
680
|
+
if (cached !== undefined)
|
|
681
|
+
return cached;
|
|
306
682
|
const resolved = this.resolveFormulaRecursive(f, visited);
|
|
307
|
-
|
|
683
|
+
// Constant-fold pure arithmetic (add, subtract, etc.) but NOT comparisons
|
|
684
|
+
// so profiles can still see the structural form of comparisons.
|
|
685
|
+
const folded = this.tryConstantFold(resolved);
|
|
686
|
+
// Cache the result and deduplicate via FormulaFactory
|
|
687
|
+
const result = formula_factory_1.FormulaFactory.create(folded);
|
|
688
|
+
this.resolveCache.set(f, result);
|
|
689
|
+
return result;
|
|
308
690
|
}
|
|
309
|
-
resolveFormulaRecursive(f, visited
|
|
691
|
+
resolveFormulaRecursive(f, visited) {
|
|
310
692
|
if (!f)
|
|
311
693
|
return f;
|
|
312
694
|
// Si es un átomo, intentar resolver
|
|
313
695
|
if (f.kind === 'atom' && f.name) {
|
|
696
|
+
if (this.hasBinding(f.name)) {
|
|
697
|
+
if (visited.has(f.name))
|
|
698
|
+
return f;
|
|
699
|
+
visited.add(f.name);
|
|
700
|
+
const result = this.resolveFormulaRecursive(this.getBinding(f.name), visited);
|
|
701
|
+
visited.delete(f.name);
|
|
702
|
+
return result;
|
|
703
|
+
}
|
|
314
704
|
// Dot notation: Theory.member o instance.member
|
|
315
705
|
if (f.name.includes('.')) {
|
|
316
706
|
const [prefix, memberName] = f.name.split('.', 2);
|
|
317
707
|
// 1. Intentar resolver el prefijo como una variable local (instancia)
|
|
318
|
-
if (this.
|
|
319
|
-
const resolvedPrefix = this.
|
|
708
|
+
if (this.hasBinding(prefix)) {
|
|
709
|
+
const resolvedPrefix = this.getBinding(prefix);
|
|
320
710
|
if (resolvedPrefix.kind === 'atom' && resolvedPrefix.name) {
|
|
321
711
|
const actualInstanceName = resolvedPrefix.name;
|
|
322
712
|
const scope = this.theories.get(actualInstanceName);
|
|
@@ -324,12 +714,12 @@ class Interpreter {
|
|
|
324
714
|
if (scope.privateMembers.has(memberName) &&
|
|
325
715
|
this.currentTheoryName !== actualInstanceName)
|
|
326
716
|
return f;
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
717
|
+
const member = scope.letBindings.get(memberName) ??
|
|
718
|
+
scope.axioms.get(memberName) ??
|
|
719
|
+
scope.theorems.get(memberName);
|
|
720
|
+
if (member) {
|
|
721
|
+
return this.resolveFormulaRecursive(member, visited);
|
|
722
|
+
}
|
|
333
723
|
}
|
|
334
724
|
}
|
|
335
725
|
}
|
|
@@ -338,37 +728,31 @@ class Interpreter {
|
|
|
338
728
|
if (scope) {
|
|
339
729
|
if (scope.privateMembers.has(memberName) && this.currentTheoryName !== prefix)
|
|
340
730
|
return f;
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
731
|
+
const member = scope.letBindings.get(memberName) ??
|
|
732
|
+
scope.axioms.get(memberName) ??
|
|
733
|
+
scope.theorems.get(memberName);
|
|
734
|
+
if (member) {
|
|
735
|
+
return this.resolveFormulaRecursive(member, visited);
|
|
736
|
+
}
|
|
347
737
|
}
|
|
348
738
|
return f;
|
|
349
739
|
}
|
|
350
|
-
// Binding local normal
|
|
351
|
-
if (this.letBindings.has(f.name)) {
|
|
352
|
-
if (visited.has(f.name))
|
|
353
|
-
return f;
|
|
354
|
-
const newVisited = new Set(visited);
|
|
355
|
-
newVisited.add(f.name);
|
|
356
|
-
return this.resolveFormulaRecursive(this.letBindings.get(f.name), newVisited);
|
|
357
|
-
}
|
|
358
740
|
// También resolver axiomas/teoremas del theory actual por nombre
|
|
359
741
|
if (this.theory.axioms.has(f.name)) {
|
|
360
742
|
if (visited.has(f.name))
|
|
361
743
|
return f;
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
744
|
+
visited.add(f.name);
|
|
745
|
+
const result = this.resolveFormulaRecursive(this.theory.axioms.get(f.name), visited);
|
|
746
|
+
visited.delete(f.name);
|
|
747
|
+
return result;
|
|
365
748
|
}
|
|
366
749
|
if (this.theory.theorems.has(f.name)) {
|
|
367
750
|
if (visited.has(f.name))
|
|
368
751
|
return f;
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
752
|
+
visited.add(f.name);
|
|
753
|
+
const result = this.resolveFormulaRecursive(this.theory.theorems.get(f.name), visited);
|
|
754
|
+
visited.delete(f.name);
|
|
755
|
+
return result;
|
|
372
756
|
}
|
|
373
757
|
}
|
|
374
758
|
// Llamada a función como expresión
|
|
@@ -378,8 +762,16 @@ class Interpreter {
|
|
|
378
762
|
}
|
|
379
763
|
// Recorrer hijos recursivamente
|
|
380
764
|
if (f.args && f.args.length > 0) {
|
|
381
|
-
const newArgs = f.args.map((a) => a ? this.resolveFormulaRecursive(a,
|
|
382
|
-
|
|
765
|
+
const newArgs = f.args.map((a) => (a ? this.resolveFormulaRecursive(a, visited) : a));
|
|
766
|
+
// Check if args actually changed to avoid unnecessary object creation
|
|
767
|
+
let changed = false;
|
|
768
|
+
for (let i = 0; i < f.args.length; i++) {
|
|
769
|
+
if (f.args[i] !== newArgs[i]) {
|
|
770
|
+
changed = true;
|
|
771
|
+
break;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
return changed ? { ...f, args: newArgs } : f;
|
|
383
775
|
}
|
|
384
776
|
return f;
|
|
385
777
|
}
|
|
@@ -456,10 +848,60 @@ class Interpreter {
|
|
|
456
848
|
}
|
|
457
849
|
execTruthTableCmd(stmt) {
|
|
458
850
|
const profile = this.requireProfile();
|
|
851
|
+
const formula = this.resolveFormula(stmt.formula);
|
|
852
|
+
if (profile.name === 'classical.propositional') {
|
|
853
|
+
const atoms = Array.from((0, propositional_1.collectAtoms)(formula)).sort();
|
|
854
|
+
// Streaming de tabla de verdad para evitar OOM
|
|
855
|
+
this.emit(`Tabla de verdad para ${(0, propositional_1.formulaToString)(formula)}:`);
|
|
856
|
+
this.emit(` ${atoms.join(' | ')} | Resultado`);
|
|
857
|
+
this.emit(` ${atoms.map(() => '---').join('-|-')}-|----------`);
|
|
858
|
+
let isTautology = true;
|
|
859
|
+
let isSatisfiable = false;
|
|
860
|
+
let count = 0;
|
|
861
|
+
let satCount = 0;
|
|
862
|
+
for (const v of (0, propositional_1.generateValuationsLazy)(atoms)) {
|
|
863
|
+
const res = (0, propositional_1.evaluateClassical)(formula, v);
|
|
864
|
+
if (res) {
|
|
865
|
+
isSatisfiable = true;
|
|
866
|
+
satCount++;
|
|
867
|
+
}
|
|
868
|
+
else {
|
|
869
|
+
isTautology = false;
|
|
870
|
+
}
|
|
871
|
+
count++;
|
|
872
|
+
// Solo imprimir las primeras 64 filas para no saturar el stdout en tablas masivas
|
|
873
|
+
if (count <= 64) {
|
|
874
|
+
const row = atoms.map((a) => (v[a] ? 'V' : 'F')).join(' | ');
|
|
875
|
+
this.emit(` ${row} | ${res ? 'V' : 'F'}`);
|
|
876
|
+
}
|
|
877
|
+
else if (count === 65) {
|
|
878
|
+
this.emit(' ... (tabla masiva, ocultando filas intermedias) ...');
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
this.emit(`\nResumen: ${count} valuaciones analizadas.`);
|
|
882
|
+
this.emit(`Estatus: ${isTautology ? 'Tautología ✓' : isSatisfiable ? 'Satisfacible' : 'Contradicción ✗'}`);
|
|
883
|
+
this.results.push({
|
|
884
|
+
status: isTautology ? 'valid' : isSatisfiable ? 'satisfiable' : 'unsatisfiable',
|
|
885
|
+
output: `Tabla de verdad de ${count} filas procesada.`,
|
|
886
|
+
truthTable: {
|
|
887
|
+
variables: atoms,
|
|
888
|
+
rows: [],
|
|
889
|
+
subFormulas: [],
|
|
890
|
+
subFormulaValues: [],
|
|
891
|
+
isTautology,
|
|
892
|
+
isContradiction: !isSatisfiable,
|
|
893
|
+
isSatisfiable,
|
|
894
|
+
satisfyingCount: satCount,
|
|
895
|
+
},
|
|
896
|
+
diagnostics: [],
|
|
897
|
+
formula: formula,
|
|
898
|
+
});
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
// Fallback para otros perfiles
|
|
459
902
|
if (!profile.truthTable) {
|
|
460
903
|
throw new Error('Este perfil no soporta truth_table');
|
|
461
904
|
}
|
|
462
|
-
const formula = this.resolveFormula(stmt.formula);
|
|
463
905
|
const tt = profile.truthTable(formula);
|
|
464
906
|
const result = {
|
|
465
907
|
status: tt.isTautology ? 'valid' : tt.isSatisfiable ? 'satisfiable' : 'unsatisfiable',
|
|
@@ -485,19 +927,37 @@ class Interpreter {
|
|
|
485
927
|
this.emit(`Formalizacion ${stmt.name}: ${stmt.passageName} -> ${(0, propositional_1.formulaToString)(formula)}`);
|
|
486
928
|
}
|
|
487
929
|
else if (stmt.letType === 'description') {
|
|
488
|
-
this.
|
|
489
|
-
|
|
930
|
+
if (this.currentBindingFrame)
|
|
931
|
+
this.currentBindingFrame.descriptions.set(stmt.name, stmt.description);
|
|
932
|
+
else
|
|
933
|
+
this.letDescriptions.set(stmt.name, stmt.description);
|
|
934
|
+
if (this.shouldEmitLocalBindings())
|
|
935
|
+
this.emit(`Let ${stmt.name} = "${stmt.description}"`);
|
|
936
|
+
}
|
|
937
|
+
else if (stmt.letType === 'action') {
|
|
938
|
+
const actionStmt = stmt;
|
|
939
|
+
const captured = this.executeActionExpr(actionStmt.action);
|
|
940
|
+
this.results.push(captured.result);
|
|
941
|
+
this.bindCapturedAction(actionStmt.name, actionStmt.action.action, captured.primary, captured.result, captured.formulas, captured.extraBindings);
|
|
942
|
+
if (this.shouldEmitLocalBindings()) {
|
|
943
|
+
this.emit(`Let ${actionStmt.name} = ${(0, format_1.formulaToUnicode)(captured.primary)}`);
|
|
944
|
+
}
|
|
490
945
|
}
|
|
491
946
|
else if (stmt.letType === 'formula' && stmt.formula) {
|
|
492
|
-
|
|
493
|
-
this.
|
|
494
|
-
this.
|
|
947
|
+
// Resolve bindings but preserve symbolic structure (no constant-folding)
|
|
948
|
+
const resolved = this.resolveFormulaRecursive(stmt.formula, new Set());
|
|
949
|
+
this.defineBinding(stmt.name, resolved, 'description' in stmt ? stmt.description : undefined);
|
|
950
|
+
if (!this.currentBindingFrame)
|
|
951
|
+
this.theory.axioms.set(stmt.name, resolved);
|
|
495
952
|
if ('description' in stmt && stmt.description) {
|
|
496
|
-
this.
|
|
497
|
-
|
|
953
|
+
if (!this.currentBindingFrame)
|
|
954
|
+
this.letDescriptions.set(stmt.name, stmt.description);
|
|
955
|
+
if (this.shouldEmitLocalBindings())
|
|
956
|
+
this.emit(`Let ${stmt.name} = "${stmt.description}" : ${(0, format_1.formulaToUnicode)(resolved)}`);
|
|
498
957
|
}
|
|
499
958
|
else {
|
|
500
|
-
|
|
959
|
+
if (this.shouldEmitLocalBindings())
|
|
960
|
+
this.emit(`Let ${stmt.name} = ${(0, format_1.formulaToUnicode)(resolved)}`);
|
|
501
961
|
}
|
|
502
962
|
}
|
|
503
963
|
}
|
|
@@ -637,21 +1097,7 @@ class Interpreter {
|
|
|
637
1097
|
}
|
|
638
1098
|
const resolvedGoal = this.resolveFormula(stmt.goal);
|
|
639
1099
|
this.emit(` show ${(0, format_1.formulaToUnicode)(resolvedGoal)}`);
|
|
640
|
-
|
|
641
|
-
try {
|
|
642
|
-
this.executeStatement(bodyStmt);
|
|
643
|
-
}
|
|
644
|
-
catch (e) {
|
|
645
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
646
|
-
this.diagnostics.push({
|
|
647
|
-
severity: 'error',
|
|
648
|
-
message,
|
|
649
|
-
file: stmt.source.file,
|
|
650
|
-
line: bodyStmt.source.line,
|
|
651
|
-
column: bodyStmt.source.column,
|
|
652
|
-
});
|
|
653
|
-
}
|
|
654
|
-
}
|
|
1100
|
+
this.executeStatementsIterative(stmt.body, stmt.source.file);
|
|
655
1101
|
const premiseNames = stmt.assumptions.map((a) => a.name);
|
|
656
1102
|
const result = profile.derive(resolvedGoal, premiseNames, this.theory);
|
|
657
1103
|
this.results.push(result);
|
|
@@ -670,6 +1116,7 @@ class Interpreter {
|
|
|
670
1116
|
this.theory.axioms = savedAxioms;
|
|
671
1117
|
this.letBindings = savedLetBindings;
|
|
672
1118
|
this.letDescriptions = savedLetDescriptions;
|
|
1119
|
+
this.invalidateResolveCache();
|
|
673
1120
|
this.emit('── End Proof Block ──');
|
|
674
1121
|
}
|
|
675
1122
|
execTheoryDecl(stmt) {
|
|
@@ -768,6 +1215,7 @@ class Interpreter {
|
|
|
768
1215
|
this.theory.axioms = savedAxioms;
|
|
769
1216
|
this.theory.theorems = savedTheorems;
|
|
770
1217
|
this.currentTheoryName = savedTheoryName;
|
|
1218
|
+
this.invalidateResolveCache();
|
|
771
1219
|
this.theories.set(theoryName, scope);
|
|
772
1220
|
this.emit(`── End Theory Instance ${theoryName} ──`);
|
|
773
1221
|
return theoryName;
|
|
@@ -776,107 +1224,29 @@ class Interpreter {
|
|
|
776
1224
|
if (stmt.value !== null)
|
|
777
1225
|
this.emit(stmt.value);
|
|
778
1226
|
else if (stmt.formula) {
|
|
779
|
-
|
|
1227
|
+
// Resolve bindings but skip constant-folding so print shows symbolic form
|
|
1228
|
+
const resolved = this.resolveFormulaRecursive(stmt.formula, new Set());
|
|
780
1229
|
this.emit((0, format_1.formulaToUnicode)(resolved));
|
|
781
1230
|
}
|
|
782
1231
|
}
|
|
783
1232
|
execSetCmd(stmt) {
|
|
784
|
-
const resolved = this.
|
|
785
|
-
this.
|
|
786
|
-
this.
|
|
787
|
-
|
|
1233
|
+
const resolved = this.evaluateFormulaValue(stmt.formula);
|
|
1234
|
+
this.setBinding(stmt.name, resolved);
|
|
1235
|
+
if (!this.currentBindingFrame)
|
|
1236
|
+
this.theory.axioms.set(stmt.name, resolved);
|
|
1237
|
+
if (this.shouldEmitLocalBindings())
|
|
1238
|
+
this.emit(`Set ${stmt.name} = ${(0, format_1.formulaToUnicode)(resolved)}`);
|
|
788
1239
|
}
|
|
789
1240
|
execIfStmt(stmt) {
|
|
790
|
-
const
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
let matched;
|
|
794
|
-
if (branch.condition === 'valid' || branch.condition === 'invalid') {
|
|
795
|
-
const result = profile.checkValid(resolved);
|
|
796
|
-
matched =
|
|
797
|
-
branch.condition === 'valid' ? result.status === 'valid' : result.status !== 'valid';
|
|
798
|
-
}
|
|
799
|
-
else {
|
|
800
|
-
const result = profile.checkSatisfiable(resolved);
|
|
801
|
-
matched =
|
|
802
|
-
branch.condition === 'satisfiable'
|
|
803
|
-
? result.status === 'satisfiable' || result.status === 'valid'
|
|
804
|
-
: result.status === 'unsatisfiable';
|
|
805
|
-
}
|
|
806
|
-
if (matched) {
|
|
807
|
-
for (const bodyStmt of branch.body) {
|
|
808
|
-
if (this.returnSignal)
|
|
809
|
-
return;
|
|
810
|
-
this.executeStatement(bodyStmt);
|
|
811
|
-
}
|
|
812
|
-
return;
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
if (stmt.elseBranch) {
|
|
816
|
-
for (const bodyStmt of stmt.elseBranch) {
|
|
817
|
-
if (this.returnSignal)
|
|
818
|
-
return;
|
|
819
|
-
this.executeStatement(bodyStmt);
|
|
820
|
-
}
|
|
821
|
-
}
|
|
1241
|
+
const selectedBody = this.selectIfBody(stmt);
|
|
1242
|
+
if (selectedBody)
|
|
1243
|
+
this.executeStatementsIterative(selectedBody, stmt.source.file);
|
|
822
1244
|
}
|
|
823
1245
|
execForStmt(stmt) {
|
|
824
|
-
|
|
825
|
-
for (const item of stmt.items) {
|
|
826
|
-
if (this.returnSignal)
|
|
827
|
-
break;
|
|
828
|
-
const resolved = this.resolveFormula(item);
|
|
829
|
-
this.letBindings.set(stmt.variable, resolved);
|
|
830
|
-
for (const bodyStmt of stmt.body) {
|
|
831
|
-
if (this.returnSignal)
|
|
832
|
-
break;
|
|
833
|
-
this.executeStatement(bodyStmt);
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
if (savedBinding !== undefined)
|
|
837
|
-
this.letBindings.set(stmt.variable, savedBinding);
|
|
838
|
-
else
|
|
839
|
-
this.letBindings.delete(stmt.variable);
|
|
1246
|
+
this.executeStatementsIterative([stmt], stmt.source.file);
|
|
840
1247
|
}
|
|
841
1248
|
execWhileStmt(stmt) {
|
|
842
|
-
|
|
843
|
-
const maxIter = stmt.maxIterations || 1000;
|
|
844
|
-
let iter = 0;
|
|
845
|
-
while (iter < maxIter) {
|
|
846
|
-
if (this.returnSignal)
|
|
847
|
-
break;
|
|
848
|
-
iter++;
|
|
849
|
-
const resolved = this.resolveFormula(stmt.formula);
|
|
850
|
-
let matched;
|
|
851
|
-
if (stmt.condition === 'valid' || stmt.condition === 'invalid') {
|
|
852
|
-
const result = profile.checkValid(resolved);
|
|
853
|
-
matched =
|
|
854
|
-
stmt.condition === 'valid' ? result.status === 'valid' : result.status !== 'valid';
|
|
855
|
-
}
|
|
856
|
-
else {
|
|
857
|
-
const result = profile.checkSatisfiable(resolved);
|
|
858
|
-
matched =
|
|
859
|
-
stmt.condition === 'satisfiable'
|
|
860
|
-
? result.status === 'satisfiable' || result.status === 'valid'
|
|
861
|
-
: result.status === 'unsatisfiable';
|
|
862
|
-
}
|
|
863
|
-
if (!matched)
|
|
864
|
-
break;
|
|
865
|
-
for (const bodyStmt of stmt.body) {
|
|
866
|
-
if (this.returnSignal)
|
|
867
|
-
break;
|
|
868
|
-
this.executeStatement(bodyStmt);
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
if (iter >= maxIter) {
|
|
872
|
-
this.diagnostics.push({
|
|
873
|
-
severity: 'warning',
|
|
874
|
-
message: `while: se alcanzó el límite de ${maxIter} iteraciones`,
|
|
875
|
-
file: stmt.source.file,
|
|
876
|
-
line: stmt.source.line,
|
|
877
|
-
column: stmt.source.column,
|
|
878
|
-
});
|
|
879
|
-
}
|
|
1249
|
+
this.executeStatementsIterative([stmt], stmt.source.file);
|
|
880
1250
|
}
|
|
881
1251
|
execFnDecl(stmt) {
|
|
882
1252
|
const name = this.currentTheoryName ? `${this.currentTheoryName}.${stmt.name}` : stmt.name;
|
|
@@ -888,77 +1258,249 @@ class Interpreter {
|
|
|
888
1258
|
}
|
|
889
1259
|
execReturnStmt(stmt) {
|
|
890
1260
|
if (stmt.formula)
|
|
891
|
-
this.returnValue = this.
|
|
1261
|
+
this.returnValue = this.evaluateFormulaValue(stmt.formula);
|
|
892
1262
|
else
|
|
893
1263
|
this.returnValue = undefined;
|
|
894
1264
|
this.returnSignal = true;
|
|
895
1265
|
}
|
|
896
1266
|
executeFnCall(stmt) {
|
|
1267
|
+
const initial = this.startFunctionFrame(stmt, undefined);
|
|
1268
|
+
if ('result' in initial)
|
|
1269
|
+
return initial.result;
|
|
1270
|
+
const callStack = [initial.frame];
|
|
1271
|
+
let finalResult = undefined;
|
|
1272
|
+
while (callStack.length > 0) {
|
|
1273
|
+
const frame = callStack[callStack.length - 1];
|
|
1274
|
+
if (this.returnSignal) {
|
|
1275
|
+
const result = this.finishFunctionFrame(frame);
|
|
1276
|
+
callStack.pop();
|
|
1277
|
+
if (frame.continuation)
|
|
1278
|
+
this.applyFunctionContinuation(frame.continuation, result);
|
|
1279
|
+
else
|
|
1280
|
+
finalResult = result;
|
|
1281
|
+
continue;
|
|
1282
|
+
}
|
|
1283
|
+
const nextStmt = this.nextRuntimeStatement(frame.runtimeStack);
|
|
1284
|
+
if (!nextStmt) {
|
|
1285
|
+
const result = this.finishFunctionFrame(frame);
|
|
1286
|
+
callStack.pop();
|
|
1287
|
+
if (frame.continuation)
|
|
1288
|
+
this.applyFunctionContinuation(frame.continuation, result);
|
|
1289
|
+
else
|
|
1290
|
+
finalResult = result;
|
|
1291
|
+
continue;
|
|
1292
|
+
}
|
|
1293
|
+
const nestedCall = this.getDirectFunctionCall(nextStmt);
|
|
1294
|
+
if (nestedCall) {
|
|
1295
|
+
if (nestedCall.type === 'return_stmt') {
|
|
1296
|
+
const replaced = this.replaceFunctionFrame(frame, nestedCall.call);
|
|
1297
|
+
if ('result' in replaced) {
|
|
1298
|
+
this.returnValue = replaced.result || { kind: 'atom', name: 'undefined' };
|
|
1299
|
+
this.returnSignal = true;
|
|
1300
|
+
}
|
|
1301
|
+
else {
|
|
1302
|
+
callStack[callStack.length - 1] = replaced.frame;
|
|
1303
|
+
}
|
|
1304
|
+
continue;
|
|
1305
|
+
}
|
|
1306
|
+
const started = this.startFunctionFrame(nestedCall.call, {
|
|
1307
|
+
type: nestedCall.type,
|
|
1308
|
+
stmt: nestedCall.stmt,
|
|
1309
|
+
});
|
|
1310
|
+
if ('result' in started)
|
|
1311
|
+
this.applyFunctionContinuation(started.resultContinuation, started.result);
|
|
1312
|
+
else
|
|
1313
|
+
callStack.push(started.frame);
|
|
1314
|
+
continue;
|
|
1315
|
+
}
|
|
1316
|
+
this.dispatchRuntimeStatement(nextStmt, frame.runtimeStack);
|
|
1317
|
+
}
|
|
1318
|
+
return finalResult;
|
|
1319
|
+
}
|
|
1320
|
+
startFunctionFrame(stmt, continuation, inheritedReturnState) {
|
|
897
1321
|
this.callDepth++;
|
|
898
1322
|
if (this.callDepth > MAX_CALL_DEPTH) {
|
|
899
1323
|
this.callDepth--;
|
|
900
1324
|
throw new Error(`Límite de recursión excedido (${MAX_CALL_DEPTH}).`);
|
|
901
1325
|
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1326
|
+
this.runtimeCallCount++;
|
|
1327
|
+
if (this.runtimeCallCount > this.getRuntimeCallLimit()) {
|
|
1328
|
+
this.callDepth--;
|
|
1329
|
+
throw new Error(`Límite de llamadas ST excedido (${this.getRuntimeCallLimit()}).`);
|
|
1330
|
+
}
|
|
1331
|
+
if ([
|
|
1332
|
+
'typeof',
|
|
1333
|
+
'is_valid',
|
|
1334
|
+
'is_satisfiable',
|
|
1335
|
+
'get_atoms',
|
|
1336
|
+
'atoms_of',
|
|
1337
|
+
'len',
|
|
1338
|
+
'at',
|
|
1339
|
+
'formula_eq',
|
|
1340
|
+
'input',
|
|
1341
|
+
].includes(stmt.name)) {
|
|
1342
|
+
const result = this.executeBuiltin(stmt.name, stmt.args);
|
|
1343
|
+
this.callDepth--;
|
|
1344
|
+
return { result, resultContinuation: continuation || { type: 'discard' } };
|
|
1345
|
+
}
|
|
1346
|
+
if (stmt.name.includes('.')) {
|
|
1347
|
+
const [prefix, methodName] = stmt.name.split('.', 2);
|
|
1348
|
+
let actualInstanceName = prefix;
|
|
1349
|
+
if (this.hasBinding(prefix)) {
|
|
1350
|
+
const resolved = this.getBinding(prefix);
|
|
1351
|
+
if (resolved.kind === 'atom' && resolved.name)
|
|
1352
|
+
actualInstanceName = resolved.name;
|
|
905
1353
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1354
|
+
const scope = this.theories.get(actualInstanceName);
|
|
1355
|
+
if (scope) {
|
|
1356
|
+
const internalFnName = `${actualInstanceName}.${methodName}`;
|
|
1357
|
+
const fn = this.functions.get(internalFnName);
|
|
1358
|
+
if (fn)
|
|
1359
|
+
return {
|
|
1360
|
+
frame: this.createFunctionFrame(internalFnName, fn, stmt.args, continuation, scope, inheritedReturnState),
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
const template = this.theoryTemplates.get(stmt.name);
|
|
1365
|
+
if (template) {
|
|
1366
|
+
const instanceId = `inst_${stmt.name}_${this.theories.size}`;
|
|
1367
|
+
this.instantiateTheory(template.node, instanceId, stmt.args);
|
|
1368
|
+
this.callDepth--;
|
|
1369
|
+
return {
|
|
1370
|
+
result: { kind: 'atom', name: instanceId },
|
|
1371
|
+
resultContinuation: continuation || { type: 'discard' },
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
const fn = this.functions.get(stmt.name);
|
|
1375
|
+
if (!fn) {
|
|
1376
|
+
this.callDepth--;
|
|
1377
|
+
throw new Error(`Función o Teoría '${stmt.name}' no declarada`);
|
|
1378
|
+
}
|
|
1379
|
+
return {
|
|
1380
|
+
frame: this.createFunctionFrame(stmt.name, fn, stmt.args, continuation, undefined, inheritedReturnState),
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
createFunctionFrame(name, fn, args, continuation, scope, inheritedReturnState) {
|
|
1384
|
+
if (args.length !== fn.params.length) {
|
|
1385
|
+
this.callDepth--;
|
|
1386
|
+
throw new Error(`Argumentos incorrectos.`);
|
|
1387
|
+
}
|
|
1388
|
+
let scopeSnapshot;
|
|
1389
|
+
if (scope) {
|
|
1390
|
+
scopeSnapshot = {
|
|
1391
|
+
letBindings: new Map(this.letBindings),
|
|
1392
|
+
axioms: new Map(this.theory.axioms),
|
|
1393
|
+
theorems: new Map(this.theory.theorems),
|
|
1394
|
+
theoryName: this.currentTheoryName,
|
|
1395
|
+
};
|
|
1396
|
+
for (const [key, value] of scope.letBindings)
|
|
1397
|
+
this.letBindings.set(key, value);
|
|
1398
|
+
for (const [key, value] of scope.axioms)
|
|
1399
|
+
this.theory.axioms.set(key, value);
|
|
1400
|
+
for (const [key, value] of scope.theorems)
|
|
1401
|
+
this.theory.theorems.set(key, value);
|
|
1402
|
+
this.currentTheoryName = scope.name;
|
|
1403
|
+
}
|
|
1404
|
+
const bindingFrame = this.createBindingFrame();
|
|
1405
|
+
this.currentBindingFrame = bindingFrame;
|
|
1406
|
+
for (let i = 0; i < fn.params.length; i++) {
|
|
1407
|
+
this.currentBindingFrame.bindings.set(fn.params[i], this.evaluateFormulaValue(args[i]));
|
|
1408
|
+
}
|
|
1409
|
+
const savedReturnSignal = inheritedReturnState?.signal ?? this.returnSignal;
|
|
1410
|
+
const savedReturnValue = inheritedReturnState?.value ?? this.returnValue;
|
|
1411
|
+
this.returnSignal = false;
|
|
1412
|
+
this.returnValue = undefined;
|
|
1413
|
+
return {
|
|
1414
|
+
name,
|
|
1415
|
+
runtimeStack: [{ kind: 'statements', statements: fn.body, index: 0 }],
|
|
1416
|
+
bindingFrame,
|
|
1417
|
+
savedReturnSignal,
|
|
1418
|
+
savedReturnValue,
|
|
1419
|
+
scopeSnapshot,
|
|
1420
|
+
continuation,
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
finishFunctionFrame(frame) {
|
|
1424
|
+
const result = this.returnValue;
|
|
1425
|
+
this.returnSignal = frame.savedReturnSignal;
|
|
1426
|
+
this.returnValue = frame.savedReturnValue;
|
|
1427
|
+
this.discardFunctionFrameState(frame);
|
|
1428
|
+
this.callDepth--;
|
|
1429
|
+
return result;
|
|
1430
|
+
}
|
|
1431
|
+
discardFunctionFrameState(frame) {
|
|
1432
|
+
this.currentBindingFrame = frame.bindingFrame.parent;
|
|
1433
|
+
if (frame.scopeSnapshot) {
|
|
1434
|
+
this.letBindings = frame.scopeSnapshot.letBindings;
|
|
1435
|
+
this.theory.axioms = frame.scopeSnapshot.axioms;
|
|
1436
|
+
this.theory.theorems = frame.scopeSnapshot.theorems;
|
|
1437
|
+
this.currentTheoryName = frame.scopeSnapshot.theoryName;
|
|
1438
|
+
this.invalidateResolveCache();
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
replaceFunctionFrame(frame, stmt) {
|
|
1442
|
+
const inheritedReturnState = {
|
|
1443
|
+
signal: frame.savedReturnSignal,
|
|
1444
|
+
value: frame.savedReturnValue,
|
|
1445
|
+
};
|
|
1446
|
+
const inheritedContinuation = frame.continuation;
|
|
1447
|
+
const evaluatedArgs = stmt.args.map((arg) => this.evaluateFormulaValue(arg));
|
|
1448
|
+
this.discardFunctionFrameState(frame);
|
|
1449
|
+
this.callDepth--;
|
|
1450
|
+
const replaced = this.startFunctionFrame({ name: stmt.name, args: evaluatedArgs }, inheritedContinuation, inheritedReturnState);
|
|
1451
|
+
if ('frame' in replaced)
|
|
1452
|
+
return replaced;
|
|
1453
|
+
return { result: replaced.result };
|
|
1454
|
+
}
|
|
1455
|
+
nextRuntimeStatement(runtimeStack) {
|
|
1456
|
+
while (runtimeStack.length > 0) {
|
|
1457
|
+
const frame = runtimeStack[runtimeStack.length - 1];
|
|
1458
|
+
if (frame.kind === 'statements') {
|
|
1459
|
+
if (frame.index >= frame.statements.length) {
|
|
1460
|
+
runtimeStack.pop();
|
|
1461
|
+
continue;
|
|
913
1462
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1463
|
+
return frame.statements[frame.index++];
|
|
1464
|
+
}
|
|
1465
|
+
if (frame.kind === 'for') {
|
|
1466
|
+
if (this.returnSignal || frame.index >= frame.stmt.items.length) {
|
|
1467
|
+
this.restoreBindingSnapshot(frame.stmt.variable, frame.savedBinding);
|
|
1468
|
+
runtimeStack.pop();
|
|
1469
|
+
continue;
|
|
920
1470
|
}
|
|
1471
|
+
const resolved = this.evaluateFormulaValue(frame.stmt.items[frame.index]);
|
|
1472
|
+
this.setBinding(frame.stmt.variable, resolved);
|
|
1473
|
+
frame.index++;
|
|
1474
|
+
runtimeStack.push({ kind: 'statements', statements: frame.stmt.body, index: 0 });
|
|
1475
|
+
continue;
|
|
921
1476
|
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
this.instantiateTheory(template.node, instanceId, stmt.args);
|
|
926
|
-
return { kind: 'atom', name: instanceId };
|
|
1477
|
+
if (this.returnSignal) {
|
|
1478
|
+
runtimeStack.pop();
|
|
1479
|
+
continue;
|
|
927
1480
|
}
|
|
928
|
-
const
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
this.returnSignal = false;
|
|
941
|
-
this.returnValue = undefined;
|
|
942
|
-
for (const bodyStmt of fn.body) {
|
|
943
|
-
if (this.returnSignal)
|
|
944
|
-
break;
|
|
945
|
-
this.executeStatement(bodyStmt);
|
|
1481
|
+
const profile = this.requireProfile();
|
|
1482
|
+
const maxIter = frame.stmt.maxIterations || 1000;
|
|
1483
|
+
if (frame.iterations >= maxIter) {
|
|
1484
|
+
this.diagnostics.push({
|
|
1485
|
+
severity: 'warning',
|
|
1486
|
+
message: `while: se alcanzó el límite de ${maxIter} iteraciones`,
|
|
1487
|
+
file: frame.stmt.source.file,
|
|
1488
|
+
line: frame.stmt.source.line,
|
|
1489
|
+
column: frame.stmt.source.column,
|
|
1490
|
+
});
|
|
1491
|
+
runtimeStack.pop();
|
|
1492
|
+
continue;
|
|
946
1493
|
}
|
|
947
|
-
const
|
|
948
|
-
this.
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
if (prev !== undefined)
|
|
953
|
-
this.letBindings.set(param, prev);
|
|
954
|
-
else
|
|
955
|
-
this.letBindings.delete(param);
|
|
1494
|
+
const resolved = this.evaluateFormulaValue(frame.stmt.formula);
|
|
1495
|
+
const matched = this.matchesRuntimeCondition(profile, frame.stmt.condition, resolved);
|
|
1496
|
+
if (!matched) {
|
|
1497
|
+
runtimeStack.pop();
|
|
1498
|
+
continue;
|
|
956
1499
|
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
finally {
|
|
960
|
-
this.callDepth--;
|
|
1500
|
+
frame.iterations++;
|
|
1501
|
+
runtimeStack.push({ kind: 'statements', statements: frame.stmt.body, index: 0 });
|
|
961
1502
|
}
|
|
1503
|
+
return undefined;
|
|
962
1504
|
}
|
|
963
1505
|
executeFunctionInScope(fn, args, scope) {
|
|
964
1506
|
this.callDepth++;
|
|
@@ -1009,38 +1551,42 @@ class Interpreter {
|
|
|
1009
1551
|
add: (a, b) => a + b,
|
|
1010
1552
|
subtract: (a, b) => a - b,
|
|
1011
1553
|
multiply: (a, b) => a * b,
|
|
1012
|
-
divide: (a, b) =>
|
|
1013
|
-
modulo: (a, b) =>
|
|
1014
|
-
};
|
|
1015
|
-
const CMP_OPS = {
|
|
1016
|
-
less: (a, b) => (a < b ? 1 : 0),
|
|
1017
|
-
greater: (a, b) => (a > b ? 1 : 0),
|
|
1018
|
-
less_eq: (a, b) => (a <= b ? 1 : 0),
|
|
1019
|
-
greater_eq: (a, b) => (a >= b ? 1 : 0),
|
|
1554
|
+
divide: (a, b) => a / b,
|
|
1555
|
+
modulo: (a, b) => a % b,
|
|
1020
1556
|
};
|
|
1557
|
+
// Note: comparison operators (less, greater, etc.) are NOT folded here.
|
|
1558
|
+
// They must retain their structural form so profiles can evaluate them properly.
|
|
1021
1559
|
const newArgs = f.args.map((a) => this.tryConstantFold(a));
|
|
1022
1560
|
if (newArgs.length === 2 &&
|
|
1023
1561
|
newArgs[0].kind === 'number' &&
|
|
1024
1562
|
newArgs[1].kind === 'number' &&
|
|
1025
|
-
|
|
1026
|
-
|
|
1563
|
+
ARITH_OPS[f.kind]) {
|
|
1564
|
+
// Don't fold division/modulo by zero — preserve structure for checkWellFormed
|
|
1565
|
+
if ((f.kind === 'divide' || f.kind === 'modulo') && (newArgs[1].value ?? 0) === 0) {
|
|
1566
|
+
return { ...f, args: newArgs };
|
|
1567
|
+
}
|
|
1568
|
+
const op = ARITH_OPS[f.kind];
|
|
1027
1569
|
const res = op(newArgs[0].value ?? 0, newArgs[1].value ?? 0);
|
|
1028
1570
|
return { kind: 'number', value: res, source: f.source };
|
|
1029
1571
|
}
|
|
1030
1572
|
return { ...f, args: newArgs };
|
|
1031
1573
|
}
|
|
1032
1574
|
executeBuiltin(name, args) {
|
|
1033
|
-
const arg = this.resolveFormula(args[0]);
|
|
1575
|
+
const arg = args[0] ? this.resolveFormula(args[0]) : undefined;
|
|
1034
1576
|
if (name === 'typeof') {
|
|
1035
1577
|
let typeStr = 'Formula';
|
|
1036
|
-
if (arg
|
|
1578
|
+
if (arg?.kind === 'number')
|
|
1037
1579
|
typeStr = 'Number';
|
|
1038
|
-
if (arg
|
|
1580
|
+
if (arg?.kind === 'list')
|
|
1581
|
+
typeStr = 'List';
|
|
1582
|
+
if (arg?.kind === 'atom' && arg.name?.startsWith('"'))
|
|
1039
1583
|
typeStr = 'String';
|
|
1040
|
-
return { kind: 'atom', name: `"${typeStr}"`, source: arg
|
|
1584
|
+
return { kind: 'atom', name: `"${typeStr}"`, source: arg?.source };
|
|
1041
1585
|
}
|
|
1042
1586
|
if (name === 'is_valid' || name === 'is_satisfiable') {
|
|
1043
1587
|
const profile = this.requireProfile();
|
|
1588
|
+
if (!arg)
|
|
1589
|
+
return { kind: 'atom', name: '"Error"' };
|
|
1044
1590
|
try {
|
|
1045
1591
|
const result = name === 'is_valid' ? profile.checkValid(arg) : profile.checkSatisfiable(arg);
|
|
1046
1592
|
const isTrue = result.status === 'valid' || result.status === 'satisfiable';
|
|
@@ -1051,10 +1597,55 @@ class Interpreter {
|
|
|
1051
1597
|
}
|
|
1052
1598
|
}
|
|
1053
1599
|
if (name === 'get_atoms') {
|
|
1600
|
+
if (!arg)
|
|
1601
|
+
return { kind: 'atom', name: '"{ }"' };
|
|
1054
1602
|
const atoms = this.collectAtoms(arg);
|
|
1055
1603
|
return { kind: 'atom', name: `"{ ${atoms.join(', ')} }"`, source: arg.source };
|
|
1056
1604
|
}
|
|
1605
|
+
if (name === 'atoms_of') {
|
|
1606
|
+
const source = arg?.source;
|
|
1607
|
+
const items = (arg ? this.collectValueAtoms(arg) : []).map((atomName) => ({
|
|
1608
|
+
kind: 'atom',
|
|
1609
|
+
name: atomName,
|
|
1610
|
+
source,
|
|
1611
|
+
}));
|
|
1612
|
+
return { kind: 'list', args: items, source };
|
|
1613
|
+
}
|
|
1614
|
+
if (name === 'len') {
|
|
1615
|
+
if (arg?.kind === 'list') {
|
|
1616
|
+
return { kind: 'number', value: arg.args?.length ?? 0, source: arg.source };
|
|
1617
|
+
}
|
|
1618
|
+
if (arg?.kind === 'atom' && arg.name?.startsWith('"')) {
|
|
1619
|
+
return {
|
|
1620
|
+
kind: 'number',
|
|
1621
|
+
value: arg.name.replace(/(^"|"$)/g, '').length,
|
|
1622
|
+
source: arg.source,
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
return { kind: 'number', value: 0, source: arg?.source };
|
|
1626
|
+
}
|
|
1627
|
+
if (name === 'at') {
|
|
1628
|
+
const listArg = arg;
|
|
1629
|
+
const indexArg = args[1] ? this.resolveFormula(args[1]) : undefined;
|
|
1630
|
+
if (listArg?.kind === 'list' && indexArg?.kind === 'number') {
|
|
1631
|
+
const index = Math.floor(indexArg.value ?? -1);
|
|
1632
|
+
return listArg.args?.[index] || { kind: 'atom', name: 'undefined', source: listArg.source };
|
|
1633
|
+
}
|
|
1634
|
+
return { kind: 'atom', name: 'undefined', source: listArg?.source || indexArg?.source };
|
|
1635
|
+
}
|
|
1636
|
+
if (name === 'formula_eq') {
|
|
1637
|
+
const left = arg;
|
|
1638
|
+
const right = args[1] ? this.resolveFormula(args[1]) : undefined;
|
|
1639
|
+
const equal = !!left && !!right && (0, propositional_1.formulaToString)(left) === (0, propositional_1.formulaToString)(right);
|
|
1640
|
+
return {
|
|
1641
|
+
kind: 'number',
|
|
1642
|
+
value: equal ? 1 : 0,
|
|
1643
|
+
source: left?.source || right?.source,
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1057
1646
|
if (name === 'input') {
|
|
1647
|
+
if (!arg)
|
|
1648
|
+
return { kind: 'atom', name: '""' };
|
|
1058
1649
|
const prompt = arg.kind === 'atom' && arg.name?.startsWith('"')
|
|
1059
1650
|
? arg.name.replace(/(^"|"$)/g, '')
|
|
1060
1651
|
: (0, propositional_1.formulaToString)(arg);
|
|
@@ -1075,6 +1666,197 @@ class Interpreter {
|
|
|
1075
1666
|
}
|
|
1076
1667
|
return undefined;
|
|
1077
1668
|
}
|
|
1669
|
+
executeActionExpr(action) {
|
|
1670
|
+
const profile = this.requireProfile();
|
|
1671
|
+
switch (action.action) {
|
|
1672
|
+
case 'check_valid': {
|
|
1673
|
+
const formula = this.resolveFormula(action.formula);
|
|
1674
|
+
return {
|
|
1675
|
+
result: profile.checkValid(formula),
|
|
1676
|
+
primary: formula,
|
|
1677
|
+
formulas: [formula],
|
|
1678
|
+
extraBindings: [],
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
case 'check_satisfiable': {
|
|
1682
|
+
const formula = this.resolveFormula(action.formula);
|
|
1683
|
+
return {
|
|
1684
|
+
result: profile.checkSatisfiable(formula),
|
|
1685
|
+
primary: formula,
|
|
1686
|
+
formulas: [formula],
|
|
1687
|
+
extraBindings: [],
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
case 'check_equivalent': {
|
|
1691
|
+
if (!profile.checkEquivalent) {
|
|
1692
|
+
throw new Error('Este perfil no soporta check equivalent');
|
|
1693
|
+
}
|
|
1694
|
+
const left = this.resolveFormula(action.left);
|
|
1695
|
+
const right = this.resolveFormula(action.right);
|
|
1696
|
+
const comparison = {
|
|
1697
|
+
kind: 'biconditional',
|
|
1698
|
+
args: [left, right],
|
|
1699
|
+
source: action.source,
|
|
1700
|
+
};
|
|
1701
|
+
return {
|
|
1702
|
+
result: profile.checkEquivalent(left, right),
|
|
1703
|
+
primary: comparison,
|
|
1704
|
+
formulas: [left, right],
|
|
1705
|
+
extraBindings: [
|
|
1706
|
+
['left', left],
|
|
1707
|
+
['right', right],
|
|
1708
|
+
[
|
|
1709
|
+
'equivalent',
|
|
1710
|
+
{
|
|
1711
|
+
kind: 'number',
|
|
1712
|
+
value: (0, propositional_1.formulaToString)(left) === (0, propositional_1.formulaToString)(right) ? 1 : 0,
|
|
1713
|
+
source: action.source,
|
|
1714
|
+
},
|
|
1715
|
+
],
|
|
1716
|
+
],
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
case 'derive': {
|
|
1720
|
+
const goal = this.resolveFormula(action.goal);
|
|
1721
|
+
const premises = action.premises || [];
|
|
1722
|
+
const result = profile.derive(goal, premises, this.theory);
|
|
1723
|
+
const premiseFormulas = premises
|
|
1724
|
+
.map((premise) => this.theory.axioms.get(premise) || this.theory.theorems.get(premise))
|
|
1725
|
+
.filter((value) => !!value)
|
|
1726
|
+
.map((formula) => this.resolveFormula(formula));
|
|
1727
|
+
return {
|
|
1728
|
+
result,
|
|
1729
|
+
primary: result.formula || goal,
|
|
1730
|
+
formulas: [goal, ...premiseFormulas],
|
|
1731
|
+
extraBindings: [
|
|
1732
|
+
['goal', goal],
|
|
1733
|
+
['premises', this.createListFormula(premiseFormulas, action.source)],
|
|
1734
|
+
['premise_names', this.createStringListFormula(premises, action.source)],
|
|
1735
|
+
],
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1738
|
+
case 'prove': {
|
|
1739
|
+
const goal = this.resolveFormula(action.goal);
|
|
1740
|
+
const premises = action.premises || [];
|
|
1741
|
+
const result = profile.prove(goal, this.theory);
|
|
1742
|
+
const premiseFormulas = premises
|
|
1743
|
+
.map((premise) => this.theory.axioms.get(premise) || this.theory.theorems.get(premise))
|
|
1744
|
+
.filter((value) => !!value)
|
|
1745
|
+
.map((formula) => this.resolveFormula(formula));
|
|
1746
|
+
return {
|
|
1747
|
+
result,
|
|
1748
|
+
primary: result.formula || goal,
|
|
1749
|
+
formulas: [goal, ...premiseFormulas],
|
|
1750
|
+
extraBindings: [
|
|
1751
|
+
['goal', goal],
|
|
1752
|
+
['premises', this.createListFormula(premiseFormulas, action.source)],
|
|
1753
|
+
['premise_names', this.createStringListFormula(premises, action.source)],
|
|
1754
|
+
],
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
case 'countermodel': {
|
|
1758
|
+
const formula = this.resolveFormula(action.formula);
|
|
1759
|
+
const result = profile.countermodel(formula);
|
|
1760
|
+
return {
|
|
1761
|
+
result,
|
|
1762
|
+
primary: result.formula || formula,
|
|
1763
|
+
formulas: [formula],
|
|
1764
|
+
extraBindings: [
|
|
1765
|
+
[
|
|
1766
|
+
'has_countermodel',
|
|
1767
|
+
{ kind: 'number', value: result.model ? 1 : 0, source: action.source },
|
|
1768
|
+
],
|
|
1769
|
+
],
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
case 'truth_table': {
|
|
1773
|
+
if (!profile.truthTable) {
|
|
1774
|
+
throw new Error('Este perfil no soporta truth_table');
|
|
1775
|
+
}
|
|
1776
|
+
const formula = this.resolveFormula(action.formula);
|
|
1777
|
+
const tt = profile.truthTable(formula);
|
|
1778
|
+
const result = {
|
|
1779
|
+
status: tt.isTautology ? 'valid' : tt.isSatisfiable ? 'satisfiable' : 'unsatisfiable',
|
|
1780
|
+
output: this.formatTruthTable(formula, tt),
|
|
1781
|
+
truthTable: tt,
|
|
1782
|
+
diagnostics: [],
|
|
1783
|
+
formula,
|
|
1784
|
+
};
|
|
1785
|
+
return {
|
|
1786
|
+
result,
|
|
1787
|
+
primary: formula,
|
|
1788
|
+
formulas: [formula],
|
|
1789
|
+
extraBindings: [
|
|
1790
|
+
['variables', this.createAtomListFormula(tt.variables, action.source)],
|
|
1791
|
+
['rows_count', { kind: 'number', value: tt.rows.length, source: action.source }],
|
|
1792
|
+
[
|
|
1793
|
+
'satisfying_count',
|
|
1794
|
+
{ kind: 'number', value: tt.satisfyingCount ?? 0, source: action.source },
|
|
1795
|
+
],
|
|
1796
|
+
],
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
case 'explain': {
|
|
1800
|
+
const formula = this.resolveFormula(action.formula);
|
|
1801
|
+
return {
|
|
1802
|
+
result: profile.explain(formula),
|
|
1803
|
+
primary: formula,
|
|
1804
|
+
formulas: [formula],
|
|
1805
|
+
extraBindings: [],
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
bindCapturedAction(baseName, actionName, primary, result, formulas, extraBindings) {
|
|
1811
|
+
this.defineBinding(baseName, primary);
|
|
1812
|
+
this.defineBinding(`${baseName}.formula`, primary);
|
|
1813
|
+
this.defineBinding(`${baseName}.status`, this.createStringFormula(result.status, primary.source));
|
|
1814
|
+
this.defineBinding(`${baseName}.output`, this.createStringFormula(result.output || '', primary.source));
|
|
1815
|
+
this.defineBinding(`${baseName}.ok`, {
|
|
1816
|
+
kind: 'number',
|
|
1817
|
+
value: this.isSuccessfulStatus(result.status) ? 1 : 0,
|
|
1818
|
+
source: primary.source,
|
|
1819
|
+
});
|
|
1820
|
+
this.defineBinding(`${baseName}.command`, this.createStringFormula(actionName, primary.source));
|
|
1821
|
+
this.defineBinding(`${baseName}.formulas`, this.createListFormula(formulas, primary.source));
|
|
1822
|
+
this.defineBinding(`${baseName}.diagnostics`, this.createStringListFormula(result.diagnostics.map((diag) => diag.message), primary.source));
|
|
1823
|
+
for (const [suffix, value] of extraBindings) {
|
|
1824
|
+
this.defineBinding(`${baseName}.${suffix}`, value);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
createStringFormula(value, source) {
|
|
1828
|
+
return {
|
|
1829
|
+
kind: 'atom',
|
|
1830
|
+
name: `"${value.replace(/"/g, '\\"')}"`,
|
|
1831
|
+
source,
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
createListFormula(items, source) {
|
|
1835
|
+
return { kind: 'list', args: items, source };
|
|
1836
|
+
}
|
|
1837
|
+
createAtomListFormula(items, source) {
|
|
1838
|
+
return this.createListFormula(items.map((item) => ({ kind: 'atom', name: item, source })), source);
|
|
1839
|
+
}
|
|
1840
|
+
createStringListFormula(items, source) {
|
|
1841
|
+
return this.createListFormula(items.map((item) => this.createStringFormula(item, source)), source);
|
|
1842
|
+
}
|
|
1843
|
+
isSuccessfulStatus(status) {
|
|
1844
|
+
return status === 'valid' || status === 'satisfiable' || status === 'provable';
|
|
1845
|
+
}
|
|
1846
|
+
collectValueAtoms(formula) {
|
|
1847
|
+
const seen = new Set();
|
|
1848
|
+
const walk = (value) => {
|
|
1849
|
+
if (value.kind === 'atom' && value.name && !value.name.startsWith('"')) {
|
|
1850
|
+
seen.add(value.name);
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
for (const arg of value.args || []) {
|
|
1854
|
+
walk(arg);
|
|
1855
|
+
}
|
|
1856
|
+
};
|
|
1857
|
+
walk(formula);
|
|
1858
|
+
return Array.from(seen);
|
|
1859
|
+
}
|
|
1078
1860
|
execImportDecl(stmt) {
|
|
1079
1861
|
let filePath = stmt.path;
|
|
1080
1862
|
if (!filePath.endsWith('.st'))
|
|
@@ -1152,7 +1934,7 @@ class Interpreter {
|
|
|
1152
1934
|
this.stdoutLines.push(msg);
|
|
1153
1935
|
}
|
|
1154
1936
|
getVerbosity() {
|
|
1155
|
-
const v = this.
|
|
1937
|
+
const v = this.getBinding('verbose');
|
|
1156
1938
|
if (v && v.kind === 'atom' && v.name)
|
|
1157
1939
|
return v.name.toLowerCase().replace(/(^"|"$)/g, '');
|
|
1158
1940
|
return 'off';
|
|
@@ -1197,8 +1979,10 @@ class Interpreter {
|
|
|
1197
1979
|
statusIcon(status) {
|
|
1198
1980
|
switch (status) {
|
|
1199
1981
|
case 'valid':
|
|
1982
|
+
case 'provable':
|
|
1200
1983
|
return '✓';
|
|
1201
1984
|
case 'invalid':
|
|
1985
|
+
case 'refutable':
|
|
1202
1986
|
return '✗';
|
|
1203
1987
|
case 'satisfiable':
|
|
1204
1988
|
return '◎';
|