diff src/luan/impl/LuanParser.java @ 775:1a68fc55a80c

simplify dir structure
author Franklin Schmidt <fschmidt@gmail.com>
date Fri, 26 Aug 2016 14:36:40 -0600
parents core/src/luan/impl/LuanParser.java@47add8eeb513
children d69d3c51c44e
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/impl/LuanParser.java	Fri Aug 26 14:36:40 2016 -0600
@@ -0,0 +1,2004 @@
+package luan.impl;
+
+//import java.io.StringWriter;
+//import java.io.PrintWriter;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Arrays;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+import luan.Luan;
+import luan.LuanState;
+import luan.LuanTable;
+import luan.modules.PackageLuan;
+
+
+final class LuanParser {
+
+	private interface Sym {
+		public Expr exp();
+	}
+
+	private int symCounter = 0;
+
+	private class LocalSym implements Sym {
+		final String name;
+		final String javaName;
+		boolean isPointer = false;
+
+		LocalSym(String name) {
+			this.name = name;
+			this.javaName = name + "_" + (++symCounter);
+		}
+
+		Stmts declaration(Expr value) {
+			Stmts stmt = new Stmts();
+			if( value==null ) {
+				stmt.add( new Object() {
+					@Override public String toString() {
+						if( !isPointer )
+							return "Object " + javaName + ";  ";
+						else
+							return "final Pointer " + javaName + " = new Pointer();  ";
+					}
+				} );
+			} else {
+				if( value.valType != Val.SINGLE )  throw new RuntimeException();
+				stmt.add( new Object() {
+					@Override public String toString() {
+						if( !isPointer )
+							return "Object " + javaName + " = ";
+						else
+							return "final Pointer " + javaName + " = new Pointer(";
+					}
+				} );
+				stmt.addAll(value);
+				stmt.add( new Object() {
+					@Override public String toString() {
+						if( !isPointer )
+							return ";  ";
+						else
+							return ");  ";
+					}
+				} );
+			}
+			return stmt;
+		}
+
+		@Override public Expr exp() {
+			Expr exp = new Expr(Val.SINGLE,false);
+			exp.add( new Object() {
+				@Override public String toString() {
+					if( !isPointer )
+						return javaName;
+					else
+						return javaName + ".o";
+				}
+			} );
+			return exp;
+		}
+	}
+
+	private class UpSym implements Sym {
+		final String name;
+		final int i;
+		final String value;
+
+		UpSym(String name,int i,String value) {
+			this.name = name;
+			this.i = i;
+			this.value = value;
+		}
+
+		String init() {
+			return "upValues[" + i + "] = " + value + ";  ";
+		}
+
+		@Override public Expr exp() {
+			Expr exp = new Expr(Val.SINGLE,false);
+			exp.add( new Object() {
+				@Override public String toString() {
+					return "upValues[" + i + "].o";
+				}
+			} );
+			return exp;
+		}
+	}
+
+	private final class Frame {
+		final Frame parent;
+		final List<LocalSym> symbols = new ArrayList<LocalSym>();
+		int loops = 0;
+		boolean isVarArg = false;
+		final List<UpSym> upValueSymbols = new ArrayList<UpSym>();
+
+		Frame() {
+			this.parent = null;
+		}
+
+		Frame(Frame parent) {
+			this.parent = parent;
+		}
+
+		LocalSym addLocalSym(String name) {
+			LocalSym sym = new LocalSym(name);
+			symbols.add(sym);
+			return sym;
+		}
+
+		UpSym addUpSym(String name,String value) {
+			UpSym sym = new UpSym( name, upValueSymbols.size(), value );
+			upValueSymbols.add(sym);
+			return sym;
+		}
+
+		LocalSym getLocalSym(String name) {
+			int i = symbols.size();
+			while( --i >= 0 ) {
+				LocalSym sym = symbols.get(i);
+				if( sym.name.equals(name) )
+					return sym;
+			}
+			return null;
+		}
+
+		UpSym getUpSym(String name) {
+			for( UpSym upSym : upValueSymbols ) {
+				if( upSym.name.equals(name) )
+					return upSym;
+			}
+			if( parent != null ) {
+				LocalSym sym = parent.getLocalSym(name);
+				if( sym != null ) {
+					sym.isPointer = true;
+					return addUpSym(name,sym.javaName);
+				}
+				UpSym upSym = parent.getUpSym(name);
+				if( upSym != null ) {
+					return addUpSym(name,"parentUpValues["+upSym.i+"]");
+				}
+			}
+			return null;
+		}
+
+		Sym getSym(String name) {
+			Sym sym = getLocalSym(name);
+			return sym != null ? sym : getUpSym(name);
+		}
+
+	}
+
+	private static class In {
+		static final In NOTHING = new In(false);
+
+		final boolean template;
+
+		private In(boolean template) {
+			this.template = template;
+		}
+
+		In template() {
+			return template ? this : new In(true);
+		}
+	}
+
+	private Frame frame;
+	private final Parser parser;
+	private final Stmts top;
+
+	LuanParser(String sourceText,String sourceName) {
+		this.frame = new Frame();
+		this.parser = new Parser(sourceText,sourceName);
+		this.top = new Stmts();
+	}
+
+	void addVar(String name) {
+		UpSym upSym = frame.addUpSym( "-ADDED-" ,"new Pointer()");
+		LocalSym sym = frame.addLocalSym( name );
+		sym.isPointer = true;
+		top.add( "final Pointer " + sym.javaName + " = upValues[" + upSym.i + "];  " );
+	}
+
+	private int symbolsSize() {
+		return frame.symbols.size();
+	}
+
+	private Stmts addSymbol(String name,Expr value) {
+		final LocalSym sym = frame.addLocalSym(name);
+		return sym.declaration(value);
+	}
+
+	private Sym getSym(String name) {
+		return frame.getSym(name);
+	}
+
+	private void popSymbols(int n) {
+		List<LocalSym> symbols = frame.symbols;
+		while( n-- > 0 ) {
+			symbols.remove(symbols.size()-1);
+		}
+	}
+
+	private void incLoops() {
+		frame.loops++;
+	}
+
+	private void decLoops() {
+		frame.loops--;
+	}
+
+	private <T> T required(T t) throws ParseException {
+		if( t==null )
+			throw parser.exception();
+		return t;
+	}
+
+	private <T> T required(T t,String msg) throws ParseException {
+		if( t==null )
+			throw parser.exception(msg);
+		return t;
+	}
+
+	private Class newFnClass(Stmts stmt) {
+		return toFnClass( stmt, frame.upValueSymbols );
+	}
+
+	private Expr newFnExp(Stmts stmt,String name) {
+		return toFnExp( stmt, frame.upValueSymbols, name );
+	}
+/*
+	Class Expression() throws ParseException {
+		Spaces();
+		parser.begin();
+		Expr expr = ExprZ(In.NOTHING);
+		if( expr != null && parser.endOfInput() ) {
+			top.add( "return " );
+			top.addAll( expr );
+			top.add( ";  " );
+			top.hasReturn = true;
+			return parser.success(newFnClass(top));
+		}
+		return parser.failure(null);
+	}
+*/
+	Class RequiredModule() throws ParseException {
+		GetRequiredModule();
+		return newFnClass(top);
+	}
+
+	String RequiredModuleSource() throws ParseException {
+		GetRequiredModule();
+		return toFnString( top, frame.upValueSymbols );
+	}
+
+	void GetRequiredModule() throws ParseException {
+		Spaces();
+		parser.begin();
+		frame.isVarArg = true;
+		top.add( "final Object[] varArgs = LuanImpl.varArgs(args,0);  " );
+		Stmts block = RequiredBlock();
+		top.addAll( block );
+		top.hasReturn = block.hasReturn;
+		if( !parser.endOfInput() )
+			throw parser.exception();
+		parser.success();
+	}
+
+	private Stmts RequiredBlock() throws ParseException {
+		Stmts stmts = new Stmts();
+		int stackStart = symbolsSize();
+		do {
+			Spaces();
+			stmts.addNewLines();
+			Stmts stmt = Stmt();
+			if( stmt != null ) {
+				stmts.addAll(stmt);
+				stmts.hasReturn = stmt.hasReturn;
+			}
+		} while( !stmts.hasReturn && (StmtSep() || TemplateSep(stmts)) );
+		Spaces();
+		while( StmtSep() )
+			Spaces();
+		stmts.addNewLines();
+		int stackEnd = symbolsSize();
+		popSymbols( stackEnd - stackStart );
+		return stmts;
+	}
+
+	private boolean StmtSep() throws ParseException {
+		return parser.match( ';' ) || EndOfLine();
+	}
+
+	private boolean TemplateSep(Stmts stmts) throws ParseException {
+		Stmts stmt = TemplateStmt();
+		if( stmt != null ) {
+			stmts.addAll(stmt);
+			return true;
+		}
+		return false;
+	}
+
+	private boolean EndOfLine() {
+		if( MatchEndOfLine() ) {
+			parser.sb().append('\n');
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	private boolean MatchEndOfLine() {
+		return parser.match( "\r\n" ) || parser.match( '\r' ) || parser.match( '\n' );
+	}
+
+	private Stmts Stmt() throws ParseException {
+		Stmts stmt;
+		if( (stmt=ReturnStmt()) != null
+			|| (stmt=FunctionStmt()) != null
+			|| (stmt=LocalStmt()) != null
+			|| (stmt=LocalFunctionStmt()) != null
+			|| (stmt=BreakStmt()) != null
+			|| (stmt=ForStmt()) != null
+			|| (stmt=DoStmt()) != null
+			|| (stmt=WhileStmt()) != null
+			|| (stmt=RepeatStmt()) != null
+			|| (stmt=IfStmt()) != null
+			|| (stmt=SetStmt()) != null
+			|| (stmt=ExpressionsStmt()) != null
+		) {
+			return stmt;
+		}
+		return null;
+	}
+
+	private Expr indexExpStr(Expr exp1,Expr exp2) {
+		Expr exp = new Expr(Val.SINGLE,false);
+		exp.add( "luan.index(" );
+		exp.addAll( exp1.single() );
+		exp.add( "," );
+		exp.addAll( exp2.single() );
+		exp.add( ")" );
+		return exp;
+	}
+
+	private Expr callExpStr(Expr fn,Expr args) {
+		Expr exp = new Expr(null,true);
+		exp.add( "Luan.checkFunction(" );
+		exp.addAll( fn.single() );
+		exp.add( ").call(luan," );
+		exp.addAll( args.array() );
+		exp.add( ")" );
+		return exp;
+	}
+
+	private Stmts TemplateStmt() throws ParseException {
+		Expr exprs = TemplateExpressions(In.NOTHING);
+		if( exprs == null )
+			return null;
+		Stmts stmt = new Stmts();
+		stmt.add( "Luan.checkFunction(luan.index(PackageLuan.require(luan,\"luan:Io.luan\"),\"template_write\")).call(luan," );
+		stmt.addAll( exprs.array() );
+		stmt.add( ");  " );
+		return stmt;
+	}
+
+	private Expr TemplateExpressions(In in) throws ParseException {
+		if( in.template )
+			return null;
+		int start = parser.begin();
+		if( !parser.match( "%>" ) )
+			return parser.failure(null);
+		EndOfLine();
+		In inTemplate = in.template();
+		List<Expr> builder = new ArrayList<Expr>();
+		while(true) {
+			if( parser.match( "<%=" ) ) {
+				Spaces();
+				Expr exp = new Expr(Val.SINGLE,false);
+				exp.addAll( RequiredExpr(inTemplate).single() );
+				builder.add(exp);
+				RequiredMatch( "%>" );
+			} else if( parser.match( "<%" ) ) {
+				Spaces();
+				return parser.success(expString(builder));
+			} else {
+				Expr exp = new Expr(Val.SINGLE,false);
+				int i = parser.currentIndex();
+				do {
+					if( parser.match( "%>" ) )
+						throw parser.exception("'%>' unexpected");
+					if( !(EndOfLine() || parser.anyChar()) )
+						throw parser.exception("Unclosed template expression");
+				} while( !parser.test( "<%" ) );
+				String match = parser.textFrom(i);
+				String rtns = parser.sb().toString();
+				parser.sb().setLength(0);
+				exp.addAll( constExpStr(match) );
+				if( rtns.length() > 0 )
+					exp.add(rtns);
+				builder.add(exp);
+			}
+		}
+	}
+
+	private Stmts ReturnStmt() throws ParseException {
+		parser.begin();
+		if( !Keyword("return") )
+			return parser.failure(null);
+		Expr exprs = ExpStringList(In.NOTHING);
+		Stmts stmt = new Stmts();
+		stmt.add( "return " );
+		if( exprs != null )
+			stmt.addAll( exprs );
+		else
+			stmt.add( "LuanFunction.NOTHING" );
+		stmt.add( ";  " );
+		stmt.hasReturn = true;
+		return parser.success( stmt );
+	}
+
+	private Stmts FunctionStmt() throws ParseException {
+		parser.begin();
+		if( !Keyword("function") )
+			return parser.failure(null);
+
+		parser.currentIndex();
+		String name = RequiredName();
+		Var var = nameVar(name);
+		while( parser.match( '.' ) ) {
+			Spaces();
+//			Expr exp = NameExpr();
+			name = Name();
+			if( name==null )
+				return parser.failure(null);
+			var = indexVar( var.exp(), constExpStr(name) );
+		}
+
+		Expr fnDef = RequiredFunction(name);
+		return parser.success( var.set(fnDef) );
+	}
+
+	private Stmts LocalFunctionStmt() throws ParseException {
+		parser.begin();
+		if( !(Keyword("local") && Keyword("function")) )
+			return parser.failure(null);
+		Stmts stmt = new Stmts();
+		String name = RequiredName();
+		stmt.addAll( addSymbol(name,null) );
+		Expr fnDef = RequiredFunction(name);
+		stmt.addAll( nameVar(name).set(fnDef) );
+		return parser.success( stmt );
+	}
+
+	private Stmts BreakStmt() throws ParseException {
+		parser.begin();
+		if( !Keyword("break") )
+			return parser.failure(null);
+		if( frame.loops <= 0 )
+			throw parser.exception("'break' outside of loop");
+		Stmts stmt = new Stmts();
+		stmt.add( "break;  " );
+		return parser.success( stmt );
+	}
+
+	int forCounter = 0;
+
+	private Stmts ForStmt() throws ParseException {
+		parser.begin();
+		int stackStart = symbolsSize();
+		if( !Keyword("for") )
+			return parser.failure(null);
+		List<String> names = RequiredNameList();
+		if( !Keyword("in") )
+			return parser.failure(null);
+		Expr expr = RequiredExpr(In.NOTHING).single();
+		RequiredKeyword("do");
+
+		String fnVar = "fn"+ ++forCounter;
+		Expr fnExp = new Expr(null,false);
+		fnExp.add( fnVar + ".call(luan)" );
+		Stmts stmt = new Stmts();
+		stmt.add( ""
+			+"LuanFunction "+fnVar+" = Luan.checkFunction("
+		);
+		stmt.addAll( expr );
+		stmt.add( ");  " );
+		stmt.add( "while(true) {  " );
+		stmt.addAll( makeLocalSetStmt(names,fnExp) );
+		stmt.add( "if( " );
+		stmt.addAll( nameVar(names.get(0)).exp() );
+ 		stmt.add( "==null )  break;  " );
+		Stmts loop = RequiredLoopBlock();
+		RequiredKeyword("end");
+		stmt.addAll( loop );
+		stmt.add( "}  " );
+		popSymbols( symbolsSize() - stackStart );
+		return parser.success(stmt);
+	}
+
+	private Stmts DoStmt() throws ParseException {
+		parser.begin();
+		if( !Keyword("do") )
+			return parser.failure(null);
+		Stmts stmt = RequiredBlock();
+		RequiredKeyword("end");
+		return parser.success(stmt);
+	}
+
+	private Stmts LocalStmt() throws ParseException {
+		parser.begin();
+		if( !Keyword("local") )
+			return parser.failure(null);
+		List<String> names = NameList();
+		if( names==null ) {
+			if( Keyword("function") )
+				return parser.failure(null);  // handled later
+			throw parser.exception("Invalid local statement");
+		}
+		Stmts stmt = new Stmts();
+		if( parser.match( '=' ) ) {
+			Spaces();
+			Expr values = ExpStringList(In.NOTHING);
+			if( values==null )
+				throw parser.exception("Expressions expected");
+			stmt.addAll( makeLocalSetStmt(names,values) );
+		} else {
+			Expr value = new Expr(Val.SINGLE,false);
+			value.add( "null" );
+			for( String name : names ) {
+				stmt.addAll( addSymbol(name,value) );
+			}
+		}
+		return parser.success(stmt);
+	}
+
+	private List<String> RequiredNameList() throws ParseException {
+		parser.begin();
+		List<String> names = NameList();
+		if( names==null )
+			throw parser.exception("Name expected");
+		return parser.success(names);
+	}
+
+	private List<String> NameList() throws ParseException {
+		String name = Name();
+		if( name==null )
+			return null;
+		List<String> names = new ArrayList<String>();
+		names.add(name);
+		while( (name=anotherName()) != null ) {
+			names.add(name);
+		}
+		return names;
+	}
+
+	private String anotherName() throws ParseException {
+		parser.begin();
+		if( !parser.match( ',' ) )
+			return parser.failure(null);
+		Spaces();
+		String name = Name();
+		if( name==null )
+			return parser.failure(null);
+		return parser.success(name);
+	}
+
+	private Stmts WhileStmt() throws ParseException {
+		parser.begin();
+		if( !Keyword("while") )
+			return parser.failure(null);
+		Expr cnd = RequiredExpr(In.NOTHING).single();
+		RequiredKeyword("do");
+		Stmts loop = RequiredLoopBlock();
+		RequiredKeyword("end");
+		Stmts stmt = new Stmts();
+		stmt.add( "while( Luan.checkBoolean(" );
+		stmt.addAll( cnd );
+		stmt.add( ") ) {  " );
+		stmt.addAll( loop );
+		stmt.add( "}  " );
+		return parser.success( stmt );
+	}
+
+	private Stmts RepeatStmt() throws ParseException {
+		parser.begin();
+		if( !Keyword("repeat") )
+			return parser.failure(null);
+		Stmts loop =RequiredLoopBlock();
+		RequiredKeyword("until");
+		Expr cnd = RequiredExpr(In.NOTHING).single();
+		Stmts stmt = new Stmts();
+		stmt.add( "do {  " );
+		stmt.addAll( loop );
+		stmt.add( "} while( !Luan.checkBoolean(" );
+		stmt.addAll( cnd );
+		stmt.add( ") );  " );
+		return parser.success( stmt );
+	}
+
+	private Stmts RequiredLoopBlock() throws ParseException {
+		incLoops();
+		Stmts stmt = RequiredBlock();
+		decLoops();
+		return stmt;
+	}
+
+	private Stmts IfStmt() throws ParseException {
+		parser.begin();
+		if( !Keyword("if") )
+			return parser.failure(null);
+		Stmts stmt = new Stmts();
+		Expr cnd;
+		Stmts block;
+		boolean hasReturn = true;
+		cnd = RequiredExpr(In.NOTHING).single();
+		RequiredKeyword("then");
+		block = RequiredBlock();
+		stmt.add( "if( Luan.checkBoolean(" );
+		stmt.addAll( cnd );
+		stmt.add( ") ) {  " );
+		stmt.addAll( block );
+		if( !block.hasReturn )
+			hasReturn = false;
+		while( Keyword("elseif") ) {
+			cnd = RequiredExpr(In.NOTHING).single();
+			RequiredKeyword("then");
+			block = RequiredBlock();
+			stmt.add( "} else if( Luan.checkBoolean(" );
+			stmt.addAll( cnd );
+			stmt.add( ") ) {  " );
+			stmt.addAll( block );
+			if( !block.hasReturn )
+				hasReturn = false;
+		}
+		if( Keyword("else") ) {
+			block = RequiredBlock();
+			stmt.add( "} else {  " );
+			stmt.addAll( block );
+			if( !block.hasReturn )
+				hasReturn = false;
+		} else {
+			hasReturn = false;
+		}
+		RequiredKeyword("end");
+		stmt.add( "}  " );
+		stmt.hasReturn = hasReturn;
+		return parser.success( stmt );
+	}
+
+	private Stmts SetStmt() throws ParseException {
+		parser.begin();
+		List<Var> vars = new ArrayList<Var>();
+		Var v = SettableVar();
+		if( v == null )
+			return parser.failure(null);
+		vars.add(v);
+		while( parser.match( ',' ) ) {
+			Spaces();
+			v = SettableVar();
+			if( v == null )
+				return parser.failure(null);
+			vars.add(v);
+		}
+		if( !parser.match( '=' ) )
+			return parser.failure(null);
+		Spaces();
+		Expr values = ExpStringList(In.NOTHING);
+		if( values==null )
+//			throw parser.exception("Expressions expected");
+			return parser.failure(null);
+		return parser.success( makeSetStmt(vars,values) );
+	}
+
+	private Stmts makeSetStmt(List<Var> vars,Expr values) throws ParseException {
+		int n = vars.size();
+		if( n == 1 )
+			return vars.get(0).set(values);
+		Stmts stmt = new Stmts();
+		String varName = values.valType==Val.ARRAY ? "a" : "t";
+		stmt.add( varName + " = " );
+		stmt.addAll( values );
+		stmt.add( ";  " );
+		Expr t = new Expr(values.valType,false);
+		t.add( varName );
+		t = t.single();
+		stmt.addAll( vars.get(0).set(t) );
+		for( int i=1; i<n; i++ ) {
+			t.clear();
+			t.add( "LuanImpl.pick(" + varName + ","+i+")" );
+			stmt.addAll( vars.get(i).set(t) );
+		}
+		return stmt;
+	}
+
+	private Stmts makeLocalSetStmt(List<String> names,Expr values) throws ParseException {
+		int n = names.size();
+		if( n == 1 )
+			return addSymbol(names.get(0),values.single());
+		Stmts stmt = new Stmts();
+		String varName = values.valType==Val.ARRAY ? "a" : "t";
+		stmt.add( varName + " = " );
+		stmt.addAll( values );
+		stmt.add( ";  " );
+		Expr t = new Expr(values.valType,false);
+		t.add( varName );
+		t = t.single();
+		stmt.addAll( addSymbol(names.get(0),t) );
+		for( int i=1; i<n; i++ ) {
+			t.clear();
+			t.add( "LuanImpl.pick(" + varName + ","+i+")" );
+			stmt.addAll( addSymbol(names.get(i),t) );
+		}
+		return stmt;
+	}
+
+	private Stmts ExpressionsStmt() throws ParseException {
+		parser.begin();
+		Expr exp = ExprZ(In.NOTHING);
+		if( exp != null && exp.isStmt ) {
+			Stmts stmt = new Stmts();
+			if( exp.valType==Val.SINGLE ) {
+				stmt.add( "LuanImpl.nop(" );
+				stmt.addAll( exp );
+				stmt.add( ")" );
+			} else {
+				stmt.addAll( exp );
+			}
+			stmt.add( ";  " );
+			return parser.success( stmt );
+		}
+		return parser.failure(null);
+	}
+
+	private Var SettableVar() throws ParseException {
+		int start = parser.begin();
+		Var var = VarZ(In.NOTHING);
+		if( var==null || !var.isSettable() )
+			return parser.failure(null);
+		return parser.success( var );
+	}
+
+	private Expr RequiredExpr(In in) throws ParseException {
+		parser.begin();
+		return parser.success(required(ExprZ(in),"Bad expression"));
+	}
+
+	private Expr ExprZ(In in) throws ParseException {
+		return OrExpr(in);
+	}
+
+	private Expr OrExpr(In in) throws ParseException {
+		parser.begin();
+		Expr exp = AndExpr(in);
+		if( exp==null )
+			return parser.failure(null);
+		while( Keyword("or") ) {
+			exp = exp.single();
+			Expr exp2 = required(AndExpr(in)).single();
+			Expr newExp = new Expr(Val.SINGLE,true);
+			newExp.add( "(LuanImpl.cnd(t = " );
+			newExp.addAll( exp );
+			newExp.add( ") ? t : (" );
+			newExp.addAll( exp2 );
+			newExp.add( "))" );
+			exp = newExp;
+		}
+		return parser.success(exp);
+	}
+
+	private Expr AndExpr(In in) throws ParseException {
+		parser.begin();
+		Expr exp = RelExpr(in);
+		if( exp==null )
+			return parser.failure(null);
+		while( Keyword("and") ) {
+			exp = exp.single();
+			Expr exp2 = required(RelExpr(in)).single();
+			Expr newExp = new Expr(Val.SINGLE,true);
+			newExp.add( "(LuanImpl.cnd(t = " );
+			newExp.addAll( exp );
+			newExp.add( ") ? (" );
+			newExp.addAll( exp2 );
+			newExp.add( ") : t)" );
+			exp = newExp;
+		}
+		return parser.success(exp);
+	}
+
+	private Expr RelExpr(In in) throws ParseException {
+		parser.begin();
+		Expr exp = ConcatExpr(in);
+		if( exp==null )
+			return parser.failure(null);
+		while(true) {
+			if( parser.match("==") ) {
+				Spaces();
+				exp = exp.single();
+				Expr exp2 = required(ConcatExpr(in)).single();
+				Expr newExp = new Expr(Val.SINGLE,false);
+				newExp.add( "LuanImpl.eq(luan," );
+				newExp.addAll( exp );
+				newExp.add( "," );
+				newExp.addAll( exp2 );
+				newExp.add( ")" );
+				exp = newExp;
+			} else if( parser.match("~=") ) {
+				Spaces();
+				exp = exp.single();
+				Expr exp2 = required(ConcatExpr(in)).single();
+				Expr newExp = new Expr(Val.SINGLE,false);
+				newExp.add( "!LuanImpl.eq(luan," );
+				newExp.addAll( exp );
+				newExp.add( "," );
+				newExp.addAll( exp2 );
+				newExp.add( ")" );
+				exp = newExp;
+			} else if( parser.match("<=") ) {
+				Spaces();
+				exp = exp.single();
+				Expr exp2 = required(ConcatExpr(in)).single();
+				Expr newExp = new Expr(Val.SINGLE,false);
+				newExp.add( "LuanImpl.le(luan," );
+				newExp.addAll( exp );
+				newExp.add( "," );
+				newExp.addAll( exp2 );
+				newExp.add( ")" );
+				exp = newExp;
+			} else if( parser.match(">=") ) {
+				Spaces();
+				exp = exp.single();
+				Expr exp2 = required(ConcatExpr(in)).single();
+				Expr newExp = new Expr(Val.SINGLE,false);
+				newExp.add( "LuanImpl.le(luan," );
+				newExp.addAll( exp2 );
+				newExp.add( "," );
+				newExp.addAll( exp );
+				newExp.add( ")" );
+				exp = newExp;
+			} else if( parser.match("<") ) {
+				Spaces();
+				exp = exp.single();
+				Expr exp2 = required(ConcatExpr(in)).single();
+				Expr newExp = new Expr(Val.SINGLE,false);
+				newExp.add( "LuanImpl.lt(luan," );
+				newExp.addAll( exp );
+				newExp.add( "," );
+				newExp.addAll( exp2 );
+				newExp.add( ")" );
+				exp = newExp;
+			} else if( parser.match(">") ) {
+				Spaces();
+				exp = exp.single();
+				Expr exp2 = required(ConcatExpr(in)).single();
+				Expr newExp = new Expr(Val.SINGLE,false);
+				newExp.add( "LuanImpl.lt(luan," );
+				newExp.addAll( exp2 );
+				newExp.add( "," );
+				newExp.addAll( exp );
+				newExp.add( ")" );
+				exp = newExp;
+			} else
+				break;
+		}
+		return parser.success(exp);
+	}
+
+	private Expr ConcatExpr(In in) throws ParseException {
+		parser.begin();
+		Expr exp = SumExpr(in);
+		if( exp==null )
+			return parser.failure(null);
+		if( parser.match("..") ) {
+			Spaces();
+			exp = exp.single();
+			Expr exp2 = required(ConcatExpr(in)).single();
+			Expr newExp = new Expr(Val.SINGLE,false);
+			newExp.add( "LuanImpl.concat(luan," );
+			newExp.addAll( exp );
+			newExp.add( "," );
+			newExp.addAll( exp2 );
+			newExp.add( ")" );
+			exp = newExp;
+		}
+		return parser.success(exp);
+	}
+
+	private Expr SumExpr(In in) throws ParseException {
+		parser.begin();
+		Expr exp = TermExpr(in);
+		if( exp==null )
+			return parser.failure(null);
+		while(true) {
+			if( parser.match('+') ) {
+				Spaces();
+				exp = exp.single();
+				Expr exp2 = required(TermExpr(in)).single();
+				Expr newExp = new Expr(Val.SINGLE,false);
+				newExp.add( "LuanImpl.add(luan," );
+				newExp.addAll( exp );
+				newExp.add( "," );
+				newExp.addAll( exp2 );
+				newExp.add( ")" );
+				exp = newExp;
+			} else if( Minus() ) {
+				Spaces();
+				exp = exp.single();
+				Expr exp2 = required(TermExpr(in)).single();
+				Expr newExp = new Expr(Val.SINGLE,false);
+				newExp.add( "LuanImpl.sub(luan," );
+				newExp.addAll( exp );
+				newExp.add( "," );
+				newExp.addAll( exp2 );
+				newExp.add( ")" );
+				exp = newExp;
+			} else
+				break;
+		}
+		return parser.success(exp);
+	}
+
+	private boolean Minus() {
+		parser.begin();
+		return parser.match('-') && !parser.match('-') ? parser.success() : parser.failure();
+	}
+
+	private Expr TermExpr(In in) throws ParseException {
+		parser.begin();
+		Expr exp = UnaryExpr(in);
+		if( exp==null )
+			return parser.failure(null);
+		while(true) {
+			if( parser.match('*') ) {
+				Spaces();
+				exp = exp.single();
+				Expr exp2 = required(UnaryExpr(in)).single();
+				Expr newExp = new Expr(Val.SINGLE,false);
+				newExp.add( "LuanImpl.mul(luan," );
+				newExp.addAll( exp );
+				newExp.add( "," );
+				newExp.addAll( exp2 );
+				newExp.add( ")" );
+				exp = newExp;
+			} else if( parser.match('/') ) {
+				Spaces();
+				exp = exp.single();
+				Expr exp2 = required(UnaryExpr(in)).single();
+				Expr newExp = new Expr(Val.SINGLE,false);
+				newExp.add( "LuanImpl.div(luan," );
+				newExp.addAll( exp );
+				newExp.add( "," );
+				newExp.addAll( exp2 );
+				newExp.add( ")" );
+				exp = newExp;
+			} else if( Mod() ) {
+				Spaces();
+				exp = exp.single();
+				Expr exp2 = required(UnaryExpr(in)).single();
+				Expr newExp = new Expr(Val.SINGLE,false);
+				newExp.add( "LuanImpl.mod(luan," );
+				newExp.addAll( exp );
+				newExp.add( "," );
+				newExp.addAll( exp2 );
+				newExp.add( ")" );
+				exp = newExp;
+			} else
+				break;
+		}
+		return parser.success(exp);
+	}
+
+	private boolean Mod() {
+		parser.begin();
+		return parser.match('%') && !parser.match('>') ? parser.success() : parser.failure();
+	}
+
+	private Expr UnaryExpr(In in) throws ParseException {
+		parser.begin();
+		if( parser.match('#') ) {
+			Spaces();
+			Expr exp = required(UnaryExpr(in)).single();
+			Expr newExp = new Expr(Val.SINGLE,false);
+			newExp.add( "LuanImpl.len(luan," );
+			newExp.addAll( exp );
+			newExp.add( ")" );
+			return parser.success(newExp);
+		}
+		if( Minus() ) {
+			Spaces();
+			Expr exp = required(UnaryExpr(in)).single();
+			Expr newExp = new Expr(Val.SINGLE,false);
+			newExp.add( "LuanImpl.unm(luan," );
+			newExp.addAll( exp );
+			newExp.add( ")" );
+			return parser.success(newExp);
+		}
+		if( Keyword("not") ) {
+			Spaces();
+			Expr exp = required(UnaryExpr(in)).single();
+			Expr newExp = new Expr(Val.SINGLE,false);
+			newExp.add( "!Luan.checkBoolean(" );
+			newExp.addAll( exp );
+			newExp.add( ")" );
+			return parser.success(newExp);
+		}
+		Expr exp = PowExpr(in);
+		if( exp==null )
+			return parser.failure(null);
+		return parser.success(exp);
+	}
+
+	private Expr PowExpr(In in) throws ParseException {
+		parser.begin();
+		Expr exp1 = SingleExpr(in);
+		if( exp1==null )
+			return parser.failure(null);
+		if( parser.match('^') ) {
+			Spaces();
+			Expr exp2 = required(PowExpr(in));
+			Expr newExp = new Expr(Val.SINGLE,false);
+			newExp.add( "LuanImpl.pow(luan," );
+			newExp.addAll( exp1.single() );
+			newExp.add( "," );
+			newExp.addAll( exp2.single() );
+			newExp.add( ")" );
+			exp1 = newExp;
+		}
+		return parser.success(exp1);
+	}
+
+	private Expr SingleExpr(In in) throws ParseException {
+		parser.begin();
+		Expr exp = FunctionExpr();
+		if( exp != null )
+			return parser.success(exp);
+		exp = VarExp(in);
+		if( exp != null )
+			return parser.success(exp);
+		exp = VarArgs();
+		if( exp != null )
+			return parser.success(exp);
+		return parser.failure(null);
+	}
+
+	private Expr FunctionExpr() throws ParseException {
+		if( !Keyword("function") )
+			return null;
+		return RequiredFunction(null);
+	}
+
+	private Expr RequiredFunction(String name) throws ParseException {
+		parser.begin();
+		RequiredMatch('(');
+		Spaces();
+		frame = new Frame(frame);
+		Stmts stmt = new Stmts();
+		List<String> names = NameList();
+		if( names != null ) {
+			Expr args = new Expr(Val.ARRAY,false);
+			args.add( "args" );
+			stmt.addAll( makeLocalSetStmt(names,args) );
+			if( parser.match(',') ) {
+				Spaces();
+				if( !parser.match("...") )
+					throw parser.exception();
+				Spaces();
+				frame.isVarArg = true;
+				stmt.add( "final Object[] varArgs = LuanImpl.varArgs(args," + names.size() + ");  " );
+			}
+		} else if( parser.match("...") ) {
+			Spaces();
+			frame.isVarArg = true;
+			stmt.add( "final Object[] varArgs = LuanImpl.varArgs(args,0);  " );
+		}
+		RequiredMatch(')');
+		Spaces();
+		Stmts block = RequiredBlock();
+		stmt.addAll( block );
+		stmt.hasReturn = block.hasReturn;
+		Expr fnDef = newFnExp(stmt,name);
+		RequiredKeyword("end");
+		frame = frame.parent;
+		return parser.success(fnDef);
+	}
+
+	private Expr VarArgs() throws ParseException {
+		parser.begin();
+		if( !frame.isVarArg || !parser.match("...") )
+			return parser.failure(null);
+		Spaces();
+		Expr exp = new Expr(Val.ARRAY,false);
+		exp.add("varArgs");
+		return parser.success(exp);
+	}
+
+	private Expr TableExpr() throws ParseException {
+		parser.begin();
+		if( !parser.match('{') )
+			return parser.failure(null);
+		Expr tblExp = new Expr(Val.SINGLE,false);
+		tblExp.add( "LuanImpl.table(" );
+		Expr lastExp = tblExp;
+		List<Expr> builder = new ArrayList<Expr>();
+/*
+		Spaces();
+		Field(builder);
+		while( FieldSep() ) {
+			Spaces();
+			Field(builder);
+		}
+*/
+		do {
+			Spaces();  lastExp.addNewLines();
+			Expr exp = Field();
+			if( exp != null ) {
+				builder.add(exp);
+				lastExp = exp;
+				Spaces();  lastExp.addNewLines();
+			}
+		} while( FieldSep() );
+		Expr exp = TemplateExpressions(In.NOTHING);
+		if( exp != null )
+			builder.add(exp);
+		if( !parser.match('}') )
+			throw parser.exception("Expected table element or '}'");
+		tblExp.addAll( expString(builder).array() );
+		tblExp.add( ")" );
+		Spaces();
+		tblExp.addNewLines();
+		return parser.success( tblExp );
+	}
+
+	private boolean FieldSep() throws ParseException {
+		return parser.anyOf(",;") || EndOfLine();
+	}
+
+	private Expr Field() throws ParseException {
+		parser.begin();
+		Expr exp = SubExpr(In.NOTHING);
+		if( exp==null )
+			exp = NameExpr();
+		if( exp!=null && parser.match('=') ) {
+			Spaces();
+			Expr val = RequiredExpr(In.NOTHING).single();
+			Expr newExp = new Expr(Val.SINGLE,false);
+			newExp.add( "new TableField(" );
+			newExp.addAll( exp );
+			newExp.add( "," );
+			newExp.addAll( val );
+			newExp.add( ")" );
+			return parser.success(newExp);
+		}
+		parser.rollback();
+		Expr exprs = ExprZ(In.NOTHING);
+		if( exprs != null ) {
+			return parser.success(exprs);
+		}
+		return parser.failure(null);
+	}
+
+	private Expr VarExp(In in) throws ParseException {
+		Var var = VarZ(in);
+		return var==null ? null : var.exp();
+	}
+
+	private Var VarZ(In in) throws ParseException {
+		parser.begin();
+		Var var = VarStart(in);
+		if( var==null )
+			return parser.failure(null);
+		Var var2;
+		while( (var2=Var2(in,var.exp())) != null ) {
+			var = var2;
+		}
+		return parser.success(var);
+	}
+
+	private Var VarStart(In in) throws ParseException {
+		if( parser.match('(') ) {
+			Spaces();
+			Expr exp = RequiredExpr(in).single();
+			RequiredMatch(')');
+			Spaces();
+			return exprVar(exp);
+		}
+		String name = Name();
+		if( name != null )
+			return nameVar(name);
+		Expr exp;
+		exp = TableExpr();
+		if( exp != null )
+			return exprVar(exp);
+		exp = Literal();
+		if( exp != null )
+			return exprVar(exp);
+		return null;
+	}
+
+	private Var Var2(In in,Expr exp1) throws ParseException {
+		parser.begin();
+		Expr exp2 = SubExpr(in);
+		if( exp2 != null )
+			return parser.success(indexVar(exp1,exp2));
+		if( parser.match('.') ) {
+			Spaces();
+			exp2 = NameExpr();
+			if( exp2!=null )
+				return parser.success(indexVar(exp1,exp2));
+			return parser.failure(null);
+		}
+		Expr fnCall = Args( in, exp1, new ArrayList<Expr>() );
+		if( fnCall != null )
+			return parser.success(exprVar(fnCall));
+		return parser.failure(null);
+	}
+
+	private interface Var {
+		public Expr exp() throws ParseException;
+//		public Settable settable() throws ParseException;
+		public boolean isSettable();
+		public Stmts set(Expr val) throws ParseException;
+	}
+
+	private Expr env() {
+		Sym sym = getSym("_ENV");
+		if( sym != null )
+			return sym.exp();
+		return null;
+	}
+
+	private Var nameVar(final String name) {
+		return new Var() {
+
+			public Expr exp() throws ParseException {
+				Sym sym = getSym(name);
+				if( sym != null )
+					return sym.exp();
+				Expr envExpr = env();
+				if( envExpr != null )
+					return indexExpStr( envExpr, constExpStr(name) );
+				parser.failure(null);
+				throw parser.exception("name '"+name+"' not defined");
+			}
+
+			public boolean isSettable() {
+				return true;
+			}
+
+			public Stmts set(Expr val) throws ParseException {
+				Sym sym = getSym(name);
+				if( sym != null ) {
+					Stmts stmt = new Stmts();
+					stmt.addAll( sym.exp() );
+					stmt.add( " = " );
+					stmt.addAll( val.single() );
+					stmt.add( ";  " );
+					return stmt;
+				}
+				Expr envExpr = env();
+				if( envExpr != null )
+					return indexVar( envExpr, constExpStr(name) ).set(val);
+				parser.failure(null);
+				throw parser.exception("name '"+name+"' not defined");
+			}
+		};
+	}
+
+	private Var exprVar(final Expr expr) {
+		return new Var() {
+
+			public Expr exp() {
+				return expr;
+			}
+
+			public boolean isSettable() {
+				return false;
+			}
+
+			public Stmts set(Expr val) {
+				throw new RuntimeException();
+			}
+		};
+	}
+
+	private Var indexVar(final Expr table,final Expr key) {
+		return new Var() {
+
+			public Expr exp() {
+				return indexExpStr( table, key );
+			}
+
+			public boolean isSettable() {
+				return true;
+			}
+
+			public Stmts set(Expr val) {
+				Stmts stmt = new Stmts();
+				stmt.add( "LuanImpl.put(luan," );
+				stmt.addAll( table.single() );
+				stmt.add( "," );
+				stmt.addAll( key.single() );
+				stmt.add( "," );
+				stmt.addAll( val.single() );
+				stmt.add( ");  " );
+				return stmt;
+			}
+		};
+	}
+
+	private Expr Args(In in,Expr fn,List<Expr> builder) throws ParseException {
+		parser.begin();
+		return args(in,builder)
+			? parser.success( callExpStr( fn, expString(builder) ) )
+			: parser.failure((Expr)null);
+	}
+
+	private boolean args(In in,List<Expr> builder) throws ParseException {
+		parser.begin();
+		if( parser.match('(') ) {
+			Spaces();
+			ExpList(in,builder);  // optional
+			if( !parser.match(')') )
+				throw parser.exception("Expression or ')' expected");
+			Spaces();
+			return parser.success();
+		}
+		Expr exp = TableExpr();
+		if( exp != null ) {
+			builder.add(exp);
+			return parser.success();
+		}
+		exp = StringLiteral();
+		if( exp != null ) {
+			builder.add(exp);
+			return parser.success();
+		}
+		return parser.failure();
+	}
+
+	private Expr ExpStringList(In in) throws ParseException {
+		List<Expr> builder = new ArrayList<Expr>();
+		return ExpList(in,builder) ? expString(builder) : null;
+	}
+
+	private boolean ExpList(In in,List<Expr> builder) throws ParseException {
+		parser.begin();
+		Expr exp = TemplateExpressions(in);
+		if( exp != null ) {
+			builder.add(exp);
+			return parser.success();
+		}
+		exp = ExprZ(in);
+		if( exp==null )
+			return parser.failure();
+		exp.addNewLines();
+		builder.add(exp);
+		while( parser.match(',') ) {
+			Spaces();
+			exp = TemplateExpressions(in);
+			if( exp != null ) {
+				builder.add(exp);
+				return parser.success();
+			}
+			exp = RequiredExpr(in);
+			exp.addNewLines();
+			builder.add(exp);
+		}
+		return parser.success();
+	}
+
+	private Expr SubExpr(In in) throws ParseException {
+		parser.begin();
+		if( !parser.match('[') || parser.test("[") || parser.test("=") )
+			return parser.failure(null);
+		Spaces();
+		Expr exp = RequiredExpr(In.NOTHING).single();
+		RequiredMatch(']');
+		Spaces();
+		return parser.success(exp);
+	}
+
+	private Expr NameExpr() throws ParseException {
+		parser.begin();
+		String name = Name();
+		if( name==null )
+			return parser.failure(null);
+		return parser.success(constExpStr(name));
+	}
+
+	private String RequiredName() throws ParseException {
+		parser.begin();
+		String name = Name();
+		if( name==null )
+			throw parser.exception("Name expected");
+		return parser.success(name);
+	}
+
+	private String Name() throws ParseException {
+		int start = parser.begin();
+		if( !NameFirstChar() )
+			return parser.failure(null);
+		while( NameChar() );
+		String match = parser.textFrom(start);
+		if( keywords.contains(match) )
+			return parser.failure(null);
+		Spaces();
+		return parser.success(match);
+	}
+
+	private boolean NameChar() {
+		return NameFirstChar() || Digit();
+	}
+
+	private boolean NameFirstChar() {
+		return parser.inCharRange('a', 'z') || parser.inCharRange('A', 'Z') || parser.match('_');
+	}
+
+	private void RequiredMatch(char c) throws ParseException {
+		if( !parser.match(c) )
+			throw parser.exception("'"+c+"' expected");
+	}
+
+	private void RequiredMatch(String s) throws ParseException {
+		if( !parser.match(s) )
+			throw parser.exception("'"+s+"' expected");
+	}
+
+	private void RequiredKeyword(String keyword) throws ParseException {
+		if( !Keyword(keyword) )
+			throw parser.exception("'"+keyword+"' expected");
+	}
+
+	private boolean Keyword(String keyword) throws ParseException {
+		parser.begin();
+		if( !parser.match(keyword) || NameChar() )
+			return parser.failure();
+		Spaces();
+		return parser.success();
+	}
+
+	private static final Set<String> keywords = new HashSet<String>(Arrays.asList(
+		"and",
+		"break",
+		"do",
+		"else",
+		"elseif",
+		"end",
+		"false",
+		"for",
+		"function",
+		"goto",
+		"if",
+		"in",
+		"local",
+		"nil",
+		"not",
+		"or",
+		"repeat",
+		"return",
+		"then",
+		"true",
+		"until",
+		"while"
+	));
+
+	private Expr Literal() throws ParseException {
+		parser.begin();
+		if( NilLiteral() ) {
+			Expr exp = new Expr(Val.SINGLE,false);
+			exp.add( "null" );
+			return parser.success(exp);
+		}
+		Boolean b = BooleanLiteral();
+		if( b != null ) {
+			Expr exp = new Expr(Val.SINGLE,false);
+			exp.add( b.toString() );
+			return parser.success(exp);
+		}
+		Number n = NumberLiteral();
+		if( n != null ) {
+			String s = n.toString();
+			if( n instanceof Long )
+				s += "L";
+			Expr exp = new Expr(Val.SINGLE,false);
+			exp.add( s );
+			return parser.success(exp);
+		}
+		Expr s = StringLiteral();
+		if( s != null )
+			return parser.success(s);
+		return parser.failure(null);
+	}
+
+	private static int STR_LIM = 65000;
+
+	private Expr constExpStr(String s) {
+		s = s
+			.replace("\\","\\\\")
+			.replace("\"","\\\"")
+			.replace("\n","\\n")
+			.replace("\r","\\r")
+			.replace("\t","\\t")
+			.replace("\b","\\b")
+		;
+		if( s.length() > STR_LIM ) {
+			int len = s.length();
+			StringBuilder sb = new StringBuilder();
+			sb.append( "LuanImpl.strconcat(" );
+			int start = 0;
+			while(true) {
+				int end = start + STR_LIM;
+				if( end >= len )
+					break;
+				sb.append( "\"" ).append( s.substring(start,end) ).append( "\"," );
+				start = end;
+			}
+			sb.append( "\"" ).append( s.substring(start) ).append( "\")" );
+			s = sb.toString();
+		} else
+			s = "\"" + s + "\"";
+		Expr exp = new Expr(Val.SINGLE,false);
+		exp.add( s );
+		return exp;
+	}
+
+	private boolean NilLiteral() throws ParseException {
+		return Keyword("nil");
+	}
+
+	private Boolean BooleanLiteral() throws ParseException {
+		if( Keyword("true") )
+			return true;
+		if( Keyword("false") )
+			return false;
+		return null;
+	}
+
+	private Number NumberLiteral() throws ParseException {
+		parser.begin();
+		Number n;
+		if( parser.matchIgnoreCase("0x") ) {
+			n = HexNumber();
+		} else {
+			n = DecNumber();
+		}
+		if( n==null || NameChar() )
+			return parser.failure(null);
+		Spaces();
+		return parser.success(n);
+	}
+
+	private Number DecNumber() {
+		int start = parser.begin();
+		boolean isInt = true;
+		if( Int() ) {
+			if( parser.match('.') ) {
+				isInt = false;
+				Int();  // optional
+			}
+		} else if( parser.match('.') && Int() ) {
+			// ok
+			isInt = false;
+		} else
+			return parser.failure(null);
+		if( Exponent() )  // optional
+			isInt = false;
+		String s = parser.textFrom(start);
+		if( isInt ) {
+			try {
+				return parser.success(Integer.valueOf(s));
+			} catch(NumberFormatException e) {}
+			try {
+				return parser.success(Long.valueOf(s));
+			} catch(NumberFormatException e) {}
+		}
+		return parser.success(Double.valueOf(s));
+	}
+
+	private boolean Exponent() {
+		parser.begin();
+		if( !parser.matchIgnoreCase("e") )
+			return parser.failure();
+		parser.anyOf("+-");  // optional
+		if( !Int() )
+			return parser.failure();
+		return parser.success();
+	}
+
+	private boolean Int() {
+		if( !Digit() )
+			return false;
+		while( Digit() );
+		return true;
+	}
+
+	private boolean Digit() {
+		return parser.inCharRange('0', '9');
+	}
+
+	private Number HexNumber() {
+		int start = parser.begin();
+		long nLong = 0;
+		double n;
+		if( HexInt() ) {
+			nLong = Long.parseLong(parser.textFrom(start),16);
+			n = (double)nLong;
+			if( parser.match('.') ) {
+				start = parser.currentIndex();
+				if( HexInt() ) {
+					String dec = parser.textFrom(start);
+					n += (double)Long.parseLong(dec,16) / Math.pow(16,dec.length());
+				}
+			}
+		} else if( parser.match('.') && HexInt() ) {
+			String dec = parser.textFrom(start+1);
+			n = (double)Long.parseLong(dec,16) / Math.pow(16,dec.length());
+		} else {
+			return parser.failure(null);
+		}
+		if( parser.matchIgnoreCase("p") ) {
+			parser.anyOf("+-");  // optional
+			start = parser.currentIndex();
+			if( !HexInt() )
+				return parser.failure(null);
+			n *= Math.pow(2,(double)Long.parseLong(parser.textFrom(start)));
+		}
+		if( nLong == n ) {
+			int nInt = (int)nLong;
+			if( nInt == nLong )
+				return parser.success(Integer.valueOf(nInt));
+			return parser.success(Long.valueOf(nLong));
+		}
+		return parser.success(Double.valueOf(n));
+	}
+
+	private boolean HexInt() {
+		if( !HexDigit() )
+			return false;
+		while( HexDigit() );
+		return true;
+	}
+
+
+	private boolean HexDigit() {
+		return Digit() || parser.anyOf("abcdefABCDEF");
+	}
+
+	private Expr StringLiteral() throws ParseException {
+		Expr s;
+		if( (s=QuotedString('"'))==null
+			&& (s=QuotedString('\''))==null
+			&& (s=LongString())==null
+		)
+			return null;
+		Spaces();
+		return s;
+	}
+
+	private Expr LongString() throws ParseException {
+		parser.begin();
+		if( !parser.match('[') )
+			return parser.failure(null);
+		int start = parser.currentIndex();
+		while( parser.match('=') );
+		int nEquals = parser.currentIndex() - start;
+		if( !parser.match('[') )
+			return parser.failure(null);
+		EndOfLine();
+		start = parser.currentIndex();
+		while( !LongBracketsEnd(nEquals) ) {
+			if( !(EndOfLine() || parser.anyChar()) )
+				throw parser.exception("Unclosed long string");
+		}
+		String s = parser.text.substring( start, parser.currentIndex() - nEquals - 2 );
+		String rtns = parser.sb().toString();
+		parser.sb().setLength(0);
+		Expr exp = constExpStr(s);
+		if( rtns.length() > 0 )
+			exp.add(rtns);
+		return parser.success(exp);
+	}
+
+	private Expr QuotedString(char quote) throws ParseException {
+		parser.begin();
+		if( !parser.match(quote) )
+			return parser.failure(null);
+		StringBuilder buf = new StringBuilder();
+		while( !parser.match(quote) ) {
+			Character c = EscSeq();
+			if( c != null ) {
+				buf.append(c);
+			} else {
+				if( parser.test('\r') || parser.test('\n') || !parser.anyChar() )
+					throw parser.exception("Unclosed string");
+				buf.append(parser.lastChar());
+			}
+		}
+		return parser.success(constExpStr(buf.toString()));
+	}
+
+	private Character EscSeq() {
+		parser.begin();
+		if( !parser.match('\\') )
+			return parser.failure(null);
+		if( parser.match('a') )  return parser.success('\u0007');
+		if( parser.match('b') )  return parser.success('\b');
+		if( parser.match('f') )  return parser.success('\f');
+		if( parser.match('n') )  return parser.success('\n');
+		if( parser.match('r') )  return parser.success('\r');
+		if( parser.match('t') )  return parser.success('\t');
+		if( parser.match('v') )  return parser.success('\u000b');
+		if( parser.match('\\') )  return parser.success('\\');
+		if( parser.match('"') )  return parser.success('"');
+		if( parser.match('\'') )  return parser.success('\'');
+		int start = parser.currentIndex();
+		if( parser.match('x') && HexDigit() && HexDigit() )
+			return parser.success((char)Integer.parseInt(parser.textFrom(start+1),16));
+		if( parser.match('u') && HexDigit() && HexDigit() && HexDigit() && HexDigit() )
+			return parser.success((char)Integer.parseInt(parser.textFrom(start+1),16));
+		if( Digit() ) {
+			if( Digit() ) Digit();  // optional
+			return parser.success((char)Integer.parseInt(parser.textFrom(start)));
+		}
+		if( MatchEndOfLine() ) {
+			return parser.success('\n');
+		}
+		return parser.failure(null);
+	}
+
+	private void Spaces() throws ParseException {
+		while( parser.anyOf(" \t") || Comment() || ContinueOnNextLine() );
+	}
+
+	private boolean ContinueOnNextLine() {
+		parser.begin();
+		if( parser.match('\\') && EndOfLine() ) {
+			parser.upSb();
+			return parser.success();
+		} else
+			return parser.failure();
+	}
+
+	private boolean Comment() throws ParseException {
+		if( LongComment() )
+			return true;
+		if( parser.match("--") ) {
+			while( parser.noneOf("\r\n") );
+			return true;
+		}
+		return false;
+	}
+
+	private boolean LongComment() throws ParseException {
+		parser.begin();
+		if( !parser.match("--[") )
+			return parser.failure();
+		int start = parser.currentIndex();
+		while( parser.match('=') );
+		int nEquals = parser.currentIndex() - start;
+		if( !parser.match('[') )
+			return parser.failure();
+		while( !LongBracketsEnd(nEquals) ) {
+			if( !(EndOfLine() || parser.anyChar()) )
+				throw parser.exception("Unclosed comment");
+		}
+		parser.upSb();
+		return parser.success();
+	}
+
+	private boolean LongBracketsEnd(int nEquals) {
+		parser.begin();
+		if( !parser.match(']') )
+			return parser.failure();
+		while( nEquals-- > 0 ) {
+			if( !parser.match('=') )
+				return parser.failure();
+		}
+		if( !parser.match(']') )
+			return parser.failure();
+		return parser.success();
+	}
+
+
+
+	private class ParseList extends ArrayList {
+
+		void addNewLines() {
+			if( parser.sb().length() > 0 ) {
+				add( parser.sb().toString() );
+				parser.sb().setLength(0);
+/*
+if( parser.sourceName.equals("stdin") ) {
+	StringWriter sw = new StringWriter();
+	new Throwable().printStackTrace(new PrintWriter(sw,true));
+//	add(sw.toString());
+}
+*/
+			}
+		}
+
+		ParseList() {
+			addNewLines();
+		}
+
+		@Override public boolean add(Object obj) {
+			if( obj instanceof List )  throw new RuntimeException();
+			return super.add(obj);
+		}
+
+		@Override public void add(int index,Object obj) {
+			if( obj instanceof List )  throw new RuntimeException();
+			super.add(index,obj);
+		}
+
+		@Override public String toString() {
+			StringBuilder sb = new StringBuilder();
+			for( Object o : this ) {
+				sb.append( o.toString() );
+			}
+			return sb.toString();
+		}
+	}
+
+
+	private static AtomicInteger classCounter = new AtomicInteger();
+
+	private enum Val { SINGLE, ARRAY }
+
+	private class Expr extends ParseList {
+		final Val valType;
+		final boolean isStmt;
+
+		Expr(Val valType,boolean isStmt) {
+			this.valType = valType;
+			this.isStmt = isStmt;
+		}
+
+		Expr single() {
+			if( valType==Val.SINGLE )
+				return this;
+			Expr exp = new Expr(Val.SINGLE,isStmt);
+			exp.add( valType==Val.ARRAY ? "LuanImpl.first(" : "Luan.first(" );
+			exp.addAll( this );
+			exp.add( ")" );
+			return exp;
+		}
+
+		Expr array() {
+			if( valType==Val.ARRAY )
+				return this;
+			Expr exp = new Expr(Val.ARRAY,isStmt);
+			if( valType==Val.SINGLE ) {
+				exp.add( "new Object[]{" );
+				exp.addAll( this );
+				exp.add( "}" );
+			} else {
+				exp.add( "Luan.array(" );
+				exp.addAll( this );
+				exp.add( ")" );
+			}
+			return exp;
+		}
+
+	}
+
+	private Expr expString(List<Expr> list) {
+		Expr exp = new Expr(Val.ARRAY,false);
+		switch(list.size()) {
+		case 0:
+			exp.add("LuanFunction.NOTHING");
+			return exp;
+		case 1:
+			return list.get(0);
+		default:
+			int lastI = list.size() - 1;
+			exp.add( "new Object[]{" );
+			for( int i=0; i<lastI; i++ ) {
+				exp.addAll( list.get(i).single() );
+				exp.add( "," );
+			}
+			Expr last = list.get(lastI);
+			if( last.valType==Val.SINGLE ) {
+				exp.addAll( last );
+				exp.add( "}" );
+			} else {
+				exp.add( "}" );
+				exp.add( 0, "LuanImpl.concatArgs(" );
+				exp.add( "," );
+				exp.addAll( last );
+				exp.add( ")" );
+			}
+			return exp;
+		}
+	}
+
+	private class Stmts extends ParseList {
+		boolean hasReturn = false;
+	}
+
+	private Class toFnClass(Stmts stmts,List<UpSym> upValueSymbols) {
+		String className = "EXP" + classCounter.incrementAndGet();
+		String classCode = toFnString(stmts,upValueSymbols,className);
+		try {
+//System.out.println(parser.sourceName);
+//System.out.println(classCode);
+			return LuanJavaCompiler.compile("luan.impl."+className,parser.sourceName,classCode);
+		} catch(ClassNotFoundException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private String toFnString(Stmts stmts,List<UpSym> upValueSymbols) {
+		String className = "EXP" + classCounter.incrementAndGet();
+		return toFnString(stmts,upValueSymbols,className);
+	}
+
+	private String toFnString(Stmts stmts,List<UpSym> upValueSymbols,String className) {
+		if( !stmts.hasReturn )
+			stmts.add( "\nreturn LuanFunction.NOTHING;" );
+		return ""
+			+"package luan.impl;  "
+			+"import luan.Luan;  "
+			+"import luan.LuanFunction;  "
+			+"import luan.LuanState;  "
+			+"import luan.LuanJava;  "
+			+"import luan.LuanException;  "
+			+"import luan.modules.PackageLuan;  "
+
+			+"public class " + className +" extends Closure {  "
+				+"public "+className+"(LuanJava java) throws LuanException {  "
+					+"super("+upValueSymbols.size()+",java);  "
+					+ init(upValueSymbols)
+				+"}  "
+
+				+"@Override public Object doCall(LuanState luan,Object[] args) throws LuanException {  "
+					+"final Pointer[] parentUpValues = upValues;  "
+					+"Object t;  "
+					+"Object[] a;  "
+					+ stmts
+				+"\n}  "
+			+"}\n"
+		;
+	}
+
+	private Expr toFnExp(Stmts stmt,List<UpSym> upValueSymbols,String name) {
+		stmt.addNewLines();
+		if( !stmt.hasReturn )
+			stmt.add( "return LuanFunction.NOTHING;  " );
+		Expr exp = new Expr(Val.SINGLE,false);
+		exp.add( ""
+			+"new Closure("+upValueSymbols.size()+",java) {  "
+				+"{  "
+				+ init(upValueSymbols)
+				+"}  "
+				+"@Override public Object doCall(LuanState luan,Object[] args) throws LuanException {  "
+		);
+		if( name != null ) {
+			exp.add( ""
+					+"return _" + name + "(luan,args);  "
+				+"}  "
+				+"private Object _" + name + "(LuanState luan,Object[] args) throws LuanException {  "
+			);
+		}
+		exp.add( ""
+					+"final Pointer[] parentUpValues = upValues;  "
+					+"Object t;  "
+					+"Object[] a;  "
+		);
+		exp.addAll( stmt );
+		exp.add( ""
+				+"}  "
+			+"}  "
+		);
+		return exp;
+	}
+
+	private static String init(List<UpSym> upValueSymbols) {
+		StringBuilder sb = new StringBuilder();
+		for( UpSym upSym : upValueSymbols ) {
+			sb.append( upSym.init() );
+		}
+		return sb.toString();
+	}
+
+}