简体   繁体   English

jsr223 +编写脚本解释器

[英]jsr223 + writing a script interpreter

OK. 好。 ScriptEngine.eval(String string) evaluates a string in its entirety, and ScriptEngine.eval(Reader reader) evaluates the input from a Reader in its entirety. ScriptEngine.eval(String string)整体评估一个字符串,而ScriptEngine.eval(Reader reader)评估一个Reader的输入。

So if I have a file, I can open a FileInputStream, wrap a Reader around it, and call scriptEngine.eval(reader) . 因此,如果我有一个文件,则可以打开FileInputStream,将Reader包裹起来,然后调用scriptEngine.eval(reader)

If I have a complete statement as a string, I can call scriptEngine.eval(string) . 如果我有一个完整的语句作为字符串,则可以调用scriptEngine.eval(string)

What do I do if I need to implement an interactive interpreter? 如果需要实现交互式解释器,该怎么办? I have a user who is interactively typing in a multiline statement, eg 我有一个正在交互式输入多行语句的用户,例如

 function f() {
     return 3;
 }

If I read the input line by line, and use the String form of eval() , I'll end up passing it incomplete statements, eg function f() { and get an error. 如果我逐行阅读输入,并使用eval()的String形式,则最终将不完整的语句(例如function f() {传递给它,并得到一个错误。

If I pass in a Reader, the ScriptEngine will wait forever until the input is complete, and it's not interactive. 如果我传递阅读器,则ScriptEngine将永远等待直到输入完成,并且它不是交互式的。

Help! 救命!


Just to clarify: the problem here is that I can only pass ScriptEngine.eval() complete statements, and as the customer of ScriptEngine, I don't know when an input line is complete without some help from the ScriptEngine itself. 需要澄清的是:这里的问题是我只能传递ScriptEngine.eval()完整语句,作为ScriptEngine的客户,如果没有ScriptEngine本身的帮助,我不知道输入行何时完成。


Rhino's interactive shell uses Rhino's Context.stringIsCompilableUnit() (see LXR for usage and implementation ). Rhino的交互式外壳使用Rhino的Context.stringIsCompilableUnit() (有关用法实现,请参见LXR)。

I implemented something that works OK with Java SE 6 Rhino (Javascript) and Jython 1.5.2 (Python), using a fairly simple approach similar to Rhino's interactive shell (see my remark at the end of the question): 我使用类似于Rhino交互式shell的相当简单的方法,实现了可以在Java SE 6 Rhino(Javascript)和Jython 1.5.2(Python)上正常运行的方法(请参见问题末尾的评论):

  • Keep a pending list of input lines not yet evaluated. 保留尚未评估的输入行的待处理列表。
  • Try compiling (but not evaluating) the pending input lines. 尝试编译(但不评估)挂起的输入行。
    • If the compilation is OK, we may be able to execute pending input lines. 如果编译正常,我们也许可以执行挂起的输入行。
    • If the compilation throws an exception, and there is an indication of the position (line + column number) of the error, and this matches the end of the pending input, then that's a clue that we're expecting more input, so swallow the exception and wait for the next line. 如果编译引发异常,并且指示错误的位置(行+列号),并且与挂起的输入的末尾相匹配,那么这就是我们期望有更多输入的线索,因此请吞下异常并等待下一行。
    • Otherwise, we either don't know where the error is, or it happened prior to the end of the pending input, so rethrow the exception. 否则,我们要么不知道错误在哪里,要么发生在挂起的输入结束之前,因此请重新引发异常。
  • If we are not expecting any more input lines, and we only have one line of pending input, then evaluate it and restart. 如果我们不希望输入更多的行,而只有一行待处理的输入,请对其进行评估并重新启动。
  • If we are not expecting any more input lines, and the last one is a blank one (per @karakuricoder's answer) and we have more than one line of pending input, then evaluate it and restart. 如果我们不希望再有任何输入行,而最后一行是空白行(根据@karakuricoder的回答),而我们有多于一行待处理的输入,则对其进行评估并重新启动。 Python's interactive shell seems to do this. Python的交互式外壳似乎可以做到这一点。
  • Otherwise, keep reading input lines. 否则,请继续阅读输入线。

What I didn't want to happen is either: 希望发生的事情可以是:

  • users get annoyed having to enter extra blank lines after single-line inputs 用户在单行输入后不得不输入多余的空白行而感到烦恼
  • users enter a long multi-line statement and only find out after the fact that there was a syntax error in the 2nd line. 用户输入一个长的多行语句,并且仅在第二行出现语法错误这一事实之后才能发现。

Here's a helper class I wrote that implements my approach: 这是我编写的实现我的方法的帮助程序类:

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;
    }

}

Create a method that reads from the keyboard (Scanner class) and creates a complete string from multiple lines of input. 创建一个从键盘读取的方法(Scanner类),并从多行输入中创建一个完整的字符串。 Enter on a blank line signals the end of user input. 空行中的Enter表示用户输入结束。 Pass the string into the eval method. 将字符串传递给eval方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM