changeset 104:754e6030c029

remove parboiled and rewrite parser; add jline; git-svn-id: https://luan-java.googlecode.com/svn/trunk@105 21e917c8-12df-6dd8-5cb6-c86387c605b9
author fschmidt@gmail.com <fschmidt@gmail.com@21e917c8-12df-6dd8-5cb6-c86387c605b9>
date Mon, 12 May 2014 05:57:53 +0000
parents c6c0aed1dbec
children a9560839104d
files ext/asm-all-4.0.jar ext/jline-1.0.jar ext/parboiled-core-1.1.5-SNAPSHOT.jar ext/parboiled-java-1.1.5-SNAPSHOT.jar src/luan/Luan.java src/luan/interp/ExpList.java src/luan/interp/LuanCompiler.java src/luan/interp/LuanParser.java src/luan/parser/ParseException.java src/luan/parser/Parser.java src/luan/tools/CmdLine.java
diffstat 11 files changed, 1241 insertions(+), 950 deletions(-) [+]
line wrap: on
line diff
Binary file ext/asm-all-4.0.jar has changed
Binary file ext/jline-1.0.jar has changed
Binary file ext/parboiled-core-1.1.5-SNAPSHOT.jar has changed
Binary file ext/parboiled-java-1.1.5-SNAPSHOT.jar has changed
--- a/src/luan/Luan.java	Mon Mar 17 21:17:31 2014 +0000
+++ b/src/luan/Luan.java	Mon May 12 05:57:53 2014 +0000
@@ -2,7 +2,7 @@
 
 
 public final class Luan {
-	public static final String version = "Luan 0.0";
+	public static final String version = "Luan 0.1";
 
 	public static String type(Object obj) {
 		if( obj == null )
--- a/src/luan/interp/ExpList.java	Mon Mar 17 21:17:31 2014 +0000
+++ b/src/luan/interp/ExpList.java	Mon May 12 05:57:53 2014 +0000
@@ -54,6 +54,8 @@
 		private final List<Adder> adders = new ArrayList<Adder>();
 
 		void add(Expr expr) {
+			if( expr==null )
+				throw new NullPointerException();
 			adders.add( new ExprAdder(expr) );
 		}
 
--- a/src/luan/interp/LuanCompiler.java	Mon Mar 17 21:17:31 2014 +0000
+++ b/src/luan/interp/LuanCompiler.java	Mon May 12 05:57:53 2014 +0000
@@ -1,16 +1,12 @@
 package luan.interp;
 
-import org.parboiled.Parboiled;
-import org.parboiled.errors.ErrorUtils;
-import org.parboiled.parserunners.ReportingParseRunner;
-import org.parboiled.parserunners.TracingParseRunner;
-import org.parboiled.support.ParsingResult;
 import luan.LuanFunction;
 import luan.LuanState;
 import luan.LuanException;
 import luan.LuanSource;
 import luan.LuanElement;
 import luan.LuanTable;
+import luan.parser.ParseException;
 
 
 public final class LuanCompiler {
@@ -18,16 +14,14 @@
 
 	public static LuanFunction compile(LuanState luan,LuanSource src,LuanTable env) throws LuanException {
 		UpValue.Getter envGetter = env!=null ? new UpValue.ValueGetter(env) : new UpValue.EnvGetter();
-		LuanParser parser = Parboiled.createParser(LuanParser.class,src,envGetter);
-		ParsingResult<?> result = new ReportingParseRunner(parser.Target()).run(src.text);
-//		ParsingResult<?> result = new TracingParseRunner(parser.Target()).run(src);
-		if( result.hasErrors() ) {
-//			throw luan.COMPILER.exception( ErrorUtils.printParseErrors(result) );
+		try {
+			FnDef fnDef = LuanParser.parse(src,envGetter);
+			return new Closure((LuanStateImpl)luan,fnDef);
+		} catch(ParseException e) {
+//e.printStackTrace();
 			LuanElement le = new LuanSource.CompilerElement(src);
-			throw luan.bit(le).exception( ErrorUtils.printParseErrors(result) );
+			throw luan.bit(le).exception( e.getFancyMessage() );
 		}
-		FnDef fnDef = (FnDef)result.resultValue;
-		return new Closure((LuanStateImpl)luan,fnDef);
 	}
 
 	public static LuanState newLuanState() {
--- a/src/luan/interp/LuanParser.java	Mon Mar 17 21:17:31 2014 +0000
+++ b/src/luan/interp/LuanParser.java	Mon May 12 05:57:53 2014 +0000
@@ -6,25 +6,20 @@
 import java.util.List;
 import java.util.ArrayList;
 import java.util.Scanner;
-import org.parboiled.BaseParser;
-import org.parboiled.Parboiled;
-import org.parboiled.Rule;
-import org.parboiled.parserunners.ReportingParseRunner;
-import org.parboiled.support.ParsingResult;
-import org.parboiled.support.Var;
-import org.parboiled.support.StringVar;
-import org.parboiled.support.StringBuilderVar;
-import org.parboiled.support.ValueStack;
-import org.parboiled.errors.ErrorUtils;
-import org.parboiled.annotations.Cached;
 import luan.Luan;
 import luan.LuanState;
 import luan.LuanSource;
+import luan.parser.Parser;
+import luan.parser.ParseException;
 
 
-class LuanParser extends BaseParser<Object> {
+final class LuanParser {
 
-	static final class Frame {
+	static FnDef parse(LuanSource source,UpValue.Getter envGetter) throws ParseException {
+		return new LuanParser(source,envGetter).RequiredTarget();
+	}
+
+	private static final class Frame {
 		final Frame parent;
 		final List<String> symbols = new ArrayList<String>();
 		int stackSize = 0;
@@ -76,116 +71,106 @@
 		}
 	}
 
-	static final String _ENV = "_ENV";
-	static final UpValue.Getter[] NO_UP_VALUE_GETTERS = new UpValue.Getter[0];
+	private static final String _ENV = "_ENV";
+	private static final UpValue.Getter[] NO_UP_VALUE_GETTERS = new UpValue.Getter[0];
 
-//	UpValue.Getter envGetter = new UpValue.EnvGetter();
-	final LuanSource source;
-	Frame frame;
-	int nEquals;
+	private final LuanSource source;
+	private Frame frame;
+	private final Parser parser;
 
-	LuanParser(LuanSource source,UpValue.Getter envGetter) {
+	private LuanParser(LuanSource source,UpValue.Getter envGetter) {
 		this.source = source;
 		this.frame = new Frame(envGetter);
-	}
-
-	LuanSource.Element se(int start) {
-		return new LuanSource.Element(source,start,currentIndex());
+		this.parser = new Parser(source.text);
 	}
 
-	boolean nEquals(int n) {
-		nEquals = n;
-		return true;
+	private LuanSource.Element se(int start) {
+		return new LuanSource.Element(source,start,parser.currentIndex());
 	}
 
-	List<String> symbols() {
+	private List<String> symbols() {
 		return frame.symbols;
 	}
 
-	int symbolsSize() {
+	private int symbolsSize() {
 		return frame.symbols.size();
 	}
 
-	boolean addSymbol(String name) {
+	private void addSymbol(String name) {
 		frame.symbols.add(name);
 		if( frame.stackSize < symbolsSize() )
 			frame.stackSize = symbolsSize();
-		return true;
 	}
 
-	boolean addSymbols(List<String> names) {
+	private void addSymbols(List<String> names) {
 		frame.symbols.addAll(names);
 		if( frame.stackSize < symbolsSize() )
 			frame.stackSize = symbolsSize();
-		return true;
 	}
 
-	int stackIndex(String name) {
+	private int stackIndex(String name) {
 		return frame.stackIndex(name);
 	}
 
-	boolean popSymbols(int n) {
+	private void popSymbols(int n) {
 		List<String> symbols = frame.symbols;
 		while( n-- > 0 ) {
 			symbols.remove(symbols.size()-1);
 		}
-		return true;
 	}
 
-	int upValueIndex(String name) {
+	private int upValueIndex(String name) {
 		return frame.upValueIndex(name);
 	}
 
-	boolean incLoops() {
+	private void incLoops() {
 		frame.loops++;
-		return true;
+	}
+
+	private void decLoops() {
+		frame.loops--;
 	}
 
-	boolean decLoops() {
-		frame.loops--;
-		return true;
+	private <T> T required(T t) throws ParseException {
+		if( t==null )
+			throw parser.exception();
+		return t;
 	}
 
-	FnDef newFnDef(int start) {
-		return new FnDef( se(start), (Stmt)pop(), frame.stackSize, symbolsSize(), frame.isVarArg, frame.upValueGetters.toArray(NO_UP_VALUE_GETTERS) );
+	private <T> T required(T t,String msg) throws ParseException {
+		if( t==null )
+			throw parser.exception(msg);
+		return t;
+	}
+
+	private FnDef newFnDef(int start,Stmt stmt) {
+		return new FnDef( se(start), stmt, frame.stackSize, symbolsSize(), frame.isVarArg, frame.upValueGetters.toArray(NO_UP_VALUE_GETTERS) );
 	}
 
-	Rule Target() {
-		Var<Integer> start = new Var<Integer>();
-		return Sequence(
-			Spaces(false),
-			start.set(currentIndex()),
-			FirstOf(
-				Sequence(
-					ExpList(false),
-					push( new ReturnStmt( se(start.get()), (Expressions)pop() ) ),
-					push( newFnDef(start.get()) ),
-					EOI
-				),
-				Sequence(
-					action( frame.isVarArg = true ),
-					Block(),
-					push( newFnDef(start.get()) ),
-					EOI
-				)
-			)
-		);
+	private FnDef RequiredTarget() throws ParseException {
+		Spaces();
+		int start = parser.begin();
+		Expressions exprs = ExpList();
+		if( exprs != null && parser.endOfInput() ) {
+			Stmt stmt = new ReturnStmt( se(start), exprs );
+			return parser.success(newFnDef(start,stmt));
+		}
+		parser.rollback();
+		frame.isVarArg = true;
+		Stmt stmt = RequiredBlock();
+		if( parser.endOfInput() )
+			return parser.success(newFnDef(start,stmt));
+		throw parser.exception();
 	}
 
-	Rule Block() {
-		Var<List<Stmt>> stmts = new Var<List<Stmt>>(new ArrayList<Stmt>());
-		Var<Integer> stackStart = new Var<Integer>(symbolsSize());
-		return Sequence(
-			Optional( Stmt(stmts) ),
-			ZeroOrMore(
-				StmtSep(stmts),
-				Optional( Stmt(stmts) )
-			),
-			push( newBlock(stmts.get(),stackStart.get()) )
-		);
-	}
-
-	Stmt newBlock(List<Stmt> stmts,int stackStart) {
+	private Stmt RequiredBlock() throws ParseException {
+		List<Stmt> stmts = new ArrayList<Stmt>();
+		int stackStart = symbolsSize();
+		Stmt(stmts);
+		while( StmtSep(stmts) ) {
+			Spaces();
+			Stmt(stmts);
+		}
 		int stackEnd = symbolsSize();
 		popSymbols( stackEnd - stackStart );
 		if( stmts.isEmpty() )
@@ -195,610 +180,658 @@
 		return new Block( stmts.toArray(new Stmt[0]), stackStart, stackEnd );
 	}
 
-	Rule StmtSep(Var<List<Stmt>> stmts) {
-		return Sequence(
-			FirstOf(
-				';',
-				Sequence(
-					Optional( "--", ZeroOrMore(NoneOf("\r\n")) ),
-					EndOfLine()
-				),
-				Sequence(
-					OutputStmt(),
-					stmts.get().add( (Stmt)pop() )
-				)
-			),
-			Spaces(false)
-		);
+	private boolean StmtSep(List<Stmt> stmts) throws ParseException {
+		parser.begin();
+		if( parser.match( ';' ) )
+			return parser.success();
+		if( parser.match( "--" ) ) {
+			while( parser.noneOf("\r\n") );
+			EndOfLine();
+			return parser.success();
+		}
+		if( EndOfLine() )
+			return parser.success();
+		parser.rollback();
+		Stmt stmt = OutputStmt();
+		if( stmt != null ) {
+			stmts.add(stmt);
+			return parser.success();
+		}
+		return parser.failure();
 	}
 
-	Rule EndOfLine() {
-		return FirstOf("\r\n", '\r', '\n');
+	private boolean EndOfLine() {
+		return parser.match( "\r\n" ) || parser.match( '\r' ) || parser.match( '\n' );
+	}
+
+	private void Stmt(List<Stmt> stmts) throws ParseException {
+		if( LocalStmt(stmts) )
+			return;
+		Stmt stmt;
+		if( (stmt=ReturnStmt()) != null
+			|| (stmt=FunctionStmt()) != null
+			|| (stmt=LocalFunctionStmt()) != null
+			|| (stmt=BreakStmt()) != null
+			|| (stmt=GenericForStmt()) != null
+			|| (stmt=NumericForStmt()) != null
+			|| (stmt=TryStmt()) != null
+			|| (stmt=DoStmt()) != null
+			|| (stmt=WhileStmt()) != null
+			|| (stmt=FunctionStmt()) != null
+			|| (stmt=RepeatStmt()) != null
+			|| (stmt=IfStmt()) != null
+			|| (stmt=SetStmt()) != null
+			|| (stmt=ExpressionsStmt()) != null
+		) {
+			stmts.add(stmt);
+		}
 	}
 
-	Rule Stmt(Var<List<Stmt>> stmts) {
-		return FirstOf(
-			LocalStmt(stmts),
-			Sequence(
-				FirstOf(
-					ReturnStmt(),
-					FunctionStmt(),
-					LocalFunctionStmt(),
-					BreakStmt(),
-					GenericForStmt(),
-					NumericForStmt(),
-					TryStmt(),
-					DoStmt(),
-					WhileStmt(),
-					RepeatStmt(),
-					IfStmt(),
-					SetStmt(),
-					ExpressionsStmt()
-				),
-				stmts.get().add( (Stmt)pop() )
-			)
-		);
+	private Stmt OutputStmt() throws ParseException {
+		int start = parser.begin();
+		if( !parser.match( "%>" ) )
+			return parser.failure(null);
+		EndOfLine();
+		ExpList.Builder builder = new ExpList.Builder();
+		while(true) {
+			if( parser.match( "<%=" ) ) {
+				Spaces();
+				builder.add( RequiredExpr() );
+				RequiredMatch( "%>" );
+			} else if( parser.match( "<%" ) ) {
+				Spaces();
+				return parser.success(new OutputStmt( se(start), builder.build() ));
+			} else {
+				int i = parser.currentIndex();
+				do {
+					if( parser.match( "%>" ) )
+						throw parser.exception("'%>' unexpected");
+					if( !parser.anyChar() )
+						throw parser.exception("Unclosed output statement");
+				} while( !parser.test( "<%" ) );
+				String match = parser.textFrom(i);
+				builder.add( new ConstExpr(match) );
+			}
+		}
+	}
+
+	private Stmt ReturnStmt() throws ParseException {
+		int start = parser.begin();
+		if( !Keyword("return") )
+			return parser.failure(null);
+		Expressions exprs = ExpList();
+		if( exprs==null )
+			exprs = ExpList.emptyExpList;
+		return parser.success( new ReturnStmt(se(start),exprs) );
+	}
+
+	private Stmt FunctionStmt() throws ParseException {
+		parser.begin();
+		if( !Keyword("function") )
+			return parser.failure(null);
+
+		int start = parser.currentIndex();
+		Var var = nameVar(start,RequiredName());
+		while( parser.match( '.' ) ) {
+			Spaces();
+			var = indexVar( start, var.expr(), NameExpr(false) );
+		}
+		Settable fnName = var.settable();
+
+		FnDef fnDef = RequiredFunction(false);
+		return parser.success( new SetStmt(fnName,fnDef) );
+	}
+
+	private Stmt LocalFunctionStmt() throws ParseException {
+		parser.begin();
+		if( !(Keyword("local") && Keyword("function")) )
+			return parser.failure(null);
+		String name = RequiredName();
+		addSymbol( name );
+		FnDef fnDef = RequiredFunction(false);
+		return parser.success( new SetStmt( new SetLocalVar(symbolsSize()-1), fnDef ) );
+	}
+
+	private Stmt BreakStmt() throws ParseException {
+		parser.begin();
+		if( !Keyword("break") )
+			return parser.failure(null);
+		if( frame.loops <= 0 )
+			throw parser.exception("'break' outside of loop");
+		return parser.success( new BreakStmt() );
 	}
 
-	Rule OutputStmt() {
-		Var<Integer> start = new Var<Integer>();
-		Var<ExpList.Builder> builder = new Var<ExpList.Builder>(new ExpList.Builder());
-		return Sequence(
-			start.set(currentIndex()),
-			"%>", Optional( EndOfLine() ),
-			ZeroOrMore(
-				FirstOf(
-					Sequence(
-						OneOrMore(
-							TestNot("<%"),
-							TestNot("%>"),
-							ANY
-						),
-						addToExpList(builder.get(),new ConstExpr(match()))
-					),
-					Sequence(
-						"<%=", Spaces(false),
-						Expr(false),
-						addToExpList(builder.get()),
-						"%>"
-					)
-				)
-			),
-			"<%", Spaces(false),
-			push( new OutputStmt( se(start.get()), builder.get().build() ) )
-		);
+	private Stmt GenericForStmt() throws ParseException {
+		int start = parser.begin();
+		int stackStart = symbolsSize();
+		if( !Keyword("for") )
+			return parser.failure(null);
+		List<String> names = RequiredNameList(false);
+		if( !Keyword("in") )
+			return parser.failure(null);
+		Expr expr = RequiredExpr();
+		RequiredKeyword("do");
+		addSymbols(names);
+		Stmt loop = RequiredLoopBlock();
+		RequiredKeyword("end");
+		Stmt stmt = new GenericForStmt( se(start), stackStart, symbolsSize() - stackStart, expr, loop );
+		popSymbols( symbolsSize() - stackStart );
+		return parser.success(stmt);
 	}
 
-	Rule ReturnStmt() {
-		Var<Integer> start = new Var<Integer>();
-		return Sequence(
-			start.set(currentIndex()),
-			Keyword("return",false), Expressions(false),
-			push( new ReturnStmt( se(start.get()), (Expressions)pop() ) )
-		);
+	private Stmt NumericForStmt() throws ParseException {
+		int start = parser.begin();
+		if( !Keyword("for") )
+			return parser.failure(null);
+		String name = RequiredName();
+		RequiredMatch( "=" );
+		Spaces();
+		Expr from = RequiredExpr();
+		RequiredKeyword("to");
+		Expr to = RequiredExpr();
+		Expr step = Keyword("step") ? RequiredExpr() : new ConstExpr(1);
+		addSymbol(name);  // add "for" var to symbols
+		RequiredKeyword("do");
+		Stmt loop = RequiredLoopBlock();
+		RequiredKeyword("end");
+		Stmt stmt = new NumericForStmt( se(start), symbolsSize()-1, from, to, step, loop );
+		popSymbols(1);
+		return parser.success(stmt);
+	}
+
+	private Stmt TryStmt() throws ParseException {
+		parser.begin();
+		if( !Keyword("try") )
+			return parser.failure(null);
+		Stmt tryBlock = RequiredBlock();
+		RequiredKeyword("catch");
+		String name = RequiredName();
+		addSymbol(name);
+		RequiredKeyword("do");
+		Stmt catchBlock = RequiredBlock();
+		RequiredKeyword("end");
+		Stmt stmt = new TryStmt( tryBlock, symbolsSize()-1, catchBlock );
+		popSymbols(1);
+		return parser.success(stmt);
+	}
+
+	private Stmt DoStmt() throws ParseException {
+		parser.begin();
+		if( !Keyword("do") )
+			return parser.failure(null);
+		Stmt stmt = RequiredBlock();
+		RequiredKeyword("end");
+		return parser.success(stmt);
 	}
 
-	Rule FunctionStmt() {
-		return Sequence(
-			Keyword("function",false), FnName(), Function(false),
-			push( new SetStmt( (Settable)pop(1), expr(pop()) ) )
-		);
+	private boolean LocalStmt(List<Stmt> stmts) throws ParseException {
+		parser.begin();
+		if( !Keyword("local") )
+			return parser.failure();
+		List<String> names = NameList(false);
+		if( names==null )
+			return parser.failure();
+		if( parser.match( '=' ) ) {
+			Spaces();
+			Expressions values = ExpList();
+			if( values==null )
+				throw parser.exception("Expressions expected");
+			SetLocalVar[] vars = new SetLocalVar[names.size()];
+			int stackStart = symbolsSize();
+			for( int i=0; i<vars.length; i++ ) {
+				vars[i] = new SetLocalVar(stackStart+i);
+			}
+			stmts.add( new SetStmt( vars, values ) );
+		}
+		addSymbols(names);
+		return parser.success();
+	}
+
+	private List<String> RequiredNameList(boolean inParens) throws ParseException {
+		parser.begin();
+		List<String> names = NameList(inParens);
+		if( names==null )
+			parser.exception("Name expected");
+		return parser.success(names);
+	}
+
+	private List<String> NameList(boolean inParens) throws ParseException {
+		String name = Name(inParens);
+		if( name==null )
+			return null;
+		List<String> names = new ArrayList<String>();
+		names.add(name);
+		while( parser.match( ',' ) ) {
+			Spaces(inParens);
+			names.add( RequiredName() );
+		}
+		return names;
 	}
 
-	Rule FnName() {
-		Var<Integer> start = new Var<Integer>();
-		return Sequence(
-			start.set(currentIndex()),
-			push(null),  // marker
-			Name(false),
-			ZeroOrMore(
-				'.', Spaces(false),
-				makeVarExp(start.get()),
-				NameExpr(false)
-			),
-			makeSettableVar(start.get())
-		);
+	private Stmt WhileStmt() throws ParseException {
+		parser.begin();
+		if( !Keyword("while") )
+			return parser.failure(null);
+		Expr cnd = RequiredExpr();
+		RequiredKeyword("do");
+		Stmt loop = RequiredLoopBlock();
+		RequiredKeyword("end");
+		return parser.success( new WhileStmt(cnd,loop) );
+	}
+
+	private Stmt RepeatStmt() throws ParseException {
+		parser.begin();
+		if( !Keyword("repeat") )
+			return parser.failure(null);
+		Stmt loop = RequiredLoopBlock();
+		RequiredKeyword("until");
+		Expr cnd = RequiredExpr();
+		return parser.success( new RepeatStmt(loop,cnd) );
 	}
 
-	Rule LocalFunctionStmt() {
-		return Sequence(
-			Keyword("local",false), Keyword("function",false),
-			Name(false),
-			addSymbol( (String)pop() ),
-			Function(false),
-			push( new SetStmt( new SetLocalVar(symbolsSize()-1), expr(pop()) ) )
-		);
+	private Stmt RequiredLoopBlock() throws ParseException {
+		incLoops();
+		Stmt stmt = RequiredBlock();
+		decLoops();
+		return stmt;
 	}
 
-	Rule BreakStmt() {
-		return Sequence(
-			Keyword("break",false),
-			frame.loops > 0,
-			push( new BreakStmt() )
-		);
+	private Stmt IfStmt() throws ParseException {
+		parser.begin();
+		if( !Keyword("if") )
+			return parser.failure(null);
+		return parser.success( IfStmt2() );
+	}
+
+	private Stmt IfStmt2() throws ParseException {
+		Expr cnd = RequiredExpr();
+		RequiredKeyword("then");
+		Stmt thenBlock = RequiredBlock();
+		Stmt elseBlock;
+		if( Keyword("elseif") ) {
+			elseBlock = IfStmt2();
+		} else {
+			elseBlock = Keyword("else") ? RequiredBlock() : Stmt.EMPTY;
+			RequiredKeyword("end");
+		}
+		return new IfStmt(cnd,thenBlock,elseBlock);
 	}
 
-	Rule GenericForStmt() {
-		Var<Integer> start = new Var<Integer>();
-		Var<Integer> stackStart = new Var<Integer>(symbolsSize());
-		Var<List<String>> names = new Var<List<String>>(new ArrayList<String>());
-		return Sequence(
-			start.set(currentIndex()),
-			Keyword("for",false), NameList(false,names), Keyword("in",false), Expr(false), Keyword("do",false),
-			addSymbols(names.get()),
-			LoopBlock(), Keyword("end",false),
-			push( new GenericForStmt( se(start.get()), stackStart.get(), symbolsSize() - stackStart.get(), expr(pop(1)), (Stmt)pop() ) ),
-			popSymbols( symbolsSize() - stackStart.get() )
-		);
+	private Stmt SetStmt() throws ParseException {
+		parser.begin();
+		List<Settable> vars = new ArrayList<Settable>();
+		Settable s = SettableVar();
+		if( s == null )
+			return parser.failure(null);
+		vars.add(s);
+		while( parser.match( ',' ) ) {
+			Spaces();
+			s = SettableVar();
+			if( s == null )
+				return parser.failure(null);
+			vars.add(s);
+		}
+		if( !parser.match( '=' ) )
+			return parser.failure(null);
+		Spaces();
+		Expressions values = ExpList();
+		if( values==null )
+			throw parser.exception("Expressions expected");
+		return parser.success( new SetStmt( vars.toArray(new Settable[0]), values ) );
+	}
+
+	private Stmt ExpressionsStmt() throws ParseException {
+		parser.begin();
+		Expressions exprs = ExpList();
+		if( exprs==null )
+			return parser.failure(null);
+		return parser.success( new ExpressionsStmt(exprs) );
+	}
+
+	private Settable SettableVar() throws ParseException {
+		int start = parser.begin();
+		Var var = VarZ(false);
+		if( var==null )
+			return null;
+		return var.settable();
+	}
+
+	private Expr RequiredExpr() throws ParseException {
+		return RequiredExpr(false);
+	}
+
+	private Expr Expr() throws ParseException {
+		return Expr(false);
+	}
+
+	private Expr RequiredExpr(boolean inParens) throws ParseException {
+		parser.begin();
+		return parser.success(required(Expr(inParens),"Bad expression"));
+	}
+
+	private Expr Expr(boolean inParens) throws ParseException {
+		parser.begin();
+		Expressions exps = VarArgs(inParens);
+		if( exps != null )
+			return parser.success( new ExpressionsExpr(exps) );
+		Expr exp = OrExpr(inParens);
+		if( exp==null )
+			return parser.failure(null);
+		return parser.success(exp);
 	}
 
-	Rule NumericForStmt() {
-		Var<Integer> start = new Var<Integer>();
-		return Sequence(
-			start.set(currentIndex()),
-			Keyword("for",false), Name(false), '=', Spaces(false), Expr(false), Keyword("to",false), Expr(false),
-			push( new ConstExpr(1) ),  // default step
-			Optional(
-				Keyword("step",false),
-				drop(),
-				Expr(false)
-			),
-			addSymbol( (String)pop(3) ),  // add "for" var to symbols
-			Keyword("do",false), LoopBlock(), Keyword("end",false),
-			push( new NumericForStmt( se(start.get()), symbolsSize()-1, expr(pop(3)), expr(pop(2)), expr(pop(1)), (Stmt)pop() ) ),
-			popSymbols(1)
-		);
+	private Expr OrExpr(boolean inParens) throws ParseException {
+		int start = parser.begin();
+		Expr exp = AndExpr(inParens);
+		if( exp==null )
+			return parser.failure(null);
+		while( Keyword("or",inParens) ) {
+			exp = new OrExpr( se(start), exp, required(AndExpr(inParens)) );
+		}
+		return parser.success(exp);
+	}
+
+	private Expr AndExpr(boolean inParens) throws ParseException {
+		int start = parser.begin();
+		Expr exp = RelExpr(inParens);
+		if( exp==null )
+			return parser.failure(null);
+		while( Keyword("and",inParens) ) {
+			exp = new AndExpr( se(start), exp, required(RelExpr(inParens)) );
+		}
+		return parser.success(exp);
 	}
 
-	Rule TryStmt() {
-		return Sequence(
-			Keyword("try",false), Block(),
-			Keyword("catch",false), Name(false), addSymbol( (String)pop() ),
-			Keyword("do",false), Block(), Keyword("end",false),
-			push( new TryStmt( (Stmt)pop(1), symbolsSize()-1, (Stmt)pop() ) ),
-			popSymbols(1)
-		);
+	private Expr RelExpr(boolean inParens) throws ParseException {
+		int start = parser.begin();
+		Expr exp = ConcatExpr(inParens);
+		if( exp==null )
+			return parser.failure(null);
+		while(true) {
+			if( parser.match("==") ) {
+				Spaces(inParens);
+				exp = new EqExpr( se(start), exp, required(ConcatExpr(inParens)) );
+			} else if( parser.match("~=") ) {
+				Spaces(inParens);
+				exp = new NotExpr( se(start), new EqExpr( se(start), exp, required(ConcatExpr(inParens)) ) );
+			} else if( parser.match("<=") ) {
+				Spaces(inParens);
+				exp = new LeExpr( se(start), exp, required(ConcatExpr(inParens)) );
+			} else if( parser.match(">=") ) {
+				Spaces(inParens);
+				exp = new LeExpr( se(start), required(ConcatExpr(inParens)), exp );
+			} else if( parser.match("<") ) {
+				Spaces(inParens);
+				exp = new LtExpr( se(start), exp, required(ConcatExpr(inParens)) );
+			} else if( parser.match(">") ) {
+				Spaces(inParens);
+				exp = new LtExpr( se(start), required(ConcatExpr(inParens)), exp );
+			} else
+				break;
+		}
+		return parser.success(exp);
 	}
 
-	Rule DoStmt() {
-		return Sequence(
-			Keyword("do",false), Block(), Keyword("end",false)
-		);
-	}
-
-	Rule LocalStmt(Var<List<Stmt>> stmts) {
-		Var<List<String>> names = new Var<List<String>>(new ArrayList<String>());
-		return Sequence(
-			Keyword("local",false), NameList(false,names),
-			Optional(
-				'=', Spaces(false), ExpList(false),
-				stmts.get().add( newSetLocalStmt(names.get().size()) )
-			),
-			addSymbols(names.get())
-		);
+	private Expr ConcatExpr(boolean inParens) throws ParseException {
+		int start = parser.begin();
+		Expr exp = SumExpr(inParens);
+		if( exp==null )
+			return parser.failure(null);
+		if( parser.match("..") ) {
+			Spaces(inParens);
+			exp = new ConcatExpr( se(start), exp, required(ConcatExpr(inParens)) );
+		}
+		return parser.success(exp);
 	}
 
-	Rule NameList(boolean inParens,Var<List<String>> names) {
-		return Sequence(
-			Name(inParens),
-			names.get().add( (String)pop() ),
-			ZeroOrMore(
-				',', Spaces(inParens), Name(inParens),
-				names.get().add( (String)pop() )
-			)
-		);
+	private Expr SumExpr(boolean inParens) throws ParseException {
+		int start = parser.begin();
+		Expr exp = TermExpr(inParens);
+		if( exp==null )
+			return parser.failure(null);
+		while(true) {
+			if( parser.match('+') ) {
+				Spaces(inParens);
+				exp = new AddExpr( se(start), exp, required(TermExpr(inParens)) );
+			} else if( Minus() ) {
+				Spaces(inParens);
+				exp = new SubExpr( se(start), exp, required(TermExpr(inParens)) );
+			} else
+				break;
+		}
+		return parser.success(exp);
+	}
+
+	private boolean Minus() {
+		parser.begin();
+		return parser.match('-') && !parser.match('-') ? parser.success() : parser.failure();
 	}
 
-	SetStmt newSetLocalStmt(int nVars) {
-		Expressions values = (Expressions)pop();
-		SetLocalVar[] vars = new SetLocalVar[nVars];
-		int stackStart = symbolsSize();
-		for( int i=0; i<vars.length; i++ ) {
-			vars[i] = new SetLocalVar(stackStart+i);
+	private Expr TermExpr(boolean inParens) throws ParseException {
+		int start = parser.begin();
+		Expr exp = UnaryExpr(inParens);
+		if( exp==null )
+			return parser.failure(null);
+		while(true) {
+			if( parser.match('*') ) {
+				Spaces(inParens);
+				exp = new MulExpr( se(start), exp, required(UnaryExpr(inParens)) );
+			} else if( parser.match('/') ) {
+				Spaces(inParens);
+				exp = new DivExpr( se(start), exp, required(UnaryExpr(inParens)) );
+			} else if( parser.match('%') ) {
+				Spaces(inParens);
+				exp = new ModExpr( se(start), exp, required(UnaryExpr(inParens)) );
+			} else
+				break;
 		}
-		return new SetStmt( vars, values );
+		return parser.success(exp);
 	}
 
-	Rule WhileStmt() {
-		return Sequence(
-			Keyword("while",false), Expr(false), Keyword("do",false), LoopBlock(), Keyword("end",false),
-			push( new WhileStmt( expr(pop(1)), (Stmt)pop() ) )
-		);
+	private Expr UnaryExpr(boolean inParens) throws ParseException {
+		int start = parser.begin();
+		if( parser.match('#') ) {
+			Spaces(inParens);
+			return parser.success( new LenExpr( se(start), required(UnaryExpr(inParens)) ) );
+		}
+		if( Minus() ) {
+			Spaces(inParens);
+			return parser.success( new UnmExpr( se(start), required(UnaryExpr(inParens)) ) );
+		}
+		if( Keyword("not",inParens) ) {
+			Spaces(inParens);
+			return parser.success( new NotExpr( se(start), required(UnaryExpr(inParens)) ) );
+		}
+		Expr exp = PowExpr(inParens);
+		if( exp==null )
+			return parser.failure(null);
+		return parser.success(exp);
+	}
+
+	private Expr PowExpr(boolean inParens) throws ParseException {
+		int start = parser.begin();
+		Expr exp = SingleExpr(inParens);
+		if( exp==null )
+			return parser.failure(null);
+		if( parser.match('^') ) {
+			Spaces(inParens);
+			exp = new ConcatExpr( se(start), exp, required(PowExpr(inParens)) );
+		}
+		return parser.success(exp);
 	}
 
-	Rule RepeatStmt() {
-		return Sequence(
-			Keyword("repeat",false), LoopBlock(), Keyword("until",false), Expr(false),
-			push( new RepeatStmt( (Stmt)pop(1), expr(pop()) ) )
-		);
+	private Expr SingleExpr(boolean inParens) throws ParseException {
+		parser.begin();
+		Expr exp;
+		exp = FunctionExpr(inParens);
+		if( exp != null )
+			return parser.success(exp);
+		exp = TableExpr(inParens);
+		if( exp != null )
+			return parser.success(exp);
+		exp = VarExp(inParens);
+		if( exp != null )
+			return parser.success(exp);
+		exp = Literal(inParens);
+		if( exp != null )
+			return parser.success(exp);
+		return parser.failure(null);
 	}
 
-	Rule LoopBlock() {
-		return Sequence( incLoops(), Block(), decLoops() );
+	private Expr FunctionExpr(boolean inParens) throws ParseException {
+		if( !Keyword("function",inParens) )
+			return null;
+		return RequiredFunction(inParens);
 	}
 
-	Rule IfStmt() {
-		Var<Integer> n = new Var<Integer>(1);
-		return Sequence(
-			Keyword("if",false), Expr(false), Keyword("then",false), Block(),
-			push(Stmt.EMPTY),
-			ZeroOrMore(
-				Keyword("elseif",false), drop(), Expr(false), Keyword("then",false), Block(),
-				push(Stmt.EMPTY),
-				n.set(n.get()+1)
-			),
-			Optional(
-				Keyword("else",false), drop(), Block()
-			),
-			Keyword("end",false),
-			buildIfStmt(n.get())
-		);
+	private FnDef RequiredFunction(boolean inParens) throws ParseException {
+		int start = parser.begin();
+		RequiredMatch('(');
+		Spaces(true);
+		frame = new Frame(frame);
+		List<String> names = NameList(false);
+		if( names != null ) {
+			addSymbols(names);
+			if( parser.match(',') ) {
+				Spaces(true);
+				if( !parser.match("...") )
+					throw parser.exception();
+				frame.isVarArg = true;
+			}
+		} else if( parser.match("...") ) {
+			Spaces(true);
+			frame.isVarArg = true;
+		}
+		RequiredMatch(')');
+		Spaces(inParens);
+		Stmt block = RequiredBlock();
+		RequiredKeyword("end",inParens);
+		FnDef fnDef = newFnDef(start,block);
+		frame = frame.parent;
+		return parser.success(fnDef);
 	}
 
-	boolean buildIfStmt(int n) {
-		while( n-- > 0 ) {
-			Stmt elseStmt = (Stmt)pop();
-			Stmt thenStmt = (Stmt)pop();
-			Expr cnd = expr(pop());
-			push( new IfStmt(cnd,thenStmt,elseStmt) );
-		}
+	private VarArgs VarArgs(boolean inParens) throws ParseException {
+		int start = parser.begin();
+		if( !frame.isVarArg || !parser.match("...") )
+			return parser.failure(null);
+		Spaces(inParens);
+		return parser.success( new VarArgs(se(start)) );
+	}
+
+	private Expr TableExpr(boolean inParens) throws ParseException {
+		int start = parser.begin();
+		if( !parser.match('{') )
+			return parser.failure(null);
+		Spaces(true);
+		List<TableExpr.Field> fields = new ArrayList<TableExpr.Field>();
+		ExpList.Builder builder = new ExpList.Builder();
+		while( Field(fields,builder) && FieldSep() );
+		RequiredMatch('}');
+		return parser.success( new TableExpr( se(start), fields.toArray(new TableExpr.Field[0]), builder.build() ) );
+	}
+
+	private boolean FieldSep() throws ParseException {
+		if( !parser.anyOf(",;") )
+			return false;
+		Spaces(true);
 		return true;
 	}
 
-	Rule SetStmt() {
-		return Sequence(
-			VarList(),
-			'=', Spaces(false),
-			ExpList(false),
-			push( newSetStmt() )
-		);
-	}
-
-	Rule ExpressionsStmt() {
-		return Sequence(
-			ExpList(false),
-			push( new ExpressionsStmt((Expressions)pop()) )
-		);
-	}
-
-	SetStmt newSetStmt() {
-		Expressions values = (Expressions)pop();
-		@SuppressWarnings("unchecked")
-		List<Settable> vars = (List<Settable>)pop();
-		return new SetStmt( vars.toArray(new Settable[0]), values );
-	}
-
-	Rule VarList() {
-		Var<List<Settable>> vars = new Var<List<Settable>>(new ArrayList<Settable>());
-		return Sequence(
-			SettableVar(),
-			vars.get().add( (Settable)pop() ),
-			ZeroOrMore(
-				',', Spaces(false), SettableVar(),
-				vars.get().add( (Settable)pop() )
-			),
-			push(vars.get())
-		);
-	}
-
-	Rule SettableVar() {
-		Var<Integer> start = new Var<Integer>();
-		return Sequence(
-			start.set(currentIndex()),
-			Var(false),
-			makeSettableVar(start.get())
-		);
-	}
-
-	boolean makeSettableVar(int start) {
-		Object obj2 = pop();
-		if( obj2==null )
-			return false;
-		Object obj1 = pop();
-		if( obj1!=null ) {
-			Expr key = expr(obj2);
-			Expr table = expr(obj1);
-			return push( new SetTableEntry(se(start),table,key) );
+	private boolean Field(List<TableExpr.Field> fields,ExpList.Builder builder) throws ParseException {
+		parser.begin();
+		Expr exp = SubExpr(true);
+		if( exp==null )
+			exp = NameExpr(true);
+		if( exp!=null && parser.match('=') ) {
+			fields.add( new TableExpr.Field( exp, required(Expr(true)) ) );
+			return parser.success();
 		}
-		String name = (String)obj2;
-		int index = stackIndex(name);
-		if( index != -1 )
-			return push( new SetLocalVar(index) );
-		index = upValueIndex(name);
-		if( index != -1 )
-			return push( new SetUpVar(index) );
-		return push( new SetTableEntry( se(start), env(), new ConstExpr(name) ) );
-	}
-
-	@Cached
-	Rule Expr(boolean inParens) {
-		return FirstOf(
-			VarArgs(inParens),
-			OrExpr(inParens)
-		);
-	}
-
-	@Cached
-	Rule OrExpr(boolean inParens) {
-		Var<Integer> start = new Var<Integer>();
-		return Sequence(
-			start.set(currentIndex()),
-			AndExpr(inParens),
-			ZeroOrMore( Keyword("or",inParens), AndExpr(inParens), push( new OrExpr(se(start.get()),expr(pop(1)),expr(pop())) ) )
-		);
+		parser.rollback();
+		exp = Expr(true);
+		if( exp != null ) {
+			builder.add(exp);
+			return parser.success();
+		}
+		return parser.failure();
 	}
 
-	@Cached
-	Rule AndExpr(boolean inParens) {
-		Var<Integer> start = new Var<Integer>();
-		return Sequence(
-			start.set(currentIndex()),
-			RelExpr(inParens),
-			ZeroOrMore( Keyword("and",inParens), RelExpr(inParens), push( new AndExpr(se(start.get()),expr(pop(1)),expr(pop())) ) )
-		);
-	}
-
-	@Cached
-	Rule RelExpr(boolean inParens) {
-		Var<Integer> start = new Var<Integer>();
-		return Sequence(
-			start.set(currentIndex()),
-			ConcatExpr(inParens),
-			ZeroOrMore(
-				FirstOf(
-					Sequence( "==", Spaces(inParens), ConcatExpr(inParens), push( new EqExpr(se(start.get()),expr(pop(1)),expr(pop())) ) ),
-					Sequence( "~=", Spaces(inParens), ConcatExpr(inParens), push( new NotExpr(se(start.get()),new EqExpr(se(start.get()),expr(pop(1)),expr(pop()))) ) ),
-					Sequence( "<=", Spaces(inParens), ConcatExpr(inParens), push( new LeExpr(se(start.get()),expr(pop(1)),expr(pop())) ) ),
-					Sequence( ">=", Spaces(inParens), ConcatExpr(inParens), push( new LeExpr(se(start.get()),expr(pop()),expr(pop())) ) ),
-					Sequence( "<", Spaces(inParens), ConcatExpr(inParens), push( new LtExpr(se(start.get()),expr(pop(1)),expr(pop())) ) ),
-					Sequence( ">", Spaces(inParens), ConcatExpr(inParens), push( new LtExpr(se(start.get()),expr(pop()),expr(pop())) ) )
-				)
-			)
-		);
-	}
-
-	@Cached
-	Rule ConcatExpr(boolean inParens) {
-		Var<Integer> start = new Var<Integer>();
-		return Sequence(
-			start.set(currentIndex()),
-			SumExpr(inParens),
-			Optional( "..", Spaces(inParens), ConcatExpr(inParens), push( new ConcatExpr(se(start.get()),expr(pop(1)),expr(pop())) ) )
-		);
+	private Expr VarExp(boolean inParens) throws ParseException {
+		Var var = VarZ(inParens);
+		return var==null ? null : var.expr();
 	}
 
-	@Cached
-	Rule SumExpr(boolean inParens) {
-		Var<Integer> start = new Var<Integer>();
-		return Sequence(
-			start.set(currentIndex()),
-			TermExpr(inParens),
-			ZeroOrMore(
-				FirstOf(
-					Sequence( '+', Spaces(inParens), TermExpr(inParens), push( new AddExpr(se(start.get()),expr(pop(1)),expr(pop())) ) ),
-					Sequence( '-', TestNot('-'), Spaces(inParens), TermExpr(inParens), push( new SubExpr(se(start.get()),expr(pop(1)),expr(pop())) ) )
-				)
-			)
-		);
-	}
-
-	@Cached
-	Rule TermExpr(boolean inParens) {
-		Var<Integer> start = new Var<Integer>();
-		return Sequence(
-			start.set(currentIndex()),
-			UnaryExpr(inParens),
-			ZeroOrMore(
-				FirstOf(
-					Sequence( '*', Spaces(inParens), UnaryExpr(inParens), push( new MulExpr(se(start.get()),expr(pop(1)),expr(pop())) ) ),
-					Sequence( '/', Spaces(inParens), UnaryExpr(inParens), push( new DivExpr(se(start.get()),expr(pop(1)),expr(pop())) ) ),
-					Sequence( '%', Spaces(inParens), UnaryExpr(inParens), push( new ModExpr(se(start.get()),expr(pop(1)),expr(pop())) ) )
-				)
-			)
-		);
-	}
-
-	@Cached
-	Rule UnaryExpr(boolean inParens) {
-		Var<Integer> start = new Var<Integer>();
-		return Sequence(
-			start.set(currentIndex()),
-			FirstOf(
-				Sequence( '#', Spaces(inParens), PowExpr(inParens), push( new LenExpr(se(start.get()),expr(pop())) ) ),
-				Sequence( '-', TestNot('-'), Spaces(inParens), PowExpr(inParens), push( new UnmExpr(se(start.get()),expr(pop())) ) ),
-				Sequence( Keyword("not",inParens), PowExpr(inParens), push( new NotExpr(se(start.get()),expr(pop())) ) ),
-				PowExpr(inParens)
-			)
-		);
-	}
-
-	@Cached
-	Rule PowExpr(boolean inParens) {
-		Var<Integer> start = new Var<Integer>();
-		return Sequence(
-			start.set(currentIndex()),
-			SingleExpr(inParens),
-			Optional( '^', Spaces(inParens), PowExpr(inParens), push( new PowExpr(se(start.get()),expr(pop(1)),expr(pop())) ) )
-		);
+	private Var VarZ(boolean inParens) throws ParseException {
+		int start = parser.begin();
+		Var var = VarStart(inParens);
+		if( var==null )
+			return parser.failure(null);
+		Var var2;
+		while( (var2=Var2(inParens,start,var.expr())) != null ) {
+			var = var2;
+		}
+		return parser.success(var);
 	}
 
-	@Cached
-	Rule SingleExpr(boolean inParens) {
-		return FirstOf(
-			FunctionExpr(inParens),
-			TableExpr(inParens),
-			VarExp(inParens),
-			Literal(inParens)
-		);
-	}
-
-	@Cached
-	Rule FunctionExpr(boolean inParens) {
-		return Sequence( "function", Spaces(inParens), Function(inParens) );
-	}
-
-	@Cached
-	Rule Function(boolean inParens) {
-		Var<Integer> start = new Var<Integer>();
-		Var<List<String>> names = new Var<List<String>>(new ArrayList<String>());
-		return Sequence(
-			start.set(currentIndex()),
-			'(', Spaces(true),
-			action( frame = new Frame(frame) ),
-			Optional(
-				FirstOf(
-					Sequence(
-						NameList(true,names), addSymbols(names.get()),
-						Optional( ',', Spaces(true), VarArgName() )
-					),
-					VarArgName()
-				)
-			),
-			')', Spaces(inParens), Block(), Keyword("end",inParens),
-			push( newFnDef(start.get()) ),
-			action( frame = frame.parent )
-		);
+	private Var Var2(boolean inParens,int start,Expr exp1) throws ParseException {
+		Var var = VarExt(inParens,start,exp1);
+		if( var != null )
+			return var;
+		if( parser.match("->") ) {
+			Spaces(inParens);
+			ExpList.Builder builder = new ExpList.Builder();
+			builder.add(exp1);
+			Expr exp2 = RequiredVarExpB(inParens);
+			FnCall fnCall = required(Args( inParens, start, exp2, builder ));
+			return exprVar( new ExpressionsExpr(fnCall) );
+		}
+		FnCall fnCall = Args( inParens, start, exp1, new ExpList.Builder() );
+		if( fnCall != null )
+			return exprVar( new ExpressionsExpr(fnCall) );
+		return null;
 	}
 
-	Rule VarArgName() {
-		return Sequence(
-			"...", Spaces(true),
-			action( frame.isVarArg = true )
-		);
-	}
-
-	@Cached
-	Rule VarArgs(boolean inParens) {
-		Var<Integer> start = new Var<Integer>();
-		return Sequence(
-			start.set(currentIndex()),
-			"...", Spaces(inParens),
-			frame.isVarArg,
-			push( new VarArgs(se(start.get())) )
-		);
-	}
-
-	@Cached
-	Rule TableExpr(boolean inParens) {
-		Var<Integer> start = new Var<Integer>();
-		Var<List<TableExpr.Field>> fields = new Var<List<TableExpr.Field>>(new ArrayList<TableExpr.Field>());
-		Var<ExpList.Builder> builder = new Var<ExpList.Builder>(new ExpList.Builder());
-		return Sequence(
-			start.set(currentIndex()),
-			'{', Spaces(true),
-			Optional(
-				Field(fields,builder),
-				ZeroOrMore(
-					FieldSep(),
-					Field(fields,builder)
-				),
-				Optional( FieldSep() )
-			),
-			'}',
-			Spaces(inParens),
-			push( new TableExpr( se(start.get()), fields.get().toArray(new TableExpr.Field[0]), builder.get().build() ) )
-		);
-	}
-
-	Rule FieldSep() {
-		return Sequence( AnyOf(",;"), Spaces(true) );
+	private Expr RequiredVarExpB(boolean inParens) throws ParseException {
+		int start = parser.begin();
+		Var var = required(VarStart(inParens));
+		Var var2;
+		while( (var2=VarExt(inParens,start,var.expr())) != null ) {
+			var = var2;
+		}
+		return parser.success(var.expr());
 	}
 
-	Rule Field(Var<List<TableExpr.Field>> fields,Var<ExpList.Builder> builder) {
-		return FirstOf(
-			Sequence(
-				FirstOf( SubExpr(true), NameExpr(true) ),
-				'=', Spaces(true), Expr(true),
-				fields.get().add( new TableExpr.Field( expr(pop(1)), expr(pop()) ) )
-			),
-			Sequence(
-				Expr(true),
-				addToExpList(builder.get())
-			)
-		);
-	}
-
-	static Expr expr(Object obj) {
-		if( obj instanceof Expressions )
-			return new ExpressionsExpr((Expressions)obj);
-		return (Expr)obj;
-	}
-
-	@Cached
-	Rule VarExp(boolean inParens) {
-		Var<Integer> start = new Var<Integer>();
-		return Sequence(
-			start.set(currentIndex()),
-			Var(inParens),
-			makeVarExp(start.get())
-		);
+	private Var VarExt(boolean inParens,int start,Expr exp1) throws ParseException {
+		parser.begin();
+		Expr exp2 = SubExpr(inParens);
+		exp2 = SubExpr(inParens);
+		if( exp2 != null )
+			return parser.success(indexVar(start,exp1,exp2));
+		if( parser.match('.') ) {
+			Spaces(inParens);
+			exp2 = NameExpr(inParens);
+			if( exp2!=null )
+				return parser.success(indexVar(start,exp1,exp2));
+		}
+		return parser.failure(null);
 	}
 
-	@Cached
-	Rule Var(boolean inParens) {
-		Var<Integer> start = new Var<Integer>();
-		Var<ExpList.Builder> builder = new Var<ExpList.Builder>();
-		return Sequence(
-			start.set(currentIndex()),
-			VarStart(inParens),
-			ZeroOrMore(
-				makeVarExp(start.get()),
-				FirstOf(
-					VarExt(inParens),
-					Sequence(
-						builder.set(new ExpList.Builder()),
-						Args(inParens,start,builder)
-					),
-					Sequence(
-						"->", Spaces(inParens),
-						builder.set(new ExpList.Builder()),
-						addToExpList(builder.get()),
-						VarExpB(inParens),
-						Args(inParens,start,builder)
-					)
-				)
-			)
-		);
+	private Var VarStart(boolean inParens) throws ParseException {
+		int start = parser.begin();
+		if( parser.match('(') ) {
+			Spaces(true);
+			Expr exp = Expr(true);
+			RequiredMatch(')');
+			Spaces(inParens);
+			return parser.success(exprVar(exp));
+		}
+		String name = Name(inParens);
+		if( name != null )
+			return parser.success(nameVar(start,name));
+		return parser.failure(null);
 	}
 
-	@Cached
-	Rule VarExpB(boolean inParens) {
-		Var<Integer> start = new Var<Integer>();
-		return Sequence(
-			start.set(currentIndex()),
-			VarStart(inParens),
-			ZeroOrMore(
-				makeVarExp(start.get()),
-				VarExt(inParens)
-			),
-			makeVarExp(start.get())
-		);
-	}
-
-	@Cached
-	Rule VarExt(boolean inParens) {
-		return FirstOf(
-			SubExpr(inParens),
-			Sequence( '.', Spaces(inParens), NameExpr(inParens) )
-		);
-	}
-
-	@Cached
-	Rule VarStart(boolean inParens) {
-		return FirstOf(
-			Sequence(
-				'(', Spaces(true), Expr(true), ')', Spaces(inParens),
-				push(expr(pop())),
-				push(null)  // marker
-			),
-			Sequence(
-				push(null),  // marker
-				Name(inParens)
-			)
-		);
-	}
-
-	Expr env() {
+	private Expr env() {
 		int index = stackIndex(_ENV);
 		if( index != -1 )
 			return new GetLocalVar(null,index);
@@ -808,135 +841,187 @@
 		throw new RuntimeException("_ENV not found");
 	}
 
-	boolean makeVarExp(int start) {
-		Object obj2 = pop();
-		if( obj2==null )
-			return true;
-		Object obj1 = pop();
-		if( obj1 != null )
-			return push( new IndexExpr( se(start), expr(obj1), expr(obj2) ) );
-		String name = (String)obj2;
-		int index = stackIndex(name);
-		if( index != -1 )
-			return push( new GetLocalVar(se(start),index) );
-		index = upValueIndex(name);
-		if( index != -1 )
-			return push( new GetUpVar(se(start),index) );
-		return push( new IndexExpr( se(start), env(), new ConstExpr(name) ) );
+	private interface Var {
+		public Expr expr();
+		public Settable settable();
+	}
+
+	private Var nameVar(final int start,final String name) {
+		return new Var() {
+
+			public Expr expr() {
+				int index = stackIndex(name);
+				if( index != -1 )
+					return new GetLocalVar(se(start),index);
+				index = upValueIndex(name);
+				if( index != -1 )
+					return new GetUpVar(se(start),index);
+				return new IndexExpr( se(start), env(), new ConstExpr(name) );
+			}
+
+			public Settable settable() {
+				int index = stackIndex(name);
+				if( index != -1 )
+					return new SetLocalVar(index);
+				index = upValueIndex(name);
+				if( index != -1 )
+					return new SetUpVar(index);
+				return new SetTableEntry( se(start), env(), new ConstExpr(name) );
+			}
+		};
+	}
+
+	private Var exprVar(final Expr expr) {
+		return new Var() {
+
+			public Expr expr() {
+				return expr;
+			}
+
+			public Settable settable() {
+				return null;
+			}
+		};
 	}
 
-	// function should be on top of the stack
-	Rule Args(boolean inParens,Var<Integer> start,Var<ExpList.Builder> builder) {
-		return Sequence(
-			FirstOf(
-				Sequence(
-					'(', Spaces(true), Optional(ExpList(true,builder)), ')', Spaces(inParens)
-				),
-				Sequence(
-					TableExpr(inParens),
-					addToExpList(builder.get())
-				),
-				Sequence(
-					StringLiteral(inParens),
-					push(new ConstExpr(pop())),
-					addToExpList(builder.get())
-				)
-			),
-			push( new FnCall( se(start.get()), expr(pop()), builder.get().build() ) ),
-			push(null)  // marker
-		);
+	private Var indexVar(final int start,final Expr table,final Expr key) {
+		return new Var() {
+
+			public Expr expr() {
+				return new IndexExpr( se(start), table, key );
+			}
+
+			public Settable settable() {
+				return new SetTableEntry(se(start),table,key);
+			}
+		};
 	}
 
-	@Cached
-	Rule Expressions(boolean inParens) {
-		return FirstOf(
-			ExpList(inParens),
-			push( ExpList.emptyExpList )
-		);
+	private FnCall Args(boolean inParens,int start,Expr fn,ExpList.Builder builder) throws ParseException {
+		parser.begin();
+		if( parser.match('(') ) {
+			Spaces(true);
+			ExpList(true,builder);  // optional
+			RequiredMatch(')');
+			Spaces(inParens);
+		} else {
+			Expr exp = TableExpr(inParens);
+			if( exp != null ) {
+				builder.add(exp);
+			} else {
+				String s = StringLiteral(inParens);
+				if( s != null ) {
+					builder.add( new ConstExpr(s) );
+				} else {
+					return parser.failure(null);
+				}
+			}
+		}
+		return parser.success( new FnCall( se(start), fn, builder.build() ) );
 	}
 
-	@Cached
-	Rule ExpList(boolean inParens) {
-		Var<ExpList.Builder> builder = new Var<ExpList.Builder>(new ExpList.Builder());
-		return Sequence(
-			ExpList(inParens,builder),
-			push( builder.get().build() )
-		);
+	private Expressions ExpList() throws ParseException {
+		return ExpList(false);
+	}
+
+	private Expressions ExpList(boolean inParens) throws ParseException {
+		ExpList.Builder builder = new ExpList.Builder();
+		return ExpList(inParens,builder) ? builder.build() : null;
 	}
 
-	Rule ExpList(boolean inParens,Var<ExpList.Builder> builder) {
-		return Sequence(
-			Expr(inParens),
-			addToExpList(builder.get()),
-			ZeroOrMore(
-				',', Spaces(inParens), Expr(inParens),
-				addToExpList(builder.get())
-			)
-		);
+	private boolean ExpList(boolean inParens,ExpList.Builder builder) throws ParseException {
+		parser.begin();
+		Expr exp = Expr(inParens);
+		if( exp==null )
+			return parser.failure();
+		builder.add(exp);
+		while( parser.match(',') ) {
+			Spaces(inParens);
+			builder.add( RequiredExpr(inParens) );
+		}
+		return parser.success();
 	}
 
-	boolean addToExpList(ExpList.Builder bld) {
-		Object obj = pop();
-		if( obj instanceof Expressions ) {
-			bld.add( (Expressions)obj );
-		} else {
-			bld.add( (Expr)obj );
-		}
-		return true;
+	private Expr SubExpr(boolean inParens) throws ParseException {
+		parser.begin();
+		if( !parser.match('[') )
+			return parser.failure(null);
+		Spaces(true);
+		Expr exp = RequiredExpr(true);
+		RequiredMatch(']');
+		Spaces(inParens);
+		return parser.success(exp);
 	}
 
-	boolean addToExpList(ExpList.Builder bld, Expr expr) {
-		bld.add(expr);
-		return true;
+	private Expr NameExpr(boolean inParens) throws ParseException {
+		String name = Name(inParens);
+		return name==null ? null : new ConstExpr(name);
 	}
 
-	@Cached
-	Rule SubExpr(boolean inParens) {
-		return Sequence( '[', Spaces(true), Expr(true), ']', Spaces(inParens) );
+	private String RequiredName() throws ParseException {
+		parser.begin();
+		String name = Name();
+		if( name==null )
+			parser.exception("Name expected");
+		return parser.success(name);
+	}
+
+	private String Name() throws ParseException {
+		return Name(false);
 	}
 
-	@Cached
-	Rule NameExpr(boolean inParens) {
-		return Sequence(
-			Name(inParens),
-			push( new ConstExpr((String)pop()) )
-		);
+	private String Name(boolean inParens) 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(inParens);
+		return parser.success(match);
 	}
 
-	@Cached
-	Rule Name(boolean inParens) {
-		return Sequence(
-			Sequence(
-				NameFirstChar(),
-				ZeroOrMore( NameChar() )
-			),
-			!keywords.contains(match()),
-			push(match()),
-			Spaces(inParens)
-		);
+	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");
 	}
 
-	Rule NameChar() {
-		return FirstOf( NameFirstChar(), Digit() );
+	private void RequiredMatch(String s) throws ParseException {
+		if( !parser.match(s) )
+			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);
 	}
 
-	Rule NameFirstChar() {
-		return FirstOf(
-			CharRange('a', 'z'),
-			CharRange('A', 'Z'),
-			'_'
-		);
+	private void RequiredKeyword(String keyword,boolean inParens) throws ParseException {
+		if( !Keyword(keyword,inParens) )
+			throw parser.exception("'"+keyword+"' expected");
 	}
 
-	Rule Keyword(String keyword,boolean inParens) {
-		return Sequence(
-			keyword,
-			TestNot( NameChar() ),
-			Spaces(inParens)
-		);
+	private boolean Keyword(String keyword,boolean inParens) throws ParseException {
+		parser.begin();
+		if( !parser.match(keyword) || NameChar() )
+			return parser.failure();
+		Spaces(inParens);
+		return parser.success();
 	}
 
-	static final Set<String> keywords = new HashSet<String>(Arrays.asList(
+	private static final Set<String> keywords = new HashSet<String>(Arrays.asList(
 		"and",
 		"break",
 		"catch",
@@ -965,239 +1050,241 @@
 		"while"
 	));
 
-	@Cached
-	Rule Literal(boolean inParens) {
-		return Sequence(
-			FirstOf(
-				NilLiteral(inParens),
-				BooleanLiteral(inParens),
-				NumberLiteral(inParens),
-				StringLiteral(inParens)
-			),
-			push(new ConstExpr(pop()))
-		);
+	private Expr Literal(boolean inParens) throws ParseException {
+		if( NilLiteral(inParens) )
+			return new ConstExpr(null);
+		Boolean b = BooleanLiteral(inParens);
+		if( b != null )
+			return new ConstExpr(b);
+		Number n = NumberLiteral(inParens);
+		if( n != null )
+			return new ConstExpr(n);
+		String s = StringLiteral(inParens);
+		if( s != null )
+			return new ConstExpr(s);
+		return null;
 	}
 
-	@Cached
-	Rule NilLiteral(boolean inParens) {
-		return Sequence( Keyword("nil",inParens), push(null) );
+	private boolean NilLiteral(boolean inParens) throws ParseException {
+		return Keyword("nil",inParens);
 	}
 
-	@Cached
-	Rule BooleanLiteral(boolean inParens) {
-		return FirstOf(
-			Sequence( Keyword("true",inParens), push(true) ),
-			Sequence( Keyword("false",inParens), push(false) )
-		);
+	private Boolean BooleanLiteral(boolean inParens) throws ParseException {
+		if( Keyword("true",inParens) )
+			return true;
+		if( Keyword("false",inParens) )
+			return false;
+		return null;
 	}
 
-	@Cached
-	Rule NumberLiteral(boolean inParens) {
-		return Sequence(
-			FirstOf(
-				Sequence(
-					IgnoreCase("0x"),
-					HexNumber()
-				),
-				Sequence(
-					DecNumber(),
-					push(Double.valueOf(match()))
-				)
-			),
-			TestNot( NameChar() ),
-			Spaces(inParens)
-		);
+	private Number NumberLiteral(boolean inParens) throws ParseException {
+		parser.begin();
+		Number n;
+		if( parser.matchIgnoreCase("0x") ) {
+			n = HexNumber();
+		} else {
+			n = DecNumber();
+		}
+		if( n==null || NameChar() )
+			return parser.failure(null);
+		Spaces(inParens);
+		return parser.success(n);
+	}
+
+	private Number DecNumber() {
+		int start = parser.begin();
+		if( Int() ) {
+			if( parser.match('.') )
+				Int();  // optional
+		} else if( parser.match('.') && Int() ) {
+			// ok
+		} else
+			return parser.failure(null);
+		Exponent();  // optional
+		return parser.success(Double.valueOf(parser.textFrom(start)));
 	}
 
-	Rule DecNumber() {
-		return FirstOf(
-			Sequence(
-				Int(),
-				Optional( '.', Optional(Int()) ),
-				Exponent()
-			),
-			Sequence( '.', Int(), Exponent() )
-		);
+	private boolean Exponent() {
+		parser.begin();
+		if( !parser.matchIgnoreCase("e") )
+			return parser.failure();
+		parser.anyOf("+-");  // optional
+		if( !Int() )
+			return parser.failure();
+		return parser.success();
 	}
 
-	Rule Exponent() {
-		return Optional(
-			IgnoreCase('e'),
-			Optional(AnyOf("+-")),
-			Int()
-		);
+	private boolean Int() {
+		if( !Digit() )
+			return false;
+		while( Digit() );
+		return true;
 	}
 
-	Rule Int() {
-		return OneOrMore(Digit());
-	}
-
-	Rule Digit() {
-		return CharRange('0', '9');
+	private boolean Digit() {
+		return parser.inCharRange('0', '9');
 	}
 
-	Rule HexNumber() {
-		return FirstOf(
-			Sequence(
-				HexInt(),
-				push( (double)Long.parseLong(match(),16) ),
-				Optional( '.', Optional(HexDec()) ),
-				HexExponent()
-			),
-			Sequence( push(0.0), '.', HexDec(), HexExponent() )
-		);
+	private Number HexNumber() {
+		int start = parser.begin();
+		double n;
+		if( HexInt() ) {
+			n = (double)Long.parseLong(parser.textFrom(start),16);
+			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)));
+		}
+		return parser.success(Double.valueOf(n));
 	}
 
-	Rule HexDec() {
-		return Sequence(
-			HexInt(),
-			push( (Double)pop() + (double)Long.parseLong(match(),16) / Math.pow(16,matchLength()) )
-		);
-	}
-
-	Rule HexExponent() {
-		return Optional(
-			IgnoreCase('p'),
-			Sequence(
-				Optional(AnyOf("+-")),
-				HexInt()
-			),
-			push( (Double)pop() * Math.pow(2,(double)Long.parseLong(match())) )
-		);
-	}
-
-	Rule HexInt() {
-		return OneOrMore(Digit());
+	private boolean HexInt() {
+		if( !HexDigit() )
+			return false;
+		while( HexDigit() );
+		return true;
 	}
 
 
-	Rule HexDigit() {
-		return FirstOf(
-			Digit(),
-			AnyOf("abcdefABCDEF")
-		);
+	private boolean HexDigit() {
+		return Digit() || parser.anyOf("abcdefABCDEF");
 	}
 
-	@Cached
-	Rule StringLiteral(boolean inParens) {
-		return Sequence(
-			FirstOf(
-				QuotedString('"'),
-				QuotedString('\''),
-				LongString()
-			),
-			Spaces(inParens)
-		);
+	private String StringLiteral(boolean inParens) throws ParseException {
+		String s;
+		if( (s=QuotedString('"'))==null
+			&& (s=QuotedString('\''))==null
+			&& (s=LongString())==null
+		)
+			return null;
+		Spaces(inParens);
+		return s;
 	}
 
-	Rule LongString() {
-		return Sequence(
-			'[',
-			ZeroOrMore('='),
-			nEquals(matchLength()),
-			'[',
-			ZeroOrMore(
-				TestNot(LongBracketsEnd()),
-				ANY
-			),
-			push( match() ),
-			LongBracketsEnd()
-		);
-	}
-
-	Rule QuotedString(char quote) {
-		StringBuilderVar buf = new StringBuilderVar();
-		return Sequence(
-			quote,
-			ZeroOrMore(
-				FirstOf(
-					Sequence(
-						NoneOf("\\\n"+quote),
-						buf.append(matchedChar())
-					),
-					EscSeq(buf)
-				)
-			),
-			quote,
-			push( buf.getString() )
-		);
+	private String 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);
+		start = parser.currentIndex();
+		while( !LongBracketsEnd(nEquals) ) {
+			if( !parser.anyChar() )
+				throw parser.exception("Unclosed long string");
+		}
+		String s = parser.text.substring( start, parser.currentIndex() - nEquals - 2 );
+		return parser.success(s);
 	}
 
-	Rule EscSeq(StringBuilderVar buf) {
-		return Sequence(
-			'\\',
-			FirstOf(
-				Sequence( 'a', buf.append('\u0007') ),
-				Sequence( 'b', buf.append('\b') ),
-				Sequence( 'f', buf.append('\f') ),
-				Sequence( 'n', buf.append('\n') ),
-				Sequence( 'r', buf.append('\r') ),
-				Sequence( 't', buf.append('\t') ),
-				Sequence( 'v', buf.append('\u000b') ),
-				Sequence( '\\', buf.append('\\') ),
-				Sequence( '"', buf.append('"') ),
-				Sequence( '\'', buf.append('\'') ),
-				Sequence(
-					'x',
-					Sequence( HexDigit(), HexDigit() ),
-					buf.append( (char)Integer.parseInt(match(),16) )
-				),
-				Sequence(
-					Sequence(
-						Digit(),
-						Optional(
-							Digit(),
-							Optional(
-								Digit()
-							)
-						)
-					),
-					buf.append( (char)Integer.parseInt(match()) )
-				)
-			)
-		);
+	private String 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.anyChar() )
+					throw parser.exception("Unclosed string");
+				buf.append(parser.lastChar());
+			}
+		}
+		return parser.success(buf.toString());
 	}
 
-	@Cached
-	Rule Spaces(boolean inParens) {
-		return ZeroOrMore(
-			FirstOf(
-				AnyOf(" \t"),
-				Comment(),
-				Sequence( '\\', EndOfLine() ),
-				Sequence( inParens, AnyOf("\r\n"),
-					Optional( "--", ZeroOrMore(NoneOf("\r\n")) )
-				)
-			)
-		);
+	private Character EscSeq() {
+		parser.begin();
+		if( !parser.match('\\') )
+			return parser.failure(null);
+		if( parser.match('a') )  return '\u0007';
+		if( parser.match('b') )  return '\b';
+		if( parser.match('f') )  return '\f';
+		if( parser.match('n') )  return '\n';
+		if( parser.match('r') )  return '\r';
+		if( parser.match('t') )  return '\t';
+		if( parser.match('v') )  return '\u000b';
+		if( parser.match('\\') )  return '\\';
+		if( parser.match('"') )  return '"';
+		if( parser.match('\'') )  return '\'';
+		int start = parser.currentIndex();
+		if( parser.match('x') && HexDigit() && HexDigit() )
+			return (char)Integer.parseInt(parser.textFrom(start+1),16);
+		if( Digit() ) {
+			if( Digit() ) Digit();  // optional
+			return (char)Integer.parseInt(parser.textFrom(start));
+		}
+		return parser.failure(null);
 	}
 
-	Rule Comment() {
-		return Sequence(
-			"--[",
-			ZeroOrMore('='),
-			nEquals(matchLength()),
-			'[',
-			ZeroOrMore(
-				TestNot(LongBracketsEnd()),
-				ANY
-			),
-			LongBracketsEnd()
-		);
+	private void Spaces() throws ParseException {
+		Spaces(false);
+	}
+
+	private void Spaces(boolean inParens) throws ParseException {
+		while( parser.anyOf(" \t") || Comment() || ContinueOnNextLine() || inParens && NewLine() );
 	}
 
-	Rule LongBracketsEnd() {
-		return Sequence( ']', ZeroOrMore('='), nEquals==matchLength(), ']' );
+	private boolean ContinueOnNextLine() {
+		parser.begin();
+		return parser.match('\\') &&  EndOfLine() ? parser.success() : parser.failure();
 	}
 
-	static boolean action(Object obj) {
+	private boolean NewLine() {
+		if( !EndOfLine() )
+			return false;
+		if( parser.match("--") ) {
+			while( parser.noneOf("\r\n") );
+		}
 		return true;
 	}
 
-	// for debugging
-	boolean print(Object o) {
-		System.out.println(o);
-		return true;
+	private boolean Comment() 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( !parser.anyChar() )
+				throw parser.exception("Unclosed comment");
+		}
+		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();
 	}
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/parser/ParseException.java	Mon May 12 05:57:53 2014 +0000
@@ -0,0 +1,53 @@
+package luan.parser;
+
+
+public class ParseException extends Exception {
+	public final String text;
+	public final int iCurrent;
+	public final int iHigh;
+
+	ParseException(String msg,String text,int iCurrent,int iHigh) {
+		super(msg);
+		this.text = text;
+		this.iCurrent = iCurrent;
+		this.iHigh = iHigh;
+//System.out.println("iCurrent = "+iCurrent);
+//System.out.println("iHigh = "+iHigh);
+	}
+
+	private class Location {
+		final int line;
+		final int pos;
+
+		Location(int index) {
+			int line = 0;
+			int i = -1;
+			while(true) {
+				int j = text.indexOf('\n',i+1);
+				if( j == -1 || j > index )
+					break;
+				i = j;
+				line++;
+			}
+			this.line = line;
+			this.pos = index - i - 1;
+		}
+	}
+
+	private String[] lines() {
+		return text.split("\n",-1);
+	}
+
+	public String getFancyMessage() {
+		Location loc = new Location(iCurrent);
+		String line = lines()[loc.line];
+		String msg = getMessage() +  " (line " + (loc.line+1) + ", pos " + (loc.pos+1) + ")\n";
+		StringBuilder sb = new StringBuilder(msg);
+		sb.append( line + "\n" );
+		for( int i=0; i<loc.pos; i++ ) {
+			sb.append( line.charAt(i)=='\t' ? '\t' : ' ' );
+		}
+		sb.append("^\n");
+		return sb.toString();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/parser/Parser.java	Mon May 12 05:57:53 2014 +0000
@@ -0,0 +1,148 @@
+package luan.parser;
+
+
+public final class Parser {
+	public final String text;
+	private final int len;
+	private int[] stack = new int[256];
+	private int frame = 0;
+	private int iHigh;
+
+	public Parser(String text) {
+		this.text = text;
+		this.len = text.length();
+	}
+
+	private int i() {
+		return stack[frame];
+	}
+
+	private void i(int i) {
+		stack[frame] += i;
+		if( iHigh < stack[frame] )
+			iHigh = stack[frame];
+	}
+
+	public int begin() {
+		if( frame == stack.length ) {
+			int[] a = new int[2*frame];
+			System.arraycopy(stack,0,a,0,frame);
+			stack = a;
+		}
+		frame++;
+		stack[frame] = stack[frame-1];
+		return i();
+	}
+
+	public void rollback() {
+		stack[frame] = stack[frame-1];
+	}
+
+	public <T> T success(T t) {
+		success();
+		return t;
+	}
+
+	public boolean success() {
+		frame--;
+		stack[frame] = stack[frame+1];
+		return true;
+	}
+
+	public <T> T failure(T t) {
+		failure();
+		return t;
+	}
+
+	public boolean failure() {
+		frame--;
+		return false;
+	}
+
+	public ParseException exception(String msg) {
+		return new ParseException(msg,text,i(),iHigh);
+	}
+
+	public ParseException exception() {
+		return exception("Invalid input");
+	}
+
+	public int currentIndex() {
+		return i();
+	}
+
+	public char lastChar() {
+		return text.charAt(i()-1);
+	}
+
+	public char currentChar() {
+		return text.charAt(i());
+	}
+
+	public boolean endOfInput() {
+		return i() >= len;
+	}
+
+	public boolean match(char c) {
+		if( endOfInput() || text.charAt(i()) != c )
+			return false;
+		i(1);
+		return true;
+	}
+
+	public boolean match(String s) {
+		int n = s.length();
+		if( !text.regionMatches(i(),s,0,n) )
+			return false;
+		i(n);
+		return true;
+	}
+
+	public boolean matchIgnoreCase(String s) {
+		int n = s.length();
+		if( !text.regionMatches(true,i(),s,0,n) )
+			return false;
+		i(n);
+		return true;
+	}
+
+	public boolean anyOf(String s) {
+		if( endOfInput() || s.indexOf(text.charAt(i())) == -1 )
+			return false;
+		i(1);
+		return true;
+	}
+
+	public boolean noneOf(String s) {
+		if( endOfInput() || s.indexOf(text.charAt(i())) != -1 )
+			return false;
+		i(1);
+		return true;
+	}
+
+	public boolean inCharRange(char cLow, char cHigh) {
+		if( endOfInput() )
+			return false;
+		char c = text.charAt(i());
+		if( !(cLow <= c && c <= cHigh) )
+			return false;
+		i(1);
+		return true;
+	}
+
+	public boolean anyChar() {
+		if( endOfInput() )
+			return false;
+		i(1);
+		return true;
+	}
+
+	public boolean test(String s) {
+		return text.regionMatches(i(),s,0,s.length());
+	}
+
+	public String textFrom(int start) {
+		return text.substring(start,i());
+	}
+
+}
--- a/src/luan/tools/CmdLine.java	Mon Mar 17 21:17:31 2014 +0000
+++ b/src/luan/tools/CmdLine.java	Mon May 12 05:57:53 2014 +0000
@@ -1,13 +1,13 @@
 package luan.tools;
 
-import java.util.Arrays;
-import java.util.Scanner;
+import java.io.IOException;
 import luan.lib.BasicLib;
 import luan.Luan;
 import luan.LuanState;
 import luan.LuanFunction;
 import luan.LuanTable;
 import luan.LuanException;
+import jline.ConsoleReader;
 
 
 public class CmdLine {
@@ -101,16 +101,23 @@
 	}
 
 	static void interactive(LuanState luan,LuanTable env) {
-		while( true ) {
-			System.out.print("> ");
-			String input = new Scanner(System.in).nextLine();
-			try {
-				Object[] rtn = luan.eval(input,"stdin",env);
-				if( rtn.length > 0 )
-					BasicLib.print(luan,rtn);
-			} catch(LuanException e) {
-				System.out.println(e.getMessage());
+		try {
+			ConsoleReader console = new ConsoleReader();
+			console.setDefaultPrompt("> ");
+			while( true ) {
+				String input = console.readLine();
+				if( input==null )
+					break;
+				try {
+					Object[] rtn = luan.eval(input,"stdin",env);
+					if( rtn.length > 0 )
+						BasicLib.print(luan,rtn);
+				} catch(LuanException e) {
+					System.out.println(e.getMessage());
+				}
 			}
+		} catch(IOException e) {
+			throw new RuntimeException(e);
 		}
 	}