aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2019-01-31 20:01:34 +0000
committerAleksey Kladov <[email protected]>2019-01-31 20:23:30 +0000
commit4c0ab7db85d2084870db4a2f92d92a3ae67a3bb1 (patch)
tree4f3f6f904f72253d31ac9be6e42a972ab6b9f003
parentb4b522fb3952fae365e02ffdc6ef05c8d75b7674 (diff)
explain the magic
-rw-r--r--crates/ra_mbe/src/lib.rs7
-rw-r--r--crates/ra_mbe/src/mbe_expander.rs68
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.
1use rustc_hash::FxHashMap; 4use rustc_hash::FxHashMap;
2use ra_syntax::SmolStr; 5use ra_syntax::SmolStr;
3 6
4use crate::tt_cursor::TtCursor; 7use crate::tt_cursor::TtCursor;
5 8
6pub fn exapnd(rules: &crate::MacroRules, input: &tt::Subtree) -> Option<tt::Subtree> { 9pub(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)]
17struct Bindings { 65struct 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
100macro_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
112impl_froms! (Foo: Bar, Baz)
113
114*/
115
116fn expand_subtree( 146fn expand_subtree(
117 template: &crate::Subtree, 147 template: &crate::Subtree,
118 bindings: &Bindings, 148 bindings: &Bindings,