Skip to content

Perl_study_chunk - static helpers for flattening nested branches - #24611

Open
richardleach wants to merge 2 commits into
Perl:bleadfrom
richardleach:trie_harder
Open

Perl_study_chunk - static helpers for flattening nested branches#24611
richardleach wants to merge 2 commits into
Perl:bleadfrom
richardleach:trie_harder

Conversation

@richardleach

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.

It does not flatten more complicated examples, such as:

/(?:(?:frog|fan)|fog)|(?:farce|(?:forge|flambe))/

It also does not flatten some nested branches when flattening all
nested branches would overflow a TRIE node; it's currently all or
nothing.

Future enhancements might be able to go further.

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.

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.

It does not flatten more complicated examples, such as:

    /(?:(?:frog|fan)|fog)|(?:farce|(?:forge|flambe))/

It also does not flatten _some_ nested branches when flattening _all_
nested branches would overflow a TRIE node; it's currently all or
nothing.

Future enhancements might be able to go further.
@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.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants