Current section
Files
Jump to
Current section
Files
lib/codi/pattern/struct/struct.ex
defmodule Plymio.Codi.Pattern.Struct do
@moduledoc ~S"""
The *struct* patterns create a range of transform functions for a module's struct.
See `Plymio.Codi` for an overview and documentation terms.
## Set and Unset Fields
These patterns use *the unset value*
(see `Plymio.Fontais.Guard.the_unset_value/0`) to decide whether a
field has a useful value. Fields can be unset by default by using
*the unset value* in the `Kernel.defstruct/1` e.g.
defstruct [x: Plymio.Fontais.Guard.the_unset_value()]
For example, a function built using the `struct_fetch` pattern will return
`{error, error}` if the target field's value is unset. Similary,
for `struct_get`, `{:ok, default}` will be returned if the field's
value is unset.
## Errors
The code generated by most *struct* patterns checks the first
argument is an instance of the target module's *struct* and returns
`{:error, error}` if not.
## Test Environment
The doctests use a helper (`codi_helper_struct_compile_module/1`) to
compile the generated function(s) in a dynamically created module,
returning `{:ok, {forms, test_mod}}`.
The `forms` are the generated code and another helper
(`Harnais.Helper.harnais_helper_format_forms!/2`) is used to
"textify" the code using the Elixir code formatter.
The `test_mod` is the dynamically created module and is used to call the generated function(s).
The default `Kernel.defstruct/1` for the doctests is shown here. Note the `:z` field is unset.
defstruct [x: 42, y: nil, z: Plymio.Fontais.Guard.the_unset_value()]
The first example for each pattern just shows the generated code with
subsequent examples performing the actual test(s).
## Common Codi Pattern Opts (cpo) Keys
As well as the top level common options, these keys are valid in any *struct* pattern's *cpo*:
| Key | Aliases |
| :--- | :--- |
| `:fun_name` | *:name, :function_name* |
| `:fun_field` | *:field, ::function_field, :key, :fun_key, :function_key* |
| `:fun_args` | *:args, :function_args* |
| `:fun_arity` | *:arity, :function_arity* |
| `:fun_doc` | *:doc, :function_doc* |
| `:typesepc_spec_args` | *:spec_args* |
| `:typespec_spec_result` |*:spec_result, :result, :fun_result, :function_result* |
| `:since` | |
All *struct* patterns support the generation of `@doc`, `@spec` and
`@since` module attribute forms.
All *struct* patterns generate a `@doc` by default. (It can be disabled in
the usual way by specifying `doc: false` in the *cpo*.)
> In the doctests below the `@doc` forms are mostly disabled (i.e. `doc: nil`) for clarity.
## Pattern: *struct_get*
This pattern is a convenience to generate both *struct_get1* and *struct_get2* patterns.
The *cpo* must be configured for a *struct_get2* and will be "reduced" to suite a *struct_get1*.
## Pattern: *struct_get1*
The *struct_get1* pattern creates a function to get the value of a
struct's field and if the value is unset return a fixed default.
## Examples
This example generate a `get/1` function for the `:z` field. Note
the `:field` in the *cpo* is a `Keyword` with the fixed default for
the `:z` field (`"z get default"`)
iex> {:ok, {forms, _test_mod}} = [
...> struct_get1: [args: :t, name: :get_z, field: [z: "z get default"], doc: nil],
...> ] |> codi_helper_struct_compile_module
...> forms |> harnais_helper_format_forms!
["def(get_z(t))",
"",
"def(get_z(%__MODULE__{z: field_value})) do",
" field_value",
" |> case do",
" x when Plymio.Fontais.Guard.is_value_set(x) ->",
" {:ok, x}",
"",
" _ ->",
" {:ok, \"z get default\"}",
" end",
"end",
"",
"def(get_z(state)) do",
" Plymio.Codi.Error.new_error_result(m: \"struct invalid\", v: state)",
"end"]
iez> {:ok, {_forms, test_mod}} = [
...> struct_get1: [args: :t, name: :get_z, field: [z: "z get default"], doc: nil],
...> ] |> codi_helper_struct_compile_module
...> # z is unset by default so the get/1 default will be returned.
...> struct(test_mod) |> test_mod.get_z
{:ok, "z get default"}
## Pattern: *struct_get2*
The *struct_get2* pattern creates a function to get the value of a
struct's field and, if the value is unset, return the second argument.
## Examples
This example generate a `get/2` function for the `:z` field.
iex> {:ok, {forms, _test_mod}} = [
...> struct_get2: [args: [:t, :the_default_for_z], name: :get_z, field: :z, doc: nil],
...> ] |> codi_helper_struct_compile_module
...> forms |> harnais_helper_format_forms!
["def(get_z(t, the_default_for_z))",
"",
"def(get_z(%__MODULE__{z: field_value}, default)) do",
" field_value",
" |> case do",
" x when Plymio.Fontais.Guard.is_value_set(x) ->",
" {:ok, x}",
"",
" _ ->",
" {:ok, default}",
" end",
"end",
"",
"def(get_z(state, _default)) do",
" Plymio.Codi.Error.new_error_result(m: \"struct invalid\", v: state)",
"end"]
iez> {:ok, {_forms, test_mod}} = [
...> struct_get2: [args: [:t, :the_default_for_z], name: :get_z, field: :z, doc: nil],
...> ] |> codi_helper_struct_compile_module
...> # z is unset by default so the get/2 2nd argument will be returned.
...> struct(test_mod) |> test_mod.get_z("an explicit default")
{:ok, "an explicit default"}
## Pattern: *struct_fetch*
The *struct_fetch* pattern creates a function to fetch the value of a struct's field.
## Examples
This example generate a `fetch/1` function for the `:x` field.
iex> {:ok, {forms, _test_mod}} = [
...> struct_fetch: [args: :t, name: :fetch_x, field: :x, doc: nil],
...> ] |> codi_helper_struct_compile_module
...> forms |> harnais_helper_format_forms!
["def(fetch_x(t))",
"",
"def(fetch_x(%__MODULE__{x: field_value} = state)) do",
" field_value",
" |> case do",
" x when Plymio.Fontais.Guard.is_value_set(x) ->",
" {:ok, x}",
"",
" _ ->",
" Plymio.Codi.Error.new_error_result(m: \"struct field \#{:x} unset\", v: state)",
" end",
"end",
"",
"def(fetch_x(state)) do",
" Plymio.Codi.Error.new_error_result(m: \"struct invalid\", v: state)",
"end"]
iex> {:ok, {_forms, test_mod}} = [
...> struct_fetch: [args: :t, name: :fetch_x, field: :x, doc: nil],
...> ] |> codi_helper_struct_compile_module
...> {:ok, 42} = struct(test_mod) |> test_mod.fetch_x
...> # setting x to the unset value causes the fetch to fail
...> {:error, error} = struct(test_mod, x: Plymio.Fontais.Guard.the_unset_value) |> test_mod.fetch_x
...> true = error |> Exception.message |> String.starts_with?("struct field x unset")
...> # the argument must be an instance of the module's struct
...> {:error, error} = :not_a_struct |> test_mod.fetch_x
...> error |> Exception.message
"struct invalid, got: :not_a_struct"
## Pattern: *struct_put*
The *struct_put* pattern creates a function to put a value for a struct's field.
## Examples
This example generates a `put/2` function for the `:x` field.
iex> {:ok, {forms, _test_mod}} = [
...> struct_put: [args: [:t, :value], name: :put_x, field: :x, doc: nil],
...> ] |> codi_helper_struct_compile_module
...> forms |> harnais_helper_format_forms!
["def(put_x(t, value))",
"",
"def(put_x(%__MODULE__{x: _} = state, value)) do",
" {:ok, state |> struct!(x: value)}",
"end",
"",
"def(put_x(state, _value)) do",
" Plymio.Codi.Error.new_error_result(m: \"struct invalid\", v: state)",
"end"]
iex> {:ok, {_forms, test_mod}} = [
...> struct_put: [args: [:t, :value], name: :put_x, field: :x, doc: nil],
...> struct_fetch: [args: :t, name: :fetch_x, field: :x, doc: nil],
...> ] |> codi_helper_struct_compile_module
...> # set the :x field's value to 123
...> {:ok, %test_mod{} = t1} = struct(test_mod) |> test_mod.put_x(123)
...> # use `fetch_x/1` to check
...> {:ok, 123} = t1 |> test_mod.fetch_x
...> # the argument must be an instance of the module's struct
...> {:error, error} = :not_a_struct |> test_mod.put_x(123)
...> error |> Exception.message
"struct invalid, got: :not_a_struct"
## Pattern: *struct_maybe_put*
The *struct_maybe_put* pattern create a function to put a value for
a struct's field *only* if the field's current value is unset.
## Examples
This code shows a `maybe_put/2` function for the `:z` field:
iex> {:ok, {forms, _test_mod}} = [
...> struct_maybe_put: [args: [:t, :value], name: :maybe_put_z, field: :z, doc: nil],
...> ] |> codi_helper_struct_compile_module
...> forms |> harnais_helper_format_forms!
["def(maybe_put_z(t, value))",
"",
"def(",
" maybe_put_z(%__MODULE__{z: field_value} = state, _value)",
" when Plymio.Fontais.Guard.is_value_set(field_value)",
") do",
" {:ok, state}",
"end",
"",
"def(",
" maybe_put_z(%__MODULE__{z: field_value} = state, value)",
" when Plymio.Fontais.Guard.is_value_unset(field_value)",
") do",
" value",
" |> Plymio.Fontais.Guard.is_value_unset()",
" |> case do",
" true ->",
" {:ok, state}",
"",
" _ ->",
" {:ok, state |> struct!(z: value)}",
" end",
"end",
"",
"def(maybe_put_z(state, _value)) do",
" Plymio.Codi.Error.new_error_result(m: \"struct invalid\", v: state)",
"end"]
Here `maybe_put/2` and `fetch/1` functions are generated for two fields: the
`:x` field has `42` for its default, whereas the `:z` field is
unset.
iex> {:ok, {_forms, test_mod}} = [
...> struct_maybe_put: [args: [:t, :value], name: :maybe_put_x, field: :x, doc: nil],
...> struct_fetch: [args: :t, name: :fetch_x, field: :x, doc: nil],
...> struct_maybe_put: [args: [:t, :value], name: :maybe_put_z, field: :z, doc: nil],
...> struct_fetch: [args: :t, name: :fetch_z, field: :z, doc: nil],
...> ] |> codi_helper_struct_compile_module
...> # by default the `:z` field is unset so `fetch_z/1` will fail
...> t1 = struct(test_mod)
...> {:error, _error} = t1 |> test_mod.fetch_z
...> # maybe_put/2 will update the field since it is unset
...> {:ok, %test_mod{} = t1} = t1 |> test_mod.maybe_put_z(123)
...> # use `fetch_z/1` to check
...> {:ok, 123} = t1 |> test_mod.fetch_z
...> # field `:x` has a default of `42` so maybe_put/2 will not update the field
...> {:ok, %test_mod{} = t1} = t1 |> test_mod.maybe_put_x("will be ignored")
...> # the `:x` field will still be `42`
...> t1 |> test_mod.fetch_x
{:ok, 42}
## Pattern: *struct_has?*
The *struct_has?* pattern creates a function that returns `true` if
the field's value is set, otherwise `false`.
> Note: if the first argument is not a valid struct, `false` is returned..
## Examples
This code shows `has?/1` function for the `:z` field:
iex> {:ok, {forms, _test_mod}} = [
...> struct_has?: [args: :t, name: :has_z?, field: :z, doc: nil],
...> ] |> codi_helper_struct_compile_module
...> forms |> harnais_helper_format_forms!
["def(has_z?(t))",
"",
"def(has_z?(%__MODULE__{z: field_value}) when Plymio.Fontais.Guard.is_value_set(field_value)) do",
" true",
"end",
"",
"def(has_z?(_state)) do",
" false",
"end"]
Here `has?/1` functions are generated for two fields: the
`:x` field has `42` for its default, whereas the `:z` field is
unset.
iex> {:ok, {_forms, test_mod}} = [
...> struct_has?: [args: :t, name: :has_x?, field: :x, doc: nil],
...> struct_has?: [args: :t, name: :has_z?, field: :z, doc: nil],
...> ] |> codi_helper_struct_compile_module
...> t1 = struct(test_mod)
...> false = t1 |> test_mod.has_z?
...> t1 |> test_mod.has_x?
true
## Pattern: *struct_update*
The *struct_update* pattern creates a function to call the modules's `update/2` function.
The module's `update/2` function is a standard `Plymio` module state
function that works like a validated put. Apart from showing the generated code, it is not
documented or tested further here.
## Examples
This example generates an `update/2` function for the `:x` field.
iex> {:ok, {forms, _codi}} = [
...> struct_update: [args: [:t, :value], name: :update_x, field: :x, doc: nil],
...> ] |> CODI.produce_codi
...> forms |> harnais_helper_format_forms!
["def(update_x(t, value))",
"",
"def(update_x(%__MODULE__{x: _} = state, value)) do",
" state |> update(x: value)",
"end",
"",
"def(update_x(state, _value)) do",
" Plymio.Codi.Error.new_error_result(m: \"struct invalid\", v: state)",
"end"]
## Pattern: *struct_set*
The *struct_set* pattern is a simple but versatile pattern for
creating a function that sets one or more fields in the *struct* to
specific values, defaulting to *the unset value*.
## Examples
This example generates an `set/2` function to set the `:x` field to value `123`.
Note the `:field` in the *cpo* is a `Keyword`.
iex> {:ok, {forms, _codi}} = [
...> struct_set: [args: :t, name: :set_x, field: [x: 123], doc: nil],
...> ] |> codi_helper_struct_compile_module
...> forms |> harnais_helper_format_forms!
["def(set_x(t))",
"",
"def(set_x(%__MODULE__{x: _} = state)) do",
" {:ok, state |> struct!(x: 123)}",
"end",
"",
"def(set_x(state)) do",
" Plymio.Codi.Error.new_error_result(m: \"struct invalid\", v: state)",
"end"]
This example create a `set/1` function that sets the `:x` and `:z` fields.
iex> {:ok, {_forms, test_mod}} = [
...> struct_set: [args: :t, name: :set_xz, doc: nil,
...> field: [x: 123, z: "z is no longer unset"]],
...> struct_fetch: [args: :t, name: :fetch_x, field: :x, doc: nil],
...> struct_fetch: [args: :t, name: :fetch_y, field: :y, doc: nil],
...> struct_fetch: [args: :t, name: :fetch_z, field: :z, doc: nil],
...> ] |> codi_helper_struct_compile_module
...> # set the :x and :z fields
...> {:ok, %test_mod{} = t1} = struct(test_mod) |> test_mod.set_xz
...> # use fetch to check
...> {:ok, 123} = t1 |> test_mod.fetch_x
...> {:ok, nil} = t1 |> test_mod.fetch_y
...> t1 |> test_mod.fetch_z
{:ok, "z is no longer unset"}
This example create a `set/1` function that sets the `:x` and `:z`
fields to specific values, but unsets the `:y` field. Note the
`:field` in the *cpo* is not a `Keyword`: The value for `:y` is not given and
defaults to be the unset value.
iex> {:ok, {_forms, test_mod}} = [
...> struct_set: [args: :t, name: :set_xyz, doc: nil,
...> field: [{:x, 123}, :y, {:z, "z is no longer unset"}]],
...> struct_fetch: [args: :t, name: :fetch_x, field: :x, doc: nil],
...> struct_fetch: [args: :t, name: :fetch_y, field: :y, doc: nil],
...> struct_fetch: [args: :t, name: :fetch_z, field: :z, doc: nil],
...> ] |> codi_helper_struct_compile_module
...> # set all 3 fields
...> {:ok, %test_mod{} = t1} = struct(test_mod) |> test_mod.set_xyz
...> {:ok, 123} = t1 |> test_mod.fetch_x
...> {:ok, "z is no longer unset"} = t1 |> test_mod.fetch_z
...> # :y is now unset
...> {:error, error} = t1 |> test_mod.fetch_y
...> error |> Exception.message |> String.starts_with?("struct field y unset")
true
## Pattern: *struct_export*
The *struct_export* pattern creates
a function that exports one or more fields in the *struct* to
an *opts* (`Keyword`).
The export (*opts*) is sparse: only keys that are set are included.
Default values can be provided; if the value of the key in the *struct* is unset, the default is used.
## Examples
This example generates an `export/1` function for all three fields
in the test *struct*. Note, since no default export values were given
in the `:field` in the *cpo*, the defaults are the unset value and
the field will only appear in the export if the *struct* value is set.
iex> {:ok, {forms, _codi}} = [
...> struct_export: [args: :t, name: :export_all, field: [:x, :y, :z], doc: nil],
...> ] |> codi_helper_struct_compile_module
...> forms |> harnais_helper_format_forms!
["def(export_all(t))", "",
"def(export_all(%__MODULE__{x: field_value, y: field_value1, z: field_value2})) do",
" tuples =",
" [",
" x: :plymio_fontais_t3h1e4_u9n8s7e2t7_v1a8l3u8e,",
" y: :plymio_fontais_t3h1e4_u9n8s7e2t7_v1a8l3u8e,",
" z: :plymio_fontais_t3h1e4_u9n8s7e2t7_v1a8l3u8e",
" ] ++ [x: field_value, y: field_value1, z: field_value2]",
"",
" export =",
" tuples",
" |> Keyword.keys()",
" |> Stream.uniq()",
" |> Stream.map(fn k ->",
" tuples",
" |> Keyword.get_values(k)",
" |> Enum.filter(fn v -> v |> Plymio.Fontais.Guard.is_value_set() end)",
" |> case do",
" [] ->",
" {k, @plymio_fontais_the_unset_value}",
"",
" values ->",
" {k, values |> List.last()}",
" end",
" end)",
" |> Stream.filter(fn {_k, v} -> v |> Plymio.Fontais.Guard.is_value_set() end)",
" |> Keyword.new()",
"",
" {:ok, export}",
"end",
"",
"def(export_all(state)) do",
" Plymio.Codi.Error.new_error_result(m: \"struct invalid\", v: state)",
"end"]
This example creates an `export/1` function that exports the `:x` and `:z`
fields. The `:z` field is unset by default so will not appear in the export unless set explicitly.
iex> {:ok, {_forms, test_mod}} = [
...> struct_export: [args: :t, name: :export_xz, doc: nil,
...> field: [:x, :z]],
...> struct_put: [args: [:t, :value], name: :put_z, field: :z, doc: nil],
...> ] |> codi_helper_struct_compile_module
...> # the :z field is by default unset and will not be in the export
...> {:ok, [x: 42]} = struct(test_mod) |> test_mod.export_xz
...> # set z and export
...> {:ok, %test_mod{} = t1} = struct(test_mod) |> test_mod.put_z("z is now set")
...> t1 |> test_mod.export_xz
{:ok, [x: 42, z: "z is now set"]}
Another example but providing default values for each key in the export by
supplying a `Keyword` for `:field` in the *cpo*:.
iex> {:ok, {_forms, test_mod}} = [
...> struct_export: [args: :t, name: :export_xz, doc: nil,
...> field: [x: :x_default, z: :z_default]],
...> struct_put: [args: [:t, :value], name: :put_z, field: :z, doc: nil],
...> ] |> codi_helper_struct_compile_module
...> # the :z field has a default export value
...> struct(test_mod) |> test_mod.export_xz
{:ok, [x: 42, z: :z_default]}
"""
alias Plymio.Codi, as: CODI
alias Plymio.Codi.Utility, as: CODIUTIL
alias Plymio.Codi.Utility.Depend, as: DEPEND
use Plymio.Fontais.Attribute
use Plymio.Codi.Attribute
import Plymio.Codi.Error,
only: [
new_error_result: 1
]
import Plymio.Fontais.Option,
only: [
opts_take_canonical_keys: 2,
opts_create_aliases_dict: 1
]
import Plymio.Codi.Utility,
only: [
cpo_resolve_fun_name: 1,
cpo_resolve_guard_fun_args: 2,
cpo_resolve_guard_field_match: 1,
cpo_resolve_guard_field_match: 2,
cpo_resolve_typespec_spec_args: 1
]
import Plymio.Codi.CPO
@pattern_struct_get_kvs_alias [
@plymio_codi_key_alias_pattern,
@plymio_codi_key_alias_status,
@plymio_codi_key_alias_form,
@plymio_codi_key_alias_since,
# limited aliases
{@plymio_codi_key_typespec_spec_args, [:spec_args]},
@plymio_codi_key_alias_typespec_spec_result,
@plymio_codi_key_alias_fun_name,
@plymio_codi_key_alias_fun_doc,
@plymio_codi_key_alias_fun_args,
@plymio_codi_key_alias_fun_arity,
@plymio_codi_key_alias_fun_key,
@plymio_codi_key_alias_fun_default,
@plymio_codi_key_alias_forms_edit
]
@pattern_struct_get_dict_alias @pattern_struct_get_kvs_alias
|> opts_create_aliases_dict
@doc false
def cpo_pattern_struct_get_normalise(opts, dict \\ nil) do
opts |> opts_take_canonical_keys(dict || @pattern_struct_get_dict_alias)
end
@pattern_struct_fetch_kvs_alias [
@plymio_codi_key_alias_pattern,
@plymio_codi_key_alias_status,
@plymio_codi_key_alias_form,
@plymio_codi_key_alias_since,
# limited aliases
{@plymio_codi_key_typespec_spec_args, [:spec_args]},
@plymio_codi_key_alias_typespec_spec_result,
@plymio_codi_key_alias_fun_name,
@plymio_codi_key_alias_fun_doc,
@plymio_codi_key_alias_fun_args,
@plymio_codi_key_alias_fun_arity,
@plymio_codi_key_alias_fun_key,
@plymio_codi_key_alias_forms_edit
]
@pattern_struct_fetch_dict_alias @pattern_struct_fetch_kvs_alias
|> opts_create_aliases_dict
@doc false
def cpo_pattern_struct_fetch_normalise(opts, dict \\ nil) do
opts |> opts_take_canonical_keys(dict || @pattern_struct_fetch_dict_alias)
end
@pattern_struct_put_kvs_alias [
@plymio_codi_key_alias_pattern,
@plymio_codi_key_alias_status,
@plymio_codi_key_alias_form,
@plymio_codi_key_alias_since,
# limited aliases
{@plymio_codi_key_typespec_spec_args, [:spec_args]},
@plymio_codi_key_alias_typespec_spec_result,
@plymio_codi_key_alias_fun_name,
@plymio_codi_key_alias_fun_doc,
@plymio_codi_key_alias_fun_args,
@plymio_codi_key_alias_fun_arity,
@plymio_codi_key_alias_fun_key,
@plymio_codi_key_alias_forms_edit
]
@pattern_struct_put_dict_alias @pattern_struct_put_kvs_alias
|> opts_create_aliases_dict
@doc false
def cpo_pattern_struct_put_normalise(opts, dict \\ nil) do
opts |> opts_take_canonical_keys(dict || @pattern_struct_put_dict_alias)
end
@pattern_struct_maybe_put_kvs_alias [
@plymio_codi_key_alias_pattern,
@plymio_codi_key_alias_status,
@plymio_codi_key_alias_form,
@plymio_codi_key_alias_since,
# limited aliases
{@plymio_codi_key_typespec_spec_args, [:spec_args]},
@plymio_codi_key_alias_typespec_spec_result,
@plymio_codi_key_alias_fun_name,
@plymio_codi_key_alias_fun_doc,
@plymio_codi_key_alias_fun_args,
@plymio_codi_key_alias_fun_arity,
@plymio_codi_key_alias_fun_key,
@plymio_codi_key_alias_forms_edit
]
@pattern_struct_maybe_put_dict_alias @pattern_struct_maybe_put_kvs_alias
|> opts_create_aliases_dict
@doc false
def cpo_pattern_struct_maybe_put_normalise(opts, dict \\ nil) do
opts |> opts_take_canonical_keys(dict || @pattern_struct_maybe_put_dict_alias)
end
@pattern_struct_set_kvs_alias [
@plymio_codi_key_alias_pattern,
@plymio_codi_key_alias_status,
@plymio_codi_key_alias_form,
@plymio_codi_key_alias_since,
# limited aliases
{@plymio_codi_key_typespec_spec_args, [:spec_args]},
@plymio_codi_key_alias_typespec_spec_result,
@plymio_codi_key_alias_fun_name,
@plymio_codi_key_alias_fun_doc,
@plymio_codi_key_alias_fun_args,
@plymio_codi_key_alias_fun_arity,
@plymio_codi_key_alias_fun_key,
@plymio_codi_key_alias_fun_default,
@plymio_codi_key_alias_forms_edit
]
@pattern_struct_set_dict_alias @pattern_struct_set_kvs_alias
|> opts_create_aliases_dict
@doc false
def cpo_pattern_struct_set_normalise(opts, dict \\ nil) do
opts |> opts_take_canonical_keys(dict || @pattern_struct_set_dict_alias)
end
@pattern_struct_has_kvs_alias [
@plymio_codi_key_alias_pattern,
@plymio_codi_key_alias_status,
@plymio_codi_key_alias_form,
@plymio_codi_key_alias_since,
# limited aliases
{@plymio_codi_key_typespec_spec_args, [:spec_args]},
@plymio_codi_key_alias_typespec_spec_result,
@plymio_codi_key_alias_fun_name,
@plymio_codi_key_alias_fun_doc,
@plymio_codi_key_alias_fun_args,
@plymio_codi_key_alias_fun_arity,
@plymio_codi_key_alias_fun_key,
@plymio_codi_key_alias_fun_default,
@plymio_codi_key_alias_forms_edit
]
@pattern_struct_has_dict_alias @pattern_struct_has_kvs_alias
|> opts_create_aliases_dict
@doc false
def cpo_pattern_struct_has_normalise(opts, dict \\ nil) do
opts |> opts_take_canonical_keys(dict || @pattern_struct_has_dict_alias)
end
@pattern_struct_update_kvs_alias [
@plymio_codi_key_alias_pattern,
@plymio_codi_key_alias_status,
@plymio_codi_key_alias_form,
@plymio_codi_key_alias_since,
# limited aliases
{@plymio_codi_key_typespec_spec_args, [:spec_args]},
@plymio_codi_key_alias_typespec_spec_result,
@plymio_codi_key_alias_fun_name,
@plymio_codi_key_alias_fun_doc,
@plymio_codi_key_alias_fun_args,
@plymio_codi_key_alias_fun_arity,
@plymio_codi_key_alias_fun_key,
@plymio_codi_key_alias_forms_edit
]
@pattern_struct_update_dict_alias @pattern_struct_update_kvs_alias
|> opts_create_aliases_dict
@doc false
def cpo_pattern_struct_update_normalise(opts, dict \\ nil) do
opts |> opts_take_canonical_keys(dict || @pattern_struct_update_dict_alias)
end
@pattern_struct_export_kvs_alias [
@plymio_codi_key_alias_pattern,
@plymio_codi_key_alias_status,
@plymio_codi_key_alias_form,
@plymio_codi_key_alias_since,
# limited aliases
{@plymio_codi_key_typespec_spec_args, [:spec_args]},
@plymio_codi_key_alias_typespec_spec_result,
@plymio_codi_key_alias_fun_name,
@plymio_codi_key_alias_fun_doc,
@plymio_codi_key_alias_fun_args,
@plymio_codi_key_alias_fun_arity,
@plymio_codi_key_alias_fun_key,
@plymio_codi_key_alias_forms_edit
]
@pattern_struct_export_dict_alias @pattern_struct_export_kvs_alias
|> opts_create_aliases_dict
@doc false
def cpo_pattern_struct_export_normalise(opts, dict \\ nil) do
opts |> opts_take_canonical_keys(dict || @pattern_struct_export_dict_alias)
end
@doc false
def express_pattern(codi, pattern, opts)
# expectation if the cpo is initially configured for get2
def express_pattern(%CODI{} = state, pattern, cpo)
when pattern == @plymio_codi_pattern_struct_get do
with {:ok, cpo} <- cpo |> cpo_pattern_struct_get_normalise,
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_arity(2),
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_args([:struct, :default]),
{:ok, ctrl} <- [] |> ctrl_put_fun_arity_value(2),
{:ok, get2_fun_args} <- cpo |> cpo_resolve_guard_fun_args(ctrl),
# typespec if any
{:ok, cpo} <-
cpo
|> cpo_maybe_transform(&cpo_has_typespec_spec_opts?/1, [
{:cpo_maybe_put_typespec_spec_result, :result},
{:cpo_maybe_put_typespec_spec_args, [[:struct, :any]]}
]),
{:ok, cpo_get1} <- cpo |> cpo_put_pattern(@plymio_codi_pattern_struct_get1),
{:ok, cpo_get1} <- cpo_get1 |> cpo_put_fun_args(get2_fun_args |> Enum.take(1)),
{:ok, cpo_get1} <- cpo_get1 |> cpo_put_fun_arity(1),
{:ok, cpo_get1} <-
cpo_get1
|> cpo_maybe_transform(
&cpo_has_typespec_spec_opts?/1,
fn cpo ->
with {:ok, spec_args} <- cpo_get1 |> cpo_resolve_typespec_spec_args,
{:ok, cpo} <- cpo |> cpo_put_typespec_spec_args(spec_args |> Enum.take(1)),
true <- true do
{:ok, cpo}
else
{:error, %{__exception__: true}} = result -> result
end
end
),
{:ok, cpo_get2} <- cpo |> cpo_put_pattern(@plymio_codi_pattern_struct_get2),
{:ok, cpo_get2} <- cpo_get2 |> cpo_put_fun_args(get2_fun_args),
true <- true do
{:ok, {[cpo_get1, cpo_get2], state}}
else
{:error, %{__exception__: true}} = result -> result
end
end
def express_pattern(%CODI{} = state, pattern, cpo)
when pattern == @plymio_codi_pattern_struct_get1 do
with {:ok, cpo} <- cpo |> cpo_pattern_struct_get_normalise,
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_arity(1),
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_args(:struct),
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_doc(pattern),
{:ok, ctrl} <- [] |> ctrl_put_fun_arity_value(1),
{:ok, ctrl} <- ctrl |> ctrl_put_fun_key_length(1),
{:ok, {{field_name, field_value_var}, field_vars, field_tuples, field_match}} <-
cpo
|> cpo_resolve_guard_field_match(ctrl),
{:ok, cpo} <-
cpo
|> cpo_maybe_put_fun_name("struct_#{to_string(field_name)}_get" |> String.to_atom()),
{:ok, fun_name} <- cpo |> cpo_resolve_fun_name,
{:ok, fun_args} <- cpo |> cpo_resolve_guard_fun_args(ctrl),
# save resolved for e.g. doc pattern
{:ok, cpo} <- cpo |> cpo_put_fun_key(field_tuples),
{:ok, cpo} <- cpo |> cpo_put_fun_args(fun_args),
true <- true do
fun_default = field_tuples |> hd |> elem(1)
pattern_form =
quote do
def unquote(fun_name)(unquote_splicing(fun_args))
def unquote(fun_name)(unquote(field_match)) do
unquote(field_value_var)
|> case do
x when Plymio.Fontais.Guard.is_value_set(x) ->
{:ok, x}
_ ->
{:ok, unquote(fun_default)}
end
end
def unquote(fun_name)(state) do
Plymio.Codi.Error.new_error_result(m: "struct invalid", v: state)
end
end
depend_args = [
{&cpo_has_fun_doc?/1, &DEPEND.cpo_transform_doc_depend/1},
{&cpo_has_since?/1, &DEPEND.cpo_transform_since_depend/1},
{&cpo_has_typespec_spec_opts?/1,
[
&DEPEND.cpo_transform_typespec_spec_depend/1,
{:cpo_maybe_put_typespec_spec_result, :result},
{:cpo_maybe_put_typespec_spec_args, :struct}
]}
]
with {:ok, cpo} <- cpo |> cpo_done_with_edited_form(pattern_form),
{:ok, {depend_cpos, %CODI{}}} <- state |> DEPEND.create_depend_cpos(cpo, depend_args) do
cpos = depend_cpos ++ [cpo]
{:ok, {cpos, state}}
else
{:error, %{__exception__: true}} = result -> result
end
else
{:error, %{__exception__: true}} = result -> result
end
end
def express_pattern(%CODI{} = state, pattern, cpo)
when pattern == @plymio_codi_pattern_struct_get2 do
with {:ok, cpo} <- cpo |> cpo_pattern_struct_get_normalise,
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_arity(2),
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_args([:struct, :default]),
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_doc(pattern),
{:ok, ctrl} <- [] |> ctrl_put_fun_arity_value(2),
{:ok, ctrl} <- ctrl |> ctrl_put_fun_key_length(1),
{:ok, {{field_name, field_value_var}, field_vars, field_tuples, field_match}} <-
cpo
|> cpo_resolve_guard_field_match(ctrl),
{:ok, cpo} <-
cpo
|> cpo_maybe_put_fun_name("struct_#{to_string(field_name)}_get" |> String.to_atom()),
{:ok, fun_name} <- cpo |> cpo_resolve_fun_name,
{:ok, fun_args} <- cpo |> cpo_resolve_guard_fun_args(ctrl),
# save resolved for e.g. doc pattern
{:ok, cpo} <- cpo |> cpo_put_fun_key(field_tuples),
{:ok, cpo} <- cpo |> cpo_put_fun_args(fun_args),
true <- true do
pattern_form =
quote do
def unquote(fun_name)(unquote_splicing(fun_args))
def unquote(fun_name)(unquote(field_match), default) do
unquote(field_value_var)
|> case do
x when Plymio.Fontais.Guard.is_value_set(x) ->
{:ok, x}
_ ->
{:ok, default}
end
end
def unquote(fun_name)(state, _default) do
Plymio.Codi.Error.new_error_result(m: "struct invalid", v: state)
end
end
depend_args = [
{&cpo_has_fun_doc?/1, &DEPEND.cpo_transform_doc_depend/1},
{&cpo_has_since?/1, &DEPEND.cpo_transform_since_depend/1},
{&cpo_has_typespec_spec_opts?/1,
[
&DEPEND.cpo_transform_typespec_spec_depend/1,
{:cpo_maybe_put_typespec_spec_result, :result},
{:cpo_maybe_put_typespec_spec_args, [[:struct, :any]]}
]}
]
with {:ok, cpo} <- cpo |> cpo_done_with_edited_form(pattern_form),
{:ok, {depend_cpos, %CODI{}}} <- state |> DEPEND.create_depend_cpos(cpo, depend_args) do
cpos = depend_cpos ++ [cpo]
{:ok, {cpos, state}}
else
{:error, %{__exception__: true}} = result -> result
end
else
{:error, %{__exception__: true}} = result -> result
end
end
def express_pattern(%CODI{} = state, pattern, cpo)
when pattern == @plymio_codi_pattern_struct_fetch do
with {:ok, cpo} <- cpo |> cpo_pattern_struct_fetch_normalise,
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_arity(1),
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_args(:struct),
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_doc(pattern),
{:ok, ctrl} <- [] |> ctrl_put_fun_arity_value(1),
{:ok, ctrl} <- ctrl |> ctrl_put_fun_key_length(1),
{:ok, {{field_name, field_value_var}, field_vars, field_tuples, field_match}} <-
cpo
|> cpo_resolve_guard_field_match(ctrl),
{:ok, cpo} <-
cpo
|> cpo_maybe_put_fun_name("struct_#{to_string(field_name)}_fetch" |> String.to_atom()),
{:ok, fun_name} <- cpo |> cpo_resolve_fun_name,
{:ok, fun_args} <- cpo |> cpo_resolve_guard_fun_args(ctrl),
# save resolved for e.g. doc pattern
{:ok, cpo} <- cpo |> cpo_put_fun_key(field_tuples),
{:ok, cpo} <- cpo |> cpo_put_fun_args(fun_args),
true <- true do
pattern_form =
quote do
def unquote(fun_name)(unquote_splicing(fun_args))
def unquote(fun_name)(unquote(field_match) = state) do
unquote(field_value_var)
|> case do
x when Plymio.Fontais.Guard.is_value_set(x) ->
{:ok, x}
_ ->
Plymio.Codi.Error.new_error_result(
m: "struct field #{unquote(field_name)} unset",
v: state
)
end
end
def unquote(fun_name)(state) do
Plymio.Codi.Error.new_error_result(m: "struct invalid", v: state)
end
end
depend_args = [
{&cpo_has_fun_doc?/1, &DEPEND.cpo_transform_doc_depend/1},
{&cpo_has_since?/1, &DEPEND.cpo_transform_since_depend/1},
{&cpo_has_typespec_spec_opts?/1,
[
&DEPEND.cpo_transform_typespec_spec_depend/1,
{:cpo_maybe_put_typespec_spec_result, :result},
{:cpo_maybe_put_typespec_spec_args, :struct}
]}
]
with {:ok, cpo} <- cpo |> cpo_done_with_edited_form(pattern_form),
{:ok, {depend_cpos, %CODI{}}} <- state |> DEPEND.create_depend_cpos(cpo, depend_args) do
cpos = depend_cpos ++ [cpo]
{:ok, {cpos, state}}
else
{:error, %{__exception__: true}} = result -> result
end
else
{:error, %{__exception__: true}} = result -> result
end
end
def express_pattern(%CODI{} = state, pattern, cpo)
when pattern == @plymio_codi_pattern_struct_put do
with {:ok, cpo} <- cpo |> cpo_pattern_struct_put_normalise,
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_arity(2),
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_args([:struct, :value]),
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_doc(pattern),
{:ok, ctrl} <- [] |> ctrl_put_fun_arity_value(2),
{:ok, ctrl} <- ctrl |> ctrl_put_fun_key_length(1),
{:ok, ctrl} <- ctrl |> ctrl_put_fun_build_value(&CODIUTIL.field_build_anon_var/1),
{:ok, {{field_name, field_value_var}, field_vars, field_tuples, field_match}} <-
cpo
|> cpo_resolve_guard_field_match(ctrl),
{:ok, cpo} <-
cpo
|> cpo_maybe_put_fun_name("struct_#{to_string(field_name)}_put" |> String.to_atom()),
{:ok, fun_name} <- cpo |> cpo_resolve_fun_name,
{:ok, fun_args} <- cpo |> cpo_resolve_guard_fun_args(ctrl),
# save resolved for e.g. doc pattern
{:ok, cpo} <- cpo |> cpo_put_fun_key(field_tuples),
{:ok, cpo} <- cpo |> cpo_put_fun_args(fun_args),
true <- true do
pattern_form =
quote do
def unquote(fun_name)(unquote_splicing(fun_args))
def unquote(fun_name)(unquote(field_match) = state, value) do
{:ok, state |> struct!([{unquote(field_name), value}])}
end
def unquote(fun_name)(state, _value) do
Plymio.Codi.Error.new_error_result(m: "struct invalid", v: state)
end
end
depend_args = [
{&cpo_has_fun_doc?/1, &DEPEND.cpo_transform_doc_depend/1},
{&cpo_has_since?/1, &DEPEND.cpo_transform_since_depend/1},
{&cpo_has_typespec_spec_opts?/1,
[
&DEPEND.cpo_transform_typespec_spec_depend/1,
{:cpo_maybe_put_typespec_spec_result, :struct_result},
{:cpo_maybe_put_typespec_spec_args, [[:struct, :any]]}
]}
]
with {:ok, cpo} <- cpo |> cpo_done_with_edited_form(pattern_form),
{:ok, {depend_cpos, %CODI{}}} <- state |> DEPEND.create_depend_cpos(cpo, depend_args) do
cpos = depend_cpos ++ [cpo]
{:ok, {cpos, state}}
else
{:error, %{__exception__: true}} = result -> result
end
else
{:error, %{__exception__: true}} = result -> result
end
end
def express_pattern(%CODI{} = state, pattern, cpo)
when pattern == @plymio_codi_pattern_struct_maybe_put do
with {:ok, cpo} <- cpo |> cpo_pattern_struct_maybe_put_normalise,
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_arity(2),
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_args([:struct, :value]),
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_doc(pattern),
{:ok, ctrl} <- [] |> ctrl_put_fun_arity_value(2),
{:ok, ctrl} <- ctrl |> ctrl_put_fun_key_length(1),
{:ok, {{field_name, field_value_var}, field_vars, field_tuples, field_match}} <-
cpo
|> cpo_resolve_guard_field_match,
{:ok, cpo} <-
cpo
|> cpo_maybe_put_fun_name(
"struct_#{to_string(field_name)}_maybe_put"
|> String.to_atom()
),
{:ok, fun_name} <- cpo |> cpo_resolve_fun_name,
{:ok, fun_args} <- cpo |> cpo_resolve_guard_fun_args(ctrl),
# save resolved for e.g. doc pattern
{:ok, cpo} <- cpo |> cpo_put_fun_key(field_tuples),
{:ok, cpo} <- cpo |> cpo_put_fun_args(fun_args),
true <- true do
pattern_form =
quote do
def unquote(fun_name)(unquote_splicing(fun_args))
def unquote(fun_name)(unquote(field_match) = state, _value)
when Plymio.Fontais.Guard.is_value_set(unquote(field_value_var)) do
{:ok, state}
end
def unquote(fun_name)(unquote(field_match) = state, value)
when Plymio.Fontais.Guard.is_value_unset(unquote(field_value_var)) do
value
|> Plymio.Fontais.Guard.is_value_unset()
|> case do
true ->
{:ok, state}
_ ->
{:ok, state |> struct!([{unquote(field_name), value}])}
end
end
def unquote(fun_name)(state, _value) do
Plymio.Codi.Error.new_error_result(m: "struct invalid", v: state)
end
end
depend_args = [
{&cpo_has_fun_doc?/1, &DEPEND.cpo_transform_doc_depend/1},
{&cpo_has_since?/1, &DEPEND.cpo_transform_since_depend/1},
{&cpo_has_typespec_spec_opts?/1,
[
&DEPEND.cpo_transform_typespec_spec_depend/1,
{:cpo_maybe_put_typespec_spec_result, :struct_result},
{:cpo_maybe_put_typespec_spec_args, [[:struct, :any]]}
]}
]
with {:ok, cpo} <- cpo |> cpo_done_with_edited_form(pattern_form),
{:ok, {depend_cpos, %CODI{}}} <- state |> DEPEND.create_depend_cpos(cpo, depend_args) do
cpos = depend_cpos ++ [cpo]
{:ok, {cpos, state}}
else
{:error, %{__exception__: true}} = result -> result
end
else
{:error, %{__exception__: true}} = result -> result
end
end
def express_pattern(%CODI{} = state, pattern, cpo)
when pattern == @plymio_codi_pattern_struct_has? do
with {:ok, cpo} <- cpo |> cpo_pattern_struct_has_normalise,
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_arity(1),
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_args(:struct),
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_doc(pattern),
{:ok, ctrl} <- [] |> ctrl_put_fun_arity_value(1),
{:ok, ctrl} <- ctrl |> ctrl_put_fun_key_length(1),
{:ok, {{field_name, field_value_var}, field_vars, field_tuples, field_match}} <-
cpo
|> cpo_resolve_guard_field_match,
{:ok, cpo} <-
cpo
|> cpo_maybe_put_fun_name("struct_#{to_string(field_name)}_has?" |> String.to_atom()),
{:ok, fun_name} <- cpo |> cpo_resolve_fun_name,
{:ok, fun_args} <- cpo |> cpo_resolve_guard_fun_args(ctrl),
# save resolved for e.g. doc pattern
{:ok, cpo} <- cpo |> cpo_put_fun_key(field_tuples),
{:ok, cpo} <- cpo |> cpo_put_fun_args(fun_args),
true <- true do
pattern_form =
quote do
def unquote(fun_name)(unquote_splicing(fun_args))
def unquote(fun_name)(unquote(field_match))
when Plymio.Fontais.Guard.is_value_set(unquote(field_value_var)) do
true
end
def unquote(fun_name)(_state) do
false
end
end
depend_args = [
{&cpo_has_fun_doc?/1, &DEPEND.cpo_transform_doc_depend/1},
{&cpo_has_since?/1, &DEPEND.cpo_transform_since_depend/1},
{&cpo_has_typespec_spec_opts?/1,
[
&DEPEND.cpo_transform_typespec_spec_depend/1,
{:cpo_put_typespec_spec_result, :boolean},
{:cpo_maybe_put_typespec_spec_args, :struct}
]}
]
with {:ok, cpo} <- cpo |> cpo_done_with_edited_form(pattern_form),
{:ok, {depend_cpos, %CODI{}}} <- state |> DEPEND.create_depend_cpos(cpo, depend_args) do
cpos = depend_cpos ++ [cpo]
{:ok, {cpos, state}}
else
{:error, %{__exception__: true}} = result -> result
end
else
{:error, %{__exception__: true}} = result -> result
end
end
def express_pattern(%CODI{} = state, pattern, cpo)
when pattern == @plymio_codi_pattern_struct_update do
with {:ok, cpo} <- cpo |> cpo_pattern_struct_update_normalise,
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_arity(2),
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_args([:struct, :value]),
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_doc(pattern),
{:ok, ctrl} <- [] |> ctrl_put_fun_arity_value(2),
{:ok, ctrl} <- ctrl |> ctrl_put_fun_key_length(1),
{:ok, ctrl} <- ctrl |> ctrl_put_fun_build_value(&CODIUTIL.field_build_anon_var/1),
{:ok, {{field_name, field_value_var}, field_vars, field_tuples, field_match}} <-
cpo
|> cpo_resolve_guard_field_match(ctrl),
{:ok, cpo} <-
cpo
|> cpo_maybe_put_fun_name("struct_#{to_string(field_name)}_update" |> String.to_atom()),
{:ok, fun_name} <- cpo |> cpo_resolve_fun_name,
{:ok, fun_args} <- cpo |> cpo_resolve_guard_fun_args(ctrl),
# save resolved for e.g. doc pattern
{:ok, cpo} <- cpo |> cpo_put_fun_key(field_tuples),
{:ok, cpo} <- cpo |> cpo_put_fun_args(fun_args),
true <- true do
pattern_form =
quote do
def unquote(fun_name)(unquote_splicing(fun_args))
def unquote(fun_name)(unquote(field_match) = state, value) do
state |> update([{unquote(field_name), value}])
end
def unquote(fun_name)(state, _value) do
Plymio.Codi.Error.new_error_result(m: "struct invalid", v: state)
end
end
depend_args = [
{&cpo_has_fun_doc?/1, &DEPEND.cpo_transform_doc_depend/1},
{&cpo_has_since?/1, &DEPEND.cpo_transform_since_depend/1},
{&cpo_has_typespec_spec_opts?/1,
[
&DEPEND.cpo_transform_typespec_spec_depend/1,
{:cpo_maybe_put_typespec_spec_result, :struct_result},
{:cpo_maybe_put_typespec_spec_args, [[:struct, :keyword]]}
]}
]
with {:ok, cpo} <- cpo |> cpo_done_with_edited_form(pattern_form),
{:ok, {depend_cpos, %CODI{}}} <- state |> DEPEND.create_depend_cpos(cpo, depend_args) do
cpos = depend_cpos ++ [cpo]
{:ok, {cpos, state}}
else
{:error, %{__exception__: true}} = result -> result
end
else
{:error, %{__exception__: true}} = result -> result
end
end
def express_pattern(%CODI{} = state, pattern, cpo)
when pattern == @plymio_codi_pattern_struct_set do
with {:ok, cpo} <- cpo |> cpo_pattern_struct_set_normalise,
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_arity(1),
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_args([:struct]),
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_doc(pattern),
{:ok, ctrl} <- [] |> ctrl_put_fun_arity_value(1),
{:ok, ctrl} <- ctrl |> ctrl_put_fun_build_value(&CODIUTIL.field_build_anon_var/1),
{:ok, {{field_name, field_value_var}, field_vars, field_tuples, field_match}} <-
cpo
|> cpo_resolve_guard_field_match(ctrl),
{:ok, cpo} <-
cpo
|> cpo_maybe_put_fun_name("struct_#{to_string(field_name)}_set" |> String.to_atom()),
{:ok, fun_name} <- cpo |> cpo_resolve_fun_name,
{:ok, fun_args} <- cpo |> cpo_resolve_guard_fun_args(ctrl),
# save resolved for e.g. doc pattern
{:ok, cpo} <- cpo |> cpo_put_fun_key(field_tuples),
{:ok, cpo} <- cpo |> cpo_put_fun_args(fun_args),
true <- true do
pattern_form =
quote do
def unquote(fun_name)(unquote_splicing(fun_args))
def unquote(fun_name)(unquote(field_match) = state) do
{:ok, state |> struct!(unquote(field_tuples))}
end
def unquote(fun_name)(state) do
Plymio.Codi.Error.new_error_result(m: "struct invalid", v: state)
end
end
depend_args = [
{&cpo_has_fun_doc?/1, &DEPEND.cpo_transform_doc_depend/1},
{&cpo_has_since?/1, &DEPEND.cpo_transform_since_depend/1},
{&cpo_has_typespec_spec_opts?/1,
[
&DEPEND.cpo_transform_typespec_spec_depend/1,
{:cpo_maybe_put_typespec_spec_result, :struct_result},
{:cpo_maybe_put_typespec_spec_args, :struct}
]}
]
with {:ok, cpo} <- cpo |> cpo_done_with_edited_form(pattern_form),
{:ok, {depend_cpos, %CODI{}}} <- state |> DEPEND.create_depend_cpos(cpo, depend_args) do
cpos = depend_cpos ++ [cpo]
{:ok, {cpos, state}}
else
{:error, %{__exception__: true}} = result -> result
end
else
{:error, %{__exception__: true}} = result -> result
end
end
def express_pattern(%CODI{} = state, pattern, cpo)
when pattern == @plymio_codi_pattern_struct_export do
with {:ok, cpo} <- cpo |> cpo_pattern_struct_export_normalise,
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_arity(1),
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_args(:struct),
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_doc(pattern),
{:ok, cpo} <- cpo |> cpo_maybe_put_fun_name("struct_export" |> String.to_atom()),
{:ok, ctrl} <- [] |> ctrl_put_fun_arity_value(1),
{:ok, {_, field_vars, field_tuples, field_match}} <-
cpo |> cpo_resolve_guard_field_match(ctrl),
# saves resolved fields for e.g. doc pattern
{:ok, cpo} <- cpo |> cpo_put_fun_key(field_tuples),
{:ok, fun_name} <- cpo |> cpo_resolve_fun_name,
{:ok, fun_args} <- cpo |> cpo_resolve_guard_fun_args(ctrl) do
pattern_form =
quote do
def unquote(fun_name)(unquote_splicing(fun_args))
def unquote(fun_name)(unquote(field_match)) do
# must maintain order
tuples = unquote(field_tuples) ++ unquote(field_vars)
export =
tuples
|> Keyword.keys()
|> Stream.uniq()
|> Stream.map(fn k ->
tuples
|> Keyword.get_values(k)
# dop unset values
|> Enum.filter(fn v -> v |> Plymio.Fontais.Guard.is_value_set() end)
|> case do
# no set values => mark to drop in next stage
[] ->
{k, @plymio_fontais_the_unset_value}
# want last value
values ->
{k, values |> List.last()}
end
end)
# make the export sparse i.e. no unset values
|> Stream.filter(fn {_k, v} -> v |> Plymio.Fontais.Guard.is_value_set() end)
# take last tuple for each key
|> Keyword.new()
{:ok, export}
end
def unquote(fun_name)(state) do
Plymio.Codi.Error.new_error_result(m: "struct invalid", v: state)
end
end
:ok = pattern_form |> Macro.validate()
## pattern_form |> Code.eval_quoted([], __ENV__)
depend_args = [
{&cpo_has_fun_doc?/1, &DEPEND.cpo_transform_doc_depend/1},
{&cpo_has_since?/1, &DEPEND.cpo_transform_since_depend/1},
{&cpo_has_typespec_spec_opts?/1,
[
&DEPEND.cpo_transform_typespec_spec_depend/1,
{:cpo_put_typespec_spec_result, :opts_result},
{:cpo_maybe_put_typespec_spec_args, :struct}
]}
]
with {:ok, cpo} <- cpo |> cpo_done_with_edited_form(pattern_form),
{:ok, {depend_cpos, %CODI{}}} <- state |> DEPEND.create_depend_cpos(cpo, depend_args) do
cpos = depend_cpos ++ [cpo]
{:ok, {cpos, state}}
else
{:error, %{__exception__: true}} = result -> result
end
else
{:error, %{__exception__: true}} = result -> result
end
end
def express_pattern(_codi, pattern, opts) do
new_error_result(m: "proxy pattern #{inspect(pattern)} invalid", v: opts)
end
end