git.iko.soy/rowtype-yoga/purescript-arraybuffer-builder
on master help sign in
Add cons, length, subBuilder
9a795f5James Brock
CHANGELOG.mdnew file+7
@@ -0,0 +1,7 @@
1+## v1.1.0 2020-09-10
2+
3+Add and export
4+
5+* `cons :: ArrayBuffer -> Builder -> Builder`
6+* `subBuilder :: forall m. (MonadEffect m) => PutM m Unit -> PutM m Builder`
7+* `length :: Builder -> Int`
DEVELOPMENT.md+2−3
@@ -34,10 +34,9 @@ The test dependencies are specified seperately in `test.dhall`.
34
34
35```
35```
36[nix-shell:]$ spago bump-version --no-dry-run major
36[nix-shell:]$ spago bump-version --no-dry-run major
37−[nix-shell:]$ npm install pulp
37+[nix-shell:]$ pulp publish
38−[nix-shell:]$ node node_modules/pulp/index.js publish
39```
38```
40
39
41## Bower
40## Bower
42
41
43−The `bower.json` is generated by running `spago bump-version`, do not edit it.
42+The `bower.json` is generated by running `spago bump-version`, do not edit it.
shell.nix+3−7
@@ -4,8 +4,8 @@ let
4 (pkgs.fetchFromGitHub {
4 (pkgs.fetchFromGitHub {
5 owner = "justinwoo";
5 owner = "justinwoo";
6 repo = "easy-purescript-nix";
6 repo = "easy-purescript-nix";
7− rev = "0ba91d9aa9f7421f6bfe4895677159a8a999bf20";
7+ rev = "1ec689df0adf8e8ada7fcfcb513876307ea34226";
8− sha256 = "1baq7mmd3vjas87f0gzlq83n2l1h3dlqajjqr7fgaazpa9xgzs7q";
8+ sha256 = "12hk2zbjkrq2i5fs6xb3x254lnhm9fzkcxph0a7ngxyzfykvf4hi";
9 }) {
9 }) {
10 inherit pkgs;
10 inherit pkgs;
11 };
11 };
@@ -14,13 +14,9 @@ pkgs.mkShell {
14 buildInputs = [
14 buildInputs = [
15 easy-ps.purs-0_13_8
15 easy-ps.purs-0_13_8
16 easy-ps.spago
16 easy-ps.spago
17+ easy-ps.pulp
17 pkgs.nodejs-13_x
18 pkgs.nodejs-13_x
18 pkgs.nodePackages.bower
19 pkgs.nodePackages.bower
19− # pkgs.nodePackages.pulp
20− #
21− # The pulp nix derivation doesn't work, so to run pulp:
22− # $ npm install pulp
23− # $ node node_modules/pulp/index.js
24 ];
20 ];
25 LC_ALL = "C.UTF-8"; # https://github.com/purescript/spago/issues/507
21 LC_ALL = "C.UTF-8"; # https://github.com/purescript/spago/issues/507
26}
22}
src/Data/ArrayBuffer/Builder.purs+41−5
@@ -14,6 +14,7 @@ module Data.ArrayBuffer.Builder
14, Put
14, Put
15, execPutM
15, execPutM
16, execPut
16, execPut
17+, subBuilder
17, putArrayBuffer
18, putArrayBuffer
18, putUint8
19, putUint8
19, putInt8
20, putInt8
@@ -32,7 +33,9 @@ module Data.ArrayBuffer.Builder
32, Builder
33, Builder
33, (<>>)
34, (<>>)
34, execBuilder
35, execBuilder
36+, length
35, singleton
37, singleton
38+, cons
36, snoc
39, snoc
37, encodeUint8
40, encodeUint8
38, encodeInt8
41, encodeInt8
@@ -53,7 +56,7 @@ where
53
56
54import Prelude
57import Prelude
55
58
56−import Control.Monad.Writer.Trans (WriterT, execWriterT, tell)
59+import Control.Monad.Writer.Trans (WriterT, execWriterT, tell, lift)
57import Data.ArrayBuffer.ArrayBuffer as AB
60import Data.ArrayBuffer.ArrayBuffer as AB
58import Data.ArrayBuffer.DataView as DV
61import Data.ArrayBuffer.DataView as DV
59import Data.ArrayBuffer.Typed as AT
62import Data.ArrayBuffer.Typed as AT
@@ -67,10 +70,12 @@ import Effect.Class (class MonadEffect, liftEffect)
67-- | The `PutM` monad is a `WriterT Builder` transformer monad which
70-- | The `PutM` monad is a `WriterT Builder` transformer monad which
68-- | gives us do-notation for the `Builder` monoid. The base monad must be
71-- | gives us do-notation for the `Builder` monoid. The base monad must be
69-- | a `MonadEffect`.
72-- | a `MonadEffect`.
73+-- |
74+-- | To append `Builder`s in this monad call `tell`, or any of the `put*`
75+-- | functions in this module.
70type PutM = WriterT Builder
76type PutM = WriterT Builder
71
77
72−-- | The `Put` monad is a `WriterT Builder Effect` monad which
78+-- | The `PutM` type reified to `Effect`, in other words `WriterT Builder Effect`.
73−-- | gives us do-notation for the `Builder` monoid.
74type Put = PutM Effect
79type Put = PutM Effect
75
80
76-- | Monoidal builder for `ArrayBuffer`s.
81-- | Monoidal builder for `ArrayBuffer`s.
@@ -143,10 +148,18 @@ foldM f a0 = foldl (\ma b -> ma >>= flip f b) (pure a0)
143singleton :: ArrayBuffer -> Builder
148singleton :: ArrayBuffer -> Builder
144singleton buf = Node Null buf Null
149singleton buf = Node Null buf Null
145
150
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+
146−-- | Add an `ArrayBuffer` to the end of the `Builder`. *O(1)*
155+-- | Append an `ArrayBuffer` to the end of the `Builder`. *O(1)*
147snoc :: Builder -> ArrayBuffer -> Builder
156snoc :: Builder -> ArrayBuffer -> Builder
148snoc bs x = Node bs x Null
157snoc bs x = Node bs x Null
149
158
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+
150-- | Build a single `ArrayBuffer` from a `Builder`. *O(n)*
163-- | Build a single `ArrayBuffer` from a `Builder`. *O(n)*
151execBuilder :: forall m. (MonadEffect m) => Builder -> m ArrayBuffer
164execBuilder :: forall m. (MonadEffect m) => Builder -> m ArrayBuffer
152execBuilder bldr = do
165execBuilder bldr = do
@@ -173,6 +186,28 @@ execPutM = execBuilder <=< execWriterT
173execPut :: Put Unit -> Effect ArrayBuffer
186execPut :: Put Unit -> Effect ArrayBuffer
174execPut = execPutM
187execPut = execPutM
175
188
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+
176-- | Append an `ArrayBuffer` to the builder.
211-- | Append an `ArrayBuffer` to the builder.
177putArrayBuffer :: forall m. (MonadEffect m) => ArrayBuffer -> PutM m Unit
212putArrayBuffer :: forall m. (MonadEffect m) => ArrayBuffer -> PutM m Unit
178putArrayBuffer = tell <<< singleton
213putArrayBuffer = tell <<< singleton
@@ -404,4 +439,5 @@ putFloat64le = putArrayBuffer <=< encodeFloat64le
404-- so that if we want to change the Builder implementation later then we can.
439-- so that if we want to change the Builder implementation later then we can.
405--
440--
406-- Another way this could go:
441-- 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+
test/Main.purs+19−1
@@ -7,6 +7,7 @@ import Data.UInt as UInt
7import Effect (Effect)
7import Effect (Effect)
8import Test.Assert (assertEqual')
8import Test.Assert (assertEqual')
9import Data.ArrayBuffer.Builder
9import Data.ArrayBuffer.Builder
10+import Control.Monad.Writer.Trans (tell)
10
11
11asBytes :: ArrayBuffer -> Effect (Array Int)
12asBytes :: ArrayBuffer -> Effect (Array Int)
12asBytes x = do
13asBytes x = do
@@ -79,4 +80,21 @@ main = do
79 b7 <- encodeInt8 7
80 b7 <- encodeInt8 7
80 b8 <- encodeInt8 8
81 b8 <- encodeInt8 8
81 pure $ ((singleton b1 <> singleton b2) <> (singleton b3 <> singleton b4))
82 pure $ ((singleton b1 <> singleton b2) <> (singleton b3 <> singleton b4))
82− <> ((singleton b5 <> singleton b6) <> (singleton b7 <> singleton b8))
83+ <> ((singleton b5 <> singleton b6) <> (singleton b7 <> singleton b8))
84+
85+ buildTest "Test 8" [1,2,3,4] $ do
86+ b1 <- encodeInt8 1
87+ b2 <- encodeInt8 2
88+ b3 <- encodeInt8 3
89+ b4 <- encodeInt8 4
90+ pure $ (cons b1 (singleton b2)) <> (cons b3 (singleton b4))
91+
92+ putTest "Test 9" [1,2,2,3,4] $ do
93+ x <- subBuilder $ do
94+ putInt8 3
95+ putInt8 4
96+ putInt8 1
97+ putInt8 2
98+ putInt8 $ length x
99+ tell x
100+