2016-09-16 81 views
2

Parsec的新手,初学者的问题。如何解析一行文件,其中一些行可能是空白的,只包含空格后跟一个换行符?我只想跳过它们,而不是在解析后的输出中。跳过Parsec的空白行

import Text.ParserCombinators.Parsec 

-- alias for parseTest 
run :: Show a => Parser a -> String -> IO() 
run = parseTest 

-- parse lines 
p :: Parser [[String]] 
p = lineP `endBy` newline <* eof 
    where lineP = wordP `sepBy` (char ' ') 
     wordP = many $ noneOf "\n" 

实例解析空白行:

*Main> run p "z x c\n1 2 3\n \na\n" 
[["z x c"],["1 2 3"],[" "],["a"]] 

我怀疑我会对此完全错误的。

+2

为什么不只是'筛选(任何(不是。isSpace))。 lines'? – melpomene

+0

为了简化问题,我抽取了我正在做的事情的细节。我正在解析配置文件,不仅仅是文字,而是解析各种复杂类型的键和值。在我看来,解析器应该能够放弃作为其语法一部分的空白行,并且不应该在文件级别的词法读取时执行此操作。 – andro

+0

你的'wordP'解析空格,所以'\'sepBy \'(char'')'什么也不做。 –

回答

3

而不是使用newline的,你可以定义捕捉概念一行的末尾,这将解析至少一个newline,然后有选择地许多空行的自定义分析器(即空格,接着又换行) 。您将需要try操作原路返回,如果空格后面没有其他换行符(或输入结束后,我猜):

代码:

-- parse lines 
p :: Parser [[String]] 
p = lineP `endBy` lineEnd <* eof 
    where lineP = wordP `sepBy` (char ' ') 
     wordP = many $ noneOf " \n" 

lineEnd :: Parser() 
lineEnd = do 
    newline 
    many (try (many (oneOf " \t") >> newline)) 
    return() 

输出:

*Main> run p "z x c\n1 2 3\n \na\n" 
[["z","x","c"],["1","2","3"],["a"]] 
3

一种方法可能是将文件视为一系列空白或非空白的行。下面用表达式line <|> emptyLine表达这个想法。以下内容使用Maybe数据类型来区分解析非空行的结果,使用catMaybes最后过滤掉Nothing

#!/usr/bin/env stack 
{- stack 
    --resolver lts-7.0 
    --install-ghc 
    runghc 
    --package parsec 
-} 

import Prelude hiding (lines) 
import Data.Maybe (catMaybes) 
import Text.ParserCombinators.Parsec 

-- parse lines 
p :: Parser [[String]] 
p = catMaybes <$> lines 
    where lines = (line <|> emptyLine) `endBy` newline <* eof 
     line = Just <$> word `sepBy1` spaces1 
     emptyLine = spaces1 >> pure Nothing 
     word = many1 $ noneOf ['\n', ' '] 
     spaces1 = skipMany1 (char ' ') 

main = parseTest p "z x c\n1 2 3\n \na\n" 

输出是:

[["z","x","c"],["1","2","3"],["a"]] 

另一种方法可能是使用Prelude功能与Data.Char.isSpace一起收集非空行,然后再开始:

#!/usr/bin/env stack 
{- stack 
    --resolver lts-7.0 
    --install-ghc 
    runghc 
    --package parsec 
-} 

import Data.Char 
import Text.ParserCombinators.Parsec 

p :: Parser [[String]] 
p = line `endBy` newline <* eof where 
    line = word `sepBy1` spaces1 
    word = many1 $ noneOf ['\n', ' '] 
    spaces1 = skipMany1 (char ' ') 

main = parseTest p (unlines nonBlankLines) 
    where input = "z x c\n1 2 3\n \na\n" 
     nonBlankLines = filter (not . all isSpace) $ lines input 

输出是:

[["z","x","c"],["1","2","3"],["a"]] 

这非常简单,并且具有额外的好处,即使用lines将不需要每行末尾的newline(这有助于便携性)。

请注意,您的wordP解析器存在一个小错误。还要注意,如指定的那样,这些解析器不能处理前面或后面的空格(在非空行上)。我正在想象你的非最小代码更具弹性。