2

下面的代码输出Right ["1<!>2<!>3"],但我需要Right ["1", "2", "3"]

import Text.ParserCombinators.Parsec

response = contents :: CharParser () [String]
  where
    contents = sepBy content contentDelimiter
    contentDelimiter = string "<!>"
    content = many anyChar

main = do
  putStrLn $ show $ parse response "Response" "1<!>2<!>3"

我想这里的问题是解析器在测试分隔符content之前消耗了所有输入。sepBy所以,我的问题是:

  1. 我的假设是否正确?如果没有,我犯了什么错误?

  2. 对于这样的问题,您会推荐什么解决方案?(使用秒差)

* content必须匹配任何不包含分隔符的字符串。这1<!>2<!>3只是一个例子,它可以是dslkf\n><!>dsf<!>3什么

4

2 回答 2

8

对于您的第一个示例,您将替换

content = many anyChar

content = many digit

这样内容的解析器就不会错误地匹配分隔符。

也许您想匹配的不仅仅是数字,但即便如此,我还是建议您仔细考虑s之间的有效内容<!>并编写一个解析器来执行此操作。

为什么?
一旦你有了一个非常好的内容解析器,你的响应定义就会很完美。这样,您的内容可以包含mystring = "hello<!>mum"而不会被顶级解析器截断 - 低级stringLiteral解析器将吃掉整个内容"hello<!>mum",而顶级解析器将永远不会看到<!>正确和无辜地包含在其中。

一般来说,...
在大多数解析情况下,最好明确您的内容中允许的内容,并仅解析这些内容,原因有以下三个:

  • 可重用性(然后您可以在更大的解析器中使用它)
  • 正确性
  • 通常是效率——如果你避免过多的前瞻,你的解析器运行得更快。

可重用性很重要。目前,如果您使用的解析器只是拆分<!>并吃掉其他所有内容,则可以保证吃掉整个输入,并且您将无法再进行任何解析。

自下而上
您的解析器应该从头开始工作-您在评论中很好地描述了这一点,即“将解析器从特定堆叠到一般”。

为了便于测试,按顺序编写它们是最简单的,因此首先编写一个匹配 a stringCharthen stringLiteralbefore memberbefore arraybefore objectbefore jsonbefore contentthen的内容response。您可以让他们在此过程中递归地互相调用。然后,您可以parseTest像往常一样测试每个小家伙;输入parseTest response "1<!>2<!>3"ghci 比重写 main 和编译更快。

自顶向下?
自上而下编写解析器并没有错,只是更难。你可以写

response = many $ content `sepBy` contentSeparator
content = json <|> somethingElse
json = object <|> array
array = ...

但是在您编写了最小的解析器之前,没有什么是可测试的。

于 2012-11-17T14:43:27.353 回答
0

我建议使用 的解决方案noneOf,前提是 '<' 和 '!' 或 '>' 都不是您的内容的一部分。

import Text.ParserCombinators.Parsec

response = contents :: CharParser () [String]
 where
   contents = sepBy content contentDelimiter
   contentDelimiter = string "<!>"
   content = many (noneOf ['<','!','>']) 

main = print $ parse response "Response" "1<!>2<!>3"
于 2012-11-17T15:03:43.580 回答