diff options
author | Aleksey Kladov <[email protected]> | 2019-01-31 20:01:34 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2019-01-31 20:23:30 +0000 |
commit | 4c0ab7db85d2084870db4a2f92d92a3ae67a3bb1 (patch) | |
tree | 4f3f6f904f72253d31ac9be6e42a972ab6b9f003 | |
parent | b4b522fb3952fae365e02ffdc6ef05c8d75b7674 (diff) |
explain the magic
-rw-r--r-- | crates/ra_mbe/src/lib.rs | 7 | ||||
-rw-r--r-- | crates/ra_mbe/src/mbe_expander.rs | 68 |
2 files changed, 56 insertions, 19 deletions
diff --git a/crates/ra_mbe/src/lib.rs b/crates/ra_mbe/src/lib.rs index af3ccc0f5..3e7f458cf 100644 --- a/crates/ra_mbe/src/lib.rs +++ b/crates/ra_mbe/src/lib.rs | |||
@@ -109,6 +109,13 @@ mod tests { | |||
109 | 109 | ||
110 | use super::*; | 110 | use super::*; |
111 | 111 | ||
112 | // Good first issue (although a slightly chellegning one): | ||
113 | // | ||
114 | // * Pick a random test from here | ||
115 | // https://github.com/intellij-rust/intellij-rust/blob/c4e9feee4ad46e7953b1948c112533360b6087bb/src/test/kotlin/org/rust/lang/core/macros/RsMacroExpansionTest.kt | ||
116 | // * Port the test to rust and add it to this module | ||
117 | // * Make it pass :-) | ||
118 | |||
112 | #[test] | 119 | #[test] |
113 | fn test_convert_tt() { | 120 | fn test_convert_tt() { |
114 | let macro_definition = r#" | 121 | let macro_definition = r#" |
diff --git a/crates/ra_mbe/src/mbe_expander.rs b/crates/ra_mbe/src/mbe_expander.rs index 19c75404d..af5beb786 100644 --- a/crates/ra_mbe/src/mbe_expander.rs +++ b/crates/ra_mbe/src/mbe_expander.rs | |||
@@ -1,9 +1,12 @@ | |||
1 | /// This module takes a (parsed) defenition of `macro_rules` invocation, a | ||
2 | /// `tt::TokenTree` representing an argument of macro invocation, and produces a | ||
3 | /// `tt::TokenTree` for the result of the expansion. | ||
1 | use rustc_hash::FxHashMap; | 4 | use rustc_hash::FxHashMap; |
2 | use ra_syntax::SmolStr; | 5 | use ra_syntax::SmolStr; |
3 | 6 | ||
4 | use crate::tt_cursor::TtCursor; | 7 | use crate::tt_cursor::TtCursor; |
5 | 8 | ||
6 | pub fn exapnd(rules: &crate::MacroRules, input: &tt::Subtree) -> Option<tt::Subtree> { | 9 | pub(crate) fn exapnd(rules: &crate::MacroRules, input: &tt::Subtree) -> Option<tt::Subtree> { |
7 | rules.rules.iter().find_map(|it| expand_rule(it, input)) | 10 | rules.rules.iter().find_map(|it| expand_rule(it, input)) |
8 | } | 11 | } |
9 | 12 | ||
@@ -13,6 +16,51 @@ fn expand_rule(rule: &crate::Rule, input: &tt::Subtree) -> Option<tt::Subtree> { | |||
13 | expand_subtree(&rule.rhs, &bindings, &mut Vec::new()) | 16 | expand_subtree(&rule.rhs, &bindings, &mut Vec::new()) |
14 | } | 17 | } |
15 | 18 | ||
19 | /// The actual algorithm for expansion is not too hard, but is pretty tricky. | ||
20 | /// `Bindings` structure is the key to understanding what we are doing here. | ||
21 | /// | ||
22 | /// On the high level, it stores mapping from meta variables to the bits of | ||
23 | /// syntax it should be substituted with. For example, if `$e:expr` is matched | ||
24 | /// with `1 + 1` by macro_rules, the `Binding` will store `$e -> 1 + 1`. | ||
25 | /// | ||
26 | /// The tricky bit is dealing with repetitions (`$()*`). Consider this example: | ||
27 | /// | ||
28 | /// ```ignore | ||
29 | /// macro_rules! foo { | ||
30 | /// ($($ i:ident $($ e:expr),*);*) => { | ||
31 | /// $(fn $ i() { $($ e);*; })* | ||
32 | /// } | ||
33 | /// } | ||
34 | /// foo! { foo 1,2,3; bar 4,5,6 } | ||
35 | /// ``` | ||
36 | /// | ||
37 | /// Here, the `$i` meta variable is matched first with `foo` and then with | ||
38 | /// `bar`, and `$e` is matched in turn with `1`, `2`, `3`, `4`, `5`, `6`. | ||
39 | /// | ||
40 | /// To represent such "multi-mappings", we use a recursive structures: we map | ||
41 | /// variables not to values, but to *lists* of values or other lists (that is, | ||
42 | /// to the trees). | ||
43 | /// | ||
44 | /// For the above example, the bindings would store | ||
45 | /// | ||
46 | /// ```ignore | ||
47 | /// i -> [foo, bar] | ||
48 | /// e -> [[1, 2, 3], [4, 5, 6]] | ||
49 | /// ``` | ||
50 | /// | ||
51 | /// We construct `Bindings` in the `match_lhs`. The interesting case is | ||
52 | /// `TokenTree::Repeat`, where we use `push_nested` to create the desired | ||
53 | /// nesting structure. | ||
54 | /// | ||
55 | /// The other side of the puzzle is `expand_subtree`, where we use the bindings | ||
56 | /// to substitute meta variables in the output template. When expanding, we | ||
57 | /// maintain a `nesteing` stack of indicies whihc tells us which occurence from | ||
58 | /// the `Bindings` we should take. We push to the stack when we enter a | ||
59 | /// repetition. | ||
60 | /// | ||
61 | /// In other words, `Bindings` is a *multi* mapping from `SmolStr` to | ||
62 | /// `tt::TokenTree`, where the index to select a particular `TokenTree` among | ||
63 | /// many is not a plain `usize`, but an `&[usize]`. | ||
16 | #[derive(Debug, Default)] | 64 | #[derive(Debug, Default)] |
17 | struct Bindings { | 65 | struct Bindings { |
18 | inner: FxHashMap<SmolStr, Binding>, | 66 | inner: FxHashMap<SmolStr, Binding>, |
@@ -95,24 +143,6 @@ fn match_lhs(pattern: &crate::Subtree, input: &mut TtCursor) -> Option<Bindings> | |||
95 | Some(res) | 143 | Some(res) |
96 | } | 144 | } |
97 | 145 | ||
98 | /* | ||
99 | |||
100 | macro_rules! impl_froms { | ||
101 | ($e:ident: $($v:ident),*) => { | ||
102 | $( | ||
103 | impl From<$v> for $e { | ||
104 | fn from(it: $v) -> $e { | ||
105 | $e::$v(it) | ||
106 | } | ||
107 | } | ||
108 | )* | ||
109 | } | ||
110 | } | ||
111 | |||
112 | impl_froms! (Foo: Bar, Baz) | ||
113 | |||
114 | */ | ||
115 | |||
116 | fn expand_subtree( | 146 | fn expand_subtree( |
117 | template: &crate::Subtree, | 147 | template: &crate::Subtree, |
118 | bindings: &Bindings, | 148 | bindings: &Bindings, |