use syn::{parse::Parse, Token};
#[derive(Debug)]
pub struct PropertyValue {
pub mutability: Option<Token![mut]>,
pub name: syn::Ident,
#[allow(missing_docs)]
pub colon_token: Token![:],
pub expr: syn::Expr,
}
#[derive(Debug)]
pub struct Match {
pub pattern: syn::Pat,
#[allow(missing_docs)]
pub arrow_token: Token![=>],
pub component: ComponentInput,
}
#[derive(Debug)]
pub struct TransitionDefinition {
pub source_state: syn::Ident,
#[allow(missing_docs)]
pub arrow_token: Token![=>],
pub destination_state: syn::Ident,
pub action: syn::Block,
}
impl Parse for TransitionDefinition {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let source_state: syn::Ident = input.parse()?;
let arrow_token: Token![=>] = input.parse()?;
let destination_state: syn::Ident = input.parse()?;
let action: syn::Block = input.parse()?;
Ok(TransitionDefinition {
source_state,
arrow_token,
destination_state,
action,
})
}
}
#[derive(Debug)]
pub struct OnValue {
#[allow(missing_docs)]
pub on_keyword: syn::Ident,
pub name: syn::Ident,
#[allow(missing_docs)]
pub colon_token: Token![:],
pub transition_definitions: syn::punctuated::Punctuated<TransitionDefinition, Token![,]>,
}
#[derive(Debug)]
enum ComponentContent {
Id(syn::Ident),
Property(PropertyValue),
Child(ComponentInput),
On(OnValue),
}
#[derive(Debug)]
enum MatchComponentContent {
Id(syn::Ident),
Property(PropertyValue),
Match(Match),
}
#[derive(Debug)]
pub struct ComponentInput {
pub name: syn::Ident,
#[allow(missing_docs)]
pub brace_token: syn::token::Brace,
pub id: Option<syn::Ident>,
pub properties: Vec<PropertyValue>,
pub children: Vec<ComponentInput>,
pub on_expressions: Vec<OnValue>,
pub matches: Vec<Match>,
pub visibility: syn::Visibility,
}
impl Parse for MatchComponentContent {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
if input.peek2(Token![:]) || (input.peek(Token![mut]) && input.peek3(Token![:])) {
let mutability = input.parse()?;
let lookahead1 = input.lookahead1();
if lookahead1.peek(syn::Ident) {
let r = input.parse();
let name: syn::Ident = r?;
if name == "id" {
let _: Token![:] = input.parse()?;
let id: syn::Ident = input.parse()?;
Ok(MatchComponentContent::Id(id))
} else {
let colon_token: Token![:] = input.parse()?;
let expr: syn::Expr = input.parse()?;
Ok(MatchComponentContent::Property(PropertyValue {
mutability,
name,
colon_token,
expr,
}))
}
} else {
let error = lookahead1.error();
Err(error)
}
} else {
let pattern = syn::Pat::parse_multi(input)?;
let arrow_token = input.parse()?;
let component = input.parse()?;
Ok(MatchComponentContent::Match(Match {
pattern,
arrow_token,
component,
}))
}
}
}
impl Parse for ComponentContent {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
if input.cursor().ident().is_some_and(|(i, _)| i == "on") && !input.peek2(Token![:]) {
let on_keyword: syn::Ident = input.parse()?;
let name: syn::Ident = input.parse()?;
let colon_token: Token![:] = input.parse()?;
let transition_definitions = if input.peek(syn::token::Bracket) {
let unparsed_content;
let _ = syn::bracketed!(unparsed_content in input);
unparsed_content.parse_terminated(TransitionDefinition::parse, Token![,])?
} else {
let mut p = syn::punctuated::Punctuated::<TransitionDefinition, Token![,]>::new();
p.push(TransitionDefinition::parse(input)?);
p
};
let on = OnValue {
on_keyword,
name,
colon_token,
transition_definitions,
};
Ok(ComponentContent::On(on))
} else if input.peek2(Token![:]) || (input.peek(Token![mut]) && input.peek3(Token![:])) {
let mutability = input.parse()?;
let lookahead1 = input.lookahead1();
if lookahead1.peek(syn::Ident) {
let r = input.parse();
let name: syn::Ident = r?;
if name == "id" {
let _: Token![:] = input.parse()?;
let id: syn::Ident = input.parse()?;
Ok(ComponentContent::Id(id))
} else {
let colon_token: Token![:] = input.parse()?;
let expr: syn::Expr = input.parse()?;
Ok(ComponentContent::Property(PropertyValue {
mutability,
name,
colon_token,
expr,
}))
}
} else {
let error = lookahead1.error();
Err(error)
}
} else {
Ok(Self::Child(ComponentInput::parse(input)?))
}
}
}
impl syn::parse::Parse for ComponentInput {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let visibility: syn::Visibility = input.parse()?;
let name = input.parse::<syn::Ident>()?;
let unparsed_content;
let brace_token = syn::braced!(unparsed_content in input);
let mut id = Default::default();
let mut properties: Vec<PropertyValue> = Default::default();
let mut children: Vec<ComponentInput> = Default::default();
let mut on_expressions: Vec<OnValue> = Default::default();
let mut matches: Vec<Match> = Default::default();
if name == "Match" {
let content =
unparsed_content.parse_terminated(MatchComponentContent::parse, Token![,])?;
for item in content {
match item {
MatchComponentContent::Id(idid) => id = Some(idid),
MatchComponentContent::Property(prop) => {
properties.push(prop);
}
MatchComponentContent::Match(match_) => {
matches.push(match_);
}
}
}
} else {
let content = unparsed_content.parse_terminated(ComponentContent::parse, Token![,])?;
for item in content {
match item {
ComponentContent::Id(idid) => id = Some(idid),
ComponentContent::Property(prop) => {
properties.push(prop);
}
ComponentContent::Child(child) => {
children.push(child);
}
ComponentContent::On(on_expr) => {
on_expressions.push(on_expr);
}
}
}
}
Ok(Self {
name,
brace_token,
id,
properties,
children,
on_expressions,
visibility,
matches,
})
}
}