2011-04-07 83 views
5

好的。 ScriptEngine.eval(String string)评估字符串的完整性,并且ScriptEngine.eval(Reader reader)完整地评估来自Reader的输入。jsr223 +写脚本解释器

所以如果我有一个文件,我可以打开一个FileInputStream,在它周围包装一个阅读器,并调用scriptEngine.eval(reader)

如果我有一个完整的语句作为字符串,我可以拨打scriptEngine.eval(string)

如果我需要实现交互式解释器,该怎么办?我有一个交互式输入多行语句的用户,例如

function f() { 
    return 3; 
} 

如果我逐行读取输入线,使用的eval()字符串形式,我将最终传递给它不完整的语句,例如function f() {,并得到一个错误。

如果我通过阅读器,ScriptEngine将永远等待,直到输入完成,并且它不是交互式的。

帮助!


只是为了澄清:这里的问题是,我只能通过ScriptEngine.eval()完整的语句,并作为ScriptEngine的客户,我不知道什么时候输入线是完全没有从自身ScriptEngine的一些帮助。


Rhino的交互shell使用Rhino的Context.stringIsCompilableUnit()(见LXR为usageimplementation)。

回答

4

我使用类似于Rhino交互式shell的相当简单的方法(请参阅我在问题结尾处的注释)实现了可与Java SE 6 Rhino(Javascript)和Jython 1.5.2(Python)一起工作的一些东西:

  • 保留尚未评估的待处理输入行列表。
  • 尝试编译(但不评估)悬而未决的输入行。
    • 如果编译正常,我们可能能够执行悬而未决的输入行。
    • 如果编译引发异常,并且有错误位置(行+列号)的指示,并且这与未决输入的结尾相匹配,那么这就是我们期待更多输入的线索,所以吞下异常并等待下一行。
    • 否则,我们要么不知道错误在哪里,要么发生在悬而未决的输入结束之前,因此重新抛出异常。
  • 如果我们不期待更多的输入行,并且我们只有一行待定输入,然后评估它并重新启动。
  • 如果我们不期待更多的输入行,并且最后一行是空白行(per @ karakuricoder的答案),并且我们有多行未决输入,则评估它并重新启动。 Python的交互式shell似乎这样做。
  • 否则,请继续阅读输入行。

没有要发生什么,或者是:

  • 用户生气不必输入单行输入
  • 用户输入一个长的多行语句之后,多余的空行只有在第二行出现语法错误后才能查明。

这里是一个辅助类,我写的是我的执行办法:

import java.lang.reflect.Method; 
import javax.script.Bindings; 
import javax.script.Compilable; 
import javax.script.CompiledScript; 
import javax.script.ScriptEngine; 
import javax.script.ScriptException; 

public class ScriptEngineInterpreter 
{ 
    private static final boolean DEBUG = false; 
    final private ScriptEngine engine; 
    final private Bindings bindings; 
    final private StringBuilder sb; 
    private int lineNumber; 
    private int pendingLineCount; 
    private boolean expectingMoreInput; 

    /** 
    * @param engine ScriptingEngine to use in this interpreter 
    * @param bindings Bindings to use in this interpreter 
    */ 
    public ScriptEngineInterpreter(ScriptEngine engine, Bindings bindings) 
    { 
     this.engine = engine; 
     this.bindings = bindings; 
     this.sb = new StringBuilder(); 
     this.lineNumber = 0; 
     reset(); 
    }  
    /** @return ScriptEngine used by this interpreter */ 
    public ScriptEngine getEngine() { return this.engine; } 
    protected void reset() { 
     this.sb.setLength(0); 
     this.pendingLineCount = 0; 
     setExpectingMoreInput(false); 
    } 
    /** @return whether the interpreter is ready for a brand new statement. */ 
    public boolean isReady() { return this.sb.length() == 0; } 
    /** 
    * @return whether the interpreter expects more input 
    * 
    * A true value means there is definitely more input needed. 
    * A false value means no more input is needed, but it may not yet 
    * be appropriate to evaluate all the pending lines. 
    * (there's some ambiguity depending on the language) 
    */ 
    public boolean isExpectingMoreInput() { return this.expectingMoreInput; } 
    protected void setExpectingMoreInput(boolean b) { this.expectingMoreInput = b; } 
    /** 
    * @return number of lines pending execution 
    */ 
    protected int getPendingLineCount() { return this.pendingLineCount; } 
    /** 
    * @param lineIsEmpty whether the last line is empty 
    * @return whether we should evaluate the pending input 
    * The default behavior is to evaluate if we only have one line of input, 
    * or if the user enters a blank line. 
    * This behavior should be overridden where appropriate. 
    */ 
    protected boolean shouldEvaluatePendingInput(boolean lineIsEmpty) 
    { 
     if (isExpectingMoreInput()) 
      return false; 
     else 
      return (getPendingLineCount() == 1) || lineIsEmpty; 
    } 
    /** 
    * @param line line to interpret 
    * @return value of the line (or null if there is still pending input) 
    * @throws ScriptException in case of an exception 
    */ 
    public Object interpret(String line) throws ScriptException 
    { 
     ++this.lineNumber; 
     if (line.isEmpty()) 
     { 
      if (!shouldEvaluatePendingInput(true)) 
       return null; 
     } 

     ++this.pendingLineCount;   
     this.sb.append(line); 
     this.sb.append("\n"); 
     CompiledScript cs = tryCompiling(this.sb.toString(), getPendingLineCount(), line.length()); 

     if (cs == null) 
     { 
      return null; 
     } 
     else if (shouldEvaluatePendingInput(line.isEmpty())) 
     { 
      try 
      { 
       Object result = cs.eval(this.bindings); 
       return result; 
      } 
      finally 
      { 
       reset(); 
      } 
     } 
     else 
     { 
      return null; 
     } 
    } 
    private CompiledScript tryCompiling(String string, int lineCount, int lastLineLength) 
     throws ScriptException 
    { 
     CompiledScript result = null; 
     try 
     { 
      Compilable c = (Compilable)this.engine; 
      result = c.compile(string); 
     } 
     catch (ScriptException se) { 
      boolean rethrow = true; 
      if (se.getCause() != null) 
      { 
       Integer col = columnNumber(se); 
       Integer line = lineNumber(se); 
       /* swallow the exception if it occurs at the last character 
       * of the input (we may need to wait for more lines) 
       */ 
       if (col != null 
       && line != null 
       && line.intValue() == lineCount 
       && col.intValue() == lastLineLength) 
       { 
        rethrow = false; 
       } 
       else if (DEBUG) 
       { 
        String msg = se.getCause().getMessage(); 
        System.err.println("L"+line+" C"+col+"("+lineCount+","+lastLineLength+"): "+msg); 
        System.err.println("in '"+string+"'"); 
       } 
      } 

      if (rethrow) 
      { 
       reset(); 
       throw se; 
      } 
     } 

     setExpectingMoreInput(result == null); 
     return result; 
    } 
    private Integer columnNumber(ScriptException se) 
    {  
     if (se.getColumnNumber() >= 0) 
      return se.getColumnNumber(); 
     return callMethod(se.getCause(), "columnNumber", Integer.class); 
    } 
    private Integer lineNumber(ScriptException se) 
    {  
     if (se.getLineNumber() >= 0) 
      return se.getLineNumber(); 
     return callMethod(se.getCause(), "lineNumber", Integer.class); 
    } 
    static private Method getMethod(Object object, String methodName) 
    { 
     try { 
      return object.getClass().getMethod(methodName); 
     } 
     catch (NoSuchMethodException e) { 
      return null; 
      /* gulp */ 
     } 
    } 
    static private <T> T callMethod(Object object, String methodName, Class<T> cl) { 
     try { 
      Method m = getMethod(object, methodName); 
      if (m != null) 
      { 
       Object result = m.invoke(object); 
       return cl.cast(result); 
      } 
     } 
     catch (Exception e) { 
      e.printStackTrace(); 
     } 
     return null; 
    } 

} 
2

创建一个从键盘读取(Scanner类)并从多行输入创建完整字符串的方法。在空行输入信号表示用户输入的结束。将字符串传递给eval方法。

+0

......以及如何帮助?如果没有ScriptingEngine本身的帮助,我该如何区分不完整的陈述和完整的陈述? – 2011-04-07 17:08:20

+0

“在空行输入信号表示用户输入的结束” - ICK - 可以工作,但是很烦人。 – 2011-04-07 17:14:57

+0

没有什么可以阻止来自任何流的不完整语句,无论是文件,网络连接还是键盘。您必须进行一些预先验证,否则eval impl将不得不采取必要的措施来处理/验证陈述未完成。其次,每个流都必须有一个结束标记。从非键盘流中读取返回一些标志值,例如EOF,-1,null等。您可以选择任何您希望用于输入结尾的方式,但必须通过编程方式来确定用户已完成。 – karakuricoder 2011-04-08 02:41:40