farmbot_os/farmbot_celery_script/lib/farmbot_celery_script/ast/heap.ex

99 lines
2.8 KiB
Elixir

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.Heap
# Constants and key names.
@link "__"
@body String.to_atom(@link <> "body")
@next String.to_atom(@link <> "next")
@parent String.to_atom(@link <> "parent")
@kind String.to_atom(@link <> "kind")
@primary_fields [@parent, @body, @kind, @next]
@null Address.new(0)
@nothing %{
@kind => :nothing,
@parent => @null,
@body => @null,
@next => @null
}
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
defstruct [:entries, :here]
@type t :: %Heap{
entries: %{Address.t() => cell()},
here: here()
}
@type here :: Address.t()
@typedoc "this is actually an atom that starts with __"
@type link :: atom
@typedoc "individual heap entry."
@type cell :: %{
required(:__kind) => atom,
required(:__body) => Address.t(),
required(:__next) => Address.t(),
required(:__parent) => Address.t()
}
@doc "Initialize a new heap."
@spec new() :: t()
def new do
%{struct(Heap) | here: @null, entries: %{@null => @nothing}}
end
@doc "Alot a new kind on the heap. Increments `here` on the heap."
@spec alot(t(), atom) :: t()
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{heap | here: here_plus_one, entries: new_entries}
end
@doc "Puts a key/value pair at `here` on the heap."
@spec put(t(), any, any) :: t()
def put(%Heap{here: addr} = heap, key, value) do
put(heap, addr, key, value)
end
@doc "Puts a key/value pair at an arbitrary address on the heap."
@spec put(t(), Address.t(), any, any) :: t()
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."
@spec values(t()) :: %{Address.t() => cell()}
def values(%Heap{entries: entries}), do: entries
# Access behaviour.
@doc false
@spec fetch(t, Address.t()) :: {:ok, cell()}
def fetch(%Heap{} = heap, %Address{} = adr),
do: Map.fetch(Heap.values(heap), adr)
end