Farmbot-Web-App/app/lib/celery_script/cs_heap.rb

154 lines
4.7 KiB
Ruby

# 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. This is very important when:
# * You are using a (functional) language that does not like mutable refs
# * You are storing data in a database that does not like
# trees (SQL, not Mongo)
# * You need to create "traces" of where you are in a sequence (using numbers)
# MORE INFO: https://github.com/FarmBot-Labs/Celery-Slicer
module CeleryScript
# Supporting class for CsHeap (below this class)
# PROBLEM: CsHeap uses numbers to address sibling/parent nodes.
# PROBLEM: Numbers are very easy to mix up. Is it an array index? A SQL
# primary key? A primitive value? It's not always easy to say.
# SOLUTION: Create a `HeapAddress` value type to remove ambiguity.
# Prevents confusion between index IDs and SQL IDs.
class HeapAddress
attr_reader :value
def initialize(value)
raise "BAD INPUT" unless value.is_a?(Integer)
@value = value.to_i
end
def self.[](value)
return self.new(value)
end
def inspect
"HeapAddress(#{value})"
end
def hash
self.value
end
def ==(other)
self.value == other.try(:value)
end
def eql?(other)
self.value == other.value
end
def +(val)
HeapAddress[@value + 1]
end
def is_address?
true
end
def to_i
@value
end
def to_s
@value.to_s
end
end
class CsHeap
class BadAddress < Exception; end
BAD_ADDR = "Bad node address: "
# Nodes that point to other nodes rather than primitive data types (eg:
# `locals` and friends) will be prepended with a LINK.
LINK = "__"
# Points to the originator (parent) of an `arg` or `body` node.
PARENT = (LINK + "parent").to_sym
# Points to the first element in the `body``
BODY = (LINK + "body").to_sym
# Points to the next node in the body chain. Pointing to NOTHING indicates
# the end of the body linked list.
NEXT = (LINK + "next").to_sym
# Unique key name. See `celery_script_settings_bag.rb`
KIND = :__KIND__
COMMENT = :__COMMENT__
# Keys that primary nodes must have
PRIMARY_FIELDS = [PARENT, BODY, KIND, NEXT, COMMENT]
# Index 0 of the heap represents a null pointer of sorts.
# If a field points to this address, it is considered empty.
NULL = HeapAddress[0]
# What you will find at index 0 of the heap:
NOTHING = {
KIND => "nothing",
PARENT => NULL,
BODY => NULL,
NEXT => NULL,
}
# A dictionary of nodes in the CeleryScript tree, as stored in the heap.
# Nodes will have:
# * A `KIND` field - What kind of node is it?
# `send_message`, `move_rel`, etc..
# * A `PARENT` field - The node directly above the current node.
# * A `BODY` field - If a node has a body member (and it might not!),
# this field will point to the first node in the
# chain. NOTHING pointer indicates that the node has
# no body.
# * A `NEXT` field - If you are inside a node's BODY, the next node in
# chain is denoted by the address of NEXT. A NEXT
# value of NOTHING means you hit the end of the chain
attr_accessor :entries
# "here" represents the last item added to the heap and, often, the item that
# is currently being edited.
attr_accessor :here
# Set "here" to "null". Prepopulates "here" with an empty entry.
def initialize
@here = NULL
@entries = { @here => NOTHING }
end
# Grow the heap and fill it was a CS node of type `__KIND__`.
# Returns the new value of `@here` after expansion.
# "Create a new empty heap object and return its address for access later"
def allot(__KIND__)
entries[@here += 1] = { KIND => __KIND__ }
return @here
end
# augment a heap entry with a new key/value pair.
# Throws an exception when given a bad heap index.
# "Put this VALUE into this ADDRESS and annotate it with the KEY provided"
def put(address, key, value)
address.is_address?
block = entries[address]
if (block)
block[key.to_sym] = value
return
else
raise BadAddress, BAD_ADDR + address.inspect
end
end
# Just an alias
def values
entries.values
end
# Dump the heap as an easy-to-traverse JSON object.
# We need this to reconstruct the node from its IR form to its canonical form.
def dump
return values
end
end
end