eyeleng 1.0.5 → 1.0.6

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