changeset 1563:8fbcc4747091

remove LuanFunction.luan
author Franklin Schmidt <fschmidt@gmail.com>
date Mon, 09 Nov 2020 01:37:57 -0700
parents b89212fd04b5
children b1195cfe8712
files src/luan/Luan.java src/luan/LuanClosure.java src/luan/LuanException.java src/luan/LuanFunction.java src/luan/LuanJavaFunction.java src/luan/LuanTable.java src/luan/host/WebHandler.java src/luan/impl/Compiled.java src/luan/impl/LuanCompiler.java src/luan/impl/LuanImpl.java src/luan/impl/LuanParser.java src/luan/modules/BasicLuan.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/LuanDomainHandler.java src/luan/modules/http/LuanHandler.java src/luan/modules/logging/LuanLogger.java src/luan/modules/lucene/LuanOpDoer.java src/luan/modules/lucene/LuceneIndex.java src/luan/modules/lucene/PostgresBackup.java src/luan/modules/lucene/SupplementingConfig.java src/luan/modules/parsers/BBCode.java src/luan/modules/parsers/LuanToString.java src/luan/modules/parsers/Xml.java
diffstat 28 files changed, 361 insertions(+), 375 deletions(-) [+]
line wrap: on
line diff
--- a/src/luan/Luan.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/Luan.java	Mon Nov 09 01:37:57 2020 -0700
@@ -69,17 +69,17 @@
 	}
 
 	public Object eval(String cmd,Object... args) throws LuanException {
-		return load(cmd,"eval",false).call(args);
+		return load(cmd,"eval",false).call(this,args);
 	}
 
 	public Object require(String modName) throws LuanException {
 		return PackageLuan.require(this,modName);
 	}
 
-	public static String luanToString(Object obj) throws LuanException {
+	public String luanToString(Object obj) throws LuanException {
 		if( obj instanceof LuanTable ) {
 			LuanTable tbl = (LuanTable)obj;
-			return tbl.toStringLuan();
+			return tbl.toStringLuan(this);
 		}
 		if( obj == null )
 			return "nil";
@@ -101,7 +101,7 @@
 	}
 
 
-	public static boolean isLessThan(Object o1,Object o2) throws LuanException {
+	public boolean isLessThan(Object o1,Object o2) throws LuanException {
 		if( o1 instanceof Number && o2 instanceof Number ) {
 			Number n1 = (Number)o1;
 			Number n2 = (Number)o2;
@@ -114,7 +114,7 @@
 		}
 		LuanFunction fn = getBinHandler("__lt",o1,o2);
 		if( fn != null )
-			return Luan.checkBoolean( Luan.first(fn.call(o1,o2)) );
+			return Luan.checkBoolean( Luan.first(fn.call(this,o1,o2)) );
 		throw new LuanException( "attempt to compare " + Luan.type(o1) + " with " + Luan.type(o2) );
 	}
 
@@ -172,7 +172,7 @@
 	public static void main(String[] args) throws LuanException {
 		Luan luan = new Luan();
 		LuanFunction fn = loadClasspath(luan,"luan/cmd_line.luan");
-		fn.call((Object[])args);
+		fn.call(luan,(Object[])args);
 	}
 
 	public static LuanFunction loadClasspath(Luan luan,String classpath)
--- a/src/luan/LuanClosure.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/LuanClosure.java	Mon Nov 09 01:37:57 2020 -0700
@@ -3,31 +3,68 @@
 import luan.impl.Pointer;
 
 
-public abstract class LuanClosure extends LuanFunction {
+public abstract class LuanClosure extends LuanFunction implements LuanCloneable, Cloneable {
 	public Pointer[] upValues;
 	public boolean javaOk;
 	public final String sourceName;
+	private LuanCloner cloner;
+	private Luan luan;
 
-	public LuanClosure(Luan luan,Pointer[] upValues,boolean javaOk,String sourceName) throws LuanException {
-		super(luan);
+	public LuanClosure(Pointer[] upValues,boolean javaOk,String sourceName) throws LuanException {
 		this.upValues = upValues;
 		this.javaOk = javaOk;
 		this.sourceName = sourceName;
 	}
 
-	@Override protected void completeClone(LuanFunction dc,LuanCloner cloner) {
-		LuanClosure clone = (LuanClosure)dc;
-		clone.upValues = (Pointer[])cloner.clone(upValues);
-		super.completeClone(dc,cloner);
+	@Override public final LuanClosure shallowClone() {
+		check();
+		try {
+			return (LuanClosure)clone();
+		} catch(CloneNotSupportedException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private void check() {
+		if( cloner != null ) {
+			completeClone(this,cloner);
+			cloner = null;
+		}
+	}
+
+	private void checkLuan(Luan luan) {
+		check();
+		if( this.luan==null ) {
+			this.luan = luan;
+		} else if( this.luan != luan ) {
+			throw new RuntimeException("wrong luan");
+		}
 	}
 
-	@Override public void makeImmutable(LuanImmutabler immutabler) throws LuanException {
-		immutabler.makeImmutable(upValues);
-		super.makeImmutable(immutabler);
+	@Override public final void deepenClone(LuanCloneable dc,LuanCloner cloner) {
+		LuanClosure clone = (LuanClosure)dc;
+		switch( cloner.type ) {
+		case COMPLETE:
+			completeClone(clone,cloner);
+			return;
+		case INCREMENTAL:
+			clone.cloner = cloner;
+			return;
+		}
 	}
 
-	@Override public final Object call(Object... args) throws LuanException {
-		Luan luan = luan();
+	private void completeClone(LuanClosure dc,LuanCloner cloner) {
+		LuanClosure clone = (LuanClosure)dc;
+		clone.upValues = (Pointer[])cloner.clone(upValues);
+		clone.luan = (Luan)cloner.clone(luan);
+	}
+
+	@Override public final void makeImmutable(LuanImmutabler immutabler) throws LuanException {
+		immutabler.makeImmutable(upValues);
+	}
+
+	@Override public final Object call(Luan luan,Object... args) throws LuanException {
+		check();
 		luan.push(this);
 		try {
 			return doCall(luan,args);
--- a/src/luan/LuanException.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/LuanException.java	Mon Nov 09 01:37:57 2020 -0700
@@ -52,7 +52,7 @@
 		if( table==null ) {
 			try {
 				LuanTable Boot = (LuanTable)luan.require("luan:Boot.luan");
-				table = (LuanTable)Boot.fn(luan,"new_error_table").call(this );
+				table = (LuanTable)Boot.fn("new_error_table").call(luan,this );
 				for( Object stupid : extra.entrySet() ) {
 					Map.Entry entry = (Map.Entry)stupid;
 					table.put( luan, entry.getKey(), entry.getValue() );
--- a/src/luan/LuanFunction.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/LuanFunction.java	Mon Nov 09 01:37:57 2020 -0700
@@ -1,73 +1,9 @@
 package luan;
 
 
-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 class LuanFunction {
 
-	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);
-	}
-
-	@Override public void makeImmutable(LuanImmutabler immutabler) throws LuanException {
-		immutabler.makeImmutable(luan);
-	}
-
-	public Luan luan() {
-		check();
-		return luan;
-	}
-
-	public abstract Object call(Object... args) throws LuanException;
+	public abstract Object call(Luan luan,Object... args) throws LuanException;
 
 	public static final Object[] NOTHING = new Object[0];
 
--- a/src/luan/LuanJavaFunction.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/LuanJavaFunction.java	Mon Nov 09 01:37:57 2020 -0700
@@ -21,16 +21,15 @@
 	private final ArgConverter[] argConverters;
 	private final Class varArgCls;
 
-	public LuanJavaFunction(Luan luan,Method method,Object obj) {
-		this( luan, JavaMethod.of(method), obj );
+	public LuanJavaFunction(Method method,Object obj) {
+		this( JavaMethod.of(method), obj );
 	}
 
-	public LuanJavaFunction(Luan luan,Constructor constr,Object obj) {
-		this( luan, JavaMethod.of(constr), obj );
+	public LuanJavaFunction(Constructor constr,Object obj) {
+		this( JavaMethod.of(constr), obj );
 	}
 
-	private LuanJavaFunction(Luan luan,JavaMethod method,Object obj) {
-		super(luan);
+	private LuanJavaFunction(JavaMethod method,Object obj) {
 		this.method = method;
 		this.obj = obj;
 		this.rtnConverter = getRtnConverter(method);
@@ -42,8 +41,6 @@
 		} else {
 			this.varArgCls = null;
 		}
-		if( !takesLuan )
-			dontClone();
 	}
 
 	@Override public String toString() {
@@ -58,9 +55,9 @@
 		return method.isVarArgs();
 	}
 
-	@Override public Object call(Object[] args) throws LuanException {
+	@Override public Object call(Luan luan,Object[] args) throws LuanException {
 		try {
-			args = fixArgs(args);
+			args = fixArgs(luan,args);
 			return doCall(args);
 		} catch(IllegalArgumentException e) {
 			checkArgs(args);
@@ -68,8 +65,8 @@
 		}
 	}
 
-	public Object rawCall(Object[] args) throws LuanException {
-		args = fixArgs(args);
+	public Object rawCall(Luan luan,Object[] args) throws LuanException {
+		args = fixArgs(luan,args);
 		return doCall(args);
 	}
 
@@ -156,7 +153,7 @@
 		return type;
 	}
 
-	private Object[] fixArgs(Object[] args) throws LuanException {
+	private Object[] fixArgs(Luan luan,Object[] args) throws LuanException {
 		int n = argConverters.length;
 		Object[] rtn;
 		int start = 0;
@@ -167,7 +164,7 @@
 				n++;
 			rtn = new Object[n];
 			if( takesLuan ) {
-				rtn[start++] = luan();
+				rtn[start++] = luan;
 			}
 			n = argConverters.length;
 			if( varArgCls == null ) {
--- a/src/luan/LuanTable.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/LuanTable.java	Mon Nov 09 01:37:57 2020 -0700
@@ -20,6 +20,7 @@
 	public LuanClosure closure;
 	private LuanCloner cloner;
 	private boolean immutable = false;
+	private Luan luan;
 
 	public LuanTable() {}
 
@@ -99,11 +100,21 @@
 		}
 	}
 
+	private void checkLuan(Luan luan) {
+		check();
+		if( this.luan==null ) {
+			this.luan = luan;
+		} else if( this.luan != luan ) {
+			throw new RuntimeException("wrong luan");
+		}
+	}
+
 	private void completeClone(LuanTable clone,LuanCloner cloner) {
 		clone.map = cloner.clone(map);
 		clone.list = (List)cloner.clone(list);
 		clone.metatable = (LuanTable)cloner.clone(metatable);
 		clone.closure = (LuanClosure)cloner.clone(closure);
+		clone.luan = (Luan)cloner.clone(luan);
 	}
 
 	@Override public void makeImmutable(LuanImmutabler immutabler) throws LuanException {
@@ -133,12 +144,12 @@
 		return map!=null ? map : Collections.emptyMap();
 	}
 
-	public String toStringLuan() throws LuanException {
+	public String toStringLuan(Luan luan) throws LuanException {
 		Object h = getHandler("__to_string");
 		if( h == null )
 			return rawToString();
 		LuanFunction fn = Luan.checkFunction(h);
-		return Luan.checkString( Luan.first( fn.call(this) ) );
+		return Luan.checkString( Luan.first( fn.call(luan,this) ) );
 	}
 
 	public String rawToString() {
@@ -146,6 +157,7 @@
 	}
 
 	public Object get(Luan luan,Object key) throws LuanException {
+		//checkLuan(luan);
 		Object value = rawGet(key);
 		if( value != null )
 			return value;
@@ -154,7 +166,7 @@
 			return null;
 		if( h instanceof LuanFunction ) {
 			LuanFunction fn = (LuanFunction)h;
-			return Luan.first(fn.call(this,key));
+			return Luan.first(fn.call(luan,this,key));
 		}
 		return luan.index(h,key);
 	}
@@ -179,6 +191,7 @@
 	}
 
 	public void put(Luan luan,Object key,Object value) throws LuanException {
+		//checkLuan(luan);
 		Object h = getHandler("__new_index");
 		if( h==null || rawGet(key)!=null ) {
 			rawPut(key,value);
@@ -186,7 +199,7 @@
 		}
 		if( h instanceof LuanFunction ) {
 			LuanFunction fn = (LuanFunction)h;
-			fn.call(this,key,value);
+			fn.call(luan,this,key,value);
 			return;
 		}
 		if( h instanceof LuanTable ) {
@@ -295,11 +308,11 @@
 		Collections.sort(list(),cmp);
 	}
 
-	public int length() throws LuanException {
+	public int length(Luan luan) throws LuanException {
 		Object h = getHandler("__len");
 		if( h != null ) {
 			LuanFunction fn = Luan.checkFunction(h);
-			return (Integer)Luan.first(fn.call(this));
+			return (Integer)Luan.first(fn.call(luan,this));
 		}
 		return rawLength();
 	}
@@ -309,8 +322,8 @@
 		return list==null ? 0 : list.size();
 	}
 
-	public Iterable<Map.Entry> iterable() throws LuanException {
-		final Iterator<Map.Entry> iter = iterator();
+	public Iterable<Map.Entry> iterable(Luan luan) throws LuanException {
+		final Iterator<Map.Entry> iter = iterator(luan);
 		return new Iterable<Map.Entry>() {
 			public Iterator<Map.Entry> iterator() {
 				return iter;
@@ -327,16 +340,16 @@
 		};
 	}
 
-	public Iterator<Map.Entry> iterator() throws LuanException {
+	public Iterator<Map.Entry> iterator(final Luan luan) throws LuanException {
 		if( getHandler("__pairs") == null )
 			return rawIterator();
-		final LuanFunction fn = pairs();
+		final LuanFunction fn = pairs(luan);
 		return new Iterator<Map.Entry>() {
 			private Map.Entry<Object,Object> next = getNext();
 
 			private Map.Entry<Object,Object> getNext() {
 				try {
-					Object obj = fn.call();
+					Object obj = fn.call(luan);
 					if( obj==null )
 						return null;
 					Object[] a = (Object[])obj;
@@ -364,12 +377,12 @@
 		};
 	}
 
-	public LuanFunction pairs() throws LuanException {
+	public LuanFunction pairs(Luan luan) throws LuanException {
 		Object h = getHandler("__pairs");
 		if( h != null ) {
 			if( h instanceof LuanFunction ) {
 				LuanFunction fn = (LuanFunction)h;
-				Object obj = Luan.first(fn.call(this));
+				Object obj = Luan.first(fn.call(luan,this));
 				if( !(obj instanceof LuanFunction) )
 					throw new LuanException( "metamethod __pairs should return function but returned " + Luan.type(obj) );
 				return (LuanFunction)obj;
@@ -380,10 +393,10 @@
 	}
 
 	private LuanFunction rawPairs() {
-		return new LuanFunction(false) {  // ???
+		return new LuanFunction() {
 			final Iterator<Map.Entry> iter = rawIterator();
 
-			@Override public Object[] call(Object[] args) {
+			@Override public Object[] call(Luan luan,Object[] args) {
 				if( !iter.hasNext() )
 					return LuanFunction.NOTHING;
 				Map.Entry<Object,Object> entry = iter.next();
@@ -473,7 +486,7 @@
 	}
 
 	public boolean isSet() throws LuanException {
-		for( Map.Entry<Object,Object> entry : iterable() ) {
+		for( Map.Entry<Object,Object> entry : rawIterable() ) {
 			if( !entry.getValue().equals(Boolean.TRUE) )
 				return false;
 		}
@@ -482,7 +495,7 @@
 
 	public Set<Object> asSet() throws LuanException {
 		Set<Object> set = new HashSet<Object>();
-		for( Map.Entry<Object,Object> entry : iterable() ) {
+		for( Map.Entry<Object,Object> entry : rawIterable() ) {
 			set.add(entry.getKey());
 		}
 		return set;
@@ -490,7 +503,7 @@
 
 	public Map<Object,Object> asMap() throws LuanException {
 		Map<Object,Object> map = newMap();
-		for( Map.Entry<Object,Object> entry : iterable() ) {
+		for( Map.Entry<Object,Object> entry : rawIterable() ) {
 			map.put(entry.getKey(),entry.getValue());
 		}
 		return map;
@@ -536,13 +549,13 @@
 		Object h = getHandler("__gc");
 		if( h != null ) {
 			LuanFunction fn = Luan.checkFunction(h);
-			fn.call(this);
+			fn.call(new Luan(),this);  // ??? should be immutable
 		}
 		super.finalize();
 	}
 
-	public LuanFunction fn(Luan luan,String fnName) throws LuanException {
-		return (LuanFunction)get(luan,fnName);
+	public LuanFunction fn(String fnName) throws LuanException {
+		return (LuanFunction)rawGet(fnName);
 	}
 
 	public static void debug(LuanTable table) {
--- a/src/luan/host/WebHandler.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/host/WebHandler.java	Mon Nov 09 01:37:57 2020 -0700
@@ -68,7 +68,7 @@
 		LuanLogger.startThreadLogging(luan);
 		try {
 			LuanFunction fn = Luan.loadClasspath(luan,"luan/host/init.luan");
-			fn.call(dir,domain);
+			fn.call(luan,dir,domain);
 		} catch(LuanException e) {
 			throw new LuanRuntimeException(e);
 		} finally {
--- a/src/luan/impl/Compiled.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/impl/Compiled.java	Mon Nov 09 01:37:57 2020 -0700
@@ -133,7 +133,7 @@
 	}
 
 
-	private static final int VERSION = 5;
+	private static final int VERSION = 6;
 	private static final File tmpDir;
 	static {
 		File f = new File(System.getProperty("java.io.tmpdir"));
--- a/src/luan/impl/LuanCompiler.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/impl/LuanCompiler.java	Mon Nov 09 01:37:57 2020 -0700
@@ -30,7 +30,7 @@
 			javaOk = env.closure.javaOk;
 		LuanClosure closure;
 		try {
-			closure = (LuanClosure)fnClass.getConstructor(Luan.class,Boolean.TYPE,String.class).newInstance(luan,javaOk,sourceName);
+			closure = (LuanClosure)fnClass.getConstructor(Boolean.TYPE,String.class).newInstance(javaOk,sourceName);
 		} catch(NoSuchMethodException e) {
 			throw new RuntimeException(e);
 		} catch(InstantiationException e) {
--- a/src/luan/impl/LuanImpl.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/impl/LuanImpl.java	Mon Nov 09 01:37:57 2020 -0700
@@ -13,7 +13,7 @@
 public final class LuanImpl {
 	private LuanImpl() {}  // never
 
-	public static int len(Object o) throws LuanException {
+	public static int len(Luan luan,Object o) throws LuanException {
 		if( o instanceof String ) {
 			String s = (String)o;
 			return s.length();
@@ -24,80 +24,80 @@
 		}
 		if( o instanceof LuanTable ) {
 			LuanTable t = (LuanTable)o;
-			return t.length();
+			return t.length(luan);
 		}
 		throw new LuanException( "attempt to get length of a " + Luan.type(o) + " value" );
 	}
 
-	public static Object unm(Object o) throws LuanException {
+	public static Object unm(Luan luan,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(o));
+				return Luan.first(fn.call(luan,o));
 			}
 		}
 		throw new LuanException("attempt to perform arithmetic on a "+Luan.type(o)+" value");
 	}
 
-	private static Object arithmetic(String op,Object o1,Object o2) throws LuanException {
+	private static Object arithmetic(Luan luan,String op,Object o1,Object o2) throws LuanException {
 		LuanFunction fn = Luan.getBinHandler(op,o1,o2);
 		if( fn != null )
-			return Luan.first(fn.call(o1,o2));
+			return Luan.first(fn.call(luan,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(Object o1,Object o2) throws LuanException {
+	public static Object pow(Luan luan,Object o1,Object o2) throws LuanException {
 		if( o1 instanceof Number && o2 instanceof Number )
 			return Math.pow( ((Number)o1).doubleValue(), ((Number)o2).doubleValue() );
-		return arithmetic("__pow",o1,o2);
+		return arithmetic(luan,"__pow",o1,o2);
 	}
 
-	public static Object mul(Object o1,Object o2) throws LuanException {
+	public static Object mul(Luan luan,Object o1,Object o2) throws LuanException {
 		if( o1 instanceof Number && o2 instanceof Number )
 			return ((Number)o1).doubleValue() * ((Number)o2).doubleValue();
-		return arithmetic("__mul",o1,o2);
+		return arithmetic(luan,"__mul",o1,o2);
 	}
 
-	public static Object div(Object o1,Object o2) throws LuanException {
+	public static Object div(Luan luan,Object o1,Object o2) throws LuanException {
 		if( o1 instanceof Number && o2 instanceof Number )
 			return ((Number)o1).doubleValue() / ((Number)o2).doubleValue();
-		return arithmetic("__div",o1,o2);
+		return arithmetic(luan,"__div",o1,o2);
 	}
 
-	public static Object mod(Object o1,Object o2) throws LuanException {
+	public static Object mod(Luan luan,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("__mod",o1,o2);
+		return arithmetic(luan,"__mod",o1,o2);
 	}
 
-	public static Object add(Object o1,Object o2) throws LuanException {
+	public static Object add(Luan luan,Object o1,Object o2) throws LuanException {
 		if( o1 instanceof Number && o2 instanceof Number )
 			return ((Number)o1).doubleValue() + ((Number)o2).doubleValue();
-		return arithmetic("__add",o1,o2);
+		return arithmetic(luan,"__add",o1,o2);
 	}
 
-	public static Object sub(Object o1,Object o2) throws LuanException {
+	public static Object sub(Luan luan,Object o1,Object o2) throws LuanException {
 		if( o1 instanceof Number && o2 instanceof Number )
 			return ((Number)o1).doubleValue() - ((Number)o2).doubleValue();
-		return arithmetic("__sub",o1,o2);
+		return arithmetic(luan,"__sub",o1,o2);
 	}
 
-	public static Object concat(Object o1,Object o2) throws LuanException {
+	public static Object concat(Luan luan,Object o1,Object o2) throws LuanException {
 		LuanFunction fn = Luan.getBinHandler("__concat",o1,o2);
 		if( fn != null )
-			return Luan.first(fn.call(o1,o2));
-		String s1 = Luan.luanToString(o1);
-		String s2 = Luan.luanToString(o2);
+			return Luan.first(fn.call(luan,o1,o2));
+		String s1 = luan.luanToString(o1);
+		String s2 = luan.luanToString(o2);
 		return s1 + s2;
 	}
 
-	public static boolean eq(Object o1,Object o2) throws LuanException {
+	public static boolean eq(Luan luan,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(o1,o2)) );
+		return Luan.checkBoolean( Luan.first(fn.call(luan,o1,o2)) );
 	}
 
-	public static boolean le(Object o1,Object o2) throws LuanException {
+	public static boolean le(Luan luan,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(o1,o2)) );
+			return Luan.checkBoolean( Luan.first(fn.call(luan,o1,o2)) );
 		fn = Luan.getBinHandler("__lt",o1,o2);
 		if( fn != null )
-			return !Luan.checkBoolean( Luan.first(fn.call(o2,o1)) );
+			return !Luan.checkBoolean( Luan.first(fn.call(luan,o2,o1)) );
 		throw new LuanException( "attempt to compare " + Luan.type(o1) + " with " + Luan.type(o2) );
 	}
 
-	public static boolean lt(Object o1,Object o2) throws LuanException {
-		return Luan.isLessThan(o1,o2);
+	public static boolean lt(Luan luan,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	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/impl/LuanParser.java	Mon Nov 09 01:37:57 2020 -0700
@@ -376,7 +376,7 @@
 		Expr exp = new Expr(null,true);
 		exp.add( "Luan.checkFunction(" );
 		exp.addAll( fn.single() );
-		exp.add( ").call(" );
+		exp.add( ").call(luan," );
 		exp.addAll( args.array() );
 		exp.add( ")" );
 		return exp;
@@ -387,7 +387,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(" );
+		stmt.add( "Luan.checkFunction(luan.index(PackageLuan.require(luan,\"luan:Io.luan\"),\"template_write\")).call(luan," );
 		stmt.addAll( exprs.array() );
 		stmt.add( ");  " );
 		return stmt;
@@ -515,7 +515,7 @@
 
 		String fnVar = "fn" + ++forCounter;
 		Expr fnExp = new Expr(null,false);
-		fnExp.add( fnVar + ".call()" );
+		fnExp.add( fnVar + ".call(luan)" );
 		Stmts stmt = new Stmts();
 		stmt.add( ""
 			+"LuanFunction "+fnVar+" = Luan.checkFunction("
@@ -871,7 +871,7 @@
 				exp = exp.single();
 				Expr exp2 = required(ConcatExpr()).single();
 				Expr newExp = new Expr(Val.SINGLE,false);
-				newExp.add( "LuanImpl.eq(" );
+				newExp.add( "LuanImpl.eq(luan," );
 				newExp.addAll( exp );
 				newExp.add( "," );
 				newExp.addAll( exp2 );
@@ -882,7 +882,7 @@
 				exp = exp.single();
 				Expr exp2 = required(ConcatExpr()).single();
 				Expr newExp = new Expr(Val.SINGLE,false);
-				newExp.add( "!LuanImpl.eq(" );
+				newExp.add( "!LuanImpl.eq(luan," );
 				newExp.addAll( exp );
 				newExp.add( "," );
 				newExp.addAll( exp2 );
@@ -893,7 +893,7 @@
 				exp = exp.single();
 				Expr exp2 = required(ConcatExpr()).single();
 				Expr newExp = new Expr(Val.SINGLE,false);
-				newExp.add( "LuanImpl.le(" );
+				newExp.add( "LuanImpl.le(luan," );
 				newExp.addAll( exp );
 				newExp.add( "," );
 				newExp.addAll( exp2 );
@@ -904,7 +904,7 @@
 				exp = exp.single();
 				Expr exp2 = required(ConcatExpr()).single();
 				Expr newExp = new Expr(Val.SINGLE,false);
-				newExp.add( "LuanImpl.le(" );
+				newExp.add( "LuanImpl.le(luan," );
 				newExp.addAll( exp2 );
 				newExp.add( "," );
 				newExp.addAll( exp );
@@ -915,7 +915,7 @@
 				exp = exp.single();
 				Expr exp2 = required(ConcatExpr()).single();
 				Expr newExp = new Expr(Val.SINGLE,false);
-				newExp.add( "LuanImpl.lt(" );
+				newExp.add( "LuanImpl.lt(luan," );
 				newExp.addAll( exp );
 				newExp.add( "," );
 				newExp.addAll( exp2 );
@@ -926,7 +926,7 @@
 				exp = exp.single();
 				Expr exp2 = required(ConcatExpr()).single();
 				Expr newExp = new Expr(Val.SINGLE,false);
-				newExp.add( "LuanImpl.lt(" );
+				newExp.add( "LuanImpl.lt(luan," );
 				newExp.addAll( exp2 );
 				newExp.add( "," );
 				newExp.addAll( exp );
@@ -948,7 +948,7 @@
 			exp = exp.single();
 			Expr exp2 = required(ConcatExpr()).single();
 			Expr newExp = new Expr(Val.SINGLE,false);
-			newExp.add( "LuanImpl.concat(" );
+			newExp.add( "LuanImpl.concat(luan," );
 			newExp.addAll( exp );
 			newExp.add( "," );
 			newExp.addAll( exp2 );
@@ -969,7 +969,7 @@
 				exp = exp.single();
 				Expr exp2 = required(TermExpr()).single();
 				Expr newExp = new Expr(Val.SINGLE,false);
-				newExp.add( "LuanImpl.add(" );
+				newExp.add( "LuanImpl.add(luan," );
 				newExp.addAll( exp );
 				newExp.add( "," );
 				newExp.addAll( exp2 );
@@ -980,7 +980,7 @@
 				exp = exp.single();
 				Expr exp2 = required(TermExpr()).single();
 				Expr newExp = new Expr(Val.SINGLE,false);
-				newExp.add( "LuanImpl.sub(" );
+				newExp.add( "LuanImpl.sub(luan," );
 				newExp.addAll( exp );
 				newExp.add( "," );
 				newExp.addAll( exp2 );
@@ -1008,7 +1008,7 @@
 				exp = exp.single();
 				Expr exp2 = required(UnaryExpr()).single();
 				Expr newExp = new Expr(Val.SINGLE,false);
-				newExp.add( "LuanImpl.mul(" );
+				newExp.add( "LuanImpl.mul(luan," );
 				newExp.addAll( exp );
 				newExp.add( "," );
 				newExp.addAll( exp2 );
@@ -1019,7 +1019,7 @@
 				exp = exp.single();
 				Expr exp2 = required(UnaryExpr()).single();
 				Expr newExp = new Expr(Val.SINGLE,false);
-				newExp.add( "LuanImpl.div(" );
+				newExp.add( "LuanImpl.div(luan," );
 				newExp.addAll( exp );
 				newExp.add( "," );
 				newExp.addAll( exp2 );
@@ -1030,7 +1030,7 @@
 				exp = exp.single();
 				Expr exp2 = required(UnaryExpr()).single();
 				Expr newExp = new Expr(Val.SINGLE,false);
-				newExp.add( "LuanImpl.mod(" );
+				newExp.add( "LuanImpl.mod(luan," );
 				newExp.addAll( exp );
 				newExp.add( "," );
 				newExp.addAll( exp2 );
@@ -1053,7 +1053,7 @@
 			Spaces();
 			Expr exp = required(UnaryExpr()).single();
 			Expr newExp = new Expr(Val.SINGLE,false);
-			newExp.add( "LuanImpl.len(" );
+			newExp.add( "LuanImpl.len(luan," );
 			newExp.addAll( exp );
 			newExp.add( ")" );
 			return parser.success(newExp);
@@ -1062,7 +1062,7 @@
 			Spaces();
 			Expr exp = required(UnaryExpr()).single();
 			Expr newExp = new Expr(Val.SINGLE,false);
-			newExp.add( "LuanImpl.unm(" );
+			newExp.add( "LuanImpl.unm(luan," );
 			newExp.addAll( exp );
 			newExp.add( ")" );
 			return parser.success(newExp);
@@ -1091,7 +1091,7 @@
 			Spaces();
 			Expr exp2 = required(UnaryExpr());
 			Expr newExp = new Expr(Val.SINGLE,false);
-			newExp.add( "LuanImpl.pow(" );
+			newExp.add( "LuanImpl.pow(luan," );
 			newExp.addAll( exp1.single() );
 			newExp.add( "," );
 			newExp.addAll( exp2.single() );
@@ -2023,8 +2023,8 @@
 			+"import luan.modules.PackageLuan;  "
 
 			+"public class " + className +" extends LuanClosure {  "
-				+"public "+className+"(Luan luan,boolean javaOk,String sourceName) throws LuanException {  "
-					+"super(luan,"+toUpValues(upValueSymbols)+",javaOk,sourceName);  "
+				+"public "+className+"(boolean javaOk,String sourceName) throws LuanException {  "
+					+"super("+toUpValues(upValueSymbols)+",javaOk,sourceName);  "
 				+"}  "
 
 				+"@Override public Object doCall(Luan luan,Object[] args) throws LuanException {  "
@@ -2066,7 +2066,7 @@
 		Expr toInnerFnExp(List<UpSym> upValueSymbols) {
 			StringBuilder sb = new StringBuilder();
 			sb.append(
-				"new "+className+"(luan(),"+toUpValues(upValueSymbols)+",javaOk,sourceName)"
+				"new "+className+"("+toUpValues(upValueSymbols)+",javaOk,sourceName)"
 			);
 			for( int i=0; i<lines; i++ ) {
 				sb.append('\n');
@@ -2085,8 +2085,8 @@
 			StringBuilder sb = new StringBuilder();
 			sb.append( ""
 				+"private static class " + className +" extends LuanClosure {  "
-					+className+"(Luan luan,Pointer[] upValues,boolean javaOk,String sourceName) throws LuanException {  "
-						+"super(luan,upValues,javaOk,sourceName);  "
+					+className+"(Pointer[] upValues,boolean javaOk,String sourceName) throws LuanException {  "
+						+"super(upValues,javaOk,sourceName);  "
 					+"}  "
 					+"@Override public Object doCall(Luan luan,Object[] args) throws LuanException {  "
 						+"return _" + name + "(luan,args);  "
--- a/src/luan/modules/BasicLuan.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/modules/BasicLuan.java	Mon Nov 09 01:37:57 2020 -0700
@@ -15,6 +15,7 @@
 import luan.LuanFunction;
 import luan.LuanException;
 import luan.LuanCloner;
+import luan.LuanCloneable;
 import luan.LuanImmutabler;
 import luan.modules.parsers.LuanToString;
 
@@ -45,33 +46,39 @@
 		return load(luan,src,fileName,null);
 	}
 */
-	public static LuanFunction pairs(final LuanTable t) throws LuanException {
+	public static LuanFunction pairs(Luan luan,final LuanTable t) throws LuanException {
 		Utils.checkNotNull(t);
-		return t.pairs();
+		return t.pairs(luan);
 	}
 
-	private static class Ipairs extends LuanFunction {
+	private static class Ipairs extends LuanFunction implements LuanCloneable, Cloneable {
 		List<Object> list;
 		int i = 0;
 		final int size;
 
 		Ipairs(LuanTable t) {
-			super(true);
 			list = t.asList();
 			size = list.size();
 		}
 
-		@Override public Object[] call(Object[] args) {
+		@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 protected void completeClone(LuanFunction dc,LuanCloner cloner) {
+		@Override public final Ipairs shallowClone() {
+			try {
+				return (Ipairs)clone();
+			} catch(CloneNotSupportedException e) {
+				throw new RuntimeException(e);
+			}
+		}
+
+		@Override public final void deepenClone(LuanCloneable dc,LuanCloner cloner) {
 			Ipairs clone = (Ipairs)dc;
 			clone.list = (List)cloner.clone(list);
-			super.completeClone(dc,cloner);
 		}
 
 		@Override public void makeImmutable(LuanImmutabler immutabler) throws LuanException {
@@ -124,12 +131,12 @@
 		throw new LuanException( "bad argument #1 to 'raw_len' (table or string expected)" );
 	}
 
-	public static String to_string(Object v) throws LuanException {
-		return Luan.luanToString(v);
+	public static String to_string(Luan luan,Object v) throws LuanException {
+		return luan.luanToString(v);
 	}
 
 	public static LuanTable new_error(Luan luan,Object msg) throws LuanException {
-		String s = Luan.luanToString(msg);
+		String s = luan.luanToString(msg);
 		LuanTable tbl = new LuanException(s).table(luan);
 		tbl.rawPut( "message", msg );
 		return tbl;
@@ -155,10 +162,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(false) {
+		return new LuanFunction() {
 			double v = from;
 
-			@Override public Object call(Object[] args) {
+			@Override public Object call(Luan luan,Object[] args) {
 				if( step > 0.0 && v > to || step < 0.0 && v < to )
 					return LuanFunction.NOTHING;
 				double rtn = v;
@@ -168,26 +175,32 @@
 		};
 	}
 
-	private static class Values extends LuanFunction {
+	private static class Values extends LuanFunction implements LuanCloneable, Cloneable {
 		Object[] args;
 		int i = 0;
 
 		Values(Object[] args) {
-			super(true);
 			this.args = args;
 		}
 
-		@Override public Object[] call(Object[] x) {
+		@Override public Object[] call(Luan luan,Object[] x) {
 			if( i >= args.length )
 				return LuanFunction.NOTHING;
 			Object val = args[i++];
 			return new Object[]{i,val};
 		}
 
-		@Override protected void completeClone(LuanFunction dc,LuanCloner cloner) {
+		@Override public final Values shallowClone() {
+			try {
+				return (Values)clone();
+			} catch(CloneNotSupportedException e) {
+				throw new RuntimeException(e);
+			}
+		}
+
+		@Override public final void deepenClone(LuanCloneable dc,LuanCloner cloner) {
 			Values clone = (Values)dc;
 			clone.args = (Object[])cloner.clone(args);
-			super.completeClone(dc,cloner);
 		}
 
 		@Override public void makeImmutable(LuanImmutabler immutabler) throws LuanException {
@@ -218,8 +231,8 @@
 		}
 	}
 
-	public static String stringify(Luan luan,Object obj,LuanTable options,LuanTable subOptions) throws LuanException {
-		LuanToString lts = new LuanToString(luan,options,subOptions);
+	public static String stringify(Object obj,LuanTable options,LuanTable subOptions) throws LuanException {
+		LuanToString lts = new LuanToString(options,subOptions);
 		return lts.toString(obj);
 	}
 
--- a/src/luan/modules/IoLuan.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/modules/IoLuan.java	Mon Nov 09 01:37:57 2020 -0700
@@ -50,7 +50,7 @@
 
 	public interface LuanWriter {
 		public Object out();
-		public void write(Object... args) throws LuanException, IOException;
+		public void write(Luan luan,Object... args) throws LuanException, IOException;
 		public void close() throws IOException;
 	}
 
@@ -61,9 +61,9 @@
 				return out;
 			}
 
-			public void write(Object... args) throws LuanException {
+			public void write(Luan luan,Object... args) throws LuanException {
 				for( Object obj : args ) {
-					out.print( Luan.luanToString(obj) );
+					out.print( luan.luanToString(obj) );
 				}
 			}
 
@@ -80,9 +80,9 @@
 				return out;
 			}
 
-			public void write(Object... args) throws LuanException, IOException {
+			public void write(Luan luan,Object... args) throws LuanException, IOException {
 				for( Object obj : args ) {
-					out.write( Luan.luanToString(obj) );
+					out.write( luan.luanToString(obj) );
 				}
 			}
 
@@ -93,8 +93,8 @@
 	}
 
 	static LuanFunction lines(final BufferedReader in) {
-		return new LuanFunction(false) {
-			@Override public Object call(Object[] args) throws LuanException {
+		return new LuanFunction() {
+			@Override public Object call(Luan luan,Object[] args) throws LuanException {
 				try {
 					if( args.length > 0 ) {
 						if( args.length > 1 || !"close".equals(args[0]) )
@@ -114,10 +114,10 @@
 	}
 
 	static LuanFunction blocks(final InputStream in,final int blockSize) {
-		return new LuanFunction(false) {
+		return new LuanFunction() {
 			final byte[] a = new byte[blockSize];
 
-			@Override public Object call(Object[] args) throws LuanException {
+			@Override public Object call(Luan luan,Object[] args) throws LuanException {
 				try {
 					if( args.length > 0 ) {
 						if( args.length > 1 || !"close".equals(args[0]) )
@@ -290,9 +290,9 @@
 			return new BufferedOutputStream(outputStream());
 		}
 
-		public void write_text(Object... args) throws LuanException, IOException {
+		public void write_text(Luan luan,Object... args) throws LuanException, IOException {
 			LuanWriter luanWriter = text_writer();
-			luanWriter.write(args);
+			luanWriter.write(luan,args);
 			luanWriter.close();
 		}
 
@@ -375,9 +375,9 @@
 					return out;
 				}
 	
-				public void write(Object... args) throws LuanException, IOException {
+				public void write(Luan luan,Object... args) throws LuanException, IOException {
 					for( Object obj : args ) {
-						out.write( Luan.luanToString(obj) );
+						out.write( luan.luanToString(obj) );
 					}
 				}
 	
--- a/src/luan/modules/JavaLuan.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/modules/JavaLuan.java	Mon Nov 09 01:37:57 2020 -0700
@@ -52,11 +52,11 @@
 					Constructor[] constructors = cls.getConstructors();
 					if( constructors.length > 0 ) {
 						if( constructors.length==1 ) {
-							return new LuanJavaFunction(luan,constructors[0],null);
+							return new LuanJavaFunction(constructors[0],null);
 						} else {
 							List<LuanJavaFunction> fns = new ArrayList<LuanJavaFunction>();
 							for( Constructor constructor : constructors ) {
-								fns.add(new LuanJavaFunction(luan,constructor,null));
+								fns.add(new LuanJavaFunction(constructor,null));
 							}
 							return new AmbiguousJavaFunction(fns);
 						}
@@ -66,11 +66,11 @@
 					return new LuanJavaFunction(assertClass,new AssertClass(cls));
 */
 				} else if( "luan_proxy".equals(name) ) {
-					return new LuanJavaFunction(luan,luan_proxyMethod,st);
+					return new LuanJavaFunction(luan_proxyMethod,st);
 				} else {
 					List<Member> members = getStaticMembers(cls,name);
 					if( !members.isEmpty() ) {
-						return member(luan,null,members);
+						return member(null,members);
 					}
 				}
 			}
@@ -88,11 +88,11 @@
 			} else if( key instanceof String ) {
 				String name = (String)key;
 				if( "instanceof".equals(name) ) {
-					return new LuanJavaFunction(luan,instanceOf,new InstanceOf(obj));
+					return new LuanJavaFunction(instanceOf,new InstanceOf(obj));
 				} else {
 					List<Member> members = getMembers(cls,name);
 					if( !members.isEmpty() ) {
-						return member(luan,obj,members);
+						return member(obj,members);
 					}
 				}
 			}
@@ -101,7 +101,7 @@
 		throw new LuanException( "invalid index '"+key+"' for java "+cls );
 	}
 
-	private static Object member(Luan luan,Object obj,List<Member> members) throws LuanException {
+	private static Object member(Object obj,List<Member> members) throws LuanException {
 		try {
 			if( members.size()==1 ) {
 				Member member = members.get(0);
@@ -113,13 +113,13 @@
 					return rtn instanceof Object[] ? Arrays.asList((Object[])rtn) : rtn;
 				} else {
 					Method method = (Method)member;
-					return new LuanJavaFunction(luan,method,obj);
+					return new LuanJavaFunction(method,obj);
 				}
 			} else {
 				List<LuanJavaFunction> fns = new ArrayList<LuanJavaFunction>();
 				for( Member member : members ) {
 					Method method = (Method)member;
-					fns.add(new LuanJavaFunction(luan,method,obj));
+					fns.add(new LuanJavaFunction(method,obj));
 				}
 				return new AmbiguousJavaFunction(fns);
 			}
@@ -302,7 +302,7 @@
 			return cls.isSynthetic();
 		}
 
-		public Object luan_proxy(Luan luan,final LuanTable t) throws LuanException {
+		public Object luan_proxy(final Luan luan,final LuanTable t) throws LuanException {
 			return Proxy.newProxyInstance(
 				cls.getClassLoader(),
 				new Class[]{cls},
@@ -317,7 +317,7 @@
 						if( fnObj == null )
 							throw new NullPointerException("luan_proxy couldn't find method '"+name+"'");
 						LuanFunction fn = Luan.checkFunction(fnObj);
-						return Luan.first(fn.call(args));
+						return Luan.first(fn.call(luan,args));
 					}
 				}
 			);
@@ -359,7 +359,6 @@
 		private List<LuanJavaFunction> varArgs = new ArrayList<LuanJavaFunction>();
 
 		AmbiguousJavaFunction(List<LuanJavaFunction> fns) {
-			super(true);
 			for( LuanJavaFunction fn : fns ) {
 				if( fn.isVarArgs() ) {
 					varArgs.add(fn);
@@ -376,31 +375,18 @@
 			Collections.sort(varArgs,varArgsSorter);
 		}
 
-		@Override protected void completeClone(LuanFunction dc,LuanCloner cloner) {
-			AmbiguousJavaFunction clone = (AmbiguousJavaFunction)dc;
-			clone.fnMap = (Map)cloner.clone(fnMap);
-			clone.varArgs = (List)cloner.clone(varArgs);
-			// no call to super?
-		}
-
-		@Override public void makeImmutable(LuanImmutabler immutabler) throws LuanException {
-			immutabler.makeImmutable(fnMap);
-			immutabler.makeImmutable(varArgs);
-			super.makeImmutable(immutabler);
-		}
-
-		@Override public Object call(Object[] args) throws LuanException {
+		@Override public Object call(Luan luan,Object[] args) throws LuanException {
 			List<LuanJavaFunction> list = fnMap.get(args.length);
 			if( list != null ) {
 				for( LuanJavaFunction fn : list ) {
 					try {
-						return fn.rawCall(args);
+						return fn.rawCall(luan,args);
 					} catch(IllegalArgumentException e) {}
 				}
 			}
 			for( LuanJavaFunction fn : varArgs ) {
 				try {
-					return fn.rawCall(args);
+					return fn.rawCall(luan,args);
 				} catch(IllegalArgumentException e) {}
 			}
 			throw new LuanException("no method matched args: "+Arrays.asList(args));
--- a/src/luan/modules/PackageLuan.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/modules/PackageLuan.java	Mon Nov 09 01:37:57 2020 -0700
@@ -20,7 +20,7 @@
 		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);
+				fn = new LuanJavaFunction(PackageLuan.class.getMethod("require",Luan.class,String.class),null);
 			} catch(NoSuchMethodException e) {
 				throw new RuntimeException(e);
 			}
@@ -64,7 +64,7 @@
 				}
 				LuanFunction loader = luan.load(src,modName,true);
 				mod = Luan.first(
-					loader.call(modName)
+					loader.call(luan,modName)
 				);
 				if( mod == null )
 					throw new RuntimeException();
@@ -79,7 +79,7 @@
 				} else {
 					LuanFunction loader = luan.load(src,modName,true);
 					mod = Luan.first(
-						loader.call(modName)
+						loader.call(luan,modName)
 					);
 					if( mod == null ) {
 						mod = loaded.rawGet(modName);
@@ -103,7 +103,7 @@
 		}
 		Luan.Security security = Luan.setSecurity(luan,null);
 		try {
-			return (String)Luan.first(boot.fn(luan,"read").call(uri));
+			return (String)Luan.first(boot.fn("read").call(luan,uri));
 		} catch(LuanException e) {
 			return null;
 		} finally {
--- a/src/luan/modules/StringLuan.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/modules/StringLuan.java	Mon Nov 09 01:37:57 2020 -0700
@@ -129,8 +129,8 @@
 		Utils.checkNotNull(s);
 		Utils.checkNotNull(pattern,2);
 		final Matcher m = Pattern.compile(pattern).matcher(s);
-		return new LuanFunction(false) {
-			@Override public Object call(Object[] args) {
+		return new LuanFunction() {
+			@Override public Object call(Luan luan,Object[] args) {
 				if( !m.find() )
 					return null;
 				final int n = m.groupCount();
@@ -168,7 +168,7 @@
 				String match = m.groupCount()==0 ? m.group() : m.group(1);
 				Object val = t.get(luan,match);
 				if( val != null ) {
-					String replacement = Luan.luanToString(val);
+					String replacement = luan.luanToString(val);
 					m.appendReplacement(sb,replacement);
 				}
 				i++;
@@ -191,9 +191,9 @@
 						args[j] = m.group(j+1);
 					}
 				}
-				Object val = Luan.first( fn.call(args) );
+				Object val = Luan.first( fn.call(luan,args) );
 				if( val != null ) {
-					String replacement = Luan.luanToString(val);
+					String replacement = luan.luanToString(val);
 					m.appendReplacement(sb,replacement);
 				}
 				i++;
--- a/src/luan/modules/TableLuan.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/modules/TableLuan.java	Mon Nov 09 01:37:57 2020 -0700
@@ -15,7 +15,7 @@
 
 	public static String concat(Luan luan,LuanTable list,String sep,Integer i,Integer j) throws LuanException {
 		int first = i==null ? 1 : i;
-		int last = j==null ? list.length() : j;
+		int last = j==null ? list.length(luan) : j;
 		StringBuilder buf = new StringBuilder();
 		for( int k=first; k<=last; k++ ) {
 			Object val = list.get(luan,k);
@@ -23,7 +23,7 @@
 				break;
 			if( sep!=null && k > first )
 				buf.append(sep);
-			String s = Luan.luanToString(val);
+			String s = luan.luanToString(val);
 			buf.append(s);
 		}
 		return buf.toString();
@@ -46,7 +46,7 @@
 		public boolean isLessThan(Object o1,Object o2);
 	}
 
-	public static void sort(LuanTable list,final LuanFunction comp) throws LuanException {
+	public static void sort(Luan luan,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;
@@ -54,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);
 					}
@@ -64,7 +64,7 @@
 			lt = new LessThan() {
 				public boolean isLessThan(Object o1,Object o2) {
 					try {
-						return Luan.checkBoolean(Luan.first(comp.call(o1,o2)));
+						return Luan.checkBoolean(Luan.first(comp.call(luan,o1,o2)));
 					} catch(LuanException e) {
 						throw new LuanRuntimeException(e);
 					}
@@ -95,7 +95,7 @@
 			to = iTo;
 		} else {
 			Integer n = Luan.asInteger( tbl.get(luan,"n") );
-			to = n!=null ? n : tbl.length();
+			to = n!=null ? n : tbl.length(luan);
 		}
 		List<Object> list = new ArrayList<Object>();
 		for( int i=from; i<=to; i++ ) {
--- a/src/luan/modules/ThreadLuan.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/modules/ThreadLuan.java	Mon Nov 09 01:37:57 2020 -0700
@@ -30,12 +30,12 @@
 	private static final Executor exec = Executors.newCachedThreadPool();
 	public static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
 
-	private static Runnable runnable(final LuanFunction fn) {
+	private static Runnable runnable(final Luan luan,final LuanFunction fn) {
 		return new Runnable() {
 			public synchronized void run() {
-				LuanLogger.startThreadLogging(fn.luan());
+				LuanLogger.startThreadLogging(luan);
 				try {
-					fn.call();
+					fn.call(luan);
 				} catch(LuanException e) {
 					e.printStackTrace();
 				} finally {
@@ -45,10 +45,11 @@
 		};
 	}
 
-	public static void fork(LuanFunction fn) {
+	public static void fork(Luan luan,LuanFunction fn) {
 		LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
-		final LuanFunction newFn = (LuanFunction)cloner.get(fn);
-		exec.execute(runnable(newFn));
+		luan = (Luan)cloner.get(luan);
+		fn = (LuanFunction)cloner.get(fn);
+		exec.execute(runnable(luan,fn));
 	}
 
 	private static final Map<String,ScheduledFuture> scheduleds = new WeakCacheMap<String,ScheduledFuture>();
@@ -59,7 +60,7 @@
 			logger.error(src+" cancel="+b+" isCancelled="+sf.isCancelled()+" isDone="+sf.isDone()+" "+sf);
 	}
 
-	public static synchronized void schedule(LuanFunction fn,LuanTable options)
+	public static synchronized void schedule(Luan luan,LuanFunction fn,LuanTable options)
 		throws LuanException
 	{
 		options = new LuanTable(options);
@@ -76,11 +77,10 @@
 			if( sf != null )
 				cancel(sf,"id "+id);
 		}
-		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);
-		final Runnable r = runnable(newFn);
+		final Runnable r = runnable(newLuan,newFn);
 		final ScheduledFuture sf;
 		if( repeatingDelay != null ) {
 			if( delay==null )
@@ -210,7 +210,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(args);
+			Object rtn = fn.call(luan,args);
 			rtn = makeSafe(rtn);
 			if( rtn instanceof Unsafe )
 				throw new LuanException("can't return "+((Unsafe)rtn).reason+" from global_callable");
@@ -245,13 +245,13 @@
 	}
 
 
-	public static Object runInLock(Lock lock,long timeout,LuanFunction fn,Object... args)
+	public static Object runInLock(Luan luan,Lock lock,long timeout,LuanFunction fn,Object... args)
 		throws LuanException, InterruptedException
 	{
 		if( !lock.tryLock(timeout,TimeUnit.MILLISECONDS) )
 			throw new LuanException("failed to acquire lock");
 		try {
-			return fn.call(args);
+			return fn.call(luan,args);
 		} finally {
 			lock.unlock();
 		}
--- a/src/luan/modules/http/LuanDomainHandler.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/modules/http/LuanDomainHandler.java	Mon Nov 09 01:37:57 2020 -0700
@@ -31,8 +31,8 @@
 	protected Luan newLuan(final String domain) {
 		LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
 		Luan luan = (Luan)cloner.clone(luanInit);
-		LuanFunction reset_luan = new LuanFunction(false) {
-			@Override public Object call(Object[] args) {
+		LuanFunction reset_luan = new LuanFunction() {
+			@Override public Object call(Luan luan,Object[] args) {
 				domainHandler.removeHandler(domain);
 				return LuanFunction.NOTHING;
 			}
--- a/src/luan/modules/http/LuanHandler.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/modules/http/LuanHandler.java	Mon Nov 09 01:37:57 2020 -0700
@@ -67,10 +67,10 @@
 			Fns fns = new Fns(this);
 			LuanTable Http = (LuanTable)luanInit.require("luan:http/Http.luan");
 			if( Http.get(luanInit,"reset_luan") == null )
-				Http.put( luanInit, "reset_luan", new LuanJavaFunction(luanInit,resetLuanMethod,fns) );
-			Http.put( luanInit, "eval_in_root", new LuanJavaFunction(luanInit,evalInRootMethod,fns) );
-			Http.put( luanInit, "disable_luan", new LuanJavaFunction(luanInit,disableLuanMethod,fns) );
-			Http.put( luanInit, "dont_gc", new LuanJavaFunction(luanInit,dontGcMethod,fns) );
+				Http.put( luanInit, "reset_luan", new LuanJavaFunction(resetLuanMethod,fns) );
+			Http.put( luanInit, "eval_in_root", new LuanJavaFunction(evalInRootMethod,fns) );
+			Http.put( luanInit, "disable_luan", new LuanJavaFunction(disableLuanMethod,fns) );
+			Http.put( luanInit, "dont_gc", new LuanJavaFunction(dontGcMethod,fns) );
 		} catch(LuanException e) {
 			throw new RuntimeException(e);
 		}
@@ -153,7 +153,7 @@
 				LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL);
 				fn = (LuanFunction)cloner.get(fn);
 			}
-			return fn.call(args);
+			return fn.call(luan,args);
 		} finally {
 			LuanLogger.endThreadLogging();
 			rwLock.readLock().unlock();
@@ -190,7 +190,7 @@
 			LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
 			luan = (Luan)cloner.clone(currentLuan);
 		}
-		luan.load(text,"<eval_in_root>",false,null).call();
+		luan.load(text,"<eval_in_root>",false,null).call(luan);
 		currentLuan = luan;
 	}
 
@@ -256,7 +256,7 @@
 			luan = (Luan)cloner.clone(currentLuan);
 		}
 		LuanTable module = (LuanTable)luan.require("luan:http/Http.luan");
-		return (Response)module.fn(luan,"handle_error").call( request, e.table(luan) );
+		return (Response)module.fn("handle_error").call( luan, request, e.table(luan) );
 	}
 
 	private Response serviceLuan(Request request)
@@ -278,10 +278,10 @@
 			fn = (LuanFunction)cloner.get(mod);
 		}
 		LuanTable module = (LuanTable)luan.require("luan:http/Http.luan");
-		module.fn(luan,"new_request").call(request);
-		module.fn(luan,"new_response").call();
-		fn.call();
-		return (Response)module.fn(luan,"finish").call();
+		module.fn("new_request").call(luan,request);
+		module.fn("new_response").call(luan);
+		fn.call(luan);
+		return (Response)module.fn("finish").call(luan);
 	}
 
 	private Response serviceNotFound(Request request)
@@ -292,7 +292,7 @@
 		synchronized(luanInit) {
 			enableLoad("luan:http/Http.luan");
  			LuanTable module = (LuanTable)currentLuan.require("luan:http/Http.luan");
-			fn = module.fn(currentLuan,"not_found_handler");
+			fn = module.fn("not_found_handler");
 			if( fn == null )
 				return null;
 			LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL);
@@ -300,13 +300,13 @@
 			fn = (LuanFunction)cloner.get(fn);
 		}
 		LuanTable module = (LuanTable)luan.require("luan:http/Http.luan");
-		module.fn(luan,"new_request").call(request);
-		module.fn(luan,"new_response").call();
-		Object obj = Luan.first(fn.call());
+		module.fn("new_request").call(luan,request);
+		module.fn("new_response").call(luan);
+		Object obj = Luan.first(fn.call(luan));
 		if( !(obj instanceof Boolean) )
 			throw new LuanException("not_found_handler must return boolean");
 		boolean handled = (Boolean)obj;
-		return handled ? (Response)module.fn(luan,"finish").call() : null;
+		return handled ? (Response)module.fn("finish").call(luan) : null;
 	}
 
 	private void enableLoad(String... mods) throws LuanException {
--- a/src/luan/modules/logging/LuanLogger.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/modules/logging/LuanLogger.java	Mon Nov 09 01:37:57 2020 -0700
@@ -19,20 +19,20 @@
 		this.logger = LoggerFactory.getLogger(name);
 	}
 
-	public void error(Object obj) throws LuanException {
-		logger.error( Luan.luanToString(obj) );
+	public void error(Luan luan,Object obj) throws LuanException {
+		logger.error( luan.luanToString(obj) );
 	}
 
-	public void warn(Object obj) throws LuanException {
-		logger.warn( Luan.luanToString(obj) );
+	public void warn(Luan luan,Object obj) throws LuanException {
+		logger.warn( luan.luanToString(obj) );
 	}
 
-	public void info(Object obj) throws LuanException {
-		logger.info( Luan.luanToString(obj) );
+	public void info(Luan luan,Object obj) throws LuanException {
+		logger.info( luan.luanToString(obj) );
 	}
 
-	public void debug(Object obj) throws LuanException {
-		logger.debug( Luan.luanToString(obj) );
+	public void debug(Luan luan,Object obj) throws LuanException {
+		logger.debug( luan.luanToString(obj) );
 	}
 
 
--- a/src/luan/modules/lucene/LuanOpDoer.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/modules/lucene/LuanOpDoer.java	Mon Nov 09 01:37:57 2020 -0700
@@ -6,63 +6,66 @@
 import goodjava.lucene.api.GoodIndexWriter;
 import goodjava.lucene.logging.OpDoer;
 import goodjava.lucene.logging.BasicOpDoer;
+import luan.Luan;
 import luan.LuanFunction;
 import luan.LuanException;
 import luan.LuanRuntimeException;
 
 
 final class LuanOpDoer implements OpDoer {
+	private final Luan luan;
 	private final OpDoer opDoer;
 	private final LuanFunction fn;
 
-	LuanOpDoer(GoodIndexWriter writer,LuanFunction fn) {
+	LuanOpDoer(GoodIndexWriter writer,Luan luan,LuanFunction fn) {
 		this.opDoer = new BasicOpDoer(writer);
+		this.luan = luan;
 		this.fn = fn;
 	}
 
-	public void commit() throws IOException {
+	@Override public void commit() throws IOException {
 		try {
-			fn.call(new CommitAction(opDoer));
+			fn.call(luan,new CommitAction(opDoer));
 		} catch(LuanException e) {
 			throw new LuanRuntimeException(e);
 		}
 	}
 
-	public void deleteAll(long time) throws IOException {
+	@Override public void deleteAll(long time) throws IOException {
 		try {
-			fn.call(new DeleteAllAction(opDoer,time));
+			fn.call(luan,new DeleteAllAction(opDoer,time));
 		} catch(LuanException e) {
 			throw new LuanRuntimeException(e);
 		}
 	}
 
-	public void deleteDocuments(long time,Query query) throws IOException {
+	@Override public void deleteDocuments(long time,Query query) throws IOException {
 		try {
-			fn.call(new DeleteDocumentsAction(opDoer,time,query));
+			fn.call(luan,new DeleteDocumentsAction(opDoer,time,query));
 		} catch(LuanException e) {
 			throw new LuanRuntimeException(e);
 		}
 	}
 
-	public void addDocument(long time,Map<String,Object> storedFields) throws IOException {
+	@Override public void addDocument(long time,Map<String,Object> storedFields) throws IOException {
 		try {
-			fn.call(new AddDocumentAction(opDoer,time,storedFields));
+			fn.call(luan,new AddDocumentAction(opDoer,time,storedFields));
 		} catch(LuanException e) {
 			throw new LuanRuntimeException(e);
 		}
 	}
 
-	public void updateDocument(long time,String keyFieldName,Map<String,Object> storedFields) throws IOException {
+	@Override public void updateDocument(long time,String keyFieldName,Map<String,Object> storedFields) throws IOException {
 		try {
-			fn.call(new UpdateDocumentAction(opDoer,time,keyFieldName,storedFields));
+			fn.call(luan,new UpdateDocumentAction(opDoer,time,keyFieldName,storedFields));
 		} catch(LuanException e) {
 			throw new LuanRuntimeException(e);
 		}
 	}
 
-	public void tag(long time,String tag) throws IOException {
+	@Override public void tag(long time,String tag) throws IOException {
 		try {
-			fn.call(new TagAction(opDoer,time,tag));
+			fn.call(luan,new TagAction(opDoer,time,tag));
 		} catch(LuanException e) {
 			throw new LuanRuntimeException(e);
 		}
--- a/src/luan/modules/lucene/LuceneIndex.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/modules/lucene/LuceneIndex.java	Mon Nov 09 01:37:57 2020 -0700
@@ -171,12 +171,12 @@
 			analyzer = sfp.analyzer;
 		}
 		this.analyzer = analyzer;
-		this.config = new SupplementingConfig(luceneVersion,mfp,supplementer);
+		this.config = new SupplementingConfig(luceneVersion,mfp,luan,supplementer);
 		wasCreated = reopen();
 		if( postgresSpec == null ) {
 			postgresBackup = null;
 		} else {
-			postgresBackup = new PostgresBackup(luan,postgresSpec);
+			postgresBackup = new PostgresBackup(postgresSpec);
 			if( !wasCreated && postgresBackup.wasCreated ) {
 				logger.error("rebuilding postgres backup");
 				rebuild_postgres_backup(luan);
@@ -323,7 +323,7 @@
 		return writeLock.isHeldByCurrentThread();
 	}
 
-	public Object run_in_transaction(LuanFunction fn)
+	public Object run_in_transaction(Luan luan,LuanFunction fn)
 		throws IOException, LuanException, SQLException
 	{
 		boolean commit = !writeLock.isHeldByCurrentThread();
@@ -332,7 +332,7 @@
 		try {
 			if( commit && postgresBackup != null )
 				postgresBackup.begin();
-			Object rtn = fn.call();
+			Object rtn = fn.call(luan);
 			ok = true;
 			if(commit) {
 				if( postgresBackup != null )
@@ -353,13 +353,13 @@
 	}
 
 	// ???
-	public Object run_in_lock(LuanFunction fn) throws IOException, LuanException {
+	public Object run_in_lock(Luan luan,LuanFunction fn) throws IOException, LuanException {
 		if( writeLock.isHeldByCurrentThread() )
 			throw new RuntimeException();
 		writeLock.lock();
 		try {
 			synchronized(this) {
-				return fn.call();
+				return fn.call(luan);
 			}
 		} finally {
 			wrote();
@@ -408,13 +408,13 @@
 		return (SnapshotDeletionPolicy)writer.getLuceneIndexWriter().getConfig().getIndexDeletionPolicy();
 	}
 
-	public Object snapshot(LuanFunction fn) throws LuanException, IOException {
+	public Object snapshot(Luan luan,LuanFunction fn) throws LuanException, IOException {
 		SnapshotDeletionPolicy snapshotDeletionPolicy = snapshotDeletionPolicy();
 		IndexCommit ic = snapshotDeletionPolicy.snapshot();
 		try {
 			String dir = fsDir.getDirectory().toString();
 			LuanTable fileNames = new LuanTable(new ArrayList(ic.getFileNames()));
-			return fn.call(dir,fileNames);
+			return fn.call(luan,dir,fileNames);
 		} finally {
 			snapshotDeletionPolicy.release(ic);
 		}
@@ -464,13 +464,12 @@
 		final Query query;
 		int docID;
 
-		DocFn(Luan luan,IndexSearcher searcher,Query query) {
-			super(luan);
+		DocFn(IndexSearcher searcher,Query query) {
 			this.searcher = searcher;
 			this.query = query;
 		}
 
-		@Override public Object call(Object[] args) throws LuanException {
+		@Override public Object call(Luan luan,Object[] args) throws LuanException {
 			try {
 				LuanTable doc = toTable(searcher.doc(docID));
 				if( args.length > 0 && "explain".equals(args[0]) ) {
@@ -523,7 +522,7 @@
 		close(openSearcher());
 	}
 
-	public int advanced_search( String queryStr, LuanFunction fn, Integer n, String sortStr )
+	public int advanced_search( final Luan luan, String queryStr, LuanFunction fn, Integer n, String sortStr )
 		throws LuanException, IOException, ParseException
 	{
 		Utils.checkNotNull(queryStr);
@@ -536,12 +535,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(fn.luan(),searcher,query);
+				final DocFn docFn = new DocFn(searcher,query);
 				MyCollector col = new MyCollector() {
 					@Override public void collect(int doc) {
 						try {
 							docFn.docID = docBase + doc;
-							fn.call(++i,docFn);
+							fn.call(luan,++i,docFn);
 						} catch(LuanException e) {
 							throw new LuanRuntimeException(e);
 						}
@@ -562,11 +561,11 @@
 			Sort sort = sortStr==null ? null : GoodQueryParser.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(fn.luan(),searcher,query);
+			DocFn docFn = new DocFn(searcher,query);
 			for( int i=0; i<scoreDocs.length; i++ ) {
 				ScoreDoc scoreDoc = scoreDocs[i];
 				docFn.docID = scoreDoc.doc;
-				fn.call(i+1,docFn,scoreDoc.score);
+				fn.call(luan,i+1,docFn,scoreDoc.score);
 			}
 			return td.totalHits;
 		} finally {
@@ -575,13 +574,13 @@
 		}
 	}
 
-	public Object search_in_transaction(LuanFunction fn) throws LuanException, IOException {
+	public Object search_in_transaction(Luan luan,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();
+			return fn.call(luan);
 		} finally {
 			threadLocalSearcher.set(null);
 			close(searcher);
@@ -616,7 +615,7 @@
 		}
 	};
 
-	public LuanFunction highlighter(String queryStr,final LuanFunction formatter,final Integer fragmentSize,String dotdotdot)
+	public LuanFunction highlighter(final Luan luan,String queryStr,final LuanFunction formatter,final Integer fragmentSize,String dotdotdot)
 		throws ParseException
 	{
 		Query query = GoodQueryParser.parseQuery(mfp,queryStr);
@@ -625,7 +624,7 @@
 				if( tokenGroup.getTotalScore() <= 0 )
 					return originalText;
 				try {
-					return (String)Luan.first(formatter.call(originalText));
+					return (String)Luan.first(formatter.call(luan,originalText));
 				} catch(LuanException e) {
 					throw new LuanRuntimeException(e);
 				}
@@ -637,8 +636,8 @@
 			chooser.setTextFragmenter( new SimpleSpanFragmenter(queryScorer,fragmentSize) );
 		final Highlighter hl = new Highlighter(fmt,queryScorer);
 		hl.setTextFragmenter( new NullFragmenter() );
-		return new LuanFunction(false) {  // ???
-			@Override public String call(Object[] args) throws LuanException {
+		return new LuanFunction() {
+			@Override public String call(Luan luan,Object[] args) throws LuanException {
 				String text = (String)args[0];
 				try {
 					if( chooser != null ) {
@@ -791,23 +790,23 @@
 		logger.info("end relog");
 	}
 
-	public void restore_from_log(LuanFunction handler)
+	public void restore_from_log(Luan luan,LuanFunction handler)
 		throws IOException, LuanException, SQLException, ParseException
 	{
 		LoggingIndexWriter loggingWriter = (LoggingIndexWriter)writer;
 		if( wasCreated && !loggingWriter.wasCreated ) {
 			logger.error("restoring from log");
-			force_restore_from_log(handler);
+			force_restore_from_log(luan,handler);
 		}
 	}
 
-	public void force_restore_from_log(LuanFunction handler)
+	public void force_restore_from_log(Luan luan,LuanFunction handler)
 		throws IOException
 	{
 		logger.warn("start force_restore_from_log");
 		if( writeLock.isHeldByCurrentThread() )
 			throw new RuntimeException();
-		OpDoer opDoer = handler==null ? null : new LuanOpDoer(writer,handler);
+		OpDoer opDoer = handler==null ? null : new LuanOpDoer(writer,luan,handler);
 		writeLock.lock();
 		boolean ok = false;
 		try {
@@ -829,7 +828,7 @@
 		logger.warn("end force_restore_from_log");
 	}
 
-	public void check(Luan luan) throws IOException, SQLException, LuanException, ParseException {
+	public void check() throws IOException, SQLException, LuanException, ParseException {
 		boolean hasPostgres = postgresBackup != null;
 		String msg = "start check";
 		if( hasPostgres )
@@ -845,12 +844,12 @@
 		}
 		if( hasPostgres ) {
 			logger.info("postgres check");
-			checkPostgres(luan);
+			checkPostgres();
 		}
 		logger.info("end check");
 	}
 
-	private void checkPostgres(Luan luan)
+	private void checkPostgres()
 		throws IOException, SQLException, LuanException, ParseException
 	{
 		final PostgresBackup.Checker postgresChecker = postgresBackup.newChecker();
@@ -872,7 +871,7 @@
 			final int nPostgres = idsPostgres.size();
 			int iLucene = 0;
 			int iPostgres = 0;
-			LuanToString lts = new LuanToString(luan,null,null);
+			LuanToString lts = new LuanToString(null,null);
 			lts.settingsInit.strict = true;
 			lts.settingsInit.numberTypes = true;
 			while( iLucene < nLucene && iPostgres < nPostgres ) {
--- a/src/luan/modules/lucene/PostgresBackup.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/modules/lucene/PostgresBackup.java	Mon Nov 09 01:37:57 2020 -0700
@@ -36,7 +36,7 @@
 	private int trans = 0;
 	private final LuanToString luanToString;
 
-	PostgresBackup(Luan luan,LuanTable spec)
+	PostgresBackup(LuanTable spec)
 		throws ClassNotFoundException, SQLException, LuanException
 	{
 		spec = new LuanTable(spec);
@@ -82,7 +82,7 @@
 			"delete from lucene where id=?"
 		);
 
-		luanToString = new LuanToString(luan,null,null);
+		luanToString = new LuanToString(null,null);
 		luanToString.settingsInit.strict = true;
 		luanToString.settingsInit.numberTypes = true;
 	}
--- a/src/luan/modules/lucene/SupplementingConfig.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/modules/lucene/SupplementingConfig.java	Mon Nov 09 01:37:57 2020 -0700
@@ -19,27 +19,29 @@
 
 
 final class SupplementingConfig extends MultiFieldParserConfig {
+	private final Luan luan;
 	private final LuanFunction supplementer;
 
-	SupplementingConfig(Version luceneVersion,MultiFieldParser mfp,LuanFunction supplementer) {
+	SupplementingConfig(Version luceneVersion,MultiFieldParser mfp,Luan luan,LuanFunction supplementer) {
 		super(luceneVersion,mfp);
 		LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
+		this.luan = (Luan)cloner.get(luan);
 		this.supplementer = (LuanFunction)cloner.get(supplementer);
 	}
 
-	public IndexWriterConfig newLuceneConfig() {
+	@Override public IndexWriterConfig newLuceneConfig() {
 		IndexWriterConfig luceneConfig = super.newLuceneConfig();
 		SnapshotDeletionPolicy snapshotDeletionPolicy = new SnapshotDeletionPolicy(luceneConfig.getIndexDeletionPolicy());
 		luceneConfig.setIndexDeletionPolicy(snapshotDeletionPolicy);
 		return luceneConfig;
 	}
 
-	public MoreFieldInfo getMoreFieldInfo(Map<String,Object> storedFields) {
+	@Override public MoreFieldInfo getMoreFieldInfo(Map<String,Object> storedFields) {
 		if( supplementer == null )
 			return super.getMoreFieldInfo(storedFields);
 		try {
 			LuanTable tbl = toTable(storedFields);
-			tbl = (LuanTable)supplementer.call(tbl);
+			tbl = (LuanTable)supplementer.call(luan,tbl);
 			if( tbl == null ) {
 				return super.getMoreFieldInfo(storedFields);
 			} else {
@@ -64,7 +66,7 @@
 
 	static Map<String,Object> toLucene(LuanTable table) throws LuanException {
 		Map<String,Object> map = new LinkedHashMap<String,Object>();
-		for( Map.Entry<Object,Object> entry : table.iterable() ) {
+		for( Map.Entry<Object,Object> entry : table.rawIterable() ) {
 			String name = (String)entry.getKey();
 			Object value  = entry.getValue();
 			if( value instanceof LuanTable ) {
--- a/src/luan/modules/parsers/BBCode.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/modules/parsers/BBCode.java	Mon Nov 09 01:37:57 2020 -0700
@@ -12,21 +12,23 @@
 
 public final class BBCode {
 
-	public static String toHtml(String bbcode,LuanFunction quoter) throws LuanException {
-		return new BBCode(bbcode,quoter,true).parse();
+	public static String toHtml(Luan luan,String bbcode,LuanFunction quoter) throws LuanException {
+		return new BBCode(luan,bbcode,quoter,true).parse();
 	}
 
-	public static String toText(String bbcode,LuanFunction quoter) throws LuanException {
-		return new BBCode(bbcode,quoter,false).parse();
+	public static String toText(Luan luan,String bbcode,LuanFunction quoter) throws LuanException {
+		return new BBCode(luan,bbcode,quoter,false).parse();
 	}
 
+	private final Luan luan;
 	private final Parser parser;
 	private final LuanFunction quoter;
 	private final boolean toHtml;
 
-	private BBCode(String text,LuanFunction quoter,boolean toHtml) throws LuanException {
+	private BBCode(Luan luan,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;
@@ -283,7 +285,7 @@
 			else
 				return "";
 		}
-		Object obj = quoter.call(args);
+		Object obj = quoter.call(luan,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/parsers/LuanToString.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/modules/parsers/LuanToString.java	Mon Nov 09 01:37:57 2020 -0700
@@ -19,21 +19,21 @@
 		public boolean longStrings = false;
 		public boolean inline = false;
 
-		void applyOptions(Luan luan,LuanTable options) throws LuanException {
+		void applyOptions(LuanTable options) throws LuanException {
 			Boolean b;
-			b = (Boolean)options.get(luan,"strict");
+			b = (Boolean)options.rawGet("strict");
 			if( b != null )
 				strict = b;
-			b = (Boolean)options.get(luan,"number_types");
+			b = (Boolean)options.rawGet("number_types");
 			if( b != null )
 				numberTypes = b;
-			b = (Boolean)options.get(luan,"compressed");
+			b = (Boolean)options.rawGet("compressed");
 			if( b != null )
 				compressed = b;
-			b = (Boolean)options.get(luan,"long_strings");
+			b = (Boolean)options.rawGet("long_strings");
 			if( b != null )
 				longStrings = b;
-			b = (Boolean)options.get(luan,"inline");
+			b = (Boolean)options.rawGet("inline");
 			if( b != null )
 				inline = b;
 		}
@@ -53,7 +53,7 @@
 	}
 
 	private static void checkOptions(LuanTable options) throws LuanException {
-		for( Map.Entry entry : options.iterable() ) {
+		for( Map.Entry entry : options.rawIterable() ) {
 			if( !settingsKeys.contains(entry.getKey()) )
 				throw new LuanException("invalid option: "+entry.getKey());
 			if( !(entry.getValue() instanceof Boolean) )
@@ -62,18 +62,16 @@
 	}
 
 	public final Settings settingsInit = new Settings();
-	private final Luan luan;
 	private final LuanTable subOptions;
 
-	public LuanToString(Luan luan,LuanTable options,LuanTable subOptions) throws LuanException {
-		this.luan = luan;
+	public LuanToString(LuanTable options,LuanTable subOptions) throws LuanException {
 		this.subOptions = subOptions;
 		if( options != null ) {
 			checkOptions(options);
-			settingsInit.applyOptions(luan,options);
+			settingsInit.applyOptions(options);
 		}
 		if( subOptions != null ) {
-			for( Map.Entry entry : subOptions.iterable() ) {
+			for( Map.Entry entry : subOptions.rawIterable() ) {
 /*
 				if( !(entry.getKey() instanceof String) )
 					throw new LuanException("sub_options keys must be strings");
@@ -183,10 +181,10 @@
 		}
 		sb.append( settings.compressed ? "=" : " = " );
 		if( subOptions != null ) {
-			LuanTable options = (LuanTable)subOptions.get(luan,key);
+			LuanTable options = (LuanTable)subOptions.rawGet(key);
 			if( options != null ) {
 				settings = settings.cloneSettings();
-				settings.applyOptions(luan,options);
+				settings.applyOptions(options);
 			}
 		}
 		toString( entry.getValue(), sb, indented, settings );
@@ -212,10 +210,10 @@
 	public static void addNumberTypes(Luan luan,LuanTable env) {
 		try {
 			LuanTable module = (LuanTable)luan.require("luan:Number.luan");
-			env.put( luan, "double", module.fn(luan,"double") );
-			env.put( luan, "float", module.fn(luan,"float") );
-			env.put( luan, "integer", module.fn(luan,"integer") );
-			env.put( luan, "long", module.fn(luan,"long") );
+			env.rawPut( "double", module.fn("double") );
+			env.rawPut( "float", module.fn("float") );
+			env.rawPut( "integer", module.fn("integer") );
+			env.rawPut( "long", module.fn("long") );
 		} catch(LuanException e) {
 			throw new LuanRuntimeException(e);
 		}
--- a/src/luan/modules/parsers/Xml.java	Sun Nov 08 16:50:59 2020 -0700
+++ b/src/luan/modules/parsers/Xml.java	Mon Nov 09 01:37:57 2020 -0700
@@ -16,8 +16,8 @@
 
 public final class Xml {
 
-	public static String toString(Luan luan,LuanTable tbl) throws LuanException {
-		XmlElement[] elements = elements(luan,tbl);
+	public static String toString(LuanTable tbl) throws LuanException {
+		XmlElement[] elements = elements(tbl);
 		if( elements.length != 1 )
 			throw new LuanException("XML must have 1 root element");
 		return elements[0].toString();
@@ -26,9 +26,9 @@
 	private static final String ATTRIBUTES = "xml_attributes";
 	private static final String TEXT = "xml_text";
 
-	private static XmlElement[] elements(Luan luan,LuanTable tbl) throws LuanException {
+	private static XmlElement[] elements(LuanTable tbl) throws LuanException {
 		List<XmlElement> list = new ArrayList<XmlElement>();
-		for( Map.Entry entry : tbl.iterable() ) {
+		for( Map.Entry entry : tbl.rawIterable() ) {
 			Object key = entry.getKey();
 			if( !(key instanceof String) )
 				throw new LuanException("XML key must be string");
@@ -40,27 +40,27 @@
 				throw new LuanException("Can't mix text and elements");
 			LuanTable t = (LuanTable)value;
 			if( t.isMap() ) {
-				list.add( element(luan,name,t) );
+				list.add( element(name,t) );
 			} else {
 				for( Object obj : t.asList() ) {
-					list.add( element(luan,name,obj) );
+					list.add( element(name,obj) );
 				}
 			}
 		}
 		return list.toArray(new XmlElement[0]);
 	}
 
-	private static XmlElement element(Luan luan,String name,Object obj) throws LuanException {
+	private static XmlElement element(String name,Object obj) throws LuanException {
 		if( obj instanceof String ) {
 			return new XmlElement( name, Collections.emptyMap(), (String)obj );
 		}
 		LuanTable t = (LuanTable)obj;
-		Map<String,String> attributes = attributes(luan,t);
-		String s = (String)t.get(luan,TEXT);
+		Map<String,String> attributes = attributes(t);
+		String s = (String)t.rawGet(TEXT);
 		if( s != null ) {
 			return new XmlElement(name,attributes,s);
 		} else {
-			XmlElement[] elements = elements(luan,t);
+			XmlElement[] elements = elements(t);
 			if( elements.length==0 ) {
 				return new XmlElement(name,attributes);
 			} else {
@@ -69,13 +69,13 @@
 		}
 	}
 
-	private static Map<String,String> attributes(Luan luan,LuanTable tbl) throws LuanException {
-		Object obj = tbl.get(luan,ATTRIBUTES);
+	private static Map<String,String> attributes(LuanTable tbl) throws LuanException {
+		Object obj = tbl.rawGet(ATTRIBUTES);
 		if( obj==null )
 			return Collections.emptyMap();
 		LuanTable t = (LuanTable)obj;
 		Map<String,String> map = new LinkedHashMap<String,String>();
-		for( Map.Entry entry : t.iterable() ) {
+		for( Map.Entry entry : t.rawIterable() ) {
 			String name =(String)entry.getKey();
 			String value =(String)entry.getValue();
 			map.put(name,value);