behaviortree-derive 0.5.0

Derive macros for behaviortree
Documentation
// Copyright © 2025 Stephan Kunz
//! Common derive behavior macro implementation

#![allow(unused)]

use darling::{FromDeriveInput, FromField};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{DataStruct, DeriveInput, Field, Generics, Ident, Type};

/// Structure for the attributes on struct level.
/// All attributes are optional.
#[derive(FromDeriveInput, Default, Debug)]
#[darling(attributes(behavior))]
struct StructAttributes {
	/// Attribute `no_create` signals whether to derive the creation method or not.
	#[darling(default)]
	no_create: bool,
	/// Attribute `no_register signals whether to derive the default registration method or not.`
	#[darling(default)]
	no_register: bool,
	/// Attribute `no_register_with` signals whether to derive the parametrized regstration method or not.
	#[darling(default)]
	no_register_with: bool,
	/// Attribute `groot2` signals whether it is a behavior internally known to Groot2.
	#[darling(default)]
	groot2: bool,
}

/// Structure for the attributes on field level.
/// All attributes are optional.
#[derive(FromField, Default, Debug)]
#[darling(attributes(behavior))]
struct StructFieldAttributes {
	/// Attribute `parameter declares its variable to be a parameter
	/// that must be given during registration.
	#[darling(default)]
	parameter: bool,
	/// Attribute `default` provides an initialisation value
	/// other than types default() or if no Default implementation exists.
	default: Option<syn::Expr>,
	/// Attribute `portlist` signals whether behavior has a port list or not.
	#[darling(default)]
	portlist: bool,
}

/// Collects information of all fields
fn collect_field_informations(
	data: &mut DataStruct,
) -> (
	Vec<Field>,         // collects the 'parameter' field definitions
	Vec<Ident>,         // collects the 'parmeter' names only
	Vec<Ident>,         // collects the internal defined types
	Vec<Ident>,         // collects the 'default' fields
	Vec<syn::Expr>,     // collects the values for 'default' fields
	Vec<Ident>,         // collects the non 'default' fields
	Vec<Ident>,         // collects the non 'default' field types
	Option<syn::Ident>, // the optional name of the portlist field
) {
	let mut parameters = Vec::new();
	let mut arguments = Vec::new();
	let mut internal_types = Vec::new();
	let mut default_idents = Vec::new();
	let mut default_values = Vec::new();
	let mut no_default_idents = Vec::new();
	let mut no_default_types = Vec::new();
	let mut portlist_ident = None;
	for field in data.fields.iter() {
		let attrs = match StructFieldAttributes::from_field(field) {
			Ok(value) => value,
			Err(err) => panic!("parsing field attributes failed: {err}"),
		};
		let field_name = field
			.ident
			.as_ref()
			.expect("no field name")
			.clone();
		let field_type = match field.ty.clone() {
			Type::Path(type_path) => {
				let ident = if type_path.path.leading_colon.is_none() {
					type_path.path.segments[0].ident.clone()
				} else {
					unreachable!("a Path should always have an Ident")
				};
				ident
			}
			_ => unreachable!("found a non Path where only a Path is expected"),
		};
		if attrs.parameter {
			if attrs.portlist {
				panic!("portlist attribute cannot be used with any other attribute")
			}
			// create parameter field without attributes, mutability & visibility!
			let mut param = field.clone();
			param.attrs = Vec::new();
			param.vis = syn::Visibility::Inherited;
			param.mutability = syn::FieldMutability::None;
			parameters.push(param);
			arguments.push(field_name.clone());
			// both attributes 'parameter' and 'default' are set
			if let Some(default) = attrs.default {
				default_idents.push(field_name);
				default_values.push(default);
			} else {
				internal_types.push(field_type.clone());
			}
		} else if let Some(default) = attrs.default {
			if attrs.portlist {
				panic!("portlist attribute cannot be used with any other attribute")
			}
			default_idents.push(field_name);
			default_values.push(default);
		} else {
			if attrs.portlist {
				portlist_ident = Some(field_name)
			} else {
				no_default_idents.push(field_name);
				no_default_types.push(field_type.clone());
			}
		}
	}

	// check dependencies of the vec's
	assert_eq!(parameters.len(), arguments.len());
	assert_eq!(parameters.len(), internal_types.len());
	assert_eq!(default_idents.len(), default_values.len());
	assert_eq!(no_default_idents.len(), no_default_types.len());

	(
		parameters,
		arguments,
		internal_types,
		default_idents,
		default_values,
		no_default_idents,
		no_default_types,
		portlist_ident,
	)
}

fn handle_struct(
	kind_token: TokenStream,
	ident: &Ident,
	generics: &Generics,
	mut attribs: StructAttributes,
	data: &mut DataStruct,
) -> TokenStream {
	// Annotation for derived implementations
	let derived: TokenStream = "#[automatically_derived]".parse().unwrap();
	//let diagnostic: TokenStream = "#[diagnostic::do_not_recommend]".parse().unwrap();
	let no_documentation: TokenStream = "#[allow(missing_docs)]".parse().unwrap();

	// separate generics
	let (impl_generics, type_generics, where_clause) = generics.split_for_impl();

	let (parameters, arguments, types, default_idents, default_values, no_default_idents, no_default_types, portlist_ident) =
		collect_field_informations(data);
	// if there are no parameter we do not need a register_with(...) at all
	if parameters.len() == 0 {
		attribs.no_register_with = true;
	}

	// create the impl block only if it has content
	let impl_block = if attribs.no_create && attribs.no_register && attribs.no_register_with {
		quote! {}
	} else {
		// creation of token streams for the different functions
		let fn_create_fn = if attribs.no_create {
			quote! {}
		} else {
			// Behavior creation function
			// SAFETY: The created function will panic on first run
			if let Some(portlist_ident) = portlist_ident.clone() {
				quote! {
					fn create_fn(#(#parameters),*) -> alloc::boxed::Box<behaviortree_core::BehaviorCreationFn>  {
						alloc::boxed::Box::new(move |blackboard| alloc::boxed::Box::new(Self {
							#(#arguments: #arguments.clone(),)*
							#portlist_ident : Self::port_collection_init(&blackboard).unwrap(),
							#(#default_idents : #default_values,)*
							#(#no_default_idents : #no_default_types::default(),)*
						}))
					}
				}
			} else {
				quote! {
					fn create_fn(#(#parameters),*) -> alloc::boxed::Box<behaviortree_core::BehaviorCreationFn>  {
						alloc::boxed::Box::new(move |blackboard| alloc::boxed::Box::new(Self {
							#(#arguments: #arguments.clone(),)*
							#(#default_idents : #default_values,)*
							#(#no_default_idents : #no_default_types::default(),)*
						}))
					}
				}
			}
		};

		let groot2 = attribs.groot2;
		// create register_with only if wanted
		let fn_register = if attribs.no_register {
			quote! {}
		} else {
			/// Registers the behavior.
			quote! {
				pub fn register(registry: &mut impl behaviortree_core::behavior_traits::BehaviorRegistry, name: &str) -> Result<(), behaviortree_core::error::Error> {
					let bhvr_desc = behaviortree_core::behavior_description::BehaviorDescription::new(name, name, #groot2);
					let bhvr_creation_fn = Self::create_fn(#(#types::default()),*);
					registry.add_behavior(bhvr_desc, bhvr_creation_fn)
				}
			}
		};

		// create register_with only if wanted or necessary
		let fn_register_with = if attribs.no_register_with {
			quote! {}
		} else {
			/// Registers the behavior with parameter.
			quote! {
				pub fn register_with(registry: &mut impl behaviortree_core::behavior_traits::BehaviorRegistry, name: &str, #(#parameters),*) -> Result<(), behaviortree_core::error::Error> {
					let bhvr_desc = behaviortree_core::behavior_description::BehaviorDescription::new(name, name, #groot2);
					let bhvr_creation_fn = Self::create_fn(#(#arguments),*);
					registry.add_behavior(bhvr_desc, bhvr_creation_fn)
				}
			}
		};

		// concatenate impl block
		quote! {
			#derived
			#no_documentation
			impl #impl_generics #ident #type_generics #where_clause {
				#fn_create_fn
				#fn_register
				#fn_register_with
			}
		}
	};

	// portlist block
	let provided_ports_block = if let Some(portlist_ident) = portlist_ident.clone() {
		quote! {
			#derived
			#no_documentation
			impl #impl_generics dataport::PortCollectionProvider  for #ident #type_generics #where_clause {
				fn provided_ports(&self) -> &impl dataport::PortCollectionAccessors {
					&self.#portlist_ident
				}

				fn provided_ports_mut(&mut self) -> &mut impl dataport::PortCollectionAccessors {
					&mut self.#portlist_ident
				}

				fn port_collection(&self) -> &impl dataport::PortCollection {
					&self.#portlist_ident
				}
			}
		}
	} else {
		// the default implementation is using a static empty array
		quote! {
			#derived
			#no_documentation
			impl #impl_generics dataport::PortCollectionProvider  for #ident #type_generics #where_clause {}
		}
	};

	// provided ports source
	let portlist = if let Some(portlist_ident) = portlist_ident {
		quote! {
			fn portlist(&self) -> &dyn dataport::PortList {
				&self.#portlist_ident
			}

			fn portlist_mut(&mut self) -> &mut dyn dataport::PortList {
				&mut self.#portlist_ident
			}
		}
	} else {
		quote! {
			fn portlist(&self) -> &dyn dataport::PortList {
				dataport::EMPTY_PORT_ARRAY.get()
			}

			fn portlist_mut(&mut self) -> &mut dyn dataport::PortList {
				dataport::EMPTY_PORT_ARRAY.get_mut()
			}
		}
	};

	// create token stream
	quote! {
		#derived
		#no_documentation
		impl #impl_generics behaviortree_core::behavior_traits::BehaviorExecution for #ident #type_generics #where_clause {
			fn as_any(&self) -> &dyn ::core::any::Any { self }
			fn as_any_mut(&mut self) -> &mut dyn ::core::any::Any { self }
			fn kind(&self) -> behaviortree_core::behavior_kind::BehaviorKind { #kind_token }
			#portlist
		}

		#impl_block

		#provided_ports_block
	}
}

/// Implementation of the derive macro.
pub fn derive_behavior_impl(input: TokenStream, kind_token: TokenStream) -> TokenStream {
	// parse input token stream as DeriveInput
	match syn::parse2::<DeriveInput>(input) {
		Ok(mut derive_input) => {
			// structure name
			let ident = &derive_input.ident;
			// generics
			let generics = &derive_input.generics;
			// attributes
			let attribs = match StructAttributes::from_derive_input(&derive_input) {
				Ok(result) => result,
				Err(err) => panic!("parsing derive attributes failed: {err}"),
			};
			match &mut derive_input.data {
				syn::Data::Struct(data) => handle_struct(kind_token, ident, generics, attribs, data),
				_ => unimplemented!("only 'structs' are supported by now"),
			}
		}
		Err(err) => panic!("parsing derive input failed: {err}"),
	}
}