n3 2.11.1 → 2.11.3
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 +7 -0
- package/browser/n3.esm.min.js +13 -13
- package/browser/n3.min.js +13 -13
- package/lib/N3Lexer.js +148 -133
- package/lib/N3Parser.js +24 -18
- package/lib/N3Writer.js +8 -5
- package/lib/Util.js +1 -1
- package/package.json +2 -29
- package/src/N3Lexer.js +162 -142
- package/src/N3Parser.js +26 -18
- package/src/N3Writer.js +8 -5
- package/src/Util.js +1 -1
package/lib/N3Lexer.js
CHANGED
|
@@ -54,7 +54,8 @@ const localNameEscapeReplacements = {
|
|
|
54
54
|
};
|
|
55
55
|
const illegalIriChars = /[\x00-\x20<>\\"\{\}\|\^\`]/;
|
|
56
56
|
// Characters that cannot occur in a prefixed name, not even escaped
|
|
57
|
-
|
|
57
|
+
// (global, so that testAt searches the rest of the input from a position)
|
|
58
|
+
const nonPrefixedNameChar = /[\s<>"{}|^`]/g;
|
|
58
59
|
|
|
59
60
|
// A valid code point is a Unicode scalar value: at most U+10FFFF and not a surrogate
|
|
60
61
|
function isValidCodePoint(charCode) {
|
|
@@ -70,30 +71,48 @@ const lineModeRegExps = {
|
|
|
70
71
|
_whitespace: true
|
|
71
72
|
};
|
|
72
73
|
const invalidRegExp = /$0^/;
|
|
74
|
+
const nonWhitespace = /\S*/y;
|
|
75
|
+
|
|
76
|
+
// Matches a sticky regular expression at the given position of the input
|
|
77
|
+
function execAt(regExp, input, pos) {
|
|
78
|
+
regExp.lastIndex = pos;
|
|
79
|
+
return regExp.exec(input);
|
|
80
|
+
}
|
|
81
|
+
function testAt(regExp, input, pos) {
|
|
82
|
+
regExp.lastIndex = pos;
|
|
83
|
+
return regExp.test(input);
|
|
84
|
+
}
|
|
85
|
+
// Matches the rest of the input followed by a space, as at the end of the input,
|
|
86
|
+
// a token that can contain (but not end with) a dot needs a non-dot character after it
|
|
87
|
+
function execAtEnd(regExp, input, pos) {
|
|
88
|
+
regExp.lastIndex = 0;
|
|
89
|
+
return regExp.exec(`${input.slice(pos)} `);
|
|
90
|
+
}
|
|
73
91
|
|
|
74
92
|
// ## Constructor
|
|
75
93
|
class N3Lexer {
|
|
76
94
|
constructor(options) {
|
|
77
95
|
// ## Regular expressions
|
|
78
|
-
// It's slightly faster to have these as properties than as in-scope variables
|
|
79
|
-
|
|
80
|
-
this.
|
|
81
|
-
this.
|
|
82
|
-
this.
|
|
83
|
-
this.
|
|
84
|
-
this.
|
|
85
|
-
this.
|
|
86
|
-
this.
|
|
87
|
-
this.
|
|
88
|
-
this.
|
|
89
|
-
this.
|
|
90
|
-
this.
|
|
91
|
-
this.
|
|
92
|
-
this.
|
|
93
|
-
this.
|
|
94
|
-
this.
|
|
95
|
-
this.
|
|
96
|
-
this.
|
|
96
|
+
// It's slightly faster to have these as properties than as in-scope variables.
|
|
97
|
+
// They are sticky, so they only match at the `lastIndex` set by `execAt`.
|
|
98
|
+
this._iri = /<((?:[^ <>{}\\]|\\[uU])+)>[ \t]*/y; // IRI with escape sequences; needs sanity check after unescaping
|
|
99
|
+
this._unescapedIri = /<([^\x00-\x20<>\\"\{\}\|\^\`]*)>[ \t]*/y; // IRI without escape sequences; no unescaping
|
|
100
|
+
this._simpleQuotedString = /"([^"\\\r\n]*)"(?=[^"])/y; // string without escape sequences
|
|
101
|
+
this._simpleApostropheString = /'([^'\\\r\n]*)'(?=[^'])/y;
|
|
102
|
+
this._langcode = /@([a-z]+(?:-[a-z0-9]+)*)(?=[^a-z0-9])/iy;
|
|
103
|
+
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;
|
|
104
|
+
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;
|
|
105
|
+
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;
|
|
106
|
+
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;
|
|
107
|
+
this._number = /[\-+]?(?:(\d+\.\d*|\.?\d+)[eE][\-+]?\d+|(?=\.?\d)\d*(?:(\.)\d+)?)(?=\.?[,;:!\^\s#()\[\]\{\}"'<>])/y;
|
|
108
|
+
this._boolean = /(?:true|false)(?=[.,;!\^\s#()\[\]\{\}"'<>])/y;
|
|
109
|
+
this._atKeyword = /@[a-z]+(?=[\s#<:])/iy;
|
|
110
|
+
this._keyword = /(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<])/iy;
|
|
111
|
+
this._n3Verb = /(?:has|is|of)(?=[\s#()\[\]\{\}"'<>?_+\-0-9])/y;
|
|
112
|
+
this._n3Id = /id(?=[\s#<])/y;
|
|
113
|
+
this._shortPredicates = /a(?=[\s#()\[\]\{\}"'<>])/y;
|
|
114
|
+
this._commentLine = /[ \t]*#([^\n\r]*)(?:\r\n|\n|\r)([ \t]*)/y;
|
|
115
|
+
this._whitespace = /[ \t]+/y;
|
|
97
116
|
options = options || {};
|
|
98
117
|
|
|
99
118
|
// Whether the log:isImpliedBy predicate is supported
|
|
@@ -121,77 +140,71 @@ class N3Lexer {
|
|
|
121
140
|
|
|
122
141
|
// ### `_tokenizeToEnd` tokenizes as for as possible, emitting tokens through the callback
|
|
123
142
|
_tokenizeToEnd(callback, inputFinished) {
|
|
124
|
-
// Continue parsing as far as possible; the loop will return eventually
|
|
125
|
-
|
|
143
|
+
// Continue parsing as far as possible; the loop will return eventually.
|
|
144
|
+
// Rather than slicing off every token, track the position of the remaining input;
|
|
145
|
+
// the regular expressions are sticky, so they match at that position.
|
|
146
|
+
const input = this._input;
|
|
147
|
+
let pos = 0;
|
|
126
148
|
let currentLineLength = this._linePosition + input.length;
|
|
127
149
|
while (true) {
|
|
128
150
|
// Consume one separator line at a time, including its following indentation.
|
|
129
151
|
while (true) {
|
|
130
|
-
let charCode = input.charCodeAt(
|
|
152
|
+
let charCode = input.charCodeAt(pos),
|
|
131
153
|
separatorLength = 0;
|
|
132
154
|
if (charCode === SPACE || charCode === TAB) {
|
|
133
|
-
const next = input.charCodeAt(1);
|
|
134
|
-
separatorLength = next === SPACE || next === TAB ? this._whitespace
|
|
135
|
-
charCode = input.charCodeAt(separatorLength);
|
|
155
|
+
const next = input.charCodeAt(pos + 1);
|
|
156
|
+
separatorLength = next === SPACE || next === TAB ? execAt(this._whitespace, input, pos)[0].length : 1;
|
|
157
|
+
charCode = input.charCodeAt(pos + separatorLength);
|
|
136
158
|
}
|
|
137
159
|
if (charCode === HASH) {
|
|
138
|
-
const comment = this._commentLine
|
|
160
|
+
const comment = execAt(this._commentLine, input, pos);
|
|
139
161
|
if (comment) {
|
|
140
162
|
const commentLength = comment[0].length;
|
|
141
163
|
// Keep a trailing CR buffered in case the next chunk starts with LF.
|
|
142
|
-
if (!inputFinished && commentLength === input.length && input.charCodeAt(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (this.comments) emitComment(comment[1], this._line, separatorLength);
|
|
147
|
-
input = input.slice(commentLength);
|
|
148
|
-
currentLineLength = input.length + comment[2].length;
|
|
164
|
+
if (!inputFinished && pos + commentLength === input.length && input.charCodeAt(input.length - 1) === CR) return this._suspend(input, pos, currentLineLength);
|
|
165
|
+
if (this.comments) emitComment(comment[1], this._line, currentLineLength - (input.length - pos) + separatorLength);
|
|
166
|
+
pos += commentLength;
|
|
167
|
+
currentLineLength = input.length - pos + comment[2].length;
|
|
149
168
|
this._line++;
|
|
150
169
|
} else {
|
|
151
170
|
// A comment without a line ending stays buffered until EOF.
|
|
152
|
-
|
|
153
|
-
if (!inputFinished)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
if (this.comments) emitComment(input.slice(1), this._line, 0);
|
|
158
|
-
input = '';
|
|
171
|
+
pos += separatorLength;
|
|
172
|
+
if (!inputFinished) return this._suspend(input, pos, currentLineLength);
|
|
173
|
+
if (this.comments) emitComment(input.slice(pos + 1), this._line, currentLineLength - (input.length - pos));
|
|
174
|
+
pos = input.length;
|
|
159
175
|
break;
|
|
160
176
|
}
|
|
161
177
|
} else if (charCode === LF || charCode === CR) {
|
|
162
178
|
// A CR at the end of a chunk may still be followed by LF.
|
|
163
|
-
if (!inputFinished && charCode === CR && separatorLength + 1 === input.length)
|
|
164
|
-
|
|
165
|
-
return this._input = input;
|
|
166
|
-
}
|
|
167
|
-
separatorLength += charCode === CR && input.charCodeAt(separatorLength + 1) === LF ? 2 : 1;
|
|
179
|
+
if (!inputFinished && charCode === CR && pos + separatorLength + 1 === input.length) return this._suspend(input, pos, currentLineLength);
|
|
180
|
+
separatorLength += charCode === CR && input.charCodeAt(pos + separatorLength + 1) === LF ? 2 : 1;
|
|
168
181
|
// Indentation is consumed with the newline, but belongs to the next line's columns.
|
|
169
182
|
let indentationLength = 0;
|
|
170
|
-
const next = input.charCodeAt(separatorLength);
|
|
183
|
+
const next = input.charCodeAt(pos + separatorLength);
|
|
171
184
|
if (next === SPACE || next === TAB) {
|
|
172
|
-
const following = input.charCodeAt(separatorLength + 1);
|
|
173
|
-
indentationLength = following === SPACE || following === TAB ? this._whitespace
|
|
185
|
+
const following = input.charCodeAt(pos + separatorLength + 1);
|
|
186
|
+
indentationLength = following === SPACE || following === TAB ? execAt(this._whitespace, input, pos + separatorLength)[0].length : 1;
|
|
174
187
|
}
|
|
175
|
-
|
|
176
|
-
currentLineLength = input.length + indentationLength;
|
|
188
|
+
pos += separatorLength + indentationLength;
|
|
189
|
+
currentLineLength = input.length - pos + indentationLength;
|
|
177
190
|
this._line++;
|
|
178
191
|
} else {
|
|
179
|
-
|
|
192
|
+
pos += separatorLength;
|
|
180
193
|
break;
|
|
181
194
|
}
|
|
182
195
|
}
|
|
183
|
-
if (input.length
|
|
196
|
+
if (pos >= input.length) {
|
|
197
|
+
this._linePosition = currentLineLength;
|
|
184
198
|
if (inputFinished) {
|
|
185
|
-
|
|
186
|
-
|
|
199
|
+
emitToken('eof', '', '', this._line, currentLineLength, 0);
|
|
200
|
+
return this._input = null;
|
|
187
201
|
}
|
|
188
|
-
this.
|
|
189
|
-
return this._input = input;
|
|
202
|
+
return this._input = '';
|
|
190
203
|
}
|
|
191
204
|
|
|
192
205
|
// Look for specific token types based on the first character
|
|
193
206
|
const line = this._line,
|
|
194
|
-
firstChar = input[
|
|
207
|
+
firstChar = input[pos];
|
|
195
208
|
let type = '',
|
|
196
209
|
value = '',
|
|
197
210
|
prefix = '',
|
|
@@ -203,13 +216,13 @@ class N3Lexer {
|
|
|
203
216
|
switch (firstChar) {
|
|
204
217
|
case '^':
|
|
205
218
|
// We need at least 3 tokens lookahead to distinguish ^^<IRI> and ^^pre:fixed
|
|
206
|
-
if (input.length < 3) break;
|
|
219
|
+
if (input.length - pos < 3) break;
|
|
207
220
|
// Try to match a type
|
|
208
|
-
else if (input[1] === '^') {
|
|
221
|
+
else if (input[pos + 1] === '^') {
|
|
209
222
|
this._previousMarker = '^^';
|
|
210
223
|
// Move to type IRI or prefixed name
|
|
211
|
-
|
|
212
|
-
if (input[
|
|
224
|
+
pos += 2;
|
|
225
|
+
if (input[pos] !== '<') {
|
|
213
226
|
inconclusive = true;
|
|
214
227
|
break;
|
|
215
228
|
}
|
|
@@ -225,53 +238,53 @@ class N3Lexer {
|
|
|
225
238
|
// Fall through in case the type is an IRI
|
|
226
239
|
case '<':
|
|
227
240
|
// Try to find a full IRI without escape sequences
|
|
228
|
-
if (match = this._unescapedIri
|
|
241
|
+
if (match = execAt(this._unescapedIri, input, pos)) {
|
|
229
242
|
type = 'IRI', value = match[1];
|
|
230
243
|
lexicalLength = match[1].length + 2;
|
|
231
244
|
}
|
|
232
245
|
// Try to find a full IRI with escape sequences
|
|
233
|
-
else if (match = this._iri
|
|
246
|
+
else if (match = execAt(this._iri, input, pos)) {
|
|
234
247
|
value = this._unescape(match[1], stringEscapeReplacements);
|
|
235
|
-
if (value === null || illegalIriChars.test(value)) return reportSyntaxError(this);
|
|
248
|
+
if (value === null || illegalIriChars.test(value)) return reportSyntaxError(this, input, pos);
|
|
236
249
|
type = 'IRI';
|
|
237
250
|
lexicalLength = match[1].length + 2;
|
|
238
251
|
}
|
|
239
252
|
// Try to find a triple term
|
|
240
|
-
else if (input.length > 2 && input[1] === '<' && input[2] === '(') type = '<<(', matchLength = 3;
|
|
253
|
+
else if (input.length - pos > 2 && input[pos + 1] === '<' && input[pos + 2] === '(') type = '<<(', matchLength = 3;
|
|
241
254
|
// Try to find a reified triple
|
|
242
|
-
else if (!this._lineMode && input.length > (inputFinished ? 1 : 2) && input[1] === '<') type = '<<', matchLength = 2;
|
|
255
|
+
else if (!this._lineMode && input.length - pos > (inputFinished ? 1 : 2) && input[pos + 1] === '<') type = '<<', matchLength = 2;
|
|
243
256
|
// Try to find a backwards implication arrow
|
|
244
|
-
else if (this._n3Mode && input.length > 1 && input[1] === '=') {
|
|
257
|
+
else if (this._n3Mode && input.length - pos > 1 && input[pos + 1] === '=') {
|
|
245
258
|
matchLength = 2;
|
|
246
259
|
if (this._isImpliedBy) type = 'abbreviation', value = '<';else type = 'inverse', value = '>';
|
|
247
260
|
}
|
|
248
261
|
// Try to find an inverted predicate marker
|
|
249
|
-
else if (this._n3Mode && input.length > 1 && input[1] === '-') type = 'inversePredicate', matchLength = 2;
|
|
262
|
+
else if (this._n3Mode && input.length - pos > 1 && input[pos + 1] === '-') type = 'inversePredicate', matchLength = 2;
|
|
250
263
|
break;
|
|
251
264
|
case '>':
|
|
252
265
|
// Try to find a reified triple
|
|
253
|
-
if (input.length > 1 && input[1] === '>') type = '>>', matchLength = 2;
|
|
266
|
+
if (input.length - pos > 1 && input[pos + 1] === '>') type = '>>', matchLength = 2;
|
|
254
267
|
break;
|
|
255
268
|
case '_':
|
|
256
269
|
// Try to find a blank node. Since it can contain (but not end with) a dot,
|
|
257
270
|
// we always need a non-dot character before deciding it is a blank node.
|
|
258
271
|
// Therefore, try inserting a space if we're at the end of the input.
|
|
259
|
-
if ((match = this._blank
|
|
272
|
+
if ((match = execAt(this._blank, input, pos)) || inputFinished && (match = execAtEnd(this._blank, input, pos))) {
|
|
260
273
|
type = 'blank', prefix = '_', value = match[1];
|
|
261
274
|
lexicalLength = match[1].length + 2;
|
|
262
275
|
}
|
|
263
276
|
break;
|
|
264
277
|
case '"':
|
|
265
278
|
// Try to find a literal without escape sequences
|
|
266
|
-
if (match = this._simpleQuotedString
|
|
279
|
+
if (match = execAt(this._simpleQuotedString, input, pos)) value = match[1];
|
|
267
280
|
// Try to find a literal wrapped in three pairs of quotes
|
|
268
281
|
else {
|
|
269
282
|
({
|
|
270
283
|
value,
|
|
271
284
|
matchLength,
|
|
272
285
|
finalLineLength
|
|
273
|
-
} = this._parseLiteral(input));
|
|
274
|
-
if (value === null) return reportSyntaxError(this);
|
|
286
|
+
} = this._parseLiteral(input, pos));
|
|
287
|
+
if (value === null) return reportSyntaxError(this, input, pos);
|
|
275
288
|
}
|
|
276
289
|
if (match !== null || matchLength !== 0) {
|
|
277
290
|
type = 'literal';
|
|
@@ -281,15 +294,15 @@ class N3Lexer {
|
|
|
281
294
|
case "'":
|
|
282
295
|
if (!this._lineMode) {
|
|
283
296
|
// Try to find a literal without escape sequences
|
|
284
|
-
if (match = this._simpleApostropheString
|
|
297
|
+
if (match = execAt(this._simpleApostropheString, input, pos)) value = match[1];
|
|
285
298
|
// Try to find a literal wrapped in three pairs of quotes
|
|
286
299
|
else {
|
|
287
300
|
({
|
|
288
301
|
value,
|
|
289
302
|
matchLength,
|
|
290
303
|
finalLineLength
|
|
291
|
-
} = this._parseLiteral(input));
|
|
292
|
-
if (value === null) return reportSyntaxError(this);
|
|
304
|
+
} = this._parseLiteral(input, pos));
|
|
305
|
+
if (value === null) return reportSyntaxError(this, input, pos);
|
|
293
306
|
}
|
|
294
307
|
if (match !== null || matchLength !== 0) {
|
|
295
308
|
type = 'literal';
|
|
@@ -299,7 +312,7 @@ class N3Lexer {
|
|
|
299
312
|
break;
|
|
300
313
|
case '?':
|
|
301
314
|
// Try to find a variable
|
|
302
|
-
if (this._n3Mode && (match = this._variable
|
|
315
|
+
if (this._n3Mode && (match = execAt(this._variable, input, pos))) type = 'var', value = match[0];
|
|
303
316
|
break;
|
|
304
317
|
case '@':
|
|
305
318
|
// Try to find a language code. A language code can contain dash-separated
|
|
@@ -307,15 +320,16 @@ class N3Lexer {
|
|
|
307
320
|
// input is not finished, another subtag may still arrive in a later chunk and
|
|
308
321
|
// the match would be premature; wait for more input in that case.
|
|
309
322
|
// A double dash starts a direction code, which cannot extend the language code.
|
|
310
|
-
if (this._previousMarker === 'literal' && (match = this._langcode
|
|
311
|
-
|
|
323
|
+
if (this._previousMarker === 'literal' && (match = execAt(this._langcode, input, pos)) && match[1] !== 'version') {
|
|
324
|
+
const end = pos + match[0].length;
|
|
325
|
+
if (!inputFinished && input[end] === '-' && input[end + 1] !== '-') match = null;else type = 'langcode', value = match[1];
|
|
312
326
|
}
|
|
313
327
|
// Try to find a keyword
|
|
314
|
-
else if (match = this._atKeyword
|
|
328
|
+
else if (match = execAt(this._atKeyword, input, pos)) type = match[0];
|
|
315
329
|
break;
|
|
316
330
|
case '.':
|
|
317
331
|
// Try to find a dot as punctuation
|
|
318
|
-
if (input.length === 1 ? inputFinished : input[1] < '0' || input[1] > '9') {
|
|
332
|
+
if (input.length - pos === 1 ? inputFinished : input[pos + 1] < '0' || input[pos + 1] > '9') {
|
|
319
333
|
type = '.';
|
|
320
334
|
matchLength = 1;
|
|
321
335
|
break;
|
|
@@ -334,10 +348,10 @@ class N3Lexer {
|
|
|
334
348
|
case '9':
|
|
335
349
|
case '+':
|
|
336
350
|
case '-':
|
|
337
|
-
if (input[1] === '-') {
|
|
351
|
+
if (input[pos + 1] === '-') {
|
|
338
352
|
// Try to find a direction code
|
|
339
353
|
if (this._previousMarker === 'langcode') {
|
|
340
|
-
if (input.startsWith('--ltr')) type = 'dircode', value = 'ltr', matchLength = 5;else if (input.startsWith('--rtl')) type = 'dircode', value = 'rtl', matchLength = 5;
|
|
354
|
+
if (input.startsWith('--ltr', pos)) type = 'dircode', value = 'ltr', matchLength = 5;else if (input.startsWith('--rtl', pos)) type = 'dircode', value = 'rtl', matchLength = 5;
|
|
341
355
|
}
|
|
342
356
|
break;
|
|
343
357
|
}
|
|
@@ -345,7 +359,7 @@ class N3Lexer {
|
|
|
345
359
|
// Try to find a number. Since it can contain (but not end with) a dot,
|
|
346
360
|
// we always need a non-dot character before deciding it is a number.
|
|
347
361
|
// Therefore, try inserting a space if we're at the end of the input.
|
|
348
|
-
if (match = this._number
|
|
362
|
+
if (match = execAt(this._number, input, pos) || inputFinished && (match = execAtEnd(this._number, input, pos))) {
|
|
349
363
|
type = 'literal', value = match[0];
|
|
350
364
|
prefix = typeof match[1] === 'string' ? xsd.double : typeof match[2] === 'string' ? xsd.decimal : xsd.integer;
|
|
351
365
|
}
|
|
@@ -359,42 +373,42 @@ class N3Lexer {
|
|
|
359
373
|
case 'V':
|
|
360
374
|
case 'v':
|
|
361
375
|
// Try to find a SPARQL-style keyword
|
|
362
|
-
if (match = this._keyword
|
|
376
|
+
if (match = execAt(this._keyword, input, pos)) type = match[0].toUpperCase();else inconclusive = true;
|
|
363
377
|
break;
|
|
364
378
|
case 'f':
|
|
365
379
|
case 't':
|
|
366
380
|
// Try to match a boolean
|
|
367
|
-
if (this._boolean
|
|
381
|
+
if (testAt(this._boolean, input, pos)) type = 'literal', value = firstChar === 't' ? 'true' : 'false', prefix = xsd.boolean, matchLength = value.length;else inconclusive = true;
|
|
368
382
|
break;
|
|
369
383
|
case 'a':
|
|
370
384
|
// Try to find an abbreviated predicate
|
|
371
|
-
if (this._shortPredicates
|
|
385
|
+
if (testAt(this._shortPredicates, input, pos)) type = 'abbreviation', value = 'a', matchLength = 1;else inconclusive = true;
|
|
372
386
|
break;
|
|
373
387
|
case 'h':
|
|
374
388
|
case 'o':
|
|
375
389
|
// Try to find an N3 verb keyword
|
|
376
|
-
if (this._n3Mode && (match = this._matchN3Verb(input, inputFinished))) type = match[0];else inconclusive = true;
|
|
390
|
+
if (this._n3Mode && (match = this._matchN3Verb(input, pos, inputFinished))) type = match[0];else inconclusive = true;
|
|
377
391
|
break;
|
|
378
392
|
case 'i':
|
|
379
393
|
// Try to find an IRI property list identifier or N3 verb keyword
|
|
380
|
-
if (this._n3Mode && this._n3Id
|
|
394
|
+
if (this._n3Mode && testAt(this._n3Id, input, pos)) type = 'id', matchLength = 2;else if (this._n3Mode && (match = this._matchN3Verb(input, pos, inputFinished))) type = match[0];else inconclusive = true;
|
|
381
395
|
break;
|
|
382
396
|
case '=':
|
|
383
397
|
// Try to find an implication arrow or equals sign
|
|
384
|
-
if (this._n3Mode && input.length > 1) {
|
|
398
|
+
if (this._n3Mode && input.length - pos > 1) {
|
|
385
399
|
type = 'abbreviation';
|
|
386
|
-
if (input[1] !== '>') matchLength = 1, value = '=';else matchLength = 2, value = '>';
|
|
400
|
+
if (input[pos + 1] !== '>') matchLength = 1, value = '=';else matchLength = 2, value = '>';
|
|
387
401
|
}
|
|
388
402
|
break;
|
|
389
403
|
case '!':
|
|
390
404
|
if (!this._n3Mode) break;
|
|
391
405
|
case ')':
|
|
392
|
-
if (!inputFinished && (input.length === 1 || input.length === 2 && input[1] === '>')) {
|
|
406
|
+
if (!inputFinished && (input.length - pos === 1 || input.length - pos === 2 && input[pos + 1] === '>')) {
|
|
393
407
|
// Don't consume yet, as it *could* become a triple term end.
|
|
394
408
|
break;
|
|
395
409
|
}
|
|
396
410
|
// Try to find a triple term
|
|
397
|
-
if (input.length > 2 && input[1] === '>' && input[2] === '>') {
|
|
411
|
+
if (input.length - pos > 2 && input[pos + 1] === '>' && input[pos + 2] === '>') {
|
|
398
412
|
type = ')>>', matchLength = 3;
|
|
399
413
|
break;
|
|
400
414
|
}
|
|
@@ -412,15 +426,15 @@ class N3Lexer {
|
|
|
412
426
|
break;
|
|
413
427
|
case '{':
|
|
414
428
|
// We need at least 2 tokens lookahead to distinguish "{|" and "{ "
|
|
415
|
-
if (!this._lineMode && input.length >= 2) {
|
|
429
|
+
if (!this._lineMode && input.length - pos >= 2) {
|
|
416
430
|
// Try to find a quoted triple annotation start
|
|
417
|
-
if (input[1] === '|') type = '{|', matchLength = 2;else type = firstChar, matchLength = 1;
|
|
431
|
+
if (input[pos + 1] === '|') type = '{|', matchLength = 2;else type = firstChar, matchLength = 1;
|
|
418
432
|
}
|
|
419
433
|
break;
|
|
420
434
|
case '|':
|
|
421
435
|
// We need 2 tokens lookahead to parse "|}"
|
|
422
436
|
// Try to find a quoted triple annotation end
|
|
423
|
-
if (input.length >= 2 && input[1] === '}') type = '|}', matchLength = 2;
|
|
437
|
+
if (input.length - pos >= 2 && input[pos + 1] === '}') type = '|}', matchLength = 2;
|
|
424
438
|
break;
|
|
425
439
|
default:
|
|
426
440
|
inconclusive = true;
|
|
@@ -429,11 +443,11 @@ class N3Lexer {
|
|
|
429
443
|
// Some first characters do not allow an immediate decision, so inspect more
|
|
430
444
|
if (inconclusive) {
|
|
431
445
|
// Try to find a prefix
|
|
432
|
-
if ((this._previousMarker === '@prefix' || this._previousMarker === 'PREFIX') && (match = this._prefix
|
|
446
|
+
if ((this._previousMarker === '@prefix' || this._previousMarker === 'PREFIX') && (match = execAt(this._prefix, input, pos))) type = 'prefix', value = match[1] || '';
|
|
433
447
|
// Try to find a prefixed name. Since it can contain (but not end with) a dot,
|
|
434
448
|
// we always need a non-dot character before deciding it is a prefixed name.
|
|
435
449
|
// Therefore, try inserting a space if we're at the end of the input.
|
|
436
|
-
else if ((match = this._prefixed
|
|
450
|
+
else if ((match = execAt(this._prefixed, input, pos)) || inputFinished && (match = execAtEnd(this._prefixed, input, pos))) {
|
|
437
451
|
type = 'prefixed', prefix = match[1] || '';
|
|
438
452
|
value = this._unescape(match[2], localNameEscapeReplacements);
|
|
439
453
|
lexicalLength = prefix.length + match[2].length + 1;
|
|
@@ -459,16 +473,14 @@ class N3Lexer {
|
|
|
459
473
|
// We could be in streaming mode, and then we just wait for more input to arrive.
|
|
460
474
|
// Otherwise, a syntax error has occurred in the input.
|
|
461
475
|
// One exception: error on an unaccounted linebreak (= not inside a triple-quoted literal).
|
|
462
|
-
if (inputFinished ||
|
|
463
|
-
this._linePosition = currentLineLength - input.length;
|
|
464
|
-
return this._input = input;
|
|
465
|
-
}
|
|
476
|
+
if (inputFinished || !input.startsWith("'''", pos) && !input.startsWith('"""', pos) && /\n|\r/.test(input.slice(pos))) return reportSyntaxError(this, input, pos);else return this._suspend(input, pos, currentLineLength);
|
|
466
477
|
}
|
|
467
478
|
|
|
468
479
|
// Emit the parsed token
|
|
469
480
|
// Consumption includes separator whitespace; lexicalLength excludes it
|
|
470
|
-
// and any synthetic EOF space.
|
|
481
|
+
// and any synthetic EOF space. Consumption is clamped to the input below.
|
|
471
482
|
const length = matchLength || match[0].length;
|
|
483
|
+
const start = currentLineLength - (input.length - pos);
|
|
472
484
|
let token;
|
|
473
485
|
if (finalLineLength) {
|
|
474
486
|
token = {
|
|
@@ -476,23 +488,22 @@ class N3Lexer {
|
|
|
476
488
|
value,
|
|
477
489
|
prefix,
|
|
478
490
|
line,
|
|
479
|
-
start
|
|
491
|
+
start,
|
|
480
492
|
end: finalLineLength,
|
|
481
493
|
endLine: this._line
|
|
482
494
|
};
|
|
483
495
|
callback(null, token);
|
|
484
|
-
} else token = emitToken(type, value, prefix, line, lexicalLength || length);
|
|
496
|
+
} else token = emitToken(type, value, prefix, line, start, lexicalLength || length);
|
|
485
497
|
this.previousToken = token;
|
|
486
498
|
this._previousMarker = type;
|
|
487
499
|
|
|
488
500
|
// Advance to next part to tokenize
|
|
489
|
-
|
|
490
|
-
if (finalLineLength) currentLineLength = input.length + finalLineLength;
|
|
501
|
+
pos = Math.min(pos + length, input.length);
|
|
502
|
+
if (finalLineLength) currentLineLength = input.length - pos + finalLineLength;
|
|
491
503
|
}
|
|
492
504
|
|
|
493
505
|
// Emits a comment at its exact position within matched whitespace.
|
|
494
|
-
function emitComment(value, line,
|
|
495
|
-
const start = currentLineLength - input.length + offset;
|
|
506
|
+
function emitComment(value, line, start) {
|
|
496
507
|
callback(null, {
|
|
497
508
|
type: 'comment',
|
|
498
509
|
value,
|
|
@@ -503,49 +514,53 @@ class N3Lexer {
|
|
|
503
514
|
});
|
|
504
515
|
}
|
|
505
516
|
// Emits the token through the callback
|
|
506
|
-
function emitToken(type, value, prefix, line, length) {
|
|
507
|
-
const start = input ? currentLineLength - input.length : currentLineLength;
|
|
508
|
-
const end = start + length;
|
|
517
|
+
function emitToken(type, value, prefix, line, start, length) {
|
|
509
518
|
const token = {
|
|
510
519
|
type,
|
|
511
520
|
value,
|
|
512
521
|
prefix,
|
|
513
522
|
line,
|
|
514
523
|
start,
|
|
515
|
-
end
|
|
524
|
+
end: start + length
|
|
516
525
|
};
|
|
517
526
|
callback(null, token);
|
|
518
527
|
return token;
|
|
519
528
|
}
|
|
520
529
|
// Signals the syntax error through the callback
|
|
521
|
-
function reportSyntaxError(self) {
|
|
522
|
-
callback(self._syntaxError(
|
|
530
|
+
function reportSyntaxError(self, input, pos) {
|
|
531
|
+
callback(self._syntaxError(execAt(nonWhitespace, input, pos)[0]));
|
|
523
532
|
}
|
|
524
533
|
}
|
|
525
534
|
|
|
535
|
+
// ### `_suspend` keeps the unconsumed input until more input arrives
|
|
536
|
+
_suspend(input, pos, currentLineLength) {
|
|
537
|
+
this._linePosition = currentLineLength - (input.length - pos);
|
|
538
|
+
return this._input = input.slice(pos);
|
|
539
|
+
}
|
|
540
|
+
|
|
526
541
|
// ### `_matchN3Verb` matches an N3 verb unless the input is a longer prefixed name
|
|
527
|
-
_matchN3Verb(input, inputFinished) {
|
|
528
|
-
const verb = this._n3Verb
|
|
542
|
+
_matchN3Verb(input, pos, inputFinished) {
|
|
543
|
+
const verb = execAt(this._n3Verb, input, pos);
|
|
529
544
|
if (!verb) return null;
|
|
530
545
|
|
|
531
546
|
// Most verb boundaries cannot be part of a prefix, so keep the common path fast.
|
|
532
|
-
const next = input[verb[0].length];
|
|
547
|
+
const next = input[pos + verb[0].length];
|
|
533
548
|
if (next !== '-' && next !== '_' && (next < '0' || next > '9')) return verb;
|
|
534
549
|
|
|
535
550
|
// A prefix can start with a verb and continue with characters that are also
|
|
536
551
|
// valid verb boundaries. Prefer the longer prefixed name when it is complete.
|
|
537
|
-
if (this._prefixed
|
|
552
|
+
if (execAt(this._prefixed, input, pos)) return null;
|
|
538
553
|
// Appending to the input only matters when a prefixed name could run up to
|
|
539
554
|
// its end, which a character that cannot occur in prefixed names rules out.
|
|
540
555
|
// This avoids copying the rest of the document for every such verb.
|
|
541
|
-
if (nonPrefixedNameChar
|
|
542
|
-
if (this._prefixed
|
|
556
|
+
if (testAt(nonPrefixedNameChar, input, pos)) return verb;
|
|
557
|
+
if (execAtEnd(this._prefixed, input, pos)) return null;
|
|
543
558
|
|
|
544
559
|
// If a stream chunk ends partway through such a prefix, wait for the colon
|
|
545
560
|
// instead of prematurely emitting the verb. Appending ": " lets the prefix
|
|
546
561
|
// grammar determine whether all input seen so far can be a complete prefix.
|
|
547
562
|
if (!inputFinished) {
|
|
548
|
-
const prefix = this._prefix
|
|
563
|
+
const prefix = execAt(this._prefix, `${input.slice(pos)}: `, 0);
|
|
549
564
|
if (prefix) return null;
|
|
550
565
|
}
|
|
551
566
|
return verb;
|
|
@@ -592,19 +607,19 @@ class N3Lexer {
|
|
|
592
607
|
return result + item.slice(start);
|
|
593
608
|
}
|
|
594
609
|
|
|
595
|
-
// ### `_parseLiteral` parses a literal into an unescaped value
|
|
596
|
-
_parseLiteral(input) {
|
|
610
|
+
// ### `_parseLiteral` parses a literal at the given position into an unescaped value
|
|
611
|
+
_parseLiteral(input, pos) {
|
|
597
612
|
// Ensure we have enough lookahead to identify triple-quoted strings
|
|
598
|
-
if (input.length >= 3) {
|
|
613
|
+
if (input.length - pos >= 3) {
|
|
599
614
|
// The caller has already identified a single or double quote.
|
|
600
|
-
const quote = input[
|
|
601
|
-
const openingLength = input[1] === quote && input[2] === quote ? 3 : 1;
|
|
615
|
+
const quote = input[pos];
|
|
616
|
+
const openingLength = input[pos + 1] === quote && input[pos + 2] === quote ? 3 : 1;
|
|
602
617
|
let opening = quote;
|
|
603
618
|
if (openingLength === 3) opening = quote === '"' ? '"""' : "'''";
|
|
604
619
|
|
|
605
620
|
// Find the next candidate closing quotes
|
|
606
|
-
let closingPos = Math.max(this._literalClosingPos, openingLength);
|
|
607
|
-
while ((closingPos = input.indexOf(opening, closingPos)) >
|
|
621
|
+
let closingPos = pos + Math.max(this._literalClosingPos, openingLength);
|
|
622
|
+
while ((closingPos = input.indexOf(opening, closingPos)) > pos) {
|
|
608
623
|
// Count backslashes right before the closing quotes
|
|
609
624
|
let backslashCount = 0;
|
|
610
625
|
while (input[closingPos - backslashCount - 1] === '\\') backslashCount++;
|
|
@@ -613,10 +628,10 @@ class N3Lexer {
|
|
|
613
628
|
// means these are actual, non-escaped closing quotes
|
|
614
629
|
if (backslashCount % 2 === 0) {
|
|
615
630
|
// Extract and unescape the value
|
|
616
|
-
const raw = input.substring(openingLength, closingPos),
|
|
631
|
+
const raw = input.substring(pos + openingLength, closingPos),
|
|
617
632
|
lines = raw.split(/\r\n|\r|\n/),
|
|
618
633
|
lineCount = lines.length - 1;
|
|
619
|
-
const matchLength = closingPos + openingLength;
|
|
634
|
+
const matchLength = closingPos - pos + openingLength;
|
|
620
635
|
// Only triple-quoted strings can be multi-line
|
|
621
636
|
if (openingLength === 1 && lineCount !== 0 || openingLength === 3 && this._lineMode) break;
|
|
622
637
|
this._line += lineCount;
|
|
@@ -629,7 +644,7 @@ class N3Lexer {
|
|
|
629
644
|
}
|
|
630
645
|
closingPos++;
|
|
631
646
|
}
|
|
632
|
-
this._literalClosingPos = input.length - openingLength + 1;
|
|
647
|
+
this._literalClosingPos = input.length - pos - openingLength + 1;
|
|
633
648
|
}
|
|
634
649
|
return {
|
|
635
650
|
value: '',
|