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.
.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.endSelection
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 instructionAgain: 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 $S0Labels 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.
.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.endgoto
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.
.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.endInfinite loops have their occasional uses, but most of the time they’re just annoying.
$ parrot example-03-03.pirWhat is your name? BrianWhat is your name? BrianWhat 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.
.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
.endI 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.
.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 ENDB_IS_GREATER: say "b is greater" goto ENDBOTH_EQUAL: say "a and b are the same" goto END
END: say "Wasn't that fun?".endThis 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.pira: 5b: 10b is greaterWasn'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.
.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!".endand 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.pirEnter a number (10 - 100): 990That is not in the acceptable range!Thank you!$ parrot example-03-06.pirEnter a number (10 - 100): 82That 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.
.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!".endunless
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.
.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.endThe 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?
.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!".endThis 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.
.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.endOur 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.pirWhat is your name?What is your name? BrianHello, BrianCollection 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.