eyeling 1.6.19 → 1.6.20

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.
Files changed (2) hide show
  1. package/eyeling.js +109 -46
  2. package/package.json +1 -1
package/eyeling.js CHANGED
@@ -62,9 +62,9 @@ function termToJsonText(t) {
62
62
  function makeRdfJsonLiteral(jsonText) {
63
63
  // Prefer a readable long literal when safe; fall back to short if needed.
64
64
  if (!jsonText.includes('"""')) {
65
- return new Literal('"""' + jsonText + '"""^^<' + RDF_JSON_DT + '>');
65
+ return internLiteral('"""' + jsonText + '"""^^<' + RDF_JSON_DT + '>');
66
66
  }
67
- return new Literal(JSON.stringify(jsonText) + '^^<' + RDF_JSON_DT + '>');
67
+ return internLiteral(JSON.stringify(jsonText) + '^^<' + RDF_JSON_DT + '>');
68
68
  }
69
69
 
70
70
  // For a single reasoning run, this maps a canonical representation
@@ -212,6 +212,37 @@ class Var extends Term {
212
212
  }
213
213
  }
214
214
 
215
+ // ===========================================================================
216
+ // Term interning
217
+ // ===========================================================================
218
+
219
+ // Intern IRIs and literals by their raw lexical string.
220
+ // This reduces allocations when the same terms repeat and can improve performance.
221
+ //
222
+ // NOTE: Terms are treated as immutable. Do NOT mutate .value on interned objects.
223
+ const __iriIntern = new Map();
224
+ const __literalIntern = new Map();
225
+
226
+ /** @param {string} value */
227
+ function internIri(value) {
228
+ let t = __iriIntern.get(value);
229
+ if (!t) {
230
+ t = new Iri(value);
231
+ __iriIntern.set(value, t);
232
+ }
233
+ return t;
234
+ }
235
+
236
+ /** @param {string} value */
237
+ function internLiteral(value) {
238
+ let t = __literalIntern.get(value);
239
+ if (!t) {
240
+ t = new Literal(value);
241
+ __literalIntern.set(value, t);
242
+ }
243
+ return t;
244
+ }
245
+
215
246
  class Blank extends Term {
216
247
  constructor(label) {
217
248
  super();
@@ -976,23 +1007,23 @@ class Parser {
976
1007
  const val = tok.value;
977
1008
 
978
1009
  if (typ === 'Equals') {
979
- return new Iri(OWL_NS + 'sameAs');
1010
+ return internIri(OWL_NS + 'sameAs');
980
1011
  }
981
1012
 
982
1013
  if (typ === 'IriRef') {
983
1014
  const base = this.prefixes.map[''] || '';
984
- return new Iri(resolveIriRef(val || '', base));
1015
+ return internIri(resolveIriRef(val || '', base));
985
1016
  }
986
1017
  if (typ === 'Ident') {
987
1018
  const name = val || '';
988
1019
  if (name === 'a') {
989
- return new Iri(RDF_NS + 'type');
1020
+ return internIri(RDF_NS + 'type');
990
1021
  } else if (name.startsWith('_:')) {
991
1022
  return new Blank(name);
992
1023
  } else if (name.includes(':')) {
993
- return new Iri(this.prefixes.expandQName(name));
1024
+ return internIri(this.prefixes.expandQName(name));
994
1025
  } else {
995
- return new Iri(name);
1026
+ return internIri(name);
996
1027
  }
997
1028
  }
998
1029
 
@@ -1030,7 +1061,7 @@ class Parser {
1030
1061
  }
1031
1062
  s = `${s}^^<${dtIri}>`;
1032
1063
  }
1033
- return new Literal(s);
1064
+ return internLiteral(s);
1034
1065
  }
1035
1066
 
1036
1067
  if (typ === 'Var') return new Var(val || '');
@@ -1069,7 +1100,7 @@ class Parser {
1069
1100
  let invert = false;
1070
1101
  if (this.peek().typ === 'Ident' && (this.peek().value || '') === 'a') {
1071
1102
  this.next();
1072
- pred = new Iri(RDF_NS + 'type');
1103
+ pred = internIri(RDF_NS + 'type');
1073
1104
  } else if (this.peek().typ === 'OpPredInvert') {
1074
1105
  this.next(); // consume "<-"
1075
1106
  pred = this.parseTerm();
@@ -1113,7 +1144,7 @@ class Parser {
1113
1144
  if (this.peek().typ === 'OpImplies') {
1114
1145
  this.next();
1115
1146
  const right = this.parseTerm();
1116
- const pred = new Iri(LOG_NS + 'implies');
1147
+ const pred = internIri(LOG_NS + 'implies');
1117
1148
  triples.push(new Triple(left, pred, right));
1118
1149
  if (this.peek().typ === 'Dot') this.next();
1119
1150
  else if (this.peek().typ === 'RBrace') {
@@ -1124,7 +1155,7 @@ class Parser {
1124
1155
  } else if (this.peek().typ === 'OpImpliedBy') {
1125
1156
  this.next();
1126
1157
  const right = this.parseTerm();
1127
- const pred = new Iri(LOG_NS + 'impliedBy');
1158
+ const pred = internIri(LOG_NS + 'impliedBy');
1128
1159
  triples.push(new Triple(left, pred, right));
1129
1160
  if (this.peek().typ === 'Dot') this.next();
1130
1161
  else if (this.peek().typ === 'RBrace') {
@@ -1172,7 +1203,7 @@ class Parser {
1172
1203
 
1173
1204
  if (this.peek().typ === 'Ident' && (this.peek().value || '') === 'a') {
1174
1205
  this.next();
1175
- verb = new Iri(RDF_NS + 'type');
1206
+ verb = internIri(RDF_NS + 'type');
1176
1207
  } else if (this.peek().typ === 'OpPredInvert') {
1177
1208
  this.next(); // "<-"
1178
1209
  verb = this.parseTerm();
@@ -1669,13 +1700,18 @@ function tripleFastKey(tr) {
1669
1700
  }
1670
1701
 
1671
1702
  function ensureFactIndexes(facts) {
1672
- if (facts.__byPred && facts.__byPO && facts.__keySet) return;
1703
+ if (facts.__byPred && facts.__byPS && facts.__byPO && facts.__keySet) return;
1673
1704
 
1674
1705
  Object.defineProperty(facts, '__byPred', {
1675
1706
  value: new Map(),
1676
1707
  enumerable: false,
1677
1708
  writable: true,
1678
1709
  });
1710
+ Object.defineProperty(facts, '__byPS', {
1711
+ value: new Map(),
1712
+ enumerable: false,
1713
+ writable: true,
1714
+ });
1679
1715
  Object.defineProperty(facts, '__byPO', {
1680
1716
  value: new Map(),
1681
1717
  enumerable: false,
@@ -1701,6 +1737,21 @@ function indexFact(facts, tr) {
1701
1737
  }
1702
1738
  pb.push(tr);
1703
1739
 
1740
+ const sk = termFastKey(tr.s);
1741
+ if (sk !== null) {
1742
+ let ps = facts.__byPS.get(pk);
1743
+ if (!ps) {
1744
+ ps = new Map();
1745
+ facts.__byPS.set(pk, ps);
1746
+ }
1747
+ let psb = ps.get(sk);
1748
+ if (!psb) {
1749
+ psb = [];
1750
+ ps.set(sk, psb);
1751
+ }
1752
+ psb.push(tr);
1753
+ }
1754
+
1704
1755
  const ok = termFastKey(tr.o);
1705
1756
  if (ok !== null) {
1706
1757
  let po = facts.__byPO.get(pk);
@@ -1727,15 +1778,27 @@ function candidateFacts(facts, goal) {
1727
1778
  if (goal.p instanceof Iri) {
1728
1779
  const pk = goal.p.value;
1729
1780
 
1781
+ const sk = termFastKey(goal.s);
1730
1782
  const ok = termFastKey(goal.o);
1783
+
1784
+ /** @type {Triple[] | null} */
1785
+ let byPS = null;
1786
+ if (sk !== null) {
1787
+ const ps = facts.__byPS.get(pk);
1788
+ if (ps) byPS = ps.get(sk) || null;
1789
+ }
1790
+
1791
+ /** @type {Triple[] | null} */
1792
+ let byPO = null;
1731
1793
  if (ok !== null) {
1732
1794
  const po = facts.__byPO.get(pk);
1733
- if (po) {
1734
- const pob = po.get(ok);
1735
- if (pob) return pob;
1736
- }
1795
+ if (po) byPO = po.get(ok) || null;
1737
1796
  }
1738
1797
 
1798
+ if (byPS && byPO) return byPS.length <= byPO.length ? byPS : byPO;
1799
+ if (byPS) return byPS;
1800
+ if (byPO) return byPO;
1801
+
1739
1802
  return facts.__byPred.get(pk) || [];
1740
1803
  }
1741
1804
 
@@ -2332,7 +2395,7 @@ function termToJsString(t) {
2332
2395
  function makeStringLiteral(str) {
2333
2396
  // JSON.stringify gives us a valid N3/Turtle-style quoted string
2334
2397
  // (with proper escaping for quotes, backslashes, newlines, …).
2335
- return new Literal(JSON.stringify(str));
2398
+ return internLiteral(JSON.stringify(str));
2336
2399
  }
2337
2400
 
2338
2401
  function termToJsStringDecoded(t) {
@@ -2381,8 +2444,8 @@ function jsonPointerUnescape(seg) {
2381
2444
  function jsonToTerm(v) {
2382
2445
  if (v === null) return makeStringLiteral('null');
2383
2446
  if (typeof v === 'string') return makeStringLiteral(v);
2384
- if (typeof v === 'number') return new Literal(String(v));
2385
- if (typeof v === 'boolean') return new Literal(v ? 'true' : 'false');
2447
+ if (typeof v === 'number') return internLiteral(String(v));
2448
+ if (typeof v === 'boolean') return internLiteral(v ? 'true' : 'false');
2386
2449
  if (Array.isArray(v)) return new ListTerm(v.map(jsonToTerm));
2387
2450
 
2388
2451
  if (v && typeof v === 'object') {
@@ -2855,7 +2918,7 @@ function formatDurationLiteralFromSeconds(secs) {
2855
2918
  const neg = secs < 0;
2856
2919
  const days = Math.round(Math.abs(secs) / 86400.0);
2857
2920
  const literalLex = neg ? `"-P${days}D"` : `"P${days}D"`;
2858
- return new Literal(`${literalLex}^^<${XSD_NS}duration>`);
2921
+ return internLiteral(`${literalLex}^^<${XSD_NS}duration>`);
2859
2922
  }
2860
2923
  function numEqualTerm(t, n, eps = 1e-9) {
2861
2924
  const v = parseNum(t);
@@ -2967,21 +3030,21 @@ function commonNumericDatatype(terms, outTerm) {
2967
3030
 
2968
3031
  function makeNumericOutputLiteral(val, dt) {
2969
3032
  if (dt === XSD_INTEGER_DT) {
2970
- if (typeof val === 'bigint') return new Literal(val.toString());
2971
- if (Number.isInteger(val)) return new Literal(String(val));
3033
+ if (typeof val === 'bigint') return internLiteral(val.toString());
3034
+ if (Number.isInteger(val)) return internLiteral(String(val));
2972
3035
  // If a non-integer sneaks in, promote to decimal.
2973
- return new Literal(`"${formatNum(val)}"^^<${XSD_DECIMAL_DT}>`);
3036
+ return internLiteral(`"${formatNum(val)}"^^<${XSD_DECIMAL_DT}>`);
2974
3037
  }
2975
3038
 
2976
3039
  if (dt === XSD_FLOAT_DT || dt === XSD_DOUBLE_DT) {
2977
3040
  const sp = formatXsdFloatSpecialLex(val);
2978
3041
  const lex = sp !== null ? sp : formatNum(val);
2979
- return new Literal(`"${lex}"^^<${dt}>`);
3042
+ return internLiteral(`"${lex}"^^<${dt}>`);
2980
3043
  }
2981
3044
 
2982
3045
  // decimal
2983
3046
  const lex = typeof val === 'bigint' ? val.toString() : formatNum(val);
2984
- return new Literal(`"${lex}"^^<${dt}>`);
3047
+ return internLiteral(`"${lex}"^^<${dt}>`);
2985
3048
  }
2986
3049
 
2987
3050
  function evalUnaryMathRel(g, subst, forwardFn, inverseFn /* may be null */) {
@@ -3229,7 +3292,7 @@ function hashLiteralTerm(t, algo) {
3229
3292
  const input = stripQuotes(lex);
3230
3293
  try {
3231
3294
  const digest = nodeCrypto.createHash(algo).update(input, 'utf8').digest('hex');
3232
- return new Literal(JSON.stringify(digest));
3295
+ return internLiteral(JSON.stringify(digest));
3233
3296
  } catch (e) {
3234
3297
  return null;
3235
3298
  }
@@ -3429,7 +3492,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3429
3492
  if (secs !== null) {
3430
3493
  const outSecs = aDt.getTime() / 1000.0 - secs;
3431
3494
  const lex = utcIsoDateTimeStringFromEpochSeconds(outSecs);
3432
- const lit = new Literal(`"${lex}"^^<${XSD_NS}dateTime>`);
3495
+ const lit = internLiteral(`"${lex}"^^<${XSD_NS}dateTime>`);
3433
3496
  if (g.o instanceof Var) {
3434
3497
  const s2 = { ...subst };
3435
3498
  s2[g.o.name] = lit;
@@ -3445,7 +3508,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3445
3508
  const bi = parseIntLiteral(b0);
3446
3509
  if (ai !== null && bi !== null) {
3447
3510
  const ci = ai - bi;
3448
- const lit = new Literal(ci.toString());
3511
+ const lit = internLiteral(ci.toString());
3449
3512
  if (g.o instanceof Var) {
3450
3513
  const s2 = { ...subst };
3451
3514
  s2[g.o.name] = lit;
@@ -3480,7 +3543,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3480
3543
  }
3481
3544
 
3482
3545
  // Fallback (if you *don’t* have those helpers yet):
3483
- const lit = new Literal(formatNum(c));
3546
+ const lit = internLiteral(formatNum(c));
3484
3547
  if (g.o instanceof Var) {
3485
3548
  const s2 = { ...subst };
3486
3549
  s2[g.o.name] = lit;
@@ -3531,7 +3594,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3531
3594
  if (ai !== null && bi !== null) {
3532
3595
  if (bi === 0n) return [];
3533
3596
  const q = ai / bi; // BigInt division truncates toward zero
3534
- const lit = new Literal(q.toString());
3597
+ const lit = internLiteral(q.toString());
3535
3598
  if (g.o instanceof Var) {
3536
3599
  const s2 = { ...subst };
3537
3600
  s2[g.o.name] = lit;
@@ -3560,7 +3623,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3560
3623
  if (!Number.isInteger(a) || !Number.isInteger(b)) return [];
3561
3624
 
3562
3625
  const q = Math.trunc(a / b);
3563
- const lit = new Literal(String(q));
3626
+ const lit = internLiteral(String(q));
3564
3627
  if (g.o instanceof Var) {
3565
3628
  const s2 = { ...subst };
3566
3629
  s2[g.o.name] = lit;
@@ -3758,7 +3821,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3758
3821
  if (Number.isNaN(a)) return [];
3759
3822
 
3760
3823
  const rVal = Math.round(a); // ties go toward +∞ in JS (e.g., -1.5 -> -1)
3761
- const lit = new Literal(String(rVal)); // integer token
3824
+ const lit = internLiteral(String(rVal)); // integer token
3762
3825
 
3763
3826
  if (g.o instanceof Var) {
3764
3827
  const s2 = { ...subst };
@@ -3786,7 +3849,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3786
3849
 
3787
3850
  if (g.o instanceof Var) {
3788
3851
  const s2 = { ...subst };
3789
- s2[g.o.name] = new Literal(`"${now}"^^<${XSD_NS}dateTime>`);
3852
+ s2[g.o.name] = internLiteral(`"${now}"^^<${XSD_NS}dateTime>`);
3790
3853
  return [s2];
3791
3854
  }
3792
3855
  if (g.o instanceof Literal) {
@@ -3848,7 +3911,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3848
3911
  const outs = [];
3849
3912
 
3850
3913
  for (let i = 0; i < xs.length; i++) {
3851
- const idxLit = new Literal(String(i)); // 0-based
3914
+ const idxLit = internLiteral(String(i)); // 0-based
3852
3915
  const val = xs[i];
3853
3916
 
3854
3917
  // Fast path: object is exactly a 2-element list (idx, value)
@@ -3904,7 +3967,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3904
3967
  const outs = [];
3905
3968
 
3906
3969
  for (let i = 0; i < xs.length; i++) {
3907
- const idxLit = new Literal(String(i)); // index starts at 0
3970
+ const idxLit = internLiteral(String(i)); // index starts at 0
3908
3971
 
3909
3972
  // --- index side: strict if ground, otherwise unify/bind
3910
3973
  let s1 = null;
@@ -3980,7 +4043,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3980
4043
  // list:length (strict: do not accept integer<->decimal matches for a ground object)
3981
4044
  if (pv === LIST_NS + 'length') {
3982
4045
  if (!(g.s instanceof ListTerm)) return [];
3983
- const nTerm = new Literal(String(g.s.elems.length));
4046
+ const nTerm = internLiteral(String(g.s.elems.length));
3984
4047
 
3985
4048
  const o2 = applySubstTerm(g.o, subst);
3986
4049
  if (isGroundTerm(o2)) {
@@ -4092,7 +4155,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
4092
4155
  if (!(inputTerm instanceof ListTerm)) return [];
4093
4156
  const inputList = inputTerm.elems;
4094
4157
  if (!(predTerm instanceof Iri)) return [];
4095
- const pred = new Iri(predTerm.value);
4158
+ const pred = internIri(predTerm.value);
4096
4159
  if (!isBuiltinPred(pred)) return [];
4097
4160
  if (!inputList.every((e) => isGroundTerm(e))) return [];
4098
4161
 
@@ -4185,8 +4248,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
4185
4248
  }
4186
4249
 
4187
4250
  if (oDt !== null) {
4188
- const strLit = isQuotedLexical(oLex) ? new Literal(oLex) : makeStringLiteral(String(oLex));
4189
- const subjList = new ListTerm([strLit, new Iri(oDt)]);
4251
+ const strLit = isQuotedLexical(oLex) ? internLiteral(oLex) : makeStringLiteral(String(oLex));
4252
+ const subjList = new ListTerm([strLit, internIri(oDt)]);
4190
4253
  const s2 = unifyTerm(goal.s, subjList, subst);
4191
4254
  if (s2 !== null) results.push(s2);
4192
4255
  }
@@ -4205,7 +4268,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
4205
4268
  if (okString) {
4206
4269
  const dtIri = b.value;
4207
4270
  // For xsd:string, prefer the plain string literal form.
4208
- const outLit = dtIri === XSD_NS + 'string' ? new Literal(sLex) : new Literal(`${sLex}^^<${dtIri}>`);
4271
+ const outLit = dtIri === XSD_NS + 'string' ? internLiteral(sLex) : internLiteral(`${sLex}^^<${dtIri}>`);
4209
4272
  const s2 = unifyTerm(goal.o, outLit, subst);
4210
4273
  if (s2 !== null) results.push(s2);
4211
4274
  }
@@ -4241,7 +4304,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
4241
4304
  const tag = extractLangTag(g.o.value);
4242
4305
  if (tag !== null) {
4243
4306
  const [oLex] = literalParts(g.o.value); // strips @lang into lexical part
4244
- const strLit = isQuotedLexical(oLex) ? new Literal(oLex) : makeStringLiteral(String(oLex));
4307
+ const strLit = isQuotedLexical(oLex) ? internLiteral(oLex) : makeStringLiteral(String(oLex));
4245
4308
  const langLit = makeStringLiteral(tag);
4246
4309
  const subjList = new ListTerm([strLit, langLit]);
4247
4310
  const s2 = unifyTerm(goal.s, subjList, subst);
@@ -4261,7 +4324,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
4261
4324
  if (okString && okLang) {
4262
4325
  const tag = stripQuotes(langLex);
4263
4326
  if (LANG_RE.test(tag)) {
4264
- const outLit = new Literal(`${sLex}@${tag}`);
4327
+ const outLit = internLiteral(`${sLex}@${tag}`);
4265
4328
  const s2 = unifyTerm(goal.o, outLit, subst);
4266
4329
  if (s2 !== null) results.push(s2);
4267
4330
  }
@@ -4283,7 +4346,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
4283
4346
  const r = standardizeRule(r0, varGen);
4284
4347
 
4285
4348
  const premF = new FormulaTerm(r.premise);
4286
- const concTerm = r0.isFuse ? new Literal('false') : new FormulaTerm(r.conclusion);
4349
+ const concTerm = r0.isFuse ? internLiteral('false') : new FormulaTerm(r.conclusion);
4287
4350
 
4288
4351
  // unify subject with the premise formula
4289
4352
  let s2 = unifyTerm(goal.s, premF, subst);
@@ -4420,7 +4483,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
4420
4483
  let iri = skolemCache.get(key);
4421
4484
  if (!iri) {
4422
4485
  const id = deterministicSkolemIdFromKey(key);
4423
- iri = new Iri(SKOLEM_NS + id);
4486
+ iri = internIri(SKOLEM_NS + id);
4424
4487
  skolemCache.set(key, iri);
4425
4488
  }
4426
4489
 
@@ -4442,7 +4505,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
4442
4505
  if (g.o instanceof Literal) {
4443
4506
  const uriStr = termToJsString(g.o); // JS string from the literal
4444
4507
  if (uriStr === null) return [];
4445
- const iri = new Iri(uriStr);
4508
+ const iri = internIri(uriStr);
4446
4509
  const s2 = unifyTerm(goal.s, iri, subst);
4447
4510
  return s2 !== null ? [s2] : [];
4448
4511
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.6.19",
3
+ "version": "1.6.20",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [