A JavaScript port of most of Rainbow (conanite's JVM-based Arc implementation).
Arc JavaScript Scheme HTML
Failed to load latest commit information.
build.js Separated static fields and some constructor behaviors. Mar 31, 2012



This is a port of Conan Dalton's Rainbow, a Java implementation of
Arc, to JavaScript. It hasn't been used for any real applications yet,
but it's at the stage where it seems to be working under the test
cases it's been given. If you write an application that uses it,
you're likely to stumble across a few simple-to-fix bugs here and
there. Bug reports and bug fixes are both welcome. ^_^

You can play around with the REPL here:


At over 10000 lines of code (not including blank lines and comments!),
Rainbow.js is a pretty freaking big JavaScript file, but with the help
of the Closure Compiler, I'm minifying Rainbow.js together with the
code for the test REPL down to about 148 KB. I'm using a command like

  java -jar compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS \
    --js index-first.js --js rainbow.js --js index-last.js \
    --js_output_file index-min.js

I've observed that simple Rainbow.js-based test applications that
*don't* have a full REPL can minify down to a smaller size. However,
it's not very much smaller yet. Ideally, if a Rainbow.js application
only uses the compiler during initialization, I'd like the Closure
Compiler to be able to weed out the whole Rainbow.js compiler as dead

The main goals of this project are as follows:

- To be faithful to Rainbow (even where it differs from Arc 3.1), even
  down to having similar code wherever possible so that the Java
  Rainbow and Rainbow.js codebases can improve side-by-side.

- To be performant. Java Rainbow is the fastest faithful Arc
  implementation, and it would be nice for Rainbow.js to carry that
  benefit over as much as possible. As part of this goal, I've done
  silly premature optimizations like using for loops and imperative
  style instead of higher-order functions.

  Actually, it turns out that Rainbow.js performs even faster than
  Java Rainbow in my tests, at least on Chrome, Firefox, and Opera.

- To be compatible with lots of browsers, ECMAScript 5's strict mode,
  and the Closure Compiler. We're probably already there, at least for
  people who've upgraded their browsers.

As you can see, Rainbow.js is coming up on a point where its
unfinished business is in short supply. If you have any ideas for
where you want this project to go, please feel free to contact me or
open an issue or something. :)

Despite the focus on keeping the Rainbow.js code similar to the Java
Rainbow code, it differs in at least the following ways:

- There is no support for threads or Java-specific operations.
  However, for certain thread operations and Java-specific operations
  that make sense in a single-threaded JavaScript program (even if
  they don't do anything useful!), a JavaScript equivalent is given
  under the same name.

- Input streams in the implementation of Rainbow.js are asynchronous,
  but in the language itself they're still synchronous. This is done
  by making the evaluation model itself asynchronous. Actually,
  there's still one place where Arc code is run synchronously: when
  calculating the toString of a Rainbow tagged value. If input would
  block in this context, an ArcError is thrown instead.

- In order to allow for asynchronous IO during the compilation phase
  of a Rainbow command (as though anyone would ever really use that
  :-p ), compilation is now performed by way of Instructions, the same
  way as execution is performed. For example, in Java Rainbow, calling
  'eval creates a new VM object, whereas in Rainbow.js it uses the
  same VM it's executing in. This means the behavior of capturing a
  continuation during compile time (during the expansion of a macro)
  may be quite different. For the moment, I recommend not actually
  capturing continuations at that time; the instructions are currently
  implemented in terms of a lot of mutation "on the heap", which a
  captured continuation won't restore, so it's bound to be a bit ugly.

- The Java version of Rainbow uses the Java/CC parser generator. I see
  no suitable replacement for that in JavaScript: Most
  JavaScript-targeting parser generators support parsing from strings
  but don't support incremental parsing from streams (which is to be
  expected, considering the fact that JavaScript doesn't really have a
  standard, widely-used stream type), and although ANTLR seems to be a
  bit of an exception, ANTLR's support for JavaScript seems to be a
  bit unstable. Instead of bothering to port the Java/CC grammar
  specification to some other kind of grammar specification that
  doesn't really solve the right problem, I've hand-rolled the parser.
  My parser actually implements a syntax that's not quite the same as
  Rainbow's, in order to make the implementation easier. Where it
  differs from Rainbow (e.g. the way it parses "(#\newlyne)" as an
  error rather than as "(#\n ewlyne)"), I believe it's actually closer
  in behavior to Arc 3.1.

There are other significant design issues worth mentioning, which I
*don't* consider code differences:

- Where Java Rainbow uses doubles, longs, ints, and chars, Rainbow.js
  uses JavaScript numbers--which are 64-bit floating point values,
  with about 53 bits of precision when used for exact integer
  calculations. Some floating-point calculations and some calculations
  on very large integers may not be perfectly consistent with Rainbow.

- Where Java Rainbow relies on the platform's default charset for the
  purposes of mapping bytes to characters and vice versa, Rainbow.js
  currently uses big-endian UTF-16, since String.prototype.charCodeAt
  returns UTF-16 code units. The big-endianness was chosen rather
  arbitrarily, but at least this way
  "str.getCharCodeAt( i ).toString( 16 )" shows the nibbles in the
  same order as they appear in the stream.

Some other miscellaneous notes:

It may have been possible to add synchronous IO without changing the
evaluation model too harshly, by using Rainbow's existing support for
first-class continuations. However, I instead implemented it in a way
that will not cause the VM to be copied.

Incidentally, given the asynchronous evaluation model, it may turn out
to be possible to implement full threading semantics.