brisk-it 0.8.0

brisk-it is the core library of the brisk declarative engine.
Documentation
//! Data structure created from parsing a declarative component.
use syn::{parse::Parse, Token};

/// Represents a property in a component.
///
/// ```ignore
///   identifier: some+expression
/// ```
///
/// In which case, the `identifier` is stored in the `name` field, while `some+expression` is in `expr`.
#[derive(Debug)]
pub struct PropertyValue {
    /// Mutability of the field, used only by a few components.
    pub mutability: Option<Token![mut]>,
    /// name of the property
    pub name: syn::Ident,
    #[allow(missing_docs)]
    pub colon_token: Token![:],
    /// Expression of the property
    pub expr: syn::Expr,
}

#[derive(Debug)]

/// Represent a match element.
///
/// ```ignore
///  pattern => { some_expression }
/// ```
pub struct Match {
    /// Pattern
    pub pattern: syn::Pat,
    #[allow(missing_docs)]
    pub arrow_token: Token![=>],
    /// Action
    pub component: ComponentInput,
}

/// Represent a transition definition
///
/// ```ignore
///   State1 => State2 { statements; }
/// ```
///
/// `State1` is stored in `source_state`,  `State2` in `destination_state` and `{ statements }` in action.
#[derive(Debug)]
pub struct TransitionDefinition {
    /// Source state
    pub source_state: syn::Ident,
    #[allow(missing_docs)]
    pub arrow_token: Token![=>],
    /// Destination state
    pub destination_state: syn::Ident,
    /// Action executed after the transition
    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,
        })
    }
}

/// Represents a `on` statement in a component.
///
/// ```ignore
///   on event_name: State1 => State2 { statements; }
///   on event_name: [ State1 => State2 { statements; }, State3 => State4 { statements; } ]
/// ```
///
/// In which case, the `event_name` is stored in the `name` field, while `State1` in `source_state`,
/// `State2` in `destination_state` and `{ statements }` in action.
#[derive(Debug)]
pub struct OnValue {
    #[allow(missing_docs)]
    pub on_keyword: syn::Ident,
    /// name of the on vale
    pub name: syn::Ident,
    #[allow(missing_docs)]
    pub colon_token: Token![:],
    /// Transition definitions for this on value
    pub transition_definitions: syn::punctuated::Punctuated<TransitionDefinition, Token![,]>,
}

/// Intermediary structure used for parsing
#[derive(Debug)]
enum ComponentContent {
    Id(syn::Ident),
    Property(PropertyValue),
    Child(ComponentInput),
    On(OnValue),
}

/// Intermediary structure used for parsing
#[derive(Debug)]
enum MatchComponentContent {
    Id(syn::Ident),
    Property(PropertyValue),
    Match(Match),
}

/// Represents a component
/// ```ignore
/// Identifier
/// {
///   id: some_name
///   prop1: value1
///   Child
///   {
///     prop2: value2
///   }
/// }
/// ```
///
/// `Identifier` is stored in the `name` field, while `prop1/value1` are in the list of `properties`.
/// And `Child` is in the list of `children`.
#[derive(Debug)]
pub struct ComponentInput {
    /// Name of the component
    pub name: syn::Ident,
    #[allow(missing_docs)]
    pub brace_token: syn::token::Brace,
    /// Identifier, used for storing in a variable
    pub id: Option<syn::Ident>,
    /// List of properties
    pub properties: Vec<PropertyValue>,
    /// List of children
    pub children: Vec<ComponentInput>,
    /// List of on_expressions
    pub on_expressions: Vec<OnValue>,
    /// List of of_matches
    pub matches: Vec<Match>,
    /// Visibility of the component (not always applicable).
    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,
        })
    }
}