eyeling 1.11.4 → 1.11.5

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.
Files changed (2) hide show
  1. package/HANDBOOK.md +76 -19
  2. package/package.json +1 -1
package/HANDBOOK.md CHANGED
@@ -109,22 +109,24 @@ If you want to follow the code in the same order Eyeling “thinks”, read:
109
109
  2. `lib/lexer.js` — N3/Turtle-ish tokenization.
110
110
  3. `lib/parser.js` — parsing tokens into triples, formulas, and rules.
111
111
  4. `lib/rules.js` — small rule “compiler passes” (blank lifting, constraint delaying).
112
- 5. `lib/engine.js` — the core engine:
112
+ 5. `lib/engine.js` — the core inference engine:
113
113
  - equality + alpha equivalence for formulas
114
114
  - unification + substitutions
115
115
  - indexing facts and backward rules
116
- - backward goal proving (`proveGoals`)
117
- - forward saturation (`forwardChain`)
118
- - built-ins (`evalBuiltin`)
116
+ - backward goal proving (`proveGoals`) and forward saturation (`forwardChain`)
119
117
  - scoped-closure machinery (for `log:*In` and includes tests)
120
- - explanations and output construction
121
118
  - tracing hooks (`lib/trace.js`, `log:trace`)
122
119
  - time helpers for `time:*` built-ins (`lib/time.js`)
123
120
  - deterministic Skolem IDs (head existentials + `log:skolem`) (`lib/skolem.js`)
124
- 6. `lib/deref.js` — synchronous dereferencing for `log:content` / `log:semantics`.
125
- 7. `lib/printing.js` conversion back to N3 text.
126
- 8. `lib/cli.js` + `lib/entry.js` command-line wiring and bundle entry exports.
127
- 9. `index.js` the npm API wrapper (spawns the bundled CLI synchronously).
121
+ 6. `lib/builtins.js` — builtin predicate evaluation plus shared literal/number/string/list helpers:
122
+ - `makeBuiltins(deps)` dependency-injects engine hooks (unification, proving, deref, …)
123
+ - exports `evalBuiltin(...)` and `isBuiltinPred(...)` back to the engine
124
+ - includes `materializeRdfLists(...)`, a small pre-pass that rewrites *anonymous* `rdf:first`/`rdf:rest` linked lists into concrete N3 list terms so `list:*` builtins can work uniformly
125
+ 7. `lib/explain.js` — proof comments + `log:outputString` aggregation (fact ordering and pretty output).
126
+ 8. `lib/deref.js` — synchronous dereferencing for `log:content` / `log:semantics` (used by builtins and engine).
127
+ 9. `lib/printing.js` — conversion back to N3 text.
128
+ 10. `lib/cli.js` + `lib/entry.js` — command-line wiring and bundle entry exports.
129
+ 11. `index.js` — the npm API wrapper (spawns the bundled CLI synchronously).
128
130
 
129
131
  This is almost literally a tiny compiler pipeline:
130
132
 
@@ -299,6 +301,30 @@ Eyeling treats these as “constraints” and moves them to the *end* of a forwa
299
301
 
300
302
  It’s not logically necessary, but it improves the chance that constraints run with variables already grounded, reducing wasted search.
301
303
 
304
+ ### 5.3 Materializing anonymous RDF collections into N3 list terms
305
+
306
+ Many N3 documents encode lists using RDF’s linked-list vocabulary:
307
+
308
+ ```n3
309
+ _:c rdf:first :a.
310
+ _:c rdf:rest _:d.
311
+ _:d rdf:first :b.
312
+ _:d rdf:rest rdf:nil.
313
+ ```
314
+
315
+ Eyeling supports *both* representations:
316
+
317
+ * **Concrete N3 lists** like `(:a :b)` are parsed as `ListTerm([...])` directly.
318
+ * **RDF collections** using `rdf:first`/`rdf:rest` can be traversed by list-aware builtins.
319
+
320
+ To make list handling simpler and faster, Eyeling runs a small pre-pass called `materializeRdfLists(...)` (implemented in `lib/builtins.js` and invoked by the CLI/entry code). It:
321
+
322
+ * scans the **input triples** for well‑formed `rdf:first`/`rdf:rest` chains,
323
+ * **rewrites only anonymous (blank-node) list nodes** into concrete `ListTerm(...)`,
324
+ * and applies that rewrite consistently across the input triple set and all rule premises/heads.
325
+
326
+ Why only blank nodes? Named list nodes (IRIs) must keep their identity, because some programs treat them as addressable resources; Eyeling leaves those as `rdf:first`/`rdf:rest` graphs so list builtins can still walk them when needed.
327
+
302
328
  ---
303
329
 
304
330
  <a id="ch06"></a>
@@ -446,7 +472,7 @@ Eyeling’s order is intentional: built-ins often bind variables cheaply; rules
446
472
 
447
473
  ### 8.3 Built-ins: return *deltas*, not full substitutions
448
474
 
449
- A built-in is evaluated as:
475
+ A built-in is evaluated by the engine via the builtin library in `lib/builtins.js`:
450
476
 
451
477
  ```js
452
478
  deltas = evalBuiltin(goal0, {}, facts, backRules, ...)
@@ -454,9 +480,18 @@ for delta in deltas:
454
480
  composed = composeSubst(currentSubst, delta)
455
481
  ```
456
482
 
457
- So built-ins behave like relations that can generate zero, one, or many possible bindings.
483
+ So built-ins behave like relations that can generate zero, one, or many possible bindings. A list generator might yield many deltas; a numeric test yields zero or one.
484
+
485
+ #### 8.3.1 Builtin deferral and “vacuous” solutions
458
486
 
459
- This is important: a list generator might yield many deltas; a numeric test yields zero or one.
487
+ Conjunction in N3 is order-insensitive, but many builtins are only useful once some variables are bound by *other* goals in the same body. When `proveGoals` is called from forward chaining, Eyeling enables **builtin deferral**: if a builtin goal can’t make progress yet, it is rotated to the end of the goal list and retried later (with a small cycle guard to avoid infinite rotation).
488
+
489
+ “Can’t make progress” includes both cases:
490
+
491
+ - the builtin returns **no solutions** (`[]`), and
492
+ - the builtin returns only **vacuous solutions** (`[{}]`, i.e., success with *no new bindings*) while the goal still contains unbound vars/blanks.
493
+
494
+ That second case matters for “satisfiable but non-enumerating” builtins (e.g., some `log:` helpers) where early vacuous success would otherwise prevent later goals from ever binding the variables the builtin needs.
460
495
 
461
496
  ### 8.4 Loop prevention: a simple visited list
462
497
 
@@ -636,10 +671,12 @@ This makes formulas a little world you can reason about as data.
636
671
  ---
637
672
 
638
673
  <a id="ch11"></a>
639
- ## Chapter 11 — Built-ins as a standard library (`evalBuiltin`)
674
+ ## Chapter 11 — Built-ins as a standard library (`lib/builtins.js`)
640
675
 
641
676
  Built-ins are where Eyeling stops being “just a Datalog engine” and becomes a practical N3 tool.
642
677
 
678
+ Implementation note: builtin code lives in `lib/builtins.js` and is wired into the prover by the engine via `makeBuiltins(deps)` (dependency injection keeps the modules loosely coupled).
679
+
643
680
  ### 11.1 How Eyeling recognizes built-ins
644
681
 
645
682
  A predicate is treated as builtin if:
@@ -664,7 +701,7 @@ That means built-ins can be:
664
701
 
665
702
  List operations are a common source of generators; numeric comparisons are tests.
666
703
 
667
- Below is a drop-in replacement for **§11.3 “A tour of builtin families”** that aims to be *fully self-contained* and to cover **every builtin currently implemented in `lib/engine.js`** (including the `rdf:first` / `rdf:rest` aliases).
704
+ Below is a drop-in replacement for **§11.3 “A tour of builtin families”** that aims to be *fully self-contained* and to cover **every builtin currently implemented in `lib/builtins.js`** (including the `rdf:first` / `rdf:rest` aliases).
668
705
 
669
706
  ---
670
707
 
@@ -1446,7 +1483,7 @@ From a logic-programming point of view, printing is awkward: if you print *durin
1446
1483
  The predicate `log:outputString` is the only officially supported “side-effect channel”, and even it is handled in two phases:
1447
1484
 
1448
1485
  1. **During reasoning (declarative phase):**
1449
- `log:outputString` behaves like a constraint-style builtin: it succeeds when its arguments are well-formed and sufficiently bound (notably, when the object is a string literal that can be emitted). Importantly, it does *not* print anything at this time. If a rule derives a triple like:
1486
+ `log:outputString` behaves like a constraint-style builtin (implemented in `lib/builtins.js`): it succeeds when its arguments are well-formed and sufficiently bound (notably, when the object is a string literal that can be emitted). Importantly, it does *not* print anything at this time. If a rule derives a triple like:
1450
1487
 
1451
1488
  ```n3
1452
1489
  :k log:outputString "Hello\n".
@@ -1455,7 +1492,7 @@ The predicate `log:outputString` is the only officially supported “side-effect
1455
1492
  then that triple simply becomes part of the fact base like any other fact.
1456
1493
 
1457
1494
  2. **After reasoning (rendering phase):**
1458
- Once saturation finishes, Eyeling scans the *final closure* for `log:outputString` facts and renders them deterministically. Concretely, the CLI collects all such triples, orders them in a stable way (using the subject as a key so output order is reproducible), and concatenates their string objects into the final emitted text.
1495
+ Once saturation finishes, Eyeling scans the *final closure* for `log:outputString` facts and renders them deterministically (this post-pass lives in `lib/explain.js`). Concretely, the CLI collects all such triples, orders them in a stable way (using the subject as a key so output order is reproducible), and concatenates their string objects into the final emitted text.
1459
1496
 
1460
1497
  This separation is not just an aesthetic choice; it preserves the meaning of logic search:
1461
1498
 
@@ -1530,6 +1567,8 @@ When enabled, Eyeling prints a compact comment block per derived triple:
1530
1567
 
1531
1568
  It’s a “why this triple holds” explanation, not a globally exported proof graph.
1532
1569
 
1570
+ Implementation note: the engine records lightweight `DerivedFact` objects during forward chaining, and `lib/explain.js` (via `makeExplain(...)`) is responsible for turning those objects into the human-readable proof comment blocks.
1571
+
1533
1572
  ### 13.3 Streaming derived facts
1534
1573
 
1535
1574
  The engine’s `reasonStream` API can accept an `onDerived` callback. Each time a new forward fact is derived, Eyeling can report it immediately.
@@ -1553,6 +1592,20 @@ The bundle contains the whole engine. The CLI path is the “canonical behavior
1553
1592
  * optional proof comments
1554
1593
  * optional streaming
1555
1594
 
1595
+ #### 14.1.1 CLI options at a glance
1596
+
1597
+ The current CLI supports a small set of flags (see `lib/cli.js`):
1598
+
1599
+ * `-a`, `--ast` — print the parsed AST as JSON and exit.
1600
+ * `-d`, `--deterministic-skolem` — make `log:skolem` stable across runs.
1601
+ * `-e`, `--enforce-https` — rewrite `http://…` to `https://…` for dereferencing builtins.
1602
+ * `-p`, `--proof-comments` — include per-fact proof comment blocks in output.
1603
+ * `-r`, `--strings` — after reasoning, render only `log:outputString` values (ordered by subject key).
1604
+ * `-s`, `--super-restricted` — disable all builtins except `log:implies` / `log:impliedBy`.
1605
+ * `-t`, `--stream` — stream derived triples as soon as they are derived.
1606
+ * `-v`, `--version` — print version and exit.
1607
+ * `-h`, `--help` — show usage.
1608
+
1556
1609
  ### 14.2 `lib/entry.js`: bundler-friendly exports
1557
1610
 
1558
1611
  `lib/entry.js` exports:
@@ -1623,21 +1676,25 @@ Eyeling is small, which makes it pleasant to extend — but there are a few inva
1623
1676
 
1624
1677
  ### 16.1 Adding a builtin
1625
1678
 
1626
- Most extensions belong in `evalBuiltin`:
1679
+ Most extensions belong in `lib/builtins.js` (inside `evalBuiltin`):
1627
1680
 
1628
1681
  * Decide if your builtin is:
1629
-
1630
1682
  * a test (0/1 solution)
1631
1683
  * functional (bind output)
1632
1684
  * generator (many solutions)
1633
1685
  * Return *deltas* `{ varName: Term }`, not full substitutions.
1634
1686
  * Be cautious with fully-unbound cases: generators can explode the search space.
1687
+ * If you add a *new predicate* (not just a new case inside an existing namespace), make sure it is recognized by `isBuiltinPred(...)`.
1635
1688
 
1636
- If your builtin needs a stable view of the closure, follow the scoped-builtin pattern:
1689
+ A small architectural note: `lib/builtins.js` is initialized by the engine via `makeBuiltins(deps)`. It receives hooks (unification, proving, deref, scoped-closure helpers, …) instead of importing the engine directly, which keeps the module graph acyclic and makes browser bundling easier.
1690
+
1691
+ If your builtin needs a stable view of the scoped closure, follow the scoped-builtin pattern:
1637
1692
 
1638
1693
  * read from `facts.__scopedSnapshot`
1639
1694
  * honor `facts.__scopedClosureLevel` and priority gating
1640
1695
 
1696
+ And if your builtin is “forward-only” (needs inputs bound), it’s fine to **fail early** until inputs are available — forward rule proving enables builtin deferral, so the goal can be retried later in the same conjunction.
1697
+
1641
1698
  ### 16.2 Adding new term shapes
1642
1699
 
1643
1700
  If you add a new Term subclass, you’ll likely need to touch:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.11.4",
3
+ "version": "1.11.5",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [