check in
Some checks failed
test / test (push) Failing after 18s

This commit is contained in:
Hamcha 2024-11-30 15:48:01 +01:00
commit 42e195dcbd
Signed by: hamcha
GPG key ID: 1669C533B8CF6D89
7 changed files with 188 additions and 0 deletions

View file

@ -0,0 +1,21 @@
name: test
on:
push:
branches:
- master
pull_request:
jobs:
test:
runs-on: docker
steps:
- uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: "27.1.2"
gleam-version: "1.6.2"
rebar3-version: "3"
- run: gleam deps download
- run: gleam test
- run: gleam format --check src test

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
*.beam
*.ez
/build
erl_crash.dump

12
README.md Normal file
View file

@ -0,0 +1,12 @@
# Kmer, father of [Pdor]
Decoder library for CBOR.
Based on Noah Pederson's [gleemor] module, licensed under MIT
## License
This library is licensed under the GNU Affero General Public License version 3, see `LICENSE`.
[Pdor]:https://www.youtube.com/watch?v=k3aehC2SSPw
[gleemor]:https://github.com/chiefnoah/gleebor

13
gleam.toml Normal file
View file

@ -0,0 +1,13 @@
name = "kmer"
version = "0.1.0"
description = "CBOR decoder library"
licences = ["AGPL-3.0-only"]
repository = { type = "forgejo", host = "git.fromouter.space", user = "hamcha", repo = "kmer" }
gleam = ">= 1.6.2"
[dependencies]
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"

11
manifest.toml Normal file
View file

@ -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.45.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "206FCE1A76974AECFC55AEBCD0217D59EDE4E408C016E2CFCCC8FF51278F186E" },
{ 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" }

51
src/kmer.gleam Normal file
View file

@ -0,0 +1,51 @@
import gleam/result.{try}
pub type DecodedValue {
Integer(value: Int)
}
type DecoderResult(t) =
Result(#(t, BitArray), DecodeError)
pub type DecodeResult =
DecoderResult(DecodedValue)
pub opaque type DecodeError {
// More data was expected but file/stream ended prematurely
UnexpectedEnd
// Malformed sequence found
Malformed
}
pub fn decode_next_value(bits: BitArray) -> DecodeResult {
case bits {
<<0:3, rest:bits>> ->
decode_int(rest)
|> map_decoded(Integer)
<<1:3, rest:bits>> ->
decode_int(rest)
|> map_decoded(fn(x) { Integer(-1 - x) })
_ -> Error(Malformed)
}
}
fn decode_int(bits: BitArray) -> DecoderResult(Int) {
case bits {
<<24:int-size(5), val:int-unsigned-size(8), rest:bits>> -> Ok(#(val, rest))
<<25:int-size(5), val:int-unsigned-size(16), rest:bits>> -> Ok(#(val, rest))
<<26:int-size(5), val:int-unsigned-size(32), rest:bits>> -> Ok(#(val, rest))
<<27:int-size(5), val:int-unsigned-size(64), rest:bits>> -> Ok(#(val, rest))
<<x:int-size(5), rest:bits>> if x < 24 -> Ok(#(x, rest))
<<x:int-size(5), _:bits>> if x >= 24 -> Error(Malformed)
_ -> Error(UnexpectedEnd)
}
}
fn map_decoded(
result: DecoderResult(t),
map_fn: fn(t) -> DecodedValue,
) -> DecodeResult {
use #(value, rest) <- try(result)
Ok(#(map_fn(value), rest))
}

76
test/kmer_test.gleam Normal file
View file

@ -0,0 +1,76 @@
import gleam/list
import gleeunit
import gleeunit/should
import kmer.{type DecodeResult, type DecodedValue, Integer}
pub fn main() {
gleeunit.main()
}
fn table_test_decode_next_value(test_case: BitArray, expected: DecodedValue) {
kmer.decode_next_value(test_case) |> is_value(expected)
}
fn is_value(val: DecodeResult, expected: DecodedValue) {
should.be_ok(val) |> should.equal(#(expected, <<>>))
}
pub fn decode_immediate_int_test() {
[
// Small integers under 24
#(<<0>>, Integer(0)),
#(<<10>>, Integer(10)),
#(<<23>>, Integer(23)),
]
|> table_fn(table_test_decode_next_value)
}
pub fn decode_int_test() {
[
// 1-byte length (Maj.arg 24)
#(<<24, 24>>, Integer(24)),
#(<<24, 100>>, Integer(100)),
// 2-byte length (Maj.arg 25)
#(<<25, 1234:16>>, Integer(1234)),
#(<<25, 12_345:16>>, Integer(12_345)),
// 3-byte length (Maj.arg 26)
#(<<26, 1_234_567:32>>, Integer(1_234_567)),
#(<<26, 999_999_999:32>>, Integer(999_999_999)),
// 4-byte length (Maj.arg 27)
#(<<27, 2_099_210_021_019_898_881:64>>, Integer(2_099_210_021_019_898_881)),
#(<<27, 3:64>>, Integer(3)),
]
|> table_fn(table_test_decode_next_value)
}
pub fn decode_negative_int_test() {
[
// Immediate negative (under 25)
#(<<1:3, 0:5>>, Integer(-1)),
#(<<1:3, 8:5>>, Integer(-9)),
#(<<1:3, 23:5>>, Integer(-24)),
// 1-byte length (Maj.arg 24)
#(<<1:3, 24:5, 24:8>>, Integer(-25)),
#(<<1:3, 24:5, 100:8>>, Integer(-101)),
// 2-byte length (Maj.arg 25)
#(<<1:3, 25:5, 1234:16>>, Integer(-1235)),
#(<<1:3, 25:5, 12_345:16>>, Integer(-12_346)),
// 3-byte length (Maj.arg 26)
#(<<1:3, 26:5, 1_234_567:32>>, Integer(-1_234_568)),
#(<<1:3, 26:5, 999_999_999:32>>, Integer(-1_000_000_000)),
// 4-byte length (Maj.arg 27)
#(
<<1:3, 27:5, 2_099_210_021_019_898_881:64>>,
Integer(-2_099_210_021_019_898_882),
),
#(<<1:3, 27:5, 3:64>>, Integer(-4)),
]
|> table_fn(table_test_decode_next_value)
}
fn table_fn(tests: List(#(a, b)), test_fn: fn(a, b) -> Nil) {
list.map(tests, fn(t) {
let #(test_case, expected) = t
test_fn(test_case, expected)
})
}