using System; using System.IO; using System.Collections.Generic; using ULDebug = UniLua.Tools.ULDebug; namespace UniLua { using InstructionPtr = Pointer; public class FuncState { public FuncState Prev; public BlockCnt Block; public LuaProto Proto; public LuaState State; public LLex Lexer; public Dictionary 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(); 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 ActVars; private List PendingGotos; private List ActiveLabels; private LuaState Lua; private Parser() { ActVars = new List(); PendingGotos = new List(); ActiveLabels = new List(); 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( " 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 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 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 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(" 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; } } }