[ 
https://issues.apache.org/jira/browse/GROOVY-12133?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18095736#comment-18095736
 ] 

ASF GitHub Bot commented on GROOVY-12133:
-----------------------------------------

testlens-app[bot] commented on PR #2669:
URL: https://github.com/apache/groovy/pull/2669#issuecomment-4952471625

   ## ✅ All tests passed ✅
   
   🏷️ Commit: 4ed84504d6de3b24fcc1b5f0f5c11358f0aad944
   ▶️ Tests:  103991 executed
   ⚪️ Checks: 31/31 completed
   
   ---
   _Learn more about TestLens at [testlens.app](https://testlens.app)._
   




> Implement findBalancedGroups utility to support Regex Balanced Groups
> ---------------------------------------------------------------------
>
>                 Key: GROOVY-12133
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12133
>             Project: Groovy
>          Issue Type: Improvement
>            Reporter: Daniel Sun
>            Priority: Major
>             Fix For: 6.0.0-beta-1
>
>
> h2. Summary
> Add a GDK utility to extract nested balanced groups from text (parentheses, 
> braces, HTML-like tags, etc.), providing a structured alternative to .NET 
> regex balancing groups for the Java/Groovy regex stack.
> h2. Motivation
> {{java.util.regex.Pattern}} has no equivalent of .NET balancing groups 
> ({{{}(?<name1-name2>…){}}}). Nested constructs are therefore awkward to 
> handle with pure Java regex and often lead to fragile or backtracking-heavy 
> patterns. Groovy users need a first-class, safe way to recover hierarchical 
> balanced spans with clear source offsets.
> h2. Solution
> Introduce {{groovy.util.regex.BalancedGroup}} and a GDK method 
> {{{}CharSequence.findBalancedGroups(...){}}}, backed by a single-pass 
> tokenizer + stack (not recursive regex). Nesting is resolved by an explicit 
> stack, so balancing itself does not introduce ReDoS; cost scales with 
> tokenizer matches and the user-supplied open/close/ignore patterns.
> h3. API
>  * {{BalancedGroup.find(text, openRegex, closeRegex[, options])}} — core 
> entry point
>  * {{CharSequence.findBalancedGroups(openRegex, closeRegex[, options])}} — 
> GDK sugar (delegates to the above)
>  * {{BalancedGroup.MatchOptions}} — immutable options ({{{}ignoreRegex{}}}, 
> {{{}includeEdges{}}}) with wither methods
>  * Result tree: {{{}matchedString{}}}, 
> {{{}start{}}}/{{{}end{}}}/{{{}length{}}}, {{{}fullStart{}}}/{{{}fullEnd{}}}, 
> {{{}children{}}}, {{{}parent{}}}, {{depth}}
> h3. Behaviour notes
>  * *Open/close* are user regexes for delimiters (must be non-empty).
>  * {*}{{includeEdges}}{*}: when {{{}false{}}}, {{matchedString}} is the 
> interior only (interval between open and close), while 
> {{{}fullStart{}}}/{{{}fullEnd{}}} still cover the full pair including 
> delimiters.
>  * {*}{{ignoreRegex}}{*}: spans to skip while scanning (e.g. string literals, 
> comments, self-closing tags).
>  * Unmatched closers are ignored. Unclosed openers are dropped at 
> end-of-input; completed groups nested inside them are promoted (“orphan 
> rescue”).
>  * Named groups {{{}OPEN{}}}, {{{}CLOSE{}}}, and {{IGNORE}} are reserved by 
> the internal tokenizer.
>  * Compiled tokenizers are cached (bounded LRU) for repeated 
> open/close/ignore triples.
> h2. Examples
> {code:groovy}
> def roots = 'Root: (A + (B * C) + (D(E)))'.findBalancedGroups(/\(/, /\)/)
> assert roots.size() == 1
> assert roots[0].matchedString == '(A + (B * C) + (D(E)))'
> assert roots[0].children*.matchedString == ['(B * C)', '(D(E))']
> def opts = BalancedGroup.MatchOptions.defaults().withIncludeEdges(false)
> assert 'func(arg1, arg2)'.findBalancedGroups(/\(/, /\)/, 
> opts)[0].matchedString == 'arg1, arg2'
> def code = 'if (true) { String s = "}"; }'
> def ignoreStrings = 
> BalancedGroup.MatchOptions.defaults().withIgnoreRegex(/"(?:\\.|[^"\\])*"/)
> assert code.findBalancedGroups(/\{/, /\}/, ignoreStrings)[0].matchedString == 
> '{ String s = "}"; }'
> {code}



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to