xpflow 0.1b

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 (74) hide show
  1. data/bin/xpflow +96 -0
  2. data/lib/colorado.rb +198 -0
  3. data/lib/json/add/core.rb +243 -0
  4. data/lib/json/add/rails.rb +8 -0
  5. data/lib/json/common.rb +423 -0
  6. data/lib/json/editor.rb +1369 -0
  7. data/lib/json/ext.rb +28 -0
  8. data/lib/json/pure/generator.rb +442 -0
  9. data/lib/json/pure/parser.rb +320 -0
  10. data/lib/json/pure.rb +15 -0
  11. data/lib/json/version.rb +8 -0
  12. data/lib/json.rb +62 -0
  13. data/lib/mime/types.rb +881 -0
  14. data/lib/mime-types.rb +3 -0
  15. data/lib/restclient/abstract_response.rb +106 -0
  16. data/lib/restclient/exceptions.rb +193 -0
  17. data/lib/restclient/net_http_ext.rb +55 -0
  18. data/lib/restclient/payload.rb +235 -0
  19. data/lib/restclient/raw_response.rb +34 -0
  20. data/lib/restclient/request.rb +316 -0
  21. data/lib/restclient/resource.rb +169 -0
  22. data/lib/restclient/response.rb +24 -0
  23. data/lib/restclient.rb +174 -0
  24. data/lib/xpflow/bash.rb +341 -0
  25. data/lib/xpflow/bundle.rb +113 -0
  26. data/lib/xpflow/cmdline.rb +249 -0
  27. data/lib/xpflow/collection.rb +122 -0
  28. data/lib/xpflow/concurrency.rb +79 -0
  29. data/lib/xpflow/data.rb +393 -0
  30. data/lib/xpflow/dsl.rb +816 -0
  31. data/lib/xpflow/engine.rb +574 -0
  32. data/lib/xpflow/ensemble.rb +135 -0
  33. data/lib/xpflow/events.rb +56 -0
  34. data/lib/xpflow/experiment.rb +65 -0
  35. data/lib/xpflow/exts/facter.rb +30 -0
  36. data/lib/xpflow/exts/g5k.rb +931 -0
  37. data/lib/xpflow/exts/g5k_use.rb +50 -0
  38. data/lib/xpflow/exts/gui.rb +140 -0
  39. data/lib/xpflow/exts/model.rb +155 -0
  40. data/lib/xpflow/graph.rb +1603 -0
  41. data/lib/xpflow/graph_xpflow.rb +251 -0
  42. data/lib/xpflow/import.rb +196 -0
  43. data/lib/xpflow/library.rb +349 -0
  44. data/lib/xpflow/logging.rb +153 -0
  45. data/lib/xpflow/manager.rb +147 -0
  46. data/lib/xpflow/nodes.rb +1250 -0
  47. data/lib/xpflow/runs.rb +773 -0
  48. data/lib/xpflow/runtime.rb +125 -0
  49. data/lib/xpflow/scope.rb +168 -0
  50. data/lib/xpflow/ssh.rb +186 -0
  51. data/lib/xpflow/stat.rb +50 -0
  52. data/lib/xpflow/stdlib.rb +381 -0
  53. data/lib/xpflow/structs.rb +369 -0
  54. data/lib/xpflow/taktuk.rb +193 -0
  55. data/lib/xpflow/templates/ssh-config.basic +14 -0
  56. data/lib/xpflow/templates/ssh-config.inria +18 -0
  57. data/lib/xpflow/templates/ssh-config.proxy +13 -0
  58. data/lib/xpflow/templates/taktuk +6590 -0
  59. data/lib/xpflow/templates/utils/batch +4 -0
  60. data/lib/xpflow/templates/utils/bootstrap +12 -0
  61. data/lib/xpflow/templates/utils/hostname +3 -0
  62. data/lib/xpflow/templates/utils/ping +3 -0
  63. data/lib/xpflow/templates/utils/rsync +12 -0
  64. data/lib/xpflow/templates/utils/scp +17 -0
  65. data/lib/xpflow/templates/utils/scp_many +8 -0
  66. data/lib/xpflow/templates/utils/ssh +3 -0
  67. data/lib/xpflow/templates/utils/ssh-interactive +4 -0
  68. data/lib/xpflow/templates/utils/taktuk +19 -0
  69. data/lib/xpflow/threads.rb +187 -0
  70. data/lib/xpflow/utils.rb +569 -0
  71. data/lib/xpflow/visual.rb +230 -0
  72. data/lib/xpflow/with_g5k.rb +7 -0
  73. data/lib/xpflow.rb +349 -0
  74. metadata +135 -0
@@ -0,0 +1,773 @@
1
+ # encoding: UTF-8
2
+
3
+ #
4
+ # Implementation of 'runs' or actions that can be executed
5
+ # within the experiment engine. They build logic behind DSL.
6
+ #
7
+
8
+ require 'timeout'
9
+
10
+ module XPFlow
11
+
12
+ class AbstractRun
13
+
14
+ include Traverse
15
+ include Meta
16
+
17
+ attr_accessor :key
18
+ constructor :key
19
+
20
+ def engine
21
+ # shortcut to get an engine
22
+ return Scope.engine
23
+ end
24
+
25
+ def run()
26
+ exc = nil
27
+ begin
28
+ x = execute()
29
+ Scope.current[@key] = x
30
+ rescue => e
31
+ raise if (e.is_a?(RunError)) and (e.run == self)
32
+ exc = RunError.new(self, e)
33
+ end
34
+ raise exc unless exc.nil?
35
+ return x
36
+ end
37
+
38
+ def run_threads(list, opts = {}, &block)
39
+ # TODO: pool should be in the scope...
40
+ list = listize(list)
41
+ pool_size = engine.getset.get(:pool)
42
+ nonnil = opts.select { |k, v| !v.nil? }
43
+ opts = { :pool => pool_size }.merge(nonnil)
44
+ return Threads.run(self, list, opts, &block)
45
+ end
46
+
47
+ def restart()
48
+ # returns non-nil value (in fact, a hash) when can restart from this activity
49
+ # the returned hash contains hash to copy to the scope
50
+ return false
51
+ end
52
+
53
+ def listize(o)
54
+ # tries to execute :to_list, if not
55
+ # checks if is an array, otherwise panics
56
+ if o.respond_to?(:to_list)
57
+ o = o.to_list
58
+ end
59
+ if !o.is_a?(Array)
60
+ raise "#{o} is not an array"
61
+ end
62
+ return o
63
+ end
64
+
65
+ end
66
+
67
+
68
+ class SequenceRun < AbstractRun
69
+
70
+ attr_reader :body
71
+
72
+ constructor [ :key ], :body
73
+ children :body
74
+
75
+ def check_restartability()
76
+ name = engine.config(:checkpoint)
77
+ if engine.config(:ignore_checkpoints)
78
+ engine.paranoic("Ignoring checkpoints.") \
79
+ if @body.any? { |r| r.is_a?(CheckpointRun) }
80
+ return @body
81
+ end
82
+ list = []
83
+ # TODO: I should collect all checkpoints and verify
84
+ for r in @body.reverse do
85
+ if name.nil? == false and r.is_a?(CheckpointRun) and r.name.to_s != name
86
+ list = [ r ] + list
87
+ next
88
+ end
89
+ cp = r.restart()
90
+ if cp == true
91
+ # we restarted
92
+ engine.log("Checkpoint '#{r.name}' restarted.")
93
+ break
94
+ else
95
+ list = [ r ] + list # standard case
96
+ end
97
+ end
98
+ return list
99
+ end
100
+
101
+ def execute()
102
+ tail = check_restartability()
103
+ results = []
104
+ for r in tail do
105
+ x = r.run()
106
+ results.push(x)
107
+ end
108
+ return results.last
109
+ end
110
+
111
+ def attach_to_checkpoints(obj)
112
+ @body.each do |i|
113
+ if i.is_a?(CheckpointRun)
114
+ i.parent = obj
115
+ end
116
+ end
117
+ end
118
+
119
+ def split(node)
120
+ before = []
121
+ after = []
122
+ first = true
123
+ @body.each do |el|
124
+ first = false if el == node
125
+ (first ? before : after).push(el) if el != node
126
+ end
127
+ return [before, after].map { |x| ActivityList.new(x) }
128
+ end
129
+
130
+ end
131
+
132
+ class SeqtryRun < AbstractRun
133
+
134
+ attr_reader :body
135
+ constructor [ :key ], :body
136
+
137
+ children :body
138
+
139
+ def execute()
140
+ result = nil
141
+ return nil if @body.length == 0
142
+ for r in @body do
143
+ fine = true
144
+ begin
145
+ result = r.run()
146
+ rescue RunError => e
147
+ engine.verbose("Error caused by #{e.summary}. Trying the next activity.")
148
+ fine = false
149
+ end
150
+ return result if fine == true
151
+ end
152
+ raise "Seqtry execution failed."
153
+ end
154
+
155
+ end
156
+
157
+ class ExperimentRun < AbstractRun
158
+
159
+ constructor [ :key ], :name, :args
160
+ children :name, :args
161
+
162
+ def execute()
163
+ # TODO: more things here
164
+ r = nil
165
+ name = @name.evaluate(Scope.current)
166
+ args = @args.evaluate(Scope.current)
167
+ full_name = "#{name}.__standard__"
168
+ ActivityRun.run_activity_block(full_name) do |activity|
169
+ Scope.region do |scope|
170
+ # scope[:__collection__] = Collection.new
171
+ text = "Running experiment #{name}"
172
+ r = engine.activity_period(text, { :gantt => true }) do
173
+ activity.execute(args, &@block)
174
+ end
175
+ end
176
+ end
177
+ return r
178
+ end
179
+
180
+ def get_name()
181
+ # tries to evaluate the experiment name
182
+ # without running (if it is possible)
183
+ # returns nil otherwise
184
+ return @name.evaluate_offline()
185
+ end
186
+
187
+ end
188
+
189
+ class ActivityRun < AbstractRun
190
+
191
+ constructor [ :key ], :name, :args, :opts, :block
192
+ children :args
193
+
194
+ attr_reader :opts
195
+ attr_reader :name
196
+
197
+ def self.run_activity_block(full_name)
198
+ # runs activity using the current scope
199
+ full_name = full_name.to_s
200
+ lib = Scope.current[:__library__]
201
+ ns = Scope.current[:__namespace__]
202
+ if full_name.start_with?("/")
203
+ full_name = full_name[1..-1]
204
+ lib = Scope.engine
205
+ end
206
+ libs, name = lib.into_parts(full_name)
207
+ library = lib.resolve_libs(libs)
208
+ result = Scope.region do |scope|
209
+ scope[:__library__] = library
210
+ scope[:__namespace__] = namespace = ns + libs
211
+ activity = library.get_activity_object(name)
212
+ raise "No such activity '#{namespace.join(".")}.#{name}'" if activity.nil?
213
+ yield(activity)
214
+ end
215
+ return result
216
+ end
217
+
218
+ def execute()
219
+ r = nil
220
+ this_name = @name.evaluate(Scope.current)
221
+ preargs = []
222
+ if this_name.is_a?(RunLater)
223
+ preargs = this_name.args
224
+ this_name = this_name.name
225
+ end
226
+ ActivityRun.run_activity_block(this_name) do |activity|
227
+
228
+ activity_id = engine.activity_id(this_name)
229
+ args = preargs + @args.evaluate(Scope.current)
230
+
231
+ opts = { :gantt => true }.merge(activity.opts).merge(@opts)
232
+
233
+ text = "#{this_name}:#{activity_id}"
234
+
235
+ log_level = :verbose
236
+ if this_name.to_s.start_with?("__")
237
+ log_level = :paranoic
238
+ end
239
+ if activity.doc.nil? == false
240
+ text = "[#{activity.doc}] (#{text})"
241
+ log_level = :normal
242
+ end
243
+
244
+ if !opts[:log_level].nil?
245
+ log_level = opts[:log_level]
246
+ end
247
+
248
+ period_opts = opts.merge({ :log_level => log_level })
249
+ r = engine.activity_period(text, period_opts) do
250
+ activity.execute(args, &@block)
251
+ end
252
+ end
253
+ return r
254
+ end
255
+
256
+ def get_name
257
+ @name
258
+ end
259
+
260
+ def report(started, args)
261
+ age = Time.now - started
262
+ return {
263
+ :title => "Activity #{get_name.to_s}",
264
+ :args => args.inspect,
265
+ :started => started,
266
+ :age => "#{age} s"
267
+ }
268
+ end
269
+
270
+ def to_s
271
+ "<Activity #{get_name}>"
272
+ end
273
+
274
+ def builtin?
275
+ # TODO: do it properly
276
+ name = get_name().to_s
277
+ return name.start_with?('__') && !name.start_with?('__nodes__')
278
+ end
279
+
280
+ end
281
+
282
+ class ForEachRun < AbstractRun
283
+
284
+ attr_reader :body
285
+
286
+ constructor [ :key ], :list, :iter, :opts, :body
287
+ children :list, :body
288
+ declares :iter
289
+
290
+ def execute()
291
+ list = @list.evaluate(Scope.current)
292
+ opts = @opts.evaluate(Scope.current)
293
+ ignore_errors = opts[:ignore_errors]
294
+ result = []
295
+ for item in listize(list) do
296
+ Scope.region do |scope|
297
+ scope[@iter] = item
298
+ x = Marker.new
299
+ begin
300
+ x = @body.run()
301
+ rescue RunError
302
+ raise if !ignore_errors
303
+ end
304
+ result.push(x) if !x.is_a?(Marker)
305
+ end
306
+ end
307
+ return result
308
+ end
309
+
310
+ end
311
+
312
+ class Marker
313
+ end
314
+
315
+ class ForAllRun < AbstractRun
316
+
317
+ attr_reader :body
318
+
319
+ constructor [ :key ], :list, :iter, :opts, :body
320
+ children :list, :body
321
+ declares :iter
322
+
323
+ def execute()
324
+ list = @list.evaluate(Scope.current)
325
+ opts = @opts.evaluate(Scope.current)
326
+ size = opts[:pool]
327
+ ignore_errors = opts[:ignore_errors]
328
+ result = OrderedArray.new
329
+ scope = Scope.current
330
+ run_threads(list, :pool => size) do |el, i|
331
+ Scope.set(scope.push, { @iter => el })
332
+ x = Marker.new
333
+ begin
334
+ x = @body.run()
335
+ rescue RunError
336
+ raise if !ignore_errors
337
+ end
338
+ result.give(i, x)
339
+ end
340
+ tabl = result.take(list.length)
341
+ tabl = tabl.select { |x| !x.is_a?(Marker) }
342
+ return tabl
343
+ end
344
+
345
+ end
346
+
347
+ class ForManyRun < AbstractRun
348
+
349
+ attr_reader :body
350
+
351
+ constructor [ :key ], :number, :list, :iter, :body
352
+ children :number, :list, :body
353
+ declares :iter
354
+
355
+ def execute()
356
+ rendez = Meeting.new(self)
357
+ n = @number.evaluate(Scope.current)
358
+ list = @list.evaluate(Scope.current)
359
+ scope = Scope.current
360
+ run_threads(list, :join => false) do |it, _|
361
+ Scope.set(scope.push, { @iter => it })
362
+ x = @body.run()
363
+ rendez.give(x)
364
+ end
365
+ # TODO: what about joining?
366
+ return rendez.take(n)
367
+ end
368
+
369
+ end
370
+
371
+ class ManyRun < AbstractRun
372
+
373
+ attr_reader :body
374
+
375
+ constructor [ :key ], :number, :body
376
+ children :number, :body
377
+
378
+ def execute()
379
+ n = @number.evaluate(Scope.current)
380
+ rendez = Meeting.new(self)
381
+ scope = Scope.current
382
+ run_threads(@body, :join => false) do |r, _|
383
+ Scope.set(scope)
384
+ x = r.run()
385
+ rendez.give(x)
386
+ end
387
+ return rendez.take(n)
388
+ end
389
+
390
+ end
391
+
392
+ class ParallelRun < AbstractRun
393
+
394
+ attr_reader :body
395
+
396
+ constructor [ :key ], :body
397
+ children :body
398
+
399
+ def execute()
400
+ arr = OrderedArray.new
401
+ scope = Scope.current
402
+ rs = run_threads(@body) do |r, i|
403
+ Scope.set(scope)
404
+ x = r.run()
405
+ arr.give(i, x)
406
+ end
407
+ values = arr.take(@body.length)
408
+ return values.last
409
+ end
410
+ end
411
+
412
+ class IfRun < AbstractRun
413
+
414
+ attr_reader :on_true
415
+ attr_reader :on_false
416
+
417
+ constructor [ :key ], :condition, :on_true, :on_false
418
+ children :condition, :on_true, :on_false
419
+
420
+ def execute()
421
+ x = @condition.evaluate(Scope.current)
422
+ if x
423
+ return @on_true.run()
424
+ else
425
+ return @on_false.run()
426
+ end
427
+ end
428
+
429
+ end
430
+
431
+ class SwitchRun < AbstractRun
432
+
433
+ constructor [ :key ], :cases, :default
434
+ children :cases, :default
435
+
436
+ def execute()
437
+ matches = @cases.select { |cond, result| cond.evaluate(Scope.current) }
438
+ return execute_cases(matches)
439
+ end
440
+
441
+ def execute_cases(cases)
442
+ if cases.length != 0
443
+ _, r = cases.first # execute only the first match
444
+ return r.run()
445
+ elsif @default
446
+ return @default.run()
447
+ else
448
+ return nil
449
+ end
450
+ end
451
+
452
+ end
453
+
454
+ class MultiRun < SwitchRun
455
+
456
+ def execute_cases(cases)
457
+ if cases.length != 0
458
+ scope = Scope.current
459
+ arr = OrderedArray.new
460
+ ress = run_threads(cases) do |r, i|
461
+ Scope.set(scope.push)
462
+ x = r.last.run()
463
+ arr.give(i, x)
464
+ end
465
+ return arr.take(cases.length)
466
+ elsif @default
467
+ return @default.run()
468
+ else
469
+ return nil
470
+ end
471
+ end
472
+
473
+ end
474
+
475
+ class BoundSwitchRun < SwitchRun
476
+
477
+ constructor [ :key, :cases, :default ], :condition
478
+ children :cases, :default, :condition
479
+
480
+ def execute()
481
+ v = @condition.evaluate(Scope.current)
482
+ matches = @cases.select { |cond, result|
483
+ cond.evaluate(Scope.current) == v
484
+ }
485
+ return execute_cases(matches)
486
+ end
487
+
488
+ end
489
+
490
+ class CheckpointRun < AbstractRun
491
+
492
+ attr_accessor :parent
493
+
494
+ constructor [ :key ], :name, :opts, :parent
495
+ children
496
+
497
+ def name
498
+ return '[no name]' if @name.nil?
499
+ return @name
500
+ end
501
+
502
+ def parent_keys
503
+ return @parent.args # arguments to the process
504
+ end
505
+
506
+ def state_keys
507
+ before, after = @parent.split(self)
508
+ vars1 = before.declarations.keys # vars defined BEFORE the checkpoint
509
+ vars2 = after.vars # vars used AFTER the checkpoint
510
+ return (vars1 + parent_keys) # & vars2
511
+ end
512
+
513
+ def meta_info(scope)
514
+ {
515
+ :type => :checkpoint,
516
+ :args => parent_keys.map { |x| scope[x] },
517
+ :name => @name,
518
+ :key => @key,
519
+ :parent => @parent.name
520
+ }
521
+ end
522
+
523
+ def checkpointable_libs()
524
+ libs = engine.get_libraries
525
+ libs = libs.select { |ns, l| l.respond_to?(:checkpoint) }
526
+ return libs
527
+ end
528
+
529
+ def execute()
530
+ scope = Scope.current
531
+ state = { 'vars' => {} }
532
+ state_keys.each { |k| state['vars'][k] = scope.get(k, true) } # TODO: fix this
533
+ state['meta'] = meta_info(scope)
534
+ lib_dump = checkpointable_libs().map do |ns, l|
535
+ {
536
+ :state => l.checkpoint(),
537
+ :namespace => ns
538
+ }
539
+ end
540
+ state['libs'] = lib_dump
541
+ engine.dumper.dump(state, @opts)
542
+ engine.verbose "Checkpoint '#{name}' saved."
543
+ return nil
544
+ end
545
+
546
+ def restart()
547
+ scope = Scope.current
548
+ m = meta_info(scope)
549
+ obj = engine.dumper.load(m)
550
+ return false if obj.nil?
551
+ vars = obj['vars']
552
+ libs = obj['libs']
553
+ vars.each_pair { |k, v| scope[k] = v }
554
+ cplibs = checkpointable_libs()
555
+ raise "Fatal checkpoint error (#{cplibs.length} != #{libs.length})" if cplibs.length != libs.length
556
+ raise "Fatal checkpoint error (something wrong)" if cplibs.length != libs.length
557
+ names1 = cplibs.map { |x| x.last }
558
+ names2 = libs.map { |x| x[:namespace] }
559
+ libs.each do |cp|
560
+ ns = cp[:namespace]
561
+ library = cplibs[ns]
562
+ library.restore(cp[:state])
563
+ end
564
+ return true
565
+ end
566
+
567
+ def to_s
568
+ "<Checkpoint #{@name.inspect}>"
569
+ end
570
+
571
+ end
572
+
573
+ class CacheRun < AbstractRun
574
+
575
+ attr_reader :body
576
+
577
+ constructor [ :key ], :opts, :body
578
+ children :body
579
+
580
+ def meta_info()
581
+ {
582
+ :type => :cache,
583
+ :key => @key
584
+ }
585
+ end
586
+
587
+ def execute()
588
+ ignoring = engine.config(:ignore_checkpoints)
589
+ m = meta_info()
590
+ o = engine.dumper.load(m)
591
+ if o.nil? or ignoring == true
592
+ value = @body.run()
593
+ obj = { 'meta' => m, 'value' => value }
594
+ engine.dumper.dump(obj)
595
+ return value
596
+ else
597
+ engine.verbose("Cached block for key = #{@key} loaded.")
598
+ return o['value']
599
+ end
600
+ end
601
+
602
+ end
603
+
604
+ class InfoRun < AbstractRun
605
+
606
+ attr_reader :body
607
+
608
+ constructor [ :key ], :opts, :body
609
+ children :body
610
+
611
+ def execute()
612
+ opts = @opts.evaluate(Scope.current)
613
+ opts = { :fail => true }.merge(opts)
614
+ failed = false
615
+ begin
616
+ start_time = Time.now.to_f
617
+ @body.run()
618
+ end_time = Time.now.to_f
619
+ total_time = end_time - start_time
620
+ rescue RunError => e
621
+ engine.verbose("Info run errored with #{e.summary}")
622
+ failed = true
623
+ total_time = 0.0
624
+ end
625
+ if opts[:fail] and failed
626
+ raise "info block failed: "
627
+ end
628
+ return {
629
+ :time => total_time,
630
+ :failed => failed
631
+ }
632
+ end
633
+
634
+ end
635
+
636
+ class TryRun < AbstractRun
637
+
638
+ attr_reader :body
639
+
640
+ constructor [ :key ], :opts, :body
641
+ children :body
642
+
643
+ def execute()
644
+ opts = { :retry => 1, :timeout => 0 }.merge(@opts.evaluate(Scope.current))
645
+ engine.verbose("Try block: #{opts}")
646
+ timeout, times = opts[:timeout], opts[:retry]
647
+ times = 1 if times == false
648
+ times = Infinity if times == true
649
+ exc = nil
650
+ for i in 1..times do
651
+ begin
652
+ if timeout == 0
653
+ return @body.run()
654
+ else
655
+ begin
656
+ r = Timeout::timeout(timeout, exc) do
657
+ @body.run()
658
+ end
659
+ return r
660
+ rescue Timeout::Error => e
661
+ raise RunMsgError.new(self, "Timeout")
662
+ end
663
+ end
664
+ rescue RunError => e
665
+ engine.verbose("Try rerun at #{meta.location}; caused by #{e.summary}")
666
+ exc = e
667
+ end
668
+ end
669
+ raise exc
670
+ end
671
+ end
672
+
673
+ class ResultRun < AbstractRun
674
+
675
+ attr_reader :body
676
+
677
+ constructor [ :key ], :path, :opts, :body
678
+ children :body
679
+
680
+ def execute()
681
+ path = @path.evaluate(Scope.current)
682
+ opts = @opts.evaluate(Scope.current)
683
+ if File.exist?(path)
684
+ engine.log("Result `#{path}' exists already. I won't run again.")
685
+ yaml = IO.read(path)
686
+ return YAML.load(yaml)
687
+ end
688
+ r = @body.run()
689
+ IO.write(path, r.to_yaml)
690
+ return r
691
+ end
692
+
693
+ end
694
+
695
+ class TimesRun < AbstractRun
696
+
697
+ attr_reader :body
698
+
699
+ constructor [ :key ], :loops, :iter, :body
700
+ children :body
701
+
702
+ def execute()
703
+ loops = @loops.evaluate(Scope.current)
704
+ vals = []
705
+ for i in 0...loops do
706
+ Scope.region do |scope|
707
+ scope[@iter] = i
708
+ r = @body.run()
709
+ vals.push(r)
710
+ end
711
+ end
712
+ return vals
713
+ end
714
+
715
+ end
716
+
717
+ class LoopRun < AbstractRun
718
+
719
+ attr_reader :body
720
+
721
+ constructor [ :key ], :flag, :iter, :array, :body, :opts
722
+ children :body
723
+
724
+ def execute()
725
+ opts = @opts.evaluate(Scope.current)
726
+ max = opts[:max]
727
+ arr = []
728
+ result = Scope.region do |scope|
729
+ scope[@flag] = done = [ :nothing, nil ]
730
+
731
+ count = 0
732
+ while true do
733
+ scope[@iter] = count
734
+ scope[@array] = arr.clone
735
+ res = nil
736
+ for r in @body do
737
+ res = r.run()
738
+ done = scope[@flag]
739
+ break if done.first != :nothing
740
+ end
741
+ break if done.first == :return
742
+ arr.push(res)
743
+ count += 1
744
+ break if (!max.nil? and count >= max)
745
+ end
746
+
747
+ if done.last.nil?
748
+ arr
749
+ else
750
+ done.last
751
+ end
752
+ end
753
+ return result
754
+ end
755
+
756
+ end
757
+
758
+ class ReturnLoopRun < AbstractRun
759
+
760
+ constructor [ :key ], :flag, :cond, :value
761
+ children
762
+
763
+ def execute()
764
+ v = @cond.evaluate(Scope.current)
765
+ if v then
766
+ r = @value.evaluate(Scope.current)
767
+ Scope.current[@flag] = [ :return, r ]
768
+ end
769
+ end
770
+ end
771
+
772
+ end
773
+