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