Current section
Files
Jump to
Current section
Files
CHANGELOG.md
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.1.0] - 2026-07-31
### Added
- `Xeger.random/2` -- generate a single random string matching a pattern
directly, without enumerating the full match set (much cheaper than
`Xeger.take(pattern, 1)` for a pattern with a large or unbounded match
count). Accepts `:seed` for reproducible output and the existing
`:max_repeat`/`:alphabet` options; for an unbounded quantifier with no
`:max_repeat`, each repeat beyond the quantifier's minimum is a coin
flip on whether to continue (unbounded in principle, geometrically
short in practice) rather than a uniform draw, since there's no finite
range to draw uniformly from without a cap. Raises `ArgumentError` for
a pattern with no possible matches at all.
- The `~X` sigil replaces `~G`: `~X/pattern/` now calls `random/1` and
returns one random matching string; `~X/pattern/c` and `~X/pattern/s`
still compile or stream, same as `~G` did.
### Changed
- **`:max_repeat` now defaults to unbounded instead of `5`.** `*`, `+`, and
`{m,}` are genuinely infinite by default: `Xeger.stream/2` enumerates them
lazily, one length at a time, forever, rather than silently stopping at 5
repeats. `take/3` is unaffected either way (it only ever pulls `n`
elements), but code that called `stream/2` on an unbounded pattern and
then consumed it eagerly (`Enum.to_list/1`, `Enum.count/1`, ...) without
passing `:max_repeat` will now hang instead of returning -- pass
`max_repeat: 5` explicitly to keep the old behavior.
- As part of the above, a repeated unit that can itself match the empty
string (e.g. `(a?)*`) no longer relies on `:max_repeat` to terminate: the
generator now caps that case at `len + 1` candidate repeat-counts per
target length internally, regardless of whether `:max_repeat` is set.
- The pattern parser (`Xeger.Parser`) is now generated by `mix ichor.gen`
from a declarative grammar (`priv/grammar/xeger.aether`) via
[Ichor](https://github.com/joetjen/ichor), instead of a hand-written
recursive-descent parser. The generated module
(`lib/xeger/grammar.ex`) is checked in like any other source file;
`Xeger.Parser` is now a thin adapter translating its errors to a
plain message. `Xeger.Parser.Actions` builds `Xeger.AST` nodes
directly from the grammar's parse tree.
- `ichor` (`~> 0.2.1`, the grammar compiler) is a `dev`-only,
`runtime: false` dependency -- it never ships. `ichor_runtime`
(`~> 0.1.0`, the small support library the generated parser calls at
match time, published as its own independent Hex package) is the only
new runtime dependency.
### Fixed
- `Xeger.stream/2`/`take/3` could take combinatorially long enumerating a
sequence of several fixed- or narrow-range parts at a large target
length (e.g. `Xeger.take("a+", 1, max_repeat: 50)` at length 25 -- up to
`C(49,24)` candidate splits, nearly all invalid). `distributions/3` now
prunes candidate splits by each part's own `max_len` instead of trying
every nonnegative composition of the remaining length -- this was
latent even before unbounded quantifiers became infinite by default.