Turn a sequence of bytes into a nice Elm value.
-- decode (unsignedInt16 BE) <0007> == Just 7
-- decode (unsignedInt16 LE) <0700> == Just 7
-- decode (unsignedInt16 BE) <0700> == Just 1792
-- decode (unsignedInt32 BE) <0700> == NothingThe Decoder specifies exactly how this should happen. This process may fail
if the sequence of bytes is corrupted or unexpected somehow. The examples above
show a case where there are not enough bytes.
Decode one byte into an integer from -128 to 127.
Decode two bytes into an integer from -32768 to 32767.
Decode four bytes into an integer from -2147483648 to 2147483647.
Decode one byte into an integer from 0 to 255.
Decode two bytes into an integer from 0 to 65535.
Decode four bytes into an integer from 0 to 4294967295.
Decode four bytes into a floating point number.
Decode eight bytes into a floating point number.
Decode a given number of UTF-8 bytes into a String.
Most protocols store the width of the string right before the content, so you will probably write things like this:
import Bytes exposing (Endianness(..))
import Bytes.Decode as Decode
sizedString : Decode.Decoder String
sizedString =
Decode.unsignedInt32 BE
|> Decode.andThen Decode.stringIn this case we read the width as a 32-bit unsigned integer, but you have the leeway to read the width as a Base 128 Varint for ProtoBuf, a Variable-Length Integer for SQLite, or whatever else they dream up.
Transform the value produced by a decoder. If you encode negative numbers in a special way, you can say something like this:
negativeInt8 : Decoder Int
negativeInt8 =
map negate unsignedInt8In practice you may see something like ProtoBuf’s ZigZag encoding which decreases the size of small negative numbers.
Combine two decoders.
import Bytes exposing (Endiannness(..))
import Bytes.Decode as Decode
type alias Point = { x : Float, y : Float }
decoder : Decode.Decoder Point
decoder =
Decode.map2 Point
(Decode.float32 BE)
(Decode.float32 BE)Combine three decoders.
Combine four decoders.
Combine five decoders. If you need to combine more things, it is possible
to define more of these with map2 or andThen.
Decode something and then use that information to decode something else. This is most common with strings or sequences where you need to read how long the value is going to be:
import Bytes exposing (Endianness(..))
import Bytes.Decode as Decode
string : Decoder String
string =
Decode.unsignedInt32 BE
|> Decode.andThen Decode.stringCheck out the docs for succeed, fail, and
loop to see andThen used in more ways!
A decoder that always succeeds with a certain value. Maybe we are making
a Maybe decoder:
import Bytes.Decode as Decode exposing (Decoder)
maybe : Decoder a -> Decoder (Maybe a)
maybe decoder =
let
helper n =
if n == 0 then
Decode.succeed Nothing
else
Decode.map Just decoder
in
Decode.unsignedInt8
|> Decode.andThen helperIf the first byte is 00000000 then it is Nothing, otherwise we start
decoding the value and put it in a Just.
A decoder that always fails. This can be useful when using andThen to
decode custom types:
import Bytes exposing (Endianness(..))
import Bytes.Encode as Encode
import Bytes.Decode as Decode
type Distance = Yards Float | Meters Float
toEncoder : Distance -> Encode.Encoder
toEncoder distance =
case distance of
Yards n -> Encode.sequence [ Encode.unsignedInt8 0, Encode.float32 BE n ]
Meters n -> Encode.sequence [ Encode.unsignedInt8 1, Encode.float32 BE n ]
decoder : Decode.Decoder Distance
decoder =
Decode.unsignedInt8
|> Decode.andThen pickDecoder
pickDecoder : Int -> Decode.Decoder Distance
pickDecoder tag =
case tag of
0 -> Decode.map Yards (Decode.float32 BE)
1 -> Decode.map Meters (Decode.float32 BE)
_ -> Decode.failThe encoding chosen here uses an 8-bit unsigned integer to indicate which variant we are working with. If we are working with yards do this, if we are working with meters do that, and otherwise something went wrong!
Decide what steps to take next in your loop.
If you are Done, you give the result of the whole loop. If you decide to
Loop around again, you give a new state to work from. Maybe you need to add
an item to a list? Or maybe you need to track some information about what you
just saw?
Note: It may be helpful to learn about finite-state machines to get
a broader intuition about using state. I.e. You may want to create a type
that describes four possible states, and then use Loop to transition between
them as you consume characters.
A decoder that can loop indefinitely. This can be helpful when parsing repeated structures, like a list:
import Bytes exposing (Endianness(..))
import Bytes.Decode as Decode exposing (..)
list : Decoder a -> Decoder (List a)
list decoder =
unsignedInt32 BE
|> andThen (\len -> loop (len, []) (listStep decoder))
listStep : Decoder a -> (Int, List a) -> Step (Int, List a) (List a)
listStep decoder (n, xs) =
if n <= 0 then
succeed (Done xs)
else
map (\x -> Step (n - 1, x :: xs)) decoderThe list decoder first reads a 32-bit unsigned integer. That determines how
many items will be decoded. From there we use loop to track all the
items we have parsed so far and figure out when to stop.
Describes how to turn a sequence of bytes into a nice Elm value.