Team LiB   Previous Section   Next Section

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.

    Team LiB   Previous Section   Next Section