/*********************************************************************************/
/**			copyright (c) PlanetCalc.com, 2009-2010			**/
/**		    	Planet Calc interpreter functions			 */
/*********************************************************************************/

var PCI = function () {
var tabs = 0;	
function getValue( arg ) {
	var t = typeof(arg);
	if ( t=='number') {
		return arg.toString();
	} else if ( t=='string' || t=='undefined') {
		return arg;
	}
	return arg.getValue?arg.getValue():arg;
};

function grammarMatcher() {
	this.match = function ( stream, rules, ruleIn ) {
		var rule = ruleIn
		if ( typeof( rule ) == 'object' ) {
			var res = null;
			switch( rule.t ) {
				case "&":
					res = this.matchAnd( stream, rules, rule );
					break;	
				case "!":
					res = this.matchNot( stream, rules, rule );	
					break;	
				case "|":
					res = this.matchOneOf( stream, rules, rule );	
					break;	
				case "->":
					res = this.matchReference( stream, rules, rule );	
					break;	

				case "*":
					res = this.matchArray( stream, rules, rule );	
					break;	
				case "+":
					res = this.matchArray1( stream, rules, rule );	
					break;	
				default:
					throw "Invalid rule type '" + rule.t + "'";
			}
			return res;
		} 
		return this.matchExactString( stream, rules, rule );	
	}

	this.matchAnd = function ( stream, rules, rule ) {
		var result  = [];
		var current = stream.getPos();
		for( var i = 0;i<rule.v.length;++i ) {
			var ruleRes=this.match( stream, rules, rule.v[i] );
			if ( !ruleRes ) {
				stream.setPos( current );
				return null;
			}
			result[ i ] = ruleRes;
		}
		return new PCI.resultArray( result, rule );
	}


	this.matchOneOf = function ( stream, rules, rule ) {
		for( var i = 0;i<rule.v.length;++i ) {
			var ruleRes=this.match( stream, rules, rule.v[i] );
			if ( ruleRes ) {
				return ruleRes;
			}
		}
		return null;
	}

	this.matchArray = function ( stream, rules, rule ) {
		var ruleRes = null; 
		var result = [];
		while( ruleRes = this.match( stream, rules, rule.v ) ) {
			result[result.length] = ruleRes;
		}
		return new PCI.resultArray( result, rule );
	}
	this.matchArray1 = function ( stream, rules, rule ) {
		var ruleRes = null; 
		var result = [];
		while( ruleRes = this.match( stream, rules, rule.v ) ) {
			result[result.length] = ruleRes;
		}
		if ( result.length == 0 ) {
			return null;
		}
		return new PCI.resultArray( result, rule );
	}
	this.matchNot = function ( stream, rules, rule ) {
		var current = stream.getPos();
		var res = this.match( stream, rules, rule.v );
		stream.setPos( current );
		if ( res ) {
			return null
		}
		var ch = stream.get();
		if ( ch ) {
			return new PCI.resultString( ch, rule );
		}
		return null;
	}


	this.matchReference = function ( stream, rules, rule ) {
		var pos = stream.getPos();
		var ruleRes=this.match( stream, rules, rules[ rule.v ] );
		if ( ruleRes ) {
			return new PCI.resultReference( ruleRes, rule.v, pos );
		}		
		return null;
	}

	this.matchExactString = function ( stream, rules, rule ) {
		var matched = "";
		var current = stream.getPos();
		for( var i=0;i<rule.length;++i ) {
			var ch = stream.get();
			if ( !ch || ch != rule.charAt(i) )  {
				stream.setPos( current );
				return null;
			}
			matched+=ch;
		}
		return new PCI.resultString( matched, rule );
	}
};


return {
"resultString" : function ( s ) {
	this.value = s;
	this.getValue = function () {
		return this.value;
	}
	this.__enum = function() {
		return [this.value];
	}
	this.__clone = function( arr ) {
		return new PCI.resultString( arr[0] );
	}

},

"resultReference" : function ( d, name, pos ) {
	this.value = d; 
	this.pos = pos;
	this.getPos = function () {
		return pos;
	}
	this.getValue = function () {
		return this.value.getValue();
	}
	this.__enum = function() {
		return [ this.value ];
	}
	this.__name = name;
	this.__clone = function( arr ) {
		return new PCI.resultReference( arr[0], this.__name );
	}

},
"resultArray" : function ( d ) {
	this.length = d.length;
	for( var i = 0;i<d.length;++i ) {
		this[i]= d[i];		
	}
	this.getValue = function () {
		var v = "";
		for( var i=0;i<this.length;++i ) {
			v+=getValue( this[i]);
		}
		return v;
	}
	this.__enum = function() {
		var arr = [];
		for( var i=0;i<this.length;++i ) {
			arr[i] = this[i];
		}
		return arr;
	}

	this.__clone = function( arr ) {
		return new PCI.resultArray( arr );
	}
},


"checkrules" : function( rules ) {
	for( var rulename in rules ) {
		var rule = rules[rulename];
		if ( !PCI.checkrule( rule, rules ) ) {
			return false;
		}
	}
	return true;
}

, "checkrule" : function( rule, rules ) {
	if ( typeof(rule)!='object' ) {
		return true;
	}
	if ( rule instanceof Array ) {
		for( var i=0;i<rule.length;++i ) {
			if ( !PCI.checkrule( rule[i], rules ) ) {
				return false;
			}
		}		
	} else if ( rule.t == "->" ) {
		if ( rules[rule.v] ) {
			return PCI.checkrule( rule.v, rules );
		} else {
			return false;	
		}
	} 
	return PCI.checkrule( rule.v, rules);
}
, "match" :  function ( stream, rules, rule ) {
	var matcher = new grammarMatcher();
	return matcher.match( stream, rules, rule );
}

, "parserLine" : function( line ) {
	var data = { "buf":line, "idx":0 };
	this.get = function( ) {
		while( data.idx != data.buf.length ) {
			ch = data.buf.charAt( data.idx++ );
			if ( ch == "\t" || ch == "\r" || ch == " ") {
				continue;
			}
			return ch;
		}
		return 0;
	}	
	this.remainder = function() {
		return data.buf.substr( data.idx );
	}
	this.setPos = function( pos ) {
		data.idx=pos;
	}
	this.getPos = function( pos ) {
		return data.idx;
	}
}
, "simpleStream" : function ( line ) {
	var data = { "buf":line, "idx":0 };
	this.get = function( ) {
		while( data.idx != data.buf.length ) {
			ch = data.buf.charAt( data.idx++ );
			return ch;
		}
		return 0;
	}	
	this.remainder = function() {
		return data.buf.substr( data.idx );
	}
	this.setPos = function( pos ) {
		data.idx=pos;
	}
	this.getPos = function( pos ) {
		return data.idx;
	}
}

};
}();