好吧,您也可以使用递归正则表达式构建此解析器:
$regex = "([a-zA-Z0-9.-]+|\"([^\"\\\\]+(?1)|\\\\.(?1)|)\"|'([^'\\\\]+(?2)|\\\\.(?2)|)')s";
现在这有点长,所以让我们把它分解一下:
$identifier = '[a-zA-Z0-9.-]+';
$doubleQuotedString = "\"([^\"\\\\]+(?1)|\\\\.(?1)|)\"";
$singleQuotedString = "'([^'\\\\]+(?2)|\\\\.(?2)|)'";
$regex = "($identifier|$doubleQuotedString|$singleQuotedString)s";
那么这是如何工作的呢?好吧,标识符应该很明显......
两个带引号的子模式基本上是一样的,我们看一下单引号字符串:
'([^'\\\\]+(?2)|\\\\.(?2)|)'
实际上,这是一个引号字符,后跟一个递归子模式,然后是一个结束引号。
魔术发生在子模式中。
[^'\\\\]+(?2)
该部分基本上使用任何非引号和非转义字符。我们不在乎它们,所以吃掉它们。然后,如果我们遇到引号或反斜杠,则触发再次匹配整个子模式的尝试。
\\\\.(?2)
如果我们可以消费一个反斜杠,那么消费下一个字符(不关心它是什么),并再次递归。
最后,我们有一个空组件(如果转义字符是最后一个,或者没有转义字符)。
在@HamZa 提供的测试输入上运行它会返回相同的结果:
array(8) {
[0]=>
string(3) "foo"
[1]=>
string(13) ""bar \"baz\"""
[2]=>
string(10) "'\'quux\''"
[3]=>
string(9) "'foo"bar'"
[4]=>
string(9) ""baz'boz""
[5]=>
string(5) "hello"
[6]=>
string(16) ""regex
world\"""
[7]=>
string(18) ""escaped escape\\""
}
发生的主要区别在于效率。这种模式应该更少回溯(因为它是递归模式,所以对于格式良好的字符串应该几乎没有回溯),其中另一个正则表达式是非递归正则表达式,并且将回溯每个字符(这就是力量?
之后*
,非贪婪模式消费)。
对于短输入,这无关紧要。提供的测试用例,它们彼此相差几个百分点(误差幅度大于差异)。但是对于没有转义序列的单个长字符串:
"with a really long escape sequence match that will force a large backtrack loop"
差异很大(100 次运行):
- 递归:
float(0.00030398368835449)
- 回溯:
float(0.00055909156799316)
当然,我们可以通过大量转义序列部分失去这个优势:
"This is \" A long string \" With a\lot \of \"escape \sequences"
- 递归:
float(0.00040411949157715)
- 回溯:
float(0.00045490264892578)
但请注意,长度仍然占主导地位。这是因为回溯器在 处缩放O(n^2)
,而递归解决方案在 处缩放O(n)
。然而,由于递归模式总是需要至少递归一次,它比短字符串的回溯解决方案要慢:
"1"
- 递归:
float(0.0002598762512207)
- 回溯:
float(0.00017595291137695)
权衡似乎发生在 15 个字符左右...但是两者都足够快,除非您解析几 KB 或 MB 的数据,否则不会有任何区别...但是值得讨论...
在理智的输入上,它不会产生重大影响。但是如果你匹配的字节数超过几百个,它可能会开始显着增加......
编辑
如果您需要处理任意“裸词”(未引用的字符串),则可以将原始正则表达式更改为:
$regex = "([^\s'\"]\S*|\"([^\"\\\\]+(?1)|\\\\.(?1)|)\"|'([^'\\\\]+(?2)|\\\\.(?2)|)')s";
但是,这实际上取决于您的语法以及您是否认为命令。我建议将您期望的语法形式化...