|
@@ -0,0 +1,837 @@
|
|
|
+//! Types for parsing the protocol grammar.
|
|
|
+
|
|
|
+use syn::{bracketed, parenthesized, parse::Parse, punctuated::Punctuated, token, Ident, Token};
|
|
|
+
|
|
|
+/// This type represents the top-level production for the protocol grammar.
|
|
|
+#[cfg_attr(test, derive(Debug, PartialEq))]
|
|
|
+pub(crate) struct Protocol {
|
|
|
+ pub(crate) name_def: NameDef,
|
|
|
+ pub(crate) states_def: StatesDef,
|
|
|
+ pub(crate) transitions: Punctuated<Transition, Token![;]>,
|
|
|
+}
|
|
|
+
|
|
|
+impl Parse for Protocol {
|
|
|
+ /// protocol : name_def states_def transition* ;
|
|
|
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
|
+ Ok(Protocol {
|
|
|
+ name_def: input.parse()?,
|
|
|
+ states_def: input.parse()?,
|
|
|
+ transitions: input.parse_terminated(Transition::parse, Token![;])?,
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg_attr(test, derive(Debug, PartialEq))]
|
|
|
+pub(crate) struct NameDef {
|
|
|
+ pub(crate) name: Ident,
|
|
|
+}
|
|
|
+
|
|
|
+impl NameDef {
|
|
|
+ const NAME_IDENT_ERR: &str = "invalid name declaration identifier";
|
|
|
+}
|
|
|
+
|
|
|
+impl Parse for NameDef {
|
|
|
+ /// name_def : "let" "name" '=' Ident ';' ;
|
|
|
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
|
+ input.parse::<Token![let]>()?;
|
|
|
+ if Ident::parse(input)? != "name" {
|
|
|
+ return Err(input.error(Self::NAME_IDENT_ERR));
|
|
|
+ }
|
|
|
+ input.parse::<Token![=]>()?;
|
|
|
+ let name = Ident::parse(input)?;
|
|
|
+ input.parse::<Token![;]>()?;
|
|
|
+ Ok(NameDef { name })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg_attr(test, derive(Debug, PartialEq))]
|
|
|
+pub(crate) struct StatesDef {
|
|
|
+ pub(crate) states: IdentArray,
|
|
|
+}
|
|
|
+
|
|
|
+impl StatesDef {
|
|
|
+ const ARRAY_IDENT_ERR: &str = "invalid states array identifier";
|
|
|
+}
|
|
|
+
|
|
|
+impl Parse for StatesDef {
|
|
|
+ /// states_def : "let" "states" '=' ident_array ';' ;
|
|
|
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
|
+ input.parse::<Token![let]>()?;
|
|
|
+ if Ident::parse(input)? != "states" {
|
|
|
+ return Err(input.error(Self::ARRAY_IDENT_ERR));
|
|
|
+ };
|
|
|
+ input.parse::<Token![=]>()?;
|
|
|
+ let ident_array = IdentArray::parse(input)?;
|
|
|
+ input.parse::<Token![;]>()?;
|
|
|
+ Ok(StatesDef {
|
|
|
+ states: ident_array,
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg_attr(test, derive(Debug, PartialEq))]
|
|
|
+pub(crate) struct IdentArray(Punctuated<Ident, Token![,]>);
|
|
|
+
|
|
|
+impl IdentArray {
|
|
|
+ const EMPTY_ERR: &str = "at least one state is required";
|
|
|
+
|
|
|
+ fn empty() -> Self {
|
|
|
+ Self(Punctuated::new())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Parse for IdentArray {
|
|
|
+ /// ident_array : '[' Ident ( ',' Ident )* ','? ']' ;
|
|
|
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
|
+ let content;
|
|
|
+ let bracket_token = bracketed!(content in input);
|
|
|
+ let states = content.parse_terminated(Ident::parse, Token![,])?;
|
|
|
+ if states.is_empty() {
|
|
|
+ return Err(syn::Error::new(bracket_token.span.open(), Self::EMPTY_ERR));
|
|
|
+ }
|
|
|
+ Ok(Self(states))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl AsRef<Punctuated<Ident, Token![,]>> for IdentArray {
|
|
|
+ fn as_ref(&self) -> &Punctuated<Ident, Token![,]> {
|
|
|
+ &self.0
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg_attr(test, derive(Debug, PartialEq))]
|
|
|
+pub(crate) struct Transition {
|
|
|
+ pub(crate) in_state: State,
|
|
|
+ pub(crate) in_msg: Option<Message>,
|
|
|
+ pub(crate) out_states: StatesList,
|
|
|
+ pub(crate) out_msgs: DestList,
|
|
|
+}
|
|
|
+
|
|
|
+impl Parse for Transition {
|
|
|
+ /// transition : state ( '?' message )? "->" states_list ( '>' dest_list )? ';' ;
|
|
|
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
|
+ let in_state = State::parse(input)?;
|
|
|
+ let in_msg = if input.parse::<Token![?]>().is_ok() {
|
|
|
+ Some(Message::parse(input)?)
|
|
|
+ } else {
|
|
|
+ None
|
|
|
+ };
|
|
|
+ input.parse::<Token![->]>()?;
|
|
|
+ let out_states = StatesList::parse(input)?;
|
|
|
+ let out_msgs = if input.parse::<Token![>]>().is_ok() {
|
|
|
+ DestList::parse(input)?
|
|
|
+ } else {
|
|
|
+ DestList::empty()
|
|
|
+ };
|
|
|
+ // Note that we must not eat the semicolon because the Punctuated parser expects it.
|
|
|
+ Ok(Self {
|
|
|
+ in_state,
|
|
|
+ in_msg,
|
|
|
+ out_states,
|
|
|
+ out_msgs,
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg_attr(test, derive(Debug, PartialEq))]
|
|
|
+pub(crate) struct State {
|
|
|
+ pub(crate) state_trait: Ident,
|
|
|
+ pub(crate) owned_states: IdentArray,
|
|
|
+}
|
|
|
+
|
|
|
+impl Parse for State {
|
|
|
+ /// state : Ident ident_array? ;
|
|
|
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
|
+ let state_trait = Ident::parse(input)?;
|
|
|
+ let owned_states = if input.peek(token::Bracket) {
|
|
|
+ IdentArray::parse(input)?
|
|
|
+ } else {
|
|
|
+ IdentArray::empty()
|
|
|
+ };
|
|
|
+ Ok(Self {
|
|
|
+ state_trait,
|
|
|
+ owned_states,
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg_attr(test, derive(Debug, PartialEq))]
|
|
|
+pub(crate) struct StatesList(Punctuated<State, Token![,]>);
|
|
|
+
|
|
|
+impl StatesList {
|
|
|
+ const EMPTY_ERR: &str = "at lest one out state is required";
|
|
|
+}
|
|
|
+
|
|
|
+impl Parse for StatesList {
|
|
|
+ /// states_list : state ( ',' state )* ','? ;
|
|
|
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
|
+ let mut states = Punctuated::new();
|
|
|
+ while !(input.peek(Token![>]) || input.peek(Token![;]) || input.cursor().eof()) {
|
|
|
+ states.push_value(input.parse()?);
|
|
|
+ if let Ok(comma) = input.parse::<Token![,]>() {
|
|
|
+ states.push_punct(comma);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if states.is_empty() {
|
|
|
+ return Err(input.error(Self::EMPTY_ERR));
|
|
|
+ }
|
|
|
+ Ok(Self(states))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl AsRef<Punctuated<State, Token![,]>> for StatesList {
|
|
|
+ fn as_ref(&self) -> &Punctuated<State, Token![,]> {
|
|
|
+ &self.0
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg_attr(test, derive(Debug, PartialEq))]
|
|
|
+pub(crate) struct DestList(Punctuated<Dest, Token![,]>);
|
|
|
+
|
|
|
+impl DestList {
|
|
|
+ fn empty() -> Self {
|
|
|
+ Self(Punctuated::new())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Parse for DestList {
|
|
|
+ /// dest_list : dest ( ',' dest )* ;
|
|
|
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
|
+ let mut dests = Punctuated::new();
|
|
|
+ while !(input.peek(Token![;]) || input.cursor().eof()) {
|
|
|
+ dests.push_value(input.parse()?);
|
|
|
+ if let Ok(comma) = input.parse::<Token![,]>() {
|
|
|
+ dests.push_punct(comma);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Ok(DestList(dests))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl AsRef<Punctuated<Dest, Token![,]>> for DestList {
|
|
|
+ fn as_ref(&self) -> &Punctuated<Dest, Token![,]> {
|
|
|
+ &self.0
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg_attr(test, derive(Debug, PartialEq))]
|
|
|
+pub(crate) struct Dest {
|
|
|
+ pub(crate) state: DestinationState,
|
|
|
+ pub(crate) msg: Message,
|
|
|
+}
|
|
|
+
|
|
|
+impl Parse for Dest {
|
|
|
+ /// dest : dest_state '!' message
|
|
|
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
|
+ let state = DestinationState::parse(input)?;
|
|
|
+ input.parse::<Token![!]>()?;
|
|
|
+ let msg = Message::parse(input)?;
|
|
|
+ Ok(Self { state, msg })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg_attr(test, derive(Debug, PartialEq))]
|
|
|
+pub(crate) enum DestinationState {
|
|
|
+ Service(Ident),
|
|
|
+ Individual(State),
|
|
|
+}
|
|
|
+
|
|
|
+impl DestinationState {
|
|
|
+ const NO_STATE_ERR: &str = "expected destination state";
|
|
|
+ const MULTI_STATE_ERR: &str = "only one destination state is allowed";
|
|
|
+}
|
|
|
+
|
|
|
+impl Parse for DestinationState {
|
|
|
+ /// dest_state : ( "service" '(' Ident ')' ) | state ;
|
|
|
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
|
+ let tester = input.fork();
|
|
|
+ let ident = Ident::parse(&tester)?;
|
|
|
+ if ident == "service" {
|
|
|
+ Ident::parse(input)?;
|
|
|
+ let content;
|
|
|
+ let paren_token = parenthesized!(content in input);
|
|
|
+ let mut dest_states = content
|
|
|
+ .parse_terminated(Ident::parse, Token![,])?
|
|
|
+ .into_iter();
|
|
|
+ let dest_state = dest_states
|
|
|
+ .next()
|
|
|
+ .ok_or(syn::Error::new(paren_token.span.open(), Self::NO_STATE_ERR))?;
|
|
|
+ if let Some(extra_dest) = dest_states.next() {
|
|
|
+ return Err(syn::Error::new(extra_dest.span(), Self::MULTI_STATE_ERR));
|
|
|
+ }
|
|
|
+ Ok(DestinationState::Service(dest_state))
|
|
|
+ } else {
|
|
|
+ Ok(DestinationState::Individual(input.parse()?))
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg_attr(test, derive(Debug, PartialEq))]
|
|
|
+pub(crate) struct Message {
|
|
|
+ pub(crate) msg_type: Ident,
|
|
|
+ pub(crate) is_reply: bool,
|
|
|
+ pub(crate) owned_states: IdentArray,
|
|
|
+}
|
|
|
+
|
|
|
+impl Message {
|
|
|
+ const REPLY_ERR: &str = "expected 'Reply'";
|
|
|
+}
|
|
|
+
|
|
|
+impl Parse for Message {
|
|
|
+ /// message : Ident ( "::" "Reply" )? ident_array? ;
|
|
|
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
|
+ let msg_type = Ident::parse(input)?;
|
|
|
+ let is_reply = input.peek(Token![::]);
|
|
|
+ if is_reply {
|
|
|
+ input.parse::<Token![::]>()?;
|
|
|
+ let reply = Ident::parse(input)?;
|
|
|
+ if reply != "Reply" {
|
|
|
+ return Err(syn::Error::new(reply.span(), Self::REPLY_ERR));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ let owned_states = if input.peek(token::Bracket) {
|
|
|
+ IdentArray::parse(input)?
|
|
|
+ } else {
|
|
|
+ IdentArray(Punctuated::new())
|
|
|
+ };
|
|
|
+ Ok(Self {
|
|
|
+ msg_type,
|
|
|
+ is_reply,
|
|
|
+ owned_states,
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg(test)]
|
|
|
+mod tests {
|
|
|
+ use super::*;
|
|
|
+ use proc_macro2::Span;
|
|
|
+ use std::iter::{self, IntoIterator};
|
|
|
+ use syn::parse_str;
|
|
|
+
|
|
|
+ fn ident(str: &str) -> Ident {
|
|
|
+ Ident::new(str, Span::call_site())
|
|
|
+ }
|
|
|
+
|
|
|
+ impl Protocol {
|
|
|
+ fn new(
|
|
|
+ name_def: NameDef,
|
|
|
+ states_def: StatesDef,
|
|
|
+ transitions: impl Iterator<Item = Transition>,
|
|
|
+ ) -> Self {
|
|
|
+ let mut transitions: Punctuated<Transition, Token![;]> = transitions.collect();
|
|
|
+ transitions.push_punct(Token));
|
|
|
+ Self {
|
|
|
+ name_def,
|
|
|
+ states_def,
|
|
|
+ transitions,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn protocol_parse_minimal() {
|
|
|
+ const EXPECTED_NAME: &str = "Foo";
|
|
|
+ const EXPECTED_STATES: [&str; 3] = ["First", "Second", "Third"];
|
|
|
+ let input = format!(
|
|
|
+ "let name = {EXPECTED_NAME};
|
|
|
+let states = [{}];
|
|
|
+{} -> {};
|
|
|
+{} -> {};",
|
|
|
+ EXPECTED_STATES.join(", "),
|
|
|
+ EXPECTED_STATES[0],
|
|
|
+ EXPECTED_STATES[1],
|
|
|
+ EXPECTED_STATES[1],
|
|
|
+ EXPECTED_STATES[2],
|
|
|
+ );
|
|
|
+ let expected = Protocol::new(
|
|
|
+ NameDef::new(EXPECTED_NAME),
|
|
|
+ StatesDef::new(EXPECTED_STATES.into_iter()),
|
|
|
+ [
|
|
|
+ Transition::new(
|
|
|
+ State::new(EXPECTED_STATES[0], iter::empty()),
|
|
|
+ None,
|
|
|
+ [State::new(EXPECTED_STATES[1], iter::empty())].into_iter(),
|
|
|
+ iter::empty(),
|
|
|
+ ),
|
|
|
+ Transition::new(
|
|
|
+ State::new(EXPECTED_STATES[1], iter::empty()),
|
|
|
+ None,
|
|
|
+ [State::new(EXPECTED_STATES[2], iter::empty())].into_iter(),
|
|
|
+ iter::empty(),
|
|
|
+ ),
|
|
|
+ ]
|
|
|
+ .into_iter(),
|
|
|
+ );
|
|
|
+
|
|
|
+ let actual = parse_str::<Protocol>(&input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ impl NameDef {
|
|
|
+ fn new(name: &str) -> Self {
|
|
|
+ Self { name: ident(name) }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn name_def_parse() {
|
|
|
+ const EXPECTED_NAME: &str = "Foofercorg";
|
|
|
+ let input = format!("let name = {EXPECTED_NAME};");
|
|
|
+ let expected = NameDef::new(EXPECTED_NAME);
|
|
|
+
|
|
|
+ let actual = parse_str::<NameDef>(&input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn name_def_wrong_ident_err() {
|
|
|
+ let result = parse_str::<NameDef>("let nam = Samson;");
|
|
|
+
|
|
|
+ assert!(result.is_err());
|
|
|
+ let err_str = result.err().unwrap().to_string();
|
|
|
+ assert_eq!(NameDef::NAME_IDENT_ERR, err_str);
|
|
|
+ }
|
|
|
+
|
|
|
+ impl StatesDef {
|
|
|
+ fn new(state_names: impl Iterator<Item = &'static str>) -> Self {
|
|
|
+ Self {
|
|
|
+ states: IdentArray::new(state_names),
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn states_def_parse() {
|
|
|
+ const EXPECTED_STATES: [&str; 2] = ["First", "Second"];
|
|
|
+ let input = format!("let states = [{}];", EXPECTED_STATES.join(", "));
|
|
|
+ let expected = StatesDef::new(EXPECTED_STATES.into_iter());
|
|
|
+
|
|
|
+ let actual = parse_str::<StatesDef>(&input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn states_def_parse_wrong_ident_err() {
|
|
|
+ let result = parse_str::<StatesDef>("let staties = [Alpha, Beta];");
|
|
|
+
|
|
|
+ assert!(result.is_err());
|
|
|
+ let err_str = result.err().unwrap().to_string();
|
|
|
+ assert_eq!(StatesDef::ARRAY_IDENT_ERR, err_str);
|
|
|
+ }
|
|
|
+
|
|
|
+ impl IdentArray {
|
|
|
+ fn new(state_names: impl Iterator<Item = &'static str>) -> Self {
|
|
|
+ Self(state_names.map(ident).collect())
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn ident_array_new() {
|
|
|
+ const EXPECTED: [&str; 2] = ["Red", "Green"];
|
|
|
+
|
|
|
+ let actual = IdentArray::new(EXPECTED.into_iter());
|
|
|
+
|
|
|
+ assert_eq!(EXPECTED.len(), actual.0.len());
|
|
|
+ assert_eq!(actual.0[0], EXPECTED[0]);
|
|
|
+ assert_eq!(actual.0[1], EXPECTED[1]);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn ident_array_not_equal() {
|
|
|
+ let expected = IdentArray::new(["Red", "Green"].into_iter());
|
|
|
+
|
|
|
+ let actual = IdentArray::new(["Blue", "Gold"].into_iter());
|
|
|
+
|
|
|
+ assert_ne!(expected, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn ident_array_not_equal_different_lens() {
|
|
|
+ let expected = IdentArray::new(["Red", "Green"].into_iter());
|
|
|
+
|
|
|
+ let actual = IdentArray::new(["Red"].into_iter());
|
|
|
+
|
|
|
+ assert_ne!(expected, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn ident_array_parse() {
|
|
|
+ const EXPECTED_STATES: [&str; 2] = ["Sad", "Glad"];
|
|
|
+ let input = format!("[{}, {}]", EXPECTED_STATES[0], EXPECTED_STATES[1]);
|
|
|
+ let expected = IdentArray::new(EXPECTED_STATES.into_iter());
|
|
|
+
|
|
|
+ let actual = parse_str::<IdentArray>(&input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn ident_array_parse_empty_err() {
|
|
|
+ let result = parse_str::<IdentArray>("[]");
|
|
|
+
|
|
|
+ assert!(result.is_err());
|
|
|
+ let err_str = result.err().unwrap().to_string();
|
|
|
+ assert_eq!(IdentArray::EMPTY_ERR, err_str);
|
|
|
+ }
|
|
|
+
|
|
|
+ impl Transition {
|
|
|
+ fn new(
|
|
|
+ in_state: State,
|
|
|
+ in_msg: Option<Message>,
|
|
|
+ out_states: impl Iterator<Item = State>,
|
|
|
+ out_msgs: impl Iterator<Item = Dest>,
|
|
|
+ ) -> Self {
|
|
|
+ Self {
|
|
|
+ in_state,
|
|
|
+ in_msg,
|
|
|
+ out_states: StatesList(out_states.collect()),
|
|
|
+ out_msgs: DestList(out_msgs.collect()),
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn transition_parse_minimal() {
|
|
|
+ const EXPECTED_IN_STATE: &str = "Catcher";
|
|
|
+ const EXPECTED_OUT_STATE: &str = "End";
|
|
|
+ let input = format!("{EXPECTED_IN_STATE} -> {EXPECTED_OUT_STATE}");
|
|
|
+ let expected = Transition::new(
|
|
|
+ State::new(EXPECTED_IN_STATE, iter::empty()),
|
|
|
+ None,
|
|
|
+ [EXPECTED_OUT_STATE].map(State::new_empty_owned).into_iter(),
|
|
|
+ iter::empty(),
|
|
|
+ );
|
|
|
+
|
|
|
+ let actual = parse_str::<Transition>(&input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn transition_parse_with_in_msg() {
|
|
|
+ const EXPECTED_IN_STATE: &str = "Catcher";
|
|
|
+ const EXPECTED_IN_MSG: &str = "Msg";
|
|
|
+ const EXPECTED_OUT_STATE: &str = "End";
|
|
|
+ let input = format!("{EXPECTED_IN_STATE}?{EXPECTED_IN_MSG} -> {EXPECTED_OUT_STATE}");
|
|
|
+ let expected = Transition::new(
|
|
|
+ State::new(EXPECTED_IN_STATE, iter::empty()),
|
|
|
+ Some(Message::new(EXPECTED_IN_MSG, false, iter::empty())),
|
|
|
+ [EXPECTED_OUT_STATE].map(State::new_empty_owned).into_iter(),
|
|
|
+ iter::empty(),
|
|
|
+ );
|
|
|
+
|
|
|
+ let actual = parse_str::<Transition>(&input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn transition_parse_with_in_msg_reply() {
|
|
|
+ const EXPECTED_IN_STATE: &str = "Catcher";
|
|
|
+ const EXPECTED_IN_MSG: &str = "Msg";
|
|
|
+ const EXPECTED_OUT_STATE: &str = "End";
|
|
|
+ let input = format!("{EXPECTED_IN_STATE}?{EXPECTED_IN_MSG}::Reply -> {EXPECTED_OUT_STATE}");
|
|
|
+ let expected = Transition::new(
|
|
|
+ State::new(EXPECTED_IN_STATE, iter::empty()),
|
|
|
+ Some(Message::new(EXPECTED_IN_MSG, true, iter::empty())),
|
|
|
+ [EXPECTED_OUT_STATE].map(State::new_empty_owned).into_iter(),
|
|
|
+ iter::empty(),
|
|
|
+ );
|
|
|
+
|
|
|
+ let actual = parse_str::<Transition>(&input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn transition_parse_with_in_msg_reply_owned_states() {
|
|
|
+ const EXPECTED_IN_STATE: &str = "Catcher";
|
|
|
+ const EXPECTED_IN_MSG: &str = "Msg";
|
|
|
+ const EXPECTED_OWNED_STATES: [&str; 2] = ["First", "Second"];
|
|
|
+ const EXPECTED_OUT_STATE: &str = "End";
|
|
|
+ let input = format!(
|
|
|
+ "{EXPECTED_IN_STATE}?{EXPECTED_IN_MSG}::Reply[{}, {}] -> {EXPECTED_OUT_STATE}",
|
|
|
+ EXPECTED_OWNED_STATES[0], EXPECTED_OWNED_STATES[1]
|
|
|
+ );
|
|
|
+ let expected = Transition::new(
|
|
|
+ State::new(EXPECTED_IN_STATE, iter::empty()),
|
|
|
+ Some(Message::new(
|
|
|
+ EXPECTED_IN_MSG,
|
|
|
+ true,
|
|
|
+ EXPECTED_OWNED_STATES.into_iter(),
|
|
|
+ )),
|
|
|
+ [EXPECTED_OUT_STATE].map(State::new_empty_owned).into_iter(),
|
|
|
+ iter::empty(),
|
|
|
+ );
|
|
|
+
|
|
|
+ let actual = parse_str::<Transition>(&input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn transition_parse_has_single_dest() {
|
|
|
+ const EXPECTED_IN_STATE: &str = "Catcher";
|
|
|
+ const EXPECTED_OUT_STATE: &str = "End";
|
|
|
+ const EXPECTED_DEST_STATE: &str = "Target";
|
|
|
+ const EXPECTED_DEST_MSG: &str = "DeathNote";
|
|
|
+ let input = format!("{EXPECTED_IN_STATE} -> {EXPECTED_OUT_STATE} >{EXPECTED_DEST_STATE}!{EXPECTED_DEST_MSG}");
|
|
|
+ let expected = Transition::new(
|
|
|
+ State::new(EXPECTED_IN_STATE, iter::empty()),
|
|
|
+ None,
|
|
|
+ [EXPECTED_OUT_STATE].map(State::new_empty_owned).into_iter(),
|
|
|
+ [Dest::new(
|
|
|
+ DestinationState::Individual(State::new(EXPECTED_DEST_STATE, iter::empty())),
|
|
|
+ Message::new(EXPECTED_DEST_MSG, false, iter::empty()),
|
|
|
+ )]
|
|
|
+ .into_iter(),
|
|
|
+ );
|
|
|
+
|
|
|
+ let actual = parse_str::<Transition>(&input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn transition_parse_multiple_out_states_and_out_msgs() {
|
|
|
+ const EXPECTED_IN_STATE: &str = "Catcher";
|
|
|
+ const EXPECTED_IN_MSG: &str = "Tap";
|
|
|
+ const EXPECTED_OUT_STATES: [&str; 2] = ["Out0", "Out1"];
|
|
|
+ const EXPECTED_DESTS: [(&str, &str); 2] = [("Dest0", "Msg0"), ("Dest1", "Msg1")];
|
|
|
+ let states_list = EXPECTED_OUT_STATES.join(", ");
|
|
|
+ let dest_list = EXPECTED_DESTS.map(|(l, r)| format!("{l}!{r}")).join(", ");
|
|
|
+ let input = format!("{EXPECTED_IN_STATE}?{EXPECTED_IN_MSG} -> {states_list} >{dest_list}");
|
|
|
+ let expected = Transition::new(
|
|
|
+ State::new(EXPECTED_IN_STATE, iter::empty()),
|
|
|
+ Some(Message::new(EXPECTED_IN_MSG, false, iter::empty())),
|
|
|
+ EXPECTED_OUT_STATES.map(State::new_empty_owned).into_iter(),
|
|
|
+ EXPECTED_DESTS
|
|
|
+ .map(|(l, r)| {
|
|
|
+ Dest::new(
|
|
|
+ DestinationState::Individual(State::new(l, iter::empty())),
|
|
|
+ Message::new(r, false, iter::empty()),
|
|
|
+ )
|
|
|
+ })
|
|
|
+ .into_iter(),
|
|
|
+ );
|
|
|
+
|
|
|
+ let actual = parse_str::<Transition>(&input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ impl State {
|
|
|
+ fn new(state_trait: &str, owned_states: impl Iterator<Item = &'static str>) -> Self {
|
|
|
+ Self {
|
|
|
+ state_trait: ident(state_trait),
|
|
|
+ owned_states: IdentArray::new(owned_states),
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn new_empty_owned(state_trait: &str) -> Self {
|
|
|
+ Self::new(state_trait, iter::empty())
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn state_parse_no_owned_states() {
|
|
|
+ const EXPECTED_TRAIT: &str = "Contained";
|
|
|
+ let expected = State::new(EXPECTED_TRAIT, iter::empty());
|
|
|
+
|
|
|
+ let actual = parse_str::<State>(EXPECTED_TRAIT).unwrap();
|
|
|
+
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn state_parse_has_owned_states() {
|
|
|
+ const EXPECTED_TRAIT: &str = "Contained";
|
|
|
+ const EXPECTED_OWNED: [&str; 2] = ["First", "Second"];
|
|
|
+ let input = format!(
|
|
|
+ "{EXPECTED_TRAIT}[{}, {}]",
|
|
|
+ EXPECTED_OWNED[0], EXPECTED_OWNED[1]
|
|
|
+ );
|
|
|
+ let expected = State::new(EXPECTED_TRAIT, EXPECTED_OWNED.into_iter());
|
|
|
+
|
|
|
+ let actual = parse_str::<State>(&input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ impl Dest {
|
|
|
+ fn new(state: DestinationState, msg: Message) -> Self {
|
|
|
+ Self { state, msg }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn dest_parse() {
|
|
|
+ const EXPECTED_STATE: &str = "Opened";
|
|
|
+ const EXPECTED_MSG: &str = "Read";
|
|
|
+ let input = format!("{EXPECTED_STATE}!{EXPECTED_MSG}");
|
|
|
+ let expected = Dest::new(
|
|
|
+ DestinationState::Individual(State::new(EXPECTED_STATE, iter::empty())),
|
|
|
+ Message::new(EXPECTED_MSG, false, iter::empty()),
|
|
|
+ );
|
|
|
+
|
|
|
+ let actual = parse_str::<Dest>(&input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn destination_state_parse_regular() {
|
|
|
+ const EXPECTED_DEST_STATE: &str = "Listening";
|
|
|
+ let expected = DestinationState::Individual(State::new(EXPECTED_DEST_STATE, iter::empty()));
|
|
|
+
|
|
|
+ let actual = parse_str::<DestinationState>(EXPECTED_DEST_STATE).unwrap();
|
|
|
+
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn destination_state_parse_owned_states() {
|
|
|
+ const EXPECTED_DEST_STATE: &str = "Listening";
|
|
|
+ const EXPECTED_OWNED_STATES: [&str; 2] = ["First", "Second"];
|
|
|
+ let input = format!(
|
|
|
+ "{EXPECTED_DEST_STATE}[{}]",
|
|
|
+ EXPECTED_OWNED_STATES.join(", ")
|
|
|
+ );
|
|
|
+ let expected = DestinationState::Individual(State::new(
|
|
|
+ EXPECTED_DEST_STATE,
|
|
|
+ EXPECTED_OWNED_STATES.into_iter(),
|
|
|
+ ));
|
|
|
+
|
|
|
+ let actual = parse_str::<DestinationState>(&input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn destination_state_parse_service() {
|
|
|
+ const EXPECTED_DEST_STATE: &str = "Listening";
|
|
|
+ let expected =
|
|
|
+ DestinationState::Service(Ident::new(EXPECTED_DEST_STATE, Span::call_site()));
|
|
|
+ let input = format!("service({EXPECTED_DEST_STATE})");
|
|
|
+
|
|
|
+ let actual = parse_str::<DestinationState>(&input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn destination_state_parse_service_no_state_err() {
|
|
|
+ let result = parse_str::<DestinationState>("service()");
|
|
|
+
|
|
|
+ assert!(result.is_err());
|
|
|
+ let err_str = result.err().unwrap().to_string();
|
|
|
+ assert_eq!(DestinationState::NO_STATE_ERR, err_str);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn destination_state_parse_service_multi_state_err() {
|
|
|
+ let result = parse_str::<DestinationState>("service(Left, Right)");
|
|
|
+
|
|
|
+ assert!(result.is_err());
|
|
|
+ let err_str = result.err().unwrap().to_string();
|
|
|
+ assert_eq!(DestinationState::MULTI_STATE_ERR, err_str);
|
|
|
+ }
|
|
|
+
|
|
|
+ impl Message {
|
|
|
+ fn new(
|
|
|
+ msg_type: &str,
|
|
|
+ is_reply: bool,
|
|
|
+ owned_states: impl Iterator<Item = &'static str>,
|
|
|
+ ) -> Self {
|
|
|
+ Self {
|
|
|
+ msg_type: ident(msg_type),
|
|
|
+ is_reply,
|
|
|
+ owned_states: IdentArray::new(owned_states),
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn message_new() {
|
|
|
+ const EXPECTED_MSG_TYPE: &str = "Orders";
|
|
|
+ const EXPECTED_IS_REPLY: bool = true;
|
|
|
+ const EXPECTED_OWNED_STATES: [&str; 2] = ["First", "Second"];
|
|
|
+
|
|
|
+ let actual = Message::new(
|
|
|
+ EXPECTED_MSG_TYPE,
|
|
|
+ EXPECTED_IS_REPLY,
|
|
|
+ EXPECTED_OWNED_STATES.into_iter(),
|
|
|
+ );
|
|
|
+
|
|
|
+ assert_eq!(actual.msg_type, EXPECTED_MSG_TYPE);
|
|
|
+ assert_eq!(actual.is_reply, EXPECTED_IS_REPLY);
|
|
|
+ assert_eq!(actual.owned_states.0.len(), EXPECTED_OWNED_STATES.len());
|
|
|
+ assert_eq!(actual.owned_states.0[0], EXPECTED_OWNED_STATES[0]);
|
|
|
+ assert_eq!(actual.owned_states.0[1], EXPECTED_OWNED_STATES[1]);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn message_parse_regular() {
|
|
|
+ const EXPECTED_MSG: &str = "Write";
|
|
|
+ let expected = Message::new(EXPECTED_MSG, false, iter::empty());
|
|
|
+
|
|
|
+ let actual = parse_str::<Message>(EXPECTED_MSG).unwrap();
|
|
|
+
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn message_parse_reply() {
|
|
|
+ const EXPECTED_MSG: &str = "ReadAttr";
|
|
|
+ let input = format!("{EXPECTED_MSG}::Reply");
|
|
|
+ let expected = Message::new(EXPECTED_MSG, true, iter::empty());
|
|
|
+
|
|
|
+ let actual = parse_str::<Message>(&input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn message_parse_reply_err() {
|
|
|
+ const EXPECTED_MSG: &str = "ReadAttr";
|
|
|
+ // Notice the intentional typo: "Rely" instead of "Reply".
|
|
|
+ let input = format!("{EXPECTED_MSG}::Rely");
|
|
|
+
|
|
|
+ let result = parse_str::<Message>(&input);
|
|
|
+
|
|
|
+ assert!(result.is_err());
|
|
|
+ let err_str = result.err().unwrap().to_string();
|
|
|
+ assert_eq!(Message::REPLY_ERR, err_str);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn message_parse_owned_states_not_reply() {
|
|
|
+ const EXPECTED_MSG: &str = "Open";
|
|
|
+ const OWNED_STATES: [&str; 2] = ["Handle0", "Handle1"];
|
|
|
+ let input = format!("{EXPECTED_MSG}[{}, {}]", OWNED_STATES[0], OWNED_STATES[1]);
|
|
|
+ let expected = Message::new(EXPECTED_MSG, false, OWNED_STATES.into_iter());
|
|
|
+
|
|
|
+ let actual = parse_str::<Message>(&input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn message_parse_owned_states_is_reply() {
|
|
|
+ const EXPECTED_MSG: &str = "Open";
|
|
|
+ const OWNED_STATES: [&str; 2] = ["Handle0", "Handle1"];
|
|
|
+ let input = format!(
|
|
|
+ "{EXPECTED_MSG}::Reply[{}, {}]",
|
|
|
+ OWNED_STATES[0], OWNED_STATES[1]
|
|
|
+ );
|
|
|
+ let expected = Message::new(EXPECTED_MSG, true, OWNED_STATES.into_iter());
|
|
|
+
|
|
|
+ let actual = parse_str::<Message>(&input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ }
|
|
|
+}
|