flor 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +9 -0
  3. data/CREDITS.md +21 -0
  4. data/LICENSE.txt +4 -1
  5. data/Makefile +4 -0
  6. data/README.md +8 -0
  7. data/flor.gemspec +10 -10
  8. data/lib/flor.rb +2 -2
  9. data/lib/flor/changes.rb +3 -3
  10. data/lib/flor/colours.rb +14 -8
  11. data/lib/flor/conf.rb +63 -58
  12. data/lib/flor/core.rb +4 -4
  13. data/lib/flor/core/executor.rb +65 -29
  14. data/lib/flor/core/node.rb +37 -20
  15. data/lib/flor/core/procedure.rb +182 -40
  16. data/lib/flor/core/texecutor.rb +125 -52
  17. data/lib/flor/djan.rb +111 -82
  18. data/lib/flor/dollar.rb +31 -30
  19. data/lib/flor/flor.rb +314 -237
  20. data/lib/flor/id.rb +7 -2
  21. data/lib/flor/log.rb +250 -245
  22. data/lib/flor/parser.rb +72 -38
  23. data/lib/flor/pcore/_arr.rb +10 -10
  24. data/lib/flor/pcore/_att.rb +49 -14
  25. data/lib/flor/pcore/_coll.rb +18 -0
  26. data/lib/flor/pcore/_obj.rb +23 -7
  27. data/lib/flor/pcore/_pat_.rb +1 -1
  28. data/lib/flor/pcore/_pat_guard.rb +8 -0
  29. data/lib/flor/pcore/_pat_obj.rb +3 -3
  30. data/lib/flor/pcore/_pat_or.rb +4 -0
  31. data/lib/flor/pcore/_pat_regex.rb +24 -0
  32. data/lib/flor/pcore/_skip.rb +4 -0
  33. data/lib/flor/pcore/_val.rb +0 -1
  34. data/lib/flor/pcore/all.rb +111 -0
  35. data/lib/flor/pcore/any.rb +83 -0
  36. data/lib/flor/pcore/arith.rb +35 -6
  37. data/lib/flor/pcore/break.rb +39 -1
  38. data/lib/flor/pcore/case.rb +82 -4
  39. data/lib/flor/pcore/cmp.rb +7 -7
  40. data/lib/flor/pcore/collect.rb +50 -0
  41. data/lib/flor/pcore/cond.rb +17 -3
  42. data/lib/flor/pcore/cursor.rb +8 -2
  43. data/lib/flor/pcore/detect.rb +45 -0
  44. data/lib/flor/pcore/each.rb +52 -0
  45. data/lib/flor/pcore/empty.rb +60 -0
  46. data/lib/flor/pcore/filter.rb +94 -0
  47. data/lib/flor/pcore/find.rb +67 -0
  48. data/lib/flor/pcore/for_each.rb +65 -0
  49. data/lib/flor/pcore/includes.rb +32 -0
  50. data/lib/flor/pcore/inject.rb +55 -0
  51. data/lib/flor/pcore/iterator.rb +151 -0
  52. data/lib/flor/pcore/keys.rb +60 -0
  53. data/lib/flor/pcore/length.rb +34 -7
  54. data/lib/flor/pcore/logo.rb +18 -0
  55. data/lib/flor/pcore/loop.rb +4 -0
  56. data/lib/flor/pcore/map.rb +77 -46
  57. data/lib/flor/pcore/match.rb +8 -2
  58. data/lib/flor/pcore/matchr.rb +4 -5
  59. data/lib/flor/pcore/move.rb +3 -3
  60. data/lib/flor/pcore/noeval.rb +13 -0
  61. data/lib/flor/pcore/not.rb +16 -0
  62. data/lib/flor/pcore/on.rb +172 -0
  63. data/lib/flor/pcore/on_cancel.rb +54 -0
  64. data/lib/flor/pcore/on_error.rb +68 -0
  65. data/lib/flor/pcore/rand.rb +2 -2
  66. data/lib/flor/pcore/range.rb +2 -1
  67. data/lib/flor/pcore/reduce.rb +124 -0
  68. data/lib/flor/pcore/reverse.rb +46 -0
  69. data/lib/flor/pcore/select.rb +72 -0
  70. data/lib/flor/pcore/set.rb +8 -0
  71. data/lib/flor/pcore/stall.rb +10 -0
  72. data/lib/flor/pcore/to_array.rb +61 -0
  73. data/lib/flor/pcore/until.rb +34 -0
  74. data/lib/flor/punit/cancel.rb +30 -5
  75. data/lib/flor/punit/ccollect.rb +11 -0
  76. data/lib/flor/punit/cmap.rb +10 -5
  77. data/lib/flor/punit/concurrence.rb +42 -51
  78. data/lib/flor/punit/cron.rb +33 -0
  79. data/lib/flor/punit/do_trap.rb +42 -0
  80. data/lib/flor/punit/every.rb +48 -13
  81. data/lib/flor/punit/graft.rb +3 -3
  82. data/lib/flor/punit/on_timeout.rb +38 -0
  83. data/lib/flor/punit/schedule.rb +69 -6
  84. data/lib/flor/punit/signal.rb +54 -0
  85. data/lib/flor/punit/sleep.rb +1 -1
  86. data/lib/flor/punit/task.rb +4 -1
  87. data/lib/flor/punit/trap.rb +188 -13
  88. data/lib/flor/tools/shell.rb +408 -62
  89. data/lib/flor/tools/shell_out.rb +31 -0
  90. data/lib/flor/unit.rb +1 -1
  91. data/lib/flor/unit/caller.rb +177 -0
  92. data/lib/flor/unit/executor.rb +1 -0
  93. data/lib/flor/unit/ganger.rb +15 -21
  94. data/lib/flor/unit/hook.rb +1 -1
  95. data/lib/flor/unit/hooker.rb +22 -10
  96. data/lib/flor/unit/loader.rb +22 -22
  97. data/lib/flor/unit/logger.rb +63 -36
  98. data/lib/flor/unit/models.rb +6 -1
  99. data/lib/flor/unit/models/execution.rb +12 -1
  100. data/lib/flor/unit/models/message.rb +7 -0
  101. data/lib/flor/unit/models/trap.rb +31 -17
  102. data/lib/flor/unit/scheduler.rb +18 -10
  103. data/lib/flor/unit/storage.rb +83 -23
  104. data/lib/flor/unit/waiter.rb +1 -2
  105. metadata +96 -52
  106. data/lib/flor/deep.rb +0 -144
  107. data/lib/flor/punit/on.rb +0 -57
  108. data/lib/flor/unit/runner.rb +0 -84
  109. data/match.md +0 -22
@@ -9,6 +9,27 @@ class Flor::Pro::Cancel < Flor::Procedure
9
9
  # sequence
10
10
  # cancel ref: 'blue'
11
11
  # ```
12
+ # You can drop the `ref:`
13
+ # ```
14
+ # concurrence
15
+ # sequence tag: 'blue'
16
+ # sequence
17
+ # cancel 'blue'
18
+ # ```
19
+ #
20
+ # It's also OK to use nids directly:
21
+ # ```
22
+ # concurrence # 0
23
+ # sequence # 0_0
24
+ # # ...
25
+ # sequence # 0_1
26
+ # cancel nid: '0_0'
27
+ # # or
28
+ # #cancel '0_0'
29
+ # ```
30
+ # But it's kind of brittle compared to using tags.
31
+ #
32
+ # # TODO document "kill"
12
33
 
13
34
  name 'cancel', 'kill'
14
35
  # ruote had "undo" as well...
@@ -26,19 +47,23 @@ class Flor::Pro::Cancel < Flor::Procedure
26
47
  .inject([]) { |a, (k, v)|
27
48
  v = Array(v)
28
49
  a.concat(v) if v.all? { |e| e.is_a?(String) }
29
- a
30
- } +
50
+ a } +
31
51
  att_a('nid') +
32
52
  att_a('ref')
33
53
 
34
54
  nids, tags = targets.partition { |t| Flor.is_nid?(t) }
35
-
36
55
  nids += tags_to_nids(tags)
56
+ nids = nids.uniq
37
57
 
38
58
  fla = @node['heap']
39
59
 
40
- nids.uniq.map { |nid| wrap_cancel('nid' => nid, 'flavour' => fla)[0] } +
41
- wrap_reply
60
+ messages = nids
61
+ .collect { |nid| wrap_cancel('nid' => nid, 'flavour' => fla)[0] }
62
+
63
+ messages = messages + wrap_reply \
64
+ unless nids.find { |nid| is_ancestor_node?(nid) }
65
+
66
+ messages
42
67
  end
43
68
  end
44
69
 
@@ -0,0 +1,11 @@
1
+
2
+ class Flor::Pro::Ccollect < Flor::Macro::Iterator
3
+
4
+ name 'ccollect'
5
+
6
+ def rewrite_tree
7
+
8
+ rewrite_iterator_tree('cmap')
9
+ end
10
+ end
11
+
@@ -26,20 +26,25 @@ class Flor::Pro::Cmap < Flor::Procedure
26
26
 
27
27
  fun = payload['ret']
28
28
 
29
- fail ArgumentError.new(
30
- "cmap expects a function"
31
- ) unless Flor.is_func_tree?(fun)
29
+ fail Flor::FlorError.new("'#{tree[0]}' expects a function", self) \
30
+ unless Flor.is_func_tree?(fun)
32
31
 
33
32
  @node['fun'] = fun
34
33
 
35
34
  att(nil)
36
- .collect.with_index { |e, i| apply(fun, [ e, i ], tree[2]) }
35
+ .collect
36
+ .with_index { |e, i|
37
+ apply(fun, [ e, i ], tree[2], vars: { 'idx' => i }) }
37
38
  .flatten(1)
38
39
  end
39
40
 
40
41
  def receive_elt
41
42
 
42
- @node['col'] << payload['ret']
43
+ idx =
44
+ (message['rvars'] && message['rvars']['idx']) ||
45
+ Flor.sub_nid(message['from']) - 1 # fall back :-(
46
+
47
+ @node['col'][idx] = payload['ret']
43
48
 
44
49
  return [] if cnodes_any?
45
50
 
@@ -47,12 +47,25 @@ class Flor::Pro::Concurrence < Flor::Procedure
47
47
  # being already gone).
48
48
  #
49
49
  # `remaining:` may be shortened to `rem:`.
50
+ #
50
51
  # ```
51
52
  # concurrence expect: 1 rem: 'forget'
53
+ # #
54
+ # # will forget child 'alpha' as soon as child 'bravo' replies,
55
+ # # and vice versa.
56
+ # #
57
+ # task 'alpha'
58
+ # task 'bravo'
59
+ # ```
60
+ #
61
+ # ```
62
+ # concurrence expect: 1 rem: 'wait'
63
+ # #
64
+ # # if 'alpha' replies before 'bravo', the concurrence will wait for
65
+ # # 'bravo', without cancelling it. And vice versa.
66
+ # #
52
67
  # task 'alpha'
53
68
  # task 'bravo'
54
- # # will forget child 'alpha' as soon as child 'bravo' replies,
55
- # # and vice versa.
56
69
  # ```
57
70
 
58
71
  name 'concurrence'
@@ -60,12 +73,16 @@ class Flor::Pro::Concurrence < Flor::Procedure
60
73
  def pre_execute
61
74
 
62
75
  @node['atts'] = []
76
+ @node['payloads'] = {}
63
77
  end
64
78
 
65
79
  def receive_last_att
66
80
 
67
81
  return wrap_reply unless children[@ncid]
68
82
 
83
+ @node['receiver'] = determine_receiver
84
+ @node['merger'] = determine_merger
85
+
69
86
  (@ncid..children.size - 1)
70
87
  .map { |i| execute_child(i, 0, 'payload' => payload.copy_current) }
71
88
  .flatten(1)
@@ -75,23 +92,32 @@ class Flor::Pro::Concurrence < Flor::Procedure
75
92
 
76
93
  def receive_non_att
77
94
 
78
- @node['receiver'] ||= determine_receiver
79
- @node['merger'] ||= determine_merger
95
+ @node['payloads'][@message['from']] = @message['payload']
80
96
 
81
- return [] if @node['over']
97
+ over = @node['over'] || invoke_receiver
98
+ just_over = over && ! @node['over']
99
+ @node['over'] ||= just_over
82
100
 
83
- over = invoke_receiver
84
- # true: the concurrence is over, false: the concurrence is still waiting
101
+ @node['merged_payload'] ||= just_over ? invoke_merger : nil
102
+ rem = just_over ? (att(:remaining, :rem) || 'cancel') : nil
85
103
 
86
- return [] unless over
104
+ cancel_children(rem) + reply_to_parent(rem)
105
+ end
87
106
 
88
- @node['over'] = true
107
+ def cancel_children(rem)
89
108
 
90
- pld = invoke_merger
91
- # determine post-concurrence payload
109
+ (rem && rem != 'forget') ? wrap_cancel_children : []
110
+ end
111
+
112
+ def reply_to_parent(rem)
92
113
 
93
- cancel_remaining +
94
- wrap_reply('payload' => pld)
114
+ return [] \
115
+ if @node['replied']
116
+ return [] \
117
+ if @node['payloads'].size < non_att_count && ( ! rem || rem == 'wait')
118
+
119
+ @node['replied'] = true
120
+ wrap_reply('payload' => @node['merged_payload'])
95
121
  end
96
122
 
97
123
  def receive_from_child_when_closed
@@ -119,51 +145,16 @@ class Flor::Pro::Concurrence < Flor::Procedure
119
145
  'default_merge'
120
146
  end
121
147
 
122
- def cancel_remaining
123
-
124
- # remaining:
125
- # * 'cancel' (default)
126
- # * 'forget'
127
- # * 'wait'
128
-
129
- rem = att(:remaining, :rem)
130
-
131
- return [] if rem == 'forget'
132
-
133
- wrap_cancel_children
134
- end
135
-
136
- def invoke_receiver
137
-
138
- # TODO: receiver function case
139
-
140
- self.send(@node['receiver'])
141
- end
142
-
143
- def invoke_merger
144
-
145
- # TODO: merger function case
146
-
147
- self.send(@node['merger'])
148
- end
149
-
150
- def store_payload
151
-
152
- (@node['payloads'] ||= {})[@message['from']] =
153
- @message['payload']
154
- end
148
+ def invoke_receiver; self.send(@node['receiver']); end
149
+ def invoke_merger; self.send(@node['merger']); end
155
150
 
156
151
  def default_receive
157
152
 
158
- store_payload
159
-
160
- @node['payloads'].size >= non_att_children.size
153
+ @node['payloads'].size >= non_att_count
161
154
  end
162
155
 
163
156
  def expect_integer_receive
164
157
 
165
- store_payload
166
-
167
158
  @node['payloads'].size >= att(:expect)
168
159
  end
169
160
 
@@ -0,0 +1,33 @@
1
+
2
+ require 'flor/punit/schedule'
3
+
4
+
5
+ class Flor::Pro::Cron < Flor::Macro::Schedule
6
+ #
7
+ # "cron" is a macro procedure.
8
+ #
9
+ # ```
10
+ # cron '0 0 1 jan *'
11
+ # task albert 'take out garbage'
12
+ # ```
13
+ #
14
+ # is automatically turned into:
15
+ #
16
+ # ```
17
+ # schedule cron: '0 0 1 jan *'
18
+ # def msg
19
+ # task albert 'take out garbage'
20
+ # ```
21
+ #
22
+ # ## see also
23
+ #
24
+ # Schedule, and every.
25
+
26
+ name 'cron'
27
+
28
+ def rewrite_tree
29
+
30
+ rewrite_schedule_tree('cron')
31
+ end
32
+ end
33
+
@@ -0,0 +1,42 @@
1
+
2
+ class Flor::Pro::DoTrap < Flor::Macro
3
+ #
4
+ # A version of trap that accepts a block instead of a function.
5
+ #
6
+ # do-trap accepts the same attributes as [trap][trap.md] does.
7
+ #
8
+ # ```
9
+ # sequence
10
+ # do-trap 'terminated'
11
+ # trace "terminated(f:$(msg.from))"
12
+ # trace "here($(nid))"
13
+ # ```
14
+ # which traces to:
15
+ # ```
16
+ # here(0_1_0_0)
17
+ # terminated(f:0)
18
+ # ```
19
+ #
20
+ # ## see also
21
+ #
22
+ # Trap, on, signal.
23
+
24
+ name 'do-trap'
25
+
26
+ def rewrite_tree
27
+
28
+ l = tree[2]
29
+
30
+ th = [ 'trap', [], l, *tree[3] ]
31
+ att_children.each { |ac| th[1] << Flor.dup(ac) }
32
+
33
+ td = [ 'def', [], l ]
34
+ td[1] << [ '_att', [ [ 'msg', [], l ] ], l ]
35
+ non_att_children.each { |nac| td[1] << Flor.dup(nac) }
36
+
37
+ th[1] << td
38
+
39
+ th
40
+ end
41
+ end
42
+
@@ -1,22 +1,57 @@
1
1
 
2
- class Flor::Pro::Every < Flor::Macro
2
+ require 'flor/punit/schedule'
3
+
4
+
5
+ class Flor::Pro::Every < Flor::Macro::Schedule
6
+ #
7
+ # "every" is a macro procedure.
8
+ #
9
+ # ```
10
+ # every 'day at midnight'
11
+ # task 'alpha'
12
+ # ```
13
+ #
14
+ # is automatically turned into:
15
+ #
16
+ # ```
17
+ # schedule every: 'day at midnight'
18
+ # def msg
19
+ # task 'alpha'
20
+ # ```
21
+ #
22
+ # ## time strings
23
+ #
24
+ # Every understands time durations and, somehow, frequencies.
25
+ #
26
+ # ```
27
+ # every: "5m10s"
28
+ # every: "5 minutes and 10 seconds"
29
+ # ```
30
+ #
31
+ # Fugit translates `every: 'day at five'` into `cron: '0 5 * * *'`.
32
+ #
33
+ # ```
34
+ # every: 'day at five' # ==> '0 5 * * *'
35
+ # every: 'weekday at five' # ==> '0 5 * * 1,2,3,4,5'
36
+ # every: 'day at 5 pm' # ==> '0 17 * * *'
37
+ # every: 'tuesday at 5 pm' # ==> '0 17 * * 2'
38
+ # every: 'wed at 5 pm' # ==> '0 17 * * 3'
39
+ # every: 'day at 16:30' # ==> '30 16 * * *'
40
+ # every: 'day at noon' # ==> '0 12 * * *'
41
+ # every: 'day at midnight' # ==> '0 0 * * *'
42
+ # every: 'tuesday and monday at 5pm' # ==> '0 17 * * 1,2'
43
+ # every: 'wed or Monday at 5pm and 11' # ==> '0 11,17 * * 1,3'
44
+ # ```
45
+ #
46
+ # ## see also
47
+ #
48
+ # Cron, at, in, every, and sleep.
3
49
 
4
50
  name 'every'
5
51
 
6
52
  def rewrite_tree
7
53
 
8
- l = tree[2]
9
-
10
- th = [ 'schedule', [], l, *tree[3] ]
11
- att_children.each { |ac| th[1] << Flor.dup(ac) }
12
-
13
- td = [ 'def', [], l ]
14
- td[1] << [ '_att', [ [ 'msg', [], l ] ], l ]
15
- non_att_children.each { |nac| td[1] << Flor.dup(nac) }
16
-
17
- th[1] << td
18
-
19
- th
54
+ rewrite_schedule_tree(nil)
20
55
  end
21
56
  end
22
57
 
@@ -56,11 +56,11 @@ class Flor::Pro::Graft < Flor::Procedure
56
56
  source_path, source =
57
57
  @executor.unit.loader.library(domain, sub, subflows: true)
58
58
 
59
- fail ArgumentError.new(
60
- "no subtree #{sub.inspect} found (domain #{domain.inspect})"
59
+ fail Flor::FlorError.new(
60
+ "no subtree #{sub.inspect} found (domain #{domain.inspect})", self
61
61
  ) unless source
62
62
 
63
- tree = Flor::Lang.parse(source, source_path, {})
63
+ tree = Flor.parse(source, source_path, {})
64
64
 
65
65
  # graft subtree into parent node
66
66
 
@@ -0,0 +1,38 @@
1
+
2
+ class Flor::Pro::OnTimeout < Flor::Procedure
3
+ #
4
+ # Counterpart to the on_timeout: attribute.
5
+ #
6
+ # Sets the on_timeout "attribute" of the parent procedure.
7
+ #
8
+ # ```
9
+ # set l []
10
+ # sequence timeout: '1s'
11
+ # push l 0
12
+ # on_timeout (def msg \ push l "$(msg.point):$(msg.nid)")
13
+ # stall _
14
+ # push l 2
15
+ # push l 3
16
+ # ```
17
+ # Ends up with `[ 0, 'cancel:0_1', 3 ]` in the variable `l`. The on_timeout
18
+ # is set on the "sequence".
19
+ #
20
+ # ## see also
21
+ #
22
+ # On, on_error, on_cancel.
23
+
24
+ name 'on_timeout'
25
+
26
+ def pre_execute
27
+
28
+ unatt_unkeyed_children
29
+ end
30
+
31
+ def receive_non_att
32
+
33
+ store_on(:timeout)
34
+
35
+ super
36
+ end
37
+ end
38
+
@@ -9,7 +9,38 @@ class Flor::Pro::Schedule < Flor::Procedure
9
9
  # check_systems
10
10
  # ```
11
11
  #
12
- # See also: cron, at, in and sleep
12
+ # It understands `cron:`, `at:`, `in:`, and `every:`.
13
+ #
14
+ # The time string parsing is done by the
15
+ # [fugit](https://github.com/floraison/fugit) gem.
16
+ #
17
+ # ## every:
18
+ #
19
+ # Every understands time durations and, somehow, frequencies.
20
+ #
21
+ # ```
22
+ # every: "5m10s"
23
+ # every: "5 minutes and 10 seconds"
24
+ # ```
25
+ #
26
+ # Fugit translates `every: 'day at five'` into `cron: '0 5 * * *'`.
27
+ #
28
+ # ```
29
+ # every: 'day at five' # ==> '0 5 * * *'
30
+ # every: 'weekday at five' # ==> '0 5 * * 1,2,3,4,5'
31
+ # every: 'day at 5 pm' # ==> '0 17 * * *'
32
+ # every: 'tuesday at 5 pm' # ==> '0 17 * * 2'
33
+ # every: 'wed at 5 pm' # ==> '0 17 * * 3'
34
+ # every: 'day at 16:30' # ==> '30 16 * * *'
35
+ # every: 'day at noon' # ==> '0 12 * * *'
36
+ # every: 'day at midnight' # ==> '0 0 * * *'
37
+ # every: 'tuesday and monday at 5pm' # ==> '0 17 * * 1,2'
38
+ # every: 'wed or Monday at 5pm and 11' # ==> '0 11,17 * * 1,3'
39
+ # ```
40
+ #
41
+ # ## see also
42
+ #
43
+ # Cron, at, in, every, and sleep.
13
44
 
14
45
  name 'schedule'
15
46
 
@@ -22,18 +53,18 @@ class Flor::Pro::Schedule < Flor::Procedure
22
53
 
23
54
  fun = @fcid > 0 ? payload['ret'] : nil
24
55
 
25
- fail ArgumentError.new(
26
- "missing a function to call when the scheduler triggers"
56
+ fail Flor::FlorError.new(
57
+ "missing a function to call when the scheduler triggers", self
27
58
  ) unless fun
28
59
 
29
- m = apply(fun, [], tree[2], false).first
60
+ m = apply(fun, [], tree[2], anid: false).first
30
61
 
31
62
  t, s =
32
63
  @node['atts'].find { |k, v| %w[ cron at in every ].include?(k) } ||
33
64
  @node['atts'].find { |k, v| k == nil }
34
65
 
35
- fail ArgumentError.new(
36
- "missing a schedule"
66
+ fail Flor::FlorError.new(
67
+ "missing a schedule", self
37
68
  ) unless s
38
69
 
39
70
  @node['scheduled'] = true
@@ -57,3 +88,35 @@ class Flor::Pro::Schedule < Flor::Procedure
57
88
  end
58
89
  end
59
90
 
91
+
92
+ class Flor::Macro::Schedule < Flor::Macro
93
+
94
+ def rewrite_schedule_tree(schedule_type)
95
+
96
+ atts = att_children
97
+ schedule_i = atts.index { |at| at[1].size == 1 }
98
+
99
+ fail Flor::FlorError.new("schedule not found in #{tree.inspect}", self) \
100
+ unless schedule_i
101
+
102
+ schedule =
103
+ schedule_type ?
104
+ Flor.dup(atts.delete_at(schedule_i)[1][0]) :
105
+ nil
106
+
107
+ l = tree[2]
108
+
109
+ th = [ 'schedule', [], l, *tree[3] ]
110
+ th[1] << [ '_att', [ [ schedule_type, [], l ], schedule ], l ] if schedule
111
+ atts.each { |ac| th[1] << Flor.dup(ac) }
112
+
113
+ td = [ 'def', [], l ]
114
+ td[1] << [ '_att', [ [ 'msg', [], l ] ], l ]
115
+ non_att_children.each { |nac| td[1] << Flor.dup(nac) }
116
+
117
+ th[1] << td
118
+
119
+ th
120
+ end
121
+ end
122
+