app "roctorrent" packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" } imports [pf.Stdout] provides [main] to pf # Decodes an exact string, errors if there is remaining unused data bDecodeStr: List U8 -> Result (List U8) [Malformatted] bDecodeStr = \i -> when bDecodeStrAndRemainder i is Ok {before, others} -> if List.len others == 0 then Ok before else Err Malformatted Err _ -> Err Malformatted # Decodes a string and returns the remainder of the input as well if there was any bDecodeStrAndRemainder: List U8 -> Result {before: List U8, others: List U8} [Malformatted] bDecodeStrAndRemainder = \i -> when (List.splitFirst i ':') is Ok {before, after} -> when (Str.fromUtf8 before) is Ok ls -> when (Str.toNat ls) is Ok l -> if List.len after >= l then Ok (List.split after l) else Err Malformatted Err _err -> Err Malformatted Err _err -> Err Malformatted Err _err -> Err Malformatted expect res = bDecodeStr (Str.toUtf8 "3:foo") res == Ok (Str.toUtf8 "foo") expect errCases = [ "", "foo", "1:foo", "4:foo", ":", "foo:", "1:", "::", ] List.all errCases \t -> res = bDecodeStr (Str.toUtf8 t) res == Err Malformatted # Decodes an exact number, exta input causes an error bDecodeNum: List U8 -> Result I64 [Malformatted Str] bDecodeNum = \i -> when bDecodeNumAndRem i is Ok {num, rem} -> if (List.len rem) == 0 then Ok num else Err (Malformatted "remaining characters after number") Err e -> Err e # Decodes a number and returns the number and any unused input bDecodeNumAndRem: List U8 -> Result {num: I64, rem: (List U8)} [Malformatted Str] bDecodeNumAndRem = \i -> when (List.first i) is Ok f -> if f == 'i' then when List.findFirstIndex i (\j -> j == 'e') is Ok ix -> {before, others} = List.split i (ix+1) numl = List.sublist before {start: 1, len: ix-1} when (Str.fromUtf8 numl) is Ok nums -> when (Str.toI64 nums) is Ok num -> Ok {num: num, rem: others} Err _e -> Err (Malformatted "failed to parse number from \(nums)") Err _e -> Err (Malformatted "failed to parse utf8") Err _e -> Err (Malformatted "failed to find number end char 'e'") else Err (Malformatted "number didn't start with 'i'") Err _e -> Err (Malformatted "number string was empty") expect res = bDecodeNum (Str.toUtf8 "i32e") res == Ok 32i64 expect errCases = [ {case: "", msg: "number string was empty"}, {case: "foo", msg: "number didn't start with 'i'"}, {case: "i32", msg: "failed to find number end char 'e'"}, {case: "32e", msg: "number didn't start with 'i'"}, {case: "ifooe", msg: "failed to parse number from foo"}, {case: "ie", msg: "failed to parse number from "}, {case: "i", msg: "failed to find number end char 'e'"}, {case: "e", msg: "number didn't start with 'i'"}, {case: "i522222222222222222343423432434322e", msg: "failed to parse number from 522222222222222222343423432434322"}, ] List.all errCases \{case, msg} -> res = bDecodeNum (Str.toUtf8 case) res == Err (Malformatted msg) # Tags for storing heterogenous collections BVal: [ BStr (List U8), BNum I64, BList (List BVal), ] # Detects and decodes any kind of element. bDecodeValueAndRem: (List U8) -> Result {res: BVal, others: (List U8)} [Malformatted, End (List U8)] bDecodeValueAndRem = \i -> when (List.first i) is Ok first -> when first is '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9' -> when bDecodeStrAndRemainder i is Ok {before, others} -> Ok {res: BStr before, others: others} Err _err -> Err Malformatted 'i' -> when bDecodeNumAndRem i is Ok {num, rem} -> Ok {res: BNum num, others: rem} Err _err -> Err Malformatted 'l' -> when bDecodeListAndRem i is Ok {res, others} -> Ok {res: BList res, others: others} Err _err -> Err Malformatted 'e' -> {before, others} = List.split i 1 Err (End others) _ -> Err Malformatted Err _err -> Err Malformatted expect res = bDecodeValueAndRem (Str.toUtf8 "i64efoo") res == Ok {res: BNum 64, others: (Str.toUtf8 "foo")} expect res = bDecodeValueAndRem (Str.toUtf8 "3:foo3:foo") res == Ok {res: BStr (Str.toUtf8 "foo"), others: (Str.toUtf8 "3:foo")} expect res = bDecodeValueAndRem (Str.toUtf8 "efoo") res == Err (End (Str.toUtf8 "foo")) expect res = bDecodeValueAndRem (Str.toUtf8 "z") res == Err Malformatted # Decodes a list. List elements can be any bDecodeList: (List U8) -> Result {res: (List BVal)} [Malformatted] bDecodeList = \i -> when bDecodeListAndRem i is Ok {res, others} -> if (List.len others) == 0 then Ok {res: res} else Err Malformatted Err e -> Err e bDecodeListAndRem: (List U8) -> Result {res: (List BVal), others: (List U8)} [Malformatted] bDecodeListAndRem = \i -> when List.first i is Ok f -> if f == 'l' then {before, others} = List.split i 1 bDecodeListAndRemInternal [] others else Err Malformatted Err _e -> Err Malformatted bDecodeListAndRemInternal: List BVal, List U8 -> Result {res: (List BVal), others: (List U8)} [Malformatted] bDecodeListAndRemInternal = \l, i -> when bDecodeValueAndRem i is Ok {res, others} -> l2 = List.append l res bDecodeListAndRemInternal l2 others Err e -> when e is End others -> Ok {res: l, others: others} Malformatted -> Err Malformatted expect res = bDecodeList (Str.toUtf8 "le") res == Ok { res: []} expect res = bDecodeList (Str.toUtf8 "li123e3:fooe") res == Ok { res: [BNum 123i64, BStr (Str.toUtf8 "foo")] } expect res = bDecodeList (Str.toUtf8 "li123e3:fooe123") res == Err Malformatted expect res = bDecodeList (Str.toUtf8 "lli123eee") res == Ok { res: [BList [BNum 123i64]]} main = Stdout.line "Hello, World"