/
ByteString.purs
227 lines (172 loc) · 6.48 KB
/
ByteString.purs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
module Data.ByteString
( module Node.Encoding
, Octet
, ByteString
, unsafeFreeze
, unsafeThaw
, empty
, singleton
, pack
, unpack
, index
, (!!)
, unsafeIndex
, cons
, snoc
, uncons
, unsnoc
, head
, tail
, last
, init
, length
, isEmpty
, map
, reverse
, Foldable
, foldableOfOctet
, foldl
, foldr
, fromString
, toString
, toUTF8
, fromUTF8
) where
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Unsafe (unsafePerformEff)
import Data.Array as Array
import Data.Foldable (class Foldable, foldMapDefaultL)
import Data.Leibniz (type (~), Leibniz(..), coerceSymm)
import Data.Maybe (Maybe)
import Data.Monoid (class Monoid)
import Data.Newtype (class Newtype)
import Node.Buffer (BUFFER, Buffer)
import Node.Buffer as Buffer
import Node.Encoding (Encoding(..))
import Prelude as Prelude
import Prelude hiding (map)
import Test.QuickCheck (class Arbitrary, arbitrary)
import Type.Quotient (type (/), Mod256, runQuotient)
import Unsafe.Coerce (unsafeCoerce)
--------------------------------------------------------------------------------
-- | An 8-bit non-negative integer.
type Octet = Int / Mod256
-- | A packed sequence of bytes.
newtype ByteString = ByteString Buffer
instance semigroupByteString :: Semigroup ByteString where
append a b = unsafeFreeze $ unsafePerformEff $ Buffer.concat [unsafeThaw a, unsafeThaw b]
instance monoidByteString :: Monoid ByteString where
mempty = empty
instance eqByteString :: Eq ByteString where
eq a b = unpack a == unpack b
instance ordByteString :: Ord ByteString where
compare a b = unpack a `compare` unpack b
instance arbitraryByteString :: Arbitrary ByteString where
arbitrary = fromString `flip` UTF8 <$> arbitrary
instance showByteString :: Show ByteString where
show bs = "(pack " <> show (unpack bs) <> ")"
--------------------------------------------------------------------------------
-- | *O(1)* The result points directly into the buffer. Mutating the buffer
-- | afterwards results in undefined behavior.
unsafeFreeze :: Buffer -> ByteString
unsafeFreeze = ByteString
-- | *O(1)* The result points directly into the byte string. Mutating the
-- | buffer results in undefined behavior.
unsafeThaw :: ByteString -> Buffer
unsafeThaw (ByteString s) = s
--------------------------------------------------------------------------------
-- | *O(1)* The empty byte string.
empty :: ByteString
empty = pack []
-- | *O(1)* A byte string with a single byte.
singleton :: Octet -> ByteString
singleton = pack <<< pure
-- | *Θ(n)* A byte string with many bytes.
pack :: Array Octet -> ByteString
pack = unsafeFreeze <<< unsafePerformEff <<< Buffer.fromArray <<< Prelude.map runQuotient
-- | *Θ(n)* Get the bytes from a byte string.
unpack :: ByteString -> Array Octet
unpack = coerce <<< unsafePerformEff <<< Buffer.toArray <<< unsafeThaw
where coerce = unsafeCoerce :: Array Int -> Array Octet
--------------------------------------------------------------------------------
-- | *O(1)* Get the nth byte.
index :: ByteString -> Int -> Maybe Octet
index b i = unsafePerformEff $ realGetAtOffset i (unsafeThaw b)
infixl 8 index as !!
-- | *O(1)* Get the nth byte. If the index is out of bounds, the behavior is
-- | undefined.
foreign import unsafeIndex :: ByteString -> Int -> Octet
-- https://github.com/purescript-node/purescript-node-buffer/issues/14
foreign import realGetAtOffset
:: ∀ eff. Int -> Buffer -> Eff (buffer :: BUFFER | eff) (Maybe Octet)
-- | *Θ(n)* Prepend a byte.
cons :: Octet -> ByteString -> ByteString
cons b bs = singleton b <> bs
-- | *Θ(n)* Append a byte.
snoc :: ByteString -> Octet -> ByteString
snoc bs b = bs <> singleton b
-- | *Θ(n)* Unprepend a byte.
uncons :: ByteString -> Maybe {head :: Octet, tail :: ByteString}
uncons bs = Array.uncons (unpack bs)
<#> case _ of {head: h, tail: t} -> {head: h, tail: pack t}
-- | *Θ(n)* Unappend a byte.
unsnoc :: ByteString -> Maybe {init :: ByteString, last :: Octet}
unsnoc bs = Array.unsnoc (unpack bs)
<#> case _ of {init: i, last: l} -> {init: pack i, last: l}
-- | *O(1)* Get the first byte.
head :: ByteString -> Maybe Octet
head = (_ !! 0)
-- | *Θ(n)* Get all but the first byte.
tail :: ByteString -> Maybe ByteString
tail = uncons >>> Prelude.map _.tail
-- | *O(1)* Get the last byte.
last :: ByteString -> Maybe Octet
last bs = bs !! (length bs - 1)
-- | *Θ(n)* Get all but the last byte.
init :: ByteString -> Maybe ByteString
init = unsnoc >>> Prelude.map _.init
-- | *O(1)* How many bytes are in this byte string?
length :: ByteString -> Int
length = unsafePerformEff <<< Buffer.size <<< unsafeThaw
-- | *O(1)* Check if a byte string is empty.
isEmpty :: ByteString -> Boolean
isEmpty = length >>> eq 0
--------------------------------------------------------------------------------
-- | *Θ(n)* Transform the bytes in the byte string.
map :: (Octet -> Octet) -> ByteString -> ByteString
map f = pack <<< Prelude.map f <<< unpack
-- | *Θ(n)* Reverse the byte string.
reverse :: ByteString -> ByteString
reverse = pack <<< Array.reverse <<< unpack
--------------------------------------------------------------------------------
-- | A foldable byte string.
newtype Foldable a = Foldable ByteString
derive instance newtypeFoldable :: Newtype (Foldable (Int / Mod256)) _
instance foldableFoldable :: Foldable Foldable where
foldMap = foldMapDefaultL
foldl f z fb@(Foldable b) = foldl f' z b
where f' x o = f x (coerceSymm leibniz o)
leibniz = foldableOfOctet fb
foldr f z fb@(Foldable b) = foldr f' z b
where f' o x = f (coerceSymm leibniz o) x
leibniz = foldableOfOctet fb
-- | *O(1)* Witness that foldable byte strings can only contain octets.
foldableOfOctet :: ∀ a. Foldable a -> a ~ Octet
foldableOfOctet = const $ Leibniz unsafeCoerce
-- | *Θ(n)* Fold a byte string.
foreign import foldl :: ∀ a. (a -> Octet -> a) -> a -> ByteString -> a
-- | *Θ(n)* Fold a byte string.
foreign import foldr :: ∀ a. (Octet -> a -> a) -> a -> ByteString -> a
--------------------------------------------------------------------------------
-- | *Θ(n)* Encode a string.
fromString :: String -> Encoding -> ByteString
fromString s e = unsafeFreeze $ unsafePerformEff $ Buffer.fromString s e
-- | *Θ(n)* Decode a string.
toString :: ByteString -> Encoding -> String
toString s e = unsafePerformEff $ Buffer.toString e (unsafeThaw s)
-- | *Θ(n)* `flip fromString UTF8`.
toUTF8 :: String -> ByteString
toUTF8 = flip fromString UTF8
-- | *Θ(n)* `flip toString UTF8`
fromUTF8 :: ByteString -> String
fromUTF8 = flip toString UTF8