C# 表达式解析器,使用 RPN






4.57/5 (25投票s)
2004年1月18日
5分钟阅读

215137

7435
使用 C# 中的 RPN 进行表达式解析器的设计与实现。
引言
本文的目的是在 C# 中使用 RPN 的设计与实现一个简单的表达式解析器和求值器,来演示面向接口的编程范式。目标受众的技能水平应为初级到中级。
背景
有关表达式和逆波兰表示法(RPN)的介绍,请参阅本文 - 表达式求值器:使用 RPN,作者 lallous,位于 C++/MFC 部分。
设计说明
(此处引用的所有代码均在 RPNParser.cs 文件中。)
接口
每个表达式都由操作数和运算符组成。这在接口 IOperand 和 IOperator 中得到了体现。
- IArithmeticOperations定义了二元算术运算符(+、-、*、\、%)的方法。
- IComparisonOperations定义了所有比较运算符(>、>=、<、<=、==、!=)的方法。
- ILogicalOperations定义了逻辑运算符(&& 、||)的方法。
这些接口背后的思想如下——
每个操作数支持一组操作,而运算符则使用两个操作数所支持的操作来计算一对操作数。
例如:Long 操作数支持算术和比较运算,而 String 可能只支持比较运算。Boolean 操作数仅支持逻辑运算。
像 + 这样的运算符只需要知道它需要调用实现 IArithmeticOperations 接口的 IOperand 上的 Plus 方法。这使得表达式解析与求值(在此例中为 RPN 序列化)的算法与执行实际求值的数据类型分离,因此可以非常方便地扩展以支持新的数据类型。
操作数
Operand 类是一个抽象基类,实现了 IOperand 接口,并额外提供数据存储。
LongOperand 是一个 Operand 类,专门用于 Long 和 Int(Int64 和 Int32)类型的操作数,并实现了 IArithmeticOperations、IComparisonOperations 接口。
BoolOperand 是一个 Operand 类,专门用于 bool 类型操作数,并且仅实现 ILogicalOperations 接口。
OperandHelper 是一个静态类,提供对象工厂功能,根据传递给它的 Type 来创建相应的 Operand 对象(OperandHelper.CreateOperand)。
对于 Decimal/String 或用户定义类型(例如 Matrix)等新的操作数类型,则需要扩展此工厂方法。
运算符
IOperator 定义了一个名为 Eval 的方法,用于计算表达式的左侧(LHS)和右侧(RHS)。
Operator 是一个实现了此接口的抽象基类,并为运算符提供数据存储。
ArithmeticOperator、ComparisonOperator、LogicalOperator 是运算符类,分别用于支持 IArithmeticOperations、IComparisonOperations、ILogicalOperations。
OperatorHelper 是一个辅助类,提供对象工厂功能,用于创建相应的 Operator 对象(OperatorHelper.CreateOperator)。此类提供确定运算符优先级的服务。这基于运算符在 m_AllOps 变量中定义的相对索引。它还提供用于解析输入表达式字符串的正则表达式。例如,使用正则表达式 [=<>!]{1,2} 来解析比较运算符。
表达式解析
Tokenizer 类提供使用正则表达式解析输入表达式字符串的功能。
TokenEnumerator 类支持 IEnumerator 接口,用于遍历输入表达式字符串中的各种标记。标记以 Token 对象的形式返回给调用者。
给定的表达式可以解析为算术、逻辑或比较表达式类型。这由 enums ExpressionType::ET_ARITHMETIC, ExpressionType::ET_COMPARISON 和 ExpressionType::ET_LOGICAL 控制。也可以组合这些 enum 类型。
例如,要将表达式解析为所有这些类型,请将 ExpressionType.ET_ARITHMETIC| ExpressionType.ET_COMPARISON|ExpressionType.ET_LOGICAL 传递给 Tokenizer 构造函数。
RPN 序列的生成在 RPNParser::GetPostFixNotation 方法中完成,并使用了上述 C++ 文章中描述的算法。返回值是一个 ArrayList,其中包含 RPN 序列中的操作数和运算符。
此方法接受一个名为 bFormula 的参数,该参数确定正在解析的表达式是公式(形式为 x+y*z/t)还是包含直接值(3+6*7/2)。如果表达式不是公式,则此方法会提取值并将其存储在 Token 对象中以供后续求值。如果表达式是公式,则需要将相应变量的值作为哈希表单独传递以进行求值。
表达式求值
RPNParser::EvaluateRPN 是执行 RPNParser::GetPostFixNotation 方法生成的 RPN 的实际求值方法,同样基于上述 C++ 文章中描述的算法。此方法以包含 RPN 序列的 ArrayList 作为输入,如果表达式是公式,则还需要一个包含表达式中变量值的哈希表。
用法
解析器的典型用法如下——
RPNParser parser = new RPNParser();
string szExpression = @"((2+3)*2<10 || 1!=1) && 2*2==4";
parser.EvaluateExpression( szExpression, 
    Type.GetType("System.Int64" ), false, null );
附带的代码包含了一个小型基于窗体的应用程序来测试该逻辑。
它使用 RPN_Parser::Convert2String() 方法来获取 RPN 序列的字符串表示。
可扩展性
通过继承 Operand 和 Operator 类,可以轻松地扩展解析器以支持新的操作数类型和运算符。由于解析和求值逻辑完全基于接口,因此通常无需更改它们。当然,需要扩展工厂类以支持新类型,并且如果添加新运算符,则需要相应地修改 Operatorhelper 类中使用的正则表达式及其优先级信息。
希望有人能从中受益。
当前的不足
- 未处理一元运算符(+、-、!)。
- 尚不支持混合变量和值的表达式(例如,x+y/3*z%2)。
- 未处理按位 & 和 |。
- 是否不支持将带有多个括号的组合表达式仅视为一种 ExpressionType?// 例如,((2+3)*2<10 || 1!=1) && 2*2==4 无法仅作为逻辑表达式处理。 
