6.4 Flow Control
Although it has many advanced
features, at heart PASM is an assembly language. All flow control in
PASM—as in most assembly languages—is done with branches
and jumps.
Branch instructions transfer control to a relative offset from the
current instruction. The rightmost argument to every branch opcode is
a label, which the assembler converts to the integer value of the
offset. You can also branch on a literal integer value, but
there's rarely any need to do so. The simplest
branch instruction is branch:
branch L1 # branch 4
print "skipped\n"
L1:
print "after branch\n"
end
This example unconditionally branches to the location of the label
L1, skipping over the first
print statement.
Jump instructions transfer control to an absolute address. The
jump opcode doesn't calculate an
address from a label, so it's used together with
set_addr:
et_addr I0, L1
jump I0
print "skipped\n"
end
L1:
print "after jump\n"
end
The set_addr opcode takes a label or an integer
offset and returns an absolute address.
You've probably noticed the
end opcode as the last statement in many
examples above. This terminates the execution of the current bytecode
segment. Terminating the main bytecode segment (the first one) stops
the interpreter. Without the end statement,
execution just falls off the end of the segment, with a good chance
of crashing the interpreter.
6.4.1 Conditional Branches
Unconditional jumps and
branches aren't really enough for flow control. What
you need to implement the control structures of high-level languages
is the ability to select different actions based on a set of
conditions. PASM has opcodes that conditionally branch based on the
truth of a single value or the comparison of two values. The
following example has if and
unless conditional branches:
set I0, 0
if I0, TRUE
unless I0, FALSE
print "skipped\n"
end
TRUE:
print "shouldn't happen\n"
end
FALSE:
print "the value was false\n"
end
if branches if its first argument is a true value,
and unless branches if its first argument is a
false value. In this case, the if
doesn't branch because I0 is
false, but the unless does branch. Numeric values
are false if they are 0, and true otherwise. Strings are false if
they are the empty string or a single character
"0", and true otherwise. PMCs are
true when their
get_bool vtable method returns a nonzero value.
The comparison branching opcodes compare two values and branch if the
stated relation holds true. These are
eq (branch when equal),
ne (when not equal),
lt (when less than),
gt (when greater than),
le (when less than or equal), and
ge (when greater than or equal). The two
compared arguments must be the same register type:
set I0, 4
set I1, 4
eq I0, I1, EQUAL
print "skipped\n"
end
EQUAL:
print "the two values are equal\n"
end
This compares two integers, I0 and
I1, and branches if they are equal. Strings of
different character sets or encodings are converted to Unicode before
they're compared. PMCs have a cmp
vtable method. This gets called on the left argument to perform the
comparison of the two objects.
6.4.2 Iteration
PASM
doesn't define high-level loop constructs. These are
built up from a combination of conditional and unconditional
branches. A do-while style
loop can be constructed with a single conditional branch:
set I0, 0
set I1, 10
REDO:
inc I0
print I0
print "\n"
lt I0, I1, REDO
end
This example prints out the numbers 1 to 10. The first time through,
it executes all statements up to the lt statement.
If the condition evaluates as true (I0 is less
than I1) it branches to the
REDO label and runs the three statements in the
loop body again. The loop ends when the condition evaluates as false.
Conditional and unconditional branches can build up quite complex
looping constructs, as follows:
# loop ($i=1; $i<=10; $i++) {
# print "$i\n";
# }
loop_init:
set I0, 1
branch loop_test
loop_body:
print I0
print "\n"
branch loop_continue
loop_test:
le I0, 10, loop_body
branch out
loop_continue:
inc I0
branch loop_test
out:
end
This
example emulates a counter-controlled loop like Perl
6's loop keyword or
C's for. The first time through
the loop it sets the initial value of the counter in
loop_init, tests that the loop condition is met in
loop_test, and then executes the body of the loop
in loop_body. If the test fails on the first
iteration, the loop body will never execute. The end of
loop_body branches to
loop_continue, which increments the counter and
then goes to loop_test again. The loop ends when
the condition fails, and it branches to out. The
example is more complex than it needs to be just to count to 10, but
it nicely shows the major components of a loop.
|