// The CMLeonOS Project (https://github.com/Leonmmcoset/CMLeonOS)
// Copyright (C) 2025-present LeonOS 2 Developer Team
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

using System;
using System.IO;
using System.Collections.Generic;

using ULDebug = UniLua.Tools.ULDebug;

namespace UniLua
{
	using InstructionPtr = Pointer<Instruction>;

	public class FuncState
	{
		public FuncState Prev;
		public BlockCnt	Block;
		public LuaProto Proto;
		public LuaState State;
		public LLex		Lexer;

		public Dictionary<int, int> H;

        public int NumActVar;
		public int FreeReg;

		public int Pc;
		public int LastTarget;
		public int Jpc;
		public int FirstLocal;

		public FuncState()
		{
			Proto = new LuaProto();
			H = new Dictionary<int, int>();
			NumActVar = 0;
			FreeReg = 0;
		}

		public InstructionPtr GetCode( ExpDesc e )
		{
			return new InstructionPtr( Proto.Code, e.Info );
		}
	}

	public class BlockCnt
	{
		public BlockCnt Previous;
		public int		FirstLabel;
		public int		FirstGoto;
		public int		NumActVar;
		public bool		HasUpValue;
		public bool		IsLoop;
	}

	public class ConstructorControl
	{
		public ExpDesc	ExpLastItem;
		public ExpDesc	ExpTable;
		public int		NumRecord;
		public int		NumArray;
		public int		NumToStore;

		public ConstructorControl()
		{
			ExpLastItem = new ExpDesc();
		}
	}

	public enum ExpKind
	{
		VVOID,	/* no value */
		VNIL,
		VTRUE,
		VFALSE,
		VK,		/* info = index of constant in `k' */
		VKNUM,	/* nval = numerical value */
		VNONRELOC,	/* info = result register */
		VLOCAL,	/* info = local register */
		VUPVAL,       /* info = index of upvalue in 'upvalues' */
		VINDEXED,	/* t = table register/upvalue; idx = index R/K */
		VJMP,		/* info = instruction pc */
		VRELOCABLE,	/* info = instruction pc */
		VCALL,	/* info = instruction pc */
		VVARARG	/* info = instruction pc */
	}

	public static class ExpKindUtl
	{
		public static bool VKIsVar( ExpKind k )
		{
			return ((int)ExpKind.VLOCAL <= (int)k &&
					(int)k <= (int)ExpKind.VINDEXED);
		}

		public static bool VKIsInReg( ExpKind k )
		{
			return k == ExpKind.VNONRELOC || k == ExpKind.VLOCAL;
		}
	}
	
	public enum BinOpr
	{
		ADD,
		SUB,
		MUL,
		DIV,
		MOD,
		POW,
		CONCAT,
		EQ,
		LT,
		LE,
		NE,
		GT,
		GE,
		AND,
		OR,
		NOBINOPR,
	}

	public enum UnOpr
	{
		MINUS,
		NOT,
		LEN,
		NOUNOPR,
	}

	public class ExpDesc
	{
		public ExpKind Kind;

		public int Info;

		public struct IndData
		{
			public int T;
			public int Idx;
			public ExpKind Vt;
		}
		public IndData Ind;

		public double NumberValue;

		public int ExitTrue;
		public int ExitFalse;

		public void CopyFrom( ExpDesc e )
		{
			this.Kind 			= e.Kind;
			this.Info 			= e.Info;
			// this.Ind.T 		= e.Ind.T;
			// this.Ind.Idx 	= e.Ind.Idx;
			// this.Ind.Vt 		= e.Ind.Vt;
			this.Ind 			= e.Ind;
			this.NumberValue 	= e.NumberValue;
			this.ExitTrue 		= e.ExitTrue;
			this.ExitFalse 		= e.ExitFalse;
		}
	}

	public class VarDesc
	{
		public int Index;
	}

	public class LabelDesc
	{
		public string 	Name;		// label identifier
		public int 		Pc;			// position in code
		public int		Line;		// line where it appear
		public int		NumActVar;	// local level where it appears in current block
	}

	public class LHSAssign
	{
		public LHSAssign 	Prev;
		public ExpDesc		Exp;

		public LHSAssign() {
			Prev = null;
			Exp  = new ExpDesc();
		}
	}

	public class Parser
	{
		public static LuaProto Parse(
			ILuaState lua, ILoadInfo loadinfo, string name )
		{
			var parser = new Parser();
			parser.Lua = (LuaState)lua;
			parser.Lexer = new LLex( lua, loadinfo, name );

			var topFuncState = new FuncState();
			parser.MainFunc( topFuncState );
			return topFuncState.Proto;
		}

		private const int 		MAXVARS = 200;

		private LLex 			Lexer;
		private FuncState 		CurFunc;
		private List<VarDesc> 	ActVars;
		private List<LabelDesc> PendingGotos;
		private List<LabelDesc> ActiveLabels;
		private LuaState		Lua;

		private Parser()
		{
			ActVars = new List<VarDesc>();
			PendingGotos = new List<LabelDesc>();
			ActiveLabels = new List<LabelDesc>();
			CurFunc = null;
		}

		private LuaProto AddPrototype()
		{
			var p = new LuaProto();
			CurFunc.Proto.P.Add( p );
			return p;
		}

		private void CodeClosure( ExpDesc v )
		{
			FuncState fs = CurFunc.Prev;
			InitExp( v, ExpKind.VRELOCABLE,
				Coder.CodeABx( fs, OpCode.OP_CLOSURE, 0,
					(uint)(fs.Proto.P.Count-1) ) );

			// fix it at stack top
			Coder.Exp2NextReg( fs, v );
		}

		private void OpenFunc( FuncState fs, BlockCnt block )
		{
			fs.Lexer = Lexer;

			fs.Prev = CurFunc;
			CurFunc = fs;

			fs.Pc = 0;
			fs.LastTarget = 0;
			fs.Jpc = Coder.NO_JUMP;
			fs.FreeReg = 0;
			fs.NumActVar = 0;
			fs.FirstLocal = ActVars.Count;

			// registers 0/1 are always valid
			fs.Proto.MaxStackSize = 2;
			fs.Proto.Source = Lexer.Source;

			EnterBlock( fs, block, false );
		}

		private void CloseFunc()
		{
			Coder.Ret( CurFunc, 0, 0 );

			LeaveBlock( CurFunc );

			CurFunc = CurFunc.Prev;
		}

		private void MainFunc( FuncState fs )
		{
			ExpDesc v = new ExpDesc();
			var block = new BlockCnt();
			OpenFunc( fs, block );
			fs.Proto.IsVarArg = true; // main func is always vararg
			InitExp( v, ExpKind.VLOCAL, 0 );
			NewUpvalue( fs, LuaDef.LUA_ENV, v );
			Lexer.Next(); // read first token
			StatList();
			// check TK_EOS
			CloseFunc();
		}

		private bool BlockFollow( bool withUntil )
		{
			switch( Lexer.Token.TokenType )
			{
				case (int)TK.ELSE:
				case (int)TK.ELSEIF:
				case (int)TK.END:
				case (int)TK.EOS:
					return true;

				case (int)TK.UNTIL:
					return withUntil;

				default:
					return false;
			}
		}

		private bool TestNext( int tokenType )
		{
			if( Lexer.Token.TokenType == tokenType )
			{
				Lexer.Next();
				return true;
			}
			else return false;
		}

		private void Check( int tokenType )
		{
			if( Lexer.Token.TokenType != tokenType )
				ErrorExpected( tokenType );
		}

		private void CheckNext( int tokenType )
		{
			Check( tokenType );
			Lexer.Next();
		}

		private void CheckCondition( bool cond, string msg )
		{
			if( !cond )
				Lexer.SyntaxError( msg );
		}

		private void EnterLevel()
		{
			++Lua.NumCSharpCalls;
			CheckLimit( CurFunc, Lua.NumCSharpCalls,
				LuaLimits.LUAI_MAXCCALLS, "C# levels" );
		}

		private void LeaveLevel()
		{
			--Lua.NumCSharpCalls;
		}

		private void SemanticError( string msg )
		{
			// TODO
			Lexer.SyntaxError( msg );
		}

		private void ErrorLimit( FuncState fs, int limit, string what )
		{
			int line = fs.Proto.LineDefined;
			string where = (line == 0)
				? "main function"
				: string.Format("function at line {0}", line);
			string msg = string.Format("too many {0} (limit is {1}) in {2}",
				what, limit, where);
			Lexer.SyntaxError( msg );
		}

		private void CheckLimit( FuncState fs, int v, int l, string what )
		{
			if( v > l )
				ErrorLimit( fs, l, what );
		}

		private int RegisterLocalVar( string varname )
		{
			var v = new LocVar();
			v.VarName = varname;
			v.StartPc = 0;
			v.EndPc   = 0;
			CurFunc.Proto.LocVars.Add(v);
			return CurFunc.Proto.LocVars.Count - 1;
		}

		private VarDesc NewLocalVar( string name )
		{
			var fs = CurFunc;
			int reg = RegisterLocalVar( name );
			CheckLimit( fs, ActVars.Count + 1 - fs.FirstLocal,
				MAXVARS, "local variables");
			var v = new VarDesc();
			v.Index = reg;
			return v;
		}

		private LocVar GetLocalVar( FuncState fs, int i )
		{
			int idx = ActVars[fs.FirstLocal + i].Index;
			Utl.Assert( idx < fs.Proto.LocVars.Count );
			return fs.Proto.LocVars[idx];
		}

		private void AdjustLocalVars( int nvars )
		{
			var fs = CurFunc;
			fs.NumActVar += nvars;
			for( ; nvars > 0; --nvars )
			{
				var v = GetLocalVar( fs, fs.NumActVar - nvars );
				v.StartPc = fs.Pc;
			}
		}

		private void RemoveVars( FuncState fs, int toLevel )
		{
			var len = fs.NumActVar - toLevel;
			while( fs.NumActVar > toLevel )
			{
				var v = GetLocalVar( fs, --fs.NumActVar );
				v.EndPc = fs.Pc;
			}
			ActVars.RemoveRange( ActVars.Count-len, len );
		}

		private void CloseGoto( int g, LabelDesc label )
		{
			var gt = PendingGotos[g];
			Utl.Assert( gt.Name == label.Name );
			if( gt.NumActVar < label.NumActVar )
			{
				var v = GetLocalVar( CurFunc, gt.NumActVar );
				var msg = string.Format(
					"<goto {0}> at line {1} jumps into the scope of local '{2}'",
					gt.Name, gt.Line, v.VarName);
				SemanticError( msg );
			}
			Coder.PatchList( CurFunc, gt.Pc, label.Pc );
			
			PendingGotos.RemoveAt(g);
		}

		// try to close a goto with existing labels; this solves backward jumps
		private bool FindLabel( int g )
		{
			var gt = PendingGotos[g];
			var block = CurFunc.Block;
			for( int i=block.FirstLabel; i<ActiveLabels.Count; ++i )
			{
				var label = ActiveLabels[i];

				// correct label?
				if( label.Name == gt.Name )
				{
					if( gt.NumActVar > label.NumActVar &&
						(block.HasUpValue || ActiveLabels.Count > block.FirstLabel) )
					{
						Coder.PatchClose( CurFunc, gt.Pc, label.NumActVar );
					}
					CloseGoto( g, label );
					return true;
				}
			}
			return false;
		}

		private LabelDesc NewLebelEntry( string name, int line, int pc )
		{
			var desc = new LabelDesc();
			desc.Name 		= name;
			desc.Pc			= pc;
			desc.Line		= line;
			desc.NumActVar	= CurFunc.NumActVar;
			return desc;
		}

		// check whether new label `label' matches any pending gotos in current
		// block; solves forward jumps
		private void FindGotos( LabelDesc label )
		{
			int i = CurFunc.Block.FirstGoto;
			while( i < PendingGotos.Count )
			{
				if( PendingGotos[i].Name == label.Name )
					CloseGoto( i, label );
				else
					++i;
			}
		}

		// "export" pending gotos to outer level, to check them against
		// outer labels; if the block being exited has upvalues, and
		// the goto exits the scope of any variable (which can be the
		// upvalue), close those variables being exited.
		private void MoveGotosOut( FuncState fs, BlockCnt block )
		{
			int i = block.FirstGoto;

			// correct pending gotos to current block and try to close it
			// with visible labels
			while( i < PendingGotos.Count )
			{
				var gt = PendingGotos[i];
				if( gt.NumActVar > block.NumActVar )
				{
					if( block.HasUpValue )
						Coder.PatchClose( fs, gt.Pc, block.NumActVar );
					gt.NumActVar = block.NumActVar;
				}
				if( !FindLabel(i) )
					++i; // move to next one
			}
		}

		// create a label named `break' to resolve break statements
		private void BreakLabel()
		{
			var desc = NewLebelEntry( "break", 0, CurFunc.Pc );
			ActiveLabels.Add( desc );
			FindGotos( ActiveLabels[ActiveLabels.Count-1] );
		}

		// generates an error for an undefined `goto'; choose appropriate
		// message when label name is a reserved word (which can only be `break')
		private void UndefGoto( LabelDesc gt )
		{
			string template = Lexer.IsReservedWord( gt.Name )
				? "<{0}> at line {1} not inside a loop"
				: "no visible label '{0}' for <goto> at line {1}";
			var msg = string.Format( template, gt.Name, gt.Line );
			SemanticError( msg );
		}

		private void EnterBlock( FuncState fs, BlockCnt block, bool isLoop )
		{
			block.IsLoop 		= isLoop;
			block.NumActVar 	= fs.NumActVar;
			block.FirstLabel 	= ActiveLabels.Count;
			block.FirstGoto 	= PendingGotos.Count;
			block.HasUpValue 	= false;
			block.Previous 		= fs.Block;
			fs.Block 			= block;
			Utl.Assert( fs.FreeReg == fs.NumActVar );
		}

		private void LeaveBlock( FuncState fs )
		{
			var block = fs.Block;

			if( block.Previous != null && block.HasUpValue )
			{
				int j = Coder.Jump( fs );
				Coder.PatchClose( fs, j, block.NumActVar );
				Coder.PatchToHere( fs, j );
			}

			if( block.IsLoop )
				BreakLabel();

			fs.Block = block.Previous;
			RemoveVars( fs, block.NumActVar );
			Utl.Assert( block.NumActVar == fs.NumActVar );
			fs.FreeReg =  fs.NumActVar; // free registers
			ActiveLabels.RemoveRange( block.FirstLabel,
				ActiveLabels.Count - block.FirstLabel );

			// inner block?
			if( block.Previous != null )
				MoveGotosOut( fs, block );

			// pending gotos in outer block?
			else if( block.FirstGoto < PendingGotos.Count )
				UndefGoto( PendingGotos[block.FirstGoto] ); // error
		}

		private UnOpr GetUnOpr( int op )
		{
			switch( op )
			{
				case (int)TK.NOT:
					return UnOpr.NOT;
				case (int)'-':
					return UnOpr.MINUS;
				case (int)'#':
					return UnOpr.LEN;
				default:
					return UnOpr.NOUNOPR;
			}
		}

		private BinOpr GetBinOpr( int op )
		{
			switch( op )
			{
				case (int)'+': return BinOpr.ADD;
				case (int)'-': return BinOpr.SUB;
				case (int)'*': return BinOpr.MUL;
				case (int)'/': return BinOpr.DIV;
				case (int)'%': return BinOpr.MOD;
				case (int)'^': return BinOpr.POW;
				case (int)TK.CONCAT: return BinOpr.CONCAT;
				case (int)TK.NE: return BinOpr.NE;
				case (int)TK.EQ: return BinOpr.EQ;
				case (int)'<': return BinOpr.LT;
				case (int)TK.LE: return BinOpr.LE;
				case (int)'>': return BinOpr.GT;
				case (int)TK.GE: return BinOpr.GE;
				case (int)TK.AND: return BinOpr.AND;
				case (int)TK.OR: return BinOpr.OR;
				default: return BinOpr.NOBINOPR;
			}
		}

		private int GetBinOprLeftPrior( BinOpr opr )
		{
			switch( opr )
			{
				case BinOpr.ADD: return 6;
				case BinOpr.SUB: return 6;
				case BinOpr.MUL: return 7;
				case BinOpr.DIV: return 7;
				case BinOpr.MOD: return 7;
				case BinOpr.POW: return 10;
				case BinOpr.CONCAT: return 5;
				case BinOpr.EQ: return 3;
				case BinOpr.LT: return 3;
				case BinOpr.LE: return 3;
				case BinOpr.NE: return 3;
				case BinOpr.GT: return 3;
				case BinOpr.GE: return 3;
				case BinOpr.AND: return 2;
				case BinOpr.OR: return 1;
				case BinOpr.NOBINOPR:
					throw new Exception("GetBinOprLeftPrior(NOBINOPR)");
				default:
					throw new Exception("Unknown BinOpr");
			}
		}

		private int GetBinOprRightPrior( BinOpr opr )
		{
			switch( opr )
			{
				case BinOpr.ADD: return 6;
				case BinOpr.SUB: return 6;
				case BinOpr.MUL: return 7;
				case BinOpr.DIV: return 7;
				case BinOpr.MOD: return 7;
				case BinOpr.POW: return 9;
				case BinOpr.CONCAT: return 4;
				case BinOpr.EQ: return 3;
				case BinOpr.LT: return 3;
				case BinOpr.LE: return 3;
				case BinOpr.NE: return 3;
				case BinOpr.GT: return 3;
				case BinOpr.GE: return 3;
				case BinOpr.AND: return 2;
				case BinOpr.OR: return 1;
				case BinOpr.NOBINOPR:
					throw new Exception("GetBinOprRightPrior(NOBINOPR)");
				default:
					throw new Exception("Unknown BinOpr");
			}
		}

		private const int UnaryPrior = 8;

		// statlist -> { stat [';'] }
		private void StatList()
		{
			while( ! BlockFollow( true ) )
			{
				if( Lexer.Token.TokenType == (int)TK.RETURN )
				{
					Statement();
					return; // 'return' must be last statement
				}
				Statement();
			}
		}

		// fieldsel -> ['.' | ':'] NAME
		private void FieldSel( ExpDesc v )
		{
			var fs = CurFunc;
			var key = new ExpDesc();
			Coder.Exp2AnyRegUp( fs, v );
			Lexer.Next(); // skip the dot or colon
			CodeString( key, CheckName() );
			Coder.Indexed( fs, v, key );
		}

		// cond -> exp
		private int Cond()
		{
			var v = new ExpDesc();
			Expr( v ); // read condition

			// `falses' are all equal here
			if( v.Kind == ExpKind.VNIL )
				v.Kind = ExpKind.VFALSE;

			Coder.GoIfTrue( CurFunc, v );
			return v.ExitFalse;
		}

		private void GotoStat( int pc )
		{
			string label;
			if( TestNext( (int)TK.GOTO ) )
				label = CheckName();
			else
			{
				Lexer.Next();
				label = "break";
			}

			PendingGotos.Add( NewLebelEntry( label, Lexer.LineNumber, pc ) );

			// close it if label already defined
			FindLabel( PendingGotos.Count-1 );
		}

		// check for repeated labels on the same block
		private void CheckRepeated( FuncState fs, List<LabelDesc> list, string label )
		{
			for( int i = fs.Block.FirstLabel; i < list.Count; ++i )
			{
				if( label == list[i].Name )
				{
					SemanticError( string.Format(
						"label '{0}' already defined on line {1}",
						label, list[i].Line ) );
				}
			}
		}

		// skip no-op statements
		private void SkipNoOpStat()
		{
			while( Lexer.Token.TokenType == (int)';' ||
				   Lexer.Token.TokenType == (int)TK.DBCOLON )
			{
				Statement();
			}
		}

		private void LabelStat( string label, int line )
		{
			var fs = CurFunc;
			CheckRepeated( fs, ActiveLabels, label );
			CheckNext( (int)TK.DBCOLON );

			var desc = NewLebelEntry( label, line, fs.Pc );
			ActiveLabels.Add( desc );
			SkipNoOpStat();
			if( BlockFollow( false ) )
			{
				// assume that locals are already out of scope
				desc.NumActVar = fs.Block.NumActVar;
			}
			FindGotos( desc );
		}

		// whilestat -> WHILE cond DO block END
		private void WhileStat( int line )
		{
			var fs = CurFunc;
			var block = new BlockCnt();

			Lexer.Next(); // skip WHILE
			int whileInit = Coder.GetLabel( fs );
			int condExit = Cond();
			EnterBlock( fs, block, true );
			CheckNext( (int)TK.DO );
			Block();
			Coder.JumpTo( fs, whileInit );
			CheckMatch( (int)TK.END, (int)TK.WHILE, line );
			LeaveBlock( fs );
			Coder.PatchToHere( fs, condExit );
		}

		// repeatstat -> REPEAT block UNTIL cond
		private void RepeatStat( int line )
		{
			var fs = CurFunc;
			int repeatInit = Coder.GetLabel( fs );
			var blockLoop  = new BlockCnt();
			var blockScope = new BlockCnt();
			EnterBlock( fs, blockLoop, true );
			EnterBlock( fs, blockScope, false );
			Lexer.Next();
			StatList();
			CheckMatch( (int)TK.UNTIL, (int)TK.REPEAT, line );
			int condExit = Cond();
			if( blockScope.HasUpValue )
			{
				Coder.PatchClose( fs, condExit, blockScope.NumActVar );
			}
			LeaveBlock( fs );
			Coder.PatchList( fs, condExit, repeatInit ); // close the loop
			LeaveBlock( fs );
		}

		private int Exp1()
		{
			var e = new ExpDesc();
			Expr( e );
			Coder.Exp2NextReg( CurFunc, e );
			Utl.Assert( e.Kind == ExpKind.VNONRELOC );
			return e.Info;
		}

		// forbody -> DO block
		private void ForBody( int t, int line, int nvars, bool isnum )
		{
			var fs = CurFunc;
			var block = new BlockCnt();
			AdjustLocalVars( 3 ); // control variables
			CheckNext( (int)TK.DO );
			int prep = isnum ? Coder.CodeAsBx( fs, OpCode.OP_FORPREP, t, Coder.NO_JUMP )
				: Coder.Jump( fs );
			EnterBlock( fs, block, false );
			AdjustLocalVars( nvars );
			Coder.ReserveRegs( fs, nvars );
			Block();
			LeaveBlock( fs );
			Coder.PatchToHere( fs, prep );

			int endfor;
			if( isnum ) // numeric for?
			{
				endfor = Coder.CodeAsBx( fs, OpCode.OP_FORLOOP, t, Coder.NO_JUMP );
			}
			else // generic for
			{
				Coder.CodeABC( fs, OpCode.OP_TFORCALL, t, 0, nvars );
				Coder.FixLine( fs, line );
				endfor = Coder.CodeAsBx( fs, OpCode.OP_TFORLOOP, t+2, Coder.NO_JUMP );
			}
			Coder.PatchList( fs, endfor, prep+1 );
			Coder.FixLine( fs, line );
		}

		// fornum -> NAME = exp1,expe1[,exp1] forbody
		private void ForNum( string varname, int line )
		{
			var fs = CurFunc;
			var save = fs.FreeReg;
			ActVars.Add( NewLocalVar("(for index)") );
			ActVars.Add( NewLocalVar("(for limit)") );
			ActVars.Add( NewLocalVar("(for step)") );
			ActVars.Add( NewLocalVar(varname) );
			CheckNext( (int)'=' );
			Exp1(); // initial value
			CheckNext( (int)',' );
			Exp1(); // limit
			if( TestNext( (int)',' ) )
			{
				Exp1(); // optional step
			}
			else // default step = 1
			{
				Coder.CodeK( fs, fs.FreeReg, Coder.NumberK( fs, 1 ) );
				Coder.ReserveRegs( fs, 1 );
			}
			ForBody( save, line, 1, true );
		}

		// forlist -> NAME {,NAME} IN explist forbody
		private void ForList( string indexName )
		{
			var fs = CurFunc;
			var e = new ExpDesc();
			int nvars = 4; // gen, state, control, plus at least one declared var
			int save = fs.FreeReg;

			// create control variables
			ActVars.Add( NewLocalVar("(for generator)") );
			ActVars.Add( NewLocalVar("(for state)") );
			ActVars.Add( NewLocalVar("(for control)") );

			// create declared variables
			ActVars.Add( NewLocalVar( indexName ) );
			while( TestNext( (int)',' ) )
			{
				ActVars.Add( NewLocalVar( CheckName() ) );
				nvars++;
			}
			CheckNext( (int)TK.IN );
			int line = Lexer.LineNumber;
			AdjustAssign( 3, ExpList(e), e );
			Coder.CheckStack( fs, 3 ); // extra space to call generator
			ForBody( save, line, nvars-3, false );
		}

		// forstat -> FOR (fornum | forlist) END
		private void ForStat( int line )
		{
			var fs = CurFunc;
			var block = new BlockCnt();
			EnterBlock( fs, block, true );
			Lexer.Next(); // skip `for'
			string varname = CheckName();
			switch( Lexer.Token.TokenType )
			{
				case (int)'=': ForNum( varname, line ); break;
				case (int)',': case (int)TK.IN: ForList( varname ); break;
				default: Lexer.SyntaxError("'=' or 'in' expected"); break;
			}
			CheckMatch( (int)TK.END, (int)TK.FOR, line );
			LeaveBlock( fs );
		}

		// test_then_block -> [IF | ELSEIF] cond THEN block
		private int TestThenBlock( int escapeList )
		{
			var fs = CurFunc;
			var block = new BlockCnt();
			int jf; // instruction to skip `then' code (if condition is false)

			// skip IF or ELSEIF
			Lexer.Next();

			// read condition
			var v = new ExpDesc();
			Expr( v );

			CheckNext( (int)TK.THEN );
			if( Lexer.Token.TokenType == (int)TK.GOTO ||
				Lexer.Token.TokenType == (int)TK.BREAK )
			{
				// will jump to label if condition is true
				Coder.GoIfFalse( CurFunc, v );

				// must enter block before `goto'
				EnterBlock( fs, block, false );

				// handle goto/break
				GotoStat( v.ExitTrue );

				// skip other no-op statements
				SkipNoOpStat();

				// `goto' is the entire block?
				if( BlockFollow( false ) )
				{
					LeaveBlock( fs );
					return escapeList;
				}
				else
				{
					jf = Coder.Jump( fs );
				}
			}
			// regular case (not goto/break)
			else
			{
				// skip over block if condition is false
				Coder.GoIfTrue( CurFunc, v );
				EnterBlock( fs, block, false );
				jf = v.ExitFalse;
			}

			// `then' part
			StatList();
			LeaveBlock( fs );

			// followed by `else' or `elseif'
			if( Lexer.Token.TokenType == (int)TK.ELSE ||
				Lexer.Token.TokenType == (int)TK.ELSEIF )
			{
				// must jump over it
				escapeList = Coder.Concat( fs, escapeList, Coder.Jump(fs) );
			}
			Coder.PatchToHere( fs, jf );
			return escapeList;
		}

		// ifstat -> IF cond THEN block {ELSEIF cond THEN block} [ELSE block] END
		private void IfStat( int line )
		{
			var fs = CurFunc;

			// exit list for finished parts
			int escapeList = Coder.NO_JUMP;

			// IF cond THEN block
			escapeList = TestThenBlock( escapeList );

			// ELSEIF cond THEN block
			while( Lexer.Token.TokenType == (int)TK.ELSEIF )
				escapeList = TestThenBlock( escapeList );

			// `else' part
			if( TestNext( (int)TK.ELSE ) )
				Block();

			CheckMatch( (int)TK.END, (int)TK.IF, line );
			Coder.PatchToHere( fs, escapeList );
		}

		private void LocalFunc()
		{
			var b = new ExpDesc();
			var fs = CurFunc;
			var v = NewLocalVar(CheckName());
			ActVars.Add(v);
			AdjustLocalVars(1); /// enter its scope
			Body( b, false, Lexer.LineNumber );
			GetLocalVar( fs, b.Info ).StartPc = fs.Pc;
		}

		// stat -> LOCAL NAME {`,' NAME} [`=' explist]
		private void LocalStat()
		{
			int nvars = 0;
			int nexps;
			var e = new ExpDesc();
			do {
				var v = NewLocalVar( CheckName() );
				ActVars.Add(v);
				++nvars;
			} while( TestNext( (int)',' ) );

			if( TestNext( (int)'=' ) )
				nexps = ExpList( e );
			else
			{
				e.Kind = ExpKind.VVOID;
				nexps = 0;
			}
			AdjustAssign( nvars, nexps, e );
			AdjustLocalVars( nvars );
		}

		// funcname -> NAME {fieldsel} [`:' NAME]
		private bool FuncName( ExpDesc v )
		{
			SingleVar( v );
			while( Lexer.Token.TokenType == (int)'.' )
			{
				FieldSel( v );
			}
			if( Lexer.Token.TokenType == (int)':' )
			{
				FieldSel( v );
				return true; // is method
			}
			else
			{
				return false;
			}
		}

		// funcstat -> FUNCTION funcname BODY
		private void FuncStat( int line )
		{
			var v = new ExpDesc();
			var b = new ExpDesc();
			Lexer.Next();
			bool isMethod = FuncName( v );
			Body( b, isMethod, line );
			Coder.StoreVar( CurFunc, v, b );
			Coder.FixLine( CurFunc, line );
		}

		// stat -> func | assignment
		private void ExprStat()
		{
			var v = new LHSAssign();
			SuffixedExp( v.Exp );

			// stat -> assignment ?
			if( Lexer.Token.TokenType == (int)'=' ||
				Lexer.Token.TokenType == (int)',' )
			{
				v.Prev = null;
				Assignment( v, 1 );
			}

			// stat -> func
			else
			{
				if( v.Exp.Kind != ExpKind.VCALL )
					Lexer.SyntaxError("syntax error");

				var pi = CurFunc.GetCode( v.Exp );
				pi.Value = pi.Value.SETARG_C( 1 ); // call statment uses no results
			}
		}

		// stat -> RETURN [explist] [';']
		private void RetStat()
		{
			var fs = CurFunc;
			int first, nret; // registers with returned values
			if( BlockFollow( true ) || Lexer.Token.TokenType == (int)';' )
			{
				first = nret = 0; // return no values
			}
			else
			{
				var e = new ExpDesc();
				nret = ExpList( e );
				if( HasMultiRet( e.Kind ) )
				{
					Coder.SetMultiRet( fs, e );
					if( e.Kind == ExpKind.VCALL && nret == 1 ) // tail call?
					{
						var pi = fs.GetCode(e);
						pi.Value = pi.Value.SET_OPCODE( OpCode.OP_TAILCALL );
						Utl.Assert( pi.Value.GETARG_A() == fs.NumActVar );
					}
					first = fs.NumActVar;
					nret = LuaDef.LUA_MULTRET;
				}
				else
				{
					if( nret == 1 ) // only one single value
					{
						first = Coder.Exp2AnyReg( fs, e );
					}
					else
					{
						Coder.Exp2NextReg( fs, e ); // values must go to the `stack'
						first = fs.NumActVar;
						Utl.Assert( nret == fs.FreeReg - first );
					}
				}
			}
			Coder.Ret( fs, first, nret );
			TestNext( (int)';' ); // skip optional semicolon
		}

		private void Statement()
		{
			// Console.WriteLine("Statement ::" + Lexer.Token);
			int line = Lexer.LineNumber;
			EnterLevel();
			switch( Lexer.Token.TokenType )
			{
				case (int)';': {
					Lexer.Next();
					break;
				}

				// stat -> ifstat
				case (int)TK.IF: {
					IfStat( line );
					break;
				}

				// stat -> whilestat
				case (int)TK.WHILE: {
					WhileStat( line );
					break;
				}

				// stat -> DO block END
				case (int)TK.DO: {
					Lexer.Next();
					Block();
					CheckMatch( (int)TK.END, (int)TK.DO, line );
					break;
				}

				// stat -> forstat
				case (int)TK.FOR: {
					ForStat( line );
					break;
				}

				// stat -> repeatstat
				case (int)TK.REPEAT: {
					RepeatStat( line );
					break;
				}

				// stat -> funcstat
				case (int)TK.FUNCTION: {
					FuncStat( line );
					break;
				}

				// stat -> localstat
				case (int)TK.LOCAL: {
					Lexer.Next();

					// local function?
					if( TestNext( (int)TK.FUNCTION ) )
						LocalFunc();
					else
						LocalStat();
					break;
				}

				// stat -> label
				case (int)TK.DBCOLON: {
					Lexer.Next(); // skip double colon
					LabelStat( CheckName(), line );
					break;
				}

				// stat -> retstat
				case (int)TK.RETURN: {
					Lexer.Next(); // skip RETURN
					RetStat();
					break;
				}

				// stat -> breakstat
				// stat -> 'goto' NAME
				case (int)TK.BREAK:
				case (int)TK.GOTO: {
					GotoStat( Coder.Jump( CurFunc ) );
					break;
				}

				// stat -> func | assignment
				default:
					ExprStat();
					break;
			}
			// Console.WriteLine( "MaxStackSize: " + CurFunc.Proto.MaxStackSize );
			// Console.WriteLine( "FreeReg: " + CurFunc.FreeReg );
			// Console.WriteLine( "NumActVar: " + CurFunc.NumActVar );
			Utl.Assert( CurFunc.Proto.MaxStackSize >= CurFunc.FreeReg &&
						CurFunc.FreeReg >= CurFunc.NumActVar );
			CurFunc.FreeReg = CurFunc.NumActVar; // free registers
			LeaveLevel();
		}

		private string CheckName()
		{
			// Console.WriteLine( Lexer.Token );
			var t = Lexer.Token as NameToken;

			// TEST CODE
			if( t == null )
			{
				Console.WriteLine( Lexer.LineNumber + ":" + Lexer.Token );
			}
			string name = t.SemInfo;
			Lexer.Next();
			return name;
		}

		private int SearchVar( FuncState fs, string name )
		{
			for( int i=fs.NumActVar-1; i>=0; i-- )
			{
				if( name == GetLocalVar( fs, i ).VarName )
					return i;
			}
			return -1; // not found
		}

		private void MarkUpvalue( FuncState fs, int level )
		{
			var block = fs.Block;
			while( block.NumActVar > level ) block = block.Previous;
			block.HasUpValue = true;
		}

		private ExpKind SingleVarAux( FuncState fs, string name, ExpDesc e, bool flag )
		{
			if( fs == null )
				return ExpKind.VVOID;

			// look up locals at current level
			int v = SearchVar( fs, name );
			if( v >= 0 )
			{
				InitExp( e, ExpKind.VLOCAL, v );
				if( !flag )
					MarkUpvalue( fs, v ); // local will be used as an upval
				return ExpKind.VLOCAL;
			}

			// not found as local at current level; try upvalues
			int idx = SearchUpvalues( fs, name );
			if( idx < 0 ) // not found?
			{
				if( SingleVarAux( fs.Prev, name, e, false ) == ExpKind.VVOID )
					return ExpKind.VVOID; // not found; is a global
				idx = NewUpvalue( fs, name, e );
			}
			InitExp( e, ExpKind.VUPVAL, idx );
			return ExpKind.VUPVAL;
		}

		private void SingleVar( ExpDesc e )
		{
			string name = CheckName();
			if( SingleVarAux( CurFunc, name, e, true ) == ExpKind.VVOID )
			{
				ExpDesc key = new ExpDesc();
				SingleVarAux( CurFunc, LuaDef.LUA_ENV, e, true );
				Utl.Assert( e.Kind == ExpKind.VLOCAL ||
							e.Kind == ExpKind.VUPVAL );
				CodeString( key, name );
				Coder.Indexed( CurFunc, e, key );
			}
		}

		private void AdjustAssign( int nvars, int nexps, ExpDesc e )
		{
			var fs = CurFunc;
			int extra = nvars - nexps;
			if( HasMultiRet( e.Kind ) )
			{
				// includes call itself
				++extra;
				if( extra < 0 )
					extra = 0;
				Coder.SetReturns( fs, e, extra );
				if( extra > 1 )
					Coder.ReserveRegs( fs, extra-1 );
			}
			else
			{
				if( e.Kind != ExpKind.VVOID )
					Coder.Exp2NextReg( fs, e ); // close last expression
				if( extra > 0 )
				{
					int reg = fs.FreeReg;
					Coder.ReserveRegs( fs, extra );
					Coder.CodeNil( fs, reg, extra );
				}
			}
		}

		// check whether, in an assignment to an upvalue/local variable, the
		// upvalue/local variable is begin used in a previous assignment to a
		// table. If so, save original upvalue/local value in a safe place and
		// use this safe copy in the previous assignment.
		private void CheckConflict( LHSAssign lh, ExpDesc v )
		{
			var fs = CurFunc;

			// eventual position to save local variable
			int extra = fs.FreeReg;
			bool conflict = false;

			// check all previous assignments
			for( ; lh != null; lh = lh.Prev )
			{
				var e = lh.Exp;
				// assign to a table?
				if( e.Kind == ExpKind.VINDEXED )
				{
					// table is the upvalue/local being assigned now?
					if( e.Ind.Vt == v.Kind && e.Ind.T == v.Info )
					{
						conflict = true;
						e.Ind.Vt = ExpKind.VLOCAL;
						e.Ind.T  = extra; // previous assignment will use safe copy
					}
					// index is the local being assigned? (index cannot be upvalue)
					if( v.Kind == ExpKind.VLOCAL && e.Ind.Idx == v.Info )
					{
						conflict = true;
						e.Ind.Idx = extra; // previous assignment will use safe copy
					}
				}
			}
			if( conflict )
			{
				// copy upvalue/local value to a temporary (in position 'extra')
				var op = (v.Kind == ExpKind.VLOCAL) ? OpCode.OP_MOVE : OpCode.OP_GETUPVAL;
				Coder.CodeABC( fs, op, extra, v.Info, 0 );
				Coder.ReserveRegs( fs, 1 );
			}
		}

		// assignment -> ',' suffixedexp assignment
		private void Assignment( LHSAssign lh, int nvars )
		{
			CheckCondition( ExpKindUtl.VKIsVar( lh.Exp.Kind ), "syntax error" );
			ExpDesc e = new ExpDesc();

			if( TestNext( (int)',' ) )
			{
				var nv = new LHSAssign();
				nv.Prev = lh;
				SuffixedExp( nv.Exp );
				if( nv.Exp.Kind != ExpKind.VINDEXED )
					CheckConflict( lh, nv.Exp );
				CheckLimit( CurFunc, nvars + Lua.NumCSharpCalls,
					LuaLimits.LUAI_MAXCCALLS, "C# levels" );
				Assignment( nv, nvars+1 );
			}
			else
			{
				CheckNext( (int)'=' );

				int nexps = ExpList( e );
				if( nexps != nvars )
				{
					AdjustAssign( nvars, nexps, e );
					if( nexps > nvars )
					{
						// remove extra values
						CurFunc.FreeReg -= (nexps - nvars);
					}
				}
				else
				{
					Coder.SetOneRet( CurFunc, e );
					Coder.StoreVar( CurFunc, lh.Exp, e );
					return;
				}
			}

			// default assignment
			InitExp( e, ExpKind.VNONRELOC, CurFunc.FreeReg-1 );
			Coder.StoreVar( CurFunc, lh.Exp, e );
		}

		private int ExpList( ExpDesc e )
		{
			int n = 1; // at least one expression
			Expr( e );
			while( TestNext( (int)',' ) )
			{
				Coder.Exp2NextReg( CurFunc, e );
				Expr( e );
				n++;
			}
			return n;
		}
		
		private void Expr( ExpDesc e )
		{
			SubExpr( e, 0 );
		}

		private BinOpr SubExpr( ExpDesc e, int limit )
		{
			// Console.WriteLine("SubExpr limit:" + limit);
			EnterLevel();
			UnOpr uop = GetUnOpr( Lexer.Token.TokenType );
			if( uop != UnOpr.NOUNOPR )
			{
				int line = Lexer.LineNumber;
				Lexer.Next();
				SubExpr( e, UnaryPrior );
				Coder.Prefix( CurFunc, uop, e, line );
			}
			else SimpleExp( e );

			// expand while operators have priorities higher than `limit'
			BinOpr op = GetBinOpr(Lexer.Token.TokenType );
			while( op != BinOpr.NOBINOPR && GetBinOprLeftPrior( op ) > limit )
			{
				// Console.WriteLine("op:" + op);
				int line = Lexer.LineNumber;
				Lexer.Next();
				Coder.Infix( CurFunc, op, e );

				// read sub-expression with higher priority
				ExpDesc e2 = new ExpDesc();
				BinOpr nextOp = SubExpr( e2, GetBinOprRightPrior( op ) );
				Coder.Posfix( CurFunc, op, e, e2, line );
				op = nextOp;
			}

			LeaveLevel();
			return op;
		}

		private bool HasMultiRet( ExpKind k )
		{
			return k == ExpKind.VCALL || k == ExpKind.VVARARG;
		}

		private void ErrorExpected( int token )
		{
			Lexer.SyntaxError( string.Format( "{0} expected",
				((char)token).ToString() ) );
		}

		private void CheckMatch( int what, int who, int where )
		{
			if( !TestNext( what ) )
			{
				if( where == Lexer.LineNumber )
					ErrorExpected( what );
				else
					Lexer.SyntaxError( string.Format(
						"{0} expected (to close {1} at line {2})",
						((char)what).ToString(),
						((char)who).ToString(),
						where ) );
			}
		}

		// block -> statlist
		private void Block()
		{
			var fs = CurFunc;
			var block = new BlockCnt();
			EnterBlock( fs, block, false );
			StatList();
			LeaveBlock( fs );
		}

		// index -> '[' expr ']'
		private void YIndex( ExpDesc v )
		{
			Lexer.Next();
			Expr( v );
			Coder.Exp2Val( CurFunc, v );
			CheckNext( (int)']' );
		}

		// recfield -> (NAME | '[' exp1 ']') = exp1
		private void RecField( ConstructorControl cc )
		{
			var fs = CurFunc;
			int reg = fs.FreeReg;
			var key = new ExpDesc();
			var val = new ExpDesc();
			if( Lexer.Token.TokenType == (int)TK.NAME )
			{
				CheckLimit( fs, cc.NumRecord, LuaLimits.MAX_INT,
					"items in a constructor" );
				CodeString( key, CheckName() );
			}
			// ls->t.token == '['
			else
			{
				YIndex( key );
			}
			cc.NumRecord++;
			CheckNext( (int)'=' );
			int rkkey = Coder.Exp2RK( fs, key );
			Expr( val );
			Coder.CodeABC( fs, OpCode.OP_SETTABLE, cc.ExpTable.Info, rkkey,
				Coder.Exp2RK( fs, val ) );
			fs.FreeReg = reg; // free registers
		}

		private void CloseListField( FuncState fs, ConstructorControl cc )
		{
			// there is no list item
			if( cc.ExpLastItem.Kind == ExpKind.VVOID )
				return;

			Coder.Exp2NextReg( fs, cc.ExpLastItem );
			cc.ExpLastItem.Kind = ExpKind.VVOID;
			if( cc.NumToStore == LuaDef.LFIELDS_PER_FLUSH )
			{
				// flush
				Coder.SetList( fs, cc.ExpTable.Info, cc.NumArray, cc.NumToStore );

				// no more item pending
				cc.NumToStore = 0;
			}
		}

		private void LastListField( FuncState fs, ConstructorControl cc )
		{
			if( cc.NumToStore == 0 )
				return;

			if( HasMultiRet( cc.ExpLastItem.Kind ) )
			{
				Coder.SetMultiRet( fs, cc.ExpLastItem );
				Coder.SetList( fs, cc.ExpTable.Info, cc.NumArray, LuaDef.LUA_MULTRET );

				// do not count last expression (unknown number of elements)
				cc.NumArray--;
			}
			else
			{
				if( cc.ExpLastItem.Kind != ExpKind.VVOID )
					Coder.Exp2NextReg( fs, cc.ExpLastItem );
				Coder.SetList( fs, cc.ExpTable.Info, cc.NumArray, cc.NumToStore );
			}
		}

		// listfield -> exp
		private void ListField( ConstructorControl cc )
		{
			Expr( cc.ExpLastItem );
			CheckLimit( CurFunc, cc.NumArray, LuaLimits.MAX_INT,
				"items in a constructor" );
			cc.NumArray++;
			cc.NumToStore++;
		}

		// field -> listfield | recfield
		private void Field( ConstructorControl cc )
		{
			switch( Lexer.Token.TokenType )
			{
				// may be 'listfield' or 'recfield'
				case (int)TK.NAME: {
					// expression?
					if( Lexer.GetLookAhead().TokenType != (int)'=' )
						ListField( cc );
					else
						RecField( cc );
					break;
				}

				case (int)'[': {
					RecField( cc );
					break;
				}

				default: {
					ListField( cc );
					break;
				}
			}
		}

		// 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.
		private int Integer2FloatingPointByte(uint x)
		{
			int e = 0; // exponent
			if( x < 8 )
				return (int)x;
			while( x >= 0x10 )
			{
				x = (x+1) >> 1;
				++e;
			}
			return ((e+1) << 3) | ((int)x - 8);
		}

		// constructor -> '{' [ field { sep field } [sep] ] '}'
		// sep -> ',' | ';'
		private void Constructor( ExpDesc t )
		{
			var fs = CurFunc;
			int line = Lexer.LineNumber;
			int pc = Coder.CodeABC( fs, OpCode.OP_NEWTABLE, 0, 0, 0 );
			var cc = new ConstructorControl();
			cc.ExpTable = t;
			InitExp( t, ExpKind.VRELOCABLE, pc );
			InitExp( cc.ExpLastItem, ExpKind.VVOID, 0); // no value (yet)
			Coder.Exp2NextReg( fs, t );
			CheckNext( (int)'{' );
			do {
				Utl.Assert( cc.ExpLastItem.Kind == ExpKind.VVOID ||
							cc.NumToStore > 0 );
				if( Lexer.Token.TokenType == (int)'}' )
					break;
				CloseListField( fs, cc );
				Field( cc );
			} while( TestNext( (int)',' ) || TestNext( (int)';' ) );
			CheckMatch( (int)'}', (int)'{', line );
			LastListField( fs, cc );

			// set initial array size and table size
			//        OP_NEWTABLE   ARG_B   ARG_C    ,          
			//      ,       
			// var ins = fs.Proto.Code[pc];
			// ins.SETARG_B( 0 );
			// ins.SETARG_C( 0 );

			//   ,      ,         bytecode     luac   
			var ins = fs.Proto.Code[pc];
			ins.SETARG_B( Integer2FloatingPointByte( (uint)cc.NumArray ) );
			ins.SETARG_C( Integer2FloatingPointByte( (uint)cc.NumRecord) );
			fs.Proto.Code[pc] = ins; // Instruction        
		}

		private void ParList()
		{
			int numParams = 0;
			CurFunc.Proto.IsVarArg = false;

			// is `parlist' not empty?
			if( Lexer.Token.TokenType != (int)')' )
			{
				do {
					switch( Lexer.Token.TokenType )
					{
						// param -> NAME
						case (int)TK.NAME: {
							var v =NewLocalVar( CheckName() );
							ActVars.Add( v );
							++numParams;
							break;
						}

						case (int)TK.DOTS: {
							Lexer.Next();
							CurFunc.Proto.IsVarArg = true;
							break;
						}

						default: {
							Lexer.SyntaxError("<name> or '...' expected");
							break;
						}
					}
				} while( !CurFunc.Proto.IsVarArg && TestNext( (int)',' ) );
			}
			AdjustLocalVars( numParams );
			CurFunc.Proto.NumParams = CurFunc.NumActVar;
			Coder.ReserveRegs( CurFunc, CurFunc.NumActVar );
		}

		private void Body( ExpDesc e, bool isMethod, int line )
		{
			var newFs = new FuncState();
			var block = new BlockCnt();
			newFs.Proto = AddPrototype();
			newFs.Proto.LineDefined = line;
			OpenFunc( newFs, block );
			CheckNext( (int)'(' );
			if( isMethod )
			{
				// create `self' parameter
				var v = NewLocalVar("self");
				ActVars.Add(v);
				AdjustLocalVars(1);
			}
			ParList();
			CheckNext( (int)')' );
			StatList();
			newFs.Proto.LastLineDefined = Lexer.LineNumber;
			CheckMatch( (int)TK.END, (int)TK.FUNCTION, line );
			CodeClosure(e);
			CloseFunc();
		}

		private void FuncArgs( ExpDesc e, int line )
		{
			var args = new ExpDesc();
			switch( Lexer.Token.TokenType )
			{
				// funcargs -> `(' [ explist ] `)'
				case (int)'(': {
					Lexer.Next();
					if( Lexer.Token.TokenType == ')' ) // arg list is empty?
						args.Kind = ExpKind.VVOID;
					else {
						ExpList( args );
						Coder.SetMultiRet( CurFunc, args );
					}
					CheckMatch( (int)')', (int)'(', line );
					break;
				}

				// funcargs -> constructor
				case (int)'{': {
					Constructor( args );
					break;
				}

				// funcargs -> STRING
				case (int)TK.STRING: {
					var st = Lexer.Token as StringToken;
					CodeString( args, st.SemInfo );
					Lexer.Next();
					break;
				}

				default: {
					Lexer.SyntaxError( "function arguments expected" );
					break;
				}
			}

			Utl.Assert( e.Kind == ExpKind.VNONRELOC );
			int baseReg = e.Info;
			int nparams;
			if( HasMultiRet( args.Kind ) )
				nparams = LuaDef.LUA_MULTRET;
			else {
				if( args.Kind != ExpKind.VVOID )
					Coder.Exp2NextReg( CurFunc, args ); // close last argument
				nparams = CurFunc.FreeReg - (baseReg+1);
			}
			InitExp( e, ExpKind.VCALL, Coder.CodeABC( CurFunc,
				OpCode.OP_CALL, baseReg, nparams+1, 2 ) );
			Coder.FixLine( CurFunc, line );

			// call remove function and arguments and leaves
			// (unless changed) one result
			CurFunc.FreeReg = baseReg + 1;
		}

		// ==============================================================
		// Expression parsing
		// ==============================================================

		// primaryexp -> NAME | '(' expr ')'
		private void PrimaryExp( ExpDesc e )
		{
			switch( Lexer.Token.TokenType )
			{
				case (int)'(': {
					int line = Lexer.LineNumber;
					Lexer.Next();
					Expr( e );
					CheckMatch( (int)')', (int)'(', line );
					Coder.DischargeVars( CurFunc, e );
					return;
				}

				case (int)TK.NAME: {
					SingleVar( e );
					return;
				}

				default: {
					Lexer.SyntaxError("unexpected symbol");
					return;
				}
			}
		}

		// suffixedexp -> primaryexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs
		private void SuffixedExp( ExpDesc e )
		{
			var fs = CurFunc;
			int line = Lexer.LineNumber;
			PrimaryExp( e );
			for(;;)
			{
				switch( Lexer.Token.TokenType )
				{
					case (int)'.': { // fieldsel
						FieldSel( e );
						break;
					}
					case (int)'[': { // `[' exp1 `]'
						var key = new ExpDesc();
						Coder.Exp2AnyRegUp( fs, e );
						YIndex( key );
						Coder.Indexed( fs, e, key );
						break;
					}
					case (int)':': { // `:' NAME funcargs
						var key = new ExpDesc();
						Lexer.Next();
						CodeString( key, CheckName() );
						Coder.Self( fs, e, key );
						FuncArgs( e, line );
						break;
					}
					case (int)'(':
					case (int)TK.STRING:
					case (int)'{': { // funcargs
						Coder.Exp2NextReg( CurFunc, e );
						FuncArgs( e, line );
						break;
					}
					default: return;
				}
			}
		}

		private void SimpleExp( ExpDesc e )
		{
			var t = Lexer.Token;
			switch( t.TokenType )
			{
				case (int)TK.NUMBER: {
					var nt = t as NumberToken;
					InitExp( e, ExpKind.VKNUM, 0 );
					e.NumberValue = nt.SemInfo;
					break;
				}

				case (int)TK.STRING: {
					var st = t as StringToken;
					CodeString( e, st.SemInfo );
					break;
				}

				case (int)TK.NIL: {
					InitExp( e, ExpKind.VNIL, 0 );
					break;
				}

				case (int)TK.TRUE: {
					InitExp( e, ExpKind.VTRUE, 0 );
					break;
				}

				case (int)TK.FALSE: {
					InitExp( e, ExpKind.VFALSE, 0 );
					break;
				}

				case (int)TK.DOTS: {
					CheckCondition( CurFunc.Proto.IsVarArg,
						"cannot use '...' outside a vararg function" );
					InitExp( e, ExpKind.VVARARG,
						Coder.CodeABC( CurFunc, OpCode.OP_VARARG, 0, 1, 0 ) );
					break;
				}

				case (int)'{': {
					Constructor( e );
					return;
				}

				case (int)TK.FUNCTION: {
					Lexer.Next();
					Body( e, false, Lexer.LineNumber );
					return;
				}

				default: {
					SuffixedExp( e );
					return;
				}
			}
			Lexer.Next();
		}

		private int SearchUpvalues( FuncState fs, string name )
		{
			var upvalues = fs.Proto.Upvalues;
			for( int i=0; i< upvalues.Count; ++i )
			{
				if( upvalues[i].Name == name )
					return i;
			}
			return -1;
		}

		private int NewUpvalue( FuncState fs, string name, ExpDesc e )
		{
			var f = fs.Proto;
			int idx = f.Upvalues.Count;
			var upval = new UpvalDesc();
			upval.InStack = (e.Kind == ExpKind.VLOCAL);
			upval.Index = e.Info;
			upval.Name = name;
			f.Upvalues.Add( upval );
			return idx;
		}

		private void CodeString( ExpDesc e, string s )
		{
			InitExp( e, ExpKind.VK, Coder.StringK( CurFunc, s) );
		}

		private void InitExp( ExpDesc e, ExpKind k, int i )
		{
			e.Kind = k;
			e.Info = i;
			e.ExitTrue = Coder.NO_JUMP;
			e.ExitFalse = Coder.NO_JUMP;
		}
	}

}

