use proc_macro2::{Span, TokenStream, TokenTree};
use quote::{ToTokens, TokenStreamExt};
use syn::{
Attribute, Expr, Ident, Pat, Token,
parse::{Parse, ParseStream, Result},
punctuated::Punctuated,
token,
};
#[derive(Debug, Clone)]
pub struct SpecArgs {
pub args: Punctuated<SpecArg, Token![,]>,
}
impl Parse for SpecArgs {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
args: Punctuated::<SpecArg, Token![,]>::parse_terminated(input)?,
})
}
}
impl SpecArgs {
pub fn is_sorted(&self) -> bool {
self.args
.iter()
.filter(|arg| !matches!(arg.keyword, Keyword::Unknown(_)))
.is_sorted_by_key(|arg| &arg.keyword)
}
}
#[derive(Debug, Clone)]
pub struct SpecArg {
pub attrs: Vec<Attribute>,
pub keyword: Keyword,
pub keyword_span: Span,
pub colon: Option<Token![:]>,
pub value: SpecArgValue,
}
impl Parse for SpecArg {
fn parse(input: ParseStream) -> Result<Self> {
let attrs = input.call(Attribute::parse_outer)?;
let (keyword, keyword_span) = Keyword::parse(input)?;
let (colon, value) = if input.peek(Token![:]) {
(
input.parse()?,
match keyword {
Keyword::Binds => SpecArgValue::parse_pat_or_expr(input)?,
Keyword::Captures => SpecArgValue::Captures(input.parse()?),
_ => SpecArgValue::parse_expr_or_pat(input)?,
},
)
} else {
(None, SpecArgValue::None)
};
Ok(Self {
attrs,
keyword,
keyword_span,
colon,
value,
})
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum SpecArgValue {
None,
Expr(Expr),
Pat(Pat),
Captures(Captures),
}
impl SpecArgValue {
pub fn try_into_expr(self) -> Result<Expr> {
if let Self::Expr(expr) = self {
return Ok(expr);
};
Err(syn::Error::new_spanned(self, "expected an expression"))
}
pub fn try_into_pat(self) -> Result<Pat> {
if let Self::Pat(pat) = self {
return Ok(pat);
};
Err(syn::Error::new_spanned(self, "expected a pattern"))
}
pub fn try_into_captures(self) -> Result<Captures> {
if let Self::Captures(captures) = self {
return Ok(captures);
};
Err(syn::Error::new_spanned(
self,
"expected captures: expression `as` pattern",
))
}
fn parse_expr_or_pat(input: ParseStream) -> Result<Self> {
if let Ok(expr) = Self::parse_expr_or_nothing(input) {
Ok(Self::Expr(expr))
} else if let Ok(pat) = Self::parse_pat_or_nothing(input) {
Ok(Self::Pat(pat))
} else {
Err(input.error("expected an expression or a pattern"))
}
}
fn parse_pat_or_expr(input: ParseStream) -> Result<Self> {
if let Ok(pat) = Self::parse_pat_or_nothing(input) {
Ok(Self::Pat(pat))
} else if let Ok(expr) = Self::parse_expr_or_nothing(input) {
Ok(Self::Expr(expr))
} else {
Err(input.error("expected a pattern or an expression"))
}
}
fn parse_expr_or_nothing(input: ParseStream<'_>) -> Result<Expr> {
use syn::parse::discouraged::Speculative;
let fork = input.fork();
match Expr::parse(&fork) {
Ok(expr) => {
input.advance_to(&fork);
Ok(expr)
}
Err(err) => Err(err),
}
}
fn parse_pat_or_nothing(input: ParseStream<'_>) -> Result<Pat> {
use syn::parse::discouraged::Speculative;
let fork = input.fork();
match Pat::parse_single(&fork) {
Ok(pat) => {
input.advance_to(&fork);
Ok(pat)
}
Err(err) => Err(err),
}
}
}
impl ToTokens for SpecArgValue {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
SpecArgValue::None => {}
SpecArgValue::Expr(expr) => expr.to_tokens(tokens),
SpecArgValue::Pat(pat) => pat.to_tokens(tokens),
SpecArgValue::Captures(captures) => captures.to_tokens(tokens),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Captures {
One(Box<CaptureExpr>),
Many {
bracket: token::Bracket,
elems: Punctuated<CaptureExpr, Token![,]>,
},
}
impl Parse for Captures {
fn parse(input: ParseStream) -> Result<Self> {
if input.peek(token::Bracket) && !input.peek2(Token![as]) {
let content;
let bracket = syn::bracketed!(content in input);
let elems = Punctuated::parse_terminated(&content)?;
Ok(Captures::Many { bracket, elems })
} else {
Ok(Captures::One(input.parse()?))
}
}
}
impl ToTokens for Captures {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::One(capture_expr) => capture_expr.to_tokens(tokens),
Self::Many { bracket, elems } => bracket.surround(tokens, |tokens| {
elems.to_tokens(tokens);
}),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct CaptureExpr {
pub expr: Option<Expr>,
pub as_: Option<Token![as]>,
pub pat: Option<Pat>,
}
impl ToTokens for CaptureExpr {
fn to_tokens(&self, tokens: &mut TokenStream) {
if let Some(expr) = &self.expr {
expr.to_tokens(tokens);
}
if let Some(as_) = &self.as_ {
as_.to_tokens(tokens);
}
if let Some(pat) = &self.pat {
pat.to_tokens(tokens);
}
}
}
impl Parse for CaptureExpr {
fn parse(input: ParseStream) -> Result<Self> {
let tokens_before_as = take_until_comma_or_last_as(input)?;
let expr = syn::parse::Parser::parse2(Expr::parse, tokens_before_as).ok();
let as_ = if input.peek(Token![as]) {
Some(input.parse()?)
} else {
None
};
let pat = SpecArgValue::parse_pat_or_nothing(input).ok();
Ok(Self { expr, as_, pat })
}
}
pub mod kw {
syn::custom_keyword!(functional);
syn::custom_keyword!(pure);
syn::custom_keyword!(total);
syn::custom_keyword!(deterministic);
syn::custom_keyword!(effectfree);
syn::custom_keyword!(infallible);
syn::custom_keyword!(terminating);
syn::custom_keyword!(requires);
syn::custom_keyword!(maintains);
syn::custom_keyword!(captures);
syn::custom_keyword!(binds);
syn::custom_keyword!(ensures);
syn::custom_keyword!(decreases);
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum Keyword {
Unknown(Ident),
Functional,
Pure,
Total,
Deterministic,
Effectfree,
Infallible,
Terminating,
Requires,
Maintains,
Captures,
Binds,
Ensures,
Decreases,
}
impl Keyword {
fn parse(input: ParseStream) -> Result<(Self, Span)> {
use Keyword::*;
Ok(if input.peek(kw::functional) {
let keyword: kw::functional = input.parse()?;
(Functional, keyword.span)
} else if input.peek(kw::pure) {
let keyword: kw::pure = input.parse()?;
(Pure, keyword.span)
} else if input.peek(kw::total) {
let keyword: kw::total = input.parse()?;
(Total, keyword.span)
} else if input.peek(kw::deterministic) {
let keyword: kw::deterministic = input.parse()?;
(Deterministic, keyword.span)
} else if input.peek(kw::effectfree) {
let keyword: kw::effectfree = input.parse()?;
(Effectfree, keyword.span)
} else if input.peek(kw::infallible) {
let keyword: kw::infallible = input.parse()?;
(Infallible, keyword.span)
} else if input.peek(kw::terminating) {
let keyword: kw::terminating = input.parse()?;
(Terminating, keyword.span)
} else if input.peek(kw::requires) {
let keyword: kw::requires = input.parse()?;
(Requires, keyword.span)
} else if input.peek(kw::maintains) {
let token: kw::maintains = input.parse()?;
(Maintains, token.span)
} else if input.peek(kw::captures) {
let token: kw::captures = input.parse()?;
(Captures, token.span)
} else if input.peek(kw::binds) {
let token: kw::binds = input.parse()?;
(Binds, token.span)
} else if input.peek(kw::ensures) {
let token: kw::ensures = input.parse()?;
(Ensures, token.span)
} else if input.peek(kw::decreases) {
let token: kw::decreases = input.parse()?;
(Decreases, token.span)
} else {
let ident: Ident = input.parse()?;
let span = ident.span();
(Unknown(ident), span)
})
}
}
impl std::fmt::Display for Keyword {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Keyword::Unknown(ident) => write!(f, "{}", ident),
Keyword::Functional => write!(f, "functional"),
Keyword::Pure => write!(f, "pure"),
Keyword::Total => write!(f, "total"),
Keyword::Deterministic => write!(f, "deterministic"),
Keyword::Effectfree => write!(f, "effectfree"),
Keyword::Infallible => write!(f, "infallible"),
Keyword::Terminating => write!(f, "terminating"),
Keyword::Requires => write!(f, "requires"),
Keyword::Maintains => write!(f, "maintains"),
Keyword::Captures => write!(f, "captures"),
Keyword::Binds => write!(f, "binds"),
Keyword::Ensures => write!(f, "ensures"),
Keyword::Decreases => write!(f, "decreases"),
}
}
}
fn take_until_comma_or_last_as(input: ParseStream) -> Result<TokenStream> {
use syn::parse::discouraged::Speculative;
let fork = input.fork();
let mut peeked_tokens = TokenStream::new();
let mut consumed_tokens = TokenStream::new();
let mut has_seen_as = false;
while !fork.is_empty() && !fork.peek(Token![,]) {
if fork.peek(Token![as]) {
has_seen_as = true;
consumed_tokens.extend(peeked_tokens);
peeked_tokens = TokenStream::new();
input.advance_to(&fork);
}
let token: TokenTree = fork.parse()?;
peeked_tokens.append(token);
}
if has_seen_as {
Ok(consumed_tokens)
} else {
input.advance_to(&fork);
Ok(peeked_tokens)
}
}