eyeprolog 1.3.3 → 1.3.7
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 +78 -4
- package/examples/book/README.md +2 -1
- package/examples/book/chapter-07/02-move.pl +4 -0
- package/examples/book/chapter-08/01-findall.pl +1 -0
- package/examples/book/chapter-39/04-cost.pl +1 -0
- package/examples/chart-parser.pl +1 -0
- package/examples/dog.pl +1 -0
- package/examples/donald-gerald-robert.pl +1 -0
- package/examples/equality-saturation.pl +1 -0
- package/examples/job-shop-scheduling.pl +1 -0
- package/examples/knapsack-optimization.pl +1 -0
- package/examples/knuth-bendix-completion.pl +1 -0
- package/examples/matrix-chain-order.pl +1 -0
- package/examples/missionaries-cannibals.pl +1 -0
- package/examples/prime-range.pl +1 -0
- package/examples/register-allocation.pl +1 -0
- package/examples/send-more-money.pl +1 -0
- package/examples/stable-marriage.pl +1 -0
- package/examples/totient-summatory.pl +1 -0
- package/examples/weighted-interval-scheduling.pl +1 -0
- package/index.d.ts +7 -0
- package/package.json +1 -1
- package/src/datalog.js +354 -0
- package/src/iso.js +148 -14
- package/src/lib/iso_ext.pl +4 -0
- package/src/lib/lists.pl +0 -3
- package/src/program.js +258 -20
- package/src/solver.js +890 -46
- package/src/standard-library.js +12 -7
- package/src/term.js +1 -1
- package/src/wfs.js +338 -0
- package/test/conformance/ISO-COMPLIANCE.md +2 -1
- package/test/conformance/README.md +4 -1
- package/test/run-conformance.mjs +1 -0
- package/test/run-regression.mjs +230 -3
- package/the-art-of-eyeprolog.md +95 -21
- package/why-eyeprolog.md +17 -0
- /package/examples/book/chapter-07/{02-all_tests_pass.pl → 03-all_tests_pass.pl} +0 -0
package/src/standard-library.js
CHANGED
|
@@ -109,10 +109,10 @@ export const eyePrologInteropAutoload = Object.freeze({
|
|
|
109
109
|
'foldl/6': 'lists',
|
|
110
110
|
'sum_list/2': 'lists',
|
|
111
111
|
'list_to_set/2': 'lists',
|
|
112
|
-
// call_nth/2 is available in both Trealla and Scryer
|
|
113
|
-
// library(iso_ext)
|
|
114
|
-
//
|
|
115
|
-
'call_nth/2': '
|
|
112
|
+
// call_nth/2 is available in both Trealla and Scryer. EyeProlog exposes
|
|
113
|
+
// it from library(iso_ext), matching the explicit Scryer import while still
|
|
114
|
+
// allowing Trealla-style unqualified source to use the same autoload entry.
|
|
115
|
+
'call_nth/2': 'iso_ext',
|
|
116
116
|
// Trealla and Scryer expose between/3 without an EyeProlog-style
|
|
117
117
|
// library(prologue) dependency. EyeProlog keeps its implementation in the
|
|
118
118
|
// Prologue module but autoloads it so portable source need not name that
|
|
@@ -125,9 +125,13 @@ export const eyePrologInteropLibraryIndicators = Object.freeze(
|
|
|
125
125
|
);
|
|
126
126
|
|
|
127
127
|
// Libraries whose *name* is part of the current interop profile. A program
|
|
128
|
-
// may freely use_module
|
|
129
|
-
// outside eyePrologInteropLibraryIndicators are still diagnosed when used.
|
|
130
|
-
export const eyePrologInteropLibraryModules = Object.freeze(['lists']);
|
|
128
|
+
// may freely use_module/1 with these common module names; predicates in those
|
|
129
|
+
// modules outside eyePrologInteropLibraryIndicators are still diagnosed when used.
|
|
130
|
+
export const eyePrologInteropLibraryModules = Object.freeze(['lists', 'iso_ext']);
|
|
131
|
+
|
|
132
|
+
function* tabledNegationBuiltin({ solver, goal, env }) {
|
|
133
|
+
yield* solver.solveTabledNegation(goal.args[0], env);
|
|
134
|
+
}
|
|
131
135
|
|
|
132
136
|
function runtimeStatistics(solver) {
|
|
133
137
|
return { ...solver.stats, ...memoryStatistics() };
|
|
@@ -167,6 +171,7 @@ export function createEyePrologRegistry() {
|
|
|
167
171
|
const registry = createDefaultRegistry();
|
|
168
172
|
registry.add('statistics', 0, statisticsBuiltin, { deterministic: true });
|
|
169
173
|
registry.add('statistics', 2, statisticsValueBuiltin);
|
|
174
|
+
registry.add('tnot', 1, tabledNegationBuiltin, { deterministic: true });
|
|
170
175
|
eyePrologLibraryBuiltins.register(registry);
|
|
171
176
|
clpzBuiltins.register(registry);
|
|
172
177
|
registry.eyePrologLibrary = true;
|
package/src/term.js
CHANGED
|
@@ -82,7 +82,7 @@ export const compactVariableList = (length, variablePrefix) => {
|
|
|
82
82
|
return size === 0n ? emptyList() : new CompactListTerm(size, variablePrefix);
|
|
83
83
|
};
|
|
84
84
|
export const isCompactList = (term) => term instanceof CompactListTerm;
|
|
85
|
-
export const compactListLength = (term) =>
|
|
85
|
+
export const compactListLength = (term) => typeof term?._compactLength === 'bigint' ? term._compactLength : null;
|
|
86
86
|
|
|
87
87
|
export class Env {
|
|
88
88
|
constructor(bindings) {
|
package/src/wfs.js
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
// Finite Datalog well-founded semantics (WFS) evaluator.
|
|
2
|
+
//
|
|
3
|
+
// This module deliberately targets the function-free, range-restricted subset
|
|
4
|
+
// marked by Program#markRecursivePredicates as group.wfsDatalog. It uses the
|
|
5
|
+
// alternating-fixpoint characterization of WFS: starting from an upper bound on
|
|
6
|
+
// possible atoms, repeatedly compute the least model of the Gelfond-Lifschitz
|
|
7
|
+
// reduct. The final lower relation contains unconditional truths; the final
|
|
8
|
+
// upper relation contains truths plus undefined (conditional) answers.
|
|
9
|
+
//
|
|
10
|
+
// Keeping this separate from ordinary \+/1 is intentional. Only explicit
|
|
11
|
+
// tnot/1 participates in WFS, matching the common tabled-negation convention.
|
|
12
|
+
|
|
13
|
+
import { ATOM, COMPOUND, VAR } from './term.js';
|
|
14
|
+
import { numberValueKey } from './number-value.js';
|
|
15
|
+
|
|
16
|
+
function predicateKey(module, name, arity) {
|
|
17
|
+
return `${module ?? 'user'}:${name}/${arity}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function scalarKey(term) {
|
|
21
|
+
if (term.type === 'number') return `number\u0000${numberValueKey(term.name)}`;
|
|
22
|
+
return `${term.type}\u0000${term.name}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function sameScalar(left, right) {
|
|
26
|
+
return scalarKey(left) === scalarKey(right);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function tupleKey(tuple) {
|
|
30
|
+
return tuple.map(scalarKey).join('\u0001');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class Relation {
|
|
34
|
+
constructor(arity) {
|
|
35
|
+
this.arity = arity;
|
|
36
|
+
this.rows = [];
|
|
37
|
+
this.keys = new Set();
|
|
38
|
+
this.indexes = Array.from({ length: arity }, () => new Map());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
add(tuple) {
|
|
42
|
+
const key = tupleKey(tuple);
|
|
43
|
+
if (this.keys.has(key)) return false;
|
|
44
|
+
const rowIndex = this.rows.length;
|
|
45
|
+
this.keys.add(key);
|
|
46
|
+
this.rows.push(tuple);
|
|
47
|
+
for (let i = 0; i < tuple.length; i++) {
|
|
48
|
+
const keyPart = scalarKey(tuple[i]);
|
|
49
|
+
let bucket = this.indexes[i].get(keyPart);
|
|
50
|
+
if (!bucket) {
|
|
51
|
+
bucket = [];
|
|
52
|
+
this.indexes[i].set(keyPart, bucket);
|
|
53
|
+
}
|
|
54
|
+
bucket.push(rowIndex);
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
has(tuple) {
|
|
60
|
+
return this.keys.has(tupleKey(tuple));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
candidateIndexes(args, bindings) {
|
|
64
|
+
let selected = null;
|
|
65
|
+
for (let i = 0; i < args.length; i++) {
|
|
66
|
+
const value = resolvePatternTerm(args[i], bindings);
|
|
67
|
+
if (value == null) continue;
|
|
68
|
+
const bucket = this.indexes[i].get(scalarKey(value)) ?? EMPTY_ARRAY;
|
|
69
|
+
if (selected == null || bucket.length < selected.length) selected = bucket;
|
|
70
|
+
if (selected.length === 0) break;
|
|
71
|
+
}
|
|
72
|
+
return selected ?? null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const EMPTY_ARRAY = Object.freeze([]);
|
|
77
|
+
|
|
78
|
+
function emptyRelations(groups) {
|
|
79
|
+
const relations = new Map();
|
|
80
|
+
for (const group of groups) {
|
|
81
|
+
relations.set(predicateKey(group.module, group.name, group.arity), new Relation(group.arity));
|
|
82
|
+
}
|
|
83
|
+
return relations;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function copyBaseRelations(base, groups) {
|
|
87
|
+
const relations = emptyRelations(groups);
|
|
88
|
+
for (const [key, relation] of base) {
|
|
89
|
+
const target = relations.get(key);
|
|
90
|
+
if (!target) continue;
|
|
91
|
+
for (const row of relation.rows) target.add(row);
|
|
92
|
+
}
|
|
93
|
+
return relations;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function directLiteral(goal, module) {
|
|
97
|
+
if (goal?.type !== COMPOUND && goal?.type !== ATOM) return null;
|
|
98
|
+
return {
|
|
99
|
+
key: predicateKey(goal.module ?? module, goal.name, goal.arity),
|
|
100
|
+
name: goal.name,
|
|
101
|
+
arity: goal.arity,
|
|
102
|
+
module: goal.module ?? module,
|
|
103
|
+
args: goal.args ?? EMPTY_ARRAY,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function negativeLiteral(goal, module) {
|
|
108
|
+
if (goal?.type !== COMPOUND || goal.name !== 'tnot' || goal.arity !== 1) return null;
|
|
109
|
+
return directLiteral(goal.args[0], module);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function dependencyCone(program, rootGroup) {
|
|
113
|
+
const groups = [];
|
|
114
|
+
const seen = new Set();
|
|
115
|
+
const stack = [rootGroup];
|
|
116
|
+
while (stack.length > 0) {
|
|
117
|
+
const group = stack.pop();
|
|
118
|
+
const key = predicateKey(group.module, group.name, group.arity);
|
|
119
|
+
if (seen.has(key)) continue;
|
|
120
|
+
seen.add(key);
|
|
121
|
+
groups.push(group);
|
|
122
|
+
|
|
123
|
+
for (const clause of group.clauses) {
|
|
124
|
+
for (const goal of clause.body) {
|
|
125
|
+
const neg = negativeLiteral(goal, group.module);
|
|
126
|
+
const literal = neg ?? directLiteral(goal, group.module);
|
|
127
|
+
if (!literal) continue;
|
|
128
|
+
const target = program.findGroup(literal.name, literal.arity, literal.module);
|
|
129
|
+
if (target) stack.push(target);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return groups;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function compileProgram(program, rootGroup) {
|
|
137
|
+
const groups = dependencyCone(program, rootGroup);
|
|
138
|
+
const base = emptyRelations(groups);
|
|
139
|
+
const rules = [];
|
|
140
|
+
|
|
141
|
+
for (const group of groups) {
|
|
142
|
+
const headKey = predicateKey(group.module, group.name, group.arity);
|
|
143
|
+
for (const clause of group.clauses) {
|
|
144
|
+
if (clause.body.length === 0) {
|
|
145
|
+
const tuple = clause.head.args ?? EMPTY_ARRAY;
|
|
146
|
+
// Program analysis already guarantees ground scalar facts in the WFS
|
|
147
|
+
// cone. Keep the guard so malformed dynamic input cannot corrupt a model.
|
|
148
|
+
if (tuple.every((term) => term.type !== VAR && (term.type === ATOM || term.type === 'number' || term.type === 'string'))) {
|
|
149
|
+
base.get(headKey)?.add(tuple);
|
|
150
|
+
}
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const positives = [];
|
|
155
|
+
const negatives = [];
|
|
156
|
+
for (const goal of clause.body) {
|
|
157
|
+
const neg = negativeLiteral(goal, group.module);
|
|
158
|
+
if (neg) negatives.push(neg);
|
|
159
|
+
else {
|
|
160
|
+
const pos = directLiteral(goal, group.module);
|
|
161
|
+
if (pos) positives.push(pos);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
rules.push({
|
|
165
|
+
head: {
|
|
166
|
+
key: headKey,
|
|
167
|
+
args: clause.head.args ?? EMPTY_ARRAY,
|
|
168
|
+
},
|
|
169
|
+
positives,
|
|
170
|
+
negatives,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return { groups, base, rules };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function resolvePatternTerm(term, bindings) {
|
|
179
|
+
if (term.type === VAR) return bindings.get(term.name) ?? null;
|
|
180
|
+
return term;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function matchTuple(args, tuple, bindings) {
|
|
184
|
+
let next = null;
|
|
185
|
+
for (let i = 0; i < args.length; i++) {
|
|
186
|
+
const pattern = args[i];
|
|
187
|
+
if (pattern.type === VAR) {
|
|
188
|
+
const current = (next ?? bindings).get(pattern.name);
|
|
189
|
+
if (current != null) {
|
|
190
|
+
if (!sameScalar(current, tuple[i])) return null;
|
|
191
|
+
} else {
|
|
192
|
+
if (next == null) next = new Map(bindings);
|
|
193
|
+
next.set(pattern.name, tuple[i]);
|
|
194
|
+
}
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (!sameScalar(pattern, tuple[i])) return null;
|
|
198
|
+
}
|
|
199
|
+
return next ?? bindings;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function estimateLiteral(literal, relation, bindings) {
|
|
203
|
+
const candidates = relation.candidateIndexes(literal.args, bindings);
|
|
204
|
+
return candidates == null ? relation.rows.length : candidates.length;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function forEachPositiveBinding(positives, relations, callback, bindings = new Map(), remaining = null) {
|
|
208
|
+
if (remaining == null) remaining = positives.map((_, index) => index);
|
|
209
|
+
if (remaining.length === 0) {
|
|
210
|
+
callback(bindings);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let bestPos = 0;
|
|
215
|
+
let bestEstimate = Infinity;
|
|
216
|
+
for (let position = 0; position < remaining.length; position++) {
|
|
217
|
+
const literal = positives[remaining[position]];
|
|
218
|
+
const relation = relations.get(literal.key);
|
|
219
|
+
const estimate = relation ? estimateLiteral(literal, relation, bindings) : 0;
|
|
220
|
+
if (estimate < bestEstimate) {
|
|
221
|
+
bestEstimate = estimate;
|
|
222
|
+
bestPos = position;
|
|
223
|
+
if (estimate === 0) return;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const literalIndex = remaining[bestPos];
|
|
228
|
+
const literal = positives[literalIndex];
|
|
229
|
+
const relation = relations.get(literal.key);
|
|
230
|
+
if (!relation) return;
|
|
231
|
+
const candidates = relation.candidateIndexes(literal.args, bindings);
|
|
232
|
+
const nextRemaining = remaining.length === 1
|
|
233
|
+
? EMPTY_ARRAY
|
|
234
|
+
: [...remaining.slice(0, bestPos), ...remaining.slice(bestPos + 1)];
|
|
235
|
+
|
|
236
|
+
if (candidates == null) {
|
|
237
|
+
for (let rowIndex = 0; rowIndex < relation.rows.length; rowIndex++) {
|
|
238
|
+
const next = matchTuple(literal.args, relation.rows[rowIndex], bindings);
|
|
239
|
+
if (next) forEachPositiveBinding(positives, relations, callback, next, nextRemaining);
|
|
240
|
+
}
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
for (const rowIndex of candidates) {
|
|
244
|
+
const next = matchTuple(literal.args, relation.rows[rowIndex], bindings);
|
|
245
|
+
if (next) forEachPositiveBinding(positives, relations, callback, next, nextRemaining);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function instantiateTuple(args, bindings) {
|
|
250
|
+
const tuple = [];
|
|
251
|
+
for (const arg of args) {
|
|
252
|
+
const value = arg.type === VAR ? bindings.get(arg.name) : arg;
|
|
253
|
+
if (value == null || value.type === VAR) return null;
|
|
254
|
+
tuple.push(value);
|
|
255
|
+
}
|
|
256
|
+
return tuple;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function blockedByNegative(negatives, blockerRelations, bindings) {
|
|
260
|
+
if (!blockerRelations) return false;
|
|
261
|
+
for (const literal of negatives) {
|
|
262
|
+
const relation = blockerRelations.get(literal.key);
|
|
263
|
+
if (!relation) continue;
|
|
264
|
+
const tuple = instantiateTuple(literal.args, bindings);
|
|
265
|
+
if (tuple == null) return true; // range restriction should make this unreachable
|
|
266
|
+
if (relation.has(tuple)) return true;
|
|
267
|
+
}
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function gamma(compiled, blockerRelations = null) {
|
|
272
|
+
const relations = copyBaseRelations(compiled.base, compiled.groups);
|
|
273
|
+
let changed = true;
|
|
274
|
+
while (changed) {
|
|
275
|
+
changed = false;
|
|
276
|
+
for (const rule of compiled.rules) {
|
|
277
|
+
const headRelation = relations.get(rule.head.key);
|
|
278
|
+
if (!headRelation) continue;
|
|
279
|
+
forEachPositiveBinding(rule.positives, relations, (bindings) => {
|
|
280
|
+
if (blockedByNegative(rule.negatives, blockerRelations, bindings)) return;
|
|
281
|
+
const tuple = instantiateTuple(rule.head.args, bindings);
|
|
282
|
+
if (tuple != null && headRelation.add(tuple)) changed = true;
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return relations;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function relationSetsEqual(left, right) {
|
|
290
|
+
if (left.size !== right.size) return false;
|
|
291
|
+
for (const [key, leftRelation] of left) {
|
|
292
|
+
const rightRelation = right.get(key);
|
|
293
|
+
if (!rightRelation || leftRelation.keys.size !== rightRelation.keys.size) return false;
|
|
294
|
+
for (const tuple of leftRelation.keys) if (!rightRelation.keys.has(tuple)) return false;
|
|
295
|
+
}
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function evaluateWfs(program, rootGroup) {
|
|
300
|
+
const compiled = compileProgram(program, rootGroup);
|
|
301
|
+
|
|
302
|
+
// Positive closure with tnot erased is an upper bound on every atom that can
|
|
303
|
+
// participate in the WFS model, avoiding construction of the full Herbrand
|
|
304
|
+
// cross product for predicates of arity > 1.
|
|
305
|
+
let upper = gamma(compiled, null);
|
|
306
|
+
let lower = emptyRelations(compiled.groups);
|
|
307
|
+
let rounds = 0;
|
|
308
|
+
|
|
309
|
+
while (true) {
|
|
310
|
+
rounds++;
|
|
311
|
+
const nextLower = gamma(compiled, upper);
|
|
312
|
+
const nextUpper = gamma(compiled, nextLower);
|
|
313
|
+
if (relationSetsEqual(lower, nextLower) && relationSetsEqual(upper, nextUpper)) {
|
|
314
|
+
lower = nextLower;
|
|
315
|
+
upper = nextUpper;
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
lower = nextLower;
|
|
319
|
+
upper = nextUpper;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return { lower, upper, rounds, groups: compiled.groups };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function relationForGroup(model, group, kind = 'upper') {
|
|
326
|
+
const relations = kind === 'lower' ? model.lower : model.upper;
|
|
327
|
+
return relations.get(predicateKey(group.module, group.name, group.arity)) ?? null;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function truthOfGroundGoal(model, goal) {
|
|
331
|
+
const key = predicateKey(goal.module ?? 'user', goal.name, goal.arity);
|
|
332
|
+
const tuple = goal.args ?? EMPTY_ARRAY;
|
|
333
|
+
const lower = model.lower.get(key);
|
|
334
|
+
if (lower?.has(tuple)) return 'true';
|
|
335
|
+
const upper = model.upper.get(key);
|
|
336
|
+
if (upper?.has(tuple)) return 'undefined';
|
|
337
|
+
return 'false';
|
|
338
|
+
}
|
|
@@ -56,7 +56,8 @@ not interpret the following as core-language features:
|
|
|
56
56
|
- the implementation-specific `occurs_check` Prolog flag;
|
|
57
57
|
- Part 2 module directives (`module/2`, `use_module/1-2`, `meta_predicate/1`);
|
|
58
58
|
- Part 3 grammar-rule expansion and `phrase/2-3`;
|
|
59
|
-
- automatic tabling, cycle guards, and recursive numeric execution shortcuts
|
|
59
|
+
- automatic tabling, cycle guards, and recursive numeric execution shortcuts;
|
|
60
|
+
- EyeProlog well-founded negation via `tnot/1` and its WFS runtime statistics.
|
|
60
61
|
|
|
61
62
|
The predefined Part 1 `1200 fx` `?-` operator and `1200 xfx` `-->` operator
|
|
62
63
|
remain ordinary operator syntax in strict core mode. A conforming `op/3`
|
|
@@ -21,7 +21,10 @@ processor-requirement ledger for the Part 1 strict-core audit. This suite is
|
|
|
21
21
|
still not an independent certification of every processor requirement, lexical
|
|
22
22
|
edge, option combination, or prescribed error precedence. Cases under `iso/`
|
|
23
23
|
identify standards-derived behavior; other directories cover EyeProlog host
|
|
24
|
-
contracts and extensions.
|
|
24
|
+
contracts and extensions. EyeProlog-only execution features such as automatic
|
|
25
|
+
tabling and `tnot/1` well-founded negation are outside the Part 1 strict-core
|
|
26
|
+
claim. Their focused semantic coverage lives primarily in regression tests;
|
|
27
|
+
`tnot/1` is absent from the strict ISO registry.
|
|
25
28
|
|
|
26
29
|
All conformance files live under topic directories such as `arithmetic/`, `lists/`, `syntax/`, or `variables/`; new top-level numbered files should not be added. The report uses those directories as coverage categories.
|
|
27
30
|
|
package/test/run-conformance.mjs
CHANGED
|
@@ -18,6 +18,7 @@ function withStandardModules(text) {
|
|
|
18
18
|
return `:- use_module(library(aggregate)).
|
|
19
19
|
:- use_module(library(comparison)).
|
|
20
20
|
:- use_module(library(dates)).
|
|
21
|
+
:- use_module(library(iso_ext)).
|
|
21
22
|
:- use_module(library(lists)).
|
|
22
23
|
:- use_module(library(primes)).
|
|
23
24
|
:- use_module(library(prologue), [between/3]).
|