// Copyright (C) 2020-2025 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 // . #include "rust-ast-fragment.h" #include "rust-fmt.h" #include "rust-macro-builtins-helpers.h" #include "rust-expand-format-args.h" namespace Rust { struct FormatArgsInput { std::string format_str; AST::FormatArguments args; // bool is_literal? }; struct FormatArgsParseError { enum class Kind { MissingArguments } kind; }; static tl::expected format_args_parse_arguments (AST::MacroInvocData &invoc) { MacroInvocLexer lex (invoc.get_delim_tok_tree ().to_token_stream ()); Parser parser (lex); // TODO: check if EOF - return that format_args!() requires at least one // argument auto args = AST::FormatArguments (); auto last_token_id = macro_end_token (invoc.get_delim_tok_tree (), parser); std::unique_ptr format_expr = nullptr; // TODO: Handle the case where we're not parsing a string literal (macro // invocation for e.g.) if (parser.peek_current_token ()->get_id () == STRING_LITERAL) format_expr = parser.parse_literal_expr (); // TODO(Arthur): Clean this up - if we haven't parsed a string literal but a // macro invocation, what do we do here? return a tl::unexpected? auto format_str = static_cast (*format_expr) .get_literal () .as_string (); // TODO: Allow implicit captures ONLY if the the first arg is a string literal // and not a macro invocation // TODO: How to consume all of the arguments until the delimiter? // TODO: What we then want to do is as follows: // for each token, check if it is an identifier // yes? is the next token an equal sign (=) // yes? // -> if that identifier is already present in our map, error // out // -> parse an expression, return a FormatArgument::Named // no? // -> if there have been named arguments before, error out // (positional after named error) // -> parse an expression, return a FormatArgument::Normal while (parser.peek_current_token ()->get_id () != last_token_id) { parser.skip_token (COMMA); if (parser.peek_current_token ()->get_id () == IDENTIFIER && parser.peek (1)->get_id () == EQUAL) { // FIXME: This is ugly - just add a parser.parse_identifier()? auto ident_tok = parser.peek_current_token (); auto ident = Identifier (ident_tok); parser.skip_token (IDENTIFIER); parser.skip_token (EQUAL); auto expr = parser.parse_expr (); // TODO: Handle graciously if (!expr) rust_unreachable (); args.push (AST::FormatArgument::named (ident, std::move (expr))); } else { auto expr = parser.parse_expr (); // TODO: Handle graciously if (!expr) rust_unreachable (); args.push (AST::FormatArgument::normal (std::move (expr))); } // we need to skip commas, don't we? } return FormatArgsInput{std::move (format_str), std::move (args)}; } tl::optional MacroBuiltin::format_args_handler (location_t invoc_locus, AST::MacroInvocData &invoc, AST::InvocKind semicolon, AST::FormatArgs::Newline nl) { auto input = format_args_parse_arguments (invoc); if (!input) { rust_error_at (invoc_locus, "could not parse arguments to %"); return tl::nullopt; } // TODO(Arthur): We need to handle this // // if it is not a literal, it's an eager macro invocation - return it // if (!fmt_expr->is_literal ()) // { // auto token_tree = invoc.get_delim_tok_tree (); // return AST::Fragment ({AST::SingleASTNode (std::move (fmt_expr))}, // token_tree.to_token_stream ()); // } // TODO(Arthur): Handle this as well - raw strings are special for the // format_args parser auto fmt_str = static_cast // (*fmt_arg.get ()); Switch on the format string to know if the string is raw // or cooked switch (fmt_str.get_lit_type ()) // { // // case AST::Literal::RAW_STRING: // case AST::Literal::STRING: // break; // case AST::Literal::CHAR: // case AST::Literal::BYTE: // case AST::Literal::BYTE_STRING: // case AST::Literal::INT: // case AST::Literal::FLOAT: // case AST::Literal::BOOL: // case AST::Literal::ERROR: // rust_unreachable (); // } bool append_newline = nl == AST::FormatArgs::Newline::Yes; auto fmt_str = std::move (input->format_str); if (append_newline) fmt_str += '\n'; auto pieces = Fmt::Pieces::collect (fmt_str, append_newline, Fmt::ffi::ParseMode::Format); // TODO: // do the transformation into an AST::FormatArgs node // return that // expand it during lowering // TODO: we now need to take care of creating `unfinished_literal`? this is // for creating the `template` auto fmt_args_node = AST::FormatArgs (invoc_locus, std::move (pieces), std::move (input->args)); auto expanded = Fmt::expand_format_args (fmt_args_node, invoc.get_delim_tok_tree ().to_token_stream ()); if (!expanded.has_value ()) return AST::Fragment::create_error (); return *expanded; // auto node = std::unique_ptr (fmt_args_node); // auto single_node = AST::SingleASTNode (std::move (node)); // return AST::Fragment ({std::move (single_node)}, // invoc.get_delim_tok_tree ().to_token_stream ()); } } // namespace Rust