Skip to content

Regex compilation - static helpers for flattening nested branches - #24611

Merged
richardleach merged 3 commits into
Perl:bleadfrom
richardleach:trie_harder
Sep 7, 2026
Merged

Regex compilation - static helpers for flattening nested branches#24611
richardleach merged 3 commits into
Perl:bleadfrom
richardleach:trie_harder

Conversation

@richardleach

@richardleach richardleach commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

This commit attempts to flatten BRANCHes within a BRANCH, mostly for the
benefit of conversion of EXACT alternations into fewer TRIE nodes.

For example, prior to this commit, the following pattern:

/mat|mat2|(?:mat3|mat4)|mat5|(?:mat6|mat7)/

would compile to:

 1: TRIEC-EXACT[m] (35)
    <mat> (35)
    <mat2> (35)
    <mat> (13)
13: TRIE-EXACT[34] (35)
    <3>
    <4>
    <mat5> (35)
    <mat> (28)
28: TRIE-EXACT[67] (35)
    <6>
    <7>
35: END (0)

now it compiles to:

 1: EXACT <mat> (3)
 3: TRIE-EXACT[2-7] (35)
    <>
    <2>
    <3>
    <4>
    <5>
    <6>
    <7>
35: END (0)

The commit only flattens branches where the branch tails directly match.

Update: Following the suggestion to implement in S_reg, more complicated examples get flattened than was the case with the original attempt.

Notes:

1. While a person might not write that sort of branching pattern,
it could arise from combining RE pieces, otherwise programatically
generating a pattern, or (potentially) from earlier parsing of
non-explicit branches into BRANCH regnodes.

2. I'm no regex engine guru. There may be a better way / place to
do this, or the implementation might be as suboptimal as my
understanding of regcomp.
In particular, I was unsure if or how
to apply to BRANCHJ regnodes.

3. Also, any suggestions for improving the tests would be
welcomed!


  • This set of changes requires a perldelta entry, and it is included.

@richardleach
richardleach force-pushed the trie_harder branch 2 times, most recently from b1b244a to e038d5d Compare July 26, 2026 12:30
@richardleach
richardleach requested a review from demerphq August 4, 2026 20:48
@demerphq

demerphq commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Nice! I really like this. Thanks! I only gave it a quick look now, and it needs @khwilliamson to review as well, but it makes sense to me at a high level for sure.

I am curious why the flattening isn't recursive. Is it performance reasons? Or keeping track of the number of items involved? Off the top of my head id expect that study_chunk (Which is effectively depth first) would find the leafmost nested caste, linearize them, and then repeat the process as it returned back up the tree, flattening them all.

This really reminds me of something that we do (and which @khwilliamson rewrote IIRC), which was gathering adjacent single char EXACTish nodes into single multi-char nodes. What you are doing there is conceptually quite similar.

I actually would say we should just flatten always, even regardless of what the trie would do.

@demerphq

demerphq commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

I had a look at this:

perl -Mre=Debug,ALL -e'/(?:foo|bar|(?:baz|bop|bing)|zoop)/'

which currently triggers the common prefix extraction, and actually turns into this:

Final program:
   1: TRIE-EXACT(JUMP)<S:1/11 W:4 L:1/4 C:11/7>[bfz] (22)
      <foo> (22)
      <bar> (22)
      <b> (10)
  10:   TRIE-EXACT<S:2/9 W:3 L:2/3 C:10/8>[aio] (22)
        <az> 
        <op> 
        <ing> 
      <zoop> (22)
  22: END (0)

which demonstrates the depth firstness of the procedure. the common prefix extraction in the TRIE code turns (?:baz|bop|bing) into b(?:az|op|ing) - and then when it returns up the recursion stack, it converts the nesting BRANCH into a TRIE, with the 'b' key. at least tying them together.

I think this depth first processing nature is going to get in the way of arbitrary flattening. It may be better to do this during the parse phase. If you look at the use re Debug=>'ALL'; mode of a pattern like the above you will see something like this:

$ perl -Mre=Debug,ALL -e'/(?:foo|bar|(?:baz|bop|bing)|zoop)/'                                                               10:23:22 [219/1248]
Assembling pattern from 1 elements                                                                                                                                                            
Compiling REx "(?:foo|bar|(?:baz|bop|bing)|zoop)"                                                                                                                                             
Starting parse and generation                                                                                                                                                                 
<(?:foo|bar|>...|   1|  reg                                                                                                                                                                   
                |    |    brnc                                                                                                                                                                
                |    |      piec                                                                                                                                                              
                |    |        atom                                                                                                                                                            
<?:foo|bar|(>...|    |          reg                                                                                                                                                           
<foo|bar|(?:>...|    |            brnc                                                                                                                                                        
                |    |              piec                                                                                                                                                      
                |    |                atom                                                                                                                                                    
<|bar|(?:baz>...|   3|            inst - BRANCH                                                                                                                                               
<bar|(?:baz|>...|   4|            brnc                                                                                                                                                        
                |   5|              piec                                                                                                                                                      
                |    |                atom                                                                                                                                                    
<|(?:baz|bop>...|   7|            tail~ BRANCH (1) -> BRANCH                                                                                                                                  
<(?:baz|bop|>...|    |            brnc                                                                                                                                                        
                |   8|              piec                                                                                                                                                      
                |    |                atom                                                                                                                                                    
<?:baz|bop|b>...|    |                  reg                                                                                                                                                   
<baz|bop|bin>...|    |                    brnc                                                                                                                                                
                |    |                      piec                                                                                                                                              
                |    |                        atom                                                                                                                                            
<|bop|bing)|>...|  10|                    inst - BRANCH                                                                                                                                       
<bop|bing)|z>...|  11|                    brnc                                                                                                                                                
                |  12|                      piec                                                                                                                                              
                |    |                        atom                                                                                                                                            
<|bing)|zoop)>  |  14|                    tail~ BRANCH (8) -> BRANCH                                                                                                                          
<bing)|zoop)>   |    |                    brnc                                                                                                                                                
                |  15|                      piec                                                                                                                                              
                |    |                        atom                                                                                                                                            
<)|zoop)>       |  17|                    tail~ BRANCH (11) -> BRANCH                                                                                                                         
                |  18|                  lsbr~ tying lastbr BRANCH (14) to ender TAIL (17) offset 3                                                                                            
                |    |                    tail~ BRANCH (14) -> TAIL                                                                                                                           
                |    |                    tsdy~ EXACT <baz> (9) -> EXACT                                                                                                                      
                |    |                        ~ attach to TAIL (17) offset to 8
                |    |                    tsdy~ EXACT <bop> (12) -> EXACT
                |    |                        ~ attach to TAIL (17) offset to 5
                |    |                    tsdy~ EXACT <bing> (15) -> EXACT
                |    |                        ~ attach to TAIL (17) offset to 2
<|zoop)>        |    |            tail~ BRANCH (4) -> BRANCH
<zoop)>         |    |            brnc   
                |  19|              piec   
                |    |                atom   
<)>             |  21|            tail~ BRANCH (7) -> BRANCH
                |  22|          lsbr~ tying lastbr BRANCH (18) to ender TAIL (21) offset 3

The output on the last line there, (and similar above) mentioning 'tying lastbr ... to ender' is the debug output of taking all the branches in an alternation and trying their tail to the right node (which cant be determined when the node is parsed) . Consider /(A|B)C/ it doesnt know the location of the C node when it finishes the A branch, so when the alternation finishes, we know where the next node will be written and we can go back over the tails of each branch and tying them to the right place (compute the offset to jump from them to the C node). This means we can check if C is going to be a BRANCH-like node, and coupled with the bookkeeping to determine if there was a prefix, etc, we can determine if we should just merge the branches or not.

BTW, the general pattern for this is as follows, S_reg() calls S_regbranch() to parse each branch (every regex is assumed to be an alternation with one branch to start off with), when there is more than one such branch, the next point of each BRANCH node is tied to the next BRANCH in the sequence, and S_reg() keeps track of the first node in the sequence. When S_reg() encounters a close paren (or pattern end) and it has a set of branches waiting, it ties their end branch to the subsequent node.

The point being the code is actually probably structured petty well to move the flattening logic to parse time which in turn would make it more independent from the trie logic and make the flattening arbitrarily deep.

NB. It is interesting to note that if we were doing full DFA construction all of this would just come out in the wash. We would construct the same DFA regardless.

@richardleach

Copy link
Copy Markdown
Contributor Author

The point being the code is actually probably structured petty well to move the flattening logic to parse time which in turn would make it more independent from the trie logic and make the flattening arbitrarily deep.

Thanks, that does sound like a better place to attempt this flattening. I'll have a go.

@richardleach
richardleach marked this pull request as draft August 16, 2026 21:03
@richardleach

Copy link
Copy Markdown
Contributor Author

The point being the code is actually probably structured petty well to move the flattening logic to parse time which in turn would make it more independent from the trie logic and make the flattening arbitrarily deep.

Thanks, that does sound like a better place to attempt this flattening. I'll have a go.

An attempt at doing this in S_reg is looking effective, but still a lot of poking, tweaking and checking to do.

$ ./perl -Ilib -Mre=Debug,ALL -e'/(?:foo|bar|(?:baz|bop|bing)|zoop)/'
Assembling pattern from 1 elements
Compiling REx "(?:foo|bar|(?:baz|bop|bing)|zoop)"
Starting parse and generation
<(?:foo|bar|>...|   1|  reg    
                |    |    brnc   
                |    |      piec   
                |    |        atom   
<?:foo|bar|(>...|    |          reg    
<foo|bar|(?:>...|    |            brnc   
                |    |              piec   
                |    |                atom   
<|bar|(?:baz>...|   3|            inst - BRANCH
<bar|(?:baz|>...|   5|            brnc   
                |   7|              piec   
                |    |                atom   
<|(?:baz|bop>...|   9|            tail~ BRANCH (buf:0/0) (1) -> BRANCH
<(?:baz|bop|>...|    |            brnc   
                |  11|              piec   
                |    |                atom   
<?:baz|bop|b>...|    |                  reg    
<baz|bop|bin>...|    |                    brnc   
                |    |                      piec   
                |    |                        atom   
<|bop|bing)|>...|  13|                    inst - BRANCH
<bop|bing)|z>...|  15|                    brnc   
                |  17|                      piec   
                |    |                        atom   
<|bing)|zoop)>  |  19|                    tail~ BRANCH (buf:0/0) (11) -> BRANCH
<bing)|zoop)>   |    |                    brnc   
                |  21|                      piec   
                |    |                        atom   
<)|zoop)>       |  23|                    tail~ BRANCH (buf:0/0) (15) -> BRANCH
                |  24|                  lsbr~ tying lastbr BRANCH (buf:0/0) (19) to ender TAIL (23) offset 4
                |    |                    tail~ BRANCH (buf:0/0) (19) -> TAIL
                |    |                    tsdy~ EXACT <baz> (13) -> EXACT
                |    |                        ~ attach to TAIL (23) offset to 10
                |    |                    tsdy~ EXACT <bop> (17) -> EXACT
                |    |                        ~ attach to TAIL (23) offset to 6
                |    |                    tsdy~ EXACT <bing> (21) -> EXACT
                |    |                        ~ attach to TAIL (23) offset to 2
<|zoop)>        |    |            tail~ BRANCH (buf:0/0) (5) -> BRANCH
<zoop)>         |    |            brnc   
                |  26|              piec   
                |    |                atom   
<)>             |  28|            tail~ BRANCH (buf:0/0) (9) -> BRANCH
                |  29|          lsbr~ tying lastbr BRANCH (buf:0/0) (24) to ender TAIL (28) offset 4
                |    |            tail~ BRANCH (buf:0/0) (24) -> TAIL
                |    |            tsdy~ EXACT <foo> (3) -> EXACT
                |    |                ~ attach to TAIL (28) offset to 25
                |    |            tsdy~ EXACT <bar> (7) -> EXACT
                |    |                ~ attach to TAIL (28) offset to 21
                |    |            tsdy~ BRANCH (buf:0/0) (11) -> END
                |    |                ~ BRANCH (buf:0/0) (15) -> END
                |    |                ~ BRANCH (buf:0/0) (19) -> END
                |    |                ~ TAIL (23) -> END
                |    |                ~ attach to TAIL (28) offset to 5
                |    |            tsdy~ EXACT <zoop> (26) -> EXACT
                |    |                ~ attach to TAIL (28) offset to 2
                |    |          fltn~ EXACT <baz> (brnc 13) attach to TAIL (28)
                |    |          fltn~ EXACT <bop> (brnc 17) attach to TAIL (28)
                |    |          fltn~ EXACT <bing> (brnc 21) attach to TAIL (28)
                |    |          fltn~ 3 inner brncs (baz|bop|...) lifted (with 3 brncs)
<>              |  30|  lsbr~ tying lastbr BRANCH (buf:0/0) (1) to ender END (29) offset 28
                |    |    tail~ BRANCH (buf:0/0) (1)  
                |    |        ~ BRANCH (buf:0/0) (5)  
                |    |        ~ BRANCH (buf:0/0) (11)  
                |    |        ~ BRANCH (buf:0/0) (15)  
                |    |        ~ BRANCH (buf:0/0) (19)  
                |    |        ~ BRANCH (buf:0/0) (24)  
                |    |        ~ TAIL (28) -> END

...


Final program:
   1: TRIEC-EXACT<S:1/17 W:6 L:3/4 C:20/10>[bfz] (29)
      <foo> 
      <bar> 
      <baz> 
      <bop> 
      <bing> 
      <zoop> 
  29: END (0)
stclass AHOCORASICKC-EXACT<S:1/17 W:6 L:3/4 C:20/10>[bfz] minlen 3 

@richardleach

Copy link
Copy Markdown
Contributor Author

Still could do with a bit of refinement, but I've pushed for CI and any comments at this stage.

@richardleach richardleach changed the title Perl_study_chunk - static helpers for flattening nested branches Regex compilation - static helpers for flattening nested branches Aug 23, 2026
@richardleach
richardleach force-pushed the trie_harder branch 3 times, most recently from a3127c4 to 4e79c8b Compare August 26, 2026 22:28
@richardleach
richardleach marked this pull request as ready for review September 5, 2026 00:00
@richardleach
richardleach marked this pull request as draft September 5, 2026 00:20
@richardleach
richardleach force-pushed the trie_harder branch 2 times, most recently from 107ce17 to eb49208 Compare September 5, 2026 20:32
This commit attempts to flatten BRANCHes within a BRANCH, mostly for the
benefit of conversion of EXACT alternations into fewer TRIE nodes.

For example, prior to this commit, the following pattern:

    /mat|mat2|(?:mat3|mat4)|mat5|(?:mat6|mat7)/

would compile to:

     1: TRIEC-EXACT[m] (35)
        <mat> (35)
        <mat2> (35)
        <mat> (13)
    13: TRIE-EXACT[34] (35)
        <3>
        <4>
        <mat5> (35)
        <mat> (28)
    28: TRIE-EXACT[67] (35)
        <6>
        <7>
    35: END (0)

now it compiles to:

     1: EXACT <mat> (3)
     3: TRIE-EXACT[2-7] (35)
        <>
        <2>
        <3>
        <4>
        <5>
        <6>
        <7>
    35: END (0)

The commit only flattens branches where the branch tails directly match.

Exactly when and to what extent inner branches get flattened may change
as this behaviour is further refined.
@richardleach
richardleach marked this pull request as ready for review September 6, 2026 14:40
@richardleach
richardleach merged commit 4155d85 into Perl:blead Sep 7, 2026
33 checks passed
@richardleach
richardleach deleted the trie_harder branch September 7, 2026 20:52
@demerphq

demerphq commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Thanks a lot @richardleach .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants