1 use crate::algorithm::Printer;
2 use crate::path::PathKind;
3 use crate::token::Token;
4 use crate::INDENT;
5 use proc_macro2::{Delimiter, Spacing, TokenStream};
6 use syn::{Ident, Macro, MacroDelimiter};
7
8 impl Printer {
mac(&mut self, mac: &Macro, ident: Option<&Ident>, semicolon: bool)9 pub fn mac(&mut self, mac: &Macro, ident: Option<&Ident>, semicolon: bool) {
10 if mac.path.is_ident("macro_rules") {
11 if let Some(ident) = ident {
12 self.macro_rules(ident, &mac.tokens);
13 return;
14 }
15 }
16 self.path(&mac.path, PathKind::Simple);
17 self.word("!");
18 if let Some(ident) = ident {
19 self.nbsp();
20 self.ident(ident);
21 }
22 let (open, close, delimiter_break) = match mac.delimiter {
23 MacroDelimiter::Paren(_) => ("(", ")", Self::zerobreak as fn(&mut Self)),
24 MacroDelimiter::Brace(_) => (" {", "}", Self::hardbreak as fn(&mut Self)),
25 MacroDelimiter::Bracket(_) => ("[", "]", Self::zerobreak as fn(&mut Self)),
26 };
27 self.word(open);
28 if !mac.tokens.is_empty() {
29 self.cbox(INDENT);
30 delimiter_break(self);
31 self.ibox(0);
32 self.macro_rules_tokens(mac.tokens.clone(), false);
33 self.end();
34 delimiter_break(self);
35 self.offset(-INDENT);
36 self.end();
37 }
38 self.word(close);
39 if semicolon {
40 match mac.delimiter {
41 MacroDelimiter::Paren(_) | MacroDelimiter::Bracket(_) => self.word(";"),
42 MacroDelimiter::Brace(_) => {}
43 }
44 }
45 }
46
macro_rules(&mut self, name: &Ident, rules: &TokenStream)47 fn macro_rules(&mut self, name: &Ident, rules: &TokenStream) {
48 enum State {
49 Start,
50 Matcher,
51 Equal,
52 Greater,
53 Expander,
54 }
55
56 use State::*;
57
58 self.word("macro_rules! ");
59 self.ident(name);
60 self.word(" {");
61 self.cbox(INDENT);
62 self.hardbreak_if_nonempty();
63 let mut state = State::Start;
64 for tt in rules.clone() {
65 let token = Token::from(tt);
66 match (state, token) {
67 (Start, Token::Group(delimiter, stream)) => {
68 self.delimiter_open(delimiter);
69 if !stream.is_empty() {
70 self.cbox(INDENT);
71 self.zerobreak();
72 self.ibox(0);
73 self.macro_rules_tokens(stream, true);
74 self.end();
75 self.zerobreak();
76 self.offset(-INDENT);
77 self.end();
78 }
79 self.delimiter_close(delimiter);
80 state = Matcher;
81 }
82 (Matcher, Token::Punct('=', Spacing::Joint)) => {
83 self.word(" =");
84 state = Equal;
85 }
86 (Equal, Token::Punct('>', Spacing::Alone)) => {
87 self.word(">");
88 state = Greater;
89 }
90 (Greater, Token::Group(_delimiter, stream)) => {
91 self.word(" {");
92 self.neverbreak();
93 if !stream.is_empty() {
94 self.cbox(INDENT);
95 self.hardbreak();
96 self.ibox(0);
97 self.macro_rules_tokens(stream, false);
98 self.end();
99 self.hardbreak();
100 self.offset(-INDENT);
101 self.end();
102 }
103 self.word("}");
104 state = Expander;
105 }
106 (Expander, Token::Punct(';', Spacing::Alone)) => {
107 self.word(";");
108 self.hardbreak();
109 state = Start;
110 }
111 _ => unimplemented!("bad macro_rules syntax"),
112 }
113 }
114 match state {
115 Start => {}
116 Expander => {
117 self.word(";");
118 self.hardbreak();
119 }
120 _ => self.hardbreak(),
121 }
122 self.offset(-INDENT);
123 self.end();
124 self.word("}");
125 }
126
macro_rules_tokens(&mut self, stream: TokenStream, matcher: bool)127 pub fn macro_rules_tokens(&mut self, stream: TokenStream, matcher: bool) {
128 #[derive(PartialEq)]
129 enum State {
130 Start,
131 Dollar,
132 DollarIdent,
133 DollarIdentColon,
134 DollarParen,
135 DollarParenSep,
136 Pound,
137 PoundBang,
138 Dot,
139 Colon,
140 Colon2,
141 Ident,
142 IdentBang,
143 Delim,
144 Other,
145 }
146
147 use State::*;
148
149 let mut state = Start;
150 let mut previous_is_joint = true;
151 for tt in stream {
152 let token = Token::from(tt);
153 let (needs_space, next_state) = match (&state, &token) {
154 (Dollar, Token::Ident(_)) => (false, if matcher { DollarIdent } else { Other }),
155 (DollarIdent, Token::Punct(':', Spacing::Alone)) => (false, DollarIdentColon),
156 (DollarIdentColon, Token::Ident(_)) => (false, Other),
157 (DollarParen, Token::Punct('+' | '*' | '?', Spacing::Alone)) => (false, Other),
158 (DollarParen, Token::Ident(_) | Token::Literal(_)) => (false, DollarParenSep),
159 (DollarParen, Token::Punct(_, Spacing::Joint)) => (false, DollarParen),
160 (DollarParen, Token::Punct(_, Spacing::Alone)) => (false, DollarParenSep),
161 (DollarParenSep, Token::Punct('+' | '*', _)) => (false, Other),
162 (Pound, Token::Punct('!', _)) => (false, PoundBang),
163 (Dollar, Token::Group(Delimiter::Parenthesis, _)) => (false, DollarParen),
164 (Pound | PoundBang, Token::Group(Delimiter::Bracket, _)) => (false, Other),
165 (Ident, Token::Group(Delimiter::Parenthesis | Delimiter::Bracket, _)) => {
166 (false, Delim)
167 }
168 (Ident, Token::Punct('!', Spacing::Alone)) => (false, IdentBang),
169 (IdentBang, Token::Group(Delimiter::Parenthesis | Delimiter::Bracket, _)) => {
170 (false, Other)
171 }
172 (Colon, Token::Punct(':', _)) => (false, Colon2),
173 (_, Token::Group(Delimiter::Parenthesis | Delimiter::Bracket, _)) => (true, Delim),
174 (_, Token::Group(Delimiter::Brace | Delimiter::None, _)) => (true, Other),
175 (_, Token::Ident(ident)) if !is_keyword(ident) => {
176 (state != Dot && state != Colon2, Ident)
177 }
178 (_, Token::Literal(_)) => (state != Dot, Ident),
179 (_, Token::Punct(',' | ';', _)) => (false, Other),
180 (_, Token::Punct('.', _)) if !matcher => (state != Ident && state != Delim, Dot),
181 (_, Token::Punct(':', Spacing::Joint)) => (state != Ident, Colon),
182 (_, Token::Punct('$', _)) => (true, Dollar),
183 (_, Token::Punct('#', _)) => (true, Pound),
184 (_, _) => (true, Other),
185 };
186 if !previous_is_joint {
187 if needs_space {
188 self.space();
189 } else if let Token::Punct('.', _) = token {
190 self.zerobreak();
191 }
192 }
193 previous_is_joint = match token {
194 Token::Punct(_, Spacing::Joint) | Token::Punct('$', _) => true,
195 _ => false,
196 };
197 self.single_token(
198 token,
199 if matcher {
200 |printer, stream| printer.macro_rules_tokens(stream, true)
201 } else {
202 |printer, stream| printer.macro_rules_tokens(stream, false)
203 },
204 );
205 state = next_state;
206 }
207 }
208 }
209
is_keyword(ident: &Ident) -> bool210 fn is_keyword(ident: &Ident) -> bool {
211 match ident.to_string().as_str() {
212 "as" | "async" | "await" | "box" | "break" | "const" | "continue" | "crate" | "dyn"
213 | "else" | "enum" | "extern" | "fn" | "for" | "if" | "impl" | "in" | "let" | "loop"
214 | "macro" | "match" | "mod" | "move" | "mut" | "pub" | "ref" | "return" | "static"
215 | "struct" | "trait" | "type" | "unsafe" | "use" | "where" | "while" | "yield" => true,
216 _ => false,
217 }
218 }
219