eyeprolog 1.1.26 → 1.1.28

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.1.26",
6
+ "version": "1.1.28",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/iso.js CHANGED
@@ -1783,7 +1783,7 @@ function* phraseBuiltin({ solver, goal, env }) {
1783
1783
  solver.absorbStatsFrom(child);
1784
1784
  }
1785
1785
  }
1786
- function formalErrorTerm(error) {
1786
+ export function formalErrorTerm(error) {
1787
1787
  const context = error.contextTerm ?? atom('eyeprolog');
1788
1788
  if (error.formalTerm != null) return compound('error', [error.formalTerm, context]);
1789
1789
  const parse = (text) => {
package/src/repl.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import fs from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { createInterface } from 'node:readline';
5
+ import { formalErrorTerm } from './iso.js';
5
6
 
6
7
  const ANSWER_HELP = `
7
8
  SPACE, "n" or ";": next solution, if any
@@ -370,6 +371,17 @@ function formatAnswer(engine, state, variables, env) {
370
371
  const bindings = [];
371
372
  const queryVariableNames = new Set(variables.map((variable) => variable.name));
372
373
  const names = new Map(variables.map((variable) => [variable.name, variable.name]));
374
+ const operators = [...state.program.operators.values()];
375
+ // Answer substitutions are displayed as `Variable = Value`. Format Value
376
+ // in the right-operand context of the active infix `=/2` definition so an
377
+ // operator term such as `a = b` is parenthesized rather than producing the
378
+ // invalid `T = a = b`. ISO does not standardize a top level, but the term
379
+ // syntax used by the display must still respect the current operator table.
380
+ const equality = operators.find((definition) =>
381
+ definition.name === '=' && ['xfx', 'xfy', 'yfx'].includes(definition.specifier));
382
+ const valueMaxPriority = equality == null
383
+ ? 699
384
+ : equality.specifier === 'xfy' ? equality.priority : equality.priority - 1;
373
385
  let generated = 0;
374
386
 
375
387
  for (const variable of variables) collectUnboundVariables(engine, variable, env, names, () => `_${letterName(generated++)}`);
@@ -379,8 +391,9 @@ function formatAnswer(engine, state, variables, env) {
379
391
  (value.name === variable.name || !queryVariableNames.has(value.name))) continue;
380
392
  bindings.push(`${variable.name} = ${engine.formatTermForWrite(value, env, {
381
393
  quoted: true,
382
- operators: [...state.program.operators.values()],
394
+ operators,
383
395
  variableNames: names,
396
+ maxPriority: valueMaxPriority,
384
397
  })}`);
385
398
  }
386
399
  return bindings.length === 0 ? 'true' : bindings.join(', ');
@@ -413,46 +426,17 @@ function formatError(engine, state, error) {
413
426
  const env = new engine.Env();
414
427
  const variableNames = new Map();
415
428
  let generated = 0;
416
- if (error.formalTerm != null) {
417
- collectUnboundVariables(engine, error.formalTerm, env, variableNames, () => `_${letterName(generated++)}`);
418
- if (error.contextTerm != null) {
419
- collectUnboundVariables(engine, error.contextTerm, env, variableNames, () => `_${letterName(generated++)}`);
420
- }
421
- const formal = engine.formatTermForWrite(error.formalTerm, env, {
422
- quoted: true,
423
- operators: [...state.program.operators.values()],
424
- variableNames,
425
- });
426
- if (error.contextTerm != null) {
427
- const context = engine.formatTermForWrite(error.contextTerm, env, {
428
- quoted: true,
429
- operators: [...state.program.operators.values()],
430
- variableNames,
431
- });
432
- return `error(${formal}, ${context}).`;
433
- }
434
- return `error(${formal}).`;
435
- }
436
- if (error.culprit != null) {
437
- collectUnboundVariables(engine, error.culprit, env, variableNames, () => `_${letterName(generated++)}`);
438
- }
439
- if (error.contextTerm != null) {
440
- collectUnboundVariables(engine, error.contextTerm, env, variableNames, () => `_${letterName(generated++)}`);
441
- }
442
- const culprit = error.culprit == null ? '' : `, ${engine.formatTermForWrite(error.culprit, env, {
429
+ // ISO 7.12.1 represents processor errors as error(ErrorTerm, ImpDef).
430
+ // Reuse the same conversion as catch/3 so uncaught errors at the top
431
+ // level cannot lose the implementation-defined context or misplace a
432
+ // culprit as the second argument of error/2.
433
+ const term = formalErrorTerm(error);
434
+ collectUnboundVariables(engine, term, env, variableNames, () => `_${letterName(generated++)}`);
435
+ return `${engine.formatTermForWrite(term, env, {
443
436
  quoted: true,
444
437
  operators: [...state.program.operators.values()],
445
438
  variableNames,
446
- })}`;
447
- if (error.contextTerm != null) {
448
- const context = engine.formatTermForWrite(error.contextTerm, env, {
449
- quoted: true,
450
- operators: [...state.program.operators.values()],
451
- variableNames,
452
- });
453
- return `error(${error.formal}${culprit}, ${context}).`;
454
- }
455
- return `error(${error.formal}${culprit}).`;
439
+ })}.`;
456
440
  }
457
441
  const message = error?.message ?? String(error);
458
442
  return message.endsWith('.') ? message : `${message}.`;
package/src/write.js CHANGED
@@ -208,5 +208,8 @@ export function formatTermForWrite(term, env = new Env(), options = {}) {
208
208
  compact: options.compact === true,
209
209
  operatorAtomsAsArgs: options.operatorAtomsAsArgs === true,
210
210
  };
211
- return format(term, env, normalized, operatorTable(options.operators), 1200);
211
+ const maxPriority = Number.isInteger(options.maxPriority)
212
+ ? Math.max(0, Math.min(1200, options.maxPriority))
213
+ : 1200;
214
+ return format(term, env, normalized, operatorTable(options.operators), maxPriority);
212
215
  }
@@ -533,8 +533,23 @@ c4 ?- call((!;1)).
533
533
  });
534
534
  assertEqual(result.status, 0, 'exit status');
535
535
  assertEqual(result.stdout,
536
- '?- error(type_error(list), [1, [], _A | 2]).\n' +
537
- '?- error(type_error(list), [1, [], _A | 2]).\n' +
536
+ '?- error(type_error(list, [1, [], _A | 2]), eyeprolog).\n' +
537
+ '?- error(type_error(list, [1, [], _A | 2]), eyeprolog).\n' +
538
+ '?- ',
539
+ 'stdout');
540
+ assertEqual(result.stderr, '', 'stderr');
541
+ },
542
+ },
543
+ {
544
+ name: 'REPL uncaught ISO errors retain the error/2 implementation context',
545
+ run: () => {
546
+ const result = runCli([], {
547
+ input: '_ is _.\ncatch(_ is _, error(Error, Imp_def), true).\nhalt.\n',
548
+ });
549
+ assertEqual(result.status, 0, 'exit status');
550
+ assertEqual(result.stdout,
551
+ '?- error(instantiation_error, eyeprolog).\n' +
552
+ '?- Error = instantiation_error, Imp_def = eyeprolog.\n' +
538
553
  '?- ',
539
554
  'stdout');
540
555
  assertEqual(result.stderr, '', 'stderr');
@@ -585,6 +600,23 @@ c4 ?- call((!;1)).
585
600
  assertEqual(result.stderr, '', 'stderr');
586
601
  },
587
602
  },
603
+ {
604
+ name: 'REPL parenthesizes operator-valued answer substitutions',
605
+ run: () => {
606
+ const result = runCli([], {
607
+ input: 'T = (a=b).\nU = (a,b).\nV = (a;b).\nW = (a+b).\nhalt.\n',
608
+ });
609
+ assertEqual(result.status, 0, 'exit status');
610
+ assertEqual(result.stdout,
611
+ '?- T = (a = b).\n' +
612
+ '?- U = (a, b).\n' +
613
+ '?- V = (a ; b).\n' +
614
+ '?- W = a + b.\n' +
615
+ '?- ',
616
+ 'stdout');
617
+ assertEqual(result.stderr, '', 'stderr');
618
+ },
619
+ },
588
620
  {
589
621
  name: 'REPL accepts multiline period-terminated queries',
590
622
  run: () => {
@@ -5803,11 +5803,14 @@ instances whose message contains the corresponding Prolog error term.
5803
5803
  `error(Formal,eyeprolog)` term. `throw/1` copies its ball before unwinding: bound
5804
5804
  parts are preserved, repeated variables remain shared within the copied ball,
5805
5805
  and unbound variables are fresh with respect to the protected goal and catcher.
5806
- Catchable error terms follow the same variable-freshening rule. At the interactive
5807
- top level, variables that occur in an uncaught ISO error are rendered with fresh
5808
- answer names such as `_A` rather than reusing query-variable spellings such as
5809
- `X` or `Xx`; this keeps the displayed error consistent with the copied exception
5810
- term. An unmatched ball or error continues outward.
5806
+ Catchable error terms follow the same variable-freshening rule. The interactive
5807
+ top level uses the same ISO error envelope for uncaught processor errors, so an
5808
+ ordinary runtime error is displayed as `error(Formal,eyeprolog)` rather than
5809
+ dropping the implementation-defined second argument. Variables that occur in an
5810
+ uncaught ISO error are rendered with fresh answer names such as `_A` rather than
5811
+ reusing query-variable spellings such as `X` or `Xx`; this keeps the displayed
5812
+ error consistent with the copied exception term. An unmatched ball or error
5813
+ continues outward.
5811
5814
 
5812
5815
  Streams belong to one solver run and are shared by nested calls, exceptions,
5813
5816
  and solution collectors. `user_input` and `user_output` are always present.
@@ -6191,7 +6194,11 @@ When another answer exists in an interactive terminal, press `;`, Space, or
6191
6194
  enumeration, `a` enumerates all remaining answers, `f` enumerates the next
6192
6195
  five, and `h` displays the answer-control help. A
6193
6196
  period-terminated query with no solutions prints `false.`; a solution without
6194
- visible variable bindings prints `true.`. Use `[file].` or `['file.pl'].` to
6197
+ visible variable bindings prints `true.`. Answer substitutions are rendered as
6198
+ valid Prolog syntax under the current operator table: when a bound value would
6199
+ not be a valid right operand of the displayed `=/2`, EyeProlog adds parentheses,
6200
+ for example `T = (a = b).` rather than the invalid `T = a = b.`. Use `[file].`
6201
+ or `['file.pl'].` to
6195
6202
  consult local source, and `halt.` or `halt(Status).` to leave the top level.
6196
6203
  Up and Down recall queries from the current session. Explicit `eyeprolog -h`
6197
6204
  displays command-line help.