4 个版本 (重大变更)

使用旧的Rust 2015

0.3.0 2019年6月14日
0.2.0 2019年1月29日
0.1.0 2015年6月29日
0.0.1 2015年3月27日

#1502 in 过程宏

Download history 32/week @ 2024-03-13 38/week @ 2024-03-20 50/week @ 2024-03-27 164/week @ 2024-04-03 191/week @ 2024-04-10 410/week @ 2024-04-17 335/week @ 2024-04-24 167/week @ 2024-05-01 29/week @ 2024-05-08 30/week @ 2024-05-15 33/week @ 2024-05-22 32/week @ 2024-05-29 31/week @ 2024-06-05 32/week @ 2024-06-12 64/week @ 2024-06-19 144/week @ 2024-06-26

每月276次下载
用于 miniconf_mqtt

MIT/Apache

38KB
739

Machine

Join the chat at https://gitter.im/Geal/nom Build Status Coverage Status

功能

此crate定义了三个过程宏,以帮助您编写基于枚举的状态机,无需编写关联的样板代码。

  • 将状态机定义为枚举,每个变体可以包含成员
  • 自动添加错误状态,用于无效转换
  • 如果需要,转换可以有多个结束状态(根据消息内容等条件)
  • 可以生成对状态成员的访问器
  • 在父枚举上生成包装方法和访问器
  • 生成的代码也写入 target/machine 目录,以便进一步检查
  • target/machine 目录中写入点文件,用于生成图形

使用方法

machine可在 crates.io 上获取,并且可以像这样包含到您的Cargo项目中

[dependencies]
machine = "^0.2"

然后像这样将其包含到您的代码中

#[macro_use]
extern crate machine;

示例:交通灯

我们将定义一个表示交通灯的状态机,指定在绿灯状态下允许通过的最大车辆数。

以下机器定义

machine!(
  enum Traffic {
    Green { count: u8 },
    Orange,
    Red
  }
);

将生成以下代码

#[derive(Clone, Debug, PartialEq)]
pub enum Traffic {
    Error,
    Green(Green),
    Orange(Orange),
    Red(Red),
}

#[derive(Clone, Debug, PartialEq)]
pub struct Green {
    count: u8,
}

#[derive(Clone, Debug, PartialEq)]
pub struct Orange {}

#[derive(Clone, Debug, PartialEq)]
pub struct Red {}

impl Traffic {
  pub fn green(count: u8) -> Traffic {
    Traffic::Green(Green { count })
  }
  pub fn orange() -> Traffic {
    Traffic::Orange(Orange {})
  }
  pub fn red() -> Traffic {
    Traffic::Red(Red {})
  }
  pub fn error() -> Traffic {
    Traffic::Error
  }
}

转换

从那里,我们可以定义 Advance 消息以转到下一个颜色,以及相关的转换

#[derive(Clone,Debug,PartialEq)]
pub struct Advance;

transitions!(Traffic,
  [
    (Green, Advance) => Orange,
    (Orange, Advance) => Red,
    (Red, Advance) => Green
  ]
);

这将生成一个包含该状态机消息的枚举,以及在父枚举上的 on_advance 方法。

#[derive(Clone, Debug, PartialEq)]
pub enum TrafficMessages {
    Advance(Advance),
}

impl Traffic {
  pub fn on_advance(self, input: Advance) -> Traffic {
    match self {
      Traffic::Green(state) => Traffic::Orange(state.on_advance(input)),
      Traffic::Orange(state) => Traffic::Red(state.on_advance(input)),
      Traffic::Red(state) => Traffic::Green(state.on_advance(input)),
      _ => Traffic::Error,
    }
  }
}

编译器会抱怨在 GreenOrangeRed 结构中缺少 on_advance

error[E0599]: no method named `on_advance` found for type `Green` in the current scope
  --> tests/t.rs:18:1
   |
4  | / machine!(
5  | |   enum Traffic {
6  | |     Green { count: u8 },
7  | |     Orange,
8  | |     Red,
9  | |   }
10 | | );
   | |__- method `on_advance` not found for this
...
18 | / transitions!(Traffic,
19 | |   [
20 | |     (Green, Advance) => Orange,
21 | |     (Orange, Advance) => Red,
22 | |     (Red, Advance) => Green
23 | |   ]
24 | | );
   | |__^

[...]

transitions 宏处理了样板代码,编写包装方法,并确保状态机接收错误消息时将进入错误状态。但我们仍需要手动为每个状态定义转换函数,因为大部分工作将在那里完成

impl Green {
  pub fn on_advance(self, _: Advance) -> Orange {
    Orange {}
  }
}

impl Orange {
  pub fn on_advance(self, _: Advance) -> Red {
    Red {}
  }
}

impl Red {
  pub fn on_advance(self, _: Advance) -> Green {
    Green {
      count: 0
    }
  }
}

现在我们希望在绿灯状态下添加一个计数通过车辆的消息,并且当至少有10辆车通过时切换到黄灯状态。因此,PassCar 消息只被绿灯状态接受,并且转换有两个可能的最终状态,即绿灯和黄灯。虽然我们可能希望有一个干净的状态机,其中每个状态和消息组合只有一个最终状态,但我们可能会有根据消息值或状态成员的条件,而不需要创建新的状态或消息。

#[derive(Clone,Debug,PartialEq)]
pub struct PassCar { count: u8 }

transitions!(Traffic,
  [
    (Green, Advance) => Orange,
    (Orange, Advance) => Red,
    (Red, Advance) => Green,
    (Green, PassCar) => [Green, Orange]
  ]
);

impl Green {
  pub fn on_pass_car(self, input: PassCar) -> Traffic {
    let count = self.count + input.count;
    if count >= 10 {
      println!("reached max cars count: {}", count);
      Traffic::orange()
    } else {
      Traffic::green(count)
    }
  }
}

on_pass_car 方法可以有多个最终状态,因此它必须返回一个 Traffic

现在生成的代码将包含一个 on_pass_car 方法,用于 Traffic 枚举。请注意,如果除了 Green 之外的状态接收到 PassCar 消息,状态机将进入 Error 状态并无限期地停留在该状态。

#[derive(Clone, Debug, PartialEq)]
pub enum TrafficMessages {
  Advance(Advance),
  PassCar(PassCar),
}

impl Traffic {
  pub fn on_advance(self, input: Advance) -> Traffic {
    match self {
      Traffic::Green(state) => Traffic::Orange(state.on_advance(input)),
      Traffic::Orange(state) => Traffic::Red(state.on_advance(input)),
      Traffic::Red(state) => Traffic::Green(state.on_advance(input)),
      _ => Traffic::Error,
    }
  }

  pub fn on_pass_car(self, input: PassCar) -> Traffic {
    match self {
      Traffic::Green(state) => state.on_pass_car(input),
      _ => Traffic::Error,
    }
  }
}

完整的生成代码可以在 target/machine/traffic.rs 中找到。

机器包还会生成用于 graphviz 的 target/machine/traffic.dot 文件。

digraph Traffic {
Green -> Orange [ label = "Advance" ];
Orange -> Red [ label = "Advance" ];
Red -> Green [ label = "Advance" ];
Green -> Green [ label = "PassCar" ];
Green -> Orange [ label = "PassCar" ];
}

dot -Tpng target/machine/traffic.dot > traffic.png 将生成以下图像

traffic light transitions graph

然后我们可以使用消息来触发转换

// starting in green state, no cars have passed
let mut t = Traffic::Green(Green { count: 0 });

t = t.on_pass_car(PassCar { count: 1});
t = t.on_pass_car(PassCar { count: 2});
// still in green state, 3 cars have passed
assert_eq!(t, Traffic::green(3));

// each advance call will move to the next color
t = t.on_advance(Advance);
assert_eq!(t, Traffic::orange());

t = t.on_advance(Advance);
assert_eq!(t, Traffic::red());

t = t.on_advance(Advance);
assert_eq!(t, Traffic::green(0));
t = t.on_pass_car(PassCar { count: 5 });
assert_eq!(t, Traffic::green(5));

// when more than 10 cars have passed, go to the orange state
t = t.on_pass_car(PassCar { count: 7 });
assert_eq!(t, Traffic::orange());
t = t.on_advance(Advance);
assert_eq!(t, Traffic::red());

// if we try to use the PassCar message on state other than Green,
// we go into the error state
t = t.on_pass_car(PassCar { count: 7 });
assert_eq!(t, Traffic::error());

// once in the error state, we stay in the error state
t = t.on_advance(Advance);
assert_eq!(t, Traffic::error());

方法

methods! 过程宏可以生成用于状态成员访问器的包装方法,或者要求在状态上实现方法

methods!(Traffic,
  [
    Green => get count: u8,
    Green => set count: u8,
    Green, Orange, Red => fn can_pass(&self) -> bool
  ]
);

这将生成

  • 一个用于 Green 状态的 count() 获取器(get)和包装枚举
  • 一个用于 Green 状态的 count_mut() 设置器(set)和包装枚举
  • 一个用于包装枚举的 can_pass() 方法,要求所有状态都实现该方法

方法可以有参数,并且这些参数将按照预期传递给状态的相应方法。

impl Orange {}
impl Red {}
impl Green {
  pub fn count(&self) -> &u8 {
    &self.count
  }

  pub fn count_mut(&mut self) -> &mut u8 {
    &mut self.count
  }
}

impl Traffic {
  pub fn count(&self) -> Option<&u8> {
    match self {
      Traffic::Green(ref v) => Some(v.count()),
      _ => None,
    }
  }

  pub fn count_mut(&mut self) -> Option<&mut u8> {
    match self {
      Traffic::Green(ref mut v) => Some(v.count_mut()),
      _ => None,
    }
  }

  pub fn can_pass(&self) -> Option<bool> {
    match self {
      Traffic::Green(ref v) => Some(v.can_pass()),
      Traffic::Orange(ref v) => Some(v.can_pass()),
      Traffic::Red(ref v) => Some(v.can_pass()),
      _ => None,
    }
  }
}

现在我们可以添加剩余的方法,并得到一个工作的状态机

impl Green {
  pub fn can_pass(&self) -> bool {
    true
  }
}

impl Orange {
  pub fn can_pass(&self) -> bool {
    false
  }
}

impl Red {
  pub fn can_pass(&self) -> bool {
    false
  }
}

许可证

在以下任一许可下授权

任由您选择。

贡献

除非您明确声明,否则您提交的任何有意包含在作品中的贡献,根据 Apache-2.0 许可证的定义,将如上所述双许可,没有任何附加条款或条件。

依赖关系

~2MB
~48K SLoC