This works much better if you can make two small changes.

First, I'm guessing that your `rotateR` function has some sort of inverse named `rotateL`. If it does, then you can make a rotation lens:

rotated :: Int -> Lens' (Producer ByteString m x) (Producer ByteString m x)
    rotated n = iso (PB.map (`rotateR` n)) (PB.map (`rotateL` n))

Second, if you can use utf8 instead of latin1, then you can just write:

    decodeFileName :: Parser ByteString String
decodeFileName = zoom (PB.span (/= 0) . rotated 3 . PT.utf8 . from PT.packChars) PP.drawAll

The reason this works is that `rotated` and `utf8` contain extra information for how to propagate unused bytes back to the original input source. In the case of `rotated` it reverse the original rotation and in the case of `utf8` it re-encodes them.

If you don't have information for how to re-encode unused values, then you must apply the rotation and encoding to the producer before feeding it to the parser:

    yourProducer :: Producer ByteString IO ()

runStateT PP.drawAll (yourProducer ^. span (/= 0) ^. to (PB.map (`rotateR` n)) ^. PT.utf8 ^. fromPT.packChars) :: IO (String, Producer String IO (... {- more nested producers -}))

`pipes-parse` doesn't let you merge logic into the parser unless you also include logic for how to propagate unused bytes to the input source. Without that guarantee you get bugs related to silently dropping input values.

On 5/9/14, 11:06 AM, Torgeir Strand Henriksen wrote:
While working with a binary file format, I started out with this naive code:

import qualified Pipes.Parse as P
import qualified Pipes.Binary as P
import qualified Pipes.ByteString as PB
import qualified Data.Text as T
import qualified Data.ByteString as BS

entryParser tableStart = P.decodeGet $ (,,,) <$> decodeFilename <*> fmap (tableStart +) getWord32le <*> getWord32le <*> getWord32le

decodeFilename = T.unpack . decodeLatin1 . BS.pack <$> go where
    go = do
        c <- (`rotateR` 3) <$> getWord8
if c /= 0 then (c :) <$> go else pure [] -- terminate on (and consume the) 0

While it does work, I'm unhappy with decodeFilename as it basically implements a combination of map and span/fold with explicit recursion. But the underlying ByteString isn't available inside the Get monad without consuming it, so using e.g. BS.span seems out of the question. Let's see if lenses can come to the rescue:

entryParser tableStart = do
    nameChunks <- zoom (PB.span (/= 0)) P.drawAll
    PB.drawByte -- draw the terminating 0
let fileName = T.unpack . decodeLatin1 . BS.map (flip rotateR 3) . BS.concat $ nameChunks P.decodeGet $ (,,,) fileName <$> fmap (tableStart +) getWord32le <*> getWord32le <*> getWord32le

I like this better - map and span aren't implemented manually anymore - but at the same time I was hoping for more. It doesn't seem right to work directly on ByteStrings (i.e. BS.map instead of PB.map, and text instead of pipes-text), and the combination of drawAll and concat is a bit awkward, especially since drawAll is only for testing (even though all the tutorials use it :) ). The latter point might be addressed by giving pipes-bytestring a folding function similar to P.foldAll, but even so I wonder if there's a more ideomatic way to do this?
--
You received this message because you are subscribed to the Google Groups "Haskell Pipes" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected] <mailto:[email protected]>. To post to this group, send email to [email protected] <mailto:[email protected]>.

--
You received this message because you are subscribed to the Google Groups "Haskell 
Pipes" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].

Reply via email to