微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

为什么在这个例子中尝试不触发回溯

如何解决为什么在这个例子中尝试不触发回溯

我正在尝试在 Haskell 中使用 parsec 编写解析器,特别是回溯的工作原理。

采用以下简单的解析器:

import Text.Parsec

type Parser = Parsec String () String

parseConst :: Parser
parseConst =  do {
    x <- many digit;
    return $ read x
}

parseAdd :: Parser
parseAdd = do {
    l <- parseExp;
    char '+';
    r <- parseExp;
    return $ l <> "+" <> r
}

parseExp :: Parser
parseExp = try parseConst <|> parseAdd

pp :: Parser
pp = parseExp <* eof

test = parse pp "" "1+1"

test 有价值

Left (line 1,column 2):
unexpected '+'
expecting digit or end of input

在我看来,这应该会成功,因为我在 try 的定义中使用了 parseConst 上的 parseExp 组合子。

我错过了什么?我也对如何自己调试的指针很感兴趣,我尝试使用 parserTraced 这只是让我得出结论,它确实不是回溯。

附注。 我知道这是一种编写表达式解析器的糟糕方法,但我想了解为什么它不起作用。

解决方法

这里有很多问题。

首先,parseConst 永远无法正常工作。类型说它必须产生一个 String,所以 read :: String -> String。该特定的 Read 实例要求输入是带引号的字符串,因此如果您尝试评估 read,则将 0 个或多个数字字符传递给 error 总是会导致调用 parseConst它产生的价值。

其次,some 可以成功匹配零个字符。我想你可能想要 many 而不是 (<|>)。如果遇到不以数字开头的输入,这将使其实际上失败。

第三,(a <* c) <|> (b <* c) 与您想的不一样。您可能认为 (a <|> b) <* c 可以与 try 互换,但事实并非如此。也无法将 (<|>) 放入并使其相同。问题是 (a <|> b) <* c 提交到任何成功的分支,如果有的话。在 a 中,如果 b 匹配,则以后无法回溯并在那里尝试 try。不管你如何吊打 (<|>),它都无法撤销 a 承诺给 (a <* c) <|> (b <* c) 的事实。相比之下,a 不会提交,直到 cbc(try parseConst <|> parseAdd) <* eof 与输入匹配。

这就是您遇到的情况。经过一些内联后,您有 parseConst。由于 parseAdd 总是会成功(请参阅第二个问题),因此即使 eof 失败,也永远不会尝试 parseConst。因此,在 (<|>) 消耗零个或多个前导数字后,解析将失败,除非这是输入的结尾。解决这个问题本质上需要仔细规划您的语法,以便在本地提交任何 (<|>) 使用都是安全的。也就是说,每个分支的内容不能以一种只能通过语法的后面部分来消除歧义的方式重叠。

请注意,if(isGrounded) { moveSpeed = isSprinting ? 10f // sprinting on ground : 6f; // walking on ground } else { moveSpeed = isSprinting ? 3f // sprinting in the air : 2f; // moving in the air } 的这种令人不快的行为是 parsec 系列库的工作方式,而不是 Haskell 中所有解析器库的工作方式。其他库在没有 parsec 系列选择的左偏差或提交行为的情况下工作。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。