如果你想同时允许,不要使用<|>
您的 program 解析器主要使用
program = do
spaces
try bindings <|> expressions
spaces >> eof
这个<|> 是选择 - 如果可以,它会选择bindings,如果失败,expressions,这不是你想要的。您想要零个或多个绑定,然后是表达式,所以让我们这样做。
可悲的是,即使这样有效,解析器的最后一行是 eof 和
首先,让我们允许零绑定,因为它们是可选的,然后让我们同时获取绑定和表达式:
bindings = many binding
program = do
spaces
bs <- bindings
es <- expressions
spaces >> eof
return (bs,es)
使用更多<?> "binding" 类型提示会更容易找到此错误,因此您可以更清楚地看到预期的内容。
endBy 不需要 many
您收到的错误消息源于该行
expressions = many (endBy expression eol)
应该是
expressions :: Parser [Expr]
expressions = endBy expression eol
endBy 与 sepBy 一样工作 - 您无需在其上使用 many,因为它已经解析了很多。
使用更强大的数据类型树会更容易找到此错误,因此:
使用try处理常用前缀
您遇到的一个难以调试的问题是当您在解析表达式时收到错误expecting space or "="。如果我们考虑这一点,我们期望= 的唯一位置是在绑定中,所以当我们给它一个表达式时,它必须是解析绑定的一部分。只有当我们的表达式以标识符开头时才会发生这种情况,就像绑定一样。
binding 看到第一个标识符并说“没关系,伙计们,我有这个”,但随后找不到 = 并给你一个错误,我们希望它回溯并让 expression 试一试.关键是我们已经使用了标识符输入,并且我们想取消使用它。 try 是正确的。
用try 封装你的binding 解析器,所以如果它失败了,我们将回到行首并交给expression。
binding = try (do
(Var id) <- identifier
_ <- char '='
spaces
exp <- expression
spaces
eol <?> "end of line"
return $ Eq id exp
<?> "binding")
重要的是,每个解析器都尽可能从匹配唯一的东西开始以避免这个问题。 (try 是回溯,因此效率低下,因此应尽可能避免。)
特别是,避免使用spaces 启动解析器,而是确保用空格完成它们。如果您愿意,您的主要program 可以以spaces 开头,因为这是唯一的选择。
在大多数作品中使用类型 - 更好的结构和可读性
我的第一条一般建议是,您可以使用更细粒度的数据类型,并且应该使用它们的类型注释您的解析器。目前,所有内容都包含在Expr 中,这意味着您只能收到有关您是否拥有Expr 或[Expr] 的错误消息。您必须将Eq 添加到Expr 的事实表明您将类型推得太远了。
通常值得为相当多的产生式制作数据类型,如果你import Control.Applicative hiding ((<|>),(<$>),many) Control.Applicative 你可以使用<$> 和<*> 这样产生式、数据类型和解析器都是相同的结构:
--<program> ::= <spaces> [<bindings>] <expressions>
data Program = Prog [Binding] [Expr]
program = spaces >> Prog <$> bindings <*> expressions
-- <expression> ::= <abstraction> | factors
data Expression = Ab Abstraction | Fa [Factor]
expression = Ab <$> abstraction <|> Fa <$> factors <?> "expression"
例如,不要对字母这样做,而是对重要的事情这样做。重要的是判断问题,但我将从标识符开始。 (您可以使用<* 或*> 在结果中不包含= 之类的语法。)
修改代码:
在重构类型和使用 Applicative 之前here
然后here