gv_fsm 0.0.3 → 0.2.2

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 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