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