eyeling 1.16.0 → 1.16.2
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/arctifacts/README.md +2 -0
- package/eyeling.js +78 -30
- package/lib/builtins.js +67 -19
- package/lib/cli.js +2 -2
- package/lib/deref.js +1 -1
- package/lib/explain.js +2 -2
- package/lib/prelude.js +1 -1
- package/package.json +1 -1
- package/test/api.test.js +18 -18
- package/test/playground.test.js +1 -1
- package/tools/bundle.js +7 -5
- package/tools/n3gen.js +22 -22
package/arctifacts/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
ARCtifacts are trustworthy programs telling a concise story in three parts. First comes the **Answer** to a specific question. This is followed by the **Reason Why** that answer is correct, articulated in everyday language and supported by the relevant identities, rules, or ideas. Finally, every case includes a **Check**—a concrete test designed to fail loudly if an assumption doesn't hold or an edge case bites. The result is a computation with a complete, auditable trail: you can see precisely what was done, why it was valid, and how the page verifies its own work.
|
|
4
4
|
|
|
5
|
+
At the core of this approach are three familiar ingredients: **Data**, **Logic**, and a **Question**. We summarize the workflow as **P3 — Prompt → Program → Proof**: the prompt supplies the task and materials, the program turns them into a concrete, repeatable procedure, and the proof is practical rather than ceremonial, consisting of the **Reason Why** together with the **Check**. This is what makes each ARCtifact not just a result, but a portable, auditable, and trustworthy computational artifact.
|
|
6
|
+
|
|
5
7
|
## Science
|
|
6
8
|
|
|
7
9
|
- [**Body Mass Index**](https://eyereasoner.github.io/eyeling/arctifacts/bmi.html) — Compute BMI categories with explainable thresholds and sanity checks.
|
package/eyeling.js
CHANGED
|
@@ -135,6 +135,33 @@ function __builtinCollectVarsInTriple(tr, out) {
|
|
|
135
135
|
__builtinCollectVarsInTerm(tr.o, out);
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
function __isRuleFormulaLikeTerm(t) {
|
|
139
|
+
return t instanceof GraphTerm || (t instanceof Literal && (t.value === 'true' || t.value === 'false'));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function __expandScopedVarPredicateGoals(goals) {
|
|
143
|
+
if (!Array.isArray(goals) || goals.length === 0) return [{ goals, bind: null }];
|
|
144
|
+
|
|
145
|
+
let variants = [{ goals: goals.slice(), bind: null }];
|
|
146
|
+
const impliesIri = internIri(LOG_NS + 'implies');
|
|
147
|
+
|
|
148
|
+
for (let i = 0; i < goals.length; i++) {
|
|
149
|
+
const tr = goals[i];
|
|
150
|
+
if (!(tr.p instanceof Var)) continue;
|
|
151
|
+
if (!__isRuleFormulaLikeTerm(tr.s) && !__isRuleFormulaLikeTerm(tr.o)) continue;
|
|
152
|
+
|
|
153
|
+
const next = variants.slice();
|
|
154
|
+
for (const v of variants) {
|
|
155
|
+
const altGoals = v.goals.slice();
|
|
156
|
+
altGoals[i] = new Triple(altGoals[i].s, impliesIri, altGoals[i].o);
|
|
157
|
+
const altBind = { ...(v.bind || {}), [tr.p.name]: impliesIri };
|
|
158
|
+
next.push({ goals: altGoals, bind: altBind });
|
|
159
|
+
}
|
|
160
|
+
variants = next;
|
|
161
|
+
}
|
|
162
|
+
return variants;
|
|
163
|
+
}
|
|
164
|
+
|
|
138
165
|
function __builtinCollectVarsInTriples(triples, out) {
|
|
139
166
|
for (const tr of triples) __builtinCollectVarsInTriple(tr, out);
|
|
140
167
|
}
|
|
@@ -267,7 +294,7 @@ function termToJsString(t) {
|
|
|
267
294
|
if (t instanceof Iri) return t.value;
|
|
268
295
|
if (!(t instanceof Literal)) return null;
|
|
269
296
|
|
|
270
|
-
const [lex
|
|
297
|
+
const [lex] = literalParts(t.value);
|
|
271
298
|
|
|
272
299
|
if (isQuotedLexical(lex)) {
|
|
273
300
|
// Interpret N3/Turtle string escapes (\" \n \uXXXX \UXXXXXXXX …)
|
|
@@ -289,7 +316,7 @@ function termToJsStringDecoded(t) {
|
|
|
289
316
|
// Like termToJsString, but for short literals it *also* interprets escapes
|
|
290
317
|
// (\" \n \uXXXX …) by attempting JSON.parse on the quoted lexical form.
|
|
291
318
|
if (!(t instanceof Literal)) return null;
|
|
292
|
-
const [lex
|
|
319
|
+
const [lex] = literalParts(t.value);
|
|
293
320
|
|
|
294
321
|
// Long strings: """ ... """ are taken verbatim.
|
|
295
322
|
if (lex.length >= 6 && lex.startsWith('"""') && lex.endsWith('"""')) {
|
|
@@ -300,7 +327,7 @@ function termToJsStringDecoded(t) {
|
|
|
300
327
|
if (lex.length >= 2 && lex[0] === '"' && lex[lex.length - 1] === '"') {
|
|
301
328
|
try {
|
|
302
329
|
return JSON.parse(lex);
|
|
303
|
-
} catch
|
|
330
|
+
} catch {
|
|
304
331
|
/* fall through */
|
|
305
332
|
}
|
|
306
333
|
return stripQuotes(lex);
|
|
@@ -365,13 +392,13 @@ function compileSwapRegex(pattern, extraFlags) {
|
|
|
365
392
|
const flags = (extraFlags || '') + (needU ? 'u' : '');
|
|
366
393
|
try {
|
|
367
394
|
return new RegExp(pattern, flags);
|
|
368
|
-
} catch
|
|
395
|
+
} catch {
|
|
369
396
|
if (needU) {
|
|
370
397
|
const p2 = sanitizeForUnicodeMode(pattern);
|
|
371
398
|
if (p2 !== pattern) {
|
|
372
399
|
try {
|
|
373
400
|
return new RegExp(p2, flags);
|
|
374
|
-
} catch
|
|
401
|
+
} catch {}
|
|
375
402
|
}
|
|
376
403
|
}
|
|
377
404
|
return null;
|
|
@@ -1393,7 +1420,7 @@ function hashLiteralTerm(t, algo) {
|
|
|
1393
1420
|
try {
|
|
1394
1421
|
const digest = nodeCrypto.createHash(algo).update(input, 'utf8').digest('hex');
|
|
1395
1422
|
return internLiteral(JSON.stringify(digest));
|
|
1396
|
-
} catch
|
|
1423
|
+
} catch {
|
|
1397
1424
|
return null;
|
|
1398
1425
|
}
|
|
1399
1426
|
}
|
|
@@ -2590,7 +2617,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
2590
2617
|
|
|
2591
2618
|
const results = [];
|
|
2592
2619
|
for (const el of inputList) {
|
|
2593
|
-
const yvar = new Var('
|
|
2620
|
+
const yvar = new Var('mapY');
|
|
2594
2621
|
const goal2 = new Triple(el, pred, yvar);
|
|
2595
2622
|
const sols = proveGoals([goal2], subst, facts, backRules, depth + 1, [], varGen);
|
|
2596
2623
|
|
|
@@ -3071,18 +3098,39 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
3071
3098
|
const visited2 = [];
|
|
3072
3099
|
const keepVars = new Set();
|
|
3073
3100
|
if (g.s instanceof GraphTerm) __builtinCollectVarsInTriples(g.s.triples, keepVars);
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3101
|
+
|
|
3102
|
+
const goalVariants = __expandScopedVarPredicateGoals(Array.from(g.o.triples));
|
|
3103
|
+
const out = [];
|
|
3104
|
+
for (const variant of goalVariants) {
|
|
3105
|
+
const sols = proveGoals(
|
|
3106
|
+
variant.goals,
|
|
3107
|
+
{ ...subst },
|
|
3108
|
+
scopeFacts,
|
|
3109
|
+
scopeBackRules,
|
|
3110
|
+
depth + 1,
|
|
3111
|
+
visited2,
|
|
3112
|
+
varGen,
|
|
3113
|
+
maxResults,
|
|
3114
|
+
keepVars.size ? { keepVars } : undefined,
|
|
3115
|
+
);
|
|
3116
|
+
for (const s2 of sols) {
|
|
3117
|
+
const merged = { ...s2 };
|
|
3118
|
+
let ok = true;
|
|
3119
|
+
if (variant.bind) {
|
|
3120
|
+
for (const [k, v] of Object.entries(variant.bind)) {
|
|
3121
|
+
if (Object.prototype.hasOwnProperty.call(merged, k) && !termsEqual(merged[k], v)) {
|
|
3122
|
+
ok = false;
|
|
3123
|
+
break;
|
|
3124
|
+
}
|
|
3125
|
+
merged[k] = v;
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
if (!ok) continue;
|
|
3129
|
+
out.push(merged);
|
|
3130
|
+
if (typeof maxResults === 'number' && maxResults > 0 && out.length >= maxResults) return out;
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
return out;
|
|
3086
3134
|
}
|
|
3087
3135
|
|
|
3088
3136
|
// log:notIncludes
|
|
@@ -3972,7 +4020,7 @@ function main() {
|
|
|
3972
4020
|
}
|
|
3973
4021
|
|
|
3974
4022
|
if (showAst) {
|
|
3975
|
-
function astReplacer(
|
|
4023
|
+
function astReplacer(unusedJsonKey, value) {
|
|
3976
4024
|
if (value instanceof Set) return Array.from(value);
|
|
3977
4025
|
if (value && typeof value === 'object' && value.constructor) {
|
|
3978
4026
|
const t = value.constructor.name;
|
|
@@ -4112,7 +4160,7 @@ function main() {
|
|
|
4112
4160
|
engine.setTracePrefixes(outPrefixes);
|
|
4113
4161
|
|
|
4114
4162
|
const entries = Object.entries(outPrefixes.map)
|
|
4115
|
-
.filter(([
|
|
4163
|
+
.filter(([, base]) => !!base)
|
|
4116
4164
|
.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
|
|
4117
4165
|
|
|
4118
4166
|
for (const [pfx, base] of entries) {
|
|
@@ -4546,7 +4594,7 @@ function parseSemanticsToFormula(text, baseIri) {
|
|
|
4546
4594
|
const parser = new Parser(toks);
|
|
4547
4595
|
if (typeof baseIri === 'string' && baseIri) parser.prefixes.setBase(baseIri);
|
|
4548
4596
|
|
|
4549
|
-
const [
|
|
4597
|
+
const [, triples, frules, brules] = parser.parseDocument();
|
|
4550
4598
|
|
|
4551
4599
|
const all = triples.slice();
|
|
4552
4600
|
|
|
@@ -7926,7 +7974,7 @@ function makeExplain(deps) {
|
|
|
7926
7974
|
// log:outputString support
|
|
7927
7975
|
// ===========================================================================
|
|
7928
7976
|
|
|
7929
|
-
function
|
|
7977
|
+
function compareOutputStringKeys(a, b) {
|
|
7930
7978
|
// Deterministic ordering of keys. The spec only requires "order of the subject keys"
|
|
7931
7979
|
// and leaves concrete term ordering reasoner-dependent. We implement:
|
|
7932
7980
|
// 1) numeric literals (numeric value)
|
|
@@ -8004,7 +8052,7 @@ function makeExplain(deps) {
|
|
|
8004
8052
|
}
|
|
8005
8053
|
|
|
8006
8054
|
pairs.sort((a, b) => {
|
|
8007
|
-
const c =
|
|
8055
|
+
const c = compareOutputStringKeys(a.key, b.key, prefixes);
|
|
8008
8056
|
if (c !== 0) return c;
|
|
8009
8057
|
return a.idx - b.idx; // stable tie-breaker
|
|
8010
8058
|
});
|
|
@@ -10005,7 +10053,7 @@ function collectIrisInTerm(t) {
|
|
|
10005
10053
|
if (t instanceof Iri) {
|
|
10006
10054
|
out.push(t.value);
|
|
10007
10055
|
} else if (t instanceof Literal) {
|
|
10008
|
-
const [
|
|
10056
|
+
const [, dt] = literalParts(t.value);
|
|
10009
10057
|
if (dt) out.push(dt); // so rdf/xsd prefixes are emitted when only used in ^^...
|
|
10010
10058
|
} else if (t instanceof ListTerm) {
|
|
10011
10059
|
for (const x of t.elems) out.push(...collectIrisInTerm(x));
|
|
@@ -11576,8 +11624,8 @@ module.exports = {
|
|
|
11576
11624
|
const __entry = __loadEntry();
|
|
11577
11625
|
const __api = { reasonStream: __entry.reasonStream, reasonRdfJs: __entry.reasonRdfJs };
|
|
11578
11626
|
|
|
11579
|
-
try { if (__outerModule && __outerModule.exports) __outerModule.exports = __api; } catch (
|
|
11580
|
-
try { if (__outerSelf) __outerSelf.eyeling = __api; } catch (
|
|
11627
|
+
try { if (__outerModule && __outerModule.exports) __outerModule.exports = __api; } catch (ignoredError) {}
|
|
11628
|
+
try { if (__outerSelf) __outerSelf.eyeling = __api; } catch (ignoredError) {}
|
|
11581
11629
|
|
|
11582
11630
|
// ---- demo.html compatibility ----
|
|
11583
11631
|
// The original monolithic eyeling.js exposed internal functions/flags as globals.
|
|
@@ -11605,18 +11653,18 @@ module.exports = {
|
|
|
11605
11653
|
// Fallback (no live linkage)
|
|
11606
11654
|
if (typeof getFn === "function") __outerSelf[name] = getFn();
|
|
11607
11655
|
}
|
|
11608
|
-
} catch (
|
|
11656
|
+
} catch (ignoredError) {}
|
|
11609
11657
|
};
|
|
11610
11658
|
|
|
11611
11659
|
def("enforceHttpsEnabled", __entry.getEnforceHttpsEnabled, __entry.setEnforceHttpsEnabled);
|
|
11612
11660
|
def("proofCommentsEnabled", __entry.getProofCommentsEnabled, __entry.setProofCommentsEnabled);
|
|
11613
11661
|
def("__tracePrefixes", __entry.getTracePrefixes, __entry.setTracePrefixes);
|
|
11614
11662
|
}
|
|
11615
|
-
} catch (
|
|
11663
|
+
} catch (ignoredError) {}
|
|
11616
11664
|
|
|
11617
11665
|
try {
|
|
11618
11666
|
if (__outerModule && __outerRequire && __outerRequire.main === __outerModule && typeof __entry.main === "function") {
|
|
11619
11667
|
__entry.main();
|
|
11620
11668
|
}
|
|
11621
|
-
} catch (
|
|
11669
|
+
} catch (ignoredError) {}
|
|
11622
11670
|
})();
|
package/lib/builtins.js
CHANGED
|
@@ -123,6 +123,33 @@ function __builtinCollectVarsInTriple(tr, out) {
|
|
|
123
123
|
__builtinCollectVarsInTerm(tr.o, out);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
function __isRuleFormulaLikeTerm(t) {
|
|
127
|
+
return t instanceof GraphTerm || (t instanceof Literal && (t.value === 'true' || t.value === 'false'));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function __expandScopedVarPredicateGoals(goals) {
|
|
131
|
+
if (!Array.isArray(goals) || goals.length === 0) return [{ goals, bind: null }];
|
|
132
|
+
|
|
133
|
+
let variants = [{ goals: goals.slice(), bind: null }];
|
|
134
|
+
const impliesIri = internIri(LOG_NS + 'implies');
|
|
135
|
+
|
|
136
|
+
for (let i = 0; i < goals.length; i++) {
|
|
137
|
+
const tr = goals[i];
|
|
138
|
+
if (!(tr.p instanceof Var)) continue;
|
|
139
|
+
if (!__isRuleFormulaLikeTerm(tr.s) && !__isRuleFormulaLikeTerm(tr.o)) continue;
|
|
140
|
+
|
|
141
|
+
const next = variants.slice();
|
|
142
|
+
for (const v of variants) {
|
|
143
|
+
const altGoals = v.goals.slice();
|
|
144
|
+
altGoals[i] = new Triple(altGoals[i].s, impliesIri, altGoals[i].o);
|
|
145
|
+
const altBind = { ...(v.bind || {}), [tr.p.name]: impliesIri };
|
|
146
|
+
next.push({ goals: altGoals, bind: altBind });
|
|
147
|
+
}
|
|
148
|
+
variants = next;
|
|
149
|
+
}
|
|
150
|
+
return variants;
|
|
151
|
+
}
|
|
152
|
+
|
|
126
153
|
function __builtinCollectVarsInTriples(triples, out) {
|
|
127
154
|
for (const tr of triples) __builtinCollectVarsInTriple(tr, out);
|
|
128
155
|
}
|
|
@@ -255,7 +282,7 @@ function termToJsString(t) {
|
|
|
255
282
|
if (t instanceof Iri) return t.value;
|
|
256
283
|
if (!(t instanceof Literal)) return null;
|
|
257
284
|
|
|
258
|
-
const [lex
|
|
285
|
+
const [lex] = literalParts(t.value);
|
|
259
286
|
|
|
260
287
|
if (isQuotedLexical(lex)) {
|
|
261
288
|
// Interpret N3/Turtle string escapes (\" \n \uXXXX \UXXXXXXXX …)
|
|
@@ -277,7 +304,7 @@ function termToJsStringDecoded(t) {
|
|
|
277
304
|
// Like termToJsString, but for short literals it *also* interprets escapes
|
|
278
305
|
// (\" \n \uXXXX …) by attempting JSON.parse on the quoted lexical form.
|
|
279
306
|
if (!(t instanceof Literal)) return null;
|
|
280
|
-
const [lex
|
|
307
|
+
const [lex] = literalParts(t.value);
|
|
281
308
|
|
|
282
309
|
// Long strings: """ ... """ are taken verbatim.
|
|
283
310
|
if (lex.length >= 6 && lex.startsWith('"""') && lex.endsWith('"""')) {
|
|
@@ -288,7 +315,7 @@ function termToJsStringDecoded(t) {
|
|
|
288
315
|
if (lex.length >= 2 && lex[0] === '"' && lex[lex.length - 1] === '"') {
|
|
289
316
|
try {
|
|
290
317
|
return JSON.parse(lex);
|
|
291
|
-
} catch
|
|
318
|
+
} catch {
|
|
292
319
|
/* fall through */
|
|
293
320
|
}
|
|
294
321
|
return stripQuotes(lex);
|
|
@@ -353,13 +380,13 @@ function compileSwapRegex(pattern, extraFlags) {
|
|
|
353
380
|
const flags = (extraFlags || '') + (needU ? 'u' : '');
|
|
354
381
|
try {
|
|
355
382
|
return new RegExp(pattern, flags);
|
|
356
|
-
} catch
|
|
383
|
+
} catch {
|
|
357
384
|
if (needU) {
|
|
358
385
|
const p2 = sanitizeForUnicodeMode(pattern);
|
|
359
386
|
if (p2 !== pattern) {
|
|
360
387
|
try {
|
|
361
388
|
return new RegExp(p2, flags);
|
|
362
|
-
} catch
|
|
389
|
+
} catch {}
|
|
363
390
|
}
|
|
364
391
|
}
|
|
365
392
|
return null;
|
|
@@ -1381,7 +1408,7 @@ function hashLiteralTerm(t, algo) {
|
|
|
1381
1408
|
try {
|
|
1382
1409
|
const digest = nodeCrypto.createHash(algo).update(input, 'utf8').digest('hex');
|
|
1383
1410
|
return internLiteral(JSON.stringify(digest));
|
|
1384
|
-
} catch
|
|
1411
|
+
} catch {
|
|
1385
1412
|
return null;
|
|
1386
1413
|
}
|
|
1387
1414
|
}
|
|
@@ -2578,7 +2605,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
2578
2605
|
|
|
2579
2606
|
const results = [];
|
|
2580
2607
|
for (const el of inputList) {
|
|
2581
|
-
const yvar = new Var('
|
|
2608
|
+
const yvar = new Var('mapY');
|
|
2582
2609
|
const goal2 = new Triple(el, pred, yvar);
|
|
2583
2610
|
const sols = proveGoals([goal2], subst, facts, backRules, depth + 1, [], varGen);
|
|
2584
2611
|
|
|
@@ -3059,18 +3086,39 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
3059
3086
|
const visited2 = [];
|
|
3060
3087
|
const keepVars = new Set();
|
|
3061
3088
|
if (g.s instanceof GraphTerm) __builtinCollectVarsInTriples(g.s.triples, keepVars);
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3089
|
+
|
|
3090
|
+
const goalVariants = __expandScopedVarPredicateGoals(Array.from(g.o.triples));
|
|
3091
|
+
const out = [];
|
|
3092
|
+
for (const variant of goalVariants) {
|
|
3093
|
+
const sols = proveGoals(
|
|
3094
|
+
variant.goals,
|
|
3095
|
+
{ ...subst },
|
|
3096
|
+
scopeFacts,
|
|
3097
|
+
scopeBackRules,
|
|
3098
|
+
depth + 1,
|
|
3099
|
+
visited2,
|
|
3100
|
+
varGen,
|
|
3101
|
+
maxResults,
|
|
3102
|
+
keepVars.size ? { keepVars } : undefined,
|
|
3103
|
+
);
|
|
3104
|
+
for (const s2 of sols) {
|
|
3105
|
+
const merged = { ...s2 };
|
|
3106
|
+
let ok = true;
|
|
3107
|
+
if (variant.bind) {
|
|
3108
|
+
for (const [k, v] of Object.entries(variant.bind)) {
|
|
3109
|
+
if (Object.prototype.hasOwnProperty.call(merged, k) && !termsEqual(merged[k], v)) {
|
|
3110
|
+
ok = false;
|
|
3111
|
+
break;
|
|
3112
|
+
}
|
|
3113
|
+
merged[k] = v;
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
if (!ok) continue;
|
|
3117
|
+
out.push(merged);
|
|
3118
|
+
if (typeof maxResults === 'number' && maxResults > 0 && out.length >= maxResults) return out;
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
return out;
|
|
3074
3122
|
}
|
|
3075
3123
|
|
|
3076
3124
|
// log:notIncludes
|
package/lib/cli.js
CHANGED
|
@@ -153,7 +153,7 @@ function main() {
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
if (showAst) {
|
|
156
|
-
function astReplacer(
|
|
156
|
+
function astReplacer(unusedJsonKey, value) {
|
|
157
157
|
if (value instanceof Set) return Array.from(value);
|
|
158
158
|
if (value && typeof value === 'object' && value.constructor) {
|
|
159
159
|
const t = value.constructor.name;
|
|
@@ -293,7 +293,7 @@ function main() {
|
|
|
293
293
|
engine.setTracePrefixes(outPrefixes);
|
|
294
294
|
|
|
295
295
|
const entries = Object.entries(outPrefixes.map)
|
|
296
|
-
.filter(([
|
|
296
|
+
.filter(([, base]) => !!base)
|
|
297
297
|
.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
|
|
298
298
|
|
|
299
299
|
for (const [pfx, base] of entries) {
|
package/lib/deref.js
CHANGED
|
@@ -359,7 +359,7 @@ function parseSemanticsToFormula(text, baseIri) {
|
|
|
359
359
|
const parser = new Parser(toks);
|
|
360
360
|
if (typeof baseIri === 'string' && baseIri) parser.prefixes.setBase(baseIri);
|
|
361
361
|
|
|
362
|
-
const [
|
|
362
|
+
const [, triples, frules, brules] = parser.parseDocument();
|
|
363
363
|
|
|
364
364
|
const all = triples.slice();
|
|
365
365
|
|
package/lib/explain.js
CHANGED
|
@@ -118,7 +118,7 @@ function makeExplain(deps) {
|
|
|
118
118
|
// log:outputString support
|
|
119
119
|
// ===========================================================================
|
|
120
120
|
|
|
121
|
-
function
|
|
121
|
+
function compareOutputStringKeys(a, b) {
|
|
122
122
|
// Deterministic ordering of keys. The spec only requires "order of the subject keys"
|
|
123
123
|
// and leaves concrete term ordering reasoner-dependent. We implement:
|
|
124
124
|
// 1) numeric literals (numeric value)
|
|
@@ -196,7 +196,7 @@ function makeExplain(deps) {
|
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
pairs.sort((a, b) => {
|
|
199
|
-
const c =
|
|
199
|
+
const c = compareOutputStringKeys(a.key, b.key, prefixes);
|
|
200
200
|
if (c !== 0) return c;
|
|
201
201
|
return a.idx - b.idx; // stable tie-breaker
|
|
202
202
|
});
|
package/lib/prelude.js
CHANGED
|
@@ -426,7 +426,7 @@ function collectIrisInTerm(t) {
|
|
|
426
426
|
if (t instanceof Iri) {
|
|
427
427
|
out.push(t.value);
|
|
428
428
|
} else if (t instanceof Literal) {
|
|
429
|
-
const [
|
|
429
|
+
const [, dt] = literalParts(t.value);
|
|
430
430
|
if (dt) out.push(dt); // so rdf/xsd prefixes are emitted when only used in ^^...
|
|
431
431
|
} else if (t instanceof ListTerm) {
|
|
432
432
|
for (const x of t.elems) out.push(...collectIrisInTerm(x));
|
package/package.json
CHANGED
package/test/api.test.js
CHANGED
|
@@ -1305,15 +1305,15 @@ ex:a p:trig ex:b.
|
|
|
1305
1305
|
});
|
|
1306
1306
|
|
|
1307
1307
|
// stash for check()
|
|
1308
|
-
this.
|
|
1309
|
-
this.
|
|
1308
|
+
this.seen = seen;
|
|
1309
|
+
this.result = r;
|
|
1310
1310
|
return r.closureN3;
|
|
1311
1311
|
},
|
|
1312
1312
|
expect: [/http:\/\/example\.org\/q/m],
|
|
1313
1313
|
notExpect: [/http:\/\/example\.org\/p/m],
|
|
1314
1314
|
check(out, tc) {
|
|
1315
|
-
assert.equal(tc.
|
|
1316
|
-
assert.match(tc.
|
|
1315
|
+
assert.equal(tc.seen.length, 1, 'Expected onDerived to be called once');
|
|
1316
|
+
assert.match(tc.seen[0], /http:\/\/example\.org\/q/, 'Expected streamed triple to be the derived one');
|
|
1317
1317
|
// closureN3 should be exactly the derived triple (no input facts).
|
|
1318
1318
|
assert.ok(String(out).trim().includes('http://example.org/q'));
|
|
1319
1319
|
assert.ok(!String(out).includes('http://example.org/p'));
|
|
@@ -1584,19 +1584,19 @@ _:x :hates { _:foo :making :mess }.
|
|
|
1584
1584
|
onDerived: ({ quad }) => seen.push(quad),
|
|
1585
1585
|
},
|
|
1586
1586
|
);
|
|
1587
|
-
this.
|
|
1588
|
-
this.
|
|
1587
|
+
this.seen = seen;
|
|
1588
|
+
this.result = result;
|
|
1589
1589
|
return result.closureN3;
|
|
1590
1590
|
},
|
|
1591
1591
|
expect: [/http:\/\/example\.org\/q/m],
|
|
1592
1592
|
notExpect: [/http:\/\/example\.org\/p/m],
|
|
1593
|
-
check(
|
|
1594
|
-
assert.equal(tc.
|
|
1595
|
-
assert.equal(tc.
|
|
1596
|
-
assert.equal(tc.
|
|
1597
|
-
assert.ok(Array.isArray(tc.
|
|
1598
|
-
assert.equal(tc.
|
|
1599
|
-
assert.equal(tc.
|
|
1593
|
+
check(outputIgnored, tc) {
|
|
1594
|
+
assert.equal(tc.seen.length, 1, 'Expected one streamed RDF/JS quad');
|
|
1595
|
+
assert.equal(tc.seen[0].termType, 'Quad');
|
|
1596
|
+
assert.equal(tc.seen[0].predicate.value, 'http://example.org/q');
|
|
1597
|
+
assert.ok(Array.isArray(tc.result.closureQuads), 'Expected closureQuads array');
|
|
1598
|
+
assert.equal(tc.result.closureQuads.length, 1);
|
|
1599
|
+
assert.equal(tc.result.closureQuads[0].object.value, 'http://example.org/o');
|
|
1600
1600
|
},
|
|
1601
1601
|
},
|
|
1602
1602
|
{
|
|
@@ -1633,14 +1633,14 @@ _:x :hates { _:foo :making :mess }.
|
|
|
1633
1633
|
})) {
|
|
1634
1634
|
quads.push(quad);
|
|
1635
1635
|
}
|
|
1636
|
-
this.
|
|
1636
|
+
this.quads = quads;
|
|
1637
1637
|
return quads.map((q) => `${q.subject.value} ${q.predicate.value} ${q.object.value}`).join('\n');
|
|
1638
1638
|
},
|
|
1639
1639
|
expect: [/http:\/\/example\.org\/q/],
|
|
1640
|
-
check(
|
|
1641
|
-
assert.equal(tc.
|
|
1642
|
-
assert.equal(tc.
|
|
1643
|
-
assert.equal(tc.
|
|
1640
|
+
check(outputIgnored, tc) {
|
|
1641
|
+
assert.equal(tc.quads.length, 1, 'Expected one yielded quad');
|
|
1642
|
+
assert.equal(tc.quads[0].predicate.value, 'http://example.org/q');
|
|
1643
|
+
assert.equal(tc.quads[0].graph.termType, 'DefaultGraph');
|
|
1644
1644
|
},
|
|
1645
1645
|
},
|
|
1646
1646
|
{
|
package/test/playground.test.js
CHANGED
|
@@ -74,7 +74,7 @@ function startStaticServer(rootDir) {
|
|
|
74
74
|
|
|
75
75
|
res.writeHead(200, { 'Content-Type': guessContentType(fsPath), 'Cache-Control': 'no-store' });
|
|
76
76
|
fs.createReadStream(fsPath).pipe(res);
|
|
77
|
-
} catch
|
|
77
|
+
} catch {
|
|
78
78
|
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
79
79
|
res.end('Not found');
|
|
80
80
|
}
|
package/tools/bundle.js
CHANGED
|
@@ -140,8 +140,10 @@ out.push(' }');
|
|
|
140
140
|
out.push(' const __entry = __loadEntry();');
|
|
141
141
|
out.push(' const __api = { reasonStream: __entry.reasonStream, reasonRdfJs: __entry.reasonRdfJs };');
|
|
142
142
|
out.push('');
|
|
143
|
-
out.push(
|
|
144
|
-
|
|
143
|
+
out.push(
|
|
144
|
+
' try { if (__outerModule && __outerModule.exports) __outerModule.exports = __api; } catch (ignoredError) {}',
|
|
145
|
+
);
|
|
146
|
+
out.push(' try { if (__outerSelf) __outerSelf.eyeling = __api; } catch (ignoredError) {}');
|
|
145
147
|
out.push('');
|
|
146
148
|
out.push(' // ---- demo.html compatibility ----');
|
|
147
149
|
out.push(' // The original monolithic eyeling.js exposed internal functions/flags as globals.');
|
|
@@ -175,14 +177,14 @@ out.push(' } else {');
|
|
|
175
177
|
out.push(' // Fallback (no live linkage)');
|
|
176
178
|
out.push(' if (typeof getFn === "function") __outerSelf[name] = getFn();');
|
|
177
179
|
out.push(' }');
|
|
178
|
-
out.push(' } catch (
|
|
180
|
+
out.push(' } catch (ignoredError) {}');
|
|
179
181
|
out.push(' };');
|
|
180
182
|
out.push('');
|
|
181
183
|
out.push(' def("enforceHttpsEnabled", __entry.getEnforceHttpsEnabled, __entry.setEnforceHttpsEnabled);');
|
|
182
184
|
out.push(' def("proofCommentsEnabled", __entry.getProofCommentsEnabled, __entry.setProofCommentsEnabled);');
|
|
183
185
|
out.push(' def("__tracePrefixes", __entry.getTracePrefixes, __entry.setTracePrefixes);');
|
|
184
186
|
out.push(' }');
|
|
185
|
-
out.push(' } catch (
|
|
187
|
+
out.push(' } catch (ignoredError) {}');
|
|
186
188
|
out.push('');
|
|
187
189
|
out.push(' try {');
|
|
188
190
|
out.push(
|
|
@@ -190,7 +192,7 @@ out.push(
|
|
|
190
192
|
);
|
|
191
193
|
out.push(' __entry.main();');
|
|
192
194
|
out.push(' }');
|
|
193
|
-
out.push(' } catch (
|
|
195
|
+
out.push(' } catch (ignoredError) {}');
|
|
194
196
|
out.push('})();');
|
|
195
197
|
|
|
196
198
|
fs.writeFileSync(OUT, out.join('\n') + '\n', { encoding: 'utf8' });
|
package/tools/n3gen.js
CHANGED
|
@@ -32,7 +32,7 @@ const process = require('node:process');
|
|
|
32
32
|
|
|
33
33
|
const crypto = require('node:crypto');
|
|
34
34
|
|
|
35
|
-
function
|
|
35
|
+
function stripIriRef(s) {
|
|
36
36
|
// Allow passing an IRIREF like <...>
|
|
37
37
|
if (typeof s !== 'string') return '';
|
|
38
38
|
s = s.trim();
|
|
@@ -41,7 +41,7 @@ function _stripIriRef(s) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
function normalizeSkolemRoot(root) {
|
|
44
|
-
root =
|
|
44
|
+
root = stripIriRef(root);
|
|
45
45
|
if (!root) return '';
|
|
46
46
|
// Ensure it ends with '/.well-known/genid/' OR at least with '/'
|
|
47
47
|
if (!root.endsWith('/')) root += '/';
|
|
@@ -64,7 +64,7 @@ const SKOLEM_ROOT = normalizeSkolemRoot(process.env.SKOLEM_ROOT) || DEFAULT_SKOL
|
|
|
64
64
|
let SKOLEM_UUID = null; // e.g., '3f2504e0-4f89-5d3a-9a0c-0305e82c3301'
|
|
65
65
|
let SKOLEM_PREFIX_IRI = null; // e.g., 'https://.../.well-known/genid/<UUID>#'
|
|
66
66
|
|
|
67
|
-
function
|
|
67
|
+
function deterministicUuidFromText(inputText) {
|
|
68
68
|
const h = crypto.createHash('sha256').update(inputText, 'utf8').digest();
|
|
69
69
|
const b = Buffer.from(h.subarray(0, 16));
|
|
70
70
|
|
|
@@ -77,11 +77,11 @@ function _deterministicUuidFromText(inputText) {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
function initSkolemForInput(inputText) {
|
|
80
|
-
SKOLEM_UUID =
|
|
80
|
+
SKOLEM_UUID = deterministicUuidFromText(inputText);
|
|
81
81
|
SKOLEM_PREFIX_IRI = `${SKOLEM_ROOT}${SKOLEM_UUID}#`;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
function
|
|
84
|
+
function pnLocalSafe(s) {
|
|
85
85
|
// Turtle PN_LOCAL allows percent escapes (PLX). We make sure all "special"
|
|
86
86
|
// encodeURIComponent survivors are percent-escaped too.
|
|
87
87
|
return encodeURIComponent(s).replace(/[!'()*]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase());
|
|
@@ -786,7 +786,7 @@ class TurtleParser {
|
|
|
786
786
|
this.blankCounter = 0;
|
|
787
787
|
this.pendingTriples = [];
|
|
788
788
|
this.reifierCounter = 0;
|
|
789
|
-
this.
|
|
789
|
+
this.reifiesEmitted = new Set();
|
|
790
790
|
}
|
|
791
791
|
|
|
792
792
|
peek() {
|
|
@@ -811,16 +811,16 @@ class TurtleParser {
|
|
|
811
811
|
return new Blank(`_:n3r${this.reifierCounter}`);
|
|
812
812
|
}
|
|
813
813
|
|
|
814
|
-
|
|
814
|
+
termKey(t) {
|
|
815
815
|
if (t == null) return '[]';
|
|
816
816
|
if (t instanceof Iri) return `I:${t.value}`;
|
|
817
817
|
if (t instanceof Blank) return `B:${t.label}`;
|
|
818
818
|
if (t instanceof Literal) return `L:${t.value}`;
|
|
819
819
|
if (t instanceof Var) return `V:${t.name}`;
|
|
820
|
-
if (t instanceof ListTerm) return `T:(` + t.elems.map((x) => this.
|
|
820
|
+
if (t instanceof ListTerm) return `T:(` + t.elems.map((x) => this.termKey(x)).join(' ') + `)`;
|
|
821
821
|
if (t instanceof GraphTerm) {
|
|
822
822
|
const inner = t.triples
|
|
823
|
-
.map((tr) => `${this.
|
|
823
|
+
.map((tr) => `${this.termKey(tr.s)} ${this.termKey(tr.p)} ${this.termKey(tr.o)}`)
|
|
824
824
|
.join(' | ');
|
|
825
825
|
return `G:{${inner}}`;
|
|
826
826
|
}
|
|
@@ -831,9 +831,9 @@ class TurtleParser {
|
|
|
831
831
|
// reifier log:nameOf tripleTerm .
|
|
832
832
|
// We represent tripleTerm in N3 as a quoted graph term: { s p o . }
|
|
833
833
|
emitReifies(reifier, tripleGraph) {
|
|
834
|
-
const key = `${this.
|
|
835
|
-
if (this.
|
|
836
|
-
this.
|
|
834
|
+
const key = `${this.termKey(reifier)}|${this.termKey(tripleGraph)}`;
|
|
835
|
+
if (this.reifiesEmitted.has(key)) return;
|
|
836
|
+
this.reifiesEmitted.add(key);
|
|
837
837
|
this.pendingTriples.push(new Triple(reifier, internIri(LOG_NS + 'nameOf'), tripleGraph));
|
|
838
838
|
}
|
|
839
839
|
|
|
@@ -1519,7 +1519,7 @@ function buildSkolemMapForBnodesThatCrossScopes(triples) {
|
|
|
1519
1519
|
if (scopes.size <= 1) continue;
|
|
1520
1520
|
|
|
1521
1521
|
const id = lbl.startsWith('_:') ? lbl.slice(2) : lbl;
|
|
1522
|
-
const local =
|
|
1522
|
+
const local = pnLocalSafe(id);
|
|
1523
1523
|
skolemMap.set(lbl, `${SKOLEM_PREFIX}:${local}`);
|
|
1524
1524
|
}
|
|
1525
1525
|
return skolemMap;
|
|
@@ -1538,16 +1538,16 @@ function buildSkolemMapForBnodesThatCrossScopes(triples) {
|
|
|
1538
1538
|
// semantics-preserving.
|
|
1539
1539
|
// ---------------------------------------------------------------------------
|
|
1540
1540
|
|
|
1541
|
-
function
|
|
1541
|
+
function termKey(t) {
|
|
1542
1542
|
if (t == null) return 'N:null';
|
|
1543
1543
|
if (t instanceof Iri) return `I:${t.value}`;
|
|
1544
1544
|
if (t instanceof Blank) return `B:${t.label}`;
|
|
1545
1545
|
if (t instanceof Literal) return `L:${t.value}`;
|
|
1546
1546
|
if (t instanceof Var) return `V:${t.name}`;
|
|
1547
|
-
if (t instanceof ListTerm) return `T:(` + t.elems.map(
|
|
1548
|
-
if (t instanceof OpenListTerm) return `T:(` + t.prefix.map(
|
|
1547
|
+
if (t instanceof ListTerm) return `T:(` + t.elems.map(termKey).join(' ') + `)`;
|
|
1548
|
+
if (t instanceof OpenListTerm) return `T:(` + t.prefix.map(termKey).join(' ') + ` ... ?${t.tailVar})`;
|
|
1549
1549
|
if (t instanceof GraphTerm)
|
|
1550
|
-
return `G:{` + t.triples.map((tr) => `${
|
|
1550
|
+
return `G:{` + t.triples.map((tr) => `${termKey(tr.s)} ${termKey(tr.p)} ${termKey(tr.o)}`).join(' ; ') + `}`;
|
|
1551
1551
|
return `X:${String(t)}`;
|
|
1552
1552
|
}
|
|
1553
1553
|
|
|
@@ -1567,11 +1567,11 @@ function foldRdfLists(triples) {
|
|
|
1567
1567
|
|
|
1568
1568
|
for (let i = 0; i < triples.length; i++) {
|
|
1569
1569
|
const tr = triples[i];
|
|
1570
|
-
const sKey =
|
|
1570
|
+
const sKey = termKey(tr.s);
|
|
1571
1571
|
if (!outBySubj.has(sKey)) outBySubj.set(sKey, { term: tr.s, idxs: [] });
|
|
1572
1572
|
outBySubj.get(sKey).idxs.push(i);
|
|
1573
1573
|
|
|
1574
|
-
const oKey =
|
|
1574
|
+
const oKey = termKey(tr.o);
|
|
1575
1575
|
const viaRest = isIri(tr.p, rdfRest);
|
|
1576
1576
|
addIncoming(oKey, viaRest);
|
|
1577
1577
|
}
|
|
@@ -1655,7 +1655,7 @@ function foldRdfLists(triples) {
|
|
|
1655
1655
|
break;
|
|
1656
1656
|
}
|
|
1657
1657
|
|
|
1658
|
-
const nextKey =
|
|
1658
|
+
const nextKey = termKey(next);
|
|
1659
1659
|
|
|
1660
1660
|
// Intermediate node safety: only referenced via rdf:rest and exactly once.
|
|
1661
1661
|
const inc = incoming.get(nextKey) || 0;
|
|
@@ -1694,7 +1694,7 @@ function foldRdfLists(triples) {
|
|
|
1694
1694
|
if (t == null) return t;
|
|
1695
1695
|
|
|
1696
1696
|
if (t instanceof Blank) {
|
|
1697
|
-
const m = listMap.get(
|
|
1697
|
+
const m = listMap.get(termKey(t));
|
|
1698
1698
|
if (m) return replaceTerm(m.listTerm);
|
|
1699
1699
|
return t;
|
|
1700
1700
|
}
|
|
@@ -1813,7 +1813,7 @@ function ensureSkolemPrefix(prefixes, skolemMap) {
|
|
|
1813
1813
|
const base = prefixes ? prefixes.baseIri || '' : '';
|
|
1814
1814
|
const labels = [...skolemMap.keys()].sort().join('\n');
|
|
1815
1815
|
const seed = ['n3gen-skolem', SKOLEM_ROOT, base, labels, ''].join('\n');
|
|
1816
|
-
const uuid =
|
|
1816
|
+
const uuid = deterministicUuidFromText(seed);
|
|
1817
1817
|
SKOLEM_PREFIX_IRI = `${SKOLEM_ROOT}${uuid}#`;
|
|
1818
1818
|
} else if (!SKOLEM_PREFIX_IRI) {
|
|
1819
1819
|
SKOLEM_PREFIX_IRI = `${SKOLEM_ROOT}${SKOLEM_UUID}#`;
|