【问题标题】:Can conditionals be used to pair balance group elements?可以使用条件来配对平衡组元素吗?
【发布时间】:2015-01-07 20:11:16
【问题描述】:

TL;DR:有没有办法指定条件,以便开始元素必须匹配其配对的结束元素?

示例位于on regex101.com。

=====

正则表达式中的平衡元素通常通过递归处理。这意味着可以定位嵌套的{...{...{...}...}...}。

此外,PCRE 允许 (?(DEFINE)...) 构造,它允许您定义各种模式而无需实际开始匹配。

在正则表达式中

# Define the opening and closing elements before the recursion occurs
(?(DEFINE)
  (?<open_curly>\{)
  (?<close_curly>\})
  # ... other definitions here ...

  (?<open>\g'open_curly')
  (?<close>\g'close_curly')
)

# Match the opening element
(\g'open'
  (?>
    # For recursion, don't match either the opening or closing element
    (?!\g'open'|\g'close')(?s:.)
  |
    # Recurse this captured pattern
    (?-1)
  )*

# Match the closing element
\g'close')

元素是{ 和} 字符,可以匹配诸如

{{{}}}
{ test1 { test2 { test3 { test4 } } } }

我想包含其他打开/关闭元素,例如[ 和],或--[ 和--],所以在(?(DEFINE)) 中包含这些元素:

(?<open_square>\[)
(?<close_square>\])
(?P<open_pascal>(?i:\bbegin\b))
(?P<close_pascal>(?i:\bend\b))
(?P<open_lua>--\[)
(?P<close_lua>--\])
(?<open>\g'open_curly'|\g'open_square'|\g'open_pascal'|\g'open_lua')
(?<close>\g'close_curly'|\g'close_square'|\g'close_pascal'|\g'close_lua')

这样做的错误在于将开始元素与其结束元素配对,从而允许--[ 与} 组合,这是不可取的。

有没有办法在这样的正则表达式中创建打开/关闭对?

【问题讨论】:

  • 一般情况下,正则表达式无法检测平衡元素,因为正则表达式是状态机,但您需要堆栈机。
  • @Mark 模式匹配库允许您使用递归(需要堆栈)来匹配嵌套结构。看我的回答。一些实现 (.NET) 使用平衡组,这也是一个堆栈。所以你肯定可以将嵌套结构与这些模式相匹配——只是你不能再称它们为 regular。
  • 谢谢。这对我来说是一个新信息。

标签: regex recursion pcre


【解决方案1】:

你错了。您可以通过这样的递归来实现它:

(?(DEFINE)
  (?<curly>  \{        \g<content>*? \}      )
  (?<square> \[        \g<content>*? \]      )
  (?<pascal> \bbegin\b \g<content>*? \bend\b )
  (?<lua>    --\[      \g<content>*? --\]    )

  (?<nested> \g<curly> | \g<square> | \g<pascal> | \g<lua> )

  (?<content>
    # Math non-recursive content (atomically)
    (?: (?! [{}\[\]] | \bbegin\b | \bend\b | --[\[\]] ) . )++
    # Or recurse
    | \g<nested>
  )
)

\g<nested>

Demo

这里的技巧是利用正则表达式引擎堆栈来保留匹配的开始符号的一种内存,以便在正则表达式引擎在堆栈离开时展开堆栈时需要匹配的结束符号递归。您还需要确保 catch-all . 不使用组开始/结束符号。


更简单的案例更具可读性:

(?(DEFINE)
  (?<curly>  \{ \g<content>*? \} )
  (?<square> \[ \g<content>*? \] )

  (?<nested> \g<curly> | \g<square> )

  (?<content> [^{}\[\]]++ | \g<nested> )
)

\g<nested>

Demo

注意原子组。它可以防止过度回溯。此外,这个正则表达式会立即消耗任何不能递归的东西,而无需先尝试递归。


另外,为了回答你的问题,条件在这里没有用,因为你需要一个堆栈来跟踪你需要匹配的结束符号。

某些实现 (PCRE) 允许您通过递归模式使用堆栈,而其他实现 (.NET) 通过 balancing groups 公开堆栈。但是当你有几种平衡的结构时,递归显然是赢家。

【讨论】:

  • 我很喜欢没有明确的递归调用这一事实,但它会根据这样相互引用的组进行递归。我同意,单字符元素 更加优化,因为它们不需要回溯。我很好奇为什么(?&lt;content&gt;...) 行需要重新指定要被否定的元素,而不是重用\g&lt;curly&gt;-style 构造。
  • @OnlineCop 事情是......\g 是一个显式递归,只有在这种情况下它是一个相互递归:)这就是为什么你不能重新 -使用\g 否定content 中的任何内容 - 它会递归。另外,我以可读性为代价优化了第一个案例。
【解决方案2】:

我会说用一堆命名组和
污染逻辑是没有用的 不可预测的不必要递归。

维护正确的递归需要三个主要部分(如下所列)。
要做到正确,您必须解析所有内容,因此您必须考虑到
内容和不平衡的错误。

引擎不会让您详细捕捉第一级以外的任何内容。
这意味着更容易维护可以从中提取信息的 TOP 和 CORE
您无法维护,但会进行递归。一些细微的差异,但可以看到
在下面的例子中。

任何时候核心被递归,它立即被一个人包围
一组(对)唯一分隔符。这是为了正确展开堆栈。
这个过程不能被分解(概括)。

更新
通常这个正则表达式在递归函数中被调用,每次都将 CORE 传递给它。
示例伪代码:

bool bIsOk = true;
bool RecurseCore( string core )
{  
     while( regex_search ( core, regex, match ) )
     {
          if ( match[1].success ) { print 'content' }
          else
          if ( match[2].success ) { print 'square'; bIsOk = RecurseCore( match[2].value ) }
          else
          if ( match[3].success ) { print 'curly'; bIsOk = RecurseCore( match[3].value ) }
          else
          if ( match[4].success ) { print 'pascal'; bIsOk = RecurseCore( match[4].value )  }
          else
          if ( match[5].success ) { print 'lua'; bIsOk = RecurseCore( match[5].value )   }
          else
          if ( match[6].success ) { print 'error'; bIsOk = false } // error
          if ( bIsOk == false ) { break }
     }
     return bIsOk;
 }

正则表达式:

 # //////////////////////////////////////////////////////
 # // The General Guide to 3-Part Recursive Parsing
 # // ----------------------------------------------
 # // Part 1. CONTENT
 # // Part 2. CORE
 # // Part 3. ERRORS

 (?si)                      # Dot all, no case

 (?:
      (                          # (1), Take off CONTENT
           (?&content) 
      )
   |                           # OR
      \[                         # Square's delimiter
      (                          # (2), square CORE
           (?= . )
           (?&core) 
        |  
      )
      \]                         # End-Delimiter
   |                           # OR
      \{                         # Curly's delimiter
      (                          # (3), curly CORE
           (?= . )
           (?&core) 
        |  
      )
      \}                         # End-Delimiter
   |                           # OR
      \b begin \b                # Pascal's delimiter
      (                          # (4), pascal CORE
           (?= . )
           (?&core) 
        |  
      )
      \b end \b                  # End-Delimiter
   |                           # OR  
      --\[                       # Lua's delimiter
      (                          # (5), lua CORE
           (?= . )
           (?&core) 
        |  
      )
      --\]                       # End-Delimiter
   |                           # OR
      (                          # (6), Take off Unbalanced (delimeter) ERRORS
           \b 
           (?: begin | end )
           \b 
        |  -- [\[\]] 
        |  [\[\]{}] 
      )
 )

 # ///////////////////////
 # // Subroutines
 # // ---------------

 (?(DEFINE)

      # core
      (?<core>
           (?>
                (?&content) 
             |  
                \[                         
                (?:                        # Square delimiter
                     (?= . )                    # recurse core
                     (?&core)                   
                  |  
                )
                \]
             |                           # OR
                \{
                (?:                        # Curly delimiter
                     (?= . )                    # recurse core 
                     (?&core) 
                  |  
                )
                \}      
             |                           # OR
                \b begin \b 
                (?:                        # Pascal delimiter
                     (?= . )                    # recurse core 
                     (?&core) 
                  |  
                )
                \b end \b     
             |                           # OR
                --\[
                (?:                        # Lua delimiter
                     (?= . )                    # recurse core 
                     (?&core) 
                  |  
                )
                --\]      
           )+
      )

      # content 
      (?<content>
           (?>
                (?!
                     \b 
                     (?: begin | end )
                     \b 
                  |  -- [\[\]] 
                  |  [\[\]{}] 
                )
                . 
           )+
      )

 )

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-04-29
    • 2020-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-26
    • 2022-01-17
    • 1970-01-01
    相关资源
    最近更新 更多