1. 程式人生 > >C#實現的表示式解析與計算類TExprParser介紹

C#實現的表示式解析與計算類TExprParser介紹

        在一個報表稽核軟體專案中需要計算字串表示式的值(例如"1+2/3")。同時,在編制稽核規則時也需要獲取稽核表示式是否存在語法錯誤。於是,在國內外網上搜索了幾個免費元件,感覺都不太好靈活實用。也使用了網上介紹的基於JScript.NET的表示式計算方法(本質上是模擬Javascript計算表示式),程式碼異常簡短且能獲取表示式的值,但不能滿足專案需求:

  • 不能確定表示式或其子項計算後的資料型別。
  • 不支援自定義函式,如常見的數值計算函式Truncate和字元處理函式Substr等。起因是,稽核軟體中要判斷某個字元型資料是否含空格、是否含非可見字元等,或根據某個標誌位判斷一個數據與另一個數據是否匹配。
  • 不能自定義常見的iif(e1,e2,e3)函式和if e1 then e2 else e3結構。
  • 不能使用{n}佔位符的引數計算形式,應用缺乏靈活性。
  • 不能自定義運算子。特別,定義符合中文習慣的小於等於(≤)、大於等於(≥)和不等於(≠)運算子,以及更好理解的and/or邏輯表示。這個基於考慮,稽核規則表示式不僅我們要能看懂,更需要軟體使用者能看懂!例如1>=2 && 5<=6就不及1≥2 and 5≤6好理解。
  • 計算出現異常時直接丟擲錯誤,無法內部捕獲,例如表示式錯誤、資料被0除、資料溢位等。

        總之,能找到的現有免費元件和方法不能滿足靈活性應用需求。於是,在2015年春節前編寫了一組TExprParser類(其dll庫檔案和測試程式可以

到csdn下載),基本滿足了上述需求,具體介紹如下:

        1、名稱空間、類版本及其建構函式

        全部類位於名稱空間CSUST.Data.Expr中,可以直接使用的類有如下四個版本(基類為TExprChecker)和一個檢測類(TExprChecker):

  • 核心版:TExprParserKernal。該版本的表示式使用c/c++/c#的語法,支援常規數學計算(+-*/^,其中^是冪計算)、關係計算(==、!=、>=和<=)、邏輯計算(&&、||),支援兩個數學單目運算子(+-)和一個邏輯非單目運算子(!)。運算子的優先級別和結合方法與c/c++/c#的運算子相同。注意,該版本假設表示式沒有空格(字串中可以有空格)、沒有非可見字元。即,該版本沒有表示式規範性檢測的詳細解析資訊。
  • 基本版:TExprParserBasic。該版本可以使用核心版的全部運算子,表示式可以不規範,將做嚴格的規範性檢測和完整的解析資訊提示。
  • 擴充套件版:TExprParserEx。該版本支援表示式中使用佔位符{n},支援iif(e1,e2,e3)函式,支援if e1 then e2 或if e1 then e2 else e3結構,支援and/or(代替&&/||)邏輯運算子。但iff()函式不支援巢狀。
  • 中文版:TExprParserChina。該版本支援自定義函式(見下面的說明)及其巢狀使用(引數也可以使用函式),支援iif(e1,e2,e3)巢狀,支援四個中文數學運算子、六個中文關係運算符、中文左右圓括號、中文左右花括號、中文逗號。注意,表示式不支援中文雙引號(該字元作為一般字元處理)。
  • 檢測類:TExprChecker。該類用於測試表達式是否正確。雖然上述四個版本也可以測試一個表示式,但TExprChecker類可以測試帶佔位符{n}的表示式,也可以避免參數不滿足要求的函式,例如被0除等。

        上述五個類都只有一個建構函式。其中,核心版、普通版和檢測版沒有動態引數(即不支援佔位符{n}),擴充套件版和中文版則有動態引數(與佔位符匹配,也可以省略),以擴充套件版和檢測舉例和簡介如下:

  • TExprParserEx(string expr, params string[] paramValues):expr為表示式字串,字元型資料使用西文雙引號標識。paramValues為給佔位符提供引數的字元動態陣列,資料項之間使用逗號分隔。例如:TExprParserEx parser = new TExprParserEx("1+2/{1}", 1.1),{1}將由1.1代替。如果沒有動態引數,則TExprParserEx parser = new TExprParserEx("1+2/5")即可。
  • TExprChecker(string expr):該類有兩個事件Checked(檢測完成通知)和Checking(跟蹤每一步檢測情況),以及三個方法Check()(同步呼叫)、CheckAsync()(非同步呼叫)和Stop()(停止檢測)。該類的IsPassed屬性表示表示式是否通過測試,IsFinished表示檢測是否完成。

       事實上還有一個原生版TExprParserNative類,該版本不能直接使用,用於強調速度的特定場合。設計成這麼五個類,主要是考慮執行效能問題。從10萬次重複計算的結果看,中文版的時間是基本版的2倍多,是核心版2.2倍左右,是原生版的3倍多(使用等價表示式)。中文版耗時的關鍵原因在於函式搜尋及其遞迴呼叫,特別是函式及其引數的遞迴呼叫。如果在專案中使用並構建一般的計算表示式(自己可以保證表示式語法和運算子,沒有if判斷,也沒有函式和遞迴呼叫),使用核心板或效能接近的基本版就可以滿足要求。有if沒有函式呼叫,則使用擴充套件版即可。

       2、表示式解析與計算結果

       四個解析與計算類均有屬性:HasError、ErrorMessage、Result和IsValid(為HasError的邏輯反)。其中,布林值HasError為true表示表示式有錯誤,此時ErrorMessage是錯誤訊息。Result是一個TData類的例項,該類有三類結果值(boolean、decimal和string),由其TDataKind屬性決定,結果值由DeciamalValue、StringValue和BooleanValue三個屬性分別給出。注意,每個運算子均需要判斷所需要資料的型別,結果也將標記資料型別。

        3、函式及其引數

        TExprParserChina版支援三大類函式,可以滿足常規的數值、字串和日期計算需求(以下n2表示函式的第二引數,n3表示第三個引數):

  • 數值型函式:Abs(取絕對值)、Ceil(上取整)、Floor(下取整)、Truncate(擷取小數位)、Round(舍入到指定精度,n2)、ToStr(數值轉化為字串)、Val(轉換字串為數值)。例如,Ceil(1.1)將返回2,Round(1.555,2)將返回1.56。
  • 字元型函式:Substr(取子串,n2,n3)、Left(取左串,n2)、Right(取右串,n2)、Trim(去前後空格)、Lower(全轉小寫)、Upper(全轉大寫)、Len(取字串長)、Match(匹配正則式,n2)、IsBlank(是空白)、HasBlank(含空白)、HasHideChar(含非可見字元)、HasBlankOrHideChar(含空白或非可見字元)、HasStr(包含子串,n2)、InStr(包含在字串中,n2)。其中的,Substr的第二引數n2表示起始位置(注意,以1開始計數)。例如:Substr("12345",2,4)將返回"2345",Match("abc","abc")將返回true,InStr("abc","def")將返回false。
  • 日期型函式:IsDate(是否為正確日期格式)、GetYear(取年份數)、GetMonth(取月份數)、GetDay(取日數)、DaysBetween(取兩天間的天數,n2)、Today(取當前日期,n2)、LastDay(取日期所在月的最後一天)、IsDayBefore(是否在日期之前,n2)、IsDaySame(是否為同一天,n2)、IsDayAfter(是否在日期之後,n2)、AddDay(增加日期天數,n2)、AddYear(增加日期年分數,n2)、AddMonth(增加日期月份數,n2)。例如,IsDate("2015-01-14")將返回true,Today(1)將返回"2015-01-15"(如果今天是14號),IsDayAfter("2015-02-14","2015-02-15")的結果是false。

        4、使用舉例(下載rar包有exe測試程式和dll)

        1)四個版均可以使用的表示式

        (-1*2+5)/-2+(2+3*5)>5^2&&-15.3++3!=6||5>=4||3<=5||!(4==4)&&5<6

        2)if e1 then e2 else e3 語句及函式巢狀

        if len("123") > 2 then truncate(len("123")) < 1 else 5/-3 > -3 || substr("123",2,abs(-1)) != "2"

        (if 2>1 then 2 else 1) * (if 22>11 then 22 else 11)

        3)iif(e1,e2,e3) 結構

        iif(3>2,"3>2","3<=2") + iif(5>6,"5<2","5>=6") 

        4)函式與佔位符使用

        Substr
        (
            IIF({1}≠{2} or 5 ≤ 4.999999999,  "12345", "abcde"),
            len({2})/2,
            5
        )

        Substr
        (
            if IsDaySame({1},today(0)) then today(1) else AddYear(today(1),1),
            2,
            iif(IsDaySame({1},today(0)), 8, 2)
        )

        上述表示式是換行形式,在測試程式中如果有佔位符則需要給佔位符號的值(字元型),如:"csust"、"csust"。如果是程式設計形式,見如下程式碼

        TExprParserChina parser = new TExprParserChina(str, "csust","csust"),其中str為前面給出的表示式字串。

        5、幾點使用說明

  • 佔位符{n}對應的引數中不能有函式、iif和if結構,但可以是表示式。這是因為使用了TExprParserBasic類來解析佔位符所代表的資料。
  • if e1 then e2 else e3 語句不能巢狀,即不支援 if ... if ... then ... then ...的形式。事實上,支援巢狀的iif(e1,e2,e3)函式完全可以代替if...then...的作用。設計if...then...這個結構完全是從使用者理解表示式的角度考慮。當然,也可以支援if巢狀,只是搜尋關鍵字麻煩點、速度慢點而已。
  • if e1 then e2 else e3 語句的結果可以使用,此時需要帶左右括號。例如:if (if 3>5 then 5 else 5) > 5 then 6 else 7 表示式是有效的。
  • 檢測類TExprChecker使用了窮舉方法遍歷佔位符的兩種資料型別(數值和日期字串)。
  • 表示式中最多支援30個不同的佔位符。

        該組類測試還夠充分(這裡謝謝幾位幫測試並發現了不少問題的同學),特別是其中的函式、佔位符和遞迴使用,難免有錯誤或不足,使用者如果發現問題或有更好的建議和方法,請不吝留言指正。這組類也是筆者將要使用的,自然會經常更新、維護和釋出(雖然釋出的是一個Dotfuscator混淆的dll庫(到csdn下載),但保證該dll能及時更新和可用)。