eyeleng 1.0.5 → 1.0.7
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 +311 -11
- package/dist/browser/eyeleng.browser.js +2649 -1162
- package/examples/output/collection-nesting.trig +1 -1
- package/examples/output/rdf-messages.trig +3 -0
- package/examples/rdf-messages.srl +15 -0
- package/examples/rdf-messages.trig +12 -0
- package/eyeleng.js +2631 -1138
- package/package.json +9 -2
- package/playground.html +1 -1
- package/reports/w3c-rdf-earl.ttl +11707 -0
- package/reports/w3c-shacl12-rules-earl.ttl +1055 -1336
- package/src/api.js +4 -0
- package/src/cli.js +6 -0
- package/src/parser.js +171 -3
- package/src/rdfEntailment.js +571 -0
- package/src/rdfManifest.js +737 -0
- package/src/rdfMessages.js +321 -0
- package/src/rdfSyntax.js +955 -0
- package/src/shacl12RulesManifest.js +386 -0
- package/src/tokenizer.js +47 -11
- package/test/api.test.js +95 -0
- package/test/harness.js +38 -10
- package/test/run.js +6 -3
- package/test/shacl12-rules.test.js +40 -217
- package/test/w3c-rdf.test.js +205 -0
- package/tools/browser-bundle.js +0 -0
- package/tools/bundle.js +0 -0
- package/tools/w3c-rdf.js +55 -0
- package/tools/w3c-shacl12-rules.js +55 -0
- package/HANDBOOK.md +0 -1070
package/src/api.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { parse, parseQuery } = require('./parser.js');
|
|
4
4
|
const { parseRdfSyntax, parseRdfDocument, rdfDocumentToProgram, looksLikeRdfRules } = require('./rdfSyntax.js');
|
|
5
|
+
const { parseRdfMessageLog, looksLikeRdfMessageLog } = require('./rdfMessages.js');
|
|
5
6
|
const { evaluate } = require('./engine.js');
|
|
6
7
|
const { analyze } = require('./analyze.js');
|
|
7
8
|
const { formatTriples, sortTriples, toJSON, formatTrace, formatBindings } = require('./format.js');
|
|
@@ -10,6 +11,7 @@ const { resultTriples } = require('./output.js');
|
|
|
10
11
|
|
|
11
12
|
function parseInput(source, options = {}) {
|
|
12
13
|
if (typeof source !== 'string') return source;
|
|
14
|
+
if (looksLikeRdfMessageLog(source, options)) return parseRdfMessageLog(source, options);
|
|
13
15
|
return looksLikeRdfRules(source, options) ? parseRdfSyntax(source, options) : parse(source, options);
|
|
14
16
|
}
|
|
15
17
|
|
|
@@ -106,6 +108,8 @@ module.exports = {
|
|
|
106
108
|
parseInput,
|
|
107
109
|
parseRdfSyntax,
|
|
108
110
|
parseRdfDocument,
|
|
111
|
+
parseRdfMessageLog,
|
|
112
|
+
looksLikeRdfMessageLog,
|
|
109
113
|
rdfDocumentToProgram,
|
|
110
114
|
compile,
|
|
111
115
|
resolveImports,
|
package/src/cli.js
CHANGED
|
@@ -52,6 +52,8 @@ function parseArgs(argv) {
|
|
|
52
52
|
imports: true,
|
|
53
53
|
syntax: 'auto',
|
|
54
54
|
ruleSet: null,
|
|
55
|
+
rdfMessages: false,
|
|
56
|
+
includeMessageFacts: false,
|
|
55
57
|
};
|
|
56
58
|
const files = [];
|
|
57
59
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -64,6 +66,8 @@ function parseArgs(argv) {
|
|
|
64
66
|
else if (arg === '--strict') options.strict = true;
|
|
65
67
|
else if (arg === '--deps') options.deps = true;
|
|
66
68
|
else if (arg === '--no-imports') options.imports = false;
|
|
69
|
+
else if (arg === '--rdf-messages' || arg === '--stream-messages') options.rdfMessages = true;
|
|
70
|
+
else if (arg === '--include-message-facts') options.includeMessageFacts = true;
|
|
67
71
|
else if (arg === '--syntax') {
|
|
68
72
|
i += 1;
|
|
69
73
|
if (i >= argv.length) throw new Error('--syntax requires srl, rdf, or auto');
|
|
@@ -173,6 +177,8 @@ function main(argv = process.argv.slice(2), io = process) {
|
|
|
173
177
|
importResolver: options.imports ? createFileImportResolver() : null,
|
|
174
178
|
syntax: options.syntax === 'auto' ? undefined : options.syntax,
|
|
175
179
|
ruleSet: options.ruleSet,
|
|
180
|
+
rdfMessages: options.rdfMessages,
|
|
181
|
+
includeMessageFacts: options.includeMessageFacts,
|
|
176
182
|
});
|
|
177
183
|
const fatal = hasFatalDiagnostics(compiled.analysis, options.strict);
|
|
178
184
|
|
package/src/parser.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { tokenize, SyntaxErrorWithLocation } = require('./tokenizer.js');
|
|
4
|
+
const { parseN3 } = require('./rdfSyntax.js');
|
|
4
5
|
const { isBuiltinName } = require('./builtins.js');
|
|
5
6
|
const { ruleNeedsRunOnce } = require('./assignments.js');
|
|
6
7
|
const {
|
|
@@ -14,6 +15,7 @@ const {
|
|
|
14
15
|
RDF_REST,
|
|
15
16
|
RDF_NIL,
|
|
16
17
|
RDF_REIFIES,
|
|
18
|
+
XSD_STRING,
|
|
17
19
|
XSD_BOOLEAN,
|
|
18
20
|
XSD_INTEGER,
|
|
19
21
|
XSD_DECIMAL,
|
|
@@ -22,7 +24,7 @@ const {
|
|
|
22
24
|
|
|
23
25
|
class Parser {
|
|
24
26
|
constructor(source, options = {}) {
|
|
25
|
-
this.tokens = Array.isArray(source) ? source : tokenize(source, options
|
|
27
|
+
this.tokens = Array.isArray(source) ? source : tokenize(source, options);
|
|
26
28
|
this.pos = 0;
|
|
27
29
|
this.options = options;
|
|
28
30
|
this.baseIRI = options.baseIRI || null;
|
|
@@ -52,7 +54,7 @@ class Parser {
|
|
|
52
54
|
this.parseImports();
|
|
53
55
|
} else if (this.matchWord('DATA')) {
|
|
54
56
|
this.expectValue('{');
|
|
55
|
-
data.push(...this.
|
|
57
|
+
data.push(...this.parseDataBlockWithRdfSyntax());
|
|
56
58
|
} else if (this.matchWord('RULE')) {
|
|
57
59
|
rules.push(this.parseRule());
|
|
58
60
|
} else if (this.matchWord('IF')) {
|
|
@@ -85,6 +87,7 @@ class Parser {
|
|
|
85
87
|
let name = nameToken.value;
|
|
86
88
|
if (!name.endsWith(':')) throw this.error('Prefix name must end with :', nameToken);
|
|
87
89
|
name = name.slice(0, -1);
|
|
90
|
+
if (this.strictGrammar() && !isValidPNPrefix(name)) throw this.error(`Invalid prefix name ${nameToken.value}`, nameToken);
|
|
88
91
|
const iriToken = this.expectType('iri');
|
|
89
92
|
this.prefixes[name] = this.resolveIRI(iriToken.value, iriToken);
|
|
90
93
|
if (wasAtPrefix) this.consumeOptionalDot();
|
|
@@ -92,6 +95,10 @@ class Parser {
|
|
|
92
95
|
|
|
93
96
|
parseVersion() {
|
|
94
97
|
const token = this.expectType('string');
|
|
98
|
+
if (this.strictGrammar()) {
|
|
99
|
+
if (token.long) throw this.error('VERSION must use a short string literal', token);
|
|
100
|
+
if (token.value !== '1.2') throw this.error('VERSION must be the SHACL Rules version label \"1.2\"', token);
|
|
101
|
+
}
|
|
95
102
|
this.version = token.value;
|
|
96
103
|
}
|
|
97
104
|
|
|
@@ -196,6 +203,98 @@ class Parser {
|
|
|
196
203
|
return triples;
|
|
197
204
|
}
|
|
198
205
|
|
|
206
|
+
parseDataBlockWithRdfSyntax() {
|
|
207
|
+
const blockSource = this.collectBalancedDataBlockSource();
|
|
208
|
+
const program = parseN3(blockSource, {
|
|
209
|
+
profile: 'trig',
|
|
210
|
+
base: this.baseIRI || '',
|
|
211
|
+
prefixes: this.prefixes,
|
|
212
|
+
});
|
|
213
|
+
return (program.facts || []).map((triple) => this.convertRdfSyntaxTriple(triple));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
collectBalancedDataBlockSource() {
|
|
217
|
+
const tokens = [];
|
|
218
|
+
let depth = 1;
|
|
219
|
+
while (!this.is('eof')) {
|
|
220
|
+
const token = this.advance();
|
|
221
|
+
if (token.value === '{') {
|
|
222
|
+
depth += 1;
|
|
223
|
+
tokens.push(token);
|
|
224
|
+
} else if (token.value === '}') {
|
|
225
|
+
depth -= 1;
|
|
226
|
+
if (depth === 0) return this.tokensToRdfSource(tokens);
|
|
227
|
+
tokens.push(token);
|
|
228
|
+
} else {
|
|
229
|
+
tokens.push(token);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
throw this.error('Unterminated DATA block');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
tokensToRdfSource(tokens) {
|
|
236
|
+
const parts = [];
|
|
237
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
238
|
+
const token = tokens[i];
|
|
239
|
+
if (token.type === 'eof') continue;
|
|
240
|
+
if ((token.value === '+' || token.value === '-') && tokens[i + 1] && tokens[i + 1].type === 'number') {
|
|
241
|
+
parts.push(`${token.value}${this.tokenToRdfSource(tokens[i + 1])}`);
|
|
242
|
+
i += 1;
|
|
243
|
+
} else {
|
|
244
|
+
parts.push(this.tokenToRdfSource(token));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return parts.join(' ');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
tokenToRdfSource(token) {
|
|
251
|
+
if (token.type === 'iri') return `<${String(token.value).replace(/>/g, '\\>')}>`;
|
|
252
|
+
if (token.type === 'string') return JSON.stringify(token.value);
|
|
253
|
+
if (token.type === 'variable') return `?${token.value}`;
|
|
254
|
+
if (token.type === 'number') return String(token.value);
|
|
255
|
+
return String(token.value);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
convertRdfSyntaxTriple(triple) {
|
|
259
|
+
const out = {
|
|
260
|
+
s: this.convertRdfSyntaxTerm(triple.s),
|
|
261
|
+
p: this.convertRdfSyntaxTerm(triple.p),
|
|
262
|
+
o: this.convertRdfSyntaxTerm(triple.o),
|
|
263
|
+
};
|
|
264
|
+
if (out.s.type === 'var' || out.p.type === 'var' || out.o.type === 'var') {
|
|
265
|
+
throw this.error('DATA blocks may not contain variables');
|
|
266
|
+
}
|
|
267
|
+
if (out.p.type !== 'iri') {
|
|
268
|
+
throw this.error('DATA predicates must be IRIs');
|
|
269
|
+
}
|
|
270
|
+
if (triple.graph) out.graph = this.convertRdfSyntaxTerm(triple.graph);
|
|
271
|
+
return out;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
convertRdfSyntaxTerm(term) {
|
|
275
|
+
if (!term) return null;
|
|
276
|
+
if (term.type) return term;
|
|
277
|
+
if (term.kind === 'iri') return iri(term.value);
|
|
278
|
+
if (term.kind === 'blank') return blankNode(term.value);
|
|
279
|
+
if (term.kind === 'var') return variable(term.name || term.value);
|
|
280
|
+
if (term.kind === 'literal') {
|
|
281
|
+
return literal(
|
|
282
|
+
coerceLexicalLiteral(term.value, term.datatype),
|
|
283
|
+
term.datatype === XSD_STRING ? null : (term.datatype || null),
|
|
284
|
+
term.language || null,
|
|
285
|
+
term.langDir || null,
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
if (term.kind === 'triple') {
|
|
289
|
+
return tripleTerm(
|
|
290
|
+
this.convertRdfSyntaxTerm(term.s),
|
|
291
|
+
this.convertRdfSyntaxTerm(term.p),
|
|
292
|
+
this.convertRdfSyntaxTerm(term.o),
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
throw this.error(`Unsupported RDF term kind ${term.kind || typeof term}`);
|
|
296
|
+
}
|
|
297
|
+
|
|
199
298
|
parseTripleStatement(options = {}) {
|
|
200
299
|
const subjectNode = this.parseGraphNode(options);
|
|
201
300
|
const triples = [...subjectNode.triples];
|
|
@@ -395,6 +494,7 @@ class Parser {
|
|
|
395
494
|
} else if (this.matchWord('SET')) {
|
|
396
495
|
clauses.push(this.parseSetClause());
|
|
397
496
|
} else if (this.matchWord('BIND')) {
|
|
497
|
+
if (this.strictGrammar()) throw this.error('BIND is not part of the SHACL 1.2 Rules grammar; use SET');
|
|
398
498
|
clauses.push(this.parseBindClause());
|
|
399
499
|
} else if (this.matchWord('NOT')) {
|
|
400
500
|
this.expectValue('{');
|
|
@@ -502,8 +602,9 @@ class Parser {
|
|
|
502
602
|
if (colon < 0) throw this.error(`Expected IRI, prefixed name, literal, blank node, or variable; got ${value}`, token);
|
|
503
603
|
const prefix = value.slice(0, colon);
|
|
504
604
|
const local = value.slice(colon + 1);
|
|
605
|
+
if (this.strictGrammar()) validatePrefixedName(prefix, local, value, token, (message, errToken) => this.error(message, errToken));
|
|
505
606
|
if (!(prefix in this.prefixes)) throw this.error(`Unknown prefix ${prefix}:`, token);
|
|
506
|
-
return this.prefixes[prefix] + local;
|
|
607
|
+
return this.prefixes[prefix] + decodePNLocalEscapes(local);
|
|
507
608
|
}
|
|
508
609
|
|
|
509
610
|
resolveIRI(value, token = null) {
|
|
@@ -662,9 +763,76 @@ class Parser {
|
|
|
662
763
|
peek() { return this.tokens[this.pos]; }
|
|
663
764
|
peekN(n) { return this.tokens[this.pos + n] || this.tokens[this.tokens.length - 1]; }
|
|
664
765
|
previous() { return this.tokens[this.pos - 1]; }
|
|
766
|
+
strictGrammar() { return !!this.options.strictGrammar; }
|
|
665
767
|
error(message, token = this.peek()) { return new SyntaxErrorWithLocation(message, token); }
|
|
666
768
|
}
|
|
667
769
|
|
|
770
|
+
|
|
771
|
+
function isPnCharsBase(ch) {
|
|
772
|
+
if (!ch) return false;
|
|
773
|
+
return /[A-Za-z]/.test(ch) || ch.codePointAt(0) >= 0x00C0;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function isPnCharsU(ch) {
|
|
777
|
+
return isPnCharsBase(ch) || ch === '_';
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function isPnChars(ch) {
|
|
781
|
+
return isPnCharsU(ch) || /[0-9-]/.test(ch) || ch === '\u00B7' || /[\u0300-\u036F\u203F-\u2040]/u.test(ch);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function isValidPNPrefix(prefix) {
|
|
785
|
+
if (prefix === '') return true;
|
|
786
|
+
const chars = Array.from(prefix);
|
|
787
|
+
if (!isPnCharsBase(chars[0])) return false;
|
|
788
|
+
if (chars.length > 1 && chars.at(-1) === '.') return false;
|
|
789
|
+
return chars.slice(1).every((ch) => isPnChars(ch) || ch === '.');
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function plxLength(text, index) {
|
|
793
|
+
const ch = text[index];
|
|
794
|
+
if (ch === '%' && /[0-9A-Fa-f]/.test(text[index + 1] || '') && /[0-9A-Fa-f]/.test(text[index + 2] || '')) return 3;
|
|
795
|
+
if (ch === '\\' && /[_~.!$&'()*+,;=/?#@%-]/.test(text[index + 1] || '')) return 2;
|
|
796
|
+
return 0;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function isPNLocalStartAt(text, index) {
|
|
800
|
+
const ch = text[index];
|
|
801
|
+
return isPnCharsU(ch) || /[0-9:]/.test(ch || '') || plxLength(text, index) > 0;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function isPNLocalBodyAt(text, index) {
|
|
805
|
+
const ch = text[index];
|
|
806
|
+
return isPnChars(ch) || ch === '.' || ch === ':' || plxLength(text, index) > 0;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function isPNLocalEndAt(text, index) {
|
|
810
|
+
const ch = text[index];
|
|
811
|
+
return isPnChars(ch) || ch === ':' || plxLength(text, index) > 0;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function validatePNLocal(local) {
|
|
815
|
+
if (local === '') return true;
|
|
816
|
+
if (!isPNLocalStartAt(local, 0)) return false;
|
|
817
|
+
let lastStart = 0;
|
|
818
|
+
for (let i = 0; i < local.length;) {
|
|
819
|
+
const len = plxLength(local, i) || 1;
|
|
820
|
+
if (i > 0 && !isPNLocalBodyAt(local, i)) return false;
|
|
821
|
+
lastStart = i;
|
|
822
|
+
i += len;
|
|
823
|
+
}
|
|
824
|
+
return isPNLocalEndAt(local, lastStart);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
function validatePrefixedName(prefix, local, value, token, makeError) {
|
|
828
|
+
if (!isValidPNPrefix(prefix)) throw makeError(`Invalid prefixed name ${value}: invalid prefix`, token);
|
|
829
|
+
if (!validatePNLocal(local)) throw makeError(`Invalid prefixed name ${value}: invalid local name`, token);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function decodePNLocalEscapes(local) {
|
|
833
|
+
return String(local).replace(/\\([_~.!$&'()*+,;=/?#@%-])/g, '$1');
|
|
834
|
+
}
|
|
835
|
+
|
|
668
836
|
function numericLiteral(value) {
|
|
669
837
|
if (Number.isInteger(value)) return literal(value, XSD_INTEGER);
|
|
670
838
|
return literal(value, XSD_DECIMAL);
|