Skip to content

Instantly share code, notes, and snippets.

@josevalim
Last active July 9, 2024 15:01
Show Gist options
  • Select an option

  • Save josevalim/7432084 to your computer and use it in GitHub Desktop.

Select an option

Save josevalim/7432084 to your computer and use it in GitHub Desktop.

Revisions

  1. José Valim revised this gist Jan 20, 2015. 1 changed file with 11 additions and 21 deletions.
    32 changes: 11 additions & 21 deletions rules.exs
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,4 @@
    # Elixir v1.0
    defmodule Rules do
    defmacro __using__(_) do
    quote do
    @@ -13,39 +14,28 @@ defmodule Rules do
    condition = Macro.escape(condition)
    block = Macro.escape(block)
    quote do
    @rules [{ unquote(condition), unquote(block) }|@rules]
    @rules [{unquote(condition), unquote(block)}|@rules]
    end
    end

    defmacro __before_compile__(env) do
    rules = Module.get_attribute(env.module, :rules)
    arg = quote do: arg

    # Compile each rule to a clause in cond.
    # Each clause in a cond:
    # Compile each rule to a ->.
    #
    # left -> right
    #
    # Compiles to an AST node in the format:
    #
    # { [left], meta, right }
    #
    # We will do the same here.
    rules = lc { condition, block } inlist rules do
    # Later we will inject those clauses into a cond.
    rules = Enum.flat_map rules, fn {condition, block} ->
    condition = add_arg(condition, arg)
    { [condition], [], block }
    quote do: (unquote(condition) -> unquote(block))
    end

    # Now we pack all clauses/rules together in
    # a -> node which we will pass to cond.
    arrow = { :->, [], rules }

    quote do
    def run_rules(arg) do
    # Make the argument available in the block as the "number" var.
    # This is usually seen as bad practice but here it goes as an example.
    var!(number) = arg
    cond do: unquote(arrow)
    cond do: unquote(rules)
    end
    end
    end
    @@ -54,12 +44,12 @@ defmodule Rules do
    # implicitly receives an argument. So we go into each node,
    # taking into account "and/or" operators and pipe the arg as
    # the first argument.
    defp add_arg({ op, meta, [left, right] }, arg) when op in [:and, :or] do
    { op, meta, [add_arg(left, arg), add_arg(right, arg)] }
    defp add_arg({op, meta, [left, right]}, arg) when op in [:and, :or] do
    {op, meta, [add_arg(left, arg), add_arg(right, arg)]}
    end

    defp add_arg(other, arg) do
    Macro.pipe(arg, other)
    Macro.pipe(arg, other, 0)
    end
    end

    @@ -92,4 +82,4 @@ end
    Sample.run_rules(1)
    Sample.run_rules(5)
    Sample.run_rules(50)
    Sample.run_rules(150)
    Sample.run_rules(150)
  2. josevalim revised this gist Nov 13, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion rules.exs
    Original file line number Diff line number Diff line change
    @@ -55,7 +55,7 @@ defmodule Rules do
    # taking into account "and/or" operators and pipe the arg as
    # the first argument.
    defp add_arg({ op, meta, [left, right] }, arg) when op in [:and, :or] do
    { :and, meta, [add_arg(left, arg), add_arg(right, arg)] }
    { op, meta, [add_arg(left, arg), add_arg(right, arg)] }
    end

    defp add_arg(other, arg) do
  3. josevalim revised this gist Nov 12, 2013. 1 changed file with 12 additions and 5 deletions.
    17 changes: 12 additions & 5 deletions rules.exs
    Original file line number Diff line number Diff line change
    @@ -22,16 +22,22 @@ defmodule Rules do
    arg = quote do: arg

    # Compile each rule to a clause in cond.
    # Each clause has the ast in the format:
    # Each clause in a cond:
    #
    # { args, meta, block }
    # left -> right
    #
    # Compiles to an AST node in the format:
    #
    # { [left], meta, right }
    #
    # We will do the same here.
    rules = lc { condition, block } inlist rules do
    condition = add_arg(condition, arg)
    { [condition], [], block }
    end

    # The -> in "cond do: (... -> ...)" is represented by this node:
    # Now we pack all clauses/rules together in
    # a -> node which we will pass to cond.
    arrow = { :->, [], rules }

    quote do
    @@ -46,8 +52,9 @@ defmodule Rules do

    # A rule is writen as "foo and bar" but each item in the rule
    # implicitly receives an argument. So we go into each node,
    # taking into account "and" and pipe the arg as the first argument.
    defp add_arg({ :and, meta, [left, right] }, arg) do
    # taking into account "and/or" operators and pipe the arg as
    # the first argument.
    defp add_arg({ op, meta, [left, right] }, arg) when op in [:and, :or] do
    { :and, meta, [add_arg(left, arg), add_arg(right, arg)] }
    end

  4. josevalim revised this gist Nov 12, 2013. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions rules.exs
    Original file line number Diff line number Diff line change
    @@ -7,6 +7,8 @@ defmodule Rules do
    end
    end

    # Store each rules in the @rules attribute so we can
    # compile them all at once later
    defmacro rule(condition, do: block) do
    condition = Macro.escape(condition)
    block = Macro.escape(block)
  5. josevalim created this gist Nov 12, 2013.
    86 changes: 86 additions & 0 deletions rules.exs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,86 @@
    defmodule Rules do
    defmacro __using__(_) do
    quote do
    import unquote(__MODULE__)
    @before_compile unquote(__MODULE__)
    @rules []
    end
    end

    defmacro rule(condition, do: block) do
    condition = Macro.escape(condition)
    block = Macro.escape(block)
    quote do
    @rules [{ unquote(condition), unquote(block) }|@rules]
    end
    end

    defmacro __before_compile__(env) do
    rules = Module.get_attribute(env.module, :rules)
    arg = quote do: arg

    # Compile each rule to a clause in cond.
    # Each clause has the ast in the format:
    #
    # { args, meta, block }
    #
    rules = lc { condition, block } inlist rules do
    condition = add_arg(condition, arg)
    { [condition], [], block }
    end

    # The -> in "cond do: (... -> ...)" is represented by this node:
    arrow = { :->, [], rules }

    quote do
    def run_rules(arg) do
    # Make the argument available in the block as the "number" var.
    # This is usually seen as bad practice but here it goes as an example.
    var!(number) = arg
    cond do: unquote(arrow)
    end
    end
    end

    # A rule is writen as "foo and bar" but each item in the rule
    # implicitly receives an argument. So we go into each node,
    # taking into account "and" and pipe the arg as the first argument.
    defp add_arg({ :and, meta, [left, right] }, arg) do
    { :and, meta, [add_arg(left, arg), add_arg(right, arg)] }
    end

    defp add_arg(other, arg) do
    Macro.pipe(arg, other)
    end
    end

    defmodule Sample do
    use Rules

    rule is_one do
    IO.puts "A"
    end

    rule is_more_than_one and is_less_than_ten do
    IO.puts "B"
    end

    rule is_more_than(10) and is_less_than(99) do
    IO.puts "C"
    end

    rule is_more_than(100) do
    IO.puts "D: #{number}"
    end

    defp is_one(x), do: x == 1
    defp is_more_than_one(x), do: x > 1
    defp is_less_than_ten(x), do: x < 10
    defp is_more_than(x, y), do: x > y
    defp is_less_than(x, y), do: x < y
    end

    Sample.run_rules(1)
    Sample.run_rules(5)
    Sample.run_rules(50)
    Sample.run_rules(150)