n3 2.7.11 → 2.8.0
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 +33 -0
- package/browser/n3.esm.min.js +11 -11
- package/browser/n3.min.js +11 -11
- package/lib/N3Lexer.js +102 -25
- package/lib/N3Reasoner.js +13 -4
- package/lib/N3Store.js +385 -27
- package/package.json +1 -1
- package/src/N3Lexer.js +90 -27
- package/src/N3Reasoner.js +14 -4
- package/src/N3Store.js +418 -32
package/lib/N3Lexer.js
CHANGED
|
@@ -92,7 +92,7 @@ class N3Lexer {
|
|
|
92
92
|
this._n3Verb = /^(?:has|is|of)(?=[\s#()\[\]\{\}"'<>?_+\-0-9])/;
|
|
93
93
|
this._n3Id = /^id(?=[\s#<])/;
|
|
94
94
|
this._shortPredicates = /^a(?=[\s#()\[\]\{\}"'<>])/;
|
|
95
|
-
this._commentLine = /^[ \t]*#([^\n\r]*)(?:\r\n|\n|\r)[ \t]
|
|
95
|
+
this._commentLine = /^[ \t]*#([^\n\r]*)(?:\r\n|\n|\r)([ \t]*)/;
|
|
96
96
|
this._whitespace = /^[ \t]+/;
|
|
97
97
|
options = options || {};
|
|
98
98
|
|
|
@@ -123,7 +123,7 @@ class N3Lexer {
|
|
|
123
123
|
_tokenizeToEnd(callback, inputFinished) {
|
|
124
124
|
// Continue parsing as far as possible; the loop will return eventually
|
|
125
125
|
let input = this._input;
|
|
126
|
-
let currentLineLength = input.length;
|
|
126
|
+
let currentLineLength = this._linePosition + input.length;
|
|
127
127
|
while (true) {
|
|
128
128
|
// Consume one separator line at a time, including its following indentation.
|
|
129
129
|
while (true) {
|
|
@@ -137,28 +137,43 @@ class N3Lexer {
|
|
|
137
137
|
if (charCode === HASH) {
|
|
138
138
|
const comment = this._commentLine.exec(input);
|
|
139
139
|
if (comment) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
const commentLength = comment[0].length;
|
|
141
|
+
// Keep a trailing CR buffered in case the next chunk starts with LF.
|
|
142
|
+
if (!inputFinished && commentLength === input.length && input.charCodeAt(commentLength - 1) === CR) {
|
|
143
|
+
this._linePosition = currentLineLength - input.length;
|
|
144
|
+
return this._input = input;
|
|
145
|
+
}
|
|
146
|
+
if (this.comments) emitComment(comment[1], this._line, separatorLength);
|
|
147
|
+
input = input.slice(commentLength);
|
|
148
|
+
currentLineLength = input.length + comment[2].length;
|
|
143
149
|
this._line++;
|
|
144
150
|
} else {
|
|
145
151
|
// A comment without a line ending stays buffered until EOF.
|
|
146
152
|
input = input.slice(separatorLength);
|
|
147
|
-
if (!inputFinished)
|
|
148
|
-
|
|
153
|
+
if (!inputFinished) {
|
|
154
|
+
this._linePosition = currentLineLength - input.length;
|
|
155
|
+
return this._input = input;
|
|
156
|
+
}
|
|
157
|
+
if (this.comments) emitComment(input.slice(1), this._line, 0);
|
|
149
158
|
input = '';
|
|
150
159
|
break;
|
|
151
160
|
}
|
|
152
161
|
} else if (charCode === LF || charCode === CR) {
|
|
162
|
+
// A CR at the end of a chunk may still be followed by LF.
|
|
163
|
+
if (!inputFinished && charCode === CR && separatorLength + 1 === input.length) {
|
|
164
|
+
this._linePosition = currentLineLength - input.length;
|
|
165
|
+
return this._input = input;
|
|
166
|
+
}
|
|
153
167
|
separatorLength += charCode === CR && input.charCodeAt(separatorLength + 1) === LF ? 2 : 1;
|
|
154
|
-
// Indentation is
|
|
168
|
+
// Indentation is consumed with the newline, but belongs to the next line's columns.
|
|
169
|
+
let indentationLength = 0;
|
|
155
170
|
const next = input.charCodeAt(separatorLength);
|
|
156
171
|
if (next === SPACE || next === TAB) {
|
|
157
172
|
const following = input.charCodeAt(separatorLength + 1);
|
|
158
|
-
|
|
173
|
+
indentationLength = following === SPACE || following === TAB ? this._whitespace.exec(input.slice(separatorLength))[0].length : 1;
|
|
159
174
|
}
|
|
160
|
-
input = input.slice(separatorLength);
|
|
161
|
-
currentLineLength = input.length;
|
|
175
|
+
input = input.slice(separatorLength + indentationLength);
|
|
176
|
+
currentLineLength = input.length + indentationLength;
|
|
162
177
|
this._line++;
|
|
163
178
|
} else {
|
|
164
179
|
if (separatorLength !== 0) input = input.slice(separatorLength);
|
|
@@ -170,6 +185,7 @@ class N3Lexer {
|
|
|
170
185
|
input = null;
|
|
171
186
|
emitToken('eof', '', '', this._line, 0);
|
|
172
187
|
}
|
|
188
|
+
this._linePosition = currentLineLength;
|
|
173
189
|
return this._input = input;
|
|
174
190
|
}
|
|
175
191
|
|
|
@@ -181,6 +197,8 @@ class N3Lexer {
|
|
|
181
197
|
prefix = '',
|
|
182
198
|
match = null,
|
|
183
199
|
matchLength = 0,
|
|
200
|
+
lexicalLength = 0,
|
|
201
|
+
finalLineLength = 0,
|
|
184
202
|
inconclusive = false;
|
|
185
203
|
switch (firstChar) {
|
|
186
204
|
case '^':
|
|
@@ -207,12 +225,16 @@ class N3Lexer {
|
|
|
207
225
|
// Fall through in case the type is an IRI
|
|
208
226
|
case '<':
|
|
209
227
|
// Try to find a full IRI without escape sequences
|
|
210
|
-
if (match = this._unescapedIri.exec(input))
|
|
228
|
+
if (match = this._unescapedIri.exec(input)) {
|
|
229
|
+
type = 'IRI', value = match[1];
|
|
230
|
+
lexicalLength = match[1].length + 2;
|
|
231
|
+
}
|
|
211
232
|
// Try to find a full IRI with escape sequences
|
|
212
233
|
else if (match = this._iri.exec(input)) {
|
|
213
234
|
value = this._unescape(match[1], stringEscapeReplacements);
|
|
214
235
|
if (value === null || illegalIriChars.test(value)) return reportSyntaxError(this);
|
|
215
236
|
type = 'IRI';
|
|
237
|
+
lexicalLength = match[1].length + 2;
|
|
216
238
|
}
|
|
217
239
|
// Try to find a triple term
|
|
218
240
|
else if (input.length > 2 && input[1] === '<' && input[2] === '(') type = '<<(', matchLength = 3;
|
|
@@ -234,7 +256,10 @@ class N3Lexer {
|
|
|
234
256
|
// Try to find a blank node. Since it can contain (but not end with) a dot,
|
|
235
257
|
// we always need a non-dot character before deciding it is a blank node.
|
|
236
258
|
// Therefore, try inserting a space if we're at the end of the input.
|
|
237
|
-
if ((match = this._blank.exec(input)) || inputFinished && (match = this._blank.exec(`${input} `)))
|
|
259
|
+
if ((match = this._blank.exec(input)) || inputFinished && (match = this._blank.exec(`${input} `))) {
|
|
260
|
+
type = 'blank', prefix = '_', value = match[1];
|
|
261
|
+
lexicalLength = match[1].length + 2;
|
|
262
|
+
}
|
|
238
263
|
break;
|
|
239
264
|
case '"':
|
|
240
265
|
// Try to find a literal without escape sequences
|
|
@@ -243,7 +268,8 @@ class N3Lexer {
|
|
|
243
268
|
else {
|
|
244
269
|
({
|
|
245
270
|
value,
|
|
246
|
-
matchLength
|
|
271
|
+
matchLength,
|
|
272
|
+
finalLineLength
|
|
247
273
|
} = this._parseLiteral(input));
|
|
248
274
|
if (value === null) return reportSyntaxError(this);
|
|
249
275
|
}
|
|
@@ -260,7 +286,8 @@ class N3Lexer {
|
|
|
260
286
|
else {
|
|
261
287
|
({
|
|
262
288
|
value,
|
|
263
|
-
matchLength
|
|
289
|
+
matchLength,
|
|
290
|
+
finalLineLength
|
|
264
291
|
} = this._parseLiteral(input));
|
|
265
292
|
if (value === null) return reportSyntaxError(this);
|
|
266
293
|
}
|
|
@@ -406,7 +433,11 @@ class N3Lexer {
|
|
|
406
433
|
// Try to find a prefixed name. Since it can contain (but not end with) a dot,
|
|
407
434
|
// we always need a non-dot character before deciding it is a prefixed name.
|
|
408
435
|
// Therefore, try inserting a space if we're at the end of the input.
|
|
409
|
-
else if ((match = this._prefixed.exec(input)) || inputFinished && (match = this._prefixed.exec(`${input} `)))
|
|
436
|
+
else if ((match = this._prefixed.exec(input)) || inputFinished && (match = this._prefixed.exec(`${input} `))) {
|
|
437
|
+
type = 'prefixed', prefix = match[1] || '';
|
|
438
|
+
value = this._unescape(match[2], localNameEscapeReplacements);
|
|
439
|
+
lexicalLength = prefix.length + match[2].length + 1;
|
|
440
|
+
}
|
|
410
441
|
}
|
|
411
442
|
|
|
412
443
|
// A type token is special: it can only be emitted after an IRI or prefixed name is read
|
|
@@ -428,19 +459,49 @@ class N3Lexer {
|
|
|
428
459
|
// We could be in streaming mode, and then we just wait for more input to arrive.
|
|
429
460
|
// Otherwise, a syntax error has occurred in the input.
|
|
430
461
|
// One exception: error on an unaccounted linebreak (= not inside a triple-quoted literal).
|
|
431
|
-
if (inputFinished || !/^'''|^"""/.test(input) && /\n|\r/.test(input)) return reportSyntaxError(this);else
|
|
462
|
+
if (inputFinished || !/^'''|^"""/.test(input) && /\n|\r/.test(input)) return reportSyntaxError(this);else {
|
|
463
|
+
this._linePosition = currentLineLength - input.length;
|
|
464
|
+
return this._input = input;
|
|
465
|
+
}
|
|
432
466
|
}
|
|
433
467
|
|
|
434
468
|
// Emit the parsed token
|
|
469
|
+
// Consumption includes separator whitespace; lexicalLength excludes it
|
|
470
|
+
// and any synthetic EOF space. slice below clamps consumption to the input.
|
|
435
471
|
const length = matchLength || match[0].length;
|
|
436
|
-
|
|
472
|
+
let token;
|
|
473
|
+
if (finalLineLength) {
|
|
474
|
+
token = {
|
|
475
|
+
type,
|
|
476
|
+
value,
|
|
477
|
+
prefix,
|
|
478
|
+
line,
|
|
479
|
+
start: currentLineLength - input.length,
|
|
480
|
+
end: finalLineLength,
|
|
481
|
+
endLine: this._line
|
|
482
|
+
};
|
|
483
|
+
callback(null, token);
|
|
484
|
+
} else token = emitToken(type, value, prefix, line, lexicalLength || length);
|
|
437
485
|
this.previousToken = token;
|
|
438
486
|
this._previousMarker = type;
|
|
439
487
|
|
|
440
488
|
// Advance to next part to tokenize
|
|
441
489
|
input = input.slice(length);
|
|
490
|
+
if (finalLineLength) currentLineLength = input.length + finalLineLength;
|
|
442
491
|
}
|
|
443
492
|
|
|
493
|
+
// Emits a comment at its exact position within matched whitespace.
|
|
494
|
+
function emitComment(value, line, offset) {
|
|
495
|
+
const start = currentLineLength - input.length + offset;
|
|
496
|
+
callback(null, {
|
|
497
|
+
type: 'comment',
|
|
498
|
+
value,
|
|
499
|
+
prefix: '',
|
|
500
|
+
line,
|
|
501
|
+
start,
|
|
502
|
+
end: start + value.length + 1
|
|
503
|
+
});
|
|
504
|
+
}
|
|
444
505
|
// Emits the token through the callback
|
|
445
506
|
function emitToken(type, value, prefix, line, length) {
|
|
446
507
|
const start = input ? currentLineLength - input.length : currentLineLength;
|
|
@@ -539,15 +600,18 @@ class N3Lexer {
|
|
|
539
600
|
// means these are actual, non-escaped closing quotes
|
|
540
601
|
if (backslashCount % 2 === 0) {
|
|
541
602
|
// Extract and unescape the value
|
|
542
|
-
const raw = input.substring(openingLength, closingPos)
|
|
543
|
-
|
|
603
|
+
const raw = input.substring(openingLength, closingPos),
|
|
604
|
+
lines = raw.split(/\r\n|\r|\n/),
|
|
605
|
+
lineCount = lines.length - 1;
|
|
544
606
|
const matchLength = closingPos + openingLength;
|
|
545
607
|
// Only triple-quoted strings can be multi-line
|
|
546
|
-
if (openingLength === 1 &&
|
|
547
|
-
this._line +=
|
|
608
|
+
if (openingLength === 1 && lineCount !== 0 || openingLength === 3 && this._lineMode) break;
|
|
609
|
+
this._line += lineCount;
|
|
610
|
+
const finalLineLength = lineCount === 0 ? 0 : lines[lines.length - 1].length + openingLength;
|
|
548
611
|
return {
|
|
549
612
|
value: this._unescape(raw, stringEscapeReplacements),
|
|
550
|
-
matchLength
|
|
613
|
+
matchLength,
|
|
614
|
+
finalLineLength
|
|
551
615
|
};
|
|
552
616
|
}
|
|
553
617
|
closingPos++;
|
|
@@ -556,7 +620,8 @@ class N3Lexer {
|
|
|
556
620
|
}
|
|
557
621
|
return {
|
|
558
622
|
value: '',
|
|
559
|
-
matchLength: 0
|
|
623
|
+
matchLength: 0,
|
|
624
|
+
finalLineLength: 0
|
|
560
625
|
};
|
|
561
626
|
}
|
|
562
627
|
|
|
@@ -574,18 +639,30 @@ class N3Lexer {
|
|
|
574
639
|
|
|
575
640
|
// ### Strips off any starting UTF BOM mark.
|
|
576
641
|
_readStartingBom(input) {
|
|
577
|
-
|
|
642
|
+
if (input.startsWith('\ufeff')) {
|
|
643
|
+
this._linePosition = 1;
|
|
644
|
+
return input.slice(1);
|
|
645
|
+
}
|
|
646
|
+
return input;
|
|
578
647
|
}
|
|
579
648
|
|
|
580
649
|
// ## Public methods
|
|
581
650
|
|
|
582
651
|
// ### `tokenize` starts the transformation of an N3 document into an array of tokens.
|
|
583
652
|
// The input can be a string or a stream.
|
|
653
|
+
// Token ranges use one-based lines and zero-based, end-exclusive UTF-16 columns.
|
|
654
|
+
// Separator whitespace counts towards the next token's start, outside either range.
|
|
655
|
+
// Multiline tokens also have endLine; their end column is relative to that line.
|
|
584
656
|
tokenize(input, callback) {
|
|
585
657
|
// Deferred tokenization and stream events can outlive their invocation.
|
|
586
658
|
// Ignore them once a later call takes ownership of the lexer state.
|
|
587
659
|
const tokenization = this._tokenization = {};
|
|
588
660
|
this._line = 1;
|
|
661
|
+
this._linePosition = 0;
|
|
662
|
+
this._previousMarker = undefined;
|
|
663
|
+
this.previousToken = undefined;
|
|
664
|
+
this._literalClosingPos = 0;
|
|
665
|
+
this._input = undefined;
|
|
589
666
|
|
|
590
667
|
// If the input is a string, continuously emit tokens through the callback until the end
|
|
591
668
|
if (typeof input === 'string') {
|
package/lib/N3Reasoner.js
CHANGED
|
@@ -34,10 +34,17 @@ class N3Reasoner {
|
|
|
34
34
|
this._maxPremiseDepth = options.maxPremiseDepth === undefined ? Infinity : options.maxPremiseDepth;
|
|
35
35
|
}
|
|
36
36
|
_add(subject, predicate, object, graphItem, c, cb) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
const store = this._store;
|
|
38
|
+
// Observing views must see each derivation before indexing
|
|
39
|
+
// (stores from other N3 versions have no observers)
|
|
40
|
+
if (store._observers) {
|
|
41
|
+
if (!store._addQuad(subject, predicate, object, this._graphId)) return;
|
|
42
|
+
} else {
|
|
43
|
+
// Only add to the remaining indexes if there is not already a value in the index
|
|
44
|
+
if (!store._addToIndex(graphItem.subjects, subject, predicate, object)) return;
|
|
45
|
+
store._addToIndex(graphItem.predicates, predicate, object, subject);
|
|
46
|
+
store._addToIndex(graphItem.objects, object, subject, predicate);
|
|
47
|
+
}
|
|
41
48
|
// Count genuinely new derivations and fail past the budget. The check comes
|
|
42
49
|
// after all three indexes are updated, so a caught error leaves the store
|
|
43
50
|
// in a consistent state (the reasoning result is merely incomplete).
|
|
@@ -224,6 +231,8 @@ class N3Reasoner {
|
|
|
224
231
|
const graphs = this._store._getGraphs();
|
|
225
232
|
try {
|
|
226
233
|
for (const graphId in graphs) {
|
|
234
|
+
// Observers compare numeric graph identifiers
|
|
235
|
+
this._graphId = Number(graphId);
|
|
227
236
|
this._reasonGraphNaive(rules, graphs[graphId]);
|
|
228
237
|
}
|
|
229
238
|
} finally {
|