I have been working on a Bencoded string parser in order to improve my knowledge of Haskell. After running the code through hlint, I still have a few questions:
As I noted in the comments, the key value in the definition of BMapT should always be a Bstr. Is there any way to enforce a particular data constructor when pattern matching an algrebraic datatype?
Since Bencoded dictionaries are generally stored as human readable text, I made an instance of the Show typeclass so that I could take Bencode structures and turn them into text. I was wondering if this is the right thing to do, or if Haskell has another typeclass for storage/serialization a la Python's
This question is kind of nebulous, but would you consider this code 'idiomatic'? Code without style isn't worth writing :)
-- This is an implementation of Bencoding for bittorrent as described at -- http://www.bittorrent.org/beps/bep_0003.html module Bencode where import Text.Parsec.ByteString import Text.Parsec.Char import Text.Parsec.Prim import Text.Parsec.Combinator import qualified Text.Parsec.Error as PE import Data.Char import qualified Data.Map as M import qualified Control.Monad as Mon -- Technically this first argument can only be a Bstr, but I don't know -- how to express that type BMapT = M.Map Bencode Bencode -- I have no idea why Bstr is okay as a String when it's a ByteString data Bencode = Bint Integer | Bstr String | Blist [Bencode] | Bmap BMapT deriving (Eq, Ord) instance Show Bencode where show (Bint i) = "i" ++ show i ++ "e" show (Bstr s) = (show . length) s ++ ":" ++ s show (Blist bs) = 'l':concatMap show bs ++ "e" show (Bmap bm) = M.foldlWithKey (\a k b -> a ++ show k ++ show b) "d" bm ++ "e" -- Parse a Bencoded Integer bInt :: Parser Bencode bInt = do char 'i' num <- validNum char 'e' return $ Bint num -- This parser parses valid integers in Bencodings where validNum = do neg <- option ' ' (char '-') d <- digit case digitToInt d of -- Only time the first digit == 0 is "i0e" 0 -> if neg == ' ' then -- "i0e" allowed but NOT "i-0e" or zero padded integer lookAhead (char 'e') >> return 0 else parserFail "Can't have a negative zero" _ -> many digit >>= \xs -> return $ read (neg:d:xs) -- Parse a Bencoded String bString :: Parser Bencode bString = do ss <- many1 digit char ':' let size = read ss Mon.liftM Bstr $ count size anyChar bList :: Parser Bencode bList = do char 'l' ls <- many (bInt <|> bString <|> bList <|> bMap) char 'e' return $ Blist ls -- A parser which parses bencoded dictionaries bMap :: Parser Bencode bMap = do char 'd' entries <- many dictEntry char 'e' return $ Bmap $ M.fromList entries -- This parser will parse a key-value pair dictEntry :: Parser (Bencode, Bencode) dictEntry = do key <- bString value <- bString <|> bList <|> bInt <|> bMap return (key, value) -- This function reads a torrent file. readBencodedFile "filename" reads -- that filename and returns the parsed bencoded dictionary readBencodedFile :: String -> IO (Either PE.ParseError Bencode) readBencodedFile = parseFromFile bMap