n3 3.0.0-alpha.7 → 3.0.0-alpha.9

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/src/N3Lexer.js CHANGED
@@ -2,7 +2,35 @@
2
2
  import namespaces from './IRIs';
3
3
 
4
4
  const { xsd } = namespaces;
5
- const SPACE = 0x20, TAB = 0x09, LF = 0x0A, CR = 0x0D, HASH = 0x23;
5
+ const SPACE = 0x20, TAB = 0x09, LF = 0x0A, CR = 0x0D, HASH = 0x23,
6
+ DOT = 0x2E, COLON = 0x3A, ZERO = 0x30, NINE = 0x39,
7
+ PERCENT = 0x25, BACKSLASH = 0x5C;
8
+
9
+ // Whitespace as matched by `\s`
10
+ function isWhitespace(charCode) {
11
+ return charCode === SPACE || charCode >= TAB && charCode <= CR ||
12
+ charCode >= 0xA0 && (charCode === 0xA0 || charCode === 0x1680 ||
13
+ charCode >= 0x2000 && charCode <= 0x200A || charCode === 0x2028 || charCode === 0x2029 ||
14
+ charCode === 0x202F || charCode === 0x205F || charCode === 0x3000 || charCode === 0xFEFF);
15
+ }
16
+ // Characters that can directly follow a name: whitespace and punctuation
17
+ // (the lookahead `[,;!\^\s#()\[\]\{\}"'<>]` of the former regular expressions)
18
+ const asciiDelimiters = new Uint8Array(0x80);
19
+ for (const char of ',;!^#()[]{}"\'<> \t\n\v\f\r')
20
+ asciiDelimiters[char.charCodeAt(0)] = 1;
21
+ function isDelimiter(charCode) {
22
+ return charCode < 0x80 ? asciiDelimiters[charCode] === 1 : isWhitespace(charCode);
23
+ }
24
+ // Whether a name can end before the given position: it must be followed by
25
+ // a delimiter, optionally after a dot. At the end of finished input, it can always end.
26
+ function canEndName(input, pos, inputFinished) {
27
+ let charCode = input.charCodeAt(pos);
28
+ if (charCode === DOT)
29
+ charCode = input.charCodeAt(++pos);
30
+ if (pos >= input.length)
31
+ return inputFinished;
32
+ return isDelimiter(charCode);
33
+ }
6
34
 
7
35
  // Fixed escape sequences allowed in string literals (ECHAR)
8
36
  const stringEscapeReplacements = {
@@ -16,9 +44,105 @@ const localNameEscapeReplacements = {
16
44
  '=': '=', '/': '/', '?': '?', '#': '#', '@': '@', '%': '%',
17
45
  };
18
46
  const illegalIriChars = /[\x00-\x20<>\\"\{\}\|\^\`]/;
19
- // Characters that cannot occur in a prefixed name, not even escaped
20
- // (global, so that testAt searches the rest of the input from a position)
21
- const nonPrefixedNameChar = /[\s<>"{}|^`]/g;
47
+
48
+ // Character classes of names, as bit flags for ASCII characters
49
+ const PREFIX_START = 1, // PN_CHARS_BASE
50
+ LOCAL_START = 2, // PN_CHARS_U, digits, and colon
51
+ NAME_CHAR = 4, // PN_CHARS
52
+ LOCAL_CHAR = 8, // PN_CHARS and colon
53
+ LOCAL_ESCAPE = 16; // characters that can be escaped in local names (PN_LOCAL_ESC)
54
+ const asciiNameClasses = new Uint8Array(0x80);
55
+ for (let charCode = 0; charCode < 0x80; charCode++) {
56
+ const char = String.fromCharCode(charCode);
57
+ const letter = char >= 'A' && char <= 'Z' || char >= 'a' && char <= 'z';
58
+ const nameChar = letter || char === '_' || char === '-' || char >= '0' && char <= '9';
59
+ asciiNameClasses[charCode] =
60
+ (letter ? PREFIX_START : 0) |
61
+ (nameChar && char !== '-' || char === ':' ? LOCAL_START : 0) |
62
+ (nameChar ? NAME_CHAR : 0) |
63
+ (nameChar || char === ':' ? LOCAL_CHAR : 0) |
64
+ (char in localNameEscapeReplacements ? LOCAL_ESCAPE : 0);
65
+ }
66
+ // Returns the length (0, 1, or 2 code units) of the character at the given position
67
+ // if it is in the given name character class
68
+ function nameCharLength(input, pos, charClass) {
69
+ const charCode = input.charCodeAt(pos);
70
+ if (charCode < 0x80)
71
+ return (asciiNameClasses[charCode] & charClass) !== 0 ? 1 : 0;
72
+ // Characters from U+10000 to U+EFFFF consist of a surrogate pair
73
+ if (charCode >= 0xD800 && charCode <= 0xDB7F) {
74
+ const low = input.charCodeAt(pos + 1);
75
+ return low >= 0xDC00 && low <= 0xDFFF ? 2 : 0;
76
+ }
77
+ // PN_CHARS has some characters that PN_CHARS_BASE does not
78
+ if (charClass >= NAME_CHAR && (charCode === 0xB7 || charCode >= 0x300 && charCode <= 0x36F ||
79
+ charCode === 0x203F || charCode === 0x2040))
80
+ return 1;
81
+ // PN_CHARS_BASE
82
+ return charCode >= 0xC0 && charCode <= 0x1FFF && charCode !== 0xD7 && charCode !== 0xF7 &&
83
+ (charCode < 0x300 || charCode >= 0x370 && charCode !== 0x37E) ||
84
+ charCode >= 0x200C && charCode <= 0x200D || charCode >= 0x2070 && charCode <= 0x218F ||
85
+ charCode >= 0x2C00 && charCode <= 0x2FEF || charCode >= 0x3001 && charCode <= 0xD7FF ||
86
+ charCode >= 0xF900 && charCode <= 0xFDCF || charCode >= 0xFDF0 && charCode <= 0xFFFD ? 1 : 0;
87
+ }
88
+ function isLocalEscape(charCode) {
89
+ return charCode < 0x80 && (asciiNameClasses[charCode] & LOCAL_ESCAPE) !== 0;
90
+ }
91
+ function isHexDigit(charCode) {
92
+ return charCode >= ZERO && charCode <= NINE ||
93
+ charCode >= 0x41 && charCode <= 0x46 || charCode >= 0x61 && charCode <= 0x66;
94
+ }
95
+ // Returns the end of the prefix (PN_PREFIX) at the given position,
96
+ // which can contain single dots, but not start or end with one
97
+ function skipPrefix(input, pos) {
98
+ let length = nameCharLength(input, pos, PREFIX_START);
99
+ while (length !== 0) {
100
+ pos += length;
101
+ const next = input.charCodeAt(pos) === DOT ? pos + 1 : pos;
102
+ // Most names are ASCII, so look those up without a call
103
+ const charCode = input.charCodeAt(next);
104
+ length = charCode < 0x80 ? (asciiNameClasses[charCode] & NAME_CHAR) !== 0 ? 1 : 0 :
105
+ nameCharLength(input, next, NAME_CHAR);
106
+ if (length !== 0)
107
+ pos = next;
108
+ }
109
+ return pos;
110
+ }
111
+ // Returns the end of the local name (PN_LOCAL) at the given position,
112
+ // which can contain dots, but not start or end with one
113
+ function skipLocalName(input, pos) {
114
+ let end = pos, charClass = LOCAL_START;
115
+ while (true) {
116
+ // Most names are ASCII, so look those up without a call
117
+ const charCode = input.charCodeAt(pos);
118
+ let length = charCode < 0x80 ? (asciiNameClasses[charCode] & charClass) !== 0 ? 1 : 0 :
119
+ nameCharLength(input, pos, charClass);
120
+ // Percent-encoded character (PERCENT)
121
+ if (length === 0 && charCode === PERCENT &&
122
+ isHexDigit(input.charCodeAt(pos + 1)) && isHexDigit(input.charCodeAt(pos + 2)))
123
+ length = 3;
124
+ // Escaped character (PN_LOCAL_ESC)
125
+ else if (length === 0 && charCode === BACKSLASH && isLocalEscape(input.charCodeAt(pos + 1)))
126
+ length = 2;
127
+ if (length !== 0) {
128
+ end = pos += length;
129
+ charClass = LOCAL_CHAR;
130
+ }
131
+ // Dots are allowed after the first character, but not at the end
132
+ else if (charCode === DOT && charClass === LOCAL_CHAR)
133
+ pos++;
134
+ else
135
+ return end;
136
+ }
137
+ }
138
+ // Returns the end of the prefixed name at the given position, or -1 if there is none
139
+ function skipPrefixedName(input, pos, inputFinished) {
140
+ const colon = skipPrefix(input, pos);
141
+ if (input.charCodeAt(colon) !== COLON)
142
+ return -1;
143
+ const end = skipLocalName(input, colon + 1);
144
+ return canEndName(input, end, inputFinished) ? end : -1;
145
+ }
22
146
 
23
147
  // A valid code point is a Unicode scalar value: at most U+10FFFF and not a surrogate
24
148
  function isValidCodePoint(charCode) {
@@ -75,13 +199,11 @@ export default class N3Lexer {
75
199
  this._simpleQuotedString = /"([^"\\\r\n]*)"(?=[^"])/y; // string without escape sequences
76
200
  this._simpleApostropheString = /'([^'\\\r\n]*)'(?=[^'])/y;
77
201
  this._langcode = /@([a-z]+(?:-[a-z0-9]+)*)(?=[^a-z0-9])/iy;
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<])/y;
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-9: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-9: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-9: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#()\[\]\{\}"'<>]))/y;
80
202
  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-9: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#()\[\]\{\}"'<>])/y;
81
203
  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
204
  this._number = /[\-+]?(?:(\d+\.\d*|\.?\d+)[eE][\-+]?\d+|(?=\.?\d)\d*(?:(\.)\d+)?)(?=\.?[,;:!\^\s#()\[\]\{\}"'<>])/y;
83
205
  this._boolean = /(?:true|false)(?=[.,;!\^\s#()\[\]\{\}"'<>])/y;
84
- this._atKeyword = /@[a-z]+(?=[\s#<:"'])/iy;
206
+ this._atKeyword = /@[a-z]+(?=[\s#<:"'.])/iy;
85
207
  this._keyword = /(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<"'])/iy;
86
208
  this._n3Verb = /(?:has|is|of)(?=[\s#()\[\]\{\}"'<>?_+\-0-9])/y;
87
209
  this._n3Id = /id(?=[\s#<])/y;
@@ -116,7 +238,9 @@ export default class N3Lexer {
116
238
  if (!/^[a-z]+$/i.test(name) || reservedWords.test(name))
117
239
  throw new Error(`Invalid directive name: "${name}"`);
118
240
  }
119
- this._directive = new RegExp(`(?:${options.directives.join('|')})(?=[\\s#<])`, 'iy');
241
+ // Like VERSION, directives are case-sensitive in N-Triples and N-Quads
242
+ const names = this._lineMode ? options.directives.map(name => name.toUpperCase()) : options.directives;
243
+ this._directive = new RegExp(`(?:${names.join('|')})(?=[\\s#<])`, this._lineMode ? 'y' : 'iy');
120
244
  this._directiveMaxLength = Math.max(...options.directives.map(name => name.length));
121
245
  // The first characters of directive names, so other words skip the regular expression
122
246
  this._directiveStarts = options.directives.map(name => name[0].toLowerCase() + name[0].toUpperCase()).join('');
@@ -501,9 +625,14 @@ export default class N3Lexer {
501
625
  // Some first characters do not allow an immediate decision, so inspect more
502
626
  if (inconclusive) {
503
627
  // Try to find a prefix
628
+ let end;
504
629
  if ((this._previousMarker === '@prefix' || this._previousMarker === 'PREFIX') &&
505
- (match = execAt(this._prefix, input, pos)))
506
- type = 'prefix', value = match[1] || '';
630
+ input.charCodeAt(end = skipPrefix(input, pos)) === COLON &&
631
+ end + 1 < input.length && (input[end + 1] === '#' || input[end + 1] === '<' ||
632
+ isWhitespace(input.charCodeAt(end + 1)))) {
633
+ type = 'prefix', value = input.slice(pos, end);
634
+ matchLength = end + 1 - pos;
635
+ }
507
636
  // Try to find an additional directive keyword
508
637
  // (at the end of the input, only a short final word can be one)
509
638
  else if (this._directive !== null && this._directiveStarts.includes(firstChar) &&
@@ -512,13 +641,13 @@ export default class N3Lexer {
512
641
  (match = execAtEnd(this._directive, input, pos))))
513
642
  type = match[0].toUpperCase();
514
643
  // Try to find a prefixed name. Since it can contain (but not end with) a dot,
515
- // we always need a non-dot character before deciding it is a prefixed name.
516
- // Therefore, try inserting a space if we're at the end of the input.
517
- else if ((match = execAt(this._prefixed, input, pos)) ||
518
- inputFinished && (match = execAtEnd(this._prefixed, input, pos))) {
519
- type = 'prefixed', prefix = match[1] || '';
520
- value = this._unescape(match[2], localNameEscapeReplacements);
521
- lexicalLength = prefix.length + match[2].length + 1;
644
+ // we always need a non-dot character before deciding it is a prefixed name,
645
+ // except at the end of the input.
646
+ else if (!this._lineMode && (end = skipPrefixedName(input, pos, inputFinished)) >= 0) {
647
+ const colon = input.indexOf(':', pos);
648
+ type = 'prefixed', prefix = input.slice(pos, colon);
649
+ value = this._unescape(input.slice(colon + 1, end), localNameEscapeReplacements);
650
+ matchLength = end - pos;
522
651
  }
523
652
  }
524
653
 
@@ -605,30 +734,21 @@ export default class N3Lexer {
605
734
  return null;
606
735
 
607
736
  // Most verb boundaries cannot be part of a prefix, so keep the common path fast.
737
+ // U+1680 and U+FEFF are whitespace to the regular expression but name characters.
608
738
  const next = input[pos + verb[0].length];
609
- if (next !== '-' && next !== '_' && (next < '0' || next > '9'))
739
+ if (next !== '-' && next !== '_' && (next < '0' || next > '9') &&
740
+ next !== '\u1680' && next !== '\ufeff')
610
741
  return verb;
611
742
 
612
743
  // A prefix can start with a verb and continue with characters that are also
613
744
  // valid verb boundaries. Prefer the longer prefixed name when it is complete.
614
- if (execAt(this._prefixed, input, pos))
615
- return null;
616
- // Appending to the input only matters when a prefixed name could run up to
617
- // its end, which a character that cannot occur in prefixed names rules out.
618
- // This avoids copying the rest of the document for every such verb.
619
- if (testAt(nonPrefixedNameChar, input, pos))
620
- return verb;
621
- if (execAtEnd(this._prefixed, input, pos))
745
+ if (skipPrefixedName(input, pos, true) >= 0)
622
746
  return null;
623
747
 
624
- // If a stream chunk ends partway through such a prefix, wait for the colon
625
- // instead of prematurely emitting the verb. Appending ": " lets the prefix
626
- // grammar determine whether all input seen so far can be a complete prefix.
627
- if (!inputFinished) {
628
- const prefix = execAt(this._prefix, `${input.slice(pos)}: `, 0);
629
- if (prefix)
630
- return null;
631
- }
748
+ // If a stream chunk ends partway through such a prefix,
749
+ // wait for the colon instead of prematurely emitting the verb.
750
+ if (!inputFinished && skipPrefix(input, pos) === input.length)
751
+ return null;
632
752
  return verb;
633
753
  }
634
754
 
@@ -758,6 +878,27 @@ export default class N3Lexer {
758
878
  return err;
759
879
  }
760
880
 
881
+ // ### `_startTokenization` resets the lexer state for a new input
882
+ _startTokenization() {
883
+ this._line = 1;
884
+ this._linePosition = 0;
885
+ this._previousMarker = undefined;
886
+ this.previousToken = undefined;
887
+ this._literalClosingPos = 0;
888
+ this._input = undefined;
889
+ // Deferred tokenization and stream events can outlive their invocation.
890
+ // Ignore them once a later call takes ownership of the lexer state.
891
+ return this._tokenization = {};
892
+ }
893
+
894
+ // ### `_tokenizeString` synchronously emits the tokens of a complete string through the callback,
895
+ // so that the caller can consume each token without the lexer collecting them all first
896
+ _tokenizeString(input, callback) {
897
+ this._startTokenization();
898
+ this._input = this._readStartingBom(input);
899
+ this._tryTokenizeToEnd(callback, true);
900
+ }
901
+
761
902
  // ### Strips off any starting UTF BOM mark.
762
903
  _readStartingBom(input) {
763
904
  if (input.startsWith('\ufeff')) {
@@ -775,15 +916,7 @@ export default class N3Lexer {
775
916
  // Separator whitespace counts towards the next token's start, outside either range.
776
917
  // Multiline tokens also have endLine; their end column is relative to that line.
777
918
  tokenize(input, callback) {
778
- // Deferred tokenization and stream events can outlive their invocation.
779
- // Ignore them once a later call takes ownership of the lexer state.
780
- const tokenization = this._tokenization = {};
781
- this._line = 1;
782
- this._linePosition = 0;
783
- this._previousMarker = undefined;
784
- this.previousToken = undefined;
785
- this._literalClosingPos = 0;
786
- this._input = undefined;
919
+ const tokenization = this._startTokenization();
787
920
 
788
921
  // If the input is a string, continuously emit tokens through the callback until the end
789
922
  if (typeof input === 'string') {
package/src/N3Parser.js CHANGED
@@ -1683,6 +1683,10 @@ export default class N3Parser {
1683
1683
  // The read callback is the next function to be executed when a token arrives.
1684
1684
  // We start reading in the top context.
1685
1685
  this._readCallback = this._readBeforeTopContext;
1686
+ // A parse that failed part-way can have left scopes and a statement open
1687
+ this._contextStack = [];
1688
+ this._graph = this._subject = this._predicate = this._object = null;
1689
+ this._tripleTerm = this._reifier = null;
1686
1690
  this._sparqlStyle = false;
1687
1691
  this._prefixes = Object.create(null);
1688
1692
  this._prefixes._ = this._blankNodePrefix ? this._blankNodePrefix.substr(2)
@@ -1702,14 +1706,14 @@ export default class N3Parser {
1702
1706
  this._emptyFormula = false;
1703
1707
  this._tripleTermDepth = 0;
1704
1708
 
1705
- let readToken = token => {
1709
+ const readGrammarToken = token => {
1706
1710
  return this._readCallback = this._readCallback(token);
1707
1711
  };
1712
+ let readToken = readGrammarToken;
1708
1713
 
1709
1714
  // Comments bypass the grammar, but participate in the token lifecycle.
1710
1715
  if (onComment || this._lexer.comments) {
1711
1716
  this._lexer.comments = true;
1712
- const readGrammarToken = readToken;
1713
1717
  readToken = token => {
1714
1718
  if (token.type !== 'comment')
1715
1719
  return readGrammarToken(token);
@@ -1744,7 +1748,26 @@ export default class N3Parser {
1744
1748
  const quads = [];
1745
1749
  let error;
1746
1750
  this._callback = (e, t) => { e ? (error = e) : t && quads.push(t); };
1747
- this._lexer.tokenize(input).every(readToken);
1751
+ const lexer = this._lexer;
1752
+ // Without callbacks, nothing observes the parse before the whole document
1753
+ // has lexed, so tokens can be parsed as they are lexed instead of being
1754
+ // collected into an array first. A lexer that replaces the built-in
1755
+ // tokenize keeps going through its own implementation.
1756
+ if (!onPrefix && !onVersion && !onDirective && !onComment && !onToken && !onTokenEnd &&
1757
+ lexer.tokenize === N3Lexer.prototype.tokenize && typeof lexer._tokenizeString === 'function') {
1758
+ // A lexical error used to stop the parse before any token was read,
1759
+ // so undo base declarations that were read before it
1760
+ const base = [this._base, this._basePath, this._baseRoot, this._baseScheme];
1761
+ lexer._tokenizeString(input, (e, token) => {
1762
+ if (e) {
1763
+ [this._base, this._basePath, this._baseRoot, this._baseScheme] = base;
1764
+ this._callback(e), this._callback = noop;
1765
+ }
1766
+ else if (this._readCallback) readToken(token);
1767
+ });
1768
+ }
1769
+ else
1770
+ lexer.tokenize(input).every(readToken);
1748
1771
  if (error) throw error;
1749
1772
  return quads;
1750
1773
  }
package/src/N3Reasoner.js CHANGED
@@ -34,6 +34,7 @@ export default class N3Reasoner {
34
34
  if (!store._addToIndex(graphItem.subjects, subject, predicate, object)) return;
35
35
  store._addToIndex(graphItem.predicates, predicate, object, subject);
36
36
  store._addToIndex(graphItem.objects, object, subject, predicate);
37
+ this._indexed++;
37
38
  }
38
39
  // Count genuinely new derivations and fail past the budget. The check comes
39
40
  // after all three indexes are updated, so a caught error leaves the store
@@ -175,7 +176,7 @@ export default class N3Reasoner {
175
176
  }
176
177
 
177
178
  reason(rules) {
178
- this._derivations = 0;
179
+ this._derivations = this._indexed = 0;
179
180
  if (!Array.isArray(rules)) {
180
181
  rules = getRulesFromDataset(rules);
181
182
  }
@@ -242,9 +243,10 @@ export default class N3Reasoner {
242
243
  }
243
244
  }
244
245
  finally {
245
- // Invalidate the cached size even if a derivation budget was exceeded,
246
- // so a caught budget error leaves the store fully consistent.
247
- this._store._size = null;
246
+ // Count the quads added directly to the indexes, even if a derivation
247
+ // budget was exceeded, so a caught budget error leaves the store consistent
248
+ if (this._store._size !== null)
249
+ this._store._size += this._indexed;
248
250
  }
249
251
  }
250
252
  }