仿查询分析器的C#计算器——2.记号对象

上一篇地址:http://www.cnblogs.com/conexpress/archive/2009/03/04/MyCalculator_01.html
上一篇中提到了用树形结构来分析表达式并计算求值的思路。但对程序来说,输入的表达式只是一个字符串而已。要将表达式表示成树型结构,首先必须可以将表达式分解成一个个节点,然后才可以由节点组成树。这里将树上的每一个节点称之为记号对象TokenRecord。

根据上面的分析得出,记号对象要求有一个存储自身值的变量,有自己特定的计算方法,还要能知道其下级的值。由此可以得出TokenRecord的基本信息(略去非关键信息):
属性
Index:在表达式中的列号,int类型,出错时用于指示错误所在,从1开始。
Priority:优先级,int类型,在分析表达式的时候需要。
TokenValue:记号值,object类型
ChildList:下级列表,List对象,用来存储下级元素,实现树结构。
方法
Execute:执行该元素的操作,abstract方法。
SetChildCount:设置下级数量,虚方法,用于检查下级数量合法性,在构造函数中调用。对应有一个m_ChildCount字段,用于存储下级数量。因为检查下级属于元素内部的任务,所以将m_ChildCount设置为protected,也没有对应的ChildCount属性。
SetPriority:设置优先级,虚方法,在构造函数中调用。
CheckChildCount:检查下级数量,在Execute中调用,保证表达式合法。

其中TokenValue属性使用object类型是因为这里支持字符串、数值和逻辑值的运算。在最开始设计的时候,曾经采用过两个字段string和double类型来存储字符串和数值,逻辑值也用数值表示,后来改成object更简单了。

TokenRecord类的代码如下:

ContractedBlock.gif ExpandedBlockStart.gif Code
    /// <summary>
    
/// 记号对象
    
/// </summary>
    
/// <remarks>Author:Alex Leo;</remarks>
    public abstract class TokenRecord
    {
        
#region 属性和字段

        
//下级个数
        protected int m_ChildCount;

        
private int m_Index;
        
/// <summary>
        
/// 列序号
        
/// </summary>
        public int Index
        {
            
get { return m_Index; }
        }

        
/// <summary>
        
/// 优先级,必须赋值
        
/// </summary>
        protected int m_Priority;
        
/// <summary>
        
/// 优先级
        
/// </summary>
        
/// <returns></returns>
        public int Priority
        {
            
get { return m_Priority; }
        }

        
private int m_Length;
        
/// <summary>
        
/// 操作符长度
        
/// </summary>
        public int Length
        {
            
get { return m_Length; }
        }

        
private Type m_TokenValueType;
        
/// <summary>
        
/// 记号值类型
        
/// </summary>
        public Type TokenValueType
        {
            
get { return m_TokenValueType; }
            
set { m_TokenValueType = value; }
        }

        
private object m_TokenValue;
        
/// <summary>
        
/// 记号值
        
/// </summary>
        public object TokenValue
        {
            
get { return m_TokenValue; }
            
set { m_TokenValue = value; }
        }


        
private List<TokenRecord> m_ChildList = new List<TokenRecord>();
        
/// <summary>
        
/// 下级列表
        
/// </summary>
        public List<TokenRecord> ChildList
        {
            
get { return m_ChildList; }
        }

        
#endregion

        
/// <summary>
        
/// 构造函数
        
/// </summary>
        
/// <param name="Index">序号</param>
        
/// <param name="Length">自身长度</param>
        public TokenRecord(int Index, int Length)
        {
            
this.m_Index = Index;
            
this.m_Length = Length;
            
this.SetPriority();
            
this.SetChildCount();
        }


        
#region 方法

        
/// <summary>
        
/// 重写ToString方法
        
/// </summary>
        
/// <returns></returns>
        public override string ToString()
        {
            
//可以根据需要修改以显示不同的信息
            return this.GetType().Name + "_" + GetValueString() + "_" + TokenValueType.ToString();
        }

        
/// <summary>
        
/// 获取值的字符串表示
        
/// </summary>
        
/// <returns></returns>
        public string GetValueString()
        {
            
return this.TokenValue.ToString();
        }

        
/// <summary>
        
/// 检查下级数量,必要时可以重写,因为有些Token的下级数量可以是一个区间
        
/// </summary>
        
/// <param name="ErrorInformation">下级数量不符时显示的错误信息</param>
        internal void CheckChildCount(string ErrorInformation)
        {
            
if (this.m_ChildList.Count != this.m_ChildCount)
                
throw new SyntaxException(this.m_Index, this.m_Length, ErrorInformation);
        }

        
#region 必须重写的方法

        
/// <summary>
        
/// 执行代码
        
/// </summary>
        public abstract void Execute();

        
/// <summary>
        
/// 设置下级数量
        
/// </summary>
        protected abstract void SetChildCount();

        
/// <summary>
        
/// 设置优先级
        
/// </summary>
        protected abstract void SetPriority();

        
#endregion


        
#endregion


        
#region 转换记号值类型

        
/// <summary>
        
/// 将记号值转换为字符串类型
        
/// </summary>
        internal string ChangeTokenToString()
        {
            
string strValue;
            strValue 
= (string)(this.TokenValue = this.TokenValue.ToString());
            
this.TokenValueType = typeof(string);
            
return strValue;
        }

        
/// <summary>
        
/// 将记号值转换为数字类型
        
/// </summary>
        
/// <param name="ErrorInformation">无法转换成数字时显示的错误信息</param>
        internal double ChangeTokenToDouble(string ErrorInformation)
        {
            
double dblValue;
            
if (this.TokenValueType != typeof(double))
            {
                
if (double.TryParse(this.TokenValue.ToString(), out dblValue))
                    
this.TokenValueType = typeof(double);
                
else
                    
throw new SyntaxException(this.m_Index, this.m_Length, ErrorInformation);
            }
            
else
            {
                dblValue 
= (double)this.TokenValue;
            }
            
return dblValue;
        }

        
/// <summary>
        
/// 将记号值转换为逻辑值
        
/// </summary>
        internal bool ChangeTokenToBoolean()
        {
            
bool blnValue = false;
            
if (this.TokenValueType == typeof(string))
            {
                
switch (this.TokenValue.ToString().Trim().ToLower())
                {
                    
case "true":
                        blnValue 
= (bool)(this.TokenValue = true);
                        
break;
                    
case "false":
                    
case "":
                    
default:
                        blnValue 
= (bool)(this.TokenValue = false);
                        
break;
                }
                
this.TokenValueType = typeof(bool);
            }
            
else if (this.TokenValueType == typeof(double))
            {
                blnValue 
= (bool)((Convert.ToInt32(this.TokenValue) != 0? (this.TokenValue = true) : (this.TokenValue = false));
                
//检查上一行代码是否错误
                this.TokenValueType = typeof(bool);
            }
            
else if (this.TokenValueType == typeof(bool))
            {
                blnValue 
= (bool)this.TokenValue;
            }
            
else
            {
            }

            
return blnValue;
        }

        
#endregion

    }
//class TokenRecord

设计出基类TokenRecord后,其他记号类都可以从它继承,然后实现自己的特定计算方法即可。那么实际的表达式中有哪些类型的记号呢?经过分析得出,表达式中所包含的的操作元素无非这几种:关键字,运算符,字符串,数字。
关键字:if,sin,cos,true,false,pi等以英文字母开头的词,其中可以包含数字或者其他允许的符号
运算符:+,-,*,/,\,>,<,>=,<=,&&,||,!等,纯符号操作元素,可以由好几个字符组成
字符串:"hello",'good',"where's my book"等,由字符串标识符单引号或者双引号标识的字符串。字符串中允许包含当前字符串标识符之外另一种字符串标识符,即双引号字符串中可以包含单引号,单引号字符串中可以包含双引号,这一点和JavaScript类似。如果在字符串中包含当前标识符,则必须用连续两个标识符进行转义。
数值:12,856,42.123,-62.45,允许包含小数点和负数。(本程序中对负数的处理在某些情况下会有错误,有兴趣的朋友可以完善一下)
进一步分析发现,字符串和数值有一定的相似性,就是它们并不需要计算,只需要作为一个存储单元即可。那么就可以将它们合并,称为值记号对象TokenValue。其他记号对象在Execute中实现自己的特定算法,而值记号对象的Execute方法中不做任何操作。

按照上面对操作元素的分类,从TokenRecord类衍生出一下几个类:TokenValue(对应字符串和数值),TokenMethod(方法类,抽象类,对应部分关键字而非全部,因为有些关键字是常量,可以用TokenValue表示),  TokenSymbol(对应运算符,抽象类)。类图如下:

然后再从这些类中衍生出更多的具体的类,比如TokenIf, TokenSin, TokenCos, TokenPlus, TokenMinus。
以这个表达式为例,23.5+(54/3-9)*2,分析出来的TokenList记号对象列表如下:
 

记号对象

对应表达式

TokenValue

23.5

TokenPlus

+

TokenLeftBracket

(

TokenValue

54

TokenDivide

/

TokenValue

3

TokenMinus

-

TokenValue

9

TokenRightBracket

)

TokenMultiply

*

TokenValue

2

这样就把传入的表达式字符串表示成程序可以识别的对象了,然后再对这些记号对象进行处理,最终得到计算结果。这一节只是介绍记号对象,至于怎么将表达式转换成记号对象,下一篇再详细介绍。

接下来是如何实现具体的TokenRecord类。以乘法记号类TokenMultiply为例,TokenMultiply继承自TokenArithmetic,TokenArithmetic继承自TokenSymbol,TokenSymbol继承自TokenRecord。

TokenSymbol类也是一个抽象类,只是将部分重复的设置实现了,减少其子类中重复的代码,TokenSymbol类的代码如下:

ContractedBlock.gif ExpandedBlockStart.gif Code
    public abstract class TokenSymbol : TokenRecord
    {
        
public TokenSymbol(int Index, int Length)
            : 
base(Index, Length)
        {
            
this.TokenValueType = typeof(double);//部分操作符必须根据实际修改
        }

        
protected override void SetChildCount()
        {
            
this.m_ChildCount = 2;//必须根据实际修改
        }

        
public abstract override void Execute();
    }
TokenArithmetic类则是个完全的抽象类,只是为了进行分类而已,任何功能都没实现,其代码如下:
ContractedBlock.gif ExpandedBlockStart.gif Code
    public abstract class TokenArithmeticSymbol : TokenSymbol
    {
        
public TokenArithmeticSymbol(int Index, int Length)
            : 
base(Index, Length)
        {
        }

        
public abstract override void Execute();
    }
最后是TokenMultiply类,它实现具体的乘法操作,代码如下:
ContractedBlock.gif ExpandedBlockStart.gif Code
    public class TokenMultiply : TokenArithmeticSymbol
    {
        
public TokenMultiply(int Index, int Length)
            : 
base(Index, Length)
        {
        }

        
protected override void SetPriority()
        {
            
this.m_Priority = 3;
        }

        
public override void Execute()
        {
            
this.CheckChildCount("乘法的运算元素数量不合法");

            TokenRecord TokenFirst 
= this.ChildList[0];
            TokenRecord TokenSecond 
= this.ChildList[1];
            TokenFirst.Execute();
            TokenSecond.Execute();

            
this.TokenValue = TokenFirst.ChangeTokenToDouble("乘法的运算元素不是数值"* TokenSecond.ChangeTokenToDouble("乘法的运算元素不是数值");
        }
    }
TokenRecord中的三个虚方法SetChildCount, SetPriority, Execute都得到了实现。这里每一个具体的记号都只负责自己内部的计算,降低了类和外部的联系,即高内聚低耦合。如果需要添加新的计算方法,只需要从TokenRecord继承,然后实现这三个方法即可,可以很容易的进行扩展。例如本程序中实现了三角函数sin和cos,但未实现tan。如果需要实现tan,只需要根据sin或者cos的实现方法去做就可以了。甚至可以再提炼出一个抽象类,让所有的三角函数计算都从它继承。

本篇就到此,下一篇将介绍如何将表达式字符串转换成程序可以处理的TokenRecord对象列表。
代码下载: http://files.cnblogs.com/conexpress/ConExpress_MyCalculator.rar

转载于:https://www.cnblogs.com/conexpress/archive/2009/03/18/MyCalculator_02.html

猜你喜欢

转载自blog.csdn.net/weixin_33704591/article/details/93352495