[編譯原理-詞法分析(一)] 輸入緩衝 雙緩衝區方案
阿新 • • 發佈:2019-09-25
前言
在實踐中, 通常需要向前看一個字元.
比如, 當讀到一個 非字母或數字的字元 時才能確定已經讀到一個識別符號的結尾. 因此, 這個字元不是id詞素的一部分.
採用雙緩衝區方案能夠安全地處理向前看多個符號的問題. 然後, 將考慮一種改進方案, 使用"哨兵標記"來節約用於檢查緩衝區末端的時間. {P72}
前情提要
一、緩衝區對
二、哨兵標記
三、實現雙緩衝區
正文
一、緩衝區對
描述: 兩個交替讀入的緩衝區, 容量為N個字元, 使用系統命令一次性將N個字元讀入到緩衝區; 如果輸入字元不足N個, 則有特殊字元EOF來標記檔案結尾; 程式維護兩個指標lexemeBegin和forward; lexemeBegin指向當前詞素的開始處, 當前正試圖確定這個詞素的結尾; forward向前掃描, 直到與某個模式匹配為止; 當確定該詞素時, forward指向該詞素結尾的字元; 將詞素作為摸個返回給語法分析器的詞法單元的屬性值記錄; lexemeBegin指向該詞素後的第一個字元, 然後將forward左移一個字元; 在forward不斷掃描中, 檢查是否掃描到EOF, 如果是則將N個新字元讀入另外一個緩衝區, 且將forward指向緩衝區頭部;
二、哨兵標記
當採用雙緩衝區方案, 那麼每次向前移動forward指標時, 都需要檢查是否到緩衝區結尾, 若是則載入另外一個緩衝區.
如果擴充套件每個緩衝區, 使它們在末尾包含一個哨兵(sentinel)字元, 就可以把緩衝區末尾的測試和當前字元的測試結合在一起, 這個字元選擇不會出現在源程式中的 EOF標記.
三、實現雙緩衝區
將使用<~> 標記來自哪個檔案
<~Buffer.h> namespace Lexical_Analysis { template <int size = 1024> class Buffer { private: enum Tag { ONE, TWO }; // 緩衝區標號 public: explicit Buffer(std::string _fileStr); ~Buffer() noexcept; public: std::string fileStr; // 檔案路徑 std::ifstream fileStream; // 檔案流 char* lexemeBegin = nullptr; char* forward = nullptr; char buffer_1[size]; char buffer_2[size]; Buffer::Tag bufferTag = Tag::ONE; // 哪個緩衝區 /** * @return 返回lexemeBegin 與 forward 的字元序列 */ std::string getString(); /** * 從fileStream流讀取字元序列 */ void read(); /** * forward向前移動一個字元 * @return 返回當前字元 */ char next(); }; };
<~Buffer_TailAffix.h> namespace Lexical_Analysis { template<int size> Buffer<size>::Buffer(std::string _fileStr):fileStr(std::move(_fileStr)) { fileStream.open(fileStr); buffer_1[size - 1] = EOF; fileStream.read(buffer_1, size - 1); lexemeBegin = forward = &buffer_1[0]; } template<int size> Buffer<size>::~Buffer() noexcept { if (fileStream) { fileStream.close(); } } template<int size> std::string Buffer<size>::getString() { std::stringstream ss; char* current = lexemeBegin; while (current != forward) { if (*current == EOF) { if (bufferTag == Tag::ONE) { current = &buffer_1[0]; } else if (bufferTag == Tag::TWO) { current = &buffer_2[0]; } } ss << *current++; } return ss.str(); } template<int size> void Buffer<size>::read() { if (!fileStream) return ; /** * bufferTag 為當前從檔案流讀入的緩衝區標號 * 將每個緩衝區的末尾設定為 哨兵標記 */ if (bufferTag == Tag::ONE) { // 當前在第一個緩衝區末尾, 裝載第二個緩衝區 buffer_2[size - 1] = EOF; fileStream.read(buffer_2, size - 1); // 設定Tag為第二個緩衝區, 並且設定forward為第二個緩衝區的開頭 bufferTag = Tag::TWO; forward = &buffer_2[0]; } else if (bufferTag == Tag::TWO) { // 當前在第二個緩衝區末尾, 裝載第一個緩衝區 buffer_1[size - 1] = EOF; fileStream.read(buffer_1, size - 1); // 設定Tag為第一個緩衝區, 並且設定forward為第一個緩衝區的開頭 bufferTag = Tag::ONE; forward = &buffer_1[0]; } } template<int size> char Buffer<size>::next() { char c = *forward; if (c == '\0') { // 終止詞法分析 return '\0'; } if (c == EOF) { // 已到緩衝區末尾標記 read(); } return *forward++; } };
尾記
只要從不需要越過實際詞素向前看很遠, 以至於這個詞素的長度加上向前看的距離大於N,就決不會識別這個詞素之前覆蓋尚在緩衝區的詞素 {P72}
lexemeBegin指標在第一個緩衝區, 而forward指標已經指向第二個緩衝區的EOF. 當forward向前移動一個字元時, 需要切換緩衝區, 這樣會導致將第一個緩