Skip to content

Commit e0cefe6

Browse files
committed
Hex!
1 parent d55bfcb commit e0cefe6

File tree

4 files changed

+95
-1
lines changed

4 files changed

+95
-1
lines changed

src/gleam/bit_array.gleam

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,11 @@ pub fn base64_url_decode(encoded: String) -> Result(BitArray, Nil) {
147147
|> string.replace("_", "/")
148148
|> base64_decode()
149149
}
150+
151+
@external(erlang, "binary", "encode_hex")
152+
@external(javascript, "../gleam_stdlib.mjs", "base16_encode")
153+
pub fn base16_encode(input: BitArray) -> String
154+
155+
@external(erlang, "gleam_stdlib", "base16_decode")
156+
@external(javascript, "../gleam_stdlib.mjs", "base16_decode")
157+
pub fn base16_decode(input: String) -> Result(BitArray, Nil)

src/gleam_stdlib.erl

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
decode_tuple5/1, decode_tuple6/1, tuple_get/2, classify_dynamic/1, print/1,
1414
println/1, print_error/1, println_error/1, inspect/1, float_to_string/1,
1515
int_from_base_string/2, utf_codepoint_list_to_string/1, contains_string/2,
16-
crop_string/2
16+
crop_string/2, base16_decode/1
1717
]).
1818

1919
%% Taken from OTP's uri_string module
@@ -490,3 +490,10 @@ crop_string(String, Prefix) ->
490490

491491
contains_string(String, Substring) ->
492492
is_bitstring(string:find(String, Substring)).
493+
494+
base16_decode(String) ->
495+
try
496+
{ok, binary:decode_hex(String)}
497+
catch
498+
_:_ -> {error, nil}
499+
end.

src/gleam_stdlib.mjs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,3 +842,22 @@ export function inspectBitArray(bits) {
842842
export function inspectUtfCodepoint(codepoint) {
843843
return `//utfcodepoint(${String.fromCodePoint(codepoint.value)})`;
844844
}
845+
846+
export function base16_encode(bit_array) {
847+
let result = "";
848+
for (const byte of bit_array.buffer) {
849+
result += byte.toString(16).padStart(2, "0").toUpperCase();
850+
}
851+
return result;
852+
}
853+
854+
export function base16_decode(string) {
855+
const bytes = new Uint8Array(string.length / 2);
856+
for (let i = 0; i < string.length; i += 2) {
857+
const a = parseInt(string[i], 16);
858+
const b = parseInt(string[i + 1], 16);
859+
if (isNaN(a) || isNaN(b)) return new Error(Nil);
860+
bytes[i / 2] = a * 16 + b;
861+
}
862+
return new Ok(new BitArray(bytes));
863+
}

test/gleam/bit_array_test.gleam

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,63 @@ pub fn decode64_crash_regression_1_test() {
219219
|> bit_array.base64_decode()
220220
|> should.equal(Error(Nil))
221221
}
222+
223+
pub fn base16_test() {
224+
bit_array.base16_encode(<<"":utf8>>)
225+
|> should.equal("")
226+
227+
bit_array.base16_encode(<<"f":utf8>>)
228+
|> should.equal("66")
229+
230+
bit_array.base16_encode(<<"fo":utf8>>)
231+
|> should.equal("666F")
232+
233+
bit_array.base16_encode(<<"foo":utf8>>)
234+
|> should.equal("666F6F")
235+
236+
bit_array.base16_encode(<<"foob":utf8>>)
237+
|> should.equal("666F6F62")
238+
239+
bit_array.base16_encode(<<"fooba":utf8>>)
240+
|> should.equal("666F6F6261")
241+
242+
bit_array.base16_encode(<<"foobar":utf8>>)
243+
|> should.equal("666F6F626172")
244+
245+
bit_array.base16_encode(<<161, 178, 195, 212, 229, 246, 120, 145>>)
246+
|> should.equal("A1B2C3D4E5F67891")
247+
}
248+
249+
pub fn base16_decode_test() {
250+
bit_array.base16_decode("")
251+
|> should.equal(Ok(<<>>))
252+
253+
bit_array.base16_decode("66")
254+
|> should.equal(Ok(<<"f":utf8>>))
255+
256+
bit_array.base16_decode("666F")
257+
|> should.equal(Ok(<<"fo":utf8>>))
258+
259+
bit_array.base16_decode("666F6F")
260+
|> should.equal(Ok(<<"foo":utf8>>))
261+
262+
bit_array.base16_decode("666F6F62")
263+
|> should.equal(Ok(<<"foob":utf8>>))
264+
265+
bit_array.base16_decode("666F6F6261")
266+
|> should.equal(Ok(<<"fooba":utf8>>))
267+
268+
bit_array.base16_decode("666F6F626172")
269+
|> should.equal(Ok(<<"foobar":utf8>>))
270+
271+
bit_array.base16_decode("A1B2C3D4E5F67891")
272+
|> should.equal(Ok(<<161, 178, 195, 212, 229, 246, 120, 145>>))
273+
274+
// Not a hex string
275+
bit_array.base16_decode("?")
276+
|> should.equal(Error(Nil))
277+
278+
// Lowercase hex
279+
bit_array.base16_decode("a1b2c3d4e5f67891")
280+
|> should.equal(Ok(<<161, 178, 195, 212, 229, 246, 120, 145>>))
281+
}

0 commit comments

Comments
 (0)