自己寫個 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)
;
...