eyeling 1.29.2 → 1.29.3
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/dist/browser/eyeling.browser.js +150 -8
- package/eyeling.js +150 -8
- package/lib/builtins.js +128 -3
- package/lib/engine.js +1 -1
- package/lib/prelude.js +18 -0
- package/lib/rules.js +3 -4
- package/package.json +1 -1
- package/test/builtins.test.js +36 -0
|
@@ -41,6 +41,8 @@ const {
|
|
|
41
41
|
PrefixEnv,
|
|
42
42
|
literalParts,
|
|
43
43
|
copyQuotedGraphMetadata,
|
|
44
|
+
isInternalBlankVarName,
|
|
45
|
+
internalBlankVarSuffix,
|
|
44
46
|
} = require('./prelude');
|
|
45
47
|
|
|
46
48
|
const { decodeN3StringEscapes } = require('./lexer');
|
|
@@ -143,6 +145,127 @@ function __assertBuiltinHandlerResult(iri, out) {
|
|
|
143
145
|
}
|
|
144
146
|
}
|
|
145
147
|
|
|
148
|
+
function collectVarNamesInTerm(t, acc) {
|
|
149
|
+
if (t instanceof Var) {
|
|
150
|
+
acc.add(t.name);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (t instanceof ListTerm) {
|
|
154
|
+
for (const e of t.elems) collectVarNamesInTerm(e, acc);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (t instanceof OpenListTerm) {
|
|
158
|
+
for (const e of t.prefix) collectVarNamesInTerm(e, acc);
|
|
159
|
+
acc.add(t.tailVar);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (t instanceof GraphTerm) {
|
|
163
|
+
for (const tr of t.triples) collectVarNamesInTriple(tr, acc);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function collectVarNamesInTriple(tr, acc) {
|
|
168
|
+
collectVarNamesInTerm(tr.s, acc);
|
|
169
|
+
collectVarNamesInTerm(tr.p, acc);
|
|
170
|
+
collectVarNamesInTerm(tr.o, acc);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function cloneSubstWithMappedKeys(subst, keyMap, externalizeTerm) {
|
|
174
|
+
const out = Object.create(null);
|
|
175
|
+
for (const [key, val] of Object.entries(subst || {})) {
|
|
176
|
+
out[keyMap.get(key) || key] = externalizeTerm ? externalizeTerm(val) : val;
|
|
177
|
+
}
|
|
178
|
+
return out;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function hasInternalBlankVarInSubst(subst) {
|
|
182
|
+
for (const key of Object.keys(subst || {})) {
|
|
183
|
+
if (isInternalBlankVarName(key)) return true;
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function makeCustomBuiltinPublicBridge(goal, subst) {
|
|
189
|
+
const used = new Set(Object.keys(subst || {}).filter((name) => !isInternalBlankVarName(name)));
|
|
190
|
+
collectVarNamesInTriple(goal, used);
|
|
191
|
+
for (const name of Array.from(used)) {
|
|
192
|
+
if (isInternalBlankVarName(name)) used.delete(name);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const internalNames = new Set();
|
|
196
|
+
collectVarNamesInTriple(goal, internalNames);
|
|
197
|
+
for (const key of Object.keys(subst || {})) internalNames.add(key);
|
|
198
|
+
for (const name of Array.from(internalNames)) {
|
|
199
|
+
if (!isInternalBlankVarName(name)) internalNames.delete(name);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (internalNames.size === 0 && !hasInternalBlankVarInSubst(subst)) return null;
|
|
203
|
+
|
|
204
|
+
const internalToPublic = new Map();
|
|
205
|
+
const publicToInternal = new Map();
|
|
206
|
+
|
|
207
|
+
function allocatePublicName(internalName) {
|
|
208
|
+
const suffix = internalBlankVarSuffix(internalName) || String(internalToPublic.size + 1);
|
|
209
|
+
const base = /^[$A-Za-z_][0-9A-Za-z_]*$/u.test(`_b${suffix}`) ? `_b${suffix}` : `_b${internalToPublic.size + 1}`;
|
|
210
|
+
let name = base;
|
|
211
|
+
let n = 1;
|
|
212
|
+
while (used.has(name) || publicToInternal.has(name)) {
|
|
213
|
+
n += 1;
|
|
214
|
+
name = `${base}_${n}`;
|
|
215
|
+
}
|
|
216
|
+
used.add(name);
|
|
217
|
+
internalToPublic.set(internalName, name);
|
|
218
|
+
publicToInternal.set(name, internalName);
|
|
219
|
+
return name;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
for (const name of internalNames) allocatePublicName(name);
|
|
223
|
+
|
|
224
|
+
function externalName(name) {
|
|
225
|
+
if (!isInternalBlankVarName(name)) return name;
|
|
226
|
+
return internalToPublic.get(name) || allocatePublicName(name);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function externalizeTerm(t) {
|
|
230
|
+
if (t instanceof Var) return new Var(externalName(t.name));
|
|
231
|
+
if (t instanceof ListTerm) return new ListTerm(t.elems.map(externalizeTerm));
|
|
232
|
+
if (t instanceof OpenListTerm) return new OpenListTerm(t.prefix.map(externalizeTerm), externalName(t.tailVar));
|
|
233
|
+
if (t instanceof GraphTerm) {
|
|
234
|
+
const triples = t.triples.map((tr) => new Triple(externalizeTerm(tr.s), externalizeTerm(tr.p), externalizeTerm(tr.o)));
|
|
235
|
+
return copyQuotedGraphMetadata(t, new GraphTerm(triples));
|
|
236
|
+
}
|
|
237
|
+
return t;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function internalizeTerm(t) {
|
|
241
|
+
if (t instanceof Var) return new Var(publicToInternal.get(t.name) || t.name);
|
|
242
|
+
if (t instanceof ListTerm) return new ListTerm(t.elems.map(internalizeTerm));
|
|
243
|
+
if (t instanceof OpenListTerm) return new OpenListTerm(t.prefix.map(internalizeTerm), publicToInternal.get(t.tailVar) || t.tailVar);
|
|
244
|
+
if (t instanceof GraphTerm) {
|
|
245
|
+
const triples = t.triples.map((tr) => new Triple(internalizeTerm(tr.s), internalizeTerm(tr.p), internalizeTerm(tr.o)));
|
|
246
|
+
return copyQuotedGraphMetadata(t, new GraphTerm(triples));
|
|
247
|
+
}
|
|
248
|
+
return t;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function internalizeDelta(delta) {
|
|
252
|
+
const out = Object.create(null);
|
|
253
|
+
for (const [key, val] of Object.entries(delta || {})) {
|
|
254
|
+
out[publicToInternal.get(key) || key] = internalizeTerm(val);
|
|
255
|
+
}
|
|
256
|
+
return out;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
goal: new Triple(externalizeTerm(goal.s), externalizeTerm(goal.p), externalizeTerm(goal.o)),
|
|
261
|
+
subst: cloneSubstWithMappedKeys(subst, internalToPublic, externalizeTerm),
|
|
262
|
+
internalizeResults(out) {
|
|
263
|
+
if (out == null) return out;
|
|
264
|
+
return out.map(internalizeDelta);
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
146
269
|
function apiTermToN3(term, prefixes = PrefixEnv.newDefault()) {
|
|
147
270
|
return termToN3(term, prefixes || PrefixEnv.newDefault());
|
|
148
271
|
}
|
|
@@ -242,10 +365,11 @@ function __evalRegisteredBuiltin(pv, goal, subst, facts, backRules, depth, varGe
|
|
|
242
365
|
const handler = __customBuiltinHandlers.get(pv);
|
|
243
366
|
if (typeof handler !== 'function') return null;
|
|
244
367
|
|
|
368
|
+
const bridge = makeCustomBuiltinPublicBridge(goal, subst);
|
|
245
369
|
const ctx = {
|
|
246
370
|
iri: pv,
|
|
247
|
-
goal,
|
|
248
|
-
subst,
|
|
371
|
+
goal: bridge ? bridge.goal : goal,
|
|
372
|
+
subst: bridge ? bridge.subst : subst,
|
|
249
373
|
facts,
|
|
250
374
|
backRules,
|
|
251
375
|
depth,
|
|
@@ -255,7 +379,8 @@ function __evalRegisteredBuiltin(pv, goal, subst, facts, backRules, depth, varGe
|
|
|
255
379
|
};
|
|
256
380
|
|
|
257
381
|
try {
|
|
258
|
-
const
|
|
382
|
+
const raw = handler(ctx);
|
|
383
|
+
const out = bridge ? bridge.internalizeResults(raw) : raw;
|
|
259
384
|
if (out == null) return [];
|
|
260
385
|
__assertBuiltinHandlerResult(pv, out);
|
|
261
386
|
return out;
|
|
@@ -10995,7 +11120,7 @@ async function runStoreBacked(input, store, opts = {}) {
|
|
|
10995
11120
|
}
|
|
10996
11121
|
|
|
10997
11122
|
let queryTriples = [];
|
|
10998
|
-
|
|
11123
|
+
let queryDerived = [];
|
|
10999
11124
|
if (qrules.length) {
|
|
11000
11125
|
for (const r of qrules) {
|
|
11001
11126
|
const solutions = await __proveGoalsAgainstStore(r.premise || [], __emptySubst(), store, brules, triples, 0, varGen, {});
|
|
@@ -15468,6 +15593,21 @@ const DT_NS = 'https://eyereasoner.github.io/eyeling/datatype#';
|
|
|
15468
15593
|
const SKOLEM_NS = 'https://eyereasoner.github.io/.well-known/genid/';
|
|
15469
15594
|
const RDF_JSON_DT = RDF_NS + 'JSON';
|
|
15470
15595
|
|
|
15596
|
+
// Private rule-lifting variable prefix used for blank nodes that appear in
|
|
15597
|
+
// rule-body patterns. The prefix is intentionally not spellable in N3 input,
|
|
15598
|
+
// but public extension APIs should present these variables with stable,
|
|
15599
|
+
// ordinary names and translate them back internally.
|
|
15600
|
+
const INTERNAL_BLANK_VAR_PREFIX = '\uE000eyeling_b';
|
|
15601
|
+
|
|
15602
|
+
function isInternalBlankVarName(name) {
|
|
15603
|
+
return typeof name === 'string' && name.startsWith(INTERNAL_BLANK_VAR_PREFIX);
|
|
15604
|
+
}
|
|
15605
|
+
|
|
15606
|
+
function internalBlankVarSuffix(name) {
|
|
15607
|
+
if (!isInternalBlankVarName(name)) return '';
|
|
15608
|
+
return name.slice(INTERNAL_BLANK_VAR_PREFIX.length);
|
|
15609
|
+
}
|
|
15610
|
+
|
|
15471
15611
|
function parseUriReferenceForResolution(uri) {
|
|
15472
15612
|
// RFC 3986 Appendix B-style component parser, with the scheme tightened to
|
|
15473
15613
|
// the RFC scheme grammar. Capturing delimiter presence matters: `?` with an
|
|
@@ -16171,6 +16311,9 @@ module.exports = {
|
|
|
16171
16311
|
DT_NS,
|
|
16172
16312
|
SKOLEM_NS,
|
|
16173
16313
|
RDF_JSON_DT,
|
|
16314
|
+
INTERNAL_BLANK_VAR_PREFIX,
|
|
16315
|
+
isInternalBlankVarName,
|
|
16316
|
+
internalBlankVarSuffix,
|
|
16174
16317
|
resolveIriRef,
|
|
16175
16318
|
literalParts,
|
|
16176
16319
|
normalizeLiteralForTid,
|
|
@@ -17645,6 +17788,7 @@ const {
|
|
|
17645
17788
|
GraphTerm,
|
|
17646
17789
|
Triple,
|
|
17647
17790
|
copyQuotedGraphMetadata,
|
|
17791
|
+
INTERNAL_BLANK_VAR_PREFIX,
|
|
17648
17792
|
} = require('./prelude');
|
|
17649
17793
|
|
|
17650
17794
|
function liftBlankRuleVars(premise, conclusion) {
|
|
@@ -17660,10 +17804,8 @@ function liftBlankRuleVars(premise, conclusion) {
|
|
|
17660
17804
|
// These variables are implementation details used to correlate one blank
|
|
17661
17805
|
// node across the lifted rule body. Their names must not be spellable by
|
|
17662
17806
|
// user input; otherwise a user variable such as ?_b1 can capture the
|
|
17663
|
-
// lifted blank node and leak it into the rule head.
|
|
17664
|
-
//
|
|
17665
|
-
const INTERNAL_BLANK_VAR_PREFIX = '\uE000eyeling_b';
|
|
17666
|
-
|
|
17807
|
+
// lifted blank node and leak it into the rule head. The public custom
|
|
17808
|
+
// builtin API maps these names back to ordinary-looking ?_bN variables.
|
|
17667
17809
|
function blankToVar(label) {
|
|
17668
17810
|
let name = mapping[label];
|
|
17669
17811
|
if (name === undefined) {
|
package/eyeling.js
CHANGED
|
@@ -41,6 +41,8 @@ const {
|
|
|
41
41
|
PrefixEnv,
|
|
42
42
|
literalParts,
|
|
43
43
|
copyQuotedGraphMetadata,
|
|
44
|
+
isInternalBlankVarName,
|
|
45
|
+
internalBlankVarSuffix,
|
|
44
46
|
} = require('./prelude');
|
|
45
47
|
|
|
46
48
|
const { decodeN3StringEscapes } = require('./lexer');
|
|
@@ -143,6 +145,127 @@ function __assertBuiltinHandlerResult(iri, out) {
|
|
|
143
145
|
}
|
|
144
146
|
}
|
|
145
147
|
|
|
148
|
+
function collectVarNamesInTerm(t, acc) {
|
|
149
|
+
if (t instanceof Var) {
|
|
150
|
+
acc.add(t.name);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (t instanceof ListTerm) {
|
|
154
|
+
for (const e of t.elems) collectVarNamesInTerm(e, acc);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (t instanceof OpenListTerm) {
|
|
158
|
+
for (const e of t.prefix) collectVarNamesInTerm(e, acc);
|
|
159
|
+
acc.add(t.tailVar);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (t instanceof GraphTerm) {
|
|
163
|
+
for (const tr of t.triples) collectVarNamesInTriple(tr, acc);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function collectVarNamesInTriple(tr, acc) {
|
|
168
|
+
collectVarNamesInTerm(tr.s, acc);
|
|
169
|
+
collectVarNamesInTerm(tr.p, acc);
|
|
170
|
+
collectVarNamesInTerm(tr.o, acc);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function cloneSubstWithMappedKeys(subst, keyMap, externalizeTerm) {
|
|
174
|
+
const out = Object.create(null);
|
|
175
|
+
for (const [key, val] of Object.entries(subst || {})) {
|
|
176
|
+
out[keyMap.get(key) || key] = externalizeTerm ? externalizeTerm(val) : val;
|
|
177
|
+
}
|
|
178
|
+
return out;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function hasInternalBlankVarInSubst(subst) {
|
|
182
|
+
for (const key of Object.keys(subst || {})) {
|
|
183
|
+
if (isInternalBlankVarName(key)) return true;
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function makeCustomBuiltinPublicBridge(goal, subst) {
|
|
189
|
+
const used = new Set(Object.keys(subst || {}).filter((name) => !isInternalBlankVarName(name)));
|
|
190
|
+
collectVarNamesInTriple(goal, used);
|
|
191
|
+
for (const name of Array.from(used)) {
|
|
192
|
+
if (isInternalBlankVarName(name)) used.delete(name);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const internalNames = new Set();
|
|
196
|
+
collectVarNamesInTriple(goal, internalNames);
|
|
197
|
+
for (const key of Object.keys(subst || {})) internalNames.add(key);
|
|
198
|
+
for (const name of Array.from(internalNames)) {
|
|
199
|
+
if (!isInternalBlankVarName(name)) internalNames.delete(name);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (internalNames.size === 0 && !hasInternalBlankVarInSubst(subst)) return null;
|
|
203
|
+
|
|
204
|
+
const internalToPublic = new Map();
|
|
205
|
+
const publicToInternal = new Map();
|
|
206
|
+
|
|
207
|
+
function allocatePublicName(internalName) {
|
|
208
|
+
const suffix = internalBlankVarSuffix(internalName) || String(internalToPublic.size + 1);
|
|
209
|
+
const base = /^[$A-Za-z_][0-9A-Za-z_]*$/u.test(`_b${suffix}`) ? `_b${suffix}` : `_b${internalToPublic.size + 1}`;
|
|
210
|
+
let name = base;
|
|
211
|
+
let n = 1;
|
|
212
|
+
while (used.has(name) || publicToInternal.has(name)) {
|
|
213
|
+
n += 1;
|
|
214
|
+
name = `${base}_${n}`;
|
|
215
|
+
}
|
|
216
|
+
used.add(name);
|
|
217
|
+
internalToPublic.set(internalName, name);
|
|
218
|
+
publicToInternal.set(name, internalName);
|
|
219
|
+
return name;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
for (const name of internalNames) allocatePublicName(name);
|
|
223
|
+
|
|
224
|
+
function externalName(name) {
|
|
225
|
+
if (!isInternalBlankVarName(name)) return name;
|
|
226
|
+
return internalToPublic.get(name) || allocatePublicName(name);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function externalizeTerm(t) {
|
|
230
|
+
if (t instanceof Var) return new Var(externalName(t.name));
|
|
231
|
+
if (t instanceof ListTerm) return new ListTerm(t.elems.map(externalizeTerm));
|
|
232
|
+
if (t instanceof OpenListTerm) return new OpenListTerm(t.prefix.map(externalizeTerm), externalName(t.tailVar));
|
|
233
|
+
if (t instanceof GraphTerm) {
|
|
234
|
+
const triples = t.triples.map((tr) => new Triple(externalizeTerm(tr.s), externalizeTerm(tr.p), externalizeTerm(tr.o)));
|
|
235
|
+
return copyQuotedGraphMetadata(t, new GraphTerm(triples));
|
|
236
|
+
}
|
|
237
|
+
return t;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function internalizeTerm(t) {
|
|
241
|
+
if (t instanceof Var) return new Var(publicToInternal.get(t.name) || t.name);
|
|
242
|
+
if (t instanceof ListTerm) return new ListTerm(t.elems.map(internalizeTerm));
|
|
243
|
+
if (t instanceof OpenListTerm) return new OpenListTerm(t.prefix.map(internalizeTerm), publicToInternal.get(t.tailVar) || t.tailVar);
|
|
244
|
+
if (t instanceof GraphTerm) {
|
|
245
|
+
const triples = t.triples.map((tr) => new Triple(internalizeTerm(tr.s), internalizeTerm(tr.p), internalizeTerm(tr.o)));
|
|
246
|
+
return copyQuotedGraphMetadata(t, new GraphTerm(triples));
|
|
247
|
+
}
|
|
248
|
+
return t;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function internalizeDelta(delta) {
|
|
252
|
+
const out = Object.create(null);
|
|
253
|
+
for (const [key, val] of Object.entries(delta || {})) {
|
|
254
|
+
out[publicToInternal.get(key) || key] = internalizeTerm(val);
|
|
255
|
+
}
|
|
256
|
+
return out;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
goal: new Triple(externalizeTerm(goal.s), externalizeTerm(goal.p), externalizeTerm(goal.o)),
|
|
261
|
+
subst: cloneSubstWithMappedKeys(subst, internalToPublic, externalizeTerm),
|
|
262
|
+
internalizeResults(out) {
|
|
263
|
+
if (out == null) return out;
|
|
264
|
+
return out.map(internalizeDelta);
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
146
269
|
function apiTermToN3(term, prefixes = PrefixEnv.newDefault()) {
|
|
147
270
|
return termToN3(term, prefixes || PrefixEnv.newDefault());
|
|
148
271
|
}
|
|
@@ -242,10 +365,11 @@ function __evalRegisteredBuiltin(pv, goal, subst, facts, backRules, depth, varGe
|
|
|
242
365
|
const handler = __customBuiltinHandlers.get(pv);
|
|
243
366
|
if (typeof handler !== 'function') return null;
|
|
244
367
|
|
|
368
|
+
const bridge = makeCustomBuiltinPublicBridge(goal, subst);
|
|
245
369
|
const ctx = {
|
|
246
370
|
iri: pv,
|
|
247
|
-
goal,
|
|
248
|
-
subst,
|
|
371
|
+
goal: bridge ? bridge.goal : goal,
|
|
372
|
+
subst: bridge ? bridge.subst : subst,
|
|
249
373
|
facts,
|
|
250
374
|
backRules,
|
|
251
375
|
depth,
|
|
@@ -255,7 +379,8 @@ function __evalRegisteredBuiltin(pv, goal, subst, facts, backRules, depth, varGe
|
|
|
255
379
|
};
|
|
256
380
|
|
|
257
381
|
try {
|
|
258
|
-
const
|
|
382
|
+
const raw = handler(ctx);
|
|
383
|
+
const out = bridge ? bridge.internalizeResults(raw) : raw;
|
|
259
384
|
if (out == null) return [];
|
|
260
385
|
__assertBuiltinHandlerResult(pv, out);
|
|
261
386
|
return out;
|
|
@@ -10995,7 +11120,7 @@ async function runStoreBacked(input, store, opts = {}) {
|
|
|
10995
11120
|
}
|
|
10996
11121
|
|
|
10997
11122
|
let queryTriples = [];
|
|
10998
|
-
|
|
11123
|
+
let queryDerived = [];
|
|
10999
11124
|
if (qrules.length) {
|
|
11000
11125
|
for (const r of qrules) {
|
|
11001
11126
|
const solutions = await __proveGoalsAgainstStore(r.premise || [], __emptySubst(), store, brules, triples, 0, varGen, {});
|
|
@@ -15468,6 +15593,21 @@ const DT_NS = 'https://eyereasoner.github.io/eyeling/datatype#';
|
|
|
15468
15593
|
const SKOLEM_NS = 'https://eyereasoner.github.io/.well-known/genid/';
|
|
15469
15594
|
const RDF_JSON_DT = RDF_NS + 'JSON';
|
|
15470
15595
|
|
|
15596
|
+
// Private rule-lifting variable prefix used for blank nodes that appear in
|
|
15597
|
+
// rule-body patterns. The prefix is intentionally not spellable in N3 input,
|
|
15598
|
+
// but public extension APIs should present these variables with stable,
|
|
15599
|
+
// ordinary names and translate them back internally.
|
|
15600
|
+
const INTERNAL_BLANK_VAR_PREFIX = '\uE000eyeling_b';
|
|
15601
|
+
|
|
15602
|
+
function isInternalBlankVarName(name) {
|
|
15603
|
+
return typeof name === 'string' && name.startsWith(INTERNAL_BLANK_VAR_PREFIX);
|
|
15604
|
+
}
|
|
15605
|
+
|
|
15606
|
+
function internalBlankVarSuffix(name) {
|
|
15607
|
+
if (!isInternalBlankVarName(name)) return '';
|
|
15608
|
+
return name.slice(INTERNAL_BLANK_VAR_PREFIX.length);
|
|
15609
|
+
}
|
|
15610
|
+
|
|
15471
15611
|
function parseUriReferenceForResolution(uri) {
|
|
15472
15612
|
// RFC 3986 Appendix B-style component parser, with the scheme tightened to
|
|
15473
15613
|
// the RFC scheme grammar. Capturing delimiter presence matters: `?` with an
|
|
@@ -16171,6 +16311,9 @@ module.exports = {
|
|
|
16171
16311
|
DT_NS,
|
|
16172
16312
|
SKOLEM_NS,
|
|
16173
16313
|
RDF_JSON_DT,
|
|
16314
|
+
INTERNAL_BLANK_VAR_PREFIX,
|
|
16315
|
+
isInternalBlankVarName,
|
|
16316
|
+
internalBlankVarSuffix,
|
|
16174
16317
|
resolveIriRef,
|
|
16175
16318
|
literalParts,
|
|
16176
16319
|
normalizeLiteralForTid,
|
|
@@ -17645,6 +17788,7 @@ const {
|
|
|
17645
17788
|
GraphTerm,
|
|
17646
17789
|
Triple,
|
|
17647
17790
|
copyQuotedGraphMetadata,
|
|
17791
|
+
INTERNAL_BLANK_VAR_PREFIX,
|
|
17648
17792
|
} = require('./prelude');
|
|
17649
17793
|
|
|
17650
17794
|
function liftBlankRuleVars(premise, conclusion) {
|
|
@@ -17660,10 +17804,8 @@ function liftBlankRuleVars(premise, conclusion) {
|
|
|
17660
17804
|
// These variables are implementation details used to correlate one blank
|
|
17661
17805
|
// node across the lifted rule body. Their names must not be spellable by
|
|
17662
17806
|
// user input; otherwise a user variable such as ?_b1 can capture the
|
|
17663
|
-
// lifted blank node and leak it into the rule head.
|
|
17664
|
-
//
|
|
17665
|
-
const INTERNAL_BLANK_VAR_PREFIX = '\uE000eyeling_b';
|
|
17666
|
-
|
|
17807
|
+
// lifted blank node and leak it into the rule head. The public custom
|
|
17808
|
+
// builtin API maps these names back to ordinary-looking ?_bN variables.
|
|
17667
17809
|
function blankToVar(label) {
|
|
17668
17810
|
let name = mapping[label];
|
|
17669
17811
|
if (name === undefined) {
|
package/lib/builtins.js
CHANGED
|
@@ -30,6 +30,8 @@ const {
|
|
|
30
30
|
PrefixEnv,
|
|
31
31
|
literalParts,
|
|
32
32
|
copyQuotedGraphMetadata,
|
|
33
|
+
isInternalBlankVarName,
|
|
34
|
+
internalBlankVarSuffix,
|
|
33
35
|
} = require('./prelude');
|
|
34
36
|
|
|
35
37
|
const { decodeN3StringEscapes } = require('./lexer');
|
|
@@ -132,6 +134,127 @@ function __assertBuiltinHandlerResult(iri, out) {
|
|
|
132
134
|
}
|
|
133
135
|
}
|
|
134
136
|
|
|
137
|
+
function collectVarNamesInTerm(t, acc) {
|
|
138
|
+
if (t instanceof Var) {
|
|
139
|
+
acc.add(t.name);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (t instanceof ListTerm) {
|
|
143
|
+
for (const e of t.elems) collectVarNamesInTerm(e, acc);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (t instanceof OpenListTerm) {
|
|
147
|
+
for (const e of t.prefix) collectVarNamesInTerm(e, acc);
|
|
148
|
+
acc.add(t.tailVar);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (t instanceof GraphTerm) {
|
|
152
|
+
for (const tr of t.triples) collectVarNamesInTriple(tr, acc);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function collectVarNamesInTriple(tr, acc) {
|
|
157
|
+
collectVarNamesInTerm(tr.s, acc);
|
|
158
|
+
collectVarNamesInTerm(tr.p, acc);
|
|
159
|
+
collectVarNamesInTerm(tr.o, acc);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function cloneSubstWithMappedKeys(subst, keyMap, externalizeTerm) {
|
|
163
|
+
const out = Object.create(null);
|
|
164
|
+
for (const [key, val] of Object.entries(subst || {})) {
|
|
165
|
+
out[keyMap.get(key) || key] = externalizeTerm ? externalizeTerm(val) : val;
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function hasInternalBlankVarInSubst(subst) {
|
|
171
|
+
for (const key of Object.keys(subst || {})) {
|
|
172
|
+
if (isInternalBlankVarName(key)) return true;
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function makeCustomBuiltinPublicBridge(goal, subst) {
|
|
178
|
+
const used = new Set(Object.keys(subst || {}).filter((name) => !isInternalBlankVarName(name)));
|
|
179
|
+
collectVarNamesInTriple(goal, used);
|
|
180
|
+
for (const name of Array.from(used)) {
|
|
181
|
+
if (isInternalBlankVarName(name)) used.delete(name);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const internalNames = new Set();
|
|
185
|
+
collectVarNamesInTriple(goal, internalNames);
|
|
186
|
+
for (const key of Object.keys(subst || {})) internalNames.add(key);
|
|
187
|
+
for (const name of Array.from(internalNames)) {
|
|
188
|
+
if (!isInternalBlankVarName(name)) internalNames.delete(name);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (internalNames.size === 0 && !hasInternalBlankVarInSubst(subst)) return null;
|
|
192
|
+
|
|
193
|
+
const internalToPublic = new Map();
|
|
194
|
+
const publicToInternal = new Map();
|
|
195
|
+
|
|
196
|
+
function allocatePublicName(internalName) {
|
|
197
|
+
const suffix = internalBlankVarSuffix(internalName) || String(internalToPublic.size + 1);
|
|
198
|
+
const base = /^[$A-Za-z_][0-9A-Za-z_]*$/u.test(`_b${suffix}`) ? `_b${suffix}` : `_b${internalToPublic.size + 1}`;
|
|
199
|
+
let name = base;
|
|
200
|
+
let n = 1;
|
|
201
|
+
while (used.has(name) || publicToInternal.has(name)) {
|
|
202
|
+
n += 1;
|
|
203
|
+
name = `${base}_${n}`;
|
|
204
|
+
}
|
|
205
|
+
used.add(name);
|
|
206
|
+
internalToPublic.set(internalName, name);
|
|
207
|
+
publicToInternal.set(name, internalName);
|
|
208
|
+
return name;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
for (const name of internalNames) allocatePublicName(name);
|
|
212
|
+
|
|
213
|
+
function externalName(name) {
|
|
214
|
+
if (!isInternalBlankVarName(name)) return name;
|
|
215
|
+
return internalToPublic.get(name) || allocatePublicName(name);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function externalizeTerm(t) {
|
|
219
|
+
if (t instanceof Var) return new Var(externalName(t.name));
|
|
220
|
+
if (t instanceof ListTerm) return new ListTerm(t.elems.map(externalizeTerm));
|
|
221
|
+
if (t instanceof OpenListTerm) return new OpenListTerm(t.prefix.map(externalizeTerm), externalName(t.tailVar));
|
|
222
|
+
if (t instanceof GraphTerm) {
|
|
223
|
+
const triples = t.triples.map((tr) => new Triple(externalizeTerm(tr.s), externalizeTerm(tr.p), externalizeTerm(tr.o)));
|
|
224
|
+
return copyQuotedGraphMetadata(t, new GraphTerm(triples));
|
|
225
|
+
}
|
|
226
|
+
return t;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function internalizeTerm(t) {
|
|
230
|
+
if (t instanceof Var) return new Var(publicToInternal.get(t.name) || t.name);
|
|
231
|
+
if (t instanceof ListTerm) return new ListTerm(t.elems.map(internalizeTerm));
|
|
232
|
+
if (t instanceof OpenListTerm) return new OpenListTerm(t.prefix.map(internalizeTerm), publicToInternal.get(t.tailVar) || t.tailVar);
|
|
233
|
+
if (t instanceof GraphTerm) {
|
|
234
|
+
const triples = t.triples.map((tr) => new Triple(internalizeTerm(tr.s), internalizeTerm(tr.p), internalizeTerm(tr.o)));
|
|
235
|
+
return copyQuotedGraphMetadata(t, new GraphTerm(triples));
|
|
236
|
+
}
|
|
237
|
+
return t;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function internalizeDelta(delta) {
|
|
241
|
+
const out = Object.create(null);
|
|
242
|
+
for (const [key, val] of Object.entries(delta || {})) {
|
|
243
|
+
out[publicToInternal.get(key) || key] = internalizeTerm(val);
|
|
244
|
+
}
|
|
245
|
+
return out;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
goal: new Triple(externalizeTerm(goal.s), externalizeTerm(goal.p), externalizeTerm(goal.o)),
|
|
250
|
+
subst: cloneSubstWithMappedKeys(subst, internalToPublic, externalizeTerm),
|
|
251
|
+
internalizeResults(out) {
|
|
252
|
+
if (out == null) return out;
|
|
253
|
+
return out.map(internalizeDelta);
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
135
258
|
function apiTermToN3(term, prefixes = PrefixEnv.newDefault()) {
|
|
136
259
|
return termToN3(term, prefixes || PrefixEnv.newDefault());
|
|
137
260
|
}
|
|
@@ -231,10 +354,11 @@ function __evalRegisteredBuiltin(pv, goal, subst, facts, backRules, depth, varGe
|
|
|
231
354
|
const handler = __customBuiltinHandlers.get(pv);
|
|
232
355
|
if (typeof handler !== 'function') return null;
|
|
233
356
|
|
|
357
|
+
const bridge = makeCustomBuiltinPublicBridge(goal, subst);
|
|
234
358
|
const ctx = {
|
|
235
359
|
iri: pv,
|
|
236
|
-
goal,
|
|
237
|
-
subst,
|
|
360
|
+
goal: bridge ? bridge.goal : goal,
|
|
361
|
+
subst: bridge ? bridge.subst : subst,
|
|
238
362
|
facts,
|
|
239
363
|
backRules,
|
|
240
364
|
depth,
|
|
@@ -244,7 +368,8 @@ function __evalRegisteredBuiltin(pv, goal, subst, facts, backRules, depth, varGe
|
|
|
244
368
|
};
|
|
245
369
|
|
|
246
370
|
try {
|
|
247
|
-
const
|
|
371
|
+
const raw = handler(ctx);
|
|
372
|
+
const out = bridge ? bridge.internalizeResults(raw) : raw;
|
|
248
373
|
if (out == null) return [];
|
|
249
374
|
__assertBuiltinHandlerResult(pv, out);
|
|
250
375
|
return out;
|
package/lib/engine.js
CHANGED
|
@@ -3930,7 +3930,7 @@ async function runStoreBacked(input, store, opts = {}) {
|
|
|
3930
3930
|
}
|
|
3931
3931
|
|
|
3932
3932
|
let queryTriples = [];
|
|
3933
|
-
|
|
3933
|
+
let queryDerived = [];
|
|
3934
3934
|
if (qrules.length) {
|
|
3935
3935
|
for (const r of qrules) {
|
|
3936
3936
|
const solutions = await __proveGoalsAgainstStore(r.premise || [], __emptySubst(), store, brules, triples, 0, varGen, {});
|
package/lib/prelude.js
CHANGED
|
@@ -25,6 +25,21 @@ const DT_NS = 'https://eyereasoner.github.io/eyeling/datatype#';
|
|
|
25
25
|
const SKOLEM_NS = 'https://eyereasoner.github.io/.well-known/genid/';
|
|
26
26
|
const RDF_JSON_DT = RDF_NS + 'JSON';
|
|
27
27
|
|
|
28
|
+
// Private rule-lifting variable prefix used for blank nodes that appear in
|
|
29
|
+
// rule-body patterns. The prefix is intentionally not spellable in N3 input,
|
|
30
|
+
// but public extension APIs should present these variables with stable,
|
|
31
|
+
// ordinary names and translate them back internally.
|
|
32
|
+
const INTERNAL_BLANK_VAR_PREFIX = '\uE000eyeling_b';
|
|
33
|
+
|
|
34
|
+
function isInternalBlankVarName(name) {
|
|
35
|
+
return typeof name === 'string' && name.startsWith(INTERNAL_BLANK_VAR_PREFIX);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function internalBlankVarSuffix(name) {
|
|
39
|
+
if (!isInternalBlankVarName(name)) return '';
|
|
40
|
+
return name.slice(INTERNAL_BLANK_VAR_PREFIX.length);
|
|
41
|
+
}
|
|
42
|
+
|
|
28
43
|
function parseUriReferenceForResolution(uri) {
|
|
29
44
|
// RFC 3986 Appendix B-style component parser, with the scheme tightened to
|
|
30
45
|
// the RFC scheme grammar. Capturing delimiter presence matters: `?` with an
|
|
@@ -728,6 +743,9 @@ module.exports = {
|
|
|
728
743
|
DT_NS,
|
|
729
744
|
SKOLEM_NS,
|
|
730
745
|
RDF_JSON_DT,
|
|
746
|
+
INTERNAL_BLANK_VAR_PREFIX,
|
|
747
|
+
isInternalBlankVarName,
|
|
748
|
+
internalBlankVarSuffix,
|
|
731
749
|
resolveIriRef,
|
|
732
750
|
literalParts,
|
|
733
751
|
normalizeLiteralForTid,
|
package/lib/rules.js
CHANGED
|
@@ -17,6 +17,7 @@ const {
|
|
|
17
17
|
GraphTerm,
|
|
18
18
|
Triple,
|
|
19
19
|
copyQuotedGraphMetadata,
|
|
20
|
+
INTERNAL_BLANK_VAR_PREFIX,
|
|
20
21
|
} = require('./prelude');
|
|
21
22
|
|
|
22
23
|
function liftBlankRuleVars(premise, conclusion) {
|
|
@@ -32,10 +33,8 @@ function liftBlankRuleVars(premise, conclusion) {
|
|
|
32
33
|
// These variables are implementation details used to correlate one blank
|
|
33
34
|
// node across the lifted rule body. Their names must not be spellable by
|
|
34
35
|
// user input; otherwise a user variable such as ?_b1 can capture the
|
|
35
|
-
// lifted blank node and leak it into the rule head.
|
|
36
|
-
//
|
|
37
|
-
const INTERNAL_BLANK_VAR_PREFIX = '\uE000eyeling_b';
|
|
38
|
-
|
|
36
|
+
// lifted blank node and leak it into the rule head. The public custom
|
|
37
|
+
// builtin API maps these names back to ordinary-looking ?_bN variables.
|
|
39
38
|
function blankToVar(label) {
|
|
40
39
|
let name = mapping[label];
|
|
41
40
|
if (name === undefined) {
|
package/package.json
CHANGED
package/test/builtins.test.js
CHANGED
|
@@ -7,6 +7,7 @@ const { detail, failResult, info, pass } = require('./report');
|
|
|
7
7
|
const builtins = require('../lib/builtins');
|
|
8
8
|
require('../lib/engine');
|
|
9
9
|
const { reason } = require('../index');
|
|
10
|
+
const { reasonStream } = require('../lib/engine');
|
|
10
11
|
|
|
11
12
|
const expectedApiKeys = [
|
|
12
13
|
'registerBuiltin',
|
|
@@ -218,6 +219,41 @@ const cases = [
|
|
|
218
219
|
assert.match(out, /:canonical :midnightRollover "2027-01-01T00:00:00Z"\^\^xsd:dateTime \./);
|
|
219
220
|
},
|
|
220
221
|
},
|
|
222
|
+
{
|
|
223
|
+
name: 'custom builtin API hides internal blank-node variable prefix',
|
|
224
|
+
run() {
|
|
225
|
+
const iri = 'http://example.org/custom#format';
|
|
226
|
+
builtins.unregisterBuiltin(iri);
|
|
227
|
+
builtins.registerBuiltin(iri, ({ goal, subst, api }) => {
|
|
228
|
+
const formatted = api.termToN3(goal.s);
|
|
229
|
+
assert.doesNotMatch(formatted, /\uE000eyeling_b/);
|
|
230
|
+
assert.match(formatted, /\?_b1/);
|
|
231
|
+
const next = api.unifyTerm(goal.o, api.internLiteral(JSON.stringify(formatted)), subst);
|
|
232
|
+
return next === null ? [] : [next];
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
const out = reasonStream(`
|
|
237
|
+
@prefix : <http://example.org/> .
|
|
238
|
+
@prefix cb: <http://example.org/custom#> .
|
|
239
|
+
|
|
240
|
+
{
|
|
241
|
+
{ [] a ?class } cb:format ?format .
|
|
242
|
+
}
|
|
243
|
+
=>
|
|
244
|
+
{
|
|
245
|
+
:result :is ?format .
|
|
246
|
+
} .
|
|
247
|
+
`, { proof: false, includeInputFactsInClosure: false }).closureN3;
|
|
248
|
+
assert.doesNotMatch(out, /\uE000eyeling_b/);
|
|
249
|
+
assert.match(out, /:result :is/);
|
|
250
|
+
assert.match(out, /\?_b1/);
|
|
251
|
+
assert.match(out, /\?class/);
|
|
252
|
+
} finally {
|
|
253
|
+
builtins.unregisterBuiltin(iri);
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
},
|
|
221
257
|
|
|
222
258
|
];
|
|
223
259
|
|