User:Stewbasic/calc.js

var valid_var_name=/^[a-zA-Z][a-zA-Z0-9_]*$/;

var dict={ vars:new Object, subs:new Object, cols:new Object, defvar:0, get_default_var:function{ return "x"+(++this.defvar); },	get:function(t,s){ if(!s.match(valid_var_name)){ warn("Invalid variable name '"+s+"'! Using '"+(s=this.get_default_var)+"' instead."); }		var o=dict[t][s]; if(o) return o;		switch(t){ case 'vars':o=new Variable(s); break; case 'subs':o=new Sub(s); break; case 'cols':o={ls:[]}; }		return (dict[t][s]=o); } };

function Col{ this.ls=[]; this.onclick=null; }

function Variable(s){ this.value=0; this.a=0;	// a variable is like a function with no arguments this.name=s; } Variable.prototype.eval=function{ switch(this.type){ case 'string': case 'select': this.value=this.$ctl.val; break; case 'int': case 'number': var v=this.$ctl.val; v=(this.type=='int')?parseInt(v):parseFloat(v); if(v==NaN) v=0; if(this.range.length>0&&v1&&v>this.range[1]) v=this.range[1]; this.$ctl.val(this.value=v); break; case 'radio': this.value=this.$ctl.filter(':checked').val; break; case 'check': this.value=this.$ctl.is(':checked')?1:0; break; default: }	return this.value; }; Variable.prototype.let=function(v,allowhtml){ this.value=v; switch(this.type){ case 'string': case 'select': case 'int': case 'number': case 'button': this.$ctl.val(v); break; case 'output': if(allowhtml) this.$ctl.html(v); else this.$ctl.text(v); break; case 'radio': this.$ctl.filter('[value='+parseInt(v)+']').attr('checked',true); break; case 'check': this.$ctl.attr('checked',v!=0); break; case 'pane': if(this.cols){ this.value=parseInt(this.value)%this.cols.ls.length; if(isNaN(this.value)) this.value=0; else if(this.value<0) this.value+=this.cols.ls.length; this.$ctl.css('background-color',this.cols.ls[this.value]); }			break; default: } };

function Sub{ this.next=null; this.prev=[this]; } Sub.prototype.run=function{ if(this.next) this.next.run; };

function Expression{ this.ls=[]; this.ops=[];	// used while parsing } Expression.prototype.oplist=[ {s:'+',a:2,eval:function(y,x){return x+y},p:4,la:true}, {s:'-',a:2,eval:function(y,x){return x-y},p:4,la:true}, {s:'*',a:2,eval:function(y,x){return x*y},p:5,la:true}, {s:'/',a:2,eval:function(y,x){return x/y},p:5,la:true}, {s:'^',a:2,eval:function(y,x){return Math.pow(x,y)},p:6,la:false}, {s:'=',a:2,eval:function(y,x){return (x==y)?1:0},p:2,la:false}, {s:'!=',a:2,eval:function(y,x){return (x!=y)?1:0},p:2,la:false}, {s:'<',a:2,eval:function(y,x){return (x',a:2,eval:function(y,x){return (x>y)?1:0},p:2,la:false}, {s:'mod',a:2,eval:function(y,x){return x%y},p:3,la:true}, {s:'!',a:1,eval:function(x){return x?0:1}}, {s:'floor',a:1,eval:Math.floor}, {s:'ceil',a:1,eval:Math.ceil}, {s:'sin',a:1,eval:Math.sin}, {s:'cos',a:1,eval:Math.cos}, {s:'tan',a:1,eval:Math.tan}, {s:'exp',a:1,eval:Math.exp}, {s:'log',a:1,eval:Math.log}, {s:'-',a:1,eval:function(x){return -x;}}, {s:'(',a:1} ]; Expression.prototype.eval=function{	var stack=[];	for(i in this.ls){		var token=this.ls[i];		if(typeof(token)=='number'||typeof(token)=='string') stack.push(token);		else if(typeof(token)=='object'){			if(stack.length<token.a){				warn("Too few arguments to"+token.s);				return 0;			}			switch(token.a){				case 0: stack.push(token.eval); break;				case 1: stack.push(token.eval(stack.pop)); break;				case 2: stack.push(token.eval(stack.pop,stack.pop)); break;			}		}	}	return stack[0]; } // based on // http://runescape.wikia.com/wiki/User:Tyilo/calc.js // and // http://en.wikipedia.org/wiki/Shunting-yard_algorithm // interprets a token in the expression. When done, it returns: //	1 if the next token should be the start of a new expression - ie a number, unary function or ( //	-1 if the next token should be a binary function or ) //	0 if the last token was a mismatched ) - actually we'll use this find the end of the expression Expression.prototype.push=function(t,num){ var m;	if(num==1){	// interpret t as a number or the start of a new expression for(i in this.oplist){ var op=this.oplist[i]; if(op.a==1&&op.s==t){ this.ops.push(op); return 1; }		}		if(m=t.match(/"((?:\\"|[^"])*)"/)){ var s=m[1].replace(/\\\"/g,'"'); this.ls.push(s); return -1; }		var n=parseFloat(t); if(isNaN(n)) this.ls.push(dict.get('vars',t)); else this.ls.push(n); return -1; }else{	// interpret t as a binary operator or )		if(t==')'){ var op; while(op=this.ops.pop) if(op.s=='(') return -1; else this.ls.push(op);			return 0;		}		for(i in this.oplist){			var op=this.oplist[i];			if(op.a==2&&t==op.s){				var op2,minp=op.p-(op.la?1:0);				while(true){					op2=this.ops.pop;					if(op2&&op2.s!='('&&op2.p>minp) this.ls.push(op2); else break; }				if(op2) this.ops.push(op2); this.ops.push(op); return 1; }		}	} }

function If(e){ this.e=e; this.s1=null; this.s2=null; this.prev=[]; this.next=null; } If.prototype.run=function{ if(this.e.eval==0){ if(this.s2) this.s2.run; }else{ if(this.s1) this.s1.run; }	if(this.next) this.next.run; }

function Let(s,e){ this.v=dict.get('vars',s); this.e=e; this.next=null; } Let.prototype.run=function{ this.v.let(this.e.eval,false); if(this.next) this.next.run; }

function Show(s,e){ this.v=dict.get('vars',s); this.e=e; this.next=null; } Show.prototype.run=function{ var show=(this.e.eval!=0); if(this.v.$ctl) this.v.$ctl.toggle(show); if(this.next) this.next.run; }

function Get(t,s){ this.template=t; this.v=dict.get('vars',s); this.params=[]; this.next=null; } Get.prototype.run=function{ var code = ''; alert(code); alert("wgScriptPath="+wgScriptPath+"."); $.ajax({		data: {			action: 'parse',			text: code,			prop: 'text',			title: this.template,			format: 'json'		},		dataType: 'json',		type: 'POST',		url: wgScriptPath + '/api.php',		context: this,		success: function(response){ alert(response.parse.text['*']);			this.v.let(response.parse.text['*'],true);			if(this.next) this.next.run;		},		error: function(jqXHR, textStatus, errorThrown){ alert(textStatus);			warn(textStatus);		},	}); }

function parseSub(sub_name,s){ if(!s) return null; function getToken(re){ s=s.replace(/^\s*/,""); var m;		if(m=s.match(re)){ s=s.replace(re,""); return m;		} return null; }	function parseExpression{ var e=new Expression; var m,num=1; while((m=getToken((num==1)?			/^("(?:\\"|[^"])*"|[0-9.]+(?:e-?[0-9]+)?|[a-zA-Z][a-zA-Z0-9_]*|[!(\-])/:	// looking for a number /^(!=|mod|[)+\-\/*=<>^])/)) // looking for a binary operator			&&(num=e.push(m[1],num))){} return e;	} var var_name_re=/^([a-zA-Z][a-zA-Z0-9_]*)/; var if_stack=[]; var prev=dict.get('subs',sub_name).prev; var m;	while(m=getToken(/^(if|let|get|show|\})/i)){ var o;		if(m[1]=='}'){ var last_if=if_stack.pop; if(last_if==null) break; if(last_if.s1){ last_if.s2=last_if.next; last_if.next=null; prev=prev.concat(last_if.prev); }else{ last_if.s1=last_if.next; last_if.next=null; if(getToken(/^{/)){	// is there an 'else' clause? last_if.prev=prev; prev=[last_if]; if_stack.push(last_if); }else prev.push(last_if); }		}else if(getToken(/^\(/)){			switch(m[1].toLowerCase){				case 'if':					o=new If(parseExpression);					if(getToken(/^{/)) if_stack.push(o); else o=null;					break;				case 'let':					if((m=getToken(var_name_re))&&getToken(/^,/)) o=new Let(m[1],parseExpression); else o=null;					break;				case 'get':					if((m=getToken(/^([a-zA-Z0-9 _]+)/))&&getToken(/^,/)&&(n=getToken(var_name_re))){						o=new Get(m[1],n[1]);						while(getToken(/^,/)&&(m=getToken(var_name_re))) o.params.push(dict.get('vars',m[1]));						getToken(/^\)/); }else o=null; break; case 'show': if((m=getToken(var_name_re))&&getToken(/^,/)) o=new Show(m[1],parseExpression); else o=null; break; case '}': }			while(o2=prev.pop){o2.next=o;} prev=[o]; }else o=null; if(o==null) break; }	dict.get('subs',sub_name).prev=prev; if(s!='') warn("Invalid expression; ignoring '"+s+"'. "); if(if_stack.length) warn("Unterminated if expressions. "); }

function sub_list(s){ if(!s) return null; var ls=s.split(/,/); for(var i in ls) ls[i]=dict.get('subs',ls[i]); return function{for(var i in ls) ls[i].run;}	// this may be dodgy; confused by javascript scoping... } $(document).ready(function{	$('.jcInput').each(function { // split up the parameters var v=new Variable('');	// v will be the variable object associated with this input var options=$(this).text.split('|',7); for(var i in options){ var pair=options[i].split('=',2); if(pair[0].match(/^(name|type|value|range|size|style|sublist)$/)){ v[pair[0]]=$.trim(pair[1]); }else{ warn("Ignoring unknown parameter '"+pair[0]+"'."); }		}		// name parameter if(v.name=='') v.name=dict.get_default_var; if(!v.name.match(valid_var_name)){ warn("Invalid variable name '"+v.name+"'! Using '"+(v.name=dict.get_default_var)+"' instead."); }		if(dict.vars[v.name]){ warn("Variable name '"+v.name+"' is tied to multiple fields! Using '"+(v.name=dict.get_default_var)+"' instead."); }		dict.vars[v.name]=v; // type parameter if(v.type==null) v.type='string'; v.type=v.type.toLowerCase; if(!v.type.match(/^(string|number|int|select|radio|check|button|output)$/)) warn("Invalid input type '"+v.type+"', changing to defult '"+(v.type='string')+"'."); // value paramter if(v.value==null) v.value=(v.type=='string')?"":0; // range parameter if(v.range==null) v.range=v.type.match(/^(select|radio)$/)?'default':'-inf,inf'; v.range=v.range.split(/,/); if(!v.type.match(/^(select|radio)$/)) for(i in v.range) v.range[i]=parseFloat(v.range[i]); // size parameter if(v.size==null) v.size=20; // style parameter if(v.style==null) v.style=''; // sublist parameter v.sublist=sub_list(v.sublist); // create input element switch(v.type){ case 'string': case 'number': case 'int': v.$ctl=$(' ').val(v.value).attr('size',v.size); break; case 'radio': $(this).empty; for(i in v.range){ var $input=$(' ').attr({'type':'radio','name':v.name,'value':i}); if(v.value==i) $input.attr('checked',true); $(this).append($input); $(this).append(v.range[i]+' '); if(v.sublist) $input.click(v.sublist); }				v.$ctl=$('input:radio[name='+v.name+']'); break; case 'select': v.$ctl=$(' '); for(i in v.range){ var $opt=$(' ').val(i).text(v.range[i]); if(i==v.value) $opt.attr('selected','selected'); v.$ctl.append($opt); }				break; case 'check': v.$ctl=$(' ').attr({'type':'checkbox','checked':v.value!=0}); break; case 'button': v.$ctl=$(' ').attr('type','submit').val(v.value); break; case 'output': v.$ctl=$(this).text(v.value); break; default: }		if(!v.type.match(/^(radio|output)$/)){ $(this).empty.append(v.$ctl); v.$ctl.css('cssText',v.style); if(v.sublist) if(v.type.match(/^(check|button|output)$/)) v.$ctl.click(v.sublist); else v.$ctl.change(v.sublist); }	});	$('.jcColours').each(function { var options=$(this).text.split('|',3); var c=dict.get('cols',options[0]); c.ls=c.ls.concat(options[1].match(/#[0-9a-f]{6}/ig)); c.onclick=sub_list(options[2]); });	$('[class*="jcPane"]').each(function { var v=new Variable($(this).attr('id'));	// v will be the variable object associated with this pane v.type='pane'; if(!v.name.match(valid_var_name)){ warn("Invalid variable name '"+v.name+"'! Using '"+(v.name=dict.get_default_var)+"' instead."); }		if(dict.vars[v.name]){ warn("Variable name '"+v.name+"' is tied to multiple fields! Using '"+(v.name=dict.get_default_var)+"' instead."); }		dict.vars[v.name]=v; v.$ctl=$(this); var m;		if(m=$(this).attr('class').match(/jcPane([a-zA-Z][a-zA-Z0-9_]*)/)){ v.cols=dict.get('cols',m[1]); if(v.cols.onclick) $(this).click(function{v.let(v.value+1,false); v.cols.onclick}); else $(this).click(function{v.let(v.value+1,false);}); v.let(0,false); }	});	$('.jcSub').each(function { $(this).hide; var options=$(this).text.split('|',2); parseSub(options[0],options[1]); });	$('.jcJSOnly').each(function {$(this).show;});	$('.jcNoJS').each(function {$(this).hide;});	dict.get('subs','init').run; });

function warn(s){ var err=dict.get('vars',"error"); err.let(""+err.eval+s,false); }