Simple algebraic dice notation parser for Gleam
3

Configure Feed

Select the types of activity you want to include in your feed.

Switch to Birdie snapshot testing

Naomi Roberts (May 7, 2025, 4:03 PM +0100) 14186db3 f3cca6c4

+167 -22
+7
birdie_snapshots/2d20_with_advantage_(kh).accepted
··· 1 + --- 2 + version: 1.2.7 3 + title: 2d20 with Advantage (kh) 4 + file: ./test/diced_test.gleam 5 + test_name: roll_1d20_advantage_test 6 + --- 7 + Ok(Basic(2, 20, [KeepHighest(1)]))
+7
birdie_snapshots/2d20_with_disadvantage_(kl).accepted
··· 1 + --- 2 + version: 1.2.7 3 + title: 2d20 with Disadvantage (kl) 4 + file: ./test/diced_test.gleam 5 + test_name: roll_1d20_disadvantage_test 6 + --- 7 + Ok(Basic(2, 20, [KeepLowest(1)]))
+7
birdie_snapshots/2d_f_with_advantage.accepted
··· 1 + --- 2 + version: 1.2.7 3 + title: 2dF with Advantage 4 + file: ./test/diced_test.gleam 5 + test_name: roll_1df_keep_highest_test 6 + --- 7 + Ok(Fate(2, [KeepHighest(1)]))
+11
birdie_snapshots/5d20_with_multiple_modifiers.accepted
··· 1 + --- 2 + version: 1.2.7 3 + title: 5d20 with multiple modifiers 4 + file: ./test/diced_test.gleam 5 + test_name: roll_5d20_keep_highest_and_lowest_2_test 6 + --- 7 + Ok(Basic( 8 + 5, 9 + 20, 10 + [KeepHighest(2), KeepLowest(2)], 11 + ))
+7
birdie_snapshots/basic_1.accepted
··· 1 + --- 2 + version: 1.2.7 3 + title: Basic 1 4 + file: ./test/diced_test.gleam 5 + test_name: roll_1_test 6 + --- 7 + Ok(Number(1, []))
+7
birdie_snapshots/basic_1d.accepted
··· 1 + --- 2 + version: 1.2.7 3 + title: Basic 1d 4 + file: ./test/diced_test.gleam 5 + test_name: invalid_input_1d_test 6 + --- 7 + Ok(Basic(1, 6, []))
+7
birdie_snapshots/basic_1d20.accepted
··· 1 + --- 2 + version: 1.2.7 3 + title: Basic 1d20 4 + file: ./test/diced_test.gleam 5 + test_name: roll_1d20_test 6 + --- 7 + Ok(Basic(1, 20, []))
+7
birdie_snapshots/basic_1d6.accepted
··· 1 + --- 2 + version: 1.2.7 3 + title: Basic 1d6 4 + file: ./test/diced_test.gleam 5 + test_name: roll_1d6_test 6 + --- 7 + Ok(Basic(1, 6, []))
+7
birdie_snapshots/basic_2.accepted
··· 1 + --- 2 + version: 1.2.7 3 + title: Basic 2 4 + file: ./test/diced_test.gleam 5 + test_name: roll_2_test 6 + --- 7 + Ok(Number(2, []))
+7
birdie_snapshots/basic_20d20.accepted
··· 1 + --- 2 + version: 1.2.7 3 + title: Basic 20d20 4 + file: ./test/diced_test.gleam 5 + test_name: roll_20d20_test 6 + --- 7 + Ok(Basic(20, 20, []))
+7
birdie_snapshots/basic_2d12.accepted
··· 1 + --- 2 + version: 1.2.7 3 + title: Basic 2d12 4 + file: ./test/diced_test.gleam 5 + test_name: roll_2d12_test 6 + --- 7 + Ok(Basic(2, 12, []))
+7
birdie_snapshots/basic_d20.accepted
··· 1 + --- 2 + version: 1.2.7 3 + title: Basic d20 4 + file: ./test/diced_test.gleam 5 + test_name: roll_d20_test 6 + --- 7 + Ok(Basic(1, 20, []))
+7
birdie_snapshots/fate_1d_f.accepted
··· 1 + --- 2 + version: 1.2.7 3 + title: Fate 1dF 4 + file: ./test/diced_test.gleam 5 + test_name: roll_1df_test 6 + --- 7 + Ok(Fate(1, []))
+2
gleam.toml
··· 11 11 12 12 [dev-dependencies] 13 13 gleeunit = ">= 1.0.0 and < 2.0.0" 14 + birdie = ">= 1.2.7 and < 2.0.0" 15 + pprint = ">= 1.0.5 and < 2.0.0"
+20
manifest.toml
··· 2 2 # You typically do not need to edit this file 3 3 4 4 packages = [ 5 + { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, 6 + { name = "birdie", version = "1.2.7", build_tools = ["gleam"], requirements = ["argv", "edit_distance", "filepath", "glance", "gleam_community_ansi", "gleam_erlang", "gleam_stdlib", "justin", "rank", "simplifile", "term_size", "trie_again"], otp_app = "birdie", source = "hex", outer_checksum = "F9EFE5AD3F61E840CB7D4F6C88121CF7CCE5F89B74828ED86BD90F61E69CA80D" }, 7 + { name = "edit_distance", version = "2.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "edit_distance", source = "hex", outer_checksum = "A1E485C69A70210223E46E63985FA1008B8B2DDA9848B7897469171B29020C05" }, 8 + { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, 9 + { name = "glam", version = "2.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glam", source = "hex", outer_checksum = "4932A2D139AB0389E149396407F89654928D7B815E212BB02F13C66F53B1BBA1" }, 10 + { name = "glance", version = "3.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "F3458292AFB4136CEE23142A8727C1270494E7A96978B9B9F9D2C1618583EF3D" }, 11 + { name = "gleam_community_ansi", version = "1.4.3", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8A62AE9CC6EA65BEA630D95016D6C07E4F9973565FA3D0DE68DC4200D8E0DD27" }, 12 + { name = "gleam_community_colour", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "FDD6AC62C6EC8506C005949A4FCEF032038191D5EAAEC3C9A203CD53AE956ACA" }, 13 + { name = "gleam_erlang", version = "0.34.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "0C38F2A128BAA0CEF17C3000BD2097EB80634E239CE31A86400C4416A5D0FDCC" }, 14 + { name = "gleam_json", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "C55C5C2B318533A8072D221C5E06E5A75711C129E420DD1CE463342106012E5D" }, 15 + { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" }, 5 16 { name = "gleam_stdlib", version = "0.59.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "F8FEE9B35797301994B81AF75508CF87C328FE1585558B0FFD188DC2B32EAA95" }, 6 17 { name = "gleeunit", version = "1.3.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "A7DD6C07B7DA49A6E28796058AA89E651D233B357D5607006D70619CD89DAAAB" }, 18 + { name = "glexer", version = "2.2.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "5C235CBDF4DA5203AD5EAB1D6D8B456ED8162C5424FE2309CFFB7EF438B7C269" }, 19 + { name = "justin", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "7FA0C6DB78640C6DC5FBFD59BF3456009F3F8B485BF6825E97E1EB44E9A1E2CD" }, 20 + { name = "pprint", version = "1.0.5", build_tools = ["gleam"], requirements = ["glam", "gleam_stdlib"], otp_app = "pprint", source = "hex", outer_checksum = "B5328E55FB9EED3941F1648536FDBFA0D97B0C21D21A6625D726CCB22A36782E" }, 21 + { name = "rank", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "rank", source = "hex", outer_checksum = "5660E361F0E49CBB714CC57CC4C89C63415D8986F05B2DA0C719D5642FAD91C9" }, 22 + { name = "simplifile", version = "2.2.1", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "C88E0EE2D509F6D86EB55161D631657675AA7684DAB83822F7E59EB93D9A60E3" }, 7 23 { name = "splitter", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "splitter", source = "hex", outer_checksum = "128FC521EE33B0012E3E64D5B55168586BC1B9C8D7B0D0CA223B68B0D770A547" }, 24 + { name = "term_size", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "term_size", source = "hex", outer_checksum = "D00BD2BC8FB3EBB7E6AE076F3F1FF2AC9D5ED1805F004D0896C784D06C6645F1" }, 25 + { name = "trie_again", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "trie_again", source = "hex", outer_checksum = "5B19176F52B1BD98831B57FDC97BD1F88C8A403D6D8C63471407E78598E27184" }, 8 26 ] 9 27 10 28 [requirements] 29 + birdie = { version = ">= 1.2.7 and < 2.0.0" } 11 30 gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } 12 31 gleeunit = { version = ">= 1.0.0 and < 2.0.0" } 32 + pprint = { version = ">= 1.0.5 and < 2.0.0" } 13 33 splitter = { version = ">= 1.0.0 and < 2.0.0" }
+13 -9
src/diced.gleam
··· 39 39 pub type Dice { 40 40 Basic(amount: Int, value: Int, modifiers: List(Modifier)) 41 41 Fate(amount: Int, modifiers: List(Modifier)) 42 + Number(value: Int, modifiers: List(Modifier)) 42 43 } 43 44 44 45 pub opaque type DiceError { ··· 51 52 pub fn parse(string string: String) -> Result(Dice, DiceError) { 52 53 let dice_splitter = splitter.new(["d", "dF"]) 53 54 case splitter.split(dice_splitter, string) { 54 - #(amount, "", mods) -> { 55 - use amount <- result.try(parse_amount(amount)) 55 + #(value, "", mods) -> { 56 + use value <- result.try( 57 + int.parse(value) |> result.replace_error(InvalidValue), 58 + ) 56 59 use modifiers <- result.try(parse_modifiers(mods)) 57 - Ok(Basic(amount:, value: amount, modifiers:)) 60 + Ok(Number(value:, modifiers:)) 58 61 } 59 62 #(amount, "d", "") -> { 60 63 use amount <- result.try(parse_amount(amount)) 61 64 Ok(Basic(amount:, value: 6, modifiers: [])) 62 65 } 63 - #("", "d", value_mods) -> { 64 - use #(value, rest) <- result.try(parse_value(value_mods)) 66 + #("", "d", mods) -> { 67 + use #(value, rest) <- result.try(parse_value(mods)) 65 68 use modifiers <- result.try(parse_modifiers(rest)) 66 69 Ok(Basic(1, value:, modifiers:)) 67 70 } 68 - #(amount, "d", value_mods) -> { 71 + #(amount, "d", mods) -> { 69 72 use amount <- result.try(parse_amount(amount)) 70 - use #(value, rest) <- result.try(parse_value(value_mods)) 73 + use #(value, rest) <- result.try(parse_value(mods)) 71 74 use modifiers <- result.try(parse_modifiers(rest)) 72 75 Ok(Basic(amount:, value:, modifiers:)) 73 76 } 74 - #(amount, "dF", _mods) -> { 77 + #(amount, "dF", mods) -> { 75 78 use amount <- result.try(parse_amount(amount)) 76 - Ok(Fate(amount:, modifiers: [])) 79 + use modifiers <- result.try(parse_modifiers(mods)) 80 + Ok(Fate(amount:, modifiers:)) 77 81 } 78 82 #(_, _, _) -> Error(InvalidString) 79 83 }
+37 -13
test/diced_test.gleam
··· 1 - import diced.{Basic, Fate} 1 + import birdie 2 + import diced 2 3 import gleeunit 3 - import gleeunit/should 4 + import pprint 4 5 5 6 pub fn main() { 6 7 gleeunit.main() ··· 8 9 9 10 pub fn roll_1d6_test() { 10 11 diced.parse("1d6") 11 - |> should.equal(Ok(Basic(1, 6, []))) 12 + |> pprint.format() 13 + |> birdie.snap(title: "Basic 1d6") 12 14 } 13 15 14 16 pub fn roll_1d20_test() { 15 17 diced.parse("1d20") 16 - |> should.equal(Ok(Basic(1, 20, []))) 18 + |> pprint.format() 19 + |> birdie.snap(title: "Basic 1d20") 17 20 } 18 21 19 22 pub fn roll_2d12_test() { 20 23 diced.parse("2d12") 21 - |> should.equal(Ok(Basic(2, 12, []))) 24 + |> pprint.format() 25 + |> birdie.snap(title: "Basic 2d12") 22 26 } 23 27 24 28 pub fn roll_1d20_advantage_test() { 25 29 diced.parse("2d20kh") 26 - |> should.equal(Ok(Basic(2, 20, [diced.KeepHighest(1)]))) 30 + |> pprint.format() 31 + |> birdie.snap(title: "2d20 with Advantage (kh)") 27 32 } 28 33 29 34 pub fn roll_1d20_disadvantage_test() { 30 35 diced.parse("2d20kl") 31 - |> should.equal(Ok(Basic(2, 20, [diced.KeepLowest(1)]))) 36 + |> pprint.format() 37 + |> birdie.snap(title: "2d20 with Disadvantage (kl)") 32 38 } 33 39 34 40 pub fn roll_1_test() { 35 41 diced.parse("1") 36 - |> should.equal(Ok(Basic(1, 1, []))) 42 + |> pprint.format() 43 + |> birdie.snap(title: "Basic 1") 44 + } 45 + 46 + pub fn roll_2_test() { 47 + diced.parse("2") 48 + |> pprint.format() 49 + |> birdie.snap(title: "Basic 2") 37 50 } 38 51 39 52 pub fn invalid_input_1d_test() { 40 53 diced.parse("1d") 41 - |> should.equal(Ok(Basic(1, 6, []))) 54 + |> pprint.format() 55 + |> birdie.snap(title: "Basic 1d") 42 56 } 43 57 44 58 pub fn roll_d20_test() { 45 59 diced.parse("d20") 46 - |> should.equal(Ok(Basic(1, 20, []))) 60 + |> pprint.format() 61 + |> birdie.snap(title: "Basic d20") 47 62 } 48 63 49 64 pub fn roll_20d20_test() { 50 65 diced.parse("20d20") 51 - |> should.equal(Ok(Basic(20, 20, []))) 66 + |> pprint.format() 67 + |> birdie.snap(title: "Basic 20d20") 52 68 } 53 69 54 70 pub fn roll_5d20_keep_highest_and_lowest_2_test() { 55 71 diced.parse("5d20kh2kl2") 56 - |> should.equal(Ok(Basic(5, 20, [diced.KeepHighest(2), diced.KeepLowest(2)]))) 72 + |> pprint.format() 73 + |> birdie.snap(title: "5d20 with multiple modifiers") 57 74 } 58 75 59 76 pub fn roll_1df_test() { 60 77 diced.parse("1dF") 61 - |> should.equal(Ok(Fate(1, []))) 78 + |> pprint.format() 79 + |> birdie.snap(title: "Fate 1dF") 80 + } 81 + 82 + pub fn roll_1df_keep_highest_test() { 83 + diced.parse("2dFkh") 84 + |> pprint.format() 85 + |> birdie.snap(title: "2dF with Advantage") 62 86 }