@@ -2,7 +2,7 @@
3-- | You probably don’t want to import anything from this module.
3-- | You probably don’t want to import anything from this module.
5−-- | # Implementation Details
5+-- | ## Implementation Details
7-- | We want our `Builder` to be a data structure with
7-- | We want our `Builder` to be a data structure with
8-- | * *O(1)* monoid append
8-- | * *O(1)* monoid append
@@ -57,19 +57,20 @@
57-- | __bytestring-tree-builder__
57-- | __bytestring-tree-builder__
58-- | which “according to the benchmarks … beats all the alternatives.”
58-- | which “according to the benchmarks … beats all the alternatives.”
59-- | However, we
59-- | However, we
60−-- | haven't chosen this implementation because it's fast, we've chosen
60+-- | haven’t chosen this implementation because it’s fast, we've chosen
61−-- | this implementation because it's simple.
61+-- | this implementation because it’s simple.
62−-- | If someone wants to create a fast PureScript ArrayBuffer serialization
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
63-- | library, then they can benchmark against this one to prove that the new
64-- | one is fast.
64-- | one is fast.
66-- | One relatively cheap and simple performance improvement for this library would be to
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.
67+-- | remove the `Null` constructor of `Builder` and instead use Javascript nulls?
68−-- | __UPDATE__ Actually no, we need the Null constructor for the Monoid instance.
70-- | In the longer term, it might make sense to try to change the `Builder` so
69-- | In the longer term, it might make sense to try to change the `Builder` so
71-- | that it works like the
70-- | that it works like the
72−-- | https://hackage.haskell.org/package/bytestring/docs/Data-ByteString-Builder.html
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 .
74-- | Here are some benchmarks of different Haskell ByteString builders
75-- | Here are some benchmarks of different Haskell ByteString builders
75-- | https://github.com/haskell-perf/strict-bytestring-builders
76-- | https://github.com/haskell-perf/strict-bytestring-builders
@@ -77,56 +78,63 @@
77-- | We've tried to design the API for this library with minimal assumptions,
78-- | We've tried to design the API for this library with minimal assumptions,
78-- | so that if we want to change the `Builder` implementation later then we can.
79-- | so that if we want to change the `Builder` implementation later then we can.
80−-- | Another way this could go:
81−-- | https://kodimensional.dev/posts/2019-03-25-comonadic-builders
83−-- | Our convention is that functions which add `DataView`s to
84−-- | the `Builder` are suffixed with underscore (`_`).
86module Data.ArrayBuffer.Builder.Internal
81module Data.ArrayBuffer.Builder.Internal
87−( Builder(..)
82+ ( Builder(..)
89−, Bytes(..)
84+ , DataBuff(..)
91−, execBuilder
86+ , execBuilder
95−, singleton
90+ , singleton
101−, encodeUint8
93+ , encodeUint8
102−, encodeInt8
94+ , encodeInt8
103−, encodeUint16be
95+ , encodeUint16be
104−, encodeUint16le
96+ , encodeUint16le
105−, encodeInt16be
97+ , encodeInt16be
106−, encodeInt16le
98+ , encodeInt16le
107−, encodeUint32be
99+ , encodeUint32be
108−, encodeUint32le
100+ , encodeUint32le
109−, encodeInt32be
101+ , encodeInt32be
110−, encodeInt32le
102+ , encodeInt32le
111−, encodeFloat32be
103+ , encodeFloat32be
112−, encodeFloat32le
104+ , encodeFloat32le
113−, encodeFloat64be
105+ , encodeFloat64be
114−, encodeFloat64le
106+ , encodeFloat64le
118import Prelude
109import Prelude
111+import Control.Monad.Rec.Class (class MonadRec, Step(..), tailRecM)
120import Data.ArrayBuffer.ArrayBuffer as AB
112import Data.ArrayBuffer.ArrayBuffer as AB
121import Data.ArrayBuffer.DataView as DV
113import Data.ArrayBuffer.DataView as DV
122import Data.ArrayBuffer.Typed as AT
114import Data.ArrayBuffer.Typed as AT
123import Data.ArrayBuffer.Types (ArrayBuffer, ByteLength, DataView, Uint8Array)
115import Data.ArrayBuffer.Types (ArrayBuffer, ByteLength, DataView, Uint8Array)
124import Data.Float32 (Float32)
116import Data.Float32 (Float32)
117+import Data.Identity (Identity)
118+import Data.List (List(..), uncons, (:))
125import Data.Maybe (Maybe(..))
119import Data.Maybe (Maybe(..))
120+import Data.Newtype (unwrap)
126import Data.UInt (UInt)
121import Data.UInt (UInt)
127import Effect (Effect)
122import Effect (Effect)
128import Effect.Class (class MonadEffect, liftEffect)
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`.
134+toView :: DataBuff -> DataView
135+toView (Buff ab) = DV.whole ab
136+toView (View dv) = dv
130-- | Monoidal builder for `ArrayBuffer`s.
138-- | Monoidal builder for `ArrayBuffer`s.
132-- | We can add two types of things to the `Builder`:
140-- | We can add two types of things to the `Builder`:
@@ -138,7 +146,7 @@ import Effect.Class (class MonadEffect, liftEffect)
138-- | from some other `ArrayBuffer`, so that we don’t
146-- | from some other `ArrayBuffer`, so that we don’t
139-- | need an extra intermediate copy of the slice.
147-- | need an extra intermediate copy of the slice.
140data Builder
148data Builder
141− = Node Builder Bytes Builder
149+ = Node Builder DataBuff Builder
144-- | ### Left-associative `<>>` append operator
152-- | ### Left-associative `<>>` append operator
@@ -182,71 +190,62 @@ instance semigroupBuilder :: Semigroup Builder where
182 append Null r = r
190 append Null r = r
183 append l (Node Null rx rr) = Node l rx rr -- this is the snoc case
191 append l (Node Null rx rr) = Node l rx rr -- this is the snoc case
184 append (Node ll lx Null) r = Node ll lx r -- found the rightmost Null
192 append (Node ll lx Null) r = Node ll lx r -- found the rightmost Null
185− -- of the left tree
193+ -- of the left tree
186 append (Node ll lx lr) r = Node ll lx $ append lr r -- search down the right
194 append (Node ll lx lr) r = Node ll lx $ append lr r -- search down the right
187− -- side of the left tree
196+-- side of the left tree
188− -- to find the rightmost
197+-- to find the rightmost
191infixl 5 append as <>>
200infixl 5 append as <>>
193instance monoidBuilder :: Monoid Builder where
202instance monoidBuilder :: Monoid Builder where
194 mempty = Null
203 mempty = Null
196−-- | For distinguishing between `ArrayBuffer` and `DataView`.
205+-- | Stack-safe `foldl` over a `Builder`. *O(n)*
198− = ByteBuff ArrayBuffer
201−-- | `Builder` is not an instance of `Foldable` because `Builder` is monomorphic.
202−foldl :: forall b. (b -> Bytes -> b) -> b -> Builder -> b
206+foldl :: forall a. (a -> DataBuff -> a) -> a -> Builder -> a
203−foldl _ a Null = a
207+foldl f a0 b = (unwrap :: Identity a -> a) $ foldM (\a' b' -> pure (f a' b')) a0 b
204−foldl f a (Node l x r) = foldl f (f (foldl f a l) x) r
206−-- | Monomorphic foldM copied from
207−-- | [`Data.Foldable.foldM`](https://pursuit.purescript.org/packages/purescript-foldable-traversable/4.1.1/docs/Data.Foldable#v:foldM)
208−foldM :: forall b m. (Monad m) => (b -> Bytes -> m b) -> b -> Builder -> m b
209−foldM f a0 = foldl (\ma b -> ma >>= flip f b) (pure a0)
211−-- | Construct a `Builder` with a single `ArrayBuffer`. *O(1)*
209+-- | Stack-safe `foldM` over a `Builder`. *O(n)*.
212−singleton :: ArrayBuffer -> Builder
210+foldM :: forall m a. MonadRec m => (a -> DataBuff -> m a) -> a -> Builder -> m a
213−singleton buf = Node Null (ByteBuff buf) Null
211+-- The Art of Computer Programming 2.3.1
214−
212+-- Inorder traversal using a stack
213+-- https://yuyuan.org/MorrisAlgorithm/
214+foldM 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_ }
215−-- | Construct a `Builder` with a single `DataView`. *O(1)*
227+-- | Construct a `Builder` with a single `DataBuff`. *O(1)*
216−singleton_ :: DataView -> Builder
228+singleton :: DataBuff -> Builder
217−singleton_ buf = Node Null (ByteView buf) Null
229+singleton buf = Node Null buf Null
219-- | Calculate the total byte length of the `Builder`, without actually
231-- | Calculate the total byte length of the `Builder`, without actually
220-- | building it yet. *O(n)*
232-- | building it yet. *O(n)*
221length :: Builder -> ByteLength
233length :: Builder -> ByteLength
222length bldr = foldl (\b a -> b + len a) 0 bldr
234length bldr = foldl (\b a -> b + len a) 0 bldr
224− len (ByteBuff ab) = AB.byteLength ab
236+ len (Buff ab) = AB.byteLength ab
225− len (ByteView dv) = DV.byteLength dv
237+ len (View dv) = DV.byteLength dv
227− -- | View the contents of `Bytes` as a `DataView`.
239+-- | Prepend a `DataBuff` to the beginning of the `Builder`. *O(1)*
228−toView :: Bytes -> DataView
240+cons :: DataBuff -> Builder -> Builder
229−toView (ByteBuff ab) = DV.whole ab
241+cons x bs = Node Null x bs
230−toView (ByteView dv) = dv
232−-- | Prepend an `ArrayBuffer` to the beginning of the `Builder`. *O(1)*
233−cons :: ArrayBuffer -> Builder -> Builder
234−cons x bs = Node Null (ByteBuff x) bs
236−-- | Prepend a `DataView` to the beginning of the `Builder`. *O(1)*
237−cons_ :: DataView -> Builder -> Builder
238−cons_ x bs = Node Null (ByteView x) bs
240−-- | Append an `ArrayBuffer` to the end of the `Builder`. *O(1)*
243+-- | Append a `DataBuff` to the end of the `Builder`. *O(1)*
241−snoc :: Builder -> ArrayBuffer -> Builder
244+snoc :: Builder -> DataBuff -> Builder
242−snoc bs x = Node bs (ByteBuff x) Null
245+snoc bs x = Node bs x Null
244−-- | Append a `DataView` to the end of the `Builder`. *O(1)*
245−snoc_ :: Builder -> DataView -> Builder
246−snoc_ bs x = Node bs (ByteView x) Null
248-- | Build a single `ArrayBuffer` from a `Builder`. *O(n)*
247-- | Build a single `ArrayBuffer` from a `Builder`. *O(n)*
249−execBuilder :: forall m. (MonadEffect m) => Builder -> m ArrayBuffer
248+execBuilder :: forall m. MonadEffect m => MonadRec m => Builder -> m ArrayBuffer
250execBuilder bldr = do
249execBuilder bldr = do
251 let buflen = length bldr
250 let buflen = length bldr
252 -- Allocate the final ArrayBuffer
251 -- Allocate the final ArrayBuffer
@@ -260,12 +259,14 @@ execBuilder bldr = do
260 aview <- liftEffect $ toUint8Array a
259 aview <- liftEffect $ toUint8Array a
261 _ <- liftEffect $ AT.setTyped newview (Just offset) aview
260 _ <- liftEffect $ AT.setTyped newview (Just offset) aview
262 pure $ offset + AT.byteLength aview
261 pure $ offset + AT.byteLength aview
266− toUint8Array :: Bytes -> Effect Uint8Array
267+ toUint8Array :: DataBuff -> Effect Uint8Array
267− toUint8Array (ByteBuff ab) = AT.whole ab
268+ toUint8Array (Buff ab) = AT.whole ab
268− toUint8Array (ByteView dv) =
269+ toUint8Array (View dv) =
269 AT.part (DV.buffer dv) (DV.byteOffset dv) (DV.byteLength dv)
270 AT.part (DV.buffer dv) (DV.byteOffset dv) (DV.byteLength dv)
271-- | Serialize an 8-bit unsigned integer (byte) into a new `ArrayBuffer`.
272-- | Serialize an 8-bit unsigned integer (byte) into a new `ArrayBuffer`.