使用正则表达式编写 PHP 查询解析器


Writing a PHP query parser with regular expressions

我正在尝试在PHP中编写正确的正则表达式来解析字符串(由某个用户编写)以构建请求。它可以像 :

name = 'benjo' and (surname = 'benny' or surname = 'bennie') or age = 4

稍后我将解析字符串以构建 mySQL 查询。现在,我只是想找到正确的正则表达式来将此字符串解析为如下所示的数组:

$result = array(
0 => name = 'benjo',
1 => and
2 => array(
    0 => surname = 'benny',
    1 => or,
    2 => surname = 'bennie',
    ),
3 => age = 4
);

我已经考虑过使用递归函数,我现在的正则表达式是:

"#'((([^()]+|(?R))*)')|(ou[^()])|(et[^()])#",

这当然行不通。

如果有人能帮忙,我会很高兴,我有点被困在这里了! :)啧,罗曼

让我们改变挑战! :)好的,现在让我们让它更简单一点。使用正则表达式并添加我们保持在"一级"的约束需要什么!没有嵌套的括号,只有一个级别,但仍然有很多 AND/OR......这会改变任何有利于或正则表达式的东西吗?(我真的很想避免编写我的迷你解析器,尽管这听起来很有趣......

理论上的正则表达式不够强大,无法进行括号匹配。理论正则表达式只能照顾左递归/右递归规则。中间递归规则不能用正则表达式表示(例如 <exp> -> "(" <exp> ")" )。

然而,编程语言中的正则表达式实现了允许正则表达式超越常规语法功能的功能。例如,正则表达式中的反向引用允许编写与非上下文无关语言匹配的正则表达式。但是,即使使用反向引用,仍然无法平衡括号和正则表达式。

由于 PCRE 库通过子例程调用功能支持递归正则表达式,因此在技术上可以使用正则表达式解析此类表达式。但是,除非你可以自己编写正则表达式,这意味着你了解自己在做什么,并且可以修改正则表达式以满足你的需求,否则你应该编写自己的解析器。否则,您最终将陷入无法维护的混乱。

(?(DEFINE)
  (?<string>'[^']++')
  (?<int>'b'd+'b)
  (?<sp>'s*)
  (?<key>'b'w+'b)
  (?<value>(?&string)|(?&int))
  (?<exp>(?&key) (?&sp) = (?&sp) (?&value))
  (?<logic>'b (?:and|or) 'b)
  (?<main>
    (?<token> '( (?&sp) (?&main) (?&sp) ') | (?&exp) )
    (?:
      (?&sp) (?&logic) (?&sp)
      (?&token) 
    )*
  )
)
(?:
  ^ (?&sp) (?= (?&main) (?&sp) $ )
  |
  (?!^) 'G
  (?&sp) (?&logic) (?&sp)
)
(?:
  '( (?&sp) (?<m_main>(?&main)) (?&sp) ')
  |
  (?<m_key>(?&key)) (?&sp) = (?&sp) (?<m_value>(?&value))
)

正则表达式101演示

上面的正则表达式应该与 preg_match_all 一起使用,并放置在带有 x 标志的分隔符之间(自由间距模式):/.../x

对于每个匹配项:

  • 如果捕获组m_main内容,请对内容进行另一轮匹配。
  • 否则,获取m_keym_value捕获组中的键和值。

解释

(?(DEFINE)...) 块允许您定义命名的捕获组,以便在与主模式分开的子例程调用中使用。

(?(DEFINE)
  (?<string>'[^']++')  # String literal
  (?<int>'b'd+'b)      # Integer
  (?<sp>'s*)           # Whitespaces between tokens
  (?<key>'b'w+'b)      # Field name
  (?<value>(?&string)|(?&int)) # Field value
  (?<exp>(?&key) (?&sp) = (?&sp) (?&value)) # Simple expression
  (?<logic>'b (?:and|or) 'b) # Logical operators
  (?<main>             # <token> ( <logic> <token> )*
    # A token can contain a simple expression, or open a parentheses (...)
    # When we open a parentheses, we recurse into the main pattern again
    (?<token> '( (?&sp) (?&main) (?&sp) ') | (?&exp) )
    (?:
      (?&sp) (?&logic) (?&sp)
      (?&token) 
    )*
  )
)

模式的其余部分基于此技术,以使用全局匹配操作匹配<token> ( <logic> <token> )*中的所有<token>

正则表达式的最后一部分虽然可以写成 (?&token) ,但会扩展以匹配简单表达式中的字段名称和值。

(?:
  '( (?&sp) (?<m_main>(?&main)) (?&sp) ')
  |
  (?<m_key>(?&key)) (?&sp) = (?&sp) (?<m_value>(?&value))
)