Line data Source code
1 : // Copyright (C) 2020-2026 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 : 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 inline tl::expected<std::string, AST::Fragment>
41 4 : format_args_parse_expr (location_t invoc_locus, AST::MacroInvocData &invoc,
42 : Parser<MacroInvocLexer> &parser,
43 : BuiltinMacro macro_kind)
44 : {
45 4 : auto format_expr_res = parser.parse_expr ();
46 4 : rust_assert (format_expr_res);
47 4 : auto format_expr = std::move (format_expr_res.value ());
48 :
49 4 : if (format_expr->get_expr_kind () == AST::Expr::Kind::MacroInvocation)
50 : {
51 1 : std::vector<std::unique_ptr<AST::MacroInvocation>> pending;
52 1 : pending.emplace_back (
53 1 : static_cast<AST::MacroInvocation *> (format_expr.release ()));
54 2 : return tl::unexpected<AST::Fragment> (
55 2 : make_eager_builtin_invocation (macro_kind, invoc_locus,
56 1 : invoc.get_delim_tok_tree (),
57 1 : std::move (pending)));
58 1 : }
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 3 : rust_assert (format_expr->is_literal ());
63 3 : return static_cast<AST::LiteralExpr &> (*format_expr)
64 3 : .get_literal ()
65 3 : .as_string ();
66 4 : }
67 :
68 : static inline tl::expected<AST::FormatArguments, FormatArgsParseError>
69 3 : format_args_parse_arguments (AST::MacroInvocData &invoc,
70 : Parser<MacroInvocLexer> &parser,
71 : TokenId last_token_id)
72 : {
73 : // TODO: check if EOF - return that format_args!() requires at least one
74 : // argument
75 :
76 3 : auto args = AST::FormatArguments ();
77 : // TODO: Allow implicit captures ONLY if the the first arg is a string literal
78 : // and not a macro invocation
79 :
80 : // TODO: How to consume all of the arguments until the delimiter?
81 :
82 : // TODO: What we then want to do is as follows:
83 : // for each token, check if it is an identifier
84 : // yes? is the next token an equal sign (=)
85 : // yes?
86 : // -> if that identifier is already present in our map, error
87 : // out
88 : // -> parse an expression, return a FormatArgument::Named
89 : // no?
90 : // -> if there have been named arguments before, error out
91 : // (positional after named error)
92 : // -> parse an expression, return a FormatArgument::Normal
93 14 : while (parser.peek_current_token ()->get_id () != last_token_id)
94 : {
95 5 : parser.skip_token (COMMA);
96 :
97 : // Check in case of an extraneous comma in the args list, which is
98 : // allowed - format_args!("fmt", arg, arg2,)
99 10 : if (parser.peek_current_token ()->get_id () == last_token_id)
100 : break;
101 :
102 4 : if (parser.peek_current_token ()->get_id () == IDENTIFIER
103 4 : && parser.peek (1)->get_id () == EQUAL)
104 : {
105 : // FIXME: This is ugly - just add a parser.parse_identifier()?
106 0 : auto ident_tok = parser.peek_current_token ();
107 0 : auto ident = Identifier (ident_tok);
108 :
109 0 : parser.skip_token (IDENTIFIER);
110 0 : parser.skip_token (EQUAL);
111 :
112 0 : auto expr = parser.parse_expr ();
113 :
114 : // TODO: Handle graciously
115 0 : if (!expr)
116 0 : rust_unreachable ();
117 :
118 0 : args.push (
119 0 : AST::FormatArgument::named (ident, std::move (expr.value ())));
120 0 : }
121 : else
122 : {
123 4 : auto expr = parser.parse_expr ();
124 :
125 : // TODO: Handle graciously
126 4 : if (!expr)
127 0 : rust_unreachable ();
128 :
129 4 : args.push (AST::FormatArgument::normal (std::move (expr.value ())));
130 4 : }
131 : // we need to skip commas, don't we?
132 : }
133 :
134 3 : return args;
135 3 : }
136 :
137 : tl::optional<AST::Fragment>
138 4 : MacroBuiltin::format_args_handler (location_t invoc_locus,
139 : AST::MacroInvocData &invoc,
140 : AST::InvocKind semicolon,
141 : AST::FormatArgs::Newline nl)
142 : {
143 4 : MacroInvocLexer lex (invoc.get_delim_tok_tree ().to_token_stream ());
144 4 : Parser<MacroInvocLexer> parser (lex);
145 :
146 4 : auto last_token_id = macro_end_token (invoc.get_delim_tok_tree (), parser);
147 :
148 4 : auto format_str = format_args_parse_expr (invoc_locus, invoc, parser,
149 : nl == AST::FormatArgs::Newline::Yes
150 : ? BuiltinMacro::FormatArgsNl
151 8 : : BuiltinMacro::FormatArgs);
152 :
153 4 : if (!format_str)
154 : {
155 1 : return std::move (format_str.error ());
156 : }
157 :
158 3 : auto args = format_args_parse_arguments (invoc, parser, last_token_id);
159 :
160 3 : if (!args)
161 : {
162 0 : rust_error_at (invoc_locus,
163 : "could not parse arguments to %<format_args!()%>");
164 0 : return tl::nullopt;
165 : }
166 :
167 : // TODO(Arthur): We need to handle this
168 : // // if it is not a literal, it's an eager macro invocation - return it
169 : // if (!fmt_expr->is_literal ())
170 : // {
171 : // auto token_tree = invoc.get_delim_tok_tree ();
172 : // return AST::Fragment ({AST::SingleASTNode (std::move (fmt_expr))},
173 : // token_tree.to_token_stream ());
174 : // }
175 :
176 : // TODO(Arthur): Handle this as well - raw strings are special for the
177 : // format_args parser auto fmt_str = static_cast<AST::LiteralExpr &>
178 : // (*fmt_arg.get ()); Switch on the format string to know if the string is raw
179 : // or cooked switch (fmt_str.get_lit_type ())
180 : // {
181 : // // case AST::Literal::RAW_STRING:
182 : // case AST::Literal::STRING:
183 : // break;
184 : // case AST::Literal::CHAR:
185 : // case AST::Literal::BYTE:
186 : // case AST::Literal::BYTE_STRING:
187 : // case AST::Literal::INT:
188 : // case AST::Literal::FLOAT:
189 : // case AST::Literal::BOOL:
190 : // case AST::Literal::ERROR:
191 : // rust_unreachable ();
192 : // }
193 :
194 3 : bool append_newline = nl == AST::FormatArgs::Newline::Yes;
195 :
196 3 : auto fmt_str = std::move (format_str.value ());
197 3 : if (append_newline)
198 0 : fmt_str += '\n';
199 :
200 3 : auto pieces = Fmt::Pieces::collect (fmt_str, append_newline,
201 3 : Fmt::ffi::ParseMode::Format);
202 :
203 : // TODO:
204 : // do the transformation into an AST::FormatArgs node
205 : // return that
206 : // expand it during lowering
207 :
208 : // TODO: we now need to take care of creating `unfinished_literal`? this is
209 : // for creating the `template`
210 :
211 3 : auto fmt_args_node = AST::FormatArgs (invoc_locus, std::move (pieces),
212 3 : std::move (args.value ()));
213 :
214 3 : auto expanded
215 : = Fmt::expand_format_args (fmt_args_node,
216 3 : invoc.get_delim_tok_tree ().to_token_stream ());
217 :
218 3 : if (!expanded.has_value ())
219 0 : return AST::Fragment::create_error ();
220 :
221 3 : return *expanded;
222 :
223 : // auto node = std::unique_ptr<AST::Expr> (fmt_args_node);
224 : // auto single_node = AST::SingleASTNode (std::move (node));
225 :
226 : // return AST::Fragment ({std::move (single_node)},
227 : // invoc.get_delim_tok_tree ().to_token_stream ());
228 14 : }
229 :
230 : } // namespace Rust
|