# Hex — strict hexadecimal codec for Base List<&2, U32> and String. # Base-only, no termination escapes. Encoding is lowercase and octet-oriented. import Base # Digits 0..15 are emitted as lowercase 0-9a-f. def Hex.digit(+n: U32) -> Char: Bool.pick(Char, U32.is_lt(n, 10), Chr{U32.add(48, n)}, Chr{U32.add(87, n)}) # Encode one octet. As with Bytes.hex, only the low eight bits participate. def Hex.encode.byte(+b: U32) -> String: +octet = U32.and(b, 255) +hi = U32.div(octet, 16) +lo = U32.mod(octet, 16) SCon{Hex.digit(hi), SCon{Hex.digit(lo), SNil{}}} def Hex.encode.go(xs: List<&2, U32>) -> String: match xs: case Nil{}: SNil{} case h <> t: String.append(Hex.encode.byte(h), Hex.encode.go(t)) def Hex.encode(xs: List<&2, U32>) -> String: Hex.encode.go(xs) # Convert one accepted hexadecimal character to its nibble value. # Both cases are accepted; no whitespace or prefixes are accepted. def Hex.value(+c: Char) -> Maybe<&2, U32>: +x = Char.to_u32(c) Bool.pick(Maybe<&2, U32>, Bool.and(U32.is_ge(x, 48), U32.is_le(x, 57)), Some{U32.sub(x, 48)}, Bool.pick(Maybe<&2, U32>, Bool.and(U32.is_ge(x, 65), U32.is_le(x, 70)), Some{U32.add(10, U32.sub(x, 65))}, Bool.pick(Maybe<&2, U32>, Bool.and(U32.is_ge(x, 97), U32.is_le(x, 102)), Some{U32.add(10, U32.sub(x, 97))}, None{}))) # Prepend one decoded octet to a successful tail; preserve failure. def Hex.decode.prefix( +hi: U32, +lo: U32, r: Maybe<&2, List<&2, U32>> ) -> Maybe<&2, List<&2, U32>>: match r: case None{}: None{} case Some{rest}: Some{U32.add(U32.shln(hi, 4n), lo) <> rest} # Decode exactly two hex characters. The recursive decoder supplies r, so any # malformed suffix makes the whole result None. def Hex.decode.pair( r: Maybe<&2, List<&2, U32>>, ma: Maybe<&2, U32>, mb: Maybe<&2, U32> ) -> Maybe<&2, List<&2, U32>>: match ma mb: case Some{+hi} Some{+lo}: Hex.decode.prefix(hi, lo, r) case _ _: None{} # Strict decoding: input must have even length and every character must be # 0-9, a-f, or A-F. Empty input decodes to Some{Nil{}}. def Hex.decode.go(s: String) -> Maybe<&2, List<&2, U32>>: match s: case SNil{}: Some{Nil{}} case SCon{+a, SCon{+b, tail}}: +ma = Hex.value(a) +mb = Hex.value(b) Hex.decode.pair(Hex.decode.go(tail), ma, mb) case _: None{} def Hex.decode(s: String) -> Maybe<&2, List<&2, U32>>: Hex.decode.go(s)