Skip to content

Instantly share code, notes, and snippets.

@neenjaw
Last active August 20, 2019 06:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save neenjaw/6715205dd10e1f9bd3c5f50a391981ca to your computer and use it in GitHub Desktop.
Save neenjaw/6715205dd10e1f9bd3c5f50a391981ca to your computer and use it in GitHub Desktop.
Elixir macro to create a pattern matcher of an AST
  1. install with the bash script
  2. cd into the directory
  3. iex -S mix
  4. MyMacroTest.example_one()
#!/bin/bash
mix new my_macro_test
rm ./my_macro_test/lib/my_macro_test.ex
cp ./*.ex ./my_macro_test/lib
defmodule MyMacroTest do
@moduledoc """
Documentation for MacroTest.
"""
use ModuleA
@doc false
example "one" do
code left _ignore + 2
code right 1 + 2
end
end
defmodule ModuleA do
@moduledoc false
@doc false
defmacro __using__(_opts) do
quote do
import unquote(__MODULE__)
end
end
defmacro example(description, do: block) do
function_name = String.to_atom("example_"<>description)
%{left: left, right: right} =
block
# find the code blocks and put them in a map
|> Macro.prewalk(%{}, fn
{:code, _, [{name, _, block}]} = node, acc -> {node, Map.put(acc, name, block)}
node, acc -> {node, acc}
end)
# return only the map
|> elem(1)
string_left =
# Take the block and remove the metadata
Macro.prewalk(left, fn
{name, _, param} -> {name, :_ignore, param}
node -> node
end)
# Look for :_ignore, replace the node with a string
|> Macro.prewalk(fn
{atom, meta, param} = node ->
cond do
atom == :_ignore -> "_"
meta == :_ignore -> {atom, "_", param}
true -> node
end
node -> node
end)
# Turn the AST into a string
|> inspect()
# Replace double-quoted ""_"" with "_"
|> String.replace("\"_\"", "_")
# turn the right side into a string
string_right = inspect(right)
# concat the string with "=" to make a match
string_match = string_left <> " = " <> string_right
# re-create the AST, but now with a legal _ and =
match_ast = Code.string_to_quoted(string_match) |> elem(1)
quote do
def unquote(function_name)() do
result = """
This is the left side: `#{unquote(string_left)}`
This is the right side: `#{unquote(string_right)}`
This is the code to be evaluated: `#{unquote(string_match)}`
If the match succeed then the result should be equal to the right side
result: `#{inspect(unquote(match_ast))}`
right: `#{unquote(string_right)}`
"""
IO.puts(result)
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment