flor 0.0.1 → 0.9.0

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 (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
+