roc/crates/cli_testing_examples/benchmarks/Base64/Encode.roc

176 lines
4.8 KiB
Text

interface Base64.Encode
exposes [toBytes]
imports [Bytes.Encode.{ ByteEncoder }]
InvalidChar : U8
# State : [None, One U8, Two U8, Three U8]
toBytes : Str -> List U8
toBytes = \str ->
str
|> Str.toUtf8
|> encodeChunks
|> Bytes.Encode.sequence
|> Bytes.Encode.encode
encodeChunks : List U8 -> List ByteEncoder
encodeChunks = \bytes ->
List.walk bytes { output: [], accum: None } folder
|> encodeResidual
coerce : Nat, a -> a
coerce = \_, x -> x
# folder : { output : List ByteEncoder, accum : State }, U8 -> { output : List ByteEncoder, accum : State }
folder = \{ output, accum }, char ->
when accum is
Unreachable n -> coerce n { output, accum: Unreachable n }
None -> { output, accum: One char }
One a -> { output, accum: Two a char }
Two a b -> { output, accum: Three a b char }
Three a b c ->
when encodeCharacters a b c char is
Ok encoder ->
{
output: List.append output encoder,
accum: None,
}
Err _ ->
{ output, accum: None }
# SGVs bG8g V29y bGQ=
# encodeResidual : { output : List ByteEncoder, accum : State } -> List ByteEncoder
encodeResidual = \{ output, accum } ->
when accum is
Unreachable _ -> output
None -> output
One _ -> output
Two a b ->
when encodeCharacters a b equals equals is
Ok encoder -> List.append output encoder
Err _ -> output
Three a b c ->
when encodeCharacters a b c equals is
Ok encoder -> List.append output encoder
Err _ -> output
equals : U8
equals = 61
# Convert 4 characters to 24 bits (as an ByteEncoder)
encodeCharacters : U8, U8, U8, U8 -> Result ByteEncoder InvalidChar
encodeCharacters = \a, b, c, d ->
if !(isValidChar a) then
Err a
else if !(isValidChar b) then
Err b
else
# `=` is the padding character, and must be special-cased
# only the `c` and `d` char are allowed to be padding
n1 = unsafeConvertChar a
n2 = unsafeConvertChar b
x : U32
x = Num.intCast n1
y : U32
y = Num.intCast n2
if d == equals then
if c == equals then
n = Num.bitwiseOr (Num.shiftLeftBy x 18) (Num.shiftLeftBy y 12)
# masking higher bits is not needed, Encode.unsignedInt8 ignores higher bits
b1 : U8
b1 = Num.intCast (Num.shiftRightBy n 16)
Ok (Bytes.Encode.u8 b1)
else if !(isValidChar c) then
Err c
else
n3 = unsafeConvertChar c
z : U32
z = Num.intCast n3
n = Num.bitwiseOr (Num.bitwiseOr (Num.shiftLeftBy x 18) (Num.shiftLeftBy y 12)) (Num.shiftLeftBy z 6)
combined : U16
combined = Num.intCast (Num.shiftRightBy n 8)
Ok (Bytes.Encode.u16 BE combined)
else if !(isValidChar d) then
Err d
else
n3 = unsafeConvertChar c
n4 = unsafeConvertChar d
z : U32
z = Num.intCast n3
w : U32
w = Num.intCast n4
n =
Num.bitwiseOr
(Num.bitwiseOr (Num.shiftLeftBy x 18) (Num.shiftLeftBy y 12))
(Num.bitwiseOr (Num.shiftLeftBy z 6) w)
b3 : U8
b3 = Num.intCast n
combined : U16
combined = Num.intCast (Num.shiftRightBy n 8)
Ok (Bytes.Encode.sequence [Bytes.Encode.u16 BE combined, Bytes.Encode.u8 b3])
# is the character a base64 digit?
# The base16 digits are: A-Z, a-z, 0-1, '+' and '/'
isValidChar : U8 -> Bool
isValidChar = \c ->
if isAlphaNum c then
Bool.true
else
when c is
43 ->
# '+'
Bool.true
47 ->
# '/'
Bool.true
_ ->
Bool.false
isAlphaNum : U8 -> Bool
isAlphaNum = \key ->
(key >= 48 && key <= 57) || (key >= 64 && key <= 90) || (key >= 97 && key <= 122)
# Convert a base64 character/digit to its index
# See also [Wikipedia](https://en.wikipedia.org/wiki/Base64#Base64_table)
unsafeConvertChar : U8 -> U8
unsafeConvertChar = \key ->
if key >= 65 && key <= 90 then
# A-Z
key - 65
else if key >= 97 && key <= 122 then
# a-z
(key - 97) + 26
else if key >= 48 && key <= 57 then
# 0-9
(key - 48) + 26 + 26
else
when key is
43 ->
# '+'
62
47 ->
# '/'
63
_ ->
0