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.
Files changed (107) hide show
  1. data/.gitignore +8 -0
  2. data/README +5 -0
  3. data/RELEASE-NOTES +455 -0
  4. data/TODO +431 -0
  5. data/bench/alg-state.rb +61 -0
  6. data/bench/bench +26 -0
  7. data/bench/bench.rb +10 -0
  8. data/bench/continuous.rb +76 -0
  9. data/bench/diff-bench +86 -0
  10. data/bench/discrete.rb +101 -0
  11. data/bench/euler.rb +50 -0
  12. data/bench/formula.rb +78 -0
  13. data/bench/half-strict.rb +103 -0
  14. data/bench/inertness.rb +116 -0
  15. data/bench/queue.rb +92 -0
  16. data/bench/run +66 -0
  17. data/bench/simple.rb +74 -0
  18. data/bench/strictness.rb +86 -0
  19. data/examples/ball-tkar.rb +72 -0
  20. data/examples/ball.rb +123 -0
  21. data/examples/collide.rb +70 -0
  22. data/examples/connect-parallel.rb +48 -0
  23. data/examples/connect.rb +109 -0
  24. data/examples/constants.rb +27 -0
  25. data/examples/delay.rb +80 -0
  26. data/examples/derivative.rb +77 -0
  27. data/examples/euler.rb +46 -0
  28. data/examples/external-lib.rb +33 -0
  29. data/examples/guard-debugger.rb +77 -0
  30. data/examples/lotka-volterra.rb +33 -0
  31. data/examples/persist-ball.rb +68 -0
  32. data/examples/pid.rb +87 -0
  33. data/examples/ports.rb +60 -0
  34. data/examples/queue.rb +56 -0
  35. data/examples/queue2.rb +98 -0
  36. data/examples/reset-with-event-val.rb +28 -0
  37. data/examples/scheduler.rb +104 -0
  38. data/examples/set-dest.rb +23 -0
  39. data/examples/simulink/README +1 -0
  40. data/examples/simulink/delay.mdl +827 -0
  41. data/examples/simulink/derivative.mdl +655 -0
  42. data/examples/step-discrete-profiler.rb +103 -0
  43. data/examples/subsystem.rb +109 -0
  44. data/examples/sync-deadlock.rb +32 -0
  45. data/examples/sync-queue.rb +91 -0
  46. data/examples/sync-retry.rb +20 -0
  47. data/examples/sync.rb +51 -0
  48. data/examples/thermostat.rb +53 -0
  49. data/examples/zeno.rb +53 -0
  50. data/lib/accessible-index.rb +47 -0
  51. data/lib/redshift.rb +1 -0
  52. data/lib/redshift/component.rb +412 -0
  53. data/lib/redshift/meta.rb +183 -0
  54. data/lib/redshift/mixins/zeno-debugger.rb +69 -0
  55. data/lib/redshift/port.rb +57 -0
  56. data/lib/redshift/queue.rb +104 -0
  57. data/lib/redshift/redshift.rb +111 -0
  58. data/lib/redshift/state.rb +31 -0
  59. data/lib/redshift/syntax.rb +558 -0
  60. data/lib/redshift/target/c.rb +37 -0
  61. data/lib/redshift/target/c/component-gen.rb +1303 -0
  62. data/lib/redshift/target/c/flow-gen.rb +325 -0
  63. data/lib/redshift/target/c/flow/algebraic.rb +85 -0
  64. data/lib/redshift/target/c/flow/buffer.rb +74 -0
  65. data/lib/redshift/target/c/flow/delay.rb +203 -0
  66. data/lib/redshift/target/c/flow/derivative.rb +101 -0
  67. data/lib/redshift/target/c/flow/euler.rb +67 -0
  68. data/lib/redshift/target/c/flow/expr.rb +113 -0
  69. data/lib/redshift/target/c/flow/rk4.rb +80 -0
  70. data/lib/redshift/target/c/library.rb +85 -0
  71. data/lib/redshift/target/c/world-gen.rb +1370 -0
  72. data/lib/redshift/target/spec.rb +34 -0
  73. data/lib/redshift/world.rb +300 -0
  74. data/rakefile +37 -0
  75. data/test/test.rb +52 -0
  76. data/test/test_buffer.rb +58 -0
  77. data/test/test_connect.rb +242 -0
  78. data/test/test_connect_parallel.rb +47 -0
  79. data/test/test_connect_strict.rb +135 -0
  80. data/test/test_constant.rb +74 -0
  81. data/test/test_delay.rb +145 -0
  82. data/test/test_derivative.rb +48 -0
  83. data/test/test_discrete.rb +592 -0
  84. data/test/test_discrete_isolated.rb +92 -0
  85. data/test/test_exit.rb +59 -0
  86. data/test/test_flow.rb +200 -0
  87. data/test/test_flow_link.rb +288 -0
  88. data/test/test_flow_sub.rb +100 -0
  89. data/test/test_flow_trans.rb +292 -0
  90. data/test/test_inherit.rb +127 -0
  91. data/test/test_inherit_event.rb +74 -0
  92. data/test/test_inherit_flow.rb +139 -0
  93. data/test/test_inherit_link.rb +65 -0
  94. data/test/test_inherit_setup.rb +56 -0
  95. data/test/test_inherit_state.rb +66 -0
  96. data/test/test_inherit_transition.rb +168 -0
  97. data/test/test_numerics.rb +34 -0
  98. data/test/test_queue.rb +90 -0
  99. data/test/test_queue_alone.rb +115 -0
  100. data/test/test_reset.rb +209 -0
  101. data/test/test_setup.rb +119 -0
  102. data/test/test_strict_continuity.rb +410 -0
  103. data/test/test_strict_reset_error.rb +30 -0
  104. data/test/test_strictness_error.rb +32 -0
  105. data/test/test_sync.rb +185 -0
  106. data/test/test_world.rb +328 -0
  107. 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