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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
 
3
- ### Copyright 2021-2026 Jos De Roo, KNoWS office of IDLab, Ghent University - imec
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
- - [Eyeling streaming playground](https://eyereasoner.github.io/eyeling/stream)
21
-
22
- The playground runs `eyeling` client-side. You can:
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
- - edit an N3 program directly
25
- - load an N3 program from a URL (in the "Load N3 from URL" box or as ?url=...)
26
- - share a link with the program encoded in the URL fragment (`#...`)
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 + built-ins
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
- `eyeling` implements a pragmatic subset of common N3 builtin families and evaluates them during backward goal proving:
229
+ ## Builtins
227
230
 
228
- - **crypto**: `crypto:md5` `crypto:sha` `crypto:sha256` `crypto:sha512`
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
- const next = new URL(res.headers.location, u).toString();
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 xNum = parseNum(g.s);
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 { baseIri = null, proof = false, onDerived = null, includeInputFactsInClosure = true } = opts;
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
- return {
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));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.7.16",
3
+ "version": "1.7.18",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [