在具有共同前缀的两个解析器之间进行选择

Choosing between two parsers with a common prefix

我目前正尝试在 Parsec 中编写一个简单的解析器,但 运行 陷入有关白色的问题space:作为一个最小的例子,我有一个解析器可以解析两个字母,或者两个小写或一个大写和一个小写。我会这样做

testP :: Parser String
testP = do
    lookAhead lower
    a1 <- lower
    a2 <- lower
    return [a1,a2]
    <|> do
    a1 <- upper
    a2 <- lower
    return [a1,a2]

对于“as”或“Bs”等字符串,这可以按预期工作。现在我想在输入字符串的开头处理可能的 whitespace 。如果我这样做

testP :: Parser String
testP = do
    spaces
    lookAhead lower
    a1 <- lower
    a2 <- lower
    return [a1,a2]
    <|> do
    a1 <- upper
    a2 <- lower
    return [a1,a2]

我希望程序现在能够解析“as”和“Bs”,但对于第二个字符串,我得到一个错误“expecting space or lowercase letter”。 好的,我认为无论选择哪个选项,space 都会被解析,但显然不是这样,让我们​​在第二个选项的开头放置另一个 spaces,如下所示:

testP :: Parser String
testP = do
    spaces
    lookAhead lower
    a1 <- lower
    a2 <- lower
    return [a1,a2]
    <|> do
    spaces
    a1 <- upper
    a2 <- lower
    return [a1,a2]

不过,当我尝试解析“Bs”时,仍然出现同样的错误。我怎么会误解 whitespace 在这里处理,我怎样才能正确地做到这一点?

如果第一个解析器消耗了 任何东西

<|> 将不会尝试第二个选择。这样做是为了防止 space 泄漏。这是 parsec 的基础设计之一。

spaces 消耗一些输入时,一切都已决定,该解析器现在必须成功 - 否则,将不会尝试替代方案,整个机器就会失败。这就是您观察此行为的原因。 spaces 消耗一些输入,lookAhead lower 失败,整个解析器失败。

可以 通过 try 实现任意前瞻并确保即使第一个消耗输入也尝试第二个备选方案,但你不应该,在这种情况下不是.这里,spaces 是一个 非致命 解析器,它对这两个操作都是 初步 - 所以只需使用解析器 你的任何一个选择之前。

testP :: Parser String
testP = spaces *> (do
    lookAhead lower
    a1 <- lower
    a2 <- lower
    return [a1,a2]
    <|> do
    a1 <- upper
    a2 <- lower
    return [a1,a2])