]> git.proxmox.com Git - rustc.git/blob - vendor/xflags-macros/src/parse.rs
New upstream version 1.64.0+dfsg1
[rustc.git] / vendor / xflags-macros / src / parse.rs
1 use std::mem;
2
3 #[cfg(not(test))]
4 use proc_macro::{Delimiter, TokenStream, TokenTree};
5 #[cfg(test)]
6 use proc_macro2::{Delimiter, TokenStream, TokenTree};
7
8 use crate::ast;
9
10 type Result<T, E = Error> = std::result::Result<T, E>;
11
12 #[derive(Debug)]
13 pub(crate) struct Error {
14 msg: String,
15 }
16
17 impl std::error::Error for Error {}
18
19 impl std::fmt::Display for Error {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 write!(f, "{}", self.msg)
22 }
23 }
24
25 pub(crate) fn parse(ts: TokenStream) -> Result<ast::XFlags> {
26 let mut p = Parser::new(ts);
27 xflags(&mut p)
28 }
29
30 macro_rules! format_err {
31 ($($tt:tt)*) => {
32 Error { msg: format!($($tt)*) }
33 // panic!($($tt)*)
34 };
35 }
36
37 macro_rules! bail {
38 ($($tt:tt)*) => {
39 return Err(format_err!($($tt)*))
40 };
41 }
42
43 fn xflags(p: &mut Parser) -> Result<ast::XFlags> {
44 let src = if p.eat_keyword("src") { Some(p.expect_string()?) } else { None };
45 let doc = opt_doc(p)?;
46 let mut cmd = cmd(p)?;
47 cmd.doc = doc;
48 let res = ast::XFlags { src, cmd };
49 Ok(res)
50 }
51
52 fn cmd(p: &mut Parser) -> Result<ast::Cmd> {
53 p.expect_keyword("cmd")?;
54
55 let name = cmd_name(p)?;
56 let mut res = ast::Cmd {
57 name,
58 doc: None,
59 args: Vec::new(),
60 flags: Vec::new(),
61 subcommands: Vec::new(),
62 default: false,
63 };
64
65 while !p.at_delim(Delimiter::Brace) {
66 let doc = opt_doc(p)?;
67 let arity = arity(p)?;
68 match opt_val(p)? {
69 Some(val) => {
70 let arg = ast::Arg { arity, doc, val };
71 res.args.push(arg);
72 }
73 None => bail!("expected ident"),
74 }
75 }
76
77 p.enter_delim(Delimiter::Brace)?;
78 while !p.end() {
79 let doc = opt_doc(p)?;
80 let default = p.eat_keyword("default");
81 if default || p.at_keyword("cmd") {
82 let mut cmd = cmd(p)?;
83 cmd.doc = doc;
84 res.subcommands.push(cmd);
85 if default {
86 if res.default {
87 bail!("only one subcommand can be default")
88 }
89 res.default = true;
90 res.subcommands.rotate_right(1);
91 }
92 } else {
93 let mut flag = flag(p)?;
94 flag.doc = doc;
95 res.flags.push(flag);
96 }
97 }
98 p.exit_delim()?;
99 Ok(res)
100 }
101
102 fn flag(p: &mut Parser) -> Result<ast::Flag> {
103 let arity = arity(p)?;
104
105 let mut short = None;
106 let mut name = flag_name(p)?;
107 if !name.starts_with("--") {
108 short = Some(name);
109 if !p.eat_punct(',') {
110 bail!("long option is required for `{}`", short.unwrap());
111 }
112 name = flag_name(p)?;
113 if !name.starts_with("--") {
114 bail!("long name must begin with `--`: `{}`", name);
115 }
116 }
117
118 let val = opt_val(p)?;
119 Ok(ast::Flag {
120 arity,
121 name: name[2..].to_string(),
122 short: short.map(|it| it[1..].to_string()),
123 doc: None,
124 val,
125 })
126 }
127
128 fn opt_val(p: &mut Parser) -> Result<Option<ast::Val>, Error> {
129 if !p.lookahead_punct(':', 1) {
130 return Ok(None);
131 }
132
133 let name = p.expect_name()?;
134 p.expect_punct(':')?;
135 let ty = ty(p)?;
136 let res = ast::Val { name, ty };
137 Ok(Some(res))
138 }
139
140 fn arity(p: &mut Parser) -> Result<ast::Arity> {
141 if p.eat_keyword("optional") {
142 return Ok(ast::Arity::Optional);
143 }
144 if p.eat_keyword("required") {
145 return Ok(ast::Arity::Required);
146 }
147 if p.eat_keyword("repeated") {
148 return Ok(ast::Arity::Repeated);
149 }
150 if let Some(name) = p.eat_name() {
151 bail!("expected one of `optional`, `required`, `repeated`, got `{}`", name)
152 }
153 bail!("expected one of `optional`, `required`, `repeated`, got {:?}", p.ts.pop())
154 }
155
156 fn ty(p: &mut Parser) -> Result<ast::Ty> {
157 let name = p.expect_name()?;
158 let res = match name.as_str() {
159 "PathBuf" => ast::Ty::PathBuf,
160 "OsString" => ast::Ty::OsString,
161 _ => ast::Ty::FromStr(name),
162 };
163 Ok(res)
164 }
165
166 fn opt_single_doc(p: &mut Parser) -> Result<Option<String>> {
167 if !p.eat_punct('#') {
168 return Ok(None);
169 }
170 p.enter_delim(Delimiter::Bracket)?;
171 p.expect_keyword("doc")?;
172 p.expect_punct('=')?;
173 let mut res = p.expect_string()?;
174 if let Some(suf) = res.strip_prefix(' ') {
175 res = suf.to_string();
176 }
177 p.exit_delim()?;
178 Ok(Some(res))
179 }
180
181 fn opt_doc(p: &mut Parser) -> Result<Option<String>> {
182 let lines =
183 core::iter::from_fn(|| opt_single_doc(p).transpose()).collect::<Result<Vec<String>>>()?;
184 let lines = lines.join("\n");
185
186 if lines.is_empty() {
187 Ok(None)
188 } else {
189 Ok(Some(lines))
190 }
191 }
192
193 fn cmd_name(p: &mut Parser) -> Result<String> {
194 let name = p.expect_name()?;
195 if name.starts_with('-') {
196 bail!("command name can't begin with `-`: `{}`", name);
197 }
198 Ok(name)
199 }
200
201 fn flag_name(p: &mut Parser) -> Result<String> {
202 let name = p.expect_name()?;
203 if !name.starts_with('-') {
204 bail!("flag name should begin with `-`: `{}`", name);
205 }
206 Ok(name)
207 }
208
209 struct Parser {
210 stack: Vec<Vec<TokenTree>>,
211 ts: Vec<TokenTree>,
212 }
213
214 impl Parser {
215 fn new(ts: TokenStream) -> Self {
216 let mut ts = ts.into_iter().collect::<Vec<_>>();
217 ts.reverse();
218 Self { stack: Vec::new(), ts }
219 }
220
221 fn at_delim(&mut self, delimiter: Delimiter) -> bool {
222 match self.ts.last() {
223 Some(TokenTree::Group(g)) => g.delimiter() == delimiter,
224 _ => false,
225 }
226 }
227 fn enter_delim(&mut self, delimiter: Delimiter) -> Result<()> {
228 match self.ts.pop() {
229 Some(TokenTree::Group(g)) if g.delimiter() == delimiter => {
230 let mut ts = g.stream().into_iter().collect::<Vec<_>>();
231 ts.reverse();
232 let ts = mem::replace(&mut self.ts, ts);
233 self.stack.push(ts);
234 }
235 _ => bail!("expected `{{`"),
236 }
237 Ok(())
238 }
239 fn exit_delim(&mut self) -> Result<()> {
240 if !self.end() {
241 bail!("expected `}}`")
242 }
243 self.ts = self.stack.pop().unwrap();
244 Ok(())
245 }
246 fn end(&mut self) -> bool {
247 self.ts.last().is_none()
248 }
249
250 fn expect_keyword(&mut self, kw: &str) -> Result<()> {
251 if !self.eat_keyword(kw) {
252 bail!("expected `{}`", kw)
253 }
254 Ok(())
255 }
256 fn eat_keyword(&mut self, kw: &str) -> bool {
257 if self.at_keyword(kw) {
258 self.ts.pop().unwrap();
259 true
260 } else {
261 false
262 }
263 }
264 fn at_keyword(&mut self, kw: &str) -> bool {
265 match self.ts.last() {
266 Some(TokenTree::Ident(ident)) => &ident.to_string() == kw,
267 _ => false,
268 }
269 }
270
271 fn expect_name(&mut self) -> Result<String> {
272 self.eat_name().ok_or_else(|| {
273 let next = self.ts.pop().map(|it| it.to_string()).unwrap_or_default();
274 format_err!("expected a name, got: `{}`", next)
275 })
276 }
277 fn eat_name(&mut self) -> Option<String> {
278 let mut buf = String::new();
279 let mut prev_ident = false;
280 loop {
281 match self.ts.last() {
282 Some(TokenTree::Punct(p)) if p.as_char() == '-' => {
283 prev_ident = false;
284 buf.push('-');
285 }
286 Some(TokenTree::Ident(ident)) if !prev_ident => {
287 prev_ident = true;
288 buf.push_str(&ident.to_string());
289 }
290 _ => break,
291 }
292 self.ts.pop();
293 }
294 if buf.is_empty() {
295 None
296 } else {
297 Some(buf)
298 }
299 }
300
301 fn _expect_ident(&mut self) -> Result<String> {
302 match self.ts.pop() {
303 Some(TokenTree::Ident(ident)) => Ok(ident.to_string()),
304 _ => bail!("expected ident"),
305 }
306 }
307
308 fn expect_punct(&mut self, punct: char) -> Result<()> {
309 if !self.eat_punct(punct) {
310 bail!("expected `{}`", punct)
311 }
312 Ok(())
313 }
314 fn eat_punct(&mut self, punct: char) -> bool {
315 match self.ts.last() {
316 Some(TokenTree::Punct(p)) if p.as_char() == punct => {
317 self.ts.pop();
318 true
319 }
320 _ => false,
321 }
322 }
323 fn lookahead_punct(&mut self, punct: char, n: usize) -> bool {
324 match self.ts.iter().rev().nth(n) {
325 Some(TokenTree::Punct(p)) => p.as_char() == punct,
326 _ => false,
327 }
328 }
329
330 fn expect_string(&mut self) -> Result<String> {
331 match self.ts.pop() {
332 Some(TokenTree::Literal(lit)) if lit.to_string().starts_with('"') => {
333 let text = lit.to_string();
334 let res = text.trim_matches('"').to_string();
335 Ok(res)
336 }
337 _ => bail!("expected a string"),
338 }
339 }
340 }