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/src/N3Lexer.js
CHANGED
|
@@ -57,7 +57,7 @@ export default class N3Lexer {
|
|
|
57
57
|
this._n3Verb = /^(?:has|is|of)(?=[\s#()\[\]\{\}"'<>?_+\-0-9])/;
|
|
58
58
|
this._n3Id = /^id(?=[\s#<])/;
|
|
59
59
|
this._shortPredicates = /^a(?=[\s#()\[\]\{\}"'<>])/;
|
|
60
|
-
this._commentLine = /^[ \t]*#([^\n\r]*)(?:\r\n|\n|\r)[ \t]
|
|
60
|
+
this._commentLine = /^[ \t]*#([^\n\r]*)(?:\r\n|\n|\r)([ \t]*)/;
|
|
61
61
|
this._whitespace = /^[ \t]+/;
|
|
62
62
|
options = options || {};
|
|
63
63
|
|
|
@@ -89,7 +89,7 @@ export default class N3Lexer {
|
|
|
89
89
|
_tokenizeToEnd(callback, inputFinished) {
|
|
90
90
|
// Continue parsing as far as possible; the loop will return eventually
|
|
91
91
|
let input = this._input;
|
|
92
|
-
let currentLineLength = input.length;
|
|
92
|
+
let currentLineLength = this._linePosition + input.length;
|
|
93
93
|
while (true) {
|
|
94
94
|
// Consume one separator line at a time, including its following indentation.
|
|
95
95
|
while (true) {
|
|
@@ -103,34 +103,49 @@ export default class N3Lexer {
|
|
|
103
103
|
if (charCode === HASH) {
|
|
104
104
|
const comment = this._commentLine.exec(input);
|
|
105
105
|
if (comment) {
|
|
106
|
+
const commentLength = comment[0].length;
|
|
107
|
+
// Keep a trailing CR buffered in case the next chunk starts with LF.
|
|
108
|
+
if (!inputFinished && commentLength === input.length &&
|
|
109
|
+
input.charCodeAt(commentLength - 1) === CR) {
|
|
110
|
+
this._linePosition = currentLineLength - input.length;
|
|
111
|
+
return this._input = input;
|
|
112
|
+
}
|
|
106
113
|
if (this.comments)
|
|
107
|
-
|
|
108
|
-
input = input.slice(
|
|
109
|
-
currentLineLength = input.length;
|
|
114
|
+
emitComment(comment[1], this._line, separatorLength);
|
|
115
|
+
input = input.slice(commentLength);
|
|
116
|
+
currentLineLength = input.length + comment[2].length;
|
|
110
117
|
this._line++;
|
|
111
118
|
}
|
|
112
119
|
else {
|
|
113
120
|
// A comment without a line ending stays buffered until EOF.
|
|
114
121
|
input = input.slice(separatorLength);
|
|
115
|
-
if (!inputFinished)
|
|
122
|
+
if (!inputFinished) {
|
|
123
|
+
this._linePosition = currentLineLength - input.length;
|
|
116
124
|
return this._input = input;
|
|
125
|
+
}
|
|
117
126
|
if (this.comments)
|
|
118
|
-
|
|
127
|
+
emitComment(input.slice(1), this._line, 0);
|
|
119
128
|
input = '';
|
|
120
129
|
break;
|
|
121
130
|
}
|
|
122
131
|
}
|
|
123
132
|
else if (charCode === LF || charCode === CR) {
|
|
133
|
+
// 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._linePosition = currentLineLength - input.length;
|
|
136
|
+
return this._input = input;
|
|
137
|
+
}
|
|
124
138
|
separatorLength += charCode === CR && input.charCodeAt(separatorLength + 1) === LF ? 2 : 1;
|
|
125
|
-
// Indentation is
|
|
139
|
+
// Indentation is consumed with the newline, but belongs to the next line's columns.
|
|
140
|
+
let indentationLength = 0;
|
|
126
141
|
const next = input.charCodeAt(separatorLength);
|
|
127
142
|
if (next === SPACE || next === TAB) {
|
|
128
143
|
const following = input.charCodeAt(separatorLength + 1);
|
|
129
|
-
|
|
144
|
+
indentationLength = following === SPACE || following === TAB ?
|
|
130
145
|
this._whitespace.exec(input.slice(separatorLength))[0].length : 1;
|
|
131
146
|
}
|
|
132
|
-
input = input.slice(separatorLength);
|
|
133
|
-
currentLineLength = input.length;
|
|
147
|
+
input = input.slice(separatorLength + indentationLength);
|
|
148
|
+
currentLineLength = input.length + indentationLength;
|
|
134
149
|
this._line++;
|
|
135
150
|
}
|
|
136
151
|
else {
|
|
@@ -144,13 +159,15 @@ export default class N3Lexer {
|
|
|
144
159
|
input = null;
|
|
145
160
|
emitToken('eof', '', '', this._line, 0);
|
|
146
161
|
}
|
|
162
|
+
this._linePosition = currentLineLength;
|
|
147
163
|
return this._input = input;
|
|
148
164
|
}
|
|
149
165
|
|
|
150
166
|
// Look for specific token types based on the first character
|
|
151
167
|
const line = this._line, firstChar = input[0];
|
|
152
168
|
let type = '', value = '', prefix = '',
|
|
153
|
-
match = null, matchLength = 0,
|
|
169
|
+
match = null, matchLength = 0, lexicalLength = 0,
|
|
170
|
+
finalLineLength = 0, inconclusive = false;
|
|
154
171
|
switch (firstChar) {
|
|
155
172
|
case '^':
|
|
156
173
|
// We need at least 3 tokens lookahead to distinguish ^^<IRI> and ^^pre:fixed
|
|
@@ -177,14 +194,17 @@ export default class N3Lexer {
|
|
|
177
194
|
// Fall through in case the type is an IRI
|
|
178
195
|
case '<':
|
|
179
196
|
// Try to find a full IRI without escape sequences
|
|
180
|
-
if (match = this._unescapedIri.exec(input))
|
|
197
|
+
if (match = this._unescapedIri.exec(input)) {
|
|
181
198
|
type = 'IRI', value = match[1];
|
|
199
|
+
lexicalLength = match[1].length + 2;
|
|
200
|
+
}
|
|
182
201
|
// Try to find a full IRI with escape sequences
|
|
183
202
|
else if (match = this._iri.exec(input)) {
|
|
184
203
|
value = this._unescape(match[1], stringEscapeReplacements);
|
|
185
204
|
if (value === null || illegalIriChars.test(value))
|
|
186
205
|
return reportSyntaxError(this);
|
|
187
206
|
type = 'IRI';
|
|
207
|
+
lexicalLength = match[1].length + 2;
|
|
188
208
|
}
|
|
189
209
|
// Try to find a triple term
|
|
190
210
|
else if (input.length > 2 && input[1] === '<' && input[2] === '(')
|
|
@@ -214,8 +234,10 @@ export default class N3Lexer {
|
|
|
214
234
|
// we always need a non-dot character before deciding it is a blank node.
|
|
215
235
|
// Therefore, try inserting a space if we're at the end of the input.
|
|
216
236
|
if ((match = this._blank.exec(input)) ||
|
|
217
|
-
inputFinished && (match = this._blank.exec(`${input} `)))
|
|
237
|
+
inputFinished && (match = this._blank.exec(`${input} `))) {
|
|
218
238
|
type = 'blank', prefix = '_', value = match[1];
|
|
239
|
+
lexicalLength = match[1].length + 2;
|
|
240
|
+
}
|
|
219
241
|
break;
|
|
220
242
|
|
|
221
243
|
case '"':
|
|
@@ -224,7 +246,7 @@ export default class N3Lexer {
|
|
|
224
246
|
value = match[1];
|
|
225
247
|
// Try to find a literal wrapped in three pairs of quotes
|
|
226
248
|
else {
|
|
227
|
-
({ value, matchLength } = this._parseLiteral(input));
|
|
249
|
+
({ value, matchLength, finalLineLength } = this._parseLiteral(input));
|
|
228
250
|
if (value === null)
|
|
229
251
|
return reportSyntaxError(this);
|
|
230
252
|
}
|
|
@@ -241,7 +263,7 @@ export default class N3Lexer {
|
|
|
241
263
|
value = match[1];
|
|
242
264
|
// Try to find a literal wrapped in three pairs of quotes
|
|
243
265
|
else {
|
|
244
|
-
({ value, matchLength } = this._parseLiteral(input));
|
|
266
|
+
({ value, matchLength, finalLineLength } = this._parseLiteral(input));
|
|
245
267
|
if (value === null)
|
|
246
268
|
return reportSyntaxError(this);
|
|
247
269
|
}
|
|
@@ -436,8 +458,11 @@ export default class N3Lexer {
|
|
|
436
458
|
// we always need a non-dot character before deciding it is a prefixed name.
|
|
437
459
|
// Therefore, try inserting a space if we're at the end of the input.
|
|
438
460
|
else if ((match = this._prefixed.exec(input)) ||
|
|
439
|
-
inputFinished && (match = this._prefixed.exec(`${input} `)))
|
|
440
|
-
type = 'prefixed', prefix = match[1] || ''
|
|
461
|
+
inputFinished && (match = this._prefixed.exec(`${input} `))) {
|
|
462
|
+
type = 'prefixed', prefix = match[1] || '';
|
|
463
|
+
value = this._unescape(match[2], localNameEscapeReplacements);
|
|
464
|
+
lexicalLength = prefix.length + match[2].length + 1;
|
|
465
|
+
}
|
|
441
466
|
}
|
|
442
467
|
|
|
443
468
|
// A type token is special: it can only be emitted after an IRI or prefixed name is read
|
|
@@ -456,20 +481,44 @@ export default class N3Lexer {
|
|
|
456
481
|
// One exception: error on an unaccounted linebreak (= not inside a triple-quoted literal).
|
|
457
482
|
if (inputFinished || (!/^'''|^"""/.test(input) && /\n|\r/.test(input)))
|
|
458
483
|
return reportSyntaxError(this);
|
|
459
|
-
else
|
|
484
|
+
else {
|
|
485
|
+
this._linePosition = currentLineLength - input.length;
|
|
460
486
|
return this._input = input;
|
|
487
|
+
}
|
|
461
488
|
}
|
|
462
489
|
|
|
463
490
|
// Emit the parsed token
|
|
491
|
+
// Consumption includes separator whitespace; lexicalLength excludes it
|
|
492
|
+
// and any synthetic EOF space. slice below clamps consumption to the input.
|
|
464
493
|
const length = matchLength || match[0].length;
|
|
465
|
-
|
|
494
|
+
let token;
|
|
495
|
+
if (finalLineLength) {
|
|
496
|
+
token = {
|
|
497
|
+
type, value, prefix, line,
|
|
498
|
+
start: currentLineLength - input.length,
|
|
499
|
+
end: finalLineLength, endLine: this._line,
|
|
500
|
+
};
|
|
501
|
+
callback(null, token);
|
|
502
|
+
}
|
|
503
|
+
else
|
|
504
|
+
token = emitToken(type, value, prefix, line, lexicalLength || length);
|
|
466
505
|
this.previousToken = token;
|
|
467
506
|
this._previousMarker = type;
|
|
468
507
|
|
|
469
508
|
// Advance to next part to tokenize
|
|
470
509
|
input = input.slice(length);
|
|
510
|
+
if (finalLineLength)
|
|
511
|
+
currentLineLength = input.length + finalLineLength;
|
|
471
512
|
}
|
|
472
513
|
|
|
514
|
+
// Emits a comment at its exact position within matched whitespace.
|
|
515
|
+
function emitComment(value, line, offset) {
|
|
516
|
+
const start = currentLineLength - input.length + offset;
|
|
517
|
+
callback(null, {
|
|
518
|
+
type: 'comment', value, prefix: '', line,
|
|
519
|
+
start, end: start + value.length + 1,
|
|
520
|
+
});
|
|
521
|
+
}
|
|
473
522
|
// Emits the token through the callback
|
|
474
523
|
function emitToken(type, value, prefix, line, length) {
|
|
475
524
|
const start = input ? currentLineLength - input.length : currentLineLength;
|
|
@@ -568,21 +617,23 @@ export default class N3Lexer {
|
|
|
568
617
|
// means these are actual, non-escaped closing quotes
|
|
569
618
|
if (backslashCount % 2 === 0) {
|
|
570
619
|
// Extract and unescape the value
|
|
571
|
-
const raw = input.substring(openingLength, closingPos)
|
|
572
|
-
|
|
620
|
+
const raw = input.substring(openingLength, closingPos),
|
|
621
|
+
lines = raw.split(/\r\n|\r|\n/),
|
|
622
|
+
lineCount = lines.length - 1;
|
|
573
623
|
const matchLength = closingPos + openingLength;
|
|
574
624
|
// Only triple-quoted strings can be multi-line
|
|
575
|
-
if (openingLength === 1 &&
|
|
625
|
+
if (openingLength === 1 && lineCount !== 0 ||
|
|
576
626
|
openingLength === 3 && this._lineMode)
|
|
577
627
|
break;
|
|
578
|
-
this._line +=
|
|
579
|
-
|
|
628
|
+
this._line += lineCount;
|
|
629
|
+
const finalLineLength = lineCount === 0 ? 0 : lines[lines.length - 1].length + openingLength;
|
|
630
|
+
return { value: this._unescape(raw, stringEscapeReplacements), matchLength, finalLineLength };
|
|
580
631
|
}
|
|
581
632
|
closingPos++;
|
|
582
633
|
}
|
|
583
634
|
this._literalClosingPos = input.length - openingLength + 1;
|
|
584
635
|
}
|
|
585
|
-
return { value: '', matchLength: 0 };
|
|
636
|
+
return { value: '', matchLength: 0, finalLineLength: 0 };
|
|
586
637
|
}
|
|
587
638
|
|
|
588
639
|
// ### `_syntaxError` creates a syntax error for the given issue
|
|
@@ -599,18 +650,30 @@ export default class N3Lexer {
|
|
|
599
650
|
|
|
600
651
|
// ### Strips off any starting UTF BOM mark.
|
|
601
652
|
_readStartingBom(input) {
|
|
602
|
-
|
|
653
|
+
if (input.startsWith('\ufeff')) {
|
|
654
|
+
this._linePosition = 1;
|
|
655
|
+
return input.slice(1);
|
|
656
|
+
}
|
|
657
|
+
return input;
|
|
603
658
|
}
|
|
604
659
|
|
|
605
660
|
// ## Public methods
|
|
606
661
|
|
|
607
662
|
// ### `tokenize` starts the transformation of an N3 document into an array of tokens.
|
|
608
663
|
// The input can be a string or a stream.
|
|
664
|
+
// Token ranges use one-based lines and zero-based, end-exclusive UTF-16 columns.
|
|
665
|
+
// Separator whitespace counts towards the next token's start, outside either range.
|
|
666
|
+
// Multiline tokens also have endLine; their end column is relative to that line.
|
|
609
667
|
tokenize(input, callback) {
|
|
610
668
|
// Deferred tokenization and stream events can outlive their invocation.
|
|
611
669
|
// Ignore them once a later call takes ownership of the lexer state.
|
|
612
670
|
const tokenization = this._tokenization = {};
|
|
613
671
|
this._line = 1;
|
|
672
|
+
this._linePosition = 0;
|
|
673
|
+
this._previousMarker = undefined;
|
|
674
|
+
this.previousToken = undefined;
|
|
675
|
+
this._literalClosingPos = 0;
|
|
676
|
+
this._input = undefined;
|
|
614
677
|
|
|
615
678
|
// If the input is a string, continuously emit tokens through the callback until the end
|
|
616
679
|
if (typeof input === 'string') {
|
package/src/N3Reasoner.js
CHANGED
|
@@ -23,10 +23,18 @@ export default class N3Reasoner {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
_add(subject, predicate, object, graphItem, c, cb) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
const store = this._store;
|
|
27
|
+
// Observing views must see each derivation before indexing
|
|
28
|
+
// (stores from other N3 versions have no observers)
|
|
29
|
+
if (store._observers) {
|
|
30
|
+
if (!store._addQuad(subject, predicate, object, this._graphId)) return;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
// Only add to the remaining indexes if there is not already a value in the index
|
|
34
|
+
if (!store._addToIndex(graphItem.subjects, subject, predicate, object)) return;
|
|
35
|
+
store._addToIndex(graphItem.predicates, predicate, object, subject);
|
|
36
|
+
store._addToIndex(graphItem.objects, object, subject, predicate);
|
|
37
|
+
}
|
|
30
38
|
// Count genuinely new derivations and fail past the budget. The check comes
|
|
31
39
|
// after all three indexes are updated, so a caught error leaves the store
|
|
32
40
|
// in a consistent state (the reasoning result is merely incomplete).
|
|
@@ -228,6 +236,8 @@ export default class N3Reasoner {
|
|
|
228
236
|
const graphs = this._store._getGraphs();
|
|
229
237
|
try {
|
|
230
238
|
for (const graphId in graphs) {
|
|
239
|
+
// Observers compare numeric graph identifiers
|
|
240
|
+
this._graphId = Number(graphId);
|
|
231
241
|
this._reasonGraphNaive(rules, graphs[graphId]);
|
|
232
242
|
}
|
|
233
243
|
}
|