eyeling 1.10.10 → 1.10.12

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/HANDBOOK.md CHANGED
@@ -26,6 +26,7 @@
26
26
  - [Chapter 15 — A worked example: Socrates, step by step](#ch15)
27
27
  - [Chapter 16 — Extending Eyeling (without breaking it)](#ch16)
28
28
  - [Epilogue](#epilogue)
29
+ - [Appendix A — Eyeling user notes](#app-a)
29
30
 
30
31
  ---
31
32
 
@@ -1700,3 +1701,133 @@ If you remember only one sentence from this handbook, make it this:
1700
1701
 
1701
1702
  Everything else is engineering detail — interesting, careful, sometimes subtle — but always in service of that core shape.
1702
1703
 
1704
+ ---
1705
+
1706
+ <a id="app-a"></a>
1707
+ ## Appendix A — Eyeling user notes
1708
+
1709
+ This appendix is a compact, user-facing reference for **running Eyeling** and **writing inputs that work well**.
1710
+ For deeper explanations and implementation details, follow the chapter links in each section.
1711
+
1712
+ ### A.1 Install and run
1713
+
1714
+ Eyeling is distributed as an npm package.
1715
+
1716
+ - Run without installing:
1717
+
1718
+ ```bash
1719
+ npx eyeling --help
1720
+ npx eyeling yourfile.n3
1721
+ ```
1722
+
1723
+ - Or install globally:
1724
+
1725
+ ```bash
1726
+ npm i -g eyeling
1727
+ eyeling yourfile.n3
1728
+ ```
1729
+
1730
+ See also: [Chapter 14 — Entry points: CLI, bundle exports, and npm API](#ch14).
1731
+
1732
+ ### A.2 What Eyeling prints
1733
+
1734
+ By default, Eyeling prints **newly derived forward facts** (the heads of fired `=>` rules), serialized as N3.
1735
+ It does **not** reprint your input facts.
1736
+
1737
+ For proof/explanation output and output modes, see:
1738
+ - [Chapter 13 — Printing, proofs, and the user-facing output](#ch13)
1739
+
1740
+ ### A.3 CLI quick reference
1741
+
1742
+ The authoritative list is always:
1743
+
1744
+ ```bash
1745
+ eyeling --help
1746
+ ```
1747
+
1748
+ Options:
1749
+ ```
1750
+ -a, --ast Print parsed AST as JSON and exit.
1751
+ -d, --deterministic-skolem Make log:skolem stable across reasoning runs.
1752
+ -e, --enforce-https Rewrite http:// IRIs to https:// for log dereferencing builtins.
1753
+ -h, --help Show this help and exit.
1754
+ -p, --proof-comments Enable proof explanations.
1755
+ -r, --strings Print log:outputString strings (ordered by key) instead of N3 output.
1756
+ -s, --super-restricted Disable all builtins except => and <=.
1757
+ -t, --stream Stream derived triples as soon as they are derived.
1758
+ -v, --version Print version and exit.
1759
+ ```
1760
+
1761
+ See also:
1762
+ - [Chapter 13 — Printing, proofs, and the user-facing output](#ch13)
1763
+ - [Chapter 12 — Dereferencing and web-like semantics](#ch12)
1764
+
1765
+ ### A.4 N3 syntax notes that matter in practice
1766
+
1767
+ Eyeling implements a practical N3 subset centered around facts and rules.
1768
+
1769
+ - A **fact** is a triple ending in `.`:
1770
+
1771
+ ```n3
1772
+ :alice :knows :bob .
1773
+ ```
1774
+
1775
+ - A **forward rule**:
1776
+
1777
+ ```n3
1778
+ { ?x :p ?y } => { ?y :q ?x } .
1779
+ ```
1780
+
1781
+ - A **backward rule**:
1782
+
1783
+ ```n3
1784
+ { ?x :ancestor ?z } <= { ?x :parent ?z } .
1785
+ ```
1786
+
1787
+ Quoted graphs/formulas use `{ ... }`. Inside a quoted formula, directive scope matters:
1788
+
1789
+ - `@prefix/@base` and `PREFIX/BASE` directives may appear at top level **or inside `{ ... }`**, and apply to the formula they occur in (formula-local scoping).
1790
+
1791
+ For the formal grammar, see the N3 spec grammar:
1792
+ - https://w3c.github.io/N3/spec/#grammar
1793
+
1794
+ See also:
1795
+ - [Chapter 4 — From characters to AST: lexing and parsing](#ch04)
1796
+
1797
+ ### A.5 Builtins
1798
+
1799
+ Eyeling supports a built-in “standard library” across namespaces like `log:`, `math:`, `string:`, `list:`, `time:`, `crypto:`.
1800
+
1801
+ References:
1802
+ - W3C N3 Built-ins overview: https://w3c.github.io/N3/reports/20230703/builtins.html
1803
+ - Eyeling implementation details: [Chapter 11 — Built-ins as a standard library](#ch11)
1804
+ - The shipped builtin catalogue: `eyeling-builtins.ttl` (in this repo)
1805
+
1806
+ If you are running untrusted inputs, consider `--super-restricted` to disable all builtins except implication.
1807
+
1808
+ ### A.6 Skolemization and `log:skolem`
1809
+
1810
+ When forward rule heads contain blank nodes (existentials), Eyeling replaces them with generated Skolem IRIs so derived facts are ground.
1811
+
1812
+ See:
1813
+ - [Chapter 9 — Forward chaining: saturation, skolemization, and meta-rules](#ch09)
1814
+
1815
+ ### A.7 Networking and `log:semantics`
1816
+
1817
+ `log:content`, `log:semantics`, and related builtins dereference IRIs and parse retrieved content.
1818
+ This is powerful, but it is also I/O.
1819
+
1820
+ See:
1821
+ - [Chapter 12 — Dereferencing and web-like semantics](#ch12)
1822
+
1823
+ Safety tip:
1824
+ - Use `--super-restricted` if you want to ensure *no* dereferencing (and no other builtins) can run.
1825
+
1826
+ ### A.8 Embedding Eyeling in JavaScript
1827
+
1828
+ If you depend on Eyeling as a library, the package exposes:
1829
+ - a CLI wrapper API (`reason(...)`), and
1830
+ - in-process engine entry points (via the bundle exports).
1831
+
1832
+ See:
1833
+ - [Chapter 14 — Entry points: CLI, bundle exports, and npm API](#ch14)
@@ -3,6 +3,7 @@
3
3
  # Example from Wout Slabbinck
4
4
  # ===========================
5
5
 
6
+ @prefix list: <http://www.w3.org/2000/10/swap/list#> .
6
7
  @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
7
8
  @prefix string: <http://www.w3.org/2000/10/swap/string#> .
8
9
  @prefix log: <http://www.w3.org/2000/10/swap/log#> .
@@ -12,16 +13,20 @@
12
13
 
13
14
  # uuid backward rule (component)
14
15
  {
15
- ?input :getUUID ?urnUuid
16
+ ?input :getUUID ?urnUuid
16
17
  }
17
- <=
18
+ <=
18
19
  {
20
+ # 0. add some skolem uri to introduce randomness when there are multiple runs with the same ID
21
+ ( "test" ) log:skolem ?skolem .
22
+ (?input (?skolem) ) list:append ?newList .
23
+
19
24
  # 1. Convert input to a stable string
20
- ?input :listToString ?inputString .
21
-
25
+ ?newList :listToString ?inputString .
26
+
22
27
  # 2. Generate a stable hash (works identically in 2026 eyeJS and Eyeling)
23
28
  ?inputString crypto:sha ?hash .
24
-
29
+
25
30
  # 3. Format the hash into a UUID-compliant structure (8-4-4-4-12)
26
31
  ( ?hash "^(.{8}).*$" ) string:scrape ?p1 .
27
32
  ( ?hash "^.{8}(.{4}).*$" ) string:scrape ?p2 .
@@ -29,7 +34,7 @@
29
34
  ( ?hash "^.{16}(.{4}).*$" ) string:scrape ?p4 .
30
35
  ( ?hash "^.{20}(.{12}).*$" ) string:scrape ?p5 .
31
36
  ( "urn:uuid:" ?p1 "-" ?p2 "-" ?p3 "-" ?p4 "-" ?p5 ) string:concatenation ?urnUuidString .
32
-
37
+
33
38
  # 4. Cast the final string to a URI
34
39
  ?urnUuid log:uri ?urnUuidString .
35
40
  } .
@@ -38,16 +43,16 @@
38
43
  { () :listToString "" } <= { } .
39
44
 
40
45
  # 2. Recursive Case: Process list using rdf:first and rdf:rest
41
- { ?list :listToString ?result } <= {
46
+ { ?list :listToString ?result } <= {
42
47
  ?list rdf:first ?head .
43
48
  ?list rdf:rest ?tail .
44
-
49
+
45
50
  # Convert the current URI to a string
46
51
  ?head log:uri ?headStr .
47
-
52
+
48
53
  # Recursively convert the tail of the list
49
54
  ?tail :listToString ?tailStr .
50
-
55
+
51
56
  # Combine head and tail strings
52
57
  ( ?headStr ?tailStr ) string:concatenation ?result .
53
58
  } .
@@ -55,11 +60,10 @@
55
60
  <test> a odrl:Policy .
56
61
  <lol> a odrl:Policy .
57
62
 
58
- {
63
+ {
59
64
  ?policy a odrl:Policy .
60
65
  ( ?policy ) :getUUID ?urnUuid .
61
66
  }
62
67
  =>
63
- {
64
- ?urnUuid <a> <b> .
65
- } .
68
+ { ?urnUuid <a> <b> } .
69
+
@@ -1,2 +1,2 @@
1
- <urn:uuid:a94a8fe5-ccb1-9ba6-1c4c-0873d391e987> <a> <b> .
2
- <urn:uuid:40392603-3d00-1b52-79df-37cbbe5287b7> <a> <b> .
1
+ <urn:uuid:ad88d7b6-5b37-4eeb-a044-7bca1ffc366c> <a> <b> .
2
+ <urn:uuid:37d41865-0910-9953-46c7-01359fbab6fa> <a> <b> .
package/eyeling.js CHANGED
@@ -493,6 +493,10 @@ function __fetchHttpTextViaSubprocess(url) {
493
493
  path: uu.pathname + uu.search,
494
494
  headers: {
495
495
  'accept': 'text/n3, text/turtle, application/n-triples, application/n-quads, text/plain;q=0.1, */*;q=0.01',
496
+ // Ask for an uncompressed response when possible; some servers send
497
+ // compressed bodies that are not valid UTF-8 text for the parser.
498
+ // We still handle common encodings below if they are returned anyway.
499
+ 'accept-encoding': 'identity',
496
500
  'user-agent': 'eyeling-log-builtins'
497
501
  }
498
502
  };
@@ -509,10 +513,29 @@ function __fetchHttpTextViaSubprocess(url) {
509
513
  console.error('HTTP status ' + sc);
510
514
  process.exit(4);
511
515
  }
512
- res.setEncoding('utf8');
513
- let data = '';
514
- res.on('data', (c) => { data += c; });
515
- res.on('end', () => { process.stdout.write(data); });
516
+ const chunks = [];
517
+ res.on('data', (c) => { chunks.push(c); });
518
+ res.on('end', () => {
519
+ try {
520
+ const { Buffer } = require('buffer');
521
+ const zlib = require('zlib');
522
+ const buf = Buffer.concat(chunks);
523
+ const enc = ((res.headers && res.headers['content-encoding']) || '').toString().toLowerCase();
524
+ let out = buf;
525
+ if (enc.includes('gzip')) out = zlib.gunzipSync(buf);
526
+ else if (enc.includes('deflate')) out = zlib.inflateSync(buf);
527
+ else if (enc.includes('br')) out = zlib.brotliDecompressSync(buf);
528
+ process.stdout.write(out.toString('utf8'));
529
+ } catch (e) {
530
+ // Best-effort fallback: treat as UTF-8.
531
+ try {
532
+ const { Buffer } = require('buffer');
533
+ process.stdout.write(Buffer.concat(chunks).toString('utf8'));
534
+ } catch {
535
+ process.exit(6);
536
+ }
537
+ }
538
+ });
516
539
  });
517
540
  req.on('error', (e) => { console.error(e && e.message ? e.message : String(e)); process.exit(5); });
518
541
  req.end();
@@ -7308,6 +7331,46 @@ class Parser {
7308
7331
  parseGraph() {
7309
7332
  const triples = [];
7310
7333
  while (this.peek().typ !== 'RBrace') {
7334
+ // N3 allows @prefix/@base and SPARQL-style PREFIX/BASE directives anywhere
7335
+ // outside of a triple. This includes inside quoted graph terms.
7336
+ // These directives affect parsing (prefix/base resolution) but do not emit triples.
7337
+ if (this.peek().typ === 'AtPrefix') {
7338
+ this.next();
7339
+ this.parsePrefixDirective();
7340
+ continue;
7341
+ }
7342
+ if (this.peek().typ === 'AtBase') {
7343
+ this.next();
7344
+ this.parseBaseDirective();
7345
+ continue;
7346
+ }
7347
+ if (
7348
+ this.peek().typ === 'Ident' &&
7349
+ typeof this.peek().value === 'string' &&
7350
+ this.peek().value.toLowerCase() === 'prefix' &&
7351
+ this.toks[this.pos + 1] &&
7352
+ this.toks[this.pos + 1].typ === 'Ident' &&
7353
+ typeof this.toks[this.pos + 1].value === 'string' &&
7354
+ this.toks[this.pos + 1].value.endsWith(':') &&
7355
+ this.toks[this.pos + 2] &&
7356
+ (this.toks[this.pos + 2].typ === 'IriRef' || this.toks[this.pos + 2].typ === 'Ident')
7357
+ ) {
7358
+ this.next();
7359
+ this.parseSparqlPrefixDirective();
7360
+ continue;
7361
+ }
7362
+ if (
7363
+ this.peek().typ === 'Ident' &&
7364
+ typeof this.peek().value === 'string' &&
7365
+ this.peek().value.toLowerCase() === 'base' &&
7366
+ this.toks[this.pos + 1] &&
7367
+ (this.toks[this.pos + 1].typ === 'IriRef' || this.toks[this.pos + 1].typ === 'Ident')
7368
+ ) {
7369
+ this.next();
7370
+ this.parseSparqlBaseDirective();
7371
+ continue;
7372
+ }
7373
+
7311
7374
  const left = this.parseTerm();
7312
7375
  if (this.peek().typ === 'OpImplies') {
7313
7376
  this.next();
package/lib/deref.js CHANGED
@@ -162,6 +162,10 @@ function __fetchHttpTextViaSubprocess(url) {
162
162
  path: uu.pathname + uu.search,
163
163
  headers: {
164
164
  'accept': 'text/n3, text/turtle, application/n-triples, application/n-quads, text/plain;q=0.1, */*;q=0.01',
165
+ // Ask for an uncompressed response when possible; some servers send
166
+ // compressed bodies that are not valid UTF-8 text for the parser.
167
+ // We still handle common encodings below if they are returned anyway.
168
+ 'accept-encoding': 'identity',
165
169
  'user-agent': 'eyeling-log-builtins'
166
170
  }
167
171
  };
@@ -178,10 +182,29 @@ function __fetchHttpTextViaSubprocess(url) {
178
182
  console.error('HTTP status ' + sc);
179
183
  process.exit(4);
180
184
  }
181
- res.setEncoding('utf8');
182
- let data = '';
183
- res.on('data', (c) => { data += c; });
184
- res.on('end', () => { process.stdout.write(data); });
185
+ const chunks = [];
186
+ res.on('data', (c) => { chunks.push(c); });
187
+ res.on('end', () => {
188
+ try {
189
+ const { Buffer } = require('buffer');
190
+ const zlib = require('zlib');
191
+ const buf = Buffer.concat(chunks);
192
+ const enc = ((res.headers && res.headers['content-encoding']) || '').toString().toLowerCase();
193
+ let out = buf;
194
+ if (enc.includes('gzip')) out = zlib.gunzipSync(buf);
195
+ else if (enc.includes('deflate')) out = zlib.inflateSync(buf);
196
+ else if (enc.includes('br')) out = zlib.brotliDecompressSync(buf);
197
+ process.stdout.write(out.toString('utf8'));
198
+ } catch (e) {
199
+ // Best-effort fallback: treat as UTF-8.
200
+ try {
201
+ const { Buffer } = require('buffer');
202
+ process.stdout.write(Buffer.concat(chunks).toString('utf8'));
203
+ } catch {
204
+ process.exit(6);
205
+ }
206
+ }
207
+ });
185
208
  });
186
209
  req.on('error', (e) => { console.error(e && e.message ? e.message : String(e)); process.exit(5); });
187
210
  req.end();
package/lib/parser.js CHANGED
@@ -461,6 +461,46 @@ class Parser {
461
461
  parseGraph() {
462
462
  const triples = [];
463
463
  while (this.peek().typ !== 'RBrace') {
464
+ // N3 allows @prefix/@base and SPARQL-style PREFIX/BASE directives anywhere
465
+ // outside of a triple. This includes inside quoted graph terms.
466
+ // These directives affect parsing (prefix/base resolution) but do not emit triples.
467
+ if (this.peek().typ === 'AtPrefix') {
468
+ this.next();
469
+ this.parsePrefixDirective();
470
+ continue;
471
+ }
472
+ if (this.peek().typ === 'AtBase') {
473
+ this.next();
474
+ this.parseBaseDirective();
475
+ continue;
476
+ }
477
+ if (
478
+ this.peek().typ === 'Ident' &&
479
+ typeof this.peek().value === 'string' &&
480
+ this.peek().value.toLowerCase() === 'prefix' &&
481
+ this.toks[this.pos + 1] &&
482
+ this.toks[this.pos + 1].typ === 'Ident' &&
483
+ typeof this.toks[this.pos + 1].value === 'string' &&
484
+ this.toks[this.pos + 1].value.endsWith(':') &&
485
+ this.toks[this.pos + 2] &&
486
+ (this.toks[this.pos + 2].typ === 'IriRef' || this.toks[this.pos + 2].typ === 'Ident')
487
+ ) {
488
+ this.next();
489
+ this.parseSparqlPrefixDirective();
490
+ continue;
491
+ }
492
+ if (
493
+ this.peek().typ === 'Ident' &&
494
+ typeof this.peek().value === 'string' &&
495
+ this.peek().value.toLowerCase() === 'base' &&
496
+ this.toks[this.pos + 1] &&
497
+ (this.toks[this.pos + 1].typ === 'IriRef' || this.toks[this.pos + 1].typ === 'Ident')
498
+ ) {
499
+ this.next();
500
+ this.parseSparqlBaseDirective();
501
+ continue;
502
+ }
503
+
464
504
  const left = this.parseTerm();
465
505
  if (this.peek().typ === 'OpImplies') {
466
506
  this.next();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.10.10",
3
+ "version": "1.10.12",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
@@ -40,6 +40,7 @@
40
40
  "test:api": "node test/api.test.js",
41
41
  "test:n3gen": "node test/n3gen.test.js",
42
42
  "test:examples": "node test/examples.test.js",
43
+ "test:manifest": "node test/manifest.test.js",
43
44
  "test:package": "node test/package.test.js",
44
45
  "test": "npm run build && npm run test:packlist && npm run test:api && npm run test:n3gen && npm run test:examples",
45
46
  "preversion": "npm test",