作为一个副业项目,我正在实现自己的正则表达式解析器/编译器。
我认为实际匹配部分是学习一些回溯单子的好时机;比如列表单子。
最终,我将我的“匹配”算法结构化如下:
基本上,我尝试在每个术语上进行匹配,但即使匹配失败,它仍然会改变状态!因此,在失败之间需要手动倒回状态。这是由于
我认为实际匹配部分是学习一些回溯单子的好时机;比如列表单子。
最终,我将我的“匹配”算法结构化如下:
data Expr
= Group Int Expr
| Terms [Expr]
| OneOf [Expr]
| Repetition Expr Int (Maybe Int)
| Empty
| BackRef Int
| Wildcard
| Atom Char
| Start
| End
deriving (Show)
data RState = RState
{ groups :: Groups
, leftover :: String
} deriving Show
match :: Expr -> ListT (State RState) String
其中ListT来自Gabriel的List.Transformer库
State单子部分跟踪已捕获的组(以便在回溯引用时使用)和剩余要消费的字符串。Expr是一种表示正则表达式的数据结构,类似于AST。
总之,这很有效;我会不断递归尝试匹配,如果匹配成功,我将匹配部分作为结果返回,并相应地更改状态中的剩余部分和组,这最终在非确定性中起作用,因为它将继续尝试使用所有可能的先前匹配项在尝试处理regex的下一部分时。问题是它仅在先前结果上回溯,但不会回溯到先前的状态!因此,我的匹配代码alternative
用于匹配正则表达式中的可选链 (例如a|b*|c; 其中每个部分都是一个“term”) 看起来像这样:
match (OneOf terms) = do
st <- get
-- Backtrack the state and try again on each alternative
let try t = put st >> match t
asum (try <$> terms)
基本上,我尝试在每个术语上进行匹配,但即使匹配失败,它仍然会改变状态!因此,在失败之间需要手动倒回状态。这是由于
<|>
的 ListT 实现造成的:ListT m <|> l = ListT (do
s <- m
case s of
Nil -> next l
Cons x l' -> return (Cons x (l' <|> l)) )
我们看到它在运行基础单子并在相同的上下文中继续。
我想要类似于这样的东西:
ListT m <|> l = ListT (do
st <- get
s <- m
case s of
Nil -> put st >> next l
Cons x l' -> return (Cons x (l' <|> l)) )
...但对于所有的单子效应,我甚至不确定这是否可能。
我研究了逻辑单子作为一种可能的解决方案,但即使在阅读论文之后,我也无法确定它是否能够实现我想要的,也许可以使用ifte
?
是否有一种回溯单子,不仅可以回溯单子的结果,而且还可以回溯单子计算?我想这对于像IO这样的东西是不可能的,但从理论上讲,对于纯单子来说是可能的。我想这意味着它在一般情况下是不可能的,但是否有一种类型类可以实现这个功能以满足我的需求?
谢谢!我知道我在这里胡言乱语,感谢你帮我思考这个问题!
StateT RState [] String
中工作。这样每个分支应该有自己的状态。在ListT (State RState) String
栈中,状态在分支之间是共享的,正如你所发现的那样。 - danidiazIO
,用户执行了launchNuclearMissiles china
,你会取消对中国的导弹发射吗? - user253751