eyeling 1.33.4 → 1.33.6

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 (98) hide show
  1. package/README.md +50 -105
  2. package/docs/eyelang-guide.md +551 -0
  3. package/{examples/eyelang/SPEC.md → docs/eyelang-language-reference.md} +6 -10
  4. package/lib/eyelang/builtins/arithmetic.js +1 -3
  5. package/lib/eyelang/builtins/formula.js +0 -16
  6. package/lib/eyelang/builtins/lists.js +0 -5
  7. package/lib/eyelang/builtins/registry.js +1 -2
  8. package/lib/eyelang/builtins/search.js +1 -61
  9. package/lib/eyelang/builtins/strings.js +10 -14
  10. package/package.json +2 -1
  11. package/test/eyelang/conformance/README.md +7 -7
  12. package/test/eyelang/conformance/cases/core/001_fact_output.pl +1 -1
  13. package/test/eyelang/conformance/cases/core/002_rule_recursion.pl +1 -1
  14. package/test/eyelang/conformance/cases/core/003_terms_and_readback.pl +1 -1
  15. package/test/eyelang/conformance/cases/core/004_conjunction_and_parentheses.pl +1 -1
  16. package/test/eyelang/conformance/cases/core/005_list_deconstruction.pl +1 -1
  17. package/test/eyelang/conformance/cases/core/006_comma_formula_data.pl +1 -1
  18. package/test/eyelang/conformance/cases/core/007_anonymous_variables.pl +1 -1
  19. package/test/eyelang/conformance/cases/core/008_graphic_atoms.pl +1 -1
  20. package/test/eyelang/conformance/cases/core/009_comments_and_whitespace.pl +1 -1
  21. package/test/eyelang/conformance/cases/core/010_variable_scope_and_reuse.pl +1 -1
  22. package/test/eyelang/conformance/cases/core/011_predicate_arity.pl +1 -1
  23. package/test/eyelang/conformance/cases/core/012_nested_compound_unification.pl +1 -1
  24. package/test/eyelang/conformance/cases/core/013_multiple_clauses_order.pl +1 -1
  25. package/test/eyelang/conformance/cases/core/014_failure_filters_answers.pl +1 -1
  26. package/test/eyelang/conformance/cases/core/015_improper_list_unification.pl +1 -1
  27. package/test/eyelang/conformance/cases/core/016_zero_arity_compound.pl +1 -1
  28. package/test/eyelang/conformance/cases/core/017_three_step_recursion.pl +1 -1
  29. package/test/eyelang/conformance/cases/core/018_quoted_atom_readback.pl +1 -1
  30. package/test/eyelang/conformance/cases/core/019_parenthesized_three_conjuncts.pl +1 -1
  31. package/test/eyelang/conformance/cases/core/020_nested_list_terms.pl +1 -1
  32. package/test/eyelang/conformance/cases/core/021_repeated_variable_head.pl +1 -1
  33. package/test/eyelang/conformance/cases/core/022_rule_head_structure.pl +1 -1
  34. package/test/eyelang/conformance/cases/core/023_quoted_escapes_readback.pl +1 -1
  35. package/test/eyelang/conformance/cases/core/024_numeric_literal_readback.pl +1 -1
  36. package/test/eyelang/conformance/cases/core/025_body_parentheses_with_formula_data.pl +1 -1
  37. package/test/eyelang/conformance/cases/core/026_underscore_named_variable_reuse.pl +1 -1
  38. package/test/eyelang/conformance/cases/extension/001_default_derived_output.pl +1 -1
  39. package/test/eyelang/conformance/cases/extension/002_materialize_focus.pl +1 -1
  40. package/test/eyelang/conformance/cases/extension/003_arithmetic_and_comparison.pl +1 -2
  41. package/test/eyelang/conformance/cases/extension/004_strings_and_atoms.pl +1 -3
  42. package/test/eyelang/conformance/cases/extension/005_lists_aggregation_ordering.pl +1 -1
  43. package/test/eyelang/conformance/cases/extension/006_formula_terms.pl +1 -2
  44. package/test/eyelang/conformance/cases/extension/007_negation_once_generators.pl +1 -1
  45. package/test/eyelang/conformance/cases/extension/008_equality_and_inequality.pl +1 -1
  46. package/test/eyelang/conformance/cases/extension/009_list_relations.pl +1 -2
  47. package/test/eyelang/conformance/cases/extension/010_append_splits.pl +1 -1
  48. package/test/eyelang/conformance/cases/extension/011_matching_and_comparison.pl +1 -1
  49. package/test/eyelang/conformance/cases/extension/012_memoize_declaration.pl +1 -1
  50. package/test/eyelang/conformance/cases/extension/013_numeric_functions.pl +1 -1
  51. package/test/eyelang/conformance/cases/extension/014_between_enumeration.pl +1 -1
  52. package/test/eyelang/conformance/cases/extension/015_smallest_divisor.pl +1 -1
  53. package/test/eyelang/conformance/cases/extension/016_negation_filter.pl +1 -1
  54. package/test/eyelang/conformance/cases/extension/017_once_user_predicate.pl +1 -1
  55. package/test/eyelang/conformance/cases/extension/018_findall_user_goal.pl +1 -1
  56. package/test/eyelang/conformance/cases/extension/019_sort_deduplicates_atoms.pl +1 -1
  57. package/test/eyelang/conformance/cases/extension/020_append_bound_prefix_suffix.pl +1 -1
  58. package/test/eyelang/conformance/cases/extension/021_nth0_index_generation.pl +1 -1
  59. package/test/eyelang/conformance/cases/extension/022_set_nth0_edges.pl +1 -1
  60. package/test/eyelang/conformance/cases/extension/023_select_duplicate_occurrences.pl +1 -1
  61. package/test/eyelang/conformance/cases/extension/024_not_member_filter.pl +1 -1
  62. package/test/eyelang/conformance/cases/extension/026_nested_formula_terms.pl +1 -2
  63. package/test/eyelang/conformance/cases/extension/027_materialize_excludes_source_fact.pl +1 -1
  64. package/test/eyelang/conformance/cases/extension/028_numeric_and_lexical_comparison.pl +1 -1
  65. package/test/eyelang/conformance/cases/extension/029_string_matching_filters.pl +1 -1
  66. package/test/eyelang/conformance/cases/extension/030_string_and_atom_concat.pl +1 -2
  67. package/test/eyelang/conformance/cases/extension/035_date_difference.pl +1 -1
  68. package/test/eyelang/conformance/cases/extension/036_extended_gcd.pl +1 -1
  69. package/test/eyelang/conformance/cases/extension/037_collatz_trajectory.pl +1 -1
  70. package/test/eyelang/conformance/cases/extension/038_kaprekar_steps.pl +1 -1
  71. package/test/eyelang/conformance/cases/extension/039_goldbach_pair.pl +1 -1
  72. package/test/eyelang/conformance/cases/extension/040_matrix_operations.pl +1 -1
  73. package/test/eyelang/conformance/cases/extension/042_n_queens_small.pl +1 -1
  74. package/test/eyelang/conformance/cases/extension/043_cnf_model.pl +1 -1
  75. package/test/eyelang/conformance/cases/extension/045_alphametic_sum_small.pl +1 -1
  76. package/test/eyelang/conformance/cases/extension/046_bounded_subset.pl +1 -1
  77. package/test/eyelang/conformance/expected/extension/003_arithmetic_and_comparison.out +0 -1
  78. package/test/eyelang/conformance/expected/extension/004_strings_and_atoms.out +0 -2
  79. package/test/eyelang/conformance/expected/extension/006_formula_terms.out +0 -2
  80. package/test/eyelang/conformance/expected/extension/009_list_relations.out +0 -1
  81. package/test/eyelang/conformance/expected/extension/026_nested_formula_terms.out +0 -3
  82. package/test/eyelang/conformance/expected/extension/030_string_and_atom_concat.out +0 -1
  83. package/test/eyelang/run-regression.mjs +2 -2
  84. package/test/packlist.test.js +2 -0
  85. package/examples/eyelang/INTEGRATION.md +0 -20
  86. package/examples/eyelang/README.md +0 -567
  87. package/examples/eyelang/exact-cover-sudoku.pl +0 -113
  88. package/examples/eyelang/output/exact-cover-sudoku.pl +0 -3
  89. package/examples/eyelang/output/sudoku.pl +0 -2
  90. package/examples/eyelang/sudoku.pl +0 -20
  91. package/lib/eyelang/builtins/sudoku.js +0 -141
  92. package/test/eyelang/conformance/cases/extension/025_is_list_filter.pl +0 -5
  93. package/test/eyelang/conformance/cases/extension/025_is_list_filter.query +0 -1
  94. package/test/eyelang/conformance/cases/extension/041_atom_range_generators.pl +0 -5
  95. package/test/eyelang/conformance/cases/extension/044_cover9_filter.pl +0 -6
  96. package/test/eyelang/conformance/expected/extension/025_is_list_filter.out +0 -1
  97. package/test/eyelang/conformance/expected/extension/041_atom_range_generators.out +0 -8
  98. package/test/eyelang/conformance/expected/extension/044_cover9_filter.out +0 -2
@@ -1,4 +1,4 @@
1
- # eyelang Language Specification
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 a built-in name such as `formula_atom/2`, it means **atom constant**, not atomic formula.
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. `formula_atom/2` and `formula_binary/4` enumerate members inside such formula terms.
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
- | `max(A, B, C)`, `min(A, B, C)` | Numeric maximum or minimum. |
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. `formula_atom/2` uses "atom" in the logic-programming sense of atomic formula; it MUST NOT be confused with atom constants such as `alice` or `name`.
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 Sudoku solving, 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.
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
 
@@ -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', 'max', 'min', 'pow'];
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, sudokuBuiltins, portfolioBuiltins, searchBuiltins, numberTheoryBuiltins, matrixBuiltins, alphameticBuiltins]) {
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, range, and n-queens helpers), not compiled
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 and atom conversion builtins.
1
+ // String builtins.
2
2
  // They mostly project from already-ground terms to avoid guessing string domains.
3
- import { atom, lexicalValue, stringTerm, unify } from '../term.js';
3
+ import { lexicalValue, stringTerm, unify } from '../term.js';
4
4
 
5
5
  export const stringBuiltins = {
6
6
  register(registry) {
7
- for (const name of ['atom_concat', 'str_concat']) registry.add(name, 3, concat(name), { deterministic: true });
8
- for (const name of ['contains', 'not_contains', 'matches', 'not_matches']) registry.add(name, 2, contains(name), { deterministic: true });
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(name) {
14
- return function* ({ goal, env }) {
15
- const left = lexicalValue(goal.args[0], env);
16
- const right = lexicalValue(goal.args[1], env);
17
- if (left == null || right == null) return;
18
- const result = name === 'str_concat' ? stringTerm(left + right) : atom(left + right);
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.4",
3
+ "version": "1.33.6",
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 [`../SPEC.md`](../SPEC.md).
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
- npm run test:conformance
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.js core
30
- node test/run-conformance.js extension
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 `../SPEC.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.
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 specification; most of them correspond to the standard built-in profile and standard host profile in `../SPEC.md`.
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
- % SPEC 6, 7, 11: facts can be exposed through a materialized derived predicate.
1
+ % Reference 6, 7, 11: facts can be exposed through a materialized derived predicate.
2
2
  materialize(parent, 2).
3
3
  base_parent(pat, jan).
4
4
  parent(X, Y) :- base_parent(X, Y).
@@ -1,4 +1,4 @@
1
- % SPEC 6, 7: definite clauses, conjunction, and recursive proof search.
1
+ % Reference 6, 7: definite clauses, conjunction, and recursive proof search.
2
2
  materialize(ancestor, 2).
3
3
  parent(pat, jan).
4
4
  parent(jan, emma).
@@ -1,4 +1,4 @@
1
- % SPEC 3, 5, 11: scalars, compounds, lists, and read-back printing.
1
+ % Reference 3, 5, 11: scalars, compounds, lists, and read-back printing.
2
2
  materialize(value, 2).
3
3
  raw_value(atom, pat).
4
4
  raw_value(quoted_atom, 'atom with spaces').
@@ -1,4 +1,4 @@
1
- % SPEC 5.5, 7: parenthesized comma terms are conjunctions in goal position.
1
+ % Reference 5.5, 7: parenthesized comma terms are conjunctions in goal position.
2
2
  p(a).
3
3
  q(a).
4
4
  ok(X, yes) :- (p(X), q(X)).
@@ -1,4 +1,4 @@
1
- % SPEC 5.4, 12.1: list syntax and unification in rule heads.
1
+ % Reference 5.4, 12.1: list syntax and unification in rule heads.
2
2
  first([X | _Rest], X).
3
3
  tail([_Head | Tail], Tail).
4
4
  answer(first, X) :- first([a, b, c], X).
@@ -1,4 +1,4 @@
1
- % SPEC 5.5: comma terms remain data outside goal position.
1
+ % Reference 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
4
  materialize(answer, 2).
@@ -1,4 +1,4 @@
1
- % SPEC 3.4, 5.1, 7.1: each anonymous variable occurrence is fresh.
1
+ % Reference 3.4, 5.1, 7.1: each anonymous variable occurrence is fresh.
2
2
  pair(a, one).
3
3
  pair(b, two).
4
4
  answer(fresh, yes) :- pair(a, _), pair(b, _).
@@ -1,4 +1,4 @@
1
- % SPEC 3.5, 5.2, 11: graphic atom constants are scalar terms.
1
+ % Reference 3.5, 5.2, 11: graphic atom constants are scalar terms.
2
2
  materialize(value, 2).
3
3
  raw_value(hash, #).
4
4
  raw_value(arrow, =>).
@@ -1,4 +1,4 @@
1
- % SPEC 3.2, 3.6: comments are ignored outside quoted text and atoms.
1
+ % Reference 3.2, 3.6: comments are ignored outside quoted text and atoms.
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).
@@ -1,4 +1,4 @@
1
- % SPEC 5.1, 7.1: variable occurrences are scoped per clause and reused within a clause.
1
+ % Reference 5.1, 7.1: variable occurrences are scoped per clause and reused within a clause.
2
2
  edge(a, b).
3
3
  edge(b, c).
4
4
  edge(c, d).
@@ -1,4 +1,4 @@
1
- % SPEC 6: predicate name and arity both identify a predicate group.
1
+ % Reference 6: predicate name and arity both identify a predicate group.
2
2
  p(a).
3
3
  p(a, b).
4
4
  answer(unary, X) :- p(X).
@@ -1,4 +1,4 @@
1
- % SPEC 5.3, 7.1: unification follows nested compound term structure.
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
- % SPEC 6, 7: multiple clauses for one predicate are explored as alternatives.
1
+ % Reference 6, 7: multiple clauses for one predicate are explored as alternatives.
2
2
  color(red).
3
3
  color(blue).
4
4
  paint(X) :- color(X).
@@ -1,4 +1,4 @@
1
- % SPEC 7.2: a failing subgoal removes that candidate answer.
1
+ % Reference 7.2: a failing subgoal removes that candidate answer.
2
2
  candidate(a).
3
3
  candidate(b).
4
4
  allowed(a).
@@ -1,4 +1,4 @@
1
- % SPEC 5.4: improper list surface syntax unifies with head-tail structure.
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
- % SPEC 4, 5.3: zero-arity compounds are written and matched with parentheses.
1
+ % Reference 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
4
  materialize(answer, 2).
@@ -1,4 +1,4 @@
1
- % SPEC 7: recursive definite clauses can derive paths beyond one step.
1
+ % Reference 7: recursive definite clauses can derive paths beyond one step.
2
2
  link(a, b).
3
3
  link(b, c).
4
4
  link(c, d).
@@ -1,4 +1,4 @@
1
- % SPEC 3.5, 11: quoted atoms preserve spaces, quotes, and the empty atom.
1
+ % Reference 3.5, 11: quoted atoms preserve spaces, quotes, and the empty atom.
2
2
  symbol('two words').
3
3
  symbol('needs''quote').
4
4
  symbol('').
@@ -1,4 +1,4 @@
1
- % SPEC 5.5, 7: parenthesized comma terms with more than two members are conjunctions as goals.
1
+ % Reference 5.5, 7: parenthesized comma terms with more than two members are conjunctions as goals.
2
2
  p(a).
3
3
  q(a).
4
4
  r(a).
@@ -1,4 +1,4 @@
1
- % SPEC 5.3, 5.4: lists may contain structured terms that unify positionally.
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)]).
@@ -1,4 +1,4 @@
1
- % SPEC 5.1, 7.1: repeated variables in a rule body require the same term.
1
+ % Reference 5.1, 7.1: repeated variables in a rule body require the same term.
2
2
  pair(a, a).
3
3
  pair(a, b).
4
4
  pair(c, c).
@@ -1,4 +1,4 @@
1
- % SPEC 5.3, 6, 7.1: structured rule heads destructure matching goals.
1
+ % Reference 5.3, 6, 7.1: structured rule heads destructure matching goals.
2
2
  unpack(pair(X, Y), X, Y).
3
3
  answer(first, A) :- unpack(pair(alpha, beta), A, _).
4
4
  answer(second, B) :- unpack(pair(alpha, beta), _, B).
@@ -1,4 +1,4 @@
1
- % SPEC 3.5, 11: quoted strings and atoms preserve escape sequences at read-back.
1
+ % Reference 3.5, 11: quoted strings and atoms preserve escape sequences at read-back.
2
2
  raw(string, "line\nnext\t\\slash").
3
3
  raw(atom, 'line\nnext\t\\slash').
4
4
  answer(K, V) :- raw(K, V).
@@ -1,4 +1,4 @@
1
- % SPEC 3.3, 11: signed, decimal, and exponent numeric literals retain lexical form.
1
+ % Reference 3.3, 11: signed, decimal, and exponent numeric literals retain lexical form.
2
2
  raw(negative_decimal, -3.5).
3
3
  raw(positive_exp, 6.02e23).
4
4
  raw(negative_exp, -1.0e-3).
@@ -1,4 +1,4 @@
1
- % SPEC 5.5, 7: formula data can be passed through parenthesized conjunctions.
1
+ % Reference 5.5, 7: formula data can be passed through parenthesized conjunctions.
2
2
  record((left(a), right(b))).
3
3
  accept((left(a), right(b))).
4
4
  answer(ok, F) :- (record(F), accept(F)).
@@ -1,4 +1,4 @@
1
- % SPEC 3.4, 5.1: variables beginning with underscore are named variables unless exactly _.
1
+ % Reference 3.4, 5.1: variables beginning with underscore are named variables unless exactly _.
2
2
  pair(a, a).
3
3
  pair(a, b).
4
4
  answer(shared, _Value) :- pair(_Value, _Value).
@@ -1,4 +1,4 @@
1
- % SPEC 11: default output prints new ground binary derivations, not source facts.
1
+ % Reference 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 default predicate groups.
1
+ % Reference 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).
@@ -1,4 +1,4 @@
1
- % SPEC 9.2, 9.3: arithmetic and comparison built-ins.
1
+ % Reference 9.2, 9.3: arithmetic and comparison built-ins.
2
2
  answer(sum, X) :- add(2, 3, X).
3
3
  answer(diff, X) :- sub(7, 4, X).
4
4
  answer(product, X) :- mul(6, 7, X).
@@ -6,7 +6,6 @@ answer(integer_division, X) :- div(7, 2, X).
6
6
  answer(remainder, X) :- mod(7, 2, X).
7
7
  answer(power, X) :- pow(2, 8, X).
8
8
  answer(minimum, X) :- min(3, 9, X).
9
- answer(maximum, X) :- max(3, 9, X).
10
9
  answer(less_than, true) :- lt(3, 9).
11
10
  answer(greater_equal, true) :- ge(9, 9).
12
11
  materialize(answer, 2).
@@ -1,6 +1,4 @@
1
- % SPEC 9.6: atom and string built-ins.
2
- answer(atom_concat, X) :- atom_concat(eye, lang, X).
1
+ % Reference 9.6: atom and string built-ins.
3
2
  answer(str_concat, X) :- str_concat("eye", "lang", X).
4
3
  answer(contains, true) :- contains("eyelang", "lang").
5
- answer(not_contains, true) :- not_contains("eyelang", "cat").
6
4
  materialize(answer, 2).
@@ -1,4 +1,4 @@
1
- % SPEC 9.7, 9.8: list, aggregation, and ordering built-ins.
1
+ % Reference 9.7, 9.8: list, aggregation, and ordering built-ins.
2
2
  answer(member, X) :- member(X, [a, b]).
3
3
  answer(append, X) :- append([a], [b, c], X).
4
4
  answer(nth0, X) :- nth0(1, [a, b, c], X).
@@ -1,5 +1,4 @@
1
- % SPEC 9.9: formula_atom/2 and formula_binary/4 over comma formula data.
1
+ % Reference 9.9: formula_binary/4 over comma formula data.
2
2
  formula((name(alice, "Alice"), knows(alice, bob))).
3
- answer(atom, A) :- formula(F), formula_atom(F, A).
4
3
  answer(binary, exposed(S, P, O)) :- formula(F), formula_binary(F, S, P, O).
5
4
  materialize(answer, 2).
@@ -1,4 +1,4 @@
1
- % SPEC 9.5, 9.10: generators, negation as failure, and once/1.
1
+ % Reference 9.5, 9.10: generators, negation as failure, and once/1.
2
2
  candidate(a).
3
3
  candidate(b).
4
4
  closed(b).
@@ -1,4 +1,4 @@
1
- % SPEC 9.1: eq/2 unifies terms and neq/2 succeeds on non-unifiable terms.
1
+ % Reference 9.1: eq/2 unifies terms and neq/2 succeeds on non-unifiable terms.
2
2
  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).
@@ -1,6 +1,5 @@
1
- % SPEC 9.7: rest/2, select/3, not_member/2, and is_list/1.
1
+ % Reference 9.7: rest/2, select/3, and not_member/2.
2
2
  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
- answer(is_list, true) :- is_list([a, b]).
6
5
  materialize(answer, 2).
@@ -1,3 +1,3 @@
1
- % SPEC 9.7: append/3 can enumerate proper prefix/suffix splits.
1
+ % Reference 9.7: append/3 can enumerate proper prefix/suffix splits.
2
2
  answer(split, split(A, B)) :- append(A, B, [a, b]).
3
3
  materialize(answer, 2).