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 : set_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 run loop. Terminating the main bytecode segment (the first run loop) stops the interpreter. Without the end statement, execution just falls off the end of the bytecode segment, with a good chance of crashing the interpreter. 9.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. 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're 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. The comparison opcodes don't specify if a numeric or string comparison is intended. The type of the register selects for integers, floats, and strings. With PMCs, the vtable method cmp or is_equal of the first argument is responsible for comparing the PMC meaningfully with the other operand. If you need to force a numeric or string comparison on two PMCs, use the alternate comparison opcodes that end in the _num and _str suffixes. eq_str P0, P1, label # always a string compare gt_num P0, P1, label # always numerically Finally, the eq_addr opcode branches if two PMCs or strings are actually the same object (have the same address), and the is_null opcode branches if a PMC is NULL (has no assigned address): eq_addr P0, P1, same_pmcs_found is_null P2, the_pmc_is_null 9.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. |