我正在尝试解析一个看起来像这样的文件:
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直到换行符,但事实并非如此。
答案 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的默认语言定义并不是为处理具有重要意义的换行而设计的。