n3 3.0.0-alpha.3 → 3.0.0-alpha.5
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/README.md +32 -0
- package/browser/n3.esm.min.js +11 -11
- package/browser/n3.min.js +11 -11
- package/lib/N3Lexer.js +204 -149
- package/lib/N3Parser.js +54 -27
- package/lib/N3Reasoner.js +1 -1
- package/lib/N3Store.js +46 -5
- package/lib/N3StreamParser.js +4 -0
- package/lib/N3Writer.js +46 -16
- package/lib/Util.js +1 -1
- package/package.json +2 -29
- package/src/N3Lexer.js +228 -162
- package/src/N3Parser.js +61 -27
- package/src/N3Reasoner.js +1 -1
- package/src/N3Store.js +57 -8
- package/src/N3StreamParser.js +2 -0
- package/src/N3Writer.js +48 -20
- package/src/Util.js +1 -1
package/src/N3Lexer.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// **N3Lexer** tokenizes N3 documents.
|
|
2
|
-
import { Buffer } from 'buffer';
|
|
3
2
|
import namespaces from './IRIs';
|
|
4
3
|
|
|
5
4
|
const { xsd } = namespaces;
|
|
@@ -18,7 +17,8 @@ const localNameEscapeReplacements = {
|
|
|
18
17
|
};
|
|
19
18
|
const illegalIriChars = /[\x00-\x20<>\\"\{\}\|\^\`]/;
|
|
20
19
|
// Characters that cannot occur in a prefixed name, not even escaped
|
|
21
|
-
|
|
20
|
+
// (global, so that testAt searches the rest of the input from a position)
|
|
21
|
+
const nonPrefixedNameChar = /[\s<>"{}|^`]/g;
|
|
22
22
|
|
|
23
23
|
// A valid code point is a Unicode scalar value: at most U+10FFFF and not a surrogate
|
|
24
24
|
function isValidCodePoint(charCode) {
|
|
@@ -35,30 +35,59 @@ const lineModeRegExps = {
|
|
|
35
35
|
_whitespace: true,
|
|
36
36
|
};
|
|
37
37
|
const invalidRegExp = /$0^/;
|
|
38
|
+
const nonWhitespace = /\S*/y;
|
|
39
|
+
|
|
40
|
+
// Matches a sticky regular expression at the given position of the input
|
|
41
|
+
function execAt(regExp, input, pos) {
|
|
42
|
+
regExp.lastIndex = pos;
|
|
43
|
+
return regExp.exec(input);
|
|
44
|
+
}
|
|
45
|
+
function testAt(regExp, input, pos) {
|
|
46
|
+
regExp.lastIndex = pos;
|
|
47
|
+
return regExp.test(input);
|
|
48
|
+
}
|
|
49
|
+
// Matches the rest of the input followed by a space, as at the end of the input,
|
|
50
|
+
// a token that can contain (but not end with) a dot needs a non-dot character after it
|
|
51
|
+
function execAtEnd(regExp, input, pos) {
|
|
52
|
+
regExp.lastIndex = 0;
|
|
53
|
+
return regExp.exec(`${input.slice(pos)} `);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Whitespace or the start of a comment
|
|
57
|
+
function isSeparatorCode(code) {
|
|
58
|
+
return code === SPACE || code === TAB || code === LF || code === CR || code === HASH;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Words with a fixed meaning in the grammar, which cannot name an additional directive
|
|
62
|
+
const reservedWords = /^(?:prefix|base|version|graph|forsome|forall|iri|a|true|false|has|is|of|id)$/i;
|
|
63
|
+
|
|
64
|
+
// Unfinished input in a stream up to this length is tokenized again with every chunk
|
|
65
|
+
const MIN_RESCAN_LENGTH = 1024;
|
|
38
66
|
|
|
39
67
|
// ## Constructor
|
|
40
68
|
export default class N3Lexer {
|
|
41
69
|
constructor(options) {
|
|
42
70
|
// ## Regular expressions
|
|
43
|
-
// It's slightly faster to have these as properties than as in-scope variables
|
|
44
|
-
|
|
45
|
-
this.
|
|
46
|
-
this.
|
|
47
|
-
this.
|
|
48
|
-
this.
|
|
49
|
-
this.
|
|
50
|
-
this.
|
|
51
|
-
this.
|
|
52
|
-
this.
|
|
53
|
-
this.
|
|
54
|
-
this.
|
|
55
|
-
this.
|
|
56
|
-
this.
|
|
57
|
-
this.
|
|
58
|
-
this.
|
|
59
|
-
this.
|
|
60
|
-
this.
|
|
61
|
-
this.
|
|
71
|
+
// It's slightly faster to have these as properties than as in-scope variables.
|
|
72
|
+
// They are sticky, so they only match at the `lastIndex` set by `execAt`.
|
|
73
|
+
this._iri = /<((?:[^ <>{}\\]|\\[uU])+)>[ \t]*/y; // IRI with escape sequences; needs sanity check after unescaping
|
|
74
|
+
this._unescapedIri = /<([^\x00-\x20<>\\"\{\}\|\^\`]*)>[ \t]*/y; // IRI without escape sequences; no unescaping
|
|
75
|
+
this._simpleQuotedString = /"([^"\\\r\n]*)"(?=[^"])/y; // string without escape sequences
|
|
76
|
+
this._simpleApostropheString = /'([^'\\\r\n]*)'(?=[^'])/y;
|
|
77
|
+
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
|
+
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
|
+
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
|
+
this._number = /[\-+]?(?:(\d+\.\d*|\.?\d+)[eE][\-+]?\d+|(?=\.?\d)\d*(?:(\.)\d+)?)(?=\.?[,;:!\^\s#()\[\]\{\}"'<>])/y;
|
|
83
|
+
this._boolean = /(?:true|false)(?=[.,;!\^\s#()\[\]\{\}"'<>])/y;
|
|
84
|
+
this._atKeyword = /@[a-z]+(?=[\s#<:])/iy;
|
|
85
|
+
this._keyword = /(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<])/iy;
|
|
86
|
+
this._n3Verb = /(?:has|is|of)(?=[\s#()\[\]\{\}"'<>?_+\-0-9])/y;
|
|
87
|
+
this._n3Id = /id(?=[\s#<])/y;
|
|
88
|
+
this._shortPredicates = /a(?=[\s#()\[\]\{\}"'<>])/y;
|
|
89
|
+
this._commentLine = /[ \t]*#([^\n\r]*)(?:\r\n|\n|\r)([ \t]*)/y;
|
|
90
|
+
this._whitespace = /[ \t]+/y;
|
|
62
91
|
options = options || {};
|
|
63
92
|
|
|
64
93
|
// Whether the log:isImpliedBy predicate is supported
|
|
@@ -77,6 +106,19 @@ export default class N3Lexer {
|
|
|
77
106
|
else {
|
|
78
107
|
this._n3Mode = options.n3 !== false;
|
|
79
108
|
}
|
|
109
|
+
// Recognize additional directive keywords, such as MESSAGE
|
|
110
|
+
// (the @-form of a directive is always tokenized as an @-keyword)
|
|
111
|
+
this._directive = null;
|
|
112
|
+
if (options.directives && options.directives.length !== 0) {
|
|
113
|
+
for (const name of options.directives) {
|
|
114
|
+
if (!/^[a-z]+$/i.test(name) || reservedWords.test(name))
|
|
115
|
+
throw new Error(`Invalid directive name: "${name}"`);
|
|
116
|
+
}
|
|
117
|
+
this._directive = new RegExp(`(?:${options.directives.join('|')})(?=[\\s#<])`, 'iy');
|
|
118
|
+
this._directiveMaxLength = Math.max(...options.directives.map(name => name.length));
|
|
119
|
+
// The first characters of directive names, so other words skip the regular expression
|
|
120
|
+
this._directiveStarts = options.directives.map(name => name[0].toLowerCase() + name[0].toUpperCase()).join('');
|
|
121
|
+
}
|
|
80
122
|
// Don't output comment tokens by default
|
|
81
123
|
this.comments = !!options.comments;
|
|
82
124
|
// Cache the last tested closing position of long literals
|
|
@@ -87,98 +129,103 @@ export default class N3Lexer {
|
|
|
87
129
|
|
|
88
130
|
// ### `_tokenizeToEnd` tokenizes as for as possible, emitting tokens through the callback
|
|
89
131
|
_tokenizeToEnd(callback, inputFinished) {
|
|
90
|
-
// Continue parsing as far as possible; the loop will return eventually
|
|
91
|
-
|
|
132
|
+
// Continue parsing as far as possible; the loop will return eventually.
|
|
133
|
+
// Rather than slicing off every token, track the position of the remaining input;
|
|
134
|
+
// the regular expressions are sticky, so they match at that position.
|
|
135
|
+
const input = this._input;
|
|
136
|
+
let pos = 0;
|
|
92
137
|
let currentLineLength = this._linePosition + input.length;
|
|
93
138
|
while (true) {
|
|
94
139
|
// Consume one separator line at a time, including its following indentation.
|
|
95
140
|
while (true) {
|
|
96
|
-
let charCode = input.charCodeAt(
|
|
141
|
+
let charCode = input.charCodeAt(pos), separatorLength = 0;
|
|
97
142
|
if (charCode === SPACE || charCode === TAB) {
|
|
98
|
-
const next = input.charCodeAt(1);
|
|
143
|
+
const next = input.charCodeAt(pos + 1);
|
|
99
144
|
separatorLength = next === SPACE || next === TAB ?
|
|
100
|
-
this._whitespace
|
|
101
|
-
charCode = input.charCodeAt(separatorLength);
|
|
145
|
+
execAt(this._whitespace, input, pos)[0].length : 1;
|
|
146
|
+
charCode = input.charCodeAt(pos + separatorLength);
|
|
102
147
|
}
|
|
103
148
|
if (charCode === HASH) {
|
|
104
|
-
const comment = this._commentLine
|
|
149
|
+
const comment = execAt(this._commentLine, input, pos);
|
|
105
150
|
if (comment) {
|
|
106
151
|
const commentLength = comment[0].length;
|
|
107
152
|
// Keep a trailing CR buffered in case the next chunk starts with LF.
|
|
108
|
-
if (!inputFinished && commentLength === input.length &&
|
|
109
|
-
input.charCodeAt(
|
|
110
|
-
this.
|
|
111
|
-
return this._input = input;
|
|
112
|
-
}
|
|
153
|
+
if (!inputFinished && pos + commentLength === input.length &&
|
|
154
|
+
input.charCodeAt(input.length - 1) === CR)
|
|
155
|
+
return this._suspend(input, pos, currentLineLength);
|
|
113
156
|
if (this.comments)
|
|
114
|
-
emitComment(comment[1], this._line, separatorLength);
|
|
115
|
-
|
|
116
|
-
currentLineLength = input.length + comment[2].length;
|
|
157
|
+
emitComment(comment[1], this._line, currentLineLength - (input.length - pos) + separatorLength);
|
|
158
|
+
pos += commentLength;
|
|
159
|
+
currentLineLength = input.length - pos + comment[2].length;
|
|
117
160
|
this._line++;
|
|
118
161
|
}
|
|
119
162
|
else {
|
|
120
163
|
// A comment without a line ending stays buffered until EOF.
|
|
121
|
-
|
|
122
|
-
if (!inputFinished)
|
|
123
|
-
this.
|
|
124
|
-
return this._input = input;
|
|
125
|
-
}
|
|
164
|
+
pos += separatorLength;
|
|
165
|
+
if (!inputFinished)
|
|
166
|
+
return this._suspend(input, pos, currentLineLength);
|
|
126
167
|
if (this.comments)
|
|
127
|
-
emitComment(input.slice(1), this._line,
|
|
128
|
-
|
|
168
|
+
emitComment(input.slice(pos + 1), this._line, currentLineLength - (input.length - pos));
|
|
169
|
+
pos = input.length;
|
|
129
170
|
break;
|
|
130
171
|
}
|
|
131
172
|
}
|
|
132
173
|
else if (charCode === LF || charCode === CR) {
|
|
133
174
|
// A CR at the end of a chunk may still be followed by LF.
|
|
134
|
-
if (!inputFinished && charCode === CR && separatorLength + 1 === input.length)
|
|
135
|
-
this.
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
separatorLength += charCode === CR && input.charCodeAt(separatorLength + 1) === LF ? 2 : 1;
|
|
175
|
+
if (!inputFinished && charCode === CR && pos + separatorLength + 1 === input.length)
|
|
176
|
+
return this._suspend(input, pos, currentLineLength);
|
|
177
|
+
separatorLength += charCode === CR && input.charCodeAt(pos + separatorLength + 1) === LF ? 2 : 1;
|
|
139
178
|
// Indentation is consumed with the newline, but belongs to the next line's columns.
|
|
140
179
|
let indentationLength = 0;
|
|
141
|
-
const next = input.charCodeAt(separatorLength);
|
|
180
|
+
const next = input.charCodeAt(pos + separatorLength);
|
|
142
181
|
if (next === SPACE || next === TAB) {
|
|
143
|
-
const following = input.charCodeAt(separatorLength + 1);
|
|
182
|
+
const following = input.charCodeAt(pos + separatorLength + 1);
|
|
144
183
|
indentationLength = following === SPACE || following === TAB ?
|
|
145
|
-
this._whitespace
|
|
184
|
+
execAt(this._whitespace, input, pos + separatorLength)[0].length : 1;
|
|
146
185
|
}
|
|
147
|
-
|
|
148
|
-
currentLineLength = input.length + indentationLength;
|
|
186
|
+
pos += separatorLength + indentationLength;
|
|
187
|
+
currentLineLength = input.length - pos + indentationLength;
|
|
149
188
|
this._line++;
|
|
150
189
|
}
|
|
151
190
|
else {
|
|
152
|
-
|
|
153
|
-
input = input.slice(separatorLength);
|
|
191
|
+
pos += separatorLength;
|
|
154
192
|
break;
|
|
155
193
|
}
|
|
156
194
|
}
|
|
157
|
-
if (input.length
|
|
195
|
+
if (pos >= input.length) {
|
|
196
|
+
// A datatype marker needs a type
|
|
197
|
+
if (inputFinished && this._previousMarker === '^^')
|
|
198
|
+
return reportSyntaxError(this, input, pos);
|
|
199
|
+
this._linePosition = currentLineLength;
|
|
158
200
|
if (inputFinished) {
|
|
159
|
-
|
|
160
|
-
|
|
201
|
+
emitToken('eof', '', '', this._line, currentLineLength, 0);
|
|
202
|
+
return this._input = null;
|
|
161
203
|
}
|
|
162
|
-
this.
|
|
163
|
-
return this._input = input;
|
|
204
|
+
return this._input = '';
|
|
164
205
|
}
|
|
165
206
|
|
|
166
207
|
// Look for specific token types based on the first character
|
|
167
|
-
const line = this._line, firstChar = input[
|
|
208
|
+
const line = this._line, firstChar = input[pos];
|
|
168
209
|
let type = '', value = '', prefix = '',
|
|
169
210
|
match = null, matchLength = 0, lexicalLength = 0,
|
|
170
211
|
finalLineLength = 0, inconclusive = false;
|
|
171
212
|
switch (firstChar) {
|
|
172
213
|
case '^':
|
|
214
|
+
// A datatype marker separated from its type cannot be followed by another marker
|
|
215
|
+
if (this._previousMarker === '^^')
|
|
216
|
+
return reportSyntaxError(this, input, pos);
|
|
173
217
|
// We need at least 3 tokens lookahead to distinguish ^^<IRI> and ^^pre:fixed
|
|
174
|
-
if (input.length < 3)
|
|
218
|
+
if (input.length - pos < 3)
|
|
175
219
|
break;
|
|
176
220
|
// Try to match a type
|
|
177
|
-
else if (input[1] === '^') {
|
|
221
|
+
else if (input[pos + 1] === '^') {
|
|
178
222
|
this._previousMarker = '^^';
|
|
179
223
|
// Move to type IRI or prefixed name
|
|
180
|
-
|
|
181
|
-
if (input[
|
|
224
|
+
pos += 2;
|
|
225
|
+
if (input[pos] !== '<') {
|
|
226
|
+
// Whitespace and comments may separate the marker from the type
|
|
227
|
+
if (isSeparatorCode(input.charCodeAt(pos)))
|
|
228
|
+
continue; // eslint-disable-line no-continue
|
|
182
229
|
inconclusive = true;
|
|
183
230
|
break;
|
|
184
231
|
}
|
|
@@ -194,38 +241,38 @@ export default class N3Lexer {
|
|
|
194
241
|
// Fall through in case the type is an IRI
|
|
195
242
|
case '<':
|
|
196
243
|
// Try to find a full IRI without escape sequences
|
|
197
|
-
if (match = this._unescapedIri
|
|
244
|
+
if (match = execAt(this._unescapedIri, input, pos)) {
|
|
198
245
|
type = 'IRI', value = match[1];
|
|
199
246
|
lexicalLength = match[1].length + 2;
|
|
200
247
|
}
|
|
201
248
|
// Try to find a full IRI with escape sequences
|
|
202
|
-
else if (match = this._iri
|
|
249
|
+
else if (match = execAt(this._iri, input, pos)) {
|
|
203
250
|
value = this._unescape(match[1], stringEscapeReplacements);
|
|
204
251
|
if (value === null || illegalIriChars.test(value))
|
|
205
|
-
return reportSyntaxError(this);
|
|
252
|
+
return reportSyntaxError(this, input, pos);
|
|
206
253
|
type = 'IRI';
|
|
207
254
|
lexicalLength = match[1].length + 2;
|
|
208
255
|
}
|
|
209
256
|
// Try to find a triple term
|
|
210
|
-
else if (input.length > 2 && input[1] === '<' && input[2] === '(')
|
|
257
|
+
else if (input.length - pos > 2 && input[pos + 1] === '<' && input[pos + 2] === '(')
|
|
211
258
|
type = '<<(', matchLength = 3;
|
|
212
259
|
// Try to find a reified triple
|
|
213
|
-
else if (!this._lineMode && input.length > (inputFinished ? 1 : 2) && input[1] === '<')
|
|
260
|
+
else if (!this._lineMode && input.length - pos > (inputFinished ? 1 : 2) && input[pos + 1] === '<')
|
|
214
261
|
type = '<<', matchLength = 2;
|
|
215
262
|
// Try to find a backwards implication arrow
|
|
216
|
-
else if (this._n3Mode && input.length > 1 && input[1] === '=') {
|
|
263
|
+
else if (this._n3Mode && input.length - pos > 1 && input[pos + 1] === '=') {
|
|
217
264
|
matchLength = 2;
|
|
218
265
|
if (this._isImpliedBy) type = 'abbreviation', value = '<';
|
|
219
266
|
else type = 'inverse', value = '>';
|
|
220
267
|
}
|
|
221
268
|
// Try to find an inverted predicate marker
|
|
222
|
-
else if (this._n3Mode && input.length > 1 && input[1] === '-')
|
|
269
|
+
else if (this._n3Mode && input.length - pos > 1 && input[pos + 1] === '-')
|
|
223
270
|
type = 'inversePredicate', matchLength = 2;
|
|
224
271
|
break;
|
|
225
272
|
|
|
226
273
|
case '>':
|
|
227
274
|
// Try to find a reified triple
|
|
228
|
-
if (input.length > 1 && input[1] === '>')
|
|
275
|
+
if (input.length - pos > 1 && input[pos + 1] === '>')
|
|
229
276
|
type = '>>', matchLength = 2;
|
|
230
277
|
break;
|
|
231
278
|
|
|
@@ -233,8 +280,8 @@ export default class N3Lexer {
|
|
|
233
280
|
// Try to find a blank node. Since it can contain (but not end with) a dot,
|
|
234
281
|
// we always need a non-dot character before deciding it is a blank node.
|
|
235
282
|
// Therefore, try inserting a space if we're at the end of the input.
|
|
236
|
-
if ((match = this._blank
|
|
237
|
-
inputFinished && (match = this._blank
|
|
283
|
+
if ((match = execAt(this._blank, input, pos)) ||
|
|
284
|
+
inputFinished && (match = execAtEnd(this._blank, input, pos))) {
|
|
238
285
|
type = 'blank', prefix = '_', value = match[1];
|
|
239
286
|
lexicalLength = match[1].length + 2;
|
|
240
287
|
}
|
|
@@ -242,13 +289,13 @@ export default class N3Lexer {
|
|
|
242
289
|
|
|
243
290
|
case '"':
|
|
244
291
|
// Try to find a literal without escape sequences
|
|
245
|
-
if (match = this._simpleQuotedString
|
|
292
|
+
if (match = execAt(this._simpleQuotedString, input, pos))
|
|
246
293
|
value = match[1];
|
|
247
294
|
// Try to find a literal wrapped in three pairs of quotes
|
|
248
295
|
else {
|
|
249
|
-
({ value, matchLength, finalLineLength } = this._parseLiteral(input));
|
|
296
|
+
({ value, matchLength, finalLineLength } = this._parseLiteral(input, pos));
|
|
250
297
|
if (value === null)
|
|
251
|
-
return reportSyntaxError(this);
|
|
298
|
+
return reportSyntaxError(this, input, pos);
|
|
252
299
|
}
|
|
253
300
|
if (match !== null || matchLength !== 0) {
|
|
254
301
|
type = 'literal';
|
|
@@ -259,13 +306,13 @@ export default class N3Lexer {
|
|
|
259
306
|
case "'":
|
|
260
307
|
if (!this._lineMode) {
|
|
261
308
|
// Try to find a literal without escape sequences
|
|
262
|
-
if (match = this._simpleApostropheString
|
|
309
|
+
if (match = execAt(this._simpleApostropheString, input, pos))
|
|
263
310
|
value = match[1];
|
|
264
311
|
// Try to find a literal wrapped in three pairs of quotes
|
|
265
312
|
else {
|
|
266
|
-
({ value, matchLength, finalLineLength } = this._parseLiteral(input));
|
|
313
|
+
({ value, matchLength, finalLineLength } = this._parseLiteral(input, pos));
|
|
267
314
|
if (value === null)
|
|
268
|
-
return reportSyntaxError(this);
|
|
315
|
+
return reportSyntaxError(this, input, pos);
|
|
269
316
|
}
|
|
270
317
|
if (match !== null || matchLength !== 0) {
|
|
271
318
|
type = 'literal';
|
|
@@ -276,7 +323,7 @@ export default class N3Lexer {
|
|
|
276
323
|
|
|
277
324
|
case '?':
|
|
278
325
|
// Try to find a variable
|
|
279
|
-
if (this._n3Mode && (match = this._variable
|
|
326
|
+
if (this._n3Mode && (match = execAt(this._variable, input, pos)))
|
|
280
327
|
type = 'var', value = match[0];
|
|
281
328
|
break;
|
|
282
329
|
|
|
@@ -286,20 +333,21 @@ export default class N3Lexer {
|
|
|
286
333
|
// input is not finished, another subtag may still arrive in a later chunk and
|
|
287
334
|
// the match would be premature; wait for more input in that case.
|
|
288
335
|
// A double dash starts a direction code, which cannot extend the language code.
|
|
289
|
-
if (this._previousMarker === 'literal' && (match = this._langcode
|
|
290
|
-
|
|
336
|
+
if (this._previousMarker === 'literal' && (match = execAt(this._langcode, input, pos)) && match[1] !== 'version') {
|
|
337
|
+
const end = pos + match[0].length;
|
|
338
|
+
if (!inputFinished && input[end] === '-' && input[end + 1] !== '-')
|
|
291
339
|
match = null;
|
|
292
340
|
else
|
|
293
341
|
type = 'langcode', value = match[1];
|
|
294
342
|
}
|
|
295
343
|
// Try to find a keyword
|
|
296
|
-
else if (match = this._atKeyword
|
|
344
|
+
else if (match = execAt(this._atKeyword, input, pos))
|
|
297
345
|
type = match[0];
|
|
298
346
|
break;
|
|
299
347
|
|
|
300
348
|
case '.':
|
|
301
349
|
// Try to find a dot as punctuation
|
|
302
|
-
if (input.length === 1 ? inputFinished : (input[1] < '0' || input[1] > '9')) {
|
|
350
|
+
if (input.length - pos === 1 ? inputFinished : (input[pos + 1] < '0' || input[pos + 1] > '9')) {
|
|
303
351
|
type = '.';
|
|
304
352
|
matchLength = 1;
|
|
305
353
|
break;
|
|
@@ -318,12 +366,12 @@ export default class N3Lexer {
|
|
|
318
366
|
case '9':
|
|
319
367
|
case '+':
|
|
320
368
|
case '-':
|
|
321
|
-
if (input[1] === '-') {
|
|
369
|
+
if (input[pos + 1] === '-') {
|
|
322
370
|
// Try to find a direction code
|
|
323
371
|
if (this._previousMarker === 'langcode') {
|
|
324
|
-
if (input.startsWith('--ltr'))
|
|
372
|
+
if (input.startsWith('--ltr', pos))
|
|
325
373
|
type = 'dircode', value = 'ltr', matchLength = 5;
|
|
326
|
-
else if (input.startsWith('--rtl'))
|
|
374
|
+
else if (input.startsWith('--rtl', pos))
|
|
327
375
|
type = 'dircode', value = 'rtl', matchLength = 5;
|
|
328
376
|
}
|
|
329
377
|
break;
|
|
@@ -332,8 +380,8 @@ export default class N3Lexer {
|
|
|
332
380
|
// Try to find a number. Since it can contain (but not end with) a dot,
|
|
333
381
|
// we always need a non-dot character before deciding it is a number.
|
|
334
382
|
// Therefore, try inserting a space if we're at the end of the input.
|
|
335
|
-
if (match = this._number
|
|
336
|
-
inputFinished && (match = this._number
|
|
383
|
+
if (match = execAt(this._number, input, pos) ||
|
|
384
|
+
inputFinished && (match = execAtEnd(this._number, input, pos))) {
|
|
337
385
|
type = 'literal', value = match[0];
|
|
338
386
|
prefix = (typeof match[1] === 'string' ? xsd.double :
|
|
339
387
|
(typeof match[2] === 'string' ? xsd.decimal : xsd.integer));
|
|
@@ -349,7 +397,7 @@ export default class N3Lexer {
|
|
|
349
397
|
case 'V':
|
|
350
398
|
case 'v':
|
|
351
399
|
// Try to find a SPARQL-style keyword
|
|
352
|
-
if (match = this._keyword
|
|
400
|
+
if (match = execAt(this._keyword, input, pos))
|
|
353
401
|
type = match[0].toUpperCase();
|
|
354
402
|
else
|
|
355
403
|
inconclusive = true;
|
|
@@ -358,7 +406,7 @@ export default class N3Lexer {
|
|
|
358
406
|
case 'f':
|
|
359
407
|
case 't':
|
|
360
408
|
// Try to match a boolean
|
|
361
|
-
if (this._boolean
|
|
409
|
+
if (testAt(this._boolean, input, pos))
|
|
362
410
|
type = 'literal', value = firstChar === 't' ? 'true' : 'false', prefix = xsd.boolean, matchLength = value.length;
|
|
363
411
|
else
|
|
364
412
|
inconclusive = true;
|
|
@@ -366,7 +414,7 @@ export default class N3Lexer {
|
|
|
366
414
|
|
|
367
415
|
case 'a':
|
|
368
416
|
// Try to find an abbreviated predicate
|
|
369
|
-
if (this._shortPredicates
|
|
417
|
+
if (testAt(this._shortPredicates, input, pos))
|
|
370
418
|
type = 'abbreviation', value = 'a', matchLength = 1;
|
|
371
419
|
else
|
|
372
420
|
inconclusive = true;
|
|
@@ -375,7 +423,7 @@ export default class N3Lexer {
|
|
|
375
423
|
case 'h':
|
|
376
424
|
case 'o':
|
|
377
425
|
// Try to find an N3 verb keyword
|
|
378
|
-
if (this._n3Mode && (match = this._matchN3Verb(input, inputFinished)))
|
|
426
|
+
if (this._n3Mode && (match = this._matchN3Verb(input, pos, inputFinished)))
|
|
379
427
|
type = match[0];
|
|
380
428
|
else
|
|
381
429
|
inconclusive = true;
|
|
@@ -383,9 +431,9 @@ export default class N3Lexer {
|
|
|
383
431
|
|
|
384
432
|
case 'i':
|
|
385
433
|
// Try to find an IRI property list identifier or N3 verb keyword
|
|
386
|
-
if (this._n3Mode && this._n3Id
|
|
434
|
+
if (this._n3Mode && testAt(this._n3Id, input, pos))
|
|
387
435
|
type = 'id', matchLength = 2;
|
|
388
|
-
else if (this._n3Mode && (match = this._matchN3Verb(input, inputFinished)))
|
|
436
|
+
else if (this._n3Mode && (match = this._matchN3Verb(input, pos, inputFinished)))
|
|
389
437
|
type = match[0];
|
|
390
438
|
else
|
|
391
439
|
inconclusive = true;
|
|
@@ -393,9 +441,9 @@ export default class N3Lexer {
|
|
|
393
441
|
|
|
394
442
|
case '=':
|
|
395
443
|
// Try to find an implication arrow or equals sign
|
|
396
|
-
if (this._n3Mode && input.length > 1) {
|
|
444
|
+
if (this._n3Mode && input.length - pos > 1) {
|
|
397
445
|
type = 'abbreviation';
|
|
398
|
-
if (input[1] !== '>')
|
|
446
|
+
if (input[pos + 1] !== '>')
|
|
399
447
|
matchLength = 1, value = '=';
|
|
400
448
|
else
|
|
401
449
|
matchLength = 2, value = '>';
|
|
@@ -406,12 +454,12 @@ export default class N3Lexer {
|
|
|
406
454
|
if (!this._n3Mode)
|
|
407
455
|
break;
|
|
408
456
|
case ')':
|
|
409
|
-
if (!inputFinished && (input.length === 1 || (input.length === 2 && input[1] === '>'))) {
|
|
457
|
+
if (!inputFinished && (input.length - pos === 1 || (input.length - pos === 2 && input[pos + 1] === '>'))) {
|
|
410
458
|
// Don't consume yet, as it *could* become a triple term end.
|
|
411
459
|
break;
|
|
412
460
|
}
|
|
413
461
|
// Try to find a triple term
|
|
414
|
-
if (input.length > 2 && input[1] === '>' && input[2] === '>') {
|
|
462
|
+
if (input.length - pos > 2 && input[pos + 1] === '>' && input[pos + 2] === '>') {
|
|
415
463
|
type = ')>>', matchLength = 3;
|
|
416
464
|
break;
|
|
417
465
|
}
|
|
@@ -429,9 +477,9 @@ export default class N3Lexer {
|
|
|
429
477
|
break;
|
|
430
478
|
case '{':
|
|
431
479
|
// We need at least 2 tokens lookahead to distinguish "{|" and "{ "
|
|
432
|
-
if (!this._lineMode && input.length >= 2) {
|
|
480
|
+
if (!this._lineMode && input.length - pos >= 2) {
|
|
433
481
|
// Try to find a quoted triple annotation start
|
|
434
|
-
if (input[1] === '|')
|
|
482
|
+
if (input[pos + 1] === '|')
|
|
435
483
|
type = '{|', matchLength = 2;
|
|
436
484
|
else
|
|
437
485
|
type = firstChar, matchLength = 1;
|
|
@@ -440,7 +488,7 @@ export default class N3Lexer {
|
|
|
440
488
|
case '|':
|
|
441
489
|
// We need 2 tokens lookahead to parse "|}"
|
|
442
490
|
// Try to find a quoted triple annotation end
|
|
443
|
-
if (input.length >= 2 && input[1] === '}')
|
|
491
|
+
if (input.length - pos >= 2 && input[pos + 1] === '}')
|
|
444
492
|
type = '|}', matchLength = 2;
|
|
445
493
|
break;
|
|
446
494
|
|
|
@@ -452,13 +500,20 @@ export default class N3Lexer {
|
|
|
452
500
|
if (inconclusive) {
|
|
453
501
|
// Try to find a prefix
|
|
454
502
|
if ((this._previousMarker === '@prefix' || this._previousMarker === 'PREFIX') &&
|
|
455
|
-
(match = this._prefix
|
|
503
|
+
(match = execAt(this._prefix, input, pos)))
|
|
456
504
|
type = 'prefix', value = match[1] || '';
|
|
505
|
+
// Try to find an additional directive keyword
|
|
506
|
+
// (at the end of the input, only a short final word can be one)
|
|
507
|
+
else if (this._directive !== null && this._directiveStarts.includes(firstChar) &&
|
|
508
|
+
((match = execAt(this._directive, input, pos)) ||
|
|
509
|
+
inputFinished && input.length - pos <= this._directiveMaxLength &&
|
|
510
|
+
(match = execAtEnd(this._directive, input, pos))))
|
|
511
|
+
type = match[0].toUpperCase();
|
|
457
512
|
// Try to find a prefixed name. Since it can contain (but not end with) a dot,
|
|
458
513
|
// we always need a non-dot character before deciding it is a prefixed name.
|
|
459
514
|
// Therefore, try inserting a space if we're at the end of the input.
|
|
460
|
-
else if ((match = this._prefixed
|
|
461
|
-
inputFinished && (match = this._prefixed
|
|
515
|
+
else if ((match = execAt(this._prefixed, input, pos)) ||
|
|
516
|
+
inputFinished && (match = execAtEnd(this._prefixed, input, pos))) {
|
|
462
517
|
type = 'prefixed', prefix = match[1] || '';
|
|
463
518
|
value = this._unescape(match[2], localNameEscapeReplacements);
|
|
464
519
|
lexicalLength = prefix.length + match[2].length + 1;
|
|
@@ -479,86 +534,91 @@ export default class N3Lexer {
|
|
|
479
534
|
// We could be in streaming mode, and then we just wait for more input to arrive.
|
|
480
535
|
// Otherwise, a syntax error has occurred in the input.
|
|
481
536
|
// One exception: error on an unaccounted linebreak (= not inside a triple-quoted literal).
|
|
482
|
-
if (inputFinished || (
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
return this.
|
|
487
|
-
}
|
|
537
|
+
if (inputFinished || (!input.startsWith("'''", pos) && !input.startsWith('"""', pos) &&
|
|
538
|
+
/\n|\r/.test(input.slice(pos))))
|
|
539
|
+
return reportSyntaxError(this, input, pos);
|
|
540
|
+
else
|
|
541
|
+
return this._suspend(input, pos, currentLineLength);
|
|
488
542
|
}
|
|
489
543
|
|
|
490
544
|
// Emit the parsed token
|
|
491
545
|
// Consumption includes separator whitespace; lexicalLength excludes it
|
|
492
|
-
// and any synthetic EOF space.
|
|
546
|
+
// and any synthetic EOF space. Consumption is clamped to the input below.
|
|
493
547
|
const length = matchLength || match[0].length;
|
|
548
|
+
const start = currentLineLength - (input.length - pos);
|
|
494
549
|
let token;
|
|
495
550
|
if (finalLineLength) {
|
|
496
551
|
token = {
|
|
497
|
-
type, value, prefix, line,
|
|
498
|
-
start: currentLineLength - input.length,
|
|
552
|
+
type, value, prefix, line, start,
|
|
499
553
|
end: finalLineLength, endLine: this._line,
|
|
500
554
|
};
|
|
501
555
|
callback(null, token);
|
|
502
556
|
}
|
|
503
557
|
else
|
|
504
|
-
token = emitToken(type, value, prefix, line, lexicalLength || length);
|
|
558
|
+
token = emitToken(type, value, prefix, line, start, lexicalLength || length);
|
|
505
559
|
this.previousToken = token;
|
|
506
|
-
|
|
560
|
+
// The string of a version declaration cannot take a language tag, so a following @keyword is a keyword
|
|
561
|
+
this._previousMarker = type === 'literal' && (this._previousMarker === 'VERSION' || this._previousMarker === '@version') ? 'version' : type;
|
|
507
562
|
|
|
508
563
|
// Advance to next part to tokenize
|
|
509
|
-
|
|
564
|
+
pos = Math.min(pos + length, input.length);
|
|
510
565
|
if (finalLineLength)
|
|
511
|
-
currentLineLength = input.length + finalLineLength;
|
|
566
|
+
currentLineLength = input.length - pos + finalLineLength;
|
|
512
567
|
}
|
|
513
568
|
|
|
514
569
|
// Emits a comment at its exact position within matched whitespace.
|
|
515
|
-
function emitComment(value, line,
|
|
516
|
-
const start = currentLineLength - input.length + offset;
|
|
570
|
+
function emitComment(value, line, start) {
|
|
517
571
|
callback(null, {
|
|
518
572
|
type: 'comment', value, prefix: '', line,
|
|
519
573
|
start, end: start + value.length + 1,
|
|
520
574
|
});
|
|
521
575
|
}
|
|
522
576
|
// Emits the token through the callback
|
|
523
|
-
function emitToken(type, value, prefix, line, length) {
|
|
524
|
-
const
|
|
525
|
-
const end = start + length;
|
|
526
|
-
const token = { type, value, prefix, line, start, end };
|
|
577
|
+
function emitToken(type, value, prefix, line, start, length) {
|
|
578
|
+
const token = { type, value, prefix, line, start, end: start + length };
|
|
527
579
|
callback(null, token);
|
|
528
580
|
return token;
|
|
529
581
|
}
|
|
530
582
|
// Signals the syntax error through the callback
|
|
531
|
-
function reportSyntaxError(self
|
|
583
|
+
function reportSyntaxError(self, input, pos) {
|
|
584
|
+
callback(self._syntaxError(execAt(nonWhitespace, input, pos)[0]));
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// ### `_suspend` keeps the unconsumed input until more input arrives
|
|
589
|
+
_suspend(input, pos, currentLineLength) {
|
|
590
|
+
this._linePosition = currentLineLength - (input.length - pos);
|
|
591
|
+
return this._input = input.slice(pos);
|
|
532
592
|
}
|
|
533
593
|
|
|
534
594
|
// ### `_matchN3Verb` matches an N3 verb unless the input is a longer prefixed name
|
|
535
|
-
_matchN3Verb(input, inputFinished) {
|
|
536
|
-
const verb = this._n3Verb
|
|
595
|
+
_matchN3Verb(input, pos, inputFinished) {
|
|
596
|
+
const verb = execAt(this._n3Verb, input, pos);
|
|
537
597
|
if (!verb)
|
|
538
598
|
return null;
|
|
539
599
|
|
|
540
600
|
// Most verb boundaries cannot be part of a prefix, so keep the common path fast.
|
|
541
|
-
const next = input[verb[0].length];
|
|
601
|
+
const next = input[pos + verb[0].length];
|
|
542
602
|
if (next !== '-' && next !== '_' && (next < '0' || next > '9'))
|
|
543
603
|
return verb;
|
|
544
604
|
|
|
545
605
|
// A prefix can start with a verb and continue with characters that are also
|
|
546
606
|
// valid verb boundaries. Prefer the longer prefixed name when it is complete.
|
|
547
|
-
if (this._prefixed
|
|
607
|
+
if (execAt(this._prefixed, input, pos))
|
|
548
608
|
return null;
|
|
549
609
|
// Appending to the input only matters when a prefixed name could run up to
|
|
550
610
|
// its end, which a character that cannot occur in prefixed names rules out.
|
|
551
611
|
// This avoids copying the rest of the document for every such verb.
|
|
552
|
-
if (nonPrefixedNameChar
|
|
612
|
+
if (testAt(nonPrefixedNameChar, input, pos))
|
|
553
613
|
return verb;
|
|
554
|
-
if (this._prefixed
|
|
614
|
+
if (execAtEnd(this._prefixed, input, pos))
|
|
555
615
|
return null;
|
|
556
616
|
|
|
557
617
|
// If a stream chunk ends partway through such a prefix, wait for the colon
|
|
558
618
|
// instead of prematurely emitting the verb. Appending ": " lets the prefix
|
|
559
619
|
// grammar determine whether all input seen so far can be a complete prefix.
|
|
560
620
|
if (!inputFinished) {
|
|
561
|
-
const prefix = this._prefix
|
|
621
|
+
const prefix = execAt(this._prefix, `${input.slice(pos)}: `, 0);
|
|
562
622
|
if (prefix)
|
|
563
623
|
return null;
|
|
564
624
|
}
|
|
@@ -612,20 +672,20 @@ export default class N3Lexer {
|
|
|
612
672
|
return result + item.slice(start);
|
|
613
673
|
}
|
|
614
674
|
|
|
615
|
-
// ### `_parseLiteral` parses a literal into an unescaped value
|
|
616
|
-
_parseLiteral(input) {
|
|
675
|
+
// ### `_parseLiteral` parses a literal at the given position into an unescaped value
|
|
676
|
+
_parseLiteral(input, pos) {
|
|
617
677
|
// Ensure we have enough lookahead to identify triple-quoted strings
|
|
618
|
-
if (input.length >= 3) {
|
|
678
|
+
if (input.length - pos >= 3) {
|
|
619
679
|
// The caller has already identified a single or double quote.
|
|
620
|
-
const quote = input[
|
|
621
|
-
const openingLength = input[1] === quote && input[2] === quote ? 3 : 1;
|
|
680
|
+
const quote = input[pos];
|
|
681
|
+
const openingLength = input[pos + 1] === quote && input[pos + 2] === quote ? 3 : 1;
|
|
622
682
|
let opening = quote;
|
|
623
683
|
if (openingLength === 3)
|
|
624
684
|
opening = quote === '"' ? '"""' : "'''";
|
|
625
685
|
|
|
626
686
|
// Find the next candidate closing quotes
|
|
627
|
-
let closingPos = Math.max(this._literalClosingPos, openingLength);
|
|
628
|
-
while ((closingPos = input.indexOf(opening, closingPos)) >
|
|
687
|
+
let closingPos = pos + Math.max(this._literalClosingPos, openingLength);
|
|
688
|
+
while ((closingPos = input.indexOf(opening, closingPos)) > pos) {
|
|
629
689
|
// Count backslashes right before the closing quotes
|
|
630
690
|
let backslashCount = 0;
|
|
631
691
|
while (input[closingPos - backslashCount - 1] === '\\')
|
|
@@ -635,10 +695,10 @@ export default class N3Lexer {
|
|
|
635
695
|
// means these are actual, non-escaped closing quotes
|
|
636
696
|
if (backslashCount % 2 === 0) {
|
|
637
697
|
// Extract and unescape the value
|
|
638
|
-
const raw = input.substring(openingLength, closingPos),
|
|
698
|
+
const raw = input.substring(pos + openingLength, closingPos),
|
|
639
699
|
lines = raw.split(/\r\n|\r|\n/),
|
|
640
700
|
lineCount = lines.length - 1;
|
|
641
|
-
const matchLength = closingPos + openingLength;
|
|
701
|
+
const matchLength = closingPos - pos + openingLength;
|
|
642
702
|
// Only triple-quoted strings can be multi-line
|
|
643
703
|
if (openingLength === 1 && lineCount !== 0 ||
|
|
644
704
|
openingLength === 3 && this._lineMode)
|
|
@@ -649,7 +709,7 @@ export default class N3Lexer {
|
|
|
649
709
|
}
|
|
650
710
|
closingPos++;
|
|
651
711
|
}
|
|
652
|
-
this._literalClosingPos = input.length - openingLength + 1;
|
|
712
|
+
this._literalClosingPos = input.length - pos - openingLength + 1;
|
|
653
713
|
}
|
|
654
714
|
return { value: '', matchLength: 0, finalLineLength: 0 };
|
|
655
715
|
}
|
|
@@ -713,36 +773,42 @@ export default class N3Lexer {
|
|
|
713
773
|
}
|
|
714
774
|
// Otherwise, the input must be a stream
|
|
715
775
|
else {
|
|
716
|
-
|
|
776
|
+
let decoder, retryLength = 0;
|
|
717
777
|
if (typeof input.setEncoding === 'function')
|
|
718
778
|
input.setEncoding('utf8');
|
|
719
779
|
// Adds the data chunk to the buffer and parses as far as possible
|
|
720
780
|
input.on('data', data => {
|
|
721
781
|
if (this._tokenization === tokenization && this._input !== null && data.length !== 0) {
|
|
722
|
-
//
|
|
723
|
-
if (
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
// Hold if the buffer ends in an incomplete unicode sequence
|
|
728
|
-
if (data[data.length - 1] & 0x80) {
|
|
729
|
-
this._pendingBuffer = data;
|
|
782
|
+
// Decode bytes, keeping an incomplete trailing character for the next chunk
|
|
783
|
+
if (typeof data !== 'string') {
|
|
784
|
+
decoder = decoder || new TextDecoder('utf-8', { ignoreBOM: true });
|
|
785
|
+
if (!(data = decoder.decode(data, { stream: true })))
|
|
786
|
+
return;
|
|
730
787
|
}
|
|
731
|
-
//
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
788
|
+
// Only read a BOM at the start
|
|
789
|
+
if (typeof this._input === 'undefined')
|
|
790
|
+
this._input = this._readStartingBom(data);
|
|
791
|
+
else
|
|
792
|
+
this._input += data;
|
|
793
|
+
// Tokenize as far as possible. When a previous attempt left a long unfinished token,
|
|
794
|
+
// wait until the buffered input has doubled, so the token is not rescanned for every chunk.
|
|
795
|
+
if (this._input.length >= retryLength) {
|
|
738
796
|
this._tokenizeToEnd(callback, false);
|
|
797
|
+
retryLength = this._input !== null && this._input.length > MIN_RESCAN_LENGTH ?
|
|
798
|
+
2 * this._input.length : 0;
|
|
739
799
|
}
|
|
740
800
|
}
|
|
741
801
|
});
|
|
742
802
|
// Parses until the end
|
|
743
803
|
input.on('end', () => {
|
|
744
|
-
if (this._tokenization === tokenization &&
|
|
745
|
-
|
|
804
|
+
if (this._tokenization === tokenization && this._input !== null) {
|
|
805
|
+
// Decode any incomplete character left at the end
|
|
806
|
+
const rest = decoder ? decoder.decode() : '';
|
|
807
|
+
if (rest)
|
|
808
|
+
this._input = typeof this._input === 'string' ? this._input + rest : rest;
|
|
809
|
+
if (typeof this._input === 'string')
|
|
810
|
+
this._tokenizeToEnd(callback, true);
|
|
811
|
+
}
|
|
746
812
|
});
|
|
747
813
|
input.on('error', error => {
|
|
748
814
|
if (this._tokenization === tokenization)
|