source: branches/0.15.x/abcl/src/org/armedbear/lisp/scripting/AbclScriptEngine.java

Last change on this file was 11894, checked in by astalla, 16 years ago

Fixed function evaluation using invokeFunction. It was broken since last
commit on JSR-223. Now invokeFunction uses the same "eval-in-script-context"
macro that is used to evaluate interpreted and compiled code in the right
environment, including special variables from the ScriptContext?.
In passing, the invokeFunction() method has also been fixed so that
javaInstance() is called on its return value, like it happens in all other
kinds of Lisp calls from Java.

  • Property svn:eol-style set to LF
File size: 13.4 KB
Line 
1/*
2 * AbclScriptEngine.java
3 *
4 * Copyright (C) 2008 Alessio Stalla
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19 */
20
21package org.armedbear.lisp.scripting;
22
23import java.io.File;
24import java.io.IOException;
25import java.io.InputStream;
26import java.io.Reader;
27import java.io.StringWriter;
28import java.math.BigInteger;
29import java.util.Map;
30import java.util.Properties;
31
32import javax.script.*;
33
34import org.armedbear.lisp.*;
35import org.armedbear.lisp.scripting.util.ReaderInputStream;
36import org.armedbear.lisp.scripting.util.WriterOutputStream;
37
38
39public class AbclScriptEngine extends AbstractScriptEngine implements Invocable, Compilable {
40
41    private Interpreter interpreter;
42    /**
43     * The function used to evaluate a string of code.
44     */
45    private Function evalScript;
46    /**
47     * The function used to evaluate a Lisp function.
48     */
49    private Function evalFunction;
50    /**
51     * The function used to compile Lisp code.
52     */
53    private Function compileScript;
54    /**
55     * The function used to evaluate a compiled script.
56     */
57    private Function evalCompiledScript;
58
59    protected AbclScriptEngine() {
60  interpreter = Interpreter.getInstance();
61  if(interpreter == null) {
62      interpreter = Interpreter.createInstance();
63  }
64  try {
65      loadFromClasspath("/org/armedbear/lisp/scripting/lisp/packages.lisp");
66      loadFromClasspath("/org/armedbear/lisp/scripting/lisp/abcl-script.lisp");
67      loadFromClasspath("/org/armedbear/lisp/scripting/lisp/config.lisp");
68      if(getClass().getResource("/abcl-script-config.lisp") != null) {
69    System.out.println("ABCL: loading configuration from " + getClass().getResource("/abcl-script-config.lisp"));
70    loadFromClasspath("/abcl-script-config.lisp");
71      }
72      ((Function) interpreter.eval("#'abcl-script:configure-abcl")).execute(new JavaObject(this));
73      System.out.println("ABCL: configured");
74      evalScript = (Function) this.findSymbol("EVAL-SCRIPT", "ABCL-SCRIPT").getSymbolFunction();
75      compileScript = (Function) this.findSymbol("COMPILE-SCRIPT", "ABCL-SCRIPT").getSymbolFunction();
76      evalCompiledScript = (Function) this.findSymbol("EVAL-COMPILED-SCRIPT", "ABCL-SCRIPT").getSymbolFunction();
77      evalFunction = (Function) this.findSymbol("EVAL-FUNCTION", "ABCL-SCRIPT").getSymbolFunction();
78  } catch (ConditionThrowable e) {
79      throw new RuntimeException(e);
80  }
81    }
82   
83    public Interpreter getInterpreter() {
84  return interpreter;
85    }
86
87    public void setStandardInput(InputStream stream, LispThread thread) {
88  thread.setSpecialVariable(Symbol.STANDARD_INPUT, new Stream(stream, Symbol.CHARACTER, true));
89    }
90   
91    public void setStandardInput(InputStream stream) {
92  setStandardInput(stream, LispThread.currentThread());
93    }
94   
95    public void setInterpreter(Interpreter interpreter) {
96  this.interpreter = interpreter;
97    }
98
99    public static String escape(String s) {
100  StringBuffer b = new StringBuffer();
101  int len = s.length();
102  char c;
103  for (int i = 0; i < len; ++i) {
104      c = s.charAt(i);
105      if (c == '\\' || c == '"') {
106    b.append('\\');
107      }
108      b.append(c);
109  }
110  return b.toString();
111    }
112
113  public LispObject loadFromClasspath(String classpathResource) throws ConditionThrowable {
114    InputStream istream = getClass().getResourceAsStream(classpathResource);
115    Stream stream = new Stream(istream, Symbol.CHARACTER);
116    return load(stream);
117  }
118
119  public LispObject load(Stream stream) throws ConditionThrowable {
120    Symbol keyword_verbose = Lisp.internKeyword("VERBOSE");
121    Symbol keyword_print = Lisp.internKeyword("PRINT");
122    /*
123     * load (filespec &key (verbose *load-verbose*) (print *load-print*)
124     * (if-does-not-exist t) (external-format :default)
125     */
126    return Symbol.LOAD.getSymbolFunction().execute(
127        new LispObject[] { stream, keyword_verbose, Lisp.NIL,
128            keyword_print, Lisp.T, Keyword.IF_DOES_NOT_EXIST,
129            Lisp.T, Keyword.EXTERNAL_FORMAT, Keyword.DEFAULT });
130  }
131
132  public LispObject load(String filespec) throws ConditionThrowable {
133    return load(filespec, true);
134  }
135
136  public LispObject load(String filespec, boolean compileIfNecessary) throws ConditionThrowable {
137    if (isCompiled(filespec) || !compileIfNecessary) {
138      return interpreter.eval("(load \"" + escape(filespec) + "\")");
139    } else {
140      return compileAndLoad(filespec);
141    }
142  }
143
144  public static boolean isCompiled(String filespec) {
145    if (filespec.endsWith(".abcl")) {
146      return true;
147    }
148    File source;
149    File compiled;
150    if (filespec.endsWith(".lisp")) {
151      source = new File(filespec);
152      compiled = new File(filespec.substring(0, filespec.length() - 5)
153          + ".abcl");
154    } else {
155      source = new File(filespec + ".lisp");
156      compiled = new File(filespec + ".abcl");
157    }
158    if (!source.exists()) {
159      throw new IllegalArgumentException("The source file " + filespec + " cannot be found");
160    }
161    return compiled.exists()
162        && compiled.lastModified() >= source.lastModified();
163  }
164
165  public LispObject compileFile(String filespec) throws ConditionThrowable {
166    return interpreter.eval("(compile-file \"" + escape(filespec) + "\")");
167  }
168
169  public LispObject compileAndLoad(String filespec) throws ConditionThrowable {
170    return interpreter.eval("(load (compile-file \"" + escape(filespec) + "\"))");
171  }
172
173  public static boolean functionp(LispObject obj) {
174    return obj instanceof Function;
175  }
176
177  public JavaObject jsetq(String symbol, Object value) throws ConditionThrowable {
178    Symbol s = findSymbol(symbol);
179    JavaObject jo;
180    if (value instanceof JavaObject) {
181      jo = (JavaObject) value;
182    } else {
183      jo = new JavaObject(value);
184    }
185    s.setSymbolValue(jo);
186    return jo;
187  }
188
189  public Symbol findSymbol(String name, String pkg) throws ConditionThrowable {
190    Cons values = (Cons) (interpreter.eval("(cl:multiple-value-list (find-symbol (symbol-name '#:"
191                         + escape(name) + ")" + (pkg == null ? "" : " :" + escape(pkg))
192                         + "))"));
193    if(values.cadr() == Lisp.NIL) {
194      return null;
195    } else {
196      return (Symbol) values.car();
197    }
198  }
199
200  public Symbol findSymbol(String name) throws ConditionThrowable {
201    //Known bug: doesn't handle escaped ':' e.g. |a:b|
202    int i = name.indexOf(':');
203    if(i < 0) { 
204      return findSymbol(name, null);
205    } else {
206        if((i < name.length() - 1) && (name.charAt(i + 1) == ':')) {
207      return findSymbol(name.substring(i + 2), name.substring(0, i));
208        } else {
209      return findSymbol(name.substring(i + 1), name.substring(0, i));
210        }
211    }
212  }
213 
214  public Function findFunction(String name) throws ConditionThrowable {
215    return (Function) interpreter.eval("#'" + name);
216  }
217
218  @Override
219  public Bindings createBindings() {
220    return new SimpleBindings();
221  }
222
223  private static LispObject makeBindings(Bindings bindings) throws ConditionThrowable {
224    if (bindings == null || bindings.size() == 0) {
225      return Lisp.NIL;
226    }
227    LispObject[] argList = new LispObject[bindings.size()];
228    int i = 0;
229    for (Map.Entry<String, Object> entry : bindings.entrySet()) {
230      argList[i++] = Symbol.CONS.execute(new SimpleString(entry.getKey()), toLisp(entry.getValue()));
231    }
232    return Symbol.LIST.getSymbolFunction().execute(argList);
233  }
234
235    private Object eval(Function evaluator, LispObject code, ScriptContext ctx) throws ScriptException {
236  ReaderInputStream in = null;
237  WriterOutputStream out = null;
238  LispObject retVal = null;
239  try {
240      in = new ReaderInputStream(ctx.getReader());
241      out = new WriterOutputStream(ctx.getWriter());
242      Stream outStream = new Stream(out, Symbol.CHARACTER);
243      Stream inStream  = new Stream(in,  Symbol.CHARACTER);
244      retVal = evaluator.execute(makeBindings(ctx.getBindings(ScriptContext.GLOBAL_SCOPE)),
245               makeBindings(ctx.getBindings(ScriptContext.ENGINE_SCOPE)),
246               inStream, outStream,
247               code, new JavaObject(ctx));
248      return retVal.javaInstance();
249  } catch (ConditionThrowable e) {
250      throw new ScriptException(new Exception(e));
251  } catch (IOException e) {
252      throw new ScriptException(e);
253  }
254    }
255 
256  @Override
257  public Object eval(String code, ScriptContext ctx) throws ScriptException {
258    return eval(evalScript, new SimpleString(code), ctx);
259  }
260
261  private static String toString(Reader reader) throws IOException {
262    StringWriter w = new StringWriter();
263    int i;
264    i = reader.read();
265    while (i != -1) {
266      w.write(i);
267      i = reader.read();
268    }
269    return w.toString();
270  }
271 
272  @Override
273  public Object eval(Reader code, ScriptContext ctx) throws ScriptException {
274    try {
275      return eval(toString(code), ctx);
276    } catch (IOException e) {
277      return new ScriptException(e);
278    }
279  }
280
281  @Override
282  public ScriptEngineFactory getFactory() {
283    return new AbclScriptEngineFactory();
284  }
285 
286  public static LispObject toLisp(Object javaObject) {
287    if(javaObject == null) {
288            return Lisp.NIL;
289    } else if(javaObject instanceof Boolean) {
290            return ((Boolean)javaObject).booleanValue() ? Lisp.T : Lisp.NIL;
291    } else if(javaObject instanceof Byte) {
292            return Fixnum.getInstance(((Byte)javaObject).intValue());
293    } else if(javaObject instanceof Integer) {
294            return Fixnum.getInstance(((Integer)javaObject).intValue());
295    } else if(javaObject instanceof Short) {
296            return Fixnum.getInstance(((Short)javaObject).shortValue());
297    } else if(javaObject instanceof Long) {
298            return Bignum.getInstance((Long)javaObject);
299    } else if(javaObject instanceof BigInteger) {
300      return Bignum.getInstance((BigInteger) javaObject);
301    } else if(javaObject instanceof Float) {
302            return new SingleFloat(((Float)javaObject).floatValue());
303    } else if(javaObject instanceof Double) {
304            return new DoubleFloat(((Double)javaObject).doubleValue());
305    } else if(javaObject instanceof String) {
306            return new SimpleString((String)javaObject);
307    } else if(javaObject instanceof Character) {
308            return LispCharacter.getInstance((Character)javaObject);
309    } else if(javaObject instanceof Object[]) {
310            Object[] array = (Object[]) javaObject;
311            SimpleVector v = new SimpleVector(array.length);
312            for(int i = array.length; i > 0; --i) {
313              try {
314          v.aset(i, new JavaObject(array[i]));
315        } catch (ConditionThrowable e) {
316          throw new Error("Can't set SimpleVector index " + i, e);
317        }
318            }
319            return v;
320        } else if(javaObject instanceof LispObject) {
321            return (LispObject) javaObject;
322        } else {
323          return new JavaObject(javaObject);
324        }
325  }
326 
327  @Override
328  public <T> T getInterface(Class<T> clasz) {
329    try {
330      return getInterface(eval("(cl:find-package '#:ABCL-SCRIPT-USER)"), clasz);
331    } catch (ScriptException e) {
332      throw new Error(e);
333    }
334  }
335
336  @SuppressWarnings("unchecked")
337  @Override
338  public <T> T getInterface(Object thiz, Class<T> clasz) {
339    try {
340      Symbol s = findSymbol("jmake-proxy", "JAVA");
341      JavaObject iface = new JavaObject(clasz);
342      return (T) ((JavaObject) s.execute(iface, (LispObject) thiz)).javaInstance();
343    } catch (ConditionThrowable e) {
344      throw new Error(e);
345    }
346  } 
347 
348    @Override
349    public Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException {
350  try {
351      Symbol s;
352      if(name.indexOf(':') >= 0) {
353    s = findSymbol(name);
354      } else {
355    s = findSymbol(name, "ABCL-SCRIPT-USER");
356      }
357      if(s != null) {
358    LispObject f = s.getSymbolFunction();
359    if(f != null && f instanceof Function) {
360        LispObject functionAndArgs = Lisp.NIL.push(f);
361        for(int i = 0; i < args.length; ++i) {
362      functionAndArgs = functionAndArgs.push(toLisp(args[i]));
363        }
364        functionAndArgs = functionAndArgs.reverse();
365        return eval(evalFunction, functionAndArgs, getContext());
366    } else {
367        throw new NoSuchMethodException(name);
368    }
369      } else {
370    throw new NoSuchMethodException(name);
371      }
372  } catch (ConditionThrowable e) {
373      throw new ScriptException(new RuntimeException(e));
374  }
375    }
376
377    @Override
378    public Object invokeMethod(Object thiz, String name, Object... args) throws ScriptException, NoSuchMethodException {
379  throw new UnsupportedOperationException("Common Lisp does not have methods in the Java sense.");
380    }
381
382    public class AbclCompiledScript extends CompiledScript {
383
384  private LispObject function;
385 
386  public AbclCompiledScript(LispObject function) {
387      this.function = function;
388  }
389 
390  @Override
391  public Object eval(ScriptContext context) throws ScriptException {
392      return AbclScriptEngine.this.eval(evalCompiledScript, function, context);
393  }
394 
395  @Override
396  public ScriptEngine getEngine() {
397      return AbclScriptEngine.this;
398  }
399 
400    }
401
402 
403  @Override
404  public CompiledScript compile(String script) throws ScriptException {
405    try {
406        Function f = (Function) compileScript.execute(new SimpleString(script));
407        return new AbclCompiledScript(f);
408    } catch (ConditionThrowable e) {
409      throw new ScriptException(new Exception(e));
410    } catch(ClassCastException e) {
411      throw new ScriptException(e);
412    }
413  }
414
415  @Override
416  public CompiledScript compile(Reader script) throws ScriptException {
417    try {
418      return compile(toString(script));
419    } catch (IOException e) {
420      throw new ScriptException(e);
421    }
422  }
423
424}
Note: See TracBrowser for help on using the repository browser.