eyeleng 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -0
- package/dist/browser/eyeleng.browser.js +2462 -1085
- package/examples/cat-koko.srl +52 -0
- package/examples/graph-term-emulation.srl +83 -0
- package/examples/output/cat-koko.trig +3 -0
- package/examples/output/collection-nesting.trig +1 -1
- package/examples/output/graph-term-emulation.trig +11 -0
- package/examples/output/rdf-messages.trig +3 -0
- package/examples/rdf-messages.srl +15 -0
- package/examples/rdf-messages.trig +12 -0
- package/eyeleng.js +2466 -1083
- package/package.json +7 -2
- package/playground.html +1 -1
- package/src/api.js +4 -0
- package/src/cli.js +6 -0
- package/src/parser.js +95 -1
- package/src/rdfEntailment.js +571 -0
- package/src/rdfManifest.js +724 -0
- package/src/rdfMessages.js +321 -0
- package/src/rdfSyntax.js +955 -0
- package/test/api.test.js +63 -0
- package/test/harness.js +38 -10
- package/test/run.js +6 -3
- package/test/shacl12-rules.test.js +14 -13
- package/test/w3c-rdf.test.js +202 -0
- package/tools/browser-bundle.js +0 -0
- package/tools/bundle.js +0 -0
- package/tools/w3c-rdf.js +36 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { parseRdfDocument } = require('./rdfSyntax.js');
|
|
4
|
+
const {
|
|
5
|
+
iri,
|
|
6
|
+
blankNode,
|
|
7
|
+
literal,
|
|
8
|
+
tripleTerm,
|
|
9
|
+
RDF_TYPE,
|
|
10
|
+
RDF_FIRST,
|
|
11
|
+
RDF_REST,
|
|
12
|
+
RDF_NIL,
|
|
13
|
+
XSD_INTEGER,
|
|
14
|
+
} = require('./term.js');
|
|
15
|
+
|
|
16
|
+
const RDF_MESSAGE_VERSION_RE = /^\s*(?:@version|VERSION)\s+(["'])(?:1\.1|1\.2|1\.2-basic)-messages\1\s*\.?\s*(?:#.*)?$/im;
|
|
17
|
+
const RDF_MESSAGE_VERSION_LINE_RE = /^\s*(?:@version|VERSION)\s+(["'])(?:1\.1|1\.2|1\.2-basic)-messages\1\s*\.?\s*(?:#.*)?$/i;
|
|
18
|
+
const RDF_DIRECTIVE_LINE_RE = /^\s*(?:@?(?:prefix|base)\b|PREFIX\b|BASE\b)/i;
|
|
19
|
+
const RDF_MESSAGE_DELIMITER_LINE_RE = /^\s*(?:MESSAGE\b|@message\s*\.?)\s*(?:#.*)?$/i;
|
|
20
|
+
|
|
21
|
+
const EYMSG_NS = 'https://eyereasoner.github.io/eyeling/vocab/message#';
|
|
22
|
+
const LOG_NS = 'http://www.w3.org/2000/10/swap/log#';
|
|
23
|
+
const EYMSG = Object.freeze({
|
|
24
|
+
RDFMessageStream: `${EYMSG_NS}RDFMessageStream`,
|
|
25
|
+
MessageEnvelope: `${EYMSG_NS}MessageEnvelope`,
|
|
26
|
+
envelope: `${EYMSG_NS}envelope`,
|
|
27
|
+
firstEnvelope: `${EYMSG_NS}firstEnvelope`,
|
|
28
|
+
lastEnvelope: `${EYMSG_NS}lastEnvelope`,
|
|
29
|
+
orderedEnvelopes: `${EYMSG_NS}orderedEnvelopes`,
|
|
30
|
+
messageCount: `${EYMSG_NS}messageCount`,
|
|
31
|
+
offset: `${EYMSG_NS}offset`,
|
|
32
|
+
nextEnvelope: `${EYMSG_NS}nextEnvelope`,
|
|
33
|
+
payloadGraph: `${EYMSG_NS}payloadGraph`,
|
|
34
|
+
payloadKind: `${EYMSG_NS}payloadKind`,
|
|
35
|
+
payloadTriple: `${EYMSG_NS}payloadTriple`,
|
|
36
|
+
tripleCount: `${EYMSG_NS}tripleCount`,
|
|
37
|
+
empty: `${EYMSG_NS}empty`,
|
|
38
|
+
nonEmpty: `${EYMSG_NS}nonEmpty`,
|
|
39
|
+
});
|
|
40
|
+
const LOG_NAME_OF = `${LOG_NS}nameOf`;
|
|
41
|
+
|
|
42
|
+
function simpleHashText(value) {
|
|
43
|
+
let h = 0x811c9dc5;
|
|
44
|
+
const text = String(value || '');
|
|
45
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
46
|
+
h ^= text.charCodeAt(i);
|
|
47
|
+
h = Math.imul(h, 0x01000193) >>> 0;
|
|
48
|
+
}
|
|
49
|
+
return h.toString(16).padStart(8, '0');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function looksLikeRdfMessageLog(source, options = {}) {
|
|
53
|
+
return !!options.rdfMessages || RDF_MESSAGE_VERSION_RE.test(String(source || ''));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function splitPreservingLineEndings(text) {
|
|
57
|
+
return String(text || '').match(/.*(?:\r\n|\n|\r)|.+$/g) || [];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isOnlyWhitespaceAndComments(text) {
|
|
61
|
+
return splitPreservingLineEndings(text).every((line) => {
|
|
62
|
+
const hash = line.indexOf('#');
|
|
63
|
+
const body = hash >= 0 ? line.slice(0, hash) : line;
|
|
64
|
+
return body.trim() === '';
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function stripMessageVersionLines(text) {
|
|
69
|
+
return splitPreservingLineEndings(text).filter((line) => !RDF_MESSAGE_VERSION_LINE_RE.test(line)).join('');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function stripDirectiveLines(text) {
|
|
73
|
+
return splitPreservingLineEndings(text).filter((line) => !RDF_DIRECTIVE_LINE_RE.test(line) && !RDF_MESSAGE_VERSION_LINE_RE.test(line)).join('');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function collectDirectiveLines(text) {
|
|
77
|
+
const seen = new Set();
|
|
78
|
+
const out = [];
|
|
79
|
+
for (const line of splitPreservingLineEndings(text)) {
|
|
80
|
+
if (!RDF_DIRECTIVE_LINE_RE.test(line)) continue;
|
|
81
|
+
const key = line.trim();
|
|
82
|
+
if (!key || seen.has(key)) continue;
|
|
83
|
+
seen.add(key);
|
|
84
|
+
out.push(line.endsWith('\n') || line.endsWith('\r') ? line : `${line}\n`);
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function readStringAt(source, index) {
|
|
90
|
+
const quote = source[index];
|
|
91
|
+
const long = source.startsWith(quote.repeat(3), index);
|
|
92
|
+
let i = index + (long ? 3 : 1);
|
|
93
|
+
while (i < source.length) {
|
|
94
|
+
if (source[i] === '\\') { i += 2; continue; }
|
|
95
|
+
if (long && source.startsWith(quote.repeat(3), i)) return { end: i + 3 };
|
|
96
|
+
if (!long && source[i] === quote) return { end: i + 1 };
|
|
97
|
+
i += 1;
|
|
98
|
+
}
|
|
99
|
+
return { end: source.length };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function readIriAt(source, index) {
|
|
103
|
+
let i = index + 1;
|
|
104
|
+
while (i < source.length) {
|
|
105
|
+
if (source[i] === '\\') { i += 2; continue; }
|
|
106
|
+
if (source[i] === '>') return { end: i + 1 };
|
|
107
|
+
i += 1;
|
|
108
|
+
}
|
|
109
|
+
return { end: source.length };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function skipWsAndComments(source, index) {
|
|
113
|
+
let i = index;
|
|
114
|
+
while (i < source.length) {
|
|
115
|
+
if (/\s/.test(source[i])) { i += 1; continue; }
|
|
116
|
+
if (source[i] === '#') {
|
|
117
|
+
while (i < source.length && source[i] !== '\n' && source[i] !== '\r') i += 1;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
return i;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function isWordChar(ch) { return !!ch && /[A-Za-z0-9_\-]/.test(ch); }
|
|
126
|
+
function startsWordAt(source, word, index) {
|
|
127
|
+
return source.slice(index, index + word.length).toUpperCase() === word && !isWordChar(source[index - 1]) && !isWordChar(source[index + word.length]);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function findMessageDirectiveAt(source, index) {
|
|
131
|
+
if (startsWordAt(source, 'MESSAGE', index)) return { start: index, end: index + 'MESSAGE'.length };
|
|
132
|
+
if (source.slice(index, index + 8).toLowerCase() === '@message' && !isWordChar(source[index + 8])) {
|
|
133
|
+
let end = index + 8;
|
|
134
|
+
end = skipWsAndComments(source, end);
|
|
135
|
+
if (source[end] === '.') end += 1;
|
|
136
|
+
return { start: index, end };
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function splitRdfMessageLog(source) {
|
|
142
|
+
const text = stripMessageVersionLines(source);
|
|
143
|
+
const chunks = [];
|
|
144
|
+
let i = 0;
|
|
145
|
+
let start = 0;
|
|
146
|
+
let braceDepth = 0;
|
|
147
|
+
let bracketDepth = 0;
|
|
148
|
+
let parenDepth = 0;
|
|
149
|
+
let statementStart = true;
|
|
150
|
+
let sawDelimiter = false;
|
|
151
|
+
|
|
152
|
+
while (i < text.length) {
|
|
153
|
+
const ch = text[i];
|
|
154
|
+
if (ch === '"' || ch === "'") { i = readStringAt(text, i).end; statementStart = false; continue; }
|
|
155
|
+
if (ch === '<' && !text.startsWith('<<', i)) { i = readIriAt(text, i).end; statementStart = false; continue; }
|
|
156
|
+
if (ch === '#') { while (i < text.length && text[i] !== '\n' && text[i] !== '\r') i += 1; statementStart = true; continue; }
|
|
157
|
+
|
|
158
|
+
if (statementStart && braceDepth === 0 && bracketDepth === 0 && parenDepth === 0) {
|
|
159
|
+
const termStart = skipWsAndComments(text, i);
|
|
160
|
+
const directive = findMessageDirectiveAt(text, termStart);
|
|
161
|
+
if (directive) {
|
|
162
|
+
chunks.push(text.slice(start, termStart));
|
|
163
|
+
start = directive.end;
|
|
164
|
+
i = directive.end;
|
|
165
|
+
statementStart = true;
|
|
166
|
+
sawDelimiter = true;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (termStart !== i) { i = termStart; continue; }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (ch === '{') braceDepth += 1;
|
|
173
|
+
else if (ch === '}' && braceDepth > 0) braceDepth -= 1;
|
|
174
|
+
else if (ch === '[') bracketDepth += 1;
|
|
175
|
+
else if (ch === ']' && bracketDepth > 0) bracketDepth -= 1;
|
|
176
|
+
else if (ch === '(') parenDepth += 1;
|
|
177
|
+
else if (ch === ')' && parenDepth > 0) parenDepth -= 1;
|
|
178
|
+
|
|
179
|
+
if (ch === '.' && braceDepth === 0 && bracketDepth === 0 && parenDepth === 0) statementStart = true;
|
|
180
|
+
else if (ch === '\n' || ch === '\r') statementStart = true;
|
|
181
|
+
else if (!/\s/.test(ch)) statementStart = false;
|
|
182
|
+
i += 1;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const tail = text.slice(start);
|
|
186
|
+
if (!sawDelimiter || !isOnlyWhitespaceAndComments(tail)) chunks.push(tail);
|
|
187
|
+
return chunks;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function rewriteMessageBlankLabels(source, messageIndex) {
|
|
191
|
+
const prefix = `msg${String(messageIndex).padStart(3, '0')}_`;
|
|
192
|
+
let out = '';
|
|
193
|
+
let i = 0;
|
|
194
|
+
while (i < source.length) {
|
|
195
|
+
const ch = source[i];
|
|
196
|
+
if (ch === '"' || ch === "'") {
|
|
197
|
+
const end = readStringAt(source, i).end;
|
|
198
|
+
out += source.slice(i, end);
|
|
199
|
+
i = end;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (ch === '<' && !source.startsWith('<<', i)) {
|
|
203
|
+
const end = readIriAt(source, i).end;
|
|
204
|
+
out += source.slice(i, end);
|
|
205
|
+
i = end;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (ch === '#') {
|
|
209
|
+
while (i < source.length) {
|
|
210
|
+
const c = source[i++]; out += c;
|
|
211
|
+
if (c === '\n' || c === '\r') break;
|
|
212
|
+
}
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (source.startsWith('_:', i)) {
|
|
216
|
+
let j = i + 2;
|
|
217
|
+
while (j < source.length && !/\s/.test(source[j]) && !'{}[](),;.<>'.includes(source[j])) j += 1;
|
|
218
|
+
const label = source.slice(i + 2, j);
|
|
219
|
+
if (label) {
|
|
220
|
+
out += `_:${prefix}${label.replace(/[^A-Za-z0-9_\-]/g, '_')}`;
|
|
221
|
+
i = j;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
out += ch;
|
|
226
|
+
i += 1;
|
|
227
|
+
}
|
|
228
|
+
return out;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function messageChunkHasRdf(chunk) {
|
|
232
|
+
return !isOnlyWhitespaceAndComments(stripDirectiveLines(chunk));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function listTriples(headTerm, items, data, makeBlank) {
|
|
236
|
+
if (items.length === 0) return iri(RDF_NIL);
|
|
237
|
+
const cells = items.map(() => makeBlank());
|
|
238
|
+
for (let i = 0; i < cells.length; i += 1) {
|
|
239
|
+
data.push({ s: cells[i], p: iri(RDF_FIRST), o: items[i] });
|
|
240
|
+
data.push({ s: cells[i], p: iri(RDF_REST), o: i + 1 < cells.length ? cells[i + 1] : iri(RDF_NIL) });
|
|
241
|
+
}
|
|
242
|
+
return headTerm || cells[0];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function parseRdfMessageLog(source, options = {}) {
|
|
246
|
+
const text = String(source || '');
|
|
247
|
+
const directives = collectDirectiveLines(text);
|
|
248
|
+
const chunks = splitRdfMessageLog(text);
|
|
249
|
+
// Keep generated message-log IRIs stable across machines and checkout paths.
|
|
250
|
+
// The previous seed used baseIRI/filename, which made golden outputs depend on
|
|
251
|
+
// absolute local paths such as file:///home/.../examples/rdf-messages.trig.
|
|
252
|
+
// A caller that needs a location-specific identity can still pass
|
|
253
|
+
// options.messageBaseIRI explicitly.
|
|
254
|
+
const hash = simpleHashText(text);
|
|
255
|
+
const base = options.messageBaseIRI || `urn:eyeleng:message-log:${hash}`;
|
|
256
|
+
const stream = iri(`${base}#stream`);
|
|
257
|
+
const envelopes = chunks.map((unused, index) => iri(`${base}#m${String(index + 1).padStart(3, '0')}`));
|
|
258
|
+
const payloads = chunks.map((unused, index) => iri(`${base}#m${String(index + 1).padStart(3, '0')}/payload`));
|
|
259
|
+
const data = [];
|
|
260
|
+
let bnodeCounter = 0;
|
|
261
|
+
const makeBlank = () => blankNode(`msg${++bnodeCounter}`);
|
|
262
|
+
const prefixes = {
|
|
263
|
+
rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
|
|
264
|
+
xsd: 'http://www.w3.org/2001/XMLSchema#',
|
|
265
|
+
eymsg: EYMSG_NS,
|
|
266
|
+
log: LOG_NS,
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
data.push({ s: stream, p: iri(RDF_TYPE), o: iri(EYMSG.RDFMessageStream) });
|
|
270
|
+
data.push({ s: stream, p: iri(EYMSG.messageCount), o: literal(chunks.length, XSD_INTEGER) });
|
|
271
|
+
if (envelopes.length > 0) {
|
|
272
|
+
data.push({ s: stream, p: iri(EYMSG.orderedEnvelopes), o: listTriples(null, envelopes, data, makeBlank) });
|
|
273
|
+
data.push({ s: stream, p: iri(EYMSG.firstEnvelope), o: envelopes[0] });
|
|
274
|
+
data.push({ s: stream, p: iri(EYMSG.lastEnvelope), o: envelopes[envelopes.length - 1] });
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
for (let i = 0; i < chunks.length; i += 1) {
|
|
278
|
+
const envelope = envelopes[i];
|
|
279
|
+
const payload = payloads[i];
|
|
280
|
+
const rawChunk = chunks[i];
|
|
281
|
+
const chunk = rewriteMessageBlankLabels(rawChunk, i + 1);
|
|
282
|
+
const hasBody = messageChunkHasRdf(chunk);
|
|
283
|
+
const bodySource = `${directives.join('')}\n${stripDirectiveLines(chunk)}`;
|
|
284
|
+
const parsed = hasBody ? parseRdfDocument(bodySource, { ...options, filename: `${options.filename || '<message>'}#m${i + 1}`, baseIRI: options.baseIRI }) : { triples: [], prefixes: {} };
|
|
285
|
+
Object.assign(prefixes, parsed.prefixes || {});
|
|
286
|
+
const payloadTriples = parsed.triples || [];
|
|
287
|
+
const tripleTerms = payloadTriples.map((t) => tripleTerm(t.s, t.p, t.o));
|
|
288
|
+
|
|
289
|
+
data.push({ s: stream, p: iri(EYMSG.envelope), o: envelope });
|
|
290
|
+
data.push({ s: envelope, p: iri(RDF_TYPE), o: iri(EYMSG.MessageEnvelope) });
|
|
291
|
+
data.push({ s: envelope, p: iri(EYMSG.offset), o: literal(i + 1, XSD_INTEGER) });
|
|
292
|
+
data.push({ s: envelope, p: iri(EYMSG.payloadKind), o: iri(hasBody ? EYMSG.nonEmpty : EYMSG.empty) });
|
|
293
|
+
data.push({ s: envelope, p: iri(EYMSG.tripleCount), o: literal(payloadTriples.length, XSD_INTEGER) });
|
|
294
|
+
if (i + 1 < envelopes.length) data.push({ s: envelope, p: iri(EYMSG.nextEnvelope), o: envelopes[i + 1] });
|
|
295
|
+
if (hasBody) {
|
|
296
|
+
data.push({ s: envelope, p: iri(EYMSG.payloadGraph), o: payload });
|
|
297
|
+
data.push({ s: payload, p: iri(LOG_NAME_OF), o: listTriples(null, tripleTerms, data, makeBlank) });
|
|
298
|
+
for (const term of tripleTerms) data.push({ s: payload, p: iri(EYMSG.payloadTriple), o: term });
|
|
299
|
+
if (options.includeMessageFacts) data.push(...payloadTriples);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
baseIRI: options.baseIRI || null,
|
|
305
|
+
version: '1.2-messages',
|
|
306
|
+
imports: [],
|
|
307
|
+
prefixes,
|
|
308
|
+
data,
|
|
309
|
+
rules: [],
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
module.exports = {
|
|
314
|
+
EYMSG_NS,
|
|
315
|
+
EYMSG,
|
|
316
|
+
LOG_NS,
|
|
317
|
+
LOG_NAME_OF,
|
|
318
|
+
looksLikeRdfMessageLog,
|
|
319
|
+
splitRdfMessageLog,
|
|
320
|
+
parseRdfMessageLog,
|
|
321
|
+
};
|