|
@@ -80,23 +80,38 @@ impl Message {
|
|
|
|
|
|
impl ToTokens for Transition {
|
|
impl ToTokens for Transition {
|
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
|
- let transition = if let Some(msg) = &self.in_msg {
|
|
|
|
- self.generate_message_handler(msg)
|
|
|
|
|
|
+ let (msg_arg, method_ident) = if let Some(msg) = &self.in_msg {
|
|
|
|
+ let msg_type = if msg.msg_type == Activate::ident() {
|
|
|
|
+ quote! { ::btrun::Activate }
|
|
|
|
+ } else {
|
|
|
|
+ msg.msg_type.to_token_stream()
|
|
|
|
+ };
|
|
|
|
+ let method_ident = format_ident!("handle_{}", msg.variant().pascal_to_snake());
|
|
|
|
+ let msg_arg = quote! { , msg: #msg_type };
|
|
|
|
+ (msg_arg, method_ident)
|
|
} else {
|
|
} else {
|
|
- self.generate_sender_method()
|
|
|
|
|
|
+ let msg_arg = quote! {};
|
|
|
|
+ let msg_names = self
|
|
|
|
+ .out_msgs
|
|
|
|
+ .as_ref()
|
|
|
|
+ .iter()
|
|
|
|
+ .fold(Option::<String>::None, |accum, curr| {
|
|
|
|
+ let msg_name = curr.msg.variant().pascal_to_snake();
|
|
|
|
+ if let Some(mut accum) = accum {
|
|
|
|
+ accum.push('_');
|
|
|
|
+ accum.push_str(&msg_name);
|
|
|
|
+ Some(accum)
|
|
|
|
+ } else {
|
|
|
|
+ Some(msg_name)
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ // Since no message is being handled, the validator ensures that at least one
|
|
|
|
+ // message is being sent. Hence this unwrap will not panic.
|
|
|
|
+ .unwrap();
|
|
|
|
+ let method_ident = format_ident!("send_{}", msg_names);
|
|
|
|
+ (msg_arg, method_ident)
|
|
};
|
|
};
|
|
- tokens.extend(transition);
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl Transition {
|
|
|
|
- fn generate_message_handler(&self, msg: &Message) -> TokenStream {
|
|
|
|
- let msg_type = if msg.msg_type == Activate::ident() {
|
|
|
|
- quote! { ::btrun::Activate }
|
|
|
|
- } else {
|
|
|
|
- msg.msg_type.to_token_stream()
|
|
|
|
- };
|
|
|
|
- let method_ident = format_ident!("handle_{}", msg.msg_type.to_lowercase());
|
|
|
|
|
|
+ let method_type_prefix = method_ident.snake_to_pascal();
|
|
let output_pairs: Vec<_> = self
|
|
let output_pairs: Vec<_> = self
|
|
.out_states
|
|
.out_states
|
|
.as_ref()
|
|
.as_ref()
|
|
@@ -106,7 +121,7 @@ impl Transition {
|
|
if state_trait == End::ident() {
|
|
if state_trait == End::ident() {
|
|
(quote! {}, quote! { ::btrun::End })
|
|
(quote! {}, quote! { ::btrun::End })
|
|
} else {
|
|
} else {
|
|
- let assoc_type = format_ident!("{}Output{}", msg.variant(), state_trait);
|
|
|
|
|
|
+ let assoc_type = format_ident!("{}{}", method_type_prefix, state_trait);
|
|
let output_decl = quote! { type #assoc_type: #state_trait; };
|
|
let output_decl = quote! { type #assoc_type: #state_trait; };
|
|
let output_type = quote! { Self::#assoc_type };
|
|
let output_type = quote! { Self::#assoc_type };
|
|
(output_decl, output_type)
|
|
(output_decl, output_type)
|
|
@@ -115,27 +130,172 @@ impl Transition {
|
|
.collect();
|
|
.collect();
|
|
let output_decls = output_pairs.iter().map(|(decl, _)| decl);
|
|
let output_decls = output_pairs.iter().map(|(decl, _)| decl);
|
|
let output_types = output_pairs.iter().map(|(_, output_type)| output_type);
|
|
let output_types = output_pairs.iter().map(|(_, output_type)| output_type);
|
|
- let future_name = format_ident!("Handle{}Fut", msg.variant());
|
|
|
|
- quote! {
|
|
|
|
|
|
+ let future_name = format_ident!("{}Fut", method_type_prefix);
|
|
|
|
+ let transition = quote! {
|
|
#( #output_decls )*
|
|
#( #output_decls )*
|
|
type #future_name: ::std::future::Future<Output = Result<( #( #output_types ),* )>>;
|
|
type #future_name: ::std::future::Future<Output = Result<( #( #output_types ),* )>>;
|
|
- fn #method_ident(self, msg: #msg_type) -> Self::#future_name;
|
|
|
|
|
|
+ fn #method_ident(self #msg_arg) -> Self::#future_name;
|
|
|
|
+ };
|
|
|
|
+ tokens.extend(transition);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+trait CaseConvert {
|
|
|
|
+ /// Converts a name in snake_case to PascalCase.
|
|
|
|
+ fn snake_to_pascal(&self) -> String;
|
|
|
|
+ /// Converts a name in PascalCase to snake_case.
|
|
|
|
+ fn pascal_to_snake(&self) -> String;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl CaseConvert for String {
|
|
|
|
+ fn snake_to_pascal(&self) -> String {
|
|
|
|
+ let mut pascal = String::with_capacity(self.len());
|
|
|
|
+ let mut prev_underscore = true;
|
|
|
|
+ for c in self.chars() {
|
|
|
|
+ if '_' == c {
|
|
|
|
+ prev_underscore = true;
|
|
|
|
+ } else {
|
|
|
|
+ if prev_underscore {
|
|
|
|
+ pascal.extend(c.to_uppercase());
|
|
|
|
+ } else {
|
|
|
|
+ pascal.push(c);
|
|
|
|
+ }
|
|
|
|
+ prev_underscore = false;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
+ pascal
|
|
}
|
|
}
|
|
|
|
|
|
- fn generate_sender_method(&self) -> TokenStream {
|
|
|
|
- quote! {}
|
|
|
|
|
|
+ fn pascal_to_snake(&self) -> String {
|
|
|
|
+ let mut snake = String::with_capacity(self.len());
|
|
|
|
+ let mut prev_lower = false;
|
|
|
|
+ for c in self.chars() {
|
|
|
|
+ if c.is_uppercase() {
|
|
|
|
+ if prev_lower {
|
|
|
|
+ snake.push('_');
|
|
|
|
+ }
|
|
|
|
+ snake.extend(c.to_lowercase());
|
|
|
|
+ prev_lower = false;
|
|
|
|
+ } else {
|
|
|
|
+ prev_lower = true;
|
|
|
|
+ snake.push(c);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ snake
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-trait ToLowercase {
|
|
|
|
- fn to_lowercase(&self) -> String;
|
|
|
|
|
|
+impl CaseConvert for Ident {
|
|
|
|
+ fn snake_to_pascal(&self) -> String {
|
|
|
|
+ self.to_string().snake_to_pascal()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fn pascal_to_snake(&self) -> String {
|
|
|
|
+ self.to_string().pascal_to_snake()
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
-impl ToLowercase for Ident {
|
|
|
|
- fn to_lowercase(&self) -> String {
|
|
|
|
- let mut buf = self.to_string();
|
|
|
|
- buf.make_ascii_lowercase();
|
|
|
|
- buf
|
|
|
|
|
|
+#[cfg(test)]
|
|
|
|
+mod tests {
|
|
|
|
+ use super::*;
|
|
|
|
+
|
|
|
|
+ #[test]
|
|
|
|
+ fn string_snake_to_pascal_multiple_segments() {
|
|
|
|
+ const EXPECTED: &str = "FirstSecondThird";
|
|
|
|
+ let input = String::from("first_second_third");
|
|
|
|
+
|
|
|
|
+ let actual = input.snake_to_pascal();
|
|
|
|
+
|
|
|
|
+ assert_eq!(EXPECTED, actual);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ #[test]
|
|
|
|
+ fn string_snake_to_pascal_single_segment() {
|
|
|
|
+ const EXPECTED: &str = "First";
|
|
|
|
+ let input = String::from("first");
|
|
|
|
+
|
|
|
|
+ let actual = input.snake_to_pascal();
|
|
|
|
+
|
|
|
|
+ assert_eq!(EXPECTED, actual);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ #[test]
|
|
|
|
+ fn string_snake_to_pascal_empty_string() {
|
|
|
|
+ const EXPECTED: &str = "";
|
|
|
|
+ let input = String::from(EXPECTED);
|
|
|
|
+
|
|
|
|
+ let actual = input.snake_to_pascal();
|
|
|
|
+
|
|
|
|
+ assert_eq!(EXPECTED, actual);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ #[test]
|
|
|
|
+ fn string_snake_to_pascal_leading_underscore() {
|
|
|
|
+ const EXPECTED: &str = "First";
|
|
|
|
+ let input = String::from("_first");
|
|
|
|
+
|
|
|
|
+ let actual = input.snake_to_pascal();
|
|
|
|
+
|
|
|
|
+ assert_eq!(EXPECTED, actual);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ #[test]
|
|
|
|
+ fn string_snake_to_pascal_leading_underscores() {
|
|
|
|
+ const EXPECTED: &str = "First";
|
|
|
|
+ let input = String::from("__first");
|
|
|
|
+
|
|
|
|
+ let actual = input.snake_to_pascal();
|
|
|
|
+
|
|
|
|
+ assert_eq!(EXPECTED, actual);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ #[test]
|
|
|
|
+ fn string_snake_to_pascal_multiple_underscores() {
|
|
|
|
+ const EXPECTED: &str = "FirstSecondThird";
|
|
|
|
+ let input = String::from("first__second___third");
|
|
|
|
+
|
|
|
|
+ let actual = input.snake_to_pascal();
|
|
|
|
+
|
|
|
|
+ assert_eq!(EXPECTED, actual);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ #[test]
|
|
|
|
+ fn string_pascal_to_snake_multiple_segments() {
|
|
|
|
+ const EXPECTED: &str = "first_second_third";
|
|
|
|
+ let input = String::from("FirstSecondThird");
|
|
|
|
+
|
|
|
|
+ let actual = input.pascal_to_snake();
|
|
|
|
+
|
|
|
|
+ assert_eq!(EXPECTED, actual);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ #[test]
|
|
|
|
+ fn string_pascal_to_snake_single_segment() {
|
|
|
|
+ let input = String::from("First");
|
|
|
|
+ const EXPECTED: &str = "first";
|
|
|
|
+
|
|
|
|
+ let actual = input.pascal_to_snake();
|
|
|
|
+
|
|
|
|
+ assert_eq!(EXPECTED, actual);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ #[test]
|
|
|
|
+ fn string_pascal_to_snake_empty_string() {
|
|
|
|
+ const EXPECTED: &str = "";
|
|
|
|
+ let input = String::from(EXPECTED);
|
|
|
|
+
|
|
|
|
+ let actual = input.pascal_to_snake();
|
|
|
|
+
|
|
|
|
+ assert_eq!(EXPECTED, actual);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ #[test]
|
|
|
|
+ fn string_pascal_to_snake_consecutive_uppercase() {
|
|
|
|
+ const EXPECTED: &str = "kernel_mc";
|
|
|
|
+ let input = String::from("KernelMC");
|
|
|
|
+
|
|
|
|
+ let actual = input.pascal_to_snake();
|
|
|
|
+
|
|
|
|
+ assert_eq!(EXPECTED, actual);
|
|
}
|
|
}
|
|
}
|
|
}
|