gv_fsm 0.1.1 → 0.2.4

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.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/bin/gv_fsm +44 -12
  3. data/lib/gv_fsm.rb +31 -7
  4. data/lib/templates.rb +145 -71
  5. data/lib/version.rb +1 -1
  6. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9cdb522af0bd9bac3a853e75ad379b063657a6bd21ceb2fcad80c427ca6c00ed
4
- data.tar.gz: 350cd6c0dbdbe1e3ff03f8e26eda97b16be7764dc7f5c244a98b3e0997d50b3a
3
+ metadata.gz: 687c6994a931d95a7cc4f190dbe26dd1c392e960f9ef1c41f544f53408f1ad70
4
+ data.tar.gz: 880237c706a106208808ae00a9c42be507dfd5dd5c43d09671f32e419d715b4d
5
5
  SHA512:
6
- metadata.gz: 2b623ee5a2d45345ff3ab13db5605ffa978b30add3b0ca912588d531fbf81f13b3f5a3210b02bbcee29e736115a10b936201e57828635d81c9c2c82eec869e74
7
- data.tar.gz: bc2584dfcc1025b41499bc66ff85113eb2b440031467e1ac74a44450717cc48850cc4c35cfe8594262b59ac6d3e0164c766f439aa9f8eaf6e36e6cdc790262d9
6
+ metadata.gz: b690e25215ce7e994c96e5231759315fcf8804f13e6357c7b36f5a6cd6a925e1f7f6c761cbe494c76d592d967e981cb91b180de094f758919bd676e616376b74
7
+ data.tar.gz: 641de18537305498dafa1afc161f2d3bcb0d05a2c30835289eb4afbb1eb8cd965950f94a19691951d50f038daa5520de766bce5dade4d6db477018341d522022
data/bin/gv_fsm CHANGED
@@ -6,7 +6,7 @@ require 'optparse'
6
6
  sm = GV_FSM::FSM.new
7
7
 
8
8
  options = {header: true, source: true}
9
- OptionParser.new do |parser|
9
+ op = OptionParser.new do |parser|
10
10
  parser.banner =<<~EOB
11
11
  Graphviz to Finite State Machine generator
12
12
  Version: #{GV_FSM::VERSION}
@@ -36,20 +36,52 @@ OptionParser.new do |parser|
36
36
  options[:header] = false
37
37
  end
38
38
 
39
- end.parse!
39
+ parser.on("-x", "--prefix PREFIX", "Prepend PREFIX to names of generated functions and objects") do |p|
40
+ sm.prefix = p
41
+ end
40
42
 
41
- raise ArgumentError, "I need the path to a Graphviz file!" unless ARGV[0]
42
- raise ArgumentError, "#{ARGV[0]} does not look like a Graphviz file!" unless File.extname(ARGV[0]) == ".dot"
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
43
55
 
44
- sm.parse(ARGV[0])
45
- puts "Parsed #{sm.dotfile}.\nGenerating C stub for states: #{sm.states_list.join(", ")}."
46
- if options[:header] then
47
- sm.generate_h
48
- puts "Generated header #{sm.cname}.h"
49
56
  end
50
- if options[:source] then
51
- sm.generate_c
52
- puts "Generated source #{sm.cname}.c"
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
53
69
  end
54
70
 
71
+ sm.parse(ARGV[0])
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
55
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?)
@@ -25,13 +32,21 @@ module GV_FSM
25
32
  GraphViz.parse(filename) do |g|
26
33
  g.each_node do |id|
27
34
  n = g.get_node(id)
28
- label = n[:label].source.empty? ? "do_#{id}" : n[:label].source
29
- @states << {id: id, function: label}
35
+ if n[:label].source.empty? or
36
+ (n[:label].source == id and !n[:label].source.match(/^do_/)) then
37
+ label = "do_#{id}"
38
+ else
39
+ label = n[:label].source
40
+ end
41
+ @states << {id: id, function: @prefix+label}
30
42
  end
31
43
  g.each_edge do |e|
32
44
  from = e.node_one
33
45
  to = e.node_two
34
- next unless e[:label]
46
+ unless e[:label] then
47
+ @transitions << {from: from, to: to, function: nil}
48
+ next
49
+ end
35
50
  case e[:label].source
36
51
  when ""
37
52
  label = nil
@@ -40,7 +55,7 @@ module GV_FSM
40
55
  else
41
56
  label = e[:label].source
42
57
  end
43
- @transitions << {from: from, to: to, function: label}
58
+ @transitions << {from: from, to: to, function: label ? @prefix+label : nil}
44
59
  end
45
60
  end
46
61
  end
@@ -56,7 +71,7 @@ module GV_FSM
56
71
  def transition_functions_list
57
72
  lst = []
58
73
  @transitions.each do |t|
59
- if !lst.include? t[:function] then
74
+ if t[:function] and !lst.include? t[:function] then
60
75
  lst << (t[:function] or "NULL")
61
76
  end
62
77
  end
@@ -105,6 +120,15 @@ module GV_FSM
105
120
  f.puts ERB.new(HH, 0, "<>").result(binding)
106
121
  end
107
122
  end
123
+
124
+ def generate_ino(filename=@cname)
125
+ @syslog = false
126
+ File.open("#{filename}.ino", "w") do |f|
127
+ f.puts ERB.new(HEADER, 0, "<>").result(binding)
128
+ f.puts ERB.new(HH, 0, "<>").result(binding)
129
+ f.puts ERB.new(CC, 0, "<>").result(binding)
130
+ end
131
+ end
108
132
  end
109
133
 
110
134
  end
@@ -2,81 +2,139 @@
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
+ extern const char *state_names[];
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
 
74
+
44
75
  // List of state functions
45
- state_func_t *const state_table[NUM_STATES] = {
46
- <%= self.state_functions_list.join(",\n ")%>
47
- };
76
+ extern state_func_t *const <%= @prefix %>state_table[<%= @prefix.upcase %>NUM_STATES];
48
77
 
49
- <% if self.transition_functions_list.count > 0 then %>
50
- // transition functions
51
- <% self.transition_functions_list.each do |t| %>
78
+
79
+ <% if transition_functions_list.count > 0 then %>
80
+ // Transition functions
81
+ <% transition_functions_list.each do |t| %>
52
82
  <% next if t == "NULL" %>
53
- void <%= t %>(void *data);
83
+ void <%= t %>(<%= @prefix %>state_data_t *data);
54
84
  <% end %>
55
85
 
56
86
  // Table of transition functions
57
- transition_func_t *const transition_table[NUM_STATES][NUM_STATES] = {
58
- <% sl = self.states_list %>
59
- <% fw = self.transition_functions_list.max {|a, b| a.length <=> b.length}.length %>
60
- <% sw = self.states_list.max {|a, b| a.length <=> b.length}.length %>
61
- /* <%= "states:".ljust(sw) %> <%= sl.map {|e| e.ljust(fw) }.join(", ") %> */
62
- <% self.transitions_map.each_with_index do |l, i| %>
63
- /* <%= sl[i].ljust(sw) %> */ {<%= l.map {|e| e.ljust(fw)}.join(", ") %>},
64
- <% end %>
65
- };
87
+ extern transition_func_t *const <%= @prefix %>transition_table[<%= @prefix.upcase %>NUM_STATES][<%= @prefix.upcase %>NUM_STATES];
66
88
  <% else %>
67
89
  // No transition functions
68
90
  <% end %>
69
91
 
70
92
  // state manager
71
- state_t run_state(state_t cur_state, void *data);
93
+ <%= @prefix %>state_t <%= @prefix %>run_state(<%= @prefix %>state_t cur_state, state_data_t *data);
72
94
 
95
+ <% if !@ino then %>
73
96
  #endif
97
+ <% end %>
74
98
  EOH
75
99
 
76
100
  CC =<<~EOC
101
+ <% if !@ino then %>
102
+ <% if @syslog then %>
77
103
  #include <syslog.h>
78
- #include "<%= self.cname %>.h"
104
+ <% end %>
105
+ #include "<%= @cname %>.h"
106
+ <% end %>
79
107
 
108
+ <% placeholder = "Your Code Here" %>
109
+ // SEARCH FOR <%= placeholder %> FOR CODE INSERTION POINTS!
110
+
111
+ // GLOBALS
112
+ // State human-readable names
113
+ const char *state_names[] = {<%= states_list.map {|sn| '"'+sn+'"'}.join(", ") %>};
114
+
115
+ // List of state functions
116
+ <% fw = state_functions_list.max {|a, b| a.length <=> b.length}.length %>
117
+ state_func_t *const <%= @prefix %>state_table[<%= @prefix.upcase %>NUM_STATES] = {
118
+ <% @states.each do |s| %>
119
+ <%= (s[:function] + ',').ljust(fw+1) %> // in state <%= s[:id] %>
120
+ <% end %>
121
+ };
122
+ <% if transition_functions_list.count > 0 then %>
123
+
124
+ // Table of transition functions
125
+ transition_func_t *const <%= @prefix %>transition_table[<%= @prefix.upcase %>NUM_STATES][<%= @prefix.upcase %>NUM_STATES] = {
126
+ <% sl = states_list %>
127
+ <% fw = transition_functions_list.max {|a, b| a.length <=> b.length}.length %>
128
+ <% sw = [states_list, "states:"].flatten.max {|a, b| a.length <=> b.length}.length %>
129
+ /* <%= "states:".ljust(sw) %> <%= sl.map {|e| e.ljust(fw) }.join(", ") %> */
130
+ <% transitions_map.each_with_index do |l, i| %>
131
+ /* <%= sl[i].ljust(sw) %> */ {<%= l.map {|e| e.ljust(fw)}.join(", ") %>},
132
+ <% end %>
133
+ };
134
+ <% else %>
135
+ // No transition functions
136
+ <% end %>
137
+
80
138
  // ____ _ _
81
139
  // / ___|| |_ __ _| |_ ___
82
140
  // \\___ \\| __/ _` | __/ _ \\
@@ -89,39 +147,43 @@ module GV_FSM
89
147
  // | _| |_| | | | | (__| |_| | (_) | | | \\__ \\
90
148
  // |_| \\__,_|_| |_|\\___|\\__|_|\\___/|_| |_|___/
91
149
  //
92
- <% dest = self.destinations.dup %>
93
- <% self.states.each do |s| %>
150
+ <% dest = destinations.dup %>
151
+ <% @states.each do |s| %>
94
152
  <% stable = true if dest[s[:id]].include? s[:id] %>
95
- <% dest[s[:id]].map! {|n| "STATE_"+n.upcase} %>
153
+ <% dest[s[:id]].map! {|n| (@prefix+"STATE_"+n).upcase} %>
96
154
  <% if dest[s[:id]].empty? or stable then
97
- dest[s[:id]].unshift "NO_CHANGE"
155
+ dest[s[:id]].unshift @prefix.upcase+"NO_CHANGE"
98
156
  end %>
99
- // To be executed in state <%= s[:id] %>
100
- state_t <%= s[:function] %>(void *data) {
101
- state_t next_state = <%= dest[s[:id]].first %>;
157
+ // Function to be executed in state <%= s[:id] %>
158
+ // valid return states: <%= dest[s[:id]].join(", ") %>
159
+ <%= @prefix %>state_t <%= s[:function] %>(<%= @prefix %>state_data_t *data) {
160
+ <%= @prefix %>state_t next_state = <%= dest[s[:id]].first %>;
102
161
 
162
+ <% if @syslog then %>
103
163
  syslog(LOG_INFO, "[FSM] In state <%= s[:id] %>");
104
- /* Your code here */
164
+ <% end %>
165
+ /* <%= placeholder %> */
105
166
 
106
- // valid return states: <%= dest[s[:id]].join(", ") %>
107
167
  switch (next_state) {
108
168
  <% dest[s[:id]].each do |str| %>
109
169
  case <%= str %>:
110
170
  <% end %>
111
171
  break;
112
172
  default:
173
+ <% if @syslog then %>
113
174
  syslog(LOG_WARNING, "[FSM] Cannot pass from <%= s[:id] %> to %s, remaining in this state", state_names[next_state]);
114
- next_state = NO_CHANGE;
175
+ <% end %>
176
+ next_state = <%= @prefix.upcase %>NO_CHANGE;
115
177
  }
116
178
  return next_state;
117
179
  }
118
180
 
119
181
  <% end %>
120
182
 
121
- <% if self.transition_functions_list.count > 0 then %>
183
+ <% if transition_functions_list.count > 0 then %>
122
184
  // _____ _ _ _
123
185
  // |_ _| __ __ _ _ __ ___(_) |_(_) ___ _ __
124
- // | || '__/ _` | '_ \\/ __| | __| |/ _ \\| '_ \\
186
+ // | || '__/ _` | '_ \\/ __| | __| |/ _ \\| '_ \\
125
187
  // | || | | (_| | | | \\__ \\ | |_| | (_) | | | |
126
188
  // |_||_| \\__,_|_| |_|___/_|\\__|_|\\___/|_| |_|
127
189
  //
@@ -132,15 +194,18 @@ module GV_FSM
132
194
  // |_| \\__,_|_| |_|\\___|\\__|_|\\___/|_| |_|___/
133
195
  //
134
196
 
135
- <% self.transition_functions_list.each do |t| %>
197
+ <% transition_functions_list.each do |t| %>
136
198
  <% next if t == "NULL" %>
137
- // This function is called in transitions:
138
- <% self.transitions_paths[t].each do |e| %>
139
- // from <%= e[:from] %> to <%= e[:to] %>
199
+ <% tpaths = transitions_paths[t] %>
200
+ // This function is called in <%= tpaths.count %> transition<%= tpaths.count == 1 ? '' : 's' %>:
201
+ <% tpaths.each_with_index do |e, i| %>
202
+ // <%= i+1 %>. from <%= e[:from] %> to <%= e[:to] %>
140
203
  <% end %>
141
- void <%= t %>(void *data) {
204
+ void <%= t %>(<%= @prefix %>state_data_t *data) {
205
+ <% if @syslog then %>
142
206
  syslog(LOG_INFO, "[FSM] State transition <%= t %>");
143
- /* Your code here */
207
+ <% end %>
208
+ /* <%= placeholder %> */
144
209
  }
145
210
 
146
211
  <% end %>
@@ -148,7 +213,7 @@ module GV_FSM
148
213
 
149
214
  // ____ _ _
150
215
  // / ___|| |_ __ _| |_ ___
151
- // \\___ \\| __/ _` | __/ _ \\
216
+ // \\___ \\| __/ _` | __/ _ \\
152
217
  // ___) | || (_| | || __/
153
218
  // |____/ \\__\\__,_|\\__\\___|
154
219
  //
@@ -159,30 +224,39 @@ module GV_FSM
159
224
  // |_| |_| |_|\\__,_|_| |_|\\__,_|\\__, |\\___|_|
160
225
  // |___/
161
226
 
162
- state_t run_state(state_t cur_state, void *data) {
163
- state_t new_state = state_table[cur_state](data);
164
- <% if self.transition_functions_list.count > 0 then %>
165
- transition_func_t *transition = transition_table[cur_state][new_state];
227
+ <%= @prefix %>state_t <%= @prefix %>run_state(<%= @prefix %>state_t cur_state, <%= @prefix %>state_data_t *data) {
228
+ <%= @prefix %>state_t new_state = <%= @prefix %>state_table[cur_state](data);
229
+ <% if transition_functions_list.count > 0 then %>
230
+ transition_func_t *transition = <%= @prefix %>transition_table[cur_state][new_state];
166
231
  if (transition)
167
232
  transition(data);
168
233
  <% end %>
169
- return new_state == NO_CHANGE ? cur_state : new_state;
234
+ return new_state == <%= @prefix.upcase %>NO_CHANGE ? cur_state : new_state;
170
235
  };
171
236
 
172
-
237
+ <% if @ino then %>
238
+ /* Example usage:
239
+ <% else %>
173
240
  #ifdef TEST_MAIN
174
241
  #include <unistd.h>
175
242
  int main() {
176
- state_t cur_state = STATE_INIT;
243
+ <% end %>
244
+ <%= @prefix %>state_t cur_state = <%= @prefix.upcase %>STATE_INIT;
245
+ <% if @syslog then %>
177
246
  openlog("SM", LOG_PID | LOG_PERROR, LOG_USER);
178
247
  syslog(LOG_INFO, "Starting SM");
248
+ <% end %>
179
249
  do {
180
- cur_state = run_state(cur_state, NULL);
250
+ cur_state = <%= @prefix %>run_state(cur_state, NULL);
181
251
  sleep(1);
182
- } while (cur_state != STATE_STOP);
252
+ } while (cur_state != <%= @prefix.upcase %>STATE_STOP);
253
+ <% if !@ino then %>
183
254
  return 0;
184
255
  }
185
256
  #endif
257
+ <% else %>
258
+ */
259
+ <% end %>
186
260
  EOC
187
261
  end
188
262
  end
@@ -1,3 +1,3 @@
1
1
  module GV_FSM
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.4"
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.1.1
4
+ version: 0.2.4
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-08 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