eyeprolog 1.3.39 → 1.3.41
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 -0
- package/examples/book/README.md +2 -0
- package/examples/book/chapter-40/02-program.pl +5 -0
- package/examples/book/chapter-40/03-program-2.pl +5 -0
- package/package.json +1 -1
- package/src/io.js +1 -1
- package/src/iso.js +12 -4
- package/src/quads.js +56 -16
- package/src/write.js +59 -6
- package/test/conformance/WG17-SYNTAX-STATUS.md +2 -2
- package/test/conformance/expected/iso/wg17_syntax_high_risk.pl +2 -2
- package/test/conformance/wg17-syntax-cases.json +99 -90
- package/test/run-regression.mjs +63 -1
- package/the-art-of-eyeprolog.md +87 -27
package/test/run-regression.mjs
CHANGED
|
@@ -1333,6 +1333,24 @@ c4 ?- call((!;1)).
|
|
|
1333
1333
|
assertEqual(result.stdout, 'quads: 1 run, 1 passed, 0 failed.\n', 'quad report');
|
|
1334
1334
|
},
|
|
1335
1335
|
},
|
|
1336
|
+
{
|
|
1337
|
+
name: 'quad recursion-cycle evidence consistently refutes finite failure (issue #58 comment 5381101420)',
|
|
1338
|
+
run: () => {
|
|
1339
|
+
const result = publicApi.runQuads(`inf :- inf, inf.
|
|
1340
|
+
|
|
1341
|
+
23
|
|
1342
|
+
?- inf.
|
|
1343
|
+
loops.
|
|
1344
|
+
false.
|
|
1345
|
+
`);
|
|
1346
|
+
assertEqual(result.total, 2, 'quad total');
|
|
1347
|
+
assertEqual(result.passed, 1, 'loops passed');
|
|
1348
|
+
assertEqual(result.failed, 1, 'false failed');
|
|
1349
|
+
assertEqual(result.undecided, 0, 'no undecided result');
|
|
1350
|
+
assertIncludes(result.stdout, 'expected: false.', 'finite failure diagnostic');
|
|
1351
|
+
assertNotIncludes(result.stdout, 'undecided: recursion cycle encountered.', 'cycle is decisive');
|
|
1352
|
+
},
|
|
1353
|
+
},
|
|
1336
1354
|
{
|
|
1337
1355
|
name: 'quad search-budget exhaustion is undecided rather than loops or failure (issue #58)',
|
|
1338
1356
|
run: () => {
|
|
@@ -1383,6 +1401,21 @@ c4 ?- call((!;1)).
|
|
|
1383
1401
|
assertIncludes(nsto.stdout, 'quads: FAILED 34, <input>:1', 'NSTO diagnostic');
|
|
1384
1402
|
},
|
|
1385
1403
|
},
|
|
1404
|
+
{
|
|
1405
|
+
name: 'quads peeks/1 supplies one unconsumed look-ahead character (issue #62)',
|
|
1406
|
+
run: () => {
|
|
1407
|
+
const result = publicApi.runQuads(`29
|
|
1408
|
+
?- read(X).
|
|
1409
|
+
inputs("1."), X = 1, unexpected.
|
|
1410
|
+
inputs("1."), peeks(" "), X = 1.
|
|
1411
|
+
inputs("1. "), peeks(" "), X = 1, unexpected.
|
|
1412
|
+
`);
|
|
1413
|
+
assertEqual(result.total, 3, 'quad total');
|
|
1414
|
+
assertEqual(result.passed, 3, 'quad passed');
|
|
1415
|
+
assertEqual(result.failed, 0, 'quad failed');
|
|
1416
|
+
assertEqual(result.stdout, 'quads: 3 run, 3 passed, 0 failed.\n', 'quad report');
|
|
1417
|
+
},
|
|
1418
|
+
},
|
|
1386
1419
|
{
|
|
1387
1420
|
name: 'outputs/1 accepts DCG bodies over captured characters (issue #59)',
|
|
1388
1421
|
run: () => {
|
|
@@ -3282,6 +3315,28 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
|
|
|
3282
3315
|
}
|
|
3283
3316
|
},
|
|
3284
3317
|
},
|
|
3318
|
+
{
|
|
3319
|
+
name: 'read and read_term report invalid UTF-8 as representation_error(character) (issue #64)',
|
|
3320
|
+
run: () => {
|
|
3321
|
+
const invalidPath = path.join(tmp, `read-invalid-utf8-${++tmpCounter}.bin`);
|
|
3322
|
+
fs.writeFileSync(invalidPath, Buffer.from([0xff]));
|
|
3323
|
+
const quotedPath = sourceAtom(invalidPath);
|
|
3324
|
+
for (const goal of [
|
|
3325
|
+
`open(${quotedPath}, read, S, []), read(S, C)`,
|
|
3326
|
+
`open(${quotedPath}, read, S, []), read_term(S, C, [])`,
|
|
3327
|
+
`open(${quotedPath}, read, S, []), set_input(S), read(C)`,
|
|
3328
|
+
`open(${quotedPath}, read, S, []), set_input(S), read_term(C, [])`,
|
|
3329
|
+
]) {
|
|
3330
|
+
let caught = null;
|
|
3331
|
+
try {
|
|
3332
|
+
run('', { goal });
|
|
3333
|
+
} catch (error) {
|
|
3334
|
+
caught = error;
|
|
3335
|
+
}
|
|
3336
|
+
assertEqual(caught?.formal, 'representation_error(character)', `${goal} invalid UTF-8`);
|
|
3337
|
+
}
|
|
3338
|
+
},
|
|
3339
|
+
},
|
|
3285
3340
|
{
|
|
3286
3341
|
name: 'writeq gives unnamed variables underscore-prefixed names (issue #53)',
|
|
3287
3342
|
run: () => {
|
|
@@ -3310,6 +3365,13 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
|
|
|
3310
3365
|
);
|
|
3311
3366
|
},
|
|
3312
3367
|
},
|
|
3368
|
+
{
|
|
3369
|
+
name: 'writeq separates operators only where lexical ambiguity requires it (issue #63)',
|
|
3370
|
+
run: () => {
|
|
3371
|
+
const source = "emit :- writeq(1+2), put_char('|'), writeq(a+ -b), put_char('|'), writeq(a+b*c), nl.\n";
|
|
3372
|
+
assertEqual(run(source, { goal: 'emit' }).stdout, '1+2|a+ -b|a+b*c\nemit.\n', 'minimal operator spacing');
|
|
3373
|
+
},
|
|
3374
|
+
},
|
|
3313
3375
|
{
|
|
3314
3376
|
name: 'write predicates and write_term options select distinct formats',
|
|
3315
3377
|
run: () => {
|
|
@@ -3332,7 +3394,7 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
|
|
|
3332
3394
|
].join('\n');
|
|
3333
3395
|
assertEqual(
|
|
3334
3396
|
run(source, { goal: 'emit' }).stdout,
|
|
3335
|
-
"hello world|'hello world'|a
|
|
3397
|
+
"hello world|'hello world'|a+b*c|+(a,*(b,c))|hello world|'hello world'|+(a,b)|a+b|A|$VAR(0)|pair(Left,Right)|\"ab\"|[a,b]emit.\n",
|
|
3336
3398
|
'stdout',
|
|
3337
3399
|
);
|
|
3338
3400
|
},
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -5412,8 +5412,11 @@ where needed, and `write_canonical/1-2` exposes canonical structure. Dotted
|
|
|
5412
5412
|
graphic atoms do not need quotes merely because they contain a period:
|
|
5413
5413
|
`writeq(./*)`, `writeq(.*)`, and `writeq(...*)` output `./*`, `.*`, and `...*`
|
|
5414
5414
|
respectively. ISO term output uses only the separator characters needed by the
|
|
5415
|
-
syntax, so functional arguments
|
|
5416
|
-
|
|
5415
|
+
syntax, so functional arguments, list elements, and operator applications are
|
|
5416
|
+
emitted compactly when no lexical ambiguity would arise. For example,
|
|
5417
|
+
`writeq([a,b])` outputs `[a,b]` and `writeq(1+2)` outputs `1+2`; a separator is
|
|
5418
|
+
still retained where adjacent graphic tokens would otherwise merge, as in
|
|
5419
|
+
`a+ -b`.
|
|
5417
5420
|
`write_term/2-3` supports `quoted/1`, `ignore_ops/1`, `numbervars/1`, and
|
|
5418
5421
|
`variable_names/1`.
|
|
5419
5422
|
|
|
@@ -6891,31 +6894,88 @@ variable in the renamed exception term. `...` and `ad_infinitum` accept further
|
|
|
6891
6894
|
indented descriptions after one query are independent checks: each re-runs the
|
|
6892
6895
|
query, each is counted in the `quads:` summary, and a failing description does
|
|
6893
6896
|
not suppress later descriptions for that query. `inputs/1` supplies and checks
|
|
6894
|
-
consumed
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
`
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6897
|
+
exactly the characters consumed by the query. `peeks/1` may add one character
|
|
6898
|
+
that is available for look-ahead but must remain unconsumed. The runner puts an
|
|
6899
|
+
invalid-character sentinel immediately after the declared input boundary, so a
|
|
6900
|
+
reader cannot accidentally use an artificial end-of-file to decide that a full
|
|
6901
|
+
stop terminates the term. For example:
|
|
6902
|
+
|
|
6903
|
+
```eyeprolog
|
|
6904
|
+
?- read(X).
|
|
6905
|
+
inputs("1."), X = 1, unexpected.
|
|
6906
|
+
inputs("1."), peeks(" "), X = 1.
|
|
6907
|
+
inputs("1. "), peeks(" "), X = 1, unexpected.
|
|
6908
|
+
```
|
|
6909
|
+
|
|
6910
|
+
`outputs/1` checks characters emitted while reaching the described answer or
|
|
6911
|
+
error, including output produced before a later exception. Its argument may be
|
|
6912
|
+
an exact character list/string or a DCG body: terminal sequences,
|
|
6913
|
+
conjunction/disjunction, `...`/`ad_infinitum` sequence wildcards, and
|
|
6914
|
+
user-defined DCG nonterminals are matched against the captured characters.
|
|
6915
|
+
The `waits` and unordered `other_answer_sequence` annotations are not executed
|
|
6916
|
+
by the current runner.
|
|
6917
|
+
|
|
6918
|
+
#### STO, loops, and undecided quad results
|
|
6919
|
+
|
|
6920
|
+
Following Trealla's quad convention, `sto` declares that a query is subject to
|
|
6921
|
+
occurs-check. EyeProlog checks this conservatively rather than attempting a
|
|
6922
|
+
complete STO/NSTO decision procedure. During the query's ordinary execution,
|
|
6923
|
+
the finite-tree unifier records a concrete occurs-check event as positive STO
|
|
6924
|
+
evidence; the query is not run a second time merely to probe STO-ness. A finite
|
|
6925
|
+
execution that completes naturally without such an event disproves `sto`, while
|
|
6926
|
+
a search or resource boundary leaves the declaration conservatively unchecked.
|
|
6927
|
+
The answer portion of an `sto`-annotated leaf remains implementation-dependent
|
|
6928
|
+
and is therefore not compared.
|
|
6929
|
+
|
|
6930
|
+
For example, the cyclic binding in the first query provides positive STO
|
|
6931
|
+
evidence, whereas the second query is finite and cannot be STO:
|
|
6932
|
+
|
|
6933
|
+
```eyeprolog
|
|
6934
|
+
?- X = s(X).
|
|
6935
|
+
X = ..., unexpected.
|
|
6936
|
+
false, unexpected.
|
|
6937
|
+
sto, false
|
|
6938
|
+
| sto, true.
|
|
6939
|
+
|
|
6940
|
+
?- true.
|
|
6941
|
+
sto. % fails: no STO evidence
|
|
6942
|
+
```
|
|
6943
|
+
|
|
6944
|
+
When a quad declares STO and the execution observes an occurs-check event, an
|
|
6945
|
+
unannotated `unexpected` leaf does not reject the implementation-dependent
|
|
6946
|
+
finite-tree outcome. This is partial STO detection only: EyeProlog makes a
|
|
6947
|
+
definite statement where execution provides definite evidence and otherwise
|
|
6948
|
+
does not guess.
|
|
6949
|
+
|
|
6950
|
+
`loops` is kept distinct from merely exhausting the quad runner's resources.
|
|
6951
|
+
EyeProlog accepts structural nontermination evidence such as an active-variant
|
|
6952
|
+
recursion cycle, with the loop depth/inference bounds as a bounded fallback. A
|
|
6953
|
+
structural cycle is also strong enough to refute a finite `false` description;
|
|
6954
|
+
it is not reported as undecided merely because the same query was checked by a
|
|
6955
|
+
different answer description:
|
|
6956
|
+
|
|
6957
|
+
```eyeprolog
|
|
6958
|
+
inf :- inf, inf.
|
|
6959
|
+
|
|
6960
|
+
?- inf.
|
|
6961
|
+
loops.
|
|
6962
|
+
```
|
|
6963
|
+
|
|
6964
|
+
Ordinary answer descriptions also have a finite inference budget (100000 by
|
|
6965
|
+
default). Exhausting that budget does **not** establish `loops` and does not
|
|
6966
|
+
turn an unfinished search into `false`; instead the description is reported as
|
|
6967
|
+
`UNDECIDED`, for example:
|
|
6968
|
+
|
|
6969
|
+
```text
|
|
6970
|
+
quads: UNDECIDED expensive_case, program.pl:12
|
|
6971
|
+
undecided: inference limit reached.
|
|
6972
|
+
```
|
|
6973
|
+
|
|
6974
|
+
Thus quad execution has three useful outcomes: passed, failed, and undecided.
|
|
6975
|
+
When there are no failures but at least one undecided description, the CLI exits
|
|
6976
|
+
with status `2`. The JavaScript API may override the ordinary search budget with
|
|
6977
|
+
`quadMaxInferences`; `loopMaxDepth` and `loopMaxInferences` control the explicit
|
|
6978
|
+
`loops` probe.
|
|
6919
6979
|
|
|
6920
6980
|
The JavaScript API exposes the same operation without process I/O:
|
|
6921
6981
|
|