116 lines
3.2 KiB
Elixir
116 lines
3.2 KiB
Elixir
defmodule Farmbot.CeleryScript.AST.Slicer do
|
|
@moduledoc """
|
|
ORIGINAL IMPLEMENTATION HERE: https://github.com/FarmBot-Labs/Celery-Slicer
|
|
Take a nested ("canonical") representation of a CeleryScript sequence and
|
|
transofrms it to a flat/homogenous intermediate representation which is better
|
|
suited for storage in a relation database.
|
|
"""
|
|
alias Farmbot.CeleryScript.AST
|
|
alias AST.Heap
|
|
|
|
@doc "Sice the canonical AST format into a AST Heap."
|
|
@spec run(AST.t()) :: Heap.t()
|
|
def run(canonical)
|
|
|
|
def run(%AST{} = canonical) do
|
|
Heap.new()
|
|
|> allocate(canonical, Heap.null())
|
|
|> elem(1)
|
|
|> Map.update(:entries, :error, fn entries ->
|
|
Map.new(entries, fn {key, entry} ->
|
|
entry =
|
|
Map.put(
|
|
entry,
|
|
Heap.body(),
|
|
Map.get(entry, Heap.body(), Heap.null())
|
|
)
|
|
|
|
entry =
|
|
Map.put(
|
|
entry,
|
|
Heap.next(),
|
|
Map.get(entry, Heap.next(), Heap.null())
|
|
)
|
|
|
|
{key, entry}
|
|
end)
|
|
end)
|
|
end
|
|
|
|
@doc false
|
|
@spec allocate(Heap.t(), AST.t(), Address.t()) :: {Heap.here(), Heap.t()}
|
|
def allocate(%Heap{} = heap, %AST{} = ast, %Address{} = parent_addr) do
|
|
%Heap{here: addr} = heap = Heap.alot(heap, ast.kind)
|
|
|
|
new_heap =
|
|
Heap.put(heap, Heap.parent(), parent_addr)
|
|
|> iterate_over_body(ast, addr)
|
|
|> iterate_over_args(ast, addr)
|
|
|
|
{addr, new_heap}
|
|
end
|
|
|
|
@spec iterate_over_args(Heap.t(), AST.t(), Address.t()) :: Heap.t()
|
|
defp iterate_over_args(
|
|
%Heap{} = heap,
|
|
%AST{} = canonical_node,
|
|
parent_addr
|
|
) do
|
|
keys = Map.keys(canonical_node.args)
|
|
|
|
Enum.reduce(keys, heap, fn key, %Heap{} = heap ->
|
|
case canonical_node.args[key] do
|
|
%AST{} = another_node ->
|
|
k = Heap.link() <> to_string(key)
|
|
{addr, new_heap} = allocate(heap, another_node, parent_addr)
|
|
Heap.put(new_heap, parent_addr, k, addr)
|
|
|
|
val ->
|
|
Heap.put(heap, parent_addr, key, val)
|
|
end
|
|
end)
|
|
end
|
|
|
|
@spec iterate_over_body(Heap.t(), AST.t(), Address.t()) :: Heap.t()
|
|
defp iterate_over_body(
|
|
%Heap{} = heap,
|
|
%AST{} = canonical_node,
|
|
parent_addr
|
|
) do
|
|
recurse_into_body(heap, canonical_node.body, parent_addr)
|
|
end
|
|
|
|
@spec recurse_into_body(Heap.t(), [AST.t()], Address.t(), integer) :: Heap.t()
|
|
defp recurse_into_body(heap, body, parent_addr, index \\ 0)
|
|
|
|
defp recurse_into_body(
|
|
%Heap{} = heap,
|
|
[body_item | rest],
|
|
prev_addr,
|
|
0
|
|
) do
|
|
{my_heap_address, %Heap{} = new_heap} =
|
|
heap
|
|
|> Heap.put(prev_addr, Heap.body(), Address.inc(prev_addr))
|
|
|> allocate(body_item, prev_addr)
|
|
|
|
new_heap
|
|
|> Heap.put(prev_addr, Heap.next(), Heap.null())
|
|
|> recurse_into_body(rest, my_heap_address, 1)
|
|
end
|
|
|
|
defp recurse_into_body(
|
|
%Heap{} = heap,
|
|
[body_item | rest],
|
|
prev_addr,
|
|
index
|
|
) do
|
|
{my_heap_address, %Heap{} = heap} = allocate(heap, body_item, prev_addr)
|
|
|
|
new_heap = Heap.put(heap, prev_addr, Heap.next(), my_heap_address)
|
|
recurse_into_body(new_heap, rest, my_heap_address, index + 1)
|
|
end
|
|
|
|
defp recurse_into_body(%Heap{} = heap, [], _, _), do: heap
|
|
end
|