18个版本
0.8.0 | 2024年8月9日 |
---|---|
0.7.0 | 2024年7月3日 |
0.6.0 | 2022年11月2日 |
0.5.1 | 2022年2月5日 |
0.1.1 | 2019年5月6日 |
#978 在 嵌入式开发
9,867 每月下载量
在 9 个crate中使用(通过 smlang)
94KB
2K SLoC
smlang: Rust中的no_std
状态机语言领域特定语言
基于Boost-SML语法的状态机语言领域特定语言。
smlang
是一个过程宏库,创建了一个状态机语言领域特定语言,其目的是为了简化状态机的使用,因为它们很快就会变得过于复杂,难以编写和概述。
该库支持异步和非异步代码。
转换领域特定语言
以下是一个领域特定语言的示例。有关statemachine
宏的完整描述,请参阅领域特定语言文档。
statemachine!{
transitions: {
*SrcState1 + Event1 [ guard1 ] / action1 = DstState2, // * denotes starting state
SrcState2 + Event2 [ guard2 ] / action2 = DstState1,
}
// ...
}
其中guard
和action
是可选的,可以省略。一个guard
是一个函数,如果状态转换应该发生,则返回Ok(true)
- 否则,转换不应发生。在状态机转换期间运行action
函数。
这意味着任何状态机都必须以转换列表的形式编写。
领域特定语言支持通配符和与Rust模式匹配类似的输入状态模式匹配
statemachine!{
transitions: {
*State1 | State3 + ToState2 = State2,
State1 | State2 + ToState3 = State3,
_ + ToState4 = State4,
State4 + ToState1 = State1,
}
// ...
}
这等价于
statemachine!{
transitions: {
*State1 + ToState2 = State2,
State3 + ToState2 = State2,
State1 + ToState3 = State3,
State2 + ToState3 = State3,
State1 + ToState4 = State4,
State2 + ToState4 = State4,
State3 + ToState4 = State4,
State4 + ToState4 = State4,
State4 + ToState1 = State1,
}
// ...
}
请参阅examples/input_state_pattern_match.rs
中的使用示例。
内部转换
领域特定语言支持内部转换。内部转换允许接收事件并处理动作,然后保持在当前状态。可以显式指定内部转换,例如。
State2 + Event2 / event2_action = State2,
或
State2 + Event2 / event2_action = _,
或隐式地省略目标状态,包括'='。
State2 + Event2 / event2_action,
还可以定义通配符隐式(或使用'_'显式)内部转换。
statemachine! {
transitions: {
*State1 + Event2 = State2,
State1 + Event3 = State3,
State1 + Event4 = State4,
_ + Event2 / event2_action,
},
}
上述示例演示了如何使Event2对任何状态都适用,这些状态都不是之前任何转换所涵盖的,并且对其执行一个动作以进行处理。
这等价于
statemachine! {
transitions: {
*State1 + Event2 = State2,
State1 + Event3 = State3,
State1 + Event4 = State4,
State2 + Event2 / event2_action = State2,
State3 + Event2 / event2_action = State3,
State4 + Event2 / event2_action = State4,
},
}
请参阅以下测试:test_internal_transition_with_data()
或test_wildcard_states_and_internal_transitions()
以获取使用示例。
守卫表达式
方括号[]中的守卫表达式允许定义多个守卫函数的布尔表达式。例如
statemachine! {
transitions: {
*Init + Login(Entry) [valid_entry] / attempt = LoggedIn,
Init + Login(Entry) [!valid_entry && !too_many_attempts] / attempt = Init,
Init + Login(Entry) [!valid_entry && too_many_attempts] / attempt = LoginDenied,
LoggedIn + Logout / reset = Init,
}
}
守卫表达式可以由守卫函数名称组成,以及它们与&&、||和!运算符的组合。
同一状态和触发事件的多个守卫转换
支持同一状态和触发事件的多个守卫转换(见上述示例)。在这种情况下,假设只有一个守卫被启用,以避免在哪个转换应被选择方面产生冲突。然而,如果有冲突并且多个守卫被启用,将选择在状态机定义中出现的第一个启用的转换。
状态机上下文
状态机需要一个上下文来定义。《StateMachineContext》是由《statemachine!》过程宏生成的,它实现了守卫和动作,以及状态机内部所有状态中都可用并在状态转换之间持续的数据。
statemachine!{
transitions: {
State1 + Event1 = State2,
}
// ...
}
pub struct Context;
impl StateMachineContext for Context {}
fn main() {
let mut sm = StateMachine::new(Context);
// ...
}
请参阅示例 examples/context.rs
以获取使用示例。
状态数据
任何状态都可以与一些数据相关联。
pub struct MyStateData(pub u32);
statemachine!{
transitions: {
State1(MyStateData) + Event1 = State2,
}
// ...
}
请参阅示例 examples/state_with_data.rs
以获取使用示例。
如果起始状态包含数据,则在创建新机器后必须提供这些数据。
pub struct MyStateData(pub u32);
statemachine!{
transitions: {
State2 + Event2 / action = State1(MyStateData),
*State1(MyStateData) + Event1 = State2,
// ...
}
// ...
}
// ...
let mut sm = StateMachine::new(Context, MyStateData(42));
状态数据还可以具有与它们相关联的生命周期,这些生命周期将由《statemachine!》宏获取并添加到《States》枚举和《StateMachine》结构中。这意味着以下也将工作
pub struct MyStateData<'a>(&'a u32);
statemachine! {
transitions: {
*State1 + Event1 / action = State2,
State2(MyStateData<'a>) + Event2 = State1,
// ...
}
// ...
}
请参阅示例 examples/state_with_reference_data.rs
以获取使用示例。
事件数据
数据可以与事件一起传递到《guard》和《action》中。
pub struct MyEventData(pub u32);
statemachine!{
transitions: {
State1 + Event1(MyEventData) [guard] = State2,
}
// ...
}
事件数据还可以具有与它们相关联的生命周期,这些生命周期将由《statemachine!》宏获取并添加到《Events》枚举中。这意味着以下也将工作
pub struct MyEventData<'a>(pub &'a u32);
statemachine!{
transitions: {
State1 + Event1(MyEventData<'a>) [guard1] = State2,
State1 + Event2(&'a [u8]) [guard2] = State3,
}
// ...
}
请参阅示例 examples/event_with_data.rs
以获取使用示例。
守卫和动作语法
请参阅示例 examples/guard_action_syntax.rs
以获取使用示例。
异步守卫和动作
守卫和动作都可以可选地使用async
。
use smlang::{async_trait, statemachine};
statemachine! {
transitions: {
*State1 + Event1 [guard1] / async action1 = State2,
State2 + Event2 [async guard2] / action2 = State3,
}
}
pub struct Context {
// ...
}
impl StateMachineContext for Context {
async fn action1(&mut self) -> () {
// ...
}
async fn guard2(&mut self) -> Result<(), ()> {
// ...
}
fn guard1(&mut self) -> Result<(), ()> {
// ...
}
fn action2(&mut self) -> () {
// ...
}
}
请参阅示例 examples/async.rs
以获取使用示例。
状态机示例
以下是将UML转换为状态机语言DSL的状态机的一些示例。每个示例的运行版本都可在《examples》文件夹中找到。`.png`文件是用《graphviz》功能生成的。
线性状态机
DSL实现
statemachine!{
transitions: {
*State1 + Event1 = State2,
State2 + Event2 = State3,
}
}
此示例在《ex1.rs》中。
循环状态机
DSL实现
statemachine!{
transitions: {
*State1 + Event1 = State2,
State2 + Event2 = State3,
State3 + Event3 = State2,
}
}
此示例在《ex2.rs》中。
使用守卫和动作
DSL实现
statemachine!{
transitions: {
*State1 + Event1 [guard] / action = State2,
}
}
此示例在《ex3.rs》中。
在转换中使用入口和退出函数
状态机将为所有状态创建一个《on_entry_》和《on_exit_》函数。如果没有使用,它们将被编译器优化掉。一个示例可以在《on_entry_on_exit_generic》中找到。
转换回调
状态机将为每个转换调用转换回调。这个函数将使用旧状态和新状态作为参数被调用。一个例子可以在 dominos
中找到。
辅助工具
自动为状态和事件推导某些特征
将 derive_events
和 derive_states
字段设置为特征数组将为 Events
和 States
枚举分别添加一个推导表达式。要推导显示,使用 derive_more::Display
。
use core::Debug;
use derive_more::Display;
// ...
statemachine!{
derive_states: [Debug, Display],
derive_events: [Debug, Display],
transitions: {
*State1 + Event1 = State2,
}
}
// ...
println!("Current state: {}", sm.state().unwrap());
println!("Expected state: {}", States::State1);
println!("Sending event: {}", Events::Event1);
// ...
用于记录事件、守卫、动作和状态转换的钩子
StateMachineContext
特征定义(并提供默认、无操作实现)了对于每个事件、守卫、动作和状态转换被调用的函数。你可以提供自己的实现,以将其插入你首选的日志机制。
fn log_process_event(&self, current_state: &States, event: &Events) {}
fn log_guard(&self, guard: &'static str, result: &Result<(), ()>) {}
fn log_action(&self, action: &'static str) {}
fn log_state_change(&self, new_state: &States) {}
请参阅 examples/state_machine_logger.rs
,其中使用了 derive_states
和 derive_events
推导 Debug
实现,以便于记录。
贡献者
按字母顺序排列的贡献者名单
- Emil Fresk (@korken89)
- Mathias Koch (@MathiasKoch)
- Ryan Summers (@ryan-summers)
- Donny Zimmanck (@dzimmanck)
许可证
根据以下任一许可证授权
- Apache License, Version 2.0 LICENSE-APACHE 或 http://www.apache.org/licenses/LICENSE-2.0
- MIT 许可证 LICENSE-MIT 或 http://opensource.org/licenses/MIT
由您选择。
依赖关系
~1.5MB
~35K SLoC