Parrot Babysteps 03 - Simple Control Structures
I’ve already written a little bit about control structures on another page. 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.
# 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.
<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.
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.
# 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.
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.
# 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.
$ 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:
if <conditional> goto <LABEL>
Let’s see if
in action by adding a little control logic to our name
prompt.
# 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.
# 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.
$ 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.
# 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.
$ 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:
$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:
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.
# 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
:
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.
# 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?
# 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.
# 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.
$ 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.