eyeling 1.7.16 → 1.7.18
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 +12 -14
- package/eyeling.js +46 -9
- package/package.json +1 -1
package/LICENSE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# MIT License
|
|
2
2
|
|
|
3
|
-
### Copyright
|
|
3
|
+
### Copyright 2025-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
|
@@ -17,13 +17,14 @@ A [Notation3 (N3)](https://notation3.org/) reasoner in **JavaScript**.
|
|
|
17
17
|
Try it here:
|
|
18
18
|
|
|
19
19
|
- [Eyeling playground](https://eyereasoner.github.io/eyeling/demo)
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
- Edit an N3 program directly.
|
|
21
|
+
- Load an N3 program from a URL (in the "Load N3 from URL" box or as ?url=...).
|
|
22
|
+
- Share a link with the program encoded in the URL fragment (`#...`).
|
|
23
23
|
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
24
|
+
- [Eyeling streaming playground](https://eyereasoner.github.io/eyeling/stream)
|
|
25
|
+
- Browse a Wikidata entity, load its facts, and see Eyeling’s **deductive closure appear incrementally** as triples are derived.
|
|
26
|
+
- Edit **N3 rules live** and re-run to watch how different inference rules change what gets derived.
|
|
27
|
+
- Demo **CORS-safe dynamic fetching**: derived “fetch requests” can trigger extra facts (e.g., Wikiquote extracts) that are injected and re-reasoned.
|
|
27
28
|
|
|
28
29
|
## Quick start
|
|
29
30
|
|
|
@@ -120,6 +121,7 @@ Options:
|
|
|
120
121
|
-s, --super-restricted Disable all builtins except => and <=.
|
|
121
122
|
-a, --ast Print parsed AST as JSON and exit.
|
|
122
123
|
--strings Print log:outputString strings (ordered by key) instead of N3 output.
|
|
124
|
+
--enforce-https Rewrite http:// IRIs to https:// for log dereferencing builtins.
|
|
123
125
|
```
|
|
124
126
|
|
|
125
127
|
By default, `eyeling`:
|
|
@@ -165,6 +167,7 @@ The CLI prints only newly derived forward facts.
|
|
|
165
167
|
- backward rules are indexed by head predicate
|
|
166
168
|
- the backward prover is **iterative** (explicit stack), so deep chains won’t blow the JS call stack
|
|
167
169
|
- for very deep backward chains, substitutions may be compactified (semantics-preserving) to avoid quadratic “copy a growing substitution object” behavior
|
|
170
|
+
- if the head is **structurally ground** and has no head blanks, one body proof suffices—and if the head triples are already known, we can skip the body proof.
|
|
168
171
|
|
|
169
172
|
## Blank nodes and quantification
|
|
170
173
|
|
|
@@ -202,7 +205,7 @@ Rules whose conclusion is `false` are treated as hard failures:
|
|
|
202
205
|
|
|
203
206
|
As soon as the premise is provable, `eyeling` exits with status code `2`.
|
|
204
207
|
|
|
205
|
-
## Syntax
|
|
208
|
+
## Syntax
|
|
206
209
|
|
|
207
210
|
`eyeling`’s parser targets (nearly) the full *Notation3 Language* grammar from the [W3C N3 Community Group spec](https://w3c.github.io/N3/spec/).
|
|
208
211
|
|
|
@@ -223,14 +226,9 @@ Commonly used N3/Turtle features:
|
|
|
223
226
|
- Resource paths (`!` and `^`)
|
|
224
227
|
- `#` line comments
|
|
225
228
|
|
|
226
|
-
|
|
229
|
+
## Builtins
|
|
227
230
|
|
|
228
|
-
|
|
229
|
-
- **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`
|
|
230
|
-
- **log**: `log:collectAllIn` `log:content` `log:dtlit` `log:equalTo` `log:forAllIn` `log:impliedBy` `log:implies` `log:includes` `log:langlit` `log:notEqualTo` `log:notIncludes` `log:outputString` `log:parsedAsN3` `log:rawType` `log:semantics` `log:semanticsOrError` `log:skolem` `log:trace` `log:uri`
|
|
231
|
-
- **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`
|
|
232
|
-
- **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`
|
|
233
|
-
- **time**: `time:day` `time:hour` `time:localTime` `time:minute` `time:month` `time:second` `time:timeZone` `time:year`
|
|
231
|
+
`eyeling` implements the builtins described in [eyeling-builtins](https://github.com/eyereasoner/eyeling/blob/main/eyeling-builtins.ttl).
|
|
234
232
|
|
|
235
233
|
## License
|
|
236
234
|
|
package/eyeling.js
CHANGED
|
@@ -104,6 +104,17 @@ const __logSemanticsCache = new Map(); // iri -> GraphTerm | null (null means pa
|
|
|
104
104
|
const __logSemanticsOrErrorCache = new Map(); // iri -> Term (GraphTerm | Literal) for log:semanticsOrError
|
|
105
105
|
const __logConclusionCache = new WeakMap(); // GraphTerm -> GraphTerm (deductive closure)
|
|
106
106
|
|
|
107
|
+
// When enabled, force http:// IRIs to be dereferenced as https://
|
|
108
|
+
// (CLI: --enforce-https, API: reasonStream({ enforceHttps: true })).
|
|
109
|
+
let enforceHttpsEnabled = false;
|
|
110
|
+
|
|
111
|
+
function __maybeEnforceHttps(iri) {
|
|
112
|
+
if (!enforceHttpsEnabled) return iri;
|
|
113
|
+
return typeof iri === 'string' && iri.startsWith('http://')
|
|
114
|
+
? 'https://' + iri.slice('http://'.length)
|
|
115
|
+
: iri;
|
|
116
|
+
}
|
|
117
|
+
|
|
107
118
|
// Environment detection (Node vs Browser/Worker).
|
|
108
119
|
// Eyeling is primarily synchronous, so we use sync XHR in browsers for log:content/log:semantics.
|
|
109
120
|
// Note: Browser fetches are subject to CORS; use CORS-enabled resources or a proxy.
|
|
@@ -150,9 +161,9 @@ function __fetchHttpTextSyncBrowser(url) {
|
|
|
150
161
|
|
|
151
162
|
function __normalizeDerefIri(iriNoFrag) {
|
|
152
163
|
// In Node, treat non-http as local path; leave as-is.
|
|
153
|
-
if (__IS_NODE) return iriNoFrag;
|
|
164
|
+
if (__IS_NODE) return __maybeEnforceHttps(iriNoFrag);
|
|
154
165
|
// In browsers/workers, resolve relative references against the page URL.
|
|
155
|
-
return __resolveBrowserUrl(iriNoFrag);
|
|
166
|
+
return __maybeEnforceHttps(__resolveBrowserUrl(iriNoFrag));
|
|
156
167
|
}
|
|
157
168
|
|
|
158
169
|
function __stripFragment(iri) {
|
|
@@ -195,9 +206,17 @@ function __fetchHttpTextViaSubprocess(url) {
|
|
|
195
206
|
const cp = require('child_process');
|
|
196
207
|
// Use a subprocess so this code remains synchronous without rewriting the whole reasoner to async.
|
|
197
208
|
const script = `
|
|
209
|
+
const enforceHttps = ${enforceHttpsEnabled ? 'true' : 'false'};
|
|
198
210
|
const url = process.argv[1];
|
|
199
211
|
const maxRedirects = 10;
|
|
212
|
+
function norm(u) {
|
|
213
|
+
if (enforceHttps && typeof u === 'string' && u.startsWith('http://')) {
|
|
214
|
+
return 'https://' + u.slice('http://'.length);
|
|
215
|
+
}
|
|
216
|
+
return u;
|
|
217
|
+
}
|
|
200
218
|
function get(u, n) {
|
|
219
|
+
u = norm(u);
|
|
201
220
|
if (n > maxRedirects) { console.error('Too many redirects'); process.exit(3); }
|
|
202
221
|
let mod;
|
|
203
222
|
if (u.startsWith('https://')) mod = require('https');
|
|
@@ -219,7 +238,8 @@ function __fetchHttpTextViaSubprocess(url) {
|
|
|
219
238
|
const req = mod.request(opts, (res) => {
|
|
220
239
|
const sc = res.statusCode || 0;
|
|
221
240
|
if (sc >= 300 && sc < 400 && res.headers && res.headers.location) {
|
|
222
|
-
|
|
241
|
+
let next = new URL(res.headers.location, u).toString();
|
|
242
|
+
next = norm(next);
|
|
223
243
|
res.resume();
|
|
224
244
|
return get(next, n + 1);
|
|
225
245
|
}
|
|
@@ -5700,9 +5720,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
5700
5720
|
// to mimic EYE's fm(...) formatting branch.
|
|
5701
5721
|
if (pv === LOG_NS + 'trace') {
|
|
5702
5722
|
const pref = __tracePrefixes || PrefixEnv.newDefault();
|
|
5703
|
-
const xStr = termToN3(g.s, pref);
|
|
5704
5723
|
|
|
5705
|
-
const
|
|
5724
|
+
const xStr = termToN3(g.s, pref);
|
|
5706
5725
|
const yStr = termToN3(g.o, pref);
|
|
5707
5726
|
|
|
5708
5727
|
__traceWriteLine(`${xStr} TRACE ${yStr}`);
|
|
@@ -6992,8 +7011,16 @@ function __collectOutputStringsFromFacts(facts, prefixes) {
|
|
|
6992
7011
|
}
|
|
6993
7012
|
|
|
6994
7013
|
function reasonStream(n3Text, opts = {}) {
|
|
6995
|
-
const {
|
|
6996
|
-
|
|
7014
|
+
const {
|
|
7015
|
+
baseIri = null,
|
|
7016
|
+
proof = false,
|
|
7017
|
+
onDerived = null,
|
|
7018
|
+
includeInputFactsInClosure = true,
|
|
7019
|
+
enforceHttps = false,
|
|
7020
|
+
} = opts;
|
|
7021
|
+
|
|
7022
|
+
const __oldEnforceHttps = enforceHttpsEnabled;
|
|
7023
|
+
enforceHttpsEnabled = !!enforceHttps;
|
|
6997
7024
|
proofCommentsEnabled = !!proof;
|
|
6998
7025
|
|
|
6999
7026
|
const toks = lex(n3Text);
|
|
@@ -7021,12 +7048,14 @@ function reasonStream(n3Text, opts = {}) {
|
|
|
7021
7048
|
|
|
7022
7049
|
const closureTriples = includeInputFactsInClosure ? facts : derived.map((d) => d.fact);
|
|
7023
7050
|
|
|
7024
|
-
|
|
7051
|
+
const __out = {
|
|
7025
7052
|
prefixes,
|
|
7026
7053
|
facts, // saturated closure (Triple[])
|
|
7027
7054
|
derived, // DerivedFact[]
|
|
7028
7055
|
closureN3: closureTriples.map((t) => tripleToN3(t, prefixes)).join('\n'),
|
|
7029
7056
|
};
|
|
7057
|
+
enforceHttpsEnabled = __oldEnforceHttps;
|
|
7058
|
+
return __out;
|
|
7030
7059
|
}
|
|
7031
7060
|
|
|
7032
7061
|
// Minimal export surface for Node + browser/worker
|
|
@@ -7057,7 +7086,8 @@ function main() {
|
|
|
7057
7086
|
` -n, --no-proof-comments Disable proof explanations (default).\n` +
|
|
7058
7087
|
` -s, --super-restricted Disable all builtins except => and <=.\n` +
|
|
7059
7088
|
` -a, --ast Print parsed AST as JSON and exit.\n` +
|
|
7060
|
-
` --strings Print log:outputString strings (ordered by key) instead of N3 output.\n
|
|
7089
|
+
` --strings Print log:outputString strings (ordered by key) instead of N3 output.\n` +
|
|
7090
|
+
` --enforce-https Rewrite http:// IRIs to https:// for log dereferencing builtins.\n`;
|
|
7061
7091
|
(toStderr ? console.error : console.log)(msg);
|
|
7062
7092
|
}
|
|
7063
7093
|
|
|
@@ -7080,6 +7110,11 @@ function main() {
|
|
|
7080
7110
|
|
|
7081
7111
|
const outputStringsMode = argv.includes('--strings');
|
|
7082
7112
|
|
|
7113
|
+
// --enforce-https: rewrite http:// -> https:// for log dereferencing builtins
|
|
7114
|
+
if (argv.includes('--enforce-https')) {
|
|
7115
|
+
enforceHttpsEnabled = true;
|
|
7116
|
+
}
|
|
7117
|
+
|
|
7083
7118
|
// --proof-comments / -p: enable proof explanations
|
|
7084
7119
|
if (argv.includes('--proof-comments') || argv.includes('-p')) {
|
|
7085
7120
|
proofCommentsEnabled = true;
|
|
@@ -7127,6 +7162,8 @@ function main() {
|
|
|
7127
7162
|
toks = lex(text);
|
|
7128
7163
|
const parser = new Parser(toks);
|
|
7129
7164
|
[prefixes, triples, frules, brules] = parser.parseDocument();
|
|
7165
|
+
// Make the parsed prefixes available to log:trace output (CLI path)
|
|
7166
|
+
__tracePrefixes = prefixes;
|
|
7130
7167
|
} catch (e) {
|
|
7131
7168
|
if (e && e.name === 'N3SyntaxError') {
|
|
7132
7169
|
console.error(formatN3SyntaxError(e, text, path));
|