Source: MVM/MVM-DataModel.js

/**
 * @namespace MVM
 */

var MVM = MVM || {};


/**
 * @classdesc The data management subsystem of MVM. It works by maintaining a stack of scope frames - themselves parts of a wider scope tree. Variable modifications are made by chaining up parent scopes when necessary.
 * @class MVM.DataModel
 * @public
 * @author FelixMcFelix (Kyle S.)
 */
MVM.DataModel = function(){
	this.root = new MVM.StackFrame(null);
	this.stack = [this.root];
};

MVM.DataModel.prototype = {
	/**
	 * Retrieve the currently active scope frame.
	 * @method MVM.DataModel#current
	 * @return {MVM.StackFrame}
	 * @public
	 */
	current: function(){
		try{
			return this.stack[this.stack.length - 1];
		} catch(e){
			return undefined;
		}
	},

	/**
	 * Retrieve the scope frame a set height above the current frame, where 0 retrieves the current frame.
	 * @method MVM.DataModel#relative
	 * @param {number} count - the target relative height to retrieve a scope frame from.
	 * @return {MVM.StackFrame}
	 * @public
	 */
	relative: function(count){
		var cursor = this.current();
		while(count>0 && cursor && cursor.parent){
			cursor = cursor.parent;
			count--;
		}

		if(count>0){
			throw "Invalid relative call - too few parents.";
		}

		return cursor;
	},

	/**
	 * Enter a new {@link MVM.StackFrame} - signifying a new block scope.
	 * @method MVM.DataModel#enter
	 * @return {MVM.DataModel} Returns self to allow for some degree of method chaining.
	 * @public
	 */
	enter: function(){
		var tmp = new MVM.StackFrame(this.current());
		this.stack[this.stack.length - 1] = tmp;

		return this;
	},

	/**
	 * Leave the current {@link MVM.StackFrame} - signifying an end to a block scope.
	 * @method MVM.DataModel#exit
	 * @return {MVM.DataModel} Returns self to allow for some degree of method chaining.
	 * @public
	 */
	exit: function(){
		if(this.current() !== this.root){
			this.stack[this.stack.length - 1] = this.current().parent;
		} else{
			throw "Tried to exit from scope past root level.";
		}

		return this;
	},

	/**
	 * Call a function at another point in the code, moving any relevant data into the new {@link MVM.StackFrame}.
	 * @method MVM.DataModel#call
	 * @param {number} argc - the amount of arguments to copy into the new scope.
	 * @param {number} rel - the relative height in scope frames of where the function declaration occurred.
	 * @param {number} ret - the address the {@link MVM.VM} must return to once the function ends.
	 * @return {MVM.DataModel} Returns self to allow for some degree of method chaining.
	 * @public
	 */
	call: function(argc, rel, ret){
		var prev = this.current();
		var parent = this.relative(rel);
		this.stack.push(new MVM.StackFrame(parent));

		var c = this.current();
		c.returnAddr = ret;
		c.functionBase = true;

		while (argc>0){
			c.setVar(argc-1, prev.pop());	
			argc--;
		}

		return this;
	},

	/**
	 * Return from the current function, moving back to the original scope.
	 * @method MVM.DataModel#funcreturn
	 * @param {*} value - the value to place into the {@link MVM.StackFrame} which we are returning to.
	 * @return {number} The return address that the {@link MVM.VM} must utilise.
	 * @public
	 */
	funcreturn: function(value){
		var p = this.stack.pop();

		while(!p.functionBase){
			p = p.parent;
		}
		
		if (value!==null) {
			this.current().push(value);
		}

		return p.returnAddr;
	}
};

/**
 * @classdesc An individual stack frame used by the {@link MVM.DataModel}.
 * @class MVM.StackFrame
 * @public
 * @author FelixMcFelix (Kyle S.)
 */
MVM.StackFrame = function(parent){
	this.parent = parent;
	this.variables = [];
	this.stack = [];
	this.returnAddr = undefined;
	this.functionBase = false;
};

MVM.StackFrame.prototype = {
	/**
	 * Push a new value to the top of the stack.
	 * @method MVM.StackFrame#push
	 * @param {*} value - the value to place onto the top of the stack.
	 * @return {MVM.StackFrame} Returns self to allow for some degree of method chaining.
	 * @public
	 */
	push: function(value){
		this.stack.push(value);

		return this;
	},

	/**
	 * Pop off the top value from the stack, and return it.
	 * @method MVM.StackFrame#pop
	 * @return {*} The value retrieved from the top of the stack.
	 * @public
	 */
	pop: function(){
		return this.stack.pop();
	},

	/**
	 * Return the top value of the stack without modifying the frame's state.
	 * @method MVM.StackFrame#peek
	 * @return {*} The value retrieved from the top of the stack.
	 * @public
	 */
	peek: function(){
		try {
			return this.stack[this.stack.length-1];
		} catch (e){
			return undefined;
		}
	},

	/**
	 * Set a variable in this stack frame to a given value.
	 * @method MVM.StackFrame#setVar
	 * @param {number} varNo - the index of the variable's location.
	 * @param {*} value - the value to place into the variable store.
	 * @return {MVM.StackFrame} Returns self to allow for some degree of method chaining.
	 * @public
	 */
	setVar: function(varNo, val){
		this.variables[varNo] = val;

		return this;
	},

	/**
	 * Get the value of a variable in this stack frame.
	 * @method MVM.StackFrame#getVar
	 * @param {number} varNo - the index of the variable's location.
	 * @return {*} The value obtained from the specified variable index.
	 * @public
	 */
	getVar: function(varNo){
		return this.variables[varNo];
	}
};