1414namespace RegexParser \Tests \Integration ;
1515
1616use PHPUnit \Framework \Attributes \DataProvider ;
17+ use PHPUnit \Framework \Attributes \DoesNotPerformAssertions ;
1718use PHPUnit \Framework \TestCase ;
1819use RegexParser \NodeVisitor \CompilerNodeVisitor ;
1920use RegexParser \Regex ;
2223 * Tests that the compiler correctly reconstructs the parsed AST back into a regex string
2324 * that is semantically equivalent to the input.
2425 */
25- final class ReverseCompilerTest extends TestCase
26+ class ReverseCompilerTest extends TestCase
2627{
2728 private Regex $ regexService ;
2829
@@ -39,31 +40,40 @@ public function test_round_trip_compilation(string $originalPattern): void
3940 $ recompiled = $ ast ->accept ($ compiler );
4041
4142 // 1. The recompiled regex must be valid
42- $ this ->assertNotFalse (
43- @preg_match ($ recompiled , '' ),
44- "Recompiled regex ' $ recompiled' from ' $ originalPattern' is invalid. " ,
45- );
43+ // We suppress errors because we want to catch the false return value
44+ $ isValid = @preg_match ($ recompiled , '' );
45+
46+ if ($ isValid === false ) {
47+ $ error = preg_last_error ();
48+ $ msg = match ($ error ) {
49+ PREG_INTERNAL_ERROR => 'PREG_INTERNAL_ERROR ' ,
50+ PREG_BACKTRACK_LIMIT_ERROR => 'PREG_BACKTRACK_LIMIT_ERROR ' ,
51+ PREG_RECURSION_LIMIT_ERROR => 'PREG_RECURSION_LIMIT_ERROR ' ,
52+ PREG_BAD_UTF8_ERROR => 'PREG_BAD_UTF8_ERROR ' ,
53+ PREG_BAD_UTF8_OFFSET_ERROR => 'PREG_BAD_UTF8_OFFSET_ERROR ' ,
54+ PREG_JIT_STACKLIMIT_ERROR => 'PREG_JIT_STACKLIMIT_ERROR ' ,
55+ default => 'Unknown Error '
56+ };
57+ $ this ->fail ("Recompiled regex ' $ recompiled' from ' $ originalPattern' is invalid ( $ msg). " );
58+ }
4659
4760 // 2. Generate a matching sample from the original
4861 // This proves that the recompiled regex matches what the original matched.
4962 try {
63+ // Some recursive/complex patterns might hit generator limits, so we wrap in try/catch
5064 $ sample = $ this ->regexService ->generate ($ originalPattern );
5165
52- if ('' !== $ sample ) {
53- $ this ->assertMatchesRegularExpression (
66+ if ($ sample !== '' ) {
67+ $ this ->assertMatchesRegularExpression (
5468 $ recompiled ,
5569 $ sample ,
56- "Recompiled regex ' $ recompiled' failed to match sample ' $ sample' generated from ' $ originalPattern' " ,
70+ "Recompiled regex ' $ recompiled' failed to match sample ' $ sample' generated from ' $ originalPattern' "
5771 );
5872 }
59- } catch (\Exception ) {
60- // Some patterns (like assertions) don't generate samples easily, skip sample test
73+ } catch (\Exception $ e ) {
74+ // If generation fails (e.g. infinite recursion), we skip the sample match test
75+ // but the validity test above is still valuable.
6176 }
62-
63- // 3. If the optimizer is skipped, simple patterns should match exactly or be very close
64- // (ignoring insignificant differences handled by the compiler like escape chars)
65- // Note: We don't assert strict string equality because the compiler might normalize things
66- // e.g. \p{L} vs \pL, or escaping / inside / delimiters.
6777 }
6878
6979 public static function providePatterns (): \Iterator
@@ -95,10 +105,10 @@ public static function providePatterns(): \Iterator
95105 yield ['/\p{L}+/u ' ];
96106 yield ['/\o{101}/ ' ];
97107
98- // Conditionals and Subroutines
99- yield ['/(?(1)yes|no)/ ' ];
100- yield ['/(?R)/ ' ];
101- yield ['/(?&name)/ ' ];
108+ // Conditionals and Subroutines (FIXED: Added context so they are valid PCRE)
109+ yield ['/(a)( ?(1)yes|no)/ ' ]; // Defined group 1
110+ yield ['/a (?R)? / ' ]; // Optional recursion to avoid infinite loop on empty match
111+ yield ['/(?<name>a)(? &name)/ ' ]; // Defined named group
102112
103113 // Complex Real world
104114 yield ['/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ ' ];
0 commit comments