jwacs documentation

jwacs is a program transformer. You write code in an extended syntax and then transform it into standard Javascript code. This quick-start document has the following sections:
  1. Syntax - describes the syntax extensions
    1. The function_continuation statement
    2. The resume statement
    3. The suspend statement
    4. Extended throw syntax
    5. The import statement
    6. Some caveats
  2. Library - describes the library functions
  3. Compiler - describes how to use the actual compiler
    1. Using the jwacs executable
    2. Using the build-app function
    3. URI path translation

Syntax

jwacs adds 4 new keywords to Javascript. It also extends the syntax of the throw statement somewhat to allow throwing exceptions into continuation objects.

The function_continuation statement

The new function_continuation statement takes no arguments and returns the return continuation for the current function. For example, in the following code:
  function sleep(msec)
  {
    var k = function_continuation;
    setTimeout(function() { resume k; }, msec);
    suspend;
  }

the local variable k is set to the return continuation for the sleep function. This continuation is resumed by the timeout function that is set by the setTimeout call. When the k continuation is resumed, the sleep function "returns" to the caller, in the same state as when it was called.

You can think of the value returned by the function_continuation statement as being a copy of the call stack (complete with all locals, parameters, and return addresses) at the time of entry into the current function.

The resume statement

The resume statement takes either one or two arguments. The first argument is an expression that returns a continuation to resume, and the second, optional argument is an expression that returns the value for the resumed continuation's function to return. If the optional second argument is omitted, the function returns undefined. The syntax is:

The anonymous function in the setTimeout call above shows the single-argument version of resume. The version of sleep above will return undefined when its continuation is resumed. The following, modified version of sleep will return the number of milliseconds that it was called with:

  function sleep(msec)
  {
    var k = function_continuation;
    setTimeout(function() { resume k <- msec; }, msec);
    suspend;
  }
Note that the first and second arguments to resume must be separated by a left-pointing arrow token. This slightly-hokey syntax is intended to convey that the value is passed into the continuation. Also it makes the grammar unambiguous. :)

You can think of the resume statement as replacing the current call stack with the stack saved in the continuation and then executing a return statement. In particular, since they replace the call stack, resume statements never return.

So, in this code:

  function foo(k)
  {
    if(k)
      return 20;
    else
      resume k <- 20;
    
    alert("you'll never see this");
  }
the alert statement is never executed, since foo either returns or resumes the continuation argument k.

The suspend statement

The suspend statement suspends the current thread. It accepts no arguments.

It is frequently the case that a function that captures its own continuation in one form or another will want to delay its return until some event has occurred. In the case of the fetchData function (see the library section), we want to delay returning until the data has returned from the server, so we save a continuation in an event handler and execute a suspend statement. In the case of the sleep function defined above, we want to delay returning until a specified number of milliseconds have elapsed, so we save a continuation to be resumed by a timer and then execute a suspend statement.

In the above definition of sleep, if the suspend statement were missing, then sleep would actually return twice: Once immediately, and once when the timer fired and resumed its continuation.

You can think of the suspend statement as discarding the current call stack.

Extended throw syntax

Of course, there's more ways for a function to exit than by returning. It is also possible to leave a function by throwing an exception. To make it possible to cause the function whose continuation was captured to throw an exception, jwacs extends the syntax of the throw statement to allow you to throw an exception "into" a continuation:
  throw value -> continuation
The usual, 1-argument syntax still works as expected (ie, it throws an exception in the current control context). However, there is now also an optional second argument that specifies that an exception is to be thrown into a continuation. Note that the second argument is separated from the first by a right-facing arrow (indicating that the exception is thrown into the exception).

For example:

  function strictSleep(msec)
  {
    var k = function_continuation;
    var s = (new Date).getTime();
    setTimeout(function() { 
      var e = (new Date).getTime();
      if(e - s < msec)
        throw "setTimeout did not sleep for long enough!" -> k;
      else
        resume k <- msec;
    }, msec);
  }
In the above code, strictSleep verifies that the timeout is not called until at least msec milliseconds have elapsed. If enough time has elapsed, then it returns msec as in the definition of sleep above. However, if enough time has not elapsed, it throws an error. Note that the exception is thrown into strictSleep's return stack, not into the timeout handler's stack, just as it is strictSleep that returns msec when there is no error, and not the event handler.

You can think of the two-argument version of throw as replacing the current call stack with the stack saved in the continuation argument, and then throwing the exception argument.

The import statement

As a convenience, jwacs also provides an import statement for linking together multiple source files. It has the following syntax:
  import [type] "path";
The type can be one of jwacs, jw, javascript, or js. The type is optional; if it is omitted it will be inferred from the extension of the path.

The path should be a relative or absolute path to a Javascript or jwacs source file to include in the web app. (See the compiler section for details of how absolute paths are resolved). These paths will be passed straight through to the src attribute of a <script> tag, so use absolute paths with care.

Imports of Javascript files will be turned directly into a <script> tag in the output html file. jwacs files that are imported will be transformed into Javascript files, which will then be referenced from a <script> tag in the output html file.

Ex:

  import "../lib/prototype.js";
  import "utils.jw";
will cause a <script> tag to be omitted for ../lib/prototype.js and utils.js. The utils.jw file will be transformed into utils.js.

Some caveats

  1. When talking about replacing/discarding the call stack, it is sometimes important to remember an important restriction: resume, suspend, and extended throw statements all replace the existing call stack, but only back to the nearest non-jwacs function.

    In practice, this means that event handlers called from non-jwacs code (eg, handlers for built-in events and those called by third-party libraries) will return undefined as soon as a resume, suspend, or extended throw statement is executed. In the following code:

        window.addEventListener("load", function() {
          alert("alpha");
          JwacsLib.sleep(5000);
          alert("beta");
        }, false);
    The user will see an "alpha" alert box followed by a "beta" alert box 5 seconds later. However, the anonymous event handler for the "load" event will return as soon as JwacsLib.sleep is executed, because JwacsLib.sleep executes a suspend statement.
  2. The jwacs parser does not perform semicolon insertion, so all jwacs statements must be properly terminated.

Library

tk - this section still needs to be written.

Compiler

There are two ways to use the jwacs compiler: Either as a standalone binary file that is invoked using command-line arguments, or as a Lisp function that is invoked from a Lisp program or REPL.

The compiler is invoked on a single jwacs source file. That jwacs file may contain imports to other Javascript or jwacs files. The compiler transforms all imported jwacs files (and all jwacs files that they import, and so forth) into standard Javascript.

Once Javascript files have been generated, an html file is generated by adding <script> tags to a template html file. If no template file exists, a standard template is used. (If you don't care about generating an html file directly, you can ignore these aspects of the output and just use the Javascript file that will be generated).

Using the jwacs executable

The jwacs executable has the following usage:
  jwacs [options] main_source_file
The following options are available:
-t uri-path
URI-path of the template file to use. Default: the name of the main source file, with new extension ".template". This file will be generated if it doesn't exist.
-r uri-path
URI-path of the runtime script to use. Default: "jw-rt.js". This file will be generated if it doesn't exist.
-o uri-path
URI-path of the output file to create. Default: the name of the main source file with new extension ".html". (Note that this option controls the name of the html output file, not of the Javascript file that is generated from the main source file).
-p uri-path=directory[;uri-path=directory ...]
Specifies the mapping between absolute URI paths and the filesystem. See URI path translation for details.

Using the build-app Lisp function

The build-app function takes one required argument and four keyword arguments. The required argument is a path specifier designating the main jwacs source file to transform. The keyword arguments closely mirror the command-line arguments of the executable (or perhaps it's the other way around):
:template-uripath
URI-path of the template file to use. Default: the name of the main source file, with new extension ".template". This file will be generated if it doesn't exist.
:runtime-uripath
URI-path of the runtime script to use. Default: "jw-rt.js". This file will be generated if it doesn't exist.
:output-uripath
URI-path of the output file to create. Default: the name of the main source file with new extension ".html". (Note that this option controls the name of the html output file, not of the Javascript file that is generated from the main source file).
:prefix-lookup
Specifies the mapping between absolute URI paths and the filesystem. See URI path translation for details.

URI path translation

The paths specified by import statements may be relative paths, in which case files are found in a straightforward fashion, or they may be absolute paths. If they are absolute paths, then you must specify a translation from absolute URI paths to file system paths.

When using the executable compiler, use the -p option to specify the translation. When using the Lisp function, use the :prefix-lookup keyword argument to specify a list of cons cells; the CAR of each cell is the path prefix, and the CDR is a pathname that specifies which filesystem directory that prefix represents.

For example, to indicate that absolute import paths beginning with /lib/ refer to files in the /home/james/lib directory, and all other absolute paths refer to files in the /home/james/jwacs directory, pass the following arguments to the binary:

  -p /lib=/home/james/lib;/=/home/james/jwacs
or pass the following list as the :prefix-lookup keyword argument to build-app:
  '(("/lib/" . #P"/home/james/lib/") 
    ("/" . #P"/home/james/jwacs/"))