Code Monkey home page Code Monkey logo

rainbow-js's Introduction

Rainbow.js

Rainbow.js logo

CI

Rainbow.js is an implementation of the Arc programming language for JavaScript. It's a port of Conan Dalton's Rainbow, a performant implementation of Arc for Java.

Compared to Rainbow, Rainbow.js isn't quite as full-featured. Pieces that are missing include threads, a Java FFI, the system function, and sockets. In principle, some of these features could be added. Some, like threads, were only ever skipped because Rocketnia didn't realize it was possible to translate them to JavaScript at first. :)

To use Rainbow.js at the command line, first install Node.js, and then run the following command to install the rainbow-js-arc command:

npm install --global rainbow-js-arc

Then, pick a directory you'd like to run Arc from. This directory will need a number of libraries like arc.arc in it. The rainbow-js-arc command lets you copy those libraries into your chosen directory like so, where <my-arc-host-dir> is the directory's path:

rainbow-js-arc init-arc <my-arc-host-dir>

Then you can run commands similar to those of Java Rainbow, using rainbow-js-arc run-compat [args...] from within that directory. For instance, rainbow-js-arc run-compat runs a REPL, rainbow-js-arc run-compat --help displays information about other options, and rainbow-js-arc run-compat -e '(prn "Hello, world!")' -q displays "Hello, world!" and quits. More usage scenarios are documented in the readme for Java Rainbow.

ℹī¸ For a better REPL experience, we recommend installing rlwrap and using rlwrap rainbow-js-arc run-compat.

Once you've copied the Arc core libraries into a directory, there isn't any easy way to upgrade or uninstall them except by deleting the directory and rebuilding it again. We recommend treating the Arc host directory as a build target and using a build script to copy in any libraries you want to add or patches you want to apply. Alternatively, you could use Git to track changes to the directory, so that in case it gets messed up when you try to upgrade or uninstall something by hand, you can restore a previous state.

Besides being usable from the command line, Rainbow.js can also be used from the browser. You can play around with a Rainbow.js REPL on the web here (or here, which loads the core Arc libraries and takes slightly more resources to do so).

Minified with the Closure Compiler, and without the core Arc libraries, Rainbow.js comes out to about 156KiB. The minification command we're using for the web REPL is like this, where index-first.js and index-last.js implement the REPL and the I/O primitives needed by Rainbow.js:

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

Priorities of Rainbow.js

Rainbow.js is split between being a faithful implementation of Arc and being a faithful port of Java Rainbow. Originally, we preferred to be faithful to Java Rainbow in a bug-for-bug way so that the maintenance of one codebase could be translated to the other. However, there have been many places where a more significant refactoring has been needed, and there have been a few pieces of functionality (such as the JavaScript FFI and quit's status code support) that have been implemented despite not existing in Java Rainbow.

The reason we ported Rainbow was its performance. Rainbow.js is a rather faithful Arc implementation, complete with continuation support, and it was also the fastest one among the options at the time. Rainbow.js has been written with the Closure Compiler in mind to help ensure that performance carries over. In practice, it seems to have carried over well enough to provide a fast-loading REPL, although we still haven't properly properly tested this. (TODO: Run Rainbow's benchmark suite.)

The reason we ported Rainbow to JavaScript was JavaScript's cross-platform availability. In 2012, JavaScript was one of the only languages that could be used on an iOS device. As of 2022, many more languages compile to JavaScript and/or WebAssembly and have source map support for debugging purposes, so this may be a decision worth revisiting at some point.

Future goals

Rainbow.js is maintained in basically one giant file of more than 10,000 lines of code, primarily because Rocketnia wasn't familiar with any easy way to find and replace text across multiple files. We might do something about that someday, possibly bringing the directory structure more in line with that of Java Rainbow.

Rocketnia observed in 2012 that simple Rainbow.js-based test applications that didn't have a full REPL could minify down to a smaller size. However, it wasn't very much smaller at the time. Ideally, if a Rainbow.js application only uses the compiler during initialization, we'd like the Closure Compiler to be able to weed out the whole Rainbow.js compiler as dead code. In 2022, ihe Closure Compiler is still one of the go-to options for JavaScript applications where tree-shaking of dead code is critical, so we may just need to try again (and possibly add a lot more type annotations to the code).

The JavaScript FFI capabilities of Rainbow.js could use some more attention. Presently, Rainbow.js just implements java-invoke in a way that imitates the way it works in Java Rainbow. Not only is "java-invoke" a misnomer in the Rainbow.js context, but it's not a full-featured FFI. The Rainbow.js web REPL also provides the window object, which makes it possible to java-invoke JavaScript's eval function to access JavaScript's other functionality, but some of the automatic type conversions java-invoke provides could make it difficult to pass certain JavaScript values around without them being subtly adjusted on their way through.

Differences from Java Rainbow

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 I/O during the macroexpansion of an expression, 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, we 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 unreliable.

  • The Java version of Rainbow uses the Java/CC parser generator. As of 2012, we've found 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 was to be expected in 2012, considering the fact that JavaScript did't really have a standard, widely used stream type), and although ANTLR seemed to be a bit of an exception, ANTLR's support for JavaScript seemed unstable. Instead of bothering to port the Java/CC grammar specification to use with some other grammar-based parser that doesn't give us what we need, we've hand-rolled a recursive descent parser. Our 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)), it may be closer in behavior to other implementations of Arc.

  • The quit function now uses its argument as the exit code if it's a number between 1 and 255, inclusive. Otherwise, it exits with an exit code of 0. This is consistent with the Racket implementations of Arc, which just use Racket's exit to implement quit, and it's handy for implementing continuous integration scripts. Currently, in Java Rainbow, quit ignores its argument and always exits with a status code of 0.

  • Rainbow.js defines peekc from Arc, which Java Rainbow doesn't define.

  • Rainbow.js has a modified version of rainbow/rainbow-libs.arc that doesn't load these Java-specific libraries:

    • rainbow/welder.arc
    • rainbow/fsb.arc
    • rainbow/tetris.arc
    • rainbow/mines.arc

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

  • Where Java Rainbow uses doubles, longs, ints, and chars, Rainbow.js uses JavaScript numbers. These 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.

rainbow-js's People

Contributors

rocketnia avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rainbow-js's Issues

Make the npm package more useful

The npm install rainbow-js-arc npm package is currently better than nothing, and it has two parts, neither of which is in the best shape:

  • A Node.js library that can be imported with require("rainbow-js-arc"). This library has no documentation.
  • A rainbow-js-arc command that can execute Arc code. This command has to be run from the node_modules/rainbow-js-arc/src/arc/ directory, or else it can't load essential libraries like arc.arc.

Let's facilitate a workflow like this:

  • Application developers set up a directory in which they assemble arc.arc and all the libraries they're using.
  • Library developers offer a command that operates on a given directory by performing one of the following tasks:
    • Copying the library's Arc code into the directory.
    • Getting a map from library versions to a list of file paths that represent code belonging to this library, application state belonging exclusively to this library, and application state which may be a collaboration between libraries. This list can be used to back up and restore application state on a whole-application or per-library basis, and it can be used to facilitate certain simple cases of library uninstallation or upgrade.
  • Rainbow.js offers a command that operates on a given directory by performing one of the following tasks:
    • Performing any of the above tasks for the core libraries (such as arc.arc).
    • Performing compound package management actions, such as library upgrades.
    • Executing batch-mode Arc programs based on some given filenames or expressions.
    • Starting a REPL.
    • Perhaps other, more adventurous things, like attaching a REPL to another running Arc program, running an Arc program in such a way that files reload automatically when modified, or starting a language server for IDE interaction.
    • Interpreting command line arguments in a way that conforms better with what the original Rainbow would do. This way, if we change up the command line argument parsing logic somewhat, we can still retain some parity with Java Rainbow.

Document how to use as a JS library with `require("rainbow-js-arc")`

We should document the library people get when they require("rainbow-js-arc") from a Node.js program. This is the API surface area that should be documented:

const rainbowLibrary = require("rainbow-js-arc");

const rainbowInstance =
    rainbowLibrary.makeRainbow(
       System_in, System_out, System_err, System_getenvAsync0,
       System_getenvAsync1, System_exitAsync, System_fs);

const rainbowInstance2 =
    rainbowLibrary.makeNodeRainbow(stdin, getStdout, getStderr);

const rainbowInstance3 = rainbowLibrary.getSharedRainbow();

The documentation for makeRainbow() is bound to be the most complex part. If possible, we should document it thoroughly enough that people can write their own I/O systems for Rainbow.js like index-first.js, index-last.js, and rainbow-node-src.js do.

Make the startup timer more accurate

Currently, timing begins from the moment Console_st.mainAsync() is called. In Java, the start of main is one of the earliest, if not the earliest, possible times to do something. But in our Node.js and web REPLs, we spend some time loading Rainbow.js itself, and we can potentially start the clock much earlier on. Let's do that.

Update the readme and the demo page text

The readme and the demo page could be updated in a few ways:

  • Give more up-to-date instructions for how to install and run the project. Let's formulate some specific advice for how to set up Node.js applications and front-end web libraries that run Arc code using Rainbow.js.
  • Reevaluate how the performance compares with Java Rainbow. We should at least tone down the old excitement we had that was based on a version of Rainbow.js that didn't pass the unit tests, but we might have a more interesting perspective to show if the benchmarks can run (#5).
  • Most features that don't yet exist are possible to "implement" in the same way we do already for System_out, System_getenvAsync0, and so on, where different wrappers like src/node/rainbow-node.js and src/web/index-first.js implement these variables in ways that make the most sense for their platforms. Threads are absolutely possible to implement in terms of continuation-passing style, too, which was something I learned in part thanks to doing this port. While I didn't start porting this project with that extent of parity in mind, it is possible, and let's make sure the readme reflects that potential.

Implement stubs for bindings used by the unit tests

In the browser-based REPL, running the (rat) unit tests gives one test failure with a very clear error message: "No filesystem."

We could do the same thing for other operations the unit tests use, such as java-new. Having some binding for java-new will then cause a few more files of unit tests to run, as per the conditional logic in src/arc/lib/unit-test.arc. (Like the other unit tests, these extra ones pass except for some expected failures, like when they attempt to use the java-... FFI operations.)

Create a `run` subcommand, not just `run-compat`

We should create a rainbow-js-args run [args...] subcommand that has functionality similar to rainbow-js-args run-compat [args...] for executing Arc programs in batch mode and starting REPLs.

By allowing ourselves to change the CLI syntax, we can probably achieve a bit more flexibility than what Java Rainbow supports, such as the way Racket's CLI supports interleaving files to load and expressions to evaluate. Java Rainbow's CLI can already execute multiple files and multiple expressions, but it executes every file before executing any of the expressions.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤ī¸ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.