Skip to content

Commit

Permalink
Rework protocols
Browse files Browse the repository at this point in the history
  • Loading branch information
christhekeele committed Mar 3, 2023
1 parent 154d056 commit 6bd499e
Show file tree
Hide file tree
Showing 14 changed files with 240 additions and 194 deletions.
6 changes: 5 additions & 1 deletion lib/dice.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ defmodule Dice do
string |> Dice.Expression.new()
end

def roll(%Dice.Expression{} = expression) do
expression |> Dice.Expression.Evaluate.evaluate()
end

def roll(string) when is_binary(string) do
string |> expression |> Dice.Expression.evaluate()
string |> expression |> roll
end
end
12 changes: 8 additions & 4 deletions lib/dice/constant.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@ defmodule Dice.Constant do
{unparsed, [constant], context}
end

def evaluate(%__MODULE__{} = constant) do
constant.value
defimpl Dice.Expression.Evaluate do
def evaluate(%Dice.Constant{} = constant) do
constant.value
end
end

def to_string(%__MODULE__{} = constant) do
constant.value |> Integer.to_string()
defimpl String.Chars do
def to_string(%Dice.Constant{} = constant) do
Kernel.to_string(constant.value)
end
end
end
24 changes: 14 additions & 10 deletions lib/dice/die.ex
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,23 @@ defmodule Dice.Die do
{unparsed, [numbers], context}
end

def roll(%__MODULE__{} = dice) do
case dice.faces do
number when is_integer(number) and number > 0 -> :rand.uniform(number)
%Range{} = range -> Enum.random(range)
faces when is_list(faces) -> Enum.random(faces)
defimpl Dice.Expression.Evaluate do
def evaluate(%Dice.Die{} = die) do
case die.faces do
number when is_integer(number) and number > 0 -> :rand.uniform(number)
%Range{} = range -> Enum.random(range)
faces when is_list(faces) -> Enum.random(faces)
end
end
end

def to_string(%__MODULE__{} = dice) do
case dice.faces do
number when is_integer(number) -> "d#{Integer.to_string(number)}"
%Range{} = range -> "d[#{range.first}..#{range.last}]"
faces when is_list(faces) -> "d{#{Enum.join(faces, ", ")}}"
defimpl String.Chars do
def to_string(%Dice.Die{} = die) do
case die.faces do
number when is_integer(number) -> "d#{number}"
%Range{} = range -> "d[#{range.first}..#{range.last}]"
faces when is_list(faces) -> "d{#{Enum.join(faces, ", ")}}"
end
end
end
end
102 changes: 57 additions & 45 deletions lib/dice/expression.ex
Original file line number Diff line number Diff line change
@@ -1,73 +1,85 @@
defprotocol Dice.Expression.Evaluate do
def evaluate(node)
end

defmodule Dice.Expression.Term do
import Dice.Parser.Builder

defparser do
choice([
parsec({Dice.Pool.Parser, :combinator}),
parsec({Dice.Constant.Parser, :combinator}),
])
end

end

defmodule Dice.Expression.Operation do
import Dice.Parser.Builder

defparser do
choice([
parsec({Dice.Operator.Addition.Parser, :combinator}),
parsec({Dice.Operator.Subtraction.Parser, :combinator}),
])
end

end

defmodule Dice.Expression do
defstruct terms: []
defstruct [:expression]

import Dice.Parser.Builder

defparser do

expression = [
parsec({Dice.Expression.Operation.Parser, :combinator}),
parsec({Dice.Expression.Term.Parser, :combinator}),
]

optional(whitespace_literal())
|> concat(unwrap_and_tag(Dice.Term.Parser.maybe_signed_term_combinator(), :term))
|> concat(
repeat(
concat(
optional(whitespace_literal()),
unwrap_and_tag(Dice.Term.Parser.signed_term_combinator(), :term)
)
)
)
|> unwrap_and_tag(choice(expression), :expression)
|> optional(whitespace_literal())
|> post_traverse({__MODULE__, :from_parse, []})
end

# import NimbleParsec
# defparsec :parse, parsec({__MODULE__.Parser, :parse})

def from_parse(unparsed, parsed, context, _line, _offset) do
terms = :lists.reverse(Keyword.get_values(parsed, :term))
expression = Keyword.fetch!(parsed, :expression)

expression = %__MODULE__{
terms: terms
expression: expression
}

{unparsed, [expression], context}
end

def new(expression) when is_binary(expression) do
expression |> parse
end

def evaluate(%__MODULE__{} = expression) do
expression.terms
|> Enum.map(&Dice.Term.evaluate(&1))
|> Enum.sum()
defimpl Dice.Expression.Evaluate do
def evaluate(%Dice.Expression{} = expression) do
Dice.Expression.Evaluate.evaluate(expression.expression)
end
end

def to_string(%__MODULE__{} = expression) do
expression.terms
|> Enum.map(&Dice.Term.to_string(&1))
|> Enum.join()
|> String.trim_leading("+ ")
|> String.replace_leading("- ", "-")
defimpl String.Chars do
def to_string(%Dice.Expression{} = expression) do
Kernel.to_string(expression.expression)
end
end

defimpl Inspect do
import Inspect.Algebra

def inspect(expression, _opts) do
[first_term | rest] = expression.terms

concat(
List.flatten([
"#{inspect(__MODULE__)}.new(\"",
break(""),
first_term
|> Dice.Term.to_string()
|> String.trim_leading("+ ")
|> String.replace_leading("- ", "-"),
break(),
rest
|> Enum.map(&Dice.Term.to_string(&1))
|> Enum.intersperse(break()),
break(""),
"\")"
])
)
def inspect(%Dice.Expression{} = expression, _opts) do
concat([
"#{inspect(__impl__(:for))}.new(\"",
break(""),
Kernel.to_string(expression.expression),
break(""),
"\")"
])
end
end
end
Empty file removed lib/dice/operators.ex
Empty file.
42 changes: 42 additions & 0 deletions lib/dice/operators/addition.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
defmodule Dice.Operator.Addition do
defstruct [:left, :right]

import Dice.Parser.Builder

defparser do
unwrap_and_tag(parsec({Dice.Expression.Term.Parser, :combinator}), :left)
|> concat(optional(whitespace_literal()))
|> concat(ignore(positive_literal()))
|> concat(optional(whitespace_literal()))
|> unwrap_and_tag(parsec({Dice.Expression.Term.Parser, :combinator}), :right)
|> post_traverse({__MODULE__, :from_parse, []})
end

def from_parse(unparsed, parsed, context, _line, _offset) do
left = Keyword.fetch!(parsed, :left)
right = Keyword.fetch!(parsed, :right)

addition = %__MODULE__{
left: left,
right: right
}

{unparsed, [addition], context}
end

defimpl Dice.Expression.Evaluate do
def evaluate(%Dice.Operator.Addition{} = addition) do
Dice.Expression.Evaluate.evaluate(addition.left) + Dice.Expression.Evaluate.evaluate(addition.right)
end
end

defimpl String.Chars do
def to_string(%Dice.Operator.Addition{} = addition) do
Enum.join([
Kernel.to_string(addition.left),
"+",
Kernel.to_string(addition.right)
], " ")
end
end
end
42 changes: 42 additions & 0 deletions lib/dice/operators/subtract.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
defmodule Dice.Operator.Subtraction do
defstruct [:left, :right]

import Dice.Parser.Builder

defparser do
unwrap_and_tag(parsec({Dice.Expression.Term.Parser, :combinator}), :left)
|> concat(optional(whitespace_literal()))
|> concat(ignore(negative_literal()))
|> concat(optional(whitespace_literal()))
|> unwrap_and_tag(parsec({Dice.Expression.Term.Parser, :combinator}), :right)
|> post_traverse({__MODULE__, :from_parse, []})
end

def from_parse(unparsed, parsed, context, _line, _offset) do
left = Keyword.fetch!(parsed, :left)
right = Keyword.fetch!(parsed, :right)

subtraction = %__MODULE__{
left: left,
right: right
}

{unparsed, [subtraction], context}
end

defimpl Dice.Expression.Evaluate do
def evaluate(%Dice.Operator.Subtraction{} = subtraction) do
Dice.Expression.Evaluate.evaluate(subtraction.left) - Dice.Expression.Evaluate.evaluate(subtraction.right)
end
end

defimpl String.Chars do
def to_string(%Dice.Operator.Subtraction{} = subtraction) do
Enum.join([
Kernel.to_string(subtraction.left),
"-",
Kernel.to_string(subtraction.right)
], " ")
end
end
end
65 changes: 0 additions & 65 deletions lib/dice/parser.ex

This file was deleted.

34 changes: 31 additions & 3 deletions lib/dice/parser/builder.ex
Original file line number Diff line number Diff line change
@@ -1,17 +1,45 @@
defmodule Dice.Parser.Builder do
defmacro defparser(_args \\ [], do: combinator) do
alias URI.Error
defmacro defparser(args \\ [], do: combinator) do
Module.put_attribute(__CALLER__.module, :combinator, combinator)

quote location: :keep do
@behaviour Dice.Parser
variable = args
|> Keyword.get(:variable, __CALLER__.module |> Module.split |> List.last |> String.downcase |> String.to_atom)
|> Macro.var(__CALLER__.module)

quote location: :keep, generated: true do
@before_compile {Dice.Parser.Module, :build_parser_module}

def new(expression) when is_binary(expression) do
expression |> parse |> validate!
end

def parse(input) when is_binary(input) do
{:ok, [result: result], _unparsed, _context, _line, _offset} =
unquote(Module.concat(__CALLER__.module, Parser)).parse(input)

result
end

def validate!(unquote(variable)) do
case validate(unquote(variable)) do
{:ok, unquote(variable)} -> unquote(variable)
{:error, reason} -> raise reason
end
end

def valid?(unquote(variable)) do
case validate(unquote(variable)) do
{:ok, _} -> true
{:error, _reason} -> false
end
end

def validate(unquote(variable)) do
{:ok, unquote(variable)}
end
defoverridable(validate: 1)

end
end
end
Loading

0 comments on commit 6bd499e

Please sign in to comment.