Import Upstream version 20180207
[hcoop/debian/mlton.git] / lib / mlton / basic / base64.sml
CommitLineData
7f918cf1
CE
1(* Copyright (C) 2009 Matthew Fluet.
2 * Copyright (C) 1999-2006 Henry Cejtin, Matthew Fluet, Suresh
3 * Jagannathan, and Stephen Weeks.
4 *
5 * MLton is released under a BSD-style license.
6 * See the file MLton-LICENSE for details.
7 *)
8
9structure Base64: BASE64 =
10 struct
11 val chars =
12 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
13
14 fun word8ToChar(w: Word8.t): char =
15 String.sub(chars, Word8.toInt w)
16
17 val word8ToChar =
18 Trace.traceAssert
19 ("Base64.word8ToChar", Word8.layout, Char.layout,
20 fn w => (0w0 <= w andalso w < 0w64, fn _ => true))
21 word8ToChar
22
23 val charToWord8: char -> Word8.t option =
24 Char.memoize(fn c =>
25 Option.map(String.peeki(chars, fn (_, c') => c = c'),
26 fn (i, _) => Word8.fromInt i))
27
28 val charToWord8 =
29 Trace.trace("Base64.charToWord8",
30 Char.layout, Option.layout Word8.layout)
31 charToWord8
32
33 val pad = #"="
34
35 fun 'a encodeGen{array: 'a,
36 length: 'a -> int,
37 sub: 'a * int -> Word8.t} =
38 let
39 val n = length array
40 val sub = fn i => sub(array, i)
41 val (d, m) = Int.divMod(n, 3)
42 val (d, n') = if m = 0
43 then (d, n)
44 else if m = 1
45 then (d + 1, n + 2)
46 else (d + 1, n + 1)
47 val sub = fn i => if i >= n then 0w0 else sub i
48 val _ = Assert.assert("Base64.encodeGen", fn () => n' mod 3 = 0)
49 val numChars: int = 4 * d
50 val chars = CharArray.array(numChars, #"\000")
51 fun updateChar(j, c) = CharArray.update(chars, j, c)
52 fun update(j: int, w: Word8.t) = updateChar(j, word8ToChar w)
53 fun loop(i: int, j: int) =
54 if i = n'
55 then j
56 else
57 let val w1 = sub i
58 val w2 = sub(i + 1)
59 val w3 = sub(i + 2)
60 open Word8
61 val op + = Int.+
62 in update(j, >>(w1, 0w2))
63 ; update(j + 1, orb(<<(andb(w1, 0w3), 0w4),
64 >>(w2, 0w4)))
65 ; update(j + 2, orb(<<(andb(w2, 0wxF), 0w2),
66 >>(w3, 0w6)))
67 ; update(j + 3, andb(w3, 0wx3F))
68 ; loop(i + 3, j + 4)
69 end
70 val j = loop(0, 0)
71 (* insert padding *)
72 val _ = if m = 0
73 then ()
74 else (updateChar(j - 1, pad)
75 ; if m = 1
76 then updateChar(j - 2, pad)
77 else ())
78 (* need to patch for leftover bits *)
79 in String.fromCharArray chars
80 end
81
82 fun encode s = encodeGen{array = s,
83 length = String.size,
84 sub = Char.toWord8 o String.sub}
85
86 val encode =
87 Trace.trace("Base64.encode", String.layout, String.layout) encode
88
89 fun 'a decodeGen{string: string,
90 new: int * Word8.t -> 'a,
91 update: 'a * int * Word8.t -> unit}: 'a =
92 let
93 val n = String.size string
94 val (d, m) = Int.divMod(n, 4)
95 val _ = Assert.assert("Base64.decodeGen", fn () => m = 0)
96 fun sub i = String.sub(string, i)
97 val numPads =
98 if pad = sub(n - 1)
99 then if pad = sub(n - 2)
100 then 2
101 else 1
102 else 0
103 val outputLength = d * 3 - numPads
104 val a = new(outputLength, 0w0)
105 fun loop(i: int, j: int): unit =
106 if i = n
107 then ()
108 else
109 let
110 val sub =
111 fn i =>
112 let val c = sub i
113 in if pad = c
114 then 0w0
115 else
116 case charToWord8 c of
117 NONE =>
118 Error.bug (concat
119 ["Base64.decodeGen: strange char ",
120 Char.escapeSML c])
121 | SOME w => w
122 end
123 val w0 = sub i
124 val w1 = sub(i + 1)
125 val w2 = sub(i + 2)
126 val w3 = sub(i + 3)
127 val update =
128 fn (k, w) =>
129 if j + k >= outputLength
130 then ()
131 else update(a, j + k, w)
132 val _ =
133 let open Word8
134 in update(0, orb(<<(w0, 0w2), >>(w1, 0w4)))
135 ; update(1, orb(<<(andb(w1, 0wxF), 0w4), >>(w2, 0w2)))
136 ; update(2, orb(<<(andb(w2, 0w3), 0w6), w3))
137 end
138 in loop(i + 4, j + 3)
139 end
140 val _ = loop(0, 0)
141 in a
142 end
143
144 fun decode s =
145 String.fromCharArray
146 (decodeGen
147 {string = s,
148 new = fn (n, w) => CharArray.array(n, Char.fromWord8 w),
149 update = fn (a, i, w) => CharArray.update(a, i, Char.fromWord8 w)})
150
151 val decode =
152 Trace.trace("Base64.decode", String.layout, String.layout) decode
153 end