我有一个基于行的文本格式,我想用Parsec
† 解析。一行要么以井号开头并指定以冒号分隔的键值对,要么是前面标记描述的 URL。
这是一个简短的示例:
#foo:bar
#faz:baz
https://example.com
#foo:beep
https://example.net
为简单起见,我将所有内容存储为String
. 一个标签是一个type Tag = (String, String)
,例如("foo", "bar")
。最终,我想将这些分组为([Tag], URL)
.
但是,我很难弄清楚如何解析 [一个或多个标签] 或 [一个 URL]。
我目前的方法如下所示:
import qualified System.Environment as Env
import qualified Text.Megaparsec as M
import qualified Text.Megaparsec.Text as M
type Tag = (String, String)
data Segment = Tags [Tag] | URL String
deriving (Eq, Show)
tagP :: M.Parser Tag
tagP = M.char '#' *> ((,) <$> M.someTill M.printChar (M.char ':') <*> M.someTill M.printChar M.eol) M.<?> "Tag starting with #"
urlP :: M.Parser String
urlP = M.someTill M.printChar M.eol M.<?> "Some URL"
parser :: M.Parser Segment
parser = (Tags <$> M.many tagP) M.<|> (URL <$> urlP)
main :: IO ()
main = do
fname <- head <$> Env.getArgs
res <- M.parseFromFile (parser <* M.eof) fname
print res
如果我尝试在上面的示例上运行它,我会收到如下解析错误:
3:1:
unexpected 'h'
expecting Tag starting with # or end of input
显然我的many
结合使用<|>
是不正确的。由于标签解析器不会使用来自 URL 解析器的任何输入,因此它与回溯无关。我需要如何更改才能获得所需的结果?
完整示例可在GitHub上找到。
† 我实际上在这里使用 MegaParsec 来获得更好的错误消息,但我认为这个问题非常普遍,与解析器组合器的任何特定实现无关。