20个版本
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.2 | 2019年5月6日 |
在 嵌入式开发 中排名第 30
每月下载量 10,595
在 8 个包中使用(直接使用7个)8 包
59KB
200 代码行
smlang:Rust中一个no_std
状态机语言DSL
基于Boost-SML语法的状态机语言DSL。
smlang
是一个过程宏库,创建了一个状态机语言DSL,其目的是简化状态机的使用,因为状态机很快就会变得过于复杂,难以编写和概述。
该库支持异步和非异步代码。
转换DSL
以下是DSL的示例。有关statemachine
宏的完整说明,请参阅DSL文档。
statemachine!{
transitions: {
*SrcState1 + Event1 [ guard1 ] / action1 = DstState2, // * denotes starting state
SrcState2 + Event2 [ guard2 ] / action2 = DstState1,
}
// ...
}
其中guard
和action
是可选的,可以省略。一个guard
是一个函数,当状态转换应该发生时返回Ok(true)
,否则转换不应该发生。在状态机转换期间运行action
函数。
这意味着任何状态机都必须写成一系列转换。
DSL支持通配符和类似于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
中的使用示例。
内部转换
DSL支持内部转换。内部转换允许接收事件并处理操作,然后保持在当前状态。可以明确指定内部转换,例如。
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!
proc-macro 生成的,实现了守卫和动作,以及状态机中所有状态都可以访问的数据,并且这些数据在状态转换之间持续存在。
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
枚举添加推导表达式。要推导 Display,请使用 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 或 https://apache.ac.cn/licenses/LICENSE-2.0
- MIT license LICENSE-MIT 或 http://opensource.org/licenses/MIT
任选其一。
依赖关系
~1.5MB
~36K SLoC