Current section

Files

Jump to
argx lib core parser.ex
Raw

lib/core/parser.ex

defmodule Argx.Parser do
@moduledoc false
import Argx.Util
alias Argx.Const
@allowed_fun_types Const.allowed_fun_types()
@not_support_types Const.not_support_types()
@allowed_types Const.allowed_types()
@names_key Const.names_key()
@configs_keyword Const.configs_keyword()
###
def parse_fun({:__block__, _, [_ | _] = block}), do: parse_fun(block, [])
def parse_fun(block), do: parse_fun([block], [])
def parse_fun([], funs), do: Enum.reverse(funs)
def parse_fun([expr | rest], funs) do
fun = do_parse_fun(expr, %{})
parse_fun(rest, [fun | funs])
end
defp do_parse_fun({type, _, fun}, parts) when type in @allowed_fun_types do
do_parse_fun(fun, parts)
end
defp do_parse_fun([{:when, _, fa_guard}, [{:do, block}]], parts) do
fa_guard
|> do_parse_fun(parts)
|> Map.put(:block, block)
end
defp do_parse_fun([fa, [{:do, block}]], parts) do
fa
|> do_parse_fun(parts)
|> Map.put(:block, block)
|> Map.put(:guard, true)
end
defp do_parse_fun([fa, guard], parts) do
fa
|> do_parse_fun(parts)
|> Map.put(:guard, guard)
end
defp do_parse_fun({f, _, [{_, _, _} | _] = a}, parts) when f not in @not_support_types do
parts
|> Map.put(:f, f)
|> Map.put(:a, a)
end
defp do_parse_fun({f, _, _}, parts) when f not in @not_support_types do
parts
|> Map.put(:f, f)
|> Map.put(:a, [])
end
defp do_parse_fun({type, _, [_ | _]}, _parts), do: raise(Argx.Error, "not support #{type}")
defp do_parse_fun(_other_expr, _parts), do: raise(Argx.Error, "unknown function")
###
def parse_configs({name, _, _} = configs) when name != @configs_keyword do
do_parse_configs([configs], %{})
end
def parse_configs([_ | _] = configs), do: do_parse_configs(configs, %{})
def parse_configs({@configs_keyword, _, [_ | _] = configs}), do: do_parse_configs(configs, %{})
def parse_configs({@configs_keyword, _, []}), do: raise(Argx.Error, "configs is empty")
def parse_configs([]), do: raise(Argx.Error, "configs is empty")
defp do_parse_configs([], configs) do
configs
|> Map.get(@names_key)
|> is_nil()
|> if(
do: configs,
else:
(
{_, new_configs} =
Map.get_and_update(configs, @names_key, fn curr ->
new_value = curr && MapSet.to_list(curr)
{curr, new_value}
end)
new_configs
)
)
end
defp do_parse_configs([{:__aliases__, _, [defconfig_name]} | rest], configs)
when is_atom(defconfig_name) do
new_configs = reduce_defconfig_names(configs, @names_key, defconfig_name)
do_parse_configs(rest, new_configs)
end
defp do_parse_configs([defconfig_name | rest], configs)
when is_bitstring(defconfig_name) or is_atom(defconfig_name) do
new_configs = reduce_defconfig_names(configs, @names_key, defconfig_name)
do_parse_configs(rest, new_configs)
end
defp do_parse_configs([config | rest], configs) do
config_map = every_config(config)
new_configs = Map.merge(configs, config_map)
do_parse_configs(rest, new_configs)
end
defp reduce_defconfig_names(%{} = configs, names_key, name) do
update = fn configs, names_key, name ->
Map.get_and_update(configs, names_key, fn curr ->
new_value = (curr && MapSet.put(curr, name)) || MapSet.new([name])
{curr, new_value}
end)
end
with name <- prune_names(name),
{_, new_configs} <- update.(configs, names_key, name) do
new_configs
end
end
defp every_config({:||, _, [{field, _, items}, default]}) do
do_every_config(field, items, default)
end
defp every_config({field, _, items}), do: do_every_config(field, items)
defp do_every_config(field, items, default \\ nil) do
config = %Argx.Config{
auto: false,
optional: false,
type: nil,
range: nil,
default: default,
empty: false,
nested: nil
}
items = every_item(items, config)
Map.put(%{}, field, items)
end
defp every_item([], items), do: items
defp every_item([type | rest], items) when type in @allowed_types do
every_item(
rest,
Map.put(items, :type, type)
)
end
defp every_item([{type, {:__aliases__, _, [nested_name]}} | rest], items)
when type in [:list, :map] do
with name <- prune_names(nested_name),
items <- put_nested_name(items, type, name) do
every_item(rest, items)
end
end
defp every_item([{type, nested_name} | rest], items)
when type in [:list, :map] and (is_bitstring(nested_name) or is_atom(nested_name)) do
with name <- prune_names(nested_name),
items <- put_nested_name(items, type, name) do
every_item(rest, items)
end
end
defp every_item([:optional | rest], items) do
every_item(
rest,
Map.put(items, :optional, true)
)
end
defp every_item([:auto | rest], items) do
every_item(
rest,
Map.put(items, :auto, true)
)
end
defp every_item([:empty | rest], items) do
every_item(
rest,
Map.put(items, :empty, true)
)
end
defp every_item([{:.., _, [_, _]} = range | rest], items) do
every_item(
rest,
Map.put(items, :range, range)
)
end
defp every_item([range | rest], items) when is_integer(range) do
every_item(
rest,
Map.put(items, :range, range)
)
end
defp every_item([other_expr | _rest], _items) do
raise(Argx.Error, "unknown #{inspect(other_expr)}")
end
defp put_nested_name(items, type, nested_name) do
items
|> Map.put(:type, type)
|> Map.put(:nested, nested_name)
end
###
def parse_defconfig_name(name) when is_atom(name), do: name
def parse_defconfig_name(name) when is_bitstring(name), do: String.to_atom(name)
def parse_defconfig_name({:__aliases__, _, [_ | _] = parts}) do
parts
|> Enum.map(fn part -> to_string(part) end)
|> Enum.join("_")
|> String.to_atom()
end
def parse_defconfig_name(_other), do: raise(Argx.Error, "defconfig name should be atom/string")
###
def parse_range(v) when is_number(v), do: [v, v]
def parse_range({:.., _, [l, r]}), do: [l, r]
def parse_range(other), do: raise(Argx.Error, "not support #{inspect(other)}")
end