jsr223 + написание интерпретатора сценариев - PullRequest
5 голосов
/ 07 апреля 2011

OK.ScriptEngine.eval(String string) оценивает строку полностью, а ScriptEngine.eval(Reader reader) оценивает ввод от Reader полностью.

Так что, если у меня есть файлЯ могу открыть FileInputStream, обернуть вокруг него Reader и вызвать scriptEngine.eval(reader).

Если у меня есть полный оператор в виде строки, я могу позвонить scriptEngine.eval(string).

Что мне делать, если мне нужно реализовать интерактивный переводчик?У меня есть пользователь, который в интерактивном режиме набирает многострочный оператор, например,

 function f() {
     return 3;
 }

Если я читаю входные данные построчно и использую строковую форму eval(), я получу в итоге его неполноеоператоры, например function f() { и получающие ошибку.

Если я передам Reader, ScriptEngine будет ждать бесконечно, пока ввод не будет завершен, и он не будет интерактивным.

Помощь!


Просто чтобы уточнить: проблема здесь в том, что я могу только пропустить ScriptEngine.eval() полных операторов, и, как клиент ScriptEngine, я не знаю, когда строка ввода завершится без какой-либо помощи со сторонысам ScriptEngine.


Интерактивная оболочка Rhino использует Rhino Context.stringIsCompilableUnit() (см. LXR для использования использования и ).

Ответы [ 2 ]

4 голосов
/ 08 апреля 2011

Я реализовал что-то, что хорошо работает с Java SE 6 Rhino (Javascript) и Jython 1.5.2 (Python), используя довольно простой подход, похожий на интерактивную оболочку Rhino (см. Мое замечание в конце вопроса):

  • Сохранить ожидающий список строк ввода, еще не оцененных.
  • Попробуйте скомпилировать (но не оценивать) ожидающие строки ввода.
    • Если с компиляцией все в порядке, мы можем иметь возможность выполнять ожидающие строки ввода.
    • Если компиляция выдает исключение, и есть указание на позицию (номер строки + номер столбца) ошибки, и это соответствует концу ожидающего ввода, то это подсказка, что мы ожидаем больше ввода, поэтому проглотите исключение и дождитесь следующей строки.
    • В противном случае мы либо не знаем, где находится ошибка, либо она произошла до конца ожидающего ввода, поэтому сбросьте исключение.
  • Если мы не ожидаем больше строк ввода, и у нас есть только одна строка ожидающих ввода, оцените ее и перезапустите.
  • Если мы не ожидаем больше входных строк, а последняя пустая (на каждый ответ @ karakuricoder) и у нас более одной строки ожидающих ввода, оцените ее и перезапустите. Интерактивная оболочка Python, кажется, делает это.
  • В противном случае продолжайте читать строки ввода.

То, чего я не хотел бы получить:

  • пользователей раздражает необходимость вводить дополнительные пустые строки после однострочного ввода
  • пользователи вводят длинную многострочную инструкцию и узнают только после того, как во 2-й строке произошла синтаксическая ошибка.

Вот вспомогательный класс, который я написал, который реализует мой подход:

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 голосов
/ 07 апреля 2011

Создайте метод, который читает с клавиатуры (класс Scanner) и создает полную строку из нескольких строк ввода. Ввод в пустую строку сигнализирует об окончании пользовательского ввода. Передайте строку в метод eval.

...