eyeling 1.6.16 → 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 +367 -424
- 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;
|
|
@@ -1993,6 +2034,10 @@ function applySubstTriple(tr, s) {
|
|
|
1993
2034
|
return new Triple(applySubstTerm(tr.s, s), applySubstTerm(tr.p, s), applySubstTerm(tr.o, s));
|
|
1994
2035
|
}
|
|
1995
2036
|
|
|
2037
|
+
function iriValue(t) {
|
|
2038
|
+
return t instanceof Iri ? t.value : null;
|
|
2039
|
+
}
|
|
2040
|
+
|
|
1996
2041
|
function unifyOpenWithList(prefix, tailv, ys, subst) {
|
|
1997
2042
|
if (ys.length < prefix.length) return null;
|
|
1998
2043
|
let s2 = { ...subst };
|
|
@@ -2184,9 +2229,9 @@ function composeSubst(outer, delta) {
|
|
|
2184
2229
|
return out;
|
|
2185
2230
|
}
|
|
2186
2231
|
|
|
2187
|
-
//
|
|
2232
|
+
// ===========================================================================
|
|
2188
2233
|
// BUILTINS
|
|
2189
|
-
//
|
|
2234
|
+
// ===========================================================================
|
|
2190
2235
|
|
|
2191
2236
|
function literalParts(lit) {
|
|
2192
2237
|
// Split a literal into lexical form and datatype IRI (if any).
|
|
@@ -2524,6 +2569,10 @@ function parseXsdFloatSpecialLex(s) {
|
|
|
2524
2569
|
return null;
|
|
2525
2570
|
}
|
|
2526
2571
|
|
|
2572
|
+
// ===========================================================================
|
|
2573
|
+
// Math builtin helpers
|
|
2574
|
+
// ===========================================================================
|
|
2575
|
+
|
|
2527
2576
|
function formatXsdFloatSpecialLex(n) {
|
|
2528
2577
|
if (n === Infinity) return 'INF';
|
|
2529
2578
|
if (n === -Infinity) return '-INF';
|
|
@@ -2678,6 +2727,10 @@ function pow10n(k) {
|
|
|
2678
2727
|
return 10n ** BigInt(k);
|
|
2679
2728
|
}
|
|
2680
2729
|
|
|
2730
|
+
// ===========================================================================
|
|
2731
|
+
// Time & duration builtin helpers
|
|
2732
|
+
// ===========================================================================
|
|
2733
|
+
|
|
2681
2734
|
function parseXsdDateTerm(t) {
|
|
2682
2735
|
if (!(t instanceof Literal)) return null;
|
|
2683
2736
|
const [lex, dt] = literalParts(t.value);
|
|
@@ -2759,7 +2812,7 @@ function parseNumericForCompareTerm(t) {
|
|
|
2759
2812
|
}
|
|
2760
2813
|
|
|
2761
2814
|
function cmpNumericInfo(aInfo, bInfo, op) {
|
|
2762
|
-
// op is one of ">", "<", ">=", "<="
|
|
2815
|
+
// op is one of ">", "<", ">=", "<=", "==", "!="
|
|
2763
2816
|
if (!aInfo || !bInfo) return false;
|
|
2764
2817
|
|
|
2765
2818
|
if (aInfo.kind === 'bigint' && bInfo.kind === 'bigint') {
|
|
@@ -2784,6 +2837,19 @@ function cmpNumericInfo(aInfo, bInfo, op) {
|
|
|
2784
2837
|
return false;
|
|
2785
2838
|
}
|
|
2786
2839
|
|
|
2840
|
+
function evalNumericComparisonBuiltin(g, subst, op) {
|
|
2841
|
+
const aInfo = parseNumericForCompareTerm(g.s);
|
|
2842
|
+
const bInfo = parseNumericForCompareTerm(g.o);
|
|
2843
|
+
if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, op)) return [{ ...subst }];
|
|
2844
|
+
|
|
2845
|
+
if (g.s instanceof ListTerm && g.s.elems.length === 2) {
|
|
2846
|
+
const a2 = parseNumericForCompareTerm(g.s.elems[0]);
|
|
2847
|
+
const b2 = parseNumericForCompareTerm(g.s.elems[1]);
|
|
2848
|
+
if (a2 && b2 && cmpNumericInfo(a2, b2, op)) return [{ ...subst }];
|
|
2849
|
+
}
|
|
2850
|
+
return [];
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2787
2853
|
function parseNumOrDuration(t) {
|
|
2788
2854
|
const n = parseNum(t);
|
|
2789
2855
|
if (n !== null) return n;
|
|
@@ -2816,24 +2882,6 @@ function formatDurationLiteralFromSeconds(secs) {
|
|
|
2816
2882
|
const literalLex = neg ? `"-P${days}D"` : `"P${days}D"`;
|
|
2817
2883
|
return new Literal(`${literalLex}^^<${XSD_NS}duration>`);
|
|
2818
2884
|
}
|
|
2819
|
-
|
|
2820
|
-
function listAppendSplit(parts, resElems, subst) {
|
|
2821
|
-
if (!parts.length) {
|
|
2822
|
-
if (!resElems.length) return [{ ...subst }];
|
|
2823
|
-
return [];
|
|
2824
|
-
}
|
|
2825
|
-
const out = [];
|
|
2826
|
-
const n = resElems.length;
|
|
2827
|
-
for (let k = 0; k <= n; k++) {
|
|
2828
|
-
const left = new ListTerm(resElems.slice(0, k));
|
|
2829
|
-
let s1 = unifyTermListAppend(parts[0], left, subst);
|
|
2830
|
-
if (s1 === null) continue;
|
|
2831
|
-
const restElems = resElems.slice(k);
|
|
2832
|
-
out.push(...listAppendSplit(parts.slice(1), restElems, s1));
|
|
2833
|
-
}
|
|
2834
|
-
return out;
|
|
2835
|
-
}
|
|
2836
|
-
|
|
2837
2885
|
function numEqualTerm(t, n, eps = 1e-9) {
|
|
2838
2886
|
const v = parseNum(t);
|
|
2839
2887
|
if (v === null) return false;
|
|
@@ -3010,6 +3058,27 @@ function evalUnaryMathRel(g, subst, forwardFn, inverseFn /* may be null */) {
|
|
|
3010
3058
|
return [];
|
|
3011
3059
|
}
|
|
3012
3060
|
|
|
3061
|
+
// ===========================================================================
|
|
3062
|
+
// List builtin helpers
|
|
3063
|
+
// ===========================================================================
|
|
3064
|
+
|
|
3065
|
+
function listAppendSplit(parts, resElems, subst) {
|
|
3066
|
+
if (!parts.length) {
|
|
3067
|
+
if (!resElems.length) return [{ ...subst }];
|
|
3068
|
+
return [];
|
|
3069
|
+
}
|
|
3070
|
+
const out = [];
|
|
3071
|
+
const n = resElems.length;
|
|
3072
|
+
for (let k = 0; k <= n; k++) {
|
|
3073
|
+
const left = new ListTerm(resElems.slice(0, k));
|
|
3074
|
+
let s1 = unifyTermListAppend(parts[0], left, subst);
|
|
3075
|
+
if (s1 === null) continue;
|
|
3076
|
+
const restElems = resElems.slice(k);
|
|
3077
|
+
out.push(...listAppendSplit(parts.slice(1), restElems, s1));
|
|
3078
|
+
}
|
|
3079
|
+
return out;
|
|
3080
|
+
}
|
|
3081
|
+
|
|
3013
3082
|
function evalListFirstLikeBuiltin(sTerm, oTerm, subst) {
|
|
3014
3083
|
if (!(sTerm instanceof ListTerm)) return [];
|
|
3015
3084
|
if (!sTerm.elems.length) return [];
|
|
@@ -3042,175 +3111,219 @@ function evalListRestLikeBuiltin(sTerm, oTerm, subst) {
|
|
|
3042
3111
|
return [];
|
|
3043
3112
|
}
|
|
3044
3113
|
|
|
3045
|
-
//
|
|
3046
|
-
//
|
|
3047
|
-
//
|
|
3114
|
+
// ===========================================================================
|
|
3115
|
+
// RDF list materialization
|
|
3116
|
+
// ===========================================================================
|
|
3048
3117
|
|
|
3049
|
-
|
|
3050
|
-
|
|
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';
|
|
3051
3124
|
|
|
3052
|
-
function
|
|
3053
|
-
|
|
3054
|
-
if (
|
|
3055
|
-
|
|
3056
|
-
const input = stripQuotes(lex);
|
|
3057
|
-
try {
|
|
3058
|
-
const digest = nodeCrypto.createHash(algo).update(input, 'utf8').digest('hex');
|
|
3059
|
-
// plain string literal with the hex digest
|
|
3060
|
-
return new Literal(JSON.stringify(digest));
|
|
3061
|
-
} catch (e) {
|
|
3062
|
-
return null;
|
|
3063
|
-
}
|
|
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;
|
|
3064
3129
|
}
|
|
3065
3130
|
|
|
3066
|
-
//
|
|
3067
|
-
//
|
|
3068
|
-
//
|
|
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;
|
|
3069
3142
|
|
|
3070
|
-
//
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
if (
|
|
3075
|
-
if (
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
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;
|
|
3079
3157
|
}
|
|
3080
|
-
const s2 = unifyTerm(g.o, lit, subst);
|
|
3081
|
-
return s2 !== null ? [s2] : [];
|
|
3082
|
-
}
|
|
3083
3158
|
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
const s2 = { ...subst };
|
|
3090
|
-
s2[g.o.name] = lit;
|
|
3091
|
-
return [s2];
|
|
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
|
|
3092
3164
|
}
|
|
3093
|
-
const s2 = unifyTerm(g.o, lit, subst);
|
|
3094
|
-
return s2 !== null ? [s2] : [];
|
|
3095
|
-
}
|
|
3096
3165
|
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
if (
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
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;
|
|
3105
3183
|
}
|
|
3106
|
-
|
|
3107
|
-
|
|
3184
|
+
|
|
3185
|
+
const out = new ListTerm([headTerm, ...tailListElems]);
|
|
3186
|
+
cache.set(k, out);
|
|
3187
|
+
visiting.delete(k);
|
|
3188
|
+
return out;
|
|
3108
3189
|
}
|
|
3109
3190
|
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
const
|
|
3113
|
-
if (
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
return [
|
|
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;
|
|
3118
3200
|
}
|
|
3119
|
-
|
|
3120
|
-
|
|
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;
|
|
3121
3224
|
}
|
|
3122
3225
|
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3226
|
+
function rewriteTriple(tr) {
|
|
3227
|
+
tr.s = rewriteTerm(tr.s);
|
|
3228
|
+
tr.p = rewriteTerm(tr.p);
|
|
3229
|
+
tr.o = rewriteTerm(tr.o);
|
|
3230
|
+
}
|
|
3126
3231
|
|
|
3127
|
-
//
|
|
3128
|
-
|
|
3129
|
-
const aInfo = parseNumericForCompareTerm(g.s);
|
|
3130
|
-
const bInfo = parseNumericForCompareTerm(g.o);
|
|
3131
|
-
if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, '>')) return [{ ...subst }];
|
|
3232
|
+
// Pre-build all reachable list heads
|
|
3233
|
+
for (const k of firstMap.keys()) buildListForKey(k);
|
|
3132
3234
|
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
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);
|
|
3139
3244
|
}
|
|
3245
|
+
}
|
|
3140
3246
|
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
const bInfo = parseNumericForCompareTerm(g.o);
|
|
3145
|
-
if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, '<')) return [{ ...subst }];
|
|
3247
|
+
// ===========================================================================
|
|
3248
|
+
// Crypto builtin helpers
|
|
3249
|
+
// ===========================================================================
|
|
3146
3250
|
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3251
|
+
function hashLiteralTerm(t, algo) {
|
|
3252
|
+
if (!(t instanceof Literal)) return null;
|
|
3253
|
+
const [lex] = literalParts(t.value);
|
|
3254
|
+
const input = stripQuotes(lex);
|
|
3255
|
+
try {
|
|
3256
|
+
const digest = nodeCrypto.createHash(algo).update(input, 'utf8').digest('hex');
|
|
3257
|
+
return new Literal(JSON.stringify(digest));
|
|
3258
|
+
} catch (e) {
|
|
3259
|
+
return null;
|
|
3153
3260
|
}
|
|
3261
|
+
}
|
|
3154
3262
|
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
const a2 = parseNumericForCompareTerm(g.s.elems[0]);
|
|
3163
|
-
const b2 = parseNumericForCompareTerm(g.s.elems[1]);
|
|
3164
|
-
if (a2 && b2 && cmpNumericInfo(a2, b2, '>=')) return [{ ...subst }];
|
|
3165
|
-
}
|
|
3166
|
-
return [];
|
|
3263
|
+
function evalCryptoHashBuiltin(g, subst, algo) {
|
|
3264
|
+
const lit = hashLiteralTerm(g.s, algo);
|
|
3265
|
+
if (!lit) return [];
|
|
3266
|
+
if (g.o instanceof Var) {
|
|
3267
|
+
const s2 = { ...subst };
|
|
3268
|
+
s2[g.o.name] = lit;
|
|
3269
|
+
return [s2];
|
|
3167
3270
|
}
|
|
3271
|
+
const s2 = unifyTerm(g.o, lit, subst);
|
|
3272
|
+
return s2 !== null ? [s2] : [];
|
|
3273
|
+
}
|
|
3168
3274
|
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, '<=')) return [{ ...subst }];
|
|
3275
|
+
// ===========================================================================
|
|
3276
|
+
// Builtin evaluation
|
|
3277
|
+
// ===========================================================================
|
|
3278
|
+
// Backward proof & builtins mutual recursion — declarations first
|
|
3174
3279
|
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
}
|
|
3180
|
-
return [];
|
|
3181
|
-
}
|
|
3280
|
+
function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
3281
|
+
const g = applySubstTriple(goal, subst);
|
|
3282
|
+
const pv = iriValue(g.p);
|
|
3283
|
+
if (pv === null) return null;
|
|
3182
3284
|
|
|
3183
|
-
//
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
const bInfo = parseNumericForCompareTerm(g.o);
|
|
3187
|
-
if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, '==')) return [{ ...subst }];
|
|
3285
|
+
// -----------------------------------------------------------------
|
|
3286
|
+
// 4.1 crypto: builtins
|
|
3287
|
+
// -----------------------------------------------------------------
|
|
3188
3288
|
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3289
|
+
// crypto:sha, crypto:md5, crypto:sha256, crypto:sha512
|
|
3290
|
+
// Digest builtins. crypto:sha uses SHA-1 per the N3/crypto convention.
|
|
3291
|
+
const cryptoAlgo =
|
|
3292
|
+
pv === CRYPTO_NS + 'sha'
|
|
3293
|
+
? 'sha1'
|
|
3294
|
+
: pv === CRYPTO_NS + 'md5'
|
|
3295
|
+
? 'md5'
|
|
3296
|
+
: pv === CRYPTO_NS + 'sha256'
|
|
3297
|
+
? 'sha256'
|
|
3298
|
+
: pv === CRYPTO_NS + 'sha512'
|
|
3299
|
+
? 'sha512'
|
|
3300
|
+
: null;
|
|
3301
|
+
if (cryptoAlgo) return evalCryptoHashBuiltin(g, subst, cryptoAlgo);
|
|
3196
3302
|
|
|
3197
|
-
//
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
const bInfo = parseNumericForCompareTerm(g.o);
|
|
3201
|
-
if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, '!=')) return [{ ...subst }];
|
|
3303
|
+
// -----------------------------------------------------------------
|
|
3304
|
+
// 4.2 math: builtins
|
|
3305
|
+
// -----------------------------------------------------------------
|
|
3202
3306
|
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3307
|
+
// math:greaterThan / lessThan / notLessThan / notGreaterThan / equalTo / notEqualTo
|
|
3308
|
+
const mathCmpOp =
|
|
3309
|
+
pv === MATH_NS + 'greaterThan'
|
|
3310
|
+
? '>'
|
|
3311
|
+
: pv === MATH_NS + 'lessThan'
|
|
3312
|
+
? '<'
|
|
3313
|
+
: pv === MATH_NS + 'notLessThan'
|
|
3314
|
+
? '>='
|
|
3315
|
+
: pv === MATH_NS + 'notGreaterThan'
|
|
3316
|
+
? '<='
|
|
3317
|
+
: pv === MATH_NS + 'equalTo'
|
|
3318
|
+
? '=='
|
|
3319
|
+
: pv === MATH_NS + 'notEqualTo'
|
|
3320
|
+
? '!='
|
|
3321
|
+
: null;
|
|
3322
|
+
if (mathCmpOp) return evalNumericComparisonBuiltin(g, subst, mathCmpOp);
|
|
3210
3323
|
|
|
3211
3324
|
// math:sum
|
|
3212
3325
|
// Schema: ( $s.i+ )+ math:sum $o-
|
|
3213
|
-
if (
|
|
3326
|
+
if (pv === MATH_NS + 'sum') {
|
|
3214
3327
|
if (!(g.s instanceof ListTerm) || g.s.elems.length < 2) return [];
|
|
3215
3328
|
const xs = g.s.elems;
|
|
3216
3329
|
|
|
@@ -3264,7 +3377,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3264
3377
|
|
|
3265
3378
|
// math:product
|
|
3266
3379
|
// Schema: ( $s.i+ )+ math:product $o-
|
|
3267
|
-
if (
|
|
3380
|
+
if (pv === MATH_NS + 'product') {
|
|
3268
3381
|
if (!(g.s instanceof ListTerm) || g.s.elems.length < 2) return [];
|
|
3269
3382
|
const xs = g.s.elems;
|
|
3270
3383
|
|
|
@@ -3316,7 +3429,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3316
3429
|
|
|
3317
3430
|
// math:difference
|
|
3318
3431
|
// Schema: ( $s.1+ $s.2+ )+ math:difference $o-
|
|
3319
|
-
if (
|
|
3432
|
+
if (pv === MATH_NS + 'difference') {
|
|
3320
3433
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
3321
3434
|
const [a0, b0] = g.s.elems;
|
|
3322
3435
|
|
|
@@ -3404,7 +3517,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3404
3517
|
|
|
3405
3518
|
// math:quotient
|
|
3406
3519
|
// Schema: ( $s.1+ $s.2+ )+ math:quotient $o-
|
|
3407
|
-
if (
|
|
3520
|
+
if (pv === MATH_NS + 'quotient') {
|
|
3408
3521
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
3409
3522
|
const [a0, b0] = g.s.elems;
|
|
3410
3523
|
|
|
@@ -3433,7 +3546,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3433
3546
|
// math:integerQuotient
|
|
3434
3547
|
// Schema: ( $a $b ) math:integerQuotient $q
|
|
3435
3548
|
// Cwm: divide first integer by second integer, ignoring remainder. :contentReference[oaicite:1]{index=1}
|
|
3436
|
-
if (
|
|
3549
|
+
if (pv === MATH_NS + 'integerQuotient') {
|
|
3437
3550
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
3438
3551
|
const [a0, b0] = g.s.elems;
|
|
3439
3552
|
|
|
@@ -3487,7 +3600,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3487
3600
|
}
|
|
3488
3601
|
|
|
3489
3602
|
// math:exponentiation
|
|
3490
|
-
if (
|
|
3603
|
+
if (pv === MATH_NS + 'exponentiation') {
|
|
3491
3604
|
if (g.s instanceof ListTerm && g.s.elems.length === 2) {
|
|
3492
3605
|
const baseTerm = g.s.elems[0];
|
|
3493
3606
|
const expTerm = g.s.elems[1];
|
|
@@ -3534,7 +3647,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3534
3647
|
}
|
|
3535
3648
|
|
|
3536
3649
|
// math:absoluteValue
|
|
3537
|
-
if (
|
|
3650
|
+
if (pv === MATH_NS + 'absoluteValue') {
|
|
3538
3651
|
const a = parseNum(g.s);
|
|
3539
3652
|
if (a === null) return [];
|
|
3540
3653
|
|
|
@@ -3555,58 +3668,58 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3555
3668
|
}
|
|
3556
3669
|
|
|
3557
3670
|
// math:acos
|
|
3558
|
-
if (
|
|
3671
|
+
if (pv === MATH_NS + 'acos') {
|
|
3559
3672
|
return evalUnaryMathRel(g, subst, Math.acos, Math.cos);
|
|
3560
3673
|
}
|
|
3561
3674
|
|
|
3562
3675
|
// math:asin
|
|
3563
|
-
if (
|
|
3676
|
+
if (pv === MATH_NS + 'asin') {
|
|
3564
3677
|
return evalUnaryMathRel(g, subst, Math.asin, Math.sin);
|
|
3565
3678
|
}
|
|
3566
3679
|
|
|
3567
3680
|
// math:atan
|
|
3568
|
-
if (
|
|
3681
|
+
if (pv === MATH_NS + 'atan') {
|
|
3569
3682
|
return evalUnaryMathRel(g, subst, Math.atan, Math.tan);
|
|
3570
3683
|
}
|
|
3571
3684
|
|
|
3572
3685
|
// math:sin (inverse uses principal asin)
|
|
3573
|
-
if (
|
|
3686
|
+
if (pv === MATH_NS + 'sin') {
|
|
3574
3687
|
return evalUnaryMathRel(g, subst, Math.sin, Math.asin);
|
|
3575
3688
|
}
|
|
3576
3689
|
|
|
3577
3690
|
// math:cos (inverse uses principal acos)
|
|
3578
|
-
if (
|
|
3691
|
+
if (pv === MATH_NS + 'cos') {
|
|
3579
3692
|
return evalUnaryMathRel(g, subst, Math.cos, Math.acos);
|
|
3580
3693
|
}
|
|
3581
3694
|
|
|
3582
3695
|
// math:tan (inverse uses principal atan)
|
|
3583
|
-
if (
|
|
3696
|
+
if (pv === MATH_NS + 'tan') {
|
|
3584
3697
|
return evalUnaryMathRel(g, subst, Math.tan, Math.atan);
|
|
3585
3698
|
}
|
|
3586
3699
|
|
|
3587
3700
|
// math:sinh / cosh / tanh (guard for JS availability)
|
|
3588
|
-
if (
|
|
3701
|
+
if (pv === MATH_NS + 'sinh') {
|
|
3589
3702
|
if (typeof Math.sinh !== 'function' || typeof Math.asinh !== 'function') return [];
|
|
3590
3703
|
return evalUnaryMathRel(g, subst, Math.sinh, Math.asinh);
|
|
3591
3704
|
}
|
|
3592
|
-
if (
|
|
3705
|
+
if (pv === MATH_NS + 'cosh') {
|
|
3593
3706
|
if (typeof Math.cosh !== 'function' || typeof Math.acosh !== 'function') return [];
|
|
3594
3707
|
return evalUnaryMathRel(g, subst, Math.cosh, Math.acosh);
|
|
3595
3708
|
}
|
|
3596
|
-
if (
|
|
3709
|
+
if (pv === MATH_NS + 'tanh') {
|
|
3597
3710
|
if (typeof Math.tanh !== 'function' || typeof Math.atanh !== 'function') return [];
|
|
3598
3711
|
return evalUnaryMathRel(g, subst, Math.tanh, Math.atanh);
|
|
3599
3712
|
}
|
|
3600
3713
|
|
|
3601
3714
|
// math:degrees (inverse is radians)
|
|
3602
|
-
if (
|
|
3715
|
+
if (pv === MATH_NS + 'degrees') {
|
|
3603
3716
|
const toDeg = (rad) => (rad * 180.0) / Math.PI;
|
|
3604
3717
|
const toRad = (deg) => (deg * Math.PI) / 180.0;
|
|
3605
3718
|
return evalUnaryMathRel(g, subst, toDeg, toRad);
|
|
3606
3719
|
}
|
|
3607
3720
|
|
|
3608
3721
|
// math:negation (inverse is itself)
|
|
3609
|
-
if (
|
|
3722
|
+
if (pv === MATH_NS + 'negation') {
|
|
3610
3723
|
const neg = (x) => -x;
|
|
3611
3724
|
return evalUnaryMathRel(g, subst, neg, neg);
|
|
3612
3725
|
}
|
|
@@ -3614,7 +3727,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3614
3727
|
// math:remainder
|
|
3615
3728
|
// Subject is a list (dividend divisor); object is the remainder.
|
|
3616
3729
|
// Schema: ( $a $b ) math:remainder $r
|
|
3617
|
-
if (
|
|
3730
|
+
if (pv === MATH_NS + 'remainder') {
|
|
3618
3731
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
3619
3732
|
const [a0, b0] = g.s.elems;
|
|
3620
3733
|
|
|
@@ -3664,7 +3777,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3664
3777
|
// If there are two such numbers, then the one closest to positive infinity is returned.
|
|
3665
3778
|
// Schema: $s+ math:rounded $o-
|
|
3666
3779
|
// Note: spec says $o is xsd:integer, but we also accept any numeric $o that equals the rounded value.
|
|
3667
|
-
if (
|
|
3780
|
+
if (pv === MATH_NS + 'rounded') {
|
|
3668
3781
|
const a = parseNum(g.s);
|
|
3669
3782
|
if (a === null) return [];
|
|
3670
3783
|
if (Number.isNaN(a)) return [];
|
|
@@ -3693,7 +3806,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3693
3806
|
|
|
3694
3807
|
// time:localTime
|
|
3695
3808
|
// "" time:localTime ?D. binds ?D to “now” as xsd:dateTime.
|
|
3696
|
-
if (
|
|
3809
|
+
if (pv === TIME_NS + 'localTime') {
|
|
3697
3810
|
const now = getNowLex();
|
|
3698
3811
|
|
|
3699
3812
|
if (g.o instanceof Var) {
|
|
@@ -3715,7 +3828,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3715
3828
|
// list:append
|
|
3716
3829
|
// true if and only if $o is the concatenation of all lists $s.i.
|
|
3717
3830
|
// Schema: ( $s.i?[*] )+ list:append $o?
|
|
3718
|
-
if (
|
|
3831
|
+
if (pv === LIST_NS + 'append') {
|
|
3719
3832
|
if (!(g.s instanceof ListTerm)) return [];
|
|
3720
3833
|
const parts = g.s.elems;
|
|
3721
3834
|
if (g.o instanceof ListTerm) {
|
|
@@ -3739,14 +3852,14 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3739
3852
|
// list:first and rdf:first
|
|
3740
3853
|
// true iff $s is a list and $o is the first member of that list.
|
|
3741
3854
|
// Schema: $s+ list:first $o-
|
|
3742
|
-
if (
|
|
3855
|
+
if (pv === LIST_NS + 'first' || pv === RDF_NS + 'first') {
|
|
3743
3856
|
return evalListFirstLikeBuiltin(g.s, g.o, subst);
|
|
3744
3857
|
}
|
|
3745
3858
|
|
|
3746
3859
|
// list:rest and rdf:rest
|
|
3747
3860
|
// true iff $s is a (non-empty) list and $o is the rest (tail) of that list.
|
|
3748
3861
|
// Schema: $s+ list:rest $o-
|
|
3749
|
-
if (
|
|
3862
|
+
if (pv === LIST_NS + 'rest' || pv === RDF_NS + 'rest') {
|
|
3750
3863
|
return evalListRestLikeBuiltin(g.s, g.o, subst);
|
|
3751
3864
|
}
|
|
3752
3865
|
|
|
@@ -3754,7 +3867,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3754
3867
|
// Multi-solution builtin:
|
|
3755
3868
|
// For a list subject $s, generate solutions by unifying $o with (index value).
|
|
3756
3869
|
// This allows $o to be a variable (e.g., ?Y) or a pattern (e.g., (?i "Dewey")).
|
|
3757
|
-
if (
|
|
3870
|
+
if (pv === LIST_NS + 'iterate') {
|
|
3758
3871
|
if (!(g.s instanceof ListTerm)) return [];
|
|
3759
3872
|
const xs = g.s.elems;
|
|
3760
3873
|
const outs = [];
|
|
@@ -3795,7 +3908,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3795
3908
|
// list:last
|
|
3796
3909
|
// true iff $s is a list and $o is the last member of that list.
|
|
3797
3910
|
// Schema: $s+ list:last $o-
|
|
3798
|
-
if (
|
|
3911
|
+
if (pv === LIST_NS + 'last') {
|
|
3799
3912
|
if (!(g.s instanceof ListTerm)) return [];
|
|
3800
3913
|
const xs = g.s.elems;
|
|
3801
3914
|
if (!xs.length) return [];
|
|
@@ -3807,7 +3920,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3807
3920
|
// list:memberAt
|
|
3808
3921
|
// true iff $s.1 is a list, $s.2 is a valid index, and $o is the member at that index.
|
|
3809
3922
|
// Schema: ( $s.1+ $s.2?[*] )+ list:memberAt $o?[*]
|
|
3810
|
-
if (
|
|
3923
|
+
if (pv === LIST_NS + 'memberAt') {
|
|
3811
3924
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
3812
3925
|
const [listTerm, indexTerm] = g.s.elems;
|
|
3813
3926
|
if (!(listTerm instanceof ListTerm)) return [];
|
|
@@ -3846,7 +3959,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3846
3959
|
// list:remove
|
|
3847
3960
|
// true iff $s.1 is a list and $o is that list with all occurrences of $s.2 removed.
|
|
3848
3961
|
// Schema: ( $s.1+ $s.2+ )+ list:remove $o-
|
|
3849
|
-
if (
|
|
3962
|
+
if (pv === LIST_NS + 'remove') {
|
|
3850
3963
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
3851
3964
|
const [listTerm, itemTerm] = g.s.elems;
|
|
3852
3965
|
if (!(listTerm instanceof ListTerm)) return [];
|
|
@@ -3868,7 +3981,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3868
3981
|
}
|
|
3869
3982
|
|
|
3870
3983
|
// list:member
|
|
3871
|
-
if (
|
|
3984
|
+
if (pv === LIST_NS + 'member') {
|
|
3872
3985
|
if (!(g.s instanceof ListTerm)) return [];
|
|
3873
3986
|
const outs = [];
|
|
3874
3987
|
for (const x of g.s.elems) {
|
|
@@ -3879,7 +3992,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3879
3992
|
}
|
|
3880
3993
|
|
|
3881
3994
|
// list:in
|
|
3882
|
-
if (
|
|
3995
|
+
if (pv === LIST_NS + 'in') {
|
|
3883
3996
|
if (!(g.o instanceof ListTerm)) return [];
|
|
3884
3997
|
const outs = [];
|
|
3885
3998
|
for (const x of g.o.elems) {
|
|
@@ -3890,7 +4003,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3890
4003
|
}
|
|
3891
4004
|
|
|
3892
4005
|
// list:length (strict: do not accept integer<->decimal matches for a ground object)
|
|
3893
|
-
if (
|
|
4006
|
+
if (pv === LIST_NS + 'length') {
|
|
3894
4007
|
if (!(g.s instanceof ListTerm)) return [];
|
|
3895
4008
|
const nTerm = new Literal(String(g.s.elems.length));
|
|
3896
4009
|
|
|
@@ -3904,7 +4017,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3904
4017
|
}
|
|
3905
4018
|
|
|
3906
4019
|
// list:notMember
|
|
3907
|
-
if (
|
|
4020
|
+
if (pv === LIST_NS + 'notMember') {
|
|
3908
4021
|
if (!(g.s instanceof ListTerm)) return [];
|
|
3909
4022
|
for (const el of g.s.elems) {
|
|
3910
4023
|
if (unifyTerm(g.o, el, subst) !== null) return [];
|
|
@@ -3913,7 +4026,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3913
4026
|
}
|
|
3914
4027
|
|
|
3915
4028
|
// list:reverse
|
|
3916
|
-
if (
|
|
4029
|
+
if (pv === LIST_NS + 'reverse') {
|
|
3917
4030
|
if (g.s instanceof ListTerm) {
|
|
3918
4031
|
const rev = [...g.s.elems].reverse();
|
|
3919
4032
|
const rterm = new ListTerm(rev);
|
|
@@ -3930,7 +4043,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3930
4043
|
}
|
|
3931
4044
|
|
|
3932
4045
|
// list:sort
|
|
3933
|
-
if (
|
|
4046
|
+
if (pv === LIST_NS + 'sort') {
|
|
3934
4047
|
function cmpTermForSort(a, b) {
|
|
3935
4048
|
if (a instanceof Literal && b instanceof Literal) {
|
|
3936
4049
|
const [lexA] = literalParts(a.value);
|
|
@@ -3998,7 +4111,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3998
4111
|
}
|
|
3999
4112
|
|
|
4000
4113
|
// list:map
|
|
4001
|
-
if (
|
|
4114
|
+
if (pv === LIST_NS + 'map') {
|
|
4002
4115
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
4003
4116
|
const [inputTerm, predTerm] = g.s.elems;
|
|
4004
4117
|
if (!(inputTerm instanceof ListTerm)) return [];
|
|
@@ -4024,7 +4137,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4024
4137
|
}
|
|
4025
4138
|
|
|
4026
4139
|
// list:firstRest
|
|
4027
|
-
if (
|
|
4140
|
+
if (pv === LIST_NS + 'firstRest') {
|
|
4028
4141
|
if (g.s instanceof ListTerm) {
|
|
4029
4142
|
if (!g.s.elems.length) return [];
|
|
4030
4143
|
const first = g.s.elems[0];
|
|
@@ -4062,13 +4175,13 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4062
4175
|
// -----------------------------------------------------------------
|
|
4063
4176
|
|
|
4064
4177
|
// log:equalTo
|
|
4065
|
-
if (
|
|
4178
|
+
if (pv === LOG_NS + 'equalTo') {
|
|
4066
4179
|
const s2 = unifyTerm(goal.s, goal.o, subst);
|
|
4067
4180
|
return s2 !== null ? [s2] : [];
|
|
4068
4181
|
}
|
|
4069
4182
|
|
|
4070
4183
|
// log:notEqualTo
|
|
4071
|
-
if (
|
|
4184
|
+
if (pv === LOG_NS + 'notEqualTo') {
|
|
4072
4185
|
const s2 = unifyTerm(goal.s, goal.o, subst);
|
|
4073
4186
|
if (s2 !== null) return [];
|
|
4074
4187
|
return [{ ...subst }];
|
|
@@ -4077,7 +4190,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4077
4190
|
// log:dtlit
|
|
4078
4191
|
// Schema: ( $s.1? $s.2? )? log:dtlit $o?
|
|
4079
4192
|
// true iff $o is a datatyped literal with string value $s.1 and datatype IRI $s.2
|
|
4080
|
-
if (
|
|
4193
|
+
if (pv === LOG_NS + 'dtlit') {
|
|
4081
4194
|
// Fully unbound (both arguments '?'-mode): treat as satisfiable, succeed once.
|
|
4082
4195
|
// Required by notation3tests "success-fullUnbound-*".
|
|
4083
4196
|
if (g.s instanceof Var && g.o instanceof Var) return [{ ...subst }];
|
|
@@ -4130,7 +4243,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4130
4243
|
// log:langlit
|
|
4131
4244
|
// Schema: ( $s.1? $s.2? )? log:langlit $o?
|
|
4132
4245
|
// true iff $o is a language-tagged literal with string value $s.1 and language tag $s.2
|
|
4133
|
-
if (
|
|
4246
|
+
if (pv === LOG_NS + 'langlit') {
|
|
4134
4247
|
// Fully unbound (both arguments '?'-mode): treat as satisfiable, succeed once.
|
|
4135
4248
|
if (g.s instanceof Var && g.o instanceof Var) return [{ ...subst }];
|
|
4136
4249
|
const results = [];
|
|
@@ -4184,7 +4297,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4184
4297
|
}
|
|
4185
4298
|
|
|
4186
4299
|
// log:implies — expose internal forward rules as data
|
|
4187
|
-
if (
|
|
4300
|
+
if (pv === LOG_NS + 'implies') {
|
|
4188
4301
|
const allFw = backRules.__allForwardRules || [];
|
|
4189
4302
|
const results = [];
|
|
4190
4303
|
|
|
@@ -4212,7 +4325,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4212
4325
|
}
|
|
4213
4326
|
|
|
4214
4327
|
// log:impliedBy — expose internal backward rules as data
|
|
4215
|
-
if (
|
|
4328
|
+
if (pv === LOG_NS + 'impliedBy') {
|
|
4216
4329
|
const allBw = backRules.__allBackwardRules || backRules;
|
|
4217
4330
|
const results = [];
|
|
4218
4331
|
|
|
@@ -4242,7 +4355,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4242
4355
|
|
|
4243
4356
|
// log:notIncludes (EYE-style: "not provable in scope")
|
|
4244
4357
|
// Delay until we have a frozen scope snapshot to avoid early success.
|
|
4245
|
-
if (
|
|
4358
|
+
if (pv === LOG_NS + 'notIncludes') {
|
|
4246
4359
|
if (!(g.o instanceof FormulaTerm)) return [];
|
|
4247
4360
|
|
|
4248
4361
|
let scopeFacts = null;
|
|
@@ -4265,7 +4378,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4265
4378
|
}
|
|
4266
4379
|
|
|
4267
4380
|
// log:collectAllIn (scoped)
|
|
4268
|
-
if (
|
|
4381
|
+
if (pv === LOG_NS + 'collectAllIn') {
|
|
4269
4382
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 3) return [];
|
|
4270
4383
|
const [valueTempl, clauseTerm, listTerm] = g.s.elems;
|
|
4271
4384
|
if (!(clauseTerm instanceof FormulaTerm)) return [];
|
|
@@ -4294,7 +4407,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4294
4407
|
}
|
|
4295
4408
|
|
|
4296
4409
|
// log:forAllIn (scoped)
|
|
4297
|
-
if (
|
|
4410
|
+
if (pv === LOG_NS + 'forAllIn') {
|
|
4298
4411
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
4299
4412
|
const [whereClause, thenClause] = g.s.elems;
|
|
4300
4413
|
if (!(whereClause instanceof FormulaTerm) || !(thenClause instanceof FormulaTerm)) return [];
|
|
@@ -4324,7 +4437,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4324
4437
|
}
|
|
4325
4438
|
|
|
4326
4439
|
// log:skolem
|
|
4327
|
-
if (
|
|
4440
|
+
if (pv === LOG_NS + 'skolem') {
|
|
4328
4441
|
// Subject must be ground; commonly a list, but we allow any ground term.
|
|
4329
4442
|
if (!isGroundTerm(g.s)) return [];
|
|
4330
4443
|
|
|
@@ -4341,7 +4454,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4341
4454
|
}
|
|
4342
4455
|
|
|
4343
4456
|
// log:uri
|
|
4344
|
-
if (
|
|
4457
|
+
if (pv === LOG_NS + 'uri') {
|
|
4345
4458
|
// Direction 1: subject is an IRI -> object is its string representation
|
|
4346
4459
|
if (g.s instanceof Iri) {
|
|
4347
4460
|
const uriStr = g.s.value; // raw IRI string, e.g. "https://www.w3.org"
|
|
@@ -4370,7 +4483,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4370
4483
|
// -----------------------------------------------------------------
|
|
4371
4484
|
|
|
4372
4485
|
// string:concatenation
|
|
4373
|
-
if (
|
|
4486
|
+
if (pv === STRING_NS + 'concatenation') {
|
|
4374
4487
|
if (!(g.s instanceof ListTerm)) return [];
|
|
4375
4488
|
const parts = [];
|
|
4376
4489
|
for (const t of g.s.elems) {
|
|
@@ -4390,7 +4503,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4390
4503
|
}
|
|
4391
4504
|
|
|
4392
4505
|
// string:contains
|
|
4393
|
-
if (
|
|
4506
|
+
if (pv === STRING_NS + 'contains') {
|
|
4394
4507
|
const sStr = termToJsString(g.s);
|
|
4395
4508
|
const oStr = termToJsString(g.o);
|
|
4396
4509
|
if (sStr === null || oStr === null) return [];
|
|
@@ -4398,7 +4511,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4398
4511
|
}
|
|
4399
4512
|
|
|
4400
4513
|
// string:containsIgnoringCase
|
|
4401
|
-
if (
|
|
4514
|
+
if (pv === STRING_NS + 'containsIgnoringCase') {
|
|
4402
4515
|
const sStr = termToJsString(g.s);
|
|
4403
4516
|
const oStr = termToJsString(g.o);
|
|
4404
4517
|
if (sStr === null || oStr === null) return [];
|
|
@@ -4406,7 +4519,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4406
4519
|
}
|
|
4407
4520
|
|
|
4408
4521
|
// string:endsWith
|
|
4409
|
-
if (
|
|
4522
|
+
if (pv === STRING_NS + 'endsWith') {
|
|
4410
4523
|
const sStr = termToJsString(g.s);
|
|
4411
4524
|
const oStr = termToJsString(g.o);
|
|
4412
4525
|
if (sStr === null || oStr === null) return [];
|
|
@@ -4414,7 +4527,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4414
4527
|
}
|
|
4415
4528
|
|
|
4416
4529
|
// string:equalIgnoringCase
|
|
4417
|
-
if (
|
|
4530
|
+
if (pv === STRING_NS + 'equalIgnoringCase') {
|
|
4418
4531
|
const sStr = termToJsString(g.s);
|
|
4419
4532
|
const oStr = termToJsString(g.o);
|
|
4420
4533
|
if (sStr === null || oStr === null) return [];
|
|
@@ -4423,7 +4536,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4423
4536
|
|
|
4424
4537
|
// string:format
|
|
4425
4538
|
// (limited: only %s and %% are supported, anything else ⇒ builtin fails)
|
|
4426
|
-
if (
|
|
4539
|
+
if (pv === STRING_NS + 'format') {
|
|
4427
4540
|
if (!(g.s instanceof ListTerm) || g.s.elems.length < 1) return [];
|
|
4428
4541
|
const fmtStr = termToJsString(g.s.elems[0]);
|
|
4429
4542
|
if (fmtStr === null) return [];
|
|
@@ -4450,10 +4563,10 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4450
4563
|
|
|
4451
4564
|
// string:jsonPointer
|
|
4452
4565
|
// Schema: ( $jsonText $pointer ) string:jsonPointer $value
|
|
4453
|
-
if (
|
|
4566
|
+
if (pv === STRING_NS + 'jsonPointer') {
|
|
4454
4567
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
4455
4568
|
|
|
4456
|
-
const jsonText = termToJsonText(g.s.elems[0]);
|
|
4569
|
+
const jsonText = termToJsonText(g.s.elems[0]);
|
|
4457
4570
|
const ptr = termToJsStringDecoded(g.s.elems[1]);
|
|
4458
4571
|
if (jsonText === null || ptr === null) return [];
|
|
4459
4572
|
|
|
@@ -4465,7 +4578,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4465
4578
|
}
|
|
4466
4579
|
|
|
4467
4580
|
// string:greaterThan
|
|
4468
|
-
if (
|
|
4581
|
+
if (pv === STRING_NS + 'greaterThan') {
|
|
4469
4582
|
const sStr = termToJsString(g.s);
|
|
4470
4583
|
const oStr = termToJsString(g.o);
|
|
4471
4584
|
if (sStr === null || oStr === null) return [];
|
|
@@ -4473,7 +4586,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4473
4586
|
}
|
|
4474
4587
|
|
|
4475
4588
|
// string:lessThan
|
|
4476
|
-
if (
|
|
4589
|
+
if (pv === STRING_NS + 'lessThan') {
|
|
4477
4590
|
const sStr = termToJsString(g.s);
|
|
4478
4591
|
const oStr = termToJsString(g.o);
|
|
4479
4592
|
if (sStr === null || oStr === null) return [];
|
|
@@ -4481,7 +4594,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4481
4594
|
}
|
|
4482
4595
|
|
|
4483
4596
|
// string:matches
|
|
4484
|
-
if (
|
|
4597
|
+
if (pv === STRING_NS + 'matches') {
|
|
4485
4598
|
const sStr = termToJsString(g.s);
|
|
4486
4599
|
const pattern = termToJsString(g.o);
|
|
4487
4600
|
if (sStr === null || pattern === null) return [];
|
|
@@ -4496,7 +4609,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4496
4609
|
}
|
|
4497
4610
|
|
|
4498
4611
|
// string:notEqualIgnoringCase
|
|
4499
|
-
if (
|
|
4612
|
+
if (pv === STRING_NS + 'notEqualIgnoringCase') {
|
|
4500
4613
|
const sStr = termToJsString(g.s);
|
|
4501
4614
|
const oStr = termToJsString(g.o);
|
|
4502
4615
|
if (sStr === null || oStr === null) return [];
|
|
@@ -4504,7 +4617,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4504
4617
|
}
|
|
4505
4618
|
|
|
4506
4619
|
// string:notGreaterThan (≤ in Unicode code order)
|
|
4507
|
-
if (
|
|
4620
|
+
if (pv === STRING_NS + 'notGreaterThan') {
|
|
4508
4621
|
const sStr = termToJsString(g.s);
|
|
4509
4622
|
const oStr = termToJsString(g.o);
|
|
4510
4623
|
if (sStr === null || oStr === null) return [];
|
|
@@ -4512,7 +4625,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4512
4625
|
}
|
|
4513
4626
|
|
|
4514
4627
|
// string:notLessThan (≥ in Unicode code order)
|
|
4515
|
-
if (
|
|
4628
|
+
if (pv === STRING_NS + 'notLessThan') {
|
|
4516
4629
|
const sStr = termToJsString(g.s);
|
|
4517
4630
|
const oStr = termToJsString(g.o);
|
|
4518
4631
|
if (sStr === null || oStr === null) return [];
|
|
@@ -4520,7 +4633,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4520
4633
|
}
|
|
4521
4634
|
|
|
4522
4635
|
// string:notMatches
|
|
4523
|
-
if (
|
|
4636
|
+
if (pv === STRING_NS + 'notMatches') {
|
|
4524
4637
|
const sStr = termToJsString(g.s);
|
|
4525
4638
|
const pattern = termToJsString(g.o);
|
|
4526
4639
|
if (sStr === null || pattern === null) return [];
|
|
@@ -4534,7 +4647,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4534
4647
|
}
|
|
4535
4648
|
|
|
4536
4649
|
// string:replace
|
|
4537
|
-
if (
|
|
4650
|
+
if (pv === STRING_NS + 'replace') {
|
|
4538
4651
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 3) return [];
|
|
4539
4652
|
const dataStr = termToJsString(g.s.elems[0]);
|
|
4540
4653
|
const searchStr = termToJsString(g.s.elems[1]);
|
|
@@ -4562,7 +4675,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4562
4675
|
}
|
|
4563
4676
|
|
|
4564
4677
|
// string:scrape
|
|
4565
|
-
if (
|
|
4678
|
+
if (pv === STRING_NS + 'scrape') {
|
|
4566
4679
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
4567
4680
|
const dataStr = termToJsString(g.s.elems[0]);
|
|
4568
4681
|
const pattern = termToJsString(g.s.elems[1]);
|
|
@@ -4591,7 +4704,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4591
4704
|
}
|
|
4592
4705
|
|
|
4593
4706
|
// string:startsWith
|
|
4594
|
-
if (
|
|
4707
|
+
if (pv === STRING_NS + 'startsWith') {
|
|
4595
4708
|
const sStr = termToJsString(g.s);
|
|
4596
4709
|
const oStr = termToJsString(g.o);
|
|
4597
4710
|
if (sStr === null || oStr === null) return [];
|
|
@@ -4621,9 +4734,9 @@ function isBuiltinPred(p) {
|
|
|
4621
4734
|
);
|
|
4622
4735
|
}
|
|
4623
4736
|
|
|
4624
|
-
//
|
|
4737
|
+
// ===========================================================================
|
|
4625
4738
|
// Backward proof (SLD-style)
|
|
4626
|
-
//
|
|
4739
|
+
// ===========================================================================
|
|
4627
4740
|
|
|
4628
4741
|
function standardizeRule(rule, gen) {
|
|
4629
4742
|
function renameTerm(t, vmap, genArr) {
|
|
@@ -4670,9 +4783,9 @@ function listHasTriple(list, tr) {
|
|
|
4670
4783
|
return list.some((t) => triplesEqual(t, tr));
|
|
4671
4784
|
}
|
|
4672
4785
|
|
|
4673
|
-
//
|
|
4786
|
+
// ===========================================================================
|
|
4674
4787
|
// Substitution compaction (to avoid O(depth^2) in deep backward chains)
|
|
4675
|
-
//
|
|
4788
|
+
// ===========================================================================
|
|
4676
4789
|
//
|
|
4677
4790
|
// Why: backward chaining with standardizeRule introduces fresh variables at
|
|
4678
4791
|
// each step. composeSubst frequently copies a growing substitution object.
|
|
@@ -4907,9 +5020,9 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen) {
|
|
|
4907
5020
|
return results;
|
|
4908
5021
|
}
|
|
4909
5022
|
|
|
4910
|
-
//
|
|
5023
|
+
// ===========================================================================
|
|
4911
5024
|
// Forward chaining to fixpoint
|
|
4912
|
-
//
|
|
5025
|
+
// ===========================================================================
|
|
4913
5026
|
|
|
4914
5027
|
function forwardChain(facts, forwardRules, backRules) {
|
|
4915
5028
|
ensureFactIndexes(facts);
|
|
@@ -5093,9 +5206,9 @@ function forwardChain(facts, forwardRules, backRules) {
|
|
|
5093
5206
|
return derivedForward;
|
|
5094
5207
|
}
|
|
5095
5208
|
|
|
5096
|
-
//
|
|
5209
|
+
// ===========================================================================
|
|
5097
5210
|
// Pretty printing as N3/Turtle
|
|
5098
|
-
//
|
|
5211
|
+
// ===========================================================================
|
|
5099
5212
|
|
|
5100
5213
|
function termToN3(t, pref) {
|
|
5101
5214
|
if (t instanceof Iri) {
|
|
@@ -5262,179 +5375,9 @@ function printExplanation(df, prefixes) {
|
|
|
5262
5375
|
console.log('# ----------------------------------------------------------------------\n');
|
|
5263
5376
|
}
|
|
5264
5377
|
|
|
5265
|
-
//
|
|
5266
|
-
// Misc helpers
|
|
5267
|
-
// ============================================================================
|
|
5268
|
-
|
|
5269
|
-
// Turn RDF Collections described with rdf:first/rdf:rest (+ rdf:nil) into ListTerm terms.
|
|
5270
|
-
// This mutates triples/rules in-place so list:* builtins work on RDF-serialized lists too.
|
|
5271
|
-
function materializeRdfLists(triples, forwardRules, backwardRules) {
|
|
5272
|
-
const RDF_FIRST = RDF_NS + 'first';
|
|
5273
|
-
const RDF_REST = RDF_NS + 'rest';
|
|
5274
|
-
const RDF_NIL = RDF_NS + 'nil';
|
|
5275
|
-
|
|
5276
|
-
function nodeKey(t) {
|
|
5277
|
-
if (t instanceof Blank) return 'B:' + t.label;
|
|
5278
|
-
if (t instanceof Iri) return 'I:' + t.value;
|
|
5279
|
-
return null;
|
|
5280
|
-
}
|
|
5281
|
-
|
|
5282
|
-
// Collect first/rest arcs from *input triples*
|
|
5283
|
-
const firstMap = new Map(); // key(subject) -> Term (object)
|
|
5284
|
-
const restMap = new Map(); // key(subject) -> Term (object)
|
|
5285
|
-
for (const tr of triples) {
|
|
5286
|
-
if (!(tr.p instanceof Iri)) continue;
|
|
5287
|
-
const k = nodeKey(tr.s);
|
|
5288
|
-
if (!k) continue;
|
|
5289
|
-
if (tr.p.value === RDF_FIRST) firstMap.set(k, tr.o);
|
|
5290
|
-
else if (tr.p.value === RDF_REST) restMap.set(k, tr.o);
|
|
5291
|
-
}
|
|
5292
|
-
if (!firstMap.size && !restMap.size) return;
|
|
5293
|
-
|
|
5294
|
-
const cache = new Map(); // key(node) -> ListTerm
|
|
5295
|
-
const visiting = new Set(); // cycle guard
|
|
5296
|
-
|
|
5297
|
-
function buildListForKey(k) {
|
|
5298
|
-
if (cache.has(k)) return cache.get(k);
|
|
5299
|
-
if (visiting.has(k)) return null; // cycle => not a well-formed list
|
|
5300
|
-
visiting.add(k);
|
|
5301
|
-
|
|
5302
|
-
// rdf:nil => ()
|
|
5303
|
-
if (k === 'I:' + RDF_NIL) {
|
|
5304
|
-
const empty = new ListTerm([]);
|
|
5305
|
-
cache.set(k, empty);
|
|
5306
|
-
visiting.delete(k);
|
|
5307
|
-
return empty;
|
|
5308
|
-
}
|
|
5309
|
-
|
|
5310
|
-
const head = firstMap.get(k);
|
|
5311
|
-
const tail = restMap.get(k);
|
|
5312
|
-
if (head === undefined || tail === undefined) {
|
|
5313
|
-
visiting.delete(k);
|
|
5314
|
-
return null; // not a full cons cell
|
|
5315
|
-
}
|
|
5316
|
-
|
|
5317
|
-
const headTerm = rewriteTerm(head);
|
|
5318
|
-
|
|
5319
|
-
let tailListElems = null;
|
|
5320
|
-
if (tail instanceof Iri && tail.value === RDF_NIL) {
|
|
5321
|
-
tailListElems = [];
|
|
5322
|
-
} else {
|
|
5323
|
-
const tk = nodeKey(tail);
|
|
5324
|
-
if (!tk) {
|
|
5325
|
-
visiting.delete(k);
|
|
5326
|
-
return null;
|
|
5327
|
-
}
|
|
5328
|
-
const tailList = buildListForKey(tk);
|
|
5329
|
-
if (!tailList) {
|
|
5330
|
-
visiting.delete(k);
|
|
5331
|
-
return null;
|
|
5332
|
-
}
|
|
5333
|
-
tailListElems = tailList.elems;
|
|
5334
|
-
}
|
|
5335
|
-
|
|
5336
|
-
const out = new ListTerm([headTerm, ...tailListElems]);
|
|
5337
|
-
cache.set(k, out);
|
|
5338
|
-
visiting.delete(k);
|
|
5339
|
-
return out;
|
|
5340
|
-
}
|
|
5341
|
-
|
|
5342
|
-
function rewriteTerm(t) {
|
|
5343
|
-
// Replace list nodes (Blank/Iri) by their constructed ListTerm when possible
|
|
5344
|
-
const k = nodeKey(t);
|
|
5345
|
-
if (k) {
|
|
5346
|
-
const built = buildListForKey(k);
|
|
5347
|
-
if (built) return built;
|
|
5348
|
-
// Also rewrite rdf:nil even if not otherwise referenced
|
|
5349
|
-
if (t instanceof Iri && t.value === RDF_NIL) return new ListTerm([]);
|
|
5350
|
-
return t;
|
|
5351
|
-
}
|
|
5352
|
-
if (t instanceof ListTerm) {
|
|
5353
|
-
let changed = false;
|
|
5354
|
-
const elems = t.elems.map((e) => {
|
|
5355
|
-
const r = rewriteTerm(e);
|
|
5356
|
-
if (r !== e) changed = true;
|
|
5357
|
-
return r;
|
|
5358
|
-
});
|
|
5359
|
-
return changed ? new ListTerm(elems) : t;
|
|
5360
|
-
}
|
|
5361
|
-
if (t instanceof OpenListTerm) {
|
|
5362
|
-
let changed = false;
|
|
5363
|
-
const prefix = t.prefix.map((e) => {
|
|
5364
|
-
const r = rewriteTerm(e);
|
|
5365
|
-
if (r !== e) changed = true;
|
|
5366
|
-
return r;
|
|
5367
|
-
});
|
|
5368
|
-
return changed ? new OpenListTerm(prefix, t.tailVar) : t;
|
|
5369
|
-
}
|
|
5370
|
-
if (t instanceof FormulaTerm) {
|
|
5371
|
-
for (const tr of t.triples) rewriteTriple(tr);
|
|
5372
|
-
return t;
|
|
5373
|
-
}
|
|
5374
|
-
return t;
|
|
5375
|
-
}
|
|
5376
|
-
|
|
5377
|
-
function rewriteTriple(tr) {
|
|
5378
|
-
tr.s = rewriteTerm(tr.s);
|
|
5379
|
-
tr.p = rewriteTerm(tr.p);
|
|
5380
|
-
tr.o = rewriteTerm(tr.o);
|
|
5381
|
-
}
|
|
5382
|
-
|
|
5383
|
-
// Pre-build all reachable list heads
|
|
5384
|
-
for (const k of firstMap.keys()) buildListForKey(k);
|
|
5385
|
-
|
|
5386
|
-
// Rewrite input triples + rules in-place
|
|
5387
|
-
for (const tr of triples) rewriteTriple(tr);
|
|
5388
|
-
for (const r of forwardRules) {
|
|
5389
|
-
for (const tr of r.premise) rewriteTriple(tr);
|
|
5390
|
-
for (const tr of r.conclusion) rewriteTriple(tr);
|
|
5391
|
-
}
|
|
5392
|
-
for (const r of backwardRules) {
|
|
5393
|
-
for (const tr of r.premise) rewriteTriple(tr);
|
|
5394
|
-
for (const tr of r.conclusion) rewriteTriple(tr);
|
|
5395
|
-
}
|
|
5396
|
-
}
|
|
5397
|
-
|
|
5398
|
-
function localIsoDateTimeString(d) {
|
|
5399
|
-
function pad(n, width = 2) {
|
|
5400
|
-
return String(n).padStart(width, '0');
|
|
5401
|
-
}
|
|
5402
|
-
const year = d.getFullYear();
|
|
5403
|
-
const month = d.getMonth() + 1;
|
|
5404
|
-
const day = d.getDate();
|
|
5405
|
-
const hour = d.getHours();
|
|
5406
|
-
const min = d.getMinutes();
|
|
5407
|
-
const sec = d.getSeconds();
|
|
5408
|
-
const ms = d.getMilliseconds();
|
|
5409
|
-
const offsetMin = -d.getTimezoneOffset(); // minutes east of UTC
|
|
5410
|
-
const sign = offsetMin >= 0 ? '+' : '-';
|
|
5411
|
-
const abs = Math.abs(offsetMin);
|
|
5412
|
-
const oh = Math.floor(abs / 60);
|
|
5413
|
-
const om = abs % 60;
|
|
5414
|
-
const msPart = ms ? '.' + String(ms).padStart(3, '0') : '';
|
|
5415
|
-
return (
|
|
5416
|
-
pad(year, 4) +
|
|
5417
|
-
'-' +
|
|
5418
|
-
pad(month) +
|
|
5419
|
-
'-' +
|
|
5420
|
-
pad(day) +
|
|
5421
|
-
'T' +
|
|
5422
|
-
pad(hour) +
|
|
5423
|
-
':' +
|
|
5424
|
-
pad(min) +
|
|
5425
|
-
':' +
|
|
5426
|
-
pad(sec) +
|
|
5427
|
-
msPart +
|
|
5428
|
-
sign +
|
|
5429
|
-
pad(oh) +
|
|
5430
|
-
':' +
|
|
5431
|
-
pad(om)
|
|
5432
|
-
);
|
|
5433
|
-
}
|
|
5434
|
-
|
|
5435
|
-
// ============================================================================
|
|
5378
|
+
// ===========================================================================
|
|
5436
5379
|
// CLI entry point
|
|
5437
|
-
//
|
|
5380
|
+
// ===========================================================================
|
|
5438
5381
|
function main() {
|
|
5439
5382
|
// Drop "node" and script name; keep only user-provided args
|
|
5440
5383
|
const argv = process.argv.slice(2);
|