From a3b0188f4230485726c61b0ca82b9568eaf4b9fe Mon Sep 17 00:00:00 2001 From: Brendo Costa Date: Fri, 19 Jul 2024 16:29:56 -0300 Subject: [PATCH] Initial commit --- .github/workflows/test.yml | 22 +++ .gitignore | 4 + LICENSE | 21 +++ README.md | 70 ++++++++ gleam.toml | 11 ++ manifest.toml | 11 ++ src/gleb128.gleam | 324 +++++++++++++++++++++++++++++++++++++ test/gleb128_test.gleam | 269 ++++++++++++++++++++++++++++++ 8 files changed, 732 insertions(+) create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 gleam.toml create mode 100644 manifest.toml create mode 100644 src/gleb128.gleam create mode 100644 test/gleb128_test.gleam diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..39c782e --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,22 @@ +name: test + +on: + push: + branches: + - master + - main + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: erlef/setup-beam@v1 + with: + otp-version: "26.0.2" + gleam-version: "1.2.1" + rebar3-version: "3" + # elixir-version: "1.15.4" + - run: gleam deps download + - run: gleam test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..599be4e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.beam +*.ez +/build +erl_crash.dump diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ee100ff --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Brendo Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9a37fff --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# GLEB128 + +[![Package Version](https://img.shields.io/hexpm/v/gleb128)](https://hex.pm/packages/gleb128) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/gleb128/) +[![Package License](https://img.shields.io/hexpm/l/gleb128)](https://hex.pm/packages/gleb128) +[![Package Total Downloads Count](https://img.shields.io/hexpm/dt/gleb128)](https://hex.pm/packages/gleb128) +[![Build Status](https://img.shields.io/github/actions/workflow/status/BrendoCosta/gleb128/test.yml)](https://hex.pm/packages/gleb128) +[![Total Stars Count](https://img.shields.io/github/stars/BrendoCosta/gleb128)](https://hex.pm/packages/gleb128) + +## Description + +GLEB128 is a small Gleam library that provides functions for encoding and decoding LEB128 (Little Endian Base 128) integers. LEB128 is a variable-length code compression method used to store arbitrarily large integers in a small number of bytes. Notable use cases for LEB128 are in the DWARF debug file format and the WebAssembly's binary format. + +## Usage + +### Encoding + +```gleam +import gleam/io +import gleb128 + +pub fn main() +{ + let unsigned_encoded = gleb128.encode_unsigned(255) + let signed_encoded = gleb128.encode_signed(-255) + + io.debug(unsigned_encoded) + io.debug(signed_encoded) +} +``` + +Shows the following in output: + +```console +Ok(<<255, 1>>) +<<129, 126>> +``` + +### Decoding + +```gleam +import gleam/io +import gleb128 + +pub fn main() +{ + let unsigned_decoded = gleb128.decode_unsigned(<<255, 1>>) + let signed_decoded = gleb128.decode_signed(<<129, 126>>) + + io.debug(unsigned_decoded) + io.debug(signed_decoded) +} +``` + +Shows the following in output: + +```console +Ok(255) +Ok(-255) +``` + +### Fast decoding + +The ``fast_decode_unsigned`` and ``fast_decode_signed`` functions are optimized for decoding small LEB128 integers on 64-bit systems. Those functions will treat and process the data as a native integer when its length is less than or equal to 8 bytes (64 bits); otherwise, they will fallback to the default decoding functions. + +On a Ryzen 5 5600G with 32 GB RAM, encoding and then decoding all numbers in the range from 0 to 100000000 with the default ``decode_signed`` took 50.10 seconds and used about 20 GB of memory. Repeating the test using ``fast_decode_signed`` reduced the elapsed time to 40.16 seconds and memory usage to about 14.5 GB. The ``fast_decode_unsigned`` function can be even faster when targeting Erlang, as it can use its stdlib's built-in ``binary:decode_unsigned/2`` function. + +## License + +GLEB128 source code is avaliable under the [MIT license](/LICENSE). diff --git a/gleam.toml b/gleam.toml new file mode 100644 index 0000000..afa3922 --- /dev/null +++ b/gleam.toml @@ -0,0 +1,11 @@ +name = "gleb128" +version = "1.0.0" +description = "GLEB128 is a small Gleam library that provides functions for encoding and decoding LEB128 (Little Endian Base 128) integers." +licences = ["MIT"] +repository = { type = "github", user = "BrendoCosta", repo = "gleb128" } + +[dependencies] +gleam_stdlib = ">= 0.34.0 and < 2.0.0" + +[dev-dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/manifest.toml b/manifest.toml new file mode 100644 index 0000000..a4f6951 --- /dev/null +++ b/manifest.toml @@ -0,0 +1,11 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "gleam_stdlib", version = "0.39.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "2D7DE885A6EA7F1D5015D1698920C9BAF7241102836CE0C3837A4F160128A9C4" }, + { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, +] + +[requirements] +gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } +gleeunit = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/src/gleb128.gleam b/src/gleb128.gleam new file mode 100644 index 0000000..9944a7f --- /dev/null +++ b/src/gleb128.gleam @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: MIT + +import gleam/bit_array +import gleam/bytes_builder.{type BytesBuilder} +import gleam/int + +pub type Endianness +{ + Big + Little +} + +// Determines the if the current CPU is either little endian or big endian. +pub fn get_cpu_endianness() -> Result(Endianness, String) +{ + case <<0x01:native-size(32)>> + { + <<0x01, 0x00, 0x00, 0x00>> -> Ok(Little) + <<0x00, 0x00, 0x00, 0x01>> -> Ok(Big) + _ -> Error("Can't determine CPU's endianness. Maybe the CPU uses a mixed-endian format?") + } +} + +/// Decodes an arbitrary bit array into a native unsigned (positive) integer. +@external(erlang, "binary", "decode_unsigned") +pub fn decode_native_unsigned_integer(data: BitArray, endianness: Endianness) -> Int +{ + let size_in_bits = bit_array.byte_size(data) * 8 + + case endianness, data + { + Little, <> -> x + Big, <> -> x + _, _ -> 0 + } +} + +/// Decodes an arbitrary bit array into a native signed (positive or negative) integer. +pub fn decode_native_signed_integer(data: BitArray, endianness: Endianness) -> Int +{ + let size_in_bits = bit_array.byte_size(data) * 8 + + case endianness, data + { + Little, <> -> x + Big, <> -> x + _, _ -> 0 + } +} + +fn do_encode_unsigned(value: Int, builder: BytesBuilder) -> Result(BytesBuilder, String) +{ + case value >= 0 + { + True -> + { + // Get value's least significant 8 bits, keeping the most significant bit among them unset, as the current chunk; + // 0b01111111 = 0x7f + let current_chunk = int.bitwise_and(value, 0b01111111) + + // Get the next 7 bits chunk + let next_chunk = int.bitwise_shift_right(value, 7) + + // There is more chunks to come? + // -> If done, then all next chunk's bits should be 0 + case next_chunk + { + 0 -> Ok(bytes_builder.append(builder, <>)) // No, append + _ -> + { + // Yes, then set the continuation bit (the most significant bit left unset) of the current chunk; + // 0b10000000 = 0x80 + let current_chunk = int.bitwise_or(current_chunk, 0b10000000) + // Append the current chunk to the list and proceeds to encode the next chunk; + do_encode_unsigned(next_chunk, bytes_builder.append(builder, <>)) + } + } + } + False -> Error("Can't encode a negative value with an unsigned function") + } +} + +fn do_encode_signed(value: Int, builder: BytesBuilder) -> BytesBuilder +{ + // Get value's least significant 8 bits, keeping the most significant bit among them unset, as the current chunk; + // 0b01111111 = 0x7f + let current_chunk = int.bitwise_and(value, 0b01111111) + + // Get the next 7 bits chunk + let next_chunk = int.bitwise_shift_right(value, 7) + + // Get the state of the sign bit (second most significant bit) of the current chunk + // 0b01000000 = 0x40 + let sign = int.bitwise_and(value, 0b01000000) + let sign = int.bitwise_shift_right(sign, 6) + + // There is more chunks to come? To check, we'll do the following... + // -> If done and the signed number is positive, then all next chunk's bits will be 0 and the sign bit will be 0 + // -> If done and the signed number is negative, then all next chunk's bits will be 1 (two's complement) and the sign bit will be 1 + case { next_chunk == 0 && sign == 0 } + || { next_chunk == int.bitwise_not(0) && sign == 1 } + { + True -> + { + // Then we're done, lets append the current and last chunk and return; + bytes_builder.append(builder, <>) + } + _ -> + { + // Then set the continuation bit (the most significant bit left unset) of the current chunk; + // 0b10000000 = 0x80 + let current_chunk = int.bitwise_or(current_chunk, 0b10000000) + // Append the current chunk to the list and proceeds to encode the next chunk; + do_encode_signed(next_chunk, bytes_builder.append(builder, <>)) + } + } +} + +fn do_decode_unsigned(data: BitArray, position_accumulator: Int, result_accumulator: Int, shift_accumulator: Int) -> Result(Int, String) +{ + case bit_array.slice(from: data, at: position_accumulator, take: 1) + { + Ok(slice) -> case slice + { + <> -> + { + // Get byte's least significant 8 bits, keeping the most significant bit among them unset, as the current chunk; + // 0b01111111 = 0x7f + let current_chunk = int.bitwise_and(byte, 0b01111111) + + // Join the current chunk with result accumulator + let current_chunk = int.bitwise_shift_left(current_chunk, shift_accumulator) + let result_accumulator = int.bitwise_or(result_accumulator, current_chunk) + + // Get the next 7 bits chunk + let next_chunk = int.bitwise_shift_right(byte, 7) + + case next_chunk + { + 0 -> Ok(result_accumulator) // No more chunks to process, return the result + _ -> do_decode_unsigned(data, position_accumulator + 1, result_accumulator, shift_accumulator + 7) // Continue + } + } + _ -> Error("Can't decode the bit array slice into a byte") + } + _ -> Error("Can't get the bit array slice") + } +} + +fn do_decode_signed(data: BitArray, position_accumulator: Int, result_accumulator: Int, shift_accumulator: Int) -> Result(Int, String) +{ + case bit_array.slice(from: data, at: position_accumulator, take: 1) + { + Ok(slice) -> case slice + { + <> -> + { + // Get value's least significant 8 bits, keeping the most significant bit among them unset, as the current chunk; + // 0b01111111 = 0x7f + let current_chunk = int.bitwise_and(byte, 0b01111111) + + // Join the current chunk with result accumulator + let current_chunk = int.bitwise_shift_left(current_chunk, shift_accumulator) + let result_accumulator = int.bitwise_or(result_accumulator, current_chunk) + + let shift_accumulator = shift_accumulator + 7 + + // Get the next 7 bits chunk + let next_chunk = int.bitwise_shift_right(byte, 7) + + case next_chunk + { + 0 -> + { + // Check the state of the sign bit (second most significant bit) of the current chunk + // 0b01000000 = 0x40 + let sign = int.bitwise_and(byte, 0b01000000) + let sign = int.bitwise_shift_right(sign, 6) + case sign + { + 1 -> Ok(int.bitwise_or(result_accumulator, int.bitwise_shift_left(int.bitwise_not(0), shift_accumulator))) + _ -> Ok(result_accumulator) + } + } + _ -> do_decode_signed(data, position_accumulator + 1, result_accumulator, shift_accumulator) + } + } + _ -> Error("Can't decode the bit array slice into a byte") + } + _ -> Error("Can't get the bit array slice") + } +} + +fn do_fast_decode_unsigned(data: Int, position_accumulator: Int, result_accumulator: Int, shift_accumulator: Int) -> Result(Int, String) +{ + let byte = int.bitwise_shift_right(data, 8 * position_accumulator) + let byte = int.bitwise_and(byte, 0xff) + + // Get byte's least significant 8 bits, keeping the most significant bit among them unset, as the current chunk; + // 0b01111111 = 0x7f + let current_chunk = int.bitwise_and(byte, 0b01111111) + + // Join the current chunk with result accumulator + let current_chunk = int.bitwise_shift_left(current_chunk, shift_accumulator) + let result_accumulator = int.bitwise_or(result_accumulator, current_chunk) + + // Get the next 7 bits chunk + let next_chunk = int.bitwise_shift_right(byte, 7) + + case next_chunk + { + 0 -> Ok(result_accumulator) // No more chunks to process, return the result + _ -> do_fast_decode_unsigned(data, position_accumulator + 1, result_accumulator, shift_accumulator + 7) // Continue + } +} + +fn do_fast_decode_signed(data: Int, position_accumulator: Int, result_accumulator: Int, shift_accumulator: Int) -> Result(Int, String) +{ + let byte = int.bitwise_shift_right(data, 8 * position_accumulator) + let byte = int.bitwise_and(byte, 0xff) + + // Get byte's least significant 8 bits, keeping the most significant bit among them unset, as the current chunk; + // 0b01111111 = 0x7f + let current_chunk = int.bitwise_and(byte, 0b01111111) + + // Join the current chunk with result accumulator + let current_chunk = int.bitwise_shift_left(current_chunk, shift_accumulator) + let result_accumulator = int.bitwise_or(result_accumulator, current_chunk) + + let shift_accumulator = shift_accumulator + 7 + + // Get the next 7 bits chunk + let next_chunk = int.bitwise_shift_right(byte, 7) + + case next_chunk + { + 0 -> + { + // Check the state of the sign bit (second most significant bit) of the current chunk + // 0b01000000 = 0x40 + let sign = int.bitwise_and(byte, 0b01000000) + let sign = int.bitwise_shift_right(sign, 6) + case sign + { + 1 -> Ok(int.bitwise_or(result_accumulator, int.bitwise_shift_left(int.bitwise_not(0), shift_accumulator))) + _ -> Ok(result_accumulator) + } + } + _ -> do_fast_decode_signed(data, position_accumulator + 1, result_accumulator, shift_accumulator) + } +} + +/// Encodes an unsigned (positive) integer to a bit array containing its LEB128 representation. +/// +/// Returns an error when the given value to encode is negative. +pub fn encode_unsigned(value: Int) -> Result(BitArray, String) +{ + case do_encode_unsigned(value, bytes_builder.new()) + { + Ok(result) -> Ok(bytes_builder.to_bit_array(result)) + Error(e) -> Error(e) + } +} + +/// Encodes an signed (positive or negative) integer to a bit array containing its LEB128 representation. +pub fn encode_signed(value: Int) -> BitArray +{ + do_encode_signed(value, bytes_builder.new()) + |> bytes_builder.to_bit_array +} + +/// Decodes a bit array containing some LEB128 integer as an unsigned (positive) native integer. +/// +/// Returns an error when the given data can't be decoded. +pub fn decode_unsigned(data: BitArray) -> Result(Int, String) +{ + do_decode_unsigned(data, 0, 0, 0) +} + +/// Decodes a bit array containing some LEB128 integer as an signed (positive or negative) native integer. +/// +/// Returns an error when the given data can't be decoded. +pub fn decode_signed(data: BitArray) -> Result(Int, String) +{ + do_decode_signed(data, 0, 0, 0) +} + +/// Decodes a bit array containing some LEB128 integer as an unsigned (positive) native integer. +/// When the length of the data is less than or equal to 8 bytes, this function will treat and process +/// the data as an native integer, thus enabling a performance boost. It will fallback to the default +/// decoding function when the length of the data is greater than 8 bytes. +/// +/// Returns an error when the given data can't be decoded. +pub fn fast_decode_unsigned(data: BitArray) -> Result(Int, String) +{ + case bit_array.byte_size(data) + { + s if s <= 8 -> case get_cpu_endianness() + { + Ok(endianness) -> do_fast_decode_unsigned(decode_native_unsigned_integer(data, endianness), 0, 0, 0) + Error(reason) -> Error(reason) + } + _ -> do_decode_unsigned(data, 0, 0, 0) + } +} + +/// Decodes a bit array containing some LEB128 integer as an signed (positive or negative) native integer. +/// When the length of the data is less than or equal to 8 bytes, this function will treat and process +/// the data as an native integer, thus enabling a performance boost. It will fallback to the default +/// decoding function when the length of the data is greater than 8 bytes. +/// +/// Returns an error when the given data can't be decoded. +pub fn fast_decode_signed(data: BitArray) -> Result(Int, String) +{ + case bit_array.byte_size(data) + { + s if s <= 8 -> case get_cpu_endianness() + { + Ok(endianness) -> do_fast_decode_signed(decode_native_signed_integer(data, endianness), 0, 0, 0) + Error(reason) -> Error(reason) + } + _ -> do_decode_signed(data, 0, 0, 0) + } +} diff --git a/test/gleb128_test.gleam b/test/gleb128_test.gleam new file mode 100644 index 0000000..9fcf067 --- /dev/null +++ b/test/gleb128_test.gleam @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: MIT + +import gleeunit +import gleeunit/should +import gleam/list +import gleb128 + +const unsigned_test_cases = +[ + // #(number, unsigned leb128) + #(0, <<0x00>>), + #(1, <<0x01>>), + #(2, <<0x02>>), + #(127, <<0x7f>>), + #(128, <<0x80, 0x01>>), + #(129, <<0x81, 0x01>>), + #(130, <<0x82, 0x01>>), + #(255, <<0xff, 0x01>>), + #(1036, <<0x8c, 0x08>>), + #(12857, <<0xb9, 0x64>>), + #(16256, <<0x80, 0x7f>>), + #(123456, <<0xc0, 0xc4, 0x07>>), + #(624485, <<0xe5, 0x8e, 0x26>>), + #(2000000, <<0x80, 0x89, 0x7a>>), + #(2147483647, <<0xff, 0xff, 0xff, 0xff, 0x07>>), + #(4294967295, <<0xff, 0xff, 0xff, 0xff, 0x0f>>), + #(60000000000000000, <<0x80, 0x80, 0x98, 0xf4, 0xe9, 0xb5, 0xca, 0x6a>>), + #(24197857200151252728969465429440056815, <<0xef, 0x9b, 0xaf, 0x85, 0x89, 0xcf, 0x95, 0x9a, 0x92, 0xde, 0xb7, 0xde, 0x8a, 0x92, 0x9e, 0xab, 0xb4, 0x24>>) +] + +const signed_test_cases = +[ + // #(number, signed leb128) + #(-24197857200151252728969465429440056815, <<0x91, 0xe4, 0xd0, 0xfa, 0xf6, 0xb0, 0xea, 0xe5, 0xed, 0xa1, 0xc8, 0xa1, 0xf5, 0xed, 0xe1, 0xd4, 0xcb, 0x5b>>), + #(-60000000000000000, <<0x80, 0x80, 0xe8, 0x8b, 0x96, 0xca, 0xb5, 0x95, 0x7f>>), + #(-4294967295, <<0x81, 0x80, 0x80, 0x80, 0x70>>), + #(-2147483647, <<0x81, 0x80, 0x80, 0x80, 0x78>>), + #(-2000000, <<0x80, 0xf7, 0x85, 0x7f>>), + #(-624485, <<0x9b, 0xf1, 0x59>>), + #(-123456, <<0xc0, 0xbb, 0x78>>), + #(-16256, <<0x80, 0x81, 0x7f>>), + #(-12857, <<0xc7, 0x9b, 0x7f>>), + #(-1036, <<0xf4, 0x77>>), + #(-255, <<0x81, 0x7e>>), + #(-130, <<0xfe, 0x7e>>), + #(-129, <<0xff, 0x7e>>), + #(-128, <<0x80, 0x7f>>), + #(-127, <<0x81, 0x7f>>), + #(-2, <<0x7e>>), + #(-1, <<0x7f>>), + #(0, <<0x00>>), + #(1, <<0x01>>), + #(2, <<0x02>>), + #(127, <<0xff, 0x00>>), + #(128, <<0x80, 0x01>>), + #(129, <<0x81, 0x01>>), + #(130, <<0x82, 0x01>>), + #(255, <<0xff, 0x01>>), + #(1036, <<0x8c, 0x08>>), + #(12857, <<0xb9, 0xe4, 0x00>>), + #(16256, <<0x80, 0xff, 0x00>>), + #(123456, <<0xc0, 0xc4, 0x07>>), + #(624485, <<0xe5, 0x8e, 0x26>>), + #(2000000, <<0x80, 0x89, 0xfa, 0x00>>), + #(2147483647, <<0xff, 0xff, 0xff, 0xff, 0x07>>), + #(4294967295, <<0xff, 0xff, 0xff, 0xff, 0x0f>>), + #(60000000000000000, <<0x80, 0x80, 0x98, 0xf4, 0xe9, 0xb5, 0xca, 0xea, 0x00>>), + #(24197857200151252728969465429440056815, <<0xef, 0x9b, 0xaf, 0x85, 0x89, 0xcf, 0x95, 0x9a, 0x92, 0xde, 0xb7, 0xde, 0x8a, 0x92, 0x9e, 0xab, 0xb4, 0x24>>) +] + +const unsigned_little_endian_test_cases = +[ + #(0, <<0x00>>), + #(1, <<0x01>>), + #(2, <<0x02>>), + #(16, <<0x10>>), + #(65536, <<0x00, 0x00, 0x01>>), + #(16777216, <<0x00, 0x00, 0x00, 0x01>>), + #(4294967295, <<0xff, 0xff, 0xff, 0xff>>), + #(4294967296, <<0x00, 0x00, 0x00, 0x00, 0x01>>) +] + +const unsigned_big_endian_test_cases = +[ + #(0, <<0x00>>), + #(1, <<0x01>>), + #(1, <<0x00, 0x01>>), + #(2, <<0x02>>), + #(16, <<0x10>>), + #(65536, <<0x01, 0x00, 0x00>>), + #(16777216, <<0x01, 0x00, 0x00, 0x00>>), + #(4294967295, <<0xff, 0xff, 0xff, 0xff>>), + #(4294967296, <<0x01, 0x00, 0x00, 0x00, 0x00>>) +] + +const signed_little_endian_test_cases = +[ + #(-2147483647, <<0x01, 0x00, 0x00, 0x80>>), + #(-65536, <<0x00, 0x00, 0xff, 0xff>>), + #(-65535, <<0x01, 0x00, 0xff, 0xff>>), + #(-1024, <<0x00, 0xfc>>), + #(-3, <<0xfd>>), + #(-2, <<0xfe>>), + #(-1, <<0xff>>), + #(-1, <<0xff, 0xff, 0xff, 0xff>>), + #(2, <<0x02>>), + #(3, <<0x03>>), + #(1024, <<0x00, 0x04>>), + #(65535, <<0xff, 0xff, 0x00>>), + #(65536, <<0x00, 0x00, 0x01>>), + #(2147483647, <<0xff, 0xff, 0xff, 0x7f>>) +] + +const signed_big_endian_test_cases = +[ + #(-2147483647, <<0x80, 0x00, 0x00, 0x01>>), + #(-65536, <<0xff, 0xff, 0x00, 0x00>>), + #(-65535, <<0xff, 0xff, 0x00, 0x01>>), + #(-1024, <<0xfc, 0x00>>), + #(-3, <<0xfd>>), + #(-2, <<0xfe>>), + #(-1, <<0xff>>), + #(-1, <<0xff, 0xff, 0xff, 0xff>>), + #(2, <<0x02>>), + #(3, <<0x03>>), + #(1024, <<0x04, 0x00>>), + #(65535, <<0x00, 0xff, 0xff>>), + #(65536, <<0x01, 0x00, 0x00>>), + #(2147483647, <<0x7f, 0xff, 0xff, 0xff>>) +] + +pub fn main() +{ + gleeunit.main() +} + +pub fn encode_unsigned_test() +{ + unsigned_test_cases + |> list.each + ( + fn (pair) + { + gleb128.encode_unsigned(pair.0) + |> should.equal(Ok(pair.1)) + } + ) +} + +pub fn encode_signed_test() +{ + signed_test_cases + |> list.each + ( + fn (pair) + { + gleb128.encode_signed(pair.0) + |> should.equal(pair.1) + } + ) +} + +pub fn decode_unsigned_test() +{ + unsigned_test_cases + |> list.each + ( + fn (pair) + { + gleb128.decode_unsigned(pair.1) + |> should.be_ok + |> should.equal(pair.0) + } + ) +} + +pub fn decode_signed_test() +{ + signed_test_cases + |> list.each + ( + fn (pair) + { + gleb128.decode_signed(pair.1) + |> should.be_ok + |> should.equal(pair.0) + } + ) +} + +pub fn fast_decode_unsigned_test() +{ + unsigned_test_cases + |> list.each + ( + fn (pair) + { + gleb128.fast_decode_unsigned(pair.1) + |> should.be_ok + |> should.equal(pair.0) + } + ) +} + +pub fn fast_decode_signed_test() +{ + signed_test_cases + |> list.each + ( + fn (pair) + { + gleb128.fast_decode_signed(pair.1) + |> should.be_ok + |> should.equal(pair.0) + } + ) +} + +pub fn decode_native_unsigned_integer__little_endian_test() +{ + unsigned_little_endian_test_cases + |> list.each + ( + fn (pair) + { + gleb128.decode_native_unsigned_integer(pair.1, gleb128.Little) + |> should.equal(pair.0) + } + ) +} + +pub fn decode_native_unsigned_integer__big_endian_test() +{ + unsigned_big_endian_test_cases + |> list.each + ( + fn (pair) + { + gleb128.decode_native_unsigned_integer(pair.1, gleb128.Big) + |> should.equal(pair.0) + } + ) +} + +pub fn decode_native_signed_integer__little_endian_test() +{ + signed_little_endian_test_cases + |> list.each + ( + fn (pair) + { + gleb128.decode_native_signed_integer(pair.1, gleb128.Little) + |> should.equal(pair.0) + } + ) +} + +pub fn decode_native_signed_integer__big_endian_test() +{ + signed_big_endian_test_cases + |> list.each + ( + fn (pair) + { + gleb128.decode_native_signed_integer(pair.1, gleb128.Big) + |> should.equal(pair.0) + } + ) +}