eyeprolog 1.3.71 → 1.3.72
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/README.md +5 -2
- package/examples/dif-constraints.pl +2 -2
- package/package.json +1 -1
- package/src/iso.js +74 -0
- package/src/lib/atts.pl +6 -1
- package/src/repl.js +7 -7
- package/src/standard-library.js +4 -1
- package/src/term.js +22 -0
- package/test/run-regression.mjs +73 -6
- package/the-art-of-eyeprolog.md +1 -1
package/README.md
CHANGED
|
@@ -151,7 +151,9 @@ top level includes pending `dif/2` goals in residual answers. See
|
|
|
151
151
|
|
|
152
152
|
`library(atts)` exposes Scryer-style attributed-variable operations on the same
|
|
153
153
|
persistent `Env` machinery used by `dif/2`: `put_atts/2`, `get_atts/2`,
|
|
154
|
-
`put_attr/3`, `get_attr/3`, `del_attr/2`,
|
|
154
|
+
`put_attr/3`, `get_attr/3`, `del_attr/2`, `term_attributed_variables/2`, and
|
|
155
|
+
`call_residue_vars/2`. The latter reports variables whose residual attributes or
|
|
156
|
+
constraints were created or modified by a goal, including native `dif/2` residues.
|
|
155
157
|
`:- attribute ...` declarations are accepted, `verify_attributes/3` runs before
|
|
156
158
|
an attributed binding is committed, and the hook's returned goals run after
|
|
157
159
|
that binding. Attribute state follows variable representatives and ordinary
|
|
@@ -359,7 +361,8 @@ lambda syntax also changes the active operator table.
|
|
|
359
361
|
Outside `--iso-strict`, an otherwise undefined unqualified call may autoload a
|
|
360
362
|
predicate only when the interop profile has one canonical EyeProlog provider.
|
|
361
363
|
For example, `member/2` autoloads from `library(lists)`, `call_nth/2` from
|
|
362
|
-
`library(iso_ext)`, and `between/3`
|
|
364
|
+
`library(iso_ext)`, `call_residue_vars/2` from `library(atts)`, and `between/3`
|
|
365
|
+
from EyeProlog's internal
|
|
363
366
|
`library(prologue)` implementation. Use `--no-autoload` to disable this
|
|
364
367
|
convenience. Strict ISO mode never autoloads library predicates.
|
|
365
368
|
|
|
@@ -11,8 +11,8 @@ allowed_pair(X, Y) :-
|
|
|
11
11
|
X = left,
|
|
12
12
|
Y = right.
|
|
13
13
|
|
|
14
|
-
%
|
|
15
|
-
%
|
|
14
|
+
% X=Y already proves X-Y and 1-2 different: X-X cannot unify with 1-2,
|
|
15
|
+
% so the dif/2 constraint is discharged before the final specialization Y=1.
|
|
16
16
|
specialization(X, Y) :-
|
|
17
17
|
dif(X-Y, 1-2),
|
|
18
18
|
X = Y,
|
package/package.json
CHANGED
package/src/iso.js
CHANGED
|
@@ -165,6 +165,7 @@ export const isoBuiltins = {
|
|
|
165
165
|
export const eyePrologLibraryBuiltins = {
|
|
166
166
|
register(registry) {
|
|
167
167
|
registry.add('eyeprolog__call_nth', 2, callNthBuiltin, { eyePrologLibrary: true });
|
|
168
|
+
registry.add('eyeprolog__call_residue_vars', 2, callResidueVarsBuiltin, { eyePrologLibrary: true });
|
|
168
169
|
registry.add('eyeprolog__countall', 2, countAllBuiltin, { eyePrologLibrary: true });
|
|
169
170
|
registry.add('eyeprolog__freeze', 2, freezeBuiltin, { eyePrologLibrary: true });
|
|
170
171
|
registry.add('dif', 2, difBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
@@ -2497,6 +2498,79 @@ function* callNthBuiltin({ solver, goal, env }) {
|
|
|
2497
2498
|
}
|
|
2498
2499
|
}
|
|
2499
2500
|
|
|
2501
|
+
function residueSnapshot(env) {
|
|
2502
|
+
const attributes = new Map();
|
|
2503
|
+
for (const name of env.attributedVariableNames?.() ?? []) {
|
|
2504
|
+
attributes.set(name, env.prologAttributes(name));
|
|
2505
|
+
}
|
|
2506
|
+
const delays = new Map();
|
|
2507
|
+
for (const name of env.delayedVariableNames?.() ?? []) {
|
|
2508
|
+
delays.set(name, env.delayedGoals(name));
|
|
2509
|
+
}
|
|
2510
|
+
const constraints = new Map();
|
|
2511
|
+
for (const constraint of env.variableConstraints?.() ?? []) {
|
|
2512
|
+
constraints.set(constraint, [...constraint.variables(env)]);
|
|
2513
|
+
}
|
|
2514
|
+
return { attributes, delays, constraints };
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
function sameAttributeEntries(before, after) {
|
|
2518
|
+
if (before == null || before.length !== after.length) return false;
|
|
2519
|
+
for (let i = 0; i < after.length; i++) {
|
|
2520
|
+
if (before[i].module !== after[i].module || before[i].attribute !== after[i].attribute) return false;
|
|
2521
|
+
}
|
|
2522
|
+
return true;
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
function sameDelayEntries(before, after) {
|
|
2526
|
+
if (before == null || before.length !== after.length) return false;
|
|
2527
|
+
for (let i = 0; i < after.length; i++) if (before[i] !== after[i]) return false;
|
|
2528
|
+
return true;
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
function residueVariablesSince(snapshot, env) {
|
|
2532
|
+
const names = [];
|
|
2533
|
+
const seen = new Set();
|
|
2534
|
+
const add = (name) => {
|
|
2535
|
+
const root = deref(variable(name), env);
|
|
2536
|
+
if (root.type !== VAR || seen.has(root.name)) return;
|
|
2537
|
+
seen.add(root.name);
|
|
2538
|
+
names.push(root);
|
|
2539
|
+
};
|
|
2540
|
+
|
|
2541
|
+
for (const name of env.attributedVariableNames?.() ?? []) {
|
|
2542
|
+
const after = env.prologAttributes(name);
|
|
2543
|
+
if (!sameAttributeEntries(snapshot.attributes.get(name), after)) add(name);
|
|
2544
|
+
}
|
|
2545
|
+
for (const name of env.delayedVariableNames?.() ?? []) {
|
|
2546
|
+
const after = env.delayedGoals(name);
|
|
2547
|
+
if (!sameDelayEntries(snapshot.delays.get(name), after)) add(name);
|
|
2548
|
+
}
|
|
2549
|
+
for (const constraint of env.variableConstraints?.() ?? []) {
|
|
2550
|
+
const after = [...constraint.variables(env)];
|
|
2551
|
+
const before = snapshot.constraints.get(constraint);
|
|
2552
|
+
const unchanged = before != null && before.length === after.length && before.every((name, index) => name === after[index]);
|
|
2553
|
+
if (unchanged) continue;
|
|
2554
|
+
for (const name of after) add(name);
|
|
2555
|
+
}
|
|
2556
|
+
return names;
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
function* callResidueVarsBuiltin({ solver, goal, env }) {
|
|
2560
|
+
const invoked = callable(goal.args[0], env);
|
|
2561
|
+
if (invoked.module == null) invoked.module = goal.module ?? 'user';
|
|
2562
|
+
const snapshot = residueSnapshot(env);
|
|
2563
|
+
const child = solver.cloneForInnerGoal();
|
|
2564
|
+
try {
|
|
2565
|
+
for (const answerEnv of child.solve([invoked], env, 0)) {
|
|
2566
|
+
const next = answerEnv.clone();
|
|
2567
|
+
if (unify(goal.args[1], listFromItems(residueVariablesSince(snapshot, answerEnv)), next)) yield next;
|
|
2568
|
+
}
|
|
2569
|
+
} finally {
|
|
2570
|
+
solver.absorbStatsFrom(child);
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2500
2574
|
function* freezeBuiltin({ solver, goal, env }) {
|
|
2501
2575
|
const watched = deref(goal.args[0], env);
|
|
2502
2576
|
if (watched.type !== VAR) {
|
package/src/lib/atts.pl
CHANGED
|
@@ -12,9 +12,14 @@
|
|
|
12
12
|
put_attr/3,
|
|
13
13
|
get_attr/3,
|
|
14
14
|
del_attr/2,
|
|
15
|
-
term_attributed_variables/2
|
|
15
|
+
term_attributed_variables/2,
|
|
16
|
+
call_residue_vars/2
|
|
16
17
|
]).
|
|
17
18
|
:- op(1199, fx, attribute).
|
|
19
|
+
:- meta_predicate(call_residue_vars(0, '?')).
|
|
18
20
|
|
|
19
21
|
term_attributed_variables(Term, Variables) :-
|
|
20
22
|
'$term_attributed_variables'(Term, Variables).
|
|
23
|
+
|
|
24
|
+
call_residue_vars(Goal, Variables) :-
|
|
25
|
+
eyeprolog__call_residue_vars(Goal, Variables).
|
package/src/repl.js
CHANGED
|
@@ -672,18 +672,18 @@ async function solveQuery(engine, state, goal, reader, output) {
|
|
|
672
672
|
}
|
|
673
673
|
|
|
674
674
|
function pullSolution(solver, solutions, reader) {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
675
|
+
// Prolog output is an observable side effect of execution, so never hold it
|
|
676
|
+
// until the search reaches its next leaf. In particular an infinite search
|
|
677
|
+
// that periodically writes progress must remain observable at the terminal.
|
|
678
|
+
// Completed queries keep the same byte order because user_output writes are
|
|
679
|
+
// synchronous and occur before solutions.next() returns its answer.
|
|
679
680
|
const suspended = reader.suspendForComputation();
|
|
680
681
|
try {
|
|
681
|
-
return { result: solutions.next(), output:
|
|
682
|
+
return { result: solutions.next(), output: '' };
|
|
682
683
|
} catch (error) {
|
|
683
|
-
return { error, output:
|
|
684
|
+
return { error, output: '' };
|
|
684
685
|
} finally {
|
|
685
686
|
reader.resumeAfterComputation(suspended);
|
|
686
|
-
if (stream) stream.write = originalWrite;
|
|
687
687
|
}
|
|
688
688
|
}
|
|
689
689
|
|
package/src/standard-library.js
CHANGED
|
@@ -63,7 +63,7 @@ function libraryUrl(filename) {
|
|
|
63
63
|
|
|
64
64
|
export const eyePrologNativeLibraryIndicators = Object.freeze([
|
|
65
65
|
'call_nth/2', 'freeze/2', 'dif/2', 'countall/2', 'time/1',
|
|
66
|
-
'put_atts/2', 'get_atts/2', 'put_attr/3', 'get_attr/3', 'del_attr/2', 'term_attributed_variables/2',
|
|
66
|
+
'put_atts/2', 'get_atts/2', 'put_attr/3', 'get_attr/3', 'del_attr/2', 'term_attributed_variables/2', 'call_residue_vars/2',
|
|
67
67
|
'#>/2', '#</2', '#>=/2', '#=</2', '#=/2', '#\\=/2', '#\\/1',
|
|
68
68
|
'#<==>/2', '#==>/2', '#<==/2', '#\\//2', '#\\/2', '#/\\/2',
|
|
69
69
|
'in/2', 'ins/2', 'all_different/1', 'all_distinct/1', 'nvalue/2', 'sum/3',
|
|
@@ -133,6 +133,9 @@ export const eyePrologInteropAutoload = Object.freeze({
|
|
|
133
133
|
// it from library(iso_ext), matching the explicit Scryer import while still
|
|
134
134
|
// allowing Trealla-style unqualified source to use the same autoload entry.
|
|
135
135
|
'call_nth/2': 'iso_ext',
|
|
136
|
+
// Scryer exposes call_residue_vars/2 from library(atts); autoload it for
|
|
137
|
+
// constraint-test source that uses the predicate without an explicit import.
|
|
138
|
+
'call_residue_vars/2': 'atts',
|
|
136
139
|
// Trealla exposes time/1 as a meta timing predicate and library(iso_ext)
|
|
137
140
|
// supplies ... //0. Autoload both so UWN's DCG hand-off benchmark runs
|
|
138
141
|
// unchanged while their implementations remain outside the ISO core.
|
package/src/term.js
CHANGED
|
@@ -268,6 +268,28 @@ export class Env {
|
|
|
268
268
|
delays.set(name, [...(delays.get(name) ?? []), { goal, module }]);
|
|
269
269
|
this._delays = delays;
|
|
270
270
|
}
|
|
271
|
+
delayedVariableNames() {
|
|
272
|
+
if (this._delays == null || this._delays.size === 0) return [];
|
|
273
|
+
const names = [];
|
|
274
|
+
const seen = new Set();
|
|
275
|
+
for (const name of this._delays.keys()) {
|
|
276
|
+
const root = deref(variable(name), this);
|
|
277
|
+
if (root.type !== VAR || seen.has(root.name)) continue;
|
|
278
|
+
seen.add(root.name);
|
|
279
|
+
names.push(root.name);
|
|
280
|
+
}
|
|
281
|
+
return names;
|
|
282
|
+
}
|
|
283
|
+
delayedGoals(name) {
|
|
284
|
+
const root = deref(variable(name), this);
|
|
285
|
+
if (root.type !== VAR || this._delays == null) return [];
|
|
286
|
+
const result = [];
|
|
287
|
+
for (const [source, goals] of this._delays) {
|
|
288
|
+
const current = deref(variable(source), this);
|
|
289
|
+
if (current.type === VAR && current.name === root.name) result.push(...goals);
|
|
290
|
+
}
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
271
293
|
takeReadyDelays() {
|
|
272
294
|
if (this._delays == null || this._delays.size === 0) return [];
|
|
273
295
|
const ready = [];
|
package/test/run-regression.mjs
CHANGED
|
@@ -132,7 +132,7 @@ function regressionCases() {
|
|
|
132
132
|
{
|
|
133
133
|
name: 'library(atts) provides Scryer-style attributed-variable hooks',
|
|
134
134
|
run: () => {
|
|
135
|
-
const source = `:- module(attr_probe, [check_get/0, check_alias/0, check_ok/0, check_bad/0, check_conflict/0, check_backtrack/0, check_term_vars/0]).
|
|
135
|
+
const source = `:- module(attr_probe, [check_get/0, check_alias/0, check_ok/0, check_bad/0, check_conflict/0, check_backtrack/0, check_term_vars/0, check_residue_new/0, check_residue_unchanged/0, check_residue_modified/0, check_residue_dif/0]).
|
|
136
136
|
:- use_module(library(atts)).
|
|
137
137
|
:- attribute mark/1.
|
|
138
138
|
attach(X, V) :- put_atts(X, mark(V)).
|
|
@@ -151,6 +151,10 @@ check_bad :- attach(X, 3), X = 4.
|
|
|
151
151
|
check_conflict :- attach(X, a), attach(Y, b), X = Y.
|
|
152
152
|
check_backtrack :- (attach(X, a), fail ; true), \\+ get_atts(X, mark(_)).
|
|
153
153
|
check_term_vars :- attach(X, a), term_attributed_variables(pair(X,Y), [X]), var(Y), X = a.
|
|
154
|
+
check_residue_new :- call_residue_vars(attach(X, a), Vs), Vs = [X].
|
|
155
|
+
check_residue_unchanged :- attach(X, a), call_residue_vars(true, Vs), Vs = [], X = a.
|
|
156
|
+
check_residue_modified :- attach(X, a), call_residue_vars(put_atts(X, mark(b)), Vs), Vs = [X], X = b.
|
|
157
|
+
check_residue_dif :- call_residue_vars(dif(X,Y), Vs), Vs = [X,Y].
|
|
154
158
|
`;
|
|
155
159
|
assertIncludes(run(source, { goal: 'attr_probe:check_get' }).stdout, 'attr_probe:check_get.', 'get_atts/2');
|
|
156
160
|
assertIncludes(run(source, { goal: 'attr_probe:check_alias' }).stdout, 'attr_probe:check_alias.', 'attribute transfer on aliasing');
|
|
@@ -159,6 +163,20 @@ check_term_vars :- attach(X, a), term_attributed_variables(pair(X,Y), [X]), var(
|
|
|
159
163
|
assertEqual(run(source, { goal: 'attr_probe:check_conflict' }).stdout, '', 'same-module attributes are verified before aliasing');
|
|
160
164
|
assertIncludes(run(source, { goal: 'attr_probe:check_backtrack' }).stdout, 'attr_probe:check_backtrack.', 'attributes backtrack with Env branches');
|
|
161
165
|
assertIncludes(run(source, { goal: 'attr_probe:check_term_vars' }).stdout, 'attr_probe:check_term_vars.', 'term_attributed_variables/2');
|
|
166
|
+
assertIncludes(run(source, { goal: 'attr_probe:check_residue_new' }).stdout, 'attr_probe:check_residue_new.', 'call_residue_vars/2 reports newly attributed variables');
|
|
167
|
+
assertIncludes(run(source, { goal: 'attr_probe:check_residue_unchanged' }).stdout, 'attr_probe:check_residue_unchanged.', 'call_residue_vars/2 ignores unchanged pre-existing attributes');
|
|
168
|
+
assertIncludes(run(source, { goal: 'attr_probe:check_residue_modified' }).stdout, 'attr_probe:check_residue_modified.', 'call_residue_vars/2 reports modified attributes');
|
|
169
|
+
assertIncludes(run(source, { goal: 'attr_probe:check_residue_dif' }).stdout, 'attr_probe:check_residue_dif.', 'call_residue_vars/2 includes native dif/2 residual variables');
|
|
170
|
+
const freezeResidue = `:- use_module(library(atts)).
|
|
171
|
+
:- use_module(library(prologue), [freeze/2]).
|
|
172
|
+
check :- call_residue_vars(freeze(X,true), Vs), Vs = [X].
|
|
173
|
+
`;
|
|
174
|
+
assertIncludes(run(freezeResidue, { goal: 'check' }).stdout, 'check.', 'call_residue_vars/2 includes delayed freeze/2 variables');
|
|
175
|
+
const clpzResidue = `:- use_module(library(atts)).
|
|
176
|
+
:- use_module(library(clpz)).
|
|
177
|
+
check :- call_residue_vars(X in 1..3, Vs), Vs = [X].
|
|
178
|
+
`;
|
|
179
|
+
assertIncludes(run(clpzResidue, { goal: 'check' }).stdout, 'check.', 'call_residue_vars/2 includes CLP(Z) attributed variables');
|
|
162
180
|
|
|
163
181
|
const residualFile = path.join(tmp, `atts-residual-${++tmpCounter}.pl`);
|
|
164
182
|
fs.writeFileSync(residualFile, `:- use_module(library(atts)).
|
|
@@ -2090,6 +2108,53 @@ c4 ?- call((!;1)).
|
|
|
2090
2108
|
assertEqual(advanced.stderr, '', 'advanced stderr');
|
|
2091
2109
|
},
|
|
2092
2110
|
},
|
|
2111
|
+
{
|
|
2112
|
+
name: 'REPL exposes output side effects while search is still running (issue #72)',
|
|
2113
|
+
run: () => {
|
|
2114
|
+
const helper = `
|
|
2115
|
+
import { spawn } from 'node:child_process';
|
|
2116
|
+
|
|
2117
|
+
const child = spawn(${JSON.stringify(process.execPath)}, [${JSON.stringify(bin)}], {
|
|
2118
|
+
cwd: ${JSON.stringify(packageRoot)},
|
|
2119
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
2120
|
+
});
|
|
2121
|
+
child.stdout.setEncoding('utf8');
|
|
2122
|
+
child.stderr.setEncoding('utf8');
|
|
2123
|
+
let stdout = '';
|
|
2124
|
+
let stderr = '';
|
|
2125
|
+
child.stdout.on('data', (text) => { stdout += text; });
|
|
2126
|
+
child.stderr.on('data', (text) => { stderr += text; });
|
|
2127
|
+
|
|
2128
|
+
async function waitFor(predicate, label) {
|
|
2129
|
+
const deadline = Date.now() + 5000;
|
|
2130
|
+
while (!predicate()) {
|
|
2131
|
+
if (Date.now() >= deadline) {
|
|
2132
|
+
child.kill('SIGKILL');
|
|
2133
|
+
throw new Error(label + ' timeout; stdout=' + JSON.stringify(stdout) + '; stderr=' + JSON.stringify(stderr));
|
|
2134
|
+
}
|
|
2135
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
child.stdin.write('use_module(library(iso_ext)).\\n');
|
|
2140
|
+
await waitFor(() => stdout.includes(' true.\\n?- '), 'module import');
|
|
2141
|
+
child.stdin.write('call_nth(repeat,Nth), (Nth mod 2=:=0->writeq(r(Nth)),nl;true),Nth<0.\\n');
|
|
2142
|
+
await waitFor(() => stdout.includes('r(2)\\nr(4)\\n'), 'progress output');
|
|
2143
|
+
if (stdout.includes('false.')) throw new Error('infinite search unexpectedly reached a leaf');
|
|
2144
|
+
child.kill('SIGKILL');
|
|
2145
|
+
await new Promise((resolve) => child.once('exit', resolve));
|
|
2146
|
+
process.stdout.write('progress-visible');
|
|
2147
|
+
`;
|
|
2148
|
+
const result = spawnSync(process.execPath, [
|
|
2149
|
+
'--input-type=module',
|
|
2150
|
+
'--eval',
|
|
2151
|
+
helper,
|
|
2152
|
+
], { cwd: packageRoot, encoding: 'utf8', timeout: 10000 });
|
|
2153
|
+
if (result.error) throw result.error;
|
|
2154
|
+
assertEqual(result.status, 0, `progress helper status; stderr=${result.stderr}`);
|
|
2155
|
+
assertEqual(result.stdout, 'progress-visible', 'newline-terminated progress is visible before a leaf answer');
|
|
2156
|
+
},
|
|
2157
|
+
},
|
|
2093
2158
|
{
|
|
2094
2159
|
name: 'REPL bindings use argument syntax for operator atoms',
|
|
2095
2160
|
run: () => {
|
|
@@ -4939,7 +5004,7 @@ answer(ok) :-
|
|
|
4939
5004
|
assertEqual(Boolean(registry.get('is', 2)), true, 'ISO is/2 exists');
|
|
4940
5005
|
assertEqual(Boolean(registry.get('append', 3)), false, 'append/3 is not ISO core');
|
|
4941
5006
|
assertEqual(library.eyePrologLibrary, true, 'complete registry marker');
|
|
4942
|
-
assertEqual(library.defs.size,
|
|
5007
|
+
assertEqual(library.defs.size, 155, 'EyeProlog registry contains ISO definitions, cleanup controls, observability extensions, WFS tnot/1, and generic library adapters');
|
|
4943
5008
|
assertEqual(Boolean(registry.get('phrase', 2)), true, 'Part 3 phrase/2 exists');
|
|
4944
5009
|
assertEqual(Boolean(registry.get('phrase', 3)), true, 'Part 3 phrase/3 exists');
|
|
4945
5010
|
assertEqual(registry.get('statistics', 0), null, 'statistics/0 is absent from the ISO registry');
|
|
@@ -4956,21 +5021,23 @@ answer(ok) :-
|
|
|
4956
5021
|
assertEqual(Boolean(library.get('call_cleanup', 2)), true, 'call_cleanup/2 is an EyeProlog cleanup control');
|
|
4957
5022
|
assertEqual(registry.get('setup_call_cleanup', 3), null, 'setup_call_cleanup/3 is absent from the ISO registry');
|
|
4958
5023
|
assertEqual(Boolean(library.get('setup_call_cleanup', 3)), true, 'setup_call_cleanup/3 is an EyeProlog cleanup control');
|
|
4959
|
-
assertEqual(registeredNativeEyePrologLibraryNames().length,
|
|
5024
|
+
assertEqual(registeredNativeEyePrologLibraryNames().length, 49, 'public native EyeProlog builtin count');
|
|
4960
5025
|
assertEqual(eyePrologPortableLibraryIndicators.length, 87, 'portable Prolog library count');
|
|
4961
|
-
assertEqual(eyePrologInteropLibraryIndicators.length,
|
|
5026
|
+
assertEqual(eyePrologInteropLibraryIndicators.length, 30, 'cross-implementation interop profile count');
|
|
4962
5027
|
assertEqual(eyePrologInteropLibraryModules.join(','), 'lists,iso_ext,lambda,atts', 'common explicit library module profile');
|
|
4963
5028
|
assertEqual(eyePrologInteropAutoload['member/2'], 'lists', 'member/2 canonical autoload');
|
|
4964
5029
|
assertEqual(eyePrologInteropAutoload['between/3'], 'prologue', 'between/3 canonical internal autoload');
|
|
4965
5030
|
assertEqual(eyePrologInteropAutoload['call_nth/2'], 'iso_ext', 'call_nth/2 canonical interop autoload');
|
|
5031
|
+
assertEqual(eyePrologInteropAutoload['call_residue_vars/2'], 'atts', 'call_residue_vars/2 canonical interop autoload');
|
|
4966
5032
|
assertEqual(eyePrologInteropAutoload['time/1'], 'iso_ext', 'time/1 canonical interop autoload');
|
|
4967
5033
|
assertEqual(eyePrologInteropAutoload['.../2'], 'iso_ext', '.../2 canonical interop autoload');
|
|
4968
5034
|
assertEqual(eyePrologInteropAutoload['set_nth0/4'] ?? null, null, 'EyeProlog-only set_nth0/4 is not autoloadable');
|
|
4969
|
-
assertEqual(eyePrologNativeLibraryIndicators.length,
|
|
5035
|
+
assertEqual(eyePrologNativeLibraryIndicators.length, 49, 'native host library count');
|
|
4970
5036
|
assertEqual(eyePrologNativeLibraryIndicators.slice(0, 3).join(','), 'call_nth/2,freeze/2,dif/2', 'control and constraint predicates requiring host support');
|
|
4971
|
-
assertEqual(eyePrologLibraryIndicators.length,
|
|
5037
|
+
assertEqual(eyePrologLibraryIndicators.length, 136, 'complete EyeProlog library surface');
|
|
4972
5038
|
assertEqual(registry.get('eyeprolog__call_nth', 2), null, 'private call_nth adapter is absent from ISO registry');
|
|
4973
5039
|
assertEqual(Boolean(library.get('eyeprolog__call_nth', 2)), true, 'private call_nth adapter is registered for EyeProlog');
|
|
5040
|
+
assertEqual(Boolean(library.get('eyeprolog__call_residue_vars', 2)), true, 'private call_residue_vars adapter is registered for EyeProlog');
|
|
4974
5041
|
assertEqual(library.get('eyeprolog__call_nth', 2)?.eyePrologLibrary, true, 'private adapter is marked as library support');
|
|
4975
5042
|
assertEqual(registry.get('eyeprolog__freeze', 2), null, 'private freeze adapter is absent from ISO registry');
|
|
4976
5043
|
assertEqual(Boolean(library.get('eyeprolog__freeze', 2)), true, 'private freeze adapter is registered for EyeProlog');
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -6253,7 +6253,7 @@ between solution branches.
|
|
|
6253
6253
|
| Module | Exported predicate indicators |
|
|
6254
6254
|
| --- | --- |
|
|
6255
6255
|
| `library(aggregate)` | `sumall/3`, `aggregate_min/5`, `aggregate_max/5` |
|
|
6256
|
-
| `library(atts)` | `put_atts/2`, `get_atts/2`, `put_attr/3`, `get_attr/3`, `del_attr/2`, `term_attributed_variables/2` |
|
|
6256
|
+
| `library(atts)` | `put_atts/2`, `get_atts/2`, `put_attr/3`, `get_attr/3`, `del_attr/2`, `term_attributed_variables/2`, `call_residue_vars/2` |
|
|
6257
6257
|
| `library(clpz)` | `#>/2`, `#</2`, `#>=/2`, `#=</2`, `#=/2`, `#\=/2`, `#\/1`, `#<==>/2`, `#==>/2`, `#<==/2`, `#\//2`, `#\/2`, `#/\/2`, `in/2`, `ins/2`, `all_different/1`, `all_distinct/1`, `nvalue/2`, `sum/3`, `scalar_product/4`, `tuples_in/2`, `labeling/2`, `label/1`, `indomain/1`, `lex_chain/1`, `serialized/2`, `global_cardinality/2`, `global_cardinality/3`, `circuit/1`, `chain/2`, `element/3`, `zcompare/3`, `fd_var/1`, `fd_inf/2`, `fd_sup/2`, `fd_size/2`, `fd_dom/2` |
|
|
6258
6258
|
| `library(comparison)` | `lt/2`, `gt/2`, `le/2`, `ge/2` |
|
|
6259
6259
|
| `library(dates)` | `difference/3` |
|