|
Block | ::= | { ( Statement )* } |
Blocks can also be a part of other compound statements that are discussed in this chapter. Blocks can also have catch
and finally
clauses, discussed in Exception Handling.
Compound statements
Compound statements are those that may contain or require other statements for processing based on different conditions and/or in different situations. The enclosed statements are usually organized in block statements. In the general programming area, the flow control statements (such as if-else and loops) are all compound statements. Many functional statements are also compound statements.
Event-driven statements
Some of Judo's functional statements use an event-driven programming model; those statements are event-driven statements. For instance, the guiEvent
statement, the SGML and XML statements. The following is an example of SGML statement:
do 'http://www.yahoo.com' as sgml { <a>: if $_.href != null { println $_.href; } <img>: println $_.src; }
In this SGML statement, each SGML tag encountered is treated as an "event"; you as a programmer provides an event "handler", which is a series of statements, for each "event".
The if-else statement has this syntax:
IfElse | ::= | if Expr Block ( ( else if | elif ) Expr Block )* [ else Block ] |
The Expr's are evaluated to boolean values; if true
, the chunk of code that follows will be executed. If-else statement is straightforward and is one of the most frequently used statement. There is only one catch: the blocks following if
, elif
and else
expressions do not define new scopes. If you declare local variables in these blocks, the scope for these variable is actually the scope that contains the if-statement. To avoid confusion, do not declare local variables in any of these blocks. For instance,
if foo() { var tmp = 5; println 'foo() returns true.'; } else { println 'foo() returns false.'; } println tmp;
In the last line, tmp
may or may not be defined, depending on the boolean result of the foo()
call. A better way is to move the var tmp
declaration out.
The syntax for the switch statement is:
Switch | ::= | switch Expr { ( CaseClaus )* } |
CaseClause | ::= | ( case Expr | default ) : ( Statement )* |
No more than one default
clause is allowed. The Expr following switch
is evaluated and checked against values specified in each case
clause; if a match is found, the chunk of code is executed; if no match is found and the default
clause is present, execute the chunk of code for default
; otherwise, simply finish the statement.
The values in the case
clauses are usually constant values but actually can be any expressions; the value types can be anything, such as primitive types like integer, number, string, date/time, and Java objects. The match between the switch
value and case
values tries the comparisons with different types and is carried out in this order: Java object, number and date/time, and string. The following example demonstrates this:
Listing 6.1 stmt_switch.judo |
---|
values = [ 9, Date(2001,11,20), new java::HashMap, 'uvw', 'XYZ', '{}' ]; for a in values { switch a { case 9: println 'CASE OF number: ', a; break; case 'uvw': println 'CASE OF string: ', a; break; case Date(2001,11,20): println 'CASE OF Date: ', a; break; case new java::HashMap: println 'CASE OF Java: ', a; break; default: println 'DEFAULT: ', a; break; } } |
The values
is an array of values of different types; each value is run against the switch statement. The result is:
CASE OF number: 9 CASE OF Date: 11/20/01 12:00 AM CASE OF Java: {} CASE OF string: uvw DEFAULT: XYZ CASE OF Java: {}
What happened to the value of '{}'
? It is a string and happens to match new java::HashMap
because they both share the same stringn representation, {}
. In most situations, the case
values are numbers or strings.
Statements for each case
and default
normally ends with a break
statement. If no break
statement is present at the end, the program continues on to the statements in the next case
or default
; you can have no statements for certain case
and/or default
clauses to share a same chunk of code. For instance,
switch x { case 1: case 2: case 3: println 'In [1,3]'; break; case 4: case 5: case 6: println 'In [4,6]'; break; default: println 'Outside of [1,6];'; break; }
Note that the case
values do not have to be constants; they can be any expression. This may be used for dynamic matching. The following example uses the switch statement in a function (which will be discussed shortly in this chapter); it is not a practical function for the purpose of its name.
Listing 6.2 stmt_switch2.judo |
---|
for i from 0 to 100 { isPowerOf i, 3; } function isPowerOf x, base { base2 = base * base; base3 = base2 * base; base4 = base3 * base; base5 = base4 * base; base6 = base5 * base; switch x { case 0: case base: case base2: case base3: case base4: case base5: case base6: println x, ' is a power of ', base; break; } } |
The result is:
0 is a power of 3 3 is a power of 3 9 is a power of 3 27 is a power of 3 81 is a power of 3
The while and do-while statements have the following syntax:
While | ::= | while Expr Block |
DoWhile | ::= | do Block while Expr ; |
The meaning of these two statements are self-evident. The following example tests both:
Listing 6.3 stmt_while.judo |
---|
println 'while() {}'; a = 3; while a >= 0 { println a; --a; } println; println 'do {} while.'; do { println a; a++; } while a < 2; |
The result is:
while() {} 3 2 1 0 do {} while. -1 0 1
Judo has three for loop statements.
The generic for loop
The syntax for the generic for loop is:
ForLoop | ::= | for [ ( ] [ ExprList ] ; [ Expr ] ; [ ExprList ] [ ) ] Block |
ExprList | ::= | Expr ( , Expr )*
|
This form of for loop is the same as the for statement in Java or C/C++. It runs the first list of expressions sort of like initialization, then check on the boolean expression in the second place; if true
, the block is executed, and at the end the expression list in the third place is executed. So, for the following pseudocode:
for init(); cond(); postproc() { do_work(); }
it is equivalent to:
init(); while cond() { do_work(); postproc(); }
This is an example:
Listing 6.4 forloop.judo |
---|
for a=0; a<3; ++a { println a; } |
The for-from-to loop
This statement is to loop through a series of numbers as specified. The syntax is:
ForFromTo | ::= | for [ ( ] INDENTIFIER [ from Expr ] ( do | downto ) Expr [ step Expr ] [ ) ] Block |
The loop number can be ascending (with the to
clause) or down (with the downto
clause). If the from
clause is missing, by default it starts with 0
. If the step
is not specified, the step is defaulted to 1
. Step can never be negative; if negative value is specified, its absolute value is used instead. The following program demonstrates varios usages:
Listing 6.5 for_fromto.judo |
---|
print 'for [from 0] to 3: '; for a to 3 { print a, ' '; } println; print 'for from 3 to 5: '; for a from 3 to 5 { print a, ' '; } println; print 'for from 3 to 10 step 2: '; for a from 3 to 10 step 2 { print a, ' '; } println; print 'for from 10 downto 5 step 2: '; for a from 10 downto 5 step 2 { print a, ' '; } println; |
The result is:
for [from 0] to 3: 0 1 2 3 for from 3 to 5: 3 4 5 for from 3 to 10 step 2: 3 5 7 9 for from 10 downto 5 step 2: 10 8 6
The for-in loop
The for-in statement is to iterate through collection data structures. Collections include built-in data structures such as Array
, LinkedList
, Set
and Object
. Java arrays and instances of java.util.Iterator
, java.util.Enumeration
, java.util.Collection
, java.util.List
and java.util.Map
can also be iterated with for-in statement. In the case of Judo Object
and Java java.util.Map
subjects, their keys are iterated. The syntax of for-in statement is:
ForIn | ::= | for [ ( ] INDENTIFIER in Expr [ backward ] [ from Expr ] ( do | downto ) ExprList [ step Expr ] [ ) ] Block |
The backward
and from
/to
apply only to fixed-length collections such as Array
, Java arrays and java.util.List
instances. The following example shows some of the usages:
Listing 6.6 |
---|
println 'Iterating Array:'; a = [ 1, 2, 3 ]; for x in a { println loopIndex(), ': ', x; } println nl, 'Iterating Java array:'; a = new java::int[]{ 10, 20, 30 }; for x in a { println loopIndex(), ': ', x; } println nl, 'Iterating java.util.ArrayList:'; a = new java::ArrayList; a.add(100); a.add(200); a.add(300); for x in a { println loopIndex(), ': ', x; } println nl, 'Iterating with range:'; a = [ 1, 2, 3, 4 ]; print "--- for in "; for e in a { print e, ' '; } print nl, "--- for in backward "; for e in a backward { print e, ' '; } print nl, "--- for in from 1 "; for e in a from 1 { print e, ' '; } print nl, "--- for in to 1 "; for e in a to 1 { print e, ' '; } print nl, "--- for in from 1 to 2 "; for e in a from 1 to 2 { print e, ' '; } print nl, "--- for in from 2 downto 1 "; for e in a from 2 downto 1 { print e, ' '; } print nl, "--- for in downto 1 "; for e in a downto 1 { print e, ' '; } println; |
The result is:
Iterating Array: 0: 1 1: 2 2: 3 Iterating Java array: 0: 10 1: 20 2: 30 Iterating java.util.ArrayList: 0: 100 1: 200 2: 300 Iterating with range: --- for in 1 2 3 4 --- for in backward 4 3 2 1 --- for in from 1 2 3 4 --- for in to 1 1 2 --- for in from 1 to 2 2 3 --- for in from 2 downto 1 3 2 --- for in downto 1 4 3 2
The repeat statement
If you simply want to repeat certain operations for a number times, the repeat statement is an easy one.
Repeat | ::= | repeat Expr [ times ] Block |
The break and continue statements
In loops, you can use the break statement to break the loop, or continue statement to skip the rest of the statement in the current iteration and go to the start of next iteration. The syntax for these statements is:
BreakContinue | ::= | ( break | continue ) [ IDENTIFIER ] ; |
The optional IDENTIFIER is a label name for a loop to break or continue from within a nested loop. The following is an example:
Listing 6.7 breakcont.judo |
---|
aloop: for i from 0 to 5 { for j from 1 to 5 { if i+j < 5 { continue aloop; } else if i+j >= 10 { break aloop; } println i, '-', j; } } |
The result is:
4-1 4-2 4-3 4-4 4-5 5-1 5-2 5-3 5-4
Loop index
All loop statements, including the general programming flow control and special-purpose statements to be discussed later in the book, have a loop index. The loop index starts at 0
for the first iteration and increments thereafter. This loop index is accessible via the system function, loopIndex()
. It can take a negative integer number to return the loop index of the enclosing loop statement in case of nested loops.
Listing 6.8 loopidx.judo |
---|
for i from 1 to 3 { for j from 2 to 5 { println ' outer loop: ', loopIndex(-1), ' inner loop: ', loopIndex(); } } |
The result is:
outer loop: 0 inner loop: 0 outer loop: 0 inner loop: 1 outer loop: 0 inner loop: 2 outer loop: 0 inner loop: 3 outer loop: 1 inner loop: 0 outer loop: 1 inner loop: 1 outer loop: 1 inner loop: 2 outer loop: 1 inner loop: 3 outer loop: 2 inner loop: 0 outer loop: 2 inner loop: 1 outer loop: 2 inner loop: 2 outer loop: 2 inner loop: 3
This loopIndex()
function can be quite useful in loops that do not have an explicit loop indicator; without it, sometimes you would need to use an explicit counter variable. For instance, the following program prints out lines starting at line number 5 in a file:
do file_name as lines { if loopIndex() < 5 { continue; } println $_; }
The do .. as lines statement is covered later.
Judo functions organize code into reusable units. A function can take 0 or more parameters, and return a value. They can be built-in system functions or user-defined functions. The syntax for user-defined function declaration is:
Function | ::= | function IDENTIFIER [ ( ] params [ ) ] { ( Statement )* } |
params | ::= | param ( , param )* [ , .. ] | .. | < Expr > |
param | ::= | IDENTIFIER [ = Expr ] |
The same syntax is used for methods within user-defined classes; there are some fundamental differences between functions and class methods, which will be discussed in chapter 10. Object-Oriented Programming.
The parentheses around parameters are not required; if parentheses are used, the right parenthesis must match the left one. Functions always return a value; the return
statement can return a value and exit the function. If no return
statements are called, the function code will fall out of scope and exit, returning undefined
. Functions, like any declarations in Judo, can appear anywhere, that is, you can call a function before it is declared. The following is the famous Hanoi Tower problem followed by the result of a test run.
Listing 6.9 hanoi.judo |
---|
cnt = 0; hanoiTower(4, 'A', 'B', 'C'); function hanoiTower N, src, aux, dst { if N == 0 { return; } hanoiTower(N-1, src, dst, aux); println 'step ', ++cnt :>2, ': ', src, ' => ', dst; hanoiTower(N-1, aux, src, dst); } |
The Tower of Hanoi puzzle was invented by the French mathematician Edouard Lucas in 1883. There are three pegs; initially a number of disks of different sizes are stacked on one peg; the objective is to transfer the entire tower to another peg, moving only one disk at a time and never a larger one on top of a smaller. This is a well-known recursive algorithm. In hanoi.judo
, we used a global variable, cnt
, to track the history of the transfers. The following is the result of moving an initial tower of 4 disks.
step 1: A => B step 2: A => C step 3: B => C step 4: A => B step 5: C => A step 6: C => B step 7: A => B step 8: A => C step 9: B => C step 10: B => A step 11: C => A step 12: B => C step 13: A => B step 14: A => C step 15: B => C
Function parameter names are the same as variable names. Parameters may have default values. Function calls can take any number of parameter values. If the number of passed values is less than the number of declared parameters, the missing parameters take the default values in the function declaration; if no default values specified, null
is used. If the number of passed values is more than the number of declared parameters, the extra parameters are stored in an array in the predefined local variable, $$args
.
To explicitly declare that a function takes variable number of parameters, you may end the parameter list with two dots (..
); this has no further meaning.
During function calls, parameters are pass-by-value for primitive Judo data types (including integer, number, date and time and string), and pass-by-reference for other object types.
Listing 6.10 var_params.judo |
---|
function inc x, delta=1, .. { ret = x + delta; if $$args != null { for x in $$args { ret += x; } } return ret; } println inc(2); println inc(2,2); println inc(2,2,2); function sum .. { ret = 0; for x in $$args { ret += x; } return ret; } println sum(1,2,3,4,5,6,7,8,9); function oneparam a { println '---', nl, 'The parameter: ', a; if $$args != null { println unit($$args.length, 'useless parameter'); } } oneparam('abc'); oneparam('abc', x, y, z); |
The result is:
3 4 6 45 ---- The parameter: abc ---- The parameter: abc 3 useless parameters
Judo allows you to dynamically create a parameter list in an array, and use that array as function call parameters through the {{ }}
syntax.
Listing 6.11 dyn_params.judo |
---|
function sentence subj, verb, obj, .. { print subj, ' ', verb, ' ', obj; for x in $$args { print ' ', x; } println '.'; } params = [ 'James', 'wrote', 'Judo' ]; sentence( {{params}} ); params = [ 'He', 'uses', 'Judo', 'day-in', 'and', 'day-out' ]; sentence( {{params}} ); |
In the example, the functionsentence()
prints out all its parameters separated by spaces. The first three parameters are named, and it handles variable number of parameters. When called with dynamic parameters in an array, the elements of array are taken as individual parameters and the result of execution is:
James wrote Judo. He uses Judo day-in and day-out.
A function variable is a variable that references a function. A function reference is obtained by the &
operator. They can be assigned to variables or passed as paramter values to other functions.
To call a function reference stored in a variable, you can simply invoke the function with the variable name. If the function reference is stored in an array element, use that array element. If the function reference is stored as a value for a key in an Object
, use the key name to invoke the function. The following example demonstrates all these situations.
Listing 6.15 fxn_var.judo |
---|
function foo1 a, b { return a + b; } function foo2 a, b { return a * b; } function whichFoo f, a, b { return f(a,b); } println 'whichFoo(&foo1, 2, 3) = ', whichFoo(&foo1, 2, 3); println 'whichFoo(&foo2, 2, 3) = ', whichFoo(&foo2, 2, 3); x = [ &foo1, &foo2 ]; println 'x = ', x; println 'x[0](2, 3) = ', x[0](2, 3); println 'x[1](2, 3) = ', x[1](2, 3); y = { fxn1 = &foo1, fxn2 = &foo2 }; println 'y = ', y; println 'y.fxn1(4, 6) = ', y.fxn1(4, 6); println 'y.fxn2(4, 6) = ', y.fxn2(4, 6); |
The only ambiguous situation is that, if there is a function defined with the same name as a variable that holds a function reference, the defined function will be accessed. To remedy this situation, Judo uses the ->
operator to explicitly invoke a function referenced in a variable as follows:
function foo1 a, b { return a + b; } function foo2 a, b { return a * b; } function whichFoo f, a, b { return f(a,b); } println 'whichFoo->(&foo1, 2, 3) = ', whichFoo->(&foo1, 2, 3); println 'whichFoo->(&foo2, 2, 3) = ', whichFoo->(&foo2, 2, 3);
Function variables are important in Judo. Many data structures have methods that take particular function variables to do tasks such as sorting, filtering and transformation. For instance, Array
's sort()
method can take a function reference which takes two parameters and return 1
, 0
or -1
as the result of comparison; its filter()
method can take a function reference which takes one parameter and returns true
or false
. The following example shows how to do a custom sorting on array elements.
Listing 6.13 custom_sort |
---|
a = [ '1.2', '3', '3.9', '1.10', '1.2.1', '2.3', '3' ]; a.sort( &my_comparator ); for x in al { println x; } function my_comparator(lhs, rhs) { la = lhs.csv('.'); ra = rhs.csv('.'); for i from 0 to la.size() { if la[i].int() < ra[i].int() { return -1; } if la[i].int() > ra[i].int() { return 1; } } if la.size() == ra.size() { return 0; } return la.size() > ra.size(); } |
This program prints the array list as book section numbers. The result is:
1.2 1.2.1 1.10 2.3 3 3 3.9
Another good example is the toCsv()
of the Array
object. The following code converts an array of values into comma-separated, quoted strings, which can be used in SQL's where clause as the values for the in expression:
last_names = [ 'Olajuwon', 'Yao' ]; in_expr = last_names.toCsv(',', function(x){ return "'"+x+"'"; }); exeucteQuery qry: SELECT * FROM emp WHERE last_name IN ( (* in_expr *) ) ;
The resultant SQL statement is:
SELECT * FROM emp WHERE last_name IN ( 'Olajuwon', 'Yao' )
The SQL scripting is introduced in chapter 22. JDBC (SQL) Scripting.
With function variables, functions do not always need names. Anonymous functions can be declared without names; in this case, parentheses around parameters are required, and it must be assigned to a location.
Listing 6.14 filter.judo |
---|
a = [ 'a', 'ab', 'abc', 'abcd', 'abcde', 'abcdef', 'abcdefg' ]; f = function(elem){ return elem.length() >= 5; }; a.filter(f,true); println a; |
In the filter()
call, the first parameter is a filter function. The second parameter of true
indicates the filtering is done locally; if false
, a new array is returned with the filtered elements. The filtering is for strings over 5-character long. The result is:
[abcde,abcdef,abcdefg]
Another way to make new functions is to alias Java class static methods. Aliasing Java static methods is also discussed in Alias Java Static Methods as Functions. The syntax is:
Once a Java static method is aliased to be a function, that function can be used almost like a native Judo function:
function now for java::System.currentTimeMillis(); function loadLibrary for java::System.loadLibrary(); millis = now(); loadLibrary("native.dll");
Because the function aliases are indeed Java methods, the method parameters are fixed and strongly typed. You have to pass exactly the same number of parameters, and the parameter values should be compatible with the declared parameter types, and sometimes explicit casting may be needed. See Alias Java Static Methods as Functions for more details.
Judo has a number of system functions for various purposes. They are actually the methods of an internal $$sys
object but $$sys
is not required to use them. This is called method shortcut. In addition to $$sys
, shortcut methods also exists for internal object $$con
(the default database connection). In Judo, values and objects all have methods. The mathematical functions, for instance, are methods of numeric values. System functions are those that do not belong to any objects but useful system-wide.
The system functions fall into the following categories: values and numbers, system properties and related, system controls, system standard input-output, file input-output and generic. Refer to appendix 3. Built-In System Functions for a complete listing.
Judo can catch runtime exceptions, either from the language engine or from Java. The try-catch-finally syntax has two equivalent forms:
BlockCatchFinally | ::= | { ( Statement )+ [ catch [ IDENTIFIER ] : ( Statement )+ ] [ finally : ( Statement )+ ] } |
TryCatchFinally | ::= | try Block [ catch [ IDENTIFIER ] Block ] [ finally Block ] |
In the second form, at least one of catch
and finally
clauses must appear. The following is an example:
try { xxxxx(); // doesn't exist and will fail. } catch ex { println 'Catch: ', ex; } finally { println 'Finally.'; }
The first form is compact and easy; the second form is more Java-like and elegant but verbose. The choice is totally up to your style. The block form can also be applied to bodies of functions and compound statements. Note that if-else and switch statements' blocks can not have catch
and finally
clauses, as these blocks are just syntactic blocks and not block statements.
The catch
clause can take a variable name to hold the exception object; if not specified, $_
is used, as is the case in the following example:
{ xxxxx(); // doesn't exist and will fail. catch: println 'Catch: ', $_; finally: println 'Finally.'; }
In the global scope, you can have one catch
and one finally
clauses:
xxxxx(); // doesn't exist and will fail. catch: println 'Catch: ', $_; finally: cleanup();
The exception object in the catch
clause is a built-in object. For Java exceptions, the exception object wraps the Java exception, and all the Java exception methods can be accessed. The Judo exception object has these properties:
Name | Description |
---|---|
line | the line number in the script where the exception happened. |
file | the file name of the script in which the exception happened. |
message | the message for the exception. |
name | the internal name of the exception. |
type | the internal type of the exception. |
The most frequently used properties are line
and message
.
The Throw and Resume Statements
The throw and resume statements are two exception-related statements in Judo. The throw statement throws an exception that transfers the control to the immediate catch clause, or it aborts the program. Its syntax is:
Throw | ::= | throw [ Expr ] ; |
The Expr should be evaluated to be either an exception object (including Java exception object) or a string. If a string or no value is specified, a USER_EXCEPTION
is thrown.
The resume
statement is used only during handling exception in the catch
clause. It resumes the execution after where the exception has happened. If it has no effect if it appears in non-catch
code. It takes no parameters:
Resume | ::= | resume ; |
The following test demonstrates most of the exeption handling topics, including the exception object properties, throw and resume statements, and catch
and finally
in local and global scopes.
Listing 6.15 excpt_test.judo |
---|
{ xxxxx(); // method does not exist println 'Not reached.'; catch: println '[Line ', $_.line, '] INSIDE CATCH: <', $_.name, '> ', $_.message; throw $_; finally: println 'INSIDE FINALLY.'; } throw; throw new java::Exception("This exception is pure Java."); throw "ABCDE"; catch: println '[Line ', $_.line, '] OUTSIDE CATCH: <', $_.name, '> ', $_.message; resume; |
The output is:
[Line 2] INSIDE CATCH: <METHOD_NOT_FOUND> Function xxxxx() not found. INSIDE FINALLY. [Line 2] OUTSIDE CATCH: <METHOD_NOT_FOUND> Function xxxxx() not found. [Line 12] OUTSIDE CATCH: <USER_EXCEPTION> [Line 13] OUTSIDE CATCH: <JAVA_EXCEPTION> This exception is pure Java. [Line 14] OUTSIDE CATCH: <USER_EXCEPTION> ABCDE
The catch
and finally
clauses do not create new scopes; that is, they are all in the same scope as the whole block. In the format of try { .. } catch { .. } finally { .. }
, this may not be obvious.