eyeling 1.6.17 → 1.6.19
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/eyeling.js +220 -241
- package/package.json +1 -1
package/eyeling.js
CHANGED
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
const { version } = require('./package.json');
|
|
20
20
|
const nodeCrypto = require('crypto');
|
|
21
21
|
|
|
22
|
-
//
|
|
22
|
+
// ===========================================================================
|
|
23
23
|
// Namespace constants
|
|
24
|
-
//
|
|
24
|
+
// ===========================================================================
|
|
25
25
|
|
|
26
26
|
const RDF_NS = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
|
|
27
27
|
const RDFS_NS = 'http://www.w3.org/2000/01/rdf-schema#';
|
|
@@ -87,14 +87,45 @@ let fixedNowLex = null;
|
|
|
87
87
|
// If not fixed, we still memoize one value per run to avoid re-firing rules.
|
|
88
88
|
let runNowLex = null;
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
90
|
+
// ===========================================================================
|
|
91
|
+
// Run-level time helpers
|
|
92
|
+
// ===========================================================================
|
|
93
|
+
|
|
94
|
+
function localIsoDateTimeString(d) {
|
|
95
|
+
function pad(n, width = 2) {
|
|
96
|
+
return String(n).padStart(width, '0');
|
|
97
|
+
}
|
|
98
|
+
const year = d.getFullYear();
|
|
99
|
+
const month = d.getMonth() + 1;
|
|
100
|
+
const day = d.getDate();
|
|
101
|
+
const hour = d.getHours();
|
|
102
|
+
const min = d.getMinutes();
|
|
103
|
+
const sec = d.getSeconds();
|
|
104
|
+
const ms = d.getMilliseconds();
|
|
105
|
+
const offsetMin = -d.getTimezoneOffset(); // minutes east of UTC
|
|
106
|
+
const sign = offsetMin >= 0 ? '+' : '-';
|
|
107
|
+
const abs = Math.abs(offsetMin);
|
|
108
|
+
const oh = Math.floor(abs / 60);
|
|
109
|
+
const om = abs % 60;
|
|
110
|
+
const msPart = ms ? '.' + String(ms).padStart(3, '0') : '';
|
|
111
|
+
return (
|
|
112
|
+
pad(year, 4) +
|
|
113
|
+
'-' +
|
|
114
|
+
pad(month) +
|
|
115
|
+
'-' +
|
|
116
|
+
pad(day) +
|
|
117
|
+
'T' +
|
|
118
|
+
pad(hour) +
|
|
119
|
+
':' +
|
|
120
|
+
pad(min) +
|
|
121
|
+
':' +
|
|
122
|
+
pad(sec) +
|
|
123
|
+
msPart +
|
|
124
|
+
sign +
|
|
125
|
+
pad(oh) +
|
|
126
|
+
':' +
|
|
127
|
+
pad(om)
|
|
128
|
+
);
|
|
98
129
|
}
|
|
99
130
|
|
|
100
131
|
function utcIsoDateTimeStringFromEpochSeconds(sec) {
|
|
@@ -154,9 +185,9 @@ function deterministicSkolemIdFromKey(key) {
|
|
|
154
185
|
|
|
155
186
|
let runLocalTimeCache = null;
|
|
156
187
|
|
|
157
|
-
//
|
|
188
|
+
// ===========================================================================
|
|
158
189
|
// AST (Abstract Syntax Tree)
|
|
159
|
-
//
|
|
190
|
+
// ===========================================================================
|
|
160
191
|
|
|
161
192
|
class Term {}
|
|
162
193
|
|
|
@@ -238,9 +269,9 @@ class DerivedFact {
|
|
|
238
269
|
}
|
|
239
270
|
}
|
|
240
271
|
|
|
241
|
-
//
|
|
272
|
+
// ===========================================================================
|
|
242
273
|
// LEXER
|
|
243
|
-
//
|
|
274
|
+
// ===========================================================================
|
|
244
275
|
|
|
245
276
|
class Token {
|
|
246
277
|
constructor(typ, value = null) {
|
|
@@ -631,9 +662,9 @@ function lex(inputText) {
|
|
|
631
662
|
return tokens;
|
|
632
663
|
}
|
|
633
664
|
|
|
634
|
-
//
|
|
665
|
+
// ===========================================================================
|
|
635
666
|
// PREFIX ENVIRONMENT
|
|
636
|
-
//
|
|
667
|
+
// ===========================================================================
|
|
637
668
|
|
|
638
669
|
class PrefixEnv {
|
|
639
670
|
constructor(map) {
|
|
@@ -789,9 +820,9 @@ function collectBlankLabelsInTriples(triples) {
|
|
|
789
820
|
return acc;
|
|
790
821
|
}
|
|
791
822
|
|
|
792
|
-
//
|
|
823
|
+
// ===========================================================================
|
|
793
824
|
// PARSER
|
|
794
|
-
//
|
|
825
|
+
// ===========================================================================
|
|
795
826
|
|
|
796
827
|
class Parser {
|
|
797
828
|
constructor(tokens) {
|
|
@@ -1231,9 +1262,9 @@ class Parser {
|
|
|
1231
1262
|
}
|
|
1232
1263
|
}
|
|
1233
1264
|
|
|
1234
|
-
//
|
|
1265
|
+
// ===========================================================================
|
|
1235
1266
|
// Blank-node lifting and Skolemization
|
|
1236
|
-
//
|
|
1267
|
+
// ===========================================================================
|
|
1237
1268
|
|
|
1238
1269
|
function liftBlankRuleVars(premise, conclusion) {
|
|
1239
1270
|
function convertTerm(t, mapping, counter) {
|
|
@@ -1340,9 +1371,9 @@ function skolemizeTripleForHeadBlanks(tr, headBlankLabels, mapping, skCounter, f
|
|
|
1340
1371
|
);
|
|
1341
1372
|
}
|
|
1342
1373
|
|
|
1343
|
-
//
|
|
1374
|
+
// ===========================================================================
|
|
1344
1375
|
// Alpha equivalence helpers
|
|
1345
|
-
//
|
|
1376
|
+
// ===========================================================================
|
|
1346
1377
|
|
|
1347
1378
|
function termsEqual(a, b) {
|
|
1348
1379
|
if (a === b) return true;
|
|
@@ -1610,13 +1641,9 @@ function alphaEqTriple(a, b) {
|
|
|
1610
1641
|
return alphaEqTerm(a.s, b.s, bmap) && alphaEqTerm(a.p, b.p, bmap) && alphaEqTerm(a.o, b.o, bmap);
|
|
1611
1642
|
}
|
|
1612
1643
|
|
|
1613
|
-
|
|
1614
|
-
return triples.some((t) => alphaEqTriple(t, tr));
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
|
-
// ============================================================================
|
|
1644
|
+
// ===========================================================================
|
|
1618
1645
|
// Indexes (facts + backward rules)
|
|
1619
|
-
//
|
|
1646
|
+
// ===========================================================================
|
|
1620
1647
|
//
|
|
1621
1648
|
// Facts:
|
|
1622
1649
|
// - __byPred: Map<predicateIRI, Triple[]>
|
|
@@ -1784,9 +1811,9 @@ function indexBackRule(backRules, r) {
|
|
|
1784
1811
|
}
|
|
1785
1812
|
}
|
|
1786
1813
|
|
|
1787
|
-
//
|
|
1814
|
+
// ===========================================================================
|
|
1788
1815
|
// Special predicate helpers
|
|
1789
|
-
//
|
|
1816
|
+
// ===========================================================================
|
|
1790
1817
|
|
|
1791
1818
|
function isRdfTypePred(p) {
|
|
1792
1819
|
return p instanceof Iri && p.value === RDF_NS + 'type';
|
|
@@ -1804,9 +1831,9 @@ function isLogImpliedBy(p) {
|
|
|
1804
1831
|
return p instanceof Iri && p.value === LOG_NS + 'impliedBy';
|
|
1805
1832
|
}
|
|
1806
1833
|
|
|
1807
|
-
//
|
|
1834
|
+
// ===========================================================================
|
|
1808
1835
|
// Constraint / "test" builtins
|
|
1809
|
-
//
|
|
1836
|
+
// ===========================================================================
|
|
1810
1837
|
|
|
1811
1838
|
function isConstraintBuiltin(tr) {
|
|
1812
1839
|
if (!(tr.p instanceof Iri)) return false;
|
|
@@ -1855,12 +1882,10 @@ function isConstraintBuiltin(tr) {
|
|
|
1855
1882
|
return false;
|
|
1856
1883
|
}
|
|
1857
1884
|
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
* - pure test / constraint builtins last (checked once bindings are in place).
|
|
1863
|
-
*/
|
|
1885
|
+
// Move constraint builtins to the end of the rule premise.
|
|
1886
|
+
// This is a simple "delaying" strategy similar in spirit to Prolog's when/2:
|
|
1887
|
+
// - normal goals first (can bind variables),
|
|
1888
|
+
// - pure test / constraint builtins last (checked once bindings are in place).
|
|
1864
1889
|
function reorderPremiseForConstraints(premise) {
|
|
1865
1890
|
if (!premise || premise.length === 0) return premise;
|
|
1866
1891
|
|
|
@@ -1874,9 +1899,9 @@ function reorderPremiseForConstraints(premise) {
|
|
|
1874
1899
|
return normal.concat(delayed);
|
|
1875
1900
|
}
|
|
1876
1901
|
|
|
1877
|
-
//
|
|
1902
|
+
// ===========================================================================
|
|
1878
1903
|
// Unification + substitution
|
|
1879
|
-
//
|
|
1904
|
+
// ===========================================================================
|
|
1880
1905
|
|
|
1881
1906
|
function containsVarTerm(t, v) {
|
|
1882
1907
|
if (t instanceof Var) return t.name === v;
|
|
@@ -2188,9 +2213,9 @@ function composeSubst(outer, delta) {
|
|
|
2188
2213
|
return out;
|
|
2189
2214
|
}
|
|
2190
2215
|
|
|
2191
|
-
//
|
|
2216
|
+
// ===========================================================================
|
|
2192
2217
|
// BUILTINS
|
|
2193
|
-
//
|
|
2218
|
+
// ===========================================================================
|
|
2194
2219
|
|
|
2195
2220
|
function literalParts(lit) {
|
|
2196
2221
|
// Split a literal into lexical form and datatype IRI (if any).
|
|
@@ -2528,9 +2553,9 @@ function parseXsdFloatSpecialLex(s) {
|
|
|
2528
2553
|
return null;
|
|
2529
2554
|
}
|
|
2530
2555
|
|
|
2531
|
-
//
|
|
2556
|
+
// ===========================================================================
|
|
2532
2557
|
// Math builtin helpers
|
|
2533
|
-
//
|
|
2558
|
+
// ===========================================================================
|
|
2534
2559
|
|
|
2535
2560
|
function formatXsdFloatSpecialLex(n) {
|
|
2536
2561
|
if (n === Infinity) return 'INF';
|
|
@@ -2635,15 +2660,6 @@ function parseIntLiteral(t) {
|
|
|
2635
2660
|
}
|
|
2636
2661
|
}
|
|
2637
2662
|
|
|
2638
|
-
function parseNumberLiteral(t) {
|
|
2639
|
-
// Prefer BigInt for integers, fall back to Number for other numeric literals.
|
|
2640
|
-
const bi = parseIntLiteral(t);
|
|
2641
|
-
if (bi !== null) return bi;
|
|
2642
|
-
const n = parseNum(t);
|
|
2643
|
-
if (n !== null) return n;
|
|
2644
|
-
return null;
|
|
2645
|
-
}
|
|
2646
|
-
|
|
2647
2663
|
function formatNum(n) {
|
|
2648
2664
|
return String(n);
|
|
2649
2665
|
}
|
|
@@ -2686,9 +2702,9 @@ function pow10n(k) {
|
|
|
2686
2702
|
return 10n ** BigInt(k);
|
|
2687
2703
|
}
|
|
2688
2704
|
|
|
2689
|
-
//
|
|
2705
|
+
// ===========================================================================
|
|
2690
2706
|
// Time & duration builtin helpers
|
|
2691
|
-
//
|
|
2707
|
+
// ===========================================================================
|
|
2692
2708
|
|
|
2693
2709
|
function parseXsdDateTerm(t) {
|
|
2694
2710
|
if (!(t instanceof Literal)) return null;
|
|
@@ -3017,9 +3033,9 @@ function evalUnaryMathRel(g, subst, forwardFn, inverseFn /* may be null */) {
|
|
|
3017
3033
|
return [];
|
|
3018
3034
|
}
|
|
3019
3035
|
|
|
3020
|
-
//
|
|
3036
|
+
// ===========================================================================
|
|
3021
3037
|
// List builtin helpers
|
|
3022
|
-
//
|
|
3038
|
+
// ===========================================================================
|
|
3023
3039
|
|
|
3024
3040
|
function listAppendSplit(parts, resElems, subst) {
|
|
3025
3041
|
if (!parts.length) {
|
|
@@ -3070,9 +3086,142 @@ function evalListRestLikeBuiltin(sTerm, oTerm, subst) {
|
|
|
3070
3086
|
return [];
|
|
3071
3087
|
}
|
|
3072
3088
|
|
|
3073
|
-
//
|
|
3089
|
+
// ===========================================================================
|
|
3090
|
+
// RDF list materialization
|
|
3091
|
+
// ===========================================================================
|
|
3092
|
+
|
|
3093
|
+
// Turn RDF Collections described with rdf:first/rdf:rest (+ rdf:nil) into ListTerm terms.
|
|
3094
|
+
// This mutates triples/rules in-place so list:* builtins work on RDF-serialized lists too.
|
|
3095
|
+
function materializeRdfLists(triples, forwardRules, backwardRules) {
|
|
3096
|
+
const RDF_FIRST = RDF_NS + 'first';
|
|
3097
|
+
const RDF_REST = RDF_NS + 'rest';
|
|
3098
|
+
const RDF_NIL = RDF_NS + 'nil';
|
|
3099
|
+
|
|
3100
|
+
function nodeKey(t) {
|
|
3101
|
+
if (t instanceof Blank) return 'B:' + t.label;
|
|
3102
|
+
if (t instanceof Iri) return 'I:' + t.value;
|
|
3103
|
+
return null;
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
// Collect first/rest arcs from *input triples*
|
|
3107
|
+
const firstMap = new Map(); // key(subject) -> Term (object)
|
|
3108
|
+
const restMap = new Map(); // key(subject) -> Term (object)
|
|
3109
|
+
for (const tr of triples) {
|
|
3110
|
+
if (!(tr.p instanceof Iri)) continue;
|
|
3111
|
+
const k = nodeKey(tr.s);
|
|
3112
|
+
if (!k) continue;
|
|
3113
|
+
if (tr.p.value === RDF_FIRST) firstMap.set(k, tr.o);
|
|
3114
|
+
else if (tr.p.value === RDF_REST) restMap.set(k, tr.o);
|
|
3115
|
+
}
|
|
3116
|
+
if (!firstMap.size && !restMap.size) return;
|
|
3117
|
+
|
|
3118
|
+
const cache = new Map(); // key(node) -> ListTerm
|
|
3119
|
+
const visiting = new Set(); // cycle guard
|
|
3120
|
+
|
|
3121
|
+
function buildListForKey(k) {
|
|
3122
|
+
if (cache.has(k)) return cache.get(k);
|
|
3123
|
+
if (visiting.has(k)) return null; // cycle => not a well-formed list
|
|
3124
|
+
visiting.add(k);
|
|
3125
|
+
|
|
3126
|
+
// rdf:nil => ()
|
|
3127
|
+
if (k === 'I:' + RDF_NIL) {
|
|
3128
|
+
const empty = new ListTerm([]);
|
|
3129
|
+
cache.set(k, empty);
|
|
3130
|
+
visiting.delete(k);
|
|
3131
|
+
return empty;
|
|
3132
|
+
}
|
|
3133
|
+
|
|
3134
|
+
const head = firstMap.get(k);
|
|
3135
|
+
const tail = restMap.get(k);
|
|
3136
|
+
if (head === undefined || tail === undefined) {
|
|
3137
|
+
visiting.delete(k);
|
|
3138
|
+
return null; // not a full cons cell
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
const headTerm = rewriteTerm(head);
|
|
3142
|
+
|
|
3143
|
+
let tailListElems = null;
|
|
3144
|
+
if (tail instanceof Iri && tail.value === RDF_NIL) {
|
|
3145
|
+
tailListElems = [];
|
|
3146
|
+
} else {
|
|
3147
|
+
const tk = nodeKey(tail);
|
|
3148
|
+
if (!tk) {
|
|
3149
|
+
visiting.delete(k);
|
|
3150
|
+
return null;
|
|
3151
|
+
}
|
|
3152
|
+
const tailList = buildListForKey(tk);
|
|
3153
|
+
if (!tailList) {
|
|
3154
|
+
visiting.delete(k);
|
|
3155
|
+
return null;
|
|
3156
|
+
}
|
|
3157
|
+
tailListElems = tailList.elems;
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3160
|
+
const out = new ListTerm([headTerm, ...tailListElems]);
|
|
3161
|
+
cache.set(k, out);
|
|
3162
|
+
visiting.delete(k);
|
|
3163
|
+
return out;
|
|
3164
|
+
}
|
|
3165
|
+
|
|
3166
|
+
function rewriteTerm(t) {
|
|
3167
|
+
// Replace list nodes (Blank/Iri) by their constructed ListTerm when possible
|
|
3168
|
+
const k = nodeKey(t);
|
|
3169
|
+
if (k) {
|
|
3170
|
+
const built = buildListForKey(k);
|
|
3171
|
+
if (built) return built;
|
|
3172
|
+
// Also rewrite rdf:nil even if not otherwise referenced
|
|
3173
|
+
if (t instanceof Iri && t.value === RDF_NIL) return new ListTerm([]);
|
|
3174
|
+
return t;
|
|
3175
|
+
}
|
|
3176
|
+
if (t instanceof ListTerm) {
|
|
3177
|
+
let changed = false;
|
|
3178
|
+
const elems = t.elems.map((e) => {
|
|
3179
|
+
const r = rewriteTerm(e);
|
|
3180
|
+
if (r !== e) changed = true;
|
|
3181
|
+
return r;
|
|
3182
|
+
});
|
|
3183
|
+
return changed ? new ListTerm(elems) : t;
|
|
3184
|
+
}
|
|
3185
|
+
if (t instanceof OpenListTerm) {
|
|
3186
|
+
let changed = false;
|
|
3187
|
+
const prefix = t.prefix.map((e) => {
|
|
3188
|
+
const r = rewriteTerm(e);
|
|
3189
|
+
if (r !== e) changed = true;
|
|
3190
|
+
return r;
|
|
3191
|
+
});
|
|
3192
|
+
return changed ? new OpenListTerm(prefix, t.tailVar) : t;
|
|
3193
|
+
}
|
|
3194
|
+
if (t instanceof FormulaTerm) {
|
|
3195
|
+
for (const tr of t.triples) rewriteTriple(tr);
|
|
3196
|
+
return t;
|
|
3197
|
+
}
|
|
3198
|
+
return t;
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
function rewriteTriple(tr) {
|
|
3202
|
+
tr.s = rewriteTerm(tr.s);
|
|
3203
|
+
tr.p = rewriteTerm(tr.p);
|
|
3204
|
+
tr.o = rewriteTerm(tr.o);
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3207
|
+
// Pre-build all reachable list heads
|
|
3208
|
+
for (const k of firstMap.keys()) buildListForKey(k);
|
|
3209
|
+
|
|
3210
|
+
// Rewrite input triples + rules in-place
|
|
3211
|
+
for (const tr of triples) rewriteTriple(tr);
|
|
3212
|
+
for (const r of forwardRules) {
|
|
3213
|
+
for (const tr of r.premise) rewriteTriple(tr);
|
|
3214
|
+
for (const tr of r.conclusion) rewriteTriple(tr);
|
|
3215
|
+
}
|
|
3216
|
+
for (const r of backwardRules) {
|
|
3217
|
+
for (const tr of r.premise) rewriteTriple(tr);
|
|
3218
|
+
for (const tr of r.conclusion) rewriteTriple(tr);
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3222
|
+
// ===========================================================================
|
|
3074
3223
|
// Crypto builtin helpers
|
|
3075
|
-
//
|
|
3224
|
+
// ===========================================================================
|
|
3076
3225
|
|
|
3077
3226
|
function hashLiteralTerm(t, algo) {
|
|
3078
3227
|
if (!(t instanceof Literal)) return null;
|
|
@@ -3098,9 +3247,9 @@ function evalCryptoHashBuiltin(g, subst, algo) {
|
|
|
3098
3247
|
return s2 !== null ? [s2] : [];
|
|
3099
3248
|
}
|
|
3100
3249
|
|
|
3101
|
-
//
|
|
3250
|
+
// ===========================================================================
|
|
3102
3251
|
// Builtin evaluation
|
|
3103
|
-
//
|
|
3252
|
+
// ===========================================================================
|
|
3104
3253
|
// Backward proof & builtins mutual recursion — declarations first
|
|
3105
3254
|
|
|
3106
3255
|
function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
@@ -4560,9 +4709,9 @@ function isBuiltinPred(p) {
|
|
|
4560
4709
|
);
|
|
4561
4710
|
}
|
|
4562
4711
|
|
|
4563
|
-
//
|
|
4712
|
+
// ===========================================================================
|
|
4564
4713
|
// Backward proof (SLD-style)
|
|
4565
|
-
//
|
|
4714
|
+
// ===========================================================================
|
|
4566
4715
|
|
|
4567
4716
|
function standardizeRule(rule, gen) {
|
|
4568
4717
|
function renameTerm(t, vmap, genArr) {
|
|
@@ -4609,9 +4758,9 @@ function listHasTriple(list, tr) {
|
|
|
4609
4758
|
return list.some((t) => triplesEqual(t, tr));
|
|
4610
4759
|
}
|
|
4611
4760
|
|
|
4612
|
-
//
|
|
4761
|
+
// ===========================================================================
|
|
4613
4762
|
// Substitution compaction (to avoid O(depth^2) in deep backward chains)
|
|
4614
|
-
//
|
|
4763
|
+
// ===========================================================================
|
|
4615
4764
|
//
|
|
4616
4765
|
// Why: backward chaining with standardizeRule introduces fresh variables at
|
|
4617
4766
|
// each step. composeSubst frequently copies a growing substitution object.
|
|
@@ -4846,9 +4995,9 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen) {
|
|
|
4846
4995
|
return results;
|
|
4847
4996
|
}
|
|
4848
4997
|
|
|
4849
|
-
//
|
|
4998
|
+
// ===========================================================================
|
|
4850
4999
|
// Forward chaining to fixpoint
|
|
4851
|
-
//
|
|
5000
|
+
// ===========================================================================
|
|
4852
5001
|
|
|
4853
5002
|
function forwardChain(facts, forwardRules, backRules) {
|
|
4854
5003
|
ensureFactIndexes(facts);
|
|
@@ -5032,9 +5181,9 @@ function forwardChain(facts, forwardRules, backRules) {
|
|
|
5032
5181
|
return derivedForward;
|
|
5033
5182
|
}
|
|
5034
5183
|
|
|
5035
|
-
//
|
|
5184
|
+
// ===========================================================================
|
|
5036
5185
|
// Pretty printing as N3/Turtle
|
|
5037
|
-
//
|
|
5186
|
+
// ===========================================================================
|
|
5038
5187
|
|
|
5039
5188
|
function termToN3(t, pref) {
|
|
5040
5189
|
if (t instanceof Iri) {
|
|
@@ -5201,179 +5350,9 @@ function printExplanation(df, prefixes) {
|
|
|
5201
5350
|
console.log('# ----------------------------------------------------------------------\n');
|
|
5202
5351
|
}
|
|
5203
5352
|
|
|
5204
|
-
//
|
|
5205
|
-
// Misc helpers
|
|
5206
|
-
// ============================================================================
|
|
5207
|
-
|
|
5208
|
-
// Turn RDF Collections described with rdf:first/rdf:rest (+ rdf:nil) into ListTerm terms.
|
|
5209
|
-
// This mutates triples/rules in-place so list:* builtins work on RDF-serialized lists too.
|
|
5210
|
-
function materializeRdfLists(triples, forwardRules, backwardRules) {
|
|
5211
|
-
const RDF_FIRST = RDF_NS + 'first';
|
|
5212
|
-
const RDF_REST = RDF_NS + 'rest';
|
|
5213
|
-
const RDF_NIL = RDF_NS + 'nil';
|
|
5214
|
-
|
|
5215
|
-
function nodeKey(t) {
|
|
5216
|
-
if (t instanceof Blank) return 'B:' + t.label;
|
|
5217
|
-
if (t instanceof Iri) return 'I:' + t.value;
|
|
5218
|
-
return null;
|
|
5219
|
-
}
|
|
5220
|
-
|
|
5221
|
-
// Collect first/rest arcs from *input triples*
|
|
5222
|
-
const firstMap = new Map(); // key(subject) -> Term (object)
|
|
5223
|
-
const restMap = new Map(); // key(subject) -> Term (object)
|
|
5224
|
-
for (const tr of triples) {
|
|
5225
|
-
if (!(tr.p instanceof Iri)) continue;
|
|
5226
|
-
const k = nodeKey(tr.s);
|
|
5227
|
-
if (!k) continue;
|
|
5228
|
-
if (tr.p.value === RDF_FIRST) firstMap.set(k, tr.o);
|
|
5229
|
-
else if (tr.p.value === RDF_REST) restMap.set(k, tr.o);
|
|
5230
|
-
}
|
|
5231
|
-
if (!firstMap.size && !restMap.size) return;
|
|
5232
|
-
|
|
5233
|
-
const cache = new Map(); // key(node) -> ListTerm
|
|
5234
|
-
const visiting = new Set(); // cycle guard
|
|
5235
|
-
|
|
5236
|
-
function buildListForKey(k) {
|
|
5237
|
-
if (cache.has(k)) return cache.get(k);
|
|
5238
|
-
if (visiting.has(k)) return null; // cycle => not a well-formed list
|
|
5239
|
-
visiting.add(k);
|
|
5240
|
-
|
|
5241
|
-
// rdf:nil => ()
|
|
5242
|
-
if (k === 'I:' + RDF_NIL) {
|
|
5243
|
-
const empty = new ListTerm([]);
|
|
5244
|
-
cache.set(k, empty);
|
|
5245
|
-
visiting.delete(k);
|
|
5246
|
-
return empty;
|
|
5247
|
-
}
|
|
5248
|
-
|
|
5249
|
-
const head = firstMap.get(k);
|
|
5250
|
-
const tail = restMap.get(k);
|
|
5251
|
-
if (head === undefined || tail === undefined) {
|
|
5252
|
-
visiting.delete(k);
|
|
5253
|
-
return null; // not a full cons cell
|
|
5254
|
-
}
|
|
5255
|
-
|
|
5256
|
-
const headTerm = rewriteTerm(head);
|
|
5257
|
-
|
|
5258
|
-
let tailListElems = null;
|
|
5259
|
-
if (tail instanceof Iri && tail.value === RDF_NIL) {
|
|
5260
|
-
tailListElems = [];
|
|
5261
|
-
} else {
|
|
5262
|
-
const tk = nodeKey(tail);
|
|
5263
|
-
if (!tk) {
|
|
5264
|
-
visiting.delete(k);
|
|
5265
|
-
return null;
|
|
5266
|
-
}
|
|
5267
|
-
const tailList = buildListForKey(tk);
|
|
5268
|
-
if (!tailList) {
|
|
5269
|
-
visiting.delete(k);
|
|
5270
|
-
return null;
|
|
5271
|
-
}
|
|
5272
|
-
tailListElems = tailList.elems;
|
|
5273
|
-
}
|
|
5274
|
-
|
|
5275
|
-
const out = new ListTerm([headTerm, ...tailListElems]);
|
|
5276
|
-
cache.set(k, out);
|
|
5277
|
-
visiting.delete(k);
|
|
5278
|
-
return out;
|
|
5279
|
-
}
|
|
5280
|
-
|
|
5281
|
-
function rewriteTerm(t) {
|
|
5282
|
-
// Replace list nodes (Blank/Iri) by their constructed ListTerm when possible
|
|
5283
|
-
const k = nodeKey(t);
|
|
5284
|
-
if (k) {
|
|
5285
|
-
const built = buildListForKey(k);
|
|
5286
|
-
if (built) return built;
|
|
5287
|
-
// Also rewrite rdf:nil even if not otherwise referenced
|
|
5288
|
-
if (t instanceof Iri && t.value === RDF_NIL) return new ListTerm([]);
|
|
5289
|
-
return t;
|
|
5290
|
-
}
|
|
5291
|
-
if (t instanceof ListTerm) {
|
|
5292
|
-
let changed = false;
|
|
5293
|
-
const elems = t.elems.map((e) => {
|
|
5294
|
-
const r = rewriteTerm(e);
|
|
5295
|
-
if (r !== e) changed = true;
|
|
5296
|
-
return r;
|
|
5297
|
-
});
|
|
5298
|
-
return changed ? new ListTerm(elems) : t;
|
|
5299
|
-
}
|
|
5300
|
-
if (t instanceof OpenListTerm) {
|
|
5301
|
-
let changed = false;
|
|
5302
|
-
const prefix = t.prefix.map((e) => {
|
|
5303
|
-
const r = rewriteTerm(e);
|
|
5304
|
-
if (r !== e) changed = true;
|
|
5305
|
-
return r;
|
|
5306
|
-
});
|
|
5307
|
-
return changed ? new OpenListTerm(prefix, t.tailVar) : t;
|
|
5308
|
-
}
|
|
5309
|
-
if (t instanceof FormulaTerm) {
|
|
5310
|
-
for (const tr of t.triples) rewriteTriple(tr);
|
|
5311
|
-
return t;
|
|
5312
|
-
}
|
|
5313
|
-
return t;
|
|
5314
|
-
}
|
|
5315
|
-
|
|
5316
|
-
function rewriteTriple(tr) {
|
|
5317
|
-
tr.s = rewriteTerm(tr.s);
|
|
5318
|
-
tr.p = rewriteTerm(tr.p);
|
|
5319
|
-
tr.o = rewriteTerm(tr.o);
|
|
5320
|
-
}
|
|
5321
|
-
|
|
5322
|
-
// Pre-build all reachable list heads
|
|
5323
|
-
for (const k of firstMap.keys()) buildListForKey(k);
|
|
5324
|
-
|
|
5325
|
-
// Rewrite input triples + rules in-place
|
|
5326
|
-
for (const tr of triples) rewriteTriple(tr);
|
|
5327
|
-
for (const r of forwardRules) {
|
|
5328
|
-
for (const tr of r.premise) rewriteTriple(tr);
|
|
5329
|
-
for (const tr of r.conclusion) rewriteTriple(tr);
|
|
5330
|
-
}
|
|
5331
|
-
for (const r of backwardRules) {
|
|
5332
|
-
for (const tr of r.premise) rewriteTriple(tr);
|
|
5333
|
-
for (const tr of r.conclusion) rewriteTriple(tr);
|
|
5334
|
-
}
|
|
5335
|
-
}
|
|
5336
|
-
|
|
5337
|
-
function localIsoDateTimeString(d) {
|
|
5338
|
-
function pad(n, width = 2) {
|
|
5339
|
-
return String(n).padStart(width, '0');
|
|
5340
|
-
}
|
|
5341
|
-
const year = d.getFullYear();
|
|
5342
|
-
const month = d.getMonth() + 1;
|
|
5343
|
-
const day = d.getDate();
|
|
5344
|
-
const hour = d.getHours();
|
|
5345
|
-
const min = d.getMinutes();
|
|
5346
|
-
const sec = d.getSeconds();
|
|
5347
|
-
const ms = d.getMilliseconds();
|
|
5348
|
-
const offsetMin = -d.getTimezoneOffset(); // minutes east of UTC
|
|
5349
|
-
const sign = offsetMin >= 0 ? '+' : '-';
|
|
5350
|
-
const abs = Math.abs(offsetMin);
|
|
5351
|
-
const oh = Math.floor(abs / 60);
|
|
5352
|
-
const om = abs % 60;
|
|
5353
|
-
const msPart = ms ? '.' + String(ms).padStart(3, '0') : '';
|
|
5354
|
-
return (
|
|
5355
|
-
pad(year, 4) +
|
|
5356
|
-
'-' +
|
|
5357
|
-
pad(month) +
|
|
5358
|
-
'-' +
|
|
5359
|
-
pad(day) +
|
|
5360
|
-
'T' +
|
|
5361
|
-
pad(hour) +
|
|
5362
|
-
':' +
|
|
5363
|
-
pad(min) +
|
|
5364
|
-
':' +
|
|
5365
|
-
pad(sec) +
|
|
5366
|
-
msPart +
|
|
5367
|
-
sign +
|
|
5368
|
-
pad(oh) +
|
|
5369
|
-
':' +
|
|
5370
|
-
pad(om)
|
|
5371
|
-
);
|
|
5372
|
-
}
|
|
5373
|
-
|
|
5374
|
-
// ============================================================================
|
|
5353
|
+
// ===========================================================================
|
|
5375
5354
|
// CLI entry point
|
|
5376
|
-
//
|
|
5355
|
+
// ===========================================================================
|
|
5377
5356
|
function main() {
|
|
5378
5357
|
// Drop "node" and script name; keep only user-provided args
|
|
5379
5358
|
const argv = process.argv.slice(2);
|