[編譯原理讀書筆記][第3章 詞法分析]

[編譯原理讀書筆記][第3章 詞法分析]

標籤(空格分隔): 未分類node


  • 本章咱們主要討論如何構建一個詞法分析器
    • 首先創建起每一個詞法單元的詞法結構圖或其餘描述.
    • 編寫代碼識別輸入中出現的每一個詞素,並返回識別到詞法單元的有關信息
  • 詞法分析器生成工具(lexical-analyzer generator)
    • 描述出詞素模式,而後將這些模式編譯爲具備詞法分析功能的代碼.
    • 程序員只要在抽象很高的層次上描述軟件,就能生成代碼.
    • 3.5節將介紹一個名爲Lex分析器生成工具
  • 正則表達式
    • 正則表達式是一種很方便描述詞素模式的方法.
    • 咱們將介紹正則表達式進行轉換:
      • 首先轉換爲不肯定有窮自動機.
      • 而後轉換爲肯定又窮自動機.
    • 驅動程序就是模擬這些自動機的代碼,使用自動機肯定下一個詞法單元.
  • 驅動程序和自動機的規約造成詞法分析器的核心部分.

3.1 詞法分析器的做用

詞法分析是編譯的第一階段.程序員

  • 詞法分析器的主要任務是
    • 1.讀入源程序的輸入字符
    • 2.將他們組成詞素,生成並輸出一個詞法單元序列,每一個詞法單元對應一個詞素.
  • 詞法分析,語法分析,符號表的交互
    image_1b0p9hhn422lfakl33pb8o2k9.png-25.4kB
    • getNextToken所指示的調用使得詞法單元從它的輸入不斷讀取字符,直到識別到下一個詞素爲止.
    • 詞法單元根據詞生成一個詞法單元返回給語法分析.
  • 詞法分析其他任務
    • 過濾註釋和空白
    • 編譯器生成的錯誤信息,和源代碼的位置練習起來
  • 有時候,詞法分析分紅兩個級聯處理
    • 掃描階段: 不生成詞法的單元的簡單處理:刪除註釋和將多個空白壓縮成一個
    • 分析極端:處理掃描階段的輸出,返回詞法單元

3.1.1 詞法分析及語法分析

把編譯階段的分析部分化爲詞法分析和語法分析階段有以下幾個緣由:正則表達式

  • 最重要的考慮是簡化編譯器設計
  • 提升編譯器效率(由於能專精)
  • 加強編譯器的可移植性.

3.1.2 詞法單元,模式和詞素

三個相關但有區別的術語.
image_1b0pap3du1gmc1n00105l5d1cbh13.png-37kB算法

  • 詞法單元:由一個詞法單元名和一個可選屬性組成.
    • 詞法單元名:是一種表示某種詞法單元的抽象符號.
      • 好比一個關鍵詞,標識符的輸入字符序列.
      • 詞法單元名是由語法分析處理的輸入符號.
      • 一般用黑體字表示詞法單元名
  • 模式:描述一個詞法單元的詞素可能具備的形式.
    • 詞法單元是關鍵詞時:
      • 模式是組成這個關鍵詞的字符序列
    • 對於標識符和其餘詞法單元:
      • 模式是一個更加複雜的結構,能夠和不少符號串匹配.
    • 正則表達式
  • 詞素:是源程序的一個字符序列.
    • 和某個詞法單元的模式匹配
    • 被詞法分析器識別爲該詞法單元的一個實例.

在不少程序設計語言中,下面類別覆蓋了大多數詞法單元vim

  • 每一個關鍵詞有一個詞法單元.
    • 一個關鍵詞的模式是他自己.
  • 表示運算符的詞法單元
  • 表示全部標識符的詞法單元
  • 一個或多個表示常量的詞法單元
  • 每個標點符號 有一個詞法單元

3.1.3 詞法單元的屬性

  • 詞法單元的屬性:若是多個詞素能夠和一個模式匹配,那麼詞法分析器必須向編譯器的後續階段提供被匹配詞素的附加信息.
    • 一個標識符的屬性值是一個指向符號表中該標識符條目的指針.

image_1b0pbat8p15otmqo1fuh1anfbn1g.png-37.6kB

3.1.4 詞法錯誤

  • 若是沒有其餘組件幫助,詞法分析器很難發現源代碼的錯誤.數組

    好比:安全

    fi(a==f(x))
    • 詞法分析器會當作一個標識符做爲詞法單元傳給語法分析器
    • 這個錯誤將由語法分析器處理.
  • 恐慌模式:全部詞法單元都沒法和剩餘輸入的某個前綴相匹配時的策略
    • 咱們從剩餘的輸入不斷刪除字符,直到詞法分析器可以在剩餘的開頭髮現一個正確的詞法單元爲止.

3.1.5 練習

image_1b0pbqj101i0q11jd3ihsea95s1t.png-33.1kB

<float> <id, limitedSquaare> <(> <id, x> <)> <{>
  <float> <id, x>
  <return> <(> <id, x> <op,"<="> <num, -10.0> <op, "||"> <id, x> <op, ">="> <num, 10.0> <)> <op, "?"> <num, 100> <op, ":"> <id, x> <op, "*"> <id, x>
<}>

image_1b0pchipq1no61havj4r10n7vik2a.png-63.1kB

<text, "Here is a photo of"> <nodestart, b> <text, "my house"> <nodeend, b>
<nodestart, p> <selfendnode, img> <selfendnode, br>
<text, "see"> <nodestart, a> <text, "More Picture"> <nodeend, a>
<text, "if you liked that one."> <nodeend, p>

3.2 輸入緩衝

討論幾種能夠加快源程序讀入速度的方法.數據結構

  • 咱們將介紹一種雙緩衝區方案
    • 這種方案能安全處理向前看多個符號問題.
    • -,=,< 多是->,==,<=這樣雙字符運算符的開始.
  • 咱們將考慮一種改進方法,這種方法用哨兵標記來節約檢查緩衝區末端的時間.

3.2.1 緩衝區對

何爲緩衝區對?

因爲在編譯一個大型程序須要處理大量的字符,處理這些字符須要不少時間,由此開發了一些特殊的緩衝技術來減小用於處理單個輸入字符的時間開銷.一種重要機制是利用兩個交替讀入的緩衝區.閉包

image_1b0pq7rf2is314dk1q0t67p46q2n.png-14.5kB

緩衝區具體

  • 每一個緩衝區容量都是N個字符,一般N是一塊磁盤塊大小,如4096字節
  • 程序爲輸入維護了兩個指針
    • lexemeBegin指針:該指針指向當前詞素的開始處.當前咱們正試圖肯定這個詞素的結尾.
    • forward指針:它一直向前掃,直到發現某個模式被匹配到爲止
      • 作出這個決定的策略在以後描述.
  • 一旦肯定下一個詞素,forward指針將指向該詞素結尾的字符.
    • 詞法分析器將這個詞素做爲某個詞法單元記錄下來並返回.
    • 而後使lexemeBegin指針指向剛找到詞素後的第一個位置
    • 處理完後,forward指針也會前移一個位置
  • 若是forward指針前移超過緩衝區末尾(哨兵標記優化的地方)
    • 將N個新字符讀到另外一個緩衝區末尾,且將forward指針指向這個新載入符的頭部.

3.2.2 哨兵標記

如何優化檢測緩衝區末尾呢?

思考以前的有兩步操做編輯器

  • 檢查是否到了末尾
  • 肯定讀入的字符

將兩步合二爲一

在緩衝區末尾擴展一個絕對不會使用的符號,叫作哨兵(sentinel)字符,一個天然的選擇是eof.

image_1b0pu33in1030u7quls133t1sv734.png-17.5kB

image_1b0pu3bi21nn16732v249cr1d3h.png-45.8kB

3.3 詞法單元的規約

  • 正則表達式是一種用來描述詞素模式的重要表示方法.
    • 雖然不能表達全部可能的模式,但能高效描述在處理詞法單元時要用到的模式類型.
  • 這一節咱們將研究正則表達式的形式化表示方法

  • 在3.5節中 咱們將看到如何將正則表達式用到詞法分析生成工具中.
  • 在3.7節中 咱們將學到如何能將正則表達式轉換爲可以識別詞法單元的自動機,並由此創建一個詞法分析樹.

3.3.1 串和語言

  • 字母表(alphabet)是一個有限的符號集合.
    • {0,1},ASCII,Unicode .
  • 某個字母表的串(string)是該字母表符號的有窮序列.

image_1b0pvmc611qni17mvvjlvvr1gl33u.png-104.7kB

  • 語言(language)是某個給定字符表上任意的可數的串的集合.

  • 字符串的指數運算

    image_1b0q0g3ss44teshgmv3534jg4b.png-22.6kB

3.3.2 語言上的運算

在詞法分析,最重要的語言上的運算是,鏈接,和閉包運算.

image_1b0q1sim5sq61dn71o7n1b7034i4o.png-27.6kB
image_1b0q1us2jjaj1rum1rd62dt7ko55.png-109.3kB

3.3.3 正則表達式

  • 正則表達式能夠由較小的正則表達式按照以下規則遞歸地構建.

    • 每一個正則表達式r表示一個語言L(r),也是根據r的子表達式所構建的語言遞歸構造.
  • 某個字母表Σ上的正則表達式以及這些表達式所表示的語言

    image_1b0q2ilahg5126811tn18rp1fu15i.png-103.9kB

  • 根據優先級丟掉括號

    • 一元運算符*是最高級,而且是左結合.
    • 連接運算符次高的優先級,也是左結合
    • |的遊戲級最低,也是左結合.
    • (a)|((b)*(c))改寫爲a|b*c
  • 例子:
    image_1b0q2n98f1pd311pujuh15o21ipu5v.png-84.6kB

正則集合

  • 能夠用同一個正則表達式定義的語言叫作正則集合(regular set)

  • 若是兩個正則表達式rs表示的語言相同的語言,則稱二者等價,記作r=s.

  • 正則表達式遵照必定的代數定律

    image_1b0q4rjp81iklij2g261lkagr86c.png-36.6kB

3.3.4 正則定義

  • 爲方便表示,咱們可能但願給某些正則表達式命名,並在以後像使用符號同樣使用這些名字,這叫作正則定義(regular definition)是具備以下形式的定義序列:
    image_1b0q4uigojrr1r0j1vjk159o1d0h6p.png-2.6kB
    • 每一個di都是一個新符號,不在Σ中,而且各不相同.
    • 每一個ri是字母表`Σ U {d1,d2,...di-1}上的正則表達式.

image_1b0q571241gbf176g1f841gaaveb76.png-76.3kB

  • 能夠看出 遞歸定義是正則表達式的很重要的性質

3.3.5 正則表達式的擴展

除了以上的運算,在現代像Lex這樣的實用Unix程序都有對正則的擴展

  • 一個或多個實例:+
    • 單目後綴運算符+表示一個正則表達式及其語言的正閉包.
    • (r)+ 意思爲 (L(r))+
    • +*有相同的優先級
  • 零個或一個實例:?
    • 單目後綴運算符?的意思是零個和一個出現.
    • r?等價於r|空集
    • ?+*有相同的運算集
  • 字符類:[]
    • 一個正則表達式 a1 | a2 |...|an能夠縮寫爲[a1a2...an]
    • 若是a1,a2,a2還具備邏輯關係,能夠縮寫爲[a1-an]
      • 例如[1-9],[a-z]

image_1b0q6oukq6dh166818rf1fdq13n27j.png-34.5kB

3.3.6 3.3練習

image_1b0urvdrtlr4acrml572olm09.png-120.8kB

  • 第三題
    \/\*([^*"]*|".*"|\*+[^/])*\*\/
  • 第四題
    先解決比較簡單的{0,1,2}
    
    SB解法
      0?1?2?|0?2?1?|1?0?2?|1?2?0?|2?0?1?|2?1?0?|
    正確解法
        want -> 0|A?0?1(A0?1|01)*A?0?|A0?
        A -> 0?2(02)*
      證實以下面的圖

    image_1b0ust8t2100m13k516hno31r8am.png-42.7kB
    step3
    image_1b0usuv7f1ccsi8n1olu1f1cptm13.png-49.5kB
    image_1b0usvhqptmn1tufv0gebqtnr1g.png-49.6kB

  • 5-7太難
    第八題:
    b*(a+b?)*
    第九題:
    b* | b*a+ | b*a+ba*

###Lex的擴展方法:
image_1b0uti7uvshulkk1io51u3414a01t.png-90.9kB
###如何引用這些被使用的符號
image_1b0utm1rb10g7dcb9s01trvpo2a.png-93.6kB

#3.4 詞法單元的識別

  • 咱們學習如何根據各個須要識別的詞法單元的模式來構造一段代碼.
    • 識別檢查輸入的串,並找到匹配的詞素.

image_1b0v32hb4qo5ri5n8nq1ncdc2n.png-59.7kB
image_1b0v332c01c5jihnp5d19611b4v34.png-41.6kB
image_1b0v3380m1vqj1goc18uj14m0g03h.png-51.2kB

3.4.1 狀態轉換圖

介紹

  • 詞法分析器的中間步驟,將模式轉換爲狀態轉換圖
  • 本節用人工方式,將正則表示的模式轉換爲狀態圖
    • 3.6節介紹一種使用自動化方法來進行
  • 狀態轉換圖有一組被稱爲狀態的結點和圓圈.

例子

image_1b0v5ca9283qucicdbenc1poi3u.png-160.8kB

image_1b0v5hvpo12kd12461egv1nqe1g964b.png-57.5kB

3.4.2 保留字和標識符的識別

image_1b0v60djjtoh1k9pcvr4c20t4o.png-25.4kB

咱們能夠用兩種方法處理像標識符的保留字.

  • 初始化時將保留字填入符號表
  • 爲每一個關鍵字創建單獨的狀態轉換圖

    image_1b0v636uf1fn11qbe1n1ln7u19do55.png-23.3kB

3.4.3 完成咱們的例子

image_1b0v66ien13be6d21f7j1abn13os5i.png-47.3kB

image_1b0v66v74ss2lip1405lv75n15v.png-17.3kB

3.4.4 基於狀態轉換圖的詞法分析器的體系結構

有幾種方法根據一組狀態圖構造出詞法分析器.

例子

根據這個狀態圖,寫出getRelop()函數
image_1b0v5hvpo12kd12461egv1nqe1g964b.png-57.5kB
image_1b0v9tn7n1nl11teu1n8h1pnh196c6c.png-68.6kB

  • 函數fail()具體操做依賴於全局恢復策略
    • forward指針重置爲lexemeBegin的值
    • 使用另外一個狀態圖從還沒有處理的輸入部分的真實位置開始識別.
    • 將state改成另外一狀態圖的初始狀態,將尋找別的詞法單元
    • 當全部狀態圖都已經試過,fail()啓動一個錯誤糾正步驟.
  • 狀態8,帶有*,輸入指針會回退,c放回輸入流

    • retract()完成
  • 狀態圖的執行

    • 順序
    • 並行
      • 取最長的
    • 合併(麻煩)

3.4.5 3.4的練習

3.5 詞法分析器生成工具 Lex

介紹一個名爲Lex的工具,在最近的實現中也稱爲Flex.

  • 支持使用正則表達式來描述詞法單元的模式,給出一個詞法分析器的規約
  • 輸入表示方法叫作Lex語言,工具自己是Lex編譯器.
  • 核心部分: 根據模式,生成轉換圖,生成相應代碼,存放到lex.yy.c中

3.5.1 Lex的使用

image_1b10om0bln8crv717q811h6ukj9.png-31.5kB

  • a.out 一般是語法分析器調用的子例程
  • 子例程一般返回一個整數值,表明詞法單元的編碼
  • 詞法單元的屬性值,保存在全局變量yylval
    • 這個變量由語法分析器,詞法分析器共享.

3.5.2 Lex 程序的結構

image_1b10p07ici1gid11f892kdikom.png-15.8kB

  • 聲明部分包括變量和明示常量(manifest con-stant)和正則定義
    • 明示常量,表示一個常數的標識符
  • Lex的轉換規則有以下形式
    模式 {動做}
    • 每一個模式是一個正則表達式,可使用聲明部分的正則定義.
    • 動做部分是代碼片斷,通常是C語言
  • Lex第三個部分包含各個動做要的輔助函數.
    • 還有一種方法將這些代碼單獨編譯,一塊兒裝載

lex詞法分析器和語法分析器的協同

  • 當詞法分析器被語法分析器調用時,詞法分析從餘下輸入逐個讀取字符.
    • 直到發現最長的與某個模式Pi匹配的前綴.
  • 而後詞法分析器執行動做Ai.
    • 一般Ai會返回語法分析器
    • 若是不返回控制,繼續尋找其餘詞素,直到某個動做返回
  • 詞法分析器只返回一個值,即詞法單元名
    • 在須要時,經過yylval傳遞詞素附加信息

例子

image_1b10tfd071j97akm1274v0tq0v13.png-72.5kB
image_1b10tfl4q1p8asnggh2759166f1g.png-27.6kB

  • %{ %}
    image_1b10u1piq1fca47ld3q9gqhf1t.png-53kB
    image_1b10u6s1f11u91flhur81scjpsb2n.png-20.5kB

  • 處理名爲ID的詞法單元的時候

    image_1b10ubnfath3v981ltf1s0ddaq34.png-51kB
    image_1b10uc2tnk081j2i14tdg7qs0c3h.png-6.5kB
    image_1b10uccl6hn9rpf1uh11p9hr2c3u.png-7.2kB

3.5.3 Lex中的衝突解決

  • 老是選擇最長的前綴
  • 若是最長的前綴與多個模式匹配,選擇聲明靠前的那個.

3.5.4 向前看運算符

某些時候,咱們但願僅僅詞素後面跟隨特定字符,才能和模式匹配.

  • 這種狀況,咱們使用/以後跟隨表示一個附加的模式.
  • 附加部分能匹配,但最後不屬於詞素

image_1b10vfp5d6tka1k11fn99h11f84b.png-71.8kB

3.6 有窮自動機

揭示Lex如何將輸入程序轉換爲詞法分析器,核心在於有窮自動機

這些自動機本質和轉換狀態圖相似,但也有如下不一樣

  • 有窮自動機是識別器(recognizer).
    • 只能對每一個輸入串簡單的回答 是,否.
  • 有窮自動機分爲兩類
    • 不肯定的有窮自動機(NFA):對其邊上的標號沒有任何限制,一個符號能夠標記離開同一個狀態的多條邊.
    • 肯定的有窮自動機(DFA):對於每一個符號,有且一條離開該狀態的邊.

肯定和不肯定的能識別的語言的集合是相同的,剛好也是正則表達式能識別的集合,這個集合的語言叫作正則語言

3.6.1 不肯定的有窮自動機

一個NFA由如下部分組成

  • 一個有窮狀態集合S
  • 一個輸入符號集合Σ,即輸入字母表.
    • 假設空串不在這個集合.
  • 一個轉換函數,他爲每一個狀態給出了相應的後繼狀態.
  • S中的s0被稱爲開始狀態
  • S的一個子集F被稱爲接受狀態

跟轉換圖有如下區別

  • 同一個符號能夠標記同一狀態到達多個目標狀態的多條邊.
  • 一個邊的標號不只能夠是輸入字母表的字符,也但是空串

例子

(a|b)*abb的NFA轉換圖

image_1b11lea581tdb16p01se319741en84o.png-19.9kB

3.6.2 轉換表

image_1b11ljvge14i50v84kv901kp355.png-20.7kB

3.6.3 自動機輸入字符串的接受

一個NFA接受輸入字符串x,**當且僅當對應的轉換圖中存在一條開始狀態到某個接受狀態的路徑,使得路徑中各條變上的標號組成字符串x.

  • 注意:路徑的ε被忽略

  • 咱們能夠用L(A)表示自動機A接受的語言

3.6.4 肯定的有窮自動機

image_1b11m6pnng2k1soon3g16o11uer5i.png-36.9kB

  • 在構造詞法分析器時,咱們真正實現和模擬的是DFA.
  • ?幸運的是,每一個正則表達式和每一個DFA均可以轉變爲接受相同語言的DFA.

如何用DFA進行串的識別(十分簡單)

image_1b11mhelpga93b1lt41115h3f5v.png-24.4kB

3.7 從正則表達式到自動機

  • 首先介紹如何把NFA轉換爲DFA.
    • 利用子集構造法的技術給出一個直接模擬NFA的算法
      • 這個算法可用於那些將NFA轉化爲DFA比直接模擬NFA更加耗時的(非詞法分析)情形
  • 接着,咱們介紹正則表達式轉爲NFA
    • 在必要時刻,根據這個NFA構造DFA
  • 最後討論不一樣正則表達式實現技術的時間-空間權衡,並說明如何選擇正確的方法.

3.7.1 從NFA到DFA的自動轉換

子集構造法的基本思想:是讓構造獲得的DFA的每一個狀態對應於NFA的一個狀態集合.

  • DFA在讀入輸入a1a2...an以後到達的狀態對應於相應NFA從開始狀態出發,沿着以a1a2...an爲標號走到的路徑可以到達狀態的集合.

  • DFA狀態數多是NFA狀態數的指數,此時試圖實現這個DNA有點困難.

  • 然而,基於自動機的詞法分析方法的處理能力部分基於這個事實:
    • 對於一個真實的語言,它的NFA和DFA的狀態數量大體相同,狀態數量呈指數還沒有在實踐中出現

子集構造算法

輸入: 一個NFA N
輸出: 一個接受一樣語言的 DFA D

方法: 咱們的算法爲D構造一個轉換表Dtran.

  • D的每一個狀態是一個NFA狀態集合,咱們將構造Dtran,使得D "並行的" 模擬N在遇到一個給定輸入串可能執行的全部動做.

  • 第一個問題:正確處理N的`轉換.
  • 給出一些基本操做 s表示N的單個狀態,T表示一個狀態集.

    image_1b11tq5ld1r9m5nebs6n2h1nf06c.png-57.8kB

  • 算法代碼(有ACM功底很容易懂):

    image_1b11v9sb21u9o1en15pu90em396p.png-46.5kB

  • D的開始狀態是ε-closure(s0),D的接受狀態是全部至少包含N的一個接受狀態的狀態集合,

  • ε-closure(T)代碼,一個簡單的深搜而已

    image_1b11vfom51qv9qfo1nv41rsc1ehd7j.png-51kB

例子

image_1b11vld203g61jd1p8863gevc80.png-46kB

image_1b11vlia71mud1kl81c574h41l0h8d.png-31.1kB

image_1b11vo0gvemgpca1cv71mcvbeg8q.png-32.5kB

  • A和C能夠合併,將在後面介紹DFA的最小化問題.

3.7.2 NFA的模擬

許多的文本編輯器使用的策略是根據一個正則表達式構造出相應的NFA,而後使用相似於on the fly(邊構造邊)的子集構造法來模擬這個NFA的執行.

算法:模擬一個NFA的執行

也相似廣搜,一步一步把全部狀況跑一遍.

image_1b120u3eb15n31tbfft21pp11bgs97.png-30.6kB

3.7.3 NFA模擬的效率

更詳細的介紹這個算法

須要如下數據結構

  • 兩個堆棧,其中每一個堆棧都存放了一個NFA狀態集合.
    • 堆棧oldStates存放 "當前狀態集合"
    • 堆棧newStates存放 "下一個狀態集合"
  • 一個以NFA狀態爲下標的布爾數組 alreadyOn,指示那個狀態已經在newStates.

  • 一個二維數組 move[s,a],保存了這個NFA的轉換表,是一個鄰接表(由於轉換表單元格有多個元素).

image_1b123s3c8gup1tu7ekq13ims19k.png-30.5kB
image_1b123sae61o5shbo1pr31m0j141va1.png-40.8kB

  • 既然書說他是O(k(m+n))複雜度...那就是吧

3.7.4 從正則表達式構造NFA

如今給出一個算法,它能夠將任何正則表達式轉爲接受相同語言的NFA

  • 這個算法是語法制導的,沿着正則表達式的語法分析樹自底向上遞歸處理.
  • 對於每一個子表達式,該算法構造一個只有一個

算法 3.23 將正則表達式 轉換爲一個NFA的McMaughton-Yamada-Thompson算法

image_1b12619mjpao1in5bll1hsoo19ae.png-62.6kB

  • 基本規則

    • image_1b13vmvob1bca143ir5p1et91tk69.png-97.9kB
  • r=s|t

    image_1b1419e5j1ng318tshi9176n1215m.png-30.1kB

  • r=st

    image_1b1419q161b611042eeh89911c613.png-20.6kB

  • r=s*

    image_1b141ah881j4e3qs1uee1t9anpj1g.png-31.8kB

  • 幾個有趣的性質

    • N(r)的狀態數最可能是 r中出現的運算符和運算份量的總數的2倍.
    • N(r)有且只有一個開始狀態,接收狀態沒有出邊,開始狀態沒有入邊.
    • 除接受狀態,都最多有一條帶字符的出邊,最多有兩條空邊

例子

image_1b141tav21v7r1f1drnfe3n1qt61t.png-16.5kB
image_1b141tv99cka1bom14u919k9bc62n.png-33.9kB

3.7.5 字符串處理算法的效率

image_1b141vr7k1cklfo9t1kr3aa34.png-101.7kB

  • 子集構造法 : O(|r|^2 * DFA狀態數) DFA狀態數通常是|r|

  • 模擬算法: O(|r|*|x|)

3.8 詞法分析器生成工具的設計

  • 應用3.7中介紹的技術,討論Lex這樣生成工具的體系
  • 將討論基於NFADFA的方法,後者實質上就是Lex的實現方法

3.8.1 生成的詞法分析器的結構

image_1b1448igr14fire5c6d92916mb3h.png-47.8kB
image_1b147iun2om21iljr421agduu3u.png-51.2kB

image_1b147p23c1vfp1dr6fh11ql2t904b.png-15.4kB
image_1b147p5mi1ks61cbp1m4sci91j7l4o.png-38.6kB

3.8.2 基於NFA的模式匹配

image_1b147p5mi1ks61cbp1m4sci91j7l4o.png-38.6kB
image_1b14889nlhe3u4kmm86ca1ji655.png-22.1kB

  • 沿着狀態集回頭尋找直到有一個完成狀態

3.8.3 詞法分析器使用的DFA

image_1b148c2la1256152u1o3914is13tj5i.png-63.4kB

image_1b148hn5j110s1dh817vmceh1eeg5v.png-21kB

3.8.4 實現向前看運算符/

image_1b149i0he1rik1ihfghg3le1t7p6c.png-24.2kB

3.9 基於DFA 的模式匹配器的優化

本節給出三個算法,用於實現和優化根據正則表達式構建的模式匹配器

  • 第一個算法能夠用於Lex編譯器
    • 能夠不用通過構造中間NFA就能構建DFA,
    • 獲得的DFA狀態數也比通過中間NFA獲得的DFA狀態數少
  • 第二個算法,能夠將任何DFA將來具備相同行爲的狀態合併.
    • 算法複雜度O(nlogn)
  • 第三個算法,能夠生成比標準二維表更加緊湊的轉換表方式.

3.9.1 NFA的重要狀態

  • 若是一個NFA狀態有一個標號非ε的離開轉換,則稱這個狀態是重要狀態(important state)

    • ε-closure(move(T,a)),它只使用了集合T中的重要狀態.
    • 只要當集合s是重要狀態時,move(s,a)纔多是非空的.
  • 在子集構造法中,兩個NFA狀態能夠被認爲是一致的的條件是:
    • 1.具備相同的重要狀態
    • 2.要麼包含接受狀態,要麼都不包含接受狀態.
    • 一致的意思是把它們當作同一個集合來處理.

正則轉換的NFA

  • 若是NFA由正則表達式生成,還有更多關於重要狀態的性質
    • 重要狀態只包括在基礎規則部分爲正則表達式中某個特定符號位置引入的初始狀態.
    • 也就是說,每一個重要狀態對應於正則表達式某個運算份量.
    • 在正則表達式後面加一個#變爲(r)#,使得本來的接受狀態變爲重要狀態,使得在構造過程當中,不要考慮接受狀態.
      • (r)#又叫擴展的正則變道時
  • 使用抽象語法樹表示擴展的正則表達式.

    • 葉子節點表明運算份量
    • 內部節點表示運算符號

image_1b169r17nn111fst18ps1g3u17bpv.png-53.6kB

  • 整數叫作葉子節點的位置,也表示他對應符號的位置
    • 一個符號能夠有兩個位置:好比a有1和3

image_1b169uo74h621fqkg821brud3k1c.png-54kB

3.9.2 根據抽象語法樹計算的到的函數

  • 要作一個正則表達式直接構造出DFA,咱們首先要構造它的抽象語法樹
  • 而後計算以下四個函數:nullable,firstpos,lastposfollowpos.
    • 都是在(r)#下進行的.
    • nullable(n):對於一個節點n當且僅當此節點表明的子表達式中包含空串ε返回真
      • 子表達式能夠生成空串或者本身就是空串,即便能生成一些其餘串
    • firstpos(n):以節點n爲根的子樹中的位置集合.
      • 這些位置對應於以n爲根的子表達式的語言中某個串的第一個符號.
    • lastpos(n):以節點n爲根的子樹中的位置集合.
      • 這些位置對應於以n爲根的子表達式的語言中某個串的最後一個符號.
    • followpos(p):定義了一個和位置p相關的,某些位置的集合.
      • 一個位置qfollowpos(p)中當且僅當存在L((r)#)中的某個串x=a1a2...an,使得咱們在解釋爲何x屬於L((r)#)時,能夠將x中某個ai能夠和位置p匹配,且將位置ai+1和位置q匹配

3.9.3 計算nullable,firstpos和lastpos

  • 直接樹形遞歸就行

image_1b16fqeuf1s6c1ai91agbgci13fi26.png-70.1kB

3.9.4 計算followpos

只有兩種狀況,正則表達式某個位置會跟在另外一個位置以後

  • 若是n是一個cat節點,且左右節點是c1,c2.
    • 那麼對於lastpos(c1)中的每一個位置i,firstpos(c2)中的全部位置都在followpos(i)中.
    • 若是 n 是 star節點,而且i是 lastpos(n)中的一個位置,那麼firstpos(n)中的全部位置都在followpos(i)中.

例子

image_1b16k71lme3q1cvouk61gc519mr2j.png-18.5kB
image_1b16k79lkpf76d9m571i6g121g30.png-42.4kB

3.9.5 根據正則表達式構建DFA

算法3.3.6 從一個正則表達式r構造DNF

image_1b16k9b131kn783r1k6v29e1d93d.png-13.1kB

  • 根據擴展表達式(r)#構造出一顆抽象語法樹T
  • 計算T的函數nullable,firstpos,lastpos,followpos
  • 使用下列程序,構造出D的狀態集DstatesD的轉換函數Dtran

image_1b16lep9ug471bm6jmj1pdu854n.png-60.9kB

image_1b16le47n8a08f4m33m1bqv4a.png-30.3kB

3.9.6 最小化一個DFA的狀態數

(數電中有說起)

任何正則語言都一個惟一的(不計同構)狀態數目最少的DFA.,從任意一個接受相同語言的DFA出發,經過分組合並等價的狀態,咱們老是構建這個狀態數最少的DFA.

算法

  • 該算法首先建立輸入DFA的狀態的集合的劃分.

  • 輸入串如何區分各個狀態:
    • 若是分別從狀態s和t出發,沿着標號爲x的路徑到達的兩個狀態中只有一個是接受狀態,咱們說串x區分狀態s和狀態t的串
    • 若是存在一個串能區分s和t,那麼他們是可區分的.
  • DFA狀態最小化的工做原理是將一個DFA的狀態集合分劃成多個組,每一個組中的各個狀態之間相互不可區分.

算法3.39

image_1b16nqh851lvuvrttsg1oni5qe5h.png-42.4kB
image_1b179cpelo3n1sj13dk7bc16ei5u.png-49.6kB

以後就相似於縮點的算法.

例子版本:
image_1b16npg0f1se13ih13qh1flt48h54.png-270.5kB

3.9.8 DFA模擬中的時間和空間權衡

  • 轉換鏈表壓縮

    image_1b17b8mr82p71dsh1juc12km1icu6b.png-10.8kB

  • 一種方式,即有數組的便攜,又有鏈表壓縮的默認狀態.

    image_1b17bao291q0p6j2ofn1ab3eva6o.png-22.5kB

    image_1b17bbikvkfb10j21q54e64f1p75.png-87.7kB

    • 如何保存base值比較好

      image_1b17bda5f1sqo70bkddrgc1rgd7i.png-61.9kB

相關文章
相關標籤/搜索