eyeling 1.6.17 → 1.6.18
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 +218 -214
- 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#';
|
|
@@ -97,6 +97,47 @@ function normalizeDateTimeLex(s) {
|
|
|
97
97
|
return t.trim();
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
// ===========================================================================
|
|
101
|
+
// Run-level time helpers
|
|
102
|
+
// ===========================================================================
|
|
103
|
+
|
|
104
|
+
function localIsoDateTimeString(d) {
|
|
105
|
+
function pad(n, width = 2) {
|
|
106
|
+
return String(n).padStart(width, '0');
|
|
107
|
+
}
|
|
108
|
+
const year = d.getFullYear();
|
|
109
|
+
const month = d.getMonth() + 1;
|
|
110
|
+
const day = d.getDate();
|
|
111
|
+
const hour = d.getHours();
|
|
112
|
+
const min = d.getMinutes();
|
|
113
|
+
const sec = d.getSeconds();
|
|
114
|
+
const ms = d.getMilliseconds();
|
|
115
|
+
const offsetMin = -d.getTimezoneOffset(); // minutes east of UTC
|
|
116
|
+
const sign = offsetMin >= 0 ? '+' : '-';
|
|
117
|
+
const abs = Math.abs(offsetMin);
|
|
118
|
+
const oh = Math.floor(abs / 60);
|
|
119
|
+
const om = abs % 60;
|
|
120
|
+
const msPart = ms ? '.' + String(ms).padStart(3, '0') : '';
|
|
121
|
+
return (
|
|
122
|
+
pad(year, 4) +
|
|
123
|
+
'-' +
|
|
124
|
+
pad(month) +
|
|
125
|
+
'-' +
|
|
126
|
+
pad(day) +
|
|
127
|
+
'T' +
|
|
128
|
+
pad(hour) +
|
|
129
|
+
':' +
|
|
130
|
+
pad(min) +
|
|
131
|
+
':' +
|
|
132
|
+
pad(sec) +
|
|
133
|
+
msPart +
|
|
134
|
+
sign +
|
|
135
|
+
pad(oh) +
|
|
136
|
+
':' +
|
|
137
|
+
pad(om)
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
100
141
|
function utcIsoDateTimeStringFromEpochSeconds(sec) {
|
|
101
142
|
const ms = sec * 1000;
|
|
102
143
|
const d = new Date(ms);
|
|
@@ -154,9 +195,9 @@ function deterministicSkolemIdFromKey(key) {
|
|
|
154
195
|
|
|
155
196
|
let runLocalTimeCache = null;
|
|
156
197
|
|
|
157
|
-
//
|
|
198
|
+
// ===========================================================================
|
|
158
199
|
// AST (Abstract Syntax Tree)
|
|
159
|
-
//
|
|
200
|
+
// ===========================================================================
|
|
160
201
|
|
|
161
202
|
class Term {}
|
|
162
203
|
|
|
@@ -238,9 +279,9 @@ class DerivedFact {
|
|
|
238
279
|
}
|
|
239
280
|
}
|
|
240
281
|
|
|
241
|
-
//
|
|
282
|
+
// ===========================================================================
|
|
242
283
|
// LEXER
|
|
243
|
-
//
|
|
284
|
+
// ===========================================================================
|
|
244
285
|
|
|
245
286
|
class Token {
|
|
246
287
|
constructor(typ, value = null) {
|
|
@@ -631,9 +672,9 @@ function lex(inputText) {
|
|
|
631
672
|
return tokens;
|
|
632
673
|
}
|
|
633
674
|
|
|
634
|
-
//
|
|
675
|
+
// ===========================================================================
|
|
635
676
|
// PREFIX ENVIRONMENT
|
|
636
|
-
//
|
|
677
|
+
// ===========================================================================
|
|
637
678
|
|
|
638
679
|
class PrefixEnv {
|
|
639
680
|
constructor(map) {
|
|
@@ -789,9 +830,9 @@ function collectBlankLabelsInTriples(triples) {
|
|
|
789
830
|
return acc;
|
|
790
831
|
}
|
|
791
832
|
|
|
792
|
-
//
|
|
833
|
+
// ===========================================================================
|
|
793
834
|
// PARSER
|
|
794
|
-
//
|
|
835
|
+
// ===========================================================================
|
|
795
836
|
|
|
796
837
|
class Parser {
|
|
797
838
|
constructor(tokens) {
|
|
@@ -1231,9 +1272,9 @@ class Parser {
|
|
|
1231
1272
|
}
|
|
1232
1273
|
}
|
|
1233
1274
|
|
|
1234
|
-
//
|
|
1275
|
+
// ===========================================================================
|
|
1235
1276
|
// Blank-node lifting and Skolemization
|
|
1236
|
-
//
|
|
1277
|
+
// ===========================================================================
|
|
1237
1278
|
|
|
1238
1279
|
function liftBlankRuleVars(premise, conclusion) {
|
|
1239
1280
|
function convertTerm(t, mapping, counter) {
|
|
@@ -1340,9 +1381,9 @@ function skolemizeTripleForHeadBlanks(tr, headBlankLabels, mapping, skCounter, f
|
|
|
1340
1381
|
);
|
|
1341
1382
|
}
|
|
1342
1383
|
|
|
1343
|
-
//
|
|
1384
|
+
// ===========================================================================
|
|
1344
1385
|
// Alpha equivalence helpers
|
|
1345
|
-
//
|
|
1386
|
+
// ===========================================================================
|
|
1346
1387
|
|
|
1347
1388
|
function termsEqual(a, b) {
|
|
1348
1389
|
if (a === b) return true;
|
|
@@ -1614,9 +1655,9 @@ function hasAlphaEquiv(triples, tr) {
|
|
|
1614
1655
|
return triples.some((t) => alphaEqTriple(t, tr));
|
|
1615
1656
|
}
|
|
1616
1657
|
|
|
1617
|
-
//
|
|
1658
|
+
// ===========================================================================
|
|
1618
1659
|
// Indexes (facts + backward rules)
|
|
1619
|
-
//
|
|
1660
|
+
// ===========================================================================
|
|
1620
1661
|
//
|
|
1621
1662
|
// Facts:
|
|
1622
1663
|
// - __byPred: Map<predicateIRI, Triple[]>
|
|
@@ -1784,9 +1825,9 @@ function indexBackRule(backRules, r) {
|
|
|
1784
1825
|
}
|
|
1785
1826
|
}
|
|
1786
1827
|
|
|
1787
|
-
//
|
|
1828
|
+
// ===========================================================================
|
|
1788
1829
|
// Special predicate helpers
|
|
1789
|
-
//
|
|
1830
|
+
// ===========================================================================
|
|
1790
1831
|
|
|
1791
1832
|
function isRdfTypePred(p) {
|
|
1792
1833
|
return p instanceof Iri && p.value === RDF_NS + 'type';
|
|
@@ -1804,9 +1845,9 @@ function isLogImpliedBy(p) {
|
|
|
1804
1845
|
return p instanceof Iri && p.value === LOG_NS + 'impliedBy';
|
|
1805
1846
|
}
|
|
1806
1847
|
|
|
1807
|
-
//
|
|
1848
|
+
// ===========================================================================
|
|
1808
1849
|
// Constraint / "test" builtins
|
|
1809
|
-
//
|
|
1850
|
+
// ===========================================================================
|
|
1810
1851
|
|
|
1811
1852
|
function isConstraintBuiltin(tr) {
|
|
1812
1853
|
if (!(tr.p instanceof Iri)) return false;
|
|
@@ -1874,9 +1915,9 @@ function reorderPremiseForConstraints(premise) {
|
|
|
1874
1915
|
return normal.concat(delayed);
|
|
1875
1916
|
}
|
|
1876
1917
|
|
|
1877
|
-
//
|
|
1918
|
+
// ===========================================================================
|
|
1878
1919
|
// Unification + substitution
|
|
1879
|
-
//
|
|
1920
|
+
// ===========================================================================
|
|
1880
1921
|
|
|
1881
1922
|
function containsVarTerm(t, v) {
|
|
1882
1923
|
if (t instanceof Var) return t.name === v;
|
|
@@ -2188,9 +2229,9 @@ function composeSubst(outer, delta) {
|
|
|
2188
2229
|
return out;
|
|
2189
2230
|
}
|
|
2190
2231
|
|
|
2191
|
-
//
|
|
2232
|
+
// ===========================================================================
|
|
2192
2233
|
// BUILTINS
|
|
2193
|
-
//
|
|
2234
|
+
// ===========================================================================
|
|
2194
2235
|
|
|
2195
2236
|
function literalParts(lit) {
|
|
2196
2237
|
// Split a literal into lexical form and datatype IRI (if any).
|
|
@@ -2528,9 +2569,9 @@ function parseXsdFloatSpecialLex(s) {
|
|
|
2528
2569
|
return null;
|
|
2529
2570
|
}
|
|
2530
2571
|
|
|
2531
|
-
//
|
|
2572
|
+
// ===========================================================================
|
|
2532
2573
|
// Math builtin helpers
|
|
2533
|
-
//
|
|
2574
|
+
// ===========================================================================
|
|
2534
2575
|
|
|
2535
2576
|
function formatXsdFloatSpecialLex(n) {
|
|
2536
2577
|
if (n === Infinity) return 'INF';
|
|
@@ -2686,9 +2727,9 @@ function pow10n(k) {
|
|
|
2686
2727
|
return 10n ** BigInt(k);
|
|
2687
2728
|
}
|
|
2688
2729
|
|
|
2689
|
-
//
|
|
2730
|
+
// ===========================================================================
|
|
2690
2731
|
// Time & duration builtin helpers
|
|
2691
|
-
//
|
|
2732
|
+
// ===========================================================================
|
|
2692
2733
|
|
|
2693
2734
|
function parseXsdDateTerm(t) {
|
|
2694
2735
|
if (!(t instanceof Literal)) return null;
|
|
@@ -3017,9 +3058,9 @@ function evalUnaryMathRel(g, subst, forwardFn, inverseFn /* may be null */) {
|
|
|
3017
3058
|
return [];
|
|
3018
3059
|
}
|
|
3019
3060
|
|
|
3020
|
-
//
|
|
3061
|
+
// ===========================================================================
|
|
3021
3062
|
// List builtin helpers
|
|
3022
|
-
//
|
|
3063
|
+
// ===========================================================================
|
|
3023
3064
|
|
|
3024
3065
|
function listAppendSplit(parts, resElems, subst) {
|
|
3025
3066
|
if (!parts.length) {
|
|
@@ -3070,9 +3111,142 @@ function evalListRestLikeBuiltin(sTerm, oTerm, subst) {
|
|
|
3070
3111
|
return [];
|
|
3071
3112
|
}
|
|
3072
3113
|
|
|
3073
|
-
//
|
|
3114
|
+
// ===========================================================================
|
|
3115
|
+
// RDF list materialization
|
|
3116
|
+
// ===========================================================================
|
|
3117
|
+
|
|
3118
|
+
// Turn RDF Collections described with rdf:first/rdf:rest (+ rdf:nil) into ListTerm terms.
|
|
3119
|
+
// This mutates triples/rules in-place so list:* builtins work on RDF-serialized lists too.
|
|
3120
|
+
function materializeRdfLists(triples, forwardRules, backwardRules) {
|
|
3121
|
+
const RDF_FIRST = RDF_NS + 'first';
|
|
3122
|
+
const RDF_REST = RDF_NS + 'rest';
|
|
3123
|
+
const RDF_NIL = RDF_NS + 'nil';
|
|
3124
|
+
|
|
3125
|
+
function nodeKey(t) {
|
|
3126
|
+
if (t instanceof Blank) return 'B:' + t.label;
|
|
3127
|
+
if (t instanceof Iri) return 'I:' + t.value;
|
|
3128
|
+
return null;
|
|
3129
|
+
}
|
|
3130
|
+
|
|
3131
|
+
// Collect first/rest arcs from *input triples*
|
|
3132
|
+
const firstMap = new Map(); // key(subject) -> Term (object)
|
|
3133
|
+
const restMap = new Map(); // key(subject) -> Term (object)
|
|
3134
|
+
for (const tr of triples) {
|
|
3135
|
+
if (!(tr.p instanceof Iri)) continue;
|
|
3136
|
+
const k = nodeKey(tr.s);
|
|
3137
|
+
if (!k) continue;
|
|
3138
|
+
if (tr.p.value === RDF_FIRST) firstMap.set(k, tr.o);
|
|
3139
|
+
else if (tr.p.value === RDF_REST) restMap.set(k, tr.o);
|
|
3140
|
+
}
|
|
3141
|
+
if (!firstMap.size && !restMap.size) return;
|
|
3142
|
+
|
|
3143
|
+
const cache = new Map(); // key(node) -> ListTerm
|
|
3144
|
+
const visiting = new Set(); // cycle guard
|
|
3145
|
+
|
|
3146
|
+
function buildListForKey(k) {
|
|
3147
|
+
if (cache.has(k)) return cache.get(k);
|
|
3148
|
+
if (visiting.has(k)) return null; // cycle => not a well-formed list
|
|
3149
|
+
visiting.add(k);
|
|
3150
|
+
|
|
3151
|
+
// rdf:nil => ()
|
|
3152
|
+
if (k === 'I:' + RDF_NIL) {
|
|
3153
|
+
const empty = new ListTerm([]);
|
|
3154
|
+
cache.set(k, empty);
|
|
3155
|
+
visiting.delete(k);
|
|
3156
|
+
return empty;
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
const head = firstMap.get(k);
|
|
3160
|
+
const tail = restMap.get(k);
|
|
3161
|
+
if (head === undefined || tail === undefined) {
|
|
3162
|
+
visiting.delete(k);
|
|
3163
|
+
return null; // not a full cons cell
|
|
3164
|
+
}
|
|
3165
|
+
|
|
3166
|
+
const headTerm = rewriteTerm(head);
|
|
3167
|
+
|
|
3168
|
+
let tailListElems = null;
|
|
3169
|
+
if (tail instanceof Iri && tail.value === RDF_NIL) {
|
|
3170
|
+
tailListElems = [];
|
|
3171
|
+
} else {
|
|
3172
|
+
const tk = nodeKey(tail);
|
|
3173
|
+
if (!tk) {
|
|
3174
|
+
visiting.delete(k);
|
|
3175
|
+
return null;
|
|
3176
|
+
}
|
|
3177
|
+
const tailList = buildListForKey(tk);
|
|
3178
|
+
if (!tailList) {
|
|
3179
|
+
visiting.delete(k);
|
|
3180
|
+
return null;
|
|
3181
|
+
}
|
|
3182
|
+
tailListElems = tailList.elems;
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3185
|
+
const out = new ListTerm([headTerm, ...tailListElems]);
|
|
3186
|
+
cache.set(k, out);
|
|
3187
|
+
visiting.delete(k);
|
|
3188
|
+
return out;
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
function rewriteTerm(t) {
|
|
3192
|
+
// Replace list nodes (Blank/Iri) by their constructed ListTerm when possible
|
|
3193
|
+
const k = nodeKey(t);
|
|
3194
|
+
if (k) {
|
|
3195
|
+
const built = buildListForKey(k);
|
|
3196
|
+
if (built) return built;
|
|
3197
|
+
// Also rewrite rdf:nil even if not otherwise referenced
|
|
3198
|
+
if (t instanceof Iri && t.value === RDF_NIL) return new ListTerm([]);
|
|
3199
|
+
return t;
|
|
3200
|
+
}
|
|
3201
|
+
if (t instanceof ListTerm) {
|
|
3202
|
+
let changed = false;
|
|
3203
|
+
const elems = t.elems.map((e) => {
|
|
3204
|
+
const r = rewriteTerm(e);
|
|
3205
|
+
if (r !== e) changed = true;
|
|
3206
|
+
return r;
|
|
3207
|
+
});
|
|
3208
|
+
return changed ? new ListTerm(elems) : t;
|
|
3209
|
+
}
|
|
3210
|
+
if (t instanceof OpenListTerm) {
|
|
3211
|
+
let changed = false;
|
|
3212
|
+
const prefix = t.prefix.map((e) => {
|
|
3213
|
+
const r = rewriteTerm(e);
|
|
3214
|
+
if (r !== e) changed = true;
|
|
3215
|
+
return r;
|
|
3216
|
+
});
|
|
3217
|
+
return changed ? new OpenListTerm(prefix, t.tailVar) : t;
|
|
3218
|
+
}
|
|
3219
|
+
if (t instanceof FormulaTerm) {
|
|
3220
|
+
for (const tr of t.triples) rewriteTriple(tr);
|
|
3221
|
+
return t;
|
|
3222
|
+
}
|
|
3223
|
+
return t;
|
|
3224
|
+
}
|
|
3225
|
+
|
|
3226
|
+
function rewriteTriple(tr) {
|
|
3227
|
+
tr.s = rewriteTerm(tr.s);
|
|
3228
|
+
tr.p = rewriteTerm(tr.p);
|
|
3229
|
+
tr.o = rewriteTerm(tr.o);
|
|
3230
|
+
}
|
|
3231
|
+
|
|
3232
|
+
// Pre-build all reachable list heads
|
|
3233
|
+
for (const k of firstMap.keys()) buildListForKey(k);
|
|
3234
|
+
|
|
3235
|
+
// Rewrite input triples + rules in-place
|
|
3236
|
+
for (const tr of triples) rewriteTriple(tr);
|
|
3237
|
+
for (const r of forwardRules) {
|
|
3238
|
+
for (const tr of r.premise) rewriteTriple(tr);
|
|
3239
|
+
for (const tr of r.conclusion) rewriteTriple(tr);
|
|
3240
|
+
}
|
|
3241
|
+
for (const r of backwardRules) {
|
|
3242
|
+
for (const tr of r.premise) rewriteTriple(tr);
|
|
3243
|
+
for (const tr of r.conclusion) rewriteTriple(tr);
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
|
|
3247
|
+
// ===========================================================================
|
|
3074
3248
|
// Crypto builtin helpers
|
|
3075
|
-
//
|
|
3249
|
+
// ===========================================================================
|
|
3076
3250
|
|
|
3077
3251
|
function hashLiteralTerm(t, algo) {
|
|
3078
3252
|
if (!(t instanceof Literal)) return null;
|
|
@@ -3098,9 +3272,9 @@ function evalCryptoHashBuiltin(g, subst, algo) {
|
|
|
3098
3272
|
return s2 !== null ? [s2] : [];
|
|
3099
3273
|
}
|
|
3100
3274
|
|
|
3101
|
-
//
|
|
3275
|
+
// ===========================================================================
|
|
3102
3276
|
// Builtin evaluation
|
|
3103
|
-
//
|
|
3277
|
+
// ===========================================================================
|
|
3104
3278
|
// Backward proof & builtins mutual recursion — declarations first
|
|
3105
3279
|
|
|
3106
3280
|
function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
@@ -4560,9 +4734,9 @@ function isBuiltinPred(p) {
|
|
|
4560
4734
|
);
|
|
4561
4735
|
}
|
|
4562
4736
|
|
|
4563
|
-
//
|
|
4737
|
+
// ===========================================================================
|
|
4564
4738
|
// Backward proof (SLD-style)
|
|
4565
|
-
//
|
|
4739
|
+
// ===========================================================================
|
|
4566
4740
|
|
|
4567
4741
|
function standardizeRule(rule, gen) {
|
|
4568
4742
|
function renameTerm(t, vmap, genArr) {
|
|
@@ -4609,9 +4783,9 @@ function listHasTriple(list, tr) {
|
|
|
4609
4783
|
return list.some((t) => triplesEqual(t, tr));
|
|
4610
4784
|
}
|
|
4611
4785
|
|
|
4612
|
-
//
|
|
4786
|
+
// ===========================================================================
|
|
4613
4787
|
// Substitution compaction (to avoid O(depth^2) in deep backward chains)
|
|
4614
|
-
//
|
|
4788
|
+
// ===========================================================================
|
|
4615
4789
|
//
|
|
4616
4790
|
// Why: backward chaining with standardizeRule introduces fresh variables at
|
|
4617
4791
|
// each step. composeSubst frequently copies a growing substitution object.
|
|
@@ -4846,9 +5020,9 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen) {
|
|
|
4846
5020
|
return results;
|
|
4847
5021
|
}
|
|
4848
5022
|
|
|
4849
|
-
//
|
|
5023
|
+
// ===========================================================================
|
|
4850
5024
|
// Forward chaining to fixpoint
|
|
4851
|
-
//
|
|
5025
|
+
// ===========================================================================
|
|
4852
5026
|
|
|
4853
5027
|
function forwardChain(facts, forwardRules, backRules) {
|
|
4854
5028
|
ensureFactIndexes(facts);
|
|
@@ -5032,9 +5206,9 @@ function forwardChain(facts, forwardRules, backRules) {
|
|
|
5032
5206
|
return derivedForward;
|
|
5033
5207
|
}
|
|
5034
5208
|
|
|
5035
|
-
//
|
|
5209
|
+
// ===========================================================================
|
|
5036
5210
|
// Pretty printing as N3/Turtle
|
|
5037
|
-
//
|
|
5211
|
+
// ===========================================================================
|
|
5038
5212
|
|
|
5039
5213
|
function termToN3(t, pref) {
|
|
5040
5214
|
if (t instanceof Iri) {
|
|
@@ -5201,179 +5375,9 @@ function printExplanation(df, prefixes) {
|
|
|
5201
5375
|
console.log('# ----------------------------------------------------------------------\n');
|
|
5202
5376
|
}
|
|
5203
5377
|
|
|
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
|
-
// ============================================================================
|
|
5378
|
+
// ===========================================================================
|
|
5375
5379
|
// CLI entry point
|
|
5376
|
-
//
|
|
5380
|
+
// ===========================================================================
|
|
5377
5381
|
function main() {
|
|
5378
5382
|
// Drop "node" and script name; keep only user-provided args
|
|
5379
5383
|
const argv = process.argv.slice(2);
|