eyeprolog 1.3.75 → 1.3.77
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 +6 -6
- package/examples/binomial-vandermonde.pl +1 -1
- package/examples/book/chapter-26/02-triple.pl +1 -1
- package/examples/book/chapter-28/02-triangle.pl +1 -1
- package/examples/book/chapter-28/04-integer_rectangle.pl +1 -1
- package/examples/book/chapter-29/01-counterexample_to_odd_square.pl +1 -1
- package/examples/book/chapter-31/03-double.pl +1 -1
- package/examples/book/chapter-32/04-reference_square.pl +1 -1
- package/examples/book/chapter-33/03-chosen_pair.pl +1 -1
- package/examples/book/chapter-39/01-answer.pl +1 -1
- package/examples/catalan-convolution.pl +1 -1
- package/examples/collatz-1000.pl +1 -1
- package/examples/continued-fraction-sqrt2.pl +1 -1
- package/examples/easter-computus.pl +1 -1
- package/examples/equality-saturation.pl +1 -1
- package/examples/goldbach.pl +1 -1
- package/examples/integer-partitions.pl +1 -1
- package/examples/job-shop-scheduling.pl +1 -1
- package/examples/matrix-chain-order.pl +1 -1
- package/examples/missionaries-cannibals.pl +1 -1
- package/examples/pell-equation.pl +1 -1
- package/examples/polynomial.pl +1 -1
- package/examples/prime-range.pl +1 -1
- package/examples/stirling-bell-numbers.pl +1 -1
- package/examples/totient-summatory.pl +1 -1
- package/package.json +1 -1
- package/src/iso.js +42 -17
- package/src/lib/lists.pl +15 -5
- package/src/lib/prologue.pl +30 -166
- package/src/program.js +20 -8
- package/src/quads.js +24 -3
- package/src/repl.js +10 -2
- package/src/solver.js +5 -5
- package/src/standard-library.js +5 -6
- package/src/term.js +4 -3
- package/test/fixtures/README.md +8 -0
- package/test/fixtures/variable_names_quad.pl +163 -0
- package/test/run-regression.mjs +88 -7
- package/test/test-support.mjs +1 -1
- package/the-art-of-eyeprolog.md +30 -23
package/src/quads.js
CHANGED
|
@@ -110,10 +110,15 @@ function checkAlternative(program, quad, alternative, options, context) {
|
|
|
110
110
|
const actual = executeQuery(program, quad.query, input, maxSolutions, {
|
|
111
111
|
...options,
|
|
112
112
|
detectLoops: leaves.some((leaf) => leaf.loops),
|
|
113
|
+
detectWaits: leaves.some((leaf) => leaf.waits),
|
|
113
114
|
inputBoundary: hasInputSpec,
|
|
114
115
|
inputPeek: peek,
|
|
115
116
|
});
|
|
116
117
|
|
|
118
|
+
if (leaves.some((leaf) => leaf.waits)) {
|
|
119
|
+
return { ok: leaves.length === 1 && actual.inputWaitObserved };
|
|
120
|
+
}
|
|
121
|
+
|
|
117
122
|
if (requiresSto) {
|
|
118
123
|
// Trealla currently treats the answer part of an `sto`-annotated leaf as
|
|
119
124
|
// implementation-dependent and skips it. EyeProlog can strengthen that
|
|
@@ -199,6 +204,7 @@ function describeLeaf(term) {
|
|
|
199
204
|
false: false,
|
|
200
205
|
truth: false,
|
|
201
206
|
loops: false,
|
|
207
|
+
waits: false,
|
|
202
208
|
error: null,
|
|
203
209
|
input: null,
|
|
204
210
|
peek: null,
|
|
@@ -213,7 +219,8 @@ function describeLeaf(term) {
|
|
|
213
219
|
else if (item.name === '...' || item.name === 'ad_infinitum') leaf.more = true;
|
|
214
220
|
else if (item.name === 'sto') leaf.sto = true;
|
|
215
221
|
else if (item.name === 'loops') leaf.loops = true;
|
|
216
|
-
else if (item.name === 'waits'
|
|
222
|
+
else if (item.name === 'waits') leaf.waits = true;
|
|
223
|
+
else if (item.name === 'other_answer_sequence') {
|
|
217
224
|
leaf.unsupported ??= item;
|
|
218
225
|
} else if (item.name === 'false') leaf.false = true;
|
|
219
226
|
else if (item.name === 'true') leaf.truth = true;
|
|
@@ -246,7 +253,8 @@ function describeLeaf(term) {
|
|
|
246
253
|
if (isErrorDescription(item)) leaf.error = item;
|
|
247
254
|
else leaf.malformed ??= item;
|
|
248
255
|
}
|
|
249
|
-
leaf.hasExpectation = leaf.bindings.length > 0 || leaf.truth || leaf.false || leaf.loops || leaf.
|
|
256
|
+
leaf.hasExpectation = leaf.bindings.length > 0 || leaf.truth || leaf.false || leaf.loops || leaf.waits ||
|
|
257
|
+
leaf.error != null || leaf.output != null;
|
|
250
258
|
if (!leaf.hasExpectation && !leaf.more && !leaf.sto && leaf.unsupported == null) leaf.malformed ??= term;
|
|
251
259
|
if ([leaf.false, leaf.truth, leaf.error != null].filter(Boolean).length > 1) leaf.malformed ??= term;
|
|
252
260
|
return leaf;
|
|
@@ -254,6 +262,7 @@ function describeLeaf(term) {
|
|
|
254
262
|
|
|
255
263
|
function executeQuery(program, query, input, maxSolutions, options) {
|
|
256
264
|
let pendingOutput = '';
|
|
265
|
+
let inputWaitObserved = false;
|
|
257
266
|
const boundedInput = options.inputBoundary
|
|
258
267
|
? `${input}${options.inputPeek ?? ''}${INVALID_UTF8_SENTINEL}`
|
|
259
268
|
: input;
|
|
@@ -274,13 +283,24 @@ function executeQuery(program, query, input, maxSolutions, options) {
|
|
|
274
283
|
write: (text) => { pendingOutput += String(text); },
|
|
275
284
|
},
|
|
276
285
|
});
|
|
286
|
+
if (options.detectWaits) {
|
|
287
|
+
const inputStream = solver.io.resolve('user_input');
|
|
288
|
+
const observeWait = () => {
|
|
289
|
+
inputWaitObserved = true;
|
|
290
|
+
return null;
|
|
291
|
+
};
|
|
292
|
+
inputStream.interactiveReadTerm = observeWait;
|
|
293
|
+
inputStream.interactiveReadUnit = observeWait;
|
|
294
|
+
}
|
|
277
295
|
if (options.inputBoundary) {
|
|
278
296
|
// Quads need a boundary that is neither EOF nor a valid character. This
|
|
279
297
|
// mirrors Trealla's sentinel technique: inputs/1 says exactly what was
|
|
280
298
|
// consumed, while peeks/1 supplies one additional character that must
|
|
281
299
|
// remain unread. If the query asks for anything beyond that boundary it
|
|
282
300
|
// sees representation_error(character), not an artificial EOF.
|
|
283
|
-
solver.io.resolve('user_input')
|
|
301
|
+
const inputStream = solver.io.resolve('user_input');
|
|
302
|
+
inputStream.strictUtf8 = true;
|
|
303
|
+
inputStream.syntheticInputBoundary = input.length;
|
|
284
304
|
}
|
|
285
305
|
// Undefined predicates are test failures rather than silent negative
|
|
286
306
|
// answers unless the source explicitly selected another unknown policy.
|
|
@@ -338,6 +358,7 @@ function executeQuery(program, query, input, maxSolutions, options) {
|
|
|
338
358
|
inputRemainder,
|
|
339
359
|
complete,
|
|
340
360
|
loopObserved,
|
|
361
|
+
inputWaitObserved,
|
|
341
362
|
stoObserved: solver.occursCheckObserved,
|
|
342
363
|
nstoObserved: complete && !solver.occursCheckObserved && !loopObserved &&
|
|
343
364
|
!bounded && !resourceInterrupted,
|
package/src/repl.js
CHANGED
|
@@ -722,6 +722,12 @@ function formatAnswer(engine, state, variables, env) {
|
|
|
722
722
|
: equality.specifier === 'xfy' ? equality.priority : equality.priority - 1;
|
|
723
723
|
let generated = 0;
|
|
724
724
|
|
|
725
|
+
// Meta-predicate wrappers can make a query variable alias an internal fresh
|
|
726
|
+
// variable. Keep residual constraints tied to the query's visible name.
|
|
727
|
+
for (const variable of variables) {
|
|
728
|
+
const root = engine.deref(variable, env);
|
|
729
|
+
if (root.type === 'var' && !names.has(root.name)) names.set(root.name, variable.name);
|
|
730
|
+
}
|
|
725
731
|
for (const variable of variables) collectUnboundVariables(engine, variable, env, names, () => `_${letterName(generated++)}`);
|
|
726
732
|
for (const variable of variables) {
|
|
727
733
|
const value = engine.deref(variable, env);
|
|
@@ -797,7 +803,7 @@ function letterName(index) {
|
|
|
797
803
|
}
|
|
798
804
|
|
|
799
805
|
function formatError(engine, state, error) {
|
|
800
|
-
if (error?.name === 'PrologError') {
|
|
806
|
+
if (error?.name === 'PrologError' || (error?.name === 'ThrownTerm' && error.term != null)) {
|
|
801
807
|
const env = new engine.Env();
|
|
802
808
|
const variableNames = new Map();
|
|
803
809
|
let generated = 0;
|
|
@@ -805,7 +811,9 @@ function formatError(engine, state, error) {
|
|
|
805
811
|
// Reuse the same conversion as catch/3 so uncaught errors at the top
|
|
806
812
|
// level cannot lose the implementation-defined context or misplace a
|
|
807
813
|
// culprit as the second argument of error/2.
|
|
808
|
-
const term =
|
|
814
|
+
const term = error.name === 'ThrownTerm'
|
|
815
|
+
? engine.compound('throw', [error.term])
|
|
816
|
+
: formalErrorTerm(error);
|
|
809
817
|
collectUnboundVariables(engine, term, env, variableNames, () => `_${letterName(generated++)}`);
|
|
810
818
|
return `${engine.formatTermForWrite(term, env, {
|
|
811
819
|
quoted: true,
|
package/src/solver.js
CHANGED
|
@@ -1428,12 +1428,12 @@ function pushWfsAnswerFrames(stack, model, group, goal, rest, env, depth, active
|
|
|
1428
1428
|
|
|
1429
1429
|
function bundledBetweenIterator(solver, group, goal, env) {
|
|
1430
1430
|
if (solver.registry.eyePrologLibrary !== true ||
|
|
1431
|
-
group.module !== '
|
|
1431
|
+
group.module !== 'between' || group.name !== 'between' || group.arity !== 3 ||
|
|
1432
1432
|
group.bundledLibrary !== true || group.clauses.length !== 1) {
|
|
1433
1433
|
return null;
|
|
1434
1434
|
}
|
|
1435
1435
|
|
|
1436
|
-
// The portable
|
|
1436
|
+
// The portable library(between) definition is kept as the semantic
|
|
1437
1437
|
// source of between/3. Its recursive helper, however, carries the output
|
|
1438
1438
|
// variable through one fresh clause instance per integer. With persistent
|
|
1439
1439
|
// environments that builds a growing variable-alias chain, so dereferencing
|
|
@@ -1475,7 +1475,7 @@ function* bundledBetweenSolutions(solver, goal, env) {
|
|
|
1475
1475
|
|
|
1476
1476
|
function bundledMemberIterator(solver, group, goal, env) {
|
|
1477
1477
|
if (solver.registry.eyePrologLibrary !== true ||
|
|
1478
|
-
|
|
1478
|
+
group.module !== 'lists' || group.name !== 'member' || group.arity !== 2 ||
|
|
1479
1479
|
group.bundledLibrary !== true || group.clauses.length !== 2) {
|
|
1480
1480
|
return null;
|
|
1481
1481
|
}
|
|
@@ -1588,7 +1588,7 @@ function* bundledEllipsisSolutions(solver, output, input, env) {
|
|
|
1588
1588
|
|
|
1589
1589
|
function bundledLengthIterator(solver, group, goal, env) {
|
|
1590
1590
|
if (solver.registry.eyePrologLibrary !== true ||
|
|
1591
|
-
|
|
1591
|
+
group.module !== 'lists' || group.name !== 'length' || group.arity !== 2 ||
|
|
1592
1592
|
group.bundledLibrary !== true || group.clauses.length !== 2) {
|
|
1593
1593
|
return null;
|
|
1594
1594
|
}
|
|
@@ -1900,7 +1900,7 @@ function findallLengthFusion(solver, goal, rest, env) {
|
|
|
1900
1900
|
if (lengthGoal.type !== COMPOUND || lengthGoal.name !== 'length' || lengthGoal.arity !== 2) return null;
|
|
1901
1901
|
const lengthGroup = solver.program.findGroup('length', 2, lengthGoal.module ?? 'user');
|
|
1902
1902
|
if (!lengthGroup || lengthGroup.bundledLibrary !== true ||
|
|
1903
|
-
|
|
1903
|
+
lengthGroup.module !== 'lists') return null;
|
|
1904
1904
|
const lengthList = derefForLocal(lengthGoal.args[0], env);
|
|
1905
1905
|
const countArg = derefForLocal(lengthGoal.args[1], env);
|
|
1906
1906
|
if (lengthList.type !== 'var' || lengthList.name !== bag.name || countArg.type !== 'var') return null;
|
package/src/standard-library.js
CHANGED
|
@@ -141,11 +141,10 @@ export const eyePrologInteropAutoload = Object.freeze({
|
|
|
141
141
|
// unchanged while their implementations remain outside the ISO core.
|
|
142
142
|
'time/1': 'iso_ext',
|
|
143
143
|
'.../2': 'iso_ext',
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
'between/3': 'prologue',
|
|
144
|
+
// Scryer provides library(between), while Trealla exposes the relation as a
|
|
145
|
+
// system predicate. Keep EyeProlog's canonical portable owner in the
|
|
146
|
+
// specialized module; library(prologue) re-exports it for compatibility.
|
|
147
|
+
'between/3': 'between',
|
|
149
148
|
});
|
|
150
149
|
|
|
151
150
|
export const eyePrologInteropLibraryIndicators = Object.freeze(
|
|
@@ -155,7 +154,7 @@ export const eyePrologInteropLibraryIndicators = Object.freeze(
|
|
|
155
154
|
// Libraries whose *name* is part of the current interop profile. A program
|
|
156
155
|
// may freely use_module/1 with these common module names; predicates in those
|
|
157
156
|
// modules outside eyePrologInteropLibraryIndicators are still diagnosed when used.
|
|
158
|
-
export const eyePrologInteropLibraryModules = Object.freeze(['lists', 'iso_ext', 'lambda', 'atts']);
|
|
157
|
+
export const eyePrologInteropLibraryModules = Object.freeze(['lists', 'iso_ext', 'lambda', 'atts', 'freeze']);
|
|
159
158
|
|
|
160
159
|
function* tabledNegationBuiltin({ solver, goal, env }) {
|
|
161
160
|
yield* solver.solveTabledNegation(goal.args[0], env);
|
package/src/term.js
CHANGED
|
@@ -650,6 +650,7 @@ export function unify(left, right, env, options = {}) {
|
|
|
650
650
|
// unification: a variable cannot be bound to a term containing itself.
|
|
651
651
|
// Bindings are written into the supplied Env.
|
|
652
652
|
const occursCheckHandler = options.occursCheck === 'fail' ? null : env?._occursCheckHandler;
|
|
653
|
+
const runAttributeHooks = options.skipAttributeHooks !== true;
|
|
653
654
|
// Callers may provide a proof that selected variables cannot occur in the
|
|
654
655
|
// term they are about to receive. Source-level first-use analysis and a few
|
|
655
656
|
// construction fast paths share this internal proof; ordinary unification
|
|
@@ -663,7 +664,7 @@ export function unify(left, right, env, options = {}) {
|
|
|
663
664
|
|
|
664
665
|
if (a.type === VAR && b.type === VAR && a.name === b.name) continue;
|
|
665
666
|
if (a.type === VAR && b.type === VAR) {
|
|
666
|
-
if (env?._prologAttributes != null) {
|
|
667
|
+
if (runAttributeHooks && env?._prologAttributes != null) {
|
|
667
668
|
if (!env.preparePrologAttributeUnification(a, b)) return false;
|
|
668
669
|
a = deref(a, env);
|
|
669
670
|
b = deref(b, env);
|
|
@@ -694,7 +695,7 @@ export function unify(left, right, env, options = {}) {
|
|
|
694
695
|
occursCheckHandler?.(a, b, env);
|
|
695
696
|
return false;
|
|
696
697
|
}
|
|
697
|
-
if (env?._prologAttributes != null && env.hasPrologAttributes(a.name)) {
|
|
698
|
+
if (runAttributeHooks && env?._prologAttributes != null && env.hasPrologAttributes(a.name)) {
|
|
698
699
|
if (!env.preparePrologAttributeUnification(a, b)) return false;
|
|
699
700
|
a = deref(a, env);
|
|
700
701
|
b = deref(b, env);
|
|
@@ -712,7 +713,7 @@ export function unify(left, right, env, options = {}) {
|
|
|
712
713
|
occursCheckHandler?.(b, a, env);
|
|
713
714
|
return false;
|
|
714
715
|
}
|
|
715
|
-
if (env?._prologAttributes != null && env.hasPrologAttributes(b.name)) {
|
|
716
|
+
if (runAttributeHooks && env?._prologAttributes != null && env.hasPrologAttributes(b.name)) {
|
|
716
717
|
if (!env.preparePrologAttributeUnification(b, a)) return false;
|
|
717
718
|
b = deref(b, env);
|
|
718
719
|
a = deref(a, env);
|
package/test/fixtures/README.md
CHANGED
|
@@ -9,6 +9,14 @@ Retrieved on 2026-08-11. It is vendored so the regression suite exercises all
|
|
|
9
9
|
58 quads without depending on network access or availability of the source
|
|
10
10
|
server.
|
|
11
11
|
|
|
12
|
+
`variable_names_quad.pl` is an unmodified snapshot of the 75 machine-readable
|
|
13
|
+
quads for the ISO read and write option `variable_names/1`:
|
|
14
|
+
|
|
15
|
+
<https://www.complang.tuwien.ac.at/ulrich/iso-prolog/variable_names_quad.pl>
|
|
16
|
+
|
|
17
|
+
Retrieved on 2026-08-25. It is vendored so all input, output, waiting, and
|
|
18
|
+
error cases remain release-gated.
|
|
19
|
+
|
|
12
20
|
`prologue_quad.pl` is an unmodified snapshot of the 33 machine-readable quads
|
|
13
21
|
for the predicates proposed by the Prolog Prologue working draft:
|
|
14
22
|
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
1 ?- write_term(T,[quoted(true),variable_names([N=T])]).
|
|
2
|
+
instantiation_error.
|
|
3
|
+
2 ?- N = 'X', /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
4
|
+
outputs("X"), N = 'X'.
|
|
5
|
+
outputs("X"), N = ... . % Same, ignoring 'X'
|
|
6
|
+
3 ?- N = T, /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
7
|
+
instantiation_error.
|
|
8
|
+
4 ?- N = '_', /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
9
|
+
outputs("_"), N = ... .
|
|
10
|
+
65 ?- N = '_/*.*/', /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
11
|
+
outputs("_/*.*/"), N = ... .
|
|
12
|
+
5 ?- N = x, /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
13
|
+
outputs("x"), N = ... .
|
|
14
|
+
6 ?- N = 'x+y', /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
15
|
+
outputs("x+y"), N = ... .
|
|
16
|
+
50 ?- N = '))', /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
17
|
+
outputs("))"), N = ... .
|
|
18
|
+
7 ?- N = 7, /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
19
|
+
domain_error(write_option, variable_names(...)).
|
|
20
|
+
8 ?- N = 1+2, /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
21
|
+
domain_error(write_option, variable_names(...)).
|
|
22
|
+
9 ?- N = '$VAR'(9), /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
23
|
+
domain_error(write_option, variable_names(...)).
|
|
24
|
+
10 ?- T = a, /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
25
|
+
instantiation_error.
|
|
26
|
+
11 ?- T = a, N = 'Any', /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
27
|
+
outputs("a"), T = ..., N = ... .
|
|
28
|
+
12 ?- T = '$VAR'(9), N = '_', /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
29
|
+
outputs("'$VAR'(9)"), T = ..., N = ... .
|
|
30
|
+
74 ?- T = f(_), N = 'Bad', /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
31
|
+
outputs(("f(_",...,")")), T = ..., N = ... .
|
|
32
|
+
28 ?- freeze(T,throw(g(T))), N = 'X', /**/write_term(T,[quoted(true),variable_names([N=T])]), false.
|
|
33
|
+
outputs("X"), false.
|
|
34
|
+
13 ?- write_term(T,[quoted(true),variable_names(['X'=X,'Y'=Y,'Z'=Z])]).
|
|
35
|
+
outputs(("_",...)).
|
|
36
|
+
14 ?- T=(X,Y,Z), /**/write_term(T,[quoted(true),variable_names(['X'=X,'Y'=Y,'Z'=Z])]).
|
|
37
|
+
outputs("X,Y,Z"), T = ... .
|
|
38
|
+
15 ?- Z=Y, T=(X,Y,Z), /**/write_term(T,[quoted(true),variable_names(['X'=X,'Y'=Y,'Z'=Z])]).
|
|
39
|
+
outputs("X,Y,Y"), Z = Y, T = ... .
|
|
40
|
+
16 ?- Z=Y, Y=X, T=(X,Y,Z), /**/write_term(T,[quoted(true),variable_names(['X'=X,'Y'=Y,'Z'=Z])]).
|
|
41
|
+
outputs("X,X,X"), Z = X, Y = X, T = ... .
|
|
42
|
+
17 ?- T=(Y,Z), /**/write_term(T,[quoted(true),variable_names(['X'=X,'Y'=Y,'Z'=Z])]).
|
|
43
|
+
outputs("Y,Z"), T = ... .
|
|
44
|
+
18 ?- T=(Z,Y), /**/write_term(T,[quoted(true),variable_names(['X'=X,'Y'=Y,'Z'=Z])]).
|
|
45
|
+
outputs("Z,Y"), T = ... .
|
|
46
|
+
19 ?- write_term(T,[quoted(true),variable_names(['Z'=Z,'Y'=Y,'X'=X])]).
|
|
47
|
+
outputs(("_",...)).
|
|
48
|
+
20 ?- T=(X,Y,Z), /**/write_term(T,[quoted(true),variable_names(['Z'=Z,'Y'=Y,'X'=X])]).
|
|
49
|
+
outputs("X,Y,Z"), T = ... .
|
|
50
|
+
21 ?- Z=Y, T=(X,Y,Z), /**/write_term(T,[quoted(true),variable_names(['Z'=Z,'Y'=Y,'X'=X])]).
|
|
51
|
+
outputs("X,Z,Z"), Z = Y, T = ... .
|
|
52
|
+
22 ?- Z=Y, Y=X, T=(X,Y,Z), /**/write_term(T,[quoted(true),variable_names(['Z'=Z,'Y'=Y,'X'=X])]).
|
|
53
|
+
outputs("Z,Z,Z"), Z = X, Y = X, T = ... .
|
|
54
|
+
23 ?- T=(Y,Z), /**/write_term(T,[quoted(true),variable_names(['Z'=Z,'Y'=Y,'X'=X])]).
|
|
55
|
+
outputs("Y,Z"), T = ... .
|
|
56
|
+
24 ?- T=(Z,Y), /**/write_term(T,[quoted(true),variable_names(['Z'=Z,'Y'=Y,'X'=X])]).
|
|
57
|
+
outputs("Z,Y"), T = ... .
|
|
58
|
+
25 ?- write_term(T,[quoted(true),variable_names(['X'=Z,'X'=Y,'X'=X])]).
|
|
59
|
+
outputs(("_",...)).
|
|
60
|
+
26 ?- T=(X,Y,Z), /**/write_term(T,[quoted(true),variable_names(['X'=Z,'X'=Y,'X'=X])]).
|
|
61
|
+
outputs("X,X,X"), T = ... .
|
|
62
|
+
27 ?- T=(1,2,3), T=(X,Y,Z), /**/write_term(T,[quoted(true),variable_names(['X'=Z,'X'=Y,'X'=X])]).
|
|
63
|
+
outputs("1,2,3"), T = ..., X = 1, Y = 2, Z = 3.
|
|
64
|
+
32 ?- read_term(T,[variable_names(VN_list)]),VN_list=[_=1,_=2,_=3],writeq(VN_list).
|
|
65
|
+
waits.
|
|
66
|
+
29 ?- /**/read_term(T,[variable_names(VN_list)]),VN_list=[_=1,_=2,_=3],writeq(VN_list).
|
|
67
|
+
inputs("B+C+A+B+C+A."), peeks("\n"),
|
|
68
|
+
outputs("['B'=1,'C'=2,'A'=3]"),
|
|
69
|
+
T = 1+2+3+1+2+3,
|
|
70
|
+
VN_list=['B'=1,'C'=2,'A'=3].
|
|
71
|
+
30 ?- write_term(T,[variable_names(VN_list)]).
|
|
72
|
+
instantiation_error.
|
|
73
|
+
31 ?- VN_list = 1, /**/write_term(T,[variable_names(VN_list)]).
|
|
74
|
+
domain_error(write_option, variable_names(1)).
|
|
75
|
+
33 ?- VN_list = [[]], /**/write_term(T,[variable_names(VN_list)]).
|
|
76
|
+
domain_error(write_option, variable_names([[]])).
|
|
77
|
+
34 ?- VN_list = non_list, /**/write_term(T,[variable_names(VN_list)]).
|
|
78
|
+
domain_error(write_option, variable_names(non_list)).
|
|
79
|
+
35 ?- VN_list = [T='T'|non_list], /**/write_term(T,[variable_names(VN_list)]).
|
|
80
|
+
domain_error(write_option, variable_names([_='T'|non_list]))
|
|
81
|
+
| instantiation_error.
|
|
82
|
+
52 ?- VN_list = ['T'=T|_], /**/write_term(T,[variable_names(VN_list)]).
|
|
83
|
+
instantiation_error.
|
|
84
|
+
51 ?- VN_list = ['T'=T|non_list], /**/write_term(T,[variable_names(VN_list)]).
|
|
85
|
+
domain_error(write_option, variable_names(...)).
|
|
86
|
+
36 ?- VN_list = [T-'T'], /**/write_term(T,[variable_names(VN_list)]).
|
|
87
|
+
domain_error(write_option, variable_names(...)).
|
|
88
|
+
63 ?- VN_list = [_,a], /**/write_term(T,[variable_names(VN_list)]).
|
|
89
|
+
domain_error(write_option, variable_names(...))
|
|
90
|
+
| instantiation_error.
|
|
91
|
+
64 ?- VN_list = [a,_], /**/write_term(T,[variable_names(VN_list)]).
|
|
92
|
+
domain_error(write_option, variable_names(...))
|
|
93
|
+
| instantiation_error.
|
|
94
|
+
66 ?- VN_list = [a|_], /**/write_term(T,[variable_names(VN_list)]).
|
|
95
|
+
domain_error(write_option, variable_names(...))
|
|
96
|
+
| instantiation_error.
|
|
97
|
+
67 ?- VN_list = [i=i,7=i], /**/write_term(T,[variable_names(VN_list)]).
|
|
98
|
+
domain_error(write_option,variable_names([i=i,7=i])).
|
|
99
|
+
68 ?- VN_list = [_,_], /**/write_term(T,[variable_names(VN_list)]).
|
|
100
|
+
instantiation_error.
|
|
101
|
+
43 ?- write_term(-X^2,[variable_names(['X'=X])]).
|
|
102
|
+
outputs("- (X^2)").
|
|
103
|
+
44 ?- X=1, /**/write_term(-X^2,[variable_names(['X'=X])]).
|
|
104
|
+
outputs("- (1^2)"), X = ... .
|
|
105
|
+
37 ?- open(f,write,_,[O]).
|
|
106
|
+
instantiation_error.
|
|
107
|
+
38 ?- O = 1, /**/open(f,write,_,[O]).
|
|
108
|
+
domain_error(stream_option,1).
|
|
109
|
+
56 ?- O = typex(_), /**/open(f,write,_,[O]).
|
|
110
|
+
domain_error(stream_option,typex(_)).
|
|
111
|
+
57 ?- O = typex(1), /**/open(f,write,_,[O]).
|
|
112
|
+
domain_error(stream_option,typex(1)).
|
|
113
|
+
62 ?- O = typex(s(_)), /**/open(f,write,_,[O]).
|
|
114
|
+
domain_error(stream_option,typex(s(_))).
|
|
115
|
+
39 ?- O = type(text), /**/open(f,write,_,[O]).
|
|
116
|
+
O = ... .
|
|
117
|
+
40 ?- O = type(1), /**/open(f,write,_,[O]).
|
|
118
|
+
domain_error(stream_option,type(1)).
|
|
119
|
+
41 ?- O = type(_), /**/open(f,write,_,[O]).
|
|
120
|
+
instantiation_error.
|
|
121
|
+
60 ?- O = alias(_), /**/open(f,write,_,[O]).
|
|
122
|
+
instantiation_error.
|
|
123
|
+
42 ?- O = type(nontype), /**/open(f,write,_,[O]).
|
|
124
|
+
domain_error(stream_option,type(nontype)).
|
|
125
|
+
61 ?- O = alias(1), /**/open(f,write,_,[O]).
|
|
126
|
+
domain_error(stream_option,alias(1)).
|
|
127
|
+
45 ?- read_term(T,[variable_names(VN_list)]).
|
|
128
|
+
waits.
|
|
129
|
+
46 ?- /**/read_term(T,[variable_names(VN_list)]).
|
|
130
|
+
inputs("a."), peeks("\n"),
|
|
131
|
+
T = a, VN_list = [].
|
|
132
|
+
47 ?- VN_list = 42, /**/read_term(T,[variable_names(VN_list)]).
|
|
133
|
+
waits.
|
|
134
|
+
48 ?- VN_list = 42, /**/read_term(T,[variable_names(VN_list)]).
|
|
135
|
+
inputs("a."), peeks("\n"),
|
|
136
|
+
false.
|
|
137
|
+
49 ?- VN_list = 42, /**/read_term(T,[variable_names(VN_list)]).
|
|
138
|
+
inputs("a b."), peeks("\n"),
|
|
139
|
+
syntax_error(...).
|
|
140
|
+
53 ?- write_term(S,[quoted(true),variable_names([N=T])]).
|
|
141
|
+
instantiation_error.
|
|
142
|
+
54 ?- S=1+T,N='/*r*/V',/**/write_term(S,[quoted(true),variable_names([N=T])]).
|
|
143
|
+
outputs("1+/*r*/V"), S = ..., N = ...
|
|
144
|
+
| outputs("1+ /*r*/V"), S = ..., N = ... .
|
|
145
|
+
55 ?- S=1+T,N=' /*r*/V',/**/write_term(S,[quoted(true),variable_names([N=T])]).
|
|
146
|
+
outputs("1+ /*r*/V"), S = ..., N = ... .
|
|
147
|
+
58 ?- S=1+T,N=(+),/**/write_term(S,[quoted(true),variable_names([N=T])]).
|
|
148
|
+
outputs("1++"), S = ..., N = ...
|
|
149
|
+
| outputs("1+ +"), S = ..., N = ... .
|
|
150
|
+
59 ?- S=T+1,N=(+),/**/write_term(S,[quoted(true),variable_names([N=T])]).
|
|
151
|
+
outputs("++1"), S = ..., N = ...
|
|
152
|
+
| outputs("+ +1"), S = ..., N = ... .
|
|
153
|
+
73 ?- S=(1 is T),N='X',/**/write_term(S,[quoted(true),variable_names([N=T])]).
|
|
154
|
+
outputs("1 is X"), S = ..., N = ... .
|
|
155
|
+
69 ?- read_term(T,[singletons(1)]).
|
|
156
|
+
waits.
|
|
157
|
+
70 ?- /**/read_term(T,[singletons(1)]).
|
|
158
|
+
inputs("a."), peeks("\n"),
|
|
159
|
+
false.
|
|
160
|
+
71 ?- write_term(T,[variable_names(['Bad'=T]),variable_names(['Good'=T])]).
|
|
161
|
+
outputs("Good").
|
|
162
|
+
72 ?- read_term(T,[singletons([])]).
|
|
163
|
+
waits.
|
package/test/run-regression.mjs
CHANGED
|
@@ -127,6 +127,17 @@ function regressionCases() {
|
|
|
127
127
|
'scalar-fact fast matching still validates annotated variables');
|
|
128
128
|
assertEqual(run('p(b).', { goal: 'dif(X,a),p(X)' }).stats.completed_goal_lists, 1,
|
|
129
129
|
'scalar-fact fast matching can discharge an annotated disequality');
|
|
130
|
+
|
|
131
|
+
const freezeProgram = Program.parse(':- use_module(library(freeze), [freeze/2]).');
|
|
132
|
+
const freezeSolver = new Solver(freezeProgram);
|
|
133
|
+
const freezeDifGoal = parseGoalText('freeze(X,a),dif(X,c)');
|
|
134
|
+
const freezeDifAnswer = freezeSolver.solve([freezeDifGoal], new Env(), 0).next();
|
|
135
|
+
assertEqual(freezeDifAnswer.done, false, 'freeze/2 and dif/2 answer');
|
|
136
|
+
const freezeResiduals = freezeSolver.attributeResidualGoals(
|
|
137
|
+
freezeDifGoal.args[0].args[0], freezeDifAnswer.value,
|
|
138
|
+
);
|
|
139
|
+
assertEqual(freezeResiduals.length, 1,
|
|
140
|
+
'freeze/2 residual projection terminates alongside dif/2 (issue #73)');
|
|
130
141
|
},
|
|
131
142
|
},
|
|
132
143
|
{
|
|
@@ -190,6 +201,7 @@ check :- call_residue_vars(X in 1..3, Vs), Vs = [X].
|
|
|
190
201
|
|
|
191
202
|
const residualFile = path.join(tmp, `atts-residual-${++tmpCounter}.pl`);
|
|
192
203
|
fs.writeFileSync(residualFile, `:- use_module(library(atts)).
|
|
204
|
+
:- use_module(library(freeze), [freeze/2]).
|
|
193
205
|
:- attribute required/1.
|
|
194
206
|
attach(X,V) :- put_atts(X, required(V)).
|
|
195
207
|
verify_attributes(Var, Other, Goals) :-
|
|
@@ -199,9 +211,13 @@ verify_attributes(Var, Other, Goals) :-
|
|
|
199
211
|
same(X,X).
|
|
200
212
|
attribute_goals(X) --> { get_atts(X, required(V)) }, [required(X,V)].
|
|
201
213
|
`);
|
|
202
|
-
const repl = runCli([], {
|
|
214
|
+
const repl = runCli([], {
|
|
215
|
+
input: `[${sourceAtom(residualFile)}].\nattach(X,a).\nfreeze(Y,a),dif(Y,c).\nhalt.\n`,
|
|
216
|
+
});
|
|
203
217
|
assertEqual(repl.status, 0, 'attribute residual REPL status');
|
|
204
218
|
assertIncludes(repl.stdout, 'required(X, a).', 'attribute_goals//1 residual projection');
|
|
219
|
+
assertIncludes(repl.stdout, 'dif(Y, c), freeze:freeze(Y, a).',
|
|
220
|
+
'freeze/2 and dif/2 residuals share the query variable (issue #73)');
|
|
205
221
|
|
|
206
222
|
const scryerShape = Program.parse(`:- module(scryer_shape, [op(700, xfx, #=), probe/0]).
|
|
207
223
|
:- op(700, xfx, #=).
|
|
@@ -1549,6 +1565,18 @@ c4 ?- call((!;1)).
|
|
|
1549
1565
|
assertEqual(result.stderr, '', 'quad stderr');
|
|
1550
1566
|
},
|
|
1551
1567
|
},
|
|
1568
|
+
{
|
|
1569
|
+
name: 'CLI passes the complete authoritative variable_names quad corpus (issue #69)',
|
|
1570
|
+
run: () => {
|
|
1571
|
+
const filename = path.join(testRoot, 'fixtures', 'variable_names_quad.pl');
|
|
1572
|
+
const source = fs.readFileSync(filename, 'utf8');
|
|
1573
|
+
assertEqual(Program.parse(source).quads.length, 74, 'vendored query-record total');
|
|
1574
|
+
const result = runCli(['-q', filename], { cwd: tmp });
|
|
1575
|
+
assertEqual(result.status, 0, 'quad exit status');
|
|
1576
|
+
assertEqual(result.stdout, 'quads: 75 run, 75 passed, 0 failed.\n', 'quad report');
|
|
1577
|
+
assertEqual(result.stderr, '', 'quad stderr');
|
|
1578
|
+
},
|
|
1579
|
+
},
|
|
1552
1580
|
{
|
|
1553
1581
|
name: 'REPL advances anonymous Prologue length checks through I = 28',
|
|
1554
1582
|
run: () => {
|
|
@@ -1809,6 +1837,15 @@ c4 ?- call((!;1)).
|
|
|
1809
1837
|
assertEqual(result.stderr, '', 'stderr');
|
|
1810
1838
|
},
|
|
1811
1839
|
},
|
|
1840
|
+
{
|
|
1841
|
+
name: 'REPL displays uncaught user exceptions as throw terms (issue #75)',
|
|
1842
|
+
run: () => {
|
|
1843
|
+
const result = runCli([], { input: 'throw(stopped).\nhalt.\n' });
|
|
1844
|
+
assertEqual(result.status, 0, 'exit status');
|
|
1845
|
+
assertEqual(result.stdout, '?- throw(stopped).\n?- ', 'stdout');
|
|
1846
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
1847
|
+
},
|
|
1848
|
+
},
|
|
1812
1849
|
{
|
|
1813
1850
|
name: 'REPL freshens variables displayed in uncaught ISO errors',
|
|
1814
1851
|
run: () => {
|
|
@@ -2814,6 +2851,22 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
|
|
|
2814
2851
|
assertEqual(result.stderr, '', 'stderr');
|
|
2815
2852
|
},
|
|
2816
2853
|
},
|
|
2854
|
+
{
|
|
2855
|
+
name: 'REPL combines canonical libraries with the Prologue facade',
|
|
2856
|
+
run: () => {
|
|
2857
|
+
const result = runCli([], {
|
|
2858
|
+
input:
|
|
2859
|
+
'use_module(library(freeze)).\n' +
|
|
2860
|
+
'use_module(library(lists)).\n' +
|
|
2861
|
+
'use_module(library(iso_ext)).\n' +
|
|
2862
|
+
'use_module(library(prologue)).\n' +
|
|
2863
|
+
'halt.\n',
|
|
2864
|
+
});
|
|
2865
|
+
assertEqual(result.status, 0, 'exit status');
|
|
2866
|
+
assertEqual(result.stdout, '?- true.\n?- true.\n?- true.\n?- true.\n?- ', 'stdout');
|
|
2867
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
2868
|
+
},
|
|
2869
|
+
},
|
|
2817
2870
|
{
|
|
2818
2871
|
name: 'REPL halt status is returned by the CLI',
|
|
2819
2872
|
run: () => {
|
|
@@ -4550,7 +4603,7 @@ answer(ok) :-
|
|
|
4550
4603
|
const program = Program.parse('answer(X) :- member(X, [a]), between(1, 1, _).\n');
|
|
4551
4604
|
assertEqual(program.autoloadedPredicates.length, 2, 'autoloaded predicate count');
|
|
4552
4605
|
assertEqual(program.autoloadedPredicates.map((entry) => `${entry.indicator}:${entry.library}`).sort().join(','),
|
|
4553
|
-
'between/3:
|
|
4606
|
+
'between/3:between,member/2:lists', 'autoload mapping');
|
|
4554
4607
|
assertEqual(program.interopPortabilityWarnings.length, 0, 'autoloaded portable calls are warning-free');
|
|
4555
4608
|
},
|
|
4556
4609
|
},
|
|
@@ -5034,9 +5087,9 @@ answer(ok) :-
|
|
|
5034
5087
|
assertEqual(registeredNativeEyePrologLibraryNames().length, 49, 'public native EyeProlog builtin count');
|
|
5035
5088
|
assertEqual(eyePrologPortableLibraryIndicators.length, 87, 'portable Prolog library count');
|
|
5036
5089
|
assertEqual(eyePrologInteropLibraryIndicators.length, 30, 'cross-implementation interop profile count');
|
|
5037
|
-
assertEqual(eyePrologInteropLibraryModules.join(','), 'lists,iso_ext,lambda,atts', 'common explicit library module profile');
|
|
5090
|
+
assertEqual(eyePrologInteropLibraryModules.join(','), 'lists,iso_ext,lambda,atts,freeze', 'common explicit library module profile');
|
|
5038
5091
|
assertEqual(eyePrologInteropAutoload['member/2'], 'lists', 'member/2 canonical autoload');
|
|
5039
|
-
assertEqual(eyePrologInteropAutoload['between/3'], '
|
|
5092
|
+
assertEqual(eyePrologInteropAutoload['between/3'], 'between', 'between/3 canonical internal autoload');
|
|
5040
5093
|
assertEqual(eyePrologInteropAutoload['call_nth/2'], 'iso_ext', 'call_nth/2 canonical interop autoload');
|
|
5041
5094
|
assertEqual(eyePrologInteropAutoload['call_residue_vars/2'], 'atts', 'call_residue_vars/2 canonical interop autoload');
|
|
5042
5095
|
assertEqual(eyePrologInteropAutoload['time/1'], 'iso_ext', 'time/1 canonical interop autoload');
|
|
@@ -5097,13 +5150,41 @@ answer(ok) :-
|
|
|
5097
5150
|
assertEqual(program.findGroup('random', 3)?.module, 'random', 'random/3 is imported from library(random)');
|
|
5098
5151
|
},
|
|
5099
5152
|
},
|
|
5153
|
+
{
|
|
5154
|
+
name: 'Prologue facade re-exports canonical library predicates without conflicts',
|
|
5155
|
+
run: () => {
|
|
5156
|
+
const sources = [
|
|
5157
|
+
':- use_module(library(freeze)).\n:- use_module(library(lists)).\n:- use_module(library(iso_ext)).\n:- use_module(library(prologue)).',
|
|
5158
|
+
':- use_module(library(prologue)).\n:- use_module(library(freeze)).\n:- use_module(library(lists)).\n:- use_module(library(iso_ext)).',
|
|
5159
|
+
];
|
|
5160
|
+
for (const source of sources) {
|
|
5161
|
+
const program = Program.parse(source);
|
|
5162
|
+
assertEqual(program.findGroup('freeze', 2)?.module, 'freeze', 'freeze/2 canonical owner');
|
|
5163
|
+
assertEqual(program.findGroup('member', 2)?.module, 'lists', 'member/2 canonical owner');
|
|
5164
|
+
assertEqual(program.findGroup('call_nth', 2)?.module, 'iso_ext', 'call_nth/2 canonical owner');
|
|
5165
|
+
assertEqual(program.findGroup('between', 3)?.module, 'between', 'between/3 canonical owner');
|
|
5166
|
+
}
|
|
5167
|
+
|
|
5168
|
+
const conflicting = new Program();
|
|
5169
|
+
conflicting.defineModule('first', [{ name: 'shared', arity: 1 }]);
|
|
5170
|
+
conflicting.defineModule('second', [{ name: 'shared', arity: 1 }]);
|
|
5171
|
+
conflicting.importModule('user', 'first');
|
|
5172
|
+
let conflict = null;
|
|
5173
|
+
try {
|
|
5174
|
+
conflicting.importModule('user', 'second');
|
|
5175
|
+
} catch (error) {
|
|
5176
|
+
conflict = error;
|
|
5177
|
+
}
|
|
5178
|
+
assertEqual(conflict?.formal, 'permission_error(import, procedure)', 'distinct implementations still conflict');
|
|
5179
|
+
},
|
|
5180
|
+
},
|
|
5100
5181
|
{
|
|
5101
5182
|
name: 'aligned bundled libraries have no overlapping full-module exports',
|
|
5102
5183
|
run: () => {
|
|
5103
5184
|
const owners = new Map();
|
|
5104
5185
|
for (const [moduleName, entry] of standardLibrarySources) {
|
|
5105
|
-
// library(prologue) is
|
|
5106
|
-
//
|
|
5186
|
+
// library(prologue) is a facade whose nominal exports are re-exports;
|
|
5187
|
+
// canonical libraries must remain pairwise collision-free so full
|
|
5107
5188
|
// use_module/1 imports can be combined safely.
|
|
5108
5189
|
if (moduleName === 'prologue') continue;
|
|
5109
5190
|
const parsed = Program.parse(entry.source);
|
|
@@ -6525,7 +6606,7 @@ child.on('close', (status, signal) => {
|
|
|
6525
6606
|
|
|
6526
6607
|
function runCli(args, options = {}) {
|
|
6527
6608
|
return spawnSync(process.execPath, [bin, ...args], {
|
|
6528
|
-
cwd: packageRoot,
|
|
6609
|
+
cwd: options.cwd ?? packageRoot,
|
|
6529
6610
|
encoding: 'utf8',
|
|
6530
6611
|
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
6531
6612
|
input: options.input ?? undefined,
|
package/test/test-support.mjs
CHANGED
|
@@ -10,7 +10,7 @@ const standardModulePrelude = `:- use_module(library(aggregate)).
|
|
|
10
10
|
:- use_module(library(iso_ext)).
|
|
11
11
|
:- use_module(library(lists)).
|
|
12
12
|
:- use_module(library(primes)).
|
|
13
|
-
:- use_module(library(
|
|
13
|
+
:- use_module(library(between), [between/3]).
|
|
14
14
|
:- use_module(library(random)).
|
|
15
15
|
:- use_module(library(strings)).
|
|
16
16
|
:- use_module(library(uuid)).
|