/**************************************************
calc.js : JS library for http://www.instacalc.com
Copyright 2006 Kalid Azad (http://www.instacalc.com)

***************************************************/

/* Sanitize text coming from user

1) replace predefined variables and functions (all CAPS)
2) remove problematic code, such as "alert("you stink!")" and other functions

 */

/* return true if function is allowed; false otherwise */
function validfn(name){

   if (name == "") { return true; }// just parens

   // in allowed function list
   if ( InstaCalc.allowedfn[name.toUpperCase()] ){ return true;}
	
   return false;
}
 
 
function cleanup(str){
    if (str == null || str == undefined)
	  return str;
    	
	// ignore if contains
	// look for functions: (\w+)\s*[\(] 
	// word characters ... whitespace ... open paren

	// declare all allowed functions here
	
	// matches a function -- text followed by paren
	// user functions

	var ufns = [];
	
	if ( ufns = str.match(/\w+\s*[(][^)]*[)]/g) ){
	  var i;
	  for (i = 0; i < ufns.length; i++){ // extract out the function name
	    tmp = ufns[i].match(/\w+/); // the function name
		
		// ignore line if contains invalid function name
		if ( !validfn(tmp[0]) )
		  return "";
	  }
	}
	
    // remove forbidden characters: anything not A-Z,a-z,0-9,+/*-.
    //str = str.replace(/,/g, ""); // remove commas
    //str = str.replace(/['"]+/g, ""); // remove quotes
    
    // TODO: Clean this up to use a loop. For now, brute force it.
    
	// match reserverd words (Math equations)
    str = str.replace(/RANDOM/g, "Math.random()");
	str = str.replace(/PI/g, "Math.PI");
	str = str.replace(/PHI/g, "InstaCalc.PHI");
	str = str.replace(/MOL/g, "* InstaCalc.MOL"); // multiply by unit
	
	// javascript keywords -- replace with underscore
	/*from http://www.perlscriptsjavascripts.com/tutorials/javascript/keywords.html */

    // ignore words the user is likely to use... otherwise, let eval throw an error	
	// todo: use word boundaries to help filter?
	str = str.replace(/\babstract\b/g, "abstract_");
	str = str.replace(/\bbreak\b/g, "break_");
	str = str.replace(/\bcase\b/g, "case_");
	str = str.replace(/\bcatch\b/g, "catch_");
	str = str.replace(/\bclass\b/g, "class_");
	str = str.replace(/\bconst\b/g, "const_");
	str = str.replace(/\bcontinue\b/g, "continue_");
	str = str.replace(/\bdebugger\b/g, "debugger_");
	str = str.replace(/\bdefault\b/g, "default_");
	str = str.replace(/\bdelete\b/g, "delete_");
	str = str.replace(/\bdo\b/g, "do_");
	str = str.replace(/\benum\b/g, "enum_");
	str = str.replace(/\bexport\b/g, "export_");
	str = str.replace(/\bextends\b/g, "extends_");
	str = str.replace(/\bfinal\b/g, "final_");
	str = str.replace(/\bnew\b/g, "new_");

	str = str.replace(/\bimport\b/g, "import_");
	str = str.replace(/\bint\b/g, "int_");
	str = str.replace(/\bin\b/g, "in_");
	str = str.replace(/\bthis\b/g, "this_");
		
	// peta, tera, giga, mega, kilobyte -- 5MB becomes 5 * 1024 * 1024
	str = str.replace(/PB\b/g, "* 1024 * 1024 * 1024 * 1024 * 1024");
	str = str.replace(/TB\b/g, "* 1024 * 1024 * 1024 * 1024");
	str = str.replace(/GB\b/g, "* 1024 * 1024 * 1024");
	str = str.replace(/MB\b/g, "* 1024 * 1024");
	str = str.replace(/KB\b/g, "* 1024");
	
	// match functions
	// be careful when replacing... need to look for any other character besides a paren
	
	// let's get simpler on our replacement
	// can do Math.xyz or just XYZ (capital)

	//Make sure there are word boundaries on the front; otherwise "BIN" will replace the text in FROMBIN
			
	str = str.replace(/\bABS/g, "Math.abs");
	str = str.replace(/\bACOS/g, "Math.acos($1)");
	str = str.replace(/\bASIN/g, "Math.asin");
	str = str.replace(/\bATAN/g, "Math.atan");
	str = str.replace(/\bATAN2/g, "Math.atan2");
	str = str.replace(/\bCEIL/g, "Math.ceil");
	str = str.replace(/\bCOS/g, "Math.cos");
	str = str.replace(/\bEXP/g, "Math.exp");
	str = str.replace(/\bFLOOR/g, "Math.floor");
	str = str.replace(/\bLOG/g, "Math.log");
	str = str.replace(/\bMAX/g, "Math.max");
	str = str.replace(/\bMIN/g, "Math.min");
	str = str.replace(/\bPOW/g, "Math.pow");
	str = str.replace(/\bROOT/g, "Math.sqrt");
	str = str.replace(/\bROUNDDOWN/g, "Math.floor");
	str = str.replace(/\bROUNDUP/g, "Math.ceil");
	str = str.replace(/\bROUND/g, "Math.round"); // only check after longer ones have been replaced
	str = str.replace(/\bSIN/g, "Math.sin");
	str = str.replace(/\bSQRT/g, "Math.sqrt");
	str = str.replace(/\bTAN/g, "Math.tan");
	
	// Custom functions
	
	// base conversion
	str = str.replace(/\bBIN\s*\(([^)]+)\)/g, "(($1).toString(2))");
	str = str.replace(/\bOCT\s*\(([^)]+)\)/g, "(($1).toString(8))");
	str = str.replace(/\bHEX\s*\(([^)]+)\)/g, "(($1).toString(16))");
    str = str.replace(/\bDEC\s*\(([^)]+)\)/g, "($1)");	
	
	str = str.replace(/\bFROMBIN\b/g, "InstaCalc.frombin");
	
    // character conversions	
	str = str.replace(/\bCHARCODE\s*\(([^)]+)\)/g, "$1.charCodeAt()");
	str = str.replace(/\bFROMCHARCODE\s*\(([^)]+)\)/g, "String.fromCharCode($1)");

	str = str.replace(/\bFIB/g, "InstaCalc.fibonacci");
	str = str.replace(/\bFAC/g, "InstaCalc.factorial");
	str = str.replace(/\bNCHOOSEK/g, "InstaCalc.nchoosek");

	// degree functions
	str = str.replace(/\bRAD/g, "InstaCalc.rad");
	str = str.replace(/\bDEG/g, "InstaCalc.deg");
	
	// exponetiation: A ** B = a ^ b... look for word characters
	// BUG: otherwise, (A**B) becomes Math.pow((A,b)) ... which fails
	str = str.replace(/(\w+)\s*[*][*]\s*(\w+)/g, "Math.pow($1,$2)");
	
	return str;
}

///////////////////////////////////////////////////////////////
/// Load, Save current input in cells
///////////////////////////////////////////////////////////////

// return values in inputObj array as array
function getValueArray(inputObj){    
    if (inputObj == undefined){ return inputObj; }
    
    var i;
    var InputText = new Array(inputObj.length);
    
    for (i = 0; i < inputObj.length; i++){
        if (undefined != inputObj[i]){
            InputText[i] = inputObj[i].value;
        }
    }
	
    return InputText;
}

// set text in outputArray to values in textArray. objArray could be a collection of input fields that need to be set.
function setValueArray(values, objects){
    var i;
    
    for (i = 0; i < values.length; i++){
        if (undefined != values[i] && undefined != objects[i]){
            objects[i].value = values[i];
        }
    }
}

///////////////////////////////////////////////////////////////
/// Add input & output box to div element
///////////////////////////////////////////////////////////////
function addCell(maindiv, i, value){
    if (maindiv != undefined){
	//maindiv.innerHTML += "<table><tr>" + "<td><span class=\"number\">" + (i + 1) + "</span></td>" + "<td><input id=\"input_" + i +"\"/></td><td><span id=\"output_"+ i +"\"></span></td></tr></table>"
	
	//maindiv.innerHTML += "<div class=\"container\">" + "<div class=\"number\">" + (i + 1) + "</div>" + "<div><input id=\"input_" + i +"\" class=\"inputArea\" /></div> <div id=\"output_"+ i +"\" class=\"outputArea\"></div><div class=\"spacer\"></div></div>"
	
	// backup to save
        //maindiv.innerHTML += "<div>" + "<span class=\"number\">" + (i + 1) + "</span>" + " <input id=\"input_" + i +"\" class=\"inputArea\" /><span id=\"output_"+ i +"\" class=\"outputArea\"></span></div>"
        //new Effect.Highlight("input_" + i);
        //new Effect.Grow("input_" + i);
        //new Effect.Appear("input_" + i);
		
	// create table dynamically	
	var tbodyElem = document.getElementById("myTbody");
	var trElem, tdElem;
	// loop through 500-item tableData array
   trElem = tbodyElem.insertRow(tbodyElem.rows.length);
   trElem.className = "tr0";
   
   // first column - number
   tdElem = trElem.insertCell(trElem.cells.length);
   tdElem.className = "col0";
   tdElem.innerHTML = "<span class=\"number\">" + (i + 1) + "</span>"; // start counting at 1
   
   // second column - input area
   tdElem = trElem.insertCell(trElem.cells.length);
   tdElem.className = "col1";
   tdElem.innerHTML = "<div><input id=\"input_" + i +"\" class=\"inputArea\"/></div>";
   
   // third column - output area
   tdElem = trElem.insertCell(trElem.cells.length);
   tdElem.className = "col2";
   tdElem.innerHTML = "<div id=\"output_"+ i +"\" class=\"outputArea\"></div>";
   
   }
}

function addMultipleCells(n){
  
  if (InstaCalc.cells >= InstaCalc.maxCells){
    setStatus("Whoa there, I think you've had enough. I'm cutting you off.");
	return;
  }
  
  saveCells();
  var i;
  for (i = 0; i < n; i++){
    addCell(core, InstaCalc.cells, InstaCalc.cells);
    InstaCalc.cells++;
  }

  setCells();
  keyListener();
  //loadCells();
}

/////////
/// Set all cells to value in saved
////////////
function setCells(values){
	 var i;
	
	if (undefined == values)
	   values = saved;
	
    for (i = 0; i < InstaCalc.cells; i++){
        // input and output cellts
        InstaCalc.inputs[i] = document.getElementById("input_" + i);
        InstaCalc.outputs[i] = document.getElementById("output_" + i);
        
/*		
        if (undefined == InstaCalc.outputs[i] || undefined == InstaCalc.inputs[i] || undefined == saved[i]){
            continue;
        }
*/
/*
		x = getInput(i);
		
		if (values[i] != undefined){
			x.value = values[i];
		}
		else{
			x.value = "";
		}
*/		
	// BUG: saved is only 10 elements, inputs goes up to 15 (let's say).
        if (values[i]!= undefined){
		InstaCalc.inputs[i].value = values[i];
	}else{
		InstaCalc.inputs[i].value = "";
	}

    }
}

function setStatus(str, error){
  if (InstaCalc.status != undefined){
     InstaCalc.status.innerHTML = str;
	 
	 if (error == true){
	   InstaCalc.status.className = "statusError";
	 }
	 else {
		InstaCalc.status.className = "statusOK";
	 }	   
  }
}

/* hrm... is this returning a copy of the object? */
function getInput(i){
	if ( InstaCalc.inputs[i] == undefined )
	    InstaCalc.inputs[i] = document.getElementById("input_" + i);
		
	return InstaCalc.inputs[i];
}

function getOutput(i){
	if ( InstaCalc.outputs[i] == undefined )
	    InstaCalc.outputs[i] = document.getElementById("output_" + i);
	
	return InstaCalc.outputs[i];
}

/* On every key press... */
function keyListener(e){
  createURL(); // create URL first for any bugs
  // in future -- wait 250 ms before recalculating...
  recalculate(); 
}

// check whether string is a comment and should be ignored
function iscomment(str){
 // check if matches  "//"
	if (str.match(/[\/][\/]/)){ 
		return true;
	}
	else {
		return false;
	}
}

// evaluate input and update cell values
function recalculate(){
   // clear status before starting each recalculate
   //setStatus("");
   var tmp = "";
   var status = "";
   var error = false;

   for (i = 0; i < InstaCalc.cells; i++){
        // get input and output cellts
        InstaCalc.inputs[i] = document.getElementById("input_" + i);
        InstaCalc.outputs[i] = document.getElementById("output_" + i);

	// clear everything before evaluation 
        InstaCalc.outputs[i].innerHTML = "";
	// can't clear status on each run; need to show previous message, if any
	tmp = "";

        // check for validity
        if (undefined == InstaCalc.outputs[i] || undefined == InstaCalc.inputs[i]){
            continue;
        }

	// skip eval if input empty
        if (InstaCalc.inputs[i].value == null || InstaCalc.inputs[i].value == undefined || InstaCalc.inputs[i].value == ""){
	   //setStatus("");
	   continue;
        }
        	
        inputstr = InstaCalc.inputs[i].value;	
	
	InstaCalc.outputs[i].className = "outputArea";
	
	// don't evaluate comments
	if ( iscomment(inputstr) ){
		// BUG: escape all HTML from comment
	    InstaCalc.outputs[i].innerHTML = inputstr.escapeHTML();
		InstaCalc.outputs[i].className = "outputComment";
	    continue;
	}

	// need to evaluate input
	inputstr = cleanup(inputstr); // sanitize variables

	try {
	     tmp = eval(inputstr);
	} catch (e) {
	  if (e instanceof ReferenceError){
	  
		if (!error){ // make sure no previous errors
			status = "Waiting to define variable in row " + (i + 1) + "...";
			error = true;
		 }
	  }
	  else {
	     if (!error){
			status = "Waiting on row " + (i + 1) + "...";
			error = true;
		}
	  }

	  tmp = "";
	}
        
        if (tmp == "NaN" || tmp == "undefined"){
            if (!error){
				tmp = "Error -- please fix" // variable not recognized
				status = "Sorry, a variable in row " + (i + 1) + "is not defined.";
				error = true;
			}
        }

        InstaCalc.outputs[i].innerHTML = tmp.toString().escapeHTML();
	//setStatus(" ");
    }
	
	setStatus(status);
	
}

// Set initialization variables
function init(){
    //loadURLParams();

	// need first cell active by default
	
	getInput(0).focus();
}
function isNull(obj){
    if (obj == null || obj == undefined || obj === null || obj == "")
    return true;
    else
    return false;
}

// compression strategy: replace _ and ~ with their charcodes, and use those characters for the more frequent space and equals
function compressURL(str){
    // replace real underscore with %5f;
	str = str.replace(/_/g, '%5F');
	
	// replace real tilde with charcode
	str = str.replace(/~/g, '%7E');
	
	// now that the real ones are gone, convert spaces (%20) and equals (%3d), which are more common
	str = str.replace(/%20/g, '_');
	str = str.replace(/%3D/g, '~');
	
	// replace parens -- for some reason they are URL encoded
	str = str.replace(/%28/g, '(');
	str = str.replace(/%29/g, ')');
	
	return str;
}

function decompressURL(str){
	str = str.replace(/_/g, '%20');
	str = str.replace(/~/g, '%3D');
	
	// underscores and tildes will be decompressed by unescape, but we could decompress here too()
	
	return str;
}

// remove extra spaces from text
// "x = 3" becomes "x=3"
// ignore comments
function shrinkspaces(str){
  if ( iscomment(str) ) return;
  
  // remove spaces around the operators
  str = str.replace(/\s*[=]\s*/g, '=');
  str = str.replace(/\s*[\+]\s*/g, '+');
  str = str.replace(/\s*[\/]\s*/g, '/');
  str = str.replace(/\s*[\-]\s*/g, '-');
  str = str.replace(/\s*[\*]\s*/g, '*');
  
  return str;
}

// add spaces back into string
// x=3 becomes x = 3
function expandspaces(str){

	if ( iscomment(str) ) return;

	str = str.replace(/[=]/g, ' = ');
	str = str.replace(/[\+]/g, ' + ');
	str = str.replace(/[\/]/g, ' / ');
	str = str.replace(/[\-]/g, ' - ');
	str = str.replace(/[\*]/g, ' * ');
  
  return str;
}

// have pretty URLs
// BUGFIX: Was creating URL from saved, but do it from input directly...
function createURL(){
    saveCells();
    var str = "";
    var tmp = "";

    for (i = 0; i < InstaCalc.cells; i++){
	  tmp = getInput(i).value;

	if (i ==0){ // first item has no delimiter
	   str += escape(tmp);
	}
	else { 	// subsequent items need to be separated
	        str += "|" + escape(tmp); // BUG: extra delimiters created extra rows 
	}
    }

    str = compressURL(str);
	
    var url = getURLNoParams(); //base url, includes trailing slash
	var miniurl = url + "mini/";
	var params = "?c=" + str + "&v=" + InstaCalc.version;
	
	urlbox.value = url + params;
	miniurlbox.value = miniurl + params; //add in the object code, etc.
	
    //var items = escape(str);
    //urlbox.value = url + "?c=" + str;
	
    // also add version
    //urlbox.value += "&v=" + InstaCalc.version;
	
    if ( urlbox.value.length > 2000 ){
       urlbox.value = "Wow! That's too big for this version. Try trimming down your formulas...";
    }
		
	InstaCalc.permalink = urlbox.value;
	InstaCalc.minipermalink = "../" + params; // link to main page, not mini
	InstaCalc.minipermalinkdemo = miniurl + params; // demonstration of mini mode for current page
	
	var h = 80 + (24 * InstaCalc.cells); // compute proper height
	if (h > 400 ) h = 400; // max height; begin scrolling
	
	InstaCalc.miniembed = "<iframe src=\"" + InstaCalc.minipermalinkdemo + "\" width=\"400\" height=\"" + h + "\" frameborder=\"0\" scrolling=\"auto\" valign=\"top\"> </iframe>";
    
	
	
	miniurlbox.value = InstaCalc.miniembed;
	//miniurlbox.value = "foo";
}

// write URL out to status area
function createURL_old(){
    saveCells();
    str = ""
    for (i = 0; i < saved.length; i++){
        str += saved[i] + "|";
    }
    var url = getURLNoParams(); //base url
    var items = escape(str);
    urlbox.value = url + "?c=" + items;
}

/* parse URL and load cells

url will be like this: ...?c=1*2|x~3|||||||||&v=1
1) need to decompress (swap ~ with hex encoding of equals sign)
2) unescape (turn %3D back into equal sign)
3) add spaces back in (turn '=' back into ' = ')

 */
function loadURLParams(){

    str = getURLParam("c"); /* &c=123|123|123... */
    str = str.replace(/%7E/g, "~").replace(/%7C/g,"|");

 str = decompressURL(str); //swap in spaces and ~
	
    if (isNull(str)){
        return;
    }
	
    str = unescape(str); /* decode spaces, etc. as needed */
	
	/* todo: replace multiple |||| with a single delimiter */
    
	var cellArray = str.split("|");
	
	// add spaces back into array
	/*
	for (i = 0; i < cellArray.length; i++){
	  cellArray[i] = expandspaces(cellArray[i]);
	}
	*/
    
    /* add cells as needed */
    while(cellArray.length > InstaCalc.cells && InstaCalc.cells < InstaCalc.maxCells){
        addCell(core, InstaCalc.cells, "");
        InstaCalc.cells += 1;
    }
	
	//saved = cellArray;
	setCells(cellArray);
	saveCells();
	
    keyListener();
}

/* get text array with current inputs */
function saveCells(){
    //core = document.getElementById("core")
    saved = getValueArray(InstaCalc.inputs);
}

/* load inputs with previously saved text */
function loadCells(){
	setValueArray(saved, InstaCalc.inputs);
	/*
    for (i = 0; i < InstaCalc.cells; i++){

        InstaCalc.inputs[i].innerHTML = "i set by load";
    }
	*/
}

function hideAll(){
	hideItem("instructions");
    hideItem("overview");
	hideItem("tutorial");
	hideItem("examples");
	hideItem("comments");
	hideItem("reference");
}

// toggle one item and hide the rest
function toggleAndHide(item, divarray){
  for (i = 0; i < divarray.length; i++){
    if (item == divarray[i]){
		toggleItem(divarray[i]);
	}
	else{
		hideItem(divarray[i]);
	}
  }


}

// render the cells and page
function createPage(){
    // write input and output areas
    for (i = 0; i < InstaCalc.cells; i++){
        addCell(core, i, "");
    }
    
    var variablearea=document.getElementById("variablearea")
    var status="stopped"
    var localvars = new Object(); // local variables the user has assigned

    /* // load input values; wait until content has been written
    for (i = 0; i < InstaCalc.cells; i++){
        InstaCalc.inputs[i] = document.getElementById("input_" + i);
        InstaCalc.outputs[i] = document.getElementById("output_" + i);
    }
    */
    document.onkeyup = keyListener;
    
	//hideItem("log")
    hideAll();
    
	loadURLParams();
	keyListener(); // must come after loading the parameters -- it saves over

	setStatus("Ready to go! Just start typing...");
}

function makeWeb20() {
   var i;
   
   // jazz up the inputs
   for (i = 0; i < InstaCalc.inputs.length; i++){
     InstaCalc.inputs[i].className = "input20"; 
	 InstaCalc.outputs[i].className = "output20";
   }
   
   // swap out the image
   document["logo"].src = "(reflect)Online+CalculatrBETA.png"

 }



/*********************************************
Globals, Settings and Functions 
*********************************************/
var InstaCalc = new Object();


InstaCalc.cells = 7;
InstaCalc.maxCells = 30;
InstaCalc.version = 0.5;
InstaCalc.inputs = new Array()
InstaCalc.outputs = new Array()
InstaCalc.saved = new Array();
InstaCalc.permalink = "";

// items to hide
InstaCalc.divs = ["instructions", "overview", "tutorial", "examples", "comments", "reference"];
    
/*********************************************
Math Constants and Functions
*********************************************/

InstaCalc.PHI = 1.6180339887;
InstaCalc.MOL = 6.0221415e23;

/* Binet's formula: see http://en.wikipedia.org/wiki/Fibonacci_number */
InstaCalc.fibonacci = function(n){
		var t = (Math.pow(InstaCalc.PHI,n) - Math.pow(1-InstaCalc.PHI, n))/Math.sqrt(5);
		return Math.round(t);
} 

InstaCalc.factorial = function (n) {
		if (n < 0 ) return "undefined";
		if (n == 0 || n == 1) return 1;
		if (n > 170) return "too large";
				
		var i = 1;
		while (n > 1){
		 i *= n;
		 n--;
		}
        return i;
}

/* binomial function: choose k items from n, order irrelevant */
InstaCalc.nchoosek = function(n,k) {
   return InstaCalc.factorial(n) / ( InstaCalc.factorial(n-k) * InstaCalc.factorial(k) );
}

/* convert sequence of binary digits to decimal */
InstaCalc.convert = function(n, base){
   var i = 0;
   var sum = 0;
   
   while (n > 0){
     var digit = n % 10;
	 sum += digit * Math.pow(base, i);
	 i++;
	 n = Math.floor(n / 10); // shift n over one digit, drop remainder
   }
   return sum;
}

// from binary -- have functions to aid with parsing, which isn't easy with regular expressions
// hex and oct can be written using 0x1234abcdef or 01234567
InstaCalc.frombin = function(n){ return InstaCalc.convert(n, 2); }

// radians to degrees; 2 * PI radians = 360 degrees, so radian = 360 / (2*PI) degrees
InstaCalc.deg = function(n){ return n * 180 / Math.PI; }

// degrees to radians
InstaCalc.rad = function(n) { return n * Math.PI / 180 };


/*********************
Allowed Functions / Keywords - items before parens. f(x,y)
**********************/

InstaCalc.allowedfn = new Object();
	
	// allowed keywords -- "if (x)" looks like a function
	InstaCalc.allowedfn.IF = true;
	
	InstaCalc.allowedfn.ABS = true;
	InstaCalc.allowedfn.ACOS = true;
	InstaCalc.allowedfn.ASIN = true;
	InstaCalc.allowedfn.ATAN = true;
	InstaCalc.allowedfn.ATAN2 = true;
	InstaCalc.allowedfn.CEIL = true;
	InstaCalc.allowedfn.COS = true;
	InstaCalc.allowedfn.EXP = true;
	InstaCalc.allowedfn.FLOOR = true;
	InstaCalc.allowedfn.LOG = true;
	InstaCalc.allowedfn.MAX = true;
	InstaCalc.allowedfn.MIN = true;
	InstaCalc.allowedfn.POW = true;  // disabled because we already have A**B
	InstaCalc.allowedfn.ROOT = true;
	InstaCalc.allowedfn.ROUND = true;
	InstaCalc.allowedfn.ROUNDDOWN = true;
	InstaCalc.allowedfn.ROUNDUP = true;
	InstaCalc.allowedfn.SIN = true;
	InstaCalc.allowedfn.SQRT = true;
	InstaCalc.allowedfn.TAN = true;
	
	// Custom functions
	InstaCalc.allowedfn.CHARCODE = true;
	InstaCalc.allowedfn.FROMCHARCODE = true;
	InstaCalc.allowedfn.FIB = true;
	InstaCalc.allowedfn.FAC = true;
	InstaCalc.allowedfn.NCHOOSEK = true;
	
	// base conversion
	InstaCalc.allowedfn.BIN = true;
	InstaCalc.allowedfn.OCT = true;
	InstaCalc.allowedfn.HEX = true;
	InstaCalc.allowedfn.DEC = true; // nop -- does nothing
	
	InstaCalc.allowedfn.FROMBIN = true;
	
	// degrees and radians
	InstaCalc.allowedfn.DEG = true;
	InstaCalc.allowedfn.RAD = true;
