仿查询分析器的C#计算器——5.计算求值

代码下载http://files.cnblogs.com/conexpress/ConExpress_MyCalculator.rar

前面几篇文章介绍了各种分析过程,本篇作为完结篇,介绍如何调用之前实现的代码,如何实现多行表达式或者选择部分表达式进行运算,以及如何定位错误。

本程序可以不需要UI界面,独立成一个模块。如果表达式分析与计算功能打包成一个dll,那入口只有一个,SyntaxAnalyse类。new一个SyntaxAnalyse类之后,调用其中的Analyse方法,将要计算的运算表达式作为参数传递进去,返回一个顶级TokenRecord对象,再根据返回的TokenRecord对象的值类型取得结果,整个计算过程就完成了,使用起来非常方便。

ContractedBlock.gif ExpandedBlockStart.gif Code
    /// <summary>
    
/// 表达式分析计算类,功能入口
    
/// </summary>
    
/// <remarks>Author:Alex Leo</remarks>
    public class SyntaxAnalyse
    {
        
/// <summary>
        
/// 构造函数
        
/// </summary>
        
/// <remarks>Author:Alex Leo; Date:2007-8-2</remarks>
        public SyntaxAnalyse()
        { }

        
/// <summary>
        
/// 分析语句并返回记号记录对象
        
/// </summary>
        
/// <param name="Code">运算表达式</param>
        
/// <returns>顶级TokenRecord对象</returns>
        public TokenRecord Analyse(string Code)
        {
            
if (Code.Trim().Equals(string.Empty))
            {
                
return new TokenValue(0,1);
            }

            List
<TokenRecord> ListToken = new List<TokenRecord>();//TokenRecord列表

            
int intIndex = 0;
            TokenFactory.LexicalAnalysis(ListToken, Code, 
ref intIndex);//词法分析,将代码转换为TokenRecord列表

            
//语法树分析,将Token列表按优先级转换为树
            TokenRecord TokenTop = SyntaxTreeAnalyse.SyntaxTreeGetTopTokenAnalyse(ListToken, 0, ListToken.Count - 1);
            TokenTop.Execute();
            
return TokenTop;
        }
   }

从代码中可以看出,首先是词法分析,得到一个记号对象列表List<TokenRecord>,然后进行语法分析,调用SyntaxTreeAnalyse的SnytaxTreeGetTopTokenAnalyse方法,分析出顶级记号对象,这样一棵树就出来了。接下来执行顶级节点的Execute方法,该方法中首先会执行下级节点的Execute方法,然后再针对下级节点的值执行自身的运算。所有的TokenRecord都是这样的模式,逐级递归调用,最后得到计算结果。TokenRecord基类中包含一个object类型的Value属性和一个Type类型的TokenValueType属性,通过这两个属性可以得到具体的值及其类型,然后做下一步处理。因为这里不只能执行数学运算,还能做字符串和逻辑值运算,所以必须通过TokenValueType来确定值的类型。如果只需要实现数学运算,程序会简单一些。

窗体的调用也很简单,并没有设计漂亮的外观和高级设置等。主要的代码是“计算”按钮的Click事件处理方法,代码如下:

ContractedBlock.gif ExpandedBlockStart.gif Code
        /// <summary>
        
/// 点击“计算”按钮
        
/// </summary>
        
/// <param name="sender"></param>
        
/// <param name="e"></param>
        private void btnExecute_Click(object sender, EventArgs e)
        {
            
if (this.rtbInput.Text.Trim().Replace("\n""").Length == 0)
            {
                
this.rtbOutput.Text = "输入的表达式不能为空,请重新输入。";
            }
            
else
            {
                
string strSource;
                
int intTotalIndex = 0;
                
this.rtbOutput.Text = "";
                
string[] strLines;
                
this.trvSyntaxTree.Nodes.Clear();//清空语法树

                
if (this.rtbInput.SelectedText.Trim().Length == 0)//获取选中的代码,如果未选中,则执行全部
                {
                    strSource 
= this.rtbInput.Text;
                }
                
else
                {
                    strSource 
= this.rtbInput.SelectedText;
                    intTotalIndex 
= this.rtbInput.SelectionStart;
                }

                
if (this.chkAllowMultiLine.Checked)//判断是按多行执行还是单行执行
                {
                    strLines 
= strSource.Split(new char[] { '\n' });//多行则用换行符分割成多行
                }
                
else
                {
                    strLines 
= new string[] { strSource.Replace("\n""") };//单行则移除换行符成一行
                }

                
foreach (string Line in strLines)
                {
                    
if (Line.Trim().Length != 0)//避免中间出现空行
                    {
                        
try
                        {
                            TokenRecord TokenTop 
= myAnalyse.Analyse(Line);//计算表达式
                            this.rtbOutput.Text += TokenTop.GetValueString() + "\n";//显示计算结果
                            this.LoadSyntaxTree(TokenTop);//加载语法树到TreeView控件
                        }
                        
catch (Exception ex)
                        {
                            
this.rtbOutput.Text += "发生错误\n" + ex.Message + "\n";//显示错误信息
                            if (ex is SyntaxException)//如果是语法错误,则选中错误的代码
                            {
                                SyntaxException myException 
= (SyntaxException)ex;
                                
this.ActiveControl = this.rtbInput;//设置输入框为激活控件
                                this.rtbInput.Select(myException.Index + intTotalIndex, myException.Length);//定位发生错误的字符串
                            }
                            
return;
                        }
//try
                    }//if
                    intTotalIndex += Line.Length + 1;
                }
//foreach
            }//else
        }//btnExecute_Click


代码中包含详细的注释,这里做简要说明。未选中输入框中的文本则执行全部代码,否则执行选中部分的代码。将要执行的代码根据是否计算多行进行分解,存放在一个字符串数组中。然后对表达式数组循环计算。如此实现了选择部分表达式计算以及多行表达式计算。另外如何实现错误定位,则是通过捕获错误。程序中定义了一个Exception类,但进行词法分析和语法分析的时候,如果发生错误,则会抛出该异常。通过该异常类中的错误序号以及长度,就可以选中输入框中的错误部分。但是这里只能选中第一次发生的错误,不能像VS.NET的IDE一样捕获所有错误。Exception类的定义如下:

ContractedBlock.gif ExpandedBlockStart.gif Code
    /// <summary>
    
/// 语法错误类,用于发生错误时提示用户并选中错误的操作符
    
/// </summary>
    
/// <remarks>Author:Alex Leo; Date:2008-5-21;</remarks>
    public class SyntaxException : Exception
    {
        
private int m_Index;
        
/// <summary>
        
/// 错误列号
        
/// </summary>
        
/// <remarks>Author:Alex Leo; Date:2008-5-21;</remarks>
        public int Index
        {
            
get { return m_Index; }
        }

        
private int m_Length;
        
/// <summary>
        
/// 错误操作符长度
        
/// </summary>
        
/// <remarks>Author:Alex Leo; Date:2008-5-21;</remarks>
        public int Length
        {
            
get { return m_Length; }
        }

        
private string m_Message;
        
/// <summary>
        
/// 错误信息
        
/// </summary>
        public override string Message
        {
            
get { return m_Message; }
        }

        
/// <summary>
        
/// 构造函数
        
/// </summary>
        
/// <param name="Index">错误处的列号(用于错误时确定错误操作符起始位置)</param>
        
/// <param name="Length">错误操作符长度(用于错误时选择错误操作符的长度)</param>
        
/// <param name="ErrorInformation">错误信息</param>
        public SyntaxException(int Index, int Length, string ErrorInformation)
        {
            
this.m_Index = Index;
            
this.m_Length = Length;
            
this.m_Message = ErrorInformation;
        }
    }

单行多行切换只需要设置窗体的AcceptButton属性为“计算按钮”即可,这样在单行状态下,用户回车就相当于点击“计算按钮”。而按“F5”键执行计算则是通过检测输入框的KeyUp事件,当释放“F5”键时用代码去执行“计算”按钮的Click操作实现计算。

另外这里有一个语法树分析,是为了显示语法树的结构,用更直观的方法来验证分析是否正确。树节点的文本是调用TokenRecord的ToString方法得到的,如果需要显示为其他信息,也可以自行修改。

到这里本系列文章就结束了,其中包含了一些编程的技巧,希望对看了本系列文章的各位有帮助。
输入界面
语法树分析
代码下载http://files.cnblogs.com/conexpress/ConExpress_MyCalculator.rar

转载于:https://www.cnblogs.com/conexpress/archive/2009/03/30/MyCalculator_05.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值