/*******************************************************************************
 * Copyright (c) 2009 Luaj.org. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 ******************************************************************************/
package org.luaj.vm2.compiler;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Hashtable;

import org.luaj.vm2.LocVars;
import org.luaj.vm2.Lua;
import org.luaj.vm2.LuaError;
import org.luaj.vm2.LuaInteger;
import org.luaj.vm2.LuaString;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.Prototype;
import org.luaj.vm2.compiler.FuncState.BlockCnt;
import org.luaj.vm2.libs.MathLib;


public class LexState extends Constants {

	protected static final String RESERVED_LOCAL_VAR_FOR_CONTROL = "(for control)";
	protected static final String RESERVED_LOCAL_VAR_FOR_STATE = "(for state)";
	protected static final String RESERVED_LOCAL_VAR_FOR_GENERATOR = "(for generator)";
	protected static final String RESERVED_LOCAL_VAR_FOR_STEP = "(for step)";
	protected static final String RESERVED_LOCAL_VAR_FOR_LIMIT = "(for limit)";
	protected static final String RESERVED_LOCAL_VAR_FOR_INDEX = "(for index)";
	protected static final LuaString LOOP_LABEL_BREAK = LuaString.valueOf("break");
	protected static final LuaString LOOP_LABEL_CONTINUE = LuaString.valueOf("continue");

	// keywords array
	protected static final String[] RESERVED_LOCAL_VAR_KEYWORDS = new String[] {
			RESERVED_LOCAL_VAR_FOR_CONTROL,
			RESERVED_LOCAL_VAR_FOR_GENERATOR,
			RESERVED_LOCAL_VAR_FOR_INDEX,
			RESERVED_LOCAL_VAR_FOR_LIMIT,
			RESERVED_LOCAL_VAR_FOR_STATE,
			RESERVED_LOCAL_VAR_FOR_STEP
	};
	private static final Hashtable RESERVED_LOCAL_VAR_KEYWORDS_TABLE = new Hashtable();
	static {
		for ( int i=0; i<RESERVED_LOCAL_VAR_KEYWORDS.length; i++ )
			RESERVED_LOCAL_VAR_KEYWORDS_TABLE.put( RESERVED_LOCAL_VAR_KEYWORDS[i], Boolean.TRUE );
	}

	private static final int EOZ    = (-1);
	private static final int MAX_INT = Integer.MAX_VALUE-2;
	private static final int UCHAR_MAX = 255; // TODO, convert to unicode CHAR_MAX?
	private static final int LUAI_MAXCCALLS = 200;

	private static final String LUA_QS(String s) { return "'"+s+"'"; }
	private static final String LUA_QL(Object o) { return LUA_QS(String.valueOf(o)); }

	private static final int     LUA_COMPAT_LSTR   =    1; // 1 for compatibility, 2 for old behavior
	private static final boolean LUA_COMPAT_VARARG = true;

	public static boolean isReservedKeyword(String varName) {
		return RESERVED_LOCAL_VAR_KEYWORDS_TABLE.containsKey(varName)
				|| RESERVED.containsKey(LuaString.valueOf(varName));
	}

	/*
	 ** Marks the end of a patch list. It is an invalid value both as an absolute
	 ** address, and as a list link (would link an element to itself).
	 */
	static final int NO_JUMP = (-1);

	/*
	 ** grep "ORDER OPR" if you change these enums
	 */
	static final int
			OPR_ADD=0, OPR_SUB=1, OPR_MUL=2, OPR_DIV=3, OPR_IDIV=4, OPR_BAND=5, OPR_BOR=6,
			OPR_BXOR=7, OPR_SHL=8, OPR_SHR=9, OPR_MOD=10, OPR_POW=11,
			OPR_CONCAT=12,
			OPR_NE=13, OPR_EQ=14,
			OPR_LT=15, OPR_LE=16, OPR_GT=17, OPR_GE=18,
			OPR_AND=19, OPR_OR=20,
			OPR_NOBINOPR=21;

	static final int
			OPR_MINUS=0, OPR_NOT=1, OPR_LEN=2, OPR_BNOT=3, OPR_NOUNOPR=4;

	/* exp kind */
	static final int
			VVOID = 0,	/* no value */
			VNIL = 1,
			VTRUE = 2,
			VFALSE = 3,
			VK = 4,		/* info = index of constant in `k' */
			VKNUM = 5,	/* nval = numerical value */
			VNONRELOC = 6,	/* info = result register */
			VLOCAL = 7,	/* info = local register */
			VUPVAL = 8,       /* info = index of upvalue in `upvalues' */
			VINDEXED = 9,	/* info = table register, aux = index register (or `k') */
			VJMP = 10,		/* info = instruction pc */
			VRELOCABLE = 11,	/* info = instruction pc */
			VCALL = 12,	/* info = instruction pc */
			VVARARG = 13;	/* info = instruction pc */

	/* semantics information */
	private static class SemInfo {
		LuaValue r;
		LuaString ts;
	};

	private static class Token {
		int token;
		final SemInfo seminfo = new SemInfo();
		public void set(Token other) {
			this.token = other.token;
			this.seminfo.r = other.seminfo.r;
			this.seminfo.ts = other.seminfo.ts;
		}
	};

	int current;  /* current character (charint) */
	int linenumber;  /* input line counter */
	int lastline;  /* line of last token `consumed' */
	final Token t = new Token();  /* current token */
	final Token lookahead = new Token();  /* look ahead token */
	FuncState fs;  /* `FuncState' is private to the parser */
	LuaC.CompileState L;
	InputStream z;  /* input stream */
	char[] buff;  /* buffer for tokens */
	int nbuff; /* length of buffer */
	Dyndata dyd = new Dyndata();  /* dynamic structures used by the parser */
	LuaString source;  /* current source name */
	LuaString envn;  /* environment variable name */
	byte decpoint;  /* locale decimal point */
	FuncState.BlockCnt declbl;  /* lexical declaration scope chain */

	/* ORDER RESERVED */
	final static String luaX_tokens [] = {
			"and", "break", "continue", "do", "else", "elseif",
			"end", "false", "for", "function", "global", "goto", "if",
			"in", "local", "nil", "not", "or", "repeat",
			"return", "then", "true", "until", "while",
			"..", "...", "//", "<<", ">>", "==", ">=", "<=", "~=",
			"::", "<eos>", "<number>", "<name>", "<string>", "<eof>",
	};

	final static int
			/* terminal symbols denoted by reserved words */
			TK_AND=257,  TK_BREAK=258, TK_CONTINUE=259, TK_DO=260, TK_ELSE=261, TK_ELSEIF=262,
			TK_END=263, TK_FALSE=264, TK_FOR=265, TK_FUNCTION=266, TK_GLOBAL=267, TK_GOTO=268, TK_IF=269,
			TK_IN=270, TK_LOCAL=271, TK_NIL=272, TK_NOT=273, TK_OR=274, TK_REPEAT=275,
			TK_RETURN=276, TK_THEN=277, TK_TRUE=278, TK_UNTIL=279, TK_WHILE=280,
	/* other terminal symbols */
	TK_CONCAT=281, TK_DOTS=282, TK_IDIV=283, TK_SHL=284, TK_SHR=285, TK_EQ=286, TK_GE=287, TK_LE=288, TK_NE=289,
			TK_DBCOLON=290, TK_EOS=291, TK_NUMBER=292, TK_NAME=293, TK_STRING=294;

	final static int FIRST_RESERVED = TK_AND;
	final static int NUM_RESERVED = TK_WHILE+1-FIRST_RESERVED;

	final static Hashtable RESERVED = new Hashtable();
	static {
		for ( int i=0; i<NUM_RESERVED; i++ ) {
			LuaString ts = (LuaString) LuaValue.valueOf( luaX_tokens[i] );
			RESERVED.put(ts, FIRST_RESERVED+i);
		}
	}

	private boolean isalnum(int c) {
		return (c >= '0' && c <= '9')
				|| (c >= 'a' && c <= 'z')
				|| (c >= 'A' && c <= 'Z')
				|| (c == '_');
		// return Character.isLetterOrDigit(c);
	}

	private boolean isalpha(int c) {
		return (c >= 'a' && c <= 'z')
				|| (c >= 'A' && c <= 'Z');
	}

	private boolean isdigit(int c) {
		return (c >= '0' && c <= '9');
	}

	private boolean isxdigit(int c) {
		return (c >= '0' && c <= '9')
				|| (c >= 'a' && c <= 'f')
				|| (c >= 'A' && c <= 'F');
	}

	private boolean isspace(int c) {
		return (c >= 0 && c <= ' ');
	}


	public LexState(LuaC.CompileState state, InputStream stream) {
		this.z = stream;
		this.buff = new char[32];
		this.L = state;
	}

	void nextChar() {
		try {
			current = z.read();
		} catch ( IOException e ) {
			e.printStackTrace();
			current = EOZ;
		}
	}

	boolean currIsNewline() {
		return current == '\n' || current == '\r';
	}

	void save_and_next() {
		save( current );
		nextChar();
	}

	void save(int c) {
		if ( buff == null || nbuff + 1 > buff.length )
			buff = realloc( buff, nbuff*2+1 );
		buff[nbuff++] = (char) c;
	}


	String token2str( int token ) {
		if ( token < FIRST_RESERVED ) {
			return iscntrl(token)?
					L.pushfstring( "char("+((int)token)+")" ):
					L.pushfstring( String.valueOf( (char) token ) );
		} else {
			if (token == TK_EOS)
				return "<eof>";
			return luaX_tokens[token-FIRST_RESERVED];
		}
	}

	private static boolean iscntrl(int token) {
		return token < ' ';
	}

	String txtToken(int token) {
		switch ( token ) {
			case TK_NAME:
			case TK_STRING:
			case TK_NUMBER:
				return new String( buff, 0, nbuff );
			default:
				return token2str( token );
		}
	}

	void lexerror( String msg, int token ) {
		String cid = Lua.chunkid( source.tojstring() );
		boolean hasNear = msg.indexOf(" near ") >= 0;
		String full = token != 0 && !hasNear ? msg+" near "+txtToken(token) : msg;
		L.pushfstring( cid+":"+linenumber+": "+msg );
		if ( token != 0 )
			L.pushfstring( "syntax error: "+full );
		throw new LuaError(cid+":"+linenumber+": "+full);
	}

	void syntaxerror( String msg ) {
		lexerror( msg, t.token );
	}

	// only called by new_localvarliteral() for var names.
	LuaString newstring( String s ) {
		return L.newTString(s);
	}

	LuaString newstring( char[] chars, int offset, int len ) {
		return L.newTString(new String(chars, offset, len));
	}

	void inclinenumber() {
		int old = current;
		_assert( currIsNewline() );
		nextChar(); /* skip '\n' or '\r' */
		if ( currIsNewline() && current != old )
			nextChar(); /* skip '\n\r' or '\r\n' */
		if ( ++linenumber >= MAX_INT )
			syntaxerror("chunk has too many lines");
	}

	void setinput(LuaC.CompileState L, int firstByte, InputStream z, LuaString source) {
		this.decpoint = '.';
		this.L = L;
		this.lookahead.token = TK_EOS; /* no look-ahead token */
		this.z = z;
		this.fs = null;
		this.linenumber = 1;
		this.lastline = 1;
		this.source = source;
		this.envn = LuaValue.ENV;  /* environment variable name */
		this.nbuff = 0;   /* initialize buffer */
		this.current = firstByte; /* read first char */
		this.skipShebang();
	}

	private void skipShebang() {
		if ( current == '#' )
			while (!currIsNewline() && current != EOZ)
				nextChar();
	}



	/*
	 ** =======================================================
	 ** LEXICAL ANALYZER
	 ** =======================================================
	 */


	boolean check_next(String set) {
		if (set.indexOf(current) < 0)
			return false;
		save_and_next();
		return true;
	}

	void buffreplace(char from, char to) {
		int n = nbuff;
		char[] p = buff;
		while ((--n) >= 0)
			if (p[n] == from)
				p[n] = to;
	}

	LuaValue strx2number(String str, SemInfo seminfo) {
		String trimmed = str.trim();
		try {
			if (trimmed.indexOf('.') < 0 && trimmed.indexOf('p') < 0 && trimmed.indexOf('P') < 0) {
				boolean negative = trimmed.startsWith("-");
				boolean positive = trimmed.startsWith("+");
				int prefix = negative || positive ? 3 : 2;
				String digits = trimmed.substring(prefix);
				long value = Long.parseUnsignedLong(digits, 16);
				if (negative) {
					if (value == Long.MIN_VALUE) {
						return LuaValue.valueOf(Long.MIN_VALUE);
					}
					return LuaValue.valueOf(-value);
				}
				return LuaValue.valueOf(value);
			}
			return LuaValue.valueOf(Double.parseDouble(trimmed));
		} catch (NumberFormatException e) {
			lexerror("malformed number (" + e.getMessage() + ")", TK_NUMBER);
			return LuaValue.NIL;
		}
	}

	boolean str2d(String str, SemInfo seminfo) {
		if (str.indexOf('n')>=0 || str.indexOf('N')>=0)
			seminfo.r = LuaValue.ZERO;
		else if (str.indexOf('x')>=0 || str.indexOf('X')>=0)
			seminfo.r = strx2number(str, seminfo);
		else {
			try {
				String trimmed = str.trim();
				if (trimmed.indexOf('.') < 0 && trimmed.indexOf('e') < 0 && trimmed.indexOf('E') < 0) {
					try {
						seminfo.r = LuaValue.valueOf(Long.parseLong(trimmed));
					} catch (NumberFormatException overflow) {
						seminfo.r = LuaValue.valueOf(Double.parseDouble(trimmed));
					}
				} else {
					seminfo.r = LuaValue.valueOf(Double.parseDouble(trimmed));
				}
			} catch (NumberFormatException e) {
				lexerror("malformed number (" + e.getMessage() + ")", TK_NUMBER);
			}
		}
		return true;
	}

	void read_numeral(SemInfo seminfo) {
		String expo = "Ee";
		int first = current;
		_assert (isdigit(current));
		save_and_next();
		if (first == '0' && check_next("Xx"))
			expo = "Pp";
		while (true) {
			if (check_next(expo))
				check_next("+-");
			if(isxdigit(current) || current == '.')
				save_and_next();
			else
				break;
		}
		String str = new String(buff, 0, nbuff);
		str2d(str, seminfo);
	}

	int skip_sep() {
		int count = 0;
		int s = current;
		_assert (s == '[' || s == ']');
		save_and_next();
		while (current == '=') {
			save_and_next();
			count++;
		}
		return (current == s) ? count : (-count) - 1;
	}

	void read_long_string(SemInfo seminfo, int sep) {
		int cont = 0;
		save_and_next(); /* skip 2nd `[' */
		if (currIsNewline()) /* string starts with a newline? */
			inclinenumber(); /* skip it */
		for (boolean endloop = false; !endloop;) {
			switch (current) {
				case EOZ:
					lexerror((seminfo != null) ? "unfinished long string"
							: "unfinished long comment", TK_EOS);
					break; /* to avoid warnings */
				case '[': {
					if (skip_sep() == sep) {
						save_and_next(); /* skip 2nd `[' */
						cont++;
						if (LUA_COMPAT_LSTR == 1) {
							if (sep == 0)
								lexerror("nesting of [[...]] is deprecated", '[');
						}
					}
					break;
				}
				case ']': {
					if (skip_sep() == sep) {
						save_and_next(); /* skip 2nd `]' */
						if (LUA_COMPAT_LSTR == 2) {
							cont--;
							if (sep == 0 && cont >= 0)
								break;
						}
						endloop = true;
					}
					break;
				}
				case '\n':
				case '\r': {
					save('\n');
					inclinenumber();
					if (seminfo == null)
						nbuff = 0; /* avoid wasting space */
					break;
				}
				default: {
					if (seminfo != null)
						save_and_next();
					else
						nextChar();
				}
			}
		}
		if (seminfo != null)
			seminfo.ts =  L.newTString(LuaString.valueOf(buff, 2 + sep, nbuff - 2 * (2 + sep)));
	}

	int hexvalue(int c) {
		return c <= '9'? c - '0': c <= 'F'? c + 10 - 'A': c + 10 - 'a';
	}

	int readhexaesc() {
		nextChar();
		int c1 = current;
		nextChar();
		int c2 = current;
		if (!isxdigit(c1) || !isxdigit(c2))
			lexerror("hexadecimal digit expected 'x"+((char)c1)+((char)c2), TK_STRING);
		return (hexvalue(c1) << 4) + hexvalue(c2);
	}

	int readutf8esc() {
		nextChar();
		if (current != '{')
			lexerror("missing '{' after '\\u'", TK_STRING);
		nextChar();
		long codepoint = 0;
		int digits = 0;
		while (current != '}') {
			if (!isxdigit(current))
				lexerror("hexadecimal digit expected", TK_STRING);
			codepoint = (codepoint << 4) + hexvalue(current);
			if (codepoint > 0x10FFFFL)
				lexerror("UTF-8 value too large", TK_STRING);
			digits++;
			nextChar();
		}
		if (digits == 0)
			lexerror("missing hexadecimal digits after '\\u{'", TK_STRING);
		if (codepoint >= 0xD800L && codepoint <= 0xDFFFL)
			lexerror("invalid Unicode code point", TK_STRING);
		return (int) codepoint;
	}

	void saveutf8(int codepoint) {
		if (codepoint < 0x80) {
			save(codepoint);
		} else if (codepoint < 0x800) {
			save(0xC0 | (codepoint >> 6));
			save(0x80 | (codepoint & 0x3F));
		} else if (codepoint < 0x10000) {
			save(0xE0 | (codepoint >> 12));
			save(0x80 | ((codepoint >> 6) & 0x3F));
			save(0x80 | (codepoint & 0x3F));
		} else {
			save(0xF0 | (codepoint >> 18));
			save(0x80 | ((codepoint >> 12) & 0x3F));
			save(0x80 | ((codepoint >> 6) & 0x3F));
			save(0x80 | (codepoint & 0x3F));
		}
	}

	void read_string(int del, SemInfo seminfo) {
		save_and_next();
		while (current != del) {
			switch (current) {
				case EOZ:
					lexerror("unfinished string", TK_EOS);
					continue; /* to avoid warnings */
				case '\n':
				case '\r':
					lexerror("unfinished string", TK_STRING);
					continue; /* to avoid warnings */
				case '\\': {
					int c;
					nextChar(); /* do not save the `\' */
					switch (current) {
						case 'a': /* bell */
							c = '\u0007';
							break;
						case 'b': /* backspace */
							c = '\b';
							break;
						case 'f': /* form feed */
							c = '\f';
							break;
						case 'n': /* newline */
							c = '\n';
							break;
						case 'r': /* carriage return */
							c = '\r';
							break;
						case 't': /* tab */
							c = '\t';
							break;
						case 'v': /* vertical tab */
							c = '\u000B';
							break;
						case 'x':
							c = readhexaesc();
							break;
						case 'u':
							c = readutf8esc();
							saveutf8(c);
							nextChar();
							continue;
						case '\n': /* go through */
						case '\r':
							save('\n');
							inclinenumber();
							continue;
						case EOZ:
							continue; /* will raise an error next loop */
						case 'z': {  /* zap following span of spaces */
							nextChar();  /* skip the 'z' */
							while (isspace(current)) {
								if (currIsNewline()) inclinenumber();
								else nextChar();
							}
							continue;
						}
						default: {
							if (!isdigit(current)) {
								if (current == '\\' || current == '"' || current == '\'')
									save_and_next();
								else
									lexerror("invalid escape sequence", TK_STRING);
							} else { /* \xxx */
								int i = 0;
								c = 0;
								do {
									c = 10 * c + (current - '0');
									nextChar();
								} while (++i < 3 && isdigit(current));
								if (c > UCHAR_MAX)
									lexerror("escape sequence too large", TK_STRING);
								save(c);
							}
							continue;
						}
					}
					save(c);
					nextChar();
					continue;
				}
				default:
					save_and_next();
			}
		}
		save_and_next(); /* skip delimiter */
		seminfo.ts = L.newTString(LuaString.valueOf(buff, 1, nbuff-2));
	}

	int llex(SemInfo seminfo) {
		nbuff = 0;
		while (true) {
			switch (current) {
				case '\n':
				case '\r': {
					inclinenumber();
					continue;
				}
				case ' ':
				case '\f':
				case '\t':
				case 0x0B: /* \v */ {
					nextChar();
					continue;
				}
				case '-': {
					nextChar();
					if (current != '-')
						return '-';
					/* else is a comment */
					nextChar();
					if (current == '[') {
						int sep = skip_sep();
						nbuff = 0; /* `skip_sep' may dirty the buffer */
						if (sep >= 0) {
							read_long_string(null, sep); /* long comment */
							nbuff = 0;
							continue;
						}
					}
					/* else short comment */
					while (!currIsNewline() && current != EOZ)
						nextChar();
					continue;
				}
				case '[': {
					int sep = skip_sep();
					if (sep >= 0) {
						read_long_string(seminfo, sep);
						return TK_STRING;
					} else if (sep == -1)
						return '[';
					else
						lexerror("invalid long string delimiter", TK_STRING);
				}
				case '=': {
					nextChar();
					if (current != '=')
						return '=';
					else {
						nextChar();
						return TK_EQ;
					}
				}
				case '<': {
					nextChar();
					if (current == '<') {
						nextChar();
						return TK_SHL;
					} else if (current != '=')
						return '<';
					else {
						nextChar();
						return TK_LE;
					}
				}
				case '>': {
					nextChar();
					if (current == '>') {
						nextChar();
						return TK_SHR;
					} else if (current != '=')
						return '>';
					else {
						nextChar();
						return TK_GE;
					}
				}
				case '~': {
					nextChar();
					if (current != '=')
						return '~';
					else {
						nextChar();
						return TK_NE;
					}
				}
				case ':': {
					nextChar();
					if (current != ':')
						return ':';
					else {
						nextChar();
						return TK_DBCOLON;
					}
				}
				case '/': {
					nextChar();
					if (current != '/')
						return '/';
					else {
						nextChar();
						return TK_IDIV;
					}
				}
				case '"':
				case '\'': {
					read_string(current, seminfo);
					return TK_STRING;
				}
				case '.': {
					save_and_next();
					if (check_next(".")) {
						if (check_next("."))
							return TK_DOTS; /* ... */
						else
							return TK_CONCAT; /* .. */
					} else if (!isdigit(current))
						return '.';
					else {
						read_numeral(seminfo);
						return TK_NUMBER;
					}
				}
				case '0': case '1': case '2': case '3': case '4':
				case '5': case '6': case '7': case '8': case '9': {
					read_numeral(seminfo);
					return TK_NUMBER;
				}
				case EOZ: {
					return TK_EOS;
				}
				default: {
					if (isalpha(current) || current == '_') {
						/* identifier or reserved word */
						LuaString ts;
						do {
							save_and_next();
						} while (isalnum(current));
						ts = newstring(buff, 0, nbuff);
						if ( RESERVED.containsKey(ts) )
							return ((Integer)RESERVED.get(ts)).intValue();
						else {
							seminfo.ts = ts;
							return TK_NAME;
						}
					} else {
						int c = current;
						nextChar();
						return c; /* single-char tokens (+ - / ...) */
					}
				}
			}
		}
	}

	void next() {
		lastline = linenumber;
		if (lookahead.token != TK_EOS) { /* is there a look-ahead token? */
			t.set( lookahead ); /* use this one */
			lookahead.token = TK_EOS; /* and discharge it */
		} else
			t.token = llex(t.seminfo); /* read next token */
	}

	void lookahead() {
		_assert (lookahead.token == TK_EOS);
		lookahead.token = llex(lookahead.seminfo);
	}

	// =============================================================
	// from lcode.h
	// =============================================================


	// =============================================================
	// from lparser.c
	// =============================================================

	static final boolean vkisvar(final int k) {
		return (VLOCAL <= (k) && (k) <= VINDEXED);
	}

	static final boolean vkisinreg(final int k) {
		return ((k) == VNONRELOC || (k) == VLOCAL);
	}

	static class expdesc {
		int k; // expkind, from enumerated list, above
		boolean isconst;
		static class U { // originally a union
			short ind_idx; // index (R/K)
			short ind_t; // table(register or upvalue)
			short ind_vt; // whether 't' is register (VLOCAL) or (UPVALUE)
			private LuaValue _nval;
			int info;
			public void setNval(LuaValue r) {
				_nval = r;
			}
			public LuaValue nval() {
				return (_nval == null? LuaInteger.valueOf(info): _nval);
			}
		};
		final U u = new U();
		final IntPtr t = new IntPtr(); /* patch list of `exit when true' */
		final IntPtr f = new IntPtr(); /* patch list of `exit when false' */
		void init( int k, int i ) {
			this.f.i = NO_JUMP;
			this.t.i = NO_JUMP;
			this.k = k;
			this.isconst = false;
			this.u.info = i;
		}

		boolean hasjumps() {
			return (t.i != f.i);
		}

		boolean isnumeral() {
			return (k == VKNUM && t.i == NO_JUMP && f.i == NO_JUMP);
		}

		public void setvalue(expdesc other) {
			this.f.i = other.f.i;
			this.k = other.k;
			this.isconst = other.isconst;
			this.t.i = other.t.i;
			this.u._nval = other.u._nval;
			this.u.ind_idx = other.u.ind_idx;
			this.u.ind_t = other.u.ind_t;
			this.u.ind_vt = other.u.ind_vt;
			this.u.info = other.u.info;
		}
	}


	/* description of active local variable */
	static class Vardesc {
		final short idx;  /* variable index in stack */
		final boolean isconst;
		Vardesc(int idx, boolean isconst) {
			this.idx = (short) idx;
			this.isconst = isconst;
		}
	};

	static final class GlobalDecl {
		final LuaString name;
		final boolean isconst;
		final boolean wildcard;

		GlobalDecl(LuaString name, boolean isconst, boolean wildcard) {
			this.name = name;
			this.isconst = isconst;
			this.wildcard = wildcard;
		}
	};


	/* description of pending goto statements and label statements */
	static class Labeldesc {
		LuaString name;  /* label identifier */
		int pc;  /* position in code */
		int line;  /* line where it appeared */
		short nactvar;  /* local level where it appears in current block */
		public Labeldesc(LuaString name, int pc, int line, short nactvar) {
			this.name = name;
			this.pc = pc;
			this.line = line;
			this.nactvar = nactvar;
		}
	};


	/* dynamic structures used by the parser */
	static class Dyndata {
		Vardesc[] actvar;  /* list of active local variables */
		int n_actvar = 0;
		Labeldesc[] gt;  /* list of pending gotos */
		int n_gt = 0;
		Labeldesc[] label;   /* list of active labels */
		int n_label = 0;
	};


	boolean hasmultret(int k) {
		return ((k) == VCALL || (k) == VVARARG);
	}

	/*----------------------------------------------------------------------
	name		args	description
	------------------------------------------------------------------------*/

	void anchor_token () {
		/* last token from outer function must be EOS */
		_assert(fs != null || t.token == TK_EOS);
		if (t.token == TK_NAME || t.token == TK_STRING) {
			LuaString ts = t.seminfo.ts;
			// TODO: is this necessary?
			L.cachedLuaString(t.seminfo.ts);
		}
	}

	/* semantic error */
	void semerror (String msg) {
		t.token = 0;  /* remove 'near to' from final message */
		syntaxerror(msg);
	}

	void error_expected(int token) {
		syntaxerror(L.pushfstring(LUA_QS(token2str(token)) + " expected"));
	}

	boolean testnext(int c) {
		if (t.token == c) {
			next();
			return true;
		} else
			return false;
	}

	void check(int c) {
		if (t.token != c)
			error_expected(c);
	}

	void checknext (int c) {
		check(c);
		next();
	}

	void check_condition(boolean c, String msg) {
		if (!(c))
			syntaxerror(msg);
	}


	void check_match(int what, int who, int where) {
		if (!testnext(what)) {
			if (where == linenumber)
				error_expected(what);
			else {
				syntaxerror(L.pushfstring(LUA_QS(token2str(what))
						+ " expected " + "(to close " + LUA_QS(token2str(who))
						+ " at line " + where + ")"));
			}
		}
	}

	LuaString str_checkname() {
		LuaString ts;
		check(TK_NAME);
		ts = t.seminfo.ts;
		next();
		return ts;
	}

	void codestring(expdesc e, LuaString s) {
		e.init(VK, fs.stringK(s));
	}

	void checkname(expdesc e) {
		codestring(e, str_checkname());
	}


	int registerlocalvar(LuaString varname, boolean toclose, boolean isconst) {
		FuncState fs = this.fs;
		Prototype f = fs.f;
		if (f.locvars == null || fs.nlocvars + 1 > f.locvars.length)
			f.locvars = realloc( f.locvars, fs.nlocvars*2+1 );
		f.locvars[fs.nlocvars] = new LocVars(varname,0,0,-1,toclose,isconst);
		return fs.nlocvars++;
	}

	void new_localvar(LuaString name) {
		new_localvar(name, false, false);
	}

	void new_localvar(LuaString name, boolean toclose) {
		new_localvar(name, toclose, false);
	}

	void new_localvar(LuaString name, boolean toclose, boolean isconst) {
		int reg = registerlocalvar(name, toclose, isconst);
		fs.checklimit(dyd.n_actvar + 1, FuncState.LUAI_MAXVARS, "local variables");
		if (dyd.actvar == null || dyd.n_actvar + 1 > dyd.actvar.length)
			dyd.actvar = realloc(dyd.actvar, Math.max(1, dyd.n_actvar * 2));
		dyd.actvar[dyd.n_actvar++] = new Vardesc(reg, isconst);
	}

	void new_localvarliteral(String v) {
		LuaString ts = newstring(v);
		new_localvar(ts);
	}

	void adjustlocalvars(int nvars) {
		FuncState fs = this.fs;
		fs.nactvar = (short) (fs.nactvar + nvars);
		for (; nvars > 0; nvars--) {
			LocVars local = fs.getlocvar(fs.nactvar - nvars);
			local.startpc = fs.pc;
			local.slot = fs.nactvar - nvars;
			if (local.toclose)
				fs.marktoclose(local.slot);
		}
	}

	void removevars(int tolevel) {
		FuncState fs = this.fs;
		while (fs.nactvar > tolevel)
			fs.getlocvar(--fs.nactvar).endpc = fs.pc;
	}

	void singlevar(expdesc var) {
		LuaString varname = this.str_checkname();
		FuncState fs = this.fs;
		if (FuncState.singlevaraux(fs, varname, var, 1) == VVOID) { /* global name? */
			if (!resolveglobal(varname, var))
				this.semerror("variable '" + varname.tojstring() + "' is not declared");
		}
	}

	private void makeglobalvar(LuaString varname, expdesc var) {
		expdesc key = new expdesc();
		FuncState.singlevaraux(this.fs, this.envn, var, 1);
		_assert(var.k == VLOCAL || var.k == VUPVAL);
		this.codestring(key, varname);
		this.fs.indexed(var, key);
	}

	private boolean resolveglobal(LuaString varname, expdesc var) {
		boolean explicitSeen = false;
		for (FuncState.BlockCnt block = declbl; block != null; block = block.declPrevious) {
			if (block.globaldecls == null)
				continue;
			explicitSeen = true;
			for (int i = block.globaldecls.size() - 1; i >= 0; i--) {
				GlobalDecl decl = (GlobalDecl) block.globaldecls.get(i);
				if (decl.wildcard || decl.name.eq_b(varname)) {
					makeglobalvar(varname, var);
					var.isconst = decl.isconst;
					return true;
				}
			}
		}
		if (!explicitSeen) {
			makeglobalvar(varname, var);
			var.isconst = false;
			return true;
		}
		return false;
	}

	private void registerglobaldecl(LuaString name, boolean isconst, boolean wildcard) {
		if (declbl.globaldecls == null)
			declbl.globaldecls = new ArrayList();
		declbl.globaldecls.add(new GlobalDecl(name, isconst, wildcard));
	}

	void adjust_assign(int nvars, int nexps, expdesc e) {
		FuncState fs = this.fs;
		int extra = nvars - nexps;
		if (hasmultret(e.k)) {
			/* includes call itself */
			extra++;
			if (extra < 0)
				extra = 0;
			/* last exp. provides the difference */
			fs.setreturns(e, extra);
			if (extra > 1)
				fs.reserveregs(extra - 1);
		} else {
			/* close last expression */
			if (e.k != VVOID)
				fs.exp2nextreg(e);
			if (extra > 0) {
				int reg = fs.freereg;
				fs.reserveregs(extra);
				fs.nil(reg, extra);
			}
		}
	}

	void enterlevel() {
		if (++L.nCcalls > LUAI_MAXCCALLS)
			lexerror("chunk has too many syntax levels", 0);
	}

	void leavelevel() {
		L.nCcalls--;
	}

	void closegoto(int g, Labeldesc label) {
		FuncState fs = this.fs;
		Labeldesc[] gl = this.dyd.gt;
		Labeldesc gt = gl[g];
		_assert(gt.name.eq_b(label.name));
		if (gt.nactvar < label.nactvar) {
			LuaString vname = fs.getlocvar(gt.nactvar).varname;
			String msg = L.pushfstring("<goto " + gt.name + "> at line "
					+ gt.line + " jumps into the scope of local '"
					+ vname.tojstring() + "'");
			semerror(msg);
		}
		fs.patchlist(gt.pc, label.pc);
		/* remove goto from pending list */
		System.arraycopy(gl, g + 1, gl, g, this.dyd.n_gt - g - 1);
		gl[--this.dyd.n_gt] = null;
	}

	/*
	 ** try to close a goto with existing labels; this solves backward jumps
	 */
	boolean findlabel (int g) {
		int i;
		BlockCnt bl = fs.bl;
		Dyndata dyd = this.dyd;
		Labeldesc gt = dyd.gt[g];
		/* check labels in current block for a match */
		for (i = bl.firstlabel; i < dyd.n_label; i++) {
			Labeldesc lb = dyd.label[i];
			if (lb.name.eq_b(gt.name)) {  /* correct label? */
				if (gt.nactvar > lb.nactvar &&
						(bl.upval || dyd.n_label > bl.firstlabel))
					fs.patchclose(gt.pc, lb.nactvar);
				closegoto(g, lb);  /* close it */
				return true;
			}
		}
		return false;  /* label not found; cannot close goto */
	}

	/* Caller must grow() the vector before calling this. */
	int newlabelentry(Labeldesc[] l, int index, LuaString name, int line, int pc) {
		l[index] = new Labeldesc(name, pc, line, fs.nactvar);
		return index;
	}

	/*
	 ** check whether new label 'lb' matches any pending gotos in current
	 ** block; solves forward jumps
	 */
	void findgotos (Labeldesc lb) {
		Labeldesc[] gl = dyd.gt;
		int i = fs.bl.firstgoto;
		while (i < dyd.n_gt) {
			if (gl[i].name.eq_b(lb.name))
				closegoto(i, lb);
			else
				i++;
		}
	}


	/*
	 ** create a label named "break" to resolve break statements
	 */
	void breaklabel () {
		LuaString n = LuaString.valueOf("break");
		int l = newlabelentry(dyd.label=grow(dyd.label, dyd.n_label+1), dyd.n_label++, n, 0, fs.pc);
		findgotos(dyd.label[l]);
	}

	/*
	 ** generates an error for an undefined 'goto'; choose appropriate
	 ** message when label name is a reserved word (which can only be 'break')
	 */
	void undefgoto (Labeldesc gt) {
		String msg = L.pushfstring(isReservedKeyword(gt.name.tojstring())
				? "<"+gt.name+"> at line "+gt.line+" not inside a loop"
				: "no visible label '"+gt.name+"' for <goto> at line "+gt.line);
		semerror(msg);
	}

	Prototype addprototype () {
		Prototype clp;
		Prototype f = fs.f;  /* prototype of current function */
		if (f.p == null || fs.np >= f.p.length) {
			f.p = realloc(f.p, Math.max(1, fs.np * 2));
		}
		f.p[fs.np++] = clp = new Prototype();
		return clp;
	}

	void codeclosure (expdesc v) {
		FuncState fs = this.fs.prev;
		v.init(VRELOCABLE, fs.codeABx(OP_CLOSURE, 0, fs.np - 1));
		fs.exp2nextreg(v);  /* fix it at stack top (for GC) */
	}

	void open_func (FuncState fs, BlockCnt bl) {
		fs.prev = this.fs;  /* linked list of funcstates */
		fs.ls = this;
		this.fs = fs;
		fs.pc = 0;
		fs.lasttarget = -1;
		fs.jpc = new IntPtr( NO_JUMP );
		fs.freereg = 0;
		fs.nk = 0;
		fs.np = 0;
		fs.nups = 0;
		fs.nlocvars = 0;
		fs.nactvar = 0;
		fs.firstlocal = dyd.n_actvar;
		fs.bl = null;
		fs.f.source = this.source;
		fs.f.maxstacksize = 2;  /* registers 0/1 are always valid */
		fs.enterblock(bl,  false);
	}

	void close_func() {
		FuncState fs = this.fs;
		Prototype f = fs.f;
		fs.ret(0, 0); /* final return */
		fs.leaveblock();
		f.code = realloc(f.code, fs.pc);
		f.lineinfo = realloc(f.lineinfo, fs.pc);
		f.k = realloc(f.k, fs.nk);
		f.p = realloc(f.p, fs.np);
		f.locvars = realloc(f.locvars, fs.nlocvars);
		f.upvalues = realloc(f.upvalues, fs.nups);
		_assert (fs.bl == null);
		this.fs = fs.prev;
		// last token read was anchored in defunct function; must reanchor it
		// ls.anchor_token();
	}

	/*============================================================*/
	/* GRAMMAR RULES */
	/*============================================================*/

	void fieldsel(expdesc v) {
		/* fieldsel -> ['.' | ':'] NAME */
		FuncState fs = this.fs;
		expdesc key = new expdesc();
		fs.exp2anyregup(v);
		this.next(); /* skip the dot or colon */
		this.checkname(key);
		fs.indexed(v, key);
	}

	void yindex(expdesc v) {
		/* index -> '[' expr ']' */
		this.next(); /* skip the '[' */
		this.expr(v);
		this.fs.exp2val(v);
		this.checknext(']');
	}


	/*
	 ** {======================================================================
	 ** Rules for Constructors
	 ** =======================================================================
	 */


	static class ConsControl {
		expdesc v = new expdesc(); /* last list item read */
		expdesc t; /* table descriptor */
		int nh; /* total number of `record' elements */
		int na; /* total number of array elements */
		int tostore; /* number of array elements pending to be stored */
	};


	void recfield(ConsControl cc) {
		/* recfield -> (NAME | `['exp1`]') = exp1 */
		FuncState fs = this.fs;
		int reg = this.fs.freereg;
		expdesc key = new expdesc();
		expdesc val = new expdesc();
		int rkkey;
		if (this.t.token == TK_NAME) {
			fs.checklimit(cc.nh, MAX_INT, "items in a constructor");
			this.checkname(key);
		} else
			/* this.t.token == '[' */
			this.yindex(key);
		cc.nh++;
		this.checknext('=');
		rkkey = fs.exp2RK(key);
		this.expr(val);
		fs.codeABC(Lua.OP_SETTABLE, cc.t.u.info, rkkey, fs.exp2RK(val));
		fs.freereg = (short)reg; /* free registers */
	}

	void listfield (ConsControl cc) {
		this.expr(cc.v);
		fs.checklimit(cc.na, MAX_INT, "items in a constructor");
		cc.na++;
		cc.tostore++;
	}


	void constructor(expdesc t) {
		/* constructor -> ?? */
		FuncState fs = this.fs;
		int line = this.linenumber;
		int pc = fs.codeABC(Lua.OP_NEWTABLE, 0, 0, 0);
		ConsControl cc = new ConsControl();
		cc.na = cc.nh = cc.tostore = 0;
		cc.t = t;
		t.init(VRELOCABLE, pc);
		cc.v.init(VVOID, 0); /* no value (yet) */
		fs.exp2nextreg(t); /* fix it at stack top (for gc) */
		this.checknext('{');
		do {
			_assert (cc.v.k == VVOID || cc.tostore > 0);
			if (this.t.token == '}')
				break;
			fs.closelistfield(cc);
			switch (this.t.token) {
				case TK_NAME: { /* may be listfields or recfields */
					this.lookahead();
					if (this.lookahead.token != '=') /* expression? */
						this.listfield(cc);
					else
						this.recfield(cc);
					break;
				}
				case '[': { /* constructor_item -> recfield */
					this.recfield(cc);
					break;
				}
				default: { /* constructor_part -> listfield */
					this.listfield(cc);
					break;
				}
			}
		} while (this.testnext(',') || this.testnext(';'));
		this.check_match('}', '{', line);
		fs.lastlistfield(cc);
		InstructionPtr i = new InstructionPtr(fs.f.code, pc);
		SETARG_B(i, luaO_int2fb(cc.na)); /* set initial array size */
		SETARG_C(i, luaO_int2fb(cc.nh));  /* set initial table size */
	}

	/*
	 ** converts an integer to a "floating point byte", represented as
	 ** (eeeeexxx), where the real value is (1xxx) * 2^(eeeee - 1) if
	 ** eeeee != 0 and (xxx) otherwise.
	 */
	static int luaO_int2fb (int x) {
		int e = 0;  /* expoent */
		while (x >= 16) {
			x = (x+1) >> 1;
			e++;
		}
		if (x < 8) return x;
		else return ((e+1) << 3) | (((int)x) - 8);
	}


	/* }====================================================================== */

	void parlist () {
		/* parlist -> [ param { `,' param } ] */
		FuncState fs = this.fs;
		Prototype f = fs.f;
		int firstparam = fs.nactvar;
		int nparams = 0;
		LuaString varargname = null;
		f.is_vararg = 0;
		if (this.t.token != ')') {  /* is `parlist' not empty? */
			do {
				switch (this.t.token) {
					case TK_NAME: {  /* param . NAME */
						this.new_localvar(this.str_checkname());
						++nparams;
						break;
					}
					case TK_DOTS: {  /* param . `...' */
						this.next();
						f.is_vararg = 1;
						if (this.t.token == TK_NAME) {
							varargname = this.str_checkname();
							this.new_localvar(varargname);
						}
						break;
					}
					default: this.syntaxerror("<name> or " + LUA_QL("...") + " expected");
				}
			} while ((f.is_vararg==0) && this.testnext(','));
		}
		this.adjustlocalvars(nparams + (varargname != null ? 1 : 0));
		f.numparams = firstparam + nparams;
		f.varargname = varargname;
		fs.reserveregs(fs.nactvar);  /* reserve register for parameters */
	}


	void body(expdesc e, boolean needself, int line) {
		/* body -> `(' parlist `)' chunk END */
		FuncState new_fs = new FuncState();
		BlockCnt bl = new BlockCnt();
		new_fs.f = addprototype();
		new_fs.f.linedefined = line;
		open_func(new_fs, bl);
		this.checknext('(');
		if (needself) {
			new_localvarliteral("self");
			adjustlocalvars(1);
		}
		this.parlist();
		this.checknext(')');
		this.statlist();
		new_fs.f.lastlinedefined = this.linenumber;
		this.check_match(TK_END, TK_FUNCTION, line);
		this.codeclosure(e);
		this.close_func();
	}

	int explist(expdesc v) {
		/* explist1 -> expr { `,' expr } */
		int n = 1; /* at least one expression */
		this.expr(v);
		while (this.testnext(',')) {
			fs.exp2nextreg(v);
			this.expr(v);
			n++;
		}
		return n;
	}


	void funcargs(expdesc f, int line) {
		FuncState fs = this.fs;
		expdesc args = new expdesc();
		int base, nparams;
		switch (this.t.token) {
			case '(': { /* funcargs -> `(' [ explist1 ] `)' */
				this.next();
				if (this.t.token == ')') /* arg list is empty? */
					args.k = VVOID;
				else {
					this.explist(args);
					fs.setmultret(args);
				}
				this.check_match(')', '(', line);
				break;
			}
			case '{': { /* funcargs -> constructor */
				this.constructor(args);
				break;
			}
			case TK_STRING: { /* funcargs -> STRING */
				this.codestring(args, this.t.seminfo.ts);
				this.next(); /* must use `seminfo' before `next' */
				break;
			}
			default: {
				this.syntaxerror("function arguments expected");
				return;
			}
		}
		_assert (f.k == VNONRELOC);
		base = f.u.info; /* base register for call */
		if (hasmultret(args.k))
			nparams = Lua.LUA_MULTRET; /* open call */
		else {
			if (args.k != VVOID)
				fs.exp2nextreg(args); /* close last argument */
			nparams = fs.freereg - (base + 1);
		}
		f.init(VCALL, fs.codeABC(Lua.OP_CALL, base, nparams + 1, 2));
		fs.fixline(line);
		fs.freereg = (short)(base+1);  /* call remove function and arguments and leaves
		 * (unless changed) one result */
	}


	/*
	 ** {======================================================================
	 ** Expression parsing
	 ** =======================================================================
	 */

	void primaryexp (expdesc v) {
		/* primaryexp -> NAME | '(' expr ')' */
		switch (t.token) {
			case '(': {
				int line = linenumber;
				this.next();
				this.expr(v);
				this.check_match(')', '(', line);
				fs.dischargevars(v);
				return;
			}
			case TK_NAME: {
				singlevar(v);
				return;
			}
			default: {
				this.syntaxerror("unexpected symbol near " + txtToken(t.token));
				return;
			}
		}
	}


	void suffixedexp (expdesc v) {
		/* suffixedexp ->
       	primaryexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs } */
		int line = linenumber;
		primaryexp(v);
		for (;;) {
			switch (t.token) {
				case '.': { /* fieldsel */
					this.fieldsel(v);
					break;
				}
				case '[': { /* `[' exp1 `]' */
					expdesc key = new expdesc();
					fs.exp2anyregup(v);
					this.yindex(key);
					fs.indexed(v, key);
					break;
				}
				case ':': { /* `:' NAME funcargs */
					expdesc key = new expdesc();
					this.next();
					this.checkname(key);
					fs.self(v, key);
					this.funcargs(v, line);
					break;
				}
				case '(':
				case TK_STRING:
				case '{': { /* funcargs */
					fs.exp2nextreg(v);
					this.funcargs(v, line);
					break;
				}
				default:
					return;
			}
		}
	}


	void simpleexp(expdesc v) {
		/*
		 * simpleexp -> NUMBER | STRING | NIL | true | false | ... | constructor |
		 * FUNCTION body | primaryexp
		 */
		switch (this.t.token) {
			case TK_NUMBER: {
				v.init(VKNUM, 0);
				v.u.setNval(this.t.seminfo.r);
				break;
			}
			case TK_STRING: {
				this.codestring(v, this.t.seminfo.ts);
				break;
			}
			case TK_NIL: {
				v.init(VNIL, 0);
				break;
			}
			case TK_TRUE: {
				v.init(VTRUE, 0);
				break;
			}
			case TK_FALSE: {
				v.init(VFALSE, 0);
				break;
			}
			case TK_DOTS: { /* vararg */
				FuncState fs = this.fs;
				this.check_condition(fs.f.is_vararg!=0, "cannot use " + LUA_QL("...")
						+ " outside a vararg function");
				v.init(VVARARG, fs.codeABC(Lua.OP_VARARG, 0, 1, 0));
				break;
			}
			case '{': { /* constructor */
				this.constructor(v);
				return;
			}
			case TK_FUNCTION: {
				this.next();
				this.body(v, false, this.linenumber);
				return;
			}
			default: {
				this.suffixedexp(v);
				return;
			}
		}
		this.next();
	}


	int getunopr(int op) {
		switch (op) {
			case TK_NOT:
				return OPR_NOT;
			case '-':
				return OPR_MINUS;
			case '#':
				return OPR_LEN;
			case '~':
				return OPR_BNOT;
			default:
				return OPR_NOUNOPR;
		}
	}


	int getbinopr(int op) {
		switch (op) {
			case '+':
				return OPR_ADD;
			case '-':
				return OPR_SUB;
			case '*':
				return OPR_MUL;
			case '/':
				return OPR_DIV;
			case TK_IDIV:
				return OPR_IDIV;
			case '&':
				return OPR_BAND;
			case '|':
				return OPR_BOR;
			case '~':
				return OPR_BXOR;
			case TK_SHL:
				return OPR_SHL;
			case TK_SHR:
				return OPR_SHR;
			case '%':
				return OPR_MOD;
			case '^':
				return OPR_POW;
			case TK_CONCAT:
				return OPR_CONCAT;
			case TK_NE:
				return OPR_NE;
			case TK_EQ:
				return OPR_EQ;
			case '<':
				return OPR_LT;
			case TK_LE:
				return OPR_LE;
			case '>':
				return OPR_GT;
			case TK_GE:
				return OPR_GE;
			case TK_AND:
				return OPR_AND;
			case TK_OR:
				return OPR_OR;
			default:
				return OPR_NOBINOPR;
		}
	}

	static class Priority {
		final byte left; /* left priority for each binary operator */

		final byte right; /* right priority */

		public Priority(int i, int j) {
			left = (byte) i;
			right = (byte) j;
		}
	};

	static Priority[] priority = {  /* ORDER OPR */
			new Priority(10, 10), new Priority(10, 10), new Priority(11, 11), new Priority(11, 11), new Priority(11, 11), new Priority(6, 6), new Priority(4, 4),
			new Priority(5, 5), new Priority(7, 7), new Priority(7, 7), new Priority(11, 11), new Priority(13, 12),
			new Priority(8, 7),
			new Priority(3, 3), new Priority(3, 3),
			new Priority(3, 3), new Priority(3, 3), new Priority(3, 3), new Priority(3, 3),
			new Priority(2, 2), new Priority(1, 1)
	};

	static final int UNARY_PRIORITY	= 12;  /* priority for unary operators */


	/*
	 ** subexpr -> (simpleexp | unop subexpr) { binop subexpr }
	 ** where `binop' is any binary operator with a priority higher than `limit'
	 */
	int subexpr(expdesc v, int limit) {
		int op;
		int uop;
		this.enterlevel();
		uop = getunopr(this.t.token);
		if (uop != OPR_NOUNOPR) {
			int line = linenumber;
			this.next();
			this.subexpr(v, UNARY_PRIORITY);
			fs.prefix(uop, v, line);
		} else
			this.simpleexp(v);
		/* expand while operators have priorities higher than `limit' */
		op = getbinopr(this.t.token);
		while (op != OPR_NOBINOPR && priority[op].left > limit) {
			expdesc v2 = new expdesc();
			int line = linenumber;
			this.next();
			fs.infix(op, v);
			/* read sub-expression with higher priority */
			int nextop = this.subexpr(v2, priority[op].right);
			fs.posfix(op, v, v2, line);
			op = nextop;
		}
		this.leavelevel();
		return op; /* return first untreated operator */
	}

	void expr(expdesc v) {
		this.subexpr(v, 0);
	}

	/* }==================================================================== */



	/*
	 ** {======================================================================
	 ** Rules for Statements
	 ** =======================================================================
	 */


	boolean block_follow (boolean withuntil) {
		switch (t.token) {
			case TK_ELSE: case TK_ELSEIF: case TK_END: case TK_EOS:
				return true;
			case TK_UNTIL:
				return withuntil;
			default: return false;
		}
	}


	void block () {
		/* block -> chunk */
		FuncState fs = this.fs;
		BlockCnt bl = new BlockCnt();
		fs.enterblock(bl, false);
		this.statlist();
		fs.leaveblock();
	}


	/*
	 ** structure to chain all variables in the left-hand side of an
	 ** assignment
	 */
	static class LHS_assign {
		LHS_assign prev;
		/* variable (global, local, upvalue, or indexed) */
		expdesc v = new expdesc();
	};


	/*
	 ** check whether, in an assignment to a local variable, the local variable
	 ** is needed in a previous assignment (to a table). If so, save original
	 ** local value in a safe place and use this safe copy in the previous
	 ** assignment.
	 */
	void check_conflict (LHS_assign lh, expdesc v) {
		FuncState fs = this.fs;
		short extra = (short) fs.freereg;  /* eventual position to save local variable */
		boolean conflict = false;
		for (; lh!=null; lh = lh.prev) {
			if (lh.v.k == VINDEXED) {
				/* table is the upvalue/local being assigned now? */
				if (lh.v.u.ind_vt == v.k && lh.v.u.ind_t == v.u.info) {
					conflict = true;
					lh.v.u.ind_vt = VLOCAL;
					lh.v.u.ind_t = extra;  /* previous assignment will use safe copy */
				}
				/* index is the local being assigned? (index cannot be upvalue) */
				if (v.k == VLOCAL && lh.v.u.ind_idx == v.u.info) {
					conflict = true;
					lh.v.u.ind_idx = extra;  /* previous assignment will use safe copy */
				}
			}
		}
		if (conflict) {
			/* copy upvalue/local value to a temporary (in position 'extra') */
			int op = (v.k == VLOCAL) ? Lua.OP_MOVE : Lua.OP_GETUPVAL;
			fs.codeABC(op, extra, v.u.info, 0);
			fs.reserveregs(1);
		}
	}

	void check_readonly(expdesc v) {
		if (v.isconst)
			this.semerror("attempt to assign to const variable");
	}


	void assignment (LHS_assign lh, int nvars) {
		expdesc e = new expdesc();
		this.check_condition(VLOCAL <= lh.v.k && lh.v.k <= VINDEXED,
				"syntax error");
		if (this.testnext(',')) {  /* assignment -> `,' primaryexp assignment */
			LHS_assign nv = new LHS_assign();
			nv.prev = lh;
			this.suffixedexp(nv.v);
			if (nv.v.k != VINDEXED)
				this.check_conflict(lh, nv.v);
			this.check_readonly(nv.v);
			this.assignment(nv, nvars+1);
		}
		else {  /* assignment . `=' explist1 */
			int nexps;
			this.checknext('=');
			nexps = this.explist(e);
			if (nexps != nvars) {
				this.adjust_assign(nvars, nexps, e);
				if (nexps > nvars)
					this.fs.freereg -= nexps - nvars;  /* remove extra values */
			}
			else {
				fs.setoneret(e);  /* close last expression */
				this.check_readonly(lh.v);
				fs.storevar(lh.v, e);
				return;  /* avoid default */
			}
		}
		e.init(VNONRELOC, this.fs.freereg-1);  /* default assignment */
		this.check_readonly(lh.v);
		fs.storevar(lh.v, e);
	}


	int cond() {
		/* cond -> exp */
		expdesc v = new expdesc();
		/* read condition */
		this.expr(v);
		/* `falses' are all equal here */
		if (v.k == VNIL)
			v.k = VFALSE;
		fs.goiftrue(v);
		return v.f.i;
	}

	void gotostat(int pc) {
		int line = linenumber;
		LuaString label;
		int g;
		if (testnext(TK_GOTO))
			label = str_checkname();
		else if (testnext(TK_BREAK)) {
			label = LOOP_LABEL_BREAK;
		} else {
			checknext(TK_CONTINUE);
			label = LOOP_LABEL_CONTINUE;
		}
		g = newlabelentry(dyd.gt =grow(dyd.gt, dyd.n_gt+1), dyd.n_gt++, label, line, pc);
		findlabel(g);  /* close it if label already defined */
	}


	/* skip no-op statements */
	void skipnoopstat () {
		while (t.token == ';' || t.token == TK_DBCOLON)
			statement();
	}


	void labelstat (LuaString label, int line) {
		/* label -> '::' NAME '::' */
		int l;  /* index of new label being created */
		fs.checkrepeated(dyd.label, dyd.n_label, label);  /* check for repeated labels */
		checknext(TK_DBCOLON);  /* skip double colon */
		/* create new entry for this label */
		l = newlabelentry(dyd.label=grow(dyd.label, dyd.n_label+1), dyd.n_label++, label, line, fs.getlabel());
		skipnoopstat();  /* skip other no-op statements */
		if (block_follow(false)) {  /* label is last no-op statement in the block? */
			/* assume that locals are already out of scope */
			dyd.label[l].nactvar = fs.bl.nactvar;
		}
		findgotos(dyd.label[l]);
	}


	void looplabel(LuaString label, int pc) {
		int l = newlabelentry(dyd.label = grow(dyd.label, dyd.n_label + 1), dyd.n_label++, label, 0, pc);
		findgotos(dyd.label[l]);
	}


	void whilestat (int line) {
		/* whilestat -> WHILE cond DO block END */
		FuncState fs = this.fs;
		int whileinit;
		int condexit;
		BlockCnt bl = new BlockCnt();
		this.next();  /* skip WHILE */
		whileinit = fs.getlabel();
		condexit = this.cond();
		fs.enterblock(bl, true);
		this.checknext(TK_DO);
		this.block();
		this.looplabel(LOOP_LABEL_CONTINUE, fs.getlabel());
		fs.patchlist(fs.jump(), whileinit);
		this.check_match(TK_END, TK_WHILE, line);
		fs.leaveblock();
		fs.patchtohere(condexit);  /* false conditions finish the loop */
	}

	void repeatstat(int line) {
		/* repeatstat -> REPEAT block UNTIL cond */
		int condexit;
		FuncState fs = this.fs;
		int repeat_init = fs.getlabel();
		BlockCnt bl1 = new BlockCnt();
		BlockCnt bl2 = new BlockCnt();
		fs.enterblock(bl1, true); /* loop block */
		fs.enterblock(bl2, false); /* scope block */
		this.next(); /* skip REPEAT */
		this.statlist();
		this.check_match(TK_UNTIL, TK_REPEAT, line);
		this.looplabel(LOOP_LABEL_CONTINUE, fs.getlabel());
		condexit = this.cond(); /* read condition (inside scope block) */
		if (bl2.upval) { /* upvalues? */
			fs.patchclose(condexit, bl2.nactvar);
		}
		fs.leaveblock(); /* finish scope */
		fs.patchlist(condexit, repeat_init); /* close the loop */
		fs.leaveblock(); /* finish loop */
	}


	int exp1() {
		expdesc e = new expdesc();
		int k;
		this.expr(e);
		k = e.k;
		fs.exp2nextreg(e);
		return k;
	}


	void forbody(int base, int line, int nvars, boolean isnum) {
		/* forbody -> DO block */
		BlockCnt bl = new BlockCnt();
		FuncState fs = this.fs;
		int prep, endfor;
		this.adjustlocalvars(3); /* control variables */
		this.checknext(TK_DO);
		prep = isnum ? fs.codeAsBx(Lua.OP_FORPREP, base, NO_JUMP) : fs.jump();
		fs.enterblock(bl, false); /* scope for declared variables */
		this.adjustlocalvars(nvars);
		fs.reserveregs(nvars);
		this.block();
		fs.leaveblock(); /* end of scope for declared variables */
		this.looplabel(LOOP_LABEL_CONTINUE, fs.getlabel());
		fs.patchtohere(prep);
		if (isnum)  /* numeric for? */
			endfor = fs.codeAsBx(Lua.OP_FORLOOP, base, NO_JUMP);
		else {  /* generic for */
			fs.codeABC(Lua.OP_TFORCALL, base, 0, nvars);
			fs.fixline(line);
			endfor = fs.codeAsBx(Lua.OP_TFORLOOP, base + 2, NO_JUMP);
		}
		fs.patchlist(endfor, prep + 1);
		fs.fixline(line);
	}


	void fornum(LuaString varname, int line) {
		/* fornum -> NAME = exp1,exp1[,exp1] forbody */
		FuncState fs = this.fs;
		int base = fs.freereg;
		this.new_localvarliteral(RESERVED_LOCAL_VAR_FOR_INDEX);
		this.new_localvarliteral(RESERVED_LOCAL_VAR_FOR_LIMIT);
		this.new_localvarliteral(RESERVED_LOCAL_VAR_FOR_STEP);
		this.new_localvar(varname, false, true);
		this.checknext('=');
		this.exp1(); /* initial value */
		this.checknext(',');
		this.exp1(); /* limit */
		if (this.testnext(','))
			this.exp1(); /* optional step */
		else { /* default step = 1 */
			fs.codeK(fs.freereg, fs.numberK(LuaInteger.valueOf(1)));
			fs.reserveregs(1);
		}
		this.forbody(base, line, 1, true);
	}


	void forlist(LuaString indexname) {
		/* forlist -> NAME {,NAME} IN explist1 forbody */
		FuncState fs = this.fs;
		expdesc e = new expdesc();
		int nvars = 4;   /* gen, state, control, plus at least one declared var */
		int line;
		int base = fs.freereg;
		/* create control variables */
		this.new_localvarliteral(RESERVED_LOCAL_VAR_FOR_GENERATOR);
		this.new_localvarliteral(RESERVED_LOCAL_VAR_FOR_STATE);
		this.new_localvarliteral(RESERVED_LOCAL_VAR_FOR_CONTROL);
		/* create declared variables */
		this.new_localvar(indexname, false, true);
		while (this.testnext(',')) {
			this.new_localvar(this.str_checkname(), false, true);
			++nvars;
		}
		this.checknext(TK_IN);
		line = this.linenumber;
		this.adjust_assign(3, this.explist(e), e);
		fs.checkstack(3); /* extra space to call generator */
		this.forbody(base, line, nvars - 3, false);
	}


	void forstat(int line) {
		/* forstat -> FOR (fornum | forlist) END */
		FuncState fs = this.fs;
		LuaString varname;
		BlockCnt bl = new BlockCnt();
		fs.enterblock(bl, true); /* scope for loop and control variables */
		this.next(); /* skip `for' */
		varname = this.str_checkname(); /* first variable name */
		switch (this.t.token) {
			case '=':
				this.fornum(varname, line);
				break;
			case ',':
			case TK_IN:
				this.forlist(varname);
				break;
			default:
				this.syntaxerror(LUA_QL("=") + " or " + LUA_QL("in") + " expected");
		}
		this.check_match(TK_END, TK_FOR, line);
		fs.leaveblock(); /* loop scope (`break' jumps to this point) */
	}


	void test_then_block(IntPtr escapelist) {
		/* test_then_block -> [IF | ELSEIF] cond THEN block */
		expdesc v = new expdesc();
		BlockCnt bl = new BlockCnt();
		int jf;  /* instruction to skip 'then' code (if condition is false) */
		this.next(); /* skip IF or ELSEIF */
		expr(v);  /* read expression */
		this.checknext(TK_THEN);
		if (t.token == TK_GOTO || t.token == TK_BREAK || t.token == TK_CONTINUE) {
			fs.goiffalse(v); /* will jump to label if condition is true */
			fs.enterblock(bl, false); /* must enter block before 'goto' */
			gotostat(v.t.i); /* handle goto/break */
			skipnoopstat(); /* skip other no-op statements */
			if (block_follow(false)) { /* 'goto' is the entire block? */
				fs.leaveblock();
				return; /* and that is it */
			} else
				/* must skip over 'then' part if condition is false */
				jf = fs.jump();
		} else { /* regular case (not goto/break) */
			fs.goiftrue(v); /* skip over block if condition is false */
			fs.enterblock(bl, false);
			jf = v.f.i;
		}
		statlist(); /* `then' part */
		fs.leaveblock();
		if (t.token == TK_ELSE || t.token == TK_ELSEIF)
			fs.concat(escapelist, fs.jump()); /* must jump over it */
		fs.patchtohere(jf);
	}


	void ifstat(int line) {
		IntPtr escapelist = new IntPtr(NO_JUMP);  /* exit list for finished parts */
		test_then_block(escapelist);  /* IF cond THEN block */
		while (t.token == TK_ELSEIF)
			test_then_block(escapelist);  /* ELSEIF cond THEN block */
		if (testnext(TK_ELSE))
			block();  /* `else' part */
		check_match(TK_END, TK_IF, line);
		fs.patchtohere(escapelist.i);  /* patch escape list to 'if' end */
	}

	void localfunc() {
		expdesc b = new expdesc();
		FuncState fs = this.fs;
		this.new_localvar(this.str_checkname());
		this.adjustlocalvars(1);
		this.body(b, false, this.linenumber);
		/* debug information will only see the variable after this point! */
		fs.getlocvar(fs.nactvar - 1).startpc = fs.pc;
	}


	void localstat() {
		/* stat -> LOCAL NAME attrib {`,' NAME attrib} [`=' explist1] */
		int nvars = 0;
		int nexps;
		expdesc e = new expdesc();
		do {
			LuaString name = this.str_checkname();
			boolean toclose = false;
			boolean isconst = false;
			if (this.t.token == '<') {
				this.next();
				LuaString attr = this.str_checkname();
				this.checknext('>');
				if (attr.eq_b(LuaValue.valueOf("close"))) {
					toclose = true;
				} else if (attr.eq_b(LuaValue.valueOf("const"))) {
					isconst = true;
				} else {
					this.syntaxerror("unknown attribute '" + attr.tojstring() + "'");
				}
			}
			this.new_localvar(name, toclose, isconst);
			++nvars;
		} while (this.testnext(','));
		if (this.testnext('='))
			nexps = this.explist(e);
		else {
			e.k = VVOID;
			nexps = 0;
		}
		this.adjust_assign(nvars, nexps, e);
		this.adjustlocalvars(nvars);
	}

	private boolean parseGlobalAttribute() {
		this.checknext('<');
		LuaString attr = this.str_checkname();
		this.checknext('>');
		if (attr.eq_b(LuaValue.valueOf("const")))
			return true;
		if (attr.eq_b(LuaValue.valueOf("close")))
			this.syntaxerror("global variables do not support attribute 'close'");
		this.syntaxerror("unknown attribute '" + attr.tojstring() + "'");
		return false;
	}

	private void storeDeclaredGlobals(ArrayList names, expdesc e, int nvars, int nexps) {
		ArrayList vars = new ArrayList(nvars);
		for (int i = 0; i < nvars; i++) {
			expdesc var = new expdesc();
			makeglobalvar((LuaString) names.get(i), var);
			vars.add(var);
		}
		if (nexps == nvars) {
			fs.setoneret(e);
			fs.storevar((expdesc) vars.get(nvars - 1), e);
			nvars--;
		}
		for (int i = nvars - 1; i >= 0; i--) {
			expdesc value = new expdesc();
			value.init(VNONRELOC, this.fs.freereg - 1);
			fs.storevar((expdesc) vars.get(i), value);
		}
	}

	void globalstat() {
		int nvars = 0;
		int nexps = 0;
		expdesc e = new expdesc();
		boolean defaultConst = false;
		ArrayList names = new ArrayList();
		ArrayList consts = new ArrayList();
		this.next(); /* skip GLOBAL */
		if (this.t.token == '<')
			defaultConst = parseGlobalAttribute();
		if (this.t.token == '*') {
			this.next();
			registerglobaldecl(null, defaultConst, true);
			return;
		}
		do {
			LuaString name = this.str_checkname();
			boolean isconst = defaultConst;
			if (this.t.token == '<')
				isconst = parseGlobalAttribute();
			names.add(name);
			consts.add(Boolean.valueOf(isconst));
			nvars++;
		} while (this.testnext(','));
		if (this.testnext('=')) {
			nexps = this.explist(e);
			this.adjust_assign(nvars, nexps, e);
			storeDeclaredGlobals(names, e, nvars, nexps);
		}
		for (int i = 0; i < nvars; i++)
			registerglobaldecl((LuaString) names.get(i), ((Boolean) consts.get(i)).booleanValue(), false);
	}


	boolean funcname(expdesc v) {
		/* funcname -> NAME {field} [`:' NAME] */
		boolean ismethod = false;
		this.singlevar(v);
		while (this.t.token == '.')
			this.fieldsel(v);
		if (this.t.token == ':') {
			ismethod = true;
			this.fieldsel(v);
		}
		return ismethod;
	}


	void funcstat(int line) {
		/* funcstat -> FUNCTION funcname body */
		boolean needself;
		expdesc v = new expdesc();
		expdesc b = new expdesc();
		this.next(); /* skip FUNCTION */
		needself = this.funcname(v);
		this.body(b, needself, line);
		fs.storevar(v, b);
		fs.fixline(line); /* definition `happens' in the first line */
	}


	void exprstat() {
		/* stat -> func | assignment */
		FuncState fs = this.fs;
		LHS_assign v = new LHS_assign();
		this.suffixedexp(v.v);
		if (t.token == '=' || t.token == ',') { /* stat -> assignment ? */
			v.prev = null;
			assignment(v, 1);
		}
		else {  /* stat -> func */
			check_condition(v.v.k == VCALL, "syntax error");
			SETARG_C(fs.getcodePtr(v.v), 1);  /* call statement uses no results */
		}
	}

	void retstat() {
		/* stat -> RETURN explist */
		FuncState fs = this.fs;
		expdesc e = new expdesc();
		int first, nret; /* registers with returned values */
		if (block_follow(true) || this.t.token == ';')
			first = nret = 0; /* return no values */
		else {
			nret = this.explist(e); /* optional return values */
			if (hasmultret(e.k)) {
				fs.setmultret(e);
				if (e.k == VCALL && nret == 1) { /* tail call? */
					SET_OPCODE(fs.getcodePtr(e), Lua.OP_TAILCALL);
					_assert (Lua.GETARG_A(fs.getcode(e)) == fs.nactvar);
				}
				first = fs.nactvar;
				nret = Lua.LUA_MULTRET; /* return all values */
			} else {
				if (nret == 1) /* only one single value? */
					first = fs.exp2anyreg(e);
				else {
					fs.exp2nextreg(e); /* values must go to the `stack' */
					first = fs.nactvar; /* return all `active' values */
					_assert (nret == fs.freereg - first);
				}
			}
		}
		fs.ret(first, nret);
		testnext(';');  /* skip optional semicolon */
	}

	void statement() {
		int line = this.linenumber; /* may be needed for error messages */
		enterlevel();
		switch (this.t.token) {
			case ';': { /* stat -> ';' (empty statement) */
				next(); /* skip ';' */
				break;
			}
			case TK_IF: { /* stat -> ifstat */
				this.ifstat(line);
				break;
			}
			case TK_WHILE: { /* stat -> whilestat */
				this.whilestat(line);
				break;
			}
			case TK_DO: { /* stat -> DO block END */
				this.next(); /* skip DO */
				this.block();
				this.check_match(TK_END, TK_DO, line);
				break;
			}
			case TK_FOR: { /* stat -> forstat */
				this.forstat(line);
				break;
			}
			case TK_REPEAT: { /* stat -> repeatstat */
				this.repeatstat(line);
				break;
			}
			case TK_FUNCTION: {
				this.funcstat(line); /* stat -> funcstat */
				break;
			}
			case TK_LOCAL: { /* stat -> localstat */
				this.next(); /* skip LOCAL */
				if (this.testnext(TK_FUNCTION)) /* local function? */
					this.localfunc();
				else
					this.localstat();
				break;
			}
			case TK_GLOBAL: {
				this.globalstat();
				break;
			}
			case TK_DBCOLON: { /* stat -> label */
				next(); /* skip double colon */
				labelstat(str_checkname(), line);
				break;
			}
			case TK_RETURN: { /* stat -> retstat */
				next();  /* skip RETURN */
				this.retstat();
				break;
			}
			case TK_BREAK:
			case TK_CONTINUE:
			case TK_GOTO: { /* stat -> breakstat */
				this.gotostat(fs.jump());
				break;
			}
			default: {
				this.exprstat();
				break;
			}
		}
		_assert(fs.f.maxstacksize >= fs.freereg
				&& fs.freereg >= fs.nactvar);
		fs.freereg = fs.nactvar; /* free registers */
		leavelevel();
	}

	void statlist() {
		/* statlist -> { stat [`;'] } */
		while (!block_follow(true)) {
			if (t.token == TK_RETURN) {
				statement();
				return; /* 'return' must be last statement */
			}
			statement();
		}
	}

	/*
	 ** compiles the main function, which is a regular vararg function with an
	 ** upvalue named LUA_ENV
	 */
	public void mainfunc(FuncState funcstate) {
		BlockCnt bl = new BlockCnt();
		open_func(funcstate, bl);
		fs.f.is_vararg = 1;  /* main function is always vararg */
		expdesc v = new expdesc();
		v.init(VLOCAL, 0);  /* create and... */
		fs.newupvalue(envn, v);  /* ...set environment upvalue */
		next();  /* read first token */
		statlist();  /* parse main body */
		check(TK_EOS);
		close_func();
	}

	/* }====================================================================== */

}
