eyeprolog 1.1.19 → 1.1.20

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.
@@ -11,7 +11,7 @@ This report summarizes the file-based conformance corpus under `test/conformance
11
11
  | builtins | 11 | 0 | 0 | 0 | 11 |
12
12
  | context | 11 | 0 | 0 | 0 | 11 |
13
13
  | control | 15 | 0 | 0 | 0 | 15 |
14
- | iso | 164 | 209 | 0 | 0 | 373 |
14
+ | iso | 165 | 209 | 0 | 0 | 374 |
15
15
  | lists | 52 | 3 | 0 | 0 | 55 |
16
16
  | modules | 2 | 0 | 0 | 0 | 2 |
17
17
  | negation | 8 | 0 | 19 | 0 | 27 |
@@ -23,4 +23,4 @@ This report summarizes the file-based conformance corpus under `test/conformance
23
23
  | terms | 26 | 3 | 0 | 0 | 29 |
24
24
  | unification | 18 | 0 | 0 | 0 | 18 |
25
25
  | variables | 16 | 9 | 0 | 0 | 25 |
26
- | **Total** | **479** | **260** | **19** | **21** | **779** |
26
+ | **Total** | **480** | **260** | **19** | **21** | **780** |
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.1.19",
6
+ "version": "1.1.20",
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
@@ -1814,7 +1814,7 @@ function* catchBuiltin({ solver, goal, env }) {
1814
1814
  const ball = error instanceof ThrownTerm
1815
1815
  ? error.term
1816
1816
  : error instanceof PrologError
1817
- ? formalErrorTerm(error)
1817
+ ? freshCopy(formalErrorTerm(error), new Env())
1818
1818
  : null;
1819
1819
  if (ball == null) throw error;
1820
1820
  const recovered = env.clone();
@@ -1827,7 +1827,9 @@ function* catchBuiltin({ solver, goal, env }) {
1827
1827
  function* throwBuiltin({ goal, env }) {
1828
1828
  const ball = deref(goal.args[0], env);
1829
1829
  if (ball.type === VAR) throw new PrologError('instantiation_error');
1830
- throw new ThrownTerm(copyResolved(ball, env));
1830
+ // ISO throw/1 copies the thrown term before control unwinds. Freshen
1831
+ // variables so the catcher cannot retain aliases to the protected goal.
1832
+ throw new ThrownTerm(freshCopy(ball, env));
1831
1833
  }
1832
1834
  function* onceBuiltin({ solver, goal, env }) {
1833
1835
  const child = solver.cloneForInnerGoal(1);
package/src/repl.js CHANGED
@@ -57,7 +57,7 @@ export async function runRepl(engine, options = {}) {
57
57
  exitCode = error.code;
58
58
  break;
59
59
  }
60
- output.write(` ${formatError(error)}\n`);
60
+ output.write(` ${formatError(engine, state, error)}\n`);
61
61
  }
62
62
  }
63
63
  } catch (error) {
@@ -368,13 +368,15 @@ function queryVariables(goal) {
368
368
 
369
369
  function formatAnswer(engine, state, variables, env) {
370
370
  const bindings = [];
371
+ const queryVariableNames = new Set(variables.map((variable) => variable.name));
371
372
  const names = new Map(variables.map((variable) => [variable.name, variable.name]));
372
373
  let generated = 0;
373
374
 
374
375
  for (const variable of variables) collectUnboundVariables(engine, variable, env, names, () => `_${letterName(generated++)}`);
375
376
  for (const variable of variables) {
376
377
  const value = engine.deref(variable, env);
377
- if (value.type === 'var' && value.name === variable.name) continue;
378
+ if (value.type === 'var' &&
379
+ (value.name === variable.name || !queryVariableNames.has(value.name))) continue;
378
380
  bindings.push(`${variable.name} = ${engine.formatTermForWrite(value, env, {
379
381
  quoted: true,
380
382
  operators: [...state.program.operators.values()],
@@ -406,7 +408,21 @@ function letterName(index) {
406
408
  return suffix === 0 ? letter : `${letter}${suffix}`;
407
409
  }
408
410
 
409
- function formatError(error) {
411
+ function formatError(engine, state, error) {
412
+ if (error?.name === 'PrologError') {
413
+ const env = new engine.Env();
414
+ const variableNames = new Map();
415
+ let generated = 0;
416
+ if (error.culprit != null) {
417
+ collectUnboundVariables(engine, error.culprit, env, variableNames, () => `_${letterName(generated++)}`);
418
+ }
419
+ const culprit = error.culprit == null ? '' : `, ${engine.formatTermForWrite(error.culprit, env, {
420
+ quoted: true,
421
+ operators: [...state.program.operators.values()],
422
+ variableNames,
423
+ })}`;
424
+ return `error(${error.formal}${culprit}).`;
425
+ }
410
426
  const message = error?.message ?? String(error);
411
427
  return message.endsWith('.') ? message : `${message}.`;
412
428
  }
@@ -9,7 +9,7 @@ case totals.
9
9
  | --- | --- | --- |
10
10
  | Clause 6 lexical and term syntax | tokenizer, operator parser, lists, curly terms, quotes, numeric syntax, comments | `scryer_lexical_terms`, `lexical_and_curly_terms`, `double_quoted_lists`, `corrigendum1_double_quote_operator`, syntax error cases |
11
11
  | Clause 7 term order and unification | finite-tree unification, identity, standard order, errors | `unification_control_information`, `swipl_occurs_check`, `term_modes_and_ordering`, `logtalk_compare_standard_order` |
12
- | Clause 7 control and exceptions | call, cut, conjunction, disjunction, if-then-else, catch and throw | `cut_control`, `control_and_terms`, `exceptions_and_flags`, `corrigenda_catch_callability` |
12
+ | Clause 7 control and exceptions | call, cut, conjunction, disjunction, if-then-else, catch and throw | `cut_control`, `control_and_terms`, `exceptions_and_flags`, `corrigenda_catch_callability`, `throw_copies_ball` |
13
13
  | 8.2-8.5 term predicates | unification, Corrigendum 2 tests, comparison, sorting, creation and decomposition | `corrigenda_term_predicates`, `corrigenda_sort_keysort`, `logtalk_arg_unification`, `logtalk_univ`, associated error cases |
14
14
  | 8.6-8.7 arithmetic predicates | `is/2` and all six arithmetic comparisons | `arithmetic`, comparison cases, isolated evaluation errors |
15
15
  | 8.8-8.10 database and solutions | logical update view, dynamic mutation, all-solutions grouping | `dynamic_database`, `trealla_logical_update_view`, `corrigenda_retractall`, `grouped_solutions_and_clauses` |
@@ -94,7 +94,7 @@ Selected cases are adapted from the ISO and standard-core suites of Logtalk,
94
94
  Scryer Prolog, Trealla Prolog, and SWI-Prolog. Their upstream identifiers and licenses
95
95
  are recorded in [THIRD_PARTY.md](THIRD_PARTY.md).
96
96
 
97
- The corpus has 373 cases in `iso/` and 779 file-based conformance cases in
97
+ The corpus has 374 cases in `iso/` and 780 file-based conformance cases in
98
98
  total. The generated `conformance-report.md` is the authoritative source for
99
99
  current category totals. Together with regression, documentation-sync, API,
100
100
  example, and book-example checks, `npm test` is the release gate.
@@ -0,0 +1,17 @@
1
+ % ISO 7.12.1: throw/1 copies the ball before searching for a catcher.
2
+ %% goal: user_throw_copy
3
+ %% goal: error_throw_copy
4
+
5
+ user_throw_copy :-
6
+ catch(throw(g(X)), g(V), true),
7
+ X = protected,
8
+ V = caught.
9
+
10
+ error_throw_copy :-
11
+ catch(
12
+ number_chars(_, [1, [], X|2]),
13
+ error(type_error(list, Culprit), _),
14
+ true
15
+ ),
16
+ X = protected,
17
+ Culprit = [1, [], caught|2].
@@ -0,0 +1,2 @@
1
+ user_throw_copy.
2
+ error_throw_copy.
@@ -496,6 +496,32 @@ c4 ?- call((!;1)).
496
496
  assertEqual(result.stderr, '', 'stderr');
497
497
  },
498
498
  },
499
+ {
500
+ name: 'REPL hides aliases to fresh throw variables while preserving query aliases',
501
+ run: () => {
502
+ const result = runCli([], {
503
+ input: 'catch(throw(g(X)),g(V),true).\nX = Y.\nhalt.\n',
504
+ });
505
+ assertEqual(result.status, 0, 'exit status');
506
+ assertEqual(result.stdout, '?- true.\n?- X = Y.\n?- ', 'stdout');
507
+ assertEqual(result.stderr, '', 'stderr');
508
+ },
509
+ },
510
+ {
511
+ name: 'REPL freshens variables displayed in uncaught ISO errors',
512
+ run: () => {
513
+ const result = runCli([], {
514
+ input: 'number_chars(V,[1,[],X|2]).\nnumber_chars(V,[1,[],Xx|2]).\nhalt.\n',
515
+ });
516
+ assertEqual(result.status, 0, 'exit status');
517
+ assertEqual(result.stdout,
518
+ '?- error(type_error(list), [1, [], _A | 2]).\n' +
519
+ '?- error(type_error(list), [1, [], _A | 2]).\n' +
520
+ '?- ',
521
+ 'stdout');
522
+ assertEqual(result.stderr, '', 'stderr');
523
+ },
524
+ },
499
525
  {
500
526
  name: 'REPL enumerates and stops answers like the Scryer top level',
501
527
  run: () => {
@@ -5749,8 +5749,14 @@ instances whose message contains the corresponding Prolog error term.
5749
5749
  | *syntax_error(number)*, *syntax_error(read_term)* | Lexical number conversion or streamed term parsing failed. |
5750
5750
 
5751
5751
  `catch/3` converts a `PrologError` into a catchable
5752
- `error(Formal,eyeprolog)` term. A user ball from `throw/1` is delivered as-is.
5753
- An unmatched ball or error continues outward.
5752
+ `error(Formal,eyeprolog)` term. `throw/1` copies its ball before unwinding: bound
5753
+ parts are preserved, repeated variables remain shared within the copied ball,
5754
+ and unbound variables are fresh with respect to the protected goal and catcher.
5755
+ Catchable error terms follow the same variable-freshening rule. At the interactive
5756
+ top level, variables that occur in an uncaught ISO error are rendered with fresh
5757
+ answer names such as `_A` rather than reusing query-variable spellings such as
5758
+ `X` or `Xx`; this keeps the displayed error consistent with the copied exception
5759
+ term. An unmatched ball or error continues outward.
5754
5760
 
5755
5761
  Streams belong to one solver run and are shared by nested calls, exceptions,
5756
5762
  and solution collectors. `user_input` and `user_output` are always present.
@@ -6815,7 +6821,7 @@ node test/run-conformance-report.mjs
6815
6821
  ```
6816
6822
 
6817
6823
  The complete suite must pass before release. The file-based conformance corpus
6818
- contains 779 cases, including 373 focused ISO
6824
+ contains 780 cases, including 374 focused ISO
6819
6825
  cases derived from the success, failure, mode, and error behavior in
6820
6826
  ISO/IEC 13211-1 clauses 7 and 8, Part 2 modules, and Part 3 grammar rules.
6821
6827
  Separate exact-output suites check 189 normal