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 : : #include "rust-ast-fragment.h"
19 : : #include "rust-fmt.h"
20 : : #include "rust-macro-builtins-helpers.h"
21 : : #include "rust-expand-format-args.h"
22 : :
23 : : namespace Rust {
24 : :
25 : 8 : struct FormatArgsInput
26 : : {
27 : : std::string format_str;
28 : : AST::FormatArguments args;
29 : : // bool is_literal?
30 : : };
31 : :
32 : : struct FormatArgsParseError
33 : : {
34 : : enum class Kind
35 : : {
36 : : MissingArguments
37 : : } kind;
38 : : };
39 : :
40 : : static tl::expected<FormatArgsInput, FormatArgsParseError>
41 : 4 : format_args_parse_arguments (AST::MacroInvocData &invoc)
42 : : {
43 : 4 : MacroInvocLexer lex (invoc.get_delim_tok_tree ().to_token_stream ());
44 : 4 : Parser<MacroInvocLexer> parser (lex);
45 : :
46 : : // TODO: check if EOF - return that format_args!() requires at least one
47 : : // argument
48 : :
49 : 4 : auto args = AST::FormatArguments ();
50 : 4 : auto last_token_id = macro_end_token (invoc.get_delim_tok_tree (), parser);
51 : 4 : std::unique_ptr<AST::Expr> format_expr = nullptr;
52 : :
53 : : // TODO: Handle the case where we're not parsing a string literal (macro
54 : : // invocation for e.g.)
55 : 8 : if (parser.peek_current_token ()->get_id () == STRING_LITERAL)
56 : 4 : format_expr = parser.parse_literal_expr ();
57 : :
58 : 4 : rust_assert (format_expr);
59 : :
60 : : // TODO(Arthur): Clean this up - if we haven't parsed a string literal but a
61 : : // macro invocation, what do we do here? return a tl::unexpected?
62 : 4 : auto format_str = static_cast<AST::LiteralExpr &> (*format_expr)
63 : 4 : .get_literal ()
64 : 4 : .as_string ();
65 : :
66 : : // TODO: Allow implicit captures ONLY if the the first arg is a string literal
67 : : // and not a macro invocation
68 : :
69 : : // TODO: How to consume all of the arguments until the delimiter?
70 : :
71 : : // TODO: What we then want to do is as follows:
72 : : // for each token, check if it is an identifier
73 : : // yes? is the next token an equal sign (=)
74 : : // yes?
75 : : // -> if that identifier is already present in our map, error
76 : : // out
77 : : // -> parse an expression, return a FormatArgument::Named
78 : : // no?
79 : : // -> if there have been named arguments before, error out
80 : : // (positional after named error)
81 : : // -> parse an expression, return a FormatArgument::Normal
82 : 20 : while (parser.peek_current_token ()->get_id () != last_token_id)
83 : : {
84 : 8 : parser.skip_token (COMMA);
85 : :
86 : : // Check in case of an extraneous comma in the args list, which is
87 : : // allowed - format_args!("fmt", arg, arg2,)
88 : 16 : if (parser.peek_current_token ()->get_id () == last_token_id)
89 : : break;
90 : :
91 : 6 : if (parser.peek_current_token ()->get_id () == IDENTIFIER
92 : 6 : && parser.peek (1)->get_id () == EQUAL)
93 : : {
94 : : // FIXME: This is ugly - just add a parser.parse_identifier()?
95 : 0 : auto ident_tok = parser.peek_current_token ();
96 : 0 : auto ident = Identifier (ident_tok);
97 : :
98 : 0 : parser.skip_token (IDENTIFIER);
99 : 0 : parser.skip_token (EQUAL);
100 : :
101 : 0 : auto expr = parser.parse_expr ();
102 : :
103 : : // TODO: Handle graciously
104 : 0 : if (!expr)
105 : 0 : rust_unreachable ();
106 : :
107 : 0 : args.push (AST::FormatArgument::named (ident, std::move (expr)));
108 : 0 : }
109 : : else
110 : : {
111 : 6 : auto expr = parser.parse_expr ();
112 : :
113 : : // TODO: Handle graciously
114 : 6 : if (!expr)
115 : 0 : rust_unreachable ();
116 : :
117 : 6 : args.push (AST::FormatArgument::normal (std::move (expr)));
118 : 6 : }
119 : : // we need to skip commas, don't we?
120 : : }
121 : :
122 : 8 : return FormatArgsInput{std::move (format_str), std::move (args)};
123 : 8 : }
124 : :
125 : : tl::optional<AST::Fragment>
126 : 4 : MacroBuiltin::format_args_handler (location_t invoc_locus,
127 : : AST::MacroInvocData &invoc,
128 : : AST::InvocKind semicolon,
129 : : AST::FormatArgs::Newline nl)
130 : : {
131 : 4 : auto input = format_args_parse_arguments (invoc);
132 : :
133 : 4 : if (!input)
134 : : {
135 : 0 : rust_error_at (invoc_locus,
136 : : "could not parse arguments to %<format_args!()%>");
137 : 0 : return tl::nullopt;
138 : : }
139 : :
140 : : // TODO(Arthur): We need to handle this
141 : : // // if it is not a literal, it's an eager macro invocation - return it
142 : : // if (!fmt_expr->is_literal ())
143 : : // {
144 : : // auto token_tree = invoc.get_delim_tok_tree ();
145 : : // return AST::Fragment ({AST::SingleASTNode (std::move (fmt_expr))},
146 : : // token_tree.to_token_stream ());
147 : : // }
148 : :
149 : : // TODO(Arthur): Handle this as well - raw strings are special for the
150 : : // format_args parser auto fmt_str = static_cast<AST::LiteralExpr &>
151 : : // (*fmt_arg.get ()); Switch on the format string to know if the string is raw
152 : : // or cooked switch (fmt_str.get_lit_type ())
153 : : // {
154 : : // // case AST::Literal::RAW_STRING:
155 : : // case AST::Literal::STRING:
156 : : // break;
157 : : // case AST::Literal::CHAR:
158 : : // case AST::Literal::BYTE:
159 : : // case AST::Literal::BYTE_STRING:
160 : : // case AST::Literal::INT:
161 : : // case AST::Literal::FLOAT:
162 : : // case AST::Literal::BOOL:
163 : : // case AST::Literal::ERROR:
164 : : // rust_unreachable ();
165 : : // }
166 : :
167 : 4 : bool append_newline = nl == AST::FormatArgs::Newline::Yes;
168 : :
169 : 4 : auto fmt_str = std::move (input->format_str);
170 : 4 : if (append_newline)
171 : 0 : fmt_str += '\n';
172 : :
173 : 4 : auto pieces = Fmt::Pieces::collect (fmt_str, append_newline,
174 : 4 : Fmt::ffi::ParseMode::Format);
175 : :
176 : : // TODO:
177 : : // do the transformation into an AST::FormatArgs node
178 : : // return that
179 : : // expand it during lowering
180 : :
181 : : // TODO: we now need to take care of creating `unfinished_literal`? this is
182 : : // for creating the `template`
183 : :
184 : 4 : auto fmt_args_node = AST::FormatArgs (invoc_locus, std::move (pieces),
185 : 4 : std::move (input->args));
186 : :
187 : 4 : auto expanded
188 : : = Fmt::expand_format_args (fmt_args_node,
189 : 4 : invoc.get_delim_tok_tree ().to_token_stream ());
190 : :
191 : 4 : if (!expanded.has_value ())
192 : 0 : return AST::Fragment::create_error ();
193 : :
194 : 4 : return *expanded;
195 : :
196 : : // auto node = std::unique_ptr<AST::Expr> (fmt_args_node);
197 : : // auto single_node = AST::SingleASTNode (std::move (node));
198 : :
199 : : // return AST::Fragment ({std::move (single_node)},
200 : : // invoc.get_delim_tok_tree ().to_token_stream ());
201 : 4 : }
202 : :
203 : : } // namespace Rust
|