日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

实现一个正则表达式引擎in Python(一)

發布時間:2023/12/20 python 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 实现一个正则表达式引擎in Python(一) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

項目地址:Regex in Python

開學摸魚了幾個禮拜,最近幾天用Python造了一個正則表達式引擎的輪子,在這里記錄分享一下。

實現目標

實現了所有基本語法

st = 'AS342abcdefg234aaaaabccccczczxczcasdzxc' pattern = '([A-Z]+[0-9]*abcdefg)([0-9]*)(\*?|a+)(zx|bc*)([a-z]+|[0-9]*)(asd|fgh)(zxc)'regex = Regex(st, pattern) result = regex.match() log(result)

更多示例可以在github上看到

前置知識

其實正則表達式的引擎完全可以看作是一個小型的編譯器,所以完全可以按之前寫的那個C語言的編譯器的思路來,只是沒有那么復雜而已

  • 首先進行詞法分析
  • 語法分析(這里用自頂向下)
  • 語義分析 (因為正則的表達能力非常弱,所以可以省略生成AST的部分直接進行代碼生成)
  • 代碼生成,這里也就是進行NFA的生成
  • NFA到DFA的轉換,這里開始就是正則和狀態機的相關的知識了
  • DFA的最小化
  • NFA和DFA

    有限狀態機可以看作是一個有向圖,狀態機中有若干個節點,每個節點都可以根據輸入字符來跳轉到下一個節點,而區別NFA((非確定性有限狀態自動機)和DFA(確定性有限狀態自動機)的是DFA的下一個跳轉狀態是唯一確定的)

    有限狀態自動機從開始的初始狀態開始讀取輸入的字符串,自動機使用狀態轉移函數move根據當前狀態和當前的輸入字符來判斷下一個狀態,但是NFA的下一個狀態不是唯一確定的,所以只能確定的是下一個狀態集合,這個狀態集合還需要依賴之后的輸入才能確定唯一所處的狀態。如果當自動機完成讀取的時候,它處于接收狀態的話,則說明NFA可以接收這個輸入字符串

    對于所有的NFA最后都可以轉換為對應的DFA

    NFA構造O(n),匹配O(nm)

    DFA構造O(2^n),最小化O(kn'logn')(N'=O(2^n)),匹配O(m)

    n=regex長度,m=串長,k=字母表大小,n'=原始的dfa大小

    NFA接受的所有字符串的集合是NFA接受的語言。這個語言是正則語言。

    例子

    對于正則表達式:[0-9]*[A-Z]+,對應的NFA就是將下面兩個NFA的節點3和節點4連接起來


    詞法分析

    對于NFA和DFA其實只要知道這么多和一些相應的算法就已經足夠了,相應的算法在后面提及,先完成詞法分析的部分,

    這個詞法分析比之前C語言編譯器的語法分析要簡單許多,只要處理幾種可能性

  • 普通字符
  • 含有語義的字符
  • 轉義字符
  • token

    token沒什么好說的,就是對應正則里的語法

    Tokens = {'.': Token.ANY,'^': Token.AT_BOL,'$': Token.AT_EOL,']': Token.CCL_END,'[': Token.CCL_START,'}': Token.CLOSE_CURLY,')': Token.CLOSE_PAREN,'*': Token.CLOSURE,'-': Token.DASH,'{': Token.OPEN_CURLY,'(': Token.OPEN_PAREN,'?': Token.OPTIONAL,'|': Token.OR,'+': Token.PLUS_CLOSE, }

    advance

    advance是詞法分析里最主要的函數,用來返回當前輸入字符的Token類型

    def advance(self):pos = self.pospattern = self.patternif pos > len(pattern) - 1:self.current_token = Token.EOSreturn Token.EOStext = self.lexeme = pattern[pos]if text == '\\':self.isescape = not self.isescapeself.pos = self.pos + 1self.current_token = self.handle_escape()else:self.current_token = self.handle_semantic_l(text)return self.current_token

    advance的主要邏輯就是讀入當前字符,再來判斷是否是轉義字符或者是其它字符

    handle_escape用來處理轉義字符,當然轉義字符最后本質上返回的還是普通字符類型,這個函數的主要功能就是來記錄當前轉義后的字符,然后賦值給lexem,供之后構造自動機使用

    handle_semantic_l只有兩行,一是查表,這個表保存了所有的擁有語義的字符,如果查不到就直接返回普通字符類型了

    完整代碼就不放上來了,都在github上

    構造NFA

    構造NFA的主要文件都在nfa包下,nfa.py是NFA節點的定義,construction.py是實現對NFA的構造

    NFA節點定義

    NFA節點的定義也很簡單,其實這個正則表達式引擎完整的實現只有900行左右,每一部分拆開看都非常簡單

    • edge和input_set都是用來指示邊的,邊一共可能有四種種可能的屬性

      • 對應的節點有兩個出去的ε邊
        edge = PSILON = -1
      • 邊對應的是字符集
        edge = CCL = -2
        input_set = 相應字符集
      • 一條ε邊
        edge = EMPTY = -3
      • 邊對應的是單獨的一個輸入字符c
        edge = c
    • status_num每個節點都有唯一的一個標識
    • visited則是為了debug用來遍歷NFA

    class Nfa(object):STATUS_NUM = 0def __init__(self):self.edge = EPSILONself.next_1 = Noneself.next_2 = Noneself.visited = Falseself.input_set = set()self.set_status_num()def set_status_num(self):self.status_num = Nfa.STATUS_NUMNfa.STATUS_NUM = Nfa.STATUS_NUM + 1def set_input_set(self):self.input_set = set()for i in range(ASCII_COUNT):self.input_set.add(chr(i))

    簡單節點的構造

    節點的構造在nfa.construction下,這里為了簡化代碼把Lexer作為全局變量,讓所有函數共享

    正則表達式的BNF范式如下,這樣我們可以采用自頂向下來分析,從最頂層的group開始向下遞歸

    group ::= ("(" expr ")")* expr ::= factor_conn ("|" factor_conn)* factor_conn ::= factor | factor factor* factor ::= (term | term ("*" | "+" | "?"))* term ::= char | "[" char "-" char "]" | .

    BNF在之前寫C語言編譯器的時候有提到:從零寫一個編譯器(二)

    主入口

    這里為了簡化代碼,就把詞法分析器作為全局變量,讓所有函數共享

    主要邏輯非常簡單,就是初始化詞法分析器,然后傳入NFA頭節點開始進行遞歸創建節點

    def pattern(pattern_string):global lexerlexer = Lexer(pattern_string)lexer.advance()nfa_pair = NfaPair()group(nfa_pair)return nfa_pair.start_node

    term

    雖然是采用的是自頂向下的語法分析,但是從自底向上看會更容易理解,term是最底部的構建,也就是像單個字符、字符集、.符號的節點的構建

    term ::= char | "[" char "-" char "]" | | .

    term的主要邏輯就是根據當前讀入的字符來判斷應該構建什么節點

    def term(pair_out):if lexer.match(Token.L):nfa_single_char(pair_out)elif lexer.match(Token.ANY):nfa_dot_char(pair_out)elif lexer.match(Token.CCL_START):nfa_set_nega_char(pair_out)

    三種節點的構造函數都很簡單,下面圖都是用markdown的mermaid隨便畫畫的

    • nfa_single_char

    單個字符的NFA構造就是創建兩個節點然后把當前匹配的字符作為edge

    def nfa_single_char(pair_out):if not lexer.match(Token.L):return Falsestart = pair_out.start_node = Nfa()pair_out.end_node = pair_out.start_node.next_1 = Nfa()start.edge = lexer.lexemelexer.advance()return True
    • nfa_dot_char

    . 這個的NFA和上面單字符的唯一區別就是它的edge被設置為CCL,并且設置了input_set

    # . 匹配任意單個字符 def nfa_dot_char(pair_out):if not lexer.match(Token.ANY):return Falsestart = pair_out.start_node = Nfa()pair_out.end_node = pair_out.start_node.next_1 = Nfa()start.edge = CCLstart.set_input_set()lexer.advance()return False
    • nfa_set_nega_char

    這個函數邏輯上只比上面的多了一個處理input_set

    def nfa_set_nega_char(pair_out):if not lexer.match(Token.CCL_START):return Falseneagtion = Falselexer.advance()if lexer.match(Token.AT_BOL):neagtion = Truestart = pair_out.start_node = Nfa()start.next_1 = pair_out.end_node = Nfa()start.edge = CCLdodash(start.input_set)if neagtion:char_set_inversion(start.input_set)lexer.advance()return True

    小結

    篇幅原因,現在已經寫到了三百多行,所以就分篇寫,準備在三篇內完成。下一篇寫構造更復雜的NFA和通過構造的NFA來分析輸入字符串。最后寫從NFA轉換到DFA,再最后用DFA分析輸入的字符串

    轉載于:https://www.cnblogs.com/secoding/p/11576864.html

    總結

    以上是生活随笔為你收集整理的实现一个正则表达式引擎in Python(一)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。