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
code
stack.
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
code
stack. - Fewer than 2 items on the
int
stack.
B&A: Counting up
Loop counter is 2
and loop will stop at 5
.
Question: Why is the current loop index put on the
:int
stack 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