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,473 @@
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
+ module Flor
27
+
28
+ class Executor
29
+
30
+ attr_reader :unit
31
+ attr_reader :execution
32
+ attr_reader :traps
33
+
34
+ def initialize(unit, traps, execution)
35
+
36
+ @unit = unit
37
+ @execution = execution
38
+ @traps = traps
39
+ end
40
+
41
+ def conf; @unit.conf; end
42
+ def exid; @execution['exid']; end
43
+
44
+ def node(msg_or_nid, node_instance=false)
45
+
46
+ return nil unless msg_or_nid
47
+
48
+ nid = msg_or_nid
49
+ msg = msg_or_nid
50
+ #
51
+ if nid.is_a?(String)
52
+ msg = nil
53
+ else
54
+ nid = msg['nid']
55
+ end
56
+
57
+ n = @execution['nodes'][nid]
58
+
59
+ return nil unless n
60
+ node_instance ? Flor::Node.new(self, n, msg) : n
61
+ end
62
+
63
+ def counter(key)
64
+
65
+ @execution['counters'][key.to_s] || -1
66
+ end
67
+
68
+ def counter_add(key, count)
69
+
70
+ k = key.to_s
71
+
72
+ @execution['counters'][k] ||= 0
73
+ @execution['counters'][k] += count
74
+ end
75
+
76
+ def counter_next(key)
77
+
78
+ counter_add(key, 1)
79
+ end
80
+
81
+ def trigger_hook(hook, message)
82
+
83
+ hook.notify(self, message)
84
+ end
85
+
86
+ def trigger_trap(trap, message)
87
+
88
+ del, msgs = trap.trigger(self, message)
89
+ @traps.delete(trap) if del
90
+
91
+ msgs
92
+ end
93
+
94
+ # Given a nid, returns a copy of all the var the node sees
95
+ #
96
+ def vars(nid, vs={})
97
+
98
+ n = node(nid); return vs unless n
99
+
100
+ (n['vars'] || {})
101
+ .each { |k, v| vs[k] = Flor.dup(v) unless vs.has_key?(k) }
102
+
103
+ if @unit.loader && n['parent'] == nil && n['vdomain'] != false
104
+
105
+ @unit.loader.variables(n['vdomain'] || Flor.domain(@exid))
106
+ .each { |k, v| vs[k] = Flor.dup(v) unless vs.has_key?(k) }
107
+ end
108
+
109
+ if cn = n['cnid']; vars(cn, vs); end
110
+ if pa = n['parent']; vars(pa, vs); end
111
+
112
+ vs
113
+ end
114
+
115
+ protected
116
+
117
+ def make_node(message)
118
+
119
+ nid = message['nid']
120
+
121
+ now = Flor.tstamp
122
+
123
+ node = {
124
+ 'nid' => nid,
125
+ 'parent' => message['from'],
126
+ 'payload' => message['payload'],
127
+ 'status' => [ { 'status' => nil, 'point' => 'execute', 'ctime' => now } ],
128
+ 'ctime' => now,
129
+ 'mtime' => now }
130
+
131
+ %w[ vars vdomain cnid noreply dbg ].each do |k|
132
+ v = message[k]
133
+ node[k] = v if v != nil
134
+ end
135
+ #
136
+ # vars: variables
137
+ # vdomain: variable domain (used in conjuction with the loader)
138
+ # cnid: closure nid
139
+ # noreply: this new node has a parent but shouldn't reply to it
140
+ # dbg: used to debug messages (useful @node['dbg'] when 'receive')
141
+
142
+ @execution['nodes'][nid] = node
143
+ end
144
+
145
+ def determine_heat(message)
146
+
147
+ nid = message['nid']
148
+
149
+ return unless nid
150
+
151
+ node =
152
+ message['point'] == 'execute' ?
153
+ make_node(message) :
154
+ @execution['nodes'][nid]
155
+
156
+ return unless node
157
+
158
+ return if node['heat']
159
+
160
+ n = Flor::Node.new(self, node, message)
161
+
162
+ mt = message['tree']
163
+ mt = [ 'noeval', [ 0 ], mt[2] ] \
164
+ if mt[0] == '_' && Flor.is_array_of_trees?(mt[1])
165
+
166
+ nt = n.lookup_tree(nid)
167
+
168
+ node['tree'] = mt if mt && (mt != nt)
169
+ tree = node['tree'] || nt
170
+
171
+ t0 = tree[0]
172
+ t0 = (t0.is_a?(Array) && t0[0] == '_dqs') ? n.expand(t0[1]) : t0
173
+
174
+ node['heat0'] = tree[0]
175
+ node['heat'] = heat = n.deref(t0)
176
+
177
+ node['heap'] = heap =
178
+ if ! heat.is_a?(Array)
179
+ '_val'
180
+ elsif tree && tree[1] == []
181
+ '_val'
182
+ elsif heat[0] == '_proc'
183
+ heat[1]['proc']
184
+ elsif heat[0] == '_func'
185
+ 'apply'
186
+ elsif heat[0] == '_task'
187
+ 'task'
188
+ elsif Flor.is_tree_head_tree?(tree)
189
+ '_happly'
190
+ else
191
+ '_val'
192
+ end
193
+
194
+ if heap == 'task' && heat[0] == '_task'
195
+ #
196
+ # rewrite `alpha` into `task alpha`
197
+
198
+ l = message['tree'][2]
199
+
200
+ message['otree'] = Flor.dup(message['tree'])
201
+
202
+ message['tree'][0] =
203
+ 'task'
204
+ message['tree'][1].unshift(
205
+ [ '_att', [ [ '_sqs', heat[1]['task'], l ] ], l ])
206
+ end
207
+ end
208
+
209
+ def execute(message)
210
+
211
+ apply(@execution['nodes'][message['nid']], message)
212
+ end
213
+
214
+ def apply(node, message)
215
+
216
+ heap =
217
+ if node['heat']
218
+ node['heap']
219
+ else
220
+ node['failure'] ? '_err' : nil
221
+ end
222
+
223
+ return ([{
224
+ 'point' => 'receive',
225
+ 'nid' => message['from'], 'from' => message['nid'],
226
+ 'exid' => message['exid'],
227
+ 'payload' => Flor.dupm(message['payload'], 'ret' => node['heat0'])
228
+ }]) if heap == nil && message['accept_symbol'] == true
229
+
230
+ return error_reply(
231
+ node, message, "don't know how to apply #{node['heat0'].inspect}"
232
+ ) if heap == nil
233
+
234
+ heac = Flor::Procedure[heap]
235
+ fail NameError.new("unknown procedure #{heap.inspect}") unless heac
236
+
237
+ head = heac.new(self, node, message)
238
+
239
+ #return process(head.rewrite) if head.is_a?(Flor::Macro)
240
+ return [ head.rewrite ] if head.is_a?(Flor::Macro)
241
+
242
+ nid = message['nid']
243
+ pt = message['point']
244
+ pt = "do_#{pt}" if pt == 'receive' || pt == 'cancel'
245
+
246
+ if pt == 'execute'
247
+ head.pre_execute
248
+ pnode = @execution['nodes'][node['parent']]
249
+ cnodes = pnode && (pnode['cnodes'] ||= [])
250
+ cnodes << nid if cnodes && ( ! cnodes.include?(nid))
251
+ end
252
+ head.send(pt)
253
+ end
254
+
255
+ def remove_node(n)
256
+
257
+ return unless n
258
+
259
+ n['removed'] = true # or should I use "status" => "removed" ?
260
+
261
+ @unit.remove_node(exid, n)
262
+ # remove timers/waiters for this node, if any
263
+
264
+ return if (n['closures'] || []).any?
265
+ # don't remove the node if it's a closure for some other nodes
266
+
267
+ nid = n['nid']
268
+
269
+ return if nid == '0'
270
+ # don't remove if it's the "root" node
271
+
272
+ @execution['nodes'].delete(nid)
273
+ end
274
+
275
+ def leave(node, message)
276
+
277
+ ts = node && node['tags']
278
+ return [] unless ts && ts.any?
279
+
280
+ [
281
+ { 'point' => 'left',
282
+ 'tags' => ts,
283
+ 'exid' => exid,
284
+ 'nid' => node['nid'],
285
+ 'payload' => message['payload'] }
286
+ ]
287
+ end
288
+
289
+ # "receive_terminated_or_ceased"
290
+ #
291
+ def receive_toc(message, fnode)
292
+
293
+ msg =
294
+ %w[
295
+ exid nid from payload
296
+ ].inject({}) { |h, k| h[k] = message[k] if message.has_key?(k); h }
297
+
298
+ msg['sm'] = message['m']
299
+
300
+ msg['point'] =
301
+ if message['from'] == '0' || @execution['nodes'].empty? # termination?
302
+ 'terminated'
303
+ else
304
+ 'ceased'
305
+ end
306
+
307
+ [ msg ]
308
+ end
309
+
310
+ def receive(message)
311
+
312
+ from = message['from']
313
+ fnode = @execution['nodes'][from]
314
+
315
+ if fnode && fnode.has_key?('aret')
316
+ message['payload']['ret'] = fnode['aret']
317
+ end
318
+
319
+ remove_node(fnode)
320
+ messages = leave(fnode, message)
321
+
322
+ nid = message['nid']
323
+ nid = nil if fnode && fnode['noreply']
324
+
325
+ return messages + receive_toc(message, fnode) unless nid
326
+
327
+ node = @execution['nodes'][nid]
328
+
329
+ return messages unless node
330
+
331
+ messages + apply(node, message)
332
+ end
333
+
334
+ def error_reply(node, message, err)
335
+
336
+ m = message
337
+ .select { |k, v| %w[ sm exid nid from payload tree ].include?(k) }
338
+
339
+ m['point'] = 'failed'
340
+ m['fpoint'] = message['point']
341
+ m['error'] = Flor.to_error(err)
342
+
343
+ Flor.detail_msg(self, m, flag: true) if @unit.conf['log_err']
344
+
345
+ #if m['error']['msg'].match(/\AToo many open files in system/)
346
+ # puts "=" * 80 + ' ...'
347
+ # system(`lsof #{Process.pid}`)
348
+ # puts "=" * 80 + ' .'
349
+ #end
350
+ #
351
+ # can't seem to provoke that error, so keeping the trap
352
+ # around but commented out...
353
+
354
+ [ m ]
355
+ end
356
+
357
+ def task(message)
358
+
359
+ @execution['tasks'][message['nid']] =
360
+ { 'tasker' => message['tasker'], 'name' => message['taskname'] }
361
+
362
+ @unit.tasker.task(message)
363
+ end
364
+ alias detask task
365
+
366
+ def return(message)
367
+
368
+ @execution['tasks'].delete(message['nid'])
369
+
370
+ [
371
+ { 'point' => 'receive',
372
+ 'exid' => message['exid'],
373
+ 'nid' => message['nid'],
374
+ 'payload' => message['payload'],
375
+ 'tasker' => message['tasker'] }
376
+ ]
377
+ end
378
+
379
+ def cancel(message)
380
+
381
+ if n = @execution['nodes'][message['nid']]
382
+ apply(n, message)
383
+ else
384
+ [] # nothing, node gone
385
+ end
386
+ end
387
+
388
+ def process(message)
389
+
390
+ begin
391
+
392
+ message['m'] = counter_next('msgs') # number messages
393
+ message['pr'] = counter('runs') # "processing run"
394
+
395
+ determine_heat(message)
396
+
397
+ ms = []
398
+ ms += @unit.notify(self, message) # pre
399
+
400
+ ms += self.send(message['point'].to_sym, message)
401
+
402
+ message['payload'] = message.delete('pld') if message.has_key?('pld')
403
+ message['consumed'] = Flor.tstamp
404
+
405
+ ms += @unit.notify(self, message) # post
406
+
407
+ ms.each { |m| m['er'] = counter('runs') } # "emitting run"
408
+
409
+ rescue => e
410
+ error_reply(nil, message, e)
411
+ rescue ScriptError => se
412
+ error_reply(nil, message, se)
413
+ end
414
+ end
415
+
416
+ def trap(message)
417
+
418
+ exid = message['exid']
419
+ nid = message['nid']
420
+ trap = message['trap']
421
+
422
+ nd = node(nid)
423
+ nd['exid'] = exid
424
+
425
+ @traps << @unit.trap(nd, trap)
426
+
427
+ []
428
+ end
429
+
430
+ def entered(message); []; end
431
+ def left(message); []; end
432
+
433
+ def ceased(message); []; end
434
+
435
+ def terminated(message)
436
+
437
+ message['vars'] = @execution['nodes']['0']['vars']
438
+ # especially useful for debugging
439
+
440
+ []
441
+ end
442
+
443
+
444
+ def failed(message)
445
+
446
+ n = node(message['nid'])
447
+
448
+ fail RuntimeError.new(
449
+ "node #{message['nid']} is gone, cannot flag it as failed"
450
+ ) unless n
451
+
452
+ #begin
453
+ n['failure'] = Flor.dup(message)
454
+ #rescue; pp message; exit 0; end
455
+
456
+ oep = lookup_on_error_parent(message)
457
+ return oep.trigger_on_error if oep
458
+
459
+ Flor.detail_msg(self, message) if @unit.conf['log_err']
460
+
461
+ []
462
+ end
463
+
464
+ def signal(message); []; end
465
+
466
+ def lookup_on_error_parent(message)
467
+
468
+ nd = Flor::Node.new(self, nil, message).on_error_parent
469
+ nd ? nd.to_procedure : nil
470
+ end
471
+ end
472
+ end
473
+