eyeling 1.5.24 → 1.5.26
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/eyeling.js +79 -6
- package/package.json +1 -1
- package/test/api.test.js +241 -165
package/eyeling.js
CHANGED
|
@@ -318,9 +318,47 @@ function lex(inputText) {
|
|
|
318
318
|
continue;
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
-
// Directives: @prefix, @base
|
|
321
|
+
// Directives: @prefix, @base (and language tags after string literals)
|
|
322
322
|
if (c === "@") {
|
|
323
|
-
|
|
323
|
+
const prevTok = tokens.length ? tokens[tokens.length - 1] : null;
|
|
324
|
+
const prevWasQuotedLiteral =
|
|
325
|
+
prevTok &&
|
|
326
|
+
prevTok.typ === "Literal" &&
|
|
327
|
+
typeof prevTok.value === "string" &&
|
|
328
|
+
prevTok.value.startsWith('"');
|
|
329
|
+
|
|
330
|
+
i++; // consume '@'
|
|
331
|
+
|
|
332
|
+
if (prevWasQuotedLiteral) {
|
|
333
|
+
// N3 grammar production LANGTAG:
|
|
334
|
+
// "@" [a-zA-Z]+ ("-" [a-zA-Z0-9]+)*
|
|
335
|
+
const tagChars = [];
|
|
336
|
+
let cc = peek();
|
|
337
|
+
if (cc === null || !/[A-Za-z]/.test(cc)) {
|
|
338
|
+
throw new Error("Invalid language tag (expected [A-Za-z] after '@')");
|
|
339
|
+
}
|
|
340
|
+
while ((cc = peek()) !== null && /[A-Za-z]/.test(cc)) {
|
|
341
|
+
tagChars.push(cc);
|
|
342
|
+
i++;
|
|
343
|
+
}
|
|
344
|
+
while (peek() === "-") {
|
|
345
|
+
tagChars.push("-");
|
|
346
|
+
i++; // consume '-'
|
|
347
|
+
const segChars = [];
|
|
348
|
+
while ((cc = peek()) !== null && /[A-Za-z0-9]/.test(cc)) {
|
|
349
|
+
segChars.push(cc);
|
|
350
|
+
i++;
|
|
351
|
+
}
|
|
352
|
+
if (!segChars.length) {
|
|
353
|
+
throw new Error("Invalid language tag (expected [A-Za-z0-9]+ after '-')");
|
|
354
|
+
}
|
|
355
|
+
tagChars.push(...segChars);
|
|
356
|
+
}
|
|
357
|
+
tokens.push(new Token("LangTag", tagChars.join("")));
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Otherwise, treat as a directive (@prefix, @base)
|
|
324
362
|
const wordChars = [];
|
|
325
363
|
let cc;
|
|
326
364
|
while ((cc = peek()) !== null && /[A-Za-z]/.test(cc)) {
|
|
@@ -684,6 +722,23 @@ class Parser {
|
|
|
684
722
|
|
|
685
723
|
if (typ === "Literal") {
|
|
686
724
|
let s = val || "";
|
|
725
|
+
|
|
726
|
+
// Optional language tag: "..."@en, per N3 LANGTAG production.
|
|
727
|
+
if (this.peek().typ === "LangTag") {
|
|
728
|
+
// Only quoted string literals can carry a language tag.
|
|
729
|
+
if (!(s.startsWith('"') && s.endsWith('"'))) {
|
|
730
|
+
throw new Error("Language tag is only allowed on quoted string literals");
|
|
731
|
+
}
|
|
732
|
+
const langTok = this.next();
|
|
733
|
+
const lang = langTok.value || "";
|
|
734
|
+
s = `${s}@${lang}`;
|
|
735
|
+
|
|
736
|
+
// N3/Turtle: language tags and datatypes are mutually exclusive.
|
|
737
|
+
if (this.peek().typ === "HatHat") {
|
|
738
|
+
throw new Error("A literal cannot have both a language tag (@...) and a datatype (^^...)");
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
687
742
|
if (this.peek().typ === "HatHat") {
|
|
688
743
|
this.next();
|
|
689
744
|
const dtTok = this.next();
|
|
@@ -1594,16 +1649,34 @@ function composeSubst(outer, delta) {
|
|
|
1594
1649
|
// ============================================================================
|
|
1595
1650
|
|
|
1596
1651
|
function literalParts(lit) {
|
|
1652
|
+
// Split a literal into lexical form and datatype IRI (if any).
|
|
1653
|
+
// Also strip an optional language tag from the lexical form:
|
|
1654
|
+
// "\"hello\"@en" -> "\"hello\""
|
|
1655
|
+
// "\"hello\"@en^^<...>" is rejected earlier in the parser.
|
|
1597
1656
|
const idx = lit.indexOf("^^");
|
|
1657
|
+
let lex = lit;
|
|
1658
|
+
let dt = null;
|
|
1659
|
+
|
|
1598
1660
|
if (idx >= 0) {
|
|
1599
|
-
|
|
1600
|
-
|
|
1661
|
+
lex = lit.slice(0, idx);
|
|
1662
|
+
dt = lit.slice(idx + 2).trim();
|
|
1601
1663
|
if (dt.startsWith("<") && dt.endsWith(">")) {
|
|
1602
1664
|
dt = dt.slice(1, -1);
|
|
1603
1665
|
}
|
|
1604
|
-
return [lex, dt];
|
|
1605
1666
|
}
|
|
1606
|
-
|
|
1667
|
+
|
|
1668
|
+
// Strip LANGTAG from the lexical form when present.
|
|
1669
|
+
if (lex.length >= 2 && lex[0] === '"') {
|
|
1670
|
+
const lastQuote = lex.lastIndexOf('"');
|
|
1671
|
+
if (lastQuote > 0 && lastQuote < lex.length - 1 && lex[lastQuote + 1] === "@") {
|
|
1672
|
+
const lang = lex.slice(lastQuote + 2);
|
|
1673
|
+
if (/^[A-Za-z]+(?:-[A-Za-z0-9]+)*$/.test(lang)) {
|
|
1674
|
+
lex = lex.slice(0, lastQuote + 1);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
return [lex, dt];
|
|
1607
1680
|
}
|
|
1608
1681
|
|
|
1609
1682
|
function stripQuotes(lex) {
|
package/package.json
CHANGED
package/test/api.test.js
CHANGED
|
@@ -8,9 +8,15 @@ const C = TTY
|
|
|
8
8
|
? { g: '\x1b[32m', r: '\x1b[31m', y: '\x1b[33m', dim: '\x1b[2m', n: '\x1b[0m' }
|
|
9
9
|
: { g: '', r: '', y: '', dim: '', n: '' };
|
|
10
10
|
|
|
11
|
-
function ok(msg)
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
function ok(msg) {
|
|
12
|
+
console.log(`${C.g}OK${C.n} ${msg}`);
|
|
13
|
+
}
|
|
14
|
+
function info(msg) {
|
|
15
|
+
console.log(`${C.y}==${C.n} ${msg}`);
|
|
16
|
+
}
|
|
17
|
+
function fail(msg) {
|
|
18
|
+
console.error(`${C.r}FAIL${C.n} ${msg}`);
|
|
19
|
+
}
|
|
14
20
|
|
|
15
21
|
function msNow() {
|
|
16
22
|
return Date.now();
|
|
@@ -24,8 +30,21 @@ function mustNotMatch(output, re, label) {
|
|
|
24
30
|
assert.ok(!re.test(output), label || `Expected output NOT to match ${re}`);
|
|
25
31
|
}
|
|
26
32
|
|
|
27
|
-
|
|
33
|
+
function countMatches(output, re) {
|
|
34
|
+
// ensure global counting without mutating caller regex
|
|
35
|
+
const flags = re.flags.includes('g') ? re.flags : re.flags + 'g';
|
|
36
|
+
const rg = new RegExp(re.source, flags);
|
|
37
|
+
let c = 0;
|
|
38
|
+
while (rg.exec(output)) c++;
|
|
39
|
+
return c;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function mustOccurExactly(output, re, n, label) {
|
|
43
|
+
const c = countMatches(output, re);
|
|
44
|
+
assert.equal(c, n, label || `Expected ${n} matches of ${re}, got ${c}`);
|
|
45
|
+
}
|
|
28
46
|
|
|
47
|
+
const EX = 'http://example.org/';
|
|
29
48
|
// Helper to build a URI quickly
|
|
30
49
|
const U = (path) => `<${EX}${path}>`;
|
|
31
50
|
|
|
@@ -43,7 +62,6 @@ function parentChainN3(n) {
|
|
|
43
62
|
}
|
|
44
63
|
|
|
45
64
|
function subclassChainN3(n) {
|
|
46
|
-
// C0 sub C1 ... Cn sub C(n+1)
|
|
47
65
|
let s = '';
|
|
48
66
|
for (let i = 0; i <= n; i++) {
|
|
49
67
|
s += `${U(`C${i}`)} ${U('sub')} ${U(`C${i + 1}`)}.\n`;
|
|
@@ -54,7 +72,6 @@ function subclassChainN3(n) {
|
|
|
54
72
|
}
|
|
55
73
|
|
|
56
74
|
function ruleChainN3(n) {
|
|
57
|
-
// p0 -> p1 -> ... -> pn, starting from (s p0 o)
|
|
58
75
|
let s = '';
|
|
59
76
|
for (let i = 0; i < n; i++) {
|
|
60
77
|
s += `{ ${U('s')} ${U(`p${i}`)} ${U('o')}. } => { ${U('s')} ${U(`p${i + 1}`)} ${U('o')}. }.\n`;
|
|
@@ -64,9 +81,7 @@ function ruleChainN3(n) {
|
|
|
64
81
|
}
|
|
65
82
|
|
|
66
83
|
function binaryTreeParentN3(depth) {
|
|
67
|
-
|
|
68
|
-
// depth=0 -> 1 node; depth=1 -> 3 nodes; depth=4 -> 31 nodes
|
|
69
|
-
const maxNode = (1 << (depth + 1)) - 2; // last index at given depth
|
|
84
|
+
const maxNode = (1 << (depth + 1)) - 2;
|
|
70
85
|
let s = '';
|
|
71
86
|
|
|
72
87
|
for (let i = 0; i <= maxNode; i++) {
|
|
@@ -90,12 +105,10 @@ function transitiveClosureN3(pred) {
|
|
|
90
105
|
}
|
|
91
106
|
|
|
92
107
|
function reachabilityGraphN3(n) {
|
|
93
|
-
// chain plus a few extra edges for branching
|
|
94
108
|
let s = '';
|
|
95
109
|
for (let i = 0; i < n; i++) {
|
|
96
110
|
s += `${U(`g${i}`)} ${U('edge')} ${U(`g${i + 1}`)}.\n`;
|
|
97
111
|
}
|
|
98
|
-
// add some shortcuts/branches
|
|
99
112
|
if (n >= 6) {
|
|
100
113
|
s += `${U('g0')} ${U('edge')} ${U('g3')}.\n`;
|
|
101
114
|
s += `${U('g2')} ${U('edge')} ${U('g5')}.\n`;
|
|
@@ -121,7 +134,6 @@ ${U('x')} ${U('type')} ${U('A')}.
|
|
|
121
134
|
}
|
|
122
135
|
|
|
123
136
|
function join3HopN3(k) {
|
|
124
|
-
// a0 --p--> a1 --p--> a2 --p--> ... ; rule derives hop3 edges
|
|
125
137
|
let s = '';
|
|
126
138
|
for (let i = 0; i < k; i++) {
|
|
127
139
|
s += `${U(`j${i}`)} ${U('p')} ${U(`j${i + 1}`)}.\n`;
|
|
@@ -162,7 +174,6 @@ function bigFactsN3(n) {
|
|
|
162
174
|
}
|
|
163
175
|
|
|
164
176
|
function negativeEntailmentBatchN3(n) {
|
|
165
|
-
// if any forbidden fact exists, derive false
|
|
166
177
|
let s = '';
|
|
167
178
|
for (let i = 0; i < n; i++) {
|
|
168
179
|
s += `${U('x')} ${U('ok')} ${U(`v${i}`)}.\n`;
|
|
@@ -173,7 +184,6 @@ function negativeEntailmentBatchN3(n) {
|
|
|
173
184
|
}
|
|
174
185
|
|
|
175
186
|
function symmetricTransitiveN3() {
|
|
176
|
-
// friend is symmetric; reachFriend is transitive closure over friend edges
|
|
177
187
|
return `
|
|
178
188
|
${U('a')} ${U('friend')} ${U('b')}.
|
|
179
189
|
${U('b')} ${U('friend')} ${U('c')}.
|
|
@@ -185,62 +195,6 @@ ${U('c')} ${U('friend')} ${U('d')}.
|
|
|
185
195
|
`;
|
|
186
196
|
}
|
|
187
197
|
|
|
188
|
-
function mkChainRewriteCase(i, steps) {
|
|
189
|
-
const input = ruleChainN3(steps); // already defined earlier
|
|
190
|
-
return {
|
|
191
|
-
name: `${String(i).padStart(2, '0')} chain rewrite: ${steps} steps`,
|
|
192
|
-
opt: { proofComments: false },
|
|
193
|
-
input,
|
|
194
|
-
expect: [new RegExp(`${EX}s>\\s+<${EX}p${steps}>\\s+<${EX}o>\\s*\\.`)],
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function mkSubclassChainCase(i, steps) {
|
|
199
|
-
const input = subclassChainN3(steps); // already defined earlier
|
|
200
|
-
return {
|
|
201
|
-
name: `${String(i).padStart(2, '0')} subclass chain: ${steps} steps`,
|
|
202
|
-
opt: { proofComments: false },
|
|
203
|
-
input,
|
|
204
|
-
expect: [new RegExp(`${EX}x>\\s+<${EX}type>\\s+<${EX}C${steps + 1}>\\s*\\.`)],
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function mkParentChainCase(i, links) {
|
|
209
|
-
const input = parentChainN3(links); // already defined earlier
|
|
210
|
-
return {
|
|
211
|
-
name: `${String(i).padStart(2, '0')} ancestor chain: ${links} links`,
|
|
212
|
-
opt: { proofComments: false },
|
|
213
|
-
input,
|
|
214
|
-
expect: [new RegExp(`${EX}n0>\\s+<${EX}ancestor>\\s+<${EX}n${links}>\\s*\\.`)],
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function mkJoinCase(i, len) {
|
|
219
|
-
const input = join3HopN3(len); // already defined earlier
|
|
220
|
-
// Check a couple of hop-3 inferences that always exist for len>=6
|
|
221
|
-
return {
|
|
222
|
-
name: `${String(i).padStart(2, '0')} 3-hop join over chain len ${len}`,
|
|
223
|
-
opt: { proofComments: false },
|
|
224
|
-
input,
|
|
225
|
-
expect: [
|
|
226
|
-
new RegExp(`${EX}j0>\\s+<${EX}p3>\\s+<${EX}j3>\\s*\\.`),
|
|
227
|
-
new RegExp(`${EX}j2>\\s+<${EX}p3>\\s+<${EX}j5>\\s*\\.`),
|
|
228
|
-
],
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function mkBranchReachCase(i, n) {
|
|
233
|
-
const input = reachabilityGraphN3(n); // already defined earlier
|
|
234
|
-
return {
|
|
235
|
-
name: `${String(i).padStart(2, '0')} reachability: n=${n}`,
|
|
236
|
-
opt: { proofComments: false },
|
|
237
|
-
input,
|
|
238
|
-
expect: [
|
|
239
|
-
new RegExp(`${EX}g0>\\s+<${EX}reach>\\s+<${EX}g${n}>\\s*\\.`),
|
|
240
|
-
],
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
|
|
244
198
|
const cases = [
|
|
245
199
|
{
|
|
246
200
|
name: '01 forward rule: p -> q',
|
|
@@ -249,11 +203,8 @@ const cases = [
|
|
|
249
203
|
{ ${U('s')} ${U('p')} ${U('o')}. } => { ${U('s')} ${U('q')} ${U('o')}. }.
|
|
250
204
|
${U('s')} ${U('p')} ${U('o')}.
|
|
251
205
|
`,
|
|
252
|
-
expect: [
|
|
253
|
-
new RegExp(`${EX}s>\\s+<${EX}q>\\s+<${EX}o>\\s*\\.`),
|
|
254
|
-
],
|
|
206
|
+
expect: [new RegExp(`${EX}s>\\s+<${EX}q>\\s+<${EX}o>\\s*\\.`)],
|
|
255
207
|
},
|
|
256
|
-
|
|
257
208
|
{
|
|
258
209
|
name: '02 two-step: p -> q -> r',
|
|
259
210
|
opt: { proofComments: false },
|
|
@@ -262,11 +213,8 @@ ${U('s')} ${U('p')} ${U('o')}.
|
|
|
262
213
|
{ ${U('s')} ${U('q')} ${U('o')}. } => { ${U('s')} ${U('r')} ${U('o')}. }.
|
|
263
214
|
${U('s')} ${U('p')} ${U('o')}.
|
|
264
215
|
`,
|
|
265
|
-
expect: [
|
|
266
|
-
new RegExp(`${EX}s>\\s+<${EX}r>\\s+<${EX}o>\\s*\\.`),
|
|
267
|
-
],
|
|
216
|
+
expect: [new RegExp(`${EX}s>\\s+<${EX}r>\\s+<${EX}o>\\s*\\.`)],
|
|
268
217
|
},
|
|
269
|
-
|
|
270
218
|
{
|
|
271
219
|
name: '03 join antecedents: (x p y & y p z) -> (x p2 z)',
|
|
272
220
|
opt: { proofComments: false },
|
|
@@ -275,11 +223,8 @@ ${U('s')} ${U('p')} ${U('o')}.
|
|
|
275
223
|
${U('a')} ${U('p')} ${U('b')}.
|
|
276
224
|
${U('b')} ${U('p')} ${U('c')}.
|
|
277
225
|
`,
|
|
278
|
-
expect: [
|
|
279
|
-
new RegExp(`${EX}a>\\s+<${EX}p2>\\s+<${EX}c>\\s*\\.`),
|
|
280
|
-
],
|
|
226
|
+
expect: [new RegExp(`${EX}a>\\s+<${EX}p2>\\s+<${EX}c>\\s*\\.`)],
|
|
281
227
|
},
|
|
282
|
-
|
|
283
228
|
{
|
|
284
229
|
name: '04 inverse relation: (x p y) -> (y invp x)',
|
|
285
230
|
opt: { proofComments: false },
|
|
@@ -287,11 +232,8 @@ ${U('b')} ${U('p')} ${U('c')}.
|
|
|
287
232
|
{ ?x ${U('p')} ?y. } => { ?y ${U('invp')} ?x. }.
|
|
288
233
|
${U('alice')} ${U('p')} ${U('bob')}.
|
|
289
234
|
`,
|
|
290
|
-
expect: [
|
|
291
|
-
new RegExp(`${EX}bob>\\s+<${EX}invp>\\s+<${EX}alice>\\s*\\.`),
|
|
292
|
-
],
|
|
235
|
+
expect: [new RegExp(`${EX}bob>\\s+<${EX}invp>\\s+<${EX}alice>\\s*\\.`)],
|
|
293
236
|
},
|
|
294
|
-
|
|
295
237
|
{
|
|
296
238
|
name: '05 subclass rule: type + sub -> inferred type (two-level chain)',
|
|
297
239
|
opt: { proofComments: false },
|
|
@@ -307,7 +249,6 @@ ${U('Socrates')} ${U('type')} ${U('Human')}.
|
|
|
307
249
|
new RegExp(`${EX}Socrates>\\s+<${EX}type>\\s+<${EX}Being>\\s*\\.`),
|
|
308
250
|
],
|
|
309
251
|
},
|
|
310
|
-
|
|
311
252
|
{
|
|
312
253
|
name: '06 transitive closure: sub is transitive',
|
|
313
254
|
opt: { proofComments: false },
|
|
@@ -317,11 +258,8 @@ ${U('B')} ${U('sub')} ${U('C')}.
|
|
|
317
258
|
|
|
318
259
|
{ ?a ${U('sub')} ?b. ?b ${U('sub')} ?c } => { ?a ${U('sub')} ?c }.
|
|
319
260
|
`,
|
|
320
|
-
expect: [
|
|
321
|
-
new RegExp(`${EX}A>\\s+<${EX}sub>\\s+<${EX}C>\\s*\\.`),
|
|
322
|
-
],
|
|
261
|
+
expect: [new RegExp(`${EX}A>\\s+<${EX}sub>\\s+<${EX}C>\\s*\\.`)],
|
|
323
262
|
},
|
|
324
|
-
|
|
325
263
|
{
|
|
326
264
|
name: '07 symmetric: knows is symmetric',
|
|
327
265
|
opt: { proofComments: false },
|
|
@@ -329,11 +267,8 @@ ${U('B')} ${U('sub')} ${U('C')}.
|
|
|
329
267
|
{ ?x ${U('knows')} ?y } => { ?y ${U('knows')} ?x }.
|
|
330
268
|
${U('a')} ${U('knows')} ${U('b')}.
|
|
331
269
|
`,
|
|
332
|
-
expect: [
|
|
333
|
-
new RegExp(`${EX}b>\\s+<${EX}knows>\\s+<${EX}a>\\s*\\.`),
|
|
334
|
-
],
|
|
270
|
+
expect: [new RegExp(`${EX}b>\\s+<${EX}knows>\\s+<${EX}a>\\s*\\.`)],
|
|
335
271
|
},
|
|
336
|
-
|
|
337
272
|
{
|
|
338
273
|
name: '08 recursion: ancestor from parent (2 steps)',
|
|
339
274
|
opt: { proofComments: false },
|
|
@@ -344,11 +279,8 @@ ${U('b')} ${U('parent')} ${U('c')}.
|
|
|
344
279
|
{ ?x ${U('parent')} ?y } => { ?x ${U('ancestor')} ?y }.
|
|
345
280
|
{ ?x ${U('parent')} ?y. ?y ${U('ancestor')} ?z } => { ?x ${U('ancestor')} ?z }.
|
|
346
281
|
`,
|
|
347
|
-
expect: [
|
|
348
|
-
new RegExp(`${EX}a>\\s+<${EX}ancestor>\\s+<${EX}c>\\s*\\.`),
|
|
349
|
-
],
|
|
282
|
+
expect: [new RegExp(`${EX}a>\\s+<${EX}ancestor>\\s+<${EX}c>\\s*\\.`)],
|
|
350
283
|
},
|
|
351
|
-
|
|
352
284
|
{
|
|
353
285
|
name: '09 literals preserved: age -> hasAge',
|
|
354
286
|
opt: { proofComments: false },
|
|
@@ -356,11 +288,8 @@ ${U('b')} ${U('parent')} ${U('c')}.
|
|
|
356
288
|
{ ?s ${U('age')} ?n } => { ?s ${U('hasAge')} ?n }.
|
|
357
289
|
${U('x')} ${U('age')} "42".
|
|
358
290
|
`,
|
|
359
|
-
expect: [
|
|
360
|
-
new RegExp(`${EX}x>\\s+<${EX}hasAge>\\s+"42"\\s*\\.`),
|
|
361
|
-
],
|
|
291
|
+
expect: [new RegExp(`${EX}x>\\s+<${EX}hasAge>\\s+"42"\\s*\\.`)],
|
|
362
292
|
},
|
|
363
|
-
|
|
364
293
|
{
|
|
365
294
|
name: '10 API option: opt can be an args array',
|
|
366
295
|
opt: ['--no-proof-comments'],
|
|
@@ -368,11 +297,9 @@ ${U('x')} ${U('age')} "42".
|
|
|
368
297
|
{ ${U('s')} ${U('p')} ${U('o')}. } => { ${U('s')} ${U('q')} ${U('o')}. }.
|
|
369
298
|
${U('s')} ${U('p')} ${U('o')}.
|
|
370
299
|
`,
|
|
371
|
-
expect: [
|
|
372
|
-
|
|
373
|
-
],
|
|
300
|
+
expect: [new RegExp(`${EX}s>\\s+<${EX}q>\\s+<${EX}o>\\s*\\.`)],
|
|
301
|
+
notExpect: [/^#/m],
|
|
374
302
|
},
|
|
375
|
-
|
|
376
303
|
{
|
|
377
304
|
name: '11 negative entailment: rule derives false (expect exit 2 => throws)',
|
|
378
305
|
opt: { proofComments: false },
|
|
@@ -382,12 +309,11 @@ ${U('a')} ${U('p')} ${U('b')}.
|
|
|
382
309
|
`,
|
|
383
310
|
expectErrorCode: 2,
|
|
384
311
|
},
|
|
385
|
-
|
|
386
312
|
{
|
|
387
313
|
name: '12 invalid syntax should throw (non-zero exit)',
|
|
388
314
|
opt: { proofComments: false },
|
|
389
315
|
input: `
|
|
390
|
-
@prefix :
|
|
316
|
+
@prefix : # missing dot on purpose
|
|
391
317
|
: s :p :o .
|
|
392
318
|
`,
|
|
393
319
|
expectError: true,
|
|
@@ -401,34 +327,27 @@ ${U('a')} ${U('p')} ${U('b')}.
|
|
|
401
327
|
new RegExp(`${EX}n3>\\s+<${EX}ancestor>\\s+<${EX}n12>\\s*\\.`),
|
|
402
328
|
],
|
|
403
329
|
},
|
|
404
|
-
|
|
405
330
|
{
|
|
406
331
|
name: '14 heavier taxonomy: 60-step subclass chain',
|
|
407
332
|
opt: { proofComments: false, maxBuffer: 200 * 1024 * 1024 },
|
|
408
333
|
input: subclassChainN3(60),
|
|
409
|
-
expect: [
|
|
410
|
-
new RegExp(`${EX}x>\\s+<${EX}type>\\s+<${EX}C61>\\s*\\.`),
|
|
411
|
-
],
|
|
334
|
+
expect: [new RegExp(`${EX}x>\\s+<${EX}type>\\s+<${EX}C61>\\s*\\.`)],
|
|
412
335
|
},
|
|
413
|
-
|
|
414
336
|
{
|
|
415
337
|
name: '15 heavier chaining: 40-step predicate rewrite chain',
|
|
416
338
|
opt: { proofComments: false, maxBuffer: 200 * 1024 * 1024 },
|
|
417
339
|
input: ruleChainN3(40),
|
|
418
|
-
expect: [
|
|
419
|
-
new RegExp(`${EX}s>\\s+<${EX}p40>\\s+<${EX}o>\\s*\\.`),
|
|
420
|
-
],
|
|
340
|
+
expect: [new RegExp(`${EX}s>\\s+<${EX}p40>\\s+<${EX}o>\\s*\\.`)],
|
|
421
341
|
},
|
|
422
342
|
{
|
|
423
343
|
name: '16 heavier recursion: binary tree ancestor closure (depth 4)',
|
|
424
344
|
opt: { proofComments: false, maxBuffer: 200 * 1024 * 1024 },
|
|
425
|
-
input: binaryTreeParentN3(4),
|
|
345
|
+
input: binaryTreeParentN3(4),
|
|
426
346
|
expect: [
|
|
427
|
-
new RegExp(`${EX}t0>\\s+<${EX}ancestor>\\s+<${EX}t30>\\s*\\.`),
|
|
347
|
+
new RegExp(`${EX}t0>\\s+<${EX}ancestor>\\s+<${EX}t30>\\s*\\.`),
|
|
428
348
|
new RegExp(`${EX}t1>\\s+<${EX}ancestor>\\s+<${EX}t22>\\s*\\.`),
|
|
429
349
|
],
|
|
430
350
|
},
|
|
431
|
-
|
|
432
351
|
{
|
|
433
352
|
name: '17 heavier reachability: branching graph reach closure',
|
|
434
353
|
opt: { proofComments: false, maxBuffer: 200 * 1024 * 1024 },
|
|
@@ -438,16 +357,12 @@ ${U('a')} ${U('p')} ${U('b')}.
|
|
|
438
357
|
new RegExp(`${EX}g2>\\s+<${EX}reach>\\s+<${EX}g10>\\s*\\.`),
|
|
439
358
|
],
|
|
440
359
|
},
|
|
441
|
-
|
|
442
360
|
{
|
|
443
361
|
name: '18 heavier taxonomy: diamond subclass inference',
|
|
444
362
|
opt: { proofComments: false },
|
|
445
363
|
input: diamondSubclassN3(),
|
|
446
|
-
expect: [
|
|
447
|
-
new RegExp(`${EX}x>\\s+<${EX}type>\\s+<${EX}D>\\s*\\.`),
|
|
448
|
-
],
|
|
364
|
+
expect: [new RegExp(`${EX}x>\\s+<${EX}type>\\s+<${EX}D>\\s*\\.`)],
|
|
449
365
|
},
|
|
450
|
-
|
|
451
366
|
{
|
|
452
367
|
name: '19 heavier join: 3-hop path rule over a chain of 25 edges',
|
|
453
368
|
opt: { proofComments: false, maxBuffer: 200 * 1024 * 1024 },
|
|
@@ -458,26 +373,21 @@ ${U('a')} ${U('p')} ${U('b')}.
|
|
|
458
373
|
new RegExp(`${EX}j20>\\s+<${EX}p3>\\s+<${EX}j23>\\s*\\.`),
|
|
459
374
|
],
|
|
460
375
|
},
|
|
461
|
-
|
|
462
376
|
{
|
|
463
377
|
name: '20 heavier branching: p produces q and r, then q+r produces qr',
|
|
464
378
|
opt: { proofComments: false },
|
|
465
379
|
input: ruleBranchJoinN3(),
|
|
466
|
-
expect: [
|
|
467
|
-
new RegExp(`${EX}s>\\s+<${EX}qr>\\s+<${EX}o>\\s*\\.`),
|
|
468
|
-
],
|
|
380
|
+
expect: [new RegExp(`${EX}s>\\s+<${EX}qr>\\s+<${EX}o>\\s*\\.`)],
|
|
469
381
|
},
|
|
470
|
-
|
|
471
382
|
{
|
|
472
383
|
name: '21 heavier equivalence: sameAs propagation (with symmetric sameAs)',
|
|
473
384
|
opt: { proofComments: false },
|
|
474
385
|
input: sameAsN3(),
|
|
475
386
|
expect: [
|
|
476
|
-
new RegExp(`${EX}b>\\s+<${EX}p>\\s+<${EX}o>\\s*\\.`),
|
|
477
|
-
new RegExp(`${EX}b>\\s+<${EX}sameAs>\\s+<${EX}a>\\s*\\.`),
|
|
387
|
+
new RegExp(`${EX}b>\\s+<${EX}p>\\s+<${EX}o>\\s*\\.`),
|
|
388
|
+
new RegExp(`${EX}b>\\s+<${EX}sameAs>\\s+<${EX}a>\\s*\\.`),
|
|
478
389
|
],
|
|
479
390
|
},
|
|
480
|
-
|
|
481
391
|
{
|
|
482
392
|
name: '22 heavier closure: transitive property via generic rule',
|
|
483
393
|
opt: { proofComments: false },
|
|
@@ -493,17 +403,15 @@ ${transitiveClosureN3('sub')}
|
|
|
493
403
|
new RegExp(`${EX}b>\\s+<${EX}sub>\\s+<${EX}d>\\s*\\.`),
|
|
494
404
|
],
|
|
495
405
|
},
|
|
496
|
-
|
|
497
406
|
{
|
|
498
407
|
name: '23 heavier social: symmetric + reachFriend closure',
|
|
499
408
|
opt: { proofComments: false, maxBuffer: 200 * 1024 * 1024 },
|
|
500
409
|
input: symmetricTransitiveN3(),
|
|
501
410
|
expect: [
|
|
502
411
|
new RegExp(`${EX}a>\\s+<${EX}reachFriend>\\s+<${EX}d>\\s*\\.`),
|
|
503
|
-
new RegExp(`${EX}d>\\s+<${EX}reachFriend>\\s+<${EX}a>\\s*\\.`),
|
|
412
|
+
new RegExp(`${EX}d>\\s+<${EX}reachFriend>\\s+<${EX}a>\\s*\\.`),
|
|
504
413
|
],
|
|
505
414
|
},
|
|
506
|
-
|
|
507
415
|
{
|
|
508
416
|
name: '24 heavier volume: 400 facts, simple rewrite rule p -> q',
|
|
509
417
|
opt: { proofComments: false, maxBuffer: 200 * 1024 * 1024 },
|
|
@@ -513,44 +421,207 @@ ${transitiveClosureN3('sub')}
|
|
|
513
421
|
new RegExp(`${EX}x>\\s+<${EX}q>\\s+<${EX}o399>\\s*\\.`),
|
|
514
422
|
],
|
|
515
423
|
},
|
|
516
|
-
|
|
517
424
|
{
|
|
518
425
|
name: '25 heavier negative entailment: batch + forbidden => false (expect exit 2)',
|
|
519
426
|
opt: { proofComments: false, maxBuffer: 200 * 1024 * 1024 },
|
|
520
427
|
input: negativeEntailmentBatchN3(200),
|
|
521
428
|
expectErrorCode: 2,
|
|
522
429
|
},
|
|
523
|
-
|
|
430
|
+
{
|
|
431
|
+
name: '26 sanity: no rules => no newly derived facts',
|
|
432
|
+
opt: { proofComments: false },
|
|
433
|
+
input: `
|
|
434
|
+
${U('a')} ${U('p')} ${U('b')}.
|
|
435
|
+
`,
|
|
436
|
+
expect: [/^\s*$/],
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
name: '27 regression: backward rule (<=) can satisfy a forward rule premise',
|
|
440
|
+
opt: { proofComments: false },
|
|
441
|
+
input: `
|
|
442
|
+
${U('a')} ${U('p')} ${U('b')}.
|
|
524
443
|
|
|
525
|
-
|
|
526
|
-
|
|
444
|
+
{ ${U('a')} ${U('q')} ${U('b')}. } <= { ${U('a')} ${U('p')} ${U('b')}. }.
|
|
445
|
+
{ ${U('a')} ${U('q')} ${U('b')}. } => { ${U('a')} ${U('r')} ${U('b')}. }.
|
|
446
|
+
`,
|
|
447
|
+
expect: [new RegExp(`${EX}a>\\s+<${EX}r>\\s+<${EX}b>\\s*\\.`)],
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
name: '28 regression: top-level log:implies behaves like a forward rule',
|
|
451
|
+
opt: { proofComments: false },
|
|
452
|
+
input: `
|
|
453
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
527
454
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
}
|
|
455
|
+
{ ${U('a')} ${U('p')} ${U('b')}. } log:implies { ${U('a')} ${U('q')} ${U('b')}. }.
|
|
456
|
+
${U('a')} ${U('p')} ${U('b')}.
|
|
457
|
+
`,
|
|
458
|
+
expect: [new RegExp(`${EX}a>\\s+<${EX}q>\\s+<${EX}b>\\s*\\.`)],
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
name: '29 regression: derived log:implies becomes a live rule during reasoning',
|
|
462
|
+
opt: { proofComments: false },
|
|
463
|
+
input: `
|
|
464
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
532
465
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
}
|
|
466
|
+
{ ${U('a')} ${U('trigger')} ${U('go')}. }
|
|
467
|
+
=>
|
|
468
|
+
{ { ${U('a')} ${U('p')} ${U('b')}. } log:implies { ${U('a')} ${U('q2')} ${U('b')}. }. }.
|
|
537
469
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
}
|
|
470
|
+
${U('a')} ${U('trigger')} ${U('go')}.
|
|
471
|
+
${U('a')} ${U('p')} ${U('b')}.
|
|
472
|
+
`,
|
|
473
|
+
expect: [new RegExp(`${EX}a>\\s+<${EX}q2>\\s+<${EX}b>\\s*\\.`)],
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
name: '30 sanity: proofComments:true enables proof comments',
|
|
477
|
+
opt: { proofComments: true },
|
|
478
|
+
input: `
|
|
479
|
+
{ ${U('s')} ${U('p')} ${U('o')}. } => { ${U('s')} ${U('q')} ${U('o')}. }.
|
|
480
|
+
${U('s')} ${U('p')} ${U('o')}.
|
|
481
|
+
`,
|
|
482
|
+
expect: [/^#/m, new RegExp(`${EX}s>\\s+<${EX}q>\\s+<${EX}o>\\s*\\.`)],
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
name: '31 sanity: -n suppresses proof comments',
|
|
486
|
+
opt: ['-n'],
|
|
487
|
+
input: `
|
|
488
|
+
{ ${U('s')} ${U('p')} ${U('o')}. } => { ${U('s')} ${U('q')} ${U('o')}. }.
|
|
489
|
+
${U('s')} ${U('p')} ${U('o')}.
|
|
490
|
+
`,
|
|
491
|
+
expect: [new RegExp(`${EX}s>\\s+<${EX}q>\\s+<${EX}o>\\s*\\.`)],
|
|
492
|
+
notExpect: [/^#/m],
|
|
493
|
+
},
|
|
542
494
|
|
|
543
|
-
//
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}
|
|
495
|
+
// -------------------------
|
|
496
|
+
// Added sanity/regression tests
|
|
497
|
+
// -------------------------
|
|
547
498
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
499
|
+
{
|
|
500
|
+
name: '32 sanity: variable rule fires for multiple matching facts',
|
|
501
|
+
opt: { proofComments: false },
|
|
502
|
+
input: `
|
|
503
|
+
${U('a')} ${U('p')} ${U('b')}.
|
|
504
|
+
${U('c')} ${U('p')} ${U('d')}.
|
|
552
505
|
|
|
553
|
-
|
|
506
|
+
{ ?s ${U('p')} ?o. } => { ?s ${U('q')} ?o. }.
|
|
507
|
+
`,
|
|
508
|
+
expect: [
|
|
509
|
+
new RegExp(`${EX}a>\\s+<${EX}q>\\s+<${EX}b>\\s*\\.`),
|
|
510
|
+
new RegExp(`${EX}c>\\s+<${EX}q>\\s+<${EX}d>\\s*\\.`),
|
|
511
|
+
],
|
|
512
|
+
},
|
|
513
|
+
|
|
514
|
+
{
|
|
515
|
+
name: '33 regression: mutual cycle does not echo already-known facts',
|
|
516
|
+
opt: { proofComments: false },
|
|
517
|
+
input: `
|
|
518
|
+
${U('s')} ${U('p')} ${U('o')}.
|
|
519
|
+
|
|
520
|
+
{ ?x ${U('p')} ?y. } => { ?x ${U('q')} ?y. }.
|
|
521
|
+
{ ?x ${U('q')} ?y. } => { ?x ${U('p')} ?y. }.
|
|
522
|
+
`,
|
|
523
|
+
expect: [new RegExp(`${EX}s>\\s+<${EX}q>\\s+<${EX}o>\\s*\\.`)],
|
|
524
|
+
notExpect: [new RegExp(`${EX}s>\\s+<${EX}p>\\s+<${EX}o>\\s*\\.`)],
|
|
525
|
+
},
|
|
526
|
+
|
|
527
|
+
{
|
|
528
|
+
name: '34 sanity: rule that reproduces same triple produces no output',
|
|
529
|
+
opt: { proofComments: false },
|
|
530
|
+
input: `
|
|
531
|
+
${U('s')} ${U('p')} ${U('o')}.
|
|
532
|
+
{ ${U('s')} ${U('p')} ${U('o')}. } => { ${U('s')} ${U('p')} ${U('o')}. }.
|
|
533
|
+
`,
|
|
534
|
+
expect: [/^\s*$/],
|
|
535
|
+
},
|
|
536
|
+
|
|
537
|
+
{
|
|
538
|
+
name: '35 regression: fuse from derived fact',
|
|
539
|
+
opt: { proofComments: false },
|
|
540
|
+
input: `
|
|
541
|
+
${U('a')} ${U('p')} ${U('b')}.
|
|
542
|
+
|
|
543
|
+
{ ${U('a')} ${U('p')} ${U('b')}. } => { ${U('a')} ${U('q')} ${U('b')}. }.
|
|
544
|
+
{ ${U('a')} ${U('q')} ${U('b')}. } => false.
|
|
545
|
+
`,
|
|
546
|
+
expectErrorCode: 2,
|
|
547
|
+
},
|
|
548
|
+
|
|
549
|
+
{
|
|
550
|
+
name: '36 sanity: multiple consequents in one rule',
|
|
551
|
+
opt: { proofComments: false },
|
|
552
|
+
input: `
|
|
553
|
+
${U('s')} ${U('p')} ${U('o')}.
|
|
554
|
+
|
|
555
|
+
{ ${U('s')} ${U('p')} ${U('o')}. } => { ${U('s')} ${U('q')} ${U('o')}. ${U('s')} ${U('r')} ${U('o')}. }.
|
|
556
|
+
`,
|
|
557
|
+
expect: [
|
|
558
|
+
new RegExp(`${EX}s>\\s+<${EX}q>\\s+<${EX}o>\\s*\\.`),
|
|
559
|
+
new RegExp(`${EX}s>\\s+<${EX}r>\\s+<${EX}o>\\s*\\.`),
|
|
560
|
+
],
|
|
561
|
+
},
|
|
562
|
+
|
|
563
|
+
{
|
|
564
|
+
name: '37 regression: backward chaining can chain (<= then <= then =>)',
|
|
565
|
+
opt: { proofComments: false },
|
|
566
|
+
input: `
|
|
567
|
+
${U('a')} ${U('p')} ${U('b')}.
|
|
568
|
+
|
|
569
|
+
{ ${U('a')} ${U('q')} ${U('b')}. } <= { ${U('a')} ${U('p')} ${U('b')}. }.
|
|
570
|
+
{ ${U('a')} ${U('r')} ${U('b')}. } <= { ${U('a')} ${U('q')} ${U('b')}. }.
|
|
571
|
+
{ ${U('a')} ${U('r')} ${U('b')}. } => { ${U('a')} ${U('s')} ${U('b')}. }.
|
|
572
|
+
`,
|
|
573
|
+
expect: [new RegExp(`${EX}a>\\s+<${EX}s>\\s+<${EX}b>\\s*\\.`)],
|
|
574
|
+
},
|
|
575
|
+
|
|
576
|
+
{
|
|
577
|
+
name: '38 regression: backward rule body can require multiple facts',
|
|
578
|
+
opt: { proofComments: false },
|
|
579
|
+
input: `
|
|
580
|
+
${U('a')} ${U('p')} ${U('b')}.
|
|
581
|
+
${U('a')} ${U('p2')} ${U('b')}.
|
|
582
|
+
|
|
583
|
+
{ ${U('a')} ${U('q')} ${U('b')}. } <= { ${U('a')} ${U('p')} ${U('b')}. ${U('a')} ${U('p2')} ${U('b')}. }.
|
|
584
|
+
{ ${U('a')} ${U('q')} ${U('b')}. } => { ${U('a')} ${U('r')} ${U('b')}. }.
|
|
585
|
+
`,
|
|
586
|
+
expect: [new RegExp(`${EX}a>\\s+<${EX}r>\\s+<${EX}b>\\s*\\.`)],
|
|
587
|
+
},
|
|
588
|
+
|
|
589
|
+
{
|
|
590
|
+
name: '39 sanity: backward rule fails when a required fact is missing',
|
|
591
|
+
opt: { proofComments: false },
|
|
592
|
+
input: `
|
|
593
|
+
${U('a')} ${U('p')} ${U('b')}.
|
|
594
|
+
|
|
595
|
+
{ ${U('a')} ${U('q')} ${U('b')}. } <= { ${U('a')} ${U('p')} ${U('b')}. ${U('a')} ${U('p2')} ${U('b')}. }.
|
|
596
|
+
{ ${U('a')} ${U('q')} ${U('b')}. } => { ${U('a')} ${U('r')} ${U('b')}. }.
|
|
597
|
+
`,
|
|
598
|
+
expect: [/^\s*$/],
|
|
599
|
+
},
|
|
600
|
+
|
|
601
|
+
{
|
|
602
|
+
name: '40 sanity: comments and whitespace are tolerated',
|
|
603
|
+
opt: { proofComments: false },
|
|
604
|
+
input: `
|
|
605
|
+
# leading comment
|
|
606
|
+
{ ${U('s')} ${U('p')} ${U('o')}. } => { ${U('s')} ${U('q')} ${U('o')}. }. # trailing comment
|
|
607
|
+
|
|
608
|
+
${U('s')} ${U('p')} ${U('o')}. # another trailing comment
|
|
609
|
+
`,
|
|
610
|
+
expect: [new RegExp(`${EX}s>\\s+<${EX}q>\\s+<${EX}o>\\s*\\.`)],
|
|
611
|
+
},
|
|
612
|
+
|
|
613
|
+
{
|
|
614
|
+
name: '41 stability: diamond subclass derives D only once',
|
|
615
|
+
opt: { proofComments: false },
|
|
616
|
+
input: diamondSubclassN3(),
|
|
617
|
+
expect: [new RegExp(`${EX}x>\\s+<${EX}type>\\s+<${EX}D>\\s*\\.`)],
|
|
618
|
+
// and ensure it doesn't print the same derived triple twice via the two paths
|
|
619
|
+
check(out) {
|
|
620
|
+
const reD = new RegExp(`${EX}x>\\s+<${EX}type>\\s+<${EX}D>\\s*\\.`, 'm');
|
|
621
|
+
mustOccurExactly(out, reD, 1, 'diamond subclass should not duplicate x type D');
|
|
622
|
+
},
|
|
623
|
+
},
|
|
624
|
+
];
|
|
554
625
|
|
|
555
626
|
let passed = 0;
|
|
556
627
|
let failed = 0;
|
|
@@ -568,8 +639,10 @@ let failed = 0;
|
|
|
568
639
|
throw new Error(`Expected an error, but reason() returned output:\n${out}`);
|
|
569
640
|
}
|
|
570
641
|
|
|
571
|
-
for (const re of
|
|
572
|
-
for (const re of
|
|
642
|
+
for (const re of tc.expect || []) mustMatch(out, re, `${tc.name}: missing expected pattern ${re}`);
|
|
643
|
+
for (const re of tc.notExpect || []) mustNotMatch(out, re, `${tc.name}: unexpected pattern ${re}`);
|
|
644
|
+
|
|
645
|
+
if (typeof tc.check === 'function') tc.check(out);
|
|
573
646
|
|
|
574
647
|
const dur = msNow() - start;
|
|
575
648
|
ok(`${tc.name} ${C.dim}(${dur} ms)${C.n}`);
|
|
@@ -577,7 +650,6 @@ let failed = 0;
|
|
|
577
650
|
} catch (e) {
|
|
578
651
|
const dur = msNow() - start;
|
|
579
652
|
|
|
580
|
-
// Expected error handling
|
|
581
653
|
if (tc.expectErrorCode != null) {
|
|
582
654
|
if (e && typeof e === 'object' && 'code' in e && e.code === tc.expectErrorCode) {
|
|
583
655
|
ok(`${tc.name} ${C.dim}(expected exit ${tc.expectErrorCode}, ${dur} ms)${C.n}`);
|
|
@@ -585,7 +657,11 @@ let failed = 0;
|
|
|
585
657
|
continue;
|
|
586
658
|
}
|
|
587
659
|
fail(`${tc.name} ${C.dim}(${dur} ms)${C.n}`);
|
|
588
|
-
fail(
|
|
660
|
+
fail(
|
|
661
|
+
`Expected exit code ${tc.expectErrorCode}, got: ${e && e.code != null ? e.code : 'unknown'}\n${
|
|
662
|
+
e && e.stderr ? e.stderr : e && e.stack ? e.stack : String(e)
|
|
663
|
+
}`
|
|
664
|
+
);
|
|
589
665
|
failed++;
|
|
590
666
|
continue;
|
|
591
667
|
}
|