aboutsummaryrefslogtreecommitdiff
path: root/crates/mbe
diff options
context:
space:
mode:
Diffstat (limited to 'crates/mbe')
-rw-r--r--crates/mbe/Cargo.toml3
-rw-r--r--crates/mbe/src/benchmark.rs211
-rw-r--r--crates/mbe/src/expander/matcher.rs68
-rw-r--r--crates/mbe/src/lib.rs7
-rw-r--r--crates/mbe/src/syntax_bridge.rs41
-rw-r--r--crates/mbe/src/tests.rs23
-rw-r--r--crates/mbe/src/tt_iter.rs77
7 files changed, 360 insertions, 70 deletions
diff --git a/crates/mbe/Cargo.toml b/crates/mbe/Cargo.toml
index ef0907194..0abba3584 100644
--- a/crates/mbe/Cargo.toml
+++ b/crates/mbe/Cargo.toml
@@ -19,3 +19,6 @@ parser = { path = "../parser", version = "0.0.0" }
19tt = { path = "../tt", version = "0.0.0" } 19tt = { path = "../tt", version = "0.0.0" }
20test_utils = { path = "../test_utils", version = "0.0.0" } 20test_utils = { path = "../test_utils", version = "0.0.0" }
21 21
22[dev-dependencies]
23profile = { path = "../profile", version = "0.0.0" }
24
diff --git a/crates/mbe/src/benchmark.rs b/crates/mbe/src/benchmark.rs
new file mode 100644
index 000000000..0d0acd589
--- /dev/null
+++ b/crates/mbe/src/benchmark.rs
@@ -0,0 +1,211 @@
1//! This module add real world mbe example for benchmark tests
2
3use rustc_hash::FxHashMap;
4use syntax::{
5 ast::{self, NameOwner},
6 AstNode, SmolStr,
7};
8use test_utils::{bench, bench_fixture, skip_slow_tests};
9
10use crate::{
11 ast_to_token_tree,
12 parser::{Op, RepeatKind, Separator},
13 MacroRules,
14};
15
16#[test]
17fn benchmark_parse_macro_rules() {
18 if skip_slow_tests() {
19 return;
20 }
21 let rules = macro_rules_fixtures_tt();
22 let hash: usize = {
23 let _pt = bench("mbe parse macro rules");
24 rules.values().map(|it| MacroRules::parse(it).unwrap().rules.len()).sum()
25 };
26 assert_eq!(hash, 1144);
27}
28
29#[test]
30fn benchmark_expand_macro_rules() {
31 if skip_slow_tests() {
32 return;
33 }
34 let rules = macro_rules_fixtures();
35 let invocations = invocation_fixtures(&rules);
36
37 let hash: usize = {
38 let _pt = bench("mbe expand macro rules");
39 invocations
40 .into_iter()
41 .map(|(id, tt)| {
42 let res = rules[&id].expand(&tt);
43 if res.err.is_some() {
44 // FIXME:
45 // Currently `invocation_fixtures` will generate some correct invocations but
46 // cannot be expanded by mbe. We ignore errors here.
47 // See: https://github.com/rust-analyzer/rust-analyzer/issues/4777
48 eprintln!("err from {} {:?}", id, res.err);
49 }
50 res.value.token_trees.len()
51 })
52 .sum()
53 };
54 assert_eq!(hash, 66995);
55}
56
57fn macro_rules_fixtures() -> FxHashMap<String, MacroRules> {
58 macro_rules_fixtures_tt()
59 .into_iter()
60 .map(|(id, tt)| (id, MacroRules::parse(&tt).unwrap()))
61 .collect()
62}
63
64fn macro_rules_fixtures_tt() -> FxHashMap<String, tt::Subtree> {
65 let fixture = bench_fixture::numerous_macro_rules();
66 let source_file = ast::SourceFile::parse(&fixture).ok().unwrap();
67
68 source_file
69 .syntax()
70 .descendants()
71 .filter_map(ast::MacroRules::cast)
72 .map(|rule| {
73 let id = rule.name().unwrap().to_string();
74 let (def_tt, _) = ast_to_token_tree(&rule.token_tree().unwrap()).unwrap();
75 (id, def_tt)
76 })
77 .collect()
78}
79
80// Generate random invocation fixtures from rules
81fn invocation_fixtures(rules: &FxHashMap<String, MacroRules>) -> Vec<(String, tt::Subtree)> {
82 let mut seed = 123456789;
83 let mut res = Vec::new();
84
85 for (name, it) in rules {
86 for rule in &it.rules {
87 // Generate twice
88 for _ in 0..2 {
89 let mut subtree = tt::Subtree::default();
90 for op in rule.lhs.iter() {
91 collect_from_op(op, &mut subtree, &mut seed);
92 }
93 res.push((name.clone(), subtree));
94 }
95 }
96 }
97 return res;
98
99 fn collect_from_op(op: &Op, parent: &mut tt::Subtree, seed: &mut usize) {
100 return match op {
101 Op::Var { kind, .. } => match kind.as_ref().map(|it| it.as_str()) {
102 Some("ident") => parent.token_trees.push(make_ident("foo")),
103 Some("ty") => parent.token_trees.push(make_ident("Foo")),
104 Some("tt") => parent.token_trees.push(make_ident("foo")),
105 Some("vis") => parent.token_trees.push(make_ident("pub")),
106 Some("pat") => parent.token_trees.push(make_ident("foo")),
107 Some("path") => parent.token_trees.push(make_ident("foo")),
108 Some("literal") => parent.token_trees.push(make_literal("1")),
109 Some("expr") => parent.token_trees.push(make_ident("foo").into()),
110 Some("lifetime") => {
111 parent.token_trees.push(make_punct('\''));
112 parent.token_trees.push(make_ident("a"));
113 }
114 Some("block") => {
115 parent.token_trees.push(make_subtree(tt::DelimiterKind::Brace, None))
116 }
117 Some("item") => {
118 parent.token_trees.push(make_ident("fn"));
119 parent.token_trees.push(make_ident("foo"));
120 parent.token_trees.push(make_subtree(tt::DelimiterKind::Parenthesis, None));
121 parent.token_trees.push(make_subtree(tt::DelimiterKind::Brace, None));
122 }
123 Some("meta") => {
124 parent.token_trees.push(make_ident("foo"));
125 parent.token_trees.push(make_subtree(tt::DelimiterKind::Parenthesis, None));
126 }
127
128 None => (),
129 Some(kind) => panic!("Unhandled kind {}", kind),
130 },
131 Op::Leaf(leaf) => parent.token_trees.push(leaf.clone().into()),
132 Op::Repeat { tokens, kind, separator } => {
133 let max = 10;
134 let cnt = match kind {
135 RepeatKind::ZeroOrMore => rand(seed) % max,
136 RepeatKind::OneOrMore => 1 + rand(seed) % max,
137 RepeatKind::ZeroOrOne => rand(seed) % 2,
138 };
139 for i in 0..cnt {
140 for it in tokens.iter() {
141 collect_from_op(it, parent, seed);
142 }
143 if i + 1 != cnt {
144 if let Some(sep) = separator {
145 match sep {
146 Separator::Literal(it) => parent
147 .token_trees
148 .push(tt::Leaf::Literal(it.clone().into()).into()),
149 Separator::Ident(it) => parent
150 .token_trees
151 .push(tt::Leaf::Ident(it.clone().into()).into()),
152 Separator::Puncts(puncts) => {
153 for it in puncts {
154 parent
155 .token_trees
156 .push(tt::Leaf::Punct(it.clone().into()).into())
157 }
158 }
159 };
160 }
161 }
162 }
163 }
164 Op::Subtree { tokens, delimiter } => {
165 let mut subtree =
166 tt::Subtree { delimiter: delimiter.clone(), token_trees: Vec::new() };
167 tokens.iter().for_each(|it| {
168 collect_from_op(it, &mut subtree, seed);
169 });
170 parent.token_trees.push(subtree.into());
171 }
172 };
173
174 // Simple linear congruential generator for determistic result
175 fn rand(seed: &mut usize) -> usize {
176 let a = 1664525;
177 let c = 1013904223;
178 *seed = usize::wrapping_add(usize::wrapping_mul(*seed, a), c);
179 return *seed;
180 };
181 fn make_ident(ident: &str) -> tt::TokenTree {
182 tt::Leaf::Ident(tt::Ident { id: tt::TokenId::unspecified(), text: SmolStr::new(ident) })
183 .into()
184 }
185 fn make_punct(char: char) -> tt::TokenTree {
186 tt::Leaf::Punct(tt::Punct {
187 id: tt::TokenId::unspecified(),
188 char,
189 spacing: tt::Spacing::Alone,
190 })
191 .into()
192 }
193 fn make_literal(lit: &str) -> tt::TokenTree {
194 tt::Leaf::Literal(tt::Literal {
195 id: tt::TokenId::unspecified(),
196 text: SmolStr::new(lit),
197 })
198 .into()
199 }
200 fn make_subtree(
201 kind: tt::DelimiterKind,
202 token_trees: Option<Vec<tt::TokenTree>>,
203 ) -> tt::TokenTree {
204 tt::Subtree {
205 delimiter: Some(tt::Delimiter { id: tt::TokenId::unspecified(), kind }),
206 token_trees: token_trees.unwrap_or_default(),
207 }
208 .into()
209 }
210 }
211}
diff --git a/crates/mbe/src/expander/matcher.rs b/crates/mbe/src/expander/matcher.rs
index 800931cd1..e3bd4c09a 100644
--- a/crates/mbe/src/expander/matcher.rs
+++ b/crates/mbe/src/expander/matcher.rs
@@ -3,15 +3,13 @@
3use crate::{ 3use crate::{
4 expander::{Binding, Bindings, Fragment}, 4 expander::{Binding, Bindings, Fragment},
5 parser::{Op, RepeatKind, Separator}, 5 parser::{Op, RepeatKind, Separator},
6 subtree_source::SubtreeTokenSource,
7 tt_iter::TtIter, 6 tt_iter::TtIter,
8 ExpandError, MetaTemplate, 7 ExpandError, MetaTemplate,
9}; 8};
10 9
11use super::ExpandResult; 10use super::ExpandResult;
12use parser::{FragmentKind::*, TreeSink}; 11use parser::FragmentKind::*;
13use syntax::{SmolStr, SyntaxKind}; 12use syntax::SmolStr;
14use tt::buffer::{Cursor, TokenBuffer};
15 13
16impl Bindings { 14impl Bindings {
17 fn push_optional(&mut self, name: &SmolStr) { 15 fn push_optional(&mut self, name: &SmolStr) {
@@ -409,68 +407,6 @@ impl<'a> TtIter<'a> {
409 .into()) 407 .into())
410 } 408 }
411 409
412 fn expect_fragment(
413 &mut self,
414 fragment_kind: parser::FragmentKind,
415 ) -> ExpandResult<Option<tt::TokenTree>> {
416 struct OffsetTokenSink<'a> {
417 cursor: Cursor<'a>,
418 error: bool,
419 }
420
421 impl<'a> TreeSink for OffsetTokenSink<'a> {
422 fn token(&mut self, kind: SyntaxKind, mut n_tokens: u8) {
423 if kind == SyntaxKind::LIFETIME_IDENT {
424 n_tokens = 2;
425 }
426 for _ in 0..n_tokens {
427 self.cursor = self.cursor.bump_subtree();
428 }
429 }
430 fn start_node(&mut self, _kind: SyntaxKind) {}
431 fn finish_node(&mut self) {}
432 fn error(&mut self, _error: parser::ParseError) {
433 self.error = true;
434 }
435 }
436
437 let buffer = TokenBuffer::from_tokens(&self.inner.as_slice());
438 let mut src = SubtreeTokenSource::new(&buffer);
439 let mut sink = OffsetTokenSink { cursor: buffer.begin(), error: false };
440
441 parser::parse_fragment(&mut src, &mut sink, fragment_kind);
442
443 let mut err = None;
444 if !sink.cursor.is_root() || sink.error {
445 err = Some(err!("expected {:?}", fragment_kind));
446 }
447
448 let mut curr = buffer.begin();
449 let mut res = vec![];
450
451 if sink.cursor.is_root() {
452 while curr != sink.cursor {
453 if let Some(token) = curr.token_tree() {
454 res.push(token);
455 }
456 curr = curr.bump();
457 }
458 }
459 self.inner = self.inner.as_slice()[res.len()..].iter();
460 if res.len() == 0 && err.is_none() {
461 err = Some(err!("no tokens consumed"));
462 }
463 let res = match res.len() {
464 1 => Some(res[0].cloned()),
465 0 => None,
466 _ => Some(tt::TokenTree::Subtree(tt::Subtree {
467 delimiter: None,
468 token_trees: res.into_iter().map(|it| it.cloned()).collect(),
469 })),
470 };
471 ExpandResult { value: res, err }
472 }
473
474 fn eat_vis(&mut self) -> Option<tt::TokenTree> { 410 fn eat_vis(&mut self) -> Option<tt::TokenTree> {
475 let mut fork = self.clone(); 411 let mut fork = self.clone();
476 match fork.expect_fragment(Visibility) { 412 match fork.expect_fragment(Visibility) {
diff --git a/crates/mbe/src/lib.rs b/crates/mbe/src/lib.rs
index d80bd7a33..4c298f85f 100644
--- a/crates/mbe/src/lib.rs
+++ b/crates/mbe/src/lib.rs
@@ -12,6 +12,9 @@ mod subtree_source;
12#[cfg(test)] 12#[cfg(test)]
13mod tests; 13mod tests;
14 14
15#[cfg(test)]
16mod benchmark;
17
15use std::fmt; 18use std::fmt;
16 19
17use test_utils::mark; 20use test_utils::mark;
@@ -62,8 +65,8 @@ impl fmt::Display for ExpandError {
62} 65}
63 66
64pub use crate::syntax_bridge::{ 67pub use crate::syntax_bridge::{
65 ast_to_token_tree, parse_to_token_tree, syntax_node_to_token_tree, token_tree_to_syntax_node, 68 ast_to_token_tree, parse_exprs_with_sep, parse_to_token_tree, syntax_node_to_token_tree,
66 TokenMap, 69 token_tree_to_syntax_node, TokenMap,
67}; 70};
68 71
69/// This struct contains AST for a single `macro_rules` definition. What might 72/// This struct contains AST for a single `macro_rules` definition. What might
diff --git a/crates/mbe/src/syntax_bridge.rs b/crates/mbe/src/syntax_bridge.rs
index 0cdc175be..aacae1026 100644
--- a/crates/mbe/src/syntax_bridge.rs
+++ b/crates/mbe/src/syntax_bridge.rs
@@ -10,8 +10,8 @@ use syntax::{
10}; 10};
11use tt::buffer::{Cursor, TokenBuffer}; 11use tt::buffer::{Cursor, TokenBuffer};
12 12
13use crate::subtree_source::SubtreeTokenSource;
14use crate::ExpandError; 13use crate::ExpandError;
14use crate::{subtree_source::SubtreeTokenSource, tt_iter::TtIter};
15 15
16#[derive(Debug, PartialEq, Eq, Clone, Copy)] 16#[derive(Debug, PartialEq, Eq, Clone, Copy)]
17pub enum TokenTextRange { 17pub enum TokenTextRange {
@@ -112,6 +112,43 @@ pub fn parse_to_token_tree(text: &str) -> Option<(tt::Subtree, TokenMap)> {
112 Some((subtree, conv.id_alloc.map)) 112 Some((subtree, conv.id_alloc.map))
113} 113}
114 114
115/// Split token tree with seperate expr: $($e:expr)SEP*
116pub fn parse_exprs_with_sep(tt: &tt::Subtree, sep: char) -> Vec<tt::Subtree> {
117 if tt.token_trees.is_empty() {
118 return Vec::new();
119 }
120
121 let mut iter = TtIter::new(tt);
122 let mut res = Vec::new();
123
124 while iter.peek_n(0).is_some() {
125 let expanded = iter.expect_fragment(FragmentKind::Expr);
126 if expanded.err.is_some() {
127 break;
128 }
129
130 res.push(match expanded.value {
131 None => break,
132 Some(tt @ tt::TokenTree::Leaf(_)) => {
133 tt::Subtree { delimiter: None, token_trees: vec![tt.into()] }
134 }
135 Some(tt::TokenTree::Subtree(tt)) => tt,
136 });
137
138 let mut fork = iter.clone();
139 if fork.expect_char(sep).is_err() {
140 break;
141 }
142 iter = fork;
143 }
144
145 if iter.peek_n(0).is_some() {
146 res.push(tt::Subtree { delimiter: None, token_trees: iter.into_iter().cloned().collect() });
147 }
148
149 res
150}
151
115impl TokenMap { 152impl TokenMap {
116 pub fn token_by_range(&self, relative_range: TextRange) -> Option<tt::TokenId> { 153 pub fn token_by_range(&self, relative_range: TextRange) -> Option<tt::TokenId> {
117 let &(token_id, _) = self.entries.iter().find(|(_, range)| match range { 154 let &(token_id, _) = self.entries.iter().find(|(_, range)| match range {
@@ -176,7 +213,7 @@ fn doc_comment_text(comment: &ast::Comment) -> SmolStr {
176 213
177 // Quote the string 214 // Quote the string
178 // Note that `tt::Literal` expect an escaped string 215 // Note that `tt::Literal` expect an escaped string
179 let text = format!("{:?}", text.escape_default().to_string()); 216 let text = format!("{:?}", text.escape_debug().to_string());
180 text.into() 217 text.into()
181} 218}
182 219
diff --git a/crates/mbe/src/tests.rs b/crates/mbe/src/tests.rs
index 1c467facd..f1eadcd1e 100644
--- a/crates/mbe/src/tests.rs
+++ b/crates/mbe/src/tests.rs
@@ -970,6 +970,29 @@ fn test_meta_doc_comments() {
970} 970}
971 971
972#[test] 972#[test]
973fn test_meta_doc_comments_non_latin() {
974 parse_macro(
975 r#"
976 macro_rules! foo {
977 ($(#[$ i:meta])+) => (
978 $(#[$ i])+
979 fn bar() {}
980 )
981 }
982"#,
983 ).
984 assert_expand_items(
985 r#"foo! {
986 /// 錦瑟無端五十弦,一弦一柱思華年。
987 /**
988 莊生曉夢迷蝴蝶,望帝春心託杜鵑。
989 */
990 }"#,
991 "# [doc = \" 錦瑟無端五十弦,一弦一柱思華年。\"] # [doc = \"\\\\n 莊生曉夢迷蝴蝶,望帝春心託杜鵑。\\\\n \"] fn bar () {}",
992 );
993}
994
995#[test]
973fn test_tt_block() { 996fn test_tt_block() {
974 parse_macro( 997 parse_macro(
975 r#" 998 r#"
diff --git a/crates/mbe/src/tt_iter.rs b/crates/mbe/src/tt_iter.rs
index 46c420718..a362d31fc 100644
--- a/crates/mbe/src/tt_iter.rs
+++ b/crates/mbe/src/tt_iter.rs
@@ -1,5 +1,20 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use crate::{subtree_source::SubtreeTokenSource, ExpandError, ExpandResult};
4
5use parser::TreeSink;
6use syntax::SyntaxKind;
7use tt::buffer::{Cursor, TokenBuffer};
8
9macro_rules! err {
10 () => {
11 ExpandError::BindingError(format!(""))
12 };
13 ($($tt:tt)*) => {
14 ExpandError::BindingError(format!($($tt)*))
15 };
16}
17
3#[derive(Debug, Clone)] 18#[derive(Debug, Clone)]
4pub(crate) struct TtIter<'a> { 19pub(crate) struct TtIter<'a> {
5 pub(crate) inner: std::slice::Iter<'a, tt::TokenTree>, 20 pub(crate) inner: std::slice::Iter<'a, tt::TokenTree>,
@@ -56,6 +71,68 @@ impl<'a> TtIter<'a> {
56 } 71 }
57 } 72 }
58 73
74 pub(crate) fn expect_fragment(
75 &mut self,
76 fragment_kind: parser::FragmentKind,
77 ) -> ExpandResult<Option<tt::TokenTree>> {
78 struct OffsetTokenSink<'a> {
79 cursor: Cursor<'a>,
80 error: bool,
81 }
82
83 impl<'a> TreeSink for OffsetTokenSink<'a> {
84 fn token(&mut self, kind: SyntaxKind, mut n_tokens: u8) {
85 if kind == SyntaxKind::LIFETIME_IDENT {
86 n_tokens = 2;
87 }
88 for _ in 0..n_tokens {
89 self.cursor = self.cursor.bump_subtree();
90 }
91 }
92 fn start_node(&mut self, _kind: SyntaxKind) {}
93 fn finish_node(&mut self) {}
94 fn error(&mut self, _error: parser::ParseError) {
95 self.error = true;
96 }
97 }
98
99 let buffer = TokenBuffer::from_tokens(&self.inner.as_slice());
100 let mut src = SubtreeTokenSource::new(&buffer);
101 let mut sink = OffsetTokenSink { cursor: buffer.begin(), error: false };
102
103 parser::parse_fragment(&mut src, &mut sink, fragment_kind);
104
105 let mut err = None;
106 if !sink.cursor.is_root() || sink.error {
107 err = Some(err!("expected {:?}", fragment_kind));
108 }
109
110 let mut curr = buffer.begin();
111 let mut res = vec![];
112
113 if sink.cursor.is_root() {
114 while curr != sink.cursor {
115 if let Some(token) = curr.token_tree() {
116 res.push(token);
117 }
118 curr = curr.bump();
119 }
120 }
121 self.inner = self.inner.as_slice()[res.len()..].iter();
122 if res.len() == 0 && err.is_none() {
123 err = Some(err!("no tokens consumed"));
124 }
125 let res = match res.len() {
126 1 => Some(res[0].cloned()),
127 0 => None,
128 _ => Some(tt::TokenTree::Subtree(tt::Subtree {
129 delimiter: None,
130 token_trees: res.into_iter().map(|it| it.cloned()).collect(),
131 })),
132 };
133 ExpandResult { value: res, err }
134 }
135
59 pub(crate) fn peek_n(&self, n: usize) -> Option<&tt::TokenTree> { 136 pub(crate) fn peek_n(&self, n: usize) -> Option<&tt::TokenTree> {
60 self.inner.as_slice().get(n) 137 self.inner.as_slice().get(n)
61 } 138 }