Fix pluggable scripting engine lookup, simplify implementation, and add unit tests.

This commit is contained in:
James Roseborough
2013-07-01 05:51:27 +00:00
parent 56fe850437
commit 9a3f6161ce
7 changed files with 343 additions and 321 deletions

View File

@@ -22,9 +22,13 @@
package org.luaj.vm2.script;
import java.io.*;
import javax.script.*;
import org.luaj.vm2.*;
import org.luaj.vm2.lib.ThreeArgFunction;
import org.luaj.vm2.lib.TwoArgFunction;
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
/**
* Implementation of the ScriptEngine interface which can compile and execute
@@ -37,7 +41,7 @@ import org.luaj.vm2.*;
* and for client bindings use the default engine scoped bindings or
* construct a {@link LuajBindings} directly.
*/
public class LuaScriptEngine implements ScriptEngine, Compilable {
public class LuaScriptEngine extends AbstractScriptEngine implements ScriptEngine, Compilable {
private static final String __ENGINE_VERSION__ = Lua._VERSION;
private static final String __NAME__ = "Luaj";
@@ -67,74 +71,16 @@ public class LuaScriptEngine implements ScriptEngine, Compilable {
put(NAME, __SHORT_NAME__);
put("THREADING", null);
}
public Object eval(String script) throws ScriptException {
return eval(new StringReader(script));
}
public Object eval(String script, ScriptContext context) throws ScriptException {
return eval(new StringReader(script), context);
}
public Object eval(String script, Bindings bindings) throws ScriptException {
return eval(new StringReader(script), bindings);
}
public Object eval(Reader reader) throws ScriptException {
return compile(reader).eval();
}
public Object eval(Reader reader, ScriptContext scriptContext) throws ScriptException {
return compile(reader).eval(scriptContext);
}
public Object eval(Reader reader, Bindings bindings) throws ScriptException {
return compile(reader).eval(bindings);
}
public void put(String key, Object value) {
Bindings b = getBindings(ScriptContext.ENGINE_SCOPE);
b.put(key, value);
}
public Object get(String key) {
Bindings b = getBindings(ScriptContext.ENGINE_SCOPE);
return b.get(key);
}
public Bindings getBindings(int scope) {
return getContext().getBindings(scope);
}
public void setBindings(Bindings bindings, int scope) {
getContext().setBindings(bindings, scope);
}
public Bindings createBindings() {
return new LuajBindings();
}
public ScriptContext getContext() {
return context;
}
public void setContext(ScriptContext context) {
if (!(context instanceof LuajContext))
throw new IllegalArgumentException("LuaScriptEngine can only be used with LuajScriptContext");
this.context = (LuajContext) context;
}
public ScriptEngineFactory getFactory() {
return myFactory;
}
@Override
public CompiledScript compile(String script) throws ScriptException {
return compile(new StringReader(script));
}
public CompiledScript compile(Reader reader) throws ScriptException {
@Override
public CompiledScript compile(Reader script) throws ScriptException {
try {
InputStream is = new Utf8Encoder(reader);
InputStream is = new Utf8Encoder(script);
try {
final Globals g = context.globals;
final LuaFunction f = LoadState.load(is, "script", "bt", g);
@@ -149,6 +95,29 @@ public class LuaScriptEngine implements ScriptEngine, Compilable {
}
}
@Override
public Bindings createBindings() {
return new SimpleBindings();
}
@Override
public Object eval(String script, ScriptContext context)
throws ScriptException {
return eval(new StringReader(script), context);
}
@Override
public Object eval(Reader reader, ScriptContext context)
throws ScriptException {
return compile(reader).eval();
}
@Override
public ScriptEngineFactory getFactory() {
return myFactory;
}
class LuajCompiledScript extends CompiledScript {
final LuaFunction function;
final Globals compiling_globals;
@@ -166,31 +135,25 @@ public class LuaScriptEngine implements ScriptEngine, Compilable {
}
public Object eval(Bindings bindings) throws ScriptException {
ScriptContext c = getContext();
Bindings current = c.getBindings(ScriptContext.ENGINE_SCOPE);
try {
c.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
Object result = eval(c);
return result;
} finally {
c.setBindings(current, ScriptContext.ENGINE_SCOPE);
}
return eval(((LuajContext) getContext()).globals, bindings);
}
public Object eval(ScriptContext context) throws ScriptException {
Globals g = ((LuajContext) context).globals;
return eval(((LuajContext) context).globals, context.getBindings(ScriptContext.ENGINE_SCOPE));
}
private Object eval(Globals g, Bindings b) throws ScriptException {
g.setmetatable(new BindingsMetatable(b));
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);
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);
}
@@ -224,4 +187,51 @@ public class LuaScriptEngine implements ScriptEngine, Compilable {
}
}
}
static class BindingsMetatable extends LuaTable {
BindingsMetatable(final Bindings bindings) {
this.rawset(LuaValue.INDEX, new TwoArgFunction() {
public LuaValue call(LuaValue table, LuaValue key) {
if (key.isstring())
return toLua(bindings.get(key.tojstring()));
else
return this.rawget(key);
}
});
this.rawset(LuaValue.NEWINDEX, new ThreeArgFunction() {
public LuaValue call(LuaValue table, LuaValue key, LuaValue value) {
if (key.isstring()) {
final String k = key.tojstring();
final Object v = toJava(value);
if (v == null)
bindings.remove(k);
else
bindings.put(k, v);
} else {
this.rawset(key, value);
}
return LuaValue.NONE;
}
});
}
static private 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;
}
}
static private LuaValue toLua(Object javaValue) {
return javaValue == null? LuaValue.NIL:
javaValue instanceof LuaValue? (LuaValue) javaValue:
CoerceJavaToLua.coerce(javaValue);
}
}
}

View File

@@ -1,211 +0,0 @@
/*******************************************************************************
* 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.
*
* <p>
* Normally this will not be created directly, but instead will be created fro the script
* engine using something like:
* <pre>
* ScriptEngineManager manager = new ScriptEngineManager();
* ScriptEngine engine = manager.getEngineByExtension(".lua");
* Bindings luaj_bindings = engine.createBindings();
* </pre>
*
* <p>
* 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.
*
* <p>
* Only Java String keys may be used for get(), put(), and similar
* operations, or ClassCastException will be thrown.
*
* <p>
* 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.
* <p>
* 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<Class>: {@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<java.util.Map.Entry<String, Object>> entrySet() {
HashMap<String, Object> map = new HashMap<String, Object>();
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<String> keySet() {
Set<String> set = new HashSet<String>();
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<Object> values() {
List<Object> values = new ArrayList<Object>();
for (LuaValue key : env.keys())
if (key.type() == LuaValue.TSTRING)
values.add(toJava(env.rawget(key)));
return values;
}
@Override
public boolean containsKey(Object key) {
return keySet().contains(key);
}
@Override
public Object get(Object string_key) {
return toJava(env.rawget((String)string_key));
}
@Override
public Object put(String string_key, Object java_value) {
Object previous = get(string_key);
env.rawset((String)string_key, toLua(java_value));
return previous;
}
@Override
public void putAll(Map<? extends String, ? extends Object> values) {
for (java.util.Map.Entry<? extends String, ? extends Object> e : values.entrySet())
env.rawset(e.getKey(), toLua(e.getValue()));
}
@Override
public Object remove(Object string_key) {
Object previous = get(string_key);
env.rawset((String)string_key, LuaValue.NIL);
return previous;
}
}

View File

@@ -83,22 +83,6 @@ public class LuajContext extends SimpleScriptContext implements ScriptContext {
stderr = globals.STDERR;
}
public Bindings getBindings(int scope) {
if (scope != ScriptContext.ENGINE_SCOPE)
throw new IllegalArgumentException("LuajScriptContext only supports ENGINE_SCOPE");
return super.getBindings(ENGINE_SCOPE);
}
public void setBindings(Bindings bindings, int scope) {
if (!(bindings instanceof LuajBindings))
throw new IllegalArgumentException("LuajScriptContext can only be used with LuajBindings");
if (scope != ScriptContext.ENGINE_SCOPE)
throw new IllegalArgumentException("LuajScriptContext only supports ENGINE_SCOPE");
LuajBindings luaj_bindings = (LuajBindings) bindings;
globals.setmetatable(luaj_bindings.metatable);
super.setBindings(bindings, scope);
}
@Override
public void setErrorWriter(Writer writer) {
globals.STDERR = writer != null?
@@ -139,5 +123,4 @@ public class LuajContext extends SimpleScriptContext implements ScriptContext {
return r.read();
}
}
}