farmbot_os/farmbot_celery_script/lib/farmbot_celery_script/ast/unslicer.ex

79 lines
2.2 KiB
Elixir

defmodule Farmbot.CeleryScript.AST.Unslicer do
@moduledoc """
Turn an AST Heap back into an AST.
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.AST.Heap
@link Heap.link()
@parent Heap.parent()
@body Heap.body()
@next Heap.next()
@kind Heap.kind()
@typedoc "Ast with String Keys"
@type pre_ast :: map
@doc "Unslices a Heap struct back to cannonical celeryscript."
@spec run(Heap.t(), Address.t()) :: AST.t()
def run(%Heap{} = heap, %Address{} = addr) do
heap
|> unslice(addr)
|> AST.decode()
end
@spec unslice(Heap.t(), Address.t()) :: pre_ast
defp unslice(heap, addr) do
here_cell = heap[addr] || raise "No cell at address: #{inspect(addr)}"
Enum.reduce(here_cell, %{"args" => %{}}, fn {key, value}, acc ->
if is_link?(key) do
do_unslice(heap, key, value, acc)
else
%{acc | "args" => Map.put(acc["args"], to_string(key), value)}
end
end)
end
@spec do_unslice(Heap.t(), Heap.link(), any, acc :: map) :: acc :: map
defp do_unslice(_heap, @parent, _value, acc), do: acc
defp do_unslice(_heap, @next, _value, acc), do: acc
defp do_unslice(_heap, @kind, value, acc),
do: Map.put(acc, "kind", to_string(value))
defp do_unslice(heap, @body, value, acc) do
if heap[value][@kind] == :nothing do
acc
else
next_addr = value
n = heap[next_addr]
body = reduce_body(n, next_addr, heap, [])
Map.put(acc, "body", body)
end
end
defp do_unslice(heap, key, value, acc) do
key = String.replace(to_string(key), @link, "")
args = Map.put(acc["args"], key, unslice(heap, value))
%{acc | "args" => args}
end
@spec reduce_body(Heap.cell(), Address.t(), Heap.t(), [pre_ast]) :: [pre_ast]
defp reduce_body(%{__kind: :nothing}, _next_addr, _heap, acc),
do: acc
defp reduce_body(%{} = cell, %Address{} = next_addr, heap, acc) do
item = unslice(heap, next_addr)
new_acc = acc ++ [item]
next_addr = cell[@next]
next_cell = heap[next_addr]
reduce_body(next_cell, next_addr, heap, new_acc)
end
@spec is_link?(atom) :: boolean()
defp is_link?(key) do
String.starts_with?(to_string(key), @link)
end
end