Skij: Scheme in Java for Interactive Debugging and Scripting 

Michael Travers (mt@watson.ibm.com)

IBM T. J. Watson Research Center

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: 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: 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: To start a console listener in its own thread, you can say: 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: 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:

    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):

    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:

    (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: 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:

    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
     

  •