在實踐中, 一般須要向前看一個字符. 好比, 當讀到一個 非字母或數字的字符 時才能肯定已經讀到一個標識符的結尾. 所以, 這個字符不是id詞素的一部分. 採用雙緩衝區方案可以安全地處理向前看多個符號的問題. 而後, 將考慮一種改進方案, 使用"哨兵標記"來節約用於檢查緩衝區末端的時間. {P72}
1、緩衝區對 2、哨兵標記 3、實現雙緩衝區
描述: 兩個交替讀入的緩衝區, 容量爲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: char* lexemeBegin = nullptr; char* forward = nullptr; /** * @return 返回lexemeBegin 與 forward 的字符序列 */ std::string getString(); /** * forward向前移動一個字符 * @return 返回當前字符 */ char next(); /** * @return 返回當前forward所指字符 */ char cur(); /** * forward向後移動一個字符 * @return 返回當前字符 */ char pre(); private: std::string fileStr; // 文件路徑 std::ifstream fileStream; // 文件流 char buffer_1[size]; char buffer_2[size]; Buffer::Tag bufferTag = Tag::ONE; // 哪一個緩衝區 private: /** * 從fileStream流讀取字符序列 */ void read(); public: bool is_end = false; // 是否讀到結尾 }; };
<~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') { // 終止詞法分析 is_end = true; return '\0'; } if (c == EOF) { // 已到緩衝區末尾標記 read(); } return *forward++; } template<int size> char Buffer<size>::cur() { return *forward; } template<int size> char Buffer<size>::pre() { /** * 會出現forward在某個緩衝區的第一個字符 * 當回退時, 須要判斷是否切換過緩衝區, 若是是則回退到另外一個緩衝區的EOF * 若是沒有, 則不執行任何操做 */ char c = *forward; if (forward == &buffer_2[0]) { // 若是當前爲第二個緩衝區, 則一定切換過 forward = &buffer_1[size - 1]; return c; } else if (forward == &buffer_1[0] && buffer_2[0] != '\0') { // 當前爲第一個緩衝區, 而且第二個緩衝區有數據, 則認爲當前切換過緩衝區, 執行回退 forward = &buffer_2[size - 1]; return c; } else if (forward == &buffer_1[0] && buffer_2[0] == '\0') { // 當前爲第一個緩衝區, 而且第二個緩衝區沒有數據, 則認爲當前沒有切換過緩衝區, 不執行回退 return c; } forward--; return c; } };
只要從不須要越過實際詞素向前看很遠, 以致於這個詞素的長度加上向前看的距離大於N,就決不會識別這個詞素以前覆蓋尚在緩衝區的詞素 {P72} lexemeBegin指針在第一個緩衝區, 而forward指針已經指向第二個緩衝區的EOF. 當forward向前移動一個字符時, 須要切換緩衝區, 這樣會致使將第一個緩衝區覆蓋.