diff --git a/Shell.cs b/Shell.cs index 30ef5f7..17739f5 100644 --- a/Shell.cs +++ b/Shell.cs @@ -13,6 +13,7 @@ using System.Text; using CosmosHttp.Client; using Cosmos.Core; using Cosmos.Core.Memory; +using UniLua; using Cosmos.HAL; namespace CMLeonOS @@ -199,6 +200,7 @@ namespace CMLeonOS " whoami - Show current username", " base64 encrypt - Encode text to Base64", " base64 decrypt - Decode Base64 to text", + " lua - Execute Lua script", " version - Show OS version", " about - Show about information", " help - Show help page (1-3)", @@ -465,6 +467,9 @@ namespace CMLeonOS case "base64": ProcessBase64Command(args); break; + case "lua": + ExecuteLuaScript(args); + break; default: ShowError($"Unknown command: {command}"); break; @@ -2083,5 +2088,81 @@ namespace CMLeonOS ShowError($"Base64 error: {ex.Message}"); } } + + private void ExecuteLuaScript(string args) + { + string[] parts = args.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + + if (parts.Length == 0) + { + ShowError("Error: Please specify Lua script file"); + ShowError("Usage: lua "); + return; + } + + string filePath = parts[0]; + string originalPath = filePath; + + if (!filePath.StartsWith("0:\\") && !filePath.StartsWith("0:/")) + { + if (prompt == "/" || prompt == "\\") + { + filePath = "0:\\" + filePath.TrimStart('/').TrimStart('\\'); + } + else + { + filePath = Path.Combine(prompt, filePath); + } + } + + if (!File.Exists(filePath)) + { + ShowError($"Error: File not found: {filePath}"); + return; + } + + // Console.WriteLine($"Executing: {filePath}"); + // Console.WriteLine(); + + try + { + string scriptContent = File.ReadAllText(filePath); + + if (string.IsNullOrWhiteSpace(scriptContent)) + { + ShowWarning("Script file is empty"); + return; + } + + ILuaState lua = LuaAPI.NewState(); + lua.L_OpenLibs(); + + UniLua.ThreadStatus loadResult = lua.L_LoadString(scriptContent); + + if (loadResult == UniLua.ThreadStatus.LUA_OK) + { + UniLua.ThreadStatus callResult = lua.PCall(0, 0, 0); + + if (callResult == UniLua.ThreadStatus.LUA_OK) + { + // ShowSuccess("Script run successfully"); + } + else + { + string errorMsg = lua.ToString(-1); + ShowError($"Script execution error: {errorMsg}"); + } + } + else + { + string errorMsg = lua.ToString(-1); + ShowError($"Script load error: {errorMsg}"); + } + } + catch (Exception ex) + { + ShowError($"Lua execution error: {ex.Message}"); + } + } } } \ No newline at end of file diff --git a/UniLua/ByteString.cs b/UniLua/ByteString.cs new file mode 100644 index 0000000..c18d544 --- /dev/null +++ b/UniLua/ByteString.cs @@ -0,0 +1,47 @@ + using System; +using System.Collections.Generic; + +namespace UniLua +{ + internal class ByteStringBuilder + { + public ByteStringBuilder() + { + BufList = new LinkedList(); + TotalLength = 0; + } + + public override string ToString() + { + if( TotalLength <= 0 ) + return String.Empty; + + var result = new char[TotalLength]; + var i = 0; + var node = BufList.First; + while(node != null) + { + var buf = node.Value; + for(var j=0; j BufList; + private int TotalLength; + } +} + diff --git a/UniLua/Coder.cs b/UniLua/Coder.cs new file mode 100644 index 0000000..1622576 --- /dev/null +++ b/UniLua/Coder.cs @@ -0,0 +1,1283 @@ + +using NotImplementedException = System.NotImplementedException; + +namespace UniLua +{ + using InstructionPtr = Pointer; + using Math = System.Math; + using Exception = System.Exception; + + public struct Instruction + { + public uint Value; + + public Instruction( uint val ) + { + Value = val; + } + + public static explicit operator Instruction( uint val ) + { + return new Instruction(val); + } + + public static explicit operator uint( Instruction i ) + { + return i.Value; + } + + public override string ToString() + { + var op = GET_OPCODE(); + var a = GETARG_A(); + var b = GETARG_B(); + var c = GETARG_C(); + var ax = GETARG_Ax(); + var bx = GETARG_Bx(); + var sbx = GETARG_sBx(); + var mode = OpCodeInfo.GetMode( op ); + switch( mode.OpMode ) + { + case OpMode.iABC: + { + string ret = string.Format( "{0,-9} {1}" + , op + , a ); + if( mode.BMode != OpArgMask.OpArgN ) + ret += " " + (ISK(b) ? MYK(INDEXK(b)) : b); + if( mode.CMode != OpArgMask.OpArgN ) + ret += " " + (ISK(c) ? MYK(INDEXK(c)) : c); + return ret; + } + case OpMode.iABx: + { + string ret = string.Format( "{0,-9} {1}" + , op + , a ); + if( mode.BMode == OpArgMask.OpArgK ) + ret += " " + MYK(bx); + else if( mode.BMode == OpArgMask.OpArgU ) + ret += " " + bx; + return ret; + } + case OpMode.iAsBx: + { + return string.Format( "{0,-9} {1} {2}" + , op + , a + , sbx ); + } + case OpMode.iAx: + { + return string.Format( "{0,-9} {1}" + , op + , MYK(ax) ); + } + default: + throw new System.NotImplementedException(); + } + } + + public const int SIZE_C = 9; + public const int SIZE_B = 9; + public const int SIZE_Bx = (SIZE_C + SIZE_B); + public const int SIZE_A = 8; + public const int SIZE_Ax = (SIZE_C + SIZE_B + SIZE_A); + + public const int SIZE_OP = 6; + + public const int POS_OP = 0; + public const int POS_A = (POS_OP + SIZE_OP); + public const int POS_C = (POS_A + SIZE_A); + public const int POS_B = (POS_C + SIZE_C); + public const int POS_Bx = POS_C; + public const int POS_Ax = POS_A; + +#pragma warning disable 0429 + public const int MAXARG_Bx = SIZE_Bx>1) + : LuaLimits.MAX_INT + ; +#pragma warning restore 0429 + + public const int MAXARG_Ax = ((1<> POS_OP) & MASK1(SIZE_OP, 0) ); + } + + public Instruction SET_OPCODE( OpCode op ) + { + Value = (Value & MASK0(SIZE_OP, POS_OP)) | + ((((uint)op) << POS_OP) & MASK1(SIZE_OP, POS_OP)); + return this; + } + + public int GETARG( int pos, int size ) + { + return (int)( (Value >> pos) & MASK1(size, 0) ); + } + + public Instruction SETARG( int value, int pos, int size ) + { + Value = ((Value & MASK0(size, pos)) | + (((uint)value << pos) & MASK1(size, pos))); + return this; + } + + public int GETARG_A() { return GETARG( POS_A, SIZE_A ); } + public Instruction SETARG_A(int value) { + return SETARG( value, POS_A, SIZE_A ); + } + + public int GETARG_B() { return GETARG( POS_B, SIZE_B ); } + public Instruction SETARG_B(int value) { + return SETARG( value, POS_B, SIZE_B ); + } + + public int GETARG_C() { return GETARG( POS_C, SIZE_C ); } + public Instruction SETARG_C(int value) { + return SETARG( value, POS_C, SIZE_C ); + } + + public int GETARG_Bx() { return GETARG( POS_Bx, SIZE_Bx ); } + public Instruction SETARG_Bx(int value) { + return SETARG( value, POS_Bx, SIZE_Bx ); + } + + public int GETARG_Ax() { return GETARG( POS_Ax, SIZE_Ax ); } + public Instruction SETARG_Ax(int value) { + return SETARG( value, POS_Ax, SIZE_Ax ); + } + + public int GETARG_sBx() { return GETARG_Bx() - MAXARG_sBx; } + public Instruction SETARG_sBx(int value) { + return SETARG_Bx(value+MAXARG_sBx); + } + + public static Instruction CreateABC( OpCode op, int a, int b, int c ) + { + return (Instruction)( (((uint)op) << POS_OP) + | ((uint)a << POS_A) + | ((uint)b << POS_B) + | ((uint)c << POS_C)); + } + + public static Instruction CreateABx( OpCode op, int a, uint bc ) + { + return (Instruction)( (((uint)op) << POS_OP) + | ((uint)a << POS_A) + | ((uint)bc << POS_Bx)); + } + + public static Instruction CreateAx( OpCode op, int a ) + { + return (Instruction)( (((uint)op) << POS_OP) + | ((uint)a << POS_Ax)); + } + } + + public static class Coder + { + public const int NO_JUMP = -1; + private const int NO_REG = ((1<= fs.NumActVar ) + { + fs.FreeReg--; + Utl.Assert( reg == fs.FreeReg ); + } + } + + private static void FreeExp( FuncState fs, ExpDesc e ) + { + if( e.Kind == ExpKind.VNONRELOC ) + { + FreeReg( fs, e.Info ); + } + } + + private static bool IsNumeral( ExpDesc e ) + { + return e.Kind == ExpKind.VKNUM + && e.ExitTrue == NO_JUMP + && e.ExitFalse == NO_JUMP; + } + + private static bool ConstFolding( OpCode op, ExpDesc e1, ExpDesc e2 ) + { + if( !IsNumeral(e1) || !IsNumeral(e2) ) + return false; + + if( (op == OpCode.OP_DIV || op == OpCode.OP_MOD) + && e2.NumberValue == 0.0 ) + { + return false; // do not attempt to divide by 0 + } + + switch( op ) + { + case OpCode.OP_ADD: + e1.NumberValue = e1.NumberValue + e2.NumberValue; + break; + case OpCode.OP_SUB: + e1.NumberValue = e1.NumberValue - e2.NumberValue; + break; + case OpCode.OP_MUL: + e1.NumberValue = e1.NumberValue * e2.NumberValue; + break; + case OpCode.OP_DIV: + e1.NumberValue = e1.NumberValue / e2.NumberValue; + break; + case OpCode.OP_MOD: + var v1 = e1.NumberValue; + var v2 = e2.NumberValue; + e1.NumberValue = v1 - Math.Floor(v1/v2) * v2; + break; + case OpCode.OP_POW: + e1.NumberValue = Math.Pow( e1.NumberValue, e2.NumberValue ); + break; + case OpCode.OP_UNM: + e1.NumberValue = -e1.NumberValue; + break; + default: + throw new Exception("ConstFolding unknown op" + op); + } + + return true; + } + + public static void FixLine( FuncState fs, int line ) + { + fs.Proto.LineInfo[ fs.Pc-1 ] = line; + } + + private static void CodeArith( FuncState fs, OpCode op, + ExpDesc e1, ExpDesc e2, int line ) + { + if( ConstFolding( op, e1, e2 ) ) + return; + + int o2 = ( op != OpCode.OP_UNM && op != OpCode.OP_LEN ) + ? Exp2RK( fs, e2 ) : 0; + int o1 = Exp2RK( fs, e1 ); + if( o1 > o2 ) + { + FreeExp( fs, e1 ); + FreeExp( fs, e2 ); + } + else + { + FreeExp( fs, e2 ); + FreeExp( fs, e1 ); + } + e1.Info = CodeABC( fs, op, 0, o1, o2 ); + e1.Kind = ExpKind.VRELOCABLE; + FixLine( fs, line ); + } + + public static bool TestTMode( OpCode op ) + { + return OpCodeInfo.GetMode( op ).TMode; + } + + public static bool TestAMode( OpCode op ) + { + return OpCodeInfo.GetMode( op ).AMode; + } + + private static void FixJump( FuncState fs, int pc, int dest ) + { + Instruction jmp = fs.Proto.Code[pc]; + int offset = dest - (pc + 1); + Utl.Assert( dest != NO_JUMP ); + if( Math.Abs(offset) > Instruction.MAXARG_sBx ) + fs.Lexer.SyntaxError("control structure too long"); + jmp.SETARG_sBx( offset ); + fs.Proto.Code[pc] = jmp; + } + + // returns current `pc' and mark it as a jump target + // (to avoid wrong optimizations with consecutive + // instructions not in the same basic block) + public static int GetLabel( FuncState fs ) + { + fs.LastTarget = fs.Pc; + return fs.Pc; + } + + private static int GetJump( FuncState fs, int pc ) + { + int offset = fs.Proto.Code[pc].GETARG_sBx(); + if( offset == NO_JUMP ) // point to itself represents end of list + return NO_JUMP; // end of list + else + return (pc+1) + offset; // turn offset into absolute position + } + + private static InstructionPtr GetJumpControl( FuncState fs, int pc ) + { + InstructionPtr pi = new InstructionPtr( fs.Proto.Code, pc ); + if( pc >= 1 && TestTMode( (pi-1).Value.GET_OPCODE() )) + return (pi-1); + else + return pi; + } + + // check whether list has any jump that do not produce a value + // (or produce an inverted value) + private static bool NeedValue( FuncState fs, int list ) + { + for( ; list != NO_JUMP; list = GetJump( fs, list ) ) + { + Instruction i = GetJumpControl( fs, list ).Value; + if( i.GET_OPCODE() != OpCode.OP_TESTSET ) + return true; + } + return false; + } + + private static bool PatchTestReg( FuncState fs, int node, int reg ) + { + InstructionPtr pi = GetJumpControl( fs, node ); + if( pi.Value.GET_OPCODE() != OpCode.OP_TESTSET ) + return false; // cannot patch other instructions + + if( reg != NO_REG && reg != pi.Value.GETARG_B() ) + pi.Value = pi.Value.SETARG_A( reg ); + else + pi.Value = Instruction.CreateABC( OpCode.OP_TEST, + pi.Value.GETARG_B(), 0, pi.Value.GETARG_C() ); + + return true; + } + + private static void RemoveValues( FuncState fs, int list ) + { + for(; list != NO_JUMP; list = GetJump( fs, list ) ) + PatchTestReg( fs, list, NO_REG ); + } + + private static void PatchListAux( FuncState fs, int list, int vtarget, + int reg, int dtarget ) + { + while( list != NO_JUMP ) + { + int next = GetJump( fs, list ); + if( PatchTestReg( fs, list, reg ) ) + FixJump( fs, list, vtarget ); + else + FixJump( fs, list, dtarget ); // jump to default target + list = next; + } + } + + private static void DischargeJpc( FuncState fs ) + { + PatchListAux( fs, fs.Jpc, fs.Pc, NO_REG, fs.Pc ); + fs.Jpc = NO_JUMP; + } + + private static void InvertJump( FuncState fs, ExpDesc e ) + { + InstructionPtr pc = GetJumpControl( fs, e.Info ); + Utl.Assert( TestTMode( pc.Value.GET_OPCODE() ) + && pc.Value.GET_OPCODE() != OpCode.OP_TESTSET + && pc.Value.GET_OPCODE() != OpCode.OP_TEST ); + pc.Value = pc.Value.SETARG_A( pc.Value.GETARG_A() == 0 ? 1 : 0 ); + } + + private static int JumpOnCond( FuncState fs, ExpDesc e, bool cond ) + { + if( e.Kind == ExpKind.VRELOCABLE ) + { + Instruction ie = fs.GetCode( e ).Value; + if( ie.GET_OPCODE() == OpCode.OP_NOT ) + { + fs.Pc--; // remove previous OP_NOT + return CondJump( fs, OpCode.OP_TEST, ie.GETARG_B(), 0, + (cond ? 0 : 1) ); + } + // else go through + } + Discharge2AnyReg( fs, e ); + FreeExp( fs, e ); + return CondJump( fs, OpCode.OP_TESTSET, NO_REG, e.Info, + (cond ? 1 : 0) ); + } + + public static void GoIfTrue( FuncState fs, ExpDesc e ) + { + int pc; // pc of last jump + DischargeVars( fs, e ); + switch( e.Kind ) + { + case ExpKind.VJMP: + InvertJump( fs, e ); + pc = e.Info; + break; + + case ExpKind.VK: + case ExpKind.VKNUM: + case ExpKind.VTRUE: + pc = NO_JUMP; + break; + + default: + pc = JumpOnCond( fs, e, false ); + break; + } + + // insert last jump in `f' list + e.ExitFalse = Concat( fs, e.ExitFalse, pc ); + PatchToHere( fs, e.ExitTrue ); + e.ExitTrue = NO_JUMP; + } + + public static void GoIfFalse( FuncState fs, ExpDesc e ) + { + int pc; // pc of last jump + DischargeVars( fs, e ); + switch( e.Kind ) + { + case ExpKind.VJMP: + pc = e.Info; + break; + + case ExpKind.VNIL: + case ExpKind.VFALSE: + pc = NO_JUMP; + break; + + default: + pc = JumpOnCond( fs, e, true ); + break; + } + + // insert last jump in `t' list + e.ExitTrue = Concat( fs, e.ExitTrue, pc ); + PatchToHere( fs, e.ExitFalse ); + e.ExitFalse = NO_JUMP; + } + + private static void CodeNot( FuncState fs, ExpDesc e ) + { + DischargeVars( fs, e ); + switch( e.Kind ) + { + case ExpKind.VNIL: + case ExpKind.VFALSE: + e.Kind = ExpKind.VTRUE; + break; + + case ExpKind.VK: + case ExpKind.VKNUM: + case ExpKind.VTRUE: + e.Kind = ExpKind.VFALSE; + break; + + case ExpKind.VJMP: + InvertJump( fs, e ); + break; + + case ExpKind.VRELOCABLE: + case ExpKind.VNONRELOC: + Discharge2AnyReg( fs, e ); + FreeExp( fs, e ); + e.Info = CodeABC( fs, OpCode.OP_NOT, 0, e.Info, 0 ); + e.Kind = ExpKind.VRELOCABLE; + break; + + default: + throw new Exception("CodeNot unknown e.Kind:" + e.Kind); + } + + // interchange true and false lists + { int temp = e.ExitFalse; e.ExitFalse = e.ExitTrue; e.ExitTrue = temp; } + + RemoveValues( fs, e.ExitFalse ); + RemoveValues( fs, e.ExitTrue ); + } + + private static void CodeComp( FuncState fs, OpCode op, int cond, + ExpDesc e1, ExpDesc e2 ) + { + int o1 = Exp2RK( fs, e1 ); + int o2 = Exp2RK( fs, e2 ); + FreeExp( fs, e2 ); + FreeExp( fs, e1 ); + + // exchange args to replace by `<' or `<=' + if( cond == 0 && op != OpCode.OP_EQ ) { + int temp; + temp = o1; o1 = o2; o2 = temp; // o1 <==> o2 + cond = 1; + } + e1.Info = CondJump( fs, op, cond, o1, o2 ); + e1.Kind = ExpKind.VJMP; + } + + public static void Prefix( FuncState fs, UnOpr op, ExpDesc e, int line ) + { + ExpDesc e2 = new ExpDesc(); + e2.ExitTrue = NO_JUMP; + e2.ExitFalse = NO_JUMP; + e2.Kind = ExpKind.VKNUM; + e2.NumberValue = 0.0; + + switch( op ) + { + case UnOpr.MINUS: { + if( IsNumeral( e ) ) // minus constant? + { + e.NumberValue = -e.NumberValue; + } + else + { + Exp2AnyReg( fs, e ); + CodeArith( fs, OpCode.OP_UNM, e, e2, line ); + } + } break; + + case UnOpr.NOT: { + CodeNot( fs, e ); + } break; + + case UnOpr.LEN: { + Exp2AnyReg( fs, e ); // cannot operate on constants + CodeArith( fs, OpCode.OP_LEN, e, e2, line ); + } break; + + default: + throw new Exception("[Coder]Prefix Unknown UnOpr:" + op); + } + } + + public static void Infix( FuncState fs, BinOpr op, ExpDesc e ) + { + switch( op ) + { + case BinOpr.AND: { + GoIfTrue( fs, e ); + } break; + + case BinOpr.OR: { + GoIfFalse( fs, e ); + } break; + + case BinOpr.CONCAT: { + Exp2NextReg( fs, e ); // operand must be on the `stack' + } break; + + case BinOpr.ADD: + case BinOpr.SUB: + case BinOpr.MUL: + case BinOpr.DIV: + case BinOpr.MOD: + case BinOpr.POW: { + if( !IsNumeral(e) ) + Exp2RK( fs, e ); + } break; + + default: { + Exp2RK( fs, e ); + } break; + } + } + + public static void Posfix( FuncState fs, BinOpr op, + ExpDesc e1, ExpDesc e2, int line ) + { + switch( op ) + { + case BinOpr.AND: { + Utl.Assert( e1.ExitTrue == NO_JUMP ); + DischargeVars( fs, e2 ); + e2.ExitFalse = Concat( fs, e2.ExitFalse, e1.ExitFalse ); + e1.CopyFrom( e2 ); + break; + } + case BinOpr.OR: { + Utl.Assert( e1.ExitFalse == NO_JUMP ); + DischargeVars( fs, e2 ); + e2.ExitTrue = Concat( fs, e2.ExitTrue, e1.ExitTrue ); + e1.CopyFrom( e2 ); + break; + } + case BinOpr.CONCAT: { + Exp2Val( fs, e2 ); + var pe2 = fs.GetCode( e2 ); + if( e2.Kind == ExpKind.VRELOCABLE && + pe2.Value.GET_OPCODE() == OpCode.OP_CONCAT ) + { + Utl.Assert( e1.Info == pe2.Value.GETARG_B()-1 ); + FreeExp( fs, e1 ); + pe2.Value = pe2.Value.SETARG_B( e1.Info ); + e1.Kind = ExpKind.VRELOCABLE; + e1.Info = e2.Info; + } + else + { + // operand must be on the `stack' + Exp2NextReg( fs, e2 ); + CodeArith( fs, OpCode.OP_CONCAT, e1, e2, line ); + } + break; + } + case BinOpr.ADD: { + CodeArith( fs, OpCode.OP_ADD, e1, e2, line); + break; + } + case BinOpr.SUB: { + CodeArith( fs, OpCode.OP_SUB, e1, e2, line); + break; + } + case BinOpr.MUL: { + CodeArith( fs, OpCode.OP_MUL, e1, e2, line); + break; + } + case BinOpr.DIV: { + CodeArith( fs, OpCode.OP_DIV, e1, e2, line); + break; + } + case BinOpr.MOD: { + CodeArith( fs, OpCode.OP_MOD, e1, e2, line); + break; + } + case BinOpr.POW: { + CodeArith( fs, OpCode.OP_POW, e1, e2, line); + break; + } + case BinOpr.EQ: { + CodeComp( fs, OpCode.OP_EQ, 1, e1, e2 ); + break; + } + case BinOpr.LT: { + CodeComp( fs, OpCode.OP_LT, 1, e1, e2 ); + break; + } + case BinOpr.LE: { + CodeComp( fs, OpCode.OP_LE, 1, e1, e2 ); + break; + } + case BinOpr.NE: { + CodeComp( fs, OpCode.OP_EQ, 0, e1, e2 ); + break; + } + case BinOpr.GT: { + CodeComp( fs, OpCode.OP_LT, 0, e1, e2 ); + break; + } + case BinOpr.GE: { + CodeComp( fs, OpCode.OP_LE, 0, e1, e2 ); + break; + } + default: Utl.Assert(false); break; + } + } + + public static int Jump( FuncState fs ) + { + int jpc = fs.Jpc; // save list of jumps to here + fs.Jpc = NO_JUMP; + int j = CodeAsBx( fs, OpCode.OP_JMP, 0, NO_JUMP ); + j = Concat( fs, j, jpc ); + return j; + } + + public static void JumpTo( FuncState fs, int target ) + { + PatchList( fs, Jump(fs), target ); + } + + public static void Ret( FuncState fs, int first, int nret ) + { + CodeABC( fs, OpCode.OP_RETURN, first, nret+1, 0 ); + } + + private static int CondJump( FuncState fs, OpCode op, int a, int b, int c ) + { + CodeABC( fs, op, a, b, c ); + return Jump( fs ); + } + + public static void PatchList( FuncState fs, int list, int target ) + { + if( target == fs.Pc ) + PatchToHere( fs, list ); + else + { + Utl.Assert( target < fs.Pc ); + PatchListAux( fs, list, target, NO_REG, target ); + } + } + + public static void PatchClose( FuncState fs, int list, int level ) + { + level++; // argument is +1 to reserve 0 as non-op + while( list != NO_JUMP ) + { + int next = GetJump( fs, list ); + var pi = new InstructionPtr( fs.Proto.Code, list );; + Utl.Assert( pi.Value.GET_OPCODE() == OpCode.OP_JMP && + ( pi.Value.GETARG_A() == 0 || + pi.Value.GETARG_A() >= level ) ); + pi.Value = pi.Value.SETARG_A( level ); + list = next; + } + } + + public static void PatchToHere( FuncState fs, int list ) + { + GetLabel( fs ); + fs.Jpc = Concat( fs, fs.Jpc, list ); + } + + public static int Concat( FuncState fs, int l1, int l2 ) + { + if( l2 == NO_JUMP ) + return l1; + else if( l1 == NO_JUMP ) + return l2; + else + { + int list = l1; + int next = GetJump( fs, list ); + + // find last element + while( next != NO_JUMP ) + { + list = next; + next = GetJump( fs, list ); + } + FixJump( fs, list, l2 ); + return l1; + } + } + + public static int StringK( FuncState fs, string s ) + { + var o = new TValue(); + o.SetSValue(s); + return AddK( fs, ref o, ref o ); + } + + public static int NumberK( FuncState fs, double r ) + { + var o = new TValue(); + o.SetNValue(r); + return AddK( fs, ref o, ref o ); + } + + private static int BoolK( FuncState fs, bool b ) + { + var o = new TValue(); + o.SetBValue(b); + return AddK( fs, ref o, ref o ); + } + + private static int NilK( FuncState fs ) + { + // // cannot use nil as key; + // // instead use table itself to represent nil + // var k = fs.H; + // var o = new LuaNil(); + // return AddK( fs, k, o ); + + var o = new TValue(); + o.SetNilValue(); + return AddK( fs, ref o, ref o ); + } + + public static int AddK(FuncState fs, ref TValue key, ref TValue v) + { + int idx; + int keyHash = key.GetHashCode(); + + if (fs.H.TryGetValue(keyHash, out idx)) + return idx; + + idx = fs.Proto.K.Count; + fs.H.Add(keyHash, idx); + + var newItem = new StkId(); + newItem.V.SetObj(ref v); + fs.Proto.K.Add(newItem); + return idx; + } + + public static void Indexed( FuncState fs, ExpDesc t, ExpDesc k ) + { + t.Ind.T = t.Info; + t.Ind.Idx = Exp2RK( fs, k ); + t.Ind.Vt = (t.Kind == ExpKind.VUPVAL) ? ExpKind.VUPVAL + : ExpKind.VLOCAL; // FIXME + t.Kind = ExpKind.VINDEXED; + } + + private static bool HasJumps( ExpDesc e ) + { + return e.ExitTrue != e.ExitFalse; + } + + private static int CodeLabel( FuncState fs, int a, int b, int jump ) + { + GetLabel( fs ); // those instructions may be jump targets + return CodeABC( fs, OpCode.OP_LOADBOOL, a, b, jump ); + } + + private static void Discharge2Reg( FuncState fs, ExpDesc e, int reg ) + { + DischargeVars( fs, e ); + switch( e.Kind ) + { + case ExpKind.VNIL: { + CodeNil( fs, reg, 1 ); + break; + } + case ExpKind.VFALSE: + case ExpKind.VTRUE: { + CodeABC( fs, OpCode.OP_LOADBOOL, reg, + (e.Kind == ExpKind.VTRUE ? 1 : 0), 0 ); + break; + } + case ExpKind.VK: { + CodeK( fs, reg, e.Info ); + break; + } + case ExpKind.VKNUM: { + CodeK( fs, reg, NumberK( fs, e.NumberValue ) ); + break; + } + case ExpKind.VRELOCABLE: { + InstructionPtr pi = fs.GetCode(e); + pi.Value = pi.Value.SETARG_A(reg); + break; + } + case ExpKind.VNONRELOC: { + if( reg != e.Info ) + CodeABC( fs, OpCode.OP_MOVE, reg, e.Info, 0 ); + break; + } + default: { + Utl.Assert( e.Kind == ExpKind.VVOID || e.Kind == ExpKind.VJMP ); + return; // nothing to do... + } + } + e.Info = reg; + e.Kind = ExpKind.VNONRELOC; + } + + public static void CheckStack( FuncState fs, int n ) + { + int newStack = fs.FreeReg + n; + if( newStack > fs.Proto.MaxStackSize ) + { + if( newStack >= LuaLimits.MAXSTACK ) + { + fs.Lexer.SyntaxError("function or expression too complex"); + } + fs.Proto.MaxStackSize = (byte)newStack; + } + } + + public static void ReserveRegs( FuncState fs, int n ) + { + CheckStack( fs, n ); + fs.FreeReg += n; + } + + private static void Discharge2AnyReg( FuncState fs, ExpDesc e ) + { + if( e.Kind != ExpKind.VNONRELOC ) + { + ReserveRegs( fs, 1 ); + Discharge2Reg( fs, e, fs.FreeReg-1 ); + } + } + + private static void Exp2Reg( FuncState fs, ExpDesc e, int reg ) + { + Discharge2Reg( fs, e, reg ); + if( e.Kind == ExpKind.VJMP ) + { + e.ExitTrue = Concat( fs, e.ExitTrue, e.Info ); + } + + if( HasJumps(e) ) + { + int p_f = NO_JUMP; + int p_t = NO_JUMP; + if( NeedValue( fs, e.ExitTrue ) || NeedValue( fs, e.ExitFalse ) ) + { + int fj = (e.Kind == ExpKind.VJMP) ? NO_JUMP : Jump( fs ); + p_f = CodeLabel( fs, reg, 0, 1 ); + p_t = CodeLabel( fs, reg, 1, 0 ); + PatchToHere( fs, fj ); + } + + // position after whole expression + int final = GetLabel( fs ); + PatchListAux( fs, e.ExitFalse, final, reg, p_f ); + PatchListAux( fs, e.ExitTrue, final, reg, p_t ); + } + + e.ExitFalse = NO_JUMP; + e.ExitTrue = NO_JUMP; + e.Info = reg; + e.Kind = ExpKind.VNONRELOC; + } + + public static void Exp2NextReg( FuncState fs, ExpDesc e ) + { + DischargeVars( fs, e ); + FreeExp( fs, e ); + ReserveRegs( fs, 1 ); + Exp2Reg( fs, e, fs.FreeReg-1 ); + } + + public static void Exp2Val( FuncState fs, ExpDesc e ) + { + if( HasJumps(e) ) + Exp2AnyReg( fs, e ); + else + DischargeVars( fs, e ); + } + + public static int Exp2RK( FuncState fs, ExpDesc e ) + { + Exp2Val( fs, e ); + switch( e.Kind ) + { + case ExpKind.VTRUE: + case ExpKind.VFALSE: + case ExpKind.VNIL: { + // constant fits in RK operand? + if( fs.Proto.K.Count <= Instruction.MAXINDEXRK ) + { + e.Info = (e.Kind == ExpKind.VNIL) ? NilK(fs) + : BoolK( fs, (e.Kind == ExpKind.VTRUE ) ); + e.Kind = ExpKind.VK; + return Instruction.RKASK( e.Info ); + } + else break; + } + case ExpKind.VKNUM: + case ExpKind.VK: + { + if( e.Kind == ExpKind.VKNUM ) + { + e.Info = NumberK( fs, e.NumberValue ); + e.Kind = ExpKind.VK; + } + + if( e.Info <= Instruction.MAXINDEXRK ) + return Instruction.RKASK( e.Info ); + else break; + } + + default: break; + } + + return Exp2AnyReg( fs, e ); + } + + public static int Exp2AnyReg( FuncState fs, ExpDesc e ) + { + DischargeVars( fs, e ); + if( e.Kind == ExpKind.VNONRELOC ) + { + // exp is already in a register + if( ! HasJumps( e ) ) + return e.Info; + + // reg. is not a local? + if( e.Info >= fs.NumActVar ) + { + Exp2Reg( fs, e, e.Info ); + return e.Info; + } + } + Exp2NextReg( fs, e ); // default + return e.Info; + } + + public static void Exp2AnyRegUp( FuncState fs, ExpDesc e ) + { + if( e.Kind != ExpKind.VUPVAL || HasJumps( e ) ) + { + Exp2AnyReg( fs, e ); + } + } + + public static void DischargeVars( FuncState fs, ExpDesc e ) + { + switch( e.Kind ) + { + case ExpKind.VLOCAL: + e.Kind = ExpKind.VNONRELOC; + break; + + case ExpKind.VUPVAL: + e.Info = CodeABC( fs, OpCode.OP_GETUPVAL, 0, e.Info, 0 ); + e.Kind = ExpKind.VRELOCABLE; + break; + + case ExpKind.VINDEXED: + OpCode op = OpCode.OP_GETTABUP; + FreeReg( fs, e.Ind.Idx ); + if( e.Ind.Vt == ExpKind.VLOCAL ) + { + FreeReg( fs, e.Ind.T ); + op = OpCode.OP_GETTABLE; + } + e.Info = CodeABC( fs, op, 0, e.Ind.T, e.Ind.Idx ); + e.Kind = ExpKind.VRELOCABLE; + break; + + case ExpKind.VVARARG: + case ExpKind.VCALL: + SetOneRet( fs, e ); + break; + + default: break; + } + } + + public static void SetReturns( FuncState fs, ExpDesc e, int nResults ) + { + if( e.Kind == ExpKind.VCALL ) { // expression is an open function call? + var pi = fs.GetCode(e); + pi.Value = pi.Value.SETARG_C( nResults+1 ); + } + else if( e.Kind == ExpKind.VVARARG ) { + var pi = fs.GetCode(e); + pi.Value = pi.Value.SETARG_B( nResults+1 ).SETARG_A( fs.FreeReg ); + ReserveRegs( fs, 1 ); + } + } + + public static void SetMultiRet( FuncState fs, ExpDesc e ) + { + SetReturns( fs, e, LuaDef.LUA_MULTRET ); + } + + public static void SetOneRet( FuncState fs, ExpDesc e ) + { + // expression is an open function call? + if( e.Kind == ExpKind.VCALL ) + { + e.Kind = ExpKind.VNONRELOC; + e.Info = ( fs.GetCode( e ) ).Value.GETARG_A(); + } + else if( e.Kind == ExpKind.VVARARG ) + { + var pi = fs.GetCode( e ); + pi.Value = pi.Value.SETARG_B( 2 ); + e.Kind = ExpKind.VRELOCABLE; // can relocate its simple result + } + } + + public static void StoreVar( FuncState fs, ExpDesc v, ExpDesc e ) + { + switch( v.Kind ) + { + case ExpKind.VLOCAL: { + FreeExp( fs, e ); + Exp2Reg( fs, e, v.Info ); + break; + } + + case ExpKind.VUPVAL: { + int c = Exp2AnyReg( fs, e ); + CodeABC( fs, OpCode.OP_SETUPVAL, c, v.Info, 0 ); + break; + } + + case ExpKind.VINDEXED: { + OpCode op = (v.Ind.Vt == ExpKind.VLOCAL) + ? OpCode.OP_SETTABLE + : OpCode.OP_SETTABUP; + int c = Exp2RK( fs, e ); + CodeABC( fs, op, v.Ind.T, v.Ind.Idx, c ); + break; + } + + default: + { + throw new NotImplementedException("invalid var kind to store"); + } + } + FreeExp( fs, e ); + } + + public static void Self( FuncState fs, ExpDesc e, ExpDesc key ) + { + Exp2AnyReg( fs, e ); + int ereg = e.Info; // register where `e' is placed + FreeExp( fs, e ); + e.Info = fs.FreeReg; // base register for op_self + e.Kind = ExpKind.VNONRELOC; + ReserveRegs( fs, 2 ); + CodeABC( fs, OpCode.OP_SELF, e.Info, ereg, Coder.Exp2RK(fs, key) ); + FreeExp( fs, key ); + } + + public static void SetList( FuncState fs, int t, int nelems, int tostore ) + { + int c = (nelems - 1) / LuaDef.LFIELDS_PER_FLUSH + 1; + int b = (tostore == LuaDef.LUA_MULTRET) ? 0 : tostore; + Utl.Assert( tostore != 0 ); + + if( c <= Instruction.MAXARG_C ) + { + CodeABC( fs, OpCode.OP_SETLIST, t, b, c ); + } + else if( c <= Instruction.MAXARG_Ax ) + { + CodeABC( fs, OpCode.OP_SETLIST, t, b, 0 ); + CodeExtraArg( fs, c ); + } + else + { + fs.Lexer.SyntaxError("constructor too long"); + } + + // free registers with list values + fs.FreeReg = t + 1; + } + + public static void CodeNil( FuncState fs, int from, int n ) + { + int l = from + n - 1; // last register to set nil + if( fs.Pc > fs.LastTarget ) // no jumps to current position? + { + var previous = new InstructionPtr( fs.Proto.Code, fs.Pc-1 ); + if( previous.Value.GET_OPCODE() == OpCode.OP_LOADNIL ) + { + int pfrom = previous.Value.GETARG_A(); + int pl = pfrom + previous.Value.GETARG_B(); + + // can connect both? + if( (pfrom <= from && from <= pl + 1) || + (from <= pfrom && pfrom <= l + 1)) + { + if( pfrom < from ) from = pfrom; // from=min(from,pfrom) + if( pl > l ) l = pl; // l=max(l,pl) + previous.Value = previous.Value.SETARG_A( from ); + previous.Value = previous.Value.SETARG_B( l - from ); + return; + } + } + // else go through + } + + // else no optimization + CodeABC( fs, OpCode.OP_LOADNIL, from, n-1, 0 ); + } + + private static int CodeExtraArg( FuncState fs, int a ) + { + Utl.Assert( a <= Instruction.MAXARG_Ax ); + return Code( fs, Instruction.CreateAx( OpCode.OP_EXTRAARG, a ) ); + } + + public static int CodeK( FuncState fs, int reg, int k ) + { + if( k <= Instruction.MAXARG_Bx ) + return CodeABx( fs, OpCode.OP_LOADK, reg, (uint)k ); + else + { + int p = CodeABx( fs, OpCode.OP_LOADKX, reg, 0 ); + CodeExtraArg( fs, k ); + return p; + } + } + + public static int CodeAsBx( FuncState fs, OpCode op, int a, int sBx ) + { + return CodeABx( fs, op, a, ((uint)sBx)+Instruction.MAXARG_sBx); + } + + public static int CodeABx( FuncState fs, OpCode op, int a, uint bc ) + { + var mode = OpCodeInfo.GetMode(op); + Utl.Assert( mode.OpMode == OpMode.iABx + || mode.OpMode == OpMode.iAsBx ); + Utl.Assert( mode.CMode == OpArgMask.OpArgN ); + Utl.Assert( a < Instruction.MAXARG_A & bc <= Instruction.MAXARG_Bx ); + return Code( fs, Instruction.CreateABx( op, a, bc ) ); + } + + public static int CodeABC( FuncState fs, OpCode op, int a, int b, int c ) + { + return Code( fs, Instruction.CreateABC( op, a, b, c ) ); + } + + public static int Code( FuncState fs, Instruction i ) + { + DischargeJpc( fs ); // `pc' will change + + while( fs.Proto.Code.Count <= fs.Pc ) + { + fs.Proto.Code.Add( new Instruction(LuaLimits.MAX_INT) ); + } + fs.Proto.Code[ fs.Pc ] = i; + + while( fs.Proto.LineInfo.Count <= fs.Pc ) + { + fs.Proto.LineInfo.Add( LuaLimits.MAX_INT ); + } + fs.Proto.LineInfo[ fs.Pc ] = fs.Lexer.LastLine; + + return fs.Pc++; + } + } + +} + diff --git a/UniLua/Common.cs b/UniLua/Common.cs new file mode 100644 index 0000000..9411efa --- /dev/null +++ b/UniLua/Common.cs @@ -0,0 +1,128 @@ + +namespace UniLua +{ + public static class LuaConf + { + public const int LUAI_BITSINT = 32; + +#pragma warning disable 0429 + public const int LUAI_MAXSTACK = (LUAI_BITSINT >= 32) + ? 1000000 + : 15000 + ; +#pragma warning restore 0429 + + // reserve some space for error handling + public const int LUAI_FIRSTPSEUDOIDX = (-LUAI_MAXSTACK-1000); + + public const string LUA_SIGNATURE = "\u001bLua"; + public static string LUA_DIRSEP { + get { return System.IO.Path.DirectorySeparatorChar.ToString(); } + } + } + + public static class LuaLimits + { + public const int MAX_INT = System.Int32.MaxValue - 2; + public const int MAXUPVAL = System.Byte.MaxValue; + public const int LUAI_MAXCCALLS = 200; + public const int MAXSTACK = 250; + } + + public static class LuaDef + { + public const int LUA_MINSTACK = 20; + public const int BASIC_STACK_SIZE = LUA_MINSTACK * 2; + public const int EXTRA_STACK = 5; + + public const int LUA_RIDX_MAINTHREAD = 1; + public const int LUA_RIDX_GLOBALS = 2; + public const int LUA_RIDX_LAST = LUA_RIDX_GLOBALS; + + public const int LUA_MULTRET = -1; + + public const int LUA_REGISTRYINDEX = LuaConf.LUAI_FIRSTPSEUDOIDX; + + // number of list items accumulate before a SETLIST instruction + public const int LFIELDS_PER_FLUSH = 50; + + public const int LUA_IDSIZE = 60; + + public const string LUA_VERSION_MAJOR = "5"; + public const string LUA_VERSION_MINOR = "2"; + public const string LUA_VERSION = "Lua " + LUA_VERSION_MAJOR + "." + LUA_VERSION_MINOR; + + public const string LUA_ENV = "_ENV"; + + public const int BASE_CI_SIZE = 8; + } + + public static class LuaConstants + { + public const int LUA_NOREF = -2; + public const int LUA_REFNIL = -1; + } + + public enum LuaType + { + LUA_TNONE = -1, + LUA_TNIL = 0, + LUA_TBOOLEAN = 1, + LUA_TLIGHTUSERDATA = 2, + LUA_TNUMBER = 3, + LUA_TSTRING = 4, + LUA_TTABLE = 5, + LUA_TFUNCTION = 6, + LUA_TUSERDATA = 7, + LUA_TTHREAD = 8, + + LUA_TUINT64 = 9, + + LUA_NUMTAGS = 10, + + LUA_TPROTO, + LUA_TUPVAL, + LUA_TDEADKEY, + } + + public enum ClosureType + { + LUA, + CSHARP, + } + + public enum ThreadStatus + { + LUA_RESUME_ERROR = -1, + LUA_OK = 0, + LUA_YIELD = 1, + LUA_ERRRUN = 2, + LUA_ERRSYNTAX = 3, + LUA_ERRMEM = 4, + LUA_ERRGCMM = 5, + LUA_ERRERR = 6, + + LUA_ERRFILE = 7, + } + + /* ORDER TM */ + internal enum LuaOp + { + LUA_OPADD = 0, + LUA_OPSUB = 1, + LUA_OPMUL = 2, + LUA_OPDIV = 3, + LUA_OPMOD = 4, + LUA_OPPOW = 5, + LUA_OPUNM = 6, + } + + public enum LuaEq + { + LUA_OPEQ = 0, + LUA_OPLT = 1, + LUA_OPLE = 2, + } + +} + diff --git a/UniLua/Do.cs b/UniLua/Do.cs new file mode 100644 index 0000000..8b10c68 --- /dev/null +++ b/UniLua/Do.cs @@ -0,0 +1,343 @@ + +// #define DEBUG_D_PRE_CALL +// #define DEBUG_D_POS_CALL + +namespace UniLua +{ + using ULDebug = UniLua.Tools.ULDebug; + using InstructionPtr = Pointer; + using Exception = System.Exception; + + public class LuaRuntimeException : Exception + { + public ThreadStatus ErrCode { get; private set; } + + public LuaRuntimeException( ThreadStatus errCode ) + { + ErrCode = errCode; + } + } + + public partial class LuaState + { + internal void D_Throw( ThreadStatus errCode ) + { + throw new LuaRuntimeException( errCode ); + } + + private ThreadStatus D_RawRunProtected( PFuncDelegate func, ref T ud ) + { + int oldNumCSharpCalls = NumCSharpCalls; + ThreadStatus res = ThreadStatus.LUA_OK; + try + { + func(ref ud); + } + catch( LuaRuntimeException e ) + { + NumCSharpCalls = oldNumCSharpCalls; + res = e.ErrCode; + } + NumCSharpCalls = oldNumCSharpCalls; + return res; + } + + private void SetErrorObj( ThreadStatus errCode, StkId oldTop ) + { + switch( errCode ) + { + case ThreadStatus.LUA_ERRMEM: // memory error? + oldTop.V.SetSValue("not enough memory"); + break; + + case ThreadStatus.LUA_ERRERR: + oldTop.V.SetSValue("error in error handling"); + break; + + default: // error message on current top + oldTop.V.SetObj(ref Stack[Top.Index-1].V); + break; + } + Top = Stack[oldTop.Index+1]; + } + + private ThreadStatus D_PCall( PFuncDelegate func, ref T ud, + int oldTopIndex, int errFunc ) + { + int oldCIIndex = CI.Index; + bool oldAllowHook = AllowHook; + int oldNumNonYieldable = NumNonYieldable; + int oldErrFunc = ErrFunc; + + ErrFunc = errFunc; + ThreadStatus status = D_RawRunProtected( func, ref ud ); + if( status != ThreadStatus.LUA_OK ) // an error occurred? + { + F_Close( Stack[oldTopIndex] ); + SetErrorObj( status, Stack[oldTopIndex] ); + CI = BaseCI[oldCIIndex]; + AllowHook = oldAllowHook; + NumNonYieldable = oldNumNonYieldable; + } + ErrFunc = oldErrFunc; + return status; + } + + private void D_Call( StkId func, int nResults, bool allowYield ) + { + if( ++NumCSharpCalls >= LuaLimits.LUAI_MAXCCALLS ) + { + if( NumCSharpCalls == LuaLimits.LUAI_MAXCCALLS ) + G_RunError( "CSharp Stack Overflow" ); + else if( NumCSharpCalls >= + (LuaLimits.LUAI_MAXCCALLS + (LuaLimits.LUAI_MAXCCALLS>>3)) + ) + D_Throw( ThreadStatus.LUA_ERRERR ); + } + if( !allowYield ) + NumNonYieldable++; + if( !D_PreCall( func, nResults ) ) // is a Lua function ? + V_Execute(); + if( !allowYield ) + NumNonYieldable--; + NumCSharpCalls--; + } + + /// + /// return true if function has been executed + /// + private bool D_PreCall( StkId func, int nResults ) + { + // prepare for Lua call + +#if DEBUG_D_PRE_CALL + Console.WriteLine( "============================ D_PreCall func:" + func ); +#endif + + int funcIndex = func.Index; + if(!func.V.TtIsFunction()) { + // not a function + // retry with `function' tag method + func = tryFuncTM( func ); + + // now it must be a function + return D_PreCall( func, nResults ); + } + + if(func.V.ClIsLuaClosure()) { + var cl = func.V.ClLValue(); + Utl.Assert(cl != null); + var p = cl.Proto; + + D_CheckStack(p.MaxStackSize + p.NumParams); + func = Stack[funcIndex]; + + // + int n = (Top.Index - func.Index) - 1; + for( ; n 0 ) + { +#if DEBUG_D_POS_CALL + Console.WriteLine( "[D] ==== PosCall new LuaNil()" ); +#endif + Stack[resIndex++].V.SetNilValue(); + } + Top = Stack[resIndex]; +#if DEBUG_D_POS_CALL + Console.WriteLine( "[D] ==== PosCall return " + (wanted - LuaDef.LUA_MULTRET) ); +#endif + return (wanted - LuaDef.LUA_MULTRET); + } + + private CallInfo ExtendCI() + { + int newIndex = CI.Index + 1; + if(newIndex >= BaseCI.Length) { + int newLength = BaseCI.Length*2; + var newBaseCI = new CallInfo[newLength]; + int i = 0; + while(i < BaseCI.Length) { + newBaseCI[i] = BaseCI[i]; + newBaseCI[i].List = newBaseCI; + ++i; + } + while(i < newLength) { + var newCI = new CallInfo(); + newBaseCI[i] = newCI; + newCI.List = newBaseCI; + newCI.Index = i; + ++i; + } + BaseCI = newBaseCI; + CI = newBaseCI[CI.Index]; + return newBaseCI[newIndex]; + } + else { + return BaseCI[newIndex]; + } + } + + private int AdjustVarargs( LuaProto p, int actual ) + { + // `...' + // : func (base)fixed-p1 fixed-p2 var-p1 var-p2 top + // : func nil nil var-p1 var-p2 (base)fixed-p1 fixed-p2 (reserved...) top + // + // `...' + // func (base)fixed-p1 fixed-p2 (reserved...) top + + int NumFixArgs = p.NumParams; + Utl.Assert( actual >= NumFixArgs, "AdjustVarargs (actual >= NumFixArgs) is false" ); + + int fixedArg = Top.Index - actual; // first fixed argument + int stackBase = Top.Index; // final position of first argument + for( int i=stackBase; ifunc.Index; --i) + { Stack[i].V.SetObj(ref Stack[i-1].V); } + + IncrTop(); + func.V.SetObj(ref tmObj.V); + return func; + } + + private void D_CheckStack(int n) + { + if(StackLast - Top.Index <= n) + D_GrowStack(n); + // TODO: FOR DEBUGGING + // else + // CondMoveStack(); + } + + // some space for error handling + private const int ERRORSTACKSIZE = LuaConf.LUAI_MAXSTACK + 200; + + private void D_GrowStack(int n) + { + int size = Stack.Length; + if(size > LuaConf.LUAI_MAXSTACK) + D_Throw(ThreadStatus.LUA_ERRERR); + + int needed = Top.Index + n + LuaDef.EXTRA_STACK; + int newsize = 2 * size; + if(newsize > LuaConf.LUAI_MAXSTACK) + { newsize = LuaConf.LUAI_MAXSTACK; } + if(newsize < needed) + { newsize = needed; } + if(newsize > LuaConf.LUAI_MAXSTACK) + { + D_ReallocStack(ERRORSTACKSIZE); + G_RunError("stack overflow"); + } + else + { + D_ReallocStack(newsize); + } + } + + private void D_ReallocStack(int size) + { + Utl.Assert(size <= LuaConf.LUAI_MAXSTACK || size == ERRORSTACKSIZE); + var newStack = new StkId[size]; + int i = 0; + for( ; i { + DumpBlock( BitConverter.GetBytes( (uint)ins ) ); + }); + } + + private void DumpConstants( LuaProto proto ) + { + DumpVector( proto.K, (k) => { + var t = k.V.Tt; + DumpByte( (byte)t ); + switch( t ) + { + case (int)LuaType.LUA_TNIL: + break; + case (int)LuaType.LUA_TBOOLEAN: + DumpBool(k.V.BValue()); + break; + case (int)LuaType.LUA_TNUMBER: + DumpBlock( BitConverter.GetBytes(k.V.NValue) ); + break; + case (int)LuaType.LUA_TSTRING: + DumpString(k.V.SValue()); + break; + default: + Utl.Assert(false); + break; + } + }); + + DumpVector( proto.P, (p) => { + DumpFunction( p ); + }); + } + + private void DumpUpvalues( LuaProto proto ) + { + DumpVector( proto.Upvalues, (upval) => { + DumpByte( upval.InStack ? (byte)1 : (byte)0 ); + DumpByte( (byte)upval.Index ); + }); + } + + private void DumpDebug( LuaProto proto ) + { + DumpString( Strip ? null : proto.Source ); + + DumpVector( (Strip ? null : proto.LineInfo), (line) => { + DumpInt(line); + }); + + DumpVector( (Strip ? null : proto.LocVars), (locvar) => { + DumpString( locvar.VarName ); + DumpInt( locvar.StartPc ); + DumpInt( locvar.EndPc ); + }); + + DumpVector( (Strip ? null : proto.Upvalues), (upval) => { + DumpString( upval.Name ); + }); + } + + private void DumpFunction( LuaProto proto ) + { + DumpInt( proto.LineDefined ); + DumpInt( proto.LastLineDefined ); + DumpByte( (byte)proto.NumParams ); + DumpByte( proto.IsVarArg ? (byte)1 : (byte)0 ); + DumpByte( (byte)proto.MaxStackSize ); + DumpCode( proto ); + DumpConstants( proto ); + DumpUpvalues( proto ); + DumpDebug( proto ); + } + + private delegate void DumpItemDelegate( T item ); + private void DumpVector( IList list, DumpItemDelegate dumpItem ) + { + if( list == null ) + { + DumpInt( 0 ); + } + else + { + DumpInt( list.Count ); + for( var i=0; i ReservedWordDict; + static LLex() + { + ReservedWordDict = new Dictionary(); + ReservedWordDict.Add("and", TK.AND); + ReservedWordDict.Add("break", TK.BREAK); + ReservedWordDict.Add("do", TK.DO); + ReservedWordDict.Add("else", TK.ELSE); + ReservedWordDict.Add("elseif", TK.ELSEIF); + ReservedWordDict.Add("end", TK.END); + ReservedWordDict.Add("false", TK.FALSE); + ReservedWordDict.Add("for", TK.FOR); + ReservedWordDict.Add("function", TK.FUNCTION); + ReservedWordDict.Add("goto", TK.GOTO); + ReservedWordDict.Add("if", TK.IF); + ReservedWordDict.Add("in", TK.IN); + ReservedWordDict.Add("local", TK.LOCAL); + ReservedWordDict.Add("nil", TK.NIL); + ReservedWordDict.Add("not", TK.NOT); + ReservedWordDict.Add("or", TK.OR); + ReservedWordDict.Add("repeat", TK.REPEAT); + ReservedWordDict.Add("return", TK.RETURN); + ReservedWordDict.Add("then", TK.THEN); + ReservedWordDict.Add("true", TK.TRUE); + ReservedWordDict.Add("until", TK.UNTIL); + ReservedWordDict.Add("while", TK.WHILE); + } + + public LLex( ILuaState lua, ILoadInfo loadinfo, string name ) + { + Lua = (LuaState)lua; + LoadInfo = loadinfo; + LineNumber = 1; + LastLine = 1; + Token = null; + LookAhead = null; + _Saved = null; + Source = name; + + _Next(); + } + + public void Next() + { + LastLine = LineNumber; + if( LookAhead != null ) + { + Token = LookAhead; + LookAhead = null; + } + else + { + Token = _Lex(); + } + } + + public Token GetLookAhead() + { + Utl.Assert( LookAhead == null ); + LookAhead = _Lex(); + return LookAhead; + } + + private void _Next() + { + var c = LoadInfo.ReadByte(); + Current = (c == -1) ? EOZ : c; + } + + private void _SaveAndNext() + { + Saved.Append( (char)Current ); + _Next(); + } + + private void _Save( char c ) + { + Saved.Append( c ); + } + + private string _GetSavedString() + { + return Saved.ToString(); + } + + private void _ClearSaved() + { + _Saved = null; + } + + private bool _CurrentIsNewLine() + { + return Current == '\n' || Current == '\r'; + } + + private bool _CurrentIsDigit() + { + return Char.IsDigit( (char)Current ); + } + + private bool _CurrentIsXDigit() + { + return _CurrentIsDigit() || + ('A' <= Current && Current <= 'F') || + ('a' <= Current && Current <= 'f'); + } + + private bool _CurrentIsSpace() + { + return Char.IsWhiteSpace( (char)Current ); + } + + private bool _CurrentIsAlpha() + { + return Char.IsLetter( (char)Current ); + } + + private bool _IsReserved( string identifier, out TK type ) + { + return ReservedWordDict.TryGetValue( identifier, out type ); + } + + public bool IsReservedWord( string name ) + { + return ReservedWordDict.ContainsKey( name ); + } + + private void _IncLineNumber() + { + var old = Current; + _Next(); + if( _CurrentIsNewLine() && Current != old ) + _Next(); + if( ++LineNumber >= Int32.MaxValue ) + _Error( "chunk has too many lines" ); + } + + private string _ReadLongString( int sep ) + { + _SaveAndNext(); + + if( _CurrentIsNewLine() ) + _IncLineNumber(); + + while( true ) + { + switch( Current ) + { + case EOZ: + _LexError( _GetSavedString(), + "unfinished long string/comment", + (int)TK.EOS ); + break; + + case '[': + { + if( _SkipSep() == sep ) + { + _SaveAndNext(); + if( sep == 0 ) + { + _LexError( _GetSavedString(), + "nesting of [[...]] is deprecated", + (int)TK.EOS ); + } + } + break; + } + + case ']': + { + if( _SkipSep() == sep ) + { + _SaveAndNext(); + goto endloop; + } + break; + } + + case '\n': + case '\r': + { + _Save('\n'); + _IncLineNumber(); + break; + } + + default: + { + _SaveAndNext(); + break; + } + } + } + endloop: + var r = _GetSavedString(); + return r.Substring( 2+sep, r.Length - 2*(2+sep) ); + } + + private void _EscapeError( string info, string msg ) + { + _LexError( "\\"+info, msg, (int)TK.STRING ); + } + + private byte _ReadHexEscape() + { + int r = 0; + var c = new char[3] { 'x', (char)0, (char)0 }; + // read two hex digits + for( int i=1; i<3; ++i ) + { + _Next(); + c[i] = (char)Current; + if( !_CurrentIsXDigit() ) + { + _EscapeError( new String(c, 0, i+1), + "hexadecimal digit expected" ); + // error + } + r = (r << 4) + Int32.Parse( Current.ToString(), + NumberStyles.HexNumber ); + } + return (byte)r; + } + + private byte _ReadDecEscape() + { + int r = 0; + var c = new char[3]; + // read up to 3 digits + int i = 0; + for( i=0; i<3 && _CurrentIsDigit(); ++i ) + { + c[i] = (char)Current; + r = r*10 + Current - '0'; + _Next(); + } + if( r > Byte.MaxValue ) + _EscapeError( new String(c, 0, i), + "decimal escape too large" ); + return (byte)r; + } + + private string _ReadString() + { + var del = Current; + _Next(); + while( Current != del ) + { + switch( Current ) + { + case EOZ: + _Error( "unfinished string" ); + continue; + + case '\n': + case '\r': + _Error( "unfinished string" ); + continue; + + case '\\': + { + byte c; + _Next(); + switch( Current ) + { + case 'a': c=(byte)'\a'; break; + case 'b': c=(byte)'\b'; break; + case 'f': c=(byte)'\f'; break; + case 'n': c=(byte)'\n'; break; + case 'r': c=(byte)'\r'; break; + case 't': c=(byte)'\t'; break; + case 'v': c=(byte)'\v'; break; + case 'x': c=_ReadHexEscape(); break; + + case '\n': + case '\r': _Save('\n'); _IncLineNumber(); continue; + + case '\\': + case '\"': + case '\'': c=(byte)Current; break; + + case EOZ: continue; + + // zap following span of spaces + case 'z': { + _Next(); // skip `z' + while( _CurrentIsSpace() ) + { + if( _CurrentIsNewLine() ) + _IncLineNumber(); + else + _Next(); + } + continue; + } + + default: + { + if( !_CurrentIsDigit() ) + _EscapeError( Current.ToString(), + "invalid escape sequence" ); + + // digital escape \ddd + c = _ReadDecEscape(); + _Save( (char)c ); + continue; + // { + // c = (char)0; + // for(int i=0; i<3 && _CurrentIsDigit(); ++i) + // { + // c = (char)(c*10 + Current - '0'); + // _Next(); + // } + // _Save( c ); + // } + // continue; + } + } + _Save( (char)c ); + _Next(); + continue; + } + + default: + _SaveAndNext(); + continue; + } + } + _Next(); + return _GetSavedString(); + } + + private double _ReadNumber() + { + var expo = new char[] { 'E', 'e' }; + Utl.Assert( _CurrentIsDigit() ); + var first = Current; + _SaveAndNext(); + if( first == '0' && (Current == 'X' || Current == 'x')) + { + expo = new char[] { 'P', 'p' }; + _SaveAndNext(); + } + for(;;) + { + if( Current == expo[0] || Current == expo[1] ) + { + _SaveAndNext(); + if( Current == '+' || Current == '-' ) + _SaveAndNext(); + } + if( _CurrentIsXDigit() || Current == '.' ) + _SaveAndNext(); + else + break; + } + + double ret; + var str = _GetSavedString(); + if( LuaState.O_Str2Decimal( str, out ret ) ) + { + return ret; + } + else + { + _Error( "malformed number: " + str ); + return 0.0; + } + } + + // private float _ReadNumber() + // { + // do + // { + // _SaveAndNext(); + // } while( _CurrentIsDigit() || Current == '.' ); + // if( Current == 'E' || Current == 'e' ) + // { + // _SaveAndNext(); + // if( Current == '+' || Current == '-' ) + // _SaveAndNext(); + // } + // while( _CurrentIsAlpha() || _CurrentIsDigit() || Current == '_' ) + // _SaveAndNext(); + // float ret; + // if( !Single.TryParse( _GetSavedString(), out ret ) ) + // _Error( "malformed number" ); + // return ret; + // } + + private void _Error( string error ) + { + Lua.O_PushString( string.Format( + "{0}:{1}: {2}", + Source, LineNumber, error ) ); + Lua.D_Throw( ThreadStatus.LUA_ERRSYNTAX ); + } + + private void _LexError( string info, string msg, int tokenType ) + { + // TODO + _Error( msg + ":" + info ); + } + + public void SyntaxError( string msg ) + { + // TODO + _Error( msg ); + } + + private int _SkipSep() + { + int count = 0; + var boundary = Current; + _SaveAndNext(); + while( Current == '=' ) { + _SaveAndNext(); + count++; + } + return ( Current == boundary ? count : (-count)-1 ); + } + + private Token _Lex() + { + _ClearSaved(); + while( true ) + { + switch( Current ) + { + case '\n': + case '\r': { + _IncLineNumber(); + continue; + } + + case '-': { + _Next(); + if( Current != '-' ) return new LiteralToken('-'); + + // else is a long comment + _Next(); + if( Current == '[' ) + { + int sep = _SkipSep(); + _ClearSaved(); + if( sep >= 0 ) + { + _ReadLongString( sep ); + _ClearSaved(); + continue; + } + } + + // else is a short comment + while( !_CurrentIsNewLine() && Current != EOZ ) + _Next(); + continue; + } + + case '[': { + int sep = _SkipSep(); + if( sep >= 0 ) { + string seminfo = _ReadLongString( sep ); + return new StringToken( seminfo ); + } + else if( sep == -1 ) return new LiteralToken('['); + else _Error("invalid long string delimiter"); + continue; + } + + case '=': { + _Next(); + if( Current != '=' ) return new LiteralToken('='); + _Next(); + return new TypedToken( TK.EQ ); + } + + case '<': { + _Next(); + if( Current != '=' ) return new LiteralToken('<'); + _Next(); + return new TypedToken( TK.LE ); + } + + case '>': { + _Next(); + if( Current != '=' ) return new LiteralToken('>'); + _Next(); + return new TypedToken( TK.GE ); + } + + case '~': { + _Next(); + if( Current != '=' ) return new LiteralToken('~'); + _Next(); + return new TypedToken( TK.NE ); + } + + case ':': { + _Next(); + if( Current != ':' ) return new LiteralToken(':'); + _Next(); + return new TypedToken( TK.DBCOLON ); // new in 5.2 ? + } + + case '"': + case '\'': { + return new StringToken( _ReadString() ); + } + + case '.': { + _SaveAndNext(); + if( Current == '.' ) + { + _SaveAndNext(); + if( Current == '.' ) + { + _SaveAndNext(); + return new TypedToken( TK.DOTS ); + } + else + { + return new TypedToken( TK.CONCAT ); + } + } + else if( !_CurrentIsDigit() ) + return new LiteralToken('.'); + else + return new NumberToken( _ReadNumber() ); + } + + case EOZ: { + return new TypedToken( TK.EOS ); + } + + default: { + if( _CurrentIsSpace() ) + { + _Next(); + continue; + } + else if( _CurrentIsDigit() ) + { + return new NumberToken( _ReadNumber() ); + } + else if( _CurrentIsAlpha() || Current == '_' ) + { + do { + _SaveAndNext(); + } while( _CurrentIsAlpha() || + _CurrentIsDigit() || + Current == '_' ); + + string identifier = _GetSavedString(); + TK type; + if( _IsReserved( identifier, out type ) ) + { + return new TypedToken( type ); + } + else + { + return new NameToken( identifier ); + } + } + else + { + var c = Current; + _Next(); + return new LiteralToken(c); + } + } + } + } + } + + } + +} + diff --git a/UniLua/LuaAPI.cs b/UniLua/LuaAPI.cs new file mode 100644 index 0000000..8f2f5e6 --- /dev/null +++ b/UniLua/LuaAPI.cs @@ -0,0 +1,1638 @@ + using System; + +// #define DEBUG_RECORD_INS + +namespace UniLua +{ + public interface ILoadInfo + { + int ReadByte(); + int PeekByte(); + } + + public delegate int CSharpFunctionDelegate(ILuaState state); + public interface ILuaAPI + { + LuaState NewThread(); + + ThreadStatus Load( ILoadInfo loadinfo, string name, string mode ); + DumpStatus Dump( LuaWriter writeFunc ); + + ThreadStatus GetContext( out int context ); + void Call( int numArgs, int numResults ); + void CallK( int numArgs, int numResults, + int context, CSharpFunctionDelegate continueFunc ); + ThreadStatus PCall( int numArgs, int numResults, int errFunc); + ThreadStatus PCallK( int numArgs, int numResults, int errFunc, + int context, CSharpFunctionDelegate continueFunc ); + + ThreadStatus Resume( ILuaState from, int numArgs ); + int Yield( int numResults ); + int YieldK( int numResults, + int context, CSharpFunctionDelegate continueFunc ); + + int AbsIndex( int index ); + int GetTop(); + void SetTop( int top ); + + void Remove( int index ); + void Insert( int index ); + void Replace( int index ); + void Copy( int fromIndex, int toIndex ); + void XMove( ILuaState to, int n ); + + bool CheckStack( int size ); + bool GetStack( int level, LuaDebug ar ); + int Error(); + + int UpvalueIndex( int i ); + string GetUpvalue( int funcIndex, int n ); + string SetUpvalue( int funcIndex, int n ); + + void CreateTable( int narray, int nrec ); + void NewTable(); + bool Next( int index ); + void RawGetI( int index, int n ); + void RawSetI( int index, int n ); + void RawGet( int index ); + void RawSet( int index ); + void GetField( int index, string key ); + void SetField( int index, string key ); + void GetTable( int index ); + void SetTable( int index ); + + void Concat( int n ); + + LuaType Type( int index ); + string TypeName( LuaType t ); + bool IsNil( int index ); + bool IsNone( int index ); + bool IsNoneOrNil( int index ); + bool IsString( int index ); + bool IsTable( int index ); + bool IsFunction( int index ); + + bool Compare( int index1, int index2, LuaEq op ); + bool RawEqual( int index1, int index2 ); + int RawLen( int index ); + void Len( int index ); + + void PushNil(); + void PushBoolean( bool b ); + void PushNumber( double n ); + void PushInteger( int n ); + void PushUnsigned( uint n ); + string PushString( string s ); + void PushCSharpFunction( CSharpFunctionDelegate f ); + void PushCSharpClosure( CSharpFunctionDelegate f, int n ); + void PushValue( int index ); + void PushGlobalTable(); + void PushLightUserData( object o ); + void PushUInt64( UInt64 o ); + bool PushThread(); + + void Pop( int n ); + + bool GetMetaTable( int index ); + bool SetMetaTable( int index ); + + void GetGlobal( string name ); + void SetGlobal( string name ); + + string ToString( int index ); + double ToNumberX( int index, out bool isnum ); + double ToNumber( int index ); + int ToIntegerX( int index, out bool isnum ); + int ToInteger( int index ); + uint ToUnsignedX( int index, out bool isnum ); + uint ToUnsigned( int index ); + bool ToBoolean( int index ); + UInt64 ToUInt64( int index ); + UInt64 ToUInt64X( int index, out bool isnum ); + object ToObject( int index ); + object ToUserData( int index ); + ILuaState ToThread( int index ); + + ThreadStatus Status { get; } + + string DebugGetInstructionHistory(); + } + + public interface ILuaState : ILuaAPI, ILuaAuxLib + { + } + + public static class LuaAPI + { + public static ILuaState NewState() + { + return new LuaState(); + } + } + + internal delegate void PFuncDelegate(ref T ud); + + public partial class LuaState : ILuaState + { + LuaState ILuaAPI.NewThread() + { + LuaState newLua = new LuaState(G); + Top.V.SetThValue(newLua); + ApiIncrTop(); + + newLua.HookMask = HookMask; + newLua.BaseHookCount = BaseHookCount; + newLua.Hook = Hook; + newLua.ResetHookCount(); + + return newLua; + } + + // private void F_LoadBinary( object ud ) + // { + // byte[] bytes = (byte[])ud; + + // var reader = new BinaryBytesReader( bytes ); + // Undump u = new Undump( reader ); + + // LuaProto proto = Undump.LoadBinary( this, loadinfo, "" ); + + // var cl = new LuaLClosure( proto ); + // for( int i=0; i DG_F_Load = F_Load; + + ThreadStatus ILuaAPI.Load( ILoadInfo loadinfo, string name, string mode ) + { + var param = new LoadParameter(this, loadinfo, name, mode); + var status = D_PCall( DG_F_Load, ref param, Top.Index, ErrFunc ); + + if( status == ThreadStatus.LUA_OK ) { + var below = Stack[Top.Index-1]; + Utl.Assert(below.V.TtIsFunction() && below.V.ClIsLuaClosure()); + var cl = below.V.ClLValue(); + if(cl.Upvals.Length == 1) { + var gt = G.Registry.V.HValue().GetInt(LuaDef.LUA_RIDX_GLOBALS); + cl.Upvals[0].V.V.SetObj(ref gt.V); + } + } + + return status; + } + + DumpStatus ILuaAPI.Dump( LuaWriter writeFunc ) + { + Utl.ApiCheckNumElems( this, 1 ); + + var below = Stack[Top.Index-1]; + if(!below.V.TtIsFunction() || !below.V.ClIsLuaClosure()) + return DumpStatus.ERROR; + + var o = below.V.ClLValue(); + if(o == null) + return DumpStatus.ERROR; + + return DumpState.Dump(o.Proto, writeFunc, false); + } + + ThreadStatus ILuaAPI.GetContext( out int context ) + { + if( (CI.CallStatus & CallStatus.CIST_YIELDED) != 0 ) + { + context = CI.Context; + return CI.Status; + } + else + { + context = default(int); + return ThreadStatus.LUA_OK; + } + } + + void ILuaAPI.Call( int numArgs, int numResults ) + { + // StkId func = Top - (numArgs + 1); + // if( !D_PreCall( func, numResults ) ) + // { + // V_Execute(); + // } + + API.CallK( numArgs, numResults, 0, null ); + } + + void ILuaAPI.CallK( int numArgs, int numResults, + int context, CSharpFunctionDelegate continueFunc ) + { + Utl.ApiCheck( continueFunc == null || !CI.IsLua, + "cannot use continuations inside hooks" ); + Utl.ApiCheckNumElems( this, numArgs + 1 ); + Utl.ApiCheck( Status == ThreadStatus.LUA_OK, + "cannot do calls on non-normal thread" ); + CheckResults( numArgs, numResults ); + var func = Stack[Top.Index - (numArgs+1)]; + + // need to prepare continuation? + if( continueFunc != null && NumNonYieldable == 0 ) + { + CI.ContinueFunc = continueFunc; + CI.Context = context; + D_Call( func, numResults, true ); + } + // no continuation or no yieldable + else + { + D_Call( func, numResults, false ); + } + AdjustResults( numResults ); + } + + private struct CallS + { + public LuaState L; + public int FuncIndex; + public int NumResults; + } + + private static void F_Call(ref CallS ud) + { + CallS c = (CallS)ud; + c.L.D_Call(c.L.Stack[c.FuncIndex], c.NumResults, false); + } + private static PFuncDelegate DG_F_Call = F_Call; + + private void CheckResults( int numArgs, int numResults ) + { + Utl.ApiCheck( numResults == LuaDef.LUA_MULTRET || + CI.TopIndex - Top.Index >= numResults - numArgs, + "results from function overflow current stack size" ); + } + + private void AdjustResults( int numResults ) + { + if( numResults == LuaDef.LUA_MULTRET && + CI.TopIndex < Top.Index ) + { + CI.TopIndex = Top.Index; + } + } + + ThreadStatus ILuaAPI.PCall( int numArgs, int numResults, int errFunc) + { + return API.PCallK( numArgs, numResults, errFunc, 0, null ); + } + + ThreadStatus ILuaAPI.PCallK( int numArgs, int numResults, int errFunc, + int context, CSharpFunctionDelegate continueFunc ) + { + Utl.ApiCheck( continueFunc == null || !CI.IsLua, + "cannot use continuations inside hooks" ); + Utl.ApiCheckNumElems( this, numArgs + 1 ); + Utl.ApiCheck( Status == ThreadStatus.LUA_OK, + "cannot do calls on non-normal thread" ); + CheckResults( numArgs, numResults ); + + int func; + if( errFunc == 0 ) + func = 0; + else + { + StkId addr; + if( !Index2Addr( errFunc, out addr ) ) + Utl.InvalidIndex(); + + func = addr.Index; + } + + ThreadStatus status; + CallS c = new CallS(); + c.L = this; + c.FuncIndex = Top.Index - (numArgs + 1); + if( continueFunc == null || NumNonYieldable > 0 ) // no continuation or no yieldable? + { + c.NumResults = numResults; + status = D_PCall( DG_F_Call, ref c, c.FuncIndex, func ); + } + else + { + int ciIndex = CI.Index; + CI.ContinueFunc = continueFunc; + CI.Context = context; + CI.ExtraIndex = c.FuncIndex; + CI.OldAllowHook = AllowHook; + CI.OldErrFunc = ErrFunc; + ErrFunc = func; + CI.CallStatus |= CallStatus.CIST_YPCALL; + + D_Call( Stack[c.FuncIndex], numResults, true ); + + CallInfo ci = BaseCI[ciIndex]; + ci.CallStatus &= ~CallStatus.CIST_YPCALL; + ErrFunc = ci.OldErrFunc; + status = ThreadStatus.LUA_OK; + } + AdjustResults( numResults ); + return status; + } + + private void FinishCSharpCall() + { + CallInfo ci = CI; + Utl.Assert( ci.ContinueFunc != null ); // must have a continuation + Utl.Assert( NumNonYieldable == 0 ); + // finish `CallK' + AdjustResults( ci.NumResults ); + // call continuation function + if( (ci.CallStatus & CallStatus.CIST_STAT) == 0 ) // no call status? + { + ci.Status = ThreadStatus.LUA_YIELD; // `default' status + } + Utl.Assert( ci.Status != ThreadStatus.LUA_OK ); + ci.CallStatus = ( ci.CallStatus + & ~( CallStatus.CIST_YPCALL | CallStatus.CIST_STAT)) + | CallStatus.CIST_YIELDED; + + int n = ci.ContinueFunc( this ); // call + Utl.ApiCheckNumElems( this, n ); + // finish `D_PreCall' + D_PosCall( Top.Index-n ); + } + + private void Unroll() + { + while( true ) + { + if( CI.Index == 0 ) // stack is empty? + return; // coroutine finished normally + if( !CI.IsLua ) // C# function? + FinishCSharpCall(); + else // Lua function + { + V_FinishOp(); // finish interrupted instruction + V_Execute(); // execute down to higher C# `boundary' + } + } + } + struct UnrollParam + { + public LuaState L; + } + private static void UnrollWrap(ref UnrollParam param) + { + param.L.Unroll(); + } + private static PFuncDelegate DG_Unroll = UnrollWrap; + + private void ResumeError( string msg, int firstArg ) + { + Top = Stack[firstArg]; + Top.V.SetSValue(msg); + IncrTop(); + D_Throw( ThreadStatus.LUA_RESUME_ERROR ); + } + + // check whether thread has suspended protected call + private CallInfo FindPCall() + { + for(int i=CI.Index; i>=0; --i) { + var ci = BaseCI[i]; + if( (ci.CallStatus & CallStatus.CIST_YPCALL) != 0 ) + { return ci; } + } + return null; // no pending pcall + } + + private bool Recover( ThreadStatus status ) + { + CallInfo ci = FindPCall(); + if( ci == null ) // no recover point + return false; + + StkId oldTop = Stack[ci.ExtraIndex]; + F_Close( oldTop ); + SetErrorObj( status, oldTop ); + CI = ci; + AllowHook = ci.OldAllowHook; + NumNonYieldable = 0; + ErrFunc = ci.OldErrFunc; + ci.CallStatus |= CallStatus.CIST_STAT; + ci.Status = status; + return true; + } + + // do the work for `lua_resume' in protected mode + private void Resume(int firstArg) + { + int numCSharpCalls = NumCSharpCalls; + CallInfo ci = CI; + if( numCSharpCalls >= LuaLimits.LUAI_MAXCCALLS ) + ResumeError( "C stack overflow", firstArg ); + if( Status == ThreadStatus.LUA_OK ) // may be starting a coroutine + { + if( ci.Index > 0 ) // not in base level + { + ResumeError( "cannot resume non-suspended coroutine", firstArg ); + } + if( !D_PreCall( Stack[firstArg-1], LuaDef.LUA_MULTRET ) ) // Lua function? + { + V_Execute(); // call it + } + } + else if( Status != ThreadStatus.LUA_YIELD ) + { + ResumeError( "cannot resume dead coroutine", firstArg ); + } + else // resume from previous yield + { + Status = ThreadStatus.LUA_OK; + ci.FuncIndex = ci.ExtraIndex; + if( ci.IsLua ) // yielded inside a hook? + { + V_Execute(); // just continue running Lua code + } + else // `common' yield + { + if( ci.ContinueFunc != null ) + { + ci.Status = ThreadStatus.LUA_YIELD; // `default' status + ci.CallStatus |= CallStatus.CIST_YIELDED; + int n = ci.ContinueFunc( this ); // call continuation + Utl.ApiCheckNumElems( this, n ); + firstArg = Top.Index - n; // yield results come from continuation + } + D_PosCall(firstArg); + } + Unroll(); + } + Utl.Assert( numCSharpCalls == NumCSharpCalls ); + } + struct ResumeParam + { + public LuaState L; + public int firstArg; + } + private static void ResumeWrap(ref ResumeParam param) + { + param.L.Resume(param.firstArg); + } + private static PFuncDelegate DG_Resume = ResumeWrap; + + ThreadStatus ILuaAPI.Resume( ILuaState from, int numArgs ) + { + LuaState fromState = from as LuaState; + NumCSharpCalls = (fromState != null) ? fromState.NumCSharpCalls + 1 : 1; + NumNonYieldable = 0; // allow yields + + Utl.ApiCheckNumElems( this, (Status == ThreadStatus.LUA_OK) ? numArgs + 1 : numArgs ); + + var resumeParam = new ResumeParam(); + resumeParam.L = this; + resumeParam.firstArg = Top.Index-numArgs; + ThreadStatus status = D_RawRunProtected( DG_Resume, ref resumeParam ); + if( status == ThreadStatus.LUA_RESUME_ERROR ) // error calling `lua_resume'? + { + status = ThreadStatus.LUA_ERRRUN; + } + else // yield or regular error + { + while( status != ThreadStatus.LUA_OK && status != ThreadStatus.LUA_YIELD ) // error? + { + if( Recover( status ) ) // recover point? + { + var unrollParam = new UnrollParam(); + unrollParam.L = this; + status = D_RawRunProtected( DG_Unroll, ref unrollParam ); + } + else // unrecoverable error + { + Status = status; // mark thread as `dead' + SetErrorObj( status, Top ); + CI.TopIndex = Top.Index; + break; + } + } + Utl.Assert( status == Status ); + } + + NumNonYieldable = 1; // do not allow yields + NumCSharpCalls--; + Utl.Assert( NumCSharpCalls == ((fromState != null) ? fromState.NumCSharpCalls : 0) ); + return status; + } + + int ILuaAPI.Yield( int numResults ) + { + return API.YieldK( numResults, 0, null ); + } + + int ILuaAPI.YieldK( int numResults, + int context, CSharpFunctionDelegate continueFunc ) + { + CallInfo ci = CI; + Utl.ApiCheckNumElems( this, numResults ); + + if( NumNonYieldable > 0 ) + { + if( this != G.MainThread ) + G_RunError( "attempt to yield across metamethod/C-call boundary" ); + else + G_RunError( "attempt to yield from outside a coroutine" ); + } + Status = ThreadStatus.LUA_YIELD; + ci.ExtraIndex = ci.FuncIndex; // save current `func' + if( ci.IsLua ) // inside a hook + { + Utl.ApiCheck( continueFunc == null, "hooks cannot continue after yielding" ); + } + else + { + ci.ContinueFunc = continueFunc; + if( ci.ContinueFunc != null ) // is there a continuation + { + ci.Context = context; + } + ci.FuncIndex = Top.Index - (numResults + 1); + D_Throw( ThreadStatus.LUA_YIELD ); + } + Utl.Assert( (ci.CallStatus & CallStatus.CIST_HOOKED) != 0 ); // must be inside a hook + return 0; + } + + + int ILuaAPI.AbsIndex( int index ) + { + return (index > 0 || index <= LuaDef.LUA_REGISTRYINDEX) + ? index + : Top.Index - CI.FuncIndex + index; + } + + int ILuaAPI.GetTop() + { + return Top.Index - (CI.FuncIndex + 1); + } + + void ILuaAPI.SetTop( int index ) + { + if( index >= 0 ) + { + Utl.ApiCheck(index <= StackLast-(CI.FuncIndex+1), "new top too large"); + int newTop = CI.FuncIndex+1+index; + for(int i=Top.Index; i p.Index) { + Stack[i].V.SetObj( ref Stack[i-1].V ); + i--; + } + p.V.SetObj(ref Top.V); + } + + private void MoveTo( StkId fr, int index ) + { + StkId to; + if( !Index2Addr( index, out to ) ) + Utl.InvalidIndex(); + + to.V.SetObj(ref fr.V); + } + + void ILuaAPI.Replace( int index ) + { + Utl.ApiCheckNumElems( this, 1 ); + MoveTo( Stack[Top.Index-1], index ); + Top = Stack[Top.Index-1]; + } + + void ILuaAPI.Copy( int fromIndex, int toIndex ) + { + StkId fr; + if( !Index2Addr( fromIndex, out fr ) ) + Utl.InvalidIndex(); + MoveTo( fr, toIndex ); + } + + void ILuaAPI.XMove( ILuaState to, int n ) + { + var toLua = to as LuaState; + if( (LuaState)this == toLua ) + return; + + Utl.ApiCheckNumElems( this, n ); + Utl.ApiCheck( G == toLua.G, "moving among independent states" ); + Utl.ApiCheck( toLua.CI.TopIndex - toLua.Top.Index >= n, "not enough elements to move" ); + + int index = Top.Index-n; + Top = Stack[index]; + for(int i=0; i DG_GrowStack = GrowStackWrap; + + bool ILuaAPI.CheckStack(int size) + { + bool res = false; + + if(StackLast - Top.Index > size) + { res = true; } + // need to grow stack + else { + int inuse = Top.Index + LuaDef.EXTRA_STACK; + if(inuse > LuaConf.LUAI_MAXSTACK - size) + res = false; + else { + var param = new GrowStackParam(); + param.L = this; + param.size = size; + res = D_RawRunProtected(DG_GrowStack, ref param)==ThreadStatus.LUA_OK; + } + } + + if(res && CI.TopIndex < Top.Index + size) + CI.TopIndex = Top.Index + size; // adjust frame top + + return res; + } + + int ILuaAPI.Error() + { + Utl.ApiCheckNumElems( this, 1 ); + G_ErrorMsg(); + return 0; + } + + int ILuaAPI.UpvalueIndex( int i ) + { + return LuaDef.LUA_REGISTRYINDEX - i; + } + + private string AuxUpvalue(StkId addr, int n, out StkId val) + { + val = null; + + if(!addr.V.TtIsFunction()) + return null; + + if(addr.V.ClIsLuaClosure()) { + var f = addr.V.ClLValue(); + var p = f.Proto; + if(!(1 <= n && n <= p.Upvalues.Count)) + return null; + val = f.Upvals[n-1].V; + var name = p.Upvalues[n-1].Name; + return (name == null) ? "" : name; + } + else if(addr.V.ClIsCsClosure()) { + var f = addr.V.ClCsValue(); + if(!(1 <= n && n <= f.Upvals.Length)) + return null; + val = f.Upvals[n-1]; + return ""; + } + else return null; + } + + string ILuaAPI.GetUpvalue( int funcIndex, int n ) + { + StkId addr; + if( !Index2Addr( funcIndex, out addr ) ) + return null; + + StkId val; + var name = AuxUpvalue(addr, n, out val); + if(name == null) + return null; + + Top.V.SetObj(ref val.V); + ApiIncrTop(); + return name; + } + + string ILuaAPI.SetUpvalue( int funcIndex, int n ) + { + StkId addr; + if( !Index2Addr( funcIndex, out addr ) ) + return null; + + Utl.ApiCheckNumElems( this, 1 ); + + StkId val; + var name = AuxUpvalue(addr, n, out val); + if(name == null) + return null; + + Top = Stack[Top.Index-1]; + val.V.SetObj(ref Top.V); + return name; + } + + void ILuaAPI.CreateTable( int narray, int nrec ) + { + var tbl = new LuaTable(this); + Top.V.SetHValue(tbl); + ApiIncrTop(); + if(narray > 0 || nrec > 0) + { tbl.Resize(narray, nrec); } + } + + void ILuaAPI.NewTable() + { + API.CreateTable( 0, 0 ); + } + + bool ILuaAPI.Next( int index ) + { + StkId addr; + if( !Index2Addr( index, out addr ) ) + throw new System.Exception("table expected"); + + var tbl = addr.V.HValue(); + if( tbl == null ) + throw new System.Exception("table expected"); + + var key = Stack[Top.Index-1]; + if( tbl.Next( key, Top ) ) + { + ApiIncrTop(); + return true; + } + else + { + Top = Stack[Top.Index-1]; + return false; + } + } + + void ILuaAPI.RawGetI( int index, int n ) + { + StkId addr; + if( !Index2Addr( index, out addr ) ) + Utl.ApiCheck( false, "table expected" ); + + var tbl = addr.V.HValue(); + Utl.ApiCheck( tbl != null, "table expected" ); + + Top.V.SetObj(ref tbl.GetInt(n).V); + ApiIncrTop(); + } + + // void ILuaAPI.DebugRawGetI( int index, int n ) + // { + // StkId addr; + // if( !Index2Addr( index, out addr ) ) + // Utl.ApiCheck( false, "table expected" ); + + // var tbl = addr.Value as LuaTable; + // Utl.ApiCheck( tbl != null, "table expected" ); + + // var key = new LuaNumber( n ); + // LuaObject outKey, outValue; + // tbl.DebugGet( key, out outKey, out outValue ); + // Top.Value = outKey; + // ApiIncrTop(); + // Top.Value = outValue; + // ApiIncrTop(); + // } + + string ILuaAPI.DebugGetInstructionHistory() + { +#if DEBUG_RECORD_INS + var sb = new System.Text.StringBuilder(); + foreach( var i in InstructionHistory ) { + sb.AppendLine( i.ToString() ); + } + return sb.ToString(); +#else + return "DEBUG_RECORD_INS not defined"; +#endif + } + + void ILuaAPI.RawGet( int index ) + { + StkId addr; + if( !Index2Addr( index, out addr ) ) + throw new System.Exception("table expected"); + + if(!addr.V.TtIsTable()) + throw new System.Exception("table expected"); + + var tbl = addr.V.HValue(); + var below = Stack[Top.Index-1]; + below.V.SetObj( ref tbl.Get( ref below.V ).V ); + } + + void ILuaAPI.RawSetI( int index, int n ) + { + Utl.ApiCheckNumElems( this, 1 ); + StkId addr; + if( !Index2Addr( index, out addr ) ) + Utl.InvalidIndex(); + Utl.ApiCheck( addr.V.TtIsTable(), "table expected" ); + var tbl = addr.V.HValue(); + tbl.SetInt( n, ref Stack[Top.Index-1].V ); + Top = Stack[Top.Index-1]; + } + + void ILuaAPI.RawSet( int index ) + { + Utl.ApiCheckNumElems( this, 2 ); + StkId addr; + if( !Index2Addr( index, out addr ) ) + Utl.InvalidIndex(); + Utl.ApiCheck( addr.V.TtIsTable(), "table expected" ); + var tbl = addr.V.HValue(); + tbl.Set( ref Stack[Top.Index-2].V, ref Stack[Top.Index-1].V ); + Top = Stack[Top.Index-2]; + } + + void ILuaAPI.GetField( int index, string key ) + { + StkId addr; + if( !Index2Addr( index, out addr ) ) + Utl.InvalidIndex(); + + Top.V.SetSValue(key); + var below = Top; + ApiIncrTop(); + V_GetTable( addr, below, below ); + } + + void ILuaAPI.SetField( int index, string key ) + { + StkId addr; + if( !Index2Addr( index, out addr ) ) + Utl.InvalidIndex(); + + StkId.inc(ref Top).V.SetSValue( key ); + V_SetTable( addr, Stack[Top.Index-1], Stack[Top.Index-2] ); + Top = Stack[Top.Index-2]; + } + + void ILuaAPI.GetTable( int index ) + { + StkId addr; + if(! Index2Addr( index, out addr ) ) + Utl.InvalidIndex(); + + var below = Stack[Top.Index - 1]; + V_GetTable( addr, below, below ); + } + + void ILuaAPI.SetTable( int index ) + { + StkId addr; + Utl.ApiCheckNumElems( this, 2 ); + if(! Index2Addr( index, out addr ) ) + Utl.InvalidIndex(); + + var key = Stack[Top.Index - 2]; + var val = Stack[Top.Index - 1]; + V_SetTable( addr, key, val); + Top = Stack[Top.Index-2]; + } + + void ILuaAPI.Concat( int n ) + { + Utl.ApiCheckNumElems( this, n ); + if( n >= 2 ) + { + V_Concat( n ); + } + else if( n == 0 ) + { + Top.V.SetSValue(""); + ApiIncrTop(); + } + } + + LuaType ILuaAPI.Type( int index ) + { + StkId addr; + if( !Index2Addr( index, out addr ) ) + return LuaType.LUA_TNONE; + + return (LuaType)addr.V.Tt; + } + + internal static string TypeName( LuaType t ) + { + switch( t ) + { + case LuaType.LUA_TNIL: + return "nil"; + + case LuaType.LUA_TBOOLEAN: + return "boolean"; + + case LuaType.LUA_TLIGHTUSERDATA: + return "userdata"; + + case LuaType.LUA_TUINT64: + return "UInt64"; + + case LuaType.LUA_TNUMBER: + return "number"; + + case LuaType.LUA_TSTRING: + return "string"; + + case LuaType.LUA_TTABLE: + return "table"; + + case LuaType.LUA_TFUNCTION: + return "function"; + + case LuaType.LUA_TUSERDATA: + return "userdata"; + + case LuaType.LUA_TTHREAD: + return "thread"; + + case LuaType.LUA_TPROTO: + return "proto"; + + case LuaType.LUA_TUPVAL: + return "upval"; + + default: + return "no value"; + } + } + + string ILuaAPI.TypeName( LuaType t ) + { + return TypeName(t); + } + + internal string ObjTypeName( ref TValue v ) + { + return TypeName((LuaType)v.Tt); + } + + // ApiIncrTop() Top CI.Top + internal void O_PushString( string s ) + { + Top.V.SetSValue(s); + IncrTop(); + } + + bool ILuaAPI.IsNil( int index ) + { + return API.Type( index ) == LuaType.LUA_TNIL; + } + + bool ILuaAPI.IsNone( int index ) + { + return API.Type( index ) == LuaType.LUA_TNONE; + } + + bool ILuaAPI.IsNoneOrNil( int index ) + { + LuaType t = API.Type( index ); + return t == LuaType.LUA_TNONE || + t == LuaType.LUA_TNIL; + } + + bool ILuaAPI.IsString( int index ) + { + LuaType t = API.Type( index ); + return( t == LuaType.LUA_TSTRING || t == LuaType.LUA_TNUMBER ); + } + + bool ILuaAPI.IsTable( int index ) + { + return API.Type( index ) == LuaType.LUA_TTABLE; + } + + bool ILuaAPI.IsFunction( int index ) + { + return API.Type( index ) == LuaType.LUA_TFUNCTION; + } + + bool ILuaAPI.Compare( int index1, int index2, LuaEq op ) + { + StkId addr1; + if( !Index2Addr( index1, out addr1 ) ) + Utl.InvalidIndex(); + + StkId addr2; + if( !Index2Addr( index2, out addr2 ) ) + Utl.InvalidIndex(); + + switch( op ) + { + case LuaEq.LUA_OPEQ: return EqualObj( ref addr1.V, ref addr2.V, false ); + case LuaEq.LUA_OPLT: return V_LessThan( addr1, addr2 ); + case LuaEq.LUA_OPLE: return V_LessEqual( addr1, addr2 ); + default: Utl.ApiCheck( false, "invalid option" ); return false; + } + } + + bool ILuaAPI.RawEqual( int index1, int index2 ) + { + StkId addr1; + if( !Index2Addr( index1, out addr1 ) ) + return false; + + StkId addr2; + if( !Index2Addr( index2, out addr2 ) ) + return false; + + return V_RawEqualObj( ref addr1.V, ref addr2.V ); + } + + int ILuaAPI.RawLen( int index ) + { + StkId addr; + if( !Index2Addr( index, out addr ) ) + Utl.InvalidIndex(); + + switch( addr.V.Tt ) + { + case (int)LuaType.LUA_TSTRING: + { + var s = addr.V.SValue(); + return s == null ? 0 : s.Length; + } + case (int)LuaType.LUA_TUSERDATA: + { + return addr.V.RawUValue().Length; + } + case (int)LuaType.LUA_TTABLE: + { + return addr.V.HValue().Length; + } + default: return 0; + } + } + + void ILuaAPI.Len( int index ) + { + StkId addr; + if( !Index2Addr( index, out addr ) ) + Utl.InvalidIndex(); + + V_ObjLen( Top, addr ); + + ApiIncrTop(); + } + + void ILuaAPI.PushNil() + { + Top.V.SetNilValue(); + ApiIncrTop(); + } + + void ILuaAPI.PushBoolean( bool b ) + { + Top.V.SetBValue( b ); + ApiIncrTop(); + } + + void ILuaAPI.PushNumber( double n ) + { + Top.V.SetNValue( n ); + ApiIncrTop(); + } + + void ILuaAPI.PushInteger( int n ) + { + Top.V.SetNValue( (double)n ); + ApiIncrTop(); + } + + void ILuaAPI.PushUnsigned( uint n ) + { + Top.V.SetNValue( (double)n ); + ApiIncrTop(); + } + + string ILuaAPI.PushString( string s ) + { + if( s == null ) + { + API.PushNil(); + return null; + } + else + { + Top.V.SetSValue(s); + ApiIncrTop(); + return s; + } + } + + void ILuaAPI.PushCSharpFunction( CSharpFunctionDelegate f ) + { + API.PushCSharpClosure( f, 0 ); + } + + void ILuaAPI.PushCSharpClosure( CSharpFunctionDelegate f, int n ) + { + if( n == 0 ) + { + Top.V.SetClCsValue( new LuaCsClosureValue( f ) ); + } + else + { + // UpValue C# function + Utl.ApiCheckNumElems( this, n ); + Utl.ApiCheck( n <= LuaLimits.MAXUPVAL, "upvalue index too large" ); + + LuaCsClosureValue cscl = new LuaCsClosureValue( f, n ); + int index = Top.Index - n; + Top = Stack[index]; + for(int i=0; i 0 ) + { + var addrIndex = ci.FuncIndex + index; + Utl.ApiCheck( index <= ci.TopIndex - (ci.FuncIndex + 1), "unacceptable index" ); + if( addrIndex >= Top.Index ) { + addr = default(StkId); + return false; + } + + addr = Stack[addrIndex]; + return true; + } + else if( index > LuaDef.LUA_REGISTRYINDEX ) + { + Utl.ApiCheck( index != 0 && -index <= Top.Index - (ci.FuncIndex + 1), "invalid index" ); + addr = Stack[Top.Index + index]; + return true; + } + else if( index == LuaDef.LUA_REGISTRYINDEX ) + { + addr = G.Registry; + return true; + } + // upvalues + else + { + index = LuaDef.LUA_REGISTRYINDEX - index; + Utl.ApiCheck( index <= LuaLimits.MAXUPVAL + 1, "upvalue index too large" ); + var func = Stack[ci.FuncIndex]; + Utl.Assert(func.V.TtIsFunction()); + + if(func.V.ClIsLcsClosure()) { + addr = default(StkId); + return false; + } + + Utl.Assert(func.V.ClIsCsClosure()); + var clcs = func.V.ClCsValue(); + if(index > clcs.Upvals.Length) { + addr = default(StkId); + return false; + } + + addr = clcs.Upvals[index-1]; + return true; + } + } + + // private void RegisterGlobalFunc( string name, CSharpFunctionDelegate f ) + // { + // API.PushCSharpFunction( f ); + // API.SetGlobal( name ); + // } + + } + +} + diff --git a/UniLua/LuaAuxLib.cs b/UniLua/LuaAuxLib.cs new file mode 100644 index 0000000..ec6dbd1 --- /dev/null +++ b/UniLua/LuaAuxLib.cs @@ -0,0 +1,754 @@ + +namespace UniLua +{ + using System; + using System.IO; + using System.Collections.Generic; + using System.Text; + using System.Xml.Linq; + using Cosmos.HAL.Drivers.Video.SVGAII; + + public struct NameFuncPair + { + public string Name; + public CSharpFunctionDelegate Func; + + public NameFuncPair( string name, CSharpFunctionDelegate func ) + { + Name = name; + Func = func; + } + } + + public interface ILuaAuxLib + { + void L_Where( int level ); + int L_Error( string fmt, params object[] args ); + void L_CheckStack( int size, string msg ); + void L_CheckAny( int narg ); + void L_CheckType( int index, LuaType t ); + double L_CheckNumber( int narg ); + UInt64 L_CheckUInt64( int narg ); + int L_CheckInteger( int narg ); + string L_CheckString( int narg ); + uint L_CheckUnsigned( int narg ); + void L_ArgCheck( bool cond, int narg, string extraMsg ); + int L_ArgError( int narg, string extraMsg ); + string L_TypeName( int index ); + + string L_ToString( int index ); + bool L_GetMetaField( int index, string method ); + int L_GetSubTable( int index, string fname ); + + void L_RequireF( string moduleName, CSharpFunctionDelegate openFunc, bool global ); + void L_OpenLibs(); + void L_NewLibTable( NameFuncPair[] define ); + void L_NewLib( NameFuncPair[] define ); + void L_SetFuncs( NameFuncPair[] define, int nup ); + + int L_Opt(int n, int def); + + int L_OptInt( int narg, int def ); + string L_OptString( int narg, string def ); + bool L_CallMeta( int obj, string name ); + void L_Traceback( ILuaState otherLua, string msg, int level ); + int L_Len( int index ); + + ThreadStatus L_LoadBuffer( string s, string name ); + ThreadStatus L_LoadBufferX( string s, string name, string mode ); + ThreadStatus L_LoadFile( string filename ); + ThreadStatus L_LoadFileX( string filename, string mode ); + + ThreadStatus L_LoadString( string s ); + ThreadStatus L_LoadBytes( byte[] bytes, string name); + ThreadStatus L_DoByteArray(byte[] file, string name); + ThreadStatus L_DoString( string s ); + ThreadStatus L_DoFile( string filename ); + + + string L_Gsub( string src, string pattern, string rep ); + + // reference system + int L_Ref( int t ); + void L_Unref( int t, int reference ); + } + + class StringLoadInfo : ILoadInfo + { + public StringLoadInfo(string s ) + { + Str = s; + Pos = 0; + } + + public int ReadByte() + { + if( Pos >= Str.Length ) + return -1; + else + return Str[Pos++]; + } + + public int PeekByte() + { + if( Pos >= Str.Length ) + return -1; + else + return Str[Pos]; + } + + private string Str; + private int Pos; + } + + class BytesLoadInfo : ILoadInfo + { + public BytesLoadInfo( byte[] bytes ) + { + Bytes = bytes; + Pos = 0; + } + + public int ReadByte() + { + if( Pos >= Bytes.Length ) + return -1; + else + return Bytes[Pos++]; + } + + public int PeekByte() + { + if( Pos >= Bytes.Length ) + return -1; + else + return Bytes[Pos]; + } + + private byte[] Bytes; + private int Pos; + } + + public partial class LuaState + { + private const int LEVELS1 = 12; // size of the first part of the stack + private const int LEVELS2 = 10; // size of the second part of the stack + + public void L_Where( int level ) + { + LuaDebug ar = new LuaDebug(); + if( API.GetStack( level, ar ) ) // check function at level + { + GetInfo( "Sl", ar ); // get info about it + if( ar.CurrentLine > 0 ) // is there info? + { + API.PushString( string.Format( "{0}:{1}: ", ar.ShortSrc, ar.CurrentLine ) ); + return; + } + } + API.PushString( "" ); // else, no information available... + } + + public int L_Error( string fmt, params object[] args ) + { + L_Where( 1 ); + API.PushString( string.Format( fmt, args ) ); + API.Concat( 2 ); + return API.Error(); + } + + public void L_CheckStack( int size, string msg ) + { + // keep some extra space to run error routines, if needed + if(!API.CheckStack(size + LuaDef.LUA_MINSTACK)) { + if(msg != null) + { L_Error(string.Format("stack overflow ({0})", msg)); } + else + { L_Error("stack overflow"); } + } + } + + public void L_CheckAny( int narg ) + { + if( API.Type( narg ) == LuaType.LUA_TNONE ) + L_ArgError( narg, "value expected" ); + } + + public double L_CheckNumber( int narg ) + { + bool isnum; + double d = API.ToNumberX( narg, out isnum ); + if( !isnum ) + TagError( narg, LuaType.LUA_TNUMBER ); + return d; + } + + public UInt64 L_CheckUInt64( int narg ) + { + bool isnum; + UInt64 v = API.ToUInt64X( narg, out isnum ); + if( !isnum ) + TagError( narg, LuaType.LUA_TUINT64 ); + return v; + } + + public int L_CheckInteger( int narg ) + { + bool isnum; + int d = API.ToIntegerX( narg, out isnum ); + if( !isnum ) + TagError( narg, LuaType.LUA_TNUMBER ); + return d; + } + + public string L_CheckString( int narg ) + { + string s = API.ToString( narg ); + if( s == null ) TagError( narg, LuaType.LUA_TSTRING ); + return s; + } + + public uint L_CheckUnsigned( int narg ) + { + bool isnum; + uint d = API.ToUnsignedX( narg, out isnum ); + if( !isnum ) + TagError( narg, LuaType.LUA_TNUMBER ); + return d; + } + + public int L_Opt(int n, int def) + { + LuaType t = API.Type(n); + if (t == LuaType.LUA_TNONE || t == LuaType.LUA_TNIL) + { + return def; + } + else + { + return L_CheckInteger(n); + } + } + + public int L_OptInt( int narg, int def ) + { + LuaType t = API.Type( narg ); + if( t == LuaType.LUA_TNONE || + t == LuaType.LUA_TNIL ) + { + return def; + } + else + { + return L_CheckInteger( narg ); + } + } + + public string L_OptString( int narg, string def ) + { + LuaType t = API.Type( narg ); + if( t == LuaType.LUA_TNONE || + t == LuaType.LUA_TNIL ) + { + return def; + } + else + { + return L_CheckString( narg ); + } + } + + private int TypeError( int index, string typeName ) + { + string msg = string.Format( "{0} expected, got {1}", + typeName, L_TypeName( index ) ); + API.PushString( msg ); + return L_ArgError( index, msg ); + } + + private void TagError( int index, LuaType t ) + { + TypeError( index, API.TypeName( t ) ); + } + + public void L_CheckType( int index, LuaType t ) + { + if( API.Type( index ) != t ) + TagError( index, t ); + } + + public void L_ArgCheck( bool cond, int narg, string extraMsg ) + { + if( !cond ) + L_ArgError( narg, extraMsg ); + } + + public int L_ArgError( int narg, string extraMsg ) + { + + LuaDebug ar = new LuaDebug(); + if( !API.GetStack( 0, ar ) ) // no stack frame ? + return L_Error( "bad argument {0} ({1})", narg, extraMsg ); + + GetInfo( "n", ar ); + if( ar.NameWhat == "method" ) + { + narg--; // do not count 'self' + if( narg == 0 ) // error is in the self argument itself? + return L_Error( "calling '{0}' on bad self", ar.Name ); + } + if( ar.Name == null ) + ar.Name = PushGlobalFuncName( ar ) ? API.ToString(-1) : "?"; + return L_Error( "bad argument {0} to '{1}' ({2})", + narg, ar.Name, extraMsg ); + } + + public string L_TypeName( int index ) + { + return API.TypeName( API.Type( index ) ); + } + + public bool L_GetMetaField( int obj, string name ) + { + if( !API.GetMetaTable(obj) ) // no metatable? + return false; + API.PushString( name ); + API.RawGet( -2 ); + if( API.IsNil( -1 ) ) + { + API.Pop( 2 ); + return false; + } + else + { + API.Remove( -2 ); + return true; + } + } + + public bool L_CallMeta( int obj, string name ) + { + obj = API.AbsIndex( obj ); + if( !L_GetMetaField( obj, name ) ) // no metafield? + return false; + + API.PushValue( obj ); + API.Call( 1, 1 ); + return true; + } + + private void PushFuncName( LuaDebug ar ) + { + if( ar.NameWhat.Length > 0 && ar.NameWhat[0] != '\0' ) // is there a name? + API.PushString( string.Format( "function '{0}'", ar.Name ) ); + else if( ar.What.Length > 0 && ar.What[0] == 'm' ) // main? + API.PushString( "main chunk" ); + else if( ar.What.Length > 0 && ar.What[0] == 'C' ) + { + if( PushGlobalFuncName( ar ) ) + { + API.PushString( string.Format( "function '{0}'", API.ToString(-1) ) ); + API.Remove( -2 ); //remove name + } + else + API.PushString( "?" ); + } + else + API.PushString( string.Format( "function <{0}:{1}>", ar.ShortSrc, ar.LineDefined ) ); + } + + private int CountLevels() + { + LuaDebug ar = new LuaDebug(); + int li = 1; + int le = 1; + // find an upper bound + while( API.GetStack(le, ar) ) + { + li = le; + le *= 2; + } + // do a binary search + while(li < le) + { + int m = (li + le)/2; + if( API.GetStack( m, ar ) ) + li = m + 1; + else + le = m; + } + return le - 1; + } + + public void L_Traceback( ILuaState otherLua, string msg, int level ) + { + LuaState oLua = otherLua as LuaState; + LuaDebug ar = new LuaDebug(); + int top = API.GetTop(); + int numLevels = oLua.CountLevels(); + int mark = (numLevels > LEVELS1 + LEVELS2) ? LEVELS1 : 0; + if( msg != null ) + API.PushString( string.Format( "{0}\n", msg ) ); + API.PushString( "stack traceback:" ); + while( otherLua.GetStack( level++, ar ) ) + { + if( level == mark ) // too many levels? + { + API.PushString( "\n\t..." ); + level = numLevels - LEVELS2; // and skip to last ones + } + else + { + oLua.GetInfo( "Slnt", ar ); + API.PushString( string.Format( "\n\t{0}:", ar.ShortSrc ) ); + if( ar.CurrentLine > 0 ) + API.PushString( string.Format( "{0}:", ar.CurrentLine ) ); + API.PushString(" in "); + PushFuncName( ar ); + if( ar.IsTailCall ) + API.PushString( "\n\t(...tail calls...)" ); + API.Concat( API.GetTop() - top ); + } + } + API.Concat( API.GetTop() - top ); + } + + public int L_Len( int index ) + { + API.Len( index ); + + bool isnum; + int l = (int)API.ToIntegerX( -1, out isnum ); + if( !isnum ) + L_Error( "object length is not a number" ); + API.Pop( 1 ); + return l; + } + + public ThreadStatus L_LoadBuffer( string s, string name ) + { + return L_LoadBufferX( s, name, null ); + } + + public ThreadStatus L_LoadBufferX( string s, string name, string mode ) + { + var loadinfo = new StringLoadInfo( s ); + return API.Load( loadinfo, name, mode ); + } + + public ThreadStatus L_LoadBytes( byte[] bytes, string name) + { + LuaFile.VirtualFiles.Add(name, bytes); + + var loadinfo = new BytesLoadInfo( bytes ); + return API.Load( loadinfo, name, null ); + } + + private ThreadStatus ErrFile( string what, int fnameindex ) + { + return ThreadStatus.LUA_ERRFILE; + } + + public ThreadStatus L_LoadFile( string filename ) + { + return L_LoadFileX( filename, null ); + } + + public ThreadStatus L_LoadFileX( string filename, string mode ) + { + var status = ThreadStatus.LUA_OK; + if( filename == null ) + { + // stdin + throw new System.NotImplementedException(); + } + + int fnameindex = API.GetTop() + 1; + API.PushString( "@" + filename ); + try + { + var loadinfo = LuaFile.OpenFile(filename); + + if (loadinfo.GetType() == typeof(FileLoadInfo)) + { + ((FileLoadInfo)loadinfo).SkipComment(); + status = API.Load(((FileLoadInfo)loadinfo), API.ToString(-1), mode); + ((FileLoadInfo)loadinfo).Dispose(); + } + else if (loadinfo.GetType() == typeof(BytesLoadInfo)) + { + status = API.Load(loadinfo, filename, null); + } + } + catch( LuaRuntimeException e ) + { + API.PushString( string.Format( "cannot open {0}: {1}", + filename, e.Message ) ); + return ThreadStatus.LUA_ERRFILE; + } + + API.Remove( fnameindex ); + return status; + } + + public ThreadStatus L_LoadString( string s ) + { + return L_LoadBuffer( s, s ); + } + + public ThreadStatus L_DoByteArray(byte[] file, string name) + { + var status = L_LoadBytes(file, name); + if (status != ThreadStatus.LUA_OK) + return status; + return API.PCall(0, LuaDef.LUA_MULTRET, 0); + } + + public ThreadStatus L_DoString( string s ) + { + var status = L_LoadString( s ); + if( status != ThreadStatus.LUA_OK ) + return status; + return API.PCall( 0, LuaDef.LUA_MULTRET, 0 ); + } + + public ThreadStatus L_DoFile( string filename ) + { + var status = L_LoadFile( filename ); + if( status != ThreadStatus.LUA_OK ) + return status; + return API.PCall( 0, LuaDef.LUA_MULTRET, 0 ); + } + + public string L_Gsub( string src, string pattern, string rep ) + { + string res = src.Replace(pattern, rep); + API.PushString( res ); + return res; + } + + public string L_ToString( int index ) + { + if( !L_CallMeta( index, "__tostring" ) ) // no metafield? // TODO L_CallMeta + { + switch( API.Type(index) ) + { + case LuaType.LUA_TNUMBER: + case LuaType.LUA_TSTRING: + API.PushValue( index ); + break; + + case LuaType.LUA_TBOOLEAN: + API.PushString( API.ToBoolean( index ) ? "true" : "false" ); + break; + + case LuaType.LUA_TNIL: + API.PushString( "nil" ); + break; + + default: + API.PushString( string.Format("{0}: {1:X}" + , L_TypeName( index ) + , API.ToObject( index ).GetHashCode() + ) ); + break; + } + } + return API.ToString( -1 ); + } + + // private static class LibLoadInfo + // { + // public static List Items; + + // static LibLoadInfo() + // { + // Items = new List(); + // Add( "_G", LuaState.LuaOpen_Base ); + // } + + // private static void Add( string name, CSharpFunctionDelegate loadFunc ) + // { + // Items.Add( new NameFuncPair { Name=name, LoadFunc=loadFunc } ); + // } + // } + + public void L_OpenLibs() + { + NameFuncPair[] define = new NameFuncPair[] + { + new NameFuncPair( "_G", LuaBaseLib.OpenLib ), + new NameFuncPair( LuaPkgLib.LIB_NAME, LuaPkgLib.OpenLib ), + new NameFuncPair( LuaCoroLib.LIB_NAME, LuaCoroLib.OpenLib ), + new NameFuncPair( LuaTableLib.LIB_NAME, LuaTableLib.OpenLib ), + new NameFuncPair( LuaIOLib.LIB_NAME, LuaIOLib.OpenLib ), + new NameFuncPair( LuaOSLib.LIB_NAME, LuaOSLib.OpenLib ), + // {LUA_OSLIBNAME, luaopen_os}, + new NameFuncPair( LuaStrLib.LIB_NAME, LuaStrLib.OpenLib ), + new NameFuncPair( LuaBitLib.LIB_NAME, LuaBitLib.OpenLib ), + new NameFuncPair( LuaMathLib.LIB_NAME, LuaMathLib.OpenLib ), + new NameFuncPair( LuaDebugLib.LIB_NAME, LuaDebugLib.OpenLib ), + new NameFuncPair( LuaEncLib.LIB_NAME, LuaEncLib.OpenLib ), + }; + + for( var i=0; i= 0 ) + { + t = API.AbsIndex(t); + API.RawGetI(t, FreeList); + API.RawSetI(t, reference); // t[ref] = t[freelist] + API.PushInteger(reference); + API.RawSetI(t, FreeList); // t[freelist] = ref + } + } + +#if UNITY_IPHONE + public void FEED_AOT_FOR_IOS(LuaState lua) + { + lua.L_Opt( lua.L_CheckInteger, 1, 1); + } +#endif + + } + +} + diff --git a/UniLua/LuaBaseLib.cs b/UniLua/LuaBaseLib.cs new file mode 100644 index 0000000..c23a1fc --- /dev/null +++ b/UniLua/LuaBaseLib.cs @@ -0,0 +1,441 @@ + +namespace UniLua +{ + using System.Collections.Generic; + using ULDebug = UniLua.Tools.ULDebug; + using StringBuilder = System.Text.StringBuilder; + using Char = System.Char; + using Int32 = System.Int32; + using System; + + internal static class LuaBaseLib + { + internal static int OpenLib( ILuaState lua ) + { + NameFuncPair[] define = new NameFuncPair[] + { + new NameFuncPair( "assert", LuaBaseLib.B_Assert ), + new NameFuncPair( "collectgarbage", LuaBaseLib.B_CollectGarbage ), + new NameFuncPair( "dofile", LuaBaseLib.B_DoFile ), + new NameFuncPair( "error", LuaBaseLib.B_Error ), + new NameFuncPair( "ipairs", LuaBaseLib.B_Ipairs ), + new NameFuncPair( "loadfile", LuaBaseLib.B_LoadFile ), + new NameFuncPair( "load", LuaBaseLib.B_Load ), + new NameFuncPair( "loadstring", LuaBaseLib.B_Load ), + new NameFuncPair( "next", LuaBaseLib.B_Next ), + new NameFuncPair( "pairs", LuaBaseLib.B_Pairs ), + new NameFuncPair( "pcall", LuaBaseLib.B_PCall ), + new NameFuncPair( "print", LuaBaseLib.B_Print ), + new NameFuncPair( "rawequal", LuaBaseLib.B_RawEqual ), + new NameFuncPair( "rawlen", LuaBaseLib.B_RawLen ), + new NameFuncPair( "rawget", LuaBaseLib.B_RawGet ), + new NameFuncPair( "rawset", LuaBaseLib.B_RawSet ), + new NameFuncPair( "select", LuaBaseLib.B_Select ), + new NameFuncPair( "getmetatable", LuaBaseLib.B_GetMetaTable ), + new NameFuncPair( "setmetatable", LuaBaseLib.B_SetMetaTable ), + new NameFuncPair( "tonumber", LuaBaseLib.B_ToNumber ), + new NameFuncPair( "tostring", LuaBaseLib.B_ToString ), + new NameFuncPair( "type", LuaBaseLib.B_Type ), + new NameFuncPair( "xpcall", LuaBaseLib.B_XPCall ), + }; + + // set global _G + lua.PushGlobalTable(); + lua.PushGlobalTable(); + lua.SetField( -2, "_G" ); + + // open lib into global lib + lua.L_SetFuncs( define, 0 ); + // lua.RegisterGlobalFunc( "type", LuaBaseLib.B_Type ); + // lua.RegisterGlobalFunc( "pairs", LuaBaseLib.B_Pairs ); + // lua.RegisterGlobalFunc( "ipairs", LuaBaseLib.B_Ipairs ); + // lua.RegisterGlobalFunc( "print", LuaBaseLib.B_Print ); + // lua.RegisterGlobalFunc( "tostring", LuaBaseLib.B_ToString ); + + lua.PushString( LuaDef.LUA_VERSION ); + lua.SetField( -2, "_VERSION" ); + + return 1; + } + + public static int B_Assert( ILuaState lua ) + { + if( !lua.ToBoolean( 1 ) ) + return lua.L_Error( "{0}", lua.L_OptString( 2, "assertion failed!" ) ); + return lua.GetTop(); + } + + public static int B_CollectGarbage( ILuaState lua ) + { + // not implement gc + string opt = lua.L_OptString( 1, "collect" ); + switch( opt ) + { + case "count": + lua.PushNumber( 0 ); + lua.PushNumber( 0 ); + return 2; + + case "step": + case "isrunning": + lua.PushBoolean( true ); + return 1; + + default: + lua.PushInteger( 0 ); + return 1; + } + } + + private static int DoFileContinuation( ILuaState lua ) + { + return lua.GetTop() - 1; + } + + public static int B_DoFile( ILuaState lua ) + { + string filename = lua.L_OptString( 1, null ); + lua.SetTop( 1 ); + if( lua.L_LoadFile( filename ) != ThreadStatus.LUA_OK ) + lua.Error(); + lua.CallK( 0, LuaDef.LUA_MULTRET, 0, DoFileContinuation ); + return DoFileContinuation( lua ); + } + + public static int B_Error( ILuaState lua ) + { + int level = lua.L_OptInt( 2, 1 ); + lua.SetTop( 1 ); + if( lua.IsString( 1 ) && level > 0 ) + { + lua.L_Where( level ); + lua.PushValue( 1 ); + lua.Concat( 2 ); + } + return lua.Error(); + } + + private static int LoadAux( ILuaState lua, ThreadStatus status, int envidx ) + { + if( status == ThreadStatus.LUA_OK ) + { + if( envidx != 0 ) // `env' parameter? + { + lua.PushValue(envidx); // push `env' on stack + if( lua.SetUpvalue(-2, 1) == null ) // set `env' as 1st upvalue of loaded function + { + lua.Pop(1); // remove `env' if not used by previous call + } + } + return 1; + } + else // error (message is on top of the stack) + { + lua.PushNil(); + lua.Insert(-2); // put before error message + return 2; // return nil plus error message + } + } + + public static int B_LoadFile( ILuaState lua ) + { + string fname = lua.L_OptString( 1, null ); + string mode = lua.L_OptString( 2, null ); + int env = (!lua.IsNone(3) ? 3 : 0); // `env' index or 0 if no `env' + var status = lua.L_LoadFileX( fname, mode ); + return LoadAux(lua, status, env); + } + + public static int B_Load( ILuaState lua ) + { + ThreadStatus status; + string s = lua.ToString(1); + string mode = lua.L_OptString(3, "bt"); + int env = (! lua.IsNone(4) ? 4 : 0); // `env' index or 0 if no `env' + if( s != null ) + { + string chunkName = lua.L_OptString(2, s); + status = lua.L_LoadBufferX( s, chunkName, mode ); + } + else // loading from a reader function + { + throw new System.NotImplementedException(); // TODO + } + return LoadAux( lua, status, env ); + } + + private static int FinishPCall( ILuaState lua, bool status ) + { + // no space for extra boolean? + if(!lua.CheckStack(1)) { + lua.SetTop(0); // create space for return values + lua.PushBoolean(false); + lua.PushString("stack overflow"); + return 2; + } + lua.PushBoolean( status ); + lua.Replace( 1 ); + return lua.GetTop(); + } + + private static int PCallContinuation( ILuaState lua ) + { + int context; + ThreadStatus status = lua.GetContext( out context ); + return FinishPCall( lua, status == ThreadStatus.LUA_YIELD ); + } + private static CSharpFunctionDelegate DG_PCallContinuation = PCallContinuation; + + public static int B_PCall( ILuaState lua ) + { + lua.L_CheckAny( 1 ); + lua.PushNil(); + lua.Insert( 1 ); // create space for status result + + ThreadStatus status = lua.PCallK( lua.GetTop() - 2, + LuaDef.LUA_MULTRET, 0, 0, DG_PCallContinuation ); + + return FinishPCall( lua, status == ThreadStatus.LUA_OK ); + } + + public static int B_XPCall( ILuaState lua ) + { + int n = lua.GetTop(); + lua.L_ArgCheck( n>=2, 2, "value expected" ); + lua.PushValue( 1 ); // exchange function... + lua.Copy( 2, 1); // ...and error handler + lua.Replace( 2 ); + ThreadStatus status = lua.PCallK( n-2, LuaDef.LUA_MULTRET, + 1, 0, DG_PCallContinuation ); + return FinishPCall( lua, status == ThreadStatus.LUA_OK ); + } + + public static int B_RawEqual( ILuaState lua ) + { + lua.L_CheckAny( 1 ); + lua.L_CheckAny( 2 ); + lua.PushBoolean( lua.RawEqual( 1, 2 ) ); + return 1; + } + + public static int B_RawLen( ILuaState lua ) + { + LuaType t = lua.Type( 1 ); + lua.L_ArgCheck( t == LuaType.LUA_TTABLE || t == LuaType.LUA_TSTRING, + 1, "table or string expected" ); + lua.PushInteger( lua.RawLen( 1 ) ); + return 1; + } + + public static int B_RawGet( ILuaState lua ) + { + lua.L_CheckType( 1, LuaType.LUA_TTABLE ); + lua.L_CheckAny( 2 ); + lua.SetTop( 2 ); + lua.RawGet( 1 ); + return 1; + } + + public static int B_RawSet( ILuaState lua ) + { + lua.L_CheckType( 1, LuaType.LUA_TTABLE ); + lua.L_CheckAny( 2 ); + lua.L_CheckAny( 3 ); + lua.SetTop( 3 ); + lua.RawSet( 1 ); + return 1; + } + + public static int B_Select( ILuaState lua ) + { + int n = lua.GetTop(); + if( lua.Type( 1 ) == LuaType.LUA_TSTRING && + lua.ToString( 1 )[0] == '#' ) + { + lua.PushInteger( n-1 ); + return 1; + } + else + { + int i = lua.L_CheckInteger( 1 ); + if( i < 0 ) i = n + i; + else if( i > n ) i = n; + lua.L_ArgCheck( 1 <= i, 1, "index out of range" ); + return n - i; + } + } + + public static int B_GetMetaTable( ILuaState lua ) + { + lua.L_CheckAny( 1 ); + if( !lua.GetMetaTable( 1 ) ) + { + lua.PushNil(); + return 1; // no metatable + } + lua.L_GetMetaField( 1, "__metatable" ); + return 1; + } + + public static int B_SetMetaTable( ILuaState lua ) + { + LuaType t = lua.Type( 2 ); + lua.L_CheckType( 1, LuaType.LUA_TTABLE ); + lua.L_ArgCheck( t == LuaType.LUA_TNIL || t == LuaType.LUA_TTABLE, + 2, "nil or table expected" ); + if( lua.L_GetMetaField( 1, "__metatable" ) ) + return lua.L_Error( "cannot change a protected metatable" ); + lua.SetTop( 2 ); + lua.SetMetaTable( 1 ); + return 1; + } + + public static int B_ToNumber( ILuaState lua ) + { + LuaType t = lua.Type( 2 ); + if( t == LuaType.LUA_TNONE || t == LuaType.LUA_TNIL ) // standard conversion + { + bool isnum; + double n = lua.ToNumberX( 1, out isnum ); + if( isnum ) + { + lua.PushNumber( n ); + return 1; + } // else not a number; must be something + lua.L_CheckAny( 1 ); + } + else + { + string s = lua.L_CheckString( 1 ); + int numBase = lua.L_CheckInteger( 2 ); + bool negative = false; + lua.L_ArgCheck( (2 <= numBase && numBase <= 36), 2, + "base out of range" ); + s = s.Trim( ' ', '\f', '\n', '\r', '\t', '\v' ); + s = s + '\0'; // guard + int pos = 0; + if(s[pos] == '-') { pos++; negative = true; } + else if(s[pos] == '+') pos++; + if( Char.IsLetterOrDigit( s, pos ) ) + { + double n = 0.0; + do + { + int digit; + if( Char.IsDigit( s, pos ) ) + digit = Int32.Parse( s[pos].ToString() ); + else + digit = Char.ToUpper( s[pos] ) - 'A' + 10; + if( digit >= numBase ) + break; // invalid numeral; force a fail + n = n * (double)numBase + (double)digit; + pos++; + } while( Char.IsLetterOrDigit( s, pos ) ); + if( pos == s.Length - 1 ) // except guard, no invalid trailing characters? + { + lua.PushNumber( negative ? -n : n ); + return 1; + } // else not a number + } // else not a number + } + lua.PushNil(); // not a number + return 1; + } + + public static int B_Type( ILuaState lua ) + { + var t = lua.Type( 1 ); + var tname = lua.TypeName( t ); + lua.PushString( tname ); + return 1; + } + + private static int PairsMeta( ILuaState lua, string method, bool isZero + , CSharpFunctionDelegate iter ) + { + if( !lua.L_GetMetaField( 1, method ) ) // no metamethod? + { + lua.L_CheckType( 1, LuaType.LUA_TTABLE ); + lua.PushCSharpFunction( iter ); + lua.PushValue( 1 ); + if( isZero ) + lua.PushInteger( 0 ); + else + lua.PushNil(); + } + else + { + lua.PushValue( 1 ); + lua.Call( 1, 3 ); + } + return 3; + } + + public static int B_Next( ILuaState lua ) + { + lua.SetTop( 2 ); + if( lua.Next(1) ) + { + return 2; + } + else + { + lua.PushNil(); + return 1; + } + } + static CSharpFunctionDelegate DG_B_Next = B_Next; + + public static int B_Pairs( ILuaState lua ) + { + return PairsMeta( lua, "__pairs", false, DG_B_Next ); + } + + private static int IpairsAux( ILuaState lua ) + { + int i = lua.ToInteger( 2 ); + i++; // next value + lua.PushInteger( i ); + lua.RawGetI( 1, i ); + return lua.IsNil( -1 ) ? 1 : 2; + } + static CSharpFunctionDelegate DG_IpairsAux = IpairsAux; + + public static int B_Ipairs( ILuaState lua ) + { + return PairsMeta( lua, "__ipairs", true, DG_IpairsAux ); + } + + public static int B_Print( ILuaState lua ) + { + StringBuilder sb = new StringBuilder(); + int n = lua.GetTop(); + lua.GetGlobal( "tostring" ); + for( int i=1; i<=n; ++i ) + { + lua.PushValue( -1 ); + lua.PushValue( i ); + lua.Call( 1, 1 ); + string s = lua.ToString( -1 ); + if( s == null ) + return lua.L_Error("'tostring' must return a string to 'print'"); + if( i > 1 ) + sb.Append( "\t" ); + sb.Append( s ); + lua.Pop( 1 ); + } + Console.WriteLine( sb.ToString() ); + return 0; + } + + public static int B_ToString( ILuaState lua ) + { + lua.L_CheckAny( 1 ); + lua.L_ToString( 1 ); + return 1; + } + + } + +} + diff --git a/UniLua/LuaBitLib.cs b/UniLua/LuaBitLib.cs new file mode 100644 index 0000000..490be69 --- /dev/null +++ b/UniLua/LuaBitLib.cs @@ -0,0 +1,204 @@ +using UniLua; + +namespace UniLua +{ + + internal class LuaBitLib + { + public const string LIB_NAME = "bit32"; + private const int LUA_NBITS = 32; + private const uint ALLONES = ~(~(uint)0 << LUA_NBITS - 1 << 1); + + public static int OpenLib(ILuaState lua) + { + NameFuncPair[] define = new NameFuncPair[] + { + new NameFuncPair( "arshift", B_ArithShift ), + new NameFuncPair( "band", B_And ), + new NameFuncPair( "bnot", B_Not ), + new NameFuncPair( "bor", B_Or ), + new NameFuncPair( "bxor", B_Xor ), + new NameFuncPair( "btest", B_Test ), + new NameFuncPair( "extract", B_Extract ), + new NameFuncPair( "lrotate", B_LeftRotate ), + new NameFuncPair( "lshift", B_LeftShift ), + new NameFuncPair( "replace", B_Replace ), + new NameFuncPair( "rrotate", B_RightRotate ), + new NameFuncPair( "rshift", B_RightShift ), + }; + + lua.L_NewLib(define); + return 1; + } + + private static uint Trim(uint x) + { + return x & ALLONES; + } + + private static uint Mask(int n) + { + return ~(ALLONES << 1 << n - 1); + } + + private static int B_Shift(ILuaState lua, uint r, int i) + { + if (i < 0) // shift right? + { + i = -i; + r = Trim(r); + if (i >= LUA_NBITS) r = 0; + else r >>= i; + } + else // shift left + { + if (i >= LUA_NBITS) r = 0; + else r <<= i; + r = Trim(r); + } + lua.PushUnsigned(r); + return 1; + } + + private static int B_LeftShift(ILuaState lua) + { + return B_Shift(lua, lua.L_CheckUnsigned(1), lua.L_CheckInteger(2)); + } + + private static int B_RightShift(ILuaState lua) + { + return B_Shift(lua, lua.L_CheckUnsigned(1), -lua.L_CheckInteger(2)); + } + + private static int B_ArithShift(ILuaState lua) + { + uint r = lua.L_CheckUnsigned(1); + int i = lua.L_CheckInteger(2); + if (i < 0 || (r & (uint)1 << LUA_NBITS - 1) == 0) + return B_Shift(lua, r, -i); + else // arithmetic shift for `nagetive' number + { + if (i >= LUA_NBITS) + r = ALLONES; + else + r = Trim(r >> i | ~(~(uint)0 >> i)); // add signal bit + lua.PushUnsigned(r); + } + return 1; + } + + private static uint AndAux(ILuaState lua) + { + int n = lua.GetTop(); + uint r = ~(uint)0; + for (int i = 1; i <= n; ++i) + { + r &= lua.L_CheckUnsigned(i); + } + return Trim(r); + } + + private static int B_And(ILuaState lua) + { + uint r = AndAux(lua); + lua.PushUnsigned(r); + return 1; + } + + private static int B_Not(ILuaState lua) + { + uint r = ~lua.L_CheckUnsigned(1); + lua.PushUnsigned(Trim(r)); + return 1; + } + + private static int B_Or(ILuaState lua) + { + int n = lua.GetTop(); + uint r = 0; + for (int i = 1; i <= n; ++i) + { + r |= lua.L_CheckUnsigned(i); + } + lua.PushUnsigned(Trim(r)); + return 1; + } + + private static int B_Xor(ILuaState lua) + { + int n = lua.GetTop(); + uint r = 0; + for (int i = 1; i <= n; ++i) + { + r ^= lua.L_CheckUnsigned(i); + } + lua.PushUnsigned(Trim(r)); + return 1; + } + + private static int B_Test(ILuaState lua) + { + uint r = AndAux(lua); + lua.PushBoolean(r != 0); + return 1; + } + + private static int FieldArgs(ILuaState lua, int farg, out int width) + { + int f = lua.L_CheckInteger(farg); + int w = lua.L_OptInt(farg + 1, 1); + lua.L_ArgCheck(0 <= f, farg, "field cannot be nagetive"); + lua.L_ArgCheck(0 < w, farg + 1, "width must be positive"); + if (f + w > LUA_NBITS) + lua.L_Error("trying to access non-existent bits"); + width = w; + return f; + } + + private static int B_Extract(ILuaState lua) + { + uint r = lua.L_CheckUnsigned(1); + int w; + int f = FieldArgs(lua, 2, out w); + r = r >> f & Mask(w); + lua.PushUnsigned(r); + return 1; + } + + private static int B_Rotate(ILuaState lua, int i) + { + uint r = lua.L_CheckUnsigned(1); + i &= LUA_NBITS - 1; // i = i % NBITS + r = Trim(r); + r = r << i | r >> LUA_NBITS - i; + lua.PushUnsigned(Trim(r)); + return 1; + } + + private static int B_LeftRotate(ILuaState lua) + { + return B_Rotate(lua, lua.L_CheckInteger(2)); + } + + private static int B_RightRotate(ILuaState lua) + { + return B_Rotate(lua, -lua.L_CheckInteger(2)); + } + + private static int B_Replace(ILuaState lua) + { + uint r = lua.L_CheckUnsigned(1); + uint v = lua.L_CheckUnsigned(2); + int w; + int f = FieldArgs(lua, 3, out w); + uint m = Mask(w); + v &= m; //erase bits outside given width + r = r & ~(m << f) | v << f; + lua.PushUnsigned(r); + return 1; + } + + } + +} + diff --git a/UniLua/LuaCoroLib.cs b/UniLua/LuaCoroLib.cs new file mode 100644 index 0000000..3bf84b9 --- /dev/null +++ b/UniLua/LuaCoroLib.cs @@ -0,0 +1,152 @@ + +namespace UniLua +{ + + internal class LuaCoroLib + { + public const string LIB_NAME = "coroutine"; + + public static int OpenLib( ILuaState lua ) + { + NameFuncPair[] define = new NameFuncPair[] + { + new NameFuncPair( "create", CO_Create ), + new NameFuncPair( "resume", CO_Resume ), + new NameFuncPair( "running", CO_Running ), + new NameFuncPair( "status", CO_Status ), + new NameFuncPair( "wrap", CO_Wrap ), + new NameFuncPair( "yield", CO_Yield ), + }; + + lua.L_NewLib( define ); + return 1; + } + + private static int CO_Create( ILuaState lua ) + { + lua.L_CheckType( 1, LuaType.LUA_TFUNCTION ); + ILuaState newLua = lua.NewThread(); + lua.PushValue( 1 ); // move function to top + lua.XMove( newLua, 1 ); // move function from lua to newLua + return 1; + } + + private static int AuxResume( ILuaState lua, ILuaState co, int narg ) + { + if(!co.CheckStack(narg)) { + lua.PushString("too many arguments to resume"); + return -1; // error flag + } + if( co.Status == ThreadStatus.LUA_OK && co.GetTop() == 0 ) + { + lua.PushString( "cannot resume dead coroutine" ); + return -1; // error flag + } + lua.XMove( co, narg ); + ThreadStatus status = co.Resume( lua, narg ); + if( status == ThreadStatus.LUA_OK || status == ThreadStatus.LUA_YIELD ) + { + int nres = co.GetTop(); + if(!lua.CheckStack(nres+1)) { + co.Pop(nres); // remove results anyway; + lua.PushString("too many results to resume"); + return -1; // error flag + } + co.XMove( lua, nres ); // move yielded values + return nres; + } + else + { + co.XMove( lua, 1 ); // move error message + return -1; + } + } + + private static int CO_Resume( ILuaState lua ) + { + ILuaState co = lua.ToThread( 1 ); + lua.L_ArgCheck( co != null, 1, "coroutine expected" ); + int r = AuxResume( lua, co, lua.GetTop() - 1 ); + if( r < 0 ) + { + lua.PushBoolean( false ); + lua.Insert( -2 ); + return 2; // return false + error message + } + else + { + lua.PushBoolean( true ); + lua.Insert( -(r+1) ); + return r+1; // return true + `resume' returns + } + } + + private static int CO_Running( ILuaState lua ) + { + bool isMain = lua.PushThread(); + lua.PushBoolean( isMain ); + return 2; + } + + private static int CO_Status( ILuaState lua ) + { + ILuaState co = lua.ToThread( 1 ); + lua.L_ArgCheck( co != null, 1, "coroutine expected" ); + if( (LuaState)lua == (LuaState)co ) + lua.PushString( "running" ); + else switch( co.Status ) + { + case ThreadStatus.LUA_YIELD: + lua.PushString( "suspended" ); + break; + case ThreadStatus.LUA_OK: + { + LuaDebug ar = new LuaDebug(); + if( co.GetStack( 0, ar ) ) // does it have frames? + lua.PushString( "normal" ); + else if( co.GetTop() == 0 ) + lua.PushString( "dead" ); + else + lua.PushString( "suspended" ); + break; + } + default: // some error occurred + lua.PushString( "dead" ); + break; + } + return 1; + } + + private static int CO_AuxWrap( ILuaState lua ) + { + ILuaState co = lua.ToThread( lua.UpvalueIndex(1) ); + int r = AuxResume( lua, co, lua.GetTop() ); + if( r < 0 ) + { + if( lua.IsString( -1 ) ) // error object is a string? + { + lua.L_Where( 1 ); // add extra info + lua.Insert( -2 ); + lua.Concat( 2 ); + } + lua.Error(); + } + return r; + } + + private static int CO_Wrap( ILuaState lua ) + { + CO_Create( lua ); + lua.PushCSharpClosure( CO_AuxWrap, 1 ); + return 1; + } + + private static int CO_Yield( ILuaState lua ) + { + return lua.Yield( lua.GetTop() ); + } + + } + +} + diff --git a/UniLua/LuaDebug.cs b/UniLua/LuaDebug.cs new file mode 100644 index 0000000..9a267e3 --- /dev/null +++ b/UniLua/LuaDebug.cs @@ -0,0 +1,516 @@ + +namespace UniLua +{ + public class LuaDebug + { + public string Name; + public string NameWhat; + public int ActiveCIIndex; + public int CurrentLine; + public int NumUps; + public bool IsVarArg; + public int NumParams; + public bool IsTailCall; + public string Source; + public int LineDefined; + public int LastLineDefined; + public string What; + public string ShortSrc; + } + + public partial class LuaState + { + bool ILuaAPI.GetStack( int level, LuaDebug ar ) + { + if( level < 0 ) + return false; + + int index; + for( index = CI.Index; level > 0 && index > 0; --index ) + { level--; } + + bool status = false; + if( level == 0 && index > 0 ) { + status = true; + ar.ActiveCIIndex = index; + } + return status; + } + + public int GetInfo( string what, LuaDebug ar ) + { + CallInfo ci; + StkId func; + + int pos = 0; + if( what[pos] == '>' ) + { + ci = null; + func = Stack[Top.Index - 1]; + + Utl.ApiCheck(func.V.TtIsFunction(), "function expected"); + pos++; + + Top = Stack[Top.Index-1]; + } + else + { + ci = BaseCI[ar.ActiveCIIndex]; + func = Stack[ci.FuncIndex]; + Utl.Assert(Stack[ci.FuncIndex].V.TtIsFunction()); + } + + // var IsClosure( func.Value ) ? func.Value + int status = AuxGetInfo( what, ar, func, ci ); + if( what.Contains( "f" ) ) + { + Top.V.SetObj(ref func.V); + IncrTop(); + } + if( what.Contains( "L" ) ) + { + CollectValidLines( func ); + } + return status; + } + + private int AuxGetInfo( string what, LuaDebug ar, StkId func, CallInfo ci ) + { + int status = 1; + for( int i=0; i LuaDef.LUA_IDSIZE ) + { + ar.ShortSrc = ar.Source.Substring(0, LuaDef.LUA_IDSIZE); + } + else ar.ShortSrc = ar.Source; + } + + private void AddInfo( string msg ) + { + // var api = (ILuaAPI)this; + // TODO + if( CI.IsLua ) + { + var line = GetCurrentLine(CI); + var src = GetCurrentLuaFunc(CI).Proto.Source; + if( src == null ) + src = "?"; + + // PushString, PushString API + // API ApiIncrTop Top CI.Top + // api.PushString( msg ); + O_PushString( string.Format( "{0}:{1}: {2}", + src, line, msg ) ); + } + } + + internal void G_RunError( string fmt, params object[] args ) + { + AddInfo( string.Format( fmt, args ) ); + G_ErrorMsg(); + } + + private void G_ErrorMsg() + { + if( ErrFunc != 0 ) // is there an error handling function? + { + StkId errFunc = RestoreStack( ErrFunc ); + + if(!errFunc.V.TtIsFunction()) + D_Throw( ThreadStatus.LUA_ERRERR ); + + var below = Stack[Top.Index-1]; + Top.V.SetObj(ref below.V); + below.V.SetObj(ref errFunc.V); + IncrTop(); + + D_Call( below, 1, false ); + } + + D_Throw( ThreadStatus.LUA_ERRRUN ); + } + + private string UpvalName( LuaProto p, int uv ) + { + // TODO + return "(UpvalName:NotImplemented)"; + } + + private string GetUpvalueName( CallInfo ci, StkId o, out string name ) + { + var func = Stack[ci.FuncIndex]; + Utl.Assert(func.V.TtIsFunction() && func.V.ClIsLuaClosure()); + var lcl = func.V.ClLValue(); + for(int i=0; i= a+2 ) + setreg = pc; + break; + } + + case OpCode.OP_CALL: + case OpCode.OP_TAILCALL: { + // effect all registers above base + if( reg >= a ) + setreg = pc; + break; + } + + case OpCode.OP_JMP: { + var b = ins.GETARG_sBx(); + var dest = pc + 1 + b; + // jump is forward and do not skip `lastpc' + if( pc < dest && dest <= lastpc ) + pc += b; // do the jump + break; + } + + case OpCode.OP_TEST: { + // jumped code can change `a' + if( reg == a ) + setreg = pc; + break; + } + + default: { + // any instruction that set A + if( Coder.TestAMode( op ) && reg == a ) { + setreg = pc; + } + break; + } + } + } + return setreg; + } + + private string GetObjName( LuaProto proto, int lastpc, int reg, + out string name ) + { + name = F_GetLocalName( proto, reg+1, lastpc ); + if( name != null ) // is a local? + return "local"; + + // else try symbolic execution + var pc = FindSetReg( proto, lastpc, reg ); + if( pc != -1 ) + { + var ins = proto.Code[pc]; + var op = ins.GET_OPCODE(); + switch( op ) + { + case OpCode.OP_MOVE: { + var b = ins.GETARG_B(); // move from `b' to `a' + if( b < ins.GETARG_A() ) + return GetObjName(proto, pc, b, out name); + break; + } + case OpCode.OP_GETTABUP: + case OpCode.OP_GETTABLE: { + var k = ins.GETARG_C(); + var t = ins.GETARG_B(); + var vn = (op == OpCode.OP_GETTABLE) + ? F_GetLocalName( proto, t+1, pc ) + : UpvalName( proto, t ); + KName( proto, pc, k, out name ); + return (vn == LuaDef.LUA_ENV) ? "global" : "field"; + } + + case OpCode.OP_GETUPVAL: { + name = UpvalName( proto, ins.GETARG_B() ); + return "upvalue"; + } + + case OpCode.OP_LOADK: + case OpCode.OP_LOADKX: { + var b = (op == OpCode.OP_LOADK) + ? ins.GETARG_Bx() + : proto.Code[pc+1].GETARG_Ax(); + var val = proto.K[b]; + if(val.V.TtIsString()) + { + name = val.V.SValue(); + return "constant"; + } + break; + } + + case OpCode.OP_SELF: { + var k = ins.GETARG_C(); // key index + KName( proto, pc, k, out name ); + return "method"; + } + + default: break; // go through to return null + } + } + + return null; // could not find reasonable name + } + + private bool IsInStack( CallInfo ci, StkId o ) + { + // TODO + return false; + } + + private void G_SimpleTypeError( ref TValue o, string op ) + { + string t = ObjTypeName( ref o ); + G_RunError( "attempt to {0} a {1} value", op, t ); + } + + private void G_TypeError( StkId o, string op ) + { + CallInfo ci = CI; + string name = null; + string kind = null; + string t = ObjTypeName(ref o.V); + if( ci.IsLua ) + { + kind = GetUpvalueName( ci, o, out name); + if( kind != null && IsInStack( ci, o ) ) + { + var lcl = Stack[ci.FuncIndex].V.ClLValue(); + kind = GetObjName( lcl.Proto, ci.CurrentPc, + (o.Index - ci.BaseIndex), out name ); + } + } + if( kind != null ) + G_RunError( "attempt to {0} {1} '{2}' (a {3} value)", + op, kind, name, t ); + else + G_RunError( "attempt to {0} a {1} value", op, t ); + } + + private void G_ArithError( StkId p1, StkId p2 ) + { + var n = new TValue(); + if( !V_ToNumber( p1, ref n ) ) + { p2 = p1; } // first operand is wrong + + G_TypeError( p2, "perform arithmetic on" ); + } + + private void G_OrderError( StkId p1, StkId p2 ) + { + string t1 = ObjTypeName(ref p1.V); + string t2 = ObjTypeName(ref p2.V); + if( t1 == t2 ) + G_RunError( "attempt to compare two {0} values", t1 ); + else + G_RunError( "attempt to compare {0} with {1}", t1, t2 ); + } + + private void G_ConcatError( StkId p1, StkId p2 ) + { + // TODO + } + } + +} + diff --git a/UniLua/LuaDebugLib.cs b/UniLua/LuaDebugLib.cs new file mode 100644 index 0000000..728d203 --- /dev/null +++ b/UniLua/LuaDebugLib.cs @@ -0,0 +1,25 @@ + +namespace UniLua +{ + internal class LuaDebugLib + { + public const string LIB_NAME = "debug"; + + public static int OpenLib( ILuaState lua ) + { + NameFuncPair[] define = new NameFuncPair[] + { + new NameFuncPair( "traceback", DBG_Traceback ), + }; + + lua.L_NewLib( define ); + return 1; + } + + private static int DBG_Traceback( ILuaState lua ) + { + return 0; + } + } +} + diff --git a/UniLua/LuaEncLib.cs b/UniLua/LuaEncLib.cs new file mode 100644 index 0000000..8ec4efd --- /dev/null +++ b/UniLua/LuaEncLib.cs @@ -0,0 +1,63 @@ + using System; +using System.Text; + +namespace UniLua +{ + class LuaEncLib + { + public const string LIB_NAME = "enc"; + + private const string ENC_UTF8 = "utf8"; + + public static int OpenLib( ILuaState lua ) + { + var define = new NameFuncPair[] + { + new NameFuncPair( "encode", ENC_Encode ), + new NameFuncPair( "decode", ENC_Decode ), + }; + + lua.L_NewLib( define ); + + lua.PushString( ENC_UTF8 ); + lua.SetField( -2, "utf8" ); + + return 1; + } + + private static int ENC_Encode( ILuaState lua ) + { + var s = lua.ToString(1); + var e = lua.ToString(2); + if( e != ENC_UTF8 ) + throw new Exception("unsupported encoding:" + e); + + var bytes = Encoding.ASCII.GetBytes(s); + var sb = new StringBuilder(); + for( var i=0; i VirtualFiles; + + public static ILoadInfo OpenFile( string filename ) + { + foreach (var file in VirtualFiles.Keys) + { + if (file == filename) + { + return new BytesLoadInfo(VirtualFiles[filename]); ; + } + } + + return new FileLoadInfo( File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite ) ); + } + + public static bool Readable( string filename ) + { + foreach (var file in VirtualFiles.Keys) + { + if (file == filename) + { + return true; + } + } + + try { + using( var stream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite ) ) { + return true; + } + } + catch( Exception ) { + return false; + } + } + } + + public class FileLoadInfo : ILoadInfo, IDisposable + { + public FileLoadInfo( FileStream stream ) + { + Stream = stream; + Reader = new StreamReader(Stream, System.Text.Encoding.ASCII); + Buf = new Queue(); + } + + public int ReadByte() + { + if( Buf.Count > 0 ) + return (int)Buf.Dequeue(); + else + return Reader.Read(); + } + + public int PeekByte() + { + if( Buf.Count > 0 ) + return (int)Buf.Peek(); + else + { + var c = Reader.Read(); + if( c == -1 ) + return c; + Save( (char)c ); + return c; + } + } + + public void Dispose() + { + Reader.Dispose(); + Stream.Dispose(); + } + + private const string UTF8_BOM = "\u00EF\u00BB\u00BF"; + private FileStream Stream; + private StreamReader Reader; + private Queue Buf; + + private void Save( char b ) + { + Buf.Enqueue( b ); + } + + private void Clear() + { + Buf.Clear(); + } + +#if false + private int SkipBOM() + { + for( var i=0; i>>>>>>>>>>>>>>>>>>> level:" + level ); +#endif + + var node = OpenUpval.First; + LinkedListNode prev = null; + while( node != null ) + { + var upval = node.Value; +#if DEBUG_FIND_UPVALUE + Console.WriteLine("[F_FindUpval] >>>>>>>>>>>>>>>>>>>> upval.V:" + upval.V ); +#endif + if(upval.V.Index < level.Index) + break; + + var next = node.Next; + if(upval.V == level) + return upval; + + prev = node; + node = next; + } + + // not found: create a new one + var ret = new LuaUpvalue(); + ret.V = level; + // ret.Prev = G.UpvalHead; + // ret.Next = G.UpvalHead.Next; + // ret.Next.Prev = ret; + // G.UpvalHead.Next = ret; + + if( prev == null ) + OpenUpval.AddFirst( ret ); + else + OpenUpval.AddAfter( prev, ret ); + +#if DEBUG_FIND_UPVALUE + Console.WriteLine("[F_FindUpval] >>>>>>>>>>>>>>>>>>>> create new one:" + ret.V ); +#endif + + return ret; + } + + private void F_Close( StkId level ) + { + var node = OpenUpval.First; + while( node != null ) + { + var upval = node.Value; + if( upval.V.Index < level.Index ) + break; + + var next = node.Next; + OpenUpval.Remove( node ); + node = next; + + upval.Value.V.SetObj(ref upval.V.V); + upval.V = upval.Value; + } + } + + private string F_GetLocalName( LuaProto proto, int localNumber, int pc ) + { + for( int i=0; + i> 52) & 0x7ffL); + long mantissa = bits & 0xfffffffffffffL; + + // Subnormal numbers; exponent is effectively one higher, + // but there's no extra normalisation bit in the mantissa + if (exponent==0) + { + exponent++; + } + // Normal numbers; leave exponent as it is but add extra + // bit to the front of the mantissa + else + { + mantissa = mantissa | (1L<<52); + } + + // Bias the exponent. It's actually biased by 1023, but we're + // treating the mantissa as m.0 rather than 0.m, so we need + // to subtract another 52 from it. + exponent -= 1075; + + if (mantissa == 0) + { + lua.PushNumber( 0.0 ); + lua.PushNumber( 0.0 ); + return 2; + } + + /* Normalize */ + while((mantissa & 1) == 0) + { /* i.e., Mantissa is even */ + mantissa >>= 1; + exponent++; + } + + double m = (double)mantissa; + double e = (double)exponent; + while( m >= 1 ) + { + m /= 2.0; + e += 1.0; + } + + if( negative ) m = -m; + lua.PushNumber( m ); + lua.PushNumber( e ); + return 2; + } + + private static int Math_Ldexp( ILuaState lua ) + { + lua.PushNumber( lua.L_CheckNumber(1) * Math.Pow(2, lua.L_CheckNumber(2)) ); + return 1; + } + + private static int Math_Log10( ILuaState lua ) + { + lua.PushNumber( Math.Log10( lua.L_CheckNumber(1) ) ); + return 1; + } + + private static int Math_Log( ILuaState lua ) + { + double x = lua.L_CheckNumber(1); + double res; + if( lua.IsNoneOrNil(2) ) + res = Math.Log(x); + else + { + double logBase = lua.L_CheckNumber(2); + if( logBase == 10.0 ) + res = Math.Log10(x); + else + res = Math.Log(x, logBase); + } + lua.PushNumber(res); + return 1; + } + + private static int Math_Max( ILuaState lua ) + { + int n = lua.GetTop(); + double dmax = lua.L_CheckNumber(1); + for( int i=2; i<=n; ++i ) + { + double d = lua.L_CheckNumber(i); + if( d > dmax ) + dmax = d; + } + lua.PushNumber(dmax); + return 1; + } + + private static int Math_Min( ILuaState lua ) + { + int n = lua.GetTop(); + double dmin = lua.L_CheckNumber(1); + for( int i=2; i<=n; ++i ) + { + double d = lua.L_CheckNumber(i); + if( d < dmin ) + dmin = d; + } + lua.PushNumber(dmin); + return 1; + } + + private static int Math_Modf( ILuaState lua ) + { + double d = lua.L_CheckNumber(1); + double c = Math.Ceiling(d); + lua.PushNumber( c ); + lua.PushNumber( d-c ); + return 2; + } + + private static int Math_Pow( ILuaState lua ) + { + lua.PushNumber( Math.Pow( lua.L_CheckNumber(1), + lua.L_CheckNumber(2))); + return 1; + } + + private static int Math_Rad( ILuaState lua ) + { + lua.PushNumber( lua.L_CheckNumber(1) * RADIANS_PER_DEGREE ); + return 1; + } + + private static int Math_Random( ILuaState lua ) + { + double r = RandObj.NextDouble(); + switch( lua.GetTop() ) + { + case 0: // no argument + lua.PushNumber( r ); + break; + case 1: + { + double u = lua.L_CheckNumber(1); + lua.L_ArgCheck( 1.0 <= u, 1, "interval is empty" ); + lua.PushNumber( Math.Floor(r*u) + 1.0 ); // int in [1, u] + break; + } + case 2: + { + double l = lua.L_CheckNumber(1); + double u = lua.L_CheckNumber(2); + lua.L_ArgCheck( l <= u, 2, "interval is empty" ); + lua.PushNumber( Math.Floor(r*(u-l+1)) + l ); // int in [l, u] + break; + } + default: return lua.L_Error( "wrong number of arguments" ); + } + return 1; + } + + private static int Math_RandomSeed( ILuaState lua ) + { + RandObj = new Random( (int)lua.L_CheckUnsigned(1) ); + RandObj.Next(); + return 0; + } + + private static int Math_Sinh( ILuaState lua ) + { + lua.PushNumber( Math.Sinh( lua.L_CheckNumber(1) ) ); + return 1; + } + + private static int Math_Sin( ILuaState lua ) + { + lua.PushNumber( Math.Sin( lua.L_CheckNumber(1) ) ); + return 1; + } + + private static int Math_Sqrt( ILuaState lua ) + { + lua.PushNumber( Math.Sqrt( lua.L_CheckNumber(1) ) ); + return 1; + } + + private static int Math_Tanh( ILuaState lua ) + { + lua.PushNumber( Math.Tanh( lua.L_CheckNumber(1) ) ); + return 1; + } + + private static int Math_Tan( ILuaState lua ) + { + lua.PushNumber( Math.Tan( lua.L_CheckNumber(1) ) ); + return 1; + } + + } + +} + diff --git a/UniLua/LuaObject.cs b/UniLua/LuaObject.cs new file mode 100644 index 0000000..769f49d --- /dev/null +++ b/UniLua/LuaObject.cs @@ -0,0 +1,419 @@ + +// #define DEBUG_DUMMY_TVALUE_MODIFY + +namespace UniLua +{ + using System; + using System.Collections.Generic; + using ULDebug = UniLua.Tools.ULDebug; + + public struct TValue + { + private const UInt64 CLOSURE_LUA = 0; // lua closure + private const UInt64 CLOSURE_CS = 1; // c# closure + private const UInt64 CLOSURE_LCS = 2; // light c# closure + + private const UInt64 BOOLEAN_FALSE = 0; + private const UInt64 BOOLEAN_TRUE = 1; + + public int Tt; + public double NValue; + public UInt64 UInt64Value; + public object OValue; +#if DEBUG_DUMMY_TVALUE_MODIFY + public bool Lock_; +#endif + + public override int GetHashCode() + { + return Tt.GetHashCode() ^ NValue.GetHashCode() + ^ UInt64Value.GetHashCode() + ^ (OValue != null ? OValue.GetHashCode() : 0x12345678); + } + public override bool Equals(object o) + { + if(!(o is TValue)) return false; + return Equals((TValue)o); + } + public bool Equals(TValue o) + { + if(Tt != o.Tt || NValue != o.NValue || UInt64Value != o.UInt64Value) + { return false; } + + switch(Tt) { + case (int)LuaType.LUA_TNIL: return true; + case (int)LuaType.LUA_TBOOLEAN: return BValue() == o.BValue(); + case (int)LuaType.LUA_TNUMBER: return NValue == o.NValue; + case (int)LuaType.LUA_TUINT64: return UInt64Value == o.UInt64Value; + case (int)LuaType.LUA_TSTRING: return SValue() == o.SValue(); + default: return System.Object.ReferenceEquals(OValue, o.OValue); + } + } + public static bool operator==(TValue lhs, TValue rhs) + { + return lhs.Equals(rhs); + } + public static bool operator!=(TValue lhs, TValue rhs) + { + return !lhs.Equals(rhs); + } + +#if DEBUG_DUMMY_TVALUE_MODIFY + private void CheckLock() { + if(Lock_) { + UnityEngine.Console.WriteLineError("changing a lock value"); + } + } +#endif + + internal bool TtIsNil() { return Tt == (int)LuaType.LUA_TNIL; } + internal bool TtIsBoolean() { return Tt == (int)LuaType.LUA_TBOOLEAN; } + internal bool TtIsNumber() { return Tt == (int)LuaType.LUA_TNUMBER; } + internal bool TtIsUInt64() { return Tt == (int)LuaType.LUA_TUINT64; } + internal bool TtIsString() { return Tt == (int)LuaType.LUA_TSTRING; } + internal bool TtIsTable() { return Tt == (int)LuaType.LUA_TTABLE; } + internal bool TtIsFunction() { return Tt == (int)LuaType.LUA_TFUNCTION; } + internal bool TtIsThread() { return Tt == (int)LuaType.LUA_TTHREAD; } + + internal bool ClIsLuaClosure() { return UInt64Value == CLOSURE_LUA; } + internal bool ClIsCsClosure() { return UInt64Value == CLOSURE_CS; } + internal bool ClIsLcsClosure() { return UInt64Value == CLOSURE_LCS; } + + internal bool BValue() { return UInt64Value != BOOLEAN_FALSE; } + internal string SValue() { return (string)OValue; } + internal LuaTable HValue() { return OValue as LuaTable; } + internal LuaLClosureValue ClLValue() { return (LuaLClosureValue)OValue; } + internal LuaCsClosureValue ClCsValue() { return (LuaCsClosureValue)OValue; } + internal LuaUserDataValue RawUValue() { return OValue as LuaUserDataValue; } + + internal void SetNilValue() { +#if DEBUG_DUMMY_TVALUE_MODIFY + CheckLock(); +#endif + Tt = (int)LuaType.LUA_TNIL; + NValue = 0.0; + UInt64Value = 0; + OValue = null; + } + internal void SetBValue(bool v) { +#if DEBUG_DUMMY_TVALUE_MODIFY + CheckLock(); +#endif + Tt = (int)LuaType.LUA_TBOOLEAN; + NValue = 0.0; + UInt64Value = v ? BOOLEAN_TRUE : BOOLEAN_FALSE; + OValue = null; + } + internal void SetObj(ref TValue v) { +#if DEBUG_DUMMY_TVALUE_MODIFY + CheckLock(); +#endif + Tt = v.Tt; + NValue = v.NValue; + UInt64Value = v.UInt64Value; + OValue = v.OValue; + } + internal void SetNValue(double v) { +#if DEBUG_DUMMY_TVALUE_MODIFY + CheckLock(); +#endif + Tt = (int)LuaType.LUA_TNUMBER; + NValue = v; + UInt64Value = 0; + OValue = null; + } + internal void SetUInt64Value(UInt64 v) { +#if DEBUG_DUMMY_TVALUE_MODIFY + CheckLock(); +#endif + Tt = (int)LuaType.LUA_TUINT64; + NValue = 0.0; + UInt64Value = v; + OValue = null; + } + internal void SetSValue(string v) { +#if DEBUG_DUMMY_TVALUE_MODIFY + CheckLock(); +#endif + Tt = (int)LuaType.LUA_TSTRING; + NValue = 0.0; + UInt64Value = 0; + OValue = v; + } + internal void SetHValue(LuaTable v) { +#if DEBUG_DUMMY_TVALUE_MODIFY + CheckLock(); +#endif + Tt = (int)LuaType.LUA_TTABLE; + NValue = 0.0; + UInt64Value = 0; + OValue = v; + } + internal void SetThValue(LuaState v) { +#if DEBUG_DUMMY_TVALUE_MODIFY + CheckLock(); +#endif + Tt = (int)LuaType.LUA_TTHREAD; + NValue = 0.0; + UInt64Value = 0; + OValue = v; + } + internal void SetPValue(object v) { +#if DEBUG_DUMMY_TVALUE_MODIFY + CheckLock(); +#endif + Tt = (int)LuaType.LUA_TLIGHTUSERDATA; + NValue = 0.0; + UInt64Value = 0; + OValue = v; + } + internal void SetClLValue(LuaLClosureValue v) { +#if DEBUG_DUMMY_TVALUE_MODIFY + CheckLock(); +#endif + Tt = (int)LuaType.LUA_TFUNCTION; + NValue = 0.0; + UInt64Value = CLOSURE_LUA; + OValue = v; + } + internal void SetClCsValue(LuaCsClosureValue v) { +#if DEBUG_DUMMY_TVALUE_MODIFY + CheckLock(); +#endif + Tt = (int)LuaType.LUA_TFUNCTION; + NValue = 0.0; + UInt64Value = CLOSURE_CS; + OValue = v; + } + internal void SetClLcsValue(CSharpFunctionDelegate v) { +#if DEBUG_DUMMY_TVALUE_MODIFY + CheckLock(); +#endif + Tt = (int)LuaType.LUA_TFUNCTION; + NValue = 0.0; + UInt64Value = CLOSURE_LCS; + OValue = v; + } + + public override string ToString() + { + if (TtIsString()) { + return string.Format("(string, {0})", SValue()); + } else if (TtIsNumber()) { + return string.Format("(number, {0})", NValue); + } else if (TtIsNil()) { + return "(nil)"; + } else { + return string.Format("(type:{0})", Tt); + } + } + } + + public class StkId + { + public TValue V; + + private StkId[] List; + public int Index { get; private set; } + + public void SetList(StkId[] list) { List = list; } + public void SetIndex(int index) { Index = index; } + + public static StkId inc(ref StkId val) + { + var ret = val; + val = val.List[val.Index+1]; + return ret; + } + + public override string ToString() + { + string detail; + if(V.TtIsString()) + { detail = V.SValue().Replace("\n", " "); } + else + { detail = "..."; } + return string.Format("StkId - {0} - {1}", LuaState.TypeName((LuaType)V.Tt), detail); + } + } + + public class LuaLClosureValue + { + public LuaProto Proto; + public LuaUpvalue[] Upvals; + + public LuaLClosureValue(LuaProto p) { + Proto = p; + + Upvals = new LuaUpvalue[p.Upvalues.Count]; + for(int i=0; i Code; + public List K; + public List P; + public List Upvalues; + + public int LineDefined; + public int LastLineDefined; + + public int NumParams; + public bool IsVarArg; + public byte MaxStackSize; + + public string Source; + public List LineInfo; + public List LocVars; + + public LuaProto() + { + Code = new List(); + K = new List(); + P = new List(); + Upvalues = new List(); + LineInfo = new List(); + LocVars = new List(); + } + + public int GetFuncLine( int pc ) + { + return (0 <= pc && pc < LineInfo.Count) ? LineInfo[pc] : 0; + } + } + + public class LuaUpvalue + { + public StkId V; + public StkId Value; + + public LuaUpvalue() + { + Value = new StkId(); + Value.V.SetNilValue(); + + V = Value; + } + } + + public class LuaCsClosureValue + { + public CSharpFunctionDelegate F; + public StkId[] Upvals; + + public LuaCsClosureValue( CSharpFunctionDelegate f ) + { + F = f; + } + + public LuaCsClosureValue( CSharpFunctionDelegate f, int numUpvalues ) + { + F = f; + Upvals = new StkId[numUpvalues]; + for(int i=0; i= path.Length ) + return false; + int end = pos+1; + while( end < path.Length && path[end] != LUA_PATH_SEP[0]) + end++; + + var template = path.Substring( pos, end-pos); + lua.PushString( template ); + + pos = end; + return true; + } + + private static string SearchPath( ILuaState lua, + string name, string path, string sep, string dirsep ) + { + var sb = new StringBuilder(); // to build error message + if( !String.IsNullOrEmpty(sep) ) // non-empty separator? + name = name.Replace( sep, dirsep ); // replace it by `dirsep' + int pos = 0; + while(PushNextTemplate(lua, path, ref pos)) + { + var template = lua.ToString(-1); + string filename = template.Replace( LUA_PATH_MARK, name ); + lua.Remove( -1 ); // remove path template + if( Readable( filename ) ) // does file exist and is readable? + return filename; // return that file name + lua.PushString( string.Format( "\n\tno file '{0}'", filename) ); + lua.Remove( -2 ); // remove file name + sb.Append( lua.ToString(-1) ); // concatenate error msg. entry + } + lua.PushString( sb.ToString() ); // create error message + return null; // not found + } + + private static string FindFile( ILuaState lua, + string name, string pname, string dirsep ) + { + lua.GetField( lua.UpvalueIndex(1), pname ); + string path = lua.ToString( -1 ); + if( path == null ) + lua.L_Error( "'package.{0}' must be a string", pname ); + return SearchPath( lua, name, path, ".", dirsep ); + } + + private static int CheckLoad( ILuaState lua, + bool stat, string filename ) + { + if( stat ) // module loaded successfully? + { + lua.PushString( filename ); // will be 2nd arg to module + return 2; // return open function and file name + } + else return lua.L_Error( + "error loading module '{0}' from file '{1}':\n\t{2}", + lua.ToString(1), filename, lua.ToString(-1) ); + } + + private static int SearcherLua( ILuaState lua ) + { + string name = lua.L_CheckString( 1 ); + string filename = FindFile( lua, name, "path", LUA_LSUBSEP ); + if( filename == null ) + return 1; + return CheckLoad( lua, + (lua.L_LoadFile(filename) == ThreadStatus.LUA_OK), + filename ); + } + + private static int LL_Module( ILuaState lua ) + { + // TODO + return 0; + } + + private static void FindLoader( ILuaState lua, string name ) + { + // will be at index 3 + lua.GetField( lua.UpvalueIndex(1), "searchers" ); + if( ! lua.IsTable(3) ) + lua.L_Error("'package.searchers' must be a table"); + + var sb = new StringBuilder(); + // iterator over available searchers to find a loader + for( int i=1; ; ++i ) + { + lua.RawGetI( 3, i ); // get a searcher + if( lua.IsNil( -1 ) ) // no more searchers? + { + lua.Pop( 1 ); // remove nil + lua.PushString( sb.ToString() ); + lua.L_Error( "module '{0}' not found:{1}", + name, lua.ToString(-1)); + return; + } + + lua.PushString( name ); + lua.Call( 1, 2 ); // call it + if( lua.IsFunction(-2) ) // did it find a loader + return; // module loader found + else if( lua.IsString(-2) ) // searcher returned error message? + { + lua.Pop( 1 ); // return extra return + sb.Append( lua.ToString(-1) ); + } + else + lua.Pop( 2 ); // remove both returns + } + } + + private static int LL_Require( ILuaState lua ) + { + string name = lua.L_CheckString( 1 ); + lua.SetTop( 1 ); + // _LOADED table will be at index 2 + lua.GetField( LuaDef.LUA_REGISTRYINDEX, "_LOADED" ); + // _LOADED[name] + lua.GetField( 2, name ); + // is it there? + if( lua.ToBoolean( -1 ) ) + return 1; // package is already loaded + // else must load package + // remove `GetField' result + lua.Pop( 1 ); + FindLoader( lua, name ); + lua.PushString( name ); // pass name as arg to module loader + lua.Insert( -2 ); // name is 1st arg (before search data) + lua.Call( 2, 1 ); // run loader to load module + if( !lua.IsNil( -1 ) ) // non-nil return? + lua.SetField( 2, name ); // _LOADED[name] = returned value + lua.GetField( 2, name ); + if( lua.IsNil( -1 ) ) // module did not set a value? + { + lua.PushBoolean( true ); // use true as result + lua.PushValue( -1 ); // extra copy to be returned + lua.SetField( 2, name ); // _LOADED[name] = true + } + return 1; + } + + private static int PKG_LoadLib( ILuaState lua ) + { + // TODO + return 0; + } + + private static int PKG_SearchPath( ILuaState lua ) + { + // TODO + return 0; + } + + private static int PKG_SeeAll( ILuaState lua ) + { + // TODO + return 0; + } + } + +} + diff --git a/UniLua/LuaState.cs b/UniLua/LuaState.cs new file mode 100644 index 0000000..0f8bee9 --- /dev/null +++ b/UniLua/LuaState.cs @@ -0,0 +1,306 @@ + +// #define ENABLE_DUMP_STACK + +// #define DEBUG_RECORD_INS + +using System.Collections.Generic; + +namespace UniLua +{ + using InstructionPtr = Pointer; + using ULDebug = UniLua.Tools.ULDebug; + + public struct Pointer + { + private List List; + public int Index { get; set; } + + public T Value + { + get + { + return List[Index]; + } + set + { + List[Index] = value; + } + } + + public T ValueInc + { + get + { + return List[Index++]; + } + set + { + List[Index++] = value; + } + } + + public Pointer( List list, int index ) : this() + { + List = list; + Index = index; + } + + public Pointer( Pointer other ) : this() + { + List = other.List; + Index = other.Index; + } + + public static Pointer operator +( Pointer lhs, int rhs ) + { + return new Pointer( lhs.List, lhs.Index + rhs ); + } + + public static Pointer operator -( Pointer lhs, int rhs ) + { + return new Pointer( lhs.List, lhs.Index - rhs ); + } + } + + public enum CallStatus + { + CIST_NONE = 0, + + CIST_LUA = (1<<0), /* call is running a Lua function */ + CIST_HOOKED = (1<<1), /* call is running a debug hook */ + CIST_REENTRY = (1<<2), /* call is running on same invocation of + luaV_execute of previous call */ + CIST_YIELDED = (1<<3), /* call reentered after suspension */ + CIST_YPCALL = (1<<4), /* call is a yieldable protected call */ + CIST_STAT = (1<<5), /* call has an error status (pcall) */ + CIST_TAIL = (1<<6), /* call was tail called */ + } + + public class CallInfo + { + public CallInfo[] List; + public int Index; + + public int FuncIndex; + public int TopIndex; + + public int NumResults; + public CallStatus CallStatus; + + public CSharpFunctionDelegate ContinueFunc; + public int Context; + public int ExtraIndex; + public bool OldAllowHook; + public int OldErrFunc; + public ThreadStatus Status; + + // for Lua functions + public int BaseIndex; + public InstructionPtr SavedPc; + + public bool IsLua + { + get { return (CallStatus & CallStatus.CIST_LUA) != 0; } + } + + public int CurrentPc + { + get + { + Utl.Assert( IsLua ); + return SavedPc.Index - 1; + } + } + } + + public class GlobalState + { + public StkId Registry; + public LuaUpvalue UpvalHead; + public LuaTable[] MetaTables; + public LuaState MainThread; + + public GlobalState( LuaState state ) + { + MainThread = state; + Registry = new StkId(); + UpvalHead = new LuaUpvalue(); + MetaTables = new LuaTable[(int)LuaType.LUA_NUMTAGS]; + } + } + + public delegate void LuaHookDelegate(ILuaState lua, LuaDebug ar); + + public partial class LuaState + { + public StkId[] Stack; + public StkId Top; + public int StackSize; + public int StackLast; + public CallInfo CI; + public CallInfo[] BaseCI; + public GlobalState G; + public int NumNonYieldable; + public int NumCSharpCalls; + public int ErrFunc; + public ThreadStatus Status { get; set; } + public bool AllowHook; + public byte HookMask; + public int BaseHookCount; + public int HookCount; + public LuaHookDelegate Hook; + + public LinkedList OpenUpval; + +#if DEBUG_RECORD_INS + private Queue InstructionHistory; +#endif + + private ILuaAPI API; + + static LuaState() + { + TheNilValue = new StkId(); + TheNilValue.V.SetNilValue(); + } + + public LuaState( GlobalState g=null ) + { + API = (ILuaAPI)this; + + NumNonYieldable = 1; + NumCSharpCalls = 0; + Hook = null; + HookMask = 0; + BaseHookCount = 0; + AllowHook = true; + ResetHookCount(); + Status = ThreadStatus.LUA_OK; + + if( g == null ) + { + G = new GlobalState(this); + InitRegistry(); + } + else + { + G = g; + } + OpenUpval = new LinkedList(); + ErrFunc = 0; + +#if DEBUG_RECORD_INS + InstructionHistory = new Queue(); +#endif + + InitStack(); + } + + private void IncrTop() + { + StkId.inc(ref Top); + D_CheckStack(0); + } + + private StkId RestoreStack( int index ) + { + return Stack[index]; + } + + private void ApiIncrTop() + { + StkId.inc(ref Top); + // Console.WriteLine( "[ApiIncrTop] ==== Top.Index:" + Top.Index ); + // Console.WriteLine( "[ApiIncrTop] ==== CI.Top.Index:" + CI.Top.Index ); + Utl.ApiCheck( Top.Index <= CI.TopIndex, "stack overflow" ); + } + + private void InitStack() + { + Stack = new StkId[LuaDef.BASIC_STACK_SIZE]; + StackSize = LuaDef.BASIC_STACK_SIZE; + StackLast = LuaDef.BASIC_STACK_SIZE - LuaDef.EXTRA_STACK; + for(int i=0; i {2} {3}" + , i-baseIndex + , i + , inStack ? Stack[i].ToString() : "" + , postfix + ); + + sb.Append( body ).Append("\n"); + } + return sb.ToString(); + } + + public void DumpStack( int baseIndex, string tag="" ) + { +#if ENABLE_DUMP_STACK + Console.WriteLine(DumpStackToString(baseIndex, tag)); +#endif + } + + private void ResetHookCount() + { + HookCount = BaseHookCount; + } + + } + +} + diff --git a/UniLua/LuaStrLib.cs b/UniLua/LuaStrLib.cs new file mode 100644 index 0000000..4d5913d --- /dev/null +++ b/UniLua/LuaStrLib.cs @@ -0,0 +1,937 @@ + +namespace UniLua +{ + using StringBuilder = System.Text.StringBuilder; + using Char = System.Char; + using Convert = System.Convert; + + internal static class LuaStrLib + { + public const string LIB_NAME = "string"; + + private const int CAP_UNFINISHED = -1; + private const int CAP_POSITION = -2; + private const int LUA_MAXCAPTURES = 32; + private const char L_ESC = '%'; + private const string FLAGS = "-+ #0"; + private static readonly char[] SPECIALS; + + static LuaStrLib() + { + SPECIALS = "^$*+?.([%-".ToCharArray(); + } + + public static int OpenLib( ILuaState lua ) + { + NameFuncPair[] define = new NameFuncPair[] + { + new NameFuncPair( "byte", Str_Byte ), + new NameFuncPair( "char", Str_Char ), + //new NameFuncPair( "dump", Str_Dump ), + new NameFuncPair( "find", Str_Find ), + new NameFuncPair( "format", Str_Format ), + new NameFuncPair( "gmatch", Str_Gmatch ), + new NameFuncPair( "gsub", Str_Gsub ), + new NameFuncPair( "len", Str_Len ), + new NameFuncPair( "lower", Str_Lower ), + new NameFuncPair( "match", Str_Match ), + new NameFuncPair( "rep", Str_Rep ), + new NameFuncPair( "reverse", Str_Reverse ), + new NameFuncPair( "sub", Str_Sub ), + new NameFuncPair( "upper", Str_Upper ), + }; + + lua.L_NewLib( define ); + CreateMetaTable( lua ); + + return 1; + } + + private static void CreateMetaTable( ILuaState lua ) + { + lua.CreateTable(0, 1); // table to be metatable for strings + lua.PushString( "" ); // dummy string + lua.PushValue( -2 ); // copy table + lua.SetMetaTable( -2 ); // set table as metatable for strings + lua.Pop( 1 ); + lua.PushValue( -2 ); // get string library + lua.SetField( -2, "__index" ); // metatable.__index = string + lua.Pop( 1 ); // pop metatable + } + + private static int PosRelative( int pos, int len ) + { + if( pos >= 0 ) return pos; + else if( 0 - pos > len ) return 0; + else return len - (-pos) + 1; + } + + private static int Str_Byte( ILuaState lua ) + { + string s = lua.L_CheckString(1); + int posi = PosRelative( lua.L_OptInt(2, 1), s.Length ); + int pose = PosRelative( lua.L_OptInt(3, posi), s.Length ); + if( posi < 1 ) posi = 1; + if( pose > s.Length ) pose = s.Length; + if( posi > pose ) return 0; // empty interval; return no values + int n = pose - posi + 1; + if( posi + n <= pose) // overflow? + return lua.L_Error( "string slice too long" ); + lua.L_CheckStack(n, "string slice too long"); + for( int i=0; i= ms.PatternEnd - 1 ) + lua.L_Error( "malformed pattern (missing arguments to '%b')" ); + if( ms.Src[s] != ms.Pattern[p] ) return -1; + else + { + char b = ms.Pattern[p]; + char e = ms.Pattern[p+1]; + int count = 1; + while( ++s < ms.SrcEnd ) + { + if( ms.Src[s] == e ) + { + if( --count == 0 ) return s+1; + } + else if( ms.Src[s] == b ) count++; + } + } + return -1; //string ends out of balance + } + + private static int MaxExpand( MatchState ms, int s, int p, int ep ) + { + int i = 0; // counts maximum expand for item + while( (s+i) < ms.SrcEnd && SingleMatch( ms, ms.Src[s+i], p, ep ) ) + i++; + // keeps trying to match with the maximum repetitions + while( i >= 0 ) + { + int res = Match( ms, (s+i), (ep+1) ); + if( res >= 0 ) return res; + i--; // else didn't match; reduce 1 repetition to try again + } + return -1; + } + + private static int MinExpand( MatchState ms, int s, int p, int ep ) + { + for(;;) + { + int res = Match( ms, s, ep+1 ); + if( res >= 0 ) + return res; + else if( s < ms.SrcEnd && SingleMatch( ms, ms.Src[s], p, ep ) ) + s++; // try with one more repetition + else return -1; + } + } + + private static int CaptureToClose( MatchState ms ) + { + var lua = ms.Lua; + int level=ms.Level; + for( level--; level>=0; level-- ) + { + if( ms.Capture[level].Len == CAP_UNFINISHED ) + return level; + } + return lua.L_Error( "invalid pattern capture" ); + } + + private static int StartCapture( MatchState ms, int s, int p, int what ) + { + var lua = ms.Lua; + int level = ms.Level; + if( level >= LUA_MAXCAPTURES ) + lua.L_Error( "too many captures" ); + ms.Capture[level].Init = s; + ms.Capture[level].Len = what; + ms.Level = level + 1; + int res = Match( ms, s, p ); + if( res == -1 ) // match failed? + ms.Level--; + return res; + } + + private static int EndCapture( MatchState ms, int s, int p ) + { + int l = CaptureToClose( ms ); + ms.Capture[l].Len = s - ms.Capture[l].Init; // close capture + int res = Match( ms, s, p ); + if( res == -1 ) // match failed? + ms.Capture[l].Len = CAP_UNFINISHED; // undo capture + return res; + } + + private static int CheckCapture( MatchState ms, char l ) + { + var lua = ms.Lua; + int i = (int)(l - '1'); + if( i < 0 || i >= ms.Level || ms.Capture[i].Len == CAP_UNFINISHED ) + return lua.L_Error( "invalid capture index %d", i+1 ); + return i; + } + + private static int MatchCapture( MatchState ms, int s, char l ) + { + int i = CheckCapture( ms, l ); + int len = ms.Capture[i].Len; + if( ms.SrcEnd - s >= len && + string.Compare(ms.Src, ms.Capture[i].Init, ms.Src, s, len) == 0 ) + return s + len; + else + return -1; + } + + private static int Match( MatchState ms, int s, int p ) + { + var lua = ms.Lua; + init: // using goto's to optimize tail recursion + if( p == ms.PatternEnd ) + return s; + switch( ms.Pattern[p] ) + { + case '(': // start capture + { + if( ms.Pattern[p+1] == ')' ) // position capture? + return StartCapture( ms, s, p+2, CAP_POSITION ); + else + return StartCapture( ms, s, p+1, CAP_UNFINISHED ); + } + case ')': // end capture + { + return EndCapture( ms, s, p+1 ); + } + case '$': + { + if( p+1 == ms.PatternEnd ) // is the `$' the last char in pattern? + return (s == ms.SrcEnd) ? s : -1; // check end of string + else goto default; + } + case L_ESC: // escaped sequences not in the format class[*+?-]? + { + switch( ms.Pattern[p+1] ) + { + case 'b': // balanced string? + { + s = MatchBalance( ms, s, p+2 ); + if( s == -1 ) return -1; + p += 4; goto init; // else return match(ms, s, p+4); + } + case 'f': // frontier? + { + p += 2; + if( ms.Pattern[p] != '[' ) + lua.L_Error( "missing '[' after '%f' in pattern" ); + int ep = ClassEnd( ms, p ); //points to what is next + char previous = (s == ms.SrcInit) ? '\0' : ms.Src[s-1]; + if( MatchBreaketClass(ms, previous, p, ep-1) || + !MatchBreaketClass(ms, ms.Src[s], p, ep-1) ) return -1; + p = ep; goto init; // else return match( ms, s, ep ); + } + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': // capture results (%0-%9)? + { + s = MatchCapture( ms, s, ms.Pattern[p+1] ); + if( s == -1 ) return -1; + p+=2; goto init; // else return match(ms, s, p+2); + } + default: goto dflt; + } + } + default: dflt: // pattern class plus optional suffix + { + int ep = ClassEnd( ms, p ); + bool m = s < ms.SrcEnd && SingleMatch(ms, ms.Src[s], p, ep); + if(ep < ms.PatternEnd){ + switch(ms.Pattern[ep]) //fix gmatch bug patten is [^a] + { + case '?': // optional + { + if( m ) + { + int res = Match(ms, s+1, ep+1); + if( res != -1 ) + return res; + } + p=ep+1; goto init; // else return match(ms, s, ep+1); + } + case '*': // 0 or more repetitions + { + return MaxExpand(ms, s, p, ep); + } + case '+': // 1 or more repetitions + { + return (m ? MaxExpand(ms, s+1, p, ep) : -1); + } + case '-': // 0 or more repetitions (minimum) + { + return MinExpand(ms, s, p, ep); + } + } + } + if(!m) return -1; + s++; p=ep; goto init; // else return match(ms, s+1, ep); + } + } + } + + private static void PushOneCapture + ( MatchState ms + , int i + , int start + , int end + ) + { + var lua = ms.Lua; + if( i >= ms.Level ) + { + if( i == 0 ) // ms.Level == 0, too + lua.PushString( ms.Src.Substring( start, end-start ) ); + else + lua.L_Error( "invalid capture index" ); + } + else + { + int l = ms.Capture[i].Len; + if( l == CAP_UNFINISHED ) + lua.L_Error( "unfinished capture" ); + if( l == CAP_POSITION ) + lua.PushInteger( ms.Capture[i].Init - ms.SrcInit + 1 ); + else + lua.PushString( ms.Src.Substring( ms.Capture[i].Init, l ) ); + } + } + + private static int PushCaptures(ILuaState lua, MatchState ms, int spos, int epos ) + { + int nLevels = (ms.Level == 0 && spos >= 0) ? 1 : ms.Level; + lua.L_CheckStack(nLevels, "too many captures"); + for( int i=0; i s.Length + 1 ) // start after string's end? + { + lua.PushNil(); // cannot find anything + return 1; + } + // explicit request or no special characters? + if( find && (lua.ToBoolean(4) || NoSpecials(p)) ) + { + // do a plain search + int pos = s.IndexOf( p, init-1 ); + if( pos >= 0 ) + { + lua.PushInteger( pos+1 ); + lua.PushInteger( pos+p.Length ); + return 2; + } + } + else + { + int s1 = init-1; + int ppos = 0; + bool anchor = p[ppos] == '^'; + if( anchor ) + ppos++; // skip anchor character + + MatchState ms = new MatchState(); + ms.Lua = lua; + ms.Src = s; + ms.SrcInit = s1; + ms.SrcEnd = s.Length; + ms.Pattern = p; + ms.PatternEnd = p.Length; + + do + { + ms.Level = 0; + int res = Match( ms, s1, ppos ); + if( res != -1 ) + { + if(find) + { + lua.PushInteger( s1+1 ); // start + lua.PushInteger( res ); // end + return PushCaptures(lua, ms, -1, 0) + 2; + } + else return PushCaptures(lua, ms, s1, res); + } + } while( s1++ < ms.SrcEnd && !anchor ); + } + lua.PushNil(); // not found + return 1; + } + + private static int Str_Find( ILuaState lua ) + { + return StrFindAux( lua, true ); + } + + private static int ScanFormat( ILuaState lua, string format, int s, out string form ) + { + int p = s; + // skip flags + while( p < format.Length && format[p] != '\0' && FLAGS.IndexOf(format[p]) != -1 ) + p++; + if( p - s > FLAGS.Length ) + lua.L_Error( "invalid format (repeat flags)" ); + if( Char.IsDigit( format[p] ) ) p++; // skip width + if( Char.IsDigit( format[p] ) ) p++; // (2 digits at most) + if( format[p] == '.' ) + { + p++; + if( Char.IsDigit( format[p] ) ) p++; // skip precision + if( Char.IsDigit( format[p] ) ) p++; // (2 digits at most) + } + if( Char.IsDigit( format[p] ) ) + lua.L_Error( "invalid format (width of precision too long)" ); + form = "%" + format.Substring( s, (p-s+1) ); + return p; + } + + private static int Str_Format( ILuaState lua ) + { + int top = lua.GetTop(); + StringBuilder sb = new StringBuilder(); + int arg = 1; + string format = lua.L_CheckString( arg ); + int s = 0; + int e = format.Length; + while(s < e) + { + if( format[s] != L_ESC ) + { + sb.Append( format[s++] ); + continue; + } + + if( format[++s] == L_ESC ) + { + sb.Append( format[s++] ); + continue; + } + + // else format item + if( ++arg > top ) + lua.L_ArgError( arg, "no value" ); + + string form; + s = ScanFormat( lua, format, s, out form ); + switch( format[s++] ) // TODO: properly handle form + { + case 'c': + { + sb.Append( (char)lua.L_CheckInteger(arg) ); + break; + } + case 'd': case 'i': + { + int n = lua.L_CheckInteger(arg); + sb.Append( n.ToString() ); + break; + } + case 'u': + { + int n = lua.L_CheckInteger(arg); + lua.L_ArgCheck( n >= 0, arg, + "not a non-negative number is proper range" ); + sb.Append( n.ToString() ); + break; + } + case 'o': + { + int n = lua.L_CheckInteger(arg); + lua.L_ArgCheck( n >= 0, arg, + "not a non-negative number is proper range" ); + sb.Append( Convert.ToString(n, 8) ); + break; + } + case 'x': + { + int n = lua.L_CheckInteger(arg); + lua.L_ArgCheck( n >= 0, arg, + "not a non-negative number is proper range" ); + // sb.Append( string.Format("{0:x}", n) ); + sb.AppendFormat("{0:x}", n); + break; + } + case 'X': + { + int n = lua.L_CheckInteger(arg); + lua.L_ArgCheck( n >= 0, arg, + "not a non-negative number is proper range" ); + // sb.Append( string.Format("{0:X}", n) ); + sb.AppendFormat("{0:X}", n); + break; + } + case 'e': case 'E': + { + sb.AppendFormat("{0:E}", lua.L_CheckNumber(arg)); + break; + } + case 'f': + { + sb.AppendFormat("{0:F}", lua.L_CheckNumber(arg)); + break; + } +#if LUA_USE_AFORMAT + case 'a': case 'A': +#endif + case 'g': case 'G': + { + sb.AppendFormat("{0:G}", lua.L_CheckNumber(arg)); + break; + } + case 'q': + { + AddQuoted(lua, sb, arg); + break; + } + case 's': + { + sb.Append(lua.L_CheckString(arg)); + break; + } + default: // also treat cases `pnLlh' + { + return lua.L_Error( "invalid option '{0}' to 'format'", + format[s-1] ); + } + } + } + lua.PushString( sb.ToString() ); + return 1; + } + + private static void AddQuoted(ILuaState lua, StringBuilder sb, int arg) + { + var s = lua.L_CheckString(arg); + sb.Append('"'); + for(var i=0; i=s.Length || !Char.IsDigit(s[i+1])) { + sb.AppendFormat("\\{0:D}", (int)c); + } + else { + sb.AppendFormat("\\{0:D3}", (int)c); + } + } + else { + sb.Append(c); + } + } + sb.Append('"'); + } + + private static int GmatchAux( ILuaState lua ) + { + MatchState ms = new MatchState(); + string src = lua.ToString( lua.UpvalueIndex(1) ); + string pattern = lua.ToString( lua.UpvalueIndex(2) ); + ms.Lua = lua; + ms.Src = src; + ms.SrcInit = 0; + ms.SrcEnd = src.Length; + ms.Pattern = pattern; + ms.PatternEnd = pattern.Length; + for( int s = lua.ToInteger( lua.UpvalueIndex(3) ) + ; s <= ms.SrcEnd + ; s++ ) + { + ms.Level = 0; + int e = Match( ms, s, 0 ); + if( e != -1 ) + { + int newStart = (e == 0) ? e+1: e; + lua.PushInteger( newStart ); + lua.Replace( lua.UpvalueIndex(3) ); + return PushCaptures(lua, ms, s, e); + } + } + return 0; // not found + } + + private static int Str_Gmatch( ILuaState lua ) + { + lua.L_CheckString(1); + lua.L_CheckString(2); + lua.SetTop(2); + lua.PushInteger(0); + lua.PushCSharpClosure( GmatchAux, 3 ); + return 1; + } + + private static void Add_S (MatchState ms, StringBuilder b, int s, int e) { + string news = ms.Lua.ToString(3); + for (int i = 0; i < news.Length; i++) { + if (news[i] != L_ESC) + b.Append(news[i]); + else { + i++; /* skip ESC */ + if (!Char.IsDigit((news[i]))) + b.Append(news[i]); + else if (news[i] == '0') + b.Append(ms.Src.Substring(s, (e - s))); + else { + PushOneCapture(ms, news[i] - '1', s, e); + b.Append(ms.Lua.ToString(-1)); /* add capture to accumulated result */ + } + } + } + } + + private static void Add_Value (MatchState ms, StringBuilder b, int s, int e) { + ILuaState lua = ms.Lua; + switch (lua.Type(3)) { + case LuaType.LUA_TNUMBER: + case LuaType.LUA_TSTRING: { + Add_S(ms, b, s, e); + return; + } + case LuaType.LUA_TFUNCTION: { + int n; + lua.PushValue(3); + n = PushCaptures(lua, ms, s, e); + lua.Call(n, 1); + break; + } + case LuaType.LUA_TTABLE: { + PushOneCapture(ms, 0, s, e); + lua.GetTable(3); + break; + } + } + if (lua.ToBoolean(-1)==false) { /* nil or false? */ + lua.Pop(1); + b.Append(ms.Src.Substring(s, (e - s))); /* keep original text */ + } + else if (!lua.IsString(-1)) + lua.L_Error("invalid replacement value (a %s)", lua.L_TypeName(-1)); + else + b.Append(lua.ToString(-1)); + } + + private static int Str_Gsub( ILuaState lua ) + { + + string src = lua.L_CheckString(1); + int srcl = src.Length; + string p = lua.L_CheckString(2); + LuaType tr = lua.Type(3); + int max_s = lua.L_OptInt(4, srcl + 1); + int anchor = 0; + if (p[0] == '^') + { + p = p.Substring(1); + anchor = 1; + } + int n = 0; + MatchState ms = new MatchState(); + StringBuilder b = new StringBuilder(srcl); + lua.L_ArgCheck(tr == LuaType.LUA_TNUMBER || tr == LuaType.LUA_TSTRING || + tr == LuaType.LUA_TFUNCTION || tr == LuaType.LUA_TTABLE, 3, + "string/function/table expected"); + ms.Lua = lua; + ms.Src = src; + ms.SrcInit = 0; + ms.SrcEnd = srcl; + ms.Pattern = p; + ms.PatternEnd = p.Length; + int s = 0; + while (n < max_s) { + ms.Level = 0; + int e = Match(ms, s, 0); + if (e != -1) { + n++; + Add_Value(ms, b, s, e); + } + if ((e != -1) && e > s) /* non empty match? */ + s = e; /* skip it */ + else if (s < ms.SrcEnd) + { + char c = src[s]; + ++s; + b.Append(c); + } + else break; + if (anchor != 0) break; + } + b.Append(src.Substring(s, ms.SrcEnd - s)); + lua.PushString(b.ToString()); + lua.PushInteger(n); /* number of substitutions */ + return 2; + } + + private static int Str_Len( ILuaState lua ) + { + string s = lua.L_CheckString(1); + lua.PushInteger( s.Length ); + return 1; + } + + private static int Str_Lower( ILuaState lua ) + { + string s = lua.L_CheckString(1); + lua.PushString( s.ToLower() ); + return 1; + } + + private static int Str_Match( ILuaState lua ) + { + return StrFindAux( lua, false ); + } + + private static int Str_Rep(ILuaState lua) + { + string s = lua.L_CheckString(1); + int n = lua.L_CheckInteger(2); + + StringBuilder sb = new StringBuilder(s.Length * n); + for (int i = 0; i < n; ++i) + sb.Append(s); + + lua.PushString(sb.ToString()); + return 1; + } + + private static int Str_Reverse( ILuaState lua ) + { + string s = lua.L_CheckString(1); + StringBuilder sb = new StringBuilder(s.Length); + for( int i=s.Length-1; i>=0; --i ) + sb.Append( s[i] ); + lua.PushString( sb.ToString() ); + return 1; + } + + private static int Str_Sub( ILuaState lua ) + { + string s = lua.L_CheckString(1); + int start = PosRelative( lua.L_CheckInteger(2), s.Length ); + int end = PosRelative( lua.L_OptInt(3, -1), s.Length ); + if( start < 1 ) start = 1; + if( end > s.Length ) end = s.Length; + if( start <= end ) + lua.PushString( s.Substring(start-1, end-start+1) ); + else + lua.PushString( "" ); + return 1; + } + + private static int Str_Upper( ILuaState lua ) + { + string s = lua.L_CheckString(1); + lua.PushString( s.ToUpper() ); + return 1; + } + + } + +} + diff --git a/UniLua/LuaTable.cs b/UniLua/LuaTable.cs new file mode 100644 index 0000000..ae5f3d2 --- /dev/null +++ b/UniLua/LuaTable.cs @@ -0,0 +1,581 @@ + +// #define DEBUG_DUMMY_TVALUE_MODIFY + +using System; +using System.Collections.Generic; + +namespace UniLua +{ + using ULDebug = UniLua.Tools.ULDebug; + + public class LuaTable { + public LuaTable MetaTable; + public uint NoTagMethodFlags; + + public LuaTable(LuaState l) { + InitLuaTable(l); + } + + ~LuaTable() + { + Recycle(); + } + + public StkId Get(ref TValue key) + { + if(key.Tt == (int)LuaType.LUA_TNIL) { return TheNilValue; } + + if(IsPositiveInteger(ref key)) + { return GetInt((int)key.NValue); } + + if(key.Tt == (int)LuaType.LUA_TSTRING) + { return GetStr(key.SValue()); } + + var h = key.GetHashCode(); + for(var node = GetHashNode(h); node != null; node = node.Next) { + if(node.Key.V == key) { + { return node.Val; } + } + } + + return TheNilValue; + } + + public StkId GetInt(int key) + { + if(0 < key && key-1 < ArrayPart.Length) + { return ArrayPart[key-1]; } + + var k = new TValue(); + k.SetNValue(key); + for(var node = GetHashNode(ref k); node != null; node = node.Next) { + if(node.Key.V.TtIsNumber() && node.Key.V.NValue == (double)key) { + return node.Val; + } + } + + return TheNilValue; + } + + public StkId GetStr(string key) + { + var h = key.GetHashCode(); + for(var node = GetHashNode(h); node != null; node = node.Next) { + if(node.Key.V.TtIsString() && node.Key.V.SValue() == key) + { return node.Val; } + } + + return TheNilValue; + } + + public void Set(ref TValue key, ref TValue val) + { + var cell = Get(ref key); + if(cell == TheNilValue) { + cell = NewTableKey(ref key); + } + cell.V.SetObj(ref val); + } + + public void SetInt(int key, ref TValue val) + { + var cell = GetInt(key); + if(cell == TheNilValue) { + var k = new TValue(); + k.SetNValue(key); + cell = NewTableKey(ref k); + } + cell.V.SetObj(ref val); + // Console.WriteLine(string.Format("---------------- SetInt {0} -> {1}", key, val)); + // DumpParts(); + } + + /* + ** returns the index of a `key' for table traversals. First goes all + ** elements in the array part, then elements in the hash part. The + ** beginning of a traversal is signaled by -1. + */ + private int FindIndex(StkId key) + { + if(key.V.TtIsNil()) + { return -1; } + + // is `key' inside array part? + int i = ArrayIndex(ref key.V); + if(0 < i && i <= ArrayPart.Length) + { return i-1; } + + var n = GetHashNode(ref key.V); + // check whether `key' is somewhere in the chain + for(;;) { + if(L.V_RawEqualObj(ref n.Key.V, ref key.V)) + { return ArrayPart.Length + n.Index; } + n = n.Next; + + // key not found + if(n == null) { L.G_RunError("invalid key to 'next'"); } + } + } + + public bool Next(StkId key, StkId val) + { + // find original element + int i = FindIndex(key); + + // try first array part + for(i++; i 0 && ArrayPart[j-1].V.TtIsNil()) { + /* there is a boundary in the array part: (binary) search for it */ + uint i = 0; + while(j - i > 1) { + uint m = (i+j)/2; + if(ArrayPart[m-1].V.TtIsNil()) { j = m; } + else { i = m; } + } + return (int)i; + } + /* else must find a boundary in hash part */ + else if(HashPart == DummyHashPart) + return (int)j; + else return UnboundSearch(j); + } } + + public void Resize(int nasize, int nhsize) + { + int oasize = ArrayPart.Length; + var oldHashPart = HashPart; + if(nasize > oasize) // array part must grow? + SetArraryVector(nasize); + + // create new hash part with appropriate size + SetNodeVector(nhsize); + + // array part must shrink? + if(nasize < oasize) { + var oldArrayPart = ArrayPart; + ArrayPart = DummyArrayPart; + // re-insert elements from vanishing slice + for(int i=nasize; i 0 && (v.NValue % 1) == 0 && v.NValue <= int.MaxValue); //fix large number key bug + } + + private HNode GetHashNode(int hashcode) + { + uint n = (uint)hashcode; + return HashPart[n % HashPart.Length]; + } + + private HNode GetHashNode(ref TValue v) + { + if(IsPositiveInteger(ref v)) { return GetHashNode((int)v.NValue); } + + if(v.TtIsString()) { return GetHashNode(v.SValue().GetHashCode()); } + + return GetHashNode(v.GetHashCode()); + } + + private void SetArraryVector(int size) + { + Utl.Assert(size >= ArrayPart.Length); + + var newArrayPart = new StkId[size]; + int i = 0; + for( ; i MAXBITS) { L.G_RunError("table overflow"); } + + size = (1 << lsize); + HashPart = new HNode[size]; + for(int i=0; i 0) { + var node = HashPart[--LastFree]; + if(node.Key.V.TtIsNil()) { return node; } + } + return null; + } + + /* + ** returns the index for `key' if `key' is an appropriate key to live in + ** the array part of the table, -1 otherwise. + */ + private int ArrayIndex(ref TValue k) + { + if(IsPositiveInteger(ref k)) + return (int)k.NValue; + else + return -1; + } + + private static readonly byte[] Log2_ = new byte[] { + 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 + }; + private int CeilLog2(int x) + { + Utl.Assert(x > 0); + int l = 0; + x--; + while(x >= 256) { l+=8; x>>=8; } + return l + Log2_[x]; + } + + private int CountInt(ref TValue key, ref int[] nums) + { + int k = ArrayIndex(ref key); + if(0 < k && k <= MAXASIZE) { + nums[CeilLog2(k)]++; + return 1; + } + else return 0; + } + + private int NumUseArray(ref int[] nums) + { + int ause = 0; + int i = 1; + for(int lg=0, ttlg=1; lg<=MAXBITS; lg++, ttlg*=2) { + int lc = 0; // counter + int lim = ttlg; + if(lim > ArrayPart.Length) { + lim = ArrayPart.Length; + if(i > lim) { break; } // no more elements to count + } + + // count elements in range (2^(lg-1), 2^lg] + for(; i<=lim; ++i) { + if(!ArrayPart[i-1].V.TtIsNil()) { lc++; } + } + nums[lg] += lc; + ause += lc; + } + return ause; + } + + private int NumUseHash(ref int[] nums, ref int nasize) + { + int totaluse = 0; + int ause = 0; + int i = HashPart.Length; + while(i-- > 0) { + var n = HashPart[i]; + if(!n.Val.V.TtIsNil()) { + ause += CountInt(ref n.Key.V, ref nums); + totaluse++; + } + } + nasize += ause; + return totaluse; + } + + private int ComputeSizes(ref int[] nums, ref int nasize) + { + int a = 0; + int na = 0; + int n = 0; + for(int i=0, tti=1; tti/2 0) { + a += nums[i]; + if(a > tti/2) { + n = tti; + na = a; + } + } + if(a == nasize) { break; } // all elements already conted + } + nasize = n; + Utl.Assert(nasize/2 <= na && na <= nasize); + return na; + } + + private static int[] Nums = new int[MAXBITS + 1]; + private void Rehash(ref TValue k) + { + for(int i=0; i<=MAXBITS; ++i) { Nums[i] = 0; } + + int nasize = NumUseArray(ref Nums); + int totaluse = nasize; + totaluse += NumUseHash(ref Nums, ref nasize); + nasize += CountInt(ref k, ref Nums); + totaluse++; + int na = ComputeSizes(ref Nums, ref nasize); + Resize(nasize, totaluse-na); + } + + private void DumpParts() + { + Console.WriteLine("------------------ [DumpParts] enter -----------------------"); + Console.WriteLine("<< Array Part >>"); + for(var i=0; i>"); + for(var i=0; i LuaLimits.MAX_INT) { + /* table was built with bad purposes: resort to linear search */ + i = 1; + while(!GetInt((int)i).V.TtIsNil()) { i++; } + return (int)(i-1); + } + } + /* now do a binary search between them */ + while(j - i > 1) { + uint m = (i + j) / 2; + if(GetInt((int)m).V.TtIsNil()) { j = m; } + else { i = m; } + } + return (int)i; + } + } +} diff --git a/UniLua/LuaTableLib.cs b/UniLua/LuaTableLib.cs new file mode 100644 index 0000000..9f8625c --- /dev/null +++ b/UniLua/LuaTableLib.cs @@ -0,0 +1,281 @@ + +#define LUA_COMPAT_UNPACK + +namespace UniLua +{ + using StringBuilder = System.Text.StringBuilder; + + internal class LuaTableLib + { + public const string LIB_NAME = "table"; + + public static int OpenLib( ILuaState lua ) + { + NameFuncPair[] define = new NameFuncPair[] + { + new NameFuncPair( "concat", TBL_Concat ), + new NameFuncPair( "maxn", TBL_MaxN ), + new NameFuncPair( "insert", TBL_Insert ), + new NameFuncPair( "pack", TBL_Pack ), + new NameFuncPair( "unpack", TBL_Unpack ), + new NameFuncPair( "remove", TBL_Remove ), + new NameFuncPair( "sort", TBL_Sort ), + }; + + lua.L_NewLib( define ); + +#if LUA_COMPAT_UNPACK + // _G.unpack = table.unpack + lua.GetField( -1, "unpack" ); + lua.SetGlobal( "unpack" ); +#endif + + return 1; + } + + private static int TBL_Concat( ILuaState lua ) + { + string sep = lua.L_OptString( 2, "" ); + lua.L_CheckType( 1, LuaType.LUA_TTABLE ); + int i = lua.L_OptInt( 3, 1 ); + int last = lua.L_Opt(4, lua.L_Len(1)); + + StringBuilder sb = new StringBuilder(); + for( ; i max ) max = v; + } + } + lua.PushNumber( max ); + return 1; + } + + private static int AuxGetN( ILuaState lua, int n ) + { + lua.L_CheckType( n, LuaType.LUA_TTABLE ); + return lua.L_Len( n ); + } + + private static int TBL_Insert( ILuaState lua ) + { + int e = AuxGetN(lua, 1) + 1; // first empty element + int pos; // where to insert new element + switch( lua.GetTop() ) + { + case 2: // called with only 2 arguments + { + pos = e; // insert new element at the end + break; + } + case 3: + { + pos = lua.L_CheckInteger(2); // 2nd argument is the position + if( pos > e ) e = pos; // `grow' array if necessary + for( int i=e; i>pos; --i ) // move up elements + { + lua.RawGetI( 1, i-1 ); + lua.RawSetI( 1, i ); // t[i] = t[i-1] + } + break; + } + default: + { + return lua.L_Error( "wrong number of arguments to 'insert'" ); + } + } + lua.RawSetI( 1, pos ); // t[pos] = v + return 0; + } + + private static int TBL_Remove( ILuaState lua ) + { + int e = AuxGetN(lua, 1); + int pos = lua.L_OptInt( 2, e ); + if( !(1 <= pos && pos <= e) ) // position is outside bounds? + return 0; // nothing to remove + lua.RawGetI(1, pos); /* result = t[pos] */ + for( ; pos 0 ) // at least one element? + { + lua.PushValue( 1 ); + lua.RawSetI( -2, 1 ); // insert first element + lua.Replace( 1 ); // move table into index 1 + for( int i=n; i>=2; --i ) // assign other elements + lua.RawSetI( 1, i ); + } + return 1; // return table + } + + private static int TBL_Unpack( ILuaState lua ) + { + lua.L_CheckType( 1, LuaType.LUA_TTABLE ); + int i = lua.L_OptInt( 2, 1 ); + int e = lua.L_OptInt( 3, lua.L_Len(1) ); + if( i > e ) return 0; // empty range + int n = e - i + 1; // number of elements + if( n <= 0 || !lua.CheckStack(n) ) // n <= 0 means arith. overflow + return lua.L_Error( "too many results to unpack" ); + lua.RawGetI( 1, i ); // push arg[i] (avoiding overflow problems + while( i++ < e ) // push arg[i + 1...e] + lua.RawGetI( 1, i ); + return n; + } + + // quick sort //////////////////////////////////////////////////////// + + private static void Set2( ILuaState lua, int i, int j ) + { + lua.RawSetI( 1, i ); + lua.RawSetI( 1, j ); + } + + private static bool SortComp( ILuaState lua, int a, int b ) + { + if( !lua.IsNil(2) ) // function? + { + lua.PushValue( 2 ); + lua.PushValue( a-1 ); // -1 to compensate function + lua.PushValue( b-2 ); // -2 to compensate function add `a' + lua.Call( 2, 1 ); + bool res = lua.ToBoolean( -1 ); + lua.Pop( 1 ); + return res; + } + else /// a < b? + return lua.Compare( a, b, LuaEq.LUA_OPLT ); + } + + private static void AuxSort( ILuaState lua, int l, int u ) + { + while( l < u ) // for tail recursion + { + // sort elements a[l], a[(l+u)/2] and a[u] + lua.RawGetI( 1, l ); + lua.RawGetI( 1, u ); + if( SortComp( lua, -1, -2 ) ) // a[u] < a[l]? + Set2( lua, l, u ); + else + lua.Pop( 2 ); + if( u-l == 1 ) break; // only 2 elements + int i = (l+u) / 2; + lua.RawGetI( 1, i ); + lua.RawGetI( 1, l ); + if( SortComp( lua, -2, -1 ) ) // a[i] < a[l]? + Set2( lua, i, l ); + else + { + lua.Pop( 1 ); // remove a[l] + lua.RawGetI( 1, u ); + if( SortComp( lua, -1, -2 ) ) // a[u] < a[i]? + Set2( lua, i, u ); + else + lua.Pop( 2 ); + } + if( u-l == 2 ) break; // only 3 arguments + lua.RawGetI( 1, i ); // Pivot + lua.PushValue( -1 ); + lua.RawGetI( 1, u-1 ); + Set2(lua, i, u-1); + /* a[l] <= P == a[u-1] <= a[u], only need to sort from l+1 to u-2 */ + i = l; + int j = u-1; + for (;;) { /* invariant: a[l..i] <= P <= a[j..u] */ + /* repeat ++i until a[i] >= P */ + lua.RawGetI( 1, ++i ); + while( SortComp(lua, -1, -2) ) + { + if (i>=u) lua.L_Error( "invalid order function for sorting" ); + lua.Pop(1); /* remove a[i] */ + lua.RawGetI( 1, ++i ); + } + /* repeat --j until a[j] <= P */ + lua.RawGetI( 1, --j ); + while ( SortComp(lua, -3, -1) ) { + if (j<=l) lua.L_Error( "invalid order function for sorting" ); + lua.Pop(1); /* remove a[j] */ + lua.RawGetI( 1, --j ); + } + if (j= R(A) + 1 */ + OP_EQ,/* A B C if ((RK(B) == RK(C)) ~= A) then pc++ */ + OP_LT,/* A B C if ((RK(B) < RK(C)) ~= A) then pc++ */ + OP_LE,/* A B C if ((RK(B) <= RK(C)) ~= A) then pc++ */ + + OP_TEST,/* A C if not (R(A) <=> C) then pc++ */ + OP_TESTSET,/* A B C if (R(B) <=> C) then R(A) := R(B) else pc++ */ + + OP_CALL,/* A B C R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1)) */ + OP_TAILCALL,/* A B C return R(A)(R(A+1), ... ,R(A+B-1)) */ + OP_RETURN,/* A B return R(A), ... ,R(A+B-2) (see note) */ + + OP_FORLOOP,/* A sBx R(A)+=R(A+2); + if R(A) + /// basic instruction format + /// + internal enum OpMode + { + iABC, + iABx, + iAsBx, + iAx, + } + + internal class OpCodeMode + { + public bool TMode; + public bool AMode; + public OpArgMask BMode; + public OpArgMask CMode; + public OpMode OpMode; + + public OpCodeMode(bool tMode, bool aMode, OpArgMask bMode, OpArgMask cMode, OpMode opMode) + { + TMode = tMode; + AMode = aMode; + BMode = bMode; + CMode = cMode; + OpMode = opMode; + } + } + + internal static class OpCodeInfo + { + public static OpCodeMode GetMode( OpCode op ) + { + return Info[(int)op]; + } + + private static Dictionary Info; + + static OpCodeInfo() + { + Info = new Dictionary(); + var opCode = OpCode.OP_MOVE; + var opCodeMode = new OpCodeMode(false, true, OpArgMask.OpArgR, OpArgMask.OpArgN, OpMode.iABC); + Info.Add((int)opCode, opCodeMode); + Info.Add( (int)OpCode.OP_LOADK, new OpCodeMode(false, true, OpArgMask.OpArgK, OpArgMask.OpArgN, OpMode.iABx) ); + Info.Add( (int)OpCode.OP_LOADKX, new OpCodeMode(false, true, OpArgMask.OpArgN, OpArgMask.OpArgN, OpMode.iABx) ); + Info.Add( (int)OpCode.OP_LOADBOOL, new OpCodeMode(false, true, OpArgMask.OpArgU, OpArgMask.OpArgU, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_LOADNIL, new OpCodeMode(false, true, OpArgMask.OpArgU, OpArgMask.OpArgN, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_GETUPVAL, new OpCodeMode(false, true, OpArgMask.OpArgU, OpArgMask.OpArgN, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_GETTABUP, new OpCodeMode(false, true, OpArgMask.OpArgU, OpArgMask.OpArgK, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_GETTABLE, new OpCodeMode(false, true, OpArgMask.OpArgR, OpArgMask.OpArgK, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_SETTABUP, new OpCodeMode(false, false, OpArgMask.OpArgK, OpArgMask.OpArgK, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_SETUPVAL, new OpCodeMode(false, false, OpArgMask.OpArgU, OpArgMask.OpArgN, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_SETTABLE, new OpCodeMode(false, false, OpArgMask.OpArgK, OpArgMask.OpArgK, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_NEWTABLE, new OpCodeMode(false, true, OpArgMask.OpArgU, OpArgMask.OpArgU, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_SELF, new OpCodeMode(false, true, OpArgMask.OpArgR, OpArgMask.OpArgK, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_ADD, new OpCodeMode(false, true, OpArgMask.OpArgK, OpArgMask.OpArgK, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_SUB, new OpCodeMode(false, true, OpArgMask.OpArgK, OpArgMask.OpArgK, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_MUL, new OpCodeMode(false, true, OpArgMask.OpArgK, OpArgMask.OpArgK, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_DIV, new OpCodeMode(false, true, OpArgMask.OpArgK, OpArgMask.OpArgK, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_MOD, new OpCodeMode(false, true, OpArgMask.OpArgK, OpArgMask.OpArgK, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_POW, new OpCodeMode(false, true, OpArgMask.OpArgK, OpArgMask.OpArgK, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_UNM, new OpCodeMode(false, true, OpArgMask.OpArgR, OpArgMask.OpArgN, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_NOT, new OpCodeMode(false, true, OpArgMask.OpArgR, OpArgMask.OpArgN, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_LEN, new OpCodeMode(false, true, OpArgMask.OpArgR, OpArgMask.OpArgN, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_CONCAT, new OpCodeMode(false, true, OpArgMask.OpArgR, OpArgMask.OpArgR, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_JMP, new OpCodeMode(false, false, OpArgMask.OpArgR, OpArgMask.OpArgN, OpMode.iAsBx) ); + Info.Add( (int)OpCode.OP_EQ, new OpCodeMode(true, false, OpArgMask.OpArgK, OpArgMask.OpArgK, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_LT, new OpCodeMode(true, false, OpArgMask.OpArgK, OpArgMask.OpArgK, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_LE, new OpCodeMode(true, false, OpArgMask.OpArgK, OpArgMask.OpArgK, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_TEST, new OpCodeMode(true, false, OpArgMask.OpArgN, OpArgMask.OpArgU, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_TESTSET, new OpCodeMode(true, true, OpArgMask.OpArgR, OpArgMask.OpArgU, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_CALL, new OpCodeMode(false, true, OpArgMask.OpArgU, OpArgMask.OpArgU, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_TAILCALL, new OpCodeMode(false, true, OpArgMask.OpArgU, OpArgMask.OpArgU, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_RETURN, new OpCodeMode(false, false, OpArgMask.OpArgU, OpArgMask.OpArgN, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_FORLOOP, new OpCodeMode(false, true, OpArgMask.OpArgR, OpArgMask.OpArgN, OpMode.iAsBx) ); + Info.Add( (int)OpCode.OP_FORPREP, new OpCodeMode(false, true, OpArgMask.OpArgR, OpArgMask.OpArgN, OpMode.iAsBx) ); + Info.Add( (int)OpCode.OP_TFORCALL, new OpCodeMode(false, false, OpArgMask.OpArgN, OpArgMask.OpArgU, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_TFORLOOP, new OpCodeMode(false, true, OpArgMask.OpArgR, OpArgMask.OpArgN, OpMode.iAsBx) ); + Info.Add( (int)OpCode.OP_SETLIST, new OpCodeMode(false, false, OpArgMask.OpArgU, OpArgMask.OpArgU, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_CLOSURE, new OpCodeMode(false, true, OpArgMask.OpArgU, OpArgMask.OpArgN, OpMode.iABx) ); + Info.Add( (int)OpCode.OP_VARARG, new OpCodeMode(false, true, OpArgMask.OpArgU, OpArgMask.OpArgN, OpMode.iABC) ); + Info.Add( (int)OpCode.OP_EXTRAARG, new OpCodeMode(false, false, OpArgMask.OpArgU, OpArgMask.OpArgU, OpMode.iAx) ); + } + } + +} + diff --git a/UniLua/Parser.cs b/UniLua/Parser.cs new file mode 100644 index 0000000..6a68dc3 --- /dev/null +++ b/UniLua/Parser.cs @@ -0,0 +1,1945 @@ + +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; + } + } + +} + diff --git a/UniLua/TagMethod.cs b/UniLua/TagMethod.cs new file mode 100644 index 0000000..45be6e9 --- /dev/null +++ b/UniLua/TagMethod.cs @@ -0,0 +1,102 @@ + +namespace UniLua +{ + // grep `NoTagMethodFlags' if num of TMS >= 32 + internal enum TMS + { + TM_INDEX, + TM_NEWINDEX, + TM_GC, + TM_MODE, + TM_LEN, + TM_EQ, + TM_ADD, + TM_SUB, + TM_MUL, + TM_DIV, + TM_MOD, + TM_POW, + TM_UNM, + TM_LT, + TM_LE, + TM_CONCAT, + TM_CALL, + TM_N /* number of elements in the enum */ + } + + public partial class LuaState + { + private string GetTagMethodName( TMS tm ) + { + switch( tm ) + { + case TMS.TM_INDEX: return "__index"; + case TMS.TM_NEWINDEX: return "__newindex"; + case TMS.TM_GC: return "__gc"; + case TMS.TM_MODE: return "__mode"; + case TMS.TM_LEN: return "__len"; + case TMS.TM_EQ: return "__eq"; + case TMS.TM_ADD: return "__add"; + case TMS.TM_SUB: return "__sub"; + case TMS.TM_MUL: return "__mul"; + case TMS.TM_DIV: return "__div"; + case TMS.TM_MOD: return "__mod"; + case TMS.TM_POW: return "__pow"; + case TMS.TM_UNM: return "__unm"; + case TMS.TM_LT: return "__lt"; + case TMS.TM_LE: return "__le"; + case TMS.TM_CONCAT: return "__concat"; + case TMS.TM_CALL: return "__call"; + default: throw new System.NotImplementedException(); + } + } + + private StkId T_GetTM( LuaTable mt, TMS tm ) + { + if( mt == null ) + return null; + + var res = mt.GetStr( GetTagMethodName( tm ) ); + if(res.V.TtIsNil()) // no tag method? + { + // cache this fact + mt.NoTagMethodFlags |= 1u << (int)tm; + return null; + } + else + return res; + } + + private StkId T_GetTMByObj( ref TValue o, TMS tm ) + { + LuaTable mt = null; + + switch( o.Tt ) + { + case (int)LuaType.LUA_TTABLE: + { + var tbl = o.HValue(); + mt = tbl.MetaTable; + break; + } + case (int)LuaType.LUA_TUSERDATA: + { + var ud = o.RawUValue(); + mt = ud.MetaTable; + break; + } + default: + { + mt = G.MetaTables[o.Tt]; + break; + } + } + return (mt != null) + ? mt.GetStr( GetTagMethodName( tm ) ) + : TheNilValue; + } + + } + +} + diff --git a/UniLua/ULDebug.cs b/UniLua/ULDebug.cs new file mode 100644 index 0000000..2509598 --- /dev/null +++ b/UniLua/ULDebug.cs @@ -0,0 +1,19 @@ + #define UNITY + +namespace UniLua.Tools +{ + // thanks to dharco + // refer to https://github.com/dharco/UniLua/commit/2854ddf2500ab2f943f01a6d3c9af767c092ce75 + public class ULDebug + { + public static System.Action Log = NoAction; + public static System.Action LogError = NoAction; + + private static void NoAction(object msg) { } + + static ULDebug() + { + } + } +} + diff --git a/UniLua/Undump.cs b/UniLua/Undump.cs new file mode 100644 index 0000000..e87e052 --- /dev/null +++ b/UniLua/Undump.cs @@ -0,0 +1,376 @@ + // #define DEBUG_BINARY_READER +// #define DEBUG_UNDUMP + +using System; + +using ULDebug = UniLua.Tools.ULDebug; + +namespace UniLua +{ + public class BinaryBytesReader + { + private ILoadInfo LoadInfo; + public int SizeOfSizeT; + + + public BinaryBytesReader( ILoadInfo loadinfo ) + { + LoadInfo = loadinfo; + SizeOfSizeT = 0; + } + + public byte[] ReadBytes( int count ) + { + byte[] ret = new byte[count]; + for( int i=0; i Int32.MaxValue ) + throw new NotImplementedException(); + + return (int)ret; + } + + public double ReadDouble() + { + var bytes = ReadBytes( 8 ); + double ret = BitConverter.ToDouble( bytes, 0 ); +#if DEBUG_BINARY_READER + Console.WriteLine( "ReadDouble: " + ret ); +#endif + return ret; + } + + public byte ReadByte() + { + var c = LoadInfo.ReadByte(); + if( c == -1 ) + throw new UndumpException("truncated"); +#if DEBUG_BINARY_READER + Console.WriteLine( "ReadBytes: " + c ); +#endif + return (byte)c; + } + + public string ReadString() + { + var n = ReadSizeT(); + if( n == 0 ) + return null; + + var bytes = ReadBytes( n ); + + // n=1: removing trailing '\0' + string ret = System.Text.Encoding.ASCII.GetString( bytes, 0, n-1 ); +#if DEBUG_BINARY_READER + Console.WriteLine( "ReadString n:" + n + " ret:" + ret ); +#endif + return ret; + } + } + + class UndumpException : Exception + { + public string Why; + + public UndumpException( string why ) + { + Why = why; + } + } + + public class Undump + { + private BinaryBytesReader Reader; + + + public static LuaProto LoadBinary( ILuaState lua, + ILoadInfo loadinfo, string name ) + { + try + { + var reader = new BinaryBytesReader( loadinfo ); + var undump = new Undump( reader ); + undump.LoadHeader(); + return undump.LoadFunction(); + } + catch( UndumpException e ) + { + var Lua = (LuaState)lua; + Lua.O_PushString( string.Format( + "{0}: {1} precompiled chunk", name, e.Why ) ); + Lua.D_Throw( ThreadStatus.LUA_ERRSYNTAX ); + return null; + } + } + + private Undump( BinaryBytesReader reader ) + { + Reader = reader; + } + + private int LoadInt() + { + return Reader.ReadInt(); + } + + private byte LoadByte() + { + return Reader.ReadByte(); + } + + private byte[] LoadBytes( int count ) + { + return Reader.ReadBytes( count ); + } + + private string LoadString() + { + return Reader.ReadString(); + } + + private bool LoadBoolean() + { + return LoadByte() != 0; + } + + private double LoadNumber() + { + return Reader.ReadDouble(); + } + + private void LoadHeader() + { + byte[] header = LoadBytes( 4 // Signature + + 8 // version, format version, size of int ... etc + + 6 // Tail + ); + byte v = header[ 4 /* skip signature */ + + 4 /* offset of sizeof(size_t) */ + ]; +#if DEBUG_UNDUMP + Console.WriteLine(string.Format("sizeof(size_t): {0}", v)); +#endif + Reader.SizeOfSizeT = v ; + } + + private Instruction LoadInstruction() + { + return (Instruction)Reader.ReadUInt(); + } + + private LuaProto LoadFunction() + { +#if DEBUG_UNDUMP + Console.WriteLine( "LoadFunction enter" ); +#endif + + LuaProto proto = new LuaProto(); + proto.LineDefined = LoadInt(); + proto.LastLineDefined = LoadInt(); + proto.NumParams = LoadByte(); + proto.IsVarArg = LoadBoolean(); + proto.MaxStackSize = LoadByte(); + + LoadCode(proto); + LoadConstants(proto); + LoadUpvalues(proto); + LoadDebug(proto); + return proto; + } + + private void LoadCode( LuaProto proto ) + { + var n = LoadInt(); +#if DEBUG_UNDUMP + Console.WriteLine( "LoadCode n:" + n ); +#endif + proto.Code.Clear(); + for( int i=0; i= s.Length ) + return false; + + char c = s[pos]; + if( c == '-' ) + { + ++pos; + return true; + } + else if( c == '+' ) + { + ++pos; + } + return false; + } + + private static bool IsXDigit( char c ) + { + if( Char.IsDigit( c ) ) + return true; + + if( 'a' <= c && c <= 'f' ) + return true; + + if( 'A' <= c && c <= 'F' ) + return true; + + return false; + } + + private static double ReadHexa( string s, ref int pos, double r, out int count ) + { + count = 0; + while( pos < s.Length && IsXDigit( s[pos] ) ) + { + r = (r * 16.0) + Int32.Parse( s[pos].ToString(), NumberStyles.HexNumber ); + ++pos; + ++count; + } + return r; + } + + private static double ReadDecimal( string s, ref int pos, double r, out int count ) + { + count = 0; + while( pos < s.Length && Char.IsDigit( s[pos] ) ) + { + r = (r * 10.0) + Int32.Parse( s[pos].ToString() ); + ++pos; + ++count; + } + return r; + } + + // following C99 specification for 'strtod' + public static double StrX2Number( string s, ref int curpos ) + { + int pos = curpos; + while( pos < s.Length && Char.IsWhiteSpace( s[pos] )) ++pos; + bool negative = IsNegative( s, ref pos ); + + // check `0x' + if( pos >= s.Length || !(s[pos] == '0' && (s[pos+1] == 'x' || s[pos+1] == 'X')) ) + return 0.0; + + pos += 2; // skip `0x' + + double r = 0.0; + int i = 0; + int e = 0; + r = ReadHexa( s, ref pos, r, out i ); + if( pos < s.Length && s[pos] == '.' ) + { + ++pos; // skip `.' + r = ReadHexa( s, ref pos, r, out e ); + } + if( i == 0 && e == 0 ) + return 0.0; // invalid format (no digit) + + // each fractional digit divides value by 2^-4 + e *= -4; + curpos = pos; + + // exponent part + if( pos < s.Length && (s[pos] == 'p' || s[pos] == 'P') ) + { + ++pos; // skip `p' + bool expNegative = IsNegative( s, ref pos ); + if( pos >= s.Length || !Char.IsDigit( s[pos] ) ) + goto ret; + + int exp1 = 0; + while( pos < s.Length && Char.IsDigit( s[pos] ) ) + { + exp1 = exp1 * 10 + Int32.Parse( s[pos].ToString() ); + ++pos; + } + if( expNegative ) + exp1 = -exp1; + e += exp1; + } + curpos = pos; + +ret: + if( negative ) r = -r; + + return r * Math.Pow(2.0, e); + } + + public static double Str2Number( string s, ref int curpos ) + { + int pos = curpos; + while( pos < s.Length && Char.IsWhiteSpace( s[pos] )) ++pos; + bool negative = IsNegative( s, ref pos ); + + double r = 0.0; + int i = 0; + int f = 0; + r = ReadDecimal( s, ref pos, r, out i ); + if( pos < s.Length && s[pos] == '.' ) + { + ++pos; + r = ReadDecimal( s, ref pos, r, out f ); + } + if( i == 0 && f == 0 ) + return 0.0; + + f = -f; + curpos = pos; + + // exponent part + double e = 0.0; + if( pos < s.Length && (s[pos] == 'e' || s[pos] == 'E') ) + { + ++pos; + bool expNegative = IsNegative( s, ref pos ); + if( pos >= s.Length || !Char.IsDigit( s[pos] ) ) + goto ret; + + int n; + e = ReadDecimal( s, ref pos, e, out n ); + if( expNegative ) + e = -e; + f += (int)e; + } + curpos = pos; + +ret: + if( negative ) r = -r; + + return r * Math.Pow(10, f); + } + + public static string TrimWhiteSpace( string str ) + { + int s = 0; + int e = str.Length; + + while( s < str.Length && Char.IsWhiteSpace( str[s] ) ) ++s; + if( s >= e ) + return ""; + + while( e >= 0 && Char.IsWhiteSpace( str[e-1] ) ) --e; + return str.Substring( s, e-s ); + } + } + +} + diff --git a/UniLua/VM.cs b/UniLua/VM.cs new file mode 100644 index 0000000..f508761 --- /dev/null +++ b/UniLua/VM.cs @@ -0,0 +1,1256 @@ + // #define DEBUG_NEW_FRAME +// #define DEBUG_INSTRUCTION +// #define DEBUG_INSTRUCTION_WITH_STACK + +// #define DEBUG_OP_GETTABUP +// #define DEBUG_OP_GETUPVAL +// #define DEBUG_OP_GETTABLE +// #define DEBUG_OP_EQ +// #define DEBUG_OP_SETLIST +// #define DEBUG_OP_CLOSURE +// #define DEBUG_OP_SETTABLE + +// #define DEBUG_RECORD_INS + +using System; +using System.Collections.Generic; + +namespace UniLua +{ + using ULDebug = UniLua.Tools.ULDebug; + using StringBuilder = System.Text.StringBuilder; + + public partial class LuaState + { + private const int MAXTAGLOOP = 100; + + private struct ExecuteEnvironment + { + public StkId[] Stack; + public List K; + public int Base; + public Instruction I; + + public StkId RA + { + get { return Stack[Base + I.GETARG_A()]; } + } + + public StkId RB + { + get { return Stack[Base + I.GETARG_B()]; } + } + + public StkId RK( int x ) + { + return Instruction.ISK( x ) ? K[Instruction.INDEXK(x)] : Stack[Base+x]; + } + + public StkId RKB + { + get { return RK( I.GETARG_B() ); } + } + + public StkId RKC + { + get { return RK( I.GETARG_C() ); } + } + } + + private void V_Execute() + { + ExecuteEnvironment env; + CallInfo ci = CI; +newframe: + Utl.Assert(ci == CI); + var cl = Stack[ci.FuncIndex].V.ClLValue(); + + env.Stack = Stack; + env.K = cl.Proto.K; + env.Base = ci.BaseIndex; + +#if DEBUG_NEW_FRAME + Console.WriteLine( "#### NEW FRAME #########################################################################" ); + Console.WriteLine( "## cl:" + cl ); + Console.WriteLine( "## Base:" + env.Base ); + Console.WriteLine( "########################################################################################" ); +#endif + + while( true ) + { + Instruction i = ci.SavedPc.ValueInc; + env.I = i; + +#if DEBUG_SRC_INFO + int line = 0; + string src = ""; + if(ci.IsLua) { + line = GetCurrentLine(ci); + src = GetCurrentLuaFunc(ci).Proto.Source; + } +#endif + + StkId ra = env.RA; + +#if DEBUG_DUMP_INS_STACK +#if DEBUG_DUMP_INS_STACK_EX + DumpStack( env.Base, i.ToString() ); +#else + DumpStack( env.Base ); +#endif +#endif + +#if DEBUG_INSTRUCTION + Console.WriteLine( System.DateTime.Now + " [VM] ======================================================================== Instruction: " + i +#if DEBUG_INSTRUCTION_WITH_STACK + + "\n" + DumpStackToString( env.Base.Index ) +#endif + ); +#endif + +#if DEBUG_RECORD_INS + InstructionHistory.Enqueue(i); + if( InstructionHistory.Count > 100 ) { + InstructionHistory.Dequeue(); + } +#endif + + switch( i.GET_OPCODE() ) + { + case OpCode.OP_MOVE: + { + var rb = env.RB; + +#if DEBUG_OP_MOVE + Console.WriteLine( "[VM] ==== OP_MOVE rb:" + rb ); + Console.WriteLine( "[VM] ==== OP_MOVE ra:" + ra ); +#endif + + ra.V.SetObj(ref rb.V); + break; + } + + case OpCode.OP_LOADK: + { + var rb = env.K[i.GETARG_Bx()]; + ra.V.SetObj(ref rb.V); + break; + } + + case OpCode.OP_LOADKX: + { + Utl.Assert( ci.SavedPc.Value.GET_OPCODE() == OpCode.OP_EXTRAARG ); + var rb = env.K[ci.SavedPc.ValueInc.GETARG_Ax()]; + ra.V.SetObj(ref rb.V); + break; + } + + case OpCode.OP_LOADBOOL: + { + ra.V.SetBValue(i.GETARG_B() != 0); + if( i.GETARG_C() != 0 ) + ci.SavedPc.Index += 1; // skip next instruction (if C) + break; + } + + case OpCode.OP_LOADNIL: + { + int b = i.GETARG_B(); + int index = ra.Index; + do { + Stack[index++].V.SetNilValue(); + } while (b-- > 0); + break; + } + + case OpCode.OP_GETUPVAL: + { + int b = i.GETARG_B(); + ra.V.SetObj(ref cl.Upvals[b].V.V); + +#if DEBUG_OP_GETUPVAL + // for( var j=0; j 0 || c > 0) + { tbl.Resize(b, c); } + break; + } + + case OpCode.OP_SELF: + { + // OP_SELF put function referenced by a table on ra + // and the table on ra+1 + // + // RB: table + // RKC: key + var ra1 = Stack[ra.Index+1]; + var rb = env.RB; + ra1.V.SetObj(ref rb.V); + V_GetTable( rb, env.RKC, ra ); + env.Base = ci.BaseIndex; + break; + } + + case OpCode.OP_ADD: + { + var rkb = env.RKB; + var rkc = env.RKC; + if(rkb.V.TtIsNumber() && rkc.V.TtIsNumber()) + { ra.V.SetNValue(rkb.V.NValue + rkc.V.NValue); } + else + { V_Arith(ra, rkb, rkc, TMS.TM_ADD); } + + env.Base = ci.BaseIndex; + break; + } + + case OpCode.OP_SUB: + { + var rkb = env.RKB; + var rkc = env.RKC; + if(rkb.V.TtIsNumber() && rkc.V.TtIsNumber()) + { ra.V.SetNValue(rkb.V.NValue - rkc.V.NValue); } + else + { V_Arith(ra, rkb, rkc, TMS.TM_SUB); } + env.Base = ci.BaseIndex; + break; + } + + case OpCode.OP_MUL: + { + var rkb = env.RKB; + var rkc = env.RKC; + if(rkb.V.TtIsNumber() && rkc.V.TtIsNumber()) + { ra.V.SetNValue(rkb.V.NValue * rkc.V.NValue); } + else + { V_Arith(ra, rkb, rkc, TMS.TM_MUL); } + env.Base = ci.BaseIndex; + break; + } + + case OpCode.OP_DIV: + { + var rkb = env.RKB; + var rkc = env.RKC; + if(rkb.V.TtIsNumber() && rkc.V.TtIsNumber()) + { ra.V.SetNValue(rkb.V.NValue / rkc.V.NValue); } + else + { V_Arith(ra, rkb, rkc, TMS.TM_DIV); } + env.Base = ci.BaseIndex; + break; + } + + case OpCode.OP_MOD: + { + var rkb = env.RKB; + var rkc = env.RKC; + if(rkb.V.TtIsNumber() && rkc.V.TtIsNumber()) { + var v1 = rkb.V.NValue; + var v2 = rkc.V.NValue; + ra.V.SetNValue(v1 - Math.Floor(v1/v2)*v2); + } + else + { V_Arith(ra, rkb, rkc, TMS.TM_MOD); } + env.Base = ci.BaseIndex; + break; + } + + case OpCode.OP_POW: + { + var rkb = env.RKB; + var rkc = env.RKC; + if(rkb.V.TtIsNumber() && rkc.V.TtIsNumber()) + { ra.V.SetNValue(Math.Pow(rkb.V.NValue, rkc.V.NValue)); } + else + { V_Arith(ra, rkb, rkc, TMS.TM_POW); } + env.Base = ci.BaseIndex; + break; + } + + case OpCode.OP_UNM: + { + var rb = env.RB; + if(rb.V.TtIsNumber()) { + ra.V.SetNValue(-rb.V.NValue); + } + else { + V_Arith(ra, rb, rb, TMS.TM_UNM); + env.Base = ci.BaseIndex; + } + break; + } + + case OpCode.OP_NOT: + { + var rb = env.RB; + ra.V.SetBValue(IsFalse(ref rb.V)); + break; + } + + case OpCode.OP_LEN: + { + V_ObjLen( ra, env.RB ); + env.Base = ci.BaseIndex; + break; + } + + case OpCode.OP_CONCAT: + { + int b = i.GETARG_B(); + int c = i.GETARG_C(); + Top = Stack[env.Base + c + 1]; + V_Concat( c - b + 1 ); + env.Base = ci.BaseIndex; + + ra = env.RA; // 'V_Concat' may invoke TMs and move the stack + StkId rb = env.RB; + ra.V.SetObj(ref rb.V); + + Top = Stack[ci.TopIndex]; // restore top + break; + } + + case OpCode.OP_JMP: + { + V_DoJump( ci, i, 0 ); + break; + } + + case OpCode.OP_EQ: + { + var lhs = env.RKB; + var rhs = env.RKC; + var expectEq = i.GETARG_A() != 0; +#if DEBUG_OP_EQ + Console.WriteLine( "[VM] ==== OP_EQ lhs:" + lhs ); + Console.WriteLine( "[VM] ==== OP_EQ rhs:" + rhs ); + Console.WriteLine( "[VM] ==== OP_EQ expectEq:" + expectEq ); + Console.WriteLine( "[VM] ==== OP_EQ (lhs.V == rhs.V):" + (lhs.V == rhs.V) ); +#endif + if((lhs.V == rhs.V) != expectEq) + { + ci.SavedPc.Index += 1; // skip next jump instruction + } + else + { + V_DoNextJump( ci ); + } + env.Base = ci.BaseIndex; + break; + } + + case OpCode.OP_LT: + { + var expectCmpResult = i.GETARG_A() != 0; + if( V_LessThan( env.RKB, env.RKC ) != expectCmpResult ) + ci.SavedPc.Index += 1; + else + V_DoNextJump( ci ); + env.Base = ci.BaseIndex; + break; + } + + case OpCode.OP_LE: + { + var expectCmpResult = i.GETARG_A() != 0; + if( V_LessEqual( env.RKB, env.RKC ) != expectCmpResult ) + ci.SavedPc.Index += 1; + else + V_DoNextJump( ci ); + env.Base = ci.BaseIndex; + break; + } + + case OpCode.OP_TEST: + { + if((i.GETARG_C() != 0) ? + IsFalse(ref ra.V) : !IsFalse(ref ra.V)) + { + ci.SavedPc.Index += 1; + } + else V_DoNextJump( ci ); + + env.Base = ci.BaseIndex; + break; + } + + case OpCode.OP_TESTSET: + { + var rb = env.RB; + if((i.GETARG_C() != 0) ? + IsFalse(ref rb.V) : !IsFalse(ref rb.V)) + { + ci.SavedPc.Index += 1; + } + else + { + ra.V.SetObj(ref rb.V); + V_DoNextJump( ci ); + } + env.Base = ci.BaseIndex; + break; + } + + case OpCode.OP_CALL: + { + int b = i.GETARG_B(); + int nresults = i.GETARG_C() - 1; + if( b != 0) { Top = Stack[ra.Index + b]; } // else previous instruction set top + if( D_PreCall( ra, nresults ) ) { // C# function? + if( nresults >= 0 ) + Top = Stack[ci.TopIndex]; + env.Base = ci.BaseIndex; + } + else { // Lua function + ci = CI; + ci.CallStatus |= CallStatus.CIST_REENTRY; + goto newframe; + } + break; + } + + case OpCode.OP_TAILCALL: + { + int b = i.GETARG_B(); + if( b != 0) { Top = Stack[ra.Index + b]; } // else previous instruction set top + + Utl.Assert( i.GETARG_C() - 1 == LuaDef.LUA_MULTRET ); + + var called = D_PreCall( ra, LuaDef.LUA_MULTRET ); + + // C# function ? + if( called ) + { + env.Base = ci.BaseIndex; + } + + // LuaFunciton + else + { + var nci = CI; // called frame + var oci = BaseCI[CI.Index-1]; // caller frame + StkId nfunc = Stack[nci.FuncIndex];// called function + StkId ofunc = Stack[oci.FuncIndex];// caller function + var ncl = nfunc.V.ClLValue(); + var ocl = ofunc.V.ClLValue(); + + // last stack slot filled by 'precall' + int lim = nci.BaseIndex + ncl.Proto.NumParams; + + if(cl.Proto.P.Count > 0) + { F_Close( Stack[env.Base] ); } + + // move new frame into old one + var nindex = nfunc.Index; + var oindex = ofunc.Index; + while(nindex < lim) { + Stack[oindex++].V.SetObj(ref Stack[nindex++].V); + } + + oci.BaseIndex = ofunc.Index + (nci.BaseIndex - nfunc.Index); + oci.TopIndex = ofunc.Index + (Top.Index - nfunc.Index); + Top = Stack[oci.TopIndex]; + oci.SavedPc = nci.SavedPc; + oci.CallStatus |= CallStatus.CIST_TAIL; + ci = CI = oci; + + ocl = ofunc.V.ClLValue(); + Utl.Assert(Top.Index == oci.BaseIndex + ocl.Proto.MaxStackSize); + + goto newframe; + } + + break; + } + + case OpCode.OP_RETURN: + { + int b = i.GETARG_B(); + if( b != 0 ) { Top = Stack[ra.Index + b - 1]; } + if( cl.Proto.P.Count > 0 ) { F_Close(Stack[env.Base]); } + b = D_PosCall( ra.Index ); + if( (ci.CallStatus & CallStatus.CIST_REENTRY) == 0 ) + { + return; + } + else + { + ci = CI; + if( b != 0 ) Top = Stack[ci.TopIndex]; + goto newframe; + } + } + + case OpCode.OP_FORLOOP: + { + var ra1 = Stack[ra.Index + 1]; + var ra2 = Stack[ra.Index + 2]; + var ra3 = Stack[ra.Index + 3]; + + var step = ra2.V.NValue; + var idx = ra.V.NValue + step; // increment index + var limit = ra1.V.NValue; + + if( (0 < step) ? idx <= limit + : limit <= idx ) + { + ci.SavedPc.Index += i.GETARG_sBx(); // jump back + ra.V.SetNValue(idx);// updateinternal index... + ra3.V.SetNValue(idx);// ... and external index + } + + break; + } + + case OpCode.OP_FORPREP: + { + var init = new TValue(); + var limit = new TValue(); + var step = new TValue(); + + var ra1 = Stack[ra.Index + 1]; + var ra2 = Stack[ra.Index + 2]; + + // WHY: why limit is not used ? + + if(!V_ToNumber(ra, ref init)) + G_RunError("'for' initial value must be a number"); + if(!V_ToNumber(ra1, ref limit)) + G_RunError("'for' limit must be a number"); + if(!V_ToNumber(ra2, ref step)) + G_RunError("'for' step must be a number"); + + ra.V.SetNValue(init.NValue - step.NValue); + ci.SavedPc.Index += i.GETARG_sBx(); + + break; + } + + case OpCode.OP_TFORCALL: + { + int rai = ra.Index; + int cbi = ra.Index + 3; + Stack[cbi+2].V.SetObj(ref Stack[rai+2].V); + Stack[cbi+1].V.SetObj(ref Stack[rai+1].V); + Stack[cbi].V.SetObj(ref Stack[rai].V); + + StkId callBase = Stack[cbi]; + Top = Stack[cbi+3]; // func. +2 args (state and index) + + D_Call( callBase, i.GETARG_C(), true ); + + env.Base = ci.BaseIndex; + + Top = Stack[ci.TopIndex]; + i = ci.SavedPc.ValueInc; // go to next instruction + env.I = i; + ra = env.RA; + + DumpStack( env.Base ); +#if DEBUG_INSTRUCTION + Console.WriteLine( "[VM] ============================================================ OP_TFORCALL Instruction: " + i ); +#endif + + Utl.Assert( i.GET_OPCODE() == OpCode.OP_TFORLOOP ); + goto l_tforloop; + } + + case OpCode.OP_TFORLOOP: +l_tforloop: + { + StkId ra1 = Stack[ra.Index + 1]; + if(!ra1.V.TtIsNil()) // continue loop? + { + ra.V.SetObj(ref ra1.V); + ci.SavedPc += i.GETARG_sBx(); + } + break; + } + + // sets the values for a range of array elements in a table(RA) + // RA -> table + // RB -> number of elements to set + // C -> encodes the block number of the table to be initialized + // the values used to initialize the table are located in + // R(A+1), R(A+2) ... + case OpCode.OP_SETLIST: + { + int n = i.GETARG_B(); + int c = i.GETARG_C(); + if( n == 0 ) n = (Top.Index - ra.Index) - 1; + if( c == 0 ) + { + Utl.Assert( ci.SavedPc.Value.GET_OPCODE() == OpCode.OP_EXTRAARG ); + c = ci.SavedPc.ValueInc.GETARG_Ax(); + } + + var tbl = ra.V.HValue(); + Utl.Assert( tbl != null ); + + int last = ((c-1) * LuaDef.LFIELDS_PER_FLUSH) + n; + int rai = ra.Index; + for(; n>0; --n) { + tbl.SetInt(last--, ref Stack[rai+n].V); + } +#if DEBUG_OP_SETLIST + Console.WriteLine( "[VM] ==== OP_SETLIST ci.Top:" + ci.Top.Index ); + Console.WriteLine( "[VM] ==== OP_SETLIST Top:" + Top.Index ); +#endif + Top = Stack[ci.TopIndex]; // correct top (in case of previous open call) + break; + } + + case OpCode.OP_CLOSURE: + { + LuaProto p = cl.Proto.P[ i.GETARG_Bx() ]; + V_PushClosure( p, cl.Upvals, env.Base, ra ); +#if DEBUG_OP_CLOSURE + Console.WriteLine( "OP_CLOSURE:" + ra.Value ); + var racl = ra.Value as LuaLClosure; + if( racl != null ) + { + for( int ii=0; ii + /// VARARG implements the vararg operator `...' in expressions. + /// VARARG copies B-1 parameters into a number of registers + /// starting from R(A), padding with nils if there aren't enough values. + /// If B is 0, VARARG copies as many values as it can based on + /// the number of parameters passed. + /// If a fixed number of values is required, B is a value greater than 1. + /// If any number of values is required, B is 0. + /// + case OpCode.OP_VARARG: + { + int b = i.GETARG_B() - 1; + int n = (env.Base - ci.FuncIndex) - cl.Proto.NumParams - 1; + if( b < 0 ) // B == 0? + { + b = n; + D_CheckStack(n); + ra = env.RA; // previous call may change the stack + Top = Stack[ra.Index + n]; + } + + var p = ra.Index; + var q = env.Base - n; + for(int j=0; j= 2 ); + + do + { + var top = Top; + int n = 2; + var lhs = Stack[top.Index - 2]; + var rhs = Stack[top.Index - 1]; + if(!(lhs.V.TtIsString() || lhs.V.TtIsNumber()) || !ToString(ref rhs.V)) + { + if( !CallBinTM( lhs, rhs, lhs, TMS.TM_CONCAT ) ) + G_ConcatError( lhs, rhs ); + } + else if(rhs.V.SValue().Length == 0) { + ToString(ref lhs.V); + } + else if(lhs.V.TtIsString() && lhs.V.SValue().Length == 0) { + lhs.V.SetObj(ref rhs.V); + } + else + { + StringBuilder sb = new StringBuilder(); + n = 0; + for( ; n 1 ); + } + + private void V_DoJump( CallInfo ci, Instruction i, int e ) + { + int a = i.GETARG_A(); + if( a > 0 ) + F_Close(Stack[ci.BaseIndex + (a-1)]); + ci.SavedPc += i.GETARG_sBx() + e; + } + + private void V_DoNextJump( CallInfo ci ) + { + Instruction i = ci.SavedPc.Value; + V_DoJump( ci, i, 1 ); + } + + private bool V_ToNumber( StkId obj, ref TValue n ) + { + if( obj.V.TtIsNumber() ) { + n.SetNValue( obj.V.NValue ); + return true; + } + if( obj.V.TtIsString() ) { + double val; + if( O_Str2Decimal(obj.V.SValue(), out val) ) { + n.SetNValue( val ); + return true; + } + } + + return false; + } + + private bool V_ToString(ref TValue v) + { + if(!v.TtIsNumber()) { return false; } + + v.SetSValue(v.NValue.ToString()); + return true; + } + + private LuaOp TMS2OP( TMS op ) + { + switch( op ) + { + case TMS.TM_ADD: return LuaOp.LUA_OPADD; + case TMS.TM_SUB: return LuaOp.LUA_OPSUB; + case TMS.TM_MUL: return LuaOp.LUA_OPMUL; + case TMS.TM_DIV: return LuaOp.LUA_OPDIV; + case TMS.TM_POW: return LuaOp.LUA_OPPOW; + case TMS.TM_UNM: return LuaOp.LUA_OPUNM; + + // case TMS.TM_EQ: return LuaOp.LUA_OPEQ; + // case TMS.TM_LT: return LuaOp.LUA_OPLT; + // case TMS.TM_LE: return LuaOp.LUA_OPLE; + + default: throw new System.NotImplementedException(); + } + } + + private void CallTM( ref TValue f, ref TValue p1, ref TValue p2, StkId p3, bool hasres ) + { + var result = p3.Index; + var func = Top; + StkId.inc(ref Top).V.SetObj(ref f); // push function + StkId.inc(ref Top).V.SetObj(ref p1); // push 1st argument + StkId.inc(ref Top).V.SetObj(ref p2); // push 2nd argument + if( !hasres ) // no result? p3 is 3rd argument + StkId.inc(ref Top).V.SetObj(ref p3.V); + D_CheckStack(0); + D_Call( func, (hasres ? 1 : 0), CI.IsLua ); + if( hasres ) // if has result, move it ot its place + { + Top = Stack[Top.Index - 1]; + Stack[result].V.SetObj(ref Top.V); + } + } + + private bool CallBinTM( StkId p1, StkId p2, StkId res, TMS tm ) + { + var tmObj = T_GetTMByObj(ref p1.V, tm); + if(tmObj.V.TtIsNil()) + tmObj = T_GetTMByObj(ref p2.V, tm); + if(tmObj.V.TtIsNil()) + return false; + + CallTM( ref tmObj.V, ref p1.V, ref p2.V, res, true ); + return true; + } + + private void V_Arith( StkId ra, StkId rb, StkId rc, TMS op ) + { + var nb = new TValue(); + var nc = new TValue(); + if(V_ToNumber(rb, ref nb) && V_ToNumber(rc, ref nc)) + { + var res = O_Arith( TMS2OP(op), nb.NValue, nc.NValue ); + ra.V.SetNValue( res ); + } + else if( !CallBinTM( rb, rc, ra, op ) ) + { + G_ArithError( rb, rc ); + } + } + + private bool CallOrderTM( StkId p1, StkId p2, TMS tm, out bool error ) + { + if( !CallBinTM( p1, p2, Top, tm ) ) + { + error = true; // no metamethod + return false; + } + + error = false; + return !IsFalse(ref Top.V); + } + + private bool V_LessThan( StkId lhs, StkId rhs ) + { + // compare number + if(lhs.V.TtIsNumber() && rhs.V.TtIsNumber()) { + return lhs.V.NValue < rhs.V.NValue; + } + + // compare string + if(lhs.V.TtIsString() && rhs.V.TtIsString()) { + return string.Compare(lhs.V.SValue(), rhs.V.SValue()) < 0; + } + + bool error; + var res = CallOrderTM( lhs, rhs, TMS.TM_LT, out error ); + if( error ) + { + G_OrderError( lhs, rhs ); + return false; + } + return res; + } + + private bool V_LessEqual( StkId lhs, StkId rhs ) + { + // compare number + if(lhs.V.TtIsNumber() && rhs.V.TtIsNumber()) { + return lhs.V.NValue <= rhs.V.NValue; + } + + // compare string + if(lhs.V.TtIsString() && rhs.V.TtIsString()) { + return string.Compare(lhs.V.SValue(), rhs.V.SValue()) <= 0; + } + + // first try `le' + bool error; + var res = CallOrderTM( lhs, rhs, TMS.TM_LE, out error ); + if( !error ) + return res; + + // else try `lt' + res = CallOrderTM( rhs, lhs, TMS.TM_LT, out error ); + if( !error ) + return res; + + G_OrderError( lhs, rhs ); + return false; + } + + private void V_FinishOp() + { + int ciIndex = CI.Index; + int stackBase = CI.BaseIndex; + Instruction i = (CI.SavedPc - 1).Value; // interrupted instruction + OpCode op = i.GET_OPCODE(); + switch( op ) + { + case OpCode.OP_ADD: case OpCode.OP_SUB: case OpCode.OP_MUL: case OpCode.OP_DIV: + case OpCode.OP_MOD: case OpCode.OP_POW: case OpCode.OP_UNM: case OpCode.OP_LEN: + case OpCode.OP_GETTABUP: case OpCode.OP_GETTABLE: case OpCode.OP_SELF: + { + var tmp = Stack[stackBase + i.GETARG_A()]; + Top = Stack[Top.Index-1]; + tmp.V.SetObj(ref Stack[Top.Index].V); + break; + } + + case OpCode.OP_LE: case OpCode.OP_LT: case OpCode.OP_EQ: + { + bool res = !IsFalse(ref Stack[Top.Index-1].V); + Top = Stack[Top.Index-1]; + // metamethod should not be called when operand is K + Utl.Assert( !Instruction.ISK( i.GETARG_B() ) ); + if( op == OpCode.OP_LE && // `<=' using `<' instead? + T_GetTMByObj(ref Stack[stackBase + i.GETARG_B()].V, TMS.TM_LE ).V.TtIsNil() ) + { + res = !res; // invert result + } + + var ci = BaseCI[ciIndex]; + Utl.Assert( ci.SavedPc.Value.GET_OPCODE() == OpCode.OP_JMP ); + if( (res ? 1 : 0) != i.GETARG_A() ) + if( (i.GETARG_A() == 0) == res ) // condition failed? + { + ci.SavedPc.Index++; // skip jump instruction + } + break; + } + + case OpCode.OP_CONCAT: + { + StkId top = Stack[Top.Index - 1]; // top when `CallBinTM' was called + int b = i.GETARG_B(); // first element to concatenate + int total = top.Index-1 - (stackBase+b); // yet to concatenate + var tmp = Stack[top.Index-2]; + tmp.V.SetObj(ref top.V); // put TM result in proper position + if(total > 1) // are there elements to concat? + { + Top = Stack[Top.Index-1]; + V_Concat( total ); + } + // move final result to final position + var ci = BaseCI[ciIndex]; + var tmp2 = Stack[ci.BaseIndex + i.GETARG_A()]; + tmp2.V.SetObj(ref Stack[Top.Index-1].V); + Top = Stack[ci.TopIndex]; + break; + } + + case OpCode.OP_TFORCALL: + { + var ci = BaseCI[ciIndex]; + Utl.Assert( ci.SavedPc.Value.GET_OPCODE() == OpCode.OP_TFORLOOP ); + Top = Stack[ci.TopIndex]; // restore top + break; + } + + case OpCode.OP_CALL: + { + if( i.GETARG_C() - 1 >= 0 ) // numResults >= 0? + { + var ci = BaseCI[ciIndex]; + Top = Stack[ci.TopIndex]; // restore top + } + break; + } + + case OpCode.OP_TAILCALL: case OpCode.OP_SETTABUP: case OpCode.OP_SETTABLE: + break; + + default: + Utl.Assert( false ); + break; + } + } + + internal bool V_RawEqualObj( ref TValue t1, ref TValue t2 ) + { + return (t1.Tt == t2.Tt) && V_EqualObject( ref t1, ref t2, true ); + } + + private bool EqualObj( ref TValue t1, ref TValue t2, bool rawEq ) + { + return (t1.Tt == t2.Tt) && V_EqualObject( ref t1, ref t2, rawEq ); + } + + private StkId GetEqualTM( LuaTable mt1, LuaTable mt2, TMS tm ) + { + var tm1 = FastTM( mt1, tm ); + if(tm1 == null) // no metamethod + return null; + if(mt1 == mt2) // same metatables => same metamethods + return tm1; + var tm2 = FastTM( mt2, tm ); + if(tm2 == null) // no metamethod + return null; + if(V_RawEqualObj(ref tm1.V, ref tm2.V)) // same metamethods? + return tm1; + return null; + } + + private bool V_EqualObject( ref TValue t1, ref TValue t2, bool rawEq ) + { + Utl.Assert( t1.Tt == t2.Tt ); + StkId tm = null; + switch( t1.Tt ) + { + case (int)LuaType.LUA_TNIL: + return true; + case (int)LuaType.LUA_TNUMBER: + return t1.NValue == t2.NValue; + case (int)LuaType.LUA_TUINT64: + return t1.UInt64Value == t2.UInt64Value; + case (int)LuaType.LUA_TBOOLEAN: + return t1.BValue() == t2.BValue(); + case (int)LuaType.LUA_TSTRING: + return t1.SValue() == t2.SValue(); + case (int)LuaType.LUA_TUSERDATA: + { + var ud1 = t1.RawUValue(); + var ud2 = t2.RawUValue(); + if(ud1.Value == ud2.Value) + return true; + if(rawEq) + return false; + tm = GetEqualTM( ud1.MetaTable, ud2.MetaTable, TMS.TM_EQ ); + break; + } + case (int)LuaType.LUA_TTABLE: + { + var tbl1 = t1.HValue(); + var tbl2 = t2.HValue(); + if( System.Object.ReferenceEquals( tbl1, tbl2 ) ) + return true; + if( rawEq ) + return false; + tm = GetEqualTM( tbl1.MetaTable, tbl2.MetaTable, TMS.TM_EQ ); + break; + } + default: + return t1.OValue == t2.OValue; + } + if( tm == null ) // no TM? + return false; + CallTM(ref tm.V, ref t1, ref t2, Top, true ); // call TM + return !IsFalse(ref Top.V); + } + + } + +} +