n3 2.3.0 → 2.4.0

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/N3Parser.js CHANGED
@@ -97,7 +97,7 @@ class N3Parser {
97
97
  });
98
98
  return;
99
99
  }
100
- this._contextStack.push({
100
+ const context = {
101
101
  type,
102
102
  subject,
103
103
  predicate,
@@ -107,7 +107,14 @@ class N3Parser {
107
107
  blankPrefix: this._prefixes._,
108
108
  quantified: this._quantified,
109
109
  emptyFormula: this._emptyFormula
110
- });
110
+ };
111
+ // Prefix and base declarations are scoped to their formula
112
+ if (type === 'formula') {
113
+ context.prefixes = this._prefixes;
114
+ context.base = [this._base, this._basePath, this._baseRoot, this._baseScheme];
115
+ this._prefixes = Object.create(this._prefixes);
116
+ }
117
+ this._contextStack.push(context);
111
118
  // Every new scope resets the predicate direction
112
119
  this._inversePredicate = false;
113
120
  // In N3, blank nodes are scoped to a formula
@@ -138,7 +145,10 @@ class N3Parser {
138
145
  // Restore N3 context settings
139
146
  if (this._n3Mode) {
140
147
  this._inversePredicate = context.inverse;
141
- this._prefixes._ = context.blankPrefix;
148
+ if (type === 'formula') {
149
+ this._prefixes = context.prefixes;
150
+ [this._base, this._basePath, this._baseRoot, this._baseScheme] = context.base;
151
+ } else this._prefixes._ = context.blankPrefix;
142
152
  this._quantified = context.quantified;
143
153
  this._emptyFormula = context.emptyFormula;
144
154
  }
@@ -188,6 +198,28 @@ class N3Parser {
188
198
  }
189
199
  }
190
200
 
201
+ // ### `_readInFormulaContext` reads a token at the statement level of a formula
202
+ _readInFormulaContext(token) {
203
+ switch (token.type) {
204
+ case 'PREFIX':
205
+ this._sparqlStyle = true;
206
+ case '@prefix':
207
+ return this._readPrefix;
208
+ case 'BASE':
209
+ this._sparqlStyle = true;
210
+ case '@base':
211
+ return this._readBaseIRI;
212
+ default:
213
+ return this._readSubject(token);
214
+ }
215
+ }
216
+
217
+ // ### `_getStatementReader` returns the reader for the current statement scope
218
+ _getStatementReader() {
219
+ const context = this._contextStack[this._contextStack.length - 1];
220
+ return context && context.type === 'formula' ? this._readInFormulaContext : this._readInTopContext;
221
+ }
222
+
191
223
  // ### `_readEntity` reads an IRI, prefixed name, blank node, or variable
192
224
  _readEntity(token, quantifier) {
193
225
  let value;
@@ -252,7 +284,7 @@ class N3Parser {
252
284
  // Start a new formula
253
285
  if (!this._n3Mode) return this._error('Unexpected graph', token);
254
286
  this._saveContext('formula', this._graph, this._graph = this._factory.blankNode(), null, null);
255
- return this._readSubject;
287
+ return this._readInFormulaContext;
256
288
  case '}':
257
289
  // No subject; the graph in which we are reading is closed instead
258
290
  return this._readPunctuation(token);
@@ -302,6 +334,7 @@ class N3Parser {
302
334
  // ### `_readPredicate` reads a quad's predicate
303
335
  _readPredicate(token) {
304
336
  const type = token.type;
337
+ let pathable = false;
305
338
  switch (type) {
306
339
  case 'inverse':
307
340
  this._inversePredicate = true;
@@ -326,6 +359,7 @@ class N3Parser {
326
359
  this._literalValue = token.value;
327
360
  return this._completePredicateLiteral;
328
361
  } else this._predicate = this._factory.literal(token.value, this._factory.namedNode(token.prefix));
362
+ pathable = true;
329
363
  break;
330
364
  case '(':
331
365
  // In N3, a list can be a predicate
@@ -336,14 +370,23 @@ class N3Parser {
336
370
  this._saveContext('blank', this._graph, this._subject, this._subject = this._factory.blankNode(), null);
337
371
  return this._readBlankNodeHead;
338
372
  }
373
+ return this._error('Disallowed blank node as predicate', token);
374
+ case '{':
375
+ // In N3, a formula can be a predicate
376
+ if (this._n3Mode) {
377
+ this._saveContext('formula', this._graph, this._subject, this._graph = this._factory.blankNode(), null);
378
+ return this._readSubject;
379
+ }
380
+ return this._readEntity(token);
339
381
  case 'blank':
340
382
  if (!this._n3Mode) return this._error('Disallowed blank node as predicate', token);
341
383
  default:
342
384
  if ((this._predicate = this._readEntity(token)) === undefined) return;
385
+ pathable = this._n3Mode;
343
386
  }
344
387
  this._validAnnotation = true;
345
388
  // The next token must be an object
346
- return this._readObject;
389
+ return pathable ? this._getPathReader(this._readObject, 'predicate') : this._readObject;
347
390
  }
348
391
 
349
392
  // ### `_readObject` reads a quad's object
@@ -372,7 +415,7 @@ class N3Parser {
372
415
  // Start a new formula
373
416
  if (!this._n3Mode) return this._error('Unexpected graph', token);
374
417
  this._saveContext('formula', this._graph, this._subject, this._predicate, this._graph = this._factory.blankNode());
375
- return this._readSubject;
418
+ return this._readInFormulaContext;
376
419
  case '<<(':
377
420
  this._saveContext('<<(', this._graph, this._subject, this._predicate, null);
378
421
  this._graph = null;
@@ -432,7 +475,7 @@ class N3Parser {
432
475
  // If the blank node was the object, restore previous context and read punctuation
433
476
  if (this._object !== null) return this._getContextEndReader();
434
477
  // If the blank node was the predicate, continue reading the object
435
- else if (this._predicate !== null) return this._readObject;
478
+ else if (this._predicate !== null) return this._getPathReader(this._readObject, 'predicate');
436
479
  // If the blank node was the subject, continue reading the predicate
437
480
  else
438
481
  // If the blank node was empty, it could be a named graph label
@@ -504,7 +547,7 @@ class N3Parser {
504
547
  // Was this list the parent's predicate?
505
548
  else if (this._object === null) {
506
549
  // The next token is the object
507
- next = this._readObject;
550
+ next = this._getPathReader(this._readObject, 'predicate');
508
551
  // No list tail if this was an empty list
509
552
  if (this._predicate === this.RDF_NIL) return next;
510
553
  }
@@ -551,7 +594,7 @@ class N3Parser {
551
594
  // Stack the current list quad and start the formula
552
595
  this._saveContext('formula', this._graph, list, this.RDF_FIRST, this._graph = item);
553
596
  this._subject = null;
554
- return this._readSubject;
597
+ return this._readInFormulaContext;
555
598
  case '<<(':
556
599
  this._saveContext('<<(', this._graph, null, null, null);
557
600
  this._graph = null;
@@ -662,12 +705,12 @@ class N3Parser {
662
705
  this._literalLanguage = undefined;
663
706
  token = null;
664
707
  }
665
- if (component === 'subject') {
666
- // A subject literal implies N3 mode, so the literal might start a path
667
- const reader = this._getPathEndReader(token, this._readPredicateOrNamedGraph);
668
- return reader || this._readPredicateOrNamedGraph(token);
708
+ if (component === 'subject' || component === 'predicate') {
709
+ // A subject or predicate literal implies N3 mode, so it might start a path
710
+ const next = component === 'subject' ? this._readPredicateOrNamedGraph : this._readObject;
711
+ const reader = this._getPathEndReader(token, next, component);
712
+ return reader || next.call(this, token);
669
713
  }
670
- if (component === 'predicate') return token === null ? this._readObject : this._readObject(token);
671
714
  return this._completeObjectLiteralPost(token, listItem);
672
715
  }
673
716
 
@@ -691,15 +734,12 @@ class N3Parser {
691
734
  return this._readDirCode;
692
735
  }
693
736
 
694
- // A subject literal implies N3 mode, so it might start a path.
695
- if (component === 'subject') {
696
- const reader = this._getPathEndReader(completed.token, next);
697
- if (reader) return reader;
698
- }
737
+ // A subject or predicate literal implies N3 mode, so it might start a path.
738
+ const reader = this._getPathEndReader(completed.token, next, component);
739
+ if (reader) return reader;
699
740
 
700
- // If the token was consumed as a datatype, continue with the next component;
701
- // otherwise, consume the token now
702
- return completed.token === null ? next : next.call(this, completed.token);
741
+ // Consume the non-path token now
742
+ return next.call(this, completed.token);
703
743
  }
704
744
 
705
745
  // Completes a literal in subject position
@@ -764,12 +804,13 @@ class N3Parser {
764
804
  // is read as the boolean literal true, following the N3 spec tests
765
805
  // and the direction discussed in https://github.com/w3c-cg/N3/issues/185
766
806
  if (empty && this._emptyFormulaAsTrue) {
767
- if (this._subject === formula) this._subject = this.N3_TRUE;else this._object = this.N3_TRUE;
807
+ if (this._subject === formula) this._subject = this.N3_TRUE;else if (this._predicate === formula) this._predicate = this.N3_TRUE;else this._object = this.N3_TRUE;
768
808
  }
769
809
 
770
- // If the formula was the subject, continue reading the predicate.
771
- // If the formula was the object, read punctuation.
772
- return this._object === null ? this._readPredicate : this._getContextEndReader();
810
+ // Continue according to the formula's position in the enclosing statement
811
+ if (this._object !== null) return this._getPathReader(this._getContextEndReader(), 'object');
812
+ if (this._predicate !== null) return this._getPathReader(this._readObject, 'predicate');
813
+ return this._getPathReader(this._readPredicate, 'subject');
773
814
  }
774
815
 
775
816
  // ### `_readPunctuation` reads punctuation between quads or quad parts
@@ -789,7 +830,7 @@ class N3Parser {
789
830
  case '.':
790
831
  this._subject = null;
791
832
  this._tripleTerm = null;
792
- next = this._contextStack.length ? this._readSubject : this._readInTopContext;
833
+ next = this._getStatementReader();
793
834
  if (inversePredicate) this._inversePredicate = false;
794
835
  break;
795
836
  // Semicolon means the subject is shared; predicate and object are different
@@ -947,10 +988,10 @@ class N3Parser {
947
988
  // SPARQL-style declarations don't have punctuation
948
989
  if (this._sparqlStyle) {
949
990
  this._sparqlStyle = false;
950
- return this._readInTopContext(token);
991
+ return this._getStatementReader().call(this, token);
951
992
  }
952
993
  if (token.type !== '.') return this._error('Expected declaration to end with a dot', token);
953
- return this._readInTopContext;
994
+ return this._getStatementReader();
954
995
  }
955
996
 
956
997
  // Reads a list of quantified symbols from a @forSome or @forAll statement
@@ -995,17 +1036,18 @@ class N3Parser {
995
1036
  }
996
1037
 
997
1038
  // ### `_getPathReader` reads a potential path and then resumes with the given function
998
- _getPathReader(afterPath) {
1039
+ _getPathReader(afterPath, position) {
999
1040
  this._afterPath = afterPath;
1041
+ this._pathPosition = position || (this._predicate === null ? 'subject' : 'object');
1000
1042
  return this._readPath;
1001
1043
  }
1002
1044
 
1003
1045
  // ### `_getPathEndReader` continues reading after a term that might start a path,
1004
1046
  // given the pending token that follows the term (or `null` if it was consumed)
1005
- _getPathEndReader(token, afterPath) {
1047
+ _getPathEndReader(token, afterPath, position) {
1006
1048
  // Other pending tokens are not handled here
1007
1049
  if (token !== null && token.type !== '!' && token.type !== '^') return null;
1008
- const reader = this._getPathReader(afterPath);
1050
+ const reader = this._getPathReader(afterPath, position);
1009
1051
  // If no token is pending, wait for the next one; otherwise, consume it now
1010
1052
  return token === null ? reader : reader.call(this, token);
1011
1053
  }
@@ -1021,6 +1063,7 @@ class N3Parser {
1021
1063
  return this._readBackwardPath;
1022
1064
  // Not a path; resume reading where we left off
1023
1065
  default:
1066
+ const afterPath = this._afterPath;
1024
1067
  const stack = this._contextStack,
1025
1068
  parent = stack.length && stack[stack.length - 1];
1026
1069
  // If we were reading a list item, we still need to output it
@@ -1032,7 +1075,9 @@ class N3Parser {
1032
1075
  // Output the list item
1033
1076
  this._emit(this._subject, this.RDF_FIRST, item, this._graph);
1034
1077
  }
1035
- return this._afterPath(token);
1078
+ this._afterPath = null;
1079
+ this._pathPosition = null;
1080
+ return afterPath.call(this, token);
1036
1081
  }
1037
1082
  }
1038
1083
 
@@ -1042,10 +1087,8 @@ class N3Parser {
1042
1087
  const object = this._factory.blankNode();
1043
1088
  // The next token is the predicate
1044
1089
  if ((predicate = this._readEntity(token)) === undefined) return;
1045
- // If we were reading a subject, replace the subject by the path's object
1046
- if (this._predicate === null) subject = this._subject, this._subject = object;
1047
- // If we were reading an object, replace the subject by the path's object
1048
- else subject = this._object, this._object = object;
1090
+ // Replace the path expression with the generated object in its current position
1091
+ if (this._pathPosition === 'subject') subject = this._subject, this._subject = object;else if (this._pathPosition === 'predicate') subject = this._predicate, this._predicate = object;else subject = this._object, this._object = object;
1049
1092
  // Emit the path's current quad and read its next section
1050
1093
  this._emit(subject, predicate, object, this._graph);
1051
1094
  return this._readPath;
@@ -1057,10 +1100,8 @@ class N3Parser {
1057
1100
  let predicate, object;
1058
1101
  // The next token is the predicate
1059
1102
  if ((predicate = this._readEntity(token)) === undefined) return;
1060
- // If we were reading a subject, replace the subject by the path's subject
1061
- if (this._predicate === null) object = this._subject, this._subject = subject;
1062
- // If we were reading an object, replace the subject by the path's subject
1063
- else object = this._object, this._object = subject;
1103
+ // Replace the path expression with the generated subject in its current position
1104
+ if (this._pathPosition === 'subject') object = this._subject, this._subject = subject;else if (this._pathPosition === 'predicate') object = this._predicate, this._predicate = subject;else object = this._object, this._object = subject;
1064
1105
  // Emit the path's current quad and read its next section
1065
1106
  this._emit(subject, predicate, object, this._graph);
1066
1107
  return this._readPath;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n3",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
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/N3Parser.js CHANGED
@@ -83,14 +83,21 @@ export default class N3Parser {
83
83
  this._contextStack.push({ type, subject, predicate, object, graph });
84
84
  return;
85
85
  }
86
- this._contextStack.push({
86
+ const context = {
87
87
  type,
88
88
  subject, predicate, object, graph,
89
89
  inverse: this._inversePredicate,
90
90
  blankPrefix: this._prefixes._,
91
91
  quantified: this._quantified,
92
92
  emptyFormula: this._emptyFormula,
93
- });
93
+ };
94
+ // Prefix and base declarations are scoped to their formula
95
+ if (type === 'formula') {
96
+ context.prefixes = this._prefixes;
97
+ context.base = [this._base, this._basePath, this._baseRoot, this._baseScheme];
98
+ this._prefixes = Object.create(this._prefixes);
99
+ }
100
+ this._contextStack.push(context);
94
101
  // Every new scope resets the predicate direction
95
102
  this._inversePredicate = false;
96
103
  // In N3, blank nodes are scoped to a formula
@@ -122,7 +129,12 @@ export default class N3Parser {
122
129
  // Restore N3 context settings
123
130
  if (this._n3Mode) {
124
131
  this._inversePredicate = context.inverse;
125
- this._prefixes._ = context.blankPrefix;
132
+ if (type === 'formula') {
133
+ this._prefixes = context.prefixes;
134
+ [this._base, this._basePath, this._baseRoot, this._baseScheme] = context.base;
135
+ }
136
+ else
137
+ this._prefixes._ = context.blankPrefix;
126
138
  this._quantified = context.quantified;
127
139
  this._emptyFormula = context.emptyFormula;
128
140
  }
@@ -175,6 +187,28 @@ export default class N3Parser {
175
187
  }
176
188
  }
177
189
 
190
+ // ### `_readInFormulaContext` reads a token at the statement level of a formula
191
+ _readInFormulaContext(token) {
192
+ switch (token.type) {
193
+ case 'PREFIX':
194
+ this._sparqlStyle = true;
195
+ case '@prefix':
196
+ return this._readPrefix;
197
+ case 'BASE':
198
+ this._sparqlStyle = true;
199
+ case '@base':
200
+ return this._readBaseIRI;
201
+ default:
202
+ return this._readSubject(token);
203
+ }
204
+ }
205
+
206
+ // ### `_getStatementReader` returns the reader for the current statement scope
207
+ _getStatementReader() {
208
+ const context = this._contextStack[this._contextStack.length - 1];
209
+ return context && context.type === 'formula' ? this._readInFormulaContext : this._readInTopContext;
210
+ }
211
+
178
212
  // ### `_readEntity` reads an IRI, prefixed name, blank node, or variable
179
213
  _readEntity(token, quantifier) {
180
214
  let value;
@@ -245,7 +279,7 @@ export default class N3Parser {
245
279
  return this._error('Unexpected graph', token);
246
280
  this._saveContext('formula', this._graph,
247
281
  this._graph = this._factory.blankNode(), null, null);
248
- return this._readSubject;
282
+ return this._readInFormulaContext;
249
283
  case '}':
250
284
  // No subject; the graph in which we are reading is closed instead
251
285
  return this._readPunctuation(token);
@@ -303,6 +337,7 @@ export default class N3Parser {
303
337
  // ### `_readPredicate` reads a quad's predicate
304
338
  _readPredicate(token) {
305
339
  const type = token.type;
340
+ let pathable = false;
306
341
  switch (type) {
307
342
  case 'inverse':
308
343
  this._inversePredicate = true;
@@ -334,6 +369,7 @@ export default class N3Parser {
334
369
  else
335
370
  this._predicate = this._factory.literal(token.value, this._factory.namedNode(token.prefix));
336
371
 
372
+ pathable = true;
337
373
  break;
338
374
  case '(':
339
375
  // In N3, a list can be a predicate
@@ -347,16 +383,26 @@ export default class N3Parser {
347
383
  this._subject = this._factory.blankNode(), null);
348
384
  return this._readBlankNodeHead;
349
385
  }
386
+ return this._error('Disallowed blank node as predicate', token);
387
+ case '{':
388
+ // In N3, a formula can be a predicate
389
+ if (this._n3Mode) {
390
+ this._saveContext('formula', this._graph, this._subject,
391
+ this._graph = this._factory.blankNode(), null);
392
+ return this._readSubject;
393
+ }
394
+ return this._readEntity(token);
350
395
  case 'blank':
351
396
  if (!this._n3Mode)
352
397
  return this._error('Disallowed blank node as predicate', token);
353
398
  default:
354
399
  if ((this._predicate = this._readEntity(token)) === undefined)
355
400
  return;
401
+ pathable = this._n3Mode;
356
402
  }
357
403
  this._validAnnotation = true;
358
404
  // The next token must be an object
359
- return this._readObject;
405
+ return pathable ? this._getPathReader(this._readObject, 'predicate') : this._readObject;
360
406
  }
361
407
 
362
408
  // ### `_readObject` reads a quad's object
@@ -389,7 +435,7 @@ export default class N3Parser {
389
435
  return this._error('Unexpected graph', token);
390
436
  this._saveContext('formula', this._graph, this._subject, this._predicate,
391
437
  this._graph = this._factory.blankNode());
392
- return this._readSubject;
438
+ return this._readInFormulaContext;
393
439
  case '<<(':
394
440
  this._saveContext('<<(', this._graph, this._subject, this._predicate, null);
395
441
  this._graph = null;
@@ -456,7 +502,7 @@ export default class N3Parser {
456
502
  return this._getContextEndReader();
457
503
  // If the blank node was the predicate, continue reading the object
458
504
  else if (this._predicate !== null)
459
- return this._readObject;
505
+ return this._getPathReader(this._readObject, 'predicate');
460
506
  // If the blank node was the subject, continue reading the predicate
461
507
  else
462
508
  // If the blank node was empty, it could be a named graph label
@@ -529,7 +575,7 @@ export default class N3Parser {
529
575
  // Was this list the parent's predicate?
530
576
  else if (this._object === null) {
531
577
  // The next token is the object
532
- next = this._readObject;
578
+ next = this._getPathReader(this._readObject, 'predicate');
533
579
  // No list tail if this was an empty list
534
580
  if (this._predicate === this.RDF_NIL)
535
581
  return next;
@@ -585,7 +631,7 @@ export default class N3Parser {
585
631
  this._saveContext('formula', this._graph, list, this.RDF_FIRST,
586
632
  this._graph = item);
587
633
  this._subject = null;
588
- return this._readSubject;
634
+ return this._readInFormulaContext;
589
635
  case '<<(':
590
636
  this._saveContext('<<(', this._graph, null, null, null);
591
637
  this._graph = null;
@@ -706,13 +752,12 @@ export default class N3Parser {
706
752
  token = null;
707
753
  }
708
754
 
709
- if (component === 'subject') {
710
- // A subject literal implies N3 mode, so the literal might start a path
711
- const reader = this._getPathEndReader(token, this._readPredicateOrNamedGraph);
712
- return reader || this._readPredicateOrNamedGraph(token);
755
+ if (component === 'subject' || component === 'predicate') {
756
+ // A subject or predicate literal implies N3 mode, so it might start a path
757
+ const next = component === 'subject' ? this._readPredicateOrNamedGraph : this._readObject;
758
+ const reader = this._getPathEndReader(token, next, component);
759
+ return reader || next.call(this, token);
713
760
  }
714
- if (component === 'predicate')
715
- return token === null ? this._readObject : this._readObject(token);
716
761
  return this._completeObjectLiteralPost(token, listItem);
717
762
  }
718
763
 
@@ -739,16 +784,13 @@ export default class N3Parser {
739
784
  return this._readDirCode;
740
785
  }
741
786
 
742
- // A subject literal implies N3 mode, so it might start a path.
743
- if (component === 'subject') {
744
- const reader = this._getPathEndReader(completed.token, next);
745
- if (reader)
746
- return reader;
747
- }
787
+ // A subject or predicate literal implies N3 mode, so it might start a path.
788
+ const reader = this._getPathEndReader(completed.token, next, component);
789
+ if (reader)
790
+ return reader;
748
791
 
749
- // If the token was consumed as a datatype, continue with the next component;
750
- // otherwise, consume the token now
751
- return completed.token === null ? next : next.call(this, completed.token);
792
+ // Consume the non-path token now
793
+ return next.call(this, completed.token);
752
794
  }
753
795
 
754
796
  // Completes a literal in subject position
@@ -823,13 +865,18 @@ export default class N3Parser {
823
865
  if (empty && this._emptyFormulaAsTrue) {
824
866
  if (this._subject === formula)
825
867
  this._subject = this.N3_TRUE;
868
+ else if (this._predicate === formula)
869
+ this._predicate = this.N3_TRUE;
826
870
  else
827
871
  this._object = this.N3_TRUE;
828
872
  }
829
873
 
830
- // If the formula was the subject, continue reading the predicate.
831
- // If the formula was the object, read punctuation.
832
- return this._object === null ? this._readPredicate : this._getContextEndReader();
874
+ // Continue according to the formula's position in the enclosing statement
875
+ if (this._object !== null)
876
+ return this._getPathReader(this._getContextEndReader(), 'object');
877
+ if (this._predicate !== null)
878
+ return this._getPathReader(this._readObject, 'predicate');
879
+ return this._getPathReader(this._readPredicate, 'subject');
833
880
  }
834
881
 
835
882
  // ### `_readPunctuation` reads punctuation between quads or quad parts
@@ -848,7 +895,7 @@ export default class N3Parser {
848
895
  case '.':
849
896
  this._subject = null;
850
897
  this._tripleTerm = null;
851
- next = this._contextStack.length ? this._readSubject : this._readInTopContext;
898
+ next = this._getStatementReader();
852
899
  if (inversePredicate) this._inversePredicate = false;
853
900
  break;
854
901
  // Semicolon means the subject is shared; predicate and object are different
@@ -1021,12 +1068,12 @@ export default class N3Parser {
1021
1068
  // SPARQL-style declarations don't have punctuation
1022
1069
  if (this._sparqlStyle) {
1023
1070
  this._sparqlStyle = false;
1024
- return this._readInTopContext(token);
1071
+ return this._getStatementReader().call(this, token);
1025
1072
  }
1026
1073
 
1027
1074
  if (token.type !== '.')
1028
1075
  return this._error('Expected declaration to end with a dot', token);
1029
- return this._readInTopContext;
1076
+ return this._getStatementReader();
1030
1077
  }
1031
1078
 
1032
1079
  // Reads a list of quantified symbols from a @forSome or @forAll statement
@@ -1078,18 +1125,19 @@ export default class N3Parser {
1078
1125
  }
1079
1126
 
1080
1127
  // ### `_getPathReader` reads a potential path and then resumes with the given function
1081
- _getPathReader(afterPath) {
1128
+ _getPathReader(afterPath, position) {
1082
1129
  this._afterPath = afterPath;
1130
+ this._pathPosition = position || (this._predicate === null ? 'subject' : 'object');
1083
1131
  return this._readPath;
1084
1132
  }
1085
1133
 
1086
1134
  // ### `_getPathEndReader` continues reading after a term that might start a path,
1087
1135
  // given the pending token that follows the term (or `null` if it was consumed)
1088
- _getPathEndReader(token, afterPath) {
1136
+ _getPathEndReader(token, afterPath, position) {
1089
1137
  // Other pending tokens are not handled here
1090
1138
  if (token !== null && token.type !== '!' && token.type !== '^')
1091
1139
  return null;
1092
- const reader = this._getPathReader(afterPath);
1140
+ const reader = this._getPathReader(afterPath, position);
1093
1141
  // If no token is pending, wait for the next one; otherwise, consume it now
1094
1142
  return token === null ? reader : reader.call(this, token);
1095
1143
  }
@@ -1103,6 +1151,7 @@ export default class N3Parser {
1103
1151
  case '^': return this._readBackwardPath;
1104
1152
  // Not a path; resume reading where we left off
1105
1153
  default:
1154
+ const afterPath = this._afterPath;
1106
1155
  const stack = this._contextStack, parent = stack.length && stack[stack.length - 1];
1107
1156
  // If we were reading a list item, we still need to output it
1108
1157
  if (parent && parent.type === 'item') {
@@ -1113,7 +1162,9 @@ export default class N3Parser {
1113
1162
  // Output the list item
1114
1163
  this._emit(this._subject, this.RDF_FIRST, item, this._graph);
1115
1164
  }
1116
- return this._afterPath(token);
1165
+ this._afterPath = null;
1166
+ this._pathPosition = null;
1167
+ return afterPath.call(this, token);
1117
1168
  }
1118
1169
  }
1119
1170
 
@@ -1124,10 +1175,11 @@ export default class N3Parser {
1124
1175
  // The next token is the predicate
1125
1176
  if ((predicate = this._readEntity(token)) === undefined)
1126
1177
  return;
1127
- // If we were reading a subject, replace the subject by the path's object
1128
- if (this._predicate === null)
1178
+ // Replace the path expression with the generated object in its current position
1179
+ if (this._pathPosition === 'subject')
1129
1180
  subject = this._subject, this._subject = object;
1130
- // If we were reading an object, replace the subject by the path's object
1181
+ else if (this._pathPosition === 'predicate')
1182
+ subject = this._predicate, this._predicate = object;
1131
1183
  else
1132
1184
  subject = this._object, this._object = object;
1133
1185
  // Emit the path's current quad and read its next section
@@ -1142,10 +1194,11 @@ export default class N3Parser {
1142
1194
  // The next token is the predicate
1143
1195
  if ((predicate = this._readEntity(token)) === undefined)
1144
1196
  return;
1145
- // If we were reading a subject, replace the subject by the path's subject
1146
- if (this._predicate === null)
1197
+ // Replace the path expression with the generated subject in its current position
1198
+ if (this._pathPosition === 'subject')
1147
1199
  object = this._subject, this._subject = subject;
1148
- // If we were reading an object, replace the subject by the path's subject
1200
+ else if (this._pathPosition === 'predicate')
1201
+ object = this._predicate, this._predicate = subject;
1149
1202
  else
1150
1203
  object = this._object, this._object = subject;
1151
1204
  // Emit the path's current quad and read its next section