@stevenvo780/st-lang 2.0.3 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/dist/cli/index.js.map +1 -1
  2. package/dist/lexer/tokens.d.ts.map +1 -1
  3. package/dist/parser/parser.d.ts +1 -0
  4. package/dist/parser/parser.d.ts.map +1 -1
  5. package/dist/parser/parser.js +99 -22
  6. package/dist/parser/parser.js.map +1 -1
  7. package/dist/profiles/aristotelian/syllogistic.d.ts.map +1 -1
  8. package/dist/profiles/aristotelian/syllogistic.js +219 -15
  9. package/dist/profiles/aristotelian/syllogistic.js.map +1 -1
  10. package/dist/profiles/arithmetic/index.d.ts +1 -6
  11. package/dist/profiles/arithmetic/index.d.ts.map +1 -1
  12. package/dist/profiles/arithmetic/index.js +142 -65
  13. package/dist/profiles/arithmetic/index.js.map +1 -1
  14. package/dist/profiles/classical/first-order.d.ts +2 -0
  15. package/dist/profiles/classical/first-order.d.ts.map +1 -1
  16. package/dist/profiles/classical/first-order.js +375 -51
  17. package/dist/profiles/classical/first-order.js.map +1 -1
  18. package/dist/profiles/classical/propositional.d.ts +7 -0
  19. package/dist/profiles/classical/propositional.d.ts.map +1 -1
  20. package/dist/profiles/classical/propositional.js +467 -32
  21. package/dist/profiles/classical/propositional.js.map +1 -1
  22. package/dist/profiles/deontic/standard.d.ts.map +1 -1
  23. package/dist/profiles/deontic/standard.js +13 -1
  24. package/dist/profiles/deontic/standard.js.map +1 -1
  25. package/dist/profiles/epistemic/s5.d.ts.map +1 -1
  26. package/dist/profiles/epistemic/s5.js +14 -1
  27. package/dist/profiles/epistemic/s5.js.map +1 -1
  28. package/dist/profiles/intuitionistic/propositional.d.ts.map +1 -1
  29. package/dist/profiles/intuitionistic/propositional.js +98 -13
  30. package/dist/profiles/intuitionistic/propositional.js.map +1 -1
  31. package/dist/profiles/modal/k.d.ts.map +1 -1
  32. package/dist/profiles/modal/k.js +9 -1
  33. package/dist/profiles/modal/k.js.map +1 -1
  34. package/dist/profiles/paraconsistent/belnap.d.ts +2 -1
  35. package/dist/profiles/paraconsistent/belnap.d.ts.map +1 -1
  36. package/dist/profiles/paraconsistent/belnap.js +81 -4
  37. package/dist/profiles/paraconsistent/belnap.js.map +1 -1
  38. package/dist/profiles/probabilistic/basic.d.ts.map +1 -1
  39. package/dist/profiles/probabilistic/basic.js +71 -1
  40. package/dist/profiles/probabilistic/basic.js.map +1 -1
  41. package/dist/profiles/shared/base-profile.d.ts +4 -2
  42. package/dist/profiles/shared/base-profile.d.ts.map +1 -1
  43. package/dist/profiles/shared/base-profile.js +72 -11
  44. package/dist/profiles/shared/base-profile.js.map +1 -1
  45. package/dist/profiles/shared/tableau-engine.d.ts +7 -7
  46. package/dist/profiles/shared/tableau-engine.d.ts.map +1 -1
  47. package/dist/profiles/shared/tableau-engine.js +74 -70
  48. package/dist/profiles/shared/tableau-engine.js.map +1 -1
  49. package/dist/profiles/temporal/ltl.d.ts +1 -0
  50. package/dist/profiles/temporal/ltl.d.ts.map +1 -1
  51. package/dist/profiles/temporal/ltl.js +65 -0
  52. package/dist/profiles/temporal/ltl.js.map +1 -1
  53. package/dist/protocol/handler.d.ts.map +1 -1
  54. package/dist/protocol/handler.js +96 -27
  55. package/dist/protocol/handler.js.map +1 -1
  56. package/dist/runtime/cross-system-compare.d.ts +4 -0
  57. package/dist/runtime/cross-system-compare.d.ts.map +1 -0
  58. package/dist/runtime/cross-system-compare.js +50 -0
  59. package/dist/runtime/cross-system-compare.js.map +1 -0
  60. package/dist/runtime/fallacies.d.ts.map +1 -1
  61. package/dist/runtime/fallacies.js +130 -0
  62. package/dist/runtime/fallacies.js.map +1 -1
  63. package/dist/runtime/format.d.ts +5 -0
  64. package/dist/runtime/format.d.ts.map +1 -1
  65. package/dist/runtime/format.js +54 -6
  66. package/dist/runtime/format.js.map +1 -1
  67. package/dist/runtime/formula-classifier.d.ts +18 -0
  68. package/dist/runtime/formula-classifier.d.ts.map +1 -0
  69. package/dist/runtime/formula-classifier.js +183 -0
  70. package/dist/runtime/formula-classifier.js.map +1 -0
  71. package/dist/runtime/interpreter.d.ts +1 -0
  72. package/dist/runtime/interpreter.d.ts.map +1 -1
  73. package/dist/runtime/interpreter.js +221 -49
  74. package/dist/runtime/interpreter.js.map +1 -1
  75. package/dist/runtime/known-theorems.d.ts +12 -0
  76. package/dist/runtime/known-theorems.d.ts.map +1 -0
  77. package/dist/runtime/known-theorems.js +147 -0
  78. package/dist/runtime/known-theorems.js.map +1 -0
  79. package/dist/tests/arithmetic.test.js +81 -27
  80. package/dist/tests/arithmetic.test.js.map +1 -1
  81. package/dist/tests/core.test.js +2 -2
  82. package/dist/tests/core.test.js.map +1 -1
  83. package/dist/tests/engines.test.js +1 -1
  84. package/dist/tests/engines.test.js.map +1 -1
  85. package/dist/tests/examples.test.js +1 -7
  86. package/dist/tests/examples.test.js.map +1 -1
  87. package/dist/tests/exhaustive-matrix.test.js +2 -2
  88. package/dist/tests/philosophy.test.js +2 -0
  89. package/dist/tests/philosophy.test.js.map +1 -1
  90. package/dist/tests/profiles.test.js +111 -0
  91. package/dist/tests/profiles.test.js.map +1 -1
  92. package/dist/tests/stress-exhaustive.test.d.ts +2 -0
  93. package/dist/tests/stress-exhaustive.test.d.ts.map +1 -0
  94. package/dist/tests/stress-exhaustive.test.js +1448 -0
  95. package/dist/tests/stress-exhaustive.test.js.map +1 -0
  96. package/dist/tests/v1-features.test.js +10 -4
  97. package/dist/tests/v1-features.test.js.map +1 -1
  98. package/dist/types/index.d.ts +29 -0
  99. package/dist/types/index.d.ts.map +1 -1
  100. package/package.json +2 -2
@@ -12,6 +12,7 @@ const fallacies_1 = require("./fallacies");
12
12
  // Barrel import: registra todos los perfiles automáticamente
13
13
  require("../profiles");
14
14
  const compiler_1 = require("../text-layer/compiler");
15
+ const formula_classifier_1 = require("./formula-classifier");
15
16
  class Interpreter {
16
17
  theory;
17
18
  profile = null;
@@ -53,7 +54,7 @@ class Interpreter {
53
54
  name,
54
55
  params: ['arg'],
55
56
  body: [],
56
- source: { line: 0, column: 0 }
57
+ source: { line: 0, column: 0 },
57
58
  });
58
59
  }
59
60
  }
@@ -315,7 +316,8 @@ class Interpreter {
315
316
  const actualInstanceName = resolvedPrefix.name;
316
317
  const scope = this.theories.get(actualInstanceName);
317
318
  if (scope) {
318
- if (scope.privateMembers.has(memberName) && this.currentTheoryName !== actualInstanceName)
319
+ if (scope.privateMembers.has(memberName) &&
320
+ this.currentTheoryName !== actualInstanceName)
319
321
  return f;
320
322
  if (scope.letBindings.has(memberName))
321
323
  return this.resolveFormula(scope.letBindings.get(memberName), new Set(visited));
@@ -369,8 +371,9 @@ class Interpreter {
369
371
  }
370
372
  // Recorrer hijos recursivamente
371
373
  if (f.args && f.args.length > 0) {
372
- const newArgs = f.args.map(a => a ? this.resolveFormula(a, new Set(visited)) : a);
373
- const changed = newArgs.some((a, i) => a !== f.args[i]);
374
+ const newArgs = f.args.map((a) => (a ? this.resolveFormula(a, new Set(visited)) : a));
375
+ const oldArgs = f.args;
376
+ const changed = newArgs.some((a, i) => a !== oldArgs[i]);
374
377
  if (changed) {
375
378
  return { ...f, args: newArgs };
376
379
  }
@@ -583,7 +586,7 @@ class Interpreter {
583
586
  }
584
587
  execAnalyzeCmd(stmt) {
585
588
  const profile = this.requireProfile();
586
- const premises = stmt.premises.map(p => this.resolveFormula(p));
589
+ const premises = stmt.premises.map((p) => this.resolveFormula(p));
587
590
  const conclusion = this.resolveFormula(stmt.conclusion);
588
591
  const fallacies = (0, fallacies_1.detectFallacies)(premises, conclusion, profile);
589
592
  const pStr = premises.map((p) => (0, format_1.formulaToUnicode)(p)).join(', ');
@@ -705,7 +708,6 @@ class Interpreter {
705
708
  /** Crea una instancia de una teoría y la registra en this.theories */
706
709
  instantiateTheory(node, instanceName, args = []) {
707
710
  const theoryName = instanceName || node.name;
708
- const templateName = node.name;
709
711
  // Crear scope vacío
710
712
  const scope = {
711
713
  name: theoryName,
@@ -832,19 +834,19 @@ class Interpreter {
832
834
  const profile = this.requireProfile();
833
835
  for (const branch of stmt.branches) {
834
836
  const resolved = this.resolveFormula(branch.formula);
835
- let matched = false;
837
+ let matched;
836
838
  if (branch.condition === 'valid' || branch.condition === 'invalid') {
837
839
  const result = profile.checkValid(resolved);
838
- matched = branch.condition === 'valid'
839
- ? result.status === 'valid'
840
- : result.status !== 'valid';
840
+ matched =
841
+ branch.condition === 'valid' ? result.status === 'valid' : result.status !== 'valid';
841
842
  }
842
843
  else {
843
844
  // satisfiable / unsatisfiable
844
845
  const result = profile.checkSatisfiable(resolved);
845
- matched = branch.condition === 'satisfiable'
846
- ? (result.status === 'satisfiable' || result.status === 'valid')
847
- : (result.status === 'unsatisfiable');
846
+ matched =
847
+ branch.condition === 'satisfiable'
848
+ ? result.status === 'satisfiable' || result.status === 'valid'
849
+ : result.status === 'unsatisfiable';
848
850
  }
849
851
  if (matched) {
850
852
  for (const bodyStmt of branch.body) {
@@ -894,18 +896,18 @@ class Interpreter {
894
896
  break;
895
897
  iter++;
896
898
  const resolved = this.resolveFormula(stmt.formula);
897
- let matched = false;
899
+ let matched;
898
900
  if (stmt.condition === 'valid' || stmt.condition === 'invalid') {
899
901
  const result = profile.checkValid(resolved);
900
- matched = stmt.condition === 'valid'
901
- ? result.status === 'valid'
902
- : result.status !== 'valid';
902
+ matched =
903
+ stmt.condition === 'valid' ? result.status === 'valid' : result.status !== 'valid';
903
904
  }
904
905
  else {
905
906
  const result = profile.checkSatisfiable(resolved);
906
- matched = stmt.condition === 'satisfiable'
907
- ? (result.status === 'satisfiable' || result.status === 'valid')
908
- : (result.status === 'unsatisfiable');
907
+ matched =
908
+ stmt.condition === 'satisfiable'
909
+ ? result.status === 'satisfiable' || result.status === 'valid'
910
+ : result.status === 'unsatisfiable';
909
911
  }
910
912
  if (!matched)
911
913
  break;
@@ -962,8 +964,8 @@ class Interpreter {
962
964
  }
963
965
  const scope = this.theories.get(actualInstanceName);
964
966
  if (scope) {
965
- // En ST actual, las funciones no se guardan en TheoryScope sino que se ejecutan
966
- // en el contexto del intérprete. Para soportar métodos de instancia,
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,
967
969
  // necesitamos que las funciones estén vinculadas al scope o prefijadas.
968
970
  // Como solución rápida y potente: buscamos la función prefijada en el mapa global.
969
971
  const internalFnName = `${actualInstanceName}.${methodName}`;
@@ -1088,7 +1090,7 @@ class Interpreter {
1088
1090
  const isTrue = result.status === 'valid' || result.status === 'satisfiable';
1089
1091
  return { kind: 'atom', name: `"${isTrue ? 'True' : 'False'}"`, source: arg.source };
1090
1092
  }
1091
- catch (e) {
1093
+ catch {
1092
1094
  return { kind: 'atom', name: '"Error"', source: arg.source };
1093
1095
  }
1094
1096
  }
@@ -1097,17 +1099,21 @@ class Interpreter {
1097
1099
  return { kind: 'atom', name: `"{ ${atoms.join(', ')} }"`, source: arg.source };
1098
1100
  }
1099
1101
  if (name === 'input') {
1100
- const prompt = arg.kind === 'atom' && arg.name?.startsWith('"') ? arg.name.replace(/(^"|"$)/g, '') : (0, propositional_1.formulaToString)(arg);
1101
- let inputStr = '';
1102
+ const prompt = arg.kind === 'atom' && arg.name?.startsWith('"')
1103
+ ? arg.name.replace(/(^"|"$)/g, '')
1104
+ : (0, propositional_1.formulaToString)(arg);
1105
+ let inputStr;
1102
1106
  try {
1103
1107
  process.stdout.write(prompt + ' ');
1108
+ /* eslint-disable @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 */
1104
1109
  const fs = require('fs');
1105
1110
  const buf = Buffer.alloc(256);
1106
1111
  const bytesRead = fs.readSync(process.stdin.fd, buf, 0, 256, null);
1107
1112
  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 */
1108
1114
  }
1109
- catch (e) {
1110
- inputStr = "interactive_not_supported";
1115
+ catch {
1116
+ inputStr = 'interactive_not_supported';
1111
1117
  }
1112
1118
  return { kind: 'atom', name: `"${inputStr}"`, source: arg.source };
1113
1119
  }
@@ -1127,7 +1133,7 @@ class Interpreter {
1127
1133
  // Intentar leer el archivo (solo funciona en Node.js / CLI)
1128
1134
  let source;
1129
1135
  try {
1130
- // eslint-disable-next-line @typescript-eslint/no-require-imports
1136
+ /* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */
1131
1137
  const fs = require('fs');
1132
1138
  const path = require('path');
1133
1139
  // Resolver relativo al archivo actual si no es absoluto
@@ -1135,6 +1141,7 @@ class Interpreter {
1135
1141
  ? filePath
1136
1142
  : path.resolve(path.dirname(stmt.source.file || '.'), filePath);
1137
1143
  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 */
1138
1145
  }
1139
1146
  catch {
1140
1147
  throw new Error(`No se pudo importar '${filePath}': archivo no encontrado`);
@@ -1220,30 +1227,146 @@ class Interpreter {
1220
1227
  emit(msg) {
1221
1228
  this.stdoutLines.push(msg);
1222
1229
  }
1230
+ getVerbosity() {
1231
+ const v = this.letBindings.get('verbose');
1232
+ if (v && v.kind === 'atom' && v.name) {
1233
+ const n = v.name.toLowerCase();
1234
+ // Remove quotes if present
1235
+ return n.replace(/(^"|"$)/g, '');
1236
+ }
1237
+ return 'off';
1238
+ }
1223
1239
  emitResult(cmd, result) {
1224
1240
  const statusIcon = this.statusIcon(result.status);
1225
1241
  this.emit(`${statusIcon} [${cmd}] ${result.output || result.status}`);
1242
+ const verbosity = this.getVerbosity();
1243
+ if (result.educationalNote && verbosity === 'on') {
1244
+ this.emit(` Nota pedagógica: ${result.educationalNote}`);
1245
+ }
1246
+ if (result.paradoxWarning) {
1247
+ this.emit(` ⚠ PARADOJA: ${result.paradoxWarning}`);
1248
+ }
1249
+ // Clasificación de la fórmula (si tenemos verbosidad on o si está precalculado)
1250
+ if (result.formula && (verbosity === 'on' || result.formulaClassification)) {
1251
+ const cls = (0, formula_classifier_1.classifyFormula)(result.formula);
1252
+ const name = result.formulaClassification || cls.formulaClassification;
1253
+ if (name) {
1254
+ this.emit(` Identificación: ${name}`);
1255
+ }
1256
+ }
1257
+ if (result.reasoningType) {
1258
+ this.emit(` Patrón de razonamiento: ${result.reasoningType}`);
1259
+ }
1260
+ if (result.reasoningSchema) {
1261
+ this.emit(` Esquema: ${result.reasoningSchema}`);
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';
1226
1292
  const proof = result.proof;
1227
- if (proof && proof.steps.length > 0) {
1228
- this.emit(' Prueba:');
1229
- for (const step of proof.steps) {
1230
- const premisesStr = step.premises.length > 0 ? ` [de ${step.premises.join(', ')}]` : '';
1231
- this.emit(` ${step.stepNumber}. ${(0, format_1.formulaToUnicode)(step.formula)} — ${step.justification}${premisesStr}`);
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
+ }
1232
1309
  }
1233
1310
  }
1234
1311
  const model = result.model;
1235
- if (model && model.valuation) {
1236
- this.emit(' Modelo:');
1237
- for (const [k, v] of Object.entries(model.valuation)) {
1238
- const desc = this.letDescriptions.get(k);
1239
- const descStr = desc ? ` ("${desc}")` : '';
1240
- this.emit(` ${k}${descStr} = ${String(v)}`);
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
+ this.emit(' Traza del tableau:');
1361
+ for (let i = 0; i < result.tableauTrace.length; i++) {
1362
+ const step = result.tableauTrace[i];
1363
+ this.emit(` ${i + 1}. ${step.toString ? step.toString() : String(step)}`);
1241
1364
  }
1242
1365
  }
1243
1366
  // Mostrar leyenda de variables con descripción si hay alguna relevante
1244
1367
  if (this.letDescriptions.size > 0 && result.formula) {
1245
1368
  const atoms = this.collectAtoms(result.formula);
1246
- const relevantDescs = atoms.filter(a => this.letDescriptions.has(a));
1369
+ const relevantDescs = atoms.filter((a) => this.letDescriptions.has(a));
1247
1370
  if (relevantDescs.length > 0) {
1248
1371
  this.emit(' Donde:');
1249
1372
  for (const a of relevantDescs) {
@@ -1291,23 +1414,72 @@ class Interpreter {
1291
1414
  }
1292
1415
  }
1293
1416
  formatTruthTable(formula, tt) {
1417
+ const verbosity = this.getVerbosity();
1418
+ const isVerbose = verbosity === 'on' || verbosity === 'model';
1294
1419
  const lines = [];
1295
- const header = [...tt.variables, (0, propositional_1.formulaToString)(formula)];
1296
- const colWidths = header.map((h) => Math.max(h.length, 5));
1420
+ // Detect Belnap (4-valued) table: results are strings like 'T','F','B','N'
1421
+ const isBelnap = tt.rows.length > 0 && typeof tt.rows[0].result === 'string'
1422
+ && ['T', 'F', 'B', 'N'].includes(String(tt.rows[0].result));
1423
+ if (isBelnap) {
1424
+ lines.push(`Tabla de verdad Belnap (4 valores) para: ${(0, propositional_1.formulaToString)(formula)}`);
1425
+ lines.push('');
1426
+ }
1297
1427
  // Header
1298
- lines.push(header.map((h, i) => h.padEnd(colWidths[i])).join(' | '));
1428
+ const colLabels = [...tt.variables];
1429
+ if (isVerbose && tt.subFormulas) {
1430
+ tt.subFormulas.forEach((sf) => colLabels.push(sf.label));
1431
+ }
1432
+ colLabels.push((0, propositional_1.formulaToString)(formula));
1433
+ const colWidths = colLabels.map((h) => Math.max(h.length, 5));
1434
+ lines.push(colLabels.map((h, i) => h.padEnd(colWidths[i])).join(' | '));
1299
1435
  lines.push(colWidths.map((w) => '-'.repeat(w)).join('-+-'));
1300
1436
  // Rows
1301
- for (const row of tt.rows) {
1302
- const vals = tt.variables.map((v) => (row.valuation[v] ? 'T' : 'F'));
1303
- vals.push(row.result ? 'T' : 'F');
1304
- lines.push(vals.map((v, i) => v.padEnd(colWidths[i])).join(' | '));
1437
+ const designated = new Set(['T', 'B']);
1438
+ for (let rowIndex = 0; rowIndex < tt.rows.length; rowIndex++) {
1439
+ const row = tt.rows[rowIndex];
1440
+ const vals = tt.variables.map((v) => {
1441
+ const val = row.valuation[v];
1442
+ if (typeof val === 'string')
1443
+ return val;
1444
+ return val ? 'T' : 'F';
1445
+ });
1446
+ if (isVerbose && tt.subFormulas && tt.subFormulaValues) {
1447
+ const subVals = tt.subFormulaValues[rowIndex];
1448
+ tt.subFormulas.forEach((sf) => {
1449
+ let v = subVals[sf.label];
1450
+ if (typeof v === 'boolean')
1451
+ v = v ? 'T' : 'F';
1452
+ vals.push(String(v));
1453
+ });
1454
+ }
1455
+ let finalVal = row.result;
1456
+ if (typeof finalVal === 'boolean')
1457
+ finalVal = finalVal ? 'T' : 'F';
1458
+ vals.push(String(finalVal));
1459
+ const isCountermodel = (tt.isTautology === false && !row.result) || (tt.isSatisfiable && row.result);
1460
+ const rowStr = vals.map((v, i) => v.padEnd(colWidths[i])).join(' | ');
1461
+ // Belnap designation marker
1462
+ if (isBelnap && designated.has(String(finalVal))) {
1463
+ lines.push(`${rowStr} ⊛ Designado`);
1464
+ }
1465
+ else if (isVerbose && isCountermodel) {
1466
+ lines.push(`${rowStr} ←`);
1467
+ }
1468
+ else {
1469
+ lines.push(rowStr);
1470
+ }
1305
1471
  }
1306
1472
  lines.push('');
1473
+ if (tt.satisfyingCount !== undefined && tt.totalCount !== undefined) {
1474
+ lines.push(`${tt.satisfyingCount}/${tt.totalCount} valuaciones verdaderas`);
1475
+ }
1476
+ if (isBelnap) {
1477
+ lines.push('Valores designados (portadores de verdad): {T, B}');
1478
+ }
1307
1479
  if (tt.isTautology)
1308
- lines.push('→ Tautologia');
1480
+ lines.push('→ Tautologia');
1309
1481
  else if (tt.isContradiction)
1310
- lines.push('→ Contradiccion');
1482
+ lines.push('→ Contradiccion');
1311
1483
  else
1312
1484
  lines.push('→ Contingente (satisfacible)');
1313
1485
  return lines.join('\n');