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