plymio_ast v1.0.0 Plymio.Ast.Utility View Source

Utility Functions for Asts (Quoted Forms)

Documentation Terms

In the documentation there are terms, usually in italics, used to mean the same thing (e.g. form).

form

A form is an ast.

forms

A forms is zero, one or more forms.

form_index

A form_index value is a 2tuple where the first element is a form and the second an index (an integer).

Function Results

Many (new) functions either return {:ok, value} or {:error, error} where error will be as Exception.

The default action for bang functions when fielding an {:error, error} result is to raise the error.

Link to this section Summary

Functions

ast_enumerate/1 takes an ast and, if a :__block__, returns the list of args

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 and works in an equivalen mannner to the latter

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 zero, 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_weights_default/0 returns the default map used to sort asts

form_enumerate/1 takes a form and, if a :__block__, returns {:ok, args} where args are the individual statements in the block

form_enumerate!/1 calls form_enumerate/1 and if the result is {:ok, forms} returns forms

form_index_normalise/2 take a value and an optional default_index and returns either {:ok, {form, index} or {:error, error}

form_index_normalise!/1 calls form_index_normalise/1 and if the result is {:ok, form_index} returns form_index

form_validate/1 calls Macro.validate/1 on the argument (the expected form) and if the result is :ok returns {:ok, form}, else {:error, error}

form_validate!/1 calls form_validate/1 with the argument and if the result is {:ok, form} returns the form

forms_pipe/1 takes a forms and uses Macro.pipe/3 to pipe them together and create a new form

forms_pipe!/1 calls forms_pipe/1 and if the result is {:ok, form} returns form

forms_reduce/1 takes a forms and reduces the forms to a single form using Kernel.SpecialForms.unquote_splicing/1

forms_reduce!/1 calls forms_reduce/1 and if the result is {:ok, forms} returns forms

forms_validate/1 validates the forms using form_validate/1 on each form, returning {:ok, forms} if all are valid, else {:error, error}

Link to this section Types

Link to this type ast_fun_or_acc_fun_tuple() View Source
ast_fun_or_acc_fun_tuple() ::
  atom() |
  (ast() -> ast()) |
  {any(), atom()} |
  {any(), (ast(), any() -> {ast(), any()})}
Link to this type ast_or_ast_acc_tuple() View Source
ast_or_ast_acc_tuple() :: ast() | {ast(), any()}
Link to this type ast_pipe_index() View Source
ast_pipe_index() :: {Macro.t(), integer()}
Link to this type ast_pipe_pure() View Source
ast_pipe_pure() :: Macro.t()
Link to this type asts() View Source
asts() :: ast() | [ast()]
Link to this type asts_pipe() View Source
asts_pipe() :: ast_pipe() | [ast_pipe()]
Link to this type asts_sort_weight_key() View Source
asts_sort_weight_key() :: atom()
Link to this type asts_sort_weight_map() View Source
asts_sort_weight_map() :: %{optional(asts_sort_weight_key()) => any()}
Link to this type error() View Source
error() :: %ArgumentError{__exception__: term(), message: term()}
Link to this type forms() View Source
forms() :: form() | [form()]

Link to this section Functions

Link to this function ast_enumerate(ast) View Source
ast_enumerate(ast()) :: asts()

ast_enumerate/1 takes an ast and, if a :__block__, returns the list of args.

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, []}
...> |> ast_from_mfa
...> |> helper_ast_test_forms_texts
{:ok, ["X1.f1()"]}

iex> {X1, :f1, [1, :two, "tre"]}
...> |> ast_from_mfa
...> |> helper_ast_test_forms_texts
{:ok, ["X1.f1(1, :two, \"tre\")"]}

iex> {X2, :f2, [{1, :two, "tre"}]}
...> |> ast_from_mfa
...> |> helper_ast_test_forms_texts
{:ok, ["X2.f2({1, :two, \"tre\"})"]}

iex> {X2, :f2, [{1, :two, "tre"} |> Macro.escape]}
...> |> ast_from_mfa
...> |> helper_ast_test_forms_texts
{:ok, ["X2.f2({1, :two, \"tre\"})"]}

iex> {X3, :f3, [%{a: 1, b: %{b: 2}, c: {3, :tre, "tre"}}]}
...> |> ast_from_mfa
...> |> helper_ast_test_forms_texts
{:ok, ["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]}
...> |> ast_from_mfa
...> |> helper_ast_test_forms_texts
{:ok, ["X3.f3(%{a: 1, b: %{b: 2}, c: {3, :tre, \"tre\"}})"]}
Link to this function ast_postwalk(ast, value \\ nil) View Source

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> quote do
...>   x = x + 1
...> end
...> |> ast_postwalk(fn
...>      {:x, _, _} -> Macro.var(:a, nil)
...>      # passthru
...>      x -> x
...> end)
...> |> helper_ast_test_forms_result_texts(binding: [a: 42])
{:ok, {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})
...> {:ok, result} = ast |> helper_ast_test_forms_result_texts(binding: [a: 42])
...> result |> Tuple.insert_at(0, acc) # add the accumulator
{7, 1844, ["(a = a + 1\n a = a * a\n a = a - 5)"]}
Link to this function ast_prewalk(ast, value \\ nil) View Source

ast_prewalk/2 takes the same arguments as ast_postwalk/2 and works in an equivalen mannner to the latter.

Link to this function asts_enumerate(asts) View Source
asts_enumerate(asts()) :: asts()

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}
Link to this function asts_group(asts) View Source
asts_group(asts()) :: map()

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.

Link to this function asts_pipe(asts \\ []) View Source
asts_pipe(asts_pipe()) :: ast()

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, after Macro.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> [
 ...>   Macro.var(:x, nil),
 ...>   quote(do: fn x -> x * x end.()),
 ...>   quote(do: List.wrap)
 ...> ]
 ...> |> asts_pipe
 ...> |> helper_ast_test_forms_result_texts(binding: [x: 42])
 {:ok, {[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> [
 ...>   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
 ...> |> helper_ast_test_forms_result_texts(binding: [x: 42])
 {:ok, {%{:p => :this_is_q, "y" => 42},
  ["(fn p, q, x, y -> [{y, x}, {p, q}] end).(:p, :this_is_q, x, \"y\") |> Enum.into(%{})"]}}
Link to this function asts_reduce(asts \\ []) View Source
asts_reduce(asts()) :: ast()

asts_reduce/1 takes zero, 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> quote(do: a = x + y)
...> |> asts_reduce
...> |> helper_ast_test_forms_result_texts(binding: [x: 42, y: 8, c: 5])
{:ok, {50, ["a = x + y"]}}

iex> [quote(do: a = x + y),
...>  quote(do: a * c)]
...> |> asts_reduce
...> |> helper_ast_test_forms_result_texts(binding: [x: 42, y: 8, c: 5])
{:ok, {250, ["(a = x + y\n a * c)"]}}

iex> nil
...> |> asts_reduce
...> |> helper_ast_test_forms_result_texts(binding: [x: 42, y: 8, c: 5])
{:ok, {nil, [""]}}

iex> [
...>   quote(do: a = x + y),
...>   nil,
...>   [
...>    quote(do: b = a / c),
...>    nil,
...>    quote(do: d = b * b),
...>   ],
...>   quote(do: e = a + d),
...> ]
...> |> asts_reduce
...> |> helper_ast_test_forms_result_texts(binding: [x: 42, y: 8, c: 5])
{:ok, {150.0, ["(a = x + y\n b = a / c\n d = b * b\n e = a + d)"]}}
Link to this function asts_sort(asts, weights \\ nil) View Source
asts_sort(asts(), asts_sort_weight_map() | nil) :: asts()

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)"]
Link to this function asts_sort_weight_default() View Source
asts_sort_weight_default() :: any()

asts_sort_weight_default/0 returns the hardcoded “backstop” weight:

Examples

iex> asts_sort_weight_default()
9999
Link to this function asts_sort_weight_get(key, weights \\ %{alias: 2000, def: 5000, default: 9999, defdelegate: 2500, defmacro: 4000, import: 3000, require: 1000, use: 1500}) View Source
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
Link to this function asts_sort_weights_default() View Source
asts_sort_weights_default() :: map()

asts_sort_weights_default/0 returns the default map used to sort asts.

Link to this function form_enumerate(form) View Source
form_enumerate(any()) :: {:ok, forms()} | {:error, error()}

form_enumerate/1 takes a form and, if a :__block__, returns {:ok, args} where args are the individual statements in the block.

Examples

iex> 1 |> form_enumerate
{:ok, [1]}

iex> [1, 2, 3] |> form_enumerate
{:ok, [[1, 2, 3]]}

iex> :two |> form_enumerate
{:ok, [:two]}

iex> quote do
...>   x = 1
...>   y = 2
...>   z = x + y
...> end
...> |> form_enumerate
{:ok, [quote(do: x = 1), quote(do: y = 2), quote(do: z = x + y)]}

iex> %{a: 1} |> form_enumerate
{:error, %ArgumentError{message: "expected a form; got: %{a: 1}"}}
Link to this function form_enumerate!(form) View Source
form_enumerate!(any()) :: forms() | no_return()

form_enumerate!/1 calls form_enumerate/1 and if the result is {:ok, forms} returns forms.

Examples

iex> quote do
...>   x = 1
...>   y = 2
...>   z = x + y
...> end
...> |> form_enumerate!
[quote(do: x = 1), quote(do: y = 2), quote(do: z = x + y)]
Link to this function form_index_normalise(value, default_index \\ 0) View Source
form_index_normalise(any(), any()) ::
  {:ok, {form(), integer()}} |
  {:error, error()}

form_index_normalise/2 take a value and an optional default_index and returns either {:ok, {form, index} or {:error, error}.

If the value is already a valid form_index it is returned unchanged as {:ok, {form, index}.

If the value is a just a form, {:ok, {form, default_index}} is returned. The default default_index is 0 but can be overidden on the call..

Both form and index (or default_index) are validated (using Macro.validate/1, is_integer/1) and if either fails {:error, error} is returned.

Examples

iex> quote(do: a = x + y)
...> |> form_index_normalise
{:ok, {quote(do: a = x + y), 0}}

iex> quote(do: a = x + y)
...> |> form_index_normalise(-1)
{:ok, {quote(do: a = x + y), -1}}

iex> {:error, error} = %{a: 1}
...> |> form_index_normalise
...> match?(%ArgumentError{message: "expected a form; got: %{a: 1}"}, error)
true

iex> {:error, error} = {quote(do: a = x + y), :this_is_not_a_valid_index}
...> |> form_index_normalise
...> match?(%ArgumentError{message: "expected a valid index; got: :this_is_not_a_valid_index"}, error)
true
Link to this function form_index_normalise!(value) View Source
form_index_normalise!(any()) :: {form(), integer()} | no_return()

form_index_normalise!/1 calls form_index_normalise/1 and if the result is {:ok, form_index} returns form_index.

Examples

iex> quote(do: a = x + y)
...> |> form_index_normalise!
{quote(do: a = x + y), 0}
Link to this function form_validate(form) View Source
form_validate(any()) :: {:ok, form()} | {:error, error()}

form_validate/1 calls Macro.validate/1 on the argument (the expected form) and if the result is :ok returns {:ok, form}, else {:error, error}.

Examples

iex> 1 |> form_validate
{:ok, 1}

iex> nil |> form_validate # nil is a valid ast
{:ok, nil}

iex> [:x, :y] |> form_validate
{:ok, [:x, :y]}

iex> ast = {:x, :y} # this 2tuple is a valid ast without escaping
...> {:ok, result} = ast |> form_validate
...> ast |> helper_ast_compare(result)
{:ok, {:x, :y}}

iex> {:error, error} = {:x, :y, :z} |> form_validate
...> match?(%ArgumentError{message: "expected a form; got: {:x, :y, :z}"}, error)
true

iex> {:error, error} = %{a: 1, b: 2, c: 3} |> form_validate # map not a valid ast
...> match?(%ArgumentError{message: "expected a form; got: %{a: 1, b: 2, c: 3}"}, error)
true

iex> ast = %{a: 1, b: 2, c: 3} |> Macro.escape # escaped map is a valid ast
...> {:ok, result} = ast |> form_validate
...> ast |> helper_ast_compare(result)
{:ok,  %{a: 1, b: 2, c: 3} |> Macro.escape}
Link to this function form_validate!(ast) View Source
form_validate!(any()) :: ast() | no_return()

form_validate!/1 calls form_validate/1 with the argument and if the result is {:ok, form} returns the form.

Examples

iex> 1 |> form_validate!
1

iex> nil |> form_validate! # nil is a valid ast
nil

iex> [:x, :y] |> form_validate!
[:x, :y]

iex> ast = {:x, :y} # this 2tuple is a valid ast without escaping
...> result = ast |> form_validate!
...> ast |> helper_ast_compare!(result)
{:x, :y}

iex> {:x, :y, :z} |> form_validate!
** (ArgumentError) expected a form; got: {:x, :y, :z}

iex> %{a: 1, b: 2, c: 3} |> form_validate! # map not a valid ast
** (ArgumentError) expected a form; got: %{a: 1, b: 2, c: 3}

iex> ast = %{a: 1, b: 2, c: 3} |> Macro.escape # escaped map is a valid ast
...> result = ast |> form_validate!
...> ast |> helper_ast_compare!(result)
%{a: 1, b: 2, c: 3} |> Macro.escape
Link to this function forms_pipe(forms \\ []) View Source
forms_pipe(any()) :: {:ok, form()} | {:error, error()}

forms_pipe/1 takes a forms and uses Macro.pipe/3 to pipe them together and create a new form.

It returns {:ok, form} or {:error, error}.

Each form in the forms is passed to an Enum.reduce/3 function, together with the result (form) of all the pipe operations to date (i.e. the accumulator).

The default behaviour is for the latest form to become the zeroth argument in the accumulator form (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 forms in the list can be a form_index. No index (i.e. just the form) implies index 0.

Any nil forms are ignored. An empty list returns {:ok, nil}.

Examples

No or empty arguments:

iex> forms_pipe()
{:ok, nil}
iex> [] |> forms_pipe()
{:ok, nil}

Simple arguments:

iex> 42 |> forms_pipe
{:ok, 42}
iex> :atom |> forms_pipe
{:ok, :atom}

An impossible pipe:

iex> {:ok, ast} = [42, :atom] |> forms_pipe
** (ArgumentError) cannot pipe 42 into :atom, can only pipe into local calls foo(), remote calls Foo.bar() or anonymous functions calls foo.()

This example show what happens when all the forms do not have an explicit index:

iex> {:ok, form} = [
...>   Macro.var(:x, nil),
...>   quote(do: fn x -> x * x end.()),
...>   quote(do: List.wrap)
...> ]
...> |> forms_pipe
...> form |> helper_ast_test_forms_result_texts(binding: [x: 42])
{:ok, {[1764], ["List.wrap((fn x -> x * x end).(x))"]}}

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> {:ok, form} = [
 ...>   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(%{}))
 ...> ]
 ...> |> forms_pipe
 ...> form |> helper_ast_test_forms_result_texts(binding: [x: 42])
 {:ok, {%{:p => :this_is_q, "y" => 42}, ["Enum.into((fn p, q, x, y -> [{y, x}, {p, q}] end).(:p, :this_is_q, x, \"y\"), %{})"]}}
Link to this function forms_pipe!(forms \\ []) View Source
forms_pipe!(any()) :: form() | no_return()

forms_pipe!/1 calls forms_pipe/1 and if the result is {:ok, form} returns form.

Examples

iex> form = [
...>   Macro.var(:x, nil),
...>   quote(do: fn x -> x * x end.()),
...>   quote(do: List.wrap)
...> ]
...> |> forms_pipe!
...> form |> helper_ast_test_forms_result_texts(binding: [x: 42])
{:ok, {[1764], ["List.wrap((fn x -> x * x end).(x))"]}}
Link to this function forms_reduce(asts \\ []) View Source
forms_reduce(any()) :: {:ok, form()} | {:error, error()}

forms_reduce/1 takes a forms and reduces the forms to a single form using Kernel.SpecialForms.unquote_splicing/1.

If the reduction suceeds, {:ok, reduced_form} is returned else {:error, error}.

The list is first flattened and any nils removed before splicing.

An empty list reduces to {:ok, nil}.

Examples

iex> {:ok, reduced_form} = quote(do: a = x + y) |> forms_reduce
...> reduced_form |> helper_ast_test_forms_result_texts(binding: [x: 42, y: 8, c: 5])
{:ok, {50, ["a = x + y"]}}

iex> {:ok, reduced_form} = [
...>  quote(do: a = x + y),
...>  quote(do: a * c)
...> ] |> forms_reduce
...> reduced_form |> helper_ast_test_forms_result_texts(binding: [x: 42, y: 8, c: 5])
{:ok, {250, ["(a = x + y\n a * c)"]}}

iex> nil
...> |> helper_ast_test_forms_result_texts(binding: [x: 42, y: 8, c: 5])
{:ok, {nil, [""]}}

iex> {:ok, form} = [
...>  quote(do: a = x + y),
...>  nil,
...>  [
...>   quote(do: b = a / c),
...>   nil,
...>   quote(do: d = b * b),
...>  ],
...>  quote(do: e = a + d),
...> ] |> forms_reduce
...> form |> helper_ast_test_forms_result_texts(binding: [x: 42, y: 8, c: 5])
{:ok, {150.0, ["(a = x + y\n b = a / c\n d = b * b\n e = a + d)"]}}
Link to this function forms_reduce!(asts \\ []) View Source
forms_reduce!(any()) :: form() | no_return()

forms_reduce!/1 calls forms_reduce/1 and if the result is {:ok, forms} returns forms.

Examples

iex> reduced_form = quote(do: a = x + y) |> forms_reduce!
...> reduced_form |> helper_ast_test_forms_result_texts(binding: [x: 42, y: 8, c: 5])
{:ok, {50, ["a = x + y"]}}

iex> [] |> forms_reduce!
nil

iex> nil |> forms_reduce!
nil
Link to this function forms_validate(forms) View Source
forms_validate(list()) :: {:ok, forms()} | {:error, error()}

forms_validate/1 validates the forms using form_validate/1 on each form, returning {:ok, forms} if all are valid, else {:error, error}.

Examples

iex> [1, 2, 3] |> forms_validate
{:ok, [1, 2, 3]}

iex> [1, {2, 2}, :three] |> forms_validate
{:ok, [1, {2, 2}, :three]}

iex> {:error, error} = [1, {2, 2, 2}, %{c: 3}] |> forms_validate
...> match?(%ArgumentError{message: "expected valid forms; got invalid_indices: [1, 2]"}, error)
true
Link to this function forms_validate!(asts) View Source
forms_validate!(list()) :: forms() | no_return()

forms_validate!/1 validates a forms using forms_validate/1.

If the result is {:ok, forms} returns the forms.

Examples

iex> [1, 2, 3] |> forms_validate!
[1, 2, 3]

iex> [1, {2, 2}, :three] |> forms_validate!
[1, {2, 2}, :three]

iex> [1, {2, 2, 2}, %{c: 3}] |> forms_validate!
** (ArgumentError) expected valid forms; got invalid_indices: [1, 2]