如何使用 ragel 正确实现正则表达式 /cat[s]?(\b|$)/ ?
How to implement regex /cat[s]?(\b|$)/ with ragel correclty?
我想加快我用 Go 编写的程序的速度,并将正则表达式转换为使用 ragel
的有限状态机。在转换类似于 /cat[s]?(\b|$)/
的正则表达式(它匹配字边框或输入结尾)时,我不知道如何正确匹配输入结尾,所以我做了这个解决方法:
package main
import(
"strings"
"fmt"
"unicode"
)
func Match(data []byte) bool {
data = []byte(strings.ToLower(string(data)))
%%{
machine test;
write data;
}%%
cs, p, pe, eof := 0, 0, len(data), len(data)
_ = eof
var matchPos int
%%{
main := ('cat' 's'?) @{matchPos = p};
write init;
write exec;
}%%
return checkMatch(data, matchPos+1)
}
func checkMatch(data []byte, p int) bool {
if p == len(data) {
return true
}
tail := string(data[p:])
c := []rune(tail)[0]
if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
return true
}
return false
}
func main () {
vs := []string{
"cat",
"cats",
"cat and dog",
"cats and dogs",
"caterpillar",
}
for _, v := range vs {
fmt.Printf("'%s': %v\n", v, Match([]byte(v)))
}
}
输出正确:
'cat': true
'cats': true
'cat and dog': true
'cats and dogs': true
'caterpillar': false
不过我确实认为有更好的方法。使用 ragel
处理输入结束的 "proper" 方法是什么?
处理输入结束的正确方法当然是使用 EOF 动作。并使用一般的动作,像这样(减少 Match
功能):
var matched bool
%%{
action setMatched {
matched = true
}
main := ('cat' 's'?) %/setMatched ([ \t] >setMatched);
write init;
write exec;
}%%
// Variable generated and not used by Ragel.
_ = _test_trans_actions
return matched
产生以下输出(注意添加了一个重要的测试用例):
'cat': true
'cats': true
'cat and dog': true
'cats and dogs': true
'catspaw': false
'caterpillar': false
并且像这样工作:
它添加的是 setMatched
操作,该操作由第一台机器 (cats?
) 的最终状态之一 (%/setMatched
) 中的 EOF 触发,或者在进入时触发(>setMatched
)第二个(差不多\b
,但实际上可以用内部space
机器代替)。它完全消除了 checkMatch
的需要。
我想加快我用 Go 编写的程序的速度,并将正则表达式转换为使用 ragel
的有限状态机。在转换类似于 /cat[s]?(\b|$)/
的正则表达式(它匹配字边框或输入结尾)时,我不知道如何正确匹配输入结尾,所以我做了这个解决方法:
package main
import(
"strings"
"fmt"
"unicode"
)
func Match(data []byte) bool {
data = []byte(strings.ToLower(string(data)))
%%{
machine test;
write data;
}%%
cs, p, pe, eof := 0, 0, len(data), len(data)
_ = eof
var matchPos int
%%{
main := ('cat' 's'?) @{matchPos = p};
write init;
write exec;
}%%
return checkMatch(data, matchPos+1)
}
func checkMatch(data []byte, p int) bool {
if p == len(data) {
return true
}
tail := string(data[p:])
c := []rune(tail)[0]
if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
return true
}
return false
}
func main () {
vs := []string{
"cat",
"cats",
"cat and dog",
"cats and dogs",
"caterpillar",
}
for _, v := range vs {
fmt.Printf("'%s': %v\n", v, Match([]byte(v)))
}
}
输出正确:
'cat': true
'cats': true
'cat and dog': true
'cats and dogs': true
'caterpillar': false
不过我确实认为有更好的方法。使用 ragel
处理输入结束的 "proper" 方法是什么?
处理输入结束的正确方法当然是使用 EOF 动作。并使用一般的动作,像这样(减少 Match
功能):
var matched bool
%%{
action setMatched {
matched = true
}
main := ('cat' 's'?) %/setMatched ([ \t] >setMatched);
write init;
write exec;
}%%
// Variable generated and not used by Ragel.
_ = _test_trans_actions
return matched
产生以下输出(注意添加了一个重要的测试用例):
'cat': true
'cats': true
'cat and dog': true
'cats and dogs': true
'catspaw': false
'caterpillar': false
并且像这样工作:
它添加的是 setMatched
操作,该操作由第一台机器 (cats?
) 的最终状态之一 (%/setMatched
) 中的 EOF 触发,或者在进入时触发(>setMatched
)第二个(差不多\b
,但实际上可以用内部space
机器代替)。它完全消除了 checkMatch
的需要。