Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Skipping first line in pipes-attoparsec

My types:

data Test = Test {
 a :: Int,
 b :: Int
} deriving (Show)

My parser:

testParser :: Parser Test
testParser = do
  a <- decimal
  tab
  b <- decimal
  return $ Test a b

tab = char '\t'

Now in order to skip the first line, I do something like this:

import qualified System.IO as IO    

parser :: Parser Test
parser = manyTill anyChar endOfLine *> testParser

main = IO.withFile testFile IO.ReadMode $ \testHandle -> runEffect $
         for (parsed (parser <* endOfLine) (fromHandle testHandle)) (lift . print)

But the above parser function makes every alternate link skip (which is obvious). How to only skip the first line in such a way that it works with Pipes ecosystem (Producer should produce a single Test value.) This is one obvious solution which I don't want (the below code will only work if I modify testParser to read newlines) because it returns the entire [Test] instead of a single value:

tests :: Parser [Test]
tests = manyTill anyChar endOfLine *>
        many1 testParser

Any ideas to tackle this problem ?

like image 626
Sibi Avatar asked Dec 25 '22 06:12

Sibi


2 Answers

You can drop the first line efficiently in constant space like this:

import Lens.Family (over)
import Pipes.Group (drops)
import Pipes.ByteString (lines)
import Prelude hiding (lines)

dropLine :: Monad m => Producer ByteString m r -> Producer ByteString m r
dropLine = over lines (drops 1)

You can apply dropLine to your Producer before you parse the Producer, like this:

main = IO.withFile testFile IO.ReadMode $ \testHandle -> runEffect $
    let p = dropLine (fromHandle testHandle)
    for (parsed (parser <* endOfLine) p) (lift . print)
like image 74
Gabriella Gonzalez Avatar answered Jan 09 '23 00:01

Gabriella Gonzalez


If the first line doesn't contain any valid Test, you can use Either () Test in order to handle it:

parserEither :: Parser (Either () Test)
parserEither = Right <$> testParser <* endOfLine 
           <|> Left <$> (manyTill anyChar endOfLine *> pure ())

After this you can use the functions provided by Pipes.Prelude to get rid of the first result (and additionally of all non-parseable lines):

producer p = parsed parserEither p 
         >-> P.drop 1 
         >-> P.filter (either (const False) (const True))
         >-> P.map    (\(Right x) -> x)

main = IO.withFile testFile IO.ReadMode $ \testHandle -> runEffect $
         for (producer (fromHandle testHandle)) (lift . print)
like image 36
Zeta Avatar answered Jan 08 '23 22:01

Zeta