summaryrefslogtreecommitdiffstats
path: root/vendor/xflags-macros/src/parse.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
commit698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch)
tree173a775858bd501c378080a10dca74132f05bc50 /vendor/xflags-macros/src/parse.rs
parentInitial commit. (diff)
downloadrustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz
rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/xflags-macros/src/parse.rs')
-rw-r--r--vendor/xflags-macros/src/parse.rs340
1 files changed, 340 insertions, 0 deletions
diff --git a/vendor/xflags-macros/src/parse.rs b/vendor/xflags-macros/src/parse.rs
new file mode 100644
index 000000000..e9749b141
--- /dev/null
+++ b/vendor/xflags-macros/src/parse.rs
@@ -0,0 +1,340 @@
+use std::mem;
+
+#[cfg(not(test))]
+use proc_macro::{Delimiter, TokenStream, TokenTree};
+#[cfg(test)]
+use proc_macro2::{Delimiter, TokenStream, TokenTree};
+
+use crate::ast;
+
+type Result<T, E = Error> = std::result::Result<T, E>;
+
+#[derive(Debug)]
+pub(crate) struct Error {
+ msg: String,
+}
+
+impl std::error::Error for Error {}
+
+impl std::fmt::Display for Error {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.msg)
+ }
+}
+
+pub(crate) fn parse(ts: TokenStream) -> Result<ast::XFlags> {
+ let mut p = Parser::new(ts);
+ xflags(&mut p)
+}
+
+macro_rules! format_err {
+ ($($tt:tt)*) => {
+ Error { msg: format!($($tt)*) }
+ // panic!($($tt)*)
+ };
+}
+
+macro_rules! bail {
+ ($($tt:tt)*) => {
+ return Err(format_err!($($tt)*))
+ };
+}
+
+fn xflags(p: &mut Parser) -> Result<ast::XFlags> {
+ let src = if p.eat_keyword("src") { Some(p.expect_string()?) } else { None };
+ let doc = opt_doc(p)?;
+ let mut cmd = cmd(p)?;
+ cmd.doc = doc;
+ let res = ast::XFlags { src, cmd };
+ Ok(res)
+}
+
+fn cmd(p: &mut Parser) -> Result<ast::Cmd> {
+ p.expect_keyword("cmd")?;
+
+ let name = cmd_name(p)?;
+ let mut res = ast::Cmd {
+ name,
+ doc: None,
+ args: Vec::new(),
+ flags: Vec::new(),
+ subcommands: Vec::new(),
+ default: false,
+ };
+
+ while !p.at_delim(Delimiter::Brace) {
+ let doc = opt_doc(p)?;
+ let arity = arity(p)?;
+ match opt_val(p)? {
+ Some(val) => {
+ let arg = ast::Arg { arity, doc, val };
+ res.args.push(arg);
+ }
+ None => bail!("expected ident"),
+ }
+ }
+
+ p.enter_delim(Delimiter::Brace)?;
+ while !p.end() {
+ let doc = opt_doc(p)?;
+ let default = p.eat_keyword("default");
+ if default || p.at_keyword("cmd") {
+ let mut cmd = cmd(p)?;
+ cmd.doc = doc;
+ res.subcommands.push(cmd);
+ if default {
+ if res.default {
+ bail!("only one subcommand can be default")
+ }
+ res.default = true;
+ res.subcommands.rotate_right(1);
+ }
+ } else {
+ let mut flag = flag(p)?;
+ flag.doc = doc;
+ res.flags.push(flag);
+ }
+ }
+ p.exit_delim()?;
+ Ok(res)
+}
+
+fn flag(p: &mut Parser) -> Result<ast::Flag> {
+ let arity = arity(p)?;
+
+ let mut short = None;
+ let mut name = flag_name(p)?;
+ if !name.starts_with("--") {
+ short = Some(name);
+ if !p.eat_punct(',') {
+ bail!("long option is required for `{}`", short.unwrap());
+ }
+ name = flag_name(p)?;
+ if !name.starts_with("--") {
+ bail!("long name must begin with `--`: `{}`", name);
+ }
+ }
+
+ let val = opt_val(p)?;
+ Ok(ast::Flag {
+ arity,
+ name: name[2..].to_string(),
+ short: short.map(|it| it[1..].to_string()),
+ doc: None,
+ val,
+ })
+}
+
+fn opt_val(p: &mut Parser) -> Result<Option<ast::Val>, Error> {
+ if !p.lookahead_punct(':', 1) {
+ return Ok(None);
+ }
+
+ let name = p.expect_name()?;
+ p.expect_punct(':')?;
+ let ty = ty(p)?;
+ let res = ast::Val { name, ty };
+ Ok(Some(res))
+}
+
+fn arity(p: &mut Parser) -> Result<ast::Arity> {
+ if p.eat_keyword("optional") {
+ return Ok(ast::Arity::Optional);
+ }
+ if p.eat_keyword("required") {
+ return Ok(ast::Arity::Required);
+ }
+ if p.eat_keyword("repeated") {
+ return Ok(ast::Arity::Repeated);
+ }
+ if let Some(name) = p.eat_name() {
+ bail!("expected one of `optional`, `required`, `repeated`, got `{}`", name)
+ }
+ bail!("expected one of `optional`, `required`, `repeated`, got {:?}", p.ts.pop())
+}
+
+fn ty(p: &mut Parser) -> Result<ast::Ty> {
+ let name = p.expect_name()?;
+ let res = match name.as_str() {
+ "PathBuf" => ast::Ty::PathBuf,
+ "OsString" => ast::Ty::OsString,
+ _ => ast::Ty::FromStr(name),
+ };
+ Ok(res)
+}
+
+fn opt_single_doc(p: &mut Parser) -> Result<Option<String>> {
+ if !p.eat_punct('#') {
+ return Ok(None);
+ }
+ p.enter_delim(Delimiter::Bracket)?;
+ p.expect_keyword("doc")?;
+ p.expect_punct('=')?;
+ let mut res = p.expect_string()?;
+ if let Some(suf) = res.strip_prefix(' ') {
+ res = suf.to_string();
+ }
+ p.exit_delim()?;
+ Ok(Some(res))
+}
+
+fn opt_doc(p: &mut Parser) -> Result<Option<String>> {
+ let lines =
+ core::iter::from_fn(|| opt_single_doc(p).transpose()).collect::<Result<Vec<String>>>()?;
+ let lines = lines.join("\n");
+
+ if lines.is_empty() {
+ Ok(None)
+ } else {
+ Ok(Some(lines))
+ }
+}
+
+fn cmd_name(p: &mut Parser) -> Result<String> {
+ let name = p.expect_name()?;
+ if name.starts_with('-') {
+ bail!("command name can't begin with `-`: `{}`", name);
+ }
+ Ok(name)
+}
+
+fn flag_name(p: &mut Parser) -> Result<String> {
+ let name = p.expect_name()?;
+ if !name.starts_with('-') {
+ bail!("flag name should begin with `-`: `{}`", name);
+ }
+ Ok(name)
+}
+
+struct Parser {
+ stack: Vec<Vec<TokenTree>>,
+ ts: Vec<TokenTree>,
+}
+
+impl Parser {
+ fn new(ts: TokenStream) -> Self {
+ let mut ts = ts.into_iter().collect::<Vec<_>>();
+ ts.reverse();
+ Self { stack: Vec::new(), ts }
+ }
+
+ fn at_delim(&mut self, delimiter: Delimiter) -> bool {
+ match self.ts.last() {
+ Some(TokenTree::Group(g)) => g.delimiter() == delimiter,
+ _ => false,
+ }
+ }
+ fn enter_delim(&mut self, delimiter: Delimiter) -> Result<()> {
+ match self.ts.pop() {
+ Some(TokenTree::Group(g)) if g.delimiter() == delimiter => {
+ let mut ts = g.stream().into_iter().collect::<Vec<_>>();
+ ts.reverse();
+ let ts = mem::replace(&mut self.ts, ts);
+ self.stack.push(ts);
+ }
+ _ => bail!("expected `{{`"),
+ }
+ Ok(())
+ }
+ fn exit_delim(&mut self) -> Result<()> {
+ if !self.end() {
+ bail!("expected `}}`")
+ }
+ self.ts = self.stack.pop().unwrap();
+ Ok(())
+ }
+ fn end(&mut self) -> bool {
+ self.ts.last().is_none()
+ }
+
+ fn expect_keyword(&mut self, kw: &str) -> Result<()> {
+ if !self.eat_keyword(kw) {
+ bail!("expected `{}`", kw)
+ }
+ Ok(())
+ }
+ fn eat_keyword(&mut self, kw: &str) -> bool {
+ if self.at_keyword(kw) {
+ self.ts.pop().unwrap();
+ true
+ } else {
+ false
+ }
+ }
+ fn at_keyword(&mut self, kw: &str) -> bool {
+ match self.ts.last() {
+ Some(TokenTree::Ident(ident)) => &ident.to_string() == kw,
+ _ => false,
+ }
+ }
+
+ fn expect_name(&mut self) -> Result<String> {
+ self.eat_name().ok_or_else(|| {
+ let next = self.ts.pop().map(|it| it.to_string()).unwrap_or_default();
+ format_err!("expected a name, got: `{}`", next)
+ })
+ }
+ fn eat_name(&mut self) -> Option<String> {
+ let mut buf = String::new();
+ let mut prev_ident = false;
+ loop {
+ match self.ts.last() {
+ Some(TokenTree::Punct(p)) if p.as_char() == '-' => {
+ prev_ident = false;
+ buf.push('-');
+ }
+ Some(TokenTree::Ident(ident)) if !prev_ident => {
+ prev_ident = true;
+ buf.push_str(&ident.to_string());
+ }
+ _ => break,
+ }
+ self.ts.pop();
+ }
+ if buf.is_empty() {
+ None
+ } else {
+ Some(buf)
+ }
+ }
+
+ fn _expect_ident(&mut self) -> Result<String> {
+ match self.ts.pop() {
+ Some(TokenTree::Ident(ident)) => Ok(ident.to_string()),
+ _ => bail!("expected ident"),
+ }
+ }
+
+ fn expect_punct(&mut self, punct: char) -> Result<()> {
+ if !self.eat_punct(punct) {
+ bail!("expected `{}`", punct)
+ }
+ Ok(())
+ }
+ fn eat_punct(&mut self, punct: char) -> bool {
+ match self.ts.last() {
+ Some(TokenTree::Punct(p)) if p.as_char() == punct => {
+ self.ts.pop();
+ true
+ }
+ _ => false,
+ }
+ }
+ fn lookahead_punct(&mut self, punct: char, n: usize) -> bool {
+ match self.ts.iter().rev().nth(n) {
+ Some(TokenTree::Punct(p)) => p.as_char() == punct,
+ _ => false,
+ }
+ }
+
+ fn expect_string(&mut self) -> Result<String> {
+ match self.ts.pop() {
+ Some(TokenTree::Literal(lit)) if lit.to_string().starts_with('"') => {
+ let text = lit.to_string();
+ let res = text.trim_matches('"').to_string();
+ Ok(res)
+ }
+ _ => bail!("expected a string"),
+ }
+ }
+}