//Version 0.0, April 10, 2006, (c) jason howald (jason.howald@gmail.com)
//License: GNU General Public License (http://www.gnu.org/copyleft/gpl.html)

Math.sec = function(x) {return 1/Math.cos(x);}
Math.csc = function(x) {return 1/Math.sin(x);}
Math.cot = function(x) {return Math.cos(x)/Math.sin(x);}
Math.asec = function(x) {return Math.acos(1/x);}
Math.acsc = function(x) {return Math.asin(1/x);}
Math.acot = function(x) {return Math.atan(1/x);}
Math.sinh = function(x) {return (Math.exp(x)-Math.exp(-x))/2;}
Math.cosh = function(x) {return (Math.exp(x)+Math.exp(-x))/2;}
Math.tanh = function(x) {return Math.sinh(x)/Math.cosh(x);}
Math.sech = function(x) {return 1/Math.cosh(x);}
Math.csch = function(x) {return 1/Math.sinh(x);}
Math.coth = function(x) {return Math.cosh(x)/Math.sinh(x);}
Math.asinh = function(x) {return Math.log(x + Math.sqrt(x^2 + 1));}
Math.acosh = function(x) {return Math.log(x + Math.sqrt(x^2 - 1));}
Math.atanh = function(x) {return 1/2*Math.log((1+x)/(1-x));}
Math.acoth = function(x) {return 1/2*Math.log((1-x)/(1+x));}
Math.acsch = function(x) {return Math.log((1 + Math.sqrt(1 + x^2))/x);}
Math.asech = function(x) {return Math.log((1 + Math.sqrt(1 - x^2))/x);}
//many of the above are from wikipedia.  I hope they're right!


function grammar() {
    this.Unknown = -1;
    this.Nonsense = 0;
    this.Group = 1;//unused
    this.Numb = 2;
    this.Operation = 3;//minus can be unary.  otherwise as expected.
    this.OpenGroup = 4;
    this.CloseGroup = 5;
    this.Funktion = 6; //correct spelling conflicts with build-in JS "function"
    this.Constant = 7;
    this.Variable = 8;
    this.Typology = ["Err","Group","Numb","Operation","OpenGroup","CloseGroup","Funktion","Constant","Variable"];
    this.Funktions = ["ln","log","exp","sqrt",
            "sin","cos","tan","sec","csc","cot",  
            "arcsin","arccos","arctan","arcsec","arccsc","arccot",
            "sinh","cosh","tanh","sech","csch","coth",
            "arcsinh","arccosh","arctanh","arcsech","arccsch","arccoth"];
    this.FnLookup = {ln:Math.log , log:Math.log ,//maple agrees log and ln both refer to natural log.
    	sqrt:Math.sqrt, 
        sin:Math.sin , cos:Math.cos , tan:Math.tan, 
        sec:Math.sec,  csc:Math.csc , cot:Math.cot,
        arcsin:Math.asin, arccos:Math.acos, arctan:Math.atan,
        arcsec:Math.asec, arccsc:Math.acsc, arccot:Math.acot
        };
        
    this.FnDiff = {ln:"1/x",log:"1/x",exp:"exp(x)",sqrt:"(1/2)*x^(-1/2)",
                   sin:"cos(x)",cos:"(-sin(x))",tan:"(sec(x))^2",
                   sec:"sec(x)tan(x)",csc:"-csc(x)cot(x)",cot:"-1-cot(x)^2",
                   arcsin:"(1-x^2)^(-1/2)",arccos:"-(1-x^2)^(-1/2)",arctan:"1/(1+x^2)",
                   arccot:"-1/(1+x^2)",arcsec:"(x^4 - x^2)^(-1/2)",arccsc:"-(x^4 - x^2)^(-1/2)",
                   sinh:"cosh(x)",cosh:"sinh(x)",tanh:"1-tanh(x)^2",
                   sech:"-sech(x)tanh(x)",csch:"-csch(x)coth(x)",coth:"1-coth(x)^2",
                   arcsinh:"(1+x^2)^(-1/2)",arccosh:"(x^2-1)^(-1/2)",arctanh:"1/(1-x^2)",
                   arccoth:"-1/(x^2-1)",arcsech:"-(x^2 - x^4)^(-1/2)",arccsch:"-(x^4 + x^2)^(-1/2)"
                   };
    this.Constants = ["pi","e"];
    this.constantModel = {e:Math.E, pi:Math.PI};
    
    this.epsilon = .00000001;

    var isPlusMinus = new RegExp;
        isPlusMinus.compile(/^(\+|-)$/);
    var isTimesDiv = new RegExp;
        isTimesDiv.compile(/^(\*|\/)$/);
    var isExponent = new RegExp;
        isExponent.compile(/^(\^)$/);

    var isOperation = new RegExp;
        isOperation.compile(/^(\+|-|\*|\/|\^)/);
    var isOpenGroup = new RegExp;
        isOpenGroup.compile(/^(\(|\[|\{)/);
    var isCloseGroup = new RegExp;
        isCloseGroup.compile(/^(\)|\]|\})/);
    var isFunktion = new RegExp;
        isFunktion.compile("^("+this.Funktions.join("\\(|") + "\\()","i");
    var isConstant = new RegExp;
        isConstant.compile("^("+this.Constants.join("|")+")","i");
    var isNumb = new RegExp;
        isNumb.compile(/^(\d*\.\d+|\d+)/i);
    var isVariable = new RegExp;
        isVariable.compile(/^([a-z])/i);
    var isSymbol = [ //order matters, since functions must be sought before indeterminates.
        {type:this.Operation,  test:isOperation}, 
        {type:this.OpenGroup,  test:isOpenGroup},
        {type:this.CloseGroup, test:isCloseGroup},
        {type:this.Funktion,   test:isFunktion},
        {type:this.Constant,   test:isConstant},
        {type:this.Numb,       test:isNumb},
        {type:this.Variable,   test:isVariable}];

    this.getSymbol = function(str) {
        for (var j=0;j<isSymbol.length;j++) {
            if (str.match(isSymbol[j].test)) {
                return {type:isSymbol[j].type, name:RegExp.$1};
            }
        }
        return {type:this.Nonsense, name:str.substr(0,1)};
    }
        
    this.comparePrecedence = function(a,b) {
        //returns true if a<b in tightness, that is, b is strictly tighter than a in precedence.
        //returns true, for example, on "+","*".
        if (a.match(/\^/))    {return 0;} //nothing is tighter than exponent.
        if (b.match(/\^/))    {return 1;} //If a isn't exponent and b is, b wins.
        if (a.match(/\*|\//)) {return 0;} //multiplication division wins for a,
        if (b.match(/\*|\//)) {return 1;} //if not, wins for b.
        if (a.match(/\+|\-/)) {return 0;} //plus and minus still tighter than grouping
        if (b.match(/\+|\-/)) {return 1;} //if not, wins for b. 
        return 0; //By now, they must both be grouping (including functions), so equal. 
    }

    this.closeParen = function(s) {
        //computes the closing paren from an opening paren or function name with (.
        if      (s.match(/\(/)) {return ")";}
        else if (s.match(/\[/)) {return "]";}
        else if (s.match(/\{/)) {return "}";}
        else {return "";}
    }

    this.openParen = function(s) {
        //computes the opening paren from a closing paren
        if      (s.match(/\)/)) {return "(";}
        else if (s.match(/\]/)) {return "[";}
        else if (s.match(/\}/)) {return "{";}
        else {return "";}
    }
    
    this.parenMatch = function(s,t) {
        //alert("paren match test on: " + s+ " and " +t);
        var conjoin = s+t;
        if (conjoin.match(/\(\)|\[\]|\{\}/)) {
        //if ((s.match(isOpenGroup)) && (this.closeParen(s).match(t))) {
            //alert("pass");
            return 1;
        } else {
            //alert("fail");
            return 0;
        }
    }

    this.beginsNoun = function(type) {
        return !!((type == this.Funktion) || 
                (type == this.Constant) || 
                (type == this.Variable) || 
                (type == this.Numb) || 
                (type == this.OpenGroup));
    }
    this.isNoun = function(type) {
        return !!((type == this.Constant) || 
                (type == this.Variable) || 
                (type == this.Numb));
    }
    this.beginsContext = function(type) {
        return !!((type == this.Funktion) || 
                (type == this.OpenGroup));
    }
    this.legalAsUnary = function(name) {
        return name.match(/\+|-/);
    }
    
    this.isPlusOrMinus = function(name) {
        //in case the name is a number, recast as string.
        return (name+"").match(isPlusMinus);
    }
    
    this.precedence = function(name) {
        if      (name.match(isOpenGroup)) {return 1;}
        else if (name.match(isPlusMinus)) {return 2;}
        else if (name.match(isTimesDiv))  {return 3;}
        else if (name.match(isExponent)) {return 4;}
        else {return 0};
        return 0;
    }
    this.sec = function(x) {
        return 1/Math.cos(x);
    }
    this.csc = function(x) {
        return 1/Math.sin(x);
    }
}


