Skip to main content

workflow_macro_tools/
attributes.rs

1use proc_macro2::{Group, Ident, Span};
2use std::convert::Into;
3// use proc_macro2::{TokenStream as TokenStream2};
4use crate::parse_error;
5use proc_macro2::TokenStream;
6use quote::ToTokens;
7use std::collections::HashMap;
8use syn::{Attribute, Error, Lit, LitBool};
9use syn::{
10    Expr, Token,
11    parse::{Parse, ParseStream},
12};
13
14/// A newtype wrapper around an identifier that implements [`Parse`] to consume
15/// an identifier from the input stream regardless of keyword status.
16#[derive(Debug, Clone)]
17pub struct IdentWraper(proc_macro2::Ident);
18
19impl Parse for IdentWraper {
20    fn parse(input: ParseStream) -> syn::Result<Self> {
21        input.step(|cursor| {
22            if let Some((ident, rest)) = cursor.ident() {
23                let wraper = IdentWraper(proc_macro2::Ident::new(&ident.to_string(), ident.span()));
24                return Ok((wraper, rest));
25            }
26            Err(cursor.error("expected identifier"))
27        })
28    }
29}
30
31/// A single lexical item encountered while parsing attribute arguments.
32#[derive(Debug, Clone, parse_variants::Parse)]
33pub enum Item {
34    /// A plain identifier, typically an argument name.
35    Identifier(syn::Ident),
36    /// An identifier captured through [`IdentWraper`].
37    IdentifierWraper(IdentWraper),
38    /// A bare integer literal.
39    Literal(syn::LitInt),
40    /// A bare string literal, treated as the `default` argument.
41    String(syn::LitStr),
42}
43
44/// A value appearing in evaluation position, e.g. inside a delimited group.
45#[derive(Debug, Clone, parse_variants::Parse)]
46pub enum EvaluationValue {
47    /// A delimited token group value.
48    Group(proc_macro2::Group),
49    /// An integer literal value.
50    Integer(syn::LitInt),
51    /// A string literal value.
52    String(syn::LitStr),
53}
54
55/// A value appearing on the right-hand side of an `=` assignment.
56#[derive(Debug, Clone, parse_variants::Parse)]
57pub enum AssignmentValue {
58    /// A string literal value.
59    String(syn::LitStr),
60    /// An integer literal value.
61    Integer(syn::LitInt),
62    /// A boolean literal value, also used for bare flag arguments.
63    Boolean(syn::LitBool),
64    /// A delimited token group value.
65    Group(proc_macro2::Group),
66}
67
68/// The value associated with an argument, distinguishing values parsed in
69/// evaluation position from those parsed on the right-hand side of an `=`.
70#[derive(Debug, Clone)]
71pub enum Value {
72    /// A value supplied in evaluation position, e.g. `name(group)`.
73    EvaluationValue(EvaluationValue),
74    /// A value supplied via assignment, e.g. `name = value`.
75    AssignmentValue(AssignmentValue),
76}
77
78impl Value {
79    /// Renders the underlying literal or group back into its token stream.
80    pub fn to_token_stream(&self) -> TokenStream {
81        match self {
82            Value::EvaluationValue(ev) => match ev {
83                EvaluationValue::Integer(lit_int) => lit_int.to_token_stream(),
84                EvaluationValue::String(lit_str) => lit_str.to_token_stream(),
85                EvaluationValue::Group(group) => group.stream(),
86            },
87            Value::AssignmentValue(av) => match av {
88                AssignmentValue::String(lit_str) => lit_str.to_token_stream(),
89                AssignmentValue::Integer(lit_int) => lit_int.to_token_stream(),
90                AssignmentValue::Boolean(lit_bool) => lit_bool.to_token_stream(),
91                AssignmentValue::Group(group) => group.stream(),
92            },
93        }
94    }
95}
96
97/// A parsed set of macro attribute arguments, keyed by argument name.
98#[derive(Debug)]
99pub struct Args {
100    /// Maps each argument identifier to its optional value (`None` when the
101    /// argument was supplied as a bare flag without a value).
102    pub map: HashMap<Ident, Option<Value>>,
103}
104
105impl Default for Args {
106    fn default() -> Self {
107        Args::new()
108    }
109}
110
111impl Args {
112    /// Creates an empty set of arguments.
113    pub fn new() -> Args {
114        Args {
115            map: HashMap::new(),
116        }
117    }
118
119    /// Returns `true` if an argument with the given name is present.
120    pub fn has(&self, ident: &str) -> bool {
121        let ident = Ident::new(ident, Span::call_site());
122        self.map.contains_key(&ident)
123    }
124
125    /// Looks up an argument by name, returning its optional value, or `None`
126    /// if the argument is not present.
127    pub fn get(&self, ident: &str) -> Option<&Option<Value>> {
128        let ident = Ident::new(ident, Span::call_site());
129        self.map.get(&ident)
130    }
131
132    /// Returns the value of `ident`: `Ok(None)` if the argument is absent,
133    /// `Ok(Some(_))` if it has a value, or an error spanned over `field` with
134    /// `msg` if the argument is present but carries no value.
135    pub fn get_value_or<T: ToTokens>(
136        &self,
137        ident: &str,
138        field: T,
139        msg: &str,
140    ) -> Result<Option<Value>, TokenStream> {
141        let v = self.get(ident);
142        match v {
143            None => Ok(None),
144            Some(v) => match v {
145                Some(v) => Ok(Some(v.clone())),
146                None => Err(parse_error(field, msg)),
147            },
148        }
149    }
150
151    /// Returns the arguments as a list of name/value string pairs, with
152    /// string literals unquoted and valueless arguments mapped to an empty string.
153    pub fn to_string_kv(&self) -> Vec<(String, String)> {
154        let mut list: Vec<(String, String)> = Vec::new();
155        for (k, v) in self.map.iter() {
156            let value = match v {
157                Some(value) => {
158                    let v = value.to_token_stream();
159                    let expr: Expr = syn::parse(v.into()).unwrap();
160                    match &expr {
161                        Expr::Lit(expr_lit) => match &expr_lit.lit {
162                            Lit::Str(lit_str) => lit_str.value(),
163                            _ => expr.to_token_stream().to_string(),
164                        },
165                        _ => expr.to_token_stream().to_string(),
166                    }
167                }
168                None => "".to_string(),
169            };
170            list.push((k.to_string(), value));
171        }
172
173        list
174    }
175
176    /// Validates that every argument name present is contained in `list`,
177    /// returning an error spanned over the first unsupported argument.
178    pub fn allow(&self, list: &[&str]) -> syn::Result<()> {
179        for (ident, _) in self.map.iter() {
180            let name = ident.to_string();
181            if !list.contains(&name.as_str()) {
182                return Err(Error::new_spanned(
183                    ident,
184                    format!(
185                        "unsupported attribute: {}, supported attributes are {}",
186                        name,
187                        list.join(", ")
188                    ),
189                ));
190            }
191        }
192
193        Ok(())
194    }
195}
196
197fn advance_one_step(input: &ParseStream<'_>) {
198    let _ = input.step(|cursor| {
199        let rest = *cursor;
200        match rest.token_tree() {
201            Some((_tt, next)) => Ok(((), next)),
202            _ => Ok(((), rest)),
203        }
204    });
205}
206
207impl Parse for Args {
208    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
209        let mut map: HashMap<Ident, Option<Value>> = HashMap::new();
210        while !input.is_empty() {
211            let token_result = input.parse::<Item>();
212            if token_result.is_err() {
213                advance_one_step(&input);
214                if input.peek(Token![=]) {
215                    let _: Token![=] = input.parse()?;
216                }
217            } else {
218                let token = token_result.ok().unwrap();
219                match token {
220                    Item::Identifier(ident) | Item::IdentifierWraper(IdentWraper(ident)) => {
221                        if input.peek(Token![,]) {
222                            let _: Token![,] = input.parse()?;
223                            map.insert(
224                                ident,
225                                Some(Value::AssignmentValue(AssignmentValue::Boolean(
226                                    LitBool::new(true, Span::call_site()),
227                                ))),
228                            );
229                        } else if input.peek(Token![=]) {
230                            let _: Token![=] = input.parse()?;
231                            let rvalue: AssignmentValue = input.parse()?;
232                            map.insert(ident, Some(Value::AssignmentValue(rvalue)));
233                        } else {
234                            let group: Group = input.parse()?;
235                            map.insert(
236                                ident,
237                                Some(Value::EvaluationValue(EvaluationValue::Group(group))),
238                            );
239                        }
240
241                        if input.peek(Token![,]) {
242                            let _: Token![,] = input.parse()?;
243                        }
244                    }
245                    // Item::Literal(lit) => {
246                    //     let title = Ident::new("title", Span::call_site());
247                    //     if map.get(&title).is_none() {
248                    //         map.insert(title, Some(Value::EvaluationValue(EvaluationValue::Integer(lit))));
249                    //     }
250                    // },
251                    Item::String(lit_str) => {
252                        let default = Ident::new("default", Span::call_site());
253                        map.entry(default).or_insert(Some(Value::EvaluationValue(
254                            EvaluationValue::String(lit_str),
255                        )));
256                    }
257                    _ => {
258                        println!("invalid attributes");
259                        // TODO check error handling
260                        let ident = Ident::new("", Span::call_site());
261                        return Err(Error::new_spanned(ident, "invalid attributes".to_string()));
262                    }
263                }
264            }
265        }
266
267        Ok(Self { map })
268    }
269}
270
271/// Parses the contents of the given attribute into [`Args`],
272/// returning `None` if the attribute arguments fail to parse.
273pub fn get_attributes(attr: &Attribute) -> Option<Args> {
274    let attributes: Option<Args> = attr.parse_args().ok();
275    attributes
276}