前面幾篇文章介紹了各種分析過程,本篇作為完結篇,介紹如何調用之前實現的代碼,如何實現多行表達式或者選擇部分表達式進行運算, 以及如何定位錯誤。
本程序可以不需要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方法得到的,如果需要顯示為其他信息,也可以自行修改。
到這裡本系列文章就結束了,其中包含了一些編程的技巧,希望對看了本系列文章的各位有幫助。
本文配套源碼