gv_fsm 0.1.1 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
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