edfsm-macros 0.12.0

Macros for the Event Driven Finite State Machine library
Documentation
use std::mem;

use quote::quote;
use syn::{
    parse::{Parse, ParseStream},
    parse2, token, Error, Ident, ImplItem, ImplItemMacro, ImplItemType, ItemImpl, Result, Type,
};

pub struct Entry {
    pub state: Type,
}

impl Parse for Entry {
    fn parse(input: ParseStream) -> Result<Self> {
        let state = input.parse()?;
        input.parse::<token::Div>()?;
        let ident = input.parse::<Ident>()?;
        let ident_str = ident.to_string();
        if ident_str != "entry" {
            return Err(Error::new_spanned(ident, format!("Unknown state qualifer: `/ {ident_str}`. Use only `/ entry` to indicate entry points here.")));
        };
        Ok(Self { state })
    }
}

#[derive(Clone)]
pub struct TargetStates {
    pub states: Vec<Type>,
}

impl Parse for TargetStates {
    fn parse(input: ParseStream) -> Result<Self> {
        let mut target_types = vec![];
        loop {
            let target_type = input.parse()?;
            target_types.push(target_type);
            if input.parse::<token::Or>().is_err() {
                break;
            }
        }
        Ok(Self {
            states: target_types,
        })
    }
}

pub trait Step {
    #[allow(clippy::wrong_self_convention)]
    fn from_state(&self) -> &Type;
    fn command(&self) -> &Option<Type>;
    fn event(&self) -> &Option<Type>;
    fn to_state(&self) -> &Option<TargetStates>;
    fn on_change(&self) -> bool;
}

pub struct CommandStep {
    pub from_state: Type,
    pub command: Option<Type>,
    pub event: Option<Type>,
    pub to_state: Option<TargetStates>,
}

impl Parse for CommandStep {
    fn parse(input: ParseStream) -> Result<Self> {
        let from_state = input.parse()?;
        input.parse::<token::FatArrow>()?;
        let command = Some(input.parse()?);
        let (event, to_state) = if input.parse::<token::FatArrow>().is_ok() {
            let event = Some(input.parse()?);
            let to_state = if input.parse::<token::FatArrow>().is_ok() {
                Some(input.parse()?)
            } else {
                None
            };
            (event, to_state)
        } else {
            (None, None)
        };
        Ok(Self {
            from_state,
            command,
            event,
            to_state,
        })
    }
}

impl Step for CommandStep {
    fn from_state(&self) -> &Type {
        &self.from_state
    }

    fn command(&self) -> &Option<Type> {
        &self.command
    }

    fn event(&self) -> &Option<Type> {
        &self.event
    }

    fn to_state(&self) -> &Option<TargetStates> {
        &self.to_state
    }

    fn on_change(&self) -> bool {
        false
    }
}

pub struct EventStep {
    pub from_state: Type,
    pub command: Option<Type>,
    pub event: Option<Type>,
    pub to_state: Option<TargetStates>,
    pub on_change: bool,
}

impl Parse for EventStep {
    fn parse(input: ParseStream) -> Result<Self> {
        let from_state = input.parse()?;
        input.parse::<token::FatArrow>()?;
        let event = Some(input.parse()?);
        let to_state = if input.peek(token::FatArrow) {
            input.parse::<token::FatArrow>()?;
            Some(input.parse()?)
        } else {
            None
        };
        let on_change = if input.peek(token::Div) {
            input.parse::<token::Div>()?;
            let ident = input.parse::<Ident>()?;
            let ident_str = ident.to_string();
            if ident_str != "action" {
                return Err(Error::new_spanned(ident, format!("Unknown state qualifer: `/ {ident_str}`. Use only `/ action` to indicate there is going to be an action here.")));
            };
            true
        } else {
            false
        };

        Ok(Self {
            from_state,
            command: None,
            event,
            to_state,
            on_change,
        })
    }
}

impl Step for EventStep {
    fn from_state(&self) -> &Type {
        &self.from_state
    }

    fn command(&self) -> &Option<Type> {
        &self.command
    }

    fn event(&self) -> &Option<Type> {
        &self.event
    }

    fn to_state(&self) -> &Option<TargetStates> {
        &self.to_state
    }

    fn on_change(&self) -> bool {
        self.on_change
    }
}

pub struct IgnoreCommand {
    pub from_state: Type,
    pub command: Type,
}

impl Parse for IgnoreCommand {
    fn parse(input: ParseStream) -> Result<Self> {
        let from_state = input.parse()?;
        input.parse::<token::FatArrow>()?;
        let command = input.parse()?;
        Ok(Self {
            from_state,
            command,
        })
    }
}

pub struct IgnoreEvent {
    pub from_state: Type,
    pub event: Type,
}

impl Parse for IgnoreEvent {
    fn parse(input: ParseStream) -> Result<Self> {
        let from_state = input.parse()?;
        input.parse::<token::FatArrow>()?;
        let event = input.parse()?;
        Ok(Self { from_state, event })
    }
}

pub struct Fsm {
    pub state_enum: Type,
    pub command_enum: Type,
    pub event_enum: Type,
    pub effect_handlers: Type,
    pub entry_handlers: Vec<Entry>,
    pub steps: Vec<Box<dyn Step>>,
    pub ignore_commands: Vec<IgnoreCommand>,
    pub ignore_events: Vec<IgnoreEvent>,
    pub item_impl: ItemImpl,
}

impl Parse for Fsm {
    fn parse(input: ParseStream) -> Result<Self> {
        let mut item_impl = input.parse::<ItemImpl>()?;

        let items = mem::take(&mut item_impl.items);

        let mut state_enum = None;
        let mut command_enum = None;
        let mut event_enum = None;
        let mut effect_handlers = None;
        let mut entry_handlers = vec![];
        let mut steps = vec![];
        let mut ignore_commands = vec![];
        let mut ignore_events = vec![];

        for item in items {
            match item {
                ImplItem::Type(ImplItemType { ident, ty, .. }) => {
                    let type_name = quote!(#ident).to_string();
                    match type_name.as_str() {
                        "S" => {
                            state_enum = Some(ty);
                        }
                        "C" => {
                            command_enum = Some(ty);
                        }
                        "E" => {
                            event_enum = Some(ty);
                        }
                        "SE" => {
                            effect_handlers = Some(ty);
                        }
                        n => {
                            return Err(Error::new_spanned(ident, format!("Unknown associated types: `{n}!`. Use only `S`, `C`, `E` and `SE` here.")));
                        }
                    }
                }
                ImplItem::Macro(ImplItemMacro { mac, .. }) => {
                    let path = mac.path.clone();
                    let macro_name = quote!(#path).to_string();
                    match macro_name.as_str() {
                        "state" => {
                            entry_handlers.push(parse2(mac.tokens)?);
                        }
                        "command" => {
                            steps.push(Box::new(parse2::<CommandStep>(mac.tokens)?) as Box<dyn Step>);
                        }
                        "event" => {
                            steps.push(Box::new(parse2::<EventStep>(mac.tokens)?) as Box<dyn Step>);
                        }
                        "ignore_command" => {
                            ignore_commands.push(parse2::<IgnoreCommand>(mac.tokens)?);
                        }
                        "ignore_event" => {
                            ignore_events.push(parse2::<IgnoreEvent>(mac.tokens)?);
                        }
                        n => {
                            return Err(Error::new_spanned(mac, format!("Unknown macro: `{n}!`. Use only `state!`, `command!`, `event!`, `ignore_command!` and `ignore_event!` macros here.")));
                        }
                    }
                }
                _ => {
                    return Err(Error::new_spanned(
                        item,
                        "Unexpected. Use only the associated type declarations, and `state!`, `transition!` and `ignore!` macros here.",
                    ));
                }
            }
        }
        if let (Some(state_enum), Some(command_enum), Some(event_enum), Some(effect_handlers)) =
            (state_enum, command_enum, event_enum, effect_handlers)
        {
            Ok(Self {
                state_enum,
                command_enum,
                event_enum,
                effect_handlers,
                entry_handlers,
                steps,
                ignore_commands,
                ignore_events,
                item_impl,
            })
        } else {
            Err(Error::new_spanned(
                item_impl,
                "Unexpected. Missing one or more associated types: `{n}!`. Declare all of `S`, `C`, `E` and `SE` here.",
            ))
        }
    }
}