commit
42e195dcbd
7 changed files with 188 additions and 0 deletions
21
.forgejo/workflows/test.yml
Normal file
21
.forgejo/workflows/test.yml
Normal 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
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
*.beam
|
||||||
|
*.ez
|
||||||
|
/build
|
||||||
|
erl_crash.dump
|
12
README.md
Normal file
12
README.md
Normal 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
13
gleam.toml
Normal 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
11
manifest.toml
Normal 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
51
src/kmer.gleam
Normal 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
76
test/kmer_test.gleam
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in a new issue