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