215 lines
8.0 KiB
Rust
215 lines
8.0 KiB
Rust
use quote::ToTokens;
|
|
use syn::parse::{Parse, ParseStream};
|
|
use syn::punctuated::Punctuated;
|
|
use syn::spanned::Spanned;
|
|
use syn::{bracketed, parenthesized, token, Error, Expr, Ident, Lit, LitStr, Path, Result, Token};
|
|
|
|
use crate::widgets::{Attr, Attrs};
|
|
|
|
impl Parse for Attrs {
|
|
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
|
let mut attrs = Vec::new();
|
|
|
|
while input.peek(Token![#]) {
|
|
let _sharp: Token![#] = input.parse()?;
|
|
let attr_tokens;
|
|
bracketed!(attr_tokens in input);
|
|
let path: Path = attr_tokens.parse()?;
|
|
|
|
// Name attribute
|
|
attrs.push(if attr_tokens.is_empty() {
|
|
if let Some(ident) = path.get_ident() {
|
|
if ident == "local" {
|
|
Attr::Local(ident.clone())
|
|
} else if ident == "local_ref" {
|
|
Attr::LocalRef(ident.clone())
|
|
} else if ident == "root" {
|
|
Attr::Root(ident.clone())
|
|
} else if ident == "watch" {
|
|
Attr::Watch(ident.clone(), None)
|
|
} else if ident == "track" {
|
|
Attr::Track(ident.clone(), None, None)
|
|
} else if ident == "iterate" {
|
|
Attr::Iterate(ident.clone())
|
|
} else if ident == "template" {
|
|
Attr::Template(ident.clone())
|
|
} else if ident == "template_child" {
|
|
Attr::TemplateChild(ident.clone())
|
|
} else {
|
|
return Err(unexpected_attr_name(ident));
|
|
}
|
|
} else {
|
|
return Err(Error::new(path.span(), "Expected identifier."));
|
|
}
|
|
|
|
// List attribute: `#[name(item1, item2)]
|
|
} else if attr_tokens.peek(token::Paren) {
|
|
let paren_input;
|
|
parenthesized!(paren_input in attr_tokens);
|
|
let nested: Punctuated<Expr, token::Comma> =
|
|
Punctuated::parse_terminated(&paren_input)?;
|
|
|
|
if let Some(ident) = path.get_ident() {
|
|
if ident == "block_signal" {
|
|
let mut signal_idents = Vec::with_capacity(nested.len());
|
|
for expr in nested {
|
|
let ident = expect_ident_from_expr(&expr)?;
|
|
signal_idents.push(ident);
|
|
}
|
|
Attr::BlockSignal(ident.clone(), signal_idents)
|
|
} else if ident == "watch" {
|
|
let expr = expect_one_nested_expr(&nested)?;
|
|
if let Some(skip_init) = expr_to_skip_init_ident(expr) {
|
|
Attr::Watch(ident.clone(), Some(skip_init))
|
|
} else {
|
|
return Err(Error::new(nested.span(), "Expected `skip_init`."));
|
|
}
|
|
} else if ident == "track" {
|
|
let (skip_init, expr) = parse_track(&nested)?;
|
|
Attr::Track(ident.clone(), skip_init, expr.map(Box::new))
|
|
} else if ident == "transition" {
|
|
let expr = expect_one_nested_expr(&nested)?;
|
|
let ident = expect_ident_from_expr(expr)?;
|
|
Attr::Transition(ident.clone(), ident)
|
|
} else if ident == "name" {
|
|
let expr = expect_one_nested_expr(&nested)?;
|
|
let ident = expect_ident_from_expr(expr)?;
|
|
Attr::Name(ident.clone(), ident)
|
|
} else if ident == "wrap" {
|
|
let expr = expect_one_nested_expr(&nested)?;
|
|
let path = expect_path_from_expr(expr)?;
|
|
Attr::Wrap(ident.clone(), path)
|
|
} else if ident == "chain" {
|
|
let expr = expect_one_nested_expr(&nested)?;
|
|
Attr::Chain(ident.clone(), Box::new(expr.clone()))
|
|
} else {
|
|
return Err(unexpected_attr_name(ident));
|
|
}
|
|
} else {
|
|
return Err(Error::new(path.span(), "Expected identifier."));
|
|
}
|
|
|
|
// Value attribute: `#[name = literal)]
|
|
} else if attr_tokens.peek(Token![=]) {
|
|
let _eq: Token![=] = attr_tokens.parse()?;
|
|
let lit = attr_tokens.parse()?;
|
|
|
|
if let Some(ident) = path.get_ident() {
|
|
if ident == "track" {
|
|
let string = expect_string_lit(&lit)?;
|
|
Attr::Track(ident.clone(), None, Some(string.parse()?))
|
|
} else if ident == "doc" {
|
|
Attr::Doc(lit.into_token_stream())
|
|
} else if ident == "transition" {
|
|
let string = expect_string_lit(&lit)?;
|
|
Attr::Transition(ident.clone(), string.parse()?)
|
|
} else if ident == "name" {
|
|
let string = expect_string_lit(&lit)?;
|
|
Attr::Name(ident.clone(), string.parse()?)
|
|
} else {
|
|
return Err(unexpected_attr_name(ident));
|
|
}
|
|
} else {
|
|
return Err(Error::new(path.span(), "Expected identifier."));
|
|
}
|
|
} else {
|
|
return Err(Error::new(attr_tokens.span(), "Expected `]`, `(` or `=`."));
|
|
});
|
|
}
|
|
|
|
Ok(Attrs { inner: attrs })
|
|
}
|
|
}
|
|
|
|
fn unexpected_attr_name(ident: &Ident) -> Error {
|
|
Error::new(
|
|
ident.span(),
|
|
format!("Unexpected attribute name `{ident}`."),
|
|
)
|
|
}
|
|
|
|
fn expect_string_lit(lit: &Lit) -> Result<&LitStr> {
|
|
if let Lit::Str(string) = lit {
|
|
Ok(string)
|
|
} else {
|
|
Err(Error::new(
|
|
lit.span(),
|
|
"Expected string literal. Try this: `\"value\"`.",
|
|
))
|
|
}
|
|
}
|
|
|
|
fn expect_path_from_expr(expr: &Expr) -> Result<Path> {
|
|
if let Expr::Path(path) = expr {
|
|
Ok(path.path.clone())
|
|
} else {
|
|
Err(Error::new(
|
|
expr.span(),
|
|
format!("Expected path `{}`.", expr.to_token_stream()),
|
|
))
|
|
}
|
|
}
|
|
|
|
fn expect_ident_from_expr(expr: &Expr) -> Result<Ident> {
|
|
if let Expr::Path(path) = expr {
|
|
expect_ident_from_path(&path.path)
|
|
} else {
|
|
Err(Error::new(
|
|
expr.span(),
|
|
format!("Expected identifier `{}`.", expr.to_token_stream()),
|
|
))
|
|
}
|
|
}
|
|
|
|
fn expect_ident_from_path(path: &Path) -> Result<Ident> {
|
|
if let Some(ident) = path.get_ident() {
|
|
Ok(ident.clone())
|
|
} else {
|
|
Err(Error::new(path.span(), "Expected identifier."))
|
|
}
|
|
}
|
|
|
|
fn expect_one_nested_expr(nested: &Punctuated<Expr, token::Comma>) -> Result<&Expr> {
|
|
if nested.len() == 1 {
|
|
Ok(nested.first().unwrap())
|
|
} else {
|
|
Err(Error::new(nested.span(), "Expected only one expression."))
|
|
}
|
|
}
|
|
|
|
fn parse_track(nested: &Punctuated<Expr, token::Comma>) -> Result<(Option<Ident>, Option<Expr>)> {
|
|
let len = nested.len();
|
|
if len == 1 {
|
|
if let Some(skip_ident) = expr_to_skip_init_ident(&nested[0]) {
|
|
Ok((Some(skip_ident), None))
|
|
} else {
|
|
Ok((None, Some(nested[0].clone())))
|
|
}
|
|
} else if len == 2 {
|
|
if let Some(skip_ident) = expr_to_skip_init_ident(&nested[0]) {
|
|
Ok((Some(skip_ident), Some(nested.last().unwrap().clone())))
|
|
} else {
|
|
Err(Error::new(
|
|
nested.span(),
|
|
"Expected `skip_init` and an expression.",
|
|
))
|
|
}
|
|
} else {
|
|
Err(Error::new(
|
|
nested.span(),
|
|
"Expected exactly one or two expressions.",
|
|
))
|
|
}
|
|
}
|
|
|
|
fn expr_to_skip_init_ident(expr: &Expr) -> Option<Ident> {
|
|
if let Expr::Path(path) = &expr {
|
|
if let Some(ident) = path.path.get_ident() {
|
|
if ident == "skip_init" {
|
|
return Some(ident.clone());
|
|
}
|
|
}
|
|
}
|
|
None
|
|
}
|