use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{
parse::{discouraged::Speculative, Parse, ParseStream},
parse_quote,
punctuated::Punctuated,
spanned::Spanned,
token::{Brace, Comma, Paren},
Attribute, Expr, Lit, Pat, PatType, Token,
};
use super::{parse::parse_valid_block_expr, InvalidBlock};
use crate::{
node::{NodeBlock, NodeName},
parser::recoverable::{ParseRecoverable, RecoverableContext},
};
#[derive(Clone, Debug, syn_derive::ToTokens)]
pub struct AttributeValueExpr {
pub token_eq: Token![=],
pub value: KVAttributeValue,
}
#[derive(Clone, Debug, syn_derive::ToTokens)]
pub enum KVAttributeValue {
Expr(Expr),
InvalidBraced(InvalidBlock),
}
impl AttributeValueExpr {
pub fn value_literal_string(&self) -> Option<String> {
match &self.value {
KVAttributeValue::Expr(Expr::Lit(l)) => match &l.lit {
Lit::Str(s) => Some(s.value()),
Lit::Char(c) => Some(c.value().to_string()),
Lit::Int(i) => Some(i.base10_digits().to_string()),
Lit::Float(f) => Some(f.base10_digits().to_string()),
Lit::Bool(b) => Some(b.value.to_string()),
_ => None,
},
_ => None,
}
}
}
#[derive(Clone, Debug, syn_derive::ToTokens)]
pub enum KeyedAttributeValue {
Binding(FnBinding),
Value(AttributeValueExpr),
None,
}
impl KeyedAttributeValue {
pub fn to_value(&self) -> Option<&AttributeValueExpr> {
match self {
KeyedAttributeValue::Value(v) => Some(v),
KeyedAttributeValue::None => None,
KeyedAttributeValue::Binding(_) => None,
}
}
}
#[derive(Clone, Debug, syn_derive::ToTokens)]
pub struct KeyedAttribute {
pub key: NodeName,
pub possible_value: KeyedAttributeValue,
}
impl KeyedAttribute {
pub fn value_literal_string(&self) -> Option<String> {
self.possible_value
.to_value()
.and_then(|v| v.value_literal_string())
}
pub fn value(&self) -> Option<&Expr> {
self.possible_value
.to_value()
.map(|v| match &v.value {
KVAttributeValue::Expr(expr) => Some(expr),
KVAttributeValue::InvalidBraced(_) => None,
})
.flatten()
}
pub(crate) fn correct_expr_error_span(error: syn::Error, input: ParseStream) -> syn::Error {
let error_str = error.to_string();
if error_str.starts_with("unexpected end of input") {
let stream = input
.parse::<TokenStream>()
.expect("BUG: Token stream should always be parsable");
return syn::Error::new(
stream.span(),
format!("failed to parse expression: {}", error),
);
}
error
}
}
#[derive(Clone, Debug)]
pub struct FnBinding {
pub paren: Paren,
pub inputs: Punctuated<Pat, Comma>,
}
fn closure_arg(input: ParseStream) -> syn::Result<Pat> {
let attrs = input.call(Attribute::parse_outer)?;
let mut pat: Pat = Pat::parse_single(input)?;
if input.peek(Token![:]) {
Ok(Pat::Type(PatType {
attrs,
pat: Box::new(pat),
colon_token: input.parse()?,
ty: input.parse()?,
}))
} else {
match &mut pat {
Pat::Ident(pat) => pat.attrs = attrs,
Pat::Lit(pat) => pat.attrs = attrs,
Pat::Macro(pat) => pat.attrs = attrs,
Pat::Or(pat) => pat.attrs = attrs,
Pat::Path(pat) => pat.attrs = attrs,
Pat::Range(pat) => pat.attrs = attrs,
Pat::Reference(pat) => pat.attrs = attrs,
Pat::Rest(pat) => pat.attrs = attrs,
Pat::Slice(pat) => pat.attrs = attrs,
Pat::Struct(pat) => pat.attrs = attrs,
Pat::Tuple(pat) => pat.attrs = attrs,
Pat::TupleStruct(pat) => pat.attrs = attrs,
Pat::Type(_) => unreachable!("BUG: Type handled in if"),
Pat::Verbatim(_) => {}
Pat::Wild(pat) => pat.attrs = attrs,
_ => unreachable!(),
}
Ok(pat)
}
}
#[derive(Clone, Debug, syn_derive::ToTokens)]
pub enum NodeAttribute {
Block(NodeBlock),
Attribute(KeyedAttribute),
}
impl ParseRecoverable for KeyedAttribute {
fn parse_recoverable(parser: &mut RecoverableContext, input: ParseStream) -> Option<Self> {
let key = NodeName::parse(input)
.map_err(|e| parser.push_diagnostic(e))
.ok()?;
let possible_value = if input.peek(Paren) {
KeyedAttributeValue::Binding(
FnBinding::parse(input)
.map_err(|e| parser.push_diagnostic(e))
.ok()?,
)
} else if input.peek(Token![=]) {
let eq = input
.parse::<Token![=]>()
.map_err(|e| parser.push_diagnostic(e))
.ok()?;
if input.is_empty() {
parser.push_diagnostic(syn::Error::new(eq.span(), "missing attribute value"));
return None;
}
let fork = input.fork();
let rs = match parse_valid_block_expr(parser, &fork) {
Ok(vbl) => {
input.advance_to(&fork);
KVAttributeValue::Expr(parse_quote!(#vbl))
}
Err(err) if input.fork().peek(Brace) && parser.config().recover_block => {
parser.push_diagnostic(err);
let ivb = parser.parse_simple(input)?;
KVAttributeValue::InvalidBraced(ivb)
}
Err(_) => {
let res = fork
.parse::<Expr>()
.map_err(|e| {
if fork.is_empty() {
KeyedAttribute::correct_expr_error_span(e, input)
} else {
e
}
})
.map_err(|e| parser.push_diagnostic(e))
.ok()?;
input.advance_to(&fork);
KVAttributeValue::Expr(res)
}
};
KeyedAttributeValue::Value(AttributeValueExpr {
token_eq: eq,
value: rs,
})
} else {
KeyedAttributeValue::None
};
Some(KeyedAttribute {
key,
possible_value,
})
}
}
impl ParseRecoverable for NodeAttribute {
fn parse_recoverable(parser: &mut RecoverableContext, input: ParseStream) -> Option<Self> {
let node = if input.peek(Brace) {
NodeAttribute::Block(parser.parse_recoverable(input)?)
} else {
NodeAttribute::Attribute(parser.parse_recoverable(input)?)
};
Some(node)
}
}
impl Parse for FnBinding {
fn parse(stream: ParseStream) -> syn::Result<Self> {
let content;
let paren = syn::parenthesized!(content in stream);
let inputs = Punctuated::parse_terminated_with(&content, closure_arg)?;
Ok(Self { paren, inputs })
}
}
impl ToTokens for FnBinding {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.paren.surround(tokens, |tokens| {
self.inputs.to_tokens(tokens);
})
}
}