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:- Syntax - describes the syntax extensions
- Library - describes the library functions
- Compiler - describes how to use the actual compiler
Syntax
jwacs adds 4 new keywords to Javascript. It also extends the syntax of thethrow
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:
resume continuationfor resuming a continuation without specifying a return value, or
resume continuation <- valueto specify a return value.
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) resume k <- 20; else return 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 -> continuationThe 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
- When talking about replacing/discarding the call stack, it is sometimes
important to remember an important restriction:
resume
,suspend
, and extendedthrow
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 aresume
,suspend
, or extendedthrow
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 asJwacsLib.sleep
is executed, becauseJwacsLib.sleep
executes asuspend
statement. - 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_fileThe 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 byimport
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/jwacsor pass the following list as the
:prefix-lookup
keyword argument
to build-app
:
'(("/lib/" . #P"/home/james/lib/") ("/" . #P"/home/james/jwacs/"))