eyelang 1.7.10 → 1.7.12
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 +1 -1
- package/docs/guide.md +1 -1
- package/docs/language-reference.md +4 -4
- package/examples/animal.eye +1 -1
- package/examples/dairy-energy-balance.eye +3 -3
- package/examples/dijkstra-risk-path.eye +1 -1
- package/examples/ev-range-worlds.eye +10 -10
- package/examples/fft8-numeric.eye +1 -1
- package/examples/field-nitrogen-balance.eye +7 -7
- package/examples/fundamental-theorem-arithmetic.eye +2 -2
- package/examples/gps.eye +8 -8
- package/examples/illegitimate-reasoning.eye +7 -7
- package/examples/map-four-color-search.eye +1 -1
- package/examples/monkey-bananas.eye +4 -4
- package/examples/stirling-bell-numbers.eye +50 -13
- package/examples/wolf-goat-cabbage.eye +2 -2
- package/examples/zebra.eye +21 -21
- package/package.json +2 -2
- package/playground.html +6 -3
- package/src/parser.js +8 -8
- package/src/term.js +1 -1
- package/test/conformance/cases/arithmetic/extra_division_by_zero_fails.eye +1 -1
- package/test/conformance/cases/atoms/103_angle_iri_atoms.eye +2 -2
- package/test/conformance/cases/builtins/extra_difference_end_before_start_fails.eye +1 -1
- package/test/conformance/cases/builtins/extra_difference_invalid_date_fails.eye +1 -1
- package/test/conformance/cases/context/extra_holds_ignores_string_parts.eye +1 -1
- package/test/conformance/cases/lists/020_nested_list_terms.eye +1 -1
- package/test/conformance/cases/lists/extra_length_improper_fails.eye +1 -1
- package/test/conformance/cases/rules/022_rule_head_structure.eye +2 -2
- package/test/conformance/cases/terms/extra_arg_atom_fails.eye +1 -1
- package/test/conformance/cases/terms/extra_arg_zero_fails.eye +1 -1
- package/test/conformance/cases/terms/extra_compound_name_arguments_bad_args_fails.eye +1 -1
- package/test/conformance/cases/variables/007_anonymous_variables.eye +1 -1
- package/test/conformance/cases/variables/026_question_underscore_named_variable_reuse.eye +1 -1
- package/test/conformance/cases/variables/anonymous_in_two_goals.eye +2 -2
- package/test/conformance/cases/variables/extra_anonymous_not_reused.eye +1 -1
- package/test/conformance/cases/variables/question_anonymous_not_reused.eye +3 -3
- package/test/conformance/expected-errors/syntax/bad_question_digit_variable.txt +1 -1
- package/test/conformance/expected-errors/variables/question_digit_rejected.txt +1 -1
- package/test/run-regression.mjs +3 -3
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/eyelang)
|
|
4
4
|
[](https://doi.org/10.5281/zenodo.20761726)
|
|
5
5
|
|
|
6
|
-
Eyelang is a small logic programming language for rules, goals, answers, and proofs.
|
|
6
|
+
Eyelang is a small logic programming language for facts, rules, goals, answers, and proofs.
|
|
7
7
|
Its source syntax is Prolog-like Horn-clause syntax with deliberate eyelang choices, including `?x` variables for N3/SPARQL-style readability, explicit `table(path, 2).` declarations for tabled predicates, advisory `mode/3` declarations for host tooling, explicit Herbrand witness terms for executable existential-style consequences, and stratified-negation diagnostics for portable `not/1` usage.
|
|
8
8
|
It grew out of logic-language experiments in the EYE/N3 reasoning tradition, but is packaged here as its own project.
|
|
9
9
|
|
package/docs/guide.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Eyelang Guide
|
|
2
2
|
|
|
3
|
-
This guide introduces Eyelang, a small Horn-clause language and engine whose source syntax is Prolog-like but deliberately its own compact language for rules, goals, answers, and proofs. Eyelang works over ordinary terms, lists, arithmetic, strings, and finite search. Run it with the `eyelang` CLI, or use `node bin/eyelang.js` when working directly from a source checkout.
|
|
3
|
+
This guide introduces Eyelang, a small Horn-clause language and engine whose source syntax is Prolog-like but deliberately its own compact language for facts, rules, goals, answers, and proofs. Eyelang works over ordinary terms, lists, arithmetic, strings, and finite search. Run it with the `eyelang` CLI, or use `node bin/eyelang.js` when working directly from a source checkout.
|
|
4
4
|
|
|
5
5
|
Programs write relations directly, for example `ancestor(pat, emma)` or `status(case1, accepted)`. Absolute IRI atoms can be written explicitly with angle brackets, for example `<https://schema.org/name>`, when a program needs web identifiers without prefix declarations. 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/2` declarations such as `materialize(answer, 2).` to focus output on selected predicates.
|
|
6
6
|
|
|
@@ -123,7 +123,7 @@ A colon outside `:-` is not part of the language. Namespace-like names SHOULD be
|
|
|
123
123
|
|
|
124
124
|
### 3.4 Variables
|
|
125
125
|
|
|
126
|
-
A variable starts with `?` followed by an ASCII letter or underscore and then zero or more ASCII letters, digits, or underscores. This N3/SPARQL-style spelling is the only source-level variable spelling in eyelang.
|
|
126
|
+
A variable is either the bare anonymous variable `?`, or starts with `?` followed by an ASCII letter or underscore and then zero or more ASCII letters, digits, or underscores. This N3/SPARQL-style spelling is the only source-level variable spelling in eyelang.
|
|
127
127
|
|
|
128
128
|
Examples:
|
|
129
129
|
|
|
@@ -131,10 +131,10 @@ Examples:
|
|
|
131
131
|
?x
|
|
132
132
|
?person
|
|
133
133
|
?_thing
|
|
134
|
-
?
|
|
134
|
+
?
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
-
Each
|
|
137
|
+
Each bare `?` anonymous variable occurrence is fresh. A bare `_` is not a variable in eyelang source.
|
|
138
138
|
|
|
139
139
|
### 3.5 Atom constants
|
|
140
140
|
|
|
@@ -171,7 +171,7 @@ A quoted atom constant is enclosed in single quotes. A single quote inside a quo
|
|
|
171
171
|
A graphic atom constant is one or more graphic characters from this set:
|
|
172
172
|
|
|
173
173
|
```text
|
|
174
|
-
|
|
174
|
+
#$&*+-/<=>@^~\
|
|
175
175
|
```
|
|
176
176
|
|
|
177
177
|
Angle-bracket IRI syntax is recognized only for absolute IRI-like contents. Graphic atoms such as `<=>`, `<`, and `>=` remain graphic atoms.
|
package/examples/animal.eye
CHANGED
|
@@ -17,15 +17,15 @@ cow(grazing, 540, 18, 5.8, 21).
|
|
|
17
17
|
|
|
18
18
|
% Maintenance scales with body weight; milk requirement scales with daily milk.
|
|
19
19
|
maintenance(?c, ?m) :-
|
|
20
|
-
cow(?c, ?weight,
|
|
20
|
+
cow(?c, ?weight, ?, ?, ?),
|
|
21
21
|
mul(?weight, 0.08, ?m).
|
|
22
22
|
|
|
23
23
|
milk_requirement(?c, ?r) :-
|
|
24
|
-
cow(?c,
|
|
24
|
+
cow(?c, ?, ?milk, ?, ?),
|
|
25
25
|
mul(?milk, 5.0, ?r).
|
|
26
26
|
|
|
27
27
|
ration_supply(?c, ?s) :-
|
|
28
|
-
cow(?c,
|
|
28
|
+
cow(?c, ?, ?, ?density, ?intake),
|
|
29
29
|
mul(?density, ?intake, ?s).
|
|
30
30
|
|
|
31
31
|
total_requirement(?c, ?r) :-
|
|
@@ -41,7 +41,7 @@ candidate(pathRelay, [depotA, relay, labD]).
|
|
|
41
41
|
candidate(pathDirectC, [depotA, labD]).
|
|
42
42
|
candidate(pathViaC, [depotA, depotC, depotB, labD]).
|
|
43
43
|
|
|
44
|
-
route_cost([?
|
|
44
|
+
route_cost([?], 0.0, 0.0, 0).
|
|
45
45
|
route_cost([?from, ?to|?rest], ?raw, ?risk, ?edges) :-
|
|
46
46
|
route_segment(?from, ?to, ?stepraw, ?steprisk),
|
|
47
47
|
route_cost([?to|?rest], ?restraw, ?restrisk, ?restedges),
|
|
@@ -24,18 +24,18 @@ trip_data(cold_commute, 120, 90, -8, 100, 35, 0.19).
|
|
|
24
24
|
|
|
25
25
|
% Each world adds a different combination of speed, temperature, payload, and
|
|
26
26
|
% reserve factors before comparing required energy with usable battery.
|
|
27
|
-
speed_factor(?t, 1.20) :- trip_data(?t,
|
|
28
|
-
speed_factor(?t, 1.00) :- trip_data(?t,
|
|
27
|
+
speed_factor(?t, 1.20) :- trip_data(?t, ?, ?s, ?, ?, ?, ?), gt(?s, 100).
|
|
28
|
+
speed_factor(?t, 1.00) :- trip_data(?t, ?, ?s, ?, ?, ?, ?), le(?s, 100).
|
|
29
29
|
|
|
30
|
-
temperature_factor(?t, 1.15) :- trip_data(?t,
|
|
31
|
-
temperature_factor(?t, 1.00) :- trip_data(?t,
|
|
30
|
+
temperature_factor(?t, 1.15) :- trip_data(?t, ?, ?, ?temp, ?, ?, ?), lt(?temp, 0).
|
|
31
|
+
temperature_factor(?t, 1.00) :- trip_data(?t, ?, ?, ?temp, ?, ?, ?), ge(?temp, 0).
|
|
32
32
|
|
|
33
|
-
payload_factor(?t, 1.15) :- trip_data(?t,
|
|
34
|
-
payload_factor(?t, 1.08) :- trip_data(?t,
|
|
35
|
-
payload_factor(?t, 1.00) :- trip_data(?t,
|
|
33
|
+
payload_factor(?t, 1.15) :- trip_data(?t, ?, ?, ?, ?p, ?, ?), gt(?p, 500).
|
|
34
|
+
payload_factor(?t, 1.08) :- trip_data(?t, ?, ?, ?, ?p, ?, ?), gt(?p, 250), le(?p, 500).
|
|
35
|
+
payload_factor(?t, 1.00) :- trip_data(?t, ?, ?, ?, ?p, ?, ?), le(?p, 250).
|
|
36
36
|
|
|
37
37
|
base_energy(?t, ?e) :-
|
|
38
|
-
trip_data(?t, ?d,
|
|
38
|
+
trip_data(?t, ?d, ?, ?, ?, ?, ?b),
|
|
39
39
|
mul(?d, ?b, ?e).
|
|
40
40
|
|
|
41
41
|
required_energy(?t, w1, ?e) :-
|
|
@@ -61,12 +61,12 @@ required_energy(?t, w3, ?e) :-
|
|
|
61
61
|
|
|
62
62
|
% safe_in_world/2 compares required trip energy with usable battery capacity.
|
|
63
63
|
safe_in_world(?t, ?w) :-
|
|
64
|
-
trip_data(?t,
|
|
64
|
+
trip_data(?t, ?, ?, ?, ?, ?battery, ?),
|
|
65
65
|
required_energy(?t, ?w, ?required),
|
|
66
66
|
le(?required, ?battery).
|
|
67
67
|
|
|
68
68
|
risky_in_world(?t, ?w) :-
|
|
69
|
-
trip_data(?t,
|
|
69
|
+
trip_data(?t, ?, ?, ?, ?, ?battery, ?),
|
|
70
70
|
required_energy(?t, ?w, ?required),
|
|
71
71
|
gt(?required, ?battery).
|
|
72
72
|
|
|
@@ -21,41 +21,41 @@ field(clay_surplus, 70, 90, 0.08, 120).
|
|
|
21
21
|
% total_n/2 and available_n/2 build the nutrient budget; surplus/deficit and
|
|
22
22
|
% leaching rules then explain the resulting field status.
|
|
23
23
|
total_n(?f, ?total) :-
|
|
24
|
-
field(?f, ?soil, ?fert,
|
|
24
|
+
field(?f, ?soil, ?fert, ?, ?),
|
|
25
25
|
add(?soil, ?fert, ?total).
|
|
26
26
|
|
|
27
27
|
available_n(?f, ?avail) :-
|
|
28
28
|
total_n(?f, ?total),
|
|
29
|
-
field(?f,
|
|
29
|
+
field(?f, ?, ?, ?loss, ?),
|
|
30
30
|
sub(1.0, ?loss, ?retained),
|
|
31
31
|
mul(?total, ?retained, ?avail).
|
|
32
32
|
|
|
33
33
|
% surplus_n/2 and deficit_n/2 split the signed balance into reportable quantities.
|
|
34
34
|
surplus_n(?f, ?surplus) :-
|
|
35
35
|
available_n(?f, ?avail),
|
|
36
|
-
field(?f,
|
|
36
|
+
field(?f, ?, ?, ?, ?demand),
|
|
37
37
|
gt(?avail, ?demand),
|
|
38
38
|
sub(?avail, ?demand, ?surplus).
|
|
39
39
|
|
|
40
40
|
surplus_n(?f, 0.0) :-
|
|
41
41
|
available_n(?f, ?avail),
|
|
42
|
-
field(?f,
|
|
42
|
+
field(?f, ?, ?, ?, ?demand),
|
|
43
43
|
le(?avail, ?demand).
|
|
44
44
|
|
|
45
45
|
deficit_n(?f, ?deficit) :-
|
|
46
46
|
available_n(?f, ?avail),
|
|
47
|
-
field(?f,
|
|
47
|
+
field(?f, ?, ?, ?, ?demand),
|
|
48
48
|
lt(?avail, ?demand),
|
|
49
49
|
sub(?demand, ?avail, ?deficit).
|
|
50
50
|
|
|
51
51
|
deficit_n(?f, 0.0) :-
|
|
52
52
|
available_n(?f, ?avail),
|
|
53
|
-
field(?f,
|
|
53
|
+
field(?f, ?, ?, ?, ?demand),
|
|
54
54
|
ge(?avail, ?demand).
|
|
55
55
|
|
|
56
56
|
leaching_index(?f, ?index) :-
|
|
57
57
|
surplus_n(?f, ?surplus),
|
|
58
|
-
field(?f,
|
|
58
|
+
field(?f, ?, ?, ?loss, ?),
|
|
59
59
|
mul(?surplus, ?loss, ?index).
|
|
60
60
|
|
|
61
61
|
status(?f, under_supplied) :- deficit_n(?f, ?d), gt(?d, 10.0).
|
|
@@ -112,8 +112,8 @@ distinctPrimeCount(case, 4) :-
|
|
|
112
112
|
|
|
113
113
|
smallestPrimeFactor(case, 3) :-
|
|
114
114
|
case(fta, ?n),
|
|
115
|
-
factor_smallest(?n, [3|?
|
|
115
|
+
factor_smallest(?n, [3|?]).
|
|
116
116
|
|
|
117
117
|
largestPrimeFactor(case, 3881) :-
|
|
118
118
|
case(fta, ?n),
|
|
119
|
-
factor_largest(?n, [3881|?
|
|
119
|
+
factor_largest(?n, [3881|?]).
|
package/examples/gps.eye
CHANGED
|
@@ -78,24 +78,24 @@ outcome(routeDirect, "Take the direct route via Brugge.").
|
|
|
78
78
|
|
|
79
79
|
% Verification checks, analogous to the false-producing guards in gps.n3.
|
|
80
80
|
check(c1, true) :-
|
|
81
|
-
traveller_path(i1, [drive_gent_brugge, drive_brugge_oostende],
|
|
81
|
+
traveller_path(i1, [drive_gent_brugge, drive_brugge_oostende], ?, ?, ?, ?).
|
|
82
82
|
|
|
83
83
|
check(c2, true) :-
|
|
84
|
-
traveller_path(i1, [drive_gent_kortrijk, drive_kortrijk_brugge, drive_brugge_oostende],
|
|
84
|
+
traveller_path(i1, [drive_gent_kortrijk, drive_kortrijk_brugge, drive_brugge_oostende], ?, ?, ?, ?).
|
|
85
85
|
|
|
86
86
|
check(c3, true) :-
|
|
87
|
-
route_metrics(routeDirect, ?d1,
|
|
88
|
-
route_metrics(routeViaKortrijk, ?d2,
|
|
87
|
+
route_metrics(routeDirect, ?d1, ?, ?, ?),
|
|
88
|
+
route_metrics(routeViaKortrijk, ?d2, ?, ?, ?),
|
|
89
89
|
lt(?d1, ?d2).
|
|
90
90
|
|
|
91
91
|
check(c4, true) :-
|
|
92
|
-
route_metrics(routeDirect,
|
|
93
|
-
route_metrics(routeViaKortrijk,
|
|
92
|
+
route_metrics(routeDirect, ?, ?c1, ?, ?),
|
|
93
|
+
route_metrics(routeViaKortrijk, ?, ?c2, ?, ?),
|
|
94
94
|
lt(?c1, ?c2).
|
|
95
95
|
|
|
96
96
|
check(c5, true) :-
|
|
97
|
-
route_metrics(routeDirect,
|
|
98
|
-
route_metrics(routeViaKortrijk,
|
|
97
|
+
route_metrics(routeDirect, ?, ?, ?b1, ?f1),
|
|
98
|
+
route_metrics(routeViaKortrijk, ?, ?, ?b2, ?f2),
|
|
99
99
|
gt(?b1, ?b2),
|
|
100
100
|
gt(?f1, ?f2).
|
|
101
101
|
|
|
@@ -67,22 +67,22 @@ fallacy(?a, hasty_generalization) :-
|
|
|
67
67
|
sample_size(?a, ?n),
|
|
68
68
|
required_sample_size(?a, ?min),
|
|
69
69
|
lt(?n, ?min),
|
|
70
|
-
concludes(?a, all(
|
|
70
|
+
concludes(?a, all(?, ?)).
|
|
71
71
|
|
|
72
72
|
fallacy(?a, false_dilemma) :-
|
|
73
73
|
argument(?a),
|
|
74
|
-
presented_alternatives(?a, ?
|
|
75
|
-
omitted_alternative(?a, ?
|
|
76
|
-
concludes(?a, ?
|
|
74
|
+
presented_alternatives(?a, ?),
|
|
75
|
+
omitted_alternative(?a, ?),
|
|
76
|
+
concludes(?a, ?).
|
|
77
77
|
|
|
78
78
|
reason(arg_affirming_consequent, "observing the consequent does not prove the antecedent").
|
|
79
79
|
reason(arg_denying_antecedent, "denying the antecedent does not disprove the consequent").
|
|
80
80
|
reason(arg_hasty_generalization, "sample size is below the threshold for a universal conclusion").
|
|
81
81
|
reason(arg_false_dilemma, "a relevant alternative is omitted").
|
|
82
82
|
|
|
83
|
-
type(?a, illegitimate_reasoning) :- fallacy(?a, ?
|
|
84
|
-
conclusion(?a, ?c) :- fallacy(?a, ?
|
|
85
|
-
reason(?a, ?r) :- fallacy(?a, ?
|
|
83
|
+
type(?a, illegitimate_reasoning) :- fallacy(?a, ?).
|
|
84
|
+
conclusion(?a, ?c) :- fallacy(?a, ?), concludes(?a, ?c).
|
|
85
|
+
reason(?a, ?r) :- fallacy(?a, ?), reason(?a, ?r).
|
|
86
86
|
sampleSize(?a, ?n) :- fallacy(?a, hasty_generalization), sample_size(?a, ?n).
|
|
87
87
|
requiredSampleSize(?a, ?min) :- fallacy(?a, hasty_generalization), required_sample_size(?a, ?min).
|
|
88
88
|
omittedAlternative(?a, ?alt) :- fallacy(?a, false_dilemma), omitted_alternative(?a, ?alt).
|
|
@@ -50,7 +50,7 @@ valid_color(?place, ?color, ?assigned) :-
|
|
|
50
50
|
not((member([?neighbor, ?color], ?assigned), member(?neighbor, ?neighbors))).
|
|
51
51
|
|
|
52
52
|
place_pairs([], []).
|
|
53
|
-
place_pairs([?place|?rest], [[?place, ?
|
|
53
|
+
place_pairs([?place|?rest], [[?place, ?]|?pairs]) :-
|
|
54
54
|
place_pairs(?rest, ?pairs).
|
|
55
55
|
|
|
56
56
|
color_places([]).
|
|
@@ -19,9 +19,9 @@ plan(?moves) :-
|
|
|
19
19
|
goal_state(?g),
|
|
20
20
|
reachable(?i, ?moves, ?g).
|
|
21
21
|
|
|
22
|
-
candidate_plan([
|
|
23
|
-
candidate_plan([
|
|
24
|
-
candidate_plan([
|
|
22
|
+
candidate_plan([?, ?, ?]).
|
|
23
|
+
candidate_plan([?, ?, ?, ?]).
|
|
24
|
+
candidate_plan([?, ?, ?, ?, ?]).
|
|
25
25
|
|
|
26
26
|
reachable(?s, [], ?s).
|
|
27
27
|
reachable(?s1, [?m|?l], ?s3) :-
|
|
@@ -29,7 +29,7 @@ reachable(?s1, [?m|?l], ?s3) :-
|
|
|
29
29
|
reachable(?s2, ?l, ?s3).
|
|
30
30
|
|
|
31
31
|
initial_state([loc1, loc2, loc3, n, n]).
|
|
32
|
-
goal_state([
|
|
32
|
+
goal_state([?, ?, ?, ?, y]).
|
|
33
33
|
|
|
34
34
|
legal_move([?b, ?m, ?m, n, ?h], climb_on, [?b, ?m, ?m, y, ?h]).
|
|
35
35
|
legal_move([?b, ?m, ?m, y, ?h], climb_off, [?b, ?m, ?m, n, ?h]).
|
|
@@ -1,30 +1,67 @@
|
|
|
1
1
|
% Stirling numbers of the second kind and Bell numbers.
|
|
2
2
|
%
|
|
3
|
-
%
|
|
4
|
-
%
|
|
5
|
-
%
|
|
6
|
-
%
|
|
3
|
+
% The Stirling count S(N,K) is computed with the inclusion-exclusion formula
|
|
4
|
+
% S(N,K) = (1/K!) * sum(I=0..K, (-1)^(K-I) * C(K,I) * I^N)
|
|
5
|
+
% instead of the overlapping two-branch recurrence. Bell numbers use
|
|
6
|
+
% B(0) = 1, B(N) = sum(K=0..N-1, C(N-1,K) * B(K)).
|
|
7
|
+
% The table declarations memoize the smaller helper relations used by both formulas.
|
|
7
8
|
materialize(stirling_bell_answer, 2).
|
|
8
9
|
|
|
10
|
+
table(factorial, 2).
|
|
11
|
+
table(binomial, 3).
|
|
9
12
|
table(stirling2, 3).
|
|
13
|
+
table(bell, 2).
|
|
14
|
+
|
|
15
|
+
factorial(0, 1).
|
|
16
|
+
factorial(?n, ?value) :-
|
|
17
|
+
gt(?n, 0),
|
|
18
|
+
sub(?n, 1, ?n1),
|
|
19
|
+
factorial(?n1, ?previous),
|
|
20
|
+
mul(?n, ?previous, ?value).
|
|
21
|
+
|
|
22
|
+
binomial(0, 0, 1).
|
|
23
|
+
binomial(?n, 0, 1) :- gt(?n, 0).
|
|
24
|
+
binomial(?n, ?n, 1) :- gt(?n, 0).
|
|
25
|
+
binomial(?n, ?k, ?value) :-
|
|
26
|
+
gt(?n, 0),
|
|
27
|
+
gt(?k, 0),
|
|
28
|
+
lt(?k, ?n),
|
|
29
|
+
sub(?n, 1, ?n1),
|
|
30
|
+
sub(?k, 1, ?k1),
|
|
31
|
+
binomial(?n1, ?k1, ?left),
|
|
32
|
+
binomial(?n1, ?k, ?right),
|
|
33
|
+
add(?left, ?right, ?value).
|
|
34
|
+
|
|
35
|
+
signed_term(?n, ?k, ?i, ?term) :-
|
|
36
|
+
binomial(?k, ?i, ?c),
|
|
37
|
+
pow(?i, ?n, ?p),
|
|
38
|
+
mul(?c, ?p, ?unsigned),
|
|
39
|
+
sub(?k, ?i, ?d),
|
|
40
|
+
mod(?d, 2, 0),
|
|
41
|
+
eq(?term, ?unsigned).
|
|
42
|
+
signed_term(?n, ?k, ?i, ?term) :-
|
|
43
|
+
binomial(?k, ?i, ?c),
|
|
44
|
+
pow(?i, ?n, ?p),
|
|
45
|
+
mul(?c, ?p, ?unsigned),
|
|
46
|
+
sub(?k, ?i, ?d),
|
|
47
|
+
mod(?d, 2, 1),
|
|
48
|
+
neg(?unsigned, ?term).
|
|
10
49
|
|
|
11
|
-
% Boundary cases define the empty partition and the impossible zero-block columns.
|
|
12
50
|
stirling2(0, 0, 1).
|
|
13
51
|
stirling2(?n, 0, 0) :- gt(?n, 0).
|
|
14
52
|
stirling2(0, ?k, 0) :- gt(?k, 0).
|
|
15
53
|
stirling2(?n, ?k, ?count) :-
|
|
16
54
|
gt(?n, 0),
|
|
17
55
|
gt(?k, 0),
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
stirling2(?n1, ?k, ?existingblocks),
|
|
22
|
-
mul(?k, ?existingblocks, ?extended),
|
|
23
|
-
add(?newblock, ?extended, ?count).
|
|
56
|
+
sumall(?term, (between(0, ?k, ?i), signed_term(?n, ?k, ?i, ?term)), ?sum),
|
|
57
|
+
factorial(?k, ?factorial),
|
|
58
|
+
div(?sum, ?factorial, ?count).
|
|
24
59
|
|
|
25
|
-
|
|
60
|
+
bell(0, 1).
|
|
26
61
|
bell(?n, ?count) :-
|
|
27
|
-
|
|
62
|
+
gt(?n, 0),
|
|
63
|
+
sub(?n, 1, ?n1),
|
|
64
|
+
sumall(?term, (between(0, ?n1, ?k), binomial(?n1, ?k, ?choose), bell(?k, ?bell), mul(?choose, ?bell, ?term)), ?count).
|
|
28
65
|
|
|
29
66
|
stirling_bell_answer(stirling_10_4, ?count) :- stirling2(10, 4, ?count).
|
|
30
67
|
stirling_bell_answer(stirling_12_5, ?count) :- stirling2(12, 5, ?count).
|
package/examples/zebra.eye
CHANGED
|
@@ -12,33 +12,33 @@ materialize(solved, 2).
|
|
|
12
12
|
|
|
13
13
|
% The single zebra/2 rule is a finite constraint model over the five house slots.
|
|
14
14
|
zebra(?waterdrinker, ?zebraowner) :-
|
|
15
|
-
eq(?houses, [
|
|
16
|
-
first(?houses, house(
|
|
17
|
-
third(?houses, house(
|
|
18
|
-
adjacent(house(
|
|
19
|
-
next_to(house(ivory,
|
|
20
|
-
member(house(red, english,
|
|
21
|
-
member(house(green,
|
|
22
|
-
member(house(yellow,
|
|
23
|
-
member(house(
|
|
24
|
-
member(house(
|
|
25
|
-
member(house(
|
|
26
|
-
adjacent(house(
|
|
27
|
-
adjacent(house(
|
|
28
|
-
member(house(
|
|
29
|
-
member(house(
|
|
30
|
-
member(house(
|
|
31
|
-
member(house(
|
|
15
|
+
eq(?houses, [?, ?, ?, ?, ?]),
|
|
16
|
+
first(?houses, house(?, norwegian, ?, ?, ?)),
|
|
17
|
+
third(?houses, house(?, ?, ?, milk, ?)),
|
|
18
|
+
adjacent(house(?, norwegian, ?, ?, ?), house(blue, ?, ?, ?, ?), ?houses),
|
|
19
|
+
next_to(house(ivory, ?, ?, ?, ?), house(green, ?, ?, ?, ?), ?houses),
|
|
20
|
+
member(house(red, english, ?, ?, ?), ?houses),
|
|
21
|
+
member(house(green, ?, ?, coffee, ?), ?houses),
|
|
22
|
+
member(house(yellow, ?, ?, ?, kools), ?houses),
|
|
23
|
+
member(house(?, spanish, dog, ?, ?), ?houses),
|
|
24
|
+
member(house(?, ukrainian, ?, tea, ?), ?houses),
|
|
25
|
+
member(house(?, ?, snail, ?, old_gold), ?houses),
|
|
26
|
+
adjacent(house(?, ?, ?, ?, chesterfields), house(?, ?, fox, ?, ?), ?houses),
|
|
27
|
+
adjacent(house(?, ?, ?, ?, kools), house(?, ?, horse, ?, ?), ?houses),
|
|
28
|
+
member(house(?, ?, ?, orange_juice, lucky_strike), ?houses),
|
|
29
|
+
member(house(?, japanese, ?, ?, parliaments), ?houses),
|
|
30
|
+
member(house(?, ?waterdrinker, ?, water, ?), ?houses),
|
|
31
|
+
member(house(?, ?zebraowner, zebra, ?, ?), ?houses).
|
|
32
32
|
|
|
33
33
|
% Positional and neighborhood helpers keep the clue encoding readable.
|
|
34
|
-
first([?x|?
|
|
35
|
-
third([
|
|
34
|
+
first([?x|?], ?x).
|
|
35
|
+
third([?, ?, ?x|?], ?x).
|
|
36
36
|
|
|
37
37
|
adjacent(?a, ?b, ?list) :- next_to(?a, ?b, ?list).
|
|
38
38
|
adjacent(?a, ?b, ?list) :- next_to(?b, ?a, ?list).
|
|
39
39
|
|
|
40
|
-
next_to(?x, ?y, [?x, ?y|?
|
|
41
|
-
next_to(?x, ?y, [
|
|
40
|
+
next_to(?x, ?y, [?x, ?y|?]).
|
|
41
|
+
next_to(?x, ?y, [?|?zs]) :- next_to(?x, ?y, ?zs).
|
|
42
42
|
|
|
43
43
|
waterDrinker(zebraPuzzle, ?waterdrinker) :- zebra(?waterdrinker, ?_zebraowner).
|
|
44
44
|
zebraOwner(zebraPuzzle, ?zebraowner) :- zebra(?_waterdrinker, ?zebraowner).
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eyelang",
|
|
3
|
-
"version": "1.7.
|
|
4
|
-
"description": "A small Prolog-like logic programming language for rules, goals, answers, and proofs.",
|
|
3
|
+
"version": "1.7.12",
|
|
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",
|
|
7
7
|
"types": "./index.d.ts",
|
package/playground.html
CHANGED
|
@@ -869,9 +869,12 @@ answer(ok) :- eq(ok, ok).
|
|
|
869
869
|
while (j < text.length && /[\d.]/.test(text[j])) j++;
|
|
870
870
|
out += span('number', text.slice(i, j));
|
|
871
871
|
i = j;
|
|
872
|
-
} else if (ch === '?'
|
|
873
|
-
let j = i +
|
|
874
|
-
|
|
872
|
+
} else if (ch === '?') {
|
|
873
|
+
let j = i + 1;
|
|
874
|
+
if (/[A-Za-z_]/.test(text[j] || '')) {
|
|
875
|
+
j++;
|
|
876
|
+
while (j < text.length && /[A-Za-z0-9_]/.test(text[j])) j++;
|
|
877
|
+
}
|
|
875
878
|
out += span('variable', text.slice(i, j));
|
|
876
879
|
i = j;
|
|
877
880
|
} else if (/[a-z]/.test(ch)) {
|
package/src/parser.js
CHANGED
|
@@ -43,14 +43,14 @@ function isNameContinueCode(code) {
|
|
|
43
43
|
function isQuestionVariableStart(source, pos) {
|
|
44
44
|
if (source[pos] !== '?') return false;
|
|
45
45
|
const code = (source[pos + 1] ?? '').charCodeAt(0);
|
|
46
|
-
return code === 95 || isAsciiLetterCode(code);
|
|
46
|
+
return !source[pos + 1] || code === 95 || isAsciiLetterCode(code) || !isNameContinueCode(code);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
function isPlainAtomStartCode(code) {
|
|
50
50
|
return code >= 97 && code <= 122;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
const graphicAtomChars = '
|
|
53
|
+
const graphicAtomChars = '#$&*+-/<=>@^~\\';
|
|
54
54
|
|
|
55
55
|
function isGraphicAtomCode(code) {
|
|
56
56
|
return graphicAtomChars.includes(String.fromCharCode(code));
|
|
@@ -172,10 +172,9 @@ class Parser {
|
|
|
172
172
|
if (isQuestionVariableStart(this.source, this.pos)) {
|
|
173
173
|
const start = this.pos;
|
|
174
174
|
this.take(); // ?
|
|
175
|
-
this.take();
|
|
175
|
+
if (isNameContinueCode(this.peek().charCodeAt(0))) this.take();
|
|
176
176
|
while (isNameContinueCode(this.peek().charCodeAt(0))) this.take();
|
|
177
|
-
|
|
178
|
-
if (text === '?_') text = `__anon${this.anonymous++}`;
|
|
177
|
+
const text = this.source.slice(start, this.pos);
|
|
179
178
|
return { type: TOK.VAR, text, line };
|
|
180
179
|
}
|
|
181
180
|
|
|
@@ -259,6 +258,7 @@ class Parser {
|
|
|
259
258
|
if (this.token.type === TOK.VAR) {
|
|
260
259
|
const name = this.token.text;
|
|
261
260
|
this.advance();
|
|
261
|
+
if (name === '?') return variable(`__anon${this.anonymous++}`);
|
|
262
262
|
return variable(name);
|
|
263
263
|
}
|
|
264
264
|
if (this.token.type === TOK.STRING) {
|
|
@@ -346,9 +346,9 @@ function isSimpleName(text) {
|
|
|
346
346
|
const SIMPLE_NUMBER = /^-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
|
|
347
347
|
const FAST_BINARY_FACT = /^([a-z][A-Za-z0-9_]*)\(\s*([^,\s()[\]|"']+)\s*,\s*([^,\s()[\]|"']+)\s*\)\.$/;
|
|
348
348
|
const FAST_BINARY_RULE = /^([a-z][A-Za-z0-9_]*)\(\s*([^,\s()[\]|"']+)\s*,\s*([^,\s()[\]|"']+)\s*\)\s*:-\s*([a-z][A-Za-z0-9_]*)\(\s*([^,\s()[\]|"']+)\s*,\s*([^,\s()[\]|"']+)\s*\)\.$/;
|
|
349
|
-
const SIMPLE_VARIABLE = /^\?[A-Za-z_][A-Za-z0-9_]
|
|
349
|
+
const SIMPLE_VARIABLE = /^\?(?:[A-Za-z_][A-Za-z0-9_]*)?$/;
|
|
350
350
|
const SIMPLE_ATOM = /^[a-z][A-Za-z0-9_]*$/;
|
|
351
|
-
const GRAPHIC_ATOM = /^[
|
|
351
|
+
const GRAPHIC_ATOM = /^[#$&*+\-\/<=>@^~\\]+$/;
|
|
352
352
|
|
|
353
353
|
function parseClausesFastNoSource(source) {
|
|
354
354
|
source = String(source ?? '');
|
|
@@ -370,7 +370,7 @@ function parseClausesFastNoSource(source) {
|
|
|
370
370
|
const scalarOrVariableFast = (text) => {
|
|
371
371
|
if (!text || !isFastScalarToken(text)) throw new Error('bad simple term');
|
|
372
372
|
const first = text.charCodeAt(0);
|
|
373
|
-
if (text === '?
|
|
373
|
+
if (text === '?') return variable(`__anon${anonymous++}`);
|
|
374
374
|
if (SIMPLE_VARIABLE.test(text)) {
|
|
375
375
|
const existing = variableCache.get(text);
|
|
376
376
|
if (existing) return existing;
|
package/src/term.js
CHANGED
|
@@ -136,7 +136,7 @@ export function termIsGround(term, env = new Env()) {
|
|
|
136
136
|
return resolved.args.every((arg) => termIsGround(arg, env));
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
const graphicAtomChars = new Set('
|
|
139
|
+
const graphicAtomChars = new Set('#$&*+-/<=>@^~\\'.split(''));
|
|
140
140
|
|
|
141
141
|
function isAbsoluteIriText(text) {
|
|
142
142
|
return /^[A-Za-z][A-Za-z0-9+.-]*:[^\s<>"'{}|\\^`]*$/.test(text);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
materialize(answer, 1).
|
|
2
|
-
answer(division_by_zero_fails) :- div(1, 0, ?
|
|
2
|
+
answer(division_by_zero_fails) :- div(1, 0, ?).
|
|
@@ -3,5 +3,5 @@ materialize(iri_subject, 1).
|
|
|
3
3
|
materialize(iri_object, 1).
|
|
4
4
|
triple(<https://example.org/alice>, <https://schema.org/name>, "Alice").
|
|
5
5
|
triple(<urn:example:bob>, <https://schema.org/knows>, <https://example.org/alice>).
|
|
6
|
-
iri_subject(?iri) :- triple(?iri,
|
|
7
|
-
iri_object(?iri) :- triple(
|
|
6
|
+
iri_subject(?iri) :- triple(?iri, ?, ?).
|
|
7
|
+
iri_object(?iri) :- triple(?, <https://schema.org/knows>, ?iri).
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
materialize(answer, 1).
|
|
2
|
-
answer(difference_end_before_start_fails) :- difference("2024-01-01", "2024-01-02", ?
|
|
2
|
+
answer(difference_end_before_start_fails) :- difference("2024-01-01", "2024-01-02", ?).
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
materialize(answer, 1).
|
|
2
|
-
answer(difference_invalid_date_fails) :- difference("2024-02-30", "2024-02-01", ?
|
|
2
|
+
answer(difference_invalid_date_fails) :- difference("2024-02-30", "2024-02-01", ?).
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
materialize(answer, 1).
|
|
2
|
-
answer(holds_ignores_string_parts) :- holds("not a term",
|
|
2
|
+
answer(holds_ignores_string_parts) :- holds("not a term", ?, ?).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
% Reference 5.3, 5.4: lists may contain structured terms that unify positionally.
|
|
2
2
|
node([pair(a, b), pair(c, d)]).
|
|
3
|
-
answer(first_key, ?x) :- node([pair(?x, ?
|
|
3
|
+
answer(first_key, ?x) :- node([pair(?x, ?), pair(c, d)]).
|
|
4
4
|
answer(second_key, ?x) :- node([pair(a, b), pair(?x, d)]).
|
|
5
5
|
materialize(answer, 2).
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
materialize(answer, 1).
|
|
2
|
-
answer(length_improper_fails) :- length([a | b], ?
|
|
2
|
+
answer(length_improper_fails) :- length([a | b], ?).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
% Reference 5.3, 6, 7.1: structured rule heads destructure matching goals.
|
|
2
2
|
unpack(pair(?x, ?y), ?x, ?y).
|
|
3
|
-
answer(first, ?a) :- unpack(pair(alpha, beta), ?a, ?
|
|
4
|
-
answer(second, ?b) :- unpack(pair(alpha, beta),
|
|
3
|
+
answer(first, ?a) :- unpack(pair(alpha, beta), ?a, ?).
|
|
4
|
+
answer(second, ?b) :- unpack(pair(alpha, beta), ?, ?b).
|
|
5
5
|
materialize(answer, 2).
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
materialize(answer, 1).
|
|
2
|
-
answer(arg_atom_fails) :- arg(1, atom, ?
|
|
2
|
+
answer(arg_atom_fails) :- arg(1, atom, ?).
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
materialize(answer, 1).
|
|
2
|
-
answer(arg_zero_fails) :- arg(0, pair(a, b), ?
|
|
2
|
+
answer(arg_zero_fails) :- arg(0, pair(a, b), ?).
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
materialize(answer, 1).
|
|
2
|
-
answer(compound_name_arguments_bad_args_fails) :- compound_name_arguments(
|
|
2
|
+
answer(compound_name_arguments_bad_args_fails) :- compound_name_arguments(?, pair, not_a_list).
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
materialize(answer, 1).
|
|
2
|
-
answer(anonymous_not_reused) :- eq(pair(
|
|
2
|
+
answer(anonymous_not_reused) :- eq(pair(?, ?), pair(a, b)).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
% Each
|
|
1
|
+
% Each `?` occurrence is anonymous and independent.
|
|
2
2
|
materialize(answer, 1).
|
|
3
3
|
pair(a, b).
|
|
4
4
|
pair(c, d).
|
|
5
|
-
answer(left(?x)) :- pair(?x, ?
|
|
6
|
-
answer(right(?y)) :- pair(
|
|
5
|
+
answer(left(?x)) :- pair(?x, ?).
|
|
6
|
+
answer(right(?y)) :- pair(?, ?y).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
parse line 1:
|
|
1
|
+
parse line 1: bad character "?"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
parse line 1:
|
|
1
|
+
parse line 1: bad character "?"
|
package/test/run-regression.mjs
CHANGED
|
@@ -639,9 +639,9 @@ function whiteBoxCases() {
|
|
|
639
639
|
},
|
|
640
640
|
},
|
|
641
641
|
{
|
|
642
|
-
name: 'parser treats question
|
|
642
|
+
name: 'parser treats bare question mark as anonymous',
|
|
643
643
|
run: () => {
|
|
644
|
-
const clauses = parseProgramText('p(
|
|
644
|
+
const clauses = parseProgramText('p(?, ?).\n');
|
|
645
645
|
const left = clauses[0].head.args[0].name;
|
|
646
646
|
const right = clauses[0].head.args[1].name;
|
|
647
647
|
assertEqual(left.startsWith('__anon'), true, 'left anonymous');
|
|
@@ -800,7 +800,7 @@ function whiteBoxCases() {
|
|
|
800
800
|
['matrix-chain-order.eye', 'cost', 3, false],
|
|
801
801
|
['modular-exponentiation.eye', 'pow_mod', 4, true],
|
|
802
802
|
['pell-equation.eye', 'pell', 3, true],
|
|
803
|
-
['stirling-bell-numbers.eye', 'stirling2', 3,
|
|
803
|
+
['stirling-bell-numbers.eye', 'stirling2', 3, false],
|
|
804
804
|
['totient-summatory.eye', 'gcd', 3, true],
|
|
805
805
|
['totient-summatory.eye', 'totient', 2, false],
|
|
806
806
|
['weighted-interval-scheduling.eye', 'best_from', 2, true],
|