n3 2.3.0 → 2.5.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/N3Lexer.js CHANGED
@@ -88,6 +88,7 @@ class N3Lexer {
88
88
  this._boolean = /^(?:true|false)(?=[.,;!\^\s#()\[\]\{\}"'<>])/;
89
89
  this._atKeyword = /^@[a-z]+(?=[\s#<:])/i;
90
90
  this._keyword = /^(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<])/i;
91
+ this._n3Id = /^id(?=[\s#<])/;
91
92
  this._shortPredicates = /^a(?=[\s#()\[\]\{\}"'<>])/;
92
93
  this._newline = /^[ \t]*(?:#[^\n\r]*)?(?:\r\n|\n|\r)[ \t]*/;
93
94
  this._comment = /#([^\n\r]*)/;
@@ -315,6 +316,10 @@ class N3Lexer {
315
316
  // Try to find an abbreviated predicate
316
317
  if (match = this._shortPredicates.exec(input)) type = 'abbreviation', value = 'a';else inconclusive = true;
317
318
  break;
319
+ case 'i':
320
+ // Try to find the IRI property list identifier
321
+ if (this._n3Mode && (match = this._n3Id.exec(input))) type = 'id';else inconclusive = true;
322
+ break;
318
323
  case '=':
319
324
  // Try to find an implication arrow or equals sign
320
325
  if (this._n3Mode && input.length > 1) {
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;
@@ -414,11 +457,33 @@ class N3Parser {
414
457
  if (parentParent.type === '<<') {
415
458
  return this._error('Unexpected compound blank node expression in reified triple', token);
416
459
  }
460
+ if (token.type === 'id') return this._readIriPropertyListId;
417
461
  this._predicate = null;
418
462
  return this._readPredicate(token);
419
463
  }
420
464
  }
421
465
 
466
+ // ### `_readIriPropertyListId` replaces a property list's blank node with its IRI
467
+ _readIriPropertyListId(token) {
468
+ const iri = this._readEntity(token);
469
+ if (iri === undefined) return;
470
+ if (iri.termType !== 'NamedNode') return this._error(`Expected IRI after id but got ${token.type}`, token);
471
+ const placeholder = this._subject;
472
+ this._subject = iri;
473
+ const context = this._contextStack[this._contextStack.length - 1];
474
+ if (context.subject === placeholder) context.subject = iri;
475
+ if (context.predicate === placeholder) context.predicate = iri;
476
+ if (context.object === placeholder) context.object = iri;
477
+ this._predicate = null;
478
+ return this._readIriPropertyListPredicate;
479
+ }
480
+
481
+ // ### `_readIriPropertyListPredicate` requires properties after an IRI property list ID
482
+ _readIriPropertyListPredicate(token) {
483
+ if (token.type === ';' || token.type === ']' || token.type === '.' || token.type === '}') return this._error(`Expected predicate but got ${token.type}`, token);
484
+ return this._readPredicate(token);
485
+ }
486
+
422
487
  // ### `_readBlankNodeTail` reads the end of a blank node
423
488
  _readBlankNodeTail(token) {
424
489
  if (token.type !== ']') return this._readBlankNodePunctuation(token);
@@ -432,7 +497,7 @@ class N3Parser {
432
497
  // If the blank node was the object, restore previous context and read punctuation
433
498
  if (this._object !== null) return this._getContextEndReader();
434
499
  // If the blank node was the predicate, continue reading the object
435
- else if (this._predicate !== null) return this._readObject;
500
+ else if (this._predicate !== null) return this._getPathReader(this._readObject, 'predicate');
436
501
  // If the blank node was the subject, continue reading the predicate
437
502
  else
438
503
  // If the blank node was empty, it could be a named graph label
@@ -504,7 +569,7 @@ class N3Parser {
504
569
  // Was this list the parent's predicate?
505
570
  else if (this._object === null) {
506
571
  // The next token is the object
507
- next = this._readObject;
572
+ next = this._getPathReader(this._readObject, 'predicate');
508
573
  // No list tail if this was an empty list
509
574
  if (this._predicate === this.RDF_NIL) return next;
510
575
  }
@@ -551,7 +616,7 @@ class N3Parser {
551
616
  // Stack the current list quad and start the formula
552
617
  this._saveContext('formula', this._graph, list, this.RDF_FIRST, this._graph = item);
553
618
  this._subject = null;
554
- return this._readSubject;
619
+ return this._readInFormulaContext;
555
620
  case '<<(':
556
621
  this._saveContext('<<(', this._graph, null, null, null);
557
622
  this._graph = null;
@@ -662,12 +727,12 @@ class N3Parser {
662
727
  this._literalLanguage = undefined;
663
728
  token = null;
664
729
  }
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);
730
+ if (component === 'subject' || component === 'predicate') {
731
+ // A subject or predicate literal implies N3 mode, so it might start a path
732
+ const next = component === 'subject' ? this._readPredicateOrNamedGraph : this._readObject;
733
+ const reader = this._getPathEndReader(token, next, component);
734
+ return reader || next.call(this, token);
669
735
  }
670
- if (component === 'predicate') return token === null ? this._readObject : this._readObject(token);
671
736
  return this._completeObjectLiteralPost(token, listItem);
672
737
  }
673
738
 
@@ -691,15 +756,12 @@ class N3Parser {
691
756
  return this._readDirCode;
692
757
  }
693
758
 
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
- }
759
+ // A subject or predicate literal implies N3 mode, so it might start a path.
760
+ const reader = this._getPathEndReader(completed.token, next, component);
761
+ if (reader) return reader;
699
762
 
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);
763
+ // Consume the non-path token now
764
+ return next.call(this, completed.token);
703
765
  }
704
766
 
705
767
  // Completes a literal in subject position
@@ -764,12 +826,13 @@ class N3Parser {
764
826
  // is read as the boolean literal true, following the N3 spec tests
765
827
  // and the direction discussed in https://github.com/w3c-cg/N3/issues/185
766
828
  if (empty && this._emptyFormulaAsTrue) {
767
- if (this._subject === formula) this._subject = this.N3_TRUE;else this._object = this.N3_TRUE;
829
+ 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
830
  }
769
831
 
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();
832
+ // Continue according to the formula's position in the enclosing statement
833
+ if (this._object !== null) return this._getPathReader(this._getContextEndReader(), 'object');
834
+ if (this._predicate !== null) return this._getPathReader(this._readObject, 'predicate');
835
+ return this._getPathReader(this._readPredicate, 'subject');
773
836
  }
774
837
 
775
838
  // ### `_readPunctuation` reads punctuation between quads or quad parts
@@ -789,7 +852,7 @@ class N3Parser {
789
852
  case '.':
790
853
  this._subject = null;
791
854
  this._tripleTerm = null;
792
- next = this._contextStack.length ? this._readSubject : this._readInTopContext;
855
+ next = this._getStatementReader();
793
856
  if (inversePredicate) this._inversePredicate = false;
794
857
  break;
795
858
  // Semicolon means the subject is shared; predicate and object are different
@@ -947,10 +1010,10 @@ class N3Parser {
947
1010
  // SPARQL-style declarations don't have punctuation
948
1011
  if (this._sparqlStyle) {
949
1012
  this._sparqlStyle = false;
950
- return this._readInTopContext(token);
1013
+ return this._getStatementReader().call(this, token);
951
1014
  }
952
1015
  if (token.type !== '.') return this._error('Expected declaration to end with a dot', token);
953
- return this._readInTopContext;
1016
+ return this._getStatementReader();
954
1017
  }
955
1018
 
956
1019
  // Reads a list of quantified symbols from a @forSome or @forAll statement
@@ -995,17 +1058,18 @@ class N3Parser {
995
1058
  }
996
1059
 
997
1060
  // ### `_getPathReader` reads a potential path and then resumes with the given function
998
- _getPathReader(afterPath) {
1061
+ _getPathReader(afterPath, position) {
999
1062
  this._afterPath = afterPath;
1063
+ this._pathPosition = position || (this._predicate === null ? 'subject' : 'object');
1000
1064
  return this._readPath;
1001
1065
  }
1002
1066
 
1003
1067
  // ### `_getPathEndReader` continues reading after a term that might start a path,
1004
1068
  // given the pending token that follows the term (or `null` if it was consumed)
1005
- _getPathEndReader(token, afterPath) {
1069
+ _getPathEndReader(token, afterPath, position) {
1006
1070
  // Other pending tokens are not handled here
1007
1071
  if (token !== null && token.type !== '!' && token.type !== '^') return null;
1008
- const reader = this._getPathReader(afterPath);
1072
+ const reader = this._getPathReader(afterPath, position);
1009
1073
  // If no token is pending, wait for the next one; otherwise, consume it now
1010
1074
  return token === null ? reader : reader.call(this, token);
1011
1075
  }
@@ -1021,6 +1085,7 @@ class N3Parser {
1021
1085
  return this._readBackwardPath;
1022
1086
  // Not a path; resume reading where we left off
1023
1087
  default:
1088
+ const afterPath = this._afterPath;
1024
1089
  const stack = this._contextStack,
1025
1090
  parent = stack.length && stack[stack.length - 1];
1026
1091
  // If we were reading a list item, we still need to output it
@@ -1032,7 +1097,9 @@ class N3Parser {
1032
1097
  // Output the list item
1033
1098
  this._emit(this._subject, this.RDF_FIRST, item, this._graph);
1034
1099
  }
1035
- return this._afterPath(token);
1100
+ this._afterPath = null;
1101
+ this._pathPosition = null;
1102
+ return afterPath.call(this, token);
1036
1103
  }
1037
1104
  }
1038
1105
 
@@ -1042,10 +1109,8 @@ class N3Parser {
1042
1109
  const object = this._factory.blankNode();
1043
1110
  // The next token is the predicate
1044
1111
  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;
1112
+ // Replace the path expression with the generated object in its current position
1113
+ 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
1114
  // Emit the path's current quad and read its next section
1050
1115
  this._emit(subject, predicate, object, this._graph);
1051
1116
  return this._readPath;
@@ -1057,10 +1122,8 @@ class N3Parser {
1057
1122
  let predicate, object;
1058
1123
  // The next token is the predicate
1059
1124
  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;
1125
+ // Replace the path expression with the generated subject in its current position
1126
+ 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
1127
  // Emit the path's current quad and read its next section
1065
1128
  this._emit(subject, predicate, object, this._graph);
1066
1129
  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.5.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/N3Lexer.js CHANGED
@@ -57,6 +57,7 @@ export default class N3Lexer {
57
57
  this._boolean = /^(?:true|false)(?=[.,;!\^\s#()\[\]\{\}"'<>])/;
58
58
  this._atKeyword = /^@[a-z]+(?=[\s#<:])/i;
59
59
  this._keyword = /^(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<])/i;
60
+ this._n3Id = /^id(?=[\s#<])/;
60
61
  this._shortPredicates = /^a(?=[\s#()\[\]\{\}"'<>])/;
61
62
  this._newline = /^[ \t]*(?:#[^\n\r]*)?(?:\r\n|\n|\r)[ \t]*/;
62
63
  this._comment = /#([^\n\r]*)/;
@@ -318,6 +319,14 @@ export default class N3Lexer {
318
319
  inconclusive = true;
319
320
  break;
320
321
 
322
+ case 'i':
323
+ // Try to find the IRI property list identifier
324
+ if (this._n3Mode && (match = this._n3Id.exec(input)))
325
+ type = 'id';
326
+ else
327
+ inconclusive = true;
328
+ break;
329
+
321
330
  case '=':
322
331
  // Try to find an implication arrow or equals sign
323
332
  if (this._n3Mode && input.length > 1) {