eyeling 1.7.6 → 1.7.8
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/README.md +1 -0
- package/eyeling.js +198 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -108,6 +108,7 @@ Options:
|
|
|
108
108
|
-n, --no-proof-comments Disable proof explanations (default).
|
|
109
109
|
-s, --super-restricted Disable all builtins except => and <=.
|
|
110
110
|
-a, --ast Print parsed AST as JSON and exit.
|
|
111
|
+
--strings Print log:outputString strings (ordered by key) instead of N3 output.
|
|
111
112
|
```
|
|
112
113
|
|
|
113
114
|
By default, `eyeling`:
|
package/eyeling.js
CHANGED
|
@@ -91,6 +91,61 @@ const __logContentCache = new Map(); // iri -> string | null (null means fetch/r
|
|
|
91
91
|
const __logSemanticsCache = new Map(); // iri -> GraphTerm | null (null means parse failed)
|
|
92
92
|
const __logConclusionCache = new WeakMap(); // GraphTerm -> GraphTerm (deductive closure)
|
|
93
93
|
|
|
94
|
+
// Environment detection (Node vs Browser/Worker).
|
|
95
|
+
// Eyeling is primarily synchronous, so we use sync XHR in browsers for log:content/log:semantics.
|
|
96
|
+
// Note: Browser fetches are subject to CORS; use CORS-enabled resources or a proxy.
|
|
97
|
+
const __IS_NODE =
|
|
98
|
+
typeof process !== 'undefined' &&
|
|
99
|
+
!!(process.versions && process.versions.node);
|
|
100
|
+
|
|
101
|
+
function __hasXmlHttpRequest() {
|
|
102
|
+
return typeof XMLHttpRequest !== 'undefined';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function __resolveBrowserUrl(ref) {
|
|
106
|
+
if (!ref) return ref;
|
|
107
|
+
// If already absolute, keep as-is.
|
|
108
|
+
if (/^[A-Za-z][A-Za-z0-9+.-]*:/.test(ref)) return ref;
|
|
109
|
+
const base =
|
|
110
|
+
(typeof document !== 'undefined' && document.baseURI) ||
|
|
111
|
+
(typeof location !== 'undefined' && location.href) ||
|
|
112
|
+
'';
|
|
113
|
+
try {
|
|
114
|
+
return new URL(ref, base).toString();
|
|
115
|
+
} catch {
|
|
116
|
+
return ref;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function __fetchHttpTextSyncBrowser(url) {
|
|
121
|
+
if (!__hasXmlHttpRequest()) return null;
|
|
122
|
+
try {
|
|
123
|
+
const xhr = new XMLHttpRequest();
|
|
124
|
+
xhr.open('GET', url, false); // synchronous
|
|
125
|
+
try {
|
|
126
|
+
xhr.setRequestHeader(
|
|
127
|
+
'Accept',
|
|
128
|
+
'text/n3, text/turtle, application/n-triples, application/n-quads, text/plain;q=0.1, */*;q=0.01'
|
|
129
|
+
);
|
|
130
|
+
} catch {
|
|
131
|
+
// Some environments restrict setting headers (ignore).
|
|
132
|
+
}
|
|
133
|
+
xhr.send(null);
|
|
134
|
+
const sc = xhr.status || 0;
|
|
135
|
+
if (sc < 200 || sc >= 300) return null;
|
|
136
|
+
return xhr.responseText;
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function __normalizeDerefIri(iriNoFrag) {
|
|
143
|
+
// In Node, treat non-http as local path; leave as-is.
|
|
144
|
+
if (__IS_NODE) return iriNoFrag;
|
|
145
|
+
// In browsers/workers, resolve relative references against the page URL.
|
|
146
|
+
return __resolveBrowserUrl(iriNoFrag);
|
|
147
|
+
}
|
|
148
|
+
|
|
94
149
|
function __stripFragment(iri) {
|
|
95
150
|
const i = iri.indexOf('#');
|
|
96
151
|
return i >= 0 ? iri.slice(0, i) : iri;
|
|
@@ -115,6 +170,7 @@ function __fileIriToPath(fileIri) {
|
|
|
115
170
|
}
|
|
116
171
|
|
|
117
172
|
function __readFileText(pathOrFileIri) {
|
|
173
|
+
if (!__IS_NODE) return null;
|
|
118
174
|
const fs = require('fs');
|
|
119
175
|
let path = pathOrFileIri;
|
|
120
176
|
if (__isFileIri(pathOrFileIri)) path = __fileIriToPath(pathOrFileIri);
|
|
@@ -126,6 +182,7 @@ function __readFileText(pathOrFileIri) {
|
|
|
126
182
|
}
|
|
127
183
|
|
|
128
184
|
function __fetchHttpTextViaSubprocess(url) {
|
|
185
|
+
if (!__IS_NODE) return null;
|
|
129
186
|
const cp = require('child_process');
|
|
130
187
|
// Use a subprocess so this code remains synchronous without rewriting the whole reasoner to async.
|
|
131
188
|
const script = `
|
|
@@ -181,16 +238,27 @@ function __fetchHttpTextViaSubprocess(url) {
|
|
|
181
238
|
}
|
|
182
239
|
|
|
183
240
|
function __derefTextSync(iriNoFrag) {
|
|
184
|
-
|
|
241
|
+
const norm = __normalizeDerefIri(iriNoFrag);
|
|
242
|
+
const key = typeof norm === 'string' && norm ? norm : iriNoFrag;
|
|
243
|
+
|
|
244
|
+
if (__logContentCache.has(key)) return __logContentCache.get(key);
|
|
185
245
|
|
|
186
246
|
let text = null;
|
|
187
|
-
|
|
188
|
-
|
|
247
|
+
|
|
248
|
+
if (__IS_NODE) {
|
|
249
|
+
if (__isHttpIri(key)) {
|
|
250
|
+
text = __fetchHttpTextViaSubprocess(key);
|
|
251
|
+
} else {
|
|
252
|
+
// Treat any non-http(s) IRI as a local path (including file://), for basic usability.
|
|
253
|
+
text = __readFileText(key);
|
|
254
|
+
}
|
|
189
255
|
} else {
|
|
190
|
-
//
|
|
191
|
-
|
|
256
|
+
// Browser / Worker: we can only dereference over HTTP(S), and it must pass CORS.
|
|
257
|
+
const url = typeof norm === 'string' && norm ? norm : key;
|
|
258
|
+
if (__isHttpIri(url)) text = __fetchHttpTextSyncBrowser(url);
|
|
192
259
|
}
|
|
193
|
-
|
|
260
|
+
|
|
261
|
+
__logContentCache.set(key, text);
|
|
194
262
|
return text;
|
|
195
263
|
}
|
|
196
264
|
|
|
@@ -218,19 +286,22 @@ function __parseSemanticsToFormula(text, baseIri) {
|
|
|
218
286
|
}
|
|
219
287
|
|
|
220
288
|
function __derefSemanticsSync(iriNoFrag) {
|
|
221
|
-
|
|
289
|
+
const norm = __normalizeDerefIri(iriNoFrag);
|
|
290
|
+
const key = typeof norm === 'string' && norm ? norm : iriNoFrag;
|
|
291
|
+
if (__logSemanticsCache.has(key)) return __logSemanticsCache.get(key);
|
|
222
292
|
|
|
223
293
|
const text = __derefTextSync(iriNoFrag);
|
|
224
294
|
if (typeof text !== 'string') {
|
|
225
|
-
__logSemanticsCache.set(
|
|
295
|
+
__logSemanticsCache.set(key, null);
|
|
226
296
|
return null;
|
|
227
297
|
}
|
|
228
298
|
try {
|
|
229
|
-
const
|
|
230
|
-
|
|
299
|
+
const baseIri = (typeof key === 'string' && key) ? key : iriNoFrag;
|
|
300
|
+
const formula = __parseSemanticsToFormula(text, baseIri);
|
|
301
|
+
__logSemanticsCache.set(key, formula);
|
|
231
302
|
return formula;
|
|
232
303
|
} catch {
|
|
233
|
-
__logSemanticsCache.set(
|
|
304
|
+
__logSemanticsCache.set(key, null);
|
|
234
305
|
return null;
|
|
235
306
|
}
|
|
236
307
|
}
|
|
@@ -5229,6 +5300,21 @@ if (pv === LOG_NS + 'conclusion') {
|
|
|
5229
5300
|
return sols.length ? [] : [{ ...subst }];
|
|
5230
5301
|
}
|
|
5231
5302
|
|
|
5303
|
+
// log:outputString
|
|
5304
|
+
// Schema: $s+ log:outputString $o+
|
|
5305
|
+
// Side-effecting output directive. As a builtin goal, we simply succeed
|
|
5306
|
+
// when both sides are bound and the object is a string literal.
|
|
5307
|
+
// Actual printing is handled at the end of a reasoning run (see --strings).
|
|
5308
|
+
if (pv === LOG_NS + 'outputString') {
|
|
5309
|
+
// Require subject to be bound (not a variable) and object to be a concrete string literal.
|
|
5310
|
+
if (g.s instanceof Var) return [];
|
|
5311
|
+
if (g.o instanceof Var) return [];
|
|
5312
|
+
const s = termToJsString(g.o);
|
|
5313
|
+
if (s === null) return [];
|
|
5314
|
+
return [{ ...subst }];
|
|
5315
|
+
}
|
|
5316
|
+
|
|
5317
|
+
|
|
5232
5318
|
// log:collectAllIn (scoped)
|
|
5233
5319
|
if (pv === LOG_NS + 'collectAllIn') {
|
|
5234
5320
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 3) return [];
|
|
@@ -6277,6 +6363,97 @@ function formatN3SyntaxError(err, text, path) {
|
|
|
6277
6363
|
// ===========================================================================
|
|
6278
6364
|
// CLI entry point
|
|
6279
6365
|
// ===========================================================================
|
|
6366
|
+
// ===========================================================================
|
|
6367
|
+
// log:outputString support
|
|
6368
|
+
// ===========================================================================
|
|
6369
|
+
|
|
6370
|
+
function __compareOutputStringKeys(a, b, prefixes) {
|
|
6371
|
+
// Deterministic ordering of keys. The spec only requires "order of the subject keys"
|
|
6372
|
+
// and leaves concrete term ordering reasoner-dependent. We implement:
|
|
6373
|
+
// 1) numeric literals (numeric value)
|
|
6374
|
+
// 2) plain literals (lexical form)
|
|
6375
|
+
// 3) IRIs
|
|
6376
|
+
// 4) blank nodes (label)
|
|
6377
|
+
// 5) fallback: skolemKeyFromTerm
|
|
6378
|
+
const aNum = parseNumericLiteralInfo(a);
|
|
6379
|
+
const bNum = parseNumericLiteralInfo(b);
|
|
6380
|
+
if (aNum && bNum) {
|
|
6381
|
+
// bigint or number
|
|
6382
|
+
if (aNum.kind === 'bigint' && bNum.kind === 'bigint') {
|
|
6383
|
+
if (aNum.value < bNum.value) return -1;
|
|
6384
|
+
if (aNum.value > bNum.value) return 1;
|
|
6385
|
+
return 0;
|
|
6386
|
+
}
|
|
6387
|
+
const av = Number(aNum.value);
|
|
6388
|
+
const bv = Number(bNum.value);
|
|
6389
|
+
if (av < bv) return -1;
|
|
6390
|
+
if (av > bv) return 1;
|
|
6391
|
+
return 0;
|
|
6392
|
+
}
|
|
6393
|
+
if (aNum && !bNum) return -1;
|
|
6394
|
+
if (!aNum && bNum) return 1;
|
|
6395
|
+
|
|
6396
|
+
// Plain literal ordering (lexical)
|
|
6397
|
+
if (a instanceof Literal && b instanceof Literal) {
|
|
6398
|
+
const [alex] = literalParts(a.value);
|
|
6399
|
+
const [blex] = literalParts(b.value);
|
|
6400
|
+
if (alex < blex) return -1;
|
|
6401
|
+
if (alex > blex) return 1;
|
|
6402
|
+
return 0;
|
|
6403
|
+
}
|
|
6404
|
+
if (a instanceof Literal && !(b instanceof Literal)) return -1;
|
|
6405
|
+
if (!(a instanceof Literal) && b instanceof Literal) return 1;
|
|
6406
|
+
|
|
6407
|
+
// IRIs
|
|
6408
|
+
if (a instanceof Iri && b instanceof Iri) {
|
|
6409
|
+
if (a.value < b.value) return -1;
|
|
6410
|
+
if (a.value > b.value) return 1;
|
|
6411
|
+
return 0;
|
|
6412
|
+
}
|
|
6413
|
+
if (a instanceof Iri && !(b instanceof Iri)) return -1;
|
|
6414
|
+
if (!(a instanceof Iri) && b instanceof Iri) return 1;
|
|
6415
|
+
|
|
6416
|
+
// Blank nodes
|
|
6417
|
+
if (a instanceof Blank && b instanceof Blank) {
|
|
6418
|
+
if (a.label < b.label) return -1;
|
|
6419
|
+
if (a.label > b.label) return 1;
|
|
6420
|
+
return 0;
|
|
6421
|
+
}
|
|
6422
|
+
if (a instanceof Blank && !(b instanceof Blank)) return -1;
|
|
6423
|
+
if (!(a instanceof Blank) && b instanceof Blank) return 1;
|
|
6424
|
+
|
|
6425
|
+
// Fallback
|
|
6426
|
+
const ak = skolemKeyFromTerm(a);
|
|
6427
|
+
const bk = skolemKeyFromTerm(b);
|
|
6428
|
+
if (ak < bk) return -1;
|
|
6429
|
+
if (ak > bk) return 1;
|
|
6430
|
+
return 0;
|
|
6431
|
+
}
|
|
6432
|
+
|
|
6433
|
+
function __collectOutputStringsFromFacts(facts, prefixes) {
|
|
6434
|
+
// Gather all (key, string) pairs from the saturated fact store.
|
|
6435
|
+
const pairs = [];
|
|
6436
|
+
for (const tr of facts) {
|
|
6437
|
+
if (!(tr && tr.p instanceof Iri)) continue;
|
|
6438
|
+
if (tr.p.value !== LOG_NS + 'outputString') continue;
|
|
6439
|
+
if (!(tr.o instanceof Literal)) continue;
|
|
6440
|
+
|
|
6441
|
+
const s = termToJsString(tr.o);
|
|
6442
|
+
if (s === null) continue;
|
|
6443
|
+
|
|
6444
|
+
pairs.push({ key: tr.s, text: s, idx: pairs.length });
|
|
6445
|
+
}
|
|
6446
|
+
|
|
6447
|
+
pairs.sort((a, b) => {
|
|
6448
|
+
const c = __compareOutputStringKeys(a.key, b.key, prefixes);
|
|
6449
|
+
if (c !== 0) return c;
|
|
6450
|
+
return a.idx - b.idx; // stable tie-breaker
|
|
6451
|
+
});
|
|
6452
|
+
|
|
6453
|
+
return pairs.map((p) => p.text).join('');
|
|
6454
|
+
}
|
|
6455
|
+
|
|
6456
|
+
|
|
6280
6457
|
function main() {
|
|
6281
6458
|
// Drop "node" and script name; keep only user-provided args
|
|
6282
6459
|
const argv = process.argv.slice(2);
|
|
@@ -6293,7 +6470,8 @@ function main() {
|
|
|
6293
6470
|
` -p, --proof-comments Enable proof explanations.\n` +
|
|
6294
6471
|
` -n, --no-proof-comments Disable proof explanations (default).\n` +
|
|
6295
6472
|
` -s, --super-restricted Disable all builtins except => and <=.\n` +
|
|
6296
|
-
` -a, --ast Print parsed AST as JSON and exit.\n
|
|
6473
|
+
` -a, --ast Print parsed AST as JSON and exit.\n` +
|
|
6474
|
+
` --strings Print log:outputString strings (ordered by key) instead of N3 output.\n`;
|
|
6297
6475
|
(toStderr ? console.error : console.log)(msg);
|
|
6298
6476
|
}
|
|
6299
6477
|
|
|
@@ -6314,6 +6492,8 @@ function main() {
|
|
|
6314
6492
|
|
|
6315
6493
|
const showAst = argv.includes('--ast') || argv.includes('-a');
|
|
6316
6494
|
|
|
6495
|
+
const outputStringsMode = argv.includes('--strings');
|
|
6496
|
+
|
|
6317
6497
|
// --proof-comments / -p: enable proof explanations
|
|
6318
6498
|
if (argv.includes('--proof-comments') || argv.includes('-p')) {
|
|
6319
6499
|
proofCommentsEnabled = true;
|
|
@@ -6389,6 +6569,12 @@ function main() {
|
|
|
6389
6569
|
|
|
6390
6570
|
const facts = triples.filter((tr) => isGroundTriple(tr));
|
|
6391
6571
|
const derived = forwardChain(facts, frules, brules);
|
|
6572
|
+
// If requested, print log:outputString values (ordered by subject key) and exit.
|
|
6573
|
+
if (outputStringsMode) {
|
|
6574
|
+
const out = __collectOutputStringsFromFacts(facts, prefixes);
|
|
6575
|
+
if (out) process.stdout.write(out);
|
|
6576
|
+
process.exit(0);
|
|
6577
|
+
}
|
|
6392
6578
|
const derivedTriples = derived.map((df) => df.fact);
|
|
6393
6579
|
const usedPrefixes = prefixes.prefixesUsedForOutput(derivedTriples);
|
|
6394
6580
|
|