C#詞法分析器之輸入緩衝和代碼定位的應用分析

來源:互聯網
上載者:User

一、輸入緩衝

在介紹如何進行詞法分析之前,先來說說一個不怎麼被提及的問題——怎麼從源檔案中讀取字元流。為什麼這個問題這麼重要呢?是因為在詞法分析中,對字元流是有要求的,它必須能夠支援後援動作(就是將多個字元放回到流中,以後會再次被讀取)。

先來解釋下為什麼需要支援後援動作,舉個簡單的例子來說,現在要對兩個模式進行匹配:

圖 1 流的回退過程

上面是一個簡單的匹配過程,僅為了展示回退過程,在後面實現 DFA 模擬器時會詳細解釋是如何匹配詞素的。

現在來看看 C# 中與輸入相關的類,有 Stream,它支援流的尋找,但是只能以位元組方式訪問;BinaryReader 和 TextReader 雖然支援讀取字元,但是又不能支援回退。所以,就必須自己完成這個輸入緩衝類了,大致思路就是以 TextReader 作為底層的字元輸入,然後由自己的類完成對回退能力的支援。

《編譯原理》上給出了一種緩衝區對的方法,簡單的說就是開闢兩個緩衝區,設緩衝區大小都是 N 個字元。每一次都將 N 個字元讀入到緩衝區中,並在這個緩衝區上實現字元操作。如果當前緩衝區的資料已經處理完畢,就將 N 個新字元讀入到另一個緩衝區中,接下來就換做操作新的緩衝區。

這樣的資料結構效率很高,而且只要維護合適的指標,就可以很容易的實現回退功能。不過它的緩衝區大小是固定的,新讀入的字元會覆蓋舊的字元。如果需要回退的字元數量過多(比如在分析很長的字串時),就容易出現錯誤。我通過使用多個緩衝區解決了舊字元被覆蓋的問題——如果緩衝區不足了,就開闢新緩衝區,而不是覆蓋舊資料。

如果僅僅是不斷的添加緩衝區,那麼佔用的記憶體只會不斷增加,這樣是沒有什麼意義的,因此我定義了三個釋放緩衝區的操作:Drop,Accept 和 AcceptToken。Drop 的作用是將當前位置之前的所有資料標記為無效(被拋棄),被標記無效的資料佔用的緩衝區就被釋放掉,可以拿來被重複利用了;Accept 則會將標記為無效的資料以字串形式返回,而不僅僅是簡單的拋棄;類似的,AcceptToken 是以 Token 形式返回被無效化的資料,是為了方便進行詞法分析。

這樣的資料結構比較類似於 STL 中的 deque,不過這裡不需要隨機訪問和插入、刪除資料,僅會在資料的頭、尾進行操作,因此我直接將多個緩衝區使用雙向鏈表連成一個環,使用三個指標 current,first 和 last 指向鏈表中有資料的緩衝區,如所示:

圖 2 多個緩衝區組成的鏈表,紅色的部分表示有資料,白色的部分沒有資料

其中,first 指向的是最早的資料緩衝區,last 指向的是最新的資料緩衝區,current 指向的是當前正在訪問的資料緩衝區,current 總是在 [first, last] 範圍之內。firstIndex 和 lastLen 之間紅色的部分,就是包含有效資料的緩衝區,idx 表示當前正在訪問的字元。白色的部分表示空緩衝區,或是緩衝區中的資料已無效。

當需要讀取下一個字元時,就從 current 中依次讀取資料,並將 idx 後移。如果 current 中的資料已經讀取完畢,則將 current 移向 last(這裡用移向,是因為 current 和 last 之間可能有多個緩衝區),同時 idx 也要相應的移動。

圖 3 current 移向 last

如果需要繼續讀取字元,但是 current 中沒有新資料了,而此時 current 已經與 last 相同,表示緩衝區中已經沒有更新的資料,那麼就需要從 TextReader 中讀取資料,放到新的緩衝區中,同時後移 current 和 last(需要保證 last 總是指向最新的緩衝區)。

圖 4 current 和 last 向後移

現在來看看後援動作。進行回退時,只需要將 current 向 first 的方向移動(同樣,current 和 first 之間可能有多個緩衝區)。

圖 5 後援動作

Drop 操作(Accept 和 AcceptToken 也同理)的實現也很簡單,只需要將 first 移動到 current 位置,將 firstIndex 移動到 idx 即可,這就表示 idx 之前的資料都看作無效資料

圖 6 Drop 操作

這裡需要注意的就是,Drop 操作完成後,被無效化的資料就有可能會被新資料覆蓋,因此應該確定資料不再需要時再執行 Drop 操作。Drop 操作的效率很高(移動兩個引用),基本不用擔心會影響效率。

使用這種環形資料結構的優點是除了將輸入鍵台到緩衝區之外,完全避免了資料的額外複製,無論是前進、回退還是 Drop 操作都只有指標(引用)操作,效率很高。當 Drop 比較及時時,僅會使用兩個緩衝區,不會額外的佔用記憶體。當佔用的緩衝區過多時,還能夠實現主動釋放多餘的記憶體(這裡現在沒有考慮)。

缺點就是實現起來會複雜些,需要仔細處理好 first、current 和 last 的關係,以及 firstIndex、index 和 lastLen 範圍限制,有時還會涉及到多個緩衝區的操作。

完整的代碼可見 SourceReader.cs。

二、代碼定位

在對原始碼進行解析的時候,記錄每個 Token 對應的行號和列號顯然是很必要的工作,沒有人會喜歡面對一大堆 Error,而且還偏偏不告訴你到底是哪錯了……因此,我認為代碼定位絕對是詞法分析必備的功能,所以直接把這個功能內建到了 SourceReader 類中了。

下面來說明如何?代碼定位。代碼定位包含三維資料:索引、行號和列號。索引是從 0 開始的字元索引,主要是方便程式進行處理;行號和列號則都是從 1 開始的,主要是為了人去看。

行定位比較簡單,Unix 的分行符號是 '\n',Windows 的分行符號是 "\r\n",所以直接統計 '\n' 的個數即可。

接下來是列定位。為了達到比較好的效果,需要考慮兩個因素:全形、半形字元和 Tab 字元。

一個中文字元(即全形字元)對應的是兩列,英文字元(半形字元)對應的則是一列,這樣在等寬字型下,每一列都是上下對齊的。在計算資料行數的時候,自然也應當如此,使用 Encoding.Default.GetByteCount() 而不是字串的長度。不過這裡我發現了一個記憶體問題(詳情參考這裡),改用 Encoding.Default.GetEncoder() 的 GetByteCount 方法就可以了。

一個 Tab 字元的長度是不定的(一般是為 4 或 8,因人而異),所以定義了一個 TabSize 來表示 Tab 字元的寬度。那麼,一個 Tab 字元就對應 TabSize 列嗎?並不是這樣的,雖然一般看來是這樣,但事實上,Tab 字元是讓下一字元對應的列總是為 TabSize 的整數倍再加 1。如果 TabSize = 4,那麼它的行為如所示,其中 a 和 bcc 後面都是有兩個 Tab 字元,bcccccc 和 bccccccc 後面都是有一個 Tab 字元,每個 Tab 字元我都用灰色箭頭標出來了。

圖 7 Tab 字元執行個體

所以,實際的列號應當使用下面的公式計算,其中 currentCol 是 Tab 字元所在的列,nextCol 就是下一字元所在的列:

nextCol = tabSize * (1 + (currentCol - 1) / tabSize) + 1;

代碼定位的計算方法有了,然後就是計算的時機。如果每次 Read 的時候都計算當前字元的位置,一是計算效率會略低,因為 GetByteCount 方法中,一次性計算較長一個字元數組的效率,差不多是多次計算長度為 1 的字元數組的一倍。二是回退的時候應該怎麼辦?如果將之前的位置計算結果都儲存起來,記憶體佔用會是一個問題,如果不考慮的話,又無法根據當前字元的位置推算出前一個字元的位置(比如當前字元在第一列的話,前一個字元應該在第幾列?)。

綜合考慮之後,我決定將代碼位置的計算放到 Drop 操作(Accept 和 AcceptToken 也一樣)中,一個是向上面所說的,計算效率會略高,另一個是一般僅當識別出了一個 Token 後才需要為它定位,此時恰好是 Drop 或 AcceptToken 的時機,識別 Token 的過程中就是定位了也沒有什麼用處。

我將代碼定位的功能單獨封裝到了 SourceLocator.cs 類中。

下一篇將會介紹詞法分析中用到的Regex,以及如何解析Regex。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.