eyeleng 1.0.8 → 1.0.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/dist/browser/eyeleng.browser.js +112 -78
- package/eyeleng.js +112 -78
- package/package.json +5 -2
- package/src/analyze.js +8 -10
- package/src/builtins.js +3 -1
- package/src/engine.js +36 -33
- package/src/parser.js +1 -1
- package/src/store.js +2 -2
- package/src/term.js +4 -2
- package/src/tokenizer.js +58 -29
- package/test/perf-baseline.json +39 -0
- package/tools/perf-bench.js +234 -0
package/eyeleng.js
CHANGED
|
@@ -1136,7 +1136,7 @@
|
|
|
1136
1136
|
peekN(n) { return this.tokens[this.pos + n] || this.tokens[this.tokens.length - 1]; }
|
|
1137
1137
|
previous() { return this.tokens[this.pos - 1]; }
|
|
1138
1138
|
strictGrammar() { return !!this.options.strictGrammar; }
|
|
1139
|
-
error(message, token = this.peek()) { return new SyntaxErrorWithLocation(message, token); }
|
|
1139
|
+
error(message, token = this.peek()) { return new SyntaxErrorWithLocation(message, token && token.filename ? token : { ...token, filename: this.options.filename || '<input>' }); }
|
|
1140
1140
|
}
|
|
1141
1141
|
|
|
1142
1142
|
|
|
@@ -1285,7 +1285,7 @@
|
|
|
1285
1285
|
|
|
1286
1286
|
function current() { return source[i]; }
|
|
1287
1287
|
function peek(n = 1) { return source[i + n]; }
|
|
1288
|
-
function startsWith(text) { return source.
|
|
1288
|
+
function startsWith(text) { return source.startsWith(text, i); }
|
|
1289
1289
|
function advance() {
|
|
1290
1290
|
const ch = source[i++];
|
|
1291
1291
|
if (ch === '\n') { line += 1; column = 1; }
|
|
@@ -1293,7 +1293,7 @@
|
|
|
1293
1293
|
return ch;
|
|
1294
1294
|
}
|
|
1295
1295
|
function token(type, value, startLine, startColumn, extra = {}) {
|
|
1296
|
-
tokens.push({ type, value, line: startLine, column: startColumn,
|
|
1296
|
+
tokens.push({ type, value, line: startLine, column: startColumn, ...extra });
|
|
1297
1297
|
}
|
|
1298
1298
|
function syntax(message, startLine, startColumn) {
|
|
1299
1299
|
throw new SyntaxErrorWithLocation(message, { line: startLine, column: startColumn, filename });
|
|
@@ -1301,19 +1301,21 @@
|
|
|
1301
1301
|
|
|
1302
1302
|
function readNumericLiteral() {
|
|
1303
1303
|
let value = '';
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1304
|
+
const start = i;
|
|
1305
|
+
while (i < source.length && isDigitCode(source.charCodeAt(i))) { i += 1; column += 1; }
|
|
1306
|
+
if (source[i] === '.' && isDigitCode(source.charCodeAt(i + 1))) {
|
|
1307
|
+
i += 1; column += 1;
|
|
1308
|
+
while (i < source.length && isDigitCode(source.charCodeAt(i))) { i += 1; column += 1; }
|
|
1308
1309
|
}
|
|
1310
|
+
value = source.slice(start, i);
|
|
1309
1311
|
if (current() === 'e' || current() === 'E') {
|
|
1310
1312
|
const saveI = i;
|
|
1311
1313
|
const saveLine = line;
|
|
1312
1314
|
const saveColumn = column;
|
|
1313
1315
|
let exponent = advance();
|
|
1314
1316
|
if (current() === '+' || current() === '-') exponent += advance();
|
|
1315
|
-
if (
|
|
1316
|
-
while (i < source.length &&
|
|
1317
|
+
if (isDigitCode(source.charCodeAt(i))) {
|
|
1318
|
+
while (i < source.length && isDigitCode(source.charCodeAt(i))) exponent += advance();
|
|
1317
1319
|
value += exponent;
|
|
1318
1320
|
} else {
|
|
1319
1321
|
i = saveI;
|
|
@@ -1331,7 +1333,7 @@
|
|
|
1331
1333
|
const length = esc === 'u' ? 4 : 8;
|
|
1332
1334
|
let hex = '';
|
|
1333
1335
|
for (let j = 0; j < length; j += 1) {
|
|
1334
|
-
if (
|
|
1336
|
+
if (!isHexCode(source.charCodeAt(i))) syntax(`Invalid \\${esc} escape`, startLine, startColumn);
|
|
1335
1337
|
hex += advance();
|
|
1336
1338
|
}
|
|
1337
1339
|
const codePoint = Number.parseInt(hex, 16);
|
|
@@ -1350,7 +1352,7 @@
|
|
|
1350
1352
|
const length = esc === 'u' ? 4 : 8;
|
|
1351
1353
|
let hex = '';
|
|
1352
1354
|
for (let j = 0; j < length; j += 1) {
|
|
1353
|
-
if (
|
|
1355
|
+
if (!isHexCode(source.charCodeAt(i))) syntax(`Invalid \\${esc} escape`, startLine, startColumn);
|
|
1354
1356
|
hex += advance();
|
|
1355
1357
|
}
|
|
1356
1358
|
const codePoint = Number.parseInt(hex, 16);
|
|
@@ -1364,7 +1366,7 @@
|
|
|
1364
1366
|
|
|
1365
1367
|
while (i < source.length) {
|
|
1366
1368
|
const ch = current();
|
|
1367
|
-
if (
|
|
1369
|
+
if (isWhitespaceCode(source.charCodeAt(i))) { advance(); continue; }
|
|
1368
1370
|
if (ch === '#') {
|
|
1369
1371
|
while (i < source.length && current() !== '\n') advance();
|
|
1370
1372
|
continue;
|
|
@@ -1450,18 +1452,21 @@
|
|
|
1450
1452
|
}
|
|
1451
1453
|
|
|
1452
1454
|
if (ch === '@') {
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
+
const wordStart = i;
|
|
1456
|
+
advance();
|
|
1457
|
+
while (i < source.length && isLangTagCode(source.charCodeAt(i))) { i += 1; column += 1; }
|
|
1458
|
+
const value = source.slice(wordStart, i);
|
|
1455
1459
|
if (!/^@[A-Za-z]+(?:-[A-Za-z0-9]+)*(?:--[A-Za-z]+)?$/.test(value)) syntax(`Invalid language tag ${value}`, startLine, startColumn);
|
|
1456
1460
|
token('word', value, startLine, startColumn);
|
|
1457
1461
|
continue;
|
|
1458
1462
|
}
|
|
1459
1463
|
|
|
1460
1464
|
if (ch === '?' || ch === '$') {
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
+
const varStart = i;
|
|
1466
|
+
advance();
|
|
1467
|
+
while (i < source.length && isVarNameCode(source.charCodeAt(i))) { i += 1; column += 1; }
|
|
1468
|
+
if (i - varStart === 1) syntax('Expected variable name', startLine, startColumn);
|
|
1469
|
+
token('variable', source.slice(varStart + 1, i), startLine, startColumn);
|
|
1465
1470
|
continue;
|
|
1466
1471
|
}
|
|
1467
1472
|
|
|
@@ -1488,24 +1493,27 @@
|
|
|
1488
1493
|
continue;
|
|
1489
1494
|
}
|
|
1490
1495
|
|
|
1491
|
-
|
|
1496
|
+
const wordStart = i;
|
|
1492
1497
|
while (i < source.length) {
|
|
1493
|
-
const c =
|
|
1494
|
-
if (c === '\\' &&
|
|
1495
|
-
|
|
1496
|
-
|
|
1498
|
+
const c = source[i];
|
|
1499
|
+
if (c === '\\' && source[i + 1] !== undefined) {
|
|
1500
|
+
i += 2;
|
|
1501
|
+
column += 2;
|
|
1497
1502
|
continue;
|
|
1498
1503
|
}
|
|
1499
|
-
|
|
1504
|
+
const code = source.charCodeAt(i);
|
|
1505
|
+
if (isWhitespaceCode(code) || '{}()[],;|'.includes(c) || '=<>+-*/!^~'.includes(c)) break;
|
|
1500
1506
|
if (c === '.') {
|
|
1501
|
-
const n =
|
|
1502
|
-
if (n === undefined ||
|
|
1507
|
+
const n = source[i + 1];
|
|
1508
|
+
if (n === undefined || isWhitespaceCode(n.charCodeAt(0)) || '{}()[],;|'.includes(n) || '=<>+-*/!^~'.includes(n)) break;
|
|
1503
1509
|
}
|
|
1504
1510
|
if (c === '#') break;
|
|
1505
|
-
|
|
1511
|
+
i += 1;
|
|
1512
|
+
column += 1;
|
|
1506
1513
|
}
|
|
1507
|
-
if (
|
|
1514
|
+
if (i === wordStart) syntax(`Unexpected character ${JSON.stringify(ch)}`, startLine, startColumn);
|
|
1508
1515
|
|
|
1516
|
+
const value = source.slice(wordStart, i);
|
|
1509
1517
|
if (/^[+-]?(?:(?:\d+\.\d*|\.\d+)(?:[eE][+-]?\d+)?|\d+[eE][+-]?\d+|\d+)$/.test(value)) token('number', Number(value), startLine, startColumn);
|
|
1510
1518
|
else token('word', value, startLine, startColumn);
|
|
1511
1519
|
}
|
|
@@ -1514,11 +1522,32 @@
|
|
|
1514
1522
|
return tokens;
|
|
1515
1523
|
}
|
|
1516
1524
|
|
|
1525
|
+
|
|
1526
|
+
function isDigitCode(code) {
|
|
1527
|
+
return code >= 48 && code <= 57;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
function isHexCode(code) {
|
|
1531
|
+
return (code >= 48 && code <= 57) || (code >= 65 && code <= 70) || (code >= 97 && code <= 102);
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
function isWhitespaceCode(code) {
|
|
1535
|
+
return code === 32 || code === 9 || code === 10 || code === 13 || code === 12;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
function isLangTagCode(code) {
|
|
1539
|
+
return (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || (code >= 48 && code <= 57) || code === 45;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
function isVarNameCode(code) {
|
|
1543
|
+
return (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || (code >= 48 && code <= 57) || code === 95 || code === 45;
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1517
1546
|
function startsNumericLiteral(source, i) {
|
|
1518
1547
|
const ch = source[i];
|
|
1519
1548
|
const next = source[i + 1];
|
|
1520
|
-
if (
|
|
1521
|
-
if (ch === '.' &&
|
|
1549
|
+
if (isDigitCode(ch.charCodeAt(0))) return true;
|
|
1550
|
+
if (ch === '.' && next !== undefined && isDigitCode(next.charCodeAt(0))) return true;
|
|
1522
1551
|
return false;
|
|
1523
1552
|
}
|
|
1524
1553
|
|
|
@@ -3148,11 +3177,13 @@
|
|
|
3148
3177
|
}
|
|
3149
3178
|
|
|
3150
3179
|
function variable(name) {
|
|
3151
|
-
|
|
3180
|
+
const value = String(name);
|
|
3181
|
+
return { type: 'var', value: value[0] === '?' || value[0] === '$' ? value.slice(1) : value };
|
|
3152
3182
|
}
|
|
3153
3183
|
|
|
3154
3184
|
function blankNode(value) {
|
|
3155
|
-
|
|
3185
|
+
const label = String(value);
|
|
3186
|
+
return { type: 'blank', value: label.startsWith('_:') ? label.slice(2) : label };
|
|
3156
3187
|
}
|
|
3157
3188
|
|
|
3158
3189
|
function literal(value, datatype = null, lang = null, langDir = null) {
|
|
@@ -3407,6 +3438,8 @@
|
|
|
3407
3438
|
const RDF_LANGSTRING = `${RDF_NS}langString`;
|
|
3408
3439
|
const RDF_DIRLANGSTRING = `${RDF_NS}dirLangString`;
|
|
3409
3440
|
const NUMERIC_DATATYPES = new Set([XSD_INTEGER, XSD_DECIMAL, XSD_DOUBLE]);
|
|
3441
|
+
const MAX_SAFE_INTEGER_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
|
|
3442
|
+
const MIN_SAFE_INTEGER_BIGINT = BigInt(Number.MIN_SAFE_INTEGER);
|
|
3410
3443
|
|
|
3411
3444
|
// This table is intentionally shaped by the SHACL 1.2 Rules grammar production BuiltInCall.
|
|
3412
3445
|
// Keys are the canonical spellings used by the draft; lookup is case-insensitive so examples
|
|
@@ -3566,7 +3599,7 @@
|
|
|
3566
3599
|
}
|
|
3567
3600
|
|
|
3568
3601
|
function fromIntegerResult(value) {
|
|
3569
|
-
if (value <=
|
|
3602
|
+
if (value <= MAX_SAFE_INTEGER_BIGINT && value >= MIN_SAFE_INTEGER_BIGINT) return Number(value);
|
|
3570
3603
|
return value;
|
|
3571
3604
|
}
|
|
3572
3605
|
|
|
@@ -4277,6 +4310,18 @@
|
|
|
4277
4310
|
layerIndexes,
|
|
4278
4311
|
analysis.dependency ? analysis.dependency.edges : [],
|
|
4279
4312
|
);
|
|
4313
|
+
const baseContext = {
|
|
4314
|
+
...evalOptions,
|
|
4315
|
+
maxIterations,
|
|
4316
|
+
inputKeys,
|
|
4317
|
+
inferred,
|
|
4318
|
+
trace,
|
|
4319
|
+
perRule,
|
|
4320
|
+
layer: 0,
|
|
4321
|
+
iteration: 0,
|
|
4322
|
+
startingIterations: 0,
|
|
4323
|
+
recursiveLayer: false,
|
|
4324
|
+
};
|
|
4280
4325
|
|
|
4281
4326
|
for (let layerIndex = 0; layerIndex < layerIndexes.length; layerIndex += 1) {
|
|
4282
4327
|
const layer = layerIndexes[layerIndex];
|
|
@@ -4286,30 +4331,17 @@
|
|
|
4286
4331
|
if (runOnce.length > 0) {
|
|
4287
4332
|
iterations += 1;
|
|
4288
4333
|
for (const ruleIndex of runOnce) {
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
inferred,
|
|
4293
|
-
trace,
|
|
4294
|
-
perRule,
|
|
4295
|
-
layer: layerIndex + 1,
|
|
4296
|
-
iteration: iterations,
|
|
4297
|
-
});
|
|
4334
|
+
baseContext.layer = layerIndex + 1;
|
|
4335
|
+
baseContext.iteration = iterations;
|
|
4336
|
+
const added = applyRuleOnce(program, store, ruleIndex, baseContext);
|
|
4298
4337
|
ruleApplications += added.applications;
|
|
4299
4338
|
}
|
|
4300
4339
|
}
|
|
4301
4340
|
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
inferred,
|
|
4307
|
-
trace,
|
|
4308
|
-
perRule,
|
|
4309
|
-
layer: layerIndex + 1,
|
|
4310
|
-
startingIterations: iterations,
|
|
4311
|
-
recursiveLayer: recursiveLayerFlags[layerIndex],
|
|
4312
|
-
});
|
|
4341
|
+
baseContext.layer = layerIndex + 1;
|
|
4342
|
+
baseContext.startingIterations = iterations;
|
|
4343
|
+
baseContext.recursiveLayer = recursiveLayerFlags[layerIndex];
|
|
4344
|
+
const ordinaryResult = runRulesToFixpoint(program, store, ordinary, baseContext);
|
|
4313
4345
|
iterations = ordinaryResult.iterations;
|
|
4314
4346
|
ruleApplications += ordinaryResult.ruleApplications;
|
|
4315
4347
|
}
|
|
@@ -4340,10 +4372,8 @@
|
|
|
4340
4372
|
const iteration = context.startingIterations + 1;
|
|
4341
4373
|
let ruleApplications = 0;
|
|
4342
4374
|
for (const ruleIndex of ruleIndexes) {
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
iteration,
|
|
4346
|
-
});
|
|
4375
|
+
context.iteration = iteration;
|
|
4376
|
+
const applied = applyRuleOnce(program, store, ruleIndex, context);
|
|
4347
4377
|
ruleApplications += applied.applications;
|
|
4348
4378
|
}
|
|
4349
4379
|
return { iterations: iteration, ruleApplications };
|
|
@@ -4359,10 +4389,8 @@
|
|
|
4359
4389
|
let addedInIteration = 0;
|
|
4360
4390
|
|
|
4361
4391
|
for (const ruleIndex of ruleIndexes) {
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
iteration: iterations,
|
|
4365
|
-
});
|
|
4392
|
+
context.iteration = iterations;
|
|
4393
|
+
const applied = applyRuleOnce(program, store, ruleIndex, context);
|
|
4366
4394
|
addedInIteration += applied.added;
|
|
4367
4395
|
ruleApplications += applied.applications;
|
|
4368
4396
|
}
|
|
@@ -4391,16 +4419,24 @@
|
|
|
4391
4419
|
return flags;
|
|
4392
4420
|
}
|
|
4393
4421
|
|
|
4422
|
+
|
|
4394
4423
|
function applyRuleOnce(program, store, ruleIndex, context) {
|
|
4395
4424
|
const rule = program.rules[ruleIndex];
|
|
4396
4425
|
let applications = 0;
|
|
4397
4426
|
let added = 0;
|
|
4398
|
-
const
|
|
4427
|
+
const dedupeBindings = rule.body.some((clause) => clause.type === 'path');
|
|
4428
|
+
const seenBindings = dedupeBindings ? new Set() : null;
|
|
4399
4429
|
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4430
|
+
const bodyBindings = rule.body.length === 1 && rule.body[0].type === 'triple'
|
|
4431
|
+
? store.match(rule.body[0].triple, {})
|
|
4432
|
+
: evaluateBodyStream(rule.body, store, {}, context);
|
|
4433
|
+
|
|
4434
|
+
for (const binding of bodyBindings) {
|
|
4435
|
+
if (seenBindings) {
|
|
4436
|
+
const key = bindingKey(binding);
|
|
4437
|
+
if (seenBindings.has(key)) continue;
|
|
4438
|
+
seenBindings.add(key);
|
|
4439
|
+
}
|
|
4404
4440
|
applications += 1;
|
|
4405
4441
|
context.perRule[ruleIndex].applications += 1;
|
|
4406
4442
|
|
|
@@ -4519,7 +4555,7 @@
|
|
|
4519
4555
|
"src/store.js": function (require, module, exports) {
|
|
4520
4556
|
'use strict';
|
|
4521
4557
|
|
|
4522
|
-
const { tripleKey, termKey, termEquals
|
|
4558
|
+
const { tripleKey, termKey, termEquals } = require('./term.js');
|
|
4523
4559
|
|
|
4524
4560
|
class TripleStore {
|
|
4525
4561
|
constructor(triples = []) {
|
|
@@ -4628,7 +4664,7 @@
|
|
|
4628
4664
|
}
|
|
4629
4665
|
|
|
4630
4666
|
function normalizeTriple(triple) {
|
|
4631
|
-
return { s:
|
|
4667
|
+
return { s: triple.s, p: triple.p, o: triple.o };
|
|
4632
4668
|
}
|
|
4633
4669
|
|
|
4634
4670
|
function bindingKey(binding) {
|
|
@@ -4788,9 +4824,7 @@
|
|
|
4788
4824
|
|
|
4789
4825
|
program.rules.forEach((rule, index) => {
|
|
4790
4826
|
const name = ruleName(rule, index);
|
|
4791
|
-
const label = displayRuleName(name, program.prefixes || {});
|
|
4792
4827
|
const bound = boundVariables(rule.body);
|
|
4793
|
-
const positive = positiveVariables(rule.body);
|
|
4794
4828
|
const head = new Set();
|
|
4795
4829
|
for (const triple of rule.head) collectTripleVars(triple, head);
|
|
4796
4830
|
|
|
@@ -4800,7 +4834,7 @@
|
|
|
4800
4834
|
code: 'unsafe-head-variable',
|
|
4801
4835
|
severity: 'error',
|
|
4802
4836
|
rule: name,
|
|
4803
|
-
message: `${
|
|
4837
|
+
message: `${displayRuleName(name, program.prefixes || {})} has unbound head variable ?${variable}`,
|
|
4804
4838
|
});
|
|
4805
4839
|
}
|
|
4806
4840
|
}
|
|
@@ -4811,19 +4845,19 @@
|
|
|
4811
4845
|
code: 'invalid-head-predicate',
|
|
4812
4846
|
severity: 'error',
|
|
4813
4847
|
rule: name,
|
|
4814
|
-
message: `${
|
|
4848
|
+
message: `${displayRuleName(name, program.prefixes || {})} has a non-IRI/non-variable predicate in the head`,
|
|
4815
4849
|
});
|
|
4816
4850
|
}
|
|
4817
4851
|
}
|
|
4818
4852
|
|
|
4819
|
-
diagnostics.push(...sequentialWellFormednessDiagnostics(rule.body, name,
|
|
4853
|
+
diagnostics.push(...sequentialWellFormednessDiagnostics(rule.body, name, program.prefixes || {}));
|
|
4820
4854
|
|
|
4821
4855
|
if (rule.runOnce && recursiveIndexes.has(index)) {
|
|
4822
4856
|
diagnostics.push({
|
|
4823
4857
|
code: 'recursive-assignment-rule',
|
|
4824
4858
|
severity: 'warning',
|
|
4825
4859
|
rule: name,
|
|
4826
|
-
message: `${
|
|
4860
|
+
message: `${displayRuleName(name, program.prefixes || {})} is a run-once rule in a recursive dependency cycle`,
|
|
4827
4861
|
});
|
|
4828
4862
|
}
|
|
4829
4863
|
|
|
@@ -5179,7 +5213,7 @@
|
|
|
5179
5213
|
return components;
|
|
5180
5214
|
}
|
|
5181
5215
|
|
|
5182
|
-
function sequentialWellFormednessDiagnostics(clauses, ruleNameValue,
|
|
5216
|
+
function sequentialWellFormednessDiagnostics(clauses, ruleNameValue, prefixes = {}) {
|
|
5183
5217
|
const diagnostics = [];
|
|
5184
5218
|
|
|
5185
5219
|
function visit(items, initialBound, scopeLabel) {
|
|
@@ -5194,7 +5228,7 @@
|
|
|
5194
5228
|
code: 'unbound-filter-variable',
|
|
5195
5229
|
severity: 'error',
|
|
5196
5230
|
rule: ruleNameValue,
|
|
5197
|
-
message: `${
|
|
5231
|
+
message: `${displayRuleName(ruleNameValue, prefixes)} FILTER uses ?${variable} before it is bound${scopeLabel}`,
|
|
5198
5232
|
});
|
|
5199
5233
|
}
|
|
5200
5234
|
}
|
|
@@ -5204,7 +5238,7 @@
|
|
|
5204
5238
|
code: 'assignment-variable-already-bound',
|
|
5205
5239
|
severity: 'error',
|
|
5206
5240
|
rule: ruleNameValue,
|
|
5207
|
-
message: `${
|
|
5241
|
+
message: `${displayRuleName(ruleNameValue, prefixes)} SET assigns ?${clause.variable}, but that variable is already bound${scopeLabel}`,
|
|
5208
5242
|
});
|
|
5209
5243
|
}
|
|
5210
5244
|
for (const variable of expressionVariables(clause.expr)) {
|
|
@@ -5213,7 +5247,7 @@
|
|
|
5213
5247
|
code: 'unbound-assignment-variable',
|
|
5214
5248
|
severity: 'error',
|
|
5215
5249
|
rule: ruleNameValue,
|
|
5216
|
-
message: `${
|
|
5250
|
+
message: `${displayRuleName(ruleNameValue, prefixes)} SET expression uses ?${variable} before it is bound${scopeLabel}`,
|
|
5217
5251
|
});
|
|
5218
5252
|
}
|
|
5219
5253
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eyeleng",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "The EYE Logic Engine: a JavaScript implementation of SHACL Rules, including SRL and RDF Rules syntax front-ends.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"SRL",
|
|
@@ -45,7 +45,10 @@
|
|
|
45
45
|
"w3c:rdf:earl": "node tools/w3c-rdf.js --earl",
|
|
46
46
|
"w3c:all": "npm run w3c:rules && npm run w3c:rdf",
|
|
47
47
|
"w3c:rules:json": "node tools/w3c-shacl12-rules.js --json",
|
|
48
|
-
"w3c:rules:earl": "node tools/w3c-shacl12-rules.js --earl"
|
|
48
|
+
"w3c:rules:earl": "node tools/w3c-shacl12-rules.js --earl",
|
|
49
|
+
"bench": "node tools/perf-bench.js --check",
|
|
50
|
+
"bench:report": "node tools/perf-bench.js --report",
|
|
51
|
+
"bench:update": "node tools/perf-bench.js --update"
|
|
49
52
|
},
|
|
50
53
|
"publishConfig": {
|
|
51
54
|
"access": "public"
|
package/src/analyze.js
CHANGED
|
@@ -11,9 +11,7 @@ function analyze(program, options = {}) {
|
|
|
11
11
|
|
|
12
12
|
program.rules.forEach((rule, index) => {
|
|
13
13
|
const name = ruleName(rule, index);
|
|
14
|
-
const label = displayRuleName(name, program.prefixes || {});
|
|
15
14
|
const bound = boundVariables(rule.body);
|
|
16
|
-
const positive = positiveVariables(rule.body);
|
|
17
15
|
const head = new Set();
|
|
18
16
|
for (const triple of rule.head) collectTripleVars(triple, head);
|
|
19
17
|
|
|
@@ -23,7 +21,7 @@ function analyze(program, options = {}) {
|
|
|
23
21
|
code: 'unsafe-head-variable',
|
|
24
22
|
severity: 'error',
|
|
25
23
|
rule: name,
|
|
26
|
-
message: `${
|
|
24
|
+
message: `${displayRuleName(name, program.prefixes || {})} has unbound head variable ?${variable}`,
|
|
27
25
|
});
|
|
28
26
|
}
|
|
29
27
|
}
|
|
@@ -34,19 +32,19 @@ function analyze(program, options = {}) {
|
|
|
34
32
|
code: 'invalid-head-predicate',
|
|
35
33
|
severity: 'error',
|
|
36
34
|
rule: name,
|
|
37
|
-
message: `${
|
|
35
|
+
message: `${displayRuleName(name, program.prefixes || {})} has a non-IRI/non-variable predicate in the head`,
|
|
38
36
|
});
|
|
39
37
|
}
|
|
40
38
|
}
|
|
41
39
|
|
|
42
|
-
diagnostics.push(...sequentialWellFormednessDiagnostics(rule.body, name,
|
|
40
|
+
diagnostics.push(...sequentialWellFormednessDiagnostics(rule.body, name, program.prefixes || {}));
|
|
43
41
|
|
|
44
42
|
if (rule.runOnce && recursiveIndexes.has(index)) {
|
|
45
43
|
diagnostics.push({
|
|
46
44
|
code: 'recursive-assignment-rule',
|
|
47
45
|
severity: 'warning',
|
|
48
46
|
rule: name,
|
|
49
|
-
message: `${
|
|
47
|
+
message: `${displayRuleName(name, program.prefixes || {})} is a run-once rule in a recursive dependency cycle`,
|
|
50
48
|
});
|
|
51
49
|
}
|
|
52
50
|
|
|
@@ -402,7 +400,7 @@ function stronglyConnectedComponents(size, edges) {
|
|
|
402
400
|
return components;
|
|
403
401
|
}
|
|
404
402
|
|
|
405
|
-
function sequentialWellFormednessDiagnostics(clauses, ruleNameValue,
|
|
403
|
+
function sequentialWellFormednessDiagnostics(clauses, ruleNameValue, prefixes = {}) {
|
|
406
404
|
const diagnostics = [];
|
|
407
405
|
|
|
408
406
|
function visit(items, initialBound, scopeLabel) {
|
|
@@ -417,7 +415,7 @@ function sequentialWellFormednessDiagnostics(clauses, ruleNameValue, label) {
|
|
|
417
415
|
code: 'unbound-filter-variable',
|
|
418
416
|
severity: 'error',
|
|
419
417
|
rule: ruleNameValue,
|
|
420
|
-
message: `${
|
|
418
|
+
message: `${displayRuleName(ruleNameValue, prefixes)} FILTER uses ?${variable} before it is bound${scopeLabel}`,
|
|
421
419
|
});
|
|
422
420
|
}
|
|
423
421
|
}
|
|
@@ -427,7 +425,7 @@ function sequentialWellFormednessDiagnostics(clauses, ruleNameValue, label) {
|
|
|
427
425
|
code: 'assignment-variable-already-bound',
|
|
428
426
|
severity: 'error',
|
|
429
427
|
rule: ruleNameValue,
|
|
430
|
-
message: `${
|
|
428
|
+
message: `${displayRuleName(ruleNameValue, prefixes)} SET assigns ?${clause.variable}, but that variable is already bound${scopeLabel}`,
|
|
431
429
|
});
|
|
432
430
|
}
|
|
433
431
|
for (const variable of expressionVariables(clause.expr)) {
|
|
@@ -436,7 +434,7 @@ function sequentialWellFormednessDiagnostics(clauses, ruleNameValue, label) {
|
|
|
436
434
|
code: 'unbound-assignment-variable',
|
|
437
435
|
severity: 'error',
|
|
438
436
|
rule: ruleNameValue,
|
|
439
|
-
message: `${
|
|
437
|
+
message: `${displayRuleName(ruleNameValue, prefixes)} SET expression uses ?${variable} before it is bound${scopeLabel}`,
|
|
440
438
|
});
|
|
441
439
|
}
|
|
442
440
|
}
|
package/src/builtins.js
CHANGED
|
@@ -28,6 +28,8 @@ const XSD_DAYTIME_DURATION = 'http://www.w3.org/2001/XMLSchema#dayTimeDuration';
|
|
|
28
28
|
const RDF_LANGSTRING = `${RDF_NS}langString`;
|
|
29
29
|
const RDF_DIRLANGSTRING = `${RDF_NS}dirLangString`;
|
|
30
30
|
const NUMERIC_DATATYPES = new Set([XSD_INTEGER, XSD_DECIMAL, XSD_DOUBLE]);
|
|
31
|
+
const MAX_SAFE_INTEGER_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
|
|
32
|
+
const MIN_SAFE_INTEGER_BIGINT = BigInt(Number.MIN_SAFE_INTEGER);
|
|
31
33
|
|
|
32
34
|
// This table is intentionally shaped by the SHACL 1.2 Rules grammar production BuiltInCall.
|
|
33
35
|
// Keys are the canonical spellings used by the draft; lookup is case-insensitive so examples
|
|
@@ -187,7 +189,7 @@ function toBigIntInteger(value) {
|
|
|
187
189
|
}
|
|
188
190
|
|
|
189
191
|
function fromIntegerResult(value) {
|
|
190
|
-
if (value <=
|
|
192
|
+
if (value <= MAX_SAFE_INTEGER_BIGINT && value >= MIN_SAFE_INTEGER_BIGINT) return Number(value);
|
|
191
193
|
return value;
|
|
192
194
|
}
|
|
193
195
|
|
package/src/engine.js
CHANGED
|
@@ -32,6 +32,18 @@ function evaluate(program, options = {}) {
|
|
|
32
32
|
layerIndexes,
|
|
33
33
|
analysis.dependency ? analysis.dependency.edges : [],
|
|
34
34
|
);
|
|
35
|
+
const baseContext = {
|
|
36
|
+
...evalOptions,
|
|
37
|
+
maxIterations,
|
|
38
|
+
inputKeys,
|
|
39
|
+
inferred,
|
|
40
|
+
trace,
|
|
41
|
+
perRule,
|
|
42
|
+
layer: 0,
|
|
43
|
+
iteration: 0,
|
|
44
|
+
startingIterations: 0,
|
|
45
|
+
recursiveLayer: false,
|
|
46
|
+
};
|
|
35
47
|
|
|
36
48
|
for (let layerIndex = 0; layerIndex < layerIndexes.length; layerIndex += 1) {
|
|
37
49
|
const layer = layerIndexes[layerIndex];
|
|
@@ -41,30 +53,17 @@ function evaluate(program, options = {}) {
|
|
|
41
53
|
if (runOnce.length > 0) {
|
|
42
54
|
iterations += 1;
|
|
43
55
|
for (const ruleIndex of runOnce) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
inferred,
|
|
48
|
-
trace,
|
|
49
|
-
perRule,
|
|
50
|
-
layer: layerIndex + 1,
|
|
51
|
-
iteration: iterations,
|
|
52
|
-
});
|
|
56
|
+
baseContext.layer = layerIndex + 1;
|
|
57
|
+
baseContext.iteration = iterations;
|
|
58
|
+
const added = applyRuleOnce(program, store, ruleIndex, baseContext);
|
|
53
59
|
ruleApplications += added.applications;
|
|
54
60
|
}
|
|
55
61
|
}
|
|
56
62
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
inferred,
|
|
62
|
-
trace,
|
|
63
|
-
perRule,
|
|
64
|
-
layer: layerIndex + 1,
|
|
65
|
-
startingIterations: iterations,
|
|
66
|
-
recursiveLayer: recursiveLayerFlags[layerIndex],
|
|
67
|
-
});
|
|
63
|
+
baseContext.layer = layerIndex + 1;
|
|
64
|
+
baseContext.startingIterations = iterations;
|
|
65
|
+
baseContext.recursiveLayer = recursiveLayerFlags[layerIndex];
|
|
66
|
+
const ordinaryResult = runRulesToFixpoint(program, store, ordinary, baseContext);
|
|
68
67
|
iterations = ordinaryResult.iterations;
|
|
69
68
|
ruleApplications += ordinaryResult.ruleApplications;
|
|
70
69
|
}
|
|
@@ -95,10 +94,8 @@ function runRulesToFixpoint(program, store, ruleIndexes, context) {
|
|
|
95
94
|
const iteration = context.startingIterations + 1;
|
|
96
95
|
let ruleApplications = 0;
|
|
97
96
|
for (const ruleIndex of ruleIndexes) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
iteration,
|
|
101
|
-
});
|
|
97
|
+
context.iteration = iteration;
|
|
98
|
+
const applied = applyRuleOnce(program, store, ruleIndex, context);
|
|
102
99
|
ruleApplications += applied.applications;
|
|
103
100
|
}
|
|
104
101
|
return { iterations: iteration, ruleApplications };
|
|
@@ -114,10 +111,8 @@ function runRulesToFixpoint(program, store, ruleIndexes, context) {
|
|
|
114
111
|
let addedInIteration = 0;
|
|
115
112
|
|
|
116
113
|
for (const ruleIndex of ruleIndexes) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
iteration: iterations,
|
|
120
|
-
});
|
|
114
|
+
context.iteration = iterations;
|
|
115
|
+
const applied = applyRuleOnce(program, store, ruleIndex, context);
|
|
121
116
|
addedInIteration += applied.added;
|
|
122
117
|
ruleApplications += applied.applications;
|
|
123
118
|
}
|
|
@@ -146,16 +141,24 @@ function computeRecursiveLayerFlags(layerIndexes, edges = []) {
|
|
|
146
141
|
return flags;
|
|
147
142
|
}
|
|
148
143
|
|
|
144
|
+
|
|
149
145
|
function applyRuleOnce(program, store, ruleIndex, context) {
|
|
150
146
|
const rule = program.rules[ruleIndex];
|
|
151
147
|
let applications = 0;
|
|
152
148
|
let added = 0;
|
|
153
|
-
const
|
|
149
|
+
const dedupeBindings = rule.body.some((clause) => clause.type === 'path');
|
|
150
|
+
const seenBindings = dedupeBindings ? new Set() : null;
|
|
154
151
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
152
|
+
const bodyBindings = rule.body.length === 1 && rule.body[0].type === 'triple'
|
|
153
|
+
? store.match(rule.body[0].triple, {})
|
|
154
|
+
: evaluateBodyStream(rule.body, store, {}, context);
|
|
155
|
+
|
|
156
|
+
for (const binding of bodyBindings) {
|
|
157
|
+
if (seenBindings) {
|
|
158
|
+
const key = bindingKey(binding);
|
|
159
|
+
if (seenBindings.has(key)) continue;
|
|
160
|
+
seenBindings.add(key);
|
|
161
|
+
}
|
|
159
162
|
applications += 1;
|
|
160
163
|
context.perRule[ruleIndex].applications += 1;
|
|
161
164
|
|
package/src/parser.js
CHANGED
|
@@ -764,7 +764,7 @@ class Parser {
|
|
|
764
764
|
peekN(n) { return this.tokens[this.pos + n] || this.tokens[this.tokens.length - 1]; }
|
|
765
765
|
previous() { return this.tokens[this.pos - 1]; }
|
|
766
766
|
strictGrammar() { return !!this.options.strictGrammar; }
|
|
767
|
-
error(message, token = this.peek()) { return new SyntaxErrorWithLocation(message, token); }
|
|
767
|
+
error(message, token = this.peek()) { return new SyntaxErrorWithLocation(message, token && token.filename ? token : { ...token, filename: this.options.filename || '<input>' }); }
|
|
768
768
|
}
|
|
769
769
|
|
|
770
770
|
|