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