Skip to main content

anodized_core/annotate/
syntax.rs

1use proc_macro2::{Span, TokenStream, TokenTree};
2use quote::{ToTokens, TokenStreamExt};
3use syn::{
4    Attribute, Expr, Ident, Pat, Token,
5    parse::{Parse, ParseStream, Result},
6    punctuated::Punctuated,
7    token,
8};
9
10/// Raw spec arguments, i.e. as they appear in the `#[spec(...)]` proc macro invocation.
11///
12/// Can represent a well-formed but invalid spec so that e.g. `anodized-fmt` may work with it.
13pub struct SpecArgs {
14    pub args: Punctuated<SpecArg, Token![,]>,
15}
16
17impl Parse for SpecArgs {
18    fn parse(input: ParseStream) -> Result<Self> {
19        Ok(Self {
20            args: Punctuated::<SpecArg, Token![,]>::parse_terminated(input)?,
21        })
22    }
23}
24
25/// A single spec argument.
26pub struct SpecArg {
27    pub attrs: Vec<Attribute>,
28    pub keyword: Keyword,
29    pub keyword_span: Span,
30    pub colon: Token![:],
31    pub value: SpecArgValue,
32}
33
34impl Parse for SpecArg {
35    fn parse(input: ParseStream) -> Result<Self> {
36        let attrs = input.call(Attribute::parse_outer)?;
37        let (keyword, keyword_span) = Keyword::parse(input)?;
38        let colon = input.parse()?;
39        let value = match keyword {
40            Keyword::Binds => SpecArgValue::parse_pat_or_expr(input)?,
41            Keyword::Captures => SpecArgValue::Captures(input.parse()?),
42            _ => SpecArgValue::parse_expr_or_pat(input)?,
43        };
44
45        Ok(Self {
46            attrs,
47            keyword,
48            keyword_span,
49            colon,
50            value,
51        })
52    }
53}
54/// Each [`SpecArg`]'s value needs to be parsed in a way that allows invalid specs, e.g.
55/// forms which do not correspond directly to an [`syn::Expr`] in standard Rust.
56///
57/// NOTE:
58/// a [`SpecArgValue`] may hold unrelated syntactic elements such as ['syn::Expr`], [`syn::Pat`],
59/// and even fragments that would never appear as part of a valid Rust program.
60#[derive(Debug, Clone)]
61pub enum SpecArgValue {
62    Expr(Expr),
63    Pat(Pat),
64    Captures(Captures),
65}
66
67impl SpecArgValue {
68    /// Return the `Expr` or fail.
69    pub fn try_into_expr(self) -> Result<Expr> {
70        if let Self::Expr(expr) = self {
71            return Ok(expr);
72        };
73        Err(syn::Error::new_spanned(self, "expected an expression"))
74    }
75
76    /// Return the `Pat` or fail.
77    pub fn try_into_pat(self) -> Result<Pat> {
78        if let Self::Pat(pat) = self {
79            return Ok(pat);
80        };
81        Err(syn::Error::new_spanned(self, "expected a pattern"))
82    }
83
84    /// Return the `Captures` or fail.
85    pub fn try_into_captures(self) -> Result<Captures> {
86        if let Self::Captures(captures) = self {
87            return Ok(captures);
88        };
89        Err(syn::Error::new_spanned(
90            self,
91            "expected captures: expression `as` pattern",
92        ))
93    }
94
95    /// Try to parse as `Expr` then as `Pat`.
96    fn parse_expr_or_pat(input: ParseStream) -> Result<Self> {
97        if let Ok(expr) = Self::parse_expr_or_nothing(input) {
98            Ok(Self::Expr(expr))
99        } else if let Ok(pat) = Self::parse_pat_or_nothing(input) {
100            Ok(Self::Pat(pat))
101        } else {
102            Err(input.error("expected an expression or a pattern"))
103        }
104    }
105
106    /// Try to parse as `Pat` then as `Expr`.
107    fn parse_pat_or_expr(input: ParseStream) -> Result<Self> {
108        if let Ok(pat) = Self::parse_pat_or_nothing(input) {
109            Ok(Self::Pat(pat))
110        } else if let Ok(expr) = Self::parse_expr_or_nothing(input) {
111            Ok(Self::Expr(expr))
112        } else {
113            Err(input.error("expected a pattern or an expression"))
114        }
115    }
116
117    /// Try to parse as `Expr` but consume no input on failure.
118    fn parse_expr_or_nothing(input: ParseStream<'_>) -> Result<Expr> {
119        use syn::parse::discouraged::Speculative;
120        let fork = input.fork();
121        match Expr::parse(&fork) {
122            Ok(expr) => {
123                input.advance_to(&fork);
124                Ok(expr)
125            }
126            Err(err) => Err(err),
127        }
128    }
129
130    /// Try to parse as `Pat` but consume no input on failure.
131    fn parse_pat_or_nothing(input: ParseStream<'_>) -> Result<Pat> {
132        use syn::parse::discouraged::Speculative;
133        let fork = input.fork();
134        match Pat::parse_single(&fork) {
135            Ok(pat) => {
136                input.advance_to(&fork);
137                Ok(pat)
138            }
139            Err(err) => Err(err),
140        }
141    }
142}
143
144impl ToTokens for SpecArgValue {
145    fn to_tokens(&self, tokens: &mut TokenStream) {
146        match self {
147            SpecArgValue::Expr(expr) => expr.to_tokens(tokens),
148            SpecArgValue::Pat(pat) => pat.to_tokens(tokens),
149            SpecArgValue::Captures(captures) => captures.to_tokens(tokens),
150        }
151    }
152}
153
154/// A group of capture expressions, either a single one or a list.
155/// These are not composed of top level [`syn::Expr`] expressions.
156#[derive(Debug, Clone)]
157pub enum Captures {
158    One(Box<CaptureExpr>),
159    Many {
160        bracket: token::Bracket,
161        elems: Punctuated<CaptureExpr, Token![,]>,
162    },
163}
164
165impl Parse for Captures {
166    fn parse(input: ParseStream) -> Result<Self> {
167        // For bracketed input, we need to distinguish between:
168        // 1. `[a, b, c]` - an array of capture expressions
169        // 2. `[a, b, c] as slice` - a single capture with an array expr
170        //
171        // Multiple captures are in brackets, not followed by `as`
172        if input.peek(token::Bracket) && !input.peek2(Token![as]) {
173            // Parse as an array of captures
174            let content;
175            let bracket = syn::bracketed!(content in input);
176            let elems = Punctuated::parse_terminated(&content)?;
177            Ok(Captures::Many { bracket, elems })
178        } else {
179            // Otherwise parse as one capture
180            Ok(Captures::One(input.parse()?))
181        }
182    }
183}
184
185impl ToTokens for Captures {
186    fn to_tokens(&self, tokens: &mut TokenStream) {
187        match self {
188            Self::One(capture_expr) => capture_expr.to_tokens(tokens),
189            Self::Many { bracket, elems } => bracket.surround(tokens, |tokens| {
190                elems.to_tokens(tokens);
191            }),
192        }
193    }
194}
195
196/// The form in a `captures` clause: <expression> `as` <pattern>.
197#[derive(Debug, Clone)]
198pub struct CaptureExpr {
199    pub expr: Option<Expr>,
200    pub as_: Option<Token![as]>,
201    pub pat: Option<Pat>,
202}
203
204impl ToTokens for CaptureExpr {
205    fn to_tokens(&self, tokens: &mut TokenStream) {
206        if let Some(expr) = &self.expr {
207            expr.to_tokens(tokens);
208        }
209        if let Some(as_) = &self.as_ {
210            as_.to_tokens(tokens);
211        }
212        if let Some(pat) = &self.pat {
213            pat.to_tokens(tokens);
214        }
215    }
216}
217
218impl Parse for CaptureExpr {
219    fn parse(input: ParseStream) -> Result<Self> {
220        let tokens_before_as = take_until_comma_or_last_as(input)?;
221        let expr = syn::parse::Parser::parse2(Expr::parse, tokens_before_as).ok();
222        // TODO: need to check that the entirety of `tokens_before_as` was consumed
223        let as_ = if input.peek(Token![as]) {
224            Some(input.parse()?)
225        } else {
226            None
227        };
228        let pat = SpecArgValue::parse_pat_or_nothing(input).ok();
229        Ok(Self { expr, as_, pat })
230    }
231}
232
233/// Custom keywords for parsing. This allows us to use `requires`, `ensures`, etc.,
234/// as if they were built-in Rust keywords during parsing.
235pub mod kw {
236    syn::custom_keyword!(requires);
237    syn::custom_keyword!(maintains);
238    syn::custom_keyword!(captures);
239    syn::custom_keyword!(binds);
240    syn::custom_keyword!(ensures);
241}
242
243#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug)]
244pub enum Keyword {
245    Unknown(Ident),
246    Requires,
247    Maintains,
248    Captures,
249    Binds,
250    Ensures,
251}
252
253impl Keyword {
254    fn parse(input: ParseStream) -> Result<(Self, Span)> {
255        use Keyword::*;
256        Ok(if input.peek(kw::requires) {
257            let keyword: kw::requires = input.parse()?;
258            (Requires, keyword.span)
259        } else if input.peek(kw::maintains) {
260            let token: kw::maintains = input.parse()?;
261            (Maintains, token.span)
262        } else if input.peek(kw::captures) {
263            let token: kw::captures = input.parse()?;
264            (Captures, token.span)
265        } else if input.peek(kw::binds) {
266            let token: kw::binds = input.parse()?;
267            (Binds, token.span)
268        } else if input.peek(kw::ensures) {
269            let token: kw::ensures = input.parse()?;
270            (Ensures, token.span)
271        } else {
272            let ident: Ident = input.parse()?;
273            let span = ident.span();
274            (Unknown(ident), span)
275        })
276    }
277}
278
279impl std::fmt::Display for Keyword {
280    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
281        match self {
282            Keyword::Requires => write!(f, "requires"),
283            Keyword::Maintains => write!(f, "maintains"),
284            Keyword::Captures => write!(f, "captures"),
285            Keyword::Binds => write!(f, "binds"),
286            Keyword::Ensures => write!(f, "ensures"),
287            Keyword::Unknown(ident) => write!(f, "{}", ident),
288        }
289    }
290}
291
292/// Find the last `as` token that is before a comma (or end of input).
293/// Consume and return tokens before the last `as`.
294/// If no `as` is encountered, consume and return all tokens before a comma.
295/// Groups (delimited by `()`, `[]`, `{}`) are considered atomically,
296/// so any `as` or comma inside them is ignored.
297fn take_until_comma_or_last_as(input: ParseStream) -> Result<TokenStream> {
298    use syn::parse::discouraged::Speculative;
299    let fork = input.fork();
300    let mut peeked_tokens = TokenStream::new();
301    let mut consumed_tokens = TokenStream::new();
302    let mut has_seen_as = false;
303    while !fork.is_empty() && !fork.peek(Token![,]) {
304        if fork.peek(Token![as]) {
305            has_seen_as = true;
306            // Consumed peeked tokens
307            consumed_tokens.extend(peeked_tokens);
308            peeked_tokens = TokenStream::new();
309            input.advance_to(&fork);
310        }
311        let token: TokenTree = fork.parse()?;
312        peeked_tokens.append(token);
313    }
314    if has_seen_as {
315        Ok(consumed_tokens)
316    } else {
317        input.advance_to(&fork);
318        Ok(peeked_tokens)
319    }
320}