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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1d793e3f46c8098f7fd5a5e9cdac035fc19ab6fb744886775dbc1e7a7757398c
4
- data.tar.gz: bc16702d154fddd02a2c774b0f651ada1ece6e1e76cc9845e64dfbbb3811aba9
3
+ metadata.gz: 2896e7f9617c0ed1a9917e78cd947d1da5ceef2d8554727a4db268a7f70e6229
4
+ data.tar.gz: 63d897002c102d5291ecbfd7b4c854f8c9fc029e01155ba573d13e2043621afb
5
5
  SHA512:
6
- metadata.gz: d06e75af4f4d1c3d1ebf2d80293d60cceae9a75859ddeaef11e44b02bab4829b248071805174d15f5818767f3ee04164ac573174cb39a76db55d196cf649ba11
7
- data.tar.gz: bea055285d64501305796e2fb0d49d9a5e915f52facc45224c7eace99d801ebd5f4cc7b4a39e88587b262f779c250dac651beb7235826d2fb445aaca03cf2b3a
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
- SM = GV_FSM::FSM.new(ARGV[0])
6
- SM.project_name = "test project"
7
- SM.description = "FSM designed to test the generator"
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
 
@@ -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
- raise ArgumentError, "File must be in .dot format" unless File.extname(filename) == ".dot"
17
- @cname = File.basename(filename, ".dot")
18
- parse(filename)
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)
@@ -2,90 +2,124 @@
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
+ <%= @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 <%= self.cname.upcase %>_H
21
- #define <%= self.cname.upcase %>_H
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
- <% self.states.each_with_index do |s, i| %>
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[] = {<%= self.states_list.map {|sn| '"'+sn+'"'}.join(", ") %>};
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(void *data);
37
- typedef void transition_func_t(void *data);
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
- // transition functions
45
- <% self.transition_functions_list.each do |t| %>
46
- <% next if t == "NULL" %>
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
- <%= self.state_functions_list.join(",\n ")%>
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 = 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 %>
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
- <% self.transitions_map.each_with_index do |l, i| %>
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 "<%= self.cname %>.h"
93
+ #include "<%= @cname %>.h"
75
94
 
76
- // State functions
77
- <% dest = self.destinations.dup %>
78
- <% self.states.each do |s| %>
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
- state_t <%= s[:function] %>(void *data) {
85
- state_t next_state = <%= dest[s[:id]].first %>;
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
- /* Your code here */
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
- // Transition functions
106
- <% self.transition_functions_list.each do |t| %>
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
- void <%= t %>(void *data) {
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
- /* Your code here */
162
+ /* <%= placeholder %> */
111
163
  }
112
164
 
165
+ <% end %>
113
166
  <% end %>
114
167
 
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];
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
- return new_state == NO_CHANGE ? cur_state : new_state;
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
@@ -1,3 +1,3 @@
1
1
  module GV_FSM
2
- VERSION = "0.0.2"
2
+ VERSION = "0.2.1"
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.2
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-28 00:00:00.000000000 Z
11
+ date: 2020-08-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-graphviz