If you want to create a simple script of Telescript code that stands on its own and allows you to declare variables that are restricted to the scope of the code, you can use a do
statement. A do
statement is simply a block--a group of statements enclosed in curly brackets ({})--preceded by the keyword do
. For example, the following is a very short do
statement:
do { mangos, papayas, fruits: Integer; mangos = 25; papayas = 47; fruits = mangos + papayas; }When you compile and execute a script that is a
do
statement, the Telescript engine executes each of the block's statements in logical order. Executing the example statement you just saw performs some declarations, assignments, and calculation, but has no observable end effect. We'll show you how to see some results later in the chapter.
When you create a do
statement, you may have blocks nested within it. For example, you may want to use nested blocks for if/else
statements in the do
statement.
It's useful to give Telescript code an external voice that can report on activities and help debugging. That voice is provided by the debugging operation dump
. dump
is implemented on development Telescript engines as an extra operation on the class Object
. (dump
has no effect when used on production engines.) As you'll recall, every object on a Telescript engine is a member of class Object
, so every type of object on an engine has Object operations. This means that you can call dump
on any object.
dump
takes no arguments. When it's called on an object, it prints the name of the object's class on the engine's monitor. If the class is a predefined Telescript class (one of the classes defined in the Telescript Language Reference), dump
also prints the object's value and the value of each of the object's properties. For example, consider this dump statement:
25.dump();When executed, it creates an integer object of value
25
. It then calls dump
on the object, which prints this on your monitor:
Integer: <25>You can use
dump
with strings to print out text that can inform you of a state or execution location within a script. For example, this dump statement:
"The first arithmetic expression has just been evaluated.".dump();creates a string that is dumped on your monitor like this:
String: <The first arithmetic expression has just been evaluated.>We'll use
dump
throughout this book to show you--on the printed page--the results of executing code.if/else
statements allow provisional execution branching. An if/else
statement tests a boolean expression and then, if the expression is true, executes a following block or, if the expression is false, skips the block. The statement can include optional following blocks, each with its own boolean test. Any of these blocks can execute if their boolean expression is true and no previous blocks in the statement have executed.if/else
statement is this:
if <boolean expression> {<statement(s)>};The boolean expression is the test for execution forking. The block of code following executes or doesn't execute depending on the boolean expression. The semicolon at the end of the statement is optional only if the statement occurs at the end of an enclosing block.
When this simple form of an if/else
statement executes, the boolean expression determines where execution continues. If it's true, the block following executes. If it's false, the block following is skipped and execution moves to the next statement following the if/else
statement. For example, this do
statement
do{ azure: = 35; if azure>20 {"azure is greater than 20.".dump();}; "This program is finished.".dump(); }has these results on execution:
String: <azure is greater than 20.> String: <This program is finished.>If you edit the second line to set the value of
azure
to 5
, you see these results:
String: <This program is finished.>
if/else
statement that uses this syntax:
if <boolean expression> {<statement(s)>} else {<statement(s)>};The keyword
else
in the statement precedes a second block of statements. If the boolean expression is true, the first block executes and execution skips the second block. If the boolean expression is false, execution skips the first block and the second block executes. Execution continues with the next statement following the if/else
statement in either case. The following do
statement is an example:
do{ azure: = 15; if azure>20 {"azure is greater than 20.".dump();} else {"azure is 20 or less.".dump();}; "This program is finished.".dump(); }When executed, it yields these results:
String: <azure is 20 or less.> String: <This program is finished.>If you change the code so that
azure
is equal to 25
, execution yields these results:
String: <azure is greater than 20.> String: <This program is finished.>
if/else
statement, one of the blocks always executes. If you'd like to have the possibility that neither block executes, you can use the keywords else if
in place of the else
keyword as shown in the following syntax:
if <boolean expression> {<statement(s)>} else if <boolean expression> {<statement(s)>};The second boolean expression matches the second block. If the first boolean expression is true, the first block executes. The second boolean expression isn't evaluated and execution skips the second block. If the first boolean is false, execution skips the first block and the second boolean expression is evaluated. If the second expression is true, the second block executes. If the second boolean is false, the second block is skipped.
Consider the following do
statement as an example:
do{ azure: = 20; if azure>20 {"azure is greater than 20.".dump();} else if azure<20 {"azure is less than 20.".dump();}; "This program is finished.".dump(); }When executed, you see these results:
String: <This program is finished.>If you change the code so that
azure
equals 30
, you see these results:
String: <azure is greater than 20.> String: <This program is finished.>And if you change the code so that
azure
is equal to 15
, you see these results:
String: <azure is less than 20.> String: <This program is finished.>
if/else
statement, you can add as many else if
clauses as you'd like following the if
clause. You can end the else if
clauses with an optional else
clause if you'd like, as in the following syntax:
if <boolean expression> {<statement(s)>} else if <boolean expression> {<statement(s)>} <more optional else-if expressions and blocks> else if <boolean expression> {<statement(s)>} else {<statement(s)>};In this case, each boolean expression in the statement is evaluated in turn. If the expression is true, its associated block is executed and execution skips the rest of the statement--no more of that statement's expressions are evaluated, and none of its following blocks is executed. If none of the expressions evaluates to true, the last block is executed (the block following the
else
keyword).
If you want to leave an opportunity open for none of the blocks in the statement to execute, you can end the statement with an else if
clause as shown in the following syntax:
if <boolean expression> {<statement(s)>} else if <boolean expression> {<statement(s)>} <more optional else-if expressions and blocks> else if <boolean expression> {<statement(s)>} else if <boolean expression> {<statement(s)>};This form of
if/else
statement is evaluated as before with this difference: If none of the boolean expressions is true, then none of the blocks in the statement is executed (instead of executing the last block if none is true).
Consider this do
statement as an example:
do{ azure: = 2; if azure==1 {"azure equals one.".dump();} else if azure==2 {"azure equals two.".dump();} else if azure==3 {"azure equals three.".dump();} else {"azure is not an integer equal to 1, 2, or 3.".dump();}; "This program is finished.".dump(); }When executed, you see these results:
String: <azure equals two.> String: <This program is finished.>If you change the code so that
azure
equals 5
, you see these results:
String: <azure is not an integer equal to 1, 2, or 3.> String: <This program is finished.>If you set up a multiple
else
clause statement like this one, note that the statement stops evaluating booleans as soon as it finds a true boolean. If you put expressions that are most often likely to be true before expressions that are less often likely to be true, you'll make execution of your code more efficient. That's because execution won't often have to step through all or most of the expression evaluations.loop
statements allow a single block to repeat execution continuously. The syntax for a loop
statement is simple:
loop {<statement(s)>};When a
loop
statement executes, the block in the statement executes from beginning to end, starts execution from the beginning of the block again, and continuously loops execution through the block until a statement within the block stops execution or jumps execution to some point outside the loop
block.
A loop
statement is often used to enclose a block containing an if/else
statement that tests a condition. When the condition is right, a break
statement (explained a little later in this chapter) executes and the loop
block stops execution. Execution then jumps to the next statement following the loop
statement.
If a statement in the loop
block throws an exception, this also stops execution of the block and--if the exception is uncaught--execution of the statements surrounding the loop
block. (You'll read about exceptions--Telescript's mechanism for reporting errors or exceptional conditions--later in this chapter.)
Please note that a loop
block executing for long periods of time is an excellent way to chew up processor resources with no useful results. A process that indiscriminately uses a loop
statement may find itself killed by the service provider whose resources it's wasting. If you use a loop
block for busy waiting (that is, testing constantly for a condition to change), you should consider using the getEvent
operation instead to wait for an event. (You'll learn about events--an advanced Telescript programming mechanism--in a future chapter.)
break
statements can only be used within iterative statements such as a loop
, while
, or for
statement. Their very simple syntax is:
break;When a
break
statement executes within an iterative block, it halts execution and skips execution to the next statement following the iterative statement. No statements following the break
statement within the block are executed.
Consider the following do
statement as an example:
do{ azure: = 0; loop { azure = azure+1; if azure>20 {break;}; }; "azure is legal now.".dump(); }The
loop
block executes 21 times. The 21st time it breaks with this result:
String: <azure is legal now.>
continue
statements can only be used within iterative statements such as a loop
, while
, or for
statement. Their syntax is as simple as that of the break
statement:
continue;When a
continue
statement executes within an iterative block, it halts execution and moves execution once again to the beginning of the iterative block, where the next iteration begins. You can use a continue
statement to skip unnecessary steps in a loop
block. Consider this example:
do{ azure: = 0; loop { azure = azure+1; if azure<19 {continue;}; "azure is approaching legal age.".dump(); if azure == 21 {break;}; }; "azure is legal now.".dump(); }When this statement executes, the
loop
block loops. On the 19th and 20th loops, the second block of the if/else
statement executes, printing an "approaching age" message. On the 21st loop, the else
block executes, halting loop
block execution so the last statement of the program executes. The results look like this:
String: <azure is approaching legal age.> String: <azure is approaching legal age.> String: <azure is legal now.>
while
statement. Its syntax is this:
while <boolean expression> {<statement(s)>};When a
while
statement executes, it evaluates the boolean expression. If it's true, the while
block executes and the expression is evaluated once again. As long as the expression is true, the while
block continues to execute. If the boolean expression is false, execution skips the while
block and continues with the next statement following the while
statement.
The following do
statement shows a while
statement at work:
do{ azure: = 0; while azure<5 { azure = azure+1; "azure is still less than five.".dump(); }; "azure has reached five.".dump(); }When executed, you see these results:
String: <azure is still less than five.> String: <azure is still less than five.> String: <azure is still less than five.> String: <azure is still less than five.> String: <azure is still less than five.> String: <azure has reached five.>
repeat
statement. The syntax is this:
repeat <integer> {<statement(s)>};When a
repeat
statement executes, it executes its block the number of times specified by the integer following the repeat
keyword. Although the integer is often a literal, it can be any expression that returns an integer.
The following do
statement shows a repeat
statement at work:
do{ repeat 4 {"Interation utterance".dump();}; "Iteration is finished.".dump(); }When executed, it produces the following results:
String: <Iteration utterance.> String: <Iteration utterance.> String: <Iteration utterance.> String: <Iteration utterance.> String: <Iteration is finished.>
for/to
statement if you want to loop through a block a set number of times and make the loop count available within the block. The for/to
statement uses the following syntax:
for <integer variable/property> to <integer> {<statement(s)>};The counter here can be either an integer variable or an integer property. If it's an integer variable (the most common case), it can be a variable that was declared previously to class
Integer
, or variable may be declared between the for
and to
keywords, in which case you use the following syntax:
for <integer variable>: Integer to <integer> {<statement(s)>};If the variable is declared using the second syntax, the variable is restricted in scope to the
for/to
block that follows. If the variable is declared outside the for/to
statement, as in the first syntax, its scope is determined by where it's declared. In any case, its scope includes--but isn't restricted to--the for/to
block that follows.
The integer value after the to
keyword is the number of times you want to execute the for/to
block. If the value is zero or a negative number, it specifies that the for/to
block won't execute at all.
When a for/to
statement is executed, the integer variable is assigned a value of 1, then the for/to
block is executed. The statements in the block can read the value of the integer variable. When the block is finished executing the first time, the integer variable is assigned a value of 2 and then the block executes a second time. After that the integer is assigned a value of 3 for the third execution, 4 for the fourth execution, and so on until the variable is larger than the integer value after the to
keyword. When that happens, execution jumps to the next statement following the for/to
statement.
It's important to note that an independent Telescript mechanism keeps track of the number of block iterations and assigns that number to the integer variable each time before the block executes. This means that if you change the value of the integer within the block (a bad programming practice in most cases), it doesn't affect the iteration count. To see how this works, look at the following do
block:
do{ counter: Integer; for counter to 4 { "New iteration.".dump(); counter.dump(); counter = counter+5; counter.dump(); }; "The for/to loop is finished.".dump(); counter.dump(); }Notice that the integer variable
counter
is initialized before the for/to
statement. Here are the results when the do
block is executed:
String: <New iteration.> Integer: <1> Integer: <6> String: <New iteration.> Integer: <2> Integer: <7> String: <New iteration.> Integer: <3> Integer: <8> String: <New iteration.> Integer: <4> Integer: <9> String: <The for/to loop is finished.> Integer: <9>If
counter
had been initialized within the for/to
statement like this
for counter: Integer to 4 {then the scope of the variable would be restricted to the
for/to
block. The compiler would object to the last statement in the do
statement because it tries to dump the variable counter
, which no longer exists outside the for/to
block.for/in
statements provide one iteration of a block for each item within a collection. A collection is any object that's a member of Collection
, a class you'll be introduced to in Chapter 9. For now, it's enough to know that a collection is a group of objects--a group of integers, for example, or of strings, agents, or even objects of mixed types.
This is the syntax of a for/in
statement:
for <variable/property> in <collection> {<statement(s)>};The variable or property after the
for
keyword should be one that is declared to be the same class or a superclass of every item in the collection. If the collection is a set of integers, for example, the variable or property should be declared to be class Integer
, class Number
, or class Object
. If you use a variable as the counter, it may be declared before the for/in
statement, in which case its scope extends beyond the for/in
block. Or the variable may be declared within the for/in
statement, just as a variable may be declared in a for/to
statement. In that case, it uses the following syntax:
for <variable>: <class ID> in <collection> {<statement(s)>};The scope of a variable declared this way is restricted to the
for/in
block.
The number of objects contained in the collection following the in
keyword determines the number of iterations of the for/in
block. When the for/in
statement executes, it assigns an object in the collection to the variable, then executes the for/in
block. When the block finishes, a second object in the collection is assigned to the variable, and the for/in
block executes a second time. This continues until an iteration of the block has occurred for each object in the collection. When all the objects in the collection have had an interation, the for/in
statement passes execution on to the next statement following. Because the for/in
variable points to a different item of the collection for each iteration, the block can work on that item during its iteration.
The following do
statement shows a for/in
statement at work:
do{ total: Number = 0; values: = Collection[Number,Equal](4, 5.2, 3.14159, 10); for itemValue: Number in values { total = total+itemValue; total.dump(); }; "The for/in loop is finished.".dump(); total.dump(); }In the third line of the program, notice that the expression assigned to the variable
values
is a collection object containing the numbers within parentheses. When executed, the statement has these results:
Integer: <4> Real: <9.2> Real: <12.34159> Real: <22.34159> String: <The for/in loop is finished.> Real: <22.34159>
The Telescript language contains many predefined subclasses of Exception, but you won't find them all listed with individual entries in the Telescript Language Reference for two reasons: They're too numerous to list individually, and many differ only in their class IDs. Some exceptions, such as TripException
, have extra features so they are listed under individual entries.
Telescript's predefined Exception subclasses are named to indicate a specific problem in execution. The Telescript language defines a subclass named DivisionByZero
, for example, that indicates a problem trying to evaluate a division expression where the divisor has a value of zero. You may, if you wish, define your own Exception subclasses with class IDs that signify special execution problems that may crop up in your custom operations. You can add attributes and operations to those subclasses to help report on the conditions that threw the exceptions.
throw
statement. It starts with the keyword throw
and is followed by an exception as shown in this syntax:
throw <exception expression>;For example, this
throw
statement throws the custom exception NoRainData
:
throw NoRainData();As you'll see in later chapters, a class's methods may often include
throw
statements to throw an exception because of conditions that exist in that location of the code. For example, the throw
statement with NoRainData
might sit after a test to see if a variable pointed to rain data, and before statements that would calculate new values using that rain data. If, during execution, the data isn't there, the throw
statement executes.
When a throw
statement executes, it immediately halts execution of the block where it occurs and throws the exception to an enclosing block if there is one (that is, if the executing block is a nested block within the enclosing block). The enclosing block may then catch the exception using mechanisms described later in this chapter. If the block doesn't catch the exception, then that block stops execution and throws the same exception to its enclosing block if there is one. In this way, an uncaught exception can percolate up through a chain of nested blocks until there are no more blocks to throw the exception.
As you'll remember, an object's methods are blocks of code. This means that methods can and do throw exceptions. If the method of a requester calls the method of a responder, the responder's method may throw an exception. If so, the responder's method halts execution and throws the exception to the requesting method, which may catch it. If it doesn't catch the exception, the requesting method halts execution and throws the same exception.
If one object requests an operation of another object that requests an operation of another object and so on to create a chain of operation calls, an exception thrown by the method of any one of those operations can--if uncaught--percolate all the way up to the original calling method. That method, as you'll learn later, will typically be the live
method of a process. If the exception is uncaught there, the engine kills the process.
To see how this works, consider the chain of operations shown in the numbered steps of Figure 7-1. The first object, a process, calls the planWeekend
operation on a second object. That operation in turn calls the checkClimate
operation on a third object, which calls checkRainfall
on a fourth object. checkRainfall
's method doesn't have the proper rainfall data, so it stops execution by throwing an exception of the class NoRainData
to checkClimate
. checkClimate
isn't set up to handle the exception, so it immediately stops execution by throwing the exception to planWeekend
, which doesn't catch the exception either. It stops execution by throwing the exception to the process, which doesn't handle the exception and is killed.
Figure 7-1 : An uncaught exception (NoRainData) percolates all the way back up a chain of operation calls, killing the process at the beginning of the chain.
The whole succession of percolating exceptions has a clear moral: Uncaught exceptions kill. Catching exceptions is an important part of writing Telescript code that won't crash and die ignominiously. As you may remember from Chapter 4, each operation in the class descriptions of the Telescript Language Reference lists any exceptions that it may throw. You can plan to catch those exceptions using the techniques described later in this chapter.
To see how an uncaught exception kills the execution of a do
statement, look at the following sample code with an attempted division by zero.
do{ "2-25".dump(); (2-25).dump(); "2.0-25".dump(); (2.0-25).dump(); "25/0".dump(); (25/0).dump(); "-4".dump(); (-4).dump(); "(4*5)-(2*5)".dump(); ((4*5)-(2*5)).dump(); }When the code executes, you get the following results:
String: <2-25> Integer: <-23> String: <2.0-25> Real: <-23> String: <25/0> <<<The process is killed at this point.>>>In this case, the
divide
operation of the class Number
(which is called when you used the /
operator) threw the exception DivisionByZero
. Because it was uncaught, it halted execution of the do
block. Notice that execution continued up to the point where the exception was thrown. All statements after the point of exception weren't executed.try
statement that's quite similar in syntax to an if/else
statement.try
statement uses this syntax:
try {<statement(s)>} catch <exception class ID> {<statement(s)>};The statement has two blocks. The first is the
try
block--the block of statements in which you expect an exception to be thrown. The second is the catch
block--the block you want to execute if an exception is thrown in the try
block. The exception class ID between the two blocks is the type of the exception you'd like to catch. If the exception thrown in the try
block isn't a member of this class, the exception isn't caught, execution stops immediately, and the exception percolates up. If the exception is a member of the class, the exception is caught and the catch
block executes.
When a try
statement of this form executes, the try
block begins execution. If it executes completely without any thrown exceptions, execution skips the catch
block and goes to the next statement following the try
statement. If the try
block throws an exception, the try
block stops execution and the exception is checked against the specified exception class ID. If the exception is a member of the specified exception class, the exception is caught; the catch
block executes and, when finished, passes execution on to the next statement following the try
statement. If the exception is not a member of the specified exception class, the exception isn't caught, execution ceases immediately, and the exception percolates up.
To see how a simple try
statement works, consider the following do
statement. It puts a previous example--the one that threw a DivisionByZero exception--in a try
block. It catches the exception and reports on that fact.
do{ try { "2-25".dump(); (2-25).dump(); "2.0-25".dump(); (2.0-25).dump(); "25/0".dump(); (25/0).dump(); "-4".dump(); (-4).dump(); "(4*5)-(2*5)".dump(); ((4*5)-(2*5)).dump() } catch Exception {"An exception was thrown and I caught it.".dump();}; "That's all, folks!".dump(); }Notice that in this
try
statement, the exception class ID is Exception
. That means that the catch
block will execute if any exception is thrown--because every exception is a member of the class Exception
. When the do
statement executes, these are the results.
String: <2-25> Integer: <-23> String: <2.0-25> Real: <-23> String: <25/0> String: <An exception was thrown and I caught it.> String: <That's all, folks!>The
try
block executes its statements, dumping to the screen, until it reaches line 8, which tries to divide by zero. It throws DivisionByZero
which halts execution of the try
block. The exception is checked against the specified exception class, which is Exception
. Because DivisionByZero
is a subclass of Exception
, the exception is caught and the catch
block executes, reporting on a thrown exception. When it finishes, execution moves to the next statement, which reports the end of the program.If lines 7 and 8 of the program were changed to divide by 5 instead of 0, you'd see these results:
String: <2-25> Integer: <-23> String: <2.0-25> Real: <-23.0> String: <25/5> Integer: <5> String: <-4> Integer: <-4> String: <(4*5)-(2*5)> Integer: <10> String: <That's all, folks!>That's because the
try
block executed without exceptions, so the catch
block was skipped.catch
block doesn't know what kind of exception was thrown, and can't take action accordingly. If you'd like to pass the exception into the catch
block so statements there can work on it, you can add an internal catch-block variable to the try
statement. The syntax is this:
try {<statement(s)>} catch <exception variable declaration> {<statement(s)>};This statement works just like the simple
try
statement discussed before with one difference: the exception class ID is replaced with an exception variable declaration. This declaration creates a variable that is declared to be a type of exception. For example, the declaration might be caughtEx: DivisionByZero
. The exception class ID specified for the variable is also the type of exception that is specified to be caught. That exception type sets the scope of exceptions that will be caught in the catch block.Telescript offers a second syntax to declare an internal catch-block variable. It encloses the declaration in parentheses:
try {<statement(s)>} catch (<exception variable declaration>) {<statement(s)>};Both syntaxes have the same effect.
When a try
statement with an internal catch-block variable executes and an exception is thrown and caught, the exception is assigned to the variable and the catch
block executes. The variable is restricted in scope to the catch
block, so when the catch
block is finished executing and execution passes to the next statement following the try
statement, the variable is dropped.
The following do
statement alters the first try
block example to use an internal catch-block variable:
do{ try { "2-25".dump(); (2-25).dump(); "2.0-25".dump(); (2.0-25).dump(); "25/0".dump(); (25/0).dump(); "-4".dump(); (-4).dump(); "(4*5)-(2*5)".dump(); ((4*5)-(2*5)).dump() } catch caughtIt: Exception { "An exception was thrown and I caught it. It's this:".dump(); caughtIt.dump(); }; "That's all, folks!".dump(); }The
try
statement now declares a variable of class Exception
, a broad enough class type to catch any kind of exception. When it runs, it has these results.
String: <2-25> Integer: <-23> String: <2.0-25> Real: <-23> String: <25/0> String: <An exception was thrown and I caught it. It's this:> DivisionByZero String: <That's all, folks!>This time through the
try
statement, the thrown DivisionByZero exception is caught and assigned to the variable caughtIt
. The last statement dumps caughtIt
so you can see what type of exception was caught.catch
blocks, which use the following syntax:
try {<statement(s)>} catch <exception type/exception variable declaration> {<statement(s)>} <more optional catch blocks> catch <exception type/exception variable declaration> {<statement(s)>};The exception type following the keyword
catch
in each catch
line can be either the class ID of an exception (as you saw in the simple try
statement example), or an exception variable (as you saw in the last try
statement example) if you want to work with the caught exception within the catch
block.
When a statement with multiple catch
blocks executes, it works very much like an if/else
statement with multiple else
blocks. If the try
block executes without any exceptions, none of the catch
blocks is executed. If the block throws an exception, however, it's checked against each catch
type in order. If it matches the catch
type, the catch
block associated with it is executed and the other catch
types aren't checked.
Generated with Harlequin WebMaker