eyeling 1.34.3 → 1.34.4

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 (48) hide show
  1. package/README.md +7 -9
  2. package/docs/eyelang-guide.md +5 -10
  3. package/docs/eyelang-language-reference.md +31 -5
  4. package/examples/eyelang/bayes-therapy.pl +4 -4
  5. package/examples/eyelang/output/reusable-builtins.pl +5 -0
  6. package/examples/eyelang/output/term-tools.pl +6 -0
  7. package/examples/eyelang/reusable-builtins.pl +32 -0
  8. package/examples/eyelang/term-tools.pl +23 -0
  9. package/lib/eyelang/builtins/arithmetic.js +19 -6
  10. package/lib/eyelang/builtins/control.js +12 -0
  11. package/lib/eyelang/builtins/lists.js +146 -7
  12. package/lib/eyelang/builtins/registry.js +2 -3
  13. package/lib/eyelang/builtins/strings.js +165 -1
  14. package/lib/eyelang/builtins/terms.js +66 -0
  15. package/package.json +1 -1
  16. package/test/eyelang/conformance/README.md +1 -1
  17. package/test/eyelang/conformance/cases/extension/036_reusable_numeric_builtins.pl +10 -0
  18. package/test/eyelang/conformance/cases/extension/037_reusable_list_builtins.pl +11 -0
  19. package/test/eyelang/conformance/cases/extension/038_reusable_string_builtins.pl +12 -0
  20. package/test/eyelang/conformance/cases/extension/039_reusable_term_control_builtins.pl +11 -0
  21. package/test/eyelang/conformance/expected/extension/036_reusable_numeric_builtins.out +8 -0
  22. package/test/eyelang/conformance/expected/extension/037_reusable_list_builtins.out +9 -0
  23. package/test/eyelang/conformance/expected/extension/038_reusable_string_builtins.out +10 -0
  24. package/test/eyelang/conformance/expected/extension/039_reusable_term_control_builtins.out +6 -0
  25. package/examples/eyelang/collatz-1000.pl +0 -14
  26. package/examples/eyelang/complex-matrix-stability.pl +0 -45
  27. package/examples/eyelang/gcd-bezout-identity.pl +0 -48
  28. package/examples/eyelang/goldbach-1000.pl +0 -185
  29. package/examples/eyelang/kaprekar.pl +0 -32
  30. package/examples/eyelang/matrix.pl +0 -296
  31. package/examples/eyelang/output/collatz-1000.pl +0 -1000
  32. package/examples/eyelang/output/complex-matrix-stability.pl +0 -5
  33. package/examples/eyelang/output/gcd-bezout-identity.pl +0 -36
  34. package/examples/eyelang/output/goldbach-1000.pl +0 -667
  35. package/examples/eyelang/output/kaprekar.pl +0 -8
  36. package/examples/eyelang/output/matrix.pl +0 -10
  37. package/lib/eyelang/builtins/matrix.js +0 -226
  38. package/lib/eyelang/builtins/number-theory.js +0 -114
  39. package/test/eyelang/conformance/cases/extension/036_extended_gcd.pl +0 -3
  40. package/test/eyelang/conformance/cases/extension/037_collatz_trajectory.pl +0 -3
  41. package/test/eyelang/conformance/cases/extension/038_kaprekar_steps.pl +0 -3
  42. package/test/eyelang/conformance/cases/extension/039_goldbach_pair.pl +0 -3
  43. package/test/eyelang/conformance/cases/extension/040_matrix_operations.pl +0 -5
  44. package/test/eyelang/conformance/expected/extension/036_extended_gcd.out +0 -1
  45. package/test/eyelang/conformance/expected/extension/037_collatz_trajectory.out +0 -1
  46. package/test/eyelang/conformance/expected/extension/038_kaprekar_steps.out +0 -1
  47. package/test/eyelang/conformance/expected/extension/039_goldbach_pair.out +0 -2
  48. package/test/eyelang/conformance/expected/extension/040_matrix_operations.out +0 -3
package/README.md CHANGED
@@ -819,20 +819,18 @@ Formula-aware built-ins make Eyeling useful for meta-reasoning. `log:includes`,
819
819
 
820
820
  ### eyelang built-ins
821
821
 
822
- The eyelang engine has its own built-in registry under `lib/eyelang/builtins/`. These are separate from the N3 namespaces above and are called as ordinary eyelang predicates. See the [eyelang language reference](docs/eyelang-language-reference.md#9-standard-built-in-predicates) for the portable profile. The bundled implementation currently registers 59 name/arity entries across 57 predicate names:
822
+ The eyelang engine has its own built-in registry under `lib/eyelang/builtins/`. These are separate from the N3 namespaces above and are called as ordinary eyelang predicates. See the [eyelang language reference](docs/eyelang-language-reference.md#9-standard-built-in-predicates) for the portable profile. The bundled implementation currently registers 80 name/arity entries across 78 predicate names:
823
823
 
824
824
  | Family | Count | Built-ins |
825
825
  |---|---:|---|
826
826
  | Core and host | 4 | `eq/2`, `neq/2`, `local_time/1`, `difference/3` |
827
- | Arithmetic and comparison | 21 | `neg/2`, `abs/2`, `sin/2`, `cos/2`, `asin/2`, `acos/2`, `rounded/2`, `log/2`, `add/3`, `sub/3`, `mul/3`, `div/3`, `mod/3`, `min/3`, `pow/3`, `lt/2`, `gt/2`, `le/2`, `ge/2`, `between/3`, `smallest_divisor_from/3` |
828
- | Strings | 5 | `str_concat/3`, `contains/2`, `matches/2`, `matches/3`, `not_matches/2` |
829
- | Lists | 10 | `append/3`, `nth0/3`, `set_nth0/4`, `rest/2`, `member/2`, `select/3`, `not_member/2`, `reverse/2`, `length/2`, `sort/2` |
827
+ | Arithmetic, comparison, and generators | 29 | `neg/2`, `abs/2`, `sin/2`, `cos/2`, `tan/2`, `asin/2`, `acos/2`, `sqrt/2`, `floor/2`, `ceiling/2`, `trunc/2`, `rounded/2`, `exp/2`, `log/2`, `add/3`, `sub/3`, `mul/3`, `div/3`, `mod/3`, `min/3`, `max/3`, `pow/3`, `atan2/3`, `lt/2`, `gt/2`, `le/2`, `ge/2`, `between/3`, `smallest_divisor_from/3` |
828
+ | Strings and conversions | 15 | `str_concat/3`, `contains/2`, `matches/2`, `matches/3`, `not_matches/2`, `split/3`, `join/3`, `substring/4`, `replace/4`, `lowercase/2`, `uppercase/2`, `trim/2`, `number_string/2`, `atom_string/2`, `term_string/2` |
829
+ | Lists | 19 | `append/3`, `nth0/3`, `set_nth0/4`, `head/2`, `rest/2`, `last/2`, `take/3`, `drop/3`, `slice/4`, `member/2`, `select/3`, `not_member/2`, `reverse/2`, `length/2`, `sum_list/2`, `min_list/2`, `max_list/2`, `list_to_set/2`, `sort/2` |
830
830
  | Aggregation | 5 | `findall/3`, `countall/2`, `sumall/3`, `aggregate_min/5`, `aggregate_max/5` |
831
- | Control | 2 | `not/1`, `once/1` |
832
- | Context terms | 2 | `holds/2`, `holds/3` |
833
- | Numeric extension helpers | 4 | `extended_gcd/5`, `collatz_trajectory/2`, `kaprekar_steps/2`, `goldbach_pair/3` |
834
- | Matrix helpers | 6 | `matrix_sum/2`, `matrix_multiply/2`, `cholesky_decomposition/2`, `determinant/2`, `matrix_inv_triang/2`, `matrix_inversion/2` |
835
- | **Total** | **59** | |
831
+ | Control | 3 | `not/1`, `once/1`, `forall/2` |
832
+ | Context and terms | 5 | `holds/2`, `holds/3`, `functor/3`, `arg/3`, `compound_name_arguments/3` |
833
+ | **Total** | **80** | |
836
834
 
837
835
  ## Custom built-ins
838
836
 
@@ -250,7 +250,7 @@ The playground has matching `--stats` and `--proof` checkboxes, so browser runs
250
250
 
251
251
  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.
252
252
 
253
- The builtin families cover unification, arithmetic, comparison, dates, strings, lists, aggregation, context terms, search control, number-theory helpers, and matrix helpers. The previous finite-search helper module has been removed because those predicates were too example-specific to be reusable. Examples that are still practical with ordinary relations and reusable list/arithmetic builtins have been rewritten in that style; only the examples that depended on non-portable shortcuts were dropped. The complete bundled implementation list is kept in the top-level [README built-ins section](../README.md#built-ins-1), and the regression suite checks that table against the actual runtime registry.
253
+ The builtin families cover unification, arithmetic, comparison, dates, strings, lists, aggregation, context terms, term inspection, and search control. Domain-specific number-theory and matrix helper modules were removed from the default registry because those predicates were examples/accelerators rather than a reusable portable surface. New reusable helpers cover common numeric functions, list slicing and summaries, string normalization/conversion, term inspection/construction, and `forall/2`. The complete bundled implementation list is kept in the top-level [README built-ins section](../README.md#built-ins-1), and the regression suite checks that table against the actual runtime registry.
254
254
 
255
255
  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.
256
256
 
@@ -321,10 +321,8 @@ The repository includes examples for recursion, graph reachability, finite searc
321
321
  | [`canary-release.pl`](../examples/eyelang/canary-release.pl) | Decides canary rollout or rollback. | [`output/canary-release.pl`](../examples/eyelang/output/canary-release.pl) |
322
322
  | [`cat-koko.pl`](../examples/eyelang/cat-koko.pl) | Demonstrates named existential witnesses from a Cat Koko rule pattern. | [`output/cat-koko.pl`](../examples/eyelang/output/cat-koko.pl) |
323
323
  | [`clinical-trial-screening.pl`](../examples/eyelang/clinical-trial-screening.pl) | Screens candidates for a trial. | [`output/clinical-trial-screening.pl`](../examples/eyelang/output/clinical-trial-screening.pl) |
324
- | [`collatz-1000.pl`](../examples/eyelang/collatz-1000.pl) | Computes shared Collatz trajectories. | [`output/collatz-1000.pl`](../examples/eyelang/output/collatz-1000.pl) |
325
324
  | [`combinatorics-findall-sort.pl`](../examples/eyelang/combinatorics-findall-sort.pl) | Collects and sorts finite combinations. | [`output/combinatorics-findall-sort.pl`](../examples/eyelang/output/combinatorics-findall-sort.pl) |
326
325
  | [`competitive-enzyme-kinetics.pl`](../examples/eyelang/competitive-enzyme-kinetics.pl) | Computes inhibited enzyme reaction rates. | [`output/competitive-enzyme-kinetics.pl`](../examples/eyelang/output/competitive-enzyme-kinetics.pl) |
327
- | [`complex-matrix-stability.pl`](../examples/eyelang/complex-matrix-stability.pl) | Checks stability of a 2x2 system. | [`output/complex-matrix-stability.pl`](../examples/eyelang/output/complex-matrix-stability.pl) |
328
326
  | [`complex.pl`](../examples/eyelang/complex.pl) | Performs arithmetic on complex pairs. | [`output/complex.pl`](../examples/eyelang/output/complex.pl) |
329
327
  | [`composition-of-injective-functions-is-injective.pl`](../examples/eyelang/composition-of-injective-functions-is-injective.pl) | Encodes composition and injectivity of finite functions. | [`output/composition-of-injective-functions-is-injective.pl`](../examples/eyelang/output/composition-of-injective-functions-is-injective.pl) |
330
328
  | [`context-association.pl`](../examples/eyelang/context-association.pl) | Associates named contexts with their contents. | [`output/context-association.pl`](../examples/eyelang/output/context-association.pl) |
@@ -344,9 +342,9 @@ The repository includes examples for recursion, graph reachability, finite searc
344
342
  | [`derived-backward-rule.pl`](../examples/eyelang/derived-backward-rule.pl) | Derives an inverse-property backward rule from rule data. | [`output/derived-backward-rule.pl`](../examples/eyelang/output/derived-backward-rule.pl) |
345
343
  | [`derived-rule.pl`](../examples/eyelang/derived-rule.pl) | Derives conclusions from rule data. | [`output/derived-rule.pl`](../examples/eyelang/output/derived-rule.pl) |
346
344
  | [`diamond-property.pl`](../examples/eyelang/diamond-property.pl) | Checks the diamond property of a relation. | [`output/diamond-property.pl`](../examples/eyelang/output/diamond-property.pl) |
345
+ | [`dijkstra.pl`](../examples/eyelang/dijkstra.pl) | Enumerates weighted simple paths. | [`output/dijkstra.pl`](../examples/eyelang/output/dijkstra.pl) |
347
346
  | [`dijkstra-findall-sort.pl`](../examples/eyelang/dijkstra-findall-sort.pl) | Finds shortest paths using collected candidates. | [`output/dijkstra-findall-sort.pl`](../examples/eyelang/output/dijkstra-findall-sort.pl) |
348
347
  | [`dijkstra-risk-path.pl`](../examples/eyelang/dijkstra-risk-path.pl) | Ranks routes by cost and trust. | [`output/dijkstra-risk-path.pl`](../examples/eyelang/output/dijkstra-risk-path.pl) |
349
- | [`dijkstra.pl`](../examples/eyelang/dijkstra.pl) | Enumerates weighted simple paths. | [`output/dijkstra.pl`](../examples/eyelang/output/dijkstra.pl) |
350
348
  | [`dining-philosophers.pl`](../examples/eyelang/dining-philosophers.pl) | Simulates Chandy-Misra fork exchanges. | [`output/dining-philosophers.pl`](../examples/eyelang/output/dining-philosophers.pl) |
351
349
  | [`dog.pl`](../examples/eyelang/dog.pl) | Counts dogs and derives when a license is required. | [`output/dog.pl`](../examples/eyelang/output/dog.pl) |
352
350
  | [`dpv-odrl-purpose-mapping.pl`](../examples/eyelang/dpv-odrl-purpose-mapping.pl) | Maps a DPV process into an ODRL permission view. | [`output/dpv-odrl-purpose-mapping.pl`](../examples/eyelang/output/dpv-odrl-purpose-mapping.pl) |
@@ -365,14 +363,12 @@ The repository includes examples for recursion, graph reachability, finite searc
365
363
  | [`fft8-numeric.pl`](../examples/eyelang/fft8-numeric.pl) | Runs an 8-point FFT over complex pairs. | [`output/fft8-numeric.pl`](../examples/eyelang/output/fft8-numeric.pl) |
366
364
  | [`fibonacci.pl`](../examples/eyelang/fibonacci.pl) | Computes large Fibonacci numbers by fast doubling. | [`output/fibonacci.pl`](../examples/eyelang/output/fibonacci.pl) |
367
365
  | [`field-nitrogen-balance.pl`](../examples/eyelang/field-nitrogen-balance.pl) | Classifies field nitrogen balance. | [`output/field-nitrogen-balance.pl`](../examples/eyelang/output/field-nitrogen-balance.pl) |
368
- | [`floating-point.pl`](../examples/eyelang/floating-point.pl) | Exercises floating-point arithmetic and comparisons. | [`output/floating-point.pl`](../examples/eyelang/output/floating-point.pl) |
369
366
  | [`flandor.pl`](../examples/eyelang/flandor.pl) | Derives a Flanders macro-insight authorization and retooling package. | [`output/flandor.pl`](../examples/eyelang/output/flandor.pl) |
367
+ | [`floating-point.pl`](../examples/eyelang/floating-point.pl) | Exercises floating-point arithmetic and comparisons. | [`output/floating-point.pl`](../examples/eyelang/output/floating-point.pl) |
370
368
  | [`four-color-map.pl`](../examples/eyelang/four-color-map.pl) | Checks a four-colour map assignment. | [`output/four-color-map.pl`](../examples/eyelang/output/four-color-map.pl) |
371
369
  | [`fundamental-theorem-arithmetic.pl`](../examples/eyelang/fundamental-theorem-arithmetic.pl) | Factors integers and reconstructs products. | [`output/fundamental-theorem-arithmetic.pl`](../examples/eyelang/output/fundamental-theorem-arithmetic.pl) |
372
- | [`gcd-bezout-identity.pl`](../examples/eyelang/gcd-bezout-identity.pl) | Computes gcd and Bézout coefficients. | [`output/gcd-bezout-identity.pl`](../examples/eyelang/output/gcd-bezout-identity.pl) |
373
370
  | [`gd-step-certified.pl`](../examples/eyelang/gd-step-certified.pl) | Certifies a gradient-descent step. | [`output/gd-step-certified.pl`](../examples/eyelang/output/gd-step-certified.pl) |
374
371
  | [`gdpr-compliance.pl`](../examples/eyelang/gdpr-compliance.pl) | Checks GDPR-style processing compliance. | [`output/gdpr-compliance.pl`](../examples/eyelang/output/gdpr-compliance.pl) |
375
- | [`goldbach-1000.pl`](../examples/eyelang/goldbach-1000.pl) | Finds Goldbach prime pairs up to 1000. | [`output/goldbach-1000.pl`](../examples/eyelang/output/goldbach-1000.pl) |
376
372
  | [`good-cobbler.pl`](../examples/eyelang/good-cobbler.pl) | Demonstrates term-level structure with a good-cobbler statement. | [`output/good-cobbler.pl`](../examples/eyelang/output/good-cobbler.pl) |
377
373
  | [`gps.pl`](../examples/eyelang/gps.pl) | Finds and verifies route paths. | [`output/gps.pl`](../examples/eyelang/output/gps.pl) |
378
374
  | [`graph-reachability.pl`](../examples/eyelang/graph-reachability.pl) | Derives reachable nodes in a graph. | [`output/graph-reachability.pl`](../examples/eyelang/output/graph-reachability.pl) |
@@ -386,14 +382,12 @@ The repository includes examples for recursion, graph reachability, finite searc
386
382
  | [`heron-theorem.pl`](../examples/eyelang/heron-theorem.pl) | Computes triangle area by Heron's theorem. | [`output/heron-theorem.pl`](../examples/eyelang/output/heron-theorem.pl) |
387
383
  | [`ideal-gas-law.pl`](../examples/eyelang/ideal-gas-law.pl) | Applies the ideal gas law. | [`output/ideal-gas-law.pl`](../examples/eyelang/output/ideal-gas-law.pl) |
388
384
  | [`illegitimate-reasoning.pl`](../examples/eyelang/illegitimate-reasoning.pl) | Detects suspect reasoning patterns. | [`output/illegitimate-reasoning.pl`](../examples/eyelang/output/illegitimate-reasoning.pl) |
389
- | [`kaprekar.pl`](../examples/eyelang/kaprekar.pl) | Iterates toward Kaprekar's constant. | [`output/kaprekar.pl`](../examples/eyelang/output/kaprekar.pl) |
390
385
  | [`knowledge-engineering-alignment-flow.pl`](../examples/eyelang/knowledge-engineering-alignment-flow.pl) | Specializes reusable alignment rules into a target-shaped flow view. | [`output/knowledge-engineering-alignment-flow.pl`](../examples/eyelang/output/knowledge-engineering-alignment-flow.pl) |
391
386
  | [`law-of-cosines.pl`](../examples/eyelang/law-of-cosines.pl) | Computes a triangle side by cosine law. | [`output/law-of-cosines.pl`](../examples/eyelang/output/law-of-cosines.pl) |
392
387
  | [`least-squares-regression.pl`](../examples/eyelang/least-squares-regression.pl) | Fits a least-squares regression line. | [`output/least-squares-regression.pl`](../examples/eyelang/output/least-squares-regression.pl) |
393
388
  | [`list-collection.pl`](../examples/eyelang/list-collection.pl) | Demonstrates list and collection built-ins. | [`output/list-collection.pl`](../examples/eyelang/output/list-collection.pl) |
394
389
  | [`lldm.pl`](../examples/eyelang/lldm.pl) | Calculates leg-length discrepancy measurements. | [`output/lldm.pl`](../examples/eyelang/output/lldm.pl) |
395
390
  | [`manufacturing-quality-control.pl`](../examples/eyelang/manufacturing-quality-control.pl) | Evaluates process capability and quality. | [`output/manufacturing-quality-control.pl`](../examples/eyelang/output/manufacturing-quality-control.pl) |
396
- | [`matrix.pl`](../examples/eyelang/matrix.pl) | Runs matrix operations over sample inputs. | [`output/matrix.pl`](../examples/eyelang/output/matrix.pl) |
397
391
  | [`microgrid-dispatch.pl`](../examples/eyelang/microgrid-dispatch.pl) | Plans microgrid dispatch and reserve. | [`output/microgrid-dispatch.pl`](../examples/eyelang/output/microgrid-dispatch.pl) |
398
392
  | [`monkey-bananas.pl`](../examples/eyelang/monkey-bananas.pl) | Solves the monkey-and-bananas puzzle. | [`output/monkey-bananas.pl`](../examples/eyelang/output/monkey-bananas.pl) |
399
393
  | [`network-sla.pl`](../examples/eyelang/network-sla.pl) | Checks network path SLA compliance. | [`output/network-sla.pl`](../examples/eyelang/output/network-sla.pl) |
@@ -412,6 +406,7 @@ The repository includes examples for recursion, graph reachability, finite searc
412
406
  | [`proof-contrapositive.pl`](../examples/eyelang/proof-contrapositive.pl) | Models proof by contrapositive. | [`output/proof-contrapositive.pl`](../examples/eyelang/output/proof-contrapositive.pl) |
413
407
  | [`quadratic-formula.pl`](../examples/eyelang/quadratic-formula.pl) | Solves sample quadratic equations. | [`output/quadratic-formula.pl`](../examples/eyelang/output/quadratic-formula.pl) |
414
408
  | [`radioactive-decay.pl`](../examples/eyelang/radioactive-decay.pl) | Computes radioactive decay over time. | [`output/radioactive-decay.pl`](../examples/eyelang/output/radioactive-decay.pl) |
409
+ | [`reusable-builtins.pl`](../examples/eyelang/reusable-builtins.pl) | Tours reusable numeric, list, and string builtins. | [`output/reusable-builtins.pl`](../examples/eyelang/output/reusable-builtins.pl) |
415
410
  | [`riemann-hypothesis.pl`](../examples/eyelang/riemann-hypothesis.pl) | Checks a finite catalogue of non-trivial zeta zeros against the Riemann-hypothesis condition. | [`output/riemann-hypothesis.pl`](../examples/eyelang/output/riemann-hypothesis.pl) |
416
411
  | [`security-incident-correlation.pl`](../examples/eyelang/security-incident-correlation.pl) | Correlates security incidents across signals. | [`output/security-incident-correlation.pl`](../examples/eyelang/output/security-incident-correlation.pl) |
417
412
  | [`service-impact.pl`](../examples/eyelang/service-impact.pl) | Analyzes service impact over cyclic dependencies. | [`output/service-impact.pl`](../examples/eyelang/output/service-impact.pl) |
@@ -422,6 +417,7 @@ The repository includes examples for recursion, graph reachability, finite searc
422
417
  | [`socrates.pl`](../examples/eyelang/socrates.pl) | Derives that Socrates is mortal. | [`output/socrates.pl`](../examples/eyelang/output/socrates.pl) |
423
418
  | [`statistics-summary.pl`](../examples/eyelang/statistics-summary.pl) | Computes population statistics for a sample. | [`output/statistics-summary.pl`](../examples/eyelang/output/statistics-summary.pl) |
424
419
  | [`superdense-coding.pl`](../examples/eyelang/superdense-coding.pl) | Models superdense-coding bit transmission. | [`output/superdense-coding.pl`](../examples/eyelang/output/superdense-coding.pl) |
420
+ | [`term-tools.pl`](../examples/eyelang/term-tools.pl) | Inspects, builds, renders, and validates terms with reusable term/control builtins. | [`output/term-tools.pl`](../examples/eyelang/output/term-tools.pl) |
425
421
  | [`trust-flow-provenance-threshold.pl`](../examples/eyelang/trust-flow-provenance-threshold.pl) | Classifies message trust from provenance confidence scores. | [`output/trust-flow-provenance-threshold.pl`](../examples/eyelang/output/trust-flow-provenance-threshold.pl) |
426
422
  | [`turing.pl`](../examples/eyelang/turing.pl) | Simulates a binary-increment Turing machine. | [`output/turing.pl`](../examples/eyelang/output/turing.pl) |
427
423
  | [`vector-similarity.pl`](../examples/eyelang/vector-similarity.pl) | Computes dot product, norm, and cosine similarity. | [`output/vector-similarity.pl`](../examples/eyelang/output/vector-similarity.pl) |
@@ -431,7 +427,6 @@ The repository includes examples for recursion, graph reachability, finite searc
431
427
  | [`zebra.pl`](../examples/eyelang/zebra.pl) | Solves the zebra logic puzzle. | [`output/zebra.pl`](../examples/eyelang/output/zebra.pl) |
432
428
 
433
429
 
434
-
435
430
  ## Golden outputs, tests, and conformance
436
431
 
437
432
  Golden answer outputs live in [`examples/eyelang/output`](../examples/eyelang/output). `npm run test:eyelang` covers the eyelang integration check, conformance cases, regression checks, runnable examples, and proof-output examples. A curated proof-output suite for `.pl` examples lives in [`examples/eyelang/proof`](../examples/eyelang/proof). Example tests pin `local_time/1` to `2026-05-30` so date-dependent examples stay deterministic. Regenerate them after an intentional output or explanation change:
@@ -360,15 +360,18 @@ Implementations MAY provide additional built-ins, but such built-ins are extensi
360
360
  |---|---|
361
361
  | `neg(A, B)` | `B` is the numeric negation of `A`. |
362
362
  | `abs(A, B)` | `B` is the absolute value of `A`. |
363
- | `sin(A, B)`, `cos(A, B)`, `asin(A, B)`, `acos(A, B)`, `log(A, B)` | Floating functions. |
364
- | `rounded(A, B)` | `B` is `A` rounded to the nearest integer. |
363
+ | `sin(A, B)`, `cos(A, B)`, `tan(A, B)` | Trigonometric floating functions. |
364
+ | `asin(A, B)`, `acos(A, B)`, `atan2(Y, X, Angle)` | Inverse trigonometric floating functions. |
365
+ | `sqrt(A, B)` | Square root. Fails for negative inputs. |
366
+ | `floor(A, B)`, `ceiling(A, B)`, `trunc(A, B)`, `rounded(A, B)` | Integer-valued numeric rounding functions. |
367
+ | `exp(A, B)`, `log(A, B)` | Natural exponent and logarithm. `log/2` fails for non-positive inputs. |
365
368
  | `add(A, B, C)` | `C = A + B`. |
366
369
  | `sub(A, B, C)` | `C = A - B`. |
367
370
  | `mul(A, B, C)` | `C = A * B`. |
368
371
  | `div(A, B, C)` | `C = A / B`; integer inputs use integer division. |
369
372
  | `mod(A, B, C)` | Integer remainder. |
370
373
  | `pow(A, B, C)` | `C = A^B`. |
371
- | `min(A, B, C)` | Numeric minimum. |
374
+ | `min(A, B, C)`, `max(A, B, C)` | Numeric minimum and maximum. |
372
375
 
373
376
  ### 9.3 Comparison
374
377
 
@@ -404,6 +407,14 @@ Comparisons interpret numeric-looking terms numerically. Other scalar terms are
404
407
  | `matches(Text, Pattern)` | Text matches a simple implementation regex/search pattern. |
405
408
  | `matches(Text, Pattern, Context)` | Text matches a JavaScript regular expression with named capture groups; `Context` is a comma context containing one unary term per matched capture group. |
406
409
  | `not_matches(Text, Pattern)` | Negation of `matches/2`. |
410
+ | `split(Text, Separator, Parts)` | Splits text into a proper list of strings. |
411
+ | `join(Parts, Separator, Text)` | Joins a proper list of scalar terms into a string. |
412
+ | `substring(Text, Start, Length, Out)` | Extracts a zero-based substring. |
413
+ | `replace(Text, Search, Replacement, Out)` | Replaces all non-empty literal occurrences of `Search`. |
414
+ | `lowercase(Text, Out)`, `uppercase(Text, Out)`, `trim(Text, Out)` | Text normalization helpers. |
415
+ | `number_string(Number, String)` | Converts a number to a string or parses a numeric string into a number. |
416
+ | `atom_string(Atom, String)` | Converts between atom constants and strings. |
417
+ | `term_string(Term, String)` | Renders a ground term as its eyelang source string. |
407
418
 
408
419
  ### 9.7 Lists
409
420
 
@@ -412,12 +423,21 @@ Comparisons interpret numeric-looking terms numerically. Other scalar terms are
412
423
  | `append(A, B, C)` | List append/split relation. |
413
424
  | `nth0(Index, List, Value)` | Zero-based list lookup. |
414
425
  | `set_nth0(Index, List, Value, Out)` | Functional list update. |
426
+ | `head(List, Head)` | Head of a non-empty list. |
415
427
  | `rest(List, Tail)` | Tail of a non-empty list. |
428
+ | `last(List, Last)` | Last element of a non-empty proper list. |
429
+ | `take(N, List, Prefix)` | First `N` items of a proper list. |
430
+ | `drop(N, List, Suffix)` | Proper-list suffix after dropping `N` items. |
431
+ | `slice(Start, Length, List, Slice)` | Zero-based proper-list slice. |
416
432
  | `member(X, List)` | Member generator. |
417
433
  | `select(X, List, Rest)` | Selects one occurrence. |
418
434
  | `not_member(X, List)` | Succeeds when `X` is not a member. |
419
435
  | `reverse(A, B)` | Reverses a proper list. |
420
436
  | `length(List, N)` | Proper-list length. |
437
+ | `sum_list(List, Sum)` | Numeric sum of a proper list; empty lists produce `0`. |
438
+ | `min_list(List, Min)`, `max_list(List, Max)` | Minimum and maximum under standard term ordering. |
439
+ | `list_to_set(List, Set)` | Removes duplicates while preserving the first occurrence order. |
440
+ | `sort(Input, Output)` | Sorts and deduplicates a proper list. |
421
441
 
422
442
  ### 9.8 Aggregation and ordering
423
443
 
@@ -428,9 +448,8 @@ Comparisons interpret numeric-looking terms numerically. Other scalar terms are
428
448
  | `sumall(Template, Goal, Sum)` | Sums numeric `Template` values over solutions of `Goal`; empty solution sets produce `0`. |
429
449
  | `aggregate_min(Key, Template, Goal, BestKey, BestTemplate)` | Selects the solution of `Goal` with the smallest resolved `Key`, returning that key and the corresponding resolved `Template`. Fails when `Goal` has no solutions. |
430
450
  | `aggregate_max(Key, Template, Goal, BestKey, BestTemplate)` | Selects the solution of `Goal` with the largest resolved `Key`, returning that key and the corresponding resolved `Template`. Fails when `Goal` has no solutions. |
431
- | `sort(Input, Output)` | Sorts and deduplicates a proper list. |
432
451
 
433
- ### 9.9 Context terms
452
+ ### 9.9 Context and term inspection
434
453
 
435
454
  Context terms are data representations of atomic formulas and comma conjunctions.
436
455
 
@@ -438,12 +457,18 @@ Context terms are data representations of atomic formulas and comma conjunctions
438
457
  |---|---|
439
458
  | `holds(Context, Term)` | Enumerates member terms inside a context term and unifies each member with `Term`. |
440
459
  | `holds(Context, Name, Args)` | Enumerates context members of any arity, exposing each member as atom constant `Name` plus a proper argument list `Args`. |
460
+ | `functor(Term, Name, Arity)` | Decomposes a non-variable term into its name and arity. |
461
+ | `arg(Index, Term, Arg)` | Extracts the 1-based argument of a compound term. |
462
+ | `compound_name_arguments(Term, Name, Args)` | Decomposes a compound term or constructs one from an atom name and proper argument list. |
441
463
 
442
464
  Example:
443
465
 
444
466
  ```prolog
445
467
  holds((name(alice, "Alice"), knows(alice, bob)), name(S, O)).
446
468
  holds((ready, name(alice, "Alice"), route(alice, bob, 7)), Name, Args).
469
+ functor(route(alice, bob, 7), route, 3).
470
+ arg(2, route(alice, bob, 7), bob).
471
+ compound_name_arguments(Term, route, [alice, bob, 7]).
447
472
  ```
448
473
 
449
474
  The first goal can yield `holds((name(alice, "Alice"), knows(alice, bob)), name(alice, "Alice")).` The second can yield `holds((ready, name(alice, "Alice"), route(alice, bob, 7)), ready, []).`, `holds((ready, name(alice, "Alice"), route(alice, bob, 7)), name, [alice, "Alice"]).`, and `holds((ready, name(alice, "Alice"), route(alice, bob, 7)), route, [alice, bob, 7]).`
@@ -458,6 +483,7 @@ The N3 example [`context-schema-audit.n3`](../examples/context-schema-audit.n3)
458
483
  |---|---|
459
484
  | `not(Goal)` | Negation as failure. Succeeds when `Goal` has no solution. |
460
485
  | `once(Goal)` | Succeeds with at most the first solution of `Goal`. |
486
+ | `forall(Generator, Test)` | Succeeds when every solution of `Generator` also satisfies `Test`; succeeds vacuously when `Generator` has no solutions. |
461
487
 
462
488
  ## 10. Extension built-ins
463
489
 
@@ -106,9 +106,9 @@ scores_for([Disease|RestDiseases], [Score|RestScores]) :-
106
106
  score(Disease, Score),
107
107
  scores_for(RestDiseases, RestScores).
108
108
 
109
- sum_list([], 0.0).
110
- sum_list([Value|Rest], Sum) :-
111
- sum_list(Rest, TailSum),
109
+ score_sum([], 0.0).
110
+ score_sum([Value|Rest], Sum) :-
111
+ score_sum(Rest, TailSum),
112
112
  add(Value, TailSum, Sum).
113
113
 
114
114
  normalize_scores([], _Total, []).
@@ -159,7 +159,7 @@ scores(case, Scores) :-
159
159
  scores_for(Diseases, Scores).
160
160
  evidenceTotal(case, Total) :-
161
161
  scores(case, Scores),
162
- sum_list(Scores, Total).
162
+ score_sum(Scores, Total).
163
163
  posteriors(case, Posteriors) :-
164
164
  scores(case, Scores),
165
165
  evidenceTotal(case, Total),
@@ -0,0 +1,5 @@
1
+ report(normalized_name, "ada lovelace").
2
+ report(unique_tags, ["logic", "math", "programming"]).
3
+ report(tag_label, "logic / math / programming").
4
+ report(score_summary, summary(42, 21, 6.4807406984078604)).
5
+ report(window, [13, 21]).
@@ -0,0 +1,6 @@
1
+ report(shape, shape(edge, 3)).
2
+ report(second_argument, b).
3
+ report(parts, parts(edge, [a, b, 3])).
4
+ report(rebuilt, edge(c, d, 5)).
5
+ report(rendered, "edge(a, [b, c])").
6
+ report(all_weights_positive, yes).
@@ -0,0 +1,32 @@
1
+ % Reusable builtin tour: normalize text, summarize lists, and compute numeric values.
2
+ materialize(report, 2).
3
+
4
+ name_raw(" Ada Lovelace ").
5
+ tag_csv("logic,math,logic,programming").
6
+ scores([8, 13, 21]).
7
+
8
+ report(normalized_name, Name) :-
9
+ name_raw(Raw),
10
+ trim(Raw, Trimmed),
11
+ lowercase(Trimmed, Name).
12
+
13
+ report(unique_tags, Tags) :-
14
+ tag_csv(Csv),
15
+ split(Csv, ",", Parts),
16
+ list_to_set(Parts, Tags).
17
+
18
+ report(tag_label, Label) :-
19
+ tag_csv(Csv),
20
+ split(Csv, ",", Parts),
21
+ list_to_set(Parts, Tags),
22
+ join(Tags, " / ", Label).
23
+
24
+ report(score_summary, summary(Total, Peak, RootTotal)) :-
25
+ scores(Scores),
26
+ sum_list(Scores, Total),
27
+ max_list(Scores, Peak),
28
+ sqrt(Total, RootTotal).
29
+
30
+ report(window, Slice) :-
31
+ scores(Scores),
32
+ slice(1, 2, Scores, Slice).
@@ -0,0 +1,23 @@
1
+ % Term tools: inspect and build structured terms, then validate all facts with forall/2.
2
+ materialize(report, 2).
3
+
4
+ edge(a, b, 3).
5
+ edge(b, c, 4).
6
+
7
+ report(shape, shape(Name, Arity)) :-
8
+ functor(edge(a, b, 3), Name, Arity).
9
+
10
+ report(second_argument, Node) :-
11
+ arg(2, edge(a, b, 3), Node).
12
+
13
+ report(parts, parts(Name, Args)) :-
14
+ compound_name_arguments(edge(a, b, 3), Name, Args).
15
+
16
+ report(rebuilt, Term) :-
17
+ compound_name_arguments(Term, edge, [c, d, 5]).
18
+
19
+ report(rendered, Text) :-
20
+ term_string(edge(a, [b, c]), Text).
21
+
22
+ report(all_weights_positive, yes) :-
23
+ forall(edge(_From, _To, Weight), gt(Weight, 0)).
@@ -2,8 +2,8 @@
2
2
  // The code keeps BigInt paths where possible so large eyelang integers remain exact.
3
3
  import { compareIntegerText, deref, isDecimalInteger, lexicalValue, numberTerm, numberTextFromDouble, parseFiniteNumber, unify } from '../term.js';
4
4
 
5
- const unaryNames = ['neg', 'abs', 'sin', 'cos', 'asin', 'acos', 'rounded', 'log'];
6
- const binaryNames = ['add', 'sub', 'mul', 'div', 'mod', 'min', 'pow'];
5
+ const unaryNames = ['neg', 'abs', 'sin', 'cos', 'tan', 'asin', 'acos', 'sqrt', 'floor', 'ceiling', 'trunc', 'rounded', 'exp', 'log'];
6
+ const binaryNames = ['add', 'sub', 'mul', 'div', 'mod', 'min', 'max', 'pow', 'atan2'];
7
7
  const compareNames = ['lt', 'gt', 'le', 'ge'];
8
8
 
9
9
  export const arithmeticBuiltins = {
@@ -22,9 +22,11 @@ function unary(name) {
22
22
  const text = lexicalValue(goal.args[0], env);
23
23
  if (text == null) return;
24
24
  let out = null;
25
- if ((name === 'neg' || name === 'abs') && isDecimalInteger(text)) {
25
+ if ((name === 'neg' || name === 'abs' || name === 'floor' || name === 'ceiling' || name === 'trunc' || name === 'rounded') && isDecimalInteger(text)) {
26
26
  const value = BigInt(text);
27
- out = (name === 'neg' ? -value : value < 0n ? -value : value).toString();
27
+ if (name === 'neg') out = (-value).toString();
28
+ else if (name === 'abs') out = (value < 0n ? -value : value).toString();
29
+ else out = value.toString();
28
30
  } else {
29
31
  const input = parseFiniteNumber(text);
30
32
  if (input == null) return;
@@ -33,14 +35,22 @@ function unary(name) {
33
35
  else if (name === 'abs') value = Math.abs(input);
34
36
  else if (name === 'sin') value = Math.sin(input);
35
37
  else if (name === 'cos') value = Math.cos(input);
38
+ else if (name === 'tan') value = Math.tan(input);
36
39
  else if (name === 'asin') value = Math.asin(input);
37
40
  else if (name === 'acos') value = Math.acos(input);
41
+ else if (name === 'sqrt') { if (input < 0) return; value = Math.sqrt(input); }
42
+ else if (name === 'floor') value = Math.floor(input);
43
+ else if (name === 'ceiling') value = Math.ceil(input);
44
+ else if (name === 'trunc') value = Math.trunc(input);
38
45
  else if (name === 'rounded') value = Math.round(input);
46
+ else if (name === 'exp') value = Math.exp(input);
39
47
  else if (name === 'log') {
40
48
  if (input <= 0) return;
41
49
  value = logCompat(input);
42
50
  }
43
- out = name === 'rounded' ? String(Math.trunc(value)) : numberTextFromDouble(value);
51
+ out = (name === 'floor' || name === 'ceiling' || name === 'trunc' || name === 'rounded')
52
+ ? String(Math.trunc(value))
53
+ : numberTextFromDouble(value);
44
54
  }
45
55
  const next = env.clone();
46
56
  if (out != null && unify(goal.args[1], numberTerm(out), next)) yield next;
@@ -53,7 +63,7 @@ function binary(name) {
53
63
  const rightText = lexicalValue(goal.args[1], env);
54
64
  if (leftText == null || rightText == null) return;
55
65
  let out = null;
56
- if (isDecimalInteger(leftText) && isDecimalInteger(rightText) && name !== 'mod') {
66
+ if (isDecimalInteger(leftText) && isDecimalInteger(rightText) && name !== 'mod' && name !== 'atan2') {
57
67
  const a = BigInt(leftText);
58
68
  const b = BigInt(rightText);
59
69
  if (name === 'add') out = (a + b).toString();
@@ -61,6 +71,7 @@ function binary(name) {
61
71
  else if (name === 'mul') out = (a * b).toString();
62
72
  else if (name === 'div') { if (b === 0n) return; out = (a / b).toString(); }
63
73
  else if (name === 'min') out = (a <= b ? a : b).toString();
74
+ else if (name === 'max') out = (a >= b ? a : b).toString();
64
75
  else if (name === 'pow') { if (b < 0n) return; out = (a ** b).toString(); }
65
76
  } else if (name === 'mod') {
66
77
  if (!isDecimalInteger(leftText) || !isDecimalInteger(rightText)) return;
@@ -77,6 +88,8 @@ function binary(name) {
77
88
  else if (name === 'div') { if (b === 0) return; value = a / b; }
78
89
  else if (name === 'pow') value = Math.pow(a, b);
79
90
  else if (name === 'min') value = Math.min(a, b);
91
+ else if (name === 'max') value = Math.max(a, b);
92
+ else if (name === 'atan2') value = Math.atan2(a, b);
80
93
  out = numberTextFromDouble(value);
81
94
  }
82
95
  const next = env.clone();
@@ -3,6 +3,7 @@ export const controlBuiltins = {
3
3
  register(registry) {
4
4
  registry.add('not', 1, notBuiltin);
5
5
  registry.add('once', 1, onceBuiltin);
6
+ registry.add('forall', 2, forallBuiltin);
6
7
  }
7
8
  };
8
9
 
@@ -20,3 +21,14 @@ function* onceBuiltin({ solver, goal, env }) {
20
21
  break;
21
22
  }
22
23
  }
24
+
25
+ function* forallBuiltin({ solver, goal, env }) {
26
+ const generator = solver.cloneForInnerGoal(10000000);
27
+ for (const answerEnv of generator.solve([goal.args[0]], env.clone(), 0)) {
28
+ const checker = solver.cloneForInnerGoal(1);
29
+ let ok = false;
30
+ for (const _ of checker.solve([goal.args[1]], answerEnv.clone(), 0)) { ok = true; break; }
31
+ if (!ok) return;
32
+ }
33
+ yield env;
34
+ }
@@ -1,23 +1,62 @@
1
- // List builtins for proper lists, selection, membership, sorting, and indexing.
1
+ // List builtins for proper lists, selection, membership, sorting, indexing, slicing, and summaries.
2
2
  // Several predicates support both checking and generation, so the argument modes are handled explicitly.
3
- import { compareTerms, copyResolved, deref, isCons, lexicalValue, listFromItems, numberTerm, properListItems, unify } from '../term.js';
3
+ import {
4
+ compareTerms,
5
+ copyResolved,
6
+ deref,
7
+ isDecimalInteger,
8
+ isCons,
9
+ lexicalValue,
10
+ listFromItems,
11
+ numberTerm,
12
+ numberTextFromDouble,
13
+ parseFiniteNumber,
14
+ properListItems,
15
+ unify,
16
+ } from '../term.js';
4
17
 
5
18
  export const listBuiltins = {
6
19
  register(registry) {
7
20
  registry.add('append', 3, append);
8
21
  registry.add('nth0', 3, nth0);
9
22
  registry.add('set_nth0', 4, setNth0, { deterministic: true });
10
- registry.add('rest', 2, rest, { deterministic: true });
23
+ registry.add('head', 2, head, { deterministic: true, fallbackWhenNotReady: true, ready: firstConsReady });
24
+ registry.add('rest', 2, rest, { deterministic: true, fallbackWhenNotReady: true, ready: firstConsReady });
25
+ registry.add('last', 2, last, { deterministic: true, fallbackWhenNotReady: true, ready: firstProperListReady });
26
+ registry.add('take', 3, take, { deterministic: true, fallbackWhenNotReady: true, ready: countAndListReady });
27
+ registry.add('drop', 3, drop, { deterministic: true, fallbackWhenNotReady: true, ready: countAndListReady });
28
+ registry.add('slice', 4, slice, { deterministic: true, fallbackWhenNotReady: true, ready: sliceReady });
11
29
  registry.add('member', 2, member);
12
30
  registry.add('select', 3, select);
13
31
  registry.add('not_member', 2, notMember, { deterministic: true });
14
32
  registry.add('reverse', 2, reverse, { deterministic: true });
15
33
  registry.add('length', 2, lengthBuiltin, { deterministic: true });
34
+ registry.add('sum_list', 2, sumList, { deterministic: true, fallbackWhenNotReady: true, ready: firstProperListReady });
35
+ registry.add('min_list', 2, minList, { deterministic: true, fallbackWhenNotReady: true, ready: firstProperListReady });
36
+ registry.add('max_list', 2, maxList, { deterministic: true, fallbackWhenNotReady: true, ready: firstProperListReady });
37
+ registry.add('list_to_set', 2, listToSet, { deterministic: true, fallbackWhenNotReady: true, ready: firstProperListReady });
16
38
  registry.add('sort', 2, sortBuiltin, { deterministic: true });
17
39
  }
18
40
  };
19
41
 
20
42
 
43
+
44
+ function firstConsReady(goal, env) {
45
+ return isCons(deref(goal.args[0], env));
46
+ }
47
+
48
+ function firstProperListReady(goal, env) {
49
+ return properListItems(goal.args[0], env) !== null;
50
+ }
51
+
52
+ function countAndListReady(goal, env) {
53
+ return safeIndex(goal.args[0], env) !== null && properListItems(goal.args[1], env) !== null;
54
+ }
55
+
56
+ function sliceReady(goal, env) {
57
+ return safeIndex(goal.args[0], env) !== null && safeIndex(goal.args[1], env) !== null && properListItems(goal.args[2], env) !== null;
58
+ }
59
+
21
60
  function listFromItemsExcept(items, skip) {
22
61
  const copy = new Array(items.length - 1);
23
62
  for (let i = 0, j = 0; i < items.length; i++) if (i !== skip) copy[j++] = items[i];
@@ -62,10 +101,8 @@ function* nth0({ goal, env }) {
62
101
  }
63
102
 
64
103
  function* setNth0({ goal, env }) {
65
- const indexText = lexicalValue(goal.args[0], env);
66
- if (!/^-?\d+$/.test(indexText ?? '')) return;
67
- const index = Number(indexText);
68
- if (!Number.isSafeInteger(index) || index < 0) return;
104
+ const index = safeIndex(goal.args[0], env);
105
+ if (index == null) return;
69
106
  const items = properListItems(goal.args[1], env);
70
107
  if (!items || index >= items.length) return;
71
108
  const out = items.slice();
@@ -74,6 +111,13 @@ function* setNth0({ goal, env }) {
74
111
  if (unify(goal.args[3], listFromItems(out), next)) yield next;
75
112
  }
76
113
 
114
+ function* head({ goal, env }) {
115
+ const list = deref(goal.args[0], env);
116
+ if (!isCons(list)) return;
117
+ const next = env.clone();
118
+ if (unify(goal.args[1], list.args[0], next)) yield next;
119
+ }
120
+
77
121
  function* rest({ goal, env }) {
78
122
  const list = deref(goal.args[0], env);
79
123
  if (!isCons(list)) return;
@@ -81,6 +125,41 @@ function* rest({ goal, env }) {
81
125
  if (unify(goal.args[1], list.args[1], next)) yield next;
82
126
  }
83
127
 
128
+ function* last({ goal, env }) {
129
+ const items = properListItems(goal.args[0], env);
130
+ if (!items || items.length === 0) return;
131
+ const next = env.clone();
132
+ if (unify(goal.args[1], items[items.length - 1], next)) yield next;
133
+ }
134
+
135
+ function* take({ goal, env }) {
136
+ const count = safeIndex(goal.args[0], env);
137
+ if (count == null) return;
138
+ const items = properListItems(goal.args[1], env);
139
+ if (!items || count > items.length) return;
140
+ const next = env.clone();
141
+ if (unify(goal.args[2], listFromItems(items, 0, count), next)) yield next;
142
+ }
143
+
144
+ function* drop({ goal, env }) {
145
+ const count = safeIndex(goal.args[0], env);
146
+ if (count == null) return;
147
+ const items = properListItems(goal.args[1], env);
148
+ if (!items || count > items.length) return;
149
+ const next = env.clone();
150
+ if (unify(goal.args[2], listFromItems(items, count, items.length), next)) yield next;
151
+ }
152
+
153
+ function* slice({ goal, env }) {
154
+ const start = safeIndex(goal.args[0], env);
155
+ const count = safeIndex(goal.args[1], env);
156
+ if (start == null || count == null) return;
157
+ const items = properListItems(goal.args[2], env);
158
+ if (!items || start + count > items.length) return;
159
+ const next = env.clone();
160
+ if (unify(goal.args[3], listFromItems(items, start, start + count), next)) yield next;
161
+ }
162
+
84
163
  function* member({ goal, env }) {
85
164
  const items = properListItems(goal.args[1], env);
86
165
  if (!items) return;
@@ -132,6 +211,59 @@ function* lengthBuiltin({ goal, env }) {
132
211
  if (unify(goal.args[1], numberTerm(items.length), next)) yield next;
133
212
  }
134
213
 
214
+ function* sumList({ goal, env }) {
215
+ const items = properListItems(goal.args[0], env);
216
+ if (!items) return;
217
+ let intSum = 0n;
218
+ let floatMode = false;
219
+ let floatSum = 0;
220
+ for (const item of items) {
221
+ const text = lexicalValue(item, env);
222
+ if (text == null) return;
223
+ if (!floatMode && isDecimalInteger(text)) intSum += BigInt(text);
224
+ else {
225
+ const value = parseFiniteNumber(text);
226
+ if (value == null) return;
227
+ if (!floatMode) { floatSum = Number(intSum); floatMode = true; }
228
+ floatSum += value;
229
+ }
230
+ }
231
+ const out = floatMode ? numberTextFromDouble(floatSum) : intSum.toString();
232
+ const next = env.clone();
233
+ if (unify(goal.args[1], numberTerm(out), next)) yield next;
234
+ }
235
+
236
+ function* minList({ goal, env }) {
237
+ yield* minMaxList(goal, env, true);
238
+ }
239
+
240
+ function* maxList({ goal, env }) {
241
+ yield* minMaxList(goal, env, false);
242
+ }
243
+
244
+ function* minMaxList(goal, env, wantMin) {
245
+ const items = properListItems(goal.args[0], env);
246
+ if (!items || items.length === 0) return;
247
+ let best = copyResolved(items[0], env);
248
+ for (let i = 1; i < items.length; i++) {
249
+ const item = copyResolved(items[i], env);
250
+ const cmp = compareTerms(item, best);
251
+ if ((wantMin && cmp < 0) || (!wantMin && cmp > 0)) best = item;
252
+ }
253
+ const next = env.clone();
254
+ if (unify(goal.args[1], best, next)) yield next;
255
+ }
256
+
257
+ function* listToSet({ goal, env }) {
258
+ const items = properListItems(goal.args[0], env);
259
+ if (!items) return;
260
+ const unique = [];
261
+ for (const item of items.map((entry) => copyResolved(entry, env))) {
262
+ if (!unique.some((seen) => compareTerms(seen, item) === 0)) unique.push(item);
263
+ }
264
+ const next = env.clone();
265
+ if (unify(goal.args[1], listFromItems(unique), next)) yield next;
266
+ }
135
267
 
136
268
  function* sortBuiltin({ goal, env }) {
137
269
  const items = properListItems(goal.args[0], env);
@@ -142,3 +274,10 @@ function* sortBuiltin({ goal, env }) {
142
274
  const next = env.clone();
143
275
  if (unify(goal.args[1], listFromItems(unique), next)) yield next;
144
276
  }
277
+
278
+ function safeIndex(term, env) {
279
+ const text = lexicalValue(term, env);
280
+ if (!/^-?\d+$/.test(text ?? '')) return null;
281
+ const index = Number(text);
282
+ return Number.isSafeInteger(index) && index >= 0 ? index : null;
283
+ }