Finite state machines in rust; bendns fork to add types.
| -rw-r--r-- | rust-fsm-dsl/src/lib.rs | 100 | ||||
| -rw-r--r-- | rust-fsm-dsl/src/parser.rs | 4 | ||||
| -rw-r--r-- | rust-fsm-dsl/src/variant.rs | 147 |
3 files changed, 140 insertions, 111 deletions
diff --git a/rust-fsm-dsl/src/lib.rs b/rust-fsm-dsl/src/lib.rs index df39b74..55385ca 100644 --- a/rust-fsm-dsl/src/lib.rs +++ b/rust-fsm-dsl/src/lib.rs @@ -6,7 +6,7 @@ extern crate proc_macro; use proc_macro::TokenStream; use quote::{quote, ToTokens}; -use std::{collections::BTreeSet, iter::FromIterator}; +use std::iter::FromIterator; use syn::*; mod parser; mod variant; @@ -63,15 +63,17 @@ pub fn state_machine(tokens: TokenStream) -> TokenStream { // use std::hash::BuildHasher; // rustc_hash::FxSeededState::with_seed(5).hash_one(x) // } - let mut states = BTreeSet::new(); - let mut inputs = BTreeSet::new(); - let mut outputs = BTreeSet::new(); - let mut transition_cases = Vec::new(); - let mut output_cases = Vec::new(); + let mut states = vec![]; + let mut inputs = vec![]; + let mut outputs = vec![]; + let mut transition_cases = vec![]; + let mut output_cases = vec![]; - use std::fmt::Write; #[cfg(feature = "diagram")] - let mut mermaid_diagram = format!("///```mermaid\n///stateDiagram-v2\n",); + let mut mermaid_diagram = format!( + "///```mermaid +///stateDiagram-v2\n", + ); for transition in transitions { let Transition { initial_state, @@ -94,11 +96,19 @@ pub fn state_machine(tokens: TokenStream) -> TokenStream { // id(&final_state) // ) // .unwrap(); + use std::fmt::Write; #[cfg(feature = "diagram")] write!( mermaid_diagram, - "/// {initial_state} --> {final_state}: {}", - input_value.match_on() + "/// {}", + &format!( + "{:?}", + format!( + "{initial_state} --> {final_state}: {}", + input_value.match_on() + ) + ) + .trim_matches('"'), ) .unwrap(); @@ -129,11 +139,11 @@ pub fn state_machine(tokens: TokenStream) -> TokenStream { #[cfg(feature = "diagram")] mermaid_diagram.push('\n'); - states.insert(initial_state); - states.insert(Box::leak(Box::new(final_state.clone().variant()))); - inputs.insert(input_value); - if let Some(ref output) = output { - outputs.insert(output); + states.push(initial_state.clone()); + states.push(final_state.clone().variant()); + inputs.push(input_value.clone()); + if let Some(output) = output { + outputs.push(output.clone().variant()); } } @@ -146,44 +156,50 @@ pub fn state_machine(tokens: TokenStream) -> TokenStream { .replace(')', "#41;") .replace('[', "#91;") .replace(']', "#93;") + .replace('|', "#124;") .replace("Default", "def") .parse() .unwrap(); - let input_impl = input_name.tokenize(|f| { - quote! { - #attrs - #visibility enum #f { - #(#inputs),* + let input_impl = variant::tokenize(&inputs, |x| { + input_name.tokenize(|f| { + quote! { + #attrs + #visibility enum #f { + #(#x),* + } } - } + }) }); let input_name = input_name.path(); - let state_impl = state_name.tokenize(|f| { - quote! { - #attrs - #visibility enum #f { - #(#states),* + let state_impl = variant::tokenize(&states, |x| { + state_name.tokenize(|f| { + quote! { + #attrs + #visibility enum #f { + #(#x),* + } } - } + }) }); let state_name = state_name.path(); - - let output_impl = output_name.tokenize(|output_name| { - // Many attrs and derives may work incorrectly (or simply not work) for empty enums, so we just skip them - // altogether if the output alphabet is empty. - let attrs = if outputs.is_empty() { - quote!() - } else { - attrs.clone() - }; - - quote! { - #attrs - #visibility enum #output_name { - #(#outputs),* + let output_impl = variant::tokenize(&*outputs, |outputs| { + output_name.tokenize(|output_name| { + // Many attrs and derives may work incorrectly (or simply not work) for empty enums, so we just skip them + // altogether if the output alphabet is empty. + let attrs = if outputs.is_empty() { + quote!() + } else { + attrs.clone() + }; + + quote! { + #attrs + #visibility enum #output_name { + #(#outputs),* + } } - } + }) }); let output_name = output_name.path(); diff --git a/rust-fsm-dsl/src/parser.rs b/rust-fsm-dsl/src/parser.rs index 81b59e0..bed9720 100644 --- a/rust-fsm-dsl/src/parser.rs +++ b/rust-fsm-dsl/src/parser.rs @@ -58,7 +58,7 @@ pub struct TransitionDef { impl Parse for TransitionDef { fn parse(input: ParseStream) -> Result<Self> { - let initial_state = input.parse()?; + let initial_state: Variant = input.parse()?; input.parse::<Token![=>]>()?; // Parse the transition in the simple format // InitialState => Input => ResultState @@ -88,7 +88,7 @@ impl Parse for TransitionDef { .collect(); if entries.is_empty() { return Err(Error::new_spanned( - initial_state, + initial_state.ident, "No transitions provided for a compact representation", )); } diff --git a/rust-fsm-dsl/src/variant.rs b/rust-fsm-dsl/src/variant.rs index a59a5f6..82e981c 100644 --- a/rust-fsm-dsl/src/variant.rs +++ b/rust-fsm-dsl/src/variant.rs @@ -1,12 +1,47 @@ -use std::fmt::Display; +use std::{collections::BTreeSet, fmt::Display}; +use proc_macro2::TokenStream; use quote::ToTokens; use syn::{parse::Parse, *}; /// Variant with no discriminator -#[derive(Hash, Debug, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct Variant { pub ident: Ident, - field: Option<(Type, Pat, Option<Expr>)>, + pub field: Option<(Option<Type>, Pat, Option<Expr>)>, +} + +pub fn find_type(of: &Variant, list: &[Variant]) -> Option<Type> { + of.field.clone().and_then(|(x, _, _)| { + x.or_else(|| { + let i = &of.ident; + list.iter() + .filter(|x| &x.ident == i) + .filter_map(|x| x.field.as_ref().and_then(|x| x.0.clone())) + .next() + }) + }) +} +pub fn tokenize( + inputs: &[Variant], + f: impl FnOnce(Vec<TokenStream>) -> TokenStream, +) -> TokenStream { + let (Ok(x) | Err(x)) = BTreeSet::from_iter(inputs) + .into_iter() + .map(|x| { + let i = &x.ident; + x.field + .as_ref() + .map(|_| { + let y = find_type(x, &*inputs); + y.ok_or(Error::new_spanned(&x.ident, "type never specified")) + .map(|y| quote::quote! {#i(#y)}) + }) + .unwrap_or(Ok(quote::quote! { #i })) + }) + .collect::<Result<_>>() + .map_err(Error::into_compile_error) + .map(f); + x } impl Parse for Variant { @@ -17,11 +52,17 @@ impl Parse for Variant { let field = if input.peek(token::Paren) { let inp; parenthesized!(inp in input); - let t = inp.parse()?; - inp.parse::<Token![=>]>()?; + let ty = inp + .to_string() + .contains("=>") + .then(|| { + inp.parse() + .and_then(|x| inp.parse::<Token![=>]>().map(|_| x)) + }) + .transpose()?; Some(( - t, + ty, Pat::parse_multi(&inp)?, inp.lookahead1() .peek(Token![if]) @@ -39,6 +80,13 @@ impl Parse for Variant { } } +impl PartialEq for Variant { + fn eq(&self, other: &Self) -> bool { + self.ident == other.ident + } +} +impl Eq for Variant {} + impl PartialOrd for Variant { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { self.ident.partial_cmp(&other.ident) @@ -50,15 +98,6 @@ impl Ord for Variant { } } -impl ToTokens for Variant { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - self.ident.to_tokens(tokens); - if let Some((t, _, _)) = &self.field { - tokens.extend(quote::quote! { (#t) }) - } - } -} - impl Variant { pub fn match_on(&self) -> proc_macro2::TokenStream { if let Self { @@ -82,7 +121,7 @@ impl Variant { { let b = g .as_ref() - .map(|x| quote::quote! { if #x }) + .map(|x| quote::quote! { if (#x) }) .unwrap_or_default(); (quote::quote! { #ident(#p) }, b) } else { @@ -97,11 +136,8 @@ impl Display for Variant { } } /// type and expression -#[derive(Hash, Debug, PartialEq, Eq, Clone)] -pub struct Final { - pub ident: Ident, - field: Option<(Type, Expr)>, -} +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Final(Variant); impl Parse for Final { fn parse(input: parse::ParseStream) -> Result<Self> { @@ -109,72 +145,49 @@ impl Parse for Final { let field = if input.peek(token::Paren) { let inp; parenthesized!(inp in input); - let t = inp.parse()?; - inp.parse::<Token![=>]>()?; + let t = inp + .to_string() + .contains("=>") + .then(|| { + inp.parse() + .and_then(|x| inp.parse::<Token![=>]>().map(|_| x)) + }) + .transpose()?; - Some((t, inp.parse()?)) + Some(( + t, + Pat::Wild(PatWild { + attrs: vec![], + underscore_token: Default::default(), + }), + Some(inp.parse()?), + )) } else { None }; - Ok(Final { - // attrs, - ident, - field, - }) - } -} - -impl PartialOrd for Final { - fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { - self.ident.partial_cmp(&other.ident) - } -} -impl Ord for Final { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.ident.cmp(&other.ident) - } -} - -impl ToTokens for Final { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - self.ident.to_tokens(tokens); - if let Some((v, _)) = &self.field { - tokens.extend(quote::quote! { (#v) }) - } + Ok(Final(Variant { ident, field })) } } impl Final { pub fn reduce(&self) -> proc_macro2::TokenStream { - if let Self { + if let Self(Variant { ident, - field: Some((_, v)), - } = self + field: Some((_, _, v)), + }) = self { quote::quote! { #ident ( #v ) } } else { - self.ident.to_token_stream() + self.0.ident.to_token_stream() } } pub fn variant(self) -> Variant { - Variant { - ident: self.ident, - field: self.field.map(|(x, _)| { - ( - x, - Pat::Wild(PatWild { - attrs: vec![], - underscore_token: Default::default(), - }), - None, - ) - }), - } + self.0 } } impl Display for Final { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.ident) + write!(f, "{}", self.0) } } |