JavaScript Shells

Recently I was working on a fairly complex JavaScript script relating to floating point conversions for a new Web page.  After a while I got tired on trying to debug the problem via a Web browser and decided to see if I could find a JavaScript shell, i.e. a standalone Javascript intrepreter just like Ruby's irb, Python's interactive prompt or the Korn shell, which could load and run JavaScript scripts from the command line without having to reload a Web page.

First, some background on the JavaScript langauge for those who are unfamilar with the details.  JavaScript is a complex full-featured weakly typed object- based functional programming language originally developed by Brendan Eich in 1995 while working on the Netscape Navigator browser.  It is most frequently used in client-side web applications but is also used to enable scripting access to embedded objects in other applications.

The langauge has been standardized in the ECMA-262 (ECMAScript) specification.  The first version of ECMAScript was published in June 1997, and was partially based on JavaScript v1.2.  The current version is Edition 3 (Dec 1999) and work is ongoing on the next edition.  Formally, Javascript is a dialect of ECMAScript whose langauge specification is controlled by the Mozilla Foundation.  There are other dialects including ActionScript which the scripting language used in Adobe Flash.  Javascript is still evolving as a language and several versions are in daily use.  The current version is JavaScript 1.8.

The JavaScript engine in Firefox is written in C.  It was orginally called Javascript Reference (JSRef) but nowadays is known as SpiderMonkey.  Other Mozilla products also use this engine and it is available to the public under a MPL/GPL/LGPL tri-license.  The current version, SpiderMonkey 1.7, conforms to JavaScript 1.8 which is a superset of ECMA-262 Edition 3. It consists of a library (or DLL) containing the JavaScript runtime (compiler, interpreter, decompiler, garbage collector, atom manager and standard classes) engine.  This codebase has no dependencies on the rest of the Mozilla codebase. The codebase also contains the routines for a simple user interface which can be linked to the runtime library in order to make a command line shell.

You can download the source code for SpiderMonkey 1.7 here.  Aternatively you can use wget,curl or ftp to download the tarball.  No build script is provided with this version of SpiderMonkey.  Here is how I downloaded, built and smoketest'ed the shell.
mkdir mozilla
cd mozilla
wget http://ftp.mozilla.org/pub/mozilla.org/js/js-1.7.0.tar.gz
tar xzf js-1.7.0.tar.gz
cd js/src
make -f Makefile.ref
If everything compiles correctly, you should then smoketest the JavaScript shell (js) by executing the following command:
./Linux_All_DBG.OBJ/js ./perfect.js 
If the 3 perfect numbers between 1 and 500 are printed and you are returned to your shell prompt without any error messages, all is well.  You should then copy the ./Linux_All_DBG.OBJ/js to /bin or /usr/local/bin to make it easier to use.

Unlike other programming languages, JavaScript does not have a concept of printing to STDOUT or reading from STDIN.  These functions, along with quit(), load() and a small number of other functions are provided within js.  They are not part of the Javascript runtime library.  The first thing you will notice is that everything is function-based.  To exit js, you do not type quit, instead you have to type quit().

Here is the standard Hello World example.
$ cat helloworld.js
//
// Hello World!
//

function helloWorld(name)
{
print("Hello World, " + name);
}
$ /bin/js
js> load('helloworld.js')
js> helloWorld('Finnbarr')
Hello World, Finnbarr
js> helloWorld('Patricia')
Hello World, Patricia
js> quit()
$
Here is the same script called directly from ksh93.  This is possible because of the shebang (!#) sytax on the first line of the script.
$ cat hw.js
#!/bin/js

//
// Hello World!
//

function helloWorld(name)
{
print("Hello World, " + name);
}

helloWorld('Finnbarr');
$ ./hw.js
Hello World, Finnbarr
$
The shell comes with a readline() function which enables you to ask a user to enter a value such a string or a number as the following example shows.
$ cat multiply.js
//
// multiply.js
//

function multiply()
{
print("Enter a number:");
var n1 = readline();
print("Enter another one:");
var n2 = readline();

print("You entered " + n1 + " and " + n2 + ". The result is " + n1*n2);
}

$ /bin/js
js> load('multiply.js')
js> multiply()
Enter a number:
12
Enter another one:
10
You entered 12 and 10. The result is 120
You have full access to all functionality that is defined in the Javascript 1.8 specification.
$ cat regex.js
//
// test regular expression
//

myRe = /d(b+)d/g;
myArray = myRe.exec("cdbbdbsbz");

print("The value of lastIndex is " + myRe.lastIndex);

$ /bin/js -f regex.js
The value of lastIndex is 5
$
You can also disassemble your JavaScript script using the dissrc() function.
js> dissrc(multiply)     

;------------------------- 7: print("Enter a number:");
00000: 7 name "print"
00003: 7 pushobj
00004: 7 string "Enter a number:"
00007: 7 call 1
00010: 7 pop
;------------------------- 8: var n1 = readline();
00011: 8 name "readline"
00014: 8 pushobj
00015: 8 call 0
00018: 8 setvar 0
00021: 8 pop
;------------------------- 9: print("Enter another one:");
00022: 9 name "print"
00025: 9 pushobj
00026: 9 string "Enter another one:"
00029: 9 call 1
00032: 9 pop
;------------------------- 10: var n2 = readline();
00033: 10 name "readline"
00036: 10 pushobj
00037: 10 call 0
00040: 10 setvar 1
00043: 10 pop
;------------------------- 11:
;------------------------- 12: print("You entered " + n1 + " and " + n2 + ". The result is " + n1*n2);
00044: 12 name "print"
00047: 12 pushobj
00048: 12 string "You entered "
00051: 12 getvar 0
00054: 12 add
00055: 12 string " and "
00058: 12 add
00059: 12 getvar 1
00062: 12 add
00063: 12 string ". The result is "
00066: 12 add
00067: 12 getvar 0
00070: 12 getvar 1
00073: 12 mul:0
00074: 12 add
00075: 12 call 1
00078: 12 pop
00079: 12 stop
js> quit()
$
A major limitation of js is that it cannot access files such as an XML documents, DOM objects nor upload or download files.  An alternative command line shell is available from Mozilla called xpcshell.  It is a XPConnect-enabled JavaScript command line shell where scripts running in it can access XPCOM functionality.  For those of you who are unfamilar with XPCOM, it is a cross platform component object model, similar to Microsoft's COM.  XPCOM is frequently used for unit testing.

Besides SpiderMonkey and TraceMonkey, there are a number of other standalone JavaScript shells available including wxjs and JSDB
.  There also is the Rhino engine, created primarily by Norris Boyd, which is a JavaScript implementation written in Java that is ECMA-262 Edition 3 compliant.

I would be amiss if I did not point out that there are alternatives to using a command line shell such as js.  My personal favorite is Firebug which is a Firefox extension that provides a more advanced interactive shell, an advanced DOM inspector, a JavaScript debugger, a profiling tool and various other useful tools.

Plans for SpiderMonkey 1.8 appear to be shelved.  The Firefox development team are currently in the process of replacing SpiderMonkey with TraceMonkey which is based on a technique developed at UC Irvine called trace trees, builds on code and ideas shared with the Tamarin Tracing project, and adds just-in-time (JIT) native code compilation.  The net result is a seriously significant speed increase both in the browser chrome and Web page content.  As with SpiderMonkey, you can download the source code from the TraceMonkey mercurial repository and build your own command line shell.

After languishing for a number of years, JavaScript is becoming an increasing important language for Web applications. It is one of those languages that every Web programmer needs to understand in depth.  Being able to run and debug JavaScript scripts from the command line greatly assists in that understanding.

Enjoy!

1 comments:

trh said...

Thanks, ;)

Post a Comment