Roll Your Own
Go with the Flow
Welcome back to Roll Your Own, where we take you through the beginnings (and eventually, the more advanced topics) of programming your Macintosh using AppleScript. I apologize for missing last month, but our family was a bit busy with a new addition, Tiana Lia Ross, born June 10, 2002, 10:44 AM, 6 lb. 1 oz., 19.5 in. Mommy, Daddy and big brother Kieran are happy to have the little one to hold at last.
Last time we covered variables and how they help to store information in your program. This time, we’ll delve into the topic of flow control, which allows the computer to make decisions for you.
Before we get into the meat of this month’s column, a word or two about learning AppleScript syntax: I’ve mentioned in earlier columns that this series isn’t meant to go into the details of AppleScript, so we’re going to go through some of the topics here rather quickly. We’ll cover the general programming topic in detail, but only scratch the surface when it comes to all of the ways AppleScript allows you to implement that topic. If you’re interested into the details of the AppleScript language, a great reference is AppleScript in a Nutshell, by Bruce W. Perry from O’Reilly & Associates. O’Reilly is a great technical publisher with many offerings on a wide range of computer topics, including about a dozen books dedicated to the Mac. (In case you’re wondering if this is a self-serving plug, rest easy, my book is not being published by O’Reilly.) In addition to a print version, AppleScript in a Nutshell is also available online for a monthly fee through Safari Tech Books Online. Safari allows you to subscribe to technical references by paying a monthly fee. The fee can be as little as $9.95 per month for 5 points (most books are worth 1 point). I’ve been subscribing to the service for about four months now, and have found it to be a great tool.
If you aren’t able to either purchase AppleScript in a Nutshell or a Safari subscription, Apple does provide a free manual which explains all of the AppleScript syntax called The AppleScript Language Guide, available both as a group of Web page and as a PDF document. Although it’s not as well written as AppleScript in a Nutshell, since this reference is free and available on the Web, when I suggest taking a look at the deeper AppleScript documentation, I’ll provide links to the appropriate sections of this book.
Back to Programming…
Flow control is the ability in a program to jump from one line of code to another without executing the lines between the two. For the most part, when a program runs, it executes each line in turn, performing the actions in sequence. But often you want to execute some lines of code out of order or conditionally. Other times you want the same code to execute repeatedly with slight variations. Both of these cases are why programming languages provide flow control. AppleScript makes use of a number of commands that allow us to control whether some statements are executed, and when.
Truthfully, we’ve already seen an example of flow control. In our prior sample applications we had a repeat
loop. The repeat
loop allows us to execute one or more lines of code as many times as necessary. But there are other kinds of flow control tools provided by AppleScript.
The first, and probably most useful, statement that we’ll cover is the if
statement. The if
statement is followed by a boolean test. The test expression needs to return a boolean value. (Remember, a boolean value is one that is either true or false.) If the test turns out to be true, then the code after the if
statement is executed. Type the following code into Script Editor and run it.
(6 / 2) is equal to 3
When you run the above program, the word true
should appear in your result window. That’s because the line (6 / 2) is equal to 3
, is a boolean expression, which in this case returns true. Change the program to read as follows:
2 is equal to 3
The line is still a boolean expression, but this time the expression is false, so running the program will show false
in the result window.
Here’s the simplest example of an if
statement:
if (6 / 2) is equal to 3 then beep
Here we are testing to see if the expression (6 / 2)
has a value of 3
. If it does, then the system will beep. If the expression had evaluated to false
, then the above line of code would do nothing.
Many programming languages have an equivalence between boolean values and numbers. For instance, FileMaker Pro and C both use 0 to indicate false and any positive value to indicate true. The following code in FileMaker Pro would work perfectly:
Set Field [ myField, 1 ] If [ myField ] Beep End If
This code will work because FileMaker interprets a 1 as true. AppleScript doesn’t work this way. Similar code in AppleScript will produce an error
set myVar to 1 if myVar then beep
AppleScript provides a number of comparison operators that programmers can use to build test expressions, and many of these operators are equivalent. If you don’t like to type as much, you could have written our first if
statement program as follows:
if (6 / 2) = 3 then beep
Here’s a list of the comparison operators that AppleScript provides. As you can see, many of them are equivalent to each other, so I’ve grouped the synonyms together. Check out your AppleScript reference for a detailed description of each of these operators.
= equals equal is equal to is equal to
≠ is not isn't is not equal is not equal to isn't equal isn't equal to doesn't equal
< is less than less than comes before is not greater than or equal to is not greater than or equal isn't greater than or equal to isn't greater than or equal
<= ≤ is less than or equal to is less than or equal less than or equal to is not greater than isn't greater than does not come after doesn't come after
> is greater than greater than comes after is not less than or equal to is not less than or equal isn't less than or equal to isn't less than or equal
>= ≥ is greater than or equal to is greater than or equal greater than or equal to is not less than isn't less than does not come before doesn't come before
begins with begin with starts with start with
ends with end with
contains does not contain is contained by is in is not contained by is not in isn't contained by
and or not
If your desire is to write programs that are readable by the average person, you would probably use the English versions of the operators. Some of the operators are only available in English versions, such as contains
and begins with
. Most of these operators are pretty self explanatory, such as the =
and the <=
operators, and the synonyms for them make sense.
Those last three operators listed, and
, or
and not
, are of great assistance in building complex boolean expressions. Often the test you wish to make is more complex than can be expressed with a single operator. Perhaps you wish to perform an action only if var1
is greater than var2
and var3
is equal to var1
. By using the and
operator, you can accomplish this sort of test:
set var1 to "Chuck" set var2 to "Adam" set var3 to var1 (var1 is greater than var2) and (var3 is equal to var1) -- result: true
Make a practice of using parentheses when building complex boolean expressions like this. In this case, the expression will work without the parentheses, but they certainly help make it clear exactly what is being tested.
The and
operator returns true only if both of the tests on either side of it return true. If either one returns false, so does the entire operation.
set x to true set y to false x and x -- result: true x and y -- result: false y and x -- result: false y and y -- result: false
The or
operator, on the other hand, will return true if either of the expressions it is comparing are true.
set x to true set y to false x or x -- result: true x or y -- result: true y or x -- result: true y or y -- result: false
Lastly, we have the not
operator. Whereas all of the operators we’ve seen so far work on two expressions, the not
operators works on only one. It simply reverses the value of a boolean.
set x to true set y to false not x -- result: false not y -- result: true
We’ve covered a lot of material so far, and before you continue, I would recommend that you experiment with the operators available to create boolean expressions. Set some variables and compare them, then see what happens in the result window. It is important that you thoroughly understand how to build boolean expressions, because you’ll be needing them throughout your programming career. Even if you later decide to change languages, the logic of using these boolean operators will be useful in whichever language you later tackle. Because of how important this will be to your future programming life, take the time to make sure you understand what we’ve gone over so far before continuing.
Don’t worry, I’ll wait…
Welcome back! OK, now that we have a solid foundation of what comparison expressions are, we can use them to tell our computer to make decisions. We’ve already checked out a simple if
statement, and now we’re going to build on that. Remember from above that our simple if
statement looked like this:
if (6 / 2) is equal to 3 then beep
This takes the form of
if [test] then [statement]
This is as simple an if
statement as you can get. You need at least this much for it to work, but you can provide more to increase its power. The first thing you might notice is that only one statement is being executed. Usually we want a number of statements to execute if the test proves to be true. In such a case, our if
statement is going to look like this:
if [test] then [statements] end if
We can place as many statements between the if
and end if
statements as we like. They will only execute if the test evaluates to true
.
if (6 / 2) is 3 then beep display dialog myVariable beep end if
Often if a condition is true you want one set of statements to execute and if not then a different set of statements. AppleScript provides the else
command for just such occasions. It takes the form:
if [test] then [statements] else [statements] end if
One set of those statements will execute, never both and never none.
set myVariable to (6 / 2) if myVariable is 3 then beep display dialog myVariable beep else display dialog "myVariable is not 3" end if
From here it just gets even more complex. Let’s say that if a condition is false, then you only want to execute the else
block of statements if a different condition is true. We have the else if
statement to cover such ground.
set myVariable to 5 if myVariable is 3 then beep display dialog myVariable beep else if myVariable is 5 display dialog "myVariable is 5" end if
That second statement block, display dialog "myVariable is 5"
will only execute if myVariable
actually is 5
. If it is 3
, the second if
never even gets evaluated. You can have as many of these if…else if…else if…end if
blocks as you need, and when we get to building user interfaces and need to check which button was clicked by the user, such statements come in quite handy.
By using nested if
s and else if
s, you can create as complex a test as needed for your program to know what to do. Be careful, however. Be sure to document your code well, as many nested if
s are difficult to decipher later. One way I use comments in my code to help me with nested if
s is to place a comment at the end of the end if statement like this:
if myVariable is 3 then beep display dialog myVariable beep end if -- myVariable is 3
The comment after the end if
statement makes it clear which if
block the end if
is closing. Read the AppleScript documentation on if statements for more detailed information on the if
statement.
Another kind of control statement is the repeat statement. The repeat
statement allows you to execute a block of code over and over again however many times you need to. We saw the repeat
statement in previous columns when we wrote a program to sum up all the values to a given point. As a refresher, here is the program that used it:
display dialog "Please enter a positive number:" — default answer "" buttons {"OK"} default button 1 set theNumber to the text returned of result set sum to 0 repeat with i from 1 to theNumber set sum to sum + i end repeat -- Report the results of the process to the user. display dialog "The sum of the first five number is " & — sum & "." buttons {"OK"} default button 1
The repeat
block of this program executes the number of times entered by the user. This is just one version of the repeat
loop, and it takes the form of
repeat with [variable] from [integer] to [integer] [statements] end repeat
This block of code will set the variable to the first integer. Each time through the loop, the variable will be incremented by 1. If after incrementing the variable it is greater than the second integer, the loop doesn’t execute and the program continues with the code found after the end repeat
statement.
Like the if
statement, the repeat
statement comes in a number of flavors, including the simpler:
repeat [statements] end repeat
This code simply executes forever, unless one of the statements within the repeat
block has an exit repeat
statement. Be very careful with this simple version of the repeat
loop. It’s easy to forget to have the exit repeat
statement or to have code which never executes the exit repeat
statement, and then you get an infinite loop. The only way to stop an infinite loop is to force the program to quit or, if the program is running within Script Editor, to press Command-Period. Here’s a simple example of such a repeat
block.
set i to 1 repeat display dialog i if i is equal to 10 then exit repeat set i to i + 1 end repeat
This code is equivalent to:
repeat with i from 1 to 10 display dialog i end repeat
The fact that these two code fragments are equivalent brings up a good point. There is almost always more than one way to accomplish your programming task. Which repeat loop you use in this case is perhaps a matter of preference, although to me the second one is simpler and easier to understand.
Another version of the repeat
statement is the repeat while
flavor:
repeat while [booleanExpression] [statements] end repeat
This version will execute the statements within the repeat
block until the booleanExpression
evaluates to false
. If the booleanExpression
is false
to begin with, the statements in the loop won’t execute at all. Using this version to perform the same loop as above, we would have:
set i to 1 repeat while i is less than or equal to 10 display dialog i set i to i + 1 end repeat
Similar to repeat while
is repeat until
, which will execute the loop until the boolean expression evaluates to true. We’ll see an example of this type of loop when we improve our program later.
There’s one version of the repeat loop that is specific to lists. Remember that a list is a collection of objects surrounded by braces and separated by commas, such as {1, 3, 5, 10}
. It is often useful to do something with each item in the list, and there is a version of the repeat
loop to handle this. It takes the form
repeat with [variable] in [list] [statements] end repeat
Since most of our example repeat
loops have had the same output, we’ll continue that theme with this one.
set myList to {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} repeat with i in myList display dialog i end repeat
Like our prior loops, this one displays ten dialog boxes with the first ten numbers in them, only this time we are iterating through a list instead of counting.
Before we leave the syntax of flow control and begin improving our program to sum numbers, we need to cover one more command, the try…on error…end try
block. Like repeat
and if
, it has many different forms which are covered in the AppleScript documentation. For now we’re only going to cover the simplest form, which looks like this:
try [statements] on error [statements] end try
There are many times when you’ll be programming and you know that under certain conditions the statements you’re going to execute will produce an error. Using a try
block you can capture the error and handle it yourself rather than having AppleScript choke on it. Here’s an example:
try set myVar to "AppleScript" as number beep on error display dialog "Can't coerce that string into a number." end try display dialog "This is outside the try block."
What the above block does is first attempt to coerce the string “AppleScript” into a number. Well, this isn’t something that AppleScript can do, so an error is produced. If we had tried to execute the statement outside of a try
block, AppleScript would have halted execution of the program and displayed an error message. By enclosing the attempt in a try
block we can handle the error ourselves and then act appropriately, allowing the program to continue. With the program above, the final line of code will execute even if there is an error.
One thing to take note of is that the above code will never beep. When the error is detected, execution of the try
block stops at that point and continues after the on error
statement.
You can also execute the on error
block by manually raising an error
within the try
block, as you’ll see in our sample program.
The try block
is quite useful in catching user errors and handling them elegantly rather than having the user have to put up with a system error. We’ll go into more detail with it in a future column, but for now we’ll use the simple version of it in our updated program.
Now that we have all this syntax under our belts, let’s put it to use. If you remember our program that sums up numbers, the first thing it does is get a number from the user. But what if the user doesn’t enter a number? What if instead of entering “5", they enter “five”? As our program currently stands, it would crash because it can’t count to “five.”
Here’s the edited version that checks for the type of data input by the user. It adds not only an if
statement, but an additional repeat
loop. Because the complexity of the program has increased, I’m beginning the practice of including comments with every line of my full sample programs. Remember that the ¬
character is entered by typing Option-Return in Script Editor and indicates that the following line is a continuation of the current line.
-- Get the number to sum up to from the user. display dialog "Please enter a positive number:" default answer ¬ "" buttons {"OK"} default button 1 -- Get the data entered by the user into the dialog's field set theNumber to the text returned of result -- Assume that invalid data was entered set isValidEntry to false -- Repeat the following block of statements until we've made -- sure that the user has entered valid data. repeat until isValidEntry -- Attempt to coerce the data entered by the user into a number. try -- If a non-numeric entry was made by the user the -- following statement will produce an error. set theNumber to theNumber as number -- If the data entered by the user is a number but -- isn't an integer, manually produce an error. if class of theNumber is not integer then error -- If no error was produced, set the isValidEntry -- variable to true so that we can exit the loop. set isValidEntry to true on error -- If an error was produced, alert the user and ask for -- valid data. display dialog "That is not a valid entry. " & ¬ "Please enter a positive integer:" default answer ¬ "" buttons {"OK"} default button 1 -- Get the data entered by the user. set theNumber to text returned of result end try -- set theNumber to theNumber as number end repeat -- until isValidEntry -- Initialize sum to 0. set sum to 0 -- Sum up the numbers. repeat with i from 1 to theNumber set sum to sum + i end repeat -- with i from 1 to theNumber -- Report the results of the process to the user. display dialog "The sum of the first ¬ " & theNumber & " numbers is " & sum & ¬ "." buttons {"OK"} default button 1
Wow, our program is getting more complex (and better) every time! Here’s what it is doing.
As before, the first command prompts the user to enter a number. We then immediately store what the user entered into a variable. Next we initialize another variable called isValidentry
to false
. We only want to sum up the numbers if the user enters an integer, so we use this variable to test if that has occurred.
Next we enter a repeat
loop. Since the repeat
loop says repeat until isValidEntry
, and isValidEntry
is false
to begin with (because that’s what we set it to), we know that this repeat
loop will execute at least once.
As soon as we enter the loop, we begin a try
block. The first line of the try
block attempts to coerce the user’s entry into a number. If the user entered something like 5
or 6.3
, this will work fine and the execution continues. If the user entered their name or today’s date, or anything but a number, the line set theNumber to theNumber as number
will produce an AppleScript error, and execution of the program will hop over to the on error
section.
If we passed the first line successfully, we know that theNumber
is in fact a number, but it still needs to be an integer for our sum to work. So we use an if
statement to check the class of theNumber
, and if it isn’t an integer, we manually call the error
, the execution of the try
portion halts and moves to the on error
portion.
If both of the first two statements in the try
block execute successfully without producing an error, then we set the isValidEntry
variable to true
so that the repeat
loop won’t execute again. The on error
portion of the program will only execute if an error was produced by one of the first statements in the try
portion. If we get to this part of the program, then the user either didn’t enter a number or the number wasn’t an integer. We inform the user that there was a problem with her entry the last time and again ask for an integer, storing the data entered by the user into the variable theNumber
again. Since isValidEntry
hasn’t been set to true
in this case, we go back up to the repeat
loop and begin the process of testing the user entry again. The repeat
loop ensures that before we continue, and attempt to sum up the numbers, we have a valid number to sum up to. The rest of the program is the same as before with the added comments.
Finishing Up
Well, we’ve covered a lot of material today. We learned how to build simple and complex boolean, expressions, how to conditionally execute part of the program, how to capture errors and how to loop through code repeatedly. Next time we’re going to go into more detail with the try
block by testing for the type of error that occurred, and we’ll cover a more general programming topics, such as algorithms and flowcharts. As always, feel free to e-mail me with questions or suggestions. Until then, happy programming!
Also in This Series
- Getting the List of It—Part 2 · July 2003
- Getting the List of It · June 2003
- The Object of Programming—Part 2 · March 2003
- The Object of Programming · February 2003
- How to Handle Anything · January 2003
- Try to Handle This · December 2002
- Charting Your Success · September 2002
- Go with the Flow · August 2002
- Variables and Data · June 2002
- The Ultimate Customization · May 2002
- Complete Archive
Reader Comments (2)
Add A Comment