git.iko.soy/rowtype-yoga/purescript-arraybuffer-builder
on master help sign in
first draft
commit682eff0624946ac207b365646f570bf38a3a2e11 authorJames Brock <jamesbrock@gmail.com> dateSat 25 Jul 2020, 16:38 (6 years ago) parenta0c803b
2 files changed+150−36
spago.dhall+1−4 src/Data/ArrayBuffer/Builder.purs+149−32
spago.dhall+1−4
@@ -4,10 +4,7 @@ You can edit this file as you like.
44-}
55{ name = "purescript-easy-nix-example"
66, dependencies =
7− [ "console"
8− , "effect"
9− , "prelude"
10− , "psci-support"
7+ [ "prelude"
118 , "arraybuffer-types"
129 , "arraybuffer"
1310 ]
src/Data/ArrayBuffer/Builder.purs+149−32
@@ -1,9 +1,11 @@
1−-- | `ArrayBuffer` serialization primitives. (Not typeclass-based.)
2−-- |
3−-- | Provides a `Builder` monoid which is roughly equivalent to the
4−-- |
5−-- | Provides a `PutM` monad which is roughly equivalent to `PutM` from the
6−-- | Haskell __binary__ library.
1+-- | `ArrayBuffer` serialization primitives in the “builder monad” style.
2+-- | An API on top of
3+-- | [`Data.ArrayBuffer.ArrayBuffer`](https://pursuit.purescript.org/packages/purescript-arraybuffer/docs/Data.ArrayBuffer.ArrayBuffer)
4+-- | which provides a `Builder` monoid and a `PutM` monad which are roughly
5+-- | equivalent to the Haskell
6+-- | [`Data.Binary.Put`](https://hackage.haskell.org/package/binary/docs/Data-Binary-Put.html)
7+-- | module.
8+-- | This library defines no typeclasses.
79-- |
810-- | See purescript-parsing-dataview for deserialization.
911-- |
@@ -13,35 +15,67 @@
1315-- |
1416-- | ## Serialize an integer
1517-- |
16−-- | Create a two-byte `ArrayBuffer` which contains the number *-2* encoded as big-endian 16-bit two's-complement.
18+-- | Create a two-byte `buf :: ArrayBuffer` which contains the number *-2* encoded as big-endian 16-bit two’s-complement.
1719-- | ```purescript
1820-- | buf <- execPut $ putInt32be (-2)
1921-- | ```
2022-- |
21−-- | ## Serialize a UTF8 string
23+-- | ## Serialize a `String` as UTF8
2224-- |
23−-- | Create an `ArrayBuffer` which contains a `String` encoded as UTF8 in a way that's compatible
24−-- | with the
25−-- | [`Binary.Put.putStringUtf8`](https://hackage.haskell.org/package/binary-0.8.7.0/docs/Data-Binary-Put.html#v:putStringUtf8)
25+-- | Create a `buf :: ArrayBuffer` which contains a `String` encoded as UTF8 with a length prefix in a
26+-- | way that's compatible with the
27+-- | [`Binary.Put.putStringUtf8`](https://hackage.haskell.org/package/binary/docs/Data-Binary-Put.html#v:putStringUtf8)
2628-- | function from the Haskell
27−-- | [__binary__](https://hackage.haskell.org/package/bytestring-0.10.10.0/docs/Data-ByteString-Builder-Prim.html#v:primMapListBounded)
29+-- | [__binary__](https://hackage.haskell.org/package/binary)
2830-- | library.
29−-- | Uses [`Data.TextEncoding.encodeUtf8`](https://pursuit.purescript.org/packages/purescript-text-encoding/1.0.0/docs/Data.TextEncoding#v:encodeUtf8).
31+-- | Uses [`Data.TextEncoding.encodeUtf8`](https://pursuit.purescript.org/packages/purescript-text-encoding/docs/Data.TextEncoding#v:encodeUtf8).
3032-- | ```purescript
3133-- | buf <- execPut $ do
32−-- | stringbuf <- = Data.ArrayBuffer.Typed.buffer $ encodeUtf8 "ACAB"
33−-- | -- Adds a 64-bit length prefix for the length of the utf8 string, in bytes
34+-- | let stringbuf = Data.ArrayBuffer.Typed.buffer $ Data.TextEncoding.encodeUtf8 "Black Lives Matter"
35+-- | -- Put a 64-bit big-endian length prefix for the length of the utf8 string, in bytes.
3436-- | putUint32be 0
35−-- | putUint32be $ Data.ArrayBuffer.byteLength stringbuf
37+-- | putUint32be $ Data.Uint.fromInt $ Data.ArrayBuffer.byteLength stringbuf
3638-- | putArrayBuffer stringbuf
3739-- | ```
38−
40+-- |
41+-- | ## Serialize an `Array Int`
42+-- |
43+-- | Create a `buf :: ArrayBuffer` which encodes an `Array Int` with a length prefix in a
44+-- | way that's compatible with the
45+-- | [`Binary` instance for `[Int32]`](https://hackage.haskell.org/package/binary/docs/Data-Binary.html#t:Binary)
46+-- | from the Haskell
47+-- | [__binary__](https://hackage.haskell.org/package/binary)
48+-- | library.
49+-- | ```purescript
50+-- | let x = [1,2,3] :: Array Int
51+-- | buf <- execPut $
52+-- | -- Put a 64-bit big-endian length prefix for the length of the array, in bytes.
53+-- | putUint32be 0
54+-- | putUint32be $ Data.Uint.fromInt $ Data.Array.length x
55+-- | traverse_ putInt32be x
56+-- | ```
3957module Build
4058( Builder
4159, PutM
4260, Put
61+, execBuilder
4362, execPutM
4463, execPut
64+, putArrayBuffer
65+, putUint8
66+, putInt8
67+, putUint16be
68+, putUint16le
69+, putInt16be
70+, putInt16le
71+, putUint32be
72+, putUint32le
73+, putInt32be
74+, putInt32le
75+, putFloat32be
76+, putFloat32le
77+, putFloat64be
78+, putFloat64le
4579)
4680where
4781
@@ -49,6 +83,7 @@ import Prelude
4983import Effect (Effect)
5084import Effect.Class (class MonadEffect, liftEffect)
5185import Data.UInt (UInt)
86+import Data.Float32 (Float32)
5287import Data.Maybe (Maybe(..))
5388import Control.Monad.Writer.Trans (WriterT, execWriterT, tell)
5489
@@ -92,7 +127,7 @@ type Put = PutM Effect
92127-- |
93128-- |
94129-- | If a Builder is built entirely by `snoc`ing, it will look like a
95−-- | left-only tree, a.k.a. a linked list.
130+-- | left-only binary tree, a.k.a. a linked list.
96131-- |
97132-- | ```
98133-- | ⬤
@@ -126,6 +161,21 @@ type Put = PutM Effect
126161-- | leaves, and therefore I think has an *O(n log(n))* fold. This `Builder`
127162-- | has an *O(n)* fold.
128163-- |
164+-- | We hope that this implementation is fairly fast, but we
165+-- | haven't chosen this implementation because it's fast, we've chosen
166+-- | this implementation because it's simple. This library provides basic
167+-- | ArrayBuffer serialization with the familar “put monad” style.
168+-- | The only currently existing Purescript libary which does ArrayBuffer
169+-- | serialization is
170+-- | https://pursuit.purescript.org/packages/purescript-arraybuffer-class
171+-- | , and that library is heavily typeclass-based and designed
172+-- | for compatibility with the Haskell cereal library.
173+-- | If someone wants to create a fast Purescript ArrayBuffer serialization
174+-- | library, then they can benchmark against this one to prove that the new
175+-- | one is fast.
176+-- |
177+-- | One relatively cheap and simple performance improvement for this library would be to
178+-- | remove the Null constructor of Builder and instead use Javascript nulls.
129179data Builder
130180 = Node Builder ArrayBuffer Builder
131181 | Null
@@ -150,7 +200,8 @@ foldl :: forall b. (b -> ArrayBuffer -> b) -> b -> Builder -> b
150200foldl f a Null = a
151201foldl f a (Node l x r) = foldl f (f (foldl f a l) x) r
152202
153−-- | foldM : "Note this function is not generally stack-safe..." ???
203+-- | Monomorphic foldM copied from
204+-- | https://pursuit.purescript.org/packages/purescript-foldable-traversable/4.1.1/docs/Data.Foldable#v:foldM
154205foldM :: forall b m. (Monad m) => (b -> ArrayBuffer -> m b) -> b -> Builder -> m b
155206foldM f a0 = foldl (\ma b -> ma >>= flip f b) (pure a0)
156207
@@ -161,10 +212,9 @@ singleton buf = Node Null buf Null
161212snoc :: Builder -> ArrayBuffer -> Builder
162213snoc bs x = Node bs x Null
163214
164−-- | Run a computation to build an `ArrayBuffer` in any `MonadEffect`.
165−execPutM :: forall m. (MonadEffect m) => PutM m Unit -> m ArrayBuffer
166−execPutM putmonad = do
167− bldr <- execWriterT putmonad
215+-- | Build a single `ArrayBuffer` from a `Builder`.
216+execBuilder :: forall m. (MonadEffect m) => Builder -> m ArrayBuffer
217+execBuilder bldr = do
168218 let buflen = foldl (\b a -> b + AB.byteLength a) 0 bldr
169219 -- Allocate the final ArrayBuffer
170220 buf <- liftEffect $ AB.empty buflen
@@ -173,15 +223,19 @@ execPutM putmonad = do
173223 -- both ArrayBuffers as ArrayView (Typed Array).
174224 newview <- liftEffect (AT.whole buf :: Effect Uint8Array)
175225 _ <- foldM
176− ( \offset a -> do
177− let offset' = offset + AB.byteLength a
178− aview <- liftEffect (AT.whole a :: Effect Uint8Array)
179− _ <- liftEffect $ AT.setTyped newview (Just offset') aview
180− pure offset'
181− ) 0 bldr
226+ ( \offset a -> do
227+ let offset' = offset + AB.byteLength a
228+ aview <- liftEffect (AT.whole a :: Effect Uint8Array)
229+ _ <- liftEffect $ AT.setTyped newview (Just offset') aview
230+ pure offset'
231+ ) 0 bldr
182232 pure buf
183233
184−-- | Run a computation to build an `ArrayBuffer` in the `Effect` monad.
234+-- | Monad computation to build an `ArrayBuffer` in any `MonadEffect`.
235+execPutM :: forall m. (MonadEffect m) => PutM m Unit -> m ArrayBuffer
236+execPutM = execBuilder <=< execWriterT
237+
238+-- | Monad computation to build an `ArrayBuffer` in the `Effect` monad.
185239execPut :: Put Unit -> Effect ArrayBuffer
186240execPut = execPutM
187241
@@ -196,6 +250,13 @@ putUint8 x = do
196250 _ <- liftEffect $ DV.setUint8 (DV.whole buf) 0 x
197251 tell $ singleton buf
198252
253+-- | Append an 8-bit two’s-complement signed integer (char) to the builder.
254+putInt8 :: forall m. (MonadEffect m) => Int -> PutM m Unit
255+putInt8 x = do
256+ buf <- liftEffect $ AB.empty 1
257+ _ <- liftEffect $ DV.setInt8 (DV.whole buf) 0 x
258+ tell $ singleton buf
259+
199260-- | Append a 16-bit big-endian unsigned integer to the builder.
200261putUint16be :: forall m. (MonadEffect m) => UInt -> PutM m Unit
201262putUint16be x = do
@@ -210,16 +271,72 @@ putUint16le x = do
210271 _ <- liftEffect $ DV.setUint16le (DV.whole buf) 0 x
211272 tell $ singleton buf
212273
213−-- | Append a 16-bit big-endian signed integer to the builder.
274+-- | Append a 16-bit big-endian two’s-complement signed integer to the builder.
214275putInt16be :: forall m. (MonadEffect m) => Int -> PutM m Unit
215276putInt16be x = do
216277 buf <- liftEffect $ AB.empty 2
217278 _ <- liftEffect $ DV.setInt16be (DV.whole buf) 0 x
218279 tell $ singleton buf
219280
220−-- | Append a 16-bit little-endian signed integer to the builder.
281+-- | Append a 16-bit little-endian two’s-complement signed integer to the builder.
221282putInt16le :: forall m. (MonadEffect m) => Int -> PutM m Unit
222283putInt16le x = do
223284 buf <- liftEffect $ AB.empty 2
224285 _ <- liftEffect $ DV.setInt16le (DV.whole buf) 0 x
286+ tell $ singleton buf
287+
288+-- | Append a 32-bit big-endian unsigned integer to the builder.
289+putUint32be :: forall m. (MonadEffect m) => UInt -> PutM m Unit
290+putUint32be x = do
291+ buf <- liftEffect $ AB.empty 4
292+ _ <- liftEffect $ DV.setUint32be (DV.whole buf) 0 x
293+ tell $ singleton buf
294+
295+-- | Append a 32-bit little-endian unsigned integer to the builder.
296+putUint32le :: forall m. (MonadEffect m) => UInt -> PutM m Unit
297+putUint32le x = do
298+ buf <- liftEffect $ AB.empty 4
299+ _ <- liftEffect $ DV.setUint32le (DV.whole buf) 0 x
300+ tell $ singleton buf
301+
302+-- | Append a 32-bit big-endian two’s-complement signed integer to the builder.
303+putInt32be :: forall m. (MonadEffect m) => Int -> PutM m Unit
304+putInt32be x = do
305+ buf <- liftEffect $ AB.empty 4
306+ _ <- liftEffect $ DV.setInt32be (DV.whole buf) 0 x
307+ tell $ singleton buf
308+
309+-- | Append a 32-bit little-endian two’s-complement signed integer to the builder.
310+putInt32le :: forall m. (MonadEffect m) => Int -> PutM m Unit
311+putInt32le x = do
312+ buf <- liftEffect $ AB.empty 4
313+ _ <- liftEffect $ DV.setInt32le (DV.whole buf) 0 x
314+ tell $ singleton buf
315+
316+-- | Append a 32-bit big-endian IEEE single-precision float to the builder.
317+putFloat32be :: forall m. (MonadEffect m) => Float32 -> PutM m Unit
318+putFloat32be x = do
319+ buf <- liftEffect $ AB.empty 4
320+ _ <- liftEffect $ DV.setFloat32be (DV.whole buf) 0 x
321+ tell $ singleton buf
322+
323+-- | Append a 32-bit little-endian IEEE single-precision float to the builder.
324+putFloat32le :: forall m. (MonadEffect m) => Float32 -> PutM m Unit
325+putFloat32le x = do
326+ buf <- liftEffect $ AB.empty 4
327+ _ <- liftEffect $ DV.setFloat32le (DV.whole buf) 0 x
328+ tell $ singleton buf
329+
330+-- | Append a 64-bit big-endian IEEE double-precision float to the builder.
331+putFloat64be :: forall m. (MonadEffect m) => Number -> PutM m Unit
332+putFloat64be x = do
333+ buf <- liftEffect $ AB.empty 8
334+ _ <- liftEffect $ DV.setFloat64be (DV.whole buf) 0 x
335+ tell $ singleton buf
336+
337+-- | Append a 64-bit little-endian IEEE double-precision float to the builder.
338+putFloat64le :: forall m. (MonadEffect m) => Number -> PutM m Unit
339+putFloat64le x = do
340+ buf <- liftEffect $ AB.empty 8
341+ _ <- liftEffect $ DV.setFloat64le (DV.whole buf) 0 x
225342 tell $ singleton buf