# Pops two values from the +:int+ stack ("destination" and "counter"), and one item from the +:exec+ stack.
# The net effect of the instruction (unless interfered with by another operation)
# is to evaluate the +:exec+ item once for every integer in the range (inclusive).
#
# note: the top integer is the "destination", the second one the "counter"
# (regardless of their values or signs)
#
# note: unlike the ExecDoRange instruction, the counter is not pushed
#
# If the counter and destination have the same value, then a copy of the +:exec+ item is
# pushed onto the +:exec+ stack.
#
# If the counter and destination have different values, then a "new_counter" value
# is calculated that is *one step closer to the destination*.
#
# A ValuePoint containing the following "macro" is created:
# block {
# value «int»
# value «int»
# do exec_do_times
# popped item
# }
# «int» new_counter
# «int» destination
# where +popped_item+ is the code from the +:exec+ stack, and +new_counter+ and +destination+ are the numeric values that were derived above.
#
# Finally,
# 1. the macro is pushed onto the +:exec+ stack
# 2. another copy of the +popped_item+ is pushed onto the +:exec+ stack (on top of the macro)
#
# The consequence is that the original item will be executed,
# then the macro will be encountered, and this process will repeat.
#
# note: if the +popped_item+ itself manipulates the +:exec+ stack, "complicated behavior" may arise
#
# *needs:* 2 +:int+ items, 1 +:exec+ item
#
# *pushes:* well, it's complicated...
#
class ExecDoTimesInstruction < Instruction
def preconditions?
needs :exec, 1
needs :int, 2
end
def setup
@destination = @context.pop(:int)
@counter = @context.pop(:int)
@code = @context.pop(:exec)
end
def derive
@finished = false
if @counter.value == @destination.value
@finished = true
elsif @counter.value < @destination.value
@new_counter = ValuePoint.new("int", @counter.value + 1)
else
@new_counter = ValuePoint.new("int", @counter.value - 1)
end
end
def cleanup
if @finished
pushes :exec, @code
else
recursor = CodeblockPoint.new([@new_counter, @destination,
InstructionPoint.new("exec_do_times"),@code])
pushes :exec, recursor
pushes :exec, @code
end
end
end