6个版本
0.6.1 | 2020年11月2日 |
---|---|
0.6.0 | 2020年11月2日 |
0.2.0 | 2019年3月31日 |
0.1.2 | 2019年3月23日 |
#748 in 开发工具
每月 25 次下载
110KB
2.5K SLoC
FSM生成器
安装
cargo install -f fsm_gen
您可以从仓库下载并像往常一样构建和安装
cargo build --release
cargo install --path . -f
版本
0.6
cpp
生成代码,可选转换为error
状态- 现在我们谈论的是
模板
而不是语言
0.5
- 迁移到 tera 模板
- 添加了dot文件生成
0.4
- 在C++上重新定义了错误动作(查看示例iceberg)
- 修复了在当前目录运行时的错误
- 为生成的.h和.cpp文件添加了完整的建议文件
0.3
- 多行守卫没有状态名称
- 多守卫和多动作
- 输入中的'_'表示任何其他输入
- 负守卫
- 如果没有转换,它将生成一个错误
- 更新rust-peg库并使用宏
- 如果抛出异常,我们将进入错误转换
- 更好的错误信息
- 在解析时显示行号
- 检查孤儿状态
- makefile安装,测试cpp
0.2
- 动作
- 私有hpp(手动文件)
- 模板函数,用于专门处理事务变化
- 函数的匿名
namespace
- 在fsm语法上添加了注释支持
待办事项
-
错误转换是特殊的
-
改进异常控制
-
模板的web界面
-
从文件读取模板
-
更新idata以使用ispush和btrees等
-
完成cpp示例并在README.md上更新
-
检查fsm格式
- 检测重复状态
- 检测重复输入和守卫
-
输出带有信号
-
添加模板
目标
所有计算过程都由接收输入并通过生成输出进行处理组成。从简单到复杂。
有时,过程取决于上下文,并且需要管理状态。
因此,消息和状态管理是任何软件过程的基本元素。
这就是为什么我很早就为这两个元素编写了两个外部DSL(我在生产中仍在使用它们,并且它们非常有帮助)。
在这个存储库中,我重写了一个,即状态机的代码生成器。
目前它生成C++代码(我在生产中最紧迫的目标)。
您可以在输入结构体和每个状态上都有数据(字段)
为了解释系统,我将使用cpp_test/fsm中的示例。
此示例是关于编写一个处理登录请求的系统的。
首先,服务器会被要求提供密码。
这个密钥将用于在登录请求中编码用户名和密码(这个将通过哈希函数传递)。
这种编码将是不可逆的(哈希函数)。服务器将执行相同的操作(从密码的哈希值开始)来验证其有效性。
如果一切正常,它将发送一个登录确认。
图表看起来是这样的
可以写出一系列转换
// Example of fsm to manage login
// on server side
[init]
rq_key -> w_login / send_key
timer -> init
[w_login]
rq_login -> login / send_login
timer
& timeout -> error
-> w_login
[login]
rq_logout -> logout / send_logout
heartbeat -> login / update_hb
timer
& timeout -> logout
-> login
[logout]
timer -> logout
[error]
_ -> error
这是此工具生成代码的输入
实际上,之前的图表也是从这个DSL
生成的(它生成了一个graphviz
dot文件)
元素
状态
[init]
...
init,w_login,login ... 是状态
根据输入和状态(稍后将看到其值),系统将转换到新状态。
转换
rq_key -> w_login
如果我们收到一个输入(在这种情况下为rq_key
),我们将转到下一个状态(w_login
)
输入
状态机接收到的元素。
INPUT
v
rq_key -> w_login
在示例中,它们是 rq_key,rq_login,rq_logout,heartbeat 和 timer。
守卫
根据状态和输入调用的函数,以决定前进的方式。
GUARD
v
rq_login & valid -> login / send_login
你可以有多个守卫
rq_login & valid & guard2 -> login / send_login
当你应用不同的选项守卫(或组合)...
在示例中,我们有 valid,timeout,ontime。
rq_login
& valid -> login / send_login
-> logout / log_err
可以用负守卫来写
rq_login
& !valid -> logout / log_err
-> login / send_login
最终状态
->箭头后面是我们将转换到的状态。
FINAL_STATUS
v
rq_key -> w_login
动作
我们可以在执行事务时定义要执行的操作。
这将在最终状态和'/'之后。
ACTION
v
rq_login -> logout / log_err
你可以有多个操作
rq_login -> logout / log_err action2
在这个例子中,我们有 send_key,send_login...
特殊转换
在所有状态下,都必须考虑所有输入。
但是,许多转换是相同的(通常是错误情况)。
这用输入 _ 标记
考虑这个状态示例
[init]
rq_key -> w_login / send_key
timer -> init
_ -> logout / log_err
_ 表示 ... 任何其他输入 ...
因此,考虑在此状态中所有可能的输入
error
状态和隐式转换
error
是一个特殊状态
你可以显式地移动到 error
状态。
任何未定义的转换都将最终结束在错误状态。
你还可以在转换函数上放置一些验证,并在失败的情况下移动到错误状态(即使它没有在 fsm
上显式编写)
这是因为检查参数如此常见,添加守卫会产生很多噪音
在我们的例子...
[init]
rq_key -> w_login / send_key
timer -> init
对于 rq_login
和 rq_logout
没有转换。两者都是隐式的,相当于 ...
[init]
rq_key -> w_login / send_key
timer -> init
_ -> error
这是转换函数控制(当然你可以按需专门化)
// status change functions
template <typename FROM, typename IN, typename TO>
std::variant<TO, st_error_t> fromin2(const FROM &, const IN &) {
// here you could check the params and decide to go to error
// instead to the programmed trasnsition
...
}
这是当你显式编写一个最终结束在错误状态的转换时
template <typename FROM, typename IN>
st_error_t fromin2error(const FROM &, const IN &) {
return st_error_t{...};
}
结束错误转换的另一种方法。如果在处理输入时抛出异常...
[...]
} catch (...) {}
auto nw_st_info = fromin2error<st_init_t, in_rq_key_t>(this->info, in);
log("[init] rq_key error/default -> error", in, info, nw_st_info);
return std::make_shared<error>(nw_st_info);
注释
注释从//
开始,并持续到行尾
用法
要获取帮助...
fsm_gen --help
> fsm_gen -h
fsm_gen 0.6.1
jleahred
Generate code from a simple fsm file
To check the supported templates --show_templs
USAGE:
fsm_gen [FLAGS] [OPTIONS] [fsm_files]...
FLAGS:
-d, --dot-graphviz Generate graphviz dot file
-h, --help Prints help information
--help-cpp Give me some information about generating cpp files
-s, --show-templs Show supported template generators
-V, --version Prints version information
OPTIONS:
-T, --threads <n_threads> Number of threads to use. 0 means one per core ;-) [default: 0]
-t, --templ <templ> Template to generate code (show available --show-templs) [default: cpp]
ARGS:
<fsm_files>... List of fsm files to be processed
默认模板是 c++
(目前只有这一个)
你可以运行
fsm_gen login.fsm
它将生成 c++
您可以传递一个fsm
文件的列表
fsm_gen login.fsm test/seller.fsm test/test2/lift.fsm
代码将在原始.fsm
文件的同一目录下生成
如果您的shell支持它,您可以运行...
fsm_gen **/*.fsm
C++代码生成
从示例login.fsm
开始,系统将创建...
fsm_login_gen.h
fsm_login_gen.cpp
您不需要修改这些文件。
您必须在这些文件上编写您的代码...
fsm_login_types.h
fsm_login_private.hpp
如果不存在,这两个文件将作为参考创建
这两个文件的空参考代码将以注释的形式添加到_gen
文件中。
文件依赖关系
fsm_login_gen.h
完整代码在cpp_test/fsm/fsm_login_gen.h
我们得知它是在何时创建的。
此文件是使用或扩展程序中生成的状态机的起点。
// generated automatically 2019-03-22 11:24:40
// do not modify it manually
根据文件名创建头文件和namespaces
#pragma once
#include <iostream>
#include <memory>
namespace login {
内部类前置声明
class BaseState;
typedef std::shared_ptr<BaseState> SState;
您需要实例化或扩展的fsm
类
// -------------------
// F S M
class Fsm {
public:
Fsm();
~Fsm();
void process(const heartbeat_t& in);
void process(const rq_key_t& in);
void process(const rq_login_t& in);
void process(const rq_logout_t& in);
void process(const timer_t& in);
...
}
fsm_login_gen.cpp
完整代码在cpp_test/fsm/fsm_login_gen.cpp
您必须不修改此文件,并且不需要了解很多关于它的信息
fsm_login_types.h
完整代码在cpp_test/fsm/fsm_login_types.h
在此文件中,您必须声明状态信息类型和输入类型
如果该文件不存在,它将以空数据类型创建
// Code generated automatically to be filled manually
// This file will not be updated by generator
// It's created just the first time as a reference
// Generator will allways create a .reference file to help with
// new methods and so
基于fsm文件的namespace
,我们有两种类型要声明
如果需要,您当然可以在cpp文件中完成这些类型
namespace login {
// status info types
struct st_init_t{};
struct st_w_login_t{};
struct st_login_t{};
struct st_logout_t{};
struct st_error_t{};
// input types
struct in_heartbeat_t {};
struct in_rq_key_t {};
struct in_rq_login_t {};
struct in_rq_logout_t {};
struct in_timer_t {};
} // namespace login
#endif // FSM_LOGIN_H
fsm_login_private.hpp
完整代码在cpp_test/fsm/fsm_login_private.hpp
这是您必须手动维护的另一文件。
// Code generated automatically to be filled manually
// This file will not be updated by generator
// It's created just the first time as a reference
// Generator will allways create a .reference file to help with
// new methods and so
// This file will be included in _gen.cpp
// (anywhere else)
// to make happy some IDEs
#include "fsm_login_types.h"
#include "fsm_login_gen.h"
namespace {
using namespace login;
如您所见,它在匿名namespace
中。
此文件是私有的,它将被_gen.cpp
包含。定义一个匿名namespace
,我们保持此实现为私有。更重要的是,如果忘记了一个实现,或者一个实现是不必要的,编译器会提醒我们。
日志和状态转换是模板。
这是一个日志示例
[init] rq_key -> w_login
[w_login] rq_login(valid) -> login
[login] rq_logout -> logout
这样,您可以根据需要专门化或泛化。
// log
template <typename IN, typename INIT_ST, typename END_ST>
void log(const std::string &txt_trans, const IN &, const INIT_ST &,
const END_ST &) {
std::cout << txt_trans << std::endl;
}
// status change functions
template <typename FROM, typename IN, typename TO>
std::variant<TO, st_error_t> fromin2(const FROM &, const IN &) {
// you can specialize this generic function
return TO{};
}
template <typename FROM, typename IN>
st_error_t fromin2error(const FROM &, const IN &) {
return st_error_t{};
}
日志的第一个参数是一个字符串,包含转换更改信息(初始转换、输入、如果有守卫,则最终转换)
接下来,我们有守卫和操作函数。
// guards
bool valid(const in_rq_login_t& /*in*/, const st_w_login_t& /*st_info*/) { return true; }
bool timeout(const in_timer_t& /*in*/, const st_w_login_t& /*st_info*/) { return true; }
bool timeout(const in_timer_t& /*in*/, const st_login_t& /*st_info*/) { return true; }
// actions
void act_send_key(const st_init_t& /*st_orig*/, const in_rq_key_t& /*in*/, const st_w_login_t& /*st_dest*/) {}
void act_send_login(const st_w_login_t& /*st_orig*/, const in_rq_login_t& /*in*/, const st_login_t& /*st_dest*/) {}
void act_send_logout(const st_login_t& /*st_orig*/, const in_rq_logout_t& /*in*/, const st_logout_t& /*st_dest*/) {}
void act_update_hb(const st_login_t& /*st_orig*/, const in_heartbeat_t& /*in*/, const st_login_t& /*st_dest*/) {}
} // namespace anonymous
图表源
digraph G {
rankdir = BT;
node [shape=plaintext fontname="Arial"];
main -> "fsm_login_gen.h"
"fsm_login_gen.cpp" -> "fsm_login_gen.h"
"fsm_login_gen.h" -> "fsm_login_types.h"
}
依赖关系
~10-21MB
~270K SLoC