mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-28 08:53:44 +00:00
176 lines
4.8 KiB
Text
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
|