defmodule Perch do @moduledoc """ Documentation for `Perch`. """ defstruct tables: %{}, output: nil def parse(str) when is_binary(str) do parsed = str |> extract_groups() |> Enum.reduce(%__MODULE__{}, &do_parse_thing/2) {:ok, parsed} end def generate_output(perch) do perch |> replace_roll_macros() |> replace_inline_macros() |> replace_inline_ranges() end def roll(%__MODULE__{} = perch, tab) do perch |> Map.get(:tables) |> Map.get(tab) |> Enum.random() end ### Private defp replace_roll_macros(perch) do Regex.replace(~r/\[(\w*)\]/, perch.output, fn _, match -> match |> String.to_atom() |> then(& Perch.roll(perch, &1)) end) end defp replace_inline_macros(output) do Regex.replace(~r/\{(.*\|.*)\}/, output, fn _full_match, match -> match |> String.split("|") |> Enum.random() end) end defp replace_inline_ranges(output) do regex = ~r/\{(\d*)-(\d*)\}/ Regex.replace(regex, output, fn _match, a, b -> Enum.random(String.to_integer(a)..String.to_integer(b)) |> Integer.to_string() end) end defp extract_groups(tab_string) do tab_string |> String.split("\n", trim: true) |> Enum.chunk_by(& String.starts_with?(&1, " ")) |> Enum.chunk_every(2) end # handles the special "output" group defp do_parse_thing([["output"], [output]], acc) do %{acc | output: String.trim(output)} end # handles a table defp do_parse_thing([[a], b], acc) do new_key = String.to_atom(a) new_tab = Enum.map(b, &String.trim/1) %{acc | tables: Map.put(acc.tables, new_key, new_tab)} end end