Parse Bytes
with custom error reporting and context tracking.
A decoder for a certain type of value.
decode : Decoder value -> Bytes -> Maybe value
Run the given decoder on the provided bytes.
import Bytes.Encode as E
import Bytes.Decode.Branchable as D
E.string "hello"
|> E.encode
|> D.decode (D.string 5)
--> Just "hello"
E.string "hello"
|> E.encode
|> D.decode (D.string 6)
--> Nothing
succeed : value -> Decoder value
Always succeed with the given value.
import Bytes.Encode as E
import Bytes.Decode.Branchable as D
E.encode (E.sequence [])
|> D.decode (D.succeed "hi there")
--> Just "hi there"
fail : Decoder value
A Decoder that always fails.
import Bytes.Encode as E
import Bytes.Decode.Branchable as D
E.sequence []
|> E.encode
|> D.decode D.fail
--> Nothing
unsignedInt8 : Decoder Basics.Int
Decode one byte into an integer from 0 to 255.
unsignedInt16 : Bytes.Endianness -> Decoder Basics.Int
Decode two bytes into an integer from 0 to 65535.
unsignedInt32 : Bytes.Endianness -> Decoder Basics.Int
Decode four bytes into an integer from 0 to 4294967295.
signedInt8 : Decoder Basics.Int
Decode one byte into an integer from -128 to 127.
signedInt16 : Bytes.Endianness -> Decoder Basics.Int
Decode two bytes into an integer from -32768 to 32767.
signedInt32 : Bytes.Endianness -> Decoder Basics.Int
Decode four bytes into an integer from -2147483648 to 2147483647.
float32 : Bytes.Endianness -> Decoder Basics.Float
Decode 4 bytes into a Float.
float64 : Bytes.Endianness -> Decoder Basics.Float
Decode 8 bytes into a Float.
string : Basics.Int -> Decoder String
Decode count
bytes representing UTF-8 characters into a String.
Note that Elm strings use UTF-16. As a result, the String.length
will not
always agree with the number of bytes that went into it!
import Bytes.Encode as E
import Bytes.Decode.Branchable as D
[ 0xF0, 0x9F, 0x91, 0x8D ]
|> List.map E.unsignedInt8
|> E.sequence
|> E.encode
|> D.decode (D.string 4)
--> Just "👍"
bytes : Basics.Int -> Decoder Bytes
Parse count
bytes as Bytes
.
map : (a -> b) -> Decoder a -> Decoder b
Transform the value a decoder produces
import Bytes.Encode as E
import Bytes.Decode.Branchable as D
E.string "hello"
|> E.encode
|> D.decode (D.map String.length (D.string 5))
--> Just 5
map2 : (x -> y -> z) -> Decoder x -> Decoder y -> Decoder z
Combine two decoders into a single one.
import Bytes exposing (Bytes)
import Bytes.Encode as E
import Bytes.Decode.Branchable as D exposing (Decoder)
input : Bytes
input =
[ E.unsignedInt8 2
, E.string "wat"
]
|> E.sequence
|> E.encode
map2Example : Decoder String
map2Example =
D.map2 String.repeat D.unsignedInt8 (D.string 3)
D.decode map2Example input
--> Just "watwat"
Note that the effect of map2
(and, in fact, every map
variation) can also be
achieved using a combination of succeed
and keep
.
equivalent : Decoder String
equivalent =
D.succeed String.repeat
|> D.keep D.unsignedInt8
|> D.keep (D.string 3)
D.decode equivalent input
--> Just "watwat"
map3 : (w -> x -> y -> z) -> Decoder w -> Decoder x -> Decoder y -> Decoder z
Combine 3 decoders into a single one.
map4 : (v -> w -> x -> y -> z) -> Decoder v -> Decoder w -> Decoder x -> Decoder y -> Decoder z
Combine 4 decoders into a single one.
map5 : (u -> v -> w -> x -> y -> z) -> Decoder u -> Decoder v -> Decoder w -> Decoder x -> Decoder y -> Decoder z
Combine 5 decoders into a single one.
keep : Decoder a -> Decoder (a -> b) -> Decoder b
Keep the value produced by a decoder in a pipeline.
Together with succeed
and ignore
, this allows writing
pretty flexible decoders in a straightforward manner: the order in which things
are parsed is apparent.
import Bytes.Encode as E
import Bytes.Decode.Branchable as D exposing (Decoder)
decoder : Decoder (Int, Int)
decoder =
D.succeed Tuple.pair
|> D.keep D.unsignedInt8
|> D.ignore D.unsignedInt8
|> D.keep D.unsignedInt8
[ E.unsignedInt8 12
, E.unsignedInt8 3
, E.unsignedInt8 45
]
|> E.sequence
|> E.encode
|> D.decode decoder
--> Just ( 12, 45 )
ignore : Decoder ignore -> Decoder keep -> Decoder keep
Ignore the value produced by a decoder.
Note that the decoder must still succeed for the pipeline to succeed. This means you can use this for checking the value of something, without using the value.
import Bytes.Encode as E
import Bytes.Decode.Branchable as D exposing (Decoder)
match : Int -> Decoder Int
match expected =
D.unsignedInt8
|> D.andThen
(\actual ->
if expected == actual then
D.succeed actual
else
D.fail
)
decoder : Decoder ()
decoder =
D.succeed ()
|> D.ignore (match 66)
E.unsignedInt8 66
|> E.encode
|> D.decode decoder
--> Just ()
E.unsignedInt8 44
|> E.encode
|> D.decode decoder
--> Nothing
skip : Basics.Int -> Decoder value -> Decoder value
Skip a number of bytes in a pipeline.
This is similar to ignore
, but rather than decoding a value and discarding it,
this just goes ahead and skips them altogether.
andThen : (a -> Decoder b) -> Decoder a -> Decoder b
Decode one thing, and then another thing based on the first thing.
This is very useful to make the content of your data drive your decoder. As an example, consider a string encoded as the length of the string, followed by the actual data:
import Bytes.Encode as E
import Bytes.Decode.Branchable as D exposing (Decoder)
string : Decoder String
string =
D.unsignedInt8
|> D.andThen D.string
[ E.unsignedInt8 5
, E.string "hello"
]
|> E.sequence
|> E.encode
|> D.decode string
--> Just "hello"
oneOf : List (Decoder value) -> Decoder value
Tries a bunch of decoders and succeeds with the first one to succeed.
All decoder alternatives start at the same location in the bytes.
repeat : Decoder value -> Basics.Int -> Decoder (List value)
Repeat a given decoder count
times.
The order of arguments is based on the common occurence of reading the number of times to repeat something through a decoder.
import Bytes.Encode as E
import Bytes.Decode.Branchable as D exposing (Decoder)
intList : Decoder (List Int)
intList =
D.unsignedInt8
|> D.andThen (D.repeat D.unsignedInt8)
[ 5, 0, 1, 2, 3, 4 ]
|> List.map E.unsignedInt8
|> E.sequence
|> E.encode
|> D.decode intList
--> Just [ 0, 1, 2, 3, 4 ]
loop : state -> (state -> Decoder (Bytes.Decode.Step state a)) -> Decoder a
Loop a decoder until it declares it is done looping.
The Step
type in the signature comes from the Bytes.Decode
module in elm/bytes.
Here is how repeat
is defined for example.
import Bytes.Decode
import Bytes.Decode.Branchable as D exposing (Decoder)
repeat : Decoder value -> Int -> Decoder (List value)
repeat p count =
D.loop ( count, [] ) (repeatHelp p)
repeatHelp p ( count, acc ) =
if count <= 0 then
succeed (Bytes.Decode.Done (List.reverse acc))
else
map (\v -> Bytes.Decode.Loop ( count - 1, v :: acc )) p