1. 程式人生 > >[編譯原理-詞法分析(一)] 輸入緩衝 雙緩衝區方案

[編譯原理-詞法分析(一)] 輸入緩衝 雙緩衝區方案

前言

在實踐中, 通常需要向前看一個字元. 
比如, 當讀到一個 非字母或數字的字元 時才能確定已經讀到一個識別符號的結尾. 因此, 這個字元不是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向前移動一個字元時, 需要切換緩衝區, 這樣會導致將第一個緩