C# 詞法分析器(一)詞法分析介紹

系列導航html

  1. (一)詞法分析介紹
  2. (二)輸入緩衝和代碼定位
  3. (三)正則表達式
  4. (四)構造 NFA
  5. (五)轉換 DFA
  6. (六)構造詞法分析器
  7. (七)總結

雖然文章的標題是詞法分析,但首先仍是要從編譯原理說開來。編譯原理應該不少人都據說過,雖然不必定會有多麼瞭解。前端

簡單的說,編譯原理就是研究如何進行編譯——也就如何從代碼(*.cs 文件)轉換爲計算機能夠執行的程序(*.exe 文件)。固然也有些語言如 JavaScript 是解釋執行的,它的代碼是直接被執行的,不須要生成可執行程序。git

編譯過程是很複雜的,它涉及到不少步驟,直接拿《編譯原理》(Compilers: Principles, Techniques and Tools,紅龍書)上的圖來看:github

圖 1 編譯器的各個步驟,實際上是我根據書上的圖綜合了一下後畫的正則表達式

這裏給出了 7 個步驟(後面的優化步驟是可選的),其中前 4 個步驟是分析部分(也被稱爲前端 front end),是把源程序分解爲多個組成要素,並在這些要素上加上語法結構,最後把信息存放在符號表(symbol table)中。後三個步驟是綜合部分(也成爲後端 back end),它們根據中間表示和符號表中的信息構造期待的目標程序。算法

將編譯器分爲這麼多步驟,其好處就是使得每一個步驟更加簡單,從而使編譯器更加容易設計,也能夠利用不少現有的工具——例如詞法分析器能夠用 Lex 或 Flex 生成,語法分析器能夠用 Yacc 或 Bison 生成,幾乎不用作太多編碼工做就能獲得一顆語法樹,前端的工做也就完成的差很少了。而至於後端,也有不少現有的技術可使用,例如現成的虛擬機(CLR 或 Java,只要翻譯成相應的 IL 就能夠了)。express

這個系列的文章,說的就是編譯原理的第一步:語法分析。大部分算法和理論都來自《編譯原理》,其他的部分則是本身搞出來的,或者是參考了 Flex 的實現(這裏的 Flex 是指 fast lexical analyzer generator,一個著名的提供詞法分析的程序,而不是 Adobe 的 Flex)。後端

我會盡可能完整的介紹詞法分析器的編寫過程,包括一些細節的實現。固然,目前只能根據正則表達式定義獲得一個能夠用來進行詞法分析的對象,要想達到 Flex 那樣直接根據詞法定義文件生成詞法分析器源代碼,還有不少工做要作,不是短時間內可以搞定的。工具

本篇文章做爲系列的第一篇,將會對詞法分析作綜合的概述,介紹一下其中用到的技術和大體的流程。post

1、詞法分析介紹

詞法分析(lexical analysis)或掃描(scanning)是編譯器的第一個步驟。詞法分析器讀入組成源程序的字符流,而且將它們組織成有意義的詞素(lexeme)的序列,並對每一個詞素產生詞法單元(token)做爲輸出。

簡單的來講,詞法分析就是將源程序(能夠認爲是一個很長的字符串)讀進來,而且「切」成小段(每一段就是一個詞法單元 token),每一個單元都是有具體的意義的,例如表示某個特定的關鍵詞,或者表明一個數字。而這個詞法單元在源程序中對應的文本,就叫作「詞素」。

以計算器來舉例,12+34*9 這一段「源程序」的詞法分析過程以下所示:

圖 2 算式的詞法分析過程

一段對計算機來講豪無心義的字符串,通過語法分析後就獲得了略微有意義的 Token 流。digit 就表示這個詞法單元對應的是數字,operator 則表示操做符,後面相應的數字和符號(粉色背景)就是詞素。同時,程序中一些沒必要要的空白、註釋也能夠由詞法分析器來過濾掉,這樣,以後的語法分析等步驟處理起來就會容易得多。

在實際的程序中,詞法單元都會以枚舉或數字來表示這是哪一類詞法單元。個人 Token<T> 類 定義以下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace  Cyjb.Text {
     class  Token<T> {
         // 詞法單元的標識符,表示詞法單元的類型。
         T Id;
         // 詞法單元的文本,即「詞素」。
         string  Text;
         // 獲取詞法單元的起始位置。
         SourceLocation Start;
         // 獲取詞法單元的結束位置。
         SourceLocation End;
         // 獲取詞法單元的值。
         object  Value;
     }
}

裏面的 Id 和 Text 屬性沒必要多作解釋,Start 和 End 是用來在源文件中定位的(索引,行數和列數),Value 則僅僅是爲了方便傳遞一些值而設。

2014.1.8 更新:這個 Token<T> 類,最開始的定義是一個 Token 結構,詞法單元的標識符是使用一個 int 值表示的。可是,我的認爲使用枚舉類型要更好些,由於枚舉類型是具備名稱的,這樣每一個標識符能夠很好的體現其語意;並且具備編譯期檢查,可以有效防止拼寫錯誤。

2、如何描述詞素

如今知道了詞法分析能夠將詞素分割開來,那麼詞素是怎麼描述的?或者說,爲何 十二、+ 和 34 都是詞素,而 一、 2+3 和 4 就不是詞素呢?這就須要用到模式了。

模式(pattern)描述了一個詞法單元的詞素可能具備的形式。

也就是說,我定義了 digit 模式爲「由一個或多個數字組成的序列」,和 operator 模式爲「單個 + 或 * 字符」,詞法分析器就知道 12 是一個詞素,而 2+3 則不是詞素了。

如今,模式通常都是用正則表達式(regular expression)表示的,這裏所謂的正則表達式,與日常所說的正則表達式(例如 System.Text.RegularExpressions.Regex 類)形式徹底相同,功能卻更有限,它只包含了字符串的匹配能力,而沒有分組、引用和替換的能力。簡單的舉個例子,a+ 這個正則表達式就表示「由一個或多個字符 a 組成的序列」。關於正則表達式更多詳細信息,我會在後面的文章中列出來,固然,有限的參考一下 System.Text.RegularExpressions.Regex 也是能夠的。

在本系列以後的文章中所提的正則表達式,都指的是這種只具備字符串匹配能力的正則表達式,你們必定要注意不要與 System.Text.RegularExpressions.Regex 相混淆。

3、如何構造詞法分析器

說完了詞素的描述,就到如何根據詞素的描述來構造詞法分析器了。大體的流程以下:

圖 3 構造詞法分析器

從上圖來看,定義了模式的正則表達式,通過 NFA 轉換、DFA 轉換和 DFA 化簡,獲得了一張轉換表。這張轉換表再加上一個固定的 DFA 模擬器,就組成了詞法分析器。它不斷的從輸入緩衝區中讀取字符,利用自動機來識別詞素並輸出。能夠說,詞法分析的精華就是如何獲得這張轉換表。

說了這麼多,詞法分析算是簡單的介紹完了,從下一篇開始,就是如何一步一步實現完整的詞法分析器。相關代碼均可以在這裏找到,一些基礎類(如輸入緩衝)則在這裏

做者:CYJB 
出處:http://www.cnblogs.com/cyjb/ 
GitHub:https://github.com/CYJB/ 本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。

相關文章
相關標籤/搜索