Add new files
This commit is contained in:
parent
843aff99ca
commit
8120d1a416
96
lib/farmbot/celery_script/ast/heap.ex
Normal file
96
lib/farmbot/celery_script/ast/heap.ex
Normal file
|
@ -0,0 +1,96 @@
|
|||
defmodule Farmbot.CeleryScript.AST.Heap do
|
||||
@moduledoc """
|
||||
A heap-ish data structure required when converting canonical CeleryScript AST
|
||||
nodes into the Flat IR form.
|
||||
This data structure is useful because it addresses each node in the
|
||||
CeleryScript tree via a unique numerical index, rather than using mutable
|
||||
references.
|
||||
MORE INFO: https://github.com/FarmBot-Labs/Celery-Slicer
|
||||
"""
|
||||
alias Farmbot.CeleryScript.AST
|
||||
alias AST.Heap
|
||||
|
||||
defmodule Address do
|
||||
@moduledoc "Address on the heap."
|
||||
|
||||
defstruct [:value]
|
||||
|
||||
@doc "New heap address."
|
||||
def new(num) when is_integer(num) do
|
||||
%__MODULE__{value: num}
|
||||
end
|
||||
|
||||
@doc "Increment an address."
|
||||
def inc(%__MODULE__{value: num}) do
|
||||
%__MODULE__{value: num + 1}
|
||||
end
|
||||
|
||||
@doc "Decrement an address."
|
||||
def dec(%__MODULE__{value: num}) do
|
||||
%__MODULE__{value: num - 1}
|
||||
end
|
||||
|
||||
defimpl Inspect, for: __MODULE__ do
|
||||
def inspect(%{value: val}, _) do
|
||||
"Address(#{val})"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@link "__"
|
||||
@parent String.to_atom(@link <> "parent" <> @link)
|
||||
@body String.to_atom(@link <> "body" <> @link)
|
||||
@next String.to_atom(@link <> "next" <> @link)
|
||||
@kind :__kind__
|
||||
|
||||
@primary_fields [@parent, @body, @kind, @next]
|
||||
|
||||
@null Address.new(0)
|
||||
@nothing %{
|
||||
@kind => AST.Node.Nothing,
|
||||
@parent => @null,
|
||||
@body => @null,
|
||||
@next => @null
|
||||
}
|
||||
|
||||
defstruct [:entries, :here]
|
||||
|
||||
def new do
|
||||
%{struct(Heap) | here: @null, entries: %{@null => @nothing}}
|
||||
end
|
||||
|
||||
def alot(%Heap{} = heap, kind) do
|
||||
here_plus_one = Address.inc(heap.here)
|
||||
new_entries = Map.put(heap.entries, here_plus_one, %{@kind => kind})
|
||||
%{heap | here: here_plus_one, entries: new_entries}
|
||||
end
|
||||
|
||||
def put(%Heap{here: addr} = heap, key, value) do
|
||||
put(heap, addr, key, value)
|
||||
end
|
||||
|
||||
def put(%Heap{} = heap, %Address{} = addr, key, value) do
|
||||
block = heap[addr] || raise "Bad node address: #{inspect addr}"
|
||||
new_block = Map.put(block, String.to_atom(to_string(key)), value)
|
||||
new_entries = Map.put(heap.entries, addr, new_block)
|
||||
%{heap | entries: new_entries}
|
||||
end
|
||||
|
||||
@doc "Gets the values of the heap entries."
|
||||
def values(%Heap{entries: entries}), do: Enum.map(entries, &elem(&1, 1))
|
||||
|
||||
@doc false
|
||||
def fetch(%Heap{} = heap, %Address{} = addr), do: Map.fetch(heap.entries, addr)
|
||||
|
||||
def link, do: @link
|
||||
def parent, do: @parent
|
||||
def body, do: @body
|
||||
def next, do: @next
|
||||
def kind, do: @kind
|
||||
def primary_fields, do: @primary_fields
|
||||
def null, do: @null
|
||||
|
||||
@compile inline: [
|
||||
link: 0, parent: 0, body: 0, next: 0, kind: 0, primary_fields: 0, null: 0
|
||||
]
|
||||
end
|
59
lib/farmbot/celery_script/ast/slicer.ex
Normal file
59
lib/farmbot/celery_script/ast/slicer.ex
Normal file
|
@ -0,0 +1,59 @@
|
|||
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
|
||||
alias AST.Heap.Address
|
||||
|
||||
def run(%AST{} = canonical) do
|
||||
Heap.new()
|
||||
|> allocate(canonical, Heap.null())
|
||||
|> Map.update(Heap.body(), Heap.null(), fn(x) -> Map.get(x, Heap.body()) end)
|
||||
|> Map.update(Heap.next(), Heap.null(), fn(x) -> Map.get(x, Heap.next()) end)
|
||||
|> Heap.values()
|
||||
end
|
||||
|
||||
def allocate(%Heap{} = heap, %AST{} = node, %Address{} = parent_addr) do
|
||||
heap
|
||||
|> Heap.alot(node.kind)
|
||||
|> Heap.put(Heap.parent(), parent_addr) # puts "here"
|
||||
|> iterate_over_body(node)
|
||||
|> iterate_over_args(node)
|
||||
end
|
||||
|
||||
def iterate_over_args(%Heap{here: %Address{} = parent_addr} = heap, %AST{} = canonical_node) 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)
|
||||
new_heap = heap |> allocate(another_node, parent_addr)
|
||||
Heap.put(new_heap, parent_addr, k, new_heap.here)
|
||||
val ->
|
||||
Heap.put(heap, parent_addr, key, val)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def iterate_over_body(%Heap{} = heap, %AST{} = canonical_node) do
|
||||
recurse_into_body(heap, canonical_node.body)
|
||||
end
|
||||
|
||||
def recurse_into_body(heap, body, index \\ 0)
|
||||
def recurse_into_body(%Heap{here: %Address{} = previous_address} = heap, [body_item | rest], index) do
|
||||
%Heap{here: my_heap_address} = heap = allocate(heap, body_item, previous_address)
|
||||
is_head? = index == 0
|
||||
prev_next_key = if is_head?, do: Heap.null(), else: my_heap_address
|
||||
prev_body_key = if is_head?, do: my_heap_address, else: Heap.null()
|
||||
heap
|
||||
|> Heap.put(previous_address, Heap.next(), prev_next_key)
|
||||
|> Heap.put(previous_address, Heap.body(), prev_body_key)
|
||||
|> recurse_into_body(rest, index + 1)
|
||||
end
|
||||
|
||||
def recurse_into_body(heap, [], _), do: heap
|
||||
end
|
7
test/farmbot/celery_script/ast/slicer_test.ex
Normal file
7
test/farmbot/celery_script/ast/slicer_test.ex
Normal file
|
@ -0,0 +1,7 @@
|
|||
defmodule Farmbot.CeleryScript.AST.SlicerTest do
|
||||
use ExUnit.Case
|
||||
alias Farmbot.CeleryScript.AST
|
||||
alias AST.Slicer
|
||||
|
||||
|
||||
end
|
Loading…
Reference in a new issue