eyeling 1.25.2 → 1.25.4

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/eyeling.js CHANGED
@@ -4965,12 +4965,14 @@ function main() {
4965
4965
  outTriples = res.queryTriples;
4966
4966
  outDerived = res.queryDerived;
4967
4967
  } else {
4968
+ const skipDerivedCollection = mayAutoRenderOutputStrings && !engine.getProofCommentsEnabled();
4968
4969
  derived = engine.forwardChain(facts, frules, brules, null, {
4969
4970
  captureExplanations: engine.getProofCommentsEnabled(),
4971
+ collectDerived: !skipDerivedCollection,
4970
4972
  prefixes,
4971
4973
  });
4972
4974
  outDerived = derived;
4973
- outTriples = derived.map((df) => df.fact);
4975
+ outTriples = skipDerivedCollection ? [] : derived.map((df) => df.fact);
4974
4976
  }
4975
4977
 
4976
4978
  const renderedOutputTriples = hasQueries ? outTriples : facts;
@@ -6521,6 +6523,12 @@ function alphaEqGraphTriples(xs, ys, opts) {
6521
6523
  // - __byPred: Map<predicateId, number[]> (indices into facts array)
6522
6524
  // - __byPS: Map<predicateId, Map<subjectId, number[]>>
6523
6525
  // - __byPO: Map<predicateId, Map<objectId, number[]>>
6526
+ // - __byPNonFastS / __byPNonFastO: Map<predicateId, number[]>
6527
+ // IRI-predicate facts whose subject/object cannot be indexed by fast key.
6528
+ // These are the fallback clauses for a constrained subject/object, just
6529
+ // like a Prolog clause index keeps variable-headed clauses as fallback.
6530
+ // - __varPred* indexes: facts whose predicate is a Var. Only these non-IRI
6531
+ // predicate facts can unify with a ground IRI predicate goal.
6524
6532
  // - __keySet: Set<"S\tP\tO"> for Iri/Literal/Blank-only triples (fast dup check)
6525
6533
  //
6526
6534
  // Backward rules:
@@ -6536,6 +6544,8 @@ const __compoundKeyToTid = new Map();
6536
6544
  // Use a negative id space so we never collide with __tid (which is positive).
6537
6545
  let __nextCompoundTid = -1;
6538
6546
 
6547
+ const EMPTY_FACT_INDEX_BUCKET = Object.freeze([]);
6548
+
6539
6549
  function __internCompoundTid(key) {
6540
6550
  const hit = __compoundKeyToTid.get(key);
6541
6551
  if (hit !== undefined) return hit;
@@ -6587,6 +6597,71 @@ function termFastKey(t) {
6587
6597
  return null;
6588
6598
  }
6589
6599
 
6600
+ function encodeLookupKeyPart(k) {
6601
+ if (typeof k === 'number') return 'T' + k;
6602
+ const s = String(k);
6603
+ return 'K' + s.length + ':' + s;
6604
+ }
6605
+
6606
+ function literalLookupKey(t) {
6607
+ const boolInfo = parseBooleanLiteralInfo(t);
6608
+ if (boolInfo) return '\u0000B' + (boolInfo.value ? '1' : '0');
6609
+
6610
+ const numInfo = parseNumericLiteralInfo(t);
6611
+ if (numInfo) {
6612
+ if (numInfo.kind === 'bigint') return '\u0000N' + numInfo.dt + '\u0000' + numInfo.value.toString();
6613
+
6614
+ const n = numInfo.value;
6615
+ // Normal unification intentionally does not make NaN value-equal to NaN;
6616
+ // only identical lexical literals match through the ordinary __tid path.
6617
+ if (!Number.isNaN(n)) return '\u0000N' + numInfo.dt + '\u0000' + String(n);
6618
+ }
6619
+
6620
+ // Covers exact literals plus plain string / xsd:string canonicalization, which
6621
+ // Literal construction already normalizes into a shared __tid.
6622
+ return termFastKey(t);
6623
+ }
6624
+
6625
+ function termLookupKey(t) {
6626
+ // Lookup keys summarize the equality accepted by ordinary unifyTerm(), not
6627
+ // merely object identity. This keeps literal-index pruning complete for
6628
+ // value-equivalent booleans/numerics such as true/"1"^^xsd:boolean and
6629
+ // 1.0/1.00, while preserving exact fast ids for IRIs, blanks, and strings.
6630
+ if (t instanceof Iri) {
6631
+ if (t.value === RDF_NIL_IRI) return '\u0000L0';
6632
+ return t.__tid;
6633
+ }
6634
+ if (t instanceof Blank) return t.__tid;
6635
+ if (t instanceof Literal) return literalLookupKey(t);
6636
+
6637
+ if (t instanceof ListTerm) {
6638
+ const cached = t.__lookupKey;
6639
+ if (cached !== undefined) return cached === false ? null : cached;
6640
+
6641
+ const xs = t.elems;
6642
+ if (xs.length === 0) {
6643
+ Object.defineProperty(t, '__lookupKey', { value: '\u0000L0', enumerable: false });
6644
+ return '\u0000L0';
6645
+ }
6646
+
6647
+ const parts = new Array(xs.length);
6648
+ for (let i = 0; i < xs.length; i++) {
6649
+ const k = termLookupKey(xs[i]);
6650
+ if (k === null) {
6651
+ Object.defineProperty(t, '__lookupKey', { value: false, enumerable: false });
6652
+ return null;
6653
+ }
6654
+ parts[i] = encodeLookupKeyPart(k);
6655
+ }
6656
+
6657
+ const key = '\u0000L' + xs.length + '\u0001' + parts.join('\u0001');
6658
+ Object.defineProperty(t, '__lookupKey', { value: key, enumerable: false });
6659
+ return key;
6660
+ }
6661
+
6662
+ return null;
6663
+ }
6664
+
6590
6665
  function tripleFastKey(tr) {
6591
6666
  const ks = termFastKey(tr.s);
6592
6667
  const kp = termFastKey(tr.p);
@@ -6600,9 +6675,13 @@ function ensureFactIndexes(facts) {
6600
6675
  facts.__byPred &&
6601
6676
  facts.__byPS &&
6602
6677
  facts.__byPO &&
6603
- facts.__wildPred &&
6604
- facts.__wildPS &&
6605
- facts.__wildPO &&
6678
+ facts.__byPNonFastS &&
6679
+ facts.__byPNonFastO &&
6680
+ facts.__varPred &&
6681
+ facts.__varPredPS &&
6682
+ facts.__varPredPO &&
6683
+ facts.__varPredNonFastS &&
6684
+ facts.__varPredNonFastO &&
6606
6685
  facts.__keySet
6607
6686
  )
6608
6687
  return;
@@ -6622,21 +6701,41 @@ function ensureFactIndexes(facts) {
6622
6701
  enumerable: false,
6623
6702
  writable: true,
6624
6703
  });
6625
- Object.defineProperty(facts, '__wildPred', {
6704
+ Object.defineProperty(facts, '__byPNonFastS', {
6705
+ value: new Map(),
6706
+ enumerable: false,
6707
+ writable: true,
6708
+ });
6709
+ Object.defineProperty(facts, '__byPNonFastO', {
6710
+ value: new Map(),
6711
+ enumerable: false,
6712
+ writable: true,
6713
+ });
6714
+ Object.defineProperty(facts, '__varPred', {
6626
6715
  value: [],
6627
6716
  enumerable: false,
6628
6717
  writable: true,
6629
6718
  });
6630
- Object.defineProperty(facts, '__wildPS', {
6719
+ Object.defineProperty(facts, '__varPredPS', {
6631
6720
  value: new Map(),
6632
6721
  enumerable: false,
6633
6722
  writable: true,
6634
6723
  });
6635
- Object.defineProperty(facts, '__wildPO', {
6724
+ Object.defineProperty(facts, '__varPredPO', {
6636
6725
  value: new Map(),
6637
6726
  enumerable: false,
6638
6727
  writable: true,
6639
6728
  });
6729
+ Object.defineProperty(facts, '__varPredNonFastS', {
6730
+ value: [],
6731
+ enumerable: false,
6732
+ writable: true,
6733
+ });
6734
+ Object.defineProperty(facts, '__varPredNonFastO', {
6735
+ value: [],
6736
+ enumerable: false,
6737
+ writable: true,
6738
+ });
6640
6739
  Object.defineProperty(facts, '__keySet', {
6641
6740
  value: new Set(),
6642
6741
  enumerable: false,
@@ -6677,16 +6776,53 @@ function cloneFactIndexesForSnapshot(src, dest) {
6677
6776
  Object.defineProperty(dest, '__byPred', { value: cloneArrayMap(src.__byPred), enumerable: false, writable: true });
6678
6777
  Object.defineProperty(dest, '__byPS', { value: cloneNestedArrayMap(src.__byPS), enumerable: false, writable: true });
6679
6778
  Object.defineProperty(dest, '__byPO', { value: cloneNestedArrayMap(src.__byPO), enumerable: false, writable: true });
6680
- Object.defineProperty(dest, '__wildPred', { value: src.__wildPred.slice(), enumerable: false, writable: true });
6681
- Object.defineProperty(dest, '__wildPS', { value: cloneArrayMap(src.__wildPS), enumerable: false, writable: true });
6682
- Object.defineProperty(dest, '__wildPO', { value: cloneArrayMap(src.__wildPO), enumerable: false, writable: true });
6779
+ Object.defineProperty(dest, '__byPNonFastS', {
6780
+ value: cloneArrayMap(src.__byPNonFastS),
6781
+ enumerable: false,
6782
+ writable: true,
6783
+ });
6784
+ Object.defineProperty(dest, '__byPNonFastO', {
6785
+ value: cloneArrayMap(src.__byPNonFastO),
6786
+ enumerable: false,
6787
+ writable: true,
6788
+ });
6789
+ Object.defineProperty(dest, '__varPred', { value: src.__varPred.slice(), enumerable: false, writable: true });
6790
+ Object.defineProperty(dest, '__varPredPS', {
6791
+ value: cloneArrayMap(src.__varPredPS),
6792
+ enumerable: false,
6793
+ writable: true,
6794
+ });
6795
+ Object.defineProperty(dest, '__varPredPO', {
6796
+ value: cloneArrayMap(src.__varPredPO),
6797
+ enumerable: false,
6798
+ writable: true,
6799
+ });
6800
+ Object.defineProperty(dest, '__varPredNonFastS', {
6801
+ value: src.__varPredNonFastS.slice(),
6802
+ enumerable: false,
6803
+ writable: true,
6804
+ });
6805
+ Object.defineProperty(dest, '__varPredNonFastO', {
6806
+ value: src.__varPredNonFastO.slice(),
6807
+ enumerable: false,
6808
+ writable: true,
6809
+ });
6683
6810
  Object.defineProperty(dest, '__keySet', { value: new Set(src.__keySet), enumerable: false, writable: true });
6684
6811
  Object.defineProperty(dest, '__keySetComplete', { value: !!src.__keySetComplete, enumerable: false, writable: true });
6685
6812
  }
6686
6813
 
6814
+ function addToIndexArrayMap(map, key, value) {
6815
+ let bucket = map.get(key);
6816
+ if (!bucket) {
6817
+ bucket = [];
6818
+ map.set(key, bucket);
6819
+ }
6820
+ bucket.push(value);
6821
+ }
6822
+
6687
6823
  function indexFact(facts, tr, idx, addKeySet = true) {
6688
- const sk = termFastKey(tr.s);
6689
- const ok = termFastKey(tr.o);
6824
+ const sk = termLookupKey(tr.s);
6825
+ const ok = termLookupKey(tr.o);
6690
6826
  let pkForKey = null;
6691
6827
 
6692
6828
  if (tr.p instanceof Iri) {
@@ -6707,12 +6843,9 @@ function indexFact(facts, tr, idx, addKeySet = true) {
6707
6843
  ps = new Map();
6708
6844
  facts.__byPS.set(pk, ps);
6709
6845
  }
6710
- let psb = ps.get(sk);
6711
- if (!psb) {
6712
- psb = [];
6713
- ps.set(sk, psb);
6714
- }
6715
- psb.push(idx);
6846
+ addToIndexArrayMap(ps, sk, idx);
6847
+ } else {
6848
+ addToIndexArrayMap(facts.__byPNonFastS, pk, idx);
6716
6849
  }
6717
6850
 
6718
6851
  if (ok !== null) {
@@ -6721,32 +6854,23 @@ function indexFact(facts, tr, idx, addKeySet = true) {
6721
6854
  po = new Map();
6722
6855
  facts.__byPO.set(pk, po);
6723
6856
  }
6724
- let pob = po.get(ok);
6725
- if (!pob) {
6726
- pob = [];
6727
- po.set(ok, pob);
6728
- }
6729
- pob.push(idx);
6857
+ addToIndexArrayMap(po, ok, idx);
6858
+ } else {
6859
+ addToIndexArrayMap(facts.__byPNonFastO, pk, idx);
6730
6860
  }
6731
- } else {
6732
- facts.__wildPred.push(idx);
6861
+ } else if (tr.p instanceof Var) {
6862
+ facts.__varPred.push(idx);
6733
6863
 
6734
6864
  if (sk !== null) {
6735
- let psb = facts.__wildPS.get(sk);
6736
- if (!psb) {
6737
- psb = [];
6738
- facts.__wildPS.set(sk, psb);
6739
- }
6740
- psb.push(idx);
6865
+ addToIndexArrayMap(facts.__varPredPS, sk, idx);
6866
+ } else {
6867
+ facts.__varPredNonFastS.push(idx);
6741
6868
  }
6742
6869
 
6743
6870
  if (ok !== null) {
6744
- let pob = facts.__wildPO.get(ok);
6745
- if (!pob) {
6746
- pob = [];
6747
- facts.__wildPO.set(ok, pob);
6748
- }
6749
- pob.push(idx);
6871
+ addToIndexArrayMap(facts.__varPredPO, ok, idx);
6872
+ } else {
6873
+ facts.__varPredNonFastO.push(idx);
6750
6874
  }
6751
6875
  }
6752
6876
 
@@ -6756,55 +6880,86 @@ function indexFact(facts, tr, idx, addKeySet = true) {
6756
6880
  }
6757
6881
  }
6758
6882
 
6883
+ function mergeIndexBuckets(primary, fallback) {
6884
+ const a = primary && primary.length ? primary : null;
6885
+ const b = fallback && fallback.length ? fallback : null;
6886
+ if (!a && !b) return EMPTY_FACT_INDEX_BUCKET;
6887
+ if (!a) return b;
6888
+ if (!b) return a;
6889
+ const out = new Array(a.length + b.length);
6890
+ for (let i = 0; i < a.length; i++) out[i] = a[i];
6891
+ for (let i = 0; i < b.length; i++) out[a.length + i] = b[i];
6892
+ return out;
6893
+ }
6894
+
6895
+ function selectPositionIndexedCandidates(all, exactByS, fallbackS, sk, exactByO, fallbackO, ok) {
6896
+ if (sk === null && ok === null) return all && all.length ? all : EMPTY_FACT_INDEX_BUCKET;
6897
+
6898
+ let sBucket = null;
6899
+ if (sk !== null) sBucket = mergeIndexBuckets(exactByS || null, fallbackS || null);
6900
+
6901
+ let oBucket = null;
6902
+ if (ok !== null) oBucket = mergeIndexBuckets(exactByO || null, fallbackO || null);
6903
+
6904
+ if (sk !== null && ok !== null) return sBucket.length <= oBucket.length ? sBucket : oBucket;
6905
+ return sk !== null ? sBucket : oBucket;
6906
+ }
6907
+
6759
6908
  function candidateFacts(facts, goal) {
6760
6909
  ensureFactIndexes(facts);
6761
6910
 
6762
6911
  if (goal.p instanceof Iri) {
6763
6912
  const pk = goal.p.__tid;
6764
6913
 
6765
- const sk = termFastKey(goal.s);
6766
- const ok = termFastKey(goal.o);
6914
+ const sk = termLookupKey(goal.s);
6915
+ const ok = termLookupKey(goal.o);
6767
6916
 
6768
- /** @type {number[] | null} */
6769
6917
  let byPS = null;
6770
6918
  if (sk !== null) {
6771
6919
  const ps = facts.__byPS.get(pk);
6772
6920
  if (ps) byPS = ps.get(sk) || null;
6773
6921
  }
6922
+ const byPNonFastS = sk !== null ? facts.__byPNonFastS.get(pk) || null : null;
6774
6923
 
6775
- /** @type {number[] | null} */
6776
6924
  let byPO = null;
6777
6925
  if (ok !== null) {
6778
6926
  const po = facts.__byPO.get(pk);
6779
6927
  if (po) byPO = po.get(ok) || null;
6780
6928
  }
6929
+ const byPNonFastO = ok !== null ? facts.__byPNonFastO.get(pk) || null : null;
6781
6930
 
6782
- let exact = null;
6783
- if (byPS && byPO) exact = byPS.length <= byPO.length ? byPS : byPO;
6784
- else if (byPS) exact = byPS;
6785
- else if (byPO) exact = byPO;
6786
- else exact = facts.__byPred.get(pk) || null;
6931
+ const exact = selectPositionIndexedCandidates(
6932
+ facts.__byPred.get(pk) || null,
6933
+ byPS,
6934
+ byPNonFastS,
6935
+ sk,
6936
+ byPO,
6937
+ byPNonFastO,
6938
+ ok,
6939
+ );
6787
6940
 
6788
- /** @type {number[] | null} */
6789
- let wildPS = null;
6790
- if (sk !== null) wildPS = facts.__wildPS.get(sk) || null;
6941
+ let varPredPS = null;
6942
+ if (sk !== null) varPredPS = facts.__varPredPS.get(sk) || null;
6791
6943
 
6792
- /** @type {number[] | null} */
6793
- let wildPO = null;
6794
- if (ok !== null) wildPO = facts.__wildPO.get(ok) || null;
6944
+ let varPredPO = null;
6945
+ if (ok !== null) varPredPO = facts.__varPredPO.get(ok) || null;
6795
6946
 
6796
- let wild = null;
6797
- if (wildPS && wildPO) wild = wildPS.length <= wildPO.length ? wildPS : wildPO;
6798
- else if (wildPS) wild = wildPS;
6799
- else if (wildPO) wild = wildPO;
6800
- else wild = facts.__wildPred.length ? facts.__wildPred : null;
6947
+ const wild = selectPositionIndexedCandidates(
6948
+ facts.__varPred,
6949
+ varPredPS,
6950
+ sk !== null ? facts.__varPredNonFastS : null,
6951
+ sk,
6952
+ varPredPO,
6953
+ ok !== null ? facts.__varPredNonFastO : null,
6954
+ ok,
6955
+ );
6801
6956
 
6802
6957
  return {
6803
- exact: exact || null,
6804
- wild: wild || null,
6805
- exactLen: exact ? exact.length : 0,
6806
- wildLen: wild ? wild.length : 0,
6807
- totalLen: (exact ? exact.length : 0) + (wild ? wild.length : 0),
6958
+ exact,
6959
+ wild,
6960
+ exactLen: exact.length,
6961
+ wildLen: wild.length,
6962
+ totalLen: exact.length + wild.length,
6808
6963
  };
6809
6964
  }
6810
6965
 
@@ -6822,20 +6977,28 @@ function hasFactIndexed(facts, tr) {
6822
6977
 
6823
6978
  if (tr.p instanceof Iri) {
6824
6979
  const pk = tr.p.__tid;
6980
+ const sk = termLookupKey(tr.s);
6981
+ let best = null;
6982
+
6983
+ if (sk !== null) {
6984
+ const ps = facts.__byPS.get(pk);
6985
+ if (!ps) return false;
6986
+ const psb = ps.get(sk);
6987
+ if (!psb || psb.length === 0) return false;
6988
+ best = psb;
6989
+ }
6825
6990
 
6826
- const ok = termFastKey(tr.o);
6991
+ const ok = termLookupKey(tr.o);
6827
6992
  if (ok !== null) {
6828
6993
  const po = facts.__byPO.get(pk);
6829
- if (po) {
6830
- const pob = po.get(ok) || [];
6831
- // Facts are all in the same graph. Different blank node labels represent
6832
- // different existentials unless explicitly connected. Do NOT treat
6833
- // triples as duplicates modulo blank renaming, or you'll incorrectly
6834
- // drop facts like: _:sk_0 :x 8.0 (because _:b8 :x 8.0 exists).
6835
- return pob.some((i) => triplesEqual(facts[i], tr));
6836
- }
6994
+ if (!po) return false;
6995
+ const pob = po.get(ok);
6996
+ if (!pob || pob.length === 0) return false;
6997
+ if (!best || pob.length < best.length) best = pob;
6837
6998
  }
6838
6999
 
7000
+ if (best) return best.some((i) => triplesEqual(facts[i], tr));
7001
+
6839
7002
  const pb = facts.__byPred.get(pk) || [];
6840
7003
  return pb.some((i) => triplesEqual(facts[i], tr));
6841
7004
  }
@@ -6944,11 +7107,25 @@ function mergeSinglePremiseAgendaBuckets() {
6944
7107
  return out;
6945
7108
  }
6946
7109
 
7110
+ function termContainsVarForAgenda(t) {
7111
+ if (t instanceof Var) return true;
7112
+ if (t instanceof ListTerm) return t.elems.some(termContainsVarForAgenda);
7113
+ if (t instanceof OpenListTerm) return true;
7114
+ if (t instanceof GraphTerm)
7115
+ return t.triples.some(
7116
+ (tr) =>
7117
+ termContainsVarForAgenda(tr.s) || termContainsVarForAgenda(tr.p) || termContainsVarForAgenda(tr.o),
7118
+ );
7119
+ return false;
7120
+ }
7121
+
6947
7122
  function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
6948
7123
  const index = {
6949
7124
  byPred: new Map(),
7125
+ byPredAll: new Map(),
6950
7126
  byPS: new Map(),
6951
7127
  byPO: new Map(),
7128
+ allIriPred: [],
6952
7129
  wildPred: [],
6953
7130
  wildPS: new Map(),
6954
7131
  wildPO: new Map(),
@@ -6970,8 +7147,8 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
6970
7147
  if (!isSinglePremiseAgendaRuleSafe(r, backRules)) continue;
6971
7148
 
6972
7149
  const goal = r.premise[0];
6973
- const goalSKey = termFastKey(goal.s);
6974
- const goalOKey = termFastKey(goal.o);
7150
+ const goalSKey = termLookupKey(goal.s);
7151
+ const goalOKey = termLookupKey(goal.o);
6975
7152
  const fastSubjectVar = goal.p instanceof Iri && goal.s instanceof Var && goalOKey !== null ? goal.s.name : null;
6976
7153
  const fastObjectVar = goal.p instanceof Iri && goal.o instanceof Var && goalSKey !== null ? goal.o.name : null;
6977
7154
  const entry = {
@@ -6990,6 +7167,8 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
6990
7167
  index.size += 1;
6991
7168
 
6992
7169
  if (entry.goalPredTid !== null) {
7170
+ addToMapArray(index.byPredAll, entry.goalPredTid, entry);
7171
+ index.allIriPred.push(entry);
6993
7172
  if (entry.goalSKey === null && entry.goalOKey === null) addToMapArray(index.byPred, entry.goalPredTid, entry);
6994
7173
  if (entry.goalSKey !== null) {
6995
7174
  let ps = index.byPS.get(entry.goalPredTid);
@@ -7020,25 +7199,37 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
7020
7199
  function getSinglePremiseAgendaCandidates(index, fact) {
7021
7200
  if (!index || index.size === 0) return null;
7022
7201
 
7023
- const sk = termFastKey(fact.s);
7024
- const ok = termFastKey(fact.o);
7202
+ const sk = termLookupKey(fact.s);
7203
+ const ok = termLookupKey(fact.o);
7025
7204
 
7026
7205
  let exact = null;
7027
7206
  if (fact.p instanceof Iri) {
7028
7207
  const pk = fact.p.__tid;
7029
- const byPred = index.byPred.get(pk) || null;
7030
- let byPS = null;
7031
- if (sk !== null) {
7208
+ if ((sk === null && termContainsVarForAgenda(fact.s)) || (ok === null && termContainsVarForAgenda(fact.o))) {
7209
+ // A fact with a variable-bearing subject/object (most importantly a
7210
+ // top-level variable fact such as `?S :p ?O.`) can match rules whose
7211
+ // premise is fixed in that position. The ordinary `(p,s)` / `(p,o)` lookup
7212
+ // would miss those rules, so fall back to all agenda-indexed rules for
7213
+ // this predicate. Do not do this merely for non-fast quoted formulas:
7214
+ // they are not wildcards, and broad fallback would over-fire rules that
7215
+ // rely on protected blank-node/formula unification semantics.
7216
+ exact = index.byPredAll.get(pk) || null;
7217
+ } else {
7218
+ const byPred = index.byPred.get(pk) || null;
7219
+ let byPS = null;
7032
7220
  const ps = index.byPS.get(pk);
7033
7221
  if (ps) byPS = ps.get(sk) || null;
7034
- }
7035
- let byPO = null;
7036
- if (ok !== null) {
7222
+ let byPO = null;
7037
7223
  const po = index.byPO.get(pk);
7038
7224
  if (po) byPO = po.get(ok) || null;
7039
- }
7040
7225
 
7041
- exact = mergeSinglePremiseAgendaBuckets(byPred, byPS, byPO);
7226
+ exact = mergeSinglePremiseAgendaBuckets(byPred, byPS, byPO);
7227
+ }
7228
+ } else if (fact.p instanceof Var) {
7229
+ // A variable-predicate fact can match any IRI-predicate agenda rule.
7230
+ // This is deliberately broad and relies on final unification below; such
7231
+ // facts are uncommon and correctness matters more than over-indexing them.
7232
+ exact = index.allIriPred.length ? index.allIriPred : null;
7042
7233
  }
7043
7234
 
7044
7235
  const wildPred = index.wildPred.length ? index.wildPred : null;
@@ -8388,6 +8579,8 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
8388
8579
  __attachGoalTable(backRules, goalTable);
8389
8580
 
8390
8581
  const captureExplanations = !(opts && opts.captureExplanations === false);
8582
+ const collectDerived = !(opts && opts.collectDerived === false);
8583
+ const hasDerivedCallback = typeof onDerived === 'function';
8391
8584
  const derivedForward = [];
8392
8585
  const varGen = [0];
8393
8586
  const skCounter = [0];
@@ -8566,8 +8759,8 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
8566
8759
  if (!hasFactIndexed(facts, instantiated)) {
8567
8760
  pushFactIndexed(facts, instantiated);
8568
8761
  const df = makeDerivedRecord(instantiated, r, getInstantiatedPremises(), s, captureExplanations);
8569
- derivedForward.push(df);
8570
- if (typeof onDerived === 'function') onDerived(df);
8762
+ if (collectDerived) derivedForward.push(df);
8763
+ if (hasDerivedCallback) onDerived(df);
8571
8764
  changedHere = true;
8572
8765
  }
8573
8766
 
@@ -8630,8 +8823,8 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
8630
8823
 
8631
8824
  pushFactIndexed(facts, inst);
8632
8825
  const df = makeDerivedRecord(inst, r, getInstantiatedPremises(), s, captureExplanations);
8633
- derivedForward.push(df);
8634
- if (typeof onDerived === 'function') onDerived(df);
8826
+ if (collectDerived) derivedForward.push(df);
8827
+ if (hasDerivedCallback) onDerived(df);
8635
8828
 
8636
8829
  changedHere = true;
8637
8830
  }
@@ -9643,15 +9836,6 @@ function isIdentChar(c) {
9643
9836
  return c === ':' || isPnChars(c);
9644
9837
  }
9645
9838
 
9646
- function canContinueAfterDot(next) {
9647
- // PN_LOCAL allows '.' but it cannot appear at the end.
9648
- // We include '.' only if it is followed by something that could continue a name.
9649
- if (next === null) return false;
9650
- if (isIdentChar(next)) return true;
9651
- if (next === '%' || next === '\\') return true;
9652
- return false;
9653
- }
9654
-
9655
9839
  function isForbiddenNoncharacterCodePoint(cp) {
9656
9840
  return (cp & 0xffff) === 0xfffe || (cp & 0xffff) === 0xffff;
9657
9841
  }
@@ -10727,7 +10911,9 @@ function lex(inputText, opts = {}) {
10727
10911
  // Avoid copying large ASCII/BMP inputs into an Array. Array.from() is
10728
10912
  // only needed when the text contains surrogate pairs and we want the old
10729
10913
  // code-point iteration behavior for non-BMP characters.
10730
- const chars = /[\uD800-\uDFFF]/.test(inputText) ? Array.from(inputText) : inputText;
10914
+ const hasSurrogates = /[\uD800-\uDFFF]/.test(inputText);
10915
+ const inputMayContainInvalidStringChar = hasSurrogates || /[\u0000\uFFFE\uFFFF]/.test(inputText);
10916
+ const chars = hasSurrogates ? Array.from(inputText) : inputText;
10731
10917
  const n = chars.length;
10732
10918
  let i = 0;
10733
10919
  const tokens = [];
@@ -10762,14 +10948,47 @@ function lex(inputText, opts = {}) {
10762
10948
  // Hard stops: delimiters cannot appear unescaped inside PNAME tokens.
10763
10949
  if (cc === '{' || cc === '}' || cc === '(' || cc === ')' || cc === '[' || cc === ']' || cc === ';' || cc === ',') break;
10764
10950
 
10765
- // Dot is allowed inside PN_LOCAL, but not at the end.
10766
- if (cc === '.') {
10767
- if (!canContinueAfterDot(peek(1))) break;
10768
- if (out !== null) out.push('.');
10951
+ const code = cc.charCodeAt(0);
10952
+
10953
+ // Common ASCII QName/identifier characters. Keep this branch inline so
10954
+ // ordinary N3 files do not call through the full Unicode PN_CHARS predicate
10955
+ // for every character.
10956
+ if (
10957
+ code === 58 || // ':'
10958
+ code === 95 || // '_'
10959
+ code === 45 || // '-'
10960
+ (code >= 48 && code <= 57) ||
10961
+ (code >= 65 && code <= 90) ||
10962
+ (code >= 97 && code <= 122)
10963
+ ) {
10964
+ if (out !== null) out.push(cc);
10769
10965
  i++;
10770
10966
  continue;
10771
10967
  }
10772
10968
 
10969
+ // Dot is allowed inside PN_LOCAL, but not at the end.
10970
+ if (cc === '.') {
10971
+ const next = peek(1);
10972
+ if (next === null) break;
10973
+ const ncode = next.charCodeAt(0);
10974
+ if (
10975
+ next === '%' ||
10976
+ next === '\\' ||
10977
+ ncode === 58 ||
10978
+ ncode === 95 ||
10979
+ ncode === 45 ||
10980
+ (ncode >= 48 && ncode <= 57) ||
10981
+ (ncode >= 65 && ncode <= 90) ||
10982
+ (ncode >= 97 && ncode <= 122) ||
10983
+ isIdentChar(next)
10984
+ ) {
10985
+ if (out !== null) out.push('.');
10986
+ i++;
10987
+ continue;
10988
+ }
10989
+ break;
10990
+ }
10991
+
10773
10992
  // Percent escape: %HH
10774
10993
  if (cc === '%') {
10775
10994
  const h1 = peek(1);
@@ -11017,7 +11236,7 @@ function lex(inputText, opts = {}) {
11017
11236
  }
11018
11237
  const rawContent = sChars === null ? sliceChars(contentStart, closed ? i - 1 : i) : sChars.join('');
11019
11238
  const decoded = sChars === null ? rawContent : decodeN3StringEscapes(rawContent, start);
11020
- assertValidStringLiteralValue(decoded, start);
11239
+ if (sChars !== null || inputMayContainInvalidStringChar) assertValidStringLiteralValue(decoded, start);
11021
11240
  const s = JSON.stringify(decoded); // canonical short quoted form
11022
11241
  tokens.push(new Token('Literal', s, start));
11023
11242
  continue;
@@ -11107,7 +11326,7 @@ function lex(inputText, opts = {}) {
11107
11326
  }
11108
11327
  const rawContent = sChars === null ? sliceChars(contentStart, closed ? i - 1 : i) : sChars.join('');
11109
11328
  const decoded = sChars === null ? rawContent : decodeN3StringEscapes(rawContent, start);
11110
- assertValidStringLiteralValue(decoded, start);
11329
+ if (sChars !== null || inputMayContainInvalidStringChar) assertValidStringLiteralValue(decoded, start);
11111
11330
  const s = JSON.stringify(decoded); // canonical short quoted form
11112
11331
  tokens.push(new Token('Literal', s, start));
11113
11332
  continue;
@@ -12217,15 +12436,21 @@ class Parser {
12217
12436
 
12218
12437
  while (true) {
12219
12438
  const { verb, invert } = this.parseStatementVerb();
12220
- const objects = this.parseObjectList();
12221
12439
 
12222
- // If VERB or OBJECTS contained paths, their helper triples must come
12223
- // before the triples that consume the path results (Easter depends on this).
12224
- this.flushPendingTriples(out);
12440
+ while (true) {
12441
+ const o = this.parseTerm();
12442
+ const postTriples = this.pendingTriplesAfter;
12443
+ this.pendingTriplesAfter = [];
12444
+
12445
+ // If VERB or OBJECT contained paths, their helper triples must come
12446
+ // before the triple that consumes the path result (Easter depends on this).
12447
+ this.flushPendingTriples(out);
12225
12448
 
12226
- for (const { term: o, postTriples } of objects) {
12227
12449
  out.push(new Triple(invert ? o : subject, verb, invert ? subject : o));
12228
- if (postTriples && postTriples.length) out.push(...postTriples);
12450
+ if (postTriples.length) out.push(...postTriples);
12451
+
12452
+ if (this.peek().typ !== 'Comma') break;
12453
+ this.next();
12229
12454
  }
12230
12455
 
12231
12456
  if (this.peek().typ === 'Semicolon') {
@@ -12239,27 +12464,6 @@ class Parser {
12239
12464
  return out;
12240
12465
  }
12241
12466
 
12242
- parseObjectList() {
12243
- // Capture any trailing property-list triples produced while parsing each
12244
- // object term so we can emit them *after* the triple that references the
12245
- // term. (See pendingTriplesAfter in the constructor.)
12246
-
12247
- const objs = [];
12248
- const readObj = () => {
12249
- const o = this.parseTerm();
12250
- const post = this.pendingTriplesAfter;
12251
- this.pendingTriplesAfter = [];
12252
- objs.push({ term: o, postTriples: post });
12253
- };
12254
-
12255
- readObj();
12256
- while (this.peek().typ === 'Comma') {
12257
- this.next();
12258
- readObj();
12259
- }
12260
- return objs;
12261
- }
12262
-
12263
12467
  makeRule(left, right, isForward) {
12264
12468
  let premiseTerm, conclTerm;
12265
12469