12,953,014 members (56,705 online)
alternative version

#### Stats

75.4K views
28 bookmarked
Posted 18 Nov 2005

# A Command Line Calculator

, 24 Nov 2005 CPOL
 Rate this:
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

```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;

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>");
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);
}```

## Share

 Software Developer (Senior) KAZ Software Limited Bangladesh
No Biography provided

## You may also be interested in...

 View All Threads First Prev Next
 it crashes on EOF grregd6-Mar-08 1:50 grregd 6-Mar-08 1:50
 Last Visit: 31-Dec-99 18:00     Last Update: 27-May-17 7:11 Refresh 1