n3 2.7.1 → 2.7.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/N3Lexer.js CHANGED
@@ -322,11 +322,11 @@ class N3Lexer {
322
322
  case 'h':
323
323
  case 'o':
324
324
  // Try to find an N3 verb keyword
325
- if (this._n3Mode && (match = this._n3Verb.exec(input))) type = match[0];else inconclusive = true;
325
+ if (this._n3Mode && (match = this._matchN3Verb(input, inputFinished))) type = match[0];else inconclusive = true;
326
326
  break;
327
327
  case 'i':
328
328
  // Try to find an IRI property list identifier or N3 verb keyword
329
- if (this._n3Mode && (match = this._n3Id.exec(input))) type = 'id';else if (this._n3Mode && (match = this._n3Verb.exec(input))) type = match[0];else inconclusive = true;
329
+ if (this._n3Mode && (match = this._n3Id.exec(input))) type = 'id';else if (this._n3Mode && (match = this._matchN3Verb(input, inputFinished))) type = match[0];else inconclusive = true;
330
330
  break;
331
331
  case '=':
332
332
  // Try to find an implication arrow or equals sign
@@ -438,6 +438,29 @@ class N3Lexer {
438
438
  }
439
439
  }
440
440
 
441
+ // ### `_matchN3Verb` matches an N3 verb unless the input is a longer prefixed name
442
+ _matchN3Verb(input, inputFinished) {
443
+ const verb = this._n3Verb.exec(input);
444
+ if (!verb) return null;
445
+
446
+ // Most verb boundaries cannot be part of a prefix, so keep the common path fast.
447
+ const next = input[verb[0].length];
448
+ if (next !== '-' && next !== '_' && (next < '0' || next > '9')) return verb;
449
+
450
+ // A prefix can start with a verb and continue with characters that are also
451
+ // valid verb boundaries. Prefer the longer prefixed name when it is complete.
452
+ if (this._prefixed.exec(input) || this._prefixed.exec(`${input} `)) return null;
453
+
454
+ // If a stream chunk ends partway through such a prefix, wait for the colon
455
+ // instead of prematurely emitting the verb. Appending ": " lets the prefix
456
+ // grammar determine whether all input seen so far can be a complete prefix.
457
+ if (!inputFinished) {
458
+ const prefix = this._prefix.exec(`${input}: `);
459
+ if (prefix) return null;
460
+ }
461
+ return verb;
462
+ }
463
+
441
464
  // ### `_unescape` replaces N3 escape codes by their corresponding characters,
442
465
  // allowing only the fixed escape sequences from the given replacement table
443
466
  _unescape(item, replacements) {
package/lib/N3Parser.js CHANGED
@@ -31,6 +31,13 @@ class N3Parser {
31
31
  isNQuads = /quad/.test(format),
32
32
  isN3 = this._n3Mode = /n3/.test(format),
33
33
  isLineMode = isNTriples || isNQuads;
34
+ // Keep inverse handling off the non-N3 emission path
35
+ this._emitCurrent = this._emit;
36
+ if (isN3) {
37
+ this._createQuad = this._createQuadInDirection;
38
+ this._emit = this._emitInDirection;
39
+ this._emitCurrent = this._emitCurrentInDirection;
40
+ }
34
41
  if (!(this._supportsNamedGraphs = !(isTurtle || isN3))) this._readPredicateOrNamedGraph = this._readPredicate;
35
42
  // Support triples in other graphs
36
43
  this._supportsQuads = !(isTurtle || isTriG || isNTriples || isN3);
@@ -514,7 +521,7 @@ class N3Parser {
514
521
  if (token.type !== ']') return this._readBlankNodePunctuation(token);
515
522
 
516
523
  // Store blank node quad
517
- if (this._subject !== null) this._emit(this._subject, this._predicate, this._object, this._graph);
524
+ if (this._subject !== null) this._emitCurrent(this._subject, this._predicate, this._object, this._graph);
518
525
 
519
526
  // Restore the parent context containing this blank node
520
527
  const empty = this._predicate === null;
@@ -841,7 +848,7 @@ class N3Parser {
841
848
  if (token.type !== '}') return this._readPunctuation(token);
842
849
 
843
850
  // Store the last quad of the formula
844
- if (this._subject !== null) this._emit(this._subject, this._predicate, this._object, this._graph);
851
+ if (this._subject !== null) this._emitCurrent(this._subject, this._predicate, this._object, this._graph);
845
852
  const formula = this._graph,
846
853
  empty = this._emptyFormula;
847
854
  // Restore the parent context containing this formula
@@ -882,6 +889,7 @@ class N3Parser {
882
889
  break;
883
890
  // Semicolon means the subject is shared; predicate and object are different
884
891
  case ';':
892
+ if (inversePredicate) this._inversePredicate = false;
885
893
  next = this._readPredicate;
886
894
  break;
887
895
  // Comma means both the subject and predicate are shared; the object is different
@@ -902,6 +910,7 @@ class N3Parser {
902
910
  // Same staleness rule as ~ above.
903
911
  if (subject !== null) this._tripleTerm = null;
904
912
  this._subject = this._readTripleTerm();
913
+ this._inversePredicate = false;
905
914
  this._validAnnotation = false;
906
915
  startingAnnotation = true;
907
916
  next = this._readPredicate;
@@ -912,6 +921,7 @@ class N3Parser {
912
921
  if (!this._validAnnotation) return this._error('Annotation block can not be empty', token);
913
922
  this._subject = null;
914
923
  this._annotation = false;
924
+ this._inversePredicate = false;
915
925
  next = this._getContextEndReader();
916
926
  break;
917
927
  default:
@@ -926,7 +936,7 @@ class N3Parser {
926
936
  if (subject !== null && (!startingAnnotation || startingAnnotation && !this._annotation)) {
927
937
  const predicate = this._predicate,
928
938
  object = this._object;
929
- if (!inversePredicate) this._emit(subject, predicate, object, graph);else this._emit(object, predicate, subject, graph);
939
+ this._emit(subject, predicate, object, graph, inversePredicate);
930
940
  }
931
941
  if (startingAnnotation) {
932
942
  this._annotation = true;
@@ -936,10 +946,12 @@ class N3Parser {
936
946
 
937
947
  // ### `_readBlankNodePunctuation` reads punctuation in a blank node
938
948
  _readBlankNodePunctuation(token) {
939
- let next;
949
+ let next,
950
+ resetInversePredicate = false;
940
951
  switch (token.type) {
941
952
  // Semicolon means the subject is shared; predicate and object are different
942
953
  case ';':
954
+ resetInversePredicate = this._inversePredicate;
943
955
  next = this._readPredicate;
944
956
  break;
945
957
  // Comma means both the subject and predicate are shared; the object is different
@@ -961,7 +973,8 @@ class N3Parser {
961
973
  // nothing left to share with a following predicate-object pair
962
974
  if (this._subject === null) return this._error('Expected ] to follow annotation', token);
963
975
  // A quad has been completed now, so return it
964
- this._emit(this._subject, this._predicate, this._object, this._graph);
976
+ this._emitCurrent(this._subject, this._predicate, this._object, this._graph);
977
+ if (resetInversePredicate) this._inversePredicate = false;
965
978
  return next;
966
979
  }
967
980
 
@@ -1158,7 +1171,7 @@ class N3Parser {
1158
1171
  _readTripleTermTail(token) {
1159
1172
  if (token.type !== ')>>') return this._error(`Expected )>> but got ${token.type}`, token);
1160
1173
  // Read the quad and restore the previous context
1161
- const quad = this._factory.quad(this._subject, this._predicate, this._object, this._graph || this.DEFAULTGRAPH);
1174
+ const quad = this._createQuad(this._subject, this._predicate, this._object, this._graph, this._inversePredicate);
1162
1175
  this._restoreContext('<<(', token);
1163
1176
 
1164
1177
  // If we're in a list, continue processing that list
@@ -1257,6 +1270,7 @@ class N3Parser {
1257
1270
  switch (token.type) {
1258
1271
  // The subject stays shared with the next predicate-object pair
1259
1272
  case ';':
1273
+ this._inversePredicate = false;
1260
1274
  return this._readPredicate;
1261
1275
  // The subject and predicate stay shared with the next object
1262
1276
  case ',':
@@ -1274,7 +1288,7 @@ class N3Parser {
1274
1288
  const parentGraph = parent ? parent.graph : undefined;
1275
1289
  const reifier = this._reifier || this._factory.blankNode();
1276
1290
  this._reifier = null;
1277
- this._tripleTerm = this._tripleTerm || this._factory.quad(this._subject, this._predicate, this._object);
1291
+ this._tripleTerm = this._tripleTerm || this._createQuad(this._subject, this._predicate, this._object, null, this._inversePredicate);
1278
1292
  this._emit(reifier, this.RDF_REIFIES, this._tripleTerm, parentGraph || this._graph || this.DEFAULTGRAPH);
1279
1293
  return reifier;
1280
1294
  }
@@ -1297,6 +1311,26 @@ class N3Parser {
1297
1311
  }
1298
1312
  }
1299
1313
 
1314
+ // ### `_createQuad` creates a quad
1315
+ _createQuad(subject, predicate, object, graph) {
1316
+ return this._factory.quad(subject, predicate, object, graph || this.DEFAULTGRAPH);
1317
+ }
1318
+
1319
+ // ### `_createQuadInDirection` creates a quad in the active predicate direction
1320
+ _createQuadInDirection(subject, predicate, object, graph, inversePredicate) {
1321
+ return inversePredicate ? this._factory.quad(object, predicate, subject, graph || this.DEFAULTGRAPH) : this._factory.quad(subject, predicate, object, graph || this.DEFAULTGRAPH);
1322
+ }
1323
+
1324
+ // ### `_emitInDirection` sends a quad in the active predicate direction
1325
+ _emitInDirection(subject, predicate, object, graph, inversePredicate) {
1326
+ this._callback(null, this._createQuad(subject, predicate, object, graph, inversePredicate));
1327
+ }
1328
+
1329
+ // ### `_emitCurrentInDirection` sends a quad in the current predicate direction
1330
+ _emitCurrentInDirection(subject, predicate, object, graph) {
1331
+ this._callback(null, this._createQuad(subject, predicate, object, graph, this._inversePredicate));
1332
+ }
1333
+
1300
1334
  // ### `_emit` sends a quad through the callback
1301
1335
  _emit(subject, predicate, object, graph) {
1302
1336
  this._callback(null, this._factory.quad(subject, predicate, object, graph || this.DEFAULTGRAPH));
package/lib/N3Store.js CHANGED
@@ -249,13 +249,12 @@ class N3Store {
249
249
 
250
250
  // ### `_findInIndex` finds a set of quads in a three-layered index.
251
251
  // The index base is `index0` and the keys at each level are `key0`, `key1`, and `key2`.
252
- // Any of these keys can be undefined, which is interpreted as a wildcard.
252
+ // A key and any keys after it can be null or undefined, which is interpreted as a wildcard.
253
253
  // `name0`, `name1`, and `name2` are the names of the keys at each level,
254
254
  // used when reconstructing the resulting quad
255
255
  // (for instance: _subject_, _predicate_, and _object_).
256
256
  // Finally, `graphId` will be the graph of the created quads.
257
257
  *_findInIndex(index0, key0, key1, key2, name0, name1, name2, graphId) {
258
- let tmp, index1, index2;
259
258
  const entityKeys = this._entities;
260
259
  const graph = this._termFromId(entityKeys[graphId]);
261
260
  const parts = {
@@ -264,24 +263,32 @@ class N3Store {
264
263
  object: null
265
264
  };
266
265
 
267
- // If a key is specified, use only that part of index 0.
268
- if (key0) (tmp = index0, index0 = {})[key0] = tmp[key0];
269
- for (const value0 in index0) {
270
- if (index1 = index0[value0]) {
271
- parts[name0] = this._termFromId(entityKeys[value0]);
272
- // If a key is specified, use only that part of index 1.
273
- if (key1) (tmp = index1, index1 = {})[key1] = tmp[key1];
274
- for (const value1 in index1) {
275
- if (index2 = index1[value1]) {
276
- parts[name1] = this._termFromId(entityKeys[value1]);
277
- // If a key is specified, use only that part of index 2, if it exists.
278
- const values = key2 ? key2 in index2 ? [key2] : [] : Object.keys(index2);
279
- // Create quads for all items found in index 2.
280
- for (let l = 0; l < values.length; l++) {
281
- parts[name2] = this._termFromId(entityKeys[values[l]]);
282
- yield this._factory.quad(parts.subject, parts.predicate, parts.object, graph);
283
- }
284
- }
266
+ // Exact matches avoid allocating key arrays or entering generic loops.
267
+ if (key2) {
268
+ const index1 = index0[key0];
269
+ const index2 = index1 && index1[key1];
270
+ if (!index2 || !(key2 in index2)) return;
271
+ parts[name0] = this._termFromId(entityKeys[key0]);
272
+ parts[name1] = this._termFromId(entityKeys[key1]);
273
+ parts[name2] = this._termFromId(entityKeys[key2]);
274
+ yield this._factory.quad(parts.subject, parts.predicate, parts.object, graph);
275
+ return;
276
+ }
277
+ if (key0 && !(key0 in index0)) return;
278
+ // A null key list stops after visiting a bound key, avoiding a one-item array.
279
+ const keys0 = key0 ? null : Object.keys(index0);
280
+ for (let i0 = 0, value0 = key0 || keys0[0]; value0; value0 = keys0 && keys0[++i0]) {
281
+ const index1 = index0[value0];
282
+ parts[name0] = this._termFromId(entityKeys[value0]);
283
+ if (key1 && !(key1 in index1)) return;
284
+ const keys1 = key1 ? null : Object.keys(index1);
285
+ for (let i1 = 0, value1 = key1 || keys1[0]; value1; value1 = keys1 && keys1[++i1]) {
286
+ const index2 = index1[value1];
287
+ parts[name1] = this._termFromId(entityKeys[value1]);
288
+ const values = Object.keys(index2);
289
+ for (let l = 0; l < values.length; l++) {
290
+ parts[name2] = this._termFromId(entityKeys[values[l]]);
291
+ yield this._factory.quad(parts.subject, parts.predicate, parts.object, graph);
285
292
  }
286
293
  }
287
294
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n3",
3
- "version": "2.7.1",
3
+ "version": "2.7.3",
4
4
  "description": "Lightning fast, asynchronous, streaming Turtle / N3 / RDF library.",
5
5
  "author": "Ruben Verborgh <ruben.verborgh@gmail.com>",
6
6
  "keywords": [
package/src/N3Lexer.js CHANGED
@@ -326,7 +326,7 @@ export default class N3Lexer {
326
326
  case 'h':
327
327
  case 'o':
328
328
  // Try to find an N3 verb keyword
329
- if (this._n3Mode && (match = this._n3Verb.exec(input)))
329
+ if (this._n3Mode && (match = this._matchN3Verb(input, inputFinished)))
330
330
  type = match[0];
331
331
  else
332
332
  inconclusive = true;
@@ -336,7 +336,7 @@ export default class N3Lexer {
336
336
  // Try to find an IRI property list identifier or N3 verb keyword
337
337
  if (this._n3Mode && (match = this._n3Id.exec(input)))
338
338
  type = 'id';
339
- else if (this._n3Mode && (match = this._n3Verb.exec(input)))
339
+ else if (this._n3Mode && (match = this._matchN3Verb(input, inputFinished)))
340
340
  type = match[0];
341
341
  else
342
342
  inconclusive = true;
@@ -455,6 +455,33 @@ export default class N3Lexer {
455
455
  function reportSyntaxError(self) { callback(self._syntaxError(/^\S*/.exec(input)[0])); }
456
456
  }
457
457
 
458
+ // ### `_matchN3Verb` matches an N3 verb unless the input is a longer prefixed name
459
+ _matchN3Verb(input, inputFinished) {
460
+ const verb = this._n3Verb.exec(input);
461
+ if (!verb)
462
+ return null;
463
+
464
+ // Most verb boundaries cannot be part of a prefix, so keep the common path fast.
465
+ const next = input[verb[0].length];
466
+ if (next !== '-' && next !== '_' && (next < '0' || next > '9'))
467
+ return verb;
468
+
469
+ // A prefix can start with a verb and continue with characters that are also
470
+ // valid verb boundaries. Prefer the longer prefixed name when it is complete.
471
+ if (this._prefixed.exec(input) || this._prefixed.exec(`${input} `))
472
+ return null;
473
+
474
+ // If a stream chunk ends partway through such a prefix, wait for the colon
475
+ // instead of prematurely emitting the verb. Appending ": " lets the prefix
476
+ // grammar determine whether all input seen so far can be a complete prefix.
477
+ if (!inputFinished) {
478
+ const prefix = this._prefix.exec(`${input}: `);
479
+ if (prefix)
480
+ return null;
481
+ }
482
+ return verb;
483
+ }
484
+
458
485
  // ### `_unescape` replaces N3 escape codes by their corresponding characters,
459
486
  // allowing only the fixed escape sequences from the given replacement table
460
487
  _unescape(item, replacements) {
package/src/N3Parser.js CHANGED
@@ -23,6 +23,13 @@ export default class N3Parser {
23
23
  isNTriples = /triple/.test(format), isNQuads = /quad/.test(format),
24
24
  isN3 = this._n3Mode = /n3/.test(format),
25
25
  isLineMode = isNTriples || isNQuads;
26
+ // Keep inverse handling off the non-N3 emission path
27
+ this._emitCurrent = this._emit;
28
+ if (isN3) {
29
+ this._createQuad = this._createQuadInDirection;
30
+ this._emit = this._emitInDirection;
31
+ this._emitCurrent = this._emitCurrentInDirection;
32
+ }
26
33
  if (!(this._supportsNamedGraphs = !(isTurtle || isN3)))
27
34
  this._readPredicateOrNamedGraph = this._readPredicate;
28
35
  // Support triples in other graphs
@@ -550,7 +557,7 @@ export default class N3Parser {
550
557
 
551
558
  // Store blank node quad
552
559
  if (this._subject !== null)
553
- this._emit(this._subject, this._predicate, this._object, this._graph);
560
+ this._emitCurrent(this._subject, this._predicate, this._object, this._graph);
554
561
 
555
562
  // Restore the parent context containing this blank node
556
563
  const empty = this._predicate === null;
@@ -911,7 +918,7 @@ export default class N3Parser {
911
918
 
912
919
  // Store the last quad of the formula
913
920
  if (this._subject !== null)
914
- this._emit(this._subject, this._predicate, this._object, this._graph);
921
+ this._emitCurrent(this._subject, this._predicate, this._object, this._graph);
915
922
 
916
923
  const formula = this._graph, empty = this._emptyFormula;
917
924
  // Restore the parent context containing this formula
@@ -958,6 +965,7 @@ export default class N3Parser {
958
965
  break;
959
966
  // Semicolon means the subject is shared; predicate and object are different
960
967
  case ';':
968
+ if (inversePredicate) this._inversePredicate = false;
961
969
  next = this._readPredicate;
962
970
  break;
963
971
  // Comma means both the subject and predicate are shared; the object is different
@@ -980,6 +988,7 @@ export default class N3Parser {
980
988
  if (subject !== null)
981
989
  this._tripleTerm = null;
982
990
  this._subject = this._readTripleTerm();
991
+ this._inversePredicate = false;
983
992
  this._validAnnotation = false;
984
993
  startingAnnotation = true;
985
994
  next = this._readPredicate;
@@ -992,6 +1001,7 @@ export default class N3Parser {
992
1001
  return this._error('Annotation block can not be empty', token);
993
1002
  this._subject = null;
994
1003
  this._annotation = false;
1004
+ this._inversePredicate = false;
995
1005
  next = this._getContextEndReader();
996
1006
  break;
997
1007
  default:
@@ -1005,10 +1015,7 @@ export default class N3Parser {
1005
1015
  // A quad has been completed now, so return it
1006
1016
  if (subject !== null && (!startingAnnotation || (startingAnnotation && !this._annotation))) {
1007
1017
  const predicate = this._predicate, object = this._object;
1008
- if (!inversePredicate)
1009
- this._emit(subject, predicate, object, graph);
1010
- else
1011
- this._emit(object, predicate, subject, graph);
1018
+ this._emit(subject, predicate, object, graph, inversePredicate);
1012
1019
  }
1013
1020
  if (startingAnnotation) {
1014
1021
  this._annotation = true;
@@ -1018,10 +1025,11 @@ export default class N3Parser {
1018
1025
 
1019
1026
  // ### `_readBlankNodePunctuation` reads punctuation in a blank node
1020
1027
  _readBlankNodePunctuation(token) {
1021
- let next;
1028
+ let next, resetInversePredicate = false;
1022
1029
  switch (token.type) {
1023
1030
  // Semicolon means the subject is shared; predicate and object are different
1024
1031
  case ';':
1032
+ resetInversePredicate = this._inversePredicate;
1025
1033
  next = this._readPredicate;
1026
1034
  break;
1027
1035
  // Comma means both the subject and predicate are shared; the object is different
@@ -1044,7 +1052,9 @@ export default class N3Parser {
1044
1052
  if (this._subject === null)
1045
1053
  return this._error('Expected ] to follow annotation', token);
1046
1054
  // A quad has been completed now, so return it
1047
- this._emit(this._subject, this._predicate, this._object, this._graph);
1055
+ this._emitCurrent(this._subject, this._predicate, this._object, this._graph);
1056
+ if (resetInversePredicate)
1057
+ this._inversePredicate = false;
1048
1058
  return next;
1049
1059
  }
1050
1060
 
@@ -1269,8 +1279,8 @@ export default class N3Parser {
1269
1279
  if (token.type !== ')>>')
1270
1280
  return this._error(`Expected )>> but got ${token.type}`, token);
1271
1281
  // Read the quad and restore the previous context
1272
- const quad = this._factory.quad(this._subject, this._predicate, this._object,
1273
- this._graph || this.DEFAULTGRAPH);
1282
+ const quad = this._createQuad(this._subject, this._predicate, this._object,
1283
+ this._graph, this._inversePredicate);
1274
1284
  this._restoreContext('<<(', token);
1275
1285
 
1276
1286
  // If we're in a list, continue processing that list
@@ -1371,6 +1381,7 @@ export default class N3Parser {
1371
1381
  switch (token.type) {
1372
1382
  // The subject stays shared with the next predicate-object pair
1373
1383
  case ';':
1384
+ this._inversePredicate = false;
1374
1385
  return this._readPredicate;
1375
1386
  // The subject and predicate stay shared with the next object
1376
1387
  case ',':
@@ -1388,7 +1399,9 @@ export default class N3Parser {
1388
1399
  const parentGraph = parent ? parent.graph : undefined;
1389
1400
  const reifier = this._reifier || this._factory.blankNode();
1390
1401
  this._reifier = null;
1391
- this._tripleTerm = this._tripleTerm || this._factory.quad(this._subject, this._predicate, this._object);
1402
+ this._tripleTerm = this._tripleTerm || this._createQuad(
1403
+ this._subject, this._predicate, this._object, null, this._inversePredicate,
1404
+ );
1392
1405
  this._emit(reifier, this.RDF_REIFIES, this._tripleTerm, parentGraph || this._graph || this.DEFAULTGRAPH);
1393
1406
  return reifier;
1394
1407
  }
@@ -1413,6 +1426,28 @@ export default class N3Parser {
1413
1426
  }
1414
1427
  }
1415
1428
 
1429
+ // ### `_createQuad` creates a quad
1430
+ _createQuad(subject, predicate, object, graph) {
1431
+ return this._factory.quad(subject, predicate, object, graph || this.DEFAULTGRAPH);
1432
+ }
1433
+
1434
+ // ### `_createQuadInDirection` creates a quad in the active predicate direction
1435
+ _createQuadInDirection(subject, predicate, object, graph, inversePredicate) {
1436
+ return inversePredicate ?
1437
+ this._factory.quad(object, predicate, subject, graph || this.DEFAULTGRAPH) :
1438
+ this._factory.quad(subject, predicate, object, graph || this.DEFAULTGRAPH);
1439
+ }
1440
+
1441
+ // ### `_emitInDirection` sends a quad in the active predicate direction
1442
+ _emitInDirection(subject, predicate, object, graph, inversePredicate) {
1443
+ this._callback(null, this._createQuad(subject, predicate, object, graph, inversePredicate));
1444
+ }
1445
+
1446
+ // ### `_emitCurrentInDirection` sends a quad in the current predicate direction
1447
+ _emitCurrentInDirection(subject, predicate, object, graph) {
1448
+ this._callback(null, this._createQuad(subject, predicate, object, graph, this._inversePredicate));
1449
+ }
1450
+
1416
1451
  // ### `_emit` sends a quad through the callback
1417
1452
  _emit(subject, predicate, object, graph) {
1418
1453
  this._callback(null, this._factory.quad(subject, predicate, object, graph || this.DEFAULTGRAPH));
package/src/N3Store.js CHANGED
@@ -268,35 +268,49 @@ export default class N3Store {
268
268
 
269
269
  // ### `_findInIndex` finds a set of quads in a three-layered index.
270
270
  // The index base is `index0` and the keys at each level are `key0`, `key1`, and `key2`.
271
- // Any of these keys can be undefined, which is interpreted as a wildcard.
271
+ // A key and any keys after it can be null or undefined, which is interpreted as a wildcard.
272
272
  // `name0`, `name1`, and `name2` are the names of the keys at each level,
273
273
  // used when reconstructing the resulting quad
274
274
  // (for instance: _subject_, _predicate_, and _object_).
275
275
  // Finally, `graphId` will be the graph of the created quads.
276
276
  *_findInIndex(index0, key0, key1, key2, name0, name1, name2, graphId) {
277
- let tmp, index1, index2;
278
277
  const entityKeys = this._entities;
279
278
  const graph = this._termFromId(entityKeys[graphId]);
280
279
  const parts = { subject: null, predicate: null, object: null };
281
280
 
282
- // If a key is specified, use only that part of index 0.
283
- if (key0) (tmp = index0, index0 = {})[key0] = tmp[key0];
284
- for (const value0 in index0) {
285
- if (index1 = index0[value0]) {
286
- parts[name0] = this._termFromId(entityKeys[value0]);
287
- // If a key is specified, use only that part of index 1.
288
- if (key1) (tmp = index1, index1 = {})[key1] = tmp[key1];
289
- for (const value1 in index1) {
290
- if (index2 = index1[value1]) {
291
- parts[name1] = this._termFromId(entityKeys[value1]);
292
- // If a key is specified, use only that part of index 2, if it exists.
293
- const values = key2 ? (key2 in index2 ? [key2] : []) : Object.keys(index2);
294
- // Create quads for all items found in index 2.
295
- for (let l = 0; l < values.length; l++) {
296
- parts[name2] = this._termFromId(entityKeys[values[l]]);
297
- yield this._factory.quad(parts.subject, parts.predicate, parts.object, graph);
298
- }
299
- }
281
+ // Exact matches avoid allocating key arrays or entering generic loops.
282
+ if (key2) {
283
+ const index1 = index0[key0];
284
+ const index2 = index1 && index1[key1];
285
+ if (!index2 || !(key2 in index2))
286
+ return;
287
+ parts[name0] = this._termFromId(entityKeys[key0]);
288
+ parts[name1] = this._termFromId(entityKeys[key1]);
289
+ parts[name2] = this._termFromId(entityKeys[key2]);
290
+ yield this._factory.quad(parts.subject, parts.predicate, parts.object, graph);
291
+ return;
292
+ }
293
+
294
+ if (key0 && !(key0 in index0))
295
+ return;
296
+ // A null key list stops after visiting a bound key, avoiding a one-item array.
297
+ const keys0 = key0 ? null : Object.keys(index0);
298
+ for (let i0 = 0, value0 = key0 || keys0[0]; value0;
299
+ value0 = keys0 && keys0[++i0]) {
300
+ const index1 = index0[value0];
301
+ parts[name0] = this._termFromId(entityKeys[value0]);
302
+
303
+ if (key1 && !(key1 in index1))
304
+ return;
305
+ const keys1 = key1 ? null : Object.keys(index1);
306
+ for (let i1 = 0, value1 = key1 || keys1[0]; value1;
307
+ value1 = keys1 && keys1[++i1]) {
308
+ const index2 = index1[value1];
309
+ parts[name1] = this._termFromId(entityKeys[value1]);
310
+ const values = Object.keys(index2);
311
+ for (let l = 0; l < values.length; l++) {
312
+ parts[name2] = this._termFromId(entityKeys[values[l]]);
313
+ yield this._factory.quad(parts.subject, parts.predicate, parts.object, graph);
300
314
  }
301
315
  }
302
316
  }