diff --git a/README.html b/README.html
index 1cba2d7b..7ec0d26d 100644
--- a/README.html
+++ b/README.html
@@ -842,6 +842,7 @@ Files are no longer hosted at LuaForge.
Fix bug 3565008 so that short substrings are backed by short arrays.
Fix bug 3495802 to return correct offset of substrings from string.find().
Add artifacts to Maven central repository.
+
Limit pluggable scripting to use compatible bindings and contexts, implement redirection.
diff --git a/examples/jse/ScriptEngineSample.java b/examples/jse/ScriptEngineSample.java
index 1583f79e..ec9b638b 100644
--- a/examples/jse/ScriptEngineSample.java
+++ b/examples/jse/ScriptEngineSample.java
@@ -1,5 +1,9 @@
+import java.io.CharArrayReader;
+import java.io.CharArrayWriter;
+import java.io.Reader;
+
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
@@ -11,6 +15,8 @@ import javax.script.SimpleBindings;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.OneArgFunction;
+import org.luaj.vm2.script.LuaScriptEngine;
+import org.luaj.vm2.script.LuajBindings;
public class ScriptEngineSample {
@@ -22,6 +28,7 @@ public class ScriptEngineSample {
ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine e = sem.getEngineByExtension(".lua");
+ e = new LuaScriptEngine();
ScriptEngineFactory f = e.getFactory();
// uncomment to enable the lua-to-java bytecode compiler
@@ -66,8 +73,8 @@ public class ScriptEngineSample {
System.out.println( "y="+b1.get("y") );
System.out.println( "y="+b2.get("y") );
- // Bindings that use a client-specified Bindings type.
- Bindings sb = new SimpleBindings();
+ // In Luaj 3.0, client bindings must derive from LuajBindings.
+ Bindings sb = new LuajBindings();
sb.put("x", 2);
System.out.println( "eval: "+cs.eval(sb) );
@@ -81,6 +88,36 @@ public class ScriptEngineSample {
testEngineBindings(e);
testClientBindings(e);
testUserClasses(e);
+
+ // Test redirection of input, output, and standard error.
+ Reader input = new CharArrayReader("abcdefg\nhijk".toCharArray());
+ CharArrayWriter output = new CharArrayWriter();
+ CharArrayWriter errors = new CharArrayWriter();
+ String script =
+ "print(\"string written using 'print'\")\n" +
+ "io.write(\"string written using 'io.write()'\\n\")\n" +
+ "io.stdout:write(\"string written using 'io.stdout:write()'\\n\")\n" +
+ "io.stderr:write(\"string written using 'io.stderr:write()'\\n\")\n" +
+ "io.write([[string read using 'io.stdin:read(\"*l\")':]]..io.stdin:read(\"*l\")..\"\\n\")\n";
+
+ System.out.println("Evaluating script with redirection set.");
+ e.getContext().setReader(input);
+ e.getContext().setWriter(output);
+ e.getContext().setErrorWriter(errors);
+ e.eval(script);
+ System.out.println("output::>"+output+"<::output");
+ System.out.println("errors::>"+errors+"<::errors");
+
+ System.out.println("Evaluating script with redirection reset.");
+ output.reset();
+ errors.reset();
+ e.getContext().setReader(null);
+ e.getContext().setWriter(null);
+ e.getContext().setErrorWriter(null);
+ e.eval(script);
+ System.out.println("output::>"+output+"<::output");
+ System.out.println("errors::>"+errors+"<::errors");
+
} catch (ScriptException ex) {
ex.printStackTrace();
@@ -97,7 +134,7 @@ public class ScriptEngineSample {
testBindings(e, e.createBindings());
}
public static void testClientBindings(ScriptEngine e) throws ScriptException {
- testBindings(e, new SimpleBindings());
+ testBindings(e, new LuajBindings());
}
public static void testBindings(ScriptEngine e, Bindings b) throws ScriptException {
CompiledScript cs = ((Compilable)e).compile(
diff --git a/src/jse/org/luaj/vm2/script/LuaScriptEngine.java b/src/jse/org/luaj/vm2/script/LuaScriptEngine.java
index d48d360f..400d570d 100644
--- a/src/jse/org/luaj/vm2/script/LuaScriptEngine.java
+++ b/src/jse/org/luaj/vm2/script/LuaScriptEngine.java
@@ -1,5 +1,5 @@
/*******************************************************************************
-* Copyright (c) 2008 LuaJ. All rights reserved.
+* Copyright (c) 2008-2013 LuaJ. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -21,38 +21,21 @@
******************************************************************************/
package org.luaj.vm2.script;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.io.StringReader;
-import java.util.Map.Entry;
+import java.io.*;
+import javax.script.*;
-import javax.script.Bindings;
-import javax.script.Compilable;
-import javax.script.CompiledScript;
-import javax.script.ScriptContext;
-import javax.script.ScriptEngine;
-import javax.script.ScriptEngineFactory;
-import javax.script.ScriptException;
-import javax.script.SimpleBindings;
-import javax.script.SimpleScriptContext;
-
-import org.luaj.vm2.Globals;
-import org.luaj.vm2.LoadState;
-import org.luaj.vm2.Lua;
-import org.luaj.vm2.LuaClosure;
-import org.luaj.vm2.LuaError;
-import org.luaj.vm2.LuaFunction;
-import org.luaj.vm2.LuaTable;
-import org.luaj.vm2.LuaValue;
-import org.luaj.vm2.Prototype;
-import org.luaj.vm2.Varargs;
-import org.luaj.vm2.lib.jse.CoerceJavaToLua;
-import org.luaj.vm2.lib.jse.JsePlatform;
+import org.luaj.vm2.*;
/**
+ * Implementation of the ScriptEngine interface which can compile and execute
+ * scripts using luaj.
*
- * @author jim_roseborough
+ *
+ * This engine requires the types of the Bindings and ScriptContext to be
+ * compatible with the engine. For creating new client context use
+ * ScriptEngine.createContext() which will return {@link LuajContext},
+ * and for client bindings use the default engine scoped bindings or
+ * construct a {@link LuajBindings} directly.
*/
public class LuaScriptEngine implements ScriptEngine, Compilable {
@@ -66,21 +49,13 @@ public class LuaScriptEngine implements ScriptEngine, Compilable {
private static final ScriptEngineFactory myFactory = new LuaScriptEngineFactory();
- private ScriptContext defaultContext;
-
- private final Globals _G;
+ private LuajContext context;
public LuaScriptEngine() {
-
- // create globals
- _G = "true".equals(System.getProperty("luaj.debug"))?
- JsePlatform.debugGlobals():
- JsePlatform.standardGlobals();
-
// set up context
- ScriptContext ctx = new SimpleScriptContext();
- ctx.setBindings(createBindings(), ScriptContext.ENGINE_SCOPE);
- setContext(ctx);
+ context = new LuajContext();
+ context.setBindings(createBindings(), ScriptContext.ENGINE_SCOPE);
+ setContext(context);
// set special values
put(LANGUAGE_VERSION, __LANGUAGE_VERSION__);
@@ -91,9 +66,6 @@ public class LuaScriptEngine implements ScriptEngine, Compilable {
put(FILENAME, __FILENAME__);
put(NAME, __SHORT_NAME__);
put("THREADING", null);
-
- // Let globals act as an index metatable
- _G.set(LuaValue.INDEX, _G);
}
public Object eval(String script) throws ScriptException {
@@ -109,7 +81,7 @@ public class LuaScriptEngine implements ScriptEngine, Compilable {
}
public Object eval(Reader reader) throws ScriptException {
- return eval(reader, getContext());
+ return compile(reader).eval();
}
public Object eval(Reader reader, ScriptContext scriptContext) throws ScriptException {
@@ -117,12 +89,7 @@ public class LuaScriptEngine implements ScriptEngine, Compilable {
}
public Object eval(Reader reader, Bindings bindings) throws ScriptException {
- ScriptContext c = getContext();
- Bindings current = c.getBindings(ScriptContext.ENGINE_SCOPE);
- c.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
- Object result = eval(reader);
- c.setBindings(current, ScriptContext.ENGINE_SCOPE);
- return result;
+ return compile(reader).eval(bindings);
}
public void put(String key, Object value) {
@@ -144,15 +111,17 @@ public class LuaScriptEngine implements ScriptEngine, Compilable {
}
public Bindings createBindings() {
- return new SimpleBindings();
+ return new LuajBindings();
}
public ScriptContext getContext() {
- return defaultContext;
+ return context;
}
public void setContext(ScriptContext context) {
- defaultContext = context;
+ if (!(context instanceof LuajContext))
+ throw new IllegalArgumentException("LuaScriptEngine can only be used with LuajScriptContext");
+ this.context = (LuajContext) context;
}
public ScriptEngineFactory getFactory() {
@@ -165,88 +134,65 @@ public class LuaScriptEngine implements ScriptEngine, Compilable {
public CompiledScript compile(Reader reader) throws ScriptException {
try {
- InputStream ris = new Utf8Encoder(reader);
+ InputStream is = new Utf8Encoder(reader);
try {
- final LuaFunction f = LoadState.load(ris, "script", "bt", _G);
- if ( f.isclosure() ) {
- // most compiled functions are closures with prototypes
- final Prototype p = f.checkclosure().p;
- return new CompiledScriptImpl() {
- protected LuaFunction newFunctionInstance(LuaTable env) {
- return new LuaClosure( p, env );
- }
- };
- } else {
- // when luajc is used, functions are java class instances
- final Class c = f.getClass();
- return new CompiledScriptImpl() {
- protected LuaFunction newFunctionInstance(LuaTable env) throws ScriptException {
- try {
- LuaFunction f = (LuaFunction) c.newInstance();
- f.initupvalue1( env );
- return f;
- } catch (Exception e) {
- throw new ScriptException("instantiation failed: "+e.toString());
- }
- }
- };
- }
+ final Globals g = context.globals;
+ final LuaFunction f = LoadState.load(is, "script", "bt", g);
+ return new LuajCompiledScript(f, g);
} catch ( LuaError lee ) {
throw new ScriptException(lee.getMessage() );
} finally {
- ris.close();
+ is.close();
}
} catch ( Exception e ) {
throw new ScriptException("eval threw "+e.toString());
}
}
-
- abstract protected class CompiledScriptImpl extends CompiledScript {
- abstract protected LuaFunction newFunctionInstance(LuaTable env) throws ScriptException;
+
+ class LuajCompiledScript extends CompiledScript {
+ final LuaFunction function;
+ final Globals compiling_globals;
+ LuajCompiledScript(LuaFunction function, Globals compiling_globals) {
+ this.function = function;
+ this.compiling_globals = compiling_globals;
+ }
+
public ScriptEngine getEngine() {
return LuaScriptEngine.this;
}
- public Object eval(ScriptContext context) throws ScriptException {
- Bindings b = context.getBindings(ScriptContext.ENGINE_SCOPE);
- BindingsGlobals env = new BindingsGlobals(b);
- LuaFunction f = newFunctionInstance(env);
+
+ public Object eval() throws ScriptException {
+ return eval(getContext());
+ }
+
+ public Object eval(Bindings bindings) throws ScriptException {
+ ScriptContext c = getContext();
+ Bindings current = c.getBindings(ScriptContext.ENGINE_SCOPE);
try {
- return f.invoke(LuaValue.NONE);
+ c.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
+ Object result = eval(c);
+ return result;
} finally {
- env.copyout();
+ c.setBindings(current, ScriptContext.ENGINE_SCOPE);
}
- }
- }
+ }
- class BindingsGlobals extends Globals {
- final Bindings b;
- BindingsGlobals(Bindings b) {
- this.b = b;
- this.setmetatable(_G);
- this.debuglib = _G.debuglib;
- for (Entry e : b.entrySet())
- rawset(toLua(e.getKey()), toLua(e.getValue()));
- }
- void copyout() {
- b.clear();
- for (Varargs v = next(LuaValue.NIL); !v.arg1().isnil(); v = next(v.arg1()))
- b.put(toJava(v.arg1()).toString(), toJava(v.arg(2)));
- }
- }
-
- LuaValue toLua(Object javaValue) {
- return javaValue == null? LuaValue.NIL:
- javaValue instanceof LuaValue? (LuaValue) javaValue:
- CoerceJavaToLua.coerce(javaValue);
- }
-
- Object toJava(LuaValue v) {
- switch ( v.type() ) {
- case LuaValue.TNIL: return null;
- case LuaValue.TSTRING: return v.tojstring();
- case LuaValue.TUSERDATA: return v.checkuserdata(Object.class);
- case LuaValue.TNUMBER: return v.isinttype()? (Object) new Integer(v.toint()): (Object) new Double(v.todouble());
- default: return v;
+ public Object eval(ScriptContext context) throws ScriptException {
+ Globals g = ((LuajContext) context).globals;
+ LuaFunction f = function;
+ if (g != compiling_globals) {
+ if (f.isclosure())
+ f = new LuaClosure(f.checkclosure().p, g);
+ else {
+ try {
+ f = f.getClass().newInstance();
+ } catch (Exception e) {
+ throw new ScriptException(e);
+ }
+ f.initupvalue1(g);
+ }
+ }
+ return f.invoke(LuaValue.NONE);
}
}
diff --git a/src/jse/org/luaj/vm2/script/LuajBindings.java b/src/jse/org/luaj/vm2/script/LuajBindings.java
new file mode 100644
index 00000000..fe262c5b
--- /dev/null
+++ b/src/jse/org/luaj/vm2/script/LuajBindings.java
@@ -0,0 +1,211 @@
+/*******************************************************************************
+* Copyright (c) 2013 LuaJ. All rights reserved.
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in
+* all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
+******************************************************************************/
+
+package org.luaj.vm2.script;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.script.Bindings;
+
+import org.luaj.vm2.LuaTable;
+import org.luaj.vm2.LuaValue;
+import org.luaj.vm2.lib.ThreeArgFunction;
+import org.luaj.vm2.lib.jse.CoerceJavaToLua;
+
+/**
+ * Implementation of Bindings that may be used with Luaj ScriptEngine implementaiton.
+ *
+ *
+ * Normally this will not be created directly, but instead will be created fro the script
+ * engine using something like:
+ *
+ * Each instance of LuajBindings holds its own global state.
+ * Calls to get(), put(), and so on will coerce the lua values
+ * into Java values on-the-fly, so that any performance or
+ * memory occur at that point.
+ *
+ *
+ * Only Java String keys may be used for get(), put(), and similar
+ * operations, or ClassCastException will be thrown.
+ *
+ *
+ * When iterating over the globals, only values which have string keys
+ * will be returned.
+ */
+public class LuajBindings implements Bindings {
+
+ /** Values that are in these bindings. This table is linked to the metatable
+ * of the context when it is executed via the metatable.
+ */
+ public final LuaTable env;
+
+ /** Metatable to be used on bindings that will forards gets and sets into our table of bindings.
+ */
+ final LuaTable metatable;
+
+ /** Construct an empty LuajBindings.
+ *
+ * Each LuajBindings has its own environment table, which will
+ * delegate to the context on global reads during execution.
+ */
+ public LuajBindings() {
+ env = new LuaTable();
+ metatable = new LuaTable();
+ metatable.set(LuaValue.INDEX, env);
+ metatable.set(LuaValue.NEWINDEX, new ThreeArgFunction() {
+ public LuaValue call(LuaValue table, LuaValue key, LuaValue value) {
+ env.rawset(key, value);
+ return LuaValue.NONE;
+ }
+ });
+ }
+
+ /** Coerce a value from luaj types to Java types.
+ * @param luajValue any value that derives from LuaValue.
+ * @return If luajValue is:
+ * {@link #NIL}, null;
+ * {@link #LuaString}: String;
+ * {@link #LuaUserdata}: Object;
+ * {@link #LuaNumber}: Integer or Double;
+ * otherwise: the raw {@link #LuaValue}.
+ */
+ public Object toJava(LuaValue luajValue) {
+ switch ( luajValue.type() ) {
+ case LuaValue.TNIL: return null;
+ case LuaValue.TSTRING: return luajValue.tojstring();
+ case LuaValue.TUSERDATA: return luajValue.checkuserdata(Object.class);
+ case LuaValue.TNUMBER: return luajValue.isinttype()?
+ (Object) new Integer(luajValue.toint()):
+ (Object) new Double(luajValue.todouble());
+ default: return luajValue;
+ }
+ }
+
+ /** Coerce a value from Java types to luaj types.
+ * @param javaValue Any Java value, including possibly null.
+ * @return LuaValue for this Java Value. If javaValue is
+ * null: {@link #NIL};
+ * {@link #LuaValue}: the value;
+ * String: {@link #LuaString};
+ * Integer: {@link #LuaInteger};
+ * Double: {@link #LuaDouble};
+ * array: {@link #LuaTable} containing list of values;
+ * otherwise {@link #LuaUserdata} from behavior of {@link CoerceJavaToLua.coerce(Object)}
+ * @see CoerceJavaToLua
+ */
+ public LuaValue toLua(Object javaValue) {
+ return javaValue == null? LuaValue.NIL:
+ javaValue instanceof LuaValue? (LuaValue) javaValue:
+ CoerceJavaToLua.coerce(javaValue);
+ }
+
+ @Override
+ public void clear() {
+ for (LuaValue key : env.keys())
+ env.rawset(key, LuaValue.NIL);
+ }
+
+ @Override
+ public boolean containsValue(Object string_key) {
+ return !env.rawget((String)string_key).isnil();
+ }
+
+ @Override
+ public Set> entrySet() {
+ HashMap map = new HashMap();
+ for (LuaValue key : env.keys())
+ if (key.type() == LuaValue.TSTRING)
+ map.put(key.tojstring(), toJava(env.rawget(key)));
+ return map.entrySet();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return keySet().isEmpty();
+ }
+
+ @Override
+ public Set keySet() {
+ Set set = new HashSet();
+ for (LuaValue key : env.keys())
+ if (key.type() == LuaValue.TSTRING)
+ set.add(key.tojstring());
+ return set;
+ }
+
+ @Override
+ public int size() {
+ return keySet().size();
+ }
+
+ @Override
+ public Collection