eyeprolog 1.3.4 → 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 +66 -0
- package/examples/book/README.md +2 -1
- package/examples/book/chapter-07/02-move.pl +4 -0
- package/index.d.ts +7 -0
- package/package.json +1 -1
- package/src/datalog.js +354 -0
- package/src/iso.js +107 -9
- package/src/program.js +258 -20
- package/src/solver.js +841 -41
- package/src/standard-library.js +5 -0
- 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-regression.mjs +188 -1
- package/the-art-of-eyeprolog.md +70 -8
- 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/program.js
CHANGED
|
@@ -166,6 +166,9 @@ export class Program {
|
|
|
166
166
|
tabled: false,
|
|
167
167
|
recursive: false,
|
|
168
168
|
tableInputPositions: [],
|
|
169
|
+
tableAllVariants: false,
|
|
170
|
+
datalogLeastModel: false,
|
|
171
|
+
wfsDatalog: false,
|
|
169
172
|
scalarFactsOnly: true,
|
|
170
173
|
dynamic: this.dynamicPredicates.has(modulePredicateKey(module, name, arity)),
|
|
171
174
|
negationStratum: null,
|
|
@@ -308,6 +311,7 @@ export class Program {
|
|
|
308
311
|
const deps = groups.map(() => new Set());
|
|
309
312
|
const cutDeps = groups.map(() => new Set());
|
|
310
313
|
const negativeEdges = [];
|
|
314
|
+
const wfsNegativeEdges = [];
|
|
311
315
|
for (const group of groups) {
|
|
312
316
|
const groupIndex = indexByGroup.get(group);
|
|
313
317
|
for (const clause of group.clauses) {
|
|
@@ -337,12 +341,17 @@ export class Program {
|
|
|
337
341
|
if (dep) {
|
|
338
342
|
const dependencyIndex = indexByGroup.get(dep);
|
|
339
343
|
deps[groupIndex].add(dependencyIndex);
|
|
340
|
-
if (dependency.negative)
|
|
344
|
+
if (dependency.negative) {
|
|
345
|
+
negativeEdges.push([groupIndex, dependencyIndex]);
|
|
346
|
+
if (dependency.wfs === true) wfsNegativeEdges.push([groupIndex, dependencyIndex]);
|
|
347
|
+
}
|
|
341
348
|
}
|
|
342
349
|
}
|
|
343
350
|
}
|
|
344
351
|
}
|
|
345
352
|
}
|
|
353
|
+
const finiteDatalogCache = new Map();
|
|
354
|
+
const rangeRestrictedDatalogCache = new Map();
|
|
346
355
|
for (const group of groups) {
|
|
347
356
|
const start = indexByGroup.get(group);
|
|
348
357
|
const standardLibraryModule = group.module !== 'user' &&
|
|
@@ -386,6 +395,36 @@ export class Program {
|
|
|
386
395
|
!componentHasNegativeEdge(start, deps, negativeEdges) &&
|
|
387
396
|
!group.cutRecursive &&
|
|
388
397
|
!linearNumeric;
|
|
398
|
+
// Function-free positive recursive rules have a finite Herbrand base.
|
|
399
|
+
// They can safely use a shared most-general table, which avoids computing
|
|
400
|
+
// a separate recursive closure for every bound input (TC/SG/Wine style).
|
|
401
|
+
group.tableAllVariants = group.tabled && isFiniteDatalogGroup(this, group, finiteDatalogCache) &&
|
|
402
|
+
datalogDependencyClauseCount(this, group) >= 128;
|
|
403
|
+
// Large finite, range-restricted positive Datalog can be evaluated as a
|
|
404
|
+
// single indexed least model. Keep tableAllVariants as the semantic
|
|
405
|
+
// fallback for finite programs whose answers may contain variables.
|
|
406
|
+
group.datalogLeastModel = group.tableAllVariants &&
|
|
407
|
+
isRangeRestrictedFiniteDatalogGroup(this, group, rangeRestrictedDatalogCache);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Explicit tnot/1 opts finite, function-free Datalog into well-founded
|
|
411
|
+
// evaluation. Keep ISO \+/1 and not/1 on their existing negation-as-failure
|
|
412
|
+
// path: WFS is an extension with different three-valued semantics and must
|
|
413
|
+
// not silently change ordinary Prolog programs. A group is WFS-backed when
|
|
414
|
+
// its dependency cone reaches an SCC containing an edge introduced by
|
|
415
|
+
// tnot/1 and the whole cone is range-restricted finite Datalog.
|
|
416
|
+
const wfsCycleNodes = new Set();
|
|
417
|
+
for (const [from, to] of wfsNegativeEdges) {
|
|
418
|
+
if (!reachableIndexes(to, deps).has(from)) continue;
|
|
419
|
+
wfsCycleNodes.add(from);
|
|
420
|
+
wfsCycleNodes.add(to);
|
|
421
|
+
}
|
|
422
|
+
const finiteWfsCache = new Map();
|
|
423
|
+
for (const group of groups) {
|
|
424
|
+
const start = indexByGroup.get(group);
|
|
425
|
+
const reachable = reachableIndexes(start, deps);
|
|
426
|
+
const reachesWfsCycle = [...wfsCycleNodes].some((index) => reachable.has(index));
|
|
427
|
+
group.wfsDatalog = reachesWfsCycle && isFiniteWfsDatalogGroup(this, group, finiteWfsCache);
|
|
389
428
|
}
|
|
390
429
|
}
|
|
391
430
|
|
|
@@ -1253,6 +1292,185 @@ function reachableIndexes(start, deps) {
|
|
|
1253
1292
|
return seen;
|
|
1254
1293
|
}
|
|
1255
1294
|
|
|
1295
|
+
|
|
1296
|
+
function isFiniteDatalogArgument(term) {
|
|
1297
|
+
return term?.type === VAR || term?.type === ATOM || term?.type === 'string' || term?.type === 'number';
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
function datalogDependencyClauseCount(program, group, seen = new Set()) {
|
|
1301
|
+
if (seen.has(group)) return 0;
|
|
1302
|
+
seen.add(group);
|
|
1303
|
+
let count = group.clauses.length;
|
|
1304
|
+
for (const clause of group.clauses) {
|
|
1305
|
+
if (isCompactBinaryClause(clause)) {
|
|
1306
|
+
if (clause.bodyName != null) {
|
|
1307
|
+
const target = program.findGroup(clause.bodyName, 2, group.module);
|
|
1308
|
+
if (target) count += datalogDependencyClauseCount(program, target, seen);
|
|
1309
|
+
}
|
|
1310
|
+
continue;
|
|
1311
|
+
}
|
|
1312
|
+
for (const goal of clause.body) {
|
|
1313
|
+
if (goal.type !== COMPOUND && goal.type !== ATOM) continue;
|
|
1314
|
+
const target = program.findGroup(goal.name, goal.arity, goal.module ?? group.module);
|
|
1315
|
+
if (target) count += datalogDependencyClauseCount(program, target, seen);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
return count;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
function isFiniteDatalogGroup(program, group, cache = new Map(), visiting = new Set()) {
|
|
1322
|
+
const cached = cache.get(group);
|
|
1323
|
+
if (cached != null) return cached;
|
|
1324
|
+
if (visiting.has(group)) return true;
|
|
1325
|
+
visiting.add(group);
|
|
1326
|
+
let finite = true;
|
|
1327
|
+
for (const clause of group.clauses) {
|
|
1328
|
+
if (isCompactBinaryClause(clause)) {
|
|
1329
|
+
if (clause.bodyName != null) {
|
|
1330
|
+
const target = program.findGroup(clause.bodyName, 2, group.module);
|
|
1331
|
+
if (!target || !isFiniteDatalogGroup(program, target, cache, visiting)) { finite = false; break; }
|
|
1332
|
+
}
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
if (clause.head.type === COMPOUND && !clause.head.args.every(isFiniteDatalogArgument)) { finite = false; break; }
|
|
1336
|
+
if (clause.head.type !== COMPOUND && clause.head.type !== ATOM) { finite = false; break; }
|
|
1337
|
+
for (const goal of clause.body) {
|
|
1338
|
+
if (goal.type !== COMPOUND && goal.type !== ATOM) { finite = false; break; }
|
|
1339
|
+
if (goal.type === COMPOUND && !goal.args.every(isFiniteDatalogArgument)) { finite = false; break; }
|
|
1340
|
+
// Walk the whole positive dependency cone. Requiring every reachable
|
|
1341
|
+
// predicate to be source-defined and flat excludes builtins/generators
|
|
1342
|
+
// that could manufacture an unbounded stream of fresh terms.
|
|
1343
|
+
const target = program.findGroup(goal.name, goal.arity, goal.module ?? group.module);
|
|
1344
|
+
if (!target || !isFiniteDatalogGroup(program, target, cache, visiting)) { finite = false; break; }
|
|
1345
|
+
}
|
|
1346
|
+
if (!finite) break;
|
|
1347
|
+
}
|
|
1348
|
+
visiting.delete(group);
|
|
1349
|
+
cache.set(group, finite);
|
|
1350
|
+
return finite;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
|
|
1354
|
+
function isRangeRestrictedFiniteDatalogGroup(program, group, cache = new Map(), visiting = new Set()) {
|
|
1355
|
+
const cached = cache.get(group);
|
|
1356
|
+
if (cached != null) return cached;
|
|
1357
|
+
if (visiting.has(group)) return true;
|
|
1358
|
+
visiting.add(group);
|
|
1359
|
+
let finite = true;
|
|
1360
|
+
|
|
1361
|
+
for (const clause of group.clauses) {
|
|
1362
|
+
const head = clause.head;
|
|
1363
|
+
if ((head.type !== COMPOUND && head.type !== ATOM) ||
|
|
1364
|
+
(head.type === COMPOUND && !head.args.every(isFiniteDatalogArgument))) {
|
|
1365
|
+
finite = false;
|
|
1366
|
+
break;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
const headVariables = new Set();
|
|
1370
|
+
const positiveVariables = new Set();
|
|
1371
|
+
collectVariables(head, headVariables);
|
|
1372
|
+
for (const goal of clause.body) {
|
|
1373
|
+
if ((goal.type !== COMPOUND && goal.type !== ATOM) ||
|
|
1374
|
+
(goal.type === COMPOUND && !goal.args.every(isFiniteDatalogArgument))) {
|
|
1375
|
+
finite = false;
|
|
1376
|
+
break;
|
|
1377
|
+
}
|
|
1378
|
+
collectVariables(goal, positiveVariables);
|
|
1379
|
+
const target = program.findGroup(goal.name, goal.arity, goal.module ?? group.module);
|
|
1380
|
+
if (!target || !isRangeRestrictedFiniteDatalogGroup(program, target, cache, visiting)) {
|
|
1381
|
+
finite = false;
|
|
1382
|
+
break;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
if (!finite) break;
|
|
1386
|
+
for (const name of headVariables) {
|
|
1387
|
+
if (!positiveVariables.has(name)) {
|
|
1388
|
+
// Ground facts have no head variables and pass naturally. A variable
|
|
1389
|
+
// fact or a rule with an unbound head variable needs ordinary Prolog
|
|
1390
|
+
// table semantics rather than finite relational materialization.
|
|
1391
|
+
finite = false;
|
|
1392
|
+
break;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
if (!finite) break;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
visiting.delete(group);
|
|
1399
|
+
cache.set(group, finite);
|
|
1400
|
+
return finite;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
|
|
1404
|
+
function isFiniteWfsDatalogGroup(program, group, cache = new Map(), visiting = new Set()) {
|
|
1405
|
+
const cached = cache.get(group);
|
|
1406
|
+
if (cached != null) return cached;
|
|
1407
|
+
if (visiting.has(group)) return true;
|
|
1408
|
+
visiting.add(group);
|
|
1409
|
+
let finite = true;
|
|
1410
|
+
|
|
1411
|
+
for (const clause of group.clauses) {
|
|
1412
|
+
if (isCompactBinaryClause(clause)) {
|
|
1413
|
+
if (clause.bodyName != null) {
|
|
1414
|
+
const target = program.findGroup(clause.bodyName, 2, group.module);
|
|
1415
|
+
if (!target || !isFiniteWfsDatalogGroup(program, target, cache, visiting)) { finite = false; break; }
|
|
1416
|
+
}
|
|
1417
|
+
continue;
|
|
1418
|
+
}
|
|
1419
|
+
if ((clause.head.type !== COMPOUND && clause.head.type !== ATOM) ||
|
|
1420
|
+
(clause.head.type === COMPOUND && !clause.head.args.every(isFiniteDatalogArgument))) {
|
|
1421
|
+
finite = false;
|
|
1422
|
+
break;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
const positiveVariables = new Set();
|
|
1426
|
+
const requiredVariables = new Set();
|
|
1427
|
+
collectVariables(clause.head, requiredVariables);
|
|
1428
|
+
for (const goal of clause.body) {
|
|
1429
|
+
if (goal.type !== COMPOUND && goal.type !== ATOM) { finite = false; break; }
|
|
1430
|
+
if (goal.type === COMPOUND && goal.name === 'tnot' && goal.arity === 1) {
|
|
1431
|
+
const inner = goal.args[0];
|
|
1432
|
+
if ((inner.type !== COMPOUND && inner.type !== ATOM) ||
|
|
1433
|
+
(inner.type === COMPOUND && !inner.args.every(isFiniteDatalogArgument))) {
|
|
1434
|
+
finite = false;
|
|
1435
|
+
break;
|
|
1436
|
+
}
|
|
1437
|
+
collectVariables(inner, requiredVariables);
|
|
1438
|
+
const target = program.findGroup(inner.name, inner.arity, inner.module ?? group.module);
|
|
1439
|
+
if (!target || !isFiniteWfsDatalogGroup(program, target, cache, visiting)) { finite = false; break; }
|
|
1440
|
+
continue;
|
|
1441
|
+
}
|
|
1442
|
+
// WFS is explicit. Ordinary \+/1 and not/1 retain NAF and therefore
|
|
1443
|
+
// disqualify this component from the WFS evaluator.
|
|
1444
|
+
if (goal.type === COMPOUND && (goal.name === '\\+' || goal.name === 'not')) {
|
|
1445
|
+
finite = false;
|
|
1446
|
+
break;
|
|
1447
|
+
}
|
|
1448
|
+
if (goal.type === COMPOUND && !goal.args.every(isFiniteDatalogArgument)) { finite = false; break; }
|
|
1449
|
+
collectVariables(goal, positiveVariables);
|
|
1450
|
+
const target = program.findGroup(goal.name, goal.arity, goal.module ?? group.module);
|
|
1451
|
+
if (!target || !isFiniteWfsDatalogGroup(program, target, cache, visiting)) { finite = false; break; }
|
|
1452
|
+
}
|
|
1453
|
+
if (!finite) break;
|
|
1454
|
+
for (const name of requiredVariables) {
|
|
1455
|
+
if (!positiveVariables.has(name)) { finite = false; break; }
|
|
1456
|
+
}
|
|
1457
|
+
if (!finite) break;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
visiting.delete(group);
|
|
1461
|
+
cache.set(group, finite);
|
|
1462
|
+
return finite;
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
function collectVariables(term, output) {
|
|
1466
|
+
if (!term) return;
|
|
1467
|
+
if (term.type === VAR) {
|
|
1468
|
+
output.add(term.name);
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
for (const arg of term.args ?? []) collectVariables(arg, output);
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1256
1474
|
function inferStructuralInputPositions(group) {
|
|
1257
1475
|
let firstPatternedPosition = -1;
|
|
1258
1476
|
let firstLinkedInputPosition = -1;
|
|
@@ -1304,14 +1522,31 @@ function hasStrictListTailRecursion(group) {
|
|
|
1304
1522
|
let foundRecursiveCall = false;
|
|
1305
1523
|
for (const clause of group.clauses) {
|
|
1306
1524
|
if (isCompactBinaryClause(clause)) return false;
|
|
1307
|
-
for (
|
|
1525
|
+
for (let goalIndex = 0; goalIndex < clause.body.length; goalIndex++) {
|
|
1526
|
+
const goal = clause.body[goalIndex];
|
|
1308
1527
|
if (goal.type !== COMPOUND || goal.name !== group.name || goal.arity !== group.arity) continue;
|
|
1309
1528
|
foundRecursiveCall = true;
|
|
1310
1529
|
const decreases = goal.args.some((argument, index) => {
|
|
1311
1530
|
const head = clause.head.args[index];
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1531
|
+
if (head?.type === COMPOUND && head.name === '.' && head.arity === 2 &&
|
|
1532
|
+
head.args[1]?.type === VAR && argument?.type === VAR &&
|
|
1533
|
+
head.args[1].name === argument.name) return true;
|
|
1534
|
+
// DCG expansion represents [_], nt as Input = [_|Tail], nt(Tail,...).
|
|
1535
|
+
// Treat that normalized prefix unification as the same strict list-tail
|
|
1536
|
+
// decrease so phrase/2-3 can choose a non-tabled fast path for new
|
|
1537
|
+
// inputs while retaining ordinary tabling for repeated invocations.
|
|
1538
|
+
if (head?.type !== VAR || argument?.type !== VAR) return false;
|
|
1539
|
+
for (let prefixIndex = 0; prefixIndex < goalIndex; prefixIndex++) {
|
|
1540
|
+
const prefix = clause.body[prefixIndex];
|
|
1541
|
+
if (prefix?.type !== COMPOUND || prefix.name !== '=' || prefix.arity !== 2) continue;
|
|
1542
|
+
for (const [left, right] of [[prefix.args[0], prefix.args[1]], [prefix.args[1], prefix.args[0]]]) {
|
|
1543
|
+
if (left?.type !== VAR || left.name !== head.name ||
|
|
1544
|
+
right?.type !== COMPOUND || right.name !== '.' || right.arity !== 2 ||
|
|
1545
|
+
right.args[1]?.type !== VAR) continue;
|
|
1546
|
+
if (right.args[1].name === argument.name) return true;
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
return false;
|
|
1315
1550
|
});
|
|
1316
1551
|
if (!decreases) return false;
|
|
1317
1552
|
}
|
|
@@ -1377,7 +1612,7 @@ function directGoalDependencyKey(goal) {
|
|
|
1377
1612
|
if (goal.type === ATOM) return `${goal.name}/0`;
|
|
1378
1613
|
if (goal.type !== COMPOUND) return null;
|
|
1379
1614
|
if (goal.name === ',' && goal.arity === 2) return null;
|
|
1380
|
-
if ((goal.name === '\\+' || goal.name === 'not') && goal.arity === 1) return null;
|
|
1615
|
+
if ((goal.name === '\\+' || goal.name === 'not' || goal.name === 'tnot') && goal.arity === 1) return null;
|
|
1381
1616
|
if (goal.name === 'once' && goal.arity === 1) return null;
|
|
1382
1617
|
if (goal.name === 'forall' && goal.arity === 2) return null;
|
|
1383
1618
|
if ((goal.name === 'findall' || goal.name === 'sumall') && goal.arity === 3) return null;
|
|
@@ -1386,43 +1621,46 @@ function directGoalDependencyKey(goal) {
|
|
|
1386
1621
|
return `${goal.name}/${goal.arity}`;
|
|
1387
1622
|
}
|
|
1388
1623
|
|
|
1389
|
-
function collectGoalDependencies(goal, negated, traverseConditionals = false) {
|
|
1390
|
-
if (goal.type === ATOM) return [{ key: `${goal.name}/0`, name: goal.name, arity: 0, module: goal.module, negative: negated }];
|
|
1624
|
+
function collectGoalDependencies(goal, negated, traverseConditionals = false, wfs = false) {
|
|
1625
|
+
if (goal.type === ATOM) return [{ key: `${goal.name}/0`, name: goal.name, arity: 0, module: goal.module, negative: negated, wfs }];
|
|
1391
1626
|
if (goal.type !== COMPOUND) return [];
|
|
1392
1627
|
if (goal.name === ',' && goal.arity === 2) {
|
|
1393
1628
|
return [
|
|
1394
|
-
...collectGoalDependencies(goal.args[0], negated, traverseConditionals),
|
|
1395
|
-
...collectGoalDependencies(goal.args[1], negated, traverseConditionals),
|
|
1629
|
+
...collectGoalDependencies(goal.args[0], negated, traverseConditionals, wfs),
|
|
1630
|
+
...collectGoalDependencies(goal.args[1], negated, traverseConditionals, wfs),
|
|
1396
1631
|
];
|
|
1397
1632
|
}
|
|
1398
1633
|
if (traverseConditionals && (goal.name === ';' || goal.name === '->') && goal.arity === 2) {
|
|
1399
1634
|
return [
|
|
1400
|
-
...collectGoalDependencies(goal.args[0], negated, true),
|
|
1401
|
-
...collectGoalDependencies(goal.args[1], negated, true),
|
|
1635
|
+
...collectGoalDependencies(goal.args[0], negated, true, wfs),
|
|
1636
|
+
...collectGoalDependencies(goal.args[1], negated, true, wfs),
|
|
1402
1637
|
];
|
|
1403
1638
|
}
|
|
1404
1639
|
if ((goal.name === '\\+' || goal.name === 'not') && goal.arity === 1) {
|
|
1405
|
-
return collectGoalDependencies(goal.args[0], !negated, traverseConditionals);
|
|
1640
|
+
return collectGoalDependencies(goal.args[0], !negated, traverseConditionals, wfs);
|
|
1641
|
+
}
|
|
1642
|
+
if (goal.name === 'tnot' && goal.arity === 1) {
|
|
1643
|
+
return collectGoalDependencies(goal.args[0], !negated, traverseConditionals, true);
|
|
1406
1644
|
}
|
|
1407
1645
|
if (goal.name === 'once' && goal.arity === 1) {
|
|
1408
|
-
return collectGoalDependencies(goal.args[0], negated, traverseConditionals);
|
|
1646
|
+
return collectGoalDependencies(goal.args[0], negated, traverseConditionals, wfs);
|
|
1409
1647
|
}
|
|
1410
1648
|
if (goal.name === 'forall' && goal.arity === 2) {
|
|
1411
1649
|
return [
|
|
1412
|
-
...collectGoalDependencies(goal.args[0], negated, traverseConditionals),
|
|
1413
|
-
...collectGoalDependencies(goal.args[1], negated, traverseConditionals),
|
|
1650
|
+
...collectGoalDependencies(goal.args[0], negated, traverseConditionals, wfs),
|
|
1651
|
+
...collectGoalDependencies(goal.args[1], negated, traverseConditionals, wfs),
|
|
1414
1652
|
];
|
|
1415
1653
|
}
|
|
1416
1654
|
if ((goal.name === 'findall' || goal.name === 'sumall') && goal.arity === 3) {
|
|
1417
|
-
return collectGoalDependencies(goal.args[1], negated, traverseConditionals);
|
|
1655
|
+
return collectGoalDependencies(goal.args[1], negated, traverseConditionals, wfs);
|
|
1418
1656
|
}
|
|
1419
1657
|
if (goal.name === 'countall' && goal.arity === 2) {
|
|
1420
|
-
return collectGoalDependencies(goal.args[0], negated, traverseConditionals);
|
|
1658
|
+
return collectGoalDependencies(goal.args[0], negated, traverseConditionals, wfs);
|
|
1421
1659
|
}
|
|
1422
1660
|
if ((goal.name === 'aggregate_min' || goal.name === 'aggregate_max') && goal.arity === 5) {
|
|
1423
|
-
return collectGoalDependencies(goal.args[2], negated, traverseConditionals);
|
|
1661
|
+
return collectGoalDependencies(goal.args[2], negated, traverseConditionals, wfs);
|
|
1424
1662
|
}
|
|
1425
|
-
return [{ key: `${goal.name}/${goal.arity}`, name: goal.name, arity: goal.arity, module: goal.module, negative: negated }];
|
|
1663
|
+
return [{ key: `${goal.name}/${goal.arity}`, name: goal.name, arity: goal.arity, module: goal.module, negative: negated, wfs }];
|
|
1426
1664
|
}
|
|
1427
1665
|
|
|
1428
1666
|
function stronglyConnectedComponents(adjacency) {
|