redshift 1.3.15
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.
- data/.gitignore +8 -0
- data/README +5 -0
- data/RELEASE-NOTES +455 -0
- data/TODO +431 -0
- data/bench/alg-state.rb +61 -0
- data/bench/bench +26 -0
- data/bench/bench.rb +10 -0
- data/bench/continuous.rb +76 -0
- data/bench/diff-bench +86 -0
- data/bench/discrete.rb +101 -0
- data/bench/euler.rb +50 -0
- data/bench/formula.rb +78 -0
- data/bench/half-strict.rb +103 -0
- data/bench/inertness.rb +116 -0
- data/bench/queue.rb +92 -0
- data/bench/run +66 -0
- data/bench/simple.rb +74 -0
- data/bench/strictness.rb +86 -0
- data/examples/ball-tkar.rb +72 -0
- data/examples/ball.rb +123 -0
- data/examples/collide.rb +70 -0
- data/examples/connect-parallel.rb +48 -0
- data/examples/connect.rb +109 -0
- data/examples/constants.rb +27 -0
- data/examples/delay.rb +80 -0
- data/examples/derivative.rb +77 -0
- data/examples/euler.rb +46 -0
- data/examples/external-lib.rb +33 -0
- data/examples/guard-debugger.rb +77 -0
- data/examples/lotka-volterra.rb +33 -0
- data/examples/persist-ball.rb +68 -0
- data/examples/pid.rb +87 -0
- data/examples/ports.rb +60 -0
- data/examples/queue.rb +56 -0
- data/examples/queue2.rb +98 -0
- data/examples/reset-with-event-val.rb +28 -0
- data/examples/scheduler.rb +104 -0
- data/examples/set-dest.rb +23 -0
- data/examples/simulink/README +1 -0
- data/examples/simulink/delay.mdl +827 -0
- data/examples/simulink/derivative.mdl +655 -0
- data/examples/step-discrete-profiler.rb +103 -0
- data/examples/subsystem.rb +109 -0
- data/examples/sync-deadlock.rb +32 -0
- data/examples/sync-queue.rb +91 -0
- data/examples/sync-retry.rb +20 -0
- data/examples/sync.rb +51 -0
- data/examples/thermostat.rb +53 -0
- data/examples/zeno.rb +53 -0
- data/lib/accessible-index.rb +47 -0
- data/lib/redshift.rb +1 -0
- data/lib/redshift/component.rb +412 -0
- data/lib/redshift/meta.rb +183 -0
- data/lib/redshift/mixins/zeno-debugger.rb +69 -0
- data/lib/redshift/port.rb +57 -0
- data/lib/redshift/queue.rb +104 -0
- data/lib/redshift/redshift.rb +111 -0
- data/lib/redshift/state.rb +31 -0
- data/lib/redshift/syntax.rb +558 -0
- data/lib/redshift/target/c.rb +37 -0
- data/lib/redshift/target/c/component-gen.rb +1303 -0
- data/lib/redshift/target/c/flow-gen.rb +325 -0
- data/lib/redshift/target/c/flow/algebraic.rb +85 -0
- data/lib/redshift/target/c/flow/buffer.rb +74 -0
- data/lib/redshift/target/c/flow/delay.rb +203 -0
- data/lib/redshift/target/c/flow/derivative.rb +101 -0
- data/lib/redshift/target/c/flow/euler.rb +67 -0
- data/lib/redshift/target/c/flow/expr.rb +113 -0
- data/lib/redshift/target/c/flow/rk4.rb +80 -0
- data/lib/redshift/target/c/library.rb +85 -0
- data/lib/redshift/target/c/world-gen.rb +1370 -0
- data/lib/redshift/target/spec.rb +34 -0
- data/lib/redshift/world.rb +300 -0
- data/rakefile +37 -0
- data/test/test.rb +52 -0
- data/test/test_buffer.rb +58 -0
- data/test/test_connect.rb +242 -0
- data/test/test_connect_parallel.rb +47 -0
- data/test/test_connect_strict.rb +135 -0
- data/test/test_constant.rb +74 -0
- data/test/test_delay.rb +145 -0
- data/test/test_derivative.rb +48 -0
- data/test/test_discrete.rb +592 -0
- data/test/test_discrete_isolated.rb +92 -0
- data/test/test_exit.rb +59 -0
- data/test/test_flow.rb +200 -0
- data/test/test_flow_link.rb +288 -0
- data/test/test_flow_sub.rb +100 -0
- data/test/test_flow_trans.rb +292 -0
- data/test/test_inherit.rb +127 -0
- data/test/test_inherit_event.rb +74 -0
- data/test/test_inherit_flow.rb +139 -0
- data/test/test_inherit_link.rb +65 -0
- data/test/test_inherit_setup.rb +56 -0
- data/test/test_inherit_state.rb +66 -0
- data/test/test_inherit_transition.rb +168 -0
- data/test/test_numerics.rb +34 -0
- data/test/test_queue.rb +90 -0
- data/test/test_queue_alone.rb +115 -0
- data/test/test_reset.rb +209 -0
- data/test/test_setup.rb +119 -0
- data/test/test_strict_continuity.rb +410 -0
- data/test/test_strict_reset_error.rb +30 -0
- data/test/test_strictness_error.rb +32 -0
- data/test/test_sync.rb +185 -0
- data/test/test_world.rb +328 -0
- metadata +204 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
class RedShift::State
|
2
|
+
attr_reader :name, :persist_name
|
3
|
+
|
4
|
+
def initialize n, context
|
5
|
+
@name = n
|
6
|
+
@persist_name = "#{context}::#{n}".intern
|
7
|
+
@context = context
|
8
|
+
end
|
9
|
+
|
10
|
+
def _dump depth
|
11
|
+
@persist_name.to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
def self._load str
|
15
|
+
pn = str.intern
|
16
|
+
## could cache this lookup in a hash
|
17
|
+
ObjectSpace.each_object(State) { |st|
|
18
|
+
if st.persist_name == pn
|
19
|
+
return st
|
20
|
+
end
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
@name.to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
def inspect
|
29
|
+
"<#{@name}>"
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,558 @@
|
|
1
|
+
require 'redshift/world'
|
2
|
+
require 'redshift/component'
|
3
|
+
|
4
|
+
module RedShift
|
5
|
+
|
6
|
+
# Register the given block to be called for instances of this class of World as
|
7
|
+
# they are instantiated (before the block passed to #new is called). The
|
8
|
+
# registered code is inherited by subclasses of this World class. The block is
|
9
|
+
# called with the world as +self+. Any number of blocks can be registered.
|
10
|
+
# (There are no per-world defaults. Use the #new block instead.)
|
11
|
+
def World.defaults(&block)
|
12
|
+
(@defaults_procs ||= []) << block if block
|
13
|
+
end
|
14
|
+
class << World
|
15
|
+
alias default defaults
|
16
|
+
end
|
17
|
+
|
18
|
+
# Register the given block to be called for instances of this class of World
|
19
|
+
# just before they are first run. The registered code is inherited by
|
20
|
+
# subclasses of this World class. The block is called with the world as +self+.
|
21
|
+
# Any number of blocks can be registered.
|
22
|
+
def World.setup(&block)
|
23
|
+
(@setup_procs ||= []) << block if block
|
24
|
+
end
|
25
|
+
|
26
|
+
# Register the given block to be called for this world just before it is
|
27
|
+
# first run. The block is called with the world as +self+.
|
28
|
+
# Any number of blocks can be registered.
|
29
|
+
class World
|
30
|
+
def setup(&block)
|
31
|
+
(@setup_procs ||= []) << block if block
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
class Component
|
37
|
+
# Create a component in the same world as this component. This method is
|
38
|
+
# provided for convenience. It just calls World#create.
|
39
|
+
def create(component_class)
|
40
|
+
if block_given?
|
41
|
+
world.create(component_class) {|c| yield c}
|
42
|
+
else
|
43
|
+
world.create(component_class)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Specify the starting state +s+ of the component.
|
48
|
+
# To be called only before the component starts running: during the default,
|
49
|
+
# setup, or initialization block (block passed to Component#new).
|
50
|
+
def start(s)
|
51
|
+
raise AlreadyStarted if state
|
52
|
+
case s
|
53
|
+
when State
|
54
|
+
@start_state = s
|
55
|
+
else
|
56
|
+
@start_state = self.class.const_get(s.to_s)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class << Component
|
62
|
+
# Specify the starting state +s+ of the component, as a default for the class.
|
63
|
+
def start(s)
|
64
|
+
default {start s}
|
65
|
+
end
|
66
|
+
|
67
|
+
def make_init_value_map(h)
|
68
|
+
h.inject({}) do |hh, (var, val)|
|
69
|
+
if val.kind_of? Proc or val.kind_of? String
|
70
|
+
raise TypeError,
|
71
|
+
"value for '#{var}' must be literal, like #{var} => 1.23"
|
72
|
+
end
|
73
|
+
hh.update "#{var}=" => val
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Register, for the current component class, the given block to be called at
|
78
|
+
# the beginning of initialization of an instance.
|
79
|
+
# The block is called with the world as +self+.
|
80
|
+
# Any number of blocks can be registered.
|
81
|
+
def defaults(h = nil, &block)
|
82
|
+
(@defaults_procs ||= []) << block if block
|
83
|
+
(@defaults_map ||= {}).update make_init_value_map(h) if h
|
84
|
+
end
|
85
|
+
alias default defaults
|
86
|
+
|
87
|
+
# Register, for the current component class, the given block to be called
|
88
|
+
# later in the initialization of an instance, after defaults and the
|
89
|
+
# initialization block (the block passed to Component#new).
|
90
|
+
# The block is called with the world as +self+.
|
91
|
+
# Any number of blocks can be registered.
|
92
|
+
def setup(h = nil, &block)
|
93
|
+
(@setup_procs ||= []) << block if block
|
94
|
+
(@setup_map ||= {}).update make_init_value_map(h) if h
|
95
|
+
end
|
96
|
+
|
97
|
+
# Define states in this component class, listed in +state_names+. A state
|
98
|
+
# name should be a string or symbol beginning with [A-Z] and consisting of
|
99
|
+
# alphanumeric (<tt>/\w/</tt>) characters. States are inherited.
|
100
|
+
def state(*state_names)
|
101
|
+
state_names.flatten!
|
102
|
+
state_names.map do |state_name|
|
103
|
+
if state_name.kind_of? Symbol
|
104
|
+
state_name = state_name.to_s
|
105
|
+
else
|
106
|
+
begin
|
107
|
+
state_name = state_name.to_str
|
108
|
+
rescue NoMethodError
|
109
|
+
raise SyntaxError, "Not a valid state name: #{state_name.inspect}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
unless state_name =~ /^[A-Z]/
|
114
|
+
raise SyntaxError,
|
115
|
+
"State name #{state_name.inspect} does not begin with [A-Z]."
|
116
|
+
end
|
117
|
+
|
118
|
+
begin
|
119
|
+
val = const_get(state_name)
|
120
|
+
rescue NameError
|
121
|
+
attach_state(state_name)
|
122
|
+
else
|
123
|
+
case val
|
124
|
+
when State
|
125
|
+
raise NameError, "state #{state_name} already exists"
|
126
|
+
else
|
127
|
+
raise NameError,
|
128
|
+
"state name '#{state_name}' is already used for a constant " +
|
129
|
+
"of type #{val.class}."
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def permissively_continuous(*var_names)
|
136
|
+
attach_continuous_variables(:permissive, var_names)
|
137
|
+
end
|
138
|
+
|
139
|
+
def strictly_continuous(*var_names)
|
140
|
+
attach_continuous_variables(:strict, var_names)
|
141
|
+
end
|
142
|
+
|
143
|
+
def continuous(*var_names)
|
144
|
+
attach_continuous_variables(:piecewise, var_names)
|
145
|
+
end
|
146
|
+
alias piecewise_continuous continuous
|
147
|
+
|
148
|
+
def permissively_constant(*var_names)
|
149
|
+
attach_constant_variables(:permissive, var_names)
|
150
|
+
end
|
151
|
+
|
152
|
+
def strictly_constant(*var_names)
|
153
|
+
attach_constant_variables(:strict, var_names)
|
154
|
+
end
|
155
|
+
|
156
|
+
def constant(*var_names)
|
157
|
+
attach_constant_variables(:piecewise, var_names)
|
158
|
+
end
|
159
|
+
alias piecewise_constant constant
|
160
|
+
|
161
|
+
def strict_link vars
|
162
|
+
attach_link vars, :strict
|
163
|
+
end
|
164
|
+
|
165
|
+
# link :x => MyComponent, :y => :FwdRefComponent
|
166
|
+
def link(*vars)
|
167
|
+
h = {}
|
168
|
+
vars.each do |var|
|
169
|
+
case var
|
170
|
+
when Hash
|
171
|
+
h.update var
|
172
|
+
else
|
173
|
+
h[var] = Component
|
174
|
+
end
|
175
|
+
end
|
176
|
+
attach_link h, false
|
177
|
+
end
|
178
|
+
|
179
|
+
def input(*var_names)
|
180
|
+
attach_input :piecewise, var_names
|
181
|
+
end
|
182
|
+
|
183
|
+
def strict_input(*var_names)
|
184
|
+
attach_input :strict, var_names
|
185
|
+
end
|
186
|
+
|
187
|
+
def strict(*var_names)
|
188
|
+
var_names.each do |var_name|
|
189
|
+
dest = find_var_superhash(var_name)
|
190
|
+
case dest
|
191
|
+
when nil
|
192
|
+
raise VarTypeError, "Variable #{var_name.inspect} not found."
|
193
|
+
when link_variables
|
194
|
+
var_type = link_variables[var_name].first
|
195
|
+
attach_variables(dest, :strict, [var_name], var_type)
|
196
|
+
else
|
197
|
+
attach_variables(dest, :strict, [var_name])
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# Defines the flow types that can be used within a flow block.
|
204
|
+
module FlowSyntax
|
205
|
+
def self.parse block
|
206
|
+
FlowParser.new(block).flows
|
207
|
+
end
|
208
|
+
|
209
|
+
class FlowParser
|
210
|
+
attr_reader :flows
|
211
|
+
|
212
|
+
def initialize block
|
213
|
+
@flows = []
|
214
|
+
instance_eval(&block)
|
215
|
+
end
|
216
|
+
|
217
|
+
def algebraic(*equations)
|
218
|
+
equations.each do |equation|
|
219
|
+
unless equation =~ /^\s*(\w+)\s*=\s*(.+)/m
|
220
|
+
raise SyntaxError, "parse error in\n\t#{equation}."
|
221
|
+
end
|
222
|
+
@flows << AlgebraicFlow.new($1.intern, $2.strip)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def euler(*equations)
|
227
|
+
for equation in equations
|
228
|
+
unless equation =~ /^\s*(\w+)\s*'\s*=\s*(.+)/m
|
229
|
+
raise SyntaxError, "parse error in\n\t#{equation}."
|
230
|
+
end
|
231
|
+
@flows << EulerDifferentialFlow.new($1.intern, $2.strip)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def rk4(*equations)
|
236
|
+
for equation in equations
|
237
|
+
unless equation =~ /^\s*(\w+)\s*'\s*=\s*(.+)/m
|
238
|
+
raise SyntaxError, "parse error in\n\t#{equation}."
|
239
|
+
end
|
240
|
+
@flows << RK4DifferentialFlow.new($1.intern, $2.strip)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def derive(*equations)
|
245
|
+
opts = equations.pop
|
246
|
+
unless opts and opts.kind_of? Hash and
|
247
|
+
(opts[:feedback] == true or opts[:feedback] == false)
|
248
|
+
raise SyntaxError, "Missing option: :feedback => <true|false>\n" +
|
249
|
+
"Use 'true' when the output of this flow feeds back into another\n" +
|
250
|
+
"derivative flow (even after a delay). Also, set <var>_init_rhs.\n"
|
251
|
+
## should false be the default?
|
252
|
+
## rename 'feedback'?
|
253
|
+
end
|
254
|
+
feedback = opts[:feedback]
|
255
|
+
for equation in equations
|
256
|
+
unless equation =~ /^\s*(\w+)\s*=\s*(.+)'\s*\z/m
|
257
|
+
raise SyntaxError, "parse error in\n\t#{equation}."
|
258
|
+
end
|
259
|
+
@flows << DerivativeFlow.new($1.intern, $2.strip, feedback)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def delay(*equations)
|
264
|
+
opts = equations.pop
|
265
|
+
unless opts and opts.kind_of? Hash and opts[:by]
|
266
|
+
raise SyntaxError, "Missing delay term: :delay => <delay>"
|
267
|
+
end
|
268
|
+
delay_by = opts[:by]
|
269
|
+
equations.each do |equation|
|
270
|
+
unless equation =~ /^\s*(\w+)\s*=\s*(.+)/m
|
271
|
+
raise SyntaxError, "parse error in\n\t#{equation}."
|
272
|
+
end
|
273
|
+
@flows << DelayFlow.new($1.intern, $2.strip, delay_by)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
alias alg algebraic
|
278
|
+
alias diff rk4
|
279
|
+
alias differential rk4
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
module TransitionSyntax
|
284
|
+
def self.parse block
|
285
|
+
TransitionParser.new(block)
|
286
|
+
end
|
287
|
+
|
288
|
+
class EventBlockParser
|
289
|
+
attr_reader :events
|
290
|
+
|
291
|
+
def method_missing event_name, *args, &bl
|
292
|
+
if args.size > 1 or (args.size == 1 and bl)
|
293
|
+
raise SyntaxError, "Too many arguments in event specifier"
|
294
|
+
end
|
295
|
+
|
296
|
+
item = Component::EventPhaseItem.new
|
297
|
+
item.event = event_name
|
298
|
+
item.value = bl || (args.size > 0 && args[0]) || true
|
299
|
+
|
300
|
+
@events << item
|
301
|
+
end
|
302
|
+
|
303
|
+
def initialize(block)
|
304
|
+
@events = []
|
305
|
+
instance_eval(&block)
|
306
|
+
end
|
307
|
+
|
308
|
+
def literal val
|
309
|
+
Component.literal val
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
class TransitionParser
|
314
|
+
attr_reader :name,
|
315
|
+
:guards, :syncs, :actions,
|
316
|
+
:resets, :events, :posts,
|
317
|
+
:connects
|
318
|
+
|
319
|
+
def initialize block
|
320
|
+
@name = nil
|
321
|
+
instance_eval(&block)
|
322
|
+
end
|
323
|
+
|
324
|
+
def name(*n); n.empty? ? @name : @name = n.first; end
|
325
|
+
|
326
|
+
def sync(*a)
|
327
|
+
@syncs ||= Component::SyncPhase.new
|
328
|
+
|
329
|
+
if a.last.kind_of?(Hash)
|
330
|
+
a.concat a.pop.to_a
|
331
|
+
end
|
332
|
+
|
333
|
+
a.each do |link_name, event|
|
334
|
+
link_names = case link_name
|
335
|
+
when Array; link_name
|
336
|
+
else [link_name]
|
337
|
+
end
|
338
|
+
|
339
|
+
events = case event
|
340
|
+
when Array; event
|
341
|
+
else [event]
|
342
|
+
end
|
343
|
+
|
344
|
+
link_names.each do |ln|
|
345
|
+
events.each do |e|
|
346
|
+
item = Component::SyncPhaseItem.new
|
347
|
+
item.link_name = ln
|
348
|
+
item.event = e
|
349
|
+
@syncs << item
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
def wait(*args)
|
356
|
+
@guards ||= Component::GuardPhase.new
|
357
|
+
|
358
|
+
args.each do |arg|
|
359
|
+
case arg
|
360
|
+
when Hash
|
361
|
+
@guards.concat(arg.sort_by {|q,m| q.to_s}.map{|q,m|
|
362
|
+
Component::QMatch[q.to_sym,*m]})
|
363
|
+
# { :queue => match }
|
364
|
+
when Symbol
|
365
|
+
@guards << Component::QMatch[arg] # :queue
|
366
|
+
else raise SyntaxError
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
def guard(*args, &block)
|
372
|
+
@guards ||= Component::GuardPhase.new
|
373
|
+
|
374
|
+
args.each do |arg|
|
375
|
+
case arg
|
376
|
+
when String; @guards << arg.strip # "<expression>"
|
377
|
+
when Proc; @guards << arg # proc { ... }
|
378
|
+
when Symbol; @guards << arg # :method
|
379
|
+
when nil, true; # no condition
|
380
|
+
when false; @guards << arg
|
381
|
+
else raise SyntaxError, "'guard #{arg.inspect}'"
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
@guards << block if block
|
386
|
+
end
|
387
|
+
|
388
|
+
def action(meth = nil, &bl)
|
389
|
+
@actions ||= Component::ActionPhase.new
|
390
|
+
@actions << meth if meth
|
391
|
+
@actions << bl if bl
|
392
|
+
end
|
393
|
+
|
394
|
+
def post(meth = nil, &bl)
|
395
|
+
@posts ||= Component::PostPhase.new
|
396
|
+
@posts << meth if meth
|
397
|
+
@posts << bl if bl
|
398
|
+
end
|
399
|
+
alias after post
|
400
|
+
|
401
|
+
# +h+ is a hash of :var => proc {value_expr_ruby} or "value_expr_c".
|
402
|
+
def reset(h)
|
403
|
+
badkeys = h.keys.reject {|k| k.is_a?(Symbol)}
|
404
|
+
unless badkeys.empty?
|
405
|
+
raise SyntaxError, "Keys #{badkeys.inspect} in reset must be symbols"
|
406
|
+
end
|
407
|
+
|
408
|
+
@resets ||= Component::ResetPhase.new
|
409
|
+
@resets.value_map ||= {}
|
410
|
+
@resets.concat [nil, nil, nil] # continuous, constant, link
|
411
|
+
@resets.value_map.update h
|
412
|
+
end
|
413
|
+
|
414
|
+
# +h+ is a hash of :var => proc {port_expr_ruby} or [:link, :var].
|
415
|
+
def connect(h)
|
416
|
+
badkeys = h.keys.reject {|k| k.is_a?(Symbol)}
|
417
|
+
unless badkeys.empty?
|
418
|
+
raise SyntaxError, "Keys #{badkeys.inspect} in connect must be symbols"
|
419
|
+
end
|
420
|
+
|
421
|
+
@connects ||= Component::ConnectPhase.new
|
422
|
+
@connects.concat h.entries
|
423
|
+
end
|
424
|
+
|
425
|
+
# each arg can be an event name (string or symbol), exported with value
|
426
|
+
# +true+, or a hash of event_name => value. In the latter case, _value_
|
427
|
+
# can be either a Proc, string (C expr), or a literal. If you need to
|
428
|
+
# treat a Proc or string as a literal, use the notation
|
429
|
+
#
|
430
|
+
# :e => literal("str")
|
431
|
+
#
|
432
|
+
# :e => literal(proc {...})
|
433
|
+
#
|
434
|
+
def event(*args, &bl)
|
435
|
+
@events ||= Component::EventPhase.new
|
436
|
+
for arg in args
|
437
|
+
case arg
|
438
|
+
when Symbol, String
|
439
|
+
item = Component::EventPhaseItem.new
|
440
|
+
item.event = arg
|
441
|
+
item.value = true
|
442
|
+
@events << item
|
443
|
+
|
444
|
+
when Hash
|
445
|
+
arg.sort_by {|e,v| e.to_s}.each do |e,v|
|
446
|
+
item = Component::EventPhaseItem.new
|
447
|
+
item.event = e
|
448
|
+
item.value = v
|
449
|
+
@events << item
|
450
|
+
end
|
451
|
+
else
|
452
|
+
raise SyntaxError, "unrecognized event specifier #{arg}."
|
453
|
+
end
|
454
|
+
end
|
455
|
+
if bl
|
456
|
+
eb = EventBlockParser.new(bl)
|
457
|
+
@events.concat(eb.events)
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
def literal val
|
462
|
+
Component.literal val
|
463
|
+
end
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
# Define flows in this component class. Flows are attached to all of the
|
468
|
+
# +states+ listed. The block contains method calls such as:
|
469
|
+
#
|
470
|
+
# alg "var = expression"
|
471
|
+
# diff "var' = expression"
|
472
|
+
#
|
473
|
+
def Component.flow(*states, &block)
|
474
|
+
raise "no flows specified. Put { on same line!" unless block
|
475
|
+
states = [Enter] if states == []
|
476
|
+
|
477
|
+
attach states, FlowSyntax.parse(block)
|
478
|
+
end
|
479
|
+
|
480
|
+
# Define transitions in this component class. Transitions are attached to
|
481
|
+
# all of the +edges+ listed as <tt>src => dst</tt>. In fact, edges may
|
482
|
+
# also be given as <tt>[s0, s1, ...] => d</tt> and then the transition
|
483
|
+
# is attached to all <tt>si => d</tt>.
|
484
|
+
#
|
485
|
+
# If no edges are specified, <tt>Enter => Enter<\tt> is used.
|
486
|
+
# If no block is given, the +Always+ transition is used.
|
487
|
+
# It is a TransitionError to omit both the edges and the block.
|
488
|
+
# Specifying two outgoing transitions for the same state is warned, but
|
489
|
+
# only when this is done within the same call to this method.
|
490
|
+
#
|
491
|
+
# The block contains method calls to define guards, events, resets, connects,
|
492
|
+
# and action and post procs.
|
493
|
+
#
|
494
|
+
# The block also can have a call to the name method, which defines the name of
|
495
|
+
# the transition--this is necessary for overriding the transition in a subclass.
|
496
|
+
#
|
497
|
+
def Component.transition(edges = {}, &block)
|
498
|
+
e = {}
|
499
|
+
warn = []
|
500
|
+
|
501
|
+
unless edges.kind_of?(Hash)
|
502
|
+
raise SyntaxError, "transition syntax must be 'S1 => S2, S3 => S4, ...' "
|
503
|
+
end
|
504
|
+
|
505
|
+
edges.each do |s, d|
|
506
|
+
must_be_state(d)
|
507
|
+
|
508
|
+
case s
|
509
|
+
when Array
|
510
|
+
s.each do |t|
|
511
|
+
must_be_state(t)
|
512
|
+
warn << t if e[t]
|
513
|
+
e[t] = d
|
514
|
+
end
|
515
|
+
|
516
|
+
else
|
517
|
+
must_be_state(s)
|
518
|
+
warn << s if e[s]
|
519
|
+
e[s] = d
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
edges = e
|
524
|
+
warn.each do |st|
|
525
|
+
warn "Two destinations for state #{st} at #{caller[0]}."
|
526
|
+
end
|
527
|
+
|
528
|
+
if block
|
529
|
+
edges = {Enter => Enter} if edges.empty?
|
530
|
+
parser = TransitionSyntax.parse(block)
|
531
|
+
|
532
|
+
if parser.events
|
533
|
+
parser.events.each do |event_phase_item|
|
534
|
+
event_phase_item.index = export(event_phase_item.event)[0]
|
535
|
+
# cache index
|
536
|
+
end
|
537
|
+
end
|
538
|
+
|
539
|
+
trans = Transition.new(parser)
|
540
|
+
|
541
|
+
else
|
542
|
+
if edges == {}
|
543
|
+
raise TransitionError, "No transition specified."
|
544
|
+
else
|
545
|
+
trans = Always
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|
549
|
+
attach edges, trans
|
550
|
+
end
|
551
|
+
|
552
|
+
def Component.must_be_state s
|
553
|
+
unless s.kind_of? State
|
554
|
+
raise TypeError, "Not a state: #{s}"
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
558
|
+
end
|