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