var ARRSIZE = 100;
var _op     = new Array("^","/","*","+","-");  // operators
var _fNames = new Array(ARRSIZE);  // names of fields
var _fVals  = new Array(ARRSIZE);  // values stored/calculated in fields
var _fields;     // number of fields
var _parseErr;    // error set by parsing expression

function calculate(calcForm) {
   var elem;    // elements in form1
   var fieldC;  // field counter
   var divElem; // div element related to hidden (calc) field
   var result;  // returned value from parser
   // step through fields and store/calculate result
   _fields=0;   // reset - about to (re)build
   elem = calcForm.elements;
   for (fieldC=0;fieldC<elem.length;fieldC++) {
      // if type is text, store value, if hidden, calculate and store
      // check for field limit
      if (_fields==ARRSIZE) {
         alert("Too many fields on page, exceeded "+ARRSIZE);
         break;
      }
      if (elem[fieldC].type=="hidden") {
         // use field value to calculate
         // find associated div and get formula from it
         divElem = document.getElementById(elem[fieldC].name+"div");
         result = runParser(divElem.value);
         // update field value (for form posting and for storage)
         elem[fieldC].value = result;
         // put result in document
         divElem.innerHTML = roundToPadded(2,result);
      }
      // store field value
      _fNames[_fields]=elem[fieldC].name;
      _fVals[_fields] =elem[fieldC].value;
      _fields++;
   }

//runParser("a^(b*(c-(d+5)/(15.2-f)))");
//runParser("a*(b-c)");

}

function runParser(exp) {
   _parseErr = false;
   return parseExp(exp);
}

function parseExp(exp) {
// recursive function to reduce a text-based formula down to a float value

   var inner; // expression between parenths
   var inVal; // exp btwn parenths converted to float
   
   // strip inner expressions out first in order of parentheses
   var parL;  // position of left parenth
   var parR;  // position of right parenth
   var balC;  // counter for left vs. right parenths
   // while parentheses remaining
   while ((parL=exp.indexOf("("))>-1) {
      // find right parenthesis to match - parse until balC is 0
      balC=1; // already found 1 left parenth, so balC set to 1
      parR=parL+1;
      do {
         // find next left and next right. If next left is sooner,
         // increment balC (entered another set of parentheses),
         // otherwise decrement balC. If end of exp reached, error.
         while ((parR<exp.length) && (exp.charAt(parR)!="(") && (exp.charAt(parR)!=")"))
            parR++;
         // if loop broken by parR reaching end, error
         if (parR==exp.length) {
           _parseErr = true;
           break;
         } else {
           // affect balC
           if (exp.charAt(parR)==")")
             balC--;
           else
             balC++;
           parR++; // for next search
         }         
         
      } while (balC>0);      
      if (!_parseErr) {
         // right parenth found - recurse to parse inner exp
         inner = exp.substring(parL+1,parR-1);
         inVal = parseExp(inner);
         exp=exp.substring(0,parL)+inVal+exp.substring(parR,exp.length);
      }
      
   }
   
   // if only one operator (after parenths removed), solve it and return
   // (end of recursion)
   var opPos;    // position of binary op in expression
   var operator; // binary operator used
   var opdL;     // left operand
   var opdR;     // right operand
   var valL;     // left operand value
   var valR;     // right operand value
   if (ops(exp)==1) {
      // extract left and right operand
      opPos=opAt(exp);
      operator=exp.substring(opPos,opPos+1);
      opdL=exp.substring(0,opPos);
      opdR=exp.substring(opPos+1,exp.length);
      // convert opdL and opdR to actual values
      valL=fetchVal(opdL);
      valR=fetchVal(opdR);
      // resolve to a value
      if (operator=="^")
         result=Math.pow(valL,valR);
      else if (operator=="/") {
         // instead of div/0 errors, return 0
         if (valR==0)
            result=0;
         else result=valL/valR;
      }
      else if (operator=="*")
         result=valL*valR;
      else if (operator=="+")
         result=valL+valR;
      else if (operator=="-")
         result=valL-valR;

      return result;
            
   } else {
   
   // otherwise move through exp finding operators and their operands 
   // in order of precedence, and recurse to solve
      var oPos;    // current operator position in expression
      var opC;     // operator counter
      var leftOp;  // left operand
      var rightOp; // right operand
      for (opC=0; opC<_op.length; opC++) {
         // for each operator (in order of precedence), 
         // scour expression and recurse until all gone
         
         while ((oPos=firstOp(exp,_op[opC]))>-1) {
            // scout backwards for start of left operand
            leftOp = oPos-1;
            while (!isOp(exp,leftOp) && (leftOp>-1)) leftOp--;
            leftOp++; // will have moved one char too far
            // scout forwards for end of right operand
            rightOp = oPos+1;
            // if right operand starts with a unary-, move past it
            if (exp.charAt(rightOp)=="-") rightOp++;
            while (!isOp(exp,rightOp) && (rightOp<exp.length)) rightOp++;
            // recurse, passing a single op expression for solution
            inner = exp.substring(leftOp,rightOp);
            inVal = parseExp(inner);
            exp=exp.substring(0,leftOp)+inVal+exp.substring(rightOp,exp.length);
            
         }
      
      }
      return exp;

   }

}

function fetchVal(ident) {
   var fieldC;  // field count
   if (isNaN(ident)) {
      // find name in name array
      fieldC = 0;
      while ((_fNames[fieldC]!=ident) && fieldC<_fields) fieldC++;
      if (fieldC==_fields) {
         alert("Invalid code used in formula: "+ident);
         return 0;
      }
      return parseFloat(_fVals[fieldC]);
   } else
      return parseFloat(ident);
}

function firstOp(exp,oper) {
   // find first occurrence of binary op
   // start search from 1 - an op at the start can only be a unary-
   // by this point, processing is down to minuses (last operator), so
   // returning anything after the first unary is enough.
   return exp.indexOf(oper,1);
}

function isOp(exp,index) {
// returns true is character passed is a binary operator
   var chr;  // char marker
   var opC;  // operator counter
   var opC2; // secondary operator counter
   // return false if passed index is 0 - will be a unary at best
   if (index==0) return false;
   
   chr = exp.charAt(index);
   for (opC=0;opC<_op.length;opC++) {
      if (chr==_op[opC]) {
         // test for unary operator
         prevchar = exp.charAt(index-1); // safe - index of 0 is eliminated
         for (opC2=0;opC2<_op.length;opC2++) {
            if (prevchar==_op[opC2]) return false; // previous char is an op, therefore unary
         }
         // unary test failed - it's a binary op
         return true;
      }   
   }
   // no ops were found
   return false;
}

function ops(exp) {
   // counts operators remaining in exp
   var numOps=0;  // return val - number of ops
   var pos;       // current operator position
   var opC;       // operator counter
   var opC2;      // secondary operator counter
   var unary;     // whether or not operator is unary-
   for (var opC=0;opC<_op.length;opC++) {
      // for each op, count its occurrences
      // if first char is a unary-, move past it
      if ((exp.length>0) && (exp.charAt(0)=="-")) pos++;
      // count binary ops
      pos=exp.indexOf(_op[opC],0);
      while (pos>-1) {
         // check for unary- and skip another char if found
         unary=false;
         if (exp.charAt(pos)=="-") { 
            for (var opC2=0;opC2<_op.length;opC2++) {
               if (exp.charAt(pos-1)==_op[opC2]) {
                  unary=true;
                  break;
               }
            }
         }
         if (!unary) {
            // binary op found, add it to count
            numOps++;
         }   
         pos++; // move pos past found occurrence - unary or binary
         pos=exp.indexOf(_op[opC],pos);
      }
   }
   return numOps;
}

function opAt(exp) {
   // returns pos of op in single-operator exp
   var opC; // operator counter
   var pos; // position of operator
   for (opC=0;opC<_op.length;opC++) {
      if ((pos=exp.indexOf(_op[opC]))>-1)
         // eliminate possibility of this being a unary-
         // - will only happen at start of exp, e.g. -2-6
         // so skip the first one, and get the second - even
         // if -2--6, the second is what we want to return
         if ((_op[opC]=="-") && (pos==0)) {
            // is unary - return next occurence (will be "-" 
            // - all other ops eliminated)
            return exp.indexOf("-",1);
         } else 
            // unary test failed
            return pos;
   }
   
}

function roundTo(digits, exp) {
   exp *= Math.pow(10,digits);
   exp = Math.round(exp);
   exp /= Math.pow(10,digits);
   return exp;
}

function roundToPadded(digits, exp) {
   var dotPos; // pos of decimal point
   var expS;   // exp converted to string
   var zeroC;  // zero counter
   exp=roundTo(digits,exp);
   expS=exp.toString();
   // check that result is padded out - if not, add sufficient zeroes right of decimal
   dotPos=expS.indexOf(".");
   if (dotPos==-1) {
      dotPos=expS.length;
      expS+=".";
   }
   for (var zeroC=expS.length-dotPos-1;zeroC<digits;zeroC++) 
      expS+="0";
   return expS;
}


