/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Narrative JavaScript compiler.
 *
 * The Initial Developer of the Original Code is
 * Neil Mix (neilmix -at- gmail -dot- com).
 * Portions created by the Initial Developer are Copyright (C) 2006
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

// TODO:
// - arguments array, +tests
// - runtime test for named arguments
// - dynamic function declarations?
// - try/catch support
// - switch support

// these methods are shortcuts (small names for smaller compilations sizes)
// for the NjsRuntime equivalents.

function njen(/*...*/) {
	return NjsRuntime.enterFrame.apply(NjsRuntime, arguments);
}

function njex(frame, retval) {
	return NjsRuntime.exitFrame(frame, retval);
}

function njkeys(obj) {
	return NjsRuntime.keys(obj);
}

/* ----------------------------------------------------
                      NjsRuntime
   ---------------------------------------------------- */

function NjsRuntime() {
}

NjsRuntime.CALL   = 1;
NjsRuntime.RETURN = 2;

NjsRuntime.createFrame = function(parentFrame) {
	var frame = function(retval) {
		NjsRuntime.returnToFrame(arguments.callee, retval);
	}

	for (var n in frame) {
		delete(frame[n]);
	}
	
	frame.isNJFrame   = true;
	frame.parentFrame = parentFrame;
	frame.cp          = 0;
	return frame;
}

NjsRuntime.enterFrame = function(frameThis, args /*, ... argNames ... */) {
	var index = args.length - 1;
	var frame  = args[index];
	if (frame == null || typeof(frame) != "function" || !frame.isNJFrame) {
		// this is a new "thread"
		frame = this.createFrame(null);
	} else {
		if (frame.state == this.CALL) {
			// the method is being invoked by another frame, and thus the frame
			// we've been given is the parent frame
			frame = this.createFrame(frame);
		} else if (frame.state == this.RETURN) {
			// we're returning back into the frame.
		} else {
			throw new Error("Invalid frame state: " + frame.state);
		}

		// remove the frame from the arguments
		delete args[index];
		args.length = index;
	}
	
	if (frame.frameThis == null) {
		// this a new frame entry.  We need to init the frame context
		frame.frameThis  = frameThis;
		frame._arguments = args;
		
		for( var i = 2; i < arguments.length; i++ ) {
			frame['_' + arguments[i]] = args[i-2];
		}
	}

	frame.state = this.CALL;
	return frame;
}

NjsRuntime.exitFrame = function(frame, retval) {
	if (frame.parentFrame)
		this.returnToFrame(frame.parentFrame, retval);
}

NjsRuntime.returnToFrame = function(frame, retval) {
	frame.state = this.RETURN;
	frame["rv" + frame.cp] = retval;
	frame._arguments.callee.call(frame.frameThis, frame);
}

NjsRuntime.keys = function(obj) {
	var keys = [];
	for(var n in obj) {
		keys.push(n);
	}
	return keys;
}

