eyeling 1.10.20 → 1.10.21
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/HANDBOOK.md +88 -2
- package/examples/bind-builtins.n3 +11 -0
- package/examples/bind.n3 +7 -0
- package/examples/brussels-brew-club.n3 +119 -0
- package/examples/builtins-string-math.n3 +11 -0
- package/examples/builtins-triple-termtests.n3 +7 -0
- package/examples/family.n3 +10 -0
- package/examples/filter-demorgan.n3 +9 -0
- package/examples/filter-in-notin.n3 +10 -0
- package/examples/filter-nested-or.n3 +10 -0
- package/examples/filter.n3 +8 -0
- package/examples/input/bind-builtins.srl +30 -0
- package/examples/input/bind.srl +12 -0
- package/examples/input/builtins-string-math.srl +38 -0
- package/examples/input/builtins-triple-termtests.srl +27 -0
- package/examples/input/family.srl +12 -0
- package/examples/input/filter-demorgan.srl +15 -0
- package/examples/input/filter-in-notin.srl +15 -0
- package/examples/input/filter-nested-or.srl +15 -0
- package/examples/input/filter.srl +9 -0
- package/examples/input/snaf.srl +6 -0
- package/examples/json-pointer.n3 +75 -0
- package/examples/json-reconcile-vat.n3 +361 -0
- package/examples/output/bind-builtins.n3 +9 -0
- package/examples/output/bind.n3 +3 -0
- package/examples/output/brussels-brew-club.n3 +22 -0
- package/examples/output/builtins-string-math.n3 +0 -0
- package/examples/output/builtins-triple-termtests.n3 +0 -0
- package/examples/output/family.n3 +13 -0
- package/examples/output/filter-demorgan.n3 +3 -0
- package/examples/output/filter-in-notin.n3 +4 -0
- package/examples/output/filter-nested-or.n3 +4 -0
- package/examples/output/filter.n3 +3 -0
- package/examples/output/json-pointer.n3 +13 -0
- package/examples/output/json-reconcile-vat.n3 +226 -0
- package/examples/output/snaf.n3 +3 -0
- package/examples/snaf.n3 +6 -0
- package/eyeling-builtins.ttl +48 -0
- package/eyeling.js +312 -1
- package/lib/engine.js +307 -1
- package/lib/rules.js +5 -0
- package/package.json +1 -1
- package/test/n3gen.test.js +4 -4
- package/test/package.test.js +1 -1
- package/tools/n3gen.js +1883 -6
package/examples/snaf.n3
ADDED
package/eyeling-builtins.ttl
CHANGED
|
@@ -55,6 +55,9 @@ crypto:md5 a ex:Builtin ; ex:kind ex:Function ;
|
|
|
55
55
|
crypto:sha256 a ex:Builtin ; ex:kind ex:Function ;
|
|
56
56
|
rdfs:comment "Hash builtin (SHA-256). Computes digest over the subject (string-ish) and unifies/binds the object with the digest." .
|
|
57
57
|
|
|
58
|
+
crypto:sha384 a ex:Builtin ; ex:kind ex:Function ;
|
|
59
|
+
rdfs:comment "Hash builtin (SHA-384). Computes digest over the subject (string-ish) and unifies/binds the object with the digest. (Eyeling extension; useful for SHACL/Sparql-style mappings.)" .
|
|
60
|
+
|
|
58
61
|
crypto:sha512 a ex:Builtin ; ex:kind ex:Function ;
|
|
59
62
|
rdfs:comment "Hash builtin (SHA-512). Computes digest over the subject (string-ish) and unifies/binds the object with the digest." .
|
|
60
63
|
|
|
@@ -101,6 +104,12 @@ math:remainder a ex:Builtin ; ex:kind ex:Function ;
|
|
|
101
104
|
math:rounded a ex:Builtin ; ex:kind ex:Function ;
|
|
102
105
|
rdfs:comment "Rounds subject to nearest integer (JS tie-breaking: toward +∞). Binds/unifies object with the rounded integer value." .
|
|
103
106
|
|
|
107
|
+
math:ceiling a ex:Builtin ; ex:kind ex:Function ;
|
|
108
|
+
rdfs:comment "Ceiling of a numeric value (like SPARQL CEIL). Returns an integer token. (Eyeling extension.)" .
|
|
109
|
+
|
|
110
|
+
math:floor a ex:Builtin ; ex:kind ex:Function ;
|
|
111
|
+
rdfs:comment "Floor of a numeric value (like SPARQL FLOOR). Returns an integer token. (Eyeling extension.)" .
|
|
112
|
+
|
|
104
113
|
math:exponentiation a ex:Builtin ; ex:kind ex:Function ;
|
|
105
114
|
rdfs:comment "Exponentiation. Forward: (base exponent) -> result. Limited inverse: if base is numeric and exponent is a variable, may solve exponent via logs for positive base != 1 and positive result." .
|
|
106
115
|
|
|
@@ -248,6 +257,27 @@ log:parsedAsN3 a ex:Builtin ; ex:kind ex:Meta ;
|
|
|
248
257
|
log:rawType a ex:Builtin ; ex:kind ex:Meta ;
|
|
249
258
|
rdfs:comment "Returns one of log:Formula, log:Literal, rdf:List, or log:Other for the subject term." .
|
|
250
259
|
|
|
260
|
+
log:isIRI a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
|
|
261
|
+
rdfs:comment "Succeeds iff the subject is an IRI term (like SPARQL isIRI). Object is typically 'true'. (Eyeling extension.)" .
|
|
262
|
+
|
|
263
|
+
log:isLiteral a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
|
|
264
|
+
rdfs:comment "Succeeds iff the subject is a literal term (like SPARQL isLITERAL). (Eyeling extension.)" .
|
|
265
|
+
|
|
266
|
+
log:isBlank a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
|
|
267
|
+
rdfs:comment "Succeeds iff the subject is a blank node term (like SPARQL isBLANK). (Eyeling extension.)" .
|
|
268
|
+
|
|
269
|
+
log:isNumeric a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
|
|
270
|
+
rdfs:comment "Succeeds iff the subject is a numeric literal (like SPARQL isNUMERIC). (Eyeling extension.)" .
|
|
271
|
+
|
|
272
|
+
log:isTriple a ex:Builtin ; ex:kind ex:Test ; ex:isConstraint true ;
|
|
273
|
+
rdfs:comment "Succeeds iff the subject is a formula with exactly one triple (like SPARQL isTRIPLE over RDF-star triple terms). (Eyeling extension.)" .
|
|
274
|
+
|
|
275
|
+
log:uuid a ex:Builtin ; ex:kind ex:Function ;
|
|
276
|
+
rdfs:comment "Generates a fresh UUID IRI (<urn:uuid:...>). Non-deterministic; can affect termination. (Eyeling extension.)" .
|
|
277
|
+
|
|
278
|
+
log:struuid a ex:Builtin ; ex:kind ex:Function ;
|
|
279
|
+
rdfs:comment "Generates a fresh UUID string literal. Non-deterministic; can affect termination. (Eyeling extension.)" .
|
|
280
|
+
|
|
251
281
|
log:dtlit a ex:Builtin ; ex:kind ex:Function ;
|
|
252
282
|
rdfs:comment "Builds a datatype literal from (lex datatypeIri). Binds/unifies object with the resulting Literal." .
|
|
253
283
|
|
|
@@ -334,3 +364,21 @@ string:scrape a ex:Builtin ; ex:kind ex:Function ;
|
|
|
334
364
|
string:format a ex:Builtin ; ex:kind ex:Function ;
|
|
335
365
|
rdfs:comment "Simple formatter: (fmt arg1 ... argN) -> output string. Only %s and %% are supported; other specifiers fail." .
|
|
336
366
|
|
|
367
|
+
string:length a ex:Builtin ; ex:kind ex:Function ;
|
|
368
|
+
rdfs:comment "Length of a string (like SPARQL STRLEN). (Eyeling extension.)" .
|
|
369
|
+
|
|
370
|
+
string:substring a ex:Builtin ; ex:kind ex:Function ;
|
|
371
|
+
rdfs:comment "Substring: subject must be a list (string start [len]); start is 1-based (like SPARQL SUBSTR). (Eyeling extension.)" .
|
|
372
|
+
|
|
373
|
+
string:upperCase a ex:Builtin ; ex:kind ex:Function ;
|
|
374
|
+
rdfs:comment "Uppercase conversion (like SPARQL UCASE). (Eyeling extension.)" .
|
|
375
|
+
|
|
376
|
+
string:lowerCase a ex:Builtin ; ex:kind ex:Function ;
|
|
377
|
+
rdfs:comment "Lowercase conversion (like SPARQL LCASE). (Eyeling extension.)" .
|
|
378
|
+
|
|
379
|
+
string:encodeForURI a ex:Builtin ; ex:kind ex:Function ;
|
|
380
|
+
rdfs:comment "Percent-encode a string (like SPARQL ENCODE_FOR_URI). (Eyeling extension.)" .
|
|
381
|
+
|
|
382
|
+
string:jsonPointer a ex:Builtin ; ex:kind ex:Function ;
|
|
383
|
+
rdfs:comment "JSON Pointer lookup: (jsonText pointer) -> value term. Expects rdf:JSON-typed literal (or treated as rdf:JSON); caches parsed JSON and pointer lookups." .
|
|
384
|
+
|
package/eyeling.js
CHANGED
|
@@ -814,6 +814,37 @@ function __makeSkolemRunSalt() {
|
|
|
814
814
|
);
|
|
815
815
|
}
|
|
816
816
|
|
|
817
|
+
function __randomUuidV4() {
|
|
818
|
+
// Best-effort UUID v4 generator (Node + browsers). Used by log:uuid / log:struuid.
|
|
819
|
+
try {
|
|
820
|
+
if (typeof globalThis !== 'undefined' && globalThis.crypto && typeof globalThis.crypto.randomUUID === 'function') {
|
|
821
|
+
return globalThis.crypto.randomUUID();
|
|
822
|
+
}
|
|
823
|
+
} catch {}
|
|
824
|
+
|
|
825
|
+
try {
|
|
826
|
+
if (nodeCrypto && typeof nodeCrypto.randomUUID === 'function') return nodeCrypto.randomUUID();
|
|
827
|
+
} catch {}
|
|
828
|
+
|
|
829
|
+
// Fallback: v4 using random bytes (Node) or Math.random
|
|
830
|
+
let bytes = null;
|
|
831
|
+
try {
|
|
832
|
+
if (nodeCrypto && typeof nodeCrypto.randomBytes === 'function') bytes = nodeCrypto.randomBytes(16);
|
|
833
|
+
} catch {}
|
|
834
|
+
if (!bytes) {
|
|
835
|
+
bytes = new Uint8Array(16);
|
|
836
|
+
for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Set version (4) and variant (RFC4122)
|
|
840
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
841
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
842
|
+
|
|
843
|
+
const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
844
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
|
|
817
848
|
function __enterReasoningRun() {
|
|
818
849
|
__skolemRunDepth += 1;
|
|
819
850
|
if (__skolemRunDepth === 1) {
|
|
@@ -860,6 +891,8 @@ const __parseNumCache = new Map(); // lit string -> number|null
|
|
|
860
891
|
const __parseIntCache = new Map(); // lit string -> bigint|null
|
|
861
892
|
const __parseNumericInfoCache = new Map(); // lit string -> info|null
|
|
862
893
|
|
|
894
|
+
// Cache for string:jsonPointer: jsonText -> { parsed: any|null, ptrCache: Map<string, Term|null> }
|
|
895
|
+
const jsonPointerCache = new Map();
|
|
863
896
|
|
|
864
897
|
// -----------------------------------------------------------------------------
|
|
865
898
|
// log:conclusion cache
|
|
@@ -1990,6 +2023,112 @@ function termToJsStringDecoded(t) {
|
|
|
1990
2023
|
return stripQuotes(lex);
|
|
1991
2024
|
}
|
|
1992
2025
|
|
|
2026
|
+
function jsonPointerUnescape(seg) {
|
|
2027
|
+
// RFC6901: ~1 -> '/', ~0 -> '~'
|
|
2028
|
+
let out = '';
|
|
2029
|
+
for (let i = 0; i < seg.length; i++) {
|
|
2030
|
+
const c = seg[i];
|
|
2031
|
+
if (c !== '~') {
|
|
2032
|
+
out += c;
|
|
2033
|
+
continue;
|
|
2034
|
+
}
|
|
2035
|
+
if (i + 1 >= seg.length) return null;
|
|
2036
|
+
const n = seg[i + 1];
|
|
2037
|
+
if (n === '0') out += '~';
|
|
2038
|
+
else if (n === '1') out += '/';
|
|
2039
|
+
else return null;
|
|
2040
|
+
i++;
|
|
2041
|
+
}
|
|
2042
|
+
return out;
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
function jsonToTerm(v) {
|
|
2046
|
+
if (v === null) return makeStringLiteral('null');
|
|
2047
|
+
if (typeof v === 'string') return makeStringLiteral(v);
|
|
2048
|
+
if (typeof v === 'number') return internLiteral(String(v));
|
|
2049
|
+
if (typeof v === 'boolean') return internLiteral(v ? 'true' : 'false');
|
|
2050
|
+
if (Array.isArray(v)) return new ListTerm(v.map(jsonToTerm));
|
|
2051
|
+
|
|
2052
|
+
if (v && typeof v === 'object') {
|
|
2053
|
+
return makeRdfJsonLiteral(JSON.stringify(v));
|
|
2054
|
+
}
|
|
2055
|
+
return null;
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
function jsonPointerLookup(jsonText, pointer) {
|
|
2059
|
+
let ptr = pointer;
|
|
2060
|
+
|
|
2061
|
+
// Support URI fragment form "#/a/b"
|
|
2062
|
+
if (ptr.startsWith('#')) {
|
|
2063
|
+
try {
|
|
2064
|
+
ptr = decodeURIComponent(ptr.slice(1));
|
|
2065
|
+
} catch {
|
|
2066
|
+
return null;
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
let entry = jsonPointerCache.get(jsonText);
|
|
2071
|
+
if (!entry) {
|
|
2072
|
+
let parsed = null;
|
|
2073
|
+
try {
|
|
2074
|
+
parsed = JSON.parse(jsonText);
|
|
2075
|
+
} catch {
|
|
2076
|
+
parsed = null;
|
|
2077
|
+
}
|
|
2078
|
+
entry = { parsed, ptrCache: new Map() };
|
|
2079
|
+
jsonPointerCache.set(jsonText, entry);
|
|
2080
|
+
}
|
|
2081
|
+
if (entry.parsed === null) return null;
|
|
2082
|
+
|
|
2083
|
+
if (entry.ptrCache.has(ptr)) return entry.ptrCache.get(ptr);
|
|
2084
|
+
|
|
2085
|
+
let cur = entry.parsed;
|
|
2086
|
+
|
|
2087
|
+
if (ptr === '') {
|
|
2088
|
+
const t = jsonToTerm(cur);
|
|
2089
|
+
entry.ptrCache.set(ptr, t);
|
|
2090
|
+
return t;
|
|
2091
|
+
}
|
|
2092
|
+
if (!ptr.startsWith('/')) {
|
|
2093
|
+
entry.ptrCache.set(ptr, null);
|
|
2094
|
+
return null;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
const parts = ptr.split('/').slice(1);
|
|
2098
|
+
for (const raw of parts) {
|
|
2099
|
+
const seg = jsonPointerUnescape(raw);
|
|
2100
|
+
if (seg === null) {
|
|
2101
|
+
entry.ptrCache.set(ptr, null);
|
|
2102
|
+
return null;
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
if (Array.isArray(cur)) {
|
|
2106
|
+
if (!/^(0|[1-9]\d*)$/.test(seg)) {
|
|
2107
|
+
entry.ptrCache.set(ptr, null);
|
|
2108
|
+
return null;
|
|
2109
|
+
}
|
|
2110
|
+
const idx = Number(seg);
|
|
2111
|
+
if (idx < 0 || idx >= cur.length) {
|
|
2112
|
+
entry.ptrCache.set(ptr, null);
|
|
2113
|
+
return null;
|
|
2114
|
+
}
|
|
2115
|
+
cur = cur[idx];
|
|
2116
|
+
} else if (cur !== null && typeof cur === 'object') {
|
|
2117
|
+
if (!Object.prototype.hasOwnProperty.call(cur, seg)) {
|
|
2118
|
+
entry.ptrCache.set(ptr, null);
|
|
2119
|
+
return null;
|
|
2120
|
+
}
|
|
2121
|
+
cur = cur[seg];
|
|
2122
|
+
} else {
|
|
2123
|
+
entry.ptrCache.set(ptr, null);
|
|
2124
|
+
return null;
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
const out = jsonToTerm(cur);
|
|
2129
|
+
entry.ptrCache.set(ptr, out);
|
|
2130
|
+
return out;
|
|
2131
|
+
}
|
|
1993
2132
|
|
|
1994
2133
|
// Tiny subset of sprintf: supports only %s and %%.
|
|
1995
2134
|
// Good enough for most N3 string:format use cases that just splice strings.
|
|
@@ -3108,7 +3247,9 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
3108
3247
|
? 'sha256'
|
|
3109
3248
|
: pv === CRYPTO_NS + 'sha512'
|
|
3110
3249
|
? 'sha512'
|
|
3111
|
-
:
|
|
3250
|
+
: pv === CRYPTO_NS + 'sha384'
|
|
3251
|
+
? 'sha384'
|
|
3252
|
+
: null;
|
|
3112
3253
|
if (cryptoAlgo) return evalCryptoHashBuiltin(g, subst, cryptoAlgo);
|
|
3113
3254
|
|
|
3114
3255
|
// -----------------------------------------------------------------
|
|
@@ -3601,6 +3742,38 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
3601
3742
|
s2[g.o.name] = lit;
|
|
3602
3743
|
return [s2];
|
|
3603
3744
|
}
|
|
3745
|
+
|
|
3746
|
+
// math:ceiling (Eyeling extension)
|
|
3747
|
+
// Schema: $s+ math:ceiling $o-
|
|
3748
|
+
// Smallest integer >= s (fails on NaN / non-numeric).
|
|
3749
|
+
if (pv === MATH_NS + 'ceiling') {
|
|
3750
|
+
const info = parseNumericLiteralInfo(g.s);
|
|
3751
|
+
if (!info) return [];
|
|
3752
|
+
if (typeof info.value !== 'number') {
|
|
3753
|
+
// BigInt (xsd:integer) – already integral
|
|
3754
|
+
const lit = internLiteral(String(info.value));
|
|
3755
|
+
return unifyTermMaybe(g.o, lit, subst);
|
|
3756
|
+
}
|
|
3757
|
+
if (Number.isNaN(info.value) || !Number.isFinite(info.value)) return [];
|
|
3758
|
+
const lit = internLiteral(String(Math.ceil(info.value)));
|
|
3759
|
+
return unifyTermMaybe(g.o, lit, subst);
|
|
3760
|
+
}
|
|
3761
|
+
|
|
3762
|
+
// math:floor (Eyeling extension)
|
|
3763
|
+
// Schema: $s+ math:floor $o-
|
|
3764
|
+
// Largest integer <= s (fails on NaN / non-numeric).
|
|
3765
|
+
if (pv === MATH_NS + 'floor') {
|
|
3766
|
+
const info = parseNumericLiteralInfo(g.s);
|
|
3767
|
+
if (!info) return [];
|
|
3768
|
+
if (typeof info.value !== 'number') {
|
|
3769
|
+
const lit = internLiteral(String(info.value));
|
|
3770
|
+
return unifyTermMaybe(g.o, lit, subst);
|
|
3771
|
+
}
|
|
3772
|
+
if (Number.isNaN(info.value) || !Number.isFinite(info.value)) return [];
|
|
3773
|
+
const lit = internLiteral(String(Math.floor(info.value)));
|
|
3774
|
+
return unifyTermMaybe(g.o, lit, subst);
|
|
3775
|
+
}
|
|
3776
|
+
|
|
3604
3777
|
if (g.o instanceof Blank) return [{ ...subst }];
|
|
3605
3778
|
|
|
3606
3779
|
// Accept typed numeric literals too (e.g., "3"^^xsd:float) if numerically equal.
|
|
@@ -4397,6 +4570,44 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
4397
4570
|
s2[g.o.name] = ty;
|
|
4398
4571
|
return [s2];
|
|
4399
4572
|
}
|
|
4573
|
+
|
|
4574
|
+
// log:isIRI / log:isLiteral / log:isBlank / log:isNumeric / log:isTriple (Eyeling extensions)
|
|
4575
|
+
// Schema: $s+ log:isIRI $o? (succeeds when s matches; o may be 'true' or a variable)
|
|
4576
|
+
function unifyBoolTrue(obj, subst0) {
|
|
4577
|
+
if (obj instanceof Blank) return [subst0];
|
|
4578
|
+
const tTrue = internLiteral('true');
|
|
4579
|
+
const s2 = unifyTermMaybe(obj, tTrue, subst0);
|
|
4580
|
+
return s2 ? [s2] : [];
|
|
4581
|
+
}
|
|
4582
|
+
|
|
4583
|
+
if (pv === LOG_NS + 'isIRI') {
|
|
4584
|
+
if (!(g.s instanceof Iri)) return [];
|
|
4585
|
+
return unifyBoolTrue(g.o, subst);
|
|
4586
|
+
}
|
|
4587
|
+
|
|
4588
|
+
if (pv === LOG_NS + 'isLiteral') {
|
|
4589
|
+
if (!(g.s instanceof Literal)) return [];
|
|
4590
|
+
return unifyBoolTrue(g.o, subst);
|
|
4591
|
+
}
|
|
4592
|
+
|
|
4593
|
+
if (pv === LOG_NS + 'isBlank') {
|
|
4594
|
+
if (!(g.s instanceof Blank)) return [];
|
|
4595
|
+
return unifyBoolTrue(g.o, subst);
|
|
4596
|
+
}
|
|
4597
|
+
|
|
4598
|
+
if (pv === LOG_NS + 'isNumeric') {
|
|
4599
|
+
if (!(g.s instanceof Literal)) return [];
|
|
4600
|
+
const dt = numericDatatypeOfTerm(g.s);
|
|
4601
|
+
if (!dt) return [];
|
|
4602
|
+
return unifyBoolTrue(g.o, subst);
|
|
4603
|
+
}
|
|
4604
|
+
|
|
4605
|
+
if (pv === LOG_NS + 'isTriple') {
|
|
4606
|
+
if (!(g.s instanceof GraphTerm)) return [];
|
|
4607
|
+
if (g.s.triples.length !== 1) return [];
|
|
4608
|
+
return unifyBoolTrue(g.o, subst);
|
|
4609
|
+
}
|
|
4610
|
+
|
|
4400
4611
|
if (g.o instanceof Blank) return [{ ...subst }];
|
|
4401
4612
|
|
|
4402
4613
|
const s2 = unifyTerm(g.o, ty, subst);
|
|
@@ -4875,6 +5086,24 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
4875
5086
|
skolemCache.set(key, iri);
|
|
4876
5087
|
}
|
|
4877
5088
|
|
|
5089
|
+
// log:uuid / log:struuid (Eyeling extensions)
|
|
5090
|
+
// NOTE: these generate fresh values and can affect termination; prefer log:skolem for deterministic IDs.
|
|
5091
|
+
//
|
|
5092
|
+
// log:uuid: $s? log:uuid $o- -> binds $o to a fresh <urn:uuid:...> IRI
|
|
5093
|
+
// log:struuid: $s? log:struuid $o- -> binds $o to a fresh UUID string literal
|
|
5094
|
+
if (pv === LOG_NS + 'uuid') {
|
|
5095
|
+
const uuid = __randomUuidV4();
|
|
5096
|
+
const iri = internIri('urn:uuid:' + uuid);
|
|
5097
|
+
return unifyTermMaybe(g.o, iri, subst);
|
|
5098
|
+
}
|
|
5099
|
+
|
|
5100
|
+
if (pv === LOG_NS + 'struuid') {
|
|
5101
|
+
const uuid = __randomUuidV4();
|
|
5102
|
+
const lit = makeStringLiteral(uuid);
|
|
5103
|
+
return unifyTermMaybe(g.o, lit, subst);
|
|
5104
|
+
}
|
|
5105
|
+
|
|
5106
|
+
|
|
4878
5107
|
const s2 = unifyTerm(goal.o, iri, subst);
|
|
4879
5108
|
return s2 !== null ? [s2] : [];
|
|
4880
5109
|
}
|
|
@@ -4982,6 +5211,68 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
4982
5211
|
args.push(aStr);
|
|
4983
5212
|
}
|
|
4984
5213
|
|
|
5214
|
+
// string:length (Eyeling extension)
|
|
5215
|
+
// Schema: $s+ string:length $o-
|
|
5216
|
+
if (pv === STRING_NS + 'length') {
|
|
5217
|
+
const s0 = termToJsStringDecoded(g.s);
|
|
5218
|
+
if (s0 === null) return [];
|
|
5219
|
+
const lit = internLiteral(String(s0.length));
|
|
5220
|
+
return unifyTermMaybe(g.o, lit, subst);
|
|
5221
|
+
}
|
|
5222
|
+
|
|
5223
|
+
// string:upperCase / string:lowerCase (Eyeling extensions)
|
|
5224
|
+
// Schema: $s+ string:upperCase $o- ; $s+ string:lowerCase $o-
|
|
5225
|
+
if (pv === STRING_NS + 'upperCase' || pv === STRING_NS + 'lowerCase') {
|
|
5226
|
+
const s0 = termToJsStringDecoded(g.s);
|
|
5227
|
+
if (s0 === null) return [];
|
|
5228
|
+
const out = pv.endsWith('upperCase') ? s0.toUpperCase() : s0.toLowerCase();
|
|
5229
|
+
const lit = makeStringLiteral(out);
|
|
5230
|
+
return unifyTermMaybe(g.o, lit, subst);
|
|
5231
|
+
}
|
|
5232
|
+
|
|
5233
|
+
// string:encodeForURI (Eyeling extension)
|
|
5234
|
+
// Schema: $s+ string:encodeForURI $o-
|
|
5235
|
+
if (pv === STRING_NS + 'encodeForURI') {
|
|
5236
|
+
const s0 = termToJsStringDecoded(g.s);
|
|
5237
|
+
if (s0 === null) return [];
|
|
5238
|
+
// SPARQL-like: start with encodeURIComponent and also escape [!'()*]
|
|
5239
|
+
const enc = encodeURIComponent(s0).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
5240
|
+
const lit = makeStringLiteral(enc);
|
|
5241
|
+
return unifyTermMaybe(g.o, lit, subst);
|
|
5242
|
+
}
|
|
5243
|
+
|
|
5244
|
+
// string:substring (Eyeling extension)
|
|
5245
|
+
// Schema: ( $s+ $start+ [$len+] ) string:substring $o-
|
|
5246
|
+
// NOTE: start is 1-based (SPARQL SUBSTR), len is optional.
|
|
5247
|
+
if (pv === STRING_NS + 'substring') {
|
|
5248
|
+
if (!(g.s instanceof ListTerm)) return [];
|
|
5249
|
+
const items = g.s.items;
|
|
5250
|
+
if (items.length !== 2 && items.length !== 3) return [];
|
|
5251
|
+
const s0 = termToJsStringDecoded(items[0]);
|
|
5252
|
+
if (s0 === null) return [];
|
|
5253
|
+
const startInfo = parseNumericLiteralInfo(items[1]);
|
|
5254
|
+
if (!startInfo || typeof startInfo.value !== 'number' || Number.isNaN(startInfo.value)) return [];
|
|
5255
|
+
let start = Math.floor(startInfo.value);
|
|
5256
|
+
if (!Number.isFinite(start)) return [];
|
|
5257
|
+
start = Math.max(1, start);
|
|
5258
|
+
|
|
5259
|
+
let outStr = '';
|
|
5260
|
+
if (items.length === 2) {
|
|
5261
|
+
outStr = s0.slice(start - 1);
|
|
5262
|
+
} else {
|
|
5263
|
+
const lenInfo = parseNumericLiteralInfo(items[2]);
|
|
5264
|
+
if (!lenInfo || typeof lenInfo.value !== 'number' || Number.isNaN(lenInfo.value)) return [];
|
|
5265
|
+
let len = Math.floor(lenInfo.value);
|
|
5266
|
+
if (!Number.isFinite(len)) return [];
|
|
5267
|
+
if (len <= 0) outStr = '';
|
|
5268
|
+
else outStr = s0.slice(start - 1, start - 1 + len);
|
|
5269
|
+
}
|
|
5270
|
+
|
|
5271
|
+
const lit = makeStringLiteral(outStr);
|
|
5272
|
+
return unifyTermMaybe(g.o, lit, subst);
|
|
5273
|
+
}
|
|
5274
|
+
|
|
5275
|
+
|
|
4985
5276
|
const formatted = simpleStringFormat(fmtStr, args);
|
|
4986
5277
|
if (formatted === null) return []; // unsupported format specifier(s)
|
|
4987
5278
|
|
|
@@ -4995,6 +5286,21 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
4995
5286
|
return s2 !== null ? [s2] : [];
|
|
4996
5287
|
}
|
|
4997
5288
|
|
|
5289
|
+
// string:jsonPointer
|
|
5290
|
+
// Schema: ( $jsonText $pointer ) string:jsonPointer $value
|
|
5291
|
+
if (pv === STRING_NS + 'jsonPointer') {
|
|
5292
|
+
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
5293
|
+
|
|
5294
|
+
const jsonText = termToJsonText(g.s.elems[0]);
|
|
5295
|
+
const ptr = termToJsStringDecoded(g.s.elems[1]);
|
|
5296
|
+
if (jsonText === null || ptr === null) return [];
|
|
5297
|
+
|
|
5298
|
+
const valTerm = jsonPointerLookup(jsonText, ptr);
|
|
5299
|
+
if (valTerm === null) return [];
|
|
5300
|
+
|
|
5301
|
+
const s2 = unifyTerm(g.o, valTerm, subst);
|
|
5302
|
+
return s2 !== null ? [s2] : [];
|
|
5303
|
+
}
|
|
4998
5304
|
|
|
4999
5305
|
// string:greaterThan
|
|
5000
5306
|
if (pv === STRING_NS + 'greaterThan') {
|
|
@@ -8084,6 +8390,11 @@ function isConstraintBuiltin(tr) {
|
|
|
8084
8390
|
v === LOG_NS + 'forAllIn' ||
|
|
8085
8391
|
v === LOG_NS + 'notEqualTo' ||
|
|
8086
8392
|
v === LOG_NS + 'notIncludes' ||
|
|
8393
|
+
v === LOG_NS + 'isIRI' ||
|
|
8394
|
+
v === LOG_NS + 'isLiteral' ||
|
|
8395
|
+
v === LOG_NS + 'isBlank' ||
|
|
8396
|
+
v === LOG_NS + 'isNumeric' ||
|
|
8397
|
+
v === LOG_NS + 'isTriple' ||
|
|
8087
8398
|
v === LOG_NS + 'outputString'
|
|
8088
8399
|
) {
|
|
8089
8400
|
return true;
|