
格言“如果一开始你没有成功,请尝试,再试一次”在模式匹配中被形式化的过程称为回溯.回溯是匹配器检查所有可能匹配的组合的方式。这篇文章使用 JMP 模式匹配器中的一些秘密功能来公开回溯过程。 下面的单个语句是一个中等复杂的模式匹配。但即使是简单的匹配(和正则表达式)也会使用回溯。
秘密 1:第 21 行的 Log() 函数在 JMP 日志窗口中产生输出。 >> before 它获取先前匹配的文本并打印它,以及源中从零开始的位置和提供的文本标签。 这与回溯无关,与调试有关。 你可以使用这个秘密来调试你自己的模式。
秘密 2:PatTest() 有三个参数,即使只使用了一个。 这都是关于回溯的。 第二个参数中的JSL在前向跟踪方向执行,第三个参数中的JSL在回溯方向执行。
秘密 3:PatMatch() 通常会尽其所能优化失败。 当它发现源字符串中没有足够的文本来匹配模式的剩余最小长度时,它就会停止。 FULLSCAN 选项关闭该优化。 对于此示例,如果没有 FULLSCAN 选项,回溯似乎会过早停止。
秘密 4:PatFail() 可用于通过强制每个匹配失败并考虑替代方案来查找所有可能的匹配。
这是代码和输出,下面有解释。
Pat Match(
"EDACGDE", // a contrived string to exercise backtracking
(
Pat Len( 1 ) >> p // match a character, store in p
+
Pat Test(
p > "B", // test
Write( "p=", p, " " ), // advance
Write( "retry\!n" ) // backtrack
)
+
Pat Arb() // skip zero or more characters
+
Pat Len( 1 ) >> q // match another, store in q
+
Pat Test(
p < q & q < "F", // test
Write( "q=", q, " " ), // advance
Write( "retry " ) // backtrack
)
) >> Log( "FOUND" ) // a debugging tool
+
Pat Fail(), // try alternatives
NULL, // no replacement
FULLSCAN // to the bitter end
);
p=E q=D 重试 q=A 重试 q=C 重试 q=G 重试 q=D 重试 q=E 重试 重试
p=D q=A 重试 q=C 重试 q=G 重试 q=D 重试 q=E
1(找到)DACGDE
重试重试
p=重试
p=C q=G 重试 q=D
3(找到)CGD
重试 q=E
3(找到)CGDE
重试重试
p=G q=D 重试 q=E 重试 重试
p=D q=E
5(找到)DE
重试重试
p=E 重试
0
解释
首先,最后的零。 那是返回码,它表示匹配失败。 比赛其实成立四个匹配,但 PatFail() 导致它寻找另一个。 这是意料之中的。
接下来是 Ps 和 Qs。 p 从 E 开始,对 >B 的测试很满意。 PatArb() 不情愿,最初什么也不跳过。 q 在 E 之后得到 D。 但是这次考试不开心; p 和“F”之间的 q 不正确。 是时候回溯了! 将 D 放入 q 的 LEN(1) 将 D 返回,并且 PatArb() 有机会做一些不同的事情。 这一次它不情愿地选择了一个角色,D。 PatLen(1) 然后将 A 赋予 q。 在第一行结束时,有两次重试;第二个是p。 下一行显示 p 获取第二个字符 D。由于模式匹配没有锚定,因此作为回溯过程的一部分,允许匹配的开头在文本中向前浮动;这次比赛继续进行,PatArb 越来越多,直到第一次成功发生。 >>Log("FOUND") 在日志中记录成功,然后 PatFail() 导致回溯继续。
如果不清楚,您可以尝试更简单的测试并使用 >>LOG("tag") 方法对它们进行检测,以了解正在发生的事情。
在这一点上,应该清楚为什么这场比赛会持续非常非常长的时间:

虽然您可以看到它不可能成功,因为 aaaa...aaa 的字符串在任何地方都没有 b,但匹配器必须通过尝试许多愚蠢的组合来发现这一点:跳过 2 个字符,然后跳过 3... 并跳过 3其次是跳过2 ...等,等等,等等...... 回溯不能修复写得不好的模式。 那个进度条并不意味着它快完成了。 (没有理由将 patarb 放在 patrepeat 中,除了看到这个对话框。 Patarb 已经匹配任意数量的字符。)
2017 年 2 月 1 日更新 - 修复新社区的格式。