[英]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)上正常运行的方法(请参见问题末尾的评论):
What I didn't want to happen is either: 我不希望发生的事情可以是:
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.