eyeling 1.22.15 → 1.23.0
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 +113 -16
- package/dist/browser/eyeling.browser.js +248 -34
- package/examples/barley-seed-becoming.n3 +497 -0
- package/examples/constructor-theory-becoming.n3 +177 -0
- package/examples/output/barley-seed-becoming.txt +25 -0
- package/examples/output/constructor-theory-becoming.n3 +18 -0
- package/examples/output/tunnel-junction-wake-switch-becoming.txt +21 -0
- package/examples/tunnel-junction-wake-switch-becoming.n3 +216 -0
- package/eyeling.js +255 -34
- package/index.d.ts +17 -5
- package/index.js +29 -8
- package/lib/cli.js +44 -32
- package/lib/engine.js +4 -2
- package/lib/multisource.js +198 -0
- package/package.json +1 -1
- package/test/api.test.js +101 -0
package/HANDBOOK.md
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
- [Epilogue](#epilogue)
|
|
28
28
|
- [Appendix A — Eyeling user notes](#app-a)
|
|
29
29
|
- [Appendix B — Notation3: when facts can carry their own logic](#app-b)
|
|
30
|
-
- [Appendix C — N3
|
|
30
|
+
- [Appendix C — Why N3 fits the Eyeling examples](#app-c)
|
|
31
31
|
- [Appendix D — LLM + Eyeling: A Repeatable Logic Toolchain](#app-d)
|
|
32
32
|
- [Appendix E — How Eyeling reaches 100% on `notation3tests`](#app-e)
|
|
33
33
|
- [Appendix F — The ARC approach: Answer • Reason Why • Check](#app-f)
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
- [Appendix H — Applied Constructor-Theory and the N3 ARC examples](#app-h)
|
|
36
36
|
- [Appendix I — The Eyeling Playground](#app-i)
|
|
37
37
|
- [Appendix J — Formalism Is Fine](#app-j)
|
|
38
|
+
- [Appendix K — Whitehead-inspired becoming examples](#app-k)
|
|
38
39
|
|
|
39
40
|
---
|
|
40
41
|
|
|
@@ -1870,7 +1871,7 @@ echo '@prefix : <http://example.org/> .
|
|
|
1870
1871
|
{ ?x a :Man } => { ?x a :Mortal } .' | npx eyeling
|
|
1871
1872
|
```
|
|
1872
1873
|
|
|
1873
|
-
You can also pass
|
|
1874
|
+
You can also pass one or more file paths/URLs, or `-` to read explicitly from stdin. When multiple inputs are given, Eyeling parses each source separately, merges the parsed ASTs, and then runs one reasoning pass over the combined facts and rules. This avoids constructing one giant N3 source string.
|
|
1874
1875
|
|
|
1875
1876
|
Show the available options:
|
|
1876
1877
|
|
|
@@ -1894,7 +1895,7 @@ npx eyeling --builtin lib/builtin-sudoku.js examples/sudoku.n3
|
|
|
1894
1895
|
|
|
1895
1896
|
The bundle contains the whole engine. The CLI path is the “canonical behavior”:
|
|
1896
1897
|
|
|
1897
|
-
- parse input
|
|
1898
|
+
- parse one or more input sources; with multiple sources, parse each source independently and merge the ASTs
|
|
1898
1899
|
- reason to closure
|
|
1899
1900
|
- print derived triples, or render `log:outputString` strings when present
|
|
1900
1901
|
- optional proof comments
|
|
@@ -1915,6 +1916,7 @@ The current CLI supports a small set of flags (see `lib/cli.js`):
|
|
|
1915
1916
|
- `-h`, `--help` — show usage.
|
|
1916
1917
|
- With no positional argument, Eyeling reads from stdin when input is piped.
|
|
1917
1918
|
- Use `-` as the input path to read explicitly from stdin.
|
|
1919
|
+
- Multiple positional inputs are allowed, for example `eyeling facts.n3 rules.n3`; rules from any input can match facts from any other input after the merge.
|
|
1918
1920
|
|
|
1919
1921
|
### 14.3 Package entrypoint split for Node, browser, and CLI
|
|
1920
1922
|
|
|
@@ -2023,13 +2025,34 @@ Notes:
|
|
|
2023
2025
|
|
|
2024
2026
|
#### 14.4.2 RDF-JS and Eyeling rule-object interoperability
|
|
2025
2027
|
|
|
2026
|
-
The JavaScript APIs accept
|
|
2028
|
+
The JavaScript APIs accept four input styles:
|
|
2027
2029
|
|
|
2028
2030
|
1. plain N3 text
|
|
2029
|
-
2.
|
|
2030
|
-
3.
|
|
2031
|
+
2. a multi-source N3 object (`{ sources: [...] }`)
|
|
2032
|
+
3. RDF/JS fact input (`quads`, `facts`, or `dataset`)
|
|
2033
|
+
4. Eyeling rule objects or full AST bundles
|
|
2031
2034
|
|
|
2032
|
-
If you want to use N3 source text, pass the whole input as a plain string.
|
|
2035
|
+
If you want to use one N3 source text, pass the whole input as a plain string. If you want to avoid concatenating several N3 sources into one large string, pass them as a source list instead.
|
|
2036
|
+
|
|
2037
|
+
For example:
|
|
2038
|
+
|
|
2039
|
+
```js
|
|
2040
|
+
const { reason } = require('eyeling');
|
|
2041
|
+
|
|
2042
|
+
const out = reason(
|
|
2043
|
+
{ proofComments: false },
|
|
2044
|
+
{
|
|
2045
|
+
sources: [
|
|
2046
|
+
'@prefix : <http://example.org/> .\n:Socrates a :Man .\n',
|
|
2047
|
+
'@prefix : <http://example.org/> .\n{ ?x a :Man } => { ?x a :Mortal } .\n',
|
|
2048
|
+
],
|
|
2049
|
+
},
|
|
2050
|
+
);
|
|
2051
|
+
|
|
2052
|
+
console.log(out);
|
|
2053
|
+
```
|
|
2054
|
+
|
|
2055
|
+
In a source list, each source is parsed with its own blank-node scope and optional base IRI. That means the same explicit blank label, such as `_:x`, in two different sources does not accidentally become the same blank node after merging. Prefix declarations are merged mainly for readable output; IRI expansion has already happened while each source was parsed.
|
|
2033
2056
|
|
|
2034
2057
|
For RDF/JS facts, the graph must be the default graph. Named-graph quads are rejected.
|
|
2035
2058
|
|
|
@@ -2466,6 +2489,12 @@ The authoritative list is always:
|
|
|
2466
2489
|
eyeling --help
|
|
2467
2490
|
```
|
|
2468
2491
|
|
|
2492
|
+
Usage:
|
|
2493
|
+
|
|
2494
|
+
```bash
|
|
2495
|
+
eyeling [options] [file-or-url.n3|- ...]
|
|
2496
|
+
```
|
|
2497
|
+
|
|
2469
2498
|
Options:
|
|
2470
2499
|
|
|
2471
2500
|
```
|
|
@@ -2480,6 +2509,8 @@ Options:
|
|
|
2480
2509
|
-v, --version Print version and exit.
|
|
2481
2510
|
```
|
|
2482
2511
|
|
|
2512
|
+
Input note: with multiple positional inputs, Eyeling reads and parses each source separately, then merges facts, forward rules, backward rules, and `log:query` directives before reasoning. Blank node labels are scoped per input document.
|
|
2513
|
+
|
|
2483
2514
|
Note: when `log:query` directives are present, or when the program may produce `log:outputString` facts, Eyeling cannot stream its final user-facing output from partial derivations, so `--stream` has no effect in those cases. In the latter case Eyeling saturates first and then renders the collected output strings.
|
|
2484
2515
|
|
|
2485
2516
|
See also:
|
|
@@ -2630,23 +2661,69 @@ In that sense, N3 is less a bid to make the web “smarter” than a bid to make
|
|
|
2630
2661
|
|
|
2631
2662
|
<a id="app-c"></a>
|
|
2632
2663
|
|
|
2633
|
-
## Appendix C — N3
|
|
2664
|
+
## Appendix C — Why N3 fits the Eyeling examples
|
|
2665
|
+
|
|
2666
|
+
The Eyeling examples combine several things at once. They contain facts about a situation, rules that derive new facts, checks that make the result testable, and an answer that can be shown to a human. That combination matters. It means that Eyeling is not only a data exercise and not only a logic exercise. It needs a notation in which data and rules can remain together.
|
|
2667
|
+
|
|
2668
|
+
This raises a practical question: which language fits these examples best?
|
|
2669
|
+
|
|
2670
|
+
SQL is a natural candidate when the main task is storing data and querying it. Prolog is a natural candidate when the main task is writing rules and deriving consequences from facts. N3 is interesting because it tries to keep those two sides together. The point of this appendix is not to rank SQL, Prolog, and N3 in general. The point is to explain why N3 works especially well for Eyeling-style examples.
|
|
2671
|
+
|
|
2672
|
+
### What the examples need
|
|
2673
|
+
|
|
2674
|
+
A typical Eyeling example is not just a small dataset. It is also not just a set of inference rules. It is a compact artifact in which several layers belong together.
|
|
2675
|
+
|
|
2676
|
+
There is usually a description of a situation: products, airports, organisms, policies, signatures, dates, or other entities. There are rules that derive new facts from those inputs. There are explicit checks that say whether the intended conclusions hold. And there is often a final answer or explanation that is part of the example itself.
|
|
2677
|
+
|
|
2678
|
+
This is the real design problem. If the language handles only one of these layers well, then the example has to be split up. The data ends up in one notation, the rules in another, the checks somewhere else, and the final answer in yet another place. Once that happens, the example becomes harder to read and harder to maintain.
|
|
2634
2679
|
|
|
2635
|
-
|
|
2680
|
+
### What SQL contributes
|
|
2636
2681
|
|
|
2637
|
-
|
|
2682
|
+
SQL is strong when the main task is structured data and queries over that data. It is excellent for tables, filtering, aggregation, joins, and efficient execution. When an Eyeling example is translated into DuckDB, SQL can do a surprising amount. Recursive queries can express route search. Views can express derived facts. Checks can be written as boolean queries. Output can be assembled from query results.
|
|
2638
2683
|
|
|
2639
|
-
|
|
2684
|
+
That is useful, and it shows that the examples can be operationalized in a relational setting.
|
|
2640
2685
|
|
|
2641
|
-
|
|
2686
|
+
However, SQL is not the original shape of these examples. To get there, the graph-like source has to be mapped into tables, and the rule logic has to be reconstructed with joins, common table expressions, macros, and views. The result can work well, but the conceptual structure becomes more indirect. Data and reasoning are still connected, but they are no longer expressed in the same native form.
|
|
2642
2687
|
|
|
2643
|
-
|
|
2688
|
+
In other words, SQL is a good execution target, but it is not always the clearest authoring language for this kind of material.
|
|
2644
2689
|
|
|
2645
|
-
|
|
2690
|
+
### What Prolog contributes
|
|
2646
2691
|
|
|
2647
|
-
|
|
2692
|
+
Prolog is strong when the main task is expressing facts and rules directly. An Eyeling example often looks much closer to Prolog than to SQL once the focus shifts to derivation. Facts become predicates. Rules become clauses. Recursive reasoning becomes natural. This makes Prolog a very good target when the aim is to express the logical behavior of an example clearly.
|
|
2648
2693
|
|
|
2649
|
-
|
|
2694
|
+
That is why Prolog translations of Eyeling examples often feel much cleaner than SQL translations. The rule layer fits naturally.
|
|
2695
|
+
|
|
2696
|
+
However, Prolog is not primarily a graph data notation. Eyeling examples often use a linked-data style in which named entities and relations remain visible as part of the knowledge representation. In Prolog this can certainly be modeled, but it is usually represented as application-specific predicates rather than as a graph-native notation. That means the rule side is natural, while the original data style becomes less central.
|
|
2697
|
+
|
|
2698
|
+
So Prolog captures the inference well, but it does not preserve the same linked-data feel as naturally as N3 does.
|
|
2699
|
+
|
|
2700
|
+
### Why N3 fits these examples well
|
|
2701
|
+
|
|
2702
|
+
N3 fits Eyeling well because it keeps the data model and the rule model close together.
|
|
2703
|
+
|
|
2704
|
+
The facts remain graph-shaped. Entities and relations can be written directly. Rules can be added in the same notation. Checks can be expressed next to the derivations they depend on. Even the final answer can remain part of the same artifact. This allows an example to stay compact from beginning to end.
|
|
2705
|
+
|
|
2706
|
+
That compactness is important. It means the reader can inspect one example and see the situation, the derivation, the checks, and the answer without mentally switching between several different layers of representation.
|
|
2707
|
+
|
|
2708
|
+
This is the main reason N3 feels like a sweet spot in Eyeling. Compared with SQL, it avoids the split between graph-shaped knowledge and relational encoding. Compared with Prolog, it avoids the split between logic programming and linked-data representation. It keeps both sides close enough that the whole example can stay in one place.
|
|
2709
|
+
|
|
2710
|
+
### Where this matters in practice
|
|
2711
|
+
|
|
2712
|
+
This matters most in examples where the structure of the knowledge is part of the point.
|
|
2713
|
+
|
|
2714
|
+
In the path-discovery example, the facts describe airports and routes, and the rule describes how a connection can be found through a bounded number of stopovers. In SQL, this becomes a recursive query over tables. In Prolog, it becomes a recursive predicate over facts. In N3, the graph and the rule remain in one notation.
|
|
2715
|
+
|
|
2716
|
+
In the barley-seed-becoming example, the facts describe stages, transitions, and constraints, and the rules determine what can and cannot become something else. In SQL and Prolog, this can be translated, but N3 preserves the original structure more directly.
|
|
2717
|
+
|
|
2718
|
+
In the delfour example, the same pattern becomes even clearer. The example combines facts about products and household needs, rules about authorization and recommendation, checks over the derived conclusions, and a final human-readable answer. That kind of example is exactly where a language that keeps data, rules, checks, and answers together becomes valuable.
|
|
2719
|
+
|
|
2720
|
+
### Conclusion
|
|
2721
|
+
|
|
2722
|
+
N3 is not the best language for every task. SQL is stronger as a database query language. Prolog is stronger as a pure rule language. But the Eyeling examples are not only database exercises and not only rule exercises.
|
|
2723
|
+
|
|
2724
|
+
They are compact knowledge artifacts in which facts, rules, checks, and answers belong together.
|
|
2725
|
+
|
|
2726
|
+
That is why N3 fits them so well. It is not because N3 wins an abstract language competition. It is because these examples need a form in which data and reasoning can remain unified. For Eyeling, that is exactly what N3 provides.
|
|
2650
2727
|
|
|
2651
2728
|
---
|
|
2652
2729
|
|
|
@@ -3680,3 +3757,23 @@ Herbrand semantics is fine because it gives symbolic reasoning a concrete semant
|
|
|
3680
3757
|
Gödel incompleteness is fine because the limits of formal systems are not a refutation of formal reasoning. They are part of its shape. Once a system becomes expressive enough, one should expect structural limits on what it can prove about itself. That does not make formal methods less serious. It shows that their boundaries are principled rather than accidental. For a handbook like this one, that is the right lesson: formal systems are valuable not because they say everything, but because they say some things clearly, explicitly, and in a form that can be checked.
|
|
3681
3758
|
|
|
3682
3759
|
Taken together, these positions support a straightforward attitude toward Eyeling. Horn logic is fine. Notation3 is fine. Executable specification is fine. Herbrand semantics is fine. Gödel incompleteness is fine. None of these commitments make the reasoner narrower in a harmful sense. They make it clearer, easier to inspect, and easier to trust. For this project, that is enough.
|
|
3760
|
+
|
|
3761
|
+
<a id="app-k"></a>
|
|
3762
|
+
|
|
3763
|
+
## Appendix K — Whitehead-inspired becoming examples
|
|
3764
|
+
|
|
3765
|
+
A small family of examples in the repository (`examples/*-becoming.n3`) explores a common idea: that logic can describe not only what **is** the case, but what a thing, system, lineage, or device can **become**. The inspiration is Whiteheadian in a broad sense. The examples do not attempt to formalize Whitehead’s metaphysics as scholarship. Instead, they borrow one guiding intuition from it: reality is often better understood as a structured passage from one state to another than as a mere inventory of static objects.
|
|
3766
|
+
|
|
3767
|
+
In N3 terms, this means the examples are written so that rules describe **state-transition potential**. Earlier examples in the handbook often use predicates such as `:can`, `:cannot`, `:supports`, or `:requires`. The becoming family shifts the emphasis toward predicates such as `:canBecome` and `:cannotBecome`, along with intermediate states such as protected dormancy, germination, negative differential response, or adaptive persistence. This is still ordinary Horn-style reasoning. The novelty is not in the engine, but in the modeling style.
|
|
3768
|
+
|
|
3769
|
+
The seven current becoming examples span several domains. One is a pure Whiteheadian toy model, where actual occasions prehend a past, respond to a lure of possibility, and become objectively available for future occasions. Others translate the same pattern into engineering revision, developmental genetics, control-systems design, constructor-theoretic task transition, barley-seed lineage renewal, and tunnel-junction wake switching. The common thread is always the same: an entity inherits a prior condition, encounters some enabling or disabling structure, and either reaches a new stabilized state or fails to do so.
|
|
3770
|
+
|
|
3771
|
+
That common pattern makes the examples useful pedagogically. They show that Eyeling is not limited to taxonomies, datatype checks, or one-step deductions. It can also express **process descriptions** in a compact symbolic form. A design revision can become a new approved baseline. A cell state can become a differentiated lineage state. A controller can become a validated closed-loop design. A substrate can become a new attribute-state under a possible task. A seed lineage can become a self-renewing cycle. A tunnel junction can become a low-bias wake-serving device.
|
|
3772
|
+
|
|
3773
|
+
These examples are also helpful because they keep different levels of abstraction visible. Some of them are deliberately metaphysical, some quasi-biological, some engineering-oriented, and some constructor-theoretic. But they all run through the same reasoner, using the same underlying machinery: terms, triples, forward rules, and closure. That is a quiet but important point. Eyeling does not care whether the domain is philosophy, control theory, genetics, or device physics. What matters is whether the modeled transitions can be stated clearly enough as explicit conditions and consequences.
|
|
3774
|
+
|
|
3775
|
+
The becoming examples should therefore be read as **executable schemata** rather than as complete scientific models. They intentionally simplify their domains. The engineering example does not replace design verification. The genetics example does not replace systems biology. The constructor-theory example does not replace the theory itself. And the Whitehead example is not a substitute for reading Whitehead. What the examples do show is that N3 can serve as a clean medium for expressing relational process in a way that remains inspectable, runnable, and easy to vary.
|
|
3776
|
+
|
|
3777
|
+
For the handbook, these examples matter for two reasons. First, they provide a concrete demonstration that Eyeling can handle a style of reasoning that feels closer to **becoming, development, and transformation** than to static classification. Second, they show how expressive gains can come from modeling choices rather than from adding new machinery to the engine. The same forward-chaining core that proves `:Socrates a :Mortal` can also prove that a lineage becomes evolvable, that a controller becomes approved, or that a wake switch becomes serviceable under a low-bias regime.
|
|
3778
|
+
|
|
3779
|
+
That is why this appendix belongs after Appendix J. “Formalism is fine” not only because it supports rigor, but because it can remain flexible enough to describe worlds in motion. The becoming examples are small demonstrations of that claim. They show that a compact N3 reasoner can host process-oriented models without ceasing to be simple, readable, and executable.
|
|
@@ -4935,6 +4935,7 @@
|
|
|
4935
4935
|
const engine = require('./engine');
|
|
4936
4936
|
const deref = require('./deref');
|
|
4937
4937
|
const { PrefixEnv } = require('./prelude');
|
|
4938
|
+
const { parseN3Text, mergeParsedDocuments } = require('./multisource');
|
|
4938
4939
|
|
|
4939
4940
|
function offsetToLineCol(text, offset) {
|
|
4940
4941
|
const chars = Array.from(text);
|
|
@@ -5018,8 +5019,9 @@
|
|
|
5018
5019
|
|
|
5019
5020
|
function printHelp(toStderr = false) {
|
|
5020
5021
|
const msg =
|
|
5021
|
-
`Usage: ${prog} [options] [file.n3|-]\n\n` +
|
|
5022
|
-
`When no file is given and stdin is piped, read N3 from stdin.\n
|
|
5022
|
+
`Usage: ${prog} [options] [file-or-url.n3|- ...]\n\n` +
|
|
5023
|
+
`When no file is given and stdin is piped, read N3 from stdin.\n` +
|
|
5024
|
+
`When multiple inputs are given, parse each source separately, merge ASTs, then reason once.\n\n` +
|
|
5023
5025
|
`Options:\n` +
|
|
5024
5026
|
` -a, --ast Print parsed AST as JSON and exit.\n` +
|
|
5025
5027
|
` --builtin <module.js> Load a custom builtin module (repeatable).\n` +
|
|
@@ -5063,7 +5065,7 @@
|
|
|
5063
5065
|
builtinModules.push(a.slice('--builtin='.length));
|
|
5064
5066
|
continue;
|
|
5065
5067
|
}
|
|
5066
|
-
if (!a.startsWith('-')) positional.push(a);
|
|
5068
|
+
if (a === '-' || !a.startsWith('-')) positional.push(a);
|
|
5067
5069
|
}
|
|
5068
5070
|
|
|
5069
5071
|
const showAst = argv.includes('--ast') || argv.includes('-a');
|
|
@@ -5089,17 +5091,12 @@
|
|
|
5089
5091
|
if (typeof engine.setSuperRestrictedMode === 'function') engine.setSuperRestrictedMode(true);
|
|
5090
5092
|
}
|
|
5091
5093
|
|
|
5092
|
-
// Positional args (
|
|
5094
|
+
// Positional args (one or more N3 sources).
|
|
5093
5095
|
const useImplicitStdin = positional.length === 0 && !process.stdin.isTTY;
|
|
5094
5096
|
if (positional.length === 0 && !useImplicitStdin) {
|
|
5095
5097
|
printHelp(false);
|
|
5096
5098
|
process.exit(0);
|
|
5097
5099
|
}
|
|
5098
|
-
if (positional.length > 1) {
|
|
5099
|
-
console.error('Error: expected at most one input [file.n3|-].');
|
|
5100
|
-
printHelp(true);
|
|
5101
|
-
process.exit(1);
|
|
5102
|
-
}
|
|
5103
5100
|
|
|
5104
5101
|
for (const spec of builtinModules) {
|
|
5105
5102
|
try {
|
|
@@ -5113,35 +5110,47 @@
|
|
|
5113
5110
|
}
|
|
5114
5111
|
}
|
|
5115
5112
|
|
|
5116
|
-
const
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
let text;
|
|
5120
|
-
try {
|
|
5121
|
-
text = __readInputSourceSync(sourceLabel);
|
|
5122
|
-
} catch (e) {
|
|
5123
|
-
if (sourceLabel === '<stdin>') console.error(`Error reading stdin: ${e.message}`);
|
|
5124
|
-
else console.error(`Error reading file ${JSON.stringify(sourceLabel)}: ${e.message}`);
|
|
5113
|
+
const sourceLabels = useImplicitStdin ? ['<stdin>'] : positional.map((item) => (item === '-' ? '<stdin>' : item));
|
|
5114
|
+
if (sourceLabels.filter((item) => item === '<stdin>').length > 1) {
|
|
5115
|
+
console.error('Error: stdin can only be used once.');
|
|
5125
5116
|
process.exit(1);
|
|
5126
5117
|
}
|
|
5127
5118
|
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
engine.setTracePrefixes(prefixes);
|
|
5137
|
-
} catch (e) {
|
|
5138
|
-
if (e && e.name === 'N3SyntaxError') {
|
|
5139
|
-
console.error(formatN3SyntaxError(e, text, sourceLabel));
|
|
5119
|
+
const parsedSources = [];
|
|
5120
|
+
for (const sourceLabel of sourceLabels) {
|
|
5121
|
+
let text;
|
|
5122
|
+
try {
|
|
5123
|
+
text = __readInputSourceSync(sourceLabel);
|
|
5124
|
+
} catch (e) {
|
|
5125
|
+
if (sourceLabel === '<stdin>') console.error(`Error reading stdin: ${e.message}`);
|
|
5126
|
+
else console.error(`Error reading source ${JSON.stringify(sourceLabel)}: ${e.message}`);
|
|
5140
5127
|
process.exit(1);
|
|
5141
5128
|
}
|
|
5142
|
-
|
|
5129
|
+
|
|
5130
|
+
try {
|
|
5131
|
+
parsedSources.push(
|
|
5132
|
+
parseN3Text(text, {
|
|
5133
|
+
baseIri: __sourceLabelToBaseIri(sourceLabel),
|
|
5134
|
+
label: sourceLabel,
|
|
5135
|
+
}),
|
|
5136
|
+
);
|
|
5137
|
+
} catch (e) {
|
|
5138
|
+
if (e && e.name === 'N3SyntaxError') {
|
|
5139
|
+
console.error(formatN3SyntaxError(e, text, sourceLabel));
|
|
5140
|
+
process.exit(1);
|
|
5141
|
+
}
|
|
5142
|
+
throw e;
|
|
5143
|
+
}
|
|
5143
5144
|
}
|
|
5144
5145
|
|
|
5146
|
+
const mergedDocument = mergeParsedDocuments(parsedSources);
|
|
5147
|
+
const prefixes = mergedDocument.prefixes;
|
|
5148
|
+
const triples = mergedDocument.triples;
|
|
5149
|
+
const frules = mergedDocument.frules;
|
|
5150
|
+
const brules = mergedDocument.brules;
|
|
5151
|
+
const qrules = mergedDocument.logQueryRules;
|
|
5152
|
+
const tokenSets = parsedSources.map((source) => ({ tokens: source.tokens, prefixes: source.prefixes }));
|
|
5153
|
+
|
|
5145
5154
|
if (showAst) {
|
|
5146
5155
|
function astReplacer(unusedJsonKey, value) {
|
|
5147
5156
|
if (value instanceof Set) return Array.from(value);
|
|
@@ -5285,7 +5294,10 @@
|
|
|
5285
5294
|
const mayAutoRenderOutputStrings = programMayProduceOutputStrings(triples, frules, qrules);
|
|
5286
5295
|
|
|
5287
5296
|
if (streamMode && !hasQueries && !mayAutoRenderOutputStrings) {
|
|
5288
|
-
const usedInInput =
|
|
5297
|
+
const usedInInput = new Set();
|
|
5298
|
+
for (const source of tokenSets) {
|
|
5299
|
+
for (const pfx of prefixesUsedInInputTokens(source.tokens, source.prefixes)) usedInInput.add(pfx);
|
|
5300
|
+
}
|
|
5289
5301
|
const outPrefixes = restrictPrefixEnv(prefixes, usedInInput);
|
|
5290
5302
|
|
|
5291
5303
|
// Ensure log:trace uses the same compact prefix set as the output.
|
|
@@ -5879,6 +5891,7 @@
|
|
|
5879
5891
|
const { lex, N3SyntaxError } = require('./lexer');
|
|
5880
5892
|
const { Parser } = require('./parser');
|
|
5881
5893
|
const { liftBlankRuleVars } = require('./rules');
|
|
5894
|
+
const { parseN3SourceList } = require('./multisource');
|
|
5882
5895
|
|
|
5883
5896
|
const {
|
|
5884
5897
|
makeBuiltins,
|
|
@@ -9228,7 +9241,8 @@ ${triples.map((tr) => ` ${tripleToN3(tr, prefixes)}`).join('\n')}
|
|
|
9228
9241
|
builtinModules = null,
|
|
9229
9242
|
} = opts;
|
|
9230
9243
|
|
|
9231
|
-
const
|
|
9244
|
+
const parsedSourceList = parseN3SourceList(input, { baseIri });
|
|
9245
|
+
const parsedInput = parsedSourceList || normalizeParsedReasonerInputSync(input);
|
|
9232
9246
|
const rdfFactory = rdfjs ? getDataFactory(dataFactory) : null;
|
|
9233
9247
|
|
|
9234
9248
|
const __oldEnforceHttps = deref.getEnforceHttpsEnabled();
|
|
@@ -9371,7 +9385,7 @@ ${triples.map((tr) => ` ${tripleToN3(tr, prefixes)}`).join('\n')}
|
|
|
9371
9385
|
|
|
9372
9386
|
Promise.resolve().then(async () => {
|
|
9373
9387
|
try {
|
|
9374
|
-
const normalizedInput = await normalizeReasonerInputAsync(input);
|
|
9388
|
+
const normalizedInput = parseN3SourceList(input, restOpts) || (await normalizeReasonerInputAsync(input));
|
|
9375
9389
|
reasonStream(normalizedInput, {
|
|
9376
9390
|
...restOpts,
|
|
9377
9391
|
rdfjs: false,
|
|
@@ -10566,6 +10580,206 @@ ${triples.map((tr) => ` ${tripleToN3(tr, prefixes)}`).join('\n')}
|
|
|
10566
10580
|
|
|
10567
10581
|
module.exports = { Token, N3SyntaxError, lex, decodeN3StringEscapes };
|
|
10568
10582
|
};
|
|
10583
|
+
__modules['lib/multisource.js'] = function (require, module, exports) {
|
|
10584
|
+
/**
|
|
10585
|
+
* Eyeling Reasoner — multi-source parsing helpers
|
|
10586
|
+
*
|
|
10587
|
+
* These helpers let the CLI/API parse several N3 documents independently and
|
|
10588
|
+
* merge their parsed ASTs before reasoning. This avoids building one giant N3
|
|
10589
|
+
* string while preserving the existing lexer/parser/engine pipeline.
|
|
10590
|
+
*/
|
|
10591
|
+
|
|
10592
|
+
'use strict';
|
|
10593
|
+
|
|
10594
|
+
const { lex } = require('./lexer');
|
|
10595
|
+
const { Parser } = require('./parser');
|
|
10596
|
+
const {
|
|
10597
|
+
Blank,
|
|
10598
|
+
ListTerm,
|
|
10599
|
+
OpenListTerm,
|
|
10600
|
+
GraphTerm,
|
|
10601
|
+
Triple,
|
|
10602
|
+
Rule,
|
|
10603
|
+
PrefixEnv,
|
|
10604
|
+
annotateQuotedGraphTerm,
|
|
10605
|
+
} = require('./prelude');
|
|
10606
|
+
|
|
10607
|
+
function emptyParsedDocument() {
|
|
10608
|
+
return {
|
|
10609
|
+
prefixes: PrefixEnv.newDefault(),
|
|
10610
|
+
triples: [],
|
|
10611
|
+
frules: [],
|
|
10612
|
+
brules: [],
|
|
10613
|
+
logQueryRules: [],
|
|
10614
|
+
};
|
|
10615
|
+
}
|
|
10616
|
+
|
|
10617
|
+
function parseN3Text(text, opts = {}) {
|
|
10618
|
+
const { baseIri = '', label = '<input>' } = opts || {};
|
|
10619
|
+
const tokens = lex(text);
|
|
10620
|
+
const parser = new Parser(tokens);
|
|
10621
|
+
if (baseIri) parser.prefixes.setBase(baseIri);
|
|
10622
|
+
const [prefixes, triples, frules, brules, logQueryRules] = parser.parseDocument();
|
|
10623
|
+
return { prefixes, triples, frules, brules, logQueryRules, tokens, text, label };
|
|
10624
|
+
}
|
|
10625
|
+
|
|
10626
|
+
function sourceBlankPrefix(sourceIndex) {
|
|
10627
|
+
return `_:src${sourceIndex}_`;
|
|
10628
|
+
}
|
|
10629
|
+
|
|
10630
|
+
function scopedBlankLabel(label, sourceIndex, mapping) {
|
|
10631
|
+
const key = String(label || '');
|
|
10632
|
+
let out = mapping.get(key);
|
|
10633
|
+
if (out) return out;
|
|
10634
|
+
|
|
10635
|
+
const bare = key.startsWith('_:') ? key.slice(2) : key;
|
|
10636
|
+
out = sourceBlankPrefix(sourceIndex) + bare;
|
|
10637
|
+
mapping.set(key, out);
|
|
10638
|
+
return out;
|
|
10639
|
+
}
|
|
10640
|
+
|
|
10641
|
+
function scopeBlankNodesInDocument(doc, sourceIndex) {
|
|
10642
|
+
const mapping = new Map();
|
|
10643
|
+
|
|
10644
|
+
function cloneTerm(term) {
|
|
10645
|
+
if (term instanceof Blank) return new Blank(scopedBlankLabel(term.label, sourceIndex, mapping));
|
|
10646
|
+
if (term instanceof ListTerm) return new ListTerm(term.elems.map(cloneTerm));
|
|
10647
|
+
if (term instanceof OpenListTerm) return new OpenListTerm(term.prefix.map(cloneTerm), term.tailVar);
|
|
10648
|
+
if (term instanceof GraphTerm) return annotateQuotedGraphTerm(new GraphTerm(term.triples.map(cloneTriple)));
|
|
10649
|
+
return term;
|
|
10650
|
+
}
|
|
10651
|
+
|
|
10652
|
+
function cloneTriple(triple) {
|
|
10653
|
+
return new Triple(cloneTerm(triple.s), cloneTerm(triple.p), cloneTerm(triple.o));
|
|
10654
|
+
}
|
|
10655
|
+
|
|
10656
|
+
function cloneRule(rule) {
|
|
10657
|
+
const headBlankLabels = new Set();
|
|
10658
|
+
if (rule && rule.headBlankLabels instanceof Set) {
|
|
10659
|
+
for (const label of rule.headBlankLabels) headBlankLabels.add(scopedBlankLabel(label, sourceIndex, mapping));
|
|
10660
|
+
}
|
|
10661
|
+
|
|
10662
|
+
const out = new Rule(
|
|
10663
|
+
(rule.premise || []).map(cloneTriple),
|
|
10664
|
+
(rule.conclusion || []).map(cloneTriple),
|
|
10665
|
+
rule.isForward,
|
|
10666
|
+
rule.isFuse,
|
|
10667
|
+
headBlankLabels,
|
|
10668
|
+
);
|
|
10669
|
+
|
|
10670
|
+
if (rule && Object.prototype.hasOwnProperty.call(rule, '__dynamicConclusionTerm')) {
|
|
10671
|
+
Object.defineProperty(out, '__dynamicConclusionTerm', {
|
|
10672
|
+
value: cloneTerm(rule.__dynamicConclusionTerm),
|
|
10673
|
+
enumerable: false,
|
|
10674
|
+
writable: false,
|
|
10675
|
+
configurable: true,
|
|
10676
|
+
});
|
|
10677
|
+
}
|
|
10678
|
+
|
|
10679
|
+
return out;
|
|
10680
|
+
}
|
|
10681
|
+
|
|
10682
|
+
return {
|
|
10683
|
+
prefixes: doc.prefixes,
|
|
10684
|
+
triples: (doc.triples || []).map(cloneTriple),
|
|
10685
|
+
frules: (doc.frules || []).map(cloneRule),
|
|
10686
|
+
brules: (doc.brules || []).map(cloneRule),
|
|
10687
|
+
logQueryRules: (doc.logQueryRules || []).map(cloneRule),
|
|
10688
|
+
tokens: doc.tokens,
|
|
10689
|
+
text: doc.text,
|
|
10690
|
+
label: doc.label,
|
|
10691
|
+
};
|
|
10692
|
+
}
|
|
10693
|
+
|
|
10694
|
+
function mergePrefixEnvs(target, source) {
|
|
10695
|
+
if (!source) return target;
|
|
10696
|
+
const map = source.map || {};
|
|
10697
|
+
for (const [prefix, iri] of Object.entries(map)) {
|
|
10698
|
+
// Every parser starts with an empty default namespace. Do not let a later
|
|
10699
|
+
// source that never declared ':' erase a useful default namespace from an
|
|
10700
|
+
// earlier source; prefix merging is for output readability only.
|
|
10701
|
+
if (iri || !Object.prototype.hasOwnProperty.call(target.map, prefix)) target.set(prefix, iri);
|
|
10702
|
+
}
|
|
10703
|
+
if (source.baseIri) target.setBase(source.baseIri);
|
|
10704
|
+
return target;
|
|
10705
|
+
}
|
|
10706
|
+
|
|
10707
|
+
function mergeParsedDocuments(docs, opts = {}) {
|
|
10708
|
+
const documents = Array.isArray(docs) ? docs : [];
|
|
10709
|
+
const scopeBlankNodes = typeof opts.scopeBlankNodes === 'boolean' ? opts.scopeBlankNodes : documents.length > 1;
|
|
10710
|
+
|
|
10711
|
+
const merged = emptyParsedDocument();
|
|
10712
|
+
const mergedSources = [];
|
|
10713
|
+
|
|
10714
|
+
for (let i = 0; i < documents.length; i++) {
|
|
10715
|
+
const originalDoc = documents[i] || emptyParsedDocument();
|
|
10716
|
+
const doc = scopeBlankNodes ? scopeBlankNodesInDocument(originalDoc, i + 1) : originalDoc;
|
|
10717
|
+
|
|
10718
|
+
mergePrefixEnvs(merged.prefixes, doc.prefixes);
|
|
10719
|
+
merged.triples.push(...(doc.triples || []));
|
|
10720
|
+
merged.frules.push(...(doc.frules || []));
|
|
10721
|
+
merged.brules.push(...(doc.brules || []));
|
|
10722
|
+
merged.logQueryRules.push(...(doc.logQueryRules || []));
|
|
10723
|
+
mergedSources.push(doc);
|
|
10724
|
+
}
|
|
10725
|
+
|
|
10726
|
+
Object.defineProperty(merged, 'sources', {
|
|
10727
|
+
value: mergedSources,
|
|
10728
|
+
enumerable: false,
|
|
10729
|
+
writable: false,
|
|
10730
|
+
configurable: true,
|
|
10731
|
+
});
|
|
10732
|
+
|
|
10733
|
+
return merged;
|
|
10734
|
+
}
|
|
10735
|
+
|
|
10736
|
+
function isN3SourceListInput(input) {
|
|
10737
|
+
return !!(input && typeof input === 'object' && !Array.isArray(input) && Array.isArray(input.sources));
|
|
10738
|
+
}
|
|
10739
|
+
|
|
10740
|
+
function normalizeN3SourceItem(source, index) {
|
|
10741
|
+
const sourceNumber = index + 1;
|
|
10742
|
+
if (typeof source === 'string') {
|
|
10743
|
+
return { text: source, label: `<source ${sourceNumber}>`, baseIri: '' };
|
|
10744
|
+
}
|
|
10745
|
+
if (!source || typeof source !== 'object' || Array.isArray(source)) {
|
|
10746
|
+
throw new TypeError('Each N3 source must be a string or an object with an n3/text field');
|
|
10747
|
+
}
|
|
10748
|
+
|
|
10749
|
+
const text = typeof source.n3 === 'string' ? source.n3 : typeof source.text === 'string' ? source.text : null;
|
|
10750
|
+
if (text === null) throw new TypeError('Each N3 source object must provide an n3 or text string');
|
|
10751
|
+
|
|
10752
|
+
return {
|
|
10753
|
+
text,
|
|
10754
|
+
label: typeof source.label === 'string' && source.label ? source.label : `<source ${sourceNumber}>`,
|
|
10755
|
+
baseIri: typeof source.baseIri === 'string' ? source.baseIri : '',
|
|
10756
|
+
};
|
|
10757
|
+
}
|
|
10758
|
+
|
|
10759
|
+
function parseN3SourceList(input, opts = {}) {
|
|
10760
|
+
if (!isN3SourceListInput(input)) return null;
|
|
10761
|
+
const sources = input.sources.map(normalizeN3SourceItem);
|
|
10762
|
+
const defaultBaseIri = typeof opts.baseIri === 'string' ? opts.baseIri : '';
|
|
10763
|
+
const parsed = sources.map((source, index) =>
|
|
10764
|
+
parseN3Text(source.text, {
|
|
10765
|
+
label: source.label,
|
|
10766
|
+
baseIri: source.baseIri || (sources.length === 1 ? defaultBaseIri : ''),
|
|
10767
|
+
}),
|
|
10768
|
+
);
|
|
10769
|
+
return mergeParsedDocuments(parsed, {
|
|
10770
|
+
scopeBlankNodes: typeof input.scopeBlankNodes === 'boolean' ? input.scopeBlankNodes : parsed.length > 1,
|
|
10771
|
+
});
|
|
10772
|
+
}
|
|
10773
|
+
|
|
10774
|
+
module.exports = {
|
|
10775
|
+
emptyParsedDocument,
|
|
10776
|
+
parseN3Text,
|
|
10777
|
+
mergeParsedDocuments,
|
|
10778
|
+
scopeBlankNodesInDocument,
|
|
10779
|
+
isN3SourceListInput,
|
|
10780
|
+
parseN3SourceList,
|
|
10781
|
+
};
|
|
10782
|
+
};
|
|
10569
10783
|
__modules['lib/parser.js'] = function (require, module, exports) {
|
|
10570
10784
|
/**
|
|
10571
10785
|
* Eyeling Reasoner — parser
|