~/bend-docscommunity

b64.bend source

b64.bend on the hub · documented module

# UTF-8 and base64 (RFC 4648), both ways; from bend-smtp.import Basedef Word.pick(c: Bool, a: U32, b: U32) -> U32:  match c:    case True{}:      a    case False{}:      b# UTF-8# -----def Utf8.cont(+c: U32, n: Nat) -> U32:  U32.or(128, U32.and(U32.shrn(c, n), 63))# How many bytes a code point takes.def Utf8.size(+c: U32) -> Nat:  Bool.pick(Nat, (c < 128 : U32), 1n,  Bool.pick(Nat, (c < 2048 : U32), 2n,  Bool.pick(Nat, (c < 65536 : U32), 3n, 4n)))def Utf8.put.k(k: Nat, +c: U32, rest: List<&2, U32>) -> List<&2, U32>:  match k:    case 1n:      c <> rest    case 2n:      U32.or(192, U32.shrn(c, 6n)) <> Utf8.cont(c, 0n) <> rest    case 3n:      U32.or(224, U32.shrn(c, 12n)) <> Utf8.cont(c, 6n) <> Utf8.cont(c, 0n)        <> rest    case _:      U32.or(240, U32.shrn(c, 18n)) <> Utf8.cont(c, 12n) <> Utf8.cont(c, 6n)        <> Utf8.cont(c, 0n) <> rest# One code point's bytes, put in front of rest.def Utf8.put(+c: U32, rest: List<&2, U32>) -> List<&2, U32>:  Utf8.put.k(Utf8.size(c), c, rest)def Utf8.bytes(s: String) -> List<&2, U32>:  match s:    case SNil{}:      Nil{}    case SCon{Chr{c}, t}:      Utf8.put(c, Utf8.bytes(t))# Base64# ------# The base64 digit for 0..63.def B64.ch(+i: U32) -> Char:  Chr{Word.pick((i < 26 : U32), (i + 65 : U32),    Word.pick((i < 52 : U32), (i + 71 : U32),    Word.pick((i < 62 : U32), (i - 4 : U32),    Word.pick(U32.is_eq(i, 62), 43, 47))))}def B64.at(+x: U32, n: Nat) -> Char:  B64.ch(U32.and(U32.shrn(x, n), 63))# A group of 4 digits, the last `pad` of them "=".def B64.quad(+x: U32, pad: Nat, rest: String) -> String:  match pad:    case 0n:      SCon{B64.at(x, 18n), SCon{B64.at(x, 12n), SCon{B64.at(x, 6n),        SCon{B64.at(x, 0n), rest}}}}    case 1n:      SCon{B64.at(x, 18n), SCon{B64.at(x, 12n), SCon{B64.at(x, 6n),        SCon{'=', rest}}}}    case _:      SCon{B64.at(x, 18n), SCon{B64.at(x, 12n), SCon{'=', SCon{'=', rest}}}}def B64.word(+a: U32, +b: U32, +c: U32) -> U32:  U32.or(U32.shln(a, 16n), U32.or(U32.shln(b, 8n), c))def B64.onto(acc: String, tail: String) -> String:  match acc:    case SNil{}:      tail    case SCon{c, t}:      B64.onto(t, SCon{c, tail})# The 4 digits of a group, reversed, on acc; pad of them "=".def B64.quad.rev(+x: U32, pad: Nat, acc: String) -> String:  match pad:    case 0n:      SCon{B64.at(x, 0n), SCon{B64.at(x, 6n), SCon{B64.at(x, 12n),        SCon{B64.at(x, 18n), acc}}}}    case 1n:      SCon{'=', SCon{B64.at(x, 6n), SCon{B64.at(x, 12n), SCon{B64.at(x, 18n), acc}}}}    case _:      SCon{'=', SCon{'=', SCon{B64.at(x, 12n), SCon{B64.at(x, 18n), acc}}}}# A CRLF (reversed) before the group when `wrap` groups fill the line.def B64.brk(full: Bool, acc: String) -> String:  match full:    case True{}:      SCon{Chr{10}, SCon{Chr{13}, acc}}    case False{}:      acc# The digits of bs, in front of tail; k groups are on the current line,# and a line takes `wrap` groups (0: one line). acc holds the digits so# far, reversed: one tail call per group, then one pass onto the tail.def B64.go(bs: List<&2, U32>, +k: U32, +wrap: U32, acc: String, tail: String) ->  String:  match bs:    case Nil{}:      B64.onto(acc, tail)    case Con{a, Con{b, Con{c, rest}}}:      +full = (wrap > 0 && k >= wrap : U32)      B64.go(rest, Word.pick(full, 1, (k + 1 : U32)), wrap,        B64.quad.rev(B64.word(a, b, c), 0n, B64.brk(full, acc)), tail)    case Con{a, Con{b, Nil{}}}:      B64.onto(B64.quad.rev(B64.word(a, b, 0), 1n,        B64.brk((wrap > 0 && k >= wrap : U32), acc)), tail)    case Con{a, Nil{}}:      B64.onto(B64.quad.rev(B64.word(a, 0, 0), 2n,        B64.brk((wrap > 0 && k >= wrap : U32), acc)), tail)def B64.encode(bs: List<&2, U32>) -> String:  B64.go(bs, 0, 0, SNil{}, SNil{})# Base64 in lines of 76 digits (19 groups), as MIME bodies need, then# tail.def B64.lines(bs: List<&2, U32>, tail: String) -> String:  B64.go(bs, 0, 19, SNil{}, tail)def B64.text(s: String) -> String:  B64.encode(Utf8.bytes(s))# Base64 decoding# ---------------# A base64 digit's value, or 64 for anything else.def B64.val(+c: U32) -> U32:  Word.pick((c >= 65 && c <= 90 : U32), (c - 65 : U32),  Word.pick((c >= 97 && c <= 122 : U32), (c - 71 : U32),  Word.pick((c >= 48 && c <= 57 : U32), (c + 4 : U32),  Word.pick(U32.is_eq(c, 43), 62, Word.pick(U32.is_eq(c, 47), 63, 64)))))def B64.keep(ok: Bool, v: U32, rest: List<&2, U32>) -> List<&2, U32>:  match ok:    case True{}:      v <> rest    case False{}:      restdef B64.vals(s: String) -> List<&2, U32>:  match s:    case SNil{}:      Nil{}    case SCon{Chr{c}, t}:      +v = B64.val(c)      B64.keep((v < 64 : U32), v, B64.vals(t))def B64.byte(+x: U32, n: Nat) -> U32:  U32.and(U32.shrn(x, n), 255)def B64.group(+a: U32, +b: U32, +c: U32, +d: U32) -> U32:  U32.or(U32.or(U32.shln(a, 18n), U32.shln(b, 12n)), U32.or(U32.shln(c, 6n), d))def B64.bytes(vs: List<&2, U32>) -> List<&2, U32>:  match vs:    case Con{a, Con{b, Con{c, Con{d, rest}}}}:      +x = B64.group(a, b, c, d)      B64.byte(x, 16n) <> B64.byte(x, 8n) <> B64.byte(x, 0n) <> B64.bytes(rest)    case Con{a, Con{b, Con{c, Nil{}}}}:      +x = B64.group(a, b, c, 0)      [B64.byte(x, 16n), B64.byte(x, 8n)]    case Con{a, Con{b, Nil{}}}:      [B64.byte(B64.group(a, b, 0, 0), 16n)]    case _:      Nil{}# The bytes of base64 text; padding and anything off the alphabet skipped.def B64.decode(s: String) -> List<&2, U32>:  B64.bytes(B64.vals(s))# Bytes as text: ASCII as is, any other byte as "?" (for error messages).def B64.ascii(bs: List<&2, U32>) -> String:  match bs:    case Nil{}:      SNil{}    case Con{+b, t}:      SCon{Chr{Word.pick((b < 128 : U32), b, 63)}, B64.ascii(t)}