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/eyeleng.js CHANGED
@@ -58,6 +58,8 @@
58
58
  imports: true,
59
59
  syntax: 'auto',
60
60
  ruleSet: null,
61
+ rdfMessages: false,
62
+ includeMessageFacts: false,
61
63
  };
62
64
  const files = [];
63
65
  for (let i = 0; i < argv.length; i += 1) {
@@ -70,6 +72,8 @@
70
72
  else if (arg === '--strict') options.strict = true;
71
73
  else if (arg === '--deps') options.deps = true;
72
74
  else if (arg === '--no-imports') options.imports = false;
75
+ else if (arg === '--rdf-messages' || arg === '--stream-messages') options.rdfMessages = true;
76
+ else if (arg === '--include-message-facts') options.includeMessageFacts = true;
73
77
  else if (arg === '--syntax') {
74
78
  i += 1;
75
79
  if (i >= argv.length) throw new Error('--syntax requires srl, rdf, or auto');
@@ -179,6 +183,8 @@
179
183
  importResolver: options.imports ? createFileImportResolver() : null,
180
184
  syntax: options.syntax === 'auto' ? undefined : options.syntax,
181
185
  ruleSet: options.ruleSet,
186
+ rdfMessages: options.rdfMessages,
187
+ includeMessageFacts: options.includeMessageFacts,
182
188
  });
183
189
  const fatal = hasFatalDiagnostics(compiled.analysis, options.strict);
184
190
 
@@ -236,6 +242,7 @@
236
242
 
237
243
  const { parse, parseQuery } = require('./parser.js');
238
244
  const { parseRdfSyntax, parseRdfDocument, rdfDocumentToProgram, looksLikeRdfRules } = require('./rdfSyntax.js');
245
+ const { parseRdfMessageLog, looksLikeRdfMessageLog } = require('./rdfMessages.js');
239
246
  const { evaluate } = require('./engine.js');
240
247
  const { analyze } = require('./analyze.js');
241
248
  const { formatTriples, sortTriples, toJSON, formatTrace, formatBindings } = require('./format.js');
@@ -244,6 +251,7 @@
244
251
 
245
252
  function parseInput(source, options = {}) {
246
253
  if (typeof source !== 'string') return source;
254
+ if (looksLikeRdfMessageLog(source, options)) return parseRdfMessageLog(source, options);
247
255
  return looksLikeRdfRules(source, options) ? parseRdfSyntax(source, options) : parse(source, options);
248
256
  }
249
257
 
@@ -340,6 +348,8 @@
340
348
  parseInput,
341
349
  parseRdfSyntax,
342
350
  parseRdfDocument,
351
+ parseRdfMessageLog,
352
+ looksLikeRdfMessageLog,
343
353
  rdfDocumentToProgram,
344
354
  compile,
345
355
  resolveImports,
@@ -363,6 +373,7 @@
363
373
  'use strict';
364
374
 
365
375
  const { tokenize, SyntaxErrorWithLocation } = require('./tokenizer.js');
376
+ const { parseN3 } = require('./rdfSyntax.js');
366
377
  const { isBuiltinName } = require('./builtins.js');
367
378
  const { ruleNeedsRunOnce } = require('./assignments.js');
368
379
  const {
@@ -376,6 +387,7 @@
376
387
  RDF_REST,
377
388
  RDF_NIL,
378
389
  RDF_REIFIES,
390
+ XSD_STRING,
379
391
  XSD_BOOLEAN,
380
392
  XSD_INTEGER,
381
393
  XSD_DECIMAL,
@@ -384,7 +396,7 @@
384
396
 
385
397
  class Parser {
386
398
  constructor(source, options = {}) {
387
- this.tokens = Array.isArray(source) ? source : tokenize(source, options.filename);
399
+ this.tokens = Array.isArray(source) ? source : tokenize(source, options);
388
400
  this.pos = 0;
389
401
  this.options = options;
390
402
  this.baseIRI = options.baseIRI || null;
@@ -414,7 +426,7 @@
414
426
  this.parseImports();
415
427
  } else if (this.matchWord('DATA')) {
416
428
  this.expectValue('{');
417
- data.push(...this.parseTriplesBlock({ allowPath: false, context: 'data' }));
429
+ data.push(...this.parseDataBlockWithRdfSyntax());
418
430
  } else if (this.matchWord('RULE')) {
419
431
  rules.push(this.parseRule());
420
432
  } else if (this.matchWord('IF')) {
@@ -447,6 +459,7 @@
447
459
  let name = nameToken.value;
448
460
  if (!name.endsWith(':')) throw this.error('Prefix name must end with :', nameToken);
449
461
  name = name.slice(0, -1);
462
+ if (this.strictGrammar() && !isValidPNPrefix(name)) throw this.error(`Invalid prefix name ${nameToken.value}`, nameToken);
450
463
  const iriToken = this.expectType('iri');
451
464
  this.prefixes[name] = this.resolveIRI(iriToken.value, iriToken);
452
465
  if (wasAtPrefix) this.consumeOptionalDot();
@@ -454,6 +467,10 @@
454
467
 
455
468
  parseVersion() {
456
469
  const token = this.expectType('string');
470
+ if (this.strictGrammar()) {
471
+ if (token.long) throw this.error('VERSION must use a short string literal', token);
472
+ if (token.value !== '1.2') throw this.error('VERSION must be the SHACL Rules version label \"1.2\"', token);
473
+ }
457
474
  this.version = token.value;
458
475
  }
459
476
 
@@ -558,6 +575,98 @@
558
575
  return triples;
559
576
  }
560
577
 
578
+ parseDataBlockWithRdfSyntax() {
579
+ const blockSource = this.collectBalancedDataBlockSource();
580
+ const program = parseN3(blockSource, {
581
+ profile: 'trig',
582
+ base: this.baseIRI || '',
583
+ prefixes: this.prefixes,
584
+ });
585
+ return (program.facts || []).map((triple) => this.convertRdfSyntaxTriple(triple));
586
+ }
587
+
588
+ collectBalancedDataBlockSource() {
589
+ const tokens = [];
590
+ let depth = 1;
591
+ while (!this.is('eof')) {
592
+ const token = this.advance();
593
+ if (token.value === '{') {
594
+ depth += 1;
595
+ tokens.push(token);
596
+ } else if (token.value === '}') {
597
+ depth -= 1;
598
+ if (depth === 0) return this.tokensToRdfSource(tokens);
599
+ tokens.push(token);
600
+ } else {
601
+ tokens.push(token);
602
+ }
603
+ }
604
+ throw this.error('Unterminated DATA block');
605
+ }
606
+
607
+ tokensToRdfSource(tokens) {
608
+ const parts = [];
609
+ for (let i = 0; i < tokens.length; i += 1) {
610
+ const token = tokens[i];
611
+ if (token.type === 'eof') continue;
612
+ if ((token.value === '+' || token.value === '-') && tokens[i + 1] && tokens[i + 1].type === 'number') {
613
+ parts.push(`${token.value}${this.tokenToRdfSource(tokens[i + 1])}`);
614
+ i += 1;
615
+ } else {
616
+ parts.push(this.tokenToRdfSource(token));
617
+ }
618
+ }
619
+ return parts.join(' ');
620
+ }
621
+
622
+ tokenToRdfSource(token) {
623
+ if (token.type === 'iri') return `<${String(token.value).replace(/>/g, '\\>')}>`;
624
+ if (token.type === 'string') return JSON.stringify(token.value);
625
+ if (token.type === 'variable') return `?${token.value}`;
626
+ if (token.type === 'number') return String(token.value);
627
+ return String(token.value);
628
+ }
629
+
630
+ convertRdfSyntaxTriple(triple) {
631
+ const out = {
632
+ s: this.convertRdfSyntaxTerm(triple.s),
633
+ p: this.convertRdfSyntaxTerm(triple.p),
634
+ o: this.convertRdfSyntaxTerm(triple.o),
635
+ };
636
+ if (out.s.type === 'var' || out.p.type === 'var' || out.o.type === 'var') {
637
+ throw this.error('DATA blocks may not contain variables');
638
+ }
639
+ if (out.p.type !== 'iri') {
640
+ throw this.error('DATA predicates must be IRIs');
641
+ }
642
+ if (triple.graph) out.graph = this.convertRdfSyntaxTerm(triple.graph);
643
+ return out;
644
+ }
645
+
646
+ convertRdfSyntaxTerm(term) {
647
+ if (!term) return null;
648
+ if (term.type) return term;
649
+ if (term.kind === 'iri') return iri(term.value);
650
+ if (term.kind === 'blank') return blankNode(term.value);
651
+ if (term.kind === 'var') return variable(term.name || term.value);
652
+ if (term.kind === 'literal') {
653
+ return literal(
654
+ coerceLexicalLiteral(term.value, term.datatype),
655
+ term.datatype === XSD_STRING ? null : (term.datatype || null),
656
+ term.language || null,
657
+ term.langDir || null,
658
+ );
659
+ }
660
+ if (term.kind === 'triple') {
661
+ return tripleTerm(
662
+ this.convertRdfSyntaxTerm(term.s),
663
+ this.convertRdfSyntaxTerm(term.p),
664
+ this.convertRdfSyntaxTerm(term.o),
665
+ );
666
+ }
667
+ throw this.error(`Unsupported RDF term kind ${term.kind || typeof term}`);
668
+ }
669
+
561
670
  parseTripleStatement(options = {}) {
562
671
  const subjectNode = this.parseGraphNode(options);
563
672
  const triples = [...subjectNode.triples];
@@ -757,6 +866,7 @@
757
866
  } else if (this.matchWord('SET')) {
758
867
  clauses.push(this.parseSetClause());
759
868
  } else if (this.matchWord('BIND')) {
869
+ if (this.strictGrammar()) throw this.error('BIND is not part of the SHACL 1.2 Rules grammar; use SET');
760
870
  clauses.push(this.parseBindClause());
761
871
  } else if (this.matchWord('NOT')) {
762
872
  this.expectValue('{');
@@ -864,8 +974,9 @@
864
974
  if (colon < 0) throw this.error(`Expected IRI, prefixed name, literal, blank node, or variable; got ${value}`, token);
865
975
  const prefix = value.slice(0, colon);
866
976
  const local = value.slice(colon + 1);
977
+ if (this.strictGrammar()) validatePrefixedName(prefix, local, value, token, (message, errToken) => this.error(message, errToken));
867
978
  if (!(prefix in this.prefixes)) throw this.error(`Unknown prefix ${prefix}:`, token);
868
- return this.prefixes[prefix] + local;
979
+ return this.prefixes[prefix] + decodePNLocalEscapes(local);
869
980
  }
870
981
 
871
982
  resolveIRI(value, token = null) {
@@ -1024,9 +1135,76 @@
1024
1135
  peek() { return this.tokens[this.pos]; }
1025
1136
  peekN(n) { return this.tokens[this.pos + n] || this.tokens[this.tokens.length - 1]; }
1026
1137
  previous() { return this.tokens[this.pos - 1]; }
1138
+ strictGrammar() { return !!this.options.strictGrammar; }
1027
1139
  error(message, token = this.peek()) { return new SyntaxErrorWithLocation(message, token); }
1028
1140
  }
1029
1141
 
1142
+
1143
+ function isPnCharsBase(ch) {
1144
+ if (!ch) return false;
1145
+ return /[A-Za-z]/.test(ch) || ch.codePointAt(0) >= 0x00C0;
1146
+ }
1147
+
1148
+ function isPnCharsU(ch) {
1149
+ return isPnCharsBase(ch) || ch === '_';
1150
+ }
1151
+
1152
+ function isPnChars(ch) {
1153
+ return isPnCharsU(ch) || /[0-9-]/.test(ch) || ch === '\u00B7' || /[\u0300-\u036F\u203F-\u2040]/u.test(ch);
1154
+ }
1155
+
1156
+ function isValidPNPrefix(prefix) {
1157
+ if (prefix === '') return true;
1158
+ const chars = Array.from(prefix);
1159
+ if (!isPnCharsBase(chars[0])) return false;
1160
+ if (chars.length > 1 && chars.at(-1) === '.') return false;
1161
+ return chars.slice(1).every((ch) => isPnChars(ch) || ch === '.');
1162
+ }
1163
+
1164
+ function plxLength(text, index) {
1165
+ const ch = text[index];
1166
+ if (ch === '%' && /[0-9A-Fa-f]/.test(text[index + 1] || '') && /[0-9A-Fa-f]/.test(text[index + 2] || '')) return 3;
1167
+ if (ch === '\\' && /[_~.!$&'()*+,;=/?#@%-]/.test(text[index + 1] || '')) return 2;
1168
+ return 0;
1169
+ }
1170
+
1171
+ function isPNLocalStartAt(text, index) {
1172
+ const ch = text[index];
1173
+ return isPnCharsU(ch) || /[0-9:]/.test(ch || '') || plxLength(text, index) > 0;
1174
+ }
1175
+
1176
+ function isPNLocalBodyAt(text, index) {
1177
+ const ch = text[index];
1178
+ return isPnChars(ch) || ch === '.' || ch === ':' || plxLength(text, index) > 0;
1179
+ }
1180
+
1181
+ function isPNLocalEndAt(text, index) {
1182
+ const ch = text[index];
1183
+ return isPnChars(ch) || ch === ':' || plxLength(text, index) > 0;
1184
+ }
1185
+
1186
+ function validatePNLocal(local) {
1187
+ if (local === '') return true;
1188
+ if (!isPNLocalStartAt(local, 0)) return false;
1189
+ let lastStart = 0;
1190
+ for (let i = 0; i < local.length;) {
1191
+ const len = plxLength(local, i) || 1;
1192
+ if (i > 0 && !isPNLocalBodyAt(local, i)) return false;
1193
+ lastStart = i;
1194
+ i += len;
1195
+ }
1196
+ return isPNLocalEndAt(local, lastStart);
1197
+ }
1198
+
1199
+ function validatePrefixedName(prefix, local, value, token, makeError) {
1200
+ if (!isValidPNPrefix(prefix)) throw makeError(`Invalid prefixed name ${value}: invalid prefix`, token);
1201
+ if (!validatePNLocal(local)) throw makeError(`Invalid prefixed name ${value}: invalid local name`, token);
1202
+ }
1203
+
1204
+ function decodePNLocalEscapes(local) {
1205
+ return String(local).replace(/\\([_~.!$&'()*+,;=/?#@%-])/g, '$1');
1206
+ }
1207
+
1030
1208
  function numericLiteral(value) {
1031
1209
  if (Number.isInteger(value)) return literal(value, XSD_INTEGER);
1032
1210
  return literal(value, XSD_DECIMAL);
@@ -1096,7 +1274,10 @@
1096
1274
  }
1097
1275
  }
1098
1276
 
1099
- function tokenize(source, filename = '<input>') {
1277
+ function tokenize(source, filenameOrOptions = '<input>') {
1278
+ const options = typeof filenameOrOptions === 'object' && filenameOrOptions !== null ? filenameOrOptions : { filename: filenameOrOptions };
1279
+ const filename = options.filename || '<input>';
1280
+ const strictGrammar = !!options.strictGrammar;
1100
1281
  const tokens = [];
1101
1282
  let i = 0;
1102
1283
  let line = 1;
@@ -1111,8 +1292,8 @@
1111
1292
  else column += 1;
1112
1293
  return ch;
1113
1294
  }
1114
- function token(type, value, startLine, startColumn) {
1115
- tokens.push({ type, value, line: startLine, column: startColumn, filename });
1295
+ function token(type, value, startLine, startColumn, extra = {}) {
1296
+ tokens.push({ type, value, line: startLine, column: startColumn, filename, ...extra });
1116
1297
  }
1117
1298
  function syntax(message, startLine, startColumn) {
1118
1299
  throw new SyntaxErrorWithLocation(message, { line: startLine, column: startColumn, filename });
@@ -1150,14 +1331,37 @@
1150
1331
  const length = esc === 'u' ? 4 : 8;
1151
1332
  let hex = '';
1152
1333
  for (let j = 0; j < length; j += 1) {
1153
- if (!/[0-9A-Fa-f]/.test(current() || '')) syntax(`Invalid \${esc} escape`, startLine, startColumn);
1334
+ if (!/[0-9A-Fa-f]/.test(current() || '')) syntax(`Invalid \\${esc} escape`, startLine, startColumn);
1154
1335
  hex += advance();
1155
1336
  }
1156
- return String.fromCodePoint(Number.parseInt(hex, 16));
1337
+ const codePoint = Number.parseInt(hex, 16);
1338
+ try { return String.fromCodePoint(codePoint); }
1339
+ catch { syntax(`Invalid \\${esc} escape`, startLine, startColumn); }
1157
1340
  }
1341
+ if (strictGrammar && !Object.hasOwn(escapeMap, esc)) syntax(`Invalid escape \\${esc}`, startLine, startColumn);
1158
1342
  return escapeValue(esc);
1159
1343
  }
1160
1344
 
1345
+ function readIriChar(startLine, startColumn) {
1346
+ if (current() === '\\') {
1347
+ advance();
1348
+ const esc = advance();
1349
+ if (esc !== 'u' && esc !== 'U') syntax(`Invalid IRI escape \\${esc}`, startLine, startColumn);
1350
+ const length = esc === 'u' ? 4 : 8;
1351
+ let hex = '';
1352
+ for (let j = 0; j < length; j += 1) {
1353
+ if (!/[0-9A-Fa-f]/.test(current() || '')) syntax(`Invalid \\${esc} escape`, startLine, startColumn);
1354
+ hex += advance();
1355
+ }
1356
+ const codePoint = Number.parseInt(hex, 16);
1357
+ try { return String.fromCodePoint(codePoint); }
1358
+ catch { syntax(`Invalid \\${esc} escape`, startLine, startColumn); }
1359
+ }
1360
+ const c = current();
1361
+ if (strictGrammar && (/[\u0000-\u0020]/.test(c) || /[<>"{}|^`]/.test(c))) syntax(`Invalid character in IRI reference ${JSON.stringify(c)}`, startLine, startColumn);
1362
+ return advance();
1363
+ }
1364
+
1161
1365
  while (i < source.length) {
1162
1366
  const ch = current();
1163
1367
  if (/\s/.test(ch)) { advance(); continue; }
@@ -1203,7 +1407,7 @@
1203
1407
  if (ch === '<' && looksLikeIRI(source, i)) {
1204
1408
  let value = '';
1205
1409
  advance();
1206
- while (i < source.length && current() !== '>') value += advance();
1410
+ while (i < source.length && current() !== '>') value += readIriChar(startLine, startColumn);
1207
1411
  if (current() !== '>') syntax('Unterminated IRI', startLine, startColumn);
1208
1412
  advance();
1209
1413
  token('iri', value, startLine, startColumn);
@@ -1223,7 +1427,7 @@
1223
1427
  }
1224
1428
  if (!startsWith(quote.repeat(3))) syntax('Unterminated long string literal', startLine, startColumn);
1225
1429
  advance(); advance(); advance();
1226
- token('string', value, startLine, startColumn);
1430
+ token('string', value, startLine, startColumn, { long: true, quote });
1227
1431
  continue;
1228
1432
  }
1229
1433
 
@@ -1241,7 +1445,7 @@
1241
1445
  }
1242
1446
  if (current() !== quote) syntax('Unterminated string literal', startLine, startColumn);
1243
1447
  advance();
1244
- token('string', value, startLine, startColumn);
1448
+ token('string', value, startLine, startColumn, { long: false, quote });
1245
1449
  continue;
1246
1450
  }
1247
1451
 
@@ -1287,7 +1491,16 @@
1287
1491
  let value = '';
1288
1492
  while (i < source.length) {
1289
1493
  const c = current();
1290
- if (/\s/.test(c) || '{}()[].,;|'.includes(c) || '=<>+-*/!^~'.includes(c)) break;
1494
+ if (c === '\\' && peek() !== undefined) {
1495
+ value += advance();
1496
+ value += advance();
1497
+ continue;
1498
+ }
1499
+ if (/\s/.test(c) || '{}()[],;|'.includes(c) || '=<>+-*/!^~'.includes(c)) break;
1500
+ if (c === '.') {
1501
+ const n = peek();
1502
+ if (n === undefined || /\s/.test(n) || '{}()[],;|'.includes(n) || '=<>+-*/!^~'.includes(n)) break;
1503
+ }
1291
1504
  if (c === '#') break;
1292
1505
  value += advance();
1293
1506
  }
@@ -1320,558 +1533,621 @@
1320
1533
  return false;
1321
1534
  }
1322
1535
 
1536
+ const escapeMap = { n: '\n', r: '\r', t: '\t', b: '\b', f: '\f', '"': '"', "'": "'", '\\': '\\' };
1537
+
1323
1538
  function escapeValue(esc) {
1324
- const map = { n: '\n', r: '\r', t: '\t', b: '\b', f: '\f', '"': '"', "'": "'", '\\': '\\' };
1325
- return map[esc] ?? esc;
1539
+ return escapeMap[esc] ?? esc;
1326
1540
  }
1327
1541
 
1328
1542
  module.exports = { tokenize, SyntaxErrorWithLocation };
1329
1543
 
1330
1544
  },
1331
- "src/builtins.js": function (require, module, exports) {
1545
+ "src/rdfSyntax.js": function (require, module, exports) {
1332
1546
  'use strict';
1333
1547
 
1548
+ const { tokenize, SyntaxErrorWithLocation } = require('./tokenizer.js');
1549
+ const { ruleNeedsRunOnce } = require('./assignments.js');
1334
1550
  const {
1335
1551
  iri,
1552
+ variable,
1336
1553
  blankNode,
1337
1554
  literal,
1338
1555
  tripleTerm,
1556
+ termKey,
1339
1557
  termEquals,
1340
- termToPrimitive,
1341
- termToString,
1342
- booleanValue,
1343
- comparePrimitives,
1344
- isIRI,
1345
- isBlank,
1346
- isLiteral,
1347
- isTripleTerm,
1348
- valueToTerm,
1349
- inferDatatype,
1350
- XSD_STRING,
1351
- RDF_NS,
1558
+ formatTerm,
1559
+ RDF_TYPE,
1560
+ RDF_FIRST,
1561
+ RDF_REST,
1562
+ RDF_NIL,
1563
+ XSD_BOOLEAN,
1352
1564
  XSD_INTEGER,
1353
1565
  XSD_DECIMAL,
1354
1566
  XSD_DOUBLE,
1355
1567
  } = require('./term.js');
1356
1568
 
1357
- const XSD_DATETIME = 'http://www.w3.org/2001/XMLSchema#dateTime';
1358
- const XSD_DAYTIME_DURATION = 'http://www.w3.org/2001/XMLSchema#dayTimeDuration';
1359
- const RDF_LANGSTRING = `${RDF_NS}langString`;
1360
- const RDF_DIRLANGSTRING = `${RDF_NS}dirLangString`;
1361
- const NUMERIC_DATATYPES = new Set([XSD_INTEGER, XSD_DECIMAL, XSD_DOUBLE]);
1569
+ const RDF_NS = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
1570
+ const SRL_NS = 'http://www.w3.org/ns/shacl-rules#';
1571
+ const SHNEX_NS = 'http://www.w3.org/ns/shacl-node-expr#';
1572
+ const SPARQL_NS = 'http://www.w3.org/ns/sparql#';
1573
+ const OWL_IMPORTS = 'http://www.w3.org/2002/07/owl#imports';
1574
+ const SRL_RULE_SET = `${SRL_NS}RuleSet`;
1575
+ const SRL_RULE = `${SRL_NS}Rule`;
1576
+ const SRL_DATA = `${SRL_NS}data`;
1577
+ const SRL_RULES = `${SRL_NS}rules`;
1578
+ const SRL_BODY = `${SRL_NS}body`;
1579
+ const SRL_HEAD = `${SRL_NS}head`;
1580
+ const SRL_SUBJECT = `${SRL_NS}subject`;
1581
+ const SRL_PREDICATE = `${SRL_NS}predicate`;
1582
+ const SRL_OBJECT = `${SRL_NS}object`;
1583
+ const SRL_FILTER = `${SRL_NS}filter`;
1584
+ const SRL_EXPR = `${SRL_NS}expr`;
1585
+ const SRL_ASSIGN = `${SRL_NS}assign`;
1586
+ const SRL_ASSIGN_VAR = `${SRL_NS}assignVar`;
1587
+ const SRL_ASSIGN_VALUE = `${SRL_NS}assignValue`;
1588
+ const SRL_NOT = `${SRL_NS}not`;
1589
+ const SRL_VAR_NAME = `${SRL_NS}varName`;
1590
+ const SHNEX_VAR = `${SHNEX_NS}var`;
1362
1591
 
1363
- // This table is intentionally shaped by the SHACL 1.2 Rules grammar production BuiltInCall.
1364
- // Keys are the canonical spellings used by the draft; lookup is case-insensitive so examples
1365
- // may use SPARQL-style uppercase or lowercase spellings while still being checked against the
1366
- // grammar's finite set of built-ins.
1367
- const BUILTIN_SIGNATURES = Object.freeze({
1368
- STR: { min: 1, max: 1 },
1369
- LANG: { min: 1, max: 1 },
1370
- LANGMATCHES: { min: 2, max: 2 },
1371
- LANGDIR: { min: 1, max: 1 },
1372
- DATATYPE: { min: 1, max: 1 },
1373
- IRI: { min: 1, max: 1 },
1374
- URI: { min: 1, max: 1 },
1375
- BNODE: { min: 0, max: 1 },
1376
- ABS: { min: 1, max: 1 },
1377
- CEIL: { min: 1, max: 1 },
1378
- FLOOR: { min: 1, max: 1 },
1379
- ROUND: { min: 1, max: 1 },
1380
- CONCAT: { min: 0, max: Infinity },
1381
- SUBSTR: { min: 2, max: 3 },
1382
- STRLEN: { min: 1, max: 1 },
1383
- REPLACE: { min: 3, max: 4 },
1384
- UCASE: { min: 1, max: 1 },
1385
- LCASE: { min: 1, max: 1 },
1386
- ENCODE_FOR_URI: { min: 1, max: 1 },
1387
- CONTAINS: { min: 2, max: 2 },
1388
- STRSTARTS: { min: 2, max: 2 },
1389
- STRENDS: { min: 2, max: 2 },
1390
- STRBEFORE: { min: 2, max: 2 },
1391
- STRAFTER: { min: 2, max: 2 },
1392
- YEAR: { min: 1, max: 1 },
1393
- MONTH: { min: 1, max: 1 },
1394
- DAY: { min: 1, max: 1 },
1395
- HOURS: { min: 1, max: 1 },
1396
- MINUTES: { min: 1, max: 1 },
1397
- SECONDS: { min: 1, max: 1 },
1398
- TIMEZONE: { min: 1, max: 1 },
1399
- TZ: { min: 1, max: 1 },
1400
- NOW: { min: 0, max: 0 },
1401
- UUID: { min: 0, max: 0 },
1402
- STRUUID: { min: 0, max: 0 },
1403
- IF: { min: 3, max: 3, lazy: true },
1404
- STRLANG: { min: 2, max: 2 },
1405
- STRLANGDIR: { min: 3, max: 3 },
1406
- STRDT: { min: 2, max: 2 },
1407
- sameTerm: { min: 2, max: 2 },
1408
- isIRI: { min: 1, max: 1 },
1409
- isURI: { min: 1, max: 1 },
1410
- isBLANK: { min: 1, max: 1 },
1411
- isLITERAL: { min: 1, max: 1 },
1412
- isNUMERIC: { min: 1, max: 1 },
1413
- hasLANG: { min: 1, max: 1 },
1414
- hasLANGDIR: { min: 1, max: 1 },
1415
- REGEX: { min: 2, max: 3 },
1416
- isTRIPLE: { min: 1, max: 1 },
1417
- TRIPLE: { min: 3, max: 3 },
1418
- SUBJECT: { min: 1, max: 1 },
1419
- PREDICATE: { min: 1, max: 1 },
1420
- OBJECT: { min: 1, max: 1 },
1421
- });
1592
+ class TurtleParser {
1593
+ constructor(source, options = {}) {
1594
+ this.tokens = Array.isArray(source) ? source : tokenize(source, options.filename || '<rdf>');
1595
+ this.pos = 0;
1596
+ this.baseIRI = options.baseIRI || null;
1597
+ this.bnodeCounter = 0;
1598
+ this.prefixes = {
1599
+ '': 'http://example/',
1600
+ rdf: RDF_NS,
1601
+ srl: SRL_NS,
1602
+ shnex: SHNEX_NS,
1603
+ sparql: SPARQL_NS,
1604
+ xsd: 'http://www.w3.org/2001/XMLSchema#',
1605
+ owl: 'http://www.w3.org/2002/07/owl#',
1606
+ ...options.prefixes,
1607
+ };
1608
+ this.triples = [];
1609
+ this.imports = [];
1610
+ }
1422
1611
 
1423
- const BUILTIN_BY_LOWER = new Map(Object.keys(BUILTIN_SIGNATURES).map((name) => [name.toLowerCase(), name]));
1612
+ parseDocument() {
1613
+ while (!this.is('eof')) {
1614
+ if (this.matchDirective('PREFIX', '@prefix')) this.parsePrefix(this.previous().value.startsWith('@'));
1615
+ else if (this.matchDirective('BASE', '@base')) this.parseBase(this.previous().value.startsWith('@'));
1616
+ else this.parseTriplesStatement();
1617
+ }
1618
+ return {
1619
+ baseIRI: this.baseIRI,
1620
+ prefixes: { ...this.prefixes },
1621
+ triples: this.triples,
1622
+ imports: this.imports.slice(),
1623
+ };
1624
+ }
1424
1625
 
1425
- function canonicalBuiltinName(name) {
1426
- return BUILTIN_BY_LOWER.get(String(name).toLowerCase()) || null;
1427
- }
1626
+ parsePrefix(atStyle = false) {
1627
+ const nameToken = this.advance();
1628
+ if (nameToken.type !== 'word' || !nameToken.value.endsWith(':')) throw this.error('Expected prefix label ending in :', nameToken);
1629
+ const iriToken = this.expectType('iri');
1630
+ this.prefixes[nameToken.value.slice(0, -1)] = this.resolveIRI(iriToken.value, iriToken);
1631
+ if (atStyle) this.expectValue('.');
1632
+ }
1428
1633
 
1429
- function isBuiltinName(name) {
1430
- return canonicalBuiltinName(name) !== null;
1431
- }
1634
+ parseBase(atStyle = false) {
1635
+ const iriToken = this.expectType('iri');
1636
+ this.baseIRI = this.resolveIRI(iriToken.value, iriToken);
1637
+ if (atStyle) this.expectValue('.');
1638
+ }
1432
1639
 
1433
- function builtinNames() {
1434
- return Object.keys(BUILTIN_SIGNATURES);
1435
- }
1640
+ parseTriplesStatement() {
1641
+ const subjectNode = this.parseNode();
1642
+ this.triples.push(...subjectNode.triples);
1643
+ this.triples.push(...this.parsePredicateObjectList(subjectNode.term, ['.']));
1644
+ this.expectValue('.');
1645
+ }
1436
1646
 
1437
- function evalExpression(expr, binding, options = {}) {
1438
- switch (expr.type) {
1439
- case 'literal':
1440
- return expr.value;
1441
- case 'term':
1442
- return expr.value;
1443
- case 'var':
1444
- return binding[expr.name];
1445
- case 'list':
1446
- return expr.items.map((item) => evalExpression(item, binding, options));
1447
- case 'unary': {
1448
- const value = evalExpression(expr.expr, binding, options);
1449
- if (expr.op === '!') return !booleanValue(value);
1450
- if (expr.op === '-') return negateNumeric(termToPrimitive(valueToTermIfNeeded(value)));
1451
- if (expr.op === '+') return unaryPlusNumeric(termToPrimitive(valueToTermIfNeeded(value)));
1452
- throw new Error(`Unsupported unary operator ${expr.op}`);
1453
- }
1454
- case 'binary': {
1455
- const left = evalExpression(expr.left, binding, options);
1456
- if (expr.op === '&&') return booleanValue(left) && booleanValue(evalExpression(expr.right, binding, options));
1457
- if (expr.op === '||') return booleanValue(left) || booleanValue(evalExpression(expr.right, binding, options));
1458
- const right = evalExpression(expr.right, binding, options);
1459
- return evalBinary(expr.op, left, right);
1647
+ parsePredicateObjectList(subject, terminators = [']']) {
1648
+ const triples = [];
1649
+ while (!terminators.some((value) => this.checkValue(value))) {
1650
+ const predicate = this.parseVerb();
1651
+ do {
1652
+ const objectNode = this.parseNode();
1653
+ triples.push(...objectNode.triples);
1654
+ triples.push({ s: subject, p: predicate, o: objectNode.term });
1655
+ if (predicate.type === 'iri' && predicate.value === OWL_IMPORTS && objectNode.term.type === 'iri') this.imports.push(objectNode.term.value);
1656
+ } while (this.matchValue(','));
1657
+ if (this.matchValue(';')) {
1658
+ while (this.matchValue(';')) { /* tolerate repeated semicolons */ }
1659
+ if (terminators.some((value) => this.checkValue(value))) break;
1660
+ } else break;
1460
1661
  }
1461
- case 'call':
1462
- return evalCallExpression(expr, binding, options);
1463
- default:
1464
- throw new Error(`Unsupported expression type ${expr.type}`);
1662
+ return triples;
1465
1663
  }
1466
- }
1467
1664
 
1468
- function evalCallExpression(expr, binding, options) {
1469
- const canonical = canonicalBuiltinName(expr.name);
1470
- if (canonical === 'IF') {
1471
- validateArity(canonical, expr.args.length);
1472
- const condition = evalExpression(expr.args[0], binding, options);
1473
- return evalExpression(booleanValue(condition) ? expr.args[1] : expr.args[2], binding, options);
1665
+ parseNode() {
1666
+ if (this.checkValue('[')) return this.parseBlankNodePropertyList();
1667
+ if (this.checkValue('(')) return this.parseCollection();
1668
+ return { term: this.parseTerm(), triples: [] };
1474
1669
  }
1475
- return callBuiltin(expr.name, expr.args.map((arg) => evalExpression(arg, binding, options)), binding, options);
1476
- }
1477
1670
 
1478
- function evalBinary(op, left, right) {
1479
- if (op === '=') return termishEquals(left, right);
1480
- if (op === '!=') return !termishEquals(left, right);
1481
- if (op === 'IN' || op === 'NOT IN') {
1482
- const list = Array.isArray(right) ? right : [];
1483
- const found = list.some((item) => termishEquals(left, item));
1484
- return op === 'IN' ? found : !found;
1671
+ parseBlankNodePropertyList() {
1672
+ this.expectValue('[');
1673
+ const node = this.freshBlankNode();
1674
+ if (this.matchValue(']')) return { term: node, triples: [] };
1675
+ const triples = this.parsePredicateObjectList(node, [']']);
1676
+ this.expectValue(']');
1677
+ return { term: node, triples };
1485
1678
  }
1486
- if (['<', '<=', '>', '>='].includes(op)) {
1487
- const cmp = comparePrimitives(left, right);
1488
- if (op === '<') return cmp < 0;
1489
- if (op === '<=') return cmp <= 0;
1490
- if (op === '>') return cmp > 0;
1491
- if (op === '>=') return cmp >= 0;
1679
+
1680
+ parseCollection() {
1681
+ this.expectValue('(');
1682
+ if (this.matchValue(')')) return { term: iri(RDF_NIL), triples: [] };
1683
+ const items = [];
1684
+ while (!this.checkValue(')')) items.push(this.parseNode());
1685
+ this.expectValue(')');
1686
+ const triples = [];
1687
+ for (const item of items) triples.push(...item.triples);
1688
+ const cells = items.map(() => this.freshBlankNode());
1689
+ for (let i = 0; i < items.length; i += 1) {
1690
+ triples.push({ s: cells[i], p: iri(RDF_FIRST), o: items[i].term });
1691
+ triples.push({ s: cells[i], p: iri(RDF_REST), o: i + 1 < cells.length ? cells[i + 1] : iri(RDF_NIL) });
1692
+ }
1693
+ return { term: cells[0], triples };
1492
1694
  }
1493
- const lp = termToPrimitive(valueToTermIfNeeded(left));
1494
- const rp = termToPrimitive(valueToTermIfNeeded(right));
1495
- if (op === '+') {
1496
- if (isNumericPrimitive(lp) && isNumericPrimitive(rp)) return addNumeric(lp, rp);
1497
- return String(lp) + String(rp);
1695
+
1696
+ parseVerb() {
1697
+ if (this.checkType('word') && this.peek().value === 'a') { this.advance(); return iri(RDF_TYPE); }
1698
+ const term = this.parseTerm();
1699
+ if (term.type !== 'iri') throw this.error('Expected IRI as Turtle predicate');
1700
+ return term;
1498
1701
  }
1499
- if (op === '-') return subtractNumeric(lp, rp);
1500
- if (op === '*') return multiplyNumeric(lp, rp);
1501
- if (op === '/') return Number(lp) / Number(rp);
1502
- throw new Error(`Unsupported binary operator ${op}`);
1503
- }
1504
1702
 
1703
+ parseTerm() {
1704
+ const token = this.advance();
1705
+ if (token.type === 'operator' && (token.value === '+' || token.value === '-') && this.peek().type === 'number') {
1706
+ const numberToken = this.advance();
1707
+ return numericLiteral(token.value === '-' ? -numberToken.value : numberToken.value);
1708
+ }
1709
+ if (token.type === 'iri') return iri(this.resolveIRI(token.value, token));
1710
+ if (token.type === 'string') return this.parseLiteralAfterToken(token);
1711
+ if (token.type === 'number') return numericLiteral(token.value);
1712
+ if (token.value === '<<(') return this.parseTripleTermAfterOpen();
1713
+ if (token.type === 'word') {
1714
+ const word = token.value.includes(':') || token.value.startsWith('_:') ? this.consumeHyphenatedWord(token.value) : token.value;
1715
+ if (word === 'a') return iri(RDF_TYPE);
1716
+ if (word === 'true') return literal(true, XSD_BOOLEAN);
1717
+ if (word === 'false') return literal(false, XSD_BOOLEAN);
1718
+ if (word.startsWith('_:')) return blankNode(word.slice(2));
1719
+ if (word.includes(':')) return iri(this.expandPrefixedName(word, token));
1720
+ }
1721
+ throw this.error(`Expected RDF term, got ${token.value}`, token);
1722
+ }
1505
1723
 
1506
- function isNumericPrimitive(value) {
1507
- return typeof value === 'number' || typeof value === 'bigint';
1508
- }
1724
+ parseTripleTermAfterOpen() {
1725
+ const s = this.parseTerm();
1726
+ const p = this.parseVerb();
1727
+ const o = this.parseTerm();
1728
+ this.expectValue(')>>');
1729
+ return tripleTerm(s, p, o);
1730
+ }
1509
1731
 
1510
- function isIntegerPrimitive(value) {
1511
- return typeof value === 'bigint' || (typeof value === 'number' && Number.isInteger(value));
1512
- }
1732
+ parseLiteralAfterToken(token) {
1733
+ if (this.matchValue('^^')) {
1734
+ const datatype = this.parseDatatypeIRI();
1735
+ return literal(coerceLexicalLiteral(token.value, datatype), datatype, null);
1736
+ }
1737
+ if (this.checkType('word') && /^@[A-Za-z]+(?:-[A-Za-z0-9]+)*(?:--[A-Za-z]+)?$/.test(this.peek().value)) {
1738
+ const tag = this.advance().value.slice(1).toLowerCase();
1739
+ const [lang, langDir = null] = tag.split('--');
1740
+ return literal(token.value, null, lang, langDir);
1741
+ }
1742
+ return literal(token.value);
1743
+ }
1513
1744
 
1514
- function toBigIntInteger(value) {
1515
- if (typeof value === 'bigint') return value;
1516
- if (typeof value === 'number' && Number.isInteger(value) && Number.isSafeInteger(value)) return BigInt(value);
1517
- throw new Error(`Cannot convert ${String(value)} to BigInt safely`);
1518
- }
1745
+ parseDatatypeIRI() {
1746
+ const token = this.advance();
1747
+ if (token.type === 'iri') return this.resolveIRI(token.value, token);
1748
+ if (token.type === 'word' && token.value.includes(':')) return this.expandPrefixedName(token.value, token);
1749
+ throw this.error(`Expected datatype IRI, got ${token.value}`, token);
1750
+ }
1519
1751
 
1520
- function fromIntegerResult(value) {
1521
- if (value <= BigInt(Number.MAX_SAFE_INTEGER) && value >= BigInt(Number.MIN_SAFE_INTEGER)) return Number(value);
1522
- return value;
1523
- }
1752
+ freshBlankNode() {
1753
+ this.bnodeCounter += 1;
1754
+ return blankNode(`rdf${this.bnodeCounter}`);
1755
+ }
1524
1756
 
1525
- function addNumeric(left, right) {
1526
- if (isIntegerPrimitive(left) && isIntegerPrimitive(right)) {
1527
- if (typeof left === 'bigint' || typeof right === 'bigint') return fromIntegerResult(toBigIntInteger(left) + toBigIntInteger(right));
1528
- const result = left + right;
1529
- if (Number.isSafeInteger(result)) return result;
1530
- return toBigIntInteger(left) + toBigIntInteger(right);
1757
+ consumeHyphenatedWord(value) {
1758
+ let out = value;
1759
+ while (this.checkValue('-') && (this.peekN(1).type === 'word' || this.peekN(1).type === 'number')) {
1760
+ this.advance();
1761
+ out += `-${this.advance().value}`;
1762
+ }
1763
+ return out;
1531
1764
  }
1532
- return Number(left) + Number(right);
1533
- }
1534
1765
 
1535
- function subtractNumeric(left, right) {
1536
- if (isIntegerPrimitive(left) && isIntegerPrimitive(right)) {
1537
- if (typeof left === 'bigint' || typeof right === 'bigint') return fromIntegerResult(toBigIntInteger(left) - toBigIntInteger(right));
1538
- const result = left - right;
1539
- if (Number.isSafeInteger(result)) return result;
1540
- return toBigIntInteger(left) - toBigIntInteger(right);
1766
+ expandPrefixedName(value, token) {
1767
+ const colon = value.indexOf(':');
1768
+ if (colon < 0) throw this.error(`Expected prefixed name, got ${value}`, token);
1769
+ const prefix = value.slice(0, colon);
1770
+ const local = value.slice(colon + 1);
1771
+ if (!Object.hasOwn(this.prefixes, prefix)) throw this.error(`Unknown prefix ${prefix}:`, token);
1772
+ return this.prefixes[prefix] + local;
1541
1773
  }
1542
- return Number(left) - Number(right);
1543
- }
1544
1774
 
1545
- function multiplyNumeric(left, right) {
1546
- if (isIntegerPrimitive(left) && isIntegerPrimitive(right)) {
1547
- if (typeof left === 'bigint' || typeof right === 'bigint') return fromIntegerResult(toBigIntInteger(left) * toBigIntInteger(right));
1548
- const result = left * right;
1549
- if (Number.isSafeInteger(result)) return result;
1550
- return toBigIntInteger(left) * toBigIntInteger(right);
1775
+ resolveIRI(value) {
1776
+ if (!this.baseIRI || /^[A-Za-z][A-Za-z0-9+.-]*:/.test(value)) return value;
1777
+ try { return new URL(value, this.baseIRI).href; } catch (_) { return value; }
1551
1778
  }
1552
- return Number(left) * Number(right);
1553
- }
1554
1779
 
1555
- function negateNumeric(value) {
1556
- if (typeof value === 'bigint') return -value;
1557
- return -Number(value);
1558
- }
1780
+ matchDirective(...names) {
1781
+ if (this.checkType('word')) {
1782
+ const value = this.peek().value;
1783
+ if (names.some((name) => value.toUpperCase() === name.toUpperCase())) { this.advance(); return true; }
1784
+ }
1785
+ return false;
1786
+ }
1559
1787
 
1560
- function unaryPlusNumeric(value) {
1561
- if (typeof value === 'bigint') return value;
1562
- return Number(value);
1788
+ previous() { return this.tokens[this.pos - 1]; }
1789
+ peek() { return this.tokens[this.pos]; }
1790
+ peekN(n) { return this.tokens[this.pos + n]; }
1791
+ is(type) { return this.peek().type === type; }
1792
+ checkType(type) { return this.peek().type === type; }
1793
+ checkValue(value) { return this.peek().value === value; }
1794
+ matchValue(value) { if (this.checkValue(value)) { this.advance(); return true; } return false; }
1795
+ advance() { return this.tokens[this.pos++]; }
1796
+ expectType(type) { const token = this.advance(); if (token.type !== type) throw this.error(`Expected ${type}, got ${token.value}`, token); return token; }
1797
+ expectValue(value) { const token = this.advance(); if (token.value !== value) throw this.error(`Expected ${value}, got ${token.value}`, token); return token; }
1798
+ error(message, token = this.peek()) { return new SyntaxErrorWithLocation(message, token); }
1563
1799
  }
1564
1800
 
1565
- function valueToTermIfNeeded(value) {
1566
- return value && value.type ? value : literal(value, inferDatatype(value));
1801
+ function parseRdfDocument(source, options = {}) {
1802
+ return new TurtleParser(source, options).parseDocument();
1567
1803
  }
1568
1804
 
1569
- function termishEquals(left, right) {
1570
- if (left && left.type && right && right.type) return termEquals(left, right);
1571
- const lp = left && left.type ? termToPrimitive(left) : left;
1572
- const rp = right && right.type ? termToPrimitive(right) : right;
1573
- return lp === rp;
1805
+ function parseRdfSyntax(source, options = {}) {
1806
+ const document = parseRdfDocument(source, options);
1807
+ return rdfDocumentToProgram(document, options);
1574
1808
  }
1575
1809
 
1576
- function callBuiltin(name, args, binding = {}, options = {}) {
1577
- const injected = options.builtins && (options.builtins[name] || options.builtins[String(name).toLowerCase()]);
1578
- if (injected) return injected(args, { binding, iri, blankNode, literal, tripleTerm, termToString, booleanValue, termToPrimitive });
1579
-
1580
- if (localName(name).toLowerCase() === 'sudoku') {
1581
- if (args.length !== 1) throw new Error(`SUDOKU expects 1 argument, got ${args.length}`);
1582
- return solveSudoku(termToString(args[0]));
1583
- }
1810
+ function rdfDocumentToProgram(document, options = {}) {
1811
+ const graph = new RdfGraph(document.triples, document.prefixes);
1812
+ const ruleSetNodes = chooseRuleSets(graph, options.ruleSet);
1813
+ if (ruleSetNodes.length === 0) throw new Error('No srl:RuleSet found in RDF Rules syntax input');
1584
1814
 
1585
- const canonical = canonicalBuiltinName(name);
1586
- if (!canonical) throw new Error(`Unknown builtin ${name}`);
1587
- validateArity(canonical, args.length);
1588
- const key = canonical.toLowerCase();
1815
+ const program = {
1816
+ baseIRI: document.baseIRI || null,
1817
+ version: null,
1818
+ imports: options.rdfImportsAsImports ? document.imports.slice() : [],
1819
+ prefixes: { ...document.prefixes },
1820
+ data: [],
1821
+ rules: [],
1822
+ rdfSyntax: true,
1823
+ options: { shacl12Conformance: !!options.shacl12Conformance },
1824
+ ruleSets: ruleSetNodes.map((term) => formatTerm(term, document.prefixes)),
1825
+ };
1589
1826
 
1590
- if (key === 'str') return termToString(args[0]);
1591
- if (key === 'iri' || key === 'uri') return makeIRI(termToString(args[0]), options);
1592
- if (key === 'bnode') return makeBlankNode(args, options);
1593
- if (key === 'concat') return args.map(termToString).join('');
1594
- if (key === 'lcase') return termToString(args[0]).toLowerCase();
1595
- if (key === 'ucase') return termToString(args[0]).toUpperCase();
1596
- if (key === 'contains') return termToString(args[0]).includes(termToString(args[1]));
1597
- if (key === 'strstarts') return termToString(args[0]).startsWith(termToString(args[1]));
1598
- if (key === 'strends') return termToString(args[0]).endsWith(termToString(args[1]));
1599
- if (key === 'strbefore') {
1600
- const s = termToString(args[0]);
1601
- const needle = termToString(args[1]);
1602
- const index = s.indexOf(needle);
1603
- return index < 0 ? '' : s.slice(0, index);
1604
- }
1605
- if (key === 'strafter') {
1606
- const s = termToString(args[0]);
1607
- const needle = termToString(args[1]);
1608
- const index = s.indexOf(needle);
1609
- return index < 0 ? '' : s.slice(index + needle.length);
1610
- }
1611
- if (key === 'encode_for_uri') return encodeURIComponent(termToString(args[0]));
1612
- if (key === 'regex') return regex(args);
1613
- if (key === 'replace') return replace(args);
1614
- if (key === 'substr') return substr(args);
1615
- if (key === 'sameterm') return termishEquals(args[0], args[1]);
1616
- if (key === 'isiri' || key === 'isuri') return isIRI(args[0]);
1617
- if (key === 'isblank') return isBlank(args[0]);
1618
- if (key === 'isliteral') return isLiteral(args[0]);
1619
- if (key === 'istriple') return isTripleTerm(args[0]);
1620
- if (key === 'isnumeric') return isNumericValue(args[0]);
1621
- if (key === 'datatype') return datatypeOf(args[0]);
1622
- if (key === 'lang') return args[0] && args[0].type === 'literal' ? (args[0].lang || '') : '';
1623
- if (key === 'langmatches') return langMatches(termToString(args[0]), termToString(args[1]));
1624
- if (key === 'haslang') return !!(args[0] && args[0].type === 'literal' && args[0].lang);
1625
- if (key === 'langdir') return args[0] && args[0].type === 'literal' ? (args[0].langDir || '') : '';
1626
- if (key === 'haslangdir') return !!(args[0] && args[0].type === 'literal' && args[0].langDir);
1627
- if (key === 'strlen') return termToString(args[0]).length;
1628
- if (key === 'abs') return Math.abs(Number(termToPrimitive(valueToTermIfNeeded(args[0]))));
1629
- if (key === 'floor') return Math.floor(Number(termToPrimitive(valueToTermIfNeeded(args[0]))));
1630
- if (key === 'ceil') return Math.ceil(Number(termToPrimitive(valueToTermIfNeeded(args[0]))));
1631
- if (key === 'round') return Math.round(Number(termToPrimitive(valueToTermIfNeeded(args[0]))));
1632
- if (key === 'if') return booleanValue(args[0]) ? args[1] : args[2];
1633
- if (key === 'strdt') return literal(termToString(args[0]), termToString(args[1]));
1634
- if (key === 'strlang') return literal(termToString(args[0]), null, termToString(args[1]).toLowerCase());
1635
- if (key === 'strlangdir') return literal(termToString(args[0]), null, termToString(args[1]).toLowerCase(), termToString(args[2]).toLowerCase());
1636
- if (key === 'triple') return tripleTerm(valueToTermIfNeeded(args[0]), valueToTermIfNeeded(args[1]), valueToTermIfNeeded(args[2]));
1637
- if (key === 'subject') return isTripleTerm(args[0]) ? args[0].s : null;
1638
- if (key === 'predicate') return isTripleTerm(args[0]) ? args[0].p : null;
1639
- if (key === 'object') return isTripleTerm(args[0]) ? args[0].o : null;
1640
- if (key === 'year') return datePart(args[0], 'year');
1641
- if (key === 'month') return datePart(args[0], 'month');
1642
- if (key === 'day') return datePart(args[0], 'day');
1643
- if (key === 'hours') return datePart(args[0], 'hours');
1644
- if (key === 'minutes') return datePart(args[0], 'minutes');
1645
- if (key === 'seconds') return datePart(args[0], 'seconds');
1646
- if (key === 'timezone') return timezoneDuration(args[0]);
1647
- if (key === 'tz') return timezoneLexical(args[0]);
1648
- if (key === 'now') return literal((options.now || new Date()).toISOString(), XSD_DATETIME);
1649
- if (key === 'uuid') return iri(`urn:uuid:${freshUuid(options)}`);
1650
- if (key === 'struuid') return freshUuid(options);
1651
- throw new Error(`Unimplemented builtin ${name}`);
1652
- }
1653
-
1654
-
1655
- function localName(name) {
1656
- const text = String(name || '');
1657
- const hash = text.lastIndexOf('#');
1658
- const slash = text.lastIndexOf('/');
1659
- const colon = text.lastIndexOf(':');
1660
- const index = Math.max(hash, slash, colon);
1661
- return index >= 0 ? text.slice(index + 1) : text;
1662
- }
1663
-
1664
- function solveSudoku(puzzle) {
1665
- const text = String(puzzle || '').trim();
1666
- if (!/^[0-9.]{81}$/.test(text)) throw new Error('SUDOKU expects an 81-character puzzle string containing digits or dots');
1667
- const cells = Array.from(text, (ch) => (ch === '.' ? 0 : Number(ch)));
1668
- const peers = sudokuPeers();
1669
-
1670
- for (let i = 0; i < 81; i += 1) {
1671
- const value = cells[i];
1672
- if (value === 0) continue;
1673
- for (const peer of peers[i]) {
1674
- if (cells[peer] === value) throw new Error('SUDOKU puzzle has conflicting givens');
1827
+ for (const ruleSet of ruleSetNodes) {
1828
+ for (const dataList of graph.objects(ruleSet, SRL_DATA)) {
1829
+ for (const item of graph.list(dataList)) program.data.push(toDataTriple(item, graph));
1675
1830
  }
1676
- }
1677
-
1678
- const solved = solveSudokuCells(cells, peers);
1679
- if (!solved) return '';
1680
- return solved.join('');
1681
- }
1682
-
1683
- function solveSudokuCells(cells, peers) {
1684
- let bestIndex = -1;
1685
- let bestCandidates = null;
1686
-
1687
- for (let i = 0; i < 81; i += 1) {
1688
- if (cells[i] !== 0) continue;
1689
- const candidates = sudokuCandidates(cells, peers[i]);
1690
- if (candidates.length === 0) return null;
1691
- if (!bestCandidates || candidates.length < bestCandidates.length) {
1692
- bestIndex = i;
1693
- bestCandidates = candidates;
1694
- if (candidates.length === 1) break;
1831
+ for (const rulesList of graph.objects(ruleSet, SRL_RULES)) {
1832
+ for (const ruleNode of graph.list(rulesList)) program.rules.push(toRule(ruleNode, graph, options));
1695
1833
  }
1696
1834
  }
1835
+ return program;
1836
+ }
1697
1837
 
1698
- if (bestIndex < 0) return cells;
1699
-
1700
- for (const value of bestCandidates) {
1701
- const next = cells.slice();
1702
- next[bestIndex] = value;
1703
- const solved = solveSudokuCells(next, peers);
1704
- if (solved) return solved;
1838
+ function chooseRuleSets(graph, selected) {
1839
+ if (selected) {
1840
+ const term = graph.parseReference(selected);
1841
+ return [term];
1705
1842
  }
1706
- return null;
1843
+ const typed = graph.subjects(RDF_TYPE, iri(SRL_RULE_SET));
1844
+ if (typed.length > 0) return uniqueTerms(typed);
1845
+ const byData = graph.subjectsWithPredicate(SRL_DATA);
1846
+ const byRules = graph.subjectsWithPredicate(SRL_RULES);
1847
+ return uniqueTerms([...byData, ...byRules]).filter((term) => graph.objects(term, SRL_RULES).length > 0 || graph.objects(term, SRL_DATA).length > 0);
1707
1848
  }
1708
1849
 
1709
- function sudokuCandidates(cells, peers) {
1710
- const used = new Set();
1711
- for (const peer of peers) if (cells[peer] !== 0) used.add(cells[peer]);
1712
- const out = [];
1713
- for (let value = 1; value <= 9; value += 1) if (!used.has(value)) out.push(value);
1714
- return out;
1850
+ function toDataTriple(item, graph) {
1851
+ if (item.type === 'triple') return { s: item.s, p: item.p, o: item.o };
1852
+ const triple = toTripleLike(item, graph);
1853
+ if ([triple.s, triple.p, triple.o].some((term) => term.type === 'var')) throw new Error('RDF Rules srl:data may not contain variables');
1854
+ if (triple.p.type !== 'iri') throw new Error('RDF Rules data triple predicate must be an IRI');
1855
+ return triple;
1715
1856
  }
1716
1857
 
1717
- let SUDOKU_PEERS = null;
1718
- function sudokuPeers() {
1719
- if (SUDOKU_PEERS) return SUDOKU_PEERS;
1720
- SUDOKU_PEERS = Array.from({ length: 81 }, (_, index) => {
1721
- const row = Math.floor(index / 9);
1722
- const col = index % 9;
1723
- const boxRow = Math.floor(row / 3) * 3;
1724
- const boxCol = Math.floor(col / 3) * 3;
1725
- const peers = new Set();
1726
- for (let c = 0; c < 9; c += 1) peers.add(row * 9 + c);
1727
- for (let r = 0; r < 9; r += 1) peers.add(r * 9 + col);
1728
- for (let r = boxRow; r < boxRow + 3; r += 1) {
1729
- for (let c = boxCol; c < boxCol + 3; c += 1) peers.add(r * 9 + c);
1730
- }
1731
- peers.delete(index);
1732
- return Array.from(peers);
1733
- });
1734
- return SUDOKU_PEERS;
1858
+ function toRule(ruleNode, graph, options = {}) {
1859
+ const bodyLists = graph.objects(ruleNode, SRL_BODY);
1860
+ const headLists = graph.objects(ruleNode, SRL_HEAD);
1861
+ if (bodyLists.length !== 1 || headLists.length !== 1) throw new Error(`RDF Rule ${graph.label(ruleNode)} must have exactly one srl:body and one srl:head`);
1862
+ const body = graph.list(bodyLists[0]).map((item) => toBodyElement(item, graph));
1863
+ const head = graph.list(headLists[0]).map((item) => toTripleLike(item, graph));
1864
+ return { name: graph.label(ruleNode), head, body, runOnce: ruleNeedsRunOnce(head, body, options) };
1735
1865
  }
1736
1866
 
1737
- function validateArity(canonical, actual) {
1738
- const sig = BUILTIN_SIGNATURES[canonical];
1739
- if (!sig) throw new Error(`Unknown builtin ${canonical}`);
1740
- const tooFew = actual < sig.min;
1741
- const tooMany = actual > sig.max;
1742
- if (tooFew || tooMany) {
1743
- const expected = sig.min === sig.max ? `${sig.min}` : `${sig.min}${sig.max === Infinity ? '+' : `..${sig.max}`}`;
1744
- throw new Error(`${canonical} expects ${expected} argument${expected === '1' ? '' : 's'}, got ${actual}`);
1867
+ function toBodyElement(node, graph) {
1868
+ if (hasTripleShape(node, graph)) return { type: 'triple', triple: toTripleLike(node, graph) };
1869
+ const filters = graph.objects(node, SRL_FILTER).concat(graph.objects(node, SRL_EXPR));
1870
+ if (filters.length > 0) {
1871
+ if (filters.length !== 1) throw new Error(`Filter element ${graph.label(node)} must have exactly one srl:filter`);
1872
+ return { type: 'filter', expr: toExpression(filters[0], graph) };
1873
+ }
1874
+ const assigns = graph.objects(node, SRL_ASSIGN);
1875
+ if (assigns.length > 0) {
1876
+ if (assigns.length !== 1) throw new Error(`Assignment element ${graph.label(node)} must have exactly one srl:assign`);
1877
+ const assign = assigns[0];
1878
+ const vars = graph.objects(assign, SRL_ASSIGN_VAR);
1879
+ const values = graph.objects(assign, SRL_ASSIGN_VALUE);
1880
+ if (vars.length !== 1 || values.length !== 1) throw new Error(`Assignment ${graph.label(assign)} must have exactly one srl:assignVar and srl:assignValue`);
1881
+ const variableTerm = toVarOrTerm(vars[0], graph);
1882
+ if (variableTerm.type !== 'var') throw new Error('srl:assignVar must point to a variable node');
1883
+ return { type: 'set', variable: variableTerm.value, expr: toExpression(values[0], graph) };
1884
+ }
1885
+ const negations = graph.objects(node, SRL_NOT);
1886
+ if (negations.length > 0) {
1887
+ if (negations.length !== 1) throw new Error(`Negation element ${graph.label(node)} must have exactly one srl:not`);
1888
+ const body = graph.list(negations[0]).map((item) => {
1889
+ const clause = toBodyElement(item, graph);
1890
+ if (clause.type === 'set' || clause.type === 'not') throw new Error('RDF Rules srl:not may contain only triple patterns and filters');
1891
+ return clause;
1892
+ });
1893
+ return { type: 'not', body };
1745
1894
  }
1895
+ throw new Error(`Unsupported RDF Rules body element ${graph.label(node)}`);
1746
1896
  }
1747
1897
 
1748
- function makeIRI(value, options) {
1749
- if (options.baseIRI && !/^[A-Za-z][A-Za-z0-9+.-]*:/.test(value)) {
1750
- try { return iri(new URL(value, options.baseIRI).href); } catch (_) { /* fall through */ }
1898
+ function toTripleLike(node, graph) {
1899
+ if (node.type === 'triple') return { s: node.s, p: node.p, o: node.o };
1900
+ const subjects = graph.objects(node, SRL_SUBJECT);
1901
+ const predicates = graph.objects(node, SRL_PREDICATE);
1902
+ const objects = graph.objects(node, SRL_OBJECT);
1903
+ if (subjects.length !== 1 || predicates.length !== 1 || objects.length !== 1) {
1904
+ throw new Error(`Triple node ${graph.label(node)} must have exactly one srl:subject, srl:predicate and srl:object`);
1751
1905
  }
1752
- return iri(value);
1906
+ return {
1907
+ s: toVarOrTerm(subjects[0], graph),
1908
+ p: toVarOrTerm(predicates[0], graph),
1909
+ o: toVarOrTerm(objects[0], graph),
1910
+ };
1753
1911
  }
1754
1912
 
1755
- function makeBlankNode(args, options) {
1756
- if (args.length === 0) return blankNode(freshId(options));
1757
- const label = termToString(args[0]);
1758
- if (!options.__bnodeLabels) options.__bnodeLabels = new Map();
1759
- if (!options.__bnodeLabels.has(label)) options.__bnodeLabels.set(label, label || freshId(options));
1760
- return blankNode(options.__bnodeLabels.get(label));
1913
+ function hasTripleShape(node, graph) {
1914
+ return graph.objects(node, SRL_SUBJECT).length > 0 || graph.objects(node, SRL_PREDICATE).length > 0 || graph.objects(node, SRL_OBJECT).length > 0;
1761
1915
  }
1762
1916
 
1763
- function regex(args) {
1764
- const flags = regexFlags(termToString(args[2] || ''));
1765
- return new RegExp(termToString(args[1]), flags).test(termToString(args[0]));
1917
+ function toVarOrTerm(node, graph) {
1918
+ const varNames = graph.objects(node, SRL_VAR_NAME);
1919
+ if (varNames.length > 0) {
1920
+ if (varNames.length !== 1 || varNames[0].type !== 'literal') throw new Error(`Variable node ${graph.label(node)} must have exactly one string srl:varName`);
1921
+ return variable(String(varNames[0].value));
1922
+ }
1923
+ return node;
1766
1924
  }
1767
1925
 
1768
- function replace(args) {
1769
- const flags = regexFlags(termToString(args[3] || ''));
1770
- const effectiveFlags = flags.includes('g') ? flags : `${flags}g`;
1771
- return termToString(args[0]).replace(new RegExp(termToString(args[1]), effectiveFlags), termToString(args[2]));
1926
+ function toExpression(node, graph) {
1927
+ const varNames = graph.objects(node, SHNEX_VAR).concat(graph.objects(node, SRL_VAR_NAME));
1928
+ if (varNames.length > 0) {
1929
+ if (varNames.length !== 1 || varNames[0].type !== 'literal') throw new Error(`Expression variable ${graph.label(node)} must name one variable`);
1930
+ return { type: 'var', name: String(varNames[0].value) };
1931
+ }
1932
+ if (node.type === 'literal') {
1933
+ if (node.datatype || node.lang) return { type: 'term', value: node };
1934
+ return { type: 'literal', value: node.value };
1935
+ }
1936
+ if (node.type === 'iri' || node.type === 'blank' || node.type === 'triple') {
1937
+ const call = graph.functionCall(node);
1938
+ if (call) return toFunctionExpression(call.name, call.args.map((arg) => toExpression(arg, graph)));
1939
+ if (node.type === 'blank' && graph.hasOutgoing(node)) return { type: 'term', value: node };
1940
+ return { type: 'term', value: toVarOrTerm(node, graph) };
1941
+ }
1942
+ return { type: 'term', value: node };
1772
1943
  }
1773
1944
 
1774
- function regexFlags(flags) {
1775
- let out = '';
1776
- for (const ch of String(flags)) {
1777
- // JavaScript RegExp has no direct SPARQL/xpath "x" free-spacing flag, so ignore it.
1778
- if (ch === 'x') continue;
1779
- if ('imsuyg'.includes(ch) && !out.includes(ch)) out += ch;
1945
+ function toFunctionExpression(name, args) {
1946
+ if (name.startsWith(SPARQL_NS)) {
1947
+ const local = name.slice(SPARQL_NS.length);
1948
+ if (local === 'less-than' || local === 'lessThan') return binary('<', args);
1949
+ if (local === 'less-than-or-equal' || local === 'lessThanOrEqual') return binary('<=', args);
1950
+ if (local === 'greater-than' || local === 'greaterThan') return binary('>', args);
1951
+ if (local === 'greater-than-or-equal' || local === 'greaterThanOrEqual') return binary('>=', args);
1952
+ if (local === 'equal' || local === 'equals') return binary('=', args);
1953
+ if (local === 'not-equal' || local === 'notEqual') return binary('!=', args);
1954
+ if (local === 'add') return foldBinary('+', args);
1955
+ if (local === 'subtract') return binary('-', args);
1956
+ if (local === 'multiply') return foldBinary('*', args);
1957
+ if (local === 'divide') return binary('/', args);
1958
+ if (local === 'and' || local === 'function-and') return foldBinary('&&', args);
1959
+ if (local === 'or' || local === 'function-or') return foldBinary('||', args);
1960
+ if (local === 'not') return { type: 'unary', op: '!', expr: args[0] };
1961
+ const builtin = sparqlLocalToBuiltin(local);
1962
+ return { type: 'call', name: builtin, args };
1780
1963
  }
1781
- return out;
1964
+ return { type: 'call', name, args };
1782
1965
  }
1783
1966
 
1784
- function substr(args) {
1785
- const value = termToString(args[0]);
1786
- const start = Math.max(0, Number(termToPrimitive(valueToTermIfNeeded(args[1]))) - 1);
1787
- if (args.length >= 3) return value.substring(start, start + Number(termToPrimitive(valueToTermIfNeeded(args[2]))));
1788
- return value.substring(start);
1967
+ function binary(op, args) {
1968
+ if (args.length !== 2) throw new Error(`sparql operator ${op} expects 2 arguments`);
1969
+ return { type: 'binary', op, left: args[0], right: args[1] };
1789
1970
  }
1790
1971
 
1791
- function datatypeOf(value) {
1792
- const term = valueToTermIfNeeded(value);
1793
- if (term.type !== 'literal') return null;
1794
- if (term.langDir) return iri(RDF_DIRLANGSTRING);
1795
- if (term.lang) return iri(RDF_LANGSTRING);
1796
- return iri(term.datatype || inferDatatype(term.value) || XSD_STRING);
1972
+ function foldBinary(op, args) {
1973
+ if (args.length < 2) throw new Error(`sparql operator ${op} expects at least 2 arguments`);
1974
+ return args.slice(1).reduce((left, right) => ({ type: 'binary', op, left, right }), args[0]);
1797
1975
  }
1798
1976
 
1799
- function isNumericValue(value) {
1800
- const term = valueToTermIfNeeded(value);
1801
- if (typeof termToPrimitive(term) === 'bigint') return true;
1802
- if (typeof termToPrimitive(term) === 'number') return true;
1803
- return term.type === 'literal' && NUMERIC_DATATYPES.has(term.datatype);
1977
+ function sparqlLocalToBuiltin(local) {
1978
+ return local.replace(/-([a-z])/g, (_, ch) => ch.toUpperCase()).replace(/^./, (ch) => ch.toUpperCase());
1804
1979
  }
1805
1980
 
1806
- function datePart(value, part) {
1807
- const lexical = termToString(value);
1808
- const match = lexical.match(/^(-?\d{4,})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}):(\d{2}(?:\.\d+)?)(Z|[+-]\d{2}:?\d{2})?)?/);
1809
- if (!match) return null;
1810
- const [, year, month, day, hours = '0', minutes = '0', seconds = '0'] = match;
1811
- if (part === 'year') return Number(year);
1812
- if (part === 'month') return Number(month);
1813
- if (part === 'day') return Number(day);
1814
- if (part === 'hours') return Number(hours);
1815
- if (part === 'minutes') return Number(minutes);
1816
- if (part === 'seconds') return Number(seconds);
1817
- return null;
1981
+ class RdfGraph {
1982
+ constructor(triples, prefixes = {}) {
1983
+ this.triples = triples;
1984
+ this.prefixes = prefixes;
1985
+ this.bySubject = new Map();
1986
+ for (const triple of triples) {
1987
+ const key = termKey(triple.s);
1988
+ if (!this.bySubject.has(key)) this.bySubject.set(key, []);
1989
+ this.bySubject.get(key).push(triple);
1990
+ }
1991
+ }
1992
+
1993
+ objects(subject, predicateIRI) {
1994
+ const rows = this.bySubject.get(termKey(subject)) || [];
1995
+ return rows.filter((triple) => triple.p.type === 'iri' && triple.p.value === predicateIRI).map((triple) => triple.o);
1996
+ }
1997
+
1998
+ subjects(predicateIRI, object) {
1999
+ return this.triples.filter((triple) => triple.p.type === 'iri' && triple.p.value === predicateIRI && termEquals(triple.o, object)).map((triple) => triple.s);
2000
+ }
2001
+
2002
+ subjectsWithPredicate(predicateIRI) {
2003
+ return this.triples.filter((triple) => triple.p.type === 'iri' && triple.p.value === predicateIRI).map((triple) => triple.s);
2004
+ }
2005
+
2006
+ hasOutgoing(subject) {
2007
+ return (this.bySubject.get(termKey(subject)) || []).length > 0;
2008
+ }
2009
+
2010
+ list(head) {
2011
+ const out = [];
2012
+ let node = head;
2013
+ const seen = new Set();
2014
+ while (!(node.type === 'iri' && node.value === RDF_NIL)) {
2015
+ const key = termKey(node);
2016
+ if (seen.has(key)) throw new Error(`Cycle in RDF list at ${this.label(node)}`);
2017
+ seen.add(key);
2018
+ const first = this.objects(node, RDF_FIRST);
2019
+ const rest = this.objects(node, RDF_REST);
2020
+ if (first.length !== 1 || rest.length !== 1) throw new Error(`Expected RDF list node at ${this.label(node)}`);
2021
+ out.push(first[0]);
2022
+ node = rest[0];
2023
+ }
2024
+ return out;
2025
+ }
2026
+
2027
+ functionCall(node) {
2028
+ if (node.type !== 'blank') return null;
2029
+ const rows = (this.bySubject.get(termKey(node)) || []).filter((triple) => triple.p.type === 'iri');
2030
+ const calls = rows.filter((triple) => triple.p.value.startsWith(SPARQL_NS) || triple.p.value.includes('#') || triple.p.value.includes('/'));
2031
+ const viable = calls.filter((triple) => isRdfListHead(triple.o, this));
2032
+ if (viable.length !== 1) return null;
2033
+ return { name: viable[0].p.value, args: this.list(viable[0].o) };
2034
+ }
2035
+
2036
+ parseReference(text) {
2037
+ if (typeof text !== 'string') return text;
2038
+ if (text.startsWith('<') && text.endsWith('>')) return iri(text.slice(1, -1));
2039
+ if (text.startsWith('_:')) return blankNode(text.slice(2));
2040
+ const colon = text.indexOf(':');
2041
+ if (colon >= 0) {
2042
+ const prefix = text.slice(0, colon);
2043
+ const local = text.slice(colon + 1);
2044
+ const ns = this.prefixes[prefix] || (prefix === 'srl' ? SRL_NS : null);
2045
+ if (ns) return iri(ns + local);
2046
+ }
2047
+ return iri(text);
2048
+ }
2049
+
2050
+ label(term) { return formatTerm(term, this.prefixes); }
1818
2051
  }
1819
2052
 
1820
- function timezoneLexical(value) {
1821
- const lexical = termToString(value);
1822
- const match = lexical.match(/(?:T\d{2}:\d{2}:\d{2}(?:\.\d+)?)(Z|[+-]\d{2}:?\d{2})$/);
1823
- return match ? match[1] : '';
2053
+ function isRdfListHead(term, graph) {
2054
+ return (term.type === 'iri' && term.value === RDF_NIL) || graph.objects(term, RDF_FIRST).length === 1;
1824
2055
  }
1825
2056
 
1826
- function timezoneDuration(value) {
1827
- const zone = timezoneLexical(value);
1828
- if (!zone) return null;
1829
- if (zone === 'Z') return literal('PT0S', XSD_DAYTIME_DURATION);
1830
- const match = zone.match(/^([+-])(\d{2}):?(\d{2})$/);
1831
- if (!match) return null;
1832
- const [, sign, hh, mm] = match;
1833
- const hours = Number(hh);
1834
- const minutes = Number(mm);
1835
- const body = `${hours ? `${hours}H` : ''}${minutes ? `${minutes}M` : ''}` || '0S';
1836
- return literal(`${sign === '-' ? '-' : ''}PT${body}`, XSD_DAYTIME_DURATION);
2057
+ function uniqueTerms(terms) {
2058
+ const seen = new Set();
2059
+ const out = [];
2060
+ for (const term of terms) {
2061
+ const key = termKey(term);
2062
+ if (!seen.has(key)) { seen.add(key); out.push(term); }
2063
+ }
2064
+ return out;
1837
2065
  }
1838
2066
 
1839
- function langMatches(lang, range) {
1840
- if (range === '*') return lang.length > 0;
1841
- return lang.toLowerCase() === range.toLowerCase() || lang.toLowerCase().startsWith(`${range.toLowerCase()}-`);
2067
+ function numericLiteral(value) {
2068
+ if (Number.isInteger(value)) return literal(value, XSD_INTEGER);
2069
+ if (String(value).includes('e') || String(value).includes('E')) return literal(value, XSD_DOUBLE);
2070
+ return literal(value, XSD_DECIMAL);
1842
2071
  }
1843
2072
 
1844
- function freshUuid(options) {
1845
- if (typeof options.uuidGenerator === 'function') return String(options.uuidGenerator());
1846
- options.__eyelengUuidCounter = (options.__eyelengUuidCounter || 0) + 1;
1847
- return `00000000-0000-4000-8000-${String(options.__eyelengUuidCounter).padStart(12, '0')}`;
2073
+ function parseIntegerLiteral(value) {
2074
+ const text = String(value);
2075
+ const asNumber = Number.parseInt(text, 10);
2076
+ return Number.isSafeInteger(asNumber) && String(asNumber) === text.replace(/^\+/, '') ? asNumber : BigInt(text);
1848
2077
  }
1849
2078
 
1850
- function freshId(options) {
1851
- options.__eyelengCounter = (options.__eyelengCounter || 0) + 1;
1852
- return `eyeleng-${options.__eyelengCounter}`;
2079
+ function coerceLexicalLiteral(value, datatype) {
2080
+ if (datatype === XSD_INTEGER) return parseIntegerLiteral(value);
2081
+ if (datatype === XSD_DECIMAL || datatype === XSD_DOUBLE) return Number(value);
2082
+ if (datatype === XSD_BOOLEAN) return value === true || value === 'true' || value === '1';
2083
+ return value;
1853
2084
  }
1854
2085
 
1855
- function asTerm(value) {
1856
- return valueToTerm(value);
2086
+ function looksLikeRdfRules(source, options = {}) {
2087
+ if (options.syntax === 'rdf') return true;
2088
+ if (options.syntax === 'srl') return false;
2089
+ if (options.filename && /\.(ttl|trig|nt|n3)$/i.test(options.filename)) return true;
2090
+ return /\bsrl:RuleSet\b|\bsrl:rules\b|http:\/\/www\.w3\.org\/ns\/shacl-rules#RuleSet/.test(source);
1857
2091
  }
1858
2092
 
1859
2093
  module.exports = {
1860
- BUILTIN_SIGNATURES,
1861
- builtinNames,
1862
- canonicalBuiltinName,
1863
- isBuiltinName,
1864
- validateArity,
1865
- evalExpression,
1866
- booleanValue,
1867
- asTerm,
1868
- callBuiltin,
1869
- evalBinary,
2094
+ parseRdfDocument,
2095
+ parseRdfSyntax,
2096
+ rdfDocumentToProgram,
2097
+ looksLikeRdfRules,
2098
+ TurtleParser,
2099
+ RdfGraph,
2100
+ constants: {
2101
+ SRL_NS,
2102
+ SHNEX_NS,
2103
+ SPARQL_NS,
2104
+ SRL_RULE_SET,
2105
+ SRL_RULE,
2106
+ },
1870
2107
  };
1871
2108
 
1872
- },
1873
- "src/term.js": function (require, module, exports) {
1874
- 'use strict';
2109
+
2110
+ // ---- Grammar-hardened RDF 1.1 / RDF 1.2 syntax helpers ----
2111
+ // These functions are used by the W3C RDF manifest harness and are kept in
2112
+ // rdfSyntax.js beside the existing Turtle/RDF-Rules front-end instead of in a
2113
+ // separate monolithic test file. They intentionally keep an internal test
2114
+ // graph representation because the W3C manifests exercise syntax, datasets,
2115
+ // triple terms, and RDF 1.2 annotation isomorphism independently from the SRL
2116
+ // rule engine representation.
2117
+ const rdfW3cSyntax = (() => {
2118
+ // Grammar-hardened RDF syntax code shared by the RDF Rules front-end and W3C manifest harness.
2119
+ function iri(value) {
2120
+ if (!value) throw new Error('iri(value) requires a non-empty value');
2121
+ return Object.freeze({ kind: 'iri', value: String(value) });
2122
+ }
2123
+ function literal(value, datatype = null, language = null, langDir = null) {
2124
+ return Object.freeze({ kind: 'literal', value: String(value), datatype, language, langDir });
2125
+ }
2126
+ function blank(value) {
2127
+ const clean = String(value || '').replace(/^_:/, '');
2128
+ if (!clean) throw new Error('blank(value) requires a name');
2129
+ return Object.freeze({ kind: 'blank', value: clean });
2130
+ }
2131
+ function tripleTerm(s, p, o) { return Object.freeze({ kind: 'triple', s, p, o }); }
2132
+ function variable(name) {
2133
+ const clean = String(name || '').replace(/^\?/, '');
2134
+ if (!clean) throw new Error('variable(name) requires a name');
2135
+ return Object.freeze({ kind: 'var', name: clean });
2136
+ }
2137
+ function triple(s, p, o, graph = null) { return Object.freeze({ s, p, o, graph }); }
2138
+ function termKey(term) {
2139
+ if (!term) return 'default';
2140
+ switch (term.kind) {
2141
+ case 'iri': return `I:${term.value}`;
2142
+ case 'literal': return `L:${JSON.stringify(term.value)}^^${term.datatype || ''}@${term.language || ''}`;
2143
+ case 'blank': return `B:${term.value}`;
2144
+ case 'var': return `V:${term.name}`;
2145
+ case 'triple': return `T:${termKey(term.s)} ${termKey(term.p)} ${termKey(term.o)}`;
2146
+ default: throw new Error(`Unsupported term kind: ${term.kind}`);
2147
+ }
2148
+ }
2149
+ function tripleKey(t) { return `${termKey(t.s)} ${termKey(t.p)} ${termKey(t.o)} ${termKey(t.graph)}`; }
2150
+ class Rule { constructor({ id, body = [], head = [], profile = 'n3-rules-subset-v0' } = {}) { this.id = id; this.body = body; this.head = head; this.profile = profile; } }
1875
2151
 
1876
2152
  const RDF_NS = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
1877
2153
  const RDF_TYPE = `${RDF_NS}type`;
@@ -1879,273 +2155,941 @@
1879
2155
  const RDF_REST = `${RDF_NS}rest`;
1880
2156
  const RDF_NIL = `${RDF_NS}nil`;
1881
2157
  const RDF_REIFIES = `${RDF_NS}reifies`;
1882
- const XSD_STRING = 'http://www.w3.org/2001/XMLSchema#string';
2158
+ const RDF_LANG_STRING = `${RDF_NS}langString`;
2159
+ const RDF_DIR_LANG_STRING = `${RDF_NS}dirLangString`;
1883
2160
  const XSD_BOOLEAN = 'http://www.w3.org/2001/XMLSchema#boolean';
1884
2161
  const XSD_INTEGER = 'http://www.w3.org/2001/XMLSchema#integer';
1885
2162
  const XSD_DECIMAL = 'http://www.w3.org/2001/XMLSchema#decimal';
1886
2163
  const XSD_DOUBLE = 'http://www.w3.org/2001/XMLSchema#double';
2164
+ const XSD_STRING = 'http://www.w3.org/2001/XMLSchema#string';
1887
2165
 
1888
- function iri(value) {
1889
- return { type: 'iri', value: String(value) };
1890
- }
2166
+ // ---- N-Triples / N-Quads parser ----
2167
+ const { parseNQuads, termToNQuads, tripleToNQuads, triplesToNQuads } = (() => {
1891
2168
 
1892
- function variable(name) {
1893
- return { type: 'var', value: String(name).replace(/^[?$]/, '') };
2169
+ function isWs(ch) { return ch === ' ' || ch === '\t'; }
2170
+ function isLineEnd(ch) { return ch === '\n' || ch === '\r'; }
2171
+ function isHex(text) { return /^[0-9A-Fa-f]+$/.test(text); }
2172
+
2173
+ function decodeCodePoint(hex, token) {
2174
+ const code = Number.parseInt(hex, 16);
2175
+ if (!Number.isFinite(code) || code < 0 || code > 0x10ffff || (code >= 0xd800 && code <= 0xdfff)) {
2176
+ throw new Error(`Invalid Unicode escape in ${token}`);
2177
+ }
2178
+ return String.fromCodePoint(code);
1894
2179
  }
1895
2180
 
1896
- function blankNode(value) {
1897
- return { type: 'blank', value: String(value).replace(/^_:/, '') };
2181
+ function decodeIriEscapes(value, token = 'IRI') {
2182
+ let out = '';
2183
+ for (let i = 0; i < value.length; i += 1) {
2184
+ const ch = value[i];
2185
+ if (ch !== '\\') {
2186
+ if (/[<>"{}|^`\u0000-\u0020]/.test(ch)) throw new Error(`Invalid character in ${token}`);
2187
+ out += ch;
2188
+ continue;
2189
+ }
2190
+ const esc = value[++i];
2191
+ if (esc === 'u') {
2192
+ const hex = value.slice(i + 1, i + 5);
2193
+ if (hex.length !== 4 || !isHex(hex)) throw new Error(`Invalid Unicode escape in ${token}`);
2194
+ out += decodeCodePoint(hex, token);
2195
+ i += 4;
2196
+ } else if (esc === 'U') {
2197
+ const hex = value.slice(i + 1, i + 9);
2198
+ if (hex.length !== 8 || !isHex(hex)) throw new Error(`Invalid Unicode escape in ${token}`);
2199
+ out += decodeCodePoint(hex, token);
2200
+ i += 8;
2201
+ } else {
2202
+ throw new Error(`Invalid IRI escape \\${esc} in ${token}`);
2203
+ }
2204
+ }
2205
+ return out;
1898
2206
  }
1899
2207
 
1900
- function literal(value, datatype = null, lang = null, langDir = null) {
1901
- return { type: 'literal', value, datatype, lang, langDir };
2208
+ function decodeLiteralEscapes(value, token = 'literal') {
2209
+ let out = '';
2210
+ for (let i = 0; i < value.length; i += 1) {
2211
+ const ch = value[i];
2212
+ if (ch !== '\\') {
2213
+ if (ch === '\n' || ch === '\r') throw new Error(`Raw line break in ${token}`);
2214
+ out += ch;
2215
+ continue;
2216
+ }
2217
+ const esc = value[++i];
2218
+ if (!esc) throw new Error(`Trailing escape in ${token}`);
2219
+ if (esc === 't') out += '\t';
2220
+ else if (esc === 'b') out += '\b';
2221
+ else if (esc === 'n') out += '\n';
2222
+ else if (esc === 'r') out += '\r';
2223
+ else if (esc === 'f') out += '\f';
2224
+ else if (esc === '"') out += '"';
2225
+ else if (esc === "'") out += "'";
2226
+ else if (esc === '\\') out += '\\';
2227
+ else if (esc === 'u') {
2228
+ const hex = value.slice(i + 1, i + 5);
2229
+ if (hex.length !== 4 || !isHex(hex)) throw new Error(`Invalid Unicode escape in ${token}`);
2230
+ out += decodeCodePoint(hex, token);
2231
+ i += 4;
2232
+ } else if (esc === 'U') {
2233
+ const hex = value.slice(i + 1, i + 9);
2234
+ if (hex.length !== 8 || !isHex(hex)) throw new Error(`Invalid Unicode escape in ${token}`);
2235
+ out += decodeCodePoint(hex, token);
2236
+ i += 8;
2237
+ } else {
2238
+ throw new Error(`Invalid escape \\${esc} in ${token}`);
2239
+ }
2240
+ }
2241
+ return out;
1902
2242
  }
1903
2243
 
1904
- function tripleTerm(s, p, o) {
1905
- return { type: 'triple', s, p, o };
2244
+ function stripNqComment(line) {
2245
+ let inString = false;
2246
+ let inIri = false;
2247
+ let escaped = false;
2248
+ for (let i = 0; i < line.length; i += 1) {
2249
+ const ch = line[i];
2250
+ if (escaped) { escaped = false; continue; }
2251
+ if (ch === '\\') { escaped = true; continue; }
2252
+ if (!inIri && ch === '"') { inString = !inString; continue; }
2253
+ if (!inString && ch === '<' && line[i + 1] !== '<') { inIri = true; continue; }
2254
+ if (!inString && inIri && ch === '>') { inIri = false; continue; }
2255
+ if (!inString && !inIri && ch === '#') return line.slice(0, i);
2256
+ }
2257
+ return line;
1906
2258
  }
1907
2259
 
1908
- function isVariable(term) {
1909
- return term && term.type === 'var';
2260
+ function validateAbsoluteIri(value, position) {
2261
+ if (!/^[A-Za-z][A-Za-z0-9+.-]*:/.test(value)) throw new Error(`${position} must be absolute`);
2262
+ return value;
1910
2263
  }
1911
2264
 
1912
- function isIRI(term) {
1913
- return term && term.type === 'iri';
1914
- }
1915
-
1916
- function isBlank(term) {
1917
- return term && term.type === 'blank';
1918
- }
1919
-
1920
- function isLiteral(term) {
1921
- return term && term.type === 'literal';
2265
+ function validateBlankLabel(value) {
2266
+ // RDF blank node labels follow PN_CHARS-style rules. This deliberately accepts
2267
+ // Unicode letters and leading underscores; it still rejects empty labels,
2268
+ // labels ending in '.', and doubled dots because those are common false
2269
+ // positives when a compact statement terminator is adjacent to a blank node.
2270
+ if (!value || value.endsWith('.') || value.includes('..')) throw new Error(`Invalid blank node label _: ${value}`);
2271
+ if (!/^[\p{L}\p{N}_](?:[\p{L}\p{N}._\-\u00B7\u0300-\u036F\u203F-\u2040]*[\p{L}\p{N}_\-\u00B7\u0300-\u036F\u203F-\u2040])?$/u.test(value)) {
2272
+ throw new Error(`Invalid blank node label _: ${value}`);
2273
+ }
2274
+ return value;
1922
2275
  }
1923
2276
 
1924
- function isTripleTerm(term) {
1925
- return term && term.type === 'triple';
2277
+ function validateLang(value) {
2278
+ // LANG_DIR uses BCP47-style language tags. Keep this intentionally strict enough
2279
+ // for the W3C syntax tests: each subtag is 1..8 alphanumeric chars, starting alpha.
2280
+ if (!value || value.includes('--') || !/^[A-Za-z]{1,8}(?:-[A-Za-z0-9]{1,8})*$/.test(value)) throw new Error(`Invalid language tag @${value}`);
2281
+ return value;
1926
2282
  }
1927
2283
 
1928
- function termEquals(a, b) {
1929
- return termKey(a) === termKey(b);
1930
- }
2284
+ class LineReader {
2285
+ constructor(line, lineNumber) {
2286
+ this.line = line;
2287
+ this.lineNumber = lineNumber;
2288
+ this.i = 0;
2289
+ }
1931
2290
 
1932
- function literalKeyValue(value) {
1933
- if (typeof value === 'bigint') return `${value.toString()}n`;
1934
- return JSON.stringify(value);
1935
- }
2291
+ eof() { return this.i >= this.line.length; }
2292
+ peek(offset = 0) { return this.line[this.i + offset]; }
2293
+ startsWith(value) { return this.line.startsWith(value, this.i); }
2294
+ skipWs() { while (isWs(this.peek())) this.i += 1; }
1936
2295
 
1937
- function isNumericPrimitive(value) {
1938
- return typeof value === 'number' || typeof value === 'bigint';
1939
- }
2296
+ expect(value) {
2297
+ if (!this.startsWith(value)) throw new Error(`Expected ${value} on line ${this.lineNumber}, got ${this.line.slice(this.i, this.i + 20) || 'end of line'}`);
2298
+ this.i += value.length;
2299
+ }
1940
2300
 
1941
- function compareNumericPrimitives(a, b) {
1942
- if (typeof a === 'bigint' && typeof b === 'bigint') {
1943
- if (a < b) return -1;
1944
- if (a > b) return 1;
1945
- return 0;
2301
+ readIri(position = 'IRI') {
2302
+ this.expect('<');
2303
+ let raw = '';
2304
+ while (!this.eof()) {
2305
+ const ch = this.peek();
2306
+ if (ch === '>') { this.i += 1; return validateAbsoluteIri(decodeIriEscapes(raw, position), position); }
2307
+ raw += ch;
2308
+ this.i += 1;
2309
+ }
2310
+ throw new Error(`Unterminated ${position} on line ${this.lineNumber}`);
2311
+ }
2312
+
2313
+ readBlank() {
2314
+ this.expect('_:');
2315
+ const start = this.i;
2316
+ while (!this.eof()) {
2317
+ const ch = this.peek();
2318
+ if (!/[\p{L}\p{N}._\-\u00B7\u0300-\u036F\u203F-\u2040]/u.test(ch)) break;
2319
+ if (ch === '.') {
2320
+ const next = this.peek(1);
2321
+ if (!next || isWs(next) || next === '<' || next === '_' || next === '"' || next === '#') break;
2322
+ }
2323
+ this.i += 1;
2324
+ }
2325
+ return blank(validateBlankLabel(this.line.slice(start, this.i)));
2326
+ }
2327
+
2328
+ readLiteral() {
2329
+ this.expect('"');
2330
+ let raw = '';
2331
+ let escaped = false;
2332
+ while (!this.eof()) {
2333
+ const ch = this.peek();
2334
+ this.i += 1;
2335
+ if (escaped) { raw += `\\${ch}`; escaped = false; continue; }
2336
+ if (ch === '\\') { escaped = true; continue; }
2337
+ if (ch === '"') {
2338
+ const value = decodeLiteralEscapes(raw, 'literal');
2339
+ let language = null;
2340
+ let datatype = XSD_STRING;
2341
+ if (this.peek() === '@') {
2342
+ this.i += 1;
2343
+ const start = this.i;
2344
+ while (!this.eof() && /[A-Za-z0-9-]/.test(this.peek())) this.i += 1;
2345
+ let rawLang = this.line.slice(start, this.i);
2346
+ if (!rawLang) throw new Error('Invalid language tag: missing');
2347
+ if (rawLang.endsWith('--ltr') || rawLang.endsWith('--rtl')) rawLang = rawLang.slice(0, -5);
2348
+ language = validateLang(rawLang);
2349
+ datatype = null;
2350
+ } else if (this.startsWith('^^')) {
2351
+ this.i += 2;
2352
+ this.skipWs();
2353
+ datatype = this.readIri('datatype IRI');
2354
+ if (datatype === RDF_LANG_STRING || datatype === RDF_DIR_LANG_STRING) {
2355
+ throw new Error(`Datatype ${datatype} requires LANG_DIR syntax, not ^^`);
2356
+ }
2357
+ }
2358
+ // RDF 1.2 base direction suffix, e.g. --ltr / --rtl. The core term model does not preserve it yet;
2359
+ // accepting it is enough for syntax tests and keeps eval comparison conservative for now.
2360
+ if (this.startsWith('--ltr') || this.startsWith('--rtl')) {
2361
+ if (!language) throw new Error('Base direction requires a language tag');
2362
+ this.i += 5;
2363
+ }
2364
+ return literal(value, datatype, language);
2365
+ }
2366
+ raw += ch;
2367
+ }
2368
+ throw new Error(`Unterminated literal on line ${this.lineNumber}`);
1946
2369
  }
1947
- if (typeof a === 'bigint' && typeof b === 'number' && Number.isInteger(b) && Number.isSafeInteger(b)) {
1948
- const bi = BigInt(b);
1949
- if (a < bi) return -1;
1950
- if (a > bi) return 1;
1951
- return 0;
2370
+
2371
+ readTerm(position = 'term') {
2372
+ this.skipWs();
2373
+ if (this.startsWith('<<')) return this.readTripleTerm();
2374
+ if (this.peek() === '<') return iri(this.readIri(position));
2375
+ if (this.startsWith('_:')) return this.readBlank();
2376
+ if (this.peek() === '"') return this.readLiteral();
2377
+ throw new Error(`Expected RDF term for ${position}, got ${this.line.slice(this.i, this.i + 20) || 'end of line'}`);
1952
2378
  }
1953
- if (typeof a === 'number' && typeof b === 'bigint' && Number.isInteger(a) && Number.isSafeInteger(a)) {
1954
- const ai = BigInt(a);
1955
- if (ai < b) return -1;
1956
- if (ai > b) return 1;
1957
- return 0;
2379
+
2380
+ readSubjectOrGraph(position) {
2381
+ const term = this.readTerm(position);
2382
+ if (term.kind === 'literal') throw new Error(`N-Quads ${position} cannot be a literal`);
2383
+ if (term.kind === 'triple' && (position === 'subject' || position === 'graph')) throw new Error(`N-Quads ${position} cannot be a triple term`);
2384
+ return term;
1958
2385
  }
1959
- const diff = Number(a) - Number(b);
1960
- if (diff < 0) return -1;
1961
- if (diff > 0) return 1;
1962
- return 0;
1963
- }
1964
2386
 
1965
- function termKey(term) {
1966
- if (!term) return 'null';
1967
- if (term.type === 'iri') return `I:${term.value}`;
1968
- if (term.type === 'blank') return `B:${term.value}`;
1969
- if (term.type === 'var') return `V:${term.value}`;
1970
- if (term.type === 'literal') return `L:${literalKeyValue(term.value)}^^${term.datatype || ''}@${term.lang || ''}--${term.langDir || ''}`;
1971
- if (term.type === 'triple') return `T:${termKey(term.s)} ${termKey(term.p)} ${termKey(term.o)}`;
1972
- return JSON.stringify(term);
2387
+ readPredicate() {
2388
+ this.skipWs();
2389
+ if (this.peek() !== '<') throw new Error(`N-Quads predicate must be an IRI, got ${this.line.slice(this.i, this.i + 20) || 'end of line'}`);
2390
+ return iri(this.readIri('predicate'));
2391
+ }
2392
+
2393
+ readTripleTerm() {
2394
+ this.expect('<<');
2395
+ this.skipWs();
2396
+ // RDF 1.2 N-Triples/N-Quads triple terms use parenthesized triples: <<( s p o )>>.
2397
+ // The older unparenthesized RDF-star form is a reified-triple syntax form and is not
2398
+ // accepted as a plain subject/object term by the RDF 1.2 syntax manifests.
2399
+ this.expect('(');
2400
+ this.skipWs();
2401
+ const s = this.readSubjectOrGraph('triple-term subject');
2402
+ this.skipWs();
2403
+ const p = this.readPredicate();
2404
+ this.skipWs();
2405
+ const o = this.readTerm('triple-term object');
2406
+ this.skipWs();
2407
+ this.expect(')');
2408
+ this.skipWs();
2409
+ this.expect('>>');
2410
+ return tripleTerm(s, p, o);
2411
+ }
1973
2412
  }
1974
2413
 
1975
- function tripleKey(triple) {
1976
- return `${termKey(triple.s)} ${termKey(triple.p)} ${termKey(triple.o)}`;
2414
+ function parseLine(line, lineNumber, format) {
2415
+ const clean = stripNqComment(line).trim();
2416
+ if (!clean) return null;
2417
+ const r = new LineReader(clean, lineNumber);
2418
+ const s = r.readSubjectOrGraph('subject');
2419
+ r.skipWs();
2420
+ const p = r.readPredicate();
2421
+ r.skipWs();
2422
+ const o = r.readTerm('object');
2423
+ r.skipWs();
2424
+ let g = null;
2425
+ if (r.peek() !== '.') {
2426
+ if (format === 'ntriples') throw new Error(`N-Triples line ${lineNumber} has too many terms before .`);
2427
+ g = r.readSubjectOrGraph('graph');
2428
+ r.skipWs();
2429
+ }
2430
+ if (r.peek() !== '.') throw new Error(`N-Quads line ${lineNumber} must end with .`);
2431
+ r.i += 1;
2432
+ r.skipWs();
2433
+ if (!r.eof()) throw new Error(`Unexpected trailing content on N-Quads line ${lineNumber}: ${clean.slice(r.i)}`);
2434
+ return triple(s, p, o, g);
2435
+ }
2436
+
2437
+ function parseNQuads(source, options = {}) {
2438
+ const facts = [];
2439
+ const prefixes = { ...(options.prefixes || {}) };
2440
+ const format = options.format || (options.profileId === 'ntriples-graph-v0' ? 'ntriples' : 'nquads');
2441
+ const lines = String(source || '').split(/\r\n|\n|\r/);
2442
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
2443
+ const fact = parseLine(lines[lineIndex], lineIndex + 1, format);
2444
+ if (fact) facts.push(fact);
2445
+ }
2446
+ return {
2447
+ profile: options.profileId || (format === 'ntriples' ? 'ntriples-graph-v0' : 'nquads-dataset-v0'),
2448
+ prefixes,
2449
+ base: options.base || '',
2450
+ imports: [],
2451
+ facts,
2452
+ rules: [],
2453
+ queries: [],
2454
+ expectations: [],
2455
+ };
1977
2456
  }
1978
2457
 
1979
- function cloneTerm(term) {
1980
- if (!term) return term;
1981
- if (term.type === 'triple') return tripleTerm(cloneTerm(term.s), cloneTerm(term.p), cloneTerm(term.o));
1982
- return { ...term };
2458
+ function escapeIri(value) {
2459
+ return String(value).replace(/[\\>\u0000-\u0020]/g, (ch) => `\\u${ch.charCodeAt(0).toString(16).padStart(4, '0')}`);
1983
2460
  }
1984
2461
 
1985
- function valueToTerm(value) {
1986
- if (value && typeof value === 'object' && value.type) return value;
1987
- return literal(value, inferDatatype(value));
2462
+ function escapeLiteral(value) {
2463
+ return String(value)
2464
+ .replace(/\\/g, '\\\\')
2465
+ .replace(/"/g, '\\"')
2466
+ .replace(/\n/g, '\\n')
2467
+ .replace(/\r/g, '\\r')
2468
+ .replace(/\t/g, '\\t')
2469
+ .replace(/\u0008/g, '\\b')
2470
+ .replace(/\u000c/g, '\\f');
2471
+ }
2472
+
2473
+ function termToNQuads(term) {
2474
+ if (!term) return '';
2475
+ switch (term.kind) {
2476
+ case 'iri':
2477
+ return `<${escapeIri(term.value)}>`;
2478
+ case 'blank':
2479
+ return `_:${term.value}`;
2480
+ case 'literal': {
2481
+ let out = `"${escapeLiteral(term.value)}"`;
2482
+ if (term.language) out += `@${term.language}`;
2483
+ else if (term.datatype && term.datatype !== XSD_STRING) out += `^^<${escapeIri(term.datatype)}>`;
2484
+ return out;
2485
+ }
2486
+ case 'triple':
2487
+ return `<< ${termToNQuads(term.s)} ${termToNQuads(term.p)} ${termToNQuads(term.o)} >>`;
2488
+ default:
2489
+ throw new Error(`Cannot serialize ${term.kind} as N-Quads`);
2490
+ }
1988
2491
  }
1989
2492
 
1990
- function inferDatatype(value) {
1991
- if (typeof value === 'boolean') return XSD_BOOLEAN;
1992
- if (typeof value === 'bigint') return XSD_INTEGER;
1993
- if (typeof value === 'number' && Number.isInteger(value)) return XSD_INTEGER;
1994
- if (typeof value === 'number') return XSD_DECIMAL;
1995
- if (typeof value === 'string') return XSD_STRING;
1996
- return null;
2493
+ function tripleToNQuads(value) {
2494
+ const terms = [termToNQuads(value.s), termToNQuads(value.p), termToNQuads(value.o)];
2495
+ if (value.graph) terms.push(termToNQuads(value.graph));
2496
+ return `${terms.join(' ')} .`;
1997
2497
  }
1998
2498
 
1999
- function termToPrimitive(term) {
2000
- if (!term) return undefined;
2001
- if (term.type === 'literal') return term.value;
2002
- if (term.type === 'iri') return term.value;
2003
- if (term.type === 'blank') return `_:${term.value}`;
2004
- if (term.type === 'var') return undefined;
2005
- if (term.type === 'triple') return term;
2006
- return term;
2499
+ function triplesToNQuads(triples) {
2500
+ return Array.from(new Set(Array.from(triples || []).map(tripleToNQuads))).sort().join('\n');
2007
2501
  }
2502
+ return { parseNQuads, termToNQuads, tripleToNQuads, triplesToNQuads };
2503
+ })();
2008
2504
 
2009
- function termToString(term) {
2010
- const value = termToPrimitive(term);
2011
- if (value === undefined || value === null) return '';
2012
- if (value && value.type === 'triple') return formatTerm(value);
2013
- return String(value);
2014
- }
2505
+ // ---- Turtle / TriG parser ----
2506
+ const { parseN3 } = (() => {
2015
2507
 
2016
- function booleanValue(value) {
2017
- const primitive = value && value.type ? termToPrimitive(value) : value;
2018
- if (primitive === undefined || primitive === null) return false;
2019
- if (typeof primitive === 'boolean') return primitive;
2020
- if (typeof primitive === 'bigint') return primitive !== 0n;
2021
- if (typeof primitive === 'number') return primitive !== 0 && !Number.isNaN(primitive);
2022
- if (typeof primitive === 'string') return primitive.length > 0 && primitive !== 'false';
2023
- return Boolean(primitive);
2024
- }
2508
+ const DEFAULT_PREFIXES = Object.freeze({
2509
+ rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
2510
+ xsd: 'http://www.w3.org/2001/XMLSchema#',
2511
+ log: 'http://www.w3.org/2000/10/swap/log#',
2512
+ });
2025
2513
 
2026
- function comparePrimitives(a, b) {
2027
- const av = a && a.type ? termToPrimitive(a) : a;
2028
- const bv = b && b.type ? termToPrimitive(b) : b;
2029
- if (isNumericPrimitive(av) && isNumericPrimitive(bv)) return compareNumericPrimitives(av, bv);
2030
- const as = String(av);
2031
- const bs = String(bv);
2032
- if (as < bs) return -1;
2033
- if (as > bs) return 1;
2034
- return 0;
2035
- }
2036
2514
 
2037
- function escapeString(value) {
2038
- return String(value)
2039
- .replace(/\\/g, '\\\\')
2040
- .replace(/\n/g, '\\n')
2041
- .replace(/\r/g, '\\r')
2042
- .replace(/\t/g, '\\t')
2043
- .replace(/"/g, '\\"');
2515
+ function isWs(ch) { return /\s/.test(ch || ''); }
2516
+ function isPunct(ch) { return '{}.;,()[]|'.includes(ch || ''); }
2517
+ function isHex(text) { return /^[0-9A-Fa-f]+$/.test(text); }
2518
+ function isAbsoluteIri(value) { return /^[A-Za-z][A-Za-z0-9+.-]*:/.test(value || ''); }
2519
+ function resolveIriReference(value, base) {
2520
+ if (isAbsoluteIri(value)) return value;
2521
+ if (!base) return value;
2522
+ try {
2523
+ const url = new URL(value, base);
2524
+ let href = url.href;
2525
+ // The RDF IRI-resolution tests expect bare authority references such as //g
2526
+ // to remain http://g, not to gain the URL API's cosmetic trailing slash.
2527
+ if (/^\/\/[^/?#]+$/.test(value) && href.endsWith('/')) href = href.slice(0, -1);
2528
+ if (/^file:\/\/[^/?#]+$/.test(value) && href.endsWith('/')) href = href.slice(0, -1);
2529
+ return href;
2530
+ } catch { return `${base}${value}`; }
2531
+ }
2532
+ function validateBlankLabel(value) {
2533
+ const clean = String(value || '').replace(/^_:/, '');
2534
+ if (!clean || clean.endsWith('.') || clean.includes('..')) throw new Error(`Invalid blank node label _: ${clean}`);
2535
+ // BLANK_NODE_LABEL follows the PN_CHARS family; ':' is only for prefixed names, not blank labels.
2536
+ if (/[\s<>"{}|^`\\:]/u.test(clean)) throw new Error(`Invalid blank node label _: ${clean}`);
2537
+ if (/^[\-.]/u.test(clean)) throw new Error(`Invalid blank node label _: ${clean}`);
2538
+ return clean;
2539
+ }
2540
+ function validateIriReference(value) {
2541
+ if (/[<>\"{}|^`\u0000-\u0020]/.test(value)) throw new Error('Invalid character in IRIREF');
2542
+ return value;
2044
2543
  }
2045
-
2046
- function compactIRI(value, prefixes = {}) {
2047
- if (value === RDF_TYPE) return 'a';
2048
- const entries = Object.entries(prefixes)
2049
- .filter(([, iriPrefix]) => iriPrefix && value.startsWith(iriPrefix))
2050
- .sort((a, b) => b[1].length - a[1].length);
2051
- if (entries.length > 0) {
2052
- const [prefix, iriPrefix] = entries[0];
2053
- const local = value.slice(iriPrefix.length);
2054
- if (/^[A-Za-z_][A-Za-z0-9_\-]*$/.test(local) || /^[A-Za-z0-9_\-]+$/.test(local)) {
2055
- return `${prefix}:${local}`;
2544
+ function validatePrefixedLocal(raw, decoded) {
2545
+ if (!raw) return decoded;
2546
+ if (raw.startsWith('-') || raw.startsWith('\\-') || raw.startsWith('.')) throw new Error(`Invalid prefixed name local ${raw}`);
2547
+ for (let i = 0; i < raw.length; i += 1) {
2548
+ const ch = raw[i];
2549
+ if (ch === '\\') {
2550
+ const esc = raw[i + 1];
2551
+ if (!esc || !'_~.-!$&\'()*+,;=/?#@%'.includes(esc)) throw new Error(`Invalid prefixed name local escape ${raw}`);
2552
+ i += 1;
2553
+ continue;
2056
2554
  }
2555
+ if (ch === '%') {
2556
+ const hex = raw.slice(i + 1, i + 3);
2557
+ if (hex.length !== 2 || !isHex(hex)) throw new Error(`Invalid percent escape in prefixed name local ${raw}`);
2558
+ i += 2;
2559
+ continue;
2560
+ }
2561
+ if (ch === '~' || ch === '^') throw new Error(`Invalid prefixed name local ${raw}`);
2057
2562
  }
2058
- return `<${value}>`;
2563
+ return decoded;
2059
2564
  }
2060
-
2061
- function formatTerm(term, prefixes = {}) {
2062
- if (term.type === 'iri') return compactIRI(term.value, prefixes);
2063
- if (term.type === 'blank') return `_:${term.value}`;
2064
- if (term.type === 'var') return `?${term.value}`;
2065
- if (term.type === 'triple') return `<<(${formatTerm(term.s, prefixes)} ${formatTerm(term.p, prefixes)} ${formatTerm(term.o, prefixes)})>>`;
2066
- if (term.type === 'literal') {
2067
- const v = term.value;
2068
- if (typeof v === 'bigint' && !term.lang && (!term.datatype || term.datatype === XSD_INTEGER)) return String(v);
2069
- if (typeof v === 'number' && Number.isFinite(v) && !term.lang && (!term.datatype || term.datatype === XSD_INTEGER || term.datatype === XSD_DECIMAL || term.datatype === XSD_DOUBLE)) return String(v);
2070
- if (typeof v === 'boolean' && !term.lang && (!term.datatype || term.datatype === XSD_BOOLEAN)) return v ? 'true' : 'false';
2071
- const lexical = `"${escapeString(v)}"`;
2072
- if (term.lang) return `${lexical}@${term.lang}${term.langDir ? `--${term.langDir}` : ''}`;
2073
- if (term.datatype && term.datatype !== XSD_STRING) return `${lexical}^^${compactIRI(term.datatype, prefixes)}`;
2074
- return lexical;
2565
+ function decodePrefixedLocal(raw) {
2566
+ let out = '';
2567
+ for (let i = 0; i < raw.length; i += 1) {
2568
+ const ch = raw[i];
2569
+ if (ch === '\\') { out += raw[i + 1] || ''; i += 1; }
2570
+ else out += ch;
2075
2571
  }
2076
- return String(term.value ?? term);
2572
+ return out;
2077
2573
  }
2078
-
2079
- function formatTriple(triple, prefixes = {}) {
2080
- return `${formatTerm(triple.s, prefixes)} ${formatTerm(triple.p, prefixes)} ${formatTerm(triple.o, prefixes)} .`;
2574
+ function codePoint(hex, label) {
2575
+ const n = Number.parseInt(hex, 16);
2576
+ if (!Number.isFinite(n) || n < 0 || n > 0x10ffff || (n >= 0xd800 && n <= 0xdfff)) throw new Error(`Invalid Unicode escape in ${label}`);
2577
+ return String.fromCodePoint(n);
2578
+ }
2579
+ function decodeEscapes(text, label, iriMode = false) {
2580
+ let out = '';
2581
+ for (let i = 0; i < text.length; i += 1) {
2582
+ const ch = text[i];
2583
+ if (ch !== '\\') { out += ch; continue; }
2584
+ const esc = text[++i];
2585
+ if (!esc) throw new Error(`Trailing escape in ${label}`);
2586
+ if (esc === 'u') {
2587
+ const hex = text.slice(i + 1, i + 5); if (hex.length !== 4 || !isHex(hex)) throw new Error(`Invalid Unicode escape in ${label}`);
2588
+ out += codePoint(hex, label); i += 4;
2589
+ } else if (esc === 'U') {
2590
+ const hex = text.slice(i + 1, i + 9); if (hex.length !== 8 || !isHex(hex)) throw new Error(`Invalid Unicode escape in ${label}`);
2591
+ out += codePoint(hex, label); i += 8;
2592
+ } else if (!iriMode && 'tbnrf"\''.includes(esc)) {
2593
+ out += { t: '\t', b: '\b', n: '\n', r: '\r', f: '\f', '"': '"', "'": "'" }[esc] ?? esc;
2594
+ } else if (!iriMode && esc === '\\') out += '\\';
2595
+ else if (iriMode) throw new Error(`Invalid escape \\${esc} in ${label}`);
2596
+ else throw new Error(`Invalid escape \\${esc} in ${label}`);
2597
+ }
2598
+ return out;
2081
2599
  }
2082
2600
 
2083
- module.exports = {
2084
- RDF_NS,
2085
- RDF_TYPE,
2086
- RDF_FIRST,
2087
- RDF_REST,
2088
- RDF_NIL,
2089
- RDF_REIFIES,
2090
- XSD_STRING,
2091
- XSD_BOOLEAN,
2092
- XSD_INTEGER,
2093
- XSD_DECIMAL,
2094
- XSD_DOUBLE,
2095
- iri,
2096
- variable,
2097
- blankNode,
2098
- literal,
2099
- tripleTerm,
2100
- isVariable,
2101
- isIRI,
2102
- isBlank,
2103
- isLiteral,
2104
- isTripleTerm,
2105
- termEquals,
2106
- termKey,
2107
- tripleKey,
2108
- cloneTerm,
2109
- valueToTerm,
2110
- inferDatatype,
2111
- termToPrimitive,
2112
- termToString,
2113
- booleanValue,
2114
- comparePrimitives,
2115
- compactIRI,
2116
- formatTerm,
2117
- formatTriple,
2118
- };
2601
+ class Tokenizer {
2602
+ constructor(source) { this.source = String(source || ''); this.i = 0; this.tokens = []; }
2603
+ eof() { return this.i >= this.source.length; }
2604
+ peek(offset = 0) { return this.source[this.i + offset]; }
2605
+ startsWith(value) { return this.source.startsWith(value, this.i); }
2606
+ push(type, value, extra = {}) { this.tokens.push({ type, value, ...extra }); }
2607
+ skipComment() { while (!this.eof() && this.peek() !== '\n' && this.peek() !== '\r') this.i += 1; }
2608
+ readIri() {
2609
+ this.i += 1;
2610
+ let raw = '';
2611
+ while (!this.eof()) {
2612
+ const ch = this.peek();
2613
+ if (ch === '>') { this.i += 1; this.push('iri', validateIriReference(decodeEscapes(raw, 'IRI', true))); return; }
2614
+ raw += ch; this.i += 1;
2615
+ }
2616
+ throw new Error('Unterminated IRIREF');
2617
+ }
2618
+ readString() {
2619
+ const quote = this.peek();
2620
+ const long = this.source.startsWith(quote.repeat(3), this.i);
2621
+ this.i += long ? 3 : 1;
2622
+ let raw = '';
2623
+ let escaped = false;
2624
+ while (!this.eof()) {
2625
+ const ch = this.peek();
2626
+ if (!escaped && long && this.source.startsWith(quote.repeat(3), this.i)) { this.i += 3; this.push(long ? 'longString' : 'string', decodeEscapes(raw, 'string'), { long }); return; }
2627
+ if (!escaped && !long && ch === quote) { this.i += 1; this.push('string', decodeEscapes(raw, 'string'), { long: false }); return; }
2628
+ if (!long && (ch === '\n' || ch === '\r')) throw new Error('Raw line break in short string');
2629
+ raw += ch;
2630
+ this.i += 1;
2631
+ escaped = !escaped && ch === '\\';
2632
+ if (ch !== '\\') escaped = false;
2633
+ }
2634
+ throw new Error('Unterminated string');
2635
+ }
2636
+ readBare() {
2637
+ const start = this.i;
2638
+ if (this.startsWith('_:')) {
2639
+ this.i += 2;
2640
+ while (!this.eof()) {
2641
+ const ch = this.peek();
2642
+ // BLANK_NODE_LABEL uses PN_CHARS, including broad Unicode ranges that are
2643
+ // not all JavaScript \p{L}/\p{N}. Tokenize generously up to a real
2644
+ // Turtle delimiter, then validate the label separately. Keep ':' as a
2645
+ // boundary so compact forms such as _:s:p tokenize as blank-node _:s
2646
+ // followed by predicate :p.
2647
+ if (isWs(ch) || '<>\"{}|^`\\;,)[]'.includes(ch) || ch === ':' || ch === '#') break;
2648
+ if (ch === '.') {
2649
+ const next = this.source[this.i + 1];
2650
+ if (!next || isWs(next) || '{};,)[]'.includes(next)) break;
2651
+ }
2652
+ this.i += 1;
2653
+ }
2654
+ this.push('bare', this.source.slice(start, this.i));
2655
+ return;
2656
+ }
2657
+ while (!this.eof()) {
2658
+ const ch = this.peek();
2659
+ if (isWs(ch)) break;
2660
+ if (ch === '\\' && this.source[this.i + 1]) { this.i += 2; continue; }
2661
+ if (ch === '<' || ch === '>' || ch === '"' || ch === "'") break;
2662
+ if (ch === '.') {
2663
+ const next = this.source[this.i + 1];
2664
+ if (!next || isWs(next) || '{};,)[]'.includes(next)) break;
2665
+ } else if (isPunct(ch)) break;
2666
+ if (ch === '#') break;
2667
+ if (ch === '^' && this.peek(1) === '^') break;
2668
+ if (ch === '=' && this.peek(1) === '>') break;
2669
+ this.i += 1;
2670
+ }
2671
+ this.push('bare', this.source.slice(start, this.i));
2672
+ }
2673
+ tokenize() {
2674
+ while (!this.eof()) {
2675
+ const ch = this.peek();
2676
+ if (isWs(ch)) { this.i += 1; continue; }
2677
+ if (ch === '#') { this.skipComment(); continue; }
2678
+ if (this.startsWith('@prefix') && (isWs(this.source[this.i + 7]) || this.source[this.i + 7] === ':')) { this.push('bare', '@prefix'); this.i += 7; continue; }
2679
+ if (this.startsWith('@base') && isWs(this.source[this.i + 5])) { this.push('bare', '@base'); this.i += 5; continue; }
2680
+ if (this.startsWith('@version') && isWs(this.source[this.i + 8])) { this.push('bare', '@version'); this.i += 8; continue; }
2681
+ if (this.startsWith('=>')) { this.push('=>', '=>'); this.i += 2; continue; }
2682
+ if (this.startsWith('^^')) { this.push('^^', '^^'); this.i += 2; continue; }
2683
+ if (this.startsWith('<<')) { this.push('<<', '<<'); this.i += 2; continue; }
2684
+ if (this.startsWith('>>')) { this.push('>>', '>>'); this.i += 2; continue; }
2685
+ if (ch === '<') { this.readIri(); continue; }
2686
+ if (ch === '"' || ch === "'") { this.readString(); continue; }
2687
+ if (ch === '.' && /[0-9]/.test(this.peek(1) || '')) { this.readBare(); continue; }
2688
+ if (ch === '~') { this.readBare(); continue; }
2689
+ if (isPunct(ch)) { this.push(ch, ch); this.i += 1; continue; }
2690
+ this.readBare();
2691
+ }
2692
+ return this.tokens;
2693
+ }
2694
+ }
2119
2695
 
2120
- },
2121
- "src/assignments.js": function (require, module, exports) {
2122
- 'use strict';
2696
+ function parseN3(source, options = {}) {
2697
+ const tokens = new Tokenizer(source).tokenize();
2698
+ let i = 0;
2699
+ let base = options.base || '';
2700
+ const prefixes = { ...DEFAULT_PREFIXES, ...(options.prefixes || {}) };
2701
+ const facts = [];
2702
+ const rules = [];
2703
+ let bnodeCounter = 0;
2704
+ const bnodes = new Map();
2705
+ const syntaxProfile = String(options.profile || options.profileId || '').toLowerCase();
2706
+ const rdf12Surface = syntaxProfile === 'turtle' || syntaxProfile === 'trig';
2707
+ const implicitStatementNodes = new Set();
2708
+
2709
+ function freshBlank() { bnodeCounter += 1; return blank(`b${bnodeCounter}`); }
2710
+ function peek(offset = 0) { return tokens[i + offset]; }
2711
+ function next() { return tokens[i++]; }
2712
+ function eof() { return i >= tokens.length; }
2713
+ function accept(value) { if (peek()?.value === value || peek()?.type === value) { i += 1; return true; } return false; }
2714
+ function expect(value) { const t = next(); if (!t || (t.value !== value && t.type !== value)) throw new Error(`Expected ${value}, got ${t?.value || 'end of input'}`); return t; }
2715
+ function error(msg) { throw new Error(msg); }
2716
+
2717
+ function parseIriValueFromBare(token) {
2718
+ if (token === 'a') return RDF_TYPE;
2719
+ if (token.startsWith('_:')) error(`Blank node label ${token} cannot be used as IRI`);
2720
+ const split = token.indexOf(':');
2721
+ if (split >= 0) {
2722
+ const prefix = token.slice(0, split);
2723
+ let local = token.slice(split + 1);
2724
+ if (!(prefix in prefixes)) throw new Error(`Unknown prefix ${prefix}:`);
2725
+ // Turtle permits reserved escaped characters in local names.
2726
+ local = validatePrefixedLocal(local, decodePrefixedLocal(local));
2727
+ return prefixes[prefix] + local;
2728
+ }
2729
+ throw new Error(`Expected IRI or prefixed name, got ${token}`);
2730
+ }
2123
2731
 
2124
- // Most SET expressions are deterministic and can safely participate in the
2125
- // ordinary fixpoint loop. Only genuinely fresh generators need run-once
2126
- // evaluation, otherwise a recursive rule such as SET(?x := UUID()) would keep
2127
- // creating new terms forever.
2128
- function assignmentsNeedRunOnce(clauses = [], options = {}) {
2129
- if (options.shacl12Conformance) {
2130
- return clauses.some((clause) => clause.type === 'set' || clause.type === 'bind');
2732
+ function parseIriLike() {
2733
+ const t = next();
2734
+ if (!t) error('Unexpected end of input while reading IRI');
2735
+ if (t.type === 'iri') return resolveIriReference(t.value, base);
2736
+ if (t.type === 'bare') return parseIriValueFromBare(t.value);
2737
+ throw new Error(`Expected IRI or prefixed name, got ${t.value}`);
2131
2738
  }
2132
- const hasSet = clauses.some((clause) => clause.type === 'set');
2133
- const hasNegation = clauses.some((clause) => clause.type === 'not');
2134
- return (hasSet && hasNegation)
2135
- || clauses.some((clause) => (clause.type === 'set' || clause.type === 'bind') && expressionIsVolatile(clause.expr));
2136
- }
2137
2739
 
2138
- function ruleNeedsRunOnce(head = [], body = [], options = {}) {
2139
- return assignmentsNeedRunOnce(body, options) || head.some(tripleHasBlankNode);
2140
- }
2740
+ function parseBlankLabel(label) {
2741
+ const key = validateBlankLabel(label);
2742
+ if (!bnodes.has(key)) bnodes.set(key, blank(key));
2743
+ return bnodes.get(key);
2744
+ }
2141
2745
 
2142
- function tripleHasBlankNode(triple) {
2143
- return termHasBlankNode(triple && triple.s)
2144
- || termHasBlankNode(triple && triple.p)
2145
- || termHasBlankNode(triple && triple.o);
2146
- }
2746
+ function parseNumber(token) {
2747
+ if (/^[+-]?\d+$/.test(token)) return literal(token, XSD_INTEGER);
2748
+ if (/^[+-]?(?:\d+\.\d*|\.\d+)$/.test(token)) return literal(token, XSD_DECIMAL);
2749
+ if (/^[+-]?(?:(?:\d+\.\d*)|(?:\.\d+)|\d+)[eE][+-]?\d+$/.test(token)) return literal(token, XSD_DOUBLE);
2750
+ return null;
2751
+ }
2147
2752
 
2148
- function termHasBlankNode(term) {
2753
+ function parseTerm(out = facts, graph = null, options2 = {}) {
2754
+ const t = peek();
2755
+ if (!t) error('Unexpected end of input while reading Turtle term');
2756
+ if (t.type === '<<') return parseTripleTerm(out, graph, options2);
2757
+ if (t.type === 'iri') { next(); return iri(resolveIriReference(t.value, base)); }
2758
+ if (t.type === 'string' || t.type === 'longString') {
2759
+ if (options2.noLiteral) throw new Error('Literal is not allowed here');
2760
+ return parseLiteral();
2761
+ }
2762
+ if (t.type === '[') {
2763
+ // ANON is a BlankNode and is permitted in rtSubject/ttSubject, even where
2764
+ // blankNodePropertyList is not. Keep [ ... ] rejected in those positions.
2765
+ if (options2.noCompound) {
2766
+ if (peek(1)?.type === ']') { expect('['); expect(']'); return freshBlank(); }
2767
+ throw new Error('Compound blank node expression is not allowed here');
2768
+ }
2769
+ return parseBlankNodePropertyList(out, graph);
2770
+ }
2771
+ if (t.type === '(') {
2772
+ if (options2.noCompound) throw new Error('Collection is not allowed here');
2773
+ return parseCollection(out, graph);
2774
+ }
2775
+ if (t.type === 'bare') {
2776
+ next();
2777
+ if (t.value.startsWith('?')) {
2778
+ if (rdf12Surface) throw new Error(`Variables are not allowed in Turtle/TriG: ${t.value}`);
2779
+ return variable(t.value.slice(1));
2780
+ }
2781
+ if (t.value.startsWith('_:')) return parseBlankLabel(t.value);
2782
+ if (t.value === 'a' && options2.noA) throw new Error('a is only allowed as a predicate');
2783
+ if (t.value === 'true' || t.value === 'false') {
2784
+ if (options2.noLiteral) throw new Error('Literal is not allowed here');
2785
+ return literal(t.value, XSD_BOOLEAN);
2786
+ }
2787
+ const num = parseNumber(t.value);
2788
+ if (num) {
2789
+ if (options2.noLiteral) throw new Error('Literal is not allowed here');
2790
+ return num;
2791
+ }
2792
+ return iri(parseIriValueFromBare(t.value));
2793
+ }
2794
+ throw new Error(`Expected IRI or prefixed name, got ${t.value}`);
2795
+ }
2796
+
2797
+ function parseLiteral() {
2798
+ const t = next(); if (!t || (t.type !== 'string' && t.type !== 'longString')) throw new Error(`Expected string, got ${t?.value || 'end of input'}`);
2799
+ if (peek()?.type === 'bare' && peek().value.startsWith('@')) {
2800
+ let lang = next().value.slice(1);
2801
+ let langDir = null;
2802
+ if (lang.endsWith('--ltr') || lang.endsWith('--rtl')) {
2803
+ langDir = lang.slice(-3);
2804
+ lang = lang.slice(0, -5);
2805
+ }
2806
+ if (!lang || lang.includes('--') || !/^[A-Za-z]+(?:-[A-Za-z0-9]+)*$/.test(lang)) throw new Error(`Invalid language tag @${lang}`);
2807
+ if (peek()?.type === 'bare' && ['--ltr', '--rtl'].includes(peek().value)) langDir = next().value.slice(2);
2808
+ return literal(t.value, null, lang, langDir);
2809
+ }
2810
+ if (accept('^^')) return literal(t.value, parseIriLike());
2811
+ if (peek()?.type === 'bare' && ['--ltr', '--rtl'].includes(peek().value)) throw new Error('Base direction requires a language tag');
2812
+ return literal(t.value, XSD_STRING);
2813
+ }
2814
+
2815
+ function parseReifierToken(out, graph) {
2816
+ const t = peek();
2817
+ if (!t || t.type !== 'bare' || !t.value.startsWith('~')) return null;
2818
+ next();
2819
+ const suffix = t.value.slice(1);
2820
+ if (suffix) {
2821
+ if (suffix.startsWith('_:')) return parseBlankLabel(suffix);
2822
+ return iri(parseIriValueFromBare(suffix));
2823
+ }
2824
+ const n = peek();
2825
+ if (n && (n.type === 'iri' || (n.type === 'bare' && (n.value.startsWith('_:') || n.value.includes(':') || n.value === 'a')))) {
2826
+ const term = parseTerm(out, graph, { noLiteral: true, noCompound: true, noTripleTerm: true });
2827
+ if (term.kind !== 'iri' && term.kind !== 'blank') throw new Error('Reifier must be an IRI or blank node');
2828
+ return term;
2829
+ }
2830
+ return freshBlank();
2831
+ }
2832
+
2833
+ function parseTripleTerm(out, graph, options2 = {}) {
2834
+ expect('<<');
2835
+ const parenthesized = accept('(');
2836
+ if (parenthesized) {
2837
+ if (options2.noTripleTerm) throw new Error('Triple term is not allowed here');
2838
+ const s = parseTerm(out, graph, { noLiteral: true, noCompound: true, noReifiedTriple: true });
2839
+ if (s.kind === 'triple') throw new Error('Triple term subject cannot be a triple term');
2840
+ const p = iri(parseIriLike());
2841
+ const o = parseTerm(out, graph, { noCompound: true, noReifiedTriple: true });
2842
+ if (o.kind !== 'iri' && o.kind !== 'blank' && o.kind !== 'literal' && o.kind !== 'triple') throw new Error('Invalid triple term object');
2843
+ expect(')');
2844
+ expect('>>');
2845
+ return tripleTerm(s, p, o);
2846
+ }
2847
+ if (options2.noReifiedTriple) throw new Error('Reified triple is not allowed here');
2848
+ const s = parseTerm(out, graph, { noLiteral: true, noCompound: true, noTripleTerm: true });
2849
+ if (s.kind !== 'iri' && s.kind !== 'blank') throw new Error('Invalid reified triple subject');
2850
+ const p = iri(parseIriLike());
2851
+ const o = parseTerm(out, graph, { noCompound: true });
2852
+ if (o.kind !== 'iri' && o.kind !== 'blank' && o.kind !== 'literal' && o.kind !== 'triple') throw new Error('Invalid reified triple object');
2853
+ let reifier = null;
2854
+ if (peek()?.type === 'bare' && peek().value.startsWith('~')) reifier = parseReifierToken(out, graph);
2855
+ expect('>>');
2856
+ const node = reifier || freshBlank();
2857
+ out.push(triple(node, iri(RDF_REIFIES), tripleTerm(s, p, o), graph));
2858
+ if (node.kind === 'blank') implicitStatementNodes.add(node.value);
2859
+ return node;
2860
+ }
2861
+
2862
+ function parseCollection(out, graph) {
2863
+ expect('(');
2864
+ if (accept(')')) return iri(RDF_NIL);
2865
+ const head = freshBlank();
2866
+ let current = head;
2867
+ while (true) {
2868
+ const item = parseTerm(out, graph);
2869
+ out.push(triple(current, iri(RDF_FIRST), item, graph));
2870
+ if (accept(')')) {
2871
+ out.push(triple(current, iri(RDF_REST), iri(RDF_NIL), graph));
2872
+ break;
2873
+ }
2874
+ const rest = freshBlank();
2875
+ out.push(triple(current, iri(RDF_REST), rest, graph));
2876
+ current = rest;
2877
+ }
2878
+ return head;
2879
+ }
2880
+
2881
+ function parseBlankNodePropertyList(out, graph) {
2882
+ expect('[');
2883
+ const node = freshBlank();
2884
+ if (accept(']')) return node;
2885
+ parsePredicateObjectList(node, out, graph);
2886
+ expect(']');
2887
+ if (node.kind === 'blank') implicitStatementNodes.add(node.value);
2888
+ return node;
2889
+ }
2890
+
2891
+ function parseAnnotationBlock(reifier, out, graph = null) {
2892
+ expect('{');
2893
+ expect('|');
2894
+ if (peek()?.type === '|') {
2895
+ // Empty annotation blocks are rejected by the RDF 1.2 syntax tests.
2896
+ throw new Error('Empty annotation block');
2897
+ }
2898
+ parsePredicateObjectList(reifier, out, graph);
2899
+ expect('|');
2900
+ expect('}');
2901
+ }
2902
+
2903
+ function ensureReifierForTriple(assertedTriple, out, graph = null) {
2904
+ const reifier = freshBlank();
2905
+ out.push(triple(reifier, iri(RDF_REIFIES), tripleTerm(assertedTriple.s, assertedTriple.p, assertedTriple.o), graph));
2906
+ return reifier;
2907
+ }
2908
+
2909
+ function parseObjectList(subject, predicate, out, graph = null) {
2910
+ while (true) {
2911
+ const object = parseTerm(out, graph, { noA: true });
2912
+ const asserted = triple(subject, predicate, object, graph);
2913
+ out.push(asserted);
2914
+ let pendingReifier = null;
2915
+ while (true) {
2916
+ if (peek()?.type === 'bare' && peek().value.startsWith('~')) {
2917
+ pendingReifier = parseReifierToken(out, graph);
2918
+ out.push(triple(pendingReifier, iri(RDF_REIFIES), tripleTerm(asserted.s, asserted.p, asserted.o), graph));
2919
+ continue;
2920
+ }
2921
+ if (peek()?.type === '{' && peek(1)?.type === '|') {
2922
+ const blockReifier = pendingReifier || ensureReifierForTriple(asserted, out, graph);
2923
+ parseAnnotationBlock(blockReifier, out, graph);
2924
+ pendingReifier = null;
2925
+ continue;
2926
+ }
2927
+ break;
2928
+ }
2929
+ if (!accept(',')) break;
2930
+ }
2931
+ }
2932
+
2933
+ function parsePredicateObjectList(subject, out, graph = null) {
2934
+ while (true) {
2935
+ const predicate = iri(parseIriLike());
2936
+ parseObjectList(subject, predicate, out, graph);
2937
+ if (!accept(';')) break;
2938
+ while (accept(';')) {}
2939
+ if ([']', '.', '}', '|'].includes(peek()?.type)) break;
2940
+ }
2941
+ }
2942
+
2943
+ function parseGraphLabel(out, inheritedGraph = null) {
2944
+ if (peek()?.type === '[' && peek(1)?.type === ']') { expect('['); expect(']'); return freshBlank(); }
2945
+ if (peek()?.type === '(') throw new Error('GRAPH name must be an IRI or blank node');
2946
+ const graph = parseTerm(out, inheritedGraph, { noLiteral: true, noCompound: true, noTripleTerm: true, noReifiedTriple: true, noA: true });
2947
+ if (graph.kind !== 'iri' && graph.kind !== 'blank') throw new Error('GRAPH name must be an IRI or blank node');
2948
+ return graph;
2949
+ }
2950
+
2951
+ function parseGraphBlock(out, inheritedGraph = null) {
2952
+ expect('GRAPH');
2953
+ const graph = parseGraphLabel(out, inheritedGraph);
2954
+ parseFormula(graph, out);
2955
+ }
2956
+
2957
+ function parseTripleStatement(out, graph = null, options3 = {}) {
2958
+ if (String(peek()?.value || '').toUpperCase() === 'GRAPH') {
2959
+ if (syntaxProfile === 'turtle') throw new Error('GRAPH blocks are not Turtle');
2960
+ if (graph) throw new Error('GRAPH blocks cannot be nested inside a graph block');
2961
+ parseGraphBlock(out, graph);
2962
+ if (options3.requireDot) expect('.'); else accept('.');
2963
+ return;
2964
+ }
2965
+ if (peek()?.type === '<<' && peek(1)?.type === '(') throw new Error('Triple term cannot be used as a subject');
2966
+ const subject = parseTerm(out, graph, { noLiteral: true, noA: true });
2967
+ if ((peek()?.type === '.' || peek()?.type === '}' || peek()?.type === undefined) && subject.kind === 'blank' && implicitStatementNodes.has(subject.value)) {
2968
+ if (options3.requireDot) expect('.'); else accept('.');
2969
+ return;
2970
+ }
2971
+ parsePredicateObjectList(subject, out, graph);
2972
+ if (options3.requireDot) expect('.'); else accept('.');
2973
+ }
2974
+
2975
+ function parseFormula(graph = null, target = null) {
2976
+ expect('{');
2977
+ const triples = target || [];
2978
+ while (peek()?.type !== '}') parseTripleStatement(triples, graph);
2979
+ expect('}');
2980
+ return triples;
2981
+ }
2982
+
2983
+ function parseBase() {
2984
+ const directive = next();
2985
+ const iriToken = next();
2986
+ if (iriToken?.type !== 'iri') throw new Error(`Expected base IRI, got ${iriToken?.value}`);
2987
+ base = resolveIriReference(iriToken.value, base);
2988
+ if (String(directive.value || '').startsWith('@')) expect('.');
2989
+ }
2990
+
2991
+ function parsePrefix() {
2992
+ const directive = next();
2993
+ const label = next();
2994
+ if (label?.type !== 'bare' || !label.value.endsWith(':')) throw new Error(`Expected prefix label ending with :, got ${label?.value}`);
2995
+ const prefixLabel = label.value.slice(0, -1);
2996
+ if (prefixLabel.endsWith('.') || prefixLabel.includes('..')) throw new Error(`Invalid prefix label ${prefixLabel}`);
2997
+ const iriToken = next();
2998
+ if (iriToken?.type !== 'iri') throw new Error(`Expected prefix IRI, got ${iriToken?.value}`);
2999
+ prefixes[prefixLabel] = resolveIriReference(iriToken.value, base);
3000
+ if (String(directive.value || '').startsWith('@')) expect('.');
3001
+ }
3002
+
3003
+ function isSimpleGraphLabelStart(t) {
3004
+ return t && (t.type === 'iri' || (t.type === 'bare' && (t.value.startsWith('_:') || t.value.includes(':'))));
3005
+ }
3006
+
3007
+ while (!eof()) {
3008
+ const token = peek();
3009
+ const lowerValue = String(token.value || '').toLowerCase();
3010
+ if (token.value === '@base' || (!String(token.value || '').startsWith('@') && lowerValue === 'base')) parseBase();
3011
+ else if (token.value === '@prefix' || (!String(token.value || '').startsWith('@') && lowerValue === 'prefix')) parsePrefix();
3012
+ else if ((!String(token.value || '').startsWith('@') && String(token.value || '').toUpperCase() === 'VERSION') || token.value === '@version') {
3013
+ const directive = next();
3014
+ const v = next();
3015
+ if (!v || v.type !== 'string') throw new Error('VERSION requires a short quoted string');
3016
+ if (String(directive.value || '').startsWith('@')) expect('.');
3017
+ }
3018
+ else if (token.type === '{') {
3019
+ if (syntaxProfile === 'turtle') throw new Error('Turtle does not allow top-level graph/formula blocks');
3020
+ const body = parseFormula();
3021
+ if (accept('=>')) {
3022
+ const head = parseFormula();
3023
+ accept('.');
3024
+ rules.push(new Rule({ id: `n3${rules.length + 1}`, body, head, profile: 'n3-rules-subset-v0' }));
3025
+ } else {
3026
+ facts.push(...body);
3027
+ accept('.');
3028
+ }
3029
+ } else if (String(token.value || '').toUpperCase() === 'GRAPH') {
3030
+ parseGraphBlock(facts);
3031
+ if (accept('.')) throw new Error('GRAPH block must not be followed by .');
3032
+ } else if (token.type === '[' && peek(1)?.type === ']' && peek(2)?.type === '{') {
3033
+ if (syntaxProfile === 'turtle') throw new Error('Turtle does not allow graph labels');
3034
+ const graph = parseGraphLabel(facts, null);
3035
+ parseFormula(graph, facts);
3036
+ accept('.');
3037
+ } else if (isSimpleGraphLabelStart(token) && peek(1)?.type === '{') {
3038
+ if (syntaxProfile === 'turtle') throw new Error('Turtle does not allow graph labels');
3039
+ const graph = parseGraphLabel(facts, null);
3040
+ parseFormula(graph, facts);
3041
+ accept('.');
3042
+ } else {
3043
+ parseTripleStatement(facts, null, { requireDot: syntaxProfile === 'turtle' });
3044
+ }
3045
+ }
3046
+
3047
+ return { profile: 'n3-rules-subset-v0', prefixes, base, facts, rules };
3048
+ }
3049
+ return { parseN3 };
3050
+ })();
3051
+
3052
+
3053
+ return {
3054
+ parseNQuads,
3055
+ termToNQuads,
3056
+ tripleToNQuads,
3057
+ triplesToNQuads,
3058
+ parseN3,
3059
+ };
3060
+ })();
3061
+
3062
+ Object.assign(module.exports, rdfW3cSyntax);
3063
+
3064
+ },
3065
+ "src/assignments.js": function (require, module, exports) {
3066
+ 'use strict';
3067
+
3068
+ // Most SET expressions are deterministic and can safely participate in the
3069
+ // ordinary fixpoint loop. Only genuinely fresh generators need run-once
3070
+ // evaluation, otherwise a recursive rule such as SET(?x := UUID()) would keep
3071
+ // creating new terms forever.
3072
+ function assignmentsNeedRunOnce(clauses = [], options = {}) {
3073
+ if (options.shacl12Conformance) {
3074
+ return clauses.some((clause) => clause.type === 'set' || clause.type === 'bind');
3075
+ }
3076
+ const hasSet = clauses.some((clause) => clause.type === 'set');
3077
+ const hasNegation = clauses.some((clause) => clause.type === 'not');
3078
+ return (hasSet && hasNegation)
3079
+ || clauses.some((clause) => (clause.type === 'set' || clause.type === 'bind') && expressionIsVolatile(clause.expr));
3080
+ }
3081
+
3082
+ function ruleNeedsRunOnce(head = [], body = [], options = {}) {
3083
+ return assignmentsNeedRunOnce(body, options) || head.some(tripleHasBlankNode);
3084
+ }
3085
+
3086
+ function tripleHasBlankNode(triple) {
3087
+ return termHasBlankNode(triple && triple.s)
3088
+ || termHasBlankNode(triple && triple.p)
3089
+ || termHasBlankNode(triple && triple.o);
3090
+ }
3091
+
3092
+ function termHasBlankNode(term) {
2149
3093
  if (!term) return false;
2150
3094
  if (term.type === 'blank') return true;
2151
3095
  if (term.type === 'triple') return termHasBlankNode(term.s) || termHasBlankNode(term.p) || termHasBlankNode(term.o);
@@ -2184,568 +3128,1117 @@
2184
3128
  module.exports = { assignmentsNeedRunOnce, ruleNeedsRunOnce, expressionIsVolatile, tripleHasBlankNode, termHasBlankNode };
2185
3129
 
2186
3130
  },
2187
- "src/rdfSyntax.js": function (require, module, exports) {
3131
+ "src/term.js": function (require, module, exports) {
2188
3132
  'use strict';
2189
3133
 
2190
- const { tokenize, SyntaxErrorWithLocation } = require('./tokenizer.js');
2191
- const { ruleNeedsRunOnce } = require('./assignments.js');
2192
- const {
2193
- iri,
2194
- variable,
2195
- blankNode,
2196
- literal,
2197
- tripleTerm,
2198
- termKey,
2199
- termEquals,
2200
- formatTerm,
2201
- RDF_TYPE,
2202
- RDF_FIRST,
2203
- RDF_REST,
2204
- RDF_NIL,
2205
- XSD_BOOLEAN,
2206
- XSD_INTEGER,
2207
- XSD_DECIMAL,
2208
- XSD_DOUBLE,
2209
- } = require('./term.js');
2210
-
2211
3134
  const RDF_NS = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
2212
- const SRL_NS = 'http://www.w3.org/ns/shacl-rules#';
2213
- const SHNEX_NS = 'http://www.w3.org/ns/shacl-node-expr#';
2214
- const SPARQL_NS = 'http://www.w3.org/ns/sparql#';
2215
- const OWL_IMPORTS = 'http://www.w3.org/2002/07/owl#imports';
2216
- const SRL_RULE_SET = `${SRL_NS}RuleSet`;
2217
- const SRL_RULE = `${SRL_NS}Rule`;
2218
- const SRL_DATA = `${SRL_NS}data`;
2219
- const SRL_RULES = `${SRL_NS}rules`;
2220
- const SRL_BODY = `${SRL_NS}body`;
2221
- const SRL_HEAD = `${SRL_NS}head`;
2222
- const SRL_SUBJECT = `${SRL_NS}subject`;
2223
- const SRL_PREDICATE = `${SRL_NS}predicate`;
2224
- const SRL_OBJECT = `${SRL_NS}object`;
2225
- const SRL_FILTER = `${SRL_NS}filter`;
2226
- const SRL_EXPR = `${SRL_NS}expr`;
2227
- const SRL_ASSIGN = `${SRL_NS}assign`;
2228
- const SRL_ASSIGN_VAR = `${SRL_NS}assignVar`;
2229
- const SRL_ASSIGN_VALUE = `${SRL_NS}assignValue`;
2230
- const SRL_NOT = `${SRL_NS}not`;
2231
- const SRL_VAR_NAME = `${SRL_NS}varName`;
2232
- const SHNEX_VAR = `${SHNEX_NS}var`;
3135
+ const RDF_TYPE = `${RDF_NS}type`;
3136
+ const RDF_FIRST = `${RDF_NS}first`;
3137
+ const RDF_REST = `${RDF_NS}rest`;
3138
+ const RDF_NIL = `${RDF_NS}nil`;
3139
+ const RDF_REIFIES = `${RDF_NS}reifies`;
3140
+ const XSD_STRING = 'http://www.w3.org/2001/XMLSchema#string';
3141
+ const XSD_BOOLEAN = 'http://www.w3.org/2001/XMLSchema#boolean';
3142
+ const XSD_INTEGER = 'http://www.w3.org/2001/XMLSchema#integer';
3143
+ const XSD_DECIMAL = 'http://www.w3.org/2001/XMLSchema#decimal';
3144
+ const XSD_DOUBLE = 'http://www.w3.org/2001/XMLSchema#double';
2233
3145
 
2234
- class TurtleParser {
2235
- constructor(source, options = {}) {
2236
- this.tokens = Array.isArray(source) ? source : tokenize(source, options.filename || '<rdf>');
2237
- this.pos = 0;
2238
- this.baseIRI = options.baseIRI || null;
2239
- this.bnodeCounter = 0;
2240
- this.prefixes = {
2241
- '': 'http://example/',
2242
- rdf: RDF_NS,
2243
- srl: SRL_NS,
2244
- shnex: SHNEX_NS,
2245
- sparql: SPARQL_NS,
2246
- xsd: 'http://www.w3.org/2001/XMLSchema#',
2247
- owl: 'http://www.w3.org/2002/07/owl#',
2248
- ...options.prefixes,
2249
- };
2250
- this.triples = [];
2251
- this.imports = [];
2252
- }
3146
+ function iri(value) {
3147
+ return { type: 'iri', value: String(value) };
3148
+ }
2253
3149
 
2254
- parseDocument() {
2255
- while (!this.is('eof')) {
2256
- if (this.matchDirective('PREFIX', '@prefix')) this.parsePrefix(this.previous().value.startsWith('@'));
2257
- else if (this.matchDirective('BASE', '@base')) this.parseBase(this.previous().value.startsWith('@'));
2258
- else this.parseTriplesStatement();
2259
- }
2260
- return {
2261
- baseIRI: this.baseIRI,
2262
- prefixes: { ...this.prefixes },
2263
- triples: this.triples,
2264
- imports: this.imports.slice(),
2265
- };
2266
- }
3150
+ function variable(name) {
3151
+ return { type: 'var', value: String(name).replace(/^[?$]/, '') };
3152
+ }
2267
3153
 
2268
- parsePrefix(atStyle = false) {
2269
- const nameToken = this.advance();
2270
- if (nameToken.type !== 'word' || !nameToken.value.endsWith(':')) throw this.error('Expected prefix label ending in :', nameToken);
2271
- const iriToken = this.expectType('iri');
2272
- this.prefixes[nameToken.value.slice(0, -1)] = this.resolveIRI(iriToken.value, iriToken);
2273
- if (atStyle) this.expectValue('.');
2274
- }
3154
+ function blankNode(value) {
3155
+ return { type: 'blank', value: String(value).replace(/^_:/, '') };
3156
+ }
2275
3157
 
2276
- parseBase(atStyle = false) {
2277
- const iriToken = this.expectType('iri');
2278
- this.baseIRI = this.resolveIRI(iriToken.value, iriToken);
2279
- if (atStyle) this.expectValue('.');
2280
- }
3158
+ function literal(value, datatype = null, lang = null, langDir = null) {
3159
+ return { type: 'literal', value, datatype, lang, langDir };
3160
+ }
2281
3161
 
2282
- parseTriplesStatement() {
2283
- const subjectNode = this.parseNode();
2284
- this.triples.push(...subjectNode.triples);
2285
- this.triples.push(...this.parsePredicateObjectList(subjectNode.term, ['.']));
2286
- this.expectValue('.');
2287
- }
3162
+ function tripleTerm(s, p, o) {
3163
+ return { type: 'triple', s, p, o };
3164
+ }
2288
3165
 
2289
- parsePredicateObjectList(subject, terminators = [']']) {
2290
- const triples = [];
2291
- while (!terminators.some((value) => this.checkValue(value))) {
2292
- const predicate = this.parseVerb();
2293
- do {
2294
- const objectNode = this.parseNode();
2295
- triples.push(...objectNode.triples);
2296
- triples.push({ s: subject, p: predicate, o: objectNode.term });
2297
- if (predicate.type === 'iri' && predicate.value === OWL_IMPORTS && objectNode.term.type === 'iri') this.imports.push(objectNode.term.value);
2298
- } while (this.matchValue(','));
2299
- if (this.matchValue(';')) {
2300
- while (this.matchValue(';')) { /* tolerate repeated semicolons */ }
2301
- if (terminators.some((value) => this.checkValue(value))) break;
2302
- } else break;
2303
- }
2304
- return triples;
2305
- }
3166
+ function isVariable(term) {
3167
+ return term && term.type === 'var';
3168
+ }
2306
3169
 
2307
- parseNode() {
2308
- if (this.checkValue('[')) return this.parseBlankNodePropertyList();
2309
- if (this.checkValue('(')) return this.parseCollection();
2310
- return { term: this.parseTerm(), triples: [] };
2311
- }
3170
+ function isIRI(term) {
3171
+ return term && term.type === 'iri';
3172
+ }
2312
3173
 
2313
- parseBlankNodePropertyList() {
2314
- this.expectValue('[');
2315
- const node = this.freshBlankNode();
2316
- if (this.matchValue(']')) return { term: node, triples: [] };
2317
- const triples = this.parsePredicateObjectList(node, [']']);
2318
- this.expectValue(']');
2319
- return { term: node, triples };
2320
- }
3174
+ function isBlank(term) {
3175
+ return term && term.type === 'blank';
3176
+ }
2321
3177
 
2322
- parseCollection() {
2323
- this.expectValue('(');
2324
- if (this.matchValue(')')) return { term: iri(RDF_NIL), triples: [] };
2325
- const items = [];
2326
- while (!this.checkValue(')')) items.push(this.parseNode());
2327
- this.expectValue(')');
2328
- const triples = [];
2329
- for (const item of items) triples.push(...item.triples);
2330
- const cells = items.map(() => this.freshBlankNode());
2331
- for (let i = 0; i < items.length; i += 1) {
2332
- triples.push({ s: cells[i], p: iri(RDF_FIRST), o: items[i].term });
2333
- triples.push({ s: cells[i], p: iri(RDF_REST), o: i + 1 < cells.length ? cells[i + 1] : iri(RDF_NIL) });
3178
+ function isLiteral(term) {
3179
+ return term && term.type === 'literal';
3180
+ }
3181
+
3182
+ function isTripleTerm(term) {
3183
+ return term && term.type === 'triple';
3184
+ }
3185
+
3186
+ function termEquals(a, b) {
3187
+ return termKey(a) === termKey(b);
3188
+ }
3189
+
3190
+ function literalKeyValue(value) {
3191
+ if (typeof value === 'bigint') return `${value.toString()}n`;
3192
+ return JSON.stringify(value);
3193
+ }
3194
+
3195
+ function isNumericPrimitive(value) {
3196
+ return typeof value === 'number' || typeof value === 'bigint';
3197
+ }
3198
+
3199
+ function compareNumericPrimitives(a, b) {
3200
+ if (typeof a === 'bigint' && typeof b === 'bigint') {
3201
+ if (a < b) return -1;
3202
+ if (a > b) return 1;
3203
+ return 0;
3204
+ }
3205
+ if (typeof a === 'bigint' && typeof b === 'number' && Number.isInteger(b) && Number.isSafeInteger(b)) {
3206
+ const bi = BigInt(b);
3207
+ if (a < bi) return -1;
3208
+ if (a > bi) return 1;
3209
+ return 0;
3210
+ }
3211
+ if (typeof a === 'number' && typeof b === 'bigint' && Number.isInteger(a) && Number.isSafeInteger(a)) {
3212
+ const ai = BigInt(a);
3213
+ if (ai < b) return -1;
3214
+ if (ai > b) return 1;
3215
+ return 0;
3216
+ }
3217
+ const diff = Number(a) - Number(b);
3218
+ if (diff < 0) return -1;
3219
+ if (diff > 0) return 1;
3220
+ return 0;
3221
+ }
3222
+
3223
+ function termKey(term) {
3224
+ if (!term) return 'null';
3225
+ if (term.type === 'iri') return `I:${term.value}`;
3226
+ if (term.type === 'blank') return `B:${term.value}`;
3227
+ if (term.type === 'var') return `V:${term.value}`;
3228
+ if (term.type === 'literal') return `L:${literalKeyValue(term.value)}^^${term.datatype || ''}@${term.lang || ''}--${term.langDir || ''}`;
3229
+ if (term.type === 'triple') return `T:${termKey(term.s)} ${termKey(term.p)} ${termKey(term.o)}`;
3230
+ return JSON.stringify(term);
3231
+ }
3232
+
3233
+ function tripleKey(triple) {
3234
+ return `${termKey(triple.s)} ${termKey(triple.p)} ${termKey(triple.o)}`;
3235
+ }
3236
+
3237
+ function cloneTerm(term) {
3238
+ if (!term) return term;
3239
+ if (term.type === 'triple') return tripleTerm(cloneTerm(term.s), cloneTerm(term.p), cloneTerm(term.o));
3240
+ return { ...term };
3241
+ }
3242
+
3243
+ function valueToTerm(value) {
3244
+ if (value && typeof value === 'object' && value.type) return value;
3245
+ return literal(value, inferDatatype(value));
3246
+ }
3247
+
3248
+ function inferDatatype(value) {
3249
+ if (typeof value === 'boolean') return XSD_BOOLEAN;
3250
+ if (typeof value === 'bigint') return XSD_INTEGER;
3251
+ if (typeof value === 'number' && Number.isInteger(value)) return XSD_INTEGER;
3252
+ if (typeof value === 'number') return XSD_DECIMAL;
3253
+ if (typeof value === 'string') return XSD_STRING;
3254
+ return null;
3255
+ }
3256
+
3257
+ function termToPrimitive(term) {
3258
+ if (!term) return undefined;
3259
+ if (term.type === 'literal') return term.value;
3260
+ if (term.type === 'iri') return term.value;
3261
+ if (term.type === 'blank') return `_:${term.value}`;
3262
+ if (term.type === 'var') return undefined;
3263
+ if (term.type === 'triple') return term;
3264
+ return term;
3265
+ }
3266
+
3267
+ function termToString(term) {
3268
+ const value = termToPrimitive(term);
3269
+ if (value === undefined || value === null) return '';
3270
+ if (value && value.type === 'triple') return formatTerm(value);
3271
+ return String(value);
3272
+ }
3273
+
3274
+ function booleanValue(value) {
3275
+ const primitive = value && value.type ? termToPrimitive(value) : value;
3276
+ if (primitive === undefined || primitive === null) return false;
3277
+ if (typeof primitive === 'boolean') return primitive;
3278
+ if (typeof primitive === 'bigint') return primitive !== 0n;
3279
+ if (typeof primitive === 'number') return primitive !== 0 && !Number.isNaN(primitive);
3280
+ if (typeof primitive === 'string') return primitive.length > 0 && primitive !== 'false';
3281
+ return Boolean(primitive);
3282
+ }
3283
+
3284
+ function comparePrimitives(a, b) {
3285
+ const av = a && a.type ? termToPrimitive(a) : a;
3286
+ const bv = b && b.type ? termToPrimitive(b) : b;
3287
+ if (isNumericPrimitive(av) && isNumericPrimitive(bv)) return compareNumericPrimitives(av, bv);
3288
+ const as = String(av);
3289
+ const bs = String(bv);
3290
+ if (as < bs) return -1;
3291
+ if (as > bs) return 1;
3292
+ return 0;
3293
+ }
3294
+
3295
+ function escapeString(value) {
3296
+ return String(value)
3297
+ .replace(/\\/g, '\\\\')
3298
+ .replace(/\n/g, '\\n')
3299
+ .replace(/\r/g, '\\r')
3300
+ .replace(/\t/g, '\\t')
3301
+ .replace(/"/g, '\\"');
3302
+ }
3303
+
3304
+ function compactIRI(value, prefixes = {}) {
3305
+ if (value === RDF_TYPE) return 'a';
3306
+ const entries = Object.entries(prefixes)
3307
+ .filter(([, iriPrefix]) => iriPrefix && value.startsWith(iriPrefix))
3308
+ .sort((a, b) => b[1].length - a[1].length);
3309
+ if (entries.length > 0) {
3310
+ const [prefix, iriPrefix] = entries[0];
3311
+ const local = value.slice(iriPrefix.length);
3312
+ if (/^[A-Za-z_][A-Za-z0-9_\-]*$/.test(local) || /^[A-Za-z0-9_\-]+$/.test(local)) {
3313
+ return `${prefix}:${local}`;
3314
+ }
3315
+ }
3316
+ return `<${value}>`;
3317
+ }
3318
+
3319
+ function formatTerm(term, prefixes = {}) {
3320
+ if (term.type === 'iri') return compactIRI(term.value, prefixes);
3321
+ if (term.type === 'blank') return `_:${term.value}`;
3322
+ if (term.type === 'var') return `?${term.value}`;
3323
+ if (term.type === 'triple') return `<<(${formatTerm(term.s, prefixes)} ${formatTerm(term.p, prefixes)} ${formatTerm(term.o, prefixes)})>>`;
3324
+ if (term.type === 'literal') {
3325
+ const v = term.value;
3326
+ if (typeof v === 'bigint' && !term.lang && (!term.datatype || term.datatype === XSD_INTEGER)) return String(v);
3327
+ if (typeof v === 'number' && Number.isFinite(v) && !term.lang && (!term.datatype || term.datatype === XSD_INTEGER || term.datatype === XSD_DECIMAL || term.datatype === XSD_DOUBLE)) return String(v);
3328
+ if (typeof v === 'boolean' && !term.lang && (!term.datatype || term.datatype === XSD_BOOLEAN)) return v ? 'true' : 'false';
3329
+ const lexical = `"${escapeString(v)}"`;
3330
+ if (term.lang) return `${lexical}@${term.lang}${term.langDir ? `--${term.langDir}` : ''}`;
3331
+ if (term.datatype && term.datatype !== XSD_STRING) return `${lexical}^^${compactIRI(term.datatype, prefixes)}`;
3332
+ return lexical;
3333
+ }
3334
+ return String(term.value ?? term);
3335
+ }
3336
+
3337
+ function formatTriple(triple, prefixes = {}) {
3338
+ return `${formatTerm(triple.s, prefixes)} ${formatTerm(triple.p, prefixes)} ${formatTerm(triple.o, prefixes)} .`;
3339
+ }
3340
+
3341
+ module.exports = {
3342
+ RDF_NS,
3343
+ RDF_TYPE,
3344
+ RDF_FIRST,
3345
+ RDF_REST,
3346
+ RDF_NIL,
3347
+ RDF_REIFIES,
3348
+ XSD_STRING,
3349
+ XSD_BOOLEAN,
3350
+ XSD_INTEGER,
3351
+ XSD_DECIMAL,
3352
+ XSD_DOUBLE,
3353
+ iri,
3354
+ variable,
3355
+ blankNode,
3356
+ literal,
3357
+ tripleTerm,
3358
+ isVariable,
3359
+ isIRI,
3360
+ isBlank,
3361
+ isLiteral,
3362
+ isTripleTerm,
3363
+ termEquals,
3364
+ termKey,
3365
+ tripleKey,
3366
+ cloneTerm,
3367
+ valueToTerm,
3368
+ inferDatatype,
3369
+ termToPrimitive,
3370
+ termToString,
3371
+ booleanValue,
3372
+ comparePrimitives,
3373
+ compactIRI,
3374
+ formatTerm,
3375
+ formatTriple,
3376
+ };
3377
+
3378
+ },
3379
+ "src/builtins.js": function (require, module, exports) {
3380
+ 'use strict';
3381
+
3382
+ const {
3383
+ iri,
3384
+ blankNode,
3385
+ literal,
3386
+ tripleTerm,
3387
+ termEquals,
3388
+ termToPrimitive,
3389
+ termToString,
3390
+ booleanValue,
3391
+ comparePrimitives,
3392
+ isIRI,
3393
+ isBlank,
3394
+ isLiteral,
3395
+ isTripleTerm,
3396
+ valueToTerm,
3397
+ inferDatatype,
3398
+ XSD_STRING,
3399
+ RDF_NS,
3400
+ XSD_INTEGER,
3401
+ XSD_DECIMAL,
3402
+ XSD_DOUBLE,
3403
+ } = require('./term.js');
3404
+
3405
+ const XSD_DATETIME = 'http://www.w3.org/2001/XMLSchema#dateTime';
3406
+ const XSD_DAYTIME_DURATION = 'http://www.w3.org/2001/XMLSchema#dayTimeDuration';
3407
+ const RDF_LANGSTRING = `${RDF_NS}langString`;
3408
+ const RDF_DIRLANGSTRING = `${RDF_NS}dirLangString`;
3409
+ const NUMERIC_DATATYPES = new Set([XSD_INTEGER, XSD_DECIMAL, XSD_DOUBLE]);
3410
+
3411
+ // This table is intentionally shaped by the SHACL 1.2 Rules grammar production BuiltInCall.
3412
+ // Keys are the canonical spellings used by the draft; lookup is case-insensitive so examples
3413
+ // may use SPARQL-style uppercase or lowercase spellings while still being checked against the
3414
+ // grammar's finite set of built-ins.
3415
+ const BUILTIN_SIGNATURES = Object.freeze({
3416
+ STR: { min: 1, max: 1 },
3417
+ LANG: { min: 1, max: 1 },
3418
+ LANGMATCHES: { min: 2, max: 2 },
3419
+ LANGDIR: { min: 1, max: 1 },
3420
+ DATATYPE: { min: 1, max: 1 },
3421
+ IRI: { min: 1, max: 1 },
3422
+ URI: { min: 1, max: 1 },
3423
+ BNODE: { min: 0, max: 1 },
3424
+ ABS: { min: 1, max: 1 },
3425
+ CEIL: { min: 1, max: 1 },
3426
+ FLOOR: { min: 1, max: 1 },
3427
+ ROUND: { min: 1, max: 1 },
3428
+ CONCAT: { min: 0, max: Infinity },
3429
+ SUBSTR: { min: 2, max: 3 },
3430
+ STRLEN: { min: 1, max: 1 },
3431
+ REPLACE: { min: 3, max: 4 },
3432
+ UCASE: { min: 1, max: 1 },
3433
+ LCASE: { min: 1, max: 1 },
3434
+ ENCODE_FOR_URI: { min: 1, max: 1 },
3435
+ CONTAINS: { min: 2, max: 2 },
3436
+ STRSTARTS: { min: 2, max: 2 },
3437
+ STRENDS: { min: 2, max: 2 },
3438
+ STRBEFORE: { min: 2, max: 2 },
3439
+ STRAFTER: { min: 2, max: 2 },
3440
+ YEAR: { min: 1, max: 1 },
3441
+ MONTH: { min: 1, max: 1 },
3442
+ DAY: { min: 1, max: 1 },
3443
+ HOURS: { min: 1, max: 1 },
3444
+ MINUTES: { min: 1, max: 1 },
3445
+ SECONDS: { min: 1, max: 1 },
3446
+ TIMEZONE: { min: 1, max: 1 },
3447
+ TZ: { min: 1, max: 1 },
3448
+ NOW: { min: 0, max: 0 },
3449
+ UUID: { min: 0, max: 0 },
3450
+ STRUUID: { min: 0, max: 0 },
3451
+ IF: { min: 3, max: 3, lazy: true },
3452
+ STRLANG: { min: 2, max: 2 },
3453
+ STRLANGDIR: { min: 3, max: 3 },
3454
+ STRDT: { min: 2, max: 2 },
3455
+ sameTerm: { min: 2, max: 2 },
3456
+ isIRI: { min: 1, max: 1 },
3457
+ isURI: { min: 1, max: 1 },
3458
+ isBLANK: { min: 1, max: 1 },
3459
+ isLITERAL: { min: 1, max: 1 },
3460
+ isNUMERIC: { min: 1, max: 1 },
3461
+ hasLANG: { min: 1, max: 1 },
3462
+ hasLANGDIR: { min: 1, max: 1 },
3463
+ REGEX: { min: 2, max: 3 },
3464
+ isTRIPLE: { min: 1, max: 1 },
3465
+ TRIPLE: { min: 3, max: 3 },
3466
+ SUBJECT: { min: 1, max: 1 },
3467
+ PREDICATE: { min: 1, max: 1 },
3468
+ OBJECT: { min: 1, max: 1 },
3469
+ });
3470
+
3471
+ const BUILTIN_BY_LOWER = new Map(Object.keys(BUILTIN_SIGNATURES).map((name) => [name.toLowerCase(), name]));
3472
+
3473
+ function canonicalBuiltinName(name) {
3474
+ return BUILTIN_BY_LOWER.get(String(name).toLowerCase()) || null;
3475
+ }
3476
+
3477
+ function isBuiltinName(name) {
3478
+ return canonicalBuiltinName(name) !== null;
3479
+ }
3480
+
3481
+ function builtinNames() {
3482
+ return Object.keys(BUILTIN_SIGNATURES);
3483
+ }
3484
+
3485
+ function evalExpression(expr, binding, options = {}) {
3486
+ switch (expr.type) {
3487
+ case 'literal':
3488
+ return expr.value;
3489
+ case 'term':
3490
+ return expr.value;
3491
+ case 'var':
3492
+ return binding[expr.name];
3493
+ case 'list':
3494
+ return expr.items.map((item) => evalExpression(item, binding, options));
3495
+ case 'unary': {
3496
+ const value = evalExpression(expr.expr, binding, options);
3497
+ if (expr.op === '!') return !booleanValue(value);
3498
+ if (expr.op === '-') return negateNumeric(termToPrimitive(valueToTermIfNeeded(value)));
3499
+ if (expr.op === '+') return unaryPlusNumeric(termToPrimitive(valueToTermIfNeeded(value)));
3500
+ throw new Error(`Unsupported unary operator ${expr.op}`);
2334
3501
  }
2335
- return { term: cells[0], triples };
3502
+ case 'binary': {
3503
+ const left = evalExpression(expr.left, binding, options);
3504
+ if (expr.op === '&&') return booleanValue(left) && booleanValue(evalExpression(expr.right, binding, options));
3505
+ if (expr.op === '||') return booleanValue(left) || booleanValue(evalExpression(expr.right, binding, options));
3506
+ const right = evalExpression(expr.right, binding, options);
3507
+ return evalBinary(expr.op, left, right);
3508
+ }
3509
+ case 'call':
3510
+ return evalCallExpression(expr, binding, options);
3511
+ default:
3512
+ throw new Error(`Unsupported expression type ${expr.type}`);
2336
3513
  }
3514
+ }
2337
3515
 
2338
- parseVerb() {
2339
- if (this.checkType('word') && this.peek().value === 'a') { this.advance(); return iri(RDF_TYPE); }
2340
- const term = this.parseTerm();
2341
- if (term.type !== 'iri') throw this.error('Expected IRI as Turtle predicate');
2342
- return term;
3516
+ function evalCallExpression(expr, binding, options) {
3517
+ const canonical = canonicalBuiltinName(expr.name);
3518
+ if (canonical === 'IF') {
3519
+ validateArity(canonical, expr.args.length);
3520
+ const condition = evalExpression(expr.args[0], binding, options);
3521
+ return evalExpression(booleanValue(condition) ? expr.args[1] : expr.args[2], binding, options);
2343
3522
  }
3523
+ return callBuiltin(expr.name, expr.args.map((arg) => evalExpression(arg, binding, options)), binding, options);
3524
+ }
2344
3525
 
2345
- parseTerm() {
2346
- const token = this.advance();
2347
- if (token.type === 'operator' && (token.value === '+' || token.value === '-') && this.peek().type === 'number') {
2348
- const numberToken = this.advance();
2349
- return numericLiteral(token.value === '-' ? -numberToken.value : numberToken.value);
2350
- }
2351
- if (token.type === 'iri') return iri(this.resolveIRI(token.value, token));
2352
- if (token.type === 'string') return this.parseLiteralAfterToken(token);
2353
- if (token.type === 'number') return numericLiteral(token.value);
2354
- if (token.value === '<<(') return this.parseTripleTermAfterOpen();
2355
- if (token.type === 'word') {
2356
- const word = token.value.includes(':') || token.value.startsWith('_:') ? this.consumeHyphenatedWord(token.value) : token.value;
2357
- if (word === 'a') return iri(RDF_TYPE);
2358
- if (word === 'true') return literal(true, XSD_BOOLEAN);
2359
- if (word === 'false') return literal(false, XSD_BOOLEAN);
2360
- if (word.startsWith('_:')) return blankNode(word.slice(2));
2361
- if (word.includes(':')) return iri(this.expandPrefixedName(word, token));
2362
- }
2363
- throw this.error(`Expected RDF term, got ${token.value}`, token);
3526
+ function evalBinary(op, left, right) {
3527
+ if (op === '=') return termishEquals(left, right);
3528
+ if (op === '!=') return !termishEquals(left, right);
3529
+ if (op === 'IN' || op === 'NOT IN') {
3530
+ const list = Array.isArray(right) ? right : [];
3531
+ const found = list.some((item) => termishEquals(left, item));
3532
+ return op === 'IN' ? found : !found;
3533
+ }
3534
+ if (['<', '<=', '>', '>='].includes(op)) {
3535
+ const cmp = comparePrimitives(left, right);
3536
+ if (op === '<') return cmp < 0;
3537
+ if (op === '<=') return cmp <= 0;
3538
+ if (op === '>') return cmp > 0;
3539
+ if (op === '>=') return cmp >= 0;
3540
+ }
3541
+ const lp = termToPrimitive(valueToTermIfNeeded(left));
3542
+ const rp = termToPrimitive(valueToTermIfNeeded(right));
3543
+ if (op === '+') {
3544
+ if (isNumericPrimitive(lp) && isNumericPrimitive(rp)) return addNumeric(lp, rp);
3545
+ return String(lp) + String(rp);
2364
3546
  }
3547
+ if (op === '-') return subtractNumeric(lp, rp);
3548
+ if (op === '*') return multiplyNumeric(lp, rp);
3549
+ if (op === '/') return Number(lp) / Number(rp);
3550
+ throw new Error(`Unsupported binary operator ${op}`);
3551
+ }
2365
3552
 
2366
- parseTripleTermAfterOpen() {
2367
- const s = this.parseTerm();
2368
- const p = this.parseVerb();
2369
- const o = this.parseTerm();
2370
- this.expectValue(')>>');
2371
- return tripleTerm(s, p, o);
3553
+
3554
+ function isNumericPrimitive(value) {
3555
+ return typeof value === 'number' || typeof value === 'bigint';
3556
+ }
3557
+
3558
+ function isIntegerPrimitive(value) {
3559
+ return typeof value === 'bigint' || (typeof value === 'number' && Number.isInteger(value));
3560
+ }
3561
+
3562
+ function toBigIntInteger(value) {
3563
+ if (typeof value === 'bigint') return value;
3564
+ if (typeof value === 'number' && Number.isInteger(value) && Number.isSafeInteger(value)) return BigInt(value);
3565
+ throw new Error(`Cannot convert ${String(value)} to BigInt safely`);
3566
+ }
3567
+
3568
+ function fromIntegerResult(value) {
3569
+ if (value <= BigInt(Number.MAX_SAFE_INTEGER) && value >= BigInt(Number.MIN_SAFE_INTEGER)) return Number(value);
3570
+ return value;
3571
+ }
3572
+
3573
+ function addNumeric(left, right) {
3574
+ if (isIntegerPrimitive(left) && isIntegerPrimitive(right)) {
3575
+ if (typeof left === 'bigint' || typeof right === 'bigint') return fromIntegerResult(toBigIntInteger(left) + toBigIntInteger(right));
3576
+ const result = left + right;
3577
+ if (Number.isSafeInteger(result)) return result;
3578
+ return toBigIntInteger(left) + toBigIntInteger(right);
2372
3579
  }
3580
+ return Number(left) + Number(right);
3581
+ }
2373
3582
 
2374
- parseLiteralAfterToken(token) {
2375
- if (this.matchValue('^^')) {
2376
- const datatype = this.parseDatatypeIRI();
2377
- return literal(coerceLexicalLiteral(token.value, datatype), datatype, null);
2378
- }
2379
- if (this.checkType('word') && /^@[A-Za-z]+(?:-[A-Za-z0-9]+)*(?:--[A-Za-z]+)?$/.test(this.peek().value)) {
2380
- const tag = this.advance().value.slice(1).toLowerCase();
2381
- const [lang, langDir = null] = tag.split('--');
2382
- return literal(token.value, null, lang, langDir);
3583
+ function subtractNumeric(left, right) {
3584
+ if (isIntegerPrimitive(left) && isIntegerPrimitive(right)) {
3585
+ if (typeof left === 'bigint' || typeof right === 'bigint') return fromIntegerResult(toBigIntInteger(left) - toBigIntInteger(right));
3586
+ const result = left - right;
3587
+ if (Number.isSafeInteger(result)) return result;
3588
+ return toBigIntInteger(left) - toBigIntInteger(right);
3589
+ }
3590
+ return Number(left) - Number(right);
3591
+ }
3592
+
3593
+ function multiplyNumeric(left, right) {
3594
+ if (isIntegerPrimitive(left) && isIntegerPrimitive(right)) {
3595
+ if (typeof left === 'bigint' || typeof right === 'bigint') return fromIntegerResult(toBigIntInteger(left) * toBigIntInteger(right));
3596
+ const result = left * right;
3597
+ if (Number.isSafeInteger(result)) return result;
3598
+ return toBigIntInteger(left) * toBigIntInteger(right);
3599
+ }
3600
+ return Number(left) * Number(right);
3601
+ }
3602
+
3603
+ function negateNumeric(value) {
3604
+ if (typeof value === 'bigint') return -value;
3605
+ return -Number(value);
3606
+ }
3607
+
3608
+ function unaryPlusNumeric(value) {
3609
+ if (typeof value === 'bigint') return value;
3610
+ return Number(value);
3611
+ }
3612
+
3613
+ function valueToTermIfNeeded(value) {
3614
+ return value && value.type ? value : literal(value, inferDatatype(value));
3615
+ }
3616
+
3617
+ function termishEquals(left, right) {
3618
+ if (left && left.type && right && right.type) return termEquals(left, right);
3619
+ const lp = left && left.type ? termToPrimitive(left) : left;
3620
+ const rp = right && right.type ? termToPrimitive(right) : right;
3621
+ return lp === rp;
3622
+ }
3623
+
3624
+ function callBuiltin(name, args, binding = {}, options = {}) {
3625
+ const injected = options.builtins && (options.builtins[name] || options.builtins[String(name).toLowerCase()]);
3626
+ if (injected) return injected(args, { binding, iri, blankNode, literal, tripleTerm, termToString, booleanValue, termToPrimitive });
3627
+
3628
+ if (localName(name).toLowerCase() === 'sudoku') {
3629
+ if (args.length !== 1) throw new Error(`SUDOKU expects 1 argument, got ${args.length}`);
3630
+ return solveSudoku(termToString(args[0]));
3631
+ }
3632
+
3633
+ const canonical = canonicalBuiltinName(name);
3634
+ if (!canonical) throw new Error(`Unknown builtin ${name}`);
3635
+ validateArity(canonical, args.length);
3636
+ const key = canonical.toLowerCase();
3637
+
3638
+ if (key === 'str') return termToString(args[0]);
3639
+ if (key === 'iri' || key === 'uri') return makeIRI(termToString(args[0]), options);
3640
+ if (key === 'bnode') return makeBlankNode(args, options);
3641
+ if (key === 'concat') return args.map(termToString).join('');
3642
+ if (key === 'lcase') return termToString(args[0]).toLowerCase();
3643
+ if (key === 'ucase') return termToString(args[0]).toUpperCase();
3644
+ if (key === 'contains') return termToString(args[0]).includes(termToString(args[1]));
3645
+ if (key === 'strstarts') return termToString(args[0]).startsWith(termToString(args[1]));
3646
+ if (key === 'strends') return termToString(args[0]).endsWith(termToString(args[1]));
3647
+ if (key === 'strbefore') {
3648
+ const s = termToString(args[0]);
3649
+ const needle = termToString(args[1]);
3650
+ const index = s.indexOf(needle);
3651
+ return index < 0 ? '' : s.slice(0, index);
3652
+ }
3653
+ if (key === 'strafter') {
3654
+ const s = termToString(args[0]);
3655
+ const needle = termToString(args[1]);
3656
+ const index = s.indexOf(needle);
3657
+ return index < 0 ? '' : s.slice(index + needle.length);
3658
+ }
3659
+ if (key === 'encode_for_uri') return encodeURIComponent(termToString(args[0]));
3660
+ if (key === 'regex') return regex(args);
3661
+ if (key === 'replace') return replace(args);
3662
+ if (key === 'substr') return substr(args);
3663
+ if (key === 'sameterm') return termishEquals(args[0], args[1]);
3664
+ if (key === 'isiri' || key === 'isuri') return isIRI(args[0]);
3665
+ if (key === 'isblank') return isBlank(args[0]);
3666
+ if (key === 'isliteral') return isLiteral(args[0]);
3667
+ if (key === 'istriple') return isTripleTerm(args[0]);
3668
+ if (key === 'isnumeric') return isNumericValue(args[0]);
3669
+ if (key === 'datatype') return datatypeOf(args[0]);
3670
+ if (key === 'lang') return args[0] && args[0].type === 'literal' ? (args[0].lang || '') : '';
3671
+ if (key === 'langmatches') return langMatches(termToString(args[0]), termToString(args[1]));
3672
+ if (key === 'haslang') return !!(args[0] && args[0].type === 'literal' && args[0].lang);
3673
+ if (key === 'langdir') return args[0] && args[0].type === 'literal' ? (args[0].langDir || '') : '';
3674
+ if (key === 'haslangdir') return !!(args[0] && args[0].type === 'literal' && args[0].langDir);
3675
+ if (key === 'strlen') return termToString(args[0]).length;
3676
+ if (key === 'abs') return Math.abs(Number(termToPrimitive(valueToTermIfNeeded(args[0]))));
3677
+ if (key === 'floor') return Math.floor(Number(termToPrimitive(valueToTermIfNeeded(args[0]))));
3678
+ if (key === 'ceil') return Math.ceil(Number(termToPrimitive(valueToTermIfNeeded(args[0]))));
3679
+ if (key === 'round') return Math.round(Number(termToPrimitive(valueToTermIfNeeded(args[0]))));
3680
+ if (key === 'if') return booleanValue(args[0]) ? args[1] : args[2];
3681
+ if (key === 'strdt') return literal(termToString(args[0]), termToString(args[1]));
3682
+ if (key === 'strlang') return literal(termToString(args[0]), null, termToString(args[1]).toLowerCase());
3683
+ if (key === 'strlangdir') return literal(termToString(args[0]), null, termToString(args[1]).toLowerCase(), termToString(args[2]).toLowerCase());
3684
+ if (key === 'triple') return tripleTerm(valueToTermIfNeeded(args[0]), valueToTermIfNeeded(args[1]), valueToTermIfNeeded(args[2]));
3685
+ if (key === 'subject') return isTripleTerm(args[0]) ? args[0].s : null;
3686
+ if (key === 'predicate') return isTripleTerm(args[0]) ? args[0].p : null;
3687
+ if (key === 'object') return isTripleTerm(args[0]) ? args[0].o : null;
3688
+ if (key === 'year') return datePart(args[0], 'year');
3689
+ if (key === 'month') return datePart(args[0], 'month');
3690
+ if (key === 'day') return datePart(args[0], 'day');
3691
+ if (key === 'hours') return datePart(args[0], 'hours');
3692
+ if (key === 'minutes') return datePart(args[0], 'minutes');
3693
+ if (key === 'seconds') return datePart(args[0], 'seconds');
3694
+ if (key === 'timezone') return timezoneDuration(args[0]);
3695
+ if (key === 'tz') return timezoneLexical(args[0]);
3696
+ if (key === 'now') return literal((options.now || new Date()).toISOString(), XSD_DATETIME);
3697
+ if (key === 'uuid') return iri(`urn:uuid:${freshUuid(options)}`);
3698
+ if (key === 'struuid') return freshUuid(options);
3699
+ throw new Error(`Unimplemented builtin ${name}`);
3700
+ }
3701
+
3702
+
3703
+ function localName(name) {
3704
+ const text = String(name || '');
3705
+ const hash = text.lastIndexOf('#');
3706
+ const slash = text.lastIndexOf('/');
3707
+ const colon = text.lastIndexOf(':');
3708
+ const index = Math.max(hash, slash, colon);
3709
+ return index >= 0 ? text.slice(index + 1) : text;
3710
+ }
3711
+
3712
+ function solveSudoku(puzzle) {
3713
+ const text = String(puzzle || '').trim();
3714
+ if (!/^[0-9.]{81}$/.test(text)) throw new Error('SUDOKU expects an 81-character puzzle string containing digits or dots');
3715
+ const cells = Array.from(text, (ch) => (ch === '.' ? 0 : Number(ch)));
3716
+ const peers = sudokuPeers();
3717
+
3718
+ for (let i = 0; i < 81; i += 1) {
3719
+ const value = cells[i];
3720
+ if (value === 0) continue;
3721
+ for (const peer of peers[i]) {
3722
+ if (cells[peer] === value) throw new Error('SUDOKU puzzle has conflicting givens');
2383
3723
  }
2384
- return literal(token.value);
2385
3724
  }
2386
3725
 
2387
- parseDatatypeIRI() {
2388
- const token = this.advance();
2389
- if (token.type === 'iri') return this.resolveIRI(token.value, token);
2390
- if (token.type === 'word' && token.value.includes(':')) return this.expandPrefixedName(token.value, token);
2391
- throw this.error(`Expected datatype IRI, got ${token.value}`, token);
2392
- }
3726
+ const solved = solveSudokuCells(cells, peers);
3727
+ if (!solved) return '';
3728
+ return solved.join('');
3729
+ }
2393
3730
 
2394
- freshBlankNode() {
2395
- this.bnodeCounter += 1;
2396
- return blankNode(`rdf${this.bnodeCounter}`);
2397
- }
3731
+ function solveSudokuCells(cells, peers) {
3732
+ let bestIndex = -1;
3733
+ let bestCandidates = null;
2398
3734
 
2399
- consumeHyphenatedWord(value) {
2400
- let out = value;
2401
- while (this.checkValue('-') && (this.peekN(1).type === 'word' || this.peekN(1).type === 'number')) {
2402
- this.advance();
2403
- out += `-${this.advance().value}`;
3735
+ for (let i = 0; i < 81; i += 1) {
3736
+ if (cells[i] !== 0) continue;
3737
+ const candidates = sudokuCandidates(cells, peers[i]);
3738
+ if (candidates.length === 0) return null;
3739
+ if (!bestCandidates || candidates.length < bestCandidates.length) {
3740
+ bestIndex = i;
3741
+ bestCandidates = candidates;
3742
+ if (candidates.length === 1) break;
2404
3743
  }
2405
- return out;
2406
3744
  }
2407
3745
 
2408
- expandPrefixedName(value, token) {
2409
- const colon = value.indexOf(':');
2410
- if (colon < 0) throw this.error(`Expected prefixed name, got ${value}`, token);
2411
- const prefix = value.slice(0, colon);
2412
- const local = value.slice(colon + 1);
2413
- if (!Object.hasOwn(this.prefixes, prefix)) throw this.error(`Unknown prefix ${prefix}:`, token);
2414
- return this.prefixes[prefix] + local;
2415
- }
3746
+ if (bestIndex < 0) return cells;
2416
3747
 
2417
- resolveIRI(value) {
2418
- if (!this.baseIRI || /^[A-Za-z][A-Za-z0-9+.-]*:/.test(value)) return value;
2419
- try { return new URL(value, this.baseIRI).href; } catch (_) { return value; }
3748
+ for (const value of bestCandidates) {
3749
+ const next = cells.slice();
3750
+ next[bestIndex] = value;
3751
+ const solved = solveSudokuCells(next, peers);
3752
+ if (solved) return solved;
2420
3753
  }
3754
+ return null;
3755
+ }
2421
3756
 
2422
- matchDirective(...names) {
2423
- if (this.checkType('word')) {
2424
- const value = this.peek().value;
2425
- if (names.some((name) => value.toUpperCase() === name.toUpperCase())) { this.advance(); return true; }
3757
+ function sudokuCandidates(cells, peers) {
3758
+ const used = new Set();
3759
+ for (const peer of peers) if (cells[peer] !== 0) used.add(cells[peer]);
3760
+ const out = [];
3761
+ for (let value = 1; value <= 9; value += 1) if (!used.has(value)) out.push(value);
3762
+ return out;
3763
+ }
3764
+
3765
+ let SUDOKU_PEERS = null;
3766
+ function sudokuPeers() {
3767
+ if (SUDOKU_PEERS) return SUDOKU_PEERS;
3768
+ SUDOKU_PEERS = Array.from({ length: 81 }, (_, index) => {
3769
+ const row = Math.floor(index / 9);
3770
+ const col = index % 9;
3771
+ const boxRow = Math.floor(row / 3) * 3;
3772
+ const boxCol = Math.floor(col / 3) * 3;
3773
+ const peers = new Set();
3774
+ for (let c = 0; c < 9; c += 1) peers.add(row * 9 + c);
3775
+ for (let r = 0; r < 9; r += 1) peers.add(r * 9 + col);
3776
+ for (let r = boxRow; r < boxRow + 3; r += 1) {
3777
+ for (let c = boxCol; c < boxCol + 3; c += 1) peers.add(r * 9 + c);
2426
3778
  }
2427
- return false;
2428
- }
3779
+ peers.delete(index);
3780
+ return Array.from(peers);
3781
+ });
3782
+ return SUDOKU_PEERS;
3783
+ }
2429
3784
 
2430
- previous() { return this.tokens[this.pos - 1]; }
2431
- peek() { return this.tokens[this.pos]; }
2432
- peekN(n) { return this.tokens[this.pos + n]; }
2433
- is(type) { return this.peek().type === type; }
2434
- checkType(type) { return this.peek().type === type; }
2435
- checkValue(value) { return this.peek().value === value; }
2436
- matchValue(value) { if (this.checkValue(value)) { this.advance(); return true; } return false; }
2437
- advance() { return this.tokens[this.pos++]; }
2438
- expectType(type) { const token = this.advance(); if (token.type !== type) throw this.error(`Expected ${type}, got ${token.value}`, token); return token; }
2439
- expectValue(value) { const token = this.advance(); if (token.value !== value) throw this.error(`Expected ${value}, got ${token.value}`, token); return token; }
2440
- error(message, token = this.peek()) { return new SyntaxErrorWithLocation(message, token); }
3785
+ function validateArity(canonical, actual) {
3786
+ const sig = BUILTIN_SIGNATURES[canonical];
3787
+ if (!sig) throw new Error(`Unknown builtin ${canonical}`);
3788
+ const tooFew = actual < sig.min;
3789
+ const tooMany = actual > sig.max;
3790
+ if (tooFew || tooMany) {
3791
+ const expected = sig.min === sig.max ? `${sig.min}` : `${sig.min}${sig.max === Infinity ? '+' : `..${sig.max}`}`;
3792
+ throw new Error(`${canonical} expects ${expected} argument${expected === '1' ? '' : 's'}, got ${actual}`);
3793
+ }
2441
3794
  }
2442
3795
 
2443
- function parseRdfDocument(source, options = {}) {
2444
- return new TurtleParser(source, options).parseDocument();
3796
+ function makeIRI(value, options) {
3797
+ if (options.baseIRI && !/^[A-Za-z][A-Za-z0-9+.-]*:/.test(value)) {
3798
+ try { return iri(new URL(value, options.baseIRI).href); } catch (_) { /* fall through */ }
3799
+ }
3800
+ return iri(value);
2445
3801
  }
2446
3802
 
2447
- function parseRdfSyntax(source, options = {}) {
2448
- const document = parseRdfDocument(source, options);
2449
- return rdfDocumentToProgram(document, options);
3803
+ function makeBlankNode(args, options) {
3804
+ if (args.length === 0) return blankNode(freshId(options));
3805
+ const label = termToString(args[0]);
3806
+ if (!options.__bnodeLabels) options.__bnodeLabels = new Map();
3807
+ if (!options.__bnodeLabels.has(label)) options.__bnodeLabels.set(label, label || freshId(options));
3808
+ return blankNode(options.__bnodeLabels.get(label));
2450
3809
  }
2451
3810
 
2452
- function rdfDocumentToProgram(document, options = {}) {
2453
- const graph = new RdfGraph(document.triples, document.prefixes);
2454
- const ruleSetNodes = chooseRuleSets(graph, options.ruleSet);
2455
- if (ruleSetNodes.length === 0) throw new Error('No srl:RuleSet found in RDF Rules syntax input');
3811
+ function regex(args) {
3812
+ const flags = regexFlags(termToString(args[2] || ''));
3813
+ return new RegExp(termToString(args[1]), flags).test(termToString(args[0]));
3814
+ }
2456
3815
 
2457
- const program = {
2458
- baseIRI: document.baseIRI || null,
2459
- version: null,
2460
- imports: options.rdfImportsAsImports ? document.imports.slice() : [],
2461
- prefixes: { ...document.prefixes },
2462
- data: [],
2463
- rules: [],
2464
- rdfSyntax: true,
2465
- options: { shacl12Conformance: !!options.shacl12Conformance },
2466
- ruleSets: ruleSetNodes.map((term) => formatTerm(term, document.prefixes)),
2467
- };
3816
+ function replace(args) {
3817
+ const flags = regexFlags(termToString(args[3] || ''));
3818
+ const effectiveFlags = flags.includes('g') ? flags : `${flags}g`;
3819
+ return termToString(args[0]).replace(new RegExp(termToString(args[1]), effectiveFlags), termToString(args[2]));
3820
+ }
2468
3821
 
2469
- for (const ruleSet of ruleSetNodes) {
2470
- for (const dataList of graph.objects(ruleSet, SRL_DATA)) {
2471
- for (const item of graph.list(dataList)) program.data.push(toDataTriple(item, graph));
2472
- }
2473
- for (const rulesList of graph.objects(ruleSet, SRL_RULES)) {
2474
- for (const ruleNode of graph.list(rulesList)) program.rules.push(toRule(ruleNode, graph, options));
2475
- }
3822
+ function regexFlags(flags) {
3823
+ let out = '';
3824
+ for (const ch of String(flags)) {
3825
+ // JavaScript RegExp has no direct SPARQL/xpath "x" free-spacing flag, so ignore it.
3826
+ if (ch === 'x') continue;
3827
+ if ('imsuyg'.includes(ch) && !out.includes(ch)) out += ch;
2476
3828
  }
2477
- return program;
3829
+ return out;
2478
3830
  }
2479
3831
 
2480
- function chooseRuleSets(graph, selected) {
2481
- if (selected) {
2482
- const term = graph.parseReference(selected);
2483
- return [term];
2484
- }
2485
- const typed = graph.subjects(RDF_TYPE, iri(SRL_RULE_SET));
2486
- if (typed.length > 0) return uniqueTerms(typed);
2487
- const byData = graph.subjectsWithPredicate(SRL_DATA);
2488
- const byRules = graph.subjectsWithPredicate(SRL_RULES);
2489
- return uniqueTerms([...byData, ...byRules]).filter((term) => graph.objects(term, SRL_RULES).length > 0 || graph.objects(term, SRL_DATA).length > 0);
3832
+ function substr(args) {
3833
+ const value = termToString(args[0]);
3834
+ const start = Math.max(0, Number(termToPrimitive(valueToTermIfNeeded(args[1]))) - 1);
3835
+ if (args.length >= 3) return value.substring(start, start + Number(termToPrimitive(valueToTermIfNeeded(args[2]))));
3836
+ return value.substring(start);
2490
3837
  }
2491
3838
 
2492
- function toDataTriple(item, graph) {
2493
- if (item.type === 'triple') return { s: item.s, p: item.p, o: item.o };
2494
- const triple = toTripleLike(item, graph);
2495
- if ([triple.s, triple.p, triple.o].some((term) => term.type === 'var')) throw new Error('RDF Rules srl:data may not contain variables');
2496
- if (triple.p.type !== 'iri') throw new Error('RDF Rules data triple predicate must be an IRI');
2497
- return triple;
3839
+ function datatypeOf(value) {
3840
+ const term = valueToTermIfNeeded(value);
3841
+ if (term.type !== 'literal') return null;
3842
+ if (term.langDir) return iri(RDF_DIRLANGSTRING);
3843
+ if (term.lang) return iri(RDF_LANGSTRING);
3844
+ return iri(term.datatype || inferDatatype(term.value) || XSD_STRING);
2498
3845
  }
2499
3846
 
2500
- function toRule(ruleNode, graph, options = {}) {
2501
- const bodyLists = graph.objects(ruleNode, SRL_BODY);
2502
- const headLists = graph.objects(ruleNode, SRL_HEAD);
2503
- if (bodyLists.length !== 1 || headLists.length !== 1) throw new Error(`RDF Rule ${graph.label(ruleNode)} must have exactly one srl:body and one srl:head`);
2504
- const body = graph.list(bodyLists[0]).map((item) => toBodyElement(item, graph));
2505
- const head = graph.list(headLists[0]).map((item) => toTripleLike(item, graph));
2506
- return { name: graph.label(ruleNode), head, body, runOnce: ruleNeedsRunOnce(head, body, options) };
3847
+ function isNumericValue(value) {
3848
+ const term = valueToTermIfNeeded(value);
3849
+ if (typeof termToPrimitive(term) === 'bigint') return true;
3850
+ if (typeof termToPrimitive(term) === 'number') return true;
3851
+ return term.type === 'literal' && NUMERIC_DATATYPES.has(term.datatype);
2507
3852
  }
2508
3853
 
2509
- function toBodyElement(node, graph) {
2510
- if (hasTripleShape(node, graph)) return { type: 'triple', triple: toTripleLike(node, graph) };
2511
- const filters = graph.objects(node, SRL_FILTER).concat(graph.objects(node, SRL_EXPR));
2512
- if (filters.length > 0) {
2513
- if (filters.length !== 1) throw new Error(`Filter element ${graph.label(node)} must have exactly one srl:filter`);
2514
- return { type: 'filter', expr: toExpression(filters[0], graph) };
2515
- }
2516
- const assigns = graph.objects(node, SRL_ASSIGN);
2517
- if (assigns.length > 0) {
2518
- if (assigns.length !== 1) throw new Error(`Assignment element ${graph.label(node)} must have exactly one srl:assign`);
2519
- const assign = assigns[0];
2520
- const vars = graph.objects(assign, SRL_ASSIGN_VAR);
2521
- const values = graph.objects(assign, SRL_ASSIGN_VALUE);
2522
- if (vars.length !== 1 || values.length !== 1) throw new Error(`Assignment ${graph.label(assign)} must have exactly one srl:assignVar and srl:assignValue`);
2523
- const variableTerm = toVarOrTerm(vars[0], graph);
2524
- if (variableTerm.type !== 'var') throw new Error('srl:assignVar must point to a variable node');
2525
- return { type: 'set', variable: variableTerm.value, expr: toExpression(values[0], graph) };
2526
- }
2527
- const negations = graph.objects(node, SRL_NOT);
2528
- if (negations.length > 0) {
2529
- if (negations.length !== 1) throw new Error(`Negation element ${graph.label(node)} must have exactly one srl:not`);
2530
- const body = graph.list(negations[0]).map((item) => {
2531
- const clause = toBodyElement(item, graph);
2532
- if (clause.type === 'set' || clause.type === 'not') throw new Error('RDF Rules srl:not may contain only triple patterns and filters');
2533
- return clause;
2534
- });
2535
- return { type: 'not', body };
2536
- }
2537
- throw new Error(`Unsupported RDF Rules body element ${graph.label(node)}`);
3854
+ function datePart(value, part) {
3855
+ const lexical = termToString(value);
3856
+ const match = lexical.match(/^(-?\d{4,})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}):(\d{2}(?:\.\d+)?)(Z|[+-]\d{2}:?\d{2})?)?/);
3857
+ if (!match) return null;
3858
+ const [, year, month, day, hours = '0', minutes = '0', seconds = '0'] = match;
3859
+ if (part === 'year') return Number(year);
3860
+ if (part === 'month') return Number(month);
3861
+ if (part === 'day') return Number(day);
3862
+ if (part === 'hours') return Number(hours);
3863
+ if (part === 'minutes') return Number(minutes);
3864
+ if (part === 'seconds') return Number(seconds);
3865
+ return null;
3866
+ }
3867
+
3868
+ function timezoneLexical(value) {
3869
+ const lexical = termToString(value);
3870
+ const match = lexical.match(/(?:T\d{2}:\d{2}:\d{2}(?:\.\d+)?)(Z|[+-]\d{2}:?\d{2})$/);
3871
+ return match ? match[1] : '';
3872
+ }
3873
+
3874
+ function timezoneDuration(value) {
3875
+ const zone = timezoneLexical(value);
3876
+ if (!zone) return null;
3877
+ if (zone === 'Z') return literal('PT0S', XSD_DAYTIME_DURATION);
3878
+ const match = zone.match(/^([+-])(\d{2}):?(\d{2})$/);
3879
+ if (!match) return null;
3880
+ const [, sign, hh, mm] = match;
3881
+ const hours = Number(hh);
3882
+ const minutes = Number(mm);
3883
+ const body = `${hours ? `${hours}H` : ''}${minutes ? `${minutes}M` : ''}` || '0S';
3884
+ return literal(`${sign === '-' ? '-' : ''}PT${body}`, XSD_DAYTIME_DURATION);
2538
3885
  }
2539
3886
 
2540
- function toTripleLike(node, graph) {
2541
- if (node.type === 'triple') return { s: node.s, p: node.p, o: node.o };
2542
- const subjects = graph.objects(node, SRL_SUBJECT);
2543
- const predicates = graph.objects(node, SRL_PREDICATE);
2544
- const objects = graph.objects(node, SRL_OBJECT);
2545
- if (subjects.length !== 1 || predicates.length !== 1 || objects.length !== 1) {
2546
- throw new Error(`Triple node ${graph.label(node)} must have exactly one srl:subject, srl:predicate and srl:object`);
2547
- }
2548
- return {
2549
- s: toVarOrTerm(subjects[0], graph),
2550
- p: toVarOrTerm(predicates[0], graph),
2551
- o: toVarOrTerm(objects[0], graph),
2552
- };
3887
+ function langMatches(lang, range) {
3888
+ if (range === '*') return lang.length > 0;
3889
+ return lang.toLowerCase() === range.toLowerCase() || lang.toLowerCase().startsWith(`${range.toLowerCase()}-`);
2553
3890
  }
2554
3891
 
2555
- function hasTripleShape(node, graph) {
2556
- return graph.objects(node, SRL_SUBJECT).length > 0 || graph.objects(node, SRL_PREDICATE).length > 0 || graph.objects(node, SRL_OBJECT).length > 0;
3892
+ function freshUuid(options) {
3893
+ if (typeof options.uuidGenerator === 'function') return String(options.uuidGenerator());
3894
+ options.__eyelengUuidCounter = (options.__eyelengUuidCounter || 0) + 1;
3895
+ return `00000000-0000-4000-8000-${String(options.__eyelengUuidCounter).padStart(12, '0')}`;
2557
3896
  }
2558
3897
 
2559
- function toVarOrTerm(node, graph) {
2560
- const varNames = graph.objects(node, SRL_VAR_NAME);
2561
- if (varNames.length > 0) {
2562
- if (varNames.length !== 1 || varNames[0].type !== 'literal') throw new Error(`Variable node ${graph.label(node)} must have exactly one string srl:varName`);
2563
- return variable(String(varNames[0].value));
2564
- }
2565
- return node;
3898
+ function freshId(options) {
3899
+ options.__eyelengCounter = (options.__eyelengCounter || 0) + 1;
3900
+ return `eyeleng-${options.__eyelengCounter}`;
2566
3901
  }
2567
3902
 
2568
- function toExpression(node, graph) {
2569
- const varNames = graph.objects(node, SHNEX_VAR).concat(graph.objects(node, SRL_VAR_NAME));
2570
- if (varNames.length > 0) {
2571
- if (varNames.length !== 1 || varNames[0].type !== 'literal') throw new Error(`Expression variable ${graph.label(node)} must name one variable`);
2572
- return { type: 'var', name: String(varNames[0].value) };
2573
- }
2574
- if (node.type === 'literal') {
2575
- if (node.datatype || node.lang) return { type: 'term', value: node };
2576
- return { type: 'literal', value: node.value };
2577
- }
2578
- if (node.type === 'iri' || node.type === 'blank' || node.type === 'triple') {
2579
- const call = graph.functionCall(node);
2580
- if (call) return toFunctionExpression(call.name, call.args.map((arg) => toExpression(arg, graph)));
2581
- if (node.type === 'blank' && graph.hasOutgoing(node)) return { type: 'term', value: node };
2582
- return { type: 'term', value: toVarOrTerm(node, graph) };
2583
- }
2584
- return { type: 'term', value: node };
3903
+ function asTerm(value) {
3904
+ return valueToTerm(value);
2585
3905
  }
2586
3906
 
2587
- function toFunctionExpression(name, args) {
2588
- if (name.startsWith(SPARQL_NS)) {
2589
- const local = name.slice(SPARQL_NS.length);
2590
- if (local === 'less-than' || local === 'lessThan') return binary('<', args);
2591
- if (local === 'less-than-or-equal' || local === 'lessThanOrEqual') return binary('<=', args);
2592
- if (local === 'greater-than' || local === 'greaterThan') return binary('>', args);
2593
- if (local === 'greater-than-or-equal' || local === 'greaterThanOrEqual') return binary('>=', args);
2594
- if (local === 'equal' || local === 'equals') return binary('=', args);
2595
- if (local === 'not-equal' || local === 'notEqual') return binary('!=', args);
2596
- if (local === 'add') return foldBinary('+', args);
2597
- if (local === 'subtract') return binary('-', args);
2598
- if (local === 'multiply') return foldBinary('*', args);
2599
- if (local === 'divide') return binary('/', args);
2600
- if (local === 'and' || local === 'function-and') return foldBinary('&&', args);
2601
- if (local === 'or' || local === 'function-or') return foldBinary('||', args);
2602
- if (local === 'not') return { type: 'unary', op: '!', expr: args[0] };
2603
- const builtin = sparqlLocalToBuiltin(local);
2604
- return { type: 'call', name: builtin, args };
3907
+ module.exports = {
3908
+ BUILTIN_SIGNATURES,
3909
+ builtinNames,
3910
+ canonicalBuiltinName,
3911
+ isBuiltinName,
3912
+ validateArity,
3913
+ evalExpression,
3914
+ booleanValue,
3915
+ asTerm,
3916
+ callBuiltin,
3917
+ evalBinary,
3918
+ };
3919
+
3920
+ },
3921
+ "src/rdfMessages.js": function (require, module, exports) {
3922
+ 'use strict';
3923
+
3924
+ const { parseRdfDocument } = require('./rdfSyntax.js');
3925
+ const {
3926
+ iri,
3927
+ blankNode,
3928
+ literal,
3929
+ tripleTerm,
3930
+ RDF_TYPE,
3931
+ RDF_FIRST,
3932
+ RDF_REST,
3933
+ RDF_NIL,
3934
+ XSD_INTEGER,
3935
+ } = require('./term.js');
3936
+
3937
+ const RDF_MESSAGE_VERSION_RE = /^\s*(?:@version|VERSION)\s+(["'])(?:1\.1|1\.2|1\.2-basic)-messages\1\s*\.?\s*(?:#.*)?$/im;
3938
+ const RDF_MESSAGE_VERSION_LINE_RE = /^\s*(?:@version|VERSION)\s+(["'])(?:1\.1|1\.2|1\.2-basic)-messages\1\s*\.?\s*(?:#.*)?$/i;
3939
+ const RDF_DIRECTIVE_LINE_RE = /^\s*(?:@?(?:prefix|base)\b|PREFIX\b|BASE\b)/i;
3940
+ const RDF_MESSAGE_DELIMITER_LINE_RE = /^\s*(?:MESSAGE\b|@message\s*\.?)\s*(?:#.*)?$/i;
3941
+
3942
+ const EYMSG_NS = 'https://eyereasoner.github.io/eyeling/vocab/message#';
3943
+ const LOG_NS = 'http://www.w3.org/2000/10/swap/log#';
3944
+ const EYMSG = Object.freeze({
3945
+ RDFMessageStream: `${EYMSG_NS}RDFMessageStream`,
3946
+ MessageEnvelope: `${EYMSG_NS}MessageEnvelope`,
3947
+ envelope: `${EYMSG_NS}envelope`,
3948
+ firstEnvelope: `${EYMSG_NS}firstEnvelope`,
3949
+ lastEnvelope: `${EYMSG_NS}lastEnvelope`,
3950
+ orderedEnvelopes: `${EYMSG_NS}orderedEnvelopes`,
3951
+ messageCount: `${EYMSG_NS}messageCount`,
3952
+ offset: `${EYMSG_NS}offset`,
3953
+ nextEnvelope: `${EYMSG_NS}nextEnvelope`,
3954
+ payloadGraph: `${EYMSG_NS}payloadGraph`,
3955
+ payloadKind: `${EYMSG_NS}payloadKind`,
3956
+ payloadTriple: `${EYMSG_NS}payloadTriple`,
3957
+ tripleCount: `${EYMSG_NS}tripleCount`,
3958
+ empty: `${EYMSG_NS}empty`,
3959
+ nonEmpty: `${EYMSG_NS}nonEmpty`,
3960
+ });
3961
+ const LOG_NAME_OF = `${LOG_NS}nameOf`;
3962
+
3963
+ function simpleHashText(value) {
3964
+ let h = 0x811c9dc5;
3965
+ const text = String(value || '');
3966
+ for (let i = 0; i < text.length; i += 1) {
3967
+ h ^= text.charCodeAt(i);
3968
+ h = Math.imul(h, 0x01000193) >>> 0;
2605
3969
  }
2606
- return { type: 'call', name, args };
3970
+ return h.toString(16).padStart(8, '0');
2607
3971
  }
2608
3972
 
2609
- function binary(op, args) {
2610
- if (args.length !== 2) throw new Error(`sparql operator ${op} expects 2 arguments`);
2611
- return { type: 'binary', op, left: args[0], right: args[1] };
3973
+ function looksLikeRdfMessageLog(source, options = {}) {
3974
+ return !!options.rdfMessages || RDF_MESSAGE_VERSION_RE.test(String(source || ''));
2612
3975
  }
2613
3976
 
2614
- function foldBinary(op, args) {
2615
- if (args.length < 2) throw new Error(`sparql operator ${op} expects at least 2 arguments`);
2616
- return args.slice(1).reduce((left, right) => ({ type: 'binary', op, left, right }), args[0]);
3977
+ function splitPreservingLineEndings(text) {
3978
+ return String(text || '').match(/.*(?:\r\n|\n|\r)|.+$/g) || [];
2617
3979
  }
2618
3980
 
2619
- function sparqlLocalToBuiltin(local) {
2620
- return local.replace(/-([a-z])/g, (_, ch) => ch.toUpperCase()).replace(/^./, (ch) => ch.toUpperCase());
3981
+ function isOnlyWhitespaceAndComments(text) {
3982
+ return splitPreservingLineEndings(text).every((line) => {
3983
+ const hash = line.indexOf('#');
3984
+ const body = hash >= 0 ? line.slice(0, hash) : line;
3985
+ return body.trim() === '';
3986
+ });
2621
3987
  }
2622
3988
 
2623
- class RdfGraph {
2624
- constructor(triples, prefixes = {}) {
2625
- this.triples = triples;
2626
- this.prefixes = prefixes;
2627
- this.bySubject = new Map();
2628
- for (const triple of triples) {
2629
- const key = termKey(triple.s);
2630
- if (!this.bySubject.has(key)) this.bySubject.set(key, []);
2631
- this.bySubject.get(key).push(triple);
2632
- }
2633
- }
3989
+ function stripMessageVersionLines(text) {
3990
+ return splitPreservingLineEndings(text).filter((line) => !RDF_MESSAGE_VERSION_LINE_RE.test(line)).join('');
3991
+ }
2634
3992
 
2635
- objects(subject, predicateIRI) {
2636
- const rows = this.bySubject.get(termKey(subject)) || [];
2637
- return rows.filter((triple) => triple.p.type === 'iri' && triple.p.value === predicateIRI).map((triple) => triple.o);
2638
- }
3993
+ function stripDirectiveLines(text) {
3994
+ return splitPreservingLineEndings(text).filter((line) => !RDF_DIRECTIVE_LINE_RE.test(line) && !RDF_MESSAGE_VERSION_LINE_RE.test(line)).join('');
3995
+ }
2639
3996
 
2640
- subjects(predicateIRI, object) {
2641
- return this.triples.filter((triple) => triple.p.type === 'iri' && triple.p.value === predicateIRI && termEquals(triple.o, object)).map((triple) => triple.s);
3997
+ function collectDirectiveLines(text) {
3998
+ const seen = new Set();
3999
+ const out = [];
4000
+ for (const line of splitPreservingLineEndings(text)) {
4001
+ if (!RDF_DIRECTIVE_LINE_RE.test(line)) continue;
4002
+ const key = line.trim();
4003
+ if (!key || seen.has(key)) continue;
4004
+ seen.add(key);
4005
+ out.push(line.endsWith('\n') || line.endsWith('\r') ? line : `${line}\n`);
2642
4006
  }
4007
+ return out;
4008
+ }
2643
4009
 
2644
- subjectsWithPredicate(predicateIRI) {
2645
- return this.triples.filter((triple) => triple.p.type === 'iri' && triple.p.value === predicateIRI).map((triple) => triple.s);
4010
+ function readStringAt(source, index) {
4011
+ const quote = source[index];
4012
+ const long = source.startsWith(quote.repeat(3), index);
4013
+ let i = index + (long ? 3 : 1);
4014
+ while (i < source.length) {
4015
+ if (source[i] === '\\') { i += 2; continue; }
4016
+ if (long && source.startsWith(quote.repeat(3), i)) return { end: i + 3 };
4017
+ if (!long && source[i] === quote) return { end: i + 1 };
4018
+ i += 1;
2646
4019
  }
4020
+ return { end: source.length };
4021
+ }
2647
4022
 
2648
- hasOutgoing(subject) {
2649
- return (this.bySubject.get(termKey(subject)) || []).length > 0;
4023
+ function readIriAt(source, index) {
4024
+ let i = index + 1;
4025
+ while (i < source.length) {
4026
+ if (source[i] === '\\') { i += 2; continue; }
4027
+ if (source[i] === '>') return { end: i + 1 };
4028
+ i += 1;
2650
4029
  }
4030
+ return { end: source.length };
4031
+ }
2651
4032
 
2652
- list(head) {
2653
- const out = [];
2654
- let node = head;
2655
- const seen = new Set();
2656
- while (!(node.type === 'iri' && node.value === RDF_NIL)) {
2657
- const key = termKey(node);
2658
- if (seen.has(key)) throw new Error(`Cycle in RDF list at ${this.label(node)}`);
2659
- seen.add(key);
2660
- const first = this.objects(node, RDF_FIRST);
2661
- const rest = this.objects(node, RDF_REST);
2662
- if (first.length !== 1 || rest.length !== 1) throw new Error(`Expected RDF list node at ${this.label(node)}`);
2663
- out.push(first[0]);
2664
- node = rest[0];
4033
+ function skipWsAndComments(source, index) {
4034
+ let i = index;
4035
+ while (i < source.length) {
4036
+ if (/\s/.test(source[i])) { i += 1; continue; }
4037
+ if (source[i] === '#') {
4038
+ while (i < source.length && source[i] !== '\n' && source[i] !== '\r') i += 1;
4039
+ continue;
2665
4040
  }
2666
- return out;
4041
+ break;
2667
4042
  }
4043
+ return i;
4044
+ }
2668
4045
 
2669
- functionCall(node) {
2670
- if (node.type !== 'blank') return null;
2671
- const rows = (this.bySubject.get(termKey(node)) || []).filter((triple) => triple.p.type === 'iri');
2672
- const calls = rows.filter((triple) => triple.p.value.startsWith(SPARQL_NS) || triple.p.value.includes('#') || triple.p.value.includes('/'));
2673
- const viable = calls.filter((triple) => isRdfListHead(triple.o, this));
2674
- if (viable.length !== 1) return null;
2675
- return { name: viable[0].p.value, args: this.list(viable[0].o) };
4046
+ function isWordChar(ch) { return !!ch && /[A-Za-z0-9_\-]/.test(ch); }
4047
+ function startsWordAt(source, word, index) {
4048
+ return source.slice(index, index + word.length).toUpperCase() === word && !isWordChar(source[index - 1]) && !isWordChar(source[index + word.length]);
4049
+ }
4050
+
4051
+ function findMessageDirectiveAt(source, index) {
4052
+ if (startsWordAt(source, 'MESSAGE', index)) return { start: index, end: index + 'MESSAGE'.length };
4053
+ if (source.slice(index, index + 8).toLowerCase() === '@message' && !isWordChar(source[index + 8])) {
4054
+ let end = index + 8;
4055
+ end = skipWsAndComments(source, end);
4056
+ if (source[end] === '.') end += 1;
4057
+ return { start: index, end };
2676
4058
  }
4059
+ return null;
4060
+ }
2677
4061
 
2678
- parseReference(text) {
2679
- if (typeof text !== 'string') return text;
2680
- if (text.startsWith('<') && text.endsWith('>')) return iri(text.slice(1, -1));
2681
- if (text.startsWith('_:')) return blankNode(text.slice(2));
2682
- const colon = text.indexOf(':');
2683
- if (colon >= 0) {
2684
- const prefix = text.slice(0, colon);
2685
- const local = text.slice(colon + 1);
2686
- const ns = this.prefixes[prefix] || (prefix === 'srl' ? SRL_NS : null);
2687
- if (ns) return iri(ns + local);
4062
+ function splitRdfMessageLog(source) {
4063
+ const text = stripMessageVersionLines(source);
4064
+ const chunks = [];
4065
+ let i = 0;
4066
+ let start = 0;
4067
+ let braceDepth = 0;
4068
+ let bracketDepth = 0;
4069
+ let parenDepth = 0;
4070
+ let statementStart = true;
4071
+ let sawDelimiter = false;
4072
+
4073
+ while (i < text.length) {
4074
+ const ch = text[i];
4075
+ if (ch === '"' || ch === "'") { i = readStringAt(text, i).end; statementStart = false; continue; }
4076
+ if (ch === '<' && !text.startsWith('<<', i)) { i = readIriAt(text, i).end; statementStart = false; continue; }
4077
+ if (ch === '#') { while (i < text.length && text[i] !== '\n' && text[i] !== '\r') i += 1; statementStart = true; continue; }
4078
+
4079
+ if (statementStart && braceDepth === 0 && bracketDepth === 0 && parenDepth === 0) {
4080
+ const termStart = skipWsAndComments(text, i);
4081
+ const directive = findMessageDirectiveAt(text, termStart);
4082
+ if (directive) {
4083
+ chunks.push(text.slice(start, termStart));
4084
+ start = directive.end;
4085
+ i = directive.end;
4086
+ statementStart = true;
4087
+ sawDelimiter = true;
4088
+ continue;
4089
+ }
4090
+ if (termStart !== i) { i = termStart; continue; }
2688
4091
  }
2689
- return iri(text);
2690
- }
2691
4092
 
2692
- label(term) { return formatTerm(term, this.prefixes); }
2693
- }
4093
+ if (ch === '{') braceDepth += 1;
4094
+ else if (ch === '}' && braceDepth > 0) braceDepth -= 1;
4095
+ else if (ch === '[') bracketDepth += 1;
4096
+ else if (ch === ']' && bracketDepth > 0) bracketDepth -= 1;
4097
+ else if (ch === '(') parenDepth += 1;
4098
+ else if (ch === ')' && parenDepth > 0) parenDepth -= 1;
2694
4099
 
2695
- function isRdfListHead(term, graph) {
2696
- return (term.type === 'iri' && term.value === RDF_NIL) || graph.objects(term, RDF_FIRST).length === 1;
4100
+ if (ch === '.' && braceDepth === 0 && bracketDepth === 0 && parenDepth === 0) statementStart = true;
4101
+ else if (ch === '\n' || ch === '\r') statementStart = true;
4102
+ else if (!/\s/.test(ch)) statementStart = false;
4103
+ i += 1;
4104
+ }
4105
+
4106
+ const tail = text.slice(start);
4107
+ if (!sawDelimiter || !isOnlyWhitespaceAndComments(tail)) chunks.push(tail);
4108
+ return chunks;
2697
4109
  }
2698
4110
 
2699
- function uniqueTerms(terms) {
2700
- const seen = new Set();
2701
- const out = [];
2702
- for (const term of terms) {
2703
- const key = termKey(term);
2704
- if (!seen.has(key)) { seen.add(key); out.push(term); }
4111
+ function rewriteMessageBlankLabels(source, messageIndex) {
4112
+ const prefix = `msg${String(messageIndex).padStart(3, '0')}_`;
4113
+ let out = '';
4114
+ let i = 0;
4115
+ while (i < source.length) {
4116
+ const ch = source[i];
4117
+ if (ch === '"' || ch === "'") {
4118
+ const end = readStringAt(source, i).end;
4119
+ out += source.slice(i, end);
4120
+ i = end;
4121
+ continue;
4122
+ }
4123
+ if (ch === '<' && !source.startsWith('<<', i)) {
4124
+ const end = readIriAt(source, i).end;
4125
+ out += source.slice(i, end);
4126
+ i = end;
4127
+ continue;
4128
+ }
4129
+ if (ch === '#') {
4130
+ while (i < source.length) {
4131
+ const c = source[i++]; out += c;
4132
+ if (c === '\n' || c === '\r') break;
4133
+ }
4134
+ continue;
4135
+ }
4136
+ if (source.startsWith('_:', i)) {
4137
+ let j = i + 2;
4138
+ while (j < source.length && !/\s/.test(source[j]) && !'{}[](),;.<>'.includes(source[j])) j += 1;
4139
+ const label = source.slice(i + 2, j);
4140
+ if (label) {
4141
+ out += `_:${prefix}${label.replace(/[^A-Za-z0-9_\-]/g, '_')}`;
4142
+ i = j;
4143
+ continue;
4144
+ }
4145
+ }
4146
+ out += ch;
4147
+ i += 1;
2705
4148
  }
2706
4149
  return out;
2707
4150
  }
2708
4151
 
2709
- function numericLiteral(value) {
2710
- if (Number.isInteger(value)) return literal(value, XSD_INTEGER);
2711
- if (String(value).includes('e') || String(value).includes('E')) return literal(value, XSD_DOUBLE);
2712
- return literal(value, XSD_DECIMAL);
2713
- }
2714
-
2715
- function parseIntegerLiteral(value) {
2716
- const text = String(value);
2717
- const asNumber = Number.parseInt(text, 10);
2718
- return Number.isSafeInteger(asNumber) && String(asNumber) === text.replace(/^\+/, '') ? asNumber : BigInt(text);
2719
- }
4152
+ function messageChunkHasRdf(chunk) {
4153
+ return !isOnlyWhitespaceAndComments(stripDirectiveLines(chunk));
4154
+ }
4155
+
4156
+ function listTriples(headTerm, items, data, makeBlank) {
4157
+ if (items.length === 0) return iri(RDF_NIL);
4158
+ const cells = items.map(() => makeBlank());
4159
+ for (let i = 0; i < cells.length; i += 1) {
4160
+ data.push({ s: cells[i], p: iri(RDF_FIRST), o: items[i] });
4161
+ data.push({ s: cells[i], p: iri(RDF_REST), o: i + 1 < cells.length ? cells[i + 1] : iri(RDF_NIL) });
4162
+ }
4163
+ return headTerm || cells[0];
4164
+ }
4165
+
4166
+ function parseRdfMessageLog(source, options = {}) {
4167
+ const text = String(source || '');
4168
+ const directives = collectDirectiveLines(text);
4169
+ const chunks = splitRdfMessageLog(text);
4170
+ // Keep generated message-log IRIs stable across machines and checkout paths.
4171
+ // The previous seed used baseIRI/filename, which made golden outputs depend on
4172
+ // absolute local paths such as file:///home/.../examples/rdf-messages.trig.
4173
+ // A caller that needs a location-specific identity can still pass
4174
+ // options.messageBaseIRI explicitly.
4175
+ const hash = simpleHashText(text);
4176
+ const base = options.messageBaseIRI || `urn:eyeleng:message-log:${hash}`;
4177
+ const stream = iri(`${base}#stream`);
4178
+ const envelopes = chunks.map((unused, index) => iri(`${base}#m${String(index + 1).padStart(3, '0')}`));
4179
+ const payloads = chunks.map((unused, index) => iri(`${base}#m${String(index + 1).padStart(3, '0')}/payload`));
4180
+ const data = [];
4181
+ let bnodeCounter = 0;
4182
+ const makeBlank = () => blankNode(`msg${++bnodeCounter}`);
4183
+ const prefixes = {
4184
+ rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
4185
+ xsd: 'http://www.w3.org/2001/XMLSchema#',
4186
+ eymsg: EYMSG_NS,
4187
+ log: LOG_NS,
4188
+ };
2720
4189
 
2721
- function coerceLexicalLiteral(value, datatype) {
2722
- if (datatype === XSD_INTEGER) return parseIntegerLiteral(value);
2723
- if (datatype === XSD_DECIMAL || datatype === XSD_DOUBLE) return Number(value);
2724
- if (datatype === XSD_BOOLEAN) return value === true || value === 'true' || value === '1';
2725
- return value;
2726
- }
4190
+ data.push({ s: stream, p: iri(RDF_TYPE), o: iri(EYMSG.RDFMessageStream) });
4191
+ data.push({ s: stream, p: iri(EYMSG.messageCount), o: literal(chunks.length, XSD_INTEGER) });
4192
+ if (envelopes.length > 0) {
4193
+ data.push({ s: stream, p: iri(EYMSG.orderedEnvelopes), o: listTriples(null, envelopes, data, makeBlank) });
4194
+ data.push({ s: stream, p: iri(EYMSG.firstEnvelope), o: envelopes[0] });
4195
+ data.push({ s: stream, p: iri(EYMSG.lastEnvelope), o: envelopes[envelopes.length - 1] });
4196
+ }
4197
+
4198
+ for (let i = 0; i < chunks.length; i += 1) {
4199
+ const envelope = envelopes[i];
4200
+ const payload = payloads[i];
4201
+ const rawChunk = chunks[i];
4202
+ const chunk = rewriteMessageBlankLabels(rawChunk, i + 1);
4203
+ const hasBody = messageChunkHasRdf(chunk);
4204
+ const bodySource = `${directives.join('')}\n${stripDirectiveLines(chunk)}`;
4205
+ const parsed = hasBody ? parseRdfDocument(bodySource, { ...options, filename: `${options.filename || '<message>'}#m${i + 1}`, baseIRI: options.baseIRI }) : { triples: [], prefixes: {} };
4206
+ Object.assign(prefixes, parsed.prefixes || {});
4207
+ const payloadTriples = parsed.triples || [];
4208
+ const tripleTerms = payloadTriples.map((t) => tripleTerm(t.s, t.p, t.o));
4209
+
4210
+ data.push({ s: stream, p: iri(EYMSG.envelope), o: envelope });
4211
+ data.push({ s: envelope, p: iri(RDF_TYPE), o: iri(EYMSG.MessageEnvelope) });
4212
+ data.push({ s: envelope, p: iri(EYMSG.offset), o: literal(i + 1, XSD_INTEGER) });
4213
+ data.push({ s: envelope, p: iri(EYMSG.payloadKind), o: iri(hasBody ? EYMSG.nonEmpty : EYMSG.empty) });
4214
+ data.push({ s: envelope, p: iri(EYMSG.tripleCount), o: literal(payloadTriples.length, XSD_INTEGER) });
4215
+ if (i + 1 < envelopes.length) data.push({ s: envelope, p: iri(EYMSG.nextEnvelope), o: envelopes[i + 1] });
4216
+ if (hasBody) {
4217
+ data.push({ s: envelope, p: iri(EYMSG.payloadGraph), o: payload });
4218
+ data.push({ s: payload, p: iri(LOG_NAME_OF), o: listTriples(null, tripleTerms, data, makeBlank) });
4219
+ for (const term of tripleTerms) data.push({ s: payload, p: iri(EYMSG.payloadTriple), o: term });
4220
+ if (options.includeMessageFacts) data.push(...payloadTriples);
4221
+ }
4222
+ }
2727
4223
 
2728
- function looksLikeRdfRules(source, options = {}) {
2729
- if (options.syntax === 'rdf') return true;
2730
- if (options.syntax === 'srl') return false;
2731
- if (options.filename && /\.(ttl|trig|nt|n3)$/i.test(options.filename)) return true;
2732
- return /\bsrl:RuleSet\b|\bsrl:rules\b|http:\/\/www\.w3\.org\/ns\/shacl-rules#RuleSet/.test(source);
4224
+ return {
4225
+ baseIRI: options.baseIRI || null,
4226
+ version: '1.2-messages',
4227
+ imports: [],
4228
+ prefixes,
4229
+ data,
4230
+ rules: [],
4231
+ };
2733
4232
  }
2734
4233
 
2735
4234
  module.exports = {
2736
- parseRdfDocument,
2737
- parseRdfSyntax,
2738
- rdfDocumentToProgram,
2739
- looksLikeRdfRules,
2740
- TurtleParser,
2741
- RdfGraph,
2742
- constants: {
2743
- SRL_NS,
2744
- SHNEX_NS,
2745
- SPARQL_NS,
2746
- SRL_RULE_SET,
2747
- SRL_RULE,
2748
- },
4235
+ EYMSG_NS,
4236
+ EYMSG,
4237
+ LOG_NS,
4238
+ LOG_NAME_OF,
4239
+ looksLikeRdfMessageLog,
4240
+ splitRdfMessageLog,
4241
+ parseRdfMessageLog,
2749
4242
  };
2750
4243
 
2751
4244
  },
@@ -4144,7 +5637,7 @@
4144
5637
 
4145
5638
  },
4146
5639
  };
4147
- const __mappings = {"src/tokenizer.js":{},"src/term.js":{},"src/builtins.js":{"./term.js":"src/term.js"},"src/assignments.js":{},"src/parser.js":{"./tokenizer.js":"src/tokenizer.js","./builtins.js":"src/builtins.js","./assignments.js":"src/assignments.js","./term.js":"src/term.js"},"src/rdfSyntax.js":{"./tokenizer.js":"src/tokenizer.js","./assignments.js":"src/assignments.js","./term.js":"src/term.js"},"src/store.js":{"./term.js":"src/term.js"},"src/analyze.js":{"./term.js":"src/term.js","./assignments.js":"src/assignments.js"},"src/engine.js":{"./store.js":"src/store.js","./term.js":"src/term.js","./builtins.js":"src/builtins.js","./analyze.js":"src/analyze.js"},"src/format.js":{"./term.js":"src/term.js"},"src/query.js":{"./parser.js":"src/parser.js","./store.js":"src/store.js","./engine.js":"src/engine.js","./api.js":"src/api.js"},"src/output.js":{},"src/api.js":{"./parser.js":"src/parser.js","./rdfSyntax.js":"src/rdfSyntax.js","./engine.js":"src/engine.js","./analyze.js":"src/analyze.js","./format.js":"src/format.js","./query.js":"src/query.js","./output.js":"src/output.js"},"src/cli.js":{"./api.js":"src/api.js","./term.js":"src/term.js"}};
5640
+ const __mappings = {"src/tokenizer.js":{},"src/assignments.js":{},"src/term.js":{},"src/rdfSyntax.js":{"./tokenizer.js":"src/tokenizer.js","./assignments.js":"src/assignments.js","./term.js":"src/term.js"},"src/builtins.js":{"./term.js":"src/term.js"},"src/parser.js":{"./tokenizer.js":"src/tokenizer.js","./rdfSyntax.js":"src/rdfSyntax.js","./builtins.js":"src/builtins.js","./assignments.js":"src/assignments.js","./term.js":"src/term.js"},"src/rdfMessages.js":{"./rdfSyntax.js":"src/rdfSyntax.js","./term.js":"src/term.js"},"src/store.js":{"./term.js":"src/term.js"},"src/analyze.js":{"./term.js":"src/term.js","./assignments.js":"src/assignments.js"},"src/engine.js":{"./store.js":"src/store.js","./term.js":"src/term.js","./builtins.js":"src/builtins.js","./analyze.js":"src/analyze.js"},"src/format.js":{"./term.js":"src/term.js"},"src/query.js":{"./parser.js":"src/parser.js","./store.js":"src/store.js","./engine.js":"src/engine.js","./api.js":"src/api.js"},"src/output.js":{},"src/api.js":{"./parser.js":"src/parser.js","./rdfSyntax.js":"src/rdfSyntax.js","./rdfMessages.js":"src/rdfMessages.js","./engine.js":"src/engine.js","./analyze.js":"src/analyze.js","./format.js":"src/format.js","./query.js":"src/query.js","./output.js":"src/output.js"},"src/cli.js":{"./api.js":"src/api.js","./term.js":"src/term.js"}};
4148
5641
  const __cache = {};
4149
5642
  function __require(id) {
4150
5643
  if (!id.startsWith("src/")) return __nativeRequire(id);