eyeling 1.17.2 → 1.18.1
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 +300 -18
- package/README.md +5 -207
- package/examples/bmi.n3 +219 -0
- package/examples/output/bmi.n3 +20 -0
- package/examples/output/pn-junction-tunneling.n3 +23 -0
- package/examples/output/sudoku.n3 +42 -0
- package/examples/output/transistor-switch.n3 +24 -0
- package/examples/pn-junction-tunneling.n3 +227 -0
- package/examples/sudoku.n3 +257 -0
- package/examples/transistor-switch.n3 +299 -0
- package/eyeling.js +678 -2
- package/index.d.ts +21 -0
- package/index.js +13 -0
- package/lib/builtin-sudoku.js +465 -0
- package/lib/builtins.js +156 -0
- package/lib/cli.js +33 -2
- package/lib/engine.js +21 -0
- package/package.json +1 -1
- package/test/api.test.js +29 -0
package/HANDBOOK.md
CHANGED
|
@@ -1767,7 +1767,41 @@ For fully stream-oriented RDF/JS consumers there is also `reasonRdfJs(...)`, whi
|
|
|
1767
1767
|
|
|
1768
1768
|
Eyeling exposes itself in three layers.
|
|
1769
1769
|
|
|
1770
|
-
### 14.1
|
|
1770
|
+
### 14.1 Install and first run
|
|
1771
|
+
|
|
1772
|
+
Eyeling targets modern JavaScript runtimes. For the npm package and CLI workflow, use **Node.js 18 or newer**.
|
|
1773
|
+
|
|
1774
|
+
Install from npm:
|
|
1775
|
+
|
|
1776
|
+
```bash
|
|
1777
|
+
npm i eyeling
|
|
1778
|
+
```
|
|
1779
|
+
|
|
1780
|
+
Run a file:
|
|
1781
|
+
|
|
1782
|
+
```bash
|
|
1783
|
+
npx eyeling examples/socrates.n3
|
|
1784
|
+
```
|
|
1785
|
+
|
|
1786
|
+
Show the available options:
|
|
1787
|
+
|
|
1788
|
+
```bash
|
|
1789
|
+
npx eyeling --help
|
|
1790
|
+
```
|
|
1791
|
+
|
|
1792
|
+
A few practical defaults are worth remembering:
|
|
1793
|
+
|
|
1794
|
+
- In normal mode, Eyeling prints **newly derived forward facts**.
|
|
1795
|
+
- If the input contains top-level `log:query` directives, Eyeling prints the **query-selected conclusion triples** instead.
|
|
1796
|
+
- If the final closure contains any `log:outputString` triples, Eyeling renders those strings instead of emitting the default N3 result set.
|
|
1797
|
+
|
|
1798
|
+
Custom builtins can be loaded explicitly from the CLI:
|
|
1799
|
+
|
|
1800
|
+
```bash
|
|
1801
|
+
npx eyeling --builtin lib/builtin-sudoku.js examples/sudoku.n3
|
|
1802
|
+
```
|
|
1803
|
+
|
|
1804
|
+
### 14.2 The bundled CLI (`eyeling.js`)
|
|
1771
1805
|
|
|
1772
1806
|
The bundle contains the whole engine. The CLI path is the “canonical behavior”:
|
|
1773
1807
|
|
|
@@ -1777,7 +1811,7 @@ The bundle contains the whole engine. The CLI path is the “canonical behavior
|
|
|
1777
1811
|
- optional proof comments
|
|
1778
1812
|
- optional streaming
|
|
1779
1813
|
|
|
1780
|
-
#### 14.
|
|
1814
|
+
#### 14.2.1 CLI options at a glance
|
|
1781
1815
|
|
|
1782
1816
|
The current CLI supports a small set of flags (see `lib/cli.js`):
|
|
1783
1817
|
|
|
@@ -1790,7 +1824,7 @@ The current CLI supports a small set of flags (see `lib/cli.js`):
|
|
|
1790
1824
|
- `-v`, `--version` — print version and exit.
|
|
1791
1825
|
- `-h`, `--help` — show usage.
|
|
1792
1826
|
|
|
1793
|
-
### 14.
|
|
1827
|
+
### 14.3 `lib/entry.js`: bundler-friendly exports
|
|
1794
1828
|
|
|
1795
1829
|
`lib/entry.js` exports:
|
|
1796
1830
|
|
|
@@ -1799,7 +1833,7 @@ The current CLI supports a small set of flags (see `lib/cli.js`):
|
|
|
1799
1833
|
|
|
1800
1834
|
`rdfjs` is a small built-in RDF/JS `DataFactory`, so browser / worker code can construct quads without pulling in another package first.
|
|
1801
1835
|
|
|
1802
|
-
### 14.
|
|
1836
|
+
### 14.4 `index.js`: the npm API wrapper
|
|
1803
1837
|
|
|
1804
1838
|
The npm `reason(...)` function does something intentionally simple and robust:
|
|
1805
1839
|
|
|
@@ -1820,6 +1854,106 @@ For structured JavaScript input, rules are supplied as current Eyeling `Rule` /
|
|
|
1820
1854
|
|
|
1821
1855
|
If you want to use N3 source text, pass the whole input as a plain N3 string.
|
|
1822
1856
|
|
|
1857
|
+
#### 14.4.1 RDF/JS quads as fact input
|
|
1858
|
+
|
|
1859
|
+
Eyeling can take RDF/JS quads directly as its fact input. At the npm API boundary, the input object may provide any of:
|
|
1860
|
+
|
|
1861
|
+
- `quads`
|
|
1862
|
+
- `facts`
|
|
1863
|
+
- `dataset`
|
|
1864
|
+
|
|
1865
|
+
Each is treated as an iterable of RDF/JS **default-graph** quads and converted into Eyeling’s internal triple form before reasoning starts.
|
|
1866
|
+
|
|
1867
|
+
```js
|
|
1868
|
+
const { reason, rdfjs } = require('eyeling');
|
|
1869
|
+
|
|
1870
|
+
const input = {
|
|
1871
|
+
quads: [
|
|
1872
|
+
rdfjs.quad(
|
|
1873
|
+
rdfjs.namedNode('http://example.org/Socrates'),
|
|
1874
|
+
rdfjs.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'),
|
|
1875
|
+
rdfjs.namedNode('http://example.org/Human'),
|
|
1876
|
+
),
|
|
1877
|
+
],
|
|
1878
|
+
n3: `
|
|
1879
|
+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
|
|
1880
|
+
@prefix : <http://example.org/>.
|
|
1881
|
+
:Human rdfs:subClassOf :Mortal.
|
|
1882
|
+
{ ?x a ?c. ?c rdfs:subClassOf ?d. } => { ?x a ?d. }.
|
|
1883
|
+
`,
|
|
1884
|
+
};
|
|
1885
|
+
|
|
1886
|
+
const out = reason(input);
|
|
1887
|
+
```
|
|
1888
|
+
|
|
1889
|
+
The important point is architectural: RDF/JS quads can be used as the **fact channel**, while rules may still come from N3 text or Eyeling rule objects. Named-graph quads are intentionally rejected here; Eyeling’s input model is triple-oriented and expects the default graph only.
|
|
1890
|
+
|
|
1891
|
+
#### 14.4.2 Passing Eyeling rule objects directly
|
|
1892
|
+
|
|
1893
|
+
The JS API also accepts Eyeling rule objects directly, so you do not have to serialize everything back into N3 first.
|
|
1894
|
+
|
|
1895
|
+
That means an input such as this is valid:
|
|
1896
|
+
|
|
1897
|
+
```js
|
|
1898
|
+
const { reason } = require('eyeling');
|
|
1899
|
+
|
|
1900
|
+
const input = {
|
|
1901
|
+
triples: [
|
|
1902
|
+
/* Eyeling Triple objects */
|
|
1903
|
+
],
|
|
1904
|
+
forwardRules: [
|
|
1905
|
+
/* Eyeling Rule objects */
|
|
1906
|
+
],
|
|
1907
|
+
backwardRules: [
|
|
1908
|
+
/* optional Eyeling Rule objects */
|
|
1909
|
+
],
|
|
1910
|
+
};
|
|
1911
|
+
|
|
1912
|
+
const out = reason(input);
|
|
1913
|
+
```
|
|
1914
|
+
|
|
1915
|
+
The accepted shapes are intentionally flexible:
|
|
1916
|
+
|
|
1917
|
+
- `triples`
|
|
1918
|
+
- `forwardRules` or `frules`
|
|
1919
|
+
- `backwardRules` or `brules`
|
|
1920
|
+
- `queryRules`, `logQueryRules`, or `qrules`
|
|
1921
|
+
- a full AST bundle like `[prefixes, triples, frules, brules, queryRules]`
|
|
1922
|
+
|
|
1923
|
+
So Eyeling rule objects can be passed directly in the API, either as separate arrays or bundled into the same object graph the parser itself uses.
|
|
1924
|
+
|
|
1925
|
+
#### 14.4.3 Consuming derived RDF/JS results
|
|
1926
|
+
|
|
1927
|
+
There are two main ways to consume derived results in RDF/JS form.
|
|
1928
|
+
|
|
1929
|
+
First, `reasonStream(...)` can emit RDF/JS quads **while reasoning runs**. Pass `rdfjs: true` and an `onDerived(...)` callback:
|
|
1930
|
+
|
|
1931
|
+
```js
|
|
1932
|
+
const { reasonStream } = require('eyeling/lib/entry');
|
|
1933
|
+
|
|
1934
|
+
reasonStream(input, {
|
|
1935
|
+
rdfjs: true,
|
|
1936
|
+
onDerived({ triple, quad }) {
|
|
1937
|
+
// triple = Eyeling's N3 string form
|
|
1938
|
+
// quad = RDF/JS Quad for the same derived fact
|
|
1939
|
+
},
|
|
1940
|
+
});
|
|
1941
|
+
```
|
|
1942
|
+
|
|
1943
|
+
This is the “push” interface: each newly derived forward fact is reported as soon as it is produced.
|
|
1944
|
+
|
|
1945
|
+
Second, `reasonRdfJs(...)` exposes the same derived results as an **async stream of RDF/JS quads**:
|
|
1946
|
+
|
|
1947
|
+
```js
|
|
1948
|
+
const { reasonRdfJs } = require('eyeling/lib/entry');
|
|
1949
|
+
|
|
1950
|
+
for await (const quad of reasonRdfJs(input)) {
|
|
1951
|
+
// consume RDF/JS quads incrementally
|
|
1952
|
+
}
|
|
1953
|
+
```
|
|
1954
|
+
|
|
1955
|
+
This is the “pull” interface: the consumer iterates an async iterable of quads instead of registering a callback.
|
|
1956
|
+
|
|
1823
1957
|
One practical implication remains:
|
|
1824
1958
|
|
|
1825
1959
|
- if you want _in-process_ access to the engine objects (facts arrays, derived proof objects), use `reasonStream` / `reasonRdfJs` from the bundle entry rather than the subprocess-based API.
|
|
@@ -1870,28 +2004,162 @@ That’s the whole engine in miniature: unify, compose substitutions, emit head
|
|
|
1870
2004
|
|
|
1871
2005
|
Eyeling is small, which makes it pleasant to extend — but there are a few invariants worth respecting.
|
|
1872
2006
|
|
|
1873
|
-
|
|
2007
|
+
The most important update is architectural: **you no longer need to patch `lib/builtins.js` just to add a project-specific builtin**. The preferred path is now to load a custom builtin module, either programmatically or from the CLI. Core builtins still live in `lib/builtins.js`, but user extensions can stay outside the engine.
|
|
2008
|
+
|
|
2009
|
+
### 16.1 The preferred path: custom builtin modules
|
|
1874
2010
|
|
|
1875
|
-
|
|
2011
|
+
Eyeling now exposes a small custom-builtin registry.
|
|
1876
2012
|
|
|
1877
|
-
|
|
1878
|
-
- a test (0/1 solution)
|
|
1879
|
-
- functional (bind output)
|
|
1880
|
-
- generator (many solutions)
|
|
1881
|
-
- Return _deltas_ `{ varName: Term }`, not full substitutions.
|
|
1882
|
-
- Be cautious with fully-unbound cases: generators can explode the search space.
|
|
1883
|
-
- If you add a _new predicate_ (not just a new case inside an existing namespace), make sure it is recognized by `isBuiltinPred(...)`.
|
|
2013
|
+
At runtime, builtin predicates can be added with:
|
|
1884
2014
|
|
|
1885
|
-
|
|
2015
|
+
- `registerBuiltin(iri, handler)`
|
|
2016
|
+
- `unregisterBuiltin(iri)`
|
|
2017
|
+
- `registerBuiltinModule(moduleExport, origin?)`
|
|
2018
|
+
- `loadBuiltinModule(specifier, { resolveFrom? })`
|
|
2019
|
+
- `listBuiltinIris()`
|
|
2020
|
+
|
|
2021
|
+
That means the extension story is:
|
|
2022
|
+
|
|
2023
|
+
- keep the engine’s shipped builtins in `lib/builtins.js`
|
|
2024
|
+
- keep your own application or domain builtins in a separate `.js` module
|
|
2025
|
+
- load that module with `--builtin` or from JavaScript
|
|
2026
|
+
|
|
2027
|
+
This is the safest way to extend Eyeling because it avoids forking the builtin dispatcher and keeps upgrades merge-friendly.
|
|
2028
|
+
|
|
2029
|
+
### 16.2 CLI loading: `--builtin`
|
|
2030
|
+
|
|
2031
|
+
The CLI accepts a repeatable `--builtin <module.js>` option:
|
|
2032
|
+
|
|
2033
|
+
```bash
|
|
2034
|
+
eyeling --builtin ./hello-builtin.js rules.n3
|
|
2035
|
+
```
|
|
2036
|
+
|
|
2037
|
+
You can pass it more than once:
|
|
2038
|
+
|
|
2039
|
+
```bash
|
|
2040
|
+
eyeling --builtin ./math-extra.js --builtin ./domain-rules.js input.n3
|
|
2041
|
+
```
|
|
2042
|
+
|
|
2043
|
+
Each module is loaded before reasoning starts. Paths are resolved from the current working directory.
|
|
2044
|
+
|
|
2045
|
+
The same capability is available through the npm wrapper:
|
|
2046
|
+
|
|
2047
|
+
```js
|
|
2048
|
+
const { reason } = require('eyeling');
|
|
2049
|
+
const out = reason({ builtinModules: ['./hello-builtin.js'] }, n3Text);
|
|
2050
|
+
```
|
|
2051
|
+
|
|
2052
|
+
### 16.3 What a builtin module may export
|
|
2053
|
+
|
|
2054
|
+
Eyeling accepts three module shapes.
|
|
2055
|
+
|
|
2056
|
+
#### A function export
|
|
2057
|
+
|
|
2058
|
+
```js
|
|
2059
|
+
module.exports = ({ registerBuiltin, internLiteral, unifyTerm, terms }) => {
|
|
2060
|
+
const { Var } = terms;
|
|
2061
|
+
|
|
2062
|
+
registerBuiltin('http://example.org/custom#hello', ({ goal, subst }) => {
|
|
2063
|
+
const lit = internLiteral('"world"');
|
|
2064
|
+
if (goal.o instanceof Var) {
|
|
2065
|
+
return [{ ...subst, [goal.o.name]: lit }];
|
|
2066
|
+
}
|
|
2067
|
+
const s2 = unifyTerm(goal.o, lit, subst);
|
|
2068
|
+
return s2 ? [s2] : [];
|
|
2069
|
+
});
|
|
2070
|
+
};
|
|
2071
|
+
```
|
|
2072
|
+
|
|
2073
|
+
#### An object with `register(api)`
|
|
2074
|
+
|
|
2075
|
+
```js
|
|
2076
|
+
module.exports = {
|
|
2077
|
+
register(api) {
|
|
2078
|
+
api.registerBuiltin('http://example.org/custom#ping', ({ subst }) => [subst]);
|
|
2079
|
+
},
|
|
2080
|
+
};
|
|
2081
|
+
```
|
|
2082
|
+
|
|
2083
|
+
#### A plain object mapping predicate IRIs to handlers
|
|
2084
|
+
|
|
2085
|
+
```js
|
|
2086
|
+
module.exports = {
|
|
2087
|
+
'http://example.org/custom#ok': ({ subst }) => [subst],
|
|
2088
|
+
};
|
|
2089
|
+
```
|
|
2090
|
+
|
|
2091
|
+
If none of those shapes match, Eyeling rejects the module with a descriptive error.
|
|
2092
|
+
|
|
2093
|
+
### 16.4 The handler contract
|
|
2094
|
+
|
|
2095
|
+
Builtin handlers are called with a context object like:
|
|
2096
|
+
|
|
2097
|
+
- `iri` — the predicate IRI string
|
|
2098
|
+
- `goal` — the current triple goal
|
|
2099
|
+
- `subst` — the current substitution
|
|
2100
|
+
- `facts`, `backRules`, `depth`, `varGen`, `maxResults`
|
|
2101
|
+
- `api` — the same registration/helper API used by modules
|
|
2102
|
+
|
|
2103
|
+
A handler returns **an array of substitutions**:
|
|
2104
|
+
|
|
2105
|
+
- `[]` means failure / no solutions
|
|
2106
|
+
- `[subst2]` means one successful continuation
|
|
2107
|
+
- multiple substitutions mean a generator builtin
|
|
2108
|
+
|
|
2109
|
+
In practice:
|
|
2110
|
+
|
|
2111
|
+
- Decide if your builtin is a test, a functional relation, or a generator.
|
|
2112
|
+
- Return substitutions (or substitution deltas merged into the current substitution), not printed output.
|
|
2113
|
+
- Be cautious with fully-unbound generators: they can explode the search space.
|
|
2114
|
+
- If a builtin needs inputs to be bound first, it is fine to fail early and let forward-rule proving retry later in the conjunction.
|
|
2115
|
+
|
|
2116
|
+
Custom builtin failures are wrapped so the predicate IRI appears in the thrown error message, which makes debugging much easier from the CLI.
|
|
2117
|
+
|
|
2118
|
+
### 16.5 The helper API exposed to builtin modules
|
|
2119
|
+
|
|
2120
|
+
Builtin modules do not need to import internal engine files directly. Eyeling passes a helper API into module registration, including:
|
|
2121
|
+
|
|
2122
|
+
- registration helpers: `registerBuiltin`, `unregisterBuiltin`, `listBuiltinIris`
|
|
2123
|
+
- term constructors via `terms` (`Literal`, `Iri`, `Var`, `Blank`, `ListTerm`, `GraphTerm`, `Triple`, `Rule`, ...)
|
|
2124
|
+
- literal/term helpers such as `internLiteral`, `internIri`, `literalParts`, `termToN3`, `termToJsString`
|
|
2125
|
+
- reasoning helpers such as `unifyTerm`, `applySubstTerm`, `applySubstTriple`, `proveGoals`
|
|
2126
|
+
- namespace constants via `ns`
|
|
2127
|
+
|
|
2128
|
+
That API keeps the extension boundary explicit: custom builtins get the operations they need without reaching into Eyeling’s private module graph.
|
|
2129
|
+
|
|
2130
|
+
### 16.6 A shipped example: the Sudoku builtin
|
|
2131
|
+
|
|
2132
|
+
The repository now ships a Sudoku builtin module (`lib/builtin-sudoku.js`) and a matching example program (`sudoku.n3`).
|
|
2133
|
+
|
|
2134
|
+
So this works out of the box:
|
|
2135
|
+
|
|
2136
|
+
```bash
|
|
2137
|
+
eyeling sudoku.n3
|
|
2138
|
+
```
|
|
2139
|
+
|
|
2140
|
+
That example is useful for two reasons:
|
|
2141
|
+
|
|
2142
|
+
- it shows a realistic domain-specific builtin implemented outside the core builtin switchboard
|
|
2143
|
+
- it demonstrates the intended deployment model for larger custom relations: keep the N3 logic in the `.n3` file, and keep specialized search/verification code in a loadable builtin module
|
|
2144
|
+
|
|
2145
|
+
### 16.7 When you should still edit `lib/builtins.js`
|
|
2146
|
+
|
|
2147
|
+
Editing `lib/builtins.js` is still reasonable when you are:
|
|
2148
|
+
|
|
2149
|
+
- adding or fixing a **core** Eyeling builtin
|
|
2150
|
+
- changing builtin behavior that should ship as part of Eyeling itself
|
|
2151
|
+
- modifying the builtin helper API that custom modules depend on
|
|
2152
|
+
|
|
2153
|
+
But if the builtin is project-specific, experimental, or domain-bound, prefer a custom module first.
|
|
2154
|
+
|
|
2155
|
+
A small architectural note: `lib/builtins.js` is still 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.
|
|
1886
2156
|
|
|
1887
2157
|
If your builtin needs a stable view of the scoped closure, follow the scoped-builtin pattern:
|
|
1888
2158
|
|
|
1889
2159
|
- read from `facts.__scopedSnapshot`
|
|
1890
2160
|
- honor `facts.__scopedClosureLevel` and priority gating
|
|
1891
2161
|
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
### 16.2 Adding new term shapes
|
|
2162
|
+
### 16.8 Adding new term shapes
|
|
1895
2163
|
|
|
1896
2164
|
If you add a new Term subclass, you’ll likely need to touch:
|
|
1897
2165
|
|
|
@@ -1900,7 +2168,7 @@ If you add a new Term subclass, you’ll likely need to touch:
|
|
|
1900
2168
|
- variable collection for compaction (`gcCollectVarsInTerm`)
|
|
1901
2169
|
- groundness checks
|
|
1902
2170
|
|
|
1903
|
-
### 16.
|
|
2171
|
+
### 16.9 Parser extensions
|
|
1904
2172
|
|
|
1905
2173
|
If you extend parsing, preserve the Rule invariants:
|
|
1906
2174
|
|
|
@@ -1983,6 +2251,7 @@ Options:
|
|
|
1983
2251
|
|
|
1984
2252
|
```
|
|
1985
2253
|
-a, --ast Print parsed AST as JSON and exit.
|
|
2254
|
+
--builtin <module.js> Load a custom builtin module (repeatable).
|
|
1986
2255
|
-d, --deterministic-skolem Make log:skolem stable across reasoning runs.
|
|
1987
2256
|
-e, --enforce-https Rewrite http:// IRIs to https:// for log dereferencing builtins.
|
|
1988
2257
|
-h, --help Show this help and exit.
|
|
@@ -2037,10 +2306,23 @@ See also:
|
|
|
2037
2306
|
|
|
2038
2307
|
Eyeling supports a built-in “standard library” across namespaces like `log:`, `math:`, `string:`, `list:`, `time:`, `crypto:`.
|
|
2039
2308
|
|
|
2309
|
+
It also supports **custom builtin modules**.
|
|
2310
|
+
|
|
2311
|
+
- From the CLI: `eyeling --builtin ./my-builtins.js input.n3`
|
|
2312
|
+
- From JavaScript: `reason({ builtinModules: ['./my-builtins.js'] }, input)`
|
|
2313
|
+
- Programmatically in-process: `registerBuiltin(...)`, `registerBuiltinModule(...)`, `loadBuiltinModule(...)`
|
|
2314
|
+
|
|
2315
|
+
A concrete shipped example is the Sudoku builtin and the root-level `sudoku.n3` program:
|
|
2316
|
+
|
|
2317
|
+
```bash
|
|
2318
|
+
eyeling sudoku.n3
|
|
2319
|
+
```
|
|
2320
|
+
|
|
2040
2321
|
References:
|
|
2041
2322
|
|
|
2042
2323
|
- W3C N3 Built-ins overview: [https://w3c.github.io/N3/reports/20230703/builtins.html](https://w3c.github.io/N3/reports/20230703/builtins.html)
|
|
2043
2324
|
- Eyeling implementation details: [Chapter 11 — Built-ins as a standard library](#ch11)
|
|
2325
|
+
- Extension API and custom module loading: [Chapter 16 — Extending Eyeling (without breaking it)](#ch16)
|
|
2044
2326
|
- The shipped builtin catalogue: `eyeling-builtins.ttl` (in this repo)
|
|
2045
2327
|
|
|
2046
2328
|
If you are running untrusted inputs, consider `--super-restricted` to disable all builtins except implication.
|
package/README.md
CHANGED
|
@@ -4,218 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
A compact [Notation3 (N3)](https://notation3.org/) reasoner in **JavaScript**.
|
|
6
6
|
|
|
7
|
-
- Single self-contained bundle (`eyeling.js`), no external runtime dependencies
|
|
8
|
-
- Forward (`=>`) and backward (`<=`) chaining over Horn-style rules
|
|
9
|
-
- **CLI / npm `reason()` output is mode-dependent by default**: it prints **newly derived forward facts** in normal mode, or (when top-level `{ ... } log:query { ... }.` directives are present) the **unique instantiated conclusion triples** of those queries, optionally with compact proof comments
|
|
10
|
-
- Works in Node.js and fully client-side (browser/worker)
|
|
11
|
-
|
|
12
|
-
## Links
|
|
13
|
-
|
|
14
|
-
- **Handbook:** [https://eyereasoner.github.io/eyeling/HANDBOOK](https://eyereasoner.github.io/eyeling/HANDBOOK)
|
|
15
|
-
- **Semantics:** [https://eyereasoner.github.io/eyeling/SEMANTICS](https://eyereasoner.github.io/eyeling/SEMANTICS)
|
|
16
|
-
- **Playground:** [https://eyereasoner.github.io/eyeling/demo](https://eyereasoner.github.io/eyeling/demo)
|
|
17
|
-
- **Conformance report:** [https://codeberg.org/phochste/notation3tests/src/branch/main/reports/report.md](https://codeberg.org/phochste/notation3tests/src/branch/main/reports/report.md)
|
|
18
|
-
|
|
19
|
-
Eyeling is regularly checked against the community Notation3 test suite. If you want implementation details (parser, unifier, proof search, skolemization, scoped closure, builtins), start with the handbook.
|
|
20
|
-
|
|
21
7
|
## Quick start
|
|
22
8
|
|
|
23
|
-
### Requirements
|
|
24
|
-
|
|
25
|
-
- Node.js >= 18
|
|
26
|
-
|
|
27
|
-
### Install
|
|
28
|
-
|
|
29
9
|
```bash
|
|
30
10
|
npm i eyeling
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## CLI usage
|
|
34
|
-
|
|
35
|
-
Run on a file:
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
11
|
npx eyeling examples/socrates.n3
|
|
39
12
|
```
|
|
40
13
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
npx eyeling --help
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
Useful flags include `--proof-comments`, `--stream`, and `--enforce-https`. If the final closure contains any `log:outputString` triples, Eyeling now renders those strings automatically instead of printing N3 output.
|
|
48
|
-
|
|
49
|
-
## What gets printed?
|
|
50
|
-
|
|
51
|
-
### Normal mode (default)
|
|
52
|
-
|
|
53
|
-
Without top-level `log:query` directives, Eyeling prints **newly derived forward facts** by default.
|
|
54
|
-
|
|
55
|
-
### `log:query` mode (output selection)
|
|
56
|
-
|
|
57
|
-
If the input contains one or more **top-level** directives of the form:
|
|
58
|
-
|
|
59
|
-
```n3
|
|
60
|
-
{ ?x a :Human. } log:query { ?x a :Mortal. }.
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Eyeling still computes the saturated forward closure, but it **prints only** the **unique instantiated conclusion triples** of those `log:query` directives (instead of all newly derived forward facts).
|
|
64
|
-
|
|
65
|
-
## JavaScript API
|
|
66
|
-
|
|
67
|
-
### npm helper: `reason()`
|
|
68
|
-
|
|
69
|
-
CommonJS:
|
|
70
|
-
|
|
71
|
-
```js
|
|
72
|
-
const { reason } = require('eyeling');
|
|
73
|
-
|
|
74
|
-
const input = `
|
|
75
|
-
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
|
|
76
|
-
@prefix : <http://example.org/socrates#>.
|
|
77
|
-
|
|
78
|
-
:Socrates a :Human.
|
|
79
|
-
:Human rdfs:subClassOf :Mortal.
|
|
80
|
-
|
|
81
|
-
{ ?s a ?A. ?A rdfs:subClassOf ?B. } => { ?s a ?B. }.
|
|
82
|
-
`;
|
|
83
|
-
|
|
84
|
-
console.log(reason({ proofComments: false }, input));
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
ESM:
|
|
88
|
-
|
|
89
|
-
```js
|
|
90
|
-
import eyeling from 'eyeling';
|
|
91
|
-
|
|
92
|
-
console.log(eyeling.reason({ proofComments: false }, input));
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
Notes:
|
|
96
|
-
|
|
97
|
-
- `reason()` returns the same textual output you would get from the CLI for the same input/options.
|
|
98
|
-
- By default, the npm helper keeps output machine-friendly (`proofComments: false`).
|
|
99
|
-
- The npm helper shells out to the bundled `eyeling.js` CLI for simplicity and robustness.
|
|
100
|
-
|
|
101
|
-
### RDF/JS and Eyeling rule-object interop
|
|
102
|
-
|
|
103
|
-
The JavaScript APIs now accept three input styles:
|
|
104
|
-
|
|
105
|
-
1. plain N3 text
|
|
106
|
-
2. RDF/JS facts (`quads`, `facts`, or `dataset`)
|
|
107
|
-
3. Eyeling rule objects / AST bundles (the same shapes you get from `eyeling --ast`)
|
|
108
|
-
|
|
109
|
-
If you want to use N3 source text, pass the whole input as a plain N3 string.
|
|
110
|
-
|
|
111
|
-
For RDF/JS facts, the graph must be the default graph. Named-graph quads are rejected.
|
|
112
|
-
|
|
113
|
-
For structured inputs, `rules` is supplied as current Eyeling rule objects:
|
|
114
|
-
|
|
115
|
-
```js
|
|
116
|
-
const { reason, rdfjs } = require('eyeling');
|
|
117
|
-
|
|
118
|
-
const ex = 'http://example.org/';
|
|
119
|
-
|
|
120
|
-
const rule = {
|
|
121
|
-
_type: 'Rule',
|
|
122
|
-
premise: [
|
|
123
|
-
{
|
|
124
|
-
_type: 'Triple',
|
|
125
|
-
s: { _type: 'Var', name: 'x' },
|
|
126
|
-
p: { _type: 'Iri', value: ex + 'parent' },
|
|
127
|
-
o: { _type: 'Var', name: 'y' },
|
|
128
|
-
},
|
|
129
|
-
],
|
|
130
|
-
conclusion: [
|
|
131
|
-
{
|
|
132
|
-
_type: 'Triple',
|
|
133
|
-
s: { _type: 'Var', name: 'x' },
|
|
134
|
-
p: { _type: 'Iri', value: ex + 'ancestor' },
|
|
135
|
-
o: { _type: 'Var', name: 'y' },
|
|
136
|
-
},
|
|
137
|
-
],
|
|
138
|
-
isForward: true,
|
|
139
|
-
isFuse: false,
|
|
140
|
-
headBlankLabels: [],
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
const out = reason(
|
|
144
|
-
{ proofComments: false },
|
|
145
|
-
{
|
|
146
|
-
quads: [rdfjs.quad(rdfjs.namedNode(ex + 'alice'), rdfjs.namedNode(ex + 'parent'), rdfjs.namedNode(ex + 'bob'))],
|
|
147
|
-
rules: [rule],
|
|
148
|
-
},
|
|
149
|
-
);
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
You can also pass a full AST bundle directly:
|
|
153
|
-
|
|
154
|
-
```js
|
|
155
|
-
const ast = [prefixes, triples, forwardRules, backwardRules];
|
|
156
|
-
const out2 = reason({ proofComments: false }, ast);
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### Direct bundle / browser-worker API: `reasonStream()`
|
|
160
|
-
|
|
161
|
-
For in-process reasoning (browser, worker, or direct use of `eyeling.js`):
|
|
162
|
-
|
|
163
|
-
```js
|
|
164
|
-
const result = eyeling.reasonStream(input, {
|
|
165
|
-
proof: false,
|
|
166
|
-
onDerived: ({ triple }) => console.log(triple),
|
|
167
|
-
// includeInputFactsInClosure: false,
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
console.log(result.closureN3);
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
`eyeling.js` now also exposes `eyeling.rdfjs` and `eyeling.reasonRdfJs(...)` for RDF/JS workflows.
|
|
174
|
-
|
|
175
|
-
#### `reasonStream()` output behavior
|
|
176
|
-
|
|
177
|
-
`closureN3` is also mode-dependent:
|
|
178
|
-
|
|
179
|
-
- **Normal mode:** by default, `closureN3` is the closure (**input facts + derived facts**)
|
|
180
|
-
- **`log:query` mode:** `closureN3` is the **query-selected triples**
|
|
181
|
-
|
|
182
|
-
To exclude input facts from the normal-mode closure, pass:
|
|
183
|
-
|
|
184
|
-
```js
|
|
185
|
-
includeInputFactsInClosure: false;
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
When `rdfjs: true` is passed, `onDerived` also receives a `quad` field and the result may include `closureQuads` / `queryQuads`.
|
|
189
|
-
|
|
190
|
-
The returned object also includes `queryMode`, `queryTriples`, and `queryDerived` (and in normal mode, `onDerived` fires for newly derived facts; in `log:query` mode it fires for the query-selected derived triples).
|
|
191
|
-
|
|
192
|
-
### `reasonRdfJs()`
|
|
193
|
-
|
|
194
|
-
`reasonRdfJs(input, opts)` exposes derived results as an async iterable of RDF/JS quads as they are produced:
|
|
195
|
-
|
|
196
|
-
```js
|
|
197
|
-
const { reasonRdfJs, rdfjs } = require('eyeling');
|
|
198
|
-
|
|
199
|
-
for await (const quad of reasonRdfJs({
|
|
200
|
-
quads: [rdfjs.quad(rdfjs.namedNode(ex + 'alice'), rdfjs.namedNode(ex + 'parent'), rdfjs.namedNode(ex + 'bob'))],
|
|
201
|
-
rules: [rule],
|
|
202
|
-
})) {
|
|
203
|
-
console.log(quad.subject.value, quad.predicate.value, quad.object.value);
|
|
204
|
-
}
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
## Builtins
|
|
208
|
-
|
|
209
|
-
Builtins are defined in [eyeling-builtins.ttl](https://github.com/eyereasoner/eyeling/blob/main/eyeling-builtins.ttl) and described in the [Handbook (Chapter 11)](https://eyereasoner.github.io/eyeling/HANDBOOK#ch11).
|
|
210
|
-
|
|
211
|
-
## Development and testing (repo checkout)
|
|
212
|
-
|
|
213
|
-
```bash
|
|
214
|
-
npm test
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
You can also inspect the `examples/` directory for many small and large N3 programs.
|
|
218
|
-
|
|
219
|
-
## License
|
|
14
|
+
## Read more
|
|
220
15
|
|
|
221
|
-
|
|
16
|
+
- **Handbook:** [eyereasoner.github.io/eyeling/HANDBOOK](https://eyereasoner.github.io/eyeling/HANDBOOK)
|
|
17
|
+
- **Semantics:** [eyereasoner.github.io/eyeling/SEMANTICS](https://eyereasoner.github.io/eyeling/SEMANTICS)
|
|
18
|
+
- **Playground:** [eyereasoner.github.io/eyeling/demo](https://eyereasoner.github.io/eyeling/demo)
|
|
19
|
+
- **Conformance report:** [codeberg.org/phochste/notation3tests/.../report.md](https://codeberg.org/phochste/notation3tests/src/branch/main/reports/report.md)
|