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