eyeling 1.7.2 → 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 +1 -1
- package/README.md +1 -1
- package/examples/output/reaching-out.n3 +12 -0
- package/examples/reaching-out.n3 +19 -0
- package/eyeling.js +215 -3
- package/package.json +1 -1
package/LICENSE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# MIT License
|
|
2
2
|
|
|
3
|
-
### Copyright 2021-
|
|
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
|
|
@@ -4934,6 +5134,11 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4934
5134
|
if (!scopeFacts) return []; // DELAY until snapshot exists
|
|
4935
5135
|
}
|
|
4936
5136
|
|
|
5137
|
+
// If sols is a blank node succeed without collecting/binding.
|
|
5138
|
+
if (listTerm instanceof Blank) {
|
|
5139
|
+
return [{ ...subst }];
|
|
5140
|
+
}
|
|
5141
|
+
|
|
4937
5142
|
const visited2 = [];
|
|
4938
5143
|
const sols = proveGoals(Array.from(clauseTerm.triples), {}, scopeFacts, scopeBackRules, depth + 1, visited2, varGen);
|
|
4939
5144
|
|
|
@@ -4995,8 +5200,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
4995
5200
|
if (pv === LOG_NS + 'uri') {
|
|
4996
5201
|
// Direction 1: subject is an IRI -> object is its string representation
|
|
4997
5202
|
if (g.s instanceof Iri) {
|
|
4998
|
-
const uriStr = g.s.value; // raw IRI string
|
|
4999
|
-
const lit = makeStringLiteral(uriStr); // "
|
|
5203
|
+
const uriStr = g.s.value; // raw IRI string
|
|
5204
|
+
const lit = makeStringLiteral(uriStr); // "..."
|
|
5000
5205
|
const s2 = unifyTerm(goal.o, lit, subst);
|
|
5001
5206
|
return s2 !== null ? [s2] : [];
|
|
5002
5207
|
}
|
|
@@ -5005,6 +5210,14 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
5005
5210
|
if (g.o instanceof Literal) {
|
|
5006
5211
|
const uriStr = termToJsString(g.o); // JS string from the literal
|
|
5007
5212
|
if (uriStr === null) return [];
|
|
5213
|
+
|
|
5214
|
+
// Reject strings that cannot be safely serialized as <...> in Turtle/N3.
|
|
5215
|
+
// Turtle IRIREF forbids control/space and these characters: < > " { } | ^ ` \
|
|
5216
|
+
// (and eyeling also prints IRIs starting with "_:" as blank-node labels)
|
|
5217
|
+
if (uriStr.startsWith('_:') || /[\u0000-\u0020<>"{}|^`\\]/.test(uriStr)) {
|
|
5218
|
+
return [];
|
|
5219
|
+
}
|
|
5220
|
+
|
|
5008
5221
|
const iri = internIri(uriStr);
|
|
5009
5222
|
const s2 = unifyTerm(goal.s, iri, subst);
|
|
5010
5223
|
return s2 !== null ? [s2] : [];
|
|
@@ -5906,7 +6119,6 @@ function printExplanation(df, prefixes) {
|
|
|
5906
6119
|
console.log('# ----------------------------------------------------------------------\n');
|
|
5907
6120
|
}
|
|
5908
6121
|
|
|
5909
|
-
|
|
5910
6122
|
function offsetToLineCol(text, offset) {
|
|
5911
6123
|
const chars = Array.from(text);
|
|
5912
6124
|
const n = Math.max(0, Math.min(typeof offset === 'number' ? offset : 0, chars.length));
|