gv_fsm 0.0.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/gv_fsm +54 -5
- data/lib/gv_fsm.rb +22 -7
- data/lib/templates.rb +127 -60
- data/lib/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2896e7f9617c0ed1a9917e78cd947d1da5ceef2d8554727a4db268a7f70e6229
|
|
4
|
+
data.tar.gz: 63d897002c102d5291ecbfd7b4c854f8c9fc029e01155ba573d13e2043621afb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ae5be7ee072d06983c139320227c6b234d703b2556071df085cb3f6a3365d3d1c6a8cf38f9f6daac0b866ed5b77c4726933eee69eda7aa8d9ffe17b9f2c2ca0b
|
|
7
|
+
data.tar.gz: bd1129ccf730d930b42a8ba53f83eb2adeb6d66368ebd9aab6466c69e2b951d6b461d0e04579254153d2c753c390f16f023806040615e6ac52d53f3b2f60c71a
|
data/bin/gv_fsm
CHANGED
|
@@ -1,11 +1,60 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
3
|
require "gv_fsm"
|
|
4
|
+
require 'optparse'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
sm = GV_FSM::FSM.new
|
|
7
|
+
|
|
8
|
+
options = {header: true, source: true}
|
|
9
|
+
OptionParser.new do |parser|
|
|
10
|
+
parser.banner =<<~EOB
|
|
11
|
+
Graphviz to Finite State Machine generator
|
|
12
|
+
Version: #{GV_FSM::VERSION}
|
|
13
|
+
See also https://github.com/pbosetti/gv_fsm
|
|
14
|
+
|
|
15
|
+
Usage: gv_fsm [options] scheme.dot
|
|
16
|
+
EOB
|
|
17
|
+
|
|
18
|
+
parser.on("-p", "--project PROJECT_NAME",
|
|
19
|
+
"Set the project name to PROJECT_NAME") do |pn|
|
|
20
|
+
sm.project_name = pn
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
parser.on("-d", "--description DESCRIPTION", "Use DESCRITION string in header") do |desc|
|
|
24
|
+
sm.description = desc
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
parser.on("-o", "--output_file NAME", "Use NAME for generated .c and .h files") do |f|
|
|
28
|
+
sm.cname = f
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
parser.on("-h", "--header-only", "Only generate header file") do
|
|
32
|
+
options[:source] = false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
parser.on("-s", "--source-only", "Only generate source file") do
|
|
36
|
+
options[:header] = false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
parser.on("-x", "--prefix PREFIX", "Prepend PREFIX to names of generated functions and objects") do |p|
|
|
40
|
+
sm.prefix = p
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end.parse!
|
|
44
|
+
|
|
45
|
+
raise ArgumentError, "I need the path to a Graphviz file!" unless ARGV[0]
|
|
46
|
+
raise ArgumentError, "#{ARGV[0]} does not look like a Graphviz file!" unless File.extname(ARGV[0]) == ".dot"
|
|
47
|
+
|
|
48
|
+
sm.parse(ARGV[0])
|
|
49
|
+
|
|
50
|
+
puts "Parsed #{sm.dotfile}.\nGenerating C stub for states: #{sm.states_list.join(", ")}."
|
|
51
|
+
if options[:header] then
|
|
52
|
+
sm.generate_h
|
|
53
|
+
puts "Generated header #{sm.cname}.h"
|
|
54
|
+
end
|
|
55
|
+
if options[:source] then
|
|
56
|
+
sm.generate_c
|
|
57
|
+
puts "Generated source #{sm.cname}.c"
|
|
58
|
+
end
|
|
8
59
|
|
|
9
|
-
SM.generate_h
|
|
10
|
-
SM.generate_c
|
|
11
60
|
|
data/lib/gv_fsm.rb
CHANGED
|
@@ -8,17 +8,22 @@ require File.expand_path("../version.rb", __FILE__)
|
|
|
8
8
|
|
|
9
9
|
module GV_FSM
|
|
10
10
|
class FSM
|
|
11
|
-
attr_reader :states, :transitions, :dotfile
|
|
11
|
+
attr_reader :states, :transitions, :dotfile, :prefix
|
|
12
12
|
attr_accessor :project_name, :description, :cname
|
|
13
13
|
include GV_FSM::Templates
|
|
14
14
|
|
|
15
|
-
def initialize(filename)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
def initialize(filename = nil)
|
|
16
|
+
@prefix = ""
|
|
17
|
+
parse(filename) if filename
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def prefix=(v)
|
|
21
|
+
@prefix = v + '_'
|
|
19
22
|
end
|
|
20
23
|
|
|
21
24
|
def parse(filename)
|
|
25
|
+
raise ArgumentError, "File must be in .dot format" unless File.extname(filename) == ".dot"
|
|
26
|
+
@cname = File.basename(filename, ".dot") unless (@cname and ! @cname.empty?)
|
|
22
27
|
@dotfile = filename
|
|
23
28
|
@states = []
|
|
24
29
|
@transitions = []
|
|
@@ -26,11 +31,12 @@ module GV_FSM
|
|
|
26
31
|
g.each_node do |id|
|
|
27
32
|
n = g.get_node(id)
|
|
28
33
|
label = n[:label].source.empty? ? "do_#{id}" : n[:label].source
|
|
29
|
-
@states << {id: id, function: label}
|
|
34
|
+
@states << {id: id, function: @prefix+label}
|
|
30
35
|
end
|
|
31
36
|
g.each_edge do |e|
|
|
32
37
|
from = e.node_one
|
|
33
38
|
to = e.node_two
|
|
39
|
+
next unless e[:label]
|
|
34
40
|
case e[:label].source
|
|
35
41
|
when ""
|
|
36
42
|
label = nil
|
|
@@ -39,7 +45,7 @@ module GV_FSM
|
|
|
39
45
|
else
|
|
40
46
|
label = e[:label].source
|
|
41
47
|
end
|
|
42
|
-
@transitions << {from: from, to: to, function: label}
|
|
48
|
+
@transitions << {from: from, to: to, function: label ? @prefix+label : nil}
|
|
43
49
|
end
|
|
44
50
|
end
|
|
45
51
|
end
|
|
@@ -82,6 +88,15 @@ module GV_FSM
|
|
|
82
88
|
return dest
|
|
83
89
|
end
|
|
84
90
|
|
|
91
|
+
def transitions_paths
|
|
92
|
+
path = {}
|
|
93
|
+
@transitions.each do |t|
|
|
94
|
+
path[t[:function]] = [] unless path[t[:function]]
|
|
95
|
+
path[t[:function]] << {from: t[:from], to: t[:to]}
|
|
96
|
+
end
|
|
97
|
+
return path
|
|
98
|
+
end
|
|
99
|
+
|
|
85
100
|
def generate_c(filename = @cname)
|
|
86
101
|
File.open("#{filename}.c", "w") do |f|
|
|
87
102
|
f.puts ERB.new(HEADER, 0, "<>").result(binding)
|
data/lib/templates.rb
CHANGED
|
@@ -2,90 +2,124 @@
|
|
|
2
2
|
module GV_FSM
|
|
3
3
|
module Templates
|
|
4
4
|
HEADER =<<~EOHEADER
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
5
|
+
/******************************************************************************
|
|
6
|
+
Finite State Machine
|
|
7
|
+
Project: <%= @project_name or @dotfile %>
|
|
8
|
+
Description: <%= @description or "<none given>" %>
|
|
9
|
+
|
|
10
|
+
Generated by gv_fsm ruby gem, see https://rubygems.org/gems/gv_fsm
|
|
11
|
+
gv_fsm version <%= GV_FSM::VERSION %>
|
|
12
|
+
Generation date: <%= Time.now %>
|
|
13
|
+
Generated from: <%= @dotfile %>
|
|
14
|
+
The finite state machine has:
|
|
15
|
+
<%= @states.count %> states
|
|
16
|
+
<%= @transitions.count %> transitions
|
|
17
|
+
<%= transition_functions_list.select {|e| e != 'NULL'}.count %> transition functions
|
|
18
|
+
<% if @prefix != '' %>
|
|
19
|
+
Functions and types have been generated with prefix "<%= @prefix %>"
|
|
20
|
+
<% end %>
|
|
21
|
+
******************************************************************************/
|
|
16
22
|
|
|
17
23
|
EOHEADER
|
|
18
24
|
|
|
19
25
|
HH =<<~EOH
|
|
20
|
-
#ifndef <%=
|
|
21
|
-
#define <%=
|
|
26
|
+
#ifndef <%= @cname.upcase %>_H
|
|
27
|
+
#define <%= @cname.upcase %>_H
|
|
22
28
|
#include <stdlib.h>
|
|
23
29
|
|
|
30
|
+
// State data object
|
|
31
|
+
// By default set to void; override this typedef or load the proper
|
|
32
|
+
// header if you need
|
|
33
|
+
typedef void <%= @prefix %>state_data_t;
|
|
34
|
+
|
|
35
|
+
// NOTHING SHALL BE CHANGED AFTER THIS LINE!
|
|
36
|
+
|
|
24
37
|
// List of states
|
|
25
38
|
typedef enum {
|
|
26
|
-
<%
|
|
27
|
-
STATE_<%= s[:id].upcase %><%= i == 0 ? " = 0" : "" %>,
|
|
39
|
+
<% @states.each_with_index do |s, i| %>
|
|
40
|
+
<%= @prefix.upcase %>STATE_<%= s[:id].upcase %><%= i == 0 ? " = 0" : "" %>,
|
|
28
41
|
<% end %>
|
|
29
|
-
NUM_STATES,
|
|
30
|
-
NO_CHANGE
|
|
31
|
-
} state_t;
|
|
42
|
+
<%= @prefix.upcase %>NUM_STATES,
|
|
43
|
+
<%= @prefix.upcase %>NO_CHANGE
|
|
44
|
+
} <%= @prefix %>state_t;
|
|
32
45
|
|
|
33
|
-
const char *state_names[] = {<%=
|
|
46
|
+
const char *state_names[] = {<%= states_list.map {|sn| '"'+sn+'"'}.join(", ") %>};
|
|
34
47
|
|
|
35
48
|
// State function and state transition prototypes
|
|
36
|
-
typedef state_t state_func_t(
|
|
37
|
-
typedef void transition_func_t(
|
|
38
|
-
|
|
39
|
-
// state functions
|
|
40
|
-
<% self.states.each do |s| %>
|
|
41
|
-
state_t <%= s[:function] %>(void *data);
|
|
42
|
-
<% end %>
|
|
49
|
+
typedef <%= @prefix %>state_t state_func_t(<%= @prefix %>state_data_t *data);
|
|
50
|
+
typedef void transition_func_t(<%= @prefix %>state_data_t *data);
|
|
43
51
|
|
|
44
|
-
//
|
|
45
|
-
<%
|
|
46
|
-
|
|
47
|
-
void <%= t %>(void *data);
|
|
52
|
+
// State functions
|
|
53
|
+
<% @states.each do |s| %>
|
|
54
|
+
<%= @prefix %>state_t <%= s[:function] %>(<%= @prefix %>state_data_t *data);
|
|
48
55
|
<% end %>
|
|
49
56
|
|
|
50
57
|
// List of state functions
|
|
51
|
-
state_func_t *const state_table[NUM_STATES] = {
|
|
52
|
-
|
|
58
|
+
state_func_t *const <%= @prefix %>state_table[<%= @prefix.upcase %>NUM_STATES] = {
|
|
59
|
+
<% @states.each do |s| %>
|
|
60
|
+
<%= s[:function] %>, // in state <%= s[:id] %>
|
|
61
|
+
<% end %>
|
|
53
62
|
};
|
|
63
|
+
|
|
64
|
+
<% if transition_functions_list.count > 0 then %>
|
|
65
|
+
// Transition functions
|
|
66
|
+
<% transition_functions_list.each do |t| %>
|
|
67
|
+
<% next if t == "NULL" %>
|
|
68
|
+
void <%= t %>(<%= @prefix %>state_data_t *data);
|
|
69
|
+
<% end %>
|
|
54
70
|
|
|
55
71
|
// Table of transition functions
|
|
56
|
-
transition_func_t *const transition_table[NUM_STATES][NUM_STATES] = {
|
|
57
|
-
<% sl =
|
|
58
|
-
<% fw =
|
|
59
|
-
<% sw =
|
|
72
|
+
transition_func_t *const <%= @prefix %>transition_table[<%= @prefix.upcase %>NUM_STATES][<%= @prefix.upcase %>NUM_STATES] = {
|
|
73
|
+
<% sl = states_list %>
|
|
74
|
+
<% fw = transition_functions_list.max {|a, b| a.length <=> b.length}.length %>
|
|
75
|
+
<% sw = states_list.max {|a, b| a.length <=> b.length}.length %>
|
|
60
76
|
/* <%= "states:".ljust(sw) %> <%= sl.map {|e| e.ljust(fw) }.join(", ") %> */
|
|
61
|
-
<%
|
|
77
|
+
<% transitions_map.each_with_index do |l, i| %>
|
|
62
78
|
/* <%= sl[i].ljust(sw) %> */ {<%= l.map {|e| e.ljust(fw)}.join(", ") %>},
|
|
63
79
|
<% end %>
|
|
64
80
|
};
|
|
81
|
+
<% else %>
|
|
82
|
+
// No transition functions
|
|
83
|
+
<% end %>
|
|
65
84
|
|
|
66
85
|
// state manager
|
|
67
|
-
state_t run_state(state_t cur_state, void *data);
|
|
86
|
+
<%= @prefix %>state_t <%= @prefix %>run_state(<%= @prefix %>state_t cur_state, void *data);
|
|
68
87
|
|
|
69
88
|
#endif
|
|
70
89
|
EOH
|
|
71
90
|
|
|
72
91
|
CC =<<~EOC
|
|
73
92
|
#include <syslog.h>
|
|
74
|
-
#include "<%=
|
|
93
|
+
#include "<%= @cname %>.h"
|
|
75
94
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
95
|
+
<% placeholder = "Your Code Here" %>
|
|
96
|
+
// SEARCH FOR <%= placeholder %> FOR CODE INSERTION POINTS!
|
|
97
|
+
|
|
98
|
+
// ____ _ _
|
|
99
|
+
// / ___|| |_ __ _| |_ ___
|
|
100
|
+
// \\___ \\| __/ _` | __/ _ \\
|
|
101
|
+
// ___) | || (_| | || __/
|
|
102
|
+
// |____/ \\__\\__,_|\\__\\___|
|
|
103
|
+
//
|
|
104
|
+
// __ _ _
|
|
105
|
+
// / _|_ _ _ __ ___| |_(_) ___ _ __ ___
|
|
106
|
+
// | |_| | | | '_ \\ / __| __| |/ _ \\| '_ \\/ __|
|
|
107
|
+
// | _| |_| | | | | (__| |_| | (_) | | | \\__ \\
|
|
108
|
+
// |_| \\__,_|_| |_|\\___|\\__|_|\\___/|_| |_|___/
|
|
109
|
+
//
|
|
110
|
+
<% dest = destinations.dup %>
|
|
111
|
+
<% @states.each do |s| %>
|
|
79
112
|
<% stable = true if dest[s[:id]].include? s[:id] %>
|
|
80
|
-
<% dest[s[:id]].map! {|n| "STATE_"+n.upcase} %>
|
|
113
|
+
<% dest[s[:id]].map! {|n| (@prefix+"STATE_"+n).upcase} %>
|
|
81
114
|
<% if dest[s[:id]].empty? or stable then
|
|
82
|
-
dest[s[:id]].unshift "NO_CHANGE"
|
|
115
|
+
dest[s[:id]].unshift @prefix.upcase+"NO_CHANGE"
|
|
83
116
|
end %>
|
|
84
|
-
|
|
85
|
-
|
|
117
|
+
// Function to be executed in state <%= s[:id] %>
|
|
118
|
+
<%= @prefix %>state_t <%= s[:function] %>(<%= @prefix %>state_data_t *data) {
|
|
119
|
+
<%= @prefix %>state_t next_state = <%= dest[s[:id]].first %>;
|
|
86
120
|
|
|
87
121
|
syslog(LOG_INFO, "[FSM] In state <%= s[:id] %>");
|
|
88
|
-
/*
|
|
122
|
+
/* <%= placeholder %> */
|
|
89
123
|
|
|
90
124
|
// valid return states: <%= dest[s[:id]].join(", ") %>
|
|
91
125
|
switch (next_state) {
|
|
@@ -95,43 +129,76 @@ module GV_FSM
|
|
|
95
129
|
break;
|
|
96
130
|
default:
|
|
97
131
|
syslog(LOG_WARNING, "[FSM] Cannot pass from <%= s[:id] %> to %s, remaining in this state", state_names[next_state]);
|
|
98
|
-
next_state = NO_CHANGE;
|
|
132
|
+
next_state = <%= @prefix.upcase %>NO_CHANGE;
|
|
99
133
|
}
|
|
100
134
|
return next_state;
|
|
101
135
|
}
|
|
102
136
|
|
|
103
137
|
<% end %>
|
|
104
138
|
|
|
105
|
-
|
|
106
|
-
|
|
139
|
+
<% if transition_functions_list.count > 0 then %>
|
|
140
|
+
// _____ _ _ _
|
|
141
|
+
// |_ _| __ __ _ _ __ ___(_) |_(_) ___ _ __
|
|
142
|
+
// | || '__/ _` | '_ \\/ __| | __| |/ _ \\| '_ \\
|
|
143
|
+
// | || | | (_| | | | \\__ \\ | |_| | (_) | | | |
|
|
144
|
+
// |_||_| \\__,_|_| |_|___/_|\\__|_|\\___/|_| |_|
|
|
145
|
+
//
|
|
146
|
+
// __ _ _
|
|
147
|
+
// / _|_ _ _ __ ___| |_(_) ___ _ __ ___
|
|
148
|
+
// | |_| | | | '_ \\ / __| __| |/ _ \\| '_ \\/ __|
|
|
149
|
+
// | _| |_| | | | | (__| |_| | (_) | | | \\__ \\
|
|
150
|
+
// |_| \\__,_|_| |_|\\___|\\__|_|\\___/|_| |_|___/
|
|
151
|
+
//
|
|
152
|
+
|
|
153
|
+
<% transition_functions_list.each do |t| %>
|
|
107
154
|
<% next if t == "NULL" %>
|
|
108
|
-
|
|
155
|
+
<% tpaths = transitions_paths[t] %>
|
|
156
|
+
// This function is called in <%= tpaths.count %> transition<%= tpaths.count == 1 ? '' : 's' %>:
|
|
157
|
+
<% tpaths.each_with_index do |e, i| %>
|
|
158
|
+
// <%= i+1 %>. from <%= e[:from] %> to <%= e[:to] %>
|
|
159
|
+
<% end %>
|
|
160
|
+
void <%= t %>(<%= @prefix %>state_data_t *data) {
|
|
109
161
|
syslog(LOG_INFO, "[FSM] State transition <%= t %>");
|
|
110
|
-
/*
|
|
162
|
+
/* <%= placeholder %> */
|
|
111
163
|
}
|
|
112
164
|
|
|
165
|
+
<% end %>
|
|
113
166
|
<% end %>
|
|
114
167
|
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
168
|
+
// ____ _ _
|
|
169
|
+
// / ___|| |_ __ _| |_ ___
|
|
170
|
+
// \\___ \\| __/ _` | __/ _ \\
|
|
171
|
+
// ___) | || (_| | || __/
|
|
172
|
+
// |____/ \\__\\__,_|\\__\\___|
|
|
173
|
+
//
|
|
174
|
+
//
|
|
175
|
+
// _ __ ___ __ _ _ __ __ _ __ _ ___ _ __
|
|
176
|
+
// | '_ ` _ \\ / _` | '_ \\ / _` |/ _` |/ _ \\ '__|
|
|
177
|
+
// | | | | | | (_| | | | | (_| | (_| | __/ |
|
|
178
|
+
// |_| |_| |_|\\__,_|_| |_|\\__,_|\\__, |\\___|_|
|
|
179
|
+
// |___/
|
|
180
|
+
|
|
181
|
+
<%= @prefix %>state_t <%= @prefix %>run_state(<%= @prefix %>state_t cur_state, <%= @prefix %>state_data_t *data) {
|
|
182
|
+
<%= @prefix %>state_t new_state = <%= @prefix %>state_table[cur_state](data);
|
|
183
|
+
<% if transition_functions_list.count > 0 then %>
|
|
184
|
+
transition_func_t *transition = <%= @prefix %>transition_table[cur_state][new_state];
|
|
119
185
|
if (transition)
|
|
120
186
|
transition(data);
|
|
121
|
-
|
|
187
|
+
<% end %>
|
|
188
|
+
return new_state == <%= @prefix.upcase %>NO_CHANGE ? cur_state : new_state;
|
|
122
189
|
};
|
|
123
190
|
|
|
124
191
|
|
|
125
192
|
#ifdef TEST_MAIN
|
|
126
193
|
#include <unistd.h>
|
|
127
194
|
int main() {
|
|
128
|
-
state_t cur_state = STATE_INIT;
|
|
195
|
+
<%= @prefix %>state_t cur_state = <%= @prefix.upcase %>STATE_INIT;
|
|
129
196
|
openlog("SM", LOG_PID | LOG_PERROR, LOG_USER);
|
|
130
197
|
syslog(LOG_INFO, "Starting SM");
|
|
131
198
|
do {
|
|
132
|
-
cur_state = run_state(cur_state, NULL);
|
|
199
|
+
cur_state = <%= @prefix %>run_state(cur_state, NULL);
|
|
133
200
|
sleep(1);
|
|
134
|
-
} while (cur_state != STATE_STOP);
|
|
201
|
+
} while (cur_state != <%= @prefix.upcase %>STATE_STOP);
|
|
135
202
|
return 0;
|
|
136
203
|
}
|
|
137
204
|
#endif
|
data/lib/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gv_fsm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Paolo Bosetti
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2020-08-
|
|
11
|
+
date: 2020-08-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ruby-graphviz
|