一个用 VB.NET 编写的表达式计算器






4.80/5 (54投票s)
一个用 VB.NET 编写的表达式计算器。
有一个新版本(仍然非常简单),地址如下: 
https://codeproject.org.cn/Articles/13779/The-expression-evaluator-revisited-Eval-function-i 

引言
我的公司需要一个小型表达式计算器来用于我们的 .NET 应用程序。使用 .NET 框架的编译功能似乎是创建计算器最直接的方法。然而,这种方法在实践中有一个令人头疼的副作用:每次评估函数时,它似乎都会在内存中创建一个新的 DLL,而且几乎不可能卸载该 DLL。您可以在文章末尾的说明中参考通过运行时编译 C# 代码来评估数学表达式以获取更多详细信息。
此计算器既不使用 CodeDOM,也不尝试编译 VB 源代码。相反,它解析您的表达式并计算其值。
与我见过的其他项目相比,此计算器可以做到以下几点:
- 访问和处理字符串表达式。您可以评估 "Hello" + " " + "world" 
- 访问和处理对象。您可以评估 ThisForm.Left。
- 它还提供了易于扩展的功能。
您可以添加任意数量的自定义函数,而无需更改计算器代码。
使用代码
只需两行代码即可运行计算器:
Dim mEvaluator As New Evaluator 
Dim res As integer = CInt(mEvaluator.Eval("1+1"))
如何为计算器提供变量
当计算器无法识别关键字时,它会引发一个 GetVariable 事件。您无需发布所有变量然后运行 eval。相反,您可以提供一个按需函数,该函数仅提供所需的变量。
 Private Sub Evaluator1_GetVariable(ByVal name As String, _
            ByRef value As Object) Handles Evaluator1.GetVariable
    Select Case name
      Case "anumber"
        value = 5.0
      Case "theForm"
        value = Me
      Case "adate"
        value = #1/1/2005#
    End Select
End Sub
如何用自定义函数扩展计算器
EvalFunctions 类中的成员函数会被计算器自动使用。在此示例中,您可以看到我们如何让计算器实现 sin 和 now 函数。
Public Class EvalFunctions 
   Function sin(ByVal v As Double) As Double
     Return Math.Sin(v)
   End Function
   Function now() As DateTime
     Return Microsoft.VisualBasic.Now
   End Function
正如您所见,不需要太多包装,函数可以直接在此类中编写和使用。但请注意,计算器不对 Integer 和 Double 之间进行区分。因此,请记住在函数参数中使用 Double 而不是 Integer。
这是如何工作的?
计算器由经典的词法分析器(Tokenizer)后跟经典的语法分析器(Parser)组成。我用 VB 编写了这两者,没有使用任何 Lex 或 Bisons 工具。目标是可读性高于速度。词法分析、语法分析和执行在一个过程中完成。这既优雅又相当高效,因为计算器只向前或向后查看一个字符。
词法分析器
它逐个读取字符,并根据遇到的字符改变其状态。当它识别出一种已识别的 Token 类型时,它会将其返回给语法分析器。如果它无法识别某个字符,它将引发一个语法错误异常。
' Recognized token types :
Private Enum eTokenType
 none                   ' temporary state
 end_of_formula         ' when the tokenizer reach the end
 operator_plus          ' +
 operator_minus         ' -
 operator_mul           ' *
 operator_div           ' /
 operator_percent       ' %
 open_parenthesis       ' (
 comma                  ' ,
 dot                    ' .
 close_parenthesis      ' )
 operator_ne            ' <>
 operator_gt            ' <=
 operator_ge            ' >=
 operator_eq            ' =
 operator_le            ' <=
 operator_lt            ' <
 operator_and           ' AND
 operator_or            ' OR
 operator_not           ' NOT
 operator_concat        ' & 
 value_identifier       ' any word starting with a letter or _ 
 value_true             ' TRUE 
 value_false            ' FALSE 
 value_number           ' any number starting 0-9 or . 
 value_string           ' any string starting ' or " 
 open_bracket           ' [ 
 close_bracket          ' ] 
End Enum
词法分析器相当简单,它接受宽松的 VB/Excel 语法。计算器分为两个类,一个进行词法分析,第二个处理 Token。这是标准做法。这也非常灵活。这样,如果您愿意,可以通过更改语法分析器检测运算符eq、ne、and、or、not... 的方式来修改它以接受 C++ 语法。更改词法分析器不会迫使您重新编程计算器的其余部分。
解析器
语法分析器比词法分析器要复杂一些。它类似于一个具有某种流程的流动机器,有点像一个管道。它将逐个处理 Token,而不会向前或向后查看。
在本文中,我讨论了运算符、左侧部分和右侧部分。在表达式
1 + 2 中,我称 + 为运算符,1 为左侧部分,2 为右侧部分。
语法分析器的复杂概念之一是优先级。例如,表达式
1 + 2 * 3
与表达式的处理方式不同
1 * 2 + 3
计算器使用标准的优先级集进行操作。乘法比加法具有更高的优先级。因此
1 + 2 * 3 = 1 + 6 = 71 * 2 + 3 = 2 + 3 = 5
在上述情况下,我们需要先进行乘法。
那么如何在一次遍历中完成呢?
在任何时候,语法分析器都知道其优先级级别。
Private Enum ePriority
  none = 0
  [concat] = 1
  [or] = 2
  [and] = 3
  [not] = 4
  equality = 5
  plusminus = 6
  muldiv = 7
  percent = 8
  unaryminus = 9
End Enum
当语法分析器遇到一个运算符时,它会递归地调用语法分析器来获取右侧部分。当语法分析器返回右侧部分时,运算符可以应用其操作(例如 +),然后继续解析。
有趣的是,在计算右侧部分的同时,词法分析器已经知道了其当前的优先级级别。因此,在解析右侧部分时,如果检测到优先级更高的运算符,它将继续解析并只返回结果值。
你说它支持对象?
是的,计算器支持 . 运算符。如果您输入表达式 theForm.text,计算器将返回窗体的标题。如果您输入表达式 theForm.left,它将返回其运行时左侧位置。此功能仅为实验性的,尚未经过测试。这就是为什么我将此代码放在这里,希望其他人能发现其功能有价值并提交他们的改进。
这是如何工作的?
事实上,对象是免费提供的。我使用 System.Reflection 来评估自定义函数。相同的代码用于访问对象的 A 方法和属性。当语法分析器遇到一个标识符,该标识符是一个对它没有意义的关键字时,它会尝试反射 CurrentObject,看看是否能找到同名的方法或属性。
 mi = CurrentObject.GetType().GetMethod(func, _
  _Reflection.BindingFlags.IgnoreCase _ 
  Or Reflection.BindingFlags.Public _ Or Reflection.BindingFlags.Instance)
如果找到方法或属性,它将传入其参数。
valueleft = mi.Invoke(CurrentObject, _ _ System.Reflection.BindingFlags.Default, Nothing, _ DirectCast(parameters.ToArray(GetType(Object)), Object()), Nothing)
关注点
我认为这是 CodeProject 上唯一一个具有独立词法分析器和语法分析器的公式计算器。由于使用了 System.Reflection,因此可以最大限度地扩展。
历史
- 2005 年 2 月 7 日- 首次发布。
 
- 2005 年 2 月 10 日- 大幅增加了本文的篇幅和详细程度。
 


