|
@@ -1,5 +1,5 @@
|
|
|
use std::{
|
|
|
- collections::{HashMap, HashSet},
|
|
|
+ collections::{HashMap, HashSet, LinkedList},
|
|
|
hash::Hash,
|
|
|
rc::Rc,
|
|
|
};
|
|
@@ -11,8 +11,8 @@ use quote::{format_ident, quote, ToTokens};
|
|
|
use crate::{
|
|
|
case_convert::CaseConvert,
|
|
|
error,
|
|
|
- parsing::MessageReplyPart,
|
|
|
parsing::{ActorDef, Dest, GetSpan, Message, Protocol, State, Transition},
|
|
|
+ parsing::{DestinationState, MessageReplyPart},
|
|
|
};
|
|
|
|
|
|
pub(crate) struct ProtocolModel {
|
|
@@ -20,6 +20,28 @@ pub(crate) struct ProtocolModel {
|
|
|
msg_lookup: MsgLookup,
|
|
|
actor_lookup: ActorLookup,
|
|
|
actors: HashMap<Rc<Ident>, ActorModel>,
|
|
|
+ /// The name of the message enum used by this protocol.
|
|
|
+ msg_enum_name: Ident,
|
|
|
+ /// The name of the message kinds enum used by this protocol.
|
|
|
+ msg_enum_kinds_name: Ident,
|
|
|
+ /// The [Ident] containing [End::ident].
|
|
|
+ end_ident: Ident,
|
|
|
+ /// The name of the [btrun::Mailbox] parameter in an actor closure.
|
|
|
+ mailbox_param: Ident,
|
|
|
+ /// The name of the [btrun::ActorId] parameter in an actor closure.
|
|
|
+ actor_id_param: Ident,
|
|
|
+ /// The name of the `&'static `[Runtime] parameter in an actor closure.
|
|
|
+ runtime_param: Ident,
|
|
|
+ /// The name of the variable used to hold the `msg` field from a [btrun::Envelope].
|
|
|
+ msg_ident: Ident,
|
|
|
+ /// The name of the variable used to hold the `reply` field from a [btrun::Envelope].
|
|
|
+ reply_ident: Ident,
|
|
|
+ /// The name of the variable used to hold the `from` field of a [btrun::Envelope].
|
|
|
+ from_ident: Ident,
|
|
|
+ /// The name of the local variable in an actor closure used to hold the actor's name.
|
|
|
+ actor_name_ident: Ident,
|
|
|
+ /// The identifier for the variable holding the initial state of an actor.
|
|
|
+ init_state_var: Ident,
|
|
|
}
|
|
|
|
|
|
impl ProtocolModel {
|
|
@@ -79,22 +101,43 @@ impl ProtocolModel {
|
|
|
.cloned();
|
|
|
(state_name.clone(), transitions)
|
|
|
});
|
|
|
- let actor = ActorModel::new(
|
|
|
- actor_def.clone(),
|
|
|
- &msg_lookup,
|
|
|
- *is_client.get(&actor_def.actor).unwrap(),
|
|
|
- transitions_by_state,
|
|
|
- )?;
|
|
|
+ let is_client = *is_client.get(&actor_def.actor).unwrap();
|
|
|
+ let is_service = actor_lookup.service_providers.contains(&actor_def.actor);
|
|
|
+ let kind = ActorKind::new(is_client, is_service).ok_or_else(|| {
|
|
|
+ syn::Error::new(actor_def.actor.span(), error::msgs::CLIENT_USED_IN_SERVICE)
|
|
|
+ })?;
|
|
|
+ let actor =
|
|
|
+ ActorModel::new(actor_def.clone(), &msg_lookup, kind, transitions_by_state)?;
|
|
|
actors.insert(actor_name.clone(), actor);
|
|
|
}
|
|
|
+
|
|
|
Ok(Self {
|
|
|
+ msg_enum_name: Self::make_msg_enum_name(&def),
|
|
|
+ msg_enum_kinds_name: Self::make_msg_enum_kinds_name(&def),
|
|
|
def,
|
|
|
msg_lookup,
|
|
|
actor_lookup,
|
|
|
actors,
|
|
|
+ end_ident: format_ident!("{}", End::ident()),
|
|
|
+ mailbox_param: format_ident!("mailbox"),
|
|
|
+ actor_id_param: format_ident!("actor_id"),
|
|
|
+ runtime_param: format_ident!("runtime"),
|
|
|
+ msg_ident: format_ident!("msg"),
|
|
|
+ reply_ident: format_ident!("reply"),
|
|
|
+ from_ident: format_ident!("from"),
|
|
|
+ actor_name_ident: format_ident!("actor_name"),
|
|
|
+ init_state_var: format_ident!("init"),
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+ fn make_msg_enum_name(def: &Protocol) -> Ident {
|
|
|
+ format_ident!("{}Msgs", def.name_def.name)
|
|
|
+ }
|
|
|
+
|
|
|
+ fn make_msg_enum_kinds_name(def: &Protocol) -> Ident {
|
|
|
+ format_ident!("{}MsgKinds", def.name_def.name)
|
|
|
+ }
|
|
|
+
|
|
|
pub(crate) fn def(&self) -> &Protocol {
|
|
|
&self.def
|
|
|
}
|
|
@@ -107,6 +150,10 @@ impl ProtocolModel {
|
|
|
&self.actor_lookup
|
|
|
}
|
|
|
|
|
|
+ pub(crate) fn actors(&self) -> &HashMap<Rc<Ident>, ActorModel> {
|
|
|
+ &self.actors
|
|
|
+ }
|
|
|
+
|
|
|
pub(crate) fn actors_iter(&self) -> impl Iterator<Item = &ActorModel> {
|
|
|
self.actors.values()
|
|
|
}
|
|
@@ -126,25 +173,160 @@ impl ProtocolModel {
|
|
|
self.methods_iter()
|
|
|
.flat_map(|method| method.outputs().iter())
|
|
|
}
|
|
|
+
|
|
|
+ pub(crate) fn msg_enum_ident(&self) -> &Ident {
|
|
|
+ &self.msg_enum_name
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn msg_enum_kinds_ident(&self) -> &Ident {
|
|
|
+ &self.msg_enum_kinds_name
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn end_ident(&self) -> &Ident {
|
|
|
+ &self.end_ident
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn mailbox_param(&self) -> &Ident {
|
|
|
+ &self.mailbox_param
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn actor_id_param(&self) -> &Ident {
|
|
|
+ &self.actor_id_param
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn runtime_param(&self) -> &Ident {
|
|
|
+ &self.runtime_param
|
|
|
+ }
|
|
|
+
|
|
|
+ #[allow(clippy::wrong_self_convention)]
|
|
|
+ pub(crate) fn from_ident(&self) -> &Ident {
|
|
|
+ &self.from_ident
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn reply_ident(&self) -> &Ident {
|
|
|
+ &self.reply_ident
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn msg_ident(&self) -> &Ident {
|
|
|
+ &self.msg_ident
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn actor_name_ident(&self) -> &Ident {
|
|
|
+ &self.actor_name_ident
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn init_state_var(&self) -> &Ident {
|
|
|
+ &self.init_state_var
|
|
|
+ }
|
|
|
+
|
|
|
+ fn get_actor<'a>(&'a self, actor_name: &Ident) -> &'a ActorModel {
|
|
|
+ self.actors
|
|
|
+ .get(actor_name)
|
|
|
+ .unwrap_or_else(|| panic!("Invalid actor name: '{actor_name}'"))
|
|
|
+ }
|
|
|
+
|
|
|
+ fn get_state<'a>(&'a self, state_name: &Ident) -> &'a StateModel {
|
|
|
+ let actor_name = self.actor_lookup().actor_with_state(state_name);
|
|
|
+ let actor = self.get_actor(actor_name);
|
|
|
+ actor
|
|
|
+ .states()
|
|
|
+ .get(state_name)
|
|
|
+ .unwrap_or_else(|| panic!("Actor {actor_name} doesn't contain state {state_name}."))
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Returns a struct with the type params and their associated constraints for the given actor's
|
|
|
+ /// spawn function.
|
|
|
+ pub(crate) fn type_param_info_for<'a>(&'a self, actor_name: &Ident) -> TypeParamInfo<'a> {
|
|
|
+ let mut type_params = Vec::<&Ident>::new();
|
|
|
+ let mut constraints = Vec::<TokenStream>::new();
|
|
|
+ // We do a breadth-first traversal over the associated types referenced by this actor,
|
|
|
+ // starting with it's initial state.
|
|
|
+ let mut visited = HashSet::<&Ident>::new();
|
|
|
+ let mut queue = LinkedList::<&Ident>::new();
|
|
|
+ queue.push_front(self.get_actor(actor_name).init_state().name());
|
|
|
+ while !queue.is_empty() {
|
|
|
+ let state_name = queue.pop_back().unwrap();
|
|
|
+ visited.insert(state_name);
|
|
|
+ let state = self.get_state(state_name);
|
|
|
+ let mut any = false;
|
|
|
+ let eqs = state
|
|
|
+ .out_states_and_assoc_types()
|
|
|
+ .map(|(out_state, assoc_type)| {
|
|
|
+ any = true;
|
|
|
+ if !visited.contains(out_state) {
|
|
|
+ queue.push_front(out_state);
|
|
|
+ }
|
|
|
+ let type_param = self.get_state(out_state).type_param();
|
|
|
+ quote! { #assoc_type = #type_param }
|
|
|
+ });
|
|
|
+ let constraint_list = quote! { #( #eqs ),* };
|
|
|
+ let constraint = if any {
|
|
|
+ quote! { < #constraint_list > }
|
|
|
+ } else {
|
|
|
+ constraint_list
|
|
|
+ };
|
|
|
+ let type_param = state.type_param();
|
|
|
+ type_params.push(type_param);
|
|
|
+ let state_name = state.name();
|
|
|
+ let constraint = quote! { #type_param: 'static + #state_name #constraint };
|
|
|
+ constraints.push(constraint);
|
|
|
+ }
|
|
|
+ TypeParamInfo {
|
|
|
+ constraints,
|
|
|
+ type_params,
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-pub(crate) struct ActorModel {
|
|
|
- #[allow(dead_code)]
|
|
|
- def: Rc<ActorDef>,
|
|
|
- /// Indicates if this actor is a client.
|
|
|
- ///
|
|
|
+pub(crate) struct TypeParamInfo<'a> {
|
|
|
+ pub(crate) type_params: Vec<&'a Ident>,
|
|
|
+ pub(crate) constraints: Vec<TokenStream>,
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Clone, Debug)]
|
|
|
+/// A categorization of different actor types based on their messaging behavior.
|
|
|
+pub(crate) enum ActorKind {
|
|
|
/// A client is an actor which is not spawned by another actor (i.e. has no parents) and
|
|
|
/// whose initial state has no transition which receives a message, or which is spawned by a
|
|
|
/// client.
|
|
|
- is_client: bool,
|
|
|
+ Client,
|
|
|
+ /// Any actor with a state that appears wrapped in `service()` is in this category.
|
|
|
+ Service,
|
|
|
+ /// Any actor not in either of the other two categories falls into this one.
|
|
|
+ Worker,
|
|
|
+}
|
|
|
+
|
|
|
+impl ActorKind {
|
|
|
+ fn new(is_client: bool, is_service: bool) -> Option<Self> {
|
|
|
+ match (is_client, is_service) {
|
|
|
+ (true, false) => Some(Self::Client),
|
|
|
+ (false, true) => Some(Self::Service),
|
|
|
+ (false, false) => Some(Self::Worker),
|
|
|
+ (true, true) => None,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn is_client(&self) -> bool {
|
|
|
+ matches!(self, Self::Client)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Copy for ActorKind {}
|
|
|
+
|
|
|
+pub(crate) struct ActorModel {
|
|
|
+ #[allow(dead_code)]
|
|
|
+ def: Rc<ActorDef>,
|
|
|
+ kind: ActorKind,
|
|
|
+ state_enum_ident: Ident,
|
|
|
states: HashMap<Rc<Ident>, StateModel>,
|
|
|
+ spawn_function_ident: Option<Ident>,
|
|
|
}
|
|
|
|
|
|
impl ActorModel {
|
|
|
fn new<S, T>(
|
|
|
def: Rc<ActorDef>,
|
|
|
messages: &MsgLookup,
|
|
|
- is_client: bool,
|
|
|
+ kind: ActorKind,
|
|
|
state_iter: S,
|
|
|
) -> syn::Result<Self>
|
|
|
where
|
|
@@ -156,6 +338,7 @@ impl ActorModel {
|
|
|
.map(|(name, transitions)| (name, transitions.into_iter().collect()))
|
|
|
.collect();
|
|
|
let mut states = HashMap::new();
|
|
|
+ let is_client = matches!(kind, ActorKind::Client);
|
|
|
for (name, transitions) in transitions.into_iter() {
|
|
|
let state = StateModel::new(name.clone(), messages, transitions, is_client)?;
|
|
|
if let Some(prev) = states.insert(name, state) {
|
|
@@ -165,25 +348,66 @@ impl ActorModel {
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ let actor_name = def.actor.as_ref();
|
|
|
Ok(Self {
|
|
|
+ state_enum_ident: Self::make_state_enum_ident(actor_name),
|
|
|
+ spawn_function_ident: Self::make_spawn_function_ident(kind, actor_name),
|
|
|
def,
|
|
|
- is_client,
|
|
|
+ kind,
|
|
|
states,
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- pub(crate) fn is_client(&self) -> bool {
|
|
|
- self.is_client
|
|
|
+ fn make_state_enum_ident(actor_name: &Ident) -> Ident {
|
|
|
+ format_ident!("{}State", actor_name.to_string().snake_to_pascal())
|
|
|
+ }
|
|
|
+
|
|
|
+ fn make_spawn_function_ident(kind: ActorKind, actor_name: &Ident) -> Option<Ident> {
|
|
|
+ // Services are registered, not spawned, so they have no spawn function.
|
|
|
+ if let ActorKind::Service = kind {
|
|
|
+ None
|
|
|
+ } else {
|
|
|
+ Some(format_ident!("spawn_{actor_name}"))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn def(&self) -> &ActorDef {
|
|
|
+ &self.def
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn name(&self) -> &Ident {
|
|
|
+ &self.def.actor
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn kind(&self) -> ActorKind {
|
|
|
+ self.kind
|
|
|
}
|
|
|
|
|
|
pub(crate) fn states(&self) -> &HashMap<Rc<Ident>, StateModel> {
|
|
|
&self.states
|
|
|
}
|
|
|
+
|
|
|
+ pub(crate) fn init_state(&self) -> &StateModel {
|
|
|
+ // It's a syntax error to have an IdentArray with no states in it, so this unwrap
|
|
|
+ // shouldn't panic.
|
|
|
+ let init = self.def.states.as_ref().first().unwrap();
|
|
|
+ self.states.get(init).unwrap()
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn state_enum_ident(&self) -> &Ident {
|
|
|
+ &self.state_enum_ident
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn spawn_function_ident(&self) -> Option<&Ident> {
|
|
|
+ self.spawn_function_ident.as_ref()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
pub(crate) struct StateModel {
|
|
|
name: Rc<Ident>,
|
|
|
methods: HashMap<Rc<Ident>, MethodModel>,
|
|
|
+ type_param: Ident,
|
|
|
}
|
|
|
|
|
|
impl StateModel {
|
|
@@ -207,7 +431,15 @@ impl StateModel {
|
|
|
));
|
|
|
}
|
|
|
}
|
|
|
- Ok(Self { name, methods })
|
|
|
+ Ok(Self {
|
|
|
+ type_param: Self::make_generic_type_param(name.as_ref()),
|
|
|
+ name,
|
|
|
+ methods,
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ fn make_generic_type_param(name: &Ident) -> Ident {
|
|
|
+ format_ident!("T{name}")
|
|
|
}
|
|
|
|
|
|
pub(crate) fn name(&self) -> &Ident {
|
|
@@ -217,6 +449,22 @@ impl StateModel {
|
|
|
pub(crate) fn methods(&self) -> &HashMap<Rc<Ident>, MethodModel> {
|
|
|
&self.methods
|
|
|
}
|
|
|
+
|
|
|
+ pub(crate) fn type_param(&self) -> &Ident {
|
|
|
+ &self.type_param
|
|
|
+ }
|
|
|
+
|
|
|
+ fn out_states_and_assoc_types(&self) -> impl Iterator<Item = (&Ident, &Ident)> {
|
|
|
+ self.methods().values().flat_map(|method| {
|
|
|
+ method.outputs().iter().flat_map(|output| {
|
|
|
+ output
|
|
|
+ .kind()
|
|
|
+ .state_trait()
|
|
|
+ .map(|ptr| ptr.as_ref())
|
|
|
+ .zip(output.assoc_type())
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
impl GetSpan for StateModel {
|
|
@@ -272,7 +520,7 @@ impl MethodModel {
|
|
|
|
|
|
fn new_inputs(def: &Transition, messages: &MsgLookup, part_of_client: bool) -> Vec<InputModel> {
|
|
|
let mut inputs = Vec::new();
|
|
|
- let arg_kind = if def.is_client() {
|
|
|
+ let arg_kind = if def.not_receiving() {
|
|
|
InputKind::ByMutRef
|
|
|
} else {
|
|
|
InputKind::ByValue
|
|
@@ -298,6 +546,22 @@ impl MethodModel {
|
|
|
inputs
|
|
|
}
|
|
|
|
|
|
+ /// Returns the input associated with the message this method is handling, or [None] if this
|
|
|
+ /// method is not handling a message.
|
|
|
+ pub(crate) fn msg_received_input(&self) -> Option<&InputModel> {
|
|
|
+ if self.def.in_msg().is_some() {
|
|
|
+ let input_model = self.inputs().get(0).unwrap_or_else(|| {
|
|
|
+ panic!(
|
|
|
+ "Method {} had no inputs despite handling a message.",
|
|
|
+ self.name()
|
|
|
+ )
|
|
|
+ });
|
|
|
+ Some(input_model)
|
|
|
+ } else {
|
|
|
+ None
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
fn new_outputs(
|
|
|
def: &Transition,
|
|
|
type_prefix: &str,
|
|
@@ -365,19 +629,26 @@ impl Copy for InputKind {}
|
|
|
#[cfg_attr(test, derive(Debug))]
|
|
|
pub(crate) struct InputModel {
|
|
|
name: Ident,
|
|
|
+ msg_name: Rc<Ident>,
|
|
|
arg_type: Rc<TokenStream>,
|
|
|
arg_kind: InputKind,
|
|
|
}
|
|
|
|
|
|
impl InputModel {
|
|
|
- fn new(type_name: Rc<Ident>, arg_type: Rc<TokenStream>, arg_kind: InputKind) -> Self {
|
|
|
- let name = format_ident!("{}_arg", type_name.to_string().pascal_to_snake());
|
|
|
+ fn new(msg_name: Rc<Ident>, arg_type: Rc<TokenStream>, arg_kind: InputKind) -> Self {
|
|
|
+ let name = format_ident!("{}_arg", msg_name.pascal_to_snake());
|
|
|
Self {
|
|
|
name,
|
|
|
+ msg_name,
|
|
|
arg_type,
|
|
|
arg_kind,
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /// Returns the name of the message this input is for.
|
|
|
+ pub(crate) fn msg_name(&self) -> &Ident {
|
|
|
+ self.msg_name.as_ref()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
impl ToTokens for InputModel {
|
|
@@ -394,25 +665,27 @@ impl ToTokens for InputModel {
|
|
|
|
|
|
#[cfg_attr(test, derive(Debug))]
|
|
|
pub(crate) struct OutputModel {
|
|
|
+ kind: OutputKind,
|
|
|
type_name: Option<TokenStream>,
|
|
|
+ assoc_type: Option<Ident>,
|
|
|
decl: Option<TokenStream>,
|
|
|
- #[allow(dead_code)]
|
|
|
- kind: OutputKind,
|
|
|
+ var_name: Ident,
|
|
|
}
|
|
|
|
|
|
impl OutputModel {
|
|
|
fn new(kind: OutputKind, type_prefix: &str) -> Self {
|
|
|
- let (decl, type_name) = match &kind {
|
|
|
+ let (decl, type_name, assoc_type) = match &kind {
|
|
|
OutputKind::State { def, .. } => {
|
|
|
let state_trait = def.state_trait.as_ref();
|
|
|
if state_trait == End::ident() {
|
|
|
let end_ident = format_ident!("{}", End::ident());
|
|
|
- (None, Some(quote! { ::btrun::model::#end_ident }))
|
|
|
+ (None, Some(quote! { ::btrun::model::#end_ident }), None)
|
|
|
} else {
|
|
|
- let type_name = format_ident!("{type_prefix}{}", state_trait);
|
|
|
+ let assoc_type = format_ident!("{type_prefix}{}", state_trait);
|
|
|
(
|
|
|
- Some(quote! { type #type_name: #state_trait; }),
|
|
|
- Some(quote! { Self::#type_name }),
|
|
|
+ Some(quote! { type #assoc_type: #state_trait; }),
|
|
|
+ Some(quote! { Self::#assoc_type }),
|
|
|
+ Some(assoc_type),
|
|
|
)
|
|
|
}
|
|
|
}
|
|
@@ -433,13 +706,15 @@ impl OutputModel {
|
|
|
} else {
|
|
|
Some(quote! { #msg_type })
|
|
|
};
|
|
|
- (None, type_name)
|
|
|
+ (None, type_name, None)
|
|
|
}
|
|
|
};
|
|
|
Self {
|
|
|
+ var_name: kind.var_name(),
|
|
|
type_name,
|
|
|
decl,
|
|
|
kind,
|
|
|
+ assoc_type,
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -450,6 +725,18 @@ impl OutputModel {
|
|
|
pub(crate) fn decl(&self) -> Option<&TokenStream> {
|
|
|
self.decl.as_ref()
|
|
|
}
|
|
|
+
|
|
|
+ pub(crate) fn kind(&self) -> &OutputKind {
|
|
|
+ &self.kind
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn assoc_type(&self) -> Option<&Ident> {
|
|
|
+ self.assoc_type.as_ref()
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn var_name(&self) -> &Ident {
|
|
|
+ &self.var_name
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
#[cfg_attr(test, derive(Debug))]
|
|
@@ -466,6 +753,24 @@ pub(crate) enum OutputKind {
|
|
|
},
|
|
|
}
|
|
|
|
|
|
+impl OutputKind {
|
|
|
+ fn var_name(&self) -> Ident {
|
|
|
+ let ident = match self {
|
|
|
+ Self::State { def, .. } => def.state_trait.as_ref(),
|
|
|
+ Self::Msg { def, .. } => def.msg.msg_type.as_ref(),
|
|
|
+ };
|
|
|
+ format_ident!("{}_out", ident.pascal_to_snake())
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn state_trait(&self) -> Option<&Rc<Ident>> {
|
|
|
+ if let Self::State { def, .. } = self {
|
|
|
+ Some(&def.state_trait)
|
|
|
+ } else {
|
|
|
+ None
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/// A type used to query information about actors, states, and their relationships.
|
|
|
pub(crate) struct ActorLookup {
|
|
|
/// A map from an actor name to the set of states which are part of that actor.
|
|
@@ -477,7 +782,10 @@ pub(crate) struct ActorLookup {
|
|
|
parents: HashMap<Rc<Ident>, HashSet<Rc<Ident>>>,
|
|
|
/// A map from an actor name to the set of actors names which it spawns.
|
|
|
children: HashMap<Rc<Ident>, HashSet<Rc<Ident>>>,
|
|
|
+ /// A map from the initial state of an actor to the actor.
|
|
|
actors_by_init_state: HashMap<Rc<Ident>, Rc<Ident>>,
|
|
|
+ /// The set of actors which are service providers in this protocol.
|
|
|
+ service_providers: HashSet<Rc<Ident>>,
|
|
|
}
|
|
|
|
|
|
impl ActorLookup {
|
|
@@ -486,6 +794,7 @@ impl ActorLookup {
|
|
|
A: IntoIterator<Item = &'a ActorDef>,
|
|
|
T: IntoIterator<Item = &'a Transition>,
|
|
|
{
|
|
|
+ // First we gather all the information we can by iterating over the actor definitions.
|
|
|
let mut actor_states = HashMap::new();
|
|
|
let mut actors_by_state = HashMap::new();
|
|
|
let mut parents = HashMap::new();
|
|
@@ -508,15 +817,25 @@ impl ActorLookup {
|
|
|
children.insert(actor_name.clone(), HashSet::new());
|
|
|
}
|
|
|
|
|
|
+ // Then, we gather information by iterating over the transitions.
|
|
|
+ let mut transitions_to = HashMap::new();
|
|
|
+ let mut service_providers = HashSet::new();
|
|
|
for transition in transitions {
|
|
|
- let in_state = transition.in_state.state_trait.as_ref();
|
|
|
+ let in_state = &transition.in_state.state_trait;
|
|
|
let parent = actors_by_state
|
|
|
.get(in_state)
|
|
|
.ok_or_else(|| syn::Error::new(in_state.span(), error::msgs::UNDECLARED_STATE))?;
|
|
|
- // The first output state is skipped because the current actor is transitioning to it,
|
|
|
- // its not creating a new actor.
|
|
|
- for out_state in transition.out_states.as_ref().iter().skip(1) {
|
|
|
- let out_state = out_state.state_trait.as_ref();
|
|
|
+ for (index, out_state) in transition.out_states.as_ref().iter().enumerate() {
|
|
|
+ let out_state = &out_state.state_trait;
|
|
|
+ transitions_to
|
|
|
+ .entry(in_state.clone())
|
|
|
+ .or_insert_with(HashSet::new)
|
|
|
+ .insert(out_state.clone());
|
|
|
+ // The first output state is skipped because the current actor is transitioning to
|
|
|
+ // it, its not creating a new actor.
|
|
|
+ if 0 == index {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
let child = actors_by_state.get(out_state).ok_or_else(|| {
|
|
|
syn::Error::new(out_state.span(), error::msgs::UNDECLARED_STATE)
|
|
|
})?;
|
|
@@ -529,6 +848,15 @@ impl ActorLookup {
|
|
|
.or_insert_with(HashSet::new)
|
|
|
.insert(child.clone());
|
|
|
}
|
|
|
+ for dest in transition.out_msgs.as_ref().iter() {
|
|
|
+ if let DestinationState::Service(service) = &dest.state {
|
|
|
+ let dest_state = &service.state_trait;
|
|
|
+ let actor_name = actors_by_state.get(dest_state).ok_or_else(|| {
|
|
|
+ syn::Error::new(dest_state.span(), error::msgs::UNDECLARED_STATE)
|
|
|
+ })?;
|
|
|
+ service_providers.insert(actor_name.clone());
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
Ok(Self {
|
|
@@ -537,6 +865,7 @@ impl ActorLookup {
|
|
|
parents,
|
|
|
children,
|
|
|
actors_by_init_state,
|
|
|
+ service_providers,
|
|
|
})
|
|
|
}
|
|
|
|
|
@@ -552,6 +881,15 @@ impl ActorLookup {
|
|
|
.unwrap_or_else(|| panic!("actor_states: {}", Self::UNKNOWN_ACTOR_ERR))
|
|
|
}
|
|
|
|
|
|
+ /// Returns the name of the actor containing the given state.
|
|
|
+ ///
|
|
|
+ /// **Panics**, if called with an unknown state name.
|
|
|
+ pub(crate) fn actor_with_state(&self, state_name: &Ident) -> &Rc<Ident> {
|
|
|
+ self.actors_by_state
|
|
|
+ .get(state_name)
|
|
|
+ .unwrap_or_else(|| panic!("can't find state {state_name}"))
|
|
|
+ }
|
|
|
+
|
|
|
pub(crate) fn parents(&self, actor_name: &Ident) -> &HashSet<Rc<Ident>> {
|
|
|
self.parents
|
|
|
.get(actor_name)
|
|
@@ -961,7 +1299,7 @@ mod tests {
|
|
|
let actual = ProtocolModel::new(input).unwrap();
|
|
|
|
|
|
let server = actual.actors.get(&format_ident!("server")).unwrap();
|
|
|
- assert!(!server.is_client());
|
|
|
+ assert!(!server.kind().is_client());
|
|
|
}
|
|
|
|
|
|
#[test]
|
|
@@ -971,7 +1309,7 @@ mod tests {
|
|
|
let actual = ProtocolModel::new(input).unwrap();
|
|
|
|
|
|
let client = actual.actors.get(&format_ident!("client")).unwrap();
|
|
|
- assert!(client.is_client());
|
|
|
+ assert!(client.kind().is_client());
|
|
|
}
|
|
|
|
|
|
#[test]
|
|
@@ -1023,6 +1361,6 @@ mod tests {
|
|
|
let actual = ProtocolModel::new(input).unwrap();
|
|
|
|
|
|
let worker = actual.actors.get(&format_ident!("worker")).unwrap();
|
|
|
- assert!(!worker.is_client());
|
|
|
+ assert!(!worker.kind().is_client());
|
|
|
}
|
|
|
}
|