farmbot_os/farmbot_celery_script/lib/farmbot_celery_script/ast.ex

92 lines
2.3 KiB
Elixir

defmodule FarmbotCeleryScript.AST do
@moduledoc """
Handy functions for turning various data types into Farbot Celery Script
Ast nodes.
"""
alias FarmbotCeleryScript.AST
@typedoc "Arguments to a ast node."
@type args :: map
@typedoc "Body of a ast node."
@type body :: [t]
@typedoc "Kind of a ast node."
@type kind :: module
@typedoc "AST node."
@type t :: %__MODULE__{
kind: kind,
args: args,
body: body,
comment: binary,
meta: any()
}
defstruct [:args, :body, :kind, :comment, :meta]
@doc "Decode a base map into CeleryScript AST."
@spec decode(t() | map | [t() | map]) :: t()
def decode(map_or_list_of_maps)
def decode(list) when is_list(list) do
decode_body(list)
end
def decode(%{__struct__: _} = thing) do
thing |> Map.from_struct() |> decode()
end
def decode(%{} = thing) do
kind = thing["kind"] || thing[:kind] || raise("Bad ast: #{inspect(thing)}")
args = thing["args"] || thing[:args] || raise("Bad ast: #{inspect(thing)}")
body = thing["body"] || thing[:body] || []
comment = thing["comment"] || thing[:comment] || nil
meta = thing["meta"] || thing[:meta] || nil
%AST{
kind: String.to_atom(to_string(kind)),
args: decode_args(args),
body: decode_body(body),
comment: comment,
meta: meta
}
end
def decode(bad_ast), do: raise("Bad ast: #{inspect(bad_ast)}")
# You can give a list of nodes.
@spec decode_body([map]) :: [t()]
def decode_body(body) when is_list(body) do
Enum.map(body, fn itm ->
decode(itm)
end)
end
@spec decode_args(map) :: args
def decode_args(map) when is_map(map) do
Enum.reduce(map, %{}, fn {key, val}, acc ->
if is_map(val) do
# if it is a map, it could be another node so decode it too.
real_val = decode(val)
Map.put(acc, String.to_atom(to_string(key)), real_val)
else
Map.put(acc, String.to_atom(to_string(key)), val)
end
end)
end
@spec new(atom, map, [map]) :: t()
def new(kind, args, body, comment \\ nil, meta \\ nil)
when is_map(args) and is_list(body) do
%AST{
kind: String.to_atom(to_string(kind)),
args: args,
body: body,
comment: comment,
meta: meta
}
|> decode()
end
end