diff options
| author | natsuoto <[email protected]> | 2026-07-15 14:40:17 +0530 |
|---|---|---|
| committer | natsuoto <[email protected]> | 2026-07-15 14:40:17 +0530 |
| commit | 578fd671992be4f45f87648e22343dc8171282ec (patch) | |
| tree | ee66434f1991d0ecff80abe8da86cd3efdd2df34 | |
| parent | 86af31327dbff6f206a15e3172a8bedb18157c66 (diff) | |
| download | edify-578fd671992be4f45f87648e22343dc8171282ec.tar.xz edify-578fd671992be4f45f87648e22343dc8171282ec.zip | |
docs(builder): document the immutability contract with branchability example in the API reference and README
| -rw-r--r-- | README.rst | 1 | ||||
| -rw-r--r-- | docs/regex-builder/builder/index.rst | 27 | ||||
| -rw-r--r-- | edify/builder/builder.py | 10 |
3 files changed, 37 insertions, 1 deletions
@@ -246,6 +246,7 @@ That's where Edify becomes extremely useful. It allows you to create regular exp - Order matters! Quantifiers are specified before the thing they change, just like in English (e.g. ``RegexBuilder().exactly(5).digit()``). - If you make a mistake, you'll know how to fix it. Edify will guide you towards a fix if your expression is invalid. - ``subexpressions`` can be used to create meaningful, reusable components. +- Every chain method returns a new builder — the receiver is never mutated. Two extensions of the same base pattern are always independent, so reusable base builders are safe to share across modules, closures, and threads. Edify turns those complex and unwieldy regexes that appear in code reviews into something that can be read, understood, and **properly reviewed** by your peers - and maintained by anyone! diff --git a/docs/regex-builder/builder/index.rst b/docs/regex-builder/builder/index.rst index 4b1702f..d7ca44b 100644 --- a/docs/regex-builder/builder/index.rst +++ b/docs/regex-builder/builder/index.rst @@ -8,6 +8,33 @@ RegexBuilder is a class that helps you build regular expressions. It is based on - If you make a mistake, you'll know how to fix it. Edify will guide you towards a fix if your expression is invalid. - ``subexpressions`` can be used to create meaningful, reusable components. +Immutability contract +--------------------- + +Every chain method on ``RegexBuilder`` returns a **new** builder; the receiver is never mutated. Two consequences follow, and both are guaranteed by contract: + +1. **Branching is safe.** A base builder can be extended along multiple independent paths without any of them observing another's state: + + .. code-block:: python + + from edify import RegexBuilder + + base = RegexBuilder().start_of_input().exactly(3).digit() + zip5 = base.exactly(2).digit().end_of_input() + zip3 = base.end_of_input() + + assert zip5.to_regex_string() == "^\\d{3}\\d{2}$" + assert zip3.to_regex_string() == "^\\d{3}$" + assert base.to_regex_string() == "^\\d{3}" + + ``zip5`` and ``zip3`` are independent chains rooted in the same ``base``; extending one has no effect on the other or on ``base`` itself. + +2. **Reusable base patterns are safe to share.** A base pattern held in a module-level constant, imported across files, or captured in a closure cannot be corrupted by any caller. The same object can be extended, forked, or handed to ``subexpression`` any number of times. + +The same guarantee holds for :class:`edify.Pattern` — it is a ``RegexBuilder`` under the hood and inherits the contract. + +The lazy compile cache preserves the guarantee: repeated no-kwargs ``.to_regex()`` calls on the same builder instance return the same :class:`Regex` object (``builder.to_regex() is builder.to_regex()``), but a chain step or ``fork()`` produces a fresh builder with its own empty cache. + .any_char() ----------- diff --git a/edify/builder/builder.py b/edify/builder/builder.py index a727ccf..cde72d9 100644 --- a/edify/builder/builder.py +++ b/edify/builder/builder.py @@ -37,7 +37,15 @@ class RegexBuilder( TerminalsMixin, TestingMixin, ): - """A fluent, immutable, strongly-typed regex builder.""" + """A fluent, immutable, strongly-typed regex builder. + + Immutability is a hard contract: every chain method returns a *new* builder + and leaves the receiver untouched. A base builder held in a constant, closure, + or attribute can be extended along multiple independent paths without any of + them observing another's state. Repeat no-kwargs ``.to_regex()`` calls on the + same builder return the same cached :class:`Regex`; a chain step or ``fork()`` + yields a fresh builder with its own empty cache. + """ @classmethod def from_regex(cls, pattern_text: str) -> RegexBuilder: |
