ObjectScript User's Guide


      Introduction to ObjectScript
      Getting Started
      Builtin Types
      Control Structures
      4.1   if-else
      4.2   while
      4.3   for
      4.4   import
      4.5   mixin
      4.6   synchronized
      Functions and Variables
      5.1   Variables
      5.2   Functions
         5.2.1   callee
         5.2.2   variable number of arguments
      Scope
      Object Oriented Programming
      7.1   Constructor Functions
      7.2   Inheritance
      7.3   Special Variables: this and super
      7.4   Scope and Access Control
      7.5   Using mixin's
      Exceptions
      The Package System
   10   Operator Overloading
   11   Java Integration
      11.1   The Basics
      11.2   Extending Java Types
      11.3   Shortcuts...
      11.4   Function Transformers
   12   Regular Expressions
   13   Advanced Topics
      13.1   Reflection
      13.2   XML-RPC
      13.3   Windows COM Support
      13.4   WeakReference Objects
      13.5   Proxy Objects
   14   Abstract Filesystem
   15   API Reference
      15.1   Array
      15.2   RegExp
      15.3   pkg.runtime
      15.4   pkg.fs
      15.5   pkg.system

1 Introduction to ObjectScript

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.

2 Getting Started

To run the interpreter in stand-alone interactive mode, you must gather the oscript.jar and bcel-5.1.jar. From the shell prompt type: (The shell prompt is ">", and the prompt for the interactive objectscript interpreter is "%", so don't type either of these characters.)

> 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!
> 
    

3 Builtin Types

The ObjectScript interpreter provides a handful of builtin types:

ExactNumber
A signed integer number up to 64 bits precision
InexactNumber
A signed floating point number up to 64 bits precision
Boolean
A boolean type, ie. true or false
Array
an array of arbitrary length, whose members can be of arbitrary type (and don't have to be of the same type). The 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.
String
a string of arbitrary length. A string is immutable, but supports read-only array like access using the [] 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.)

4 Control Structures

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.

4.1 if-else

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.

4.2 while

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
}

      

4.3 for

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;

      

4.4 import

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.

4.5 mixin

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.

4.6 synchronized

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.

5 Functions and Variables

Well printing "Hello World!" is all well and good, but to do anything interesting, you'll probably need functions and variables.

5.1 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
      

5.2 Functions

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);
};

      

5.2.1 callee

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);
        

5.2.2 variable number of arguments

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)
% 
        

6 Scope

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
    

7 Object Oriented Programming

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.

7.1 Constructor Functions

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)
% 
      

7.2 Inheritance

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
% 
      

7.3 Special Variables: this and super

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
% 
      

7.4 Scope and Access Control

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)
% 
      

7.5 Using mixin's

While ObjectScript only permits single inheritance, a effect similar to multiple inheritance, but without the associated pitfalls, can be achived by using mixins, 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.

8 Exceptions

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();
    

9 The Package System

The ScriptPackage is used to implement a package system for scripts. It handles automatically importing 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:

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 /path/to/scripts/foo/Bar.os has not yet been loaded, or has been modified since the last time it was accessed, the package system will create a new scope and import the source file into that scope. It will then return the public variable/function Bar.

10 Operator Overloading

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].

11 Java Integration

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.

11.1 The Basics

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.

11.2 Extending Java Types

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 foo/Foo.java.

// 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 foo/Foo.java so that the 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().

11.3 Shortcuts...

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 nth 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"]));
      

11.4 Function Transformers

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:

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); }
        })();
      }
    })() );
      

12 Regular Expressions

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:

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:

patternDescription
$$ 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:

paramDescription
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.

13 Advanced Topics

This section is currently a catch-all for other interesting tricks and tips.

13.1 Reflection

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
      

13.2 XML-RPC

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) );
      

13.3 Windows COM Support

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):

Using 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.

13.4 WeakReference Objects

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.

13.5 Proxy Objects

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
% 
      

14 Abstract Filesystem

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 mounted 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 /jar/oscript/base.os. FWIW, the directory .cache, which gets created by the interpreter to store cached compiled files, etc., is mounted at /cache

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...

15 API Reference

15.1 Array

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.

15.2 RegExp

TBD

15.3 pkg.runtime

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.
Parameters
in - the input reader (to the process), or null if none
out - the output writer (from the process), or null if none
err - the error writer (from the process), or null if none
cmd - the command to execute
Returns
the return code from executing the command, an exact number
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().
Parameters
in - the input reader (to the process), or null if none
out - the output writer (from the process), or null if none
err - the error writer (from the process), or null if none
cmd - the command to execute
Returns
a process object, which allows for further asynchronous queryingand control of the process, with exitValue, destroyand waitFor methods.
{
     
exitValue
function exitValue()
Get the exit value of the process, blocking until the exit value is available
Returns
the exit code
destroy
function destroy()
Destroy the process, killing it prematurely if it has not already completed. (Note: the underlying Java API does not seem to necessarily seem to work with all Java VMs)
waitFor
function waitFor()
Wait for the process to complete. This will block until the process has completed.
}
 
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).
Parameters
log - a function(str)

15.4 pkg.fs

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.
Parameters
name - the file name
body - the contents of the file
{
     
getInputStream
}
 
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.
Parameters
fs - the filesystem to mount
path - the path to the location to mount (ex: "/")
getCwd
function getCwd()
Get the current working directory path.
Returns
the current working directory file object.
setCwd
function setCwd(cwd)
Set the current working directory path.
Parameters
cwd - the new current working directory file or path
basename
function basename(path)
Return the filename portion of the specified path
Parameters
path - the path string
Returns
the filename portion of the path
dirname
function dirname(path)
Return the directory name portion of the specified path
Parameters
path - the path string
Returns
the directory name portion of the 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 "/".
Parameters
cwd - the (optional) cwd to normalize relative to
path - the string path to normalize
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.
Parameters
file - the file or path to get children of
Returns
Collection of AbstractFile
resolve
function resolve(path)
Try to resolve the specified path, returning a file object. The path can be absolute or relative.
Parameters
path - the path to resolve
Returns
the file object... possibly file object can represent a non-existant file if there is no file at the specified path

15.5 pkg.system

...

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.
Parameters
ft - the FunctionTransformer
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.
Parameters
type - the type that the return value from transformmust be assignable to
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).
Parameters
path - the string path to add
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).
Parameters
path - the string path to add
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.
Returns
an iterator of strings
mountJarFile
function mountJarFile(file)
Mount a .jar file under /jar/file and add to script path
Parameters
file - the jar file to mount
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.
Parameters
path - the path to the file to resolve
Returns
the file that is found, or a file object representing a filethat does not exist (ie. ! file.exists()). This methoddoes not return null.
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 mixin to have the same effect as import, but the actual loading of the file is deferred until the symbol it provides is accessed. For example:

 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.
Parameters
path - the path to the directory containing the files thatmay be loaded by this package
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.
Parameters
isConst - true if constant
name - the variable name
obj - the value to assign to the variable
scope - (optional) the scope to export the reference to,defaults to the global-scope.
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:
  • iterator() => iterator of stack frame objects beginning with the current node
  • getLineNumber()
  • getFile() => AbstractFile
  • getName() => function name, or null
Returns
the head of the stack-frame chain.
dumpStack
function dumpStack(args)
Dump the current stack backtrace
Parameters
out - (optional) the output stream to dump to, defaults to stderr
getDescription
function getDescription(scope)
Get a description of the scope. Return an object with the following properties:
Property NameProperty Description
typestring name scope's type, valid types are "function", "object", "file", and "global"
textdescription of scope as appopriate for the scope's type, ie function name, object type, file name, etc
The type field might be null for various minor types of scopes that get created, such as for curly braces, for loops, etc.


Last modified: Wed May 12 13:01:44 CEST 2004