eyeling 1.24.24 → 1.24.26
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 +2 -0
- package/dist/browser/eyeling.browser.js +258 -17
- package/examples/input/rdf-message-flow.trig +87 -59
- package/examples/input/rdf-message-microgrid.trig +89 -0
- package/examples/input/rdf-messages.trig +61 -40
- package/examples/output/rdf-message-flow.md +2 -2
- package/examples/output/rdf-message-microgrid.md +14 -0
- package/examples/output/rdf-messages.md +4 -2
- package/examples/rdf-message-flow.n3 +84 -69
- package/examples/rdf-message-microgrid.n3 +141 -0
- package/examples/rdf-messages.n3 +72 -52
- package/eyeling.js +258 -17
- package/lib/builtins.js +106 -10
- package/lib/engine.js +4 -1
- package/lib/parser.js +14 -0
- package/lib/prelude.js +134 -6
- package/package.json +1 -1
- package/test/api.test.js +137 -0
package/README.md
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/eyeling)
|
|
4
4
|
[](https://doi.org/10.5281/zenodo.19068086)
|
|
5
5
|
|
|
6
|
+

|
|
7
|
+
|
|
6
8
|
A compact [Notation3 (N3)](https://notation3.org/) reasoner in **JavaScript**.
|
|
7
9
|
|
|
8
10
|
## Quick start
|
|
@@ -1691,15 +1691,85 @@ function __listElemsForBuiltin(listLike, facts) {
|
|
|
1691
1691
|
return null;
|
|
1692
1692
|
}
|
|
1693
1693
|
|
|
1694
|
-
function
|
|
1695
|
-
if (
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1694
|
+
function __pushBuiltinDeltaLimited(out, delta, maxResults) {
|
|
1695
|
+
if (delta === null) return false;
|
|
1696
|
+
out.push(delta);
|
|
1697
|
+
return typeof maxResults === 'number' && maxResults > 0 && out.length >= maxResults;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
function __collectListLikeTermsFromTerm(t, out, seen) {
|
|
1701
|
+
if (t instanceof ListTerm || t instanceof OpenListTerm) {
|
|
1702
|
+
const k = termFastKey(t);
|
|
1703
|
+
if (k === null || !seen.has(k)) {
|
|
1704
|
+
if (k !== null) seen.add(k);
|
|
1705
|
+
out.push(t);
|
|
1706
|
+
}
|
|
1707
|
+
if (t instanceof ListTerm) {
|
|
1708
|
+
for (const e of t.elems) __collectListLikeTermsFromTerm(e, out, seen);
|
|
1709
|
+
} else {
|
|
1710
|
+
for (const e of t.prefix) __collectListLikeTermsFromTerm(e, out, seen);
|
|
1711
|
+
}
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
if (t instanceof GraphTerm) {
|
|
1715
|
+
for (const tr of t.triples) {
|
|
1716
|
+
__collectListLikeTermsFromTerm(tr.s, out, seen);
|
|
1717
|
+
__collectListLikeTermsFromTerm(tr.p, out, seen);
|
|
1718
|
+
__collectListLikeTermsFromTerm(tr.o, out, seen);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
function __collectListLikeTermsFromFacts(facts) {
|
|
1724
|
+
const out = [];
|
|
1725
|
+
const seen = new Set();
|
|
1726
|
+
for (const tr of facts) {
|
|
1727
|
+
__collectListLikeTermsFromTerm(tr.s, out, seen);
|
|
1728
|
+
__collectListLikeTermsFromTerm(tr.o, out, seen);
|
|
1729
|
+
}
|
|
1730
|
+
return out;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
function evalListFirstLikeBuiltin(sTerm, oTerm, subst, facts, maxResults) {
|
|
1734
|
+
if (sTerm instanceof ListTerm) {
|
|
1735
|
+
if (!sTerm.elems.length) return [];
|
|
1736
|
+
const first = sTerm.elems[0];
|
|
1737
|
+
const s2 = unifyTerm(oTerm, first, subst);
|
|
1738
|
+
return s2 !== null ? [s2] : [];
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
// For a variable subject, enumerate already-existing collection terms.
|
|
1742
|
+
// This lets a rule body such as `_:x rdf:first 1; rdf:rest rdf:nil`
|
|
1743
|
+
// bind `_:x` to an N3 collection literal `(1)` used elsewhere as a term.
|
|
1744
|
+
// Also include ordinary rdf:first facts so the builtin path does not hide
|
|
1745
|
+
// RDF-serialized lists when the subject starts unbound.
|
|
1746
|
+
if (sTerm instanceof Var) {
|
|
1747
|
+
const out = [];
|
|
1748
|
+
|
|
1749
|
+
for (const listTerm of __collectListLikeTermsFromFacts(facts)) {
|
|
1750
|
+
if (!(listTerm instanceof ListTerm) || !listTerm.elems.length) continue;
|
|
1751
|
+
let s2 = unifyTerm(sTerm, listTerm, subst);
|
|
1752
|
+
if (s2 === null) continue;
|
|
1753
|
+
s2 = unifyTerm(oTerm, listTerm.elems[0], s2);
|
|
1754
|
+
if (__pushBuiltinDeltaLimited(out, s2, maxResults)) return out;
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
const RDF_FIRST = RDF_NS + 'first';
|
|
1758
|
+
for (const tr of facts) {
|
|
1759
|
+
if (!(tr.p instanceof Iri) || tr.p.value !== RDF_FIRST) continue;
|
|
1760
|
+
let s2 = unifyTerm(sTerm, tr.s, subst);
|
|
1761
|
+
if (s2 === null) continue;
|
|
1762
|
+
s2 = unifyTerm(oTerm, tr.o, s2);
|
|
1763
|
+
if (__pushBuiltinDeltaLimited(out, s2, maxResults)) return out;
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
return out;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
return [];
|
|
1700
1770
|
}
|
|
1701
1771
|
|
|
1702
|
-
function evalListRestLikeBuiltin(sTerm, oTerm, subst) {
|
|
1772
|
+
function evalListRestLikeBuiltin(sTerm, oTerm, subst, facts, maxResults) {
|
|
1703
1773
|
// Closed list: (a b c) -> (b c)
|
|
1704
1774
|
if (sTerm instanceof ListTerm) {
|
|
1705
1775
|
if (!sTerm.elems.length) return [];
|
|
@@ -1720,6 +1790,32 @@ function evalListRestLikeBuiltin(sTerm, oTerm, subst) {
|
|
|
1720
1790
|
return s2 !== null ? [s2] : [];
|
|
1721
1791
|
}
|
|
1722
1792
|
|
|
1793
|
+
// See evalListFirstLikeBuiltin(): if the collection subject is still a
|
|
1794
|
+
// variable, enumerate known list literals and ordinary rdf:rest facts.
|
|
1795
|
+
if (sTerm instanceof Var) {
|
|
1796
|
+
const out = [];
|
|
1797
|
+
|
|
1798
|
+
for (const listTerm of __collectListLikeTermsFromFacts(facts)) {
|
|
1799
|
+
if (!(listTerm instanceof ListTerm) || !listTerm.elems.length) continue;
|
|
1800
|
+
let s2 = unifyTerm(sTerm, listTerm, subst);
|
|
1801
|
+
if (s2 === null) continue;
|
|
1802
|
+
const rest = new ListTerm(listTerm.elems.slice(1));
|
|
1803
|
+
s2 = unifyTerm(oTerm, rest, s2);
|
|
1804
|
+
if (__pushBuiltinDeltaLimited(out, s2, maxResults)) return out;
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
const RDF_REST = RDF_NS + 'rest';
|
|
1808
|
+
for (const tr of facts) {
|
|
1809
|
+
if (!(tr.p instanceof Iri) || tr.p.value !== RDF_REST) continue;
|
|
1810
|
+
let s2 = unifyTerm(sTerm, tr.s, subst);
|
|
1811
|
+
if (s2 === null) continue;
|
|
1812
|
+
s2 = unifyTerm(oTerm, tr.o, s2);
|
|
1813
|
+
if (__pushBuiltinDeltaLimited(out, s2, maxResults)) return out;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
return out;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1723
1819
|
return [];
|
|
1724
1820
|
}
|
|
1725
1821
|
|
|
@@ -2859,14 +2955,14 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
2859
2955
|
return s2 !== null ? [s2] : [];
|
|
2860
2956
|
}
|
|
2861
2957
|
if (pv === RDF_NS + 'first') {
|
|
2862
|
-
return evalListFirstLikeBuiltin(g.s, g.o, subst);
|
|
2958
|
+
return evalListFirstLikeBuiltin(g.s, g.o, subst, facts, maxResults);
|
|
2863
2959
|
}
|
|
2864
2960
|
|
|
2865
2961
|
// list:rest and rdf:rest
|
|
2866
2962
|
// true iff $s is a (non-empty) list and $o is the rest (tail) of that list.
|
|
2867
2963
|
// Schema: $s+ list:rest $o-
|
|
2868
2964
|
if (pv === LIST_NS + 'rest') {
|
|
2869
|
-
if (g.s instanceof ListTerm || g.s instanceof OpenListTerm) return evalListRestLikeBuiltin(g.s, g.o, subst);
|
|
2965
|
+
if (g.s instanceof ListTerm || g.s instanceof OpenListTerm) return evalListRestLikeBuiltin(g.s, g.o, subst, facts, maxResults);
|
|
2870
2966
|
const xs = __listElemsForBuiltin(g.s, facts);
|
|
2871
2967
|
if (!xs || !xs.length) return [];
|
|
2872
2968
|
const rest = new ListTerm(xs.slice(1));
|
|
@@ -2874,7 +2970,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
2874
2970
|
return s2 !== null ? [s2] : [];
|
|
2875
2971
|
}
|
|
2876
2972
|
if (pv === RDF_NS + 'rest') {
|
|
2877
|
-
return evalListRestLikeBuiltin(g.s, g.o, subst);
|
|
2973
|
+
return evalListRestLikeBuiltin(g.s, g.o, subst, facts, maxResults);
|
|
2878
2974
|
}
|
|
2879
2975
|
|
|
2880
2976
|
// list:iterate
|
|
@@ -7918,7 +8014,10 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
|
|
|
7918
8014
|
const isRdfFirstOrRest = goalPredicateIri === RDF_NS + 'first' || goalPredicateIri === RDF_NS + 'rest';
|
|
7919
8015
|
const shouldTreatAsBuiltin =
|
|
7920
8016
|
isBuiltinPred(goal0.p) &&
|
|
7921
|
-
!(
|
|
8017
|
+
!(
|
|
8018
|
+
isRdfFirstOrRest &&
|
|
8019
|
+
!(goal0.s instanceof ListTerm || goal0.s instanceof OpenListTerm || goal0.s instanceof Var)
|
|
8020
|
+
);
|
|
7922
8021
|
|
|
7923
8022
|
if (shouldTreatAsBuiltin) {
|
|
7924
8023
|
const remaining = max - results.length;
|
|
@@ -11399,6 +11498,20 @@ class Parser {
|
|
|
11399
11498
|
if (this.peek().typ === 'Ident' && (this.peek().value || '') === 'a') {
|
|
11400
11499
|
this.next();
|
|
11401
11500
|
pred = internIri(RDF_NS + 'type');
|
|
11501
|
+
} else if (this.peek().typ === 'Ident' && (this.peek().value || '') === 'has') {
|
|
11502
|
+
// N3 syntactic sugar is also valid in predicate-object lists,
|
|
11503
|
+
// including blank node property lists: [ has :p :o ] means _:b :p :o.
|
|
11504
|
+
this.next();
|
|
11505
|
+
pred = this.parseTerm();
|
|
11506
|
+
} else if (this.peek().typ === 'Ident' && (this.peek().value || '') === 'is') {
|
|
11507
|
+
// N3 syntactic sugar: [ is :p of :s ] means :s :p _:b.
|
|
11508
|
+
this.next();
|
|
11509
|
+
pred = this.parseTerm();
|
|
11510
|
+
if (!(this.peek().typ === 'Ident' && (this.peek().value || '') === 'of')) {
|
|
11511
|
+
this.fail(`Expected 'of' after 'is <expr>', got ${this.peek().toString()}`);
|
|
11512
|
+
}
|
|
11513
|
+
this.next();
|
|
11514
|
+
invert = true;
|
|
11402
11515
|
} else if (this.peek().typ === 'OpPredInvert') {
|
|
11403
11516
|
this.next();
|
|
11404
11517
|
pred = this.parseTerm();
|
|
@@ -11703,14 +11816,142 @@ const STRING_NS = 'http://www.w3.org/2000/10/swap/string#';
|
|
|
11703
11816
|
const SKOLEM_NS = 'https://eyereasoner.github.io/.well-known/genid/';
|
|
11704
11817
|
const RDF_JSON_DT = RDF_NS + 'JSON';
|
|
11705
11818
|
|
|
11819
|
+
function parseUriReferenceForResolution(uri) {
|
|
11820
|
+
// RFC 3986 Appendix B-style component parser, with the scheme tightened to
|
|
11821
|
+
// the RFC scheme grammar. Capturing delimiter presence matters: `?` with an
|
|
11822
|
+
// empty query is defined, while no `?` means undefined.
|
|
11823
|
+
const m = /^(([A-Za-z][A-Za-z0-9+.-]*):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/u.exec(String(uri));
|
|
11824
|
+
if (!m) return null;
|
|
11825
|
+
return {
|
|
11826
|
+
scheme: m[2] !== undefined ? m[2] : undefined,
|
|
11827
|
+
authority: m[4] !== undefined ? m[4] : undefined,
|
|
11828
|
+
path: m[5] || '',
|
|
11829
|
+
query: m[6] !== undefined ? (m[7] || '') : undefined,
|
|
11830
|
+
fragment: m[8] !== undefined ? (m[9] || '') : undefined,
|
|
11831
|
+
};
|
|
11832
|
+
}
|
|
11833
|
+
|
|
11834
|
+
function recomposeUriReference(parts) {
|
|
11835
|
+
let out = '';
|
|
11836
|
+
if (parts.scheme !== undefined) out += `${parts.scheme}:`;
|
|
11837
|
+
if (parts.authority !== undefined) out += `//${parts.authority}`;
|
|
11838
|
+
out += parts.path || '';
|
|
11839
|
+
if (parts.query !== undefined) out += `?${parts.query}`;
|
|
11840
|
+
if (parts.fragment !== undefined) out += `#${parts.fragment}`;
|
|
11841
|
+
return out;
|
|
11842
|
+
}
|
|
11843
|
+
|
|
11844
|
+
function removeLastPathSegment(path) {
|
|
11845
|
+
if (!path) return '';
|
|
11846
|
+
const i = path.lastIndexOf('/');
|
|
11847
|
+
if (i < 0) return '';
|
|
11848
|
+
if (i === 0) return '';
|
|
11849
|
+
return path.slice(0, i);
|
|
11850
|
+
}
|
|
11851
|
+
|
|
11852
|
+
function removeDotSegments(path) {
|
|
11853
|
+
// RFC 3986 section 5.2.4. This deliberately avoids WHATWG URL parsing so
|
|
11854
|
+
// Eyeling preserves IRI spelling (for example, it does not add a trailing
|
|
11855
|
+
// slash to `http://example.org`) while still normalizing `.` and `..` path
|
|
11856
|
+
// segments as required by section 5.2.2.
|
|
11857
|
+
let input = String(path || '');
|
|
11858
|
+
let output = '';
|
|
11859
|
+
|
|
11860
|
+
while (input.length > 0) {
|
|
11861
|
+
if (input.startsWith('../')) {
|
|
11862
|
+
input = input.slice(3);
|
|
11863
|
+
} else if (input.startsWith('./')) {
|
|
11864
|
+
input = input.slice(2);
|
|
11865
|
+
} else if (input.startsWith('/./')) {
|
|
11866
|
+
input = `/${input.slice(3)}`;
|
|
11867
|
+
} else if (input === '/.') {
|
|
11868
|
+
input = '/';
|
|
11869
|
+
} else if (input.startsWith('/../')) {
|
|
11870
|
+
input = `/${input.slice(4)}`;
|
|
11871
|
+
output = removeLastPathSegment(output);
|
|
11872
|
+
} else if (input === '/..') {
|
|
11873
|
+
input = '/';
|
|
11874
|
+
output = removeLastPathSegment(output);
|
|
11875
|
+
} else if (input === '.' || input === '..') {
|
|
11876
|
+
input = '';
|
|
11877
|
+
} else {
|
|
11878
|
+
let segmentEnd;
|
|
11879
|
+
if (input[0] === '/') {
|
|
11880
|
+
segmentEnd = input.indexOf('/', 1);
|
|
11881
|
+
} else {
|
|
11882
|
+
segmentEnd = input.indexOf('/');
|
|
11883
|
+
}
|
|
11884
|
+
|
|
11885
|
+
if (segmentEnd < 0) {
|
|
11886
|
+
output += input;
|
|
11887
|
+
input = '';
|
|
11888
|
+
} else {
|
|
11889
|
+
output += input.slice(0, segmentEnd);
|
|
11890
|
+
input = input.slice(segmentEnd);
|
|
11891
|
+
}
|
|
11892
|
+
}
|
|
11893
|
+
}
|
|
11894
|
+
|
|
11895
|
+
return output;
|
|
11896
|
+
}
|
|
11897
|
+
|
|
11898
|
+
function mergePaths(base, refPath) {
|
|
11899
|
+
if (base.authority !== undefined && base.path === '') {
|
|
11900
|
+
return `/${refPath}`;
|
|
11901
|
+
}
|
|
11902
|
+
const i = base.path.lastIndexOf('/');
|
|
11903
|
+
if (i < 0) return refPath;
|
|
11904
|
+
return `${base.path.slice(0, i + 1)}${refPath}`;
|
|
11905
|
+
}
|
|
11906
|
+
|
|
11706
11907
|
function resolveIriRef(ref, base) {
|
|
11707
|
-
|
|
11708
|
-
if (
|
|
11709
|
-
|
|
11710
|
-
|
|
11711
|
-
|
|
11712
|
-
|
|
11908
|
+
const r = parseUriReferenceForResolution(ref);
|
|
11909
|
+
if (!r) return ref;
|
|
11910
|
+
|
|
11911
|
+
const baseParts = base ? parseUriReferenceForResolution(base) : null;
|
|
11912
|
+
|
|
11913
|
+
// Absolute references do not need a base, but RFC 3986 section 5.2.2 still
|
|
11914
|
+
// applies remove_dot_segments(R.path) when R.scheme is defined.
|
|
11915
|
+
if (r.scheme !== undefined) {
|
|
11916
|
+
return recomposeUriReference({
|
|
11917
|
+
scheme: r.scheme,
|
|
11918
|
+
authority: r.authority,
|
|
11919
|
+
path: removeDotSegments(r.path),
|
|
11920
|
+
query: r.query,
|
|
11921
|
+
fragment: r.fragment,
|
|
11922
|
+
});
|
|
11923
|
+
}
|
|
11924
|
+
|
|
11925
|
+
// Without a usable base, preserve relative references as written.
|
|
11926
|
+
if (!baseParts || baseParts.scheme === undefined) return ref;
|
|
11927
|
+
|
|
11928
|
+
const t = {
|
|
11929
|
+
scheme: baseParts.scheme,
|
|
11930
|
+
authority: undefined,
|
|
11931
|
+
path: '',
|
|
11932
|
+
query: undefined,
|
|
11933
|
+
fragment: r.fragment,
|
|
11934
|
+
};
|
|
11935
|
+
|
|
11936
|
+
if (r.authority !== undefined) {
|
|
11937
|
+
t.authority = r.authority;
|
|
11938
|
+
t.path = removeDotSegments(r.path);
|
|
11939
|
+
t.query = r.query;
|
|
11940
|
+
} else if (r.path === '') {
|
|
11941
|
+
t.authority = baseParts.authority;
|
|
11942
|
+
t.path = baseParts.path;
|
|
11943
|
+
t.query = r.query !== undefined ? r.query : baseParts.query;
|
|
11944
|
+
} else {
|
|
11945
|
+
t.authority = baseParts.authority;
|
|
11946
|
+
if (r.path.startsWith('/')) {
|
|
11947
|
+
t.path = removeDotSegments(r.path);
|
|
11948
|
+
} else {
|
|
11949
|
+
t.path = removeDotSegments(mergePaths(baseParts, r.path));
|
|
11950
|
+
}
|
|
11951
|
+
t.query = r.query;
|
|
11713
11952
|
}
|
|
11953
|
+
|
|
11954
|
+
return recomposeUriReference(t);
|
|
11714
11955
|
}
|
|
11715
11956
|
|
|
11716
11957
|
// -----------------------------------------------------------------------------
|
|
@@ -1,5 +1,29 @@
|
|
|
1
|
+
# ==============================
|
|
2
|
+
# RDF Message Flow input sidecar
|
|
3
|
+
# ==============================
|
|
4
|
+
#
|
|
5
|
+
# This file is the data half of the runnable example:
|
|
6
|
+
#
|
|
7
|
+
# eyeling -r examples/rdf-message-flow.n3 examples/input/rdf-message-flow.trig
|
|
8
|
+
#
|
|
9
|
+
# It is intentionally plain TriG rather than a parser-level RDF Message Log with
|
|
10
|
+
# VERSION "1.2-messages" and MESSAGE delimiters. The RDF Messages draft says an
|
|
11
|
+
# RDF Message is an RDF dataset interpreted atomically, that a stream is an
|
|
12
|
+
# ordered sequence of such messages, and that messages in a stream should not be
|
|
13
|
+
# combined by default. To demonstrate those ideas in Eyeling today, the default
|
|
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).
|
|
17
|
+
#
|
|
18
|
+
# The envelope IRIs are not identifiers for RDF Messages defined by the spec;
|
|
19
|
+
# they are application-level records used by the flow-control rules. The named
|
|
20
|
+
# payload graphs are the datasets/messages being interpreted atomically. The
|
|
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.
|
|
24
|
+
|
|
1
25
|
@prefix : <https://eyereasoner.github.io/eyeling/examples/rdf-message-flow#> .
|
|
2
|
-
@prefix
|
|
26
|
+
@prefix flow: <https://eyereasoner.github.io/eyeling/examples/rdf-message-flow/vocab#> .
|
|
3
27
|
@prefix prov: <http://www.w3.org/ns/prov#> .
|
|
4
28
|
@prefix sosa: <http://www.w3.org/ns/sosa/> .
|
|
5
29
|
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
@@ -10,77 +34,87 @@
|
|
|
10
34
|
@prefix see: <https://example.org/see#> .
|
|
11
35
|
@prefix in: <https://example.org/see/input#> .
|
|
12
36
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
:temperatureFlow
|
|
17
|
-
:temperatureFlow
|
|
18
|
-
:temperatureFlow
|
|
19
|
-
:temperatureFlow
|
|
20
|
-
:temperatureFlow
|
|
21
|
-
:temperatureFlow
|
|
22
|
-
:temperatureFlow msg:message :m005 .
|
|
37
|
+
:temperatureFlow a flow:RDFMessageStream .
|
|
38
|
+
:temperatureFlow flow:orderedEnvelopes (:m001 :m002 :m003 :m004 :m005) .
|
|
39
|
+
:temperatureFlow flow:envelope :m001 .
|
|
40
|
+
:temperatureFlow flow:envelope :m002 .
|
|
41
|
+
:temperatureFlow flow:envelope :m003 .
|
|
42
|
+
:temperatureFlow flow:envelope :m004 .
|
|
43
|
+
:temperatureFlow flow:envelope :m005 .
|
|
44
|
+
:temperatureFlow :producer :thermometerA .
|
|
45
|
+
:temperatureFlow :consumer :flowProcessor .
|
|
23
46
|
:temperatureFlow :pipeline (:ingest :validate :interpret :route :sink) .
|
|
24
47
|
:temperatureFlow :highThreshold 26 .
|
|
48
|
+
|
|
49
|
+
:thermometerA a flow:StreamProducer .
|
|
50
|
+
:flowProcessor a flow:StreamConsumer .
|
|
51
|
+
:archiveSink a flow:StreamConsumer .
|
|
52
|
+
:alertSink a flow:StreamConsumer .
|
|
53
|
+
:heartbeatSink a flow:StreamConsumer .
|
|
54
|
+
|
|
55
|
+
:m001 a flow:MessageEnvelope .
|
|
25
56
|
:m001 :atStage :ingest .
|
|
26
|
-
:m001
|
|
27
|
-
:m001
|
|
28
|
-
:m001
|
|
29
|
-
:m001
|
|
30
|
-
:m001
|
|
31
|
-
:m001
|
|
32
|
-
|
|
33
|
-
:m002 a
|
|
34
|
-
:m002
|
|
35
|
-
:m002
|
|
36
|
-
:m002 prov:generatedAtTime "2026-05-12T18:21:00Z" .
|
|
37
|
-
:m002
|
|
38
|
-
:m002
|
|
39
|
-
:m002
|
|
40
|
-
|
|
41
|
-
:m003
|
|
42
|
-
:m003
|
|
43
|
-
:m003
|
|
44
|
-
:m003
|
|
45
|
-
:
|
|
46
|
-
|
|
47
|
-
:m004
|
|
48
|
-
:m004
|
|
49
|
-
:m004
|
|
50
|
-
:m004
|
|
51
|
-
:m004
|
|
52
|
-
:
|
|
53
|
-
:
|
|
54
|
-
|
|
55
|
-
:m005
|
|
56
|
-
:m005
|
|
57
|
-
:m005
|
|
57
|
+
:m001 flow:offset 1 .
|
|
58
|
+
:m001 flow:nextEnvelope :m002 .
|
|
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 .
|
|
63
|
+
|
|
64
|
+
:m002 a flow:MessageEnvelope .
|
|
65
|
+
:m002 flow:offset 2 .
|
|
66
|
+
:m002 flow:nextEnvelope :m003 .
|
|
67
|
+
:m002 prov:generatedAtTime "2026-05-12T18:21:00Z"^^xsd:dateTime .
|
|
68
|
+
:m002 flow:payloadKind :observation .
|
|
69
|
+
:m002 flow:expectedResult 22 .
|
|
70
|
+
:m002 flow:payloadGraph in:payload002 .
|
|
71
|
+
|
|
72
|
+
:m003 a flow:MessageEnvelope .
|
|
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 .
|
|
77
|
+
|
|
78
|
+
:m004 a flow:MessageEnvelope .
|
|
79
|
+
:m004 flow:offset 4 .
|
|
80
|
+
:m004 flow:nextEnvelope :m005 .
|
|
81
|
+
:m004 prov:generatedAtTime "2026-05-12T18:23:00Z"^^xsd:dateTime .
|
|
82
|
+
:m004 flow:payloadKind :observation .
|
|
83
|
+
:m004 flow:expectedResult 28 .
|
|
84
|
+
:m004 flow:payloadGraph in:payload004 .
|
|
85
|
+
|
|
86
|
+
:m005 a flow:MessageEnvelope .
|
|
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 .
|
|
58
92
|
|
|
59
|
-
in:
|
|
93
|
+
in:payload001 {
|
|
60
94
|
_:m001b0 a sosa:Observation .
|
|
61
95
|
_:m001b0 sosa:madeBySensor :thermometerA .
|
|
62
|
-
_:m001b0 sosa:resultTime "2026-05-12T18:20:00Z" .
|
|
96
|
+
_:m001b0 sosa:resultTime "2026-05-12T18:20:00Z"^^xsd:dateTime .
|
|
63
97
|
_:m001b0 sosa:hasSimpleResult 21 .
|
|
64
98
|
}
|
|
65
99
|
|
|
66
|
-
in:
|
|
100
|
+
in:payload002 {
|
|
67
101
|
_:m002b0 a sosa:Observation .
|
|
68
102
|
_:m002b0 sosa:madeBySensor :thermometerA .
|
|
69
|
-
_:m002b0 sosa:resultTime "2026-05-12T18:21:00Z" .
|
|
103
|
+
_:m002b0 sosa:resultTime "2026-05-12T18:21:00Z"^^xsd:dateTime .
|
|
70
104
|
_:m002b0 sosa:hasSimpleResult 22 .
|
|
71
105
|
}
|
|
72
106
|
|
|
73
|
-
in:
|
|
107
|
+
in:payload004 {
|
|
74
108
|
_:m004b0 a sosa:Observation .
|
|
75
109
|
_:m004b0 sosa:madeBySensor :thermometerA .
|
|
76
|
-
_:m004b0 sosa:resultTime "2026-05-12T18:23:00Z" .
|
|
110
|
+
_:m004b0 sosa:resultTime "2026-05-12T18:23:00Z"^^xsd:dateTime .
|
|
77
111
|
_:m004b0 sosa:hasSimpleResult 28 .
|
|
78
112
|
}
|
|
79
113
|
|
|
80
|
-
in:
|
|
114
|
+
in:payload005 {
|
|
81
115
|
_:m005b0 a sosa:Observation .
|
|
82
116
|
_:m005b0 sosa:madeBySensor :thermometerA .
|
|
83
|
-
_:m005b0 sosa:resultTime "2026-05-12T18:24:00Z" .
|
|
117
|
+
_:m005b0 sosa:resultTime "2026-05-12T18:24:00Z"^^xsd:dateTime .
|
|
84
118
|
_:m005b0 sosa:hasSimpleResult 29 .
|
|
85
119
|
}
|
|
86
120
|
|
|
@@ -88,13 +122,7 @@ in:metadata {
|
|
|
88
122
|
in:run a see:InputDataset .
|
|
89
123
|
in:run see:name "rdf_message_flow" .
|
|
90
124
|
in:run see:title "RDF Message Flow" .
|
|
91
|
-
in:run see:sourceFile "examples/
|
|
92
|
-
in:run see:
|
|
93
|
-
in:run see:description "A companion to rdf_messages.n3. This example focuses on a live stream where\nRDF Messages continuously flow through a small processing pipeline. The next\nmessage is released only after the current message reaches the sink, so the\nstream behaves as an ordered, replayable flow rather than a single merged RDF\ngraph." .
|
|
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." .
|
|
94
127
|
in:run see:compiler "Eyeling RDF/TriG input sidecar" .
|
|
95
|
-
in:run see:inputFacts 42 .
|
|
96
|
-
in:run see:compiledRules 9 .
|
|
97
|
-
in:run see:compiledBackwardRules 0 .
|
|
98
|
-
in:run see:compiledFuses 0 .
|
|
99
|
-
in:run see:compiledQueries 1 .
|
|
100
128
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# ===================================
|
|
2
|
+
# RDF Message Microgrid input sidecar
|
|
3
|
+
# ===================================
|
|
4
|
+
#
|
|
5
|
+
# This file is the data half of the runnable example:
|
|
6
|
+
#
|
|
7
|
+
# eyeling -r examples/rdf-message-microgrid.n3 examples/input/rdf-message-microgrid.trig
|
|
8
|
+
#
|
|
9
|
+
# The scene is a clinic operating as an islanded microgrid after a storm. The
|
|
10
|
+
# data arrives as a replayable stream of atomic messages: life-safety loads,
|
|
11
|
+
# power status, flexible demand, and an empty heartbeat. The default graph holds
|
|
12
|
+
# application-local envelope facts, while each non-empty message payload is kept
|
|
13
|
+
# in a named graph.
|
|
14
|
+
#
|
|
15
|
+
# The envelope IRIs are not RDF Message identifiers defined by the specification;
|
|
16
|
+
# they are local replay records for this example. The named graphs are the
|
|
17
|
+
# datasets/messages interpreted atomically by the N3 rules.
|
|
18
|
+
|
|
19
|
+
@prefix : <https://eyereasoner.github.io/eyeling/examples/rdf-message-microgrid#> .
|
|
20
|
+
@prefix rmsg: <https://eyereasoner.github.io/eyeling/examples/rdf-message-microgrid/vocab#> .
|
|
21
|
+
@prefix prov: <http://www.w3.org/ns/prov#> .
|
|
22
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
23
|
+
@prefix see: <https://example.org/see#> .
|
|
24
|
+
@prefix in: <https://example.org/see/input#> .
|
|
25
|
+
|
|
26
|
+
:stormClinicLog a rmsg:MessageLog .
|
|
27
|
+
:stormClinicLog rmsg:orderedMessages (:m001 :m002 :m003 :m004) .
|
|
28
|
+
:stormClinicLog rmsg:message :m001 .
|
|
29
|
+
:stormClinicLog rmsg:message :m002 .
|
|
30
|
+
:stormClinicLog rmsg:message :m003 .
|
|
31
|
+
:stormClinicLog rmsg:message :m004 .
|
|
32
|
+
:stormClinicLog rmsg:retentionPolicy "storm replay archive" .
|
|
33
|
+
|
|
34
|
+
:m001 a rmsg:MessageEnvelope .
|
|
35
|
+
:m001 rmsg:offset 1 .
|
|
36
|
+
:m001 prov:generatedAtTime "2026-05-15T19:00:00Z"^^xsd:dateTime .
|
|
37
|
+
:m001 rmsg:payloadKind :lifeSafetyLoads .
|
|
38
|
+
:m001 rmsg:payloadGraph in:lifeSafetyPayload .
|
|
39
|
+
|
|
40
|
+
:m002 a rmsg:MessageEnvelope .
|
|
41
|
+
:m002 rmsg:offset 2 .
|
|
42
|
+
:m002 prov:generatedAtTime "2026-05-15T19:01:00Z"^^xsd:dateTime .
|
|
43
|
+
:m002 rmsg:payloadKind :powerStatus .
|
|
44
|
+
:m002 rmsg:payloadGraph in:powerPayload .
|
|
45
|
+
|
|
46
|
+
:m003 a rmsg:MessageEnvelope .
|
|
47
|
+
:m003 rmsg:offset 3 .
|
|
48
|
+
:m003 prov:generatedAtTime "2026-05-15T19:02:00Z"^^xsd:dateTime .
|
|
49
|
+
:m003 rmsg:payloadKind :flexibleDemand .
|
|
50
|
+
:m003 rmsg:payloadGraph in:flexPayload .
|
|
51
|
+
|
|
52
|
+
:m004 a rmsg:MessageEnvelope .
|
|
53
|
+
:m004 rmsg:offset 4 .
|
|
54
|
+
:m004 prov:generatedAtTime "2026-05-15T19:03:00Z"^^xsd:dateTime .
|
|
55
|
+
:m004 rmsg:payloadKind :heartbeat .
|
|
56
|
+
|
|
57
|
+
in:lifeSafetyPayload {
|
|
58
|
+
:oxygenConcentrator a :LifeSafetyLoad .
|
|
59
|
+
:oxygenConcentrator :requiresWatts 500 .
|
|
60
|
+
:oxygenConcentrator :serves :respiratoryCareRoom .
|
|
61
|
+
|
|
62
|
+
:vaccineFridge a :ColdChainLoad .
|
|
63
|
+
:vaccineFridge :requiresWatts 120 .
|
|
64
|
+
:vaccineFridge :serves :vaccineColdChain .
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
in:powerPayload {
|
|
68
|
+
:batteryBank a :PowerReserve .
|
|
69
|
+
:batteryBank :availableWatts 650 .
|
|
70
|
+
:batteryBank :state :islanded .
|
|
71
|
+
|
|
72
|
+
:solarForecast a :NearTermForecast .
|
|
73
|
+
:solarForecast :expectedWatts 150 .
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
in:flexPayload {
|
|
77
|
+
:evChargers a :FlexibleLoad .
|
|
78
|
+
:evChargers :shedWatts 600 .
|
|
79
|
+
:evChargers :serves :staffVehicles .
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
in:metadata {
|
|
83
|
+
in:run a see:InputDataset .
|
|
84
|
+
in:run see:name "rdf_message_microgrid" .
|
|
85
|
+
in:run see:title "RDF Message Microgrid" .
|
|
86
|
+
in:run see:sourceFile "examples/rdf-message-microgrid.n3" .
|
|
87
|
+
in:run see:description "A storm clinic microgrid example using application-local RDF Message envelopes and named payload graphs so a reasoner can make a bounded, explainable load-shedding decision." .
|
|
88
|
+
in:run see:compiler "Eyeling RDF/TriG input sidecar" .
|
|
89
|
+
}
|