Some Push instructions are trickier to understand than others, especially the ones that implement looping/control flow. This wiki page documents the non-trivial instructions found in modern Push implementations with the hopes of making it easier to implement comparable PushGP systems.
For people using Clojush as their reference implementation, there is also the clojush-playground project which provides REPL friendly utilities for testing out instructions.
Each section is dedicated to an individual instruction. It includes a description of that instruction’s logic in prose, some rationale for why the instruction should be used, the conditions under which it should NOOP, and some example “before and after” stacks to demonstrate the behavior.
Each “before and after” (B&A) stacks two EDN maps. The top of the stack being the first item in the list (furtherest to the left).
Contributing:
To add documentation of a specific instruction, copy an existing section and populate each section. When coming up with “before and after” stacks, it is helpful to include a base case and any edge cases.
The following should NOT be considered edge cases that need documenting.
- Meeting any of the listed NOOP conditions.
- Reaching interpreter limits, such as step limit or stack depth limits.
- Any non-standard interpreter modes or evolutionary features. (ie. epigenetics)
code_do_then_pop
Pushes a code_pop instruction and the top item of the code stack to the exec stack. Does not consume the top item from the code stack.
Assumes the code_pop is an instruction which pops the top item off the code stack.
Rationale:
Used to perform the top code item before it is removed from the code stack. This can be used to implement recursive behavior if the top code item manipulates the code stack.
NOOP Conditions:
- Empty
codestack.
B&A: Base case
{
:int ()
:code (1)
:exec ()
}
{
:int ()
:code (1)
:exec (1 code_pop)
}
B&A: Recursion
Assuming code_dup in an instruction will duplicate the top item of the code stack, executing the code_do_then_pop instruction with the following stack will result in an infinite loop producing 1 values on the :int stack.
{
:int ()
:code ((code_dup 1))
:exec ()
}
{
:int ()
:code ((code_dup 1))
:exec ((code_dup 1) code_pop)
}
code_do_range
Evaluates the top item on the code stack for each step along the range i to j.
Both i and j are taken from the int stack.
Rationale:
Implements a loop where the “body” is taken from the code stack.
NOOP Conditions:
- Empty
codestack. - Fewer than 2 items on the
intstack.
B&A: Counting up
Loop counter is 2 and loop will stop at 5.
Question: Why is the current loop index put on the
:intstack each iteration?
{
:int (5 2)
:code ("A")
:exec ()
}
{
:int (2)
:code ()
:exec ("A" (3 5 code_from_exec "A" code_do_range))
}
B&A: Counting down
Loop counter is 2 and loop will stop at 0.
{
:int (0 2)
:code ("A")
:exec ()
}
{
:int (2)
:code ()
:exec ("A" (1 0 code_from_exec "A" code_do_range))
}
B&A: Exit loop
Loop stops at the current iteration (counter and termination at 2).
{
:int (2 2)
:code ("A")
:exec ()
}
{
:int (2)
:code ()
:exec ("A")
}
Undocumented
-
exec_do_range -
code_do_count -
exec_do_count -
code_do_times -
exec_do_times -
exec_while -
exec_do_while -
code_map -
code_if -
exec_if -
code_when -
exec_when - Add more to the list