diff --git a/src/diced.gleam b/src/diced.gleam index 01dac1a..7404196 100644 --- a/src/diced.gleam +++ b/src/diced.gleam @@ -1,5 +1,5 @@ import gleam/int -import gleam/option +import gleam/list import gleam/result import splitter @@ -36,22 +36,6 @@ const modifier_strings = [ "M", ] -fn parse_modifier(mod mod: String) -> Result(option.Option(Modifier), DiceError) { - case mod { - "kh" -> Ok(option.Some(KeepHighest(1))) - "kl" -> Ok(option.Some(KeepLowest(1))) - "dh" -> Ok(option.Some(DropHighest(1))) - "dl" -> Ok(option.Some(DropLowest(1))) - "r" -> Ok(option.Some(Reroll(1))) - "R" -> Ok(option.Some(RecursiveReroll(1))) - "!" -> Ok(option.Some(Explode(1))) - "m" -> Ok(option.Some(Minimum(1))) - "M" -> Ok(option.Some(Maximum(1))) - "" -> Ok(option.None) - _ -> Error(InvalidModifier) - } -} - pub type Dice { Basic(amount: Int, value: Int, modifiers: List(Modifier)) Fate(amount: Int, modifiers: List(Modifier)) @@ -67,21 +51,24 @@ pub opaque type DiceError { pub fn parse(string string: String) -> Result(Dice, DiceError) { let dice_splitter = splitter.new(["d", "dF"]) case splitter.split(dice_splitter, string) { - #(amount, "", "") -> { + #(amount, "", mods) -> { use amount <- result.try(parse_amount(amount)) - Ok(Basic(amount:, value: amount, modifiers: [])) + use modifiers <- result.try(parse_modifiers(mods)) + Ok(Basic(amount:, value: amount, modifiers:)) } #(amount, "d", "") -> { use amount <- result.try(parse_amount(amount)) Ok(Basic(amount:, value: 6, modifiers: [])) } #("", "d", value_mods) -> { - use #(value, modifiers) <- result.try(parse_value_mods(value_mods)) + use #(value, rest) <- result.try(parse_value(value_mods)) + use modifiers <- result.try(parse_modifiers(rest)) Ok(Basic(1, value:, modifiers:)) } #(amount, "d", value_mods) -> { use amount <- result.try(parse_amount(amount)) - use #(value, modifiers) <- result.try(parse_value_mods(value_mods)) + use #(value, rest) <- result.try(parse_value(value_mods)) + use modifiers <- result.try(parse_modifiers(rest)) Ok(Basic(amount:, value:, modifiers:)) } #(amount, "dF", _mods) -> { @@ -97,21 +84,65 @@ fn parse_amount(amount amount: String) -> Result(Int, DiceError) { |> result.replace_error(InvalidAmount) } -fn parse_value_mods( - value_mods value_mods: String, -) -> Result(#(Int, List(Modifier)), DiceError) { +fn parse_value(value value: String) -> Result(#(Int, String), DiceError) { let modifier_splitter = splitter.new(modifier_strings) - let #(value, modifiers, _) = splitter.split(modifier_splitter, value_mods) + let #(value, a, b) = splitter.split(modifier_splitter, value) use value <- result.try(result.replace_error(int.parse(value), InvalidValue)) - use modifier <- result.try(result.replace_error( - parse_modifier(modifiers), - InvalidModifier, - )) + Ok(#(value, a <> b)) +} - Ok( - #(value, case modifier { - option.None -> [] - option.Some(mod) -> [mod] - }), - ) +fn parse_modifiers(modifiers: String) -> Result(List(Modifier), DiceError) { + do_parse_modifiers(modifiers, Ok([])) +} + +fn do_parse_modifiers( + modifiers: String, + found: Result(List(Modifier), DiceError), +) -> Result(List(Modifier), DiceError) { + use found <- result.try(found) + let splitter = splitter.new(modifier_strings) + + case splitter.split(splitter, modifiers) { + #("", "", "") -> Ok(list.reverse(found)) + #("", mod, rest) -> { + let #(rest, value) = width_modifier_value(rest) + let modifier = case mod { + "kh" -> KeepHighest(value) + "kl" -> KeepLowest(value) + "dh" -> DropHighest(value) + "dl" -> DropLowest(value) + "r" -> Reroll(value) + "R" -> RecursiveReroll(value) + "!" -> Explode(value) + "m" -> Minimum(value) + "M" -> Maximum(value) + _ -> panic as "unreachable" + } + do_parse_modifiers(rest, Ok([modifier, ..found])) + } + _ -> Error(InvalidModifier) + } +} + +fn width_modifier_value(string: String) -> #(String, Int) { + case do_with_modifier_value(string, 0) { + #(a, v) if v == 0 -> #(a, 1) + #(a, v) -> #(a, v) + } +} + +fn do_with_modifier_value(string: String, value: Int) -> #(String, Int) { + case string { + "0" <> rest -> do_with_modifier_value(rest, value * 10) + "1" <> rest -> do_with_modifier_value(rest, value * 10 + 1) + "2" <> rest -> do_with_modifier_value(rest, value * 10 + 2) + "3" <> rest -> do_with_modifier_value(rest, value * 10 + 3) + "4" <> rest -> do_with_modifier_value(rest, value * 10 + 4) + "5" <> rest -> do_with_modifier_value(rest, value * 10 + 5) + "6" <> rest -> do_with_modifier_value(rest, value * 10 + 6) + "7" <> rest -> do_with_modifier_value(rest, value * 10 + 7) + "8" <> rest -> do_with_modifier_value(rest, value * 10 + 8) + "9" <> rest -> do_with_modifier_value(rest, value * 10 + 9) + _ -> #(string, value) + } }