eyeling 1.12.15 → 1.13.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 CHANGED
@@ -761,6 +761,8 @@ A predicate is treated as builtin if:
761
761
 
762
762
  Super restricted mode exists to let you treat all other predicates as ordinary facts/rules without any built-in evaluation.
763
763
 
764
+ **Note on `log:query`:** Eyeling also recognizes a special _top-level_ directive of the form `{...} log:query {...}.` to **select which results to print**. This is **not** a builtin predicate (it is not evaluated as part of goal solving); it is handled by the parser/CLI/output layer. See §11.3.5 below and Chapter 13 for details.
765
+
764
766
  ### 11.2 Built-ins return multiple solutions
765
767
 
766
768
  Every builtin returns a list of substitution _deltas_.
@@ -1353,6 +1355,47 @@ As _builtins_, `log:implies` and `log:impliedBy` let you **inspect the currently
1353
1355
 
1354
1356
  Each enumerated rule is standardized apart (fresh variable names) before unification so you can safely query over it.
1355
1357
 
1358
+ ### Top-level directive: `log:query` (output selection)
1359
+
1360
+ **Shape (top level only):**
1361
+
1362
+ ```n3
1363
+ { ...premise... } log:query { ...conclusion... }.
1364
+ ```
1365
+
1366
+ `log:query` is best understood as an **output projection**, not as a rule and not as a normal builtin:
1367
+
1368
+ - Eyeling still computes the saturated forward closure (facts + rules, including backward-rule proofs where needed).
1369
+ - It then proves the **premise formula** as a goal (as if it were fed to `log:includes` in the global scope).
1370
+ - For every solution, it instantiates the **conclusion formula** and collects the resulting triples.
1371
+ - The final output is the **set of unique ground triples** from those instantiated conclusions.
1372
+
1373
+ This is “forward-rule-like” in spirit (premise ⇒ conclusion), but the instantiated conclusion triples are **not added back into the fact store**; they are just what Eyeling prints.
1374
+
1375
+ **Important details:**
1376
+
1377
+ - Only **top-level** `{...} log:query {...}.` directives are recognized. Inside quoted formulas (or inside rule bodies/heads) it is just an ordinary triple.
1378
+ - Query-mode output depends on the saturated closure, so it cannot be streamed; `--stream` has no effect when any `log:query` directives are present.
1379
+ - If you want _logical_ querying inside a rule/proof, use `log:includes` (and optionally `log:conclusion`) instead.
1380
+
1381
+ **Example (project a result set):**
1382
+
1383
+ ```n3
1384
+ @prefix : <urn:ex:>.
1385
+ @prefix log: <http://www.w3.org/2000/10/swap/log#>.
1386
+
1387
+ { :a :p ?x } => { :a :q ?x }.
1388
+ :a :p :b.
1389
+
1390
+ { :a :q ?x } log:query { :result :x ?x }.
1391
+ ```
1392
+
1393
+ Output (only):
1394
+
1395
+ ```n3
1396
+ :result :x :b .
1397
+ ```
1398
+
1356
1399
  ### Scoped proof inside formulas: `log:includes` and friends
1357
1400
 
1358
1401
  #### `log:includes`
@@ -1809,6 +1852,14 @@ See also: [Chapter 14 — Entry points: CLI, bundle exports, and npm API](#ch14)
1809
1852
 
1810
1853
  By default, Eyeling prints **newly derived forward facts** (the heads of fired `=>` rules), serialized as N3. It does **not** reprint your input facts.
1811
1854
 
1855
+ If the input contains one or more **top-level** `log:query` directives:
1856
+
1857
+ ```n3
1858
+ { ...premise... } log:query { ...conclusion... }.
1859
+ ```
1860
+
1861
+ Eyeling still computes the saturated forward closure, but it prints only the **unique instantiated conclusion triples** of those `log:query` directives (instead of all newly derived facts). This is useful when you want a forward-rule-like projection of results.
1862
+
1812
1863
  For proof/explanation output and output modes, see:
1813
1864
 
1814
1865
  - [Chapter 13 — Printing, proofs, and the user-facing output](#ch13)
@@ -1835,6 +1886,8 @@ Options:
1835
1886
  -v, --version Print version and exit.
1836
1887
  ```
1837
1888
 
1889
+ Note: when `log:query` directives are present, Eyeling cannot stream output (the selected results depend on the saturated closure), so `--stream` has no effect in that mode.
1890
+
1838
1891
  See also:
1839
1892
 
1840
1893
  - [Chapter 13 — Printing, proofs, and the user-facing output](#ch13)
@@ -2031,7 +2084,7 @@ If you don’t want “stop the world”, derive a `:Violation` fact instead, an
2031
2084
  The most robust way to keep LLM-generated logic plausible is to make it live under tests:
2032
2085
 
2033
2086
  - Keep tiny **fixtures** (facts) alongside the rules.
2034
- - Run Eyeling to produce the **derived closure** (Eyeling can emit only newly derived forward facts, and can optionally include compact proof comments).
2087
+ - Run Eyeling to produce the **derived closure** (Eyeling emits only newly derived forward facts by default, can optionally include compact proof comments, and can also use `log:query` directives to project a specific result set).
2035
2088
  - Compare against an expected output (“golden file”) in CI.
2036
2089
 
2037
2090
  This turns rule edits into a normal change-management loop: diffs are explicit, reviewable, and reproducible.
package/README.md CHANGED
@@ -4,7 +4,8 @@ A compact [Notation3 (N3)](https://notation3.org/) reasoner in **JavaScript**.
4
4
 
5
5
  - Single self-contained bundle (`eyeling.js`), no external runtime deps
6
6
  - Forward (`=>`) + backward (`<=`) chaining over Horn-style rules
7
- - Outputs only **newly derived** forward facts (optionally with compact proof comments)
7
+ - Outputs only **newly derived** forward facts by default (optionally with compact proof comments)
8
+ - If the input contains one or more top-level `{ ... } log:query { ... }.` directives, the output becomes the **unique instantiated conclusion triples** of those queries (a forward-rule-like projection)
8
9
  - Works in Node.js and fully client-side (browser/worker)
9
10
 
10
11
  ## Links
@@ -45,6 +46,16 @@ See all options:
45
46
  npx eyeling --help
46
47
  ```
47
48
 
49
+ ### log:query output selection
50
+
51
+ If your input contains one or more **top-level** directives of the form:
52
+
53
+ ```n3
54
+ { ?x a :Human. } log:query { ?x a :Mortal. }.
55
+ ```
56
+
57
+ Eyeling will still compute the saturated forward closure, but it will **print only** the **unique instantiated conclusion triples** of those `log:query` directives (instead of printing all newly derived forward facts).
58
+
48
59
  ### JavaScript API
49
60
 
50
61
  CommonJS:
@@ -79,6 +90,9 @@ const { closureN3 } = eyeling.reasonStream(input, {
79
90
  proof: false,
80
91
  onDerived: ({ triple }) => console.log(triple),
81
92
  });
93
+
94
+ // With log:query directives present, closureN3 contains the query-selected triples.
95
+ // The return value also includes `queryMode`, `queryTriples`, and `queryDerived`.
82
96
  ```
83
97
 
84
98
  > Note: the npm `reason()` helper shells out to the bundled `eyeling.js` CLI for simplicity and robustness.
@@ -1,5 +1,5 @@
1
1
  # ===========================================================================================
2
- # ODRL + DPV/DPV-RISK risk assessment with ranked, explainable output.
2
+ # ODRL + DPV risk assessment with ranked, explainable output.
3
3
  #
4
4
  # What this file does
5
5
  # - Models an agreement as an ODRL policy (odrl:Policy) containing permissions,
@@ -1,3 +1,4 @@
1
1
  @prefix : <http://example.org/socrates#> .
2
2
 
3
+ :Socrates a :Human .
3
4
  :Socrates a :Mortal .
@@ -3,6 +3,7 @@
3
3
  # ==================
4
4
 
5
5
  @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
6
+ @prefix log: <http://www.w3.org/2000/10/swap/log#>.
6
7
  @prefix : <http://example.org/socrates#>.
7
8
 
8
9
  # facts
@@ -17,3 +18,5 @@
17
18
  ?S a ?B.
18
19
  }.
19
20
 
21
+ # query
22
+ { ?S a ?C } log:query { ?S a ?C }.
@@ -256,6 +256,9 @@ log:implies a ex:Builtin ; ex:kind ex:Relation ;
256
256
  log:impliedBy a ex:Builtin ; ex:kind ex:Relation ;
257
257
  rdfs:comment "Rule/formula relation used for <=." .
258
258
 
259
+ log:query a ex:Builtin ; ex:kind ex:Meta ;
260
+ rdfs:comment "Output-selection directive: a top-level triple {premise} log:query {conclusion}. does not add facts, but selects the unique instantiated conclusion triples as Eyeling's output." .
261
+
259
262
  log:includes a ex:Builtin ; ex:kind ex:Generator ;
260
263
  rdfs:comment "Proves the object formula in the (possibly scoped) facts/rules; returns the set of proof substitutions (may bind variables)." .
261
264
 
package/eyeling.js CHANGED
@@ -3798,11 +3798,11 @@ function main() {
3798
3798
  }
3799
3799
 
3800
3800
  let toks;
3801
- let prefixes, triples, frules, brules;
3801
+ let prefixes, triples, frules, brules, qrules;
3802
3802
  try {
3803
3803
  toks = engine.lex(text);
3804
3804
  const parser = new engine.Parser(toks);
3805
- [prefixes, triples, frules, brules] = parser.parseDocument();
3805
+ [prefixes, triples, frules, brules, qrules] = parser.parseDocument();
3806
3806
  // Make the parsed prefixes available to log:trace output (CLI path)
3807
3807
  engine.setTracePrefixes(prefixes);
3808
3808
  } catch (e) {
@@ -3822,13 +3822,17 @@ function main() {
3822
3822
  }
3823
3823
  return value;
3824
3824
  }
3825
+ // For backwards compatibility, --ast prints exactly four top-level elements:
3826
+ // [prefixes, triples, forwardRules, backwardRules]
3827
+ // log:query directives are output-selection statements and are not included
3828
+ // in the legacy AST contract expected by test suites and downstream tools.
3825
3829
  console.log(JSON.stringify([prefixes, triples, frules, brules], astReplacer, 2));
3826
3830
  process.exit(0);
3827
3831
  }
3828
3832
 
3829
3833
  // Materialize anonymous rdf:first/rdf:rest collections into list terms.
3830
3834
  // Named list nodes keep identity; list:* builtins can traverse them.
3831
- engine.materializeRdfLists(triples, frules, brules);
3835
+ engine.materializeRdfLists(triples, frules.concat(qrules || []), brules);
3832
3836
 
3833
3837
  const facts = triples.filter((tr) => engine.isGroundTriple(tr));
3834
3838
 
@@ -3924,7 +3928,9 @@ function main() {
3924
3928
  }
3925
3929
 
3926
3930
  // Streaming mode: print (input) prefixes first, then print derived triples as soon as they are found.
3927
- if (streamMode) {
3931
+ // Note: when log:query directives are present, we cannot stream output because
3932
+ // the selected results depend on the saturated closure.
3933
+ if (streamMode && !(Array.isArray(qrules) && qrules.length)) {
3928
3934
  const usedInInput = prefixesUsedInInputTokens(toks, prefixes);
3929
3935
  const outPrefixes = restrictPrefixEnv(prefixes, usedInInput);
3930
3936
 
@@ -3953,18 +3959,36 @@ function main() {
3953
3959
  return;
3954
3960
  }
3955
3961
 
3956
- // Default (non-streaming): derive everything first, then print only the newly derived facts.
3957
- const derived = engine.forwardChain(facts, frules, brules);
3958
- const derivedTriples = derived.map((df) => df.fact);
3959
- const usedPrefixes = prefixes.prefixesUsedForOutput(derivedTriples);
3962
+ const hasQueries = Array.isArray(qrules) && qrules.length;
3963
+
3964
+ // Default (non-streaming):
3965
+ // - without log:query: derive everything first, then print only newly derived facts
3966
+ // - with log:query: derive everything first, then print only unique instantiated
3967
+ // conclusion triples from the log:query directives.
3968
+ let derived = [];
3969
+ let outTriples = [];
3970
+ let outDerived = [];
3971
+
3972
+ if (hasQueries) {
3973
+ const res = engine.forwardChainAndCollectLogQueryConclusions(facts, frules, brules, qrules);
3974
+ derived = res.derived;
3975
+ outTriples = res.queryTriples;
3976
+ outDerived = res.queryDerived;
3977
+ } else {
3978
+ derived = engine.forwardChain(facts, frules, brules);
3979
+ outDerived = derived;
3980
+ outTriples = derived.map((df) => df.fact);
3981
+ }
3982
+
3983
+ const usedPrefixes = prefixes.prefixesUsedForOutput(outTriples);
3960
3984
 
3961
3985
  for (const [pfx, base] of usedPrefixes) {
3962
3986
  if (pfx === '') console.log(`@prefix : <${base}> .`);
3963
3987
  else console.log(`@prefix ${pfx}: <${base}> .`);
3964
3988
  }
3965
- if (derived.length && usedPrefixes.length) console.log();
3989
+ if (outTriples.length && usedPrefixes.length) console.log();
3966
3990
 
3967
- for (const df of derived) {
3991
+ for (const df of outDerived) {
3968
3992
  if (engine.getProofCommentsEnabled()) {
3969
3993
  engine.printExplanation(df, prefixes);
3970
3994
  console.log(engine.tripleToN3(df.fact, prefixes));
@@ -6820,6 +6844,159 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
6820
6844
  }
6821
6845
  }
6822
6846
 
6847
+ // ---------------------------------------------------------------------------
6848
+ // log:query output selection
6849
+ // ---------------------------------------------------------------------------
6850
+ // A top-level directive of the form:
6851
+ // { premise } log:query { conclusion }.
6852
+ // does not add facts to the closure. Instead, when one or more such directives
6853
+ // are present in the input, eyeling outputs only the **unique instantiated**
6854
+ // conclusion triples for each solution of the premise (similar to a forward
6855
+ // rule head projection).
6856
+
6857
+ function __tripleKeyForOutput(tr) {
6858
+ // Use a canonical structural encoding (covers lists and quoted graphs).
6859
+ // Note: this is used only for de-duplication of output triples.
6860
+ return skolemKeyFromTerm(tr.s) + '\t' + skolemKeyFromTerm(tr.p) + '\t' + skolemKeyFromTerm(tr.o);
6861
+ }
6862
+
6863
+ function __withScopedSnapshotForQueries(facts, fn) {
6864
+ // Some scoped log:* builtins "delay" unless a frozen snapshot exists.
6865
+ // After forwardChain completes, we create a snapshot of the saturated
6866
+ // closure so query premises can use scoped builtins reliably.
6867
+ const oldSnap = hasOwn.call(facts, '__scopedSnapshot') ? facts.__scopedSnapshot : undefined;
6868
+ const oldLvl = hasOwn.call(facts, '__scopedClosureLevel') ? facts.__scopedClosureLevel : undefined;
6869
+
6870
+ // Create a frozen snapshot of the saturated closure.
6871
+ const snap = facts.slice();
6872
+ ensureFactIndexes(snap);
6873
+ Object.defineProperty(snap, '__scopedSnapshot', {
6874
+ value: snap,
6875
+ enumerable: false,
6876
+ writable: true,
6877
+ configurable: true,
6878
+ });
6879
+ Object.defineProperty(snap, '__scopedClosureLevel', {
6880
+ value: Number.MAX_SAFE_INTEGER,
6881
+ enumerable: false,
6882
+ writable: true,
6883
+ configurable: true,
6884
+ });
6885
+
6886
+ // Ensure the live facts array exposes the snapshot/level for builtins.
6887
+ if (!hasOwn.call(facts, '__scopedSnapshot')) {
6888
+ Object.defineProperty(facts, '__scopedSnapshot', {
6889
+ value: null,
6890
+ enumerable: false,
6891
+ writable: true,
6892
+ configurable: true,
6893
+ });
6894
+ }
6895
+ if (!hasOwn.call(facts, '__scopedClosureLevel')) {
6896
+ Object.defineProperty(facts, '__scopedClosureLevel', {
6897
+ value: 0,
6898
+ enumerable: false,
6899
+ writable: true,
6900
+ configurable: true,
6901
+ });
6902
+ }
6903
+
6904
+ facts.__scopedSnapshot = snap;
6905
+ facts.__scopedClosureLevel = Number.MAX_SAFE_INTEGER;
6906
+
6907
+ try {
6908
+ return fn();
6909
+ } finally {
6910
+ facts.__scopedSnapshot = oldSnap === undefined ? null : oldSnap;
6911
+ facts.__scopedClosureLevel = oldLvl === undefined ? 0 : oldLvl;
6912
+ }
6913
+ }
6914
+
6915
+ function collectLogQueryConclusions(logQueryRules, facts, backRules) {
6916
+ const queryTriples = [];
6917
+ const queryDerived = [];
6918
+ const seen = new Set();
6919
+
6920
+ if (!Array.isArray(logQueryRules) || logQueryRules.length === 0) {
6921
+ return { queryTriples, queryDerived };
6922
+ }
6923
+
6924
+ ensureFactIndexes(facts);
6925
+ ensureBackRuleIndexes(backRules);
6926
+
6927
+ // Shared state across all query firings (mirrors forwardChain()).
6928
+ const varGen = [0];
6929
+ const skCounter = [0];
6930
+ const headSkolemCache = new Map();
6931
+
6932
+ return __withScopedSnapshotForQueries(facts, () => {
6933
+ for (let qi = 0; qi < logQueryRules.length; qi++) {
6934
+ const r = logQueryRules[qi];
6935
+ if (!r || !Array.isArray(r.premise) || !Array.isArray(r.conclusion)) continue;
6936
+
6937
+ const sols = proveGoals(r.premise, null, facts, backRules, 0, null, varGen, undefined, {
6938
+ deferBuiltins: true,
6939
+ });
6940
+
6941
+ for (const s of sols) {
6942
+ const skMap = {};
6943
+ const instantiatedPremises = r.premise.map((b) => applySubstTriple(b, s));
6944
+ const fireKey = __firingKey(1000000 + qi, instantiatedPremises);
6945
+
6946
+ // Support dynamic heads (same semantics as forwardChain).
6947
+ let dynamicHeadTriples = null;
6948
+ let headBlankLabelsHere = r.headBlankLabels;
6949
+ if (r.__dynamicConclusionTerm) {
6950
+ const dynTerm = applySubstTerm(r.__dynamicConclusionTerm, s);
6951
+ const dynTriples = __graphTriplesOrTrue(dynTerm);
6952
+ dynamicHeadTriples = dynTriples !== null ? dynTriples : [];
6953
+ const dynHeadBlankLabels =
6954
+ dynamicHeadTriples && dynamicHeadTriples.length ? collectBlankLabelsInTriples(dynamicHeadTriples) : null;
6955
+ if (dynHeadBlankLabels && dynHeadBlankLabels.size) {
6956
+ headBlankLabelsHere = new Set([...headBlankLabelsHere, ...dynHeadBlankLabels]);
6957
+ }
6958
+ }
6959
+
6960
+ const headPatterns =
6961
+ dynamicHeadTriples && dynamicHeadTriples.length ? r.conclusion.concat(dynamicHeadTriples) : r.conclusion;
6962
+
6963
+ for (const cpat of headPatterns) {
6964
+ const instantiated = applySubstTriple(cpat, s);
6965
+ const inst = skolemizeTripleForHeadBlanks(
6966
+ instantiated,
6967
+ headBlankLabelsHere,
6968
+ skMap,
6969
+ skCounter,
6970
+ fireKey,
6971
+ headSkolemCache,
6972
+ );
6973
+ if (!isGroundTriple(inst)) continue;
6974
+ const k = __tripleKeyForOutput(inst);
6975
+ if (seen.has(k)) continue;
6976
+ seen.add(k);
6977
+ queryTriples.push(inst);
6978
+ queryDerived.push(new DerivedFact(inst, r, instantiatedPremises.slice(), { ...s }));
6979
+ }
6980
+ }
6981
+ }
6982
+
6983
+ return { queryTriples, queryDerived };
6984
+ });
6985
+ }
6986
+
6987
+ function forwardChainAndCollectLogQueryConclusions(facts, forwardRules, backRules, logQueryRules, onDerived) {
6988
+ __enterReasoningRun();
6989
+ try {
6990
+ // Forward chain first (saturates `facts`).
6991
+ const derived = forwardChain(facts, forwardRules, backRules, onDerived);
6992
+ // Then collect query conclusions against the saturated closure.
6993
+ const { queryTriples, queryDerived } = collectLogQueryConclusions(logQueryRules, facts, backRules);
6994
+ return { derived, queryTriples, queryDerived };
6995
+ } finally {
6996
+ __exitReasoningRun();
6997
+ }
6998
+ }
6999
+
6823
7000
  // (proof printing + log:outputString moved to lib/explain.js)
6824
7001
 
6825
7002
  function reasonStream(n3Text, opts = {}) {
@@ -6839,32 +7016,62 @@ function reasonStream(n3Text, opts = {}) {
6839
7016
  const parser = new Parser(toks);
6840
7017
  if (baseIri) parser.prefixes.setBase(baseIri);
6841
7018
 
6842
- const [prefixes, triples, frules, brules] = parser.parseDocument();
7019
+ const [prefixes, triples, frules, brules, logQueryRules] = parser.parseDocument();
6843
7020
  // Make the parsed prefixes available to log:trace output
6844
7021
  trace.setTracePrefixes(prefixes);
6845
7022
 
6846
7023
  // Materialize anonymous rdf:first/rdf:rest collections into list terms.
6847
7024
  // Named list nodes keep identity; list:* builtins can traverse them.
6848
- materializeRdfLists(triples, frules, brules);
7025
+ materializeRdfLists(triples, frules.concat(logQueryRules || []), brules);
6849
7026
 
6850
7027
  // facts becomes the saturated closure because pushFactIndexed(...) appends into it
6851
7028
  const facts = triples.filter((tr) => isGroundTriple(tr));
6852
7029
 
6853
- const derived = forwardChain(facts, frules, brules, (df) => {
7030
+ let derived = [];
7031
+ let queryTriples = [];
7032
+ let queryDerived = [];
7033
+
7034
+ if (Array.isArray(logQueryRules) && logQueryRules.length) {
7035
+ // Query-selection mode: derive full closure, then output only the unique
7036
+ // instantiated conclusion triples of the log:query directives.
7037
+ const res = forwardChainAndCollectLogQueryConclusions(facts, frules, brules, logQueryRules);
7038
+ derived = res.derived;
7039
+ queryTriples = res.queryTriples;
7040
+ queryDerived = res.queryDerived;
7041
+
7042
+ // For compatibility with the streaming callback signature, we emit the
7043
+ // query-selected triples (not all derived facts).
6854
7044
  if (typeof onDerived === 'function') {
6855
- onDerived({
6856
- triple: tripleToN3(df.fact, prefixes),
6857
- df,
6858
- });
7045
+ for (const qdf of queryDerived) {
7046
+ onDerived({ triple: tripleToN3(qdf.fact, prefixes), df: qdf });
7047
+ }
6859
7048
  }
6860
- });
7049
+ } else {
7050
+ // Default mode: output only newly derived forward facts.
7051
+ derived = forwardChain(facts, frules, brules, (df) => {
7052
+ if (typeof onDerived === 'function') {
7053
+ onDerived({
7054
+ triple: tripleToN3(df.fact, prefixes),
7055
+ df,
7056
+ });
7057
+ }
7058
+ });
7059
+ }
6861
7060
 
6862
- const closureTriples = includeInputFactsInClosure ? facts : derived.map((d) => d.fact);
7061
+ const closureTriples =
7062
+ Array.isArray(logQueryRules) && logQueryRules.length
7063
+ ? queryTriples
7064
+ : includeInputFactsInClosure
7065
+ ? facts
7066
+ : derived.map((d) => d.fact);
6863
7067
 
6864
7068
  const __out = {
6865
7069
  prefixes,
6866
7070
  facts, // saturated closure (Triple[])
6867
7071
  derived, // DerivedFact[]
7072
+ queryMode: Array.isArray(logQueryRules) && logQueryRules.length ? true : false,
7073
+ queryTriples,
7074
+ queryDerived,
6868
7075
  closureN3: closureTriples.map((t) => tripleToN3(t, prefixes)).join('\n'),
6869
7076
  };
6870
7077
  deref.setEnforceHttpsEnabled(__oldEnforceHttps);
@@ -6917,6 +7124,8 @@ function setTracePrefixes(v) {
6917
7124
 
6918
7125
  module.exports = {
6919
7126
  reasonStream,
7127
+ collectLogQueryConclusions,
7128
+ forwardChainAndCollectLogQueryConclusions,
6920
7129
  collectOutputStringsFromFacts,
6921
7130
  main,
6922
7131
  version,
@@ -6969,6 +7178,8 @@ module.exports = {
6969
7178
  lex: engine.lex,
6970
7179
  Parser: engine.Parser,
6971
7180
  forwardChain: engine.forwardChain,
7181
+ collectLogQueryConclusions: engine.collectLogQueryConclusions,
7182
+ forwardChainAndCollectLogQueryConclusions: engine.forwardChainAndCollectLogQueryConclusions,
6972
7183
  materializeRdfLists: engine.materializeRdfLists,
6973
7184
  isGroundTriple: engine.isGroundTriple,
6974
7185
  printExplanation: engine.printExplanation,
@@ -7879,6 +8090,7 @@ const {
7879
8090
  collectBlankLabelsInTriples,
7880
8091
  isLogImplies,
7881
8092
  isLogImpliedBy,
8093
+ isLogQuery,
7882
8094
  } = require('./prelude');
7883
8095
 
7884
8096
  const { N3SyntaxError } = require('./lexer');
@@ -7927,6 +8139,7 @@ class Parser {
7927
8139
  const triples = [];
7928
8140
  const forwardRules = [];
7929
8141
  const backwardRules = [];
8142
+ const logQueries = [];
7930
8143
 
7931
8144
  while (this.peek().typ !== 'EOF') {
7932
8145
  if (this.peek().typ === 'AtPrefix') {
@@ -8001,6 +8214,11 @@ class Parser {
8001
8214
  forwardRules.push(this.makeRule(tr.s, tr.o, true));
8002
8215
  } else if (isLogImpliedBy(tr.p) && tr.s instanceof GraphTerm && tr.o instanceof GraphTerm) {
8003
8216
  backwardRules.push(this.makeRule(tr.s, tr.o, false));
8217
+ } else if (isLogQuery(tr.p) && tr.s instanceof GraphTerm && tr.o instanceof GraphTerm) {
8218
+ // Output-selection directive: { premise } log:query { conclusion }.
8219
+ // When present at top-level, eyeling prints only the instantiated conclusion
8220
+ // triples (unique) instead of all newly derived facts.
8221
+ logQueries.push(this.makeRule(tr.s, tr.o, true));
8004
8222
  } else {
8005
8223
  triples.push(tr);
8006
8224
  }
@@ -8009,7 +8227,7 @@ class Parser {
8009
8227
  }
8010
8228
  }
8011
8229
 
8012
- return [this.prefixes, triples, forwardRules, backwardRules];
8230
+ return [this.prefixes, triples, forwardRules, backwardRules, logQueries];
8013
8231
  }
8014
8232
 
8015
8233
  parsePrefixDirective() {
@@ -8950,6 +9168,10 @@ function isLogImpliedBy(p) {
8950
9168
  return p instanceof Iri && p.value === LOG_NS + 'impliedBy';
8951
9169
  }
8952
9170
 
9171
+ function isLogQuery(p) {
9172
+ return p instanceof Iri && p.value === LOG_NS + 'query';
9173
+ }
9174
+
8953
9175
  // ===========================================================================
8954
9176
  // PREFIX ENVIRONMENT
8955
9177
  // ===========================================================================
@@ -9161,6 +9383,7 @@ module.exports = {
9161
9383
  isOwlSameAsPred,
9162
9384
  isLogImplies,
9163
9385
  isLogImpliedBy,
9386
+ isLogQuery,
9164
9387
  PrefixEnv,
9165
9388
  collectIrisInTerm,
9166
9389
  varsInRule,
package/lib/cli.js CHANGED
@@ -137,11 +137,11 @@ function main() {
137
137
  }
138
138
 
139
139
  let toks;
140
- let prefixes, triples, frules, brules;
140
+ let prefixes, triples, frules, brules, qrules;
141
141
  try {
142
142
  toks = engine.lex(text);
143
143
  const parser = new engine.Parser(toks);
144
- [prefixes, triples, frules, brules] = parser.parseDocument();
144
+ [prefixes, triples, frules, brules, qrules] = parser.parseDocument();
145
145
  // Make the parsed prefixes available to log:trace output (CLI path)
146
146
  engine.setTracePrefixes(prefixes);
147
147
  } catch (e) {
@@ -161,13 +161,17 @@ function main() {
161
161
  }
162
162
  return value;
163
163
  }
164
+ // For backwards compatibility, --ast prints exactly four top-level elements:
165
+ // [prefixes, triples, forwardRules, backwardRules]
166
+ // log:query directives are output-selection statements and are not included
167
+ // in the legacy AST contract expected by test suites and downstream tools.
164
168
  console.log(JSON.stringify([prefixes, triples, frules, brules], astReplacer, 2));
165
169
  process.exit(0);
166
170
  }
167
171
 
168
172
  // Materialize anonymous rdf:first/rdf:rest collections into list terms.
169
173
  // Named list nodes keep identity; list:* builtins can traverse them.
170
- engine.materializeRdfLists(triples, frules, brules);
174
+ engine.materializeRdfLists(triples, frules.concat(qrules || []), brules);
171
175
 
172
176
  const facts = triples.filter((tr) => engine.isGroundTriple(tr));
173
177
 
@@ -263,7 +267,9 @@ function main() {
263
267
  }
264
268
 
265
269
  // Streaming mode: print (input) prefixes first, then print derived triples as soon as they are found.
266
- if (streamMode) {
270
+ // Note: when log:query directives are present, we cannot stream output because
271
+ // the selected results depend on the saturated closure.
272
+ if (streamMode && !(Array.isArray(qrules) && qrules.length)) {
267
273
  const usedInInput = prefixesUsedInInputTokens(toks, prefixes);
268
274
  const outPrefixes = restrictPrefixEnv(prefixes, usedInInput);
269
275
 
@@ -292,18 +298,36 @@ function main() {
292
298
  return;
293
299
  }
294
300
 
295
- // Default (non-streaming): derive everything first, then print only the newly derived facts.
296
- const derived = engine.forwardChain(facts, frules, brules);
297
- const derivedTriples = derived.map((df) => df.fact);
298
- const usedPrefixes = prefixes.prefixesUsedForOutput(derivedTriples);
301
+ const hasQueries = Array.isArray(qrules) && qrules.length;
302
+
303
+ // Default (non-streaming):
304
+ // - without log:query: derive everything first, then print only newly derived facts
305
+ // - with log:query: derive everything first, then print only unique instantiated
306
+ // conclusion triples from the log:query directives.
307
+ let derived = [];
308
+ let outTriples = [];
309
+ let outDerived = [];
310
+
311
+ if (hasQueries) {
312
+ const res = engine.forwardChainAndCollectLogQueryConclusions(facts, frules, brules, qrules);
313
+ derived = res.derived;
314
+ outTriples = res.queryTriples;
315
+ outDerived = res.queryDerived;
316
+ } else {
317
+ derived = engine.forwardChain(facts, frules, brules);
318
+ outDerived = derived;
319
+ outTriples = derived.map((df) => df.fact);
320
+ }
321
+
322
+ const usedPrefixes = prefixes.prefixesUsedForOutput(outTriples);
299
323
 
300
324
  for (const [pfx, base] of usedPrefixes) {
301
325
  if (pfx === '') console.log(`@prefix : <${base}> .`);
302
326
  else console.log(`@prefix ${pfx}: <${base}> .`);
303
327
  }
304
- if (derived.length && usedPrefixes.length) console.log();
328
+ if (outTriples.length && usedPrefixes.length) console.log();
305
329
 
306
- for (const df of derived) {
330
+ for (const df of outDerived) {
307
331
  if (engine.getProofCommentsEnabled()) {
308
332
  engine.printExplanation(df, prefixes);
309
333
  console.log(engine.tripleToN3(df.fact, prefixes));
package/lib/engine.js CHANGED
@@ -2378,6 +2378,159 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
2378
2378
  }
2379
2379
  }
2380
2380
 
2381
+ // ---------------------------------------------------------------------------
2382
+ // log:query output selection
2383
+ // ---------------------------------------------------------------------------
2384
+ // A top-level directive of the form:
2385
+ // { premise } log:query { conclusion }.
2386
+ // does not add facts to the closure. Instead, when one or more such directives
2387
+ // are present in the input, eyeling outputs only the **unique instantiated**
2388
+ // conclusion triples for each solution of the premise (similar to a forward
2389
+ // rule head projection).
2390
+
2391
+ function __tripleKeyForOutput(tr) {
2392
+ // Use a canonical structural encoding (covers lists and quoted graphs).
2393
+ // Note: this is used only for de-duplication of output triples.
2394
+ return skolemKeyFromTerm(tr.s) + '\t' + skolemKeyFromTerm(tr.p) + '\t' + skolemKeyFromTerm(tr.o);
2395
+ }
2396
+
2397
+ function __withScopedSnapshotForQueries(facts, fn) {
2398
+ // Some scoped log:* builtins "delay" unless a frozen snapshot exists.
2399
+ // After forwardChain completes, we create a snapshot of the saturated
2400
+ // closure so query premises can use scoped builtins reliably.
2401
+ const oldSnap = hasOwn.call(facts, '__scopedSnapshot') ? facts.__scopedSnapshot : undefined;
2402
+ const oldLvl = hasOwn.call(facts, '__scopedClosureLevel') ? facts.__scopedClosureLevel : undefined;
2403
+
2404
+ // Create a frozen snapshot of the saturated closure.
2405
+ const snap = facts.slice();
2406
+ ensureFactIndexes(snap);
2407
+ Object.defineProperty(snap, '__scopedSnapshot', {
2408
+ value: snap,
2409
+ enumerable: false,
2410
+ writable: true,
2411
+ configurable: true,
2412
+ });
2413
+ Object.defineProperty(snap, '__scopedClosureLevel', {
2414
+ value: Number.MAX_SAFE_INTEGER,
2415
+ enumerable: false,
2416
+ writable: true,
2417
+ configurable: true,
2418
+ });
2419
+
2420
+ // Ensure the live facts array exposes the snapshot/level for builtins.
2421
+ if (!hasOwn.call(facts, '__scopedSnapshot')) {
2422
+ Object.defineProperty(facts, '__scopedSnapshot', {
2423
+ value: null,
2424
+ enumerable: false,
2425
+ writable: true,
2426
+ configurable: true,
2427
+ });
2428
+ }
2429
+ if (!hasOwn.call(facts, '__scopedClosureLevel')) {
2430
+ Object.defineProperty(facts, '__scopedClosureLevel', {
2431
+ value: 0,
2432
+ enumerable: false,
2433
+ writable: true,
2434
+ configurable: true,
2435
+ });
2436
+ }
2437
+
2438
+ facts.__scopedSnapshot = snap;
2439
+ facts.__scopedClosureLevel = Number.MAX_SAFE_INTEGER;
2440
+
2441
+ try {
2442
+ return fn();
2443
+ } finally {
2444
+ facts.__scopedSnapshot = oldSnap === undefined ? null : oldSnap;
2445
+ facts.__scopedClosureLevel = oldLvl === undefined ? 0 : oldLvl;
2446
+ }
2447
+ }
2448
+
2449
+ function collectLogQueryConclusions(logQueryRules, facts, backRules) {
2450
+ const queryTriples = [];
2451
+ const queryDerived = [];
2452
+ const seen = new Set();
2453
+
2454
+ if (!Array.isArray(logQueryRules) || logQueryRules.length === 0) {
2455
+ return { queryTriples, queryDerived };
2456
+ }
2457
+
2458
+ ensureFactIndexes(facts);
2459
+ ensureBackRuleIndexes(backRules);
2460
+
2461
+ // Shared state across all query firings (mirrors forwardChain()).
2462
+ const varGen = [0];
2463
+ const skCounter = [0];
2464
+ const headSkolemCache = new Map();
2465
+
2466
+ return __withScopedSnapshotForQueries(facts, () => {
2467
+ for (let qi = 0; qi < logQueryRules.length; qi++) {
2468
+ const r = logQueryRules[qi];
2469
+ if (!r || !Array.isArray(r.premise) || !Array.isArray(r.conclusion)) continue;
2470
+
2471
+ const sols = proveGoals(r.premise, null, facts, backRules, 0, null, varGen, undefined, {
2472
+ deferBuiltins: true,
2473
+ });
2474
+
2475
+ for (const s of sols) {
2476
+ const skMap = {};
2477
+ const instantiatedPremises = r.premise.map((b) => applySubstTriple(b, s));
2478
+ const fireKey = __firingKey(1000000 + qi, instantiatedPremises);
2479
+
2480
+ // Support dynamic heads (same semantics as forwardChain).
2481
+ let dynamicHeadTriples = null;
2482
+ let headBlankLabelsHere = r.headBlankLabels;
2483
+ if (r.__dynamicConclusionTerm) {
2484
+ const dynTerm = applySubstTerm(r.__dynamicConclusionTerm, s);
2485
+ const dynTriples = __graphTriplesOrTrue(dynTerm);
2486
+ dynamicHeadTriples = dynTriples !== null ? dynTriples : [];
2487
+ const dynHeadBlankLabels =
2488
+ dynamicHeadTriples && dynamicHeadTriples.length ? collectBlankLabelsInTriples(dynamicHeadTriples) : null;
2489
+ if (dynHeadBlankLabels && dynHeadBlankLabels.size) {
2490
+ headBlankLabelsHere = new Set([...headBlankLabelsHere, ...dynHeadBlankLabels]);
2491
+ }
2492
+ }
2493
+
2494
+ const headPatterns =
2495
+ dynamicHeadTriples && dynamicHeadTriples.length ? r.conclusion.concat(dynamicHeadTriples) : r.conclusion;
2496
+
2497
+ for (const cpat of headPatterns) {
2498
+ const instantiated = applySubstTriple(cpat, s);
2499
+ const inst = skolemizeTripleForHeadBlanks(
2500
+ instantiated,
2501
+ headBlankLabelsHere,
2502
+ skMap,
2503
+ skCounter,
2504
+ fireKey,
2505
+ headSkolemCache,
2506
+ );
2507
+ if (!isGroundTriple(inst)) continue;
2508
+ const k = __tripleKeyForOutput(inst);
2509
+ if (seen.has(k)) continue;
2510
+ seen.add(k);
2511
+ queryTriples.push(inst);
2512
+ queryDerived.push(new DerivedFact(inst, r, instantiatedPremises.slice(), { ...s }));
2513
+ }
2514
+ }
2515
+ }
2516
+
2517
+ return { queryTriples, queryDerived };
2518
+ });
2519
+ }
2520
+
2521
+ function forwardChainAndCollectLogQueryConclusions(facts, forwardRules, backRules, logQueryRules, onDerived) {
2522
+ __enterReasoningRun();
2523
+ try {
2524
+ // Forward chain first (saturates `facts`).
2525
+ const derived = forwardChain(facts, forwardRules, backRules, onDerived);
2526
+ // Then collect query conclusions against the saturated closure.
2527
+ const { queryTriples, queryDerived } = collectLogQueryConclusions(logQueryRules, facts, backRules);
2528
+ return { derived, queryTriples, queryDerived };
2529
+ } finally {
2530
+ __exitReasoningRun();
2531
+ }
2532
+ }
2533
+
2381
2534
  // (proof printing + log:outputString moved to lib/explain.js)
2382
2535
 
2383
2536
  function reasonStream(n3Text, opts = {}) {
@@ -2397,32 +2550,62 @@ function reasonStream(n3Text, opts = {}) {
2397
2550
  const parser = new Parser(toks);
2398
2551
  if (baseIri) parser.prefixes.setBase(baseIri);
2399
2552
 
2400
- const [prefixes, triples, frules, brules] = parser.parseDocument();
2553
+ const [prefixes, triples, frules, brules, logQueryRules] = parser.parseDocument();
2401
2554
  // Make the parsed prefixes available to log:trace output
2402
2555
  trace.setTracePrefixes(prefixes);
2403
2556
 
2404
2557
  // Materialize anonymous rdf:first/rdf:rest collections into list terms.
2405
2558
  // Named list nodes keep identity; list:* builtins can traverse them.
2406
- materializeRdfLists(triples, frules, brules);
2559
+ materializeRdfLists(triples, frules.concat(logQueryRules || []), brules);
2407
2560
 
2408
2561
  // facts becomes the saturated closure because pushFactIndexed(...) appends into it
2409
2562
  const facts = triples.filter((tr) => isGroundTriple(tr));
2410
2563
 
2411
- const derived = forwardChain(facts, frules, brules, (df) => {
2564
+ let derived = [];
2565
+ let queryTriples = [];
2566
+ let queryDerived = [];
2567
+
2568
+ if (Array.isArray(logQueryRules) && logQueryRules.length) {
2569
+ // Query-selection mode: derive full closure, then output only the unique
2570
+ // instantiated conclusion triples of the log:query directives.
2571
+ const res = forwardChainAndCollectLogQueryConclusions(facts, frules, brules, logQueryRules);
2572
+ derived = res.derived;
2573
+ queryTriples = res.queryTriples;
2574
+ queryDerived = res.queryDerived;
2575
+
2576
+ // For compatibility with the streaming callback signature, we emit the
2577
+ // query-selected triples (not all derived facts).
2412
2578
  if (typeof onDerived === 'function') {
2413
- onDerived({
2414
- triple: tripleToN3(df.fact, prefixes),
2415
- df,
2416
- });
2579
+ for (const qdf of queryDerived) {
2580
+ onDerived({ triple: tripleToN3(qdf.fact, prefixes), df: qdf });
2581
+ }
2417
2582
  }
2418
- });
2583
+ } else {
2584
+ // Default mode: output only newly derived forward facts.
2585
+ derived = forwardChain(facts, frules, brules, (df) => {
2586
+ if (typeof onDerived === 'function') {
2587
+ onDerived({
2588
+ triple: tripleToN3(df.fact, prefixes),
2589
+ df,
2590
+ });
2591
+ }
2592
+ });
2593
+ }
2419
2594
 
2420
- const closureTriples = includeInputFactsInClosure ? facts : derived.map((d) => d.fact);
2595
+ const closureTriples =
2596
+ Array.isArray(logQueryRules) && logQueryRules.length
2597
+ ? queryTriples
2598
+ : includeInputFactsInClosure
2599
+ ? facts
2600
+ : derived.map((d) => d.fact);
2421
2601
 
2422
2602
  const __out = {
2423
2603
  prefixes,
2424
2604
  facts, // saturated closure (Triple[])
2425
2605
  derived, // DerivedFact[]
2606
+ queryMode: Array.isArray(logQueryRules) && logQueryRules.length ? true : false,
2607
+ queryTriples,
2608
+ queryDerived,
2426
2609
  closureN3: closureTriples.map((t) => tripleToN3(t, prefixes)).join('\n'),
2427
2610
  };
2428
2611
  deref.setEnforceHttpsEnabled(__oldEnforceHttps);
@@ -2475,6 +2658,8 @@ function setTracePrefixes(v) {
2475
2658
 
2476
2659
  module.exports = {
2477
2660
  reasonStream,
2661
+ collectLogQueryConclusions,
2662
+ forwardChainAndCollectLogQueryConclusions,
2478
2663
  collectOutputStringsFromFacts,
2479
2664
  main,
2480
2665
  version,
package/lib/entry.js CHANGED
@@ -23,6 +23,8 @@ module.exports = {
23
23
  lex: engine.lex,
24
24
  Parser: engine.Parser,
25
25
  forwardChain: engine.forwardChain,
26
+ collectLogQueryConclusions: engine.collectLogQueryConclusions,
27
+ forwardChainAndCollectLogQueryConclusions: engine.forwardChainAndCollectLogQueryConclusions,
26
28
  materializeRdfLists: engine.materializeRdfLists,
27
29
  isGroundTriple: engine.isGroundTriple,
28
30
  printExplanation: engine.printExplanation,
package/lib/parser.js CHANGED
@@ -25,6 +25,7 @@ const {
25
25
  collectBlankLabelsInTriples,
26
26
  isLogImplies,
27
27
  isLogImpliedBy,
28
+ isLogQuery,
28
29
  } = require('./prelude');
29
30
 
30
31
  const { N3SyntaxError } = require('./lexer');
@@ -73,6 +74,7 @@ class Parser {
73
74
  const triples = [];
74
75
  const forwardRules = [];
75
76
  const backwardRules = [];
77
+ const logQueries = [];
76
78
 
77
79
  while (this.peek().typ !== 'EOF') {
78
80
  if (this.peek().typ === 'AtPrefix') {
@@ -147,6 +149,11 @@ class Parser {
147
149
  forwardRules.push(this.makeRule(tr.s, tr.o, true));
148
150
  } else if (isLogImpliedBy(tr.p) && tr.s instanceof GraphTerm && tr.o instanceof GraphTerm) {
149
151
  backwardRules.push(this.makeRule(tr.s, tr.o, false));
152
+ } else if (isLogQuery(tr.p) && tr.s instanceof GraphTerm && tr.o instanceof GraphTerm) {
153
+ // Output-selection directive: { premise } log:query { conclusion }.
154
+ // When present at top-level, eyeling prints only the instantiated conclusion
155
+ // triples (unique) instead of all newly derived facts.
156
+ logQueries.push(this.makeRule(tr.s, tr.o, true));
150
157
  } else {
151
158
  triples.push(tr);
152
159
  }
@@ -155,7 +162,7 @@ class Parser {
155
162
  }
156
163
  }
157
164
 
158
- return [this.prefixes, triples, forwardRules, backwardRules];
165
+ return [this.prefixes, triples, forwardRules, backwardRules, logQueries];
159
166
  }
160
167
 
161
168
  parsePrefixDirective() {
package/lib/prelude.js CHANGED
@@ -317,6 +317,10 @@ function isLogImpliedBy(p) {
317
317
  return p instanceof Iri && p.value === LOG_NS + 'impliedBy';
318
318
  }
319
319
 
320
+ function isLogQuery(p) {
321
+ return p instanceof Iri && p.value === LOG_NS + 'query';
322
+ }
323
+
320
324
  // ===========================================================================
321
325
  // PREFIX ENVIRONMENT
322
326
  // ===========================================================================
@@ -528,6 +532,7 @@ module.exports = {
528
532
  isOwlSameAsPred,
529
533
  isLogImplies,
530
534
  isLogImpliedBy,
535
+ isLogQuery,
531
536
  PrefixEnv,
532
537
  collectIrisInTerm,
533
538
  varsInRule,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.12.15",
3
+ "version": "1.13.0",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
package/tools/n3gen.js CHANGED
@@ -101,7 +101,6 @@ const log = {
101
101
  nameOf: `${LOG_NS}nameOf`,
102
102
  };
103
103
 
104
-
105
104
  // ---------------------------------------------------------------------------
106
105
  // Minimal Turtle/N3 model + lexer + parser
107
106
  // ---------------------------------------------------------------------------
@@ -2001,7 +2000,10 @@ function writeN3LogNameOf({ datasetQuads, prefixes }) {
2001
2000
  const prunedPrefixes = pruneUnusedPrefixes(prefixes, pseudoTriplesForUse);
2002
2001
  const skolemMap = buildSkolemMapForBnodesThatCrossScopes(pseudoTriplesForUse);
2003
2002
  const outPrefixes = ensureRdfPrefixIfUsed(
2004
- ensureXsdPrefixIfUsed(ensureLogPrefixIfUsed(ensureSkolemPrefix(prunedPrefixes, skolemMap), pseudoTriplesForUse), pseudoTriplesForUse),
2003
+ ensureXsdPrefixIfUsed(
2004
+ ensureLogPrefixIfUsed(ensureSkolemPrefix(prunedPrefixes, skolemMap), pseudoTriplesForUse),
2005
+ pseudoTriplesForUse,
2006
+ ),
2005
2007
  pseudoTriplesForUse,
2006
2008
  );
2007
2009
  const pro = renderPrefixPrologue(outPrefixes).trim();
@@ -2059,7 +2061,10 @@ function writeN3Triples({ triples, prefixes }) {
2059
2061
  const prunedPrefixes = pruneUnusedPrefixes(prefixes, foldedTriples);
2060
2062
  const skolemMap = buildSkolemMapForBnodesThatCrossScopes(foldedTriples);
2061
2063
  const outPrefixes = ensureRdfPrefixIfUsed(
2062
- ensureXsdPrefixIfUsed(ensureLogPrefixIfUsed(ensureSkolemPrefix(prunedPrefixes, skolemMap), foldedTriples), foldedTriples),
2064
+ ensureXsdPrefixIfUsed(
2065
+ ensureLogPrefixIfUsed(ensureSkolemPrefix(prunedPrefixes, skolemMap), foldedTriples),
2066
+ foldedTriples,
2067
+ ),
2063
2068
  foldedTriples,
2064
2069
  );
2065
2070
  const blocks = [];