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
|
@@ -4721,7 +4721,7 @@ function main() {
|
|
|
4721
4721
|
` -d, --deterministic-skolem Make log:skolem stable across reasoning runs.\n` +
|
|
4722
4722
|
` -e, --enforce-https Rewrite http:// IRIs to https:// for log dereferencing builtins.\n` +
|
|
4723
4723
|
` -h, --help Show this help and exit.\n` +
|
|
4724
|
-
` -p, --proof
|
|
4724
|
+
` -p, --proof Enable proof explanations.\n` +
|
|
4725
4725
|
` -r, --rdf Enable RDF/TriG input/output compatibility.\n` +
|
|
4726
4726
|
` -s, --super-restricted Disable all builtins except => and <=.\n` +
|
|
4727
4727
|
` -t, --stream Stream derived triples as soon as they are derived.\n` +
|
|
@@ -4776,8 +4776,8 @@ function main() {
|
|
|
4776
4776
|
if (typeof engine.setDeterministicSkolemEnabled === 'function') engine.setDeterministicSkolemEnabled(true);
|
|
4777
4777
|
}
|
|
4778
4778
|
|
|
4779
|
-
// --proof
|
|
4780
|
-
if (argv.includes('--proof-comments') || argv.includes('-p')) {
|
|
4779
|
+
// --proof / -p: enable proof explanations as N3 proof graphs
|
|
4780
|
+
if (argv.includes('--proof') || argv.includes('--proof-comments') || argv.includes('-p')) {
|
|
4781
4781
|
engine.setProofCommentsEnabled(true);
|
|
4782
4782
|
}
|
|
4783
4783
|
|
|
@@ -4826,6 +4826,7 @@ function main() {
|
|
|
4826
4826
|
baseIri: __sourceLabelToBaseIri(sourceLabel),
|
|
4827
4827
|
label: sourceLabel,
|
|
4828
4828
|
collectUsedPrefixes: streamMode,
|
|
4829
|
+
collectSourceLocations: engine.getProofCommentsEnabled(),
|
|
4829
4830
|
keepSourceArtifacts: false,
|
|
4830
4831
|
rdf: rdfMode,
|
|
4831
4832
|
}),
|
|
@@ -4916,7 +4917,7 @@ function main() {
|
|
|
4916
4917
|
const hasQueries = Array.isArray(qrules) && qrules.length;
|
|
4917
4918
|
const mayAutoRenderOutputStrings = programMayProduceOutputStrings(triples, frules, qrules);
|
|
4918
4919
|
|
|
4919
|
-
if (streamMode && !hasQueries && !mayAutoRenderOutputStrings) {
|
|
4920
|
+
if (streamMode && !hasQueries && !mayAutoRenderOutputStrings && !engine.getProofCommentsEnabled()) {
|
|
4920
4921
|
const usedInInput = mergedDocument.usedPrefixes instanceof Set ? new Set(mergedDocument.usedPrefixes) : new Set();
|
|
4921
4922
|
const outPrefixes = restrictPrefixEnv(prefixes, usedInInput);
|
|
4922
4923
|
|
|
@@ -4981,6 +4982,11 @@ function main() {
|
|
|
4981
4982
|
return;
|
|
4982
4983
|
}
|
|
4983
4984
|
|
|
4985
|
+
if (engine.getProofCommentsEnabled()) {
|
|
4986
|
+
process.stdout.write(engine.renderProofDocument(outDerived, derived, triples, prefixes, brules));
|
|
4987
|
+
return;
|
|
4988
|
+
}
|
|
4989
|
+
|
|
4984
4990
|
let bodyText = '';
|
|
4985
4991
|
if (rdfMode && !engine.getProofCommentsEnabled()) {
|
|
4986
4992
|
bodyText = outTriples.map((tr) => engine.tripleToRdfCompatible(tr, prefixes)).join('\n');
|
|
@@ -5973,6 +5979,158 @@ function __varOccursElsewhereInPremise(premise, name, idx, field) {
|
|
|
5973
5979
|
return false;
|
|
5974
5980
|
}
|
|
5975
5981
|
|
|
5982
|
+
|
|
5983
|
+
function __scopedPriorityForTerm(t) {
|
|
5984
|
+
if (t instanceof GraphTerm) return 0;
|
|
5985
|
+
const p0 = __logNaturalPriorityFromTerm(t);
|
|
5986
|
+
return p0 !== null && p0 >= 1 ? p0 : 1;
|
|
5987
|
+
}
|
|
5988
|
+
|
|
5989
|
+
function __pushScopedQueryTriplesFromGraph(term, out) {
|
|
5990
|
+
if (!(term instanceof GraphTerm)) return;
|
|
5991
|
+
for (const tr of term.triples) out.push(tr);
|
|
5992
|
+
}
|
|
5993
|
+
|
|
5994
|
+
function __analyzeForwardRuleScopedUse(rule) {
|
|
5995
|
+
const out = {
|
|
5996
|
+
needsSnap: false,
|
|
5997
|
+
baseLevel: 0,
|
|
5998
|
+
queryTriples: [],
|
|
5999
|
+
hasScopedAggregate: false,
|
|
6000
|
+
};
|
|
6001
|
+
|
|
6002
|
+
if (!rule || !Array.isArray(rule.premise)) return out;
|
|
6003
|
+
|
|
6004
|
+
for (let i = 0; i < rule.premise.length; i++) {
|
|
6005
|
+
const tr = rule.premise[i];
|
|
6006
|
+
if (!(tr && tr.p instanceof Iri)) continue;
|
|
6007
|
+
const pv = tr.p.value;
|
|
6008
|
+
|
|
6009
|
+
if (pv === LOG_NS + 'includes' || pv === LOG_NS + 'notIncludes') {
|
|
6010
|
+
// Explicit quoted scopes are local formulas, not snapshots of the global closure.
|
|
6011
|
+
if (tr.s instanceof GraphTerm) continue;
|
|
6012
|
+
|
|
6013
|
+
// A scope variable that is bound by another premise may become an explicit GraphTerm.
|
|
6014
|
+
// Still, if it remains unbound at runtime the builtin treats it as priority 1.
|
|
6015
|
+
// We therefore record the scoped use, but only use quoted object triples for
|
|
6016
|
+
// dependency analysis; variables inside the quoted object do not bind the scope.
|
|
6017
|
+
out.needsSnap = true;
|
|
6018
|
+
out.baseLevel = Math.max(out.baseLevel, __scopedPriorityForTerm(tr.s));
|
|
6019
|
+
__pushScopedQueryTriplesFromGraph(tr.o, out.queryTriples);
|
|
6020
|
+
continue;
|
|
6021
|
+
}
|
|
6022
|
+
|
|
6023
|
+
if (pv === LOG_NS + 'collectAllIn') {
|
|
6024
|
+
if (tr.o instanceof GraphTerm) continue;
|
|
6025
|
+
|
|
6026
|
+
out.needsSnap = true;
|
|
6027
|
+
out.hasScopedAggregate = true;
|
|
6028
|
+
out.baseLevel = Math.max(out.baseLevel, __scopedPriorityForTerm(tr.o));
|
|
6029
|
+
if (tr.s instanceof ListTerm && tr.s.elems.length === 3) {
|
|
6030
|
+
__pushScopedQueryTriplesFromGraph(tr.s.elems[1], out.queryTriples);
|
|
6031
|
+
}
|
|
6032
|
+
continue;
|
|
6033
|
+
}
|
|
6034
|
+
|
|
6035
|
+
if (pv === LOG_NS + 'forAllIn') {
|
|
6036
|
+
if (tr.o instanceof GraphTerm) continue;
|
|
6037
|
+
|
|
6038
|
+
out.needsSnap = true;
|
|
6039
|
+
out.hasScopedAggregate = true;
|
|
6040
|
+
out.baseLevel = Math.max(out.baseLevel, __scopedPriorityForTerm(tr.o));
|
|
6041
|
+
if (tr.s instanceof ListTerm && tr.s.elems.length === 2) {
|
|
6042
|
+
__pushScopedQueryTriplesFromGraph(tr.s.elems[0], out.queryTriples);
|
|
6043
|
+
__pushScopedQueryTriplesFromGraph(tr.s.elems[1], out.queryTriples);
|
|
6044
|
+
}
|
|
6045
|
+
}
|
|
6046
|
+
}
|
|
6047
|
+
|
|
6048
|
+
return out;
|
|
6049
|
+
}
|
|
6050
|
+
|
|
6051
|
+
function __triplePatternsMayOverlap(a, b) {
|
|
6052
|
+
return unifyTriple(a, b, __emptySubst()) !== null || unifyTriple(b, a, __emptySubst()) !== null;
|
|
6053
|
+
}
|
|
6054
|
+
|
|
6055
|
+
function __ruleConclusionsMayFeedScopedQueries(producer, consumerMeta) {
|
|
6056
|
+
if (!consumerMeta || !consumerMeta.queryTriples || consumerMeta.queryTriples.length === 0) return false;
|
|
6057
|
+
|
|
6058
|
+
// Dynamic conclusions are only known after a proof solution. Conservatively
|
|
6059
|
+
// assume they can feed any scoped query if the producing rule is scoped.
|
|
6060
|
+
if (producer && producer.__dynamicConclusionTerm) return true;
|
|
6061
|
+
|
|
6062
|
+
if (!producer || !Array.isArray(producer.conclusion) || producer.conclusion.length === 0) return false;
|
|
6063
|
+
for (const q of consumerMeta.queryTriples) {
|
|
6064
|
+
for (const h of producer.conclusion) {
|
|
6065
|
+
if (__triplePatternsMayOverlap(q, h)) return true;
|
|
6066
|
+
}
|
|
6067
|
+
}
|
|
6068
|
+
return false;
|
|
6069
|
+
}
|
|
6070
|
+
|
|
6071
|
+
function __setForwardRuleScopedStratumInfo(rule, level) {
|
|
6072
|
+
const value =
|
|
6073
|
+
level > 0
|
|
6074
|
+
? { needsSnap: true, requiredLevel: level, exactLevel: true }
|
|
6075
|
+
: { needsSnap: false, requiredLevel: 0, exactLevel: false };
|
|
6076
|
+
|
|
6077
|
+
if (!hasOwn.call(rule, '__scopedStratumInfo')) {
|
|
6078
|
+
Object.defineProperty(rule, '__scopedStratumInfo', {
|
|
6079
|
+
value,
|
|
6080
|
+
enumerable: false,
|
|
6081
|
+
writable: true,
|
|
6082
|
+
configurable: true,
|
|
6083
|
+
});
|
|
6084
|
+
} else {
|
|
6085
|
+
rule.__scopedStratumInfo = value;
|
|
6086
|
+
}
|
|
6087
|
+
}
|
|
6088
|
+
|
|
6089
|
+
function __computeForwardRuleScopedStrata(forwardRules) {
|
|
6090
|
+
if (!Array.isArray(forwardRules) || forwardRules.length === 0) return 0;
|
|
6091
|
+
|
|
6092
|
+
const metas = forwardRules.map((r) => __analyzeForwardRuleScopedUse(r));
|
|
6093
|
+
const levels = metas.map((m) => (m.needsSnap ? Math.max(1, m.baseLevel || 1) : 0));
|
|
6094
|
+
|
|
6095
|
+
let maxLevel = 0;
|
|
6096
|
+
for (const lvl of levels) if (lvl > maxLevel) maxLevel = lvl;
|
|
6097
|
+
|
|
6098
|
+
// Stratify scoped rules by data dependency: if a scoped query can read facts
|
|
6099
|
+
// produced by another scoped rule, the reader must run against a later frozen
|
|
6100
|
+
// snapshot. This prevents non-monotonic scoped builtins such as collectAllIn
|
|
6101
|
+
// from emitting an early result before lower strata have derived their facts.
|
|
6102
|
+
const passLimit = Math.max(1, forwardRules.length) + maxLevel + 1;
|
|
6103
|
+
for (let pass = 0; pass < passLimit; pass++) {
|
|
6104
|
+
let changed = false;
|
|
6105
|
+
for (let ci = 0; ci < forwardRules.length; ci++) {
|
|
6106
|
+
if (!metas[ci].needsSnap || !metas[ci].hasScopedAggregate) continue;
|
|
6107
|
+
let needed = levels[ci];
|
|
6108
|
+
for (let pi = 0; pi < forwardRules.length; pi++) {
|
|
6109
|
+
if (pi === ci) continue;
|
|
6110
|
+
if (levels[pi] <= 0) continue;
|
|
6111
|
+
if (metas[pi].hasScopedAggregate) continue;
|
|
6112
|
+
if (!__ruleConclusionsMayFeedScopedQueries(forwardRules[pi], metas[ci])) continue;
|
|
6113
|
+
needed = Math.max(needed, levels[pi] + 1);
|
|
6114
|
+
}
|
|
6115
|
+
if (needed !== levels[ci]) {
|
|
6116
|
+
levels[ci] = needed;
|
|
6117
|
+
if (needed > maxLevel) maxLevel = needed;
|
|
6118
|
+
changed = true;
|
|
6119
|
+
}
|
|
6120
|
+
}
|
|
6121
|
+
if (!changed) break;
|
|
6122
|
+
}
|
|
6123
|
+
|
|
6124
|
+
|
|
6125
|
+
maxLevel = 0;
|
|
6126
|
+
for (let i = 0; i < forwardRules.length; i++) {
|
|
6127
|
+
__setForwardRuleScopedStratumInfo(forwardRules[i], levels[i]);
|
|
6128
|
+
if (levels[i] > maxLevel) maxLevel = levels[i];
|
|
6129
|
+
}
|
|
6130
|
+
|
|
6131
|
+
return maxLevel;
|
|
6132
|
+
}
|
|
6133
|
+
|
|
5976
6134
|
function __computeForwardRuleScopedSkipInfo(rule) {
|
|
5977
6135
|
let needsSnap = false;
|
|
5978
6136
|
let requiredLevel = 0;
|
|
@@ -6117,9 +6275,11 @@ const { evalBuiltin, isBuiltinPred } = makeBuiltins({
|
|
|
6117
6275
|
});
|
|
6118
6276
|
|
|
6119
6277
|
// Initialize proof/output helpers (implemented in lib/explain.js).
|
|
6120
|
-
const { printExplanation, collectOutputStringsFromFacts } = makeExplain({
|
|
6278
|
+
const { printExplanation, renderProofDocument, collectOutputStringsFromFacts } = makeExplain({
|
|
6121
6279
|
applySubstTerm,
|
|
6122
6280
|
skolemKeyFromTerm,
|
|
6281
|
+
isBuiltinPred,
|
|
6282
|
+
findBackwardProofForGoal,
|
|
6123
6283
|
});
|
|
6124
6284
|
|
|
6125
6285
|
function skolemizeTermForHeadBlanks(t, headBlankLabels, mapping, skCounter, firingKey, globalMap) {
|
|
@@ -7158,7 +7318,7 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
|
|
|
7158
7318
|
goalPredTid: goal.p instanceof Iri ? goal.p.__tid : null,
|
|
7159
7319
|
goalSKey,
|
|
7160
7320
|
goalOKey,
|
|
7161
|
-
needsSkipCheck: !!r.__needsForwardSkipCheck,
|
|
7321
|
+
needsSkipCheck: !!(r.__needsForwardSkipCheck || (r.__scopedStratumInfo && r.__scopedStratumInfo.needsSnap)),
|
|
7162
7322
|
fastSubjectVar,
|
|
7163
7323
|
fastObjectVar,
|
|
7164
7324
|
};
|
|
@@ -7770,6 +7930,217 @@ function unifyTriple(pat, fact, subst) {
|
|
|
7770
7930
|
return s3;
|
|
7771
7931
|
}
|
|
7772
7932
|
|
|
7933
|
+
|
|
7934
|
+
function __copyProofSourceMetadata(from, to) {
|
|
7935
|
+
if (!from || !to) return to;
|
|
7936
|
+
for (const key of ['__sourceOffset', '__source']) {
|
|
7937
|
+
if (hasOwn.call(from, key)) {
|
|
7938
|
+
Object.defineProperty(to, key, {
|
|
7939
|
+
value: from[key],
|
|
7940
|
+
enumerable: false,
|
|
7941
|
+
writable: false,
|
|
7942
|
+
configurable: true,
|
|
7943
|
+
});
|
|
7944
|
+
}
|
|
7945
|
+
}
|
|
7946
|
+
return to;
|
|
7947
|
+
}
|
|
7948
|
+
|
|
7949
|
+
function __standardizeRuleForProof(rule, gen) {
|
|
7950
|
+
const proofVarNames = Object.create(null);
|
|
7951
|
+
|
|
7952
|
+
function renameTerm(t, vmapName, vmapVar, genArr) {
|
|
7953
|
+
if (t instanceof Var) {
|
|
7954
|
+
if (!hasOwn.call(vmapVar, t.name)) {
|
|
7955
|
+
const name = `${t.name}__proof_${genArr[0]}`;
|
|
7956
|
+
genArr[0] += 1;
|
|
7957
|
+
vmapName[t.name] = name;
|
|
7958
|
+
vmapVar[t.name] = new Var(name);
|
|
7959
|
+
proofVarNames[name] = t.name;
|
|
7960
|
+
}
|
|
7961
|
+
return vmapVar[t.name];
|
|
7962
|
+
}
|
|
7963
|
+
if (t instanceof ListTerm) {
|
|
7964
|
+
let changed = false;
|
|
7965
|
+
const elems2 = t.elems.map((e) => {
|
|
7966
|
+
const e2 = renameTerm(e, vmapName, vmapVar, genArr);
|
|
7967
|
+
if (e2 !== e) changed = true;
|
|
7968
|
+
return e2;
|
|
7969
|
+
});
|
|
7970
|
+
return changed ? new ListTerm(elems2) : t;
|
|
7971
|
+
}
|
|
7972
|
+
if (t instanceof OpenListTerm) {
|
|
7973
|
+
let changed = false;
|
|
7974
|
+
const newXs = t.prefix.map((e) => {
|
|
7975
|
+
const e2 = renameTerm(e, vmapName, vmapVar, genArr);
|
|
7976
|
+
if (e2 !== e) changed = true;
|
|
7977
|
+
return e2;
|
|
7978
|
+
});
|
|
7979
|
+
if (!hasOwn.call(vmapName, t.tailVar)) {
|
|
7980
|
+
const name = `${t.tailVar}__proof_${genArr[0]}`;
|
|
7981
|
+
genArr[0] += 1;
|
|
7982
|
+
vmapName[t.tailVar] = name;
|
|
7983
|
+
vmapVar[t.tailVar] = new Var(name);
|
|
7984
|
+
proofVarNames[name] = t.tailVar;
|
|
7985
|
+
}
|
|
7986
|
+
const newTail = vmapName[t.tailVar];
|
|
7987
|
+
if (newTail !== t.tailVar) changed = true;
|
|
7988
|
+
return changed ? new OpenListTerm(newXs, newTail) : t;
|
|
7989
|
+
}
|
|
7990
|
+
if (t instanceof GraphTerm) {
|
|
7991
|
+
let changed = false;
|
|
7992
|
+
const triples2 = t.triples.map((tr) => {
|
|
7993
|
+
const s2 = renameTerm(tr.s, vmapName, vmapVar, genArr);
|
|
7994
|
+
const p2 = renameTerm(tr.p, vmapName, vmapVar, genArr);
|
|
7995
|
+
const o2 = renameTerm(tr.o, vmapName, vmapVar, genArr);
|
|
7996
|
+
if (s2 !== tr.s || p2 !== tr.p || o2 !== tr.o) changed = true;
|
|
7997
|
+
return s2 === tr.s && p2 === tr.p && o2 === tr.o ? tr : new Triple(s2, p2, o2);
|
|
7998
|
+
});
|
|
7999
|
+
return changed ? copyQuotedGraphMetadata(t, new GraphTerm(triples2)) : t;
|
|
8000
|
+
}
|
|
8001
|
+
return t;
|
|
8002
|
+
}
|
|
8003
|
+
|
|
8004
|
+
const vmapName = Object.create(null);
|
|
8005
|
+
const vmapVar = Object.create(null);
|
|
8006
|
+
const premise = rule.premise.map((tr) => {
|
|
8007
|
+
const s2 = renameTerm(tr.s, vmapName, vmapVar, gen);
|
|
8008
|
+
const p2 = renameTerm(tr.p, vmapName, vmapVar, gen);
|
|
8009
|
+
const o2 = renameTerm(tr.o, vmapName, vmapVar, gen);
|
|
8010
|
+
return s2 === tr.s && p2 === tr.p && o2 === tr.o ? tr : new Triple(s2, p2, o2);
|
|
8011
|
+
});
|
|
8012
|
+
const conclusion = rule.conclusion.map((tr) => {
|
|
8013
|
+
const s2 = renameTerm(tr.s, vmapName, vmapVar, gen);
|
|
8014
|
+
const p2 = renameTerm(tr.p, vmapName, vmapVar, gen);
|
|
8015
|
+
const o2 = renameTerm(tr.o, vmapName, vmapVar, gen);
|
|
8016
|
+
return s2 === tr.s && p2 === tr.p && o2 === tr.o ? tr : new Triple(s2, p2, o2);
|
|
8017
|
+
});
|
|
8018
|
+
const out = new Rule(premise, conclusion, rule.isForward, rule.isFuse, rule.headBlankLabels);
|
|
8019
|
+
Object.defineProperty(out, '__proofVarSourceNames', {
|
|
8020
|
+
value: proofVarNames,
|
|
8021
|
+
enumerable: false,
|
|
8022
|
+
writable: false,
|
|
8023
|
+
configurable: true,
|
|
8024
|
+
});
|
|
8025
|
+
return __copyProofSourceMetadata(rule, out);
|
|
8026
|
+
}
|
|
8027
|
+
|
|
8028
|
+
function __mergeProofSubst(base, delta) {
|
|
8029
|
+
const out = __cloneSubst(base);
|
|
8030
|
+
for (const k in delta || {}) {
|
|
8031
|
+
if (!hasOwn.call(delta, k)) continue;
|
|
8032
|
+
const v = applySubstTerm(delta[k], out);
|
|
8033
|
+
if (hasOwn.call(out, k)) {
|
|
8034
|
+
if (!termsEqual(applySubstTerm(out[k], out), v)) return null;
|
|
8035
|
+
} else {
|
|
8036
|
+
out[k] = v;
|
|
8037
|
+
}
|
|
8038
|
+
}
|
|
8039
|
+
return out;
|
|
8040
|
+
}
|
|
8041
|
+
|
|
8042
|
+
function __sourceSortKey(obj) {
|
|
8043
|
+
const src = obj && obj.__source;
|
|
8044
|
+
const label = src && typeof src.label === 'string' ? src.label : '';
|
|
8045
|
+
const line = src && Number.isInteger(src.line) ? src.line : 0;
|
|
8046
|
+
return `${label}\t${line}`;
|
|
8047
|
+
}
|
|
8048
|
+
|
|
8049
|
+
function __candidateFactsForProof(facts, goal) {
|
|
8050
|
+
if (!Array.isArray(facts)) return [];
|
|
8051
|
+
ensureFactIndexes(facts);
|
|
8052
|
+
const candidates = candidateFacts(facts, goal);
|
|
8053
|
+
if (!candidates) return facts;
|
|
8054
|
+
const out = [];
|
|
8055
|
+
for (let i = 0; i < candidates.exactLen; i++) out.push(facts[candidates.exact[i]]);
|
|
8056
|
+
for (let i = 0; i < candidates.wildLen; i++) out.push(facts[candidates.wild[i]]);
|
|
8057
|
+
return out;
|
|
8058
|
+
}
|
|
8059
|
+
|
|
8060
|
+
function findBackwardProofForGoal(goal, facts, backRules, opts = {}) {
|
|
8061
|
+
const maxDepth = Number.isInteger(opts.maxDepth) && opts.maxDepth > 0 ? opts.maxDepth : 64;
|
|
8062
|
+
const varGen = [0];
|
|
8063
|
+
|
|
8064
|
+
function proofKey(tr) {
|
|
8065
|
+
return `${skolemKeyFromTerm(tr.s)}\t${skolemKeyFromTerm(tr.p)}\t${skolemKeyFromTerm(tr.o)}`;
|
|
8066
|
+
}
|
|
8067
|
+
|
|
8068
|
+
function proveGoal(goal0, subst, depth, visited) {
|
|
8069
|
+
if (depth > maxDepth) return [];
|
|
8070
|
+
const g = applySubstTriple(goal0, subst);
|
|
8071
|
+
const key = proofKey(g);
|
|
8072
|
+
if (visited.has(key)) return [];
|
|
8073
|
+
|
|
8074
|
+
if (isBuiltinPred(g.p)) {
|
|
8075
|
+
const builtinGoalForEval = g.p instanceof Iri && g.p.value === LOG_NS + 'rawType' ? goal0 : g;
|
|
8076
|
+
const builtinSubstForEval = g.p instanceof Iri && g.p.value === LOG_NS + 'rawType' ? subst : __emptySubst();
|
|
8077
|
+
const deltas = evalBuiltin(builtinGoalForEval, builtinSubstForEval, facts, backRules, depth, varGen, 1) || [];
|
|
8078
|
+
const out = [];
|
|
8079
|
+
for (const delta of deltas) {
|
|
8080
|
+
const subst2 = __mergeProofSubst(subst, delta);
|
|
8081
|
+
if (subst2 === null) continue;
|
|
8082
|
+
const fact = applySubstTriple(g, subst2);
|
|
8083
|
+
out.push({ subst: subst2, proof: { kind: 'builtin', fact, builtin: fact.p } });
|
|
8084
|
+
break;
|
|
8085
|
+
}
|
|
8086
|
+
return out;
|
|
8087
|
+
}
|
|
8088
|
+
|
|
8089
|
+
const factResults = [];
|
|
8090
|
+
for (const f of __candidateFactsForProof(facts, g)) {
|
|
8091
|
+
const subst2 = unifyTriple(g, f, subst);
|
|
8092
|
+
if (subst2 === null) continue;
|
|
8093
|
+
factResults.push({ subst: subst2, proof: { kind: 'fact', fact: applySubstTriple(f, subst2), source: f.__source } });
|
|
8094
|
+
break;
|
|
8095
|
+
}
|
|
8096
|
+
if (factResults.length) return factResults;
|
|
8097
|
+
|
|
8098
|
+
if (!(g.p instanceof Iri)) return [];
|
|
8099
|
+
ensureBackRuleIndexes(backRules);
|
|
8100
|
+
const rules = (backRules.__byHeadPred.get(g.p.__tid) || []).concat(backRules.__wildHeadPred || []);
|
|
8101
|
+
rules.sort((a, b) => (__sourceSortKey(a) < __sourceSortKey(b) ? -1 : __sourceSortKey(a) > __sourceSortKey(b) ? 1 : 0));
|
|
8102
|
+
|
|
8103
|
+
const visited2 = new Set(visited);
|
|
8104
|
+
visited2.add(key);
|
|
8105
|
+
|
|
8106
|
+
for (const r of rules) {
|
|
8107
|
+
if (!r || !Array.isArray(r.conclusion) || r.conclusion.length !== 1) continue;
|
|
8108
|
+
const rStd = __standardizeRuleForProof(r, varGen);
|
|
8109
|
+
const head = rStd.conclusion[0];
|
|
8110
|
+
if (head.p instanceof Iri && head.p.__tid !== g.p.__tid) continue;
|
|
8111
|
+
const subst1 = unifyTriple(head, g, subst);
|
|
8112
|
+
if (subst1 === null) continue;
|
|
8113
|
+
const bodyProof = proveGoalList(rStd.premise || [], subst1, depth + 1, visited2);
|
|
8114
|
+
if (!bodyProof) continue;
|
|
8115
|
+
const fact = applySubstTriple(g, bodyProof.subst);
|
|
8116
|
+
const premises = (rStd.premise || []).map((prem) => applySubstTriple(prem, bodyProof.subst));
|
|
8117
|
+
const df = new DerivedFact(fact, rStd, premises, bodyProof.subst);
|
|
8118
|
+
return [{ subst: bodyProof.subst, proof: { kind: 'rule', df, children: bodyProof.proofs } }];
|
|
8119
|
+
}
|
|
8120
|
+
|
|
8121
|
+
return [];
|
|
8122
|
+
}
|
|
8123
|
+
|
|
8124
|
+
function proveGoalList(goals, subst, depth, visited) {
|
|
8125
|
+
let states = [{ subst, proofs: [] }];
|
|
8126
|
+
for (const goal1 of goals || []) {
|
|
8127
|
+
const next = [];
|
|
8128
|
+
for (const state of states) {
|
|
8129
|
+
const proofs = proveGoal(goal1, state.subst, depth, visited);
|
|
8130
|
+
for (const pr of proofs) next.push({ subst: pr.subst, proofs: state.proofs.concat([pr.proof]) });
|
|
8131
|
+
}
|
|
8132
|
+
if (!next.length) return null;
|
|
8133
|
+
// Keep proof reconstruction deterministic and cheap: one proof is enough
|
|
8134
|
+
// for an explanation of the already-derived enclosing fact.
|
|
8135
|
+
states = [next[0]];
|
|
8136
|
+
}
|
|
8137
|
+
return states[0] || { subst, proofs: [] };
|
|
8138
|
+
}
|
|
8139
|
+
|
|
8140
|
+
const found = proveGoal(goal, __emptySubst(), 0, new Set());
|
|
8141
|
+
return found.length ? found[0].proof : null;
|
|
8142
|
+
}
|
|
8143
|
+
|
|
7773
8144
|
// Strategy: when the substitution is "large" or search depth is high,
|
|
7774
8145
|
// keep only bindings that are still relevant to:
|
|
7775
8146
|
// - variables appearing in the remaining goals
|
|
@@ -8606,7 +8977,10 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
8606
8977
|
let scopedClosureLevel = 0;
|
|
8607
8978
|
|
|
8608
8979
|
// Scan known rules for the maximum requested closure priority in scoped log:* goals.
|
|
8609
|
-
let maxScopedClosurePriorityNeeded =
|
|
8980
|
+
let maxScopedClosurePriorityNeeded = Math.max(
|
|
8981
|
+
__computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
|
|
8982
|
+
__computeForwardRuleScopedStrata(forwardRules),
|
|
8983
|
+
);
|
|
8610
8984
|
|
|
8611
8985
|
// Conservative fast-skip for forward rules that cannot possibly succeed
|
|
8612
8986
|
// until a scoped snapshot exists (or a given closure level is reached).
|
|
@@ -8660,12 +9034,16 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
8660
9034
|
// until a snapshot exists (and a certain closure level is reached).
|
|
8661
9035
|
// This prevents expensive proofs that will definitely fail in Phase A
|
|
8662
9036
|
// and in early closure levels.
|
|
8663
|
-
const info = r.__scopedSkipInfo;
|
|
9037
|
+
const info = r.__scopedStratumInfo || r.__scopedSkipInfo;
|
|
8664
9038
|
if (info && info.needsSnap) {
|
|
8665
9039
|
const snapHere = facts.__scopedSnapshot || null;
|
|
8666
9040
|
const lvlHere = (facts && typeof facts.__scopedClosureLevel === 'number' && facts.__scopedClosureLevel) || 0;
|
|
8667
9041
|
if (!snapHere) return true;
|
|
8668
|
-
if (
|
|
9042
|
+
if (info.exactLevel) {
|
|
9043
|
+
if (lvlHere !== info.requiredLevel) return true;
|
|
9044
|
+
} else if (lvlHere < info.requiredLevel) {
|
|
9045
|
+
return true;
|
|
9046
|
+
}
|
|
8669
9047
|
}
|
|
8670
9048
|
|
|
8671
9049
|
// Optimization: if the rule head is **structurally ground** (no vars anywhere, even inside
|
|
@@ -8865,6 +9243,10 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
8865
9243
|
|
|
8866
9244
|
const outcome = __emitForwardRuleSolution(r, entry.ruleIndex, s);
|
|
8867
9245
|
if (outcome.rulesChanged) {
|
|
9246
|
+
maxScopedClosurePriorityNeeded = Math.max(
|
|
9247
|
+
__computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
|
|
9248
|
+
__computeForwardRuleScopedStrata(forwardRules),
|
|
9249
|
+
);
|
|
8868
9250
|
agendaIndex = makeSinglePremiseAgendaIndex(forwardRules, backRules);
|
|
8869
9251
|
agendaCursor = 0;
|
|
8870
9252
|
}
|
|
@@ -8878,7 +9260,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
8878
9260
|
for (let i = 0; i < forwardRules.length; i++) {
|
|
8879
9261
|
const r = forwardRules[i];
|
|
8880
9262
|
if (agendaIndex.indexed.has(r)) continue;
|
|
8881
|
-
if (r.__needsForwardSkipCheck && __skipForwardRuleNow(r)) continue;
|
|
9263
|
+
if ((r.__needsForwardSkipCheck || (r.__scopedStratumInfo && r.__scopedStratumInfo.needsSnap)) && __skipForwardRuleNow(r)) continue;
|
|
8882
9264
|
|
|
8883
9265
|
const headIsStrictGround = r.__headIsStrictGround;
|
|
8884
9266
|
const maxSols = r.isFuse || headIsStrictGround ? 1 : undefined;
|
|
@@ -8899,6 +9281,10 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
8899
9281
|
for (const s of sols) {
|
|
8900
9282
|
const outcome = __emitForwardRuleSolution(r, i, s);
|
|
8901
9283
|
if (outcome.rulesChanged) {
|
|
9284
|
+
maxScopedClosurePriorityNeeded = Math.max(
|
|
9285
|
+
__computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
|
|
9286
|
+
__computeForwardRuleScopedStrata(forwardRules),
|
|
9287
|
+
);
|
|
8902
9288
|
agendaIndex = makeSinglePremiseAgendaIndex(forwardRules, backRules);
|
|
8903
9289
|
agendaCursor = 0;
|
|
8904
9290
|
}
|
|
@@ -8926,8 +9312,8 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
8926
9312
|
// Rules may have been added dynamically (rule-producing triples), possibly
|
|
8927
9313
|
// introducing scoped builtins and/or higher closure priorities.
|
|
8928
9314
|
maxScopedClosurePriorityNeeded = Math.max(
|
|
8929
|
-
maxScopedClosurePriorityNeeded,
|
|
8930
9315
|
__computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
|
|
9316
|
+
__computeForwardRuleScopedStrata(forwardRules),
|
|
8931
9317
|
);
|
|
8932
9318
|
|
|
8933
9319
|
// If there are no scoped builtins in the entire program, Phase B is pure
|
|
@@ -8945,8 +9331,8 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
8945
9331
|
|
|
8946
9332
|
// Phase B can also derive rule-producing triples.
|
|
8947
9333
|
maxScopedClosurePriorityNeeded = Math.max(
|
|
8948
|
-
maxScopedClosurePriorityNeeded,
|
|
8949
9334
|
__computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
|
|
9335
|
+
__computeForwardRuleScopedStrata(forwardRules),
|
|
8950
9336
|
);
|
|
8951
9337
|
|
|
8952
9338
|
if (!changedA && !changedB && scopedClosureLevel >= maxScopedClosurePriorityNeeded) break;
|
|
@@ -9269,11 +9655,19 @@ function reasonStream(input, opts = {}) {
|
|
|
9269
9655
|
? facts
|
|
9270
9656
|
: derived.map((d) => d.fact);
|
|
9271
9657
|
|
|
9272
|
-
const closureN3 =
|
|
9273
|
-
?
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9658
|
+
const closureN3 = proof
|
|
9659
|
+
? renderProofDocument(
|
|
9660
|
+
Array.isArray(logQueryRules) && logQueryRules.length ? queryDerived : derived,
|
|
9661
|
+
derived,
|
|
9662
|
+
triples,
|
|
9663
|
+
prefixes,
|
|
9664
|
+
brules,
|
|
9665
|
+
).replace(/\n$/g, '')
|
|
9666
|
+
: useRdfCompatibility
|
|
9667
|
+
? closureTriples.map((t) => tripleToRdfCompatible(t, prefixes)).join('\n')
|
|
9668
|
+
: Array.isArray(logQueryRules) && logQueryRules.length
|
|
9669
|
+
? prettyPrintQueryTriples(closureTriples, prefixes)
|
|
9670
|
+
: closureTriples.map((t) => tripleToN3(t, prefixes)).join('\n');
|
|
9277
9671
|
|
|
9278
9672
|
const __out = {
|
|
9279
9673
|
prefixes,
|
|
@@ -9400,6 +9794,7 @@ module.exports = {
|
|
|
9400
9794
|
collectLogQueryConclusions,
|
|
9401
9795
|
forwardChainAndCollectLogQueryConclusions,
|
|
9402
9796
|
collectOutputStringsFromFacts,
|
|
9797
|
+
renderProofDocument,
|
|
9403
9798
|
main,
|
|
9404
9799
|
version,
|
|
9405
9800
|
N3SyntaxError,
|
|
@@ -9469,6 +9864,7 @@ module.exports = {
|
|
|
9469
9864
|
materializeRdfLists: engine.materializeRdfLists,
|
|
9470
9865
|
isGroundTriple: engine.isGroundTriple,
|
|
9471
9866
|
printExplanation: engine.printExplanation,
|
|
9867
|
+
renderProofDocument: engine.renderProofDocument,
|
|
9472
9868
|
tripleToN3: engine.tripleToN3,
|
|
9473
9869
|
collectOutputStringsFromFacts: engine.collectOutputStringsFromFacts,
|
|
9474
9870
|
prettyPrintQueryTriples: engine.prettyPrintQueryTriples,
|
|
@@ -9495,7 +9891,7 @@ module.exports = {
|
|
|
9495
9891
|
*/
|
|
9496
9892
|
'use strict';
|
|
9497
9893
|
|
|
9498
|
-
const { LOG_NS, Literal, Iri, Blank, Var, varsInRule, literalParts } = require('./prelude');
|
|
9894
|
+
const { LOG_NS, Literal, Iri, Blank, Var, GraphTerm, varsInRule, literalParts, PrefixEnv } = require('./prelude');
|
|
9499
9895
|
|
|
9500
9896
|
const { termToN3, tripleToN3 } = require('./printing');
|
|
9501
9897
|
const { parseNumericLiteralInfo, termToJsString } = require('./builtins');
|
|
@@ -9503,6 +9899,8 @@ const { parseNumericLiteralInfo, termToJsString } = require('./builtins');
|
|
|
9503
9899
|
function makeExplain(deps) {
|
|
9504
9900
|
const applySubstTerm = deps.applySubstTerm;
|
|
9505
9901
|
const skolemKeyFromTerm = deps.skolemKeyFromTerm;
|
|
9902
|
+
const isBuiltinPred = typeof deps.isBuiltinPred === 'function' ? deps.isBuiltinPred : () => false;
|
|
9903
|
+
const findBackwardProofForGoal = typeof deps.findBackwardProofForGoal === 'function' ? deps.findBackwardProofForGoal : null;
|
|
9506
9904
|
|
|
9507
9905
|
function printExplanation(df, prefixes) {
|
|
9508
9906
|
console.log('# ----------------------------------------------------------------------');
|
|
@@ -9600,6 +9998,282 @@ function makeExplain(deps) {
|
|
|
9600
9998
|
console.log('# ----------------------------------------------------------------------\n');
|
|
9601
9999
|
}
|
|
9602
10000
|
|
|
10001
|
+
|
|
10002
|
+
const PE_NS = 'https://eyereasoner.github.io/pe#';
|
|
10003
|
+
|
|
10004
|
+
function n3String(value) {
|
|
10005
|
+
return JSON.stringify(String(value));
|
|
10006
|
+
}
|
|
10007
|
+
|
|
10008
|
+
function lineIndent(text, prefix) {
|
|
10009
|
+
return String(text)
|
|
10010
|
+
.split(/\r?\n/)
|
|
10011
|
+
.map((line) => (line.length ? prefix + line : line))
|
|
10012
|
+
.join('\n');
|
|
10013
|
+
}
|
|
10014
|
+
|
|
10015
|
+
function graphForTriple(tr, prefixes) {
|
|
10016
|
+
const body = tripleToN3(tr, prefixes).trimEnd();
|
|
10017
|
+
if (!body.includes('\n')) return `{ ${body} }`;
|
|
10018
|
+
return `{
|
|
10019
|
+
${lineIndent(body, ' ')}
|
|
10020
|
+
}`;
|
|
10021
|
+
}
|
|
10022
|
+
|
|
10023
|
+
|
|
10024
|
+
function clonePrefixEnvWithProofVocabulary(prefixes) {
|
|
10025
|
+
const map = { ...(prefixes && prefixes.map ? prefixes.map : {}) };
|
|
10026
|
+
if (!map.pe) map.pe = PE_NS;
|
|
10027
|
+
return new PrefixEnv(map, (prefixes && prefixes.baseIri) || '');
|
|
10028
|
+
}
|
|
10029
|
+
|
|
10030
|
+
function proofTripleKey(tr) {
|
|
10031
|
+
if (!tr) return '';
|
|
10032
|
+
return `${skolemKeyFromTerm(tr.s)}\t${skolemKeyFromTerm(tr.p)}\t${skolemKeyFromTerm(tr.o)}`;
|
|
10033
|
+
}
|
|
10034
|
+
|
|
10035
|
+
function sourceLabelForProof(source) {
|
|
10036
|
+
if (!source || typeof source.label !== 'string' || !source.label) return '<unknown>';
|
|
10037
|
+
// Keep proof output stable when the CLI is invoked with paths such as examples/foo.n3.
|
|
10038
|
+
return source.label.replace(/\\/g, '/').split('/').pop() || source.label;
|
|
10039
|
+
}
|
|
10040
|
+
|
|
10041
|
+
function byBlankNode(kind, source) {
|
|
10042
|
+
const src = source || {};
|
|
10043
|
+
const props = [`pe:${kind} ${n3String(sourceLabelForProof(src))}`];
|
|
10044
|
+
if (Number.isInteger(src.line) && src.line > 0) props.push(`pe:line ${src.line}`);
|
|
10045
|
+
return `[ ${props.join('; ')} ]`;
|
|
10046
|
+
}
|
|
10047
|
+
|
|
10048
|
+
function renderBindingList(df, prefixes) {
|
|
10049
|
+
if (!df || !df.rule || !df.subst) return '';
|
|
10050
|
+
const ruleVars = varsInRule(df.rule);
|
|
10051
|
+
const visibleNames = Object.keys(df.subst)
|
|
10052
|
+
.filter((name) => ruleVars.has(name))
|
|
10053
|
+
.sort();
|
|
10054
|
+
if (!visibleNames.length) return '';
|
|
10055
|
+
const sourceNames = (df.rule && df.rule.__proofVarSourceNames) || null;
|
|
10056
|
+
return visibleNames
|
|
10057
|
+
.map((name) => {
|
|
10058
|
+
const value = applySubstTerm(new Var(name), df.subst);
|
|
10059
|
+
const displayName = sourceNames && sourceNames[name] ? sourceNames[name] : name;
|
|
10060
|
+
return `[ pe:var ${n3String(displayName)}; pe:value ${termToN3(value, prefixes)} ]`;
|
|
10061
|
+
})
|
|
10062
|
+
.join(', ');
|
|
10063
|
+
}
|
|
10064
|
+
|
|
10065
|
+
function proofSourceKey(source) {
|
|
10066
|
+
if (!source) return '';
|
|
10067
|
+
const label = typeof source.label === 'string' ? source.label : '';
|
|
10068
|
+
const line = Number.isInteger(source.line) ? source.line : 0;
|
|
10069
|
+
return `${label}\t${line}`;
|
|
10070
|
+
}
|
|
10071
|
+
|
|
10072
|
+
function proofEntryKey(kind, fact, source, extra) {
|
|
10073
|
+
return `${kind}\t${proofTripleKey(fact)}\t${proofSourceKey(source)}\t${extra || ''}`;
|
|
10074
|
+
}
|
|
10075
|
+
|
|
10076
|
+
function ruleEntryKey(df) {
|
|
10077
|
+
const rule = df && df.rule;
|
|
10078
|
+
const premises = (df && df.premises ? df.premises : []).map((prem) => proofTripleKey(prem)).join('\t');
|
|
10079
|
+
return proofEntryKey('rule', df && df.fact, rule && rule.__source, premises);
|
|
10080
|
+
}
|
|
10081
|
+
|
|
10082
|
+
function collectProofEntries(rootDf, derivedByKey, baseFactByKey, resolveBackwardProof) {
|
|
10083
|
+
const entries = [];
|
|
10084
|
+
const seen = new Set();
|
|
10085
|
+
|
|
10086
|
+
function entryKeyForProof(proof, fallbackTriple) {
|
|
10087
|
+
if (!proof) return proofEntryKey('fact', fallbackTriple, null);
|
|
10088
|
+
if (proof.kind === 'rule') return ruleEntryKey(proof.df);
|
|
10089
|
+
if (proof.kind === 'builtin') return proofEntryKey('builtin', proof.fact || fallbackTriple, null);
|
|
10090
|
+
return proofEntryKey('fact', proof.fact || fallbackTriple, proof.source);
|
|
10091
|
+
}
|
|
10092
|
+
|
|
10093
|
+
function visitProofNode(proof, fallbackTriple) {
|
|
10094
|
+
if (!proof) {
|
|
10095
|
+
visitFactTriple(fallbackTriple);
|
|
10096
|
+
return;
|
|
10097
|
+
}
|
|
10098
|
+
const key = entryKeyForProof(proof, fallbackTriple);
|
|
10099
|
+
if (!key || seen.has(key)) return;
|
|
10100
|
+
if (proof.kind === 'rule' && proof.df) {
|
|
10101
|
+
visitDerivedFact(proof.df, proof.children || null);
|
|
10102
|
+
return;
|
|
10103
|
+
}
|
|
10104
|
+
seen.add(key);
|
|
10105
|
+
if (proof.kind === 'builtin') {
|
|
10106
|
+
entries.push({
|
|
10107
|
+
kind: 'builtin',
|
|
10108
|
+
fact: proof.fact || fallbackTriple,
|
|
10109
|
+
builtin: proof.builtin || (proof.fact && proof.fact.p),
|
|
10110
|
+
});
|
|
10111
|
+
} else {
|
|
10112
|
+
entries.push({ kind: 'fact', fact: proof.fact || fallbackTriple, source: proof.source });
|
|
10113
|
+
}
|
|
10114
|
+
}
|
|
10115
|
+
|
|
10116
|
+
function visitFactTriple(tr) {
|
|
10117
|
+
const tripleKey = proofTripleKey(tr);
|
|
10118
|
+
if (!tripleKey) return;
|
|
10119
|
+
const df = derivedByKey.get(tripleKey);
|
|
10120
|
+
if (df) {
|
|
10121
|
+
visitDerivedFact(df);
|
|
10122
|
+
return;
|
|
10123
|
+
}
|
|
10124
|
+
const base = baseFactByKey.get(tripleKey) || null;
|
|
10125
|
+
if (base) {
|
|
10126
|
+
const key = proofEntryKey('fact', base, base.__source);
|
|
10127
|
+
if (seen.has(key)) return;
|
|
10128
|
+
seen.add(key);
|
|
10129
|
+
entries.push({ kind: 'fact', fact: base, source: base.__source });
|
|
10130
|
+
return;
|
|
10131
|
+
}
|
|
10132
|
+
if (tr && isBuiltinPred(tr.p)) {
|
|
10133
|
+
const key = proofEntryKey('builtin', tr, null);
|
|
10134
|
+
if (seen.has(key)) return;
|
|
10135
|
+
seen.add(key);
|
|
10136
|
+
entries.push({ kind: 'builtin', fact: tr, builtin: tr.p });
|
|
10137
|
+
return;
|
|
10138
|
+
}
|
|
10139
|
+
if (resolveBackwardProof) {
|
|
10140
|
+
const proof = resolveBackwardProof(tr);
|
|
10141
|
+
if (proof) {
|
|
10142
|
+
visitProofNode(proof, tr);
|
|
10143
|
+
return;
|
|
10144
|
+
}
|
|
10145
|
+
}
|
|
10146
|
+
const key = proofEntryKey('fact', tr, null);
|
|
10147
|
+
if (seen.has(key)) return;
|
|
10148
|
+
seen.add(key);
|
|
10149
|
+
entries.push({ kind: 'fact', fact: tr, source: null });
|
|
10150
|
+
}
|
|
10151
|
+
|
|
10152
|
+
function visitDerivedFact(df, children) {
|
|
10153
|
+
const key = ruleEntryKey(df);
|
|
10154
|
+
if (!key || seen.has(key)) return;
|
|
10155
|
+
seen.add(key);
|
|
10156
|
+
entries.push({ kind: 'rule', df });
|
|
10157
|
+
|
|
10158
|
+
if (Array.isArray(children) && children.length) {
|
|
10159
|
+
for (const child of children) visitProofNode(child);
|
|
10160
|
+
return;
|
|
10161
|
+
}
|
|
10162
|
+
for (const prem of df.premises || []) visitFactTriple(prem);
|
|
10163
|
+
}
|
|
10164
|
+
|
|
10165
|
+
visitDerivedFact(rootDf);
|
|
10166
|
+
return entries;
|
|
10167
|
+
}
|
|
10168
|
+
|
|
10169
|
+
function collectProofOutputTriples(outputDerived) {
|
|
10170
|
+
const out = [];
|
|
10171
|
+
const seen = new Set();
|
|
10172
|
+
for (const df of outputDerived || []) {
|
|
10173
|
+
if (!df || !df.fact) continue;
|
|
10174
|
+
const key = proofTripleKey(df.fact);
|
|
10175
|
+
if (seen.has(key)) continue;
|
|
10176
|
+
seen.add(key);
|
|
10177
|
+
out.push(df.fact);
|
|
10178
|
+
}
|
|
10179
|
+
return out;
|
|
10180
|
+
}
|
|
10181
|
+
|
|
10182
|
+
function renderProofEntry(entry, prefixes) {
|
|
10183
|
+
if (!entry) return '';
|
|
10184
|
+
if (entry.kind === 'fact') {
|
|
10185
|
+
return ` ${graphForTriple(entry.fact, prefixes)}\n pe:by ${byBlankNode('fact', entry.source)}.`;
|
|
10186
|
+
}
|
|
10187
|
+
if (entry.kind === 'builtin') {
|
|
10188
|
+
return ` ${graphForTriple(entry.fact, prefixes)}\n pe:by [ pe:builtin ${termToN3(entry.builtin, prefixes)} ].`;
|
|
10189
|
+
}
|
|
10190
|
+
|
|
10191
|
+
const df = entry.df;
|
|
10192
|
+
const props = [`pe:by ${byBlankNode('rule', df.rule && df.rule.__source)}`];
|
|
10193
|
+
const bindings = renderBindingList(df, prefixes);
|
|
10194
|
+
if (bindings) props.push(`pe:binding ${bindings}`);
|
|
10195
|
+
if (df.premises && df.premises.length) {
|
|
10196
|
+
props.push(`pe:uses ${df.premises.map((prem) => graphForTriple(prem, prefixes)).join(', ')}`);
|
|
10197
|
+
}
|
|
10198
|
+
|
|
10199
|
+
let out = ` ${graphForTriple(df.fact, prefixes)}\n`;
|
|
10200
|
+
for (let i = 0; i < props.length; i++) {
|
|
10201
|
+
out += ` ${props[i]}${i === props.length - 1 ? '.' : ';'}\n`;
|
|
10202
|
+
}
|
|
10203
|
+
return out.trimEnd();
|
|
10204
|
+
}
|
|
10205
|
+
|
|
10206
|
+
function renderProofBlock(rootDf, derivedByKey, baseFactByKey, prefixes, resolveBackwardProof) {
|
|
10207
|
+
const entries = collectProofEntries(rootDf, derivedByKey, baseFactByKey, resolveBackwardProof);
|
|
10208
|
+
const rootGraph = graphForTriple(rootDf.fact, prefixes);
|
|
10209
|
+
const proofBody = entries.map((entry) => renderProofEntry(entry, prefixes)).join('\n\n');
|
|
10210
|
+
const proofGraph = proofBody ? `{
|
|
10211
|
+
${proofBody}
|
|
10212
|
+
}` : '{}';
|
|
10213
|
+
return `${rootGraph} pe:why ${proofGraph}.`;
|
|
10214
|
+
}
|
|
10215
|
+
|
|
10216
|
+
function renderProofDocument(outputDerived, allDerived, baseFacts, prefixes, backRules) {
|
|
10217
|
+
const selectedDerived = Array.isArray(outputDerived) ? outputDerived.filter((df) => df && df.fact) : [];
|
|
10218
|
+
if (!selectedDerived.length) return '';
|
|
10219
|
+
|
|
10220
|
+
const proofPrefixes = clonePrefixEnvWithProofVocabulary(prefixes);
|
|
10221
|
+
const derivedByKey = new Map();
|
|
10222
|
+
for (const df of allDerived || []) {
|
|
10223
|
+
if (!df || !df.fact) continue;
|
|
10224
|
+
const key = proofTripleKey(df.fact);
|
|
10225
|
+
if (!derivedByKey.has(key)) derivedByKey.set(key, df);
|
|
10226
|
+
}
|
|
10227
|
+
// Do not insert query-wrapper output facts here: those wrappers often derive
|
|
10228
|
+
// the same triple they use, and would hide the underlying fact/rule proof.
|
|
10229
|
+
|
|
10230
|
+
const baseFactByKey = new Map();
|
|
10231
|
+
for (const tr of baseFacts || []) {
|
|
10232
|
+
if (!tr) continue;
|
|
10233
|
+
const key = proofTripleKey(tr);
|
|
10234
|
+
if (!baseFactByKey.has(key)) baseFactByKey.set(key, tr);
|
|
10235
|
+
}
|
|
10236
|
+
|
|
10237
|
+
const resolveBackwardProof = findBackwardProofForGoal
|
|
10238
|
+
? (tr) => findBackwardProofForGoal(tr, baseFacts || [], backRules || [], { maxDepth: 64 })
|
|
10239
|
+
: null;
|
|
10240
|
+
|
|
10241
|
+
const outputTriples = collectProofOutputTriples(selectedDerived);
|
|
10242
|
+
const proofRelationTriples = [];
|
|
10243
|
+
for (const df of selectedDerived) {
|
|
10244
|
+
proofRelationTriples.push({ s: new GraphTerm([df.fact]), p: new Iri(PE_NS + 'why'), o: new GraphTerm([]) });
|
|
10245
|
+
for (const entry of collectProofEntries(df, derivedByKey, baseFactByKey, resolveBackwardProof)) {
|
|
10246
|
+
const fact = entry.kind === 'rule' ? entry.df.fact : entry.fact;
|
|
10247
|
+
const byObject = entry.kind === 'builtin' && entry.builtin ? entry.builtin : new Iri(PE_NS + 'source');
|
|
10248
|
+
proofRelationTriples.push({ s: new GraphTerm([fact]), p: new Iri(PE_NS + 'by'), o: byObject });
|
|
10249
|
+
if (entry.kind === 'rule') {
|
|
10250
|
+
for (const prem of entry.df.premises || []) proofRelationTriples.push({ s: new GraphTerm([fact]), p: new Iri(PE_NS + 'uses'), o: new GraphTerm([prem]) });
|
|
10251
|
+
}
|
|
10252
|
+
}
|
|
10253
|
+
}
|
|
10254
|
+
|
|
10255
|
+
const usedPrefixes = proofPrefixes.prefixesUsedForOutput(outputTriples.concat(proofRelationTriples));
|
|
10256
|
+
if (!usedPrefixes.some(([pfx]) => pfx === 'pe')) usedPrefixes.push(['pe', PE_NS]);
|
|
10257
|
+
usedPrefixes.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
|
|
10258
|
+
|
|
10259
|
+
const parts = [];
|
|
10260
|
+
for (const [pfx, base] of usedPrefixes) {
|
|
10261
|
+
if (!base) continue;
|
|
10262
|
+
if (pfx === '') parts.push(`@prefix : <${base}> .`);
|
|
10263
|
+
else parts.push(`@prefix ${pfx}: <${base}> .`);
|
|
10264
|
+
}
|
|
10265
|
+
if (parts.length) parts.push('');
|
|
10266
|
+
|
|
10267
|
+
parts.push(...outputTriples.map((tr) => tripleToN3(tr, proofPrefixes)));
|
|
10268
|
+
parts.push('');
|
|
10269
|
+
for (let i = 0; i < selectedDerived.length; i++) {
|
|
10270
|
+
if (i > 0) parts.push('');
|
|
10271
|
+
parts.push(renderProofBlock(selectedDerived[i], derivedByKey, baseFactByKey, proofPrefixes, resolveBackwardProof));
|
|
10272
|
+
}
|
|
10273
|
+
|
|
10274
|
+
return parts.join('\n').replace(/[ \t]+$/gm, '').replace(/\s*$/g, '') + '\n';
|
|
10275
|
+
}
|
|
10276
|
+
|
|
9603
10277
|
// ===========================================================================
|
|
9604
10278
|
// CLI entry point
|
|
9605
10279
|
// ===========================================================================
|
|
@@ -9706,7 +10380,7 @@ function makeExplain(deps) {
|
|
|
9706
10380
|
return addMarkdownHardBreaks(pairs.map((p) => p.text).join(''));
|
|
9707
10381
|
}
|
|
9708
10382
|
|
|
9709
|
-
return { printExplanation, collectOutputStringsFromFacts };
|
|
10383
|
+
return { printExplanation, renderProofDocument, collectOutputStringsFromFacts };
|
|
9710
10384
|
}
|
|
9711
10385
|
|
|
9712
10386
|
module.exports = { makeExplain };
|
|
@@ -11498,6 +12172,67 @@ function emptyParsedDocument() {
|
|
|
11498
12172
|
};
|
|
11499
12173
|
}
|
|
11500
12174
|
|
|
12175
|
+
function lineStartsForText(text) {
|
|
12176
|
+
const s = String(text);
|
|
12177
|
+
const starts = [0];
|
|
12178
|
+
for (let i = 0; i < s.length; i++) {
|
|
12179
|
+
const c = s.charCodeAt(i);
|
|
12180
|
+
if (c === 10) starts.push(i + 1); // LF
|
|
12181
|
+
else if (c === 13) { // CR or CRLF
|
|
12182
|
+
if (i + 1 < s.length && s.charCodeAt(i + 1) === 10) i++;
|
|
12183
|
+
starts.push(i + 1);
|
|
12184
|
+
}
|
|
12185
|
+
}
|
|
12186
|
+
return starts;
|
|
12187
|
+
}
|
|
12188
|
+
|
|
12189
|
+
function offsetToLineFromStarts(offset, lineStarts) {
|
|
12190
|
+
if (typeof offset !== 'number') return null;
|
|
12191
|
+
let lo = 0;
|
|
12192
|
+
let hi = lineStarts.length;
|
|
12193
|
+
while (lo + 1 < hi) {
|
|
12194
|
+
const mid = (lo + hi) >> 1;
|
|
12195
|
+
if (lineStarts[mid] <= offset) lo = mid;
|
|
12196
|
+
else hi = mid;
|
|
12197
|
+
}
|
|
12198
|
+
return lo + 1;
|
|
12199
|
+
}
|
|
12200
|
+
|
|
12201
|
+
function annotateSourceLocation(obj, label, lineStarts) {
|
|
12202
|
+
if (!obj || typeof obj.__sourceOffset !== 'number') return obj;
|
|
12203
|
+
const line = offsetToLineFromStarts(obj.__sourceOffset, lineStarts);
|
|
12204
|
+
Object.defineProperty(obj, '__source', {
|
|
12205
|
+
value: { label, line },
|
|
12206
|
+
enumerable: false,
|
|
12207
|
+
writable: false,
|
|
12208
|
+
configurable: true,
|
|
12209
|
+
});
|
|
12210
|
+
return obj;
|
|
12211
|
+
}
|
|
12212
|
+
|
|
12213
|
+
function annotateParsedSourceLocations(doc, text, label) {
|
|
12214
|
+
const lineStarts = lineStartsForText(text);
|
|
12215
|
+
for (const tr of doc.triples || []) annotateSourceLocation(tr, label, lineStarts);
|
|
12216
|
+
for (const r of doc.frules || []) annotateSourceLocation(r, label, lineStarts);
|
|
12217
|
+
for (const r of doc.brules || []) annotateSourceLocation(r, label, lineStarts);
|
|
12218
|
+
for (const r of doc.logQueryRules || []) annotateSourceLocation(r, label, lineStarts);
|
|
12219
|
+
}
|
|
12220
|
+
|
|
12221
|
+
function copySourceMetadata(from, to) {
|
|
12222
|
+
if (!from || !to) return to;
|
|
12223
|
+
for (const key of ['__sourceOffset', '__source']) {
|
|
12224
|
+
if (Object.prototype.hasOwnProperty.call(from, key)) {
|
|
12225
|
+
Object.defineProperty(to, key, {
|
|
12226
|
+
value: from[key],
|
|
12227
|
+
enumerable: false,
|
|
12228
|
+
writable: false,
|
|
12229
|
+
configurable: true,
|
|
12230
|
+
});
|
|
12231
|
+
}
|
|
12232
|
+
}
|
|
12233
|
+
return to;
|
|
12234
|
+
}
|
|
12235
|
+
|
|
11501
12236
|
function prefixesUsedInTokens(tokens, prefEnv) {
|
|
11502
12237
|
const used = new Set();
|
|
11503
12238
|
const toks = Array.isArray(tokens) ? tokens : [];
|
|
@@ -11575,6 +12310,7 @@ function parseN3Text(text, opts = {}) {
|
|
|
11575
12310
|
label = '<input>',
|
|
11576
12311
|
keepSourceArtifacts = true,
|
|
11577
12312
|
collectUsedPrefixes = false,
|
|
12313
|
+
collectSourceLocations = true,
|
|
11578
12314
|
rdf = false,
|
|
11579
12315
|
} = opts || {};
|
|
11580
12316
|
const tokens = lex(text, { rdf });
|
|
@@ -11583,6 +12319,7 @@ function parseN3Text(text, opts = {}) {
|
|
|
11583
12319
|
const [prefixes, triples, frules, brules, logQueryRules] = parser.parseDocument();
|
|
11584
12320
|
|
|
11585
12321
|
const doc = { prefixes, triples, frules, brules, logQueryRules, label };
|
|
12322
|
+
if (collectSourceLocations) annotateParsedSourceLocations(doc, text, label);
|
|
11586
12323
|
|
|
11587
12324
|
if (collectUsedPrefixes) {
|
|
11588
12325
|
Object.defineProperty(doc, 'usedPrefixes', {
|
|
@@ -11628,7 +12365,7 @@ function scopeBlankNodesInDocument(doc, sourceIndex) {
|
|
|
11628
12365
|
}
|
|
11629
12366
|
|
|
11630
12367
|
function cloneTriple(triple) {
|
|
11631
|
-
return new Triple(cloneTerm(triple.s), cloneTerm(triple.p), cloneTerm(triple.o));
|
|
12368
|
+
return copySourceMetadata(triple, new Triple(cloneTerm(triple.s), cloneTerm(triple.p), cloneTerm(triple.o)));
|
|
11632
12369
|
}
|
|
11633
12370
|
|
|
11634
12371
|
function cloneRule(rule) {
|
|
@@ -11637,12 +12374,15 @@ function scopeBlankNodesInDocument(doc, sourceIndex) {
|
|
|
11637
12374
|
for (const label of rule.headBlankLabels) headBlankLabels.add(scopedBlankLabel(label, sourceIndex, mapping));
|
|
11638
12375
|
}
|
|
11639
12376
|
|
|
11640
|
-
const out =
|
|
11641
|
-
|
|
11642
|
-
|
|
11643
|
-
|
|
11644
|
-
|
|
11645
|
-
|
|
12377
|
+
const out = copySourceMetadata(
|
|
12378
|
+
rule,
|
|
12379
|
+
new Rule(
|
|
12380
|
+
(rule.premise || []).map(cloneTriple),
|
|
12381
|
+
(rule.conclusion || []).map(cloneTriple),
|
|
12382
|
+
rule.isForward,
|
|
12383
|
+
rule.isFuse,
|
|
12384
|
+
headBlankLabels,
|
|
12385
|
+
),
|
|
11646
12386
|
);
|
|
11647
12387
|
|
|
11648
12388
|
if (rule && Object.prototype.hasOwnProperty.call(rule, '__dynamicConclusionTerm')) {
|
|
@@ -11770,6 +12510,7 @@ function parseN3SourceList(input, opts = {}) {
|
|
|
11770
12510
|
label: source.label,
|
|
11771
12511
|
baseIri: source.baseIri || (sources.length === 1 ? defaultBaseIri : ''),
|
|
11772
12512
|
collectUsedPrefixes: true,
|
|
12513
|
+
collectSourceLocations: !!opts.collectSourceLocations,
|
|
11773
12514
|
keepSourceArtifacts: !!opts.keepSourceArtifacts,
|
|
11774
12515
|
rdf: !!opts.rdf,
|
|
11775
12516
|
}),
|
|
@@ -11836,6 +12577,17 @@ function failInvalidKeywordLikeIdent(fail, tok, name) {
|
|
|
11836
12577
|
fail(`invalid_keyword(${name})`, tok);
|
|
11837
12578
|
}
|
|
11838
12579
|
|
|
12580
|
+
function annotateSourceOffset(obj, offset) {
|
|
12581
|
+
if (!obj || typeof offset !== 'number') return obj;
|
|
12582
|
+
Object.defineProperty(obj, '__sourceOffset', {
|
|
12583
|
+
value: offset,
|
|
12584
|
+
enumerable: false,
|
|
12585
|
+
writable: false,
|
|
12586
|
+
configurable: true,
|
|
12587
|
+
});
|
|
12588
|
+
return obj;
|
|
12589
|
+
}
|
|
12590
|
+
|
|
11839
12591
|
class Parser {
|
|
11840
12592
|
constructor(tokens) {
|
|
11841
12593
|
this.toks = tokens;
|
|
@@ -11960,17 +12712,19 @@ class Parser {
|
|
|
11960
12712
|
continue;
|
|
11961
12713
|
}
|
|
11962
12714
|
|
|
12715
|
+
const statementToken = this.peek();
|
|
12716
|
+
const statementOffset = statementToken && typeof statementToken.offset === 'number' ? statementToken.offset : null;
|
|
11963
12717
|
const first = this.parseTerm();
|
|
11964
12718
|
if (this.peek().typ === 'OpImplies') {
|
|
11965
12719
|
this.next();
|
|
11966
12720
|
const second = this.parseTerm();
|
|
11967
12721
|
this.expectDot();
|
|
11968
|
-
forwardRules.push(this.makeRule(first, second, true));
|
|
12722
|
+
forwardRules.push(annotateSourceOffset(this.makeRule(first, second, true), statementOffset));
|
|
11969
12723
|
} else if (this.peek().typ === 'OpImpliedBy') {
|
|
11970
12724
|
this.next();
|
|
11971
12725
|
const second = this.parseTerm();
|
|
11972
12726
|
this.expectDot();
|
|
11973
|
-
backwardRules.push(this.makeRule(first, second, false));
|
|
12727
|
+
backwardRules.push(annotateSourceOffset(this.makeRule(first, second, false), statementOffset));
|
|
11974
12728
|
} else {
|
|
11975
12729
|
let more;
|
|
11976
12730
|
|
|
@@ -11988,16 +12742,17 @@ class Parser {
|
|
|
11988
12742
|
}
|
|
11989
12743
|
|
|
11990
12744
|
// normalize explicit log:implies / log:impliedBy at top-level
|
|
11991
|
-
for (const
|
|
12745
|
+
for (const tr0 of more) {
|
|
12746
|
+
const tr = annotateSourceOffset(tr0, statementOffset);
|
|
11992
12747
|
if (isLogImplies(tr.p) && tr.s instanceof GraphTerm && tr.o instanceof GraphTerm) {
|
|
11993
|
-
forwardRules.push(this.makeRule(tr.s, tr.o, true));
|
|
12748
|
+
forwardRules.push(annotateSourceOffset(this.makeRule(tr.s, tr.o, true), statementOffset));
|
|
11994
12749
|
} else if (isLogImpliedBy(tr.p) && tr.s instanceof GraphTerm && tr.o instanceof GraphTerm) {
|
|
11995
|
-
backwardRules.push(this.makeRule(tr.s, tr.o, false));
|
|
12750
|
+
backwardRules.push(annotateSourceOffset(this.makeRule(tr.s, tr.o, false), statementOffset));
|
|
11996
12751
|
} else if (isLogQuery(tr.p) && tr.s instanceof GraphTerm && tr.o instanceof GraphTerm) {
|
|
11997
12752
|
// Output-selection directive: { premise } log:query { conclusion }.
|
|
11998
12753
|
// When present at top-level, eyeling prints only the instantiated conclusion
|
|
11999
12754
|
// triples (unique) instead of all newly derived facts.
|
|
12000
|
-
logQueries.push(this.makeRule(tr.s, tr.o, true));
|
|
12755
|
+
logQueries.push(annotateSourceOffset(this.makeRule(tr.s, tr.o, true), statementOffset));
|
|
12001
12756
|
} else {
|
|
12002
12757
|
triples.push(tr);
|
|
12003
12758
|
}
|
|
@@ -14938,6 +15693,7 @@ module.exports = {
|
|
|
14938
15693
|
if (typeof __entry.materializeRdfLists === "function") __outerSelf.materializeRdfLists = __entry.materializeRdfLists;
|
|
14939
15694
|
if (typeof __entry.isGroundTriple === "function") __outerSelf.isGroundTriple = __entry.isGroundTriple;
|
|
14940
15695
|
if (typeof __entry.printExplanation === "function") __outerSelf.printExplanation = __entry.printExplanation;
|
|
15696
|
+
if (typeof __entry.renderProofDocument === "function") __outerSelf.renderProofDocument = __entry.renderProofDocument;
|
|
14941
15697
|
if (typeof __entry.tripleToN3 === "function") __outerSelf.tripleToN3 = __entry.tripleToN3;
|
|
14942
15698
|
if (typeof __entry.collectOutputStringsFromFacts === "function") __outerSelf.collectOutputStringsFromFacts = __entry.collectOutputStringsFromFacts;
|
|
14943
15699
|
if (typeof __entry.prettyPrintQueryTriples === "function") __outerSelf.prettyPrintQueryTriples = __entry.prettyPrintQueryTriples;
|