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/README.md
CHANGED
|
@@ -91,6 +91,11 @@ execution as positive STO evidence, and rejects `sto` when a finite execution
|
|
|
91
91
|
completes without such an event (for example `?- true. sto.`). If execution is
|
|
92
92
|
cut short by a search/resource boundary, the STO claim remains conservatively
|
|
93
93
|
unverified rather than being guessed.
|
|
94
|
+
For nontermination expectations, `loops` is kept distinct from resource
|
|
95
|
+
exhaustion: structural loop evidence may satisfy `loops`, while a bounded
|
|
96
|
+
search that cannot establish the requested answer sequence is reported as
|
|
97
|
+
`UNDECIDED`. In CLI quad mode, failures use exit status `1`; if there are no
|
|
98
|
+
failures but at least one undecided quad, the exit status is `2`.
|
|
94
99
|
|
|
95
100
|
## Tabling and well-founded negation
|
|
96
101
|
|
package/examples/book/README.md
CHANGED
package/package.json
CHANGED
package/src/io.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Synchronous ISO stream state shared by a solver and all of its inner solvers.
|
|
2
2
|
import { BufferCtor, fs } from './platform.js';
|
|
3
3
|
|
|
4
|
-
const INVALID_UTF8_SENTINEL = '\udc00';
|
|
4
|
+
export const INVALID_UTF8_SENTINEL = '\udc00';
|
|
5
5
|
|
|
6
6
|
export class InvalidCharacterEncodingError extends Error {
|
|
7
7
|
constructor() {
|
package/src/iso.js
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from './parser.js';
|
|
13
13
|
import { formatTermForWrite } from './write.js';
|
|
14
14
|
import { emptyTerminalSequence, expandDcgBody, isListOrPartialList, validateDcgEmbeddedGoals } from './dcg.js';
|
|
15
|
+
import { INVALID_UTF8_SENTINEL } from './io.js';
|
|
15
16
|
import {
|
|
16
17
|
characterCodeConstantEnd, continuesGraphicToken, isTerminatingFullStop, quotedEscapeEnd,
|
|
17
18
|
} from './syntax-scan.js';
|
|
@@ -1089,6 +1090,13 @@ function* termTextCandidates(stream, solver) {
|
|
|
1089
1090
|
let quote = null, lineComment = false, blockComment = false;
|
|
1090
1091
|
for (let i = stream.position; i < source.length; i++) {
|
|
1091
1092
|
const ch = source[i], next = source[i + 1];
|
|
1093
|
+
// Text streams preserve invalid UTF-8 bytes as an impossible Unicode
|
|
1094
|
+
// sentinel. read/1-2 and read_term/2-3 must surface the same character
|
|
1095
|
+
// representation error as get_char/1-2 instead of misclassifying the
|
|
1096
|
+
// undecodable byte as malformed Prolog syntax (issue #64).
|
|
1097
|
+
if (stream.strictUtf8 && ch === INVALID_UTF8_SENTINEL) {
|
|
1098
|
+
throw new PrologError('representation_error(character)');
|
|
1099
|
+
}
|
|
1092
1100
|
if (lineComment) { if (ch === '\n') lineComment = false; continue; }
|
|
1093
1101
|
if (blockComment) { if (ch === '*' && next === '/') { blockComment = false; i++; } continue; }
|
|
1094
1102
|
if (quote) {
|
|
@@ -1305,10 +1313,10 @@ function* readTermBuiltin({ solver, goal, env }) {
|
|
|
1305
1313
|
yield next;
|
|
1306
1314
|
}
|
|
1307
1315
|
function defaultTermWriteOptions(mode) {
|
|
1308
|
-
if (mode === 'writeq') return { quoted: true, ignoreOps: false, numbervars: true, variableNames: new Map(), compact: true, operatorAtomsAsArgs: true, doubleQuotes: null };
|
|
1309
|
-
if (mode === 'canonical') return { quoted: true, ignoreOps: true, numbervars: false, variableNames: new Map(), compact: true, operatorAtomsAsArgs: true, doubleQuotes: null };
|
|
1310
|
-
if (mode === 'write_term') return { quoted: false, ignoreOps: false, numbervars: false, variableNames: new Map(), compact: true, operatorAtomsAsArgs: true, doubleQuotes: null };
|
|
1311
|
-
return { quoted: false, ignoreOps: false, numbervars: true, variableNames: new Map(), compact: true, operatorAtomsAsArgs: true, doubleQuotes: null };
|
|
1316
|
+
if (mode === 'writeq') return { quoted: true, ignoreOps: false, numbervars: true, variableNames: new Map(), compact: true, minimalOperatorSpacing: true, operatorAtomsAsArgs: true, doubleQuotes: null };
|
|
1317
|
+
if (mode === 'canonical') return { quoted: true, ignoreOps: true, numbervars: false, variableNames: new Map(), compact: true, minimalOperatorSpacing: true, operatorAtomsAsArgs: true, doubleQuotes: null };
|
|
1318
|
+
if (mode === 'write_term') return { quoted: false, ignoreOps: false, numbervars: false, variableNames: new Map(), compact: true, minimalOperatorSpacing: true, operatorAtomsAsArgs: true, doubleQuotes: null };
|
|
1319
|
+
return { quoted: false, ignoreOps: false, numbervars: true, variableNames: new Map(), compact: true, minimalOperatorSpacing: true, operatorAtomsAsArgs: true, doubleQuotes: null };
|
|
1312
1320
|
}
|
|
1313
1321
|
|
|
1314
1322
|
function writeOptionBoolean(value, env, option) {
|
package/src/quads.js
CHANGED
|
@@ -11,6 +11,7 @@ import { Program } from './program.js';
|
|
|
11
11
|
import { Solver } from './solver.js';
|
|
12
12
|
import { getEyePrologRegistry } from './standard-library.js';
|
|
13
13
|
import { formatTermForWrite } from './write.js';
|
|
14
|
+
import { INVALID_UTF8_SENTINEL } from './io.js';
|
|
14
15
|
|
|
15
16
|
const DEFAULT_QUAD_MAX_INFERENCES = 100000;
|
|
16
17
|
const DEFAULT_LOOP_MAX_INFERENCES = 10000;
|
|
@@ -93,18 +94,24 @@ function checkAlternative(program, quad, alternative, options, context) {
|
|
|
93
94
|
return { ok: false, kind: 'unsupported', expected: unsupported };
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
const
|
|
97
|
-
if (
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
const ioLeaves = leaves.filter((leaf) => leaf.input != null || leaf.peek != null);
|
|
98
|
+
if (ioLeaves.length > 1 || (ioLeaves.length > 0 && leaves.length !== 1)) {
|
|
99
|
+
return { ok: false, kind: 'malformed', expected: alternative };
|
|
100
|
+
}
|
|
101
|
+
const hasInputSpec = ioLeaves.length === 1;
|
|
102
|
+
const inputLeaf = ioLeaves[0] ?? null;
|
|
103
|
+
const input = inputLeaf?.input ?? '';
|
|
104
|
+
const peek = inputLeaf?.peek ?? null;
|
|
100
105
|
const moreAt = leaves.findIndex((leaf) => leaf.more);
|
|
101
106
|
const describedCount = moreAt < 0 ? leaves.length : moreAt + (leaves[moreAt].hasExpectation ? 1 : 0);
|
|
102
|
-
const maxSolutions =
|
|
107
|
+
const maxSolutions = hasInputSpec
|
|
103
108
|
? 1
|
|
104
109
|
: moreAt < 0 ? describedCount + 1 : Math.max(describedCount, 1);
|
|
105
110
|
const actual = executeQuery(program, quad.query, input, maxSolutions, {
|
|
106
111
|
...options,
|
|
107
112
|
detectLoops: leaves.some((leaf) => leaf.loops),
|
|
113
|
+
inputBoundary: hasInputSpec,
|
|
114
|
+
inputPeek: peek,
|
|
108
115
|
});
|
|
109
116
|
|
|
110
117
|
if (requiresSto) {
|
|
@@ -119,9 +126,12 @@ function checkAlternative(program, quad, alternative, options, context) {
|
|
|
119
126
|
return { ok: true };
|
|
120
127
|
}
|
|
121
128
|
|
|
122
|
-
if (
|
|
129
|
+
if (hasInputSpec) {
|
|
123
130
|
const leaf = leaves[0];
|
|
124
|
-
const
|
|
131
|
+
const exactConsumption = actual.inputPosition === input.length;
|
|
132
|
+
const expectedRemainder = `${peek ?? ''}${INVALID_UTF8_SENTINEL}`;
|
|
133
|
+
const boundaryPreserved = actual.inputRemainder === expectedRemainder;
|
|
134
|
+
const matches = exactConsumption && boundaryPreserved && matchLeaf(program, quad.query, leaf, actual, 0);
|
|
125
135
|
if (actual.undecided && !matches) return undecidedResult(actual, alternative);
|
|
126
136
|
return { ok: leaf.unexpected ? !matches : matches };
|
|
127
137
|
}
|
|
@@ -147,14 +157,14 @@ function checkAlternative(program, quad, alternative, options, context) {
|
|
|
147
157
|
// turn a successful negative match back into a failure.
|
|
148
158
|
if (leaf.unexpected && leaf.error != null) return { ok: true };
|
|
149
159
|
if (leaf.more) return { ok: true };
|
|
150
|
-
if (!leaf.unexpected && (leaf.false || leaf.error != null)) {
|
|
160
|
+
if (!leaf.unexpected && (leaf.false || leaf.loops || leaf.error != null)) {
|
|
151
161
|
if (actual.undecided) return undecidedResult(actual, alternative);
|
|
152
162
|
return { ok: position === leaves.length - 1 };
|
|
153
163
|
}
|
|
154
164
|
position++;
|
|
155
165
|
}
|
|
156
166
|
|
|
157
|
-
const ended = position >= actual.solutions.length && actual.error == null && !actual.undecided;
|
|
167
|
+
const ended = position >= actual.solutions.length && actual.error == null && !actual.undecided && !actual.loopObserved;
|
|
158
168
|
if (!ended && actual.undecided && position >= actual.solutions.length && actual.error == null) {
|
|
159
169
|
return undecidedResult(actual, alternative);
|
|
160
170
|
}
|
|
@@ -191,6 +201,7 @@ function describeLeaf(term) {
|
|
|
191
201
|
loops: false,
|
|
192
202
|
error: null,
|
|
193
203
|
input: null,
|
|
204
|
+
peek: null,
|
|
194
205
|
output: null,
|
|
195
206
|
unsupported: null,
|
|
196
207
|
malformed: null,
|
|
@@ -227,7 +238,9 @@ function describeLeaf(term) {
|
|
|
227
238
|
continue;
|
|
228
239
|
}
|
|
229
240
|
if (item.type === COMPOUND && item.name === 'peeks' && item.arity === 1) {
|
|
230
|
-
|
|
241
|
+
const text = characterText(item.args[0]);
|
|
242
|
+
if (text == null || Array.from(text).length !== 1 || leaf.peek != null) leaf.malformed ??= item;
|
|
243
|
+
else leaf.peek = text;
|
|
231
244
|
continue;
|
|
232
245
|
}
|
|
233
246
|
if (isErrorDescription(item)) leaf.error = item;
|
|
@@ -241,6 +254,9 @@ function describeLeaf(term) {
|
|
|
241
254
|
|
|
242
255
|
function executeQuery(program, query, input, maxSolutions, options) {
|
|
243
256
|
let pendingOutput = '';
|
|
257
|
+
const boundedInput = options.inputBoundary
|
|
258
|
+
? `${input}${options.inputPeek ?? ''}${INVALID_UTF8_SENTINEL}`
|
|
259
|
+
: input;
|
|
244
260
|
const solver = new Solver(program, {
|
|
245
261
|
...options,
|
|
246
262
|
registry: options.registry ?? getEyePrologRegistry(),
|
|
@@ -254,10 +270,18 @@ function executeQuery(program, query, input, maxSolutions, options) {
|
|
|
254
270
|
// allowance.
|
|
255
271
|
solutionLimit: Math.max(maxSolutions, options.solutionLimit ?? 10000000),
|
|
256
272
|
ioOptions: {
|
|
257
|
-
input,
|
|
273
|
+
input: boundedInput,
|
|
258
274
|
write: (text) => { pendingOutput += String(text); },
|
|
259
275
|
},
|
|
260
276
|
});
|
|
277
|
+
if (options.inputBoundary) {
|
|
278
|
+
// Quads need a boundary that is neither EOF nor a valid character. This
|
|
279
|
+
// mirrors Trealla's sentinel technique: inputs/1 says exactly what was
|
|
280
|
+
// consumed, while peeks/1 supplies one additional character that must
|
|
281
|
+
// remain unread. If the query asks for anything beyond that boundary it
|
|
282
|
+
// sees representation_error(character), not an artificial EOF.
|
|
283
|
+
solver.io.resolve('user_input').strictUtf8 = true;
|
|
284
|
+
}
|
|
261
285
|
// Undefined predicates are test failures rather than silent negative
|
|
262
286
|
// answers unless the source explicitly selected another unknown policy.
|
|
263
287
|
if (!(program.prologFlagDirectives ?? []).some(([flag]) => flag.type === ATOM && flag.name === 'unknown')) {
|
|
@@ -273,7 +297,15 @@ function executeQuery(program, query, input, maxSolutions, options) {
|
|
|
273
297
|
iterator = solver.solve([query], new Env(), 0);
|
|
274
298
|
while (solutions.length < maxSolutions) {
|
|
275
299
|
pendingOutput = '';
|
|
300
|
+
const cycleBefore = solver.recursionCycleDetected;
|
|
276
301
|
const result = iterator.next();
|
|
302
|
+
if (!cycleBefore && solver.recursionCycleDetected) {
|
|
303
|
+
// The recursion guard found an operational loop wall while advancing
|
|
304
|
+
// to this result. Any answer produced only after that guard pruned the
|
|
305
|
+
// looping branch is not reachable by ordinary Prolog search, so do not
|
|
306
|
+
// count it as a leaf answer (issue #58 comment 5381101420).
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
277
309
|
if (result.done) {
|
|
278
310
|
tailOutput += pendingOutput;
|
|
279
311
|
complete = true;
|
|
@@ -289,22 +321,30 @@ function executeQuery(program, query, input, maxSolutions, options) {
|
|
|
289
321
|
} finally {
|
|
290
322
|
if (!complete) iterator?.return?.();
|
|
291
323
|
}
|
|
292
|
-
const
|
|
324
|
+
const inputStream = solver.io.resolve('user_input');
|
|
325
|
+
const inputPosition = inputStream?.position ?? 0;
|
|
326
|
+
const inputRemainder = String(inputStream?.content ?? '').slice(inputPosition);
|
|
293
327
|
const bounded = solver.depthLimitExceeded || solver.inferenceLimitExceeded;
|
|
294
|
-
const
|
|
328
|
+
const loopObserved = solver.recursionCycleDetected;
|
|
329
|
+
// A structural recursion-cycle witness is stronger than a resource limit:
|
|
330
|
+
// it establishes an operational loop wall and can therefore refute a finite
|
|
331
|
+
// answer description. Pure depth/inference exhaustion remains undecided.
|
|
332
|
+
const undecided = !options.detectLoops && !loopObserved && bounded;
|
|
295
333
|
return {
|
|
296
334
|
solutions,
|
|
297
335
|
error,
|
|
298
336
|
tailOutput,
|
|
299
337
|
inputPosition,
|
|
338
|
+
inputRemainder,
|
|
300
339
|
complete,
|
|
340
|
+
loopObserved,
|
|
301
341
|
stoObserved: solver.occursCheckObserved,
|
|
302
|
-
nstoObserved: complete && !solver.occursCheckObserved && !
|
|
342
|
+
nstoObserved: complete && !solver.occursCheckObserved && !loopObserved &&
|
|
303
343
|
!bounded && !resourceInterrupted,
|
|
304
344
|
// A loops expectation explicitly asks for bounded nontermination evidence.
|
|
305
345
|
// Other descriptions treat the same exhausted search budget as undecided:
|
|
306
346
|
// a timeout cannot establish finite failure or an exact answer sequence.
|
|
307
|
-
loops: options.detectLoops && (
|
|
347
|
+
loops: options.detectLoops && (loopObserved || bounded),
|
|
308
348
|
undecided,
|
|
309
349
|
undecidedReason: solver.inferenceLimitExceeded ? 'inference limit reached'
|
|
310
350
|
: solver.depthLimitExceeded ? 'depth limit reached'
|
|
@@ -316,7 +356,7 @@ function executeQuery(program, query, input, maxSolutions, options) {
|
|
|
316
356
|
function matchLeaf(program, query, leaf, actual, position) {
|
|
317
357
|
if (leaf.loops) return actual.loops;
|
|
318
358
|
if (leaf.false) {
|
|
319
|
-
return position >= actual.solutions.length && actual.error == null && !actual.undecided &&
|
|
359
|
+
return position >= actual.solutions.length && actual.error == null && !actual.undecided && !actual.loopObserved &&
|
|
320
360
|
outputMatches(program, leaf.output, actual.tailOutput);
|
|
321
361
|
}
|
|
322
362
|
if (leaf.error != null) {
|
package/src/write.js
CHANGED
|
@@ -58,6 +58,51 @@ function isDottedGraphicAtom(name) {
|
|
|
58
58
|
[...name].every((ch) => dottedGraphicAtomCharacters.has(ch));
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
function compactBoundaryNeedsSpace(left, right) {
|
|
62
|
+
const leftChars = Array.from(left);
|
|
63
|
+
const rightChars = Array.from(right);
|
|
64
|
+
const a = leftChars[leftChars.length - 1] ?? '';
|
|
65
|
+
const b = rightChars[0] ?? '';
|
|
66
|
+
if (!a || !b) return false;
|
|
67
|
+
// Adjacent quoted atoms need layout: `''` is part of quoted-atom syntax,
|
|
68
|
+
// so concatenating two quoted operator/argument tokens would change the
|
|
69
|
+
// tokenization (WG17 #169).
|
|
70
|
+
if (a === "'" && b === "'") return true;
|
|
71
|
+
// Adjacent graphic characters form one maximal graphic token. Keep them
|
|
72
|
+
// apart when an operator boundary would otherwise disappear (`a+ -b`,
|
|
73
|
+
// `a/ *b`, etc.).
|
|
74
|
+
if (graphicAtomCharacters.has(a) && graphicAtomCharacters.has(b)) return true;
|
|
75
|
+
// Alphanumeric operator names similarly need a lexical boundary from a
|
|
76
|
+
// neighbouring identifier/number token. Most predefined word operators
|
|
77
|
+
// are handled explicitly below; this also protects quoted/custom cases that
|
|
78
|
+
// render without punctuation.
|
|
79
|
+
if (/^[A-Za-z0-9_]$/.test(a) && /^[A-Za-z0-9_]$/.test(b)) return true;
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function isWordOperatorToken(token) {
|
|
84
|
+
return /^[a-z][A-Za-z0-9_]*$/.test(token);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function compactPrefixOperator(token, argument) {
|
|
88
|
+
if (isWordOperatorToken(token)) return `${token} ${argument}`;
|
|
89
|
+
return `${token}${compactBoundaryNeedsSpace(token, argument) ? ' ' : ''}${argument}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function compactPostfixOperator(argument, token) {
|
|
93
|
+
if (isWordOperatorToken(token)) return `${argument} ${token}`;
|
|
94
|
+
return `${argument}${compactBoundaryNeedsSpace(argument, token) ? ' ' : ''}${token}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function compactInfixOperator(left, token, right) {
|
|
98
|
+
// Solo punctuation operators already delimit themselves.
|
|
99
|
+
if (token === ',' || token === ';') return `${left}${token}${right}`;
|
|
100
|
+
if (isWordOperatorToken(token)) return `${left} ${token} ${right}`;
|
|
101
|
+
const before = compactBoundaryNeedsSpace(left, token) ? ' ' : '';
|
|
102
|
+
const after = compactBoundaryNeedsSpace(token, right) ? ' ' : '';
|
|
103
|
+
return `${left}${before}${token}${after}${right}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
61
106
|
function legacyVariableToIso(name) {
|
|
62
107
|
if (name === '?') return '_';
|
|
63
108
|
const tail = name.slice(1);
|
|
@@ -313,9 +358,11 @@ function format(term, env, options, table, maxPriority = 1200, context = 'term')
|
|
|
313
358
|
// numeric-leading argument so writeq/write_term remain read-back safe
|
|
314
359
|
// (WG17 #135, #183, #215, #216, and #248).
|
|
315
360
|
if (resolved.name === '-' && startsWithUnsignedNumber(resolved.args[0], env, table, options)) {
|
|
316
|
-
|
|
361
|
+
const argument = format(resolved.args[0], env, options, table, 1200);
|
|
362
|
+
text = options.minimalOperatorSpacing ? `${token}(${argument})` : `${token} (${argument})`;
|
|
317
363
|
} else {
|
|
318
|
-
|
|
364
|
+
const argument = format(resolved.args[0], env, options, table, argumentPriority);
|
|
365
|
+
text = options.minimalOperatorSpacing ? compactPrefixOperator(token, argument) : `${token} ${argument}`;
|
|
319
366
|
}
|
|
320
367
|
} else if (specifier === 'xf' || specifier === 'yf') {
|
|
321
368
|
let argumentPriority = specifier === 'xf' ? priority - 1 : priority;
|
|
@@ -324,7 +371,8 @@ function format(term, env, options, table, maxPriority = 1200, context = 'term')
|
|
|
324
371
|
['fx', 'fy', 'xfx', 'xfy', 'yfx'].includes(childDefinition.specifier)) {
|
|
325
372
|
argumentPriority = priority - 1;
|
|
326
373
|
}
|
|
327
|
-
|
|
374
|
+
const argument = format(resolved.args[0], env, options, table, argumentPriority);
|
|
375
|
+
text = options.minimalOperatorSpacing ? compactPostfixOperator(argument, token) : `${argument} ${token}`;
|
|
328
376
|
} else {
|
|
329
377
|
let leftPriority = specifier === 'yfx' ? priority : priority - 1;
|
|
330
378
|
const rightPriority = specifier === 'xfy' ? priority : priority - 1;
|
|
@@ -334,9 +382,13 @@ function format(term, env, options, table, maxPriority = 1200, context = 'term')
|
|
|
334
382
|
}
|
|
335
383
|
const left = format(resolved.args[0], env, options, table, leftPriority);
|
|
336
384
|
const right = format(resolved.args[1], env, options, table, rightPriority);
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
385
|
+
if (options.minimalOperatorSpacing) {
|
|
386
|
+
text = resolved.name === ',' ? `${left},${right}` : compactInfixOperator(left, token, right);
|
|
387
|
+
} else if (resolved.name === ',') {
|
|
388
|
+
text = `${left}, ${right}`;
|
|
389
|
+
} else {
|
|
390
|
+
text = compactInfixOperators.has(resolved.name) ? `${left}${token}${right}` : `${left} ${token} ${right}`;
|
|
391
|
+
}
|
|
340
392
|
}
|
|
341
393
|
return priority > maxPriority ? `(${text})` : text;
|
|
342
394
|
}
|
|
@@ -358,6 +410,7 @@ export function formatTermForWrite(term, env = new Env(), options = {}) {
|
|
|
358
410
|
? printableGeneratedVariableNames(term, env, explicitVariableNames, options.variableNameState)
|
|
359
411
|
: printableReadVariableNames(term, env, explicitVariableNames),
|
|
360
412
|
compact: options.compact === true,
|
|
413
|
+
minimalOperatorSpacing: options.minimalOperatorSpacing === true,
|
|
361
414
|
operatorAtomsAsArgs: options.operatorAtomsAsArgs === true,
|
|
362
415
|
dottedGraphicAtoms: options.dottedGraphicAtoms === true,
|
|
363
416
|
};
|
|
@@ -21,8 +21,8 @@ upstream assertion.
|
|
|
21
21
|
The matrix runs in strict ISO stream-reader mode as part of `npm test`. The
|
|
22
22
|
3 upstream `waits` cases are checked through EyeProlog's interactive input
|
|
23
23
|
hook. All 366 executable cases are independently checked against the
|
|
24
|
-
upstream Codex expectation.
|
|
25
|
-
outcomes for stronger regression checking;
|
|
24
|
+
upstream Codex expectation. 366 cases additionally retain exact reviewed
|
|
25
|
+
outcomes for stronger regression checking; 0 cases currently rely on the upstream assertion alone.
|
|
26
26
|
|
|
27
27
|
## Traceable evidence
|
|
28
28
|
|