Add new files

pull/446/head
connor rigby 2018-02-07 11:11:08 -08:00 committed by Connor Rigby
parent 843aff99ca
commit 8120d1a416
3 changed files with 162 additions and 0 deletions

View 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

View 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

View File

@ -0,0 +1,7 @@
defmodule Farmbot.CeleryScript.AST.SlicerTest do
use ExUnit.Case
alias Farmbot.CeleryScript.AST
alias AST.Slicer
end