flor 0.0.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/CHANGELOG.md +13 -0
  2. data/LICENSE.txt +1 -1
  3. data/Makefile +66 -0
  4. data/README.md +57 -0
  5. data/fail.txt +7 -0
  6. data/flor.gemspec +12 -9
  7. data/intercepted.txt +123 -0
  8. data/lib/flor/colours.rb +140 -0
  9. data/lib/flor/conf.rb +88 -0
  10. data/lib/flor/core/executor.rb +473 -0
  11. data/lib/flor/core/node.rb +397 -0
  12. data/lib/flor/core/procedure.rb +600 -0
  13. data/lib/flor/core/texecutor.rb +209 -0
  14. data/lib/flor/core.rb +93 -0
  15. data/lib/flor/dollar.rb +248 -0
  16. data/lib/flor/errors.rb +36 -0
  17. data/lib/flor/flor.rb +556 -0
  18. data/lib/flor/log.rb +336 -0
  19. data/lib/flor/migrations/0001_tables.rb +122 -0
  20. data/lib/flor/parser.rb +414 -0
  21. data/lib/flor/pcore/_arr.rb +49 -0
  22. data/lib/flor/pcore/_atom.rb +43 -0
  23. data/lib/flor/pcore/_att.rb +160 -0
  24. data/lib/flor/pcore/_dump.rb +60 -0
  25. data/lib/flor/pcore/_err.rb +30 -0
  26. data/lib/flor/pcore/_happly.rb +73 -0
  27. data/lib/flor/pcore/_obj.rb +65 -0
  28. data/lib/flor/pcore/_skip.rb +63 -0
  29. data/lib/flor/pcore/apply.rb +60 -0
  30. data/lib/flor/pcore/arith.rb +46 -0
  31. data/lib/flor/pcore/break.rb +71 -0
  32. data/lib/flor/pcore/cmp.rb +72 -0
  33. data/lib/flor/pcore/cond.rb +57 -0
  34. data/lib/flor/pcore/cursor.rb +223 -0
  35. data/lib/flor/pcore/define.rb +96 -0
  36. data/lib/flor/pcore/fail.rb +45 -0
  37. data/lib/flor/pcore/ife.rb +56 -0
  38. data/lib/flor/pcore/loop.rb +53 -0
  39. data/lib/flor/pcore/map.rb +75 -0
  40. data/lib/flor/pcore/match.rb +70 -0
  41. data/lib/flor/pcore/move.rb +65 -0
  42. data/lib/flor/pcore/noeval.rb +46 -0
  43. data/lib/flor/pcore/noret.rb +47 -0
  44. data/lib/flor/pcore/push.rb +69 -0
  45. data/lib/flor/pcore/sequence.rb +39 -0
  46. data/lib/flor/pcore/set.rb +76 -0
  47. data/lib/flor/pcore/stall.rb +35 -0
  48. data/lib/flor/pcore/until.rb +122 -0
  49. data/lib/flor/pcore/val.rb +40 -0
  50. data/lib/flor/punit/cancel.rb +69 -0
  51. data/lib/flor/punit/cmap.rb +76 -0
  52. data/lib/flor/punit/concurrence.rb +149 -0
  53. data/lib/flor/punit/every.rb +46 -0
  54. data/lib/flor/punit/on.rb +81 -0
  55. data/lib/flor/punit/schedule.rb +68 -0
  56. data/lib/flor/punit/signal.rb +47 -0
  57. data/lib/flor/punit/sleep.rb +53 -0
  58. data/lib/flor/punit/task.rb +109 -0
  59. data/lib/flor/punit/trace.rb +51 -0
  60. data/lib/flor/punit/trap.rb +100 -0
  61. data/lib/flor/to_string.rb +81 -0
  62. data/lib/flor/tools/env.rb +103 -0
  63. data/lib/flor/tools/repl.rb +231 -0
  64. data/lib/flor/unit/executor.rb +260 -0
  65. data/lib/flor/unit/hooker.rb +186 -0
  66. data/lib/flor/unit/journal.rb +52 -0
  67. data/lib/flor/unit/loader.rb +181 -0
  68. data/lib/flor/unit/logger.rb +181 -0
  69. data/lib/flor/unit/models/execution.rb +105 -0
  70. data/lib/flor/unit/models/pointer.rb +31 -0
  71. data/lib/flor/unit/models/timer.rb +52 -0
  72. data/lib/flor/unit/models/trace.rb +31 -0
  73. data/lib/flor/unit/models/trap.rb +130 -0
  74. data/lib/flor/unit/models.rb +106 -0
  75. data/lib/flor/unit/scheduler.rb +419 -0
  76. data/lib/flor/unit/storage.rb +633 -0
  77. data/lib/flor/unit/tasker.rb +191 -0
  78. data/lib/flor/unit/waiter.rb +146 -0
  79. data/lib/flor/unit/wlist.rb +77 -0
  80. data/lib/flor/unit.rb +50 -0
  81. data/lib/flor.rb +40 -3
  82. metadata +152 -22
  83. checksums.yaml +0 -7
  84. data/Rakefile +0 -52
@@ -0,0 +1,600 @@
1
+ #--
2
+ # Copyright (c) 2015-2017, John Mettraux, jmettraux+flor@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ class Flor::Procedure < Flor::Node
27
+
28
+ def self.inherited(subclass)
29
+
30
+ (@@inherited ||= []) << subclass
31
+ end
32
+
33
+ def self.[](name)
34
+
35
+ @@inherited.find { |k| k.names && k.names.include?(name) }
36
+ end
37
+
38
+ class << self
39
+
40
+ def names(*names)
41
+
42
+ names = names.flatten
43
+ @names = names if names.any?
44
+
45
+ @names
46
+ end
47
+
48
+ alias :name :names
49
+ end
50
+
51
+ def pre_execute
52
+
53
+ # empty default implementation
54
+ end
55
+
56
+ def trigger_on_error
57
+
58
+ @message['on_error'] = true
59
+
60
+ close_node('on-error')
61
+
62
+ @node['on_receive_last'] =
63
+ apply(@node['on_error'].shift, [ @message ], tree[2])
64
+
65
+ nids = @node['cnodes']
66
+
67
+ if nids && nids.any?
68
+ cancel_children
69
+ else
70
+ do_receive # which should trigger 'on_receive_last'
71
+ end
72
+ end
73
+
74
+ def debug_tree(nid=nil)
75
+
76
+ nid ||= self.nid
77
+ tree = lookup_tree(nid)
78
+
79
+ Flor.print_tree(tree, nid)
80
+ end
81
+
82
+ def debug_msg(msg=message)
83
+
84
+ Flor.detail_msg(@executor, msg)
85
+ end
86
+
87
+ protected
88
+
89
+ def counter_next(k)
90
+
91
+ @executor.counter_next(k)
92
+ end
93
+
94
+ def stack_status(flavour, status)
95
+
96
+ h = {
97
+ 'point' => @message['point'], 'status' => status, 'ctime' => Flor.tstamp }
98
+ h['flavour'] = flavour if flavour
99
+ mm = @message['m']; h['m'] = mm if mm
100
+ mf = @message['from']; h['from'] = mf if mf
101
+
102
+ s = node_status
103
+ @node['status'].pop if s['m'] == h['m'] && s['status'] != 'ended'
104
+ # only keep the latest effect of a message (probably "ended")
105
+
106
+ @node['status'] << h
107
+ end
108
+
109
+ def close_node(flavour=@message['flavour'])
110
+ stack_status(flavour, 'closed')
111
+ end
112
+ def open_node
113
+ stack_status(@message['flavour'], nil)
114
+ end
115
+ def end_node
116
+ stack_status(@message['flavour'], 'ended')
117
+ end
118
+
119
+ def children
120
+
121
+ tree[1]
122
+ end
123
+
124
+ def att_children
125
+
126
+ children.select { |c| c[0] == '_att' }
127
+ end
128
+
129
+ def non_att_children
130
+
131
+ children.select { |c| c[0] != '_att' }
132
+ end
133
+
134
+ def unkeyed_children
135
+
136
+ children.select { |c| c[0] != '_att' || c[1].size == 1 }
137
+ end
138
+
139
+ def first_unkeyed_child_id
140
+
141
+ children.index { |c| c[0] != '_att' || c[1].size == 1 }
142
+ end
143
+
144
+ def first_non_att_child_id
145
+
146
+ children.index { |c| c[0] != '_att' }
147
+ end
148
+
149
+ def att(*keys)
150
+
151
+ return nil unless @node['atts']
152
+
153
+ keys.each do |k|
154
+ k = k.to_s unless k == nil
155
+ a = @node['atts'].assoc(k)
156
+ return a.last if a
157
+ end
158
+
159
+ nil
160
+ end
161
+
162
+ def has_att?(k)
163
+
164
+ @node['atts'].collect(&:first).include?(k)
165
+ end
166
+
167
+ def att_a(*keys)
168
+
169
+ if keys.last == nil
170
+ keys.pop
171
+ Flor.to_a(att(*keys))
172
+ else
173
+ Array(att(*keys))
174
+ end
175
+ end
176
+
177
+ def tags_to_nids(tags)
178
+
179
+ tags = Array(tags)
180
+
181
+ @execution['nodes'].inject([]) { |a, (nid, n)|
182
+ a << nid if ((n['tags'] || []) & tags).any?
183
+ a
184
+ }
185
+ end
186
+
187
+ def execute_child(index=0, sub=nil, h=nil)
188
+
189
+ return reply \
190
+ if index < 0 || ( ! tree[1].is_a?(Array)) || tree[1][index] == nil
191
+
192
+ sub = counter_next('subs') if sub == true
193
+
194
+ cnid = Flor.child_nid(nid, index, sub)
195
+
196
+ hh = {
197
+ 'point' => 'execute',
198
+ 'nid' => cnid,
199
+ 'tree' => tree[1][index],
200
+ 'payload' => payload.current }
201
+ hh.merge!(h) if h
202
+
203
+ reply(hh)
204
+ end
205
+
206
+ def unatt_unkeyed_children(first_only=false)
207
+
208
+ found = false
209
+
210
+ unkeyed, keyed =
211
+ att_children.partition { |c|
212
+ if found
213
+ false
214
+ else
215
+ is_unkeyed = c[1].size == 1
216
+ found = true if is_unkeyed && first_only
217
+ is_unkeyed
218
+ end
219
+ }
220
+
221
+ unkeyed = unkeyed
222
+ .collect { |c| c[1].first }
223
+ .reject { |c| c[0] == '_' && c[1] == [] }
224
+
225
+ cn = keyed + unkeyed + non_att_children
226
+
227
+ @node['tree'] = [ tree[0], cn, tree[2] ] if cn != children
228
+ end
229
+
230
+ def unatt_first_unkeyed_child
231
+
232
+ unatt_unkeyed_children(true)
233
+ end
234
+
235
+ def stringify_first_child
236
+
237
+ c = non_att_children.first
238
+ return unless c
239
+ return unless c[1] == [] && c[0].is_a?(String)
240
+
241
+ ci = children.index(c)
242
+ cn = Flor.dup(children)
243
+ cn[ci] = [ '_sqs', c[0], c[2] ]
244
+
245
+ @node['tree'] = [ tree[0], cn, tree[2] ]
246
+ end
247
+
248
+ def execute
249
+
250
+ receive
251
+ end
252
+
253
+ # The executor calls #do_receive, while most procedure implementations
254
+ # override #receive...
255
+ #
256
+ def do_receive
257
+
258
+ from_child =
259
+ if ns = @node['cnodes']
260
+ ns.delete(from)
261
+ else
262
+ nil
263
+ end
264
+
265
+ if node_closed?
266
+ return receive_from_child_when_closed if from_child
267
+ return receive_when_closed
268
+ elsif node_ended?
269
+ return receive_when_ended
270
+ end
271
+
272
+ receive
273
+ end
274
+
275
+ def pop_on_receive_last
276
+
277
+ orl = @node['on_receive_last']
278
+
279
+ return nil unless orl
280
+ return nil if orl.empty?
281
+
282
+ open_node unless node_status['flavour'] == 'on-error'
283
+ @node['on_receive_last'] = []
284
+
285
+ @node['mtime'] = Flor.tstamp
286
+
287
+ orl
288
+ end
289
+
290
+ def receive_from_child_when_closed
291
+
292
+ (@node['cnodes'].empty? && pop_on_receive_last) || reply
293
+ end
294
+
295
+ def receive_when_closed
296
+
297
+ []
298
+ end
299
+
300
+ def receive_when_ended
301
+
302
+ []
303
+ end
304
+
305
+ def receive
306
+
307
+ @fcid = point == 'receive' ? Flor.child_id(from) : nil
308
+ @ncid = (@fcid || -1) + 1
309
+
310
+ return receive_first if @fcid == nil
311
+
312
+ child = children[@fcid]
313
+
314
+ return receive_att if child && child[0] == '_att'
315
+
316
+ receive_non_att
317
+ end
318
+
319
+ def receive_first
320
+
321
+ return receive_last_att if children[0] && children[0][0] != '_att'
322
+ execute_child(@ncid)
323
+ end
324
+
325
+ def receive_att
326
+
327
+ nctree = children[@ncid]
328
+
329
+ return receive_last_att if nctree == nil || nctree[0] != '_att'
330
+ execute_child(@ncid)
331
+ end
332
+
333
+ def receive_last_att
334
+
335
+ return receive_last if children[@ncid] == nil
336
+ execute_child(@ncid)
337
+ end
338
+
339
+ def receive_non_att
340
+
341
+ if @node['rets']
342
+ @node['rets'] << Flor.dup(payload['ret'])
343
+ @node['mtime'] = Flor.tstamp
344
+ end
345
+
346
+ return receive_last if children[@ncid] == nil
347
+ execute_child(@ncid)
348
+ end
349
+
350
+ def receive_last
351
+
352
+ reply
353
+ end
354
+
355
+ # Used by 'cursor' (and 'loop') when
356
+ # ```
357
+ # cursor 'main'
358
+ # # is equivalent to:
359
+ # cursor tag: 'main'
360
+ # ```
361
+ #
362
+ def receive_unkeyed_tag_att
363
+
364
+ return [] if @node['tags']
365
+ # "tag:" encountered, walk away
366
+
367
+ ret = @message['payload']['ret']
368
+ ret = Array(ret).flatten
369
+ ret = nil unless ret.any? && ret.all? { |e| e.is_a?(String) }
370
+
371
+ return [] unless ret
372
+
373
+ (@node['tags'] ||= []).concat(ret)
374
+
375
+ reply('point' => 'entered', 'nid' => nid, 'tags' => ret)
376
+ end
377
+
378
+ def reply(h={})
379
+
380
+ m = {}
381
+ m['point'] = 'receive'
382
+ m['exid'] = exid
383
+ m['nid'] = parent
384
+ m['from'] = nid
385
+
386
+ m['sm'] = @message['m']
387
+
388
+ ret = :no
389
+ ret = h.delete('ret') if h.has_key?('ret')
390
+
391
+ m['payload'] = payload.current
392
+
393
+ m.merge!(h)
394
+
395
+ m['payload']['ret'] = ret if ret != :no
396
+
397
+ end_node if m['nid'] == parent && m['point'] == 'receive'
398
+
399
+ [ m ]
400
+ end
401
+
402
+ def queue(h); reply(h); end
403
+
404
+ def error_reply(o)
405
+
406
+ reply('point' => 'failed', 'error' => Flor.to_error(o))
407
+ end
408
+
409
+ def schedule(h)
410
+
411
+ h['point'] ||= 'schedule'
412
+ h['payload'] ||= {}
413
+ h['nid'] ||= nid
414
+
415
+ reply(h)
416
+ end
417
+
418
+ def lookup_var_node(node, mode, k=nil)
419
+
420
+ vars = node['vars']
421
+
422
+ if vars
423
+ return node if mode == 'l'
424
+ return node if mode == '' && Flor.deep_has_key?(vars, k)
425
+ end
426
+
427
+ if cnode = mode == '' && @execution['nodes'][node['cnid']]
428
+ return cnode if Flor.deep_has_key?(cnode['vars'], k)
429
+ end
430
+
431
+ par = parent_node(node)
432
+
433
+ return node if vars && par == nil && mode == 'g'
434
+ return lookup_var_node(par, mode, k) if par
435
+
436
+ nil
437
+ end
438
+
439
+ def set_var(mode, k, v)
440
+
441
+ fail IndexError.new("cannot set domain variables") if mode == 'd'
442
+
443
+ node = lookup_var_node(@node, mode, k)
444
+ node = lookup_var_node(@node, 'l', k) if node.nil? && mode == ''
445
+
446
+ if node
447
+
448
+ b, v = Flor.deep_set(node['vars'], k, v)
449
+
450
+ return v if b
451
+ end
452
+
453
+ fail IndexError.new("couldn't set var #{mode}v.#{k}")
454
+ end
455
+
456
+ def set_field(k, v)
457
+
458
+ success, value = Flor.deep_set(payload.copy, k, v)
459
+
460
+ fail IndexError.new("couldn't set field #{k}") unless success
461
+
462
+ value
463
+ end
464
+
465
+ def set_value(k, v)
466
+
467
+ return if k == '_'
468
+
469
+ cat, mod, key = key_split(k)
470
+
471
+ case cat[0, 1]
472
+ when 'f' then set_field(key, v)
473
+ when 'v' then set_var(mod, key, v)
474
+ #when 'w' then set_war(key, v)
475
+ else fail IndexError.new("don't know how to set #{k.inspect}")
476
+ end
477
+ end
478
+
479
+ def apply(fun, args, line, anid=true)
480
+
481
+ fni = fun[1]['nid'] # fun nid
482
+ ani = anid ? Flor.sub_nid(fni, counter_next('subs')) : fni
483
+ # the "trap" apply doesn't want a subid generated before it triggers...
484
+
485
+ cni = fun[1]['cnid'] # closure nid
486
+
487
+ t = lookup_tree(fni)
488
+ t = t[0] if fun[1]['head']
489
+
490
+ sig = t[1].select { |c| c[0] == '_att' }
491
+ sig = sig.drop(1) if t[0] == 'define'
492
+
493
+ vars = {}
494
+ vars['arguments'] = args # should I dup?
495
+ sig.each_with_index do |att, i|
496
+ key = att[1].first[0]
497
+ vars[key] = args[i]
498
+ end
499
+
500
+ ms = reply(
501
+ 'point' => 'execute',
502
+ 'nid' => ani,
503
+ 'tree' => [ '_apply', t[1], line ],
504
+ 'vars' => vars,
505
+ 'cnid' => cni)
506
+
507
+ if oe = fun[1]['on_error']
508
+ ms.first['on_error'] = oe
509
+ end
510
+
511
+ ms
512
+ end
513
+
514
+ def cancel_nodes(nids)
515
+
516
+ (nids || [])
517
+ .collect { |i| reply('point' => 'cancel', 'nid' => i, 'from' => nid) }
518
+ .flatten(1)
519
+ end
520
+
521
+ def cancel_reply # "cancel" as a noun
522
+
523
+ reply(
524
+ 'cause' => 'cancel',
525
+ 'payload' => @message['payload'] || @node['payload'])
526
+ end
527
+
528
+ def cancel_children
529
+
530
+ cancel_nodes(@node['cnodes'])
531
+ end
532
+
533
+ # The executor calls #do_cancel, while most procedure implementations
534
+ # override #cancel...
535
+ #
536
+ def do_cancel
537
+
538
+ return kill if @message['flavour'] == 'kill'
539
+
540
+ return cancel_when_ended if node_ended?
541
+ return cancel_when_closed if node_closed?
542
+
543
+ cancel
544
+ end
545
+
546
+ def cancel_when_ended
547
+
548
+ [] # node has already emitted reply to parent, ignore any later request
549
+ end
550
+
551
+ def cancel_when_closed
552
+
553
+ [] # by default, no effect
554
+ end
555
+
556
+ def cancel
557
+
558
+ close_node
559
+
560
+ nids = @node['cnodes']
561
+
562
+ if nids && nids.any?
563
+ cancel_children
564
+ else
565
+ cancel_reply
566
+ end
567
+ end
568
+
569
+ def kill
570
+
571
+ return [] if node_ended?
572
+
573
+ reply + cancel_children
574
+ end
575
+ end
576
+
577
+
578
+ # Not really a procedure, more like a macro, rewrites its tree and returns
579
+ # a new message to queue (with a rewritten tree).
580
+ #
581
+ class Flor::Macro < Flor::Procedure
582
+
583
+ # Called by the executor.
584
+ #
585
+ def rewrite
586
+
587
+ t = rewrite_tree
588
+
589
+ m = @message.dup
590
+ m['tree'] = t
591
+ m['rewritten'] = tree
592
+
593
+ m
594
+ end
595
+ end
596
+
597
+ # A namespace for primitive procedures
598
+ #
599
+ module Flor::Pro; end
600
+