3-- | You probably don’t want to import anything from this module.
5-- | ## Implementation Details
7-- | We want our `Builder` to be a data structure with
8-- | * *O(1)* monoid append
11-- | Our `Builder` implementation is an unbalanced binary tree.
13-- | For monoid `append`, what we actually get is *O(1)* when either the
14-- | left or right tree is a singleton. If that's not true, then in the
15-- | unlikely worst case `append` might be *O(n)*.
17-- | `Builder` is optimized for what we consider to be normal usage, that is,
18-- | `snoc`ing singleton elements to the end of the `Builder`.
20-- | If a Builder is built entirely by `snoc`ing, it will look like a
21-- | left-only binary tree, a.k.a. a linked list.
33-- | If two of these `snoc`-built trees are `append`ed, then the new tree
48-- | This is all similar to
49-- | [__bytestring-tree-builder__](https://hackage.haskell.org/package/bytestring-tree-builder)
51-- | [`Tree`](https://hackage.haskell.org/package/bytestring-tree-builder-0.2.7.3/docs/src/ByteString.TreeBuilder.Tree.html#Tree)
52-- | structure in __bytestring-tree-builder__ only carries values in its
53-- | leaves, which is how it achieves consistent *O(1)* appending, at the cost of
54-- | a higher coefficient time factor on the fold.
56-- | We hope that this implementation is fairly fast, since it is similar to
57-- | __bytestring-tree-builder__
58-- | which “according to the benchmarks … beats all the alternatives.”
60-- | haven’t chosen this implementation because it’s fast, we've chosen
61-- | this implementation because it’s simple.
62-- | If someone wants to create a fast PureScript `ArrayBuffer` serialization
63-- | library, then they can benchmark against this one to prove that the new
66-- | One relatively cheap and simple performance improvement for this library would be to
67-- | remove the `Null` constructor of `Builder` and instead use Javascript nulls?
69-- | In the longer term, it might make sense to try to change the `Builder` so
70-- | that it works like the
71-- | https://hackage.haskell.org/package/bytestring/docs/Data-ByteString-Builder.html .
72-- | That’s the approach taken by
73-- | https://pursuit.purescript.org/packages/purescript-dynamic-buffers .
75-- | Here are some benchmarks of different Haskell ByteString builders
76-- | https://github.com/haskell-perf/strict-bytestring-builders
78-- | We've tried to design the API for this library with minimal assumptions,
79-- | so that if we want to change the `Builder` implementation later then we can.
81module Data.ArrayBuffer.Builder.Internal
111import Control.Monad.Rec.Class (class MonadRec, Step(..), tailRecM)
112import Data.ArrayBuffer.ArrayBuffer as AB
113import Data.ArrayBuffer.DataView as DV
114import Data.ArrayBuffer.Typed as AT
115import Data.ArrayBuffer.Types (ArrayBuffer, ByteLength, DataView, Uint8Array)
116import Data.Float32 (Float32)
117import Data.Identity (Identity)
118import Data.List (List(..), uncons, (:))
119import Data.Maybe (Maybe(..))
120import Data.Newtype (unwrap)
121import Data.UInt (UInt)
122import Effect (Effect)
123import Effect.Class (class MonadEffect, liftEffect)
125-- | For distinguishing between `ArrayBuffer` and `DataView`.
130-- Concerning DataViews.
131-- https://v8.dev/blog/dataview
133-- | View the contents of `DataBuff` as a `DataView`.
134toView :: DataBuff -> DataView
135toView (Buff ab) = DV.whole ab
138-- | Monoidal builder for `ArrayBuffer`s.
140-- | We can add two types of things to the `Builder`:
145-- | to add a `DataView` to a `Builder` when we’re adding a large slice of data
146-- | from some other `ArrayBuffer`, so that we don’t
147-- | need an extra intermediate copy of the slice.
149 = Node Builder DataBuff Builder
152-- | ### Left-associative `<>>` append operator
154-- | __TL;DR__ You probably don't want to use the `Builder` monoid directly
155-- | in your code, it’s better to use the `PutM` monad with do-notation instead.
157-- | The `Builder` monoid in this library is efficient when we `snoc` single
158-- | items onto the end of it, or when we only `cons` single items to the
159-- | beginning, but it can be less efficient when we are mixing `cons` and
161-- | Most of the time we want to `snoc`, but the `Semigroup` append
162-- | operator `<>` is right-associative,
163-- | which means it chains like `cons`.
165-- | To solve this, we provide an operator `<>>` for appending `Builders`.
166-- | `<>>` is exactly the same as `<>`, but left-associative,
167-- | which means it chains like `snoc`.
169-- | This __only matters__ when we're chaining together three
170-- | or more `Builder`s in a single associative expression.
173-- | builder₁ <> builder₂ <> builder₃
175-- | we should always prefer to
178-- | builder₁ <>> builder₂ <>> builder₃
180-- | so that we get the efficient
181-- | `snoc`ing of `Builder`s.
183-- | If we build our `ArrayBuffer`s with the `PutM` monad instead of appending by
184-- | using the `Semigroup` instance of `Builder`, then we always get the efficient
187instance semigroupBuilder :: Semigroup Builder where
188 append Null Null = Null
191 append l (Node Null rx rr) = Node l rx rr -- this is the snoc case
192 append (Node ll lx Null) r = Node ll lx r -- found the rightmost Null
194 append (Node ll lx lr) r = Node ll lx $ append lr r -- search down the right
196-- side of the left tree
197-- to find the rightmost
200infixl 5 append as <>>
202instance monoidBuilder :: Monoid Builder where
205-- | Stack-safe `foldl` over a `Builder`. *O(n)*
206foldl :: forall a. (a -> DataBuff -> a) -> a -> Builder -> a
207foldl f a0 b = (unwrap :: Identity a -> a) $ foldM (\a' b' -> pure (f a' b')) a0 b
209-- | Stack-safe `foldM` over a `Builder`. *O(n)*.
210foldM :: forall m a. MonadRec m => (a -> DataBuff -> m a) -> a -> Builder -> m a
211-- The Art of Computer Programming 2.3.1
212-- Inorder traversal using a stack
213-- https://yuyuan.org/MorrisAlgorithm/
214foldM f a0 b = tailRecM outer { p: b, stack: Nil, accum: a0 }
216 outer { p, stack, accum } = do
217 stack' <- tailRecM inner { p_: p, stack_: stack }
218 case uncons stack' of
219 Nothing -> pure $ Done accum
220 Just { head: Node _ x r, tail } -> do
222 pure $ Loop { p: r, stack: tail, accum: accum' }
223 Just { head: Null } -> pure $ Done accum
224 inner { p_: Null, stack_ } = pure $ Done stack_
225 inner { p_: n@(Node l _ _), stack_ } = pure $ Loop { p_: l, stack_: n : stack_ }
227-- | Construct a `Builder` with a single `DataBuff`. *O(1)*
228singleton :: DataBuff -> Builder
229singleton buf = Node Null buf Null
231-- | Calculate the total byte length of the `Builder`, without actually
232-- | building it yet. *O(n)*
233length :: Builder -> ByteLength
234length bldr = foldl (\b a -> b + len a) 0 bldr
236 len (Buff ab) = AB.byteLength ab
237 len (View dv) = DV.byteLength dv
239-- | Prepend a `DataBuff` to the beginning of the `Builder`. *O(1)*
240cons :: DataBuff -> Builder -> Builder
241cons x bs = Node Null x bs
243-- | Append a `DataBuff` to the end of the `Builder`. *O(1)*
244snoc :: Builder -> DataBuff -> Builder
245snoc bs x = Node bs x Null
247-- | Build a single `ArrayBuffer` from a `Builder`. *O(n)*
248execBuilder :: forall m. MonadEffect m => MonadRec m => Builder -> m ArrayBuffer
250 let buflen = length bldr
251 -- Allocate the final ArrayBuffer
252 buf <- liftEffect $ AB.empty buflen
253 -- Then copy each ArrayBuffer into the final ArrayBuffer.
254 -- To memcpy from one ArrayBuffer to another, apparently we have to first view
255 -- both ArrayBuffers as ArrayView (Typed Array).
256 newview <- liftEffect (AT.whole buf :: Effect Uint8Array)
259 aview <- liftEffect $ toUint8Array a
260 _ <- liftEffect $ AT.setTyped newview (Just offset) aview
261 pure $ offset + AT.byteLength aview
267 toUint8Array :: DataBuff -> Effect Uint8Array
268 toUint8Array (Buff ab) = AT.whole ab
269 toUint8Array (View dv) =
270 AT.part (DV.buffer dv) (DV.byteOffset dv) (DV.byteLength dv)
272-- | Serialize an 8-bit unsigned integer (byte) into a new `ArrayBuffer`.
273encodeUint8 :: forall m. (MonadEffect m) => UInt -> m ArrayBuffer
275 buf <- liftEffect $ AB.empty 1
276 _ <- liftEffect $ DV.setUint8 (DV.whole buf) 0 x
279-- | Serialize an 8-bit two’s-complement signed integer (char) into a new `ArrayBuffer`.
280encodeInt8 :: forall m. (MonadEffect m) => Int -> m ArrayBuffer
282 buf <- liftEffect $ AB.empty 1
283 _ <- liftEffect $ DV.setInt8 (DV.whole buf) 0 x
286-- | Serialize a 16-bit big-endian unsigned integer into a new `ArrayBuffer`.
287encodeUint16be :: forall m. (MonadEffect m) => UInt -> m ArrayBuffer
289 buf <- liftEffect $ AB.empty 2
290 _ <- liftEffect $ DV.setUint16be (DV.whole buf) 0 x
293-- | Serialize a 16-bit little-endian unsigned integer into a new `ArrayBuffer`.
294encodeUint16le :: forall m. (MonadEffect m) => UInt -> m ArrayBuffer
296 buf <- liftEffect $ AB.empty 2
297 _ <- liftEffect $ DV.setUint16le (DV.whole buf) 0 x
300-- | Serialize a 16-bit big-endian two’s-complement signed integer into a new `ArrayBuffer`.
301encodeInt16be :: forall m. (MonadEffect m) => Int -> m ArrayBuffer
303 buf <- liftEffect $ AB.empty 2
304 _ <- liftEffect $ DV.setInt16be (DV.whole buf) 0 x
307-- | Serialize a 16-bit little-endian two’s-complement signed integer into a new `ArrayBuffer`.
308encodeInt16le :: forall m. (MonadEffect m) => Int -> m ArrayBuffer
310 buf <- liftEffect $ AB.empty 2
311 _ <- liftEffect $ DV.setInt16le (DV.whole buf) 0 x
314-- | Serialize a 32-bit big-endian unsigned integer into a new `ArrayBuffer`.
315encodeUint32be :: forall m. (MonadEffect m) => UInt -> m ArrayBuffer
317 buf <- liftEffect $ AB.empty 4
318 _ <- liftEffect $ DV.setUint32be (DV.whole buf) 0 x
321-- | Serialize a 32-bit little-endian unsigned integer into a new `ArrayBuffer`.
322encodeUint32le :: forall m. (MonadEffect m) => UInt -> m ArrayBuffer
324 buf <- liftEffect $ AB.empty 4
325 _ <- liftEffect $ DV.setUint32le (DV.whole buf) 0 x
328-- | Serialize a 32-bit big-endian two’s-complement signed integer into a new `ArrayBuffer`.
329encodeInt32be :: forall m. (MonadEffect m) => Int -> m ArrayBuffer
331 buf <- liftEffect $ AB.empty 4
332 _ <- liftEffect $ DV.setInt32be (DV.whole buf) 0 x
335-- | Serialize a 32-bit little-endian two’s-complement signed integer into a new `ArrayBuffer`.
336encodeInt32le :: forall m. (MonadEffect m) => Int -> m ArrayBuffer
338 buf <- liftEffect $ AB.empty 4
339 _ <- liftEffect $ DV.setInt32le (DV.whole buf) 0 x
342-- | Serialize a 32-bit big-endian IEEE single-precision float into a new `ArrayBuffer`.
343encodeFloat32be :: forall m. (MonadEffect m) => Float32 -> m ArrayBuffer
344encodeFloat32be x = do
345 buf <- liftEffect $ AB.empty 4
346 _ <- liftEffect $ DV.setFloat32be (DV.whole buf) 0 x
349-- | Serialize a 32-bit little-endian IEEE single-precision float into a new `ArrayBuffer`.
350encodeFloat32le :: forall m. (MonadEffect m) => Float32 -> m ArrayBuffer
351encodeFloat32le x = do
352 buf <- liftEffect $ AB.empty 4
353 _ <- liftEffect $ DV.setFloat32le (DV.whole buf) 0 x
356-- | Serialize a 64-bit big-endian IEEE double-precision float into a new `ArrayBuffer`.
357encodeFloat64be :: forall m. (MonadEffect m) => Number -> m ArrayBuffer
358encodeFloat64be x = do
359 buf <- liftEffect $ AB.empty 8
360 _ <- liftEffect $ DV.setFloat64be (DV.whole buf) 0 x
363-- | Serialize a 64-bit little-endian IEEE double-precision float into a new `ArrayBuffer`.
364encodeFloat64le :: forall m. (MonadEffect m) => Number -> m ArrayBuffer
365encodeFloat64le x = do
366 buf <- liftEffect $ AB.empty 8
367 _ <- liftEffect $ DV.setFloat64le (DV.whole buf) 0 x
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list