eyelang 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +22 -43
  2. package/SPEC.md +16 -16
  3. package/bin/eyelang +0 -0
  4. package/conformance/README.md +4 -5
  5. package/conformance/cases/core/001_fact_output.pl +4 -0
  6. package/conformance/cases/core/002_rule_recursion.pl +4 -2
  7. package/conformance/cases/core/003_terms_and_readback.pl +15 -13
  8. package/conformance/cases/core/004_conjunction_and_parentheses.pl +1 -0
  9. package/conformance/cases/core/005_list_deconstruction.pl +1 -0
  10. package/conformance/cases/core/006_comma_formula_data.pl +1 -0
  11. package/conformance/cases/core/007_anonymous_variables.pl +1 -0
  12. package/conformance/cases/core/008_graphic_atoms.pl +5 -3
  13. package/conformance/cases/core/009_comments_and_whitespace.pl +1 -0
  14. package/conformance/cases/core/010_variable_scope_and_reuse.pl +1 -0
  15. package/conformance/cases/core/011_predicate_arity.pl +1 -0
  16. package/conformance/cases/core/012_nested_compound_unification.pl +1 -0
  17. package/conformance/cases/core/013_multiple_clauses_order.pl +1 -0
  18. package/conformance/cases/core/014_failure_filters_answers.pl +2 -0
  19. package/conformance/cases/core/015_improper_list_unification.pl +1 -0
  20. package/conformance/cases/core/016_zero_arity_compound.pl +1 -0
  21. package/conformance/cases/core/017_three_step_recursion.pl +1 -0
  22. package/conformance/cases/core/018_quoted_atom_readback.pl +1 -0
  23. package/conformance/cases/core/019_parenthesized_three_conjuncts.pl +1 -0
  24. package/conformance/cases/core/020_nested_list_terms.pl +1 -0
  25. package/conformance/cases/extension/001_default_derived_output.pl +1 -1
  26. package/conformance/cases/extension/002_materialize_focus.pl +1 -1
  27. package/conformance/cases/extension/003_arithmetic_and_comparison.pl +1 -0
  28. package/conformance/cases/extension/004_strings_and_atoms.pl +1 -0
  29. package/conformance/cases/extension/005_lists_aggregation_ordering.pl +1 -0
  30. package/conformance/cases/extension/006_formula_terms.pl +1 -0
  31. package/conformance/cases/extension/007_negation_once_generators.pl +1 -0
  32. package/conformance/cases/extension/008_equality_and_inequality.pl +1 -0
  33. package/conformance/cases/extension/009_list_relations.pl +1 -0
  34. package/conformance/cases/extension/010_append_splits.pl +1 -0
  35. package/conformance/cases/extension/011_matching_and_comparison.pl +1 -0
  36. package/conformance/cases/extension/012_memoize_declaration.pl +5 -3
  37. package/conformance/cases/extension/013_numeric_functions.pl +1 -0
  38. package/conformance/cases/extension/014_between_enumeration.pl +1 -0
  39. package/conformance/cases/extension/015_smallest_divisor.pl +1 -0
  40. package/conformance/cases/extension/016_negation_filter.pl +1 -0
  41. package/conformance/cases/extension/017_once_user_predicate.pl +1 -0
  42. package/conformance/cases/extension/018_findall_user_goal.pl +1 -0
  43. package/conformance/cases/extension/019_sort_deduplicates_atoms.pl +1 -0
  44. package/conformance/cases/extension/020_append_bound_prefix_suffix.pl +1 -0
  45. package/conformance/cases/extension/021_nth0_index_generation.pl +1 -0
  46. package/conformance/cases/extension/022_set_nth0_edges.pl +1 -0
  47. package/conformance/cases/extension/023_select_duplicate_occurrences.pl +1 -0
  48. package/conformance/cases/extension/024_not_member_filter.pl +1 -0
  49. package/conformance/cases/extension/025_is_list_filter.pl +1 -0
  50. package/conformance/cases/extension/026_nested_formula_terms.pl +1 -0
  51. package/conformance/cases/extension/027_materialize_excludes_source_fact.pl +1 -1
  52. package/conformance/cases/extension/028_numeric_and_lexical_comparison.pl +1 -0
  53. package/conformance/cases/extension/029_string_matching_filters.pl +1 -0
  54. package/conformance/cases/extension/030_string_and_atom_concat.pl +1 -0
  55. package/conformance/cases/extension/031_countall_empty_and_nonempty.pl +2 -0
  56. package/conformance/cases/extension/032_sumall_numeric_template.pl +2 -0
  57. package/conformance/cases/extension/033_aggregate_min_template.pl +2 -0
  58. package/conformance/cases/extension/034_aggregate_max_compound_key.pl +2 -0
  59. package/conformance/expected/extension/031_countall_empty_and_nonempty.out +1 -1
  60. package/conformance/expected/extension/032_sumall_numeric_template.out +1 -1
  61. package/conformance/expected/extension/033_aggregate_min_template.out +1 -1
  62. package/conformance/expected/extension/034_aggregate_max_compound_key.out +1 -1
  63. package/examples/basic-monadic.pl +1 -1
  64. package/examples/monkey-bananas.pl +1 -1
  65. package/examples/path-discovery.pl +3 -3
  66. package/examples/peano-arithmetic.pl +1 -1
  67. package/package.json +1 -1
  68. package/playground-worker.mjs +2 -15
  69. package/playground.html +5 -25
  70. package/src/builtins/aggregation.js +5 -5
  71. package/src/builtins/control.js +3 -3
  72. package/src/cli.js +4 -26
  73. package/src/index.js +15 -24
  74. package/src/parser.js +3 -3
  75. package/src/solver.js +2 -2
  76. package/test/run-conformance.js +1 -3
  77. package/test/run-regression.js +38 -59
  78. package/conformance/cases/core/001_fact_query.pl +0 -2
  79. /package/conformance/expected/core/{001_fact_query.out → 001_fact_output.out} +0 -0
package/README.md CHANGED
@@ -5,10 +5,10 @@
5
5
 
6
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`.
7
7
 
8
- Programs write relations directly, for example `ancestor(pat, emma)` or `status(case1, accepted)`. eyelang output is ordinary eyelang syntax: by default, it prints answer facts only. Pass `--proof` (or `-p`) when you also want each answer followed by a `why/2` explanation fact that records the proof. When no `--query` is supplied, the CLI materializes distinct new binary derivations of the form `p(S, O)`, suppresses repeated source facts, and prints each derived answer. Programs may add `materialize(Name, Arity).` declarations to focus default output on selected predicates.
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.
9
9
 
10
10
 
11
- Try it in the [browser playground](https://eyereasoner.github.io/eyelang/playground). The playground includes run options equivalent to CLI `--query`, `--stats`, and `--proof`.
11
+ Try it in the [browser playground](https://eyereasoner.github.io/eyelang/playground). The playground includes run options equivalent to CLI `--stats` and `--proof`.
12
12
 
13
13
  For the normative language definition, including lexical syntax, terms, clauses, goals, built-ins, `memoize/2`, `materialize/2`, and conformance boundaries, read the [eyelang specification](SPEC.md).
14
14
 
@@ -34,12 +34,11 @@ Install dependencies, if any, and run the command-line executable:
34
34
  npm install
35
35
  ```
36
36
 
37
- There is no build step for the CLI. Run examples, explicit queries, multiple inputs, stdin, or a URL:
37
+ There is no build step for the CLI. Run examples, multiple inputs, stdin, or a URL:
38
38
 
39
39
  ```sh
40
40
  bin/eyelang --version
41
41
  bin/eyelang examples/ancestor.pl
42
- bin/eyelang --query 'ancestor(pat, X)' examples/ancestor.pl
43
42
  bin/eyelang facts.pl rules.pl
44
43
  printf 'works(stdin, true) :- eq(ok, ok).\n' | bin/eyelang -
45
44
  bin/eyelang https://raw.githubusercontent.com/eyereasoner/eyelang/refs/heads/main/examples/ancestor.pl
@@ -69,20 +68,14 @@ Run a program and let eyelang print derived binary facts:
69
68
  bin/eyelang examples/ancestor.pl
70
69
  ```
71
70
 
72
- Run an explicit query:
73
-
74
- ```sh
75
- bin/eyelang --query 'ancestor(pat, X)' examples/ancestor.pl
76
- ```
77
-
78
71
  Enable proof explanations when you want machine-readable provenance:
79
72
 
80
73
  ```sh
81
- bin/eyelang --proof --query 'ancestor(pat, X)' examples/ancestor.pl
74
+ bin/eyelang --proof examples/ancestor.pl
82
75
  bin/eyelang -p examples/ancestor.pl
83
76
  ```
84
77
 
85
- eyelang-readable explanations are opt-in proof output. Each `why/2` fact contains a nested abstract proof term, and a blank line separates consecutive explanations. Using eyelang syntax for explanations keeps them in the same language as the answers themselves: they are readable by humans, parseable by eyelang, easy to test, and can be queried, transformed, or explained further like any other eyelang data. For example:
78
+ eyelang-readable explanations are opt-in proof output. Each `why/2` fact contains a nested abstract proof term, and a blank line separates consecutive explanations. Using eyelang syntax for explanations keeps them in the same language as the answers themselves: they are readable by humans, parseable by eyelang, easy to test, and can be transformed or explained further like any other eyelang data. For example:
86
79
 
87
80
  ```prolog
88
81
  type(socrates, mortal).
@@ -103,7 +96,7 @@ why(
103
96
 
104
97
  ```
105
98
 
106
- The explanation output can itself be read as eyelang input and queried, for example `why(type(socrates, mortal), Proof)`. `--proof` adds only these explanation facts; it does not change the answers found by the solver.
99
+ The explanation output can itself be read as eyelang input; for example, another program can materialize `why/2` facts such as `why(type(socrates, mortal), Proof)`. `--proof` adds only these explanation facts; it does not change the answers found by the solver.
107
100
 
108
101
  ### Explanation cookbook
109
102
 
@@ -112,7 +105,7 @@ eyelang answers can carry their own provenance when proof output is enabled.
112
105
  Explain one derived fact:
113
106
 
114
107
  ```sh
115
- bin/eyelang --proof --query 'type(socrates, mortal)' examples/socrates.pl
108
+ bin/eyelang --proof examples/socrates.pl
116
109
  ```
117
110
 
118
111
  The output contains the answer and a `why/2` fact. The proof term shows the source rule that produced the answer and the source fact used below it. Source references use `rule("file.pl", clause(N))` and `fact("file.pl", clause(N))`, where `N` is the 1-based clause number in that file.
@@ -130,7 +123,7 @@ status(Case, accepted) :-
130
123
  ```
131
124
 
132
125
  ```sh
133
- bin/eyelang --query 'status(Case, accepted)' policy.pl
126
+ bin/eyelang --proof policy.pl
134
127
  ```
135
128
 
136
129
  The explanation contains the instantiated answer and the variables that made the rule succeed:
@@ -153,12 +146,10 @@ Use the `uses([...])` list to follow the proof tree. In the policy example it co
153
146
  Reuse explanations as data:
154
147
 
155
148
  ```sh
156
- bin/eyelang --proof --query 'type(socrates, mortal)' examples/socrates.pl > socrates.why.pl
157
- bin/eyelang --query 'why(type(socrates, mortal), Proof)' socrates.why.pl
149
+ bin/eyelang --proof examples/socrates.pl > socrates.why.pl
158
150
  ```
159
151
 
160
- When a query has no answers, eyelang prints no answers and no explanations. That makes missing proofs explicit without inventing a reason.
161
-
152
+ The resulting file is ordinary eyelang syntax containing both answers and `why/2` proof facts.
162
153
 
163
154
  Compose multiple files, stdin, and URLs:
164
155
 
@@ -168,12 +159,6 @@ printf 'works(stdin, true) :- eq(ok, ok).\n' | bin/eyelang -
168
159
  bin/eyelang https://example.test/program.pl
169
160
  ```
170
161
 
171
- `--query GOAL` parses a single goal. Parenthesized conjunctions are accepted:
172
-
173
- ```sh
174
- bin/eyelang --query '(ancestor(pat, X), ancestor(X, emma))' examples/ancestor.pl
175
- ```
176
-
177
162
  ## Default output
178
163
 
179
164
  eyelang programs write relation predicates directly:
@@ -186,7 +171,7 @@ ancestor(X, Y) :- parent(X, Y).
186
171
  ancestor(X, Z) :- parent(X, Y), ancestor(Y, Z).
187
172
  ```
188
173
 
189
- Without `--query`, eyelang asks for new ground binary consequences of the shape `p(S, O)`, suppresses duplicates, excludes source facts, sorts the result, and prints Prolog facts:
174
+ By default, eyelang asks for new ground consequences of selected output predicates, suppresses duplicates, excludes source facts, sorts the result, and prints Prolog facts:
190
175
 
191
176
  ```prolog
192
177
  ancestor(jan, emma).
@@ -194,11 +179,11 @@ ancestor(pat, emma).
194
179
  ancestor(pat, jan).
195
180
  ```
196
181
 
197
- This default is intentionally output-oriented. It is not a complete bottom-up saturation engine. Built-ins and proof search remain goal-directed; use `--query` when you want a specific relation, arity, or non-binary answer.
182
+ This default is intentionally output-oriented. It is not a complete bottom-up saturation engine. Built-ins and proof search remain goal-directed; use `materialize/2` declarations and small output predicates when you want a specific relation, arity, or non-binary answer.
198
183
 
199
184
  ### Focusing default output
200
185
 
201
- Large examples often have internal helper predicates. Add `materialize(Name, Arity).` declarations to restrict no-query output to selected predicates:
186
+ Large examples often have internal helper predicates. Add `materialize(Name, Arity).` declarations to restrict default output to selected predicates:
202
187
 
203
188
  ```prolog
204
189
  materialize(answer, 2).
@@ -214,7 +199,7 @@ The default output is then:
214
199
  answer(case1, accepted).
215
200
  ```
216
201
 
217
- `materialize/2` is a declaration, not a logical rule to prove. It does not affect explicit `--query` answers.
202
+ `materialize/2` is a declaration, not a logical rule to prove. It affects which predicates the CLI prints, not the meaning of the rules themselves.
218
203
 
219
204
  ## Writing programs
220
205
 
@@ -250,20 +235,14 @@ materialize(reason, 2).
250
235
 
251
236
  Predicate names and atom constants use the same lexical form. Namespace-like names should be plain names such as `type`, `person_name`, or `odrl_permission`; colon names are not part of the language.
252
237
 
253
- ### Queries remain general
238
+ ### Embedding remains general
254
239
 
255
- The default output focuses on materialized binary facts, but the query engine supports arbitrary goals and arities:
256
-
257
- ```sh
258
- bin/eyelang --query 'append(A, B, [a, b])' examples/list-collection.pl
259
- bin/eyelang --query 'ackermann(4, 2, A)' examples/ackermann.pl
260
- bin/eyelang --query 'once(solution(classic, S))' examples/sudoku.pl
261
- ```
240
+ The CLI is output-oriented and uses `materialize/2` to decide what to print. Embedders can still use the JavaScript API and `Solver` directly for arbitrary goals and arities.
262
241
 
263
242
  Add `--stats` when you want lightweight solver counters on stderr without changing stdout:
264
243
 
265
244
  ```sh
266
- bin/eyelang --stats --query 'once(solution(classic, S))' examples/sudoku.pl
245
+ bin/eyelang --stats examples/sudoku.pl
267
246
  ```
268
247
 
269
248
  The playground has matching `--stats` and `--proof` checkboxes, so browser runs can show the same counters or explanations like the CLI.
@@ -504,7 +483,7 @@ npm run test:regression
504
483
  npm run test:examples
505
484
  ```
506
485
 
507
- The conformance suite lives in [`conformance/`](conformance/) and is split into `core` and `extension` profiles matching `SPEC.md`. Each case is a small program with optional query text and an exact expected stdout file, so other implementations can reuse the same cases. The regression suite lives in [`test/run-regression.js`](test/run-regression.js) and covers CLI regressions, the public JavaScript API, and white-box invariants for parser, unification, and indexing behavior.
486
+ The conformance suite lives in [`conformance/`](conformance/) and is split into `core` and `extension` profiles matching `SPEC.md`. Each case is a small program with an exact expected stdout file, and some internal conformance cases also include a goal file for testing the embeddable solver, so other implementations can reuse the same cases. The regression suite lives in [`test/run-regression.js`](test/run-regression.js) and covers CLI regressions, the public JavaScript API, and white-box invariants for parser, unification, and indexing behavior.
508
487
 
509
488
  ## Development and release
510
489
 
@@ -521,7 +500,7 @@ node bin/eyelang --help
521
500
  Useful profiling smoke test:
522
501
 
523
502
  ```sh
524
- bin/eyelang --stats --query 'once(solution(classic, S))' examples/sudoku.pl > /dev/null
503
+ bin/eyelang --stats examples/sudoku.pl > /dev/null
525
504
  ```
526
505
 
527
506
  For a release:
@@ -530,7 +509,7 @@ For a release:
530
509
  2. update `README.md` and `SPEC.md`;
531
510
  3. regenerate golden outputs if behavior changed;
532
511
  4. run `npm test`;
533
- 5. publish the repository with `playground.html` and `playground-worker.mjs` if publishing the playground. The playground includes controls equivalent to CLI `--query GOAL`, `--stats`, and `--proof`.
512
+ 5. publish the repository with `playground.html` and `playground-worker.mjs` if publishing the playground. The playground includes controls equivalent to CLI `--stats` and `--proof`.
534
513
 
535
514
  ## Performance notes
536
515
 
@@ -550,8 +529,8 @@ Ground facts use a fast path that avoids freshening and copying a rule body. Rec
550
529
  memoize(path, 2).
551
530
  ```
552
531
 
553
- For large programs, keep helper predicates selective, bind arguments early, and declare focused output predicates with `materialize/2` when default output would otherwise ask broad helper queries.
532
+ For large programs, keep helper predicates selective, bind arguments early, and declare focused output predicates with `materialize/2` when default output would otherwise solve broad helper goals.
554
533
 
555
534
  ## Implementation limits
556
535
 
557
- eyelang is intentionally smaller than ISO Prolog. It has no operators, cut, modules, dynamic database updates, DCGs, or complete ISO library. Negation is negation-as-failure through `not/1`. Search is goal-directed and expected to be finite for the supplied program and query. Output explanations are non-normative proof printouts and do not change answer semantics.
536
+ eyelang is intentionally smaller than ISO Prolog. It has no operators, cut, modules, dynamic database updates, DCGs, or complete ISO library. Negation is negation-as-failure through `not/1`. Search is goal-directed and expected to be finite for the selected output goals. Output explanations are non-normative proof printouts and do not change answer semantics.
package/SPEC.md CHANGED
@@ -87,7 +87,7 @@ A **goal** is an atomic formula, a built-in call, or a comma conjunction.
87
87
 
88
88
  A **source fact** is a fact written directly in the input program. A **new derivation** is a ground consequence found through at least one rule and not merely repeated from the source facts.
89
89
 
90
- The **Herbrand universe** of a program is the set of all ground eyelang terms constructible from the constants and functors in the program, together with the built-in list constructors `[]` and `./2` where lists are used. The **Herbrand base** is the set of all ground atomic formulas whose predicate symbols occur in the program or query and whose arguments are terms from the Herbrand universe.
90
+ The **Herbrand universe** of a program is the set of all ground eyelang terms constructible from the constants and functors in the program, together with the built-in list constructors `[]` and `./2` where lists are used. The **Herbrand base** is the set of all ground atomic formulas whose predicate symbols occur in the program and whose arguments are terms from the Herbrand universe.
91
91
 
92
92
  ## 2. Design goals
93
93
 
@@ -199,7 +199,7 @@ term ::= variable
199
199
  list_items ::= term ("," term)* ["|" term]
200
200
  ```
201
201
 
202
- Here `atom_constant` is a lexical class for symbolic scalar terms, not an atomic formula. Atomic formulas are represented by the grammar alternative `atom_constant "(" ... ")"` when such a compound appears in a clause head, rule body, or query goal.
202
+ Here `atom_constant` is a lexical class for symbolic scalar terms, not an atomic formula. Atomic formulas are represented by the grammar alternative `atom_constant "(" ... ")"` when such a compound appears in a clause head, rule body, or selected goal.
203
203
 
204
204
  A clause head SHOULD be a compound term. Non-compound heads are parsed but are not useful in the current predicate index.
205
205
 
@@ -214,7 +214,7 @@ value(example, nil()).
214
214
 
215
215
  ### 5.1 Variables
216
216
 
217
- Variables are scoped to a single clause or query. A variable in a rule head and body denotes the same logical variable within that clause.
217
+ Variables are scoped to a single clause or selected goal. A variable in a rule head and body denotes the same logical variable within that clause.
218
218
 
219
219
  ### 5.2 Atom constants, strings, and numbers
220
220
 
@@ -279,7 +279,7 @@ Clauses with the same predicate name and arity define one predicate group. Predi
279
279
 
280
280
  Goals are solved left-to-right. For a user-defined atomic-formula goal, eyelang selects candidate clauses by predicate name, arity, and available indexes. A candidate clause is freshened, its head is unified with the goal, and then its body is solved.
281
281
 
282
- A conjunction goal succeeds when all conjunct goals succeed in order. A query answer is printed as the resolved query term followed by a period.
282
+ A conjunction goal succeeds when all conjunct goals succeed in order. An answer is printed as the resolved answer term followed by a period.
283
283
 
284
284
  ### 7.1 Unification
285
285
 
@@ -291,7 +291,7 @@ A goal fails when no built-in case or user clause can prove it. eyelang has no e
291
291
 
292
292
  ### 7.3 Finite search expectation
293
293
 
294
- Programs and queries SHOULD be written so the relevant search space is finite. eyelang includes recursion guards and memoization support, but it is not required to terminate for arbitrary recursive logic programs.
294
+ Programs and selected output goals SHOULD be written so the relevant search space is finite. eyelang includes recursion guards and memoization support, but it is not required to terminate for arbitrary recursive logic programs.
295
295
 
296
296
  ## 8. Logical reading: Herbrand semantics
297
297
 
@@ -317,7 +317,7 @@ Equivalently, the least Herbrand model is obtained by repeatedly applying the im
317
317
 
318
318
  ### 8.1 Variables and quantification
319
319
 
320
- Variables do not range over external objects, records, pointers, or host-language values. In the logical reading, variables range over Herbrand terms. A rule is implicitly universally quantified over its variables. A query is existential in the usual logic-programming sense: eyelang searches for substitutions of the query variables by Herbrand terms that make the query true with respect to the program.
320
+ Variables do not range over external objects, records, pointers, or host-language values. In the logical reading, variables range over Herbrand terms. A rule is implicitly universally quantified over its variables. A selected goal is existential in the usual logic-programming sense: eyelang searches for substitutions of its variables by Herbrand terms that make the goal true with respect to the program.
321
321
 
322
322
  ### 8.2 Equality, identity, and unification
323
323
 
@@ -329,7 +329,7 @@ Operationally, eyelang uses first-order unification to find substitutions. The i
329
329
 
330
330
  eyelang's CLI and library evaluator are goal-directed. They try to prove requested goals by resolving them against facts, rules, and built-ins, using clause order, goal order, indexing, memoization, and deterministic built-in execution. This operational strategy is intended to enumerate answers that are true in the least Herbrand model for the pure Horn-clause fragment, but it is not a complete bottom-up model enumerator. Non-terminating recursion or infinite generators can prevent an answer from being found even when the answer belongs to the least Herbrand model.
331
331
 
332
- The no-query CLI output is also a host behavior, not a separate semantics. It asks broad materialization queries, suppresses duplicates, excludes source facts, keeps ground answers, and prints selected consequences. Explicit `--query` gives direct access to the goal-directed solver.
332
+ Default CLI output is also a host behavior, not a separate semantics. It asks broad materialization goals, suppresses duplicates, excludes source facts, keeps ground answers, and prints selected consequences. Embedders can still access the goal-directed solver directly through the implementation API.
333
333
 
334
334
  ### 8.4 Built-ins and operational extensions
335
335
 
@@ -498,7 +498,7 @@ memoize(path, 2).
498
498
  materialize(Name, Arity).
499
499
  ```
500
500
 
501
- `Name` MUST be an atom constant and `Arity` MUST be a non-negative integer. If a program contains one or more `materialize/2` declarations, no-query CLI output is restricted to those predicate groups. Source facts are still excluded from printed output.
501
+ `Name` MUST be an atom constant and `Arity` MUST be a non-negative integer. If a program contains one or more `materialize/2` declarations, default CLI output is restricted to those predicate groups. Source facts are still excluded from printed output.
502
502
 
503
503
  Example:
504
504
 
@@ -507,7 +507,7 @@ materialize(status, 2).
507
507
  materialize(reason, 2).
508
508
  ```
509
509
 
510
- `materialize/2` does not affect explicit queries.
510
+ `materialize/2` affects host output selection only; it does not change the logical meaning of the program.
511
511
 
512
512
  ## 12. eyelang Sockets
513
513
 
@@ -515,7 +515,7 @@ A **eyelang Socket** is a declared semantic opening in a eyelang program where f
515
515
 
516
516
  The term follows the ordinary socket pattern: a socket defines a place where a matching provider can connect. In eyelang, the matching part is knowledge. A socket identifies what shape of knowledge a program expects; a plug identifies which provider supplies it. This separates reasoning logic from knowledge providers and makes composition boundaries visible as eyelang data.
517
517
 
518
- In this specification, sockets are a portable **programming pattern** expressed with ordinary facts. The core solver does not give `socket/2`, `plug/2`, `provides/1`, or `requires/1` special proof-search behavior unless a host explicitly documents such an extension. Because they are ordinary facts, socket declarations remain readable, queryable, explainable, and safe to ignore by hosts that do not validate them.
518
+ In this specification, sockets are a portable **programming pattern** expressed with ordinary facts. The core solver does not give `socket/2`, `plug/2`, `provides/1`, or `requires/1` special proof-search behavior unless a host explicitly documents such an extension. Because they are ordinary facts, socket declarations remain readable, inspectable, explainable, and safe to ignore by hosts that do not validate them.
519
519
 
520
520
  ### 12.1 Socket vocabulary
521
521
 
@@ -566,7 +566,7 @@ ancestor(X, Z) :-
566
566
 
567
567
  The `ancestor/2` rules do not depend on a particular storage mechanism for `parent/2`. In a small test, the provider may be the same file. In an embedded host, it may be a database adapter, a document extractor, a remote service, or another eyelang module. The socket facts make that boundary explicit without changing the logical meaning of the rules.
568
568
 
569
- When eyelang derives `ancestor(pat, emma)`, the answer explanation can still refer to the source clauses that were actually used, for example facts for `parent/2` and rules for `ancestor/2`. The socket facts add a queryable description of where such knowledge is intended to enter.
569
+ When eyelang derives `ancestor(pat, emma)`, the answer explanation can still refer to the source clauses that were actually used, for example facts for `parent/2` and rules for `ancestor/2`. The socket facts add an inspectable description of where such knowledge is intended to enter.
570
570
 
571
571
  ### 12.3 Sockets and AI agents
572
572
 
@@ -578,13 +578,13 @@ This gives a clear division of labor: AI can help generate, translate, and conne
578
578
 
579
579
  Normal answer output prints one resolved answer term followed by a period. Strings are double-quoted; atom constants are quoted when needed; lists use list syntax; compound terms use functor notation. Host interfaces MAY provide an option such as `--proof` to add `why/2` explanation facts; this option MUST NOT change the answers found.
580
580
 
581
- Output SHOULD be accepted as eyelang input when it contains only supported term syntax. Explanations are ordinary eyelang facts, so answer output can be read back and queried by eyelang.
581
+ Output SHOULD be accepted as eyelang input when it contains only supported term syntax. Explanations are ordinary eyelang facts, so answer output can be read back and processed by eyelang.
582
582
 
583
- Without `--query`, the host behavior is:
583
+ Default host output behavior is:
584
584
 
585
585
  1. parse all inputs into one program;
586
586
  2. collect source fact lines for duplicate suppression;
587
- 3. if `materialize/2` declarations exist, query those predicate groups; otherwise query all binary predicate groups with at least one rule;
587
+ 3. if `materialize/2` declarations exist, solve those predicate groups; otherwise solve all binary predicate groups with at least one rule;
588
588
  4. keep only ground answers;
589
589
  5. remove answers identical to source facts;
590
590
  6. suppress duplicates;
@@ -617,7 +617,7 @@ A conforming standard host also supports:
617
617
 
618
618
  - `memoize/2` declarations;
619
619
  - `materialize/2` declarations;
620
- - default no-query derived output;
620
+ - default derived output;
621
621
  - explanation output;
622
622
  - stdin, file, and URL inputs in the CLI.
623
623
 
@@ -679,4 +679,4 @@ status(a, open) :- open(a).
679
679
 
680
680
  URL input uses host networking support when available. Hosts SHOULD treat downloaded programs as untrusted code because they can trigger expensive search.
681
681
 
682
- Programs SHOULD be written with finite search in mind. Broad no-query materialization can be expensive for helper predicates; use explicit `--query` or `materialize/2` declarations when needed.
682
+ Programs SHOULD be written with finite search in mind. Broad default materialization can be expensive for helper predicates; use `materialize/2` declarations and concise output predicates when needed.
package/bin/eyelang CHANGED
File without changes
@@ -5,7 +5,6 @@ This directory contains the executable conformance cases for the eyelang languag
5
5
  The suite is intentionally file-based so another implementation can run the same programs and compare exact standard output. A case consists of:
6
6
 
7
7
  - `conformance/cases/<profile>/<name>.pl` — input program;
8
- - optional `conformance/cases/<profile>/<name>.query` — query text passed to `eyelang --query`;
9
8
  - `conformance/expected/<profile>/<name>.out` — exact expected standard output.
10
9
 
11
10
  The current runner compares standard output, including answer facts and their `why/2` explanation facts. Standard error, performance, and resource limits are outside this suite.
@@ -31,16 +30,16 @@ node test/run-conformance.js core
31
30
  node test/run-conformance.js extension
32
31
  ```
33
32
 
34
- The runner executes cases through the public CLI path `bin/eyelang`, so the suite checks the same entry point used from the shell.
33
+ The runner executes materialized programs in-process through the public JavaScript API so small conformance cases avoid measuring Node startup overhead.
35
34
 
36
35
  ## Profiles
37
36
 
38
- `core` covers the portable core language profile from `../SPEC.md`: lexical syntax, facts, definite clauses, first-order terms, lists, conjunction, unification through user predicates, left-to-right goal-directed proof search, and answer printing with explanations.
37
+ `core` covers the portable core language profile from `../SPEC.md`: lexical syntax, facts, definite clauses, first-order terms, lists, conjunction, unification through user predicates, left-to-right goal-directed proof search, and answer printing.
39
38
 
40
- `extension` covers the standard built-in and host behavior exercised by the current reference implementation: arithmetic, comparison, strings, list relations, aggregation, formula-term helpers, `memoize/2`, `materialize/2`, and default no-query derived output.
39
+ `extension` covers the standard built-in and host behavior exercised by the current reference implementation: arithmetic, comparison, strings, list relations, aggregation, formula-term helpers, `memoize/2`, `materialize/2`, and default derived output.
41
40
 
42
41
  The profile name `extension` is a test-suite grouping name. It does not mean that these cases are outside the eyelang specification; most of them correspond to the standard built-in profile and standard host profile in `../SPEC.md`.
43
42
 
44
43
  ## Updating expected output
45
44
 
46
- There is no committed auto-accept mode. To update an expected file, run the matching case with `bin/eyelang`, inspect the result, and replace the corresponding file under `conformance/expected/<profile>/` deliberately.
45
+ There is no committed auto-accept mode. To update an expected file, run the matching case with the conformance runner, inspect the result, and replace the corresponding file under `conformance/expected/<profile>/` deliberately.
@@ -0,0 +1,4 @@
1
+ % SPEC 6, 7, 11: facts can be exposed through a materialized derived predicate.
2
+ materialize(parent, 2).
3
+ base_parent(pat, jan).
4
+ parent(X, Y) :- base_parent(X, Y).
@@ -1,5 +1,7 @@
1
1
  % SPEC 6, 7: definite clauses, conjunction, and recursive proof search.
2
+ materialize(ancestor, 2).
2
3
  parent(pat, jan).
3
4
  parent(jan, emma).
4
- ancestor(X, Y) :- parent(X, Y).
5
- ancestor(X, Z) :- parent(X, Y), ancestor(Y, Z).
5
+ ancestor_any(X, Y) :- parent(X, Y).
6
+ ancestor_any(X, Z) :- parent(X, Y), ancestor_any(Y, Z).
7
+ ancestor(pat, Y) :- ancestor_any(pat, Y).
@@ -1,14 +1,16 @@
1
1
  % SPEC 3, 5, 11: scalars, compounds, lists, and read-back printing.
2
- value(atom, pat).
3
- value(quoted_atom, 'atom with spaces').
4
- value(quoted_quote, 'needs''quote').
5
- value(empty_atom, '').
6
- value(string, "line\nquote: \"ok\"").
7
- value(integer, -42).
8
- value(decimal, 0.25).
9
- value(scientific, 1.25e-3).
10
- value(compound, pair(3, nested(atom, [x, y]))).
11
- value(zero_arity, nil()).
12
- value(empty_list, []).
13
- value(proper_list, [a, b, c]).
14
- value(improper_list, [a, b | tail]).
2
+ materialize(value, 2).
3
+ raw_value(atom, pat).
4
+ raw_value(quoted_atom, 'atom with spaces').
5
+ raw_value(quoted_quote, 'needs''quote').
6
+ raw_value(empty_atom, '').
7
+ raw_value(string, "line\nquote: \"ok\"").
8
+ raw_value(integer, -42).
9
+ raw_value(decimal, 0.25).
10
+ raw_value(scientific, 1.25e-3).
11
+ raw_value(compound, pair(3, nested(atom, [x, y]))).
12
+ raw_value(zero_arity, nil()).
13
+ raw_value(empty_list, []).
14
+ raw_value(proper_list, [a, b, c]).
15
+ raw_value(improper_list, [a, b | tail]).
16
+ value(K, V) :- raw_value(K, V).
@@ -2,3 +2,4 @@
2
2
  p(a).
3
3
  q(a).
4
4
  ok(X, yes) :- (p(X), q(X)).
5
+ materialize(ok, 2).
@@ -3,3 +3,4 @@ first([X | _Rest], X).
3
3
  tail([_Head | Tail], Tail).
4
4
  answer(first, X) :- first([a, b, c], X).
5
5
  answer(tail, Tail) :- tail([a, b, c], Tail).
6
+ materialize(answer, 2).
@@ -1,3 +1,4 @@
1
1
  % SPEC 5.5: comma terms remain data outside goal position.
2
2
  record((name(alice, "Alice"), knows(alice, bob))).
3
3
  answer(formula, F) :- record(F).
4
+ materialize(answer, 2).
@@ -2,3 +2,4 @@
2
2
  pair(a, one).
3
3
  pair(b, two).
4
4
  answer(fresh, yes) :- pair(a, _), pair(b, _).
5
+ materialize(answer, 2).
@@ -1,4 +1,6 @@
1
1
  % SPEC 3.5, 5.2, 11: graphic atom constants are scalar terms.
2
- value(hash, #).
3
- value(arrow, =>).
4
- value(comparison, =<).
2
+ materialize(value, 2).
3
+ raw_value(hash, #).
4
+ raw_value(arrow, =>).
5
+ raw_value(comparison, =<).
6
+ value(K, V) :- raw_value(K, V).
@@ -2,3 +2,4 @@
2
2
  item(quoted_percent, "% not a comment"). % trailing comment
3
3
  item(quoted_atom, 'has % sign').
4
4
  answer(K, V) :- item(K, V).
5
+ materialize(answer, 2).
@@ -5,3 +5,4 @@ edge(c, d).
5
5
  two_step(X, Z) :- edge(X, Y), edge(Y, Z).
6
6
  answer(from_a, Z) :- two_step(a, Z).
7
7
  answer(from_b, Z) :- two_step(b, Z).
8
+ materialize(answer, 2).
@@ -3,3 +3,4 @@ p(a).
3
3
  p(a, b).
4
4
  answer(unary, X) :- p(X).
5
5
  answer(binary, pair(X, Y)) :- p(X, Y).
6
+ materialize(answer, 2).
@@ -2,3 +2,4 @@
2
2
  fact(pair(a, nested(b, [c, d]))).
3
3
  answer(middle, X) :- fact(pair(a, nested(X, [c, d]))).
4
4
  answer(list_tail, T) :- fact(pair(a, nested(b, [c | T]))).
5
+ materialize(answer, 2).
@@ -3,3 +3,4 @@ color(red).
3
3
  color(blue).
4
4
  paint(X) :- color(X).
5
5
  answer(color, X) :- paint(X).
6
+ materialize(answer, 2).
@@ -3,3 +3,5 @@ candidate(a).
3
3
  candidate(b).
4
4
  allowed(a).
5
5
  answer(X, ok) :- candidate(X), allowed(X).
6
+
7
+ materialize(answer, 2).
@@ -3,3 +3,4 @@ cell([head | tail], head, tail).
3
3
  answer(list, L) :- cell(L, head, tail).
4
4
  answer(head, H) :- cell([H | tail], H, tail).
5
5
  answer(tail, T) :- cell([head | T], head, T).
6
+ materialize(answer, 2).
@@ -1,3 +1,4 @@
1
1
  % SPEC 4, 5.3: zero-arity compounds are written and matched with parentheses.
2
2
  status(nil(), ok).
3
3
  answer(value, X) :- status(X, ok).
4
+ materialize(answer, 2).
@@ -5,3 +5,4 @@ link(c, d).
5
5
  path(X, Y) :- link(X, Y).
6
6
  path(X, Z) :- link(X, Y), path(Y, Z).
7
7
  answer(reachable, X) :- path(a, X).
8
+ materialize(answer, 2).
@@ -3,3 +3,4 @@ symbol('two words').
3
3
  symbol('needs''quote').
4
4
  symbol('').
5
5
  answer(symbol, X) :- symbol(X).
6
+ materialize(answer, 2).
@@ -4,3 +4,4 @@ q(a).
4
4
  r(a).
5
5
  ok(X) :- (p(X), q(X), r(X)).
6
6
  answer(ok, X) :- ok(X).
7
+ materialize(answer, 2).
@@ -2,3 +2,4 @@
2
2
  node([pair(a, b), pair(c, d)]).
3
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
+ materialize(answer, 2).
@@ -1,4 +1,4 @@
1
- % SPEC 11: no-query output prints new ground binary derivations, not source facts.
1
+ % SPEC 11: default output prints new ground binary derivations, not source facts.
2
2
  parent(pat, jan).
3
3
  parent(jan, emma).
4
4
  ancestor(X, Y) :- parent(X, Y).
@@ -1,4 +1,4 @@
1
- % SPEC 10.2, 11: materialize/2 restricts selected no-query predicate groups.
1
+ % SPEC 10.2, 11: materialize/2 restricts selected default predicate groups.
2
2
  materialize(answer, 2).
3
3
  seed(a).
4
4
  helper(X, y) :- seed(X).
@@ -9,3 +9,4 @@ answer(minimum, X) :- min(3, 9, X).
9
9
  answer(maximum, X) :- max(3, 9, X).
10
10
  answer(less_than, true) :- lt(3, 9).
11
11
  answer(greater_equal, true) :- ge(9, 9).
12
+ materialize(answer, 2).
@@ -3,3 +3,4 @@ answer(atom_concat, X) :- atom_concat(eye, lang, X).
3
3
  answer(str_concat, X) :- str_concat("eye", "lang", X).
4
4
  answer(contains, true) :- contains("eyelang", "lang").
5
5
  answer(not_contains, true) :- not_contains("eyelang", "cat").
6
+ materialize(answer, 2).
@@ -7,3 +7,4 @@ answer(reverse, X) :- reverse([a, b, c], X).
7
7
  answer(length, N) :- length([a, b, c], N).
8
8
  answer(findall, X) :- findall(N, between(1, 3, N), X).
9
9
  answer(sort, X) :- sort([b, a, b], X).
10
+ materialize(answer, 2).
@@ -2,3 +2,4 @@
2
2
  formula((name(alice, "Alice"), knows(alice, bob))).
3
3
  answer(atom, A) :- formula(F), formula_atom(F, A).
4
4
  answer(binary, exposed(S, P, O)) :- formula(F), formula_binary(F, S, P, O).
5
+ materialize(answer, 2).
@@ -4,3 +4,4 @@ candidate(b).
4
4
  closed(b).
5
5
  answer(open, X) :- candidate(X), not(closed(X)).
6
6
  answer(first_between, X) :- once(between(2, 4, X)).
7
+ materialize(answer, 2).
@@ -3,3 +3,4 @@ answer(eq_variable, X) :- eq(X, pair(a, [b, c])).
3
3
  answer(eq_nested, true) :- eq(pair(X, X), pair(same, same)).
4
4
  answer(neq_atoms, true) :- neq(alice, bob).
5
5
  answer(neq_structures, true) :- neq(pair(a), pair(a, b)).
6
+ materialize(answer, 2).
@@ -3,3 +3,4 @@ answer(rest, X) :- rest([a, b, c], X).
3
3
  answer(select, selected(X, R)) :- select(X, [a, b], R).
4
4
  answer(not_member, true) :- not_member(c, [a, b]).
5
5
  answer(is_list, true) :- is_list([a, b]).
6
+ materialize(answer, 2).
@@ -1,2 +1,3 @@
1
1
  % SPEC 9.7: append/3 can enumerate proper prefix/suffix splits.
2
2
  answer(split, split(A, B)) :- append(A, B, [a, b]).
3
+ materialize(answer, 2).
@@ -4,3 +4,4 @@ answer(not_matches, true) :- not_matches("eyelang", "cat").
4
4
  answer(lex_lt, true) :- lt(alpha, beta).
5
5
  answer(lex_gt, true) :- gt(beta, alpha).
6
6
  answer(numeric_le, true) :- le(2, 2).
7
+ materialize(answer, 2).
@@ -1,6 +1,8 @@
1
1
  % SPEC 10.1: memoize/2 is a declaration and does not change answers.
2
- memoize(reach, 2).
2
+ materialize(reach, 2).
3
+ memoize(reach_any, 2).
3
4
  edge(a, b).
4
5
  edge(b, c).
5
- reach(X, Y) :- edge(X, Y).
6
- reach(X, Z) :- edge(X, Y), reach(Y, Z).
6
+ reach_any(X, Y) :- edge(X, Y).
7
+ reach_any(X, Z) :- edge(X, Y), reach_any(Y, Z).
8
+ reach(a, Y) :- reach_any(a, Y).
@@ -6,3 +6,4 @@ answer(sin_zero, X) :- sin(0, X).
6
6
  answer(cos_zero, X) :- cos(0, X).
7
7
  answer(log_one, X) :- log(1, X).
8
8
  answer(float_division, X) :- div(7.0, 2.0, X).
9
+ materialize(answer, 2).
@@ -1,2 +1,3 @@
1
1
  % SPEC 9.5: between/3 enumerates every integer in an inclusive range.
2
2
  answer(n, X) :- between(3, 5, X).
3
+ materialize(answer, 2).
@@ -1,2 +1,3 @@
1
1
  % SPEC 9.5: smallest_divisor_from/3 finds the first divisor at or above the start.
2
2
  answer(divisor, D) :- smallest_divisor_from(21, 2, D).
3
+ materialize(answer, 2).