Changelog

View Source

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[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, 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.