gv_fsm 0.0.3 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 37d68711f43acf07c3071b793721cf34301e3b16dd01dbaeeae5083346337e98
4
- data.tar.gz: d47b62e5dcc5f7feafecc2e27744e94e2a96c8ed81ffe6f9425545558e8802d6
3
+ metadata.gz: 3579818099a2f3e8d172c9534af08877f3e4b27f4fc09f9c9aa77c6275256ce3
4
+ data.tar.gz: 5b4acdd1da41b63fbac514f2a481766c5b5c08ba34881ef708d11871910e8d0d
5
5
  SHA512:
6
- metadata.gz: 06b70b565f5e2aacea2840528cbc7da9150f7bba5d680502d1e8103986f8b9f9162bde162ee217fa9f0a6c8d8ee635a1c41db068f5b5df198200e414af01b0cb
7
- data.tar.gz: fc11849dda745e5e0fc3d3ade2356c1ec6d201e50c9093db98f2d01878e2c7b3e0f1871762811d590ad62b34d5307527f983e896d3aa10de4e36ee43796b0fb0
6
+ metadata.gz: 5f481dacfc667927287eecc1b5f901fd915a593ab533b6de35f573e0cb1b66219c5cd37e24f9c54095e1049f8a7f3f67094530338a9c653cbb72585d1ccbcdf3
7
+ data.tar.gz: 4b835c1bc4f03a6d80e37f64f219b0801756ffaeeedbd7da20f53ffbc428b48efd92ac075c189b5379d30ef125ebaf4417e259b0729d1c9e9ac0a94db08b3e14
data/bin/gv_fsm CHANGED
@@ -5,8 +5,16 @@ require 'optparse'
5
5
 
6
6
  sm = GV_FSM::FSM.new
7
7
 
8
- options = {}
9
- OptionParser.new do |parser|
8
+ options = {header: true, source: true}
9
+ op = 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
+
10
18
  parser.on("-p", "--project PROJECT_NAME",
11
19
  "Set the project name to PROJECT_NAME") do |pn|
12
20
  sm.project_name = pn
@@ -19,12 +27,61 @@ OptionParser.new do |parser|
19
27
  parser.on("-o", "--output_file NAME", "Use NAME for generated .c and .h files") do |f|
20
28
  sm.cname = f
21
29
  end
22
- end.parse!
23
30
 
24
- raise ArgumentError, "I need the path to a Graphviz file!" unless ARGV[0]
25
- raise ArgumentError, "#{ARGV[0]} does not look like a Graphviz file!" unless File.extname(ARGV[0]) == ".dot"
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
+ parser.on("-i", "--ino", "Generate a single .ino file (for Arduino)") do
44
+ sm.ino = true
45
+ end
46
+
47
+ parser.on("-l", "--no-syslog", "Omit syslog calls in stub functions") do
48
+ sm.syslog = false
49
+ end
50
+
51
+ parser.on("-h", "--help", "Prints this help") do
52
+ puts parser
53
+ exit
54
+ end
55
+
56
+ end
57
+
58
+ op.parse!
59
+
60
+ unless ARGV[0]
61
+ STDERR.puts "ERROR: I need the path to a Graphviz file!\n\n"
62
+ STDERR.puts op
63
+ exit
64
+ end
65
+ unless File.extname(ARGV[0]) == ".dot"
66
+ STDERR.puts "ERROR: #{ARGV[0]} does not look like a Graphviz file!\n\n"
67
+ STDERR.puts op
68
+ exit
69
+ end
26
70
 
27
71
  sm.parse(ARGV[0])
28
- sm.generate_h
29
- sm.generate_c
72
+
73
+ puts "Parsed #{sm.dotfile}.\nGenerating C stub for states: #{sm.states_list.join(", ")}."
74
+ if sm.ino then
75
+ sm.generate_ino
76
+ puts "Generated .ino file #{sm.cname}.ino"
77
+ else
78
+ if options[:header] then
79
+ sm.generate_h
80
+ puts "Generated header #{sm.cname}.h"
81
+ end
82
+ if options[:source] then
83
+ sm.generate_c
84
+ puts "Generated source #{sm.cname}.c"
85
+ end
86
+ end
30
87
 
@@ -8,14 +8,21 @@ require File.expand_path("../version.rb", __FILE__)
8
8
 
9
9
  module GV_FSM
10
10
  class FSM
11
- attr_reader :states, :transitions, :dotfile
12
- attr_accessor :project_name, :description, :cname
11
+ attr_reader :states, :transitions, :dotfile, :prefix
12
+ attr_accessor :project_name, :description, :cname, :syslog, :ino
13
13
  include GV_FSM::Templates
14
14
 
15
15
  def initialize(filename = nil)
16
+ @prefix = ""
17
+ @syslog = true
18
+ @ino = false
16
19
  parse(filename) if filename
17
20
  end
18
21
 
22
+ def prefix=(v)
23
+ @prefix = v + '_'
24
+ end
25
+
19
26
  def parse(filename)
20
27
  raise ArgumentError, "File must be in .dot format" unless File.extname(filename) == ".dot"
21
28
  @cname = File.basename(filename, ".dot") unless (@cname and ! @cname.empty?)
@@ -26,11 +33,15 @@ module GV_FSM
26
33
  g.each_node do |id|
27
34
  n = g.get_node(id)
28
35
  label = n[:label].source.empty? ? "do_#{id}" : n[:label].source
29
- @states << {id: id, function: label}
36
+ @states << {id: id, function: @prefix+label}
30
37
  end
31
38
  g.each_edge do |e|
32
39
  from = e.node_one
33
40
  to = e.node_two
41
+ unless e[:label] then
42
+ @transitions << {from: from, to: to, function: nil}
43
+ next
44
+ end
34
45
  case e[:label].source
35
46
  when ""
36
47
  label = nil
@@ -39,7 +50,7 @@ module GV_FSM
39
50
  else
40
51
  label = e[:label].source
41
52
  end
42
- @transitions << {from: from, to: to, function: label}
53
+ @transitions << {from: from, to: to, function: label ? @prefix+label : nil}
43
54
  end
44
55
  end
45
56
  end
@@ -55,7 +66,7 @@ module GV_FSM
55
66
  def transition_functions_list
56
67
  lst = []
57
68
  @transitions.each do |t|
58
- if !lst.include? t[:function] then
69
+ if t[:function] and !lst.include? t[:function] then
59
70
  lst << (t[:function] or "NULL")
60
71
  end
61
72
  end
@@ -82,6 +93,15 @@ module GV_FSM
82
93
  return dest
83
94
  end
84
95
 
96
+ def transitions_paths
97
+ path = {}
98
+ @transitions.each do |t|
99
+ path[t[:function]] = [] unless path[t[:function]]
100
+ path[t[:function]] << {from: t[:from], to: t[:to]}
101
+ end
102
+ return path
103
+ end
104
+
85
105
  def generate_c(filename = @cname)
86
106
  File.open("#{filename}.c", "w") do |f|
87
107
  f.puts ERB.new(HEADER, 0, "<>").result(binding)
@@ -95,6 +115,15 @@ module GV_FSM
95
115
  f.puts ERB.new(HH, 0, "<>").result(binding)
96
116
  end
97
117
  end
118
+
119
+ def generate_ino(filename=@cname)
120
+ @syslog = false
121
+ File.open("#{filename}.ino", "w") do |f|
122
+ f.puts ERB.new(HEADER, 0, "<>").result(binding)
123
+ f.puts ERB.new(HH, 0, "<>").result(binding)
124
+ f.puts ERB.new(CC, 0, "<>").result(binding)
125
+ end
126
+ end
98
127
  end
99
128
 
100
129
  end
@@ -2,139 +2,247 @@
2
2
  module GV_FSM
3
3
  module Templates
4
4
  HEADER =<<~EOHEADER
5
- // Finite State Machine
6
- // Project: <%= self.project_name or self.dotfile %>
7
- // Description: <%= self.description or "<none given>" %>
8
- //
9
- // Generated by gv_fsm ruby gem, see https://rubygems.org/gems/gv_fsm
10
- // gv_fsm version <%= GV_FSM::VERSION %>
11
- // Generation date: <%= Time.now %>
12
- // Generated from: <%= self.dotfile %>
13
- // The finite state machine has:
14
- // <%= self.states.count %> states
15
- // <%= self.transitions.count %> transitions
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
+ <%= transition_functions_list.select {|e| e != 'NULL'}.count %> transition functions
17
+ <% if @prefix != '' %>
18
+ Functions and types have been generated with prefix "<%= @prefix %>"
19
+ <% end %>
20
+ ******************************************************************************/
16
21
 
17
22
  EOHEADER
18
23
 
19
24
  HH =<<~EOH
20
- #ifndef <%= self.cname.upcase %>_H
21
- #define <%= self.cname.upcase %>_H
25
+ <% if !@ino then %>
26
+ #ifndef <%= @cname.upcase %>_H
27
+ #define <%= @cname.upcase %>_H
22
28
  #include <stdlib.h>
29
+ <% end %>
30
+
31
+ // State data object
32
+ // By default set to void; override this typedef or load the proper
33
+ // header if you need
34
+ typedef void <%= @prefix %>state_data_t;
35
+ <% if !@ino then %>
36
+
37
+ // NOTHING SHALL BE CHANGED AFTER THIS LINE!
38
+ <% end %>
23
39
 
24
40
  // List of states
25
41
  typedef enum {
26
- <% self.states.each_with_index do |s, i| %>
27
- STATE_<%= s[:id].upcase %><%= i == 0 ? " = 0" : "" %>,
42
+ <% @states.each_with_index do |s, i| %>
43
+ <%= @prefix.upcase %>STATE_<%= s[:id].upcase %><%= i == 0 ? " = 0" : "" %>,
28
44
  <% end %>
29
- NUM_STATES,
30
- NO_CHANGE
31
- } state_t;
32
-
33
- const char *state_names[] = {<%= self.states_list.map {|sn| '"'+sn+'"'}.join(", ") %>};
45
+ <%= @prefix.upcase %>NUM_STATES,
46
+ <%= @prefix.upcase %>NO_CHANGE
47
+ } <%= @prefix %>state_t;
34
48
 
49
+ // State human-readable names
50
+ const char *state_names[] = {<%= states_list.map {|sn| '"'+sn+'"'}.join(", ") %>};
51
+
52
+ <% if transition_functions_list.count > 0 then %>
35
53
  // State function and state transition prototypes
36
- typedef state_t state_func_t(void *data);
37
- typedef void transition_func_t(void *data);
54
+ typedef <%= @prefix %>state_t state_func_t(<%= @prefix %>state_data_t *data);
55
+ typedef void transition_func_t(<%= @prefix %>state_data_t *data);
56
+ <% else %>
57
+ // State function prototype
58
+ typedef <%= @prefix %>state_t state_func_t(<%= @prefix %>state_data_t *data);
59
+ <%end %>
38
60
 
39
- // state functions
40
- <% self.states.each do |s| %>
41
- state_t <%= s[:function] %>(void *data);
61
+ // State functions
62
+ <% dest = destinations.dup %>
63
+ <% @states.each do |s| %>
64
+ <% stable = true if dest[s[:id]].include? s[:id] %>
65
+ <% dest[s[:id]].map! {|n| (@prefix+"STATE_"+n).upcase} %>
66
+ <% if dest[s[:id]].empty? or stable then
67
+ dest[s[:id]].unshift @prefix.upcase+"NO_CHANGE"
68
+ end %>
69
+ // Function to be executed in state <%= s[:id] %>
70
+ // valid return states: <%= dest[s[:id]].join(", ") %>
71
+ <%= @prefix %>state_t <%= s[:function] %>(<%= @prefix %>state_data_t *data);
42
72
  <% end %>
43
73
 
44
- // transition functions
45
- <% self.transition_functions_list.each do |t| %>
46
- <% next if t == "NULL" %>
47
- void <%= t %>(void *data);
48
- <% end %>
49
74
 
50
75
  // List of state functions
51
- state_func_t *const state_table[NUM_STATES] = {
52
- <%= self.state_functions_list.join(",\n ")%>
76
+ <% fw = state_functions_list.max {|a, b| a.length <=> b.length}.length %>
77
+ state_func_t *const <%= @prefix %>state_table[<%= @prefix.upcase %>NUM_STATES] = {
78
+ <% @states.each do |s| %>
79
+ <%= (s[:function] + ',').ljust(fw+1) %> // in state <%= s[:id] %>
80
+ <% end %>
53
81
  };
82
+
83
+
84
+ <% if transition_functions_list.count > 0 then %>
85
+ // Transition functions
86
+ <% transition_functions_list.each do |t| %>
87
+ <% next if t == "NULL" %>
88
+ void <%= t %>(<%= @prefix %>state_data_t *data);
89
+ <% end %>
54
90
 
55
91
  // Table of transition functions
56
- transition_func_t *const transition_table[NUM_STATES][NUM_STATES] = {
57
- <% sl = self.states_list %>
58
- <% fw = self.transition_functions_list.max {|a, b| a.length <=> b.length}.length %>
59
- <% sw = self.states_list.max {|a, b| a.length <=> b.length}.length %>
92
+ transition_func_t *const <%= @prefix %>transition_table[<%= @prefix.upcase %>NUM_STATES][<%= @prefix.upcase %>NUM_STATES] = {
93
+ <% sl = states_list %>
94
+ <% fw = transition_functions_list.max {|a, b| a.length <=> b.length}.length %>
95
+ <% sw = states_list.max {|a, b| a.length <=> b.length}.length %>
60
96
  /* <%= "states:".ljust(sw) %> <%= sl.map {|e| e.ljust(fw) }.join(", ") %> */
61
- <% self.transitions_map.each_with_index do |l, i| %>
97
+ <% transitions_map.each_with_index do |l, i| %>
62
98
  /* <%= sl[i].ljust(sw) %> */ {<%= l.map {|e| e.ljust(fw)}.join(", ") %>},
63
99
  <% end %>
64
100
  };
101
+ <% else %>
102
+ // No transition functions
103
+ <% end %>
65
104
 
66
105
  // state manager
67
- state_t run_state(state_t cur_state, void *data);
106
+ <%= @prefix %>state_t <%= @prefix %>run_state(<%= @prefix %>state_t cur_state, void *data);
68
107
 
108
+ <% if !@ino then %>
69
109
  #endif
110
+ <% end %>
70
111
  EOH
71
112
 
72
113
  CC =<<~EOC
114
+ <% if !@ino then %>
115
+ <% if @syslog then %>
73
116
  #include <syslog.h>
74
- #include "<%= self.cname %>.h"
117
+ <% end %>
118
+ #include "<%= @cname %>.h"
119
+ <% end %>
75
120
 
76
- // State functions
77
- <% dest = self.destinations.dup %>
78
- <% self.states.each do |s| %>
121
+ <% placeholder = "Your Code Here" %>
122
+ // SEARCH FOR <%= placeholder %> FOR CODE INSERTION POINTS!
123
+
124
+ // ____ _ _
125
+ // / ___|| |_ __ _| |_ ___
126
+ // \\___ \\| __/ _` | __/ _ \\
127
+ // ___) | || (_| | || __/
128
+ // |____/ \\__\\__,_|\\__\\___|
129
+ //
130
+ // __ _ _
131
+ // / _|_ _ _ __ ___| |_(_) ___ _ __ ___
132
+ // | |_| | | | '_ \\ / __| __| |/ _ \\| '_ \\/ __|
133
+ // | _| |_| | | | | (__| |_| | (_) | | | \\__ \\
134
+ // |_| \\__,_|_| |_|\\___|\\__|_|\\___/|_| |_|___/
135
+ //
136
+ <% dest = destinations.dup %>
137
+ <% @states.each do |s| %>
79
138
  <% stable = true if dest[s[:id]].include? s[:id] %>
80
- <% dest[s[:id]].map! {|n| "STATE_"+n.upcase} %>
139
+ <% dest[s[:id]].map! {|n| (@prefix+"STATE_"+n).upcase} %>
81
140
  <% if dest[s[:id]].empty? or stable then
82
- dest[s[:id]].unshift "NO_CHANGE"
141
+ dest[s[:id]].unshift @prefix.upcase+"NO_CHANGE"
83
142
  end %>
84
- state_t <%= s[:function] %>(void *data) {
85
- state_t next_state = <%= dest[s[:id]].first %>;
143
+ // Function to be executed in state <%= s[:id] %>
144
+ // valid return states: <%= dest[s[:id]].join(", ") %>
145
+ <%= @prefix %>state_t <%= s[:function] %>(<%= @prefix %>state_data_t *data) {
146
+ <%= @prefix %>state_t next_state = <%= dest[s[:id]].first %>;
86
147
 
148
+ <% if @syslog then %>
87
149
  syslog(LOG_INFO, "[FSM] In state <%= s[:id] %>");
88
- /* Your code here */
150
+ <% end %>
151
+ /* <%= placeholder %> */
89
152
 
90
- // valid return states: <%= dest[s[:id]].join(", ") %>
91
153
  switch (next_state) {
92
154
  <% dest[s[:id]].each do |str| %>
93
155
  case <%= str %>:
94
156
  <% end %>
95
157
  break;
96
158
  default:
159
+ <% if @syslog then %>
97
160
  syslog(LOG_WARNING, "[FSM] Cannot pass from <%= s[:id] %> to %s, remaining in this state", state_names[next_state]);
98
- next_state = NO_CHANGE;
161
+ <% end %>
162
+ next_state = <%= @prefix.upcase %>NO_CHANGE;
99
163
  }
100
164
  return next_state;
101
165
  }
102
166
 
103
167
  <% end %>
104
168
 
105
- // Transition functions
106
- <% self.transition_functions_list.each do |t| %>
169
+ <% if transition_functions_list.count > 0 then %>
170
+ // _____ _ _ _
171
+ // |_ _| __ __ _ _ __ ___(_) |_(_) ___ _ __
172
+ // | || '__/ _` | '_ \\/ __| | __| |/ _ \\| '_ \\
173
+ // | || | | (_| | | | \\__ \\ | |_| | (_) | | | |
174
+ // |_||_| \\__,_|_| |_|___/_|\\__|_|\\___/|_| |_|
175
+ //
176
+ // __ _ _
177
+ // / _|_ _ _ __ ___| |_(_) ___ _ __ ___
178
+ // | |_| | | | '_ \\ / __| __| |/ _ \\| '_ \\/ __|
179
+ // | _| |_| | | | | (__| |_| | (_) | | | \\__ \\
180
+ // |_| \\__,_|_| |_|\\___|\\__|_|\\___/|_| |_|___/
181
+ //
182
+
183
+ <% transition_functions_list.each do |t| %>
107
184
  <% next if t == "NULL" %>
108
- void <%= t %>(void *data) {
185
+ <% tpaths = transitions_paths[t] %>
186
+ // This function is called in <%= tpaths.count %> transition<%= tpaths.count == 1 ? '' : 's' %>:
187
+ <% tpaths.each_with_index do |e, i| %>
188
+ // <%= i+1 %>. from <%= e[:from] %> to <%= e[:to] %>
189
+ <% end %>
190
+ void <%= t %>(<%= @prefix %>state_data_t *data) {
191
+ <% if @syslog then %>
109
192
  syslog(LOG_INFO, "[FSM] State transition <%= t %>");
110
- /* Your code here */
193
+ <% end %>
194
+ /* <%= placeholder %> */
111
195
  }
112
196
 
197
+ <% end %>
113
198
  <% end %>
114
199
 
115
- // State manager
116
- state_t run_state(state_t cur_state, void *data) {
117
- state_t new_state = state_table[cur_state](data);
118
- transition_func_t *transition = transition_table[cur_state][new_state];
200
+ // ____ _ _
201
+ // / ___|| |_ __ _| |_ ___
202
+ // \\___ \\| __/ _` | __/ _ \\
203
+ // ___) | || (_| | || __/
204
+ // |____/ \\__\\__,_|\\__\\___|
205
+ //
206
+ //
207
+ // _ __ ___ __ _ _ __ __ _ __ _ ___ _ __
208
+ // | '_ ` _ \\ / _` | '_ \\ / _` |/ _` |/ _ \\ '__|
209
+ // | | | | | | (_| | | | | (_| | (_| | __/ |
210
+ // |_| |_| |_|\\__,_|_| |_|\\__,_|\\__, |\\___|_|
211
+ // |___/
212
+
213
+ <%= @prefix %>state_t <%= @prefix %>run_state(<%= @prefix %>state_t cur_state, <%= @prefix %>state_data_t *data) {
214
+ <%= @prefix %>state_t new_state = <%= @prefix %>state_table[cur_state](data);
215
+ <% if transition_functions_list.count > 0 then %>
216
+ transition_func_t *transition = <%= @prefix %>transition_table[cur_state][new_state];
119
217
  if (transition)
120
218
  transition(data);
121
- return new_state == NO_CHANGE ? cur_state : new_state;
219
+ <% end %>
220
+ return new_state == <%= @prefix.upcase %>NO_CHANGE ? cur_state : new_state;
122
221
  };
123
222
 
124
-
223
+ <% if @ino then %>
224
+ /* Example usage:
225
+ <% else %>
125
226
  #ifdef TEST_MAIN
126
227
  #include <unistd.h>
127
228
  int main() {
128
- state_t cur_state = STATE_INIT;
229
+ <% end %>
230
+ <%= @prefix %>state_t cur_state = <%= @prefix.upcase %>STATE_INIT;
231
+ <% if @syslog then %>
129
232
  openlog("SM", LOG_PID | LOG_PERROR, LOG_USER);
130
233
  syslog(LOG_INFO, "Starting SM");
234
+ <% end %>
131
235
  do {
132
- cur_state = run_state(cur_state, NULL);
236
+ cur_state = <%= @prefix %>run_state(cur_state, NULL);
133
237
  sleep(1);
134
- } while (cur_state != STATE_STOP);
238
+ } while (cur_state != <%= @prefix.upcase %>STATE_STOP);
239
+ <% if !@ino then %>
135
240
  return 0;
136
241
  }
137
242
  #endif
243
+ <% else %>
244
+ */
245
+ <% end %>
138
246
  EOC
139
247
  end
140
248
  end
@@ -1,3 +1,3 @@
1
1
  module GV_FSM
2
- VERSION = "0.0.2"
2
+ VERSION = "0.2.2"
3
3
  end
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.0.3
4
+ version: 0.2.2
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-28 00:00:00.000000000 Z
11
+ date: 2020-09-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-graphviz