plymio_ast_vorm v0.1.0 Plymio.Ast.Vorm View Source

The module provides a toolkit for Managing a Collection of Quoted Forms (Asts).

Forms can be added, deleted, inserted, replaced, transformed, compiled, etc.

Documentation Terms

In the documentation below these terms, usually in italics, are used to mean the same thing (e.g. vorm forms).

form

A valid ast.

forms

One or more (List) of forms.

vorm

An instance of the module’s state (create by e.g. new/1)

vorm forms

The forms held in the vorm.

form_range

The forms targetted by a function (e.g. filter/2).

The form_range is normalised to a predicate function that, when passed a {form, index} tuple, returns either true or false.

Here is a selection of valid form ranges. Note an index is zero-based and negative values are allowed.

Form RangeForms Targetted
0first form
-1last form
[2, 0]first and third forms
%{1 => :a, 3 => nil}second and fourth forms
nilall forms
predicate_functionwhen true

new_forms

new_forms is used to refer to the new forms to add, insert, replace, etc.

form_index_transform

A pipeline of one or more arity one functions

Each function in the pipeline will be passed a {:form,index} tuple and should return either {:ok, form} or {:error, error}. Any other results is converted to {:ok, form}. An error aborts the pipeline. The returned form is used create the {transformed_form,index} tuple to feed the next function in the pipeline.

form_walk_transform

A pipeline of one or more arity one functions

A form_walk_function is an arity one function suitable as the second argument to Macro.postwalk/2 or Macro.prewalk/2.

Each form will be passed through sucessively each function in the form_walk_transform pipeline.

forms transform

A pipeline of one or more arity one functions

Each function in the pipeline will be passed the current forms and should return {:ok, forms} or {:error, error}. Any other results is converted to {:ok, forms}. The returned forms will be used to feed the next function in the pipeline. An error aborts the pipeline.

Function Results

Unless otherwise stated, the result of the function will be either {:ok, vorm} or {:error, error} where error is an Exception.

Vorm Struct

The module’s struct holds its state and has only one field:

FieldPurpose
formsthe forms collection

Link to this section Summary

Functions

add/2 appends new_forms to the vorm forms

empty?/1 takes a vorm and returns true if the vorm forms is empty, else false

fetch/3 take a vorm and form_range and returns the forms in the form_range as {:ok, forms}

filter/2 takes a vorm and form_range and filters only those forms in the form_range

insert/3 take a vorm, form_range and new_forms

length/1 returns the number of vorm forms

new/1 is used to create a new vorm

new!/1 calls new/1 and if the result is {:ok, vorm} returns the vorm. Otherwise the error in {:error, error} is raised

pipe_after/2 take a vorm, form_range and new_forms

pipe_before/2 take a vorm, form_range and new_forms

postwalk/2 take a vorm, form_range and form_walk_transform

prewalk/2 take a vorm, form_range and form_walk_transform

produce_pipeline/2 take a list (often a Keyword) of {verb,args} tuples and an (optional) vorm

put/2 completely replaces the vorm forms with new_forms

reduce/2 take a vorm and a forms transform and passes the vorm forms to the normalised forms transform, expecting {:ok, reduced_forms} as the result

reify_pipeline/2 is a macro that calls produce_pipeline/2 and compiles the forms from the sucess result {:ok, {forms, vorm}}

reject/2 takes a vorm and form_range and deletes all forms in the form_range

replace/3 take a vorm, form_range and new_forms

transform/2 take a vorm, form_range and a form_index_transform

traverse/2 take a vorm, form_range, the initial traverse accummulator, the pre form_traverse_transform and the post form_traverse_transform

Link to this section Types

Link to this type error() View Source
error() :: struct()
Link to this type items() View Source
items() :: list()
Link to this type kv() View Source
kv() :: {any(), any()}
Link to this type t() View Source
t() :: %Plymio.Ast.Vorm{forms: term()}

Link to this section Functions

Link to this function add(vorm, new_forms) View Source
add(t(), any()) :: {:ok, t()} | {:error, error()}

add/2 appends new_forms to the vorm forms.

Examples

iex> {:ok, vorm} = new!() |> add(quote(do: x = x + 1))
...> vorm |> helper_vorm_test_forms!(binding: [x: 7])
{8, ["x = x + 1"]}

iex> {:ok, vorm} = new!() |> add(quote(do: x = x + 1))
...> {:ok, vorm} = vorm |> add([quote(do: x = x * x), quote(do: x = x - 1)])
...> vorm |> helper_vorm_test_forms!(binding: [x: 7])
{63, ["x = x + 1", "x = x * x", "x = x - 1"]}

iex> {:ok, vorm} = new!() |> add([
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...> vorm |> helper_vorm_test_forms!(binding: [x: 3])
{15, ["x = x + 1", "x = x * x", "x = x - 1"]}

iex> {:ok, vorm} = new!() |> add(nil)
...> vorm |> helper_vorm_test_forms!(binding: [x: 7])
{nil, []}

iex> {:ok, vorm} = new!() |> add([])
...> vorm |> helper_vorm_test_forms!(binding: [x: 7])
{nil, []}
Link to this function empty?(vorm) View Source
empty?(t()) :: true | false

empty?/1 takes a vorm and returns true if the vorm forms is empty, else false.

Examples

iex> {:ok, vorm} = new()
...> vorm |> Plymio.Ast.Vorm.empty?
true

iex> {:ok, vorm} = new(forms: [
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...> vorm |> Plymio.Ast.Vorm.empty?
false
Link to this function fetch(vorm, form_range \\ nil) View Source
fetch(t(), any()) :: {:ok, forms()} | {:error, error()}

fetch/3 take a vorm and form_range and returns the forms in the form_range as {:ok, forms}.

The default form_range is nil i.e. return all forms.

Examples

iex> {:ok, vorm} = new(forms: [
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...> {:ok, forms} = vorm |> fetch(1)
...> {:ok, fetch_vorm} = new(forms: forms)
...> fetch_vorm |> helper_vorm_test_forms!(binding: [x: 7])
{49, ["x = x * x"]}

iex> {:ok, vorm} = new(forms: [
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...> {:ok, forms} = vorm |> fetch([0,-1])
...> {:ok, fetch_vorm} = new(forms: forms)
...> fetch_vorm |> helper_vorm_test_forms!(binding: [x: 7])
{7, ["x = x + 1", "x = x - 1"]}

iex> {:ok, vorm} = new(forms: [
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...> {:ok, forms} = vorm |> fetch
...> {:ok, fetch_vorm} = new(forms: forms)
...> fetch_vorm |> helper_vorm_test_forms!(binding: [x: 7])
{63, ["x = x + 1", "x = x * x", "x = x - 1"]}
Link to this function filter(vorm, form_range) View Source
filter(t(), any()) :: {:ok, t()} | {:error, error()}

filter/2 takes a vorm and form_range and filters only those forms in the form_range

Examples

iex> {:ok, vorm} = new(forms: [
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...>  {:ok, vorm} = vorm |> filter([-1,0])
...> vorm |> helper_vorm_test_forms!(binding: [x: 7])
{7, ["x = x + 1", "x = x - 1"]}
Link to this function insert(vorm, form_range, new_forms) View Source
insert(t(), any(), any()) :: {:ok, t()} | {:error, error()}

insert/3 take a vorm, form_range and new_forms.

The new_forms are spliced into the vorm forms at each index in the form_range.

As a special case, if the form_range is :append, the new_forms are appended (i.e. add/2)

Examples

iex> {:ok, vorm} = new(forms: [
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...> {:ok, vorm} = vorm |> insert(2, quote(do: x = x * x * x))
...> vorm |> helper_vorm_test_forms!(binding: [x: 7])
{262143, ["x = x + 1", "x = x * x", "x = x * x * x", "x = x - 1"]}

iex> {:ok, vorm} = new(forms: [
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...> {:ok, vorm} = vorm |> insert(nil, quote(do: x = x + 42))
...> vorm |> helper_vorm_test_forms!(binding: [x: 7])
{8505, ["x = x + 42", "x = x + 1", "x = x + 42", "x = x * x", "x = x + 42", "x = x - 1"]}

iex> {:ok, vorm} = new(forms: [
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...> {:ok, vorm} = vorm |> insert(:append, quote(do: x = x + 42))
...> vorm |> helper_vorm_test_forms!(binding: [x: 7])
{105, ["x = x + 1", "x = x * x", "x = x - 1", "x = x + 42"]}

Inserting into an empty vorm with form_range nil is same as :append

iex> {:ok, vorm} = new()
...> {:ok, vorm} = vorm |> insert(nil, quote(do: x = x * x * x))
...> vorm |> helper_vorm_test_forms!(binding: [x: 3])
{27, ["x = x * x * x"]}
Link to this function length(vorm) View Source
length(t()) :: integer()

length/1 returns the number of vorm forms.

Examples

iex> {:ok, vorm} = new()
...> vorm |> Plymio.Ast.Vorm.length
0

iex> {:ok, vorm} = new(forms: [
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...> vorm |> Plymio.Ast.Vorm.length
3
Link to this function new(opts \\ []) View Source
new(opts()) :: {:ok, t()} | {:error, error()}

new/1 is used to create a new vorm

Examples

iex> {:ok, vorm} = new()
...> vorm |> helper_vorm_test_forms!
{nil, []}

The forms can be populated at the same time:

iex> {:ok, vorm} = new(forms: [
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...> vorm |> helper_vorm_test_forms!(binding: [x: 3])
{15, ["x = x + 1", "x = x * x", "x = x - 1"]}
Link to this function new!(opts \\ []) View Source
new!(opts()) :: t() | no_return()

new!/1 calls new/1 and if the result is {:ok, vorm} returns the vorm. Otherwise the error in {:error, error} is raised.

Examples

iex> vorm = new!()
...> vorm |> helper_vorm_test_forms!
{nil, []}

The forms can be populated at the same time:

iex> vorm = new!(forms: [
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...> vorm |> helper_vorm_test_forms!(binding: [x: 3])
{15, ["x = x + 1", "x = x * x", "x = x - 1"]}
Link to this function pipe_after(vorm, form_range, new_forms) View Source
pipe_after(t(), any(), any()) :: {:ok, t()} | {:error, error()}

pipe_after/2 take a vorm, form_range and new_forms.

Each form in the form_range is piped after the new_forms.

This is the same example as above that pipes a var (x) into a call to an anonymous function that adds 1 but the arguments are switched around:

iex> {:ok, vorm} = new(form: quote(do: fn v -> v + 1 end.()))
...> {:ok, vorm} = vorm |> pipe_after(0, quote(do: x))
...> vorm |> helper_vorm_test_forms!(binding: [x: 42])
{43, ["(fn v -> v + 1 end).(x)"]}

Again, the same example as for pipe_before that supplies a list of asts to the but with the arguments switched around:

iex> after_ast1 = Macro.var(:x, nil)
...> after_ast2 = quote(do: fn v -> v + 1 end.())
...> after_ast3 = quote(do: fn v -> v * v end.())
...> {:ok, vorm} = new(form: quote(do: fn v -> 1 - v end.()))
...> {:ok, vorm} = vorm |> pipe_after(-1, [after_ast1, after_ast2, after_ast3])
...> vorm |> helper_vorm_test_forms!(binding: [x: 42])
{-1848, ["(fn v -> 1 - v end).((fn v -> v * v end).((fn v -> v + 1 end).(x)))"]}
Link to this function pipe_before(vorm, form_range, new_forms) View Source
pipe_before(t(), any(), any()) :: {:ok, t()} | {:error, error()}

pipe_before/2 take a vorm, form_range and new_forms.

Each form in the form_range is piped before the new_forms.

This example pipes a var (x) into a call to an anonymous function that adds 1:

iex> {:ok, vorm} = new(forms: quote(do: x))
...> {:ok, vorm} = vorm |> pipe_before(0, quote(do: fn v -> v + 1 end.()))
...> vorm |> helper_vorm_test_forms!(binding: [x: 42])
{43, ["(fn v -> v + 1 end).(x)"]}

This example selects the last (-) form to be before a list of forms.

iex> before_ast1 = quote(do: fn v -> v + 1 end.())
...> before_ast2 = quote(do: fn v -> v * v end.())
...> before_ast3 = quote(do: fn v -> 1 - v end.())
...> {:ok, vorm} = new(forms: [quote(do: x = x + 5), quote(do: x)])
...> {:ok, vorm} = vorm |> pipe_before(-1, [before_ast1, before_ast2, before_ast3])
...> vorm |> helper_vorm_test_forms!(binding: [x: 37])
{-1848, ["x = x + 5", "(fn v -> 1 - v end).((fn v -> v * v end).((fn v -> v + 1 end).(x)))"]}

Implicit in the examples above has been that the left side form of the pipe (|>) should become the zeroth argument of the right side form. However the call to Macro.pipe/3 that does the piping takes the index (zero offset integer) to use (in the example above it was set automatically to zero).

To specify the pipe index, any of the asts can be a 2tuple where the first element is the “pure” ast and the second the pipe index.

Note the pipe index is unrelated to the form index.

These two simple examples of a subtraction show why the pipe index is important: The first uses the default index of zero (so the code becomes x - 100) while the second supplies an index of 1 (so the code becomes 100 - x).

iex> before_ast = quote(do: Kernel.-(100))
...> {:ok, vorm} = new(form: quote(do: x))
...> {:ok, vorm} = vorm |> pipe_before(-1, before_ast)
...> vorm |> helper_vorm_test_forms!(binding: [x: 42])
{-58, ["Kernel.-(x, 100)"]}

iex> before_ast = {quote(do: Kernel.-(100)), 1}
...> {:ok, vorm} = new(form: quote(do: x))
...> {:ok, vorm} = vorm |> pipe_before(-1, before_ast)
...> vorm |> helper_vorm_test_forms!(binding: [x: 42])
{58, ["Kernel.-(100, x)"]}

This somewhat complicated example demonstrates using multiple asts with indicies:

iex> {:ok, vorm} = new(form: Macro.var(:x, nil))
...> {:ok, vorm} = vorm |> pipe_before(-1, [
...>   {quote(do: Kernel./(42)), 1}, # index is 1
...>    quote(do: List.wrap),
...>   {quote(do: Kernel.++([1,2,3])), 1}, # index is 1
...>   {quote(do: Enum.reduce([&Enum.sum/1, fn v -> v * v end], fn f, s -> f.(s) end)), 1},
...>   ])
...> vorm |> helper_vorm_test_forms!(binding: [x: 7])
{144.0, ["Enum.reduce([&Enum.sum/1, fn v -> v * v end], Kernel.++([1, 2, 3], List.wrap(Kernel./(42, x))), fn f, s -> f.(s) end)"]}
Link to this function postwalk(vorm, form_range, form_postwalk \\ []) View Source
postwalk(any(), any(), any()) :: {:ok, t()} | {:error, error()}

postwalk/2 take a vorm, form_range and form_walk_transform.

The form_walk_transform is applied to each form in the form_range using Macro.postwalk/2.

Examples

iex> {:ok, vorm} = new(form: quote(do: z = x + y))
...> {:ok, vorm} = vorm |> postwalk(0, fn
...>     {:x, _, m} when is_atom(m) -> quote(do: a)
...>     {:y, _, m} when is_atom(m) -> quote(do: b)
...>     x -> x
...>   end)
...> vorm |> helper_vorm_test_forms!(binding: [a: 39, b: 3])
{42, ["z = a + b"]}

Here two transforms are applied:

iex> {:ok, vorm} = new(form: quote(do: z = x + y))
...> {:ok, vorm} = vorm |> postwalk(0, [
...>   fn
...>     {:x, _, m} when is_atom(m) -> quote(do: a)
...>     {:y, _, m} when is_atom(m) -> quote(do: b)
...>     x -> x
...>   end,
...>   fn
...>     {:a, _, m} when is_atom(m) -> quote(do: p)
...>     {:b, _, m} when is_atom(m) -> quote(do: q)
...>     x -> x
...>   end])
...> vorm |> helper_vorm_test_forms!(binding: [p: 20, q: 22])
{42, ["z = p + q"]}
Link to this function prewalk(vorm, form_range, form_prewalk \\ []) View Source
prewalk(any(), any(), any()) :: {:ok, t()} | {:error, error()}

prewalk/2 take a vorm, form_range and form_walk_transform.

The form_walk_transform is applied to each form in the form_range using Macro.prewalk/2.

Examples

iex> {:ok, vorm} = new(form: quote(do: z = x + y))
...> {:ok, vorm} = vorm |> prewalk(0, fn
...>     {:x, _, m} when is_atom(m) -> quote(do: a)
...>     {:y, _, m} when is_atom(m) -> quote(do: b)
...>     x -> x
...>   end)
...> vorm |> helper_vorm_test_forms!(binding: [a: 39, b: 3])
{42, ["z = a + b"]}

Here two transforms are applied:

iex> {:ok, vorm} = new(form: quote(do: z = x + y))
...> {:ok, vorm} = vorm |> prewalk(0, [
...>   fn
...>     {:x, _, m} when is_atom(m) -> quote(do: a)
...>     {:y, _, m} when is_atom(m) -> quote(do: b)
...>     x -> x
...>   end,
...>   fn
...>     {:a, _, m} when is_atom(m) -> quote(do: p)
...>     {:b, _, m} when is_atom(m) -> quote(do: q)
...>     x -> x
...>   end])
...> vorm |> helper_vorm_test_forms!(binding: [p: 20, q: 22])
{42, ["z = p + q"]}
Link to this function produce_pipeline(opts, vorm \\ nil) View Source
produce_pipeline(any(), any()) ::
  {:ok, {forms(), t()}} |
  {:error, error()}

produce_pipeline/2 take a list (often a Keyword) of {verb,args} tuples and an (optional) vorm.

If the vorm is nil, a new, empty one is created.

Each verb must be the name of one of the functions that return {:ok, vorm} on sucess (e.g. add).

Each {verb, args} tuple is applied to the vorm (using Enum.reduce/3).

The result on sucess will be {:ok, {forms, vorm}}

Each verb has aliases:

Canononical VerbVerb Aliases
:add:add_forms, :add_form
:filter:filter_forms, :filter_form
:insert:insert_forms, :insert_form
:pipe_before:pipe_before_forms, :pipe_before_form
:pipe_after:pipe_after_forms, :pipe_after_form
:postwalk:postwalk_forms, :postwalk_form
:prewalk:prewalk_forms, :prewalk_form
:put:put_forms, :put_form
:reject:reject_forms, :reject_form
:reduce:reduce_forms, :reduce_form
:replace:replace_forms, :replace_form
:transform:transform_forms, :transform_form
:traverse:traverse_forms, :traverse_form

Examples

These initial two examples demonstrate the vorm being created automatically if not given as the second argument.

iex> {:ok, {_forms, vorm}} = produce_pipeline(add: [
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...> vorm |> helper_vorm_test_forms!(binding: [x: 7])
{63, ["x = x + 1", "x = x * x", "x = x - 1"]}

iex> {:ok, {_forms, vorm}} = produce_pipeline(
...>  add: quote(do: x = x + 1),
...>  add_forms: [quote(do: x = x * x), quote(do: x = x - 1)])
...> vorm |> helper_vorm_test_forms!(binding: [x: 7])
{63, ["x = x + 1", "x = x * x", "x = x - 1"]}

This example uses an existing vorm

iex> {:ok, vorm} = new(form: quote(do: x = x + 1))
...> {:ok, {_forms, vorm}} = produce_pipeline(
...>  [add_forms: [quote(do: x = x * x), quote(do: x = x - 1)]],
...>  vorm)
...> vorm |> helper_vorm_test_forms!(binding: [x: 7])
{63, ["x = x + 1", "x = x * x", "x = x - 1"]}

Combining different actions (e.g. :add, :insert, etc.

iex> {:ok, {_forms, vorm}} = produce_pipeline(
...>  add_form: quote(do: x = x * x),
...>  insert_form: [0, quote(do: x = x + 1)],
...>  insert: [:append, quote(do: x = x - 1)])
...> vorm |> helper_vorm_test_forms!(binding: [x: 7])
{63, ["x = x + 1", "x = x * x", "x = x - 1"]}

iex> {:ok, {_forms, vorm}} = produce_pipeline(
...>  add: quote(do: x = x * x),
...>  insert: [0, quote(do: x = x + 1)],
...>  insert: [:append, quote(do: x = x - 1)],
...>  postwalk: [nil,
...>    fn
...>     {:x, _, m} when is_atom(m) -> quote(do: y)
...>     x -> x
...>    end])
...> vorm |> helper_vorm_test_forms!(binding: [y: 11])
{143, ["y = y + 1", "y = y * y", "y = y - 1"]}

iex> {:ok, {_forms, vorm}} = produce_pipeline(add: [
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)],
...>   put: quote(do: x = x * x * x))
...> vorm |> helper_vorm_test_forms!(binding: [x: 3])
{27, ["x = x * x * x"]}

Values that are not 2tuples will be intepreted as an add.

iex> {:ok, {_forms, vorm}} = produce_pipeline([
...>   quote(do: x = x + 1),
...>   quote(do: x = x * x),
...>   quote(do: x = x - 1)])
...> vorm |> helper_vorm_test_forms!(binding: [x: 7])
{63, ["x = x + 1", "x = x * x", "x = x - 1"]}

Using mix form and explicit {verb,args} syntax:

iex> {:ok, {_forms, vorm}} = produce_pipeline([
...>  quote(do: x = x + 1),
...>  {:add, [quote(do: x = x * x), quote(do: x = x - 1)]},
...>  {:insert_form, [:append, quote(do: x = x * x * x)]}])
...> vorm |> helper_vorm_test_forms!(binding: [x: 3])
{3375, ["x = x + 1", "x = x * x", "x = x - 1", "x = x * x * x"]}

Using mixed form and Keyword verb: args syntax:

iex> {:ok, {_forms, vorm}} = produce_pipeline([
...>  quote(do: x = x + 1),
...>  add: [quote(do: x = x * x), quote(do: x = x - 1)],
...>  insert: [:append, quote(do: x = x * x * x)]])
...> vorm |> helper_vorm_test_forms!(binding: [x: 3])
{3375, ["x = x + 1", "x = x * x", "x = x - 1", "x = x * x * x"]}

iex> {:ok, {_forms, vorm}} = produce_pipeline([
...>  quote(do: x = x + 1),
...>  [add_forms: [quote(do: x = x * x), quote(do: x = x - 1)]],
...>  [insert: [:append, quote(do: x = x * x * x)]]])
...> vorm |> helper_vorm_test_forms!(binding: [x: 3])
{3375, ["x = x + 1", "x = x * x", "x = x - 1", "x = x * x * x"]}

The verb here (:add_typo) is not known and a KeyError is returned:

iex> {:error, %KeyError{} = error} = produce_pipeline(add_typo: quote(do: x = x + 1))
...> error.key
:add_typo
Link to this function put(vorm, new_forms) View Source
put(t(), any()) :: {:ok, t()} | {:error, error()}

put/2 completely replaces the vorm forms with new_forms

Examples

iex> {:ok, vorm} = new(form: (quote(do: x = x + 1)))
...> {:ok, vorm} = vorm |> put([quote(do: x = x * x), quote(do: x = x - 1)])
...> vorm |> helper_vorm_test_forms!(binding: [x: 7])
{48, ["x = x * x", "x = x - 1"]}

iex> {:ok, vorm} = new!() |> put([
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...> vorm |> helper_vorm_test_forms!(binding: [x: 3])
{15, ["x = x + 1", "x = x * x", "x = x - 1"]}

iex> {:ok, vorm} = new(forms: [
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...>  {:ok, put_vorm} = vorm |> put(quote(do: x = 42))
...> put_vorm |> helper_vorm_test_forms!(binding: [x: 7])
{42, ["x = 42"]}
Link to this function reduce(vorm, form_reduce) View Source
reduce(any(), any()) :: {:ok, t()} | {:error, error()}

reduce/2 take a vorm and a forms transform and passes the vorm forms to the normalised forms transform, expecting {:ok, reduced_forms} as the result.

The reduced_forms are used to update the vorm forms.

Examples

The indentity reduction does nothing:

iex> {:ok, vorm} = new(forms: [
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...>  {:ok, collate_vorm} = vorm |> reduce(&(&1))
...> collate_vorm |> helper_vorm_test_forms!(binding: [x: 7])
{63, ["x = x + 1", "x = x * x", "x = x - 1"]}

This example uses Plymio.Ast.Utility.forms_reduce/1 to create a single form from the vorm forms.

iex> {:ok, vorm} = new(forms: [
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...>  {:ok, collate_vorm} = vorm |> reduce(&Plymio.Ast.Utility.forms_reduce/1)
...> collate_vorm |> helper_vorm_test_forms!(binding: [x: 7])
{63, ["(x = x + 1\n x = x * x\n x = x - 1)"]}
Link to this macro reify_pipeline(pipeline, opts \\ []) View Source (macro)

reify_pipeline/2 is a macro that calls produce_pipeline/2 and compiles the forms from the sucess result {:ok, {forms, vorm}}.

Link to this function reject(vorm, form_range) View Source
reject(t(), any()) :: {:ok, t()} | {:error, error()}

reject/2 takes a vorm and form_range and deletes all forms in the form_range.

Examples

iex> {:ok, vorm} = new(forms: [
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...>  {:ok, vorm} = vorm |> reject([0,2])
...> vorm |> helper_vorm_test_forms!(binding: [x: 7])
{49, ["x = x * x"]}
Link to this function replace(vorm, form_range, new_forms) View Source
replace(t(), any(), any()) :: {:ok, t()} | {:error, error()}

replace/3 take a vorm, form_range and new_forms.

The new_forms are spliced into the vorm forms at each index in the form_range, replacing the existing form.

Examples

iex> {:ok, vorm} = new(forms: [
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...>  {:ok, vorm} = vorm |> replace(1, quote(do: x = x * x * x))
...> vorm |> helper_vorm_test_forms!(binding: [x: 7])
{511, ["x = x + 1", "x = x * x * x", "x = x - 1"]}

iex> {:ok, vorm} = new(forms: [
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...>  {:ok, vorm} = vorm |> replace([0,-1], quote(do: x = x * x * x))
...> vorm |> helper_vorm_test_forms!(binding: [x: 7])
{1628413597910449, ["x = x * x * x", "x = x * x", "x = x * x * x"]}
Link to this function transform(vorm, form_range, form_transform \\ []) View Source
transform(any(), any(), any()) :: {:ok, t()} | {:error, error()}

transform/2 take a vorm, form_range and a form_index_transform.

The normalised form_index_transform is applied to each form in the vorm forms.

Examples

iex> {:ok, vorm} = new(forms: [
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...> {:ok, vorm} = vorm |> transform(
...>   1, fn {_form,_index} -> quote(do: x = x * x * x) end)
...> vorm |> helper_vorm_test_forms!(binding: [x: 3])
{63, ["x = x + 1", "x = x * x * x", "x = x - 1"]}

iex> {:ok, vorm} = new(forms: [
...>   quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...>  {:ok, vorm} = vorm |> transform(
...>    nil, fn {_form,index} -> quote(do: y = y + unquote(index)) end)
...> vorm |> helper_vorm_test_forms!(binding: [y: 5])
{8, ["y = y + 0", "y = y + 1", "y = y + 2"]}
Link to this function traverse(vorm, form_range, acc_traverse, pre_traverse, post_traverse) View Source
traverse(any(), any(), any(), any(), any()) ::
  {:ok, t()} |
  {:error, error()}

traverse/2 take a vorm, form_range, the initial traverse accummulator, the pre form_traverse_transform and the post form_traverse_transform.

Macro.traverse/4 returns {new_form, acc}; new_form is used to replace the original one and acc is ignored.

Examples

This contrived example uses the pre stage to change the x and y vars to a and b, and the post stage to change value1 and value2 to their values in the accumulator (a lookup dictionary).

iex> {:ok, vorm} = new(form: quote do
...>   x = value1
...>   y = value2
...>   z = x + y
...> end)
...> {:ok, vorm} = vorm |> traverse(-1,
...>   # the accumulator is a lookup dictionary
...>   %{value1: 39, value2: 3},
...>   # pre function
...>   fn
...>     # x to a
...>     {:x, _, _}, acc -> {Macro.var(:a,nil), acc}
...>     # y to b
...>     {:y, _, _}, acc -> {Macro.var(:b,nil), acc}
...>     # passthru
...>     ast, acc -> {ast, acc}
...>   end,
...>   # post function
...>   fn
...>     # is the var (form) in the acc lookup table? if so replace with value
...>     {form, _, mod} = ast, acc when is_atom(form) and is_atom(mod) ->
...>        case acc |> Map.has_key?(form) do
...>          true -> {Map.get(acc,form), acc}
...>          _ -> {ast, acc}
...>        end
...>     # passthru
...>     ast, acc -> {ast, acc}
...>   end)
...> vorm |> helper_vorm_test_forms!(binding: [])
{42, ["(a = 39\n b = 3\n z = a + b)"]}