7

我是 Parsec 的新手(以及一般的解析器),我在写这个解析器时遇到了一些问题:

list = char '(' *> many (spaces *> some letter) <* spaces <* char ')'

这个想法是以这种格式解析列表(我正在处理 s-expressions):

(firstElement secondElement thirdElement and so on)

我写了这段代码来测试它:

import Control.Applicative
import Text.ParserCombinators.Parsec hiding (many)

list = char '(' *> many (spaces *> some letter) <* spaces <* char ')'

test s = do
  putStrLn $ "Testing " ++ show s ++ ":"
  parseTest list s
  putStrLn ""

main = do
  test "()"
  test "(hello)"
  test "(hello world)"
  test "( hello world)"
  test "(hello world )"
  test "( )"

这是我得到的输出:

Testing "()":
[]

Testing "(hello)":
["hello"]

Testing "(hello world)":
["hello","world"]

Testing "( hello world)":
["hello","world"]

Testing "(hello world )":
parse error at (line 1, column 14):
unexpected ")"
expecting space or letter

Testing "( )":
parse error at (line 1, column 3):
unexpected ")"
expecting space or letter

如您所见,当列表的最后一个元素和结束的). spaces我不明白为什么我之前放入的空白没有消耗<* char ')'。我犯了什么愚蠢的错误?

4

3 回答 3

13

问题是最后的空格被spaces的参数消耗many

list = char '(' *> many (spaces *> some letter) <* spaces <* char ')'
                     --  ^^^^^^ that one

然后解析器期望some letter但找到一个右括号并因此失败。

为了解决这个问题,只令牌之后使用空格,

list = char '(' *> spaces *> many (some letter <* spaces) <* char ')'

根据需要工作:

$ runghc lisplists.hs 
Testing "()":
[]

Testing "(hello)":
["hello"]

Testing "(hello world)":
["hello","world"]

Testing "( hello world)":
["hello","world"]

Testing "(hello world )":
["hello","world"]

Testing "( )":
[]
于 2013-04-16T21:48:03.540 回答
3

问题在于,一旦解析器many (spaces *> some letter)看到一个空格,它就会承诺解析另一个项目,因为 Parsec 默认情况下只向前看一个字符并且不会回溯。

大锤解决方案try用于启用回溯,但最好通过简单地在每个标记之后解析可选空格来避免此类问题,如Daniel's answer中所见。

于 2013-04-16T21:51:59.163 回答
1

这有点棘手。默认情况下,解析器是贪婪的。在你的情况下是什么意思?当您尝试(hello world )从 parsing 开始解析(时,您正在尝试匹配一些空格和标识符。所以我们这样做。没有空格,但有标识符。我们完了。我们再试一次世界。现在我们有_)剩余。你试试 parser (spaces *> some letter)。它使它变得贪婪:所以你匹配空间,现在你期待一些字母,但你却得到)了。此时解析器失败了,但它已经消耗了空间,所以你注定要失败。您可以使用try组合器使此解析器进行回溯:try (many (spaces *> some letter))

于 2013-04-16T21:51:09.257 回答