eyeprolog 1.2.13 → 1.2.15
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 +6 -4
- package/package.json +1 -1
- package/src/iso.js +14 -7
- package/src/parser.js +40 -9
- package/src/solver.js +1 -1
- package/src/term.js +28 -19
- package/test/conformance/cases/iso/grouped_solutions_and_clauses.pl +6 -2
- package/test/conformance/expected/iso/grouped_solutions_and_clauses.pl +1 -1
- package/test/run-regression.mjs +63 -3
- package/the-art-of-eyeprolog.md +23 -6
package/README.md
CHANGED
|
@@ -79,10 +79,12 @@ member_test ?- member(X, [prolog, logic]).
|
|
|
79
79
|
; X = logic.
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
A label
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
A label is simply the first argument of the ordinary `(?-)/2` term, so it may
|
|
83
|
+
use any normal Prolog term syntax; EyeProlog only requires it to be ground when
|
|
84
|
+
the quad is checked. A non-ground label is a quad failure, not a source syntax
|
|
85
|
+
error, and later quads still run. When a query has multiple indented answer
|
|
86
|
+
descriptions, each description is checked and counted independently, so one
|
|
87
|
+
failed expectation does not prevent the later ones from running.
|
|
86
88
|
|
|
87
89
|
## Strict ISO/IEC 13211-1 core
|
|
88
90
|
|
package/package.json
CHANGED
package/src/iso.js
CHANGED
|
@@ -284,8 +284,8 @@ function termIsAcyclic(term, env) {
|
|
|
284
284
|
return true;
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
-
function resolvedOrder(left, right, env) {
|
|
288
|
-
return compareTerms(copyResolved(left, env), copyResolved(right, env));
|
|
287
|
+
function resolvedOrder(left, right, env, variableRanks = null) {
|
|
288
|
+
return compareTerms(copyResolved(left, env), copyResolved(right, env), variableRanks);
|
|
289
289
|
}
|
|
290
290
|
function* compareBuiltin({ goal, env }) {
|
|
291
291
|
const order = deref(goal.args[0], env);
|
|
@@ -331,10 +331,11 @@ function validateListOutput(term, env) {
|
|
|
331
331
|
function* sortBuiltin({ goal, env }) {
|
|
332
332
|
const items = requireProperList(goal.args[0], env);
|
|
333
333
|
validateListOutput(goal.args[1], env);
|
|
334
|
-
const
|
|
334
|
+
const variableRanks = new Map();
|
|
335
|
+
const sorted = [...items].sort((a, b) => resolvedOrder(a, b, env, variableRanks));
|
|
335
336
|
const unique = [];
|
|
336
337
|
for (const item of sorted) {
|
|
337
|
-
if (unique.length === 0 || resolvedOrder(unique[unique.length - 1], item, env) !== 0) unique.push(item);
|
|
338
|
+
if (unique.length === 0 || resolvedOrder(unique[unique.length - 1], item, env, variableRanks) !== 0) unique.push(item);
|
|
338
339
|
}
|
|
339
340
|
const next = env.clone();
|
|
340
341
|
if (unify(goal.args[1], listFromItems(unique), next)) yield next;
|
|
@@ -350,7 +351,11 @@ function* keysortBuiltin({ goal, env }) {
|
|
|
350
351
|
}
|
|
351
352
|
}
|
|
352
353
|
// Modern ECMAScript specifies a stable Array#sort, as required by keysort/2.
|
|
353
|
-
|
|
354
|
+
// Keep one implementation-dependent variable order for this whole sorting
|
|
355
|
+
// operation, as required by ISO 7.2.1.
|
|
356
|
+
const variableRanks = new Map();
|
|
357
|
+
const sorted = [...items].sort((a, b) =>
|
|
358
|
+
resolvedOrder(deref(a, env).args[0], deref(b, env).args[0], env, variableRanks));
|
|
354
359
|
const next = env.clone();
|
|
355
360
|
if (unify(goal.args[1], listFromItems(sorted), next)) yield next;
|
|
356
361
|
}
|
|
@@ -1701,8 +1706,10 @@ function sameWitness(left, right) {
|
|
|
1701
1706
|
}
|
|
1702
1707
|
|
|
1703
1708
|
function sortedUnique(items) {
|
|
1704
|
-
const
|
|
1705
|
-
|
|
1709
|
+
const variableRanks = new Map();
|
|
1710
|
+
const compare = (left, right) => compareTerms(left, right, variableRanks);
|
|
1711
|
+
const sorted = [...items].sort(compare);
|
|
1712
|
+
return sorted.filter((item, index) => index === 0 || compare(sorted[index - 1], item) !== 0);
|
|
1706
1713
|
}
|
|
1707
1714
|
|
|
1708
1715
|
function allSolutionsBuiltin(asSet) {
|
package/src/parser.js
CHANGED
|
@@ -794,13 +794,47 @@ class Parser {
|
|
|
794
794
|
accept(clause);
|
|
795
795
|
continue;
|
|
796
796
|
}
|
|
797
|
+
// Program clauses historically parse comma separately from the head, so
|
|
798
|
+
// keep that grammar unchanged. A quad id, however, is simply the first
|
|
799
|
+
// argument of the ordinary ?-/2 term. If the initial head parse stops at
|
|
800
|
+
// a comma, tentatively reparse that left operand with the normal term
|
|
801
|
+
// grammar (where comma is the predefined priority-1000 xfy operator).
|
|
802
|
+
// Only when the resulting term is actually followed by ?- do we keep the
|
|
803
|
+
// tentative parse; otherwise restore the parser and retain the existing
|
|
804
|
+
// clause/DCG handling below.
|
|
805
|
+
const headState = {
|
|
806
|
+
pos: this.pos,
|
|
807
|
+
line: this.line,
|
|
808
|
+
anonymous: this.anonymous,
|
|
809
|
+
variables: new Map(this.variables),
|
|
810
|
+
previousToken: this.previousToken,
|
|
811
|
+
token: this.token,
|
|
812
|
+
};
|
|
813
|
+
const restoreHeadState = () => {
|
|
814
|
+
this.pos = headState.pos;
|
|
815
|
+
this.line = headState.line;
|
|
816
|
+
this.anonymous = headState.anonymous;
|
|
817
|
+
this.variables = new Map(headState.variables);
|
|
818
|
+
this.previousToken = headState.previousToken;
|
|
819
|
+
this.token = headState.token;
|
|
820
|
+
};
|
|
821
|
+
|
|
797
822
|
let head = this.parseTerm(3);
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
823
|
+
if (this.token.type === TOK.COMMA && !this.strictIso) {
|
|
824
|
+
restoreHeadState();
|
|
825
|
+
const quadId = this.parseTerm(3, true);
|
|
826
|
+
if (this.operatorTokenName() === '?-') {
|
|
827
|
+
this.advance();
|
|
828
|
+
this.parseQuad(quadId, line, accept);
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
restoreHeadState();
|
|
832
|
+
head = this.parseTerm(3);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// Outside quad syntax, preserve the existing program-level comma rule,
|
|
836
|
+
// including the TS 13211-3 semicontext boundary. This is deliberately
|
|
837
|
+
// not a grammar for quad ids.
|
|
804
838
|
if (this.token.type === TOK.COMMA) {
|
|
805
839
|
const items = [head];
|
|
806
840
|
let extraCommaLine = null;
|
|
@@ -809,9 +843,6 @@ class Parser {
|
|
|
809
843
|
this.advance();
|
|
810
844
|
items.push(this.parseTerm(3));
|
|
811
845
|
}
|
|
812
|
-
// Historically the program grammar admitted exactly one comma in a
|
|
813
|
-
// DCG semicontext. Keep that boundary: the broader comma sequence is
|
|
814
|
-
// specifically the quad-label extension, not a new DCG syntax.
|
|
815
846
|
if (extraCommaLine != null && this.operatorTokenName() !== '?-') {
|
|
816
847
|
throw new Error(`parse line ${extraCommaLine}: expected ., got ,`);
|
|
817
848
|
}
|
package/src/solver.js
CHANGED
|
@@ -1250,7 +1250,7 @@ function copyResolvedWithKey(term, env, variables) {
|
|
|
1250
1250
|
id = variables.size;
|
|
1251
1251
|
variables.set(value.name, id);
|
|
1252
1252
|
}
|
|
1253
|
-
return { term: termModuleCache.variable(value.name
|
|
1253
|
+
return { term: termModuleCache.variable(value.name), key: `var:${id}` };
|
|
1254
1254
|
}
|
|
1255
1255
|
if (!value.args?.length) {
|
|
1256
1256
|
return {
|
package/src/term.js
CHANGED
|
@@ -6,10 +6,6 @@ export const STRING = 'string';
|
|
|
6
6
|
export const NUMBER = 'number';
|
|
7
7
|
export const COMPOUND = 'compound';
|
|
8
8
|
const EMPTY_ARGS = Object.freeze([]);
|
|
9
|
-
// Variable term order is carried by the variable objects themselves. Do not
|
|
10
|
-
// retain a process-global name registry: runtime clause freshening can create
|
|
11
|
-
// an unbounded sequence of names in an otherwise constant-space computation.
|
|
12
|
-
let variableOrder = 0;
|
|
13
9
|
|
|
14
10
|
export class Term {
|
|
15
11
|
constructor(type, name, args = []) {
|
|
@@ -22,11 +18,7 @@ export class Term {
|
|
|
22
18
|
}
|
|
23
19
|
}
|
|
24
20
|
|
|
25
|
-
export const variable = (name
|
|
26
|
-
const term = new Term(VAR, name, EMPTY_ARGS);
|
|
27
|
-
term.order = order ?? ++variableOrder;
|
|
28
|
-
return term;
|
|
29
|
-
};
|
|
21
|
+
export const variable = (name) => new Term(VAR, name, EMPTY_ARGS);
|
|
30
22
|
export const atom = (name) => new Term(ATOM, name, EMPTY_ARGS);
|
|
31
23
|
export const stringTerm = (value) => new Term(STRING, value, EMPTY_ARGS);
|
|
32
24
|
export const numberTerm = (value) => new Term(NUMBER, value, EMPTY_ARGS);
|
|
@@ -271,7 +263,7 @@ export function unify(left, right, env, options = {}) {
|
|
|
271
263
|
}
|
|
272
264
|
|
|
273
265
|
export function cloneTerm(term) {
|
|
274
|
-
if (term.type === VAR) return variable(term.name
|
|
266
|
+
if (term.type === VAR) return variable(term.name);
|
|
275
267
|
const cloned = term.type === COMPOUND && term.arity === 0
|
|
276
268
|
? atom(term.name)
|
|
277
269
|
: new Term(term.type, term.name, term.args.map(cloneTerm));
|
|
@@ -297,7 +289,7 @@ export function freshTerm(term, suffix, variables = new Map()) {
|
|
|
297
289
|
|
|
298
290
|
export function copyResolved(term, env) {
|
|
299
291
|
const resolved = deref(term, env);
|
|
300
|
-
if (resolved.type === VAR) return variable(resolved.name
|
|
292
|
+
if (resolved.type === VAR) return variable(resolved.name);
|
|
301
293
|
const copied = resolved.type === COMPOUND && resolved.arity === 0
|
|
302
294
|
? atom(resolved.name)
|
|
303
295
|
: new Term(resolved.type, resolved.name, resolved.args.map((arg) => copyResolved(arg, env)));
|
|
@@ -512,7 +504,27 @@ export function variantTerms(left, leftEnv, right, rightEnv, pairs = new Map(),
|
|
|
512
504
|
return true;
|
|
513
505
|
}
|
|
514
506
|
|
|
515
|
-
export function compareTerms(left, right) {
|
|
507
|
+
export function compareTerms(left, right, variableRanks = null) {
|
|
508
|
+
// ISO 7.2.1 deliberately leaves the order of distinct variables
|
|
509
|
+
// implementation dependent. Do not attach a permanent ordinal to a
|
|
510
|
+
// logical variable: besides retaining implementation history, that would
|
|
511
|
+
// make the chosen order observable outside the operation that needs it.
|
|
512
|
+
// A caller that is constructing one sorted list can pass a shared Map so
|
|
513
|
+
// every comparison in that operation uses one consistent variable order.
|
|
514
|
+
const ranks = variableRanks ?? new Map();
|
|
515
|
+
return compareTermsWithRanks(left, right, ranks);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function variableRank(name, ranks) {
|
|
519
|
+
let rank = ranks.get(name);
|
|
520
|
+
if (rank == null) {
|
|
521
|
+
rank = ranks.size;
|
|
522
|
+
ranks.set(name, rank);
|
|
523
|
+
}
|
|
524
|
+
return rank;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function compareTermsWithRanks(left, right, variableRanks) {
|
|
516
528
|
const rank = (term) => ({ [VAR]: 0, [NUMBER]: 1, [ATOM]: 2, [STRING]: 3, [COMPOUND]: 4 })[term.type];
|
|
517
529
|
left = deref(left, new Env());
|
|
518
530
|
right = deref(right, new Env());
|
|
@@ -527,18 +539,15 @@ export function compareTerms(left, right) {
|
|
|
527
539
|
}
|
|
528
540
|
if (left.type === VAR) {
|
|
529
541
|
if (left.name === right.name) return 0;
|
|
530
|
-
const leftOrder = left.
|
|
531
|
-
const rightOrder = right.
|
|
532
|
-
|
|
533
|
-
// Explicitly copied order values are permitted internally; keep the
|
|
534
|
-
// implementation-defined variable order total even in that case.
|
|
535
|
-
return left.name < right.name ? -1 : 1;
|
|
542
|
+
const leftOrder = variableRank(left.name, variableRanks);
|
|
543
|
+
const rightOrder = variableRank(right.name, variableRanks);
|
|
544
|
+
return leftOrder < rightOrder ? -1 : 1;
|
|
536
545
|
}
|
|
537
546
|
if (left.type === ATOM || left.type === STRING) return left.name < right.name ? -1 : left.name > right.name ? 1 : 0;
|
|
538
547
|
if (left.arity !== right.arity) return left.arity < right.arity ? -1 : 1;
|
|
539
548
|
if (left.name !== right.name) return left.name < right.name ? -1 : 1;
|
|
540
549
|
for (let i = 0; i < left.arity; i++) {
|
|
541
|
-
const cmp =
|
|
550
|
+
const cmp = compareTermsWithRanks(left.args[i], right.args[i], variableRanks);
|
|
542
551
|
if (cmp) return cmp;
|
|
543
552
|
}
|
|
544
553
|
return 0;
|
|
@@ -45,7 +45,11 @@ shared_clause(Body) :-
|
|
|
45
45
|
|
|
46
46
|
%% goal: shared_set_variables(X0)
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
% ISO 7.2.1 leaves the order of distinct variables implementation dependent.
|
|
49
|
+
% Check that setof/3 constructs one consistent sorted list without baking a
|
|
50
|
+
% particular variable order into the conformance golden.
|
|
51
|
+
shared_set_variables(ok) :-
|
|
49
52
|
setof(Value, (=(Value, Left); =(Value, Right)), Set),
|
|
50
53
|
=(Left, a),
|
|
51
|
-
=(Right, b)
|
|
54
|
+
=(Right, b),
|
|
55
|
+
(=(Set, [a,b]); =(Set, [b,a])).
|
package/test/run-regression.mjs
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
parseProgramText,
|
|
40
40
|
} from '../src/index.js';
|
|
41
41
|
import { parseGoalText } from '../src/parser.js';
|
|
42
|
+
import { compareTerms } from '../src/term.js';
|
|
42
43
|
import { selectClauseCandidates } from '../src/program.js';
|
|
43
44
|
import { TestReporter, isMainModule } from './test-style.mjs';
|
|
44
45
|
import { buildConformanceReport, formatConformanceReport } from './run-conformance-report.mjs';
|
|
@@ -278,8 +279,11 @@ why(
|
|
|
278
279
|
},
|
|
279
280
|
},
|
|
280
281
|
{
|
|
281
|
-
name: 'quad
|
|
282
|
+
name: 'quad ids use ordinary term syntax and each answer description is independent',
|
|
282
283
|
run: () => {
|
|
284
|
+
// Issue #21: the first argument of ?-/2 is an ordinary Prolog term.
|
|
285
|
+
// The commas here are the normal priority-1000 comma operator, not a
|
|
286
|
+
// special metadata grammar owned by the quad parser.
|
|
283
287
|
const source = `9, "✳54·43", passes
|
|
284
288
|
` +
|
|
285
289
|
`?- X is 1+1.
|
|
@@ -297,14 +301,50 @@ why(
|
|
|
297
301
|
const program = Program.parseSources([{ text: source, filename: 'issue-21.pl' }]);
|
|
298
302
|
assertEqual(program.quads.length, 1, 'query group count');
|
|
299
303
|
assertEqual(program.quads[0].answers.length, 4, 'answer-description count');
|
|
300
|
-
assertEqual(program.quads[0].id.name, ',', 'outer
|
|
301
|
-
assertEqual(program.quads[0].id.args[1].name, ',', 'right-associated
|
|
304
|
+
assertEqual(program.quads[0].id.name, ',', 'ordinary outer comma operator');
|
|
305
|
+
assertEqual(program.quads[0].id.args[1].name, ',', 'ordinary right-associated comma operator');
|
|
302
306
|
const result = publicApi.runQuads(program);
|
|
303
307
|
assertEqual(result.total, 4, 'answer-description total');
|
|
304
308
|
assertEqual(result.passed, 4, 'answer-description passed');
|
|
305
309
|
assertEqual(result.failed, 0, 'answer-description failed');
|
|
306
310
|
assertEqual(result.stdout, 'quads: 4 run, 4 passed, 0 failed.\n', 'issue #21 report');
|
|
307
311
|
|
|
312
|
+
// No convention is imposed on the id term. Functional/list/curly and
|
|
313
|
+
// non-comma operator forms all go through the same ordinary term parser.
|
|
314
|
+
const ordinaryIds = Program.parse(
|
|
315
|
+
`meta(9, passes) ?- true.
|
|
316
|
+
true.
|
|
317
|
+
` +
|
|
318
|
+
`[9, passes] ?- true.
|
|
319
|
+
true.
|
|
320
|
+
` +
|
|
321
|
+
`{passes} ?- true.
|
|
322
|
+
true.
|
|
323
|
+
` +
|
|
324
|
+
`(alpha ; beta) ?- true.
|
|
325
|
+
true.
|
|
326
|
+
`,
|
|
327
|
+
);
|
|
328
|
+
assertEqual(ordinaryIds.quads.length, 4, 'ordinary id term count');
|
|
329
|
+
assertEqual(publicApi.runQuads(ordinaryIds).stdout, 'quads: 4 run, 4 passed, 0 failed.\n',
|
|
330
|
+
'ordinary id term report');
|
|
331
|
+
|
|
332
|
+
// Groundness is a quad semantic check, not source syntax. A bad id is
|
|
333
|
+
// reported as a test failure and processing continues to the next quad.
|
|
334
|
+
const nonGround = publicApi.runQuads(
|
|
335
|
+
`Id ?- true.
|
|
336
|
+
true.
|
|
337
|
+
` +
|
|
338
|
+
`ok ?- true.
|
|
339
|
+
true.
|
|
340
|
+
`,
|
|
341
|
+
);
|
|
342
|
+
assertEqual(nonGround.total, 2, 'non-ground id does not abort parsing');
|
|
343
|
+
assertEqual(nonGround.passed, 1, 'following quad still passes');
|
|
344
|
+
assertEqual(nonGround.failed, 1, 'non-ground id is a quad failure');
|
|
345
|
+
assertIncludes(nonGround.stdout, 'quads: BAD_ID Id, <input>:1', 'non-ground id diagnostic');
|
|
346
|
+
assertIncludes(nonGround.stdout, 'quads: 2 run, 1 passed, 1 failed.', 'non-ground continuation summary');
|
|
347
|
+
|
|
308
348
|
const continuing = publicApi.runQuads(
|
|
309
349
|
`case ?- X is 1+1.
|
|
310
350
|
X = 3.
|
|
@@ -2443,6 +2483,26 @@ function whiteBoxCases() {
|
|
|
2443
2483
|
assertEqual(termToString(variable('X'), env, true), 'socrates', 'binding');
|
|
2444
2484
|
},
|
|
2445
2485
|
},
|
|
2486
|
+
{
|
|
2487
|
+
name: 'variable term order is scoped to one comparison or sorted-list operation',
|
|
2488
|
+
run: () => {
|
|
2489
|
+
const left = variable('Left');
|
|
2490
|
+
const right = variable('Right');
|
|
2491
|
+
assertEqual(String(left.order), 'undefined', 'variables carry no persistent order ordinal');
|
|
2492
|
+
assertEqual(String(right.order), 'undefined', 'second variable carries no persistent order ordinal');
|
|
2493
|
+
|
|
2494
|
+
// Separate comparisons are permitted to choose their own
|
|
2495
|
+
// implementation-dependent order under ISO 7.2.1.
|
|
2496
|
+
assertEqual(String(compareTerms(left, right)), '-1', 'first local comparison');
|
|
2497
|
+
assertEqual(String(compareTerms(right, left)), '-1', 'second local comparison is independent');
|
|
2498
|
+
|
|
2499
|
+
// A sorted-list operation instead supplies one shared ranking context,
|
|
2500
|
+
// so all comparisons made while constructing that list are consistent.
|
|
2501
|
+
const ranks = new Map();
|
|
2502
|
+
assertEqual(String(compareTerms(left, right, ranks)), '-1', 'shared order first direction');
|
|
2503
|
+
assertEqual(String(compareTerms(right, left, ranks)), '1', 'shared order reverse direction');
|
|
2504
|
+
},
|
|
2505
|
+
},
|
|
2446
2506
|
{
|
|
2447
2507
|
name: 'unification rejects direct and indirect cyclic bindings',
|
|
2448
2508
|
run: () => {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -1811,9 +1811,19 @@ child searches that inherit the solver limit do not stop after a fixed number of
|
|
|
1811
1811
|
solutions. This matters for re-executable goals such as `repeat/0` and for
|
|
1812
1812
|
library relations such as `call_nth/2`; an implementation safety threshold must
|
|
1813
1813
|
not turn a still re-executable search into logical failure. Embedders that need
|
|
1814
|
-
a finite answer budget should pass `solutionLimit` explicitly.
|
|
1815
|
-
|
|
1816
|
-
|
|
1814
|
+
a finite answer budget should pass `solutionLimit` explicitly.
|
|
1815
|
+
|
|
1816
|
+
Variable term order is deliberately scoped rather than stored as a permanent
|
|
1817
|
+
property of a variable. ISO 13211-1 section 7.2.1 leaves the order of two
|
|
1818
|
+
distinct variables implementation dependent and requires constancy only while
|
|
1819
|
+
a sorted list is being created. EyeProlog therefore chooses a local variable
|
|
1820
|
+
ranking for an ordinary term comparison, while `sort/2`, `keysort/2`, and the
|
|
1821
|
+
sorting step of `setof/3` share one ranking for the duration of that single
|
|
1822
|
+
sorted-list operation. No process-global variable registry or creation ordinal
|
|
1823
|
+
is retained or exposed through later comparisons.
|
|
1824
|
+
|
|
1825
|
+
Host capacity failures that V8 reports as `Map maximum size exceeded` or `Set`
|
|
1826
|
+
`maximum size exceeded` are normalized at the solver boundary to the ISO error
|
|
1817
1827
|
`resource_error(finite_memory)` instead of leaking a JavaScript `RangeError`.
|
|
1818
1828
|
|
|
1819
1829
|
### Implementation boundary
|
|
@@ -5160,8 +5170,11 @@ from the parsed `?-/1` or `?-/2` term rather than from one privileged surface
|
|
|
5160
5170
|
spelling. Thus `Label ?- Query.`, `?-(Label, Query).`, mixed forms such as
|
|
5161
5171
|
`?-((Label), Query).`, quoted-functor notation, and a parenthesized whole
|
|
5162
5172
|
`(?-(Label, Query)).` denote the same quad when followed by indented answer
|
|
5163
|
-
descriptions.
|
|
5164
|
-
|
|
5173
|
+
descriptions. `Label` itself is parsed with the ordinary Prolog term grammar:
|
|
5174
|
+
there is no quad-specific comma or metadata syntax. The runner requires the
|
|
5175
|
+
resulting first argument to be ground; if it is not, that quad is reported as
|
|
5176
|
+
`BAD_ID` and later quads are still processed. In `--iso-strict` mode this quad
|
|
5177
|
+
interpretation is disabled, and `?-/2` remains ordinary Prolog term syntax.
|
|
5165
5178
|
|
|
5166
5179
|
Run [`iso-dynamic-database.pl`](https://github.com/eyereasoner/eyeprolog/blob/main/examples/iso-dynamic-database.pl)
|
|
5167
5180
|
for an explicitly stateful queue and
|
|
@@ -6391,7 +6404,11 @@ colors ?- color(X).
|
|
|
6391
6404
|
```
|
|
6392
6405
|
|
|
6393
6406
|
Run all quads in a file with `eyeprolog --quads file.pl` or `eyeprolog -q
|
|
6394
|
-
file.pl`. A label such as `colors` is optional.
|
|
6407
|
+
file.pl`. A label such as `colors` is optional. A label is not a separate
|
|
6408
|
+
mini-language: it is the ordinary first argument of `(?-)/2`, and therefore may
|
|
6409
|
+
be any Prolog term admitted there by the normal term grammar. Quad execution
|
|
6410
|
+
requires that argument to be ground; a non-ground label is reported as a quad
|
|
6411
|
+
failure rather than aborting source parsing. Loading the file normally only
|
|
6395
6412
|
records its quads; it does not execute them or add their queries and answers as
|
|
6396
6413
|
program clauses. A quad run prints a summary and exits with status `1` when any
|
|
6397
6414
|
description fails. Quad mode imports `library(prologue)` as a compatibility
|