Control Structures

The semantics of control structures in high-level languages vary broadly. Rather than dictating one particular set of semantics for control structures, or attempting to provide multiple implementations of common control structures to fit the semantics of all major target languages, PIR provides a simple set of conditional and unconditional branch instructions.In fact, all control structures in all languages ultimately compile down to conditional and unconditional branches, so you're just getting a peek into the inner workings of your software.

Conditionals and Unconditionals

An unconditional branch always jumps to a specified label. PIR has only one unconditional branch instruction, goto. In this example, the first say statement never runs because the goto always skips over it to the label skip_all_that:

      goto skip_all_that
      say "never printed"

  skip_all_that:
      say "after branch"

A conditional branch jumps to a specified label only when a particular condition is true. The condition may be as simple as checking the truth of a particular variable or as complex as a comparison operation.

In this example, the if/goto skips to the label maybe_skip only if the value stored in $I0 is true. If $I0 is false, it will print "might be printed" and then print "after branch":

      if $I0 goto maybe_skip
      say "might be printed"
  maybe_skip:
      say "after branch"

Boolean Truth

Parrot's if and unless instructions evaluate a variable as a boolean to decide whether to jump. In PIR, an integer is false if it's 0 and true if it's any non-zero value. A number is false if it's 0.0 and true otherwise. A string is false if it's the empty string ("") or a string containing only a zero ("0"), and true otherwise. Evaluating a PMC as a boolean calls the vtable function get_bool to check if it's true or false, so each PMC is free to determine what its boolean value should be.

Comparisons

In addition to a simple check for the truth of a variable, PIR provides a collection of comparison operations for conditional branches. These jump when the comparison is true.

This example compares $I0 to $I1 and jumps to the label success if $I0 is less than $I1:

      if $I0 < $I1 goto success
      say "comparison false"
  success:
      say "comparison true"

The full set of comparison operators in PIR are == (equal), != (not equal), < (less than), <= (less than or equal), > (greater than), and >= (greater than or equal).

Complex Conditions

PIR disallows nested expressions. You cannot embed a statement within another statement. If you have a more complex condition than a simple truth test or comparison, you must build up your condition with a series of instructions that produce a final, single truth value.

This example performs two operations, addition and multiplication, then uses and to check if the results of both operations were true. The and opcode stores a boolean value (0 or 1) in the integer variable $I2; the code uses this value in an ordinary truth test:

    $I0 = 4 + 5
    $I1 = 63 * 0
    $I2 = and $I0, $I1

    if $I2 goto true
    say "maybe printed"
  true:

If/Else Construct

if control structure High-level languages often use the keywords if and else for simple conditional control structures. These control structures perform an action when a condition is true and skip the action when the condition is false. PIR's if instruction can build up simple conditionals.

This example checks the truth of the condition $I0. If $I0 is true, it jumps to the do_it label, and runs the body of the conditional construct. If $I0 is false, it continues on to the next statement, a goto instruction that skips over the body of the conditional to the label dont_do_it:

    if $I0 goto do_it
    goto dont_do_it
  do_it:
    say "in the body of the if"
  dont_do_it:

The control flow of this example may seem backwards. In a high-level language, if often means "if the condition is true, run the next few lines of code". In an assembly language, it's often more straightforward to write "if the condition is true, skip the next few lines of code". Because of the reversed logic, you may find it easier to build a simple conditional construct using the unless instruction instead of if.

    unless $I0 goto dont_do_it
    say "in the body of the if"
  dont_do_it:

This example produces the same output as the previous example, but the logic is simpler. When $I0 is true, unless does nothing and the body of the conditional runs. When $I0 is false, unless skips over the body of the conditional by jumping to dont_do_it.

else control structure An if/else control structure is easier to build using the if instruction than unless. To build an if/else, insert the body of the else right after the first if instruction.

This example checks if $I0 is true. If so, it jumps to the label true and runs the body of the if construct. If $I0 is false, the if instruction does nothing, and the code continues to the body of the else construct. When the body of the else has finished, the goto jumps to the end of the if/else control structure by skipping over the body of the if construct:

    if $I0 goto true
    say "in the body of the else"
    goto done
  true:
    say "in the body of the if"
  done:

Switch Construct

A switch control structure selects one action from a list of possible actions by comparing a single variable to a series of values until it finds one that matches. The simplest way to achieve this in PIR is with a series of unless instructions:

    $S0 = 'a'

  option1:
    unless $S0 == 'a' goto option2
    say "matched: a"
    goto end_of_switch

  option2:
    unless $S0 == 'b' goto default
    say "matched: b"
    goto end_of_switch

  default:
    say "I don't understand"

  end_of_switch:

This example uses $S0 as the case of the switch construct. It compares that case against the first value a. If they match, it prints the string "matched: a", then jumps to the end of the switch at the label end_of_switch. If the first case doesn't match a, the goto jumps to the label option2 to check the second option. The second option compares the case against the value b. If they match, it prints the string "matched: b", then jumps to the end of the switch. If the case doesn't match the second option, the goto goes on to the default case, prints "I don't understand", and continues to the end of the switch.

Do-While Loop

A do-while loop runs the body of the loop once, then checks a condition at the end to decide whether to repeat it. A single conditional branch can build this style of loop:

    $I0 = 0                 # counter

  redo:                     # start of loop
    inc $I0
    say $I0
    if $I0 < 10 goto redo   # end of loop

This example prints the numbers 1 to 10. The first time through, it executes all statements up to the if instruction. If the condition evaluates as true ($I0 is less than 10), it jumps to the redo label and runs the loop body again. The loop ends when the condition evaluates as false.

Here's a slightly more complex example that calculates the factorial 5!:

      .local int product, counter

      product = 1
      counter = 5

  redo:                         # start of loop
      product *= counter
      dec counter
      if counter > 0 goto redo  # end of loop

      say product

Each time through the loop it multiplies product by the current value of the counter, decrements the counter, and jumps to the start of the loop. The loop ends when counter has counted down to 0.

While Loop

A while loop tests the condition at the start of the loop instead of at the end. This style of loop needs a conditional branch combined with an unconditional branch. This example also calculates a factorial, but with a while loop:

      .local int product, counter
      product = 1
      counter = 5

  redo:                         # start of loop
      if counter <= 0 goto end_loop
      product *= counter
      dec counter
      goto redo
  end_loop:                     # end of loop

      say product

This code tests the counter counter at the start of the loop to see if it's less than or equal to 0, then multiplies the current product by the counter and decrements the counter. At the end of the loop, it unconditionally jumps back to the start of the loop and tests the condition again. The loop ends when the counter counter reaches 0 and the if jumps to the end_loop label. If the counter is a negative number or zero before the loop starts the first time, the body of the loop will never execute.

For Loop

A for loop is a counter-controlled loop with three declared components: a starting value, a condition to determine when to stop, and an operation to step the counter to the next iteration. A for loop in C looks something like:

  for (i = 1; i <= 10; i++) {
    ...
  }

where i is the counter, i = 1 sets the start value, i <= 10 checks the stop condition, and i++ steps to the next iteration. A for loop in PIR requires one conditional branch and two unconditional branches.

  loop_init:
    .local int counter
    counter = 1

  loop_test:
    if counter <= 10 goto loop_body
    goto loop_end

  loop_body:
    say counter

  loop_continue:
    inc counter
    goto loop_test

  loop_end:

The first time through the loop, this example sets the initial value of the counter in loop_init. It then goes on to test that the loop condition is met in loop_test. If the condition is true (counter is less than or equal to 10) it jumps to loop_body and executes the body of the loop. If the the condition is false, it will jump straight to loop_end and the loop will end. The body of the loop prints the current counter then goes on to loop_continue, which increments the counter and jumps back up to loop_test to continue on to the next iteration. Each iteration through the loop tests the condition and increments the counter, ending the loop when the condition is false. If the condition is false on the very first iteration, the body of the loop will never run.