1. 程式人生 > >自己寫個 Drools 檔案語法檢查工具——棧的應用之編譯器檢測語法錯誤

自己寫個 Drools 檔案語法檢查工具——棧的應用之編譯器檢測語法錯誤

Drools 檔案語法檢查

一、背景

當前自己開發的 Android 專案是一個智慧推薦系統,用到 drools 規則引擎,於我來說是一個新知識點,以前都沒聽說過的東東,不過用起來也不算太難,經過一段時間學習,基本掌握。關於 drools 規則引擎的內容,後面再整理JBoss 官網上面有詳細的文件,網上資料也比較多。學習 drools 規則引擎的傳送門:

這裡主要是因為自己使用 Android Studio 在編寫 drools 檔案時,沒有了智慧提示,IDE 不對語法進行檢查了,出現了兩次多寫 ) 的錯誤。這就跟用記事本寫東西程式一樣,慌的不行,所以自己寫一個簡單的語法檢查的指令碼。對 drools 檔案進行一個初步的判斷。

二、Drools 規則引擎簡單介紹

Drools 規則引擎的使用場景

對於某些企業級應用,經常會有大量的、錯綜複雜的業務規則配置,用程式語言來描述,就形如:if-else 或者 switch-case等。像這種不同的條件,做不同的處理就是一種規則。用通俗易懂的結構來表示:當 XXX 的時候,做 XXX 的事。理論上這樣的問題都可以用規則引擎來解決。但是我們也不是說為了使用規則引擎去使用它,我們視具體業務邏輯而定,一般來說,條件(規則)比較複雜,情況種類比較多,條件可能會經常變化等,這時候,選擇規則引擎去解決問題是比較明智的。 譬如隨著企業管理者的決策變化,某些業務規則也會隨之發生更改。對於我們開發人員來說,我們不得不一直處理軟體中的各種複雜問題,需要將所有資料進行關聯,還要儘可能快地一次性處理更多的資料,甚至還需要以快速的方式更新相關機制。

Drools 規則引擎的優點

Drools 規則引擎實現了將業務決策從應用程式中分離出來。 優點: 1、簡化系統架構,優化應用 2、方便系統的整合,它們是獨立的,允許不同背景的人進行合作 3、減少編寫“硬程式碼”業務規則的成本和風險,每個規則控制所需的最小資訊量 4、它們很容易更新,提高系統的可維護性,減小維護成本

Drools的基本工作工程

我們需要傳遞進去資料,用於規則的檢查,呼叫外部介面,同時還可能獲取規則執行完畢之後得到的結果

Fact物件:

指傳遞給drools指令碼的物件,是一個普通的javabean,原來javaBean物件的引用,可以對該物件進行讀寫操作,並呼叫該物件的方法。當一個java bean插入到working Memory(記憶體儲存)中,規則使用的是原有物件的引用,規則通過對fact物件的讀寫,實現對應用資料的讀寫,對其中的屬性,需要提供get和set方法,規則中可以動態的前往working memory中插入刪除新的fact物件

Drl檔案內容:

  例子:     hello.drl檔案如下:

package rules.testword
rule "test001"
  when 
    //這裡如果為空,則表示eval(true)
  then
    System.out.println("hello word");
end

Drools的基礎語法:

包路徑,引用,規則體 (其中包路徑和規則體是必須的) package: 包路徑,該路徑是邏輯路徑(可以隨便寫,但是不能不寫,最好和檔案目錄同名,以(.)的方式隔開),規則檔案中永遠是第一行。 rule: 規則體,以rule開頭,以end結尾,每個檔案可以包含多個rule ,規則體分為3個部分:LHS,RHS,屬性 三大部分。 LHS: (Left Hand Side),條件部分,在一個規則當中“when”和“then”中間的部分就是LHS部分,在LHS當中,可以包含0~N個條件,如果 LHS 為空的話,那麼引擎會自動新增一個eval(true)的條件,由於該條件總是返回true,所以LHS為空的規則總是返回true。 RHS: (Right Hand Side),在一個規則中“then”後面的部分就是RHS,只有在LHS的所有條件都滿足的情況下,RHS部分才會執行。RHS部分是規則真正做事情的部分,滿足條件觸發動作的操作部分,在RHS可以使用LHS部分當中的定義的繫結變數名,設定的全域性變數、或者是直接編寫的java程式碼,可以使用import的類。不建議有條件判斷。

三、drools 檔案的形式

Drools是一款基於Java的開源規則引擎,所以 Drools 檔案語法完全相容 java 語法,以 .drl 為字尾。大致形式如下:

package droolsexample

// list any import classes here.
import com.sample.ItemCity;
import java.math.BigDecimal;

// declare any global variables here
dialect "java"

/*
  規則1
*/
rule "Pune Medicine Item"
   when
      item : ItemCity (purchaseCity == ItemCity.City.PUNE,
                       typeofItem == ItemCity.Type.MEDICINES)
   
   then
      BigDecimal tax = new BigDecimal(0.0);
      item.setLocalTax(tax.multiply(item.getSellPrice()));
end


/**
  規則2
*/
rule "Pune Groceries Item"
   
   when
      item : ItemCity(purchaseCity == ItemCity.City.PUNE,
                      typeofItem == ItemCity.Type.GROCERIES)
   
   then
      BigDecimal tax = new BigDecimal(2.0);
      item.setLocalTax(tax.multiply(item.getSellPrice()));
end

和 java 檔案類似,先宣告包名,然後匯入相關的類。一個規則由:

rule "xxx"
    when
        xxx
    then
        xxx
end

這樣的形式構成。其中單行註釋以 // 開頭,多行註釋形如 /* */

四、Drools 檔案語法初步檢查

目的

檢測 .drl 檔案中 ( ){ }" " 是否成對出現。如果出現錯誤,指出錯誤行數。

思路

利用 的資料結構來進行檢測。

首先我們需要給出一個空棧,然後把待檢測的程式碼中的字元一一入棧,在入棧的過程中,如果字元是一個開放符號a(也就是我們的左括號),則把它壓入棧中,如果是一個封閉符號(右括號)b,則此時先判斷一下棧是否為空,如果為空的話,則報錯(也就是待檢測的程式碼中的括號不一一對應),如果棧不為空,則比較b和棧頂元素a,如果該封閉字元b和a字元匹配(也就是他們的括號能夠匹配),則彈出棧頂元素,如果不匹配,則報錯。當吧所有待檢測的程式碼全部迭代完後,此時如果棧不為空,則報錯。

上面的思路中,我們還要將註釋中的符號排除在外。

步驟

1、讀取 drl 檔案內容為字串。 2、通過正則匹配,將 :// 替換為其它不受影響的字元或者字串 ,避免誤判斷。 3、通過正則匹配,將 // 單行註釋替換為空格。正則表示式為 //.* 4、通過正則匹配,將 /**/ 替換為不受影響的字元,如 #。(純粹是為了計算出錯行數) 5、藉助棧的資料結構,進行判斷。

python 指令碼實現

# coding=utf-8
import re


def remove_annotation(file_path):
    f = open(file_path, "r", encoding="UTF-8")
    text = f.read()
    re_uri = "://"
    text1 = re.sub(re_uri, "_____", text)
    re_single = "//.*"
    text2 = re.sub(re_single, " ", text1)
    re_multi1 = "/\*"
    text3 = re.sub(re_multi1, "#", text2)
    re_multi2 = "\*/"
    result = re.sub(re_multi2, "#", text3)
    return result


def check_syntax(text):
    try:
        clist = []
        row_num = 1
        for c in text:
            if c == '\n':
                row_num = row_num + 1
            if not clist or (clist[-2] != '\"' and clist[-2] != '#'):
                if c == '\"':
                    clist.append(c)
                    clist.append(row_num)
                if c == '#':
                    clist.append(c)
                    clist.append(row_num)
                if c == '(':
                    clist.append(c)
                    clist.append(row_num)
                if c == '{':
                    clist.append(c)
                    clist.append(row_num)
                if c == ')':
                    if clist[-2] == '(':
                        clist.pop()
                        clist.pop()
                    else:
                        print("多餘的 ) , 可能錯誤行數為 " + str(row_num))
                        return -1
                if c == '}':
                    if clist[-2] == '{':
                        clist.pop()
                        clist.pop()
                    else:
                        print("多餘的 } , 可能錯誤行數為 " + str(row_num))
                        return -1
            else:
                if c == '\"':
                    if clist[-2] == '\"':
                        clist.pop()
                        clist.pop()
                if c == '#':
                    if clist[-2] == '#':
                        clist.pop()
                        clist.pop()
        if clist:
            print("存在多餘的 ( 或者 { 或者 \" 可能的錯誤行數為:" + str(clist[-1]))
            return -2
        else:
            print("語法檢查初步正確!")
            return 0
    except IndexError:
        print("存在多餘的 ) 或者 } 或者 \" 可能的錯誤行數為: " + str(row_num))
        return -1
    except Exception as e:
        print(e)
        print("其它錯誤!")


drl_path = input("請輸入 drools 檔案路徑,可拖拽:")
content = remove_annotation(drl_path)
check_syntax(content)

演示例項:

drools 檔案就取用上面的例子,命名為 test.drl ,如圖:

上面的指令碼儲存為 check_drl.py 檔案。

結果如下:

下面將故意將 drools 檔案第 40 行增加一個 ) ,如下圖:

再次測試如下圖:

結語

本文簡單介紹了一下 Drools 規則引擎的使用場景以及 drool 檔案的簡單語法檢測,主要是利用了棧資料結構後進先出的思想。本來是計劃用 java 來寫這個工具的,後來想了一下,還是覺得 python 比較實在,有很多優勢,列舉一二: python 的列表 list 直接可以代替 java 的 Stack<E> ; python 結合正則表示式,處理字串更方便; python 獲取 list 倒數第二個元素,可以直接使用 list[-2] ,java 可能需要 stack.get(stack.size()-2) ; ...