eyeprolog 1.3.92 → 1.3.93
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/test/run-regression.mjs +29 -14
- package/the-art-of-eyeprolog.md +17 -1
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/test/run-regression.mjs
CHANGED
|
@@ -2005,6 +2005,25 @@ c4 ?- call((!;1)).
|
|
|
2005
2005
|
assertEqual(result.stdout, 'quads: 37 run, 37 passed, 0 failed.\n', 'quad report');
|
|
2006
2006
|
},
|
|
2007
2007
|
},
|
|
2008
|
+
{
|
|
2009
|
+
name: 'runQuads maybe requires pending constraints without weakening substitutions (issue #80)',
|
|
2010
|
+
run: () => {
|
|
2011
|
+
const result = publicApi.runQuads(`37
|
|
2012
|
+
?- dif(X,Y), X = a.
|
|
2013
|
+
true, unexpected.
|
|
2014
|
+
X = a, unexpected.
|
|
2015
|
+
X = a, maybe.
|
|
2016
|
+
maybe, unexpected.
|
|
2017
|
+
|
|
2018
|
+
?- X = a.
|
|
2019
|
+
X = a, maybe, unexpected.
|
|
2020
|
+
`);
|
|
2021
|
+
assertEqual(result.total, 5, 'quad total');
|
|
2022
|
+
assertEqual(result.passed, 5, 'quad passed');
|
|
2023
|
+
assertEqual(result.failed, 0, 'quad failed');
|
|
2024
|
+
assertEqual(result.stdout, 'quads: 5 run, 5 passed, 0 failed.\n', 'quad report');
|
|
2025
|
+
},
|
|
2026
|
+
},
|
|
2008
2027
|
{
|
|
2009
2028
|
name: 'runQuads rejects malformed answer substitutions',
|
|
2010
2029
|
run: () => {
|
|
@@ -3234,22 +3253,18 @@ c4 ?- call((!;1)).
|
|
|
3234
3253
|
{
|
|
3235
3254
|
name: 'REPL releases terminal signals while a query computes',
|
|
3236
3255
|
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
|
-
});
|
|
3256
|
+
if (!hasUtilLinuxScript()) return;
|
|
3257
|
+
// Synchronize on output produced from inside the solver instead of a
|
|
3258
|
+
// fixed sleep: on a loaded host Ctrl-C could otherwise arrive before
|
|
3259
|
+
// the query starts, leaving repeat/fail to run until the test timeout.
|
|
3260
|
+
const result = runScriptedRepl([
|
|
3261
|
+
{ waitFor: '?- ', send: 'put_code(82), put_code(69), put_code(65), put_code(68), put_code(89), repeat, fail.\n' },
|
|
3262
|
+
{ waitFor: 'READY', send: '\u0003' },
|
|
3263
|
+
]);
|
|
3250
3264
|
assertEqual(result.error?.code, undefined, 'terminal interrupt timeout');
|
|
3251
3265
|
assertEqual(result.status, 130, 'SIGINT exit status');
|
|
3252
|
-
assertIncludes(result.stdout, '?- repeat, fail.', 'terminal query echo');
|
|
3266
|
+
assertIncludes(result.stdout, '?- put_code(82), put_code(69), put_code(65), put_code(68), put_code(89), repeat, fail.', 'terminal query echo');
|
|
3267
|
+
assertIncludes(result.stdout, 'READY', 'query entered computation');
|
|
3253
3268
|
},
|
|
3254
3269
|
},
|
|
3255
3270
|
{
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -7045,7 +7045,23 @@ is its synonym). Variables named in the query keep their identity inside answer
|
|
|
7045
7045
|
descriptions; variables introduced only by a description are fresh. For example,
|
|
7046
7046
|
a query `throw(g(X))` is described by `throw(g(_X))`, while
|
|
7047
7047
|
`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.
|
|
7048
|
+
variable in the renamed exception term. `...` and `ad_infinitum` accept further answers. The `maybe` annotation describes
|
|
7049
|
+
a successful answer that still has at least one pending residual constraint. It
|
|
7050
|
+
does not stand for an arbitrary answer and it does not weaken substitution
|
|
7051
|
+
matching: `X = a, maybe` still requires the `X = a` substitution. Conversely, a
|
|
7052
|
+
successful answer description without `maybe` requires that no residual
|
|
7053
|
+
constraint remain. For example, a pending `dif/2` constraint can be checked as:
|
|
7054
|
+
|
|
7055
|
+
```eyeprolog
|
|
7056
|
+
?- dif(X,Y), X = a.
|
|
7057
|
+
true, unexpected.
|
|
7058
|
+
X = a, unexpected.
|
|
7059
|
+
X = a, maybe.
|
|
7060
|
+
maybe, unexpected.
|
|
7061
|
+
```
|
|
7062
|
+
|
|
7063
|
+
EyeProlog treats native variable constraints, attributed-variable residue, and
|
|
7064
|
+
delayed goals as pending residue for this purpose. Multiple
|
|
7049
7065
|
indented descriptions after one query are independent checks: each re-runs the
|
|
7050
7066
|
query, each is counted in the `quads:` summary, and a failing description does
|
|
7051
7067
|
not suppress later descriptions for that query. `inputs/1` supplies and checks
|