n3 2.12.0 → 2.12.2

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
@@ -116,8 +116,8 @@ class N3Lexer {
116
116
  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#()\[\]\{\}"'<>]))/y;
117
117
  this._number = /[\-+]?(?:(\d+\.\d*|\.?\d+)[eE][\-+]?\d+|(?=\.?\d)\d*(?:(\.)\d+)?)(?=\.?[,;:!\^\s#()\[\]\{\}"'<>])/y;
118
118
  this._boolean = /(?:true|false)(?=[.,;!\^\s#()\[\]\{\}"'<>])/y;
119
- this._atKeyword = /@[a-z]+(?=[\s#<:])/iy;
120
- this._keyword = /(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<])/iy;
119
+ this._atKeyword = /@[a-z]+(?=[\s#<:"'])/iy;
120
+ this._keyword = /(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<"'])/iy;
121
121
  this._n3Verb = /(?:has|is|of)(?=[\s#()\[\]\{\}"'<>?_+\-0-9])/y;
122
122
  this._n3Id = /id(?=[\s#<])/y;
123
123
  this._shortPredicates = /a(?=[\s#()\[\]\{\}"'<>])/y;
@@ -236,7 +236,8 @@ class N3Lexer {
236
236
  matchLength = 0,
237
237
  lexicalLength = 0,
238
238
  finalLineLength = 0,
239
- inconclusive = false;
239
+ inconclusive = false,
240
+ tripleQuoted = false;
240
241
  switch (firstChar) {
241
242
  case '^':
242
243
  // A datatype marker separated from its type cannot be followed by another marker
@@ -310,7 +311,8 @@ class N3Lexer {
310
311
  ({
311
312
  value,
312
313
  matchLength,
313
- finalLineLength
314
+ finalLineLength,
315
+ tripleQuoted
314
316
  } = this._parseLiteral(input, pos));
315
317
  if (value === null) return reportSyntaxError(this, input, pos);
316
318
  }
@@ -328,7 +330,8 @@ class N3Lexer {
328
330
  ({
329
331
  value,
330
332
  matchLength,
331
- finalLineLength
333
+ finalLineLength,
334
+ tripleQuoted
332
335
  } = this._parseLiteral(input, pos));
333
336
  if (value === null) return reportSyntaxError(this, input, pos);
334
337
  }
@@ -521,7 +524,21 @@ class N3Lexer {
521
524
  line,
522
525
  start,
523
526
  end: finalLineLength,
524
- endLine: this._line
527
+ endLine: this._line,
528
+ tripleQuoted
529
+ };
530
+ callback(null, token);
531
+ }
532
+ // Triple-quoted strings are marked, since version declarations do not allow them
533
+ else if (tripleQuoted) {
534
+ token = {
535
+ type,
536
+ value,
537
+ prefix,
538
+ line,
539
+ start,
540
+ end: start + length,
541
+ tripleQuoted
525
542
  };
526
543
  callback(null, token);
527
544
  } else token = emitToken(type, value, prefix, line, start, lexicalLength || length);
@@ -671,7 +688,8 @@ class N3Lexer {
671
688
  return {
672
689
  value: this._unescape(raw, stringEscapeReplacements),
673
690
  matchLength,
674
- finalLineLength
691
+ finalLineLength,
692
+ tripleQuoted: openingLength === 3
675
693
  };
676
694
  }
677
695
  closingPos++;
@@ -681,14 +699,34 @@ class N3Lexer {
681
699
  return {
682
700
  value: '',
683
701
  matchLength: 0,
684
- finalLineLength: 0
702
+ finalLineLength: 0,
703
+ tripleQuoted: false
685
704
  };
686
705
  }
687
706
 
707
+ // ### `_tryTokenizeToEnd` tokenizes as far as possible, reporting failures through the callback
708
+ _tryTokenizeToEnd(callback, inputFinished) {
709
+ // Keep track of errors thrown by the callback, which must reach the caller unchanged
710
+ let callbackError;
711
+ try {
712
+ this._tokenizeToEnd((error, token) => {
713
+ try {
714
+ return callback(error, token);
715
+ } catch (thrown) {
716
+ throw callbackError = thrown;
717
+ }
718
+ }, inputFinished);
719
+ } catch (error) {
720
+ // Matching an extremely long token can exhaust the regular expression stack
721
+ if (error === callbackError || !(error instanceof RangeError)) throw error;
722
+ callback(this._syntaxError(null, `Token too long on line ${this._line}.`));
723
+ }
724
+ }
725
+
688
726
  // ### `_syntaxError` creates a syntax error for the given issue
689
- _syntaxError(issue) {
727
+ _syntaxError(issue, message = `Unexpected "${issue}" on line ${this._line}.`) {
690
728
  this._input = null;
691
- const err = new Error(`Unexpected "${issue}" on line ${this._line}.`);
729
+ const err = new Error(message);
692
730
  err.context = {
693
731
  token: undefined,
694
732
  line: this._line,
@@ -729,13 +767,13 @@ class N3Lexer {
729
767
  this._input = this._readStartingBom(input);
730
768
  // If a callback was passed, asynchronously call it
731
769
  if (typeof callback === 'function') queueMicrotask(() => {
732
- if (this._tokenization === tokenization) this._tokenizeToEnd(callback, true);
770
+ if (this._tokenization === tokenization) this._tryTokenizeToEnd(callback, true);
733
771
  });
734
772
  // If no callback was passed, tokenize synchronously and return
735
773
  else {
736
774
  const tokens = [];
737
775
  let error;
738
- this._tokenizeToEnd((e, t) => e ? error = e : tokens.push(t), true);
776
+ this._tryTokenizeToEnd((e, t) => e ? error = e : tokens.push(t), true);
739
777
  if (error) throw error;
740
778
  return tokens;
741
779
  }
@@ -762,7 +800,7 @@ class N3Lexer {
762
800
  // Tokenize as far as possible. When a previous attempt left a long unfinished token,
763
801
  // wait until the buffered input has doubled, so the token is not rescanned for every chunk.
764
802
  if (this._input.length >= retryLength) {
765
- this._tokenizeToEnd(callback, false);
803
+ this._tryTokenizeToEnd(callback, false);
766
804
  retryLength = this._input !== null && this._input.length > MIN_RESCAN_LENGTH ? 2 * this._input.length : 0;
767
805
  }
768
806
  }
@@ -773,7 +811,7 @@ class N3Lexer {
773
811
  // Decode any incomplete character left at the end
774
812
  const rest = decoder ? decoder.decode() : '';
775
813
  if (rest) this._input = typeof this._input === 'string' ? this._input + rest : rest;
776
- if (typeof this._input === 'string') this._tokenizeToEnd(callback, true);
814
+ if (typeof this._input === 'string') this._tryTokenizeToEnd(callback, true);
777
815
  }
778
816
  });
779
817
  input.on('error', error => {
package/lib/N3Parser.js CHANGED
@@ -1025,7 +1025,8 @@ class N3Parser {
1025
1025
  // ### `_readVersion` reads version string declaration
1026
1026
  _readVersion(token) {
1027
1027
  if (token.type !== 'literal') return this._error('Expected literal to follow version declaration', token);
1028
- if (token.end - token.start !== token.value.length + 2) return this._error('Version declarations must use single quotes', token);
1028
+ // Only short strings are allowed, so no numbers or booleans (which have a datatype prefix)
1029
+ if (token.prefix !== '' || token.tripleQuoted) return this._error('Version declarations must use single quotes', token);
1029
1030
  this._versionCallback(token.value);
1030
1031
  if (!this._isValidVersion(token.value)) return this._error(`Detected unsupported version: "${token.value}"`, token);
1031
1032
  return this._readDeclarationPunctuation;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n3",
3
- "version": "2.12.0",
3
+ "version": "2.12.2",
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
@@ -81,8 +81,8 @@ export default class N3Lexer {
81
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#()\[\]\{\}"'<>]))/y;
82
82
  this._number = /[\-+]?(?:(\d+\.\d*|\.?\d+)[eE][\-+]?\d+|(?=\.?\d)\d*(?:(\.)\d+)?)(?=\.?[,;:!\^\s#()\[\]\{\}"'<>])/y;
83
83
  this._boolean = /(?:true|false)(?=[.,;!\^\s#()\[\]\{\}"'<>])/y;
84
- this._atKeyword = /@[a-z]+(?=[\s#<:])/iy;
85
- this._keyword = /(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<])/iy;
84
+ this._atKeyword = /@[a-z]+(?=[\s#<:"'])/iy;
85
+ this._keyword = /(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<"'])/iy;
86
86
  this._n3Verb = /(?:has|is|of)(?=[\s#()\[\]\{\}"'<>?_+\-0-9])/y;
87
87
  this._n3Id = /id(?=[\s#<])/y;
88
88
  this._shortPredicates = /a(?=[\s#()\[\]\{\}"'<>])/y;
@@ -208,7 +208,7 @@ export default class N3Lexer {
208
208
  const line = this._line, firstChar = input[pos];
209
209
  let type = '', value = '', prefix = '',
210
210
  match = null, matchLength = 0, lexicalLength = 0,
211
- finalLineLength = 0, inconclusive = false;
211
+ finalLineLength = 0, inconclusive = false, tripleQuoted = false;
212
212
  switch (firstChar) {
213
213
  case '^':
214
214
  // A datatype marker separated from its type cannot be followed by another marker
@@ -293,7 +293,7 @@ export default class N3Lexer {
293
293
  value = match[1];
294
294
  // Try to find a literal wrapped in three pairs of quotes
295
295
  else {
296
- ({ value, matchLength, finalLineLength } = this._parseLiteral(input, pos));
296
+ ({ value, matchLength, finalLineLength, tripleQuoted } = this._parseLiteral(input, pos));
297
297
  if (value === null)
298
298
  return reportSyntaxError(this, input, pos);
299
299
  }
@@ -310,7 +310,7 @@ export default class N3Lexer {
310
310
  value = match[1];
311
311
  // Try to find a literal wrapped in three pairs of quotes
312
312
  else {
313
- ({ value, matchLength, finalLineLength } = this._parseLiteral(input, pos));
313
+ ({ value, matchLength, finalLineLength, tripleQuoted } = this._parseLiteral(input, pos));
314
314
  if (value === null)
315
315
  return reportSyntaxError(this, input, pos);
316
316
  }
@@ -550,10 +550,15 @@ export default class N3Lexer {
550
550
  if (finalLineLength) {
551
551
  token = {
552
552
  type, value, prefix, line, start,
553
- end: finalLineLength, endLine: this._line,
553
+ end: finalLineLength, endLine: this._line, tripleQuoted,
554
554
  };
555
555
  callback(null, token);
556
556
  }
557
+ // Triple-quoted strings are marked, since version declarations do not allow them
558
+ else if (tripleQuoted) {
559
+ token = { type, value, prefix, line, start, end: start + length, tripleQuoted };
560
+ callback(null, token);
561
+ }
557
562
  else
558
563
  token = emitToken(type, value, prefix, line, start, lexicalLength || length);
559
564
  this.previousToken = token;
@@ -705,19 +710,44 @@ export default class N3Lexer {
705
710
  break;
706
711
  this._line += lineCount;
707
712
  const finalLineLength = lineCount === 0 ? 0 : lines[lines.length - 1].length + openingLength;
708
- return { value: this._unescape(raw, stringEscapeReplacements), matchLength, finalLineLength };
713
+ return {
714
+ value: this._unescape(raw, stringEscapeReplacements), matchLength, finalLineLength,
715
+ tripleQuoted: openingLength === 3,
716
+ };
709
717
  }
710
718
  closingPos++;
711
719
  }
712
720
  this._literalClosingPos = input.length - pos - openingLength + 1;
713
721
  }
714
- return { value: '', matchLength: 0, finalLineLength: 0 };
722
+ return { value: '', matchLength: 0, finalLineLength: 0, tripleQuoted: false };
723
+ }
724
+
725
+ // ### `_tryTokenizeToEnd` tokenizes as far as possible, reporting failures through the callback
726
+ _tryTokenizeToEnd(callback, inputFinished) {
727
+ // Keep track of errors thrown by the callback, which must reach the caller unchanged
728
+ let callbackError;
729
+ try {
730
+ this._tokenizeToEnd((error, token) => {
731
+ try {
732
+ return callback(error, token);
733
+ }
734
+ catch (thrown) {
735
+ throw (callbackError = thrown);
736
+ }
737
+ }, inputFinished);
738
+ }
739
+ catch (error) {
740
+ // Matching an extremely long token can exhaust the regular expression stack
741
+ if (error === callbackError || !(error instanceof RangeError))
742
+ throw error;
743
+ callback(this._syntaxError(null, `Token too long on line ${this._line}.`));
744
+ }
715
745
  }
716
746
 
717
747
  // ### `_syntaxError` creates a syntax error for the given issue
718
- _syntaxError(issue) {
748
+ _syntaxError(issue, message = `Unexpected "${issue}" on line ${this._line}.`) {
719
749
  this._input = null;
720
- const err = new Error(`Unexpected "${issue}" on line ${this._line}.`);
750
+ const err = new Error(message);
721
751
  err.context = {
722
752
  token: undefined,
723
753
  line: this._line,
@@ -760,13 +790,13 @@ export default class N3Lexer {
760
790
  if (typeof callback === 'function')
761
791
  queueMicrotask(() => {
762
792
  if (this._tokenization === tokenization)
763
- this._tokenizeToEnd(callback, true);
793
+ this._tryTokenizeToEnd(callback, true);
764
794
  });
765
795
  // If no callback was passed, tokenize synchronously and return
766
796
  else {
767
797
  const tokens = [];
768
798
  let error;
769
- this._tokenizeToEnd((e, t) => e ? (error = e) : tokens.push(t), true);
799
+ this._tryTokenizeToEnd((e, t) => e ? (error = e) : tokens.push(t), true);
770
800
  if (error) throw error;
771
801
  return tokens;
772
802
  }
@@ -793,7 +823,7 @@ export default class N3Lexer {
793
823
  // Tokenize as far as possible. When a previous attempt left a long unfinished token,
794
824
  // wait until the buffered input has doubled, so the token is not rescanned for every chunk.
795
825
  if (this._input.length >= retryLength) {
796
- this._tokenizeToEnd(callback, false);
826
+ this._tryTokenizeToEnd(callback, false);
797
827
  retryLength = this._input !== null && this._input.length > MIN_RESCAN_LENGTH ?
798
828
  2 * this._input.length : 0;
799
829
  }
@@ -807,7 +837,7 @@ export default class N3Lexer {
807
837
  if (rest)
808
838
  this._input = typeof this._input === 'string' ? this._input + rest : rest;
809
839
  if (typeof this._input === 'string')
810
- this._tokenizeToEnd(callback, true);
840
+ this._tryTokenizeToEnd(callback, true);
811
841
  }
812
842
  });
813
843
  input.on('error', error => {
package/src/N3Parser.js CHANGED
@@ -1111,7 +1111,8 @@ export default class N3Parser {
1111
1111
  _readVersion(token) {
1112
1112
  if (token.type !== 'literal')
1113
1113
  return this._error('Expected literal to follow version declaration', token);
1114
- if ((token.end - token.start) !== token.value.length + 2)
1114
+ // Only short strings are allowed, so no numbers or booleans (which have a datatype prefix)
1115
+ if (token.prefix !== '' || token.tripleQuoted)
1115
1116
  return this._error('Version declarations must use single quotes', token);
1116
1117
  this._versionCallback(token.value);
1117
1118
  if (!this._isValidVersion(token.value))