////////////////////////////////////////////////////////////////////////////
//
//                       Copyright (c) 2008
//                       Future Team Aps 
//                       Denmark
//
//                       All Rights Reserved
//
//   This source file is subject to the terms and conditions of the
//   Future Team Software License Agreement which restricts the manner
//   in which it may be used.
//   Mail: hve@hvks.com
//
/////////////////////////////////////////////////////////////////////////////
//
// Module name     :   complexexpression.js
// Module ID Nbr   :   
// Description     :   Javascript complex expression function
// --------------------------------------------------------------------------
// Change Record   :   
//
// Version	Author/Date		Description of changes
// -------  ---------------	----------------------
// 01.01	HVE/2008-Jun-07	Initial release
// 01.02  	HVE/2009-Oct-28 Lower e as exponent symbol was not recognized
// End of Change Record
//
/////////////////////////////////////////////////////////////////////////////


// Depends on the complexbase.js
var variable=new Array;
variable["PI"]=Math.PI;
variable["E"]=Math.E;
variable["LN10"]=Math.LN10;
variable["LN2"]=Math.LN2;
variable["LOG10E"]=Math.LOG10E;
variable["LOG2E"]=Math.LOG2E;
variable["SQRT2"]=Math.SQRT2;
variable["SQRT1_2"]=Math.SQRT1_2;

var e_errors;
var error_msg;

function CE(arg)
	{
	var dbl=0;			// Debug Level
	var depth=0; 		// Recusive dept of functions calls
	var err=0; 			// Number of errors in parsing expression
	var err_msg="";	// Error message
	// Since EI string.split produce non standard result this function simulate correct behavior for all browsers
	function splitexpression(arg)
		{
		var re=/([\+\-\*\/\%\(\)\^,=;\{\}]|return)/g;
		var input=new Array;
		var fi=0;	
		while((r=re.exec(arg))!=null)
			{if(fi<r.index)input[input.length]=arg.slice(fi,r.index);	input[input.length]=r[0]; fi=re.lastIndex;}
		input[input.length]=arg.slice(fi);  // Take last argument if any 
		if(dbl>2) alert("Splitting:"+arg+":"+input.join(":")+"#");
		return input;
		}
	function next(a) // Find next non white space terminal symbol and remove wite spaces
		{while( a.length>0 && (a[0]=a[0].replace(/\s+/g,"")).length==0 ) a.shift(); return a[0];}
	function rpsyntax(ca,t,f)	// Report Syntax error
		{var sp=splitexpression(arg); var ss=sp.slice(0,sp.length-ca.length); if(err==0) err_msg="Syntax. "+t+" Found near:'"+ss.join("")+"|"+ca.join("")+"'"; err++;
		if(dbl>0) alert("["+f+"] Syntax. "+t+" Found near:'"+ss.join("")+"|"+ca.join("")+"'"); }
	function constant(a) 
		{ var n, cflag=false, sign=1; var v=NaN;
//		var fe=/^[iI+-]?(([\d]+([\.][\d]*)?)|([\.][\d]+))([eE][\d]+)?/;
		var fe=/^[iI]?(([\d]+([\.][\d]*)?)|([\.][\d]+))([eE][\d]*)?([iI])?$/; //* to prevent the fe.test to failed. The a missing exponent constant is detected later on
		n=next(a); if(n=="+"||n=="-") { if(n=="-") sign=-1; a.shift(); n=next(a); }	
		//alert("n="+n+" "+fe.test(n));	
		if(fe.test(n))
			{
			if(n.charAt(0).toLowerCase()=="i") { cflag=true; n=n.substr(1); }
			if(!isNaN(parseFloat(n)) && n.charAt(n.length-1).toLowerCase()=="e")
				{// Need to be an optional sign and an integer
				a.shift();
				if(a.length>0 && (a[0]=="+" || a[0]=="-" )){ n+=a[0]; a.shift(); }
				if(a.length>0 && !isNaN(parseInt(a[0])))   { n+=a[0]; }
				else rpsyntax(a,"Missing exponent.","constant");
				}
			v=parseFloat(n);
			var feback=/(([\d]+([\.][\d]*)?)|([\.][\d]+))([eE][\d]+)?[iI]$/
			if(feback.test(n)) cflag=true;
			if(isNaN(v)) rpsyntax(a,"Not a Number. "+n,"constant"); else a.shift();
			v=sign*v;
			if(cflag==true) v=new Complex(0,v);
			}
		else 
			{var idsyn=/^[\w\$][\w\$]*$/;
			if(idsyn.test(n))
				{if(variable[n]!=undefined) v=variable[n]; else rpsyntax(a,"Unknown Variable. '"+n+"'","constant");
				if(v instanceof Array) // Functional id with a parameter list
					{var e_l=new Array;
					var backup=new Array; 
					e_l=ParmList(a);
					if(e_l.length==v.length-1) 
						{var t;
						for(var i=1;i<v.length;i++) {backup[backup.length]=variable[v[i]]; variable[v[i]]=e_l[i-1]; } // Backup old values
						depth++; if(depth<10) t=Statement(splitexpression(v[0])); else rpsyntax(a,"Number of recusive or circular calls exceed the limits.","constant"); depth--;
						for(var i=v.length-1;i>0;i--) {variable[v[i]]=backup[i-1];}// Restore Pushed values
						v=t;  // Set return value of function
						}
					else
						{rpsyntax(a,n+"() Actual Parameter list of "+e_l.length+" Differs from function definition of "+(v.length-1),"constant");v=NaN;}
					}
				else a.shift();
				}
			else
				rpsyntax(a,"Missing Number or Identifier.","constant"); 
			}
		return v;
		}
	function identifier(a) // Probe for next token is an identifer
		{var n;
		var fe=/^[a-zA-Z][\w]*/;
		n=next(a); if(fe.test(n)) return n;
		return undefined;
		}
	function IdParmList(a)
		{var idlist=new Array;
		a.shift(); 
		if(a[0]=="(")
			{for(a.shift();a[0]!=")";a.shift()) { var id=identifier(a); if(id==undefined ) rpsyntax(a,a[0]+" Is not a formal parameter.","IdParmList"); else idlist[idlist.length]=id; a.shift(); 
			if(a[0]!="," && a[0]!=")") rpsyntax(a,"Missing , in parameter list.","IdParmList"); if(a[0]!=",") break; } 
			if(a[0]==")") a.shift(); else rpsyntax(a,"Missing ) in function parameter list.","IdParmList");
			}
		else rpsyntax(a,"Expected (...) Parameter id list.","IdParmList");
		return idlist;	
		}		
	function Statements(a){var v; var t=next(a); if(t!=undefined&&t!=""&&t!=";") v=Statement(a); while(a[0]==";") {a.shift(); t=next(a); if(t!=undefined&&t!=""&&t!=";") v=Statement(a); } if(dbl>1) alert("Statements returns:"+v); return v; }
	function	Statement(a) {var v;	
		if(a[0]=="{")
			{ a.shift(); 
			var t=next(a); if(t!=undefined&&t!=""&&t!=";"&&t!="}") v=Statement(a); while(a[0]==";") {a.shift(); t=next(a); if(t!=undefined&&t!=""&&t!=";"&&t!="}") v=Statement(a); } 
			if(a[0]=="}") a.shift(); else rpsyntax(a,"Missing Closing Bracket }.","Statements"); } else v=CommaExpressions(a); if(dbl>1) alert("Statement returns:"+v); return v; } 
	function CommaExpressions(a){var v=Expression(a); while(a[0]==",") {a.shift(); v=Expression(a); } if(dbl>1) alert("CommaExpression returns:"+v); return v; }
	function Expression(a)
		{var id, v;
		id=identifier(a);
		if(id!=undefined && a[1]!=undefined && a[1]=="=") // Simpel assignment
			{a.shift(); a.shift(); v=Expression(a); variable[id]=v;}
		else
			{var func_def=false;
			if(id!=undefined && a[1]!=undefined && a[1]=="(") // Function definition
				{var pc=0; 
				for(var i=1;i<a.length;i++) if(a[i]=="(") pc++; else if(a[i]==")") {pc--; if(pc==0&&a[i+1]=="=") func_def=true; break;}
				}
			if(func_def)
				{
				idlist=IdParmList(a);
				if(a[0]=="=")
					{var backup=new Array; var ss=new Array;
					a.shift(); ss=a.slice(0);  // Save
					idlist.unshift("");  		// Make room for function body
					variable[id]=idlist;
					// Now check for syntax error in function body 
					//for(var i=1;i<idlist.length;i++) // Backup old values
					//	{backup[backup.length]=variable[idlist[i]]; variable[idlist[i]]=1; }
					for(var i in variable) backup[i]=variable[i];
					for(var i=1;i<idlist.length;i++) variable[idlist[i]]=1; // create parm list variables
					Statement(a); // Check syntax but ignore any outcome
					variable=[]; for(var i in backup) variable[i]=backup[i];
					//for(var i=idlist.length-1;i>0;i--) // Restore Pushed values
						//{variable[idlist[i]]=backup[i-1];}
					// Saved Function definition
					idlist[0]=(ss.slice(0,ss.length-a.length)).join("");
					variable[id]=idlist;
					}
				else
					rpsyntax(a,"Missing = between function header and body.","Expression");
				v=undefined;
				}
			else v=SimpelExpression(a);
			}				
		return v;
		}	
	function SimpelExpression(a) {var v; v=Term(a); v=ExpressionPrime(a,v); return v; }
	function ExpressionPrime(a,v)
		{var t;
		switch(a[0]){
			case "+": a.shift(); t=Term(a); if(t instanceof Complex||v instanceof Complex) v=Complex.add(Complex(v),Complex(t)); else v+=t; v=ExpressionPrime(a,v); break;
			case "-": a.shift(); t=Term(a);if(t instanceof Complex||v instanceof Complex) v=Complex.sub(Complex(v),Complex(t)); else v-=t; v=ExpressionPrime(a,v); break
			}
		return v;
		}
	function Term(a) {var v; v=Factor(a); v=TermPrime(a,v); return v;}
	function TermPrime(a,v)
		{var t; 
		switch(a[0])
			{
			case "*": a.shift(); t=Factor(a);if(t instanceof Complex||v instanceof Complex) v=Complex.mul(Complex(v),Complex(t)); else v*=t; v=TermPrime(a,v); break;
			case "/": a.shift(); t=Factor(a);if(t instanceof Complex||v instanceof Complex) v=Complex.div(Complex(v),Complex(t)); else v/=t; v=TermPrime(a,v); break;
			case "%": a.shift(); t=Factor(a);if(t instanceof Complex||v instanceof Complex) rpsyntax(a,"% operator illegal for complex types.","TermPrime") ;else v%=t; v=TermPrime(a,v); break;
			}
		return v;
		}
	function Factor(a) {var v; v=PrimaryExpression(a); v=FactorPrime(a,v); return v; }
	function FactorPrime(a,v)
		{var t;
		if(a[0]=="^")
			{
			a.shift(); t=constant(a);
			if(!(t instanceof Complex) && Number(t)==Math.round(t)) {if(v instanceof Complex) v=Complex.pow(v,Complex(t)); else v=Math.pow(v,t); } else
			rpsyntax(a,"; "+t+" Power of operator needs to be a integer.","FactorPrime");
			v=FactorPrime(a,v);
			}
		return v;
		}	
	function ParmList(a)
		{var elist=new Array;
		a.shift(); 
		if(a[0]=="(")
			{ 
			for(a.shift();a[0]!=")";a.shift()){ elist[elist.length]=Expression(a); 
			if(a[0]!="," && a[0]!=")") rpsyntax(a,"Missing , in parameter list.","ParmList"); 
			if(a[0]!=",") break; } 
			if(a[0]==")") a.shift(); else rpsyntax(a,"Missing ) in parameter expression.","ParmList");
			}
		else rpsyntax(a,"Expected (...) Parameter list.","ParmList");
		return elist;	
		}			
	function PrimaryExpression(a)
		{var v=NaN; var pl;
		switch(next(a)){
			case "abs":
				el=ParmList(a);if(el.length==1) { if(el[0] instanceof Complex) v=Complex.abs(el[0]); else v=Math.abs(el[0]); }
				else rpsyntax(a,"Invalid number of argument to abs(arg).","PrimaryExpression");
				break;
			case "acos":
				el=ParmList(a);if(el.length==1) v=Math.acos(el[0]); 
				else rpsyntax(a,"Invalid number of argument to acos(arg).","PrimaryExpression");
				break;
			case "asin":
				el=ParmList(a);if(el.length==1) v=Math.asin(el[0]); 
				else rpsyntax(a,"Invalid number of argument to asin(arg)","PrimaryExpression");
				break;
			case "atan":
				el=ParmList(a);if(el.length==1) v=Math.atan(el[0]);
				else rpsyntax(a,"Invalid number of argument to atan(arg).","PrimaryExpression");
				break;
			case "atan2":
				el=ParmList(a);if(el.length==2) v=Math.atan2(el[0],el[1]); 
				else rpsyntax(a,"Invalid number of argument to atan2(y,x).","PrimaryExpression");
				break;
			case "ceil":
				el=ParmList(a);if(el.length==1) v=Math.ceil(el[0]); 
				else rpsyntax(a,"Invalid number of argument to ceil(arg).","PrimaryExpression");
				break;
			case "cos":
				el=ParmList(a);if(el.length==1) { if(el[0] instanceof Complex) v=Complex.cos(el[0]); else v=Math.cos(el[0]); }
				else rpsyntax(a,"Invalid number of argument to cos(arg).","PrimaryExpression");
				break;
			case "exp":
				el=ParmList(a);if(el.length==1) { if(el[0] instanceof Complex) v=Complex.exp(el[0]); else v=Math.exp(el[0]); }
				else rpsyntax(a,"Invalid number of argument to exp(arg).","PrimaryExpression");
				break;
			case "floor":
				el=ParmList(a);if(el.length==1) v=Math.floor(el[0]); 
				else rpsyntax(a,"Invalid number of argument to floor(arg).","PrimaryExpression");
				break;
			case "log":
				el=ParmList(a);if(el.length==1) { if(el[0] instanceof Complex) v=Complex.log(el[0]); else v=Math.log(el[0]); }
				else rpsyntax(a,"Invalid number of argument to log(arg).","PrimaryExpression");
				break;
			case "max":
				el=ParmList(a); if(el.length==0) v=Number.NEGATIVE_INFINITY; else for(var i=1,v=el[0];i<el.length;i++) v=Math.max(v,el[i]);
				break;
			case "min":
				el=ParmList(a); if(el.length==0) v=Number.POSITIVE_INFINITY; else for(var i=1,v=el[0];i<el.length;i++) v=Math.min(v,el[i]);
				break;
			case "pow":
				el=ParmList(a);if(el.length==2) { if(el[0] instanceof Complex || el[1] instanceof Complex) v=Complex.pow(Complex(el[0]),Complex(el[1])); else v=Math.pow(el[0],el[1]); 
}
				else rpsyntax(a,"Invalid number of argument to pow(x,y).","PrimaryExpression");
				break;
			case "random":
				el=ParmList(a);if(el.length==0) v=Math.random(); 
				else rpsyntax(a,"Expecting no argument to random().","PrimaryExpression");
				break;
			case "round":
				el=ParmList(a);if(el.length==1) v=Math.round(el[0]); 
				else rpsyntax(a,"Invalid number of argument to round(arg).","PrimaryExpression");
				break;
			case "sin":
				el=ParmList(a);if(el.length==1) { if(el[0] instanceof Complex) v=Complex.sin(el[0]); else v=Math.sin(el[0]); }
				else rpsyntax(a,"Invalid number of argument to sin(arg).","PrimaryExpression");
				break;
			case "sqrt":
				el=ParmList(a);
				if(el.length==1) { if(el[0] instanceof Complex) v=Complex.sqrt(el[0]); else v=Math.sqrt(el[0]); }
				else rpsyntax(a,"Invalid number of argument to sqrt(arg).","PrimaryExpression");
				break;
			case "tan":
				el=ParmList(a);if(el.length==1) { if(el[0] instanceof Complex) v=Complex.tan(el[0]); else v=Math.tan(el[0]); }
				else rpsyntax(a,"Invalid number of argument to tan(arg).","PrimaryExpression");
				break;
			case "(":
				a.shift(); v=Expression(a);
				if(a[0]==")") a.shift(); else rpsyntax(a,"Missing ) in expression.","PrimaryExpression");
				break;
			case "sinh":
				el=ParmList(a);if(el.length==1) { if(el[0] instanceof Complex) v=Complex.sinh(el[0]); else v=Math.sinh(el[0]); }
				else rpsyntax(a,"Invalid number of argument to sinh(arg).","PrimaryExpression");
				break;
			case "cosh":
				el=ParmList(a);if(el.length==1) { if(el[0] instanceof Complex) v=Complex.cosh(el[0]); else v=Math.cosh(el[0]); }
				else rpsyntax(a,"Invalid number of argument to cosh(arg).","PrimaryExpression");
				break;
			case "tanh":
				el=ParmList(a);if(el.length==1) { if(el[0] instanceof Complex) v=Complex.tanh(el[0]); else v=Math.tanh(el[0]); }
				else rpsyntax(a,"Invalid number of argument to tanh(arg).","PrimaryExpression");
				break;
			case "return": a.shift(); if(a[0]=="(") {a.shift(); v=Expression(a); if(a[0]==")") a.shift(); else rpsyntax(a,"Missing ) in return expression.","PrimaryExpression");}
				else v=Expression(a); 
				break;
			default:
				v=constant(a);
				break;
			}
		return v;	
		}	
	
	var ip=splitexpression(arg);
	var value=Statements(ip);
	if(ip.length>0&&ip[0]!="") rpsyntax(ip,"Missing Operator.","CE");
	e_errors=err;
	error_msg=err_msg;
	return value;
	}
