Packages
spark
1.1.17
2.7.2
2.7.1
2.7.0
2.6.1
2.6.0
2.5.0
2.4.1
2.4.0
2.3.14
2.3.13
2.3.12
2.3.11
2.3.10
2.3.9
2.3.8
2.3.7
2.3.6
2.3.5
2.3.4
2.3.3
2.3.2
2.3.1
2.3.0
2.2.69
2.2.68
2.2.67
2.2.66
2.2.65
2.2.64
2.2.63
2.2.62
2.2.61
2.2.60
2.2.59
2.2.58
2.2.57
2.2.56
2.2.55
2.2.54
2.2.53
2.2.52
2.2.51
2.2.50
2.2.49
2.2.48
2.2.47
2.2.46
2.2.45
2.2.44
2.2.43
2.2.42
2.2.41
2.2.40
2.2.39
2.2.38
2.2.37
2.2.36
2.2.35
2.2.34
2.2.33
2.2.32
2.2.31
2.2.30
2.2.29
2.2.28
2.2.27
2.2.26
2.2.25
2.2.24
2.2.23
2.2.22
2.2.21
2.2.20
2.2.19
2.2.18
2.2.17
2.2.16
2.2.15
2.2.14
2.2.13
2.2.12
2.2.11
2.2.10
2.2.9
2.2.8
2.2.7
2.2.6
2.2.5
2.2.4
2.2.3
2.2.2
2.2.1
2.2.0
2.1.24
2.1.23
2.1.22
2.1.21
2.1.20
2.1.19
2.1.18
2.1.17
2.1.16
2.1.15
2.1.14
2.1.13
2.1.12
2.1.11
2.1.10
2.1.9
2.1.8
2.1.7
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.0.1
2.0.0
1.1.55
1.1.54
1.1.53
1.1.52
1.1.51
1.1.50
1.1.49
1.1.48
1.1.47
1.1.46
1.1.45
1.1.44
1.1.43
1.1.42
1.1.41
1.1.40
1.1.39
1.1.38
1.1.37
1.1.36
1.1.35
1.1.34
1.1.32
1.1.31
1.1.30
1.1.29
1.1.28
1.1.27
1.1.26
1.1.25
1.1.24
1.1.22
1.1.21
1.1.20
1.1.19
1.1.18
1.1.17
1.1.16
1.1.15
1.1.14
retired
1.1.13
1.1.12
1.1.11
1.1.10
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.18
0.2.17
0.2.16
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.29
0.1.28
0.1.27
0.1.26
0.1.25
0.1.24
0.1.23
0.1.22
0.1.21
0.1.20
0.1.19
0.1.18
0.1.17
0.1.16
0.1.15
0.1.14
0.1.13
0.1.12
0.1.11
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
Generic tooling for building DSLs
Current section
Files
Jump to
Current section
Files
lib/spark/code_helpers.ex
defmodule Spark.CodeHelpers do
@moduledoc """
Helpers for meta programming around code and code snippets
"""
@doc """
Given a section of Elixir AST, generate a hash of the code to help with
generating unique names.
"""
@spec code_identifier(Macro.t()) :: binary
def code_identifier(code) do
code
|> strip_meta()
|> :erlang.term_to_binary()
|> :erlang.md5()
|> Base.encode16()
end
@doc false
@spec strip_meta(Macro.t()) :: Macro.t()
def strip_meta(code) do
Macro.prewalk(code, fn
{foo, _, bar} when is_atom(foo) and is_atom(bar) ->
{foo, [], nil}
{foo, _, bar} ->
{foo, [], bar}
other ->
other
end)
end
@doc """
Lift anonymous and captured functions.
Acts as an AST transformer to allow these kinds of functions to be added in
the AST:
In the case of captured functions, it ensures they are all captured remote
functions (ie calls with both the module and function name present) - this
often requires the definition of a new public function on the target module.
In the case of anonymous functions, it converts them into a new public
function on the module and returns a (remote) function capture much like that
of above.
"""
@spec lift_functions(Macro.t(), atom, Macro.Env.t()) :: Macro.t()
# (Don't) lift functions of the `&Module.function/arity` format
def lift_functions({:&, _, [{:/, _, [{{:., _, _}, _, _}, _]}]} = value, _key, _caller),
do: {value, nil}
# Lift functions of the `&function/arity` variety
def lift_functions({:&, context1, [{:/, context2, [{_, _, _}, arity]}]} = value, key, caller)
when is_integer(arity) do
fn_args = Macro.generate_unique_arguments(arity, caller.module)
fn_name = generate_unique_function_name(value, key)
function = generate_captured_function_caller(fn_name, arity, caller, context1, context2)
{function,
quote generated: true do
unless Module.defines?(__MODULE__, {unquote(fn_name), unquote(Enum.count(fn_args))}, :def) do
@doc false
def unquote(fn_name)(unquote_splicing(fn_args)) do
unquote(value).(unquote_splicing(fn_args))
end
end
end}
end
def lift_functions({:&, _, [{name, _, fn_args}]} = value, key, caller)
when is_atom(name) and name != :& do
fn_args = generate_captured_arguments(fn_args, caller)
fn_name = generate_unique_function_name(value, key)
function = generate_captured_function_caller(fn_name, fn_args, caller)
{function,
quote generated: true do
unless Module.defines?(__MODULE__, {unquote(fn_name), unquote(Enum.count(fn_args))}, :def) do
@doc false
def unquote(fn_name)(unquote_splicing(fn_args)) do
unquote(value).(unquote_splicing(fn_args))
end
end
end}
end
# Lift functions of the `&Module.function(&1, args)` variety
def lift_functions(
{:&, _, [{{:., _, [{:__aliases__, _, _aliases}, name]}, _, fn_args}]} = value,
key,
caller
)
when is_atom(name) do
fn_args = generate_captured_arguments(fn_args, caller)
fn_name = generate_unique_function_name(value, key)
function = generate_captured_function_caller(fn_name, fn_args, caller)
{function,
quote generated: true do
unless Module.defines?(__MODULE__, {unquote(fn_name), unquote(Enum.count(fn_args))}, :def) do
@doc false
def unquote(fn_name)(unquote_splicing(fn_args)) do
unquote(value).(unquote_splicing(fn_args))
end
end
end}
end
# Lift functions of the `&(&1 + &2)` variety
def lift_functions({:&, _, [body]} = value, key, caller) do
fn_args = generate_captured_arguments(body, caller)
fn_name = generate_unique_function_name(value, key)
function = generate_captured_function_caller(fn_name, fn_args, caller)
{function,
quote generated: true do
unless Module.defines?(__MODULE__, {unquote(fn_name), unquote(Enum.count(fn_args))}, :def) do
@doc false
def unquote(fn_name)(unquote_splicing(fn_args)) do
unquote(value).(unquote_splicing(fn_args))
end
end
end}
end
# Lift anonymous functions with one or more clauses.
def lift_functions(
{:fn, _, [{:->, _, [fn_args, _body]} | _] = clauses} = quoted_fn,
key,
caller
)
when is_list(fn_args) do
fn_name = generate_unique_function_name(quoted_fn, key)
function = generate_captured_function_caller(fn_name, fn_args, caller)
function_defs =
for {:->, _, [args, body]} <- clauses do
quote do
def unquote(fn_name)(unquote_splicing(args)) do
unquote(body)
end
end
end
{function,
quote generated: true do
unless Module.defines?(__MODULE__, {unquote(fn_name), unquote(Enum.count(fn_args))}, :def) do
@doc false
unquote_splicing(function_defs)
end
end}
end
# Ignore all other values.
def lift_functions(value, _key, _caller), do: {value, nil}
# sobelow_skip ["DOS.BinToAtom"]
defp generate_unique_function_name(value, key) do
fn_name = Spark.CodeHelpers.code_identifier(value)
:"#{key}_#{Spark.Dsl.Extension.monotonic_number({key, fn_name})}_generated_#{fn_name}"
end
# Counts up all the arguments and generates new unique arguments for them.
# Works around the caveat that each usage of a unique `&n` variable must only
# be counted once.
defp generate_captured_arguments(args, caller) do
Macro.prewalk(args, [], fn
{:&, _, [v]} = ast, acc when is_integer(v) ->
{ast, [v | acc]}
ast, acc ->
{ast, acc}
end)
|> elem(1)
|> Enum.uniq()
|> Enum.count()
|> Macro.generate_unique_arguments(caller.module)
end
# Generates the code for calling the target function as a function capture.
defp generate_captured_function_caller(
fn_name,
arity,
caller,
context1 \\ [],
context2 \\ [context: Elixir, imports: [{2, Kernel}]]
)
defp generate_captured_function_caller(fn_name, arity, caller, context1, context2)
when is_integer(arity) do
{:&, context1,
[
{:/, context2,
[
{{:., [], [{:__aliases__, [alias: false], [caller.module]}, fn_name]},
[no_parens: true], []},
arity
]}
]}
end
defp generate_captured_function_caller(fn_name, fn_args, caller, context1, context2),
do:
generate_captured_function_caller(
fn_name,
Enum.count(fn_args),
caller,
context1,
context2
)
end