n3 3.0.0-alpha.7 → 3.0.0-alpha.9
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 +10 -1
- package/browser/n3.esm.min.js +10 -9
- package/browser/n3.min.js +10 -9
- package/lib/N3DataFactory.js +3 -2
- package/lib/N3Lexer.js +153 -39
- package/lib/N3Parser.js +22 -3
- package/lib/N3Reasoner.js +5 -4
- package/lib/N3Store.js +128 -133
- package/lib/N3Writer.js +9 -38
- package/package.json +7 -2
- package/src/N3DataFactory.js +3 -2
- package/src/N3Lexer.js +176 -43
- package/src/N3Parser.js +26 -3
- package/src/N3Reasoner.js +6 -4
- package/src/N3Store.js +122 -143
- package/src/N3Writer.js +12 -42
package/src/N3Lexer.js
CHANGED
|
@@ -2,7 +2,35 @@
|
|
|
2
2
|
import namespaces from './IRIs';
|
|
3
3
|
|
|
4
4
|
const { xsd } = namespaces;
|
|
5
|
-
const SPACE = 0x20, TAB = 0x09, LF = 0x0A, CR = 0x0D, HASH = 0x23
|
|
5
|
+
const SPACE = 0x20, TAB = 0x09, LF = 0x0A, CR = 0x0D, HASH = 0x23,
|
|
6
|
+
DOT = 0x2E, COLON = 0x3A, ZERO = 0x30, NINE = 0x39,
|
|
7
|
+
PERCENT = 0x25, BACKSLASH = 0x5C;
|
|
8
|
+
|
|
9
|
+
// Whitespace as matched by `\s`
|
|
10
|
+
function isWhitespace(charCode) {
|
|
11
|
+
return charCode === SPACE || charCode >= TAB && charCode <= CR ||
|
|
12
|
+
charCode >= 0xA0 && (charCode === 0xA0 || charCode === 0x1680 ||
|
|
13
|
+
charCode >= 0x2000 && charCode <= 0x200A || charCode === 0x2028 || charCode === 0x2029 ||
|
|
14
|
+
charCode === 0x202F || charCode === 0x205F || charCode === 0x3000 || charCode === 0xFEFF);
|
|
15
|
+
}
|
|
16
|
+
// Characters that can directly follow a name: whitespace and punctuation
|
|
17
|
+
// (the lookahead `[,;!\^\s#()\[\]\{\}"'<>]` of the former regular expressions)
|
|
18
|
+
const asciiDelimiters = new Uint8Array(0x80);
|
|
19
|
+
for (const char of ',;!^#()[]{}"\'<> \t\n\v\f\r')
|
|
20
|
+
asciiDelimiters[char.charCodeAt(0)] = 1;
|
|
21
|
+
function isDelimiter(charCode) {
|
|
22
|
+
return charCode < 0x80 ? asciiDelimiters[charCode] === 1 : isWhitespace(charCode);
|
|
23
|
+
}
|
|
24
|
+
// Whether a name can end before the given position: it must be followed by
|
|
25
|
+
// a delimiter, optionally after a dot. At the end of finished input, it can always end.
|
|
26
|
+
function canEndName(input, pos, inputFinished) {
|
|
27
|
+
let charCode = input.charCodeAt(pos);
|
|
28
|
+
if (charCode === DOT)
|
|
29
|
+
charCode = input.charCodeAt(++pos);
|
|
30
|
+
if (pos >= input.length)
|
|
31
|
+
return inputFinished;
|
|
32
|
+
return isDelimiter(charCode);
|
|
33
|
+
}
|
|
6
34
|
|
|
7
35
|
// Fixed escape sequences allowed in string literals (ECHAR)
|
|
8
36
|
const stringEscapeReplacements = {
|
|
@@ -16,9 +44,105 @@ const localNameEscapeReplacements = {
|
|
|
16
44
|
'=': '=', '/': '/', '?': '?', '#': '#', '@': '@', '%': '%',
|
|
17
45
|
};
|
|
18
46
|
const illegalIriChars = /[\x00-\x20<>\\"\{\}\|\^\`]/;
|
|
19
|
-
|
|
20
|
-
//
|
|
21
|
-
const
|
|
47
|
+
|
|
48
|
+
// Character classes of names, as bit flags for ASCII characters
|
|
49
|
+
const PREFIX_START = 1, // PN_CHARS_BASE
|
|
50
|
+
LOCAL_START = 2, // PN_CHARS_U, digits, and colon
|
|
51
|
+
NAME_CHAR = 4, // PN_CHARS
|
|
52
|
+
LOCAL_CHAR = 8, // PN_CHARS and colon
|
|
53
|
+
LOCAL_ESCAPE = 16; // characters that can be escaped in local names (PN_LOCAL_ESC)
|
|
54
|
+
const asciiNameClasses = new Uint8Array(0x80);
|
|
55
|
+
for (let charCode = 0; charCode < 0x80; charCode++) {
|
|
56
|
+
const char = String.fromCharCode(charCode);
|
|
57
|
+
const letter = char >= 'A' && char <= 'Z' || char >= 'a' && char <= 'z';
|
|
58
|
+
const nameChar = letter || char === '_' || char === '-' || char >= '0' && char <= '9';
|
|
59
|
+
asciiNameClasses[charCode] =
|
|
60
|
+
(letter ? PREFIX_START : 0) |
|
|
61
|
+
(nameChar && char !== '-' || char === ':' ? LOCAL_START : 0) |
|
|
62
|
+
(nameChar ? NAME_CHAR : 0) |
|
|
63
|
+
(nameChar || char === ':' ? LOCAL_CHAR : 0) |
|
|
64
|
+
(char in localNameEscapeReplacements ? LOCAL_ESCAPE : 0);
|
|
65
|
+
}
|
|
66
|
+
// Returns the length (0, 1, or 2 code units) of the character at the given position
|
|
67
|
+
// if it is in the given name character class
|
|
68
|
+
function nameCharLength(input, pos, charClass) {
|
|
69
|
+
const charCode = input.charCodeAt(pos);
|
|
70
|
+
if (charCode < 0x80)
|
|
71
|
+
return (asciiNameClasses[charCode] & charClass) !== 0 ? 1 : 0;
|
|
72
|
+
// Characters from U+10000 to U+EFFFF consist of a surrogate pair
|
|
73
|
+
if (charCode >= 0xD800 && charCode <= 0xDB7F) {
|
|
74
|
+
const low = input.charCodeAt(pos + 1);
|
|
75
|
+
return low >= 0xDC00 && low <= 0xDFFF ? 2 : 0;
|
|
76
|
+
}
|
|
77
|
+
// PN_CHARS has some characters that PN_CHARS_BASE does not
|
|
78
|
+
if (charClass >= NAME_CHAR && (charCode === 0xB7 || charCode >= 0x300 && charCode <= 0x36F ||
|
|
79
|
+
charCode === 0x203F || charCode === 0x2040))
|
|
80
|
+
return 1;
|
|
81
|
+
// PN_CHARS_BASE
|
|
82
|
+
return charCode >= 0xC0 && charCode <= 0x1FFF && charCode !== 0xD7 && charCode !== 0xF7 &&
|
|
83
|
+
(charCode < 0x300 || charCode >= 0x370 && charCode !== 0x37E) ||
|
|
84
|
+
charCode >= 0x200C && charCode <= 0x200D || charCode >= 0x2070 && charCode <= 0x218F ||
|
|
85
|
+
charCode >= 0x2C00 && charCode <= 0x2FEF || charCode >= 0x3001 && charCode <= 0xD7FF ||
|
|
86
|
+
charCode >= 0xF900 && charCode <= 0xFDCF || charCode >= 0xFDF0 && charCode <= 0xFFFD ? 1 : 0;
|
|
87
|
+
}
|
|
88
|
+
function isLocalEscape(charCode) {
|
|
89
|
+
return charCode < 0x80 && (asciiNameClasses[charCode] & LOCAL_ESCAPE) !== 0;
|
|
90
|
+
}
|
|
91
|
+
function isHexDigit(charCode) {
|
|
92
|
+
return charCode >= ZERO && charCode <= NINE ||
|
|
93
|
+
charCode >= 0x41 && charCode <= 0x46 || charCode >= 0x61 && charCode <= 0x66;
|
|
94
|
+
}
|
|
95
|
+
// Returns the end of the prefix (PN_PREFIX) at the given position,
|
|
96
|
+
// which can contain single dots, but not start or end with one
|
|
97
|
+
function skipPrefix(input, pos) {
|
|
98
|
+
let length = nameCharLength(input, pos, PREFIX_START);
|
|
99
|
+
while (length !== 0) {
|
|
100
|
+
pos += length;
|
|
101
|
+
const next = input.charCodeAt(pos) === DOT ? pos + 1 : pos;
|
|
102
|
+
// Most names are ASCII, so look those up without a call
|
|
103
|
+
const charCode = input.charCodeAt(next);
|
|
104
|
+
length = charCode < 0x80 ? (asciiNameClasses[charCode] & NAME_CHAR) !== 0 ? 1 : 0 :
|
|
105
|
+
nameCharLength(input, next, NAME_CHAR);
|
|
106
|
+
if (length !== 0)
|
|
107
|
+
pos = next;
|
|
108
|
+
}
|
|
109
|
+
return pos;
|
|
110
|
+
}
|
|
111
|
+
// Returns the end of the local name (PN_LOCAL) at the given position,
|
|
112
|
+
// which can contain dots, but not start or end with one
|
|
113
|
+
function skipLocalName(input, pos) {
|
|
114
|
+
let end = pos, charClass = LOCAL_START;
|
|
115
|
+
while (true) {
|
|
116
|
+
// Most names are ASCII, so look those up without a call
|
|
117
|
+
const charCode = input.charCodeAt(pos);
|
|
118
|
+
let length = charCode < 0x80 ? (asciiNameClasses[charCode] & charClass) !== 0 ? 1 : 0 :
|
|
119
|
+
nameCharLength(input, pos, charClass);
|
|
120
|
+
// Percent-encoded character (PERCENT)
|
|
121
|
+
if (length === 0 && charCode === PERCENT &&
|
|
122
|
+
isHexDigit(input.charCodeAt(pos + 1)) && isHexDigit(input.charCodeAt(pos + 2)))
|
|
123
|
+
length = 3;
|
|
124
|
+
// Escaped character (PN_LOCAL_ESC)
|
|
125
|
+
else if (length === 0 && charCode === BACKSLASH && isLocalEscape(input.charCodeAt(pos + 1)))
|
|
126
|
+
length = 2;
|
|
127
|
+
if (length !== 0) {
|
|
128
|
+
end = pos += length;
|
|
129
|
+
charClass = LOCAL_CHAR;
|
|
130
|
+
}
|
|
131
|
+
// Dots are allowed after the first character, but not at the end
|
|
132
|
+
else if (charCode === DOT && charClass === LOCAL_CHAR)
|
|
133
|
+
pos++;
|
|
134
|
+
else
|
|
135
|
+
return end;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Returns the end of the prefixed name at the given position, or -1 if there is none
|
|
139
|
+
function skipPrefixedName(input, pos, inputFinished) {
|
|
140
|
+
const colon = skipPrefix(input, pos);
|
|
141
|
+
if (input.charCodeAt(colon) !== COLON)
|
|
142
|
+
return -1;
|
|
143
|
+
const end = skipLocalName(input, colon + 1);
|
|
144
|
+
return canEndName(input, end, inputFinished) ? end : -1;
|
|
145
|
+
}
|
|
22
146
|
|
|
23
147
|
// A valid code point is a Unicode scalar value: at most U+10FFFF and not a surrogate
|
|
24
148
|
function isValidCodePoint(charCode) {
|
|
@@ -75,13 +199,11 @@ export default class N3Lexer {
|
|
|
75
199
|
this._simpleQuotedString = /"([^"\\\r\n]*)"(?=[^"])/y; // string without escape sequences
|
|
76
200
|
this._simpleApostropheString = /'([^'\\\r\n]*)'(?=[^'])/y;
|
|
77
201
|
this._langcode = /@([a-z]+(?:-[a-z0-9]+)*)(?=[^a-z0-9])/iy;
|
|
78
|
-
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;
|
|
79
|
-
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;
|
|
80
202
|
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;
|
|
81
203
|
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;
|
|
82
204
|
this._number = /[\-+]?(?:(\d+\.\d*|\.?\d+)[eE][\-+]?\d+|(?=\.?\d)\d*(?:(\.)\d+)?)(?=\.?[,;:!\^\s#()\[\]\{\}"'<>])/y;
|
|
83
205
|
this._boolean = /(?:true|false)(?=[.,;!\^\s#()\[\]\{\}"'<>])/y;
|
|
84
|
-
this._atKeyword = /@[a-z]+(?=[\s#<:"'])/iy;
|
|
206
|
+
this._atKeyword = /@[a-z]+(?=[\s#<:"'.])/iy;
|
|
85
207
|
this._keyword = /(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<"'])/iy;
|
|
86
208
|
this._n3Verb = /(?:has|is|of)(?=[\s#()\[\]\{\}"'<>?_+\-0-9])/y;
|
|
87
209
|
this._n3Id = /id(?=[\s#<])/y;
|
|
@@ -116,7 +238,9 @@ export default class N3Lexer {
|
|
|
116
238
|
if (!/^[a-z]+$/i.test(name) || reservedWords.test(name))
|
|
117
239
|
throw new Error(`Invalid directive name: "${name}"`);
|
|
118
240
|
}
|
|
119
|
-
|
|
241
|
+
// Like VERSION, directives are case-sensitive in N-Triples and N-Quads
|
|
242
|
+
const names = this._lineMode ? options.directives.map(name => name.toUpperCase()) : options.directives;
|
|
243
|
+
this._directive = new RegExp(`(?:${names.join('|')})(?=[\\s#<])`, this._lineMode ? 'y' : 'iy');
|
|
120
244
|
this._directiveMaxLength = Math.max(...options.directives.map(name => name.length));
|
|
121
245
|
// The first characters of directive names, so other words skip the regular expression
|
|
122
246
|
this._directiveStarts = options.directives.map(name => name[0].toLowerCase() + name[0].toUpperCase()).join('');
|
|
@@ -501,9 +625,14 @@ export default class N3Lexer {
|
|
|
501
625
|
// Some first characters do not allow an immediate decision, so inspect more
|
|
502
626
|
if (inconclusive) {
|
|
503
627
|
// Try to find a prefix
|
|
628
|
+
let end;
|
|
504
629
|
if ((this._previousMarker === '@prefix' || this._previousMarker === 'PREFIX') &&
|
|
505
|
-
(
|
|
506
|
-
|
|
630
|
+
input.charCodeAt(end = skipPrefix(input, pos)) === COLON &&
|
|
631
|
+
end + 1 < input.length && (input[end + 1] === '#' || input[end + 1] === '<' ||
|
|
632
|
+
isWhitespace(input.charCodeAt(end + 1)))) {
|
|
633
|
+
type = 'prefix', value = input.slice(pos, end);
|
|
634
|
+
matchLength = end + 1 - pos;
|
|
635
|
+
}
|
|
507
636
|
// Try to find an additional directive keyword
|
|
508
637
|
// (at the end of the input, only a short final word can be one)
|
|
509
638
|
else if (this._directive !== null && this._directiveStarts.includes(firstChar) &&
|
|
@@ -512,13 +641,13 @@ export default class N3Lexer {
|
|
|
512
641
|
(match = execAtEnd(this._directive, input, pos))))
|
|
513
642
|
type = match[0].toUpperCase();
|
|
514
643
|
// Try to find a prefixed name. Since it can contain (but not end with) a dot,
|
|
515
|
-
// we always need a non-dot character before deciding it is a prefixed name
|
|
516
|
-
//
|
|
517
|
-
else if ((
|
|
518
|
-
|
|
519
|
-
type = 'prefixed', prefix =
|
|
520
|
-
value = this._unescape(
|
|
521
|
-
|
|
644
|
+
// we always need a non-dot character before deciding it is a prefixed name,
|
|
645
|
+
// except at the end of the input.
|
|
646
|
+
else if (!this._lineMode && (end = skipPrefixedName(input, pos, inputFinished)) >= 0) {
|
|
647
|
+
const colon = input.indexOf(':', pos);
|
|
648
|
+
type = 'prefixed', prefix = input.slice(pos, colon);
|
|
649
|
+
value = this._unescape(input.slice(colon + 1, end), localNameEscapeReplacements);
|
|
650
|
+
matchLength = end - pos;
|
|
522
651
|
}
|
|
523
652
|
}
|
|
524
653
|
|
|
@@ -605,30 +734,21 @@ export default class N3Lexer {
|
|
|
605
734
|
return null;
|
|
606
735
|
|
|
607
736
|
// Most verb boundaries cannot be part of a prefix, so keep the common path fast.
|
|
737
|
+
// U+1680 and U+FEFF are whitespace to the regular expression but name characters.
|
|
608
738
|
const next = input[pos + verb[0].length];
|
|
609
|
-
if (next !== '-' && next !== '_' && (next < '0' || next > '9')
|
|
739
|
+
if (next !== '-' && next !== '_' && (next < '0' || next > '9') &&
|
|
740
|
+
next !== '\u1680' && next !== '\ufeff')
|
|
610
741
|
return verb;
|
|
611
742
|
|
|
612
743
|
// A prefix can start with a verb and continue with characters that are also
|
|
613
744
|
// valid verb boundaries. Prefer the longer prefixed name when it is complete.
|
|
614
|
-
if (
|
|
615
|
-
return null;
|
|
616
|
-
// Appending to the input only matters when a prefixed name could run up to
|
|
617
|
-
// its end, which a character that cannot occur in prefixed names rules out.
|
|
618
|
-
// This avoids copying the rest of the document for every such verb.
|
|
619
|
-
if (testAt(nonPrefixedNameChar, input, pos))
|
|
620
|
-
return verb;
|
|
621
|
-
if (execAtEnd(this._prefixed, input, pos))
|
|
745
|
+
if (skipPrefixedName(input, pos, true) >= 0)
|
|
622
746
|
return null;
|
|
623
747
|
|
|
624
|
-
// If a stream chunk ends partway through such a prefix,
|
|
625
|
-
// instead of prematurely emitting the verb.
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
const prefix = execAt(this._prefix, `${input.slice(pos)}: `, 0);
|
|
629
|
-
if (prefix)
|
|
630
|
-
return null;
|
|
631
|
-
}
|
|
748
|
+
// If a stream chunk ends partway through such a prefix,
|
|
749
|
+
// wait for the colon instead of prematurely emitting the verb.
|
|
750
|
+
if (!inputFinished && skipPrefix(input, pos) === input.length)
|
|
751
|
+
return null;
|
|
632
752
|
return verb;
|
|
633
753
|
}
|
|
634
754
|
|
|
@@ -758,6 +878,27 @@ export default class N3Lexer {
|
|
|
758
878
|
return err;
|
|
759
879
|
}
|
|
760
880
|
|
|
881
|
+
// ### `_startTokenization` resets the lexer state for a new input
|
|
882
|
+
_startTokenization() {
|
|
883
|
+
this._line = 1;
|
|
884
|
+
this._linePosition = 0;
|
|
885
|
+
this._previousMarker = undefined;
|
|
886
|
+
this.previousToken = undefined;
|
|
887
|
+
this._literalClosingPos = 0;
|
|
888
|
+
this._input = undefined;
|
|
889
|
+
// Deferred tokenization and stream events can outlive their invocation.
|
|
890
|
+
// Ignore them once a later call takes ownership of the lexer state.
|
|
891
|
+
return this._tokenization = {};
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// ### `_tokenizeString` synchronously emits the tokens of a complete string through the callback,
|
|
895
|
+
// so that the caller can consume each token without the lexer collecting them all first
|
|
896
|
+
_tokenizeString(input, callback) {
|
|
897
|
+
this._startTokenization();
|
|
898
|
+
this._input = this._readStartingBom(input);
|
|
899
|
+
this._tryTokenizeToEnd(callback, true);
|
|
900
|
+
}
|
|
901
|
+
|
|
761
902
|
// ### Strips off any starting UTF BOM mark.
|
|
762
903
|
_readStartingBom(input) {
|
|
763
904
|
if (input.startsWith('\ufeff')) {
|
|
@@ -775,15 +916,7 @@ export default class N3Lexer {
|
|
|
775
916
|
// Separator whitespace counts towards the next token's start, outside either range.
|
|
776
917
|
// Multiline tokens also have endLine; their end column is relative to that line.
|
|
777
918
|
tokenize(input, callback) {
|
|
778
|
-
|
|
779
|
-
// Ignore them once a later call takes ownership of the lexer state.
|
|
780
|
-
const tokenization = this._tokenization = {};
|
|
781
|
-
this._line = 1;
|
|
782
|
-
this._linePosition = 0;
|
|
783
|
-
this._previousMarker = undefined;
|
|
784
|
-
this.previousToken = undefined;
|
|
785
|
-
this._literalClosingPos = 0;
|
|
786
|
-
this._input = undefined;
|
|
919
|
+
const tokenization = this._startTokenization();
|
|
787
920
|
|
|
788
921
|
// If the input is a string, continuously emit tokens through the callback until the end
|
|
789
922
|
if (typeof input === 'string') {
|
package/src/N3Parser.js
CHANGED
|
@@ -1683,6 +1683,10 @@ export default class N3Parser {
|
|
|
1683
1683
|
// The read callback is the next function to be executed when a token arrives.
|
|
1684
1684
|
// We start reading in the top context.
|
|
1685
1685
|
this._readCallback = this._readBeforeTopContext;
|
|
1686
|
+
// A parse that failed part-way can have left scopes and a statement open
|
|
1687
|
+
this._contextStack = [];
|
|
1688
|
+
this._graph = this._subject = this._predicate = this._object = null;
|
|
1689
|
+
this._tripleTerm = this._reifier = null;
|
|
1686
1690
|
this._sparqlStyle = false;
|
|
1687
1691
|
this._prefixes = Object.create(null);
|
|
1688
1692
|
this._prefixes._ = this._blankNodePrefix ? this._blankNodePrefix.substr(2)
|
|
@@ -1702,14 +1706,14 @@ export default class N3Parser {
|
|
|
1702
1706
|
this._emptyFormula = false;
|
|
1703
1707
|
this._tripleTermDepth = 0;
|
|
1704
1708
|
|
|
1705
|
-
|
|
1709
|
+
const readGrammarToken = token => {
|
|
1706
1710
|
return this._readCallback = this._readCallback(token);
|
|
1707
1711
|
};
|
|
1712
|
+
let readToken = readGrammarToken;
|
|
1708
1713
|
|
|
1709
1714
|
// Comments bypass the grammar, but participate in the token lifecycle.
|
|
1710
1715
|
if (onComment || this._lexer.comments) {
|
|
1711
1716
|
this._lexer.comments = true;
|
|
1712
|
-
const readGrammarToken = readToken;
|
|
1713
1717
|
readToken = token => {
|
|
1714
1718
|
if (token.type !== 'comment')
|
|
1715
1719
|
return readGrammarToken(token);
|
|
@@ -1744,7 +1748,26 @@ export default class N3Parser {
|
|
|
1744
1748
|
const quads = [];
|
|
1745
1749
|
let error;
|
|
1746
1750
|
this._callback = (e, t) => { e ? (error = e) : t && quads.push(t); };
|
|
1747
|
-
this._lexer
|
|
1751
|
+
const lexer = this._lexer;
|
|
1752
|
+
// Without callbacks, nothing observes the parse before the whole document
|
|
1753
|
+
// has lexed, so tokens can be parsed as they are lexed instead of being
|
|
1754
|
+
// collected into an array first. A lexer that replaces the built-in
|
|
1755
|
+
// tokenize keeps going through its own implementation.
|
|
1756
|
+
if (!onPrefix && !onVersion && !onDirective && !onComment && !onToken && !onTokenEnd &&
|
|
1757
|
+
lexer.tokenize === N3Lexer.prototype.tokenize && typeof lexer._tokenizeString === 'function') {
|
|
1758
|
+
// A lexical error used to stop the parse before any token was read,
|
|
1759
|
+
// so undo base declarations that were read before it
|
|
1760
|
+
const base = [this._base, this._basePath, this._baseRoot, this._baseScheme];
|
|
1761
|
+
lexer._tokenizeString(input, (e, token) => {
|
|
1762
|
+
if (e) {
|
|
1763
|
+
[this._base, this._basePath, this._baseRoot, this._baseScheme] = base;
|
|
1764
|
+
this._callback(e), this._callback = noop;
|
|
1765
|
+
}
|
|
1766
|
+
else if (this._readCallback) readToken(token);
|
|
1767
|
+
});
|
|
1768
|
+
}
|
|
1769
|
+
else
|
|
1770
|
+
lexer.tokenize(input).every(readToken);
|
|
1748
1771
|
if (error) throw error;
|
|
1749
1772
|
return quads;
|
|
1750
1773
|
}
|
package/src/N3Reasoner.js
CHANGED
|
@@ -34,6 +34,7 @@ export default class N3Reasoner {
|
|
|
34
34
|
if (!store._addToIndex(graphItem.subjects, subject, predicate, object)) return;
|
|
35
35
|
store._addToIndex(graphItem.predicates, predicate, object, subject);
|
|
36
36
|
store._addToIndex(graphItem.objects, object, subject, predicate);
|
|
37
|
+
this._indexed++;
|
|
37
38
|
}
|
|
38
39
|
// Count genuinely new derivations and fail past the budget. The check comes
|
|
39
40
|
// after all three indexes are updated, so a caught error leaves the store
|
|
@@ -175,7 +176,7 @@ export default class N3Reasoner {
|
|
|
175
176
|
}
|
|
176
177
|
|
|
177
178
|
reason(rules) {
|
|
178
|
-
this._derivations = 0;
|
|
179
|
+
this._derivations = this._indexed = 0;
|
|
179
180
|
if (!Array.isArray(rules)) {
|
|
180
181
|
rules = getRulesFromDataset(rules);
|
|
181
182
|
}
|
|
@@ -242,9 +243,10 @@ export default class N3Reasoner {
|
|
|
242
243
|
}
|
|
243
244
|
}
|
|
244
245
|
finally {
|
|
245
|
-
//
|
|
246
|
-
// so a caught budget error leaves the store
|
|
247
|
-
this._store._size
|
|
246
|
+
// Count the quads added directly to the indexes, even if a derivation
|
|
247
|
+
// budget was exceeded, so a caught budget error leaves the store consistent
|
|
248
|
+
if (this._store._size !== null)
|
|
249
|
+
this._store._size += this._indexed;
|
|
248
250
|
}
|
|
249
251
|
}
|
|
250
252
|
}
|