Skip to content

Commit

Permalink
[External] [mojo-stdlib] Add b16encode and b16decode to base64 module…
Browse files Browse the repository at this point in the history
… (#39561)

[External] [mojo-stdlib] Add b16encode and b16decode to base64 module

This change adds `b16encode` and `b16decode` to the `base64` module, as
well as relevant tests.

The initial implementation is simple and can be optimized in future
iterations (if needed).

modularml#474

Co-authored-by: Kern Handa <[email protected]>
Closes modularml#2584
MODULAR_ORIG_COMMIT_REV_ID: 341c7f3656c041f37d76e6c8b382b92c8762f74e
  • Loading branch information
kernhanda authored and martinvuyk committed May 24, 2024
1 parent 0b5efa5 commit 44e9f93
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 2 deletions.
4 changes: 4 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ what we publish.
- Base64 decoding support has been added.
([PR #2364](https://github.com/modularml/mojo/pull/2364) by [@mikowals](https://github.com/mikowals))

- Add Base16 encoding and decoding support.
([PR #2584](https://github.com/modularml/mojo/pull/2584)
by [@kernhanda](https://github.com/kernhanda))

- Add `repr()` function and `Representable` trait.
([PR #2361](https://github.com/modularml/mojo/pull/2361) by [@gabrieldemarmiesse](https://github.com/gabrieldemarmiesse))

Expand Down
2 changes: 1 addition & 1 deletion stdlib/src/base64/__init__.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
# ===----------------------------------------------------------------------=== #
"""Implements the base64 package."""

from .base64 import b64encode, b64decode
from .base64 import b64encode, b64decode, b16encode, b16decode
82 changes: 82 additions & 0 deletions stdlib/src/base64/base64.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,85 @@ fn b64decode(str: String) -> String:

p.append(0)
return p


# ===----------------------------------------------------------------------===#
# b16encode
# ===----------------------------------------------------------------------===#


fn b16encode(str: String) -> String:
"""Performs base16 encoding on the input string.
Args:
str: The input string.
Returns:
Base16 encoding of the input string.
"""
alias lookup = "0123456789ABCDEF"
var b16chars = lookup.unsafe_ptr()

var length = len(str)
var out = List[Int8](capacity=length * 2 + 1)

@parameter
@always_inline
fn str_bytes(idx: Int) -> Int:
return int(str.unsafe_ptr().bitcast[DType.uint8]()[idx])

for i in range(length):
var str_byte = str_bytes(i)
var hi = str_byte >> 4
var lo = str_byte & 0b1111
out.append(b16chars[hi])
out.append(b16chars[lo])

out.append(0)

return String(out^)


# ===----------------------------------------------------------------------===#
# b16decode
# ===----------------------------------------------------------------------===#


@always_inline
fn b16decode(str: String) -> String:
"""Performs base16 decoding on the input string.
Args:
str: A base16 encoded string.
Returns:
The decoded string.
"""

# TODO: Replace with dict literal when possible
@parameter
@always_inline
fn decode(c: String) -> Int:
var char_val = ord(c)

if ord("A") <= char_val <= ord("Z"):
return char_val - ord("A") + 10
elif ord("a") <= char_val <= ord("z"):
return char_val - ord("a") + 10
elif ord("0") <= char_val <= ord("9"):
return char_val - ord("0")

return -1

var n = len(str)
debug_assert(n % 2 == 0, "Input length must be divisible by 2")

var p = List[Int8](capacity=int(n / 2) + 1)

for i in range(0, n, 2):
var hi = str[i]
var lo = str[i + 1]
p.append(decode(hi) << 4 | decode(lo))

p.append(0)
return p
38 changes: 37 additions & 1 deletion stdlib/test/base64/test_base64.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# ===----------------------------------------------------------------------=== #
# RUN: %mojo %s

from base64 import b64encode, b64decode
from base64 import b64encode, b64decode, b16encode, b16decode
from testing import assert_equal


Expand Down Expand Up @@ -52,6 +52,42 @@ def test_b64decode():
assert_equal(b64decode("QUJDREVGYWJjZGVm"), "ABCDEFabcdef")


def test_b16encode():
assert_equal(b16encode("a"), "61")

assert_equal(b16encode("fo"), "666F")

assert_equal(b16encode("Hello Mojo!!!"), "48656C6C6F204D6F6A6F212121")

assert_equal(b16encode("Hello 🔥!!!"), "48656C6C6F20F09F94A5212121")

assert_equal(
b16encode("the quick brown fox jumps over the lazy dog"),
"74686520717569636B2062726F776E20666F78206A756D7073206F76657220746865206C617A7920646F67",
)

assert_equal(b16encode("ABCDEFabcdef"), "414243444546616263646566")


def test_b16decode():
assert_equal(b16decode("61"), "a")

assert_equal(b16decode("666F"), "fo")

assert_equal(b16decode("48656C6C6F204D6F6A6F212121"), "Hello Mojo!!!")

assert_equal(b16decode("48656C6C6F20F09F94A5212121"), "Hello 🔥!!!")

assert_equal(
b16encode("the quick brown fox jumps over the lazy dog"),
"74686520717569636B2062726F776E20666F78206A756D7073206F76657220746865206C617A7920646F67",
)

assert_equal(b16decode("414243444546616263646566"), "ABCDEFabcdef")


def main():
test_b64encode()
test_b64decode()
test_b16encode()
test_b16decode()

0 comments on commit 44e9f93

Please sign in to comment.