Rustでビットパターンにマッチさせるマクロを作った


はじめに

前回の記事(Rustで+++を実現する)は完全にネタでしたが、今回は同じく関数呼び出し形式の手続きマクロで実用的なものを作ってみました。

bitpattern!というマクロで整数型とビットパターンのマッチと抽出ができます。

似たようなマクロで bitpat! というものがあるのですが、こちらは macro_rules! によるマクロとなっていて、宣言的なマクロを再帰で呼び出すことで、任意長のビットパターンに対応しています。このため、ビットパターンが長くなると非常に再帰が深くなり(例えば32ビットのパターンで3万以上の再帰呼び出しが発生しました)、実用的にはちょっと厳しいところがありました。

手続きマクロであれば、ビットパターンは普通の文字列処理として扱えるので、長くなっても問題ありません。
そこで、Rust 1.45.0で安定化した関数呼び出し形式の手続きマクロを使って bitpattern! を実装しました。

使い方

ビットパターンとして使える値は 01?_ が特別な意味を持っていて、それ以外にも任意の文字が使えます。
例えば 01 を使うと

use bitpattern::bitpattern;

let x = 0xacu8; // 2進数で 10101100
assert_eq!(bitpattern!("1010_1100", x), Some(()));
assert_eq!(bitpattern!("1010_0100", x), None);

のように、ビットパターンがマッチすれば Some(()) が、しなければ Noneが返ってきます。
(パターンは_で区切ることができます)

これだけだと単なる一致比較ですが、 ?を使うと、0/1どちらにもマッチさせることができます。

let x = 0xacu8; // 2進数で 10101100
assert_eq!(bitpattern!("1?10_1?00", x), Some(()));
let x = 0xe8u8; // 2進数で 11101000
assert_eq!(bitpattern!("1?10_1?00", x), Some(()));

適当な文字を使うと、その部分を抽出することができます。
例えば a を使って上から2ビット目を抽出した例です。

let x = 0xacu8; // 2進数で 10101100
assert_eq!(bitpattern!("1a101100", x), Some(0));

同じ文字を連続させればその部分がまとめて抽出されます。

let x = 0xacu8; // 2進数で 10101100
assert_eq!(bitpattern!("1aa01100", x), Some(1));

複数の箇所を抽出することも可能です。
抽出箇所が複数の場合タプルで返ってきます。

let x = 0xacu8; // 2進数で 10101100
assert_eq!(bitpattern!("1aa0aa00", x), Some((1, 3)));

抽出箇所が隣接する場合、文字を変えればOKです。

let x = 0xacu8; // 2進数で 10101100
assert_eq!(bitpattern!("1aabbb00", x), Some((1, 3)));

パターンの長さに応じて型はu8からu128まで自動的に調整されます。

let x = 0xacf0acf0acf0acf0acf0acf0acf0acf0u128;
assert_eq!( bitpattern!( "1010_1100_1111_0000_1010_1100_1111_0000_1010_1100_1111_0000_1010_1100_1111_0000_1010_1100_1111_0000_1010_1100_1111_0000_1010_1100_1111_0000_1010_1100_1111_0000", x), Some(()));

bitmatch

同じくビットパターンのマッチをletmatchに対するアトリビュートマクロで実現したbitmatchというものもあります。

こちらも便利なので、用途に応じて使い分けるといいでしょう。
一緒に使えるように、bitpatternのパターン記法はこのbitmatchと合わせてあります。

use bitmatch::bitmatch;
#[bitmatch]
fn decode(inst: u8) -> String {
    #[bitmatch]
    match inst {
        "00oo_aabb" => format!("Op {}, {}, {}", o, a, b),
        "0100_aaii" => format!("Val {}, {}", a, i),
        "01??_????" => format!("Invalid"),
        "10ii_aabb" => format!("Ld {}, {}, {}", a, b, i),
        "11ii_aabb" => format!("St {}, {}, {}", a, b, i),
    }
}