Contents:
What is Skij?
Why would I want to use Skij?
What is Scheme?
What's a scripting language?
How do I download and install Skij?
How do I run Skij?
How does Skij let you manipulate Java objects?
What other Scheme extensions does Skij provide?
What parts of Scheme are missing or different in Skij?
Implementation Notes
History
What is Skij?
A small Scheme interpreter, written in and running
in Java, with extensions that allow a user to interactively
create, inspect, manipulate, and script arbitrary Java objects.
Skij is also "Jikes" spelled backwards, sort of. Skij comes from the
same research
group that brought you the Jikes
Compiler and Jikes
Debugger.
Why would I want to use Skij?
There are a number of different ways to use Skij:
-
Debugging
-
Skij gives you hands-on access to your own in-progress Java applications.
You can effectively control the operation of Java objects from within the
VM. Tests can be performed on the fly without having to go through a compilation
cycle.
-
Experimentation
-
You can use Skij to interactively try out a set of Java APIs. This is especially
useful where the documentation is inaccurate or incomplete (all too common).
-
Scripting and Rapid Prototyping
-
An interactive, typeless interpreter permits faster development of applications,
especially when performing "glue" tasks of connecting and scripting
pre-existing components.
-
Expressiveness
-
Scheme includes important language features not found in Java, and can
thus make more compact and expressive programs for certain tasks.
Skij was originally intended to be chiefly a debugging and experimentation
interface to Java, but it has been also used to develop applications. Being
entirely interpreted, it is not very fast, but still quite suitable for
non-speed-critical uses.
What is Scheme?
Scheme is a compact and elegant dialect of Lisp, invented in 1975 by Guy
Steele and Gerald Sussman. It is widely used as a teaching and research
language. Its small size and simple semantics make it easy to implement,
yet it has many advanced features, including higher-order functions, first-class
continuations, and macros. For more information, see the Scheme
home page or the online language
specification. The excellent computer science textbook, Structure
and Interpretation of Computer Programs, treats the elements of
Scheme programming and implementation.
What's a scripting language?
The term "scripting
language" generally refers to interpreted, late-binding, loosely-typed
languages that are designed to "glue" existing components together. The
canonical examples are Perl and Tcl,
and perhaps Visual Basic. In the scripting model, components are written
in "system programming language" and then glued together with the scripting
language, often by separate people or organizations. Scripting languages
have found widespread usage for rapid development of user interfaces and
web-based applications, among other things.
Skij can be thought of as a scripting language for Java. It has the
standard characteristics of a scripting language: it is compact, it can
be used interactively, and it provides access to the features of the underlying
platform. Since it is an extension of Scheme, it inherits Scheme's elegance
and power.
The distinction between scripting and system languages is not a hard
and fixed one. In fact, some of the best programming environments have
managed to use high-level languages like Lisp or Smalltalk for tasks at
every level of the system architecture, from user interface to device drivers.
However, such grand unifications are hard to achieve with mainstream languages.
Other Scheme-like scripting languages include Guile,
which is being developed as a standard scripting interface for Gnu software,
and George Carette's SIOD.
Other scripting languages for the Java environment include Jacl
from Sun (a version of Tcl), JPython,
and BeanShell.
There are other
Scheme implementations for Java, notably Kawa.
Kawa compiles Scheme to Java byte codes, so it will execute Scheme much
faster than Skij. However, Skij has superior access to Java facilities,
and is smaller and generally simpler. Kawa is probably better if you want
an almost full-blown compiled Scheme; Skij is probably better if you want
a small Scheme-like scripting interface to the Java universe.
How do I download and install Skij?
Skij should be able to run on any JDK1.1-compliant Java implementation.
The inspector requires Swing,
which is available for download from Sun.
1) Download and expand the file skijpkg.zip. If you are reading
this page from a local file you probably have already done this. If not,
look for the package on IBM's alphaWorks
site. IBM internal users can click here.
2) The package contains a file called skij.jar, which contains
the Java and Scheme code used by Skij. Put this file in your Java CLASSPATH
variable.
How do I run Skij?
There are several ways to run Skij: standalone, as an applet, or in conjunction
with another application.
Standalone:
After downloading, enter the following at your
OS prompt:
> java com.ibm.jikes.skij.Scheme
This starts up a Scheme listener on the standard Java console (System.in
and .out). If for some reason you don't want to use the Java console,
you can create a listener window by the alternative invocation:
> java com.ibm.jikes.skij.Scheme -init "(make-awt-listener-window)"
If you are an Emacs user, you will probably want to use the console interface
by means of the M-x shell-mode or M-x run-scheme commands. This gives you
free parenthesis matching, history, and other useful tools.
As an applet:
If your browser supports JDK 1.1, you can try out a Skij
applet right now. As far as I know, only Netscape 4.06 and above supports
enough of JDK 1.1 to run Skij.
As an add-on debugging interface for Java applications:
Starting Skij from Java
You can add Skij to an existing Java application (or applet) as a debugging
faclity. To bring up a Skij listener on the Java console (System.in/out),
you can just execute this line from within your Java code (for instance,
at the end of your main method):
com.ibm.jikes.skij.Scheme.start();
If you prefer not to use the Java console as a Skij listener, you can create
a Listener window via the following code:
com.ibm.jikes.skij.Scheme.start("(make-awt-listener-window)");
To start a console listener in its own thread, you can say:
com.ibm.jikes.skij.Scheme.start(null, true);
If you want to include a call to Skij from within an application; but still
allow the application to be compiled without Skij being present, you can
use this bit of trickery. It will create a Skij listener in the console,
with its own thread.:
try {
Class.forName("com.ibm.jikes.skij.ListenerConsole").newInstance();
}
catch (Throwable e) {
System.out.println("Error starting Skij: " + e.toString());
}
Substitute ListenerWindow for ListenerConsole in the
above to bring up a separate Skij window.
Starting your application from Skij
Alternatively, you can start Skij running and bring up your application
from within it, by typing something like:
(in-own-thread
(start-application 'mypackage.MyAppClass [args]))
This calls the application's main method in a new thread, leaving
the real main thread to run Skij. String arguments can be included in the
call to start-application.
Accessing application objects from Skij
Once you have Skij running alongside your application, there is still the
issue of how Skij is to get ahold of your application's objects. There
are a few different ways to do this.
If the application has defined public classes with public and static
methods or fields, these can be referred to by name using the static forms
of the invoke and peek functions described below.
You can also have an application add its objects to Skij's top-level
environment through the following call:
com.ibm.jikes.skij.Environment.top.addBinding(Symbol.intern(name),
value);
Where name is a string and value is any
application object.
You can also invoke the Skij inspector from Java code (you must have
Swing installed and in your classpath):
com.ibm.jikes.skij.Scheme.inspect(object);
Once you have inspectors open, the object in the topmost inspector window
can be accessed from a Skij listener through the inspected variable
How does Skij let you manipulate Java objects?
Skij includes a number of primitive functions that allow you to create
and manipulate arbitrary Java objects. These are new, invoke,
peek,
poke,
invoke-static,
peek-static, and poke-static. Some general rules apply
to all of these procedures:
-
Null values can be returned from invoke, peek, and the
corresponding static functions.
-
Class arguments must be either the fully qualified class name as a string
or symbol; or a java.lang.Class object. Method and field names
can be either strings or symbols.
-
In versions of Java prior to 1.2, only public classes, constructors, methods,
and fields may be accessed. This is a limitation of the Java Reflection
API. In JDK 1.2 and above, it is possible to access anything. Beta versions
of JDK 1.2 are available from Sun's Java
Developer Connection website.
-
Boxing and unboxing of Java primitive types is performed automatically
where appropriate.
-
(new class [args]*)
-
This procedure creates and returns a Java object of the specified class,
passing the arguments along to the appropriate constructor.
Example:
-
(define button (new 'java.Awt.Button "Press Me"))
-
-
(invoke object method-name [args]*)
-
Invoke the named method on the object, with the specified arguments. Returns
the value returned by the method, if any.
-
Examples:
-
(invoke button 'setText "Please Press Me")
-
(invoke window 'add button)
-
-
(invoke-static class method-name [args]*)
-
Invoke the named static method of class, which may be a class object
or the name of a class, with the specified arguments. Returns the value
returned by the method, if any.
-
Example:
-
(invoke-static 'java.lang.Thread 'sleep (long 100))
-
-
(peek object field-name)
-
Return the value of the named field.
-
Example:
-
(peek (invoke button 'getSize) 'width)
-
(peek-static class field-name)
-
Return the value of the named field.
-
Example:
-
(peek-static 'java.lang.System 'out)
-
-
(poke object field-name new-value)
-
(poke-static class field-name new-value)
-
Sets the value of the named field.
What other Scheme extensions does Skij provide?
Evaluation and reflection
-
(eval form)
-
(eval form env)
-
Evaluate the form.
-
(current-environment)
-
(global-environment)
-
There are no Scheme access functions for environments yet, but you can
use invoke to call the Java accessors.
-
#, (reader syntax)
-
This reader syntax will eval the following form at read-time and insert
it into the expression being read. Based on the Common Lisp #. feature.
Threads and synchronization
-
(run-in-thread thunk)
-
Starts a new Java thread which will invoke thunk, which should be
a procedure of zero arguments. Examples:
-
(run-in-thread (lambda () (print (fact 1000)))
-
-
(synchronized object expression*)
-
Evaluate expressions as in a begin, but performs Java synchronization
around the evaluation. object specifies the object that
holds the synchronization flag.
Event callbacks
Skij defines an adaptor classes that allow you to set up Scheme procedures
to handle Java AWT events. This class is called GenericCallback
and implements as many of the Listener interfaces as I've gottern around
to throwing into it. A GenericCallback object contains a Skij
procedure of one argument. When an event is signalled, the procedure gets
called with the event object.
Example:
(define b (new 'java.awt.Button 'pressMe))
(define listener (new 'com.ibm.jikes.skij.GenericCallback
(lambda (event) (print "I've been pressed"))))
(invoke b 'addActionListener listener)
Since events are self-identifying, it doesn't hurt anything to have all
Listeners and all events lumped into a single type of handler.
Exception handling
Skij's ability to deal with Java exceptions is limited to the catch
special form, which lets the program get its hands on the Java exception
object.
-
(catch body*)
-
Evaluate body. If a Java exception occurs within the body, cease evaluation
and return the exception object as the value of the catch form.
Dynamic lookup
Skij includes a way to do dynamic variable lookup, in addition to normal
Scheme lexical binding. Dynamic binding is implemented as an alternative
form of value lookup. Normally the value for a variable is searched for
using the lexical chain of defining environments. In a dynamic lookup,
the search chain is through the dynamic environments (or call tree).
-
(dynamic name)
-
Look up the value of name dynamically.
example:
(define (html-out tag)
(display (string-append "<" tag ">") (dynamic *html-output*)))
(let ((*html-output* (new 'com.ibm.jikes.skij.OutputPort (new 'java.io.FileOutputStream file))))
(tag "body"))
Dynamic binding is considered dangerous (and inefficient), and it may be
removed from the language. It's a good idea to use a variable-naming convention,
as above, to indicate variables that will be subject to dynamic lookup.
Other Extensions:
-
(trace boolean)
-
turns tracing output on or off.
-
(backtrace [condition])
-
Print a Scheme backtrace for condition, or the last condition thrown to
top level if the argument is omitted.
-
(require facility)
-
Loads files from the library. Facility is a symbol/string, ie, 'inspect.
Most library functions autoload so you don't need to use this.
-
<<< (variable)
-
contains last value printed by the listener.
Inspector
Skij contains an inspector facility, which makes use of Swing,
Sun's new interface toolkit. To use the inspector, you must download and
install Swing before starting Java. This facility is still somewhat primitive
but quite useful for finding your way through Java structure.
(inspect object)
Create a new inspector window looking at object.
inspected
This variable always contains the object displayed by the topmost inspector
window.
What parts of Scheme are missing or different in Skij?
Skij departs from the published Scheme standards in a few ways:
-
Symbols are case-sensitive.
-
Strings are immutable since they are implemented using the java.lang.String
class. Scheme's string-set! function is not implemented.
-
call-with-current-continuation only supports escape procedures
(that is, a continuation object may only be used within the dynamic context
of the call/cc that generated it).
-
The reader can't deal with atsign in symbol names or strings that cross
lines (this will be fixed eventually).
-
The Scheme numeric types and functions are neglected; there are no bignums,
rationals, or complex numbers. The reader and arithmetic functions support
Java Integers and Doubles only.
-
Scheme's hygenic macros are not supported. Instead, there is a defmacro
form that is similar to the Common Lisp facility.
Implementation Notes
The most important design principle in Skij: Skij objects are Java objects,
and any Java object can be a Skij object.
Skij uses Java's built-in classes to represent the standard Scheme types
where it can, even though in some cases this causes divergence from the
Scheme standard. All numbers are represented in boxed form using the built
in Java.lang.Integer and java.lang.Double classes, and Scheme strings are
represented using java.lang.String. Scheme vectors are represented by Java
arrays.
Skij defines its own classes for pairs, various types of procedures,
environments, ports, and a few others. There are two types of environment.
The global environment is implemented using Java hashtables, while other
environments use lists.
Dynamic access to Java objects is accomplished by means of the Java
Reflection API, extended to support fully dynamic method invocation.
The Scheme reader is implemented using Java's StreamTokenizer object.
This will probably be changed, since it is not entirely adequate to the
task.
History
Version 1.4 (October 98)
New features:
- libraries can load from .class files
- apropos
- flags to control tail-calling and saving macro sources
Bug fixes:
- better direction of error messages and other console output
- applet support
- get-method and other reflection tools
- string output quotes properly
Version 1.2 (August 98)
New features:
- setf, incf, decf, push (modelled on Common Lisp feature)
- can compile under both JDK 1.1 and 1.2
Bug fixes:
- autoloaded functions now work correctly in all contexts
- file loading is much faster
- errors in loaded files are handled better
- catch/continuation interactions are fixed
- some fixes for instanceof
- toString for lists containing null elements
Version 1.1 (Bastille Day 98)
New features:
- support for access to nonpublic members in JDK 1.2
- backtrace
- more flexibility in define-memoized
- more support for JDK1.2
- faster top-level environments; can store null
Bug fixes:
- dynamic var lookup
- dynamic lookup and recursion was causing stack growth under tailcalling
- set! of dotted arg var
- inexact->exact
- start-application
Version 1.0 (May 98):
- Welcome to Skij