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-ast-fragment.h"
20 : : #include "rust-common.h"
21 : : #include "rust-macro-builtins.h"
22 : : #include "rust-macro-builtins-helpers.h"
23 : : #include "rust-session-manager.h"
24 : : #include "optional.h"
25 : : namespace Rust {
26 : : /* Expand builtin macro include_bytes!("filename"), which includes the contents
27 : : of the given file as reference to a byte array. Yields an expression of type
28 : : &'static [u8; N]. */
29 : :
30 : : tl::optional<AST::Fragment>
31 : 55 : MacroBuiltin::include_bytes_handler (location_t invoc_locus,
32 : : AST::MacroInvocData &invoc,
33 : : AST::InvocKind semicolon)
34 : : {
35 : : /* Get target filename from the macro invocation, which is treated as a path
36 : : relative to the include!-ing file (currently being compiled). */
37 : 55 : auto lit_expr
38 : : = parse_single_string_literal (BuiltinMacro::IncludeBytes,
39 : : invoc.get_delim_tok_tree (), invoc_locus,
40 : 55 : invoc.get_expander ());
41 : 55 : if (lit_expr == nullptr)
42 : 12 : return AST::Fragment::create_error ();
43 : :
44 : 49 : if (!lit_expr->is_literal ())
45 : : {
46 : 20 : auto token_tree = invoc.get_delim_tok_tree ();
47 : 40 : return AST::Fragment ({AST::SingleASTNode (std::move (lit_expr))},
48 : 80 : token_tree.to_token_stream ());
49 : 20 : }
50 : :
51 : 29 : std::string target_filename
52 : 29 : = source_relative_path (lit_expr->as_string (), invoc_locus);
53 : :
54 : 29 : auto maybe_bytes = load_file_bytes (invoc_locus, target_filename.c_str ());
55 : :
56 : 29 : if (!maybe_bytes.has_value ())
57 : 8 : return AST::Fragment::create_error ();
58 : :
59 : 25 : std::vector<uint8_t> bytes = maybe_bytes.value ();
60 : :
61 : : /* Is there a more efficient way to do this? */
62 : 25 : std::vector<std::unique_ptr<AST::Expr>> elts;
63 : :
64 : : // We create the tokens for a borrow expression of a byte array, so
65 : : // & [ <byte0>, <byte1>, ... ]
66 : 25 : std::vector<std::unique_ptr<AST::Token>> toks;
67 : 25 : toks.emplace_back (make_token (Token::make (AMP, invoc_locus)));
68 : 25 : toks.emplace_back (make_token (Token::make (LEFT_SQUARE, invoc_locus)));
69 : :
70 : 3017 : for (uint8_t b : bytes)
71 : : {
72 : 2992 : elts.emplace_back (
73 : 5984 : new AST::LiteralExpr (std::string (1, (char) b), AST::Literal::BYTE,
74 : : PrimitiveCoreType::CORETYPE_U8,
75 : 5984 : {} /* outer_attrs */, invoc_locus));
76 : 2992 : toks.emplace_back (make_token (Token::make_byte_char (invoc_locus, b)));
77 : 5984 : toks.emplace_back (make_token (Token::make (COMMA, invoc_locus)));
78 : : }
79 : :
80 : 25 : toks.emplace_back (make_token (Token::make (RIGHT_SQUARE, invoc_locus)));
81 : :
82 : 25 : auto elems = std::unique_ptr<AST::ArrayElems> (
83 : 25 : new AST::ArrayElemsValues (std::move (elts), invoc_locus));
84 : :
85 : 25 : auto array = std::unique_ptr<AST::Expr> (
86 : 25 : new AST::ArrayExpr (std::move (elems), {}, {}, invoc_locus));
87 : :
88 : 25 : auto borrow = std::unique_ptr<AST::Expr> (
89 : : new AST::BorrowExpr (std::move (array), Mutability::Imm,
90 : : /* raw borrow */ false,
91 : 25 : /* double borrow */ false, {}, invoc_locus));
92 : :
93 : 25 : auto node = AST::SingleASTNode (std::move (borrow));
94 : :
95 : 75 : return AST::Fragment ({node}, std::move (toks));
96 : 80 : }
97 : :
98 : : /* Expand builtin macro include_str!("filename"), which includes the contents
99 : : of the given file as a string. The file must be UTF-8 encoded. Yields an
100 : : expression of type &'static str. */
101 : :
102 : : tl::optional<AST::Fragment>
103 : 61 : MacroBuiltin::include_str_handler (location_t invoc_locus,
104 : : AST::MacroInvocData &invoc,
105 : : AST::InvocKind semicolon)
106 : : {
107 : : /* Get target filename from the macro invocation, which is treated as a path
108 : : relative to the include!-ing file (currently being compiled). */
109 : 61 : auto lit_expr
110 : : = parse_single_string_literal (BuiltinMacro::IncludeStr,
111 : : invoc.get_delim_tok_tree (), invoc_locus,
112 : 61 : invoc.get_expander ());
113 : 61 : if (lit_expr == nullptr)
114 : 12 : return AST::Fragment::create_error ();
115 : :
116 : 55 : if (!lit_expr->is_literal ())
117 : : {
118 : 22 : auto token_tree = invoc.get_delim_tok_tree ();
119 : 44 : return AST::Fragment ({AST::SingleASTNode (std::move (lit_expr))},
120 : 88 : token_tree.to_token_stream ());
121 : 22 : }
122 : :
123 : 33 : std::string target_filename
124 : 33 : = source_relative_path (lit_expr->as_string (), invoc_locus);
125 : :
126 : 33 : auto maybe_bytes = load_file_bytes (invoc_locus, target_filename.c_str ());
127 : :
128 : 33 : if (!maybe_bytes.has_value ())
129 : 8 : return AST::Fragment::create_error ();
130 : :
131 : 29 : std::vector<uint8_t> bytes = maybe_bytes.value ();
132 : :
133 : : /* FIXME: reuse lexer */
134 : 29 : int expect_single = 0;
135 : 3261 : for (uint8_t b : bytes)
136 : : {
137 : 3234 : if (expect_single)
138 : : {
139 : 0 : if ((b & 0xC0) != 0x80)
140 : : /* character was truncated, exit with expect_single != 0 */
141 : : break;
142 : 0 : expect_single--;
143 : : }
144 : 3234 : else if (b & 0x80)
145 : : {
146 : 2 : if (b >= 0xF8)
147 : : {
148 : : /* more than 4 leading 1s */
149 : : expect_single = 1;
150 : : break;
151 : : }
152 : 0 : else if (b >= 0xF0)
153 : : {
154 : : /* 4 leading 1s */
155 : : expect_single = 3;
156 : : }
157 : 0 : else if (b >= 0xE0)
158 : : {
159 : : /* 3 leading 1s */
160 : : expect_single = 2;
161 : : }
162 : 0 : else if (b >= 0xC0)
163 : : {
164 : : /* 2 leading 1s */
165 : : expect_single = 1;
166 : : }
167 : : else
168 : : {
169 : : /* only 1 leading 1 */
170 : : expect_single = 1;
171 : : break;
172 : : }
173 : : }
174 : : }
175 : :
176 : 29 : std::string str;
177 : 29 : if (expect_single)
178 : 2 : rust_error_at (invoc_locus, "%s was not a valid utf-8 file",
179 : : target_filename.c_str ());
180 : : else
181 : 27 : str = std::string ((const char *) bytes.data (), bytes.size ());
182 : :
183 : 58 : auto node = AST::SingleASTNode (make_string (invoc_locus, str));
184 : 29 : auto str_tok = make_token (Token::make_string (invoc_locus, std::move (str)));
185 : :
186 : 87 : return AST::Fragment ({node}, std::move (str_tok));
187 : 90 : }
188 : :
189 : : /* Expand builtin macro include!(), which includes a source file at the current
190 : : scope compile time. */
191 : :
192 : : tl::optional<AST::Fragment>
193 : 14 : MacroBuiltin::include_handler (location_t invoc_locus,
194 : : AST::MacroInvocData &invoc,
195 : : AST::InvocKind semicolon)
196 : : {
197 : 14 : bool is_semicoloned = semicolon == AST::InvocKind::Semicoloned;
198 : : /* Get target filename from the macro invocation, which is treated as a path
199 : : relative to the include!-ing file (currently being compiled). */
200 : 14 : std::unique_ptr<AST::Expr> lit_expr
201 : : = parse_single_string_literal (BuiltinMacro::Include,
202 : : invoc.get_delim_tok_tree (), invoc_locus,
203 : 14 : invoc.get_expander (), is_semicoloned);
204 : 14 : if (lit_expr == nullptr)
205 : 0 : return AST::Fragment::create_error ();
206 : :
207 : 14 : if (!lit_expr->is_literal ())
208 : : {
209 : : // We have to expand an inner macro eagerly
210 : 4 : auto token_tree = invoc.get_delim_tok_tree ();
211 : :
212 : : // parse_single_string_literal returned an AST::MacroInvocation, which
213 : : // can either be an AST::Item or AST::Expr. Depending on the context the
214 : : // original macro was invoked in, we will set AST::Item or AST::Expr
215 : : // appropriately.
216 : 4 : if (is_semicoloned)
217 : : {
218 : 2 : std::unique_ptr<AST::Item> lit_item = std::unique_ptr<AST::Item> (
219 : 2 : static_cast<AST::MacroInvocation *> (lit_expr.release ()));
220 : 4 : return AST::Fragment ({AST::SingleASTNode (std::move (lit_item))},
221 : 8 : token_tree.to_token_stream ());
222 : 2 : }
223 : : else
224 : 4 : return AST::Fragment ({AST::SingleASTNode (std::move (lit_expr))},
225 : 8 : token_tree.to_token_stream ());
226 : 4 : }
227 : :
228 : 10 : std::string filename
229 : 10 : = source_relative_path (lit_expr->as_string (), invoc_locus);
230 : 10 : auto target_filename
231 : 10 : = Rust::Session::get_instance ().include_extra_file (std::move (filename));
232 : :
233 : 10 : RAIIFile target_file (target_filename);
234 : 10 : Linemap *linemap = Session::get_instance ().linemap;
235 : :
236 : 10 : if (!target_file.ok ())
237 : : {
238 : 0 : rust_error_at (lit_expr->get_locus (),
239 : : "cannot open included file %qs: %m", target_filename);
240 : 0 : return AST::Fragment::create_error ();
241 : : }
242 : :
243 : 10 : rust_debug ("Attempting to parse included file %s", target_filename);
244 : :
245 : 10 : Lexer lex (target_filename, std::move (target_file), linemap);
246 : 10 : Parser<Lexer> parser (lex);
247 : 10 : std::unique_ptr<AST::Expr> parsed_expr = nullptr;
248 : 10 : std::vector<std::unique_ptr<AST::Item>> parsed_items{};
249 : :
250 : 10 : if (is_semicoloned)
251 : 6 : parsed_items = parser.parse_items ();
252 : : else
253 : 4 : parsed_expr = parser.parse_expr ();
254 : :
255 : 10 : bool has_error = !parser.get_errors ().empty ();
256 : :
257 : 10 : for (const auto &error : parser.get_errors ())
258 : 0 : error.emit ();
259 : :
260 : 10 : if (has_error)
261 : : {
262 : : // inform the user that the errors above are from a included file
263 : 0 : rust_inform (invoc_locus, "included from here");
264 : 0 : return AST::Fragment::create_error ();
265 : : }
266 : :
267 : 10 : std::vector<AST::SingleASTNode> nodes{};
268 : 10 : if (is_semicoloned)
269 : 10 : for (auto &item : parsed_items)
270 : : {
271 : 4 : AST::SingleASTNode node (std::move (item));
272 : 4 : nodes.push_back (node);
273 : 4 : }
274 : : else
275 : : {
276 : 4 : AST::SingleASTNode node (std::move (parsed_expr));
277 : 4 : nodes.push_back (node);
278 : 4 : }
279 : : // FIXME: This returns an empty vector of tokens and works fine, but is that
280 : : // the expected behavior? `include` macros are a bit harder to reason about
281 : : // since they include tokens. Furthermore, our lexer has no easy way to return
282 : : // a slice of tokens like the MacroInvocLexer. So it gets even harder to
283 : : // extract tokens from here. For now, let's keep it that way and see if it
284 : : // eventually breaks, but I don't expect it to cause many issues since the
285 : : // list of tokens is only used when a macro invocation mixes eager
286 : : // macro invocations and already expanded tokens. Think
287 : : // `concat!(a!(), 15, b!())`. We need to be able to expand a!(), expand b!(),
288 : : // and then insert the `15` token in between. In the case of `include!()`, we
289 : : // only have one argument. So it's either going to be a macro invocation or a
290 : : // string literal.
291 : 20 : return AST::Fragment (nodes, std::vector<std::unique_ptr<AST::Token>> ());
292 : 34 : }
293 : : } // namespace Rust
|