eyeling 1.25.3 → 1.25.5
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/HANDBOOK.md +12 -8
- package/dist/browser/eyeling.browser.js +446 -99
- package/eyeling.js +446 -99
- package/lib/engine.js +447 -91
- package/package.json +1 -1
- package/test/api.test.js +60 -0
- package/examples/library-and-path.n3 +0 -499
- package/examples/output/library-and-path.n3 +0 -33
package/lib/engine.js
CHANGED
|
@@ -484,6 +484,158 @@ function __varOccursElsewhereInPremise(premise, name, idx, field) {
|
|
|
484
484
|
return false;
|
|
485
485
|
}
|
|
486
486
|
|
|
487
|
+
|
|
488
|
+
function __scopedPriorityForTerm(t) {
|
|
489
|
+
if (t instanceof GraphTerm) return 0;
|
|
490
|
+
const p0 = __logNaturalPriorityFromTerm(t);
|
|
491
|
+
return p0 !== null && p0 >= 1 ? p0 : 1;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function __pushScopedQueryTriplesFromGraph(term, out) {
|
|
495
|
+
if (!(term instanceof GraphTerm)) return;
|
|
496
|
+
for (const tr of term.triples) out.push(tr);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function __analyzeForwardRuleScopedUse(rule) {
|
|
500
|
+
const out = {
|
|
501
|
+
needsSnap: false,
|
|
502
|
+
baseLevel: 0,
|
|
503
|
+
queryTriples: [],
|
|
504
|
+
hasScopedAggregate: false,
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
if (!rule || !Array.isArray(rule.premise)) return out;
|
|
508
|
+
|
|
509
|
+
for (let i = 0; i < rule.premise.length; i++) {
|
|
510
|
+
const tr = rule.premise[i];
|
|
511
|
+
if (!(tr && tr.p instanceof Iri)) continue;
|
|
512
|
+
const pv = tr.p.value;
|
|
513
|
+
|
|
514
|
+
if (pv === LOG_NS + 'includes' || pv === LOG_NS + 'notIncludes') {
|
|
515
|
+
// Explicit quoted scopes are local formulas, not snapshots of the global closure.
|
|
516
|
+
if (tr.s instanceof GraphTerm) continue;
|
|
517
|
+
|
|
518
|
+
// A scope variable that is bound by another premise may become an explicit GraphTerm.
|
|
519
|
+
// Still, if it remains unbound at runtime the builtin treats it as priority 1.
|
|
520
|
+
// We therefore record the scoped use, but only use quoted object triples for
|
|
521
|
+
// dependency analysis; variables inside the quoted object do not bind the scope.
|
|
522
|
+
out.needsSnap = true;
|
|
523
|
+
out.baseLevel = Math.max(out.baseLevel, __scopedPriorityForTerm(tr.s));
|
|
524
|
+
__pushScopedQueryTriplesFromGraph(tr.o, out.queryTriples);
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (pv === LOG_NS + 'collectAllIn') {
|
|
529
|
+
if (tr.o instanceof GraphTerm) continue;
|
|
530
|
+
|
|
531
|
+
out.needsSnap = true;
|
|
532
|
+
out.hasScopedAggregate = true;
|
|
533
|
+
out.baseLevel = Math.max(out.baseLevel, __scopedPriorityForTerm(tr.o));
|
|
534
|
+
if (tr.s instanceof ListTerm && tr.s.elems.length === 3) {
|
|
535
|
+
__pushScopedQueryTriplesFromGraph(tr.s.elems[1], out.queryTriples);
|
|
536
|
+
}
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (pv === LOG_NS + 'forAllIn') {
|
|
541
|
+
if (tr.o instanceof GraphTerm) continue;
|
|
542
|
+
|
|
543
|
+
out.needsSnap = true;
|
|
544
|
+
out.hasScopedAggregate = true;
|
|
545
|
+
out.baseLevel = Math.max(out.baseLevel, __scopedPriorityForTerm(tr.o));
|
|
546
|
+
if (tr.s instanceof ListTerm && tr.s.elems.length === 2) {
|
|
547
|
+
__pushScopedQueryTriplesFromGraph(tr.s.elems[0], out.queryTriples);
|
|
548
|
+
__pushScopedQueryTriplesFromGraph(tr.s.elems[1], out.queryTriples);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return out;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function __triplePatternsMayOverlap(a, b) {
|
|
557
|
+
return unifyTriple(a, b, __emptySubst()) !== null || unifyTriple(b, a, __emptySubst()) !== null;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function __ruleConclusionsMayFeedScopedQueries(producer, consumerMeta) {
|
|
561
|
+
if (!consumerMeta || !consumerMeta.queryTriples || consumerMeta.queryTriples.length === 0) return false;
|
|
562
|
+
|
|
563
|
+
// Dynamic conclusions are only known after a proof solution. Conservatively
|
|
564
|
+
// assume they can feed any scoped query if the producing rule is scoped.
|
|
565
|
+
if (producer && producer.__dynamicConclusionTerm) return true;
|
|
566
|
+
|
|
567
|
+
if (!producer || !Array.isArray(producer.conclusion) || producer.conclusion.length === 0) return false;
|
|
568
|
+
for (const q of consumerMeta.queryTriples) {
|
|
569
|
+
for (const h of producer.conclusion) {
|
|
570
|
+
if (__triplePatternsMayOverlap(q, h)) return true;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function __setForwardRuleScopedStratumInfo(rule, level) {
|
|
577
|
+
const value =
|
|
578
|
+
level > 0
|
|
579
|
+
? { needsSnap: true, requiredLevel: level, exactLevel: true }
|
|
580
|
+
: { needsSnap: false, requiredLevel: 0, exactLevel: false };
|
|
581
|
+
|
|
582
|
+
if (!hasOwn.call(rule, '__scopedStratumInfo')) {
|
|
583
|
+
Object.defineProperty(rule, '__scopedStratumInfo', {
|
|
584
|
+
value,
|
|
585
|
+
enumerable: false,
|
|
586
|
+
writable: true,
|
|
587
|
+
configurable: true,
|
|
588
|
+
});
|
|
589
|
+
} else {
|
|
590
|
+
rule.__scopedStratumInfo = value;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function __computeForwardRuleScopedStrata(forwardRules) {
|
|
595
|
+
if (!Array.isArray(forwardRules) || forwardRules.length === 0) return 0;
|
|
596
|
+
|
|
597
|
+
const metas = forwardRules.map((r) => __analyzeForwardRuleScopedUse(r));
|
|
598
|
+
const levels = metas.map((m) => (m.needsSnap ? Math.max(1, m.baseLevel || 1) : 0));
|
|
599
|
+
|
|
600
|
+
let maxLevel = 0;
|
|
601
|
+
for (const lvl of levels) if (lvl > maxLevel) maxLevel = lvl;
|
|
602
|
+
|
|
603
|
+
// Stratify scoped rules by data dependency: if a scoped query can read facts
|
|
604
|
+
// produced by another scoped rule, the reader must run against a later frozen
|
|
605
|
+
// snapshot. This prevents non-monotonic scoped builtins such as collectAllIn
|
|
606
|
+
// from emitting an early result before lower strata have derived their facts.
|
|
607
|
+
const passLimit = Math.max(1, forwardRules.length) + maxLevel + 1;
|
|
608
|
+
for (let pass = 0; pass < passLimit; pass++) {
|
|
609
|
+
let changed = false;
|
|
610
|
+
for (let ci = 0; ci < forwardRules.length; ci++) {
|
|
611
|
+
if (!metas[ci].needsSnap || !metas[ci].hasScopedAggregate) continue;
|
|
612
|
+
let needed = levels[ci];
|
|
613
|
+
for (let pi = 0; pi < forwardRules.length; pi++) {
|
|
614
|
+
if (pi === ci) continue;
|
|
615
|
+
if (levels[pi] <= 0) continue;
|
|
616
|
+
if (metas[pi].hasScopedAggregate) continue;
|
|
617
|
+
if (!__ruleConclusionsMayFeedScopedQueries(forwardRules[pi], metas[ci])) continue;
|
|
618
|
+
needed = Math.max(needed, levels[pi] + 1);
|
|
619
|
+
}
|
|
620
|
+
if (needed !== levels[ci]) {
|
|
621
|
+
levels[ci] = needed;
|
|
622
|
+
if (needed > maxLevel) maxLevel = needed;
|
|
623
|
+
changed = true;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
if (!changed) break;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
maxLevel = 0;
|
|
631
|
+
for (let i = 0; i < forwardRules.length; i++) {
|
|
632
|
+
__setForwardRuleScopedStratumInfo(forwardRules[i], levels[i]);
|
|
633
|
+
if (levels[i] > maxLevel) maxLevel = levels[i];
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return maxLevel;
|
|
637
|
+
}
|
|
638
|
+
|
|
487
639
|
function __computeForwardRuleScopedSkipInfo(rule) {
|
|
488
640
|
let needsSnap = false;
|
|
489
641
|
let requiredLevel = 0;
|
|
@@ -1034,6 +1186,12 @@ function alphaEqGraphTriples(xs, ys, opts) {
|
|
|
1034
1186
|
// - __byPred: Map<predicateId, number[]> (indices into facts array)
|
|
1035
1187
|
// - __byPS: Map<predicateId, Map<subjectId, number[]>>
|
|
1036
1188
|
// - __byPO: Map<predicateId, Map<objectId, number[]>>
|
|
1189
|
+
// - __byPNonFastS / __byPNonFastO: Map<predicateId, number[]>
|
|
1190
|
+
// IRI-predicate facts whose subject/object cannot be indexed by fast key.
|
|
1191
|
+
// These are the fallback clauses for a constrained subject/object, just
|
|
1192
|
+
// like a Prolog clause index keeps variable-headed clauses as fallback.
|
|
1193
|
+
// - __varPred* indexes: facts whose predicate is a Var. Only these non-IRI
|
|
1194
|
+
// predicate facts can unify with a ground IRI predicate goal.
|
|
1037
1195
|
// - __keySet: Set<"S\tP\tO"> for Iri/Literal/Blank-only triples (fast dup check)
|
|
1038
1196
|
//
|
|
1039
1197
|
// Backward rules:
|
|
@@ -1049,6 +1207,8 @@ const __compoundKeyToTid = new Map();
|
|
|
1049
1207
|
// Use a negative id space so we never collide with __tid (which is positive).
|
|
1050
1208
|
let __nextCompoundTid = -1;
|
|
1051
1209
|
|
|
1210
|
+
const EMPTY_FACT_INDEX_BUCKET = Object.freeze([]);
|
|
1211
|
+
|
|
1052
1212
|
function __internCompoundTid(key) {
|
|
1053
1213
|
const hit = __compoundKeyToTid.get(key);
|
|
1054
1214
|
if (hit !== undefined) return hit;
|
|
@@ -1100,6 +1260,71 @@ function termFastKey(t) {
|
|
|
1100
1260
|
return null;
|
|
1101
1261
|
}
|
|
1102
1262
|
|
|
1263
|
+
function encodeLookupKeyPart(k) {
|
|
1264
|
+
if (typeof k === 'number') return 'T' + k;
|
|
1265
|
+
const s = String(k);
|
|
1266
|
+
return 'K' + s.length + ':' + s;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
function literalLookupKey(t) {
|
|
1270
|
+
const boolInfo = parseBooleanLiteralInfo(t);
|
|
1271
|
+
if (boolInfo) return '\u0000B' + (boolInfo.value ? '1' : '0');
|
|
1272
|
+
|
|
1273
|
+
const numInfo = parseNumericLiteralInfo(t);
|
|
1274
|
+
if (numInfo) {
|
|
1275
|
+
if (numInfo.kind === 'bigint') return '\u0000N' + numInfo.dt + '\u0000' + numInfo.value.toString();
|
|
1276
|
+
|
|
1277
|
+
const n = numInfo.value;
|
|
1278
|
+
// Normal unification intentionally does not make NaN value-equal to NaN;
|
|
1279
|
+
// only identical lexical literals match through the ordinary __tid path.
|
|
1280
|
+
if (!Number.isNaN(n)) return '\u0000N' + numInfo.dt + '\u0000' + String(n);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// Covers exact literals plus plain string / xsd:string canonicalization, which
|
|
1284
|
+
// Literal construction already normalizes into a shared __tid.
|
|
1285
|
+
return termFastKey(t);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
function termLookupKey(t) {
|
|
1289
|
+
// Lookup keys summarize the equality accepted by ordinary unifyTerm(), not
|
|
1290
|
+
// merely object identity. This keeps literal-index pruning complete for
|
|
1291
|
+
// value-equivalent booleans/numerics such as true/"1"^^xsd:boolean and
|
|
1292
|
+
// 1.0/1.00, while preserving exact fast ids for IRIs, blanks, and strings.
|
|
1293
|
+
if (t instanceof Iri) {
|
|
1294
|
+
if (t.value === RDF_NIL_IRI) return '\u0000L0';
|
|
1295
|
+
return t.__tid;
|
|
1296
|
+
}
|
|
1297
|
+
if (t instanceof Blank) return t.__tid;
|
|
1298
|
+
if (t instanceof Literal) return literalLookupKey(t);
|
|
1299
|
+
|
|
1300
|
+
if (t instanceof ListTerm) {
|
|
1301
|
+
const cached = t.__lookupKey;
|
|
1302
|
+
if (cached !== undefined) return cached === false ? null : cached;
|
|
1303
|
+
|
|
1304
|
+
const xs = t.elems;
|
|
1305
|
+
if (xs.length === 0) {
|
|
1306
|
+
Object.defineProperty(t, '__lookupKey', { value: '\u0000L0', enumerable: false });
|
|
1307
|
+
return '\u0000L0';
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
const parts = new Array(xs.length);
|
|
1311
|
+
for (let i = 0; i < xs.length; i++) {
|
|
1312
|
+
const k = termLookupKey(xs[i]);
|
|
1313
|
+
if (k === null) {
|
|
1314
|
+
Object.defineProperty(t, '__lookupKey', { value: false, enumerable: false });
|
|
1315
|
+
return null;
|
|
1316
|
+
}
|
|
1317
|
+
parts[i] = encodeLookupKeyPart(k);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
const key = '\u0000L' + xs.length + '\u0001' + parts.join('\u0001');
|
|
1321
|
+
Object.defineProperty(t, '__lookupKey', { value: key, enumerable: false });
|
|
1322
|
+
return key;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
return null;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1103
1328
|
function tripleFastKey(tr) {
|
|
1104
1329
|
const ks = termFastKey(tr.s);
|
|
1105
1330
|
const kp = termFastKey(tr.p);
|
|
@@ -1113,9 +1338,13 @@ function ensureFactIndexes(facts) {
|
|
|
1113
1338
|
facts.__byPred &&
|
|
1114
1339
|
facts.__byPS &&
|
|
1115
1340
|
facts.__byPO &&
|
|
1116
|
-
facts.
|
|
1117
|
-
facts.
|
|
1118
|
-
facts.
|
|
1341
|
+
facts.__byPNonFastS &&
|
|
1342
|
+
facts.__byPNonFastO &&
|
|
1343
|
+
facts.__varPred &&
|
|
1344
|
+
facts.__varPredPS &&
|
|
1345
|
+
facts.__varPredPO &&
|
|
1346
|
+
facts.__varPredNonFastS &&
|
|
1347
|
+
facts.__varPredNonFastO &&
|
|
1119
1348
|
facts.__keySet
|
|
1120
1349
|
)
|
|
1121
1350
|
return;
|
|
@@ -1135,21 +1364,41 @@ function ensureFactIndexes(facts) {
|
|
|
1135
1364
|
enumerable: false,
|
|
1136
1365
|
writable: true,
|
|
1137
1366
|
});
|
|
1138
|
-
Object.defineProperty(facts, '
|
|
1367
|
+
Object.defineProperty(facts, '__byPNonFastS', {
|
|
1368
|
+
value: new Map(),
|
|
1369
|
+
enumerable: false,
|
|
1370
|
+
writable: true,
|
|
1371
|
+
});
|
|
1372
|
+
Object.defineProperty(facts, '__byPNonFastO', {
|
|
1373
|
+
value: new Map(),
|
|
1374
|
+
enumerable: false,
|
|
1375
|
+
writable: true,
|
|
1376
|
+
});
|
|
1377
|
+
Object.defineProperty(facts, '__varPred', {
|
|
1139
1378
|
value: [],
|
|
1140
1379
|
enumerable: false,
|
|
1141
1380
|
writable: true,
|
|
1142
1381
|
});
|
|
1143
|
-
Object.defineProperty(facts, '
|
|
1382
|
+
Object.defineProperty(facts, '__varPredPS', {
|
|
1144
1383
|
value: new Map(),
|
|
1145
1384
|
enumerable: false,
|
|
1146
1385
|
writable: true,
|
|
1147
1386
|
});
|
|
1148
|
-
Object.defineProperty(facts, '
|
|
1387
|
+
Object.defineProperty(facts, '__varPredPO', {
|
|
1149
1388
|
value: new Map(),
|
|
1150
1389
|
enumerable: false,
|
|
1151
1390
|
writable: true,
|
|
1152
1391
|
});
|
|
1392
|
+
Object.defineProperty(facts, '__varPredNonFastS', {
|
|
1393
|
+
value: [],
|
|
1394
|
+
enumerable: false,
|
|
1395
|
+
writable: true,
|
|
1396
|
+
});
|
|
1397
|
+
Object.defineProperty(facts, '__varPredNonFastO', {
|
|
1398
|
+
value: [],
|
|
1399
|
+
enumerable: false,
|
|
1400
|
+
writable: true,
|
|
1401
|
+
});
|
|
1153
1402
|
Object.defineProperty(facts, '__keySet', {
|
|
1154
1403
|
value: new Set(),
|
|
1155
1404
|
enumerable: false,
|
|
@@ -1190,16 +1439,53 @@ function cloneFactIndexesForSnapshot(src, dest) {
|
|
|
1190
1439
|
Object.defineProperty(dest, '__byPred', { value: cloneArrayMap(src.__byPred), enumerable: false, writable: true });
|
|
1191
1440
|
Object.defineProperty(dest, '__byPS', { value: cloneNestedArrayMap(src.__byPS), enumerable: false, writable: true });
|
|
1192
1441
|
Object.defineProperty(dest, '__byPO', { value: cloneNestedArrayMap(src.__byPO), enumerable: false, writable: true });
|
|
1193
|
-
Object.defineProperty(dest, '
|
|
1194
|
-
|
|
1195
|
-
|
|
1442
|
+
Object.defineProperty(dest, '__byPNonFastS', {
|
|
1443
|
+
value: cloneArrayMap(src.__byPNonFastS),
|
|
1444
|
+
enumerable: false,
|
|
1445
|
+
writable: true,
|
|
1446
|
+
});
|
|
1447
|
+
Object.defineProperty(dest, '__byPNonFastO', {
|
|
1448
|
+
value: cloneArrayMap(src.__byPNonFastO),
|
|
1449
|
+
enumerable: false,
|
|
1450
|
+
writable: true,
|
|
1451
|
+
});
|
|
1452
|
+
Object.defineProperty(dest, '__varPred', { value: src.__varPred.slice(), enumerable: false, writable: true });
|
|
1453
|
+
Object.defineProperty(dest, '__varPredPS', {
|
|
1454
|
+
value: cloneArrayMap(src.__varPredPS),
|
|
1455
|
+
enumerable: false,
|
|
1456
|
+
writable: true,
|
|
1457
|
+
});
|
|
1458
|
+
Object.defineProperty(dest, '__varPredPO', {
|
|
1459
|
+
value: cloneArrayMap(src.__varPredPO),
|
|
1460
|
+
enumerable: false,
|
|
1461
|
+
writable: true,
|
|
1462
|
+
});
|
|
1463
|
+
Object.defineProperty(dest, '__varPredNonFastS', {
|
|
1464
|
+
value: src.__varPredNonFastS.slice(),
|
|
1465
|
+
enumerable: false,
|
|
1466
|
+
writable: true,
|
|
1467
|
+
});
|
|
1468
|
+
Object.defineProperty(dest, '__varPredNonFastO', {
|
|
1469
|
+
value: src.__varPredNonFastO.slice(),
|
|
1470
|
+
enumerable: false,
|
|
1471
|
+
writable: true,
|
|
1472
|
+
});
|
|
1196
1473
|
Object.defineProperty(dest, '__keySet', { value: new Set(src.__keySet), enumerable: false, writable: true });
|
|
1197
1474
|
Object.defineProperty(dest, '__keySetComplete', { value: !!src.__keySetComplete, enumerable: false, writable: true });
|
|
1198
1475
|
}
|
|
1199
1476
|
|
|
1477
|
+
function addToIndexArrayMap(map, key, value) {
|
|
1478
|
+
let bucket = map.get(key);
|
|
1479
|
+
if (!bucket) {
|
|
1480
|
+
bucket = [];
|
|
1481
|
+
map.set(key, bucket);
|
|
1482
|
+
}
|
|
1483
|
+
bucket.push(value);
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1200
1486
|
function indexFact(facts, tr, idx, addKeySet = true) {
|
|
1201
|
-
const sk =
|
|
1202
|
-
const ok =
|
|
1487
|
+
const sk = termLookupKey(tr.s);
|
|
1488
|
+
const ok = termLookupKey(tr.o);
|
|
1203
1489
|
let pkForKey = null;
|
|
1204
1490
|
|
|
1205
1491
|
if (tr.p instanceof Iri) {
|
|
@@ -1220,12 +1506,9 @@ function indexFact(facts, tr, idx, addKeySet = true) {
|
|
|
1220
1506
|
ps = new Map();
|
|
1221
1507
|
facts.__byPS.set(pk, ps);
|
|
1222
1508
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
ps.set(sk, psb);
|
|
1227
|
-
}
|
|
1228
|
-
psb.push(idx);
|
|
1509
|
+
addToIndexArrayMap(ps, sk, idx);
|
|
1510
|
+
} else {
|
|
1511
|
+
addToIndexArrayMap(facts.__byPNonFastS, pk, idx);
|
|
1229
1512
|
}
|
|
1230
1513
|
|
|
1231
1514
|
if (ok !== null) {
|
|
@@ -1234,32 +1517,23 @@ function indexFact(facts, tr, idx, addKeySet = true) {
|
|
|
1234
1517
|
po = new Map();
|
|
1235
1518
|
facts.__byPO.set(pk, po);
|
|
1236
1519
|
}
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
po.set(ok, pob);
|
|
1241
|
-
}
|
|
1242
|
-
pob.push(idx);
|
|
1520
|
+
addToIndexArrayMap(po, ok, idx);
|
|
1521
|
+
} else {
|
|
1522
|
+
addToIndexArrayMap(facts.__byPNonFastO, pk, idx);
|
|
1243
1523
|
}
|
|
1244
|
-
} else {
|
|
1245
|
-
facts.
|
|
1524
|
+
} else if (tr.p instanceof Var) {
|
|
1525
|
+
facts.__varPred.push(idx);
|
|
1246
1526
|
|
|
1247
1527
|
if (sk !== null) {
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
facts.__wildPS.set(sk, psb);
|
|
1252
|
-
}
|
|
1253
|
-
psb.push(idx);
|
|
1528
|
+
addToIndexArrayMap(facts.__varPredPS, sk, idx);
|
|
1529
|
+
} else {
|
|
1530
|
+
facts.__varPredNonFastS.push(idx);
|
|
1254
1531
|
}
|
|
1255
1532
|
|
|
1256
1533
|
if (ok !== null) {
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
facts.__wildPO.set(ok, pob);
|
|
1261
|
-
}
|
|
1262
|
-
pob.push(idx);
|
|
1534
|
+
addToIndexArrayMap(facts.__varPredPO, ok, idx);
|
|
1535
|
+
} else {
|
|
1536
|
+
facts.__varPredNonFastO.push(idx);
|
|
1263
1537
|
}
|
|
1264
1538
|
}
|
|
1265
1539
|
|
|
@@ -1269,55 +1543,86 @@ function indexFact(facts, tr, idx, addKeySet = true) {
|
|
|
1269
1543
|
}
|
|
1270
1544
|
}
|
|
1271
1545
|
|
|
1546
|
+
function mergeIndexBuckets(primary, fallback) {
|
|
1547
|
+
const a = primary && primary.length ? primary : null;
|
|
1548
|
+
const b = fallback && fallback.length ? fallback : null;
|
|
1549
|
+
if (!a && !b) return EMPTY_FACT_INDEX_BUCKET;
|
|
1550
|
+
if (!a) return b;
|
|
1551
|
+
if (!b) return a;
|
|
1552
|
+
const out = new Array(a.length + b.length);
|
|
1553
|
+
for (let i = 0; i < a.length; i++) out[i] = a[i];
|
|
1554
|
+
for (let i = 0; i < b.length; i++) out[a.length + i] = b[i];
|
|
1555
|
+
return out;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
function selectPositionIndexedCandidates(all, exactByS, fallbackS, sk, exactByO, fallbackO, ok) {
|
|
1559
|
+
if (sk === null && ok === null) return all && all.length ? all : EMPTY_FACT_INDEX_BUCKET;
|
|
1560
|
+
|
|
1561
|
+
let sBucket = null;
|
|
1562
|
+
if (sk !== null) sBucket = mergeIndexBuckets(exactByS || null, fallbackS || null);
|
|
1563
|
+
|
|
1564
|
+
let oBucket = null;
|
|
1565
|
+
if (ok !== null) oBucket = mergeIndexBuckets(exactByO || null, fallbackO || null);
|
|
1566
|
+
|
|
1567
|
+
if (sk !== null && ok !== null) return sBucket.length <= oBucket.length ? sBucket : oBucket;
|
|
1568
|
+
return sk !== null ? sBucket : oBucket;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1272
1571
|
function candidateFacts(facts, goal) {
|
|
1273
1572
|
ensureFactIndexes(facts);
|
|
1274
1573
|
|
|
1275
1574
|
if (goal.p instanceof Iri) {
|
|
1276
1575
|
const pk = goal.p.__tid;
|
|
1277
1576
|
|
|
1278
|
-
const sk =
|
|
1279
|
-
const ok =
|
|
1577
|
+
const sk = termLookupKey(goal.s);
|
|
1578
|
+
const ok = termLookupKey(goal.o);
|
|
1280
1579
|
|
|
1281
|
-
/** @type {number[] | null} */
|
|
1282
1580
|
let byPS = null;
|
|
1283
1581
|
if (sk !== null) {
|
|
1284
1582
|
const ps = facts.__byPS.get(pk);
|
|
1285
1583
|
if (ps) byPS = ps.get(sk) || null;
|
|
1286
1584
|
}
|
|
1585
|
+
const byPNonFastS = sk !== null ? facts.__byPNonFastS.get(pk) || null : null;
|
|
1287
1586
|
|
|
1288
|
-
/** @type {number[] | null} */
|
|
1289
1587
|
let byPO = null;
|
|
1290
1588
|
if (ok !== null) {
|
|
1291
1589
|
const po = facts.__byPO.get(pk);
|
|
1292
1590
|
if (po) byPO = po.get(ok) || null;
|
|
1293
1591
|
}
|
|
1592
|
+
const byPNonFastO = ok !== null ? facts.__byPNonFastO.get(pk) || null : null;
|
|
1593
|
+
|
|
1594
|
+
const exact = selectPositionIndexedCandidates(
|
|
1595
|
+
facts.__byPred.get(pk) || null,
|
|
1596
|
+
byPS,
|
|
1597
|
+
byPNonFastS,
|
|
1598
|
+
sk,
|
|
1599
|
+
byPO,
|
|
1600
|
+
byPNonFastO,
|
|
1601
|
+
ok,
|
|
1602
|
+
);
|
|
1294
1603
|
|
|
1295
|
-
let
|
|
1296
|
-
if (
|
|
1297
|
-
else if (byPS) exact = byPS;
|
|
1298
|
-
else if (byPO) exact = byPO;
|
|
1299
|
-
else exact = facts.__byPred.get(pk) || null;
|
|
1300
|
-
|
|
1301
|
-
/** @type {number[] | null} */
|
|
1302
|
-
let wildPS = null;
|
|
1303
|
-
if (sk !== null) wildPS = facts.__wildPS.get(sk) || null;
|
|
1604
|
+
let varPredPS = null;
|
|
1605
|
+
if (sk !== null) varPredPS = facts.__varPredPS.get(sk) || null;
|
|
1304
1606
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
if (ok !== null) wildPO = facts.__wildPO.get(ok) || null;
|
|
1607
|
+
let varPredPO = null;
|
|
1608
|
+
if (ok !== null) varPredPO = facts.__varPredPO.get(ok) || null;
|
|
1308
1609
|
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1610
|
+
const wild = selectPositionIndexedCandidates(
|
|
1611
|
+
facts.__varPred,
|
|
1612
|
+
varPredPS,
|
|
1613
|
+
sk !== null ? facts.__varPredNonFastS : null,
|
|
1614
|
+
sk,
|
|
1615
|
+
varPredPO,
|
|
1616
|
+
ok !== null ? facts.__varPredNonFastO : null,
|
|
1617
|
+
ok,
|
|
1618
|
+
);
|
|
1314
1619
|
|
|
1315
1620
|
return {
|
|
1316
|
-
exact
|
|
1317
|
-
wild
|
|
1318
|
-
exactLen: exact
|
|
1319
|
-
wildLen: wild
|
|
1320
|
-
totalLen:
|
|
1621
|
+
exact,
|
|
1622
|
+
wild,
|
|
1623
|
+
exactLen: exact.length,
|
|
1624
|
+
wildLen: wild.length,
|
|
1625
|
+
totalLen: exact.length + wild.length,
|
|
1321
1626
|
};
|
|
1322
1627
|
}
|
|
1323
1628
|
|
|
@@ -1335,20 +1640,28 @@ function hasFactIndexed(facts, tr) {
|
|
|
1335
1640
|
|
|
1336
1641
|
if (tr.p instanceof Iri) {
|
|
1337
1642
|
const pk = tr.p.__tid;
|
|
1643
|
+
const sk = termLookupKey(tr.s);
|
|
1644
|
+
let best = null;
|
|
1645
|
+
|
|
1646
|
+
if (sk !== null) {
|
|
1647
|
+
const ps = facts.__byPS.get(pk);
|
|
1648
|
+
if (!ps) return false;
|
|
1649
|
+
const psb = ps.get(sk);
|
|
1650
|
+
if (!psb || psb.length === 0) return false;
|
|
1651
|
+
best = psb;
|
|
1652
|
+
}
|
|
1338
1653
|
|
|
1339
|
-
const ok =
|
|
1654
|
+
const ok = termLookupKey(tr.o);
|
|
1340
1655
|
if (ok !== null) {
|
|
1341
1656
|
const po = facts.__byPO.get(pk);
|
|
1342
|
-
if (po)
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
// triples as duplicates modulo blank renaming, or you'll incorrectly
|
|
1347
|
-
// drop facts like: _:sk_0 :x 8.0 (because _:b8 :x 8.0 exists).
|
|
1348
|
-
return pob.some((i) => triplesEqual(facts[i], tr));
|
|
1349
|
-
}
|
|
1657
|
+
if (!po) return false;
|
|
1658
|
+
const pob = po.get(ok);
|
|
1659
|
+
if (!pob || pob.length === 0) return false;
|
|
1660
|
+
if (!best || pob.length < best.length) best = pob;
|
|
1350
1661
|
}
|
|
1351
1662
|
|
|
1663
|
+
if (best) return best.some((i) => triplesEqual(facts[i], tr));
|
|
1664
|
+
|
|
1352
1665
|
const pb = facts.__byPred.get(pk) || [];
|
|
1353
1666
|
return pb.some((i) => triplesEqual(facts[i], tr));
|
|
1354
1667
|
}
|
|
@@ -1457,11 +1770,25 @@ function mergeSinglePremiseAgendaBuckets() {
|
|
|
1457
1770
|
return out;
|
|
1458
1771
|
}
|
|
1459
1772
|
|
|
1773
|
+
function termContainsVarForAgenda(t) {
|
|
1774
|
+
if (t instanceof Var) return true;
|
|
1775
|
+
if (t instanceof ListTerm) return t.elems.some(termContainsVarForAgenda);
|
|
1776
|
+
if (t instanceof OpenListTerm) return true;
|
|
1777
|
+
if (t instanceof GraphTerm)
|
|
1778
|
+
return t.triples.some(
|
|
1779
|
+
(tr) =>
|
|
1780
|
+
termContainsVarForAgenda(tr.s) || termContainsVarForAgenda(tr.p) || termContainsVarForAgenda(tr.o),
|
|
1781
|
+
);
|
|
1782
|
+
return false;
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1460
1785
|
function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
|
|
1461
1786
|
const index = {
|
|
1462
1787
|
byPred: new Map(),
|
|
1788
|
+
byPredAll: new Map(),
|
|
1463
1789
|
byPS: new Map(),
|
|
1464
1790
|
byPO: new Map(),
|
|
1791
|
+
allIriPred: [],
|
|
1465
1792
|
wildPred: [],
|
|
1466
1793
|
wildPS: new Map(),
|
|
1467
1794
|
wildPO: new Map(),
|
|
@@ -1483,8 +1810,8 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
|
|
|
1483
1810
|
if (!isSinglePremiseAgendaRuleSafe(r, backRules)) continue;
|
|
1484
1811
|
|
|
1485
1812
|
const goal = r.premise[0];
|
|
1486
|
-
const goalSKey =
|
|
1487
|
-
const goalOKey =
|
|
1813
|
+
const goalSKey = termLookupKey(goal.s);
|
|
1814
|
+
const goalOKey = termLookupKey(goal.o);
|
|
1488
1815
|
const fastSubjectVar = goal.p instanceof Iri && goal.s instanceof Var && goalOKey !== null ? goal.s.name : null;
|
|
1489
1816
|
const fastObjectVar = goal.p instanceof Iri && goal.o instanceof Var && goalSKey !== null ? goal.o.name : null;
|
|
1490
1817
|
const entry = {
|
|
@@ -1494,7 +1821,7 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
|
|
|
1494
1821
|
goalPredTid: goal.p instanceof Iri ? goal.p.__tid : null,
|
|
1495
1822
|
goalSKey,
|
|
1496
1823
|
goalOKey,
|
|
1497
|
-
needsSkipCheck: !!r.__needsForwardSkipCheck,
|
|
1824
|
+
needsSkipCheck: !!(r.__needsForwardSkipCheck || (r.__scopedStratumInfo && r.__scopedStratumInfo.needsSnap)),
|
|
1498
1825
|
fastSubjectVar,
|
|
1499
1826
|
fastObjectVar,
|
|
1500
1827
|
};
|
|
@@ -1503,6 +1830,8 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
|
|
|
1503
1830
|
index.size += 1;
|
|
1504
1831
|
|
|
1505
1832
|
if (entry.goalPredTid !== null) {
|
|
1833
|
+
addToMapArray(index.byPredAll, entry.goalPredTid, entry);
|
|
1834
|
+
index.allIriPred.push(entry);
|
|
1506
1835
|
if (entry.goalSKey === null && entry.goalOKey === null) addToMapArray(index.byPred, entry.goalPredTid, entry);
|
|
1507
1836
|
if (entry.goalSKey !== null) {
|
|
1508
1837
|
let ps = index.byPS.get(entry.goalPredTid);
|
|
@@ -1533,25 +1862,37 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
|
|
|
1533
1862
|
function getSinglePremiseAgendaCandidates(index, fact) {
|
|
1534
1863
|
if (!index || index.size === 0) return null;
|
|
1535
1864
|
|
|
1536
|
-
const sk =
|
|
1537
|
-
const ok =
|
|
1865
|
+
const sk = termLookupKey(fact.s);
|
|
1866
|
+
const ok = termLookupKey(fact.o);
|
|
1538
1867
|
|
|
1539
1868
|
let exact = null;
|
|
1540
1869
|
if (fact.p instanceof Iri) {
|
|
1541
1870
|
const pk = fact.p.__tid;
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1871
|
+
if ((sk === null && termContainsVarForAgenda(fact.s)) || (ok === null && termContainsVarForAgenda(fact.o))) {
|
|
1872
|
+
// A fact with a variable-bearing subject/object (most importantly a
|
|
1873
|
+
// top-level variable fact such as `?S :p ?O.`) can match rules whose
|
|
1874
|
+
// premise is fixed in that position. The ordinary `(p,s)` / `(p,o)` lookup
|
|
1875
|
+
// would miss those rules, so fall back to all agenda-indexed rules for
|
|
1876
|
+
// this predicate. Do not do this merely for non-fast quoted formulas:
|
|
1877
|
+
// they are not wildcards, and broad fallback would over-fire rules that
|
|
1878
|
+
// rely on protected blank-node/formula unification semantics.
|
|
1879
|
+
exact = index.byPredAll.get(pk) || null;
|
|
1880
|
+
} else {
|
|
1881
|
+
const byPred = index.byPred.get(pk) || null;
|
|
1882
|
+
let byPS = null;
|
|
1545
1883
|
const ps = index.byPS.get(pk);
|
|
1546
1884
|
if (ps) byPS = ps.get(sk) || null;
|
|
1547
|
-
|
|
1548
|
-
let byPO = null;
|
|
1549
|
-
if (ok !== null) {
|
|
1885
|
+
let byPO = null;
|
|
1550
1886
|
const po = index.byPO.get(pk);
|
|
1551
1887
|
if (po) byPO = po.get(ok) || null;
|
|
1552
|
-
}
|
|
1553
1888
|
|
|
1554
|
-
|
|
1889
|
+
exact = mergeSinglePremiseAgendaBuckets(byPred, byPS, byPO);
|
|
1890
|
+
}
|
|
1891
|
+
} else if (fact.p instanceof Var) {
|
|
1892
|
+
// A variable-predicate fact can match any IRI-predicate agenda rule.
|
|
1893
|
+
// This is deliberately broad and relies on final unification below; such
|
|
1894
|
+
// facts are uncommon and correctness matters more than over-indexing them.
|
|
1895
|
+
exact = index.allIriPred.length ? index.allIriPred : null;
|
|
1555
1896
|
}
|
|
1556
1897
|
|
|
1557
1898
|
const wildPred = index.wildPred.length ? index.wildPred : null;
|
|
@@ -2928,7 +3269,10 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
2928
3269
|
let scopedClosureLevel = 0;
|
|
2929
3270
|
|
|
2930
3271
|
// Scan known rules for the maximum requested closure priority in scoped log:* goals.
|
|
2931
|
-
let maxScopedClosurePriorityNeeded =
|
|
3272
|
+
let maxScopedClosurePriorityNeeded = Math.max(
|
|
3273
|
+
__computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
|
|
3274
|
+
__computeForwardRuleScopedStrata(forwardRules),
|
|
3275
|
+
);
|
|
2932
3276
|
|
|
2933
3277
|
// Conservative fast-skip for forward rules that cannot possibly succeed
|
|
2934
3278
|
// until a scoped snapshot exists (or a given closure level is reached).
|
|
@@ -2982,12 +3326,16 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
2982
3326
|
// until a snapshot exists (and a certain closure level is reached).
|
|
2983
3327
|
// This prevents expensive proofs that will definitely fail in Phase A
|
|
2984
3328
|
// and in early closure levels.
|
|
2985
|
-
const info = r.__scopedSkipInfo;
|
|
3329
|
+
const info = r.__scopedStratumInfo || r.__scopedSkipInfo;
|
|
2986
3330
|
if (info && info.needsSnap) {
|
|
2987
3331
|
const snapHere = facts.__scopedSnapshot || null;
|
|
2988
3332
|
const lvlHere = (facts && typeof facts.__scopedClosureLevel === 'number' && facts.__scopedClosureLevel) || 0;
|
|
2989
3333
|
if (!snapHere) return true;
|
|
2990
|
-
if (
|
|
3334
|
+
if (info.exactLevel) {
|
|
3335
|
+
if (lvlHere !== info.requiredLevel) return true;
|
|
3336
|
+
} else if (lvlHere < info.requiredLevel) {
|
|
3337
|
+
return true;
|
|
3338
|
+
}
|
|
2991
3339
|
}
|
|
2992
3340
|
|
|
2993
3341
|
// Optimization: if the rule head is **structurally ground** (no vars anywhere, even inside
|
|
@@ -3187,6 +3535,10 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3187
3535
|
|
|
3188
3536
|
const outcome = __emitForwardRuleSolution(r, entry.ruleIndex, s);
|
|
3189
3537
|
if (outcome.rulesChanged) {
|
|
3538
|
+
maxScopedClosurePriorityNeeded = Math.max(
|
|
3539
|
+
__computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
|
|
3540
|
+
__computeForwardRuleScopedStrata(forwardRules),
|
|
3541
|
+
);
|
|
3190
3542
|
agendaIndex = makeSinglePremiseAgendaIndex(forwardRules, backRules);
|
|
3191
3543
|
agendaCursor = 0;
|
|
3192
3544
|
}
|
|
@@ -3200,7 +3552,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3200
3552
|
for (let i = 0; i < forwardRules.length; i++) {
|
|
3201
3553
|
const r = forwardRules[i];
|
|
3202
3554
|
if (agendaIndex.indexed.has(r)) continue;
|
|
3203
|
-
if (r.__needsForwardSkipCheck && __skipForwardRuleNow(r)) continue;
|
|
3555
|
+
if ((r.__needsForwardSkipCheck || (r.__scopedStratumInfo && r.__scopedStratumInfo.needsSnap)) && __skipForwardRuleNow(r)) continue;
|
|
3204
3556
|
|
|
3205
3557
|
const headIsStrictGround = r.__headIsStrictGround;
|
|
3206
3558
|
const maxSols = r.isFuse || headIsStrictGround ? 1 : undefined;
|
|
@@ -3221,6 +3573,10 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3221
3573
|
for (const s of sols) {
|
|
3222
3574
|
const outcome = __emitForwardRuleSolution(r, i, s);
|
|
3223
3575
|
if (outcome.rulesChanged) {
|
|
3576
|
+
maxScopedClosurePriorityNeeded = Math.max(
|
|
3577
|
+
__computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
|
|
3578
|
+
__computeForwardRuleScopedStrata(forwardRules),
|
|
3579
|
+
);
|
|
3224
3580
|
agendaIndex = makeSinglePremiseAgendaIndex(forwardRules, backRules);
|
|
3225
3581
|
agendaCursor = 0;
|
|
3226
3582
|
}
|
|
@@ -3248,8 +3604,8 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3248
3604
|
// Rules may have been added dynamically (rule-producing triples), possibly
|
|
3249
3605
|
// introducing scoped builtins and/or higher closure priorities.
|
|
3250
3606
|
maxScopedClosurePriorityNeeded = Math.max(
|
|
3251
|
-
maxScopedClosurePriorityNeeded,
|
|
3252
3607
|
__computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
|
|
3608
|
+
__computeForwardRuleScopedStrata(forwardRules),
|
|
3253
3609
|
);
|
|
3254
3610
|
|
|
3255
3611
|
// If there are no scoped builtins in the entire program, Phase B is pure
|
|
@@ -3267,8 +3623,8 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3267
3623
|
|
|
3268
3624
|
// Phase B can also derive rule-producing triples.
|
|
3269
3625
|
maxScopedClosurePriorityNeeded = Math.max(
|
|
3270
|
-
maxScopedClosurePriorityNeeded,
|
|
3271
3626
|
__computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
|
|
3627
|
+
__computeForwardRuleScopedStrata(forwardRules),
|
|
3272
3628
|
);
|
|
3273
3629
|
|
|
3274
3630
|
if (!changedA && !changedB && scopedClosureLevel >= maxScopedClosurePriorityNeeded) break;
|