From 86af31327dbff6f206a15e3172a8bedb18157c66 Mon Sep 17 00:00:00 2001 From: natsuoto <279971144+natsuoto@users.noreply.github.com> Date: Wed, 15 Jul 2026 14:37:38 +0530 Subject: feat(builder): RegexBuilder.from_regex reverse parser for the common construct set --- tests/builder/reverse.test.py | 113 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 tests/builder/reverse.test.py (limited to 'tests/builder/reverse.test.py') diff --git a/tests/builder/reverse.test.py b/tests/builder/reverse.test.py new file mode 100644 index 0000000..1ed00ec --- /dev/null +++ b/tests/builder/reverse.test.py @@ -0,0 +1,113 @@ +"""Tests for :meth:`RegexBuilder.from_regex` — the reverse parser.""" + +import re + +import pytest + +from edify import RegexBuilder +from edify.builder.reverse import UnsupportedReverseParseError + + +@pytest.mark.parametrize( + ("source", "expected"), + [ + ("hello", "hello"), + ("a", "a"), + (r"\d+", r"\d+"), + (r"\d*", r"\d*"), + (r"\d?", r"\d?"), + (r"\d{3}", r"\d{3}"), + (r"\d{2,5}", r"\d{2,5}"), + (r"\d{2,}", r"\d{2,}"), + (r"\w", r"\w"), + (r"\W", r"\W"), + (r"\s", r"\s"), + (r"\S", r"\S"), + (r"\D", r"\D"), + ("[a-z]", "[a-z]"), + ("[abc]", "[abc]"), + ("^", "^"), + ("$", "$"), + (r"\b", r"\b"), + (r"\B", r"\B"), + (".", "."), + ("(?:foo)", "foo"), + ("(abc)", "(abc)"), + ("(?Pabc)", "(?Pabc)"), + ("foo|bar", "(?:foo|bar)"), + ("(?=x)y", "(?=x)y"), + ("(?!x)y", "(?!x)y"), + ("(?<=x)y", "(?<=x)y"), + ("(?abc)", + "foo|bar", + r"(?=x)\w+", + ], +) +def test_round_trip_compiled_regex_matches_the_same_inputs(source): + original = re.compile(source) + reversed_builder = RegexBuilder.from_regex(source) + reversed_compiled = reversed_builder.to_regex() + for candidate in ["", "a", "abc", "abc123", "xyz", "foo bar", "hi"]: + assert bool(original.search(candidate)) == bool(reversed_compiled.search(candidate)) + + +def test_from_regex_raises_for_unsupported_construct(): + with pytest.raises(UnsupportedReverseParseError): + RegexBuilder.from_regex(r"(a)\1") + + +def test_from_regex_raises_for_alternation_with_non_literal_branch(): + with pytest.raises(UnsupportedReverseParseError): + RegexBuilder.from_regex(r"a|\d+") + + +def test_from_regex_raises_for_unsupported_class_member_shape(): + with pytest.raises(UnsupportedReverseParseError): + RegexBuilder.from_regex(r"[\da-z_]") + + +def test_from_regex_lazy_quantifier_translates_via_lazy_variants(): + builder = RegexBuilder.from_regex(r"a+?") + assert builder.to_regex_string() == "a+?" + + +def test_from_regex_lazy_between_translates_via_lazy_variant(): + builder = RegexBuilder.from_regex(r"a{2,5}?") + assert builder.to_regex_string() == "a{2,5}?" + + +def test_from_regex_lazy_zero_or_more_translates_via_lazy_variant(): + builder = RegexBuilder.from_regex(r"a*?") + assert builder.to_regex_string() == "a*?" + + +def test_from_regex_named_group_reproduces_the_name(): + builder = RegexBuilder.from_regex("(?P[a-z]+)") + emitted = builder.to_regex_string() + assert emitted == "(?P[a-z]+)" + + +def test_unsupported_error_message_names_the_offending_construct(): + with pytest.raises(UnsupportedReverseParseError) as excinfo: + RegexBuilder.from_regex(r"(a)\1") + assert "hand-write" in str(excinfo.value) + + +def test_from_regex_produces_a_regexbuilder_not_a_pattern(): + builder = RegexBuilder.from_regex("abc") + assert isinstance(builder, RegexBuilder) -- cgit v1.2.3