4

enum型付き変数のネストされた構造をコンパイル時のテンプレートに簡単に一致させるマクロを作成しています。Rust のパターン マッチングを利用して、構造の特定の場所に特定の値を強制するか、変数を他の興味深い場所にバインドするという考え方です。私の実装では基本的な考え方は機能しますが、ネストされたパターンでは機能しません。問題は、マクロ入力の一部が解析される$<name>:patと、後で として解析できないことだと思います$<name>:tt

パターンという用語のあいまいな使用を避けるために、Rust のドキュメントに従って次の表記法を使用します。

  • パターンmatchアームやステートメントに現れるものでif letあり、フラグメント指定子によってマクロで照合されます$<name>:pat
  • マッチャーは、マクロ内の構文規則の左側です。
  • テンプレートは、マクロの展開方法を決定するマクロへの入力の一部です。

遊び場MCVE

enumこれは、私が使用しているタイプの簡略版です。

#[derive(Debug, Clone)]
enum TaggedValue {
    Str(&'static str),
    Seq(Vec<TaggedValue>),
}

たとえば、次の式

use TaggedValue::*;
let expression = Seq(vec![
    Str("define"),
    Seq(vec![Str("mul"), Str("x"), Str("y")]),
    Seq(vec![Str("*"), Str("x"), Str("y")]),
]);

このマクロ呼び出しによって一致する可能性があります。

match_template!(
    &expression,                               // dynamic input structure
    { println!("fn {}: {:?}", name, body) },   // action to take after successful match
    [Str("define"), [Str(name), _, _], body]   // template to match against
);

ここで、一致が成功すると、識別子namebodyは の対応するサブ要素にバインドさexpressionれ、マクロの 2 番目の引数として渡されるブロック内の変数として使用できるようになります。

これは、上記のマクロを作成するための私の努力です:

macro_rules! match_template {
    // match sequence template with one element
    ($exp:expr, $action:block, [$single:pat]) => {
        if let Seq(seq) = $exp {
            match_template!(&seq[0], $action, $single)
        } else {
            panic!("mismatch")
        }
    };

    // match sequence template with more than one element
    ($exp:expr, $action:block, [$first:pat, $($rest:tt)*]) => {
        if let Seq(seq) = $exp {
            // match first pattern in sequence against first element of $expr
            match_template!(&seq[0], {
                // then match remaining patterns against remaining elements of $expr
                match_template!(Seq(seq[1..].into()), $action, [$($rest)*])
            }, $first)
        } else {
            panic!("mismatch")
        }
    };

    // match a non sequence template and perform $action on success
    ($exp:expr, $action:block, $atom:pat) => {
        if let $atom = $exp $action else {panic!("mismatch")}
    };
}

ネストされていないテンプレートでは期待どおりに機能し、ネストされたテンプレートではマクロ呼び出しを手動でネストできます。ただし、1 回のマクロ呼び出しでネストされたテンプレートを直接指定すると、コンパイル エラーで失敗します。

match_template!(
    &expression,
    {
        match_template!(
            signature,
            { println!("fn {}: {:?}", name, body) },
            [Str(name), _, _]
        )
    },
    [Str("define"), signature, body]
);
// prints:
//   fn mul: Seq([Str("*"), Str("x"), Str("y")])

match_template!(
    &expression,
    { println!("fn {}: {:?}", name, body) },
    [Str("define"), [Str(name), _, _], body]
);
// error[E0529]: expected an array or slice, found `TaggedValue`
//   --> src/main.rs:66:25
//    |
// 66 |         [Str("define"), [Str(name), _, _], body]
//    |                         ^^^^^^^^^^^^^^^^^ pattern cannot match with input type `TaggedValue`

遊び場MCVE

[Str(name), _, _]エラーは、型の不一致を引き起こす 3 番目のマクロ ルールによって受け入れられる単一のスライス パターンとして一致すると言っていると思われます。ただし、2 番目のルールで一連のパターンに分解できるように、トークン ツリーにしたいと考えています。

2 番目のルールを に変更しようとしました($exp:expr, $action:block, [$first:tt, $($rest:tt)*]) =>が、これによりエラーが外側のレベルで発生するだけです。

このようなテンプレートを再帰的に展開できるようにするには、マクロにどのような変更が必要ですか?

(私は明示的にパターンで識別子をバインドしたいので、Rust で一致アームを解析するための再帰マクロのようにトークンをむしゃむしゃにすることはここでは機能しないと思います。)

seqこれは、マクロ呼び出しが展開されることを期待するものです (簡潔にするために不一致の分岐を無視します。さらに、変数を後置することでマクロの衛生状態をシミュレートしました):

// macro invocation
match_template!(
    &expression,
    { println!("fn {}: {:?}", name, body) },
    [Str("define"), [Str(name), _, _], body]
);

// expansion
if let Seq(seq_1) = &expression {
    if let Str("define") = &seq_1[0] {
        if let Seq(seq_1a) = Seq(seq_1[1..].into()) {
            if let Seq(seq_2) = &seq_1a[0] {
                if let Str(name) = &seq_2[0] {
                    if let Seq(seq_2a) = Seq(seq_2[1..].into()) {
                        if let _ = &seq_2a[0] {
                            if let Seq(seq_2b) = Seq(seq_2a[1..].into()) {
                                if let _ = &seq_2b[0] {
                                    if let Seq(seq_1b) = Seq(seq_1a[1..].into()) {
                                        if let body = &seq_1b[0] {
                                            { println!("fn {}: {:?}", name, body) }
                                        }
                                    }
                                }
                            }
                        } 
                    } 
                } 
            } 
        } 
    } 
} 

完全な展開は少し冗長ですが、このわずかに短縮されたバージョンは、何が起こるべきかの本質を捉えています:

if let Seq(seq) = &expression {
    if let Str("define") = &seq[0] {
        if let Seq(signature) = &seq[1] {
            if let Str(name) = &signature[0] {
                if let body = &seq[2] {
                    println!("fn {}: {:?}", name, body)
                }
            }
        }
    }
}

最後に、再帰的展開の個々のステップを示す別のプレイグラウンド リンクを次に示します。とても濃いです。

4

1 に答える 1

2

実際、問題は、マクロがコンマ区切りのパターンのリストに一致することです。したがって、入力で[Str("define"), [Str(name), _, _], body]は、マクロは内部[...]を type の式と一致できないスライス パターンとして解釈しますTaggedValue

解決策は、入力をトークン ツリーとして展開することです。ただし、1 つのトークン ツリーですべてのパターンを表すことはできないため、これにはちょっとしたコツが必要です。特に、フォームのパターンは、とVariant(value)の 2 つのトークン ツリーで構成されます。これらの 2 つのトークンは、マクロの最終 (非再帰) ルールを呼び出す前にパターンに結合できます。Variant(value)

たとえば、単一要素のテンプレートでこのようなパターンに一致するルールは、次のように始まります。

($exp:expr, $action:block, [$single_variant:tt $single_value:tt]) =>

これらのトークンは、マクロの別の呼び出しにまとめて渡されます。

match_template!(&seq[0], $action, $single_variant $single_value)

最終規則によって単一のパターンとして一致する場合

($exp:expr, $action:block, $atom:pat) =>

最終的なマクロ定義には、Variant(value)パターンを説明する 2 つの追加ルールが含まれています。

macro_rules! match_template {
    ($exp:expr, $action:block, [$single:tt]) => {
        if let Seq(seq) = $exp {
            match_template!(&seq[0], $action, $single)
        } else {
            panic!("mismatch")
        }
    };

    ($exp:expr, $action:block, [$single_variant:tt $single_value:tt]) => {
        if let Seq(seq) = $exp {
            match_template!(&seq[0], $action, $single_variant $single_value)
        } else {
            panic!("mismatch")
        }
    };

    ($exp:expr, $action:block, [$first:tt, $($rest:tt)*]) => {
        if let Seq(seq) = $exp {
            match_template!(&seq[0], {
                match_template!(Seq(seq[1..].into()), $action, [$($rest)*])
            }, $first)
        } else {
            panic!("mismatch")
        }
    };

    ($exp:expr, $action:block, [$first_variant:tt $first_value:tt, $($rest:tt)*]) => {
        if let Seq(seq) = $exp {
            match_template!(&seq[0], {
                match_template!(Seq(seq[1..].into()), $action, [$($rest)*])
            }, $first_variant $first_value)
        } else {
            panic!("mismatch")
        }
    };

    ($exp:expr, $action:block, $atom:pat) => {
        if let $atom = $exp $action else {panic!("mismatch")}
    };
}

完全な例へのリンクは次のとおりです

于 2019-06-24T19:48:06.187 に答える