Compiling to byte-code

All Scheme functions and source files are invisibly compiled into internal Java byte-codes. (A traditional interpreter is used for macro-expansion. Kawa used to also interpret “simple” expressions in interactive mode, but always compiling makes things more consistent, and allows for better stack traces on errors.)

To save speed when loading large Scheme source files, you probably want to pre-compile them and save them on your local disk. There are two ways to do this.

You can compile a Scheme source file to a single archive file. You do this using the compile-file function. The result is a single file that you can move around and load just like the .scm source file. You just specify the name of the archive file to the load procedure. Currently, the archive is a "zip" archive and has extension ".zip"; a future release will probably use "Java Archive" (jar) files. The advantage of compiling to an archive is that it is simple and transparent.

Alternatively, you can compile a Scheme source file to a collection of ‘.class’ files. You then use the standard Java class loading mechanism to load the code. The compiled class files do have to be installed somewhere in the CLASSPATH.

Compiling to a set of .class files

Invoking ‘kawa’ (or ‘java kawa.repl’) with the ‘-C’ flag will compile a ‘.scm’ source file into one or more ‘.class’ files:

kawa --main -C myprog.scm

You run it as follows:

kawa [-d outdirectory] [-P prefix] [-T topname] [--main | --applet | --servlet] -C infile ...

Note the ‘-C’ must come last, because ‘Kawa’ processes the arguments and options in order,

Here:

-C infile ...

The Scheme source files we want to compile.

-d outdirectory

The directory under which the resulting ‘.class’ files will be. The default is the current directory.

-P prefix

A string to prepend to the generated class names. The default is the empty string.

-T topname

The name of the "top" class - i.e. the one that contains the code for the top-level expressions and definitions. The default is generated from the infile and prefix.

--main

Generate a main method so that the resulting "top" class can be used as a stand-alone application. See Application compilation.

--applet

The resulting class inherits from java.applet.Applet, and can be used as an applet. See Applet compilation.

--servlet

The resulting class implements javax.servlet.http.HttpServlet, and can be used as a servlet in a servlet container like Tomcat.

When you actually want to load the classes, the outdirectory must be in your ‘CLASSPATH’. You can use the require syntax or the load function to load the code, by specifying the top-level class, either as a file name (relative to outdirectory) or as a class name. E.g. if you did:

kawa -d /usr/local/share/java -P my.lib. -T foo -C foosrc.scm

you can use either:

(require my.lib.foo)

or:

(load "my.lib.foo")

Using require is preferred as it imports the definitions from my.lib.foo into the compile-time environment, while load only imports the definitions into the run-time environment.

If you are compiling a Scheme source file (say ‘foosrc.scm’) that uses macros defined in some other file (say ‘macs.scm’), you need to make sure the definitions are visible to the compiler. One way to do that is with the ‘-f’:

kawa -f macs.scm -C foosrc.scm

Many of the options described earlier are relevant when compiling. Commonly used options include language selection, the --warn-xxx options, and --full-tailcalls.

Compiling to an archive file

Procedure: compile-file source-file compiled-archive

Compile the source-file, producing a .zip archive compiled-file.

For example, to byte-compile a file ‘foo.scm’ do:

(compile-file "foo.scm" "foo")

This will create ‘foo.zip’, which contains byte-compiled JVM .class files. You can move this file around, without worrying about class paths. To load the compiled file, you can later load the named file, as in either (load "foo") or (load "foo.zip"). This should have the same effect as loading ‘foo.scm’, except you will get the faster byte-compiled versions.

Compiling using Ant

Many Java projects use Ant for building Java projects. Kawa includes a <kawac> Ant task that simplifies compiling Kawa source files to classes. See the build.xml in the Kawa source distribution for examples. See the kawac task documentation for details.

Compiling to a standalone application

A Java application is a Java class with a special method (whose name is main). The application can be invoked directly by naming it in the Java command. If you want to generate an application from a Scheme program, create a Scheme source file with the definitions you need, plus the top-level actions that you want the application to execute.

For example, assuming your Scheme file is MyProgram.scm, you have two ways at your disposal to compile this Scheme program to a standalone application:

  1. Compile in the regular way described in the previous section, but add the --main option.

    kawa --main -C MyProgram.scm
    

    The --main option will compile all Scheme programs received in arguments to standalone applications.

  2. Compile in the regular way decribed in the previous section, but add the main: #t module compile option to your module.

    ;; MyProgram.scm
    (module-name <myprogram>)
    (module-compile-options main: #t)
    
    kawa -C MyProgram.scm
    

    This way you can compile multiple Scheme programs at once, and still control which one(s) will compile to standalone application(s).

Both methods will create a MyProgram.class which you can either load (as described in the previous section), or invoke as an application:

java MyProgram [args]

Your Scheme program can access the command-line arguments args by using the global variable ‘command-line-arguments’, or the R6RS function ‘command-line’.

If there is no explicit module-export in a module compiled with --main then no names are exported. (The default otherwise is for all names to be exported.)

Compiling to an applet

An applet is a Java class that inherits from java.applet.Applet. The applet can be downloaded and run in a Java-capable web-browser. To generate an applet from a Scheme program, write the Scheme program with appropriate definitions of the functions ‘init’, ‘start’, ‘stop’ and ‘destroy’. You must declare these as zero-argument functions with a <void> return-type.

Here is an example, based on the scribble applet in Flanagan’s "Java Examples in a Nutshell" (O’Reilly, 1997):

(define-private last-x 0)
(define-private last-y 0)

(define (init) :: void
  (let ((applet (this)))
    (applet:addMouseListener
     (object (java.awt.event.MouseAdapter)
	     ((mousePressed e)
	      (set! last-x (e:getX))
	      (set! last-y (e:getY)))))
    (applet:addMouseMotionListener
     (object (java.awt.event.MouseMotionAdapter)
	     ((mouseDragged e)
	      (let ((g (applet:getGraphics))
		    (x (e:getX))
		    (y (e:getY)))
		(g:drawLine last-x last-y x y)
		(set! last-x x)
		(set! last-y y)))))))

(define (start) :: void (format #t "called start.~%~!"))
(define (stop) :: void (format #t "called stop.~%~!"))
(define (destroy) :: void (format #t "called destroy.~%~!"))

You compile the program with the ‘--applet’ flag in addition to the normal ‘-C’ flag:

java kawa.repl --applet -C scribble.scm

You can then create a ‘.jar’ archive containing your applet:

jar cf scribble.jar scribble*.class

Finally, you create an ‘.html’ page referencing your applet and its support jars:

<html><head><title>Scribble testapp</title></head>
<body><h1>Scribble testapp</h1>
You can scribble here:
<br>
<applet code="scribble.class" archive="scribble.jar, kawa-3.1.1.jar" width=200 height=200>
Sorry, Java is needed.</applet>
</body></html>

The problem with using Kawa to write applets is that the Kawa .jar file is quite big, and may take a while to download over a network connection. Some possible solutions:

  • Try to strip out of the Kawa .jar any classes your applet doesn’t need.

  • Java 2 provides a mechanism to install a download extension.

  • Consider some alternative to applets, such as Java Web Start.

Compiling to a native executable

In the past it was possible to compile a Scheme program to native code using GCJ. However, using GCJ with Kawa is no longer supported, as GCJ is no longer being actively maintained.