UTF-8 イテレータ再考

新エンジンの実装を進めているところですが、UTF-8 文字列の扱いを向上すべく utf8iterator を練り直してみました。

template <typename Iterator>
class utf8iterator {
    Iterator curr_;

    unsigned size() const {
	static unsigned table[] = {
	    1, 1, 1, 1, 1, 1, 1, 1,	1, 1, 1, 1, 1, 1, 1, 1, // 0x00
	    1, 1, 1, 1, 1, 1, 1, 1,	1, 1, 1, 1, 1, 1, 1, 1,
	    1, 1, 1, 1, 1, 1, 1, 1,	1, 1, 1, 1, 1, 1, 1, 1,
	    1, 1, 1, 1, 1, 1, 1, 1,	1, 1, 1, 1, 1, 1, 1, 1,
	    1, 1, 1, 1, 1, 1, 1, 1,	1, 1, 1, 1, 1, 1, 1, 1,
	    1, 1, 1, 1, 1, 1, 1, 1,	1, 1, 1, 1, 1, 1, 1, 1,
	    1, 1, 1, 1, 1, 1, 1, 1,	1, 1, 1, 1, 1, 1, 1, 1,
	    1, 1, 1, 1, 1, 1, 1, 1,	1, 1, 1, 1, 1, 1, 1, 1,
	    0, 0, 0, 0, 0, 0, 0, 0,	0, 0, 0, 0, 0, 0, 0, 0, // 0x80
	    0, 0, 0, 0, 0, 0, 0, 0,	0, 0, 0, 0, 0, 0, 0, 0,
	    0, 0, 0, 0, 0, 0, 0, 0,	0, 0, 0, 0, 0, 0, 0, 0,
	    0, 0, 0, 0, 0, 0, 0, 0,	0, 0, 0, 0, 0, 0, 0, 0,
	    2, 2, 2, 2, 2, 2, 2, 2,	2, 2, 2, 2, 2, 2, 2, 2, // 0xc0
	    2, 2, 2, 2, 2, 2, 2, 2,	2, 2, 2, 2, 2, 2, 2, 2,
	    3, 3, 3, 3, 3, 3, 3, 3,	3, 3, 3, 3, 3, 3, 3, 3, // 0xe0
	    4, 4, 4, 4, 4, 4, 4, 4,	5, 5, 5, 5, 6, 6, 0, 0  // 0xf0
	};

	return table[(unsigned char)*curr_];
    }

    bool leadbyte() const { return size() != 0; }

    utf8iterator& next(int count = 1) {
	for(int i = 0; i < count; ++ i) {
	    for(++ curr_; !leadbyte(); ++ curr_) {}
	}
	return *this;
    }

    utf8iterator& prev(int count = 1) {
	for(int i = 0; i < count; ++ i) {
	    for(-- curr_; !leadbyte(); -- curr_) {}
	}
	return *this;
    }

public:
    utf8iterator() : curr_(0) {}
    utf8iterator(Iterator iter) : curr_(iter) {}
    utf8iterator& operator=(Iterator iter) {
	curr_ = iter;
	return *this;
    }

    Iterator iterator() { return curr_; }
    unsigned charsize() const { return size(); }

    std::string operator*() const {
	return std::string(curr_, curr_ + size());
    }

    utf8iterator& operator++() { return next(); }
    utf8iterator operator++(int) {
	utf8iterator iter(*this);
	++ curr_;
	return iter;
    }

    utf8iterator& operator--() { return prev(); }
    utf8iterator operator--(int) {
	utf8iterator iter(*this);
	-- curr_;
	return iter;
    }

    utf8iterator& operator+=(int count) { return 0 < count ? next(count) : prev(abs(count)); }
    utf8iterator& operator-=(int count) { return 0 < count ? prev(count) : next(abs(count)); }

    friend bool operator==(const utf8iterator& lhs, const utf8iterator& rhs) { return lhs.curr_ == rhs.curr_; }
    friend bool operator!=(const utf8iterator& lhs, const utf8iterator& rhs) { return lhs.curr_ != rhs.curr_; }
    friend bool operator<(const utf8iterator& lhs, const utf8iterator& rhs) { return lhs.curr_ < rhs.curr_; }
};

template <typename Iterator>
int operator-(utf8iterator<Iterator> end, utf8iterator<Iterator> beg) {
    int size = 0;
    for(utf8iterator<Iterator> cur = beg; beg != end; ++ beg) { ++ size; }
    return size;
}

template <typename Iterator>
utf8iterator<Iterator> operator+(utf8iterator<Iterator> beg, int count) { return beg += count; }
template <typename Iterator>
utf8iterator<Iterator> operator-(utf8iterator<Iterator> beg, int count) { return beg -= count; }

標準イテレータ準拠のインタフェースに変更し、const もサポートするようにしました。これを std::string のメソッドと組み合わせて簡易ユーティリティを作ります。

struct utf8util {
    typedef utf8iterator<std::string::iterator> iterator;
    typedef utf8iterator<std::string::const_iterator> const_iterator;
    
    // UTF8 文字列長取得
    static unsigned length(const std::string& str) {
	return const_iterator(str.end()) - const_iterator(str.begin());
    }

    // 指定位置に文字列挿入
    //
    // 例:
    //      std::string str = "文字列";
    //      utf8util::push(str, "a", -3); // "a文字列";
    //      utf8util::push(str, "b");     // "a文字列b";
    // 
    static void push(std::string& target, const std::string& str, int offset = 0) {
	if(0 < offset || target.empty()) {
	    target += str;
	} else {
	    iterator end(target.end());
	    iterator pos(end + offset);

	    if(pos.iterator() < target.begin()) {
		pos = target.begin();
	    }
	    target.insert(pos.iterator() - target.begin(), str);
	}
    }

    // 指定位置から一文字後退して削除
    //
    // 例:
    //      std::string str = "文字列";
    //      utf8util::pop(str);           // "文字";
    //      utf8util::pop(str, -1);       // "字";
    // 
    static void pop(std::string& target, int offset = 0) {
	iterator end(target.end());
	iterator pos(end + (0 < offset ? 0 : offset)  - 1);

	if(!(pos.iterator() < target.begin())) {
	    target.erase(pos.iterator() - target.begin(), pos.charsize());
	}
    }
};

push() と pop() では、範囲外例外を発生させない調整が入ってますが、潔く assert で叩き落としたほうが良い気もします。なんにせよ、これで以下のようなクラスの実装が簡単になります。

// UTF8 文字列の編集 & カーソル移動をサポート
class UTF8Cell {
    std::string buf_;
    unsigned cursor_pos_;	// 末尾からの距離

    void initialize();

public:
    UTF8Cell();
    UTF8Cell(const std::string& buf);

    // 編集
    void Insert(unsigned char ch);
    void Insert(const std::string& str);
    void BackSpace();
    void Delete();
    void Clear();

    // カーソル移動
    void CursorLeft();
    void CursorRight();
    void CursorUp();		// 左端
    void CursorDown();		// 右端

    // その他
    const std::string& String() const;
    unsigned CursorPosition() const; // 先頭からの距離(0 ベース)
};

インライン表示する見出し語や登録文字列の内部表現が UTF8Cell になる予定です。先はまだまだ長いなぁ。