Packages

Add a dice-rolling DSL with sigil support and reusable compiled functions to your application.

Current section

Files

Jump to
ex_dice_roller lib compilers variable.ex
Raw

lib/compilers/variable.ex

defmodule ExDiceRoller.Compilers.Variable do
@moduledoc """
Handles compiling expressions that use variables.
Variables can be used to replace single letter characters in an expression
with a value, such as a number or an anonymous function that accepts list
arguments (`args` and `opts`, respectively).
Acceptable variable values include:
* integers
* floats
* compiled functions matching `t:Compiler.compiled_fun/2`
* strings that can be parsed by ExDiceRoller
* lists composed of any of the above
* lists of lists
Note that an error will be raised if values are not supplied for all varaibles
in an expression.
### Examples
iex> import ExDiceRoller.Sigil
ExDiceRoller.Sigil
iex> ExDiceRoller.roll(~a/1+x/, [x: 5])
6
iex> ExDiceRoller.roll("xdy+z", x: 5, y: 10, z: 50)
82
iex> ExDiceRoller.roll("xdy+z", [x: 5, y: 10, z: ~a/15d100/])
739
iex> ExDiceRoller.roll("xdy+z", x: [1, 2, 3], y: 1, z: 5, opts: [:keep])
[6, 6, 6, 6, 6, 6]
iex> ExDiceRoller.roll("xdy+z", [x: 1, y: [1, 10, 100], z: -6, opts: [:keep]])
[-5, 0, 68]
iex> ExDiceRoller.roll("xdy+z", x: [~a/1d2/, "1d4+1"], y: ["3,4d20/2", ~a/1d6/], z: 2, opts: [:keep])
[3, 4, 5, 5, 3, 7, 4, 5, 4, 3, 5, 4, 3, 4, 7, 3, 5, 4, 4, 5]
iex> ExDiceRoller.roll("1+x")
** (ArgumentError) no variable 'x' was found in the arguments
"""
@behaviour ExDiceRoller.Compiler
alias ExDiceRoller.{Args, Compiler, Tokenizer, Parser}
@impl true
def compile({:var, _} = var), do: compile_var(var)
@spec compile_var({:var, charlist}) :: Compiler.compiled_fun()
defp compile_var({:var, var}), do: fn args -> var_final(var, args) end
@spec var_final(charlist, Keyword.t()) :: number
defp var_final(var, args) do
key = var |> to_string() |> String.to_atom()
args
|> Args.get_var(key)
|> var_final_arg(var, args)
end
@spec var_final_arg(any, charlist, Keyword.t()) :: number
defp var_final_arg(nil, var, _),
do: raise(ArgumentError, "no variable #{inspect(var)} was found in the arguments")
defp var_final_arg(val, _, _) when is_number(val), do: val
defp var_final_arg(val, _, args) when is_function(val), do: val.(args)
defp var_final_arg(val, var, args) when is_bitstring(val) do
{:ok, tokens} = Tokenizer.tokenize(val)
{:ok, parsed} = Parser.parse(tokens)
compiled_arg = Compiler.delegate(parsed)
var_final_arg(compiled_arg, var, args)
end
defp var_final_arg(val, var, args) when is_list(val) do
Enum.map(val, &var_final_arg(&1, var, args))
end
end