I wrote a little bit about control structures in 2004-07-11 Control Structures. Because of that, I won’t spend a whole lot of time on what control structures are and more time on how to implement them in Parrot. This material follows pretty much the same steps as the Parrot book chapter on control structures. You could follow that chapter and end up with the same knowledge that you would get from reading today’s step. Maybe even a little more.
Sequence
As usual for many programming languages, the the sequence control structure is implemented in Parrot as a pattern of one instruction following another.
Code Sample
# example-03-01.pir
.sub main :main
.const string PROMPT = "What is your name? "
.local string name
.local string response
.local pmc stdin
stdin = getstdin
name = stdin.'readline_interactive'(PROMPT)
response = "Hello, " . name
say response
.end
Selection
Parrot uses simple mechanisms for building selection and repetition structures. Why is this? This is because Parrot and PIR are built for designing new programming languages. The designers opted to have the core control structures be simplistic so you wouldn’t be stuck with what Parrot thinks a repetition loop - as one example - should look like. This does mean that moderately experienced developers such as myself must release some of our prejudices about what is “good” or “bad” in code. Very experienced developers and complete newcomers should have an easier time with Parrot’s controls, due to broadened horizons or lack of preconceptions.
Selection structures are created in Parrot with a combination of labels, goto, and if test instructions. Yes, the infamous goto. Let’s relinquish our cargo cult habits and start learning how goto is used in Parrot.
We’ll look at labels first, which are how we tell goto where to go.
Labels
Labels consist of a series of letters, numbers, and underscores followed by a colon : character. Although it’s not strictly required, I use uppercase letters for label identifiers. You will generally find labels by themselves, outdented from the other instructions around it.
Code Sample
<IDENTIFIER>:
# Code following instruction
Again: this is not required. I follow this convention because I believe it makes my code easier to read. You could just as easily have the following instruction on the same line, but it has the risk of making your code visually cluttered.
Code Sample
SET_NAME: $S0 = 'Brian'
say $S0
Labels serve as markers in your code. They don’t accomplish much by themselves, but can show some of your program structure if named and placed intelligently.
Code Sample
# example-03-02.pir
.sub main :main
.const string PROMPT = "What is your name? "
.local string name
.local string response
.local pmc stdin
stdin = getstdin
GET_NAME:
name = stdin.'readline_interactive'(PROMPT)
response = "Hello, " . name
say response
.end
goto
A goto instruction sends Parrot execution to a specific labelled location in your code.
Code Sample
goto <LABEL_IDENTIFIER>
When it follows a goto, Parrot picks up by executing the first instruction following the label. In this example, it creates an infinite loop which can only be interrupted by pressing Control-C.
Code Sample
# example-03-03.pir
.sub main :main
.const string PROMPT = "What is your name? "
.local string name
.local string response
.local pmc stdin
stdin = getstdin
GET_NAME:
name = stdin.'readline_interactive'(PROMPT)
goto GET_NAME
response = "Hello, " . name
say response
.end
Infinite loops have their occasional uses, but most of the time they’re just annoying.
Code Sample
$ parrot example-03-03.pir
What is your name? Brian
What is your name? Brian
What is your name? I said "Brian!"
What is your name? <Control-C>
if
The if operation lets us perform specific instructions when a particular condition is true by branching control. At our level of expertise, that branching is performed with goto. So for us, the general if syntax looks like this:
Code Sample
if <conditional> goto <LABEL>
Let’s see if in action by adding a little control logic to our name prompt.
Code Sample
# example-03-04.pir
.sub 'main' :main
.const string PROMPT = "What is your name? "
.local string name
.local string response
.local pmc stdin
stdin = getstdin
name = stdin.'readline_interactive'(PROMPT)
if name == "Brian" goto GREETING_FOR_BRIAN
response = "Hello, " . name
goto SAY_IT
GREETING_FOR_BRIAN:
response = "Hey, Brian!"
SAY_IT:
say response
.end
I admit it. This example is all about making me feel good. One greeting is prepared for users named Brian, while another greeting is prepared for everybody else. The program accomplishes this by examining the value of name. If name is “Brian”, the program is instructed to goto the label GREETING_FOR_BRIAN. Otherwise it continues on, preparing a generic response and going to the SAY_IT label. GREETING_FOR_BRIAN prepares a custom response and follows up to the next instruction, which happens to be the SAY_IT code. Remember that labels are just markers. They don’t cut the labeled code off from the rest of your code.
Conditionals
The conditional in an if operation is checked to see if it looks false. The conditional can be a simple variable, in which case the value of the variable is examined. False looks a little different for each of the types.
| For This Type | False looks like this |
|---|---|
| Integer | 0 |
| Number | 0.0 |
| String | "", "0" |
Anything that doesn’t look false is considered true.
You can also use comparison operators to compare two values in your conditional.
Code Sample
# example-03-05.pir
.sub main :main
.local int a
.local int b
.local pmc stdin
stdin = getstdin
a = stdin.'readline_interactive'('a: ')
b = stdin.'readline_interactive'('b: ')
if a > b goto A_IS_GREATER
if b > a goto B_IS_GREATER
goto BOTH_EQUAL
A_IS_GREATER:
say "a is greater"
goto END
B_IS_GREATER:
say "b is greater"
goto END
BOTH_EQUAL:
say "a and b are the same"
goto END
END:
say "Wasn't that fun?"
.end
This little script grabs two values from the user and reports which one is greater - or reports the special case when they are the same.
Code Sample
$ parrot example-03-05.pir
a: 5
b: 10
b is greater
Wasn't that fun?
Here are the comparison operators that are available for your conditionals.
| Operator | Tests |
|---|---|
a == b |
Are a and b the same value? |
a !== b |
Are a and b different values? |
a > b |
Is a greater than b? |
a < b |
Is a less than b? |
a >= b |
Is a greater than or equal to b? |
a <= b |
Is a less than or equal to b? |
What about having multiple tests? For example, maybe you will only accept a number if it’s within a certain range. Parrot does not let us chain comparisons, but we can still use multiple tests with the and and or opcodes.
Code Sample
# example-03-06.pir
.sub main :main
.const int MINIMUM = 10
.const int MAXIMUM = 100
.local int input
.local int input_is_valid
.local pmc stdin
stdin = getstdin
GET_NUMBER:
print "Enter a number ("
print MINIMUM
print " - "
print MAXIMUM
print ")"
input = stdin.'readline_interactive'(': ')
$I0 = input >= MINIMUM
$I1 = input <= MAXIMUM
input_is_valid = and $I0, $I1
if input_is_valid goto VALID_INPUT
say "That is not in the acceptable range!"
goto END_PROGRAM
VALID_INPUT:
say "That is in the acceptable range."
END_PROGRAM:
say "Thank you!"
.end
and compares its arguments, and returns true if the arguments all look true. Parrot lets you store test results in a variable, as you can see. Then you can examine the truthiness of the variable in your if condition.
Code Sample
$ parrot example-03-06.pir
Enter a number (10 - 100): 990
That is not in the acceptable range!
Thank you!
$ parrot example-03-06.pir
Enter a number (10 - 100): 82
That is in the acceptable range.
Thank you!
This approach looked odd enough to me that I thought I’d show the equivalent Perl code.
This Parrot code:
Code Sample
$I0 = input >= MINIMUM
$I1 = input <= MAXIMUM
input_is_valid = and $I0, $I1
if input_is_valid goto VALID_INPUT
say "That is not in the acceptable range!"
goto END_PROGRAM
VALID_INPUT:
say "That is in the acceptable range."
END_PROGRAM:
say "Thank you!"
is roughly equivalent to the following Perl code:
Code Sample
if ($input >= $MINIMUM && $input <= $MAXIMUM) {
say "That is in the acceptable range.";
} else {
say "That is not in the acceptable range.";
}
say "Thank you!";
We could also use or, which compares its arguments and returns true if either argument looks true.
Code Sample
# example-03-07.pir
.sub 'main' :main
.const int MINIMUM = 10
.const int MAXIMUM = 100
.local int input
.local int input_is_invalid
.local pmc stdin
stdin = getstdin
GET_NUMBER:
print "Enter a number ("
print MINIMUM
print " - "
print MAXIMUM
print ")"
input = stdin.'readline_interactive'(': ')
$I0 = input <= MINIMUM
$I1 = input >= MAXIMUM
input_is_invalid = or $I0, $I1
if input_is_invalid goto WARN_USER_ABOUT_INPUT
goto END_PROGRAM
WARN_USER_ABOUT_INPUT:
say "That is not in the acceptable range!"
END_PROGRAM:
say "Thank you!"
.end
unless
Sometimes the normal if test does not clearly describe your needs. You may only want to branch if a test fails. While you can do this with if, Parrot also provides the unless test for exactly this situation. unless looks similar to if:
Code Sample
unless <conditional> goto <LABEL>
I can use it in the user prompt program to streamline the description of the application’s response to anyone but me.
Code Sample
# example-03-08.pir
.sub main :main
.const string PROMPT = "What is your name? "
.local string name
.local string response
.local pmc stdin
stdin = getstdin
name = stdin.'readline_interactive'(PROMPT)
unless name == "Brian" goto GENERIC_GREETING
response = "Hey, Brian!"
goto SAY_IT
GENERIC_GREETING:
response = "Hello, " . name
SAY_IT:
say response
.end
The program will run the same after this little change. Try it and see.
Usage of unless is a personal choice. I like it because it lets me describe my program a little more concisely. Others don’t like to use unless because it adds to the mental load of reading application code: “let’s see, unless means ‘if this is not true.” Both points of view are valid, and ultimately it’s up to you whether unless belongs in your code.
Repetition
We saw the infinite loop earlier when we first looked at goto. Let’s explore more controlled loops.
Believe it or not, the combination of if and goto provide us with the core features we need for a wide range of control structures. Things can get interesting now that we’ve added these tools to our kit.
How about a countdown?
Code Sample
# example-03-09.pir
.sub 'main' :main
.const int start = 10
.const int stop = 0
.local int current
current = start
COUNTDOWN:
if current < stop goto LIFTOFF
say current
current -= 1
goto COUNTDOWN
LIFTOFF:
say "Liftoff!"
.end
This presents a simple count-controlled loop. We set a loop counter current to a reasonable start value before we start looping. We check the value of the loop counter each time we start the loop, quitting if current is less than our stopping value. After our check, we display the value, subtract one, and run the loop again.
A condition-controlled loop is easy, too.
Code Sample
# example-03-10.pir
.sub main :main
.const string PROMPT = "What is your name? "
.local string name
.local string response
.local pmc stdin
stdin = getstdin
GET_NAME:
name = stdin.'readline_interactive'(PROMPT)
if name goto GREET_USER
goto GET_NAME
GREET_USER:
response = "Hello, " . name
say response
.end
Our script will now continue to check to see if the user actually entered anything for a name. If the user has entered a name, control goes to the GREET_USER label. Otherwise, control is sent backwards to the GET_USER label.
Code Sample
$ parrot example-03-10.pir
What is your name?
What is your name? Brian
Hello, Brian
Collection loops such as list iterators will have to wait until we examine PMCs.
Summary
Adding if and goto to our toolkit has given us the means to build fundamental control structures in our Parrot programs. We know how to use simple conditionals as well as how to create more complex conditionals using and and or. We have unless for those situations when if doesn’t describe our intent clearly enough. We can use the simple combination of if and goto to create counter and condition controlled loops. A really determined person could create useful programs with just this information. However, even simple useful programs would benefit from using the library of PMCs that are available.