summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LICENSE19
-rw-r--r--Setup.hs2
-rw-r--r--bencoding.cabal76
-rw-r--r--src/Data/BEncode.hs181
4 files changed, 278 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..777f89d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
1Copyright (c) 2012 Sam T.
2
3Permission is hereby granted, free of charge, to any person obtaining a copy of
4this software and associated documentation files (the "Software"), to deal in
5the Software without restriction, including without limitation the rights to
6use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7of the Software, and to permit persons to whom the Software is furnished to do
8so, subject to the following conditions:
9
10The above copyright notice and this permission notice shall be included in all
11copies or substantial portions of the Software.
12
13THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19SOFTWARE. \ No newline at end of file
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
index 0000000..9a994af
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
1import Distribution.Simple
2main = defaultMain
diff --git a/bencoding.cabal b/bencoding.cabal
new file mode 100644
index 0000000..d4d9f16
--- /dev/null
+++ b/bencoding.cabal
@@ -0,0 +1,76 @@
1name: bencoding
2version: 0.1.0.0
3synopsis:
4description:
5license: MIT
6license-file: LICENSE
7author: Sam T.
8maintainer: Sam T. <pxqr.sta@gmail.com>
9copyright: (c) 2013, Sam T.
10category: Codec, Data
11build-type: Simple
12cabal-version: >= 1.8
13
14source-repository head
15 type: git
16 location: https://github.com/fmap/bencoding.git
17
18
19library
20 exposed-modules: Data.BEncode
21 other-modules:
22 build-depends: base == 4.5.*
23 , containers >= 0.4.0
24 , bytestring >= 0.10.2.0
25 , attoparsec >= 0.10.4.0
26 , ansi-wl-pprint
27
28 hs-source-dirs: src
29
30 ghc-options: -Wall -fno-warn-unused-do-bind -Werror
31
32executable pp
33 main-is: pp.hs
34 build-depends: base == 4.5.*
35 , bytestring >= 0.10.2.0
36 , bencoding >= 0.1.0.0
37
38 hs-source-dirs: pp
39 ghc-options: -Wall -Werror -O2
40
41test-suite properties
42 type: exitcode-stdio-1.0
43 main-is: properties.hs
44 hs-source-dirs: tests
45
46 build-depends: base == 4.5.*
47 , containers >= 0.4.0
48 , bytestring >= 0.10.2.0
49 , attoparsec >= 0.10.4.0
50 , ansi-wl-pprint
51
52 , test-framework
53 , test-framework-quickcheck2
54 , QuickCheck
55 , bencoding >= 0.1.0.0
56
57 ghc-options: -Wall -fno-warn-orphans
58
59
60benchmark bench-comparison
61 type: exitcode-stdio-1.0
62 main-is: Main.hs
63 hs-source-dirs: bench
64
65 build-depends: base == 4.5.*
66 , attoparsec >= 0.10.4.0
67 , bytestring >= 0.10.2.0
68
69 , criterion
70 , deepseq
71
72 , bencoding >= 0.1.0.0
73 , bencode >= 0.5
74 , AttoBencode >= 0.2.0.1
75
76 ghc-options: -O2 -Wall -fno-warn-orphans
diff --git a/src/Data/BEncode.hs b/src/Data/BEncode.hs
new file mode 100644
index 0000000..fceb88b
--- /dev/null
+++ b/src/Data/BEncode.hs
@@ -0,0 +1,181 @@
1-- | This module is intented to be imported qualified.
2module Data.BEncode
3 ( -- ^ Datatype
4 BEncode(..)
5
6 -- ^ Construction
7 , string, integer, list, dict
8 , int, charstring, dictAssoc
9
10 -- ^ Destructuring
11 -- ^ Serialization
12 , encode, decode
13
14 -- ^ Extra
15 , builder, parser, printPretty
16
17 -- ^ Predicates
18 , isInteger, isString, isList, isDict
19 ) where
20
21
22import Control.Applicative
23import Data.Int
24import Data.Foldable
25import Data.Monoid ((<>))
26import Data.Map (Map)
27import qualified Data.Map as M
28import Data.Attoparsec.ByteString.Char8 (Parser)
29import qualified Data.Attoparsec.ByteString.Char8 as P
30import Data.ByteString (ByteString)
31import qualified Data.ByteString as B
32import qualified Data.ByteString.Lazy as Lazy
33import Data.ByteString.Internal as B (c2w, w2c)
34import qualified Data.ByteString.Builder as B
35import qualified Data.ByteString.Builder.Prim as BP ()
36import Text.PrettyPrint.ANSI.Leijen (Pretty, Doc, pretty, (<+>), (</>))
37import qualified Text.PrettyPrint.ANSI.Leijen as PP
38
39type Dict = Map ByteString BEncode
40
41-- | 'BEncode' is straightforward AST for b-encoded values.
42-- Please note that since dictionaries are sorted, in most cases we can
43-- compare BEncoded values without serialization and vice versa.
44-- Lists is not required to be sorted through.
45-- Also note that 'BEncode' have JSON-like instance for 'Pretty'.
46--
47data BEncode = BInteger Int64
48 | BString ByteString
49 | BList [BEncode]
50 | BDict Dict
51 deriving (Show, Read, Eq, Ord)
52
53integer :: Integer -> BEncode
54integer = BInteger . fromIntegral
55{-# INLINE integer #-}
56
57string :: ByteString -> BEncode
58string = BString
59{-# INLINE string #-}
60
61list :: [BEncode] -> BEncode
62list = BList
63{-# INLINE list #-}
64
65dict :: Dict -> BEncode
66dict = BDict
67{-# INLINE dict #-}
68
69
70int :: Int -> BEncode
71int = integer . fromIntegral
72{-# INLINE int #-}
73
74charstring :: String -> BEncode
75charstring = string . B.pack . map (toEnum . fromEnum)
76{-# INLINE charstring #-}
77
78dictAssoc :: [(ByteString, BEncode)] -> BEncode
79dictAssoc = dict . M.fromList
80{-# INLINE dictAssoc #-}
81
82
83isInteger :: BEncode -> Bool
84isInteger (BInteger _) = True
85isInteger _ = False
86{-# INLINE isInteger #-}
87
88isString :: BEncode -> Bool
89isString (BString _) = True
90isString _ = False
91{-# INLINE isString #-}
92
93isList :: BEncode -> Bool
94isList (BList _) = True
95isList _ = False
96{-# INLINE isList #-}
97
98isDict :: BEncode -> Bool
99isDict (BList _) = True
100isDict _ = False
101{-# INLINE isDict #-}
102
103encode :: BEncode -> Lazy.ByteString
104encode = B.toLazyByteString . builder
105
106decode :: ByteString -> Either String BEncode
107decode = P.parseOnly parser
108
109
110builder :: BEncode -> B.Builder
111builder = go
112 where
113 go (BInteger i) = B.word8 (c2w 'i') <>
114 B.intDec (fromIntegral i) <> -- TODO FIXME
115 B.word8 (c2w 'e')
116 go (BString s) = buildString s
117 go (BList l) = B.word8 (c2w 'l') <>
118 foldMap go l <>
119 B.word8 (c2w 'e')
120 go (BDict d) = B.word8 (c2w 'd') <>
121 foldMap mkKV (M.toAscList d) <>
122 B.word8 (c2w 'e')
123 where
124 mkKV (k, v) = buildString k <> go v
125
126 buildString s = B.intDec (B.length s) <>
127 B.word8 (c2w ':') <>
128 B.byteString s
129 {-# INLINE buildString #-}
130
131
132parser :: Parser BEncode
133parser = valueP
134 where
135 valueP = do
136 mc <- P.peekChar
137 case mc of
138 Nothing -> fail "end of input"
139 Just c ->
140 case c of
141 -- if we have digit it always should be string length
142 di | di <= '9' -> BString <$> stringP
143 'i' -> P.anyChar *> ((BInteger <$> integerP) <* P.anyChar)
144 'l' -> P.anyChar *> ((BList <$> many valueP) <* P.anyChar)
145 'd' -> do
146 P.anyChar
147 (BDict . M.fromDistinctAscList <$> many ((,) <$> stringP <*> valueP))
148 <* P.anyChar
149 _ -> fail ""
150
151 stringP :: Parser ByteString
152 stringP = do
153 n <- P.decimal :: Parser Int
154 P.char ':'
155 P.take n
156 {-# INLINE stringP #-}
157
158 integerP :: Parser Int64
159 integerP = negate <$> (P.char8 '-' *> P.decimal)
160 <|> P.decimal
161 {-# INLINE integerP #-}
162
163
164printPretty :: BEncode -> IO ()
165printPretty = print . pretty
166
167ppBS :: ByteString -> Doc
168ppBS = PP.string . map w2c . B.unpack
169
170instance Pretty BEncode where
171 pretty (BInteger i) = PP.integer (fromIntegral i)
172 pretty (BString s) = ppBS s
173 pretty (BList l) = PP.lbracket <+>
174 PP.hsep (PP.punctuate PP.comma (map PP.pretty l)) <+>
175 PP.rbracket
176 pretty (BDict d) =
177 PP.align $ PP.lbrace <+>
178 PP.vsep (PP.punctuate PP.comma (map ppKV (M.toAscList d))) </>
179 PP.rbrace
180 where
181 ppKV (k, v) = ppBS k <+> PP.colon <+> PP.pretty v