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
112import Control.Monad.Rec.Class (class MonadRec, Step(..), tailRecM)
113import Data.ArrayBuffer.ArrayBuffer as AB
114import Data.ArrayBuffer.DataView as DV
115import Data.ArrayBuffer.Typed as AT
116import Data.ArrayBuffer.Types (ArrayBuffer, ByteLength, DataView, Uint8Array)
117import Data.Float32 (Float32)
118import Data.Identity (Identity)
119import Data.List (List(..), uncons, (:))
120import Data.Maybe (Maybe(..))
121import Data.Newtype (unwrap)
122import Data.UInt (UInt)
123import Effect (Effect)
124import Effect.Class (class MonadEffect, liftEffect)
126-- | For distinguishing between `ArrayBuffer` and `DataView`.
131-- Concerning DataViews.
132-- https://v8.dev/blog/dataview
134 -- | View the contents of `DataBuff` as a `DataView`.
135toView :: DataBuff -> DataView
136toView (Buff ab) = DV.whole ab
139-- | Monoidal builder for `ArrayBuffer`s.
141-- | We can add two types of things to the `Builder`:
146-- | to add a `DataView` to a `Builder` when we’re adding a large slice of data
147-- | from some other `ArrayBuffer`, so that we don’t
148-- | need an extra intermediate copy of the slice.
150 = Node Builder DataBuff Builder
153-- | ### Left-associative `<>>` append operator
155-- | __TL;DR__ You probably don't want to use the `Builder` monoid directly
156-- | in your code, it’s better to use the `PutM` monad with do-notation instead.
158-- | The `Builder` monoid in this library is efficient when we `snoc` single
159-- | items onto the end of it, or when we only `cons` single items to the
160-- | beginning, but it can be less efficient when we are mixing `cons` and
162-- | Most of the time we want to `snoc`, but the `Semigroup` append
163-- | operator `<>` is right-associative,
164-- | which means it chains like `cons`.
166-- | To solve this, we provide an operator `<>>` for appending `Builders`.
167-- | `<>>` is exactly the same as `<>`, but left-associative,
168-- | which means it chains like `snoc`.
170-- | This __only matters__ when we're chaining together three
171-- | or more `Builder`s in a single associative expression.
174-- | builder₁ <> builder₂ <> builder₃
176-- | we should always prefer to
179-- | builder₁ <>> builder₂ <>> builder₃
181-- | so that we get the efficient
182-- | `snoc`ing of `Builder`s.
184-- | If we build our `ArrayBuffer`s with the `PutM` monad instead of appending by
185-- | using the `Semigroup` instance of `Builder`, then we always get the efficient
188instance semigroupBuilder :: Semigroup Builder where
189 append Null Null = Null
192 append l (Node Null rx rr) = Node l rx rr -- this is the snoc case
193 append (Node ll lx Null) r = Node ll lx r -- found the rightmost Null
195 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
265 toUint8Array :: DataBuff -> Effect Uint8Array
266 toUint8Array (Buff ab) = AT.whole ab
267 toUint8Array (View dv) =
268 AT.part (DV.buffer dv) (DV.byteOffset dv) (DV.byteLength dv)
270-- | Serialize an 8-bit unsigned integer (byte) into a new `ArrayBuffer`.
271encodeUint8 :: forall m. (MonadEffect m) => UInt -> m ArrayBuffer
273 buf <- liftEffect $ AB.empty 1
274 _ <- liftEffect $ DV.setUint8 (DV.whole buf) 0 x
277-- | Serialize an 8-bit two’s-complement signed integer (char) into a new `ArrayBuffer`.
278encodeInt8 :: forall m. (MonadEffect m) => Int -> m ArrayBuffer
280 buf <- liftEffect $ AB.empty 1
281 _ <- liftEffect $ DV.setInt8 (DV.whole buf) 0 x
284-- | Serialize a 16-bit big-endian unsigned integer into a new `ArrayBuffer`.
285encodeUint16be :: forall m. (MonadEffect m) => UInt -> m ArrayBuffer
287 buf <- liftEffect $ AB.empty 2
288 _ <- liftEffect $ DV.setUint16be (DV.whole buf) 0 x
291-- | Serialize a 16-bit little-endian unsigned integer into a new `ArrayBuffer`.
292encodeUint16le :: forall m. (MonadEffect m) => UInt -> m ArrayBuffer
294 buf <- liftEffect $ AB.empty 2
295 _ <- liftEffect $ DV.setUint16le (DV.whole buf) 0 x
298-- | Serialize a 16-bit big-endian two’s-complement signed integer into a new `ArrayBuffer`.
299encodeInt16be :: forall m. (MonadEffect m) => Int -> m ArrayBuffer
301 buf <- liftEffect $ AB.empty 2
302 _ <- liftEffect $ DV.setInt16be (DV.whole buf) 0 x
305-- | Serialize a 16-bit little-endian two’s-complement signed integer into a new `ArrayBuffer`.
306encodeInt16le :: forall m. (MonadEffect m) => Int -> m ArrayBuffer
308 buf <- liftEffect $ AB.empty 2
309 _ <- liftEffect $ DV.setInt16le (DV.whole buf) 0 x
312-- | Serialize a 32-bit big-endian unsigned integer into a new `ArrayBuffer`.
313encodeUint32be :: forall m. (MonadEffect m) => UInt -> m ArrayBuffer
315 buf <- liftEffect $ AB.empty 4
316 _ <- liftEffect $ DV.setUint32be (DV.whole buf) 0 x
319-- | Serialize a 32-bit little-endian unsigned integer into a new `ArrayBuffer`.
320encodeUint32le :: forall m. (MonadEffect m) => UInt -> m ArrayBuffer
322 buf <- liftEffect $ AB.empty 4
323 _ <- liftEffect $ DV.setUint32le (DV.whole buf) 0 x
326-- | Serialize a 32-bit big-endian two’s-complement signed integer into a new `ArrayBuffer`.
327encodeInt32be :: forall m. (MonadEffect m) => Int -> m ArrayBuffer
329 buf <- liftEffect $ AB.empty 4
330 _ <- liftEffect $ DV.setInt32be (DV.whole buf) 0 x
333-- | Serialize a 32-bit little-endian two’s-complement signed integer into a new `ArrayBuffer`.
334encodeInt32le :: forall m. (MonadEffect m) => Int -> m ArrayBuffer
336 buf <- liftEffect $ AB.empty 4
337 _ <- liftEffect $ DV.setInt32le (DV.whole buf) 0 x
340-- | Serialize a 32-bit big-endian IEEE single-precision float into a new `ArrayBuffer`.
341encodeFloat32be :: forall m. (MonadEffect m) => Float32 -> m ArrayBuffer
342encodeFloat32be x = do
343 buf <- liftEffect $ AB.empty 4
344 _ <- liftEffect $ DV.setFloat32be (DV.whole buf) 0 x
347-- | Serialize a 32-bit little-endian IEEE single-precision float into a new `ArrayBuffer`.
348encodeFloat32le :: forall m. (MonadEffect m) => Float32 -> m ArrayBuffer
349encodeFloat32le x = do
350 buf <- liftEffect $ AB.empty 4
351 _ <- liftEffect $ DV.setFloat32le (DV.whole buf) 0 x
354-- | Serialize a 64-bit big-endian IEEE double-precision float into a new `ArrayBuffer`.
355encodeFloat64be :: forall m. (MonadEffect m) => Number -> m ArrayBuffer
356encodeFloat64be x = do
357 buf <- liftEffect $ AB.empty 8
358 _ <- liftEffect $ DV.setFloat64be (DV.whole buf) 0 x
361-- | Serialize a 64-bit little-endian IEEE double-precision float into a new `ArrayBuffer`.
362encodeFloat64le :: forall m. (MonadEffect m) => Number -> m ArrayBuffer
363encodeFloat64le x = do
364 buf <- liftEffect $ AB.empty 8
365 _ <- 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