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