一个用 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 = 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 日
- 大幅增加了本文的篇幅和详细程度。