eyeprolog 1.3.70 → 1.3.72
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 -2
- package/examples/dif-constraints.pl +2 -2
- package/package.json +1 -1
- package/src/iso.js +74 -0
- package/src/lib/atts.pl +6 -1
- package/src/repl.js +7 -7
- package/src/standard-library.js +4 -1
- package/src/term.js +22 -0
- package/src/write.js +29 -29
- package/test/conformance/WG17-SYNTAX-STATUS.md +5 -1
- package/test/conformance/wg17-syntax-cases.json +44 -44
- package/test/run-regression.mjs +112 -6
- package/test/run-wg17.mjs +36 -0
- package/the-art-of-eyeprolog.md +1 -1
- package/tools/report-wg17-syntax-coverage.mjs +5 -1
- package/tools/upgrade-wg17.mjs +2 -1
package/README.md
CHANGED
|
@@ -151,7 +151,9 @@ top level includes pending `dif/2` goals in residual answers. See
|
|
|
151
151
|
|
|
152
152
|
`library(atts)` exposes Scryer-style attributed-variable operations on the same
|
|
153
153
|
persistent `Env` machinery used by `dif/2`: `put_atts/2`, `get_atts/2`,
|
|
154
|
-
`put_attr/3`, `get_attr/3`, `del_attr/2`,
|
|
154
|
+
`put_attr/3`, `get_attr/3`, `del_attr/2`, `term_attributed_variables/2`, and
|
|
155
|
+
`call_residue_vars/2`. The latter reports variables whose residual attributes or
|
|
156
|
+
constraints were created or modified by a goal, including native `dif/2` residues.
|
|
155
157
|
`:- attribute ...` declarations are accepted, `verify_attributes/3` runs before
|
|
156
158
|
an attributed binding is committed, and the hook's returned goals run after
|
|
157
159
|
that binding. Attribute state follows variable representatives and ordinary
|
|
@@ -359,7 +361,8 @@ lambda syntax also changes the active operator table.
|
|
|
359
361
|
Outside `--iso-strict`, an otherwise undefined unqualified call may autoload a
|
|
360
362
|
predicate only when the interop profile has one canonical EyeProlog provider.
|
|
361
363
|
For example, `member/2` autoloads from `library(lists)`, `call_nth/2` from
|
|
362
|
-
`library(iso_ext)`, and `between/3`
|
|
364
|
+
`library(iso_ext)`, `call_residue_vars/2` from `library(atts)`, and `between/3`
|
|
365
|
+
from EyeProlog's internal
|
|
363
366
|
`library(prologue)` implementation. Use `--no-autoload` to disable this
|
|
364
367
|
convenience. Strict ISO mode never autoloads library predicates.
|
|
365
368
|
|
|
@@ -11,8 +11,8 @@ allowed_pair(X, Y) :-
|
|
|
11
11
|
X = left,
|
|
12
12
|
Y = right.
|
|
13
13
|
|
|
14
|
-
%
|
|
15
|
-
%
|
|
14
|
+
% X=Y already proves X-Y and 1-2 different: X-X cannot unify with 1-2,
|
|
15
|
+
% so the dif/2 constraint is discharged before the final specialization Y=1.
|
|
16
16
|
specialization(X, Y) :-
|
|
17
17
|
dif(X-Y, 1-2),
|
|
18
18
|
X = Y,
|
package/package.json
CHANGED
package/src/iso.js
CHANGED
|
@@ -165,6 +165,7 @@ export const isoBuiltins = {
|
|
|
165
165
|
export const eyePrologLibraryBuiltins = {
|
|
166
166
|
register(registry) {
|
|
167
167
|
registry.add('eyeprolog__call_nth', 2, callNthBuiltin, { eyePrologLibrary: true });
|
|
168
|
+
registry.add('eyeprolog__call_residue_vars', 2, callResidueVarsBuiltin, { eyePrologLibrary: true });
|
|
168
169
|
registry.add('eyeprolog__countall', 2, countAllBuiltin, { eyePrologLibrary: true });
|
|
169
170
|
registry.add('eyeprolog__freeze', 2, freezeBuiltin, { eyePrologLibrary: true });
|
|
170
171
|
registry.add('dif', 2, difBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
@@ -2497,6 +2498,79 @@ function* callNthBuiltin({ solver, goal, env }) {
|
|
|
2497
2498
|
}
|
|
2498
2499
|
}
|
|
2499
2500
|
|
|
2501
|
+
function residueSnapshot(env) {
|
|
2502
|
+
const attributes = new Map();
|
|
2503
|
+
for (const name of env.attributedVariableNames?.() ?? []) {
|
|
2504
|
+
attributes.set(name, env.prologAttributes(name));
|
|
2505
|
+
}
|
|
2506
|
+
const delays = new Map();
|
|
2507
|
+
for (const name of env.delayedVariableNames?.() ?? []) {
|
|
2508
|
+
delays.set(name, env.delayedGoals(name));
|
|
2509
|
+
}
|
|
2510
|
+
const constraints = new Map();
|
|
2511
|
+
for (const constraint of env.variableConstraints?.() ?? []) {
|
|
2512
|
+
constraints.set(constraint, [...constraint.variables(env)]);
|
|
2513
|
+
}
|
|
2514
|
+
return { attributes, delays, constraints };
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
function sameAttributeEntries(before, after) {
|
|
2518
|
+
if (before == null || before.length !== after.length) return false;
|
|
2519
|
+
for (let i = 0; i < after.length; i++) {
|
|
2520
|
+
if (before[i].module !== after[i].module || before[i].attribute !== after[i].attribute) return false;
|
|
2521
|
+
}
|
|
2522
|
+
return true;
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
function sameDelayEntries(before, after) {
|
|
2526
|
+
if (before == null || before.length !== after.length) return false;
|
|
2527
|
+
for (let i = 0; i < after.length; i++) if (before[i] !== after[i]) return false;
|
|
2528
|
+
return true;
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
function residueVariablesSince(snapshot, env) {
|
|
2532
|
+
const names = [];
|
|
2533
|
+
const seen = new Set();
|
|
2534
|
+
const add = (name) => {
|
|
2535
|
+
const root = deref(variable(name), env);
|
|
2536
|
+
if (root.type !== VAR || seen.has(root.name)) return;
|
|
2537
|
+
seen.add(root.name);
|
|
2538
|
+
names.push(root);
|
|
2539
|
+
};
|
|
2540
|
+
|
|
2541
|
+
for (const name of env.attributedVariableNames?.() ?? []) {
|
|
2542
|
+
const after = env.prologAttributes(name);
|
|
2543
|
+
if (!sameAttributeEntries(snapshot.attributes.get(name), after)) add(name);
|
|
2544
|
+
}
|
|
2545
|
+
for (const name of env.delayedVariableNames?.() ?? []) {
|
|
2546
|
+
const after = env.delayedGoals(name);
|
|
2547
|
+
if (!sameDelayEntries(snapshot.delays.get(name), after)) add(name);
|
|
2548
|
+
}
|
|
2549
|
+
for (const constraint of env.variableConstraints?.() ?? []) {
|
|
2550
|
+
const after = [...constraint.variables(env)];
|
|
2551
|
+
const before = snapshot.constraints.get(constraint);
|
|
2552
|
+
const unchanged = before != null && before.length === after.length && before.every((name, index) => name === after[index]);
|
|
2553
|
+
if (unchanged) continue;
|
|
2554
|
+
for (const name of after) add(name);
|
|
2555
|
+
}
|
|
2556
|
+
return names;
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
function* callResidueVarsBuiltin({ solver, goal, env }) {
|
|
2560
|
+
const invoked = callable(goal.args[0], env);
|
|
2561
|
+
if (invoked.module == null) invoked.module = goal.module ?? 'user';
|
|
2562
|
+
const snapshot = residueSnapshot(env);
|
|
2563
|
+
const child = solver.cloneForInnerGoal();
|
|
2564
|
+
try {
|
|
2565
|
+
for (const answerEnv of child.solve([invoked], env, 0)) {
|
|
2566
|
+
const next = answerEnv.clone();
|
|
2567
|
+
if (unify(goal.args[1], listFromItems(residueVariablesSince(snapshot, answerEnv)), next)) yield next;
|
|
2568
|
+
}
|
|
2569
|
+
} finally {
|
|
2570
|
+
solver.absorbStatsFrom(child);
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2500
2574
|
function* freezeBuiltin({ solver, goal, env }) {
|
|
2501
2575
|
const watched = deref(goal.args[0], env);
|
|
2502
2576
|
if (watched.type !== VAR) {
|
package/src/lib/atts.pl
CHANGED
|
@@ -12,9 +12,14 @@
|
|
|
12
12
|
put_attr/3,
|
|
13
13
|
get_attr/3,
|
|
14
14
|
del_attr/2,
|
|
15
|
-
term_attributed_variables/2
|
|
15
|
+
term_attributed_variables/2,
|
|
16
|
+
call_residue_vars/2
|
|
16
17
|
]).
|
|
17
18
|
:- op(1199, fx, attribute).
|
|
19
|
+
:- meta_predicate(call_residue_vars(0, '?')).
|
|
18
20
|
|
|
19
21
|
term_attributed_variables(Term, Variables) :-
|
|
20
22
|
'$term_attributed_variables'(Term, Variables).
|
|
23
|
+
|
|
24
|
+
call_residue_vars(Goal, Variables) :-
|
|
25
|
+
eyeprolog__call_residue_vars(Goal, Variables).
|
package/src/repl.js
CHANGED
|
@@ -672,18 +672,18 @@ async function solveQuery(engine, state, goal, reader, output) {
|
|
|
672
672
|
}
|
|
673
673
|
|
|
674
674
|
function pullSolution(solver, solutions, reader) {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
675
|
+
// Prolog output is an observable side effect of execution, so never hold it
|
|
676
|
+
// until the search reaches its next leaf. In particular an infinite search
|
|
677
|
+
// that periodically writes progress must remain observable at the terminal.
|
|
678
|
+
// Completed queries keep the same byte order because user_output writes are
|
|
679
|
+
// synchronous and occur before solutions.next() returns its answer.
|
|
679
680
|
const suspended = reader.suspendForComputation();
|
|
680
681
|
try {
|
|
681
|
-
return { result: solutions.next(), output:
|
|
682
|
+
return { result: solutions.next(), output: '' };
|
|
682
683
|
} catch (error) {
|
|
683
|
-
return { error, output:
|
|
684
|
+
return { error, output: '' };
|
|
684
685
|
} finally {
|
|
685
686
|
reader.resumeAfterComputation(suspended);
|
|
686
|
-
if (stream) stream.write = originalWrite;
|
|
687
687
|
}
|
|
688
688
|
}
|
|
689
689
|
|
package/src/standard-library.js
CHANGED
|
@@ -63,7 +63,7 @@ function libraryUrl(filename) {
|
|
|
63
63
|
|
|
64
64
|
export const eyePrologNativeLibraryIndicators = Object.freeze([
|
|
65
65
|
'call_nth/2', 'freeze/2', 'dif/2', 'countall/2', 'time/1',
|
|
66
|
-
'put_atts/2', 'get_atts/2', 'put_attr/3', 'get_attr/3', 'del_attr/2', 'term_attributed_variables/2',
|
|
66
|
+
'put_atts/2', 'get_atts/2', 'put_attr/3', 'get_attr/3', 'del_attr/2', 'term_attributed_variables/2', 'call_residue_vars/2',
|
|
67
67
|
'#>/2', '#</2', '#>=/2', '#=</2', '#=/2', '#\\=/2', '#\\/1',
|
|
68
68
|
'#<==>/2', '#==>/2', '#<==/2', '#\\//2', '#\\/2', '#/\\/2',
|
|
69
69
|
'in/2', 'ins/2', 'all_different/1', 'all_distinct/1', 'nvalue/2', 'sum/3',
|
|
@@ -133,6 +133,9 @@ export const eyePrologInteropAutoload = Object.freeze({
|
|
|
133
133
|
// it from library(iso_ext), matching the explicit Scryer import while still
|
|
134
134
|
// allowing Trealla-style unqualified source to use the same autoload entry.
|
|
135
135
|
'call_nth/2': 'iso_ext',
|
|
136
|
+
// Scryer exposes call_residue_vars/2 from library(atts); autoload it for
|
|
137
|
+
// constraint-test source that uses the predicate without an explicit import.
|
|
138
|
+
'call_residue_vars/2': 'atts',
|
|
136
139
|
// Trealla exposes time/1 as a meta timing predicate and library(iso_ext)
|
|
137
140
|
// supplies ... //0. Autoload both so UWN's DCG hand-off benchmark runs
|
|
138
141
|
// unchanged while their implementations remain outside the ISO core.
|
package/src/term.js
CHANGED
|
@@ -268,6 +268,28 @@ export class Env {
|
|
|
268
268
|
delays.set(name, [...(delays.get(name) ?? []), { goal, module }]);
|
|
269
269
|
this._delays = delays;
|
|
270
270
|
}
|
|
271
|
+
delayedVariableNames() {
|
|
272
|
+
if (this._delays == null || this._delays.size === 0) return [];
|
|
273
|
+
const names = [];
|
|
274
|
+
const seen = new Set();
|
|
275
|
+
for (const name of this._delays.keys()) {
|
|
276
|
+
const root = deref(variable(name), this);
|
|
277
|
+
if (root.type !== VAR || seen.has(root.name)) continue;
|
|
278
|
+
seen.add(root.name);
|
|
279
|
+
names.push(root.name);
|
|
280
|
+
}
|
|
281
|
+
return names;
|
|
282
|
+
}
|
|
283
|
+
delayedGoals(name) {
|
|
284
|
+
const root = deref(variable(name), this);
|
|
285
|
+
if (root.type !== VAR || this._delays == null) return [];
|
|
286
|
+
const result = [];
|
|
287
|
+
for (const [source, goals] of this._delays) {
|
|
288
|
+
const current = deref(variable(source), this);
|
|
289
|
+
if (current.type === VAR && current.name === root.name) result.push(...goals);
|
|
290
|
+
}
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
271
293
|
takeReadyDelays() {
|
|
272
294
|
if (this._delays == null || this._delays.size === 0) return [];
|
|
273
295
|
const ready = [];
|
package/src/write.js
CHANGED
|
@@ -86,7 +86,12 @@ function isWordOperatorToken(token) {
|
|
|
86
86
|
|
|
87
87
|
function compactPrefixOperator(token, argument) {
|
|
88
88
|
if (isWordOperatorToken(token)) return `${token} ${argument}`;
|
|
89
|
-
|
|
89
|
+
// Without layout, `op(...)` is functional notation rather than prefix-operator
|
|
90
|
+
// notation. ISO 7.10.5 h requires operator-form output and layout whenever
|
|
91
|
+
// ambiguity could otherwise arise, so a parenthesized prefix argument must
|
|
92
|
+
// never be glued directly to the operator token.
|
|
93
|
+
const needsLayout = argument.startsWith('(') || compactBoundaryNeedsSpace(token, argument);
|
|
94
|
+
return `${token}${needsLayout ? ' ' : ''}${argument}`;
|
|
90
95
|
}
|
|
91
96
|
|
|
92
97
|
function compactPostfixOperator(argument, token) {
|
|
@@ -188,28 +193,6 @@ function chooseOperator(term, table) {
|
|
|
188
193
|
return null;
|
|
189
194
|
}
|
|
190
195
|
|
|
191
|
-
function startsWithUnsignedNumber(term, env, table, options) {
|
|
192
|
-
const resolved = deref(term, env);
|
|
193
|
-
if (resolved.type === NUMBER) return !resolved.name.startsWith('-');
|
|
194
|
-
if (resolved.type !== COMPOUND || isCons(resolved)) return false;
|
|
195
|
-
if (options.numbervars && resolved.name === '$VAR' && resolved.arity === 1) {
|
|
196
|
-
const index = deref(resolved.args[0], env);
|
|
197
|
-
if (index.type === NUMBER && /^\d+$/.test(index.name) &&
|
|
198
|
-
writeNumberedVariable(Number(index.name)) != null) {
|
|
199
|
-
return false;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
const definition = chooseOperator(resolved, table);
|
|
203
|
-
if (definition == null) return false;
|
|
204
|
-
if (definition.specifier === 'xf' || definition.specifier === 'yf') {
|
|
205
|
-
return startsWithUnsignedNumber(resolved.args[0], env, table, options);
|
|
206
|
-
}
|
|
207
|
-
if (definition.specifier === 'xfx' || definition.specifier === 'xfy' || definition.specifier === 'yfx') {
|
|
208
|
-
return startsWithUnsignedNumber(resolved.args[0], env, table, options);
|
|
209
|
-
}
|
|
210
|
-
return false;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
196
|
function printableReadVariableNames(term, env, explicit) {
|
|
214
197
|
const names = new Map(explicit);
|
|
215
198
|
const used = new Set(names.values());
|
|
@@ -353,13 +336,30 @@ function format(term, env, options, table, maxPriority = 1200, context = 'term')
|
|
|
353
336
|
let text;
|
|
354
337
|
if (specifier === 'fx' || specifier === 'fy') {
|
|
355
338
|
const argumentPriority = specifier === 'fx' ? priority - 1 : priority;
|
|
356
|
-
//
|
|
357
|
-
//
|
|
358
|
-
//
|
|
359
|
-
|
|
360
|
-
|
|
339
|
+
// Corrigendum 3 adds mandatory parentheses for prefix - when its
|
|
340
|
+
// argument is a non-negative number or is written in infix/postfix
|
|
341
|
+
// operator form (ISO 7.10.5 h 2(iii-iv)).
|
|
342
|
+
const child = deref(resolved.args[0], env);
|
|
343
|
+
const childNumbervar = options.numbervars && child.type === COMPOUND &&
|
|
344
|
+
child.name === '$VAR' && child.arity === 1 && (() => {
|
|
345
|
+
const index = deref(child.args[0], env);
|
|
346
|
+
return index.type === NUMBER && /^\d+$/.test(index.name) &&
|
|
347
|
+
writeNumberedVariable(Number(index.name)) != null;
|
|
348
|
+
})();
|
|
349
|
+
const childUsesSpecialNotation = isCons(child) ||
|
|
350
|
+
(child.type === COMPOUND && child.name === '{}' && child.arity === 1) || childNumbervar;
|
|
351
|
+
const childDefinition = childUsesSpecialNotation ? null : chooseOperator(child, table);
|
|
352
|
+
const negativeNeedsParentheses = resolved.name === '-' && (
|
|
353
|
+
(child.type === NUMBER && !child.name.startsWith('-')) ||
|
|
354
|
+
['xf', 'yf', 'xfx', 'xfy', 'yfx'].includes(childDefinition?.specifier)
|
|
355
|
+
);
|
|
356
|
+
if (negativeNeedsParentheses) {
|
|
357
|
+
// ISO 7.10.5 h 2(iii-iv): prefix - requires parentheses around a
|
|
358
|
+
// non-negative number and around an argument written in infix or
|
|
359
|
+
// postfix operator form. Keep layout before `(` so the result is
|
|
360
|
+
// operator notation, not functional notation such as `-(...)`.
|
|
361
361
|
const argument = format(resolved.args[0], env, options, table, 1200);
|
|
362
|
-
text =
|
|
362
|
+
text = `${token} (${argument})`;
|
|
363
363
|
} else {
|
|
364
364
|
const argument = format(resolved.args[0], env, options, table, argumentPriority);
|
|
365
365
|
text = options.minimalOperatorSpacing ? compactPrefixOperator(token, argument) : `${token} ${argument}`;
|
|
@@ -11,10 +11,14 @@ upstream assertion.
|
|
|
11
11
|
|
|
12
12
|
## Current standing
|
|
13
13
|
|
|
14
|
+
The percentage below is **traceability coverage**, not a conformance pass rate.
|
|
15
|
+
A represented case still has to pass the executable upstream assertion in
|
|
16
|
+
`test/run-wg17.mjs`.
|
|
17
|
+
|
|
14
18
|
| Measure | Count |
|
|
15
19
|
| --- | ---: |
|
|
16
20
|
| Active upstream cases | 366 |
|
|
17
|
-
|
|
|
21
|
+
| Upstream cases represented in the executable matrix | 366 (100.0%) |
|
|
18
22
|
| Not yet traced one-by-one | 0 |
|
|
19
23
|
| Deleted upstream identifiers | #20, #273 |
|
|
20
24
|
|
|
@@ -522,7 +522,7 @@
|
|
|
522
522
|
"query": "writeq([:-,-]).",
|
|
523
523
|
"input": "writeq([:-,-]).",
|
|
524
524
|
"readCount": 1,
|
|
525
|
-
"expected": "[:-,-]
|
|
525
|
+
"expected": "[:-,-]",
|
|
526
526
|
"outcome": {
|
|
527
527
|
"type": "success",
|
|
528
528
|
"stages": [
|
|
@@ -586,7 +586,7 @@
|
|
|
586
586
|
"query": "writeq([.,.(.,.,.)]).",
|
|
587
587
|
"input": "writeq([.,.(.,.,.)]).",
|
|
588
588
|
"readCount": 1,
|
|
589
|
-
"expected": "['.','.'('.','.','.')]
|
|
589
|
+
"expected": "['.','.'('.','.','.')]",
|
|
590
590
|
"outcome": {
|
|
591
591
|
"type": "success",
|
|
592
592
|
"stages": [
|
|
@@ -602,7 +602,7 @@
|
|
|
602
602
|
"query": "writeq((a :- b,c)).",
|
|
603
603
|
"input": "writeq((a :- b,c)).",
|
|
604
604
|
"readCount": 1,
|
|
605
|
-
"expected": "a:-b,c
|
|
605
|
+
"expected": "a:-b,c",
|
|
606
606
|
"outcome": {
|
|
607
607
|
"type": "success",
|
|
608
608
|
"stages": [
|
|
@@ -618,7 +618,7 @@
|
|
|
618
618
|
"query": "write_canonical([a]).",
|
|
619
619
|
"input": "write_canonical([a]).",
|
|
620
620
|
"readCount": 1,
|
|
621
|
-
"expected": "'.'(a,[])
|
|
621
|
+
"expected": "'.'(a,[])",
|
|
622
622
|
"outcome": {
|
|
623
623
|
"type": "success",
|
|
624
624
|
"stages": [
|
|
@@ -1052,7 +1052,7 @@
|
|
|
1052
1052
|
"query": "(- (1)) = -(1).",
|
|
1053
1053
|
"input": "(- (1)) = -(1).",
|
|
1054
1054
|
"readCount": 1,
|
|
1055
|
-
"expected": "succeeds
|
|
1055
|
+
"expected": "succeeds",
|
|
1056
1056
|
"outcome": {
|
|
1057
1057
|
"type": "success",
|
|
1058
1058
|
"stages": [
|
|
@@ -1068,7 +1068,7 @@
|
|
|
1068
1068
|
"query": "(- -1) = -(-1).",
|
|
1069
1069
|
"input": "(- -1) = -(-1).",
|
|
1070
1070
|
"readCount": 1,
|
|
1071
|
-
"expected": "succeeds
|
|
1071
|
+
"expected": "succeeds",
|
|
1072
1072
|
"outcome": {
|
|
1073
1073
|
"type": "success",
|
|
1074
1074
|
"stages": [
|
|
@@ -1084,7 +1084,7 @@
|
|
|
1084
1084
|
"query": "(- 1^2) = ^(-1,2).",
|
|
1085
1085
|
"input": "(- 1^2) = ^(-1,2).",
|
|
1086
1086
|
"readCount": 1,
|
|
1087
|
-
"expected": "succeeds
|
|
1087
|
+
"expected": "succeeds",
|
|
1088
1088
|
"outcome": {
|
|
1089
1089
|
"type": "success",
|
|
1090
1090
|
"stages": [
|
|
@@ -1303,12 +1303,12 @@
|
|
|
1303
1303
|
"query": "writeq(-(1)).",
|
|
1304
1304
|
"input": "writeq(-(1)).",
|
|
1305
1305
|
"readCount": 1,
|
|
1306
|
-
"expected": "- (1)
|
|
1306
|
+
"expected": "- (1)",
|
|
1307
1307
|
"outcome": {
|
|
1308
1308
|
"type": "success",
|
|
1309
1309
|
"stages": [
|
|
1310
1310
|
{
|
|
1311
|
-
"output": "-(1)",
|
|
1311
|
+
"output": "- (1)",
|
|
1312
1312
|
"variables": "[]"
|
|
1313
1313
|
}
|
|
1314
1314
|
]
|
|
@@ -1335,12 +1335,12 @@
|
|
|
1335
1335
|
"query": "writeq(-(1^2)).",
|
|
1336
1336
|
"input": "writeq(-(1^2)).",
|
|
1337
1337
|
"readCount": 1,
|
|
1338
|
-
"expected": "- (1^2)
|
|
1338
|
+
"expected": "- (1^2)",
|
|
1339
1339
|
"outcome": {
|
|
1340
1340
|
"type": "success",
|
|
1341
1341
|
"stages": [
|
|
1342
1342
|
{
|
|
1343
|
-
"output": "-(1^2)",
|
|
1343
|
+
"output": "- (1^2)",
|
|
1344
1344
|
"variables": "[]"
|
|
1345
1345
|
}
|
|
1346
1346
|
]
|
|
@@ -1351,12 +1351,12 @@
|
|
|
1351
1351
|
"query": "writeq(-(a^2)).",
|
|
1352
1352
|
"input": "writeq(-(a^2)).",
|
|
1353
1353
|
"readCount": 1,
|
|
1354
|
-
"expected": "- (a^2)
|
|
1354
|
+
"expected": "- (a^2)",
|
|
1355
1355
|
"outcome": {
|
|
1356
1356
|
"type": "success",
|
|
1357
1357
|
"stages": [
|
|
1358
1358
|
{
|
|
1359
|
-
"output": "-a^2",
|
|
1359
|
+
"output": "- (a^2)",
|
|
1360
1360
|
"variables": "[]"
|
|
1361
1361
|
}
|
|
1362
1362
|
]
|
|
@@ -1367,12 +1367,12 @@
|
|
|
1367
1367
|
"query": "writeq(-((a,b))).",
|
|
1368
1368
|
"input": "writeq(-((a,b))).",
|
|
1369
1369
|
"readCount": 1,
|
|
1370
|
-
"expected": "- (a,b)
|
|
1370
|
+
"expected": "- (a,b)",
|
|
1371
1371
|
"outcome": {
|
|
1372
1372
|
"type": "success",
|
|
1373
1373
|
"stages": [
|
|
1374
1374
|
{
|
|
1375
|
-
"output": "-(a,b)",
|
|
1375
|
+
"output": "- (a,b)",
|
|
1376
1376
|
"variables": "[]"
|
|
1377
1377
|
}
|
|
1378
1378
|
]
|
|
@@ -1388,7 +1388,7 @@
|
|
|
1388
1388
|
"type": "success",
|
|
1389
1389
|
"stages": [
|
|
1390
1390
|
{
|
|
1391
|
-
"output": "-(1*2)",
|
|
1391
|
+
"output": "- (1*2)",
|
|
1392
1392
|
"variables": "[]"
|
|
1393
1393
|
}
|
|
1394
1394
|
]
|
|
@@ -1420,7 +1420,7 @@
|
|
|
1420
1420
|
"type": "success",
|
|
1421
1421
|
"stages": [
|
|
1422
1422
|
{
|
|
1423
|
-
"output": "-(-)",
|
|
1423
|
+
"output": "- (-)",
|
|
1424
1424
|
"variables": "[]"
|
|
1425
1425
|
}
|
|
1426
1426
|
]
|
|
@@ -1431,7 +1431,7 @@
|
|
|
1431
1431
|
"query": "writeq(-[-]).",
|
|
1432
1432
|
"input": "writeq(-[-]).",
|
|
1433
1433
|
"readCount": 1,
|
|
1434
|
-
"expected": "-[-]
|
|
1434
|
+
"expected": "-[-]",
|
|
1435
1435
|
"outcome": {
|
|
1436
1436
|
"type": "success",
|
|
1437
1437
|
"stages": [
|
|
@@ -1479,7 +1479,7 @@
|
|
|
1479
1479
|
"query": "writeq(-{a}).",
|
|
1480
1480
|
"input": "writeq(-{a}).",
|
|
1481
1481
|
"readCount": 1,
|
|
1482
|
-
"expected": "-{a}
|
|
1482
|
+
"expected": "-{a}",
|
|
1483
1483
|
"outcome": {
|
|
1484
1484
|
"type": "success",
|
|
1485
1485
|
"stages": [
|
|
@@ -1532,7 +1532,7 @@
|
|
|
1532
1532
|
"type": "success",
|
|
1533
1533
|
"stages": [
|
|
1534
1534
|
{
|
|
1535
|
-
"output": "- -(1)",
|
|
1535
|
+
"output": "- - (1)",
|
|
1536
1536
|
"variables": "[]"
|
|
1537
1537
|
}
|
|
1538
1538
|
]
|
|
@@ -1543,7 +1543,7 @@
|
|
|
1543
1543
|
"query": "op(100,yfx,~).\nwriteq(-(1~2~3)).",
|
|
1544
1544
|
"input": "op(100,yfx,~).\nwriteq(-(1~2~3)).",
|
|
1545
1545
|
"readCount": 2,
|
|
1546
|
-
"expected": "- (1~2~3)
|
|
1546
|
+
"expected": "- (1~2~3)",
|
|
1547
1547
|
"outcome": {
|
|
1548
1548
|
"type": "success",
|
|
1549
1549
|
"stages": [
|
|
@@ -1552,7 +1552,7 @@
|
|
|
1552
1552
|
"variables": "[]"
|
|
1553
1553
|
},
|
|
1554
1554
|
{
|
|
1555
|
-
"output": "-(1~2~3)",
|
|
1555
|
+
"output": "- (1~2~3)",
|
|
1556
1556
|
"variables": "[]"
|
|
1557
1557
|
}
|
|
1558
1558
|
]
|
|
@@ -1563,7 +1563,7 @@
|
|
|
1563
1563
|
"query": " /**/ writeq(- (1~2)).",
|
|
1564
1564
|
"input": "(catch((op(100,yfx,~)), _, true) -> true ; true).\n writeq(- (1~2)).",
|
|
1565
1565
|
"readCount": 2,
|
|
1566
|
-
"expected": "- (1~2)
|
|
1566
|
+
"expected": "- (1~2)",
|
|
1567
1567
|
"outcome": {
|
|
1568
1568
|
"type": "success",
|
|
1569
1569
|
"stages": [
|
|
@@ -1572,7 +1572,7 @@
|
|
|
1572
1572
|
"variables": "[]"
|
|
1573
1573
|
},
|
|
1574
1574
|
{
|
|
1575
|
-
"output": "-(1~2)",
|
|
1575
|
+
"output": "- (1~2)",
|
|
1576
1576
|
"variables": "[]"
|
|
1577
1577
|
}
|
|
1578
1578
|
]
|
|
@@ -1619,7 +1619,7 @@
|
|
|
1619
1619
|
"query": "op(9,xf,'$VAR'), writeq(- '$VAR'(0)).",
|
|
1620
1620
|
"input": "op(9,xf,'$VAR'), writeq(- '$VAR'(0)).",
|
|
1621
1621
|
"readCount": 1,
|
|
1622
|
-
"expected": "-A
|
|
1622
|
+
"expected": "-A",
|
|
1623
1623
|
"outcome": {
|
|
1624
1624
|
"type": "success",
|
|
1625
1625
|
"stages": [
|
|
@@ -1695,7 +1695,7 @@
|
|
|
1695
1695
|
"query": "compound(+1).",
|
|
1696
1696
|
"input": "compound(+1).",
|
|
1697
1697
|
"readCount": 1,
|
|
1698
|
-
"expected": "succeeds
|
|
1698
|
+
"expected": "succeeds",
|
|
1699
1699
|
"outcome": {
|
|
1700
1700
|
"type": "success",
|
|
1701
1701
|
"stages": [
|
|
@@ -1711,7 +1711,7 @@
|
|
|
1711
1711
|
"query": "compound(+ 1).",
|
|
1712
1712
|
"input": "compound(+ 1).",
|
|
1713
1713
|
"readCount": 1,
|
|
1714
|
-
"expected": "succeeds
|
|
1714
|
+
"expected": "succeeds",
|
|
1715
1715
|
"outcome": {
|
|
1716
1716
|
"type": "success",
|
|
1717
1717
|
"stages": [
|
|
@@ -1727,7 +1727,7 @@
|
|
|
1727
1727
|
"query": "writeq(+1^2).",
|
|
1728
1728
|
"input": "writeq(+1^2).",
|
|
1729
1729
|
"readCount": 1,
|
|
1730
|
-
"expected": "+1^2
|
|
1730
|
+
"expected": "+1^2",
|
|
1731
1731
|
"outcome": {
|
|
1732
1732
|
"type": "success",
|
|
1733
1733
|
"stages": [
|
|
@@ -1754,7 +1754,7 @@
|
|
|
1754
1754
|
"query": "writeq([+{a},+[]]).",
|
|
1755
1755
|
"input": "writeq([+{a},+[]]).",
|
|
1756
1756
|
"readCount": 1,
|
|
1757
|
-
"expected": "[+{a},+[]]
|
|
1757
|
+
"expected": "[+{a},+[]]",
|
|
1758
1758
|
"outcome": {
|
|
1759
1759
|
"type": "success",
|
|
1760
1760
|
"stages": [
|
|
@@ -1819,7 +1819,7 @@
|
|
|
1819
1819
|
"query": "op(999,xfy,'|').",
|
|
1820
1820
|
"input": "op(999,xfy,'|').",
|
|
1821
1821
|
"readCount": 1,
|
|
1822
|
-
"expected": "p._e.(c., o.,'|')
|
|
1822
|
+
"expected": "p._e.(c., o.,'|')",
|
|
1823
1823
|
"outcome": {
|
|
1824
1824
|
"type": "error",
|
|
1825
1825
|
"formal": "permission_error(create, operator)"
|
|
@@ -1952,7 +1952,7 @@
|
|
|
1952
1952
|
"query": "current_op(200,fy,+).",
|
|
1953
1953
|
"input": "current_op(200,fy,+).",
|
|
1954
1954
|
"readCount": 1,
|
|
1955
|
-
"expected": "succeeds
|
|
1955
|
+
"expected": "succeeds",
|
|
1956
1956
|
"outcome": {
|
|
1957
1957
|
"type": "success",
|
|
1958
1958
|
"stages": [
|
|
@@ -2984,7 +2984,7 @@
|
|
|
2984
2984
|
"type": "success",
|
|
2985
2985
|
"stages": [
|
|
2986
2986
|
{
|
|
2987
|
-
"output": "-(a*b)",
|
|
2987
|
+
"output": "- (a*b)",
|
|
2988
2988
|
"variables": "[]"
|
|
2989
2989
|
}
|
|
2990
2990
|
]
|
|
@@ -3000,7 +3000,7 @@
|
|
|
3000
3000
|
"type": "success",
|
|
3001
3001
|
"stages": [
|
|
3002
3002
|
{
|
|
3003
|
-
"output": "\\(a*b)",
|
|
3003
|
+
"output": "\\ (a*b)",
|
|
3004
3004
|
"variables": "[]"
|
|
3005
3005
|
}
|
|
3006
3006
|
]
|
|
@@ -3173,7 +3173,7 @@
|
|
|
3173
3173
|
"query": "writeq('$VAR'(0)).",
|
|
3174
3174
|
"input": "writeq('$VAR'(0)).",
|
|
3175
3175
|
"readCount": 1,
|
|
3176
|
-
"expected": "A
|
|
3176
|
+
"expected": "A",
|
|
3177
3177
|
"outcome": {
|
|
3178
3178
|
"type": "success",
|
|
3179
3179
|
"stages": [
|
|
@@ -3738,7 +3738,7 @@
|
|
|
3738
3738
|
"query": " /**/ writeq(\"a\").",
|
|
3739
3739
|
"input": "(catch((set_prolog_flag(\n double_quotes,chars)), _, true) -> true ; true).\n writeq(\"a\").",
|
|
3740
3740
|
"readCount": 2,
|
|
3741
|
-
"expected": "[a]
|
|
3741
|
+
"expected": "[a]",
|
|
3742
3742
|
"outcome": {
|
|
3743
3743
|
"type": "success",
|
|
3744
3744
|
"stages": [
|
|
@@ -3885,7 +3885,7 @@
|
|
|
3885
3885
|
"query": " /**/ writeq((a-->b,c|d)).",
|
|
3886
3886
|
"input": "(catch((op(1105,xfy,'|')), _, true) -> true ; true).\n writeq((a-->b,c|d)).",
|
|
3887
3887
|
"readCount": 2,
|
|
3888
|
-
"expected": "a-->b,c|d
|
|
3888
|
+
"expected": "a-->b,c|d",
|
|
3889
3889
|
"outcome": {
|
|
3890
3890
|
"type": "success",
|
|
3891
3891
|
"stages": [
|
|
@@ -3956,7 +3956,7 @@
|
|
|
3956
3956
|
"query": " /**/ writeq([(a|b)]).",
|
|
3957
3957
|
"input": "(catch((op(1105,xfy,'|')), _, true) -> true ; true).\n writeq([(a|b)]).",
|
|
3958
3958
|
"readCount": 2,
|
|
3959
|
-
"expected": "[(a|b)]
|
|
3959
|
+
"expected": "[(a|b)]",
|
|
3960
3960
|
"outcome": {
|
|
3961
3961
|
"type": "success",
|
|
3962
3962
|
"stages": [
|
|
@@ -4210,7 +4210,7 @@
|
|
|
4210
4210
|
"query": "op(400,fx,f).\nwriteq(f/*.*/(1,2)).",
|
|
4211
4211
|
"input": "op(400,fx,f).\nwriteq(f/*.*/(1,2)).",
|
|
4212
4212
|
"readCount": 2,
|
|
4213
|
-
"expected": "f (1,2)
|
|
4213
|
+
"expected": "f (1,2)",
|
|
4214
4214
|
"outcome": {
|
|
4215
4215
|
"type": "success",
|
|
4216
4216
|
"stages": [
|
|
@@ -4660,7 +4660,7 @@
|
|
|
4660
4660
|
"query": "op(500, xfy, {}).",
|
|
4661
4661
|
"input": "op(500, xfy, {}).",
|
|
4662
4662
|
"readCount": 1,
|
|
4663
|
-
"expected": "p._e.(c.,o.,{})
|
|
4663
|
+
"expected": "p._e.(c.,o.,{})",
|
|
4664
4664
|
"outcome": {
|
|
4665
4665
|
"type": "error",
|
|
4666
4666
|
"formal": "permission_error(create, operator)"
|
|
@@ -4958,7 +4958,7 @@
|
|
|
4958
4958
|
"variables": "[]"
|
|
4959
4959
|
},
|
|
4960
4960
|
{
|
|
4961
|
-
"output": "~(a=b)",
|
|
4961
|
+
"output": "~ (a=b)",
|
|
4962
4962
|
"variables": "[]"
|
|
4963
4963
|
}
|
|
4964
4964
|
]
|
|
@@ -4974,7 +4974,7 @@
|
|
|
4974
4974
|
"type": "success",
|
|
4975
4975
|
"stages": [
|
|
4976
4976
|
{
|
|
4977
|
-
"output": "\\(a=b)",
|
|
4977
|
+
"output": "\\ (a=b)",
|
|
4978
4978
|
"variables": "[]"
|
|
4979
4979
|
}
|
|
4980
4980
|
]
|
|
@@ -4990,7 +4990,7 @@
|
|
|
4990
4990
|
"type": "success",
|
|
4991
4991
|
"stages": [
|
|
4992
4992
|
{
|
|
4993
|
-
"output": "+(a=b)",
|
|
4993
|
+
"output": "+ (a=b)",
|
|
4994
4994
|
"variables": "[]"
|
|
4995
4995
|
}
|
|
4996
4996
|
]
|
|
@@ -5076,7 +5076,7 @@
|
|
|
5076
5076
|
"type": "success",
|
|
5077
5077
|
"stages": [
|
|
5078
5078
|
{
|
|
5079
|
-
"output": "+(1*2)^3",
|
|
5079
|
+
"output": "+ (1*2)^3",
|
|
5080
5080
|
"variables": "[]"
|
|
5081
5081
|
}
|
|
5082
5082
|
]
|
|
@@ -5092,7 +5092,7 @@
|
|
|
5092
5092
|
"type": "success",
|
|
5093
5093
|
"stages": [
|
|
5094
5094
|
{
|
|
5095
|
-
"output": "-((1*2)^3)",
|
|
5095
|
+
"output": "- ((1*2)^3)",
|
|
5096
5096
|
"variables": "[]"
|
|
5097
5097
|
}
|
|
5098
5098
|
]
|
package/test/run-regression.mjs
CHANGED
|
@@ -132,7 +132,7 @@ function regressionCases() {
|
|
|
132
132
|
{
|
|
133
133
|
name: 'library(atts) provides Scryer-style attributed-variable hooks',
|
|
134
134
|
run: () => {
|
|
135
|
-
const source = `:- module(attr_probe, [check_get/0, check_alias/0, check_ok/0, check_bad/0, check_conflict/0, check_backtrack/0, check_term_vars/0]).
|
|
135
|
+
const source = `:- module(attr_probe, [check_get/0, check_alias/0, check_ok/0, check_bad/0, check_conflict/0, check_backtrack/0, check_term_vars/0, check_residue_new/0, check_residue_unchanged/0, check_residue_modified/0, check_residue_dif/0]).
|
|
136
136
|
:- use_module(library(atts)).
|
|
137
137
|
:- attribute mark/1.
|
|
138
138
|
attach(X, V) :- put_atts(X, mark(V)).
|
|
@@ -151,6 +151,10 @@ check_bad :- attach(X, 3), X = 4.
|
|
|
151
151
|
check_conflict :- attach(X, a), attach(Y, b), X = Y.
|
|
152
152
|
check_backtrack :- (attach(X, a), fail ; true), \\+ get_atts(X, mark(_)).
|
|
153
153
|
check_term_vars :- attach(X, a), term_attributed_variables(pair(X,Y), [X]), var(Y), X = a.
|
|
154
|
+
check_residue_new :- call_residue_vars(attach(X, a), Vs), Vs = [X].
|
|
155
|
+
check_residue_unchanged :- attach(X, a), call_residue_vars(true, Vs), Vs = [], X = a.
|
|
156
|
+
check_residue_modified :- attach(X, a), call_residue_vars(put_atts(X, mark(b)), Vs), Vs = [X], X = b.
|
|
157
|
+
check_residue_dif :- call_residue_vars(dif(X,Y), Vs), Vs = [X,Y].
|
|
154
158
|
`;
|
|
155
159
|
assertIncludes(run(source, { goal: 'attr_probe:check_get' }).stdout, 'attr_probe:check_get.', 'get_atts/2');
|
|
156
160
|
assertIncludes(run(source, { goal: 'attr_probe:check_alias' }).stdout, 'attr_probe:check_alias.', 'attribute transfer on aliasing');
|
|
@@ -159,6 +163,20 @@ check_term_vars :- attach(X, a), term_attributed_variables(pair(X,Y), [X]), var(
|
|
|
159
163
|
assertEqual(run(source, { goal: 'attr_probe:check_conflict' }).stdout, '', 'same-module attributes are verified before aliasing');
|
|
160
164
|
assertIncludes(run(source, { goal: 'attr_probe:check_backtrack' }).stdout, 'attr_probe:check_backtrack.', 'attributes backtrack with Env branches');
|
|
161
165
|
assertIncludes(run(source, { goal: 'attr_probe:check_term_vars' }).stdout, 'attr_probe:check_term_vars.', 'term_attributed_variables/2');
|
|
166
|
+
assertIncludes(run(source, { goal: 'attr_probe:check_residue_new' }).stdout, 'attr_probe:check_residue_new.', 'call_residue_vars/2 reports newly attributed variables');
|
|
167
|
+
assertIncludes(run(source, { goal: 'attr_probe:check_residue_unchanged' }).stdout, 'attr_probe:check_residue_unchanged.', 'call_residue_vars/2 ignores unchanged pre-existing attributes');
|
|
168
|
+
assertIncludes(run(source, { goal: 'attr_probe:check_residue_modified' }).stdout, 'attr_probe:check_residue_modified.', 'call_residue_vars/2 reports modified attributes');
|
|
169
|
+
assertIncludes(run(source, { goal: 'attr_probe:check_residue_dif' }).stdout, 'attr_probe:check_residue_dif.', 'call_residue_vars/2 includes native dif/2 residual variables');
|
|
170
|
+
const freezeResidue = `:- use_module(library(atts)).
|
|
171
|
+
:- use_module(library(prologue), [freeze/2]).
|
|
172
|
+
check :- call_residue_vars(freeze(X,true), Vs), Vs = [X].
|
|
173
|
+
`;
|
|
174
|
+
assertIncludes(run(freezeResidue, { goal: 'check' }).stdout, 'check.', 'call_residue_vars/2 includes delayed freeze/2 variables');
|
|
175
|
+
const clpzResidue = `:- use_module(library(atts)).
|
|
176
|
+
:- use_module(library(clpz)).
|
|
177
|
+
check :- call_residue_vars(X in 1..3, Vs), Vs = [X].
|
|
178
|
+
`;
|
|
179
|
+
assertIncludes(run(clpzResidue, { goal: 'check' }).stdout, 'check.', 'call_residue_vars/2 includes CLP(Z) attributed variables');
|
|
162
180
|
|
|
163
181
|
const residualFile = path.join(tmp, `atts-residual-${++tmpCounter}.pl`);
|
|
164
182
|
fs.writeFileSync(residualFile, `:- use_module(library(atts)).
|
|
@@ -314,6 +332,19 @@ probe :- empty_assoc(A), copy_term_nat(A, _), bb_b_put(current, ok), bb_get(curr
|
|
|
314
332
|
goal: "catch((VN=[[]],write_term(T,[variable_names(VN)])),error(E,_),writeq(E))",
|
|
315
333
|
});
|
|
316
334
|
assertEqual(list.stdout, 'domain_error(write_option,variable_names([[]]))', 'list culprit');
|
|
335
|
+
|
|
336
|
+
assertEqual(run('', { isoStrict: true, goal: 'writeq(-(1^2))' }).stdout.split('writeq(')[0],
|
|
337
|
+
'- (1^2)', 'writeq negative power layout');
|
|
338
|
+
assertEqual(run('', { isoStrict: true, goal: 'writeq(-(a^2))' }).stdout.split('writeq(')[0],
|
|
339
|
+
'- (a^2)', 'writeq symbolic negative power layout');
|
|
340
|
+
assertEqual(run('', {
|
|
341
|
+
isoStrict: true,
|
|
342
|
+
goal: "write_term(-X^2,[variable_names(['X'=X])])",
|
|
343
|
+
}).stdout, '- (X^2)', 'write_term variable_names negative power layout');
|
|
344
|
+
assertEqual(run('', {
|
|
345
|
+
isoStrict: true,
|
|
346
|
+
goal: "X=1,write_term(-X^2,[variable_names(['X'=X])])",
|
|
347
|
+
}).stdout.split('1 = 1')[0], '- (1^2)', 'write_term bound variable negative power layout');
|
|
317
348
|
},
|
|
318
349
|
},
|
|
319
350
|
{
|
|
@@ -2077,6 +2108,53 @@ c4 ?- call((!;1)).
|
|
|
2077
2108
|
assertEqual(advanced.stderr, '', 'advanced stderr');
|
|
2078
2109
|
},
|
|
2079
2110
|
},
|
|
2111
|
+
{
|
|
2112
|
+
name: 'REPL exposes output side effects while search is still running (issue #72)',
|
|
2113
|
+
run: () => {
|
|
2114
|
+
const helper = `
|
|
2115
|
+
import { spawn } from 'node:child_process';
|
|
2116
|
+
|
|
2117
|
+
const child = spawn(${JSON.stringify(process.execPath)}, [${JSON.stringify(bin)}], {
|
|
2118
|
+
cwd: ${JSON.stringify(packageRoot)},
|
|
2119
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
2120
|
+
});
|
|
2121
|
+
child.stdout.setEncoding('utf8');
|
|
2122
|
+
child.stderr.setEncoding('utf8');
|
|
2123
|
+
let stdout = '';
|
|
2124
|
+
let stderr = '';
|
|
2125
|
+
child.stdout.on('data', (text) => { stdout += text; });
|
|
2126
|
+
child.stderr.on('data', (text) => { stderr += text; });
|
|
2127
|
+
|
|
2128
|
+
async function waitFor(predicate, label) {
|
|
2129
|
+
const deadline = Date.now() + 5000;
|
|
2130
|
+
while (!predicate()) {
|
|
2131
|
+
if (Date.now() >= deadline) {
|
|
2132
|
+
child.kill('SIGKILL');
|
|
2133
|
+
throw new Error(label + ' timeout; stdout=' + JSON.stringify(stdout) + '; stderr=' + JSON.stringify(stderr));
|
|
2134
|
+
}
|
|
2135
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
child.stdin.write('use_module(library(iso_ext)).\\n');
|
|
2140
|
+
await waitFor(() => stdout.includes(' true.\\n?- '), 'module import');
|
|
2141
|
+
child.stdin.write('call_nth(repeat,Nth), (Nth mod 2=:=0->writeq(r(Nth)),nl;true),Nth<0.\\n');
|
|
2142
|
+
await waitFor(() => stdout.includes('r(2)\\nr(4)\\n'), 'progress output');
|
|
2143
|
+
if (stdout.includes('false.')) throw new Error('infinite search unexpectedly reached a leaf');
|
|
2144
|
+
child.kill('SIGKILL');
|
|
2145
|
+
await new Promise((resolve) => child.once('exit', resolve));
|
|
2146
|
+
process.stdout.write('progress-visible');
|
|
2147
|
+
`;
|
|
2148
|
+
const result = spawnSync(process.execPath, [
|
|
2149
|
+
'--input-type=module',
|
|
2150
|
+
'--eval',
|
|
2151
|
+
helper,
|
|
2152
|
+
], { cwd: packageRoot, encoding: 'utf8', timeout: 10000 });
|
|
2153
|
+
if (result.error) throw result.error;
|
|
2154
|
+
assertEqual(result.status, 0, `progress helper status; stderr=${result.stderr}`);
|
|
2155
|
+
assertEqual(result.stdout, 'progress-visible', 'newline-terminated progress is visible before a leaf answer');
|
|
2156
|
+
},
|
|
2157
|
+
},
|
|
2080
2158
|
{
|
|
2081
2159
|
name: 'REPL bindings use argument syntax for operator atoms',
|
|
2082
2160
|
run: () => {
|
|
@@ -3703,6 +3781,16 @@ function documentationSyncCases() {
|
|
|
3703
3781
|
assertEqual(parsed[0].query, 'set_prolog_flag( double_quotes,chars).', 'normalized query');
|
|
3704
3782
|
},
|
|
3705
3783
|
},
|
|
3784
|
+
{
|
|
3785
|
+
name: 'WG17 upgrader removes presentation footnote markers from Codex text',
|
|
3786
|
+
run: () => {
|
|
3787
|
+
const rows = Array.from({ length: 120 }, (_, index) =>
|
|
3788
|
+
`<tr><td>${index + 1}<td>writeq(-(1^2)).<td>- (1^2)³`).join('\n');
|
|
3789
|
+
const html = `<table><tr><th>#<th>Query<th>Codex${rows}</table>`;
|
|
3790
|
+
const parsed = parseWg17SyntaxTable(html);
|
|
3791
|
+
assertEqual(parsed[0].expected, '- (1^2)', 'normalized Codex expectation');
|
|
3792
|
+
},
|
|
3793
|
+
},
|
|
3706
3794
|
{
|
|
3707
3795
|
name: 'WG17 upstream expectations independently validate reviewed outcomes',
|
|
3708
3796
|
run: () => {
|
|
@@ -3719,6 +3807,22 @@ function documentationSyncCases() {
|
|
|
3719
3807
|
true,
|
|
3720
3808
|
'observable output',
|
|
3721
3809
|
);
|
|
3810
|
+
const negativePower = { id: 183, input: 'writeq(-(1^2)).' };
|
|
3811
|
+
assertEqual(
|
|
3812
|
+
matchesUpstreamExpectation('- (1^2)', { type: 'success', stages: [{ output: '-(1^2)', variables: '[]' }] }, negativePower),
|
|
3813
|
+
false,
|
|
3814
|
+
'mandatory operator/parenthesis layout is not erased',
|
|
3815
|
+
);
|
|
3816
|
+
assertEqual(
|
|
3817
|
+
matchesUpstreamExpectation('- (a^2)', { type: 'success', stages: [{ output: '-a^2', variables: '[]' }] }, negativePower),
|
|
3818
|
+
false,
|
|
3819
|
+
'mandatory negative-power parentheses are not erased',
|
|
3820
|
+
);
|
|
3821
|
+
assertEqual(
|
|
3822
|
+
matchesUpstreamExpectation('- (1^2)', { type: 'success', stages: [{ output: '- (1 ^ 2)', variables: '[]' }] }, negativePower),
|
|
3823
|
+
true,
|
|
3824
|
+
'non-semantic internal operator spacing remains flexible',
|
|
3825
|
+
);
|
|
3722
3826
|
const repeated = {
|
|
3723
3827
|
id: 227, input: 'write_canonical(B+B).',
|
|
3724
3828
|
outcome: { type: 'success', stages: [{ output: '+(_A,_A)', variables: "['B' = B]" }] },
|
|
@@ -4900,7 +5004,7 @@ answer(ok) :-
|
|
|
4900
5004
|
assertEqual(Boolean(registry.get('is', 2)), true, 'ISO is/2 exists');
|
|
4901
5005
|
assertEqual(Boolean(registry.get('append', 3)), false, 'append/3 is not ISO core');
|
|
4902
5006
|
assertEqual(library.eyePrologLibrary, true, 'complete registry marker');
|
|
4903
|
-
assertEqual(library.defs.size,
|
|
5007
|
+
assertEqual(library.defs.size, 155, 'EyeProlog registry contains ISO definitions, cleanup controls, observability extensions, WFS tnot/1, and generic library adapters');
|
|
4904
5008
|
assertEqual(Boolean(registry.get('phrase', 2)), true, 'Part 3 phrase/2 exists');
|
|
4905
5009
|
assertEqual(Boolean(registry.get('phrase', 3)), true, 'Part 3 phrase/3 exists');
|
|
4906
5010
|
assertEqual(registry.get('statistics', 0), null, 'statistics/0 is absent from the ISO registry');
|
|
@@ -4917,21 +5021,23 @@ answer(ok) :-
|
|
|
4917
5021
|
assertEqual(Boolean(library.get('call_cleanup', 2)), true, 'call_cleanup/2 is an EyeProlog cleanup control');
|
|
4918
5022
|
assertEqual(registry.get('setup_call_cleanup', 3), null, 'setup_call_cleanup/3 is absent from the ISO registry');
|
|
4919
5023
|
assertEqual(Boolean(library.get('setup_call_cleanup', 3)), true, 'setup_call_cleanup/3 is an EyeProlog cleanup control');
|
|
4920
|
-
assertEqual(registeredNativeEyePrologLibraryNames().length,
|
|
5024
|
+
assertEqual(registeredNativeEyePrologLibraryNames().length, 49, 'public native EyeProlog builtin count');
|
|
4921
5025
|
assertEqual(eyePrologPortableLibraryIndicators.length, 87, 'portable Prolog library count');
|
|
4922
|
-
assertEqual(eyePrologInteropLibraryIndicators.length,
|
|
5026
|
+
assertEqual(eyePrologInteropLibraryIndicators.length, 30, 'cross-implementation interop profile count');
|
|
4923
5027
|
assertEqual(eyePrologInteropLibraryModules.join(','), 'lists,iso_ext,lambda,atts', 'common explicit library module profile');
|
|
4924
5028
|
assertEqual(eyePrologInteropAutoload['member/2'], 'lists', 'member/2 canonical autoload');
|
|
4925
5029
|
assertEqual(eyePrologInteropAutoload['between/3'], 'prologue', 'between/3 canonical internal autoload');
|
|
4926
5030
|
assertEqual(eyePrologInteropAutoload['call_nth/2'], 'iso_ext', 'call_nth/2 canonical interop autoload');
|
|
5031
|
+
assertEqual(eyePrologInteropAutoload['call_residue_vars/2'], 'atts', 'call_residue_vars/2 canonical interop autoload');
|
|
4927
5032
|
assertEqual(eyePrologInteropAutoload['time/1'], 'iso_ext', 'time/1 canonical interop autoload');
|
|
4928
5033
|
assertEqual(eyePrologInteropAutoload['.../2'], 'iso_ext', '.../2 canonical interop autoload');
|
|
4929
5034
|
assertEqual(eyePrologInteropAutoload['set_nth0/4'] ?? null, null, 'EyeProlog-only set_nth0/4 is not autoloadable');
|
|
4930
|
-
assertEqual(eyePrologNativeLibraryIndicators.length,
|
|
5035
|
+
assertEqual(eyePrologNativeLibraryIndicators.length, 49, 'native host library count');
|
|
4931
5036
|
assertEqual(eyePrologNativeLibraryIndicators.slice(0, 3).join(','), 'call_nth/2,freeze/2,dif/2', 'control and constraint predicates requiring host support');
|
|
4932
|
-
assertEqual(eyePrologLibraryIndicators.length,
|
|
5037
|
+
assertEqual(eyePrologLibraryIndicators.length, 136, 'complete EyeProlog library surface');
|
|
4933
5038
|
assertEqual(registry.get('eyeprolog__call_nth', 2), null, 'private call_nth adapter is absent from ISO registry');
|
|
4934
5039
|
assertEqual(Boolean(library.get('eyeprolog__call_nth', 2)), true, 'private call_nth adapter is registered for EyeProlog');
|
|
5040
|
+
assertEqual(Boolean(library.get('eyeprolog__call_residue_vars', 2)), true, 'private call_residue_vars adapter is registered for EyeProlog');
|
|
4935
5041
|
assertEqual(library.get('eyeprolog__call_nth', 2)?.eyePrologLibrary, true, 'private adapter is marked as library support');
|
|
4936
5042
|
assertEqual(registry.get('eyeprolog__freeze', 2), null, 'private freeze adapter is absent from ISO registry');
|
|
4937
5043
|
assertEqual(Boolean(library.get('eyeprolog__freeze', 2)), true, 'private freeze adapter is registered for EyeProlog');
|
package/test/run-wg17.mjs
CHANGED
|
@@ -140,6 +140,38 @@ function stripLayoutOutsideQuotes(text) {
|
|
|
140
140
|
return output.replace(/\.$/, '').replace(/([eE])\+(?=\d)/g, '$1');
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
function leadingGraphicOperatorParenSignature(text) {
|
|
144
|
+
const source = presentationText(text);
|
|
145
|
+
let index = 0;
|
|
146
|
+
const operators = [];
|
|
147
|
+
while (index < source.length) {
|
|
148
|
+
const match = source.slice(index).match(/^[!#$&*+\-./<=>?@^~\\]+/);
|
|
149
|
+
if (match == null) return null;
|
|
150
|
+
operators.push(match[0]);
|
|
151
|
+
index += match[0].length;
|
|
152
|
+
const layoutStart = index;
|
|
153
|
+
while (/\s/.test(source[index] ?? '')) index++;
|
|
154
|
+
if (index === layoutStart) return null;
|
|
155
|
+
if (source[index] === '(') return operators;
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function preservesLeadingOperatorParenLayout(expected, actual) {
|
|
161
|
+
const signature = leadingGraphicOperatorParenSignature(expected);
|
|
162
|
+
if (signature == null) return true;
|
|
163
|
+
const source = presentationText(actual);
|
|
164
|
+
let index = 0;
|
|
165
|
+
for (const operator of signature) {
|
|
166
|
+
if (!source.startsWith(operator, index)) return false;
|
|
167
|
+
index += operator.length;
|
|
168
|
+
const layoutStart = index;
|
|
169
|
+
while (/\s/.test(source[index] ?? '')) index++;
|
|
170
|
+
if (index === layoutStart) return false;
|
|
171
|
+
}
|
|
172
|
+
return source[index] === '(';
|
|
173
|
+
}
|
|
174
|
+
|
|
143
175
|
function normalizeExampleVariables(text) {
|
|
144
176
|
const names = new Map();
|
|
145
177
|
let next = 0;
|
|
@@ -248,6 +280,10 @@ function textExpectationMatches(expectedText, actual, item, example = false) {
|
|
|
248
280
|
left = normalizeExampleVariables(left);
|
|
249
281
|
right = normalizeExampleVariables(right);
|
|
250
282
|
}
|
|
283
|
+
// Some Codex expectations intentionally carry a mandatory lexical
|
|
284
|
+
// boundary, notably Cor.3 prefix-operator cases such as `- (1^2)`. Do not
|
|
285
|
+
// erase that boundary before the semantic/layout-tolerant fallbacks below.
|
|
286
|
+
if (!preservesLeadingOperatorParenLayout(left, right)) continue;
|
|
251
287
|
if (stripLayoutOutsideQuotes(left) === stripLayoutOutsideQuotes(right)) return true;
|
|
252
288
|
if (termEquivalent(left, right, item)) return true;
|
|
253
289
|
if (example) {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -6253,7 +6253,7 @@ between solution branches.
|
|
|
6253
6253
|
| Module | Exported predicate indicators |
|
|
6254
6254
|
| --- | --- |
|
|
6255
6255
|
| `library(aggregate)` | `sumall/3`, `aggregate_min/5`, `aggregate_max/5` |
|
|
6256
|
-
| `library(atts)` | `put_atts/2`, `get_atts/2`, `put_attr/3`, `get_attr/3`, `del_attr/2`, `term_attributed_variables/2` |
|
|
6256
|
+
| `library(atts)` | `put_atts/2`, `get_atts/2`, `put_attr/3`, `get_attr/3`, `del_attr/2`, `term_attributed_variables/2`, `call_residue_vars/2` |
|
|
6257
6257
|
| `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` |
|
|
6258
6258
|
| `library(comparison)` | `lt/2`, `gt/2`, `le/2`, `ge/2` |
|
|
6259
6259
|
| `library(dates)` | `difference/3` |
|
|
@@ -99,10 +99,14 @@ upstream assertion.
|
|
|
99
99
|
|
|
100
100
|
## Current standing
|
|
101
101
|
|
|
102
|
+
The percentage below is **traceability coverage**, not a conformance pass rate.
|
|
103
|
+
A represented case still has to pass the executable upstream assertion in
|
|
104
|
+
\`test/run-wg17.mjs\`.
|
|
105
|
+
|
|
102
106
|
| Measure | Count |
|
|
103
107
|
| --- | ---: |
|
|
104
108
|
| Active upstream cases | ${activeIds.length} |
|
|
105
|
-
|
|
|
109
|
+
| Upstream cases represented in the executable matrix | ${coveredIds.length} (${percentage}%) |
|
|
106
110
|
| Not yet traced one-by-one | ${untracedIds.length} |
|
|
107
111
|
| Deleted upstream identifiers | ${formatRanges(manifest.upstream.deletedIds)} |
|
|
108
112
|
|
package/tools/upgrade-wg17.mjs
CHANGED
|
@@ -16,6 +16,7 @@ const namedEntities = new Map([
|
|
|
16
16
|
['amp', '&'], ['lt', '<'], ['gt', '>'], ['quot', '"'], ['apos', "'"],
|
|
17
17
|
['nbsp', '\u00a0'], ['ndash', '–'], ['mdash', '—'], ['minus', '−'],
|
|
18
18
|
['hellip', '…'], ['middot', '·'], ['times', '×'], ['laquo', '«'], ['raquo', '»'],
|
|
19
|
+
['sup2', '²'], ['sup3', '³'], ['deg', '°'],
|
|
19
20
|
]);
|
|
20
21
|
|
|
21
22
|
export function decodeHtmlEntities(text) {
|
|
@@ -96,7 +97,7 @@ export function parseWg17SyntaxTable(html) {
|
|
|
96
97
|
// They are not part of the Prolog source being specified, so normalize
|
|
97
98
|
// them to ordinary spaces before snapshotting/comparing rows.
|
|
98
99
|
const query = htmlCellText(cells[1]).replace(/\u00a0/g, ' ');
|
|
99
|
-
const expected = htmlCellText(cells[2]).replace(/\u00a0/g, ' ').replace(/[ \t\n]+/g, ' ').trim();
|
|
100
|
+
const expected = htmlCellText(cells[2]).replace(/\u00a0/g, ' ').replace(/[²³°]/g, '').replace(/[ \t\n]+/g, ' ').trim();
|
|
100
101
|
if (query.length === 0 || expected.length === 0) continue;
|
|
101
102
|
cases.push({ id, query, expected });
|
|
102
103
|
seen.add(id);
|