eyeprolog 1.3.2 → 1.3.4
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 +12 -4
- package/examples/book/chapter-08/01-findall.pl +1 -0
- package/examples/book/chapter-39/04-cost.pl +1 -0
- package/examples/chart-parser.pl +1 -0
- package/examples/dog.pl +1 -0
- package/examples/donald-gerald-robert.pl +1 -0
- package/examples/equality-saturation.pl +1 -0
- package/examples/job-shop-scheduling.pl +1 -0
- package/examples/knapsack-optimization.pl +1 -0
- package/examples/knuth-bendix-completion.pl +1 -0
- package/examples/matrix-chain-order.pl +1 -0
- package/examples/missionaries-cannibals.pl +1 -0
- package/examples/prime-range.pl +1 -0
- package/examples/register-allocation.pl +1 -0
- package/examples/send-more-money.pl +1 -0
- package/examples/stable-marriage.pl +1 -0
- package/examples/totient-summatory.pl +1 -0
- package/examples/weighted-interval-scheduling.pl +1 -0
- package/package.json +1 -1
- package/src/iso.js +43 -10
- package/src/lib/iso_ext.pl +4 -0
- package/src/lib/lists.pl +0 -3
- package/src/parser.js +14 -0
- package/src/solver.js +49 -5
- package/src/standard-library.js +7 -7
- package/test/run-conformance.mjs +1 -0
- package/test/run-regression.mjs +63 -2
- package/the-art-of-eyeprolog.md +25 -13
package/README.md
CHANGED
|
@@ -143,12 +143,20 @@ Trealla/Scryer organization for predicates such as `member/2`, `memberchk/2`,
|
|
|
143
143
|
`length(Xs, N)` enumerates lists of increasing length together with `N = 0, 1,
|
|
144
144
|
2, ...`.
|
|
145
145
|
|
|
146
|
+
`library(iso_ext)` is also accepted as a common interop module name.
|
|
147
|
+
EyeProlog exports `call_nth/2` there, so Scryer-style source can explicitly use
|
|
148
|
+
`:- use_module(library(iso_ext)).`; unqualified source may still autoload it.
|
|
149
|
+
The aligned `library(lists)` and `library(iso_ext)` exports are kept disjoint so
|
|
150
|
+
they can be imported together without an accidental import conflict. EyeProlog's
|
|
151
|
+
legacy `library(prologue)` remains a compatibility umbrella and should be
|
|
152
|
+
selectively imported when mixed with the aligned modules.
|
|
153
|
+
|
|
146
154
|
Outside `--iso-strict`, an otherwise undefined unqualified call may autoload a
|
|
147
155
|
predicate only when the interop profile has one canonical EyeProlog provider.
|
|
148
|
-
For example, `member/2` autoloads from `library(lists)`,
|
|
149
|
-
`
|
|
150
|
-
|
|
151
|
-
|
|
156
|
+
For example, `member/2` autoloads from `library(lists)`, `call_nth/2` from
|
|
157
|
+
`library(iso_ext)`, and `between/3` from EyeProlog's internal
|
|
158
|
+
`library(prologue)` implementation. Use `--no-autoload` to disable this
|
|
159
|
+
convenience. Strict ISO mode never autoloads library predicates.
|
|
152
160
|
|
|
153
161
|
Use `-w` / `--warnings` to diagnose dependencies outside the interop profile,
|
|
154
162
|
or `--portable` to make such diagnostics fail the run. This catches both
|
package/examples/chart-parser.pl
CHANGED
package/examples/dog.pl
CHANGED
package/examples/prime-range.pl
CHANGED
package/package.json
CHANGED
package/src/iso.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
properListItems, termIsGround, termToString, unify, variable, variantTerms,
|
|
7
7
|
} from './term.js';
|
|
8
8
|
import { sameNumberValue } from './number-value.js';
|
|
9
|
-
import { createParserOperatorState,
|
|
9
|
+
import { createParserOperatorState, parseGoalText, parseNumberTokenText, parseTermText } from './parser.js';
|
|
10
10
|
import { formatTermForWrite } from './write.js';
|
|
11
11
|
import { emptyTerminalSequence, expandDcgBody, isListOrPartialList, validateDcgEmbeddedGoals } from './dcg.js';
|
|
12
12
|
import {
|
|
@@ -1199,8 +1199,7 @@ function scopeReadTerm(term) {
|
|
|
1199
1199
|
function parseReadTermText(text, solver) {
|
|
1200
1200
|
const converted = convertedTermText(text, solver);
|
|
1201
1201
|
const operatorState = createParserOperatorState(solver.program.operators.values(), false);
|
|
1202
|
-
|
|
1203
|
-
sourceMetadata: false,
|
|
1202
|
+
return parseTermText(converted, {
|
|
1204
1203
|
operatorState,
|
|
1205
1204
|
isoStrict: solver.isoStrict,
|
|
1206
1205
|
doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
@@ -1208,8 +1207,6 @@ function parseReadTermText(text, solver) {
|
|
|
1208
1207
|
// Earlier ambiguous dots must remain available to maximal graphic tokens.
|
|
1209
1208
|
readTermEnd: converted.length - 1,
|
|
1210
1209
|
});
|
|
1211
|
-
if (clauses.length !== 1 || clauses[0].body.length) throw new Error('bad term');
|
|
1212
|
-
return clauses[0].head;
|
|
1213
1210
|
}
|
|
1214
1211
|
|
|
1215
1212
|
export function isCompleteReadTermText(text, solver) {
|
|
@@ -2035,11 +2032,28 @@ function prologErrorBall(error) {
|
|
|
2035
2032
|
return freshCopy(term, new Env());
|
|
2036
2033
|
}
|
|
2037
2034
|
function* catchBuiltin({ solver, goal, env }) {
|
|
2038
|
-
|
|
2035
|
+
let child = null;
|
|
2039
2036
|
try {
|
|
2040
2037
|
// Corrigendum 2 removed catch/3's own callability errors so that errors
|
|
2041
2038
|
// raised while converting/executing the protected goal are catchable.
|
|
2042
|
-
|
|
2039
|
+
const invoked = callable(goal.args[0], env);
|
|
2040
|
+
const direct = solver.registry.get(invoked.name, invoked.arity);
|
|
2041
|
+
if (direct?.deterministic && (direct.shouldUse == null || direct.shouldUse({ solver, goal: invoked, env }))) {
|
|
2042
|
+
// A deterministic builtin has no choice points whose lifetime must be
|
|
2043
|
+
// isolated in a child solver. Running it directly avoids constructing a
|
|
2044
|
+
// complete Solver for hot caught failures such as number_chars/2 syntax
|
|
2045
|
+
// probes, while the cloned environment keeps catch/3's rollback boundary.
|
|
2046
|
+
const iterator = direct.handler({ solver, goal: invoked, env: env.clone() });
|
|
2047
|
+
const result = iterator.next();
|
|
2048
|
+
if (result.done) solver.stats.deterministic_builtin_failures++;
|
|
2049
|
+
else {
|
|
2050
|
+
solver.stats.deterministic_builtin_successes++;
|
|
2051
|
+
yield result.value;
|
|
2052
|
+
}
|
|
2053
|
+
return;
|
|
2054
|
+
}
|
|
2055
|
+
child = solver.cloneForInnerGoal();
|
|
2056
|
+
yield* child.solve([invoked], env.clone(), 0);
|
|
2043
2057
|
} catch (error) {
|
|
2044
2058
|
const ball = error instanceof ThrownTerm
|
|
2045
2059
|
? error.term
|
|
@@ -2049,9 +2063,15 @@ function* catchBuiltin({ solver, goal, env }) {
|
|
|
2049
2063
|
if (ball == null) throw error;
|
|
2050
2064
|
const recovered = env.clone();
|
|
2051
2065
|
if (!unify(goal.args[1], ball, recovered)) throw error;
|
|
2052
|
-
|
|
2066
|
+
const recovery = callable(goal.args[2], recovered);
|
|
2067
|
+
if (recovery.type === ATOM && (recovery.name === 'false' || recovery.name === 'fail')) return;
|
|
2068
|
+
if (recovery.type === ATOM && recovery.name === 'true') {
|
|
2069
|
+
yield recovered;
|
|
2070
|
+
return;
|
|
2071
|
+
}
|
|
2072
|
+
yield* solver.solve([recovery], recovered, 0);
|
|
2053
2073
|
} finally {
|
|
2054
|
-
solver.absorbStatsFrom(child);
|
|
2074
|
+
if (child != null) solver.absorbStatsFrom(child);
|
|
2055
2075
|
}
|
|
2056
2076
|
}
|
|
2057
2077
|
function* throwBuiltin({ goal, env }) {
|
|
@@ -2074,7 +2094,20 @@ function* repeatBuiltin({ env }) {
|
|
|
2074
2094
|
while (true) yield env;
|
|
2075
2095
|
}
|
|
2076
2096
|
function* negationBuiltin({ solver, goal, env }) {
|
|
2077
|
-
|
|
2097
|
+
const invoked = callable(goal.args[0], env);
|
|
2098
|
+
const direct = solver.registry.get(invoked.name, invoked.arity);
|
|
2099
|
+
if (direct?.deterministic && (direct.shouldUse == null || direct.shouldUse({ solver, goal: invoked, env }))) {
|
|
2100
|
+
const iterator = direct.handler({ solver, goal: invoked, env: env.clone() });
|
|
2101
|
+
const result = iterator.next();
|
|
2102
|
+
if (result.done) {
|
|
2103
|
+
solver.stats.deterministic_builtin_failures++;
|
|
2104
|
+
yield env;
|
|
2105
|
+
} else {
|
|
2106
|
+
solver.stats.deterministic_builtin_successes++;
|
|
2107
|
+
}
|
|
2108
|
+
return;
|
|
2109
|
+
}
|
|
2110
|
+
for (const _ of solver.cloneForInnerGoal(1).solve([invoked], env.clone(), 0)) return;
|
|
2078
2111
|
yield env;
|
|
2079
2112
|
}
|
|
2080
2113
|
function* solveControlBranch(solver, goal, env) {
|
package/src/lib/iso_ext.pl
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** Widely implemented extensions to the ISO Prolog core. */
|
|
2
2
|
|
|
3
3
|
:- module(iso_ext, [
|
|
4
|
+
call_nth/2,
|
|
4
5
|
countall/2,
|
|
5
6
|
forall/2,
|
|
6
7
|
succ/2,
|
|
@@ -9,6 +10,7 @@
|
|
|
9
10
|
variant/2
|
|
10
11
|
]).
|
|
11
12
|
|
|
13
|
+
:- meta_predicate(call_nth(0, '?')).
|
|
12
14
|
:- meta_predicate(countall(0, '?')).
|
|
13
15
|
:- meta_predicate(forall(0, 0)).
|
|
14
16
|
:- meta_predicate(findall('?', 0, '?', '?')).
|
|
@@ -17,6 +19,8 @@
|
|
|
17
19
|
% Trealla. These definitions use only EyeProlog's ISO profile; extensions that
|
|
18
20
|
% require runtime cleanup, choice-point, alarm, or timeout hooks are omitted.
|
|
19
21
|
|
|
22
|
+
call_nth(Goal, Nth) :- eyeprolog__call_nth(Goal, Nth).
|
|
23
|
+
|
|
20
24
|
countall(Goal, Count) :- eyeprolog__countall(Goal, Count).
|
|
21
25
|
|
|
22
26
|
forall(Condition, Action) :-
|
package/src/lib/lists.pl
CHANGED
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
min_list/2,
|
|
29
29
|
max_list/2,
|
|
30
30
|
list_to_set/2,
|
|
31
|
-
countall/2,
|
|
32
31
|
set_nth0/4,
|
|
33
32
|
take/3,
|
|
34
33
|
drop/3,
|
|
@@ -45,7 +44,6 @@
|
|
|
45
44
|
:- meta_predicate(foldl(3, '?', '?', '?')).
|
|
46
45
|
:- meta_predicate(foldl(4, '?', '?', '?', '?')).
|
|
47
46
|
:- meta_predicate(foldl(5, '?', '?', '?', '?', '?')).
|
|
48
|
-
:- meta_predicate(countall(0, '?')).
|
|
49
47
|
|
|
50
48
|
% Common pure-Prolog library predicates for EyeProlog.
|
|
51
49
|
%
|
|
@@ -168,7 +166,6 @@ max_list([X|Xs], Max) :- lists__max_list(Xs, X, Max).
|
|
|
168
166
|
|
|
169
167
|
list_to_set(List, Set) :- lists__list_to_set(List, [], Set).
|
|
170
168
|
|
|
171
|
-
countall(Goal, Count) :- eyeprolog__countall(Goal, Count).
|
|
172
169
|
|
|
173
170
|
set_nth0(0, [_|Xs], X, [X|Xs]).
|
|
174
171
|
set_nth0(N, [Y|Ys], X, [Y|Zs]) :-
|
package/src/parser.js
CHANGED
|
@@ -819,6 +819,16 @@ class Parser {
|
|
|
819
819
|
}
|
|
820
820
|
throw new Error(`parse line ${this.token.line}: bad term`);
|
|
821
821
|
}
|
|
822
|
+
parseStandaloneTerm() {
|
|
823
|
+
// read/1 and read_term/* consume one ordinary Prolog term, not a program
|
|
824
|
+
// clause. In particular, commas and operators such as :- and ?- belong to
|
|
825
|
+
// the term itself and must not be reinterpreted by parseProgram().
|
|
826
|
+
const term = this.parseTerm(0, true, true, true);
|
|
827
|
+
this.expect(TOK.DOT, '.');
|
|
828
|
+
this.advance();
|
|
829
|
+
this.expect(TOK.EOF, 'end of input');
|
|
830
|
+
return term;
|
|
831
|
+
}
|
|
822
832
|
sourceLineIsIndented(line) {
|
|
823
833
|
let start = 0;
|
|
824
834
|
for (let current = 1; current < line; current++) {
|
|
@@ -1504,6 +1514,10 @@ export function parseNumberTokenText(text) {
|
|
|
1504
1514
|
return numberTerm(source);
|
|
1505
1515
|
}
|
|
1506
1516
|
|
|
1517
|
+
export function parseTermText(text, options = {}) {
|
|
1518
|
+
return new Parser(text, options).parseStandaloneTerm();
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1507
1521
|
export function parseGoalText(text, options = {}) {
|
|
1508
1522
|
const clauses = parseClauses(`zz_goal((${text})).`, options);
|
|
1509
1523
|
const head = clauses[0]?.head;
|
package/src/solver.js
CHANGED
|
@@ -374,7 +374,24 @@ export class Solver {
|
|
|
374
374
|
}
|
|
375
375
|
qualifyMetaArguments(goal, group);
|
|
376
376
|
|
|
377
|
-
const
|
|
377
|
+
const memberIterator = bundledMemberIterator(this, group, goal, env);
|
|
378
|
+
if (memberIterator != null) {
|
|
379
|
+
const firstResult = memberIterator.next();
|
|
380
|
+
if (firstResult.done) break;
|
|
381
|
+
stack.push({
|
|
382
|
+
kind: 'resumeBuiltin',
|
|
383
|
+
iterator: memberIterator,
|
|
384
|
+
goals: rest,
|
|
385
|
+
depth: depth + 1,
|
|
386
|
+
active,
|
|
387
|
+
});
|
|
388
|
+
goals = rest;
|
|
389
|
+
env = firstResult.value;
|
|
390
|
+
depth++;
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const lengthIterator = bundledLengthIterator(this, group, goal, env);
|
|
378
395
|
if (lengthIterator != null) {
|
|
379
396
|
const firstResult = lengthIterator.next();
|
|
380
397
|
if (firstResult.done) break;
|
|
@@ -757,9 +774,36 @@ function groupNeedsActiveFrame(group) {
|
|
|
757
774
|
return group.cutReachable !== false || (group.recursive && !group.linearNumeric);
|
|
758
775
|
}
|
|
759
776
|
|
|
760
|
-
function
|
|
777
|
+
function bundledMemberIterator(solver, group, goal, env) {
|
|
778
|
+
if (solver.registry.eyePrologLibrary !== true ||
|
|
779
|
+
!['lists', 'prologue'].includes(group.module) || group.name !== 'member' || group.arity !== 2 ||
|
|
780
|
+
group.bundledLibrary !== true || group.clauses.length !== 2) {
|
|
781
|
+
return null;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// Keep the relational/open-list cases in ordinary Prolog. When the second
|
|
785
|
+
// argument is already a finite list, however, the canonical two-clause
|
|
786
|
+
// member/2 definition is exactly a left-to-right scan and can avoid clause
|
|
787
|
+
// freshening and recursive solver frames for every element.
|
|
788
|
+
let cursor = deref(goal.args[1], env);
|
|
789
|
+
while (isCons(cursor)) cursor = deref(cursor.args[1], env);
|
|
790
|
+
if (!isEmptyList(cursor)) return null;
|
|
791
|
+
return bundledMemberSolutions(solver, goal, env);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function* bundledMemberSolutions(solver, goal, env) {
|
|
795
|
+
let cursor = deref(goal.args[1], env);
|
|
796
|
+
while (isCons(cursor)) {
|
|
797
|
+
const next = env.clone();
|
|
798
|
+
solver.stats.unify_calls++;
|
|
799
|
+
if (unify(goal.args[0], cursor.args[0], next)) yield next;
|
|
800
|
+
cursor = deref(cursor.args[1], env);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function bundledLengthIterator(solver, group, goal, env) {
|
|
761
805
|
if (solver.registry.eyePrologLibrary !== true ||
|
|
762
|
-
|
|
806
|
+
!['lists', 'prologue'].includes(group.module) || group.name !== 'length' || group.arity !== 2 ||
|
|
763
807
|
group.bundledLibrary !== true || group.clauses.length !== 2) {
|
|
764
808
|
return null;
|
|
765
809
|
}
|
|
@@ -778,10 +822,10 @@ function prologueLengthIterator(solver, group, goal, env) {
|
|
|
778
822
|
if (env._delays?.has(cursor.name)) return null;
|
|
779
823
|
if (length.type === VAR && cursor.name === length.name) return null;
|
|
780
824
|
}
|
|
781
|
-
return
|
|
825
|
+
return bundledLengthSolutions(solver, goal, env);
|
|
782
826
|
}
|
|
783
827
|
|
|
784
|
-
function*
|
|
828
|
+
function* bundledLengthSolutions(solver, goal, env) {
|
|
785
829
|
const requestedLength = deref(goal.args[1], env);
|
|
786
830
|
if (requestedLength.type !== VAR) {
|
|
787
831
|
if (requestedLength.type !== NUMBER || !isDecimalInteger(requestedLength.name)) {
|
package/src/standard-library.js
CHANGED
|
@@ -109,10 +109,10 @@ export const eyePrologInteropAutoload = Object.freeze({
|
|
|
109
109
|
'foldl/6': 'lists',
|
|
110
110
|
'sum_list/2': 'lists',
|
|
111
111
|
'list_to_set/2': 'lists',
|
|
112
|
-
// call_nth/2 is available in both Trealla and Scryer
|
|
113
|
-
// library(iso_ext)
|
|
114
|
-
//
|
|
115
|
-
'call_nth/2': '
|
|
112
|
+
// call_nth/2 is available in both Trealla and Scryer. EyeProlog exposes
|
|
113
|
+
// it from library(iso_ext), matching the explicit Scryer import while still
|
|
114
|
+
// allowing Trealla-style unqualified source to use the same autoload entry.
|
|
115
|
+
'call_nth/2': 'iso_ext',
|
|
116
116
|
// Trealla and Scryer expose between/3 without an EyeProlog-style
|
|
117
117
|
// library(prologue) dependency. EyeProlog keeps its implementation in the
|
|
118
118
|
// Prologue module but autoloads it so portable source need not name that
|
|
@@ -125,9 +125,9 @@ export const eyePrologInteropLibraryIndicators = Object.freeze(
|
|
|
125
125
|
);
|
|
126
126
|
|
|
127
127
|
// Libraries whose *name* is part of the current interop profile. A program
|
|
128
|
-
// may freely use_module
|
|
129
|
-
// outside eyePrologInteropLibraryIndicators are still diagnosed when used.
|
|
130
|
-
export const eyePrologInteropLibraryModules = Object.freeze(['lists']);
|
|
128
|
+
// may freely use_module/1 with these common module names; predicates in those
|
|
129
|
+
// modules outside eyePrologInteropLibraryIndicators are still diagnosed when used.
|
|
130
|
+
export const eyePrologInteropLibraryModules = Object.freeze(['lists', 'iso_ext']);
|
|
131
131
|
|
|
132
132
|
function runtimeStatistics(solver) {
|
|
133
133
|
return { ...solver.stats, ...memoryStatistics() };
|
package/test/run-conformance.mjs
CHANGED
|
@@ -18,6 +18,7 @@ function withStandardModules(text) {
|
|
|
18
18
|
return `:- use_module(library(aggregate)).
|
|
19
19
|
:- use_module(library(comparison)).
|
|
20
20
|
:- use_module(library(dates)).
|
|
21
|
+
:- use_module(library(iso_ext)).
|
|
21
22
|
:- use_module(library(lists)).
|
|
22
23
|
:- use_module(library(primes)).
|
|
23
24
|
:- use_module(library(prologue), [between/3]).
|
package/test/run-regression.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
createDefaultRegistry,
|
|
21
21
|
getStrictIsoRegistry,
|
|
22
22
|
getEyePrologRegistry,
|
|
23
|
+
standardLibrarySources,
|
|
23
24
|
eyePrologLibraryIndicators,
|
|
24
25
|
eyePrologNativeLibraryIndicators,
|
|
25
26
|
eyePrologPortableLibraryIndicators,
|
|
@@ -67,6 +68,7 @@ function withStandardModules(source) {
|
|
|
67
68
|
return `:- use_module(library(aggregate)).
|
|
68
69
|
:- use_module(library(comparison)).
|
|
69
70
|
:- use_module(library(dates)).
|
|
71
|
+
:- use_module(library(iso_ext)).
|
|
70
72
|
:- use_module(library(lists)).
|
|
71
73
|
:- use_module(library(primes)).
|
|
72
74
|
:- use_module(library(prologue), [between/3]).
|
|
@@ -819,6 +821,27 @@ c4 ?- call((!;1)).
|
|
|
819
821
|
});
|
|
820
822
|
assertEqual(consecutive.stdout, "answer(./*., ok).\n", 'following read starts after the complete term');
|
|
821
823
|
|
|
824
|
+
// Issue #41 follow-up: read/1 consumes an ordinary term, not a program
|
|
825
|
+
// clause head. A comma chain therefore has no program-level two-comma
|
|
826
|
+
// limit, and source operators such as :- and ?- remain term data.
|
|
827
|
+
const commaChain = runEyeProlog('', {
|
|
828
|
+
goal: 'read(T)',
|
|
829
|
+
ioOptions: { input: '!,!,! .\n' },
|
|
830
|
+
});
|
|
831
|
+
assertEqual(commaChain.stdout, 'read((!, !, !)).\n', 'three-element comma term');
|
|
832
|
+
|
|
833
|
+
const ruleAsData = runEyeProlog('', {
|
|
834
|
+
goal: 'read(T)',
|
|
835
|
+
ioOptions: { input: 'a :- b.\n' },
|
|
836
|
+
});
|
|
837
|
+
assertEqual(ruleAsData.stdout, 'read((a :- b)).\n', 'rule operator remains term data');
|
|
838
|
+
|
|
839
|
+
const queryAsData = runEyeProlog('', {
|
|
840
|
+
goal: 'read(T)',
|
|
841
|
+
ioOptions: { input: '?- foo.\n' },
|
|
842
|
+
});
|
|
843
|
+
assertEqual(queryAsData.stdout, 'read((?- foo)).\n', 'query operator remains term data');
|
|
844
|
+
|
|
822
845
|
// A possible full stop can fail to complete the term while still
|
|
823
846
|
// extending a current graphic operator into an ordinary atom. The
|
|
824
847
|
// later standalone full stop then completes the read term. Exercise
|
|
@@ -2193,6 +2216,22 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
|
|
|
2193
2216
|
assertEqual(result.stderr, '', 'stderr');
|
|
2194
2217
|
},
|
|
2195
2218
|
},
|
|
2219
|
+
{
|
|
2220
|
+
name: 'library(lists) and library(iso_ext) co-import without collisions',
|
|
2221
|
+
run: () => {
|
|
2222
|
+
const input = [
|
|
2223
|
+
':- use_module(library(lists)).',
|
|
2224
|
+
':- use_module(library(iso_ext)).',
|
|
2225
|
+
'%% goal: answer(N)',
|
|
2226
|
+
'answer(N) :- call_nth(member(_, [a,b,c]), N), N = 2.',
|
|
2227
|
+
'',
|
|
2228
|
+
].join('\n');
|
|
2229
|
+
const result = runCli(['--portable', '--no-autoload', '-'], { input });
|
|
2230
|
+
assertEqual(result.status, 0, 'exit status');
|
|
2231
|
+
assertEqual(result.stdout, 'answer(2).\n', 'stdout');
|
|
2232
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
2233
|
+
},
|
|
2234
|
+
},
|
|
2196
2235
|
{
|
|
2197
2236
|
name: 'strict ISO mode disables interop autoloading',
|
|
2198
2237
|
run: () => {
|
|
@@ -3597,10 +3636,10 @@ check(A, B, C, D, E, F) :-
|
|
|
3597
3636
|
assertEqual(registeredNativeEyePrologLibraryNames().length, 40, 'public native EyeProlog builtin count');
|
|
3598
3637
|
assertEqual(eyePrologPortableLibraryIndicators.length, 62, 'portable Prolog library count');
|
|
3599
3638
|
assertEqual(eyePrologInteropLibraryIndicators.length, 27, 'cross-implementation interop profile count');
|
|
3600
|
-
assertEqual(eyePrologInteropLibraryModules.join(','), 'lists', 'common explicit library module profile');
|
|
3639
|
+
assertEqual(eyePrologInteropLibraryModules.join(','), 'lists,iso_ext', 'common explicit library module profile');
|
|
3601
3640
|
assertEqual(eyePrologInteropAutoload['member/2'], 'lists', 'member/2 canonical autoload');
|
|
3602
3641
|
assertEqual(eyePrologInteropAutoload['between/3'], 'prologue', 'between/3 canonical internal autoload');
|
|
3603
|
-
assertEqual(eyePrologInteropAutoload['call_nth/2'], '
|
|
3642
|
+
assertEqual(eyePrologInteropAutoload['call_nth/2'], 'iso_ext', 'call_nth/2 canonical interop autoload');
|
|
3604
3643
|
assertEqual(eyePrologInteropAutoload['set_nth0/4'] ?? null, null, 'EyeProlog-only set_nth0/4 is not autoloadable');
|
|
3605
3644
|
assertEqual(eyePrologNativeLibraryIndicators.length, 40, 'native host library count');
|
|
3606
3645
|
assertEqual(eyePrologNativeLibraryIndicators.slice(0, 2).join(','), 'call_nth/2,freeze/2', 'control predicates requiring host support');
|
|
@@ -3650,6 +3689,28 @@ check(A, B, C, D, E, F) :-
|
|
|
3650
3689
|
assertEqual(program.findGroup('random', 3)?.module, 'random', 'random/3 is imported from library(random)');
|
|
3651
3690
|
},
|
|
3652
3691
|
},
|
|
3692
|
+
{
|
|
3693
|
+
name: 'aligned bundled libraries have no overlapping full-module exports',
|
|
3694
|
+
run: () => {
|
|
3695
|
+
const owners = new Map();
|
|
3696
|
+
for (const [moduleName, entry] of standardLibrarySources) {
|
|
3697
|
+
// library(prologue) is the documented legacy compatibility umbrella;
|
|
3698
|
+
// aligned libraries must remain pairwise collision-free so full
|
|
3699
|
+
// use_module/1 imports can be combined safely.
|
|
3700
|
+
if (moduleName === 'prologue') continue;
|
|
3701
|
+
const parsed = Program.parse(entry.source);
|
|
3702
|
+
const definition = parsed.modules.get(moduleName);
|
|
3703
|
+
if (!definition) throw new Error(`missing module declaration for ${moduleName}`);
|
|
3704
|
+
for (const indicator of definition.exports.keys()) {
|
|
3705
|
+
const previous = owners.get(indicator);
|
|
3706
|
+
if (previous != null) throw new Error(`duplicate export ${indicator}: ${previous}, ${moduleName}`);
|
|
3707
|
+
owners.set(indicator, moduleName);
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
assertEqual(owners.get('countall/2'), 'iso_ext', 'countall/2 has one aligned owner');
|
|
3711
|
+
assertEqual(owners.get('call_nth/2'), 'iso_ext', 'call_nth/2 has one aligned owner');
|
|
3712
|
+
},
|
|
3713
|
+
},
|
|
3653
3714
|
{
|
|
3654
3715
|
name: 'portable library executes against the ISO-only registry',
|
|
3655
3716
|
run: () => {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -1155,6 +1155,7 @@ Finite aggregation asks about a solution set:
|
|
|
1155
1155
|
```eyeprolog
|
|
1156
1156
|
:- use_module(library(aggregate)).
|
|
1157
1157
|
:- use_module(library(lists)).
|
|
1158
|
+
:- use_module(library(iso_ext)).
|
|
1158
1159
|
|
|
1159
1160
|
findall(Template, Goal, List).
|
|
1160
1161
|
countall(Goal, Count).
|
|
@@ -6019,8 +6020,8 @@ between solution branches.
|
|
|
6019
6020
|
| `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` |
|
|
6020
6021
|
| `library(comparison)` | `lt/2`, `gt/2`, `le/2`, `ge/2` |
|
|
6021
6022
|
| `library(dates)` | `difference/3` |
|
|
6022
|
-
| `library(iso_ext)` | `countall/2`, `forall/2`, `succ/2`, `cfor/3`, `findall/4`, `variant/2` |
|
|
6023
|
-
| `library(lists)` | `member/2`, `memberchk/2`, `select/3`, `append/2`, `append/3`, `last/2`, `same_length/2`, `nth0/3`, `nth0/4`, `nth1/3`, `nth1/4`, `reverse/2`, `length/2`, `maplist/2`, `maplist/3`, `maplist/4`, `maplist/5`, `maplist/6`, `maplist/7`, `maplist/8`, `foldl/4`, `foldl/5`, `foldl/6`, `sum_list/2`, `min_list/2`, `max_list/2`, `list_to_set/2`, `
|
|
6023
|
+
| `library(iso_ext)` | `call_nth/2`, `countall/2`, `forall/2`, `succ/2`, `cfor/3`, `findall/4`, `variant/2` |
|
|
6024
|
+
| `library(lists)` | `member/2`, `memberchk/2`, `select/3`, `append/2`, `append/3`, `last/2`, `same_length/2`, `nth0/3`, `nth0/4`, `nth1/3`, `nth1/4`, `reverse/2`, `length/2`, `maplist/2`, `maplist/3`, `maplist/4`, `maplist/5`, `maplist/6`, `maplist/7`, `maplist/8`, `foldl/4`, `foldl/5`, `foldl/6`, `sum_list/2`, `min_list/2`, `max_list/2`, `list_to_set/2`, `set_nth0/4`, `take/3`, `drop/3`, `slice/4` |
|
|
6024
6025
|
| `library(primes)` | `smallest_divisor_from/3` |
|
|
6025
6026
|
| `library(prologue)` | `member/2`, `append/3`, `length/2`, `between/3`, `select/3`, `succ/2`, `maplist/2`, `maplist/3`, `maplist/4`, `maplist/5`, `maplist/6`, `maplist/7`, `maplist/8`, `nth0/3`, `nth0/4`, `nth1/3`, `nth1/4`, `call_nth/2`, `freeze/2`, `foldl/4`, `foldl/5`, `foldl/6`, `countall/2` |
|
|
6026
6027
|
| `library(random)` | `random/3` |
|
|
@@ -6046,14 +6047,21 @@ This generator mode is important for portable stress and enumeration programs.
|
|
|
6046
6047
|
EyeProlog-only helpers such as `set_nth0/4`, `take/3`, `drop/3`, and `slice/4`
|
|
6047
6048
|
remain available for compatibility but are outside this profile.
|
|
6048
6049
|
|
|
6050
|
+
`library(iso_ext)` is also recognized as a common interop module name.
|
|
6051
|
+
EyeProlog exports `call_nth/2` there, matching Scryer's explicit import
|
|
6052
|
+
organization while still permitting unqualified autoloaded source. The aligned
|
|
6053
|
+
`library(lists)` and `library(iso_ext)` export sets are intentionally disjoint,
|
|
6054
|
+
so a source file may import both without an accidental predicate collision.
|
|
6055
|
+
`library(prologue)` is retained as an EyeProlog compatibility umbrella and may
|
|
6056
|
+
overlap these modules; use selective imports when legacy code combines it with
|
|
6057
|
+
the aligned libraries.
|
|
6058
|
+
|
|
6049
6059
|
Normal EyeProlog execution can autoload an otherwise undefined unqualified call
|
|
6050
6060
|
only when the interop table assigns it one canonical provider. Thus `member/2`
|
|
6051
|
-
autoloads from `library(lists)
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
`--no-autoload`, by the JavaScript option `autoload: false`, and always by
|
|
6056
|
-
`--iso-strict`.
|
|
6061
|
+
autoloads from `library(lists)`, `call_nth/2` from `library(iso_ext)`, and
|
|
6062
|
+
`between/3` from the internal `library(prologue)` implementation. Autoloading is
|
|
6063
|
+
disabled by `--no-autoload`, by the JavaScript option `autoload: false`, and
|
|
6064
|
+
always by `--iso-strict`.
|
|
6057
6065
|
|
|
6058
6066
|
`-w` / `--warnings` reports explicit non-profile library dependencies and calls
|
|
6059
6067
|
to non-profile predicates from common libraries. `--portable` turns those
|
|
@@ -6075,11 +6083,14 @@ cumulative and two-dimensional scheduling constraints, and unbounded-domain
|
|
|
6075
6083
|
propagation that are not yet exported here.
|
|
6076
6084
|
|
|
6077
6085
|
`library(iso_ext)` collects extensions commonly found in mature Prolog
|
|
6078
|
-
systems. `
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
`
|
|
6086
|
+
systems. `call_nth/2` exposes the ordinal number of each solution and supports
|
|
6087
|
+
the explicit import used by Scryer-style source; `forall/2` checks an action for
|
|
6088
|
+
every solution of a condition; `cfor/3` enumerates an inclusive evaluated
|
|
6089
|
+
integer range; `succ/2` relates adjacent nonnegative integers; `findall/4`
|
|
6090
|
+
collects into a difference list; and `variant/2` recognizes terms equal up to
|
|
6091
|
+
variable renaming. Its portable `countall/2` counts solutions without exposing
|
|
6092
|
+
a template. `countall/2` is not exported by `library(lists)`, avoiding a
|
|
6093
|
+
full-module import collision between the two libraries.
|
|
6083
6094
|
|
|
6084
6095
|
`uuid(+Seed0,-UUID,-Seed)` creates a version 4 UUID atom using `random/3`.
|
|
6085
6096
|
Passing the returned seed to the next call produces the next UUID; restarting
|
|
@@ -6272,6 +6283,7 @@ There is no `not/1` alias; use ISO `\+/1`. `forall/2` is available from
|
|
|
6272
6283
|
```eyeprolog
|
|
6273
6284
|
:- use_module(library(aggregate)).
|
|
6274
6285
|
:- use_module(library(lists)).
|
|
6286
|
+
:- use_module(library(iso_ext)).
|
|
6275
6287
|
|
|
6276
6288
|
cost(a, 8).
|
|
6277
6289
|
cost(b, 3).
|