eyeling 1.27.5 → 1.27.7

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/README.md CHANGED
@@ -1,22 +1,1240 @@
1
1
  # eyeling
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/eyeling.svg)](https://www.npmjs.com/package/eyeling)
4
3
  [![DOI](https://img.shields.io/badge/DOI-10.5281%2Fzenodo.19068086-blue.svg)](https://doi.org/10.5281/zenodo.19068086)
5
4
 
6
- ![Eyeling](eyeling.png)
7
-
8
5
  A compact [Notation3 (N3)](https://notation3.org/) reasoner in **JavaScript**.
9
6
 
7
+ Eyeling is characterized by:
8
+
9
+ - **Notation3 reasoning in a small JavaScript package** — facts, quoted formulas, and N3 rules are parsed and reasoned over directly.
10
+ - **Forward and backward chaining** — `=>` rules derive new facts, while `<=` rules act as goal-directed definitions.
11
+ - **Backward proving inside forward rules** — forward-rule bodies are solved with the backward engine, so rules can use derived predicates and built-ins without materializing everything first.
12
+ - **Built-ins in rule bodies** — N3 programs can combine logical rules with computations such as math, string, list, time, and web-oriented predicates.
13
+ - **Streaming RDF Messages** — supports RDF Messages streams, enabling Eyeling to fit into streaming RDF pipelines.
14
+ - **Node.js, npm, and browser use** — run it from the command line, call it from JavaScript, or use the browser-oriented bundle.
15
+ - **RDF-JS interoperability** — use N3 text, RDF-JS quads, datasets, or Eyeling’s own AST-level API.
16
+
17
+
18
+ This README is the primary guide to using, extending, and maintaining Eyeling.
19
+
20
+ | Project fact | Value |
21
+ |---|---|
22
+ | Package | `eyeling` |
23
+ | Runtime | Node.js `>=18` |
24
+ | License | MIT |
25
+ | Main entry point | `index.js` |
26
+ | CLI binary | `eyeling` |
27
+ | Browser entry point | `eyeling/browser` |
28
+
29
+ Eyeling is designed for people who want a small, inspectable reasoner that can run N3 rules in Node.js, the browser, tests, and RDF-oriented data pipelines.
30
+
31
+ ## Project links
32
+
33
+ - [Playground](https://eyereasoner.github.io/eyeling/playground)
34
+ - [Conformance report](https://codeberg.org/phochste/notation3tests/src/branch/main/reports/report.md)
35
+
36
+ ---
37
+
38
+ ## Table of contents
39
+
40
+ 1. [What Eyeling is](#what-eyeling-is)
41
+ 2. [Quick start](#quick-start)
42
+ 3. [Core concepts](#core-concepts)
43
+ 4. [Command-line interface](#command-line-interface)
44
+ 5. [JavaScript API](#javascript-api)
45
+ 6. [RDF-JS integration](#rdf-js-integration)
46
+ 7. [RDF compatibility mode and RDF 1.2](#rdf-compatibility-mode-and-rdf-12)
47
+ 8. [RDF Message Logs](#rdf-message-logs)
48
+ 9. [Built-ins](#built-ins)
49
+ 10. [Custom built-ins](#custom-built-ins)
50
+ 11. [Reasoning model](#reasoning-model)
51
+ 12. [Architecture](#architecture)
52
+ 13. [Repository layout](#repository-layout)
53
+ 14. [Examples guide](#examples-guide)
54
+ 15. [Testing and quality checks](#testing-and-quality-checks)
55
+ 16. [Development workflow](#development-workflow)
56
+ 17. [Publishing and release notes](#publishing-and-release-notes)
57
+ 18. [Troubleshooting](#troubleshooting)
58
+ 19. [Security and operational notes](#security-and-operational-notes)
59
+ 20. [Glossary](#glossary)
60
+
61
+ ---
62
+
63
+ ## What Eyeling is
64
+
65
+ Eyeling is a compact [Notation3](https://notation3.org/) reasoner implemented in JavaScript.
66
+
67
+ It accepts facts and rules written in N3-style syntax, computes the logical consequences of those rules, and emits newly derived results. It can be used as:
68
+
69
+ - a command-line tool through `npx eyeling` or the `eyeling` binary;
70
+ - a CommonJS API from Node.js through `require('eyeling')`;
71
+ - a browser/worker API through `eyeling/browser`;
72
+ - an RDF-JS adapter for applications that work with quads and data factories;
73
+ - a streaming tool for RDF Message Logs.
74
+
75
+ Eyeling is intentionally small and dependency-light. The source tree is organized as a miniature compiler and inference engine: lexer, parser, term model, rule normalization, built-ins, forward chaining, backward proving, printing, RDF-JS adapters, and CLI wiring.
76
+
77
+ ### What Eyeling is not
78
+
79
+ Eyeling is not a database, a triple store, or a full web crawler. It is a reasoner. It reads a finite set of sources, reasons over them, and returns derived output. For persistent storage, indexing at scale, access control, or distributed querying, pair Eyeling with the appropriate storage and application layer.
80
+
81
+ ---
82
+
10
83
  ## Quick start
11
84
 
85
+ ### Run without installing
86
+
12
87
  ```bash
13
88
  echo '@prefix : <http://example.org/> .
14
89
  :Socrates a :Man .
15
90
  { ?x a :Man } => { ?x a :Mortal } .' | npx eyeling
16
91
  ```
17
92
 
18
- ## Read more
93
+ Expected output:
19
94
 
20
- - [Handbook](https://eyereasoner.github.io/eyeling/HANDBOOK)
21
- - [Playground](https://eyereasoner.github.io/eyeling/playground)
22
- - [Conformance report](https://codeberg.org/phochste/notation3tests/src/branch/main/reports/report.md)
95
+ ```n3
96
+ @prefix : <http://example.org/> .
97
+
98
+ :Socrates a :Mortal .
99
+ ```
100
+
101
+ By default, the CLI prints newly derived triples, not the original input facts.
102
+
103
+ ### Install in a project
104
+
105
+ ```bash
106
+ npm install eyeling
107
+ ```
108
+
109
+ Use it from JavaScript:
110
+
111
+ ```js
112
+ const { reason } = require('eyeling');
113
+
114
+ const output = reason({}, `
115
+ @prefix : <http://example.org/> .
116
+
117
+ :Socrates a :Man .
118
+ { ?x a :Man } => { ?x a :Mortal } .
119
+ `);
120
+
121
+ console.log(output);
122
+ ```
123
+
124
+ ### Run an included example
125
+
126
+ ```bash
127
+ node eyeling.js examples/socrates.n3
128
+ ```
129
+
130
+ For proof output:
131
+
132
+ ```bash
133
+ node eyeling.js --proof examples/socrates.n3
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Core concepts
139
+
140
+ ### Facts
141
+
142
+ A fact is an RDF-like triple:
143
+
144
+ ```n3
145
+ :Socrates a :Human .
146
+ :Human rdfs:subClassOf :Mortal .
147
+ ```
148
+
149
+ Each triple has a subject, predicate, and object. Eyeling supports IRIs, prefixed names, literals, blank nodes, variables, lists, and quoted formulas in the supported N3 subset.
150
+
151
+ ### Forward rules
152
+
153
+ A forward rule uses `=>`:
154
+
155
+ ```n3
156
+ { ?s a ?class . ?class rdfs:subClassOf ?super . }
157
+ =>
158
+ { ?s a ?super . } .
159
+ ```
160
+
161
+ Read it as: if the body is provable, derive the head.
162
+
163
+ ### Backward rules
164
+
165
+ A backward rule uses `<=`:
166
+
167
+ ```n3
168
+ { ?x :moreInterestingThan ?y . }
169
+ <=
170
+ { ?x math:greaterThan ?y . } .
171
+ ```
172
+
173
+ Read it as: to prove the head, prove the body.
174
+
175
+ Backward rules are especially useful for derived predicates, reusable definitions, and built-in-backed computations that should not be materialized until needed.
176
+
177
+ ### Built-ins
178
+
179
+ Built-ins are predicates implemented by the engine. Examples include:
180
+
181
+ ```n3
182
+ (2 3 5) math:sum ?total .
183
+ "Hello Eyeling" string:contains "Eye" .
184
+ (1 2 3) list:length ?n .
185
+ ```
186
+
187
+ Built-ins are used in rule bodies to test conditions, bind variables, inspect formulas, format strings, work with lists, perform numeric operations, or dereference content.
188
+
189
+ ### Query output
190
+
191
+ Eyeling supports `log:query` as an output-selection mechanism. A program can derive a full closure internally and emit only the results selected by query-style rules.
192
+
193
+ For human-readable text output, use `log:outputString`:
194
+
195
+ ```n3
196
+ @prefix : <http://example.org/> .
197
+ @prefix log: <http://www.w3.org/2000/10/swap/log#> .
198
+
199
+ :run :value "hello" .
200
+
201
+ { :run :value ?text }
202
+ =>
203
+ { :run log:outputString ?text } .
204
+ ```
205
+
206
+ The CLI renders the `log:outputString` values directly:
207
+
208
+ ```text
209
+ hello
210
+ ```
211
+
212
+ ---
213
+
214
+ ## Command-line interface
215
+
216
+ The CLI is exposed as `eyeling` and backed by `bin/eyeling.cjs`, which loads the bundled `eyeling.js` runtime.
217
+
218
+ ```bash
219
+ eyeling [options] [file-or-url.n3|- ...]
220
+ ```
221
+
222
+ When no file is given and stdin is piped, Eyeling reads from stdin. When multiple inputs are given, Eyeling parses each source separately, merges the ASTs, and reasons once over the merged document.
223
+
224
+ ### Common commands
225
+
226
+ Run a local file:
227
+
228
+ ```bash
229
+ eyeling examples/socrates.n3
230
+ ```
231
+
232
+ Pipe a program from stdin:
233
+
234
+ ```bash
235
+ cat examples/socrates.n3 | eyeling
236
+ ```
237
+
238
+ Use explicit stdin:
239
+
240
+ ```bash
241
+ eyeling - < examples/socrates.n3
242
+ ```
243
+
244
+ Run facts and rules from separate files:
245
+
246
+ ```bash
247
+ eyeling facts.n3 rules.n3
248
+ ```
249
+
250
+ Print proof explanations:
251
+
252
+ ```bash
253
+ eyeling --proof examples/socrates.n3
254
+ ```
255
+
256
+ Print the parsed AST:
257
+
258
+ ```bash
259
+ eyeling --ast examples/socrates.n3
260
+ ```
261
+
262
+ Enable RDF/TriG compatibility mode:
263
+
264
+ ```bash
265
+ eyeling --rdf data.trig rules.n3
266
+ ```
267
+
268
+ Process an RDF Message Log one message at a time:
269
+
270
+ ```bash
271
+ eyeling --rdf --stream-messages rules.n3 messages.trig
272
+ ```
273
+
274
+ ### CLI options
275
+
276
+ | Option | Meaning |
277
+ |---|---|
278
+ | `-a`, `--ast` | Print the parsed AST as JSON and exit. |
279
+ | `--builtin <module.js>` | Load a custom built-in module. Repeatable. |
280
+ | `-d`, `--deterministic-skolem` | Make `log:skolem` stable across reasoning runs. |
281
+ | `-e`, `--enforce-https` | Rewrite `http://` IRIs to `https://` for log dereferencing built-ins. |
282
+ | `-h`, `--help` | Show help and exit. |
283
+ | `-p`, `--proof` | Enable proof explanations. |
284
+ | `-r`, `--rdf` | Enable RDF/TriG input and output compatibility. |
285
+ | `--stream-messages` | Process RDF Message Logs one message at a time under `--rdf`. |
286
+ | `-s`, `--super-restricted` | Disable all built-ins except implication handling. |
287
+ | `-t`, `--stream` | Stream derived triples as soon as they are derived. |
288
+ | `-v`, `--version` | Print the package version and exit. |
289
+
290
+ ### Output behavior
291
+
292
+ The CLI has three important output modes:
293
+
294
+ 1. **Default mode**: derive everything first, then print newly derived triples.
295
+ 2. **Streaming mode**: with `--stream`, print derived triples as they are found.
296
+ 3. **Query mode**: when `log:query` rules are present, derive the full closure, then print only query-selected triples.
297
+
298
+ When `log:outputString` appears in the output set, Eyeling writes the string values directly to stdout. This is useful for examples that generate Markdown, reports, or concise verdicts.
299
+
300
+ ### Exit codes
301
+
302
+ A rule that derives `false` triggers Eyeling's inference fuse and exits with code `65`. JavaScript API calls expose the same code on thrown errors where applicable.
303
+
304
+ ---
305
+
306
+ ## JavaScript API
307
+
308
+ Import from the package root for Node.js:
309
+
310
+ ```js
311
+ const {
312
+ reason,
313
+ reasonStream,
314
+ reasonRdfJs,
315
+ rdfjs,
316
+ registerBuiltin,
317
+ unregisterBuiltin,
318
+ registerBuiltinModule,
319
+ loadBuiltinModule,
320
+ listBuiltinIris,
321
+ INFERENCE_FUSE_EXIT_CODE,
322
+ } = require('eyeling');
323
+ ```
324
+
325
+ ### `reason(options, input)`
326
+
327
+ `reason()` is the simplest API. It runs the bundled reasoner in a child process and returns stdout as a string.
328
+
329
+ ```js
330
+ const { reason } = require('eyeling');
331
+
332
+ const out = reason({ proof: false }, `
333
+ @prefix : <http://example.org/> .
334
+ :a :p :b .
335
+ { ?s :p ?o } => { ?s :q ?o } .
336
+ `);
337
+
338
+ console.log(out);
339
+ ```
340
+
341
+ Useful options:
342
+
343
+ | Option | Description |
344
+ |---|---|
345
+ | `proof` | Include proof explanations when true. Defaults to false for API output. |
346
+ | `rdf` | Enable RDF/TriG compatibility mode. |
347
+ | `args` | Extra CLI-style arguments. |
348
+ | `maxBuffer` | Child-process output buffer limit. |
349
+ | `builtinModules` | Custom built-in module path or paths. |
350
+
351
+ `reason()` accepts N3 text, supported RDF-JS input objects, AST bundles, and multi-source inputs.
352
+
353
+ ### Multi-source input
354
+
355
+ Use `sources` when facts and rules should be parsed as separate documents and then merged:
356
+
357
+ ```js
358
+ const { reason } = require('eyeling');
359
+
360
+ const output = reason({}, {
361
+ sources: [
362
+ '@prefix : <http://example.org/> .\n:Socrates a :Man .\n',
363
+ '@prefix : <http://example.org/> .\n{ ?x a :Man } => { ?x a :Mortal } .\n',
364
+ ],
365
+ });
366
+ ```
367
+
368
+ Parsing sources separately prevents accidental blank-node label collisions across files.
369
+
370
+ ### `reasonStream(input, options)`
371
+
372
+ `reasonStream()` runs in process and returns a structured result:
373
+
374
+ ```js
375
+ const { reasonStream } = require('eyeling');
376
+
377
+ const result = reasonStream(`
378
+ @prefix : <http://example.org/> .
379
+ :a :p :b .
380
+ { ?s :p ?o } => { ?s :q ?o } .
381
+ `, {
382
+ includeInputFactsInClosure: false,
383
+ onDerived({ triple }) {
384
+ console.log('derived:', triple);
385
+ },
386
+ });
387
+
388
+ console.log(result.closureN3);
389
+ ```
390
+
391
+ Result shape:
392
+
393
+ | Field | Meaning |
394
+ |---|---|
395
+ | `prefixes` | Prefix environment used for parsing and printing. |
396
+ | `facts` | Saturated closure as internal triples. |
397
+ | `derived` | Derived facts with explanation metadata. |
398
+ | `queryMode` | True when `log:query` output selection was used. |
399
+ | `queryTriples` | Query-selected output triples. |
400
+ | `queryDerived` | Query-selected derived facts with metadata. |
401
+ | `closureN3` | Rendered closure or selected output as N3/TriG-compatible text. |
402
+ | `closureQuads` | RDF-JS quads when `rdfjs: true` is used. |
403
+ | `queryQuads` | RDF-JS query output quads when available. |
404
+
405
+ Useful options:
406
+
407
+ | Option | Description |
408
+ |---|---|
409
+ | `baseIri` | Base IRI for relative IRI resolution. |
410
+ | `proof` | Include proof explanations in `closureN3`. |
411
+ | `includeInputFactsInClosure` | Include original facts in `closureN3`. Defaults to true. |
412
+ | `onDerived` | Callback called for derived or query-selected output. |
413
+ | `enforceHttps` | Apply HTTPS rewriting for dereferencing built-ins. |
414
+ | `rdf` | Enable RDF/TriG compatibility mode. |
415
+ | `rdfjs` | Also emit RDF-JS quads where conversion is possible. |
416
+ | `dataFactory` | Custom RDF-JS DataFactory. |
417
+ | `skipUnsupportedRdfJs` | Skip N3-only terms when producing RDF-JS quads. |
418
+ | `builtinModules` | Register custom built-ins before reasoning. |
419
+
420
+ ### `reasonRdfJs(input, options)`
421
+
422
+ `reasonRdfJs()` returns an async iterable of derived RDF-JS quads:
423
+
424
+ ```js
425
+ const { reasonRdfJs } = require('eyeling');
426
+
427
+ for await (const quad of reasonRdfJs({
428
+ n3: `
429
+ @prefix : <http://example.org/> .
430
+ :a :p :b .
431
+ { ?s :p ?o } => { ?s :q ?o } .
432
+ `,
433
+ })) {
434
+ console.log(quad.subject.value, quad.predicate.value, quad.object.value);
435
+ }
436
+ ```
437
+
438
+ Use `skipUnsupportedRdfJs: true` when your rules may derive N3-only terms such as quoted formulas that cannot be represented as ordinary RDF-JS quads.
439
+
440
+ ### Browser API
441
+
442
+ Use the browser entry point in browser or worker runtimes:
443
+
444
+ ```js
445
+ import eyeling, { reasonStream } from 'eyeling/browser';
446
+
447
+ const result = reasonStream(`
448
+ @prefix : <http://example.org/> .
449
+ :a :p :b .
450
+ { ?s :p ?o } => { ?s :q ?o } .
451
+ `);
452
+
453
+ console.log(result.closureN3);
454
+ console.log(eyeling.version);
455
+ ```
456
+
457
+ The browser entry loads `dist/browser/eyeling.browser.js` and exposes the API through `globalThis.eyeling`.
458
+
459
+ ---
460
+
461
+ ## RDF-JS integration
462
+
463
+ Eyeling includes a lightweight RDF-JS DataFactory and adapters for supported RDF-JS terms and quads.
464
+
465
+ ```js
466
+ const { reasonStream, rdfjs } = require('eyeling');
467
+
468
+ const ex = 'http://example.org/';
469
+
470
+ const input = {
471
+ quads: [
472
+ rdfjs.quad(
473
+ rdfjs.namedNode(`${ex}Socrates`),
474
+ rdfjs.namedNode(`${ex}type`),
475
+ rdfjs.namedNode(`${ex}Man`),
476
+ ),
477
+ ],
478
+ n3: `
479
+ @prefix : <http://example.org/> .
480
+ { ?x :type :Man } => { ?x :type :Mortal } .
481
+ `,
482
+ };
483
+
484
+ const result = reasonStream(input, { rdfjs: true });
485
+ console.log(result.closureQuads);
486
+ ```
487
+
488
+ Supported RDF-JS input terms include named nodes, blank nodes, literals, variables, default graph terms, and default-graph quads. Named-graph input quads are rejected clearly unless handled through N3/TriG compatibility mode.
489
+
490
+ Use RDF-JS when you want Eyeling to sit inside a JavaScript RDF pipeline. Use raw N3 input when you need N3-only features such as quoted formulas or N3 rules represented directly in source text.
491
+
492
+ ---
493
+
494
+ ## RDF compatibility mode and RDF 1.2
495
+
496
+ RDF compatibility mode is enabled with `--rdf` on the CLI or `{ rdf: true }` in the API.
497
+
498
+ Use it when working with RDF/TriG-oriented syntax and RDF 1.2 constructs:
499
+
500
+ ```bash
501
+ eyeling --rdf input.trig rules.n3
502
+ ```
503
+
504
+ ```js
505
+ const result = reasonStream(input, { rdf: true });
506
+ ```
507
+
508
+ In RDF mode, Eyeling accepts and serializes RDF-compatible forms such as:
509
+
510
+ - uppercase `PREFIX` and `BASE` directives;
511
+ - TriG-style datasets;
512
+ - RDF 1.2 triple terms where supported;
513
+ - RDF 1.2 annotation syntax after objects;
514
+ - RDF Message Log replay syntax under the message-log mode described below.
515
+
516
+ RDF 1.2 triple terms require explicit RDF compatibility mode. This protects ordinary N3 users from accidentally mixing parser modes.
517
+
518
+ ---
519
+
520
+ ## RDF Message Logs
521
+
522
+ Eyeling supports RDF Message Logs, including parser-level message delimiters, under RDF compatibility mode.
523
+
524
+ A message log starts with a message version and separates messages with `MESSAGE`:
525
+
526
+ ```trig
527
+ VERSION "1.2-messages"
528
+ PREFIX : <https://example.org/messages#>
529
+
530
+ :obs1 :value 21 .
531
+
532
+ MESSAGE
533
+
534
+ # Empty heartbeat message.
535
+
536
+ MESSAGE
537
+
538
+ :obs2 :value 22 .
539
+ ```
540
+
541
+ Run the message log with rules:
542
+
543
+ ```bash
544
+ eyeling --rdf rules.n3 messages.trig
545
+ ```
546
+
547
+ For one-message-at-a-time processing:
548
+
549
+ ```bash
550
+ eyeling --rdf --stream-messages rules.n3 messages.trig
551
+ ```
552
+
553
+ Eyeling materializes a replay view under the `eymsg:` vocabulary:
554
+
555
+ ```n3
556
+ @prefix eymsg: <https://eyereasoner.github.io/eyeling/vocab/message#> .
557
+ ```
558
+
559
+ The replay view includes stream resources, ordered envelopes, offsets, payload kind, and payload graphs. Rules can inspect each payload graph with formula-aware built-ins such as `log:includes`, preserving message boundaries instead of treating all messages as one merged graph.
560
+
561
+ Important semantics:
562
+
563
+ - Message boundaries are explicit.
564
+ - Empty heartbeat messages are valid.
565
+ - Payloads are contextualized by message envelope.
566
+ - Blank-node labels are scoped per message.
567
+ - Remote text/plain RDF Message Logs can be streamed over HTTP by the CLI.
568
+
569
+ See the included examples:
570
+
571
+ ```bash
572
+ eyeling -r examples/rdf-messages.n3 examples/input/rdf-messages.trig
573
+ eyeling -r examples/rdf-message-flow.n3 examples/input/rdf-message-flow.trig
574
+ eyeling -r --stream-messages examples/rdf-message-flow.n3 examples/input/rdf-message-flow.trig
575
+ ```
576
+
577
+ ---
578
+
579
+ ## Built-ins
580
+
581
+ Eyeling implements SWAP-style built-ins across these namespaces:
582
+
583
+ | Namespace | Examples | Purpose |
584
+ |---|---|---|
585
+ | `crypto:` | `sha`, `md5`, `sha256`, `sha512` | Hashing. |
586
+ | `math:` | `sum`, `product`, `difference`, `greaterThan`, `sin`, `cos` | Numeric computation and comparison. |
587
+ | `time:` | `year`, `month`, `day`, `localTime` | `xsd:dateTime` helpers. |
588
+ | `list:` | `first`, `rest`, `member`, `length`, `map`, `sort` | N3 and RDF list operations. |
589
+ | `rdf:` | `first`, `rest` | Aliases for list traversal over RDF collections. |
590
+ | `log:` | `includes`, `notIncludes`, `semantics`, `conclusion`, `query`, `outputString` | Formula, dereferencing, query, and output operations. |
591
+ | `string:` | `contains`, `matches`, `replace`, `format`, `length` | String tests and transformations. |
592
+
593
+ The authoritative built-in catalog is `eyeling-builtins.ttl`. It documents each built-in as RDF, including its kind:
594
+
595
+ - `ex:Test`: succeeds or fails without necessarily binding variables;
596
+ - `ex:Function`: computes an output and may bind variables;
597
+ - `ex:Relation`: unification-based relation;
598
+ - `ex:Generator`: may yield multiple solutions;
599
+ - `ex:IO`: may dereference or parse external content;
600
+ - `ex:Meta`: operates on formulas or types;
601
+ - `ex:SideEffect`: produces output.
602
+
603
+ ### Numeric built-ins
604
+
605
+ Numeric built-ins support common XSD numeric literals. Integer-oriented operations use `BigInt` where possible, with safety limits to avoid accidental memory exhaustion. Date/time comparisons and timestamp arithmetic are supported for relevant operations.
606
+
607
+ Example:
608
+
609
+ ```n3
610
+ @prefix : <http://example.org/> .
611
+ @prefix math: <http://www.w3.org/2000/10/swap/math#> .
612
+
613
+ {
614
+ (2 3 5) math:sum ?total .
615
+ }
616
+ =>
617
+ {
618
+ :calculation :total ?total .
619
+ } .
620
+ ```
621
+
622
+ ### List built-ins
623
+
624
+ Eyeling supports both native N3 list terms and materialized RDF collections. Anonymous `rdf:first`/`rdf:rest` collections can be materialized into list terms, while named list nodes keep their identity.
625
+
626
+ Example:
627
+
628
+ ```n3
629
+ @prefix : <http://example.org/> .
630
+ @prefix list: <http://www.w3.org/2000/10/swap/list#> .
631
+
632
+ {
633
+ ("red" "green" "blue") list:length ?n .
634
+ }
635
+ =>
636
+ {
637
+ :palette :size ?n .
638
+ } .
639
+ ```
640
+
641
+ ### Formula and log built-ins
642
+
643
+ Formula-aware built-ins make Eyeling useful for meta-reasoning:
644
+
645
+ ```n3
646
+ @prefix : <http://example.org/> .
647
+ @prefix log: <http://www.w3.org/2000/10/swap/log#> .
648
+
649
+ :doc :graph { :alice :knows :bob } .
650
+
651
+ {
652
+ :doc :graph ?g .
653
+ ?g log:includes { :alice :knows ?person } .
654
+ }
655
+ =>
656
+ {
657
+ :doc :mentions ?person .
658
+ } .
659
+ ```
660
+
661
+ `log:semantics`, `log:content`, and related built-ins may dereference sources. Use `--enforce-https` or `{ enforceHttps: true }` in environments where HTTP-to-HTTPS rewriting is required.
662
+
663
+ ---
664
+
665
+ ## Custom built-ins
666
+
667
+ Custom built-ins let applications extend Eyeling without modifying the core engine.
668
+
669
+ ### CLI module
670
+
671
+ Create `hello-builtin.js`:
672
+
673
+ ```js
674
+ module.exports = ({ registerBuiltin, internLiteral, unifyTerm, terms }) => {
675
+ const { Var } = terms;
676
+
677
+ registerBuiltin('http://example.org/custom#hello', ({ goal, subst }) => {
678
+ const value = internLiteral('"world"');
679
+
680
+ if (goal.o instanceof Var) {
681
+ return [{ ...subst, [goal.o.name]: value }];
682
+ }
683
+
684
+ const next = unifyTerm(goal.o, value, subst);
685
+ return next === null ? [] : [next];
686
+ });
687
+ };
688
+ ```
689
+
690
+ Use it:
691
+
692
+ ```bash
693
+ eyeling --builtin ./hello-builtin.js program.n3
694
+ ```
695
+
696
+ Program:
697
+
698
+ ```n3
699
+ @prefix : <http://example.org/> .
700
+ @prefix cb: <http://example.org/custom#> .
701
+
702
+ { :x cb:hello ?value }
703
+ =>
704
+ { :x :value ?value } .
705
+ ```
706
+
707
+ Expected derived output:
708
+
709
+ ```n3
710
+ :x :value "world" .
711
+ ```
712
+
713
+ ### API registration
714
+
715
+ ```js
716
+ const { registerBuiltin, reason } = require('eyeling');
717
+
718
+ registerBuiltin('http://example.org/custom#always', ({ subst }) => [subst]);
719
+
720
+ const out = reason({}, `
721
+ @prefix : <http://example.org/> .
722
+ @prefix cb: <http://example.org/custom#> .
723
+
724
+ { :x cb:always true } => { :x :ok true } .
725
+ `);
726
+ ```
727
+
728
+ ### Module shapes
729
+
730
+ `registerBuiltinModule()` accepts these shapes:
731
+
732
+ ```js
733
+ // Function form
734
+ module.exports = (api) => {
735
+ api.registerBuiltin('http://example.org/custom#p', handler);
736
+ };
737
+ ```
738
+
739
+ ```js
740
+ // Object with register()
741
+ module.exports = {
742
+ register(api) {
743
+ api.registerBuiltin('http://example.org/custom#p', handler);
744
+ },
745
+ };
746
+ ```
747
+
748
+ ```js
749
+ // Builtin map
750
+ module.exports = {
751
+ 'http://example.org/custom#p': handler,
752
+ };
753
+ ```
754
+
755
+ ```js
756
+ // Builtin map under builtins/default
757
+ module.exports = {
758
+ builtins: {
759
+ 'http://example.org/custom#p': handler,
760
+ },
761
+ };
762
+ ```
763
+
764
+ Handlers must return an array of substitution deltas. Return an empty array for failure and `[subst]` for success without new bindings.
765
+
766
+ ---
767
+
768
+ ## Reasoning model
769
+
770
+ Eyeling combines forward saturation with backward proving.
771
+
772
+ At a high level:
773
+
774
+ ```text
775
+ parse sources
776
+
777
+ normalize terms, rules, and lists
778
+
779
+ initialize fact set
780
+
781
+ repeat until no new facts appear:
782
+ for each forward rule:
783
+ prove the rule body with the backward prover
784
+ for each solution:
785
+ instantiate and add the rule head
786
+ activate any newly derived rules
787
+ stop if false is derived
788
+
789
+ render derived output, query-selected output, proof output, or strings
790
+ ```
791
+
792
+ ### Forward chaining
793
+
794
+ Forward rules are the outer control loop. They gradually saturate the fact set by adding ground consequences.
795
+
796
+ ```n3
797
+ { ?x :parent ?y }
798
+ =>
799
+ { ?x :ancestor ?y } .
800
+
801
+ { ?x :parent ?y . ?y :ancestor ?z }
802
+ =>
803
+ { ?x :ancestor ?z } .
804
+ ```
805
+
806
+ ### Backward proving
807
+
808
+ The backward prover solves rule bodies. It can match current facts, use backward rules, and invoke built-ins. This lets forward rules depend on predicates that are computed on demand.
809
+
810
+ ```n3
811
+ { ?x :interestingComparedWith ?y }
812
+ <=
813
+ { ?x math:greaterThan ?y } .
814
+
815
+ { 5 :interestingComparedWith 3 }
816
+ =>
817
+ { :example :works true } .
818
+ ```
819
+
820
+ ### Dynamic rules
821
+
822
+ Eyeling treats top-level `log:implies` and `log:impliedBy` as rule forms and can activate derived implication facts as live rules during reasoning. This supports programs that derive rules as part of their logic.
823
+
824
+ ### Duplicate control and fixpoints
825
+
826
+ Derived facts are indexed and deduplicated. Saturation stops when no rule can add a new fact. This avoids echoing already-known facts and keeps recursive programs such as transitive closure finite when the closure is finite.
827
+
828
+ ### Negative entailment and the inference fuse
829
+
830
+ If a rule derives `false`, Eyeling treats that as a reasoning failure and exits with `INFERENCE_FUSE_EXIT_CODE`, which is `65`.
831
+
832
+ ```n3
833
+ { :policy :violated true } => false .
834
+ ```
835
+
836
+ Use this pattern for integrity constraints, policy failures, and tests that should fail when an unwanted condition is provable.
837
+
838
+ ---
839
+
840
+ ## Architecture
841
+
842
+ Eyeling is organized as a set of small modules under `lib/` plus packaging and browser glue.
843
+
844
+ ### Execution pipeline
845
+
846
+ ```text
847
+ input text / RDF-JS / AST
848
+
849
+
850
+ lib/lexer.js tokenization and RDF compatibility normalization
851
+
852
+
853
+ lib/parser.js N3/TriG-ish parser to internal AST
854
+
855
+
856
+ lib/multisource.js source-level parsing and AST merging
857
+
858
+
859
+ lib/prelude.js term model, triples, rules, prefixes, namespaces
860
+
861
+
862
+ lib/engine.js forward chain, backward prover, rule activation
863
+
864
+ ├── lib/builtins.js built-in predicates and custom registry
865
+ ├── lib/deref.js dereferencing helpers
866
+ ├── lib/skolem.js skolemization helpers
867
+ ├── lib/time.js date/time helpers
868
+ └── lib/trace.js tracing support
869
+
870
+
871
+ lib/printing.js / lib/explain.js
872
+
873
+
874
+ CLI output, API result, proof document, RDF-JS quads, or browser result
875
+ ```
876
+
877
+ ### Key modules
878
+
879
+ | Path | Responsibility |
880
+ |---|---|
881
+ | `index.js` | Public Node package API. Wraps CLI bundle for `reason()` and exports in-process APIs. |
882
+ | `bin/eyeling.cjs` | Executable CLI shim. |
883
+ | `lib/entry.js` | Bundle entry that exposes public APIs and selected playground internals. |
884
+ | `lib/cli.js` | CLI argument handling, source loading, syntax errors, stream message mode. |
885
+ | `lib/engine.js` | Core reasoning engine, proof collection, stream APIs, RDF-JS output hooks. |
886
+ | `lib/builtins.js` | Built-in predicates, custom built-in registry, helper API. |
887
+ | `lib/lexer.js` | Lexer and compatibility normalization. |
888
+ | `lib/parser.js` | Parser for supported N3/RDF syntax. |
889
+ | `lib/prelude.js` | Core term classes, namespaces, triples, rules, prefix environment. |
890
+ | `lib/multisource.js` | Parse several documents independently and merge their ASTs. |
891
+ | `lib/rdfjs.js` | RDF-JS DataFactory and conversion adapters. |
892
+ | `lib/printing.js` | N3/TriG-compatible rendering. |
893
+ | `lib/explain.js` | Proof and explanation rendering. |
894
+ | `lib/deref.js` | Dereferencing support for log built-ins. |
895
+ | `lib/skolem.js` | Deterministic skolem term construction. |
896
+ | `lib/time.js` | Date/time parsing and formatting helpers. |
897
+ | `tools/bundle.js` | Builds `eyeling.js` and the browser bundle. |
898
+
899
+ ### Public surfaces
900
+
901
+ Eyeling deliberately has a small public API:
902
+
903
+ - `reason()` for simple Node use;
904
+ - `reasonStream()` for structured in-process reasoning;
905
+ - `reasonRdfJs()` for async RDF-JS output;
906
+ - `rdfjs` for a built-in data factory;
907
+ - custom built-in registration functions;
908
+ - `INFERENCE_FUSE_EXIT_CODE` for callers that need to distinguish logical failure from ordinary runtime failure.
909
+
910
+ Everything else should be treated as internal unless explicitly documented.
911
+
912
+ ---
913
+
914
+ ## Repository layout
915
+
916
+ ```text
917
+ .
918
+ ├── README.md Project overview, user guide, and maintainer guide
919
+ ├── LICENSE.md MIT license
920
+ ├── package.json Package metadata, scripts, exports, engine range
921
+ ├── index.js Node API entry
922
+ ├── index.d.ts TypeScript declarations
923
+ ├── eyeling.js Bundled Node runtime and CLI target
924
+ ├── eyeling-builtins.ttl Built-in catalog in RDF
925
+ ├── bin/ CLI executable shim
926
+ ├── lib/ Source modules
927
+ ├── dist/browser/ Browser bundle and ESM wrapper
928
+ ├── examples/ N3 examples, RDF message inputs, and generated decks
929
+ ├── spec/ RDF 1.2 parser test adapter
930
+ ├── test/ API, built-in, example, package, playground, and stream tests
931
+ ├── tools/ Build tooling
932
+ ├── playground.html Browser playground
933
+ └── demo.html Simple browser demo
934
+ ```
935
+
936
+ The package publishes the source modules, tests, examples, bundled runtime, browser bundle, declarations, README, license, and built-in catalog.
937
+
938
+ ---
939
+
940
+ ## Examples guide
941
+
942
+ The repository contains more than two hundred N3 examples under `examples/`, plus RDF Message input files under `examples/input/` and presentation-oriented Markdown decks under `examples/deck/`.
943
+
944
+ ### Good first examples
945
+
946
+ | Example | What it demonstrates |
947
+ |---|---|
948
+ | `examples/socrates.n3` | Basic class inference. |
949
+ | `examples/backward.n3` | Backward rule proving with a math built-in. |
950
+ | `examples/age.n3` | Literal propagation. |
951
+ | `examples/family-cousins.n3` | Multi-hop relational inference. |
952
+ | `examples/dijkstra.n3` | Graph/path reasoning. |
953
+ | `examples/list-map.n3` | List processing. |
954
+ | `examples/string-builtins-tests.n3` | String built-ins. |
955
+ | `examples/math-builtins-tests.n3` | Numeric built-ins. |
956
+ | `examples/rdf-messages.n3` | RDF Message Log replay. |
957
+
958
+ ### Running all examples through tests
959
+
960
+ ```bash
961
+ npm run test:examples
962
+ ```
963
+
964
+ Proof-only example checks:
965
+
966
+ ```bash
967
+ npm run test:examples:proof
968
+ ```
969
+
970
+ ### Output-generating examples
971
+
972
+ Many advanced examples use `log:outputString` to emit Markdown reports. This keeps the logical derivation and presentation in one N3 program. Run them from the CLI and redirect stdout when needed:
973
+
974
+ ```bash
975
+ eyeling examples/rdf-message-flow.n3 examples/input/rdf-message-flow.trig > report.md
976
+ ```
977
+
978
+ ---
979
+
980
+ ## Testing and quality checks
981
+
982
+ Package scripts are defined in `package.json`.
983
+
984
+ ### Core scripts
985
+
986
+ | Script | Purpose |
987
+ |---|---|
988
+ | `npm run build` | Rebuild `eyeling.js` and browser artifacts. |
989
+ | `npm run test:packlist` | Verify the package file list. |
990
+ | `npm run test:api` | Run API and stream-message API tests. |
991
+ | `npm run test:builtins` | Validate custom built-in contracts. |
992
+ | `npm run test:examples` | Run example corpus tests. |
993
+ | `npm run test:examples:proof` | Run proof-output checks for examples. |
994
+ | `npm run test:manifest` | Validate example/test manifest expectations. |
995
+ | `npm run test:playground` | Check playground serving headers. |
996
+ | `npm run test:package` | Verify package-level behavior. |
997
+ | `npm run rdf12` | Run RDF 1.2 Turtle, N-Triples, N-Quads, and TriG syntax suites. |
998
+ | `npm test` | Build and run the full suite. |
999
+
1000
+ ### Recommended local check before committing
1001
+
1002
+ Use the full test suite as the authoritative project check:
1003
+
1004
+ ```bash
1005
+ npm test
1006
+ ```
1007
+
1008
+ For quick API-level feedback during development:
1009
+
1010
+ ```bash
1011
+ npm run build
1012
+ npm run test:api
1013
+ npm run test:builtins
1014
+ ```
1015
+
1016
+ ### What the tests cover
1017
+
1018
+ The tests exercise:
1019
+
1020
+ - parsing edge cases and syntax errors;
1021
+ - forward and backward chaining;
1022
+ - recursion and transitive closure;
1023
+ - duplicate suppression;
1024
+ - negative entailment and fuse behavior;
1025
+ - proof output;
1026
+ - lists and RDF collection materialization;
1027
+ - `log:outputString` rendering;
1028
+ - AST output;
1029
+ - multi-source parsing;
1030
+ - RDF-JS input and output;
1031
+ - custom built-ins;
1032
+ - RDF 1.2 compatibility mode;
1033
+ - RDF Message Log parsing and streaming;
1034
+ - package exports and browser playground behavior.
1035
+
1036
+ ---
1037
+
1038
+ ## Development workflow
1039
+
1040
+ ### Prerequisites
1041
+
1042
+ - Node.js 18 or newer.
1043
+ - npm.
1044
+
1045
+ ### Install dependencies
1046
+
1047
+ ```bash
1048
+ npm install
1049
+ ```
1050
+
1051
+ ### Build bundles
1052
+
1053
+ ```bash
1054
+ npm run build
1055
+ ```
1056
+
1057
+ This regenerates:
1058
+
1059
+ - `eyeling.js`
1060
+ - `dist/browser/eyeling.browser.js`
1061
+ - `dist/browser/index.mjs`
1062
+
1063
+ ### Edit source
1064
+
1065
+ Most logic lives in `lib/`. Prefer small, focused changes:
1066
+
1067
+ 1. Update or add tests first when fixing behavior.
1068
+ 2. Modify the relevant source module.
1069
+ 3. Rebuild bundles.
1070
+ 4. Run the targeted test script.
1071
+ 5. Run `npm test` before committing or publishing.
1072
+
1073
+ ### Add a built-in
1074
+
1075
+ 1. Implement behavior in `lib/builtins.js` or as an external custom built-in.
1076
+ 2. Add contract tests in `test/builtins.test.js` or behavior tests in `test/api.test.js`.
1077
+ 3. Document the built-in in `eyeling-builtins.ttl`.
1078
+ 4. Add at least one runnable example if the built-in is user-facing.
1079
+
1080
+ ### Add a parser feature
1081
+
1082
+ 1. Add focused parser tests for accepted and rejected syntax.
1083
+ 2. Update `lib/lexer.js` and/or `lib/parser.js`.
1084
+ 3. Ensure output rendering in `lib/printing.js` remains valid.
1085
+ 4. Add RDF compatibility tests if the feature is RDF/TriG-specific.
1086
+
1087
+ ### Add an example
1088
+
1089
+ A good example should include:
1090
+
1091
+ - clear prefixes;
1092
+ - a short comment block explaining the scenario;
1093
+ - facts separated from rules when practical;
1094
+ - deterministic output;
1095
+ - `log:outputString` only when human-readable report output is intended;
1096
+ - a test expectation when the example is part of the checked corpus.
1097
+
1098
+ ---
1099
+
1100
+ ## Publishing and release notes
1101
+
1102
+ The package metadata publishes Eyeling to npm with:
1103
+
1104
+ - CommonJS root export;
1105
+ - browser export;
1106
+ - TypeScript declarations;
1107
+ - CLI binary;
1108
+ - examples, tests, bundles, and built-in catalog.
1109
+
1110
+ The repository includes GitHub workflows for pages, npm publishing, RDF 1.2 compliance, and releases.
1111
+
1112
+ Before versioning or publishing:
1113
+
1114
+ ```bash
1115
+ npm test
1116
+ npm version patch # or minor / major
1117
+ ```
1118
+
1119
+ The `preversion` script runs the full test suite. The `postversion` script pushes the branch and tags.
1120
+
1121
+ ---
1122
+
1123
+ ## Troubleshooting
1124
+
1125
+ ### The CLI prints help instead of running
1126
+
1127
+ Eyeling prints help when no positional input is provided and stdin is interactive. Provide a file, URL, `-`, or pipe data into stdin.
1128
+
1129
+ ```bash
1130
+ eyeling examples/socrates.n3
1131
+ cat examples/socrates.n3 | eyeling
1132
+ eyeling - < examples/socrates.n3
1133
+ ```
1134
+
1135
+ ### RDF 1.2 syntax fails to parse
1136
+
1137
+ Enable RDF compatibility mode:
1138
+
1139
+ ```bash
1140
+ eyeling --rdf input.trig
1141
+ ```
1142
+
1143
+ or:
1144
+
1145
+ ```js
1146
+ reasonStream(input, { rdf: true });
1147
+ ```
1148
+
1149
+ ### `--stream-messages` fails immediately
1150
+
1151
+ `--stream-messages` requires RDF mode and cannot be combined with `--ast`, `--stream`, or proof output.
1152
+
1153
+ Use:
1154
+
1155
+ ```bash
1156
+ eyeling --rdf --stream-messages rules.n3 messages.trig
1157
+ ```
1158
+
1159
+ ### A rule did not fire
1160
+
1161
+ Check the following:
1162
+
1163
+ - Are prefixes identical between facts and rules?
1164
+ - Did the rule body require a fact that is only available after another rule fires?
1165
+ - Is a built-in being used in the correct direction?
1166
+ - Does the rule depend on RDF mode syntax without `--rdf`?
1167
+ - Are blank nodes scoped as intended across multiple source files?
1168
+ - Is the desired output an input fact rather than a newly derived fact?
1169
+
1170
+ For debugging, try `--proof` or create a smaller reproduction with only the relevant facts and rule.
1171
+
1172
+ ### Output contains no input facts
1173
+
1174
+ Default CLI output prints newly derived facts. Use `reasonStream()` with `includeInputFactsInClosure: true` when you need the complete closure including input facts.
1175
+
1176
+ ### RDF-JS conversion fails
1177
+
1178
+ Some N3 terms cannot be represented as ordinary RDF-JS quads. Use:
1179
+
1180
+ ```js
1181
+ reasonStream(input, { rdfjs: true, skipUnsupportedRdfJs: true });
1182
+ ```
1183
+
1184
+ or keep the N3 rendering in `closureN3`.
1185
+
1186
+ ### A program exits with code 65
1187
+
1188
+ A rule derived `false`. This is usually an integrity constraint or policy failure, not a parser error.
1189
+
1190
+ ### Remote dereferencing behaves unexpectedly
1191
+
1192
+ Use `--enforce-https` or `{ enforceHttps: true }` when policy requires HTTPS. Also ensure the runtime has network access and that the remote source returns a supported RDF/N3-compatible content type.
1193
+
1194
+ ---
1195
+
1196
+ ## Security and operational notes
1197
+
1198
+ ### Custom built-ins execute JavaScript
1199
+
1200
+ Custom built-ins are code. Only load built-in modules you trust. In server environments, do not allow arbitrary users to provide `--builtin` paths or dynamically registered handlers.
1201
+
1202
+ ### Dereferencing can access remote content
1203
+
1204
+ `log:semantics`, `log:content`, and related built-ins may dereference IRIs. Treat this like network I/O:
1205
+
1206
+ - use HTTPS where possible;
1207
+ - avoid dereferencing untrusted URLs in privileged environments;
1208
+ - apply external network restrictions when running untrusted programs;
1209
+ - consider `--super-restricted` for highly constrained execution.
1210
+
1211
+ ### Reasoning can be computationally expensive
1212
+
1213
+ Recursive rules, generators, large joins, and high-cardinality facts can produce large closures. Eyeling includes duplicate suppression and safety caps for some operations, but application-level limits are still important for untrusted workloads.
1214
+
1215
+ ### Proof output may reveal source details
1216
+
1217
+ Proof output can include source file labels and line references. Avoid exposing proof documents directly when source paths or input details are sensitive.
1218
+
1219
+ ---
1220
+
1221
+ ## Glossary
1222
+
1223
+ | Term | Meaning |
1224
+ |---|---|
1225
+ | AST | Abstract syntax tree produced by parsing N3/RDF input. |
1226
+ | Backward chaining | Goal-directed proving: to prove a goal, prove supporting facts/rules/built-ins. |
1227
+ | Built-in | Predicate implemented by JavaScript code rather than by input facts alone. |
1228
+ | Closure | The set of facts available after reasoning reaches a fixpoint. |
1229
+ | Derived fact | A fact added by a rule, not directly present as an input fact. |
1230
+ | Fact | A triple asserted in the input or derived during reasoning. |
1231
+ | Forward chaining | Saturation strategy that repeatedly applies rules to derive new facts. |
1232
+ | Formula | A quoted graph-like N3 term that can be inspected by formula built-ins. |
1233
+ | IRI | Internationalized Resource Identifier used to identify resources and predicates. |
1234
+ | N3 | Notation3, an RDF-compatible notation with rules and formulas. |
1235
+ | Prefix environment | Mapping from short prefixes such as `:` or `math:` to full IRI bases. |
1236
+ | RDF-JS | JavaScript interface conventions for RDF terms, quads, and data factories. |
1237
+ | RDF Message Log | Ordered record of RDF messages separated by message delimiters. |
1238
+ | Skolemization | Replacing existential blank nodes with generated identifiers. |
1239
+ | Substitution | Mapping from variables to terms during proof search. |
1240
+ | Triple | Subject-predicate-object statement. |