a log of my experiments with the rhino javascript engine, maybe even including getting some actual work done

Monday, October 15, 2007

readDir

Once you got rhino up and running, one of the first things you'd want to be able to do is read and write files. Out of the box, rhino doesn't offer too much in this department. To load javascript code from files, you got:

load(["foo.js", ...]) Load JavaScript source files named by
string arguments.



Good enough for a start - you can write some library functions/objects/whatever and load them as needed. A far cry from what one would really like to have, which is some sort of module framework, but still. What's sorely missing is some facility for autoloading some initialization code on startup, I haven't yet figured out how to do that. Maybe later.

To load data from files, there is

readFile(fileName [, encoding])
Returns the content of the file as a string.
Encoding of the string can be optionally specified.
readUrl(url [, encoding])
Similar to readFile, reads the contents of the url.



Not bad - but there are still many times when you would want to read the list of file names in a directory, optionally filtered by a wildcard pattern (or better - a regexp) and process them in a loop, or something. So let's get started on a stdio object, containing a readDir function and with time, hopefully, more of the missing file i/o functionality:

this.stdio = {

get moduleName() { return 'stdio'; },

readDir : /* string[] */
function ( /* string */ path,
/* opt RegExp */ filterRx
)
{
var f = new java.io.File( path );
var jlist = f.list(); // a java String[]
if ( jlist === null )
{
return null; // path is not a directory
}
var list = [];
if ( arguments.length > 1 )
{
list = jlist.filter( RegExp.prototype.test, filterRx );
} else
{
list = [].concat( jlist );
}
list = list.map(String);
return list; // a js array of js strings
}

};
(Ok, trying to format code in Blogger is painful).

Note that the job gets done by reaching into a Java class (java.io.File). But what Java code returns are Java (not Javascript) data types. So I put some effort into conversion: a handy way to convert a Java array into a Javascript one is by concating it with the empty array; but at that point, the array still contains Java strings (represented by somewhat opaque JS objects), in practice this means the entries don't have quite the same methods you would expect in JS strings. This is fixed by applying the Javascript String function, which (to remind you) when called not as a constructor, essentially calls the toString method of its argument.

Friday, October 5, 2007

getting started

Rhino is an open-source implementation of JavaScript written entirely in Java. It is typically embedded into Java applications to provide scripting to end users.

But, you can also make it work the other way around: write javascript, either in script files or interactively in the rhino shell, with the option of calling into java classes from javascript code. That way, all the goodies available in the java standard libraries, or any jars that you may feel like adding to rhino's classpath, are at your fingertips. Let's see how.

Here's the rhino download page at mozilla.org. Before that, you need java: in my case (Kubuntu Linux) that means aptitude install sun-java6-jdk. Just pull the js.jar from the downloaded rhino zip file, and you can already run java -jar js.jar to launch an interactive javascript shell in your terminal. Try typing help(); for some rather cryptic assistance. Either quit(); or end-of-file (Ctrl-D) exits rhino. java -jar js.jar -help produces a terse usage message, as you would expect.

To improve a bit on this, I pick a folder in my PATH (in this example, ~/bin), create a subfolder ~/bin/rhino and throw the js.jar there. In ~/bin, I create a launcher script for the rhino interactive shell, so I can quickly start it up by the short command js.

The js script can actually do a couple of other useful tasks for me. Here's what it currently contains:


#! /bin/bash
dir=$(dirname $0)
export CLASSPATH=${CLASSPATH}${CLASSPATH:+:}${dir}/rhino/\*
echo using CLASSPATH=$(printenv CLASSPATH) > /dev/stderr
[ "$1" = "-f" ] || { RLWRAP="rlwrap -C js" ;}
exec $RLWRAP java ${JAVAOPTS} \
org.mozilla.javascript.tools.shell.Main -strict "$@"


Ok, let's explain:
  • line 1 is pretty obvious, right?
  • line 2 lets the script know where it's located
  • line 3 allows java to find the rhino packages and classes, and maybe other jars I want to be searched for java classes I'll want to use from rhino. I take care not to obliterate any classpath that might be already set. The star means all jars found in the rhino folder will be added to the classpath. Then I echo (line 4) a reminder to myself about what's there.
  • in case you're wondering about rlwrap - of course, if you're planning to do some work at a command line, you want the line editing, history etc. features you're used to from bash, and the nifty little rlwrap command gives you that. Need I say, aptitude install rlwrap?
  • in the last line, why don't I just call java -jar .. ? Well, there's a little gotcha: if you do that, java will ignore the classpath you have so carefully set. You could work around that by using the -cp ${CLASSPATH} option to java, but I did it my way.
Voila, just chmod +x bin/js and your readline-enabled rhino shell is ready to run.