Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions encoding/binary/README.mbt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Binary

Binary encoding and decoding for in-memory byte sequences.

This package covers the pieces of Go's `encoding/binary` that map directly to
MoonBit's core library today: fixed-width unsigned integers in explicit byte
orders, plus unsigned and signed varint helpers. Stream-oriented APIs and
reflection-based struct encoding are intentionally out of scope.

## Byte Order

Use `Big` or `Little` to encode and decode fixed-width unsigned integers.
Decoding reads from the start of a `BytesView` and leaves trailing bytes
untouched.

```mbt check
///|
test "byte_order_decode" {
inspect(try! @binary.Big.decode_uint16(b"\x03\xe8"), content="1000")
inspect(try! @binary.Little.decode_uint16(b"\xe8\x03"), content="1000")
}
```

Encoding returns a new byte sequence.

```mbt check
///|
test "byte_order_encode" {
assert_eq(@binary.Big.encode_uint32(0x12345678U), b"\x12\x34\x56\x78")
assert_eq(@binary.Little.encode_uint32(0x12345678U), b"\x78\x56\x34\x12")
}
```

## Varints

Use `encode_uvarint` and `uvarint` for unsigned 64-bit values. `uvarint`
returns both the decoded value and the number of bytes consumed.

```mbt check
///|
test "uvarint" {
assert_eq(@binary.encode_uvarint(300UL), b"\xac\x02")
let (value, consumed) = try! @binary.uvarint(b"\xac\x02rest")
inspect(value, content="300")
inspect(consumed, content="2")
}
```

Use `encode_varint` and `varint` for signed 64-bit values.

```mbt check
///|
test "varint" {
assert_eq(@binary.encode_varint(-300L), b"\xd7\x04")
let (value, consumed) = try! @binary.varint(b"\xd7\x04rest")
inspect(value, content="-300")
inspect(consumed, content="2")
}
```

Malformed fixed-width input, incomplete varints, and overflowing varints raise
`Malformed`.

```mbt check
///|
test "malformed" {
try {
let _ = @binary.uvarint(b"\x80")
panic()
} catch {
Malformed(_) => ()
}
}
```
138 changes: 138 additions & 0 deletions encoding/binary/endian.mbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
/// Decodes the first two bytes as an unsigned 16-bit integer using this byte
/// order. Raises `Malformed` if fewer than two bytes are available.
pub fn Endian::decode_uint16(
self : Endian,
bytes : BytesView,
) -> UInt16 raise Malformed {
match self {
Big =>
match bytes {
[u16be(value), ..] => value.to_uint16()
_ => raise Malformed(bytes)
}
Little =>
match bytes {
[u16le(value), ..] => value.to_uint16()
_ => raise Malformed(bytes)
}
}
}

///|
/// Decodes the first four bytes as an unsigned 32-bit integer using this byte
/// order. Raises `Malformed` if fewer than four bytes are available.
pub fn Endian::decode_uint32(
self : Endian,
bytes : BytesView,
) -> UInt raise Malformed {
match self {
Big =>
match bytes {
[u32be(value), ..] => value
_ => raise Malformed(bytes)
}
Little =>
match bytes {
[u32le(value), ..] => value
_ => raise Malformed(bytes)
}
}
}

///|
/// Decodes the first eight bytes as an unsigned 64-bit integer using this byte
/// order. Raises `Malformed` if fewer than eight bytes are available.
pub fn Endian::decode_uint64(
self : Endian,
bytes : BytesView,
) -> UInt64 raise Malformed {
match self {
Big =>
match bytes {
[u64be(value), ..] => value
_ => raise Malformed(bytes)
}
Little =>
match bytes {
[u64le(value), ..] => value
_ => raise Malformed(bytes)
}
}
}

///|
/// Encodes an unsigned 16-bit integer into a new two-byte sequence using this
/// byte order.
pub fn Endian::encode_uint16(self : Endian, value : UInt16) -> Bytes {
match self {
Big => [(value >> 8).to_byte(), value.to_byte()]
Little => [value.to_byte(), (value >> 8).to_byte()]
}
}

///|
/// Encodes an unsigned 32-bit integer into a new four-byte sequence using this
/// byte order.
pub fn Endian::encode_uint32(self : Endian, value : UInt) -> Bytes {
match self {
Big =>
[
(value >> 24).to_byte(),
(value >> 16).to_byte(),
(value >> 8).to_byte(),
value.to_byte(),
]
Little =>
[
value.to_byte(),
(value >> 8).to_byte(),
(value >> 16).to_byte(),
(value >> 24).to_byte(),
]
}
}

///|
/// Encodes an unsigned 64-bit integer into a new eight-byte sequence using this
/// byte order.
pub fn Endian::encode_uint64(self : Endian, value : UInt64) -> Bytes {
match self {
Big =>
[
(value >> 56).to_byte(),
(value >> 48).to_byte(),
(value >> 40).to_byte(),
(value >> 32).to_byte(),
(value >> 24).to_byte(),
(value >> 16).to_byte(),
(value >> 8).to_byte(),
value.to_byte(),
]
Little =>
[
value.to_byte(),
(value >> 8).to_byte(),
(value >> 16).to_byte(),
(value >> 24).to_byte(),
(value >> 32).to_byte(),
(value >> 40).to_byte(),
(value >> 48).to_byte(),
(value >> 56).to_byte(),
]
}
}
152 changes: 152 additions & 0 deletions encoding/binary/endian_test.mbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
test "decode fixed-width unsigned integers" {
inspect(try! @binary.Big.decode_uint16(b"\x12\x34"), content="4660")
inspect(try! @binary.Little.decode_uint16(b"\x34\x12"), content="4660")
inspect(
try! @binary.Big.decode_uint32(b"\x12\x34\x56\x78"),
content="305419896",
)
inspect(
try! @binary.Little.decode_uint32(b"\x78\x56\x34\x12"),
content="305419896",
)
inspect(
try! @binary.Big.decode_uint64(b"\x01\x23\x45\x67\x89\xab\xcd\xef"),
content="81985529216486895",
)
inspect(
try! @binary.Little.decode_uint64(b"\xef\xcd\xab\x89\x67\x45\x23\x01"),
content="81985529216486895",
)
}

///|
test "decode from views with offsets" {
inspect(try! @binary.Big.decode_uint16(b"\xff\x12\x34"[1:]), content="4660")
inspect(
try! @binary.Little.decode_uint32(b"\xff\x78\x56\x34\x12"[1:]),
content="305419896",
)
inspect(
try! @binary.Big.decode_uint64(b"\xff\x01\x23\x45\x67\x89\xab\xcd\xef"[1:]),
content="81985529216486895",
)
}

///|
test "encode fixed-width unsigned integers" {
inspect(
@binary.Big.encode_uint16((0x1234 : UInt16)),
content=(
#|b"\x124"
),
)
inspect(
@binary.Little.encode_uint16((0x1234 : UInt16)),
content=(
#|b"4\x12"
),
)
inspect(
@binary.Big.encode_uint32(0x12345678U),
content=(
#|b"\x124Vx"
),
)
inspect(
@binary.Little.encode_uint32(0x12345678U),
content=(
#|b"xV4\x12"
),
)
inspect(
@binary.Big.encode_uint64(0x0123456789abcdefUL),
content=(
#|b"\x01#Eg\x89\xab\xcd\xef"
),
)
inspect(
@binary.Little.encode_uint64(0x0123456789abcdefUL),
content=(
#|b"\xef\xcd\xab\x89gE#\x01"
),
)
}

///|
test "fixed-width round trips" {
let u16_values = [
(0 : UInt16),
(1 : UInt16),
(0x1234 : UInt16),
(0xffff : UInt16),
]
for value in u16_values {
assert_eq(
try! @binary.Big.decode_uint16(@binary.Big.encode_uint16(value)),
value,
)
assert_eq(
try! @binary.Little.decode_uint16(@binary.Little.encode_uint16(value)),
value,
)
}
let u32_values = [0U, 1U, 0x12345678U, 0xffffffffU]
for value in u32_values {
assert_eq(
try! @binary.Big.decode_uint32(@binary.Big.encode_uint32(value)),
value,
)
assert_eq(
try! @binary.Little.decode_uint32(@binary.Little.encode_uint32(value)),
value,
)
}
let u64_values = [0UL, 1UL, 0x0123456789abcdefUL, 0xffffffffffffffffUL]
for value in u64_values {
assert_eq(
try! @binary.Big.decode_uint64(@binary.Big.encode_uint64(value)),
value,
)
assert_eq(
try! @binary.Little.decode_uint64(@binary.Little.encode_uint64(value)),
value,
)
}
}

///|
test "decode malformed fixed-width inputs" {
try {
let _ = @binary.Big.decode_uint16(b"\x01")
panic()
} catch {
Malformed(_) => ()
}
try {
let _ = @binary.Big.decode_uint32(b"\x01\x02\x03")
panic()
} catch {
Malformed(_) => ()
}
try {
let _ = @binary.Big.decode_uint64(b"\x01\x02\x03\x04")
panic()
} catch {
Malformed(_) => ()
}
}
5 changes: 5 additions & 0 deletions encoding/binary/moon.pkg
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {
"moonbitlang/core/buffer",
"moonbitlang/core/builtin",
"moonbitlang/core/debug",
}
Loading
Loading