initial commit: hsmc v0.2 with during: activities

Hierarchical statechart codegen for Rust with:
- during: activities for state-scoped concurrent I/O
- field-list borrowing (split-borrow inside the run loop)
- embassy-first async with tokio support
- STATE_CHART const for tooling/inspection
- examples: microwave, microwave_tui, embassy_full, during_radio
This commit is contained in:
swaits committed 2026-04-16 13:03:03 -06:00
commit 0341cf9379
56 files changed
+11770

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
//! Compile-fail tests for `statechart!` validations (§9.9) and
//! compile-pass parse tests (spec §8).
#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/*.rs");
t.pass("tests/ui_pass/*.rs");
}
@@ -0,0 +1,28 @@
//! t10.1: a single `during:` listing the same field twice is a compile error.
use hsmc::statechart;
#[derive(Debug, Clone)]
pub enum Ev { Halt }
pub struct Ctx {
pub a: u32,
}
statechart! {
M {
context: Ctx;
events: Ev;
default(S);
terminate(Halt);
state S {
during: tick(a, a);
}
}
}
async fn tick(_: &mut u32, _: &mut u32) -> Ev { Ev::Halt }
impl MActions for MActionContext<'_> {}
fn main() {}
@@ -0,0 +1,17 @@
error: field `a` listed twice in `during: tick(...)` — each field can only be borrowed once per during
--> tests/ui/t10_1_during_duplicate_field.rs:19:25
|
19 | during: tick(a, a);
| ^
error[E0405]: cannot find trait `MActions` in this scope
--> tests/ui/t10_1_during_duplicate_field.rs:26:6
|
26 | impl MActions for MActionContext<'_> {}
| ^^^^^^^^ not found in this scope
error[E0425]: cannot find type `MActionContext` in this scope
--> tests/ui/t10_1_during_duplicate_field.rs:26:19
|
26 | impl MActions for MActionContext<'_> {}
| ^^^^^^^^^^^^^^ not found in this scope
@@ -0,0 +1,31 @@
//! t10.2: two `during:` declarations on the same state borrowing the same
//! field produce a clear macro-level error.
use hsmc::statechart;
#[derive(Debug, Clone)]
pub enum Ev { Halt }
pub struct Ctx {
pub a: u32,
}
statechart! {
M {
context: Ctx;
events: Ev;
default(S);
terminate(Halt);
state S {
during: one(a);
during: two(a);
}
}
}
async fn one(_: &mut u32) -> Ev { Ev::Halt }
async fn two(_: &mut u32) -> Ev { Ev::Halt }
impl MActions for MActionContext<'_> {}
fn main() {}
@@ -0,0 +1,17 @@
error: field `a` is borrowed by two concurrent durings on state `S` (`one` and `two`) — move one of them to a disjoint field or combine the activities into a single during that uses `select`
--> tests/ui/t10_2_during_overlap_same_state.rs:21:21
|
21 | during: two(a);
| ^
error[E0405]: cannot find trait `MActions` in this scope
--> tests/ui/t10_2_during_overlap_same_state.rs:29:6
|
29 | impl MActions for MActionContext<'_> {}
| ^^^^^^^^ not found in this scope
error[E0425]: cannot find type `MActionContext` in this scope
--> tests/ui/t10_2_during_overlap_same_state.rs:29:19
|
29 | impl MActions for MActionContext<'_> {}
| ^^^^^^^^^^^^^^ not found in this scope
@@ -0,0 +1,26 @@
use hsmc::{statechart, Duration};
#[derive(Debug, Clone)]
pub enum Ev { Go }
pub struct Ctx;
statechart! {
M {
context: Ctx;
events: Ev;
default(Red);
state Red {
on(Go) => Green;
on(Go) => Yellow;
}
state Green { entry: e; }
state Yellow { entry: e; }
}
}
impl MActions for MActionContext<'_> {
fn e(&mut self) {}
}
fn main() {}
@@ -0,0 +1,25 @@
error: duplicate transition on trigger `Go` in state `Red`
--> tests/ui/t9_1_duplicate_transition.rs:15:12
|
15 | on(Go) => Yellow;
| ^^
error[E0405]: cannot find trait `MActions` in this scope
--> tests/ui/t9_1_duplicate_transition.rs:22:6
|
22 | impl MActions for MActionContext<'_> {
| ^^^^^^^^ not found in this scope
error[E0425]: cannot find type `MActionContext` in this scope
--> tests/ui/t9_1_duplicate_transition.rs:22:19
|
22 | impl MActions for MActionContext<'_> {
| ^^^^^^^^^^^^^^ not found in this scope
warning: unused import: `Duration`
--> tests/ui/t9_1_duplicate_transition.rs:1:24
|
1 | use hsmc::{statechart, Duration};
| ^^^^^^^^
|
= note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default
@@ -0,0 +1,24 @@
use hsmc::statechart;
#[derive(Debug, Clone)]
pub enum Ev { _U }
pub struct Ctx;
statechart! {
M {
context: Ctx;
events: Ev;
default(Parent);
state Parent {
state ChildA { entry: e; }
state ChildB { entry: e; }
}
}
}
impl MActions for MActionContext<'_> {
fn e(&mut self) {}
}
fn main() {}
@@ -0,0 +1,17 @@
error: state `Parent` has children but no `default` declaration
--> tests/ui/t9_2_missing_default.rs:13:11
|
13 | state Parent {
| ^^^^^^
error[E0405]: cannot find trait `MActions` in this scope
--> tests/ui/t9_2_missing_default.rs:20:6
|
20 | impl MActions for MActionContext<'_> {
| ^^^^^^^^ not found in this scope
error[E0425]: cannot find type `MActionContext` in this scope
--> tests/ui/t9_2_missing_default.rs:20:19
|
20 | impl MActions for MActionContext<'_> {
| ^^^^^^^^^^^^^^ not found in this scope
@@ -0,0 +1,24 @@
use hsmc::statechart;
#[derive(Debug, Clone)]
pub enum Ev { Halt }
pub struct Ctx;
statechart! {
M {
context: Ctx;
events: Ev;
default(Child);
state Child {
terminate(Halt);
entry: e;
}
}
}
impl MActions for MActionContext<'_> {
fn e(&mut self) {}
}
fn main() {}
@@ -0,0 +1,17 @@
error: `terminate` is only valid at the root level
--> tests/ui/t9_3_terminate_in_child.rs:14:9
|
14 | terminate(Halt);
| ^^^^^^^^^
error[E0405]: cannot find trait `MActions` in this scope
--> tests/ui/t9_3_terminate_in_child.rs:20:6
|
20 | impl MActions for MActionContext<'_> {
| ^^^^^^^^ not found in this scope
error[E0425]: cannot find type `MActionContext` in this scope
--> tests/ui/t9_3_terminate_in_child.rs:20:19
|
20 | impl MActions for MActionContext<'_> {
| ^^^^^^^^^^^^^^ not found in this scope
@@ -0,0 +1,25 @@
use hsmc::statechart;
#[derive(Debug, Clone)]
pub enum Ev { _U }
pub struct Ctx;
statechart! {
M {
context: Ctx;
events: Ev;
default(Parent);
state Parent {
default(Cousin);
state Child { entry: e; }
}
state Cousin { entry: e; }
}
}
impl MActions for MActionContext<'_> {
fn e(&mut self) {}
}
fn main() {}
@@ -0,0 +1,17 @@
error: `default(Cousin)` in state `Parent`: `Cousin` is not a direct child of `Parent`
--> tests/ui/t9_4_default_non_child.rs:14:9
|
14 | default(Cousin);
| ^^^^^^^
error[E0405]: cannot find trait `MActions` in this scope
--> tests/ui/t9_4_default_non_child.rs:21:6
|
21 | impl MActions for MActionContext<'_> {
| ^^^^^^^^ not found in this scope
error[E0425]: cannot find type `MActionContext` in this scope
--> tests/ui/t9_4_default_non_child.rs:21:19
|
21 | impl MActions for MActionContext<'_> {
| ^^^^^^^^^^^^^^ not found in this scope
@@ -0,0 +1,24 @@
use hsmc::statechart;
#[derive(Debug, Clone)]
pub enum Ev { _U }
pub struct Ctx;
statechart! {
M {
context: Ctx;
events: Ev;
default(Leaf);
state Leaf {
default(Ghost);
entry: e;
}
}
}
impl MActions for MActionContext<'_> {
fn e(&mut self) {}
}
fn main() {}
@@ -0,0 +1,17 @@
error: `default` in state `Leaf` which has no child states
--> tests/ui/t9_5_default_childless.rs:14:9
|
14 | default(Ghost);
| ^^^^^^^
error[E0405]: cannot find trait `MActions` in this scope
--> tests/ui/t9_5_default_childless.rs:20:6
|
20 | impl MActions for MActionContext<'_> {
| ^^^^^^^^ not found in this scope
error[E0425]: cannot find type `MActionContext` in this scope
--> tests/ui/t9_5_default_childless.rs:20:19
|
20 | impl MActions for MActionContext<'_> {
| ^^^^^^^^^^^^^^ not found in this scope
+22
View File
@@ -0,0 +1,22 @@
use hsmc::statechart;
#[derive(Debug, Clone)]
pub enum Ev { _U }
pub struct Ctx;
statechart! {
M {
context: Ctx;
events: Ev;
default(A);
state A { entry: e; }
state Empty {}
}
}
impl MActions for MActionContext<'_> {
fn e(&mut self) {}
}
fn main() {}
@@ -0,0 +1,17 @@
error: empty state `Empty`
--> tests/ui/t9_6_empty_state.rs:14:11
|
14 | state Empty {}
| ^^^^^
error[E0405]: cannot find trait `MActions` in this scope
--> tests/ui/t9_6_empty_state.rs:18:6
|
18 | impl MActions for MActionContext<'_> {
| ^^^^^^^^ not found in this scope
error[E0425]: cannot find type `MActionContext` in this scope
--> tests/ui/t9_6_empty_state.rs:18:19
|
18 | impl MActions for MActionContext<'_> {
| ^^^^^^^^^^^^^^ not found in this scope
@@ -0,0 +1,26 @@
use hsmc::statechart;
#[derive(Debug, Clone)]
pub enum Ev { _U }
pub struct Ctx;
statechart! {
M {
context: Ctx;
events: Ev;
default(Parent);
state Parent {
default(A);
default(B);
state A { entry: e; }
state B { entry: e; }
}
}
}
impl MActions for MActionContext<'_> {
fn e(&mut self) {}
}
fn main() {}
@@ -0,0 +1,17 @@
error: multiple `default` declarations in state `Parent`
--> tests/ui/t9_7_duplicate_default.rs:14:9
|
14 | default(A);
| ^^^^^^^
error[E0405]: cannot find trait `MActions` in this scope
--> tests/ui/t9_7_duplicate_default.rs:22:6
|
22 | impl MActions for MActionContext<'_> {
| ^^^^^^^^ not found in this scope
error[E0425]: cannot find type `MActionContext` in this scope
--> tests/ui/t9_7_duplicate_default.rs:22:19
|
22 | impl MActions for MActionContext<'_> {
| ^^^^^^^^^^^^^^ not found in this scope
@@ -0,0 +1,21 @@
use hsmc::statechart;
#[derive(Debug, Clone)]
pub enum Ev { Go }
pub struct Ctx;
statechart! {
M {
context: Ctx;
events: Ev;
default(A);
state A {
on(Go) => NonExistent;
}
}
}
impl MActions for MActionContext<'_> {}
fn main() {}
@@ -0,0 +1,50 @@
warning: unexpected `cfg` condition value: `tokio`
--> tests/ui/t9_8_unknown_target.rs:8:1
|
8 | / statechart! {
9 | | M {
10 | | context: Ctx;
11 | | events: Ev;
... |
17 | | }
| |_^
|
= note: no expected values for `feature`
= note: using a cfg inside a macro will use the cfgs from the destination crate and not the ones from the defining crate
= help: try referring to `statechart` crate for guidance on how handle this unexpected cfg
= help: the macro `statechart` may come from an old version of the `hsmc_macros` crate, try updating your dependency with `cargo update -p hsmc_macros`
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg/cargo-specifics.html> for more information about checking conditional configuration
= note: `#[warn(unexpected_cfgs)]` on by default
= note: this warning originates in the macro `statechart` (in Nightly builds, run with -Z macro-backtrace for more info)
warning: unexpected `cfg` condition value: `embassy`
--> tests/ui/t9_8_unknown_target.rs:8:1
|
8 | / statechart! {
9 | | M {
10 | | context: Ctx;
11 | | events: Ev;
... |
17 | | }
| |_^
|
= note: no expected values for `feature`
= note: using a cfg inside a macro will use the cfgs from the destination crate and not the ones from the defining crate
= help: try referring to `statechart` crate for guidance on how handle this unexpected cfg
= help: the macro `statechart` may come from an old version of the `hsmc_macros` crate, try updating your dependency with `cargo update -p hsmc_macros`
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg/cargo-specifics.html> for more information about checking conditional configuration
= note: this warning originates in the macro `statechart` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0046]: not all trait items implemented, missing: `NonExistent`
--> tests/ui/t9_8_unknown_target.rs:19:1
|
8 | / statechart! {
9 | | M {
10 | | context: Ctx;
11 | | events: Ev;
... |
17 | | }
| |_- `NonExistent` from trait
18 |
19 | impl MActions for MActionContext<'_> {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `NonExistent` in implementation
@@ -0,0 +1,23 @@
use hsmc::statechart;
#[derive(Debug, Clone)]
pub enum Ev { Halt, Abort }
pub struct Ctx;
statechart! {
M {
context: Ctx;
events: Ev;
default(A);
terminate(Halt);
terminate(Abort);
state A { entry: e; }
}
}
impl MActions for MActionContext<'_> {
fn e(&mut self) {}
}
fn main() {}
@@ -0,0 +1,17 @@
error: multiple `terminate` declarations
--> tests/ui/t9_9_multiple_terminate.rs:13:5
|
13 | terminate(Halt);
| ^^^^^^^^^
error[E0405]: cannot find trait `MActions` in this scope
--> tests/ui/t9_9_multiple_terminate.rs:19:6
|
19 | impl MActions for MActionContext<'_> {
| ^^^^^^^^ not found in this scope
error[E0425]: cannot find type `MActionContext` in this scope
--> tests/ui/t9_9_multiple_terminate.rs:19:19
|
19 | impl MActions for MActionContext<'_> {
| ^^^^^^^^^^^^^^ not found in this scope
@@ -0,0 +1,92 @@
//! Compile-pass parse test mirroring spec §8 "Complete Example: Nested States".
//! The only deviation from the spec snippet is the outer-brace macro call syntax.
#![allow(unexpected_cfgs)]
use hsmc::{statechart, Duration};
#[derive(Debug, Clone)]
pub enum MyEvent {
Event1,
Event2,
Halt,
}
#[derive(Default)]
pub struct MyContext {
pub log: Vec<String>,
}
statechart! {
Name {
context: MyContext;
events: MyEvent;
default(FirstState);
entry: mainentry, mainentry2, mainentry3;
exit: mainexit;
terminate(Halt);
// Root-level transitions and actions
on(after Duration::from_secs(1)) => FirstState;
on(after Duration::from_secs_f64(0.99)) => asldkfj;
on(after Duration::from_secs_f64(0.99)) => asldkfj1;
on(after Duration::from_secs_f64(0.99)) => asldkfj2;
state FirstState {
default(SubState);
entry: blah_entry;
exit: blah_exit;
on(Event2) => SecondState;
on(Event1) => x_action;
state SubState {
default(SubSubState);
state SubSubState {
default(SubSubSubState);
state SubSubSubState {
entry: subsubsub_entry;
}
}
}
}
state SecondState {
on(Event1) => FirstState;
}
state XSecondState {
entry: x_entry;
}
state SomeEndState {
entry: end_entry;
exit: end_exit;
on(after Duration::from_secs_f64(0.23)) => something;
}
}
}
impl NameActions for NameActionContext<'_> {
fn mainentry(&mut self) { self.log.push("mainentry".into()); }
fn mainentry2(&mut self) { self.log.push("mainentry2".into()); }
fn mainentry3(&mut self) { self.log.push("mainentry3".into()); }
fn mainexit(&mut self) { self.log.push("mainexit".into()); }
fn blah_entry(&mut self) { self.log.push("blah_entry".into()); }
fn blah_exit(&mut self) { self.log.push("blah_exit".into()); }
fn x_action(&mut self) { self.log.push("x_action".into()); }
fn asldkfj(&mut self) { self.log.push("asldkfj".into()); }
fn asldkfj1(&mut self) { self.log.push("asldkfj1".into()); }
fn asldkfj2(&mut self) { self.log.push("asldkfj2".into()); }
fn x_entry(&mut self) { self.log.push("x_entry".into()); }
fn end_entry(&mut self) { self.log.push("end_entry".into()); }
fn end_exit(&mut self) { self.log.push("end_exit".into()); }
fn something(&mut self) { self.log.push("something".into()); }
fn subsubsub_entry(&mut self) { self.log.push("subsubsub_entry".into()); }
}
fn main() {
let _ = Name::new(MyContext::default());
}