Simple algebraic dice notation parser for Gleam
3

Configure Feed

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

Support multiple modifiers

Naomi Roberts (May 7, 2025, 4:03 PM +0100) f3cca6c4 0d5619ab

+66 -35
+66 -35
src/diced.gleam
··· 1 1 import gleam/int 2 - import gleam/option 2 + import gleam/list 3 3 import gleam/result 4 4 import splitter 5 5 ··· 36 36 "M", 37 37 ] 38 38 39 - fn parse_modifier(mod mod: String) -> Result(option.Option(Modifier), DiceError) { 40 - case mod { 41 - "kh" -> Ok(option.Some(KeepHighest(1))) 42 - "kl" -> Ok(option.Some(KeepLowest(1))) 43 - "dh" -> Ok(option.Some(DropHighest(1))) 44 - "dl" -> Ok(option.Some(DropLowest(1))) 45 - "r" -> Ok(option.Some(Reroll(1))) 46 - "R" -> Ok(option.Some(RecursiveReroll(1))) 47 - "!" -> Ok(option.Some(Explode(1))) 48 - "m" -> Ok(option.Some(Minimum(1))) 49 - "M" -> Ok(option.Some(Maximum(1))) 50 - "" -> Ok(option.None) 51 - _ -> Error(InvalidModifier) 52 - } 53 - } 54 - 55 39 pub type Dice { 56 40 Basic(amount: Int, value: Int, modifiers: List(Modifier)) 57 41 Fate(amount: Int, modifiers: List(Modifier)) ··· 67 51 pub fn parse(string string: String) -> Result(Dice, DiceError) { 68 52 let dice_splitter = splitter.new(["d", "dF"]) 69 53 case splitter.split(dice_splitter, string) { 70 - #(amount, "", "") -> { 54 + #(amount, "", mods) -> { 71 55 use amount <- result.try(parse_amount(amount)) 72 - Ok(Basic(amount:, value: amount, modifiers: [])) 56 + use modifiers <- result.try(parse_modifiers(mods)) 57 + Ok(Basic(amount:, value: amount, modifiers:)) 73 58 } 74 59 #(amount, "d", "") -> { 75 60 use amount <- result.try(parse_amount(amount)) 76 61 Ok(Basic(amount:, value: 6, modifiers: [])) 77 62 } 78 63 #("", "d", value_mods) -> { 79 - use #(value, modifiers) <- result.try(parse_value_mods(value_mods)) 64 + use #(value, rest) <- result.try(parse_value(value_mods)) 65 + use modifiers <- result.try(parse_modifiers(rest)) 80 66 Ok(Basic(1, value:, modifiers:)) 81 67 } 82 68 #(amount, "d", value_mods) -> { 83 69 use amount <- result.try(parse_amount(amount)) 84 - use #(value, modifiers) <- result.try(parse_value_mods(value_mods)) 70 + use #(value, rest) <- result.try(parse_value(value_mods)) 71 + use modifiers <- result.try(parse_modifiers(rest)) 85 72 Ok(Basic(amount:, value:, modifiers:)) 86 73 } 87 74 #(amount, "dF", _mods) -> { ··· 97 84 |> result.replace_error(InvalidAmount) 98 85 } 99 86 100 - fn parse_value_mods( 101 - value_mods value_mods: String, 102 - ) -> Result(#(Int, List(Modifier)), DiceError) { 87 + fn parse_value(value value: String) -> Result(#(Int, String), DiceError) { 103 88 let modifier_splitter = splitter.new(modifier_strings) 104 - let #(value, modifiers, _) = splitter.split(modifier_splitter, value_mods) 89 + let #(value, a, b) = splitter.split(modifier_splitter, value) 105 90 use value <- result.try(result.replace_error(int.parse(value), InvalidValue)) 106 - use modifier <- result.try(result.replace_error( 107 - parse_modifier(modifiers), 108 - InvalidModifier, 109 - )) 91 + Ok(#(value, a <> b)) 92 + } 93 + 94 + fn parse_modifiers(modifiers: String) -> Result(List(Modifier), DiceError) { 95 + do_parse_modifiers(modifiers, Ok([])) 96 + } 97 + 98 + fn do_parse_modifiers( 99 + modifiers: String, 100 + found: Result(List(Modifier), DiceError), 101 + ) -> Result(List(Modifier), DiceError) { 102 + use found <- result.try(found) 103 + let splitter = splitter.new(modifier_strings) 104 + 105 + case splitter.split(splitter, modifiers) { 106 + #("", "", "") -> Ok(list.reverse(found)) 107 + #("", mod, rest) -> { 108 + let #(rest, value) = width_modifier_value(rest) 109 + let modifier = case mod { 110 + "kh" -> KeepHighest(value) 111 + "kl" -> KeepLowest(value) 112 + "dh" -> DropHighest(value) 113 + "dl" -> DropLowest(value) 114 + "r" -> Reroll(value) 115 + "R" -> RecursiveReroll(value) 116 + "!" -> Explode(value) 117 + "m" -> Minimum(value) 118 + "M" -> Maximum(value) 119 + _ -> panic as "unreachable" 120 + } 121 + do_parse_modifiers(rest, Ok([modifier, ..found])) 122 + } 123 + _ -> Error(InvalidModifier) 124 + } 125 + } 110 126 111 - Ok( 112 - #(value, case modifier { 113 - option.None -> [] 114 - option.Some(mod) -> [mod] 115 - }), 116 - ) 127 + fn width_modifier_value(string: String) -> #(String, Int) { 128 + case do_with_modifier_value(string, 0) { 129 + #(a, v) if v == 0 -> #(a, 1) 130 + #(a, v) -> #(a, v) 131 + } 132 + } 133 + 134 + fn do_with_modifier_value(string: String, value: Int) -> #(String, Int) { 135 + case string { 136 + "0" <> rest -> do_with_modifier_value(rest, value * 10) 137 + "1" <> rest -> do_with_modifier_value(rest, value * 10 + 1) 138 + "2" <> rest -> do_with_modifier_value(rest, value * 10 + 2) 139 + "3" <> rest -> do_with_modifier_value(rest, value * 10 + 3) 140 + "4" <> rest -> do_with_modifier_value(rest, value * 10 + 4) 141 + "5" <> rest -> do_with_modifier_value(rest, value * 10 + 5) 142 + "6" <> rest -> do_with_modifier_value(rest, value * 10 + 6) 143 + "7" <> rest -> do_with_modifier_value(rest, value * 10 + 7) 144 + "8" <> rest -> do_with_modifier_value(rest, value * 10 + 8) 145 + "9" <> rest -> do_with_modifier_value(rest, value * 10 + 9) 146 + _ -> #(string, value) 147 + } 117 148 }