Sinelabore Homepage

Generating Rust Code#

Rust is a modern programming language focusing on memory safety and thread safety. It is fast and memory-efficient, with no runtime or garbage collector. More information is available at rust-lang.org.

To generate Rust code, call the code generator with:

java -cp "path to installation/bin/*" codegen.Main -l rust -p ssc -o oven oven.xml

The generated code uses basic Rust features such as match and if/then/else, so it is straightforward to understand.

The output name (-o oven) defines:

  • the generated state machine file name
  • the state machine type name
  • the prefix for event and state enums

Microwave oven example in the integrated state machine editor

The fully generated Rust code from the oven state machine

The generator uses Rust macros. A concrete machine type must be defined with def_fsm before usage:

<code Rust>

/* Command line options: -Trace -l rust -p ssc -o oven oven.xml   */
/* This file is generated from oven.xml - do not edit manually  */
/* Generated on: Mon Nov 28 17:57:40 CET 2022 / Version 5.5.5.5 */


use std::fmt;


/// States in which the state machine can be
#[derive(Debug,PartialEq,Copy,Clone)]
pub enum OvenStates{
	Super,
	Completed,
	Cooking,
	CookingPause,
	Idle,
}

impl fmt::Display for OvenStates {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		match *self {
			OvenStates::Super => write!(f, "Super"),
			OvenStates::Completed => write!(f, "Completed"),
			OvenStates::Cooking => write!(f, "Cooking"),
			OvenStates::CookingPause => write!(f, "CookingPause"),
			OvenStates::Idle => write!(f, "Idle"),
		}
	}
}


/// Events that can be sent to the state machine
#[derive(Debug,PartialEq,Copy,Clone)]
pub enum OvenEvents {
	EvDec,
	EvTimeout,
	EvDoorClosed,
	EvDoorOpen,
	EvPowerLow,
	EvPowerHigh,
	EvInc,
	OvenNoMsg,
}


impl fmt::Display for OvenEvents {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		match *self {
			OvenEvents::EvDec => write!(f, "EvDec"),
			OvenEvents::EvTimeout => write!(f, "EvTimeout"),
			OvenEvents::EvDoorClosed => write!(f, "EvDoorClosed"),
			OvenEvents::EvDoorOpen => write!(f, "EvDoorOpen"),
			OvenEvents::EvPowerLow => write!(f, "EvPowerLow"),
			OvenEvents::EvPowerHigh => write!(f, "EvPowerHigh"),
			OvenEvents::EvInc => write!(f, "EvInc"),
			OvenEvents::OvenNoMsg => write!(f, "OvenNoMsg"),
		}
	}
}


#[macro_export]
macro_rules! def_fsm{
	($data_t:ty , $timer_service_t:ty $(,$element:ident: $ty:ty = $ex:expr)*) => {

	/* State machine struct
	 * is_init flag indicates whether the machine event handler was called the first time 
	 * state_var is the top level state variable 
	 * StateXXX are the state variables for hierachical or regions 
	 */
	#[derive(Debug,Copy,Clone)]
	pub struct Oven {
		pub is_init: bool,
		pub state_var: OvenStates,
		pub state_varmain_region: OvenStates,
		/* Start of user defined attributes */
		$($element: $ty),*
		/* End of user defined attributes */
	}

	impl Default for Oven {
		fn default() -> Self{
			Oven {
				is_init : false,
				state_var : OvenStates::Super, /* Set init for top level state */
				state_varmain_region : OvenStates::Idle, /* set init state of main_region */

				/* Start of user defined attributes */
				 $($element: $ex),*
				/* End of user defined attributes */
			}
		}
	}

	impl Oven{
		pub fn handle_event(&mut self, ev: OvenEvents,
			data:$data_t, timer_service:$timer_service_t
			) -> usize {


			let ev_consumed : usize;

			// Create copy of statevar
			let mut fsm_clone : Oven = self.clone();

			if self.is_init == false {
				fsm_clone.is_init = true;
				fsm_clone.initialize(data, timer_service);
			}

			// Action code
			/* just a comment */

			ev_consumed = fsm_clone.oven_main_machine(ev, data, timer_service);

			// Copy state variables back
			*self = fsm_clone;

			return ev_consumed;
		}




		/* Region code for state main_region */

		fn oven_main_region(&mut self, ev: OvenEvents,
			data:$data_t, timer_service:$timer_service_t) -> usize {

			let mut ev_consumed : usize = 0;

			match self.state_varmain_region {

				OvenStates::Completed => {
					if ev == OvenEvents::EvDoorOpen {
						/* Transition from Completed to Idle */
						ev_consumed=1;

						/* OnEntry code of state Idle */
						data.oven_off();

						/* adjust state variables  */
						self.state_varmain_region = OvenStates::Idle;
						data.log("Completed".to_string(), "Idle".to_string(), "EvDoorOpen".to_string());
					} else {
						/* Intentionally left blank */
					} /* End of event selection */
				} /* end of match Completed  */

				OvenStates::Cooking => {
					if ev == OvenEvents::EvDoorOpen {
						/* Transition from Cooking to CookingPause */
						ev_consumed=1;

						/* Action code for transition  */
						data.oven_off();
						timer_service.pause(self.timer_id);


						/* adjust state variables  */
						self.state_varmain_region = OvenStates::CookingPause;
						data.log("Cooking".to_string(), "CookingPause".to_string(), "EvDoorOpen".to_string());
					} else if ev == OvenEvents::EvTimeout {
						/* Transition from Cooking to Completed */
						ev_consumed=1;

						/* Action code for transition  */
						data.oven_off();
						timer_service.stop(self.timer_id);

						/* OnEntry code of state Completed */
						self.time=0;

						/* adjust state variables  */
						self.state_varmain_region = OvenStates::Completed;
						data.log("Cooking".to_string(), "Completed".to_string(), "EvTimeout".to_string());
					} else {
						/* Intentionally left blank */
					} /* End of event selection */
				} /* end of match Cooking  */

				OvenStates::CookingPause => {
					if ev == OvenEvents::EvDoorClosed {
						/* Transition from CookingPause to Cooking */
						ev_consumed=1;

						/* Action code for transition  */
						timer_service.cont(self.timer_id);

						/* OnEntry code of state Cooking */
						data.oven_on();

						/* adjust state variables  */
						self.state_varmain_region = OvenStates::Cooking;
						data.log("CookingPause".to_string(), "Cooking".to_string(), "EvDoorClosed".to_string());
					} else {
						/* Intentionally left blank */
					} /* End of event selection */
				} /* end of match CookingPause  */

				OvenStates::Idle => {
					if ev == OvenEvents::EvDoorClosed {
						if self.time > 0 {
							/* Transition from Idle to Cooking */
							ev_consumed=1;

							/* Action code for transition  */
							timer_service.start(self.timer_id, self.time);

							/* OnEntry code of state Cooking */
							data.oven_on();

							/* adjust state variables  */
							self.state_varmain_region = OvenStates::Cooking;
							data.log("Idle".to_string(), "Cooking".to_string(), "EvDoorClosed[self.time > 0]".to_string());
						} else {
							/* Intentionally left blank */
						} /* End of event selection */
					} else {
						/* Intentionally left blank */
					} /* End of event selection */
				} /* end of match Idle  */

				_ => {
					/* Intentionally left blank */
				 }
			 } /* End match stateVar_root */

			return ev_consumed;
		}


		pub fn initialize(&mut self, data:$data_t, timer_service:$timer_service_t){
			self.is_init = true;


			self.timer_id = timer_service.create(TimerType::SingleShot,OvenEvents::EvTimeout);
			data.oven_off();
			

		}


		fn oven_main_machine(&mut self, ev: OvenEvents,
			data:$data_t, timer_service:$timer_service_t) -> usize {


			let mut ev_consumed : usize = 0;


			match self.state_var {

				OvenStates::Super => {
					/* calling region code */
					ev_consumed |= self.oven_main_region(ev, data, timer_service);

					/* Check if event was already processed  */
					if ev_consumed==0{

						if ev == OvenEvents::EvDec {
							/* Transition from Super to Super */

							/* Exit code for regions in state Super */
							
							/* Action code for transition  */
							if self.time >= 1000{self.time += 1000;}



							/* Entry code for regions in state Super */
							data.oven_off();
							/* Default in entry chain  */
							self.state_varmain_region = OvenStates::Idle;/* Default in entry chain  */


							/* adjust state variables  */
							self.state_var = OvenStates::Super;
							data.log("Super".to_string(), "Super".to_string(), "EvDec".to_string());
						} else if ev == OvenEvents::EvInc {
							/* Transition from Super to Super */

							/* Exit code for regions in state Super */
							
							/* Action code for transition  */
							self.time += 1000;


							/* Entry code for regions in state Super */
							data.oven_off();
							/* Default in entry chain  */
							self.state_varmain_region = OvenStates::Idle;/* Default in entry chain  */


							/* adjust state variables  */
							self.state_var = OvenStates::Super;
							data.log("Super".to_string(), "Super".to_string(), "EvInc".to_string());
						} else if ev == OvenEvents::EvPowerHigh {
							/* Transition from Super to Super */

							/* Exit code for regions in state Super */
							
							/* Action code for transition  */
							data.oven_set_pwr(2000);


							/* Entry code for regions in state Super */
							data.oven_off();
							/* Default in entry chain  */
							self.state_varmain_region = OvenStates::Idle;/* Default in entry chain  */


							/* adjust state variables  */
							self.state_var = OvenStates::Super;
							data.log("Super".to_string(), "Super".to_string(), "EvPowerHigh".to_string());
						} else if ev == OvenEvents::EvPowerLow {
							/* Transition from Super to Super */

							/* Exit code for regions in state Super */
							
							/* Action code for transition  */
							data.oven_set_pwr(400);


							/* Entry code for regions in state Super */
							data.oven_off();
							/* Default in entry chain  */
							self.state_varmain_region = OvenStates::Idle;/* Default in entry chain  */


							/* adjust state variables  */
							self.state_var = OvenStates::Super;
							data.log("Super".to_string(), "Super".to_string(), "EvPowerLow".to_string());
						} else {
							/* Intentionally left blank */
						} /* End of event selection */
					}
				} /* end of match Super  */

				_ => {
					/* Intentionally left blank */
				 }
			 } /* End match stateVar_root */
			return ev_consumed;
		}


	}
};
}

The first macro parameter is a user-defined type passed into the state machine handler. Inside the event handler, it is available as variable data with type UserData.

This enables passing arbitrary user data into the state machine, for example values used by transition guards.

If code generation is run with -Trace, this UserData type must provide a logger function with the expected signature to trace state transitions and event flow.

The second macro parameter is another user-defined type intended to implement a timer service. Whether a timer service is needed and how it is implemented is entirely user-defined. A microwave oven sample in the examples folder demonstrates a simple timer service for cooking timeout.

All additional macro parameters are optional and are added to the generated state machine struct.

The complete example is available here. Build it with cargo build, then run with cargo run.

Expected command line output:

cargo run
...
Oven demo program written in Rust
Type in one or more commands and then press return to send them to the state machine
Supported commands:
   o to open the door
   c to close the door
   + to increase the cooking time
   - to decrease the door
   P set oven power to high
   p set oven power to low

How to continue#

  • Download codegen with included examples
  • Modify the state machine model and regenerate Rust code to explore behavior
  • Read the manual for all code generator features

Please note that the Rust backend is work-in-progress. Feedback is highly welcome.