- install with the bash script
- cd into the directory
iex -S mix
MyMacroTest.example_one()
Last active
August 20, 2019 06:44
-
-
Save neenjaw/6715205dd10e1f9bd3c5f50a391981ca to your computer and use it in GitHub Desktop.
Elixir macro to create a pattern matcher of an AST
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
mix new my_macro_test | |
rm ./my_macro_test/lib/my_macro_test.ex | |
cp ./*.ex ./my_macro_test/lib |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule MyMacroTest do | |
@moduledoc """ | |
Documentation for MacroTest. | |
""" | |
use ModuleA | |
@doc false | |
example "one" do | |
code left _ignore + 2 | |
code right 1 + 2 | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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