Finite state machines in rust; bendns fork to add types.
optional typing
bendn 5 months ago
parent 297c4d5 · commit 81a19a6
-rw-r--r--rust-fsm-dsl/src/lib.rs100
-rw-r--r--rust-fsm-dsl/src/parser.rs4
-rw-r--r--rust-fsm-dsl/src/variant.rs147
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)
}
}