eyeling 1.33.0 → 1.33.2
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 +9 -1
- package/bin/eyeling.cjs +1 -1
- package/dist/browser/eyeling.browser.js +2 -12
- package/examples/eyelang/INTEGRATION.md +7 -0
- package/examples/eyelang/README.md +4 -0
- package/examples/eyelang/output/vulnerability-impact.pl +20 -0
- package/examples/eyelang/vulnerability-impact.pl +70 -0
- package/examples/output/vulnerability-impact.n3 +22 -0
- package/examples/vulnerability-impact.n3 +80 -0
- package/eyeling-builtins.ttl +1 -1
- package/eyeling.js +2 -12
- package/index.js +1 -1
- package/lib/builtins.js +1 -11
- package/lib/cli.js +1 -1
- package/lib/eyelang/bin.js +1 -1
- package/lib/eyelang/builtins/{aggregation.mjs → aggregation.js} +1 -1
- package/lib/eyelang/builtins/{alphametic.mjs → alphametic.js} +1 -1
- package/lib/eyelang/builtins/{arithmetic.mjs → arithmetic.js} +1 -1
- package/lib/eyelang/builtins/{core.mjs → core.js} +1 -1
- package/lib/eyelang/builtins/{formula.mjs → formula.js} +1 -1
- package/lib/eyelang/builtins/{lists.mjs → lists.js} +1 -1
- package/lib/eyelang/builtins/{matrix.mjs → matrix.js} +1 -1
- package/lib/eyelang/builtins/{n3.mjs → n3.js} +4 -4
- package/lib/eyelang/builtins/{number-theory.mjs → number-theory.js} +1 -1
- package/lib/eyelang/builtins/{portfolio.mjs → portfolio.js} +1 -1
- package/lib/eyelang/builtins/{registry.mjs → registry.js} +14 -14
- package/lib/eyelang/builtins/{search.mjs → search.js} +2 -2
- package/lib/eyelang/builtins/{strings.mjs → strings.js} +1 -1
- package/lib/eyelang/builtins/{sudoku.mjs → sudoku.js} +1 -1
- package/lib/eyelang/{cli.mjs → cli.js} +5 -5
- package/lib/eyelang/{explain.mjs → explain.js} +4 -4
- package/lib/eyelang/{index.mjs → index.js} +12 -12
- package/lib/eyelang/package.json +3 -0
- package/lib/eyelang/{parser.mjs → parser.js} +1 -1
- package/lib/eyelang/{program.mjs → program.js} +3 -3
- package/lib/eyelang/{rdf.mjs → rdf.js} +1 -1
- package/lib/eyelang/{solver.mjs → solver.js} +4 -4
- package/package.json +3 -3
- package/test/builtins.test.js +15 -2
- package/test/eyelang/run-conformance.mjs +1 -1
- package/test/eyelang/run-examples.mjs +1 -1
- package/test/eyelang/run-regression.mjs +4 -4
- package/test/eyelang/test-style.mjs +10 -7
- package/test/eyelang.test.js +1 -1
- package/test/run.js +186 -37
- /package/lib/eyelang/builtins/{control.mjs → control.js} +0 -0
- /package/lib/eyelang/{hash.mjs → hash.js} +0 -0
- /package/lib/eyelang/{term.mjs → term.js} +0 -0
package/README.md
CHANGED
|
@@ -631,6 +631,13 @@ Use the CLI option when running `.pl` programs:
|
|
|
631
631
|
eyeling --engine eyelang examples/eyelang/ancestor.pl
|
|
632
632
|
```
|
|
633
633
|
|
|
634
|
+
A paired example shows the same vulnerability-impact scenario in both engines:
|
|
635
|
+
|
|
636
|
+
```bash
|
|
637
|
+
eyeling examples/vulnerability-impact.n3
|
|
638
|
+
eyeling --engine eyelang examples/eyelang/vulnerability-impact.pl
|
|
639
|
+
```
|
|
640
|
+
|
|
634
641
|
Use the CommonJS convenience API when the rest of your application already imports `eyeling`:
|
|
635
642
|
|
|
636
643
|
```js
|
|
@@ -791,7 +798,7 @@ Eyeling provides datatype built-ins in the namespace `https://eyereasoner.github
|
|
|
791
798
|
|
|
792
799
|
Supported operations include:
|
|
793
800
|
|
|
794
|
-
- `dt:datatype`, `dt:lexicalForm`, and `dt:language` for literal inspection;
|
|
801
|
+
- `dt:datatype`, `dt:lexicalForm`, and `dt:language` for literal inspection. `dt:datatype` returns the literal's actual datatype IRI only, so RDF string literals return `xsd:string`; it does not return `rdfs:Literal`, which is a class of literals rather than a datatype IRI;
|
|
795
802
|
- `dt:validForDatatype` and `dt:invalidForDatatype` for lexical validity and datatype membership checks, either as `?literal dt:validForDatatype ?datatype` tests or as tuple-to-boolean checks like `(?literal ?datatype) dt:validForDatatype true`;
|
|
796
803
|
- `dt:sameValueAs` and `dt:differentValueFrom` for value-space equality and inequality;
|
|
797
804
|
- `dt:canonicalLiteral` for canonical literal production.
|
|
@@ -1171,6 +1178,7 @@ The repository contains more than two hundred N3 examples under `examples/`, plu
|
|
|
1171
1178
|
| `examples/age.n3` | Literal propagation. |
|
|
1172
1179
|
| `examples/family-cousins.n3` | Multi-hop relational inference. |
|
|
1173
1180
|
| `examples/dijkstra.n3` | Graph/path reasoning. |
|
|
1181
|
+
| `examples/vulnerability-impact.n3` and `examples/eyelang/vulnerability-impact.pl` | Paired N3 and eyelang dependency-risk example. |
|
|
1174
1182
|
| `examples/list-map.n3` | List processing. |
|
|
1175
1183
|
| `examples/string-builtins-tests.n3` | String built-ins. |
|
|
1176
1184
|
| `examples/math-builtins-tests.n3` | Numeric built-ins. |
|
package/bin/eyeling.cjs
CHANGED
|
@@ -37,7 +37,7 @@ function normalizeEngineName(value) {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
async function runEyelangCli(argv) {
|
|
40
|
-
const cliUrl = pathToFileURL(path.join(__dirname, '..', 'lib', 'eyelang', 'cli.
|
|
40
|
+
const cliUrl = pathToFileURL(path.join(__dirname, '..', 'lib', 'eyelang', 'cli.js')).href;
|
|
41
41
|
const cli = await import(cliUrl);
|
|
42
42
|
await cli.main(argv);
|
|
43
43
|
}
|
|
@@ -1888,17 +1888,7 @@ function evalDatatypeInspectionBuiltin(g, subst, kind) {
|
|
|
1888
1888
|
const dt = literalDatatypeIri(g.s);
|
|
1889
1889
|
if (dt === null) return [];
|
|
1890
1890
|
valueTerm = internIri(dt);
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
// OWL 2 RL datatype rules often need common string literals to also
|
|
1894
|
-
// participate in generic literal comparisons, while application rules still
|
|
1895
|
-
// ask for their precise datatype explicitly. When the datatype output is
|
|
1896
|
-
// unbound, expose rdfs:Literal as an additional super-datatype answer for
|
|
1897
|
-
// xsd:string and rdf:langString; bound-object calls remain strictly exact.
|
|
1898
|
-
if (g.o instanceof Var && (dt === XSD_STRING_DT || dt === RDF_LANGSTRING_DT)) {
|
|
1899
|
-
out.push(...evalBindBuiltinObject(g.o, internIri(RDFS_LITERAL_DT), subst));
|
|
1900
|
-
}
|
|
1901
|
-
return out;
|
|
1891
|
+
return evalBindBuiltinObject(g.o, valueTerm, subst);
|
|
1902
1892
|
} else if (kind === 'lexicalForm') {
|
|
1903
1893
|
valueTerm = makeStringLiteral(literalLexicalValue(g.s.value));
|
|
1904
1894
|
} else if (kind === 'language') {
|
|
@@ -6493,7 +6483,7 @@ async function main() {
|
|
|
6493
6483
|
`Options:\n` +
|
|
6494
6484
|
` -a, --ast Print parsed AST as JSON and exit.\n` +
|
|
6495
6485
|
` --builtin <module.js> Load a custom builtin module (repeatable).\n` +
|
|
6496
|
-
` --engine <n3|eyelang>
|
|
6486
|
+
` --engine <n3|eyelang> Select the N3 engine (default) or eyelang engine.\n` +
|
|
6497
6487
|
` -d, --deterministic-skolem Make log:skolem stable across reasoning runs.\n` +
|
|
6498
6488
|
` -e, --enforce-https Rewrite http:// IRIs to https:// for log dereferencing builtins.\n` +
|
|
6499
6489
|
` -h, --help Show this help and exit.\n` +
|
|
@@ -6,6 +6,13 @@ This directory contains eyelang examples that can be exercised through Eyeling's
|
|
|
6
6
|
eyeling --engine eyelang examples/eyelang/ancestor.pl
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
+
For a paired example, compare the N3 and eyelang versions of the same dependency-risk scenario:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
eyeling examples/vulnerability-impact.n3
|
|
13
|
+
eyeling --engine eyelang examples/eyelang/vulnerability-impact.pl
|
|
14
|
+
```
|
|
15
|
+
|
|
9
16
|
The eyelang conformance corpus is kept under `test/eyelang/` and can be run with:
|
|
10
17
|
|
|
11
18
|
```bash
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# eyelang
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/eyelang)
|
|
4
|
+
[](https://doi.org/10.5281/zenodo.20342331)
|
|
5
|
+
|
|
3
6
|
eyelang is a small rule engine for Prolog-style Horn clauses over ordinary terms, lists, arithmetic, strings, and finite search. The command-line executable is `eyelang`.
|
|
4
7
|
|
|
5
8
|
Programs write relations directly, for example `ancestor(pat, emma)` or `status(case1, accepted)`. eyelang output is ordinary eyelang syntax: by default, the CLI materializes selected answer facts and prints those facts only. Pass `--proof` (or `-p`) when you also want each answer followed by a `why/2` explanation fact that records the proof. Programs may add `materialize(Name, Arity).` declarations to focus output on selected predicates.
|
|
@@ -512,6 +515,7 @@ The repository includes examples for recursion, graph reachability, finite searc
|
|
|
512
515
|
| [`traveling-salesman.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/traveling-salesman.pl) | Finds an optimal traveling-salesman tour. | [`output/traveling-salesman.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/output/traveling-salesman.pl) |
|
|
513
516
|
| [`turing.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/turing.pl) | Simulates a binary-increment Turing machine. | [`output/turing.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/output/turing.pl) |
|
|
514
517
|
| [`vector-similarity.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/vector-similarity.pl) | Computes dot product, norm, and cosine similarity. | [`output/vector-similarity.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/output/vector-similarity.pl) |
|
|
518
|
+
| [`vulnerability-impact.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/vulnerability-impact.pl) | Analyzes vulnerable transitive dependencies and urgent patch impact. | [`output/vulnerability-impact.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/output/vulnerability-impact.pl) |
|
|
515
519
|
| [`witch.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/witch.pl) | Derives the classic “burn the witch” N3 rule chain. | [`output/witch.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/output/witch.pl) |
|
|
516
520
|
| [`wolf-goat-cabbage.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/wolf-goat-cabbage.pl) | Solves the wolf-goat-cabbage river crossing. | [`output/wolf-goat-cabbage.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/output/wolf-goat-cabbage.pl) |
|
|
517
521
|
| [`zebra.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/zebra.pl) | Solves the zebra logic puzzle. | [`output/zebra.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/output/zebra.pl) |
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
uses(webapp, api).
|
|
2
|
+
uses(worker, queue).
|
|
3
|
+
uses(frontend, ui).
|
|
4
|
+
uses(webapp, auth).
|
|
5
|
+
uses(worker, openssl).
|
|
6
|
+
uses(frontend, css).
|
|
7
|
+
uses(webapp, openssl).
|
|
8
|
+
impacted_by(webapp, api).
|
|
9
|
+
impacted_by(worker, queue).
|
|
10
|
+
impacted_by(frontend, ui).
|
|
11
|
+
impacted_by(webapp, auth).
|
|
12
|
+
impacted_by(worker, openssl).
|
|
13
|
+
impacted_by(frontend, css).
|
|
14
|
+
impacted_by(webapp, openssl).
|
|
15
|
+
risk(worker, high).
|
|
16
|
+
risk(webapp, high).
|
|
17
|
+
mitigate(worker, cve_2026_0001).
|
|
18
|
+
mitigate(webapp, cve_2026_0001).
|
|
19
|
+
urgent_patch(webapp, openssl_3_0_14).
|
|
20
|
+
urgent_patch(worker, openssl_3_0_14).
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
% Vulnerability impact analysis over a transitive dependency graph.
|
|
2
|
+
%
|
|
3
|
+
% This mirrors examples/vulnerability-impact.n3 using eyelang rules.
|
|
4
|
+
|
|
5
|
+
% Output declarations: materialize/2 selects the relations written to this example's golden output.
|
|
6
|
+
materialize(uses, 2).
|
|
7
|
+
materialize(impacted_by, 2).
|
|
8
|
+
materialize(risk, 2).
|
|
9
|
+
materialize(mitigate, 2).
|
|
10
|
+
materialize(urgent_patch, 2).
|
|
11
|
+
|
|
12
|
+
% Program structure: facts set up the scenario, and rules derive the materialized conclusions.
|
|
13
|
+
service(webapp).
|
|
14
|
+
service(worker).
|
|
15
|
+
service(frontend).
|
|
16
|
+
|
|
17
|
+
critical(webapp).
|
|
18
|
+
critical(worker).
|
|
19
|
+
|
|
20
|
+
depends_on(webapp, api).
|
|
21
|
+
depends_on(worker, queue).
|
|
22
|
+
depends_on(frontend, ui).
|
|
23
|
+
depends_on(api, auth).
|
|
24
|
+
depends_on(auth, openssl).
|
|
25
|
+
depends_on(queue, openssl).
|
|
26
|
+
depends_on(ui, css).
|
|
27
|
+
|
|
28
|
+
vulnerability(openssl, cve_2026_0001).
|
|
29
|
+
cvss(cve_2026_0001, 9.8).
|
|
30
|
+
fixed_by(cve_2026_0001, openssl_3_0_14).
|
|
31
|
+
|
|
32
|
+
vulnerability(css, cve_2026_0002).
|
|
33
|
+
cvss(cve_2026_0002, 5.4).
|
|
34
|
+
fixed_by(cve_2026_0002, css_2_1_1).
|
|
35
|
+
|
|
36
|
+
% Derivation rules: each rule below contributes one logical step toward the displayed results.
|
|
37
|
+
uses(Service, Dependency) :-
|
|
38
|
+
service(Service),
|
|
39
|
+
depends_on(Service, Dependency).
|
|
40
|
+
|
|
41
|
+
uses(Service, Dependency) :-
|
|
42
|
+
service(Service),
|
|
43
|
+
depends_on(Service, Intermediate),
|
|
44
|
+
depends_on(Intermediate, Dependency).
|
|
45
|
+
|
|
46
|
+
uses(Service, Dependency) :-
|
|
47
|
+
service(Service),
|
|
48
|
+
depends_on(Service, Intermediate),
|
|
49
|
+
depends_on(Intermediate, Next),
|
|
50
|
+
depends_on(Next, Dependency).
|
|
51
|
+
|
|
52
|
+
impacted_by(Service, Dependency) :-
|
|
53
|
+
uses(Service, Dependency).
|
|
54
|
+
|
|
55
|
+
exposed_to(Service, Vulnerability) :-
|
|
56
|
+
uses(Service, Package),
|
|
57
|
+
vulnerability(Package, Vulnerability),
|
|
58
|
+
cvss(Vulnerability, Score),
|
|
59
|
+
gt(Score, 7.0).
|
|
60
|
+
|
|
61
|
+
risk(Service, high) :-
|
|
62
|
+
exposed_to(Service, _Vulnerability).
|
|
63
|
+
|
|
64
|
+
mitigate(Service, Vulnerability) :-
|
|
65
|
+
exposed_to(Service, Vulnerability).
|
|
66
|
+
|
|
67
|
+
urgent_patch(Service, Patch) :-
|
|
68
|
+
critical(Service),
|
|
69
|
+
exposed_to(Service, Vulnerability),
|
|
70
|
+
fixed_by(Vulnerability, Patch).
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
@prefix : <https://example.org/vulnerability-impact#> .
|
|
2
|
+
|
|
3
|
+
:webapp :uses :api .
|
|
4
|
+
:worker :uses :queue .
|
|
5
|
+
:frontend :uses :ui .
|
|
6
|
+
:webapp :uses :auth .
|
|
7
|
+
:worker :uses :openssl .
|
|
8
|
+
:frontend :uses :css .
|
|
9
|
+
:webapp :uses :openssl .
|
|
10
|
+
:worker :risk :high .
|
|
11
|
+
:worker :mitigate :CVE_2026_0001 .
|
|
12
|
+
:webapp :risk :high .
|
|
13
|
+
:webapp :mitigate :CVE_2026_0001 .
|
|
14
|
+
:webapp :urgentPatch :openssl_3_0_14 .
|
|
15
|
+
:worker :urgentPatch :openssl_3_0_14 .
|
|
16
|
+
:webapp :impactedBy :api .
|
|
17
|
+
:worker :impactedBy :queue .
|
|
18
|
+
:frontend :impactedBy :ui .
|
|
19
|
+
:webapp :impactedBy :auth .
|
|
20
|
+
:worker :impactedBy :openssl .
|
|
21
|
+
:frontend :impactedBy :css .
|
|
22
|
+
:webapp :impactedBy :openssl .
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# ===========================================================
|
|
2
|
+
# Vulnerability impact analysis over a transitive dependency graph
|
|
3
|
+
# ===========================================================
|
|
4
|
+
|
|
5
|
+
@prefix math: <http://www.w3.org/2000/10/swap/math#>.
|
|
6
|
+
@prefix : <https://example.org/vulnerability-impact#>.
|
|
7
|
+
|
|
8
|
+
# Services.
|
|
9
|
+
:webapp a :Service; :critical true; :dependsOn :api.
|
|
10
|
+
:worker a :Service; :critical true; :dependsOn :queue.
|
|
11
|
+
:frontend a :Service; :dependsOn :ui.
|
|
12
|
+
|
|
13
|
+
# Component dependencies.
|
|
14
|
+
:api :dependsOn :auth.
|
|
15
|
+
:auth :dependsOn :openssl.
|
|
16
|
+
:queue :dependsOn :openssl.
|
|
17
|
+
:ui :dependsOn :css.
|
|
18
|
+
|
|
19
|
+
# Vulnerability data.
|
|
20
|
+
:openssl :vulnerability :CVE_2026_0001.
|
|
21
|
+
:CVE_2026_0001 :cvss 9.8; :fixedBy :openssl_3_0_14.
|
|
22
|
+
|
|
23
|
+
:css :vulnerability :CVE_2026_0002.
|
|
24
|
+
:CVE_2026_0002 :cvss 5.4; :fixedBy :css_2_1_1.
|
|
25
|
+
|
|
26
|
+
# A service uses its direct dependencies and the components they pull in.
|
|
27
|
+
{
|
|
28
|
+
?S a :Service.
|
|
29
|
+
?S :dependsOn ?D.
|
|
30
|
+
} => {
|
|
31
|
+
?S :uses ?D.
|
|
32
|
+
}.
|
|
33
|
+
|
|
34
|
+
{
|
|
35
|
+
?S a :Service.
|
|
36
|
+
?S :dependsOn ?M.
|
|
37
|
+
?M :dependsOn ?D.
|
|
38
|
+
} => {
|
|
39
|
+
?S :uses ?D.
|
|
40
|
+
}.
|
|
41
|
+
|
|
42
|
+
{
|
|
43
|
+
?S a :Service.
|
|
44
|
+
?S :dependsOn ?M.
|
|
45
|
+
?M :dependsOn ?N.
|
|
46
|
+
?N :dependsOn ?D.
|
|
47
|
+
} => {
|
|
48
|
+
?S :uses ?D.
|
|
49
|
+
}.
|
|
50
|
+
|
|
51
|
+
# Materialize the dependency closure for impact review.
|
|
52
|
+
{
|
|
53
|
+
?S :uses ?D.
|
|
54
|
+
} => {
|
|
55
|
+
?S :impactedBy ?D.
|
|
56
|
+
}.
|
|
57
|
+
|
|
58
|
+
# A service is high risk when any reachable component has a severe vulnerability.
|
|
59
|
+
{
|
|
60
|
+
?S :uses ?Pkg.
|
|
61
|
+
?Pkg :vulnerability ?V.
|
|
62
|
+
?V :cvss ?Score.
|
|
63
|
+
?Score math:greaterThan 7.0.
|
|
64
|
+
} => {
|
|
65
|
+
?S :risk :high.
|
|
66
|
+
?S :mitigate ?V.
|
|
67
|
+
}.
|
|
68
|
+
|
|
69
|
+
# Critical services with a severe reachable vulnerability get the available patch first.
|
|
70
|
+
{
|
|
71
|
+
?S a :Service.
|
|
72
|
+
?S :critical true.
|
|
73
|
+
?S :uses ?Pkg.
|
|
74
|
+
?Pkg :vulnerability ?V.
|
|
75
|
+
?V :cvss ?Score.
|
|
76
|
+
?Score math:greaterThan 7.0.
|
|
77
|
+
?V :fixedBy ?Patch.
|
|
78
|
+
} => {
|
|
79
|
+
?S :urgentPatch ?Patch.
|
|
80
|
+
}.
|
package/eyeling-builtins.ttl
CHANGED
|
@@ -45,7 +45,7 @@ ex:aliasOf a rdf:Property .
|
|
|
45
45
|
# --- dt: datatype ---------------------------------------------------
|
|
46
46
|
|
|
47
47
|
dt:datatype a ex:Builtin ; ex:kind ex:Function ;
|
|
48
|
-
rdfs:comment "Extracts the datatype IRI of a literal. Plain RDF string literals return xsd:string; language-tagged strings return rdf:langString; shorthand numeric/boolean literals return their inferred XSD datatype." .
|
|
48
|
+
rdfs:comment "Extracts the datatype IRI of a literal. Plain RDF string literals return xsd:string; language-tagged strings return rdf:langString; shorthand numeric/boolean literals return their inferred XSD datatype. It returns the literal's actual datatype IRI only; rdfs:Literal is a class and is not returned by dt:datatype." .
|
|
49
49
|
|
|
50
50
|
dt:lexicalForm a ex:Builtin ; ex:kind ex:Function ;
|
|
51
51
|
rdfs:comment "Extracts the literal lexical form as an xsd:string/plain string value, preserving the original lexical spelling such as leading zeroes." .
|
package/eyeling.js
CHANGED
|
@@ -1888,17 +1888,7 @@ function evalDatatypeInspectionBuiltin(g, subst, kind) {
|
|
|
1888
1888
|
const dt = literalDatatypeIri(g.s);
|
|
1889
1889
|
if (dt === null) return [];
|
|
1890
1890
|
valueTerm = internIri(dt);
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
// OWL 2 RL datatype rules often need common string literals to also
|
|
1894
|
-
// participate in generic literal comparisons, while application rules still
|
|
1895
|
-
// ask for their precise datatype explicitly. When the datatype output is
|
|
1896
|
-
// unbound, expose rdfs:Literal as an additional super-datatype answer for
|
|
1897
|
-
// xsd:string and rdf:langString; bound-object calls remain strictly exact.
|
|
1898
|
-
if (g.o instanceof Var && (dt === XSD_STRING_DT || dt === RDF_LANGSTRING_DT)) {
|
|
1899
|
-
out.push(...evalBindBuiltinObject(g.o, internIri(RDFS_LITERAL_DT), subst));
|
|
1900
|
-
}
|
|
1901
|
-
return out;
|
|
1891
|
+
return evalBindBuiltinObject(g.o, valueTerm, subst);
|
|
1902
1892
|
} else if (kind === 'lexicalForm') {
|
|
1903
1893
|
valueTerm = makeStringLiteral(literalLexicalValue(g.s.value));
|
|
1904
1894
|
} else if (kind === 'language') {
|
|
@@ -6493,7 +6483,7 @@ async function main() {
|
|
|
6493
6483
|
`Options:\n` +
|
|
6494
6484
|
` -a, --ast Print parsed AST as JSON and exit.\n` +
|
|
6495
6485
|
` --builtin <module.js> Load a custom builtin module (repeatable).\n` +
|
|
6496
|
-
` --engine <n3|eyelang>
|
|
6486
|
+
` --engine <n3|eyelang> Select the N3 engine (default) or eyelang engine.\n` +
|
|
6497
6487
|
` -d, --deterministic-skolem Make log:skolem stable across reasoning runs.\n` +
|
|
6498
6488
|
` -e, --enforce-https Rewrite http:// IRIs to https:// for log dereferencing builtins.\n` +
|
|
6499
6489
|
` -h, --help Show this help and exit.\n` +
|
package/index.js
CHANGED
|
@@ -85,7 +85,7 @@ function reasonEyelangSync(opt = {}, input = '') {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
async function loadEyelangModule() {
|
|
88
|
-
return import(pathToFileURL(path.join(__dirname, 'lib', 'eyelang', 'index.
|
|
88
|
+
return import(pathToFileURL(path.join(__dirname, 'lib', 'eyelang', 'index.js')).href);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
async function runEyelang(input = '', opt = {}) {
|
package/lib/builtins.js
CHANGED
|
@@ -1877,17 +1877,7 @@ function evalDatatypeInspectionBuiltin(g, subst, kind) {
|
|
|
1877
1877
|
const dt = literalDatatypeIri(g.s);
|
|
1878
1878
|
if (dt === null) return [];
|
|
1879
1879
|
valueTerm = internIri(dt);
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
// OWL 2 RL datatype rules often need common string literals to also
|
|
1883
|
-
// participate in generic literal comparisons, while application rules still
|
|
1884
|
-
// ask for their precise datatype explicitly. When the datatype output is
|
|
1885
|
-
// unbound, expose rdfs:Literal as an additional super-datatype answer for
|
|
1886
|
-
// xsd:string and rdf:langString; bound-object calls remain strictly exact.
|
|
1887
|
-
if (g.o instanceof Var && (dt === XSD_STRING_DT || dt === RDF_LANGSTRING_DT)) {
|
|
1888
|
-
out.push(...evalBindBuiltinObject(g.o, internIri(RDFS_LITERAL_DT), subst));
|
|
1889
|
-
}
|
|
1890
|
-
return out;
|
|
1880
|
+
return evalBindBuiltinObject(g.o, valueTerm, subst);
|
|
1891
1881
|
} else if (kind === 'lexicalForm') {
|
|
1892
1882
|
valueTerm = makeStringLiteral(literalLexicalValue(g.s.value));
|
|
1893
1883
|
} else if (kind === 'language') {
|
package/lib/cli.js
CHANGED
|
@@ -805,7 +805,7 @@ async function main() {
|
|
|
805
805
|
`Options:\n` +
|
|
806
806
|
` -a, --ast Print parsed AST as JSON and exit.\n` +
|
|
807
807
|
` --builtin <module.js> Load a custom builtin module (repeatable).\n` +
|
|
808
|
-
` --engine <n3|eyelang>
|
|
808
|
+
` --engine <n3|eyelang> Select the N3 engine (default) or eyelang engine.\n` +
|
|
809
809
|
` -d, --deterministic-skolem Make log:skolem stable across reasoning runs.\n` +
|
|
810
810
|
` -e, --enforce-https Rewrite http:// IRIs to https:// for log dereferencing builtins.\n` +
|
|
811
811
|
` -h, --help Show this help and exit.\n` +
|
package/lib/eyelang/bin.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
import('./cli.
|
|
4
|
+
import('./cli.js').then(({ main }) => main(process.argv.slice(2))).catch((error) => {
|
|
5
5
|
console.error(`eyelang: ${error && error.message ? error.message : String(error)}`);
|
|
6
6
|
process.exit(1);
|
|
7
7
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Aggregation builtins that run a inner goal and collect, count, sum, or select the best answers.
|
|
2
2
|
// Each handler clones the solver so the inner goal can enumerate independently of the outer goal.
|
|
3
|
-
import { compareTerms, copyResolved, isDecimalInteger, lexicalValue, listFromItems, numberTerm, numberTextFromDouble, parseFiniteNumber, unify } from '../term.
|
|
3
|
+
import { compareTerms, copyResolved, isDecimalInteger, lexicalValue, listFromItems, numberTerm, numberTextFromDouble, parseFiniteNumber, unify } from '../term.js';
|
|
4
4
|
|
|
5
5
|
export const aggregationBuiltins = {
|
|
6
6
|
register(registry) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// It evaluates column constraints right-to-left, so examples such as
|
|
3
3
|
// SEND+MORE=MONEY and DONALD+GERALD=ROBERT do not need to express digit search
|
|
4
4
|
// with many relational select/3 and arithmetic goals.
|
|
5
|
-
import { atom, deref, listFromItems, numberTerm, properListItems, unify } from '../term.
|
|
5
|
+
import { atom, deref, listFromItems, numberTerm, properListItems, unify } from '../term.js';
|
|
6
6
|
|
|
7
7
|
export const alphameticBuiltins = {
|
|
8
8
|
register(registry) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Numeric builtins for integer-preserving arithmetic, floating point functions, comparisons, and ranges.
|
|
2
2
|
// The code keeps BigInt paths where possible so large eyelang integers remain exact.
|
|
3
|
-
import { compareIntegerText, deref, isDecimalInteger, lexicalValue, numberTerm, numberTextFromDouble, parseFiniteNumber, unify } from '../term.
|
|
3
|
+
import { compareIntegerText, deref, isDecimalInteger, lexicalValue, numberTerm, numberTextFromDouble, parseFiniteNumber, unify } from '../term.js';
|
|
4
4
|
|
|
5
5
|
const unaryNames = ['neg', 'abs', 'sin', 'cos', 'asin', 'acos', 'rounded', 'log'];
|
|
6
6
|
const binaryNames = ['add', 'sub', 'mul', 'div', 'mod', 'max', 'min', 'pow'];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Core relational builtins that do not naturally belong to arithmetic, strings, lists, or aggregation.
|
|
2
2
|
// They are deterministic filters/projections and should avoid enumerating additional answers.
|
|
3
|
-
import { stringTerm, lexicalValue, unify } from '../term.
|
|
3
|
+
import { stringTerm, lexicalValue, unify } from '../term.js';
|
|
4
4
|
|
|
5
5
|
function* ok(env) { yield env; }
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Formula builtins that treat conjunctions as first-class data terms.
|
|
2
2
|
// These are used by examples that construct or inspect rule bodies programmatically.
|
|
3
|
-
import { atom, deref, isConjunction, unify } from '../term.
|
|
3
|
+
import { atom, deref, isConjunction, unify } from '../term.js';
|
|
4
4
|
|
|
5
5
|
export const formulaBuiltins = {
|
|
6
6
|
register(registry) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// List builtins for proper lists, selection, membership, sorting, and indexing.
|
|
2
2
|
// Several predicates support both checking and generation, so the argument modes are handled explicitly.
|
|
3
|
-
import { compareTerms, copyResolved, deref, isCons, lexicalValue, listFromItems, numberTerm, properListItems, unify } from '../term.
|
|
3
|
+
import { compareTerms, copyResolved, deref, isCons, lexicalValue, listFromItems, numberTerm, properListItems, unify } from '../term.js';
|
|
4
4
|
|
|
5
5
|
export const listBuiltins = {
|
|
6
6
|
register(registry) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Small dense-matrix arithmetic builtins.
|
|
2
2
|
// These are reusable numeric kernels for ground matrices; when arguments are not
|
|
3
3
|
// ground proper numeric lists, user-defined matrix predicates remain available.
|
|
4
|
-
import { deref, isDecimalInteger, listFromItems, numberTerm, numberTextFromDouble, parseFiniteNumber, properListItems, unify } from '../term.
|
|
4
|
+
import { deref, isDecimalInteger, listFromItems, numberTerm, numberTextFromDouble, parseFiniteNumber, properListItems, unify } from '../term.js';
|
|
5
5
|
|
|
6
6
|
export const matrixBuiltins = {
|
|
7
7
|
register(registry) {
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// RDF parser lowers body triples with math:, string:, list:, crypto:, time:, and
|
|
3
3
|
// selected log: predicates to n3_* eyelang builtins. The handlers work on the
|
|
4
4
|
// explicit RDF term representation from src/rdf.js.
|
|
5
|
-
import { hashHex } from '../hash.
|
|
6
|
-
import { compareLexicalOrNumeric } from './arithmetic.
|
|
7
|
-
import { deref, isDecimalInteger, listFromItems, parseFiniteNumber, properListItems, termToString, unify } from '../term.
|
|
8
|
-
import { RDF_DIR_LANG_STRING, RDF_LANG_STRING, XSD_BOOLEAN, XSD_DECIMAL, XSD_DOUBLE, XSD_INTEGER, XSD_STRING, rdfIri, rdfLiteral } from '../rdf.
|
|
5
|
+
import { hashHex } from '../hash.js';
|
|
6
|
+
import { compareLexicalOrNumeric } from './arithmetic.js';
|
|
7
|
+
import { deref, isDecimalInteger, listFromItems, parseFiniteNumber, properListItems, termToString, unify } from '../term.js';
|
|
8
|
+
import { RDF_DIR_LANG_STRING, RDF_LANG_STRING, XSD_BOOLEAN, XSD_DECIMAL, XSD_DOUBLE, XSD_INTEGER, XSD_STRING, rdfIri, rdfLiteral } from '../rdf.js';
|
|
9
9
|
|
|
10
10
|
const XSD_NS = 'http://www.w3.org/2001/XMLSchema#';
|
|
11
11
|
const XSD_DATE_TIME = `${XSD_NS}dateTime`;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// arithmetic algorithms with many small recursive rule calls.
|
|
3
3
|
// The predicates are intentionally general: extended GCD, Collatz trajectories,
|
|
4
4
|
// Kaprekar iteration counts, and Goldbach pair generation.
|
|
5
|
-
import { deref, listFromItems, numberTerm, unify } from '../term.
|
|
5
|
+
import { deref, listFromItems, numberTerm, unify } from '../term.js';
|
|
6
6
|
|
|
7
7
|
export const numberTheoryBuiltins = {
|
|
8
8
|
register(registry) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Reusable bounded-subset optimizer for finite knapsack-style examples.
|
|
2
2
|
// Items are p(Name, Value, Cost, Risk) terms; the builtin enumerates every
|
|
3
3
|
// subset whose total cost/risk stays within the supplied caps.
|
|
4
|
-
import { deref, listFromItems, numberTerm, properListItems, unify } from '../term.
|
|
4
|
+
import { deref, listFromItems, numberTerm, properListItems, unify } from '../term.js';
|
|
5
5
|
|
|
6
6
|
export const portfolioBuiltins = {
|
|
7
7
|
register(registry) {
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
// Registry for builtins and their execution metadata.
|
|
2
2
|
// The solver uses the metadata to know when a builtin is deterministic, mode-ready, or should fall back to user clauses.
|
|
3
|
-
import { arithmeticBuiltins } from './arithmetic.
|
|
4
|
-
import { coreBuiltins } from './core.
|
|
5
|
-
import { stringBuiltins } from './strings.
|
|
6
|
-
import { listBuiltins } from './lists.
|
|
7
|
-
import { aggregationBuiltins } from './aggregation.
|
|
8
|
-
import { formulaBuiltins } from './formula.
|
|
9
|
-
import { controlBuiltins } from './control.
|
|
10
|
-
import { sudokuBuiltins } from './sudoku.
|
|
11
|
-
import { portfolioBuiltins } from './portfolio.
|
|
12
|
-
import { searchBuiltins } from './search.
|
|
13
|
-
import { numberTheoryBuiltins } from './number-theory.
|
|
14
|
-
import { matrixBuiltins } from './matrix.
|
|
15
|
-
import { alphameticBuiltins } from './alphametic.
|
|
16
|
-
import { n3Builtins } from './n3.
|
|
3
|
+
import { arithmeticBuiltins } from './arithmetic.js';
|
|
4
|
+
import { coreBuiltins } from './core.js';
|
|
5
|
+
import { stringBuiltins } from './strings.js';
|
|
6
|
+
import { listBuiltins } from './lists.js';
|
|
7
|
+
import { aggregationBuiltins } from './aggregation.js';
|
|
8
|
+
import { formulaBuiltins } from './formula.js';
|
|
9
|
+
import { controlBuiltins } from './control.js';
|
|
10
|
+
import { sudokuBuiltins } from './sudoku.js';
|
|
11
|
+
import { portfolioBuiltins } from './portfolio.js';
|
|
12
|
+
import { searchBuiltins } from './search.js';
|
|
13
|
+
import { numberTheoryBuiltins } from './number-theory.js';
|
|
14
|
+
import { matrixBuiltins } from './matrix.js';
|
|
15
|
+
import { alphameticBuiltins } from './alphametic.js';
|
|
16
|
+
import { n3Builtins } from './n3.js';
|
|
17
17
|
|
|
18
18
|
export class BuiltinRegistry {
|
|
19
19
|
constructor() {
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// of their time in small relational generators. These predicates are generic
|
|
3
3
|
// entry points (graph, CNF, QMC, range, and n-queens helpers), not compiled
|
|
4
4
|
// replacements for particular example predicate names.
|
|
5
|
-
import { atom, compound, deref, listFromItems, numberTerm, properListItems, unify } from '../term.
|
|
6
|
-
import { compareLexicalOrNumeric } from './arithmetic.
|
|
5
|
+
import { atom, compound, deref, listFromItems, numberTerm, properListItems, unify } from '../term.js';
|
|
6
|
+
import { compareLexicalOrNumeric } from './arithmetic.js';
|
|
7
7
|
|
|
8
8
|
export const searchBuiltins = {
|
|
9
9
|
register(registry) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// String and atom conversion builtins.
|
|
2
2
|
// They mostly project from already-ground terms to avoid guessing string domains.
|
|
3
|
-
import { atom, lexicalValue, stringTerm, unify } from '../term.
|
|
3
|
+
import { atom, lexicalValue, stringTerm, unify } from '../term.js';
|
|
4
4
|
|
|
5
5
|
export const stringBuiltins = {
|
|
6
6
|
register(registry) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Sudoku-specific helpers used by the example suite.
|
|
2
2
|
// The cover9/1 optimization prunes invalid row blocks early while leaving the declarative fallback available.
|
|
3
|
-
import { deref, lexicalValue, listFromItems, numberTerm, properListItems, unify } from '../term.
|
|
3
|
+
import { deref, lexicalValue, listFromItems, numberTerm, properListItems, unify } from '../term.js';
|
|
4
4
|
|
|
5
5
|
export const sudokuBuiltins = {
|
|
6
6
|
register(registry) {
|
|
@@ -87,10 +87,10 @@ export async function main(argv) {
|
|
|
87
87
|
async function loadEngine() {
|
|
88
88
|
if (engineModule == null) {
|
|
89
89
|
const [term, program, solver, registry] = await Promise.all([
|
|
90
|
-
import('./term.
|
|
91
|
-
import('./program.
|
|
92
|
-
import('./solver.
|
|
93
|
-
import('./builtins/registry.
|
|
90
|
+
import('./term.js'),
|
|
91
|
+
import('./program.js'),
|
|
92
|
+
import('./solver.js'),
|
|
93
|
+
import('./builtins/registry.js'),
|
|
94
94
|
]);
|
|
95
95
|
engineModule = { ...term, ...program, ...solver, ...registry };
|
|
96
96
|
}
|
|
@@ -98,7 +98,7 @@ async function loadEngine() {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
async function loadExplanation() {
|
|
101
|
-
if (explanationModule == null) explanationModule = await import('./explain.
|
|
101
|
+
if (explanationModule == null) explanationModule = await import('./explain.js');
|
|
102
102
|
return explanationModule;
|
|
103
103
|
}
|
|
104
104
|
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// The explanation printer replays a successful goal against the program and emits
|
|
3
3
|
// ordinary eyelang facts with nested proof terms. Explanations are therefore both
|
|
4
4
|
// human-readable and machine-readable.
|
|
5
|
-
import { COMPOUND, Env, Term, VAR, deref, flattenConjunction, freshTerm, termToString, unify, variantTerms } from './term.
|
|
6
|
-
import { selectClauseCandidates } from './program.
|
|
7
|
-
import { createDefaultRegistry } from './builtins/registry.
|
|
8
|
-
import { Solver, nextFreshId } from './solver.
|
|
5
|
+
import { COMPOUND, Env, Term, VAR, deref, flattenConjunction, freshTerm, termToString, unify, variantTerms } from './term.js';
|
|
6
|
+
import { selectClauseCandidates } from './program.js';
|
|
7
|
+
import { createDefaultRegistry } from './builtins/registry.js';
|
|
8
|
+
import { Solver, nextFreshId } from './solver.js';
|
|
9
9
|
|
|
10
10
|
export function whyProof(program, goal, options = {}) {
|
|
11
11
|
const maxDepth = options.maxDepth ?? 256;
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
// Public JavaScript API surface for embedders and the browser playground.
|
|
2
2
|
// The CLI imports the same parser, program, solver, and term primitives from here.
|
|
3
|
-
export { Program, makeProgram } from './program.
|
|
4
|
-
export { parseClauses, parseProgramText } from './parser.
|
|
5
|
-
export { parseRdfClauses, rdfToEyelang, clausesToEyelang, rdfIri, rdfBlank, rdfLiteral, rdfTripleTerm, rdfGoal } from './rdf.
|
|
6
|
-
export { Solver } from './solver.
|
|
7
|
-
export * from './term.
|
|
8
|
-
export { BuiltinRegistry, createDefaultRegistry, getDefaultRegistry } from './builtins/registry.
|
|
3
|
+
export { Program, makeProgram } from './program.js';
|
|
4
|
+
export { parseClauses, parseProgramText } from './parser.js';
|
|
5
|
+
export { parseRdfClauses, rdfToEyelang, clausesToEyelang, rdfIri, rdfBlank, rdfLiteral, rdfTripleTerm, rdfGoal } from './rdf.js';
|
|
6
|
+
export { Solver } from './solver.js';
|
|
7
|
+
export * from './term.js';
|
|
8
|
+
export { BuiltinRegistry, createDefaultRegistry, getDefaultRegistry } from './builtins/registry.js';
|
|
9
9
|
|
|
10
|
-
import { Env, copyResolved, termIsGround, termToString } from './term.
|
|
11
|
-
import { Program } from './program.
|
|
12
|
-
import { Solver } from './solver.
|
|
13
|
-
import { whyNoProof, whyProof } from './explain.
|
|
14
|
-
import { getDefaultRegistry } from './builtins/registry.
|
|
10
|
+
import { Env, copyResolved, termIsGround, termToString } from './term.js';
|
|
11
|
+
import { Program } from './program.js';
|
|
12
|
+
import { Solver } from './solver.js';
|
|
13
|
+
import { whyNoProof, whyProof } from './explain.js';
|
|
14
|
+
import { getDefaultRegistry } from './builtins/registry.js';
|
|
15
15
|
|
|
16
16
|
export function run(source, options = {}) {
|
|
17
17
|
const includeWhy = options.proof === true || options.why === true || options.explain === true;
|
|
@@ -45,4 +45,4 @@ function appendExplanation(output, program, resolved, registry) {
|
|
|
45
45
|
if (!proof.ok) output.push(whyNoProof(resolved));
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
export * from './explain.
|
|
48
|
+
export * from './explain.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Tokenizer and recursive-descent parser for the eyelang source language.
|
|
2
2
|
// It preserves the compact Prolog-like syntax while producing Term objects for the solver.
|
|
3
|
-
import { atom, compound, cons, emptyList, numberTerm, stringTerm, variable } from './term.
|
|
3
|
+
import { atom, compound, cons, emptyList, numberTerm, stringTerm, variable } from './term.js';
|
|
4
4
|
|
|
5
5
|
const TOK = {
|
|
6
6
|
EOF: 'eof', ATOM: 'atom', VAR: 'var', STRING: 'string', NUMBER: 'number',
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Program representation and clause indexing.
|
|
2
2
|
// Indexes are deliberately conservative: they speed up common scalar arguments but never replace unification as the final check.
|
|
3
|
-
import { ATOM, COMPOUND, Env, compound, deref, flattenConjunction, isScalar, termToString } from './term.
|
|
4
|
-
import { parseClauses } from './parser.
|
|
5
|
-
import { filenameLooksRdf, parseRdfClauses } from './rdf.
|
|
3
|
+
import { ATOM, COMPOUND, Env, compound, deref, flattenConjunction, isScalar, termToString } from './term.js';
|
|
4
|
+
import { parseClauses } from './parser.js';
|
|
5
|
+
import { filenameLooksRdf, parseRdfClauses } from './rdf.js';
|
|
6
6
|
|
|
7
7
|
export class Program {
|
|
8
8
|
constructor(clauses = [], options = {}) {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// N3 rules of the form `{ ... } => { ... } .` become Horn clauses whose heads
|
|
18
18
|
// and bodies are rdf/3 goals. RDF 1.2 reified triples and annotation syntax are
|
|
19
19
|
// expanded to rdf:reifies triples.
|
|
20
|
-
import { atom, compound, listFromItems, numberTerm, stringTerm, termToString, variable } from './term.
|
|
20
|
+
import { atom, compound, listFromItems, numberTerm, stringTerm, termToString, variable } from './term.js';
|
|
21
21
|
|
|
22
22
|
const TOKEN = {
|
|
23
23
|
EOF: 'eof', IRI: 'iri', STRING: 'string', NUMBER: 'number', NAME: 'name', BNODE: 'bnode', VAR: 'var', LANG: 'lang', ATWORD: 'atword',
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Depth-first eyelang solver with builtin dispatch, memoization, and guarded recursion handling.
|
|
2
2
|
// Most semantic decisions still flow through unification; optimizations only select candidates earlier.
|
|
3
|
-
import { COMPOUND, Env, flattenConjunction, freshTerm, termToString, unify, variantTerms } from './term.
|
|
4
|
-
import { createDefaultRegistry } from './builtins/registry.
|
|
5
|
-
import { selectClauseCandidates } from './program.
|
|
3
|
+
import { COMPOUND, Env, flattenConjunction, freshTerm, termToString, unify, variantTerms } from './term.js';
|
|
4
|
+
import { createDefaultRegistry } from './builtins/registry.js';
|
|
5
|
+
import { selectClauseCandidates } from './program.js';
|
|
6
6
|
|
|
7
7
|
let freshCounter = 0;
|
|
8
8
|
|
|
@@ -234,4 +234,4 @@ function importResolved(term, env) {
|
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
// Avoid circular import surprises in older Node loaders.
|
|
237
|
-
import * as termModuleCache from './term.
|
|
237
|
+
import * as termModuleCache from './term.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eyeling",
|
|
3
|
-
"version": "1.33.
|
|
3
|
+
"version": "1.33.2",
|
|
4
4
|
"description": "A minimal Notation3 (N3) reasoner in JavaScript.",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"keywords": [
|
|
@@ -87,8 +87,8 @@
|
|
|
87
87
|
"./package.json": "./package.json",
|
|
88
88
|
"./eyelang": {
|
|
89
89
|
"types": "./eyelang.d.ts",
|
|
90
|
-
"import": "./lib/eyelang/index.
|
|
91
|
-
"default": "./lib/eyelang/index.
|
|
90
|
+
"import": "./lib/eyelang/index.js",
|
|
91
|
+
"default": "./lib/eyelang/index.js"
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
94
|
"typesVersions": {
|
package/test/builtins.test.js
CHANGED
|
@@ -199,7 +199,6 @@ const cases = [
|
|
|
199
199
|
{ "11"^^xsd:integer dt:differentValueFrom "12"^^xsd:integer . } => { :different :numeric true } .
|
|
200
200
|
{ "hello@en"^^rdf:PlainLiteral dt:differentValueFrom "bye@en"^^rdf:PlainLiteral . } => { :different :plainLiteral true } .
|
|
201
201
|
{ "a" dt:differentValueFrom "b" . } => { :different :string true } .
|
|
202
|
-
{ "a" dt:datatype ?sd . ?sd <http://www.w3.org/2000/10/swap/log#notEqualTo> xsd:string . } => { :string :comparisonDatatype ?sd } .
|
|
203
202
|
{ "<a/>"^^rdf:XMLLiteral dt:differentValueFrom "<b/>"^^rdf:XMLLiteral . } => { :different :xmlLiteral true } .
|
|
204
203
|
|
|
205
204
|
{ ("1"^^xsd:integer xsd:integer) dt:validForDatatype true . } => { :tuple :valid true } .
|
|
@@ -252,7 +251,6 @@ const cases = [
|
|
|
252
251
|
assert.match(out, /:different :numeric true \./);
|
|
253
252
|
assert.match(out, /:different :plainLiteral true \./);
|
|
254
253
|
assert.match(out, /:different :string true \./);
|
|
255
|
-
assert.match(out, /:string :comparisonDatatype rdfs:Literal \./);
|
|
256
254
|
assert.match(out, /:different :xmlLiteral true \./);
|
|
257
255
|
assert.match(out, /:tuple :valid true \./);
|
|
258
256
|
assert.match(out, /:tuple :invalidBoolean true \./);
|
|
@@ -265,6 +263,21 @@ const cases = [
|
|
|
265
263
|
assert.match(out, /:canonical :dateTime "2026-06-10T12:00:00Z"\^\^xsd:dateTime \./);
|
|
266
264
|
assert.match(out, /:canonical :midnightRollover "2027-01-01T00:00:00Z"\^\^xsd:dateTime \./);
|
|
267
265
|
assert.match(out, /:sameAs :reflexive true \./);
|
|
266
|
+
|
|
267
|
+
const datatypeOut = runReason(`
|
|
268
|
+
@prefix : <http://example.org/datatype-tests#> .
|
|
269
|
+
@prefix dt: <https://eyereasoner.github.io/eyeling/datatype#> .
|
|
270
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
271
|
+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
272
|
+
:s :plainName "Alice" .
|
|
273
|
+
:s :typedName "Alice"^^xsd:string .
|
|
274
|
+
{ :s :plainName ?value . ?value dt:datatype ?datatype . } => { :plain :detectedDatatype ?datatype } .
|
|
275
|
+
{ :s :typedName ?value . ?value dt:datatype ?datatype . } => { :typed :detectedDatatype ?datatype } .
|
|
276
|
+
`);
|
|
277
|
+
assert.match(datatypeOut, /:plain :detectedDatatype xsd:string \./);
|
|
278
|
+
assert.match(datatypeOut, /:typed :detectedDatatype xsd:string \./);
|
|
279
|
+
assert.doesNotMatch(datatypeOut, /:plain :detectedDatatype rdfs:Literal \./);
|
|
280
|
+
assert.doesNotMatch(datatypeOut, /:typed :detectedDatatype rdfs:Literal \./);
|
|
268
281
|
},
|
|
269
282
|
},
|
|
270
283
|
{
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { spawnSync } from 'node:child_process';
|
|
7
|
-
import { Program, run } from '../../lib/eyelang/index.
|
|
7
|
+
import { Program, run } from '../../lib/eyelang/index.js';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { TestReporter, isMainModule } from './test-style.mjs';
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { spawnSync } from 'node:child_process';
|
|
7
|
-
import { Program, run } from '../../lib/eyelang/index.
|
|
7
|
+
import { Program, run } from '../../lib/eyelang/index.js';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { TestReporter, isMainModule } from './test-style.mjs';
|
|
10
10
|
|
|
@@ -31,11 +31,11 @@ import {
|
|
|
31
31
|
variantTerms,
|
|
32
32
|
parseProgramText,
|
|
33
33
|
rdfToEyelang,
|
|
34
|
-
} from '../../lib/eyelang/index.
|
|
35
|
-
import { parseGoalText } from '../../lib/eyelang/parser.
|
|
36
|
-
import { selectClauseCandidates } from '../../lib/eyelang/program.
|
|
34
|
+
} from '../../lib/eyelang/index.js';
|
|
35
|
+
import { parseGoalText } from '../../lib/eyelang/parser.js';
|
|
36
|
+
import { selectClauseCandidates } from '../../lib/eyelang/program.js';
|
|
37
37
|
import { TestReporter, isMainModule } from './test-style.mjs';
|
|
38
|
-
import { hashHex } from '../../lib/eyelang/hash.
|
|
38
|
+
import { hashHex } from '../../lib/eyelang/hash.js';
|
|
39
39
|
|
|
40
40
|
const testRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
|
|
41
41
|
const packageRoot = path.resolve(testRoot, '..', '..');
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
// Shared test output helpers.
|
|
2
2
|
// The runners use this small reporter so individual suites and `npm test` share
|
|
3
|
-
// one compact
|
|
3
|
+
// one compact Eyeling-style layout: colored OK/FAIL, sequence number, test description, and dimmed timing.
|
|
4
4
|
import process from 'node:process';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
|
|
8
|
-
const useColor =
|
|
8
|
+
const useColor = process.env.NO_COLOR == null && (
|
|
9
|
+
Boolean(process.stdout.isTTY) ||
|
|
10
|
+
Boolean(process.env.FORCE_COLOR && process.env.FORCE_COLOR !== '0')
|
|
11
|
+
);
|
|
9
12
|
|
|
10
13
|
export const colors = {
|
|
11
14
|
green: useColor ? '\x1b[32m' : '',
|
|
12
15
|
red: useColor ? '\x1b[31m' : '',
|
|
13
16
|
yellow: useColor ? '\x1b[33m' : '',
|
|
14
|
-
|
|
17
|
+
dim: useColor ? '\x1b[2m' : '',
|
|
15
18
|
reset: useColor ? '\x1b[0m' : '',
|
|
16
19
|
};
|
|
17
20
|
|
|
@@ -45,7 +48,7 @@ export class TestReporter {
|
|
|
45
48
|
const total = this.total - this.currentSection.totalAtStart;
|
|
46
49
|
const ms = nowMs() - this.currentSection.startedAt;
|
|
47
50
|
const suite = label ?? defaultSectionLabel(this.currentSection.name);
|
|
48
|
-
this.stdout.write(`${colors.green}OK${colors.reset} ${ok}/${total} ${suite} tests passed ${colors.
|
|
51
|
+
this.stdout.write(`${colors.green}OK${colors.reset} ${ok}/${total} ${suite} tests passed ${colors.dim}(${ms} ms)${colors.reset}\n`);
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
test(name, run) {
|
|
@@ -57,10 +60,10 @@ export class TestReporter {
|
|
|
57
60
|
run();
|
|
58
61
|
const ms = nowMs() - startedAt;
|
|
59
62
|
this.ok++;
|
|
60
|
-
this.stdout.write(`${colors.
|
|
63
|
+
this.stdout.write(`${colors.green}OK${colors.reset} ${nr} ${name} ${colors.dim}(${ms} ms)${colors.reset}\n`);
|
|
61
64
|
} catch (error) {
|
|
62
65
|
const ms = nowMs() - startedAt;
|
|
63
|
-
this.stderr.write(`${colors.
|
|
66
|
+
this.stderr.write(`${colors.red}FAIL${colors.reset} ${nr} ${name} ${colors.dim}(${ms} ms)${colors.reset}\n`);
|
|
64
67
|
this.stderr.write(`${error?.stack ?? String(error)}\n`);
|
|
65
68
|
throw error;
|
|
66
69
|
}
|
|
@@ -69,7 +72,7 @@ export class TestReporter {
|
|
|
69
72
|
totalLine() {
|
|
70
73
|
const ms = nowMs() - this.startedAt;
|
|
71
74
|
this.stdout.write(`\n${colors.yellow}== Total${colors.reset}\n`);
|
|
72
|
-
this.stdout.write(`${colors.green}OK${colors.reset} ${this.ok}/${this.total} tests passed ${colors.
|
|
75
|
+
this.stdout.write(`${colors.green}OK${colors.reset} ${this.ok}/${this.total} tests passed ${colors.dim}(${ms} ms)${colors.reset}\n`);
|
|
73
76
|
}
|
|
74
77
|
}
|
|
75
78
|
|
package/test/eyelang.test.js
CHANGED
|
@@ -47,7 +47,7 @@ out(X) :- in(X).
|
|
|
47
47
|
assert.match(directCli.stdout, /ancestor\(jan, emma\)\./);
|
|
48
48
|
|
|
49
49
|
// Sanity check the ESM file URL too; this catches nested package type regressions.
|
|
50
|
-
const esm = await import(pathToFileURL(`${process.cwd()}/lib/eyelang/index.
|
|
50
|
+
const esm = await import(pathToFileURL(`${process.cwd()}/lib/eyelang/index.js`).href);
|
|
51
51
|
assert.equal(typeof esm.Program, 'function');
|
|
52
52
|
|
|
53
53
|
pass(1, 'eyelang second-engine integration passed', Date.now() - startedAt);
|
package/test/run.js
CHANGED
|
@@ -5,57 +5,206 @@ const cp = require('node:child_process');
|
|
|
5
5
|
|
|
6
6
|
const { C, formatDuration } = require('./report');
|
|
7
7
|
|
|
8
|
+
const node = process.execPath;
|
|
9
|
+
|
|
10
|
+
// Run test files directly instead of through `npm run …`. This keeps the
|
|
11
|
+
// output in the compact Eyeling reporter style and avoids npm's script banners
|
|
12
|
+
// between the colored OK/FAIL test lines.
|
|
8
13
|
const sections = [
|
|
9
|
-
['Build bundle',
|
|
10
|
-
['Packlist checks',
|
|
11
|
-
['API tests',
|
|
12
|
-
['Streaming RDF Messages tests',
|
|
13
|
-
['Builtin contract tests',
|
|
14
|
-
['Store tests',
|
|
15
|
-
['Examples tests',
|
|
16
|
-
['Proof examples tests',
|
|
17
|
-
['Manifest tests',
|
|
18
|
-
['RDF 1.2 syntax tests',
|
|
19
|
-
['Playground tests',
|
|
20
|
-
['Eyelang second-engine tests',
|
|
21
|
-
['
|
|
14
|
+
['Build bundle', node, ['tools/bundle.js']],
|
|
15
|
+
['Packlist checks', node, ['test/packlist.test.js']],
|
|
16
|
+
['API tests', node, ['test/api.test.js']],
|
|
17
|
+
['Streaming RDF Messages tests', node, ['test/stream_messages.test.js']],
|
|
18
|
+
['Builtin contract tests', node, ['test/builtins.test.js']],
|
|
19
|
+
['Store tests', node, ['test/store.test.js']],
|
|
20
|
+
['Examples tests', node, ['test/examples.test.js']],
|
|
21
|
+
['Proof examples tests', node, ['test/examples.test.js', '--proof-only']],
|
|
22
|
+
['Manifest tests', node, ['test/manifest.test.js']],
|
|
23
|
+
['RDF 1.2 syntax tests', node, ['test/rdf12.test.js']],
|
|
24
|
+
['Playground tests', node, ['test/playground.test.js']],
|
|
25
|
+
['Eyelang second-engine tests', node, ['test/eyelang.test.js']],
|
|
26
|
+
['Eyelang corpus tests', node, ['test/eyelang/run-all.mjs']],
|
|
27
|
+
['Package tests', node, ['test/package.test.js']],
|
|
22
28
|
];
|
|
23
29
|
|
|
30
|
+
const aggregate = {
|
|
31
|
+
total: 0,
|
|
32
|
+
ok: 0,
|
|
33
|
+
incomplete: 0,
|
|
34
|
+
nonconform: 0,
|
|
35
|
+
crashed: 0,
|
|
36
|
+
};
|
|
37
|
+
|
|
24
38
|
function sectionLine(kind, label, ms) {
|
|
25
39
|
const suffix = typeof ms === 'number' ? ` (${formatDuration(ms)})` : '';
|
|
26
40
|
console.log(`${C.y}==${C.n} ${kind} ${label}${suffix}`);
|
|
27
41
|
}
|
|
28
42
|
|
|
43
|
+
function stripAnsi(s) {
|
|
44
|
+
return String(s).replace(/\x1b\[[0-9;]*m/g, '');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function makeSectionSummary() {
|
|
48
|
+
return {
|
|
49
|
+
// Normal Eyeling tests use report.js: "OK 001 ..." / "FAIL 001 ...".
|
|
50
|
+
reportOk: 0,
|
|
51
|
+
reportFail: 0,
|
|
52
|
+
// Older corpus runners used: "1 OK ..." / "1 FAIL ...". Keep
|
|
53
|
+
// parsing that shape so aggregate totals remain robust across local runs.
|
|
54
|
+
numberedOk: 0,
|
|
55
|
+
numberedFail: 0,
|
|
56
|
+
// notation3tests prints a suite-level line with the real manifest count.
|
|
57
|
+
manifestTotal: null,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function parseLineForSummary(rawLine, summary) {
|
|
62
|
+
const line = stripAnsi(rawLine).trimEnd();
|
|
63
|
+
if (!line) return;
|
|
64
|
+
|
|
65
|
+
if (/^OK\s+\d{3}\s/.test(line)) {
|
|
66
|
+
summary.reportOk++;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (/^FAIL\s+\d{3}\s/.test(line)) {
|
|
70
|
+
summary.reportFail++;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (/^\d+\s+OK\s+/.test(line)) {
|
|
74
|
+
summary.numberedOk++;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (/^\d+\s+FAIL\s+/.test(line)) {
|
|
78
|
+
summary.numberedFail++;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const manifest = line.match(
|
|
83
|
+
/^TOTAL\s+\[COUNT:(\d+)\]\s+OK:\s*[^0-9]*(\d+)[^0-9]+INCOMPLETE:\s*[^0-9]*(\d+)[^0-9]+NONCONFORM:\s*[^0-9]*(\d+)[^0-9]+CRASHED:\s*[^0-9]*(\d+)/,
|
|
84
|
+
);
|
|
85
|
+
if (manifest) {
|
|
86
|
+
summary.manifestTotal = {
|
|
87
|
+
count: Number(manifest[1]),
|
|
88
|
+
ok: Number(manifest[2]),
|
|
89
|
+
incomplete: Number(manifest[3]),
|
|
90
|
+
nonconform: Number(manifest[4]),
|
|
91
|
+
crashed: Number(manifest[5]),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function makeStreamingParser(summary) {
|
|
97
|
+
let buffer = '';
|
|
98
|
+
return {
|
|
99
|
+
feed(chunk) {
|
|
100
|
+
buffer += String(chunk);
|
|
101
|
+
const lines = buffer.split(/\r?\n/);
|
|
102
|
+
buffer = lines.pop() || '';
|
|
103
|
+
for (const line of lines) parseLineForSummary(line, summary);
|
|
104
|
+
},
|
|
105
|
+
finish() {
|
|
106
|
+
if (buffer) parseLineForSummary(buffer, summary);
|
|
107
|
+
buffer = '';
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function addSectionToAggregate(summary) {
|
|
113
|
+
if (summary.manifestTotal) {
|
|
114
|
+
aggregate.total += summary.manifestTotal.count;
|
|
115
|
+
aggregate.ok += summary.manifestTotal.ok;
|
|
116
|
+
aggregate.incomplete += summary.manifestTotal.incomplete;
|
|
117
|
+
aggregate.nonconform += summary.manifestTotal.nonconform;
|
|
118
|
+
aggregate.crashed += summary.manifestTotal.crashed;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const ok = summary.reportOk + summary.numberedOk;
|
|
123
|
+
const failed = summary.reportFail + summary.numberedFail;
|
|
124
|
+
aggregate.total += ok + failed;
|
|
125
|
+
aggregate.ok += ok;
|
|
126
|
+
aggregate.nonconform += failed;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function scoreLine() {
|
|
130
|
+
const score = aggregate.total === 0 ? 0 : (100 * aggregate.ok) / aggregate.total;
|
|
131
|
+
return `TOTAL [COUNT:${aggregate.total}] OK: ⭐${aggregate.ok}⭐ INCOMPLETE: ⭐${aggregate.incomplete}⭐ NONCONFORM: ⭐${aggregate.nonconform}⭐ CRASHED: ⭐${aggregate.crashed}⭐ => SCORE: ⭐⭐⭐${score.toFixed(1)}⭐⭐⭐`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function childEnv() {
|
|
135
|
+
const env = { ...process.env };
|
|
136
|
+
if (!env.NO_COLOR && C.g && !env.FORCE_COLOR) env.FORCE_COLOR = '1';
|
|
137
|
+
return env;
|
|
138
|
+
}
|
|
139
|
+
|
|
29
140
|
function runSection(label, cmd, args) {
|
|
30
141
|
console.log('');
|
|
31
142
|
sectionLine('Start', label);
|
|
32
143
|
const startedAt = Date.now();
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
144
|
+
const summary = makeSectionSummary();
|
|
145
|
+
const stdoutParser = makeStreamingParser(summary);
|
|
146
|
+
const stderrParser = makeStreamingParser(summary);
|
|
147
|
+
|
|
148
|
+
return new Promise((resolve) => {
|
|
149
|
+
const child = cp.spawn(cmd, args, {
|
|
150
|
+
cwd: process.cwd(),
|
|
151
|
+
env: childEnv(),
|
|
152
|
+
shell: process.platform === 'win32',
|
|
153
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
child.stdout.on('data', (chunk) => {
|
|
157
|
+
stdoutParser.feed(chunk);
|
|
158
|
+
process.stdout.write(chunk);
|
|
159
|
+
});
|
|
160
|
+
child.stderr.on('data', (chunk) => {
|
|
161
|
+
stderrParser.feed(chunk);
|
|
162
|
+
process.stderr.write(chunk);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
child.on('error', (err) => {
|
|
166
|
+
stdoutParser.finish();
|
|
167
|
+
stderrParser.finish();
|
|
168
|
+
addSectionToAggregate(summary);
|
|
169
|
+
aggregate.total++;
|
|
170
|
+
aggregate.crashed++;
|
|
171
|
+
const elapsed = Date.now() - startedAt;
|
|
172
|
+
console.error(`${C.r}FAIL${C.n} ${label}: ${err.message || String(err)}`);
|
|
173
|
+
sectionLine('End', `${label} failed`, elapsed);
|
|
174
|
+
console.log('');
|
|
175
|
+
resolve(1);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
child.on('close', (code, signal) => {
|
|
179
|
+
stdoutParser.finish();
|
|
180
|
+
stderrParser.finish();
|
|
181
|
+
addSectionToAggregate(summary);
|
|
182
|
+
const elapsed = Date.now() - startedAt;
|
|
183
|
+
const status = typeof code === 'number' ? code : 1;
|
|
184
|
+
if (status !== 0 && summary.reportFail + summary.numberedFail === 0 && !summary.manifestTotal) {
|
|
185
|
+
aggregate.total++;
|
|
186
|
+
aggregate.crashed++;
|
|
187
|
+
}
|
|
188
|
+
const suffix = signal ? ` (${signal})` : '';
|
|
189
|
+
sectionLine('End', status === 0 ? `${label} passed` : `${label} failed${suffix}`, elapsed);
|
|
190
|
+
console.log('');
|
|
191
|
+
resolve(status);
|
|
192
|
+
});
|
|
38
193
|
});
|
|
39
|
-
const elapsed = Date.now() - startedAt;
|
|
40
|
-
if (r.error) {
|
|
41
|
-
console.error(`${C.r}FAIL${C.n} ${label}: ${r.error.message || String(r.error)}`);
|
|
42
|
-
sectionLine('End', `${label} failed`, elapsed);
|
|
43
|
-
console.log('');
|
|
44
|
-
return 1;
|
|
45
|
-
}
|
|
46
|
-
const status = typeof r.status === 'number' ? r.status : 1;
|
|
47
|
-
sectionLine('End', status === 0 ? `${label} passed` : `${label} failed`, elapsed);
|
|
48
|
-
console.log('');
|
|
49
|
-
return status;
|
|
50
194
|
}
|
|
51
195
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
196
|
+
(async function main() {
|
|
197
|
+
let status = 0;
|
|
198
|
+
for (const [label, cmd, args] of sections) {
|
|
199
|
+
const sectionStatus = await runSection(label, cmd, args);
|
|
200
|
+
if (sectionStatus !== 0) {
|
|
201
|
+
status = sectionStatus;
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
58
204
|
}
|
|
59
|
-
}
|
|
60
205
|
|
|
61
|
-
|
|
206
|
+
console.log('');
|
|
207
|
+
console.log(scoreLine());
|
|
208
|
+
|
|
209
|
+
process.exit(status);
|
|
210
|
+
})();
|
|
File without changes
|
|
File without changes
|
|
File without changes
|