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