eyeling 1.7.3 → 1.7.4

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)
@@ -4690,6 +4843,53 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
4690
4843
  return s2 !== null ? [s2] : [];
4691
4844
  }
4692
4845
 
4846
+ // log:content
4847
+ // Schema: $s+ log:content $o?
4848
+ // Dereferences $s and returns the online resource as an xsd:string.
4849
+ if (pv === LOG_NS + 'content') {
4850
+ const iri = iriValue(g.s);
4851
+ if (iri === null) return [];
4852
+ const docIri = __stripFragment(iri);
4853
+
4854
+ const text = __derefTextSync(docIri);
4855
+ if (typeof text !== 'string') return [];
4856
+
4857
+ const lit = internLiteral(`${JSON.stringify(text)}^^<${XSD_NS}string>`);
4858
+
4859
+ if (g.o instanceof Var) {
4860
+ const s2 = { ...subst };
4861
+ s2[g.o.name] = lit;
4862
+ return [s2];
4863
+ }
4864
+ if (g.o instanceof Blank) return [{ ...subst }];
4865
+
4866
+ const s2 = unifyTerm(g.o, lit, subst);
4867
+ return s2 !== null ? [s2] : [];
4868
+ }
4869
+
4870
+ // log:semantics
4871
+ // Schema: $s+ log:semantics $o?
4872
+ // Dereferences $s, parses the retrieved resource, and returns it as a formula.
4873
+ if (pv === LOG_NS + 'semantics') {
4874
+ const iri = iriValue(g.s);
4875
+ if (iri === null) return [];
4876
+ const docIri = __stripFragment(iri);
4877
+
4878
+ const formula = __derefSemanticsSync(docIri);
4879
+ if (!(formula instanceof GraphTerm)) return [];
4880
+
4881
+ if (g.o instanceof Var) {
4882
+ const s2 = { ...subst };
4883
+ s2[g.o.name] = formula;
4884
+ return [s2];
4885
+ }
4886
+ if (g.o instanceof Blank) return [{ ...subst }];
4887
+
4888
+ const s2 = unifyTerm(g.o, formula, subst);
4889
+ return s2 !== null ? [s2] : [];
4890
+ }
4891
+
4892
+
4693
4893
  // log:dtlit
4694
4894
  // Schema: ( $s.1? $s.2? )? log:dtlit $o?
4695
4895
  // 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.4",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [