diff --git a/builtin/byte.mbt b/builtin/byte.mbt index dda5db09..c7c048a6 100644 --- a/builtin/byte.mbt +++ b/builtin/byte.mbt @@ -58,3 +58,269 @@ pub fn debug_write(self : Byte, buf : Buffer) -> Unit { buf.write_string(lo) buf.write_string("'") } + +/// Convert an `Int` within 0-255 (0x00-0xFF) to `Byte`. +/// +/// @alert unsafe "Panics if the input value is out of range." +pub fn Byte::from_int(v: Int) -> Byte { + // TODO: this should be a intrinsics + match v { + 0x00 => { b'\x00' } + 0x01 => { b'\x01' } + 0x02 => { b'\x02' } + 0x03 => { b'\x03' } + 0x04 => { b'\x04' } + 0x05 => { b'\x05' } + 0x06 => { b'\x06' } + 0x07 => { b'\x07' } + 0x08 => { b'\x08' } + 0x09 => { b'\x09' } + 0x0a => { b'\x0a' } + 0x0b => { b'\x0b' } + 0x0c => { b'\x0c' } + 0x0d => { b'\x0d' } + 0x0e => { b'\x0e' } + 0x0f => { b'\x0f' } + 0x10 => { b'\x10' } + 0x11 => { b'\x11' } + 0x12 => { b'\x12' } + 0x13 => { b'\x13' } + 0x14 => { b'\x14' } + 0x15 => { b'\x15' } + 0x16 => { b'\x16' } + 0x17 => { b'\x17' } + 0x18 => { b'\x18' } + 0x19 => { b'\x19' } + 0x1a => { b'\x1a' } + 0x1b => { b'\x1b' } + 0x1c => { b'\x1c' } + 0x1d => { b'\x1d' } + 0x1e => { b'\x1e' } + 0x1f => { b'\x1f' } + 0x20 => { b'\x20' } + 0x21 => { b'\x21' } + 0x22 => { b'\x22' } + 0x23 => { b'\x23' } + 0x24 => { b'\x24' } + 0x25 => { b'\x25' } + 0x26 => { b'\x26' } + 0x27 => { b'\x27' } + 0x28 => { b'\x28' } + 0x29 => { b'\x29' } + 0x2a => { b'\x2a' } + 0x2b => { b'\x2b' } + 0x2c => { b'\x2c' } + 0x2d => { b'\x2d' } + 0x2e => { b'\x2e' } + 0x2f => { b'\x2f' } + 0x30 => { b'\x30' } + 0x31 => { b'\x31' } + 0x32 => { b'\x32' } + 0x33 => { b'\x33' } + 0x34 => { b'\x34' } + 0x35 => { b'\x35' } + 0x36 => { b'\x36' } + 0x37 => { b'\x37' } + 0x38 => { b'\x38' } + 0x39 => { b'\x39' } + 0x3a => { b'\x3a' } + 0x3b => { b'\x3b' } + 0x3c => { b'\x3c' } + 0x3d => { b'\x3d' } + 0x3e => { b'\x3e' } + 0x3f => { b'\x3f' } + 0x40 => { b'\x40' } + 0x41 => { b'\x41' } + 0x42 => { b'\x42' } + 0x43 => { b'\x43' } + 0x44 => { b'\x44' } + 0x45 => { b'\x45' } + 0x46 => { b'\x46' } + 0x47 => { b'\x47' } + 0x48 => { b'\x48' } + 0x49 => { b'\x49' } + 0x4a => { b'\x4a' } + 0x4b => { b'\x4b' } + 0x4c => { b'\x4c' } + 0x4d => { b'\x4d' } + 0x4e => { b'\x4e' } + 0x4f => { b'\x4f' } + 0x50 => { b'\x50' } + 0x51 => { b'\x51' } + 0x52 => { b'\x52' } + 0x53 => { b'\x53' } + 0x54 => { b'\x54' } + 0x55 => { b'\x55' } + 0x56 => { b'\x56' } + 0x57 => { b'\x57' } + 0x58 => { b'\x58' } + 0x59 => { b'\x59' } + 0x5a => { b'\x5a' } + 0x5b => { b'\x5b' } + 0x5c => { b'\x5c' } + 0x5d => { b'\x5d' } + 0x5e => { b'\x5e' } + 0x5f => { b'\x5f' } + 0x60 => { b'\x60' } + 0x61 => { b'\x61' } + 0x62 => { b'\x62' } + 0x63 => { b'\x63' } + 0x64 => { b'\x64' } + 0x65 => { b'\x65' } + 0x66 => { b'\x66' } + 0x67 => { b'\x67' } + 0x68 => { b'\x68' } + 0x69 => { b'\x69' } + 0x6a => { b'\x6a' } + 0x6b => { b'\x6b' } + 0x6c => { b'\x6c' } + 0x6d => { b'\x6d' } + 0x6e => { b'\x6e' } + 0x6f => { b'\x6f' } + 0x70 => { b'\x70' } + 0x71 => { b'\x71' } + 0x72 => { b'\x72' } + 0x73 => { b'\x73' } + 0x74 => { b'\x74' } + 0x75 => { b'\x75' } + 0x76 => { b'\x76' } + 0x77 => { b'\x77' } + 0x78 => { b'\x78' } + 0x79 => { b'\x79' } + 0x7a => { b'\x7a' } + 0x7b => { b'\x7b' } + 0x7c => { b'\x7c' } + 0x7d => { b'\x7d' } + 0x7e => { b'\x7e' } + 0x7f => { b'\x7f' } + 0x80 => { b'\x80' } + 0x81 => { b'\x81' } + 0x82 => { b'\x82' } + 0x83 => { b'\x83' } + 0x84 => { b'\x84' } + 0x85 => { b'\x85' } + 0x86 => { b'\x86' } + 0x87 => { b'\x87' } + 0x88 => { b'\x88' } + 0x89 => { b'\x89' } + 0x8a => { b'\x8a' } + 0x8b => { b'\x8b' } + 0x8c => { b'\x8c' } + 0x8d => { b'\x8d' } + 0x8e => { b'\x8e' } + 0x8f => { b'\x8f' } + 0x90 => { b'\x90' } + 0x91 => { b'\x91' } + 0x92 => { b'\x92' } + 0x93 => { b'\x93' } + 0x94 => { b'\x94' } + 0x95 => { b'\x95' } + 0x96 => { b'\x96' } + 0x97 => { b'\x97' } + 0x98 => { b'\x98' } + 0x99 => { b'\x99' } + 0x9a => { b'\x9a' } + 0x9b => { b'\x9b' } + 0x9c => { b'\x9c' } + 0x9d => { b'\x9d' } + 0x9e => { b'\x9e' } + 0x9f => { b'\x9f' } + 0xa0 => { b'\xa0' } + 0xa1 => { b'\xa1' } + 0xa2 => { b'\xa2' } + 0xa3 => { b'\xa3' } + 0xa4 => { b'\xa4' } + 0xa5 => { b'\xa5' } + 0xa6 => { b'\xa6' } + 0xa7 => { b'\xa7' } + 0xa8 => { b'\xa8' } + 0xa9 => { b'\xa9' } + 0xaa => { b'\xaa' } + 0xab => { b'\xab' } + 0xac => { b'\xac' } + 0xad => { b'\xad' } + 0xae => { b'\xae' } + 0xaf => { b'\xaf' } + 0xb0 => { b'\xb0' } + 0xb1 => { b'\xb1' } + 0xb2 => { b'\xb2' } + 0xb3 => { b'\xb3' } + 0xb4 => { b'\xb4' } + 0xb5 => { b'\xb5' } + 0xb6 => { b'\xb6' } + 0xb7 => { b'\xb7' } + 0xb8 => { b'\xb8' } + 0xb9 => { b'\xb9' } + 0xba => { b'\xba' } + 0xbb => { b'\xbb' } + 0xbc => { b'\xbc' } + 0xbd => { b'\xbd' } + 0xbe => { b'\xbe' } + 0xbf => { b'\xbf' } + 0xc0 => { b'\xc0' } + 0xc1 => { b'\xc1' } + 0xc2 => { b'\xc2' } + 0xc3 => { b'\xc3' } + 0xc4 => { b'\xc4' } + 0xc5 => { b'\xc5' } + 0xc6 => { b'\xc6' } + 0xc7 => { b'\xc7' } + 0xc8 => { b'\xc8' } + 0xc9 => { b'\xc9' } + 0xca => { b'\xca' } + 0xcb => { b'\xcb' } + 0xcc => { b'\xcc' } + 0xcd => { b'\xcd' } + 0xce => { b'\xce' } + 0xcf => { b'\xcf' } + 0xd0 => { b'\xd0' } + 0xd1 => { b'\xd1' } + 0xd2 => { b'\xd2' } + 0xd3 => { b'\xd3' } + 0xd4 => { b'\xd4' } + 0xd5 => { b'\xd5' } + 0xd6 => { b'\xd6' } + 0xd7 => { b'\xd7' } + 0xd8 => { b'\xd8' } + 0xd9 => { b'\xd9' } + 0xda => { b'\xda' } + 0xdb => { b'\xdb' } + 0xdc => { b'\xdc' } + 0xdd => { b'\xdd' } + 0xde => { b'\xde' } + 0xdf => { b'\xdf' } + 0xe0 => { b'\xe0' } + 0xe1 => { b'\xe1' } + 0xe2 => { b'\xe2' } + 0xe3 => { b'\xe3' } + 0xe4 => { b'\xe4' } + 0xe5 => { b'\xe5' } + 0xe6 => { b'\xe6' } + 0xe7 => { b'\xe7' } + 0xe8 => { b'\xe8' } + 0xe9 => { b'\xe9' } + 0xea => { b'\xea' } + 0xeb => { b'\xeb' } + 0xec => { b'\xec' } + 0xed => { b'\xed' } + 0xee => { b'\xee' } + 0xef => { b'\xef' } + 0xf0 => { b'\xf0' } + 0xf1 => { b'\xf1' } + 0xf2 => { b'\xf2' } + 0xf3 => { b'\xf3' } + 0xf4 => { b'\xf4' } + 0xf5 => { b'\xf5' } + 0xf6 => { b'\xf6' } + 0xf7 => { b'\xf7' } + 0xf8 => { b'\xf8' } + 0xf9 => { b'\xf9' } + 0xfa => { b'\xfa' } + 0xfb => { b'\xfb' } + 0xfc => { b'\xfc' } + 0xfd => { b'\xfd' } + 0xfe => { b'\xfe' } + 0xff => { b'\xff' } + _ => abort("value \(v) overflows Byte range") + } +} diff --git a/byte/builtin_tests.mbt b/byte/builtin_tests.mbt new file mode 100644 index 00000000..41587f99 --- /dev/null +++ b/byte/builtin_tests.mbt @@ -0,0 +1,26 @@ +// Copyright 2024 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. + +// NOTE: Since the `builtin` package cannot contain test, the tests of +// primitive type `Byte` lays in this file. Don't put other logic in this +// file. + +test "from_int" { + // [definition] pub fn Byte::from_int(v: Int) -> Byte + // [source] moonbit-core/builtin/byte.mbt + for i = 0; i <= 0xff; i = i + 1 { + let r = Byte::from_int(i).to_int(); + @assertion.assert_eq(r, i)?; + } +} diff --git a/byte/moon.pkg.json b/byte/moon.pkg.json new file mode 100644 index 00000000..f3a15817 --- /dev/null +++ b/byte/moon.pkg.json @@ -0,0 +1,9 @@ +{ + "import": [ + "moonbitlang/core/builtin", + "moonbitlang/core/assertion", + "moonbitlang/core/coverage" + ], + "test_import": [ + ] +} \ No newline at end of file diff --git a/byte/view.mbt b/byte/view.mbt new file mode 100644 index 00000000..1bb1d4f2 --- /dev/null +++ b/byte/view.mbt @@ -0,0 +1,30 @@ +// Copyright 2024 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. + +/// A logical contiguous sequence of bytes with random access by index. +/// +/// Note: not guaranteed to be contiguous in memory. +pub trait ByteView { + length(Self) -> Int + op_get(Self, Int) -> Byte +} + +/// A logical contiguous sequence of bytes with random read and write by index. +/// +/// Note: not guaranteed to be contiguous in memory. +pub trait ByteMutView { + length(Self) -> Int + op_get(Self, Int) -> Byte + op_set(Self, Int, Byte) -> Unit +} diff --git a/bytes/view.mbt b/bytes/view.mbt new file mode 100644 index 00000000..0e00155a --- /dev/null +++ b/bytes/view.mbt @@ -0,0 +1,77 @@ +// Copyright 2024 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. + +/// A view of a contiguous region in Bytes. +/// +/// Impl: implements trait `ByteView`. +pub struct BytesView { + inner: Bytes + offset: Int + length: Int +} + +/// The number of `Bytes` in the view. +pub fn length(self: BytesView) -> Int { + self.length +} + +/// Retrieves the element at the specified index from the view. +/// +/// @alert unsafe "Panic when index out of range" +pub fn op_get(self: BytesView, idx: Int) -> Byte { + // IMPORTANT: Must have this check to prevent negative index + if (0 <= idx && idx < self.length).not() { + let len = self.length + abort("index out of bounds: idx=\(idx) not in [0, len=\(len)).") + } + Byte::from_int(self.inner.op_get(self.offset + idx)) +} + +/// Sets the value of the element at the specified index. +pub fn op_set(self: BytesView, idx: Int, val: Byte) -> Unit { + // IMPORTANT: Must have this check to prevent negative index + if (0 <= idx && idx < self.length).not() { + let len = self.length + abort("index out of bounds: idx=\(idx) not in [0, len=\(len)).") + } + self.inner.op_set(self.offset + idx, val.to_int()) +} + +// Get a slice of contiguous range in the `Bytes`. +pub fn op_as_view(self: Bytes, ~start: Int, ~end: Int) -> BytesView { + if start < 0 { + let len = self.length() + abort("start index out of bounds: start=\(start) not in [0, len=\(len)).") + } else if end > self.length() { + let len = self.length() + abort("end index out of bounds: end=\(end) not in [0, len=\(len)).") + } else if start > end { + abort("range [start=\(start), end=\(end)) is invalid.") + } + BytesView::{ inner: self, offset: start, length: end - start } +} + +// Get a slice of contiguous range in a existing view. +pub fn op_as_view(self: BytesView, ~start: Int, ~end: Int) -> BytesView { + if start < 0 { + let len = self.length() + abort("start index out of bounds: start=\(start) not in [0, len=\(len)).") + } else if end > self.length() { + let len = self.length() + abort("end index out of bounds: end=\(end) not in [0, len=\(len)).") + } else if start > end { + abort("range [start=\(start), end=\(end)) is invalid.") + } + BytesView::{ inner: self.inner, offset: self.offset + start, length: end - start } +} diff --git a/double/double.mbt b/double/double.mbt index 5252bcd7..b5569f0b 100644 --- a/double/double.mbt +++ b/double/double.mbt @@ -174,3 +174,67 @@ test "min_normal" { pub fn hash(self : Double) -> Int { self.reinterpret_as_i64().hash() } + +/// Write bytes representation of a `Double` by specified endianness into target view of `Byte`. +/// +/// Note: `into_byte_view_le()` and `into_byte_view_be()` can be used as a shorthand +/// to avoid writing `Endianness` enum variant explicitly. +/// +/// # Panics +/// +/// 8 bytes of data are required in view. Panics when insufficient. +pub fn into_byte_view[V: @byte.ByteMutView](self: Double, view: V, endian: @representation.Endianness) -> Unit { + self.reinterpret_as_i64().into_byte_view(view, endian) +} + +/// Write bytes representation of a `Double` by little-endian into target view of `Byte`. A shorthand +/// of `into_byte_view(V, @representation.Endianness::LittleEndian)`. +/// +/// # Panics +/// +/// 8 bytes of data are required in view. Panics when insufficient. +pub fn into_byte_view_le[V: @byte.ByteMutView](self: Double, view: V) -> Unit { + self.reinterpret_as_i64().into_byte_view(view, @representation.Endianness::LittleEndian) +} + +/// Write bytes representation of a `Double` by little-endian into target view of `Byte`. A shorthand +/// of `into_byte_view(V, @representation.Endianness::LittleEndian)`. +/// +/// # Panics +/// +/// 4 bytes of data are required in view. Panics when insufficient. +pub fn into_byte_view_be[V: @byte.ByteMutView](self: Double, view: V) -> Unit { + self.reinterpret_as_i64().into_byte_view(view, @representation.Endianness::BigEndian) +} + +/// Extract a `Double` from the source view of `Byte` by specified endianness. +/// +/// Note: `from_byte_view_le()` and `from_byte_view_be()` can be used as a shorthand +/// to avoid writing `Endianness` enum variant explicitly. +/// +/// # Panics +/// +/// 4 bytes of data are required in view. Panics when insufficient. +pub fn Double::from_byte_view[V: @byte.ByteView](view: V, endian: @representation.Endianness) -> Double { + Int64::from_byte_view(view, endian).reinterpret_as_double() +} + +/// Extract a `Double` from the source view of `Byte` by little-endian. A shorthand +/// of `from_byte_view(V, @representation.Endianness::LittleEndian)`. +/// +/// # Panics +/// +/// 8 bytes of data are required in view. Panics when insufficient. +pub fn Double::from_byte_view_le[V: @byte.ByteView](view: V) -> Double { + Int64::from_byte_view(view, @representation.Endianness::LittleEndian).reinterpret_as_double() +} + +/// Extract a `Double` from the source view of `Byte` by big-endian. A shorthand +/// of `from_byte_view(V, @representation.Endianness::BigEndian)`. +/// +/// # Panics +/// +/// 8 bytes of data are required in view. Panics when insufficient. +pub fn Double::from_byte_view_be[V: @byte.ByteView](view: V) -> Double { + Int64::from_byte_view(view, @representation.Endianness::BigEndian).reinterpret_as_double() +} diff --git a/double/double_test.mbt b/double/double_test.mbt index 4b9cd36f..31ba2463 100644 --- a/double/double_test.mbt +++ b/double/double_test.mbt @@ -36,3 +36,92 @@ test "double.num" { let y = 792.0 test_num(x, y, x + y, x * y, x - y, x / y, -1.0)? } + +test "double.into_byte_view(series)" { + let test_bytes_eq = fn (l: Bytes, r: Bytes) -> Result[Unit, String] { + if l.length() == r.length() { + for i = 0; i < l.length(); i = i + 1 { + if l[i] != r[i] { + let (lv, rv) = (l[i], r[i]) + return Err("byte mismatch at \(i): \(lv) != \(rv)") + } + } + Ok(()) + } else { + let (ll, rl) = (l.length(), r.length()) + Err("length mismatch: \(ll) != \(rl)") + } + } + + let cases = [ + ( + -5.882781560676213e+94, // 0xd39c33c7f8175d9c (IEEE 754) + 0, + Bytes::[0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc], + Bytes::[0x9c, 0x5d, 0x17, 0xf8, 0xc7, 0x33, 0x9c, 0xd3, 0xcc, 0xcc], + Bytes::[0xd3, 0x9c, 0x33, 0xc7, 0xf8, 0x17, 0x5d, 0x9c, 0xcc, 0xcc], + ), + ( + 5.2795633159472795e-219, // 0x129dd1b1e9f37d1a (IEEE 754) + 1, + Bytes::[0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa], + Bytes::[0xaa, 0x1a, 0x7d, 0xf3, 0xe9, 0xb1, 0xd1, 0x9d, 0x12, 0xaa], + Bytes::[0xaa, 0x12, 0x9d, 0xd1, 0xb1, 0xe9, 0xf3, 0x7d, 0x1a, 0xaa], + ), + ( + -2.752623365577015e-164, // 0x9df95caf8b51fc5e (IEEE 754) + 2, + Bytes::[0xdf, 0xfd, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf], + Bytes::[0xdf, 0xfd, 0x5e, 0xfc, 0x51, 0x8b, 0xaf, 0x5c, 0xf9, 0x9d], + Bytes::[0xdf, 0xfd, 0x9d, 0xf9, 0x5c, 0xaf, 0x8b, 0x51, 0xfc, 0x5e], + ), + ] + + for icase = 0; icase < cases.length(); icase = icase + 1 { + let (input, pos, original, le_expected, be_expected) = cases[icase]; + + let buffer = original.copy() + let view = buffer[pos..(pos + 8)] + input.into_byte_view(view, @representation.Endianness::LittleEndian) + test_bytes_eq(buffer, le_expected)? + + let buffer = original.copy() + let view = buffer[pos..(pos + 8)] + input.into_byte_view(view, @representation.Endianness::BigEndian) + test_bytes_eq(buffer, be_expected)? + + let buffer = original.copy() + let view = buffer[pos..(pos + 8)] + input.into_byte_view(view, @representation.Endianness::NetworkEndian) + test_bytes_eq(buffer, be_expected)? + + let buffer = original.copy() + let view = buffer[pos..(pos + 8)] + input.into_byte_view_le(view) + test_bytes_eq(buffer, le_expected)? + + let buffer = original.copy() + let view = buffer[pos..(pos + 8)] + input.into_byte_view_be(view) + test_bytes_eq(buffer, be_expected)? + } +} + +test "double.from_byte_view(series)" { + let input = Bytes::[0x23, 0xb6, 0x1e, 0xc2, 0xa3, 0xbb, 0x19, 0x0f, 0x6d, 0x27] + let cases = [ + (0, 6.322855833834645e-236, 1.1888038185948251e-136), + (1, 2.144245627950293e+217, -5.261764689278282e-48), + (2, 9.002687664123903e-119, 1.657251015555868e-160), + ] + for icase = 0; icase < cases.length(); icase = icase + 1 { + let (pos, le_expected, be_expected) = cases[icase]; + let view = input[pos..(pos + 8)] + + @assertion.assert_eq(Double::from_byte_view(view, @representation.Endianness::LittleEndian), le_expected)? + @assertion.assert_eq(Double::from_byte_view_le(view), le_expected)? + @assertion.assert_eq(Double::from_byte_view(view, @representation.Endianness::BigEndian), be_expected)? + @assertion.assert_eq(Double::from_byte_view(view, @representation.Endianness::NetworkEndian), be_expected)? + @assertion.assert_eq(Double::from_byte_view_be(view), be_expected)? + } +} diff --git a/double/moon.pkg.json b/double/moon.pkg.json index 7eacc139..c6685da2 100644 --- a/double/moon.pkg.json +++ b/double/moon.pkg.json @@ -5,7 +5,12 @@ "moonbitlang/core/assertion", "moonbitlang/core/bool", "moonbitlang/core/int64", - "moonbitlang/core/coverage" + "moonbitlang/core/coverage", + "moonbitlang/core/byte", + "moonbitlang/core/representation" ], - "test_import": ["moonbitlang/core/num"] + "test_import": [ + "moonbitlang/core/num", + "moonbitlang/core/bytes" + ] } diff --git a/int/int.mbt b/int/int.mbt index bd009b58..33263b2d 100644 --- a/int/int.mbt +++ b/int/int.mbt @@ -48,10 +48,107 @@ pub fn Int::min_value() -> Int { min_val } +pub fn hash(self : Int) -> Int { + self +} + +/// Write bytes representation of an `Int` by specified endianness into target view of `Byte`. +/// +/// # Example +/// +/// ``` +/// let bytes = Bytes::[0x4D, 0x00, 0x7A, 0x00, 0x7A, 0x00, 0x42, 0x00] +/// 0x51546708.into_view(@representation.Endianness::LittleEndian, bytes[2..6]) +/// @assertion.assert_eq(bytes.to_string(), "M\u{6708}\u{5154}B")? +/// // "M\u{6708}\u{5154}B" +/// // => "M月兔B" +/// // => hex:4D,00,08,67,54,51,42,00 (UTF-16) +/// ``` +/// +/// Note: `into_byte_view_le()` and `into_byte_view_be()` can be used as a shorthand +/// to avoid writing `Endianness` enum variant explicitly. +/// +/// # Panics +/// +/// 4 bytes of data are required in view. Panics when insufficient. +pub fn into_byte_view[V: @byte.ByteMutView](self: Int, view: V, endian: @representation.Endianness) -> Unit { + if view.length() < 4 { + let len = view.length() + abort("insufficient length: \(len) provided, 4 required") + } + for bs = 0; bs < 4; bs = bs + 1 { + let bd = endian.convert_index(bs, 4) + view[bd] = Byte::from_int(self.lsr(bs * 8).land(0xFF)) + } +} + +/// Write bytes representation of an `Int` by little-endian into target view of `Byte`. A shorthand +/// of `into_byte_view(V, @representation.Endianness::LittleEndian)`. +/// +/// # Panics +/// +/// 4 bytes of data are required in view. Panics when insufficient. +pub fn into_byte_view_le[V: @byte.ByteMutView](self: Int, view: V) -> Unit { + self.into_byte_view(view, @representation.Endianness::LittleEndian) +} +/// Write bytes representation of an `Int` by little-endian into target view of `Byte`. A shorthand +/// of `into_byte_view(V, @representation.Endianness::LittleEndian)`. +/// +/// # Panics +/// +/// 4 bytes of data are required in view. Panics when insufficient. +pub fn into_byte_view_be[V: @byte.ByteMutView](self: Int, view: V) -> Unit { + self.into_byte_view(view, @representation.Endianness::BigEndian) +} +/// Extract an `Int` from the source view of `Byte` by specified endianness. +/// +/// # Example +/// +/// ``` +/// let bytes = Bytes::[0xAA, 0x01, 0x02, 0x03, 0x04, 0xDD] +/// @assertion.assert_eq( +/// Int::from_byte_view(bytes[1..4], @representation.Endianness::LittleEndian), +/// 0x04030201 +/// )? +/// ``` +/// +/// Note: `from_byte_view_le()` and `from_byte_view_be()` can be used as a shorthand +/// to avoid writing `Endianness` enum variant explicitly. +/// +/// # Panics +/// +/// 4 bytes of data are required in view. Panics when insufficient. +pub fn Int::from_byte_view[V: @byte.ByteView](view: V, endian: @representation.Endianness) -> Int { + if view.length() < 4 { + let len = view.length() + abort("insufficient length: \(len) provided, 4 required") + } + let mut r = 0 + for bd = 0; bd < 4; bd = bd + 1 { + let bs = endian.convert_index(bd, 4) + r = r.lor(view[bs].to_int().land(0xff).lsl(8 * bd)) + } + r +} +/// Extract an `Int` from the source view of `Byte` by little-endian. A shorthand +/// of `from_byte_view(V, @representation.Endianness::LittleEndian)`. +/// +/// # Panics +/// +/// 4 bytes of data are required in view. Panics when insufficient. +pub fn Int::from_byte_view_le[V: @byte.ByteView](view: V) -> Int { + Int::from_byte_view(view, @representation.Endianness::LittleEndian) +} -pub fn hash(self : Int) -> Int { - self +/// Extract an `Int` from the source view of `Byte` by big-endian. A shorthand +/// of `from_byte_view(V, @representation.Endianness::BigEndian)`. +/// +/// # Panics +/// +/// 4 bytes of data are required in view. Panics when insufficient. +pub fn Int::from_byte_view_be[V: @byte.ByteView](view: V) -> Int { + Int::from_byte_view(view, @representation.Endianness::BigEndian) } diff --git a/int/int_test.mbt b/int/int_test.mbt index a335d4b6..c75f083c 100644 --- a/int/int_test.mbt +++ b/int/int_test.mbt @@ -35,4 +35,91 @@ test "int.num" { let x = -5 let y = 7 test_num(x, y, x + y, x * y, x - y, x / y, -1)? -} \ No newline at end of file +} + +test "int.into_byte_view(series)" { + let test_bytes_eq = fn (l: Bytes, r: Bytes) -> Result[Unit, String] { + if l.length() == r.length() { + for i = 0; i < l.length(); i = i + 1 { + if l[i] != r[i] { + let (lv, rv) = (l[i], r[i]) + return Err("byte mismatch at \(i): \(lv) != \(rv)") + } + } + Ok(()) + } else { + let (ll, rl) = (l.length(), r.length()) + Err("length mismatch: \(ll) != \(rl)") + } + } + + let cases = [ + ( + 0x07_06_05_04, + 0, + Bytes::[0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc], + Bytes::[0x04, 0x05, 0x06, 0x07, 0xcc, 0xcc], + Bytes::[0x07, 0x06, 0x05, 0x04, 0xcc, 0xcc], + ), + ( + 0xff_0b_0c_0d, + 1, + Bytes::[0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa], + Bytes::[0xaa, 0x0d, 0x0c, 0x0b, 0xff, 0xaa], + Bytes::[0xaa, 0xff, 0x0b, 0x0c, 0x0d, 0xaa], + ), + ( + 0x44_33_22_11, + 2, + Bytes::[0xdf, 0xfd, 0xdf, 0xdf, 0xdf, 0xdf], + Bytes::[0xdf, 0xfd, 0x11, 0x22, 0x33, 0x44], + Bytes::[0xdf, 0xfd, 0x44, 0x33, 0x22, 0x11] + ), + ] + + for icase = 0; icase < cases.length(); icase = icase + 1 { + let (input, pos, original, le_expected, be_expected) = cases[icase]; + + let buffer = original.copy() + let view = buffer[pos..(pos + 4)] + input.into_byte_view(view, @representation.Endianness::LittleEndian) + test_bytes_eq(buffer, le_expected)? + + let buffer = original.copy() + let view = buffer[pos..(pos + 4)] + input.into_byte_view(view, @representation.Endianness::BigEndian) + test_bytes_eq(buffer, be_expected)? + + let buffer = original.copy() + let view = buffer[pos..(pos + 4)] + input.into_byte_view(view, @representation.Endianness::NetworkEndian) + test_bytes_eq(buffer, be_expected)? + + let buffer = original.copy() + let view = buffer[pos..(pos + 4)] + input.into_byte_view_le(view) + test_bytes_eq(buffer, le_expected)? + + let buffer = original.copy() + let view = buffer[pos..(pos + 4)] + input.into_byte_view_be(view) + test_bytes_eq(buffer, be_expected)? + } +} + +test "int.from_byte_view(series)" { + let input = Bytes::[0x7c, 0x47, 0x18, 0x49, 0xff, 0x6c] + let cases = [ + (0, 0x49_18_47_7c, 0x7c_47_18_49), (1, 0xff_49_18_47, 0x47_18_49_ff), (2, 0x6c_ff_49_18, 0x18_49_ff_6c), + ] + for icase = 0; icase < cases.length(); icase = icase + 1 { + let (pos, le_expected, be_expected) = cases[icase]; + let view = input[pos..(pos + 4)] + + @assertion.assert_eq(Int::from_byte_view(view, @representation.Endianness::LittleEndian), le_expected)? + @assertion.assert_eq(Int::from_byte_view_le(view), le_expected)? + @assertion.assert_eq(Int::from_byte_view(view, @representation.Endianness::BigEndian), be_expected)? + @assertion.assert_eq(Int::from_byte_view(view, @representation.Endianness::NetworkEndian), be_expected)? + @assertion.assert_eq(Int::from_byte_view_be(view), be_expected)? + } +} diff --git a/int/moon.pkg.json b/int/moon.pkg.json index 36812239..2687e7bb 100644 --- a/int/moon.pkg.json +++ b/int/moon.pkg.json @@ -1,4 +1,13 @@ { - "import": ["moonbitlang/core/builtin", "moonbitlang/core/coverage"], - "test_import": ["moonbitlang/core/num", "moonbitlang/core/assertion"] -} + "import": [ + "moonbitlang/core/builtin", + "moonbitlang/core/coverage", + "moonbitlang/core/byte", + "moonbitlang/core/representation" + ], + "test_import": [ + "moonbitlang/core/bytes", + "moonbitlang/core/num", + "moonbitlang/core/assertion" + ] +} \ No newline at end of file diff --git a/int64/int64.mbt b/int64/int64.mbt index 21241080..d5a89a32 100644 --- a/int64/int64.mbt +++ b/int64/int64.mbt @@ -48,10 +48,109 @@ pub fn Int64::min_value() -> Int64 { min_val } - - pub fn hash(self : Int64) -> Int { let lo = self.to_int() let hi = self.lsr(32).to_int() lo.lxor(hi) } + +/// Write bytes representation of an `Int64` by specified endianness into target view of `Byte`. +/// +/// # Example +/// +/// ``` +/// let bytes = Bytes::[0x4D, 0x00, 0x7A, 0x00, 0x7A, 0x00, 0x7A, 0x00, 0x7A, 0x00, 0x42, 0x00] +/// 0x8A008BED51546708.into_view(@representation.Endianness::LittleEndian, bytes[2..6]) +/// @assertion.assert_eq(bytes.to_string(), "M\u{6708}\u{5154}\u{8BED}\u{8A00}B")? +/// // "M\u{6708}\u{5154}\u{8BED}\u{8A00}B" +/// // => "M月兔语言B" +/// // => hex:4D,00,08,67,54,51,ED,8B,00,8A,42,00 (UTF-16) +/// ``` +/// +/// Note: `into_byte_view_le()` and `into_byte_view_be()` can be used as a shorthand +/// to avoid writing `Endianness` enum variant explicitly. +/// +/// # Panics +/// +/// 8 bytes of data are required in view. Panics when insufficient. +pub fn into_byte_view[V: @byte.ByteMutView](self: Int64, view: V, endian: @representation.Endianness) -> Unit { + if view.length() < 8 { + let len = view.length() + abort("insufficient length: \(len) provided, 8 required") + } + for bs = 0; bs < 8; bs = bs + 1 { + let bd = endian.convert_index(bs, 8) + view[bd] = Byte::from_int(self.lsr(bs * 8).land(0xFFL).to_int()) + } +} + +/// Write bytes representation of an `Int64` by little-endian into target view of `Byte`. A shorthand +/// of `into_byte_view(V, @representation.Endianness::LittleEndian)`. +/// +/// # Panics +/// +/// 8 bytes of data are required in view. Panics when insufficient. +pub fn into_byte_view_le[V: @byte.ByteMutView](self: Int64, view: V) -> Unit { + self.into_byte_view(view, @representation.Endianness::LittleEndian) +} + +/// Write bytes representation of an `Int64` by little-endian into target view of `Byte`. A shorthand +/// of `into_byte_view(V, @representation.Endianness::LittleEndian)`. +/// +/// # Panics +/// +/// 4 bytes of data are required in view. Panics when insufficient. +pub fn into_byte_view_be[V: @byte.ByteMutView](self: Int64, view: V) -> Unit { + self.into_byte_view(view, @representation.Endianness::BigEndian) +} + +/// Extract an `Int64` from the source view of `Byte` by specified endianness. +/// +/// # Example +/// +/// ``` +/// let bytes = Bytes::[0xAA, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xDD] +/// @assertion.assert_eq( +/// Int64::from_byte_view(bytes[1..8], @representation.Endianness::LittleEndian), +/// 0x0807060504030201L +/// )? +/// ``` +/// +/// Note: `from_byte_view_le()` and `from_byte_view_be()` can be used as a shorthand +/// to avoid writing `Endianness` enum variant explicitly. +/// +/// # Panics +/// +/// 4 bytes of data are required in view. Panics when insufficient. +pub fn Int64::from_byte_view[V: @byte.ByteView](view: V, endian: @representation.Endianness) -> Int64 { + if view.length() < 8 { + let len = view.length() + abort("insufficient length: \(len) provided, 4 required") + } + let mut r = 0L + for bd = 0; bd < 8; bd = bd + 1 { + let bs = endian.convert_index(bd, 8) + r = r.lor(view[bs].to_int().land(0xff).to_int64().lsl(8 * bd)) + } + r +} + +/// Extract an `Int64` from the source view of `Byte` by little-endian. A shorthand +/// of `from_byte_view(V, @representation.Endianness::LittleEndian)`. +/// +/// # Panics +/// +/// 8 bytes of data are required in view. Panics when insufficient. +pub fn Int64::from_byte_view_le[V: @byte.ByteView](view: V) -> Int64 { + Int64::from_byte_view(view, @representation.Endianness::LittleEndian) +} + +/// Extract an `Int64` from the source view of `Byte` by big-endian. A shorthand +/// of `from_byte_view(V, @representation.Endianness::BigEndian)`. +/// +/// # Panics +/// +/// 8 bytes of data are required in view. Panics when insufficient. +pub fn Int64::from_byte_view_be[V: @byte.ByteView](view: V) -> Int64 { + Int64::from_byte_view(view, @representation.Endianness::BigEndian) +} diff --git a/int64/int64_test.mbt b/int64/int64_test.mbt index 2f909f91..f8873767 100644 --- a/int64/int64_test.mbt +++ b/int64/int64_test.mbt @@ -34,4 +34,92 @@ test "int64.num" { let x = -500L let y = 792L test_num(x, y, x + y, x * y, x - y, x / y, -1L)? -} \ No newline at end of file +} + +test "int64.into_byte_view(series)" { + let test_bytes_eq = fn (l: Bytes, r: Bytes) -> Result[Unit, String] { + if l.length() == r.length() { + for i = 0; i < l.length(); i = i + 1 { + if l[i] != r[i] { + let (lv, rv) = (l[i], r[i]) + return Err("byte mismatch at \(i): \(lv) != \(rv)") + } + } + Ok(()) + } else { + let (ll, rl) = (l.length(), r.length()) + Err("length mismatch: \(ll) != \(rl)") + } + } + + let cases = [ + ( + 0xff_01_02_03_04_05_06_07_L, + 0, + Bytes::[0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc], + Bytes::[0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0xff, 0xcc, 0xcc], + Bytes::[0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xcc, 0xcc], + ), + ( + 0x01_02_03_04_05_06_07_ff_L, + 1, + Bytes::[0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa], + Bytes::[0xaa, 0xff, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0xaa], + Bytes::[0xaa, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xff, 0xaa], + ), + ( + 0xd3_9c_33_c7_f8_17_5d_9c_L, + 2, + Bytes::[0xdf, 0xfd, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf], + Bytes::[0xdf, 0xfd, 0x9c, 0x5d, 0x17, 0xf8, 0xc7, 0x33, 0x9c, 0xd3], + Bytes::[0xdf, 0xfd, 0xd3, 0x9c, 0x33, 0xc7, 0xf8, 0x17, 0x5d, 0x9c], + ), + ] + + for icase = 0; icase < cases.length(); icase = icase + 1 { + let (input, pos, original, le_expected, be_expected) = cases[icase]; + + let buffer = original.copy() + let view = buffer[pos..(pos + 8)] + input.into_byte_view(view, @representation.Endianness::LittleEndian) + test_bytes_eq(buffer, le_expected)? + + let buffer = original.copy() + let view = buffer[pos..(pos + 8)] + input.into_byte_view(view, @representation.Endianness::BigEndian) + test_bytes_eq(buffer, be_expected)? + + let buffer = original.copy() + let view = buffer[pos..(pos + 8)] + input.into_byte_view(view, @representation.Endianness::NetworkEndian) + test_bytes_eq(buffer, be_expected)? + + let buffer = original.copy() + let view = buffer[pos..(pos + 8)] + input.into_byte_view_le(view) + test_bytes_eq(buffer, le_expected)? + + let buffer = original.copy() + let view = buffer[pos..(pos + 8)] + input.into_byte_view_be(view) + test_bytes_eq(buffer, be_expected)? + } +} +test "int64.from_byte_view(series)" { + let input = Bytes::[0x83, 0xe7, 0x4c, 0xcb, 0x8e, 0x35, 0x1c, 0xc5, 0xa4, 0x4e] + let cases = [ + (0, 0xc5_1c_35_8e_cb_4c_e7_83_L, 0x83_e7_4c_cb_8e_35_1c_c5_L), + (1, 0xa4_c5_1c_35_8e_cb_4c_e7_L, 0xe7_4c_cb_8e_35_1c_c5_a4_L), + (2, 0x4e_a4_c5_1c_35_8e_cb_4c_L, 0x4c_cb_8e_35_1c_c5_a4_4e_L), + ] + for icase = 0; icase < cases.length(); icase = icase + 1 { + let (pos, le_expected, be_expected) = cases[icase]; + let view = input[pos..(pos + 8)] + + @assertion.assert_eq(Int64::from_byte_view(view, @representation.Endianness::LittleEndian), le_expected)? + @assertion.assert_eq(Int64::from_byte_view_le(view), le_expected)? + @assertion.assert_eq(Int64::from_byte_view(view, @representation.Endianness::BigEndian), be_expected)? + @assertion.assert_eq(Int64::from_byte_view(view, @representation.Endianness::NetworkEndian), be_expected)? + @assertion.assert_eq(Int64::from_byte_view_be(view), be_expected)? + } +} diff --git a/int64/moon.pkg.json b/int64/moon.pkg.json index 3f548e9c..89b71050 100644 --- a/int64/moon.pkg.json +++ b/int64/moon.pkg.json @@ -3,6 +3,13 @@ "moonbitlang/core/builtin", "moonbitlang/core/num", "moonbitlang/core/assertion", - "moonbitlang/core/coverage" + "moonbitlang/core/coverage", + "moonbitlang/core/byte", + "moonbitlang/core/representation" + ], + "test_import": [ + "moonbitlang/core/bytes", + "moonbitlang/core/num", + "moonbitlang/core/assertion" ] } diff --git a/representation/endianness.mbt b/representation/endianness.mbt new file mode 100644 index 00000000..ed8a6382 --- /dev/null +++ b/representation/endianness.mbt @@ -0,0 +1,47 @@ +// Copyright 2024 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. + +/// Byte order, aka "endianness". +/// +/// # Note +/// +/// - Little-endian is the de-facto standard practice for current computer architectures, and the standardized byte order of WASM. +/// +/// # Warn +/// +/// Assumes system native endian as little-endian. +/// +/// A variant of `NativeEndian` may be introduced in the future. +pub enum Endianness { + // Little-endian. + LittleEndian + // Big-endian. + BigEndian + // Same as `BigEndian`. The network byte order defined by [IETF RFC 1700](https://datatracker.ietf.org/doc/html/rfc1700). + NetworkEndian + + // non-exhaustive +} derive(Debug, Show, Eq) + +/// Convert byte index from specified endianness to native endianness (or vice-versa). +pub fn convert_index(self: Endianness, src_idx: Int, bytes_count: Int) -> Int { + // TODO: this assumes native endianness is LE, this may not fit to targets other than WASM. + if self == Endianness::LittleEndian { + src_idx + } else if self == Endianness::BigEndian || self == Endianness::NetworkEndian { + bytes_count - 1 - src_idx + } else { + abort("invalid endianness") + } +} diff --git a/representation/moon.pkg.json b/representation/moon.pkg.json new file mode 100644 index 00000000..a158d817 --- /dev/null +++ b/representation/moon.pkg.json @@ -0,0 +1,10 @@ +{ + "import": [ + "moonbitlang/core/builtin" + ], + "test_import": [ + "moonbitlang/core/assertion", + "moonbitlang/core/coverage" + ] + } + \ No newline at end of file