eyeling 1.33.4 → 1.33.5
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 -0
- package/{examples/eyelang/SPEC.md → docs/eyelang-language-reference.md} +6 -10
- package/examples/eyelang/README.md +11 -25
- package/lib/eyelang/builtins/arithmetic.js +1 -3
- package/lib/eyelang/builtins/formula.js +0 -16
- package/lib/eyelang/builtins/lists.js +0 -5
- package/lib/eyelang/builtins/registry.js +1 -2
- package/lib/eyelang/builtins/search.js +1 -61
- package/lib/eyelang/builtins/strings.js +10 -14
- package/package.json +2 -1
- package/test/eyelang/conformance/README.md +7 -7
- package/test/eyelang/conformance/cases/core/001_fact_output.pl +1 -1
- package/test/eyelang/conformance/cases/core/002_rule_recursion.pl +1 -1
- package/test/eyelang/conformance/cases/core/003_terms_and_readback.pl +1 -1
- package/test/eyelang/conformance/cases/core/004_conjunction_and_parentheses.pl +1 -1
- package/test/eyelang/conformance/cases/core/005_list_deconstruction.pl +1 -1
- package/test/eyelang/conformance/cases/core/006_comma_formula_data.pl +1 -1
- package/test/eyelang/conformance/cases/core/007_anonymous_variables.pl +1 -1
- package/test/eyelang/conformance/cases/core/008_graphic_atoms.pl +1 -1
- package/test/eyelang/conformance/cases/core/009_comments_and_whitespace.pl +1 -1
- package/test/eyelang/conformance/cases/core/010_variable_scope_and_reuse.pl +1 -1
- package/test/eyelang/conformance/cases/core/011_predicate_arity.pl +1 -1
- package/test/eyelang/conformance/cases/core/012_nested_compound_unification.pl +1 -1
- package/test/eyelang/conformance/cases/core/013_multiple_clauses_order.pl +1 -1
- package/test/eyelang/conformance/cases/core/014_failure_filters_answers.pl +1 -1
- package/test/eyelang/conformance/cases/core/015_improper_list_unification.pl +1 -1
- package/test/eyelang/conformance/cases/core/016_zero_arity_compound.pl +1 -1
- package/test/eyelang/conformance/cases/core/017_three_step_recursion.pl +1 -1
- package/test/eyelang/conformance/cases/core/018_quoted_atom_readback.pl +1 -1
- package/test/eyelang/conformance/cases/core/019_parenthesized_three_conjuncts.pl +1 -1
- package/test/eyelang/conformance/cases/core/020_nested_list_terms.pl +1 -1
- package/test/eyelang/conformance/cases/core/021_repeated_variable_head.pl +1 -1
- package/test/eyelang/conformance/cases/core/022_rule_head_structure.pl +1 -1
- package/test/eyelang/conformance/cases/core/023_quoted_escapes_readback.pl +1 -1
- package/test/eyelang/conformance/cases/core/024_numeric_literal_readback.pl +1 -1
- package/test/eyelang/conformance/cases/core/025_body_parentheses_with_formula_data.pl +1 -1
- package/test/eyelang/conformance/cases/core/026_underscore_named_variable_reuse.pl +1 -1
- package/test/eyelang/conformance/cases/extension/001_default_derived_output.pl +1 -1
- package/test/eyelang/conformance/cases/extension/002_materialize_focus.pl +1 -1
- package/test/eyelang/conformance/cases/extension/003_arithmetic_and_comparison.pl +1 -2
- package/test/eyelang/conformance/cases/extension/004_strings_and_atoms.pl +1 -3
- package/test/eyelang/conformance/cases/extension/005_lists_aggregation_ordering.pl +1 -1
- package/test/eyelang/conformance/cases/extension/006_formula_terms.pl +1 -2
- package/test/eyelang/conformance/cases/extension/007_negation_once_generators.pl +1 -1
- package/test/eyelang/conformance/cases/extension/008_equality_and_inequality.pl +1 -1
- package/test/eyelang/conformance/cases/extension/009_list_relations.pl +1 -2
- package/test/eyelang/conformance/cases/extension/010_append_splits.pl +1 -1
- package/test/eyelang/conformance/cases/extension/011_matching_and_comparison.pl +1 -1
- package/test/eyelang/conformance/cases/extension/012_memoize_declaration.pl +1 -1
- package/test/eyelang/conformance/cases/extension/013_numeric_functions.pl +1 -1
- package/test/eyelang/conformance/cases/extension/014_between_enumeration.pl +1 -1
- package/test/eyelang/conformance/cases/extension/015_smallest_divisor.pl +1 -1
- package/test/eyelang/conformance/cases/extension/016_negation_filter.pl +1 -1
- package/test/eyelang/conformance/cases/extension/017_once_user_predicate.pl +1 -1
- package/test/eyelang/conformance/cases/extension/018_findall_user_goal.pl +1 -1
- package/test/eyelang/conformance/cases/extension/019_sort_deduplicates_atoms.pl +1 -1
- package/test/eyelang/conformance/cases/extension/020_append_bound_prefix_suffix.pl +1 -1
- package/test/eyelang/conformance/cases/extension/021_nth0_index_generation.pl +1 -1
- package/test/eyelang/conformance/cases/extension/022_set_nth0_edges.pl +1 -1
- package/test/eyelang/conformance/cases/extension/023_select_duplicate_occurrences.pl +1 -1
- package/test/eyelang/conformance/cases/extension/024_not_member_filter.pl +1 -1
- package/test/eyelang/conformance/cases/extension/026_nested_formula_terms.pl +1 -2
- package/test/eyelang/conformance/cases/extension/027_materialize_excludes_source_fact.pl +1 -1
- package/test/eyelang/conformance/cases/extension/028_numeric_and_lexical_comparison.pl +1 -1
- package/test/eyelang/conformance/cases/extension/029_string_matching_filters.pl +1 -1
- package/test/eyelang/conformance/cases/extension/030_string_and_atom_concat.pl +1 -2
- package/test/eyelang/conformance/cases/extension/035_date_difference.pl +1 -1
- package/test/eyelang/conformance/cases/extension/036_extended_gcd.pl +1 -1
- package/test/eyelang/conformance/cases/extension/037_collatz_trajectory.pl +1 -1
- package/test/eyelang/conformance/cases/extension/038_kaprekar_steps.pl +1 -1
- package/test/eyelang/conformance/cases/extension/039_goldbach_pair.pl +1 -1
- package/test/eyelang/conformance/cases/extension/040_matrix_operations.pl +1 -1
- package/test/eyelang/conformance/cases/extension/042_n_queens_small.pl +1 -1
- package/test/eyelang/conformance/cases/extension/043_cnf_model.pl +1 -1
- package/test/eyelang/conformance/cases/extension/045_alphametic_sum_small.pl +1 -1
- package/test/eyelang/conformance/cases/extension/046_bounded_subset.pl +1 -1
- package/test/eyelang/conformance/expected/extension/003_arithmetic_and_comparison.out +0 -1
- package/test/eyelang/conformance/expected/extension/004_strings_and_atoms.out +0 -2
- package/test/eyelang/conformance/expected/extension/006_formula_terms.out +0 -2
- package/test/eyelang/conformance/expected/extension/009_list_relations.out +0 -1
- package/test/eyelang/conformance/expected/extension/026_nested_formula_terms.out +0 -3
- package/test/eyelang/conformance/expected/extension/030_string_and_atom_concat.out +0 -1
- package/test/packlist.test.js +1 -0
- package/examples/eyelang/exact-cover-sudoku.pl +0 -113
- package/examples/eyelang/output/exact-cover-sudoku.pl +0 -3
- package/examples/eyelang/output/sudoku.pl +0 -2
- package/examples/eyelang/sudoku.pl +0 -20
- package/lib/eyelang/builtins/sudoku.js +0 -141
- package/test/eyelang/conformance/cases/extension/025_is_list_filter.pl +0 -5
- package/test/eyelang/conformance/cases/extension/025_is_list_filter.query +0 -1
- package/test/eyelang/conformance/cases/extension/041_atom_range_generators.pl +0 -5
- package/test/eyelang/conformance/cases/extension/044_cover9_filter.pl +0 -6
- package/test/eyelang/conformance/expected/extension/025_is_list_filter.out +0 -1
- package/test/eyelang/conformance/expected/extension/041_atom_range_generators.out +0 -8
- package/test/eyelang/conformance/expected/extension/044_cover9_filter.out +0 -2
package/README.md
CHANGED
|
@@ -1153,6 +1153,7 @@ Everything else should be treated as internal unless explicitly documented.
|
|
|
1153
1153
|
├── bin/ CLI executable shim
|
|
1154
1154
|
├── lib/ Source modules
|
|
1155
1155
|
├── dist/browser/ Browser bundle and ESM wrapper
|
|
1156
|
+
├── docs/ Project documentation, including the eyelang language reference
|
|
1156
1157
|
├── examples/ N3 examples, RDF message inputs, and generated decks
|
|
1157
1158
|
├── spec/ RDF 1.2 parser test adapter
|
|
1158
1159
|
├── test/ API, built-in, store, example, package, playground, and stream tests
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# eyelang Language
|
|
1
|
+
# eyelang Language Reference
|
|
2
2
|
|
|
3
3
|
## Table of contents
|
|
4
4
|
|
|
@@ -77,7 +77,7 @@ A **term** is a variable, atom constant, string, number, list, or compound term.
|
|
|
77
77
|
|
|
78
78
|
An **atom constant** is a symbolic scalar term, such as `pat`, `type`, or `'atom with spaces'`. It is a term and may appear as an argument, list element, functor name, or predicate name.
|
|
79
79
|
|
|
80
|
-
An **atomic formula** is a predicate application such as `parent(pat, jan)` or `status(case1, accepted)`. It is the unit of truth in a Herbrand interpretation. In some logic-programming literature atomic formulas are called "atoms"; this specification avoids that shorthand. Whenever the noun "atom" appears here outside
|
|
80
|
+
An **atomic formula** is a predicate application such as `parent(pat, jan)` or `status(case1, accepted)`. It is the unit of truth in a Herbrand interpretation. In some logic-programming literature atomic formulas are called "atoms"; this specification avoids that shorthand. Whenever the noun "atom" appears here outside the phrase "atomic formula", it means **atom constant**.
|
|
81
81
|
|
|
82
82
|
This distinction is normative: `pat` is an atom constant and can appear as a term argument; `parent(pat, jan)` is an atomic formula and can appear as a fact, rule head, or goal. A compound term such as `pair(pat, jan)` has the same surface shape as an atomic formula, but its role is determined by context: as data it is a compound term, and as a clause head or goal it is an atomic formula with predicate symbol `pair/2`.
|
|
83
83
|
|
|
@@ -252,7 +252,7 @@ Parenthesized comma terms may be goals or data:
|
|
|
252
252
|
(name(alice, "Alice"), knows(alice, bob))
|
|
253
253
|
```
|
|
254
254
|
|
|
255
|
-
When a comma term appears as a goal, it is evaluated as conjunction. When it appears as data, it remains a term. `
|
|
255
|
+
When a comma term appears as a goal, it is evaluated as conjunction. When it appears as data, it remains a term. `formula_binary/4` enumerates binary members inside such formula terms.
|
|
256
256
|
|
|
257
257
|
## 6. Clauses and predicates
|
|
258
258
|
|
|
@@ -368,7 +368,7 @@ Implementations MAY provide additional built-ins, but such built-ins are extensi
|
|
|
368
368
|
| `div(A, B, C)` | `C = A / B`; integer inputs use integer division. |
|
|
369
369
|
| `mod(A, B, C)` | Integer remainder. |
|
|
370
370
|
| `pow(A, B, C)` | `C = A^B`. |
|
|
371
|
-
| `
|
|
371
|
+
| `min(A, B, C)` | Numeric minimum. |
|
|
372
372
|
|
|
373
373
|
### 9.3 Comparison
|
|
374
374
|
|
|
@@ -399,10 +399,8 @@ Comparisons interpret numeric-looking terms numerically. Other scalar terms are
|
|
|
399
399
|
|
|
400
400
|
| Built-in | Meaning |
|
|
401
401
|
|---|---|
|
|
402
|
-
| `atom_concat(A, B, C)` | Atom-constant concatenation. |
|
|
403
402
|
| `str_concat(A, B, C)` | String concatenation. |
|
|
404
403
|
| `contains(Text, Needle)` | Text contains `Needle`. |
|
|
405
|
-
| `not_contains(Text, Needle)` | Text does not contain `Needle`. |
|
|
406
404
|
| `matches(Text, Pattern)` | Text matches a simple implementation regex/search pattern. |
|
|
407
405
|
| `not_matches(Text, Pattern)` | Negation of `matches/2`. |
|
|
408
406
|
|
|
@@ -419,7 +417,6 @@ Comparisons interpret numeric-looking terms numerically. Other scalar terms are
|
|
|
419
417
|
| `not_member(X, List)` | Succeeds when `X` is not a member. |
|
|
420
418
|
| `reverse(A, B)` | Reverses a proper list. |
|
|
421
419
|
| `length(List, N)` | Proper-list length. |
|
|
422
|
-
| `is_list(X)` | Succeeds when `X` is a proper list. |
|
|
423
420
|
|
|
424
421
|
### 9.8 Aggregation and ordering
|
|
425
422
|
|
|
@@ -434,11 +431,10 @@ Comparisons interpret numeric-looking terms numerically. Other scalar terms are
|
|
|
434
431
|
|
|
435
432
|
### 9.9 Formula terms
|
|
436
433
|
|
|
437
|
-
Formula terms are data representations of atomic formulas and comma conjunctions.
|
|
434
|
+
Formula terms are data representations of atomic formulas and comma conjunctions.
|
|
438
435
|
|
|
439
436
|
| Built-in | Meaning |
|
|
440
437
|
|---|---|
|
|
441
|
-
| `formula_atom(Formula, Atom)` | Enumerates atomic-formula members inside comma formula data. The second argument denotes an atomic formula term, not necessarily an atom constant. |
|
|
442
438
|
| `formula_binary(Formula, S, P, O)` | Enumerates binary formula members `P(S, O)`, exposing the functor as atom constant `P`. |
|
|
443
439
|
|
|
444
440
|
Example:
|
|
@@ -470,7 +466,7 @@ An extension built-in SHOULD obey the same surface-language discipline as standa
|
|
|
470
466
|
- it SHOULD document its intended modes, especially which arguments must be ground before it runs deterministically;
|
|
471
467
|
- it MUST NOT change the meaning of ordinary facts, rules, unification, or standard built-ins.
|
|
472
468
|
|
|
473
|
-
For example, an implementation may include extension modules for
|
|
469
|
+
For example, an implementation may include extension modules for portfolio selection, number-theory algorithms, graph search, matrix operations, or alphametic puzzles. Those modules may be valuable and may make example programs much faster, but their predicate names, arities, algorithms, and modes are implementation-defined unless they are separately standardized.
|
|
474
470
|
|
|
475
471
|
An implementation that provides explanation output SHOULD make extension built-ins explainable at least as opaque successful or failed built-in calls, so that proof traces do not incorrectly report "no clauses" for a host-provided relation.
|
|
476
472
|
|
|
@@ -10,7 +10,7 @@ Programs write relations directly, for example `ancestor(pat, emma)` or `status(
|
|
|
10
10
|
|
|
11
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
|
-
For the normative language definition, including lexical syntax, terms, clauses, goals, built-ins, `memoize/2`, `materialize/2`, and conformance boundaries, read the [eyelang
|
|
13
|
+
For the normative language definition, including lexical syntax, terms, clauses, goals, built-ins, `memoize/2`, `materialize/2`, and conformance boundaries, read the [eyelang language reference](../../docs/eyelang-language-reference.md).
|
|
14
14
|
|
|
15
15
|
## Contents
|
|
16
16
|
|
|
@@ -243,7 +243,7 @@ The CLI is output-oriented and uses `materialize/2` to decide what to print. Emb
|
|
|
243
243
|
Add `-s` or `--stats` when you want lightweight solver counters on stderr without changing stdout:
|
|
244
244
|
|
|
245
245
|
```sh
|
|
246
|
-
bin/eyelang -s examples/
|
|
246
|
+
bin/eyelang -s examples/eyelang/n-queens.pl
|
|
247
247
|
```
|
|
248
248
|
|
|
249
249
|
The playground has matching `--stats` and `--proof` checkboxes, so browser runs can show the same counters or explanations like the CLI.
|
|
@@ -251,15 +251,11 @@ The playground has matching `--stats` and `--proof` checkboxes, so browser runs
|
|
|
251
251
|
|
|
252
252
|
### Builtins
|
|
253
253
|
|
|
254
|
-
eyelang builtins are registered by name and arity in small modules under [`
|
|
254
|
+
eyelang builtins are registered by name and arity in small modules under [`lib/eyelang/builtins`](../../lib/eyelang/builtins). This keeps the runtime portable to Node.js and the browser while giving each builtin family a clear boundary. Builtins are enabled by normal predicate calls.
|
|
255
255
|
|
|
256
|
-
The core builtin families cover unification, arithmetic, comparison, dates, strings, lists, aggregation, formula terms, and search control. Additional reusable finite-search helpers are available
|
|
256
|
+
The core builtin families cover unification, arithmetic, comparison, dates, strings, lists, aggregation, formula terms, and search control. Additional reusable finite-search helpers are available only where bundled examples need them to avoid large amounts of repetitive generate-and-test code. These helpers are deliberately general relations rather than shortcuts tied to a particular example name. For example:
|
|
257
257
|
|
|
258
258
|
```prolog
|
|
259
|
-
solution(Name, Rows) :-
|
|
260
|
-
puzzle(Name, Grid),
|
|
261
|
-
sudoku(Grid, Rows).
|
|
262
|
-
|
|
263
259
|
answer(Queens) :-
|
|
264
260
|
n_queens(8, Queens).
|
|
265
261
|
|
|
@@ -268,11 +264,9 @@ best(Cycle, Cost) :-
|
|
|
268
264
|
weighted_hamiltonian_cycle(edge, Cities, Cycle, Cost).
|
|
269
265
|
```
|
|
270
266
|
|
|
271
|
-
`
|
|
272
|
-
|
|
273
|
-
The reusable search and numeric helpers include `atom_range/4`, `atom_ranges/4`, `n_queens/2`, Hamiltonian path/cycle helpers, `cnf_model/3`, Quine-McCluskey helpers, bounded subset/path helpers, number-theory helpers such as `extended_gcd/5`, matrix helpers such as `matrix_multiply/2`, and `alphametic_sum/5`. These helpers are extension builtins of this implementation; [`SPEC.md`](SPEC.md) defines the portable core and standard builtin profile.
|
|
267
|
+
The reusable search and numeric helpers include `n_queens/2`, Hamiltonian path/cycle helpers, `cnf_model/3`, Quine-McCluskey helpers, bounded subset/path helpers, number-theory helpers such as `extended_gcd/5`, matrix helpers such as `matrix_multiply/2`, and `alphametic_sum/5`. These helpers are extension builtins of this implementation; [the eyelang language reference](../../docs/eyelang-language-reference.md) defines the portable core and standard builtin profile.
|
|
274
268
|
|
|
275
|
-
To add a builtin, create or extend a module with `register(registry)` and call `registry.add(name, arity, handler, options)`. The default registry is assembled in [`
|
|
269
|
+
To add a builtin, create or extend a module with `register(registry)` and call `registry.add(name, arity, handler, options)`. The default registry is assembled in [`lib/eyelang/builtins/registry.js`](../../lib/eyelang/builtins/registry.js). Builtins that are only safe for specific argument modes should provide a `ready` predicate and `fallbackWhenNotReady: true`, so user-defined clauses remain visible until the builtin is applicable.
|
|
276
270
|
|
|
277
271
|
|
|
278
272
|
## Aggregation helpers
|
|
@@ -298,13 +292,7 @@ best_cycle(Cycle, Cost) :-
|
|
|
298
292
|
|
|
299
293
|
## Formula data
|
|
300
294
|
|
|
301
|
-
Comma terms can be data as well as conjunctions. eyelang provides relation-oriented formula
|
|
302
|
-
|
|
303
|
-
`formula_atom(Formula, Atom)` enumerates atomic formula terms inside a comma formula:
|
|
304
|
-
|
|
305
|
-
```prolog
|
|
306
|
-
formula_atom((name(alice, "Alice"), knows(alice, bob)), X).
|
|
307
|
-
```
|
|
295
|
+
Comma terms can be data as well as conjunctions. eyelang provides a relation-oriented formula utility.
|
|
308
296
|
|
|
309
297
|
`formula_binary(Formula, S, P, O)` enumerates binary terms and exposes their functor as an atom constant:
|
|
310
298
|
|
|
@@ -380,7 +368,6 @@ The repository includes examples for recursion, graph reachability, finite searc
|
|
|
380
368
|
| [`equivalence-classes-overlap-implies-same-class.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/equivalence-classes-overlap-implies-same-class.pl) | Packages the shared-member proof pattern for equivalence classes. | [`output/equivalence-classes-overlap-implies-same-class.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/output/equivalence-classes-overlap-implies-same-class.pl) |
|
|
381
369
|
| [`eulerian-path.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/eulerian-path.pl) | Finds an Eulerian path using each edge once. | [`output/eulerian-path.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/output/eulerian-path.pl) |
|
|
382
370
|
| [`ev-range-worlds.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/ev-range-worlds.pl) | Estimates electric-vehicle trip feasibility. | [`output/ev-range-worlds.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/output/ev-range-worlds.pl) |
|
|
383
|
-
| [`exact-cover-sudoku.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/exact-cover-sudoku.pl) | Solves Sudoku via exact-cover-style constraints. | [`output/exact-cover-sudoku.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/output/exact-cover-sudoku.pl) |
|
|
384
371
|
| [`existential-rule.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/existential-rule.pl) | Represents existential witnesses with explicit Skolem-style terms. | [`output/existential-rule.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/output/existential-rule.pl) |
|
|
385
372
|
| [`exoplanet-validation-worlds.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/exoplanet-validation-worlds.pl) | Validates exoplanet candidates across worlds. | [`output/exoplanet-validation-worlds.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/output/exoplanet-validation-worlds.pl) |
|
|
386
373
|
| [`expression-eval.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/expression-eval.pl) | Evaluates a small arithmetic expression tree. | [`output/expression-eval.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/output/expression-eval.pl) |
|
|
@@ -448,7 +435,6 @@ The repository includes examples for recursion, graph reachability, finite searc
|
|
|
448
435
|
| [`socket-family.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/socket-family.pl) | Shows socket-declared family-source inputs and ancestry rules. | [`output/socket-family.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/output/socket-family.pl) |
|
|
449
436
|
| [`socrates.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/socrates.pl) | Derives that Socrates is mortal. | [`output/socrates.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/output/socrates.pl) |
|
|
450
437
|
| [`statistics-summary.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/statistics-summary.pl) | Computes population statistics for a sample. | [`output/statistics-summary.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/output/statistics-summary.pl) |
|
|
451
|
-
| [`sudoku.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/sudoku.pl) | Solves generic 9x9 Sudoku strings through the sudoku/2 builtin. | [`output/sudoku.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/output/sudoku.pl) |
|
|
452
438
|
| [`superdense-coding.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/superdense-coding.pl) | Models superdense-coding bit transmission. | [`output/superdense-coding.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/output/superdense-coding.pl) |
|
|
453
439
|
| [`traveling-salesman.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/traveling-salesman.pl) | Finds an optimal traveling-salesman tour. | [`output/traveling-salesman.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/output/traveling-salesman.pl) |
|
|
454
440
|
| [`turing.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/turing.pl) | Simulates a binary-increment Turing machine. | [`output/turing.pl`](https://github.com/eyereasoner/eyelang/blob/main/examples/output/turing.pl) |
|
|
@@ -483,7 +469,7 @@ Run the full test suite:
|
|
|
483
469
|
npm test
|
|
484
470
|
```
|
|
485
471
|
|
|
486
|
-
The test suite runs in this order: Conformance, Regression/API/White-box, Examples. Each section prints its own subtotal, followed by a suite-specific grand total. The suite checks the conformance cases derived from
|
|
472
|
+
The test suite runs in this order: Conformance, Regression/API/White-box, Examples. Each section prints its own subtotal, followed by a suite-specific grand total. The suite checks the conformance cases derived from the language reference, supplemental regression/API/white-box checks, and every runnable example against its golden output.
|
|
487
473
|
|
|
488
474
|
Run only one suite when you are iterating:
|
|
489
475
|
|
|
@@ -493,7 +479,7 @@ npm run test:regression
|
|
|
493
479
|
npm run test:examples
|
|
494
480
|
```
|
|
495
481
|
|
|
496
|
-
The conformance suite lives in [`conformance/`](conformance/) and is split into `core` and `extension` profiles matching
|
|
482
|
+
The conformance suite lives in [`conformance/`](conformance/) and is split into `core` and `extension` profiles matching the language reference. 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.
|
|
497
483
|
|
|
498
484
|
## Development and release
|
|
499
485
|
|
|
@@ -510,13 +496,13 @@ node bin/eyelang --help
|
|
|
510
496
|
Useful profiling smoke test:
|
|
511
497
|
|
|
512
498
|
```sh
|
|
513
|
-
bin/eyelang -s examples/
|
|
499
|
+
bin/eyelang -s examples/eyelang/n-queens.pl > /dev/null
|
|
514
500
|
```
|
|
515
501
|
|
|
516
502
|
For a release:
|
|
517
503
|
|
|
518
504
|
1. update `VERSION`;
|
|
519
|
-
2. update `README.md` and
|
|
505
|
+
2. update `README.md` and the language reference;
|
|
520
506
|
3. regenerate golden outputs if behavior changed;
|
|
521
507
|
4. run `npm test`;
|
|
522
508
|
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`.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { compareIntegerText, deref, isDecimalInteger, lexicalValue, numberTerm, numberTextFromDouble, parseFiniteNumber, unify } from '../term.js';
|
|
4
4
|
|
|
5
5
|
const unaryNames = ['neg', 'abs', 'sin', 'cos', 'asin', 'acos', 'rounded', 'log'];
|
|
6
|
-
const binaryNames = ['add', 'sub', 'mul', 'div', 'mod', '
|
|
6
|
+
const binaryNames = ['add', 'sub', 'mul', 'div', 'mod', 'min', 'pow'];
|
|
7
7
|
const compareNames = ['lt', 'gt', 'le', 'ge'];
|
|
8
8
|
|
|
9
9
|
export const arithmeticBuiltins = {
|
|
@@ -60,7 +60,6 @@ function binary(name) {
|
|
|
60
60
|
else if (name === 'sub') out = (a - b).toString();
|
|
61
61
|
else if (name === 'mul') out = (a * b).toString();
|
|
62
62
|
else if (name === 'div') { if (b === 0n) return; out = (a / b).toString(); }
|
|
63
|
-
else if (name === 'max') out = (a >= b ? a : b).toString();
|
|
64
63
|
else if (name === 'min') out = (a <= b ? a : b).toString();
|
|
65
64
|
else if (name === 'pow') { if (b < 0n) return; out = (a ** b).toString(); }
|
|
66
65
|
} else if (name === 'mod') {
|
|
@@ -77,7 +76,6 @@ function binary(name) {
|
|
|
77
76
|
else if (name === 'mul') value = a * b;
|
|
78
77
|
else if (name === 'div') { if (b === 0) return; value = a / b; }
|
|
79
78
|
else if (name === 'pow') value = Math.pow(a, b);
|
|
80
|
-
else if (name === 'max') value = Math.max(a, b);
|
|
81
79
|
else if (name === 'min') value = Math.min(a, b);
|
|
82
80
|
out = numberTextFromDouble(value);
|
|
83
81
|
}
|
|
@@ -4,26 +4,10 @@ import { atom, deref, isConjunction, unify } from '../term.js';
|
|
|
4
4
|
|
|
5
5
|
export const formulaBuiltins = {
|
|
6
6
|
register(registry) {
|
|
7
|
-
registry.add('formula_atom', 2, formulaAtom);
|
|
8
7
|
registry.add('formula_binary', 4, formulaBinary);
|
|
9
8
|
}
|
|
10
9
|
};
|
|
11
10
|
|
|
12
|
-
function* emitFormulaAtoms(formula, target, env) {
|
|
13
|
-
formula = deref(formula, env);
|
|
14
|
-
if (isConjunction(formula)) {
|
|
15
|
-
yield* emitFormulaAtoms(formula.args[0], target, env);
|
|
16
|
-
yield* emitFormulaAtoms(formula.args[1], target, env);
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
if (formula.type !== 'compound') return;
|
|
20
|
-
const next = env.clone();
|
|
21
|
-
if (unify(target, formula, next)) yield next;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function* formulaAtom({ goal, env }) {
|
|
25
|
-
yield* emitFormulaAtoms(goal.args[0], goal.args[1], env);
|
|
26
|
-
}
|
|
27
11
|
|
|
28
12
|
function* emitFormulaBinary(formula, subject, predicate, object, env) {
|
|
29
13
|
formula = deref(formula, env);
|
|
@@ -13,7 +13,6 @@ export const listBuiltins = {
|
|
|
13
13
|
registry.add('not_member', 2, notMember, { deterministic: true });
|
|
14
14
|
registry.add('reverse', 2, reverse, { deterministic: true });
|
|
15
15
|
registry.add('length', 2, lengthBuiltin, { deterministic: true });
|
|
16
|
-
registry.add('is_list', 1, isList, { deterministic: true });
|
|
17
16
|
registry.add('sort', 2, sortBuiltin, { deterministic: true });
|
|
18
17
|
}
|
|
19
18
|
};
|
|
@@ -133,10 +132,6 @@ function* lengthBuiltin({ goal, env }) {
|
|
|
133
132
|
if (unify(goal.args[1], numberTerm(items.length), next)) yield next;
|
|
134
133
|
}
|
|
135
134
|
|
|
136
|
-
function* isList({ goal, env }) {
|
|
137
|
-
const items = properListItems(goal.args[0], env);
|
|
138
|
-
if (items) yield env;
|
|
139
|
-
}
|
|
140
135
|
|
|
141
136
|
function* sortBuiltin({ goal, env }) {
|
|
142
137
|
const items = properListItems(goal.args[0], env);
|
|
@@ -7,7 +7,6 @@ import { listBuiltins } from './lists.js';
|
|
|
7
7
|
import { aggregationBuiltins } from './aggregation.js';
|
|
8
8
|
import { formulaBuiltins } from './formula.js';
|
|
9
9
|
import { controlBuiltins } from './control.js';
|
|
10
|
-
import { sudokuBuiltins } from './sudoku.js';
|
|
11
10
|
import { portfolioBuiltins } from './portfolio.js';
|
|
12
11
|
import { searchBuiltins } from './search.js';
|
|
13
12
|
import { numberTheoryBuiltins } from './number-theory.js';
|
|
@@ -39,7 +38,7 @@ export class BuiltinRegistry {
|
|
|
39
38
|
|
|
40
39
|
export function createDefaultRegistry() {
|
|
41
40
|
const registry = new BuiltinRegistry();
|
|
42
|
-
for (const mod of [coreBuiltins, arithmeticBuiltins, stringBuiltins, listBuiltins, aggregationBuiltins, formulaBuiltins, controlBuiltins,
|
|
41
|
+
for (const mod of [coreBuiltins, arithmeticBuiltins, stringBuiltins, listBuiltins, aggregationBuiltins, formulaBuiltins, controlBuiltins, portfolioBuiltins, searchBuiltins, numberTheoryBuiltins, matrixBuiltins, alphameticBuiltins]) {
|
|
43
42
|
mod.register(registry);
|
|
44
43
|
}
|
|
45
44
|
return registry;
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
// Reusable finite-search builtins for examples that would otherwise spend most
|
|
2
2
|
// of their time in small relational generators. These predicates are generic
|
|
3
|
-
// entry points (graph, CNF, QMC,
|
|
3
|
+
// entry points (graph, CNF, QMC, and n-queens helpers), not compiled
|
|
4
4
|
// replacements for particular example predicate names.
|
|
5
5
|
import { atom, compound, deref, listFromItems, numberTerm, properListItems, unify } from '../term.js';
|
|
6
6
|
import { compareLexicalOrNumeric } from './arithmetic.js';
|
|
7
7
|
|
|
8
8
|
export const searchBuiltins = {
|
|
9
9
|
register(registry) {
|
|
10
|
-
registry.add('atom_range', 4, atomRange, { fallbackWhenNotReady: true, ready: atomRangeReady });
|
|
11
|
-
registry.add('atom_ranges', 4, atomRanges, { fallbackWhenNotReady: true, ready: atomRangesReady });
|
|
12
10
|
registry.add('n_queens', 2, nQueens, { fallbackWhenNotReady: true, ready: firstIntReady });
|
|
13
11
|
registry.add('weighted_hamiltonian_cycle', 4, weightedHamiltonianCycle, { fallbackWhenNotReady: true, ready: weightedGraphReady });
|
|
14
12
|
registry.add('weighted_hamiltonian_path', 4, weightedHamiltonianPath, { fallbackWhenNotReady: true, ready: weightedGraphReady });
|
|
@@ -26,67 +24,9 @@ function graphReady(goal, env) { return atomKey(deref(goal.args[0], env)) !== nu
|
|
|
26
24
|
function weightedGraphReady(goal, env) { return graphReady(goal, env); }
|
|
27
25
|
function fixedCycleReady(goal, env) { return atomKey(deref(goal.args[0], env)) !== null && intTerm(goal.args[1], env) !== null; }
|
|
28
26
|
function boundedPathReady(goal, env) { return atomKey(deref(goal.args[0], env)) !== null && atomKey(deref(goal.args[1], env)) !== null && atomKey(deref(goal.args[2], env)) !== null && intTerm(goal.args[3], env) !== null; }
|
|
29
|
-
function atomRangeReady(goal, env) { return atomKey(deref(goal.args[0], env)) !== null && intTerm(goal.args[1], env) !== null && intTerm(goal.args[2], env) !== null; }
|
|
30
|
-
function atomRangesReady(goal, env) { return atomList(goal.args[0], env) !== null && intTerm(goal.args[1], env) !== null && intTerm(goal.args[2], env) !== null; }
|
|
31
27
|
function cnfReady(goal, env) { return atomList(goal.args[0], env) !== null && clauseList(goal.args[1], env) !== null; }
|
|
32
28
|
function qmReady(goal, env) { return numberList(goal.args[0], env) !== null && numberList(goal.args[1], env) !== null && bitTable(goal.args[2], env) !== null; }
|
|
33
29
|
|
|
34
|
-
function* atomRange({ goal, env }) {
|
|
35
|
-
// atom_range(Prefix, Low, High, Atom) is a compact generator for atoms such
|
|
36
|
-
// as n1, n2, ...; it replaces between/3 + atom_concat/3 in large generated
|
|
37
|
-
// data sets without committing to any domain-specific predicate name.
|
|
38
|
-
const prefix = atomKey(deref(goal.args[0], env));
|
|
39
|
-
const low = intTerm(goal.args[1], env);
|
|
40
|
-
const high = intTerm(goal.args[2], env);
|
|
41
|
-
if (prefix == null || low == null || high == null) return;
|
|
42
|
-
const output = deref(goal.args[3], env);
|
|
43
|
-
if (output.type !== 'var') {
|
|
44
|
-
const text = atomKey(output);
|
|
45
|
-
const n = parsePrefixedInteger(prefix, text);
|
|
46
|
-
if (n == null || n < low || n > high) return;
|
|
47
|
-
yield env;
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
for (let i = low; i <= high; i++) {
|
|
51
|
-
const next = env.clone();
|
|
52
|
-
next.bind(output.name, atom(`${prefix}${i}`));
|
|
53
|
-
yield next;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function parsePrefixedInteger(prefix, text) {
|
|
58
|
-
if (text == null || !text.startsWith(prefix)) return null;
|
|
59
|
-
const suffix = text.slice(prefix.length);
|
|
60
|
-
if (!/^\d+$/.test(suffix)) return null;
|
|
61
|
-
const n = Number(suffix);
|
|
62
|
-
return Number.isSafeInteger(n) ? n : null;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function* atomRanges({ goal, env }) {
|
|
66
|
-
// atom_ranges([p, q], Low, High, Atom) is the multi-prefix companion to
|
|
67
|
-
// atom_range/4. It is useful when several generated atom families feed the
|
|
68
|
-
// same relation.
|
|
69
|
-
const prefixes = atomList(goal.args[0], env);
|
|
70
|
-
const low = intTerm(goal.args[1], env);
|
|
71
|
-
const high = intTerm(goal.args[2], env);
|
|
72
|
-
if (!prefixes || low == null || high == null) return;
|
|
73
|
-
const output = deref(goal.args[3], env);
|
|
74
|
-
if (output.type !== 'var') {
|
|
75
|
-
const text = atomKey(output);
|
|
76
|
-
for (const prefix of prefixes) {
|
|
77
|
-
const n = parsePrefixedInteger(prefix, text);
|
|
78
|
-
if (n != null && n >= low && n <= high) { yield env; return; }
|
|
79
|
-
}
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
for (const prefix of prefixes) {
|
|
83
|
-
for (let i = low; i <= high; i++) {
|
|
84
|
-
const next = env.clone();
|
|
85
|
-
next.bind(output.name, atom(`${prefix}${i}`));
|
|
86
|
-
yield next;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
30
|
|
|
91
31
|
function* nQueens({ goal, env }) {
|
|
92
32
|
// n_queens(N, Solution) performs the same finite search as the declarative
|
|
@@ -1,24 +1,21 @@
|
|
|
1
|
-
// String
|
|
1
|
+
// String builtins.
|
|
2
2
|
// They mostly project from already-ground terms to avoid guessing string domains.
|
|
3
|
-
import {
|
|
3
|
+
import { lexicalValue, stringTerm, unify } from '../term.js';
|
|
4
4
|
|
|
5
5
|
export const stringBuiltins = {
|
|
6
6
|
register(registry) {
|
|
7
|
-
|
|
8
|
-
for (const name of ['contains', '
|
|
7
|
+
registry.add('str_concat', 3, concat, { deterministic: true });
|
|
8
|
+
for (const name of ['contains', 'matches', 'not_matches']) registry.add(name, 2, contains(name), { deterministic: true });
|
|
9
9
|
}
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
function concat(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const next = env.clone();
|
|
20
|
-
if (unify(goal.args[2], result, next)) yield next;
|
|
21
|
-
};
|
|
13
|
+
function* concat({ goal, env }) {
|
|
14
|
+
const left = lexicalValue(goal.args[0], env);
|
|
15
|
+
const right = lexicalValue(goal.args[1], env);
|
|
16
|
+
if (left == null || right == null) return;
|
|
17
|
+
const next = env.clone();
|
|
18
|
+
if (unify(goal.args[2], stringTerm(left + right), next)) yield next;
|
|
22
19
|
}
|
|
23
20
|
|
|
24
21
|
function contains(name) {
|
|
@@ -29,7 +26,6 @@ function contains(name) {
|
|
|
29
26
|
const has = haystack.includes(needle);
|
|
30
27
|
const matches = simpleAlternationMatch(haystack, needle);
|
|
31
28
|
const pass = (name === 'contains' && has) ||
|
|
32
|
-
(name === 'not_contains' && !has) ||
|
|
33
29
|
(name === 'matches' && matches) ||
|
|
34
30
|
(name === 'not_matches' && !matches);
|
|
35
31
|
if (pass) yield env;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eyeling",
|
|
3
|
-
"version": "1.33.
|
|
3
|
+
"version": "1.33.5",
|
|
4
4
|
"description": "A minimal Notation3 (N3) reasoner in JavaScript.",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"keywords": [
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"eyelang.d.ts",
|
|
30
30
|
"bin",
|
|
31
31
|
"dist",
|
|
32
|
+
"docs",
|
|
32
33
|
"lib",
|
|
33
34
|
"test",
|
|
34
35
|
"tools",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# eyelang conformance suite
|
|
2
2
|
|
|
3
|
-
This directory contains the executable conformance cases for the eyelang language and reference engine. The normative language description is in [
|
|
3
|
+
This directory contains the executable conformance cases for the eyelang language and reference engine. The normative language description is in the [eyelang language reference](../../../docs/eyelang-language-reference.md).
|
|
4
4
|
|
|
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
|
|
|
@@ -20,25 +20,25 @@ npm test
|
|
|
20
20
|
Run only the conformance suite:
|
|
21
21
|
|
|
22
22
|
```sh
|
|
23
|
-
|
|
23
|
+
node test/eyelang/run-conformance.mjs
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
Run a single conformance profile directly:
|
|
27
27
|
|
|
28
28
|
```sh
|
|
29
|
-
node test/run-conformance.
|
|
30
|
-
node test/run-conformance.
|
|
29
|
+
node test/eyelang/run-conformance.mjs core
|
|
30
|
+
node test/eyelang/run-conformance.mjs extension
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
The runner executes materialized programs in-process through the public JavaScript API so small conformance cases avoid measuring Node startup overhead.
|
|
34
34
|
|
|
35
35
|
## Profiles
|
|
36
36
|
|
|
37
|
-
`core` covers the portable core language profile from
|
|
37
|
+
`core` covers the portable core language profile from the [eyelang language reference](../../../docs/eyelang-language-reference.md): lexical syntax, facts, definite clauses, first-order terms, lists, conjunction, structured unification through user predicates, left-to-right goal-directed proof search, materialized output, and read-back printing.
|
|
38
38
|
|
|
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, number-theory helpers, finite-search helpers, matrix helpers, `memoize/2`, `materialize/2`, and default 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, number-theory helpers, finite-search helpers used by the example corpus, matrix helpers, `memoize/2`, `materialize/2`, and default derived output.
|
|
40
40
|
|
|
41
|
-
The profile name `extension` is a test-suite grouping name. It does not mean that these cases are outside the eyelang
|
|
41
|
+
The profile name `extension` is a test-suite grouping name. It does not mean that these cases are outside the eyelang language reference; most of them correspond to the standard built-in profile and standard host profile in the [eyelang language reference](../../../docs/eyelang-language-reference.md).
|
|
42
42
|
|
|
43
43
|
## Updating expected output
|
|
44
44
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
%
|
|
1
|
+
% Reference 5.3, 7.1: unification follows nested compound term structure.
|
|
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]))).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
%
|
|
1
|
+
% Reference 5.4: improper list surface syntax unifies with head-tail structure.
|
|
2
2
|
cell([head | tail], head, tail).
|
|
3
3
|
answer(list, L) :- cell(L, head, tail).
|
|
4
4
|
answer(head, H) :- cell([H | tail], H, tail).
|
|
@@ -1,4 +1,4 @@
|
|
|
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
3
|
answer(first_key, X) :- node([pair(X, _), pair(c, d)]).
|
|
4
4
|
answer(second_key, X) :- node([pair(a, b), pair(X, d)]).
|