如何在Parsec中解析带有开始和结束标记的多行

时间:2015-04-09 18:57:29

标签: haskell parsec

我是Parsec的新人。将在这里欣赏这个问题的指针。说,我有一个固定数量的标头的csv文件。我不想单独解析每一行,而是想在行的开头查找一个令牌,并使用非空令牌获取所有行直到下一行。示例如下:

token,flag,values
a,1,
,,a
,,f
b,2,

有效输入的规则是:如果填写了令牌字段,则获取所有行,直到下一个非空令牌字段。所以,我希望Parsec得到下面的多行作为第一个输入(那些多行可以被另一个规则解析):

a,1,
,,a
,,f

然后,该过程在具有非空令牌字段的下一行上再次开始(此处示例中的最后一行)。我想弄清楚的是,如果有一种简单的方法来指定Parsec中的规则 - 获取满足特定规则的所有行。然后他们可以交给另一个解析器。基本上,它看起来像某种lookahead规则来指定什么是有效的多行输入。我做对了吗?

我们现在可以忽略上面的逗号分隔符,只是说在一行开头找到一个字符时开始输入,并在一行开头找到一个字符时结束。

1 个答案:

答案 0 :(得分:3)

我在@ user2407038的帮助下解决了这个问题,他在评论中提出了基本大纲。下面的解决方案和解释(请参阅函数后面的注释 - 它们显示函数在输入中的行为):

{-# LANGUAGE FlexibleContexts #-}
import Control.Monad
import Text.Parsec
import Control.Applicative hiding ((<|>), many)


-- | this one accepts everything until newline, and discards the newline
-- | This one is used as building block in the functions below
restOfLine :: Stream s m Char => ParsecT s u m [Char]
restOfLine = many1 (satisfy (\x -> not $ x == '\n')) <* char '\n'

-- | a line with token is "many alphanumeric characters" followed by 
-- | any characters until newline 
tokenLine :: Stream s m Char => ParsecT s u m [Char]
tokenLine =  (++) <$>  many1 alphaNum <*> restOfLine

-- | ghci test:
-- | *Main Text.Parsec> parseTest tokenLine "a,1,,\n"
-- | "a,1,,"
-- | *Main Text.Parsec> parseTest tokenLine ",1,,\n"
-- | parse error at (line 1, column 1):
-- | unexpected ","
-- |expecting letter or digit

-- | a non-token line is a line that has any number of spaces followed
-- | by ",", then any characters until newline
nonTokenLine :: Stream s m Char => ParsecT s u m [Char]
nonTokenLine = (++) <$> (many space) <*> ((:) <$> char ',' <*> restOfLine)

-- | ghci test:
-- | *Main Text.Parsec> parseTest nonTokenLine ",1,,\n"
-- | ",1,,"
-- | *Main Text.Parsec> parseTest nonTokenLine "a,1,,\n"
-- | parse error at (line 1, column 1):
-- | unexpected "a"
-- | expecting space or ","

-- | One entry is tokenLine followed by any number of nonTokenLine
oneEntry :: Stream s m Char => ParsecT s u m [[Char]]
oneEntry = (:) <$> tokenLine <*> (many nonTokenLine)

-- | ghci test - please note that it drops last line as expected
-- | *Main Text.Parsec> parseTest oneEntry "a,1,,\n,,a\n,,f\nb,2,,\n"
-- | ["a,1,,",",,a",",,f"]


-- | We add 'many' to oneEntry to parse the entire file, and get multiple match entries
multiEntries :: Stream s m Char => ParsecT s u m [[String]]
multiEntries = many oneEntry

-- | ghci test - please note that it gets two entries as expected
-- | *Main Text.Parsec> parseTest multiEntries "a,1,,\n,,a\n,,f\nb,2,,\n"
-- | [["a,1,,",",,a",",,f"],["b,2,,"]]

评论中看到的解析器错误是在无效输入上出现的。这很容易处理。上面的代码只是一个基本的构建块,可以开始使用。