eyelang 1.7.15 → 1.7.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -49,6 +49,7 @@ console.log(result.stdout);
49
49
  - [Playground](https://eyereasoner.github.io/eyelang/playground)
50
50
  - [Guide](docs/guide.md)
51
51
  - [Language reference](docs/language-reference.md)
52
+ - [A Compact Reasoning Workbench](docs/compact-reasoning-workbench.md)
52
53
 
53
54
  For local browser use, serve the checkout first so the playground can load ES modules and example files:
54
55
 
@@ -0,0 +1,35 @@
1
+ # A Compact Reasoning Workbench
2
+
3
+ Eyelang began as a small thing: a few facts, a few rules, a way to ask a question and get an answer.
4
+
5
+ At first it solved the familiar problems. Paths through graphs. Ancestors in families. Tiny proofs that fit on one screen. The early examples in `examples/` keep that beginning visible: they show the core idiom before anything becomes exotic. Facts describe a world, rules extend it, goals ask for visible consequences, and answers come back as ordinary terms.
6
+
7
+ Then the examples started to widen.
8
+
9
+ Mathematical programs turned recurrence, tabling, aggregation, and finite search into executable relations. Fibonacci numbers, Collatz traces, integer partitions, Catalan numbers, binomial identities, Stirling and Bell numbers, graph paths, and scheduling problems made performance visible without hiding the logic behind a host-language library.
10
+
11
+ Knowledge-modeling examples pulled the same syntax in a different direction. Policies, GDPR-style compliance, access control, clinical screening, deontic rules, purpose mapping, alignment flows, and context audits were not mainly numerical problems. They were problems of explanation, qualification, exception, and traceability. In those cases Eyelang stayed close to the vocabulary of the domain.
12
+
13
+ Science and engineering examples stretched the language again. Filters, beams, control systems, kinetics, gas laws, gradients, coding theory, and optimization showed that structured calculation could live inside a larger rule-based argument. The programs remained small enough to read, but substantial enough to test the engine.
14
+
15
+ Then came examples that looked more like systems research than toy logic programs. Markov Logic Network-style scoring represented weighted possible worlds while keeping the probability model explicit. Type inference, abstract interpretation, pointer analysis, SAT solving, truth maintenance, and register allocation showed that programming-language and analysis problems could be written as compact theories rather than opaque procedures.
16
+
17
+ At the far edge were the deliberately uncomfortable examples: partial evaluation, Knuth-Bendix completion, CDCL-style SAT solving, and bounded equality saturation. They did not pretend that Eyelang was a mutable union-find library, an industrial optimizer, or a replacement for every specialized solver. They were useful precisely because they marked the boundary. Some problems had to be bounded. Some had to be expressed carefully. Some revealed where a small logic engine ends and another tool should take over.
18
+
19
+ That made `examples/` more than a gallery. It became executable documentation, regression evidence, and a map of the language's range. Each file was a compact theory with an expected output that could be run, inspected, and kept honest by the test suite.
20
+
21
+ The surprise was not that every hard problem became easy. They did not.
22
+
23
+ The surprise was that the same small core kept reaching.
24
+
25
+ Across mathematics, knowledge modeling, science, program analysis, symbolic reasoning, optimization, explanation, and search, Eyelang stayed recognizable. The programs were not black boxes. They were little theories: readable, testable, and executable. When performance mattered, tabling and careful formulations often carried them farther than expected.
26
+
27
+ By the end, Eyelang no longer looked like a toy language with a few clever demos.
28
+
29
+ It looked like a compact reasoning workbench.
30
+
31
+ Small enough to understand.
32
+
33
+ Expressive enough to surprise.
34
+
35
+ Fast enough to take seriously.
@@ -1,11 +1,8 @@
1
1
  % =============================================================================================================================
2
- % Deep Taxonomy - depth 10 - expanded N3-style eyelang with checkpoint proof accelerators
2
+ % Deep Taxonomy - depth 10 - expanded N3-style eyelang
3
3
  %
4
- % Adjacent rules mirror the Eyeling N3 deep-taxonomy chain. The checkpoint
5
- % rules are redundant consequences of that chain and are placed first so eyelang
6
- % can prove terminal/report goals without overflowing the JavaScript call stack.
7
- % Report/check rules use once/1 so successful checks do not backtrack into a
8
- % long adjacent-only proof after a shorter checkpoint proof has succeeded.
4
+ % Adjacent rules mirror the Eyeling N3 deep-taxonomy chain. Each step derives
5
+ % the next taxonomy class together with two side labels.
9
6
  % =============================================================================================================================
10
7
 
11
8
  % Output declarations: materialize/2 selects the relations written to this example's golden output.
@@ -27,12 +24,6 @@ a(ind, n0).
27
24
  is(test, true) :- once(a(ind, a2)).
28
25
  a(?x, a2) :- a(?x, n10).
29
26
 
30
- % Redundant checkpoint accelerators.
31
-
32
- a(?x, n10) :- a(?x, n0).
33
- a(?x, n4) :- a(?x, n0).
34
- a(?x, n9) :- a(?x, n0).
35
-
36
27
  % Adjacent N3-style taxonomy rules.
37
28
 
38
29
  a(?x, n1) :- a(?x, n0).
@@ -98,7 +89,7 @@ arc(check6, "C6 OK - the success flag is raised only after the terminal class a2
98
89
  answer(report, "The test succeeds: starting from one individual classified as n0, the rules eventually classify it as n10 and then as a2.") :-
99
90
  once(is(test, true)).
100
91
 
101
- reason(report, "The adjacent rules mirror the Eyeling N3 deep-taxonomy-10 chain. Redundant checkpoint rules are included only as proof accelerators: each shortcut is entailed by the adjacent chain, and once/1 prevents backtracking into the long adjacent-only proof after a check has succeeded.") :-
92
+ reason(report, "The adjacent rules mirror the Eyeling N3 deep-taxonomy-10 chain: each rule advances one taxonomy level and adds the matching side labels.") :-
102
93
  once(a(ind, a2)),
103
94
  once(is(test, true)).
104
95
 
@@ -1,11 +1,8 @@
1
1
  % =============================================================================================================================
2
- % Deep Taxonomy - depth 100 - expanded N3-style eyelang with checkpoint proof accelerators
2
+ % Deep Taxonomy - depth 100 - expanded N3-style eyelang
3
3
  %
4
- % Adjacent rules mirror the Eyeling N3 deep-taxonomy chain. The checkpoint
5
- % rules are redundant consequences of that chain and are placed first so eyelang
6
- % can prove terminal/report goals without overflowing the JavaScript call stack.
7
- % Report/check rules use once/1 so successful checks do not backtrack into a
8
- % long adjacent-only proof after a shorter checkpoint proof has succeeded.
4
+ % Adjacent rules mirror the Eyeling N3 deep-taxonomy chain. Each step derives
5
+ % the next taxonomy class together with two side labels.
9
6
  % =============================================================================================================================
10
7
 
11
8
  % Output declarations: materialize/2 selects the relations written to this example's golden output.
@@ -27,12 +24,6 @@ a(ind, n0).
27
24
  is(test, true) :- once(a(ind, a2)).
28
25
  a(?x, a2) :- a(?x, n100).
29
26
 
30
- % Redundant checkpoint accelerators.
31
-
32
- a(?x, n100) :- a(?x, n0).
33
- a(?x, n49) :- a(?x, n0).
34
- a(?x, n99) :- a(?x, n0).
35
-
36
27
  % Adjacent N3-style taxonomy rules.
37
28
 
38
29
  a(?x, n1) :- a(?x, n0).
@@ -368,7 +359,7 @@ arc(check6, "C6 OK - the success flag is raised only after the terminal class a2
368
359
  answer(report, "The test succeeds: starting from one individual classified as n0, the rules eventually classify it as n100 and then as a2.") :-
369
360
  once(is(test, true)).
370
361
 
371
- reason(report, "The adjacent rules mirror the Eyeling N3 deep-taxonomy-100 chain. Redundant checkpoint rules are included only as proof accelerators: each shortcut is entailed by the adjacent chain, and once/1 prevents backtracking into the long adjacent-only proof after a check has succeeded.") :-
362
+ reason(report, "The adjacent rules mirror the Eyeling N3 deep-taxonomy-100 chain: each rule advances one taxonomy level and adds the matching side labels.") :-
372
363
  once(a(ind, a2)),
373
364
  once(is(test, true)).
374
365
 
@@ -1,11 +1,8 @@
1
1
  % =============================================================================================================================
2
- % Deep Taxonomy - depth 1000 - expanded N3-style eyelang with checkpoint proof accelerators
2
+ % Deep Taxonomy - depth 1000 - expanded N3-style eyelang
3
3
  %
4
- % Adjacent rules mirror the Eyeling N3 deep-taxonomy chain. The checkpoint
5
- % rules are redundant consequences of that chain and are placed first so eyelang
6
- % can prove terminal/report goals without overflowing the JavaScript call stack.
7
- % Report/check rules use once/1 so successful checks do not backtrack into a
8
- % long adjacent-only proof after a shorter checkpoint proof has succeeded.
4
+ % Adjacent rules mirror the Eyeling N3 deep-taxonomy chain. Each step derives
5
+ % the next taxonomy class together with two side labels.
9
6
  % =============================================================================================================================
10
7
 
11
8
  % Output declarations: materialize/2 selects the relations written to this example's golden output.
@@ -27,12 +24,6 @@ a(ind, n0).
27
24
  is(test, true) :- once(a(ind, a2)).
28
25
  a(?x, a2) :- a(?x, n1000).
29
26
 
30
- % Redundant checkpoint accelerators.
31
-
32
- a(?x, n1000) :- a(?x, n0).
33
- a(?x, n499) :- a(?x, n0).
34
- a(?x, n999) :- a(?x, n0).
35
-
36
27
  % Adjacent N3-style taxonomy rules.
37
28
 
38
29
  a(?x, n1) :- a(?x, n0).
@@ -3068,7 +3059,7 @@ arc(check6, "C6 OK - the success flag is raised only after the terminal class a2
3068
3059
  answer(report, "The test succeeds: starting from one individual classified as n0, the rules eventually classify it as n1000 and then as a2.") :-
3069
3060
  once(is(test, true)).
3070
3061
 
3071
- reason(report, "The adjacent rules mirror the Eyeling N3 deep-taxonomy-1000 chain. Redundant checkpoint rules are included only as proof accelerators: each shortcut is entailed by the adjacent chain, and once/1 prevents backtracking into the long adjacent-only proof after a check has succeeded.") :-
3062
+ reason(report, "The adjacent rules mirror the Eyeling N3 deep-taxonomy-1000 chain: each rule advances one taxonomy level and adds the matching side labels.") :-
3072
3063
  once(a(ind, a2)),
3073
3064
  once(is(test, true)).
3074
3065
 
@@ -1,11 +1,8 @@
1
1
  % =============================================================================================================================
2
- % Deep Taxonomy - depth 10000 - expanded N3-style eyelang with checkpoint proof accelerators
2
+ % Deep Taxonomy - depth 10000 - expanded N3-style eyelang
3
3
  %
4
- % Adjacent rules mirror the Eyeling N3 deep-taxonomy chain. The checkpoint
5
- % rules are redundant consequences of that chain and are placed first so eyelang
6
- % can prove terminal/report goals without overflowing the JavaScript call stack.
7
- % Report/check rules use once/1 so successful checks do not backtrack into a
8
- % long adjacent-only proof after a shorter checkpoint proof has succeeded.
4
+ % Adjacent rules mirror the Eyeling N3 deep-taxonomy chain. Each step derives
5
+ % the next taxonomy class together with two side labels.
9
6
  % =============================================================================================================================
10
7
 
11
8
  % Output declarations: materialize/2 selects the relations written to this example's golden output.
@@ -27,21 +24,6 @@ a(ind, n0).
27
24
  is(test, true) :- once(a(ind, a2)).
28
25
  a(?x, a2) :- a(?x, n10000).
29
26
 
30
- % Redundant checkpoint accelerators.
31
-
32
- a(?x, n1000) :- a(?x, n0).
33
- a(?x, n2000) :- a(?x, n1000).
34
- a(?x, n3000) :- a(?x, n2000).
35
- a(?x, n4000) :- a(?x, n3000).
36
- a(?x, n5000) :- a(?x, n4000).
37
- a(?x, n6000) :- a(?x, n5000).
38
- a(?x, n7000) :- a(?x, n6000).
39
- a(?x, n8000) :- a(?x, n7000).
40
- a(?x, n9000) :- a(?x, n8000).
41
- a(?x, n10000) :- a(?x, n9000).
42
- a(?x, n4999) :- a(?x, n4000).
43
- a(?x, n9999) :- a(?x, n9000).
44
-
45
27
  % Adjacent N3-style taxonomy rules.
46
28
 
47
29
  a(?x, n1) :- a(?x, n0).
@@ -30077,7 +30059,7 @@ arc(check6, "C6 OK - the success flag is raised only after the terminal class a2
30077
30059
  answer(report, "The test succeeds: starting from one individual classified as n0, the rules eventually classify it as n10000 and then as a2.") :-
30078
30060
  once(is(test, true)).
30079
30061
 
30080
- reason(report, "The adjacent rules mirror the Eyeling N3 deep-taxonomy-10000 chain. Redundant checkpoint rules are included only as proof accelerators: each shortcut is entailed by the adjacent chain, and once/1 prevents backtracking into the long adjacent-only proof after a check has succeeded.") :-
30062
+ reason(report, "The adjacent rules mirror the Eyeling N3 deep-taxonomy-10000 chain: each rule advances one taxonomy level and adds the matching side labels.") :-
30081
30063
  once(a(ind, a2)),
30082
30064
  once(is(test, true)).
30083
30065
 
@@ -1,11 +1,8 @@
1
1
  % =============================================================================================================================
2
- % Deep Taxonomy - depth 100000 - expanded N3-style eyelang with checkpoint proof accelerators
2
+ % Deep Taxonomy - depth 100000 - expanded N3-style eyelang
3
3
  %
4
- % Adjacent rules mirror the Eyeling N3 deep-taxonomy chain. The checkpoint
5
- % rules are redundant consequences of that chain and are placed first so eyelang
6
- % can prove terminal/report goals without overflowing the JavaScript call stack.
7
- % Report/check rules use once/1 so successful checks do not backtrack into a
8
- % long adjacent-only proof after a shorter checkpoint proof has succeeded.
4
+ % Adjacent rules mirror the Eyeling N3 deep-taxonomy chain. Each step derives
5
+ % the next taxonomy class together with two side labels.
9
6
  % =============================================================================================================================
10
7
 
11
8
  % Output declarations: materialize/2 selects the relations written to this example's golden output.
@@ -27,111 +24,6 @@ a(ind, n0).
27
24
  is(test, true) :- once(a(ind, a2)).
28
25
  a(?x, a2) :- a(?x, n100000).
29
26
 
30
- % Redundant checkpoint accelerators.
31
-
32
- a(?x, n1000) :- a(?x, n0).
33
- a(?x, n2000) :- a(?x, n1000).
34
- a(?x, n3000) :- a(?x, n2000).
35
- a(?x, n4000) :- a(?x, n3000).
36
- a(?x, n5000) :- a(?x, n4000).
37
- a(?x, n6000) :- a(?x, n5000).
38
- a(?x, n7000) :- a(?x, n6000).
39
- a(?x, n8000) :- a(?x, n7000).
40
- a(?x, n9000) :- a(?x, n8000).
41
- a(?x, n10000) :- a(?x, n9000).
42
- a(?x, n11000) :- a(?x, n10000).
43
- a(?x, n12000) :- a(?x, n11000).
44
- a(?x, n13000) :- a(?x, n12000).
45
- a(?x, n14000) :- a(?x, n13000).
46
- a(?x, n15000) :- a(?x, n14000).
47
- a(?x, n16000) :- a(?x, n15000).
48
- a(?x, n17000) :- a(?x, n16000).
49
- a(?x, n18000) :- a(?x, n17000).
50
- a(?x, n19000) :- a(?x, n18000).
51
- a(?x, n20000) :- a(?x, n19000).
52
- a(?x, n21000) :- a(?x, n20000).
53
- a(?x, n22000) :- a(?x, n21000).
54
- a(?x, n23000) :- a(?x, n22000).
55
- a(?x, n24000) :- a(?x, n23000).
56
- a(?x, n25000) :- a(?x, n24000).
57
- a(?x, n26000) :- a(?x, n25000).
58
- a(?x, n27000) :- a(?x, n26000).
59
- a(?x, n28000) :- a(?x, n27000).
60
- a(?x, n29000) :- a(?x, n28000).
61
- a(?x, n30000) :- a(?x, n29000).
62
- a(?x, n31000) :- a(?x, n30000).
63
- a(?x, n32000) :- a(?x, n31000).
64
- a(?x, n33000) :- a(?x, n32000).
65
- a(?x, n34000) :- a(?x, n33000).
66
- a(?x, n35000) :- a(?x, n34000).
67
- a(?x, n36000) :- a(?x, n35000).
68
- a(?x, n37000) :- a(?x, n36000).
69
- a(?x, n38000) :- a(?x, n37000).
70
- a(?x, n39000) :- a(?x, n38000).
71
- a(?x, n40000) :- a(?x, n39000).
72
- a(?x, n41000) :- a(?x, n40000).
73
- a(?x, n42000) :- a(?x, n41000).
74
- a(?x, n43000) :- a(?x, n42000).
75
- a(?x, n44000) :- a(?x, n43000).
76
- a(?x, n45000) :- a(?x, n44000).
77
- a(?x, n46000) :- a(?x, n45000).
78
- a(?x, n47000) :- a(?x, n46000).
79
- a(?x, n48000) :- a(?x, n47000).
80
- a(?x, n49000) :- a(?x, n48000).
81
- a(?x, n50000) :- a(?x, n49000).
82
- a(?x, n51000) :- a(?x, n50000).
83
- a(?x, n52000) :- a(?x, n51000).
84
- a(?x, n53000) :- a(?x, n52000).
85
- a(?x, n54000) :- a(?x, n53000).
86
- a(?x, n55000) :- a(?x, n54000).
87
- a(?x, n56000) :- a(?x, n55000).
88
- a(?x, n57000) :- a(?x, n56000).
89
- a(?x, n58000) :- a(?x, n57000).
90
- a(?x, n59000) :- a(?x, n58000).
91
- a(?x, n60000) :- a(?x, n59000).
92
- a(?x, n61000) :- a(?x, n60000).
93
- a(?x, n62000) :- a(?x, n61000).
94
- a(?x, n63000) :- a(?x, n62000).
95
- a(?x, n64000) :- a(?x, n63000).
96
- a(?x, n65000) :- a(?x, n64000).
97
- a(?x, n66000) :- a(?x, n65000).
98
- a(?x, n67000) :- a(?x, n66000).
99
- a(?x, n68000) :- a(?x, n67000).
100
- a(?x, n69000) :- a(?x, n68000).
101
- a(?x, n70000) :- a(?x, n69000).
102
- a(?x, n71000) :- a(?x, n70000).
103
- a(?x, n72000) :- a(?x, n71000).
104
- a(?x, n73000) :- a(?x, n72000).
105
- a(?x, n74000) :- a(?x, n73000).
106
- a(?x, n75000) :- a(?x, n74000).
107
- a(?x, n76000) :- a(?x, n75000).
108
- a(?x, n77000) :- a(?x, n76000).
109
- a(?x, n78000) :- a(?x, n77000).
110
- a(?x, n79000) :- a(?x, n78000).
111
- a(?x, n80000) :- a(?x, n79000).
112
- a(?x, n81000) :- a(?x, n80000).
113
- a(?x, n82000) :- a(?x, n81000).
114
- a(?x, n83000) :- a(?x, n82000).
115
- a(?x, n84000) :- a(?x, n83000).
116
- a(?x, n85000) :- a(?x, n84000).
117
- a(?x, n86000) :- a(?x, n85000).
118
- a(?x, n87000) :- a(?x, n86000).
119
- a(?x, n88000) :- a(?x, n87000).
120
- a(?x, n89000) :- a(?x, n88000).
121
- a(?x, n90000) :- a(?x, n89000).
122
- a(?x, n91000) :- a(?x, n90000).
123
- a(?x, n92000) :- a(?x, n91000).
124
- a(?x, n93000) :- a(?x, n92000).
125
- a(?x, n94000) :- a(?x, n93000).
126
- a(?x, n95000) :- a(?x, n94000).
127
- a(?x, n96000) :- a(?x, n95000).
128
- a(?x, n97000) :- a(?x, n96000).
129
- a(?x, n98000) :- a(?x, n97000).
130
- a(?x, n99000) :- a(?x, n98000).
131
- a(?x, n100000) :- a(?x, n99000).
132
- a(?x, n49999) :- a(?x, n49000).
133
- a(?x, n99999) :- a(?x, n99000).
134
-
135
27
  % Adjacent N3-style taxonomy rules.
136
28
 
137
29
  a(?x, n1) :- a(?x, n0).
@@ -300167,7 +300059,7 @@ arc(check6, "C6 OK - the success flag is raised only after the terminal class a2
300167
300059
  answer(report, "The test succeeds: starting from one individual classified as n0, the rules eventually classify it as n100000 and then as a2.") :-
300168
300060
  once(is(test, true)).
300169
300061
 
300170
- reason(report, "The adjacent rules mirror the Eyeling N3 deep-taxonomy-100000 chain. Redundant checkpoint rules are included only as proof accelerators: each shortcut is entailed by the adjacent chain, and once/1 prevents backtracking into the long adjacent-only proof after a check has succeeded.") :-
300062
+ reason(report, "The adjacent rules mirror the Eyeling N3 deep-taxonomy-100000 chain: each rule advances one taxonomy level and adds the matching side labels.") :-
300171
300063
  once(a(ind, a2)),
300172
300064
  once(is(test, true)).
300173
300065
 
@@ -6,7 +6,7 @@ arc(check4, "C4 OK - the final taxonomy step from n9 to n10 was completed.").
6
6
  arc(check5, "C5 OK - once n10 is reached, the terminal class a2 is derived.").
7
7
  arc(check6, "C6 OK - the success flag is raised only after the terminal class a2 is present.").
8
8
  answer(report, "The test succeeds: starting from one individual classified as n0, the rules eventually classify it as n10 and then as a2.").
9
- reason(report, "The adjacent rules mirror the Eyeling N3 deep-taxonomy-10 chain. Redundant checkpoint rules are included only as proof accelerators: each shortcut is entailed by the adjacent chain, and once/1 prevents backtracking into the long adjacent-only proof after a check has succeeded.").
9
+ reason(report, "The adjacent rules mirror the Eyeling N3 deep-taxonomy-10 chain: each rule advances one taxonomy level and adds the matching side labels.").
10
10
  checkPassed(report, check1).
11
11
  checkPassed(report, check2).
12
12
  checkPassed(report, check3).
@@ -6,7 +6,7 @@ arc(check4, "C4 OK - the final taxonomy step from n99 to n100 was completed.").
6
6
  arc(check5, "C5 OK - once n100 is reached, the terminal class a2 is derived.").
7
7
  arc(check6, "C6 OK - the success flag is raised only after the terminal class a2 is present.").
8
8
  answer(report, "The test succeeds: starting from one individual classified as n0, the rules eventually classify it as n100 and then as a2.").
9
- reason(report, "The adjacent rules mirror the Eyeling N3 deep-taxonomy-100 chain. Redundant checkpoint rules are included only as proof accelerators: each shortcut is entailed by the adjacent chain, and once/1 prevents backtracking into the long adjacent-only proof after a check has succeeded.").
9
+ reason(report, "The adjacent rules mirror the Eyeling N3 deep-taxonomy-100 chain: each rule advances one taxonomy level and adds the matching side labels.").
10
10
  checkPassed(report, check1).
11
11
  checkPassed(report, check2).
12
12
  checkPassed(report, check3).
@@ -6,7 +6,7 @@ arc(check4, "C4 OK - the final taxonomy step from n999 to n1000 was completed.")
6
6
  arc(check5, "C5 OK - once n1000 is reached, the terminal class a2 is derived.").
7
7
  arc(check6, "C6 OK - the success flag is raised only after the terminal class a2 is present.").
8
8
  answer(report, "The test succeeds: starting from one individual classified as n0, the rules eventually classify it as n1000 and then as a2.").
9
- reason(report, "The adjacent rules mirror the Eyeling N3 deep-taxonomy-1000 chain. Redundant checkpoint rules are included only as proof accelerators: each shortcut is entailed by the adjacent chain, and once/1 prevents backtracking into the long adjacent-only proof after a check has succeeded.").
9
+ reason(report, "The adjacent rules mirror the Eyeling N3 deep-taxonomy-1000 chain: each rule advances one taxonomy level and adds the matching side labels.").
10
10
  checkPassed(report, check1).
11
11
  checkPassed(report, check2).
12
12
  checkPassed(report, check3).
@@ -6,7 +6,7 @@ arc(check4, "C4 OK - the final taxonomy step from n9999 to n10000 was completed.
6
6
  arc(check5, "C5 OK - once n10000 is reached, the terminal class a2 is derived.").
7
7
  arc(check6, "C6 OK - the success flag is raised only after the terminal class a2 is present.").
8
8
  answer(report, "The test succeeds: starting from one individual classified as n0, the rules eventually classify it as n10000 and then as a2.").
9
- reason(report, "The adjacent rules mirror the Eyeling N3 deep-taxonomy-10000 chain. Redundant checkpoint rules are included only as proof accelerators: each shortcut is entailed by the adjacent chain, and once/1 prevents backtracking into the long adjacent-only proof after a check has succeeded.").
9
+ reason(report, "The adjacent rules mirror the Eyeling N3 deep-taxonomy-10000 chain: each rule advances one taxonomy level and adds the matching side labels.").
10
10
  checkPassed(report, check1).
11
11
  checkPassed(report, check2).
12
12
  checkPassed(report, check3).
@@ -6,7 +6,7 @@ arc(check4, "C4 OK - the final taxonomy step from n99999 to n100000 was complete
6
6
  arc(check5, "C5 OK - once n100000 is reached, the terminal class a2 is derived.").
7
7
  arc(check6, "C6 OK - the success flag is raised only after the terminal class a2 is present.").
8
8
  answer(report, "The test succeeds: starting from one individual classified as n0, the rules eventually classify it as n100000 and then as a2.").
9
- reason(report, "The adjacent rules mirror the Eyeling N3 deep-taxonomy-100000 chain. Redundant checkpoint rules are included only as proof accelerators: each shortcut is entailed by the adjacent chain, and once/1 prevents backtracking into the long adjacent-only proof after a check has succeeded.").
9
+ reason(report, "The adjacent rules mirror the Eyeling N3 deep-taxonomy-100000 chain: each rule advances one taxonomy level and adds the matching side labels.").
10
10
  checkPassed(report, check1).
11
11
  checkPassed(report, check2).
12
12
  checkPassed(report, check3).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyelang",
3
- "version": "1.7.15",
3
+ "version": "1.7.17",
4
4
  "description": "A small Prolog-like logic programming language for facts, rules, goals, answers, and proofs.",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -11,15 +11,19 @@ function* notBuiltin({ solver, goal, env }) {
11
11
  const limited = solver.cloneForInnerGoal(1);
12
12
  let found = false;
13
13
  for (const _ of limited.solve([goal.args[0]], env.clone(), 0)) { found = true; break; }
14
+ solver.absorbStatsFrom(limited);
14
15
  if (!found) yield env;
15
16
  }
16
17
 
17
18
  function* onceBuiltin({ solver, goal, env }) {
18
19
  const limited = solver.cloneForInnerGoal(1);
20
+ let first = null;
19
21
  for (const answerEnv of limited.solve([goal.args[0]], env.clone(), 0)) {
20
- yield answerEnv;
22
+ first = answerEnv;
21
23
  break;
22
24
  }
25
+ solver.absorbStatsFrom(limited);
26
+ if (first) yield first;
23
27
  }
24
28
 
25
29
  function* forallBuiltin({ solver, goal, env }) {
@@ -28,7 +32,12 @@ function* forallBuiltin({ solver, goal, env }) {
28
32
  const checker = solver.cloneForInnerGoal(1);
29
33
  let ok = false;
30
34
  for (const _ of checker.solve([goal.args[1]], answerEnv.clone(), 0)) { ok = true; break; }
31
- if (!ok) return;
35
+ solver.absorbStatsFrom(checker);
36
+ if (!ok) {
37
+ solver.absorbStatsFrom(generator);
38
+ return;
39
+ }
32
40
  }
41
+ solver.absorbStatsFrom(generator);
33
42
  yield env;
34
43
  }
package/src/cli.js CHANGED
@@ -103,13 +103,12 @@ async function runDefault(engine, program, options) {
103
103
  const materializedKeys = new Set(goals.map((goal) => `${goal.name}/${goal.arity}`));
104
104
  const facts = program.sourceFactLines(materializedKeys);
105
105
  const lines = new Set();
106
- let lastStats = null;
107
106
  const registry = engine.getDefaultRegistry();
108
107
  const explanation = options.proof ? await loadExplanation() : null;
108
+ const solver = new engine.Solver(program, { registry });
109
109
 
110
110
  for (const goal of goals) {
111
- const solver = new engine.Solver(program, { registry });
112
-
111
+ solver.solutionsSeen = 0;
113
112
  for (const env of solver.solve([goal], new engine.Env(), 0)) {
114
113
  if (!engine.termIsGround(goal, env)) continue;
115
114
 
@@ -122,10 +121,9 @@ async function runDefault(engine, program, options) {
122
121
  if (options.proof) writeExplanation(explanation, program, engine.copyResolved(goal, env), registry);
123
122
  }
124
123
 
125
- lastStats = solver.stats;
126
124
  }
127
125
 
128
- if (options.stats && lastStats) printStats(lastStats);
126
+ if (options.stats) printStats(solver.stats);
129
127
  }
130
128
 
131
129
  function writeExplanation(explanation, program, resolved, registry) {
package/src/solver.js CHANGED
@@ -1,6 +1,6 @@
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.js';
3
+ import { COMPOUND, Env, copyResolved, flattenConjunction, freshTerm, termIsGround, termToString, unify, variantTerms } from './term.js';
4
4
  import { createDefaultRegistry } from './builtins/registry.js';
5
5
  import { selectClauseCandidates } from './program.js';
6
6
 
@@ -19,6 +19,7 @@ export class Solver {
19
19
  this.solutionsSeen = 0;
20
20
  this.active = [];
21
21
  this.memo = new Map();
22
+ this.groundChainSuccess = new Set();
22
23
  this.stats = {
23
24
  completed_goal_lists: 0,
24
25
  solve_goals_calls: 0,
@@ -34,9 +35,21 @@ export class Solver {
34
35
  cloneForInnerGoal(solutionLimit = this.solutionLimit) {
35
36
  const solver = new Solver(this.program, { registry: this.registry, maxDepth: this.maxDepth, solutionLimit });
36
37
  solver.memo = this.memo;
38
+ solver.groundChainSuccess = this.groundChainSuccess;
37
39
  return solver;
38
40
  }
39
41
 
42
+ absorbStatsFrom(child) {
43
+ if (!child || child === this || !child.stats) return;
44
+ for (const [key, value] of Object.entries(child.stats)) {
45
+ if (key === 'max_depth' || key === 'max_goal_count') {
46
+ this.stats[key] = Math.max(this.stats[key] ?? 0, value ?? 0);
47
+ } else {
48
+ this.stats[key] = (this.stats[key] ?? 0) + (value ?? 0);
49
+ }
50
+ }
51
+ }
52
+
40
53
  *solve(goals, env = new Env(), depth = 0) {
41
54
  if (!Array.isArray(goals)) goals = [goals];
42
55
 
@@ -267,6 +280,7 @@ function pushMemoAnswerFrames(stack, entry, goal, rest, env, depth, active, solv
267
280
 
268
281
  function pushUserGoalUncachedFrames(stack, solver, group, goal, rest, env, depth, active) {
269
282
  if (activeVariantIn(goal, env, active)) return;
283
+ if (tryPushGroundChainFrames(stack, solver, group, goal, rest, env, depth, active)) return;
270
284
  const candidates = selectClauseCandidates(group, goal, env);
271
285
  const frames = [];
272
286
  for (const pass of [candidates.primary, candidates.fallback]) {
@@ -294,6 +308,80 @@ function pushUserGoalUncachedFrames(stack, solver, group, goal, rest, env, depth
294
308
  for (let i = frames.length - 1; i >= 0; i--) stack.push(frames[i]);
295
309
  }
296
310
 
311
+
312
+ function tryPushGroundChainFrames(stack, solver, group, goal, rest, env, depth, active) {
313
+ // Compress deterministic ground single-goal chains such as deep taxonomy
314
+ // proofs: a(ind, n100000) -> a(ind, n99999) -> ... -> a(ind, n0).
315
+ // This is a search-control optimization only. It fires only while each step
316
+ // has exactly one matching clause and a single ground body goal; otherwise the
317
+ // normal clause path below remains authoritative.
318
+ if (!termIsGround(goal, env)) return false;
319
+
320
+ const baseEnv = env;
321
+ let currentGroup = group;
322
+ let currentGoal = copyResolved(goal, env);
323
+ let currentDepth = depth;
324
+ const currentEnv = new Env();
325
+ const seen = new Set();
326
+
327
+ while (true) {
328
+ // The compressed path is iterative and protected by `seen`, so it does not
329
+ // consume JavaScript recursion depth the way the ordinary solver path does.
330
+ // Keep recording the logical depth for diagnostics, but do not cut off long
331
+ // finite taxonomy chains with the recursive maxDepth guard.
332
+ if (solver.solutionsSeen >= solver.solutionLimit) return true;
333
+ solver.stats.max_depth = Math.max(solver.stats.max_depth, currentDepth);
334
+ const key = `${currentGoal.name}/${currentGoal.arity}:${termToString(currentGoal, currentEnv, true)}`;
335
+ if (seen.has(key)) return true;
336
+ if (activeVariantIn(currentGoal, currentEnv, active)) return true;
337
+ if (solver.groundChainSuccess.has(key)) {
338
+ rememberGroundChainSuccess(solver, seen);
339
+ stack.push({ kind: 'goals', goals: rest, env: baseEnv, depth: depth + 1, active });
340
+ return true;
341
+ }
342
+ seen.add(key);
343
+
344
+ const candidates = selectClauseCandidates(currentGroup, currentGoal, currentEnv);
345
+ const matches = [];
346
+ for (const pass of [candidates.primary, candidates.fallback]) {
347
+ for (const clause of pass) {
348
+ if (headCannotMatch(currentGoal, clause.head, currentEnv)) continue;
349
+ const id = nextFreshId();
350
+ const freshHead = freshTerm(clause.head, id);
351
+ const freshBody = clause.body.map((term) => freshTerm(term, id));
352
+ const next = new Env();
353
+ solver.stats.unify_calls++;
354
+ if (!unify(currentGoal, freshHead, next)) continue;
355
+ matches.push({ body: freshBody, env: next });
356
+ if (matches.length > 1) return false;
357
+ }
358
+ }
359
+
360
+ if (matches.length !== 1) return false;
361
+ const match = matches[0];
362
+ if (match.body.length === 0) {
363
+ rememberGroundChainSuccess(solver, seen);
364
+ stack.push({ kind: 'goals', goals: rest, env: baseEnv, depth: depth + 1, active });
365
+ return true;
366
+ }
367
+ if (match.body.length !== 1) return false;
368
+ const nextGoal = match.body[0];
369
+ if (nextGoal.type !== COMPOUND || !termIsGround(nextGoal, match.env)) return false;
370
+ const resolvedNextGoal = copyResolved(nextGoal, match.env);
371
+ const nextGroup = solver.program.findGroup(resolvedNextGoal.name, resolvedNextGoal.arity);
372
+ if (!nextGroup) return false;
373
+
374
+ currentGoal = resolvedNextGoal;
375
+ currentGroup = nextGroup;
376
+ currentDepth++;
377
+ }
378
+ }
379
+
380
+
381
+ function rememberGroundChainSuccess(solver, seen) {
382
+ for (const key of seen) solver.groundChainSuccess.add(key);
383
+ }
384
+
297
385
  function rememberMemoAnswer(entry, goal, env) {
298
386
  const answerArgs = goal.args.map((arg) => importResolved(arg, env));
299
387
  const key = answerArgs.map((arg) => termToString(arg, new Env(), true)).join('\x1f');