Current section
Files
Jump to
Current section
Files
lib/ast/vorm/field.ex
defmodule Plymio.Ast.Vorm.Field do
@moduledoc ~S"""
Convenience wrappers for using a *vorm* held in a *struct*.
Most of these wrappers assumes a *vorm* is, or will be, held in the `:vorm` field of a *struct* and allow function such as `add` to be called where the first argument is the struct. For example `struct_vorm_add` looks like:
def struct_vorm_add(struct, new_forms) do
with {:ok, %{vorm: %Plymio.Ast.Vorm{} = vorm} = struct <- struct |> struct_vorm_ensure,
{:ok, %Plymio.Ast.Vorm{} = vorm} <- vorm |> Plymio.Ast.Vorm.add(new_forms) do
{:ok, struct |> struct!(vorm: vorm)
else
{:error, _} = result -> result
end
end
> Since most of the wrappers work in the same way, only `struct_vorm_add` will be documented below with illustrative examples.
## Documentation Terms
In the documentation below these terms, usually in *italics*, are used to mean the same thing (e.g. *state*).
### *vorm*
An instance of `Plymio.Ast.Vorm` (*%Plymio.Ast.Vorm{}*).
### *vorm field*
The field called `:vorm` in a struct intended to hold a *vorm*.
### *state*
A instance of a struct with a *vorm field*.
"""
alias Plymio.Ast.Vorm, as: PAV
alias Plymio.Ast.Vorm.Utility, as: PAVU
import Plymio.Ast.Vorm.Utility, only: [
new_error_result: 1,
]
use Plymio.Ast.Vorm.Attribute
@type t :: struct
@type vorm :: %Plymio.Ast.Vorm{}
@type error :: struct
@type form :: Macro.t
@type forms :: [form]
@doc ~S"""
`struct_vorm_ensure/1` take a *state* and checks if the *vorm field* already holds a *vorm*.
If it has `{:ok, state}` is returned.
If the *vorm field* is `nil`, a new, empty *vorm* is created, stored in the *vorm field*, and `{:ok, state}` returned.
Any other value in the *vorm field* will cause an error.
## Examples
iex> t = %PlymioAstVormFieldTest{}
...> match?(nil, t.vorm)
true
iex> {:ok, t} = %PlymioAstVormFieldTest{}
...> |> struct_vorm_ensure
...> match?(%Plymio.Ast.Vorm{}, t.vorm)
true
iex> {:ok, vorm} = Plymio.Ast.Vorm.new(form: quote(do: x = x + 1))
...> {:ok, t} = %PlymioAstVormFieldTest{vorm: vorm}
...> |> struct_vorm_ensure
...> t.vorm |> helper_vorm_test_forms!(binding: [x: 3])
{4, ["x = x + 1"]}
iex> %PlymioAstVormFieldTest{vorm: :not_a_vorm}
...> |> struct_vorm_ensure
{:error, %Plymio.Ast.Vorm.Error{error: nil, message: "vorm field invalid",
value: %PlymioAstVormFieldTest{vorm: :not_a_vorm}}}
"""
@spec struct_vorm_ensure(t) :: {:ok, t} | {:error, error}
def struct_vorm_ensure(t)
def struct_vorm_ensure(%{@pav_key_struct_id => _, @pav_key_vorm => %PAV{}} = state) do
{:ok, state}
end
def struct_vorm_ensure(%{@pav_key_struct_id => _, @pav_key_vorm => form} = state)
when @pav_value_vorm_initial_value == form do
with {:ok, form_state} <- PAV.new() do
{:ok, state |> struct!([{@pav_key_vorm, form_state}])}
else
{:error, _} = result -> result
end
end
def struct_vorm_ensure(state) do
new_error_result(m: "vorm field invalid", v: state)
end
@doc ~S"""
`struct_vorm_update/2` take a *state* and a second argument.
If the second argument is a *vorm*, the *state* is updated with it and `{:ok, state}` returned.
Otherwise the second argument is passed to `new/1` and the new *vorm* stored in the *state*.
## Examples
iex> {:ok, vorm} = Plymio.Ast.Vorm.new(form: quote(do: x = x + 1))
...> t = %PlymioAstVormFieldTest{}
...> {:ok, t} = t |> struct_vorm_update(vorm)
...> t.vorm |> helper_vorm_test_forms!(binding: [x: 3])
{4, ["x = x + 1"]}
iex> t = %PlymioAstVormFieldTest{}
...> {:ok, t} = t |> struct_vorm_update(forms: quote(do: x = x + 1))
...> t.vorm |> helper_vorm_test_forms!(binding: [x: 3])
{4, ["x = x + 1"]}
iex> t = %PlymioAstVormFieldTest{}
...> t |> struct_vorm_update(:not_new_opts)
{:error, %Plymio.Ast.Vorm.Error{error: nil, message: "new vorm opts invalid", value: :not_new_opts}}
"""
@spec struct_vorm_update(t, any) :: {:ok, t} | {:error, error}
def struct_vorm_update(t, new_forms)
def struct_vorm_update(%{@pav_key_struct_id => _} = state, %PAV{} = vorm) do
{:ok, state |> struct!([{@pav_key_vorm, vorm}])}
end
def struct_vorm_update(%{@pav_key_struct_id => _} = state, opts) do
with true <- opts |> Keyword.keyword? do
with {:ok, %PAV{} = vorm} <- PAV.new(opts) do
{:ok, state |> struct!([{@pav_key_vorm, vorm}])}
else
{:error, _} = result -> result
end
else
false -> new_error_result(m: "new vorm opts invalid", v: opts)
end
end
@doc ~S"""
`struct_forms_update/2` take a *state* and a second argument.
If the second argument is a *vorm*, the *state* is updated with the *vorm's* *forms* and `{:ok, state}` returned.
Otherwise the second argument is assumed to be one or more forms and the *state* is updated with them, returning `{:ok, state}`.
## Examples
iex> {:ok, vorm} = Plymio.Ast.Vorm.new(form: quote(do: x = x + 1))
...> t = %PlymioAstVormFieldTest{}
...> {:ok, t} = t |> struct_forms_update(vorm)
...> t.vorm |> helper_vorm_test_forms!(binding: [x: 3])
{4, ["x = x + 1"]}
iex> t = %PlymioAstVormFieldTest{}
...> {:ok, t} = t |> struct_forms_update(forms: quote(do: x = x + 1))
...> t.vorm |> helper_vorm_test_forms!(binding: [x: 3])
{4, ["x = x + 1"]}
iex> t = %PlymioAstVormFieldTest{}
...> {:ok, t} = t |> struct_forms_update(:a_valid_ast)
...> t.vorm |> helper_vorm_test_forms!(binding: [x: 3])
{:a_valid_ast, [":a_valid_ast"]}
"""
@spec struct_forms_update(t, any) :: {:ok, t} | {:error, error}
def struct_forms_update(t, new_forms)
def struct_forms_update(%{@pav_key_struct_id => _} = state, %PAV{} = vorm) do
state |> struct_vorm_update(vorm)
end
def struct_forms_update(%{@pav_key_struct_id => _} = state, forms) when is_list(forms) do
with true <- forms |> Keyword.keyword? do
state |> struct_vorm_update(forms)
else
false ->
with {:ok, %{@pav_key_struct_id => _, @pav_key_vorm => %PAV{} = vorm} = state} <- state |> struct_vorm_ensure,
{:ok, %PAV{} = vorm} <- vorm |> PAV.vorm_update_forms(forms) do
{:ok, state |> struct!([{@pav_key_vorm, vorm}])}
else
{:error, _} = result -> result
end
end
end
def struct_forms_update(%{@pav_key_struct_id => _} = state, value) do
with {:ok, _} <- value |> PAVU.form_validate do
state |> struct_forms_update([value])
else
_ -> new_error_result(m: "new vorm forms invalid", v: value)
end
end
@doc ~S"""
`struct_vorm_reset/1` take a *state* and resets its *vorm field* to `nil` returning `{:ok, state}`.
## Examples
iex> t = %PlymioAstVormFieldTest{}
...> {:ok, t} = t |> struct_vorm_reset
...> match?(nil, t.vorm)
true
iex> {:ok, vorm} = Plymio.Ast.Vorm.new(form: quote(do: x = x + 1))
...> {:ok, t} = %PlymioAstVormFieldTest{vorm: vorm}
...> |> struct_vorm_reset
...> match?(nil, t.vorm)
true
"""
@spec struct_vorm_reset(t) :: {:ok, t} | {:error, error}
def struct_vorm_reset(%{@pav_key_struct_id => _} = state) do
{:ok, state |> struct!([{@pav_key_vorm, @pav_value_vorm_initial_value}])}
end
@doc ~S"""
`struct_vorm_add/2` appends *new_forms* to the *vorm forms* of the *vorm* in the *vorm* field.
`struct_vorm_ensure/1` is called first to ensure the *vorm field* is a *vorm*.
If the `add` suceeds, `{:ok, state}` is returned.
## Examples
iex> t = %PlymioAstVormFieldTest{}
...> {:ok, t} = t |> struct_vorm_add(quote(do: x = x + 1))
...> t.vorm |> helper_vorm_test_forms!(binding: [x: 7])
{8, ["x = x + 1"]}
iex> t = %PlymioAstVormFieldTest{}
...> {:ok, t} = t |> struct_vorm_add(quote(do: x = x + 1))
...> {:ok, t} = t |> struct_vorm_add([quote(do: x = x * x), quote(do: x = x - 1)])
...> t.vorm |> helper_vorm_test_forms!(binding: [x: 7])
{63, ["x = x + 1", "x = x * x", "x = x - 1"]}
"""
@struct_var :state |> Macro.var(nil)
@struct_match quote(do: %{@pav_key_struct_id => _} = unquote(@struct_var))
@vorm_var :vorm |> Macro.var(nil)
@other_vars 5 |> Macro.generate_arguments(nil)
@struct_vorm_delegates_ok_state [
{2, :struct_vorm_add, :add},
{2, :struct_vorm_put, :put},
{2, :struct_vorm_filter, :filter},
{2, :struct_vorm_reject, :reject},
{3, :struct_vorm_insert, :insert},
{3, :struct_vorm_replace, :replace},
{2, :struct_vorm_reduce, :reduce},
{2, :struct_vorm_transform, :transform},
{3, :struct_vorm_transform, :transform},
{3, :struct_vorm_postwalk, :postwalk},
{3, :struct_vorm_prewalk, :prewalk},
{5, :struct_vorm_traverse, :traverse},
{3, :struct_vorm_pipe_before, :pipe_before},
{3, :struct_vorm_pipe_after, :pipe_after},
]
for {arity, struct_fun, form_fun} <- @struct_vorm_delegates_ok_state do
struct_args = [@struct_match | Enum.take(@other_vars,arity-1)]
form_args = Enum.take(@other_vars, arity-1)
def unquote(struct_fun)(unquote_splicing(struct_args)) do
with {:ok, %{@pav_key_struct_id => _, @pav_key_vorm => %Plymio.Ast.Vorm{} = unquote(@vorm_var)} = unquote(@struct_var)} <- unquote(@struct_var) |> struct_vorm_ensure,
{:ok, %Plymio.Ast.Vorm{} = unquote(@vorm_var)} <- unquote(@vorm_var) |> Plymio.Ast.Vorm.unquote(form_fun)(unquote_splicing(form_args)) do
{:ok, unquote(@struct_var) |> struct!([{@pav_key_vorm, unquote(@vorm_var)}])}
else
{:error, _} = result -> result
end
end
end
@struct_vorm_delegates_ok_forms_state [
{1, :struct_vorm_express, :express},
{1, :struct_vorm_produce, :produce},
]
for {arity, struct_fun, form_fun} <- @struct_vorm_delegates_ok_forms_state do
struct_args = [@struct_match | Enum.take(@other_vars,arity-1)]
form_args = Enum.take(@other_vars, arity-1)
def unquote(struct_fun)(unquote_splicing(struct_args)) do
with {:ok, %{@pav_key_struct_id => _, @pav_key_vorm => %Plymio.Ast.Vorm{} = unquote(@vorm_var)} = unquote(@struct_var)} <- unquote(@struct_var) |> struct_vorm_ensure,
{:ok, {forms, %Plymio.Ast.Vorm{} = unquote(@vorm_var) = unquote(@vorm_var)}} <- unquote(@vorm_var) |> Plymio.Ast.Vorm.unquote(form_fun)(unquote_splicing(form_args)) do
{:ok, {forms, unquote(@struct_var) |> struct!([{@pav_key_vorm, unquote(@vorm_var)}])}}
else
{:error, _} = result -> result
end
end
end
@struct_vorm_delegates_ok_forms [
{2, :struct_vorm_fetch, :fetch},
]
for {arity, struct_fun, form_fun} <- @struct_vorm_delegates_ok_forms do
struct_args = [@struct_match | Enum.take(@other_vars,arity-1)]
form_args = Enum.take(@other_vars, arity-1)
def unquote(struct_fun)(unquote_splicing(struct_args)) do
with {:ok, %{@pav_key_struct_id => _, @pav_key_vorm => %Plymio.Ast.Vorm{} = unquote(@vorm_var)}} <- unquote(@struct_var) |> struct_vorm_ensure,
{:ok, _forms} = result <- unquote(@vorm_var) |> Plymio.Ast.Vorm.unquote(form_fun)(unquote_splicing(form_args)) do
result
else
{:error, _} = result -> result
end
end
end
end