Re: Newb question about calling Scheme from Java

Norman Gray <[email protected]> Wed, 3 Sep 2008 12:00:04 +0100
Newsgroups gmane.comp.java.sisc.user
Message-ID <[email protected]>
--Apple-Mail-13--533248074
Content-Type: text/plain;
	charset=US-ASCII;
	format=flowed;
	delsp=yes
Content-Transfer-Encoding: 7bit


Mike, hello.

On 2008 Sep 2, at 21:48, Mike J. Bell wrote:

> Well, the problem is the Java object that is running the SISC  
> interpreter
> needs to pass in a reference to itself so that the Scheme  
> interpreter can
> perform callbacks on it.

For this bit, I think I can help.

I have a webapp which will live inside either Tomcat or Jetty, which  
has the application logic written in Scheme, using SISC.  The  
interaction between the two goes through a SchemeWrapper class, the  
code for which I've included below (350+ lines).  The  
eval(Procedure,Object[]) method in particular might be of use to you.

The way I use that is as follows, where initialiseProcedure is some  
String parameter obtained from the webapp configuration, naming  
(surprise) an initialisation procedure:

                 Object val = SchemeWrapper
                         .getInstance()
                         .eval(initialiserProcedure, new Object[]  
{this});

and the initialisation procedure looks like:

;; INITIALISE-QUAESTOR : <QuaestorServlet> -> void
;; Initialise Quaestor, with a servlet object.  We require this object
;; to implement interface QuaestorServlet
(define (initialise-quaestor scheme-servlet)
   (define-generic-java-methods
     register-handler
     get-init-parameter)
   (define (get-param s)                 ;get parameter, or #f if java- 
null
     (let ((val (get-init-parameter scheme-servlet (->jstring s))))
       (and (not (java-null? val))
            val)))

...))

(register-handler and get-init-parameter are methods on the 'this'  
passed to the procedure)

Good luck!

Norman


-- 
Norman Gray  :  http://nxg.me.uk
Dept Physics and Astronomy, University of Leicester

--Apple-Mail-13--533248074
Content-Disposition: attachment;
	filename=SchemeWrapper.java
Content-Type: application/octet-stream; x-unix-mode=0644;
	name="SchemeWrapper.java"
Content-Transfer-Encoding: 7bit

package org.eurovotech.quaestor;

import java.lang.ClassNotFoundException;
import java.io.InputStream;
import java.io.IOException;

import javax.servlet.ServletException;

import sisc.REPL;
import sisc.data.Value;
import sisc.data.Procedure;
import sisc.data.SchemeBoolean;
import sisc.data.SchemeString;
import sisc.data.SchemeVoid;
import sisc.data.Symbol;
import sisc.env.DynamicEnvironment;
import sisc.interpreter.Interpreter;
import sisc.interpreter.Context;
import sisc.interpreter.AppContext;
import sisc.interpreter.SchemeException;
import sisc.interpreter.SchemeCaller;

/**
 * Wrapper for SISC.
 * 
 * <p>The various <code>eval</code> procedures below return their
 * values as Java objects, translated from Scheme objects as described below:
 * <dl>
 * <dt>(scheme) boolean</dt>
 * <dd>return a Java {@link Boolean}</dd>
 *
 * <dt>scheme void</dt>
 * <dd>return null</dd>
 *
 * <dt>otherwise</dt>
 * <dd>convert it to a String value in the usual way (for SISC)
 * </dl>
 */
public class SchemeWrapper {

    private static SchemeWrapper instance;

    /**
     * Constructs a new wrapper for SISC.  Private constructor.
     * @throws IOException if there are problems locating the heap
     */
    private SchemeWrapper()
            throws IOException {
        // All we need do here is set the default application context
        // to one which has the non-default parameters we want.  If we
        // didn't (or don't, in the future) need this, then we don't
        // need anything at all in this method, as the
        // Context.execute() method will use the SISC-default
        // application context, which automatically loads the default
        // heap.
        //
        // The maxStackTraceDepth parameter requires SISC 1.15.1 at least.

        java.util.Properties props = new java.util.Properties();
        // Set the maximum stack-trace depth to 16.  This causes a
        // performance hit which is reportedly about 30%: see
        // <http://sourceforge.net/mailarchive/forum.php?thread_id=21786623&forum_id=7422>
        // In fact, setting this to be non-zero seems to produce longer,
        // but less-intelligible stack traces, so I might be better off with it (default) zero.
        props.setProperty("sisc.maxStackTraceDepth", "16");

        // The following should be the defaults            
        //props.setProperty("sisc.emitAnnotations", "true");
        //props.setProperty("sisc.emitDebuggingSymbols", "true");
        //props.setProperty("sisc.stackTraceOnError", "true");
        
        for (Object keyo : System.getProperties().keySet()) {
            String key = (String)keyo;
            if (key.startsWith("sisc.")) {
                props.setProperty(key, System.getProperty(key));
                System.err.println("Found property " + key + "=" + System.getProperty(key));
            }
        }

        // The following hopes to pick up additional properties, but I can't get it to work.
        java.io.InputStream swprops = ClassLoader.getSystemResourceAsStream("SchemeWrapper.properties");
        if (swprops != null) {
            System.err.println("Found resource SchemeWrapper.properties");
            props.load(swprops);
        }

        AppContext ctx = new AppContext(props);
        ctx.addDefaultHeap();
        Context.setDefaultAppContext(ctx);
    }

    /**
     * Returns the single instance of the SchemeWrapper.
     */
    public static SchemeWrapper getInstance()
            throws IOException {
        if (instance == null) {
            instance = new SchemeWrapper();
            try {
                InputStream is = SchemeWrapper.class
                        .getResourceAsStream("scheme-wrapper-support.scm");
                if (is == null)
                    throw new IOException("Failed to find scheme-wrapper-support.scm");
                instance.evalInputStream(is);

                // having loaded that, we now have to load the newly-defined module
                // (we could do this in each file which uses these routines,
                // but that's easy to forget, and in any case should really be transparent)
                instance.eval("(import quaestor-support)");
            } catch (SchemeException e) {
                throw new IOException("Error loading scheme wrapper support:" + e);
            }   
        }
        return instance;
    }

    /**
     * Tests whether a given symbol is defined.
     * @param name the symbol name to test
     * @return true if the symbol is defined
     */
    public boolean symbolIsDefined(String name) {
        return Symbol.get(name) != null;
    }

    /**
     * Evaluates the string, returning the value as a String.  This is
     * not (currently) wrapped in the error-handling code of
     * {@link #eval(Procedure,Object[])}, because it's only used within this
     * class, which we can hope would get things right (hmm).
     *
     * @param expr a scheme expression, which should not be null
     * @return a Java Object (String, Boolean or null) representing the 
     *   value of the expression; if <code>expr</code> is null, then return
     * <code>Boolean.FALSE</code>
     * @throws IOException if there was a problem parsing the expression
     * @throws SchemeException if one there was a problem executing
     *   the expression
     */
    private Object eval(final String expr)
            throws IOException, SchemeException {
        if (expr == null)
            return Boolean.FALSE;
        
        Object o = Context.execute
            (new SchemeCaller() {
                 public Object execute(Interpreter i)
                         throws SchemeException {
                     try {
                         return i.eval(expr);
                     } catch (IOException e) {
                         return e; // return exception as value
                     }
                 }
             });
        if (o instanceof Exception) {
            assert o instanceof IOException;
            throw (IOException)o;
        }
        assert o instanceof Value;
        return schemeToJava((Value)o);
    }

    /**
     * Evaluates a procedure, with arguments.  This is
     * equivalent to the expr <code>proc args...</code>, but wrapped
     * in an error-handler (procedure <code>APPLY-WITH-TOP-FC</code>)
     * which translates errors and exceptions to a suitable string,
     * which it returns.  This procedure is defined in
     * <code>scheme-wrapper-support.scm</code>,
     * which is found and evaluated in {@link #getInstance} above.
     *
     * @param proc a Scheme {@link Procedure} defined in the top level
     * @param args an array of Java objects
     * @return a Java Object (String, Boolean or null) representing the
     *   value of the expression, translated as described above
     * @throws IOException if there was a problem parsing the expression
     * @throws SchemeException if there was a problem executing
     *   the expression
     * @throws ServletException if the SISC procedure returns one
     */
    public Object eval(final Procedure proc, final Object[] args)
            throws SchemeException {
        Object o = Context.execute(new SchemeCaller() {
                public Object execute(Interpreter i)
                        throws SchemeException {
                    // The failure-continuation within procedure
                    // APPLY-WITH-TOP-FC returns a ServletException
                    Procedure apply = (Procedure)i.eval(Symbol.get("apply-with-top-fc"));
                    Value[] v = i.createValues(args.length+1);
                    v[0] = proc;
                    for (int n=0; n<args.length; n++)
                        v[n+1] = new sisc.modules.s2j.JavaObject(args[n]);
                    return i.eval(apply, v);
                }
            });
//         if (o instanceof Exception) {
//             assert o instanceof ServletException;
//             throw (ServletException)o;
//         }
        assert o instanceof Value;
        return schemeToJava((Value)o);
    }

    /**
     * Evaluates a procedure, with arguments.
     * Equivalent to {@link #eval(Procedure,Object[])}, but with a
     * string naming the procedure.
     *
     * @param proc the name of a Scheme procedure defined in the top level
     * @param args an array of Java objects
     * @return a Java Object (String, Boolean or null) representing the
     *   value of the expression, translated as described above
     * @throws IOException if there was a problem parsing the expression
     * @throws SchemeException if there was a problem executing
     *   the expression
     * @throws ServletException if the SISC procedure returns one
     */
    public Object eval(final String proc, final Object[] args)
            throws SchemeException {
        Procedure p = (Procedure)Context.execute(new SchemeCaller() {
                public Object execute(Interpreter i)
                        throws SchemeException {
                    return i.eval(Symbol.get(proc));
                }
            });
        return eval(p, args);
    }

    /**
     * Evaluate an input stream.  This is a package-only method.
     * Unlike the public methods of this class, the return value is
     * not converted to a convenient Java value.
     *
     * <p>Unlike
     * {@link #evalInputStream(java.io.InputStream,java.io.OutputStream)},
     * this is not expected to be used outside of this class.
     *
     * @param in an input stream from which s-expressions can be read
     * @returns the value of the last expression evaluated, as a SISC Value
     */
    sisc.data.Value evalInputStream(final java.io.InputStream in)
            throws IOException, SchemeException {
        Object o = Context.execute(new SchemeCaller() {
                public Object execute(Interpreter i)
                        throws SchemeException {
                    try {
                        return i.evalInput(new java.io.PushbackReader(new java.io.InputStreamReader(in)));
                    } catch (IOException e) {
                        // Smuggle it out by returning it.
                        return e;
                    }
                }
            });
        if (o instanceof Exception) {
            assert o instanceof IOException;
            throw((IOException)o);
        }
        assert o instanceof sisc.data.Value;
        return (sisc.data.Value)o;
    }
                
    /**
     * Evals the given input stream, wrapped in basic
     * error-handling code.  The method acts as a basic REPL, reading
     * expressions from the input stream, displaying any output on the
     * output stream, and returning the value of the last expression
     * evaluated.  If there is an error, it appears as a SchemeException.
     *
     * @param in a stream from which expressions are read
     * @param out a stream to which any output is written
     * @return a Java Object (String, Boolean or null) representing the
     *   value of the expression, translated as described above
     * @throws IOException if the input stream cannot be parsed
     * @throws SchemeException if there's an error evaluating the Scheme input
     */
    public Object evalInputStream(final java.io.InputStream in,
                                  final java.io.OutputStream out)
            throws IOException, SchemeException {
        DynamicEnvironment de = new DynamicEnvironment(Context.getDefaultAppContext(), in, out);
        Object o = Context.execute(de, new SchemeCaller() {
                public Object execute(Interpreter i)
                         throws SchemeException {
                     try {
                         // The following is a basic REPL, with a
                         // failure continuation which throws an improved
                         // error message as a SchemeException
                         String evalCurrentInput = "(with/fc (lambda (m e) (define (show-err r) (let ((parent (error-parent-error r))) (format #f \"Error~a: ~a~a\" (if (error-location r) (format #f \" at ~a\" (error-location r)) \"\") (error-message r) (if parent (string-append \" :-- \" (show-err parent)) \"\")))) (error (show-err m))) (lambda () (let loop ((e (read)) (last #f)) (if (eof-object? e) last (loop (read) (eval e))))))";

                         return i.eval(evalCurrentInput);
                     } catch (IOException e) {
                         // I'm not sure under what circumstances this can happen
                         return e; // return exception as value
                     }
                 }
             });
        if (o instanceof Exception) {
            assert(o instanceof IOException);
            throw (IOException)o;
        }
        assert o instanceof Value;
        return schemeToJava((Value)o);
    }
    
    /**
     * Convert a SISC Value to a Java Object.  If the value is:
     * <dl>
     * <dt>(scheme) boolean</dt>
     * <dd>return a Java {@link Boolean}</dd>
     *
     * <dt>{@link SchemeString}</dt>
     * <dd>return a Java String using the {@link SchemeString#asString}
     * method, which does suitable conversions of newlines</dd>
     *
     * <dt>scheme void</dt>
     * <dd>return null</dd>
     *
     * <dt>a Java Exception, which will be wrapped in a
     * {@sisc.modules.s2j.JavaObject}</dt>
     * <dd>return the Exception</dd>
     *
     * <dt>otherwise</dt>
     * <dd>convert it to a String value in the usual way,
     * with {@link java.lang.Object#toString}.</dd>
     * </dl>
     *
     * @param v a SISC Value, which may or may not be a SchemeString
     * @return a Java Object, which will be null, Boolean or String
     * as appropriate.
     */
    private Object schemeToJava(Value v) {
        Object ret;

        if (v instanceof SchemeString) {
            ret = ((SchemeString)v).asString();
        } else if (v instanceof SchemeBoolean) {
            SchemeBoolean sb = (SchemeBoolean)v;
            ret = Boolean.valueOf(sb.equals(sisc.data.SchemeBoolean.TRUE));
        } else if (v instanceof SchemeVoid) {
            ret = null;
//         } else if (v instanceof sisc.modules.s2j.JavaObject) {
//             ret = ((sisc.modules.s2j.JavaObject)v).get();
//             if (ret != null)
//                 ret = ret.toString();
        } else {
            // I don't think this should happen, but there's not a lot we
            // can usefully do about it here and now without creating brittleness.
            ret = v.toString();
        }
        assert ret == null || ret instanceof String || ret instanceof Boolean;
        return ret;
    }
}

--Apple-Mail-13--533248074
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
--Apple-Mail-13--533248074
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

_______________________________________________
Sisc-users mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/sisc-users

--Apple-Mail-13--533248074--