This article is old and is being consolidated into
the book.
Please refer to the corresponding chapter(s) therein. If the chapters or
sections are not completed yet, you can use this article. Refer to the
examples as they are tested against the latest code.
AbstractJudoScript has primitive data types and data structures including arrays, linked
lists, structs, ordered maps, stacks and queues, and table data. Primitive
data types include number, string and date/time and secret. String can also
represent URL and file. Arrays can be sorted and filtered with custom
comparator and filter functions. The Objects' keys can be obtained in
array, either in undetermined order, or sorted and/or filtered by keys or
by the values. Comparator and filter functions are frequently defined as
lambda functions.
JudoScript is a dynamically typed language, meaning that its variables and constants
are not typed, but their values, however, belong to one of these supported
types: number, string, date and time, secret, compound data types (data
structures), Java objects and arrays, and built-in object types. Variables
are not required to be declared first. Value types are interpreted in the
program based on the context at runtime. For instance, a boolean expression
will interpret the values as integers. This articles discusses the primitive
types and data structures.
Variable names start with a letter, underscore or dollar sign ($);
and the following can include any of these as well as digits. They are
defined by being assigned a value:
a = 1;
Constant names start with a pound sign (#). They are defined like this:
const #PI = 3.1415927;
To check whether a constant has been defined or not, use the defined
operator:
if defined #PI { println #PI; }
The println command can be abbreviated as a dot (.).
JudoScript supports these primitive data types: integer, floating-point number,
string and date/time. Boolean values are just numbers: 0 stands for false,
non-zero for true. They can be assigned a true or false,
which are just 1 and 0. A character is a length-1 string. The null
value is numerically equivalent to 0 and textually empty. Numbers and
strings are called simple values; they have a set of common methods.
The following program demonstrates how values are used:
1: println 'A) a = 1: ':<18, a = 1;
2: println 'B) a = 1.00: ':<18, a = 1.00;
3: println "C) a = '1.00': ":<18, a = '1.00';
4: println 'D) a = 1.5e5: ':<18, a = 1.5e5;
5: println 'E) a = null: ':<18, a = null;
6: println ' a = 1.0:':<18, a = 1.0;
7: println 'F) a += 2: ':<18, a += 2;
8: println ' a = 1.1':<18, a = '1.1';
9: println 'G) a *= 4: ':<18, a *= 4;
10: println ' a = 10:':<18, a = 10;
11: println 'H) b = a / 2.5: ':<18, b = a / 2.5;
12: println ' a = 3.5:':<18, a = 3.5;
13: println ' b = 2.5:':<18, b = 2.5;
14: println 'I) a % b = ':<18, a % b;
15: println ' a = 5.0:':<18, a = 5.0;
16: println 'J) a - c = ':<18, a - c; // c is not defined.
17: println 'K) a + "abc" = ':<18, a + "abc";
18: println 'L) "abc" + a = ':<18, "abc" + a;
19: println 'M) "abc" @ a = ':<18, "abc" @ a; // concatenation
20: println ' a @= "xyz": ':<18, a @= "xyz"; // self concatenation
21: println 'N) a + "0x0a" = ':<18, a + "0x0a";
22: println 'O) a + "0x0m" = ':<18, a + "0x0m";
23: println 'P) a + "007" = ':<18, a + "008";
24: println 'Q) a + "008" = ':<18, a + "008";
25: println ' a = 5:':<18, a = 5;
26: println 'R) a + "0x0a" = ':<18, a + "0x0a";
27: println 'S) a + "0x0m" = ':<18, a + "0x0m";
28: println 'T) a + "007" = ':<18, a + "007";
29: println 'U) a + "008" = ':<18, a + "008";
30:
31: catch:
32: println 'EXCEPTION: ', $_.message;
33: resume;
If this is your first glance of any JudoScript code, it may look too much. But if
you look closely, it is really very natural. The start of script and lines 31 and 34 form a
try-catch block; when an exception happens, its message is printed and the
execution is resumed (line 33). I wish Java had resume statement,
too; you never know, someday it might. The major mechanism used in this
program is println, or a dot. It takes any number of parameters
and prints them one by one to the system standard output. The <18
notion is for formatting: it means "left-aligned with a width of 18". For
right- and center-alignment, it is >18 and *18. An
assignment expression returns the value itself.
Let us see the result of the run and dive into the gory details of
numbers, strings and expressions:
% java judo simple_vars.judo
A) a = 1: 1
B) a = 1.00: 1.0
C) a = '1.00': 1.00
D) a = 1.5e5: 150000.0
E) a = null:
a = 1.0: 1.0
F) a += 2: 3.0
a = 1.1 1.1
G) a *= 4: 4.4
a = 10: 10
H) b = a / 2.5: 4.0
a = 3.5: 3.5
b = 2.5: 2.5
I) a % b = 1
a = 5.0: 5.0
J) a - c = 5.0
K) a + "abc" = EXCEPTION: Invalid floating-point value
L) "abc" + a = EXCEPTION: Invalid floating-point value
M) "abc" @ a = abc5.0
a @= "xyz": 5.0xyz
N) a + "0x0a" = EXCEPTION: Invalid integer value
O) a + "0x0m" = EXCEPTION: Invalid integer value
P) a + "007" = EXCEPTION: Invalid integer value
Q) a + "008" = EXCEPTION: Invalid integer value
a = 5: 5
R) a + "0x0a" = 15
S) a + "0x0m" = EXCEPTION: Invalid integer value
T) a + "007" = 12
U) a + "008" = EXCEPTION: Invalid integer value
First of all, strings can be quoted either by single quotes or double
quotes (line 4). When single quotes are used, double quote characters
can appear as normal text and vice versa. Its concatenation operator
is @ (line 20). Integers can be expressed as decimal (line 3),
octal (starts with 0 but not "0x"; line 29) or hexadecimal (starts with
"0x" or "0X"; line 27). Floating-point numbers can be decimal and
fraction digits (line 7) or in scientific notion (line 5).
Lines 7 through 17 demonstrates the auto-detection of number types:
JudoScript always tries to use a higher-precision type, that is, floating
points over integers. Based on context, a string is evaluated to a
number by its "face value" (lines 22, 27 and 29); if the string is
not in a valid number format, an exception is thrown (lines 18, 19,
22 through 25, 28 and 30).
On line 9, etc., nl inside println stands for newline.
Lines 10 and 11 converts the numbers explicitly to float or integer. In
lines 14 through 20, method chr() (or char()) treats
a numeric value as a character value, where ascii() and
unicode() returns a character's numeric value. A character in
JudoScript is a length-1 string, so the character value of "abcdefg" is the
same as that of "a". Lines 23 through 26 demonstrate how to round up
floating point values to integers, and lines 27 through 32 for
formatting numbers. Lines 35 through 42 show the methods that convert a
number to a Java object; this is useful when calling a Java method that
requires precise data types among overloaded methods.
These methods are specifically for formatting numbers:
formatBool() or fmtBool(): returns "true" or
"false" if the value is interpreted as boolean true or false.
formatHex() or fmtHex(): returns the hexadecimal
representation of the integer number.
formatOctal() or fmtOctal(): returns the octal
representation of the integer number.
formatCurrency() or fmtCurrency(): returns the
currency representation for this value, using the JVM's default locale.
formatDuration() or fmtDuration(): returns the
English representation of the millisecond value in days, hours,
minutes and seconds.
A JudoScript string supports most Java's java.lang.String methods. In
addition, it has a number of its own convenience and special purpose
methods, because in JudoScript strings also represent many other things. We
discuss string processing methods first.
The csv() method turns a character-separated value into an array.
It takes a string as its parameter, all characters are separators; if
missing, uses comma. This method returns null for two consecutive
separators; this is different than java.util.StringTokenizer
which discards such values.
The isEmpty() and isNotEmpty() checks if a string is empty
or not. A string is considered empty if it is null or only contains whitespace
characters. Method neverEmpty() takes a default string parameter; if
the current string value is empty, that parameter is returned. If no parameters
specified, when empty it returns a space (which is "empty" itself but what
else?) The replace() and replaceIgnoreCase() methods replace
substrings with new ones, which is more powerful than Java counterpart
(replace(char,char)) that only replaces characters. The following is
an application dealing with HTML content:
The following example shows how to use file system commands to rename all
files with extension of ".htm" or ".HTM" to ".html".
list '*.htm, *.HTM'; // result is an array stored in $$fs_result
for x in $$fs_result {
if x.toLower().endsWith('.htm') {
rename x to x.replaceIgnoreCase('.htm', '.html');
}
}
This program prints out an ASCII table for 32 to 255:
1: cnt = 0;
2: println ' ', ' 0' @ (cnt++).fmtHex() repeat 16;
3: println ' ', ' --' {16};
4: for x from 2 to 15 {
5: local base = x << 4;
6: print x.fmtHex(), '0:';
7: for y from 0 to 0x0f {
8: local ch = base | y;
9: print ' ', ch.chr();
10: }
11: println;
12: }
Lines 2 and 3 print out the header. On line 2, the repeat clause
make that expression evaluated 16 times; on line 3, the shortcut form
{16} is used. The result for values less than 127 is listed below.
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: ! " # $ % & ' ( ) * + , - . /
30: 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
40: @ A B C D E F G H I J K L M N O
50: P Q R S T U V W X Y Z [ \ ] ^ _
60: ` a b c d e f g h i j k l m n o
70: p q r s t u v w x y z { | } ~
String has other methods. count() counts the occurrances of a
character within the string. regionMatches() and
regionmatchesIgnoreCase() does the same as java.lang.String.
getReader() returns a reader that uses the string as its input.
Programs can, for instance, read lines from a string.
In the make utility, feature here-doc is used to specify a chunk of text
to be enclosed in two user-defined markers and used verbatim by the script.
JudoScript supports the same feature with the extension that expressions can be
embedded.
lastname = "Robinson";
prodname = "Dry Cleaner";
representative = "Cleo Rubinstein";
letter = [[*
Dear Mr. (* lastname *),
Thank you very much for your interest in our product, (* prodname *).
For more information, please visit our web site at (* url *).
Sincerely,
(* representative *)
*]];
flush letter;
The markers are [[* and *]]. Embedded expressions are
enclosed in between (* and *). The lines' indentations
are removed, so is the first empty new line following the [[*
marker, so the code can appear nice and neat. Use [[[* instead
of [[* to turn off automatic indentation stripping.
date() takes parameters of year, month, day, hour, minute and second.
time() creates a date object with year, month and day fields all
initialized to 0. timeToday() is a convenience method that creates a
time in today.
Date values (objects) have a list of attributes, some are settable and others
read-only. See language specification
for a complete listing. It also have a method, formatDate() or
fmtDate(), which takes a java.text.SimpleDateFormat format
string and optionally a time zone ID, one of those returned by
java.util.TimeZone.getAvailableIDs(). If the format string is
null, use the default date/time format. The default date/time format
can be obtained or set by getDefaultDateFormat() and
setDefaultDateFormat() functions.
Secret values appear to be regular values but are used only by known parties,
such as statements that take passwords. If it is attempted to be printed, it
will throw illegal-access exception. To obtain one, use the system function
secret():
secret(encrypted_text [ ,decryptor ] )
The decryptor is an object that implements the method decrypt(),
which takes a string and returns another. It does not matter whether it is
implemented in JudoScript or Java, though most likely it is in Java. The encrypted
value must be a text string. How to obtain it is up to your crypto package
that your decryptor is part of. If no decryptor is specified, by default the
value is returned as-is for now. However, this behavior may change, that is,
a default decryptor may be used, which is specified in the runtime
environment. Let us see an example. Suppose you have run some utility and
encrypted your password to "XI,8aM4/", and the decryptor is a Java class.
Strings also represents URLs and file names. String has urlEncode()
(encodeUrl()), urlDecode() (decodeUrl()) methods.
It also has a parseUrl() method that parses a string into a number
of URL parts, returned in a struct. This program also demonstrates a feature
called class transposition, on line 23. Class instances can be
transposed into objects of another class, normally with more or new methods
than the current one.
You can treat a string as a file path and get various parts:
a = [ '/usr/bin/java', 'c:/temp/Test.java', '~/alfa', 'judo.jar' ];
for x in a {
println x.getFilePath(), ' : ', x.getFileName(), ' : ', x.getFileExt();
}
The following example shows string's file methods:
Array is one of the most important data structures. A JudoScript can contain any
variables, including numbers, strings, Java objects and other data structures
including arrays. To define or initialize an array:
arr = []; // empty array
arr = [ 1, 'abc', date(2001,10,1), javanew java.util.Hashtable() ];
arr = [ [ 1, 2 ], [ 3, 4 ] ]; // multi-dimension
arr = new Array; // another way to create.
arr = new Array( 1, 'abc', date(2001,10,1) );
Linked lists are desirable over arrays in certain algorithms for performance
reasons. In JuduScript both share a common interface; the only
difference is its declaration:
The length or size attribute is the number of elements.
To add elements, call its add() or append() with one or
more values; use prepend() to insert elements to the front; or use
insert() to insert at a particular position. Array index is 0-based.
You can merge all the elements from another array by appendArray()
or prependArray(). Obtain a portion of an array via subarray().
Method clear() removes all the elements, where remove()
removes an element at a specific location. To find the index of an element,
use indexOf(). reverse() is a convenience method that
reverses all the elements.
To enumerate all the elements in an array, do one of these:
for idx=0; idx < arr.length; ++idx {
local x = arr[idx];
println idx, ' ', x;
}
for idx from 0 to arr.lastIndex() {
local x = arr[idx];
println idx, ' ', x;
}
for x in arr {
println x;
}
If the index is not used, the last form is the easiest. If the index is
used, the second form is more concise.
Arrays can also be created via new operator. This may be useful
where {} is syntactically not allowed.
These methods collect some basic statistics about all the numeric elements
in the array: sum(), max(), min(), average()
or avg() and range() (the difference between the smallest
and largest values). If elements are all integers, the result is integer,
otherwise it is float.
JudoScript has some powerful sorting capabilities for arrays and linked lists via
the sort(). This method can take a user-defined comparator function
to control the ordering. By default it sorts according to the string
representation of the elements. Sorting is done locally, and the array itself
is returned.
1: a = [ '1.2', '3', '3.9', '1.10', '1.2.1', '2.3', '3' ];
2: a.sort( &my_comparator );
3: for i from 0 to a.lastIndex() { println i, ' ', a[i]; }
4:
5: function my_comparator(lhs, rhs) {
6: la = lhs.csv('.');
7: ra = rhs.csv('.');
8: for i from 0 to la.size() {
9: if la[i].int() < ra[i].int() { return -1; }
10: if la[i].int() > ra[i].int() { return 1; }
11: }
12: if la.size() == ra.size() { return 0; }
13: return la.size() > ra.size();
14: }
Lines 5 through 14 defines a custom comparator. Comparator functions must
take two parameters, and return -1, 0 or 1. On line 1, we have an array
of things that look like book sections. Normal string comparison fails to
yield correct order. Our comparator takes apart the section number to an
array (lines 6 and 7), and compares each parts. Line 2 shows how to pass
a function to a method: the ampersand notion. The result is:
0 1.2
1 1.2.1
2 1.10
3 2.3
4 3
5 3
6 3.9
As a convenience, JudoScript has sortAsNumber(), sortAtDate()
and sortAsString() methods. The last one is not really necessary,
just for the sake of naming consistency.
Sometimes you need to pick "qualified" elements in an array. JudoScript's array
filtering feature makes this easy. Array have a filter() that
takes a user-defined filter function. It can return a new array for the
qualified elements, or change the array locally.
Lines 1 through 4 shows how a filter function, which takes a parameter
and returns a boolean, is used by the filter() method. Line 2
prints a string representation of the array.
On line 6, the second parameter is true, so filtering is done
locally. The first parameter is an anonymous function mechanism, called
"lambda" function. Functions can be assigned to a variable, for instance,
Our last topic of this seciton is converting elements in arrays. This
is done by the method convert(), which, not surprisingly,
takes a conversion function that must accept one parameter and return
a value.
A struct and an ordered map are virtually the same compound data structures
that holds variable number of named attributes. In JudoScript there is no need to
define a struct; simply assign values to an attribute name. Therefore, "map"
may be a more appropriate name for struct. Objects and ordered maps are
created by the new operator, and may take 0 or more named
initializers. Attribute names do not have to be quoted unless they have
non-identifier characters. Ordered maps maintains the order of attributes
added when you retrieve them; structs do not. This is the only difference
between the two. Ordered map also has an indexOf() method that
works the same way as for arrays. An ordered map essentially serves the use
of an array (for the names) along with a name-value mapping, which can be
very handy in some situations.
a = new Object;
a = new OrderedMap;
a = new Object( name = 'Sanjay Deepak',
title = 'Software Engineer',
sex = 'm',
age = 34
);
a = new Object( 'last name' = 'Deepak',
'first name' = 'Sanjay',
title = 'Software Engineer'
);
To access an attribute, use the dot operator; if the attribute name has
non-identifier characters, have it quoted. If an attribute is not defined,
a null is returned.
println a.'first name'; // access by name
field = 'first name'; // access by expression
println a.(field);
To remove an attribute, use remove() or delete().
Method size() returns the number of attributes, and
clear() removes them all.
Method keys() returns all the keys in an array. It is used to
enumerate all attributes. Method values() returns an array of
all values.
a = new Object( alfa = 'A', beta = 'B', gamma = 'C', delta = 'D' );
for x in a.keys() {
println x, ' => ', a.(x);
}
Keys (attributes) or their values can be sorted or filtered. This is
a powerful feature that makes in-memory data processing extremely easy.
The methods are keysSorted(), keysFiltered(),
keysSortedByValue(), keysFilteredByValue() and
keysSortedAndFilteredByValue(). All of them may take a
comparator function and/or filter function. The following example shows
how to use the latter three to achieve some kind of in-memory queries.
1: empl = new Object;
2: empl.'0001' = new Object( name = 'Tony Nugyen', age = 33 );
3: empl.'0002' = new Object( name = 'Kathy Murphy', age = 42 );
4: empl.'0003' = new Object( name = 'Michael Chu', age = 29 );
5: empl.'0004' = new Object( name = 'Rick Policsi', age = 30 );
6: empl.'0005' = new Object( name = 'Sanjay Buch', age = 23 );
7:
8: age_comp = lambda lhs, rhs { return compare(lhs.age, rhs.age); };
9: age_flt = lambda elem { return (elem.age >= 30); };
10:
11: println 'List employees by age in descending order:';
12: listThem( empl.keysSortedByValue(age_comp) );
13:
14: println 'List employees older than 30 years old:';
15: listThem( empl.keysFilteredByValue(age_flt) );
16:
17: println 'List employees older than 30 years old in descending order:';
18: listThem( empl.keysFilteredAndSortedByValue(age_flt,age_comp) );
19:
20: function listThem keys {
21: for x in keys {
22: local emp = empl.(x);
23: println emp.age, ' ', emp.name;
24: }
25: }
Lines 1 through 6 create an Object whose attributes are keys and values
are a struct containing various pieces of information. Line 8 defines a
comparator function that compares values with the system function
compare(); line 9 defines a filter function that filters the
values also. These two are used in lines 12, 15 and 18 for various sorting
and filtering combinations. As you see, tabular data in memory can be
filtered and sorted based on any "column" or "columns" -- it is all in
the comparator and filter function you implement.
Review Questions
What is the difference between a struct and an ordered map?
If an attribute name starts with a digit, how this attribute
can be initialized when creating a struct?
What is the syntax to access an attribute given a variable
for its name?
Are attribute names limited to strings? What about their values?
How to enumerate through all the attributes in a struct or
an ordered map.
For a struct, how do you get all keys in a specific order?
How to get an array of keys in the order based on its values
and/or qualified based on certain criterion?
Stacks ane queues are essential to deal with recursive data structures
(such as trees and graphs) and algorithms. To create a stack or queue,
use the new operator.
Stacks have these methods: size(), clear(), push(),
pop(), peek(), peekAt() and isEmpty().
Queues have these methods: enque() or eng(), deque()
or deq(), head(), tail() and isEmpty().
The next program traverses a tree in two ways.
1: class Node
2: {
3: constructor {
4: assert(name != null); // should be in the initializer
5: children = [];
6: }
7: function toString { return name; }
8: function addChild child { children.add(child); }
9: }
10:
11: // constuct a tree
12: root = new Node(name='ROOT');
13: a = new Node( name='A' );
14: a.addChild(new Node(name='A1'));
15: a.addChild(new Node(name='A2'));
16: root.addChild(a); // left subtree
17: a = new Node( name='B');
18: a.addChild(new Node(name='B1'));
19: a.addChild(new Node(name='B2'));
20: a.addChild(new Node(name='B3'));
21: root.addChild(a); // right subtree
22:
23: dfs(root);
24: bfs(root);
25:
26: function dfs root {
27: print 'Depth-first traverse: ';
28: stk = new stack;
29: stk.push(root);
30: while !stk.isEmpty() {
31: node = stk.pop();
32: print node, ' ';
33: for x in node.children backward { stk.push(x); }
34: }
35: println;
36: }
37:
38: function bfs root {
39: print 'Breadth-first traverse: ';
40: que = new queue;
41: que.enq(root);
42: while !que.isEmpty() {
43: node = que.deq();
44: print node, ' ';
45: for x in node.children { que.enq(x); }
46: }
47: println;
48: }
Lines 1 through 9 defines a class. In the constructor, lines 4 mandates
that an attributed called "name" must be initialized, and line 5 creates
an array attribute "children". A stack is used for depth-first traversal,
and a queue for bread-first. On line 33 the for loop runs in backward
order so the "left" child is processed first. The tree and the result of
run is shown below:
ROOT
/ \
A B
/| / | \
A1 A2 B1 B2 B3
Depth-first traverse: ROOT A A1 A2 B B1 B2 B3
Breadth-first traverse: ROOT A B A1 A2 B1 B2 B3
Review Questions
Use a stack to reverse an array (without resorting to array's
reverse() method).
JudoScript variables and constants are weakly typed. The primitive types include
integer, floating point number, string and date/time and secret. Data
structures include struct, ordered map, array, linked list, stack and
queue. Other variables include Java objects (and arrays) and built-in
objects. Numbers and strings can be used interchangeably in expressions.
They can be explicitly converted to intended types. Numbers and strings
have a set of methods that covers conversion, formatting, mathematical,
and string processing. Strings also represent URLs and file, they have
methods like parseUrl(), fileExists(), etc.
A chunk of text can be used via the enhanced here-doc mechanism. Text can
be aligned and the indentation is stripped. Expressions can be embedded.
Date and time values can be created in various ways. They have a number of
attributes, some are writable.
Arrays and linked lists share the same interface. The only difference is
their creation. They can be initialized; elements can be added to the end
or front. Other arrays/lists can be merged in. The best way to enumerate
an array is the for..in statement. A number of basic statistics
method helps the numeric processing. They can be sorted or filtered with
user-defined comparator or filter function. Such functions are often
declared as lambda functions.
Objects and ordered maps stores a number of name-value mapping. Ordered
maps maintain the order of the keys that were added to it, where structs
don't. The keys can be returned as an array, which is the way to
enumerate sturcts; they may be returned sorted or filtered (by user-defined
functions); they can even be sorted and/or filtered based on the values.