csharplangに、

みたいなのが投稿されていまして。

「それ、ライブラリとアナライザー、ちょっとしたソースコード生成でできるよ。」という話。

BitFields ライブラリ

ということで実装してみたのがこちら。

昔、ビットフィールド的なものを手作業実装してた時に、「これはコード生成でやりたい…」とか思ってて、 できる宛まではついてたんですが。 なんだかんだ言ってアナライザーを書くのは結構めんどくさいんで、放置してすでに数年。

まあ、いい機会だから久々に重い腰を上げてアナライザー書いてみるかと思って作ったのが上記のBitFieldsです。

できること

Nビット整数

Nビットまでの整数を受け付けるBitN(Nは1~64)型と、それに対するアナライザーがあります。

BitN型とアナライザー

ちゃんと、ビット数を超える値(例えばBit1に2)を代入しようとするとコンパイル エラーになります。

ビットフィールドコード生成

例えば、RGB555とか、半端なビット数で色情報を扱う形式があります。 32bitカラーが当たり前な今時だと珍しいですけど、昔はこういう形式も割と使われていました。

で、それを、こう書く。

struct Rgb555
{
    enum BitFields
    {
        B = 5,
        G = 5,
        R = 5,
    }
}

コード生成都合で、「構造体の中にBitFieldsという名前のenumを定義、値としてビット数を与える」みたいな規約ベースの型情報を書きます。 このenumはあくまでメタデータ(型情報)であって、実行時には一切使いません。

で、以下のように、クイック アクション(電球アイコン)が出るので、生成メニュー(Generate bit-fields)を選択。

ビットフィールド生成

以下のようなコードが生成されます。

using BitFields;

partial struct Rgb555
{
    public ushort Value;

    private const int BShift = 0;
    private const ushort BMask = unchecked((ushort)((1U << 5) - (1U << 0)));
    public Bit5 B
    {
        get => (Bit5)((Value & BMask) >> BShift);
        set => Value = unchecked((ushort)((Value & ~BMask) | ((((ushort)value) << BShift) & BMask)));
    }
    private const int GShift = 5;
    private const ushort GMask = unchecked((ushort)((1U << 10) - (1U << 5)));
    public Bit5 G
    {
        get => (Bit5)((Value & GMask) >> GShift);
        set => Value = unchecked((ushort)((Value & ~GMask) | ((((ushort)value) << GShift) & GMask)));
    }
    private const int RShift = 10;
    private const ushort RMask = unchecked((ushort)((1U << 15) - (1U << 10)));
    public Bit5 R
    {
        get => (Bit5)((Value & RMask) >> RShift);
        set => Value = unchecked((ushort)((Value & ~RMask) | ((((ushort)value) << RShift) & RMask)));
    }
}

現状だと1度は手作業で「クイック アクションの選択」が必要なので、使い勝手はいまいちなんですが。 そのうち、正式にコード生成機能がC#に入るはずで、その暁ににはもう少し利便性がよくなります。

ライブラリだけでできることはライブラリで

まあ、一般論として、ライブラリだけでできるならライブラリ提供でいいわけで。 ライブラリを作ってみた結果として、本当にライブラリだけだと不便ということになれば、そこで初めて言語文法の提案をすればいい。

実際、ライブラリありきで出ている提案もあったりします。

特に、今回やったビットフィールド生成みたいなものは、ちょっと「コンパイラーの仕事」にするにはいまいちかなぁと思います。

  • シフトやマスク演算のコードが見えている方が理解がしやすい
  • エンディアンの問題とかあって、汎用化するには怖い
    • 個人でライブラリを作る限りには「ビッグ エンディアン?知らない子ですね。現存するエンディアンじゃないですよ?」とか敵を作りそうな発言もできるものの、標準に取り込む際にそれを言えるかというと無理
  • 人によって求めるものが違う
    • immutable版が欲しい
    • Valueフィールドがpublicなのは嫌
    • コンストラクター、デコンストラクターもほしい
    • BitN、コンパイル時チェックだけじゃなくて実行時チェックもほしい(あるいは逆に、そんな実行時コストは絶対避けたい)

しかも今のC#だと、アナライザーを書けば「Bit1なら0と1だけ代入で来てほしい。2以上はビット数オーバーでコンパイル時エラー」みたいなこともできます。 ライブラリだけでできちゃうことも増えているので、コンパイラーのバージョンアップとか待ってないでライブラリ作っちゃえば良かったりします。