eyeling 1.24.30 → 1.24.31
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/dist/browser/eyeling.browser.js +283 -4
- package/examples/input/rdf-message-flow.trig +56 -105
- package/examples/output/rdf-message-flow.md +3 -3
- package/examples/rdf-message-flow.n3 +70 -49
- package/eyeling.js +283 -4
- package/lib/lexer.js +283 -4
- package/package.json +1 -1
- package/test/api.test.js +58 -0
|
@@ -9769,6 +9769,24 @@ function stripQuotes(lex) {
|
|
|
9769
9769
|
// This keeps all downstream parsing/reasoning N3-only.
|
|
9770
9770
|
const LOG_NAME_OF_IRI = '<http://www.w3.org/2000/10/swap/log#nameOf>';
|
|
9771
9771
|
const RDF_REIFIES_IRI = '<http://www.w3.org/1999/02/22-rdf-syntax-ns#reifies>';
|
|
9772
|
+
const RDF_TYPE_IRI = '<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>';
|
|
9773
|
+
const XSD_INTEGER_IRI = '<http://www.w3.org/2001/XMLSchema#integer>';
|
|
9774
|
+
const EYMSG_NS = 'https://eyereasoner.github.io/eyeling/vocab/message#';
|
|
9775
|
+
const EYMSG = Object.freeze({
|
|
9776
|
+
RDFMessageStream: `<${EYMSG_NS}RDFMessageStream>`,
|
|
9777
|
+
MessageEnvelope: `<${EYMSG_NS}MessageEnvelope>`,
|
|
9778
|
+
envelope: `<${EYMSG_NS}envelope>`,
|
|
9779
|
+
firstEnvelope: `<${EYMSG_NS}firstEnvelope>`,
|
|
9780
|
+
lastEnvelope: `<${EYMSG_NS}lastEnvelope>`,
|
|
9781
|
+
orderedEnvelopes: `<${EYMSG_NS}orderedEnvelopes>`,
|
|
9782
|
+
messageCount: `<${EYMSG_NS}messageCount>`,
|
|
9783
|
+
offset: `<${EYMSG_NS}offset>`,
|
|
9784
|
+
nextEnvelope: `<${EYMSG_NS}nextEnvelope>`,
|
|
9785
|
+
payloadGraph: `<${EYMSG_NS}payloadGraph>`,
|
|
9786
|
+
payloadKind: `<${EYMSG_NS}payloadKind>`,
|
|
9787
|
+
empty: `<${EYMSG_NS}empty>`,
|
|
9788
|
+
nonEmpty: `<${EYMSG_NS}nonEmpty>`,
|
|
9789
|
+
});
|
|
9772
9790
|
|
|
9773
9791
|
function normalizeRdfCompatibility(inputText) {
|
|
9774
9792
|
let text = String(inputText ?? '');
|
|
@@ -9779,10 +9797,11 @@ function normalizeRdfCompatibility(inputText) {
|
|
|
9779
9797
|
// plausible top-level TriG named graph block.
|
|
9780
9798
|
const hasTripleTerms = text.includes('<<');
|
|
9781
9799
|
const hasVersionDirective = /^\s*(?:@version|VERSION)\s+(["'])1\.2\1\s*\.?\s*(?:#.*)?$/im.test(text);
|
|
9800
|
+
const hasMessageVersionDirective = /^\s*(?:@version|VERSION)\s+(["'])(?:1\.1|1\.2|1\.2-basic)-messages\1\s*\.?\s*(?:#.*)?$/im.test(text);
|
|
9782
9801
|
const hasNamedGraphCandidate = /(?:^|[.\r\n])\s*(?:GRAPH\s+)?(?:<[^>\r\n]*>|_:[A-Za-z][A-Za-z0-9_-]*|[A-Za-z][A-Za-z0-9_-]*:[^\s{};,.()[\]]*)\s*\{/m.test(text);
|
|
9783
9802
|
const hasAnnotationSyntax = /(?:^|\s)~\s*(?:<|_:[A-Za-z]|[A-Za-z][A-Za-z0-9_-]*:|\{\|)|\{\|/.test(text);
|
|
9784
9803
|
|
|
9785
|
-
if (!hasTripleTerms && !hasVersionDirective && !hasNamedGraphCandidate && !hasAnnotationSyntax) return text;
|
|
9804
|
+
if (!hasTripleTerms && !hasVersionDirective && !hasMessageVersionDirective && !hasNamedGraphCandidate && !hasAnnotationSyntax) return text;
|
|
9786
9805
|
|
|
9787
9806
|
function isWordChar(ch) {
|
|
9788
9807
|
return ch != null && /[A-Za-z0-9_:-]/.test(ch);
|
|
@@ -10136,7 +10155,7 @@ function normalizeRdfCompatibility(inputText) {
|
|
|
10136
10155
|
}
|
|
10137
10156
|
|
|
10138
10157
|
function stripVersionDirectives(s) {
|
|
10139
|
-
return s.replace(/^\s*(?:@version|VERSION)\s+(["'])1\.2
|
|
10158
|
+
return s.replace(/^\s*(?:@version|VERSION)\s+(["'])(?:1\.1|1\.2|1\.2-basic)(?:-messages)?\1\s*\.?\s*(?:#.*)?$/gim, '');
|
|
10140
10159
|
}
|
|
10141
10160
|
|
|
10142
10161
|
function skipWsAndComments(s, at) {
|
|
@@ -10290,10 +10309,270 @@ function normalizeRdfCompatibility(inputText) {
|
|
|
10290
10309
|
return out;
|
|
10291
10310
|
}
|
|
10292
10311
|
|
|
10312
|
+
|
|
10313
|
+
function isOnlyWhitespaceAndComments(s) {
|
|
10314
|
+
return skipWsAndComments(s, 0) >= s.length;
|
|
10315
|
+
}
|
|
10316
|
+
|
|
10317
|
+
|
|
10318
|
+
function skipOldStyleDirective(s, at) {
|
|
10319
|
+
let i = at;
|
|
10320
|
+
while (i < s.length) {
|
|
10321
|
+
const ch = s[i];
|
|
10322
|
+
if (ch === '"' || ch === "'") {
|
|
10323
|
+
i = readStringAt(s, i).end;
|
|
10324
|
+
continue;
|
|
10325
|
+
}
|
|
10326
|
+
if (ch === '<') {
|
|
10327
|
+
i = readIriAt(s, i).end;
|
|
10328
|
+
continue;
|
|
10329
|
+
}
|
|
10330
|
+
if (ch === '#') {
|
|
10331
|
+
while (i < s.length && s[i] !== '\n' && s[i] !== '\r') i += 1;
|
|
10332
|
+
continue;
|
|
10333
|
+
}
|
|
10334
|
+
i += 1;
|
|
10335
|
+
if (ch === '.') return i;
|
|
10336
|
+
}
|
|
10337
|
+
return i;
|
|
10338
|
+
}
|
|
10339
|
+
|
|
10340
|
+
function stripDirectivesAndCommentsForEmptiness(s) {
|
|
10341
|
+
let out = '';
|
|
10342
|
+
let i = 0;
|
|
10343
|
+
let statementStart = true;
|
|
10344
|
+
while (i < s.length) {
|
|
10345
|
+
const ch = s[i];
|
|
10346
|
+
if (ch === '"' || ch === "'") {
|
|
10347
|
+
const str = readStringAt(s, i);
|
|
10348
|
+
out += str.text;
|
|
10349
|
+
i = str.end;
|
|
10350
|
+
statementStart = false;
|
|
10351
|
+
continue;
|
|
10352
|
+
}
|
|
10353
|
+
if (ch === '<') {
|
|
10354
|
+
const iri = readIriAt(s, i);
|
|
10355
|
+
out += iri.text;
|
|
10356
|
+
i = iri.end;
|
|
10357
|
+
statementStart = false;
|
|
10358
|
+
continue;
|
|
10359
|
+
}
|
|
10360
|
+
if (ch === '#') {
|
|
10361
|
+
while (i < s.length && s[i] !== '\n' && s[i] !== '\r') i += 1;
|
|
10362
|
+
statementStart = true;
|
|
10363
|
+
continue;
|
|
10364
|
+
}
|
|
10365
|
+
if (statementStart) {
|
|
10366
|
+
const start = skipWsAndComments(s, i);
|
|
10367
|
+
out += s.slice(i, start);
|
|
10368
|
+
i = start;
|
|
10369
|
+
if (startsWordAt(s, 'PREFIX', i) || startsWordAt(s, 'BASE', i) || startsWordAt(s, 'VERSION', i)) {
|
|
10370
|
+
while (i < s.length && s[i] !== '\n' && s[i] !== '\r') i += 1;
|
|
10371
|
+
statementStart = true;
|
|
10372
|
+
continue;
|
|
10373
|
+
}
|
|
10374
|
+
if (s[i] === '@') {
|
|
10375
|
+
const lower = s.slice(i, i + 9).toLowerCase();
|
|
10376
|
+
if (lower.startsWith('@prefix') || lower.startsWith('@base') || lower.startsWith('@version')) {
|
|
10377
|
+
i = skipOldStyleDirective(s, i);
|
|
10378
|
+
statementStart = true;
|
|
10379
|
+
continue;
|
|
10380
|
+
}
|
|
10381
|
+
}
|
|
10382
|
+
}
|
|
10383
|
+
out += ch;
|
|
10384
|
+
if (ch === '.' || ch === '}' || ch === '\n' || ch === '\r') statementStart = true;
|
|
10385
|
+
else if (!/\s/.test(ch)) statementStart = false;
|
|
10386
|
+
i += 1;
|
|
10387
|
+
}
|
|
10388
|
+
return out;
|
|
10389
|
+
}
|
|
10390
|
+
|
|
10391
|
+
function simpleHashText(s) {
|
|
10392
|
+
let h = 0x811c9dc5;
|
|
10393
|
+
for (let i = 0; i < s.length; i += 1) {
|
|
10394
|
+
h ^= s.charCodeAt(i);
|
|
10395
|
+
h = Math.imul(h, 0x01000193) >>> 0;
|
|
10396
|
+
}
|
|
10397
|
+
return h.toString(16).padStart(8, '0');
|
|
10398
|
+
}
|
|
10399
|
+
|
|
10400
|
+
function rewriteMessageBlankLabels(s, messageIndex) {
|
|
10401
|
+
let out = '';
|
|
10402
|
+
let i = 0;
|
|
10403
|
+
const prefix = `_:eyeling_m${String(messageIndex).padStart(3, '0')}_`;
|
|
10404
|
+
while (i < s.length) {
|
|
10405
|
+
const ch = s[i];
|
|
10406
|
+
if (ch === '"' || ch === "'") {
|
|
10407
|
+
const str = readStringAt(s, i);
|
|
10408
|
+
out += str.text;
|
|
10409
|
+
i = str.end;
|
|
10410
|
+
continue;
|
|
10411
|
+
}
|
|
10412
|
+
if (ch === '<' && !s.startsWith('<<', i)) {
|
|
10413
|
+
const iri = readIriAt(s, i);
|
|
10414
|
+
out += iri.text;
|
|
10415
|
+
i = iri.end;
|
|
10416
|
+
continue;
|
|
10417
|
+
}
|
|
10418
|
+
if (ch === '#') {
|
|
10419
|
+
while (i < s.length) {
|
|
10420
|
+
const c = s[i++];
|
|
10421
|
+
out += c;
|
|
10422
|
+
if (c === '\n' || c === '\r') break;
|
|
10423
|
+
}
|
|
10424
|
+
continue;
|
|
10425
|
+
}
|
|
10426
|
+
if (s.startsWith('_:', i)) {
|
|
10427
|
+
let j = i + 2;
|
|
10428
|
+
while (j < s.length && !/\s/.test(s[j]) && !'{}[](),;.'.includes(s[j])) j += 1;
|
|
10429
|
+
const label = s.slice(i + 2, j);
|
|
10430
|
+
if (label) {
|
|
10431
|
+
out += prefix + label.replace(/[^A-Za-z0-9_]/g, '_');
|
|
10432
|
+
i = j;
|
|
10433
|
+
continue;
|
|
10434
|
+
}
|
|
10435
|
+
}
|
|
10436
|
+
out += ch;
|
|
10437
|
+
i += 1;
|
|
10438
|
+
}
|
|
10439
|
+
return out;
|
|
10440
|
+
}
|
|
10441
|
+
|
|
10442
|
+
function findMessageDirectiveAt(s, at) {
|
|
10443
|
+
if (startsWordAt(s, 'MESSAGE', at)) return { start: at, end: at + 'MESSAGE'.length };
|
|
10444
|
+
if (s.slice(at, at + 8).toLowerCase() === '@message' && !isWordChar(s[at + 8])) {
|
|
10445
|
+
let end = at + 8;
|
|
10446
|
+
end = skipWsAndComments(s, end);
|
|
10447
|
+
if (s[end] === '.') end += 1;
|
|
10448
|
+
return { start: at, end };
|
|
10449
|
+
}
|
|
10450
|
+
return null;
|
|
10451
|
+
}
|
|
10452
|
+
|
|
10453
|
+
function splitRdfMessageLog(s) {
|
|
10454
|
+
const chunks = [];
|
|
10455
|
+
let i = 0;
|
|
10456
|
+
let start = 0;
|
|
10457
|
+
let braceDepth = 0;
|
|
10458
|
+
let bracketDepth = 0;
|
|
10459
|
+
let parenDepth = 0;
|
|
10460
|
+
let statementStart = true;
|
|
10461
|
+
let sawDelimiter = false;
|
|
10462
|
+
|
|
10463
|
+
while (i < s.length) {
|
|
10464
|
+
const ch = s[i];
|
|
10465
|
+
if (ch === '"' || ch === "'") {
|
|
10466
|
+
i = readStringAt(s, i).end;
|
|
10467
|
+
statementStart = false;
|
|
10468
|
+
continue;
|
|
10469
|
+
}
|
|
10470
|
+
if (ch === '<' && !s.startsWith('<<', i)) {
|
|
10471
|
+
i = readIriAt(s, i).end;
|
|
10472
|
+
statementStart = false;
|
|
10473
|
+
continue;
|
|
10474
|
+
}
|
|
10475
|
+
if (ch === '#') {
|
|
10476
|
+
while (i < s.length && s[i] !== '\n' && s[i] !== '\r') i += 1;
|
|
10477
|
+
statementStart = true;
|
|
10478
|
+
continue;
|
|
10479
|
+
}
|
|
10480
|
+
if (statementStart && braceDepth === 0 && bracketDepth === 0 && parenDepth === 0) {
|
|
10481
|
+
const termStart = skipWsAndComments(s, i);
|
|
10482
|
+
const msg = findMessageDirectiveAt(s, termStart);
|
|
10483
|
+
if (msg) {
|
|
10484
|
+
chunks.push(s.slice(start, termStart));
|
|
10485
|
+
start = msg.end;
|
|
10486
|
+
i = msg.end;
|
|
10487
|
+
statementStart = true;
|
|
10488
|
+
sawDelimiter = true;
|
|
10489
|
+
continue;
|
|
10490
|
+
}
|
|
10491
|
+
if (termStart !== i) {
|
|
10492
|
+
i = termStart;
|
|
10493
|
+
continue;
|
|
10494
|
+
}
|
|
10495
|
+
}
|
|
10496
|
+
if (ch === '{') braceDepth += 1;
|
|
10497
|
+
else if (ch === '}' && braceDepth > 0) braceDepth -= 1;
|
|
10498
|
+
else if (ch === '[') bracketDepth += 1;
|
|
10499
|
+
else if (ch === ']' && bracketDepth > 0) bracketDepth -= 1;
|
|
10500
|
+
else if (ch === '(') parenDepth += 1;
|
|
10501
|
+
else if (ch === ')' && parenDepth > 0) parenDepth -= 1;
|
|
10502
|
+
|
|
10503
|
+
if (ch === '.' && braceDepth === 0 && bracketDepth === 0 && parenDepth === 0) statementStart = true;
|
|
10504
|
+
else if (ch === '\n' || ch === '\r') statementStart = true;
|
|
10505
|
+
else if (!/\s/.test(ch)) statementStart = false;
|
|
10506
|
+
i += 1;
|
|
10507
|
+
}
|
|
10508
|
+
|
|
10509
|
+
const tail = s.slice(start);
|
|
10510
|
+
if (!sawDelimiter || !isOnlyWhitespaceAndComments(tail)) chunks.push(tail);
|
|
10511
|
+
return chunks;
|
|
10512
|
+
}
|
|
10513
|
+
|
|
10514
|
+
function normalizeMessageChunk(chunk, messageIndex) {
|
|
10515
|
+
let body = String(chunk || '');
|
|
10516
|
+
if (hasTripleTerms || body.includes('<<')) body = convertTripleTerms(body);
|
|
10517
|
+
if (hasAnnotationSyntax || /(?:^|\s)~\s*(?:<|_:[A-Za-z]|[A-Za-z][A-Za-z0-9_-]*:|\{\|)|\{\|/.test(body)) {
|
|
10518
|
+
body = convertAnnotations(body);
|
|
10519
|
+
}
|
|
10520
|
+
body = normalizeNamedGraphs(body);
|
|
10521
|
+
body = rewriteMessageBlankLabels(body, messageIndex);
|
|
10522
|
+
return body.trim();
|
|
10523
|
+
}
|
|
10524
|
+
|
|
10525
|
+
function messageChunkHasRdf(body) {
|
|
10526
|
+
return !isOnlyWhitespaceAndComments(stripDirectivesAndCommentsForEmptiness(body));
|
|
10527
|
+
}
|
|
10528
|
+
|
|
10529
|
+
function normalizeRdfMessageLog(s) {
|
|
10530
|
+
const withoutVersion = stripVersionDirectives(s);
|
|
10531
|
+
const chunks = splitRdfMessageLog(withoutVersion);
|
|
10532
|
+
const hash = simpleHashText(s);
|
|
10533
|
+
const base = `urn:eyeling:message-log:${hash}`;
|
|
10534
|
+
const stream = `<${base}#stream>`;
|
|
10535
|
+
const envelopeIris = chunks.map((unused, idx) => `<${base}#m${String(idx + 1).padStart(3, '0')}>`);
|
|
10536
|
+
const payloadIris = chunks.map((unused, idx) => `<${base}#m${String(idx + 1).padStart(3, '0')}/payload>`);
|
|
10537
|
+
const out = [];
|
|
10538
|
+
|
|
10539
|
+
out.push(`${stream} ${RDF_TYPE_IRI} ${EYMSG.RDFMessageStream} .`);
|
|
10540
|
+
out.push(`${stream} ${EYMSG.messageCount} "${chunks.length}"^^${XSD_INTEGER_IRI} .`);
|
|
10541
|
+
if (envelopeIris.length) {
|
|
10542
|
+
out.push(`${stream} ${EYMSG.orderedEnvelopes} (${envelopeIris.join(' ')}) .`);
|
|
10543
|
+
out.push(`${stream} ${EYMSG.firstEnvelope} ${envelopeIris[0]} .`);
|
|
10544
|
+
out.push(`${stream} ${EYMSG.lastEnvelope} ${envelopeIris[envelopeIris.length - 1]} .`);
|
|
10545
|
+
}
|
|
10546
|
+
|
|
10547
|
+
for (let idx = 0; idx < chunks.length; idx += 1) {
|
|
10548
|
+
const n = idx + 1;
|
|
10549
|
+
const envelope = envelopeIris[idx];
|
|
10550
|
+
const payload = payloadIris[idx];
|
|
10551
|
+
const body = normalizeMessageChunk(chunks[idx], n);
|
|
10552
|
+
const hasBody = messageChunkHasRdf(body);
|
|
10553
|
+
|
|
10554
|
+
out.push(`${stream} ${EYMSG.envelope} ${envelope} .`);
|
|
10555
|
+
out.push(`${envelope} ${RDF_TYPE_IRI} ${EYMSG.MessageEnvelope} .`);
|
|
10556
|
+
out.push(`${envelope} ${EYMSG.offset} "${n}"^^${XSD_INTEGER_IRI} .`);
|
|
10557
|
+
out.push(`${envelope} ${EYMSG.payloadKind} ${hasBody ? EYMSG.nonEmpty : EYMSG.empty} .`);
|
|
10558
|
+
if (idx + 1 < envelopeIris.length) out.push(`${envelope} ${EYMSG.nextEnvelope} ${envelopeIris[idx + 1]} .`);
|
|
10559
|
+
if (hasBody) {
|
|
10560
|
+
out.push(`${envelope} ${EYMSG.payloadGraph} ${payload} .`);
|
|
10561
|
+
out.push(`${payload} ${LOG_NAME_OF_IRI} {`);
|
|
10562
|
+
out.push(body);
|
|
10563
|
+
out.push(`} .`);
|
|
10564
|
+
}
|
|
10565
|
+
}
|
|
10566
|
+
|
|
10567
|
+
return out.join('\n') + '\n';
|
|
10568
|
+
}
|
|
10569
|
+
|
|
10570
|
+
if (hasMessageVersionDirective) return normalizeRdfMessageLog(text);
|
|
10571
|
+
|
|
10293
10572
|
if (hasTripleTerms) text = convertTripleTerms(text);
|
|
10294
10573
|
if (hasAnnotationSyntax) text = convertAnnotations(text);
|
|
10295
|
-
if (hasVersionDirective) text = stripVersionDirectives(text);
|
|
10296
|
-
if (hasVersionDirective || hasNamedGraphCandidate) text = normalizeNamedGraphs(text);
|
|
10574
|
+
if (hasVersionDirective || hasMessageVersionDirective) text = stripVersionDirectives(text);
|
|
10575
|
+
if (hasVersionDirective || hasMessageVersionDirective || hasNamedGraphCandidate) text = normalizeNamedGraphs(text);
|
|
10297
10576
|
return text;
|
|
10298
10577
|
}
|
|
10299
10578
|
|
|
@@ -1,50 +1,36 @@
|
|
|
1
|
-
#
|
|
2
|
-
# RDF Message Flow
|
|
3
|
-
#
|
|
1
|
+
# =============================
|
|
2
|
+
# RDF Message Flow message log
|
|
3
|
+
# =============================
|
|
4
4
|
#
|
|
5
5
|
# This file is the data half of the runnable example:
|
|
6
6
|
#
|
|
7
7
|
# eyeling -r examples/rdf-message-flow.n3 examples/input/rdf-message-flow.trig
|
|
8
8
|
#
|
|
9
|
-
# It is
|
|
10
|
-
# VERSION "1.2-messages" and MESSAGE delimiters
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# graph below contains example-local envelope records (:m001 ... :m005) and each
|
|
15
|
-
# non-empty message payload is placed in its own named graph (in:payload001 ...
|
|
16
|
-
# in:payload005).
|
|
9
|
+
# It is a parser-level RDF Message Log using the RDF 1.2 Messages draft syntax:
|
|
10
|
+
# VERSION "1.2-messages" selects message-log parsing and MESSAGE delimiters
|
|
11
|
+
# separate the ordered RDF Messages. Eyeling replays this log internally by
|
|
12
|
+
# materializing message envelopes and one payload graph per non-empty message.
|
|
13
|
+
# The sidecar itself does not contain application-level envelope facts.
|
|
17
14
|
#
|
|
18
|
-
# The
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# third envelope is an empty heartbeat, which is valid because RDF Messages may
|
|
22
|
-
# be empty. Blank node labels are kept unique because this sidecar is ordinary
|
|
23
|
-
# TriG; a true RDF Message Log parser would reset blank-node scope per MESSAGE.
|
|
15
|
+
# The third message is deliberately empty. The same blank node label _:obs is
|
|
16
|
+
# also reused in several messages; an RDF Message Log parser scopes blank node
|
|
17
|
+
# identifiers to each message, so those labels do not collide.
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@prefix string: <http://www.w3.org/2000/10/swap/string#> .
|
|
34
|
-
@prefix see: <https://example.org/see#> .
|
|
35
|
-
@prefix in: <https://example.org/see/input#> .
|
|
19
|
+
VERSION "1.2-messages"
|
|
20
|
+
PREFIX : <https://eyereasoner.github.io/eyeling/examples/rdf-message-flow#>
|
|
21
|
+
PREFIX flow: <https://eyereasoner.github.io/eyeling/examples/rdf-message-flow/vocab#>
|
|
22
|
+
PREFIX prov: <http://www.w3.org/ns/prov#>
|
|
23
|
+
PREFIX sosa: <http://www.w3.org/ns/sosa/>
|
|
24
|
+
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
|
|
25
|
+
PREFIX see: <https://example.org/see#>
|
|
26
|
+
PREFIX in: <https://example.org/see/input#>
|
|
36
27
|
|
|
37
|
-
|
|
38
|
-
:temperatureFlow flow:
|
|
39
|
-
:
|
|
40
|
-
:
|
|
41
|
-
:
|
|
42
|
-
:
|
|
43
|
-
:temperatureFlow flow:envelope :m005 .
|
|
44
|
-
:temperatureFlow :producer :thermometerA .
|
|
45
|
-
:temperatureFlow :consumer :flowProcessor .
|
|
46
|
-
:temperatureFlow :pipeline (:ingest :validate :interpret :route :sink) .
|
|
47
|
-
:temperatureFlow :highThreshold 26 .
|
|
28
|
+
# Message 1: stream context plus first observation.
|
|
29
|
+
:temperatureFlow a flow:RDFMessageStream ;
|
|
30
|
+
:producer :thermometerA ;
|
|
31
|
+
:consumer :flowProcessor ;
|
|
32
|
+
:pipeline (:ingest :validate :interpret :route :sink) ;
|
|
33
|
+
:highThreshold 26 .
|
|
48
34
|
|
|
49
35
|
:thermometerA a flow:StreamProducer .
|
|
50
36
|
:flowProcessor a flow:StreamConsumer .
|
|
@@ -52,77 +38,42 @@
|
|
|
52
38
|
:alertSink a flow:StreamConsumer .
|
|
53
39
|
:heartbeatSink a flow:StreamConsumer .
|
|
54
40
|
|
|
55
|
-
:
|
|
56
|
-
:
|
|
57
|
-
:
|
|
58
|
-
:
|
|
59
|
-
:m001 prov:generatedAtTime "2026-05-12T18:20:00Z"^^xsd:dateTime .
|
|
60
|
-
:m001 flow:payloadKind :observation .
|
|
61
|
-
:m001 flow:expectedResult 21 .
|
|
62
|
-
:m001 flow:payloadGraph in:payload001 .
|
|
41
|
+
_:obs a sosa:Observation ;
|
|
42
|
+
sosa:madeBySensor :thermometerA ;
|
|
43
|
+
sosa:resultTime "2026-05-12T18:20:00Z"^^xsd:dateTime ;
|
|
44
|
+
sosa:hasSimpleResult 21 .
|
|
63
45
|
|
|
64
|
-
:
|
|
65
|
-
:
|
|
66
|
-
:
|
|
67
|
-
:
|
|
68
|
-
:
|
|
69
|
-
:
|
|
70
|
-
:m002 flow:payloadGraph in:payload002 .
|
|
46
|
+
in:run a see:InputDataset ;
|
|
47
|
+
see:name "rdf_message_flow" ;
|
|
48
|
+
see:title "RDF Message Flow" ;
|
|
49
|
+
see:sourceFile "examples/rdf-message-flow.n3" ;
|
|
50
|
+
see:description "A true RDF 1.2 Message Log sidecar. Eyeling parses VERSION 1.2-messages and MESSAGE delimiters into replayable internal message envelopes." ;
|
|
51
|
+
see:compiler "Eyeling RDF Message Log input" .
|
|
71
52
|
|
|
72
|
-
|
|
73
|
-
:m003 flow:offset 3 .
|
|
74
|
-
:m003 flow:nextEnvelope :m004 .
|
|
75
|
-
:m003 prov:generatedAtTime "2026-05-12T18:22:00Z"^^xsd:dateTime .
|
|
76
|
-
:m003 flow:payloadKind :heartbeat .
|
|
53
|
+
MESSAGE
|
|
77
54
|
|
|
78
|
-
|
|
79
|
-
:
|
|
80
|
-
:
|
|
81
|
-
:
|
|
82
|
-
:
|
|
83
|
-
:m004 flow:expectedResult 28 .
|
|
84
|
-
:m004 flow:payloadGraph in:payload004 .
|
|
55
|
+
# Message 2: normal observation.
|
|
56
|
+
_:obs a sosa:Observation ;
|
|
57
|
+
sosa:madeBySensor :thermometerA ;
|
|
58
|
+
sosa:resultTime "2026-05-12T18:21:00Z"^^xsd:dateTime ;
|
|
59
|
+
sosa:hasSimpleResult 22 .
|
|
85
60
|
|
|
86
|
-
|
|
87
|
-
:m005 flow:offset 5 .
|
|
88
|
-
:m005 prov:generatedAtTime "2026-05-12T18:24:00Z"^^xsd:dateTime .
|
|
89
|
-
:m005 flow:payloadKind :observation .
|
|
90
|
-
:m005 flow:expectedResult 29 .
|
|
91
|
-
:m005 flow:payloadGraph in:payload005 .
|
|
61
|
+
MESSAGE
|
|
92
62
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
_:m001b0 sosa:resultTime "2026-05-12T18:20:00Z"^^xsd:dateTime .
|
|
97
|
-
_:m001b0 sosa:hasSimpleResult 21 .
|
|
98
|
-
}
|
|
63
|
+
# Message 3: empty heartbeat. The next MESSAGE delimiter finalizes this empty
|
|
64
|
+
# message and opens message 4.
|
|
65
|
+
MESSAGE
|
|
99
66
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
67
|
+
# Message 4: hot observation.
|
|
68
|
+
_:obs a sosa:Observation ;
|
|
69
|
+
sosa:madeBySensor :thermometerA ;
|
|
70
|
+
sosa:resultTime "2026-05-12T18:23:00Z"^^xsd:dateTime ;
|
|
71
|
+
sosa:hasSimpleResult 28 .
|
|
106
72
|
|
|
107
|
-
|
|
108
|
-
_:m004b0 a sosa:Observation .
|
|
109
|
-
_:m004b0 sosa:madeBySensor :thermometerA .
|
|
110
|
-
_:m004b0 sosa:resultTime "2026-05-12T18:23:00Z"^^xsd:dateTime .
|
|
111
|
-
_:m004b0 sosa:hasSimpleResult 28 .
|
|
112
|
-
}
|
|
73
|
+
MESSAGE
|
|
113
74
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
in:metadata {
|
|
122
|
-
in:run a see:InputDataset .
|
|
123
|
-
in:run see:name "rdf_message_flow" .
|
|
124
|
-
in:run see:title "RDF Message Flow" .
|
|
125
|
-
in:run see:sourceFile "examples/rdf-message-flow.n3" .
|
|
126
|
-
in:run see:description "A single Eyeling example split across an N3 rule file and a TriG input sidecar. It models an ordered RDF Message Stream using application-level envelopes and named payload graphs so that each payload can be interpreted atomically without merging all message contents into one graph." .
|
|
127
|
-
in:run see:compiler "Eyeling RDF/TriG input sidecar" .
|
|
128
|
-
}
|
|
75
|
+
# Message 5: hot observation. EOF finalizes the message.
|
|
76
|
+
_:obs a sosa:Observation ;
|
|
77
|
+
sosa:madeBySensor :thermometerA ;
|
|
78
|
+
sosa:resultTime "2026-05-12T18:24:00Z"^^xsd:dateTime ;
|
|
79
|
+
sosa:hasSimpleResult 29 .
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
## Source files
|
|
4
4
|
|
|
5
5
|
- [N3 rules](../rdf-message-flow.n3)
|
|
6
|
-
- [Input
|
|
6
|
+
- [Input RDF Message Log](../input/rdf-message-flow.trig)
|
|
7
7
|
|
|
8
8
|
## Answer
|
|
9
|
-
Continuous RDF Message flow accepted: 5 ordered message envelopes moved through the ingest → validate → interpret → route → sink pipeline. The threshold was 26, so results 21 and 22 were archived, the heartbeat kept the stream alive, and results 28 and 29 were emitted as alerts.
|
|
9
|
+
Continuous RDF Message flow accepted: 5 ordered parser-replayed message envelopes moved through the ingest → validate → interpret → route → sink pipeline. The threshold was 26, so results 21 and 22 were archived, the empty heartbeat kept the stream alive, and results 28 and 29 were emitted as alerts.
|
|
10
10
|
|
|
11
11
|
## Explanation
|
|
12
|
-
The input is a
|
|
12
|
+
The input sidecar is now a true RDF Message Log using VERSION \"1.2-messages\" and MESSAGE delimiters. Eyeling parses the log internally into an eymsg: replay view with ordered envelopes, offsets, next-envelope links, and one payload graph per non-empty message. Only the first replayed envelope starts at ingress; each envelope must reach :sink before the continuous-flow rule releases its eymsg:nextEnvelope. Observation payloads are inspected with log:includes inside their own message formula, and the delimiter-only third message is replayed as an empty heartbeat. This keeps message boundaries in the parser/runtime instead of modeling them by hand in the TriG sidecar.
|