on master help sign in
Initial commit
commit3c6e943097f76300dc1ff53a6cfa236771b6fd7f authoriko <mail@iko.soy> dateSat 27 May 2023, 16:58 (3 years ago)
7 files changed+350
.gitignore+1 dev/test.sh+7 elm.json+19 src/BitParser.elm+133 src/BitWriter.elm+81 src/Urbit.elm+54 tests/Test/BitParser.elm+55
.gitignorenew file+1
@@ -0,0 +1 @@
1+elm-stuff
dev/test.shnew file+7
@@ -0,0 +1,7 @@
1+#!/bin/sh
2+
3+set -e
4+
5+cd "$(dirname "$0")/.."
6+
7+nix run github:NixOS/nixpkgs#elmPackages.elm-test-rs -- "$@"
elm.jsonnew file+19
@@ -0,0 +1,19 @@
1+{
2+ "type": "package",
3+ "name": "ilyakooo0/airlock",
4+ "summary": "Urbit airlock",
5+ "license": "MIT",
6+ "version": "1.0.0",
7+ "exposed-modules": [
8+ "Urbit"
9+ ],
10+ "elm-version": "0.19.0 <= v < 0.20.0",
11+ "dependencies": {
12+ "elm/bytes": "1.0.8 <= v < 2.0.0",
13+ "elm/core": "1.0.0 <= v < 1.0.5"
14+ },
15+ "test-dependencies": {
16+ "TSFoster/elm-bytes-extra": "1.3.0 <= v < 1.4.0",
17+ "elm-explorations/test": "1.0.0 <= v < 2.0.0"
18+ }
19+}
src/BitParser.elmnew file+133
@@ -0,0 +1,133 @@
1+module BitParser exposing
2+ ( BitParser
3+ , andThen
4+ , bit
5+ , bits
6+ , bitsToInt
7+ , fail
8+ , intBits
9+ , map
10+ , rawBits
11+ , run
12+ , succeed
13+ )
14+
15+import Bitwise
16+import Bytes exposing (Bytes)
17+import Bytes.Decode as BD
18+import Bytes.Encode as BE
19+
20+
21+run : BitParser a -> Bytes -> Maybe a
22+run (BitParser f) bytes =
23+ BD.decode (f { running = Nothing }) bytes |> Maybe.map Tuple.second
24+
25+
26+type alias BitParserState =
27+ { running : Maybe { byte : Int, length : Int }
28+ }
29+
30+
31+type BitParser a
32+ = BitParser (BitParserState -> BD.Decoder ( BitParserState, a ))
33+
34+
35+bit : BitParser Int
36+bit =
37+ BitParser
38+ (\{ running } ->
39+ case running of
40+ Nothing ->
41+ BD.unsignedInt8
42+ |> BD.map
43+ (\i ->
44+ ( { running = Just { byte = Bitwise.shiftRightBy 1 i, length = 7 } }
45+ , Bitwise.and 1 i
46+ )
47+ )
48+
49+ Just { byte, length } ->
50+ BD.succeed
51+ (if length == 1 then
52+ ( { running = Nothing }, byte )
53+
54+ else
55+ ( { running = Just { byte = Bitwise.shiftRightBy 1 byte, length = length - 1 } }
56+ , Bitwise.and 1 byte
57+ )
58+ )
59+ )
60+
61+
62+rawBits : Int -> BitParser (List Int)
63+rawBits n =
64+ let
65+ go i =
66+ if i <= 0 then
67+ succeed []
68+
69+ else
70+ bit |> andThen (\b -> map (\x -> b :: x) (go (i - 1)))
71+ in
72+ go n
73+
74+
75+
76+-- PERF: use `bytes` on byte boundaries
77+
78+
79+bits : Int -> BitParser Bytes
80+bits n =
81+ rawBits n |> map bitsToBytes
82+
83+
84+intBits : Int -> BitParser Int
85+intBits n =
86+ rawBits n |> map bitsToInt
87+
88+
89+bitsToInt : List Int -> Int
90+bitsToInt =
91+ List.foldr (\b acc -> Bitwise.shiftLeftBy 1 acc |> Bitwise.or b) 0
92+
93+
94+bitsToBytes : List Int -> Bytes
95+bitsToBytes bs =
96+ if bs == [] then
97+ emptyBytes
98+
99+ else
100+ let
101+ go bb =
102+ if List.length bb > 8 then
103+ BE.unsignedInt8 (List.take 8 bb |> bitsToInt) :: go (List.drop 8 bb)
104+
105+ else
106+ [ BE.unsignedInt8 (bb |> bitsToInt) ]
107+ in
108+ go bs |> BE.sequence |> BE.encode
109+
110+
111+emptyBytes : Bytes
112+emptyBytes =
113+ BE.encode (BE.sequence [])
114+
115+
116+andThen : (a -> BitParser b) -> BitParser a -> BitParser b
117+andThen f (BitParser g) =
118+ BitParser (\s -> g s |> BD.andThen (\( s1, a ) -> f a |> (\(BitParser h) -> h s1)))
119+
120+
121+map : (a -> b) -> BitParser a -> BitParser b
122+map f (BitParser g) =
123+ BitParser (\s -> g s |> BD.map (Tuple.mapSecond f))
124+
125+
126+succeed : a -> BitParser a
127+succeed a =
128+ BitParser (\state -> BD.succeed ( state, a ))
129+
130+
131+fail : BitParser a
132+fail =
133+ BitParser (always BD.fail)
src/BitWriter.elmnew file+81
@@ -0,0 +1,81 @@
1+module BitWriter exposing (..)
2+
3+import BitParser as BP
4+import Bitwise
5+import Bytes exposing (Bytes)
6+import Bytes.Encode as BE
7+
8+
9+empty : BitWriter
10+empty =
11+ BitWriter { running = Nothing, collected = BE.sequence [] }
12+
13+
14+run : BitWriter -> Bytes
15+run (BitWriter { running, collected }) =
16+ case running of
17+ Nothing ->
18+ BE.encode collected
19+
20+ Just { value } ->
21+ BE.sequence [ collected, BE.unsignedInt8 value ] |> BE.encode
22+
23+
24+type BitWriter
25+ = BitWriter
26+ { running :
27+ Maybe
28+ { value : Int
29+ , length : Int
30+ }
31+ , collected : BE.Encoder
32+ }
33+
34+
35+{-| If the given int is longer than 1 bit the behaviour is undefined
36+-}
37+bit : Int -> BitWriter -> BitWriter
38+bit b (BitWriter { running, collected }) =
39+ case running of
40+ Nothing ->
41+ BitWriter { running = Just { value = b, length = 1 }, collected = collected }
42+
43+ Just { value, length } ->
44+ let
45+ newValue =
46+ Bitwise.or (Bitwise.shiftLeftBy length b) value
47+ in
48+ if length == 7 then
49+ BitWriter { running = Nothing, collected = BE.sequence [ collected, BE.unsignedInt8 newValue ] }
50+
51+ else
52+ BitWriter { running = Just { value = newValue, length = length + 1 }, collected = collected }
53+
54+
55+
56+-- PERF: Collect 8-bit chunks at boundaries and add them without going through bit
57+
58+
59+bits : List Int -> BitWriter -> BitWriter
60+bits bs writer =
61+ case bs of
62+ [] ->
63+ writer
64+
65+ b :: bb ->
66+ bits bb (bit b writer)
67+
68+
69+
70+-- PERF: don't go through BitParser to not hold all bits in memory
71+
72+
73+bytes : Bytes -> BitWriter -> BitWriter
74+bytes bite writer =
75+ case BP.run (BP.rawBits (Bytes.width bite * 8)) bite of
76+ -- this is bad, but shouldn't happen
77+ Nothing ->
78+ writer
79+
80+ Just bs ->
81+ bits bs writer
src/Urbit.elmnew file+54
@@ -0,0 +1,54 @@
1+module Urbit exposing (..)
2+
3+import BitParser as BP exposing (BitParser)
4+import Bytes exposing (Bytes)
5+import Bytes.Decode as BD
6+import Bytes.Encode as BE
7+
8+
9+type Noun
10+ = Cell ( Noun, Noun )
11+ | Atom Bytes
12+
13+
14+rub : BitParser Bytes
15+rub =
16+ BP.bit
17+ |> BP.andThen
18+ (\zeroBit ->
19+ if zeroBit == 1 then
20+ BP.succeed (BE.unsignedInt8 0 |> BE.encode)
21+
22+ else
23+ let
24+ countZeros n =
25+ BP.bit
26+ |> BP.andThen
27+ (\b ->
28+ if b == 0 then
29+ countZeros (n + 1)
30+
31+ else
32+ BP.succeed n
33+ )
34+ in
35+ countZeros 0
36+ |> BP.andThen
37+ (\lengthOfLength ->
38+ BP.rawBits lengthOfLength
39+ |> BP.andThen
40+ (\preLengthRawBits ->
41+ let
42+ length =
43+ BP.bitsToInt (1 :: preLengthRawBits)
44+ in
45+ BP.bits length
46+ )
47+ )
48+ )
49+
50+
51+
52+-- met : BitParser Bytes
53+-- met =
54+-- 1
tests/Test/BitParser.elmnew file+55
@@ -0,0 +1,55 @@
1+module Test.BitParser exposing (tests)
2+
3+import BitParser
4+import BitWriter
5+import Bytes exposing (Bytes)
6+import Bytes.Encode as BE
7+import Bytes.Extra
8+import Expect exposing (Expectation)
9+import Fuzz exposing (Fuzzer)
10+import Test exposing (..)
11+
12+
13+tests : Test
14+tests =
15+ concat
16+ [ describe "BitParser"
17+ [ fuzz bytes
18+ "parsed bytes are identical"
19+ (\b ->
20+ maybeBytesEq (Just b) (BitParser.run (BitParser.bits (Bytes.width b * 8)) b)
21+ )
22+ ]
23+ , describe "BitWriter"
24+ [ fuzz bytes
25+ "printed bytes are identical"
26+ (\b ->
27+ let
28+ bits =
29+ BitParser.run (BitParser.rawBits (Bytes.width b * 8)) b
30+ in
31+ case bits of
32+ Nothing ->
33+ Expect.fail "unexepcted"
34+
35+ Just bits_ ->
36+ maybeBytesEq (Just b) (BitWriter.empty |> BitWriter.bits bits_ |> BitWriter.run |> Just)
37+ )
38+ ]
39+ ]
40+
41+
42+bytes : Fuzzer Bytes
43+bytes =
44+ (Fuzz.intRange 0 255 |> Fuzz.list)
45+ |> Fuzz.map (\l -> l |> List.map BE.unsignedInt8 |> BE.sequence |> BE.encode)
46+
47+
48+bytesEq : Bytes -> Bytes -> Expectation
49+bytesEq a b =
50+ Expect.equal (Bytes.Extra.toByteValues a) (Bytes.Extra.toByteValues b)
51+
52+
53+maybeBytesEq : Maybe Bytes -> Maybe Bytes -> Expectation
54+maybeBytesEq a b =
55+ Expect.equal (Maybe.map Bytes.Extra.toByteValues a) (Maybe.map Bytes.Extra.toByteValues b)