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/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.filename);
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.parseTriplesBlock({ allowPath: false, context: 'data' }));
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);