aboutsummaryrefslogtreecommitdiff
path: root/gcc/rust/expand/rust-macro-builtins-include.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/rust/expand/rust-macro-builtins-include.cc')
-rw-r--r--gcc/rust/expand/rust-macro-builtins-include.cc249
1 files changed, 249 insertions, 0 deletions
diff --git a/gcc/rust/expand/rust-macro-builtins-include.cc b/gcc/rust/expand/rust-macro-builtins-include.cc
new file mode 100644
index 0000000..c3b1e65
--- /dev/null
+++ b/gcc/rust/expand/rust-macro-builtins-include.cc
@@ -0,0 +1,249 @@
+// Copyright (C) 2020-2024 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3. If not see
+// <http://www.gnu.org/licenses/>.
+
+#include "rust-macro-builtins.h"
+#include "rust-macro-builtins-helpers.h"
+#include "optional.h"
+namespace Rust {
+/* Expand builtin macro include_bytes!("filename"), which includes the contents
+of the given file as reference to a byte array. Yields an expression of type
+&'static [u8; N]. */
+
+tl::optional<AST::Fragment>
+MacroBuiltin::include_bytes_handler (location_t invoc_locus,
+ AST::MacroInvocData &invoc)
+{
+ /* Get target filename from the macro invocation, which is treated as a path
+ relative to the include!-ing file (currently being compiled). */
+ auto lit_expr
+ = parse_single_string_literal (BuiltinMacro::IncludeBytes,
+ invoc.get_delim_tok_tree (), invoc_locus,
+ invoc.get_expander ());
+ if (lit_expr == nullptr)
+ return AST::Fragment::create_error ();
+
+ rust_assert (lit_expr->is_literal ());
+
+ std::string target_filename
+ = source_relative_path (lit_expr->as_string (), invoc_locus);
+
+ auto maybe_bytes = load_file_bytes (invoc_locus, target_filename.c_str ());
+
+ if (!maybe_bytes.has_value ())
+ return AST::Fragment::create_error ();
+
+ std::vector<uint8_t> bytes = maybe_bytes.value ();
+
+ /* Is there a more efficient way to do this? */
+ std::vector<std::unique_ptr<AST::Expr>> elts;
+
+ // We create the tokens for a borrow expression of a byte array, so
+ // & [ <byte0>, <byte1>, ... ]
+ std::vector<std::unique_ptr<AST::Token>> toks;
+ toks.emplace_back (make_token (Token::make (AMP, invoc_locus)));
+ toks.emplace_back (make_token (Token::make (LEFT_SQUARE, invoc_locus)));
+
+ for (uint8_t b : bytes)
+ {
+ elts.emplace_back (
+ new AST::LiteralExpr (std::string (1, (char) b), AST::Literal::BYTE,
+ PrimitiveCoreType::CORETYPE_U8,
+ {} /* outer_attrs */, invoc_locus));
+ toks.emplace_back (make_token (Token::make_byte_char (invoc_locus, b)));
+ toks.emplace_back (make_token (Token::make (COMMA, invoc_locus)));
+ }
+
+ toks.emplace_back (make_token (Token::make (RIGHT_SQUARE, invoc_locus)));
+
+ auto elems = std::unique_ptr<AST::ArrayElems> (
+ new AST::ArrayElemsValues (std::move (elts), invoc_locus));
+
+ auto array = std::unique_ptr<AST::Expr> (
+ new AST::ArrayExpr (std::move (elems), {}, {}, invoc_locus));
+
+ auto borrow = std::unique_ptr<AST::Expr> (
+ new AST::BorrowExpr (std::move (array), false, false, {}, invoc_locus));
+
+ auto node = AST::SingleASTNode (std::move (borrow));
+
+ return AST::Fragment ({node}, std::move (toks));
+}
+
+/* Expand builtin macro include_str!("filename"), which includes the contents
+ of the given file as a string. The file must be UTF-8 encoded. Yields an
+ expression of type &'static str. */
+
+tl::optional<AST::Fragment>
+MacroBuiltin::include_str_handler (location_t invoc_locus,
+ AST::MacroInvocData &invoc)
+{
+ /* Get target filename from the macro invocation, which is treated as a path
+ relative to the include!-ing file (currently being compiled). */
+ auto lit_expr
+ = parse_single_string_literal (BuiltinMacro::IncludeStr,
+ invoc.get_delim_tok_tree (), invoc_locus,
+ invoc.get_expander ());
+ if (lit_expr == nullptr)
+ return AST::Fragment::create_error ();
+
+ if (!lit_expr->is_literal ())
+ {
+ auto token_tree = invoc.get_delim_tok_tree ();
+ return AST::Fragment ({AST::SingleASTNode (std::move (lit_expr))},
+ token_tree.to_token_stream ());
+ }
+
+ std::string target_filename
+ = source_relative_path (lit_expr->as_string (), invoc_locus);
+
+ auto maybe_bytes = load_file_bytes (invoc_locus, target_filename.c_str ());
+
+ if (!maybe_bytes.has_value ())
+ return AST::Fragment::create_error ();
+
+ std::vector<uint8_t> bytes = maybe_bytes.value ();
+
+ /* FIXME: reuse lexer */
+ int expect_single = 0;
+ for (uint8_t b : bytes)
+ {
+ if (expect_single)
+ {
+ if ((b & 0xC0) != 0x80)
+ /* character was truncated, exit with expect_single != 0 */
+ break;
+ expect_single--;
+ }
+ else if (b & 0x80)
+ {
+ if (b >= 0xF8)
+ {
+ /* more than 4 leading 1s */
+ expect_single = 1;
+ break;
+ }
+ else if (b >= 0xF0)
+ {
+ /* 4 leading 1s */
+ expect_single = 3;
+ }
+ else if (b >= 0xE0)
+ {
+ /* 3 leading 1s */
+ expect_single = 2;
+ }
+ else if (b >= 0xC0)
+ {
+ /* 2 leading 1s */
+ expect_single = 1;
+ }
+ else
+ {
+ /* only 1 leading 1 */
+ expect_single = 1;
+ break;
+ }
+ }
+ }
+
+ std::string str;
+ if (expect_single)
+ rust_error_at (invoc_locus, "%s was not a valid utf-8 file",
+ target_filename.c_str ());
+ else
+ str = std::string ((const char *) bytes.data (), bytes.size ());
+
+ auto node = AST::SingleASTNode (make_string (invoc_locus, str));
+ auto str_tok = make_token (Token::make_string (invoc_locus, std::move (str)));
+
+ return AST::Fragment ({node}, std::move (str_tok));
+}
+
+/* Expand builtin macro include!(), which includes a source file at the current
+scope compile time. */
+
+tl::optional<AST::Fragment>
+MacroBuiltin::include_handler (location_t invoc_locus,
+ AST::MacroInvocData &invoc)
+{
+ /* Get target filename from the macro invocation, which is treated as a path
+ relative to the include!-ing file (currently being compiled). */
+ auto lit_expr
+ = parse_single_string_literal (BuiltinMacro::Include,
+ invoc.get_delim_tok_tree (), invoc_locus,
+ invoc.get_expander ());
+ if (lit_expr == nullptr)
+ return AST::Fragment::create_error ();
+
+ rust_assert (lit_expr->is_literal ());
+
+ std::string filename
+ = source_relative_path (lit_expr->as_string (), invoc_locus);
+ auto target_filename
+ = Rust::Session::get_instance ().include_extra_file (std::move (filename));
+
+ RAIIFile target_file (target_filename);
+ Linemap *linemap = Session::get_instance ().linemap;
+
+ if (!target_file.ok ())
+ {
+ rust_error_at (lit_expr->get_locus (),
+ "cannot open included file %qs: %m", target_filename);
+ return AST::Fragment::create_error ();
+ }
+
+ rust_debug ("Attempting to parse included file %s", target_filename);
+
+ Lexer lex (target_filename, std::move (target_file), linemap);
+ Parser<Lexer> parser (lex);
+
+ auto parsed_items = parser.parse_items ();
+ bool has_error = !parser.get_errors ().empty ();
+
+ for (const auto &error : parser.get_errors ())
+ error.emit ();
+
+ if (has_error)
+ {
+ // inform the user that the errors above are from a included file
+ rust_inform (invoc_locus, "included from here");
+ return AST::Fragment::create_error ();
+ }
+
+ std::vector<AST::SingleASTNode> nodes{};
+ for (auto &item : parsed_items)
+ {
+ AST::SingleASTNode node (std::move (item));
+ nodes.push_back (node);
+ }
+
+ // FIXME: This returns an empty vector of tokens and works fine, but is that
+ // the expected behavior? `include` macros are a bit harder to reason about
+ // since they include tokens. Furthermore, our lexer has no easy way to return
+ // a slice of tokens like the MacroInvocLexer. So it gets even harder to
+ // extrac tokens from here. For now, let's keep it that way and see if it
+ // eventually breaks, but I don't expect it to cause many issues since the
+ // list of tokens is only used when a macro invocation mixes eager
+ // macro invocations and already expanded tokens. Think
+ // `concat!(a!(), 15, b!())`. We need to be able to expand a!(), expand b!(),
+ // and then insert the `15` token in between. In the case of `include!()`, we
+ // only have one argument. So it's either going to be a macro invocation or a
+ // string literal.
+ return AST::Fragment (nodes, std::vector<std::unique_ptr<AST::Token>> ());
+}
+} // namespace Rust \ No newline at end of file