eyeprolog 1.1.19 → 1.1.21
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/conformance-report.md +2 -2
- package/package.json +1 -1
- package/src/iso.js +14 -3
- package/src/repl.js +28 -3
- package/src/solver.js +14 -0
- package/src/term.js +16 -3
- package/test/conformance/ISO-MATRIX.md +1 -1
- package/test/conformance/README.md +1 -1
- package/test/conformance/cases/iso/throw_copies_ball.pl +17 -0
- package/test/conformance/expected/iso/exceptions_and_flags.pl +1 -1
- package/test/conformance/expected/iso/throw_copies_ball.pl +2 -0
- package/test/run-regression.mjs +60 -0
- package/the-art-of-eyeprolog.md +36 -6
package/conformance-report.md
CHANGED
|
@@ -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 |
|
|
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** | **
|
|
26
|
+
| **Total** | **480** | **260** | **19** | **21** | **780** |
|
package/package.json
CHANGED
package/src/iso.js
CHANGED
|
@@ -48,7 +48,7 @@ export const isoBuiltins = {
|
|
|
48
48
|
registry.add('!', 0, succeed, { deterministic: true });
|
|
49
49
|
|
|
50
50
|
registry.add('=', 2, unification, { deterministic: true });
|
|
51
|
-
registry.add('unify_with_occurs_check', 2,
|
|
51
|
+
registry.add('unify_with_occurs_check', 2, unificationWithOccursCheck, { deterministic: true });
|
|
52
52
|
registry.add('\\=', 2, nonUnification, { deterministic: true });
|
|
53
53
|
registry.add('subsumes_term', 2, subsumesTermBuiltin, { deterministic: true });
|
|
54
54
|
registry.add('==', 2, identity, { deterministic: true });
|
|
@@ -178,6 +178,14 @@ function* unification({ goal, env }) {
|
|
|
178
178
|
const next = env.clone();
|
|
179
179
|
if (unify(goal.args[0], goal.args[1], next)) yield next;
|
|
180
180
|
}
|
|
181
|
+
function* unificationWithOccursCheck({ goal, env }) {
|
|
182
|
+
const next = env.clone();
|
|
183
|
+
// ISO unify_with_occurs_check/2 always performs finite-tree unification.
|
|
184
|
+
// The implementation-specific occurs_check=error mode applies to normal
|
|
185
|
+
// unification, but must not turn this ISO predicate's ordinary failure into
|
|
186
|
+
// an exception.
|
|
187
|
+
if (unify(goal.args[0], goal.args[1], next, { occursCheck: 'fail' })) yield next;
|
|
188
|
+
}
|
|
181
189
|
function* nonUnification({ goal, env }) {
|
|
182
190
|
if (!unify(goal.args[0], goal.args[1], env.clone())) yield env;
|
|
183
191
|
}
|
|
@@ -1776,6 +1784,7 @@ function* phraseBuiltin({ solver, goal, env }) {
|
|
|
1776
1784
|
}
|
|
1777
1785
|
}
|
|
1778
1786
|
function formalErrorTerm(error) {
|
|
1787
|
+
if (error.formalTerm != null) return compound('error', [error.formalTerm, atom('eyeprolog')]);
|
|
1779
1788
|
const parse = (text) => {
|
|
1780
1789
|
const open = text.indexOf('(');
|
|
1781
1790
|
if (open === -1) return atom(text);
|
|
@@ -1814,7 +1823,7 @@ function* catchBuiltin({ solver, goal, env }) {
|
|
|
1814
1823
|
const ball = error instanceof ThrownTerm
|
|
1815
1824
|
? error.term
|
|
1816
1825
|
: error instanceof PrologError
|
|
1817
|
-
? formalErrorTerm(error)
|
|
1826
|
+
? freshCopy(formalErrorTerm(error), new Env())
|
|
1818
1827
|
: null;
|
|
1819
1828
|
if (ball == null) throw error;
|
|
1820
1829
|
const recovered = env.clone();
|
|
@@ -1827,7 +1836,9 @@ function* catchBuiltin({ solver, goal, env }) {
|
|
|
1827
1836
|
function* throwBuiltin({ goal, env }) {
|
|
1828
1837
|
const ball = deref(goal.args[0], env);
|
|
1829
1838
|
if (ball.type === VAR) throw new PrologError('instantiation_error');
|
|
1830
|
-
throw
|
|
1839
|
+
// ISO throw/1 copies the thrown term before control unwinds. Freshen
|
|
1840
|
+
// variables so the catcher cannot retain aliases to the protected goal.
|
|
1841
|
+
throw new ThrownTerm(freshCopy(ball, env));
|
|
1831
1842
|
}
|
|
1832
1843
|
function* onceBuiltin({ solver, goal, env }) {
|
|
1833
1844
|
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' &&
|
|
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,30 @@ 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.formalTerm != null) {
|
|
417
|
+
collectUnboundVariables(engine, error.formalTerm, env, variableNames, () => `_${letterName(generated++)}`);
|
|
418
|
+
const formal = engine.formatTermForWrite(error.formalTerm, env, {
|
|
419
|
+
quoted: true,
|
|
420
|
+
operators: [...state.program.operators.values()],
|
|
421
|
+
variableNames,
|
|
422
|
+
});
|
|
423
|
+
return `error(${formal}).`;
|
|
424
|
+
}
|
|
425
|
+
if (error.culprit != null) {
|
|
426
|
+
collectUnboundVariables(engine, error.culprit, env, variableNames, () => `_${letterName(generated++)}`);
|
|
427
|
+
}
|
|
428
|
+
const culprit = error.culprit == null ? '' : `, ${engine.formatTermForWrite(error.culprit, env, {
|
|
429
|
+
quoted: true,
|
|
430
|
+
operators: [...state.program.operators.values()],
|
|
431
|
+
variableNames,
|
|
432
|
+
})}`;
|
|
433
|
+
return `error(${error.formal}${culprit}).`;
|
|
434
|
+
}
|
|
410
435
|
const message = error?.message ?? String(error);
|
|
411
436
|
return message.endsWith('.') ? message : `${message}.`;
|
|
412
437
|
}
|
package/src/solver.js
CHANGED
|
@@ -23,6 +23,12 @@ export function nextFreshId() {
|
|
|
23
23
|
return ++freshCounter;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
function raiseOccursCheckError(left, right, env) {
|
|
27
|
+
const error = new PrologError('occurs_check');
|
|
28
|
+
error.formalTerm = copyResolved(compound('occurs_check', [left, right]), env);
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
export class Solver {
|
|
27
33
|
constructor(program, options = {}) {
|
|
28
34
|
this.registry = options.registry ?? getEyePrologRegistry();
|
|
@@ -37,6 +43,11 @@ export class Solver {
|
|
|
37
43
|
this.solutionLimit = options.solutionLimit ?? 10000000;
|
|
38
44
|
this.solutionsSeen = 0;
|
|
39
45
|
this.prologFlags = options.prologFlags ?? defaultPrologFlags(this.registry?.eyePrologLibrary ? 'fail' : 'error');
|
|
46
|
+
this.occursCheckHandler = (left, right, env) => {
|
|
47
|
+
if (this.prologFlags.get('occurs_check')?.value?.name === 'error') {
|
|
48
|
+
raiseOccursCheckError(left, right, env);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
40
51
|
this.charConversions = options.charConversions ?? new Map();
|
|
41
52
|
if (!options.prologFlags) {
|
|
42
53
|
if (['chars', 'codes', 'atom'].includes(program.doubleQuotes)) {
|
|
@@ -140,6 +151,7 @@ export class Solver {
|
|
|
140
151
|
|
|
141
152
|
*solve(goals, env = new Env(), depth = 0) {
|
|
142
153
|
if (!Array.isArray(goals)) goals = [goals];
|
|
154
|
+
env.setOccursCheckHandler(this.occursCheckHandler);
|
|
143
155
|
|
|
144
156
|
const savedActive = this.active;
|
|
145
157
|
let registeredStack = null;
|
|
@@ -194,6 +206,7 @@ export class Solver {
|
|
|
194
206
|
|
|
195
207
|
goals = frame.goals;
|
|
196
208
|
env = frame.env;
|
|
209
|
+
env.setOccursCheckHandler(this.occursCheckHandler);
|
|
197
210
|
depth = frame.depth;
|
|
198
211
|
let active = frame.active;
|
|
199
212
|
|
|
@@ -474,6 +487,7 @@ function defaultPrologFlags(unknown = 'error') {
|
|
|
474
487
|
['max_arity', { value: compound('unbounded', []), allowed: ['unbounded'], changeable: false }],
|
|
475
488
|
['unknown', { value: compound(unknown, []), allowed: ['error', 'fail', 'warning'], changeable: true }],
|
|
476
489
|
['double_quotes', { value: compound('chars', []), allowed: ['chars', 'codes', 'atom'], changeable: true }],
|
|
490
|
+
['occurs_check', { value: compound('true', []), allowed: ['true', 'error'], changeable: true }],
|
|
477
491
|
]);
|
|
478
492
|
}
|
|
479
493
|
|
package/src/term.js
CHANGED
|
@@ -47,6 +47,7 @@ export class Env {
|
|
|
47
47
|
};
|
|
48
48
|
this._delays = null;
|
|
49
49
|
this._clpz = null;
|
|
50
|
+
this._occursCheckHandler = null;
|
|
50
51
|
}
|
|
51
52
|
clone() {
|
|
52
53
|
// Most speculative environments are either rejected without a binding or
|
|
@@ -58,8 +59,13 @@ export class Env {
|
|
|
58
59
|
clone._state = this._state;
|
|
59
60
|
clone._delays = this._delays;
|
|
60
61
|
clone._clpz = this._clpz;
|
|
62
|
+
clone._occursCheckHandler = this._occursCheckHandler;
|
|
61
63
|
return clone;
|
|
62
64
|
}
|
|
65
|
+
setOccursCheckHandler(handler) {
|
|
66
|
+
this._occursCheckHandler = typeof handler === 'function' ? handler : null;
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
63
69
|
has(name) {
|
|
64
70
|
return this.get(name) !== undefined;
|
|
65
71
|
}
|
|
@@ -207,11 +213,12 @@ function occurs(variableName, term, env) {
|
|
|
207
213
|
return false;
|
|
208
214
|
}
|
|
209
215
|
|
|
210
|
-
export function unify(left, right, env) {
|
|
216
|
+
export function unify(left, right, env, options = {}) {
|
|
211
217
|
// Iterative unification avoids deep JavaScript recursion on long lists or
|
|
212
218
|
// deeply nested compounds. The occurs check gives EyeProlog finite-tree
|
|
213
219
|
// unification: a variable cannot be bound to a term containing itself.
|
|
214
220
|
// Bindings are written into the supplied Env.
|
|
221
|
+
const occursCheckHandler = options.occursCheck === 'fail' ? null : env?._occursCheckHandler;
|
|
215
222
|
const stack = [[left, right]];
|
|
216
223
|
while (stack.length) {
|
|
217
224
|
let [a, b] = stack.pop();
|
|
@@ -226,12 +233,18 @@ export function unify(left, right, env) {
|
|
|
226
233
|
continue;
|
|
227
234
|
}
|
|
228
235
|
if (a.type === VAR) {
|
|
229
|
-
if (occurs(a.name, b, env))
|
|
236
|
+
if (occurs(a.name, b, env)) {
|
|
237
|
+
occursCheckHandler?.(a, b, env);
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
230
240
|
env.bind(a.name, b);
|
|
231
241
|
continue;
|
|
232
242
|
}
|
|
233
243
|
if (b.type === VAR) {
|
|
234
|
-
if (occurs(b.name, a, env))
|
|
244
|
+
if (occurs(b.name, a, env)) {
|
|
245
|
+
occursCheckHandler?.(b, a, env);
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
235
248
|
env.bind(b.name, a);
|
|
236
249
|
continue;
|
|
237
250
|
}
|
|
@@ -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
|
|
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].
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
answer(7, red, caught(eyeprolog), first, caught(eyeprolog)).
|
|
2
2
|
answer(7, red, caught(eyeprolog), second, caught(eyeprolog)).
|
|
3
|
-
flags(off, on, [pair(bounded, false), pair(integer_rounding_function, toward_zero), pair(char_conversion, on), pair(debug, off), pair(max_integer, unbounded), pair(min_integer, unbounded), pair(max_arity, unbounded), pair(unknown, fail), pair(double_quotes, chars)]).
|
|
3
|
+
flags(off, on, [pair(bounded, false), pair(integer_rounding_function, toward_zero), pair(char_conversion, on), pair(debug, off), pair(max_integer, unbounded), pair(min_integer, unbounded), pair(max_arity, unbounded), pair(unknown, fail), pair(double_quotes, chars), pair(occurs_check, true)]).
|
package/test/run-regression.mjs
CHANGED
|
@@ -496,6 +496,66 @@ 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
|
+
},
|
|
525
|
+
{
|
|
526
|
+
name: 'occurs_check error mode detects STO while ISO occurs-check unification still fails',
|
|
527
|
+
run: () => {
|
|
528
|
+
const filename = path.join(tmp, `occurs-check-${++tmpCounter}.pl`);
|
|
529
|
+
fs.writeFileSync(filename, ':- set_prolog_flag(occurs_check, error).\nsame(X, X).\n');
|
|
530
|
+
const result = runCli([], {
|
|
531
|
+
input:
|
|
532
|
+
'current_prolog_flag(occurs_check, Mode).\n' +
|
|
533
|
+
'set_prolog_flag(occurs_check, error).\n' +
|
|
534
|
+
'X = f(X).\n' +
|
|
535
|
+
'catch((Y = g(Y)), E, true).\n' +
|
|
536
|
+
'unify_with_occurs_check(Z, h(Z)).\n' +
|
|
537
|
+
`[${sourceAtom(filename)}].\n` +
|
|
538
|
+
'same(W, k(W)).\n' +
|
|
539
|
+
'set_prolog_flag(occurs_check, true).\n' +
|
|
540
|
+
'Q = q(Q).\n' +
|
|
541
|
+
'halt.\n',
|
|
542
|
+
});
|
|
543
|
+
assertEqual(result.status, 0, 'exit status');
|
|
544
|
+
assertEqual(result.stdout,
|
|
545
|
+
'?- Mode = true.\n' +
|
|
546
|
+
'?- true.\n' +
|
|
547
|
+
'?- error(occurs_check(_A, f(_A))).\n' +
|
|
548
|
+
'?- E = error(occurs_check(_A, g(_A)), eyeprolog).\n' +
|
|
549
|
+
'?- false.\n' +
|
|
550
|
+
'?- true.\n' +
|
|
551
|
+
'?- error(occurs_check(_A, k(_A))).\n' +
|
|
552
|
+
'?- true.\n' +
|
|
553
|
+
'?- false.\n' +
|
|
554
|
+
'?- ',
|
|
555
|
+
'stdout');
|
|
556
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
557
|
+
},
|
|
558
|
+
},
|
|
499
559
|
{
|
|
500
560
|
name: 'REPL enumerates and stops answers like the Scryer top level',
|
|
501
561
|
run: () => {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -780,6 +780,27 @@ fails rather than constructing a cyclic term:
|
|
|
780
780
|
(X = wrapper(X)).
|
|
781
781
|
```
|
|
782
782
|
|
|
783
|
+
ISO classifies unifications whose outcome depends on an occurs check as
|
|
784
|
+
subject-to-occurs-check (STO). EyeProlog's default remains the sound finite-tree
|
|
785
|
+
behavior above. For diagnosis, EyeProlog additionally provides the
|
|
786
|
+
implementation-specific flag `occurs_check`; setting it to `error` turns a
|
|
787
|
+
normal unification that would otherwise fail because of the occurs check into
|
|
788
|
+
an exception:
|
|
789
|
+
|
|
790
|
+
```eyeprolog
|
|
791
|
+
:- set_prolog_flag(occurs_check, error).
|
|
792
|
+
|
|
793
|
+
sto_example :- X = wrapper(X).
|
|
794
|
+
% error(occurs_check(_A, wrapper(_A)))
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
The supported values are `true` (the default finite-tree behavior) and `error`
|
|
798
|
+
(STO detection). EyeProlog deliberately does not provide `occurs_check=false`,
|
|
799
|
+
because its term model does not construct cyclic terms. The ISO predicate
|
|
800
|
+
`unify_with_occurs_check/2` is independent of the diagnostic flag: it continues
|
|
801
|
+
to perform finite-tree unification and fails on `unify_with_occurs_check(X,
|
|
802
|
+
wrapper(X))` even when `occurs_check` is `error`.
|
|
803
|
+
|
|
783
804
|
### Meaning is not the search strategy
|
|
784
805
|
|
|
785
806
|
EyeProlog's evaluator is goal-directed. It resolves selected goals against facts,
|
|
@@ -5610,11 +5631,14 @@ silently changing a static program.
|
|
|
5610
5631
|
| `max_arity` | `unbounded` | `unbounded` | no |
|
|
5611
5632
|
| `unknown` | `fail` | `error`, `fail`, `warning` | yes |
|
|
5612
5633
|
| `double_quotes` | `chars` | `chars`, `codes`, `atom` | yes |
|
|
5634
|
+
| `occurs_check` | `true` | `true`, `error` | yes |
|
|
5613
5635
|
|
|
5614
5636
|
The isolated ISO-only registry defaults `unknown` to `error`; the normal
|
|
5615
|
-
EyeProlog environment defaults it to `fail`.
|
|
5616
|
-
|
|
5617
|
-
|
|
5637
|
+
EyeProlog environment defaults it to `fail`. The `occurs_check` flag is an
|
|
5638
|
+
EyeProlog diagnostic extension rather than an ISO-defined core flag; its
|
|
5639
|
+
`true` default preserves the engine's finite-tree unification, while `error`
|
|
5640
|
+
reports STO attempts. Operator and flag directives are processed per program
|
|
5641
|
+
rather than changing global JavaScript state. The `double_quotes` setting affects subsequent source text, included
|
|
5618
5642
|
files, command-line and API goal text, and terms read by `read_term/*`:
|
|
5619
5643
|
|
|
5620
5644
|
```text
|
|
@@ -5749,8 +5773,14 @@ instances whose message contains the corresponding Prolog error term.
|
|
|
5749
5773
|
| *syntax_error(number)*, *syntax_error(read_term)* | Lexical number conversion or streamed term parsing failed. |
|
|
5750
5774
|
|
|
5751
5775
|
`catch/3` converts a `PrologError` into a catchable
|
|
5752
|
-
`error(Formal,eyeprolog)` term.
|
|
5753
|
-
|
|
5776
|
+
`error(Formal,eyeprolog)` term. `throw/1` copies its ball before unwinding: bound
|
|
5777
|
+
parts are preserved, repeated variables remain shared within the copied ball,
|
|
5778
|
+
and unbound variables are fresh with respect to the protected goal and catcher.
|
|
5779
|
+
Catchable error terms follow the same variable-freshening rule. At the interactive
|
|
5780
|
+
top level, variables that occur in an uncaught ISO error are rendered with fresh
|
|
5781
|
+
answer names such as `_A` rather than reusing query-variable spellings such as
|
|
5782
|
+
`X` or `Xx`; this keeps the displayed error consistent with the copied exception
|
|
5783
|
+
term. An unmatched ball or error continues outward.
|
|
5754
5784
|
|
|
5755
5785
|
Streams belong to one solver run and are shared by nested calls, exceptions,
|
|
5756
5786
|
and solution collectors. `user_input` and `user_output` are always present.
|
|
@@ -6815,7 +6845,7 @@ node test/run-conformance-report.mjs
|
|
|
6815
6845
|
```
|
|
6816
6846
|
|
|
6817
6847
|
The complete suite must pass before release. The file-based conformance corpus
|
|
6818
|
-
contains
|
|
6848
|
+
contains 780 cases, including 374 focused ISO
|
|
6819
6849
|
cases derived from the success, failure, mode, and error behavior in
|
|
6820
6850
|
ISO/IEC 13211-1 clauses 7 and 8, Part 2 modules, and Part 3 grammar rules.
|
|
6821
6851
|
Separate exact-output suites check 189 normal
|