eyeling 1.7.3 → 1.7.5

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/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
 
3
- ### Copyright 2021-2025 Jos De Roo, KNoWS office of IDLab, Ghent University - imec
3
+ ### Copyright 2021-2026 Jos De Roo, KNoWS office of IDLab, Ghent University - imec
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -215,7 +215,7 @@ Commonly used N3/Turtle features:
215
215
 
216
216
  - **crypto**: `crypto:md5` `crypto:sha` `crypto:sha256` `crypto:sha512`
217
217
  - **list**: `list:append` `list:first` `list:firstRest` `list:in` `list:iterate` `list:last` `list:length` `list:map` `list:member` `list:memberAt` `list:notMember` `list:remove` `list:rest` `list:reverse` `list:sort`
218
- - **log**: `log:collectAllIn` `log:equalTo` `log:forAllIn` `log:impliedBy` `log:implies` `log:notEqualTo` `log:notIncludes` `log:skolem` `log:uri`
218
+ - **log**: `log:collectAllIn` `log:content` `log:equalTo` `log:forAllIn` `log:impliedBy` `log:implies` `log:notEqualTo` `log:notIncludes` `log:semantics` `log:skolem` `log:uri`
219
219
  - **math**: `math:absoluteValue` `math:acos` `math:asin` `math:atan` `math:cos` `math:cosh` `math:degrees` `math:difference` `math:equalTo` `math:exponentiation` `math:greaterThan` `math:integerQuotient` `math:lessThan` `math:negation` `math:notEqualTo` `math:notGreaterThan` `math:notLessThan` `math:product` `math:quotient` `math:remainder` `math:rounded` `math:sin` `math:sinh` `math:sum` `math:tan` `math:tanh`
220
220
  - **string**: `string:concatenation` `string:contains` `string:containsIgnoringCase` `string:endsWith` `string:equalIgnoringCase` `string:format` `string:greaterThan` `string:jsonPointer` `string:lessThan` `string:matches` `string:notEqualIgnoringCase` `string:notGreaterThan` `string:notLessThan` `string:notMatches` `string:replace` `string:scrape` `string:startsWith`
221
221
  - **time**: `time:localTime`
@@ -0,0 +1,12 @@
1
+ @prefix : <http://example.org/> .
2
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
3
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
4
+
5
+ :gotContent :is "# Schema for test data\n#\n# This is only \n@prefix daml: <http://www.daml.org/2001/03/daml+oil#> .\n@prefix mech: <#> .\n\n@prefix : <#> .\n\n:includes a daml:TransitiveProperty .\n\n:partOf a daml:TransitiveProperty; daml:inverseOf :includes .\n\n:dependsOn a daml:TransitiveProperty ;\n daml:hasSubProperty :includes . # Real name of subproperty?\n\n\n\n\n"^^xsd:string .
6
+ :gotFormula :is {
7
+ <https://www.w3.org/2000/10/swap/test/s1.n3#includes> a <http://www.daml.org/2001/03/daml+oil#TransitiveProperty> .
8
+ <https://www.w3.org/2000/10/swap/test/s1.n3#partOf> a <http://www.daml.org/2001/03/daml+oil#TransitiveProperty> .
9
+ <https://www.w3.org/2000/10/swap/test/s1.n3#partOf> <http://www.daml.org/2001/03/daml+oil#inverseOf> <https://www.w3.org/2000/10/swap/test/s1.n3#includes> .
10
+ <https://www.w3.org/2000/10/swap/test/s1.n3#dependsOn> a <http://www.daml.org/2001/03/daml+oil#TransitiveProperty> .
11
+ <https://www.w3.org/2000/10/swap/test/s1.n3#dependsOn> <http://www.daml.org/2001/03/daml+oil#hasSubProperty> <https://www.w3.org/2000/10/swap/test/s1.n3#includes> .
12
+ } .
@@ -0,0 +1,19 @@
1
+ # =============================================
2
+ # Reaching out onto the Web
3
+ # See https://www.w3.org/2000/10/swap/doc/Reach
4
+ # =============================================
5
+
6
+ @prefix : <http://example.org/>.
7
+ @prefix log: <http://www.w3.org/2000/10/swap/log#>.
8
+
9
+ {
10
+ <https://www.w3.org/2000/10/swap/test/s1.n3>
11
+ log:content ?txt ;
12
+ log:semantics ?f .
13
+ }
14
+ =>
15
+ {
16
+ :gotContent :is ?txt .
17
+ :gotFormula :is ?f .
18
+ }.
19
+
package/eyeling.js CHANGED
@@ -82,6 +82,159 @@ const __parseNumericInfoCache = new Map(); // lit string -> info|null
82
82
  // Cache for string:jsonPointer: jsonText -> { parsed: any|null, ptrCache: Map<string, Term|null> }
83
83
  const jsonPointerCache = new Map();
84
84
 
85
+ // -----------------------------------------------------------------------------
86
+ // log:content / log:semantics support (basic, synchronous)
87
+ // -----------------------------------------------------------------------------
88
+ // Cache dereferenced resources within a single run.
89
+ // Key is the dereferenced document IRI *without* fragment.
90
+ const __logContentCache = new Map(); // iri -> string | null (null means fetch/read failed)
91
+ const __logSemanticsCache = new Map(); // iri -> GraphTerm | null (null means parse failed)
92
+
93
+ function __stripFragment(iri) {
94
+ const i = iri.indexOf('#');
95
+ return i >= 0 ? iri.slice(0, i) : iri;
96
+ }
97
+
98
+ function __isHttpIri(iri) {
99
+ return typeof iri === 'string' && (iri.startsWith('http://') || iri.startsWith('https://'));
100
+ }
101
+
102
+ function __isFileIri(iri) {
103
+ return typeof iri === 'string' && iri.startsWith('file://');
104
+ }
105
+
106
+ function __fileIriToPath(fileIri) {
107
+ // Basic file:// URI decoding. Handles file:///abs/path and file://localhost/abs/path.
108
+ try {
109
+ const u = new URL(fileIri);
110
+ return decodeURIComponent(u.pathname);
111
+ } catch {
112
+ return decodeURIComponent(fileIri.replace(/^file:\/\//, ''));
113
+ }
114
+ }
115
+
116
+ function __readFileText(pathOrFileIri) {
117
+ const fs = require('fs');
118
+ let path = pathOrFileIri;
119
+ if (__isFileIri(pathOrFileIri)) path = __fileIriToPath(pathOrFileIri);
120
+ try {
121
+ return fs.readFileSync(path, { encoding: 'utf8' });
122
+ } catch {
123
+ return null;
124
+ }
125
+ }
126
+
127
+ function __fetchHttpTextViaSubprocess(url) {
128
+ const cp = require('child_process');
129
+ // Use a subprocess so this code remains synchronous without rewriting the whole reasoner to async.
130
+ const script = `
131
+ const url = process.argv[1];
132
+ const maxRedirects = 10;
133
+ function get(u, n) {
134
+ if (n > maxRedirects) { console.error('Too many redirects'); process.exit(3); }
135
+ let mod;
136
+ if (u.startsWith('https://')) mod = require('https');
137
+ else if (u.startsWith('http://')) mod = require('http');
138
+ else { console.error('Not http(s)'); process.exit(2); }
139
+
140
+ const { URL } = require('url');
141
+ const uu = new URL(u);
142
+ const opts = {
143
+ protocol: uu.protocol,
144
+ hostname: uu.hostname,
145
+ port: uu.port || undefined,
146
+ path: uu.pathname + uu.search,
147
+ headers: {
148
+ 'accept': 'text/n3, text/turtle, application/n-triples, application/n-quads, text/plain;q=0.1, */*;q=0.01',
149
+ 'user-agent': 'eyeling-log-builtins'
150
+ }
151
+ };
152
+ const req = mod.request(opts, (res) => {
153
+ const sc = res.statusCode || 0;
154
+ if (sc >= 300 && sc < 400 && res.headers && res.headers.location) {
155
+ const next = new URL(res.headers.location, u).toString();
156
+ res.resume();
157
+ return get(next, n + 1);
158
+ }
159
+ if (sc < 200 || sc >= 300) {
160
+ res.resume();
161
+ console.error('HTTP status ' + sc);
162
+ process.exit(4);
163
+ }
164
+ res.setEncoding('utf8');
165
+ let data = '';
166
+ res.on('data', (c) => { data += c; });
167
+ res.on('end', () => { process.stdout.write(data); });
168
+ });
169
+ req.on('error', (e) => { console.error(e && e.message ? e.message : String(e)); process.exit(5); });
170
+ req.end();
171
+ }
172
+ get(url, 0);
173
+ `;
174
+ const r = cp.spawnSync(process.execPath, ['-e', script, url], {
175
+ encoding: 'utf8',
176
+ maxBuffer: 32 * 1024 * 1024
177
+ });
178
+ if (r.status !== 0) return null;
179
+ return r.stdout;
180
+ }
181
+
182
+ function __derefTextSync(iriNoFrag) {
183
+ if (__logContentCache.has(iriNoFrag)) return __logContentCache.get(iriNoFrag);
184
+
185
+ let text = null;
186
+ if (__isHttpIri(iriNoFrag)) {
187
+ text = __fetchHttpTextViaSubprocess(iriNoFrag);
188
+ } else {
189
+ // Treat any non-http(s) IRI as a local path (including file://), for basic usability.
190
+ text = __readFileText(iriNoFrag);
191
+ }
192
+ __logContentCache.set(iriNoFrag, text);
193
+ return text;
194
+ }
195
+
196
+ function __parseSemanticsToFormula(text, baseIri) {
197
+ const toks = lex(text);
198
+ const parser = new Parser(toks);
199
+ if (typeof baseIri === 'string' && baseIri) parser.prefixes.setBase(baseIri);
200
+
201
+ const [_prefixes, triples, frules, brules] = parser.parseDocument();
202
+
203
+ const all = triples.slice();
204
+ const impliesPred = internIri(LOG_NS + 'implies');
205
+ const impliedByPred = internIri(LOG_NS + 'impliedBy');
206
+
207
+ // Represent top-level => / <= rules as triples between formula terms,
208
+ // so the returned formula can include them.
209
+ for (const r of frules) {
210
+ all.push(new Triple(new GraphTerm(r.premise), impliesPred, new GraphTerm(r.conclusion)));
211
+ }
212
+ for (const r of brules) {
213
+ all.push(new Triple(new GraphTerm(r.conclusion), impliedByPred, new GraphTerm(r.premise)));
214
+ }
215
+
216
+ return new GraphTerm(all);
217
+ }
218
+
219
+ function __derefSemanticsSync(iriNoFrag) {
220
+ if (__logSemanticsCache.has(iriNoFrag)) return __logSemanticsCache.get(iriNoFrag);
221
+
222
+ const text = __derefTextSync(iriNoFrag);
223
+ if (typeof text !== 'string') {
224
+ __logSemanticsCache.set(iriNoFrag, null);
225
+ return null;
226
+ }
227
+ try {
228
+ const formula = __parseSemanticsToFormula(text, iriNoFrag);
229
+ __logSemanticsCache.set(iriNoFrag, formula);
230
+ return formula;
231
+ } catch {
232
+ __logSemanticsCache.set(iriNoFrag, null);
233
+ return null;
234
+ }
235
+ }
236
+
237
+
85
238
  // Controls whether human-readable proof comments are printed.
86
239
  let proofCommentsEnabled = false;
87
240
  // Super restricted mode: disable *all* builtins except => / <= (log:implies / log:impliedBy)
@@ -4569,19 +4722,26 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
4569
4722
  const inputList = inputTerm.elems;
4570
4723
  if (!(predTerm instanceof Iri)) return [];
4571
4724
  const pred = internIri(predTerm.value);
4572
- if (!isBuiltinPred(pred)) return [];
4725
+
4726
+ // Allow mapping *any* predicate (not just builtins).
4727
+ // Semantics: for each input element `el`, collect *all* solutions of `el pred ?y`
4728
+ // (facts, rules, and builtins), in order, and concatenate them into the output list.
4729
+ // If an element has no solutions, it contributes nothing.
4573
4730
  if (!inputList.every((e) => isGroundTerm(e))) return [];
4574
4731
 
4575
4732
  const results = [];
4576
4733
  for (const el of inputList) {
4577
4734
  const yvar = new Var('_mapY');
4578
4735
  const goal2 = new Triple(el, pred, yvar);
4579
- const sols = evalBuiltin(goal2, subst, facts, backRules, depth + 1, varGen);
4580
- if (!sols.length) return [];
4581
- const yval = applySubstTerm(yvar, sols[0]);
4582
- if (yval instanceof Var) return [];
4583
- results.push(yval);
4736
+ const sols = proveGoals([goal2], subst, facts, backRules, depth + 1, [], varGen);
4737
+
4738
+ for (const sol of sols) {
4739
+ const yval = applySubstTerm(yvar, sol);
4740
+ if (yval instanceof Var) continue;
4741
+ results.push(yval);
4742
+ }
4584
4743
  }
4744
+
4585
4745
  const outList = new ListTerm(results);
4586
4746
  const s2 = unifyTerm(g.o, outList, subst);
4587
4747
  return s2 !== null ? [s2] : [];
@@ -4690,6 +4850,53 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
4690
4850
  return s2 !== null ? [s2] : [];
4691
4851
  }
4692
4852
 
4853
+ // log:content
4854
+ // Schema: $s+ log:content $o?
4855
+ // Dereferences $s and returns the online resource as an xsd:string.
4856
+ if (pv === LOG_NS + 'content') {
4857
+ const iri = iriValue(g.s);
4858
+ if (iri === null) return [];
4859
+ const docIri = __stripFragment(iri);
4860
+
4861
+ const text = __derefTextSync(docIri);
4862
+ if (typeof text !== 'string') return [];
4863
+
4864
+ const lit = internLiteral(`${JSON.stringify(text)}^^<${XSD_NS}string>`);
4865
+
4866
+ if (g.o instanceof Var) {
4867
+ const s2 = { ...subst };
4868
+ s2[g.o.name] = lit;
4869
+ return [s2];
4870
+ }
4871
+ if (g.o instanceof Blank) return [{ ...subst }];
4872
+
4873
+ const s2 = unifyTerm(g.o, lit, subst);
4874
+ return s2 !== null ? [s2] : [];
4875
+ }
4876
+
4877
+ // log:semantics
4878
+ // Schema: $s+ log:semantics $o?
4879
+ // Dereferences $s, parses the retrieved resource, and returns it as a formula.
4880
+ if (pv === LOG_NS + 'semantics') {
4881
+ const iri = iriValue(g.s);
4882
+ if (iri === null) return [];
4883
+ const docIri = __stripFragment(iri);
4884
+
4885
+ const formula = __derefSemanticsSync(docIri);
4886
+ if (!(formula instanceof GraphTerm)) return [];
4887
+
4888
+ if (g.o instanceof Var) {
4889
+ const s2 = { ...subst };
4890
+ s2[g.o.name] = formula;
4891
+ return [s2];
4892
+ }
4893
+ if (g.o instanceof Blank) return [{ ...subst }];
4894
+
4895
+ const s2 = unifyTerm(g.o, formula, subst);
4896
+ return s2 !== null ? [s2] : [];
4897
+ }
4898
+
4899
+
4693
4900
  // log:dtlit
4694
4901
  // Schema: ( $s.1? $s.2? )? log:dtlit $o?
4695
4902
  // true iff $o is a datatyped literal with string value $s.1 and datatype IRI $s.2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.7.3",
3
+ "version": "1.7.5",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [