changeset 129:486a0641bca4

add pickle client/server; fix parser bugs; git-svn-id: https://luan-java.googlecode.com/svn/trunk@130 21e917c8-12df-6dd8-5cb6-c86387c605b9
author fschmidt@gmail.com <fschmidt@gmail.com@21e917c8-12df-6dd8-5cb6-c86387c605b9>
date Mon, 09 Jun 2014 09:16:16 +0000
parents f0a4abe58593
children 0594c132888b
files src/luan/LuanJavaFunction.java src/luan/interp/LuanParser.java src/luan/lib/IoLib.java src/luan/lib/PickleClient.java src/luan/lib/PickleCon.java src/luan/lib/PickleServer.java src/luan/tools/CmdLine.java
diffstat 7 files changed, 530 insertions(+), 249 deletions(-) [+]
line wrap: on
line diff
--- a/src/luan/LuanJavaFunction.java	Sun Jun 08 03:38:25 2014 +0000
+++ b/src/luan/LuanJavaFunction.java	Mon Jun 09 09:16:16 2014 +0000
@@ -69,7 +69,7 @@
 		try {
 			rtn = method.invoke(obj,args);
 		} catch(IllegalAccessException e) {
-			throw new RuntimeException(e);
+			throw new RuntimeException("method = "+method,e);
 		} catch(InvocationTargetException e) {
 			Throwable cause = e.getCause();
 			if( cause instanceof Error )
--- a/src/luan/interp/LuanParser.java	Sun Jun 08 03:38:25 2014 +0000
+++ b/src/luan/interp/LuanParser.java	Mon Jun 09 09:16:16 2014 +0000
@@ -72,6 +72,26 @@
 		}
 	}
 
+	private static class In {
+		static final In NOTHING = new In(false,false);
+
+		final boolean parens;
+		final boolean jsp;
+
+		private In(boolean parens,boolean jsp) {
+			this.parens = parens;
+			this.jsp = jsp;
+		}
+
+		In parens() {
+			return parens ? this : new In(true,jsp);
+		}
+
+		In jsp() {
+			return jsp ? this : new In(parens,true);
+		}
+	}
+
 	private static final String _ENV = "_ENV";
 	private static final UpValue.Getter[] NO_UP_VALUE_GETTERS = new UpValue.Getter[0];
 
@@ -161,9 +181,9 @@
 	}
 
 	FnDef Expressions() throws ParseException {
-		Spaces();
+		Spaces(In.NOTHING);
 		int start = parser.begin();
-		Expressions exprs = ExpList();
+		Expressions exprs = ExpList(In.NOTHING);
 		if( exprs != null && parser.endOfInput() ) {
 			Stmt stmt = new ReturnStmt( se(start), exprs );
 			return parser.success(newFnDef(start,stmt));
@@ -172,7 +192,7 @@
 	}
 
 	FnDef RequiredModule() throws ParseException {
-		Spaces();
+		Spaces(In.NOTHING);
 		int start = parser.begin();
 		frame.isVarArg = true;
 		Stmt stmt = RequiredBlock();
@@ -186,7 +206,7 @@
 		int stackStart = symbolsSize();
 		Stmt(stmts);
 		while( StmtSep(stmts) ) {
-			Spaces();
+			Spaces(In.NOTHING);
 			Stmt(stmts);
 		}
 		int stackEnd = symbolsSize();
@@ -235,19 +255,22 @@
 		}
 	}
 
-	private Expressions JspExpressions() throws ParseException {
+	private Expressions JspExpressions(In in) throws ParseException {
+		if( in.jsp )
+			return null;
 		int start = parser.begin();
 		if( !parser.match( "%>" ) )
 			return parser.failure(null);
 		EndOfLine();
+		In inJsp = in.jsp();
 		ExpList.Builder builder = new ExpList.Builder();
 		while(true) {
 			if( parser.match( "<%=" ) ) {
-				Spaces();
-				builder.add( RequiredExpr() );
+				Spaces(inJsp);
+				builder.add( RequiredExpr(inJsp) );
 				RequiredMatch( "%>" );
 			} else if( parser.match( "<%" ) ) {
-				Spaces();
+				Spaces(inJsp);
 				return parser.success(builder.build());
 			} else {
 				int i = parser.currentIndex();
@@ -265,9 +288,9 @@
 
 	private Stmt ReturnStmt() throws ParseException {
 		int start = parser.begin();
-		if( !Keyword("return") )
+		if( !Keyword("return",In.NOTHING) )
 			return parser.failure(null);
-		Expressions exprs = ExpList();
+		Expressions exprs = ExpList(In.NOTHING);
 		if( exprs==null )
 			exprs = ExpList.emptyExpList;
 		return parser.success( new ReturnStmt(se(start),exprs) );
@@ -275,37 +298,37 @@
 
 	private Stmt FunctionStmt() throws ParseException {
 		parser.begin();
-		if( !Keyword("function") )
+		if( !Keyword("function",In.NOTHING) )
 			return parser.failure(null);
 
 		int start = parser.currentIndex();
-		Var var = nameVar(start,RequiredName());
+		Var var = nameVar(start,RequiredName(In.NOTHING));
 		while( parser.match( '.' ) ) {
-			Spaces();
-			var = indexVar( start, expr(var.expr()), NameExpr(false) );
+			Spaces(In.NOTHING);
+			var = indexVar( start, expr(var.expr()), NameExpr(In.NOTHING) );
 		}
 		Settable fnName = var.settable();
 
-		FnDef fnDef = RequiredFunction(false);
+		FnDef fnDef = RequiredFunction(In.NOTHING);
 		return parser.success( new SetStmt(fnName,fnDef) );
 	}
 
 	private Stmt LocalFunctionStmt() throws ParseException {
 		parser.begin();
-		if( !(Keyword("local") && Keyword("function")) )
+		if( !(Keyword("local",In.NOTHING) && Keyword("function",In.NOTHING)) )
 			return parser.failure(null);
-		String name = RequiredName();
+		String name = RequiredName(In.NOTHING);
 		addSymbol( name );
-		FnDef fnDef = RequiredFunction(false);
+		FnDef fnDef = RequiredFunction(In.NOTHING);
 		return parser.success( new SetStmt( new SetLocalVar(symbolsSize()-1), fnDef ) );
 	}
 
 	private Stmt ImportStmt() throws ParseException {
 		int start = parser.begin();
-		if( !Keyword("import") )
+		if( !Keyword("import",In.NOTHING) )
 			return parser.failure(null);
 		Expr importExpr = (Expr)nameVar(start,"require").expr();
-		String modName = StringLiteral(false);
+		String modName = StringLiteral(In.NOTHING);
 		if( modName==null )
 			return parser.failure(null);
 		String varName = modName.substring(modName.lastIndexOf('.')+1);
@@ -323,7 +346,7 @@
 
 	private Stmt BreakStmt() throws ParseException {
 		parser.begin();
-		if( !Keyword("break") )
+		if( !Keyword("break",In.NOTHING) )
 			return parser.failure(null);
 		if( frame.loops <= 0 )
 			throw parser.exception("'break' outside of loop");
@@ -333,16 +356,16 @@
 	private Stmt ForStmt() throws ParseException {
 		int start = parser.begin();
 		int stackStart = symbolsSize();
-		if( !Keyword("for") )
+		if( !Keyword("for",In.NOTHING) )
 			return parser.failure(null);
-		List<String> names = RequiredNameList(false);
-		if( !Keyword("in") )
+		List<String> names = RequiredNameList(In.NOTHING);
+		if( !Keyword("in",In.NOTHING) )
 			return parser.failure(null);
-		Expr expr = expr(RequiredExpr());
-		RequiredKeyword("do");
+		Expr expr = expr(RequiredExpr(In.NOTHING));
+		RequiredKeyword("do",In.NOTHING);
 		addSymbols(names);
 		Stmt loop = RequiredLoopBlock();
-		RequiredKeyword("end");
+		RequiredKeyword("end",In.NOTHING);
 		Stmt stmt = new ForStmt( se(start), stackStart, symbolsSize() - stackStart, expr, loop );
 		popSymbols( symbolsSize() - stackStart );
 		return parser.success(stmt);
@@ -350,15 +373,15 @@
 
 	private Stmt TryStmt() throws ParseException {
 		parser.begin();
-		if( !Keyword("try") )
+		if( !Keyword("try",In.NOTHING) )
 			return parser.failure(null);
 		Stmt tryBlock = RequiredBlock();
-		RequiredKeyword("catch");
-		String name = RequiredName();
+		RequiredKeyword("catch",In.NOTHING);
+		String name = RequiredName(In.NOTHING);
 		addSymbol(name);
-		RequiredKeyword("do");
+		RequiredKeyword("do",In.NOTHING);
 		Stmt catchBlock = RequiredBlock();
-		RequiredKeyword("end");
+		RequiredKeyword("end",In.NOTHING);
 		Stmt stmt = new TryStmt( tryBlock, symbolsSize()-1, catchBlock );
 		popSymbols(1);
 		return parser.success(stmt);
@@ -366,23 +389,23 @@
 
 	private Stmt DoStmt() throws ParseException {
 		parser.begin();
-		if( !Keyword("do") )
+		if( !Keyword("do",In.NOTHING) )
 			return parser.failure(null);
 		Stmt stmt = RequiredBlock();
-		RequiredKeyword("end");
+		RequiredKeyword("end",In.NOTHING);
 		return parser.success(stmt);
 	}
 
 	private boolean LocalStmt(List<Stmt> stmts) throws ParseException {
 		parser.begin();
-		if( !Keyword("local") )
+		if( !Keyword("local",In.NOTHING) )
 			return parser.failure();
-		List<String> names = NameList(false);
+		List<String> names = NameList(In.NOTHING);
 		if( names==null )
 			return parser.failure();
 		if( parser.match( '=' ) ) {
-			Spaces();
-			Expressions values = ExpList();
+			Spaces(In.NOTHING);
+			Expressions values = ExpList(In.NOTHING);
 			if( values==null )
 				throw parser.exception("Expressions expected");
 			SetLocalVar[] vars = new SetLocalVar[names.size()];
@@ -396,45 +419,45 @@
 		return parser.success();
 	}
 
-	private List<String> RequiredNameList(boolean inParens) throws ParseException {
+	private List<String> RequiredNameList(In in) throws ParseException {
 		parser.begin();
-		List<String> names = NameList(inParens);
+		List<String> names = NameList(in);
 		if( names==null )
 			parser.exception("Name expected");
 		return parser.success(names);
 	}
 
-	private List<String> NameList(boolean inParens) throws ParseException {
-		String name = Name(inParens);
+	private List<String> NameList(In in) throws ParseException {
+		String name = Name(in);
 		if( name==null )
 			return null;
 		List<String> names = new ArrayList<String>();
 		names.add(name);
 		while( parser.match( ',' ) ) {
-			Spaces(inParens);
-			names.add( RequiredName() );
+			Spaces(in);
+			names.add( RequiredName(in) );
 		}
 		return names;
 	}
 
 	private Stmt WhileStmt() throws ParseException {
 		parser.begin();
-		if( !Keyword("while") )
+		if( !Keyword("while",In.NOTHING) )
 			return parser.failure(null);
-		Expr cnd = expr(RequiredExpr());
-		RequiredKeyword("do");
+		Expr cnd = expr(RequiredExpr(In.NOTHING));
+		RequiredKeyword("do",In.NOTHING);
 		Stmt loop = RequiredLoopBlock();
-		RequiredKeyword("end");
+		RequiredKeyword("end",In.NOTHING);
 		return parser.success( new WhileStmt(cnd,loop) );
 	}
 
 	private Stmt RepeatStmt() throws ParseException {
 		parser.begin();
-		if( !Keyword("repeat") )
+		if( !Keyword("repeat",In.NOTHING) )
 			return parser.failure(null);
 		Stmt loop = RequiredLoopBlock();
-		RequiredKeyword("until");
-		Expr cnd = expr(RequiredExpr());
+		RequiredKeyword("until",In.NOTHING);
+		Expr cnd = expr(RequiredExpr(In.NOTHING));
 		return parser.success( new RepeatStmt(loop,cnd) );
 	}
 
@@ -447,21 +470,21 @@
 
 	private Stmt IfStmt() throws ParseException {
 		parser.begin();
-		if( !Keyword("if") )
+		if( !Keyword("if",In.NOTHING) )
 			return parser.failure(null);
 		return parser.success( IfStmt2() );
 	}
 
 	private Stmt IfStmt2() throws ParseException {
-		Expr cnd = expr(RequiredExpr());
-		RequiredKeyword("then");
+		Expr cnd = expr(RequiredExpr(In.NOTHING));
+		RequiredKeyword("then",In.NOTHING);
 		Stmt thenBlock = RequiredBlock();
 		Stmt elseBlock;
-		if( Keyword("elseif") ) {
+		if( Keyword("elseif",In.NOTHING) ) {
 			elseBlock = IfStmt2();
 		} else {
-			elseBlock = Keyword("else") ? RequiredBlock() : Stmt.EMPTY;
-			RequiredKeyword("end");
+			elseBlock = Keyword("else",In.NOTHING) ? RequiredBlock() : Stmt.EMPTY;
+			RequiredKeyword("end",In.NOTHING);
 		}
 		return new IfStmt(cnd,thenBlock,elseBlock);
 	}
@@ -474,7 +497,7 @@
 			return parser.failure(null);
 		vars.add(s);
 		while( parser.match( ',' ) ) {
-			Spaces();
+			Spaces(In.NOTHING);
 			s = SettableVar();
 			if( s == null )
 				return parser.failure(null);
@@ -482,8 +505,8 @@
 		}
 		if( !parser.match( '=' ) )
 			return parser.failure(null);
-		Spaces();
-		Expressions values = ExpList();
+		Spaces(In.NOTHING);
+		Expressions values = ExpList(In.NOTHING);
 		if( values==null )
 			throw parser.exception("Expressions expected");
 		return parser.success( new SetStmt( vars.toArray(new Settable[0]), values ) );
@@ -491,7 +514,7 @@
 
 	private Stmt ExpressionsStmt() throws ParseException {
 		parser.begin();
-		Expressions exprs = ExpList();
+		Expressions exprs = ExpList(In.NOTHING);
 		if( exprs==null )
 			return parser.failure(null);
 		return parser.success( new ExpressionsStmt(exprs) );
@@ -499,112 +522,104 @@
 
 	private Settable SettableVar() throws ParseException {
 		int start = parser.begin();
-		Var var = VarZ(false);
+		Var var = VarZ(In.NOTHING);
 		if( var==null )
 			return null;
 		return var.settable();
 	}
 
-	private Code RequiredExpr() throws ParseException {
-		return RequiredExpr(false);
-	}
-
-	private Code Expr() throws ParseException {
-		return Expr(false);
+	private Code RequiredExpr(In in) throws ParseException {
+		parser.begin();
+		return parser.success(required(Expr(in),"Bad expression"));
 	}
 
-	private Code RequiredExpr(boolean inParens) throws ParseException {
-		parser.begin();
-		return parser.success(required(Expr(inParens),"Bad expression"));
-	}
-
-	private Code Expr(boolean inParens) throws ParseException {
+	private Code Expr(In in) throws ParseException {
 		parser.begin();
 		Code exp;
-		return (exp = VarArgs(inParens)) != null
-			|| (exp = JspExpressions()) != null
-			|| (exp = OrExpr(inParens)) != null
+		return (exp = VarArgs(in)) != null
+			|| (exp = JspExpressions(in)) != null
+			|| (exp = OrExpr(in)) != null
 			? parser.success(exp)
 			: parser.failure((Code)null)
 		;
 	}
 
-	private Code OrExpr(boolean inParens) throws ParseException {
+	private Code OrExpr(In in) throws ParseException {
 		int start = parser.begin();
-		Code exp = AndExpr(inParens);
+		Code exp = AndExpr(in);
 		if( exp==null )
 			return parser.failure(null);
-		while( Keyword("or",inParens) ) {
-			exp = new OrExpr( se(start), expr(exp), required(expr(AndExpr(inParens))) );
+		while( Keyword("or",in) ) {
+			exp = new OrExpr( se(start), expr(exp), required(expr(AndExpr(in))) );
 		}
 		return parser.success(exp);
 	}
 
-	private Code AndExpr(boolean inParens) throws ParseException {
+	private Code AndExpr(In in) throws ParseException {
 		int start = parser.begin();
-		Code exp = RelExpr(inParens);
+		Code exp = RelExpr(in);
 		if( exp==null )
 			return parser.failure(null);
-		while( Keyword("and",inParens) ) {
-			exp = new AndExpr( se(start), expr(exp), required(expr(RelExpr(inParens))) );
+		while( Keyword("and",in) ) {
+			exp = new AndExpr( se(start), expr(exp), required(expr(RelExpr(in))) );
 		}
 		return parser.success(exp);
 	}
 
-	private Code RelExpr(boolean inParens) throws ParseException {
+	private Code RelExpr(In in) throws ParseException {
 		int start = parser.begin();
-		Code exp = ConcatExpr(inParens);
+		Code exp = ConcatExpr(in);
 		if( exp==null )
 			return parser.failure(null);
 		while(true) {
 			if( parser.match("==") ) {
-				Spaces(inParens);
-				exp = new EqExpr( se(start), expr(exp), required(expr(ConcatExpr(inParens))) );
+				Spaces(in);
+				exp = new EqExpr( se(start), expr(exp), required(expr(ConcatExpr(in))) );
 			} else if( parser.match("~=") ) {
-				Spaces(inParens);
-				exp = new NotExpr( se(start), new EqExpr( se(start), expr(exp), required(expr(ConcatExpr(inParens))) ) );
+				Spaces(in);
+				exp = new NotExpr( se(start), new EqExpr( se(start), expr(exp), required(expr(ConcatExpr(in))) ) );
 			} else if( parser.match("<=") ) {
-				Spaces(inParens);
-				exp = new LeExpr( se(start), expr(exp), required(expr(ConcatExpr(inParens))) );
+				Spaces(in);
+				exp = new LeExpr( se(start), expr(exp), required(expr(ConcatExpr(in))) );
 			} else if( parser.match(">=") ) {
-				Spaces(inParens);
-				exp = new LeExpr( se(start), required(expr(ConcatExpr(inParens))), expr(exp) );
+				Spaces(in);
+				exp = new LeExpr( se(start), required(expr(ConcatExpr(in))), expr(exp) );
 			} else if( parser.match("<") ) {
-				Spaces(inParens);
-				exp = new LtExpr( se(start), expr(exp), required(expr(ConcatExpr(inParens))) );
+				Spaces(in);
+				exp = new LtExpr( se(start), expr(exp), required(expr(ConcatExpr(in))) );
 			} else if( parser.match(">") ) {
-				Spaces(inParens);
-				exp = new LtExpr( se(start), required(expr(ConcatExpr(inParens))), expr(exp) );
+				Spaces(in);
+				exp = new LtExpr( se(start), required(expr(ConcatExpr(in))), expr(exp) );
 			} else
 				break;
 		}
 		return parser.success(exp);
 	}
 
-	private Code ConcatExpr(boolean inParens) throws ParseException {
+	private Code ConcatExpr(In in) throws ParseException {
 		int start = parser.begin();
-		Code exp = SumExpr(inParens);
+		Code exp = SumExpr(in);
 		if( exp==null )
 			return parser.failure(null);
 		if( parser.match("..") ) {
-			Spaces(inParens);
-			exp = new ConcatExpr( se(start), expr(exp), required(expr(ConcatExpr(inParens))) );
+			Spaces(in);
+			exp = new ConcatExpr( se(start), expr(exp), required(expr(ConcatExpr(in))) );
 		}
 		return parser.success(exp);
 	}
 
-	private Code SumExpr(boolean inParens) throws ParseException {
+	private Code SumExpr(In in) throws ParseException {
 		int start = parser.begin();
-		Code exp = TermExpr(inParens);
+		Code exp = TermExpr(in);
 		if( exp==null )
 			return parser.failure(null);
 		while(true) {
 			if( parser.match('+') ) {
-				Spaces(inParens);
-				exp = new AddExpr( se(start), expr(exp), required(expr(TermExpr(inParens))) );
+				Spaces(in);
+				exp = new AddExpr( se(start), expr(exp), required(expr(TermExpr(in))) );
 			} else if( Minus() ) {
-				Spaces(inParens);
-				exp = new SubExpr( se(start), expr(exp), required(expr(TermExpr(inParens))) );
+				Spaces(in);
+				exp = new SubExpr( se(start), expr(exp), required(expr(TermExpr(in))) );
 			} else
 				break;
 		}
@@ -616,21 +631,21 @@
 		return parser.match('-') && !parser.match('-') ? parser.success() : parser.failure();
 	}
 
-	private Code TermExpr(boolean inParens) throws ParseException {
+	private Code TermExpr(In in) throws ParseException {
 		int start = parser.begin();
-		Code exp = UnaryExpr(inParens);
+		Code exp = UnaryExpr(in);
 		if( exp==null )
 			return parser.failure(null);
 		while(true) {
 			if( parser.match('*') ) {
-				Spaces(inParens);
-				exp = new MulExpr( se(start), expr(exp), required(expr(UnaryExpr(inParens))) );
+				Spaces(in);
+				exp = new MulExpr( se(start), expr(exp), required(expr(UnaryExpr(in))) );
 			} else if( parser.match('/') ) {
-				Spaces(inParens);
-				exp = new DivExpr( se(start), expr(exp), required(expr(UnaryExpr(inParens))) );
+				Spaces(in);
+				exp = new DivExpr( se(start), expr(exp), required(expr(UnaryExpr(in))) );
 			} else if( Mod() ) {
-				Spaces(inParens);
-				exp = new ModExpr( se(start), expr(exp), required(expr(UnaryExpr(inParens))) );
+				Spaces(in);
+				exp = new ModExpr( se(start), expr(exp), required(expr(UnaryExpr(in))) );
 			} else
 				break;
 		}
@@ -642,128 +657,130 @@
 		return parser.match('%') && !parser.match('>') ? parser.success() : parser.failure();
 	}
 
-	private Code UnaryExpr(boolean inParens) throws ParseException {
+	private Code UnaryExpr(In in) throws ParseException {
 		int start = parser.begin();
 		if( parser.match('#') ) {
-			Spaces(inParens);
-			return parser.success( new LenExpr( se(start), required(expr(UnaryExpr(inParens))) ) );
+			Spaces(in);
+			return parser.success( new LenExpr( se(start), required(expr(UnaryExpr(in))) ) );
 		}
 		if( Minus() ) {
-			Spaces(inParens);
-			return parser.success( new UnmExpr( se(start), required(expr(UnaryExpr(inParens))) ) );
+			Spaces(in);
+			return parser.success( new UnmExpr( se(start), required(expr(UnaryExpr(in))) ) );
 		}
-		if( Keyword("not",inParens) ) {
-			Spaces(inParens);
-			return parser.success( new NotExpr( se(start), required(expr(UnaryExpr(inParens))) ) );
+		if( Keyword("not",in) ) {
+			Spaces(in);
+			return parser.success( new NotExpr( se(start), required(expr(UnaryExpr(in))) ) );
 		}
-		Code exp = PowExpr(inParens);
+		Code exp = PowExpr(in);
 		if( exp==null )
 			return parser.failure(null);
 		return parser.success(exp);
 	}
 
-	private Code PowExpr(boolean inParens) throws ParseException {
+	private Code PowExpr(In in) throws ParseException {
 		int start = parser.begin();
-		Code exp = SingleExpr(inParens);
+		Code exp = SingleExpr(in);
 		if( exp==null )
 			return parser.failure(null);
 		if( parser.match('^') ) {
-			Spaces(inParens);
-			exp = new ConcatExpr( se(start), expr(exp), required(expr(PowExpr(inParens))) );
+			Spaces(in);
+			exp = new ConcatExpr( se(start), expr(exp), required(expr(PowExpr(in))) );
 		}
 		return parser.success(exp);
 	}
 
-	private Code SingleExpr(boolean inParens) throws ParseException {
+	private Code SingleExpr(In in) throws ParseException {
 		parser.begin();
 		Code exp;
-		exp = FunctionExpr(inParens);
+		exp = FunctionExpr(in);
 		if( exp != null )
 			return parser.success(exp);
-		exp = TableExpr(inParens);
+		exp = TableExpr(in);
 		if( exp != null )
 			return parser.success(exp);
-		exp = VarExp(inParens);
+		exp = VarExp(in);
 		if( exp != null )
 			return parser.success(exp);
-		exp = Literal(inParens);
+		exp = Literal(in);
 		if( exp != null )
 			return parser.success(exp);
 		return parser.failure(null);
 	}
 
-	private Expr FunctionExpr(boolean inParens) throws ParseException {
-		if( !Keyword("function",inParens) )
+	private Expr FunctionExpr(In in) throws ParseException {
+		if( !Keyword("function",in) )
 			return null;
-		return RequiredFunction(inParens);
+		return RequiredFunction(in);
 	}
 
-	private FnDef RequiredFunction(boolean inParens) throws ParseException {
+	private FnDef RequiredFunction(In in) throws ParseException {
 		int start = parser.begin();
 		RequiredMatch('(');
-		Spaces(true);
+		In inParens = in.parens();
+		Spaces(inParens);
 		frame = new Frame(frame);
-		List<String> names = NameList(false);
+		List<String> names = NameList(in);
 		if( names != null ) {
 			addSymbols(names);
 			if( parser.match(',') ) {
-				Spaces(true);
+				Spaces(inParens);
 				if( !parser.match("...") )
 					throw parser.exception();
 				frame.isVarArg = true;
 			}
 		} else if( parser.match("...") ) {
-			Spaces(true);
+			Spaces(inParens);
 			frame.isVarArg = true;
 		}
 		RequiredMatch(')');
-		Spaces(inParens);
+		Spaces(in);
 		Stmt block = RequiredBlock();
-		RequiredKeyword("end",inParens);
+		RequiredKeyword("end",in);
 		FnDef fnDef = newFnDef(start,block);
 		frame = frame.parent;
 		return parser.success(fnDef);
 	}
 
-	private VarArgs VarArgs(boolean inParens) throws ParseException {
+	private VarArgs VarArgs(In in) throws ParseException {
 		int start = parser.begin();
 		if( !frame.isVarArg || !parser.match("...") )
 			return parser.failure(null);
-		Spaces(inParens);
+		Spaces(in);
 		return parser.success( new VarArgs(se(start)) );
 	}
 
-	private Expr TableExpr(boolean inParens) throws ParseException {
+	private Expr TableExpr(In in) throws ParseException {
 		int start = parser.begin();
 		if( !parser.match('{') )
 			return parser.failure(null);
-		Spaces(true);
+		In inParens = in.parens();
+		Spaces(inParens);
 		List<TableExpr.Field> fields = new ArrayList<TableExpr.Field>();
 		ExpList.Builder builder = new ExpList.Builder();
-		while( Field(fields,builder) && FieldSep() );
+		while( Field(fields,builder,in) && FieldSep(inParens) );
 		if( !parser.match('}') )
 			throw parser.exception("Expected table element or '}'");
 		return parser.success( new TableExpr( se(start), fields.toArray(new TableExpr.Field[0]), builder.build() ) );
 	}
 
-	private boolean FieldSep() throws ParseException {
+	private boolean FieldSep(In in) throws ParseException {
 		if( !parser.anyOf(",;") )
 			return false;
-		Spaces(true);
+		Spaces(in);
 		return true;
 	}
 
-	private boolean Field(List<TableExpr.Field> fields,ExpList.Builder builder) throws ParseException {
+	private boolean Field(List<TableExpr.Field> fields,ExpList.Builder builder,In in) throws ParseException {
 		parser.begin();
-		Expr exp = SubExpr(true);
+		Expr exp = SubExpr(in);
 		if( exp==null )
-			exp = NameExpr(true);
+			exp = NameExpr(in);
 		if( exp!=null && parser.match('=') ) {
-			fields.add( new TableExpr.Field( exp, required(expr(Expr(true))) ) );
+			fields.add( new TableExpr.Field( exp, required(expr(Expr(in))) ) );
 			return parser.success();
 		}
 		parser.rollback();
-		Code code = Expr(true);
+		Code code = Expr(in);
 		if( code != null ) {
 			builder.add(code);
 			return parser.success();
@@ -771,75 +788,76 @@
 		return parser.failure();
 	}
 
-	private Code VarExp(boolean inParens) throws ParseException {
-		Var var = VarZ(inParens);
+	private Code VarExp(In in) throws ParseException {
+		Var var = VarZ(in);
 		return var==null ? null : var.expr();
 	}
 
-	private Var VarZ(boolean inParens) throws ParseException {
+	private Var VarZ(In in) throws ParseException {
 		int start = parser.begin();
-		Var var = VarStart(inParens);
+		Var var = VarStart(in);
 		if( var==null )
 			return parser.failure(null);
 		Var var2;
-		while( (var2=Var2(inParens,start,var.expr())) != null ) {
+		while( (var2=Var2(in,start,var.expr())) != null ) {
 			var = var2;
 		}
 		return parser.success(var);
 	}
 
-	private Var Var2(boolean inParens,int start,Code exp1) throws ParseException {
-		Var var = VarExt(inParens,start,exp1);
+	private Var Var2(In in,int start,Code exp1) throws ParseException {
+		Var var = VarExt(in,start,exp1);
 		if( var != null )
 			return var;
 		if( parser.match("->") ) {
-			Spaces(inParens);
+			Spaces(in);
 			ExpList.Builder builder = new ExpList.Builder();
 			builder.add(exp1);
-			Expr exp2 = expr(RequiredVarExpB(inParens));
-			FnCall fnCall = required(Args( inParens, start, exp2, builder ));
+			Expr exp2 = expr(RequiredVarExpB(in));
+			FnCall fnCall = required(Args( in, start, exp2, builder ));
 			return exprVar(fnCall);
 		}
-		FnCall fnCall = Args( inParens, start, expr(exp1), new ExpList.Builder() );
+		FnCall fnCall = Args( in, start, expr(exp1), new ExpList.Builder() );
 		if( fnCall != null )
 			return exprVar(fnCall);
 		return null;
 	}
 
-	private Code RequiredVarExpB(boolean inParens) throws ParseException {
+	private Code RequiredVarExpB(In in) throws ParseException {
 		int start = parser.begin();
-		Var var = required(VarStart(inParens));
+		Var var = required(VarStart(in));
 		Var var2;
-		while( (var2=VarExt(inParens,start,var.expr())) != null ) {
+		while( (var2=VarExt(in,start,var.expr())) != null ) {
 			var = var2;
 		}
 		return parser.success(var.expr());
 	}
 
-	private Var VarExt(boolean inParens,int start,Code exp1) throws ParseException {
+	private Var VarExt(In in,int start,Code exp1) throws ParseException {
 		parser.begin();
-		Expr exp2 = SubExpr(inParens);
+		Expr exp2 = SubExpr(in);
 		if( exp2 != null )
 			return parser.success(indexVar(start,expr(exp1),exp2));
 		if( parser.match('.') ) {
-			Spaces(inParens);
-			exp2 = NameExpr(inParens);
+			Spaces(in);
+			exp2 = NameExpr(in);
 			if( exp2!=null )
 				return parser.success(indexVar(start,expr(exp1),exp2));
 		}
 		return parser.failure(null);
 	}
 
-	private Var VarStart(boolean inParens) throws ParseException {
+	private Var VarStart(In in) throws ParseException {
 		int start = parser.begin();
 		if( parser.match('(') ) {
-			Spaces(true);
-			Expr exp = expr(Expr(true));
+			In inParens = in.parens();
+			Spaces(inParens);
+			Expr exp = expr(Expr(inParens));
 			RequiredMatch(')');
-			Spaces(inParens);
+			Spaces(in);
 			return parser.success(exprVar(exp));
 		}
-		String name = Name(inParens);
+		String name = Name(in);
 		if( name != null )
 			return parser.success(nameVar(start,name));
 		return parser.failure(null);
@@ -911,33 +929,34 @@
 		};
 	}
 
-	private FnCall Args(boolean inParens,int start,Expr fn,ExpList.Builder builder) throws ParseException {
+	private FnCall Args(In in,int start,Expr fn,ExpList.Builder builder) throws ParseException {
 		parser.begin();
-		return args(inParens,builder)
+		return args(in,builder)
 			? parser.success( new FnCall( se(start), fn, builder.build() ) )
 			: parser.failure((FnCall)null);
 	}
 
-	private boolean args(boolean inParens,ExpList.Builder builder) throws ParseException {
+	private boolean args(In in,ExpList.Builder builder) throws ParseException {
 		if( parser.match('(') ) {
-			Spaces(true);
-			ExpList(true,builder);  // optional
+			In inParens = in.parens();
+			Spaces(inParens);
+			ExpList(inParens,builder);  // optional
 			if( !parser.match(')') )
 				throw parser.exception("Expression or ')' expected");
-			Spaces(inParens);
+			Spaces(in);
 			return true;
 		}
-		Expr exp = TableExpr(inParens);
+		Expr exp = TableExpr(in);
 		if( exp != null ) {
 			builder.add(exp);
 			return true;
 		}
-		String s = StringLiteral(inParens);
+		String s = StringLiteral(in);
 		if( s != null ) {
 			builder.add( new ConstExpr(s) );
 			return true;
 		}
-		Expressions exps = JspExpressions();
+		Expressions exps = JspExpressions(in);
 		if( exps != null ) {
 			builder.add(exps);
 			return true;
@@ -945,57 +964,50 @@
 		return false;
 	}
 
-	private Expressions ExpList() throws ParseException {
-		return ExpList(false);
+	private Expressions ExpList(In in) throws ParseException {
+		ExpList.Builder builder = new ExpList.Builder();
+		return ExpList(in,builder) ? builder.build() : null;
 	}
 
-	private Expressions ExpList(boolean inParens) throws ParseException {
-		ExpList.Builder builder = new ExpList.Builder();
-		return ExpList(inParens,builder) ? builder.build() : null;
-	}
-
-	private boolean ExpList(boolean inParens,ExpList.Builder builder) throws ParseException {
+	private boolean ExpList(In in,ExpList.Builder builder) throws ParseException {
 		parser.begin();
-		Code exp = Expr(inParens);
+		Code exp = Expr(in);
 		if( exp==null )
 			return parser.failure();
 		builder.add(exp);
 		while( parser.match(',') ) {
-			Spaces(inParens);
-			builder.add( RequiredExpr(inParens) );
+			Spaces(in);
+			builder.add( RequiredExpr(in) );
 		}
 		return parser.success();
 	}
 
-	private Expr SubExpr(boolean inParens) throws ParseException {
+	private Expr SubExpr(In in) throws ParseException {
 		parser.begin();
 		if( !parser.match('[') )
 			return parser.failure(null);
-		Spaces(true);
-		Expr exp = expr(RequiredExpr(true));
+		In inParens = in.parens();
+		Spaces(inParens);
+		Expr exp = expr(RequiredExpr(inParens));
 		RequiredMatch(']');
-		Spaces(inParens);
+		Spaces(in);
 		return parser.success(exp);
 	}
 
-	private Expr NameExpr(boolean inParens) throws ParseException {
-		String name = Name(inParens);
+	private Expr NameExpr(In in) throws ParseException {
+		String name = Name(in);
 		return name==null ? null : new ConstExpr(name);
 	}
 
-	private String RequiredName() throws ParseException {
+	private String RequiredName(In in) throws ParseException {
 		parser.begin();
-		String name = Name();
+		String name = Name(in);
 		if( name==null )
 			parser.exception("Name expected");
 		return parser.success(name);
 	}
 
-	private String Name() throws ParseException {
-		return Name(false);
-	}
-
-	private String Name(boolean inParens) throws ParseException {
+	private String Name(In in) throws ParseException {
 		int start = parser.begin();
 		if( !NameFirstChar() )
 			return parser.failure(null);
@@ -1003,7 +1015,7 @@
 		String match = parser.textFrom(start);
 		if( keywords.contains(match) )
 			return parser.failure(null);
-		Spaces(inParens);
+		Spaces(in);
 		return parser.success(match);
 	}
 
@@ -1025,24 +1037,16 @@
 			throw parser.exception("'"+s+"' expected");
 	}
 
-	private void RequiredKeyword(String keyword) throws ParseException {
-		RequiredKeyword(keyword,false);
-	}
-
-	private boolean Keyword(String keyword) throws ParseException {
-		return Keyword(keyword,false);
-	}
-
-	private void RequiredKeyword(String keyword,boolean inParens) throws ParseException {
-		if( !Keyword(keyword,inParens) )
+	private void RequiredKeyword(String keyword,In in) throws ParseException {
+		if( !Keyword(keyword,in) )
 			throw parser.exception("'"+keyword+"' expected");
 	}
 
-	private boolean Keyword(String keyword,boolean inParens) throws ParseException {
+	private boolean Keyword(String keyword,In in) throws ParseException {
 		parser.begin();
 		if( !parser.match(keyword) || NameChar() )
 			return parser.failure();
-		Spaces(inParens);
+		Spaces(in);
 		return parser.success();
 	}
 
@@ -1074,34 +1078,34 @@
 		"while"
 	));
 
-	private Expr Literal(boolean inParens) throws ParseException {
-		if( NilLiteral(inParens) )
+	private Expr Literal(In in) throws ParseException {
+		if( NilLiteral(in) )
 			return new ConstExpr(null);
-		Boolean b = BooleanLiteral(inParens);
+		Boolean b = BooleanLiteral(in);
 		if( b != null )
 			return new ConstExpr(b);
-		Number n = NumberLiteral(inParens);
+		Number n = NumberLiteral(in);
 		if( n != null )
 			return new ConstExpr(n);
-		String s = StringLiteral(inParens);
+		String s = StringLiteral(in);
 		if( s != null )
 			return new ConstExpr(s);
 		return null;
 	}
 
-	private boolean NilLiteral(boolean inParens) throws ParseException {
-		return Keyword("nil",inParens);
+	private boolean NilLiteral(In in) throws ParseException {
+		return Keyword("nil",in);
 	}
 
-	private Boolean BooleanLiteral(boolean inParens) throws ParseException {
-		if( Keyword("true",inParens) )
+	private Boolean BooleanLiteral(In in) throws ParseException {
+		if( Keyword("true",in) )
 			return true;
-		if( Keyword("false",inParens) )
+		if( Keyword("false",in) )
 			return false;
 		return null;
 	}
 
-	private Number NumberLiteral(boolean inParens) throws ParseException {
+	private Number NumberLiteral(In in) throws ParseException {
 		parser.begin();
 		Number n;
 		if( parser.matchIgnoreCase("0x") ) {
@@ -1111,7 +1115,7 @@
 		}
 		if( n==null || NameChar() )
 			return parser.failure(null);
-		Spaces(inParens);
+		Spaces(in);
 		return parser.success(n);
 	}
 
@@ -1189,14 +1193,14 @@
 		return Digit() || parser.anyOf("abcdefABCDEF");
 	}
 
-	private String StringLiteral(boolean inParens) throws ParseException {
+	private String StringLiteral(In in) throws ParseException {
 		String s;
 		if( (s=QuotedString('"'))==null
 			&& (s=QuotedString('\''))==null
 			&& (s=LongString())==null
 		)
 			return null;
-		Spaces(inParens);
+		Spaces(in);
 		return s;
 	}
 
@@ -1261,12 +1265,8 @@
 		return parser.failure(null);
 	}
 
-	private void Spaces() throws ParseException {
-		Spaces(false);
-	}
-
-	private void Spaces(boolean inParens) throws ParseException {
-		while( parser.anyOf(" \t") || Comment() || ContinueOnNextLine() || inParens && NewLine() );
+	private void Spaces(In in) throws ParseException {
+		while( parser.anyOf(" \t") || Comment() || ContinueOnNextLine() || in.parens && NewLine() );
 	}
 
 	private boolean ContinueOnNextLine() {
--- a/src/luan/lib/IoLib.java	Sun Jun 08 03:38:25 2014 +0000
+++ b/src/luan/lib/IoLib.java	Mon Jun 09 09:16:16 2014 +0000
@@ -9,6 +9,7 @@
 import java.io.FileWriter;
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
+import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
@@ -16,6 +17,8 @@
 import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
 import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
 import java.io.IOException;
 import java.net.URL;
 import java.net.Socket;
@@ -300,7 +303,7 @@
 			return binaryWriter(new BufferedOutputStream(outputStream()));
 		}
 
-		LuanTable table() {
+		@Override LuanTable table() {
 			LuanTable tbl = super.table();
 			try {
 				tbl.put( "write", new LuanJavaFunction(
@@ -373,6 +376,33 @@
 		OutputStream outputStream() throws IOException {
 			return socket.getOutputStream();
 		}
+
+		public LuanTable pickle_client(LuanState luan) throws IOException {
+			DataInputStream in = new DataInputStream(new BufferedInputStream(inputStream()));
+			DataOutputStream out = new DataOutputStream(new BufferedOutputStream(outputStream()));
+			return new PickleClient(luan,in,out).table();
+		}
+
+		public void run_pickle_server(LuanState luan) throws IOException {
+			DataInputStream in = new DataInputStream(new BufferedInputStream(inputStream()));
+			DataOutputStream out = new DataOutputStream(new BufferedOutputStream(outputStream()));
+			new PickleServer(luan,in,out).run();
+		}
+
+		@Override LuanTable table() {
+			LuanTable tbl = super.table();
+			try {
+				tbl.put( "pickle_client", new LuanJavaFunction(
+					LuanSocket.class.getMethod( "pickle_client", LuanState.class ), this
+				) );
+				tbl.put( "run_pickle_server", new LuanJavaFunction(
+					LuanSocket.class.getMethod( "run_pickle_server", LuanState.class ), this
+				) );
+			} catch(NoSuchMethodException e) {
+				throw new RuntimeException(e);
+			}
+			return tbl;
+		}
 	}
 
 	public static LuanTable socket(String host,int port) throws IOException {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/lib/PickleClient.java	Mon Jun 09 09:16:16 2014 +0000
@@ -0,0 +1,60 @@
+package luan.lib;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import luan.Luan;
+import luan.LuanState;
+import luan.LuanException;
+import luan.LuanTable;
+import luan.LuanJavaFunction;
+
+
+public final class PickleClient {
+
+	private final PickleCon con;
+
+	PickleClient(LuanState luan,DataInputStream in,DataOutputStream out) {
+		con = new PickleCon(luan,in,out);
+	}
+
+	public Object call(Object... args) throws LuanException, IOException {
+		con.write(args);
+		Object[] result = Luan.array(con.read());
+		boolean ok = (boolean)result[0];
+		if( ok ) {
+			Object[] rtn = new Object[result.length-1];
+			System.arraycopy(result,1,rtn,0,rtn.length);
+			return rtn;
+		} else {
+			String msg = (String)result[1];
+			String src = (String)result[2];
+			throw con.luan.JAVA.exception(
+				msg + "\n"
+				+ "in:\n"
+				+ "------------------\n"
+				+ src + "\n"
+				+ "------------------\n"
+			);
+		}
+	}
+
+	LuanTable table() {
+		LuanTable tbl = new LuanTable();
+		try {
+			tbl.put( "pickle", new LuanJavaFunction(
+				PickleCon.class.getMethod( "pickle", Object.class ), con
+			) );
+			tbl.put( "call", new LuanJavaFunction(
+				PickleClient.class.getMethod( "call", new Object[0].getClass() ), this
+			) );
+			tbl.put( "close", new LuanJavaFunction(
+				PickleCon.class.getMethod( "close" ), con
+			) );
+		} catch(NoSuchMethodException e) {
+			throw new RuntimeException(e);
+		}
+		return tbl;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/lib/PickleCon.java	Mon Jun 09 09:16:16 2014 +0000
@@ -0,0 +1,128 @@
+package luan.lib;
+
+import java.io.OutputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Set;
+import java.util.IdentityHashMap;
+import java.util.Collections;
+import java.util.Map;
+import java.util.List;
+import java.util.ArrayList;
+import luan.Luan;
+import luan.LuanTable;
+import luan.LuanState;
+import luan.LuanFunction;
+import luan.LuanJavaFunction;
+import luan.LuanException;
+
+
+public final class PickleCon {
+	final LuanState luan;
+	private final DataInputStream in;
+	private final LuanFunction _read_binary;
+	private final LuanTable ioModule;
+	private final DataOutputStream out;
+	private final List<byte[]> binaries = new ArrayList<byte[]>();
+	String src;
+
+	PickleCon(LuanState luan,DataInputStream in,DataOutputStream out) {
+		this.in = in;
+		this.luan = luan;
+		try {
+			this._read_binary = new LuanJavaFunction(
+				PickleCon.class.getMethod( "_read_binary", Integer.TYPE ), this
+			);
+		} catch(NoSuchMethodException e) {
+			throw new RuntimeException(e);
+		}
+		this.ioModule = (LuanTable)luan.loaded().get("Io");
+
+		this.out = out;
+	}
+
+	public byte[] _read_binary(int size) throws IOException, LuanException {
+		byte[] a = new byte[size];
+		int i = 0;
+		while( i < size ) {
+			int n = in.read(a,i,size-i);
+			if( n == -1 )
+				throw luan.JAVA.exception( "end of stream" );
+			i += n;
+		}
+		return a;
+	}
+
+	public Object read() throws IOException, LuanException {
+		ioModule.put("_read_binary",_read_binary);
+		src = in.readUTF();
+		LuanFunction fn = BasicLib.load(luan,src,"pickle-reader",true,false);
+		Object rtn = luan.JAVA.call(fn,null);
+		ioModule.put("_binaries",null);
+		return rtn;
+	}
+
+	public String pickle(Object obj) throws LuanException {
+		if( obj == null )
+			return "nil";
+		if( obj instanceof Number )
+			return Luan.toString((Number)obj);
+		if( obj instanceof String )
+			return "\"" + Luan.stringEncode((String)obj) + "\"";
+		if( obj instanceof LuanTable )
+			return pickle( (LuanTable)obj, Collections.newSetFromMap(new IdentityHashMap<LuanTable,Boolean>()) );
+		if( obj instanceof byte[] ) {
+			byte[] a = (byte[])obj;
+			binaries.add(a);
+			return "Io._binaries[" + binaries.size() + "]";
+		}
+		throw luan.JAVA.exception( "invalid type: " + obj.getClass() );
+	}
+
+	private String pickle(Object obj,Set<LuanTable> set) throws LuanException {
+		return obj instanceof LuanTable ? pickle((LuanTable)obj,set) : pickle(obj);
+	}
+
+	private String pickle(LuanTable tbl,Set<LuanTable> set) throws LuanException {
+		if( !set.add(tbl) ) {
+			throw luan.JAVA.exception( "circular reference in table" );
+		}
+		StringBuilder sb = new StringBuilder();
+		sb.append( "{" );
+		for( Map.Entry<Object,Object> entry : tbl ) {
+			sb.append( "[" );
+			sb.append( pickle(entry.getKey(),set) );
+			sb.append( "]=" );
+			sb.append( pickle(entry.getValue(),set) );
+			sb.append( ", " );
+		}
+		sb.append( "}" );
+		return sb.toString();
+	}
+
+	public void write(Object... args) throws LuanException, IOException {
+		StringBuilder sb = new StringBuilder();
+		if( !binaries.isEmpty() ) {
+			sb.append( "Io._binaries = {}\n" );
+			for( byte[] a : binaries ) {
+				sb.append( "Io._binaries[#Io._binaries+1] = Io._read_binary(" + a.length + ")\n" );
+			}
+		}
+		for( Object obj : args ) {
+			sb.append( luan.JAVA.toString(obj) );
+		}
+		out.writeUTF( sb.toString() );
+		for( byte[] a : binaries ) {
+			out.write(a);
+		}
+		out.flush();
+		binaries.clear();
+	}
+
+	public void close() throws IOException {
+		in.close();
+		out.close();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/lib/PickleServer.java	Mon Jun 09 09:16:16 2014 +0000
@@ -0,0 +1,62 @@
+package luan.lib;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.EOFException;
+import java.util.List;
+import java.util.ArrayList;
+import luan.Luan;
+import luan.LuanState;
+import luan.LuanException;
+
+
+final class PickleServer {
+
+	private final PickleCon con;
+
+	PickleServer(LuanState luan,DataInputStream in,DataOutputStream out) {
+		con = new PickleCon(luan,in,out);
+	}
+
+	void next() throws IOException {
+		try {
+			List<String> list = new ArrayList<String>();
+			try {
+				Object[] result = Luan.array(con.read());
+				list.add( "return true" );
+				for( Object obj : result ) {
+					list.add( ", " );
+					list.add( con.pickle(obj) );
+				}
+			} catch(LuanException e) {
+				list.add( "return false, " );
+				list.add( con.pickle(e.getMessage()) );
+				list.add( ", " );
+				list.add( con.pickle(con.src) );
+			}
+			list.add( "\n" );
+			con.write( list.toArray() );
+		} catch(LuanException e2) {
+			throw new RuntimeException(e2);
+		}
+	}
+
+	public void run() {
+		try {
+			while( true ) {
+				next();
+			}
+		} catch(EOFException e) {
+			// done
+		} catch(IOException e) {
+			e.printStackTrace();
+		}
+		try {
+			con.close();
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+}
--- a/src/luan/tools/CmdLine.java	Sun Jun 08 03:38:25 2014 +0000
+++ b/src/luan/tools/CmdLine.java	Mon Jun 09 09:16:16 2014 +0000
@@ -18,7 +18,8 @@
 			LuanFunction standalone = (LuanFunction)luan.get("Basic.standalone");
 			luan.JAVA.call(standalone,"standalone",args);
 		} catch(LuanException e) {
-			System.err.println(e.getMessage());
+//			System.err.println(e.getMessage());
+			e.printStackTrace();
 			System.exit(-1);
 		}
 		System.exit(0);