eyeprolog 1.3.92 → 1.3.94
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/examples/book/README.md +1 -0
- package/examples/book/chapter-40/02-program.pl +5 -4
- package/examples/book/chapter-40/03-program-2.pl +4 -4
- package/examples/book/chapter-40/04-program-3.pl +5 -0
- package/package.json +1 -1
- package/src/quads.js +23 -1
- package/src/solver.js +9 -2
- package/test/run-regression.mjs +45 -14
- package/the-art-of-eyeprolog.md +22 -2
package/examples/book/README.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
% From The Art of EyeProlog, Chapter 40.
|
|
2
|
-
?-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
?- dif(X,Y), X = a.
|
|
3
|
+
true, unexpected.
|
|
4
|
+
X = a, unexpected.
|
|
5
|
+
X = a, maybe.
|
|
6
|
+
maybe, unexpected.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
% From The Art of EyeProlog, Chapter 40.
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
?- read(X).
|
|
3
|
+
inputs("1."), X = 1, unexpected.
|
|
4
|
+
inputs("1."), peeks(" "), X = 1.
|
|
5
|
+
inputs("1. "), peeks(" "), X = 1, unexpected.
|
package/package.json
CHANGED
package/src/quads.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Embedded quad tests: a query followed by one or more answer descriptions.
|
|
2
2
|
// The syntax follows the portable "queries using answer descriptions" format
|
|
3
3
|
// used by Trealla and the ISO Prolog working examples linked from issue #1.
|
|
4
|
+
// A `maybe` annotation denotes a successful answer with residual constraints;
|
|
5
|
+
// it does not relax the ordinary answer-substitution comparison.
|
|
4
6
|
import {
|
|
5
7
|
ATOM, COMPOUND, VAR, Env, atom, compound, copyResolved, deref,
|
|
6
8
|
flattenConjunction, listFromItems, properListItems, termIsGround,
|
|
@@ -203,6 +205,7 @@ function describeLeaf(term) {
|
|
|
203
205
|
sto: false,
|
|
204
206
|
false: false,
|
|
205
207
|
truth: false,
|
|
208
|
+
maybe: false,
|
|
206
209
|
loops: false,
|
|
207
210
|
waits: false,
|
|
208
211
|
error: null,
|
|
@@ -218,6 +221,7 @@ function describeLeaf(term) {
|
|
|
218
221
|
if (item.name === 'unexpected' || item.name === 'inattendue') leaf.unexpected = true;
|
|
219
222
|
else if (item.name === '...' || item.name === 'ad_infinitum') leaf.more = true;
|
|
220
223
|
else if (item.name === 'sto') leaf.sto = true;
|
|
224
|
+
else if (item.name === 'maybe') leaf.maybe = true;
|
|
221
225
|
else if (item.name === 'loops') leaf.loops = true;
|
|
222
226
|
else if (item.name === 'waits') leaf.waits = true;
|
|
223
227
|
else if (item.name === 'other_answer_sequence') {
|
|
@@ -253,10 +257,11 @@ function describeLeaf(term) {
|
|
|
253
257
|
if (isErrorDescription(item)) leaf.error = item;
|
|
254
258
|
else leaf.malformed ??= item;
|
|
255
259
|
}
|
|
256
|
-
leaf.hasExpectation = leaf.bindings.length > 0 || leaf.truth || leaf.false || leaf.loops || leaf.waits ||
|
|
260
|
+
leaf.hasExpectation = leaf.bindings.length > 0 || leaf.truth || leaf.false || leaf.maybe || leaf.loops || leaf.waits ||
|
|
257
261
|
leaf.error != null || leaf.output != null;
|
|
258
262
|
if (!leaf.hasExpectation && !leaf.more && !leaf.sto && leaf.unsupported == null) leaf.malformed ??= term;
|
|
259
263
|
if ([leaf.false, leaf.truth, leaf.error != null].filter(Boolean).length > 1) leaf.malformed ??= term;
|
|
264
|
+
if (leaf.maybe && (leaf.false || leaf.loops || leaf.waits || leaf.error != null)) leaf.malformed ??= term;
|
|
260
265
|
return leaf;
|
|
261
266
|
}
|
|
262
267
|
|
|
@@ -386,9 +391,26 @@ function matchLeaf(program, query, leaf, actual, position) {
|
|
|
386
391
|
}
|
|
387
392
|
const solution = actual.solutions[position];
|
|
388
393
|
if (!solution || !outputMatches(program, leaf.output, solution.output)) return false;
|
|
394
|
+
// Portable answer descriptions distinguish plain success from success with
|
|
395
|
+
// residual constraints. `maybe` requires at least one pending residue, while
|
|
396
|
+
// its absence requires an unconstrained answer. In either case the stated
|
|
397
|
+
// substitutions remain exact and are checked below.
|
|
398
|
+
if (leaf.maybe !== hasPendingConstraints(solution.env)) return false;
|
|
389
399
|
return substitutionMatches(query, leaf.bindings, solution.env);
|
|
390
400
|
}
|
|
391
401
|
|
|
402
|
+
function hasPendingConstraints(env) {
|
|
403
|
+
for (const constraint of env.variableConstraints?.() ?? []) {
|
|
404
|
+
if (constraint.status?.(env) === 'pending') return true;
|
|
405
|
+
}
|
|
406
|
+
// Prolog attributed variables and delayed goals are the other residual-state
|
|
407
|
+
// mechanisms exposed by call_residue_vars/2. Treat either as pending residue
|
|
408
|
+
// even when a library does not provide a printable projection goal.
|
|
409
|
+
if ((env.attributedVariableNames?.() ?? []).length > 0) return true;
|
|
410
|
+
if ((env.delayedVariableNames?.() ?? []).length > 0) return true;
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
|
|
392
414
|
function descriptionDeclaresSto(description) {
|
|
393
415
|
return splitOperator(description, '|').some((alternative) =>
|
|
394
416
|
splitOperator(alternative, ';').some((term) => describeLeaf(term).sto));
|
package/src/solver.js
CHANGED
|
@@ -524,14 +524,21 @@ export class Solver {
|
|
|
524
524
|
if (this.solutionsSeen >= this.solutionLimit) break;
|
|
525
525
|
|
|
526
526
|
const pendingAttributeGoals = env.takePendingAttributeGoals?.() ?? [];
|
|
527
|
-
if (pendingAttributeGoals.length > 0)
|
|
527
|
+
if (pendingAttributeGoals.length > 0) {
|
|
528
|
+
// Goals produced by verify_attributes/3 are invoked by the attribute
|
|
529
|
+
// mechanism, not textually spliced into the caller. Give every
|
|
530
|
+
// awakened goal the same opaque cut boundary as call/1 so a delayed
|
|
531
|
+
// cut cannot prune choices that were made before the suspension.
|
|
532
|
+
const awakened = pendingAttributeGoals.map((pending) => compound('call', [pending]));
|
|
533
|
+
goals = [...awakened, ...goals];
|
|
534
|
+
}
|
|
528
535
|
|
|
529
536
|
const readyDelays = env.takeReadyDelays();
|
|
530
537
|
if (readyDelays.length > 0) {
|
|
531
538
|
const awakened = readyDelays.map(({ goal, module }) => {
|
|
532
539
|
const delayed = copyResolved(goal, env);
|
|
533
540
|
qualifyTerm(delayed, module);
|
|
534
|
-
return delayed;
|
|
541
|
+
return compound('call', [delayed]);
|
|
535
542
|
});
|
|
536
543
|
goals = [...awakened, ...goals];
|
|
537
544
|
}
|
package/test/run-regression.mjs
CHANGED
|
@@ -1990,6 +1990,22 @@ c4 ?- call((!;1)).
|
|
|
1990
1990
|
assertEqual(result.stdout, 'wake(ready, awake).\n', 'freeze answer');
|
|
1991
1991
|
},
|
|
1992
1992
|
},
|
|
1993
|
+
{
|
|
1994
|
+
name: 'Prologue freeze keeps awakened cuts inside the delayed call (issue #81)',
|
|
1995
|
+
run: () => {
|
|
1996
|
+
const result = publicApi.runQuads(`:- use_module(library(prologue)).
|
|
1997
|
+
38
|
|
1998
|
+
?- call( ( (Y=1;Y=2), freeze(X,!), X = c ) ); Y = 3.
|
|
1999
|
+
Y = 1, X = c
|
|
2000
|
+
; Y = 2, X = c
|
|
2001
|
+
; Y = 3.
|
|
2002
|
+
`);
|
|
2003
|
+
assertEqual(result.total, 1, 'quad total');
|
|
2004
|
+
assertEqual(result.passed, 1, 'quad passed');
|
|
2005
|
+
assertEqual(result.failed, 0, 'quad failed');
|
|
2006
|
+
assertEqual(result.stdout, 'quads: 1 run, 1 passed, 0 failed.\n', 'quad report');
|
|
2007
|
+
},
|
|
2008
|
+
},
|
|
1993
2009
|
{
|
|
1994
2010
|
name: 'runQuads covers the remaining finite Prologue examples and arities',
|
|
1995
2011
|
run: () => {
|
|
@@ -2005,6 +2021,25 @@ c4 ?- call((!;1)).
|
|
|
2005
2021
|
assertEqual(result.stdout, 'quads: 37 run, 37 passed, 0 failed.\n', 'quad report');
|
|
2006
2022
|
},
|
|
2007
2023
|
},
|
|
2024
|
+
{
|
|
2025
|
+
name: 'runQuads maybe requires pending constraints without weakening substitutions (issue #80)',
|
|
2026
|
+
run: () => {
|
|
2027
|
+
const result = publicApi.runQuads(`37
|
|
2028
|
+
?- dif(X,Y), X = a.
|
|
2029
|
+
true, unexpected.
|
|
2030
|
+
X = a, unexpected.
|
|
2031
|
+
X = a, maybe.
|
|
2032
|
+
maybe, unexpected.
|
|
2033
|
+
|
|
2034
|
+
?- X = a.
|
|
2035
|
+
X = a, maybe, unexpected.
|
|
2036
|
+
`);
|
|
2037
|
+
assertEqual(result.total, 5, 'quad total');
|
|
2038
|
+
assertEqual(result.passed, 5, 'quad passed');
|
|
2039
|
+
assertEqual(result.failed, 0, 'quad failed');
|
|
2040
|
+
assertEqual(result.stdout, 'quads: 5 run, 5 passed, 0 failed.\n', 'quad report');
|
|
2041
|
+
},
|
|
2042
|
+
},
|
|
2008
2043
|
{
|
|
2009
2044
|
name: 'runQuads rejects malformed answer substitutions',
|
|
2010
2045
|
run: () => {
|
|
@@ -3234,22 +3269,18 @@ c4 ?- call((!;1)).
|
|
|
3234
3269
|
{
|
|
3235
3270
|
name: 'REPL releases terminal signals while a query computes',
|
|
3236
3271
|
run: () => {
|
|
3237
|
-
if (
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
const
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
const result = spawnSync('sh', ['-c', scriptCommand], {
|
|
3246
|
-
cwd: packageRoot,
|
|
3247
|
-
encoding: 'utf8',
|
|
3248
|
-
timeout: 3000,
|
|
3249
|
-
});
|
|
3272
|
+
if (!hasUtilLinuxScript()) return;
|
|
3273
|
+
// Synchronize on output produced from inside the solver instead of a
|
|
3274
|
+
// fixed sleep: on a loaded host Ctrl-C could otherwise arrive before
|
|
3275
|
+
// the query starts, leaving repeat/fail to run until the test timeout.
|
|
3276
|
+
const result = runScriptedRepl([
|
|
3277
|
+
{ waitFor: '?- ', send: 'put_code(82), put_code(69), put_code(65), put_code(68), put_code(89), repeat, fail.\n' },
|
|
3278
|
+
{ waitFor: 'READY', send: '\u0003' },
|
|
3279
|
+
]);
|
|
3250
3280
|
assertEqual(result.error?.code, undefined, 'terminal interrupt timeout');
|
|
3251
3281
|
assertEqual(result.status, 130, 'SIGINT exit status');
|
|
3252
|
-
assertIncludes(result.stdout, '?- repeat, fail.', 'terminal query echo');
|
|
3282
|
+
assertIncludes(result.stdout, '?- put_code(82), put_code(69), put_code(65), put_code(68), put_code(89), repeat, fail.', 'terminal query echo');
|
|
3283
|
+
assertIncludes(result.stdout, 'READY', 'query entered computation');
|
|
3253
3284
|
},
|
|
3254
3285
|
},
|
|
3255
3286
|
{
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -6283,7 +6283,11 @@ private helpers and same-named predicates in different modules separate.
|
|
|
6283
6283
|
`freeze(?Term,:Goal)` runs `Goal` immediately when `Term` is already nonvariable;
|
|
6284
6284
|
otherwise it delays the goal until `Term` becomes nonvariable. Suspensions are
|
|
6285
6285
|
kept in the logical environment, so bindings and backtracking remain isolated
|
|
6286
|
-
between solution branches.
|
|
6286
|
+
between solution branches. When a suspension wakes, its goal is meta-invoked
|
|
6287
|
+
with its own cut scope: a `!` inside the delayed goal may commit choices made by
|
|
6288
|
+
that invocation, but it cannot prune alternatives that were created before the
|
|
6289
|
+
`freeze/2` call. Thus `call(((Y=1;Y=2),freeze(X,!),X=c));Y=3` retains the three
|
|
6290
|
+
answers `Y=1`, `Y=2`, and `Y=3`.
|
|
6287
6291
|
|
|
6288
6292
|
`dif(?Left,?Right)` posts a delayed finite-tree disequality when its arguments
|
|
6289
6293
|
can still unify. Its residual store is normalized by logical implication:
|
|
@@ -7045,7 +7049,23 @@ is its synonym). Variables named in the query keep their identity inside answer
|
|
|
7045
7049
|
descriptions; variables introduced only by a description are fresh. For example,
|
|
7046
7050
|
a query `throw(g(X))` is described by `throw(g(_X))`, while
|
|
7047
7051
|
`throw(g(X)), unexpected` verifies that ISO `throw/1` did not retain the query
|
|
7048
|
-
variable in the renamed exception term. `...` and `ad_infinitum` accept further answers.
|
|
7052
|
+
variable in the renamed exception term. `...` and `ad_infinitum` accept further answers. The `maybe` annotation describes
|
|
7053
|
+
a successful answer that still has at least one pending residual constraint. It
|
|
7054
|
+
does not stand for an arbitrary answer and it does not weaken substitution
|
|
7055
|
+
matching: `X = a, maybe` still requires the `X = a` substitution. Conversely, a
|
|
7056
|
+
successful answer description without `maybe` requires that no residual
|
|
7057
|
+
constraint remain. For example, a pending `dif/2` constraint can be checked as:
|
|
7058
|
+
|
|
7059
|
+
```eyeprolog
|
|
7060
|
+
?- dif(X,Y), X = a.
|
|
7061
|
+
true, unexpected.
|
|
7062
|
+
X = a, unexpected.
|
|
7063
|
+
X = a, maybe.
|
|
7064
|
+
maybe, unexpected.
|
|
7065
|
+
```
|
|
7066
|
+
|
|
7067
|
+
EyeProlog treats native variable constraints, attributed-variable residue, and
|
|
7068
|
+
delayed goals as pending residue for this purpose. Multiple
|
|
7049
7069
|
indented descriptions after one query are independent checks: each re-runs the
|
|
7050
7070
|
query, each is counted in the `quads:` summary, and a failing description does
|
|
7051
7071
|
not suppress later descriptions for that query. `inputs/1` supplies and checks
|