eyeling 1.25.1 → 1.25.3

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/lib/engine.js CHANGED
@@ -362,6 +362,14 @@ function __prepareForwardRule(r) {
362
362
  configurable: true,
363
363
  });
364
364
  }
365
+ if (!hasOwn.call(r, '__needsForwardSkipCheck')) {
366
+ Object.defineProperty(r, '__needsForwardSkipCheck', {
367
+ value: !!(r.__headIsStrictGround || (r.__scopedSkipInfo && r.__scopedSkipInfo.needsSnap)),
368
+ enumerable: false,
369
+ writable: false,
370
+ configurable: true,
371
+ });
372
+ }
365
373
  }
366
374
 
367
375
  function __graphTriplesOrTrue(term) {
@@ -680,6 +688,11 @@ function skolemizeTermForHeadBlanks(t, headBlankLabels, mapping, skCounter, firi
680
688
  }
681
689
 
682
690
  function skolemizeTripleForHeadBlanks(tr, headBlankLabels, mapping, skCounter, firingKey, globalMap) {
691
+ // Fast path: the common case has no explicit head blanks. Do not allocate a
692
+ // replacement Triple or compute a firing key when skolemization cannot change
693
+ // anything. This matters for long single-premise chains such as
694
+ // deep-taxonomy-100000, where every derived head triple is otherwise copied.
695
+ if (!headBlankLabels || headBlankLabels.size === 0) return tr;
683
696
  return new Triple(
684
697
  skolemizeTermForHeadBlanks(tr.s, headBlankLabels, mapping, skCounter, firingKey, globalMap),
685
698
  skolemizeTermForHeadBlanks(tr.p, headBlankLabels, mapping, skCounter, firingKey, globalMap),
@@ -1155,6 +1168,35 @@ function ensureFactIndexes(facts) {
1155
1168
  for (let i = 0; i < facts.length; i++) indexFact(facts, facts[i], i, false);
1156
1169
  }
1157
1170
 
1171
+ function cloneFactIndexesForSnapshot(src, dest) {
1172
+ ensureFactIndexes(src);
1173
+
1174
+ function cloneArrayMap(map) {
1175
+ const out = new Map();
1176
+ for (const [k, arr] of map) out.set(k, arr.slice());
1177
+ return out;
1178
+ }
1179
+
1180
+ function cloneNestedArrayMap(map) {
1181
+ const out = new Map();
1182
+ for (const [k, inner] of map) {
1183
+ const innerOut = new Map();
1184
+ for (const [k2, arr] of inner) innerOut.set(k2, arr.slice());
1185
+ out.set(k, innerOut);
1186
+ }
1187
+ return out;
1188
+ }
1189
+
1190
+ Object.defineProperty(dest, '__byPred', { value: cloneArrayMap(src.__byPred), enumerable: false, writable: true });
1191
+ Object.defineProperty(dest, '__byPS', { value: cloneNestedArrayMap(src.__byPS), enumerable: false, writable: true });
1192
+ Object.defineProperty(dest, '__byPO', { value: cloneNestedArrayMap(src.__byPO), enumerable: false, writable: true });
1193
+ Object.defineProperty(dest, '__wildPred', { value: src.__wildPred.slice(), enumerable: false, writable: true });
1194
+ Object.defineProperty(dest, '__wildPS', { value: cloneArrayMap(src.__wildPS), enumerable: false, writable: true });
1195
+ Object.defineProperty(dest, '__wildPO', { value: cloneArrayMap(src.__wildPO), enumerable: false, writable: true });
1196
+ Object.defineProperty(dest, '__keySet', { value: new Set(src.__keySet), enumerable: false, writable: true });
1197
+ Object.defineProperty(dest, '__keySetComplete', { value: !!src.__keySetComplete, enumerable: false, writable: true });
1198
+ }
1199
+
1158
1200
  function indexFact(facts, tr, idx, addKeySet = true) {
1159
1201
  const sk = termFastKey(tr.s);
1160
1202
  const ok = termFastKey(tr.o);
@@ -1441,13 +1483,20 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
1441
1483
  if (!isSinglePremiseAgendaRuleSafe(r, backRules)) continue;
1442
1484
 
1443
1485
  const goal = r.premise[0];
1486
+ const goalSKey = termFastKey(goal.s);
1487
+ const goalOKey = termFastKey(goal.o);
1488
+ const fastSubjectVar = goal.p instanceof Iri && goal.s instanceof Var && goalOKey !== null ? goal.s.name : null;
1489
+ const fastObjectVar = goal.p instanceof Iri && goal.o instanceof Var && goalSKey !== null ? goal.o.name : null;
1444
1490
  const entry = {
1445
1491
  rule: r,
1446
1492
  ruleIndex: i,
1447
1493
  goal,
1448
1494
  goalPredTid: goal.p instanceof Iri ? goal.p.__tid : null,
1449
- goalSKey: termFastKey(goal.s),
1450
- goalOKey: termFastKey(goal.o),
1495
+ goalSKey,
1496
+ goalOKey,
1497
+ needsSkipCheck: !!r.__needsForwardSkipCheck,
1498
+ fastSubjectVar,
1499
+ fastObjectVar,
1451
1500
  };
1452
1501
 
1453
1502
  index.indexed.add(r);
@@ -2852,6 +2901,8 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
2852
2901
  __attachGoalTable(backRules, goalTable);
2853
2902
 
2854
2903
  const captureExplanations = !(opts && opts.captureExplanations === false);
2904
+ const collectDerived = !(opts && opts.collectDerived === false);
2905
+ const hasDerivedCallback = typeof onDerived === 'function';
2855
2906
  const derivedForward = [];
2856
2907
  const varGen = [0];
2857
2908
  const skCounter = [0];
@@ -2908,7 +2959,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
2908
2959
 
2909
2960
  function makeScopedSnapshot() {
2910
2961
  const snap = facts.slice();
2911
- ensureFactIndexes(snap);
2962
+ cloneFactIndexesForSnapshot(facts, snap);
2912
2963
  Object.defineProperty(snap, '__scopedSnapshot', {
2913
2964
  value: snap,
2914
2965
  enumerable: false,
@@ -2962,10 +3013,21 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
2962
3013
  let changedHere = false;
2963
3014
  let rulesChanged = false;
2964
3015
 
2965
- // IMPORTANT: one skolem map per *rule firing*
3016
+ // IMPORTANT: one skolem map per *rule firing*. Instantiate premise
3017
+ // triples and build the firing key lazily: normal CLI runs do not capture
3018
+ // proof records, and most rules have no explicit head blanks, so the eager
3019
+ // work was pure allocation on large forward chains.
2966
3020
  const skMap = {};
2967
- const instantiatedPremises = r.premise.map((b) => applySubstTriple(b, s));
2968
- const fireKey = __firingKey(ruleIndex, instantiatedPremises);
3021
+ let instantiatedPremises = null;
3022
+ let fireKey = null;
3023
+ function getInstantiatedPremises() {
3024
+ if (instantiatedPremises === null) instantiatedPremises = r.premise.map((b) => applySubstTriple(b, s));
3025
+ return instantiatedPremises;
3026
+ }
3027
+ function getFireKey() {
3028
+ if (fireKey === null) fireKey = __firingKey(ruleIndex, getInstantiatedPremises());
3029
+ return fireKey;
3030
+ }
2969
3031
 
2970
3032
  // Support "dynamic" rule heads where the consequent is a term that
2971
3033
  // (after substitution) evaluates to a quoted formula.
@@ -3018,9 +3080,9 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
3018
3080
  if (isFwRuleTriple || isBwRuleTriple) {
3019
3081
  if (!hasFactIndexed(facts, instantiated)) {
3020
3082
  pushFactIndexed(facts, instantiated);
3021
- const df = makeDerivedRecord(instantiated, r, instantiatedPremises, s, captureExplanations);
3022
- derivedForward.push(df);
3023
- if (typeof onDerived === 'function') onDerived(df);
3083
+ const df = makeDerivedRecord(instantiated, r, getInstantiatedPremises(), s, captureExplanations);
3084
+ if (collectDerived) derivedForward.push(df);
3085
+ if (hasDerivedCallback) onDerived(df);
3024
3086
  changedHere = true;
3025
3087
  }
3026
3088
 
@@ -3066,22 +3128,25 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
3066
3128
  }
3067
3129
 
3068
3130
  // Only skolemize blank nodes that occur explicitly in the rule head
3069
- const inst = skolemizeTripleForHeadBlanks(
3070
- instantiated,
3071
- headBlankLabelsHere,
3072
- skMap,
3073
- skCounter,
3074
- fireKey,
3075
- headSkolemCache,
3076
- );
3131
+ const inst =
3132
+ headBlankLabelsHere && headBlankLabelsHere.size
3133
+ ? skolemizeTripleForHeadBlanks(
3134
+ instantiated,
3135
+ headBlankLabelsHere,
3136
+ skMap,
3137
+ skCounter,
3138
+ getFireKey(),
3139
+ headSkolemCache,
3140
+ )
3141
+ : instantiated;
3077
3142
 
3078
3143
  if (!isGroundTriple(inst)) continue;
3079
3144
  if (hasFactIndexed(facts, inst)) continue;
3080
3145
 
3081
3146
  pushFactIndexed(facts, inst);
3082
- const df = makeDerivedRecord(inst, r, instantiatedPremises, s, captureExplanations);
3083
- derivedForward.push(df);
3084
- if (typeof onDerived === 'function') onDerived(df);
3147
+ const df = makeDerivedRecord(inst, r, getInstantiatedPremises(), s, captureExplanations);
3148
+ if (collectDerived) derivedForward.push(df);
3149
+ if (hasDerivedCallback) onDerived(df);
3085
3150
 
3086
3151
  changedHere = true;
3087
3152
  }
@@ -3106,10 +3171,19 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
3106
3171
  for (let ci = 0; ci < total; ci++) {
3107
3172
  const entry = ci < candidates.exactLen ? candidates.exact[ci] : candidates.wild[ci - candidates.exactLen];
3108
3173
  const r = entry.rule;
3109
- if (__skipForwardRuleNow(r)) continue;
3110
-
3111
- const s = unifyTriple(entry.goal, fact, __emptySubst());
3112
- if (s === null) continue;
3174
+ if (entry.needsSkipCheck && __skipForwardRuleNow(r)) continue;
3175
+
3176
+ let s;
3177
+ if (entry.fastSubjectVar !== null) {
3178
+ s = __emptySubst();
3179
+ s[entry.fastSubjectVar] = fact.s;
3180
+ } else if (entry.fastObjectVar !== null) {
3181
+ s = __emptySubst();
3182
+ s[entry.fastObjectVar] = fact.o;
3183
+ } else {
3184
+ s = unifyTriple(entry.goal, fact, __emptySubst());
3185
+ if (s === null) continue;
3186
+ }
3113
3187
 
3114
3188
  const outcome = __emitForwardRuleSolution(r, entry.ruleIndex, s);
3115
3189
  if (outcome.rulesChanged) {
@@ -3126,7 +3200,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
3126
3200
  for (let i = 0; i < forwardRules.length; i++) {
3127
3201
  const r = forwardRules[i];
3128
3202
  if (agendaIndex.indexed.has(r)) continue;
3129
- if (__skipForwardRuleNow(r)) continue;
3203
+ if (r.__needsForwardSkipCheck && __skipForwardRuleNow(r)) continue;
3130
3204
 
3131
3205
  const headIsStrictGround = r.__headIsStrictGround;
3132
3206
  const maxSols = r.isFuse || headIsStrictGround ? 1 : undefined;
package/lib/lexer.js CHANGED
@@ -121,15 +121,6 @@ function isIdentChar(c) {
121
121
  return c === ':' || isPnChars(c);
122
122
  }
123
123
 
124
- function canContinueAfterDot(next) {
125
- // PN_LOCAL allows '.' but it cannot appear at the end.
126
- // We include '.' only if it is followed by something that could continue a name.
127
- if (next === null) return false;
128
- if (isIdentChar(next)) return true;
129
- if (next === '%' || next === '\\') return true;
130
- return false;
131
- }
132
-
133
124
  function isForbiddenNoncharacterCodePoint(cp) {
134
125
  return (cp & 0xffff) === 0xfffe || (cp & 0xffff) === 0xffff;
135
126
  }
@@ -1189,13 +1180,25 @@ function normalizeRdfCompatibility(inputText) {
1189
1180
  return text;
1190
1181
  }
1191
1182
 
1183
+
1184
+ function isNumericLikeIdentifier(word) {
1185
+ if (typeof word !== 'string' || word.length === 0) return false;
1186
+ for (let j = 0; j < word.length; j++) {
1187
+ const code = word.charCodeAt(j);
1188
+ if (!((code >= 48 && code <= 57) || code === 46 || code === 45)) return false;
1189
+ }
1190
+ return true;
1191
+ }
1192
+
1192
1193
  function lex(inputText, opts = {}) {
1193
1194
  const rdf = !!(opts && opts.rdf);
1194
1195
  if (rdf) inputText = normalizeRdfCompatibility(inputText);
1195
1196
  // Avoid copying large ASCII/BMP inputs into an Array. Array.from() is
1196
1197
  // only needed when the text contains surrogate pairs and we want the old
1197
1198
  // code-point iteration behavior for non-BMP characters.
1198
- const chars = /[\uD800-\uDFFF]/.test(inputText) ? Array.from(inputText) : inputText;
1199
+ const hasSurrogates = /[\uD800-\uDFFF]/.test(inputText);
1200
+ const inputMayContainInvalidStringChar = hasSurrogates || /[\u0000\uFFFE\uFFFF]/.test(inputText);
1201
+ const chars = hasSurrogates ? Array.from(inputText) : inputText;
1199
1202
  const n = chars.length;
1200
1203
  let i = 0;
1201
1204
  const tokens = [];
@@ -1230,14 +1233,47 @@ function lex(inputText, opts = {}) {
1230
1233
  // Hard stops: delimiters cannot appear unescaped inside PNAME tokens.
1231
1234
  if (cc === '{' || cc === '}' || cc === '(' || cc === ')' || cc === '[' || cc === ']' || cc === ';' || cc === ',') break;
1232
1235
 
1233
- // Dot is allowed inside PN_LOCAL, but not at the end.
1234
- if (cc === '.') {
1235
- if (!canContinueAfterDot(peek(1))) break;
1236
- if (out !== null) out.push('.');
1236
+ const code = cc.charCodeAt(0);
1237
+
1238
+ // Common ASCII QName/identifier characters. Keep this branch inline so
1239
+ // ordinary N3 files do not call through the full Unicode PN_CHARS predicate
1240
+ // for every character.
1241
+ if (
1242
+ code === 58 || // ':'
1243
+ code === 95 || // '_'
1244
+ code === 45 || // '-'
1245
+ (code >= 48 && code <= 57) ||
1246
+ (code >= 65 && code <= 90) ||
1247
+ (code >= 97 && code <= 122)
1248
+ ) {
1249
+ if (out !== null) out.push(cc);
1237
1250
  i++;
1238
1251
  continue;
1239
1252
  }
1240
1253
 
1254
+ // Dot is allowed inside PN_LOCAL, but not at the end.
1255
+ if (cc === '.') {
1256
+ const next = peek(1);
1257
+ if (next === null) break;
1258
+ const ncode = next.charCodeAt(0);
1259
+ if (
1260
+ next === '%' ||
1261
+ next === '\\' ||
1262
+ ncode === 58 ||
1263
+ ncode === 95 ||
1264
+ ncode === 45 ||
1265
+ (ncode >= 48 && ncode <= 57) ||
1266
+ (ncode >= 65 && ncode <= 90) ||
1267
+ (ncode >= 97 && ncode <= 122) ||
1268
+ isIdentChar(next)
1269
+ ) {
1270
+ if (out !== null) out.push('.');
1271
+ i++;
1272
+ continue;
1273
+ }
1274
+ break;
1275
+ }
1276
+
1241
1277
  // Percent escape: %HH
1242
1278
  if (cc === '%') {
1243
1279
  const h1 = peek(1);
@@ -1356,22 +1392,47 @@ function lex(inputText, opts = {}) {
1356
1392
  continue;
1357
1393
  }
1358
1394
 
1359
- // 5) Single-character punctuation
1360
- if ('{}()[];,.'.includes(c)) {
1361
- const mapping = {
1362
- '{': 'LBrace',
1363
- '}': 'RBrace',
1364
- '(': 'LParen',
1365
- ')': 'RParen',
1366
- '[': 'LBracket',
1367
- ']': 'RBracket',
1368
- ';': 'Semicolon',
1369
- ',': 'Comma',
1370
- '.': 'Dot',
1371
- };
1372
- tokens.push(new Token(mapping[c], null, i));
1373
- i++;
1374
- continue;
1395
+ // 5) Single-character punctuation. Use a switch rather than allocating a
1396
+ // mapping object for every punctuation token in large inputs.
1397
+ switch (c) {
1398
+ case '{':
1399
+ tokens.push(new Token('LBrace', null, i));
1400
+ i++;
1401
+ continue;
1402
+ case '}':
1403
+ tokens.push(new Token('RBrace', null, i));
1404
+ i++;
1405
+ continue;
1406
+ case '(':
1407
+ tokens.push(new Token('LParen', null, i));
1408
+ i++;
1409
+ continue;
1410
+ case ')':
1411
+ tokens.push(new Token('RParen', null, i));
1412
+ i++;
1413
+ continue;
1414
+ case '[':
1415
+ tokens.push(new Token('LBracket', null, i));
1416
+ i++;
1417
+ continue;
1418
+ case ']':
1419
+ tokens.push(new Token('RBracket', null, i));
1420
+ i++;
1421
+ continue;
1422
+ case ';':
1423
+ tokens.push(new Token('Semicolon', null, i));
1424
+ i++;
1425
+ continue;
1426
+ case ',':
1427
+ tokens.push(new Token('Comma', null, i));
1428
+ i++;
1429
+ continue;
1430
+ case '.':
1431
+ tokens.push(new Token('Dot', null, i));
1432
+ i++;
1433
+ continue;
1434
+ default:
1435
+ break;
1375
1436
  }
1376
1437
 
1377
1438
  // String literal: short "..." or long """..."""
@@ -1430,27 +1491,37 @@ function lex(inputText, opts = {}) {
1430
1491
  continue;
1431
1492
  }
1432
1493
 
1433
- // Short string literal " ... "
1494
+ // Short string literal " ... ". Most data files contain plain
1495
+ // unescaped labels; keep that path slice-based and avoid building an
1496
+ // intermediate character array + raw quoted string.
1434
1497
  i++; // consume opening "
1435
- const sChars = [];
1498
+ const contentStart = i;
1499
+ let sChars = null;
1500
+ let closed = false;
1436
1501
  while (i < n) {
1437
1502
  const cc = chars[i];
1438
1503
  i++;
1439
1504
  if (cc === '\\') {
1505
+ if (sChars === null) sChars = [sliceChars(contentStart, i - 1)];
1440
1506
  if (i < n) {
1441
1507
  const esc = chars[i];
1442
1508
  i++;
1443
1509
  sChars.push('\\');
1444
1510
  sChars.push(esc);
1511
+ } else {
1512
+ sChars.push('\\');
1445
1513
  }
1446
1514
  continue;
1447
1515
  }
1448
- if (cc === '"') break;
1449
- sChars.push(cc);
1516
+ if (cc === '"') {
1517
+ closed = true;
1518
+ break;
1519
+ }
1520
+ if (sChars !== null) sChars.push(cc);
1450
1521
  }
1451
- const raw = '"' + sChars.join('') + '"';
1452
- const decoded = decodeN3StringEscapes(stripQuotes(raw), start);
1453
- assertValidStringLiteralValue(decoded, start);
1522
+ const rawContent = sChars === null ? sliceChars(contentStart, closed ? i - 1 : i) : sChars.join('');
1523
+ const decoded = sChars === null ? rawContent : decodeN3StringEscapes(rawContent, start);
1524
+ if (sChars !== null || inputMayContainInvalidStringChar) assertValidStringLiteralValue(decoded, start);
1454
1525
  const s = JSON.stringify(decoded); // canonical short quoted form
1455
1526
  tokens.push(new Token('Literal', s, start));
1456
1527
  continue;
@@ -1514,25 +1585,33 @@ function lex(inputText, opts = {}) {
1514
1585
 
1515
1586
  // Short string literal ' ... '
1516
1587
  i++; // consume opening '
1517
- const sChars = [];
1588
+ const contentStart = i;
1589
+ let sChars = null;
1590
+ let closed = false;
1518
1591
  while (i < n) {
1519
1592
  const cc = chars[i];
1520
1593
  i++;
1521
1594
  if (cc === '\\') {
1595
+ if (sChars === null) sChars = [sliceChars(contentStart, i - 1)];
1522
1596
  if (i < n) {
1523
1597
  const esc = chars[i];
1524
1598
  i++;
1525
1599
  sChars.push('\\');
1526
1600
  sChars.push(esc);
1601
+ } else {
1602
+ sChars.push('\\');
1527
1603
  }
1528
1604
  continue;
1529
1605
  }
1530
- if (cc === "'") break;
1531
- sChars.push(cc);
1606
+ if (cc === "'") {
1607
+ closed = true;
1608
+ break;
1609
+ }
1610
+ if (sChars !== null) sChars.push(cc);
1532
1611
  }
1533
- const raw = "'" + sChars.join('') + "'";
1534
- const decoded = decodeN3StringEscapes(stripQuotes(raw), start);
1535
- assertValidStringLiteralValue(decoded, start);
1612
+ const rawContent = sChars === null ? sliceChars(contentStart, closed ? i - 1 : i) : sChars.join('');
1613
+ const decoded = sChars === null ? rawContent : decodeN3StringEscapes(rawContent, start);
1614
+ if (sChars !== null || inputMayContainInvalidStringChar) assertValidStringLiteralValue(decoded, start);
1536
1615
  const s = JSON.stringify(decoded); // canonical short quoted form
1537
1616
  tokens.push(new Token('Literal', s, start));
1538
1617
  continue;
@@ -1656,7 +1735,7 @@ function lex(inputText, opts = {}) {
1656
1735
  }
1657
1736
  if (word === 'true' || word === 'false') {
1658
1737
  tokens.push(new Token('Literal', word, start));
1659
- } else if ([...word].every((ch) => /[0-9.-]/.test(ch))) {
1738
+ } else if (isNumericLikeIdentifier(word)) {
1660
1739
  tokens.push(new Token('Literal', word, start));
1661
1740
  } else {
1662
1741
  tokens.push(new Token('Ident', word, start));
package/lib/parser.js CHANGED
@@ -241,7 +241,7 @@ class Parser {
241
241
  } else if (tok2.typ === 'Ident') {
242
242
  const qn = tok2.value || '';
243
243
  if (!qn.includes(':')) failInvalidKeywordLikeIdent(this.fail.bind(this), tok2, qn);
244
- assertValidQNamePrefix(qn.split(':', 1)[0], this.fail.bind(this), tok2, '@prefix directive IRI');
244
+ assertValidQNamePrefix(qn.slice(0, qn.indexOf(':')), this.fail.bind(this), tok2, '@prefix directive IRI');
245
245
  iri = this.prefixes.expandQName(qn);
246
246
  } else {
247
247
  this.fail(`Expected IRI after @prefix, got ${tok2.toString()}`, tok2);
@@ -258,7 +258,7 @@ class Parser {
258
258
  } else if (tok.typ === 'Ident') {
259
259
  const qn = tok.value || '';
260
260
  if (!qn.includes(':')) failInvalidKeywordLikeIdent(this.fail.bind(this), tok, qn);
261
- assertValidQNamePrefix(qn.split(':', 1)[0], this.fail.bind(this), tok, '@base directive IRI');
261
+ assertValidQNamePrefix(qn.slice(0, qn.indexOf(':')), this.fail.bind(this), tok, '@base directive IRI');
262
262
  iri = this.prefixes.expandQName(qn);
263
263
  } else {
264
264
  this.fail(`Expected IRI after @base, got ${tok.toString()}`, tok);
@@ -287,7 +287,7 @@ class Parser {
287
287
  } else if (tok2.typ === 'Ident') {
288
288
  const qn = tok2.value || '';
289
289
  if (!qn.includes(':')) failInvalidKeywordLikeIdent(this.fail.bind(this), tok2, qn);
290
- assertValidQNamePrefix(qn.split(':', 1)[0], this.fail.bind(this), tok2, '@prefix directive IRI');
290
+ assertValidQNamePrefix(qn.slice(0, qn.indexOf(':')), this.fail.bind(this), tok2, '@prefix directive IRI');
291
291
  iri = this.prefixes.expandQName(qn);
292
292
  } else {
293
293
  this.fail(`Expected IRI after PREFIX, got ${tok2.toString()}`, tok2);
@@ -308,7 +308,7 @@ class Parser {
308
308
  } else if (tok.typ === 'Ident') {
309
309
  const qn = tok.value || '';
310
310
  if (!qn.includes(':')) failInvalidKeywordLikeIdent(this.fail.bind(this), tok, qn);
311
- assertValidQNamePrefix(qn.split(':', 1)[0], this.fail.bind(this), tok, 'BASE directive IRI');
311
+ assertValidQNamePrefix(qn.slice(0, qn.indexOf(':')), this.fail.bind(this), tok, 'BASE directive IRI');
312
312
  iri = this.prefixes.expandQName(qn);
313
313
  } else {
314
314
  this.fail(`Expected IRI after BASE, got ${tok.toString()}`, tok);
@@ -355,14 +355,18 @@ class Parser {
355
355
  const name = val || '';
356
356
  if (name === 'a') {
357
357
  return internIri(RDF_NS + 'type');
358
- } else if (name.startsWith('_:')) {
358
+ }
359
+ const sep = name.indexOf(':');
360
+ if (sep === 1 && name.charCodeAt(0) === 95) {
359
361
  return new Blank(name);
360
- } else if (name.includes(':')) {
361
- assertValidQNamePrefix(name.split(':', 1)[0], this.fail.bind(this), tok);
362
- return internIri(this.prefixes.expandQName(name));
363
- } else {
364
- failInvalidKeywordLikeIdent(this.fail.bind(this), tok, name);
365
362
  }
363
+ if (sep >= 0) {
364
+ const prefixName = name.slice(0, sep);
365
+ assertValidQNamePrefix(prefixName, this.fail.bind(this), tok);
366
+ const base = this.prefixes.map[prefixName] || '';
367
+ return internIri(base ? base + name.slice(sep + 1) : name);
368
+ }
369
+ failInvalidKeywordLikeIdent(this.fail.bind(this), tok, name);
366
370
  }
367
371
 
368
372
  if (typ === 'Literal') {
@@ -393,7 +397,7 @@ class Parser {
393
397
  } else if (dtTok.typ === 'Ident') {
394
398
  const qn = dtTok.value || '';
395
399
  if (!qn.includes(':')) failInvalidKeywordLikeIdent(this.fail.bind(this), dtTok, qn);
396
- assertValidQNamePrefix(qn.split(':', 1)[0], this.fail.bind(this), dtTok, 'datatype prefixed name');
400
+ assertValidQNamePrefix(qn.slice(0, qn.indexOf(':')), this.fail.bind(this), dtTok, 'datatype prefixed name');
397
401
  dtIri = this.prefixes.expandQName(qn);
398
402
  } else {
399
403
  this.fail(`Expected datatype after ^^, got ${dtTok.toString()}`, dtTok);
@@ -638,15 +642,21 @@ class Parser {
638
642
 
639
643
  while (true) {
640
644
  const { verb, invert } = this.parseStatementVerb();
641
- const objects = this.parseObjectList();
642
645
 
643
- // If VERB or OBJECTS contained paths, their helper triples must come
644
- // before the triples that consume the path results (Easter depends on this).
645
- this.flushPendingTriples(out);
646
+ while (true) {
647
+ const o = this.parseTerm();
648
+ const postTriples = this.pendingTriplesAfter;
649
+ this.pendingTriplesAfter = [];
650
+
651
+ // If VERB or OBJECT contained paths, their helper triples must come
652
+ // before the triple that consumes the path result (Easter depends on this).
653
+ this.flushPendingTriples(out);
646
654
 
647
- for (const { term: o, postTriples } of objects) {
648
655
  out.push(new Triple(invert ? o : subject, verb, invert ? subject : o));
649
- if (postTriples && postTriples.length) out.push(...postTriples);
656
+ if (postTriples.length) out.push(...postTriples);
657
+
658
+ if (this.peek().typ !== 'Comma') break;
659
+ this.next();
650
660
  }
651
661
 
652
662
  if (this.peek().typ === 'Semicolon') {
@@ -660,27 +670,6 @@ class Parser {
660
670
  return out;
661
671
  }
662
672
 
663
- parseObjectList() {
664
- // Capture any trailing property-list triples produced while parsing each
665
- // object term so we can emit them *after* the triple that references the
666
- // term. (See pendingTriplesAfter in the constructor.)
667
-
668
- const objs = [];
669
- const readObj = () => {
670
- const o = this.parseTerm();
671
- const post = this.pendingTriplesAfter;
672
- this.pendingTriplesAfter = [];
673
- objs.push({ term: o, postTriples: post });
674
- };
675
-
676
- readObj();
677
- while (this.peek().typ === 'Comma') {
678
- this.next();
679
- readObj();
680
- }
681
- return objs;
682
- }
683
-
684
673
  makeRule(left, right, isForward) {
685
674
  let premiseTerm, conclTerm;
686
675
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.25.1",
3
+ "version": "1.25.3",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [