程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> 仿查詢分析器的C#計算器——5.計算求值

仿查詢分析器的C#計算器——5.計算求值

編輯:關於C#

前面幾篇文章介紹了各種分析過程,本篇作為完結篇,介紹如何調用之前實現的代碼,如何實現多行表達式或者選擇部分表達式進行運算, 以及如何定位錯誤。

本程序可以不需要UI界面,獨立成一個模塊。如果表達式分析與計算功能打包成一個dll,那入口只有一個,SyntaxAnalyse類。new一個 SyntaxAnalyse類之後,調用其中的Analyse方法,將要計算的運算表達式作為參數傳遞進去,返回一個頂級TokenRecord對象,再根據返回的 TokenRecord對象的值類型取得結果,整個計算過程就完成了,使用起來非常方便。

    /// <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事件處理方法,代碼如下:

        /// <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類的定義如下:

    /// <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方法得到的,如果需要顯示為其他信息,也可以自行修改。

到這裡本系列文章就結束了,其中包含了一些編程的技巧,希望對看了本系列文章的各位有幫助。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved