flor 0.16.1 → 0.16.2

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/CREDITS.md +1 -0
  4. data/Makefile +1 -1
  5. data/README.md +82 -6
  6. data/lib/flor.rb +1 -1
  7. data/lib/flor/conf.rb +19 -6
  8. data/lib/flor/core/executor.rb +45 -16
  9. data/lib/flor/core/node.rb +4 -4
  10. data/lib/flor/core/procedure.rb +40 -0
  11. data/lib/flor/djan.rb +5 -2
  12. data/lib/flor/flor.rb +92 -7
  13. data/lib/flor/id.rb +19 -0
  14. data/lib/flor/migrations/0001_tables.rb +6 -6
  15. data/lib/flor/migrations/0005_pointer_content.rb +20 -0
  16. data/lib/flor/pcore/_apply.rb +103 -57
  17. data/lib/flor/pcore/_att.rb +15 -1
  18. data/lib/flor/pcore/_ref.rb +2 -1
  19. data/lib/flor/pcore/arith.rb +46 -9
  20. data/lib/flor/pcore/break.rb +1 -1
  21. data/lib/flor/pcore/case.rb +41 -0
  22. data/lib/flor/pcore/collect.rb +1 -1
  23. data/lib/flor/pcore/cursor.rb +1 -1
  24. data/lib/flor/pcore/define.rb +32 -6
  25. data/lib/flor/pcore/iterator.rb +12 -0
  26. data/lib/flor/pcore/on_cancel.rb +1 -1
  27. data/lib/flor/pcore/set.rb +14 -4
  28. data/lib/flor/punit/{ccollect.rb → c_collect.rb} +2 -2
  29. data/lib/flor/punit/c_each.rb +11 -0
  30. data/lib/flor/punit/c_for_each.rb +41 -0
  31. data/lib/flor/punit/c_iterator.rb +160 -0
  32. data/lib/flor/punit/c_map.rb +43 -0
  33. data/lib/flor/punit/concurrence.rb +43 -200
  34. data/lib/flor/punit/graft.rb +3 -2
  35. data/lib/flor/punit/m_ram.rb +281 -0
  36. data/lib/flor/unit.rb +1 -0
  37. data/lib/flor/unit/caller.rb +6 -1
  38. data/lib/flor/unit/executor.rb +17 -4
  39. data/lib/flor/unit/ganger.rb +12 -1
  40. data/lib/flor/unit/hloader.rb +251 -0
  41. data/lib/flor/unit/hook.rb +74 -15
  42. data/lib/flor/unit/hooker.rb +9 -12
  43. data/lib/flor/unit/loader.rb +41 -17
  44. data/lib/flor/unit/models.rb +54 -18
  45. data/lib/flor/unit/models/execution.rb +15 -4
  46. data/lib/flor/unit/models/pointer.rb +11 -0
  47. data/lib/flor/unit/scheduler.rb +126 -30
  48. data/lib/flor/unit/spooler.rb +5 -3
  49. data/lib/flor/unit/storage.rb +40 -13
  50. data/lib/flor/unit/waiter.rb +165 -26
  51. data/lib/flor/unit/wlist.rb +98 -5
  52. metadata +10 -4
  53. data/lib/flor/punit/cmap.rb +0 -112
@@ -64,8 +64,9 @@ class Flor::Pro::Graft < Flor::Procedure
64
64
 
65
65
  # graft subtree into parent node
66
66
 
67
- parent_tree = lookup_tree(parent)
68
- parent_tree[1][child_id] = tree
67
+ if parent_tree = lookup_tree(parent)
68
+ parent_tree[1][child_id] = tree
69
+ end
69
70
 
70
71
  # re-apply self with subtree
71
72
 
@@ -0,0 +1,281 @@
1
+
2
+ # Module extracted out of "concurrence", deals with receivers and mergers.
3
+ #
4
+ # Should it deal with remainder?
5
+ #
6
+ module Flor::Pro::ReceiveAndMerge
7
+
8
+ protected
9
+
10
+ def receive_from_branch
11
+
12
+ (@node['payloads'] ||= {})[from] = @message['payload']
13
+
14
+ apply_receiver
15
+ end
16
+
17
+ def apply_receiver
18
+
19
+ @node['receiver'] ||= determine_receiver
20
+
21
+ if @node['receiver'].is_a?(String)
22
+ apply_receiver_method
23
+ else
24
+ apply_receiver_function
25
+ end
26
+ end
27
+
28
+ def apply_receiver_method
29
+
30
+ ret = send('rm__' + @node['receiver'])
31
+ msg = { 'payload' => { 'ret' => ret } }
32
+
33
+ receive_from_receiver(msg)
34
+ end
35
+
36
+ def apply_receiver_function
37
+
38
+ (@node['on_receive_queue'] ||= []) << from
39
+
40
+ dequeue_receiver_function
41
+ end
42
+
43
+ def dequeue_receiver_function
44
+
45
+ if @node['on_receive_nids']
46
+ []
47
+ elsif f = (@node['on_receive_queue'] || []).shift
48
+ ms = apply(@node['receiver'], receiver_args(f), tree[2])
49
+ @node['on_receive_nids'] = [ ms.first['nid'], f ]
50
+ ms
51
+ else
52
+ []
53
+ end
54
+ end
55
+
56
+ def receiver_args(from)
57
+
58
+ rs = Flor.dup(@node['payloads'])
59
+
60
+ [ [ 'reply', rs[from] ],
61
+ [ 'from', from ],
62
+ [ 'replies', rs ],
63
+ [ 'branch_count', @node['branch_count'] ],
64
+ [ 'over', !! @node['over'] ] ]
65
+ end
66
+
67
+ def receive_from_receiver(msg=message)
68
+
69
+ ret = msg['payload']['ret']
70
+ over = @node['over']
71
+
72
+ if ret.is_a?(Hash) && ret.keys == %w[ done payload ]
73
+ over = over || ret['done']
74
+ from = @node['on_receive_nids'][1]
75
+ @node['payloads'][from] = ret['payload']
76
+ else
77
+ over = over || ret
78
+ end
79
+
80
+ @node['on_receive_nids'] = nil
81
+
82
+ just_over = over && ! @node['over']
83
+
84
+ @node['over'] ||= just_over
85
+
86
+ if just_over
87
+ apply_merger
88
+ elsif ! over
89
+ [] # wait for more branches
90
+ else
91
+ receive_from_merger(nil)
92
+ end +
93
+ dequeue_receiver_function
94
+ end
95
+
96
+ def apply_merger
97
+
98
+ if (m = determine_merger).is_a?(Hash)
99
+ apply_merger_method(m)
100
+ else
101
+ apply_merger_function(m)
102
+ end
103
+ end
104
+
105
+ def apply_merger_method(h)
106
+
107
+ o = h[:order] || h['order']
108
+ m = h[:merger] || h['merger']
109
+
110
+ payloads = send("mom__#{o}", h, Flor.dup(@node['payloads']))
111
+ payload = send("mmm__#{m}", h, payloads)
112
+ msg = { 'payload' => payload }
113
+
114
+ receive_from_merger(msg)
115
+ end
116
+
117
+ def apply_merger_function(func_tree)
118
+
119
+ @node['merging'] = true
120
+
121
+ apply(func_tree, merger_args, tree[2])
122
+ end
123
+
124
+ def merger_args
125
+
126
+ rs = Flor.dup(@node['payloads'])
127
+
128
+ [ [ 'rets', rs.inject({}) { |h, (k, v)| h[k] = v['ret']; h } ],
129
+ [ 'replies', rs ],
130
+ [ 'branch_count', @node['branch_count'] ] ]
131
+ end
132
+
133
+ def receive_from_merger(msg=message)
134
+
135
+ pl = msg ? msg['payload'] : {}
136
+ ret = pl['ret']
137
+
138
+ pl = ret['payload'] \
139
+ if ret.is_a?(Hash) && ret.keys == %w[ done payload ]
140
+
141
+ # TODO somehow, what if done is false, should we un-over the concurrence?
142
+
143
+ @node['merged_payload'] = pl \
144
+ if msg && ! @node.has_key?('merged_payload')
145
+
146
+ rem = determine_remainder
147
+
148
+ cancel_children(rem) + reply_to_parent(rem)
149
+ end
150
+
151
+ def rm__default_receive
152
+
153
+ @node['payloads'].size >= branch_count
154
+ end
155
+
156
+ def rm__expect_integer_receive
157
+
158
+ @node['payloads'].size >= att(:expect)
159
+ end
160
+
161
+ def determine_receiver
162
+
163
+ ex = att(:expect)
164
+
165
+ return 'expect_integer_receive' if ex && ex.is_a?(Integer) && ex > 0
166
+
167
+ att(:on_receive, :receiver) || 'default_receive'
168
+ end
169
+
170
+ # order:
171
+ # * first, last: first or last to reply
172
+ # * top/north/head, bottom/south/tail: position in concurrence, in collection
173
+ #
174
+ # merge:
175
+ # * deep
176
+ # * mix/plain
177
+ # * override
178
+ # * ignore
179
+ # * stack
180
+
181
+ STACK_REX = /\Astack(?::([-_a-zA-Z0-9]+))?\z/
182
+
183
+ TRANSLATORS = { STACK_REX => 'k', /\Atail\z/ => 'a' }
184
+
185
+ MORDERS = {
186
+ 'f' => :first, 'l' => :last, /[tnh]/ => :north, /[bsa]/ => :south }
187
+ MMERGERS = {
188
+ 'd' => :deep, /[mp]/ => :mix, 'o' => :override, 'i' => :ignore,
189
+ 'k' => :stack }
190
+
191
+ def default_merger
192
+
193
+ { order: :first, merger: :deep }
194
+ end
195
+
196
+ def determine_merger
197
+
198
+ m = att(:on_merge, :merger, :merge)
199
+ h = default_merger
200
+
201
+ return h if m == nil
202
+ return m unless m.is_a?(String)
203
+
204
+ mm = m.split(/[-\s_]+/)
205
+ mm = mm[0].chars if mm.size == 1 && mm[0].size < 3
206
+ #
207
+ d = mm
208
+ .collect { |s| TRANSLATORS.inject(s) { |r, (k, v)| r.match(k) ? v : r } }
209
+ .collect { |s| s[0, 1] }.join
210
+
211
+ MORDERS.each do |rex, order|
212
+ if d.match(rex); h[:order] = order; break; end
213
+ end
214
+ MMERGERS.each do |rex, merger|
215
+ if d.match(rex); h[:merger] = merger; break; end
216
+ end
217
+
218
+ h[:key] = m.match(STACK_REX)[1] \
219
+ if h[:merger] == :stack
220
+
221
+ h
222
+ end
223
+
224
+ def mom__first(h, payloads)
225
+ mom__last(h, payloads).reverse
226
+ end
227
+ def mom__last(_, payloads)
228
+ payloads.values
229
+ end
230
+ def mom__north(h, payloads)
231
+ mom__south(h, payloads).reverse
232
+ end
233
+ def mom__south(_, payloads)
234
+ payloads.sort_by { |k, _| k }.collect(&:last)
235
+ end
236
+
237
+ def mmm__deep(_, ordered_payloads)
238
+ ordered_payloads.inject { |h, pl| Flor.deep_merge!(h, pl) }
239
+ end
240
+ def mmm__mix(_, ordered_payloads)
241
+ ordered_payloads.inject { |h, pl| h.merge!(pl) }
242
+ end
243
+ def mmm__override(_, ordered_payloads)
244
+ ordered_payloads.last
245
+ end
246
+ def mmm__ignore(_, _)
247
+ node_payload.copy
248
+ end
249
+ def mmm__stack(h, ordered_payloads)
250
+ k = h[:key] || 'ret'
251
+ node_payload.copy.merge!(k => ordered_payloads.reverse)
252
+ end
253
+
254
+ def determine_remainder
255
+
256
+ att(:remaining, :rem) || 'cancel'
257
+ end
258
+
259
+ def cancel_children(rem)
260
+
261
+ (rem && rem != 'forget') ? wrap_cancel_children : []
262
+ end
263
+
264
+ def reply_to_parent(rem)
265
+
266
+ return [] \
267
+ if @node['replied']
268
+ return [] \
269
+ if @node['payloads'].size < branch_count && ( ! rem || rem == 'wait')
270
+
271
+ @node['replied'] = true
272
+
273
+ wrap_reply('payload' => post_merge)
274
+ end
275
+
276
+ def post_merge
277
+
278
+ @node['merged_payload']
279
+ end
280
+ end
281
+
@@ -17,6 +17,7 @@ require 'flor/unit/waiter'
17
17
  require 'flor/unit/scheduler'
18
18
  require 'flor/unit/models'
19
19
  require 'flor/unit/loader'
20
+ require 'flor/unit/hloader'
20
21
  require 'flor/unit/ganger'
21
22
  require 'flor/unit/spooler'
22
23
  require 'flor/unit/taskers'
@@ -55,7 +55,12 @@ module Flor
55
55
  #
56
56
  # initialize
57
57
 
58
- k = Flor.const_lookup(conf['class'] || conf['module'])
58
+ k =
59
+ case com = conf['class'] || conf['module']
60
+ when String then Flor.const_lookup(com)
61
+ when Class then com
62
+ else fail ArgumentError.new("don't know how to call #{com.inspect}")
63
+ end
59
64
 
60
65
  o =
61
66
  if k.class == Module
@@ -16,12 +16,11 @@ module Flor
16
16
  unit.storage.fetch_traps(@exid),
17
17
  unit.storage.load_execution(@exid))
18
18
 
19
- @messages =
20
- messages.collect { |m|
19
+ @messages = messages
20
+ .collect { |m|
21
21
  Flor::Storage
22
22
  .from_blob(m[:content])
23
- .tap { |mm| mm['mid'] = m[:id].to_i }
24
- }
23
+ .tap { |mm| mm['mid'] = m[:id].to_i } }
25
24
  @consumed = []
26
25
 
27
26
  @alive = true
@@ -48,6 +47,11 @@ module Flor
48
47
 
49
48
  protected
50
49
 
50
+ CLOSING_POINTS = %w[ task terminated ceased ]
51
+ #
52
+ # point for messages that, after consumption, are conserved
53
+ # in the execution's "closing_messages" array
54
+
51
55
  def do_run
52
56
 
53
57
  @unit.logger.log_run_start(self)
@@ -84,6 +88,10 @@ module Flor
84
88
 
85
89
  @alive = false
86
90
 
91
+ @execution.merge!(
92
+ closing_messages: @consumed.select { |m|
93
+ CLOSING_POINTS.include?(m['point']) })
94
+
87
95
  @unit.storage.put_execution(@execution)
88
96
  @unit.storage.consume(@consumed)
89
97
 
@@ -170,6 +178,11 @@ module Flor
170
178
  [ m ]
171
179
  end
172
180
 
181
+ def add(message)
182
+
183
+ apply(@execution['nodes'][message['nid']], message)
184
+ end
185
+
173
186
  def on_do_run_exc(e)
174
187
 
175
188
  io = StringIO.new
@@ -62,7 +62,7 @@ module Flor
62
62
 
63
63
  message['vars'] = gather_vars(executor, tconf, message)
64
64
 
65
- m = Flor.dup(message)
65
+ m = dup_message(message)
66
66
  #
67
67
  # the tasker gets a copy of the message (and it can play with it
68
68
  # to its heart content), meanwhile the message is handed to the
@@ -81,6 +81,17 @@ module Flor
81
81
 
82
82
  protected
83
83
 
84
+ def dup_message(m)
85
+
86
+ tc = m.delete('tconf')
87
+ m1 = Flor.dup(m)
88
+ m1['tconf'] = tc.inject({}) { |h, (k, v)|
89
+ h[k] = k == 'class' ? v : Flor.dup(v); h } \
90
+ if tc
91
+
92
+ m1
93
+ end
94
+
84
95
  def var_match(k, filter)
85
96
 
86
97
  filter.each { |f| return true if (f.is_a?(String) ? k == f : f.match(k)) }
@@ -0,0 +1,251 @@
1
+
2
+ module Flor
3
+
4
+ # A loader which keeps everything in a Hash, while the traditional/default
5
+ # Flor::Loader reads from a file tree.
6
+ #
7
+ class HashLoader
8
+
9
+ # NB: tasker configuration entries start with "loa_", like for Flor::Loader
10
+
11
+ attr_reader :environment
12
+
13
+ def environment=(h)
14
+
15
+ @environment = Flor.to_string_keyed_hash(h)
16
+ .inject({}) { |r, (cat, v)|
17
+ r[cat] = recompose(v)
18
+ r }
19
+ end
20
+
21
+ def initialize(unit)
22
+
23
+ @unit = unit
24
+
25
+ class << @unit; include Flor::HashLoader::UnitAdders; end
26
+
27
+ self.environment = (@unit.conf['lod_environment'] || {})
28
+ end
29
+
30
+ def shutdown
31
+ end
32
+
33
+ HCATS = {
34
+ 'v' => 'variables',
35
+ 'var' => 'variables',
36
+ 'variable' => 'variables',
37
+ 'flo' => 'libraries',
38
+ 'flow' => 'libraries',
39
+ 'lib' => 'libraries',
40
+ 'library' => 'libraries',
41
+ 'sub' => 'sublibraries',
42
+ 'sublib' => 'sublibraries',
43
+ 'sublibrary' => 'sublibraries',
44
+ 'tasker' => 'taskers',
45
+ 'hook' => 'hooks' }
46
+ CATS =
47
+ HCATS.values.uniq
48
+
49
+ def add(cat, path, value, &block)
50
+
51
+ c = recat(cat)
52
+
53
+ path = path.to_s
54
+ path = path + '.' if c == 'hooks' && path.length > 0 && path[-1, 1] != '.'
55
+
56
+ value = block ?
57
+ block_to_class(c, block) :
58
+ Flor.to_string_keyed_hash(value)
59
+
60
+ e = (@environment[c] ||= [])
61
+ e << [ *split(path), value ]
62
+ e.sort_by! { |pa, _, _| pa.count('.') }
63
+ end
64
+
65
+ def remove(cat, path)
66
+
67
+ c = recat(cat)
68
+
69
+ e = @environment[c]
70
+ return [] unless e
71
+
72
+ pa, ke = split(path)
73
+
74
+ e.reject! { |epa, eke, _| epa == pa && eke == ke }
75
+ end
76
+
77
+ def variables(domain)
78
+
79
+ entries('variables', domain)
80
+ .inject({}) { |h, (_, k, v)| h[k] = v; h }
81
+ end
82
+
83
+ #def procedures(path)
84
+ #
85
+ # # TODO
86
+ # # TODO work with Flor.load_procedures
87
+ #end
88
+
89
+ # If found, returns [ source_path, path ]
90
+ #
91
+ def library(domain, name=nil, opts={})
92
+
93
+ path, key = split(domain, name)
94
+
95
+ libs = entries('libraries', path)
96
+ if opts[:subflows] # used by "graft"/"import"
97
+ libs += entries('sublibraries', path)
98
+ libs = libs.sort_by { |pa, _, _| pa.count('.') }
99
+ end
100
+
101
+ libs
102
+ .each { |pa, ke, va|
103
+ next unless ke == key
104
+ return [ [ pa, ke ].join('.'), va ] }
105
+
106
+ nil
107
+ end
108
+
109
+ def tasker(domain, name, message={})
110
+
111
+ path, key = split(domain, name)
112
+
113
+ entries('taskers', path)
114
+ .reverse # FIXME
115
+ .each { |pa, ke, va|
116
+ next unless ke == key
117
+ return to_conf_h('taskers', pa, va, message) }
118
+
119
+ nil
120
+ end
121
+
122
+ def hooks(domain)
123
+
124
+ entries('hooks', domain)
125
+ .collect { |_, _, va| to_conf_h('hooks', domain, va, {}) }
126
+ .flatten(1)
127
+ end
128
+
129
+ def load_hooks(exid)
130
+
131
+ hooks(Flor.domain(exid))
132
+ .collect { |h| Flor::Hook.new(@unit, exid, h) }
133
+ end
134
+
135
+ protected
136
+
137
+ def recompose(h)
138
+
139
+ deflate(h, {}, nil)
140
+ .sort_by { |k, _| k.count('.') }
141
+ .collect { |k, v| [ *split(k), v ] }
142
+ end
143
+
144
+ def deflate(h, out, path)
145
+
146
+ h
147
+ .inject(out) { |r, (k, v)|
148
+ pathk = path ? [ path, k ].join('.') : k
149
+ if v.is_a?(Hash)
150
+ deflate(v, r, pathk)
151
+ else
152
+ r[pathk] = v
153
+ end
154
+ r }
155
+ end
156
+
157
+ def recat(cat)
158
+
159
+ c = cat.to_s
160
+ c = HCATS[c] || c
161
+
162
+ fail ArgumentError.new("unknown category #{cat.to_s.inspect}") \
163
+ unless CATS.include?(c)
164
+
165
+ c
166
+ end
167
+
168
+ def split(path, key=nil)
169
+
170
+ return [ path, key ] if key
171
+
172
+ i = path.rindex('.') || 0
173
+
174
+ [ path[0, i], path[(i == 0 ? i : i + 1)..-1] ]
175
+ end
176
+
177
+ def entries(cat, domain)
178
+
179
+ (@environment[cat.to_s] || [])
180
+ .select { |path, _, _| Flor.sub_domain?(path, domain) }
181
+ end
182
+
183
+ def to_conf_h(cat, path, value, context)
184
+
185
+ is_array = value.is_a?(Array)
186
+
187
+ a = (is_array ? value : [ value ])
188
+ .collect { |v|
189
+ h =
190
+ case v
191
+ when String then eval(path, v, context)
192
+ when Class then { 'class' => v }
193
+ when Hash then v
194
+ else { 'instance' => v }
195
+ end
196
+ h['_path'] = path
197
+ h['root'] = nil
198
+ h }
199
+
200
+ is_array ? a : a.first
201
+ end
202
+
203
+ def eval(path, code, context)
204
+
205
+ code = Flor::ConfExecutor.load(code)
206
+
207
+ Flor::ConfExecutor.interpret(path, code, context)
208
+ end
209
+
210
+ def block_to_class(cat, block)
211
+
212
+ c =
213
+ cat == 'taskers' ?
214
+ Class.new(Flor::BasicTasker) :
215
+ Class.new
216
+
217
+ class << c; attr_accessor :source_location; end
218
+ c.source_location = block.source_location
219
+ c.send(:define_method, :on_message, &block)
220
+
221
+ c
222
+ end
223
+
224
+ module UnitAdders
225
+
226
+ def add_variable(path, value)
227
+ self.loader.add(:variable, path, value)
228
+ end
229
+ def add_library(path, value)
230
+ self.loader.add(:library, path, value)
231
+ end
232
+ def add_sublibrary(path, value)
233
+ self.loader.add(:sublibrary, path, value)
234
+ end
235
+ def add_tasker(path, value=nil, &block)
236
+ self.loader.add(:tasker, path, value, &block)
237
+ end
238
+ def add_hook(path, value=nil, &block)
239
+ self.loader.add(:hook, path, value, &block)
240
+ end
241
+
242
+ alias add_var add_variable
243
+ alias add_lib add_library
244
+ alias add_sub add_sublibrary
245
+ alias add_sub_lib add_sublibrary
246
+
247
+ # to remove: unit.loader.remove(:tasker, path)
248
+ end
249
+ end
250
+ end
251
+