changeset 1335:e0cf0d108a77

major cleanup
author Franklin Schmidt <fschmidt@gmail.com>
date Thu, 14 Feb 2019 03:10:45 -0700
parents c88b486a9511
children 7483108154bb
files conv.txt src/luan/Luan.java src/luan/LuanCloner.java src/luan/LuanClosure.java src/luan/LuanException.java src/luan/LuanFunction.java src/luan/LuanJavaFunction.java src/luan/LuanMethod.java src/luan/LuanTable.java src/luan/host/WebHandler.java src/luan/impl/LuanCompiler.java src/luan/impl/LuanImpl.java src/luan/impl/LuanParser.java src/luan/modules/BasicLuan.java src/luan/modules/BinaryLuan.java src/luan/modules/IoLuan.java src/luan/modules/JavaLuan.java src/luan/modules/PackageLuan.java src/luan/modules/StringLuan.java src/luan/modules/TableLuan.java src/luan/modules/ThreadLuan.java src/luan/modules/http/HttpServicer.java src/luan/modules/http/LuanHandler.java src/luan/modules/logging/Log4j.java src/luan/modules/lucene/LuceneIndex.java src/luan/modules/parsers/BBCode.java src/luan/modules/url/LuanUrl.java
diffstat 27 files changed, 568 insertions(+), 466 deletions(-) [+]
line wrap: on
line diff
--- a/conv.txt	Tue Feb 12 22:53:57 2019 -0700
+++ b/conv.txt	Thu Feb 14 03:10:45 2019 -0700
@@ -1,3 +1,4 @@
+call
 LuanState
 LuanLogger
 
--- a/src/luan/Luan.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/Luan.java	Thu Feb 14 03:10:45 2019 -0700
@@ -23,7 +23,7 @@
 
 	private final List<LuanClosure> stack = new ArrayList<LuanClosure>();
 	private Map registry;
-	public boolean isLocked = false;
+	private boolean isLocked = false;
 
 	public interface OnClose extends Closeable {
 		public void onClose(Closeable c);
@@ -57,6 +57,8 @@
 	}
 
 	void push(LuanClosure closure) {
+		if( isLocked )
+			throw new RuntimeException(this+" is locked "+closure);
 		stack.add(closure);
 	}
 
@@ -74,7 +76,7 @@
 	}
 
 	public Object eval(String cmd,Object... args) throws LuanException {
-		return Luan.load(cmd,"eval").call(this,args);
+		return load(cmd,"eval").call(args);
 	}
 
 	public Object require(String modName) throws LuanException {
@@ -106,7 +108,7 @@
 	}
 
 
-	public boolean isLessThan(Object o1,Object o2) throws LuanException {
+	public static boolean isLessThan(Object o1,Object o2) throws LuanException {
 		if( o1 instanceof Number && o2 instanceof Number ) {
 			Number n1 = (Number)o1;
 			Number n2 = (Number)o2;
@@ -119,7 +121,7 @@
 		}
 		LuanFunction fn = getBinHandler("__lt",o1,o2);
 		if( fn != null )
-			return Luan.checkBoolean( Luan.first(fn.call(this,new Object[]{o1,o2})) );
+			return Luan.checkBoolean( Luan.first(fn.call(o1,o2)) );
 		throw new LuanException( "attempt to compare " + Luan.type(o1) + " with " + Luan.type(o2) );
 	}
 
@@ -181,7 +183,7 @@
 	public static void doFile(String uri,String... args) throws LuanException {
 		Luan luan = new Luan();
 		LuanFunction fn = (LuanFunction)BasicLuan.load_file(luan,uri);
-		fn.call(luan,args);
+		fn.call((Object[])args);
 	}
 
 	public static Object first(Object obj) {
@@ -276,13 +278,13 @@
 		throw new LuanException("attempt to call a " + Luan.type(obj) + " value" );
 	}
 
-	public static LuanFunction load(String text,String sourceName,LuanTable env)
+	public LuanFunction load(String text,String sourceName,LuanTable env)
 		throws LuanException
 	{
-		return LuanCompiler.compile(text,sourceName,env);
+		return LuanCompiler.compile(this,text,sourceName,env);
 	}
 
-	public static LuanFunction load(String text,String sourceName)
+	public LuanFunction load(String text,String sourceName)
 		throws LuanException
 	{
 		return load(text,sourceName,null);
--- a/src/luan/LuanCloner.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/LuanCloner.java	Thu Feb 14 03:10:45 2019 -0700
@@ -1,7 +1,7 @@
 package luan;
 
 import java.util.Map;
-import java.util.HashMap;
+import java.util.Collection;
 import java.util.IdentityHashMap;
 
 
@@ -10,6 +10,7 @@
 
 	public final Type type;
 	private final Map cloned = new IdentityHashMap();
+	private Luan luan = null;
 
 	public LuanCloner(Type type) {
 		this.type = type;
@@ -20,6 +21,11 @@
 			return null;
 		LuanCloneable rtn = (LuanCloneable)cloned.get(obj);
 		if( rtn == null ) {
+			if( obj instanceof Luan ) {
+				if( luan != null )
+					throw new RuntimeException("2 luans in "+type+" "+this+" - "+luan+" "+obj);
+				luan = (Luan)obj;
+			}
 			rtn = obj.shallowClone();
 			cloned.put(obj,rtn);
 			obj.deepenClone(rtn,this);
@@ -42,11 +48,15 @@
 	}
 
 	public Map clone(Map obj) {
-		if( !obj.getClass().equals(HashMap.class) )
-			throw new RuntimeException("can only clone HashMap");
 		Map rtn = (Map)cloned.get(obj);
 		if( rtn == null ) {
-			rtn = new HashMap();
+			try {
+				rtn = obj.getClass().newInstance();
+			} catch(InstantiationException e) {
+				throw new RuntimeException(e);
+			} catch(IllegalAccessException e) {
+				throw new RuntimeException(e);
+			}
 			for( Object stupid : obj.entrySet() ) {
 				Map.Entry entry = (Map.Entry)stupid;
 				rtn.put( get(entry.getKey()), get(entry.getValue()) );
@@ -55,6 +65,23 @@
 		return rtn;
 	}
 
+	public Collection clone(Collection obj) {
+		Collection rtn = (Collection)cloned.get(obj);
+		if( rtn == null ) {
+			try {
+				rtn = obj.getClass().newInstance();
+			} catch(InstantiationException e) {
+				throw new RuntimeException(e);
+			} catch(IllegalAccessException e) {
+				throw new RuntimeException(e);
+			}
+			for( Object entry : (Collection)obj ) {
+				rtn.add( get(entry) );
+			}
+		}
+		return rtn;
+	}
+
 	public Object get(Object obj) {
 		if( obj instanceof LuanCloneable )
 			return clone((LuanCloneable)obj);
@@ -62,6 +89,8 @@
 			return clone((Object[])obj);
 		if( obj instanceof Map )
 			return clone((Map)obj);
+		if( obj instanceof Collection )
+			return clone((Collection)obj);
 		return obj;
 	}
 }
--- a/src/luan/LuanClosure.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/LuanClosure.java	Thu Feb 14 03:10:45 2019 -0700
@@ -3,51 +3,26 @@
 import luan.impl.Pointer;
 
 
-public abstract class LuanClosure extends LuanFunction implements LuanCloneable, Cloneable {
+public abstract class LuanClosure extends LuanFunction {
 	public Pointer[] upValues;
 	public boolean javaOk;
 	public final String sourceName;
-	private LuanCloner cloner;
 
-	public LuanClosure(int nUpValues,boolean javaOk,String sourceName) throws LuanException {
+	public LuanClosure(Luan luan,int nUpValues,boolean javaOk,String sourceName) throws LuanException {
+		super(luan);
 		this.upValues = new Pointer[nUpValues];
 		this.javaOk = javaOk;
 		this.sourceName = sourceName;
 	}
 
-	@Override public LuanClosure shallowClone() {
-		check();
-		try {
-			return (LuanClosure)clone();
-		} catch(CloneNotSupportedException e) {
-			throw new RuntimeException(e);
-		}
+	@Override protected void completeClone(LuanFunction dc,LuanCloner cloner) {
+		LuanClosure clone = (LuanClosure)dc;
+		clone.upValues = (Pointer[])cloner.clone(upValues);
+		super.completeClone(dc,cloner);
 	}
 
-	@Override public void deepenClone(LuanCloneable dc,LuanCloner cloner) {
-		LuanClosure clone = (LuanClosure)dc;
-		switch( cloner.type ) {
-		case COMPLETE:
-			clone.upValues = (Pointer[])cloner.clone(upValues);
-			return;
-		case INCREMENTAL:
-			clone.cloner = cloner;
-			clone.upValues = upValues;
-			return;
-		}
-	}
-
-	private void check() {
-		if( cloner != null ) {
-			upValues = (Pointer[])cloner.clone(upValues);
-			cloner = null;
-		}
-	}
-
-	@Override public final Object call(Luan luan,Object[] args) throws LuanException {
-		if( luan.isLocked )
-			throw new RuntimeException("luan is locked");
-		check();
+	@Override public final Object call(Object[] args) throws LuanException {
+		Luan luan = luan();
 		luan.push(this);
 		try {
 			return doCall(luan,args);
@@ -58,5 +33,9 @@
 		}	
 	}
 
+	@Override public String toString() {
+		return super.toString()+"="+sourceName;
+	}
+
 	public abstract Object doCall(Luan luan,Object[] args) throws LuanException;
 }
--- a/src/luan/LuanException.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/LuanException.java	Thu Feb 14 03:10:45 2019 -0700
@@ -5,10 +5,13 @@
 import java.io.PrintStream;
 import java.util.List;
 import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
 
 
 public final class LuanException extends Exception implements LuanCloneable {
 	private LuanTable table;
+	private Map extra = new HashMap();
 
 	public LuanException(String msg,Throwable cause) {
 		super(msg,cause);
@@ -29,13 +32,26 @@
 	@Override public void deepenClone(LuanCloneable dc,LuanCloner cloner) {
 		LuanException clone = (LuanException)dc;
 		clone.table = (LuanTable)cloner.clone(table);
+		clone.extra = (Map)cloner.clone(extra);
+	}
+
+	public void put(String key,Object value) throws LuanException {
+		if( table == null ) {
+			extra.put(key,value);
+		} else {
+			table.put(key,value);
+		}
 	}
 
 	public LuanTable table(Luan luan) {
 		if( table==null ) {
 			try {
 				LuanTable Boot = (LuanTable)luan.require("luan:Boot.luan");
-				table = (LuanTable)Boot.call( "new_error_table", this );
+				table = (LuanTable)Boot.fn("new_error_table").call(this );
+				for( Object stupid : extra.entrySet() ) {
+					Map.Entry entry = (Map.Entry)stupid;
+					table.put( entry.getKey(), entry.getValue() );
+				}
 			} catch(LuanException e) {
 				throw new RuntimeException(e);
 			}
--- a/src/luan/LuanFunction.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/LuanFunction.java	Thu Feb 14 03:10:45 2019 -0700
@@ -1,16 +1,72 @@
 package luan;
 
 
-public abstract class LuanFunction {
+public abstract class LuanFunction implements LuanCloneable, Cloneable {
+	private Luan luan;
+	private LuanCloner cloner;
+	private boolean clone;
+
+	public LuanFunction(Luan luan) {
+		if( luan==null )  throw new NullPointerException();
+		this.luan = luan;
+		this.clone = true;
+	}
+
+	public LuanFunction(boolean clone) {
+		this.clone = clone;
+	}
+
+	// for LuanJavaFunction
+	void dontClone() {
+		luan = null;
+		clone = false;
+	}
+
+	@Override public LuanFunction shallowClone() {
+		if( !clone )
+			return this;
+		check();
+		try {
+			return (LuanFunction)clone();
+		} catch(CloneNotSupportedException e) {
+			throw new RuntimeException(e);
+		}
+	}
 
-	public abstract Object call(Luan luan,Object[] args) throws LuanException;
+	private void check() {
+		if( cloner != null ) {
+			completeClone(this,cloner);
+			cloner = null;
+		}
+	}
+
+	@Override public void deepenClone(LuanCloneable dc,LuanCloner cloner) {
+		if( !clone )
+			return;
+		LuanFunction clone = (LuanFunction)dc;
+		switch( cloner.type ) {
+		case COMPLETE:
+			completeClone(clone,cloner);
+			return;
+		case INCREMENTAL:
+			clone.cloner = cloner;
+			return;
+		}
+	}
+
+	protected void completeClone(LuanFunction clone,LuanCloner cloner) {
+		clone.luan = (Luan)cloner.clone(luan);
+	}
+
+	public Luan luan() {
+		check();
+		return luan;
+	}
+
+	public abstract Object call(Object... args) throws LuanException;
 
 	public static final Object[] NOTHING = new Object[0];
 
-	public final Object call(Luan luan) throws LuanException {
-		return call(luan,NOTHING);
-	}
-
 	@Override public String toString() {
 		return "function: " + Integer.toHexString(hashCode());
 	}
--- a/src/luan/LuanJavaFunction.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/LuanJavaFunction.java	Thu Feb 14 03:10:45 2019 -0700
@@ -17,30 +17,33 @@
 	private final JavaMethod method;
 	private Object obj;
 	private final RtnConverter rtnConverter;
-	private final boolean takesLuaState;
+	private final boolean takesLuan;
 	private final ArgConverter[] argConverters;
 	private final Class varArgCls;
 
-	public LuanJavaFunction(Method method,Object obj) {
-		this( JavaMethod.of(method), obj );
+	public LuanJavaFunction(Luan luan,Method method,Object obj) {
+		this( luan, JavaMethod.of(method), obj );
 	}
 
-	public LuanJavaFunction(Constructor constr,Object obj) {
-		this( JavaMethod.of(constr), obj );
+	public LuanJavaFunction(Luan luan,Constructor constr,Object obj) {
+		this( luan, JavaMethod.of(constr), obj );
 	}
 
-	private LuanJavaFunction(JavaMethod method,Object obj) {
+	private LuanJavaFunction(Luan luan,JavaMethod method,Object obj) {
+		super(luan);
 		this.method = method;
 		this.obj = obj;
 		this.rtnConverter = getRtnConverter(method);
-		this.takesLuaState = takesLuaState(method);
-		this.argConverters = getArgConverters(takesLuaState,method);
+		this.takesLuan = takesLuan(method);
+		this.argConverters = getArgConverters(takesLuan,method);
 		if( method.isVarArgs() ) {
 			Class[] paramTypes = method.getParameterTypes();
 			this.varArgCls = paramTypes[paramTypes.length-1].getComponentType();
 		} else {
 			this.varArgCls = null;
 		}
+		if( !takesLuan )
+			dontClone();
 	}
 
 	@Override public String toString() {
@@ -55,22 +58,22 @@
 		return method.isVarArgs();
 	}
 
-	@Override public Object call(Luan luan,Object[] args) throws LuanException {
+	@Override public Object call(Object[] args) throws LuanException {
 		try {
-			args = fixArgs(luan,args);
-			return doCall(luan,args);
+			args = fixArgs(args);
+			return doCall(args);
 		} catch(IllegalArgumentException e) {
 			checkArgs(args);
 			throw e;
 		}
 	}
 
-	public Object rawCall(Luan luan,Object[] args) throws LuanException {
-		args = fixArgs(luan,args);
-		return doCall(luan,args);
+	public Object rawCall(Object[] args) throws LuanException {
+		args = fixArgs(args);
+		return doCall(args);
 	}
 
-	private Object doCall(Luan luan,Object[] args) throws LuanException {
+	private Object doCall(Object[] args) throws LuanException {
 		Object rtn;
 		try {
 			rtn = method.invoke(obj,args);
@@ -86,7 +89,7 @@
 		} catch(InstantiationException e) {
 			throw new RuntimeException(e);
 		}
-		return rtnConverter.convert(luan,rtn);
+		return rtnConverter.convert(rtn);
 	}
 
 	private static final Map primitiveMap = new HashMap();
@@ -104,7 +107,7 @@
 
 	private void checkArgs(Object[] args) throws LuanException {
 		Class[] a = method.getParameterTypes();
-		int start = takesLuaState ? 1 : 0;
+		int start = takesLuan ? 1 : 0;
 		for( int i=start; i<a.length; i++ ) {
 			Class paramType = a[i];
 			Class type = paramType;
@@ -146,18 +149,18 @@
 		return type;
 	}
 
-	private Object[] fixArgs(Luan luan,Object[] args) throws LuanException {
+	private Object[] fixArgs(Object[] args) throws LuanException {
 		int n = argConverters.length;
 		Object[] rtn;
 		int start = 0;
-		if( !takesLuaState && varArgCls==null && args.length == n ) {
+		if( !takesLuan && varArgCls==null && args.length == n ) {
 			rtn = args;
 		} else {
-			if( takesLuaState )
+			if( takesLuan )
 				n++;
 			rtn = new Object[n];
-			if( takesLuaState ) {
-				rtn[start++] = luan;
+			if( takesLuan ) {
+				rtn[start++] = luan();
 			}
 			n = argConverters.length;
 			if( varArgCls == null ) {
@@ -174,7 +177,7 @@
 					Object varArgs = Array.newInstance(varArgCls,len);
 					ArgConverter ac = argConverters[n];
 					for( int i=0; i<len; i++ ) {
-						Array.set( varArgs, i, ac.convert(luan,args[n+i]) );
+						Array.set( varArgs, i, ac.convert(args[n+i]) );
 					}
 					rtn[rtn.length-1] = varArgs;
 				}
@@ -182,28 +185,28 @@
 			System.arraycopy(args,0,rtn,start,Math.min(args.length,n));
 		}
 		for( int i=0; i<n; i++ ) {
-			rtn[start+i] = argConverters[i].convert(luan,rtn[start+i]);
+			rtn[start+i] = argConverters[i].convert(rtn[start+i]);
 		}
 		return rtn;
 	}
 
 
 	private interface RtnConverter {
-		public Object convert(Luan luan,Object obj);
+		public Object convert(Object obj);
 	}
 
 	private static final RtnConverter RTN_NOTHING = new RtnConverter() {
-		@Override public Object[] convert(Luan luan,Object obj) {
+		@Override public Object[] convert(Object obj) {
 			return NOTHING;
 		}
 	};
 
 	private static final RtnConverter RTN_SAME = new RtnConverter() {
-		@Override public Object convert(Luan luan,Object obj) {
+		@Override public Object convert(Object obj) {
 			return obj;
 		}
 	};
-
+/*
 	private static final RtnConverter RTN_ARRAY = new RtnConverter() {
 		@Override public Object convert(Luan luan,Object obj) {
 			if( obj == null )
@@ -215,23 +218,25 @@
 			return new LuanTable(luan,new ArrayList<Object>(Arrays.asList(a)));
 		}
 	};
-
+*/
 	private static RtnConverter getRtnConverter(JavaMethod m) {
 		Class rtnType = m.getReturnType();
 		if( rtnType == Void.TYPE )
 			return RTN_NOTHING;
+/*
 		if( !m.isLuan() && rtnType.isArray() && !rtnType.getComponentType().isPrimitive() ) {
 			return RTN_ARRAY;
 		}
+*/
 		return RTN_SAME;
 	}
 
 	private interface ArgConverter {
-		public Object convert(Luan luan,Object obj) throws LuanException;
+		public Object convert(Object obj) throws LuanException;
 	}
 
 	private static final ArgConverter ARG_SAME = new ArgConverter() {
-		public Object convert(Luan luan,Object obj) {
+		@Override public Object convert(Object obj) {
 			return obj;
 		}
 		@Override public String toString() {
@@ -240,7 +245,7 @@
 	};
 
 	private static final ArgConverter ARG_DOUBLE = new ArgConverter() {
-		public Object convert(Luan luan,Object obj) {
+		@Override public Object convert(Object obj) {
 			if( obj instanceof Double )
 				return obj;
 			if( obj instanceof Number ) {
@@ -255,7 +260,7 @@
 	};
 
 	private static final ArgConverter ARG_FLOAT = new ArgConverter() {
-		public Object convert(Luan luan,Object obj) {
+		@Override public Object convert(Object obj) {
 			if( obj instanceof Float )
 				return obj;
 			if( obj instanceof Number ) {
@@ -270,7 +275,7 @@
 	};
 
 	private static final ArgConverter ARG_LONG = new ArgConverter() {
-		public Object convert(Luan luan,Object obj) {
+		@Override public Object convert(Object obj) {
 			if( obj instanceof Long )
 				return obj;
 			if( obj instanceof Number ) {
@@ -287,7 +292,7 @@
 	};
 
 	private static final ArgConverter ARG_INTEGER = new ArgConverter() {
-		public Object convert(Luan luan,Object obj) {
+		@Override public Object convert(Object obj) {
 			if( obj instanceof Integer )
 				return obj;
 			if( obj instanceof Number ) {
@@ -304,7 +309,7 @@
 	};
 
 	private static final ArgConverter ARG_SHORT = new ArgConverter() {
-		public Object convert(Luan luan,Object obj) {
+		@Override public Object convert(Object obj) {
 			if( obj instanceof Short )
 				return obj;
 			if( obj instanceof Number ) {
@@ -321,7 +326,7 @@
 	};
 
 	private static final ArgConverter ARG_BYTE = new ArgConverter() {
-		public Object convert(Luan luan,Object obj) {
+		@Override public Object convert(Object obj) {
 			if( obj instanceof Byte )
 				return obj;
 			if( obj instanceof Number ) {
@@ -336,9 +341,9 @@
 			return "ARG_BYTE";
 		}
 	};
-
+/*
 	private static final ArgConverter ARG_TABLE = new ArgConverter() {
-		public Object convert(Luan luan,Object obj) {
+		@Override public Object convert(Luan luan,Object obj) {
 			LuanTable tbl = luan.toTable(obj);
 			return tbl!=null ? tbl : obj;
 		}
@@ -346,9 +351,9 @@
 			return "ARG_TABLE";
 		}
 	};
-
+*/
 	private static final ArgConverter ARG_MAP = new ArgConverter() {
-		public Object convert(Luan luan,Object obj) throws LuanException {
+		@Override public Object convert(Object obj) throws LuanException {
 			if( obj instanceof LuanTable ) {
 				LuanTable t = (LuanTable)obj;
 				return t.asMap();
@@ -361,7 +366,7 @@
 	};
 
 	private static final ArgConverter ARG_LIST = new ArgConverter() {
-		public Object convert(Luan luan,Object obj) {
+		@Override public Object convert(Object obj) {
 			if( obj instanceof LuanTable ) {
 				LuanTable t = (LuanTable)obj;
 				if( t.isList() )
@@ -375,7 +380,7 @@
 	};
 
 	private static final ArgConverter ARG_SET = new ArgConverter() {
-		public Object convert(Luan luan,Object obj) throws LuanException {
+		@Override public Object convert(Object obj) throws LuanException {
 			if( obj instanceof LuanTable ) {
 				LuanTable t = (LuanTable)obj;
 				if( t.isSet() )
@@ -389,7 +394,7 @@
 	};
 
 	private static final ArgConverter ARG_COLLECTION = new ArgConverter() {
-		public Object convert(Luan luan,Object obj) throws LuanException {
+		@Override public Object convert(Object obj) throws LuanException {
 			if( obj instanceof LuanTable ) {
 				LuanTable t = (LuanTable)obj;
 				if( t.isList() )
@@ -411,7 +416,7 @@
 			a = (Object[])Array.newInstance(cls.getComponentType(),0);
 		}
 
-		public Object convert(Luan luan,Object obj) {
+		@Override public Object convert(Object obj) {
 			if( obj instanceof LuanTable ) {
 				LuanTable t = (LuanTable)obj;
 				if( t.isList() ) {
@@ -424,15 +429,15 @@
 		}
 	}
 
-	private static boolean takesLuaState(JavaMethod m) {
+	private static boolean takesLuan(JavaMethod m) {
 		Class[] paramTypes = m.getParameterTypes();
 		return paramTypes.length > 0 && paramTypes[0].equals(Luan.class);
 	}
 
-	private static ArgConverter[] getArgConverters(boolean takesLuaState,JavaMethod m) {
+	private static ArgConverter[] getArgConverters(boolean takesLuan,JavaMethod m) {
 		final boolean isVarArgs = m.isVarArgs();
 		Class[] paramTypes = m.getParameterTypes();
-		if( takesLuaState ) {
+		if( takesLuan ) {
 			Class[] t = new Class[paramTypes.length-1];
 			System.arraycopy(paramTypes,1,t,0,t.length);
 			paramTypes = t;
@@ -460,8 +465,8 @@
 			return ARG_SHORT;
 		if( cls == Byte.TYPE || cls.equals(Byte.class) )
 			return ARG_BYTE;
-		if( cls.equals(LuanTable.class) )
-			return ARG_TABLE;
+//		if( cls.equals(LuanTable.class) )
+//			return ARG_TABLE;
 		if( cls.equals(Map.class) )
 			return ARG_MAP;
 		if( cls.equals(List.class) )
@@ -483,7 +488,7 @@
 		abstract Object invoke(Object obj,Object... args)
 			throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException;
 		abstract Class getReturnType();
-		abstract boolean isLuan();
+		abstract String getName();
 	
 		static JavaMethod of(final Method m) {
 			return new JavaMethod() {
@@ -501,8 +506,8 @@
 				@Override Class getReturnType() {
 					return m.getReturnType();
 				}
-				@Override boolean isLuan() {
-					return m.getAnnotation(LuanMethod.class) != null;
+				@Override public String getName() {
+					return m.getName();
 				}
 				@Override public String toString() {
 					return m.toString();
@@ -526,8 +531,8 @@
 				@Override Class getReturnType() {
 					return c.getDeclaringClass();
 				}
-				@Override boolean isLuan() {
-					return false;
+				@Override public String getName() {
+					return c.getName();
 				}
 				@Override public String toString() {
 					return c.toString();
--- a/src/luan/LuanMethod.java	Tue Feb 12 22:53:57 2019 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-package luan;
-
-import java.lang.annotation.*;
-
-
-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.RUNTIME)
-public @interface LuanMethod {}
--- a/src/luan/LuanTable.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/LuanTable.java	Thu Feb 14 03:10:45 2019 -0700
@@ -73,7 +73,7 @@
 		clone.security = security;
 		switch( cloner.type ) {
 		case COMPLETE:
-			deepenClone(clone,cloner);
+			completeClone(clone,cloner);
 			return;
 		case INCREMENTAL:
 			clone.cloner = cloner;
@@ -87,12 +87,17 @@
 
 	private void check() {
 		if( cloner != null ) {
-			deepenClone(this,cloner);
+			completeClone(this,cloner);
 			cloner = null;
 		}
 	}
 
-	private void deepenClone(LuanTable clone,LuanCloner cloner) {
+	public Luan luan() {
+		check();
+		return luan;
+	}
+
+	private void completeClone(LuanTable clone,LuanCloner cloner) {
 		clone.luan = (Luan)cloner.clone(luan);
 		if( map != null ) {
 			Map newMap = newMap();
@@ -132,7 +137,7 @@
 		if( h == null )
 			return rawToString();
 		LuanFunction fn = Luan.checkFunction(h);
-		return Luan.checkString( Luan.first( fn.call(luan,new Object[]{this}) ) );
+		return Luan.checkString( Luan.first( fn.call(this) ) );
 	}
 
 	public String rawToString() {
@@ -148,7 +153,7 @@
 			return null;
 		if( h instanceof LuanFunction ) {
 			LuanFunction fn = (LuanFunction)h;
-			return Luan.first(fn.call(luan,new Object[]{this,key}));
+			return Luan.first(fn.call(this,key));
 		}
 		return luan.index(h,key);
 	}
@@ -180,7 +185,7 @@
 		}
 		if( h instanceof LuanFunction ) {
 			LuanFunction fn = (LuanFunction)h;
-			fn.call(luan,new Object[]{this,key,value});
+			fn.call(this,key,value);
 			return;
 		}
 		if( h instanceof LuanTable ) {
@@ -286,7 +291,7 @@
 		Object h = getHandler("__len");
 		if( h != null ) {
 			LuanFunction fn = Luan.checkFunction(h);
-			return (Integer)Luan.first(fn.call(luan,new Object[]{this}));
+			return (Integer)Luan.first(fn.call(this));
 		}
 		return rawLength();
 	}
@@ -323,7 +328,7 @@
 
 			private Map.Entry<Object,Object> getNext() {
 				try {
-					Object obj = fn.call(luan);
+					Object obj = fn.call();
 					if( obj==null )
 						return null;
 					Object[] a = (Object[])obj;
@@ -356,7 +361,7 @@
 		if( h != null ) {
 			if( h instanceof LuanFunction ) {
 				LuanFunction fn = (LuanFunction)h;
-				Object obj = Luan.first(fn.call(luan,new Object[]{this}));
+				Object obj = Luan.first(fn.call(this));
 				if( !(obj instanceof LuanFunction) )
 					throw new LuanException( "metamethod __pairs should return function but returned " + Luan.type(obj) );
 				return (LuanFunction)obj;
@@ -367,10 +372,10 @@
 	}
 
 	private LuanFunction rawPairs() {
-		return new LuanFunction() {
+		return new LuanFunction(false) {  // ???
 			final Iterator<Map.Entry> iter = rawIterator();
 
-			@Override public Object[] call(Luan luan,Object[] args) {
+			@Override public Object[] call(Object[] args) {
 				if( !iter.hasNext() )
 					return LuanFunction.NOTHING;
 				Map.Entry<Object,Object> entry = iter.next();
@@ -517,14 +522,13 @@
 		Object h = getHandler("__gc");
 		if( h != null ) {
 			LuanFunction fn = Luan.checkFunction(h);
-			fn.call(luan,new Object[]{this});
+			fn.call(this);
 		}
 		super.finalize();
 	}
 
-	public Object call(String fnName,Object... args) throws LuanException {
-		LuanFunction fn = (LuanFunction)get(fnName);
-		return fn.call(luan,args);
+	public LuanFunction fn(String fnName) throws LuanException {
+		return (LuanFunction)get(fnName);
 	}
 
 	public static void setSecurity(LuanTable tbl,String security) {
--- a/src/luan/host/WebHandler.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/host/WebHandler.java	Thu Feb 14 03:10:45 2019 -0700
@@ -12,6 +12,7 @@
 import luan.LuanTable;
 import luan.LuanFunction;
 import luan.LuanClosure;
+import luan.LuanRuntimeException;
 import luan.modules.BasicLuan;
 import luan.modules.http.LuanHandler;
 import luan.modules.logging.Log4j;
@@ -20,14 +21,6 @@
 public class WebHandler implements Handler {
 	private static final Logger logger = LoggerFactory.getLogger(WebHandler.class);
 
-	private static final class LuanRuntimeException extends RuntimeException {
-		final LuanException e;
-
-		LuanRuntimeException(LuanException e) {
-			this.e = e;
-		}
-	}
-
 	private static final DomainHandler.Factory factory = new DomainHandler.Factory() {
 		public Handler newHandler(String domain) {
 			File dir = new File(sitesDir,domain);
@@ -103,7 +96,7 @@
 		security(luan,dir);
 		try {
 			LuanFunction fn = BasicLuan.load_file(luan,"classpath:luan/host/init.luan");
-			fn.call(luan,new Object[]{dir,domain,logging});
+			fn.call(dir,domain,logging);
 		} catch(LuanException e) {
 			throw new LuanRuntimeException(e);
 		}
@@ -117,7 +110,7 @@
 		try {
 			domainHandler.getHandler(domain);
 		} catch(LuanRuntimeException e) {
-			throw e.e;
+			throw (LuanException)e.getCause();
 		}
 	}
 
--- a/src/luan/impl/LuanCompiler.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/impl/LuanCompiler.java	Thu Feb 14 03:10:45 2019 -0700
@@ -4,6 +4,7 @@
 import java.lang.reflect.InvocationTargetException;
 import java.util.Map;
 import java.util.HashMap;
+import luan.Luan;
 import luan.LuanFunction;
 import luan.LuanException;
 import luan.LuanTable;
@@ -15,14 +16,14 @@
 public final class LuanCompiler {
 	private static final Map<String,WeakReference<Class>> map = new HashMap<String,WeakReference<Class>>();
 
-	public static LuanFunction compile(String sourceText,String sourceName,LuanTable env) throws LuanException {
+	public static LuanFunction compile(Luan luan,String sourceText,String sourceName,LuanTable env) throws LuanException {
 		Class fnClass = env==null ? getClass(sourceText,sourceName) : getClass(sourceText,sourceName,env);
 		boolean javaOk = false;
 		if( env != null && env.closure != null )
 			javaOk = env.closure.javaOk;
 		LuanClosure closure;
 		try {
-			closure = (LuanClosure)fnClass.getConstructor(Boolean.TYPE,String.class).newInstance(javaOk,sourceName);
+			closure = (LuanClosure)fnClass.getConstructor(Luan.class,Boolean.TYPE,String.class).newInstance(luan,javaOk,sourceName);
 		} catch(NoSuchMethodException e) {
 			throw new RuntimeException(e);
 		} catch(InstantiationException e) {
@@ -32,8 +33,8 @@
 		} catch(InvocationTargetException e) {
 			throw new RuntimeException(e);
 		}
-		closure.upValues[0].o = JavaLuan.javaFn;
-		closure.upValues[1].o = PackageLuan.requireFn;
+		closure.upValues[0].o = JavaLuan.javaFn(luan);
+		closure.upValues[1].o = PackageLuan.requireFn(luan);
 		if( env != null ) {
 			closure.upValues[2].o = env;
 			env.closure = closure;
--- a/src/luan/impl/LuanImpl.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/impl/LuanImpl.java	Thu Feb 14 03:10:45 2019 -0700
@@ -29,75 +29,75 @@
 		throw new LuanException( "attempt to get length of a " + Luan.type(o) + " value" );
 	}
 
-	public static Object unm(Luan luan,Object o) throws LuanException {
+	public static Object unm(Object o) throws LuanException {
 		if( o instanceof Number )
 			return -((Number)o).doubleValue();
 		if( o instanceof LuanTable ) {
 			LuanFunction fn = Luan.getHandlerFunction("__unm",(LuanTable)o);
 			if( fn != null ) {
-				return Luan.first(fn.call(luan,new Object[]{o}));
+				return Luan.first(fn.call(o));
 			}
 		}
 		throw new LuanException("attempt to perform arithmetic on a "+Luan.type(o)+" value");
 	}
 
-	private static Object arithmetic(Luan luan,String op,Object o1,Object o2) throws LuanException {
+	private static Object arithmetic(String op,Object o1,Object o2) throws LuanException {
 		LuanFunction fn = Luan.getBinHandler(op,o1,o2);
 		if( fn != null )
-			return Luan.first(fn.call(luan,new Object[]{o1,o2}));
+			return Luan.first(fn.call(o1,o2));
 		String type = !(o1 instanceof Number) ? Luan.type(o1) : Luan.type(o2);
 		throw new LuanException("attempt to perform arithmetic on a "+type+" value");
 	}
 
-	public static Object pow(Luan luan,Object o1,Object o2) throws LuanException {
+	public static Object pow(Object o1,Object o2) throws LuanException {
 		if( o1 instanceof Number && o2 instanceof Number )
 			return Math.pow( ((Number)o1).doubleValue(), ((Number)o2).doubleValue() );
-		return arithmetic(luan,"__pow",o1,o2);
+		return arithmetic("__pow",o1,o2);
 	}
 
-	public static Object mul(Luan luan,Object o1,Object o2) throws LuanException {
+	public static Object mul(Object o1,Object o2) throws LuanException {
 		if( o1 instanceof Number && o2 instanceof Number )
 			return ((Number)o1).doubleValue() * ((Number)o2).doubleValue();
-		return arithmetic(luan,"__mul",o1,o2);
+		return arithmetic("__mul",o1,o2);
 	}
 
-	public static Object div(Luan luan,Object o1,Object o2) throws LuanException {
+	public static Object div(Object o1,Object o2) throws LuanException {
 		if( o1 instanceof Number && o2 instanceof Number )
 			return ((Number)o1).doubleValue() / ((Number)o2).doubleValue();
-		return arithmetic(luan,"__div",o1,o2);
+		return arithmetic("__div",o1,o2);
 	}
 
-	public static Object mod(Luan luan,Object o1,Object o2) throws LuanException {
+	public static Object mod(Object o1,Object o2) throws LuanException {
 		if( o1 instanceof Number && o2 instanceof Number ) {
 			double d1 = ((Number)o1).doubleValue();
 			double d2 = ((Number)o2).doubleValue();
 			return d1 - Math.floor(d1/d2)*d2;
 		}
-		return arithmetic(luan,"__mod",o1,o2);
+		return arithmetic("__mod",o1,o2);
 	}
 
-	public static Object add(Luan luan,Object o1,Object o2) throws LuanException {
+	public static Object add(Object o1,Object o2) throws LuanException {
 		if( o1 instanceof Number && o2 instanceof Number )
 			return ((Number)o1).doubleValue() + ((Number)o2).doubleValue();
-		return arithmetic(luan,"__add",o1,o2);
+		return arithmetic("__add",o1,o2);
 	}
 
-	public static Object sub(Luan luan,Object o1,Object o2) throws LuanException {
+	public static Object sub(Object o1,Object o2) throws LuanException {
 		if( o1 instanceof Number && o2 instanceof Number )
 			return ((Number)o1).doubleValue() - ((Number)o2).doubleValue();
-		return arithmetic(luan,"__sub",o1,o2);
+		return arithmetic("__sub",o1,o2);
 	}
 
-	public static Object concat(Luan luan,Object o1,Object o2) throws LuanException {
+	public static Object concat(Object o1,Object o2) throws LuanException {
 		LuanFunction fn = Luan.getBinHandler("__concat",o1,o2);
 		if( fn != null )
-			return Luan.first(fn.call(luan,new Object[]{o1,o2}));
+			return Luan.first(fn.call(o1,o2));
 		String s1 = Luan.luanToString(o1);
 		String s2 = Luan.luanToString(o2);
 		return s1 + s2;
 	}
 
-	public static boolean eq(Luan luan,Object o1,Object o2) throws LuanException {
+	public static boolean eq(Object o1,Object o2) throws LuanException {
 		if( o1 == o2 || o1 != null && o1.equals(o2) )
 			return true;
 		if( o1 instanceof Number && o2 instanceof Number ) {
@@ -122,10 +122,10 @@
 		if( f == null || !f.equals(mt2.rawGet("__eq")) )
 			return false;
 		LuanFunction fn = Luan.checkFunction(f);
-		return Luan.checkBoolean( Luan.first(fn.call(luan,new Object[]{o1,o2})) );
+		return Luan.checkBoolean( Luan.first(fn.call(o1,o2)) );
 	}
 
-	public static boolean le(Luan luan,Object o1,Object o2) throws LuanException {
+	public static boolean le(Object o1,Object o2) throws LuanException {
 		if( o1 instanceof Number && o2 instanceof Number ) {
 			Number n1 = (Number)o1;
 			Number n2 = (Number)o2;
@@ -138,15 +138,15 @@
 		}
 		LuanFunction fn = Luan.getBinHandler("__le",o1,o2);
 		if( fn != null )
-			return Luan.checkBoolean( Luan.first(fn.call(luan,new Object[]{o1,o2})) );
+			return Luan.checkBoolean( Luan.first(fn.call(o1,o2)) );
 		fn = Luan.getBinHandler("__lt",o1,o2);
 		if( fn != null )
-			return !Luan.checkBoolean( Luan.first(fn.call(luan,new Object[]{o2,o1})) );
+			return !Luan.checkBoolean( Luan.first(fn.call(o2,o1)) );
 		throw new LuanException( "attempt to compare " + Luan.type(o1) + " with " + Luan.type(o2) );
 	}
 
-	public static boolean lt(Luan luan,Object o1,Object o2) throws LuanException {
-		return luan.isLessThan(o1,o2);
+	public static boolean lt(Object o1,Object o2) throws LuanException {
+		return Luan.isLessThan(o1,o2);
 	}
 
 	public static boolean cnd(Object o) throws LuanException {
--- a/src/luan/impl/LuanParser.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/impl/LuanParser.java	Thu Feb 14 03:10:45 2019 -0700
@@ -366,7 +366,7 @@
 		Expr exp = new Expr(null,true);
 		exp.add( "Luan.checkFunction(" );
 		exp.addAll( fn.single() );
-		exp.add( ").call(luan," );
+		exp.add( ").call(" );
 		exp.addAll( args.array() );
 		exp.add( ")" );
 		return exp;
@@ -377,7 +377,7 @@
 		if( exprs == null )
 			return null;
 		Stmts stmt = new Stmts();
-		stmt.add( "Luan.checkFunction(luan.index(PackageLuan.require(luan,\"luan:Io.luan\"),\"template_write\")).call(luan," );
+		stmt.add( "Luan.checkFunction(luan.index(PackageLuan.require(luan,\"luan:Io.luan\"),\"template_write\")).call(" );
 		stmt.addAll( exprs.array() );
 		stmt.add( ");  " );
 		return stmt;
@@ -508,7 +508,7 @@
 
 		String fnVar = "fn"+ ++forCounter;
 		Expr fnExp = new Expr(null,false);
-		fnExp.add( fnVar + ".call(luan)" );
+		fnExp.add( fnVar + ".call()" );
 		Stmts stmt = new Stmts();
 		stmt.add( ""
 			+"LuanFunction "+fnVar+" = Luan.checkFunction("
@@ -828,7 +828,7 @@
 				exp = exp.single();
 				Expr exp2 = required(ConcatExpr(in)).single();
 				Expr newExp = new Expr(Val.SINGLE,false);
-				newExp.add( "LuanImpl.eq(luan," );
+				newExp.add( "LuanImpl.eq(" );
 				newExp.addAll( exp );
 				newExp.add( "," );
 				newExp.addAll( exp2 );
@@ -839,7 +839,7 @@
 				exp = exp.single();
 				Expr exp2 = required(ConcatExpr(in)).single();
 				Expr newExp = new Expr(Val.SINGLE,false);
-				newExp.add( "!LuanImpl.eq(luan," );
+				newExp.add( "!LuanImpl.eq(" );
 				newExp.addAll( exp );
 				newExp.add( "," );
 				newExp.addAll( exp2 );
@@ -850,7 +850,7 @@
 				exp = exp.single();
 				Expr exp2 = required(ConcatExpr(in)).single();
 				Expr newExp = new Expr(Val.SINGLE,false);
-				newExp.add( "LuanImpl.le(luan," );
+				newExp.add( "LuanImpl.le(" );
 				newExp.addAll( exp );
 				newExp.add( "," );
 				newExp.addAll( exp2 );
@@ -861,7 +861,7 @@
 				exp = exp.single();
 				Expr exp2 = required(ConcatExpr(in)).single();
 				Expr newExp = new Expr(Val.SINGLE,false);
-				newExp.add( "LuanImpl.le(luan," );
+				newExp.add( "LuanImpl.le(" );
 				newExp.addAll( exp2 );
 				newExp.add( "," );
 				newExp.addAll( exp );
@@ -872,7 +872,7 @@
 				exp = exp.single();
 				Expr exp2 = required(ConcatExpr(in)).single();
 				Expr newExp = new Expr(Val.SINGLE,false);
-				newExp.add( "LuanImpl.lt(luan," );
+				newExp.add( "LuanImpl.lt(" );
 				newExp.addAll( exp );
 				newExp.add( "," );
 				newExp.addAll( exp2 );
@@ -883,7 +883,7 @@
 				exp = exp.single();
 				Expr exp2 = required(ConcatExpr(in)).single();
 				Expr newExp = new Expr(Val.SINGLE,false);
-				newExp.add( "LuanImpl.lt(luan," );
+				newExp.add( "LuanImpl.lt(" );
 				newExp.addAll( exp2 );
 				newExp.add( "," );
 				newExp.addAll( exp );
@@ -905,7 +905,7 @@
 			exp = exp.single();
 			Expr exp2 = required(ConcatExpr(in)).single();
 			Expr newExp = new Expr(Val.SINGLE,false);
-			newExp.add( "LuanImpl.concat(luan," );
+			newExp.add( "LuanImpl.concat(" );
 			newExp.addAll( exp );
 			newExp.add( "," );
 			newExp.addAll( exp2 );
@@ -926,7 +926,7 @@
 				exp = exp.single();
 				Expr exp2 = required(TermExpr(in)).single();
 				Expr newExp = new Expr(Val.SINGLE,false);
-				newExp.add( "LuanImpl.add(luan," );
+				newExp.add( "LuanImpl.add(" );
 				newExp.addAll( exp );
 				newExp.add( "," );
 				newExp.addAll( exp2 );
@@ -937,7 +937,7 @@
 				exp = exp.single();
 				Expr exp2 = required(TermExpr(in)).single();
 				Expr newExp = new Expr(Val.SINGLE,false);
-				newExp.add( "LuanImpl.sub(luan," );
+				newExp.add( "LuanImpl.sub(" );
 				newExp.addAll( exp );
 				newExp.add( "," );
 				newExp.addAll( exp2 );
@@ -965,7 +965,7 @@
 				exp = exp.single();
 				Expr exp2 = required(UnaryExpr(in)).single();
 				Expr newExp = new Expr(Val.SINGLE,false);
-				newExp.add( "LuanImpl.mul(luan," );
+				newExp.add( "LuanImpl.mul(" );
 				newExp.addAll( exp );
 				newExp.add( "," );
 				newExp.addAll( exp2 );
@@ -976,7 +976,7 @@
 				exp = exp.single();
 				Expr exp2 = required(UnaryExpr(in)).single();
 				Expr newExp = new Expr(Val.SINGLE,false);
-				newExp.add( "LuanImpl.div(luan," );
+				newExp.add( "LuanImpl.div(" );
 				newExp.addAll( exp );
 				newExp.add( "," );
 				newExp.addAll( exp2 );
@@ -987,7 +987,7 @@
 				exp = exp.single();
 				Expr exp2 = required(UnaryExpr(in)).single();
 				Expr newExp = new Expr(Val.SINGLE,false);
-				newExp.add( "LuanImpl.mod(luan," );
+				newExp.add( "LuanImpl.mod(" );
 				newExp.addAll( exp );
 				newExp.add( "," );
 				newExp.addAll( exp2 );
@@ -1019,7 +1019,7 @@
 			Spaces();
 			Expr exp = required(UnaryExpr(in)).single();
 			Expr newExp = new Expr(Val.SINGLE,false);
-			newExp.add( "LuanImpl.unm(luan," );
+			newExp.add( "LuanImpl.unm(" );
 			newExp.addAll( exp );
 			newExp.add( ")" );
 			return parser.success(newExp);
@@ -1048,7 +1048,7 @@
 			Spaces();
 			Expr exp2 = required(PowExpr(in));
 			Expr newExp = new Expr(Val.SINGLE,false);
-			newExp.add( "LuanImpl.pow(luan," );
+			newExp.add( "LuanImpl.pow(" );
 			newExp.addAll( exp1.single() );
 			newExp.add( "," );
 			newExp.addAll( exp2.single() );
@@ -2015,8 +2015,8 @@
 			+"import luan.modules.PackageLuan;  "
 
 			+"public class " + className +" extends LuanClosure {  "
-				+"public "+className+"(boolean javaOk,String sourceName) throws LuanException {  "
-					+"super("+upValueSymbols.size()+",javaOk,sourceName);  "
+				+"public "+className+"(Luan luan,boolean javaOk,String sourceName) throws LuanException {  "
+					+"super(luan,"+upValueSymbols.size()+",javaOk,sourceName);  "
 					+ init(upValueSymbols)
 				+"}  "
 
@@ -2036,7 +2036,7 @@
 			stmt.add( "return LuanFunction.NOTHING;  " );
 		Expr exp = new Expr(Val.SINGLE,false);
 		exp.add( ""
-			+"new LuanClosure("+upValueSymbols.size()+",javaOk,sourceName) {  "
+			+"new LuanClosure(luan(),"+upValueSymbols.size()+",javaOk,sourceName) {  "
 				+"{  "
 				+ init(upValueSymbols)
 				+"}  "
--- a/src/luan/modules/BasicLuan.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/modules/BasicLuan.java	Thu Feb 14 03:10:45 2019 -0700
@@ -12,7 +12,7 @@
 import luan.LuanTable;
 import luan.LuanFunction;
 import luan.LuanException;
-import luan.LuanMethod;
+import luan.LuanCloner;
 import luan.modules.parsers.LuanToString;
 
 
@@ -22,12 +22,12 @@
 		return Luan.type(obj);
 	}
 
-	public static LuanFunction load(String text,String sourceName,LuanTable env)
+	public static LuanFunction load(Luan luan,String text,String sourceName,LuanTable env)
 		throws LuanException
 	{
 		Utils.checkNotNull(text);
 		Utils.checkNotNull(sourceName,1);
-		return Luan.load(text,sourceName,env);
+		return luan.load(text,sourceName,env);
 	}
 
 	public static LuanFunction load_file(Luan luan,String fileName) throws LuanException {
@@ -39,7 +39,7 @@
 		String src = PackageLuan.read(luan,fileName);
 		if( src == null )
 			return null;
-		return load(src,fileName,null);
+		return load(luan,src,fileName,null);
 	}
 
 	public static LuanFunction pairs(final LuanTable t) throws LuanException {
@@ -47,20 +47,34 @@
 		return t.pairs();
 	}
 
-	public static LuanFunction ipairs(final LuanTable t) throws LuanException {
-		Utils.checkNotNull(t);
-		return new LuanFunction() {
-			List<Object> list = t.asList();
-			int i = 0;
-			final int size = list.size();
+	private static class Ipairs extends LuanFunction {
+		List<Object> list;
+		int i = 0;
+		final int size;
+
+		Ipairs(LuanTable t) {
+			super(true);
+			list = t.asList();
+			size = list.size();
+		}
 
-			@Override public Object[] call(Luan luan,Object[] args) {
-				if( i >= size )
-					return LuanFunction.NOTHING;
-				Object val = list.get(i++);
-				return new Object[]{i,val};
-			}
-		};
+		@Override public Object[] call(Object[] args) {
+			if( i >= size )
+				return LuanFunction.NOTHING;
+			Object val = list.get(i++);
+			return new Object[]{i,val};
+		}
+
+		@Override protected void completeClone(LuanFunction dc,LuanCloner cloner) {
+			Ipairs clone = (Ipairs)dc;
+			clone.list = (List)cloner.clone(list);
+			super.completeClone(dc,cloner);
+		}
+	}
+
+	public static LuanFunction ipairs(LuanTable t) throws LuanException {
+		Utils.checkNotNull(t);
+		return new Ipairs(t);
 	}
 
 	public static Object get_metatable(LuanTable table) throws LuanException {
@@ -134,10 +148,10 @@
 		final double step = stepV==null ? 1.0 : stepV;
 		if( step == 0.0 )
 			throw new LuanException("bad argument #3 (step may not be zero)");
-		return new LuanFunction() {
+		return new LuanFunction(false) {
 			double v = from;
 
-			@Override public Object call(Luan luan,Object[] args) {
+			@Override public Object call(Object[] args) {
 				if( step > 0.0 && v > to || step < 0.0 && v < to )
 					return LuanFunction.NOTHING;
 				double rtn = v;
@@ -147,24 +161,38 @@
 		};
 	}
 
-	public static LuanFunction values(final Object... args) throws LuanException {
-		return new LuanFunction() {
-			int i = 0;
+	private static class Values extends LuanFunction {
+		Object[] args;
+		int i = 0;
+
+		Values(Object[] args) {
+			super(true);
+			this.args = args;
+		}
 
-			@Override public Object call(Luan luan,Object[] unused) {
-				if( i >= args.length )
-					return LuanFunction.NOTHING;
-				Object val = args[i++];
-				return new Object[]{i,val};
-			}
-		};
+		@Override public Object[] call(Object[] args) {
+			if( i >= args.length )
+				return LuanFunction.NOTHING;
+			Object val = args[i++];
+			return new Object[]{i,val};
+		}
+
+		@Override protected void completeClone(LuanFunction dc,LuanCloner cloner) {
+			Values clone = (Values)dc;
+			clone.args = (Object[])cloner.clone(args);
+			super.completeClone(dc,cloner);
+		}
+	}
+
+	public static LuanFunction values(final Object... args) throws LuanException {
+		return new Values(args);
 	}
 
 	private LuanFunction fn(Object obj) {
 		return obj instanceof LuanFunction ? (LuanFunction)obj : null;
 	}
 
-	public static Object try_(Luan luan,LuanTable blocks,Object... args) throws LuanException {
+	public static Object try_(LuanTable blocks,Object... args) throws LuanException {
 		Utils.checkNotNull(blocks);
 		Object obj = blocks.get(1);
 		if( obj == null )
@@ -187,20 +215,20 @@
 			finallyFn = (LuanFunction)obj;
 		}
 		try {
-			return tryFn.call(luan,args);
+			return tryFn.call(args);
 		} catch(LuanException e) {
 			if( catchFn == null )
 				throw e;
-			return catchFn.call(luan,new Object[]{e.table(luan)});
+			return catchFn.call(e.table(blocks.luan()));
 		} finally {
 			if( finallyFn != null )
-				finallyFn.call(luan);
+				finallyFn.call();
 		}
 	}
 
-	@LuanMethod public static Object[] pcall(Luan luan,LuanFunction f,Object... args) {
+	public static Object[] pcall(LuanFunction f,Object... args) {
 		try {
-			Object[] r = Luan.array(f.call(luan,args));
+			Object[] r = Luan.array(f.call(args));
 			Object[] rtn = new Object[r.length+1];
 			rtn[0] = true;
 			for( int i=0; i<r.length; i++ ) {
@@ -208,7 +236,7 @@
 			}
 			return rtn;
 		} catch(LuanException e) {
-			return new Object[]{false,e.table(luan)};
+			return new Object[]{false,e.table(f.luan())};
 		}
 	}
 
--- a/src/luan/modules/BinaryLuan.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/modules/BinaryLuan.java	Thu Feb 14 03:10:45 2019 -0700
@@ -5,7 +5,6 @@
 import luan.LuanTable;
 import luan.LuanFunction;
 import luan.LuanException;
-import luan.LuanMethod;
 
 
 public final class BinaryLuan {
@@ -28,7 +27,7 @@
 		return i==null ? dflt : end(binary,i);
 	}
 
-	@LuanMethod public static Byte[] byte_(byte[] binary,Integer i,Integer j) throws LuanException {
+	public static Byte[] byte_(byte[] binary,Integer i,Integer j) throws LuanException {
 		Utils.checkNotNull(binary);
 		int start = start(binary,i,1);
 		int end = end(binary,j,start+1);
@@ -39,7 +38,7 @@
 		return bytes;
 	}
 
-	@LuanMethod public static byte[] binary(byte... bytes) {
+	public static byte[] binary(byte... bytes) {
 		return bytes;
 	}
 
--- a/src/luan/modules/IoLuan.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/modules/IoLuan.java	Thu Feb 14 03:10:45 2019 -0700
@@ -89,8 +89,8 @@
 	}
 
 	static LuanFunction lines(final BufferedReader in) {
-		return new LuanFunction() {
-			@Override public Object call(Luan luan,Object[] args) throws LuanException {
+		return new LuanFunction(false) {
+			@Override public Object call(Object[] args) throws LuanException {
 				try {
 					if( args.length > 0 ) {
 						if( args.length > 1 || !"close".equals(args[0]) )
@@ -110,10 +110,10 @@
 	}
 
 	static LuanFunction blocks(final InputStream in,final int blockSize) {
-		return new LuanFunction() {
+		return new LuanFunction(false) {
 			final byte[] a = new byte[blockSize];
 
-			@Override public Object call(Luan luan,Object[] args) throws LuanException {
+			@Override public Object call(Object[] args) throws LuanException {
 				try {
 					if( args.length > 0 ) {
 						if( args.length > 1 || !"close".equals(args[0]) )
@@ -153,41 +153,41 @@
 	public static abstract class LuanIn {
 		protected String charset = null;
 
-		public abstract InputStream inputStream(Luan luan) throws IOException, LuanException;
+		public abstract InputStream inputStream() throws IOException, LuanException;
 		public abstract String to_string();
 		public abstract String to_uri_string();
 
-		public Reader reader(Luan luan) throws IOException, LuanException {
-			InputStream in = inputStream(luan);
+		public Reader reader() throws IOException, LuanException {
+			InputStream in = inputStream();
 			return charset==null ? new InputStreamReader(in) : new InputStreamReader(in,charset);
 		}
 
-		public String read_text(Luan luan) throws IOException, LuanException {
-			Reader in = reader(luan);
+		public String read_text() throws IOException, LuanException {
+			Reader in = reader();
 			String s = Utils.readAll(in);
 			in.close();
 			return s;
 		}
 
-		public byte[] read_binary(Luan luan) throws IOException, LuanException {
-			InputStream in = inputStream(luan);
+		public byte[] read_binary() throws IOException, LuanException {
+			InputStream in = inputStream();
 			byte[] a = Utils.readAll(in);
 			in.close();
 			return a;
 		}
 
-		public LuanFunction read_lines(Luan luan) throws IOException, LuanException {
-			return lines(new BufferedReader(reader(luan)));
+		public LuanFunction read_lines() throws IOException, LuanException {
+			return lines(new BufferedReader(reader()));
 		}
 
-		public LuanFunction read_blocks(Luan luan,Integer blockSize) throws IOException, LuanException {
+		public LuanFunction read_blocks(Integer blockSize) throws IOException, LuanException {
 			int n = blockSize!=null ? blockSize : Utils.bufSize;
-			return blocks(inputStream(luan),n);
+			return blocks(inputStream(),n);
 		}
 
-		public boolean exists(Luan luan) throws IOException, LuanException {
+		public boolean exists() throws IOException, LuanException {
 			try {
-				inputStream(luan).close();
+				inputStream().close();
 				return true;
 			} catch(FileNotFoundException e) {
 				return false;
@@ -196,9 +196,9 @@
 			}
 		}
 
-		public long checksum(Luan luan) throws IOException, LuanException {
+		public long checksum() throws IOException, LuanException {
 			long cs = 0;
-			InputStream in = new BufferedInputStream(inputStream(luan));
+			InputStream in = new BufferedInputStream(inputStream());
 			int c;
 			while( (c=in.read()) != -1 ) {
 				cs = 31 * cs + c;
@@ -218,7 +218,7 @@
 
 	public static final LuanIn defaultStdin = new LuanIn() {
 
-		@Override public InputStream inputStream(Luan luan) {
+		@Override public InputStream inputStream() {
 			return System.in;
 		}
 
@@ -230,15 +230,15 @@
 			return "stdin:";
 		}
 
-		@Override public String read_text(Luan luan) throws IOException {
+		@Override public String read_text() throws IOException {
 			return Utils.readAll(new InputStreamReader(System.in));
 		}
 
-		@Override public byte[] read_binary(Luan luan) throws IOException {
+		@Override public byte[] read_binary() throws IOException {
 			return Utils.readAll(System.in);
 		}
 
-		@Override public boolean exists(Luan luan) {
+		@Override public boolean exists() {
 			return true;
 		}
 	};
@@ -251,7 +251,7 @@
 			return charset==null ? new OutputStreamWriter(out) : new OutputStreamWriter(out,charset);
 		}
 
-		public void write(Luan luan,Object obj) throws LuanException, IOException {
+		public void write(Object obj) throws LuanException, IOException {
 			if( obj instanceof String ) {
 				String s = (String)obj;
 				Writer out = writer();
@@ -271,7 +271,7 @@
 				Object java = t.rawGet("java");
 				if( java instanceof LuanIn ) {
 					LuanIn luanIn = (LuanIn)java;
-					InputStream in = luanIn.inputStream(luan);
+					InputStream in = luanIn.inputStream();
 					OutputStream out = outputStream();
 					Utils.copyAll(in,out);
 					out.close();
@@ -290,9 +290,9 @@
 			return new BufferedOutputStream(outputStream());
 		}
 
-		public void write_text(Luan luan,Object... args) throws LuanException, IOException {
+		public void write_text(Object... args) throws LuanException, IOException {
 			LuanWriter luanWriter = luanWriter(new BufferedWriter(writer()));
-			luanWriter.write(luan,args);
+			luanWriter.write(args);
 			luanWriter.close();
 		}
 	}
@@ -307,7 +307,7 @@
 			@Override public void write(int b) {}
 		};
 
-		@Override public InputStream inputStream(Luan luan) {
+		@Override public InputStream inputStream() {
 			return in;
 		}
 
@@ -333,7 +333,7 @@
 			this.s = s;
 		}
 
-		@Override public InputStream inputStream(Luan luan) {
+		@Override public InputStream inputStream() {
 			throw new UnsupportedOperationException();
 		}
 
@@ -349,15 +349,15 @@
 			return "string:" + s;
 		}
 
-		@Override public Reader reader(Luan luan) {
+		@Override public Reader reader() {
 			return new StringReader(s);
 		}
 
-		@Override public String read_text(Luan luan) {
+		@Override public String read_text() {
 			return s;
 		}
 
-		@Override public boolean exists(Luan luan) {
+		@Override public boolean exists() {
 			return true;
 		}
 
@@ -398,7 +398,7 @@
 			this.file = file;
 		}
 
-		@Override public InputStream inputStream(Luan luan) throws IOException {
+		@Override public InputStream inputStream() throws IOException {
 			return new FileInputStream(file);
 		}
 
@@ -436,7 +436,7 @@
 			return new LuanFile(luan,parent);
 		}
 
-		@Override public boolean exists(Luan luan) {
+		@Override public boolean exists() {
 			return file.exists();
 		}
 
@@ -546,7 +546,7 @@
 			this.dir = dir;
 		}
 
-		@Override public InputStream inputStream(Luan luan) throws IOException {
+		@Override public InputStream inputStream() throws IOException {
 			return proc.getInputStream();
 		}
 
@@ -562,7 +562,7 @@
 			throw new UnsupportedOperationException();
 		}
 
-		@Override public boolean exists(Luan luan) {
+		@Override public boolean exists() {
 			return true;
 		}
 
@@ -583,8 +583,8 @@
 			}
 		}
 
-		@Override public String read_text(Luan luan) throws IOException, LuanException {
-			String s = super.read_text(luan);
+		@Override public String read_text() throws IOException, LuanException {
+			String s = super.read_text();
 			wait_for();
 			return s;
 		}
@@ -614,7 +614,7 @@
 			this.in = in;
 		}
 
-		@Override public InputStream inputStream(Luan luan) {
+		@Override public InputStream inputStream() {
 			return in;
 		}
 
@@ -626,7 +626,7 @@
 			throw new UnsupportedOperationException();
 		}
 
-		@Override public boolean exists(Luan luan) {
+		@Override public boolean exists() {
 			return true;
 		}
 	};
--- a/src/luan/modules/JavaLuan.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/modules/JavaLuan.java	Thu Feb 14 03:10:45 2019 -0700
@@ -22,6 +22,7 @@
 import luan.LuanException;
 import luan.LuanFunction;
 import luan.LuanJavaFunction;
+import luan.LuanCloner;
 
 
 public final class JavaLuan {
@@ -31,13 +32,17 @@
 		luan.peek().javaOk = true;
 	}
 
-	public static final LuanFunction javaFn;
-	static {
-		try {
-			javaFn = new LuanJavaFunction(JavaLuan.class.getMethod("java",Luan.class),null);
-		} catch(NoSuchMethodException e) {
-			throw new RuntimeException(e);
+	public static LuanFunction javaFn(Luan luan) {
+		LuanFunction fn = (LuanFunction)luan.registry().get("JavaLuan.java");
+		if( fn == null ) {
+			try {
+				fn = new LuanJavaFunction(luan,JavaLuan.class.getMethod("java",Luan.class),null);
+			} catch(NoSuchMethodException e) {
+				throw new RuntimeException(e);
+			}
+			luan.registry().put("JavaLuan.java",fn);
 		}
+		return fn;
 	}
 
 	private static void checkJava(Luan luan) throws LuanException {
@@ -59,11 +64,11 @@
 					Constructor[] constructors = cls.getConstructors();
 					if( constructors.length > 0 ) {
 						if( constructors.length==1 ) {
-							return new LuanJavaFunction(constructors[0],null);
+							return new LuanJavaFunction(luan,constructors[0],null);
 						} else {
 							List<LuanJavaFunction> fns = new ArrayList<LuanJavaFunction>();
 							for( Constructor constructor : constructors ) {
-								fns.add(new LuanJavaFunction(constructor,null));
+								fns.add(new LuanJavaFunction(luan,constructor,null));
 							}
 							return new AmbiguousJavaFunction(fns);
 						}
@@ -73,11 +78,11 @@
 					return new LuanJavaFunction(assertClass,new AssertClass(cls));
 */
 				} else if( "luan_proxy".equals(name) ) {
-					return new LuanJavaFunction(luan_proxyMethod,st);
+					return new LuanJavaFunction(luan,luan_proxyMethod,st);
 				} else {
 					List<Member> members = getStaticMembers(cls,name);
 					if( !members.isEmpty() ) {
-						return member(null,members);
+						return member(luan,null,members);
 					}
 				}
 			}
@@ -95,11 +100,11 @@
 			} else if( key instanceof String ) {
 				String name = (String)key;
 				if( "instanceof".equals(name) ) {
-					return new LuanJavaFunction(instanceOf,new InstanceOf(obj));
+					return new LuanJavaFunction(luan,instanceOf,new InstanceOf(obj));
 				} else {
 					List<Member> members = getMembers(cls,name);
 					if( !members.isEmpty() ) {
-						return member(obj,members);
+						return member(luan,obj,members);
 					}
 				}
 			}
@@ -108,7 +113,7 @@
 		throw new LuanException( "invalid index '"+key+"' for java "+cls );
 	}
 
-	private static Object member(Object obj,List<Member> members) throws LuanException {
+	private static Object member(Luan luan,Object obj,List<Member> members) throws LuanException {
 		try {
 			if( members.size()==1 ) {
 				Member member = members.get(0);
@@ -120,13 +125,13 @@
 					return rtn instanceof Object[] ? Arrays.asList((Object[])rtn) : rtn;
 				} else {
 					Method method = (Method)member;
-					return new LuanJavaFunction(method,obj);
+					return new LuanJavaFunction(luan,method,obj);
 				}
 			} else {
 				List<LuanJavaFunction> fns = new ArrayList<LuanJavaFunction>();
 				for( Member member : members ) {
 					Method method = (Method)member;
-					fns.add(new LuanJavaFunction(method,obj));
+					fns.add(new LuanJavaFunction(luan,method,obj));
 				}
 				return new AmbiguousJavaFunction(fns);
 			}
@@ -309,7 +314,7 @@
 			return cls.isSynthetic();
 		}
 
-		public Object luan_proxy(final Luan luan,final LuanTable t) throws LuanException {
+		public Object luan_proxy(final LuanTable t) throws LuanException {
 			return Proxy.newProxyInstance(
 				cls.getClassLoader(),
 				new Class[]{cls},
@@ -324,7 +329,7 @@
 						if( fnObj == null )
 							throw new NullPointerException("luan_proxy couldn't find method '"+name+"'");
 						LuanFunction fn = Luan.checkFunction(fnObj);
-						return Luan.first(fn.call(luan,args));
+						return Luan.first(fn.call(args));
 					}
 				}
 			);
@@ -333,7 +338,7 @@
 	private static final Method luan_proxyMethod;
 	static {
 		try {
-			luan_proxyMethod = Static.class.getMethod("luan_proxy",Luan.class,LuanTable.class);
+			luan_proxyMethod = Static.class.getMethod("luan_proxy",LuanTable.class);
 			luan_proxyMethod.setAccessible(true);
 		} catch(NoSuchMethodException e) {
 			throw new RuntimeException(e);
@@ -361,11 +366,12 @@
 		}
 	};
 
-	private static class AmbiguousJavaFunction extends LuanFunction {
-		private final Map<Integer,List<LuanJavaFunction>> fnMap = new HashMap<Integer,List<LuanJavaFunction>>();
+	private static final class AmbiguousJavaFunction extends LuanFunction {
+		private Map<Integer,List<LuanJavaFunction>> fnMap = new HashMap<Integer,List<LuanJavaFunction>>();
 		private List<LuanJavaFunction> varArgs = new ArrayList<LuanJavaFunction>();
 
 		AmbiguousJavaFunction(List<LuanJavaFunction> fns) {
+			super(true);
 			for( LuanJavaFunction fn : fns ) {
 				if( fn.isVarArgs() ) {
 					varArgs.add(fn);
@@ -382,18 +388,24 @@
 			Collections.sort(varArgs,varArgsSorter);
 		}
 
-		@Override public Object call(Luan luan,Object[] args) throws LuanException {
+		@Override protected void completeClone(LuanFunction dc,LuanCloner cloner) {
+			AmbiguousJavaFunction clone = (AmbiguousJavaFunction)dc;
+			clone.fnMap = (Map)cloner.clone(fnMap);
+			clone.varArgs = (List)cloner.clone(varArgs);
+		}
+
+		@Override public Object call(Object[] args) throws LuanException {
 			List<LuanJavaFunction> list = fnMap.get(args.length);
 			if( list != null ) {
 				for( LuanJavaFunction fn : list ) {
 					try {
-						return fn.rawCall(luan,args);
+						return fn.rawCall(args);
 					} catch(IllegalArgumentException e) {}
 				}
 			}
 			for( LuanJavaFunction fn : varArgs ) {
 				try {
-					return fn.rawCall(luan,args);
+					return fn.rawCall(args);
 				} catch(IllegalArgumentException e) {}
 			}
 			throw new LuanException("no method matched args: "+Arrays.asList(args));
--- a/src/luan/modules/PackageLuan.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/modules/PackageLuan.java	Thu Feb 14 03:10:45 2019 -0700
@@ -15,13 +15,17 @@
 
 public final class PackageLuan {
 
-	public static final LuanFunction requireFn;
-	static {
-		try {
-			requireFn = new LuanJavaFunction(PackageLuan.class.getMethod("require",Luan.class,String.class),null);
-		} catch(NoSuchMethodException e) {
-			throw new RuntimeException(e);
+	public static LuanFunction requireFn(Luan luan) {
+		LuanFunction fn = (LuanFunction)luan.registry().get("Package.require");
+		if( fn == null ) {
+			try {
+				fn = new LuanJavaFunction(luan,PackageLuan.class.getMethod("require",Luan.class,String.class),null);
+			} catch(NoSuchMethodException e) {
+				throw new RuntimeException(e);
+			}
+			luan.registry().put("Package.require",fn);
 		}
+		return fn;
 	}
 
 	public static LuanTable loaded(Luan luan) {
@@ -53,9 +57,9 @@
 				} catch(IOException e) {
 					throw new RuntimeException(e);
 				}
-				LuanFunction loader = Luan.load(src,modName);
+				LuanFunction loader = luan.load(src,modName);
 				mod = Luan.first(
-					loader.call(luan,new Object[]{modName})
+					loader.call(modName)
 				);
 				if( mod == null )
 					throw new RuntimeException();
@@ -68,9 +72,9 @@
 				if( src == null ) {
 					mod = Boolean.FALSE;
 				} else {
-					LuanFunction loader = Luan.load(src,modName);
+					LuanFunction loader = luan.load(src,modName);
 					mod = Luan.first(
-						loader.call(luan,new Object[]{modName})
+						loader.call(modName)
 					);
 					if( mod == null ) {
 						mod = loaded.rawGet(modName);
@@ -94,7 +98,7 @@
 		}
 		Luan.Security security = Luan.setSecurity(luan,null);
 		try {
-			return (String)Luan.first(boot.call("read",uri));
+			return (String)Luan.first(boot.fn("read").call(uri));
 		} catch(LuanException e) {
 			return null;
 		} finally {
@@ -103,17 +107,4 @@
 		}
 	}
 
-	public static void enableLoad(Luan luan,String... mods) throws LuanException {
-		if( !luan.isLocked )
-			return;
-		LuanTable loaded = loaded(luan);
-		for( String mod : mods ) {
-			if( loaded.rawGet(mod) == null ) {
-				luan.isLocked = false;
-				luan.deepenClone(luan,new LuanCloner(LuanCloner.Type.COMPLETE));
-				break;
-			}
-		}
-	}
-
 }
--- a/src/luan/modules/StringLuan.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/modules/StringLuan.java	Thu Feb 14 03:10:45 2019 -0700
@@ -7,7 +7,6 @@
 import luan.LuanTable;
 import luan.LuanFunction;
 import luan.LuanException;
-import luan.LuanMethod;
 
 
 public final class StringLuan {
@@ -30,7 +29,7 @@
 		return i==null ? dflt : end(s,i);
 	}
 
-	@LuanMethod public static Integer[] unicode(String s,Integer i,Integer j) throws LuanException {
+	public static Integer[] unicode(String s,Integer i,Integer j) throws LuanException {
 		Utils.checkNotNull(s);
 		int start = start(s,i,1);
 		int end = end(s,j,start+1);
@@ -49,7 +48,7 @@
 		return new String(a);
 	}
 
-	@LuanMethod public static byte[] to_binary(String s) {
+	public static byte[] to_binary(String s) {
 		return s.getBytes();
 	}
 
@@ -92,7 +91,7 @@
 		return s.substring(start,end);
 	}
 
-	@LuanMethod public static Object[] find(String s,String pattern,Integer init,Boolean plain) {
+	public static Object[] find(String s,String pattern,Integer init,Boolean plain) {
 		int start = start(s,init,0);
 		if( Boolean.TRUE.equals(plain) ) {
 			int i = s.indexOf(pattern,start);
@@ -111,7 +110,7 @@
 		return rtn;
 	}
 
-	@LuanMethod public static String[] match(String s,String pattern,Integer init) {
+	public static String[] match(String s,String pattern,Integer init) {
 		int start = start(s,init,0);
 		Matcher m = Pattern.compile(pattern).matcher(s);
 		if( !m.find(start) )
@@ -129,8 +128,8 @@
 	public static LuanFunction gmatch(String s,String pattern) throws LuanException {
 		Utils.checkNotNull(s);
 		final Matcher m = Pattern.compile(pattern).matcher(s);
-		return new LuanFunction() {
-			@Override public Object call(Luan luan,Object[] args) {
+		return new LuanFunction(false) {
+			@Override public Object call(Object[] args) {
 				if( !m.find() )
 					return null;
 				final int n = m.groupCount();
@@ -145,7 +144,7 @@
 		};
 	}
 
-	@LuanMethod public static Object[] gsub(Luan luan,String s,String pattern,Object repl,Integer n) throws LuanException {
+	public static Object[] gsub(String s,String pattern,Object repl,Integer n) throws LuanException {
 		Utils.checkNotNull(s);
 		int max = n==null ? Integer.MAX_VALUE : n;
 		final Matcher m = Pattern.compile(pattern).matcher(s);
@@ -191,7 +190,7 @@
 						args[j] = m.group(j+1);
 					}
 				}
-				Object val = Luan.first( fn.call(luan,args) );
+				Object val = Luan.first( fn.call(args) );
 				if( val != null ) {
 					String replacement = Luan.luanToString(val);
 					m.appendReplacement(sb,replacement);
@@ -238,7 +237,7 @@
 		return Pattern.compile(pattern).matcher(s).find();
 	}
 
-	@LuanMethod public static String[] split(String s,String pattern,Integer limit) throws LuanException {
+	public static String[] split(String s,String pattern,Integer limit) throws LuanException {
 		Utils.checkNotNull(s);
 		int n = limit==null ? -1 : limit;
 		return s.split(pattern,n);
--- a/src/luan/modules/TableLuan.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/modules/TableLuan.java	Thu Feb 14 03:10:45 2019 -0700
@@ -9,7 +9,6 @@
 import luan.LuanFunction;
 import luan.LuanException;
 import luan.LuanRuntimeException;
-import luan.LuanMethod;
 
 
 public final class TableLuan {
@@ -47,7 +46,7 @@
 		public boolean isLessThan(Object o1,Object o2);
 	}
 
-	public static void sort(final Luan luan,LuanTable list,final LuanFunction comp) throws LuanException {
+	public static void sort(LuanTable list,final LuanFunction comp) throws LuanException {
 		if( list.getMetatable() != null )
 			throw new LuanException("can't sort a table with a metatable");
 		final LessThan lt;
@@ -55,7 +54,7 @@
 			lt = new LessThan() {
 				public boolean isLessThan(Object o1,Object o2) {
 					try {
-						return luan.isLessThan(o1,o2);
+						return Luan.isLessThan(o1,o2);
 					} catch(LuanException e) {
 						throw new LuanRuntimeException(e);
 					}
@@ -65,7 +64,7 @@
 			lt = new LessThan() {
 				public boolean isLessThan(Object o1,Object o2) {
 					try {
-						return Luan.checkBoolean(Luan.first(comp.call(luan,new Object[]{o1,o2})));
+						return Luan.checkBoolean(Luan.first(comp.call(o1,o2)));
 					} catch(LuanException e) {
 						throw new LuanRuntimeException(e);
 					}
@@ -89,7 +88,7 @@
 		return tbl;
 	}
 
-	@LuanMethod public static Object[] unpack(LuanTable tbl,Integer iFrom,Integer iTo) throws LuanException {
+	public static Object[] unpack(LuanTable tbl,Integer iFrom,Integer iTo) throws LuanException {
 		int from = iFrom!=null ? iFrom : 1;
 		int to;
 		if( iTo != null ) {
@@ -105,7 +104,7 @@
 		return list.toArray();
 	}
 
-	public static LuanTable copy(Luan luan,LuanTable list,Integer from,Integer to) {
+	public static LuanTable copy(LuanTable list,Integer from,Integer to) {
 		if( from == null )
 			return new LuanTable(list);
 		if( to == null )
--- a/src/luan/modules/ThreadLuan.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/modules/ThreadLuan.java	Thu Feb 14 03:10:45 2019 -0700
@@ -23,13 +23,12 @@
 	private static final Executor exec = Executors.newCachedThreadPool();
 	public static final ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(1);
 
-	public static void fork(Luan luan,LuanFunction fn) {
+	public static void fork(LuanFunction fn) {
 		LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
-		final Luan newLuan = (Luan)cloner.clone(luan);
 		final LuanFunction newFn = (LuanFunction)cloner.get(fn);
 		exec.execute(new Runnable(){public void run() {
 			try {
-				newFn.call(newLuan);
+				newFn.call();
 			} catch(LuanException e) {
 				e.printStackTrace();
 			}
@@ -47,15 +46,16 @@
 		};
 	}
 */
-	public static void schedule(Luan luan,long delay,LuanFunction fn,String repeating)
+	public static void schedule(long delay,LuanFunction fn,String repeating)
 		throws LuanException
 	{
+		Luan luan = fn.luan();
 		LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
 		final Luan newLuan = (Luan)cloner.clone(luan);
 		final LuanFunction newFn = (LuanFunction)cloner.get(fn);
 		Runnable r = new Runnable(){public void run() {
 			try {
-				newFn.call(newLuan);
+				newFn.call();
 			} catch(LuanException e) {
 				e.printStackTrace();
 			}
@@ -166,7 +166,7 @@
 		private final Luan luan = new Luan();
 		private final LuanTable fns;
 
-		Callable(Luan luan,LuanTable fns) {
+		Callable(LuanTable fns) {
 			LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
 			this.fns = (LuanTable)cloner.get(fns);
 		}
@@ -182,7 +182,7 @@
 			if( !(f instanceof LuanFunction) )
 				throw new LuanException("value of '"+fnName+"' not a function in global_callable");
 			LuanFunction fn = (LuanFunction)f;
-			Object rtn = fn.call(luan,args);
+			Object rtn = fn.call(args);
 			rtn = makeSafe(callerLuan,rtn);
 			if( rtn instanceof Unsafe )
 				throw new LuanException("can't return "+((Unsafe)rtn).reason+" from global_callable");
@@ -201,11 +201,11 @@
 		}
 	}
 
-	public static synchronized Callable globalCallable(Luan luan,String name,LuanTable fns,long timeout) {
+	public static synchronized Callable globalCallable(String name,LuanTable fns,long timeout) {
 		Callable callable = callableMap.get(name);
 		if( callable == null ) {
 			sweep();
-			callable = new Callable(luan,fns);
+			callable = new Callable(fns);
 			callableMap.put(name,callable);
 		}
 		callable.expires = System.currentTimeMillis() + timeout;
--- a/src/luan/modules/http/HttpServicer.java	Tue Feb 12 22:53:57 2019 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-package luan.modules.http;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import luan.webserver.Request;
-import luan.webserver.Response;
-import luan.Luan;
-import luan.LuanFunction;
-import luan.LuanException;
-import luan.LuanTable;
-import luan.LuanCloner;
-import luan.modules.PackageLuan;
-
-
-public final class HttpServicer {
-	private static final Logger logger = LoggerFactory.getLogger(HttpServicer.class);
-
-	public static Response service(Luan luan,Request request,String modName)
-		throws LuanException
-	{
-		try {
-			return serviceLuan(luan,request,modName);
-		} catch(LuanException e) {
-			return handleError(luan,request,e);
-		}
-	}
-
-	private static Response handleError(Luan luan,Request request,LuanException e)
-		throws LuanException
-	{
-//e.printStackTrace();
-		synchronized(luan) {
-			LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL);
-			luan = (Luan)cloner.clone(luan);
-		}
-		LuanTable module = (LuanTable)luan.require("luan:http/Http.luan");
-		return (Response)module.call( "handle_error", request, e.table(luan) );
-	}
-
-	private static Response serviceLuan(Luan luan,Request request,String modName)
-		throws LuanException
-	{
-		LuanFunction fn;
-		synchronized(luan) {
-			PackageLuan.enableLoad(luan,"luan:http/Http.luan",modName);
-			PackageLuan.require(luan,"luan:http/Http.luan");
-			Object mod = PackageLuan.load(luan,modName);
-			if( mod.equals(Boolean.FALSE) )
-				return null;
-			if( !(mod instanceof LuanFunction) )
-				throw new LuanException( "module '"+modName+"' must return a function" );
-			LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL);
-			luan = (Luan)cloner.clone(luan);
-			fn = (LuanFunction)cloner.get(mod);
-		}
-
-		LuanTable module = (LuanTable)luan.require("luan:http/Http.luan");
-		module.call( "new_request", request );
-		module.call( "new_response" );
-
-		fn.call(luan);
-
-		return (Response)module.call( "finish" );
-	}
-
-	private HttpServicer() {}  // never
-}
--- a/src/luan/modules/http/LuanHandler.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/modules/http/LuanHandler.java	Thu Feb 14 03:10:45 2019 -0700
@@ -56,9 +56,9 @@
 		this.logger = LuanLogger.getLogger(luanInit,LuanHandler.class.getName());
 		try {
 			LuanTable Http = (LuanTable)luanInit.require("luan:http/Http.luan");
-			Http.put( "reset_luan", new LuanJavaFunction(resetLuanMethod,this) );
-			Http.put( "eval_in_root", new LuanJavaFunction(evalInRootMethod,this) );
-			Http.put( "disable_luan", new LuanJavaFunction(disableLuanMethod,this) );
+			Http.put( "reset_luan", new LuanJavaFunction(luanInit,resetLuanMethod,this) );
+			Http.put( "eval_in_root", new LuanJavaFunction(luanInit,evalInRootMethod,this) );
+			Http.put( "disable_luan", new LuanJavaFunction(luanInit,disableLuanMethod,this) );
 		} catch(LuanException e) {
 			throw new RuntimeException(e);
 		}
@@ -66,8 +66,11 @@
 	}
 
 	private Luan newLuan() {
-		LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
-		Luan luan = (Luan)cloner.clone(luanInit);
+		Luan luan;
+		synchronized(luanInit) {
+			LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
+			luan = (Luan)cloner.clone(luanInit);
+		}
 		luan.onClose = this;
 		try {
 			PackageLuan.load(luan,"site:/init.luan");
@@ -99,7 +102,7 @@
 		thread.setName(request.headers.get("host")+request.path);
 		lock.readLock().lock();
 		try {
-			return HttpServicer.service(currentLuan,request,modName);
+			return service(request,modName);
 		} catch(LuanException e) {
 			String err = e.getLuanStackTraceString();
 			logger.error(err+"\n"+request.rawHead.trim()+"\n");
@@ -137,19 +140,17 @@
 		lock.readLock().lock();
 		try {
 			LuanFunction fn;
-			Luan luan = currentLuan;
-			synchronized(luan) {
-				PackageLuan.enableLoad(luan,"luan:Rpc.luan");
-				LuanTable rpc = (LuanTable)luan.require("luan:Rpc.luan");
+			synchronized(luanInit) {
+				enableLoad("luan:Rpc.luan");
+				LuanTable rpc = (LuanTable)currentLuan.require("luan:Rpc.luan");
 				LuanTable fns = (LuanTable)rpc.get("functions");
 				fn = (LuanFunction)fns.get(fnName);
 				if( fn == null )
 					throw new LuanException( "function not found: " + fnName );
 				LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL);
-				luan = (Luan)cloner.clone(luan);
 				fn = (LuanFunction)cloner.get(fn);
 			}
-			return fn.call(luan,args);
+			return fn.call(args);
 		} finally {
 			lock.readLock().unlock();
 		}
@@ -176,12 +177,12 @@
 	public Object runLuan(String sourceText,String sourceName) throws LuanException {
 		lock.readLock().lock();
 		try {
-			LuanFunction fn = Luan.load(sourceText,sourceName);
 			Luan luan = currentLuan;
-			synchronized(luan) {
+			synchronized(luanInit) {
 				LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL);
 				Luan luan2 = (Luan)cloner.clone(luan);
-				return fn.call(luan2);
+				LuanFunction fn = luan2.load(sourceText,sourceName);
+				return fn.call();
 			}
 		} finally {
 			lock.readLock().unlock();
@@ -189,14 +190,13 @@
 	}
 
 	public void eval_in_root(String text) throws LuanException {
-		Luan oldLuan = currentLuan;
 		Luan luan;
-		synchronized(oldLuan) {
+		synchronized(luanInit) {
 			LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
-			luan = (Luan)cloner.clone(oldLuan);
+			luan = (Luan)cloner.clone(currentLuan);
 		}
 		luan.onClose = this;
-		BasicLuan.load(text,"<eval_in_root>",null).call(luan);
+		luan.load(text,"<eval_in_root>",null).call();
 		currentLuan = luan;
 	}
 
@@ -208,4 +208,69 @@
 		}
 	}
 
+
+
+	// from HttpServicer
+
+	private Response service(Request request,String modName)
+		throws LuanException
+	{
+		try {
+			return serviceLuan(request,modName);
+		} catch(LuanException e) {
+			return handleError(request,e);
+		}
+	}
+
+	private Response handleError(Request request,LuanException e)
+		throws LuanException
+	{
+//e.printStackTrace();
+		Luan luan;
+		synchronized(luanInit) {
+			LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL);
+			luan = (Luan)cloner.clone(currentLuan);
+		}
+		LuanTable module = (LuanTable)luan.require("luan:http/Http.luan");
+		return (Response)module.fn("handle_error").call( request, e.table(luan) );
+	}
+
+	private Response serviceLuan(Request request,String modName)
+		throws LuanException
+	{
+		LuanFunction fn;
+		Luan luan;
+		synchronized(luanInit) {
+			enableLoad("luan:http/Http.luan",modName);
+ 			PackageLuan.require(currentLuan,"luan:http/Http.luan");
+			Object mod = PackageLuan.load(currentLuan,modName);
+			if( mod.equals(Boolean.FALSE) )
+				return null;
+			if( !(mod instanceof LuanFunction) )
+				throw new LuanException( "module '"+modName+"' must return a function" );
+			LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL);
+			luan = (Luan)cloner.clone(currentLuan);
+			fn = (LuanFunction)cloner.get(mod);
+		}
+
+		LuanTable module = (LuanTable)luan.require("luan:http/Http.luan");
+		module.fn("new_request").call(request);
+		module.fn("new_response").call();
+
+		fn.call();
+
+		return (Response)module.fn("finish").call();
+	}
+
+	private void enableLoad(String... mods) throws LuanException {
+		LuanTable loaded = PackageLuan.loaded(currentLuan);
+		for( String mod : mods ) {
+			if( loaded.rawGet(mod) == null ) {
+				LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
+				currentLuan = (Luan)cloner.clone(currentLuan);
+				break;
+			}
+		}
+	}
+
 }
--- a/src/luan/modules/logging/Log4j.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/modules/logging/Log4j.java	Thu Feb 14 03:10:45 2019 -0700
@@ -19,7 +19,7 @@
 		LoggerRepository lr =  new Hierarchy(new RootLogger(Level.DEBUG));
 		luan.registry().put(KEY,lr);
 		LuanTable module = (LuanTable)luan.require("luan:logging/Log4j.luan");
-		module.call( "init_root" );
+		module.fn("init_root").call();
 	}
 
 	private static final LoggerRepository defaultLoggerRepository = LogManager.getLoggerRepository();
--- a/src/luan/modules/lucene/LuceneIndex.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/modules/lucene/LuceneIndex.java	Thu Feb 14 03:10:45 2019 -0700
@@ -200,7 +200,7 @@
 			for( Map.Entry<String,LuanFunction> entry : map.entrySet() ) {
 				String name = entry.getKey();
 				LuanFunction fn = entry.getValue();
-				Object value = Luan.first(fn.call(luan,new Object[]{doc}));
+				Object value = Luan.first(fn.call(doc));
 				doc.put( name, value );
 				indexedOnlySet.add(name);
 			}
@@ -230,11 +230,11 @@
 		}
 	}
 
-	public void update_in_transaction(Luan luan,LuanFunction fn) throws IOException, LuanException {
+	public void update_in_transaction(LuanFunction fn) throws IOException, LuanException {
 		boolean commit = !writeLock.isHeldByCurrentThread();
 		writeLock.lock();
 		try {
-			fn.call(luan);
+			fn.call();
 			if(commit) writer.commit();
 		} finally {
 			wrote();
@@ -242,13 +242,13 @@
 		}
 	}
 
-	public void run_in_lock(Luan luan,LuanFunction fn) throws IOException, LuanException {
+	public void run_in_lock(LuanFunction fn) throws IOException, LuanException {
 		if( writeLock.isHeldByCurrentThread() )
 			throw new RuntimeException();
 		writeLock.lock();
 		try {
 			synchronized(this) {
-				fn.call(luan);
+				fn.call();
 			}
 		} finally {
 			wrote();
@@ -318,7 +318,7 @@
 		try {
 			String dir = fileDir.toString();
 			LuanTable fileNames = new LuanTable(luan,new ArrayList(ic.getFileNames()));
-			return fn.call(luan,new Object[]{dir,fileNames});
+			return fn.call(dir,fileNames);
 		} finally {
 			snapshotDeletionPolicy.release(ic);
 		}
@@ -352,13 +352,14 @@
 		final IndexSearcher searcher;
 		int docID;
 
-		DocFn(IndexSearcher searcher) {
+		DocFn(Luan luan,IndexSearcher searcher) {
+			super(luan);
 			this.searcher = searcher;
 		}
 
-		@Override public Object call(Luan luan,Object[] args) throws LuanException {
+		@Override public Object call(Object[] args) throws LuanException {
 			try {
-				return toTable(luan,searcher.doc(docID));
+				return toTable(luan(),searcher.doc(docID));
 			} catch(IOException e) {
 				throw new LuanException(e);
 			}
@@ -413,12 +414,12 @@
 			if( fn!=null && n==null ) {
 				if( sortStr != null )
 					throw new LuanException("sort must be nil when n is nil");
-				final DocFn docFn = new DocFn(searcher);
+				final DocFn docFn = new DocFn(luan,searcher);
 				MyCollector col = new MyCollector() {
 					@Override public void collect(int doc) {
 						try {
 							docFn.docID = docBase + doc;
-							fn.call(luan,new Object[]{++i,docFn});
+							fn.call(++i,docFn);
 						} catch(LuanException e) {
 							throw new LuanRuntimeException(e);
 						}
@@ -439,10 +440,10 @@
 			Sort sort = sortStr==null ? null : SaneQueryParser.parseSort(mfp,sortStr);
 			TopDocs td = sort==null ? searcher.search(query,n) : searcher.search(query,n,sort);
 			final ScoreDoc[] scoreDocs = td.scoreDocs;
-			DocFn docFn = new DocFn(searcher);
+			DocFn docFn = new DocFn(luan,searcher);
 			for( int i=0; i<scoreDocs.length; i++ ) {
 				docFn.docID = scoreDocs[i].doc;
-				fn.call(luan,new Object[]{i+1,docFn});
+				fn.call(i+1,docFn);
 			}
 			return td.totalHits;
 		} finally {
@@ -451,13 +452,13 @@
 		}
 	}
 
-	public Object search_in_transaction(Luan luan,LuanFunction fn) throws LuanException, IOException {
+	public Object search_in_transaction(LuanFunction fn) throws LuanException, IOException {
 		if( threadLocalSearcher.get() != null )
 			throw new LuanException("can't nest search_in_transaction calls");
 		IndexSearcher searcher = openSearcher();
 		threadLocalSearcher.set(searcher);
 		try {
-			return fn.call(luan);
+			return fn.call();
 		} finally {
 			threadLocalSearcher.set(null);
 			close(searcher);
@@ -588,14 +589,14 @@
 		}
 	};
 
-	public LuanFunction highlighter(Luan luan,String queryStr,LuanFunction formatter,final Integer fragmentSize,String dotdotdot) throws ParseException {
+	public LuanFunction highlighter(String queryStr,LuanFunction formatter,final Integer fragmentSize,String dotdotdot) throws ParseException {
 		Query query = SaneQueryParser.parseQuery(mfp,queryStr);
 		Formatter fmt = new Formatter() {
 			public String highlightTerm(String originalText,TokenGroup tokenGroup) {
 			if( tokenGroup.getTotalScore() <= 0 )
 					return originalText;
 				try {
-					return (String)Luan.first(formatter.call(luan,new Object[]{originalText}));
+					return (String)Luan.first(formatter.call(originalText));
 				} catch(LuanException e) {
 					throw new LuanRuntimeException(e);
 				}
@@ -607,8 +608,8 @@
 			chooser.setTextFragmenter( new SimpleSpanFragmenter(queryScorer,fragmentSize) );
 		final Highlighter hl = new Highlighter(fmt,queryScorer);
 		hl.setTextFragmenter( new NullFragmenter() );
-		return new LuanFunction() {
-			@Override public String call(Luan luan,Object[] args) throws LuanException {
+		return new LuanFunction(false) {  // ???
+			@Override public String call(Object[] args) throws LuanException {
 				String text = (String)args[0];
 				try {
 					if( chooser != null ) {
--- a/src/luan/modules/parsers/BBCode.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/modules/parsers/BBCode.java	Thu Feb 14 03:10:45 2019 -0700
@@ -12,23 +12,21 @@
 
 public final class BBCode {
 
-	public static String toHtml(Luan luan,String bbcode,LuanFunction quoter) throws LuanException {
-		return new BBCode(luan,bbcode,quoter,true).parse();
+	public static String toHtml(String bbcode,LuanFunction quoter) throws LuanException {
+		return new BBCode(bbcode,quoter,true).parse();
 	}
 
-	public static String toText(Luan luan,String bbcode,LuanFunction quoter) throws LuanException {
-		return new BBCode(luan,bbcode,quoter,false).parse();
+	public static String toText(String bbcode,LuanFunction quoter) throws LuanException {
+		return new BBCode(bbcode,quoter,false).parse();
 	}
 
-	private final Luan luan;
 	private final Parser parser;
 	private final LuanFunction quoter;
 	private final boolean toHtml;
 
-	private BBCode(Luan luan,String text,LuanFunction quoter,boolean toHtml) throws LuanException {
+	private BBCode(String text,LuanFunction quoter,boolean toHtml) throws LuanException {
 		Utils.checkNotNull(text,1);
 //		Utils.checkNotNull(quoter,2);
-		this.luan = luan;
 		this.parser = new Parser(text);
 		this.quoter = quoter;
 		this.toHtml = toHtml;
@@ -285,7 +283,7 @@
 			else
 				return "";
 		}
-		Object obj = quoter.call(luan,args);
+		Object obj = quoter.call(args);
 		if( !(obj instanceof String) )
 			throw new LuanException("BBCode quoter function returned "+Luan.type(obj)+" but string required");
 		return (String)obj;
--- a/src/luan/modules/url/LuanUrl.java	Tue Feb 12 22:53:57 2019 -0700
+++ b/src/luan/modules/url/LuanUrl.java	Thu Feb 14 03:10:45 2019 -0700
@@ -195,19 +195,19 @@
 		return t==null ? null : t.asMap();
 	}
 
-	@Override public InputStream inputStream(Luan luan) throws IOException, LuanException {
+	@Override public InputStream inputStream() throws IOException, LuanException {
 		try {
-			return inputStream(luan,null);
+			return inputStream(null);
 		} catch(AuthException e) {
 			try {
-				return inputStream(luan,e.authorization);
+				return inputStream(e.authorization);
 			} catch(AuthException e2) {
 				throw new RuntimeException(e2);  // never
 			}
 		}
 	}
 
-	private InputStream inputStream(Luan luan,String authorization)
+	private InputStream inputStream(String authorization)
 		throws IOException, LuanException, AuthException
 	{
 		URLConnection con = url.openConnection();
@@ -238,12 +238,12 @@
 		HttpURLConnection httpCon = (HttpURLConnection)con;
 
 		if( method==Method.GET ) {
-			return getInputStream(luan,httpCon,authorization);
+			return getInputStream(httpCon,authorization);
 		}
 
 		if( method==Method.DELETE ) {
 			httpCon.setRequestMethod("DELETE");
-			return getInputStream(luan,httpCon,authorization);
+			return getInputStream(httpCon,authorization);
 		}
 
 		// POST
@@ -263,13 +263,13 @@
 		}
 		out.flush();
 		try {
-			return getInputStream(luan,httpCon,authorization);
+			return getInputStream(httpCon,authorization);
 		} finally {
 			out.close();
 		}
 	}
 
-	private InputStream getInputStream(Luan luan,HttpURLConnection httpCon,String authorization)
+	private InputStream getInputStream(HttpURLConnection httpCon,String authorization)
 		throws IOException, LuanException, AuthException
 	{
 		try {
@@ -335,9 +335,8 @@
 			String msg = Utils.readAll(in);
 			in.close();
 			LuanException le = new LuanException(msg,e);
-			LuanTable tbl = le.table(luan);
-			tbl.rawPut("response_code",responseCode);
-			tbl.rawPut("response_message",responseMessage);
+			le.put("response_code",responseCode);
+			le.put("response_message",responseMessage);
 			throw le;
 		}
 	}