Current section
Files
Jump to
Current section
Files
lib/pushstate.ex
defmodule Elixush.PushState do
import Enum, only: [empty?: 1]
import Elixush.Globals.Agent
@doc "Returns an empty push state."
def make_push_state do
%{exec: [],
code: [],
integer: [],
float: [],
boolean: [],
char: [],
string: [],
zip: [],
vector_integer: [],
vector_float: [],
vector_boolean: [],
vector_string: [],
input: [],
output: [],
auxiliary: [],
tag: [],
return: [],
environment: [],
genome: []
}
end
@doc "Add the provided name to the global list of registered instructions."
def register_instruction(name) do
if MapSet.member?(get_globals(:registered_instructions), name) do
raise(ArgumentError, message: "Duplicate Push instruction defined: #{name}")
else
update_globals(:registered_instructions, MapSet.put(get_globals(:registered_instructions), name))
end
end
def define_registered(instruction, definition) do
register_instruction(instruction)
old_instruction_table = get_globals(:instruction_table)
new_instruction_table = Map.put(old_instruction_table, instruction, definition)
update_globals(:instruction_table, new_instruction_table)
end
def state_pretty_print(state) do
Enum.each(get_globals(:push_types), fn(t) ->
IO.puts "#{t} = #{Macro.to_string(Map.get(state, t))}"
end)
end
@doc """
Returns a copy of the state with the value pushed on the named stack. This is a utility,
not for use in Push programs.
"""
def push_item(value, type, state) do
Map.put(state, type, List.insert_at(state[type], 0, value))
end
@doc """
Returns the top item of the type stack in state. Returns :no-stack-item if called on
an empty stack. This is a utility, not for use as an instruction in Push programs.
"""
def top_item(type, state) do
stack = state[type]
if empty?(stack), do: :no_stack_item, else: List.first(stack)
end
@doc """
Returns the indicated item of the type stack in state. Returns :no-stack-item if called
on an empty stack. This is a utility, not for use as an instruction in Push programs.
NOT SAFE for invalid positions.
"""
@spec stack_ref(atom, integer, map) :: any
def stack_ref(type, position, state) do
stack = Map.get(state, type)
if empty?(stack), do: :no_stack_item, else: Enum.at(stack, position)
end
@doc """
Puts value at position on type stack in state. This is a utility, not for use
as an instruction in Push programs. NOT SAFE for invalid positions.
"""
def stack_assoc(value, type, position, state) do
stack = Map.get(state, type)
new_stack = List.insert_at(stack, position, value)
Map.put(state, type, new_stack)
end
@doc """
Returns a copy of the state with the specified stack popped. This is a utility,
not for use as an instruction in Push programs.
"""
@spec pop_item(atom, map) :: map
def pop_item(type, state) do
Map.put(state, type, state |> Map.get(type) |> tl)
end
@doc """
Ends the current environment by popping the :environment stack and replacing
all stacks with those on the environment stack. Then, everything on the old
:return stack is pushed onto the :exec stack.
"""
@spec end_environment(map) :: map
def end_environment(state) do
new_env = top_item(:environment, state)
new_exec = Enum.concat(Map.get(state, :exec), Map.get(new_env, :exec))
# HACK: anonymous functions used to emulate loop/recur from clojure
loop = fn
(_f, old_return, new_state) when hd(old_return) -> new_state
(f, old_return, new_state) -> f.(f, tl(old_return), push_item(hd(old_return), :exec, new_state))
end
loop.(loop, Map.get(state, :return), Map.merge(new_env, %{:exec => new_exec, :auxiliary => Map.get(state, :auxiliary)}))
end
@doc "Returns a list of all registered instructions with the given type name as a prefix."
@spec registered_for_type(atom, map) :: Enum.t
def registered_for_type(type, argmap \\ %{}) do
include_randoms = Map.get(argmap, :include_randoms, true)
for_type = Enum.filter(get_globals(:registered_instructions), &(String.starts_with?(Atom.to_string(&1), Atom.to_string(type))))
if include_randoms do
for_type
else
Enum.filter(for_type, &(not(String.ends_with?(Atom.to_string(&1), "_rand"))))
end
end
@doc "Returns a list of all registered instructions aside from random instructions."
@spec registered_nonrandom :: Enum.t
def registered_nonrandom do
Enum.filter(get_globals(:registered_instructions), &(not(String.ends_with?(Atom.to_string(&1), "_rand"))))
end
@doc """
Takes a list of stacks and returns all instructions that have all
of their stack requirements fulfilled. This won't include random instructions
unless :random is in the types list. This won't include parenthesis-altering
instructions unless :parentheses is in the types list.
"""
@spec registered_for_stacks(Enum.t) :: Enum.t
def registered_for_stacks(types_list) do
types_list
end
@doc """
Takes a map of stack names and entire stack states, and returns a new push-state
with those stacks set.
"""
@spec push_state_from_stacks(map) :: map
def push_state_from_stacks(stack_assignments) do
Map.merge(make_push_state, stack_assignments)
end
end