Branch data Line data Source code
1 : : // Copyright (C) 2020-2025 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-builtins-helpers.h"
20 : :
21 : : namespace Rust {
22 : :
23 : : std::string
24 : 65 : make_macro_path_str (BuiltinMacro kind)
25 : : {
26 : 65 : auto str = MacroBuiltin::builtins.lookup (kind);
27 : 65 : rust_assert (str.has_value ());
28 : :
29 : 65 : return str.value ();
30 : : }
31 : :
32 : : std::vector<std::unique_ptr<AST::MacroInvocation>>
33 : 145 : check_for_eager_invocations (
34 : : std::vector<std::unique_ptr<AST::Expr>> &expressions)
35 : : {
36 : 145 : std::vector<std::unique_ptr<AST::MacroInvocation>> pending;
37 : :
38 : 414 : for (auto &expr : expressions)
39 : 269 : if (expr->get_expr_kind () == AST::Expr::Kind::MacroInvocation)
40 : 54 : pending.emplace_back (std::unique_ptr<AST::MacroInvocation> (
41 : 54 : static_cast<AST::MacroInvocation *> (expr->clone_expr ().release ())));
42 : :
43 : 145 : return pending;
44 : : }
45 : :
46 : : //
47 : : // Shorthand function for creating unique_ptr tokens
48 : : //
49 : : std::unique_ptr<AST::Token>
50 : 6256 : make_token (const TokenPtr tok)
51 : : {
52 : 6256 : return std::unique_ptr<AST::Token> (new AST::Token (tok));
53 : : }
54 : :
55 : : std::unique_ptr<AST::Expr>
56 : 115 : make_string (location_t locus, std::string value)
57 : : {
58 : 115 : return std::unique_ptr<AST::Expr> (
59 : : new AST::LiteralExpr (value, AST::Literal::STRING,
60 : 230 : PrimitiveCoreType::CORETYPE_STR, {}, locus));
61 : : }
62 : :
63 : : // TODO: Is this correct?
64 : : AST::Fragment
65 : 19 : make_eager_builtin_invocation (
66 : : BuiltinMacro kind, location_t locus, AST::DelimTokenTree arguments,
67 : : std::vector<std::unique_ptr<AST::MacroInvocation>> &&pending_invocations)
68 : : {
69 : 19 : auto path_str = make_macro_path_str (kind);
70 : :
71 : 38 : std::unique_ptr<AST::Expr> node = AST::MacroInvocation::Builtin (
72 : : kind,
73 : 19 : AST::MacroInvocData (AST::SimplePath (
74 : 76 : {AST::SimplePathSegment (path_str, locus)}),
75 : 57 : std::move (arguments)),
76 : 19 : {}, locus, std::move (pending_invocations));
77 : :
78 : 38 : return AST::Fragment ({AST::SingleASTNode (std::move (node))},
79 : 57 : arguments.to_token_stream ());
80 : 19 : }
81 : :
82 : : /* Match the end token of a macro given the start delimiter of the macro */
83 : : TokenId
84 : 353 : macro_end_token (AST::DelimTokenTree &invoc_token_tree,
85 : : Parser<MacroInvocLexer> &parser)
86 : : {
87 : 353 : auto last_token_id = TokenId::RIGHT_CURLY;
88 : 353 : switch (invoc_token_tree.get_delim_type ())
89 : : {
90 : 353 : case AST::DelimType::PARENS:
91 : 353 : last_token_id = TokenId::RIGHT_PAREN;
92 : 353 : rust_assert (parser.skip_token (LEFT_PAREN));
93 : : break;
94 : :
95 : 0 : case AST::DelimType::CURLY:
96 : 0 : rust_assert (parser.skip_token (LEFT_CURLY));
97 : : break;
98 : :
99 : 0 : case AST::DelimType::SQUARE:
100 : 0 : last_token_id = TokenId::RIGHT_SQUARE;
101 : 0 : rust_assert (parser.skip_token (LEFT_SQUARE));
102 : : break;
103 : : }
104 : :
105 : 353 : return last_token_id;
106 : : }
107 : :
108 : : // Expand and then extract a string literal from the macro
109 : : std::unique_ptr<AST::LiteralExpr>
110 : 66 : try_extract_string_literal_from_fragment (const location_t &parent_locus,
111 : : std::unique_ptr<AST::Expr> &node)
112 : : {
113 : 66 : auto maybe_lit = static_cast<AST::LiteralExpr *> (node.get ());
114 : 66 : if (!node || !node->is_literal ()
115 : 130 : || maybe_lit->get_lit_type () != AST::Literal::STRING)
116 : : {
117 : 10 : rust_error_at (parent_locus, "argument must be a string literal");
118 : 10 : if (node)
119 : 10 : rust_inform (node->get_locus (), "expanded from here");
120 : 10 : return nullptr;
121 : : }
122 : 56 : return std::unique_ptr<AST::LiteralExpr> (
123 : 56 : static_cast<AST::LiteralExpr *> (node->clone_expr ().release ()));
124 : : }
125 : :
126 : : std::vector<std::unique_ptr<AST::Expr>>
127 : 149 : try_expand_many_expr (Parser<MacroInvocLexer> &parser,
128 : : const TokenId last_token_id, MacroExpander *expander,
129 : : bool &has_error)
130 : : {
131 : 149 : auto restrictions = Rust::ParseRestrictions ();
132 : : // stop parsing when encountered a braces/brackets
133 : 149 : restrictions.expr_can_be_null = true;
134 : : // we can't use std::optional, so...
135 : 149 : auto result = std::vector<std::unique_ptr<AST::Expr>> ();
136 : 149 : auto empty_expr = std::vector<std::unique_ptr<AST::Expr>> ();
137 : :
138 : 149 : auto first_token = parser.peek_current_token ()->get_id ();
139 : 149 : if (first_token == COMMA)
140 : : {
141 : 4 : rust_error_at (parser.peek_current_token ()->get_locus (),
142 : : "expected expression, found %<,%>");
143 : 4 : has_error = true;
144 : 4 : return empty_expr;
145 : : }
146 : :
147 : 828 : while (parser.peek_current_token ()->get_id () != last_token_id
148 : 1374 : && parser.peek_current_token ()->get_id () != END_OF_FILE)
149 : : {
150 : 273 : auto expr = parser.parse_expr (AST::AttrVec (), restrictions);
151 : : // something must be so wrong that the expression could not be parsed
152 : 273 : rust_assert (expr);
153 : 273 : result.push_back (std::move (expr));
154 : :
155 : 273 : auto next_token = parser.peek_current_token ();
156 : 273 : if (!parser.skip_token (COMMA) && next_token->get_id () != last_token_id)
157 : : {
158 : 4 : rust_error_at (next_token->get_locus (), "expected token: %<,%>");
159 : : // TODO: is this recoverable? to avoid crashing the parser in the next
160 : : // fragment we have to exit early here
161 : 4 : has_error = true;
162 : 4 : return empty_expr;
163 : : }
164 : 273 : }
165 : :
166 : 141 : return result;
167 : 149 : }
168 : :
169 : : // Parse a single string literal from the given delimited token tree,
170 : : // and return the LiteralExpr for it. Allow for an optional trailing comma,
171 : : // but otherwise enforce that these are the only tokens.
172 : : // FIXME(Arthur): This function needs a rework - it should not emit errors, it
173 : : // should probably be smaller
174 : : std::unique_ptr<AST::Expr>
175 : 140 : parse_single_string_literal (BuiltinMacro kind,
176 : : AST::DelimTokenTree &invoc_token_tree,
177 : : location_t invoc_locus, MacroExpander *expander,
178 : : bool is_semicoloned)
179 : : {
180 : 140 : MacroInvocLexer lex (invoc_token_tree.to_token_stream ());
181 : 140 : Parser<MacroInvocLexer> parser (lex);
182 : :
183 : 140 : auto last_token_id = macro_end_token (invoc_token_tree, parser);
184 : :
185 : 140 : std::unique_ptr<AST::LiteralExpr> lit_expr = nullptr;
186 : 140 : std::unique_ptr<AST::MacroInvocation> macro_invoc = nullptr;
187 : :
188 : 280 : if (parser.peek_current_token ()->get_id () == STRING_LITERAL)
189 : : {
190 : 82 : lit_expr = parser.parse_literal_expr ();
191 : 82 : parser.maybe_skip_token (COMMA);
192 : 164 : if (parser.peek_current_token ()->get_id () != last_token_id)
193 : : {
194 : 6 : lit_expr = nullptr;
195 : 6 : rust_error_at (invoc_locus, "macro takes 1 argument");
196 : : }
197 : : }
198 : 116 : else if (parser.peek_current_token ()->get_id () == last_token_id)
199 : 6 : rust_error_at (invoc_locus, "macro takes 1 argument");
200 : : else
201 : : {
202 : 52 : macro_invoc = parser.parse_macro_invocation (AST::AttrVec ());
203 : :
204 : 52 : parser.maybe_skip_token (COMMA);
205 : 104 : if (parser.peek_current_token ()->get_id () != last_token_id)
206 : : {
207 : 0 : lit_expr = nullptr;
208 : 0 : rust_error_at (invoc_locus, "macro takes 1 argument");
209 : : }
210 : :
211 : 52 : if (macro_invoc != nullptr)
212 : : {
213 : 46 : auto path_str = make_macro_path_str (kind);
214 : :
215 : 46 : auto pending_invocations
216 : 46 : = std::vector<std::unique_ptr<AST::MacroInvocation>> ();
217 : :
218 : 46 : pending_invocations.push_back (std::move (macro_invoc));
219 : :
220 : 92 : return AST::MacroInvocation::Builtin (
221 : : kind,
222 : 46 : AST::MacroInvocData (AST::SimplePath ({AST::SimplePathSegment (
223 : 184 : path_str, invoc_locus)}),
224 : 138 : std::move (invoc_token_tree)),
225 : 46 : {}, invoc_locus, std::move (pending_invocations), is_semicoloned);
226 : 46 : }
227 : : else
228 : : {
229 : 6 : rust_error_at (invoc_locus, "argument must be a string literal or a "
230 : : "macro which expands to a string");
231 : : }
232 : : }
233 : :
234 : 94 : parser.skip_token (last_token_id);
235 : :
236 : 94 : return std::unique_ptr<AST::Expr> (std::move (lit_expr));
237 : 280 : }
238 : :
239 : : /* Treat PATH as a path relative to the source file currently being
240 : : compiled, and return the absolute path for it. */
241 : : std::string
242 : 72 : source_relative_path (std::string path, location_t locus)
243 : : {
244 : 72 : std::string compile_fname = LOCATION_FILE (locus);
245 : :
246 : 72 : auto dir_separator_pos = compile_fname.rfind (file_separator);
247 : :
248 : : /* If there is no file_separator in the path, use current dir ('.'). */
249 : 72 : std::string dirname;
250 : 72 : if (dir_separator_pos == std::string::npos)
251 : 0 : dirname = std::string (".") + file_separator;
252 : : else
253 : 144 : dirname = compile_fname.substr (0, dir_separator_pos) + file_separator;
254 : :
255 : 72 : return dirname + path;
256 : 72 : }
257 : :
258 : : /* Read the full contents of the file FILENAME and return them in a vector.
259 : : FIXME: platform specific. */
260 : : tl::optional<std::vector<uint8_t>>
261 : 62 : load_file_bytes (location_t invoc_locus, const char *filename)
262 : : {
263 : 62 : RAIIFile file_wrap (filename);
264 : 62 : if (file_wrap.get_raw () == nullptr)
265 : : {
266 : 8 : rust_error_at (invoc_locus, "cannot open filename %s: %m", filename);
267 : 8 : return tl::nullopt;
268 : : }
269 : :
270 : 54 : FILE *f = file_wrap.get_raw ();
271 : 54 : fseek (f, 0L, SEEK_END);
272 : 54 : long fsize = ftell (f);
273 : 54 : fseek (f, 0L, SEEK_SET);
274 : :
275 : 54 : std::vector<uint8_t> buf (fsize);
276 : :
277 : 54 : if (fsize > 0 && fread (&buf[0], fsize, 1, f) != 1)
278 : : {
279 : 0 : rust_error_at (invoc_locus, "error reading file %s: %m", filename);
280 : 0 : return std::vector<uint8_t> ();
281 : : }
282 : :
283 : 54 : return buf;
284 : 54 : }
285 : : } // namespace Rust
|