For a while now I have wanted to write a dynamic formula engine that could take a math equation sting and resolve it. While researching an unrelated topic I found a bit of code for a "FormulaGenerator" by Tiberiu Ionescu. The code is written for a single thread application, and it works wonderfully in that context. Unfortunately, I need something like it in a web application.
I also want to be able to use variables within the formula for example :( 5 * x )
The Formula Generator is a wonderful starting point to build a custom system.
Let's look at the original code
using System; using System.Collections; using System.Globalization; namespace Parser { /// <summary> /// This class is used to convert a string expression to a numeric one, /// to evaluate this numeric expression and to reconvert the result back /// to string /// </summary> public class FormulaGenerator { #region constructors /// <summary> /// implicit constructor /// </summary> public FormulaGenerator() { }//FormulaGenerator #endregion constructors #region public members public static string EvaluationResult(string astrexpr) { try { if (astrexpr == "") astrexpr = "0"; EvalExpr(astrexpr); return m_strservice; } catch(Exception aobjExc) { throw aobjExc; } }//EvaluationResult #endregion public members #region private members //this alghoritm work for expression with any level of parenthesis private static void EvalExpr(string astrexpr) { if(ExistParenthesis(astrexpr)) { int iposleftparenthesis = PosMaxLevel(astrexpr); string strtemp = astrexpr.Substring(iposleftparenthesis + 1); int iposrightparenthesis = FindFirstChar(strtemp, ')'); string strbetweenparenthesis = strtemp.Substring(0, iposrightparenthesis); EvalAritm(strbetweenparenthesis.Trim()); string strbefore = astrexpr.Substring(0,iposleftparenthesis); string strafter = astrexpr.Substring(iposleftparenthesis + iposrightparenthesis + 2); EvalExpr(strbefore + m_strservice + strafter); } else EvalAritm(astrexpr.Trim()); }//EvalExpr private static bool ExistParenthesis(string astrexpr) { m_iCharContor = 0; int inbleft = 0; NbOfChar(astrexpr,'('); inbleft = m_iCharContor; m_iCharContor = 0; int inbright = 0; NbOfChar(astrexpr, ')'); inbright = m_iCharContor; if(inbleft != inbright) throw new Exception("Sintax Error!"); if (inbleft == 0) return false; return true; }//ExistParenthesis private static int PosMaxLevel(string astrexpr) { int ipos = -1; int ilevel = 0; int maxlevel = LevelOfParenthesis(astrexpr); foreach(char ch in astrexpr.ToCharArray()) { ipos+=1; if(ch == '(') { ilevel += 1; if(ilevel == maxlevel) { return ipos; } } else if(ch == ')') { ilevel -= 1; } } return 0; }//PosMaxLevel private static int LevelOfParenthesis(string astrexpr) { int ilevel = 0; int ipos = -1; int maxlevel = 0; foreach(char ch in astrexpr.ToCharArray()) { ipos += 1; if(ch == '(') { ilevel = ilevel + 1; } else if(ch == ')') { ilevel -= 1; } if(maxlevel < ilevel) { maxlevel = ilevel; } } return maxlevel; }//LevelOfParenthesis private static int FindLastChar(string astrexpr, char acfinder) { return astrexpr.LastIndexOf(acfinder); }//FindLastChar private static int FindFirstChar(string astrexpr, char acfinder) { return astrexpr.IndexOf(acfinder); }//FindFirstChar private static int FindSecondChar(string astrexpr, char acfinder) { int ifirst = astrexpr.IndexOf(acfinder); int isecond = astrexpr.Substring(ifirst + 1).IndexOf(acfinder); return ifirst + isecond + 1; }//FindSecondChar private static void NbOfChar(string astrexpr, char acfinder) { int i = astrexpr.IndexOf(acfinder); if(i >= 0) { m_iCharContor+=1; NbOfChar(astrexpr.Substring(i+1),acfinder); } }//NbOfChar private static void EvalAritm(string astrexpr) { ProcessPercent(astrexpr); string strwithoutpercent = m_strservice; ProcessDivision(strwithoutpercent); string strwithoutdivision = m_strservice; ProcessMultiply(strwithoutdivision); string strwithoutmultiply = m_strservice; ProcessMinus(strwithoutmultiply); string strwithoutminus = m_strservice; ProcessPlus(strwithoutminus); string strwithoutplus = m_strservice; }//EvalAritm private static void ProcessPercent(string astrexpr) { m_iCharContor = 0; NbOfChar(astrexpr,'%'); int inbpercent = m_iCharContor; string strnewastrexpr = astrexpr.Trim(); m_iCharContor = 0; NbOfChar(strnewastrexpr, '%'); if(m_iCharContor == 0) { m_strservice = strnewastrexpr; } if(inbpercent > 0) { string astrexprwithoutbreak = astrexpr.Trim(); int ipospercent = FindFirstChar(astrexprwithoutbreak,'%'); string astrwithoutpercent; if (ipospercent >= 1) { astrwithoutpercent = astrexprwithoutbreak.Substring(0,ipospercent); } else { throw new Exception("Sintax error!"); } int ilastdivision = FindLastChar(astrwithoutpercent,'/'); int ilastmultiply = FindLastChar(astrwithoutpercent,'*'); int ilastminus = FindLastChar(astrwithoutpercent,'-'); int ilastplus = FindLastChar(astrwithoutpercent,'+'); ArrayList arlsigns = new ArrayList(); arlsigns.Add(ilastdivision); arlsigns.Add(ilastmultiply); arlsigns.Add(ilastminus); arlsigns.Add(ilastplus); int imaxsignpos = NbMax(arlsigns); if(imaxsignpos < 0) imaxsignpos = 0; string strpercent; string strbeforepercent; if(imaxsignpos >= 1) { strpercent = astrwithoutpercent.Substring(imaxsignpos + 1).Trim(); strbeforepercent = astrwithoutpercent.Substring(0,imaxsignpos+1).Trim(); } else { strpercent = astrwithoutpercent.Trim(); strbeforepercent = ""; } string strafterpercent=""; if(ipospercent < astrexprwithoutbreak.Length - 1) { strafterpercent = astrexprwithoutbreak.Substring(ipospercent+1).Trim(); } decimal dpercentvalue = ConvertToValue(strpercent); strnewastrexpr = strbeforepercent + Convert.ToString(dpercentvalue) + strafterpercent; ProcessPercent(strnewastrexpr); } }//ProcessPercent private static void ProcessDivision(string astrexpr) { m_iCharContor = 0; NbOfChar(astrexpr,'/'); int inbdivision = m_iCharContor; string strnewastrexpr = astrexpr.Trim(); m_iCharContor = 0; NbOfChar(strnewastrexpr, '/'); if(m_iCharContor == 0) { m_strservice = strnewastrexpr; } if(inbdivision > 0) { string astrexprwithoutbreak = astrexpr.Trim(); int iposdivision = FindFirstChar(astrexprwithoutbreak,'/'); string astrbeforedivision; if (iposdivision >= 1) { astrbeforedivision = astrexprwithoutbreak.Substring(0,iposdivision).Trim(); } else { throw new Exception("Division error!"); } string astrafterdivision; if(iposdivision < astrexprwithoutbreak.Length - 1) { astrafterdivision = astrexprwithoutbreak.Substring(iposdivision + 1).Trim(); } else { throw new Exception("Division error!"); } int ilastdivisionbefore = FindLastChar(astrbeforedivision,'/'); int ilastmultiplybefore = FindLastChar(astrbeforedivision,'*'); int ilastminusbefore = FindLastChar(astrbeforedivision,'-'); int ilastplusbefore = FindLastChar(astrbeforedivision,'+'); int ilastdivisionafter = FindFirstChar(astrafterdivision,'/'); int ilastmultiplyafter = FindFirstChar(astrafterdivision,'*'); int ilastminusafter = FindFirstChar(astrafterdivision,'-'); int ilastplusafter = FindFirstChar(astrafterdivision,'+'); ArrayList arlsignsbefore = new ArrayList(); arlsignsbefore.Add(ilastdivisionbefore); arlsignsbefore.Add(ilastmultiplybefore); arlsignsbefore.Add(ilastminusbefore); arlsignsbefore.Add(ilastplusbefore); int imaxsignpos = NbMax(arlsignsbefore); if (imaxsignpos <= 0) imaxsignpos = -1; ArrayList arlsignsafter = new ArrayList(); arlsignsafter.Add(ilastdivisionafter); arlsignsafter.Add(ilastmultiplyafter); arlsignsafter.Add(ilastminusafter); arlsignsafter.Add(ilastplusafter); int iminsignpos = NbMin(arlsignsafter); if (iminsignpos <= 0) iminsignpos = 0; string strbeforedivisionafter = astrbeforedivision.Substring(imaxsignpos + 1).Trim(); string strbeforedivisionbefore = astrbeforedivision.Substring(0,imaxsignpos + 1).Trim(); string strafterdivisionbefore = astrafterdivision.Substring(0,iminsignpos).Trim(); string strafterdivisionafter = astrafterdivision.Substring(iminsignpos).Trim(); bool issignbefore = false; if(strbeforedivisionbefore.Length > 0) { if(strbeforedivisionbefore.Substring(0,1).Trim() == "-") { issignbefore = true; } } bool issignafter = false; if(strafterdivisionbefore.Length > 0) { if(strafterdivisionbefore.Substring(0,1).Trim() == "-") { issignafter = true; } } decimal dbeforedivision; if(strbeforedivisionafter.Length > 0) { dbeforedivision = Convert.ToDecimal(SetDecimalSeparator(strbeforedivisionafter)); } else if(strbeforedivisionbefore.Length > 0) { dbeforedivision = Convert.ToDecimal(SetDecimalSeparator(strbeforedivisionbefore)); } else dbeforedivision = 0; decimal dafterdivision; if(strafterdivisionbefore.Length > 0) { dafterdivision = Convert.ToDecimal(SetDecimalSeparator(strafterdivisionbefore)); } else if(strafterdivisionafter.Length > 0) { dafterdivision = Convert.ToDecimal(SetDecimalSeparator(strafterdivisionafter)); } else dafterdivision = 1; decimal ddivision=0; if(dafterdivision != 0) { if(issignbefore || issignafter) { ddivision = (-1) * dbeforedivision / dafterdivision; } else ddivision = dbeforedivision / dafterdivision; } strnewastrexpr = strbeforedivisionbefore + Convert.ToString(ddivision) + strafterdivisionafter; if(strafterdivisionbefore == "") strnewastrexpr = strbeforedivisionbefore + Convert.ToString(ddivision); ProcessDivision(strnewastrexpr); } }//ProcessDivision private static void ProcessMultiply(string astrexpr) { m_iCharContor = 0; NbOfChar(astrexpr,'*'); int inbmultiply = m_iCharContor; string strnewastrexpr = astrexpr.Trim(); m_iCharContor = 0; NbOfChar(strnewastrexpr, '*'); if(m_iCharContor == 0) { m_strservice = strnewastrexpr; } if(inbmultiply > 0) { string astrexprwithoutbreak = astrexpr.Trim(); int iposmultiply = FindFirstChar(astrexprwithoutbreak,'*'); string astrbeforemultiply; if (iposmultiply >= 1) { astrbeforemultiply = astrexprwithoutbreak.Substring(0,iposmultiply).Trim(); } else { throw new Exception("Error!"); } string astraftermultiply; if(iposmultiply < astrexprwithoutbreak.Length - 1) { astraftermultiply = astrexprwithoutbreak.Substring(iposmultiply + 1).Trim(); } else { throw new Exception("Error!"); } int ilastdivisionbefore = FindLastChar(astrbeforemultiply,'/'); int ilastmultiplybefore = FindLastChar(astrbeforemultiply,'*'); int ilastminusbefore = FindLastChar(astrbeforemultiply,'-'); int ilastplusbefore = FindLastChar(astrbeforemultiply,'+'); int ilastdivisionafter = FindFirstChar(astraftermultiply,'/'); int ilastmultiplyafter = FindFirstChar(astraftermultiply,'*'); int ilastminusafter = FindFirstChar(astraftermultiply,'-'); int ilastplusafter = FindFirstChar(astraftermultiply,'+'); ArrayList arlsignsbefore = new ArrayList(); arlsignsbefore.Add(ilastdivisionbefore); arlsignsbefore.Add(ilastmultiplybefore); arlsignsbefore.Add(ilastminusbefore); arlsignsbefore.Add(ilastplusbefore); int imaxsignpos = NbMax(arlsignsbefore); if (imaxsignpos <= 0) imaxsignpos = -1; ArrayList arlsignsafter = new ArrayList(); arlsignsafter.Add(ilastdivisionafter); arlsignsafter.Add(ilastmultiplyafter); arlsignsafter.Add(ilastminusafter); arlsignsafter.Add(ilastplusafter); int iminsignpos = NbMin(arlsignsafter); if (iminsignpos <= 0) iminsignpos = 0; string strbeforemultiplyafter = astrbeforemultiply.Substring(imaxsignpos + 1).Trim(); string strbeforemultiplybefore = astrbeforemultiply.Substring(0,imaxsignpos + 1).Trim(); string straftermultiplybefore = astraftermultiply.Substring(0,iminsignpos).Trim(); string straftermultiplyafter = astraftermultiply.Substring(iminsignpos).Trim(); bool issignbefore = false; if(strbeforemultiplybefore.Length > 0) { if(strbeforemultiplybefore.Substring(0,1).Trim() == "-") { //issignbefore = true; } } bool issignafter = false; if(straftermultiplybefore.Length > 0) { if(straftermultiplybefore.Substring(0,1).Trim() == "-") { issignafter = true; } } decimal dbeforemultiply; if(strbeforemultiplyafter.Length > 0) { if(IsDigit(strbeforemultiplyafter) == true) { dbeforemultiply = Convert.ToDecimal(SetDecimalSeparator(strbeforemultiplyafter)); } else { dbeforemultiply = ConvertToValue(strbeforemultiplyafter); } } else if(strbeforemultiplybefore.Length > 0) { if(IsDigit(strbeforemultiplybefore) == true) { dbeforemultiply = Convert.ToDecimal(SetDecimalSeparator(strbeforemultiplybefore)); } else { dbeforemultiply = ConvertToValue(strbeforemultiplybefore); } } else dbeforemultiply = 0; decimal daftermultiply; if(straftermultiplybefore.Length > 0) { if(IsDigit(straftermultiplybefore) == true) { daftermultiply = Convert.ToDecimal(SetDecimalSeparator(straftermultiplybefore)); } else { daftermultiply = ConvertToValue(straftermultiplybefore); } } else if(straftermultiplyafter.Length > 0) { if(IsDigit(straftermultiplyafter) == true) { daftermultiply = Convert.ToDecimal(SetDecimalSeparator(straftermultiplyafter)); } else { daftermultiply = ConvertToValue(straftermultiplyafter); } } else daftermultiply = 0; decimal dmultiply=0; if(daftermultiply != 0) { if(issignbefore || issignafter) { dmultiply = (-1) * dbeforemultiply * daftermultiply; } else dmultiply = dbeforemultiply * daftermultiply; } strnewastrexpr = strbeforemultiplybefore + Convert.ToString(dmultiply) + straftermultiplyafter; if(straftermultiplybefore == "") strnewastrexpr = strbeforemultiplybefore + Convert.ToString(dmultiply); ProcessMultiply(strnewastrexpr); } }//ProcessMultiply private static void ProcessMinus(string astrexpr) { m_iCharContor = 0; NbOfChar(astrexpr,'-'); int iposminustest = FindFirstChar(astrexpr.Trim(),'-'); //if(iposminustest == 0) iposminustest = this.FindSecondChar(astrexpr.Trim(),'-'); string strnewastrexpr = astrexpr.Trim(); m_iCharContor = 0; NbOfChar(strnewastrexpr, '-'); if(iposminustest == 0)m_iCharContor -= 1; int inbminus = m_iCharContor; if(m_iCharContor == 0) { m_strservice = strnewastrexpr; } if(inbminus > 0) { string astrexprwithoutbreak = astrexpr.Trim(); //if(iposminustest == 0) astrexprwithoutbreak = astrexprwithoutbreak.Substring(1); int iposminus = FindFirstChar(astrexprwithoutbreak,'-'); if (iposminus == 0)iposminus = FindSecondChar(astrexprwithoutbreak,'-'); string astrbeforeminus; if (iposminus >= 1) { astrbeforeminus = astrexprwithoutbreak.Substring(0,iposminus).Trim(); } else { astrbeforeminus = astrexprwithoutbreak.Substring(0,iposminus).Trim(); } string astrafterminus; if(iposminus < astrexprwithoutbreak.Length - 1) { astrafterminus = astrexprwithoutbreak.Substring(iposminus + 1).Trim(); //if(iposminustest == 0)astrafterminus = astrafterminus.Substring(1).Trim(); } else { throw new Exception("Error!"); } int ilastdivisionbefore = FindLastChar(astrbeforeminus,'/'); int ilastmultiplybefore = FindLastChar(astrbeforeminus,'*'); int ilastminusbefore = FindLastChar(astrbeforeminus,'-'); int ilastplusbefore = FindLastChar(astrbeforeminus,'+'); int ilastdivisionafter = FindFirstChar(astrafterminus,'/'); int ilastmultiplyafter = FindFirstChar(astrafterminus,'*'); int ilastminusafter = FindFirstChar(astrafterminus,'-'); int ilastplusafter = FindFirstChar(astrafterminus,'+'); ArrayList arlsignsbefore = new ArrayList(); arlsignsbefore.Add(ilastdivisionbefore); arlsignsbefore.Add(ilastmultiplybefore); arlsignsbefore.Add(ilastminusbefore); arlsignsbefore.Add(ilastplusbefore); int imaxsignpos = NbMax(arlsignsbefore); if (imaxsignpos <= 0) imaxsignpos = -1; ArrayList arlsignsafter = new ArrayList(); arlsignsafter.Add(ilastdivisionafter); arlsignsafter.Add(ilastmultiplyafter); arlsignsafter.Add(ilastminusafter); arlsignsafter.Add(ilastplusafter); int iminsignpos = NbMin(arlsignsafter); if (iminsignpos <= 0) iminsignpos = 0; string strbeforeminusafter = astrbeforeminus.Substring(imaxsignpos + 1).Trim(); string strbeforeminusbefore = astrbeforeminus.Substring(0,imaxsignpos + 1).Trim(); string strafterminusbefore = astrafterminus.Substring(0,iminsignpos).Trim(); string strafterminusafter = astrafterminus.Substring(iminsignpos).Trim(); decimal dbeforeminus = 0; if(strbeforeminusafter.Length > 0) { if(IsDigit(strbeforeminusafter) == true) { dbeforeminus = Convert.ToDecimal(SetDecimalSeparator(strbeforeminusafter)); } else { dbeforeminus = ConvertToValue(strbeforeminusafter); } } else if(strbeforeminusbefore.Length > 0) { if(IsDigit(strbeforeminusbefore) == true) { dbeforeminus = Convert.ToDecimal(SetDecimalSeparator(strbeforeminusbefore)); } else { dbeforeminus = ConvertToValue(strbeforeminusbefore); } } decimal dafterminus = 0; if(strafterminusbefore.Length > 0) { if(IsDigit(strafterminusbefore)) { dafterminus = Convert.ToDecimal(SetDecimalSeparator(strafterminusbefore)); } else { dafterminus = ConvertToValue(strafterminusbefore); } } else if(strafterminusafter.Length > 0) { if(IsDigit(strafterminusafter)) { dafterminus = Convert.ToDecimal(SetDecimalSeparator(strafterminusafter)); } else { dafterminus = ConvertToValue(strafterminusafter); } } decimal dminus=0; dminus = dbeforeminus - dafterminus; strnewastrexpr = strbeforeminusbefore + Convert.ToString(dminus) + strafterminusafter; if(strafterminusbefore == "") strnewastrexpr = strbeforeminusbefore + Convert.ToString(dminus); ProcessMinus(strnewastrexpr); } }//ProcessMinus private static void ProcessPlus(string astrexpr) { m_iCharContor = 0; NbOfChar(astrexpr,'+'); int inbplus = m_iCharContor; string strnewastrexpr = astrexpr.Trim(); m_iCharContor = 0; NbOfChar(strnewastrexpr, '+'); if(m_iCharContor == 0) { m_strservice = strnewastrexpr; } if(inbplus > 0) { string astrexprwithoutbreak = astrexpr.Trim(); int iposplus = FindFirstChar(astrexprwithoutbreak,'+'); string astrbeforeplus; if (iposplus >= 1) { astrbeforeplus = astrexprwithoutbreak.Substring(0,iposplus).Trim(); } else { throw new Exception("Error!"); } string astrafterplus; if(iposplus < astrexprwithoutbreak.Length - 1) { astrafterplus = astrexprwithoutbreak.Substring(iposplus + 1).Trim(); } else { throw new Exception("Error!"); } int ilastdivisionbefore = FindLastChar(astrbeforeplus,'/'); int ilastmultiplybefore = FindLastChar(astrbeforeplus,'*'); int ilastminusbefore = FindLastChar(astrbeforeplus,'-'); int ilastplusbefore = FindLastChar(astrbeforeplus,'+'); int ilastdivisionafter = FindFirstChar(astrafterplus,'/'); int ilastmultiplyafter = FindFirstChar(astrafterplus,'*'); int ilastminusafter = FindFirstChar(astrafterplus,'-'); int ilastplusafter = FindFirstChar(astrafterplus,'+'); ArrayList arlsignsbefore = new ArrayList(); arlsignsbefore.Add(ilastdivisionbefore); arlsignsbefore.Add(ilastmultiplybefore); arlsignsbefore.Add(ilastminusbefore); arlsignsbefore.Add(ilastplusbefore); int imaxsignpos = NbMax(arlsignsbefore); if (imaxsignpos <= 0) imaxsignpos = -1; ArrayList arlsignsafter = new ArrayList(); arlsignsafter.Add(ilastdivisionafter); arlsignsafter.Add(ilastmultiplyafter); arlsignsafter.Add(ilastminusafter); arlsignsafter.Add(ilastplusafter); int iminsignpos = NbMin(arlsignsafter); if (iminsignpos <= 0) iminsignpos = 0; string strbeforeplusafter = astrbeforeplus.Substring(imaxsignpos + 1).Trim(); string strbeforeplusbefore = astrbeforeplus.Substring(0,imaxsignpos + 1).Trim(); string strafterplusbefore = astrafterplus.Substring(0,iminsignpos).Trim(); string strafterplusafter = astrafterplus.Substring(iminsignpos).Trim(); decimal dbeforeplus = 0; if(strbeforeplusafter.Length > 0) { if(IsDigit(strbeforeplusafter) == true) { dbeforeplus = Convert.ToDecimal(SetDecimalSeparator(strbeforeplusafter)); } else { dbeforeplus = ConvertToValue(strbeforeplusafter); } } else if(strbeforeplusbefore.Length > 0) { if(IsDigit(strbeforeplusbefore) == true) { dbeforeplus = Convert.ToDecimal(SetDecimalSeparator(strbeforeplusbefore)); } else { dbeforeplus = ConvertToValue(strbeforeplusbefore); } } decimal dafterplus = 0; if(strafterplusbefore.Length > 0) { if(IsDigit(strafterplusbefore) == true) { dafterplus = Convert.ToDecimal(SetDecimalSeparator(strafterplusbefore)); } else { dafterplus = ConvertToValue(strafterplusbefore); } } else if(strafterplusafter.Length > 0) { if(IsDigit(strafterplusafter) == true) { dafterplus = Convert.ToDecimal(SetDecimalSeparator(strafterplusafter)); } else { dafterplus = ConvertToValue(strafterplusafter); } } decimal dplus=0; dplus = dbeforeplus + dafterplus; strnewastrexpr = strbeforeplusbefore + Convert.ToString(dplus) + strafterplusafter; if(strafterplusbefore == "") strnewastrexpr = strbeforeplusbefore + Convert.ToString(dplus); ProcessPlus(strnewastrexpr); } else { if(IsDigit(astrexpr) == false) { ConvertToValue(astrexpr); } } }//ProcessPlus private static decimal ConvertToValue(string astrexpr) { bool bmarker = true; decimal dexpr = 0; for(int i=0;i <= astrexpr.Length - 1;i++) { string strlocal = astrexpr.Substring(i,1); if ((i != 0) || (strlocal != "-")) { char[] ch = strlocal.ToCharArray(); if((Char.IsDigit(ch[0]) == false) && (strlocal != ".") && (strlocal != ",") /*&& (strlocal != " ")*/) { bmarker = false; } } } if(bmarker == true) { dexpr = Convert.ToDecimal(astrexpr)/100; } else { throw new Exception("Not numeric value!"); } return dexpr; }//ConvertToValue private static int NbMax(ArrayList aobjarl) { int max = 0; foreach(object obj in aobjarl) { if((int)obj != -1) { if(max <= (int)obj) max = (int)obj; } } return max; }//NbMax private static int NbMin(ArrayList aobjarl) { int min = NbMax(aobjarl); foreach(object obj in aobjarl) { if((int)obj != -1) { if(min >= (int)obj) min = (int)obj; } } return min; }//NbMin private static bool IsDigit(string astrexpr) { foreach(char ch in astrexpr.ToCharArray()) { if((ch != '-')&&(ch != ' ')&&(ch != '.')&&(ch != ',')) { if (Char.IsDigit(ch) == false) { return false; } } } return true; }//IsDigit private static string SetDecimalSeparator(string astrexpr) { string strseparator = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator; int ipoint = astrexpr.IndexOf('.'); int icomma = astrexpr.IndexOf(','); string newastrexpr; if(ipoint >= 0) { newastrexpr = astrexpr.Replace(".",strseparator); } else if(icomma >= 0) { newastrexpr = astrexpr.Replace(",",strseparator); } else { newastrexpr = astrexpr; } return newastrexpr; }//SetDecimalSeparator private static int m_iCharContor = 0; private static string m_strservice = ""; //private static double m_iNbOfMinus = 0; //I commented out this line to stop some warning about not in use, this is the only change to the file //private static string m_strExpression; #endregion private members } }
The first thing I did is setup a small console app to quickly run multi threaded tests in. Using this app, you can quickly see the issue.. And lets look at the code from the console application. Be warned it is not pretty, but I wanted to move onto the fun parts...(who does not?)
Program.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using Parser; using ForumlaCalculator.TestWrappeers; using Garoutte.FormulaEngine; namespace ForumlaCalculator { public class Program { static void Main(string[] args) { Tester t = menu(); if (t != null) t.Run(); } static Tester menu() { Console.WriteLine("Select a wrapper:"); Console.WriteLine("\t0.\t Quit"); Console.WriteLine("\t1.\t Original"); Console.WriteLine("\t2.\t Alpha1- make thread safe"); Console.WriteLine("\t3.\t Alpha2- Add variable support"); Console.WriteLine("\t4.\t Alpha3- Convert to RegEx and proofs"); Console.WriteLine("\t5.\t Beta1- features are all in place, rewrite the math functions"); Tester result = null; switch (Console.ReadLine()) { case "5": result = new FrameWithX<Beta1>(); break; case "4": result = new FrameWithX<Alpha3>(); break; case "3": result = new FrameWithX<Alpha2>(); break; case "2": result = new Frame<Alpha1>(); break; case "1": result = new Tester(); break; case "0": result= null; break; default: Console.WriteLine("Invalid selection. Try again"); result= menu(); break; } return result; } } }
ThreadId.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ForumlaCalculator { public class ThreadId { public Tester program { get; set; } public int Id {get;set;} public string Expected { get; set; } } }
Tester.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using Parser; namespace ForumlaCalculator { public class Tester { public List<string> formulas = new List<string>(); public const int loop = 20; List<Thread> threads = new List<Thread>(); public void AddUserOutput(string message, params object[] args) { Console.WriteLine(message, args); } public Tester() { formulas.Add("100* 50%"); formulas.Add("((1+5)*5)/2");//15 formulas.Add("((((6/2)+2)*4)-1)+(2*1)");//21 formulas.Add("1");//1 formulas.Add("((2+6)*5)/2");//20 } public string GetResult(int formulaIndex) { return GetResult(formulas[formulaIndex]); } public string GetResult(string formula) { string result = string.Empty; try { result = DoGetResult(formula); } catch (Exception e) { result = e.Message; } return result; } public virtual string DoGetResult(string formula) { return FormulaGenerator.EvaluationResult(formula); } public void Run() { threads = new List<Thread>(); List<string> expected = new List<string>(); for (int i = 0; i < formulas.Count; i++) { string result = GetResult(formulas[i]); expected.Add(result); AddUserOutput("Test {0} {1}={2}", i, formulas[i], result); threads.Add(new Thread(new ParameterizedThreadStart(ProcessStart))); } AddUserOutput("Press any to go on"); Console.ReadKey(); int x = 0; foreach (Thread item in threads) { item.IsBackground = true; item.Start(new ThreadId() { program = this, Id = x, Expected = expected[x] }); x++; } bool running = true; while (running) { running = false; foreach (Thread item in threads) { switch (item.ThreadState) { case ThreadState.Running: running = true; break; case ThreadState.AbortRequested: case ThreadState.Aborted: case ThreadState.Background: case ThreadState.StopRequested: case ThreadState.Stopped: case ThreadState.SuspendRequested: case ThreadState.Suspended: case ThreadState.Unstarted: case ThreadState.WaitSleepJoin: default: break; } if (running) break; } } Thread.Sleep(new TimeSpan(0, 0,1)); AddUserOutput("Press any key to exit or space to repeat"); if (Console.ReadKey().Key == ConsoleKey.Spacebar) { this.Run(); } } public void ProcessStart(object item) { ThreadId p = item as ThreadId; if (p == null) throw new InvalidCastException(); for (int i = 0; i < loop; i++) { string result = GetResult(p.Id); AddUserOutput("{0}:{1}\t{2}={3}", p.Id, i, formulas[p.Id], result); } AddUserOutput("{0}\t\tDone", p.Id); } } }
IFormulaGenerator.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Garoutte.FormulaEngine { public interface IFormulaGenerator { String GetStringResult(string formula); IFormulaResult ResolveFormula(string formula); void SetVariable(String name, decimal value); } }
IFormulaResult.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Garoutte.FormulaEngine { public interface IFormulaResult { String Formula { get; } Decimal Result { get; } List<String> Proof { get; } Dictionary<String, Decimal> Variables { get; } } }
Formularesult.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Garoutte.FormulaEngine { public class FormulaResult : IFormulaResult { public FormulaResult() { Proof = new List<string>(); Variables = new Dictionary<String, Decimal>(); } public String Formula { get; set; } public Decimal Result { get; set; } public List<String> Proof { get; private set; } public Dictionary<String, Decimal> Variables { get; private set; } } }
Here is a copy of the formulas and their expected results. These are run in order on a single thread. This means the first on will finish before the second one starts.
Test 0 ((1+5)*5)/2=15 Test 1 ((((6/2)+2)*4)-1)+(2*1)=21 Test 2 1=1 Test 3 ((2+6)*5)/2=20
Now, the application spawns a thread for each test and each thread runs it's formula 10 times. The output is <test number>:<run count> <the formula used>=<the result>
Here is a copy of a section from a multithreaded console application
1:0 ((((6/2)+2)*4)-1)+(2*1)=Sintax Error! 1:1 ((((6/2)+2)*4)-1)+(2*1)=21 1:2 ((((6/2)+2)*4)-1)+(2*1)=21 1:3 ((((6/2)+2)*4)-1)+(2*1)=21 1:4 ((((6/2)+2)*4)-1)+(2*1)=21 1:5 ((((6/2)+2)*4)-1)+(2*1)=21 1:6 ((((6/2)+2)*4)-1)+(2*1)=21 1:7 ((((6/2)+2)*4)-1)+(2*1)=21 1:8 ((((6/2)+2)*4)-1)+(2*1)=21 1:9 ((((6/2)+2)*4)-1)+(2*1)=21 2:0 1=21 1:10 ((((6/2)+2)*4)-1)+(2*1)=21
See how 1:0 throws an error and 2:0 thinks that the formula of "1" is 21. I like fuzzy math...but ahh....1=21?
Why? Because the generator is not thread safe it is stepping on itself when it is runs on several threads, or web requests. The problem with thread conflicts like this is that you can not always reproduce them because they only manifest when you have more than 1 thread accessing that code at the same time. Debugging this in a web site would be a nightmare.
For my test frame I added a IFormulaGenerator interface (the abstraction makes it so I do not have to adjust my console app as I work... as much)
Because I wanted to show a progression of code changes and I wanted a class at the end that did not inherit from 30 other classes, I adopted a naming convention and simply copied the file. The first rewrite is "Alpha1".
Alpha1
The first thing to do is make the class thread safe by removing the word "static" from the FormulaGenerator file entirely. If you really like the static method EvaluationResult simply create a static version that creates the class and calls the EvaluationResult method.
Because I want more data back from the calculation besides just the result; I added a IFormulaResult interface and a FormulaResult Class. They contain the formula that was passed in, the result as a Decimal a dictionary of variables and the proof. While I added extra fields to the result, the first round of refractoring ignores them.
I changed the EvaluationResult method name to ResolveFormula and made it return a IFormulaResult.
Finally I deleted a couple of fields that were not in use. (Keep that warning count at 0)
Running the test app you can see the calculations are now correct.
Alpha1.cs
using System; using System.Collections; using System.Globalization; using Garoutte.FormulaEngine; namespace Parser { /// <summary> /// This class is used to convert a string expression to a numeric one, /// to evaluate this numeric expression and to reconvert the result back /// to string /// </summary> public class Alpha1 : IFormulaGenerator { #region constructors /// <summary> /// implicit constructor /// </summary> public Alpha1() { }//FormulaGenerator #endregion constructors #region private members //this alghoritm work for expression with any level of parenthesis private void EvalExpr(string astrexpr) { if (ExistParenthesis(astrexpr)) { int iposleftparenthesis = PosMaxLevel(astrexpr); string strtemp = astrexpr.Substring(iposleftparenthesis + 1); int iposrightparenthesis = FindFirstChar(strtemp, ')'); string strbetweenparenthesis = strtemp.Substring(0, iposrightparenthesis); EvalAritm(strbetweenparenthesis.Trim()); string strbefore = astrexpr.Substring(0, iposleftparenthesis); string strafter = astrexpr.Substring(iposleftparenthesis + iposrightparenthesis + 2); EvalExpr(strbefore + m_strservice + strafter); } else EvalAritm(astrexpr.Trim()); }//EvalExpr private bool ExistParenthesis(string astrexpr) { m_iCharContor = 0; int inbleft = 0; NbOfChar(astrexpr, '('); inbleft = m_iCharContor; m_iCharContor = 0; int inbright = 0; NbOfChar(astrexpr, ')'); inbright = m_iCharContor; if (inbleft != inbright) throw new Exception("Sintax Error!"); if (inbleft == 0) return false; return true; }//ExistParenthesis private int PosMaxLevel(string astrexpr) { int ipos = -1; int ilevel = 0; int maxlevel = LevelOfParenthesis(astrexpr); foreach (char ch in astrexpr.ToCharArray()) { ipos += 1; if (ch == '(') { ilevel += 1; if (ilevel == maxlevel) { return ipos; } } else if (ch == ')') { ilevel -= 1; } } return 0; }//PosMaxLevel private int LevelOfParenthesis(string astrexpr) { int ilevel = 0; int ipos = -1; int maxlevel = 0; foreach (char ch in astrexpr.ToCharArray()) { ipos += 1; if (ch == '(') { ilevel = ilevel + 1; } else if (ch == ')') { ilevel -= 1; } if (maxlevel < ilevel) { maxlevel = ilevel; } } return maxlevel; }//LevelOfParenthesis private int FindLastChar(string astrexpr, char acfinder) { return astrexpr.LastIndexOf(acfinder); }//FindLastChar private int FindFirstChar(string astrexpr, char acfinder) { return astrexpr.IndexOf(acfinder); }//FindFirstChar private int FindSecondChar(string astrexpr, char acfinder) { int ifirst = astrexpr.IndexOf(acfinder); int isecond = astrexpr.Substring(ifirst + 1).IndexOf(acfinder); return ifirst + isecond + 1; }//FindSecondChar private void NbOfChar(string astrexpr, char acfinder) { int i = astrexpr.IndexOf(acfinder); if (i >= 0) { m_iCharContor += 1; NbOfChar(astrexpr.Substring(i + 1), acfinder); } }//NbOfChar private void EvalAritm(string astrexpr) { ProcessPercent(astrexpr); string strwithoutpercent = m_strservice; ProcessDivision(strwithoutpercent); string strwithoutdivision = m_strservice; ProcessMultiply(strwithoutdivision); string strwithoutmultiply = m_strservice; ProcessMinus(strwithoutmultiply); string strwithoutminus = m_strservice; ProcessPlus(strwithoutminus); string strwithoutplus = m_strservice; }//EvalAritm private void ProcessPercent(string astrexpr) { m_iCharContor = 0; NbOfChar(astrexpr, '%'); int inbpercent = m_iCharContor; string strnewastrexpr = astrexpr.Trim(); m_iCharContor = 0; NbOfChar(strnewastrexpr, '%'); if (m_iCharContor == 0) { m_strservice = strnewastrexpr; } if (inbpercent > 0) { string astrexprwithoutbreak = astrexpr.Trim(); int ipospercent = FindFirstChar(astrexprwithoutbreak, '%'); string astrwithoutpercent; if (ipospercent >= 1) { astrwithoutpercent = astrexprwithoutbreak.Substring(0, ipospercent); } else { throw new Exception("Sintax error!"); } int ilastdivision = FindLastChar(astrwithoutpercent, '/'); int ilastmultiply = FindLastChar(astrwithoutpercent, '*'); int ilastminus = FindLastChar(astrwithoutpercent, '-'); int ilastplus = FindLastChar(astrwithoutpercent, '+'); ArrayList arlsigns = new ArrayList(); arlsigns.Add(ilastdivision); arlsigns.Add(ilastmultiply); arlsigns.Add(ilastminus); arlsigns.Add(ilastplus); int imaxsignpos = NbMax(arlsigns); if (imaxsignpos < 0) imaxsignpos = 0; string strpercent; string strbeforepercent; if (imaxsignpos >= 1) { strpercent = astrwithoutpercent.Substring(imaxsignpos + 1).Trim(); strbeforepercent = astrwithoutpercent.Substring(0, imaxsignpos + 1).Trim(); } else { strpercent = astrwithoutpercent.Trim(); strbeforepercent = ""; } string strafterpercent = ""; if (ipospercent < astrexprwithoutbreak.Length - 1) { strafterpercent = astrexprwithoutbreak.Substring(ipospercent + 1).Trim(); } decimal dpercentvalue = ConvertToValue(strpercent); strnewastrexpr = strbeforepercent + Convert.ToString(dpercentvalue) + strafterpercent; ProcessPercent(strnewastrexpr); } }//ProcessPercent private void ProcessDivision(string astrexpr) { m_iCharContor = 0; NbOfChar(astrexpr, '/'); int inbdivision = m_iCharContor; string strnewastrexpr = astrexpr.Trim(); m_iCharContor = 0; NbOfChar(strnewastrexpr, '/'); if (m_iCharContor == 0) { m_strservice = strnewastrexpr; } if (inbdivision > 0) { string astrexprwithoutbreak = astrexpr.Trim(); int iposdivision = FindFirstChar(astrexprwithoutbreak, '/'); string astrbeforedivision; if (iposdivision >= 1) { astrbeforedivision = astrexprwithoutbreak.Substring(0, iposdivision).Trim(); } else { throw new Exception("Division error!"); } string astrafterdivision; if (iposdivision < astrexprwithoutbreak.Length - 1) { astrafterdivision = astrexprwithoutbreak.Substring(iposdivision + 1).Trim(); } else { throw new Exception("Division error!"); } int ilastdivisionbefore = FindLastChar(astrbeforedivision, '/'); int ilastmultiplybefore = FindLastChar(astrbeforedivision, '*'); int ilastminusbefore = FindLastChar(astrbeforedivision, '-'); int ilastplusbefore = FindLastChar(astrbeforedivision, '+'); int ilastdivisionafter = FindFirstChar(astrafterdivision, '/'); int ilastmultiplyafter = FindFirstChar(astrafterdivision, '*'); int ilastminusafter = FindFirstChar(astrafterdivision, '-'); int ilastplusafter = FindFirstChar(astrafterdivision, '+'); ArrayList arlsignsbefore = new ArrayList(); arlsignsbefore.Add(ilastdivisionbefore); arlsignsbefore.Add(ilastmultiplybefore); arlsignsbefore.Add(ilastminusbefore); arlsignsbefore.Add(ilastplusbefore); int imaxsignpos = NbMax(arlsignsbefore); if (imaxsignpos <= 0) imaxsignpos = -1; ArrayList arlsignsafter = new ArrayList(); arlsignsafter.Add(ilastdivisionafter); arlsignsafter.Add(ilastmultiplyafter); arlsignsafter.Add(ilastminusafter); arlsignsafter.Add(ilastplusafter); int iminsignpos = NbMin(arlsignsafter); if (iminsignpos <= 0) iminsignpos = 0; string strbeforedivisionafter = astrbeforedivision.Substring(imaxsignpos + 1).Trim(); string strbeforedivisionbefore = astrbeforedivision.Substring(0, imaxsignpos + 1).Trim(); string strafterdivisionbefore = astrafterdivision.Substring(0, iminsignpos).Trim(); string strafterdivisionafter = astrafterdivision.Substring(iminsignpos).Trim(); bool issignbefore = false; if (strbeforedivisionbefore.Length > 0) { if (strbeforedivisionbefore.Substring(0, 1).Trim() == "-") { issignbefore = true; } } bool issignafter = false; if (strafterdivisionbefore.Length > 0) { if (strafterdivisionbefore.Substring(0, 1).Trim() == "-") { issignafter = true; } } decimal dbeforedivision; if (strbeforedivisionafter.Length > 0) { dbeforedivision = Convert.ToDecimal(SetDecimalSeparator(strbeforedivisionafter)); } else if (strbeforedivisionbefore.Length > 0) { dbeforedivision = Convert.ToDecimal(SetDecimalSeparator(strbeforedivisionbefore)); } else dbeforedivision = 0; decimal dafterdivision; if (strafterdivisionbefore.Length > 0) { dafterdivision = Convert.ToDecimal(SetDecimalSeparator(strafterdivisionbefore)); } else if (strafterdivisionafter.Length > 0) { dafterdivision = Convert.ToDecimal(SetDecimalSeparator(strafterdivisionafter)); } else dafterdivision = 1; decimal ddivision = 0; if (dafterdivision != 0) { if (issignbefore || issignafter) { ddivision = (-1) * dbeforedivision / dafterdivision; } else ddivision = dbeforedivision / dafterdivision; } strnewastrexpr = strbeforedivisionbefore + Convert.ToString(ddivision) + strafterdivisionafter; if (strafterdivisionbefore == "") strnewastrexpr = strbeforedivisionbefore + Convert.ToString(ddivision); ProcessDivision(strnewastrexpr); } }//ProcessDivision private void ProcessMultiply(string astrexpr) { m_iCharContor = 0; NbOfChar(astrexpr, '*'); int inbmultiply = m_iCharContor; string strnewastrexpr = astrexpr.Trim(); m_iCharContor = 0; NbOfChar(strnewastrexpr, '*'); if (m_iCharContor == 0) { m_strservice = strnewastrexpr; } if (inbmultiply > 0) { string astrexprwithoutbreak = astrexpr.Trim(); int iposmultiply = FindFirstChar(astrexprwithoutbreak, '*'); string astrbeforemultiply; if (iposmultiply >= 1) { astrbeforemultiply = astrexprwithoutbreak.Substring(0, iposmultiply).Trim(); } else { throw new Exception("Error!"); } string astraftermultiply; if (iposmultiply < astrexprwithoutbreak.Length - 1) { astraftermultiply = astrexprwithoutbreak.Substring(iposmultiply + 1).Trim(); } else { throw new Exception("Error!"); } int ilastdivisionbefore = FindLastChar(astrbeforemultiply, '/'); int ilastmultiplybefore = FindLastChar(astrbeforemultiply, '*'); int ilastminusbefore = FindLastChar(astrbeforemultiply, '-'); int ilastplusbefore = FindLastChar(astrbeforemultiply, '+'); int ilastdivisionafter = FindFirstChar(astraftermultiply, '/'); int ilastmultiplyafter = FindFirstChar(astraftermultiply, '*'); int ilastminusafter = FindFirstChar(astraftermultiply, '-'); int ilastplusafter = FindFirstChar(astraftermultiply, '+'); ArrayList arlsignsbefore = new ArrayList(); arlsignsbefore.Add(ilastdivisionbefore); arlsignsbefore.Add(ilastmultiplybefore); arlsignsbefore.Add(ilastminusbefore); arlsignsbefore.Add(ilastplusbefore); int imaxsignpos = NbMax(arlsignsbefore); if (imaxsignpos <= 0) imaxsignpos = -1; ArrayList arlsignsafter = new ArrayList(); arlsignsafter.Add(ilastdivisionafter); arlsignsafter.Add(ilastmultiplyafter); arlsignsafter.Add(ilastminusafter); arlsignsafter.Add(ilastplusafter); int iminsignpos = NbMin(arlsignsafter); if (iminsignpos <= 0) iminsignpos = 0; string strbeforemultiplyafter = astrbeforemultiply.Substring(imaxsignpos + 1).Trim(); string strbeforemultiplybefore = astrbeforemultiply.Substring(0, imaxsignpos + 1).Trim(); string straftermultiplybefore = astraftermultiply.Substring(0, iminsignpos).Trim(); string straftermultiplyafter = astraftermultiply.Substring(iminsignpos).Trim(); bool issignbefore = false; if (strbeforemultiplybefore.Length > 0) { if (strbeforemultiplybefore.Substring(0, 1).Trim() == "-") { //issignbefore = true; } } bool issignafter = false; if (straftermultiplybefore.Length > 0) { if (straftermultiplybefore.Substring(0, 1).Trim() == "-") { issignafter = true; } } decimal dbeforemultiply; if (strbeforemultiplyafter.Length > 0) { if (IsDigit(strbeforemultiplyafter) == true) { dbeforemultiply = Convert.ToDecimal(SetDecimalSeparator(strbeforemultiplyafter)); } else { dbeforemultiply = ConvertToValue(strbeforemultiplyafter); } } else if (strbeforemultiplybefore.Length > 0) { if (IsDigit(strbeforemultiplybefore) == true) { dbeforemultiply = Convert.ToDecimal(SetDecimalSeparator(strbeforemultiplybefore)); } else { dbeforemultiply = ConvertToValue(strbeforemultiplybefore); } } else dbeforemultiply = 0; decimal daftermultiply; if (straftermultiplybefore.Length > 0) { if (IsDigit(straftermultiplybefore) == true) { daftermultiply = Convert.ToDecimal(SetDecimalSeparator(straftermultiplybefore)); } else { daftermultiply = ConvertToValue(straftermultiplybefore); } } else if (straftermultiplyafter.Length > 0) { if (IsDigit(straftermultiplyafter) == true) { daftermultiply = Convert.ToDecimal(SetDecimalSeparator(straftermultiplyafter)); } else { daftermultiply = ConvertToValue(straftermultiplyafter); } } else daftermultiply = 0; decimal dmultiply = 0; if (daftermultiply != 0) { if (issignbefore || issignafter) { dmultiply = (-1) * dbeforemultiply * daftermultiply; } else dmultiply = dbeforemultiply * daftermultiply; } strnewastrexpr = strbeforemultiplybefore + Convert.ToString(dmultiply) + straftermultiplyafter; if (straftermultiplybefore == "") strnewastrexpr = strbeforemultiplybefore + Convert.ToString(dmultiply); ProcessMultiply(strnewastrexpr); } }//ProcessMultiply private void ProcessMinus(string astrexpr) { m_iCharContor = 0; NbOfChar(astrexpr, '-'); int iposminustest = FindFirstChar(astrexpr.Trim(), '-'); //if(iposminustest == 0) iposminustest = this.FindSecondChar(astrexpr.Trim(),'-'); string strnewastrexpr = astrexpr.Trim(); m_iCharContor = 0; NbOfChar(strnewastrexpr, '-'); if (iposminustest == 0) m_iCharContor -= 1; int inbminus = m_iCharContor; if (m_iCharContor == 0) { m_strservice = strnewastrexpr; } if (inbminus > 0) { string astrexprwithoutbreak = astrexpr.Trim(); //if(iposminustest == 0) astrexprwithoutbreak = astrexprwithoutbreak.Substring(1); int iposminus = FindFirstChar(astrexprwithoutbreak, '-'); if (iposminus == 0) iposminus = FindSecondChar(astrexprwithoutbreak, '-'); string astrbeforeminus; if (iposminus >= 1) { astrbeforeminus = astrexprwithoutbreak.Substring(0, iposminus).Trim(); } else { astrbeforeminus = astrexprwithoutbreak.Substring(0, iposminus).Trim(); } string astrafterminus; if (iposminus < astrexprwithoutbreak.Length - 1) { astrafterminus = astrexprwithoutbreak.Substring(iposminus + 1).Trim(); //if(iposminustest == 0)astrafterminus = astrafterminus.Substring(1).Trim(); } else { throw new Exception("Error!"); } int ilastdivisionbefore = FindLastChar(astrbeforeminus, '/'); int ilastmultiplybefore = FindLastChar(astrbeforeminus, '*'); int ilastminusbefore = FindLastChar(astrbeforeminus, '-'); int ilastplusbefore = FindLastChar(astrbeforeminus, '+'); int ilastdivisionafter = FindFirstChar(astrafterminus, '/'); int ilastmultiplyafter = FindFirstChar(astrafterminus, '*'); int ilastminusafter = FindFirstChar(astrafterminus, '-'); int ilastplusafter = FindFirstChar(astrafterminus, '+'); ArrayList arlsignsbefore = new ArrayList(); arlsignsbefore.Add(ilastdivisionbefore); arlsignsbefore.Add(ilastmultiplybefore); arlsignsbefore.Add(ilastminusbefore); arlsignsbefore.Add(ilastplusbefore); int imaxsignpos = NbMax(arlsignsbefore); if (imaxsignpos <= 0) imaxsignpos = -1; ArrayList arlsignsafter = new ArrayList(); arlsignsafter.Add(ilastdivisionafter); arlsignsafter.Add(ilastmultiplyafter); arlsignsafter.Add(ilastminusafter); arlsignsafter.Add(ilastplusafter); int iminsignpos = NbMin(arlsignsafter); if (iminsignpos <= 0) iminsignpos = 0; string strbeforeminusafter = astrbeforeminus.Substring(imaxsignpos + 1).Trim(); string strbeforeminusbefore = astrbeforeminus.Substring(0, imaxsignpos + 1).Trim(); string strafterminusbefore = astrafterminus.Substring(0, iminsignpos).Trim(); string strafterminusafter = astrafterminus.Substring(iminsignpos).Trim(); decimal dbeforeminus = 0; if (strbeforeminusafter.Length > 0) { if (IsDigit(strbeforeminusafter) == true) { dbeforeminus = Convert.ToDecimal(SetDecimalSeparator(strbeforeminusafter)); } else { dbeforeminus = ConvertToValue(strbeforeminusafter); } } else if (strbeforeminusbefore.Length > 0) { if (IsDigit(strbeforeminusbefore) == true) { dbeforeminus = Convert.ToDecimal(SetDecimalSeparator(strbeforeminusbefore)); } else { dbeforeminus = ConvertToValue(strbeforeminusbefore); } } decimal dafterminus = 0; if (strafterminusbefore.Length > 0) { if (IsDigit(strafterminusbefore)) { dafterminus = Convert.ToDecimal(SetDecimalSeparator(strafterminusbefore)); } else { dafterminus = ConvertToValue(strafterminusbefore); } } else if (strafterminusafter.Length > 0) { if (IsDigit(strafterminusafter)) { dafterminus = Convert.ToDecimal(SetDecimalSeparator(strafterminusafter)); } else { dafterminus = ConvertToValue(strafterminusafter); } } decimal dminus = 0; dminus = dbeforeminus - dafterminus; strnewastrexpr = strbeforeminusbefore + Convert.ToString(dminus) + strafterminusafter; if (strafterminusbefore == "") strnewastrexpr = strbeforeminusbefore + Convert.ToString(dminus); ProcessMinus(strnewastrexpr); } }//ProcessMinus private void ProcessPlus(string astrexpr) { m_iCharContor = 0; NbOfChar(astrexpr, '+'); int inbplus = m_iCharContor; string strnewastrexpr = astrexpr.Trim(); m_iCharContor = 0; NbOfChar(strnewastrexpr, '+'); if (m_iCharContor == 0) { m_strservice = strnewastrexpr; } if (inbplus > 0) { string astrexprwithoutbreak = astrexpr.Trim(); int iposplus = FindFirstChar(astrexprwithoutbreak, '+'); string astrbeforeplus; if (iposplus >= 1) { astrbeforeplus = astrexprwithoutbreak.Substring(0, iposplus).Trim(); } else { throw new Exception("Error!"); } string astrafterplus; if (iposplus < astrexprwithoutbreak.Length - 1) { astrafterplus = astrexprwithoutbreak.Substring(iposplus + 1).Trim(); } else { throw new Exception("Error!"); } int ilastdivisionbefore = FindLastChar(astrbeforeplus, '/'); int ilastmultiplybefore = FindLastChar(astrbeforeplus, '*'); int ilastminusbefore = FindLastChar(astrbeforeplus, '-'); int ilastplusbefore = FindLastChar(astrbeforeplus, '+'); int ilastdivisionafter = FindFirstChar(astrafterplus, '/'); int ilastmultiplyafter = FindFirstChar(astrafterplus, '*'); int ilastminusafter = FindFirstChar(astrafterplus, '-'); int ilastplusafter = FindFirstChar(astrafterplus, '+'); ArrayList arlsignsbefore = new ArrayList(); arlsignsbefore.Add(ilastdivisionbefore); arlsignsbefore.Add(ilastmultiplybefore); arlsignsbefore.Add(ilastminusbefore); arlsignsbefore.Add(ilastplusbefore); int imaxsignpos = NbMax(arlsignsbefore); if (imaxsignpos <= 0) imaxsignpos = -1; ArrayList arlsignsafter = new ArrayList(); arlsignsafter.Add(ilastdivisionafter); arlsignsafter.Add(ilastmultiplyafter); arlsignsafter.Add(ilastminusafter); arlsignsafter.Add(ilastplusafter); int iminsignpos = NbMin(arlsignsafter); if (iminsignpos <= 0) iminsignpos = 0; string strbeforeplusafter = astrbeforeplus.Substring(imaxsignpos + 1).Trim(); string strbeforeplusbefore = astrbeforeplus.Substring(0, imaxsignpos + 1).Trim(); string strafterplusbefore = astrafterplus.Substring(0, iminsignpos).Trim(); string strafterplusafter = astrafterplus.Substring(iminsignpos).Trim(); decimal dbeforeplus = 0; if (strbeforeplusafter.Length > 0) { if (IsDigit(strbeforeplusafter) == true) { dbeforeplus = Convert.ToDecimal(SetDecimalSeparator(strbeforeplusafter)); } else { dbeforeplus = ConvertToValue(strbeforeplusafter); } } else if (strbeforeplusbefore.Length > 0) { if (IsDigit(strbeforeplusbefore) == true) { dbeforeplus = Convert.ToDecimal(SetDecimalSeparator(strbeforeplusbefore)); } else { dbeforeplus = ConvertToValue(strbeforeplusbefore); } } decimal dafterplus = 0; if (strafterplusbefore.Length > 0) { if (IsDigit(strafterplusbefore) == true) { dafterplus = Convert.ToDecimal(SetDecimalSeparator(strafterplusbefore)); } else { dafterplus = ConvertToValue(strafterplusbefore); } } else if (strafterplusafter.Length > 0) { if (IsDigit(strafterplusafter) == true) { dafterplus = Convert.ToDecimal(SetDecimalSeparator(strafterplusafter)); } else { dafterplus = ConvertToValue(strafterplusafter); } } decimal dplus = 0; dplus = dbeforeplus + dafterplus; strnewastrexpr = strbeforeplusbefore + Convert.ToString(dplus) + strafterplusafter; if (strafterplusbefore == "") strnewastrexpr = strbeforeplusbefore + Convert.ToString(dplus); ProcessPlus(strnewastrexpr); } else { if (IsDigit(astrexpr) == false) { ConvertToValue(astrexpr); } } }//ProcessPlus private decimal ConvertToValue(string astrexpr) { bool bmarker = true; decimal dexpr = 0; for (int i = 0; i <= astrexpr.Length - 1; i++) { string strlocal = astrexpr.Substring(i, 1); if ((i != 0) || (strlocal != "-")) { char[] ch = strlocal.ToCharArray(); if ((Char.IsDigit(ch[0]) == false) && (strlocal != ".") && (strlocal != ",") /*&& (strlocal != " ")*/) { bmarker = false; } } } if (bmarker == true) { dexpr = Convert.ToDecimal(astrexpr) / 100; } else { throw new Exception("Not numeric value!"); } return dexpr; }//ConvertToValue private int NbMax(ArrayList aobjarl) { int max = 0; foreach (object obj in aobjarl) { if ((int)obj != -1) { if (max <= (int)obj) max = (int)obj; } } return max; }//NbMax private int NbMin(ArrayList aobjarl) { int min = NbMax(aobjarl); foreach (object obj in aobjarl) { if ((int)obj != -1) { if (min >= (int)obj) min = (int)obj; } } return min; }//NbMin private bool IsDigit(string astrexpr) { foreach (char ch in astrexpr.ToCharArray()) { if ((ch != '-') && (ch != ' ') && (ch != '.') && (ch != ',')) { if (Char.IsDigit(ch) == false) { return false; } } } return true; }//IsDigit /// <summary> /// Sets the variable. /// </summary> /// <remarks>Not supported in this rewrite.</remarks> /// <param name="name">The name.</param> /// <param name="value">The value.</param> public void SetVariable(String name, Decimal value) { throw new NotImplementedException(); } private string SetDecimalSeparator(string astrexpr) { string strseparator = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator; int ipoint = astrexpr.IndexOf('.'); int icomma = astrexpr.IndexOf(','); string newastrexpr; if (ipoint >= 0) { newastrexpr = astrexpr.Replace(".", strseparator); } else if (icomma >= 0) { newastrexpr = astrexpr.Replace(",", strseparator); } else { newastrexpr = astrexpr; } return newastrexpr; }//SetDecimalSeparator private int m_iCharContor = 0; private string m_strservice = ""; #endregion private members #region IFormulaGenerator Members public string GetStringResult(string formula) { IFormulaResult result = ResolveFormula(formula); return result.Result.ToString(); } public IFormulaResult ResolveFormula(string formula) { Decimal result = 0m; FormulaResult formularesult = new FormulaResult() { Formula = formula }; try { if (formula == "") formula = "0"; EvalExpr(formula); Decimal.TryParse( m_strservice, out result); formularesult.Result = result; } catch (Exception aobjExc) { throw aobjExc; } return formularesult; }//EvaluationResult #endregion } }
Alpha2
First we add a Dictionary<string,decimal> to hold our variables in the FormulaGenerator. Because we do not want to expose the Dictionary directly outside the generator we add a SetVariable method (we stub in the method and toss a NotImplementedException in pervious versions of the generator). We also pass the FormulaResult around in the methods. This allows us to update the result and keep it isolated from the rest of the generator so the same instance of the generator can easily be reused.
Because unknown characters (or strings) are handled in the ConvertToValue method we can do the lookup of the variable there. The catch is this method also deals with converting 50% to .50. So we pass an extra parameter telling the method if we are converting the value to a % or not. When a variable is used we add it to the results variable collection if it does not already exist there. This allows us to have a generator that loads x variables and a result that shows us which ones were used.
Because we could have an unknown variable/expression and I do not much care for throwing application exceptions let's add a custom exception.
UndefinedExpressionException.cs
public class UndefineExpressionException : ApplicationException { public UndefineExpressionException(string expression) : base(string.Format("The expression \"{0}\" has not been defined",expression)) { Expression = expression; } public UndefineExpressionException(string expression, IFormulaGenerator generator) : this(expression) { FormulaGenerator = generator; } public String Expression { get; set; } public IFormulaGenerator FormulaGenerator { get; private set; } }
Alpha2.cs
using System; using System.Collections; using System.Globalization; using System.Collections.Generic; using Garoutte.FormulaEngine; using Garoutte.FormulaEngine.Exceptions; namespace Garoutte.FormulaEngine { /// <summary> /// This class is used to convert a string expression to a numeric one, /// to evaluate this numeric expression and to reconvert the result back /// to string /// </summary> public class Alpha2 : IFormulaGenerator { #region constructors /// <summary> /// implicit constructor /// </summary> public Alpha2() { Variables = new Dictionary<String, Decimal>(); }//FormulaGenerator #endregion constructors private Dictionary<String, Decimal> Variables { get; set; } public void SetVariable(String name, Decimal value) { if (Variables.ContainsKey(name)) Variables[name] = value; else Variables.Add(name, value); } #region private members //this alghoritm work for expression with any level of parenthesis private void EvalExpr(string astrexpr,FormulaResult formulaResult) { if (ExistParenthesis(astrexpr)) { int iposleftparenthesis = PosMaxLevel(astrexpr); string strtemp = astrexpr.Substring(iposleftparenthesis + 1); int iposrightparenthesis = FindFirstChar(strtemp, ')'); string strbetweenparenthesis = strtemp.Substring(0, iposrightparenthesis); EvalAritm(strbetweenparenthesis.Trim(),formulaResult); string strbefore = astrexpr.Substring(0, iposleftparenthesis); string strafter = astrexpr.Substring(iposleftparenthesis + iposrightparenthesis + 2); EvalExpr(strbefore + m_strservice + strafter, formulaResult); } else EvalAritm(astrexpr.Trim(),formulaResult); }//EvalExpr private bool ExistParenthesis(string astrexpr) { m_iCharContor = 0; int inbleft = 0; NbOfChar(astrexpr, '('); inbleft = m_iCharContor; m_iCharContor = 0; int inbright = 0; NbOfChar(astrexpr, ')'); inbright = m_iCharContor; if (inbleft != inbright) throw new Exception("Sintax Error!"); if (inbleft == 0) return false; return true; }//ExistParenthesis private int PosMaxLevel(string astrexpr) { int ipos = -1; int ilevel = 0; int maxlevel = LevelOfParenthesis(astrexpr); foreach (char ch in astrexpr.ToCharArray()) { ipos += 1; if (ch == '(') { ilevel += 1; if (ilevel == maxlevel) { return ipos; } } else if (ch == ')') { ilevel -= 1; } } return 0; }//PosMaxLevel private int LevelOfParenthesis(string astrexpr) { int ilevel = 0; int ipos = -1; int maxlevel = 0; foreach (char ch in astrexpr.ToCharArray()) { ipos += 1; if (ch == '(') { ilevel = ilevel + 1; } else if (ch == ')') { ilevel -= 1; } if (maxlevel < ilevel) { maxlevel = ilevel; } } return maxlevel; }//LevelOfParenthesis private int FindLastChar(string astrexpr, char acfinder) { return astrexpr.LastIndexOf(acfinder); }//FindLastChar private int FindFirstChar(string astrexpr, char acfinder) { return astrexpr.IndexOf(acfinder); }//FindFirstChar private int FindSecondChar(string astrexpr, char acfinder) { int ifirst = astrexpr.IndexOf(acfinder); int isecond = astrexpr.Substring(ifirst + 1).IndexOf(acfinder); return ifirst + isecond + 1; }//FindSecondChar private void NbOfChar(string astrexpr, char acfinder) { int i = astrexpr.IndexOf(acfinder); if (i >= 0) { m_iCharContor += 1; NbOfChar(astrexpr.Substring(i + 1), acfinder); } }//NbOfChar private void EvalAritm(string astrexpr, FormulaResult formulaResult) { ProcessPercent(astrexpr,formulaResult); string strwithoutpercent = m_strservice; ProcessDivision(strwithoutpercent,formulaResult); string strwithoutdivision = m_strservice; ProcessMultiply(strwithoutdivision,formulaResult); string strwithoutmultiply = m_strservice; ProcessMinus(strwithoutmultiply,formulaResult); string strwithoutminus = m_strservice; ProcessPlus(strwithoutminus,formulaResult); string strwithoutplus = m_strservice; }//EvalAritm private void ProcessPercent(string astrexpr, FormulaResult formulaResult) { m_iCharContor = 0; NbOfChar(astrexpr, '%'); int inbpercent = m_iCharContor; string strnewastrexpr = astrexpr.Trim(); m_iCharContor = 0; NbOfChar(strnewastrexpr, '%'); if (m_iCharContor == 0) { m_strservice = strnewastrexpr; } if (inbpercent > 0) { string astrexprwithoutbreak = astrexpr.Trim(); int ipospercent = FindFirstChar(astrexprwithoutbreak, '%'); string astrwithoutpercent; if (ipospercent >= 1) { astrwithoutpercent = astrexprwithoutbreak.Substring(0, ipospercent); } else { throw new Exception("Illegal % error!"); } int ilastdivision = FindLastChar(astrwithoutpercent, '/'); int ilastmultiply = FindLastChar(astrwithoutpercent, '*'); int ilastminus = FindLastChar(astrwithoutpercent, '-'); int ilastplus = FindLastChar(astrwithoutpercent, '+'); ArrayList arlsigns = new ArrayList(); arlsigns.Add(ilastdivision); arlsigns.Add(ilastmultiply); arlsigns.Add(ilastminus); arlsigns.Add(ilastplus); int imaxsignpos = NbMax(arlsigns); if (imaxsignpos < 0) imaxsignpos = 0; string strpercent; string strbeforepercent; if (imaxsignpos >= 1) { strpercent = astrwithoutpercent.Substring(imaxsignpos + 1).Trim(); strbeforepercent = astrwithoutpercent.Substring(0, imaxsignpos + 1).Trim(); } else { strpercent = astrwithoutpercent.Trim(); strbeforepercent = ""; } string strafterpercent = ""; if (ipospercent < astrexprwithoutbreak.Length - 1) { strafterpercent = astrexprwithoutbreak.Substring(ipospercent + 1).Trim(); } decimal dpercentvalue = ConvertToValue(strpercent, formulaResult, true); strnewastrexpr = strbeforepercent + Convert.ToString(dpercentvalue) + strafterpercent; ProcessPercent(strnewastrexpr,formulaResult); } }//ProcessPercent private void ProcessDivision(string astrexpr, FormulaResult formulaResult) { m_iCharContor = 0; NbOfChar(astrexpr, '/'); int inbdivision = m_iCharContor; string strnewastrexpr = astrexpr.Trim(); m_iCharContor = 0; NbOfChar(strnewastrexpr, '/'); if (m_iCharContor == 0) { m_strservice = strnewastrexpr; } if (inbdivision > 0) { string astrexprwithoutbreak = astrexpr.Trim(); int iposdivision = FindFirstChar(astrexprwithoutbreak, '/'); string astrbeforedivision; if (iposdivision >= 1) { astrbeforedivision = astrexprwithoutbreak.Substring(0, iposdivision).Trim(); } else { throw new Exception("Division error!"); } string astrafterdivision; if (iposdivision < astrexprwithoutbreak.Length - 1) { astrafterdivision = astrexprwithoutbreak.Substring(iposdivision + 1).Trim(); } else { throw new Exception("Division error!"); } int ilastdivisionbefore = FindLastChar(astrbeforedivision, '/'); int ilastmultiplybefore = FindLastChar(astrbeforedivision, '*'); int ilastminusbefore = FindLastChar(astrbeforedivision, '-'); int ilastplusbefore = FindLastChar(astrbeforedivision, '+'); int ilastdivisionafter = FindFirstChar(astrafterdivision, '/'); int ilastmultiplyafter = FindFirstChar(astrafterdivision, '*'); int ilastminusafter = FindFirstChar(astrafterdivision, '-'); int ilastplusafter = FindFirstChar(astrafterdivision, '+'); ArrayList arlsignsbefore = new ArrayList(); arlsignsbefore.Add(ilastdivisionbefore); arlsignsbefore.Add(ilastmultiplybefore); arlsignsbefore.Add(ilastminusbefore); arlsignsbefore.Add(ilastplusbefore); int imaxsignpos = NbMax(arlsignsbefore); if (imaxsignpos <= 0) imaxsignpos = -1; ArrayList arlsignsafter = new ArrayList(); arlsignsafter.Add(ilastdivisionafter); arlsignsafter.Add(ilastmultiplyafter); arlsignsafter.Add(ilastminusafter); arlsignsafter.Add(ilastplusafter); int iminsignpos = NbMin(arlsignsafter); if (iminsignpos <= 0) iminsignpos = 0; string strbeforedivisionafter = astrbeforedivision.Substring(imaxsignpos + 1).Trim(); string strbeforedivisionbefore = astrbeforedivision.Substring(0, imaxsignpos + 1).Trim(); string strafterdivisionbefore = astrafterdivision.Substring(0, iminsignpos).Trim(); string strafterdivisionafter = astrafterdivision.Substring(iminsignpos).Trim(); bool issignbefore = false; if (strbeforedivisionbefore.Length > 0) { if (strbeforedivisionbefore.Substring(0, 1).Trim() == "-") { issignbefore = true; } } bool issignafter = false; if (strafterdivisionbefore.Length > 0) { if (strafterdivisionbefore.Substring(0, 1).Trim() == "-") { issignafter = true; } } decimal dbeforedivision; if (strbeforedivisionafter.Length > 0) { dbeforedivision = Convert.ToDecimal(SetDecimalSeparator(strbeforedivisionafter)); } else if (strbeforedivisionbefore.Length > 0) { dbeforedivision = Convert.ToDecimal(SetDecimalSeparator(strbeforedivisionbefore)); } else dbeforedivision = 0; decimal dafterdivision; if (strafterdivisionbefore.Length > 0) { dafterdivision = Convert.ToDecimal(SetDecimalSeparator(strafterdivisionbefore)); } else if (strafterdivisionafter.Length > 0) { dafterdivision = Convert.ToDecimal(SetDecimalSeparator(strafterdivisionafter)); } else dafterdivision = 1; decimal ddivision = 0; if (dafterdivision != 0) { if (issignbefore || issignafter) { ddivision = (-1) * dbeforedivision / dafterdivision; } else ddivision = dbeforedivision / dafterdivision; } strnewastrexpr = strbeforedivisionbefore + Convert.ToString(ddivision) + strafterdivisionafter; if (strafterdivisionbefore == "") strnewastrexpr = strbeforedivisionbefore + Convert.ToString(ddivision); ProcessDivision(strnewastrexpr,formulaResult); } }//ProcessDivision private void ProcessMultiply(string astrexpr, FormulaResult formulaResult) { m_iCharContor = 0; NbOfChar(astrexpr, '*'); int inbmultiply = m_iCharContor; string strnewastrexpr = astrexpr.Trim(); m_iCharContor = 0; NbOfChar(strnewastrexpr, '*'); if (m_iCharContor == 0) { m_strservice = strnewastrexpr; } if (inbmultiply > 0) { string astrexprwithoutbreak = astrexpr.Trim(); int iposmultiply = FindFirstChar(astrexprwithoutbreak, '*'); string astrbeforemultiply; if (iposmultiply >= 1) { astrbeforemultiply = astrexprwithoutbreak.Substring(0, iposmultiply).Trim(); } else { throw new Exception("Error!"); } string astraftermultiply; if (iposmultiply < astrexprwithoutbreak.Length - 1) { astraftermultiply = astrexprwithoutbreak.Substring(iposmultiply + 1).Trim(); } else { throw new Exception("Error!"); } int ilastdivisionbefore = FindLastChar(astrbeforemultiply, '/'); int ilastmultiplybefore = FindLastChar(astrbeforemultiply, '*'); int ilastminusbefore = FindLastChar(astrbeforemultiply, '-'); int ilastplusbefore = FindLastChar(astrbeforemultiply, '+'); int ilastdivisionafter = FindFirstChar(astraftermultiply, '/'); int ilastmultiplyafter = FindFirstChar(astraftermultiply, '*'); int ilastminusafter = FindFirstChar(astraftermultiply, '-'); int ilastplusafter = FindFirstChar(astraftermultiply, '+'); ArrayList arlsignsbefore = new ArrayList(); arlsignsbefore.Add(ilastdivisionbefore); arlsignsbefore.Add(ilastmultiplybefore); arlsignsbefore.Add(ilastminusbefore); arlsignsbefore.Add(ilastplusbefore); int imaxsignpos = NbMax(arlsignsbefore); if (imaxsignpos <= 0) imaxsignpos = -1; ArrayList arlsignsafter = new ArrayList(); arlsignsafter.Add(ilastdivisionafter); arlsignsafter.Add(ilastmultiplyafter); arlsignsafter.Add(ilastminusafter); arlsignsafter.Add(ilastplusafter); int iminsignpos = NbMin(arlsignsafter); if (iminsignpos <= 0) iminsignpos = 0; string strbeforemultiplyafter = astrbeforemultiply.Substring(imaxsignpos + 1).Trim(); string strbeforemultiplybefore = astrbeforemultiply.Substring(0, imaxsignpos + 1).Trim(); string straftermultiplybefore = astraftermultiply.Substring(0, iminsignpos).Trim(); string straftermultiplyafter = astraftermultiply.Substring(iminsignpos).Trim(); bool issignbefore = false; if (strbeforemultiplybefore.Length > 0) { if (strbeforemultiplybefore.Substring(0, 1).Trim() == "-") { //issignbefore = true; } } bool issignafter = false; if (straftermultiplybefore.Length > 0) { if (straftermultiplybefore.Substring(0, 1).Trim() == "-") { issignafter = true; } } decimal dbeforemultiply; if (strbeforemultiplyafter.Length > 0) { if (IsDigit(strbeforemultiplyafter) == true) { dbeforemultiply = Convert.ToDecimal(SetDecimalSeparator(strbeforemultiplyafter)); } else { dbeforemultiply = ConvertToValue(strbeforemultiplyafter, formulaResult); } } else if (strbeforemultiplybefore.Length > 0) { if (IsDigit(strbeforemultiplybefore) == true) { dbeforemultiply = Convert.ToDecimal(SetDecimalSeparator(strbeforemultiplybefore)); } else { dbeforemultiply = ConvertToValue(strbeforemultiplybefore, formulaResult); } } else dbeforemultiply = 0; decimal daftermultiply; if (straftermultiplybefore.Length > 0) { if (IsDigit(straftermultiplybefore) == true) { daftermultiply = Convert.ToDecimal(SetDecimalSeparator(straftermultiplybefore)); } else { daftermultiply = ConvertToValue(straftermultiplybefore, formulaResult); } } else if (straftermultiplyafter.Length > 0) { if (IsDigit(straftermultiplyafter) == true) { daftermultiply = Convert.ToDecimal(SetDecimalSeparator(straftermultiplyafter)); } else { daftermultiply = ConvertToValue(straftermultiplyafter, formulaResult); } } else daftermultiply = 0; decimal dmultiply = 0; if (daftermultiply != 0) { if (issignbefore || issignafter) { dmultiply = (-1) * dbeforemultiply * daftermultiply; } else dmultiply = dbeforemultiply * daftermultiply; } strnewastrexpr = strbeforemultiplybefore + Convert.ToString(dmultiply) + straftermultiplyafter; if (straftermultiplybefore == "") strnewastrexpr = strbeforemultiplybefore + Convert.ToString(dmultiply); ProcessMultiply(strnewastrexpr,formulaResult); } }//ProcessMultiply private void ProcessMinus(string astrexpr, FormulaResult formulaResult) { m_iCharContor = 0; NbOfChar(astrexpr, '-'); int iposminustest = FindFirstChar(astrexpr.Trim(), '-'); //if(iposminustest == 0) iposminustest = this.FindSecondChar(astrexpr.Trim(),'-'); string strnewastrexpr = astrexpr.Trim(); m_iCharContor = 0; NbOfChar(strnewastrexpr, '-'); if (iposminustest == 0) m_iCharContor -= 1; int inbminus = m_iCharContor; if (m_iCharContor == 0) { m_strservice = strnewastrexpr; } if (inbminus > 0) { string astrexprwithoutbreak = astrexpr.Trim(); //if(iposminustest == 0) astrexprwithoutbreak = astrexprwithoutbreak.Substring(1); int iposminus = FindFirstChar(astrexprwithoutbreak, '-'); if (iposminus == 0) iposminus = FindSecondChar(astrexprwithoutbreak, '-'); string astrbeforeminus; if (iposminus >= 1) { astrbeforeminus = astrexprwithoutbreak.Substring(0, iposminus).Trim(); } else { astrbeforeminus = astrexprwithoutbreak.Substring(0, iposminus).Trim(); } string astrafterminus; if (iposminus < astrexprwithoutbreak.Length - 1) { astrafterminus = astrexprwithoutbreak.Substring(iposminus + 1).Trim(); //if(iposminustest == 0)astrafterminus = astrafterminus.Substring(1).Trim(); } else { throw new Exception("Error!"); } int ilastdivisionbefore = FindLastChar(astrbeforeminus, '/'); int ilastmultiplybefore = FindLastChar(astrbeforeminus, '*'); int ilastminusbefore = FindLastChar(astrbeforeminus, '-'); int ilastplusbefore = FindLastChar(astrbeforeminus, '+'); int ilastdivisionafter = FindFirstChar(astrafterminus, '/'); int ilastmultiplyafter = FindFirstChar(astrafterminus, '*'); int ilastminusafter = FindFirstChar(astrafterminus, '-'); int ilastplusafter = FindFirstChar(astrafterminus, '+'); ArrayList arlsignsbefore = new ArrayList(); arlsignsbefore.Add(ilastdivisionbefore); arlsignsbefore.Add(ilastmultiplybefore); arlsignsbefore.Add(ilastminusbefore); arlsignsbefore.Add(ilastplusbefore); int imaxsignpos = NbMax(arlsignsbefore); if (imaxsignpos <= 0) imaxsignpos = -1; ArrayList arlsignsafter = new ArrayList(); arlsignsafter.Add(ilastdivisionafter); arlsignsafter.Add(ilastmultiplyafter); arlsignsafter.Add(ilastminusafter); arlsignsafter.Add(ilastplusafter); int iminsignpos = NbMin(arlsignsafter); if (iminsignpos <= 0) iminsignpos = 0; string strbeforeminusafter = astrbeforeminus.Substring(imaxsignpos + 1).Trim(); string strbeforeminusbefore = astrbeforeminus.Substring(0, imaxsignpos + 1).Trim(); string strafterminusbefore = astrafterminus.Substring(0, iminsignpos).Trim(); string strafterminusafter = astrafterminus.Substring(iminsignpos).Trim(); decimal dbeforeminus = 0; if (strbeforeminusafter.Length > 0) { if (IsDigit(strbeforeminusafter) == true) { dbeforeminus = Convert.ToDecimal(SetDecimalSeparator(strbeforeminusafter)); } else { dbeforeminus = ConvertToValue(strbeforeminusafter, formulaResult); } } else if (strbeforeminusbefore.Length > 0) { if (IsDigit(strbeforeminusbefore) == true) { dbeforeminus = Convert.ToDecimal(SetDecimalSeparator(strbeforeminusbefore)); } else { dbeforeminus = ConvertToValue(strbeforeminusbefore, formulaResult); } } decimal dafterminus = 0; if (strafterminusbefore.Length > 0) { if (IsDigit(strafterminusbefore)) { dafterminus = Convert.ToDecimal(SetDecimalSeparator(strafterminusbefore)); } else { dafterminus = ConvertToValue(strafterminusbefore, formulaResult); } } else if (strafterminusafter.Length > 0) { if (IsDigit(strafterminusafter)) { dafterminus = Convert.ToDecimal(SetDecimalSeparator(strafterminusafter)); } else { dafterminus = ConvertToValue(strafterminusafter, formulaResult); } } decimal dminus = 0; dminus = dbeforeminus - dafterminus; strnewastrexpr = strbeforeminusbefore + Convert.ToString(dminus) + strafterminusafter; if (strafterminusbefore == "") strnewastrexpr = strbeforeminusbefore + Convert.ToString(dminus); ProcessMinus(strnewastrexpr,formulaResult); } }//ProcessMinus private void ProcessPlus(string astrexpr, FormulaResult formulaResult) { m_iCharContor = 0; NbOfChar(astrexpr, '+'); int inbplus = m_iCharContor; string strnewastrexpr = astrexpr.Trim(); m_iCharContor = 0; NbOfChar(strnewastrexpr, '+'); if (m_iCharContor == 0) { m_strservice = strnewastrexpr; } if (inbplus > 0) { string astrexprwithoutbreak = astrexpr.Trim(); int iposplus = FindFirstChar(astrexprwithoutbreak, '+'); string astrbeforeplus; if (iposplus >= 1) { astrbeforeplus = astrexprwithoutbreak.Substring(0, iposplus).Trim(); } else { throw new Exception("Error!"); } string astrafterplus; if (iposplus < astrexprwithoutbreak.Length - 1) { astrafterplus = astrexprwithoutbreak.Substring(iposplus + 1).Trim(); } else { throw new Exception("Error!"); } int ilastdivisionbefore = FindLastChar(astrbeforeplus, '/'); int ilastmultiplybefore = FindLastChar(astrbeforeplus, '*'); int ilastminusbefore = FindLastChar(astrbeforeplus, '-'); int ilastplusbefore = FindLastChar(astrbeforeplus, '+'); int ilastdivisionafter = FindFirstChar(astrafterplus, '/'); int ilastmultiplyafter = FindFirstChar(astrafterplus, '*'); int ilastminusafter = FindFirstChar(astrafterplus, '-'); int ilastplusafter = FindFirstChar(astrafterplus, '+'); ArrayList arlsignsbefore = new ArrayList(); arlsignsbefore.Add(ilastdivisionbefore); arlsignsbefore.Add(ilastmultiplybefore); arlsignsbefore.Add(ilastminusbefore); arlsignsbefore.Add(ilastplusbefore); int imaxsignpos = NbMax(arlsignsbefore); if (imaxsignpos <= 0) imaxsignpos = -1; ArrayList arlsignsafter = new ArrayList(); arlsignsafter.Add(ilastdivisionafter); arlsignsafter.Add(ilastmultiplyafter); arlsignsafter.Add(ilastminusafter); arlsignsafter.Add(ilastplusafter); int iminsignpos = NbMin(arlsignsafter); if (iminsignpos <= 0) iminsignpos = 0; string strbeforeplusafter = astrbeforeplus.Substring(imaxsignpos + 1).Trim(); string strbeforeplusbefore = astrbeforeplus.Substring(0, imaxsignpos + 1).Trim(); string strafterplusbefore = astrafterplus.Substring(0, iminsignpos).Trim(); string strafterplusafter = astrafterplus.Substring(iminsignpos).Trim(); decimal dbeforeplus = 0; if (strbeforeplusafter.Length > 0) { if (IsDigit(strbeforeplusafter) == true) { dbeforeplus = Convert.ToDecimal(SetDecimalSeparator(strbeforeplusafter)); } else { dbeforeplus = ConvertToValue(strbeforeplusafter, formulaResult); } } else if (strbeforeplusbefore.Length > 0) { if (IsDigit(strbeforeplusbefore) == true) { dbeforeplus = Convert.ToDecimal(SetDecimalSeparator(strbeforeplusbefore)); } else { dbeforeplus = ConvertToValue(strbeforeplusbefore, formulaResult); } } decimal dafterplus = 0; if (strafterplusbefore.Length > 0) { if (IsDigit(strafterplusbefore) == true) { dafterplus = Convert.ToDecimal(SetDecimalSeparator(strafterplusbefore)); } else { dafterplus = ConvertToValue(strafterplusbefore, formulaResult); } } else if (strafterplusafter.Length > 0) { if (IsDigit(strafterplusafter) == true) { dafterplus = Convert.ToDecimal(SetDecimalSeparator(strafterplusafter)); } else { dafterplus = ConvertToValue(strafterplusafter, formulaResult); } } decimal dplus = 0; dplus = dbeforeplus + dafterplus; strnewastrexpr = strbeforeplusbefore + Convert.ToString(dplus) + strafterplusafter; if (strafterplusbefore == "") strnewastrexpr = strbeforeplusbefore + Convert.ToString(dplus); ProcessPlus(strnewastrexpr,formulaResult); } else { if (IsDigit(astrexpr) == false) { ConvertToValue(astrexpr, formulaResult); } } }//ProcessPlus #region ConvertToValue private decimal ConvertToValue(string expr, FormulaResult formulaResult) { return ConvertToValue(expr, formulaResult, false); } private decimal ConvertToValue(string expr,FormulaResult formulaResult, bool isAPercentConversion) { bool bmarker = true; decimal dexpr = 0; //for each character for (int i = 0; i <= expr.Length - 1; i++) { //get the character string strlocal = expr.Substring(i, 1); if ((i != 0) || (strlocal != "-")) { char[] ch = strlocal.ToCharArray(); if ((Char.IsDigit(ch[0]) == false) && (strlocal != ".") && (strlocal != ",") /*&& (strlocal != " ")*/) { bmarker = false; } } } if ((bmarker == true) && (isAPercentConversion)) { dexpr = Convert.ToDecimal(expr) / 100; } else { //unknown expr here if (Variables.ContainsKey(expr)) { dexpr = Variables[expr]; if (!formulaResult.Variables.ContainsKey(expr)) formulaResult.Variables.Add(expr, Variables[expr]); } else throw new UndefineExpressionException(expr); } return dexpr; }//ConvertToValue #endregion ConvertToValue private int NbMax(ArrayList aobjarl) { int max = 0; foreach (object obj in aobjarl) { if ((int)obj != -1) { if (max <= (int)obj) max = (int)obj; } } return max; }//NbMax private int NbMin(ArrayList aobjarl) { int min = NbMax(aobjarl); foreach (object obj in aobjarl) { if ((int)obj != -1) { if (min >= (int)obj) min = (int)obj; } } return min; }//NbMin private bool IsDigit(string astrexpr) { foreach (char ch in astrexpr.ToCharArray()) { if ((ch != '-') && (ch != ' ') && (ch != '.') && (ch != ',')) { if (Char.IsDigit(ch) == false) { return false; } } } return true; }//IsDigit private string SetDecimalSeparator(string astrexpr) { string strseparator = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator; int ipoint = astrexpr.IndexOf('.'); int icomma = astrexpr.IndexOf(','); string newastrexpr; if (ipoint >= 0) { newastrexpr = astrexpr.Replace(".", strseparator); } else if (icomma >= 0) { newastrexpr = astrexpr.Replace(",", strseparator); } else { newastrexpr = astrexpr; } return newastrexpr; }//SetDecimalSeparator private int m_iCharContor = 0; private string m_strservice = ""; #endregion private members #region IFormulaGenerator Members public string GetStringResult(string formula) { IFormulaResult result = ResolveFormula(formula); return result.Result.ToString(); } public IFormulaResult ResolveFormula(string formula) { Decimal result = 0m; FormulaResult formulaResult = new FormulaResult() { Formula = formula }; try { if (formula == "") formula = "0"; EvalExpr(formula, formulaResult); Decimal.TryParse(m_strservice, out result); formulaResult.Result = result; } catch (Exception aobjExc) { throw aobjExc; } return formulaResult; }//EvaluationResult #endregion } }
Alpha3
If you're a little shaky on RegEx, I like the site http://www.quanetic.com/Regex. But there are plenty of sites about RegEx out there. In Alpha 3 we replaced most of the string functions with regex statements. This part took me the longest as my regex is not perfect and I wanted to make sure it worked as expected. It was a learning experience.
Alpha3.cs
//Alpha3.cs using System; using System.Collections; using System.Globalization; using System.Collections.Generic; using Garoutte.FormulaEngine; using Garoutte.FormulaEngine.Exceptions; using System.Text.RegularExpressions; namespace Garoutte.FormulaEngine { /// <summary> /// This class is used to convert a string expression to a numeric one, /// to evaluate this numeric expression and to reconvert the result back /// to string /// </summary> public class Alpha3 : IFormulaGenerator { #region constructors /// <summary> /// implicit constructor /// </summary> public Alpha3() { Variables = new Dictionary<String, Decimal>(); }//FormulaGenerator #endregion constructors #region private members private bool ExistParenthesis(string astrexpr) { m_iCharContor = 0; int inbleft = 0; NbOfChar(astrexpr, '('); inbleft = m_iCharContor; m_iCharContor = 0; int inbright = 0; NbOfChar(astrexpr, ')'); inbright = m_iCharContor; if (inbleft != inbright) throw new Exception("Sintax Error!"); if (inbleft == 0) return false; return true; }//ExistParenthesis private int PosMaxLevel(string astrexpr) { int ipos = -1; int ilevel = 0; int maxlevel = LevelOfParenthesis(astrexpr); foreach (char ch in astrexpr.ToCharArray()) { ipos += 1; if (ch == '(') { ilevel += 1; if (ilevel == maxlevel) { return ipos; } } else if (ch == ')') { ilevel -= 1; } } return 0; }//PosMaxLevel private int LevelOfParenthesis(string astrexpr) { int ilevel = 0; int ipos = -1; int maxlevel = 0; foreach (char ch in astrexpr.ToCharArray()) { ipos += 1; if (ch == '(') { ilevel = ilevel + 1; } else if (ch == ')') { ilevel -= 1; } if (maxlevel < ilevel) { maxlevel = ilevel; } } return maxlevel; }//LevelOfParenthesis private int FindLastChar(string astrexpr, char acfinder) { return astrexpr.LastIndexOf(acfinder); }//FindLastChar private int FindFirstChar(string astrexpr, char acfinder) { return astrexpr.IndexOf(acfinder); }//FindFirstChar private int FindSecondChar(string astrexpr, char acfinder) { int ifirst = astrexpr.IndexOf(acfinder); int isecond = astrexpr.Substring(ifirst + 1).IndexOf(acfinder); return ifirst + isecond + 1; }//FindSecondChar private void NbOfChar(string astrexpr, char acfinder) { int i = astrexpr.IndexOf(acfinder); if (i >= 0) { m_iCharContor += 1; NbOfChar(astrexpr.Substring(i + 1), acfinder); } }//NbOfChar private string ProcessPercent(string astrexpr, FormulaResult formulaResult) { m_iCharContor = 0; NbOfChar(astrexpr, '%'); int inbpercent = m_iCharContor; string strnewastrexpr = astrexpr.Trim(); m_iCharContor = 0; NbOfChar(strnewastrexpr, '%'); if (m_iCharContor == 0) { m_strservice = strnewastrexpr; } if (inbpercent > 0) { string astrexprwithoutbreak = astrexpr.Trim(); int ipospercent = FindFirstChar(astrexprwithoutbreak, '%'); string astrwithoutpercent; if (ipospercent >= 1) { astrwithoutpercent = astrexprwithoutbreak.Substring(0, ipospercent); } else { throw new Exception("Illegal % error!"); } int ilastdivision = FindLastChar(astrwithoutpercent, '/'); int ilastmultiply = FindLastChar(astrwithoutpercent, '*'); int ilastminus = FindLastChar(astrwithoutpercent, '-'); int ilastplus = FindLastChar(astrwithoutpercent, '+'); ArrayList arlsigns = new ArrayList(); arlsigns.Add(ilastdivision); arlsigns.Add(ilastmultiply); arlsigns.Add(ilastminus); arlsigns.Add(ilastplus); int imaxsignpos = NbMax(arlsigns); if (imaxsignpos < 0) imaxsignpos = 0; string strpercent; string strbeforepercent; if (imaxsignpos >= 1) { strpercent = astrwithoutpercent.Substring(imaxsignpos + 1).Trim(); strbeforepercent = astrwithoutpercent.Substring(0, imaxsignpos + 1).Trim(); } else { strpercent = astrwithoutpercent.Trim(); strbeforepercent = ""; } string strafterpercent = ""; if (ipospercent < astrexprwithoutbreak.Length - 1) { strafterpercent = astrexprwithoutbreak.Substring(ipospercent + 1).Trim(); } decimal dpercentvalue = ConvertToValue(strpercent, formulaResult, true); strnewastrexpr = strbeforepercent + Convert.ToString(dpercentvalue) + strafterpercent; ProcessPercent(strnewastrexpr, formulaResult); } return m_strservice; }//ProcessPercent private string ProcessDivision(string astrexpr, FormulaResult formulaResult) { m_iCharContor = 0; NbOfChar(astrexpr, '/'); int inbdivision = m_iCharContor; string strnewastrexpr = astrexpr.Trim(); m_iCharContor = 0; NbOfChar(strnewastrexpr, '/'); if (m_iCharContor == 0) { m_strservice = strnewastrexpr; } if (inbdivision > 0) { string astrexprwithoutbreak = astrexpr.Trim(); int iposdivision = FindFirstChar(astrexprwithoutbreak, '/'); string astrbeforedivision; if (iposdivision >= 1) { astrbeforedivision = astrexprwithoutbreak.Substring(0, iposdivision).Trim(); } else { throw new Exception("Division error!"); } string astrafterdivision; if (iposdivision < astrexprwithoutbreak.Length - 1) { astrafterdivision = astrexprwithoutbreak.Substring(iposdivision + 1).Trim(); } else { throw new Exception("Division error!"); } int ilastdivisionbefore = FindLastChar(astrbeforedivision, '/'); int ilastmultiplybefore = FindLastChar(astrbeforedivision, '*'); int ilastminusbefore = FindLastChar(astrbeforedivision, '-'); int ilastplusbefore = FindLastChar(astrbeforedivision, '+'); int ilastdivisionafter = FindFirstChar(astrafterdivision, '/'); int ilastmultiplyafter = FindFirstChar(astrafterdivision, '*'); int ilastminusafter = FindFirstChar(astrafterdivision, '-'); int ilastplusafter = FindFirstChar(astrafterdivision, '+'); ArrayList arlsignsbefore = new ArrayList(); arlsignsbefore.Add(ilastdivisionbefore); arlsignsbefore.Add(ilastmultiplybefore); arlsignsbefore.Add(ilastminusbefore); arlsignsbefore.Add(ilastplusbefore); int imaxsignpos = NbMax(arlsignsbefore); if (imaxsignpos <= 0) imaxsignpos = -1; ArrayList arlsignsafter = new ArrayList(); arlsignsafter.Add(ilastdivisionafter); arlsignsafter.Add(ilastmultiplyafter); arlsignsafter.Add(ilastminusafter); arlsignsafter.Add(ilastplusafter); int iminsignpos = NbMin(arlsignsafter); if (iminsignpos <= 0) iminsignpos = 0; string strbeforedivisionafter = astrbeforedivision.Substring(imaxsignpos + 1).Trim(); string strbeforedivisionbefore = astrbeforedivision.Substring(0, imaxsignpos + 1).Trim(); string strafterdivisionbefore = astrafterdivision.Substring(0, iminsignpos).Trim(); string strafterdivisionafter = astrafterdivision.Substring(iminsignpos).Trim(); bool issignbefore = false; if (strbeforedivisionbefore.Length > 0) { if (strbeforedivisionbefore.Substring(0, 1).Trim() == "-") { issignbefore = true; } } bool issignafter = false; if (strafterdivisionbefore.Length > 0) { if (strafterdivisionbefore.Substring(0, 1).Trim() == "-") { issignafter = true; } } decimal dbeforedivision; if (strbeforedivisionafter.Length > 0) { dbeforedivision = Convert.ToDecimal(SetDecimalSeparator(strbeforedivisionafter)); } else if (strbeforedivisionbefore.Length > 0) { dbeforedivision = Convert.ToDecimal(SetDecimalSeparator(strbeforedivisionbefore)); } else dbeforedivision = 0; decimal dafterdivision; if (strafterdivisionbefore.Length > 0) { dafterdivision = Convert.ToDecimal(SetDecimalSeparator(strafterdivisionbefore)); } else if (strafterdivisionafter.Length > 0) { dafterdivision = Convert.ToDecimal(SetDecimalSeparator(strafterdivisionafter)); } else dafterdivision = 1; decimal ddivision = 0; if (dafterdivision != 0) { if (issignbefore || issignafter) { ddivision = (-1) * dbeforedivision / dafterdivision; } else ddivision = dbeforedivision / dafterdivision; } strnewastrexpr = strbeforedivisionbefore + Convert.ToString(ddivision) + strafterdivisionafter; if (strafterdivisionbefore == "") strnewastrexpr = strbeforedivisionbefore + Convert.ToString(ddivision); ProcessDivision(strnewastrexpr, formulaResult); } return m_strservice; }//ProcessDivision private string ProcessMultiply(string astrexpr, FormulaResult formulaResult) { m_iCharContor = 0; NbOfChar(astrexpr, '*'); int inbmultiply = m_iCharContor; string strnewastrexpr = astrexpr.Trim(); m_iCharContor = 0; NbOfChar(strnewastrexpr, '*'); if (m_iCharContor == 0) { m_strservice = strnewastrexpr; } if (inbmultiply > 0) { string astrexprwithoutbreak = astrexpr.Trim(); int iposmultiply = FindFirstChar(astrexprwithoutbreak, '*'); string astrbeforemultiply; if (iposmultiply >= 1) { astrbeforemultiply = astrexprwithoutbreak.Substring(0, iposmultiply).Trim(); } else { throw new Exception("Error!"); } string astraftermultiply; if (iposmultiply < astrexprwithoutbreak.Length - 1) { astraftermultiply = astrexprwithoutbreak.Substring(iposmultiply + 1).Trim(); } else { throw new Exception("Error!"); } int ilastdivisionbefore = FindLastChar(astrbeforemultiply, '/'); int ilastmultiplybefore = FindLastChar(astrbeforemultiply, '*'); int ilastminusbefore = FindLastChar(astrbeforemultiply, '-'); int ilastplusbefore = FindLastChar(astrbeforemultiply, '+'); int ilastdivisionafter = FindFirstChar(astraftermultiply, '/'); int ilastmultiplyafter = FindFirstChar(astraftermultiply, '*'); int ilastminusafter = FindFirstChar(astraftermultiply, '-'); int ilastplusafter = FindFirstChar(astraftermultiply, '+'); ArrayList arlsignsbefore = new ArrayList(); arlsignsbefore.Add(ilastdivisionbefore); arlsignsbefore.Add(ilastmultiplybefore); arlsignsbefore.Add(ilastminusbefore); arlsignsbefore.Add(ilastplusbefore); int imaxsignpos = NbMax(arlsignsbefore); if (imaxsignpos <= 0) imaxsignpos = -1; ArrayList arlsignsafter = new ArrayList(); arlsignsafter.Add(ilastdivisionafter); arlsignsafter.Add(ilastmultiplyafter); arlsignsafter.Add(ilastminusafter); arlsignsafter.Add(ilastplusafter); int iminsignpos = NbMin(arlsignsafter); if (iminsignpos <= 0) iminsignpos = 0; string strbeforemultiplyafter = astrbeforemultiply.Substring(imaxsignpos + 1).Trim(); string strbeforemultiplybefore = astrbeforemultiply.Substring(0, imaxsignpos + 1).Trim(); string straftermultiplybefore = astraftermultiply.Substring(0, iminsignpos).Trim(); string straftermultiplyafter = astraftermultiply.Substring(iminsignpos).Trim(); bool issignbefore = false; if (strbeforemultiplybefore.Length > 0) { if (strbeforemultiplybefore.Substring(0, 1).Trim() == "-") { //issignbefore = true; } } bool issignafter = false; if (straftermultiplybefore.Length > 0) { if (straftermultiplybefore.Substring(0, 1).Trim() == "-") { issignafter = true; } } decimal dbeforemultiply; if (strbeforemultiplyafter.Length > 0) { if (IsDigit(strbeforemultiplyafter) == true) { dbeforemultiply = Convert.ToDecimal(SetDecimalSeparator(strbeforemultiplyafter)); } else { dbeforemultiply = ConvertToValue(strbeforemultiplyafter, formulaResult); } } else if (strbeforemultiplybefore.Length > 0) { if (IsDigit(strbeforemultiplybefore) == true) { dbeforemultiply = Convert.ToDecimal(SetDecimalSeparator(strbeforemultiplybefore)); } else { dbeforemultiply = ConvertToValue(strbeforemultiplybefore, formulaResult); } } else dbeforemultiply = 0; decimal daftermultiply; if (straftermultiplybefore.Length > 0) { if (IsDigit(straftermultiplybefore) == true) { daftermultiply = Convert.ToDecimal(SetDecimalSeparator(straftermultiplybefore)); } else { daftermultiply = ConvertToValue(straftermultiplybefore, formulaResult); } } else if (straftermultiplyafter.Length > 0) { if (IsDigit(straftermultiplyafter) == true) { daftermultiply = Convert.ToDecimal(SetDecimalSeparator(straftermultiplyafter)); } else { daftermultiply = ConvertToValue(straftermultiplyafter, formulaResult); } } else daftermultiply = 0; decimal dmultiply = 0; if (daftermultiply != 0) { if (issignbefore || issignafter) { dmultiply = (-1) * dbeforemultiply * daftermultiply; } else dmultiply = dbeforemultiply * daftermultiply; } strnewastrexpr = strbeforemultiplybefore + Convert.ToString(dmultiply) + straftermultiplyafter; if (straftermultiplybefore == "") strnewastrexpr = strbeforemultiplybefore + Convert.ToString(dmultiply); ProcessMultiply(strnewastrexpr, formulaResult); } return m_strservice; }//ProcessMultiply private string ProcessMinus(string astrexpr, FormulaResult formulaResult) { m_iCharContor = 0; NbOfChar(astrexpr, '-'); int iposminustest = FindFirstChar(astrexpr.Trim(), '-'); //if(iposminustest == 0) iposminustest = this.FindSecondChar(astrexpr.Trim(),'-'); string strnewastrexpr = astrexpr.Trim(); m_iCharContor = 0; NbOfChar(strnewastrexpr, '-'); if (iposminustest == 0) m_iCharContor -= 1; int inbminus = m_iCharContor; if (m_iCharContor == 0) { m_strservice = strnewastrexpr; } if (inbminus > 0) { string astrexprwithoutbreak = astrexpr.Trim(); //if(iposminustest == 0) astrexprwithoutbreak = astrexprwithoutbreak.Substring(1); int iposminus = FindFirstChar(astrexprwithoutbreak, '-'); if (iposminus == 0) iposminus = FindSecondChar(astrexprwithoutbreak, '-'); string astrbeforeminus; if (iposminus >= 1) { astrbeforeminus = astrexprwithoutbreak.Substring(0, iposminus).Trim(); } else { astrbeforeminus = astrexprwithoutbreak.Substring(0, iposminus).Trim(); } string astrafterminus; if (iposminus < astrexprwithoutbreak.Length - 1) { astrafterminus = astrexprwithoutbreak.Substring(iposminus + 1).Trim(); //if(iposminustest == 0)astrafterminus = astrafterminus.Substring(1).Trim(); } else { throw new Exception("Error!"); } int ilastdivisionbefore = FindLastChar(astrbeforeminus, '/'); int ilastmultiplybefore = FindLastChar(astrbeforeminus, '*'); int ilastminusbefore = FindLastChar(astrbeforeminus, '-'); int ilastplusbefore = FindLastChar(astrbeforeminus, '+'); int ilastdivisionafter = FindFirstChar(astrafterminus, '/'); int ilastmultiplyafter = FindFirstChar(astrafterminus, '*'); int ilastminusafter = FindFirstChar(astrafterminus, '-'); int ilastplusafter = FindFirstChar(astrafterminus, '+'); ArrayList arlsignsbefore = new ArrayList(); arlsignsbefore.Add(ilastdivisionbefore); arlsignsbefore.Add(ilastmultiplybefore); arlsignsbefore.Add(ilastminusbefore); arlsignsbefore.Add(ilastplusbefore); int imaxsignpos = NbMax(arlsignsbefore); if (imaxsignpos <= 0) imaxsignpos = -1; ArrayList arlsignsafter = new ArrayList(); arlsignsafter.Add(ilastdivisionafter); arlsignsafter.Add(ilastmultiplyafter); arlsignsafter.Add(ilastminusafter); arlsignsafter.Add(ilastplusafter); int iminsignpos = NbMin(arlsignsafter); if (iminsignpos <= 0) iminsignpos = 0; string strbeforeminusafter = astrbeforeminus.Substring(imaxsignpos + 1).Trim(); string strbeforeminusbefore = astrbeforeminus.Substring(0, imaxsignpos + 1).Trim(); string strafterminusbefore = astrafterminus.Substring(0, iminsignpos).Trim(); string strafterminusafter = astrafterminus.Substring(iminsignpos).Trim(); decimal dbeforeminus = 0; if (strbeforeminusafter.Length > 0) { if (IsDigit(strbeforeminusafter) == true) { dbeforeminus = Convert.ToDecimal(SetDecimalSeparator(strbeforeminusafter)); } else { dbeforeminus = ConvertToValue(strbeforeminusafter, formulaResult); } } else if (strbeforeminusbefore.Length > 0) { if (IsDigit(strbeforeminusbefore) == true) { dbeforeminus = Convert.ToDecimal(SetDecimalSeparator(strbeforeminusbefore)); } else { dbeforeminus = ConvertToValue(strbeforeminusbefore, formulaResult); } } decimal dafterminus = 0; if (strafterminusbefore.Length > 0) { if (IsDigit(strafterminusbefore)) { dafterminus = Convert.ToDecimal(SetDecimalSeparator(strafterminusbefore)); } else { dafterminus = ConvertToValue(strafterminusbefore, formulaResult); } } else if (strafterminusafter.Length > 0) { if (IsDigit(strafterminusafter)) { dafterminus = Convert.ToDecimal(SetDecimalSeparator(strafterminusafter)); } else { dafterminus = ConvertToValue(strafterminusafter, formulaResult); } } decimal dminus = 0; dminus = dbeforeminus - dafterminus; strnewastrexpr = strbeforeminusbefore + Convert.ToString(dminus) + strafterminusafter; if (strafterminusbefore == "") strnewastrexpr = strbeforeminusbefore + Convert.ToString(dminus); ProcessMinus(strnewastrexpr, formulaResult); } return m_strservice; }//ProcessMinus private string ProcessPlus(string astrexpr, FormulaResult formulaResult) { m_iCharContor = 0; NbOfChar(astrexpr, '+'); int inbplus = m_iCharContor; string strnewastrexpr = astrexpr.Trim(); m_iCharContor = 0; NbOfChar(strnewastrexpr, '+'); if (m_iCharContor == 0) { m_strservice = strnewastrexpr; } if (inbplus > 0) { string astrexprwithoutbreak = astrexpr.Trim(); int iposplus = FindFirstChar(astrexprwithoutbreak, '+'); string astrbeforeplus; if (iposplus >= 1) { astrbeforeplus = astrexprwithoutbreak.Substring(0, iposplus).Trim(); } else { throw new Exception("Error!"); } string astrafterplus; if (iposplus < astrexprwithoutbreak.Length - 1) { astrafterplus = astrexprwithoutbreak.Substring(iposplus + 1).Trim(); } else { throw new Exception("Error!"); } int ilastdivisionbefore = FindLastChar(astrbeforeplus, '/'); int ilastmultiplybefore = FindLastChar(astrbeforeplus, '*'); int ilastminusbefore = FindLastChar(astrbeforeplus, '-'); int ilastplusbefore = FindLastChar(astrbeforeplus, '+'); int ilastdivisionafter = FindFirstChar(astrafterplus, '/'); int ilastmultiplyafter = FindFirstChar(astrafterplus, '*'); int ilastminusafter = FindFirstChar(astrafterplus, '-'); int ilastplusafter = FindFirstChar(astrafterplus, '+'); ArrayList arlsignsbefore = new ArrayList(); arlsignsbefore.Add(ilastdivisionbefore); arlsignsbefore.Add(ilastmultiplybefore); arlsignsbefore.Add(ilastminusbefore); arlsignsbefore.Add(ilastplusbefore); int imaxsignpos = NbMax(arlsignsbefore); if (imaxsignpos <= 0) imaxsignpos = -1; ArrayList arlsignsafter = new ArrayList(); arlsignsafter.Add(ilastdivisionafter); arlsignsafter.Add(ilastmultiplyafter); arlsignsafter.Add(ilastminusafter); arlsignsafter.Add(ilastplusafter); int iminsignpos = NbMin(arlsignsafter); if (iminsignpos <= 0) iminsignpos = 0; string strbeforeplusafter = astrbeforeplus.Substring(imaxsignpos + 1).Trim(); string strbeforeplusbefore = astrbeforeplus.Substring(0, imaxsignpos + 1).Trim(); string strafterplusbefore = astrafterplus.Substring(0, iminsignpos).Trim(); string strafterplusafter = astrafterplus.Substring(iminsignpos).Trim(); decimal dbeforeplus = 0; if (strbeforeplusafter.Length > 0) { if (IsDigit(strbeforeplusafter) == true) { dbeforeplus = Convert.ToDecimal(SetDecimalSeparator(strbeforeplusafter)); } else { dbeforeplus = ConvertToValue(strbeforeplusafter, formulaResult); } } else if (strbeforeplusbefore.Length > 0) { if (IsDigit(strbeforeplusbefore) == true) { dbeforeplus = Convert.ToDecimal(SetDecimalSeparator(strbeforeplusbefore)); } else { dbeforeplus = ConvertToValue(strbeforeplusbefore, formulaResult); } } decimal dafterplus = 0; if (strafterplusbefore.Length > 0) { if (IsDigit(strafterplusbefore) == true) { dafterplus = Convert.ToDecimal(SetDecimalSeparator(strafterplusbefore)); } else { dafterplus = ConvertToValue(strafterplusbefore, formulaResult); } } else if (strafterplusafter.Length > 0) { if (IsDigit(strafterplusafter) == true) { dafterplus = Convert.ToDecimal(SetDecimalSeparator(strafterplusafter)); } else { dafterplus = ConvertToValue(strafterplusafter, formulaResult); } } decimal dplus = 0; dplus = dbeforeplus + dafterplus; strnewastrexpr = strbeforeplusbefore + Convert.ToString(dplus) + strafterplusafter; if (strafterplusbefore == "") strnewastrexpr = strbeforeplusbefore + Convert.ToString(dplus); ProcessPlus(strnewastrexpr, formulaResult); } else { if (IsDigit(astrexpr) == false) { ConvertToValue(astrexpr, formulaResult); } } return m_strservice; }//ProcessPlus #region ConvertToValue private decimal ConvertToValue(string expr, FormulaResult formulaResult) { return ConvertToValue(expr, formulaResult, false); } private decimal ConvertToValue(string expr, FormulaResult formulaResult, bool isAPercentConversion) { bool bmarker = true; decimal dexpr = 0; //for each character for (int i = 0; i <= expr.Length - 1; i++) { //get the character char theChar = expr[i]; if ((i != 0) || (theChar != '-')) { if ((!Char.IsDigit(theChar)) && (theChar != '.') && (theChar != ',')) { bmarker = false; } } } if ((bmarker == true) && (isAPercentConversion)) { dexpr = Convert.ToDecimal(expr) / 100; } else { //unknown expr here if (Variables.ContainsKey(expr)) { dexpr = Variables[expr]; if (!formulaResult.Variables.ContainsKey(expr)) formulaResult.Variables.Add(expr, Variables[expr]); } else throw new UndefineExpressionException(expr); } return dexpr; }//ConvertToValue #endregion ConvertToValue private int NbMax(ArrayList aobjarl) { int max = 0; foreach (object obj in aobjarl) { if ((int)obj != -1) { if (max <= (int)obj) max = (int)obj; } } return max; }//NbMax private int NbMin(ArrayList aobjarl) { int min = NbMax(aobjarl); foreach (object obj in aobjarl) { if ((int)obj != -1) { if (min >= (int)obj) min = (int)obj; } } return min; }//NbMin private bool IsDigit(string astrexpr) { foreach (char ch in astrexpr.ToCharArray()) { if ((ch != '-') && (ch != ' ') && (ch != '.') && (ch != ',')) { if (Char.IsDigit(ch) == false) { return false; } } } return true; }//IsDigit private string SetDecimalSeparator(string astrexpr) { string strseparator = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator; int ipoint = astrexpr.IndexOf('.'); int icomma = astrexpr.IndexOf(','); string newastrexpr; if (ipoint >= 0) { newastrexpr = astrexpr.Replace(".", strseparator); } else if (icomma >= 0) { newastrexpr = astrexpr.Replace(",", strseparator); } else { newastrexpr = astrexpr; } return newastrexpr; }//SetDecimalSeparator private int m_iCharContor = 0; private string m_strservice = ""; #endregion private members #region IFormulaGenerator Members public string GetStringResult(string formula) { IFormulaResult result = ResolveFormula(formula); return result.Result.ToString(); } public void SetVariable(String name, Decimal value) { if (Variables.ContainsKey(name)) Variables[name] = value; else Variables.Add(name, value); } public IFormulaResult ResolveFormula(string formula) { if (String.IsNullOrEmpty(formula)) formula = "0"; FormulaResult formulaResult = new FormulaResult() { Formula = formula }; try { EvalExpr(formula, formulaResult); Decimal result = 0m; Decimal.TryParse(m_strservice, out result); formulaResult.Result = result; } catch (Exception aobjExc) { throw aobjExc; } return formulaResult; }//EvaluationResult #endregion #region private fields private Dictionary<String, Decimal> Variables { get; set; } #endregion private fields #region private calculation methods /// <summary> /// Evaluates the expression reducing the parenthesis in the formula. /// </summary> /// <remarks>this alghoritm uses regex to find and resolve the parenthesis starting at the inner more parenthesis and working it's way out /// It also handles several groups of parenthesis and will replace any parenthesis that match, for example /// (5 + 1) * (5 + 1) would become 6 * 6 on the first pass /// ((5 + 1) * 6) + (5 + 1) would become (6 * 6) + 6 on the first pass /// </remarks> /// <param name="expr">The expr.</param> /// <param name="formulaResult">The formula result.</param> /// <returns></returns> private String EvalExpr(string expr, FormulaResult formulaResult) { formulaResult.Proof.Add(expr); string result = String.Empty; Regex regEx = new Regex(@"\([^\(|\)]*\)"); MatchCollection collection = regEx.Matches(expr); if (collection.Count == 0) { result = EvalAritm(expr.Trim(), formulaResult); formulaResult.Proof.Add(result); } else { Match match = collection[0]; string temp = match.Value.TrimStart('(').TrimEnd(')'); result = EvalAritm(temp.Trim(), formulaResult); temp = expr.Replace(match.Value, result); EvalExpr(temp, formulaResult); } return result; }//EvalExpr /// <summary> /// Evaluates the aritmathtic (DMSA). /// </summary> /// <param name="expr">The expr.</param> /// <param name="formulaResult">The formula result.</param> /// <returns></returns> private string EvalAritm(string expr, FormulaResult formulaResult) { string result = expr; result = ProcessPercent(result, formulaResult); result = ProcessDivision(result, formulaResult); result = ProcessMultiply(result, formulaResult); result = ProcessMinus(result, formulaResult); result = ProcessPlus(result, formulaResult); return result; }//EvalAritm #endregion private calculation methods } }
Beta1
Next I refactored out the math processing and did some cleaned up the code a bit. With this rewrite the code went from the original 920 lines of code to 487 lines and added a basic proof system and variable support. the process division, multiply, minus and plus all have been reworked to use the same code and a lambda.
//beta1.cs using System; using System.Collections; using System.Globalization; using System.Collections.Generic; using Garoutte.FormulaEngine; using Garoutte.FormulaEngine.Exceptions; using System.Text.RegularExpressions; using System.Linq; namespace Garoutte.FormulaEngine { /// <summary> /// This class is used to convert a string expression to a numeric one, /// to evaluate this numeric expression and to reconvert the result back /// to string /// </summary> public class Beta1 : IFormulaGenerator { #region constructors /// <summary> /// implicit constructor /// </summary> public Beta1() { Variables = new Dictionary<String, Decimal>(); }//FormulaGenerator #endregion constructors #region private members private int FindLastChar(string astrexpr, char acfinder) { return astrexpr.LastIndexOf(acfinder); }//FindLastChar private int FindFirstChar(string astrexpr, char acfinder) { return astrexpr.IndexOf(acfinder); }//FindFirstChar private int FindSecondChar(string astrexpr, char acfinder) { int ifirst = astrexpr.IndexOf(acfinder); int isecond = astrexpr.Substring(ifirst + 1).IndexOf(acfinder); return ifirst + isecond + 1; }//FindSecondChar #region ConvertToValue private decimal ConvertToValue(string expr, FormulaResult formulaResult) { return ConvertToValue(expr, formulaResult, false); } private decimal ConvertToValue(string expr, FormulaResult formulaResult, bool isAPercentConversion) { bool bmarker = true; decimal dexpr = 0; //for each character for (int i = 0; i <= expr.Length - 1; i++) { //get the character char theChar = expr[i]; if ((i != 0) || (theChar != '-')) { if ((!Char.IsDigit(theChar)) && (theChar != '.') && (theChar != ',')) { bmarker = false; } } } if ((bmarker == true) && (isAPercentConversion)) { dexpr = Convert.ToDecimal(expr) / 100; } else { //unknown expr here if (Variables.ContainsKey(expr)) { dexpr = Variables[expr]; if (!formulaResult.Variables.ContainsKey(expr)) formulaResult.Variables.Add(expr, Variables[expr]); } else throw new UndefineExpressionException(expr); } return dexpr; }//ConvertToValue #endregion ConvertToValue private bool IsDigit(string astrexpr) { foreach (char ch in astrexpr.ToCharArray()) { if ((ch != '-') && (ch != ' ') && (ch != '.') && (ch != ',')) { if (Char.IsDigit(ch) == false) { return false; } } } return true; }//IsDigit private string SetDecimalSeparator(string astrexpr) { string strseparator = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator; int ipoint = astrexpr.IndexOf('.'); int icomma = astrexpr.IndexOf(','); string newastrexpr; if (ipoint >= 0) { newastrexpr = astrexpr.Replace(".", strseparator); } else if (icomma >= 0) { newastrexpr = astrexpr.Replace(",", strseparator); } else { newastrexpr = astrexpr; } return newastrexpr; }//SetDecimalSeparator // private string m_strservice = ""; #endregion private members #region IFormulaGenerator Members public string GetStringResult(string formula) { IFormulaResult result = ResolveFormula(formula); return result.Result.ToString(); } public void SetVariable(String name, Decimal value) { if (Variables.ContainsKey(name)) Variables[name] = value; else Variables.Add(name, value); } public IFormulaResult ResolveFormula(string formula) { if (String.IsNullOrEmpty(formula)) formula = "0"; FormulaResult formulaResult = new FormulaResult() { Formula = formula }; AddProof(formula, formulaResult); try { Decimal result = 0m; string temp = EvalExpr(formula, formulaResult); if (Decimal.TryParse(temp, out result)) { formulaResult.Result = result; formulaResult.Proof.Add(temp); } else throw new ApplicationException(temp); } catch (Exception) { throw; } return formulaResult; }//EvaluationResult #endregion #region private fields private Dictionary<String, Decimal> Variables { get; set; } #endregion private fields #region private calculation methods /// <summary> /// Evaluates the expression reducing the parenthesis in the formula. /// </summary> /// <remarks>this alghoritm uses regex to find and resolve the parenthesis starting at the inner more parenthesis and working it's way out /// It also handles several groups of parenthesis and will replace any parenthesis that match, for example /// (5 + 1) * (5 + 1) would become 6 * 6 on the first pass /// ((5 + 1) * 6) + (5 + 1) would become (6 * 6) + 6 on the first pass /// </remarks> /// <param name="expr">The expr.</param> /// <param name="formulaResult">The formula result.</param> /// <returns></returns> private String EvalExpr(string expr, FormulaResult formulaResult) { AddProof(expr, formulaResult); string result = String.Empty; Regex regEx = new Regex(@"\([^\(|\)]*\)"); MatchCollection collection = regEx.Matches(expr); if (collection.Count == 0) { result = EvalAritm(expr.Trim(), formulaResult); } else { Match match = collection[0]; string temp = match.Value.TrimStart('(').TrimEnd(')'); result = EvalAritm(temp.Trim(), formulaResult); temp = expr.Replace(match.Value, result); result = EvalExpr(temp, formulaResult); } AddProof(result, formulaResult); return result; }//EvalExpr private Int32 CountChararacterOccurancesInExpression(string expression, char charToCount) { Int32 result = Regex.Matches(expression, Regex.Escape(charToCount.ToString())).Count; return result; }//CountChararacterOccurancesInExpression private string ProcessPercent(string expr, FormulaResult formulaResult) { AddProof(expr, formulaResult); string trimmedExpr = expr.Trim(); string result = expr; if (CountChararacterOccurancesInExpression(trimmedExpr, '%') == 0) { result = trimmedExpr; } else if (CountChararacterOccurancesInExpression(expr, '%') > 0) { int percentIndex = FindFirstChar(trimmedExpr, '%'); string beforeOp = string.Empty; if (percentIndex >= 1) { beforeOp = trimmedExpr.Substring(0, percentIndex); } else { throw new Exception("Illegal % error!"); } List<Int32> arlsignsbefore = new List<Int32>(); arlsignsbefore.Add(FindLastChar(beforeOp, '/')); arlsignsbefore.Add(FindLastChar(beforeOp, '*')); arlsignsbefore.Add(FindLastChar(beforeOp, '-')); arlsignsbefore.Add(FindLastChar(beforeOp, '+')); int maxSignIndex = arlsignsbefore.Max(); if (maxSignIndex <= 0) maxSignIndex = 0; string strpercent; string strbeforepercent; if (maxSignIndex >= 1) { strpercent = beforeOp.Substring(maxSignIndex + 1).Trim(); strbeforepercent = beforeOp.Substring(0, maxSignIndex + 1).Trim(); } else { strpercent = beforeOp.Trim(); strbeforepercent = ""; } string strafterpercent = ""; if (percentIndex < trimmedExpr.Length - 1) { strafterpercent = trimmedExpr.Substring(percentIndex + 1).Trim(); } decimal percentValue = ConvertToValue(strpercent, formulaResult, true); trimmedExpr = String.Format("{0}{1}{2}", strbeforepercent, percentValue, strafterpercent); result = ProcessPercent(trimmedExpr, formulaResult); } return result; }//ProcessPercent private string ProcessDivision(string expr, FormulaResult formulaResult) { return ProcessOperator(expr, '/', formulaResult, ((left, right) => left / right )); //return ProcessOperator(expr, '/', formulaResult, delegate(decimal left, decimal right) { return left / right; }); }//ProcessDivision private string ProcessOperator(string expr, char theOperator, FormulaResult formulaResult, Func<decimal, decimal, decimal> math) { AddProof(expr, formulaResult); string result = String.Empty; int count = CountChararacterOccurancesInExpression(expr, theOperator); string trimmedExpr = expr.Trim(); if (CountChararacterOccurancesInExpression(trimmedExpr, theOperator) == 0) { result = trimmedExpr; } if (count > 0) { string exprTrimmedNoBreak = expr.Trim(); int indexFirstOP = FindFirstChar(exprTrimmedNoBreak, theOperator); if (indexFirstOP == 0) indexFirstOP = FindSecondChar(exprTrimmedNoBreak, '-'); string beforeOp; if (indexFirstOP >= 1) { beforeOp = exprTrimmedNoBreak.Substring(0, indexFirstOP).Trim(); } else { switch (theOperator) { case '+': case '*': case '/': throw new Exception(theOperator + " Error!"); default: break; } beforeOp = exprTrimmedNoBreak.Substring(0, indexFirstOP).Trim(); } string afterOP; if (indexFirstOP < exprTrimmedNoBreak.Length - 1) { afterOP = exprTrimmedNoBreak.Substring(indexFirstOP + 1).Trim(); } else { throw new Exception(theOperator + " Error!"); } List<Int32> arlsignsbefore = new List<Int32>(); arlsignsbefore.Add(FindLastChar(beforeOp, '/')); arlsignsbefore.Add(FindLastChar(beforeOp, '*')); arlsignsbefore.Add(FindLastChar(beforeOp, '-')); arlsignsbefore.Add(FindLastChar(beforeOp, '+')); int maxSignIndex = arlsignsbefore.Max(); if (maxSignIndex <= 0) maxSignIndex = -1; List<Int32> arlsignsafter = new List<Int32>(); arlsignsafter.Add(FindFirstChar(afterOP, '/')); arlsignsafter.Add(FindFirstChar(afterOP, '*')); arlsignsafter.Add(FindFirstChar(afterOP, '-')); arlsignsafter.Add(FindFirstChar(afterOP, '+')); int minSignIndex = arlsignsafter.Min(); if (minSignIndex <= 0) minSignIndex = 0; string beforeOpAfter = beforeOp.Substring(maxSignIndex + 1).Trim(); string BeforeOpBefore = beforeOp.Substring(0, maxSignIndex + 1).Trim(); string afterOpBefore = afterOP.Substring(0, minSignIndex).Trim(); string afterOpAfter = afterOP.Substring(minSignIndex).Trim(); bool issignbefore = false; bool issignafter = false; decimal leftExpr = SeperateValueFromExpression(formulaResult, beforeOpAfter, BeforeOpBefore, theOperator); decimal rightExpr = SeperateValueFromExpression(formulaResult, afterOpBefore, afterOpAfter, theOperator); switch (theOperator) { case '/': if (rightExpr == 0) rightExpr = 1; if (BeforeOpBefore.Length > 0 && BeforeOpBefore.Substring(0, 1).Trim() == "-") issignbefore = true; goto case '*'; case '*': if (afterOpBefore.Length > 0 && afterOpBefore.Substring(0, 1).Trim() == "-") issignafter = true; break; default: break; } if (issignafter && rightExpr != 0) rightExpr *= -1; if (issignbefore && leftExpr != 0) leftExpr *= -1; result = math.Invoke(leftExpr, rightExpr).ToString(); if (String.IsNullOrEmpty(afterOpBefore)) trimmedExpr = BeforeOpBefore + result; else trimmedExpr = BeforeOpBefore + Convert.ToString(result) + afterOpAfter; result = ProcessOperator(trimmedExpr, theOperator, formulaResult, math); } return result.ToString(); } private string ProcessMinus(string expr, FormulaResult formulaResult) { return ProcessOperator(expr, '-', formulaResult, ((left, right) => left - right)); }//ProcessMinus private string ProcessPlus(string expr, FormulaResult formulaResult) { return ProcessOperator(expr, '+', formulaResult, ((left, right) => left + right)); }//ProcessPlus private string ProcessMultiply(string expr, FormulaResult formulaResult) { return ProcessOperator(expr, '*', formulaResult, ((left, right) => left * right)); }//ProcessMultiply private decimal SeperateValueFromExpression(FormulaResult formulaResult, string strbeforeplusafter, string strbeforeplusbefore, char theOperator) { decimal result = 0; if (strbeforeplusafter.Length > 0) { if (IsDigit(strbeforeplusafter) == true) { result = Convert.ToDecimal(SetDecimalSeparator(strbeforeplusafter)); } else { result = ConvertToValue(strbeforeplusafter, formulaResult); } } else if (strbeforeplusbefore.Length > 0) { if (IsDigit(strbeforeplusbefore) == true) { result = Convert.ToDecimal(SetDecimalSeparator(strbeforeplusbefore)); } else { result = ConvertToValue(strbeforeplusbefore, formulaResult); } } else if (theOperator == '*') result = 0; else if (theOperator == '/') result = 0; return result; } /// <summary> /// Evaluates the aritmathtic (DMSA). /// </summary> /// <param name="expr">The expr.</param> /// <param name="formulaResult">The formula result.</param> /// <returns></returns> private string EvalAritm(string expr, FormulaResult formulaResult) { string result = expr; result = ProcessPercent(result, formulaResult); result = ProcessDivision(result, formulaResult); result = ProcessMultiply(result, formulaResult); result = ProcessMinus(result, formulaResult); result = ProcessPlus(result, formulaResult); if (!IsDigit(result)) { result = ConvertToValue(result, formulaResult).ToString(); } return result; }//EvalAritm #endregion private calculation methods private void AddProof(string proof, FormulaResult formulaResult) { bool needsToAdd = true; if (formulaResult.Proof.Count != 0) { if (formulaResult.Proof.Last().TrimStart('(').TrimEnd(')') == proof.TrimStart('(').TrimEnd(')')) needsToAdd = false; } string mathsymbols = "+-/*()%"; if ((proof.ToCharArray().Any(c => mathsymbols.Contains(c))) && needsToAdd) formulaResult.Proof.Add(proof); } } }
And that's where I'm stopping. There are a few things that the code could still stand to have done to it. There are switch statements in the ProcessOperator method that could stand to be factored out (maybe with classes that handle the operators). The ProcessPercent method is wrong... well it's right but it is wrong. The % symbol has a special meaning according to computers and math where it gets the remainder from division, 6 % 5= 1. The way this the ProcessPercent handles it is 50%=.5 which is not unlike an Excel spreadsheet. There is still some string index/substring code happening and a few other tiny annoyances. But it's better. I created a new class library and moved beta1 into it as FormulaGenerator. I also copied the interfaces, support classes and exception over so the library is self contained. Making the FormulaResults class internal at this point would tidy up the library (remember the class returns an IFormulaResult not the concrete class).
It is far from perfect, and there is plenty of room for improvement. However, it now does what I need it to do in a thread safe manner.
Tiberiu Ionescu thank you for the code.
Happy coding,
-Jeff