ObjectScript is a full featured object oriented scripting language, which combines the powerful features of a full blown programming language such as Java with the ease of use of a scripting language. It supports functions (which can be closures), classes, inheritance, synchronization (for multi-threaded programs) and garbage collection. Also, an ObjectScript program can take advantage of exising Java APIs by creating instances of Java classes, calling methods of Java objects, and even implementing Java interfaces or extending Java classes.
To run the interpreter in stand-alone interactive mode, you must gather
the
> java -jar oscript.jar -i OscriptParser: Reading from standard input. . . % writeln("Hello World!") Hello World! %
In this example, the writeln function is used to print a string to stdout. Alternatively, a script program can be loaded from a file:
> cat hello.os writeln("Hello World!"); > java -jar oscript.jar hello.os OscriptParser: Reading from file hello.os. . . Hello World! >
The ObjectScript interpreter provides a handful of builtin types:
true
or false
length()
method returns the current
length of the array. An array will automatically grow
to accomodate the n'th element. Array indices start at
0. Arrays can be declared with the Array
constructor, or with the more compact square bracket
syntax: [1,2,3]. Arrays can be accessed using the []
or [..]
operators, such as arr[0]
which will evaluate to the first element in the array, or
arr[0..5]
which will return the sub-array
containing the 0'th thru 5'th (inclusive) elements of the
array.
[]
and [..]
operators. The length()
method returns the length of the string.
You might want to take some time to experiment with the built-in types:
% 1 => 1 % 1 + 2 => 3 % "a" + "b" => ab % "ab"[1] => b % "abc" == "abc" => true % "abc" == "def" => false # "abc"[1..2] => bc % var a = [ 1, 2.0, "c" ] % a => [1, 2.0, c] % a.length() => 3 % a[2] => c % a[1..2] => [2.0, c] % new Array( 1, 2.0, "c" ) => [1, 2.0, c] % "abc".length() => 3 % 1.getType() => ExactNumber % var type = (3 + "c").getType() % type => String % new type("foo") => foo
It is worth noting that, everything is just an object. The literal number
1
is an object, the statement (3 + "c")
evaluates to an object. All objects define a getType()
method, which returns the type of the object (which itself is an object.)
ObjectScript uses C-like control structures, so if you are familiar with C, C++, Java, JavaScript, etc., you should find ObjectScript's control structures familiar.
The if-else
construct is used for decisions. The formal syntax is:
if ( expression ) statement1 else statement2
The else
part is optional. When an if-else
statement
is evaluated, the expression is evaluted, and if it's result is
true
, then statement1 is executed, otherwise
(if present) statement2 is executed. It is an error for
expression to evaluate to any value other than true
or
false
. It is possible that statement2 is
itself an if-else
statement:
// this file saved as if-else-1.os if( x > y ) a = 1; else if( x == y ) a = 0; else a = -1;
Because the else
is optional, there can be an ambiguity with nested
if
statements:
// this file saved as if-else-2.os if( x > y ) if( c < d ) foo(); else bar();
The else
is associated with the closest preceding if
.
If, for example, you desired to assocated the else
from this example
with the first if
, you would need to explicitly associate them with
curly braces:
// this file saved as if-else-3.os if( x > y ) { if( c < d ) foo(); } else bar();
Because of the ambiguity, it is a good idea to make your code more readable by using curly braces.
The while
construct is the first of the two ObjectScript looping
constructs.
while ( expression ) statement
The expression is evaluated, and if it's result is true
then statement is executed. This cycle is repeated until
expression results in false
. Like with the if-else
construct, it is an error for expression to evaluate to a value other
than true
or false
.
The break
and continue
statements can be used to
modify flow control within a loop. The break
statement can
be used to exit from the body of a loop. If a break
statement
is encountered, the innermost enclosing for
or while
loop will exit immediately. The continue
statement is similar,
but rather than jumping to the end of the loop, it jumps to the beginning of
the innermost enclosing loop causing the next iteration of the loop to begin.
For example:
// this file saved as while.os var a = 0; while(true) { a++; if( a > 5 ) break; else continue; a = 10; // this line is never evaluated }
The for
construct is very similar to a while
construct.
for ( expression1; expression2; expression3 ) statement
is the same as
{ expression1; while ( expression2 ) { statement expression3; } }
There is an alternative for
construct for iterating through
sets or arrays
for ( expression1 : expression2 ) statement
is the same as
{ var $itr$ = (expression2).iterator(); while ( $itr$.hasNext() ) { expression1 = $itr$.next(); statement } }
For example:
// this file saved as for.os var arr = []; for( var i=0; i<10; i++ ) arr[i] = i; var sum = 0; for( var item : arr ) sum += item;
The import
keyword loads and evaluates a source file in the
current scope.
import expression;
The expression should evaluate to a string, which specifies the path to the source file to load. The script environment will search for the file relative to each entry in the script-path until a file is found. The file is then parsed and evaluated in the current scope.
The mixin
keyword mixes in other objects into the current
scope.
mixin expression;
The expression is evaluated, and the resulting object is mixed in to
the current scope, meaning that (in effect) all the members of that
object are now members of the current scope. This has a similar result
as the java import
keyword, but works for any sort of
object, not just package objects. For example:
// this file saved as mixin.os mixin java.util; mixin new Hashtable(); put("foo","bar"); writeln( get("foo") );
The first line causes Hashtable
to be in scope, and the
second causes the put
, get
, etc, methods
from the hashtable object to be in scope.
The synchronized
construct is used to provide thread synchronization.
Every object in the system can potentially have a monitor associated with it.
synchronized ( expression ) statement
The expression is evaluated, and the monitor of the resulting object is acquired. If another thread already owns the monitor, this will block until the monitor has been released. Once the monitor is aquired, statement is executed, and then the monitor is released.
Well printing "Hello World!" is all well and good, but to do anything interesting, you'll probably need functions and variables.
The keyword var
is used to declare a variable, for example:
// this file saved as var.os var a; var b = 42; // note: you can optionally assign an initial value // since ObjectScript is weakly/loosely typed, we can assign any type // to a variable: a = 99; a = "foobar"; // variables can also be "const", which means you can only assign a // value to that variable once, for example: const var c = 101; c = 32; // ERROR! Cannot re-assign a const
And a function can be declared using the function
keyword.
For example, the following function will compute the fibonacci number
of n:
// this file saved as fib.os /** * The mathmatical definition of fibonacci number of n: * <pre> * Fn = 1 (n == 1) * = 1 (n == 2) * = F(n-1) + F(n-2) (otherwise) * </pre> * * @param n the number to compute the fibonacci value for * @return the fibonacci value for <code>n</code> */ function fib(n) { if( n == 0 ) throw new Exception("Invalid Input"); else if( n == 1 ) return 1; else if( n == 2 ) return 1; else return fib(n-1) + fib(n-2); }
Note the use of "javadoc" style comments. ObjectScript comes with tools to extract javadoc comments from source code, for the purpose of generating interface documentation.
To try this out, lets fire up the interpreter in interactive mode:
% import "fib.os"; % fib(1) => 1 % fib(2) => 1 % fib(9) => 34 % fib(23) => 28657 % fib(0) Exception: Invalid Input at fib (fib.os line 11) at fib(0); (string: "fib(0);" line 1)
Since in ObjectScript, everything is just an object, it stands to reason
that the fib
function we just implement is also just an
object. In fact the syntax used to declare that function is really just
shorthand for:
// this file saved as fib2.os var fib = function(n) { if( n == 0 ) throw new Exception("Invalid Input"); else if( n == 1 ) return 1; else if( n == 2 ) return 1; else return fib(n-1) + fib(n-2); };
The special variable callee
always evaluates to the current
function being evaluated. It is sort of the function equivalent of
this
. This allows anonymous functions to call themself
recursively, for example:
// this file saved as fib3.os (function(n) { if( n == 0 ) throw new Exception("Invalid Input"); else if( n == 1 ) return 1; else if( n == 2 ) return 1; else return callee(n-1) + callee(n-2); })(32);
If the last parameter in the function's parameter list is followed by a ...
,
then that function can accept a variable number of parameters. When that function
is called (either as a function or as a constructor) the first n-1 arguments are assigned
to the first n-1 parameters as usual, and the remaining zero or more arguments are
stored in an array which is assigned to the nth parameter.
// this file saved as var-arg-fxn.os function varargfxn( a, b, c... ) { writeln("a=" + a + ", b=" + b + ", c=" + c); }
% import "var-arg-fxn.os" % varargfxn(1,2) a=1, b=2, c=[] % varargfxn(1,2,3,4,5,6) a=1, b=2, c=[3, 4, 5, 6] % varargfxn(1) script error: Exception: wrong number of args! at varargfxn(1); (string-input.os line 1) %
Before delving much deeper into ObjectScript, it is necessary to understand scope. Put simply, scope determines which variables are visible when. Levels of scope in a program are generally denoted with curly braces. A variable is said to be "in scope" if it has been already declared in the current scope, or a scope that encloses the current scope. Trying to access a variable that is not in scope results in an error:
// this file saved as scope.os var a = 1; // a is declared in the toplevel (global) scope function foo() { var b = 2; // b is declared within the function foo's scope { var c = 3; var a = 2; writeln("a=" + a + " ,b=" + b + ", c=" + c); } writeln("a=" + a + " ,b=" + b); writeln("c=" + c); } foo();
Firing up our trusty interpreter to try this out:
% import "scope.os"; a=2, b=2, c=3 a=1, b=2 NoSuchMemberException: no such member: c at foo (scope.os line 16) at scope_os (scope.os line 19) at import "scope.os"; (string: "import "scope.os";" line 1)
Notice that at the first call to writeln
, the second declaration
of a
eclipses the global declaration of the variable with the
same name. Also, notice that the on the line with the third writeln
an exception is thrown, because the variable c
is not in-scope.
There are a couple places where a level of scope is implicitly created, such
as with a for
loop (look at the equivalent while
loop translation), or a catch
statement.
One interesting aspect of ObjectScript is that functions can be declared within a function.
// this file saved as scope2.os var a = true; function outer() { var b = false; function inner() { var c = "c"; writeln("a=" + a + ", b=" + b + ", c=" + c); } inner(); } outer();
In the example, the enclosing scope of outer
is the global scope,
containg the variables a
and outer
. Within the
scope created when outer
is called, are defined the variables
b
and inner
. Within the scope created when
inner
is called, is the variable c
. Within the
body of inner
, a
b
and c
are all in-scope.
A symbol (variable or function) declared as static
within a function
is a member of that enclosing function object. It is worth pointing out that
while the static field is a member of the enclosing function, if multiple
instances of a function are created, for example by importing the same source
file multiple times or a function that is declared within an enclosing function
that is called multiple times, each instance of the function will have it's own
copy of the static field. For example:
% function foo() \ { \ static var cnt = 0; \ return ++cnt; \ } % foo.cnt => 0 % foo() => 1 % foo() => 2
A complete discussion of object oriented programming is probably beyond the scope of this document, but the basic idea is encapsulation of data and the functions that operate on that data.
First, to recap, when a function is called, a scope object is created within which the body of the function is evaluated. Symbols (ie. variables and functions) declared at the top-level of that function, are declared within this newly created scope. Because functions are closures, any function declared within this scope has access to other symbols declared within that scope (or recursively any enclosing scope).
If, instead of calling some function as you normally do, the call is prefixed
with the keyword new
(as in new Widget()
), the
call evaluates to the scope object itself, which contains all the symbols
declared within the function body. Any symbol that is declared with the
modifier keyword public
is then accessible as a member of the
object. The type of the object that is created is actually the function
object itself, so (new Foo()).getType()
evaluates to Foo
.
// this file saved as Counter.os /** * A counter class. Create a counter with the specified initial value. * * @param initialCount the initial value for the counter. */ public function Counter(initialCount) { var cnt = initialCount; /** * Increment the counter value. */ public function increment() { cnt++; } /** * Decrement the counter value. */ public function decrement() { cnt--; } /** * Get the counter's current value. * * @return the counter value */ public function getCount() { return cnt; } }
In this example, the outer function is the Counter
class, which
provides controlled access to a state variable, in this case incrementing
and decrementing the counter. Since the cnt
variable isn't
declared to be public, it isn't accessible to code outside the scope of the
Counter
function.
% import "Counter.os" % var counter = new Counter(0); % counter.getCount(); => 0 % counter.increment(); % counter.getCount(); => 1 % counter.cnt = 3; NoSuchMemberException: no such member: [function: Counter]: cnt is not public at counter.cnt = 3; (string: "counter.cnt = 3;" line 1) %
ObjectScript supports inheritance with the extends
keyword,
when declaring a function. The new class that extends an existing class,
known as the derived class, inherits the symbols (functions and variables)
of the parent class. It can override and/or suppliment those symbols.
If an object is an instanceof
Foo, and Foo extends
Bar, then the object is also an instanceof
Bar.
For example, if we wanted to implement a counter that could not go above
a certain value, we can leverage off our initial implementation of
Counter
to by subclasssing it:
// this file saved as CeilCounter.os var lastCnt; /** * A counter that has a maximum value, aka a ceiling. An attempt to increment * the counter beyond the ceiling will be ignored. * * @param initialCount the initial counter value * @param maxCnt the maximum counter value (the ceiling) */ public function CeilCounter( initialCount, maxCnt ) extends Counter(initialCount) { /** * Increment the counter value. */ public function increment() { cnt = (++cnt > maxCnt) ? maxCnt : cnt; lastCnt = cnt; } /** * Update the max count. * * @param maxCnt the new maximum counter value */ public function setMaxCount(maxCnt) { if( cnt > maxCnt ) cnt = maxCnt; this.maxCnt = maxCnt; } setMaxCount(maxCnt); }
Notice the use of the special variable this
, which refers
to the nearest enclosing object (including arguments to the enclosing
constructor function, in this case CeilCounter
) and can
be used to specificially refer to a member
of an object when that member would otherwise be eclipsed by another
symbol.
The arguments to the constructor are evaluated in the same scope as the
function body, but before the function body is evaluated. This means that
the parameters passed to the derived class (initialCount
and
maxCnt
) are in-scope, as are any objects declared in
the scope enclosing the derived class's declaration (lastCnt
).
The arguments to the constructor, and any private
symbols, are
not in scope in the parent or derived class, but any public
or
protected
(the default permission level) symbols are. In
other words, it is like java where private
member variables or
functions cannot be accessed by the derived class.
% import "Counter.os" % import "CeilCounter.os" % var counter = new CeilCounter( 0, 2 ); % counter.increment(); counter.increment(); counter.increment(); % counter.getCount() => 2 % lastCnt => 2 % counter.setMaxCount(1) % counter.getCount() => 1 % counter.increment(); % counter.getCount() => 1 %
As you've already seen, this
can be used to refer to a
specific object member variable, even if it may have been eclipsed by a
local variable. But it can also be used with a qualifyer, as in
Foo.this
. In this case, it resolves to the scope of the
enclosing function Foo
:
// this file saved as QualifiedThis.os function Outer() { private var a = 1; function Inner() { private var a = 2; public function test() { private var a = 3; { var a = 4; writeln("a: " + a); writeln("test.this.a: " + test.this.a); writeln("this.a: " + this.a); writeln("Inner.this.a: " + Inner.this.a); writeln("Outer.this.a: " + Outer.this.a); } } } public function test() { (new Inner()).test(); } }
In this example, test.this.a
evaluates to 3
,
the variable declared within the outermost scope of the function
test
, while Outer.this.a
resolves to
1
, the member of the outer class.
% import "QualifiedThis.os" % (new Outer()).test() a: 4 test.this.a: 3 this.a: 2 Inner.this.a: 2 Outer.this.a: 1 %
There is another special variable, super
, which can be
used to call functions from a parent class which have been overriden
in a derived class.
// this file saved as SuperExample.os function Super() { public function foo() { writeln("foo"); } public function bar() { writeln("bar"); } } function Derived() extends Super() { public function foo() { writeln("overridden foo"); super(); } public function bar() { writeln("overridden bar"); super.bar(); super.foo(); } }
By itself, super
refers to the function that the current
function overrode. But super.foo()
refers to the
function that foo
overrode, even though it occurs in the
context of a different function.
% import "SuperExample.os" % var d = new Derived(); % d.foo(); overridden foo foo % d.bar(); overridden bar bar foo %
We have already covered the public
modifier, but in the
context of inheritance there are two other modifiers that are important.
The protected
modifier can be used to declare a symbol
that is accessible to parent or child classes in the inheritance
hierarchy, but not accessible from "outside" that object (ie. protected
variable foo
cannot be accessed as obj.foo
).
The private
modifier can be used to declare a symbol that
is not even accessible to parent or child classes. If no modifier is
provided, a symbol declared with var
or function
defaults to protected, and a symbol that is a parameter to the constructor
function defaults to private. For example, since the Counter
class doesn't need the parameter initialCount
after
cnt
is initialized, we could instead have done:
// this file saved as Counter2.os /** * A counter class. Create a counter with the specified initial value. * * @param cnt the initial value for the counter. */ public function Counter( protected cnt ) { // ... }
While objects declared within the derived class are declared in the same scope as those in the parent class, which means for example that methods declared in the parent class can call methods declared in the derived class and visa versa, the methods declared in the parent class have a different enclosing scope. This can be seen if the parent class and the derived class are actually declared in different scopes:
// this file saved as inherit-scope.os // package "foo": var foo = new (function() { var fooStr = "foo"; public function Foo() { public function getFooStr() { return fooStr; } } })(); // package "bar": var bar = new (function() { public function Bar() extends foo.Foo() { public function getFooStrIndirectly() { return getFooStr(); } public function getFooStrDirectly() { return fooStr; } } })();
This example shows how objects can be used to implement packages, that can
have package-level protected variables. The variable fooStr
declared within the object foo
is in-scope for the method
getFooStr()
, but not for the method getFooStrDirectly()
declared in the derived class.
% import "inherit-scope.os" % var b = new bar.Bar(); % b.getFooStr(); => foo % b.getFooStrIndirectly(); => foo % b.getFooStrDirectly(); NoSuchMemberException: no such member: fooStr at getFooStrDirectly (inherit-scope.os line 31) at b.getFooStrDirectly(); (string: "b.getFooStrDirectly();" line 1) %
While ObjectScript only permits single inheritance, a effect similar
to multiple inheritance, but without the associated pitfalls,
can be achived by using mixin
s, by mixing in instances of
different base classes in the constructor scope. For example:
// this file saved as oo-mixin-1.os /* existing base classes: */ function Color() { public function setRGB( r, g, b ) { // ... } } function Shape() { // ... } function Square() extends Shape() { public function setSize( w, h ) { // ... } } /* derived class created using mixins: */ function ColoredSquare() { mixin new Square(); mixin new Color(); } var coloredSquare = new ColoredSquare(); coloredSquare.setRGB( 0xff, 0xff, 0xff ); coloredSquare.setSize( 25, 50 );
In this simplistic example, an instance of ColoredSquare
has "inherited" properties and type from both Square
and
Color
, despite the fact that behind the scenes this is
actually using object composition (ie. the square object, and color
object, are seperate objects, which don't have access to each others
private or protected members, which means that they can't inadvertantly
override one of each other's members). The ColoredSquare
instance is also an instanceof
Square
and
Color
.
In practice, you probably need to implement or override some
of the members of one or more of the mixed in objects. For example,
suppose Color
requires that the derived class implement the
colorChanged
method, which is called to allow the derived
class to update itself when the color changes, and similarly the
Shape
base class expects that the derived class implement
a dimensionsChanged
method, which is called when the
dimensions of the shape have changed:
// this file saved as oo-mixin-2.os function ColoredSquare() { /** * Called when the color or dimensions of this square have changed. */ private function update() { // do something interesting here... } mixin new function() extends Square() { protected var dimensionsChanged = update; }(); mixin new function() extends Color() { protected var colorChanged = update; }(); }
Note that this pattern does not even require that the methods that the different base classes expect to be implemented by the derived class have the same name.
Exceptions are thrown to cause non-local return in the case of an error or
exceptional condition. An exception can be thrown by the interpreter in the
case of a program error, such as refering to a non-existant variable or
calling a function with the wrong number of arguments. An exception can
also be programatically thrown.
The try-catch-finally
construct is used for
exception handling.
try statement1 catch ( expression variable-name1 ) statement2 catch ( variable-name2 ) statement3 finally statement4
There can be zero or more catch
statements which specifically
specify the type of exception that they handle, followed by an optional
catch
which does not specify a type, followed by an optional
finally
statement. The statement1 is
executed. If during it's evaluation an exception is thrown, either do to
a program error or explicitly using a throw
statement,
the exception is handled by the nearest enclosing catch
statement that can handle the exception. In the case of catch
statements which explicitly specify a type, if the thrown exception is an
instanceof
the specified type, then that catch
statement can handle the exception. A catch
statement which
does not specify a type can handle any type of exception.
In the case that a finally
block is present, the statement
of the finally
block is guaranteed to be executed before
program execution leaves the try
statement, regardless of
whether an exception is thrown or not.
If an exception is thrown, program control is transfered immediately to
the nearest enclosing catch
statement that handles the
error, or the nearest enclosing finally
statement. In the
case of the latter, once finally
statement has completed
control is transferred to the nearest matching catch
or the next nearest finally
. This process is repeated until
the exception is handled by evaluating the nearest matching catch
statement. When a catch
block is evaluated, the thrown
exception is assigned to the variable bound to variable-name,
and then the statement is evaluated.
// this file saved as except.os function foo() { throw new Exception("foo!"); } try { writeln("lets try something!"); foo(); } catch(java.lang.NullPointerException e) { // never get here } catch(e) { // uh, oh... lets handle the error writeln("exception was throw: " + e.getMessage()); } finally { writeln("finally"); }
In this example, the exception is thrown by a function that is called
within the body of of the try
statement.
> java -jar oscript.jar except.os OscriptParser: Reading from file except.os. . . lets try something! exception was throw: foo! finally
Because of the guarantee that the finally
block will always be
executed, regardless of whether or not an exception is thrown, it is
recommend that any time a global resource that must be released is created
a try-finally
is used:
// this file saved as finally.os var lock = new Lock(); lock.acquire(); try { doSomeProcessing(); doSomeMoreProcessing(); } finally lock.release();
The ScriptPackage
is used to implement a package system for scripts.
It handles automatically import
ing script source files when they
have not yet been loaded, or are out of date. The script package system relies
on a couple of coding conventions:
FooBar
, is declared in the source
file public
) to that source file may be declared
in the source file.For example:
// this file saved as foo/Bar.os /** * Some silly example. */ public function Bar() { /** * Convert to string. * * @return a string */ public function castToString() { return getBar(); } } // not accessible outside this file: function getBar() { return "bar"; }
Or to create a package of utility functions in a single file you might create a single public object with public member functions:
// this file saved as bar.os public var bar = new (function() { private var cnt = 0; /** * Print out "hello world" */ public function helloWorld() { pkg.output.writeln(cnt++ + ": hello world!"); } })();
% const var pkg = new ScriptPackage("/path/to/scripts"); % var fb = new pkg.foo.Bar(); % fb => bar % pkg.bar.helloWorld(); hello world! %
When resolving the request for the member Bar
, if
the source file Bar
.
In ObjectScript, all the built-in operators (ie. +
, -
,
[]
, etc.) map to method calls to an object. For example,
a + b
is the same as a.bopPlus(b)
. This means that
new types can be implemented to enrich the scripting environment, such as
Vector.
// this file saved as Vector.os /** * A vector, as in a mathmatical vector. Like an <code>Array</code>, but * supports vector addition/subtraction and multiplication/division with * a scalar. * * @param args... members of the vector */ public function Vector(args...) extends Array() { // ... snip ... /** * Perform the "+" operation. * <pre> * C = A + B * C[n] = A[n] + B[n] * </pre> * * @param val the other value * @return a vector * @throws NoSuchMethodException */ public function bopPlus( val ) { if( val.length() != length() ) return val.bopPlusR( this, new IllegalArgumentException("cannot add vectors of different size") ); var result = new Vector(); for( var i=length()-1; i>=0; i-- ) result[i] = this[i] + val[i]; return result; } public function bopPlusR( val, e ) { if( val.length() != length() ) throw e; var result = new Vector(); for( var i=length()-1; i>=0; i-- ) result[i] = val[i] + this[i]; return result; } // ... snip ... }
Notice that there are actually two methods for performing addition,
bopPlus
and bopPlusR
. (The "bop" indicates
a binary operator, the "R" indicates "reversed".) The purpose of this
is that if type A does know how to add type B to itself, it tries
the reverse, adding itself to type B.
% import "Vector.os" % var v1 = new Vector(1,2,3) % var v2 = new Vector(4,5,6) % v1 + v2 => [5, 7, 9] % v1 + [1,2,3] => [2, 4, 6] % [1,2,3] + v1 => [2, 4, 6] %
In addition to overloading the built-in operators, an object can overload
how it gets cast to different types, for example castToString()
or castToBoolean()
. For a complete list of the built-in
operator methods, or the castToXXXX methods, please refer to the ObjectScript
Language Reference [ps|pdf].
One of the strength's of ObjectScript is the integration with java code, which means that existing java APIs are automatically usable from script code.
Integrating script with pre-existing java code is remarkably simple. The
JavaPackage
class can be used to access to java classes in
the named package. For example:
% var java = new JavaPackage("java"); % var l = new java.util.LinkedList() % l.add(1); l.add("b"); l.add(3.1); l.add( function() {} ); => true % l => [1, b, 3.1, [function: anon]] % for( var itr=l.iterator(); itr.hasNext(); ) \ writeln(" -> " + itr.next()); -> 1 -> b -> 3.1 -> [function: anon] % for( var member : l ) \ writeln(" -> " + member); -> 1 -> b -> 3.1 -> [function: anon] %
Note that by default JavaPackage
objects are created for common
root package names: java
, javax
, org
,
and com
. Also, there is one caveat to be aware of: due to
limitations of the APIs available to determine what packages exist in the
system, it is sometimes necessary to tell the scripting engine about java
packages that it might not otherwise be able to figure out about. This
can be done by calling pkg.system.declareJavaPackage(packageName)
.
It is possible that a future version of Java will make this step unnecessary.
But java integration can go much further than that. It is possible to
extend
java classes and interfaces. (A java interface is
treated as an abstract class with all abstract methods. If an abstract
method that is not implemented by the derived script class is called
by java code, it results in a NoSuchMemberException
being
thrown, wrapped in the java class PackagedScriptObjectException
which is a java.lang.RuntimeException
.)
For example:
// this file saved as foo/Foo.java package foo; public class Foo { private String foo = "foo"; public class FooInner { public String getName() { return foo; } } public FooInner getFooInner() { return new FooInner(); } public String getFooInnerName() { return getFooInner().getName(); } }
Use javac to compile
// this file saved as inherit-java.os var foo = new JavaPackage("foo"); function Bar() extends foo.Foo() { public function getFooInner() { return new (function() extends FooInner() { public function getName() { return "bar"; } })(); } }
This script class overrides the getFooInner
method to
return an instance of an anonymous class that extends FooInner
(the inner class from getName()
method returns "bar" instead of "foo".)
Exending FooInner
could also be done with a regular
named function, but I wanted to demonstrate that a function's
declaration could be inline with it's use.
% var foo = new JavaPackage("foo") % (new foo.Foo()).getFooInnerName() => foo % import "inherit-java.os"; % (new Bar()).getFooInnerName() => bar %
When we try this example, we see that when the java method
getFooInnerName()
calls getFooInner()
it invokes
the script method that overrides the java method. Same goes for the
method getName()
.
To make life a bit nicer, ObjectScript also supports Java Bean property
access, so a java object with a getFoo()
and optionally
setFoo(foo)
methods can have that property access as
obj.foo
. The bean property is read-only if there is only
a "getter" method, but no "setter" method.
// this file saved as bean.os var tf = new javax.swing.JTextField(); tf.text = "default text";
Also, java objects implementing java.util.Map
or
java.util.List
can be accessed using array element syntax.
A list object behaves as a regular array, with a subscript of n
accessing the n
th element in the list. A map object behaves
as an associative array:
// this file saved as map-assoc-array.os var map = new java.util.Hashtable(); map["foo"] = 1; map["bar"] = 2; writeln("result: " + (map["foo"] + map["bar"]));
In order to simplify interfacing with java code, ObjectScript supports
function transformers. In ObjectScript, since you can assign
function objects to variables, pass them as arguments, etc., it makes
sense to use a function object itself as a listener or callback. In
Java, you cannot do that, so instead interfaces are defined, often with
only a single method, an objects implementing those interfaces are used
as the callback. Common examples of this pattern are java.lang.Runnable
or java.awt.event.ActionListener
. While it is possible to
create a script object implementing these objects, it would be convenient
to simply be able to pass a function which takes the appropriate number
of arguments to any java method or constructor which expects one of these
interfaces. A function transformer can make this possible.
By default, transformers are registered for the following types:
java.lang.Runnable
- function() { ... }java.awt.event.ActionListener
- function(evt) { ... }
So, for example, a function that takes no arguments can be used as a java.lang.Runnable
.
This can be handy in many circumstances, such as when writing a multi-threaded
application that uses swing (which isn't thread safe, see:
Threads and Swing).
For example:
// this file saved as runnable-fxn.os public function addToTabPane( name, icon, component ) { javax.swing.SwingUtilities.invokeLater( function() { tabPane.addTab( name, icon, component ); } ); }
Since the function that is declared inline with the call to invokeLater
is enclosed by the addToTabPane
, the variables name
,
icon
, and component
are in-scope. Or for another example,
using a function as an java.awt.event.ActionListener
:
// this file saved as action-listener-fxn.os var button = new javax.swing.JButton("Push Me!"); button.addActionListener( function(evt) { label.setText("Button pushed " + ++cnt + " times"); } );
It is fairly straightforeward to register additional function transformers:
// this file saved as fxn-transformer.os pkg.system.registerFunctionTransformer( new (function() extends pkg.system.FunctionTransformer(javax.swing.event.ChangeListener) { public function transform(fxn) { return new (function() extends swing.event.ChangeListener() { public function stateChanged(evt) { fxn(evt); } })(); } })() );
ObjectScript regular expression support is similar to JavaScript regular
expressions, to make them familiar to users who have already used regular
expressions under JavaScript. Regular expressions are instances of an
RegExp
, and can be created using the constructor (which
takes the regular expression string as the first arg, and optionally
a flags string as a second arg), or as a literal:
% var re = /jpg|gif|bmp/i; // same as: new RegExp("jpg|gif|bmp","i") % re.test("img.jpg") => true % re.test("img.bmp") => true % re.test("img.pdf"); => false % re.exec("img.gif"); => [jpg]
The regular expression pattern is passed straight through to the regexp
engine (currently java.util.regex
that was introduce in java v1.4, but could use others), with one exception:
whitespace characters in a regular expression literal must be escaped by a
\
character. The following flags are supported:
i
- case insensitiveI
- unicode case insensitivem
- multiline mode, the expressions ^
and
$
match just after or just before (respectively), a line
terminator or end of input sequenced
- dotall mode, the expression .
matches
any character including line terminatore
- enabled canonical equivalence.w
- permits whitespace and comments in pattern. Comments
start with #
and are terminated by an end of line.
The regular expression object has two methods, exec
and
test
. Both take a string as an argument, but the former
returns a RegExpResult
object, while the latter returns
a true
if the string is a match to the regular expression.
The result object (RegExpResult
) can be accessed both as
an array, and as an object, with the following properties/indices:
Property/Index | Description |
---|---|
property: index |
the index of the matched string |
property: input |
the original string |
index: 0 |
the last mached characters |
index: 1 thru n |
The parenthesized substring matches, if any |
In addition to the methods of the regular expression object, the
String
class defines a few methods for working with
regular expressions:
// this file saved as regexp.os // same as: /jpg|gif|bmp/i.exec("foo.jpg") var result = "foo.jpg".match(/jpg|gif|bmp/i); // find index of match: var idx = "foo.jpg".search(/jpg|gif|bmp/i); // replace matches: var result = "foo.jpg".replace( /jpg|gif|bmp/i, "EXT" );
The most interesting of these methods is replace
, whose
second argument is either a replacement string or function. If the
second parameter is a string, the following replacement patterns are
evaluated and replaced with the appropriate value:
pattern | Description |
---|---|
$$ | Inserts a $ |
$& | Inserts the matched substring |
$` | Inserts the portion of the string that precedes the matched substring |
$' | Inserts the portion of the string that follows the matched substring |
$ n | Inserts the nth parenthesized submatch string |
If the second parameter is a function, it is called with the following parameters:
param | Description |
---|---|
0 | the matched string |
1-n | zero or more parameters for parenthetical matches |
n+1 | offset of match |
n+2 | the original string |
and the return value from the function is used as the replacement string.
This section is currently a catch-all for other interesting tricks and tips.
The Object
class, which is the base type for all script types,
defines the method memberSet()
, which returns a collection of
the names of all the members of the object. This can be combine with the
getMember(name)
, callAsFunction(args)
, and
callAsConstructor(args)
to reflectively manipulate an object.
The getMember
method takes as an argument the name of the
member to access, and returns the member itself. So obj.foo
is equivalent to obj.getMember("foo")
. All objects have this
method.
The callAsFunction
and callAsConstructor
methods
both take an array of arguments. The former is present in objects that
can be called as a function, so foo(1,2,3)
is the same as
foo.callAsFunction([1,2,3])
. The latter is present in objects
that can be called as a constructor, so new Foo(1,2,3)
is
equivalent to Foo.callAsConstructor([1,2,3])
.
For example:
// this file saved as reflection.os function listobj(obj) { for( var memberName : obj.memberSet() ) writeln("obj." + memberName + " => " + obj.getMember(memberName)); }
% import "reflection.os"; % var obj = new function() { public var a = 1; } (); % listobj(obj); obj.hashCode => [method: hashCode] obj.unhand => [method: unhand] obj.toString => [method: toString] obj.bopInstanceOfR => [method: bopInstanceOfR] obj.getType => [method: getType] obj.getMember => [method: getMember] obj.memberSet => [method: memberSet] obj.bopNotEquals => [method: bopNotEquals] obj.bopInstanceOf => [method: bopInstanceOf] obj.notifyAll => [method: notifyAll] obj.a => 1 obj.castToJavaObject => [method: castToJavaObject] obj.equals => [method: equals] obj.wait => [method: wait] obj.bopEquals => [method: bopEquals] obj.castToString => [method: castToString] obj.notify => [method: notify] %
Note that even though the constructor function only creates the member
a
, it still inherits a number of members from the
Object
base class. If a constructor function does not
specify a base class (using extends
), it defaults to
Object
.
Also, in the case of script functions, it is possible to reflectively
access information about the arguments to the function (names, how many,
varargs?) and the function-comment if there is one. The method
getMinimumArgCount()
returns the number of non-vararg
parameters, takesVarArgs
returns true
if the
function takes a variable number of parameters (in which case the
function can be called with more than the minimum number of parameters),
and getArgNames
returns the names of the arguments to the
function.
Another form of reflection that is available is accessing the stack
frame of the current thread, which is possible via
pkg.system.currentStackFrame
.
// this file saved as stack-frame.os function printBackTrace() { writeln("stack trace:"); for( var stackFrame : pkg.system.currentStackFrame() ) writeln(" at " + stackFrame.getFile() + ":" + stackFrame.getLineNumber()); }
% import "stack-frame.os"> % printBackTrace() stack trace: at /jar/oscript/system.os:139 at /Users/rclark/Documents/working/oscript/stack-frame.os:4 at unknown:1 % (function() { printBackTrace(); })() stack trace: at /jar/oscript/system.os:139 at /Users/rclark/Documents/working/oscript/stack-frame.os:4 at /string-input.os:1 at unknown:1
ObjectScript has transparent support for accessing remote XML-RPC servers, and also for implementing servers. The support for building the XML-RPC requests and parsing responses is provided by the Apache XML-RPC library.
Accesses to a remote server is done by use of the
XmlRpcClient
(or XmlRpcClientLite
) constructor
which creates a proxy for the remote server:
// this file saved as xml-rpc-client.os var rpc = new XmlRpcClient("http://localhost:12345/"); // call "adder.sum" writeln( rpc.adder.sum("Hello ", "World!") );
To serve an API implemented by a script object, you would use a
XmlRpcHandler
// this file saved as xml-rpc-server.os var adder = new function() { public function sum( a, b ) { return a + b; } }(); var server = new org.apache.xmlrpc.WebServer(12345); server.start(); server.addHandler( "adder", new XmlRpcHandler(adder) );
ObjectScript has support for the windows COM system, by means
of the ComObject
type. COM support is implemented using the
Jawin
library. You will need to ensure that you have the following files
in your classpath (in the directory you are running java from is
sufficient):
ComObject
typeUsing this is similar to using COM support in jscript, visual basic, etc. Here is a simple example showing how to open up and manipulate an excel spreadsheet:
// this file saved as com-example.os var sheet = new ComObject("Excel.Sheet"); sheet.Application.Visible = true; for( var i=1; i<=10; i++ ) { for( var j=1; j<=10; j++ ) { sheet.ActiveSheet.Cells[i][j].Value = "cell " + i + ", " + j; sheet.ActiveSheet.Cells[i][j].Font.Bold = (i == j); } } sheet.SaveAs("C:\\test.xls"); sheet.Application.Quit();
Of course this will only work under windows, but it is a useful tool if you are already tied to the windows platform. For cross platform RPC, there is also XML-RPC.
The WeakReference
type is a proxy for some other object (the
referrent) without preventing the referrent from being garbage collected.
Once the referrent is garbage collected, the reference becomes a proxy for
null
. This is useful in cases were a reference is needed but
you know that it will not be used after the referrent is garbage collected,
such as an key that is referred to directly or indirectly by a value in
a java.util.WeakHashMap
, because in this case the hard-
reference to the key will prevent the key itself from being garbage
collected, such as this contrived example:
// this file saved as weak-ref-example.os var map = new java.util.WeakHashMap(); function addButtonPressListener(button) { var fxn = map.get(button); if( fxn == null ) { button.addActionListener( fxn = function(evt) { pkg.output.writeln("button text: " + button.getText()); } ); map.put( button, fxn ); } button = new WeakReference(button); }
In this example, we want to avoid installing a listener if there already
is one, so we use a WeakHashMap
to keep track of which
buttons already have listeners installed. But the listener needs to
reference the button itself, which would prevent the button from being
garbaged collected. So before addButtonPressListener()
returns, we replace the hard reference to the button with a weak-
reference proxy, so the listener doesn't prevent the button from being
garbage collected.
The Proxy
base class can be used for script objects which
want to take part in resolving access of members to itself. The derived
class must implement a public method resolve
which takes as
an argument the name of the member being resolved.
// this file saved as CaseInsensitiveObject.os /** * By subclassing this type, any all lowercase member of the object can * be resolved as uppercase or lowercase. */ public function CaseInsensitiveObject() extends Proxy() { /** * Accesses to members of this object are intercepted by this method. * * @param name the name of the member being resolved * @return the member, or <code>null</code> if not able to resolve. */ public function resolve(name) { try { var member; eval "member = " + name.toLowerCase() + ";"; return member; } catch(NoSuchMemberException e) { return null; } } }
And then to try it out:
% var obj = new function() extends pkg.CaseInsensitiveObject() { \ public var foo = 1; \ public var bar = 2; \ }(); % obj.FOO => 1 % obj.BaR => 2 %
Rather than loading files directly from the local filesystem, the
interpreter has a filesystem abstraction: AbstractFileSystem
.
Currently implemented are LocalFileSystem
(which loads files
via the native filesystem) and JarFileSystem
(which loads
files from a jar file). Different filesystems can be mount
ed
at arbitrary locations within the virtual filesystem. Jar files that are
in the classpath are automatically mounted on demand at
"/jar/" + jarName[0..jarName.indexOf(".jar")-1]
, for example
For example, the function defined in this file, cat
, will
read from the file at the specified path, and echo it to stdout.
// this file saved as cat.os public function cat(path) { var file = new pkg.fs.File(path); var in = file.getInputStream(); var bin = new java.io.BufferedInputStream(in); var line; while( (line=bin.readLine()) != null ) writeln(line); }
% pkg.cl.cat("/jar/oscript/base.os"); /*============================================================================= * Copyright Rob Clark 2000. All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /*============================================================================= * File Description: * This file is loaded by the interpreter at start-up, and is responsible * for initializing the script evironment. This includes defining any * standard but not built-in functions, variables, classes, etc. */ const var java = new JavaPackage("java"); const var javax = new JavaPackage("javax"); const var oscript = new JavaPackage("oscript"); const var org = new JavaPackage("org"); const var com = new JavaPackage("com");
TBD... more complete description of filesystems...
The Array
type has a number of built-in utility methods
indended to simplify manipulating arrays.
concat | |
---|---|
example: | concat( arr1[, arr2, ..., arrN] ) |
args: | arr1 thru arrN -
one or more arrays to join with this array. The resulting
array starts with the elements of this array (in order)
followed by the elements if arr1 (in order)
and so on. |
returns: | the new array |
description: | Join the current array with one or more additional arrays. The original arrays (including this one) are unmodified. A shallow copy is performed. |
join | |
example: | join([separator]) |
args: | separator the separator
character. If not specified, the string ","
will be used |
returns: | the genererated string |
description: | Join the current array into a
string with the elements separated by the specified separator
string or "," |
slice | |
example: | slice( being[, end] ) |
args: | begin the begin index (inclusive) |
end the end index (exclusive) | |
returns: | the resulting array |
description: | Slice out a section of the array,
starting at and including index start and
continuing through but not including index end .
If end is not specified, the sliced array
continues through the end of this array. |
splice | |
example: | splice( idx, num[, arg1, ... argN] ) |
args: | idx the index to begin
removing at |
num the number of elements
to remove | |
arg1 thru argN
zero or more elements to replace the removed elements
with. The number of elements to replace does not need
to match num . | |
returns: | the resulting array |
description: | Add and remove elements from an array. |
pop | |
example: | pop() |
returns: | the element removed from the array |
description: | Remove and return the last element from this array. |
push | |
example: | push( arg1[, arg2, ... argN] ) |
args: | arg1 thru argN
one or more elements push onto the end of this array. |
returns: | the new length of this array |
description: | Add one or more elements to the end of this array. |
shift | |
example: | shift() |
returns: | the element removed from the array |
description: | Remove and return the first element from this array. |
unshift | |
example: | unshift( arg1[, arg2, ... argN] ) |
args: | arg1 thru argN
one or more elements add to the beginning of this array. |
returns: | the new length of this array |
description: | Add one or more elements to the front of this array. |
reverse | |
example: | reverse() |
returns: | this array |
description: | Reverse the order of elements in this array. |
sort | |
example: | sort([fxn]) |
args: | fxn an optional comparision
function, which takes to arguments, and should return
less than zero if the first argument should appear before
the second, zero if it does not matter, and greater than
zero if the second argument should appear after the
first. |
returns: | this array |
description: | Sort the elements of this array. |
every | |
example: | every(fxn) |
args: | fxn function invoked
with three arguments: the value of the element, the index
of the element, and the array object containing the element;
should return true or false |
returns: | true if every element
passes the test, otherwise false |
description: | Executes the provided function
fxn once for each element present in the array
until it finds one where callback returns false .
If such an element is found, the test aborts and
false is returned, otherwise (callback returned
true for each of the elements) every will return
true . This array is not mutated. |
some | |
example: | some(fxn) |
args: | fxn function invoked
with three arguments: the value of the element, the index
of the element, and the array object containing the element;
should return true or false |
returns: | true if any element
passes the test, otherwise false |
description: | Tests whether some element in the
array passes the test implemented by the provided function.
Executes the provided function once for each element present
in the array until it finds one where callback returns
true . If such an element is found, the test
aborts and true is returned, otherwise (callback
returned false for each of the elements) return
false . |
filter | |
example: | filter(fxn) |
args: | fxn function invoked
with three arguments: the value of the element, the index
of the element, and the array object containing the element;
should return true or false |
returns: | an array containing the elements from
this array for which fxn returns true
|
description: | Create a new array with all elements
that pass the test implemented by the provided function. The
provided callback is invoked for each element in the array,
and a new array is constructed containing (in the same order)
the elements for which the callback returned true .
Elements for which the callback returns false are
skipped. This array is not mutated. |
map | |
example: | map(fxn) |
args: | fxn function invoked
with three arguments: the value of the element, the index
of the element, and the array object containing the element;
|
returns: | the new array |
description: | Creates a new array with the results of calling a provided function on every element in this array. This array is not mutated. |
forEach | |
example: | forEach(fxn) |
args: | fxn function invoked
with three arguments: the value of the element, the index
of the element, and the array object containing the element;
|
returns: | none |
description: | Executes a provided function once per array element. This array is not mutated. |
TBD
The pkg.runtime
package is part of the core ObjectScript
runtime which allows you to create external processes, etc.
exec | |||||||||||||
function exec(in, out, err, cmd)Execute a system command.
| |||||||||||||
execp | |||||||||||||
function execp(in, out, err, cmd)Execute a system command, returning the process object. This allows for more job control than the simpler {@link #exec} form of this command, because caller can destroy() , waitFor() and get the exitValue() .
| |||||||||||||
{ | |||||||||||||
| |||||||||||||
} | |||||||||||||
linkInOut | |||||||||||||
function linkInOut(p, in, out, err)helper function to copy output from process's OutputStream to the provided InputStream, and visa versa | |||||||||||||
setLog | |||||||||||||
function setLog(log)For debugging exec and execp calls, you can set a log function... at this point, not really indended for public consumption (ie. may change in the future).
|
The pkg.fs
package allows for access to the filesystem.
This API, unlike using the corresponding java.io
API
directly, allows you to access files within the virtual file-system
space, such as mounted jar files, and so on.
callAsConstructor | |||
TempFile | |||
function TempFile(name, body)A virtual, read-only, temporary file. Good for places where a file is needed, but doesn't exist.
| |||
{ | |||
| |||
} | |||
mount | |||
function mount(fs, path)Mount a new filesystem. A filesystem can be mounted at any position within the virtual filesystem. A mounted filesystem could potentially eclipse a file. If multiple filesystems are mounted at the same point, the result is the union of all those filesystems.
| |||
getCwd | |||
function getCwd()Get the current working directory path.
| |||
setCwd | |||
function setCwd(cwd)Set the current working directory path.
| |||
basename | |||
function basename(path)Return the filename portion of the specified path
| |||
dirname | |||
function dirname(path)Return the directory name portion of the specified path
| |||
normalize | |||
function normalize(args)Normalize the path. This involves ensuring the path is fully qualified (begins with "/"), and gets rid of any extra "." and "/".
| |||
children | |||
function children(path)Get an iterator of the children of the specified file or path. If the specified file or path is not a directory, this will return an empty iterator.
| |||
resolve | |||
function resolve(path)Try to resolve the specified path, returning a file object. The path can be absolute or relative.
|
...
registerFunctionTransformer | |||||||
function registerFunctionTransformer(ft)Register a function-transformer object, which is responsible for transforming a script function into a java object that is assignable to a specified type.
| |||||||
FunctionTransformer | |||||||
function FunctionTransformer(type)The base class for function tranformer objects. A function transformer must extend this base class, and implement the transform method which takes as a parameter a function object, and returns a object assignable to the specified type .
| |||||||
addScriptPath | |||||||
function addScriptPath(path)Add a specified path to the script-path, which is the set of locations in the abstract-file-system which are checked when trying to resolve a script file (as in import or through the script package system).
| |||||||
removeScriptPath | |||||||
function removeScriptPath(path)Remove a specified path from the script-path, which is the set of locations in the abstract-file-system which are checked when trying to resolve a script file (as in import or through the script package system).
| |||||||
getScriptPath | |||||||
function getScriptPath()Return an iterator of entries in the script-path. Each entry is a path that is prefixed to a relative path passed to {@link #resolve} in the process of trying to resolve a file.
| |||||||
mountJarFile | |||||||
function mountJarFile(file)Mount a .jar file under /jar/file and add to script path
| |||||||
resolve | |||||||
function resolve(path)Resolve the specified file. If path is a relative path, then each of the entries in the script-path is searched in order until the file is found.
| |||||||
makePrivatePackage | |||||||
function makePrivatePackage(path)Create a package object, which has the scope of the caller as it's parent. So the scope will work out as if the accessed members where declared in the scope of the caller. This can be combined with a import "foo/a.os"; import "foo/b.os"can be replaced with: mixin pkg.system.makePrivatePackage("/jar/ode/foo");note that since the files loaded by the package object have access to any variable that is in scope at the point where this function is called it is HIGHLY recommended that files loaded by the package be put in a unique directory which is specified with an absolute path if possible. This is to avoid inadvertantly using the package object to import a different file.
| |||||||
export | |||||||
function export(isConst, name, obj, args)A function to declare some variable from any scope in any scope. This relies on knowledge of internal interpreter APIs, so it is best if all places that need to do something like this call this function, so maintainence is easier if those internal APIs change.
| |||||||
currentStackFrame | |||||||
function currentStackFrame()Access the stack-frame object for the current thread. The stack-frame is a chain of nodes, each with information about it's corresponding stack frame, whose head is the most recent stack frame, and tail is the first stack frame for the thread. The stack-frame node has the following methods:
| |||||||
dumpStack | |||||||
function dumpStack(args)Dump the current stack backtrace
| |||||||
getDescription | |||||||
function getDescription(scope)Get a description of the scope . Return an object with the following properties:
|