Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Command Line Calculator

0.00/5 (No votes)
24 Nov 2005 1  
A command line calculator using CodeDOM.

Introduction

A command line calculator which supports mathematical expressions with scientific functions is very useful for most developers. The calculator available with Windows does not support most scientific functions. Most of the time, I do not feel comfortable with the calculator available with Windows. I needed a calculator which will not restrict writing expressions. I use variables to store results. Every time I need a simple calculation, I have to face problems with the Windows calculator. To make such a calculator, I designed a complete Mathematics library with MFC. The most difficult part I found when designing such a calculator was the parsing logic. Later while working with .NET, the runtime source code compilation made the parsing logic easy and interesting. I read some articles on .NET CodeDOM compilation. And I decided to write a new command line calculator using CodeDOM. It uses runtime compilation and saves the variables by serializing in a file. Thus you can get the values of all the variables used in the previous calculation.

Description

In this command line calculator, the result is saved in a pre-defined variable called ans. The user can declare his/her own variables to store results and can use it later in different expressions. The validation of the variable name is the same as in C#. Similarly, expression support is the same as supported in C# .NET.

The Calculate function calculates an expression. It uses the saved variables. I have generated code which has declaration of the variables.

How to use the command line calculator

Sample screenshot

C:\Documents and Settings\hasan.masud>calculate 25*(3+5-(10/2))
ans = 75

Note: By default, the result is saved in a pre-defined variable called ans.

C:\Documents and Settings\hasan.masud>calculate ans+10
ans = 85

Note: The ans variable can be accessed within the expression like other variables.

C:\Documents and Settings\hasan.masud>calculate d=75/15+2
d = 7

Note: The 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 its value.

C:\Documents and Settings\hasan.masud>calculate e=ans+d
e = 92
C:\Documents and Settings\hasan.masud>calculate +10
ans = 95

Note: If the user wants to use the ans variable at the beginning of the expression, then it is not mandatory to write ans. Just start the expression and it will add the ans variable at the beginning.

C:\Documents and Settings\hasan.masud>calculate f=+10+e
f = 197
C:\Documents and Settings\hasan.masud>calculate s=Math.Sin(90)
s = 0.893996663600558

Note: The user can use any function available in the Math class.

C:\Documents and Settings\hasan.masud>calculate angle=90
angle = 90
C:\Documents and Settings\hasan.masud>calculate s1=Math.Sin(angle)
s1 = 0.893996663600558
C:\Documents and Settings\hasan.masud>calculate list
7 variables found
angle = 90
ans = 95
d = 7
e = 92
f = 197
s = 0.893996663600558
s1 = 0.893996663600558

Note: The user can view the list of variables stored.

C:\Documents and Settings\hasan.masud>calculate clear

Note: The user can also clear the variable lists.

C:\Documents and Settings\hasan.masud>calculate list
0 variables found
C:\Documents and Settings\hasan.masud>calculate s1=Math.Sin(an gle)
Invalid argument
0 variables found

Note: If an error occurs, then it displays an error message and the list of the variables.

C:\Documents and Settings\hasan.masud>calculate r#=25+9
Syntax Error
0 variables found
C:\Documents and Settings\hasan.masud>calculate 45=25+9
Syntax Error
0 variables found
C:\Documents and Settings\hasan.masud>calculate maxint=int.MaxValue
maxint = 2147483647

Note: The user can use any expression which will be compiled in a single line without any error.

If the user provides no arguments or invalid arguments, then Calculate will take the user to calculate the console. In that case, the user does not have to write Calculate every time. To exit from the Calculate console, just write bye in the Calculate console.

New Feature

Code explanation

The ExpressionEvaluator class has two static methods. The Calculate method accepts the expression as an argument. Then it generates a class named myclass, which is inherited from the base class Calculate.MyClassBase and overrides 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 + @";
                                 }
                           }";

The %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 is compiled with the C# compiler, if no error is found in the generated source code, the 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");
}

The ExpressionCalculator class has another method which returns true if compilation of the source code is successful, otherwise it returns false. This function is used to validate the variable's name in the 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);
}

The Variables class is a serializable collection class, which uses a SortedList to store the variable name and values. It has a static method to manage the singleton 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.

The Calculate class has the main entry point. It uses the first argument as an expression or command used in this application. To manage the calculate console, there is a function named ManageConsole in the 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 parses the expression and provides functionality for the calculator.

It supports only two commands: list: displays a list of the variables with values. It invokes the 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);
   }
}

clear: This command clears the variables.

The expression is split with the '=' character. If the '=' character is found, then the first part of the split 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 validity 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 add the ans variable at the beginning of the expression to complete the expression. It saves the user from having to write ans again and again if he/she wants 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);
}

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here