summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Truzjan <pxqr.sta@gmail.com>2013-09-29 08:28:35 +0400
committerSam Truzjan <pxqr.sta@gmail.com>2013-09-29 08:28:35 +0400
commit70c43cb7f1db503080485bbf133a95c5b4d2d86c (patch)
tree50e24a0b293afdfe4a85be3c2d052e07c3bc26bd
parentca1068d8b24906c4e2fcaa637937f021567471d6 (diff)
Hide internals from main API
-rw-r--r--src/Data/BEncode.hs112
-rw-r--r--src/Data/BEncode/Internal.hs126
2 files changed, 127 insertions, 111 deletions
diff --git a/src/Data/BEncode.hs b/src/Data/BEncode.hs
index 4d7b6c5..1969595 100644
--- a/src/Data/BEncode.hs
+++ b/src/Data/BEncode.hs
@@ -65,7 +65,6 @@ module Data.BEncode
65 , BKey 65 , BKey
66 66
67 , BValue (..) 67 , BValue (..)
68 , ppBEncode
69 68
70 -- * Conversion 69 -- * Conversion
71 , BEncode (..) 70 , BEncode (..)
@@ -97,10 +96,6 @@ module Data.BEncode
97 , isString 96 , isString
98 , isList 97 , isList
99 , isDict 98 , isDict
100
101 -- * Extra
102 , builder
103 , parser
104 ) where 99 ) where
105 100
106 101
@@ -118,20 +113,14 @@ import Data.Map (Map)
118import qualified Data.Map as M 113import qualified Data.Map as M
119import Data.Set (Set) 114import Data.Set (Set)
120import qualified Data.Set as S 115import qualified Data.Set as S
121import Data.Attoparsec.ByteString.Char8 (Parser)
122import qualified Data.Attoparsec.ByteString.Char8 as P
123import Data.ByteString (ByteString) 116import Data.ByteString (ByteString)
124import qualified Data.ByteString as B 117import qualified Data.ByteString as B
125import qualified Data.ByteString.Char8 as BC 118import qualified Data.ByteString.Char8 as BC
126import qualified Data.ByteString.Lazy as Lazy 119import qualified Data.ByteString.Lazy as Lazy
127import qualified Data.ByteString.Lazy.Builder as B
128import qualified Data.ByteString.Lazy.Builder.ASCII as B
129import Data.ByteString.Internal as B (c2w, w2c)
130import Data.Text (Text) 120import Data.Text (Text)
131import qualified Data.Text.Encoding as T 121import qualified Data.Text.Encoding as T
132import Data.Typeable 122import Data.Typeable
133import Data.Version 123import Data.Version
134import Text.PrettyPrint hiding ((<>))
135import qualified Text.ParserCombinators.ReadP as ReadP 124import qualified Text.ParserCombinators.ReadP as ReadP
136 125
137#if __GLASGOW_HASKELL__ >= 702 126#if __GLASGOW_HASKELL__ >= 702
@@ -139,6 +128,7 @@ import GHC.Generics
139#endif 128#endif
140 129
141import Data.BEncode.BDict as BD 130import Data.BEncode.BDict as BD
131import Data.BEncode.Internal
142import Data.BEncode.Types 132import Data.BEncode.Types
143 133
144 134
@@ -709,16 +699,6 @@ isDict _ = False
709 Encoding 699 Encoding
710--------------------------------------------------------------------} 700--------------------------------------------------------------------}
711 701
712-- | Convert bencoded value to raw bytestring according to the
713-- specification.
714encode :: BValue -> Lazy.ByteString
715encode = B.toLazyByteString . builder
716
717-- | Try to convert raw bytestring to bencoded value according to
718-- specification.
719decode :: ByteString -> Result BValue
720decode = P.parseOnly parser
721
722-- | The same as 'decode' but returns any bencodable value. 702-- | The same as 'decode' but returns any bencodable value.
723decoded :: BEncode a => ByteString -> Result a 703decoded :: BEncode a => ByteString -> Result a
724decoded = decode >=> fromBEncode 704decoded = decode >=> fromBEncode
@@ -726,93 +706,3 @@ decoded = decode >=> fromBEncode
726-- | The same as 'encode' but takes any bencodable value. 706-- | The same as 'encode' but takes any bencodable value.
727encoded :: BEncode a => a -> Lazy.ByteString 707encoded :: BEncode a => a -> Lazy.ByteString
728encoded = encode . toBEncode 708encoded = encode . toBEncode
729
730{--------------------------------------------------------------------
731 Internals
732--------------------------------------------------------------------}
733
734-- | BEncode format encoder according to specification.
735builder :: BValue -> B.Builder
736builder = go
737 where
738 go (BInteger i) = B.word8 (c2w 'i') <>
739 B.integerDec i <>
740 B.word8 (c2w 'e')
741 go (BString s) = buildString s
742 go (BList l) = B.word8 (c2w 'l') <>
743 foldMap go l <>
744 B.word8 (c2w 'e')
745 go (BDict d) = B.word8 (c2w 'd') <>
746 bifoldMap mkKV d <>
747 B.word8 (c2w 'e')
748 where
749 mkKV k v = buildString k <> go v
750
751 buildString s = B.intDec (B.length s) <>
752 B.word8 (c2w ':') <>
753 B.byteString s
754 {-# INLINE buildString #-}
755
756-- TODO try to replace peekChar with something else
757-- | BEncode format parser according to specification.
758parser :: Parser BValue
759parser = valueP
760 where
761 valueP = do
762 mc <- P.peekChar
763 case mc of
764 Nothing -> fail "end of input"
765 Just c ->
766 case c of
767 -- if we have digit it always should be string length
768 di | di <= '9' -> BString <$> stringP
769 'i' -> P.anyChar *> ((BInteger <$> integerP) <* P.anyChar)
770 'l' -> P.anyChar *> ((BList <$> listBodyP) <* P.anyChar)
771 'd' -> P.anyChar *> (BDict <$> dictBodyP) <* P.anyChar
772 t -> fail ("bencode unknown tag: " ++ [t])
773
774 dictBodyP :: Parser BDict
775 dictBodyP = Cons <$> stringP <*> valueP <*> dictBodyP
776 <|> pure Nil
777
778 listBodyP = do
779 c <- P.peekChar
780 case c of
781 Just 'e' -> return []
782 _ -> (:) <$> valueP <*> listBodyP
783
784 stringP :: Parser ByteString
785 stringP = do
786 n <- P.decimal :: Parser Int
787 P.char ':'
788 P.take n
789 {-# INLINE stringP #-}
790
791 integerP :: Parser Integer
792 integerP = do
793 c <- P.peekChar
794 case c of
795 Just '-' -> do
796 P.anyChar
797 negate <$> P.decimal
798 _ -> P.decimal
799 {-# INLINE integerP #-}
800
801{--------------------------------------------------------------------
802 Pretty Printing
803--------------------------------------------------------------------}
804
805ppBS :: ByteString -> Doc
806ppBS = text . L.map w2c . B.unpack
807
808-- | Convert to easily readable JSON-like document. Typically used for
809-- debugging purposes.
810ppBEncode :: BValue -> Doc
811ppBEncode (BInteger i) = int $ fromIntegral i
812ppBEncode (BString s) = ppBS s
813ppBEncode (BList l)
814 = brackets $ hsep $ punctuate comma $ L.map ppBEncode l
815ppBEncode (BDict d)
816 = braces $ vcat $ punctuate comma $ L.map ppKV $ BD.toAscList d
817 where
818 ppKV (k, v) = ppBS k <+> colon <+> ppBEncode v
diff --git a/src/Data/BEncode/Internal.hs b/src/Data/BEncode/Internal.hs
new file mode 100644
index 0000000..7ea61c0
--- /dev/null
+++ b/src/Data/BEncode/Internal.hs
@@ -0,0 +1,126 @@
1module Data.BEncode.Internal
2 ( decode
3 , encode
4 , ppBEncode
5 ) where
6
7import Control.Applicative
8import Data.Attoparsec.ByteString.Char8 (Parser)
9import qualified Data.Attoparsec.ByteString.Char8 as P
10import Data.ByteString as B
11import qualified Data.ByteString.Lazy as Lazy
12import qualified Data.ByteString.Lazy.Builder as B
13import qualified Data.ByteString.Lazy.Builder.ASCII as B
14import Data.ByteString.Internal as B (c2w, w2c)
15import Data.Foldable
16import Data.List as L
17import Data.Monoid
18import Text.PrettyPrint hiding ((<>))
19
20import Data.BEncode.Types
21import Data.BEncode.BDict as BD
22
23
24{--------------------------------------------------------------------
25-- Serialization
26--------------------------------------------------------------------}
27
28-- | BEncode format encoder according to specification.
29builder :: BValue -> B.Builder
30builder = go
31 where
32 go (BInteger i) = B.word8 (c2w 'i') <>
33 B.integerDec i <>
34 B.word8 (c2w 'e')
35 go (BString s) = buildString s
36 go (BList l) = B.word8 (c2w 'l') <>
37 foldMap go l <>
38 B.word8 (c2w 'e')
39 go (BDict d) = B.word8 (c2w 'd') <>
40 bifoldMap mkKV d <>
41 B.word8 (c2w 'e')
42 where
43 mkKV k v = buildString k <> go v
44
45 buildString s = B.intDec (B.length s) <>
46 B.word8 (c2w ':') <>
47 B.byteString s
48 {-# INLINE buildString #-}
49
50-- | Convert bencoded value to raw bytestring according to the
51-- specification.
52encode :: BValue -> Lazy.ByteString
53encode = B.toLazyByteString . builder
54
55{--------------------------------------------------------------------
56-- Deserialization
57--------------------------------------------------------------------}
58
59-- TODO try to replace peekChar with something else
60-- | BEncode format parser according to specification.
61parser :: Parser BValue
62parser = valueP
63 where
64 valueP = do
65 mc <- P.peekChar
66 case mc of
67 Nothing -> fail "end of input"
68 Just c ->
69 case c of
70 -- if we have digit it always should be string length
71 di | di <= '9' -> BString <$> stringP
72 'i' -> P.anyChar *> ((BInteger <$> integerP) <* P.anyChar)
73 'l' -> P.anyChar *> ((BList <$> listBodyP) <* P.anyChar)
74 'd' -> P.anyChar *> (BDict <$> dictBodyP) <* P.anyChar
75 t -> fail ("bencode unknown tag: " ++ [t])
76
77 dictBodyP :: Parser BDict
78 dictBodyP = Cons <$> stringP <*> valueP <*> dictBodyP
79 <|> pure Nil
80
81 listBodyP = do
82 c <- P.peekChar
83 case c of
84 Just 'e' -> return []
85 _ -> (:) <$> valueP <*> listBodyP
86
87 stringP :: Parser ByteString
88 stringP = do
89 n <- P.decimal :: Parser Int
90 P.char ':'
91 P.take n
92 {-# INLINE stringP #-}
93
94 integerP :: Parser Integer
95 integerP = do
96 c <- P.peekChar
97 case c of
98 Just '-' -> do
99 P.anyChar
100 negate <$> P.decimal
101 _ -> P.decimal
102 {-# INLINE integerP #-}
103
104-- | Try to convert raw bytestring to bencoded value according to
105-- specification.
106decode :: ByteString -> Either String BValue
107decode = P.parseOnly parser
108
109{--------------------------------------------------------------------
110 Pretty Printing
111--------------------------------------------------------------------}
112
113ppBS :: ByteString -> Doc
114ppBS = text . L.map w2c . B.unpack
115
116-- | Convert to easily readable JSON-like document. Typically used for
117-- debugging purposes.
118ppBEncode :: BValue -> Doc
119ppBEncode (BInteger i) = int $ fromIntegral i
120ppBEncode (BString s) = ppBS s
121ppBEncode (BList l)
122 = brackets $ hsep $ punctuate comma $ L.map ppBEncode l
123ppBEncode (BDict d)
124 = braces $ vcat $ punctuate comma $ L.map ppKV $ BD.toAscList d
125 where
126 ppKV (k, v) = ppBS k <+> colon <+> ppBEncode v