将多行解析为Haskell中的列表列表

时间:2017-06-29 20:49:08

标签: parsing haskell parsec

我正在尝试解析一个看起来像这样的文件:

a b c 
f e d

我希望匹配行中的每个符号,并将所有内容解析为列表列表,例如:

[[A, B, C], [D, E, F]]

为了做到这一点,我尝试了以下方法:

import           Control.Monad
import           Text.ParserCombinators.Parsec
import           Text.ParserCombinators.Parsec.Language
import qualified Text.ParserCombinators.Parsec.Token    as P

parserP :: Parser [[MyType]]
parserP = do
  x  <- rowP
  xs <- many (newline >> rowP)
  return (x : xs)

rowP :: Parser [MyType]
rowP = manyTill cellP $ void newline <|> eof

cellP :: Parser (Cell Color)
cellP = aP <|> bP <|> ... -- rest of the parsers, they all look very similar

aP :: Parser MyType
aP = symbol "a" >> return A

bP :: Parser MyType
bP = symbol "b" >> return B

lexer = P.makeTokenParser emptyDef
symbol  = P.symbol lexer

但它无法返回多个内部列表。相反,我得到的是:

[[A, B, C, D, E, F]]

我做错了什么?我希望很多人能够解析cellP直到换行符,但事实并非如此。

2 个答案:

答案 0 :(得分:5)

Parser组合器对于这么简单的事情来说太过分了。我会使用lines :: String -> [String]words :: String -> [String]来分解输入,然后将各个令牌映射到MyType s。

toMyType :: String -> Maybe MyType
toMyType "a" = Just A
toMyType "b" = Just B
toMyType "c" = Just C
toMyType _ = Nothing

parseMyType :: String -> Maybe [[MyType]]
parseMyType = traverse (traverse toMyType) . fmap words . lines

答案 1 :(得分:4)

你是对的,manyTill一直在解析直到换行符。但manyTill永远不会看到换行符,因为cellP太急切了。 cellP最终会调用P.symbol,其文档说明

  

symbol :: String -> ParsecT s u m String

     

Lexeme解析器符号s解析字符串s并跳过尾随空格。

关键字有“空格”。事实证明,Parsec将空格定义为满足isSpace的任何字符,其中包括换行符。因此P.symbol很高兴使用c,然后是空格和换行符,然后manyTill看起来并且没有看到换行符,因为它已经消耗了

如果您想放弃Parsec例程,请使用Benjamin的解决方案。但是如果你决心坚持下去,那么基本的想法就是你要修改语言的whiteSpace字段以正确定义空格而不是新行。像

这样的东西
lexer = let lexer0 = P.makeTokenParser emptyDef
        in lexer0 { whiteSpace = void $ many (oneOf " \t") }

这是伪代码,可能不适用于您的具体案例,但这个想法就在那里。您希望将whiteSpace的定义更改为您要定义为whiteSpace的任何内容,而不是系统默认定义的内容。请注意,如果您已定义了注释语法,则更改此选项也会破坏您的注释语法,因为whiteSpace之前已配备处理注释。

简而言之,本杰明的回答可能是最好的方式。这里没有真正的理由使用Parsec。但是知道为什么这个特定的解决方案不起作用也是有帮助的:Parsec的默认语言定义并不是为处理具有重要意义的换行而设计的。