]> git.ipfire.org Git - thirdparty/gcc.git/blob - gcc/rust/expand/rust-macro-substitute-ctx.cc
eeb573a39d6ac2627cbe22f733579ad67d6b8032
[thirdparty/gcc.git] / gcc / rust / expand / rust-macro-substitute-ctx.cc
1 // Copyright (C) 2020-2023 Free Software Foundation, Inc.
2
3 // This file is part of GCC.
4
5 // GCC is free software; you can redistribute it and/or modify it under
6 // the terms of the GNU General Public License as published by the Free
7 // Software Foundation; either version 3, or (at your option) any later
8 // version.
9
10 // GCC is distributed in the hope that it will be useful, but WITHOUT ANY
11 // WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 // for more details.
14
15 // You should have received a copy of the GNU General Public License
16 // along with GCC; see the file COPYING3. If not see
17 // <http://www.gnu.org/licenses/>.
18
19 #include "rust-macro-substitute-ctx.h"
20
21 namespace Rust {
22
23 bool
24 SubstituteCtx::substitute_metavar (
25 std::unique_ptr<AST::Token> &metavar,
26 std::vector<std::unique_ptr<AST::Token>> &expanded)
27 {
28 auto metavar_name = metavar->get_str ();
29
30 auto it = fragments.find (metavar_name);
31 if (it == fragments.end ())
32 {
33 // fail to substitute
34
35 // HACK: substitute ($ crate) => (crate)
36 if (metavar->get_id () != CRATE)
37 return false;
38
39 expanded.push_back (metavar->clone_token ());
40 return true;
41 }
42 else
43 {
44 // If we are expanding a metavar which has a lof of matches, we are
45 // currently expanding a repetition metavar - not a simple metavar. We
46 // need to error out and inform the user.
47 // Associated test case for an example: compile/macro-issue1224.rs
48 if (!it->second->is_single_fragment ())
49 {
50 rust_error_at (metavar->get_locus (),
51 "metavariable is still repeating at this depth");
52 rust_inform (
53 metavar->get_locus (),
54 "you probably forgot the repetition operator: %<%s%s%s%>", "$(",
55 metavar->as_string ().c_str (), ")*");
56 return true;
57 }
58
59 // We only care about the vector when expanding repetitions.
60 // Just access the first element of the vector.
61 auto &frag = it->second->get_single_fragment ();
62 for (size_t offs = frag.token_offset_begin; offs < frag.token_offset_end;
63 offs++)
64 {
65 auto &tok = input.at (offs);
66 expanded.push_back (tok->clone_token ());
67 }
68 }
69
70 return true;
71 }
72
73 bool
74 SubstituteCtx::check_repetition_amount (size_t pattern_start,
75 size_t pattern_end,
76 size_t &expected_repetition_amount)
77 {
78 bool first_fragment_found = false;
79 bool is_valid = true;
80
81 for (size_t i = pattern_start; i < pattern_end; i++)
82 {
83 if (macro.at (i)->get_id () == DOLLAR_SIGN)
84 {
85 auto &frag_token = macro.at (i + 1);
86 if (token_id_is_keyword (frag_token->get_id ())
87 || frag_token->get_id () == IDENTIFIER)
88 {
89 auto it = fragments.find (frag_token->get_str ());
90 if (it == fragments.end ())
91 {
92 // If the repetition is not anything we know (ie no declared
93 // metavars, or metavars which aren't present in the
94 // fragment), we can just error out. No need to paste the
95 // tokens as if nothing had happened.
96 rust_error_at (frag_token->get_locus (),
97 "metavar %s used in repetition does not exist",
98 frag_token->get_str ().c_str ());
99
100 is_valid = false;
101 }
102
103 auto &fragment = *it->second;
104
105 if (!fragment.is_single_fragment ())
106 {
107 auto &fragment_rep
108 = static_cast<MatchedFragmentContainerRepetition &> (
109 fragment);
110 size_t repeat_amount = fragment_rep.get_match_amount ();
111 if (!first_fragment_found)
112 {
113 first_fragment_found = true;
114 expected_repetition_amount = repeat_amount;
115 }
116 else
117 {
118 if (repeat_amount != expected_repetition_amount)
119 {
120 rust_error_at (
121 frag_token->get_locus (),
122 "different amount of matches used in merged "
123 "repetitions: expected %lu, got %lu",
124 (unsigned long) expected_repetition_amount,
125 (unsigned long) repeat_amount);
126 is_valid = false;
127 }
128 }
129 }
130 }
131 }
132 }
133
134 return is_valid && first_fragment_found;
135 }
136
137 std::vector<std::unique_ptr<AST::Token>>
138 SubstituteCtx::substitute_repetition (
139 size_t pattern_start, size_t pattern_end,
140 std::unique_ptr<AST::Token> separator_token)
141 {
142 rust_assert (pattern_end < macro.size ());
143
144 size_t repeat_amount = 0;
145 if (!check_repetition_amount (pattern_start, pattern_end, repeat_amount))
146 return {};
147
148 rust_debug ("repetition amount to use: %lu", (unsigned long) repeat_amount);
149 std::vector<std::unique_ptr<AST::Token>> expanded;
150 std::vector<std::unique_ptr<AST::Token>> new_macro;
151
152 // We want to generate a "new macro" to substitute with. This new macro
153 // should contain only the tokens inside the pattern
154 for (size_t tok_idx = pattern_start; tok_idx < pattern_end; tok_idx++)
155 new_macro.emplace_back (macro.at (tok_idx)->clone_token ());
156
157 // Then, we want to create a subset of the matches so that
158 // `substitute_tokens()` can only see one fragment per metavar. Let's say we
159 // have the following user input: (1 145 'h')
160 // on the following match arm: ($($lit:literal)*)
161 // which causes the following matches: { "lit": [1, 145, 'h'] }
162 //
163 // The pattern (new_macro) is `$lit:literal`
164 // The first time we expand it, we want $lit to have the following token: 1
165 // The second time, 145
166 // The third and final time, 'h'
167 //
168 // In order to do so we must create "sub maps", which only contain parts of
169 // the original matches
170 // sub-maps: [ { "lit": 1 }, { "lit": 145 }, { "lit": 'h' } ]
171 //
172 // and give them to `substitute_tokens` one by one.
173
174 for (size_t i = 0; i < repeat_amount; i++)
175 {
176 std::map<std::string, MatchedFragmentContainer *> sub_map;
177 for (auto &kv_match : fragments)
178 {
179 if (kv_match.second->is_single_fragment ())
180 sub_map.emplace (kv_match.first, kv_match.second);
181 // Hack: A repeating meta variable might not be present in the new
182 // macro. Don't include this match if the fragment doesn't have enough
183 // items, as check_repetition_amount should prevent repetition amount
184 // mismatches anyway.
185 else if (kv_match.second->get_fragments ().size () > i)
186 sub_map.emplace (kv_match.first,
187 kv_match.second->get_fragments ().at (i).get ());
188 }
189
190 auto substitute_context = SubstituteCtx (input, new_macro, sub_map);
191 auto new_tokens = substitute_context.substitute_tokens ();
192
193 // Skip the first repetition, but add the separator to the expanded
194 // tokens if it is present
195 if (i != 0 && separator_token)
196 expanded.emplace_back (separator_token->clone_token ());
197
198 for (auto &new_token : new_tokens)
199 expanded.emplace_back (new_token->clone_token ());
200 }
201
202 // FIXME: We also need to make sure that all subsequent fragments
203 // contain the same amount of repetitions as the first one
204
205 return expanded;
206 }
207
208 static bool
209 is_rep_op (std::unique_ptr<AST::Token> &tok)
210 {
211 auto id = tok->get_id ();
212 return id == QUESTION_MARK || id == ASTERISK || id == PLUS;
213 }
214
215 std::pair<std::vector<std::unique_ptr<AST::Token>>, size_t>
216 SubstituteCtx::substitute_token (size_t token_idx)
217 {
218 auto &token = macro.at (token_idx);
219
220 switch (token->get_id ())
221 {
222 default:
223 if (token_id_is_keyword (token->get_id ()))
224 {
225 case IDENTIFIER:
226 std::vector<std::unique_ptr<AST::Token>> expanded;
227
228 rust_debug ("expanding metavar: %s", token->get_str ().c_str ());
229
230 if (substitute_metavar (token, expanded))
231 return {std::move (expanded), 2};
232 }
233
234 // don't substitute, dollar sign is alone/metavar is unknown
235 return {std::vector<std::unique_ptr<AST::Token>> (), 0};
236
237 case LEFT_PAREN: {
238 // We need to parse up until the closing delimiter and expand this
239 // fragment->n times.
240 rust_debug ("expanding repetition");
241
242 // We're in a context where macro repetitions have already been
243 // parsed and validated: This means that
244 // 1/ There will be no delimiters as that is an error
245 // 2/ There are no fragment specifiers anymore, which prevents us
246 // from reusing parser functions.
247 //
248 // Repetition patterns are also special in that they cannot contain
249 // "rogue" delimiters: For example, this is invalid, as they are
250 // parsed as MacroMatches and must contain a correct amount of
251 // delimiters.
252 // `$($e:expr ) )`
253 // ^ rogue closing parenthesis
254 //
255 // With all of that in mind, we can simply skip ahead from one
256 // parenthesis to the other to find the pattern to expand. Of course,
257 // pairs of delimiters, including parentheses, are allowed.
258 // `$($e:expr ( ) )`
259 // Parentheses are the sole delimiter for which we need a special
260 // behavior since they delimit the repetition pattern
261
262 size_t pattern_start = token_idx + 1;
263 size_t pattern_end = pattern_start;
264 auto parentheses_stack = 0;
265 for (size_t idx = pattern_start; idx < macro.size (); idx++)
266 {
267 if (macro.at (idx)->get_id () == LEFT_PAREN)
268 {
269 parentheses_stack++;
270 }
271 else if (macro.at (idx)->get_id () == RIGHT_PAREN)
272 {
273 if (parentheses_stack == 0)
274 {
275 pattern_end = idx;
276 break;
277 }
278 parentheses_stack--;
279 }
280 }
281
282 // Unreachable case, but let's make sure we don't ever run into it
283 rust_assert (pattern_end != pattern_start);
284
285 std::unique_ptr<AST::Token> separator_token = nullptr;
286 if (pattern_end + 1 <= macro.size ())
287 {
288 auto &post_pattern_token = macro.at (pattern_end + 1);
289 if (!is_rep_op (post_pattern_token))
290 separator_token = post_pattern_token->clone_token ();
291 }
292
293 // Amount of tokens to skip
294 auto to_skip = 0;
295 // Parentheses
296 to_skip += 2;
297 // Repetition operator
298 to_skip += 1;
299 // Separator
300 if (separator_token)
301 to_skip += 1;
302
303 return {substitute_repetition (pattern_start, pattern_end,
304 std::move (separator_token)),
305 pattern_end - pattern_start + to_skip + 1};
306 }
307 }
308
309 rust_unreachable ();
310 }
311
312 std::vector<std::unique_ptr<AST::Token>>
313 SubstituteCtx::substitute_tokens ()
314 {
315 std::vector<std::unique_ptr<AST::Token>> replaced_tokens;
316 rust_debug ("expanding tokens");
317
318 for (size_t i = 0; i < macro.size ();)
319 {
320 auto &tok = macro.at (i);
321 if (tok->get_id () == DOLLAR_SIGN)
322 {
323 // Aaaaah, if only we had C++17 :)
324 // auto [expanded, tok_to_skip] = ...
325 auto p = substitute_token (i + 1);
326 auto expanded = std::move (p.first);
327 auto tok_to_skip = p.second;
328
329 if (!tok_to_skip)
330 {
331 replaced_tokens.emplace_back (tok->clone_token ());
332 tok_to_skip++;
333 }
334
335 i += tok_to_skip;
336
337 for (auto &token : expanded)
338 replaced_tokens.emplace_back (token->clone_token ());
339 }
340 else
341 {
342 replaced_tokens.emplace_back (tok->clone_token ());
343 i++;
344 }
345 }
346
347 return replaced_tokens;
348 }
349
350 } // namespace Rust