命令行计算器






2.84/5 (23投票s)
一个使用 CodeDOM 的命令行计算器。
引言
一个支持带科学函数数学表达式的命令行计算器对大多数开发者来说都非常有用。Windows 自带的计算器不支持大多数科学函数。大多数时候,我对 Windows 自带的计算器感到不满意。我需要一个不会限制我书写表达式的计算器。我使用变量来存储结果。每次我需要一个简单的计算时,我都会在 Windows 计算器上遇到问题。为了制作这样一个计算器,我设计了一个完整的 MFC 数学库。我发现设计这样一个计算器时,最困难的部分是解析逻辑。后来,在使用 .NET 工作时,运行时源代码编译使得解析逻辑变得简单而有趣。我阅读了一些关于 .NET CodeDOM 编译的文章。于是我决定使用 CodeDOM 编写一个新的命令行计算器。它使用运行时编译,并通过序列化将变量保存在文件中。这样你就可以获得前一次计算中所有使用的变量的值。
描述
在这个命令行计算器中,结果保存在一个名为 ans
的预定义变量中。用户可以声明自己的变量来存储结果,并在不同的表达式中稍后使用它们。变量名的验证与 C# 中的相同。同样,表达式的支持与 C# .NET 中支持的相同。
Calculate
函数用于计算表达式。它使用保存的变量。我已经生成了包含变量声明的代码。
如何使用命令行计算器
C:\Documents and Settings\hasan.masud>calculate 25*(3+5-(10/2))
ans = 75
注意:默认情况下,结果保存在一个名为 ans
的预定义变量中。
C:\Documents and Settings\hasan.masud>calculate ans+10
ans = 85
注意:ans
变量可以在表达式中像其他变量一样访问。
C:\Documents and Settings\hasan.masud>calculate d=75/15+2
d = 7
注意:用户可以通过将某个表达式赋给它来声明一个新变量。如果变量不存在,它将自动创建一个新变量,否则将覆盖其值。
C:\Documents and Settings\hasan.masud>calculate e=ans+d
e = 92
C:\Documents and Settings\hasan.masud>calculate +10
ans = 95
注意:如果用户想在表达式的开头使用 ans
变量,则不必写 ans
。只需开始输入表达式,它就会在开头添加 ans
变量。
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
注意:用户可以使用 Math
类中可用的任何函数。
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
注意:用户可以查看存储的变量列表。
C:\Documents and Settings\hasan.masud>calculate clear
注意:用户也可以清除变量列表。
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
注意:如果发生错误,它会显示一条错误消息和变量列表。
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
注意:用户可以使用任何将在单行中无错误地编译的表达式。
如果用户没有提供参数或提供了无效参数,则 Calculate 会将用户带到计算控制台。在这种情况下,用户不必每次都写 Calculate。要退出 Calculate 控制台,只需在 Calculate 控制台中输入 bye 即可。
代码解释
ExpressionEvaluator
类有两个静态方法。Calculate
方法接受表达式作为参数。然后它生成一个名为 myclass
的类,该类继承自基类 Calculate.MyClassBase
并重写虚拟函数 eval()
来执行表达式。
string src = @"using System;
class myclass:Calculate.MyClassBase {
private double %MT_VARIABLES%;
public myclass()
{
}
public override double eval()
{
return " + expression + @";
}
}";
src
字符串变量中的 %MT_VARIABLES%
占位符将被存储在 Variables
类中的变量列表声明替换。
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);
在编译生成的代码时,会引用此应用程序的可执行文件。
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);
一旦用于执行表达式的代码使用 C# 编译器编译成功,并且生成的源代码中没有发现错误,Activator
类就会创建一个 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
类还有一个方法,如果源代码编译成功则返回 true
,否则返回 false
。此函数用于在 Calculator
类中验证变量名。
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
类是一个可序列化的集合类,它使用 SortedList
来存储变量名和值。它有一个静态方法来管理单例并保存、加载变量。它使用二进制序列化来序列化 SortedList
,并将其保存在应用程序路径下的名为 variables.dat 的文件中。
Calculate
类包含主入口点。它将第一个参数用作此应用程序中的表达式或命令。为了管理计算控制台,Calculate
类中有一个名为 ManageConsole
的函数。
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
解析表达式并为计算器提供功能。
它只支持两个命令:list:显示变量及其值的列表。它调用 Calculator
类的 PrintList
方法。
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
:此命令清除变量。
表达式以 '=' 字符分隔。如果找到 '=' 字符,则将分隔字符串的第一部分视为变量名,第二部分视为表达式。如果变量名可用,则检查其有效性并将结果与给定的变量名一起存储。如果变量名不存在,则将结果保存在一个名为 ans
的预定义变量中。如果在表达式的第一个字符处找到运算符,则我会在表达式开头添加 ans
变量以完成表达式。如果用户想再次使用 ans
变量,这将节省他们重复输入 ans
的麻烦。
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);
}