@@ -14,6 +14,7 @@ module Data.ArrayBuffer.Builder
1414, Put
1515, execPutM
1616, execPut
17+, subBuilder
1718, putArrayBuffer
1819, putUint8
1920, putInt8
@@ -32,7 +33,9 @@ module Data.ArrayBuffer.Builder
3233, Builder
3334, (<>>)
3435, execBuilder
36+, length
3537, singleton
38+, cons
3639, snoc
3740, encodeUint8
3841, encodeInt8
@@ -53,7 +56,7 @@ where
5356
5457import Prelude
5558
56−import Control.Monad.Writer.Trans (WriterT, execWriterT, tell)
59+import Control.Monad.Writer.Trans (WriterT, execWriterT, tell, lift)
5760import Data.ArrayBuffer.ArrayBuffer as AB
5861import Data.ArrayBuffer.DataView as DV
5962import Data.ArrayBuffer.Typed as AT
@@ -67,10 +70,12 @@ import Effect.Class (class MonadEffect, liftEffect)
6770-- | The `PutM` monad is a `WriterT Builder` transformer monad which
6871-- | gives us do-notation for the `Builder` monoid. The base monad must be
6972-- | a `MonadEffect`.
73+-- |
74+-- | To append `Builder`s in this monad call `tell`, or any of the `put*`
75+-- | functions in this module.
7076type PutM = WriterT Builder
7177
72−-- | The `Put` monad is a `WriterT Builder Effect` monad which
73−-- | gives us do-notation for the `Builder` monoid.
78+-- | The `PutM` type reified to `Effect`, in other words `WriterT Builder Effect`.
7479type Put = PutM Effect
7580
7681-- | Monoidal builder for `ArrayBuffer`s.
@@ -143,10 +148,18 @@ foldM f a0 = foldl (\ma b -> ma >>= flip f b) (pure a0)
143148singleton :: ArrayBuffer -> Builder
144149singleton buf = Node Null buf Null
145150
146−-- | Add an `ArrayBuffer` to the end of the `Builder`. *O(1)*
151+-- | Prepend an `ArrayBuffer` to the beginning of the `Builder`. *O(1)*
152+cons :: ArrayBuffer -> Builder -> Builder
153+cons x bs = Node Null x bs
154+
155+-- | Append an `ArrayBuffer` to the end of the `Builder`. *O(1)*
147156snoc :: Builder -> ArrayBuffer -> Builder
148157snoc bs x = Node bs x Null
149158
159+-- | Calculate the total byte length of the `Builder`. *O(n)*
160+length :: Builder -> Int
161+length bldr = foldl (\b a -> b + AB.byteLength a) 0 bldr
162+
150163-- | Build a single `ArrayBuffer` from a `Builder`. *O(n)*
151164execBuilder :: forall m. (MonadEffect m) => Builder -> m ArrayBuffer
152165execBuilder bldr = do
@@ -173,6 +186,28 @@ execPutM = execBuilder <=< execWriterT
173186execPut :: Put Unit -> Effect ArrayBuffer
174187execPut = execPutM
175188
189+-- | Build up a sub-`Builder` without `tell`ing it to the `Writer` yet.
190+-- |
191+-- | One case where we might want to call a `subBuilder` is when
192+-- | serializing length-prefixed messages in some protocol. In that case,
193+-- | we must serialize the message first, calculate the message length,
194+-- | append the message length, and then append the message.
195+-- |
196+-- | In a `PutM` monad do-block, we can
197+-- |
198+-- |
199+-- | ```
200+-- | do
201+-- | messageBuilder <- subBuilder $ do
202+-- | putField1
203+-- | putField2
204+-- |
205+-- | putInt32 $ length messageBuilder
206+-- | tell messageBuilder
207+-- | ```
208+subBuilder :: forall m. (MonadEffect m) => PutM m Unit -> PutM m Builder
209+subBuilder = lift <<< execWriterT
210+
176211-- | Append an `ArrayBuffer` to the builder.
177212putArrayBuffer :: forall m. (MonadEffect m) => ArrayBuffer -> PutM m Unit
178213putArrayBuffer = tell <<< singleton
@@ -404,4 +439,5 @@ putFloat64le = putArrayBuffer <=< encodeFloat64le
404439-- so that if we want to change the Builder implementation later then we can.
405440--
406441-- Another way this could go:
407−-- https://kodimensional.dev/posts/2019-03-25-comonadic-builders
442+-- https://kodimensional.dev/posts/2019-03-25-comonadic-builders
443+