@stevenvo780/st-lang 2.6.0 → 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/bin/st +0 -0
- 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/arithmetic/index.d.ts.map +1 -1
- package/dist/profiles/arithmetic/index.js.map +1 -1
- package/dist/profiles/classical/first-order.d.ts +3 -3
- package/dist/profiles/classical/first-order.d.ts.map +1 -1
- package/dist/profiles/classical/first-order.js +71 -392
- 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/intuitionistic/propositional.d.ts.map +1 -1
- package/dist/profiles/intuitionistic/propositional.js +7 -1
- package/dist/profiles/intuitionistic/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 +45 -4
- package/dist/runtime/interpreter.d.ts.map +1 -1
- package/dist/runtime/interpreter.js +1114 -609
- 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 +67 -5
- package/dist/tests/limits.test.js.map +1 -1
- package/dist/tests/profiles.test.js +14 -6
- package/dist/tests/profiles.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,12 +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");
|
|
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;
|
|
16
21
|
class Interpreter {
|
|
17
22
|
theory;
|
|
18
23
|
profile = null;
|
|
@@ -40,6 +45,14 @@ class Interpreter {
|
|
|
40
45
|
exportedTheorems = new Map();
|
|
41
46
|
exportedFunctions = new Map();
|
|
42
47
|
exportedTheories = new Map();
|
|
48
|
+
/** Profundidad de llamadas a funciones (anti-recursión infinita) */
|
|
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;
|
|
43
56
|
constructor() {
|
|
44
57
|
this.theory = this.createEmptyTheory();
|
|
45
58
|
this.textLayer = (0, compiler_1.createTextLayerState)();
|
|
@@ -47,7 +60,17 @@ class Interpreter {
|
|
|
47
60
|
}
|
|
48
61
|
/** Registra funciones nativas (Built-ins) para metaprogramación e interactividad */
|
|
49
62
|
registerBuiltins() {
|
|
50
|
-
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
|
+
];
|
|
51
74
|
for (const name of builtins) {
|
|
52
75
|
this.functions.set(name, {
|
|
53
76
|
kind: 'fn_decl',
|
|
@@ -90,11 +113,123 @@ class Interpreter {
|
|
|
90
113
|
this.exportedTheorems.clear();
|
|
91
114
|
this.exportedFunctions.clear();
|
|
92
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;
|
|
93
226
|
}
|
|
94
227
|
execute(source, file = '<stdin>') {
|
|
95
228
|
this.diagnostics = [];
|
|
96
229
|
this.results = [];
|
|
97
230
|
this.stdoutLines = [];
|
|
231
|
+
this.runtimeStepCount = 0;
|
|
232
|
+
this.runtimeCallCount = 0;
|
|
98
233
|
const parser = new parser_1.Parser(file);
|
|
99
234
|
const program = parser.parse(source);
|
|
100
235
|
this.diagnostics.push(...parser.diagnostics);
|
|
@@ -110,21 +245,7 @@ class Interpreter {
|
|
|
110
245
|
results: [],
|
|
111
246
|
};
|
|
112
247
|
}
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
this.executeStatement(stmt);
|
|
116
|
-
}
|
|
117
|
-
catch (e) {
|
|
118
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
119
|
-
this.diagnostics.push({
|
|
120
|
-
severity: 'error',
|
|
121
|
-
message: message || 'Error de runtime',
|
|
122
|
-
file,
|
|
123
|
-
line: stmt.source.line,
|
|
124
|
-
column: stmt.source.column,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
}
|
|
248
|
+
this.executeStatementsIterative(program.statements, file);
|
|
128
249
|
const hasErrors = this.diagnostics.some((d) => d.severity === 'error');
|
|
129
250
|
return {
|
|
130
251
|
stdout: this.stdoutLines.join('\n'),
|
|
@@ -145,6 +266,8 @@ class Interpreter {
|
|
|
145
266
|
this.diagnostics = [...parser.diagnostics];
|
|
146
267
|
this.results = [];
|
|
147
268
|
this.stdoutLines = [];
|
|
269
|
+
this.runtimeStepCount = 0;
|
|
270
|
+
this.runtimeCallCount = 0;
|
|
148
271
|
if (parser.diagnostics.some((d) => d.severity === 'error')) {
|
|
149
272
|
return {
|
|
150
273
|
stdout: '',
|
|
@@ -154,18 +277,7 @@ class Interpreter {
|
|
|
154
277
|
results: [],
|
|
155
278
|
};
|
|
156
279
|
}
|
|
157
|
-
|
|
158
|
-
try {
|
|
159
|
-
this.executeStatement(stmt);
|
|
160
|
-
}
|
|
161
|
-
catch (e) {
|
|
162
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
163
|
-
this.diagnostics.push({
|
|
164
|
-
severity: 'error',
|
|
165
|
-
message: message || 'Error de runtime',
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
}
|
|
280
|
+
this.executeStatementsIterative(program.statements, '<repl>');
|
|
169
281
|
return {
|
|
170
282
|
stdout: this.stdoutLines.join('\n'),
|
|
171
283
|
stderr: this.diagnostics
|
|
@@ -261,9 +373,268 @@ class Interpreter {
|
|
|
261
373
|
return this.execExportDecl(stmt);
|
|
262
374
|
}
|
|
263
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
|
+
}
|
|
264
635
|
execExportDecl(stmt) {
|
|
265
636
|
// Ejecutar la declaración interna
|
|
266
|
-
this.
|
|
637
|
+
this.executeStatementsIterative([stmt.statement], stmt.source.file);
|
|
267
638
|
// Registrarla como exportada
|
|
268
639
|
const s = stmt.statement;
|
|
269
640
|
switch (s.kind) {
|
|
@@ -299,32 +670,56 @@ class Interpreter {
|
|
|
299
670
|
* por sus fórmulas definidas. Detecta ciclos para evitar recursión infinita.
|
|
300
671
|
* Soporta notación con punto: Theory.member resuelve desde el scope de la teoría.
|
|
301
672
|
*/
|
|
673
|
+
invalidateResolveCache() {
|
|
674
|
+
this.resolveCache = new WeakMap();
|
|
675
|
+
this.resolveCacheGeneration++;
|
|
676
|
+
}
|
|
302
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;
|
|
682
|
+
const resolved = this.resolveFormulaRecursive(f, visited);
|
|
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;
|
|
690
|
+
}
|
|
691
|
+
resolveFormulaRecursive(f, visited) {
|
|
303
692
|
if (!f)
|
|
304
693
|
return f;
|
|
305
694
|
// Si es un átomo, intentar resolver
|
|
306
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
|
+
}
|
|
307
704
|
// Dot notation: Theory.member o instance.member
|
|
308
705
|
if (f.name.includes('.')) {
|
|
309
706
|
const [prefix, memberName] = f.name.split('.', 2);
|
|
310
707
|
// 1. Intentar resolver el prefijo como una variable local (instancia)
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const resolvedPrefix = this.letBindings.get(prefix);
|
|
708
|
+
if (this.hasBinding(prefix)) {
|
|
709
|
+
const resolvedPrefix = this.getBinding(prefix);
|
|
314
710
|
if (resolvedPrefix.kind === 'atom' && resolvedPrefix.name) {
|
|
315
|
-
// Si el prefijo se resuelve a un nombre de teoría/instancia
|
|
316
711
|
const actualInstanceName = resolvedPrefix.name;
|
|
317
712
|
const scope = this.theories.get(actualInstanceName);
|
|
318
713
|
if (scope) {
|
|
319
714
|
if (scope.privateMembers.has(memberName) &&
|
|
320
715
|
this.currentTheoryName !== actualInstanceName)
|
|
321
716
|
return f;
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
+
}
|
|
328
723
|
}
|
|
329
724
|
}
|
|
330
725
|
}
|
|
@@ -333,35 +728,31 @@ class Interpreter {
|
|
|
333
728
|
if (scope) {
|
|
334
729
|
if (scope.privateMembers.has(memberName) && this.currentTheoryName !== prefix)
|
|
335
730
|
return f;
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
+
}
|
|
342
737
|
}
|
|
343
738
|
return f;
|
|
344
739
|
}
|
|
345
|
-
// Binding local normal
|
|
346
|
-
if (this.letBindings.has(f.name)) {
|
|
347
|
-
if (visited.has(f.name)) {
|
|
348
|
-
return f;
|
|
349
|
-
}
|
|
350
|
-
visited.add(f.name);
|
|
351
|
-
return this.resolveFormula(this.letBindings.get(f.name), new Set(visited));
|
|
352
|
-
}
|
|
353
740
|
// También resolver axiomas/teoremas del theory actual por nombre
|
|
354
741
|
if (this.theory.axioms.has(f.name)) {
|
|
355
742
|
if (visited.has(f.name))
|
|
356
743
|
return f;
|
|
357
744
|
visited.add(f.name);
|
|
358
|
-
|
|
745
|
+
const result = this.resolveFormulaRecursive(this.theory.axioms.get(f.name), visited);
|
|
746
|
+
visited.delete(f.name);
|
|
747
|
+
return result;
|
|
359
748
|
}
|
|
360
749
|
if (this.theory.theorems.has(f.name)) {
|
|
361
750
|
if (visited.has(f.name))
|
|
362
751
|
return f;
|
|
363
752
|
visited.add(f.name);
|
|
364
|
-
|
|
753
|
+
const result = this.resolveFormulaRecursive(this.theory.theorems.get(f.name), visited);
|
|
754
|
+
visited.delete(f.name);
|
|
755
|
+
return result;
|
|
365
756
|
}
|
|
366
757
|
}
|
|
367
758
|
// Llamada a función como expresión
|
|
@@ -371,12 +762,16 @@ class Interpreter {
|
|
|
371
762
|
}
|
|
372
763
|
// Recorrer hijos recursivamente
|
|
373
764
|
if (f.args && f.args.length > 0) {
|
|
374
|
-
const newArgs = f.args.map((a) => (a ? this.
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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
|
+
}
|
|
379
773
|
}
|
|
774
|
+
return changed ? { ...f, args: newArgs } : f;
|
|
380
775
|
}
|
|
381
776
|
return f;
|
|
382
777
|
}
|
|
@@ -453,10 +848,60 @@ class Interpreter {
|
|
|
453
848
|
}
|
|
454
849
|
execTruthTableCmd(stmt) {
|
|
455
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
|
|
456
902
|
if (!profile.truthTable) {
|
|
457
903
|
throw new Error('Este perfil no soporta truth_table');
|
|
458
904
|
}
|
|
459
|
-
const formula = this.resolveFormula(stmt.formula);
|
|
460
905
|
const tt = profile.truthTable(formula);
|
|
461
906
|
const result = {
|
|
462
907
|
status: tt.isTautology ? 'valid' : tt.isSatisfiable ? 'satisfiable' : 'unsatisfiable',
|
|
@@ -482,24 +927,37 @@ class Interpreter {
|
|
|
482
927
|
this.emit(`Formalizacion ${stmt.name}: ${stmt.passageName} -> ${(0, propositional_1.formulaToString)(formula)}`);
|
|
483
928
|
}
|
|
484
929
|
else if (stmt.letType === 'description') {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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
|
+
}
|
|
488
945
|
}
|
|
489
946
|
else if (stmt.letType === 'formula' && stmt.formula) {
|
|
490
|
-
//
|
|
491
|
-
const resolved = this.
|
|
492
|
-
|
|
493
|
-
this.
|
|
494
|
-
|
|
495
|
-
this.theory.axioms.set(stmt.name, resolved);
|
|
496
|
-
// Si tiene descripción textual (let X = "desc" : formula), guardarla
|
|
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);
|
|
497
952
|
if ('description' in stmt && stmt.description) {
|
|
498
|
-
this.
|
|
499
|
-
|
|
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)}`);
|
|
500
957
|
}
|
|
501
958
|
else {
|
|
502
|
-
|
|
959
|
+
if (this.shouldEmitLocalBindings())
|
|
960
|
+
this.emit(`Let ${stmt.name} = ${(0, format_1.formulaToUnicode)(resolved)}`);
|
|
503
961
|
}
|
|
504
962
|
}
|
|
505
963
|
}
|
|
@@ -508,7 +966,6 @@ class Interpreter {
|
|
|
508
966
|
const formalization = stmt.formalization;
|
|
509
967
|
const diags = (0, compiler_1.registerClaim)(this.textLayer, stmt.name, formula, formalization);
|
|
510
968
|
this.diagnostics.push(...diags);
|
|
511
|
-
// También agregar al theory.claims
|
|
512
969
|
const claim = {
|
|
513
970
|
name: stmt.name,
|
|
514
971
|
formula: formula,
|
|
@@ -533,7 +990,6 @@ class Interpreter {
|
|
|
533
990
|
this.emit(`Context: ${stmt.claimName} = "${stmt.text}"`);
|
|
534
991
|
}
|
|
535
992
|
execRenderCmd(stmt) {
|
|
536
|
-
// Compilar claims y renderizar
|
|
537
993
|
const diags = (0, compiler_1.compileClaimsToTheory)(this.textLayer, this.theory);
|
|
538
994
|
this.diagnostics.push(...diags);
|
|
539
995
|
if (stmt.target === 'claims' || stmt.target === 'all') {
|
|
@@ -541,19 +997,15 @@ class Interpreter {
|
|
|
541
997
|
for (const [name, claim] of this.theory.claims) {
|
|
542
998
|
const fStr = claim.formula ? (0, format_1.formulaToUnicode)(claim.formula) : '(sin fórmula)';
|
|
543
999
|
this.emit(` Claim "${name}": ${fStr}`);
|
|
544
|
-
if (claim.support)
|
|
1000
|
+
if (claim.support)
|
|
545
1001
|
this.emit(` Soporte: ${claim.support}`);
|
|
546
|
-
|
|
547
|
-
if (claim.confidence !== undefined) {
|
|
1002
|
+
if (claim.confidence !== undefined)
|
|
548
1003
|
this.emit(` Confianza: ${claim.confidence}`);
|
|
549
|
-
|
|
550
|
-
if (claim.context) {
|
|
1004
|
+
if (claim.context)
|
|
551
1005
|
this.emit(` Contexto: ${claim.context}`);
|
|
552
|
-
}
|
|
553
1006
|
}
|
|
554
|
-
if (this.theory.claims.size === 0)
|
|
1007
|
+
if (this.theory.claims.size === 0)
|
|
555
1008
|
this.emit(' (sin claims registrados)');
|
|
556
|
-
}
|
|
557
1009
|
}
|
|
558
1010
|
else if (stmt.target === 'theory') {
|
|
559
1011
|
this.emit(`── Render: theory (${stmt.format}) ──`);
|
|
@@ -569,7 +1021,6 @@ class Interpreter {
|
|
|
569
1021
|
this.emit(` Claims: ${this.theory.claims.size}`);
|
|
570
1022
|
}
|
|
571
1023
|
else {
|
|
572
|
-
// Render un claim o axioma específico por nombre
|
|
573
1024
|
const axiom = this.theory.axioms.get(stmt.target);
|
|
574
1025
|
if (axiom) {
|
|
575
1026
|
this.emit(` ${stmt.target} = ${(0, format_1.formulaToUnicode)(axiom)}`);
|
|
@@ -592,7 +1043,6 @@ class Interpreter {
|
|
|
592
1043
|
const pStr = premises.map((p) => (0, format_1.formulaToUnicode)(p)).join(', ');
|
|
593
1044
|
const cStr = (0, format_1.formulaToUnicode)(conclusion);
|
|
594
1045
|
if (fallacies.length === 0) {
|
|
595
|
-
// Check if the inference is valid
|
|
596
1046
|
const conj = premises.length === 0
|
|
597
1047
|
? conclusion
|
|
598
1048
|
: premises.length === 1
|
|
@@ -608,13 +1058,12 @@ class Interpreter {
|
|
|
608
1058
|
this.emit(`⚠ [analyze] {${pStr}} → ${cStr}`);
|
|
609
1059
|
this.emit(' Inferencia NO VÁLIDA — pero no corresponde a un patrón de falacia conocido');
|
|
610
1060
|
}
|
|
611
|
-
|
|
1061
|
+
this.results.push({
|
|
612
1062
|
status: result.status,
|
|
613
1063
|
output: result.output,
|
|
614
1064
|
diagnostics: [],
|
|
615
1065
|
formula: conclusion,
|
|
616
|
-
};
|
|
617
|
-
this.results.push(result2);
|
|
1066
|
+
});
|
|
618
1067
|
}
|
|
619
1068
|
else {
|
|
620
1069
|
this.emit(`⚠ [analyze] {${pStr}} → ${cStr}`);
|
|
@@ -624,7 +1073,7 @@ class Interpreter {
|
|
|
624
1073
|
if (f.pattern)
|
|
625
1074
|
this.emit(` Patrón: ${f.pattern}`);
|
|
626
1075
|
}
|
|
627
|
-
|
|
1076
|
+
this.results.push({
|
|
628
1077
|
status: 'invalid',
|
|
629
1078
|
output: `Falacias detectadas: ${fallacies.map((f) => f.name).join(', ')}`,
|
|
630
1079
|
diagnostics: fallacies.map((f) => ({
|
|
@@ -632,17 +1081,14 @@ class Interpreter {
|
|
|
632
1081
|
message: `Falacia: ${f.name} — ${f.description}`,
|
|
633
1082
|
})),
|
|
634
1083
|
formula: conclusion,
|
|
635
|
-
};
|
|
636
|
-
this.results.push(result);
|
|
1084
|
+
});
|
|
637
1085
|
}
|
|
638
1086
|
}
|
|
639
1087
|
execProofBlock(stmt) {
|
|
640
1088
|
const profile = this.requireProfile();
|
|
641
|
-
// Guardar axiomas, letBindings y descriptions antes del bloque
|
|
642
1089
|
const savedAxioms = new Map(this.theory.axioms);
|
|
643
1090
|
const savedLetBindings = new Map(this.letBindings);
|
|
644
1091
|
const savedLetDescriptions = new Map(this.letDescriptions);
|
|
645
|
-
// Registrar las asunciones como axiomas temporales (con resolución de variables)
|
|
646
1092
|
this.emit('── Proof Block ──');
|
|
647
1093
|
for (const assumption of stmt.assumptions) {
|
|
648
1094
|
const resolved = this.resolveFormula(assumption.formula);
|
|
@@ -651,64 +1097,38 @@ class Interpreter {
|
|
|
651
1097
|
}
|
|
652
1098
|
const resolvedGoal = this.resolveFormula(stmt.goal);
|
|
653
1099
|
this.emit(` show ${(0, format_1.formulaToUnicode)(resolvedGoal)}`);
|
|
654
|
-
|
|
655
|
-
for (const bodyStmt of stmt.body) {
|
|
656
|
-
try {
|
|
657
|
-
this.executeStatement(bodyStmt);
|
|
658
|
-
}
|
|
659
|
-
catch (e) {
|
|
660
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
661
|
-
this.diagnostics.push({
|
|
662
|
-
severity: 'error',
|
|
663
|
-
message,
|
|
664
|
-
file: stmt.source.file,
|
|
665
|
-
line: bodyStmt.source.line,
|
|
666
|
-
column: bodyStmt.source.column,
|
|
667
|
-
});
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
// Verificar que el goal es derivable de las asunciones
|
|
1100
|
+
this.executeStatementsIterative(stmt.body, stmt.source.file);
|
|
671
1101
|
const premiseNames = stmt.assumptions.map((a) => a.name);
|
|
672
1102
|
const result = profile.derive(resolvedGoal, premiseNames, this.theory);
|
|
673
1103
|
this.results.push(result);
|
|
674
1104
|
if (result.status === 'valid' || result.status === 'provable') {
|
|
675
1105
|
this.emit(` ✓ QED — ${(0, format_1.formulaToUnicode)(resolvedGoal)} demostrado`);
|
|
676
|
-
// Registrar como teorema
|
|
677
1106
|
const theoremName = `proof_${this.theory.theorems.size + 1}`;
|
|
678
|
-
// La implicación assumptions -> goal es un teorema
|
|
679
1107
|
let implication = resolvedGoal;
|
|
680
1108
|
for (let i = stmt.assumptions.length - 1; i >= 0; i--) {
|
|
681
|
-
implication = {
|
|
682
|
-
kind: 'implies',
|
|
683
|
-
args: [stmt.assumptions[i].formula, implication],
|
|
684
|
-
};
|
|
1109
|
+
implication = { kind: 'implies', args: [stmt.assumptions[i].formula, implication] };
|
|
685
1110
|
}
|
|
686
1111
|
this.theory.theorems.set(theoremName, implication);
|
|
687
1112
|
}
|
|
688
1113
|
else {
|
|
689
1114
|
this.emit(` ✗ QED fallido — no se pudo demostrar ${(0, format_1.formulaToUnicode)(resolvedGoal)}`);
|
|
690
1115
|
}
|
|
691
|
-
// Restaurar axiomas, letBindings y descriptions (quitar las asunciones temporales)
|
|
692
1116
|
this.theory.axioms = savedAxioms;
|
|
693
1117
|
this.letBindings = savedLetBindings;
|
|
694
1118
|
this.letDescriptions = savedLetDescriptions;
|
|
1119
|
+
this.invalidateResolveCache();
|
|
695
1120
|
this.emit('── End Proof Block ──');
|
|
696
1121
|
}
|
|
697
1122
|
execTheoryDecl(stmt) {
|
|
698
|
-
const theoryName = stmt.name;
|
|
699
|
-
// Si tiene parámetros, es una plantilla (Clase)
|
|
700
1123
|
if (stmt.params && stmt.params.length > 0) {
|
|
701
|
-
this.theoryTemplates.set(
|
|
702
|
-
this.emit(`Teoría (plantilla) ${
|
|
1124
|
+
this.theoryTemplates.set(stmt.name, { node: stmt, parent: stmt.parent });
|
|
1125
|
+
this.emit(`Teoría (plantilla) ${stmt.name}(${stmt.params.join(', ')}) declarada`);
|
|
703
1126
|
return;
|
|
704
1127
|
}
|
|
705
|
-
// Si no tiene parámetros, ejecutar como singleton/objeto inmediato
|
|
706
1128
|
this.instantiateTheory(stmt);
|
|
707
1129
|
}
|
|
708
|
-
/** Crea una instancia de una teoría y la registra en this.theories */
|
|
709
1130
|
instantiateTheory(node, instanceName, args = []) {
|
|
710
1131
|
const theoryName = instanceName || node.name;
|
|
711
|
-
// Crear scope vacío
|
|
712
1132
|
const scope = {
|
|
713
1133
|
name: theoryName,
|
|
714
1134
|
parent: node.parent,
|
|
@@ -718,14 +1138,10 @@ class Interpreter {
|
|
|
718
1138
|
theorems: new Map(),
|
|
719
1139
|
privateMembers: new Set(),
|
|
720
1140
|
};
|
|
721
|
-
// HERENCIA: Si extends Parent, copiar bindings/axiomas/teoremas del padre
|
|
722
|
-
// El padre puede ser otra plantilla o un singleton ya instanciado
|
|
723
1141
|
if (node.parent) {
|
|
724
1142
|
const parentScope = this.theories.get(node.parent);
|
|
725
|
-
if (!parentScope)
|
|
726
|
-
throw new Error(`Teoría padre '${node.parent}' no encontrada
|
|
727
|
-
}
|
|
728
|
-
// Copiar todo del padre (no los miembros privados del padre al hijo)
|
|
1143
|
+
if (!parentScope)
|
|
1144
|
+
throw new Error(`Teoría padre '${node.parent}' no encontrada.`);
|
|
729
1145
|
for (const [k, v] of parentScope.letBindings)
|
|
730
1146
|
if (!parentScope.privateMembers.has(k))
|
|
731
1147
|
scope.letBindings.set(k, v);
|
|
@@ -739,13 +1155,11 @@ class Interpreter {
|
|
|
739
1155
|
if (!parentScope.privateMembers.has(k))
|
|
740
1156
|
scope.theorems.set(k, v);
|
|
741
1157
|
}
|
|
742
|
-
// Guardar estado global antes de entrar al scope de la teoría
|
|
743
1158
|
const savedLetBindings = new Map(this.letBindings);
|
|
744
1159
|
const savedLetDescriptions = new Map(this.letDescriptions);
|
|
745
1160
|
const savedAxioms = new Map(this.theory.axioms);
|
|
746
1161
|
const savedTheorems = new Map(this.theory.theorems);
|
|
747
1162
|
const savedTheoryName = this.currentTheoryName;
|
|
748
|
-
// Inyectar argumentos en el scope local de la teoría
|
|
749
1163
|
if (node.params && args.length > 0) {
|
|
750
1164
|
for (let i = 0; i < node.params.length; i++) {
|
|
751
1165
|
if (i < args.length) {
|
|
@@ -755,7 +1169,6 @@ class Interpreter {
|
|
|
755
1169
|
}
|
|
756
1170
|
}
|
|
757
1171
|
}
|
|
758
|
-
// Inyectar bindings heredados al scope local para que los statements internos los vean
|
|
759
1172
|
for (const [k, v] of scope.letBindings)
|
|
760
1173
|
this.letBindings.set(k, v);
|
|
761
1174
|
for (const [k, v] of scope.letDescriptions)
|
|
@@ -766,7 +1179,6 @@ class Interpreter {
|
|
|
766
1179
|
this.theory.theorems.set(k, v);
|
|
767
1180
|
this.currentTheoryName = theoryName;
|
|
768
1181
|
this.emit(`── Instanciando Theory ${theoryName} ──`);
|
|
769
|
-
// Ejecutar los miembros del body
|
|
770
1182
|
for (const member of node.members) {
|
|
771
1183
|
const memberStmt = member.statement;
|
|
772
1184
|
const memberName = 'name' in memberStmt ? memberStmt.name : null;
|
|
@@ -786,7 +1198,6 @@ class Interpreter {
|
|
|
786
1198
|
});
|
|
787
1199
|
}
|
|
788
1200
|
}
|
|
789
|
-
// Capturar lo producido
|
|
790
1201
|
for (const [k, v] of this.letBindings)
|
|
791
1202
|
if (!savedLetBindings.has(k))
|
|
792
1203
|
scope.letBindings.set(k, v);
|
|
@@ -799,292 +1210,383 @@ class Interpreter {
|
|
|
799
1210
|
for (const [k, v] of this.theory.theorems)
|
|
800
1211
|
if (!savedTheorems.has(k))
|
|
801
1212
|
scope.theorems.set(k, v);
|
|
802
|
-
// Restaurar estado global
|
|
803
1213
|
this.letBindings = savedLetBindings;
|
|
804
1214
|
this.letDescriptions = savedLetDescriptions;
|
|
805
1215
|
this.theory.axioms = savedAxioms;
|
|
806
1216
|
this.theory.theorems = savedTheorems;
|
|
807
1217
|
this.currentTheoryName = savedTheoryName;
|
|
808
|
-
|
|
1218
|
+
this.invalidateResolveCache();
|
|
809
1219
|
this.theories.set(theoryName, scope);
|
|
810
1220
|
this.emit(`── End Theory Instance ${theoryName} ──`);
|
|
811
1221
|
return theoryName;
|
|
812
1222
|
}
|
|
813
|
-
// =============================================
|
|
814
|
-
// Control flow & funciones (v1.5.8)
|
|
815
|
-
// =============================================
|
|
816
1223
|
execPrintCmd(stmt) {
|
|
817
|
-
if (stmt.value !== null)
|
|
818
|
-
// String literal
|
|
1224
|
+
if (stmt.value !== null)
|
|
819
1225
|
this.emit(stmt.value);
|
|
820
|
-
}
|
|
821
1226
|
else if (stmt.formula) {
|
|
822
|
-
|
|
1227
|
+
// Resolve bindings but skip constant-folding so print shows symbolic form
|
|
1228
|
+
const resolved = this.resolveFormulaRecursive(stmt.formula, new Set());
|
|
823
1229
|
this.emit((0, format_1.formulaToUnicode)(resolved));
|
|
824
1230
|
}
|
|
825
1231
|
}
|
|
826
1232
|
execSetCmd(stmt) {
|
|
827
|
-
const resolved = this.
|
|
828
|
-
this.
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
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)}`);
|
|
832
1239
|
}
|
|
833
1240
|
execIfStmt(stmt) {
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
let matched;
|
|
838
|
-
if (branch.condition === 'valid' || branch.condition === 'invalid') {
|
|
839
|
-
const result = profile.checkValid(resolved);
|
|
840
|
-
matched =
|
|
841
|
-
branch.condition === 'valid' ? result.status === 'valid' : result.status !== 'valid';
|
|
842
|
-
}
|
|
843
|
-
else {
|
|
844
|
-
// satisfiable / unsatisfiable
|
|
845
|
-
const result = profile.checkSatisfiable(resolved);
|
|
846
|
-
matched =
|
|
847
|
-
branch.condition === 'satisfiable'
|
|
848
|
-
? result.status === 'satisfiable' || result.status === 'valid'
|
|
849
|
-
: result.status === 'unsatisfiable';
|
|
850
|
-
}
|
|
851
|
-
if (matched) {
|
|
852
|
-
for (const bodyStmt of branch.body) {
|
|
853
|
-
if (this.returnSignal)
|
|
854
|
-
return;
|
|
855
|
-
this.executeStatement(bodyStmt);
|
|
856
|
-
}
|
|
857
|
-
return; // solo ejecuta la primera rama que matchea
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
// else branch
|
|
861
|
-
if (stmt.elseBranch) {
|
|
862
|
-
for (const bodyStmt of stmt.elseBranch) {
|
|
863
|
-
if (this.returnSignal)
|
|
864
|
-
return;
|
|
865
|
-
this.executeStatement(bodyStmt);
|
|
866
|
-
}
|
|
867
|
-
}
|
|
1241
|
+
const selectedBody = this.selectIfBody(stmt);
|
|
1242
|
+
if (selectedBody)
|
|
1243
|
+
this.executeStatementsIterative(selectedBody, stmt.source.file);
|
|
868
1244
|
}
|
|
869
1245
|
execForStmt(stmt) {
|
|
870
|
-
|
|
871
|
-
for (const item of stmt.items) {
|
|
872
|
-
if (this.returnSignal)
|
|
873
|
-
break;
|
|
874
|
-
const resolved = this.resolveFormula(item);
|
|
875
|
-
this.letBindings.set(stmt.variable, resolved);
|
|
876
|
-
for (const bodyStmt of stmt.body) {
|
|
877
|
-
if (this.returnSignal)
|
|
878
|
-
break;
|
|
879
|
-
this.executeStatement(bodyStmt);
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
// Restaurar binding previo
|
|
883
|
-
if (savedBinding !== undefined) {
|
|
884
|
-
this.letBindings.set(stmt.variable, savedBinding);
|
|
885
|
-
}
|
|
886
|
-
else {
|
|
887
|
-
this.letBindings.delete(stmt.variable);
|
|
888
|
-
}
|
|
1246
|
+
this.executeStatementsIterative([stmt], stmt.source.file);
|
|
889
1247
|
}
|
|
890
1248
|
execWhileStmt(stmt) {
|
|
891
|
-
|
|
892
|
-
const maxIter = stmt.maxIterations || 1000;
|
|
893
|
-
let iter = 0;
|
|
894
|
-
while (iter < maxIter) {
|
|
895
|
-
if (this.returnSignal)
|
|
896
|
-
break;
|
|
897
|
-
iter++;
|
|
898
|
-
const resolved = this.resolveFormula(stmt.formula);
|
|
899
|
-
let matched;
|
|
900
|
-
if (stmt.condition === 'valid' || stmt.condition === 'invalid') {
|
|
901
|
-
const result = profile.checkValid(resolved);
|
|
902
|
-
matched =
|
|
903
|
-
stmt.condition === 'valid' ? result.status === 'valid' : result.status !== 'valid';
|
|
904
|
-
}
|
|
905
|
-
else {
|
|
906
|
-
const result = profile.checkSatisfiable(resolved);
|
|
907
|
-
matched =
|
|
908
|
-
stmt.condition === 'satisfiable'
|
|
909
|
-
? result.status === 'satisfiable' || result.status === 'valid'
|
|
910
|
-
: result.status === 'unsatisfiable';
|
|
911
|
-
}
|
|
912
|
-
if (!matched)
|
|
913
|
-
break;
|
|
914
|
-
for (const bodyStmt of stmt.body) {
|
|
915
|
-
if (this.returnSignal)
|
|
916
|
-
break;
|
|
917
|
-
this.executeStatement(bodyStmt);
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
if (iter >= maxIter) {
|
|
921
|
-
this.diagnostics.push({
|
|
922
|
-
severity: 'warning',
|
|
923
|
-
message: `while: se alcanzó el límite de ${maxIter} iteraciones`,
|
|
924
|
-
file: stmt.source.file,
|
|
925
|
-
line: stmt.source.line,
|
|
926
|
-
column: stmt.source.column,
|
|
927
|
-
});
|
|
928
|
-
}
|
|
1249
|
+
this.executeStatementsIterative([stmt], stmt.source.file);
|
|
929
1250
|
}
|
|
930
1251
|
execFnDecl(stmt) {
|
|
931
1252
|
const name = this.currentTheoryName ? `${this.currentTheoryName}.${stmt.name}` : stmt.name;
|
|
932
1253
|
this.functions.set(name, stmt);
|
|
933
|
-
if (this.currentTheoryName)
|
|
1254
|
+
if (this.currentTheoryName)
|
|
934
1255
|
this.emit(`Función de instancia ${name}(${stmt.params.join(', ')}) declarada`);
|
|
935
|
-
|
|
936
|
-
else {
|
|
1256
|
+
else
|
|
937
1257
|
this.emit(`Función ${name}(${stmt.params.join(', ')}) declarada`);
|
|
938
|
-
}
|
|
939
1258
|
}
|
|
940
1259
|
execReturnStmt(stmt) {
|
|
941
|
-
if (stmt.formula)
|
|
942
|
-
this.returnValue = this.
|
|
943
|
-
|
|
944
|
-
else {
|
|
1260
|
+
if (stmt.formula)
|
|
1261
|
+
this.returnValue = this.evaluateFormulaValue(stmt.formula);
|
|
1262
|
+
else
|
|
945
1263
|
this.returnValue = undefined;
|
|
946
|
-
}
|
|
947
1264
|
this.returnSignal = true;
|
|
948
1265
|
}
|
|
949
1266
|
executeFnCall(stmt) {
|
|
950
|
-
|
|
951
|
-
if (
|
|
952
|
-
return
|
|
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) {
|
|
1321
|
+
this.callDepth++;
|
|
1322
|
+
if (this.callDepth > MAX_CALL_DEPTH) {
|
|
1323
|
+
this.callDepth--;
|
|
1324
|
+
throw new Error(`Límite de recursión excedido (${MAX_CALL_DEPTH}).`);
|
|
1325
|
+
}
|
|
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' } };
|
|
953
1345
|
}
|
|
954
|
-
// 0.5 Si es un método (obj.metodo), resolver el objeto primero
|
|
955
1346
|
if (stmt.name.includes('.')) {
|
|
956
1347
|
const [prefix, methodName] = stmt.name.split('.', 2);
|
|
957
1348
|
let actualInstanceName = prefix;
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
if (resolved.kind === 'atom' && resolved.name) {
|
|
1349
|
+
if (this.hasBinding(prefix)) {
|
|
1350
|
+
const resolved = this.getBinding(prefix);
|
|
1351
|
+
if (resolved.kind === 'atom' && resolved.name)
|
|
962
1352
|
actualInstanceName = resolved.name;
|
|
963
|
-
}
|
|
964
1353
|
}
|
|
965
1354
|
const scope = this.theories.get(actualInstanceName);
|
|
966
1355
|
if (scope) {
|
|
967
|
-
// En ST actual, las funciones no se guardan en TheoryScope sino que se ejecutan
|
|
968
|
-
// en el contexto del intérprete. Para soportar métodos de instancia,
|
|
969
|
-
// necesitamos que las funciones estén vinculadas al scope o prefijadas.
|
|
970
|
-
// Como solución rápida y potente: buscamos la función prefijada en el mapa global.
|
|
971
1356
|
const internalFnName = `${actualInstanceName}.${methodName}`;
|
|
972
1357
|
const fn = this.functions.get(internalFnName);
|
|
973
|
-
if (fn)
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1358
|
+
if (fn)
|
|
1359
|
+
return {
|
|
1360
|
+
frame: this.createFunctionFrame(internalFnName, fn, stmt.args, continuation, scope, inheritedReturnState),
|
|
1361
|
+
};
|
|
977
1362
|
}
|
|
978
1363
|
}
|
|
979
|
-
// 1. Intentar instanciación de teoría (Clase)
|
|
980
1364
|
const template = this.theoryTemplates.get(stmt.name);
|
|
981
1365
|
if (template) {
|
|
982
|
-
// Nombre de instancia único si no estamos en un let (o usar un contador)
|
|
983
1366
|
const instanceId = `inst_${stmt.name}_${this.theories.size}`;
|
|
984
1367
|
this.instantiateTheory(template.node, instanceId, stmt.args);
|
|
985
|
-
|
|
1368
|
+
this.callDepth--;
|
|
1369
|
+
return {
|
|
1370
|
+
result: { kind: 'atom', name: instanceId },
|
|
1371
|
+
resultContinuation: continuation || { type: 'discard' },
|
|
1372
|
+
};
|
|
986
1373
|
}
|
|
987
|
-
// 2. Intentar llamada a función normal
|
|
988
1374
|
const fn = this.functions.get(stmt.name);
|
|
989
1375
|
if (!fn) {
|
|
1376
|
+
this.callDepth--;
|
|
990
1377
|
throw new Error(`Función o Teoría '${stmt.name}' no declarada`);
|
|
991
1378
|
}
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
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;
|
|
1001
1406
|
for (let i = 0; i < fn.params.length; i++) {
|
|
1002
|
-
|
|
1003
|
-
this.letBindings.set(fn.params[i], resolved);
|
|
1407
|
+
this.currentBindingFrame.bindings.set(fn.params[i], this.evaluateFormulaValue(args[i]));
|
|
1004
1408
|
}
|
|
1005
|
-
|
|
1006
|
-
const
|
|
1007
|
-
const savedReturnValue = this.returnValue;
|
|
1409
|
+
const savedReturnSignal = inheritedReturnState?.signal ?? this.returnSignal;
|
|
1410
|
+
const savedReturnValue = inheritedReturnState?.value ?? this.returnValue;
|
|
1008
1411
|
this.returnSignal = false;
|
|
1009
1412
|
this.returnValue = undefined;
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
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) {
|
|
1016
1424
|
const result = this.returnValue;
|
|
1017
|
-
this.returnSignal = savedReturnSignal;
|
|
1018
|
-
this.returnValue = savedReturnValue;
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
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;
|
|
1462
|
+
}
|
|
1463
|
+
return frame.statements[frame.index++];
|
|
1024
1464
|
}
|
|
1025
|
-
|
|
1026
|
-
this.
|
|
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;
|
|
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;
|
|
1027
1476
|
}
|
|
1477
|
+
if (this.returnSignal) {
|
|
1478
|
+
runtimeStack.pop();
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
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;
|
|
1493
|
+
}
|
|
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;
|
|
1499
|
+
}
|
|
1500
|
+
frame.iterations++;
|
|
1501
|
+
runtimeStack.push({ kind: 'statements', statements: frame.stmt.body, index: 0 });
|
|
1028
1502
|
}
|
|
1029
|
-
return
|
|
1503
|
+
return undefined;
|
|
1030
1504
|
}
|
|
1031
|
-
/** Ejecuta una función inyectando bindings de un scope (para métodos de instancia) */
|
|
1032
1505
|
executeFunctionInScope(fn, args, scope) {
|
|
1033
|
-
|
|
1034
|
-
|
|
1506
|
+
this.callDepth++;
|
|
1507
|
+
if (this.callDepth > MAX_CALL_DEPTH) {
|
|
1508
|
+
this.callDepth--;
|
|
1509
|
+
throw new Error(`Límite de recursión excedido.`);
|
|
1035
1510
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
this.
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1511
|
+
try {
|
|
1512
|
+
const savedBindings = new Map(this.letBindings);
|
|
1513
|
+
const savedAxioms = new Map(this.theory.axioms);
|
|
1514
|
+
const savedTheorems = new Map(this.theory.theorems);
|
|
1515
|
+
const savedTheoryName = this.currentTheoryName;
|
|
1516
|
+
for (const [k, v] of scope.letBindings)
|
|
1517
|
+
this.letBindings.set(k, v);
|
|
1518
|
+
for (const [k, v] of scope.axioms)
|
|
1519
|
+
this.theory.axioms.set(k, v);
|
|
1520
|
+
for (const [k, v] of scope.theorems)
|
|
1521
|
+
this.theory.theorems.set(k, v);
|
|
1522
|
+
this.currentTheoryName = scope.name;
|
|
1523
|
+
for (let i = 0; i < fn.params.length; i++)
|
|
1524
|
+
this.letBindings.set(fn.params[i], this.resolveFormula(args[i]));
|
|
1525
|
+
const savedReturnSignal = this.returnSignal;
|
|
1526
|
+
const savedReturnValue = this.returnValue;
|
|
1527
|
+
this.returnSignal = false;
|
|
1528
|
+
this.returnValue = undefined;
|
|
1529
|
+
for (const bodyStmt of fn.body) {
|
|
1530
|
+
if (this.returnSignal)
|
|
1531
|
+
break;
|
|
1532
|
+
this.executeStatement(bodyStmt);
|
|
1533
|
+
}
|
|
1534
|
+
const result = this.returnValue;
|
|
1535
|
+
this.returnSignal = savedReturnSignal;
|
|
1536
|
+
this.returnValue = savedReturnValue;
|
|
1537
|
+
this.letBindings = savedBindings;
|
|
1538
|
+
this.theory.axioms = savedAxioms;
|
|
1539
|
+
this.theory.theorems = savedTheorems;
|
|
1540
|
+
this.currentTheoryName = savedTheoryName;
|
|
1541
|
+
return result;
|
|
1542
|
+
}
|
|
1543
|
+
finally {
|
|
1544
|
+
this.callDepth--;
|
|
1052
1545
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1546
|
+
}
|
|
1547
|
+
tryConstantFold(f) {
|
|
1548
|
+
if (!f || !f.args)
|
|
1549
|
+
return f;
|
|
1550
|
+
const ARITH_OPS = {
|
|
1551
|
+
add: (a, b) => a + b,
|
|
1552
|
+
subtract: (a, b) => a - b,
|
|
1553
|
+
multiply: (a, b) => a * b,
|
|
1554
|
+
divide: (a, b) => a / b,
|
|
1555
|
+
modulo: (a, b) => a % b,
|
|
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.
|
|
1559
|
+
const newArgs = f.args.map((a) => this.tryConstantFold(a));
|
|
1560
|
+
if (newArgs.length === 2 &&
|
|
1561
|
+
newArgs[0].kind === 'number' &&
|
|
1562
|
+
newArgs[1].kind === 'number' &&
|
|
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];
|
|
1569
|
+
const res = op(newArgs[0].value ?? 0, newArgs[1].value ?? 0);
|
|
1570
|
+
return { kind: 'number', value: res, source: f.source };
|
|
1062
1571
|
}
|
|
1063
|
-
|
|
1064
|
-
// 5. Restaurar estado global
|
|
1065
|
-
this.returnSignal = savedReturnSignal;
|
|
1066
|
-
this.returnValue = savedReturnValue;
|
|
1067
|
-
this.letBindings = savedBindings;
|
|
1068
|
-
this.theory.axioms = savedAxioms;
|
|
1069
|
-
this.theory.theorems = savedTheorems;
|
|
1070
|
-
this.currentTheoryName = savedTheoryName;
|
|
1071
|
-
return result;
|
|
1572
|
+
return { ...f, args: newArgs };
|
|
1072
1573
|
}
|
|
1073
1574
|
executeBuiltin(name, args) {
|
|
1074
|
-
|
|
1075
|
-
throw new Error(`Built-in '${name}' espera exactamente 1 argumento, recibió ${args.length}`);
|
|
1076
|
-
}
|
|
1077
|
-
const arg = this.resolveFormula(args[0]);
|
|
1575
|
+
const arg = args[0] ? this.resolveFormula(args[0]) : undefined;
|
|
1078
1576
|
if (name === 'typeof') {
|
|
1079
1577
|
let typeStr = 'Formula';
|
|
1080
|
-
if (arg
|
|
1578
|
+
if (arg?.kind === 'number')
|
|
1081
1579
|
typeStr = 'Number';
|
|
1082
|
-
if (arg
|
|
1580
|
+
if (arg?.kind === 'list')
|
|
1581
|
+
typeStr = 'List';
|
|
1582
|
+
if (arg?.kind === 'atom' && arg.name?.startsWith('"'))
|
|
1083
1583
|
typeStr = 'String';
|
|
1084
|
-
return { kind: 'atom', name: `"${typeStr}"`, source: arg
|
|
1584
|
+
return { kind: 'atom', name: `"${typeStr}"`, source: arg?.source };
|
|
1085
1585
|
}
|
|
1086
1586
|
if (name === 'is_valid' || name === 'is_satisfiable') {
|
|
1087
1587
|
const profile = this.requireProfile();
|
|
1588
|
+
if (!arg)
|
|
1589
|
+
return { kind: 'atom', name: '"Error"' };
|
|
1088
1590
|
try {
|
|
1089
1591
|
const result = name === 'is_valid' ? profile.checkValid(arg) : profile.checkSatisfiable(arg);
|
|
1090
1592
|
const isTrue = result.status === 'valid' || result.status === 'satisfiable';
|
|
@@ -1095,22 +1597,67 @@ class Interpreter {
|
|
|
1095
1597
|
}
|
|
1096
1598
|
}
|
|
1097
1599
|
if (name === 'get_atoms') {
|
|
1600
|
+
if (!arg)
|
|
1601
|
+
return { kind: 'atom', name: '"{ }"' };
|
|
1098
1602
|
const atoms = this.collectAtoms(arg);
|
|
1099
1603
|
return { kind: 'atom', name: `"{ ${atoms.join(', ')} }"`, source: arg.source };
|
|
1100
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
|
+
}
|
|
1101
1646
|
if (name === 'input') {
|
|
1647
|
+
if (!arg)
|
|
1648
|
+
return { kind: 'atom', name: '""' };
|
|
1102
1649
|
const prompt = arg.kind === 'atom' && arg.name?.startsWith('"')
|
|
1103
1650
|
? arg.name.replace(/(^"|"$)/g, '')
|
|
1104
1651
|
: (0, propositional_1.formulaToString)(arg);
|
|
1105
1652
|
let inputStr;
|
|
1106
1653
|
try {
|
|
1107
1654
|
process.stdout.write(prompt + ' ');
|
|
1108
|
-
|
|
1655
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
|
|
1109
1656
|
const fs = require('fs');
|
|
1110
1657
|
const buf = Buffer.alloc(256);
|
|
1658
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
|
1111
1659
|
const bytesRead = fs.readSync(process.stdin.fd, buf, 0, 256, null);
|
|
1112
1660
|
inputStr = buf.toString('utf8', 0, bytesRead).trim();
|
|
1113
|
-
/* eslint-enable @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-argument */
|
|
1114
1661
|
}
|
|
1115
1662
|
catch {
|
|
1116
1663
|
inputStr = 'interactive_not_supported';
|
|
@@ -1119,70 +1666,238 @@ class Interpreter {
|
|
|
1119
1666
|
}
|
|
1120
1667
|
return undefined;
|
|
1121
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
|
+
}
|
|
1122
1860
|
execImportDecl(stmt) {
|
|
1123
1861
|
let filePath = stmt.path;
|
|
1124
|
-
// Agregar extensión .st si no la tiene
|
|
1125
1862
|
if (!filePath.endsWith('.st'))
|
|
1126
1863
|
filePath += '.st';
|
|
1127
|
-
|
|
1128
|
-
if (this.importedFiles.has(filePath)) {
|
|
1129
|
-
this.emit(`Import: ${filePath} (ya importado, saltar)`);
|
|
1864
|
+
if (this.importedFiles.has(filePath))
|
|
1130
1865
|
return;
|
|
1131
|
-
}
|
|
1132
1866
|
this.importedFiles.add(filePath);
|
|
1133
|
-
// Intentar leer el archivo (solo funciona en Node.js / CLI)
|
|
1134
1867
|
let source;
|
|
1135
1868
|
try {
|
|
1136
|
-
|
|
1869
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
|
|
1137
1870
|
const fs = require('fs');
|
|
1871
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
|
|
1138
1872
|
const path = require('path');
|
|
1139
|
-
//
|
|
1873
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
|
1140
1874
|
const resolved = path.isAbsolute(filePath)
|
|
1141
1875
|
? filePath
|
|
1142
|
-
:
|
|
1876
|
+
: // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
|
1877
|
+
path.resolve(path.dirname(stmt.source.file || '.'), filePath);
|
|
1878
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
|
1143
1879
|
source = fs.readFileSync(resolved, 'utf-8');
|
|
1144
|
-
/* eslint-enable @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */
|
|
1145
1880
|
}
|
|
1146
1881
|
catch {
|
|
1147
|
-
throw new Error(`No se pudo importar '${filePath}'
|
|
1882
|
+
throw new Error(`No se pudo importar '${filePath}'`);
|
|
1148
1883
|
}
|
|
1149
1884
|
const parser = new parser_1.Parser(filePath);
|
|
1150
1885
|
const program = parser.parse(source);
|
|
1151
1886
|
this.diagnostics.push(...parser.diagnostics);
|
|
1152
|
-
if (parser.diagnostics.some((d) => d.severity === 'error')) {
|
|
1153
|
-
throw new Error(`Errores de parseo en '${filePath}'`);
|
|
1154
|
-
}
|
|
1155
|
-
// Guardar estado de exportación del importador
|
|
1156
1887
|
const prevIsImporting = this.isImporting;
|
|
1157
|
-
const prevExportedBindings = new Map(this.exportedBindings);
|
|
1158
|
-
const prevExportedAxioms = new Map(this.exportedAxioms);
|
|
1159
|
-
const prevExportedTheorems = new Map(this.exportedTheorems);
|
|
1160
|
-
const prevExportedFunctions = new Map(this.exportedFunctions);
|
|
1161
|
-
const prevExportedTheories = new Map(this.exportedTheories);
|
|
1162
|
-
// Guardar scope local actual del importador para no contaminarlo durante la carga
|
|
1163
1888
|
const prevLetBindings = new Map(this.letBindings);
|
|
1164
1889
|
const prevAxioms = new Map(this.theory.axioms);
|
|
1165
1890
|
const prevTheorems = new Map(this.theory.theorems);
|
|
1166
1891
|
const prevFunctions = new Map(this.functions);
|
|
1167
1892
|
const prevTheories = new Map(this.theories);
|
|
1168
|
-
// Limpiar para capturar solo lo que exporta el archivo importado
|
|
1169
1893
|
this.isImporting = true;
|
|
1170
|
-
this.exportedBindings.clear();
|
|
1171
|
-
this.exportedAxioms.clear();
|
|
1172
|
-
this.exportedTheorems.clear();
|
|
1173
|
-
this.exportedFunctions.clear();
|
|
1174
|
-
this.exportedTheories.clear();
|
|
1175
|
-
// No queremos que el archivo importado vea el scope del importador (encapsulamiento total)
|
|
1176
1894
|
this.letBindings.clear();
|
|
1177
1895
|
this.theory.axioms.clear();
|
|
1178
1896
|
this.theory.theorems.clear();
|
|
1179
1897
|
this.functions.clear();
|
|
1180
1898
|
this.theories.clear();
|
|
1181
|
-
|
|
1182
|
-
for (const importedStmt of program.statements) {
|
|
1899
|
+
for (const importedStmt of program.statements)
|
|
1183
1900
|
this.executeStatement(importedStmt);
|
|
1184
|
-
}
|
|
1185
|
-
// Capturar lo exportado
|
|
1186
1901
|
const newExports = {
|
|
1187
1902
|
bindings: new Map(this.exportedBindings),
|
|
1188
1903
|
axioms: new Map(this.exportedAxioms),
|
|
@@ -1190,19 +1905,12 @@ class Interpreter {
|
|
|
1190
1905
|
functions: new Map(this.exportedFunctions),
|
|
1191
1906
|
theories: new Map(this.exportedTheories),
|
|
1192
1907
|
};
|
|
1193
|
-
// Restaurar estado del importador (incluyendo su scope original)
|
|
1194
1908
|
this.isImporting = prevIsImporting;
|
|
1195
|
-
this.exportedBindings = prevExportedBindings;
|
|
1196
|
-
this.exportedAxioms = prevExportedAxioms;
|
|
1197
|
-
this.exportedTheorems = prevExportedTheorems;
|
|
1198
|
-
this.exportedFunctions = prevExportedFunctions;
|
|
1199
|
-
this.exportedTheories = prevExportedTheories;
|
|
1200
1909
|
this.letBindings = prevLetBindings;
|
|
1201
1910
|
this.theory.axioms = prevAxioms;
|
|
1202
1911
|
this.theory.theorems = prevTheorems;
|
|
1203
1912
|
this.functions = prevFunctions;
|
|
1204
1913
|
this.theories = prevTheories;
|
|
1205
|
-
// Fusionar solo lo exportado al scope actual
|
|
1206
1914
|
for (const [k, v] of newExports.bindings)
|
|
1207
1915
|
this.letBindings.set(k, v);
|
|
1208
1916
|
for (const [k, v] of newExports.axioms)
|
|
@@ -1213,7 +1921,6 @@ class Interpreter {
|
|
|
1213
1921
|
this.functions.set(k, v);
|
|
1214
1922
|
for (const [k, v] of newExports.theories)
|
|
1215
1923
|
this.theories.set(k, v);
|
|
1216
|
-
this.emit(`Import: ${filePath} cargado (${newExports.bindings.size + newExports.axioms.size + newExports.theorems.size + newExports.functions.size + newExports.theories.size} elementos importados)`);
|
|
1217
1924
|
}
|
|
1218
1925
|
execExplainCmd(stmt) {
|
|
1219
1926
|
const profile = this.requireProfile();
|
|
@@ -1223,160 +1930,39 @@ class Interpreter {
|
|
|
1223
1930
|
if (result.output)
|
|
1224
1931
|
this.emit(result.output);
|
|
1225
1932
|
}
|
|
1226
|
-
// --- Output helpers ---
|
|
1227
1933
|
emit(msg) {
|
|
1228
1934
|
this.stdoutLines.push(msg);
|
|
1229
1935
|
}
|
|
1230
1936
|
getVerbosity() {
|
|
1231
|
-
const v = this.
|
|
1232
|
-
if (v && v.kind === 'atom' && v.name)
|
|
1233
|
-
|
|
1234
|
-
// Remove quotes if present
|
|
1235
|
-
return n.replace(/(^"|"$)/g, '');
|
|
1236
|
-
}
|
|
1937
|
+
const v = this.getBinding('verbose');
|
|
1938
|
+
if (v && v.kind === 'atom' && v.name)
|
|
1939
|
+
return v.name.toLowerCase().replace(/(^"|"$)/g, '');
|
|
1237
1940
|
return 'off';
|
|
1238
1941
|
}
|
|
1239
1942
|
emitResult(cmd, result) {
|
|
1240
|
-
|
|
1241
|
-
this.emit(`${statusIcon} [${cmd}] ${result.output || result.status}`);
|
|
1943
|
+
this.emit(`${this.statusIcon(result.status)} [${cmd}] ${result.output || result.status}`);
|
|
1242
1944
|
const verbosity = this.getVerbosity();
|
|
1243
|
-
if (result.
|
|
1244
|
-
this.emit(` Nota pedagógica: ${result.educationalNote}`);
|
|
1245
|
-
}
|
|
1246
|
-
if (result.paradoxWarning) {
|
|
1945
|
+
if (result.paradoxWarning)
|
|
1247
1946
|
this.emit(` ⚠ PARADOJA: ${result.paradoxWarning}`);
|
|
1248
|
-
}
|
|
1249
|
-
// Clasificación de la fórmula (si tenemos verbosidad on o si está precalculado)
|
|
1250
1947
|
if (result.formula && (verbosity === 'on' || result.formulaClassification)) {
|
|
1251
1948
|
const cls = (0, formula_classifier_1.classifyFormula)(result.formula);
|
|
1252
1949
|
const name = result.formulaClassification || cls.formulaClassification;
|
|
1253
|
-
if (name)
|
|
1950
|
+
if (name)
|
|
1254
1951
|
this.emit(` Identificación: ${name}`);
|
|
1255
|
-
}
|
|
1256
1952
|
}
|
|
1257
|
-
if (result.
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
}
|
|
1263
|
-
if (result.normalForms && verbosity === 'on') {
|
|
1264
|
-
this.emit(` Formas Normales:`);
|
|
1265
|
-
if (result.normalForms.nnf)
|
|
1266
|
-
this.emit(` NNF: ${result.normalForms.nnf}`);
|
|
1267
|
-
if (result.normalForms.cnf)
|
|
1268
|
-
this.emit(` CNF: ${result.normalForms.cnf}`);
|
|
1269
|
-
if (result.normalForms.dnf)
|
|
1270
|
-
this.emit(` DNF: ${result.normalForms.dnf}`);
|
|
1271
|
-
if (result.normalForms.pnf)
|
|
1272
|
-
this.emit(` PNF: ${result.normalForms.pnf}`);
|
|
1273
|
-
if (result.normalForms.skolem)
|
|
1274
|
-
this.emit(` Skolem: ${result.normalForms.skolem}`);
|
|
1275
|
-
}
|
|
1276
|
-
if (result.formula && (verbosity === 'on' || cmd === 'explain')) {
|
|
1277
|
-
/* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */
|
|
1278
|
-
const { compareAcrossSystems } = require('./cross-system-compare');
|
|
1279
|
-
const { registry } = require('../profiles/interface');
|
|
1280
|
-
const comp = result.crossSystemComparison || compareAcrossSystems(result.formula, registry);
|
|
1281
|
-
/* eslint-enable @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */
|
|
1282
|
-
if (Object.keys(comp).length > 0) {
|
|
1283
|
-
this.emit(` Comparación entre sistemas:`);
|
|
1284
|
-
for (const [sys, val] of Object.entries(comp)) {
|
|
1285
|
-
this.emit(` ${sys.padEnd(30)} ${val}`);
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
const outputFormatRaw = this.letBindings.get('output');
|
|
1290
|
-
const isLatex = outputFormatRaw?.kind === 'atom' &&
|
|
1291
|
-
outputFormatRaw.name?.replace(/['"]/g, '').toLowerCase() === 'latex';
|
|
1292
|
-
const proof = result.proof;
|
|
1293
|
-
if (proof &&
|
|
1294
|
-
proof.steps.length > 0 &&
|
|
1295
|
-
(verbosity === 'on' || verbosity === 'proof' || cmd === 'derive' || cmd === 'prove')) {
|
|
1296
|
-
if (isLatex) {
|
|
1297
|
-
/* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */
|
|
1298
|
-
const { proofToLaTeX } = require('./format');
|
|
1299
|
-
this.emit(' Prueba (LaTeX):');
|
|
1300
|
-
this.emit(proofToLaTeX(proof));
|
|
1301
|
-
/* eslint-enable @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */
|
|
1302
|
-
}
|
|
1303
|
-
else {
|
|
1304
|
-
this.emit(' Prueba:');
|
|
1305
|
-
for (const step of proof.steps) {
|
|
1306
|
-
const premisesStr = step.premises.length > 0 ? ` [de ${step.premises.join(', ')}]` : '';
|
|
1307
|
-
this.emit(` ${step.stepNumber}. ${(0, format_1.formulaToUnicode)(step.formula)} — ${step.justification}${premisesStr}`);
|
|
1308
|
-
}
|
|
1953
|
+
if (result.model && (verbosity === 'on' || cmd === 'countermodel')) {
|
|
1954
|
+
if (result.model.valuation) {
|
|
1955
|
+
this.emit(' Modelo:');
|
|
1956
|
+
for (const [k, v] of Object.entries(result.model.valuation))
|
|
1957
|
+
this.emit(` ${k} = ${v}`);
|
|
1309
1958
|
}
|
|
1310
1959
|
}
|
|
1311
|
-
|
|
1312
|
-
if (model &&
|
|
1313
|
-
(verbosity === 'on' ||
|
|
1314
|
-
verbosity === 'model' ||
|
|
1315
|
-
cmd === 'countermodel' ||
|
|
1316
|
-
cmd === 'check_valid' ||
|
|
1317
|
-
cmd === 'check_satisfiable')) {
|
|
1318
|
-
// Display Kripke model with worlds if available
|
|
1319
|
-
if (model.worlds && model.worlds.length > 0) {
|
|
1320
|
-
this.emit(' Contramodelo Kripke:');
|
|
1321
|
-
this.emit(` Mundos: {${model.worlds.map((w) => w.name).join(', ')}}`);
|
|
1322
|
-
this.emit(' Accesibilidad:');
|
|
1323
|
-
for (const w of model.worlds) {
|
|
1324
|
-
if (w.accessible.length > 0) {
|
|
1325
|
-
this.emit(` ${w.name} R ${w.accessible.join(', ')}`);
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
|
-
this.emit(' Valuación:');
|
|
1329
|
-
for (const w of model.worlds) {
|
|
1330
|
-
const trueAtoms = Object.entries(w.valuation)
|
|
1331
|
-
.filter(([, val]) => val)
|
|
1332
|
-
.map(([k]) => k);
|
|
1333
|
-
const falseAtoms = Object.entries(w.valuation)
|
|
1334
|
-
.filter(([, val]) => !val)
|
|
1335
|
-
.map(([k]) => k);
|
|
1336
|
-
const parts = [];
|
|
1337
|
-
if (trueAtoms.length > 0)
|
|
1338
|
-
parts.push(trueAtoms.join(', '));
|
|
1339
|
-
if (falseAtoms.length > 0)
|
|
1340
|
-
parts.push(`¬${falseAtoms.join(', ¬')}`);
|
|
1341
|
-
this.emit(` V(${w.name}) = {${parts.join(', ')}}`);
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
else if (model.valuation) {
|
|
1345
|
-
// Flat propositional model
|
|
1346
|
-
this.emit(' Modelo / Valuación:');
|
|
1347
|
-
for (const [k, v] of Object.entries(model.valuation)) {
|
|
1348
|
-
const desc = this.letDescriptions.get(k);
|
|
1349
|
-
const descStr = desc ? ` ("${desc}")` : '';
|
|
1350
|
-
this.emit(` ${k}${descStr} = ${String(v)}`);
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
if (result.tableauTrace &&
|
|
1355
|
-
result.tableauTrace.length > 0 &&
|
|
1356
|
-
(verbosity === 'on' ||
|
|
1357
|
-
verbosity === 'proof' ||
|
|
1358
|
-
cmd === 'check valid' ||
|
|
1359
|
-
cmd === 'check satisfiable' ||
|
|
1360
|
-
cmd === 'check equivalent')) {
|
|
1960
|
+
if (result.tableauTrace && result.tableauTrace.length > 0 && verbosity === 'on') {
|
|
1361
1961
|
this.emit(' Traza del tableau:');
|
|
1362
|
-
for (let i = 0; i < result.tableauTrace.length; i++)
|
|
1363
|
-
|
|
1364
|
-
this.emit(` ${i + 1}. ${step.toString ? step.toString() : String(step)}`);
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
// Mostrar leyenda de variables con descripción si hay alguna relevante
|
|
1368
|
-
if (this.letDescriptions.size > 0 && result.formula) {
|
|
1369
|
-
const atoms = this.collectAtoms(result.formula);
|
|
1370
|
-
const relevantDescs = atoms.filter((a) => this.letDescriptions.has(a));
|
|
1371
|
-
if (relevantDescs.length > 0) {
|
|
1372
|
-
this.emit(' Donde:');
|
|
1373
|
-
for (const a of relevantDescs) {
|
|
1374
|
-
this.emit(` ${a} = "${this.letDescriptions.get(a)}"`);
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1962
|
+
for (let i = 0; i < result.tableauTrace.length; i++)
|
|
1963
|
+
this.emit(` ${i + 1}. ${String(result.tableauTrace[i])}`);
|
|
1377
1964
|
}
|
|
1378
1965
|
}
|
|
1379
|
-
/** Recolecta nombres de átomos únicos de una fórmula */
|
|
1380
1966
|
collectAtoms(f, seen = new Set()) {
|
|
1381
1967
|
if (!f)
|
|
1382
1968
|
return [];
|
|
@@ -1385,108 +1971,30 @@ class Interpreter {
|
|
|
1385
1971
|
return [f.name];
|
|
1386
1972
|
}
|
|
1387
1973
|
const result = [];
|
|
1388
|
-
if (f.args)
|
|
1389
|
-
for (const arg of f.args)
|
|
1974
|
+
if (f.args)
|
|
1975
|
+
for (const arg of f.args)
|
|
1390
1976
|
result.push(...this.collectAtoms(arg, seen));
|
|
1391
|
-
}
|
|
1392
|
-
}
|
|
1393
1977
|
return result;
|
|
1394
1978
|
}
|
|
1395
1979
|
statusIcon(status) {
|
|
1396
1980
|
switch (status) {
|
|
1397
1981
|
case 'valid':
|
|
1982
|
+
case 'provable':
|
|
1398
1983
|
return '✓';
|
|
1399
1984
|
case 'invalid':
|
|
1985
|
+
case 'refutable':
|
|
1400
1986
|
return '✗';
|
|
1401
1987
|
case 'satisfiable':
|
|
1402
1988
|
return '◎';
|
|
1403
1989
|
case 'unsatisfiable':
|
|
1404
1990
|
return '⊘';
|
|
1405
|
-
case 'provable':
|
|
1406
|
-
return '✓';
|
|
1407
|
-
case 'refutable':
|
|
1408
|
-
return '✗';
|
|
1409
|
-
case 'unknown':
|
|
1410
|
-
return '?';
|
|
1411
|
-
case 'error':
|
|
1412
|
-
return '⚠';
|
|
1413
1991
|
default:
|
|
1414
1992
|
return '•';
|
|
1415
1993
|
}
|
|
1416
1994
|
}
|
|
1417
|
-
formatTruthTable(formula,
|
|
1418
|
-
|
|
1419
|
-
const isVerbose = verbosity === 'on' || verbosity === 'model';
|
|
1420
|
-
const lines = [];
|
|
1421
|
-
// Detect Belnap (4-valued) table: results are strings like 'T','F','B','N'
|
|
1422
|
-
const isBelnap = tt.rows.length > 0 &&
|
|
1423
|
-
typeof tt.rows[0].result === 'string' &&
|
|
1424
|
-
['T', 'F', 'B', 'N'].includes(String(tt.rows[0].result));
|
|
1425
|
-
if (isBelnap) {
|
|
1426
|
-
lines.push(`Tabla de verdad Belnap (4 valores) para: ${(0, propositional_1.formulaToString)(formula)}`);
|
|
1427
|
-
lines.push('');
|
|
1428
|
-
}
|
|
1429
|
-
// Header
|
|
1430
|
-
const colLabels = [...tt.variables];
|
|
1431
|
-
if (isVerbose && tt.subFormulas) {
|
|
1432
|
-
tt.subFormulas.forEach((sf) => colLabels.push(sf.label));
|
|
1433
|
-
}
|
|
1434
|
-
colLabels.push((0, propositional_1.formulaToString)(formula));
|
|
1435
|
-
const colWidths = colLabels.map((h) => Math.max(h.length, 5));
|
|
1436
|
-
lines.push(colLabels.map((h, i) => h.padEnd(colWidths[i])).join(' | '));
|
|
1437
|
-
lines.push(colWidths.map((w) => '-'.repeat(w)).join('-+-'));
|
|
1438
|
-
// Rows
|
|
1439
|
-
const designated = new Set(['T', 'B']);
|
|
1440
|
-
for (let rowIndex = 0; rowIndex < tt.rows.length; rowIndex++) {
|
|
1441
|
-
const row = tt.rows[rowIndex];
|
|
1442
|
-
const vals = tt.variables.map((v) => {
|
|
1443
|
-
const val = row.valuation[v];
|
|
1444
|
-
if (typeof val === 'string')
|
|
1445
|
-
return val;
|
|
1446
|
-
return val ? 'T' : 'F';
|
|
1447
|
-
});
|
|
1448
|
-
if (isVerbose && tt.subFormulas && tt.subFormulaValues) {
|
|
1449
|
-
const subVals = tt.subFormulaValues[rowIndex];
|
|
1450
|
-
tt.subFormulas.forEach((sf) => {
|
|
1451
|
-
let v = subVals[sf.label];
|
|
1452
|
-
if (typeof v === 'boolean')
|
|
1453
|
-
v = v ? 'T' : 'F';
|
|
1454
|
-
vals.push(String(v));
|
|
1455
|
-
});
|
|
1456
|
-
}
|
|
1457
|
-
let finalVal = row.result;
|
|
1458
|
-
if (typeof finalVal === 'boolean')
|
|
1459
|
-
finalVal = finalVal ? 'T' : 'F';
|
|
1460
|
-
vals.push(String(finalVal));
|
|
1461
|
-
const isCountermodel = (tt.isTautology === false && !row.result) || (tt.isSatisfiable && row.result);
|
|
1462
|
-
const rowStr = vals.map((v, i) => v.padEnd(colWidths[i])).join(' | ');
|
|
1463
|
-
// Belnap designation marker
|
|
1464
|
-
if (isBelnap && designated.has(String(finalVal))) {
|
|
1465
|
-
lines.push(`${rowStr} ⊛ Designado`);
|
|
1466
|
-
}
|
|
1467
|
-
else if (isVerbose && isCountermodel) {
|
|
1468
|
-
lines.push(`${rowStr} ←`);
|
|
1469
|
-
}
|
|
1470
|
-
else {
|
|
1471
|
-
lines.push(rowStr);
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
lines.push('');
|
|
1475
|
-
if (tt.satisfyingCount !== undefined && tt.totalCount !== undefined) {
|
|
1476
|
-
lines.push(`${tt.satisfyingCount}/${tt.totalCount} valuaciones verdaderas`);
|
|
1477
|
-
}
|
|
1478
|
-
if (isBelnap) {
|
|
1479
|
-
lines.push('Valores designados (portadores de verdad): {T, B}');
|
|
1480
|
-
}
|
|
1481
|
-
if (tt.isTautology)
|
|
1482
|
-
lines.push('→ Tautologia ✓');
|
|
1483
|
-
else if (tt.isContradiction)
|
|
1484
|
-
lines.push('→ Contradiccion ✗');
|
|
1485
|
-
else
|
|
1486
|
-
lines.push('→ Contingente (satisfacible)');
|
|
1487
|
-
return lines.join('\n');
|
|
1995
|
+
formatTruthTable(formula, _tt) {
|
|
1996
|
+
return `Tabla de verdad para ${(0, propositional_1.formulaToString)(formula)}`;
|
|
1488
1997
|
}
|
|
1489
|
-
// Getters para el estado (usados por REPL)
|
|
1490
1998
|
getTheory() {
|
|
1491
1999
|
return this.theory;
|
|
1492
2000
|
}
|
|
@@ -1496,9 +2004,6 @@ class Interpreter {
|
|
|
1496
2004
|
getTextLayer() {
|
|
1497
2005
|
return this.textLayer;
|
|
1498
2006
|
}
|
|
1499
|
-
getLetDescriptions() {
|
|
1500
|
-
return this.letDescriptions;
|
|
1501
|
-
}
|
|
1502
2007
|
getLetBindings() {
|
|
1503
2008
|
return this.letBindings;
|
|
1504
2009
|
}
|