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
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.
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);
}