eyeling 1.25.4 → 1.26.0
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 +8 -8
- package/dist/browser/eyeling.browser.js +788 -32
- package/examples/output/socrates.n3 +0 -1
- package/examples/proof/age.n3 +31 -0
- package/examples/proof/socrates.n3 +19 -0
- package/examples/proof/witch.n3 +120 -0
- package/examples/socrates.n3 +0 -3
- package/eyeling.js +788 -32
- package/index.d.ts +1 -0
- package/index.js +16 -13
- package/lib/cli.js +10 -4
- package/lib/engine.js +402 -13
- package/lib/entry.js +1 -0
- package/lib/explain.js +280 -2
- package/lib/multisource.js +74 -7
- package/lib/parser.js +20 -6
- package/package.json +1 -1
- package/test/api.test.js +26 -6
- package/tools/bundle.js +3 -0
- package/examples/library-and-path.n3 +0 -499
- package/examples/output/library-and-path.n3 +0 -33
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -19,21 +19,24 @@ function reason(opt = {}, input = '') {
|
|
|
19
19
|
|
|
20
20
|
const args = [];
|
|
21
21
|
|
|
22
|
-
// default: proof
|
|
23
|
-
// set {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
// default: proof output OFF for API output (machine-friendly)
|
|
23
|
+
// set { proof: true } to include N3 proof explanations.
|
|
24
|
+
// proofComments/noProofComments are accepted as legacy aliases.
|
|
25
|
+
const proofSpecified =
|
|
26
|
+
typeof opt.proof === 'boolean' || typeof opt.proofComments === 'boolean' || typeof opt.noProofComments === 'boolean';
|
|
27
|
+
|
|
28
|
+
const proof =
|
|
29
|
+
typeof opt.proof === 'boolean'
|
|
30
|
+
? opt.proof
|
|
31
|
+
: typeof opt.proofComments === 'boolean'
|
|
32
|
+
? opt.proofComments
|
|
33
|
+
: typeof opt.noProofComments === 'boolean'
|
|
34
|
+
? !opt.noProofComments
|
|
35
|
+
: false;
|
|
32
36
|
|
|
33
37
|
// Only pass a flag when the caller explicitly asked.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (proofComments) args.push('--proof-comments');
|
|
38
|
+
if (proofSpecified) {
|
|
39
|
+
if (proof) args.push('--proof');
|
|
37
40
|
else args.push('--no-proof-comments');
|
|
38
41
|
}
|
|
39
42
|
|
package/lib/cli.js
CHANGED
|
@@ -106,7 +106,7 @@ function main() {
|
|
|
106
106
|
` -d, --deterministic-skolem Make log:skolem stable across reasoning runs.\n` +
|
|
107
107
|
` -e, --enforce-https Rewrite http:// IRIs to https:// for log dereferencing builtins.\n` +
|
|
108
108
|
` -h, --help Show this help and exit.\n` +
|
|
109
|
-
` -p, --proof
|
|
109
|
+
` -p, --proof Enable proof explanations.\n` +
|
|
110
110
|
` -r, --rdf Enable RDF/TriG input/output compatibility.\n` +
|
|
111
111
|
` -s, --super-restricted Disable all builtins except => and <=.\n` +
|
|
112
112
|
` -t, --stream Stream derived triples as soon as they are derived.\n` +
|
|
@@ -161,8 +161,8 @@ function main() {
|
|
|
161
161
|
if (typeof engine.setDeterministicSkolemEnabled === 'function') engine.setDeterministicSkolemEnabled(true);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
// --proof
|
|
165
|
-
if (argv.includes('--proof-comments') || argv.includes('-p')) {
|
|
164
|
+
// --proof / -p: enable proof explanations as N3 proof graphs
|
|
165
|
+
if (argv.includes('--proof') || argv.includes('--proof-comments') || argv.includes('-p')) {
|
|
166
166
|
engine.setProofCommentsEnabled(true);
|
|
167
167
|
}
|
|
168
168
|
|
|
@@ -211,6 +211,7 @@ function main() {
|
|
|
211
211
|
baseIri: __sourceLabelToBaseIri(sourceLabel),
|
|
212
212
|
label: sourceLabel,
|
|
213
213
|
collectUsedPrefixes: streamMode,
|
|
214
|
+
collectSourceLocations: engine.getProofCommentsEnabled(),
|
|
214
215
|
keepSourceArtifacts: false,
|
|
215
216
|
rdf: rdfMode,
|
|
216
217
|
}),
|
|
@@ -301,7 +302,7 @@ function main() {
|
|
|
301
302
|
const hasQueries = Array.isArray(qrules) && qrules.length;
|
|
302
303
|
const mayAutoRenderOutputStrings = programMayProduceOutputStrings(triples, frules, qrules);
|
|
303
304
|
|
|
304
|
-
if (streamMode && !hasQueries && !mayAutoRenderOutputStrings) {
|
|
305
|
+
if (streamMode && !hasQueries && !mayAutoRenderOutputStrings && !engine.getProofCommentsEnabled()) {
|
|
305
306
|
const usedInInput = mergedDocument.usedPrefixes instanceof Set ? new Set(mergedDocument.usedPrefixes) : new Set();
|
|
306
307
|
const outPrefixes = restrictPrefixEnv(prefixes, usedInInput);
|
|
307
308
|
|
|
@@ -366,6 +367,11 @@ function main() {
|
|
|
366
367
|
return;
|
|
367
368
|
}
|
|
368
369
|
|
|
370
|
+
if (engine.getProofCommentsEnabled()) {
|
|
371
|
+
process.stdout.write(engine.renderProofDocument(outDerived, derived, triples, prefixes, brules));
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
369
375
|
let bodyText = '';
|
|
370
376
|
if (rdfMode && !engine.getProofCommentsEnabled()) {
|
|
371
377
|
bodyText = outTriples.map((tr) => engine.tripleToRdfCompatible(tr, prefixes)).join('\n');
|
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;
|
|
@@ -628,9 +780,11 @@ const { evalBuiltin, isBuiltinPred } = makeBuiltins({
|
|
|
628
780
|
});
|
|
629
781
|
|
|
630
782
|
// Initialize proof/output helpers (implemented in lib/explain.js).
|
|
631
|
-
const { printExplanation, collectOutputStringsFromFacts } = makeExplain({
|
|
783
|
+
const { printExplanation, renderProofDocument, collectOutputStringsFromFacts } = makeExplain({
|
|
632
784
|
applySubstTerm,
|
|
633
785
|
skolemKeyFromTerm,
|
|
786
|
+
isBuiltinPred,
|
|
787
|
+
findBackwardProofForGoal,
|
|
634
788
|
});
|
|
635
789
|
|
|
636
790
|
function skolemizeTermForHeadBlanks(t, headBlankLabels, mapping, skCounter, firingKey, globalMap) {
|
|
@@ -1669,7 +1823,7 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
|
|
|
1669
1823
|
goalPredTid: goal.p instanceof Iri ? goal.p.__tid : null,
|
|
1670
1824
|
goalSKey,
|
|
1671
1825
|
goalOKey,
|
|
1672
|
-
needsSkipCheck: !!r.__needsForwardSkipCheck,
|
|
1826
|
+
needsSkipCheck: !!(r.__needsForwardSkipCheck || (r.__scopedStratumInfo && r.__scopedStratumInfo.needsSnap)),
|
|
1673
1827
|
fastSubjectVar,
|
|
1674
1828
|
fastObjectVar,
|
|
1675
1829
|
};
|
|
@@ -2281,6 +2435,217 @@ function unifyTriple(pat, fact, subst) {
|
|
|
2281
2435
|
return s3;
|
|
2282
2436
|
}
|
|
2283
2437
|
|
|
2438
|
+
|
|
2439
|
+
function __copyProofSourceMetadata(from, to) {
|
|
2440
|
+
if (!from || !to) return to;
|
|
2441
|
+
for (const key of ['__sourceOffset', '__source']) {
|
|
2442
|
+
if (hasOwn.call(from, key)) {
|
|
2443
|
+
Object.defineProperty(to, key, {
|
|
2444
|
+
value: from[key],
|
|
2445
|
+
enumerable: false,
|
|
2446
|
+
writable: false,
|
|
2447
|
+
configurable: true,
|
|
2448
|
+
});
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
return to;
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
function __standardizeRuleForProof(rule, gen) {
|
|
2455
|
+
const proofVarNames = Object.create(null);
|
|
2456
|
+
|
|
2457
|
+
function renameTerm(t, vmapName, vmapVar, genArr) {
|
|
2458
|
+
if (t instanceof Var) {
|
|
2459
|
+
if (!hasOwn.call(vmapVar, t.name)) {
|
|
2460
|
+
const name = `${t.name}__proof_${genArr[0]}`;
|
|
2461
|
+
genArr[0] += 1;
|
|
2462
|
+
vmapName[t.name] = name;
|
|
2463
|
+
vmapVar[t.name] = new Var(name);
|
|
2464
|
+
proofVarNames[name] = t.name;
|
|
2465
|
+
}
|
|
2466
|
+
return vmapVar[t.name];
|
|
2467
|
+
}
|
|
2468
|
+
if (t instanceof ListTerm) {
|
|
2469
|
+
let changed = false;
|
|
2470
|
+
const elems2 = t.elems.map((e) => {
|
|
2471
|
+
const e2 = renameTerm(e, vmapName, vmapVar, genArr);
|
|
2472
|
+
if (e2 !== e) changed = true;
|
|
2473
|
+
return e2;
|
|
2474
|
+
});
|
|
2475
|
+
return changed ? new ListTerm(elems2) : t;
|
|
2476
|
+
}
|
|
2477
|
+
if (t instanceof OpenListTerm) {
|
|
2478
|
+
let changed = false;
|
|
2479
|
+
const newXs = t.prefix.map((e) => {
|
|
2480
|
+
const e2 = renameTerm(e, vmapName, vmapVar, genArr);
|
|
2481
|
+
if (e2 !== e) changed = true;
|
|
2482
|
+
return e2;
|
|
2483
|
+
});
|
|
2484
|
+
if (!hasOwn.call(vmapName, t.tailVar)) {
|
|
2485
|
+
const name = `${t.tailVar}__proof_${genArr[0]}`;
|
|
2486
|
+
genArr[0] += 1;
|
|
2487
|
+
vmapName[t.tailVar] = name;
|
|
2488
|
+
vmapVar[t.tailVar] = new Var(name);
|
|
2489
|
+
proofVarNames[name] = t.tailVar;
|
|
2490
|
+
}
|
|
2491
|
+
const newTail = vmapName[t.tailVar];
|
|
2492
|
+
if (newTail !== t.tailVar) changed = true;
|
|
2493
|
+
return changed ? new OpenListTerm(newXs, newTail) : t;
|
|
2494
|
+
}
|
|
2495
|
+
if (t instanceof GraphTerm) {
|
|
2496
|
+
let changed = false;
|
|
2497
|
+
const triples2 = t.triples.map((tr) => {
|
|
2498
|
+
const s2 = renameTerm(tr.s, vmapName, vmapVar, genArr);
|
|
2499
|
+
const p2 = renameTerm(tr.p, vmapName, vmapVar, genArr);
|
|
2500
|
+
const o2 = renameTerm(tr.o, vmapName, vmapVar, genArr);
|
|
2501
|
+
if (s2 !== tr.s || p2 !== tr.p || o2 !== tr.o) changed = true;
|
|
2502
|
+
return s2 === tr.s && p2 === tr.p && o2 === tr.o ? tr : new Triple(s2, p2, o2);
|
|
2503
|
+
});
|
|
2504
|
+
return changed ? copyQuotedGraphMetadata(t, new GraphTerm(triples2)) : t;
|
|
2505
|
+
}
|
|
2506
|
+
return t;
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
const vmapName = Object.create(null);
|
|
2510
|
+
const vmapVar = Object.create(null);
|
|
2511
|
+
const premise = rule.premise.map((tr) => {
|
|
2512
|
+
const s2 = renameTerm(tr.s, vmapName, vmapVar, gen);
|
|
2513
|
+
const p2 = renameTerm(tr.p, vmapName, vmapVar, gen);
|
|
2514
|
+
const o2 = renameTerm(tr.o, vmapName, vmapVar, gen);
|
|
2515
|
+
return s2 === tr.s && p2 === tr.p && o2 === tr.o ? tr : new Triple(s2, p2, o2);
|
|
2516
|
+
});
|
|
2517
|
+
const conclusion = rule.conclusion.map((tr) => {
|
|
2518
|
+
const s2 = renameTerm(tr.s, vmapName, vmapVar, gen);
|
|
2519
|
+
const p2 = renameTerm(tr.p, vmapName, vmapVar, gen);
|
|
2520
|
+
const o2 = renameTerm(tr.o, vmapName, vmapVar, gen);
|
|
2521
|
+
return s2 === tr.s && p2 === tr.p && o2 === tr.o ? tr : new Triple(s2, p2, o2);
|
|
2522
|
+
});
|
|
2523
|
+
const out = new Rule(premise, conclusion, rule.isForward, rule.isFuse, rule.headBlankLabels);
|
|
2524
|
+
Object.defineProperty(out, '__proofVarSourceNames', {
|
|
2525
|
+
value: proofVarNames,
|
|
2526
|
+
enumerable: false,
|
|
2527
|
+
writable: false,
|
|
2528
|
+
configurable: true,
|
|
2529
|
+
});
|
|
2530
|
+
return __copyProofSourceMetadata(rule, out);
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
function __mergeProofSubst(base, delta) {
|
|
2534
|
+
const out = __cloneSubst(base);
|
|
2535
|
+
for (const k in delta || {}) {
|
|
2536
|
+
if (!hasOwn.call(delta, k)) continue;
|
|
2537
|
+
const v = applySubstTerm(delta[k], out);
|
|
2538
|
+
if (hasOwn.call(out, k)) {
|
|
2539
|
+
if (!termsEqual(applySubstTerm(out[k], out), v)) return null;
|
|
2540
|
+
} else {
|
|
2541
|
+
out[k] = v;
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
return out;
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
function __sourceSortKey(obj) {
|
|
2548
|
+
const src = obj && obj.__source;
|
|
2549
|
+
const label = src && typeof src.label === 'string' ? src.label : '';
|
|
2550
|
+
const line = src && Number.isInteger(src.line) ? src.line : 0;
|
|
2551
|
+
return `${label}\t${line}`;
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
function __candidateFactsForProof(facts, goal) {
|
|
2555
|
+
if (!Array.isArray(facts)) return [];
|
|
2556
|
+
ensureFactIndexes(facts);
|
|
2557
|
+
const candidates = candidateFacts(facts, goal);
|
|
2558
|
+
if (!candidates) return facts;
|
|
2559
|
+
const out = [];
|
|
2560
|
+
for (let i = 0; i < candidates.exactLen; i++) out.push(facts[candidates.exact[i]]);
|
|
2561
|
+
for (let i = 0; i < candidates.wildLen; i++) out.push(facts[candidates.wild[i]]);
|
|
2562
|
+
return out;
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
function findBackwardProofForGoal(goal, facts, backRules, opts = {}) {
|
|
2566
|
+
const maxDepth = Number.isInteger(opts.maxDepth) && opts.maxDepth > 0 ? opts.maxDepth : 64;
|
|
2567
|
+
const varGen = [0];
|
|
2568
|
+
|
|
2569
|
+
function proofKey(tr) {
|
|
2570
|
+
return `${skolemKeyFromTerm(tr.s)}\t${skolemKeyFromTerm(tr.p)}\t${skolemKeyFromTerm(tr.o)}`;
|
|
2571
|
+
}
|
|
2572
|
+
|
|
2573
|
+
function proveGoal(goal0, subst, depth, visited) {
|
|
2574
|
+
if (depth > maxDepth) return [];
|
|
2575
|
+
const g = applySubstTriple(goal0, subst);
|
|
2576
|
+
const key = proofKey(g);
|
|
2577
|
+
if (visited.has(key)) return [];
|
|
2578
|
+
|
|
2579
|
+
if (isBuiltinPred(g.p)) {
|
|
2580
|
+
const builtinGoalForEval = g.p instanceof Iri && g.p.value === LOG_NS + 'rawType' ? goal0 : g;
|
|
2581
|
+
const builtinSubstForEval = g.p instanceof Iri && g.p.value === LOG_NS + 'rawType' ? subst : __emptySubst();
|
|
2582
|
+
const deltas = evalBuiltin(builtinGoalForEval, builtinSubstForEval, facts, backRules, depth, varGen, 1) || [];
|
|
2583
|
+
const out = [];
|
|
2584
|
+
for (const delta of deltas) {
|
|
2585
|
+
const subst2 = __mergeProofSubst(subst, delta);
|
|
2586
|
+
if (subst2 === null) continue;
|
|
2587
|
+
const fact = applySubstTriple(g, subst2);
|
|
2588
|
+
out.push({ subst: subst2, proof: { kind: 'builtin', fact, builtin: fact.p } });
|
|
2589
|
+
break;
|
|
2590
|
+
}
|
|
2591
|
+
return out;
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
const factResults = [];
|
|
2595
|
+
for (const f of __candidateFactsForProof(facts, g)) {
|
|
2596
|
+
const subst2 = unifyTriple(g, f, subst);
|
|
2597
|
+
if (subst2 === null) continue;
|
|
2598
|
+
factResults.push({ subst: subst2, proof: { kind: 'fact', fact: applySubstTriple(f, subst2), source: f.__source } });
|
|
2599
|
+
break;
|
|
2600
|
+
}
|
|
2601
|
+
if (factResults.length) return factResults;
|
|
2602
|
+
|
|
2603
|
+
if (!(g.p instanceof Iri)) return [];
|
|
2604
|
+
ensureBackRuleIndexes(backRules);
|
|
2605
|
+
const rules = (backRules.__byHeadPred.get(g.p.__tid) || []).concat(backRules.__wildHeadPred || []);
|
|
2606
|
+
rules.sort((a, b) => (__sourceSortKey(a) < __sourceSortKey(b) ? -1 : __sourceSortKey(a) > __sourceSortKey(b) ? 1 : 0));
|
|
2607
|
+
|
|
2608
|
+
const visited2 = new Set(visited);
|
|
2609
|
+
visited2.add(key);
|
|
2610
|
+
|
|
2611
|
+
for (const r of rules) {
|
|
2612
|
+
if (!r || !Array.isArray(r.conclusion) || r.conclusion.length !== 1) continue;
|
|
2613
|
+
const rStd = __standardizeRuleForProof(r, varGen);
|
|
2614
|
+
const head = rStd.conclusion[0];
|
|
2615
|
+
if (head.p instanceof Iri && head.p.__tid !== g.p.__tid) continue;
|
|
2616
|
+
const subst1 = unifyTriple(head, g, subst);
|
|
2617
|
+
if (subst1 === null) continue;
|
|
2618
|
+
const bodyProof = proveGoalList(rStd.premise || [], subst1, depth + 1, visited2);
|
|
2619
|
+
if (!bodyProof) continue;
|
|
2620
|
+
const fact = applySubstTriple(g, bodyProof.subst);
|
|
2621
|
+
const premises = (rStd.premise || []).map((prem) => applySubstTriple(prem, bodyProof.subst));
|
|
2622
|
+
const df = new DerivedFact(fact, rStd, premises, bodyProof.subst);
|
|
2623
|
+
return [{ subst: bodyProof.subst, proof: { kind: 'rule', df, children: bodyProof.proofs } }];
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2626
|
+
return [];
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
function proveGoalList(goals, subst, depth, visited) {
|
|
2630
|
+
let states = [{ subst, proofs: [] }];
|
|
2631
|
+
for (const goal1 of goals || []) {
|
|
2632
|
+
const next = [];
|
|
2633
|
+
for (const state of states) {
|
|
2634
|
+
const proofs = proveGoal(goal1, state.subst, depth, visited);
|
|
2635
|
+
for (const pr of proofs) next.push({ subst: pr.subst, proofs: state.proofs.concat([pr.proof]) });
|
|
2636
|
+
}
|
|
2637
|
+
if (!next.length) return null;
|
|
2638
|
+
// Keep proof reconstruction deterministic and cheap: one proof is enough
|
|
2639
|
+
// for an explanation of the already-derived enclosing fact.
|
|
2640
|
+
states = [next[0]];
|
|
2641
|
+
}
|
|
2642
|
+
return states[0] || { subst, proofs: [] };
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
const found = proveGoal(goal, __emptySubst(), 0, new Set());
|
|
2646
|
+
return found.length ? found[0].proof : null;
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2284
2649
|
// Strategy: when the substitution is "large" or search depth is high,
|
|
2285
2650
|
// keep only bindings that are still relevant to:
|
|
2286
2651
|
// - variables appearing in the remaining goals
|
|
@@ -3117,7 +3482,10 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3117
3482
|
let scopedClosureLevel = 0;
|
|
3118
3483
|
|
|
3119
3484
|
// Scan known rules for the maximum requested closure priority in scoped log:* goals.
|
|
3120
|
-
let maxScopedClosurePriorityNeeded =
|
|
3485
|
+
let maxScopedClosurePriorityNeeded = Math.max(
|
|
3486
|
+
__computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
|
|
3487
|
+
__computeForwardRuleScopedStrata(forwardRules),
|
|
3488
|
+
);
|
|
3121
3489
|
|
|
3122
3490
|
// Conservative fast-skip for forward rules that cannot possibly succeed
|
|
3123
3491
|
// until a scoped snapshot exists (or a given closure level is reached).
|
|
@@ -3171,12 +3539,16 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3171
3539
|
// until a snapshot exists (and a certain closure level is reached).
|
|
3172
3540
|
// This prevents expensive proofs that will definitely fail in Phase A
|
|
3173
3541
|
// and in early closure levels.
|
|
3174
|
-
const info = r.__scopedSkipInfo;
|
|
3542
|
+
const info = r.__scopedStratumInfo || r.__scopedSkipInfo;
|
|
3175
3543
|
if (info && info.needsSnap) {
|
|
3176
3544
|
const snapHere = facts.__scopedSnapshot || null;
|
|
3177
3545
|
const lvlHere = (facts && typeof facts.__scopedClosureLevel === 'number' && facts.__scopedClosureLevel) || 0;
|
|
3178
3546
|
if (!snapHere) return true;
|
|
3179
|
-
if (
|
|
3547
|
+
if (info.exactLevel) {
|
|
3548
|
+
if (lvlHere !== info.requiredLevel) return true;
|
|
3549
|
+
} else if (lvlHere < info.requiredLevel) {
|
|
3550
|
+
return true;
|
|
3551
|
+
}
|
|
3180
3552
|
}
|
|
3181
3553
|
|
|
3182
3554
|
// Optimization: if the rule head is **structurally ground** (no vars anywhere, even inside
|
|
@@ -3376,6 +3748,10 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3376
3748
|
|
|
3377
3749
|
const outcome = __emitForwardRuleSolution(r, entry.ruleIndex, s);
|
|
3378
3750
|
if (outcome.rulesChanged) {
|
|
3751
|
+
maxScopedClosurePriorityNeeded = Math.max(
|
|
3752
|
+
__computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
|
|
3753
|
+
__computeForwardRuleScopedStrata(forwardRules),
|
|
3754
|
+
);
|
|
3379
3755
|
agendaIndex = makeSinglePremiseAgendaIndex(forwardRules, backRules);
|
|
3380
3756
|
agendaCursor = 0;
|
|
3381
3757
|
}
|
|
@@ -3389,7 +3765,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3389
3765
|
for (let i = 0; i < forwardRules.length; i++) {
|
|
3390
3766
|
const r = forwardRules[i];
|
|
3391
3767
|
if (agendaIndex.indexed.has(r)) continue;
|
|
3392
|
-
if (r.__needsForwardSkipCheck && __skipForwardRuleNow(r)) continue;
|
|
3768
|
+
if ((r.__needsForwardSkipCheck || (r.__scopedStratumInfo && r.__scopedStratumInfo.needsSnap)) && __skipForwardRuleNow(r)) continue;
|
|
3393
3769
|
|
|
3394
3770
|
const headIsStrictGround = r.__headIsStrictGround;
|
|
3395
3771
|
const maxSols = r.isFuse || headIsStrictGround ? 1 : undefined;
|
|
@@ -3410,6 +3786,10 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3410
3786
|
for (const s of sols) {
|
|
3411
3787
|
const outcome = __emitForwardRuleSolution(r, i, s);
|
|
3412
3788
|
if (outcome.rulesChanged) {
|
|
3789
|
+
maxScopedClosurePriorityNeeded = Math.max(
|
|
3790
|
+
__computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
|
|
3791
|
+
__computeForwardRuleScopedStrata(forwardRules),
|
|
3792
|
+
);
|
|
3413
3793
|
agendaIndex = makeSinglePremiseAgendaIndex(forwardRules, backRules);
|
|
3414
3794
|
agendaCursor = 0;
|
|
3415
3795
|
}
|
|
@@ -3437,8 +3817,8 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3437
3817
|
// Rules may have been added dynamically (rule-producing triples), possibly
|
|
3438
3818
|
// introducing scoped builtins and/or higher closure priorities.
|
|
3439
3819
|
maxScopedClosurePriorityNeeded = Math.max(
|
|
3440
|
-
maxScopedClosurePriorityNeeded,
|
|
3441
3820
|
__computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
|
|
3821
|
+
__computeForwardRuleScopedStrata(forwardRules),
|
|
3442
3822
|
);
|
|
3443
3823
|
|
|
3444
3824
|
// If there are no scoped builtins in the entire program, Phase B is pure
|
|
@@ -3456,8 +3836,8 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3456
3836
|
|
|
3457
3837
|
// Phase B can also derive rule-producing triples.
|
|
3458
3838
|
maxScopedClosurePriorityNeeded = Math.max(
|
|
3459
|
-
maxScopedClosurePriorityNeeded,
|
|
3460
3839
|
__computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
|
|
3840
|
+
__computeForwardRuleScopedStrata(forwardRules),
|
|
3461
3841
|
);
|
|
3462
3842
|
|
|
3463
3843
|
if (!changedA && !changedB && scopedClosureLevel >= maxScopedClosurePriorityNeeded) break;
|
|
@@ -3780,11 +4160,19 @@ function reasonStream(input, opts = {}) {
|
|
|
3780
4160
|
? facts
|
|
3781
4161
|
: derived.map((d) => d.fact);
|
|
3782
4162
|
|
|
3783
|
-
const closureN3 =
|
|
3784
|
-
?
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
4163
|
+
const closureN3 = proof
|
|
4164
|
+
? renderProofDocument(
|
|
4165
|
+
Array.isArray(logQueryRules) && logQueryRules.length ? queryDerived : derived,
|
|
4166
|
+
derived,
|
|
4167
|
+
triples,
|
|
4168
|
+
prefixes,
|
|
4169
|
+
brules,
|
|
4170
|
+
).replace(/\n$/g, '')
|
|
4171
|
+
: useRdfCompatibility
|
|
4172
|
+
? closureTriples.map((t) => tripleToRdfCompatible(t, prefixes)).join('\n')
|
|
4173
|
+
: Array.isArray(logQueryRules) && logQueryRules.length
|
|
4174
|
+
? prettyPrintQueryTriples(closureTriples, prefixes)
|
|
4175
|
+
: closureTriples.map((t) => tripleToN3(t, prefixes)).join('\n');
|
|
3788
4176
|
|
|
3789
4177
|
const __out = {
|
|
3790
4178
|
prefixes,
|
|
@@ -3911,6 +4299,7 @@ module.exports = {
|
|
|
3911
4299
|
collectLogQueryConclusions,
|
|
3912
4300
|
forwardChainAndCollectLogQueryConclusions,
|
|
3913
4301
|
collectOutputStringsFromFacts,
|
|
4302
|
+
renderProofDocument,
|
|
3914
4303
|
main,
|
|
3915
4304
|
version,
|
|
3916
4305
|
N3SyntaxError,
|
package/lib/entry.js
CHANGED
|
@@ -32,6 +32,7 @@ module.exports = {
|
|
|
32
32
|
materializeRdfLists: engine.materializeRdfLists,
|
|
33
33
|
isGroundTriple: engine.isGroundTriple,
|
|
34
34
|
printExplanation: engine.printExplanation,
|
|
35
|
+
renderProofDocument: engine.renderProofDocument,
|
|
35
36
|
tripleToN3: engine.tripleToN3,
|
|
36
37
|
collectOutputStringsFromFacts: engine.collectOutputStringsFromFacts,
|
|
37
38
|
prettyPrintQueryTriples: engine.prettyPrintQueryTriples,
|