65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (54投票s)

2005 年 2 月 7 日

CPOL

5分钟阅读

viewsIcon

259259

downloadIcon

6126

一个用 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 类中的成员函数会被计算器自动使用。在此示例中,您可以看到我们如何让计算器实现 sinnow 函数。

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

正如您所见,不需要太多包装,函数可以直接在此类中编写和使用。但请注意,计算器不对 IntegerDouble 之间进行区分。因此,请记住在函数参数中使用 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。这是标准做法。这也非常灵活。这样,如果您愿意,可以通过更改语法分析器检测运算符eqneandornot... 的方式来修改它以接受 C++ 语法。更改词法分析器不会迫使您重新编程计算器的其余部分。

解析器

语法分析器比词法分析器要复杂一些。它类似于一个具有某种流程的流动机器,有点像一个管道。它将逐个处理 Token,而不会向前或向后查看。

在本文中,我讨论了运算符、左侧部分和右侧部分。在表达式 1 + 2 中,我称 + 为运算符,1 为左侧部分,2 为右侧部分。

语法分析器的复杂概念之一是优先级。例如,表达式

1 + 2 * 3

与表达式的处理方式不同

1 * 2 + 3

计算器使用标准的优先级集进行操作。乘法比加法具有更高的优先级。因此

1 + 2 * 3 = 1 + 6 = 7
1 * 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 日
    • 大幅增加了本文的篇幅和详细程度。
© . All rights reserved.