|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
IntroductionA command line calculator which supports mathematical expression with scientific functions is vary useful for most of the developer. The calculator available with windows does not support most of the scientific functions. Most of the time, I could not feel comfortable with the calculator available with windows. I needed a calculator which will not restrict to write my expression. I may use some variables to store my result. Every time I needed simple calculation I had to face some problem with the windows calculator. To design such a calculator I designed a complete Mathematics library with MFC. The most difficult part I found to design such a calculator is the parsing logic. Later while working with .Net, runtime source code compilation makes the parsing logic very easier and interesting. I read some article on .Net CodeDom compilation. So, I decided to write a new command line calculator using CodeDom. It uses runtime compilation and saves the variables by serializing in a file. So, you can get the values of all the variables used in previous calculation.
Description
In this command line calculator the result is saved in a pre-defined variable called ans. User can declear his/her own variables to store result and can use later in different expression. The validation of variable name is same as C#. Similarly expression support is same as supported in C#.Net.
Calculate function calculates an expression. It uses the saved variables. I have generated a code which has the declaration of the variables.
How to use command line calculator
C:\Documents and Settings\hasan.masud>calculate 25*(3+5-(10/2)) Note: By default the result is saved in a pre defined variable called ans. C:\Documents and Settings\hasan.masud>calculate ans+10 Note: ans variable can be accessed withing the expression as the other variables does. C:\Documents and Settings\hasan.masud>calculate d=75/15+2 Note: user can declare a new variable by assigning some expression to it. If the variable doesn't exist then it will automatically create a new variable otherwise it will overwrite it's value. C:\Documents and Settings\hasan.masud>calculate e=ans+d C:\Documents and Settings\hasan.masud>calculate +10 Note: If the user want's to use the ans variable at the begining of the expression then it is not mandatory to write ans. Just start the expression it will add the ans variable at the begining. C:\Documents and Settings\hasan.masud>calculate f=+10+e C:\Documents and Settings\hasan.masud>calculate s=Math.Sin(90) Note: User can use any functions available in Math class. C:\Documents and Settings\hasan.masud>calculate angle=90 C:\Documents and Settings\hasan.masud>calculate s1=Math.Sin(angle) C:\Documents and Settings\hasan.masud>calculate list Note: User can view the list of variables stored. C:\Documents and Settings\hasan.masud>calculate clear Note: user can clear the variable lists. C:\Documents and Settings\hasan.masud>calculate list C:\Documents and Settings\hasan.masud>calculate s1=Math.Sin(an gle) Note: If any error occures then it displays an error message and the list of the variables. C:\Documents and Settings\hasan.masud>calculate r#=25+9 C:\Documents and Settings\hasan.masud>calculate 45=25+9 C:\Documents and Settings\hasan.masud>calculate maxint=int.MaxValue Note: User can use any expression which will be compiled in a single line without any error. If user provides no arguments or invalid arguments then calculate will take the user to calculate console. In that case user does not have to write calculate every time. To exit from the calculate console just write bye in calculate console.
Code Explanation
ExpressionEvaluator class has two static methods. Calculate method accepts the expression as an agrument. Then it generates a class named myclass, which is inherited from the base class Calculate.MyClassBase and override the virtual function eval() to execute the expression.
string src = @"using System;
class myclass:Calculate.MyClassBase {
private double %MT_VARIABLES%;
public myclass()
{
}
public override double eval()
{
return " + expression + @";
}
}";
%MT_VARIABLES% word in the src string variable is then replaced with the declaration of the variable list stored in the Variables class.
string variables="ans=0";
string []keys;
keys=Variables.GetVariables().GetKeys();
if(keys.Length>0)
variables="";
for(int i=0;i<keys.Length;i++)
{
variables=variables+keys[i]+"="+Convert.ToString(Variables.GetVariables()[keys[i]]);
if(i<keys.Length-1)
variables=variables+",";
}
src=src.Replace(@"%MT_VARIABLES%",variables);
The executable of this application is referenced while compiling the generated code.
Microsoft.CSharp.CSharpCodeProvider cp = new Microsoft.CSharp.CSharpCodeProvider();
System.CodeDom.Compiler.ICodeCompiler ic = cp.CreateCompiler();
System.CodeDom.Compiler.CompilerParameters cpar = new System.CodeDom.Compiler.CompilerParameters();
cpar.GenerateInMemory = true;
cpar.GenerateExecutable = false;
cpar.ReferencedAssemblies.Add("system.dll");
cpar.ReferencedAssemblies.Add(Process.GetCurrentProcess().MainModule.FileName);
Once the code to execute the expression it is compiled with the C# compiler. If no error found in the generated source code, Activator class creates an instance of the class myclass.
System.CodeDom.Compiler.CompilerResults cr = ic.CompileAssemblyFromSource(cpar,src);
foreach (System.CodeDom.Compiler.CompilerError ce in cr.Errors)
{
throw new Exception(ce.ErrorText);
}
if (cr.Errors.Count == 0 && cr.CompiledAssembly != null)
{
Type ObjType = cr.CompiledAssembly.GetType("myclass");
try
{
if (ObjType != null)
{
myobj = (MyClassBase)Activator.CreateInstance(ObjType);
}
}
catch (Exception ex)
{
throw ex;
}
return myobj.eval();
}
else
{
throw new Exception("Failed to compile");
}
ExpressionCalculator class has another method which returns true if compilation of the source code is successfull otherwise it returns false. This function is used to validate the variables name in Calculator class.
private bool CheckVariableName(string variableName)
{
string src = @"using System;
class myclass{
private double %MT_VARIABLES%;
public myclass()
{
%MT_VARIABLES%=0.0;
}
}";
src=src.Replace("%MT_VARIABLES%",variableName);
return ExpressionCalculator.IsCompiled(src);
}
Variables class is a serializable collection class, which uses a SortedList to store the variable name and values. It has static method to manage the singletone and to save, load the variables. It uses binary serialization to serialize the SortedList and saves in a file named variables.dat in the application path.
Calculate class has the main entry point. It uses the first argument as an expression or command used in this application. To manage calculate console there is a function name ManageConsole in Calculate Class.
private void ManageConsole()
{
Console.WriteLine("Type bye to exit the calculate console.");
string expression;
while(true)
{
Console.Write("Calculate>");
expression=Console.ReadLine();
if(expression.Trim().ToUpper()=="BYE")
{
return;
}
CalculateExpression(expression);
}
}
CalculateExpression parse the expression and provide fanctionality for the calculator.
It suports only two commands
list: It displays list of the variables with values. It invokes PrintList method of the class Calculator
private void PrintList()
{
string []keys;
double ans;
keys=Variables.GetVariables().GetKeys();
Console.WriteLine(keys.Length + " variables found");
for(int i=0;i<keys.Length;i++)
{
ans=Variables.GetVariables()[keys[i]];
Console.WriteLine(keys[i] + " = " + ans);
}
}
The expression is splitted with '=' character. If '=' character is found then the first part of the splitted string is treated as a variable name and the second part is treated as the expression. If the variable name is available then it checks the validaty and the result is stored with the given variable name. If the variable name is not present then it stores the result in a pre-defined variable named ans. If an operator is found at the first character of the expression then I added the ans variable at the begining of the expression to complete the expression. It saves the user not to write ans again and again if he/she want's to use the ans variable.
string []commands=commandStr.Split(("=").ToCharArray());
if(commands.Length==2)
{
commands[0]=commands[0].Trim();
if(calc.CheckVariableName(commands[0])==false)
{
Console.WriteLine("Syntax Error");
calc.PrintList();
return;
}
commandStr=commands[1];
}
string op=commandStr.Substring(0,1);
if(("+-*/").IndexOf(op)>=0)
commandStr="ans"+commandStr;
ans=ExpressionCalculator.Calculate(commandStr);
if(commands.Length==2)
{
Variables.GetVariables()[commands[0]]=ans;
Console.WriteLine(commands[0] + " = " + ans);
}
else
{
Variables.GetVariables()["ans"]=ans;
Console.WriteLine("ans = " + ans);
}
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||