eyeling 1.7.5 → 1.7.7

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.
@@ -0,0 +1,17 @@
1
+ # ========================================================
2
+ # List map
3
+ # behaves like the functional programming operator flatMap
4
+ # Examples from Giacomo Citi
5
+ # ========================================================
6
+
7
+ @prefix list: <http://www.w3.org/2000/10/swap/list#> .
8
+ @prefix : <http://example.org/#>.
9
+
10
+ :s1 :p1 :o1 .
11
+ :s2 :p1 :o2 .
12
+ :s3 :p1 :o3, :o4 .
13
+
14
+ { ((:s1 :s2) :p1) list:map (:o1 :o2) } => { :test1 :is true } .
15
+ { ((:s1 :s2 :s3) :p1) list:map (:o1 :o2 :o3 :o4) } => { :test2 :is true } .
16
+ { ((:s4) :p1) list:map () } => { :test3 :is true } .
17
+ { ((:s1) :p2) list:map () } => { :test4 :is true } .
@@ -0,0 +1,20 @@
1
+ # ======================
2
+ # log:conclusion example
3
+ # ======================
4
+
5
+ @prefix : <http://example.org/>.
6
+ @prefix log: <http://www.w3.org/2000/10/swap/log#> .
7
+
8
+ :let :param {
9
+ :Felix a :Cat .
10
+ { ?X a :Cat . } => { ?X :says "Meow" . } .
11
+ } .
12
+
13
+ {
14
+ :let :param ?param .
15
+ ?param log:conclusion ?conclusion .
16
+ }
17
+ =>
18
+ {
19
+ :result :is ?conclusion .
20
+ } .
@@ -0,0 +1,6 @@
1
+ @prefix : <http://example.org/#> .
2
+
3
+ :test1 :is true .
4
+ :test2 :is true .
5
+ :test3 :is true .
6
+ :test4 :is true .
@@ -0,0 +1,13 @@
1
+ @prefix : <http://example.org/> .
2
+ @prefix log: <http://www.w3.org/2000/10/swap/log#> .
3
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
4
+
5
+ :result :is {
6
+ :Felix a :Cat .
7
+ {
8
+ ?X a :Cat .
9
+ } => {
10
+ ?X :says "Meow" .
11
+ } .
12
+ :Felix :says "Meow" .
13
+ } .
package/eyeling.js CHANGED
@@ -89,6 +89,62 @@ const jsonPointerCache = new Map();
89
89
  // Key is the dereferenced document IRI *without* fragment.
90
90
  const __logContentCache = new Map(); // iri -> string | null (null means fetch/read failed)
91
91
  const __logSemanticsCache = new Map(); // iri -> GraphTerm | null (null means parse failed)
92
+ const __logConclusionCache = new WeakMap(); // GraphTerm -> GraphTerm (deductive closure)
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
+ }
92
148
 
93
149
  function __stripFragment(iri) {
94
150
  const i = iri.indexOf('#');
@@ -114,6 +170,7 @@ function __fileIriToPath(fileIri) {
114
170
  }
115
171
 
116
172
  function __readFileText(pathOrFileIri) {
173
+ if (!__IS_NODE) return null;
117
174
  const fs = require('fs');
118
175
  let path = pathOrFileIri;
119
176
  if (__isFileIri(pathOrFileIri)) path = __fileIriToPath(pathOrFileIri);
@@ -125,6 +182,7 @@ function __readFileText(pathOrFileIri) {
125
182
  }
126
183
 
127
184
  function __fetchHttpTextViaSubprocess(url) {
185
+ if (!__IS_NODE) return null;
128
186
  const cp = require('child_process');
129
187
  // Use a subprocess so this code remains synchronous without rewriting the whole reasoner to async.
130
188
  const script = `
@@ -180,16 +238,27 @@ function __fetchHttpTextViaSubprocess(url) {
180
238
  }
181
239
 
182
240
  function __derefTextSync(iriNoFrag) {
183
- if (__logContentCache.has(iriNoFrag)) return __logContentCache.get(iriNoFrag);
241
+ const norm = __normalizeDerefIri(iriNoFrag);
242
+ const key = typeof norm === 'string' && norm ? norm : iriNoFrag;
243
+
244
+ if (__logContentCache.has(key)) return __logContentCache.get(key);
184
245
 
185
246
  let text = null;
186
- if (__isHttpIri(iriNoFrag)) {
187
- text = __fetchHttpTextViaSubprocess(iriNoFrag);
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
+ }
188
255
  } else {
189
- // Treat any non-http(s) IRI as a local path (including file://), for basic usability.
190
- text = __readFileText(iriNoFrag);
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);
191
259
  }
192
- __logContentCache.set(iriNoFrag, text);
260
+
261
+ __logContentCache.set(key, text);
193
262
  return text;
194
263
  }
195
264
 
@@ -217,22 +286,106 @@ function __parseSemanticsToFormula(text, baseIri) {
217
286
  }
218
287
 
219
288
  function __derefSemanticsSync(iriNoFrag) {
220
- if (__logSemanticsCache.has(iriNoFrag)) return __logSemanticsCache.get(iriNoFrag);
289
+ const norm = __normalizeDerefIri(iriNoFrag);
290
+ const key = typeof norm === 'string' && norm ? norm : iriNoFrag;
291
+ if (__logSemanticsCache.has(key)) return __logSemanticsCache.get(key);
221
292
 
222
293
  const text = __derefTextSync(iriNoFrag);
223
294
  if (typeof text !== 'string') {
224
- __logSemanticsCache.set(iriNoFrag, null);
295
+ __logSemanticsCache.set(key, null);
225
296
  return null;
226
297
  }
227
298
  try {
228
- const formula = __parseSemanticsToFormula(text, iriNoFrag);
229
- __logSemanticsCache.set(iriNoFrag, formula);
299
+ const baseIri = (typeof key === 'string' && key) ? key : iriNoFrag;
300
+ const formula = __parseSemanticsToFormula(text, baseIri);
301
+ __logSemanticsCache.set(key, formula);
230
302
  return formula;
231
303
  } catch {
232
- __logSemanticsCache.set(iriNoFrag, null);
304
+ __logSemanticsCache.set(key, null);
233
305
  return null;
234
306
  }
235
307
  }
308
+ function __makeRuleFromTerms(left, right, isForward) {
309
+ // Mirror Parser.makeRule, but usable at runtime (e.g., log:conclusion).
310
+ let premiseTerm, conclTerm;
311
+
312
+ if (isForward) {
313
+ premiseTerm = left;
314
+ conclTerm = right;
315
+ } else {
316
+ premiseTerm = right;
317
+ conclTerm = left;
318
+ }
319
+
320
+ let isFuse = false;
321
+ if (isForward) {
322
+ if (conclTerm instanceof Literal && conclTerm.value === 'false') {
323
+ isFuse = true;
324
+ }
325
+ }
326
+
327
+ let rawPremise;
328
+ if (premiseTerm instanceof GraphTerm) {
329
+ rawPremise = premiseTerm.triples;
330
+ } else if (premiseTerm instanceof Literal && premiseTerm.value === 'true') {
331
+ rawPremise = [];
332
+ } else {
333
+ rawPremise = [];
334
+ }
335
+
336
+ let rawConclusion;
337
+ if (conclTerm instanceof GraphTerm) {
338
+ rawConclusion = conclTerm.triples;
339
+ } else if (conclTerm instanceof Literal && conclTerm.value === 'false') {
340
+ rawConclusion = [];
341
+ } else {
342
+ rawConclusion = [];
343
+ }
344
+
345
+ const headBlankLabels = collectBlankLabelsInTriples(rawConclusion);
346
+ const [premise0, conclusion] = liftBlankRuleVars(rawPremise, rawConclusion);
347
+ const premise = isForward ? reorderPremiseForConstraints(premise0) : premise0;
348
+ return new Rule(premise, conclusion, isForward, isFuse, headBlankLabels);
349
+ }
350
+
351
+ function __computeConclusionFromFormula(formula) {
352
+ if (!(formula instanceof GraphTerm)) return null;
353
+
354
+ const cached = __logConclusionCache.get(formula);
355
+ if (cached) return cached;
356
+
357
+ // Facts start as *all* triples in the formula, including rule triples.
358
+ const facts2 = formula.triples.slice();
359
+
360
+ // Extract rules from rule-triples present inside the formula.
361
+ const fw = [];
362
+ const bw = [];
363
+
364
+ for (const tr of formula.triples) {
365
+ // Treat {A} => {B} as a forward rule.
366
+ if (isLogImplies(tr.p)) {
367
+ fw.push(__makeRuleFromTerms(tr.s, tr.o, true));
368
+ continue;
369
+ }
370
+
371
+ // Treat {A} <= {B} as the same rule in the other direction, i.e., {B} => {A},
372
+ // so it participates in deductive closure even if only <= is used.
373
+ if (isLogImpliedBy(tr.p)) {
374
+ fw.push(__makeRuleFromTerms(tr.o, tr.s, true));
375
+ // Also index it as a backward rule for completeness (helps proveGoals in some cases).
376
+ bw.push(__makeRuleFromTerms(tr.s, tr.o, false));
377
+ continue;
378
+ }
379
+ }
380
+
381
+ // Saturate within this local formula only.
382
+ forwardChain(facts2, fw, bw);
383
+
384
+ const out = new GraphTerm(facts2.slice());
385
+ __logConclusionCache.set(formula, out);
386
+ return out;
387
+ }
388
+
236
389
 
237
390
 
238
391
  // Controls whether human-readable proof comments are printed.
@@ -4850,6 +5003,31 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
4850
5003
  return s2 !== null ? [s2] : [];
4851
5004
  }
4852
5005
 
5006
+
5007
+ // log:conclusion
5008
+ // Schema: $s+ log:conclusion $o?
5009
+ // $o is the deductive closure of the subject formula $s (including rule inferences).
5010
+ if (pv === LOG_NS + 'conclusion') {
5011
+ // Accept 'true' as the empty formula.
5012
+ let inFormula = null;
5013
+ if (g.s instanceof GraphTerm) inFormula = g.s;
5014
+ else if (g.s instanceof Literal && g.s.value === 'true') inFormula = new GraphTerm([]);
5015
+ else return [];
5016
+
5017
+ const conclusion = __computeConclusionFromFormula(inFormula);
5018
+ if (!(conclusion instanceof GraphTerm)) return [];
5019
+
5020
+ if (g.o instanceof Var) {
5021
+ const s2 = { ...subst };
5022
+ s2[g.o.name] = conclusion;
5023
+ return [s2];
5024
+ }
5025
+ if (g.o instanceof Blank) return [{ ...subst }];
5026
+
5027
+ const s2 = unifyTerm(g.o, conclusion, subst);
5028
+ return s2 !== null ? [s2] : [];
5029
+ }
5030
+
4853
5031
  // log:content
4854
5032
  // Schema: $s+ log:content $o?
4855
5033
  // Dereferences $s and returns the online resource as an xsd:string.
@@ -5998,10 +6176,17 @@ function termToN3(t, pref) {
5998
6176
  return '(' + inside.join(' ') + ')';
5999
6177
  }
6000
6178
  if (t instanceof GraphTerm) {
6179
+ const indent = ' ';
6180
+ const indentBlock = (str) =>
6181
+ str
6182
+ .split(/\r?\n/)
6183
+ .map((ln) => (ln.length ? indent + ln : ln))
6184
+ .join('\n');
6185
+
6001
6186
  let s = '{\n';
6002
6187
  for (const tr of t.triples) {
6003
- let line = tripleToN3(tr, pref).trimEnd();
6004
- if (line) s += ' ' + line + '\n';
6188
+ const block = tripleToN3(tr, pref).trimEnd();
6189
+ if (block) s += indentBlock(block) + '\n';
6005
6190
  }
6006
6191
  s += '}';
6007
6192
  return s;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.7.5",
3
+ "version": "1.7.7",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [