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 Range | Forms Targetted |
|---|---|
| 0 | first form |
| -1 | last form |
| [2, 0] | first and third forms |
| %{1 => :a, 3 => nil} | second and fourth forms |
| nil | all forms |
predicate_function | when 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:
| Field | Purpose |
|---|---|
forms | the 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
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 section Functions
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, []}
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
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"]}
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"]}
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"]}
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
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"]}
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"]}
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)))"]}
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)"]}
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"]}
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"]}
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 Verb | Verb 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
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"]}
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)"]}
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.
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"]}
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"]}
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"]}
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)"]}