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

import org.luaj.vm2.libs.DebugLib.CallFrame;

import java.util.ArrayList;
import java.util.List;

/**
 * Extension of {@link LuaFunction} which executes lua bytecode.
 * <p>
 * A {@link LuaClosure} is a combination of a {@link Prototype}
 * and a {@link LuaValue} to use as an environment for execution.
 * Normally the {@link LuaValue} is a {@link Globals} in which case the environment
 * will contain standard lua libraries.
 *
 * <p>
 * There are three main ways {@link LuaClosure} instances are created:
 * <ul>
 * <li>Construct an instance using {@link #LuaClosure(Prototype, LuaValue)}</li>
 * <li>Construct it indirectly by loading a chunk via {@link Globals#load(java.io.Reader, String)}
 * <li>Execute the lua bytecode {@link Lua#OP_CLOSURE} as part of bytecode processing
 * </ul>
 * <p>
 * To construct it directly, the {@link Prototype} is typically created via a compiler such as
 * {@link org.luaj.vm2.compiler.LuaC}:
 * <pre> {@code
 * String script = "print( 'hello, world' )";
 * InputStream is = new ByteArrayInputStream(script.getBytes());
 * Prototype p = LuaC.instance.compile(is, "script");
 * LuaValue globals = JsePlatform.standardGlobals();
 * LuaClosure f = new LuaClosure(p, globals);
 * f.call();
 * }</pre>
 * <p>
 * To construct it indirectly, the {@link Globals#load(java.io.Reader, String)} method may be used:
 * <pre> {@code
 * Globals globals = JsePlatform.standardGlobals();
 * LuaFunction f = globals.load(new StringReader(script), "script");
 * LuaClosure c = f.checkclosure();  // This may fail if LuaJC is installed.
 * c.call();
 * }</pre>
 * <p>
 * In this example, the "checkclosure()" may fail if direct lua-to-java-bytecode
 * compiling using LuaJC is installed, because no LuaClosure is created in that case
 * and the value returned is a {@link LuaFunction} but not a {@link LuaClosure}.
 * <p>
 * Since a {@link LuaClosure} is a {@link LuaFunction} which is a {@link LuaValue},
 * all the value operations can be used directly such as:
 * <ul>
 * <li>{@link LuaValue#call()}</li>
 * <li>{@link LuaValue#call(LuaValue)}</li>
 * <li>{@link LuaValue#invoke()}</li>
 * <li>{@link LuaValue#invoke(Varargs)}</li>
 * <li>{@link LuaValue#method(String)}</li>
 * <li>{@link LuaValue#method(String,LuaValue)}</li>
 * <li>{@link LuaValue#invokemethod(String)}</li>
 * <li>{@link LuaValue#invokemethod(String,Varargs)}</li>
 * <li> ...</li>
 * </ul>
 * @see LuaValue
 * @see LuaFunction
 * @see LuaValue#isclosure()
 * @see LuaValue#checkclosure()
 * @see LuaValue#optclosure(LuaClosure)
 * @see LoadState
 * @see Globals#compiler
 */
public class LuaClosure extends LuaFunction implements PrototypeProvider {
	private static final UpValue[] NOUPVALUES = new UpValue[0];
	private static final boolean[] NOCLOSEVARS = new boolean[0];

	public final Prototype p;

	public UpValue[] upValues;

	final Globals globals;

	/** Create a closure around a Prototype with a specific environment.
	 * If the prototype has upvalues, the environment will be written into the first upvalue.
	 * @param p the Prototype to construct this Closure for.
	 * @param env the environment to associate with the closure.
	 */
	public LuaClosure(Prototype p, LuaValue env) {
		this.p = p;
		this.initupvalue1(env);
		globals = env instanceof Globals? (Globals) env: null;
	}

	public Prototype prototype() {
		return p;
	}

	public void initupvalue1(LuaValue env) {
		if (p.upvalues == null || p.upvalues.length == 0)
			this.upValues = NOUPVALUES;
		else {
			this.upValues = new UpValue[p.upvalues.length];
			this.upValues[0] = new UpValue(new LuaValue[] {env}, 0);
		}
	}


	public boolean isclosure() {
		return true;
	}

	public LuaClosure optclosure(LuaClosure defval) {
		return this;
	}

	public LuaClosure checkclosure() {
		return this;
	}

	public String tojstring() {
		return "function: " + p.toString();
	}


	private List<LuaValue[]> stackPool = new ArrayList<>();
	private LuaValue[] getNewStack() {
		if (stackPool.isEmpty()) {
			return getNewStackRaw();
		} else {
			return stackPool.remove(stackPool.size() - 1);
		}
	}

	private LuaValue[] getNewStackRaw() {
		int max = p.maxstacksize;
		LuaValue[] stack = new LuaValue[max];
		System.arraycopy(NILS, 0, stack, 0, max);
		return stack;
	}

	private void releaseStack(LuaValue[] stack) {
		System.arraycopy(NILS, 0, stack, 0, stack.length);
		stackPool.add(stack);
	}

	public final LuaValue call() {
		LuaValue[] stack = getNewStack();
		LuaValue result = execute(stack,NONE).arg1();
		releaseStack(stack);
		return result;
	}

	public final LuaValue call(LuaValue arg) {
		LuaValue[] stack = getNewStack();
		LuaValue result;
		switch ( p.numparams ) {
			default:
				stack[0]=arg;
				result = execute(stack,NONE).arg1();
				break;
			case 0:
				result = execute(stack,arg).arg1();
				break;
		}
		releaseStack(stack);
		return result;
	}

	public final LuaValue call(LuaValue arg1, LuaValue arg2) {
		LuaValue[] stack = getNewStack();
		LuaValue result;
		switch ( p.numparams ) {
			default:
				stack[0]=arg1;
				stack[1]=arg2;
				result = execute(stack,NONE).arg1();
				break;
			case 1:
				stack[0]=arg1;
				result = execute(stack,arg2).arg1();
				break;
			case 0:
				result = execute(stack,p.is_vararg!=0 ? varargsOf(arg1,arg2) : NONE).arg1();
				break;
		}
		releaseStack(stack);
		return result;
	}

	public final LuaValue call(LuaValue arg1, LuaValue arg2, LuaValue arg3) {
		LuaValue[] stack = getNewStack();
		LuaValue result;
		switch ( p.numparams ) {
			default:
				stack[0]=arg1;
				stack[1]=arg2;
				stack[2]=arg3;
				result = execute(stack,NONE).arg1();
				break;
			case 2:
				stack[0]=arg1;
				stack[1]=arg2;
				result = execute(stack,arg3).arg1();
				break;
			case 1:
				stack[0]=arg1;
				result = execute(stack,p.is_vararg!=0 ? varargsOf(arg2,arg3) : NONE).arg1();
				break;
			case 0:
				result = execute(stack,p.is_vararg!=0 ? varargsOf(arg1,arg2,arg3) : NONE).arg1();
				break;
		}
		releaseStack(stack);
		return result;
	}

	public final Varargs invoke(Varargs varargs) {
		return onInvoke(varargs).eval();
	}

	public final Varargs onInvoke(Varargs varargs) {
		LuaValue[] stack = getNewStack();
		for ( int i=0; i<p.numparams; i++ )
			stack[i] = varargs.arg(i+1);
		Varargs result = execute(stack,p.is_vararg!=0 ? varargs.subargs(p.numparams+1) : NONE);
		if (result instanceof LuaValue) {
			releaseStack(stack);
		}
		return result;
	}

	protected Varargs execute( LuaValue[] stack, Varargs varargs ) {
		// loop through instructions
		int i,a,b,c,pc=0,top=0;
		LuaValue o;
		Varargs v = NONE;
		int[] code = p.code;
		LuaValue[] k = p.k;

		// upvalues are only possible when closures create closures
		// TODO: use linked list.
		UpValue[] openups = p.p.length>0? new UpValue[stack.length]: null;
		boolean[] closedLocals = hasToCloseLocals()? new boolean[stack.length]: NOCLOSEVARS;
		if (p.varargname != null && p.numparams < stack.length)
			stack[p.numparams] = packVarargs(varargs);

		// allow for debug hooks
		if (globals != null && globals.debuglib != null)
			globals.debuglib.onCall( this, varargs, stack );

		// process instructions
		try {
			for (; true; ++pc) {
				if (Thread.currentThread().isInterrupted()) {
					throw new LuaError("interrupted");
				}
				prepareToCloseLocals(stack, closedLocals, pc);
				if (globals != null && globals.debuglib != null)
					globals.debuglib.onInstruction( pc, v, top );

				// pull out instruction
				i = code[pc];
				a = ((i>>6) & 0xff);

				// process the op code
				switch ( i & 0x3f ) {

					case Lua.OP_MOVE:/*	A B	R(A):= R(B)					*/
						stack[a] = stack[i>>>23];
						continue;

					case Lua.OP_LOADK:/*	A Bx	R(A):= Kst(Bx)					*/
						stack[a] = k[i>>>14];
						continue;

					case Lua.OP_LOADKX:/*	A 	R(A) := Kst(extra arg)					*/
						++pc;
						i = code[pc];
						if ((i & 0x3f) != Lua.OP_EXTRAARG) {
							int op = i & 0x3f;
							throw new LuaError("OP_EXTRAARG expected after OP_LOADKX, got " +
									(op < Print.OPNAMES.length - 1 ? Print.OPNAMES[op] : "UNKNOWN_OP_" + op));
						}
						stack[a] = k[i>>>6];
						continue;

					case Lua.OP_LOADBOOL:/*	A B C	R(A):= (Bool)B: if (C) pc++			*/
						stack[a] = (i>>>23!=0)? LuaValue.TRUE: LuaValue.FALSE;
						if ((i&(0x1ff<<14)) != 0)
							++pc; /* skip next instruction (if C) */
						continue;

					case Lua.OP_LOADNIL: /*	A B	R(A):= ...:= R(A+B):= nil			*/
						for ( b=i>>>23; b-->=0; )
							stack[a++] = LuaValue.NIL;
						continue;

					case Lua.OP_GETUPVAL: /*	A B	R(A):= UpValue[B]				*/
						stack[a] = upValues[i>>>23].getValue();
						continue;

					case Lua.OP_GETTABUP: /*	A B C	R(A) := UpValue[B][RK(C)]			*/
						stack[a] = upValues[i>>>23].getValue().get((c=(i>>14)&0x1ff)>0xff? k[c&0x0ff]: stack[c]);
						continue;

					case Lua.OP_GETTABLE: /*	A B C	R(A):= R(B)[RK(C)]				*/
						stack[a] = stack[i>>>23].get((c=(i>>14)&0x1ff)>0xff? k[c&0x0ff]: stack[c]);
						continue;

					case Lua.OP_SETTABUP: /*	A B C	UpValue[A][RK(B)] := RK(C)			*/
						upValues[a].getValue().set(((b=i>>>23)>0xff? k[b&0x0ff]: stack[b]), (c=(i>>14)&0x1ff)>0xff? k[c&0x0ff]: stack[c]);
						continue;

					case Lua.OP_SETUPVAL: /*	A B	UpValue[B]:= R(A)				*/
						upValues[i>>>23].setValue(stack[a]);
						continue;

					case Lua.OP_SETTABLE: /*	A B C	R(A)[RK(B)]:= RK(C)				*/
						stack[a].set(((b=i>>>23)>0xff? k[b&0x0ff]: stack[b]), (c=(i>>14)&0x1ff)>0xff? k[c&0x0ff]: stack[c]);
						continue;

					case Lua.OP_NEWTABLE: /*	A B C	R(A):= {} (size = B,C)				*/
						stack[a] = new LuaTable(i>>>23,(i>>14)&0x1ff);
						continue;

					case Lua.OP_SELF: /*	A B C	R(A+1):= R(B): R(A):= R(B)[RK(C)]		*/
						stack[a+1] = (o = stack[i>>>23]);
						stack[a] = o.get((c=(i>>14)&0x1ff)>0xff? k[c&0x0ff]: stack[c]);
						continue;

					case Lua.OP_ADD: /*	A B C	R(A):= RK(B) + RK(C)				*/
						stack[a] = ((b=i>>>23)>0xff? k[b&0x0ff]: stack[b]).add((c=(i>>14)&0x1ff)>0xff? k[c&0x0ff]: stack[c]);
						continue;

					case Lua.OP_SUB: /*	A B C	R(A):= RK(B) - RK(C)				*/
						stack[a] = ((b=i>>>23)>0xff? k[b&0x0ff]: stack[b]).sub((c=(i>>14)&0x1ff)>0xff? k[c&0x0ff]: stack[c]);
						continue;

					case Lua.OP_MUL: /*	A B C	R(A):= RK(B) * RK(C)				*/
						stack[a] = ((b=i>>>23)>0xff? k[b&0x0ff]: stack[b]).mul((c=(i>>14)&0x1ff)>0xff? k[c&0x0ff]: stack[c]);
						continue;

					case Lua.OP_DIV: /*	A B C	R(A):= RK(B) / RK(C)				*/
						stack[a] = ((b=i>>>23)>0xff? k[b&0x0ff]: stack[b]).div((c=(i>>14)&0x1ff)>0xff? k[c&0x0ff]: stack[c]);
						continue;

					case Lua.OP_IDIV: /*	A B C	R(A):= RK(B) // RK(C)				*/
						stack[a] = ((b=i>>>23)>0xff? k[b&0x0ff]: stack[b]).idiv((c=(i>>14)&0x1ff)>0xff? k[c&0x0ff]: stack[c]);
						continue;

					case Lua.OP_BAND: /*	A B C	R(A):= RK(B) & RK(C)				*/
						stack[a] = ((b=i>>>23)>0xff? k[b&0x0ff]: stack[b]).band((c=(i>>14)&0x1ff)>0xff? k[c&0x0ff]: stack[c]);
						continue;

					case Lua.OP_BOR: /*	A B C	R(A):= RK(B) | RK(C)				*/
						stack[a] = ((b=i>>>23)>0xff? k[b&0x0ff]: stack[b]).bor((c=(i>>14)&0x1ff)>0xff? k[c&0x0ff]: stack[c]);
						continue;

					case Lua.OP_BXOR: /*	A B C	R(A):= RK(B) ~ RK(C)				*/
						stack[a] = ((b=i>>>23)>0xff? k[b&0x0ff]: stack[b]).bxor((c=(i>>14)&0x1ff)>0xff? k[c&0x0ff]: stack[c]);
						continue;

					case Lua.OP_SHL: /*	A B C	R(A):= RK(B) << RK(C)				*/
						stack[a] = ((b=i>>>23)>0xff? k[b&0x0ff]: stack[b]).shl((c=(i>>14)&0x1ff)>0xff? k[c&0x0ff]: stack[c]);
						continue;

					case Lua.OP_SHR: /*	A B C	R(A):= RK(B) >> RK(C)				*/
						stack[a] = ((b=i>>>23)>0xff? k[b&0x0ff]: stack[b]).shr((c=(i>>14)&0x1ff)>0xff? k[c&0x0ff]: stack[c]);
						continue;

					case Lua.OP_MOD: /*	A B C	R(A):= RK(B) % RK(C)				*/
						stack[a] = ((b=i>>>23)>0xff? k[b&0x0ff]: stack[b]).mod((c=(i>>14)&0x1ff)>0xff? k[c&0x0ff]: stack[c]);
						continue;

					case Lua.OP_POW: /*	A B C	R(A):= RK(B) ^ RK(C)				*/
						stack[a] = ((b=i>>>23)>0xff? k[b&0x0ff]: stack[b]).pow((c=(i>>14)&0x1ff)>0xff? k[c&0x0ff]: stack[c]);
						continue;

					case Lua.OP_UNM: /*	A B	R(A):= -R(B)					*/
						stack[a] = stack[i>>>23].neg();
						continue;

					case Lua.OP_BNOT: /*	A B	R(A):= ~R(B)					*/
						stack[a] = stack[i>>>23].bnot();
						continue;

					case Lua.OP_NOT: /*	A B	R(A):= not R(B)				*/
						stack[a] = stack[i>>>23].not();
						continue;

					case Lua.OP_LEN: /*	A B	R(A):= length of R(B)				*/
						stack[a] = stack[i>>>23].len();
						continue;

					case Lua.OP_CONCAT: /*	A B C	R(A):= R(B).. ... ..R(C)			*/
						b = i>>>23;
						c = (i>>14)&0x1ff;
					{
						if ( c > b+1 ) {
							Buffer sb = stack[c].buffer();
							while ( --c>=b )
								sb.concatTo(stack[c]);
							stack[a] = sb.value();
						} else {
							stack[a] = stack[c-1].concat(stack[c]);
						}
					}
					continue;

					case Lua.OP_JMP: /*	A sBx	pc+=sBx; if (A) close all upvalues >= R(A - 1)	*/
						pc  += (i>>>14)-0x1ffff;
						if (a > 0) {
							--a;
							if (openups != null) {
								for (b = openups.length; --b>=0; )
									if (openups[b] != null && openups[b].index >= a) {
										openups[b].close();
										openups[b] = null;
									}
							}
							closeLocals(stack, closedLocals, a, pc + 1, LuaValue.NIL);
						}
						continue;

					case Lua.OP_EQ: /*	A B C	if ((RK(B) == RK(C)) ~= A) then pc++		*/
						if ( ((b=i>>>23)>0xff? k[b&0x0ff]: stack[b]).eq_b((c=(i>>14)&0x1ff)>0xff? k[c&0x0ff]: stack[c]) != (a!=0) )
							++pc;
						continue;

					case Lua.OP_LT: /*	A B C	if ((RK(B) <  RK(C)) ~= A) then pc++  		*/
						if ( ((b=i>>>23)>0xff? k[b&0x0ff]: stack[b]).lt_b((c=(i>>14)&0x1ff)>0xff? k[c&0x0ff]: stack[c]) != (a!=0) )
							++pc;
						continue;

					case Lua.OP_LE: /*	A B C	if ((RK(B) <= RK(C)) ~= A) then pc++  		*/
						if ( ((b=i>>>23)>0xff? k[b&0x0ff]: stack[b]).lteq_b((c=(i>>14)&0x1ff)>0xff? k[c&0x0ff]: stack[c]) != (a!=0) )
							++pc;
						continue;

					case Lua.OP_TEST: /*	A C	if not (R(A) <=> C) then pc++			*/
						if ( stack[a].toboolean() != ((i&(0x1ff<<14))!=0) )
							++pc;
						continue;

					case Lua.OP_TESTSET: /*	A B C	if (R(B) <=> C) then R(A):= R(B) else pc++	*/
						/* note: doc appears to be reversed */
						if ( (o=stack[i>>>23]).toboolean() != ((i&(0x1ff<<14))!=0) )
							++pc;
						else
							stack[a] = o; // TODO: should be sBx?
						continue;

					case Lua.OP_CALL: /*	A B C	R(A), ... ,R(A+C-2):= R(A)(R(A+1), ... ,R(A+B-1)) */
						switch ( i & (Lua.MASK_B | Lua.MASK_C) ) {
							case (1<<Lua.POS_B) | (0<<Lua.POS_C): v=stack[a].invoke(NONE); top=a+v.narg(); continue;
							case (2<<Lua.POS_B) | (0<<Lua.POS_C): v=stack[a].invoke(stack[a+1]); top=a+v.narg(); continue;
							case (1<<Lua.POS_B) | (1<<Lua.POS_C): stack[a].call(); continue;
							case (2<<Lua.POS_B) | (1<<Lua.POS_C): stack[a].call(stack[a+1]); continue;
							case (3<<Lua.POS_B) | (1<<Lua.POS_C): stack[a].call(stack[a+1],stack[a+2]); continue;
							case (4<<Lua.POS_B) | (1<<Lua.POS_C): stack[a].call(stack[a+1],stack[a+2],stack[a+3]); continue;
							case (1<<Lua.POS_B) | (2<<Lua.POS_C): stack[a] = stack[a].call(); continue;
							case (2<<Lua.POS_B) | (2<<Lua.POS_C): stack[a] = stack[a].call(stack[a+1]); continue;
							case (3<<Lua.POS_B) | (2<<Lua.POS_C): stack[a] = stack[a].call(stack[a+1],stack[a+2]); continue;
							case (4<<Lua.POS_B) | (2<<Lua.POS_C): stack[a] = stack[a].call(stack[a+1],stack[a+2],stack[a+3]); continue;
							default:
								b = i>>>23;
								c = (i>>14)&0x1ff;
								v = stack[a].invoke(b>0?
										varargsOf(stack, a+1, b-1): // exact arg count
										varargsOf(stack, a+1, top-v.narg()-(a+1), v));  // from prev top
								if ( c > 0 ) {
									v.copyto(stack, a, c-1);
									v = NONE;
								} else {
									top = a + v.narg();
									v = v.dealias();
								}
								continue;
						}

					case Lua.OP_TAILCALL: /*	A B C	return R(A)(R(A+1), ... ,R(A+B-1))		*/
						switch ( i & Lua.MASK_B ) {
							case (1<<Lua.POS_B): return new TailcallVarargs(stack[a], NONE);
							case (2<<Lua.POS_B): return new TailcallVarargs(stack[a], stack[a+1]);
							case (3<<Lua.POS_B): return new TailcallVarargs(stack[a], varargsOf(stack[a+1],stack[a+2]));
							case (4<<Lua.POS_B): return new TailcallVarargs(stack[a], varargsOf(stack[a+1],stack[a+2],stack[a+3]));
							default:
								b = i>>>23;
								v = b>0?
										varargsOf(stack,a+1,b-1): // exact arg count
										varargsOf(stack, a+1, top-v.narg()-(a+1), v); // from prev top
								return new TailcallVarargs( stack[a], v );
						}

					case Lua.OP_RETURN: /*	A B	return R(A), ... ,R(A+B-2)	(see note)	*/
						closeLocals(stack, closedLocals, 0, p.code.length, LuaValue.NIL);
						b = i>>>23;
						switch ( b ) {
							case 0: return varargsOf(stack, a, top-v.narg()-a, v);
							case 1: return NONE;
							case 2: return stack[a];
							default:
								return varargsOf(stack, a, b-1);
						}

					case Lua.OP_FORLOOP: /*	A sBx	R(A)+=R(A+2): if R(A) <?= R(A+1) then { pc+=sBx: R(A+3)=R(A) }*/
					{
						LuaValue limit = stack[a + 1];
						LuaValue step  = stack[a + 2];
						LuaValue idx   = stack[a].add(step);
						if (step.gt_b(0)? idx.lteq_b(limit): idx.gteq_b(limit)) {
							stack[a] = idx;
							stack[a + 3] = idx;
							pc += (i>>>14)-0x1ffff;
						}
					}
					continue;

					case Lua.OP_FORPREP: /*	A sBx	R(A)-=R(A+2): pc+=sBx				*/
					{
						LuaValue init  = stack[a].checknumber("'for' initial value must be a number");
						LuaValue limit = stack[a + 1].checknumber("'for' limit must be a number");
						LuaValue step  = stack[a + 2].checknumber("'for' step must be a number");
						stack[a] = init.sub(step);
						stack[a + 1] = limit;
						stack[a + 2] = step;
						pc += (i>>>14)-0x1ffff;
					}
					continue;

					case Lua.OP_TFORCALL: /* A C	R(A+3), ... ,R(A+2+C) := R(A)(R(A+1), R(A+2));	*/
						v = stack[a].invoke(varargsOf(stack[a+1],stack[a+2]));
						c = (i>>14) & 0x1ff;
						while (--c >= 0)
							stack[a+3+c] = v.arg(c+1);
						v = NONE;
						continue;

					case Lua.OP_TFORLOOP: /* A sBx	if R(A+1) ~= nil then { R(A)=R(A+1); pc += sBx */
						if (!stack[a+1].isnil()) { /* continue loop? */
							stack[a] = stack[a+1];  /* save control varible. */
							pc += (i>>>14)-0x1ffff;
						}
						continue;

					case Lua.OP_SETLIST: /*	A B C	R(A)[(C-1)*FPF+i]:= R(A+i), 1 <= i <= B	*/
					{
						if ( (c=(i>>14)&0x1ff) == 0 )
							c = code[++pc];
						int offset = (c-1) * Lua.LFIELDS_PER_FLUSH;
						o = stack[a];
						if ( (b=i>>>23) == 0 ) {
							b = top - a - 1;
							int m = b - v.narg();
							int j=1;
							for ( ;j<=m; j++ )
								o.set(offset+j, stack[a + j]);
							for ( ;j<=b; j++ )
								o.set(offset+j, v.arg(j-m));
						} else {
							o.presize( offset + b );
							for (int j=1; j<=b; j++)
								o.set(offset+j, stack[a + j]);
						}
					}
					continue;

					case Lua.OP_CLOSURE: /*	A Bx	R(A):= closure(KPROTO[Bx])	*/
					{
						Prototype newp = p.p[i>>>14];
						LuaClosure ncl = new LuaClosure(newp, globals);
						Upvaldesc[] uv = newp.upvalues;
						for ( int j=0, nup=uv.length; j<nup; ++j ) {
							if (uv[j].instack)  /* upvalue refes to local variable? */
								ncl.upValues[j] = findupval(stack, uv[j].idx, openups);
							else  /* get upvalue from enclosing function */
								ncl.upValues[j] = upValues[uv[j].idx];
						}
						stack[a] = ncl;
					}
					continue;

					case Lua.OP_VARARG: /*	A B	R(A), R(A+1), ..., R(A+B-1) = vararg		*/
						b = i>>>23;
						if ( b == 0 ) {
							top = a + (b = varargs.narg());
							v = varargs;
						} else {
							for ( int j=1; j<b; ++j )
								stack[a+j-1] = varargs.arg(j);
						}
						continue;

					case Lua.OP_EXTRAARG:
						throw new java.lang.IllegalArgumentException("Uexecutable opcode: OP_EXTRAARG");

					default:
						throw new java.lang.IllegalArgumentException("Illegal opcode: " + (i & 0x3f));
				}
			}
		} catch ( LuaError le ) {
			if (le.traceback == null)
				processErrorHooks(le, p, pc);
			closeLocals(stack, closedLocals, 0, p.code.length, le.getMessageObject());
			throw le;
		} catch ( Exception e ) {
			LuaError le = new LuaError(e);
			processErrorHooks(le, p, pc);
			closeLocals(stack, closedLocals, 0, p.code.length, le.getMessageObject());
			throw le;
		} finally {
			closeLocals(stack, closedLocals, 0, p.code.length, LuaValue.NIL);
			if ( openups != null )
				for ( int u=openups.length; --u>=0; )
					if ( openups[u] != null )
						openups[u].close();
			if (globals != null && globals.debuglib != null)
				globals.debuglib.onReturn();
		}
	}

	private LuaTable packVarargs(Varargs varargs) {
		LuaTable table = LuaValue.tableOf(varargs, 1);
		table.set("n", LuaValue.valueOf(varargs.narg()));
		return table;
	}

	private boolean hasToCloseLocals() {
		if (p.locvars == null)
			return false;
		for (int i = 0; i < p.locvars.length; i++) {
			LocVars local = p.locvars[i];
			if (local != null && local.toclose)
				return true;
		}
		return false;
	}

	private void prepareToCloseLocals(LuaValue[] stack, boolean[] closedLocals, int pc) {
		if (closedLocals.length == 0 || p.locvars == null)
			return;
		for (int i = 0; i < p.locvars.length; i++) {
			LocVars local = p.locvars[i];
			if (local == null || !local.toclose || local.slot < 0 || local.startpc != pc)
				continue;
			if (local.slot < closedLocals.length)
				closedLocals[local.slot] = false;
			checkClosable(local, stack[local.slot]);
		}
	}

	private void closeLocals(LuaValue[] stack, boolean[] closedLocals, int minSlot, int closePcExclusive, LuaValue err) {
		if (closedLocals.length == 0 || p.locvars == null)
			return;
		for (int i = p.locvars.length - 1; i >= 0; i--) {
			LocVars local = p.locvars[i];
			if (local == null || !local.toclose || local.slot < 0 || local.slot < minSlot)
				continue;
			if (local.slot >= closedLocals.length || closedLocals[local.slot])
				continue;
			if (local.endpc > closePcExclusive)
				continue;
			closedLocals[local.slot] = true;
			LuaValue value = stack[local.slot];
			if (value == null || value == LuaValue.FALSE)
				continue;
			LuaValue close = value.metatag(LuaValue.CLOSE);
			if (!close.isnil())
				close.call(value, err.isnil() ? LuaValue.NIL : err);
		}
	}

	private void checkClosable(LocVars local, LuaValue value) {
		if (value == null || value == LuaValue.FALSE)
			return;
		if (!value.metatag(LuaValue.CLOSE).isnil())
			return;
		throw new LuaError("variable '" + local.varname.tojstring() + "' got a non-closable value");
	}

	/**
	 *  Run the error hook if there is one
	 *  @param msg the message to use in error hook processing.
	 * */
	LuaValue errorHook(LuaValue msgobj, String msg, int level) {
		if (globals == null ) return LuaValue.valueOf(msg);
		final LuaThread r = globals.running;
		if (r.errorfunc == null)
			return LuaValue.valueOf(globals.debuglib != null?
					msg + "\n" + globals.debuglib.traceback(level):
					msg);
		final LuaValue e = r.errorfunc;
		r.errorfunc = null;
		try {
			return e.call(msgobj != null ? msgobj : LuaValue.NIL);
		} catch ( Throwable t ) {
			return LuaValue.valueOf("error in error handling");
		} finally {
			r.errorfunc = e;
		}
	}

	private void processErrorHooks(LuaError le, Prototype p, int pc) {
		String file = "?";
		int line = -1;
		{
			CallFrame frame = null;
			if (globals != null && globals.debuglib != null) {
				frame = globals.debuglib.getCallFrame(le.level);
				if (frame != null) {
					String src = frame.shortsource();
					file = src != null ? src : "?";
					line = frame.currentline();
				}
			}
			if (frame == null) {
				file = p.source != null? p.source.tojstring(): "?";
				line = p.lineinfo != null && pc >= 0 && pc < p.lineinfo.length ? p.lineinfo[pc] : -1;
			}
		}
		le.fileline = file + ":" + line;
		LuaValue error = errorHook(le.getMessageObject(), le.getMessage(), le.level);
		le.setMessageObject(error);
		le.traceback = error != null ? error.tojstring() : null;
	}

	private UpValue findupval(LuaValue[] stack, short idx, UpValue[] openups) {
		final int n = openups.length;
		for (int i = 0; i < n; ++i)
			if (openups[i] != null && openups[i].index == idx)
				return openups[i];
		for (int i = 0; i < n; ++i)
			if (openups[i] == null)
				return openups[i] = new UpValue(stack, idx);
		error("No space for upvalue");
		return null;
	}

	protected LuaValue getUpvalue(int i) {
		return upValues[i].getValue();
	}

	protected void setUpvalue(int i, LuaValue v) {
		upValues[i].setValue(v);
	}

	public String name() {
		return "<"+p.shortsource()+":"+p.linedefined+">";
	}


}
