plymio_ast v0.2.0 Plymio.Ast.Utils
Utility Functions for Asts (Quoted Forms)
Summary
Functions
ast_enumerate/1
takes an ast and, if a :__block__
, “demultiplexes” the list of constituent asts, and returns the list
ast_from_mfa/1
creates an ast to implement the function call defined in an MFA 3tuple ({module,function,arguments}
)
ast_postwalk/2
runs Macro.postwalk/2
or Macro.postwalk/3
depending
on whether the 2nd argument is a either function of arity one, or a
2tuple where the first element is the accumulator and the second a
function of arity two
ast_prewalk/2
takes the same arguments as ast_postwalk/2
ast_validate/1
runs Macro.validate/1
on the argument and if the
result is not :ok
raises an ArgumentError
exception
asts_enumerate/1
takes zero (nil), one or more asts, passes each ast to ast_enumerate/1
, and “flat_maps” the results
asts_group/
take one or more asts and returns a Enum.group_by/1
map using the first element of the tuple as the key
asts_pipe/1
takes one or more asts and uses Macro.pipe/3
to pipe them together and create a new ast
asts_reduce/1
takes one or more asts and reduces them to a single
ast using Kernel.SpecialForms.unquote_splicing/1
asts_sort/2
take one or more asts together with an (optional)
weight map and returns a list of asts with the lower weight ast
earlier in the list
asts_sort_weight_default/0
returns the hardcoded “backstop” weight
asts_sort_weight_get/2
takes an ast “key” (the first element), and
an (optional) weight map, and returns the weight
asts_sort_weights_default/0
returns the default map used to sort asts
asts_validate!/1
validates a list of asts using ast_validate!/1
, returns the asts or
raises an ArgumentError
exception on the first invalid one
Types
ast_fun_or_acc_fun_tuple :: atom | (ast -> ast) | {any, atom} | {any, (ast, any -> {ast, any})}
Functions
ast_enumerate/1
takes an ast and, if a :__block__
, “demultiplexes” the list of constituent asts, and returns the list.
If not a :__block__
, the ast is returned in a list.
Examples
iex> 1 |> ast_enumerate
[1]
iex> :two |> ast_enumerate
[:two]
iex> quote do
...> x = 1
...> y = 2
...> z = x + y
...> end
...> |> ast_enumerate
[quote(do: x = 1), quote(do: y = 2), quote(do: z = x + y)]
iex> %{a: 1} |> ast_enumerate
** (ArgumentError) expected an ast; got: %{a: 1}
ast_from_mfa/1
creates an ast to implement the function call defined in an MFA 3tuple ({module,function,arguments}
).
Each argument is escaped if necessary (i.e. if not already a valid ast).
It can be thought of as the static / ast “equivalent” of Kernel.apply/1
.
Examples
iex> {X1, :f1, []} |> helper_ast_from_mfa_to_string
"X1.f1()"
iex> {X1, :f1, [1, :two, "tre"]} |> helper_ast_from_mfa_to_string
"X1.f1(1, :two, \"tre\")"
iex> {X2, :f2, [{1, :two, "tre"}]} |> helper_ast_from_mfa_to_string
"X2.f2({1, :two, \"tre\"})"
iex> {X2, :f2, [{1, :two, "tre"} |> Macro.escape]} |> helper_ast_from_mfa_to_string
"X2.f2({1, :two, \"tre\"})"
iex> {X3, :f3, [%{a: 1, b: %{b: 2}, c: {3, :tre, "tre"}}]} |> helper_ast_from_mfa_to_string
"X3.f3(%{a: 1, b: %{b: 2}, c: {3, :tre, \"tre\"}})"
iex> {X3, :f3, [%{a: 1, b: %{b: 2}, c: {3, :tre, "tre"}} |> Macro.escape]} |> helper_ast_from_mfa_to_string
"X3.f3(%{a: 1, b: %{b: 2}, c: {3, :tre, \"tre\"}})"
ast_postwalk/2
runs Macro.postwalk/2
or Macro.postwalk/3
depending
on whether the 2nd argument is a either function of arity one, or a
2tuple where the first element is the accumulator and the second a
function of arity two.
If the second argument is nil, the call to Macro.postwalk
is
prempted and the ast returned unchanged.
Examples
This examples changes occurences of the x
var to the a
var.
iex> ast = quote do
...> x = x + 1
...> end
...> |> ast_postwalk(fn
...> {:x, _, _} -> Macro.var(:a, nil)
...> # passthru
...> x -> x
...> end)
...> [a: 42] # binding
...> |> helper_ast_eval(ast)
{43, "a = a + 1"}
This example changes x
to a
and uses an accumulator to count the occurences of the a
var:
iex> {ast, acc} = quote do
...> x = x + 1
...> x = x * x
...> x = x - 5
...> end
...> |> ast_postwalk(
...> {0, fn
...> {:x, _, _}, acc -> {Macro.var(:a, nil), acc + 1}
...> # passthru
...> x,s -> {x,s}
...> end})
...> [a: 42] # binding
...> |> helper_ast_eval(ast)
...> |> Tuple.insert_at(0, acc) # add the accumulator
{7, 1844, "(\n a = a + 1\n a = a * a\n a = a - 5\n)"}
ast_prewalk/2
takes the same arguments as ast_postwalk/2
.
ast_validate/1
runs Macro.validate/1
on the argument and if the
result is not :ok
raises an ArgumentError
exception.
Examples
iex> 1 |> ast_validate!
1
iex> nil |> ast_validate! # nil is a valid ast
nil
iex> [:x, :y] |> ast_validate!
[:x, :y]
iex> ast = {:x, :y} # this 2tuple is a valid ast without escaping
...> result = ast |> ast_validate!
...> match?(^result, ast)
true
iex> {:x, :y, :z} |> ast_validate!
** (ArgumentError) expected an ast, got: {:error, {:x, :y, :z}}
iex> %{a: 1, b: 2, c: 3} |> ast_validate! # map not a valid ast
** (ArgumentError) expected an ast, got: {:error, %{a: 1, b: 2, c: 3}}
iex> ast = %{a: 1, b: 2, c: 3} |> Macro.escape # escaped map is a valid ast
...> result = ast |> ast_validate!
...> match?(^result, ast)
true
asts_enumerate/1
takes zero (nil), one or more asts, passes each ast to ast_enumerate/1
, and “flat_maps” the results.
Examples
iex> nil |> asts_enumerate
[]
iex> 1 |> asts_enumerate
[1]
iex> :two |> asts_enumerate
[:two]
iex> [1, nil, :two, nil, "tre"] |> asts_enumerate
[1, :two, "tre"]
iex> quote do
...> x = 1
...> y = 2
...> z = x + y
...> end
...> |> asts_enumerate
[quote(do: x = 1), quote(do: y = 2), quote(do: z = x + y)]
iex> [quote do
...> x = 1
...> y = 2
...> z = x + y
...> end,
...> nil,
...> quote(do: a = 42),
...> nil,
...> quote do
...> b = 7
...> c = a - b
...> end]
...> |> asts_enumerate
[quote(do: x = 1), quote(do: y = 2), quote(do: z = x + y),
quote(do: a = 42), quote(do: b = 7), quote(do: c = a - b)]
iex> %{a: 1} |> asts_enumerate
** (ArgumentError) expected an ast; got: %{a: 1}
asts_group/
take one or more asts and returns a Enum.group_by/1
map using the first element of the tuple as the key.
asts_pipe/1
takes one or more asts and uses Macro.pipe/3
to pipe them together and create a new ast.
Each ast in the list is passed to an Enum.reduce/3
function, together with the result (ast) of all the pipe operations to date (i.e. the accumulator).
The default behaviour is for the latest ast to become the zeroth
argument in the accumulator ast (i.e. just as the left hand side
of |>
becomes the zeroth argument of the right hand
side)
However the call to Macro.pipe/3
that does the piping takes the
zero-offset index.
To specify the pipe index, any of the asts in the list can be a 2tuple where the first element is the “pure” ast and the second the pipe index. No index (i.e. just the “pure” ast) implies index 0.
When the index is zero, a
left |> right
ast is generated, otherwise the generated ast inserts the latest ast directly into the auumulator ast at the index. This is just to make the code, afterMacro.to_string/1
, visually more obvious.
Any nil
asts in the list are ignored. An empty list returns nil
.
Examples
This example show what happens when all the asts do not have an explicit index:
iex> ast = [
...> Macro.var(:x, nil),
...> quote(do: fn x -> x * x end.()),
...> quote(do: List.wrap)
...> ]
...> |> asts_pipe
...> [x: 42] |> helper_ast_eval(ast)
{[1764], "x |> (fn x -> x * x end).() |> List.wrap()"}
This example show what happens when an index of 2 is used to insert
the value of x
(42) as the 3rd argument in the call to a “partial” anonymous
function which already has the 1st, 2nd and 4th arguments.
iex> ast = [
...> Macro.var(:x, nil),
...> {quote(do: fn p, q, x, y -> [{y, x}, {p,q}] end.(:p, :this_is_q, "y")), 2},
...> quote(do: Enum.into(%{}))
...> ]
...> |> asts_pipe
...> [x: 42] |> helper_ast_eval(ast)
{%{:p => :this_is_q, "y" => 42},
"(fn p, q, x, y -> [{y, x}, {p, q}] end).(:p, :this_is_q, x, \"y\") |> Enum.into(%{})"}
asts_reduce/1
takes one or more asts and reduces them to a single
ast using Kernel.SpecialForms.unquote_splicing/1
.
The list is first flattened and any nils
removed before splicing.
An empty list reduces to nil.
Examples
iex> ast = quote(do: a = x + y)
...> [x: 42, y: 8, c: 5] |> helper_asts_reduce_eval(ast)
{50, "a = x + y"}
iex> ast = [quote(do: a = x + y),
...> quote(do: a * c)]
...> [x: 42, y: 8, c: 5] |> helper_asts_reduce_eval(ast)
{250, "(\n a = x + y\n a * c\n)"}
iex> ast = nil
...> [x: 42, y: 8, c: 5] |> helper_asts_reduce_eval(ast)
{nil, ""}
iex> ast = [
...> quote(do: a = x + y),
...> nil,
...> [
...> quote(do: b = a / c),
...> nil,
...> quote(do: d = b * b),
...> ],
...> quote(do: e = a + d),
...> ]
...> [x: 42, y: 8, c: 5] |>helper_asts_reduce_eval(ast)
{150.0, "(\n a = x + y\n b = a / c\n d = b * b\n e = a + d\n)"}
asts_sort/2
take one or more asts together with an (optional)
weight map and returns a list of asts with the lower weight ast
earlier in the list.
Examples
iex> quote(do: use X1) |> helper_asts_sort_to_string
["use(X1)"]
iex> nil |> helper_asts_sort_to_string
[]
iex> [
...> quote(do: use X1),
...> quote(do: require X2),
...> quote(do: import X3),
...> ] |> helper_asts_sort_to_string
["require(X2)", "use(X1)", "import(X3)"]
iex> [
...> quote(do: use X1),
...> quote(do: require X2),
...> quote(do: import X3),
...> ] |> helper_asts_sort_to_string(%{import: 1, use: 3})
["import(X3)", "use(X1)", "require(X2)"]
iex> [
...> quote(do: use X1),
...> quote(do: require X2),
...> quote(do: import X3),
...> ] |> helper_asts_sort_to_string(%{import: 1, use: 3, default: 2})
["import(X3)", "require(X2)", "use(X1)"]
asts_sort_weight_default/0
returns the hardcoded “backstop” weight:
Examples
iex> asts_sort_weight_default()
9999
asts_sort_weight_get(asts_sort_weight_key, asts_sort_weight_map) :: any
asts_sort_weight_get/2
takes an ast “key” (the first element), and
an (optional) weight map, and returns the weight.
If no weight map is supplied, the default map is used.
If the key does not exist in the weight map, the :default
key is
tried, else the “backstop” weight (asts_sort_weight_default/0
) returned.
Examples
iex> :use |> asts_sort_weight_get
1500
iex> :require |> asts_sort_weight_get
1000
iex> :use |> asts_sort_weight_get(%{use: 42, require: 43, import: 44})
42
iex> :unknown |> asts_sort_weight_get(%{use: 42, require: 43, import: 44})
9999
iex> :unknown |> asts_sort_weight_get(%{use: 42, require: 43, import: 44, default: 99})
99
asts_sort_weights_default/0
returns the default map used to sort asts.
asts_validate!/1
validates a list of asts using ast_validate!/1
, returns the asts or
raises an ArgumentError
exception on the first invalid one.
Examples
iex> [1, 2, 3] |> asts_validate!
[1, 2, 3]
iex> [1, {2, 2}, :three] |> asts_validate!
[1, {2, 2}, :three]
iex> [1, {2, 2, 2}, %{c: 3}] |> asts_validate!
** (ArgumentError) expected an ast, got: {:error, {2, 2, 2}}