Browse Source

"Finished" implementing code generation for the protocol macro.

Matthew Carr 1 năm trước cách đây
mục cha
commit
be2fdf8d81

+ 188 - 28
crates/btproto/src/generation.rs

@@ -80,23 +80,38 @@ impl Message {
 
 impl ToTokens for Transition {
     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 {
-            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
             .out_states
             .as_ref()
@@ -106,7 +121,7 @@ impl Transition {
                 if state_trait == End::ident() {
                     (quote! {}, quote! { ::btrun::End })
                 } 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_type = quote! { Self::#assoc_type };
                     (output_decl, output_type)
@@ -115,27 +130,172 @@ impl Transition {
             .collect();
         let output_decls = output_pairs.iter().map(|(decl, _)| decl);
         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 )*
             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);
     }
 }

+ 1 - 0
crates/btproto/src/lib.rs

@@ -22,6 +22,7 @@ macro_rules! unwrap_or_compile_err {
 /// Generates types for the parties participating in a messaging protocol.
 /// The grammar recognized by this macro is given below in the dialect of Extended Backus-Naur Form
 /// recognized by the `llgen` tool:
+///
 /// ```ebnf
 /// protocol : name_def states_def transition* ;
 /// name_def : "let" "name" '=' Ident ';' ;

+ 8 - 4
crates/btproto/tests/protocol_tests.rs

@@ -68,7 +68,7 @@ fn reply() {
     struct ServerInitState;
 
     impl ServerInit for ServerInitState {
-        type ActivateOutputListening = ListeningState;
+        type HandleActivateListening = ListeningState;
         type HandleActivateFut = Ready<Result<ListeningState>>;
         fn handle_activate(self, _msg: btrun::Activate) -> Self::HandleActivateFut {
             ready(Ok(ListeningState))
@@ -78,7 +78,7 @@ fn reply() {
     struct ListeningState;
 
     impl Listening for ListeningState {
-        type PingOutputListening = Self;
+        type HandlePingListening = Self;
         type HandlePingFut = Ready<Result<Self>>;
         fn handle_ping(self, _msg: Ping) -> Self::HandlePingFut {
             ready(Ok(self))
@@ -88,14 +88,18 @@ fn reply() {
     struct ClientState;
 
     impl Client for ClientState {
-        // TODO: Need to generate methods that the client must implement.
+        type SendPingWaiting = WaitingState;
+        type SendPingFut = Ready<Result<WaitingState>>;
+        fn send_ping(self) -> Self::SendPingFut {
+            ready(Ok(WaitingState))
+        }
     }
 
     struct WaitingState;
 
     impl Waiting for WaitingState {
         type HandlePingReplyFut = Ready<Result<End>>;
-        fn handle_ping(self, _msg: Ping) -> Self::HandlePingReplyFut {
+        fn handle_ping_reply(self, _msg: Ping) -> Self::HandlePingReplyFut {
             ready(Ok(End))
         }
     }