2016-09-16 79 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解析器存在一個小錯誤。還要注意,如指定的那樣,這些解析器不能處理前面或後面的空格(在非空行上)。我正在想象你的非最小代碼更具彈性。