n3 2.2.7 → 2.2.8

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
@@ -78,9 +78,9 @@ class N3Lexer {
78
78
  this._prefix = /^((?:[A-Za-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:\.?[\-0-9A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)?:(?=[#\s<])/;
79
79
  this._prefixed = /^((?:[A-Za-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:\.?[\-0-9A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)?:((?:(?:[0-:A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff]|%[0-9a-fA-F]{2}|\\[!#-\/;=?\-@_~])(?:(?:[\.\-0-:A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff]|%[0-9a-fA-F]{2}|\\[!#-\/;=?\-@_~])*(?:[\-0-:A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff]|%[0-9a-fA-F]{2}|\\[!#-\/;=?\-@_~]))?)?)(?:[ \t]+|(?=\.?[,;!\^\s#()\[\]\{\}"'<>]))/;
80
80
  this._variable = /^\?(?:(?:[A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:[\-0-:A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)(?=[.,;!\^\s#()\[\]\{\}"'<>])/;
81
- this._blank = /^_:((?:[0-9A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:\.?[\-0-9A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)(?:[ \t]+|(?=\.?[,;:\s#()\[\]\{\}"'<>]))/;
82
- this._number = /^[\-+]?(?:(\d+\.\d*|\.?\d+)[eE][\-+]?|\d*(\.)?)\d+(?=\.?[,;:\s#()\[\]\{\}"'<>])/;
83
- this._boolean = /^(?:true|false)(?=[.,;\s#()\[\]\{\}"'<>])/;
81
+ this._blank = /^_:((?:[0-9A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:\.?[\-0-9A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)(?:[ \t]+|(?=\.?[,;:!\^\s#()\[\]\{\}"'<>]))/;
82
+ this._number = /^[\-+]?(?:(\d+\.\d*|\.?\d+)[eE][\-+]?|\d*(\.)?)\d+(?=\.?[,;:!\^\s#()\[\]\{\}"'<>])/;
83
+ this._boolean = /^(?:true|false)(?=[.,;!\^\s#()\[\]\{\}"'<>])/;
84
84
  this._atKeyword = /^@[a-z]+(?=[\s#<:])/i;
85
85
  this._keyword = /^(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<])/i;
86
86
  this._shortPredicates = /^a(?=[\s#()\[\]\{\}"'<>])/;
package/lib/N3Parser.js CHANGED
@@ -249,8 +249,11 @@ class N3Parser {
249
249
  if (token.prefix.length === 0) {
250
250
  this._literalValue = token.value;
251
251
  return this._completeSubjectLiteral;
252
- } else this._subject = this._factory.literal(token.value, this._factory.namedNode(token.prefix));
253
- break;
252
+ } else {
253
+ this._subject = this._factory.literal(token.value, this._factory.namedNode(token.prefix));
254
+ // This branch is N3-only, so the literal subject might start a path
255
+ return this._getPathReader(this._readPredicateOrNamedGraph);
256
+ }
254
257
  case '<<(':
255
258
  if (!this._n3Mode) return this._error('Disallowed triple term as subject', token);
256
259
  this._saveContext('<<(', this._graph, null, null, null);
@@ -285,8 +288,9 @@ class N3Parser {
285
288
  case ']':
286
289
  case '}':
287
290
  case '|}':
288
- // Expected predicate didn't come, must have been trailing semicolon
289
- if (this._predicate === null) return this._error(`Unexpected ${type}`, token);
291
+ // Expected predicate didn't come, must have been trailing semicolon.
292
+ // In N3 mode, a subject (such as a path) can be a statement by itself.
293
+ if (this._predicate === null && !this._n3Mode) return this._error(`Unexpected ${type}`, token);
290
294
  this._subject = null;
291
295
  return type === ']' ? this._readBlankNodeTail(token) : this._readPunctuation(token);
292
296
  case ';':
@@ -325,7 +329,11 @@ class N3Parser {
325
329
  return this._readDataTypeOrLang;
326
330
  }
327
331
  // Pre-datatyped string literal (prefix stores the datatype)
328
- else this._object = this._factory.literal(token.value, this._factory.namedNode(token.prefix));
332
+ else {
333
+ this._object = this._factory.literal(token.value, this._factory.namedNode(token.prefix));
334
+ // In N3 mode, the literal object might start a path
335
+ if (this._n3Mode) return this._getPathReader(this._getContextEndReader());
336
+ }
329
337
  break;
330
338
  case '[':
331
339
  // Start a new quad with a new blank node as subject
@@ -454,17 +462,31 @@ class N3Parser {
454
462
  this._restoreContext('list', token);
455
463
  // If this list is contained within a parent list, return the membership quad here.
456
464
  // This will be `<parent list element> rdf:first <this list>.`.
457
- if (stack.length !== 0 && stack[stack.length - 1].type === 'list') this._emit(this._subject, this._predicate, this._object, this._graph);
465
+ if (stack.length !== 0 && stack[stack.length - 1].type === 'list') {
466
+ // In N3 mode, this list might be the start of a path
467
+ if (this._n3Mode) {
468
+ // Close this list's tail, as a path would alter the membership quad only
469
+ if (previousList !== null) this._emit(previousList, this.RDF_REST, this.RDF_NIL, this._graph);
470
+ // Create a new context to read the path;
471
+ // _readPath will restore the context and output the membership quad
472
+ this._saveContext('item', this._graph, this._subject, this._predicate, this._object);
473
+ this._subject = this._object, this._predicate = null;
474
+ return this._getPathReader(this._readListItem);
475
+ }
476
+ this._emit(this._subject, this._predicate, this._object, this._graph);
477
+ }
458
478
  // Was this list the parent's subject?
459
479
  if (this._predicate === null) {
460
480
  // The next token is the predicate
461
- next = this._readPredicate;
481
+ next = this._n3Mode ? this._getPathReader(this._readPredicate) : this._readPredicate;
462
482
  // No list tail if this was an empty list
463
483
  if (this._subject === this.RDF_NIL) return next;
464
484
  }
465
485
  // The list was in the parent context's object
466
486
  else {
467
487
  next = this._getContextEndReader();
488
+ // In N3 mode, the list object might be the start of a path
489
+ if (this._n3Mode) next = this._getPathReader(next);
468
490
  // No list tail if this was an empty list
469
491
  if (this._object === this.RDF_NIL) return next;
470
492
  }
@@ -519,7 +541,7 @@ class N3Parser {
519
541
  // If an item was read, add it to the list
520
542
  if (item !== null) {
521
543
  // In N3 mode, the item might be a path
522
- if (this._n3Mode && (token.type === 'IRI' || token.type === 'prefixed')) {
544
+ if (this._n3Mode && (token.type === 'IRI' || token.type === 'prefixed' || token.type === 'var' || token.type === 'blank' || token.type === 'literal')) {
523
545
  // Create a new context to add the item's path
524
546
  this._saveContext('item', this._graph, list, this.RDF_FIRST, item);
525
547
  this._subject = item, this._predicate = null;
@@ -585,7 +607,11 @@ class N3Parser {
585
607
  this._literalLanguage = undefined;
586
608
  token = null;
587
609
  }
588
- if (component === 'subject') return token === null ? this._readPredicateOrNamedGraph : this._readPredicateOrNamedGraph(token);
610
+ if (component === 'subject') {
611
+ // A subject literal implies N3 mode, so the literal might start a path
612
+ const reader = this._getPathEndReader(token, this._readPredicateOrNamedGraph);
613
+ return reader || this._readPredicateOrNamedGraph(token);
614
+ }
589
615
  if (component === 'predicate') return token === null ? this._readObject : this._readObject(token);
590
616
  return this._completeObjectLiteralPost(token, listItem);
591
617
  }
@@ -607,6 +633,12 @@ class N3Parser {
607
633
  // Postpone completion if the literal is only partially completed (such as lang+dir).
608
634
  if (completed.readCb) return completed.readCb.bind(this, false);
609
635
 
636
+ // A subject literal implies N3 mode, so it might start a path.
637
+ if (component === 'subject') {
638
+ const reader = this._getPathEndReader(completed.token, next);
639
+ if (reader) return reader;
640
+ }
641
+
610
642
  // If the token was consumed as a datatype, continue with the next component;
611
643
  // otherwise, consume the token now
612
644
  return completed.token === null ? next : next.call(this, completed.token);
@@ -633,6 +665,17 @@ class N3Parser {
633
665
  return this._completeObjectLiteralPost(completed.token, listItem);
634
666
  }
635
667
  _completeObjectLiteralPost(token, listItem) {
668
+ // In N3 mode, the literal object might start a path
669
+ if (this._n3Mode && (token === null || token.type === '!' || token.type === '^')) {
670
+ // If this literal was part of a list, defer writing the item;
671
+ // _readPath will then restore the context and output it
672
+ if (listItem) {
673
+ this._saveContext('item', this._graph, this._subject, this.RDF_FIRST, this._object);
674
+ this._subject = this._object, this._predicate = null;
675
+ return this._getPathEndReader(token, this._readListItem);
676
+ }
677
+ return this._getPathEndReader(token, this._getContextEndReader());
678
+ }
636
679
  // If this literal was part of a list, write the item
637
680
  // (we could also check the context stack, but passing in a flag is faster)
638
681
  if (listItem) this._emit(this._subject, this.RDF_FIRST, this._object, this._graph);
@@ -887,6 +930,16 @@ class N3Parser {
887
930
  return this._readPath;
888
931
  }
889
932
 
933
+ // ### `_getPathEndReader` continues reading after a term that might start a path,
934
+ // given the pending token that follows the term (or `null` if it was consumed)
935
+ _getPathEndReader(token, afterPath) {
936
+ // Other pending tokens are not handled here
937
+ if (token !== null && token.type !== '!' && token.type !== '^') return null;
938
+ const reader = this._getPathReader(afterPath);
939
+ // If no token is pending, wait for the next one; otherwise, consume it now
940
+ return token === null ? reader : reader.call(this, token);
941
+ }
942
+
890
943
  // ### `_readPath` reads a potential path
891
944
  _readPath(token) {
892
945
  switch (token.type) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n3",
3
- "version": "2.2.7",
3
+ "version": "2.2.8",
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
@@ -48,9 +48,9 @@ export default class N3Lexer {
48
48
  this._prefix = /^((?:[A-Za-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:\.?[\-0-9A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)?:(?=[#\s<])/;
49
49
  this._prefixed = /^((?:[A-Za-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:\.?[\-0-9A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)?:((?:(?:[0-:A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff]|%[0-9a-fA-F]{2}|\\[!#-\/;=?\-@_~])(?:(?:[\.\-0-:A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff]|%[0-9a-fA-F]{2}|\\[!#-\/;=?\-@_~])*(?:[\-0-:A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff]|%[0-9a-fA-F]{2}|\\[!#-\/;=?\-@_~]))?)?)(?:[ \t]+|(?=\.?[,;!\^\s#()\[\]\{\}"'<>]))/;
50
50
  this._variable = /^\?(?:(?:[A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:[\-0-:A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)(?=[.,;!\^\s#()\[\]\{\}"'<>])/;
51
- this._blank = /^_:((?:[0-9A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:\.?[\-0-9A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)(?:[ \t]+|(?=\.?[,;:\s#()\[\]\{\}"'<>]))/;
52
- this._number = /^[\-+]?(?:(\d+\.\d*|\.?\d+)[eE][\-+]?|\d*(\.)?)\d+(?=\.?[,;:\s#()\[\]\{\}"'<>])/;
53
- this._boolean = /^(?:true|false)(?=[.,;\s#()\[\]\{\}"'<>])/;
51
+ this._blank = /^_:((?:[0-9A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:\.?[\-0-9A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)(?:[ \t]+|(?=\.?[,;:!\^\s#()\[\]\{\}"'<>]))/;
52
+ this._number = /^[\-+]?(?:(\d+\.\d*|\.?\d+)[eE][\-+]?|\d*(\.)?)\d+(?=\.?[,;:!\^\s#()\[\]\{\}"'<>])/;
53
+ this._boolean = /^(?:true|false)(?=[.,;!\^\s#()\[\]\{\}"'<>])/;
54
54
  this._atKeyword = /^@[a-z]+(?=[\s#<:])/i;
55
55
  this._keyword = /^(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<])/i;
56
56
  this._shortPredicates = /^a(?=[\s#()\[\]\{\}"'<>])/;
package/src/N3Parser.js CHANGED
@@ -252,10 +252,11 @@ export default class N3Parser {
252
252
  this._literalValue = token.value;
253
253
  return this._completeSubjectLiteral;
254
254
  }
255
- else
255
+ else {
256
256
  this._subject = this._factory.literal(token.value, this._factory.namedNode(token.prefix));
257
-
258
- break;
257
+ // This branch is N3-only, so the literal subject might start a path
258
+ return this._getPathReader(this._readPredicateOrNamedGraph);
259
+ }
259
260
  case '<<(':
260
261
  if (!this._n3Mode)
261
262
  return this._error('Disallowed triple term as subject', token);
@@ -293,8 +294,9 @@ export default class N3Parser {
293
294
  case ']':
294
295
  case '}':
295
296
  case '|}':
296
- // Expected predicate didn't come, must have been trailing semicolon
297
- if (this._predicate === null)
297
+ // Expected predicate didn't come, must have been trailing semicolon.
298
+ // In N3 mode, a subject (such as a path) can be a statement by itself.
299
+ if (this._predicate === null && !this._n3Mode)
298
300
  return this._error(`Unexpected ${type}`, token);
299
301
  this._subject = null;
300
302
  return type === ']' ? this._readBlankNodeTail(token) : this._readPunctuation(token);
@@ -343,8 +345,12 @@ export default class N3Parser {
343
345
  return this._readDataTypeOrLang;
344
346
  }
345
347
  // Pre-datatyped string literal (prefix stores the datatype)
346
- else
348
+ else {
347
349
  this._object = this._factory.literal(token.value, this._factory.namedNode(token.prefix));
350
+ // In N3 mode, the literal object might start a path
351
+ if (this._n3Mode)
352
+ return this._getPathReader(this._getContextEndReader());
353
+ }
348
354
  break;
349
355
  case '[':
350
356
  // Start a new quad with a new blank node as subject
@@ -481,12 +487,24 @@ export default class N3Parser {
481
487
  this._restoreContext('list', token);
482
488
  // If this list is contained within a parent list, return the membership quad here.
483
489
  // This will be `<parent list element> rdf:first <this list>.`.
484
- if (stack.length !== 0 && stack[stack.length - 1].type === 'list')
490
+ if (stack.length !== 0 && stack[stack.length - 1].type === 'list') {
491
+ // In N3 mode, this list might be the start of a path
492
+ if (this._n3Mode) {
493
+ // Close this list's tail, as a path would alter the membership quad only
494
+ if (previousList !== null)
495
+ this._emit(previousList, this.RDF_REST, this.RDF_NIL, this._graph);
496
+ // Create a new context to read the path;
497
+ // _readPath will restore the context and output the membership quad
498
+ this._saveContext('item', this._graph, this._subject, this._predicate, this._object);
499
+ this._subject = this._object, this._predicate = null;
500
+ return this._getPathReader(this._readListItem);
501
+ }
485
502
  this._emit(this._subject, this._predicate, this._object, this._graph);
503
+ }
486
504
  // Was this list the parent's subject?
487
505
  if (this._predicate === null) {
488
506
  // The next token is the predicate
489
- next = this._readPredicate;
507
+ next = this._n3Mode ? this._getPathReader(this._readPredicate) : this._readPredicate;
490
508
  // No list tail if this was an empty list
491
509
  if (this._subject === this.RDF_NIL)
492
510
  return next;
@@ -494,6 +512,9 @@ export default class N3Parser {
494
512
  // The list was in the parent context's object
495
513
  else {
496
514
  next = this._getContextEndReader();
515
+ // In N3 mode, the list object might be the start of a path
516
+ if (this._n3Mode)
517
+ next = this._getPathReader(next);
497
518
  // No list tail if this was an empty list
498
519
  if (this._object === this.RDF_NIL)
499
520
  return next;
@@ -558,7 +579,9 @@ export default class N3Parser {
558
579
  // If an item was read, add it to the list
559
580
  if (item !== null) {
560
581
  // In N3 mode, the item might be a path
561
- if (this._n3Mode && (token.type === 'IRI' || token.type === 'prefixed')) {
582
+ if (this._n3Mode && (token.type === 'IRI' || token.type === 'prefixed' ||
583
+ token.type === 'var' || token.type === 'blank' ||
584
+ token.type === 'literal')) {
562
585
  // Create a new context to add the item's path
563
586
  this._saveContext('item', this._graph, list, this.RDF_FIRST, item);
564
587
  this._subject = item, this._predicate = null;
@@ -628,8 +651,11 @@ export default class N3Parser {
628
651
  token = null;
629
652
  }
630
653
 
631
- if (component === 'subject')
632
- return token === null ? this._readPredicateOrNamedGraph : this._readPredicateOrNamedGraph(token);
654
+ if (component === 'subject') {
655
+ // A subject literal implies N3 mode, so the literal might start a path
656
+ const reader = this._getPathEndReader(token, this._readPredicateOrNamedGraph);
657
+ return reader || this._readPredicateOrNamedGraph(token);
658
+ }
633
659
  if (component === 'predicate')
634
660
  return token === null ? this._readObject : this._readObject(token);
635
661
  return this._completeObjectLiteralPost(token, listItem);
@@ -656,6 +682,13 @@ export default class N3Parser {
656
682
  if (completed.readCb)
657
683
  return completed.readCb.bind(this, false);
658
684
 
685
+ // A subject literal implies N3 mode, so it might start a path.
686
+ if (component === 'subject') {
687
+ const reader = this._getPathEndReader(completed.token, next);
688
+ if (reader)
689
+ return reader;
690
+ }
691
+
659
692
  // If the token was consumed as a datatype, continue with the next component;
660
693
  // otherwise, consume the token now
661
694
  return completed.token === null ? next : next.call(this, completed.token);
@@ -687,6 +720,17 @@ export default class N3Parser {
687
720
  }
688
721
 
689
722
  _completeObjectLiteralPost(token, listItem) {
723
+ // In N3 mode, the literal object might start a path
724
+ if (this._n3Mode && (token === null || token.type === '!' || token.type === '^')) {
725
+ // If this literal was part of a list, defer writing the item;
726
+ // _readPath will then restore the context and output it
727
+ if (listItem) {
728
+ this._saveContext('item', this._graph, this._subject, this.RDF_FIRST, this._object);
729
+ this._subject = this._object, this._predicate = null;
730
+ return this._getPathEndReader(token, this._readListItem);
731
+ }
732
+ return this._getPathEndReader(token, this._getContextEndReader());
733
+ }
690
734
  // If this literal was part of a list, write the item
691
735
  // (we could also check the context stack, but passing in a flag is faster)
692
736
  if (listItem)
@@ -968,6 +1012,17 @@ export default class N3Parser {
968
1012
  return this._readPath;
969
1013
  }
970
1014
 
1015
+ // ### `_getPathEndReader` continues reading after a term that might start a path,
1016
+ // given the pending token that follows the term (or `null` if it was consumed)
1017
+ _getPathEndReader(token, afterPath) {
1018
+ // Other pending tokens are not handled here
1019
+ if (token !== null && token.type !== '!' && token.type !== '^')
1020
+ return null;
1021
+ const reader = this._getPathReader(afterPath);
1022
+ // If no token is pending, wait for the next one; otherwise, consume it now
1023
+ return token === null ? reader : reader.call(this, token);
1024
+ }
1025
+
971
1026
  // ### `_readPath` reads a potential path
972
1027
  _readPath(token) {
973
1028
  switch (token.type) {