flor 0.13.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/CHANGELOG.md +8 -0
  2. data/lib/flor.rb +1 -1
  3. data/lib/flor/core.rb +7 -0
  4. data/lib/flor/core/executor.rb +43 -8
  5. data/lib/flor/core/node.rb +84 -54
  6. data/lib/flor/core/procedure.rb +36 -19
  7. data/lib/flor/core/texecutor.rb +4 -1
  8. data/lib/flor/deep.rb +43 -51
  9. data/lib/flor/dollar.rb +2 -3
  10. data/lib/flor/flor.rb +38 -9
  11. data/lib/flor/parser.rb +123 -72
  12. data/lib/flor/pcore/_arr.rb +15 -0
  13. data/lib/flor/pcore/_atom.rb +6 -5
  14. data/lib/flor/pcore/_att.rb +9 -4
  15. data/lib/flor/pcore/_obj.rb +28 -34
  16. data/lib/flor/pcore/_pat_.rb +77 -0
  17. data/lib/flor/pcore/_pat_arr.rb +131 -0
  18. data/lib/flor/pcore/_pat_guard.rb +70 -0
  19. data/lib/flor/pcore/_pat_obj.rb +143 -0
  20. data/lib/flor/pcore/_pat_or.rb +46 -0
  21. data/lib/flor/pcore/_val.rb +20 -0
  22. data/lib/flor/pcore/arith.rb +7 -1
  23. data/lib/flor/pcore/case.rb +46 -69
  24. data/lib/flor/pcore/cursor.rb +24 -24
  25. data/lib/flor/pcore/define.rb +5 -1
  26. data/lib/flor/pcore/do_return.rb +32 -0
  27. data/lib/flor/pcore/length.rb +18 -0
  28. data/lib/flor/pcore/logo.rb +47 -0
  29. data/lib/flor/pcore/map.rb +12 -10
  30. data/lib/flor/pcore/match.rb +276 -0
  31. data/lib/flor/pcore/not.rb +13 -0
  32. data/lib/flor/pcore/push.rb +52 -13
  33. data/lib/flor/pcore/range.rb +69 -0
  34. data/lib/flor/pcore/set.rb +82 -24
  35. data/lib/flor/unit/hook.rb +28 -0
  36. data/lib/flor/unit/hooker.rb +5 -0
  37. data/lib/flor/unit/loader.rb +4 -1
  38. data/lib/flor/unit/storage.rb +5 -3
  39. data/lib/flor/unit/waiter.rb +12 -2
  40. data/lib/flor/unit/wlist.rb +4 -3
  41. data/match.md +22 -0
  42. metadata +15 -5
  43. data/lib/flor/pcore/_happly.rb +0 -49
  44. data/lib/flor/pcore/val.rb +0 -16
  45. data/t.txt +0 -4
@@ -0,0 +1,276 @@
1
+
2
+ class Flor::Pro::Match < Flor::Pro::Case
3
+ #
4
+ # "match" can be thought of as a "destructuring [case](case.md)".
5
+ #
6
+ # "match", like "case", matches its first argument or the incoming `f.ret`.
7
+ #
8
+ # It can do what "case" can do:
9
+ # ```
10
+ # match v0
11
+ # 0; 'zero'
12
+ # 1; 'one'
13
+ # else; v0
14
+ # ```
15
+ # (Please note that "case" accepts arrays of possible values, while "match"
16
+ # does not, it reads arrays on the left side as patterns (see destructuring
17
+ # arrays below))
18
+ #
19
+ # But it can also destructure arrays:
20
+ # ```
21
+ # # the classical FizzBuzz
22
+ # match [ (% i 3) (% i 5) ]
23
+ # [ 0 0 ]; 'FizzBuzz'
24
+ # [ 0 _ ]; 'Fizz'
25
+ # [ _ 0 ]; 'Buzz'
26
+ # else; i
27
+ # ```
28
+ # and objects:
29
+ # ```
30
+ # match
31
+ # { type: 'car', brand: b, model: m }; "a $(b) model $(m)"
32
+ # { type: 'train', destination: d }; "a train heading for $(d)"
33
+ # else; "an unidentified mobile object"
34
+ # ```
35
+ #
36
+ # Note the general left-side; right-side structure. There is a pattern on
37
+ # the left-side as a condition and a consequent on the right-side. Variables
38
+ # may be bound in the left-side and accessed in the right-side consequent.
39
+ # In the above example, the "brand" is bound under the variable `b` and
40
+ # thus accessed in the consequent (which just builds a string that will
41
+ # be the return value of the whole "match").
42
+ #
43
+ # Note that this "left-side / right-side" distinction is arbitrary. The
44
+ # code above may be written equivalently as
45
+ # ```
46
+ # match
47
+ # { type: 'car', brand: b, model: m }
48
+ # "a $(b) model $(m)"
49
+ # { type: 'train', destination: d }
50
+ # "a train heading for $(d)"
51
+ # else
52
+ # "an unidentified mobile object"
53
+ # ```
54
+ #
55
+ # ## destructuring arrays
56
+ # ```
57
+ # match
58
+ # [ 1 _ ]; "an array with 2 elements, first one is 1"
59
+ # [ 1 _ 3 ]; "3 elts, starts with 1, ends with 3"
60
+ # [ 1 ___ 3 ]; "2 or more elts, starts with 1, ends with 3"
61
+ # [ a__2 __3 ]; "first 2 elts are $(a), in total 5 elts"
62
+ # ```
63
+ # Note the `_` that matches a single element, the `___` that matches
64
+ # the "rest" but can be declined in `{binding-name}__{_|count}`, for
65
+ # example, the `a__2` above means "take 2 elements and bind them under 'a'.
66
+ #
67
+ # ## destructuring objects
68
+ #
69
+ # As seen above, match with an object pattern lets one destructure and match.
70
+ # ```
71
+ # match
72
+ # { type: 'car', brand: b, model: m }; "a $(b) model $(m)"
73
+ # { type: 'train', destination: d }; "a train heading for $(d)"
74
+ # else; "an unidentified mobile object"
75
+ # ```
76
+ #
77
+ # ### `only`
78
+ #
79
+ # By default, "match" only looks at the keys given by the pattern. If there
80
+ # are entries under other keys in the value, it doesn't care, it matches.
81
+ # With "only", it expects the value to have the same keys as the pattern.
82
+ # ```
83
+ # set o { 'type': 'car', 'brand': 'simca' }
84
+ # set msg0
85
+ # match o
86
+ # { type: 'car' }; "it's a car" # <=== matches here
87
+ # else; "it's something else"
88
+ # set msg1
89
+ # match o
90
+ # { type: 'car' } only; "it's a car"
91
+ # else; "it's something else" # <=== matches here
92
+ # ```
93
+ #
94
+ # ### `quote: "keys"`
95
+ #
96
+ # By default, the keys in the pattern are extrapolated.
97
+ # ```
98
+ # set a 'A'
99
+ # set b 'B'
100
+ # match
101
+ # { a: 1, b: 2 }; 'match'
102
+ # else; 'no-match'
103
+ # ```
104
+ # is thus equivalent to
105
+ # ```
106
+ # match
107
+ # { 'A': 1, 'B': 2 }; 'match'
108
+ # else; 'no-match'
109
+ # ```
110
+ #
111
+ # One can make sure keys are not extrapolated by quoting them all or by
112
+ # using the `quote: keys` attribute on the pattern:
113
+ # ```
114
+ # match
115
+ # { a: 1, b: 2 } quote: keys; 'match'
116
+ # else; 'no-match'
117
+ # ```
118
+ #
119
+ # Probably, quoting manually makes the pattern easier to read than forcing
120
+ # `quote: keys` on it.
121
+ #
122
+ # ### deep keys
123
+ #
124
+ # The object pattern is OK with "deep keys", keys pointing at subelements
125
+ # in the object being matched.
126
+ # ```
127
+ # set o { player: { name: 'Eldred', 'number': 55 } }
128
+ # # ...
129
+ # match o
130
+ # { player.name: 'Eldred' }; 'USA' # <=== will match here
131
+ # { player.name: 'Johnson' }; 'USA'
132
+ # else; 'Japan'
133
+ # ```
134
+ #
135
+ # ## "or"
136
+ #
137
+ # ```
138
+ # match
139
+ # { age: (99 or 100) }; 'match'
140
+ # else; 'no-match'
141
+ # ```
142
+ #
143
+ # ## "or!"
144
+ #
145
+ # Using "or!" forces match not to transform "or" into "_pat_or". "or" thus
146
+ # stays the boolean logic "or" and returns true or false (while the "or",
147
+ # _pat_or above returns match/no-match in the "match" context).
148
+ #
149
+ # "or!" is best used in conjunction with "guard"
150
+ # ```
151
+ # match
152
+ # { age: (guard a (or! (a < 10) (a > 99))) }; "kid or centenary"
153
+ # else; "normal age"
154
+ # ```
155
+ #
156
+ # ## "bind"
157
+ #
158
+ # "bind" binds explicitely a value and allows for a sub-pattern.
159
+ # ```
160
+ # match [ 1 4 ]
161
+ # [ 1 (bind y (or 2 3)) ]; "match y:$(y)"
162
+ # [ 1 (bind x (or 4 5)) ]; "match x:$(x)"
163
+ # else; 'no-match'
164
+ # ```
165
+ #
166
+ # Behind the scenes, "bind" is an alias for "guard".
167
+ #
168
+ # ## "guard"
169
+ #
170
+ # "guard", like "bind" and "or" is used inside of array or object patterns
171
+ # to run a condition on some element or entry.
172
+ # ```
173
+ # set title
174
+ # match
175
+ # { age: (guard a (> 35)), function: f }; "senior $(f)"
176
+ # { sex: 'female' }; 'ms'
177
+ # else; 'mr'
178
+ # ```
179
+ #
180
+ # "guard" takes at least a name argument, then optional pattern or
181
+ # conditional arguments. The name is bound and available in the pattern /
182
+ # conditional arguments.
183
+ # ```
184
+ # (guard {name})
185
+ # (guard {name} {pattern|conditional}*)
186
+ # ```
187
+
188
+ name 'match'
189
+
190
+ def pre_execute
191
+
192
+ unatt_unkeyed_children
193
+
194
+ conditional = true
195
+ @node['val'] = payload['ret'] if non_att_children.size.even?
196
+ found_val = @node.has_key?('val')
197
+ t = tree
198
+ changed = false
199
+
200
+ t[1].each_with_index do |ct, i|
201
+
202
+ next if ct[0] == '_att'
203
+ next(found_val = true) unless found_val
204
+ next(conditional = true) unless conditional
205
+
206
+ conditional = false
207
+ t[1][i] = patternize(ct)
208
+ changed = changed || t[1][i] != ct
209
+ end
210
+
211
+ @node['tree'] = t if changed
212
+ end
213
+
214
+ def execute_child(index=0, sub=nil, h=nil)
215
+
216
+ t = tree[1][index]
217
+
218
+ payload['_pat_val'] = @node['val'] \
219
+ if t && t[0].match(/\A_pat_(arr|obj|or|guard)\z/)
220
+
221
+ super
222
+ end
223
+
224
+ protected
225
+
226
+ def patternize(t)
227
+
228
+ return t unless t[1].is_a?(Array)
229
+
230
+ bang = t[1]
231
+ .index { |ct|
232
+ ct[0] == '_att' &&
233
+ ct[1].size == 1 &&
234
+ ct[1][0][0, 2] == [ '!', [] ] }
235
+ pat =
236
+ case t[0]
237
+ when '_arr', '_obj' then "_pat#{t[0]}"
238
+ when 'or', 'guard' then "_pat_#{t[0]}"
239
+ when 'bind' then '_pat_guard'
240
+ when 'or!' then 'or'
241
+ else nil
242
+ end
243
+
244
+ t[0] =
245
+ if pat && bang
246
+ t[1].delete_at(bang)
247
+ t[0]
248
+ elsif pat
249
+ pat
250
+ else
251
+ t[0]
252
+ end
253
+
254
+ t[1].each_with_index { |ct, i| t[1][i] = patternize(t[1][i]) }
255
+
256
+ t
257
+ end
258
+
259
+ def match?
260
+
261
+ if b = payload.delete('_pat_binding')
262
+ b
263
+ else
264
+ payload['ret'] == @node['val']
265
+ end
266
+ end
267
+
268
+ def else?(ncid)
269
+
270
+ t = tree[1][ncid]; return false unless t
271
+
272
+ t[0, 2] == [ '_', [] ] ||
273
+ t[0, 2] == [ 'else', [] ]
274
+ end
275
+ end
276
+
@@ -0,0 +1,13 @@
1
+
2
+ class Flor::Pro::Not < Flor::Procedure
3
+
4
+ name 'not'
5
+
6
+ def receive_last
7
+
8
+ payload['ret'] = ! Flor.true?(payload['ret'])
9
+
10
+ super
11
+ end
12
+ end
13
+
@@ -1,7 +1,40 @@
1
1
 
2
2
  class Flor::Pro::Push < Flor::Procedure
3
-
4
- names 'push', 'pushr'
3
+ #
4
+ # Pushes a value into an array (in a variable or a field).
5
+ #
6
+ # ```
7
+ # sequence
8
+ # set a []
9
+ # set f.a []
10
+ # push a 1
11
+ # push f.a 2
12
+ # ```
13
+ #
14
+ # ```
15
+ # sequence
16
+ # set o { a: [], b: 3 }
17
+ # 7
18
+ # push o.a # will push value 7 (payload.ret) into array at 'o.a'
19
+ # ```
20
+ #
21
+ # ```
22
+ # push
23
+ # myarray # the 1st child is expected to hold the reference to the array
24
+ # do_this _
25
+ # do_that _
26
+ # + 1 2 # the last child should hold the value to push
27
+ # ```
28
+ #
29
+ # ## "pushr"
30
+ #
31
+ # Following ["set"](set.md) and "setr", "push", upon beginning its execution
32
+ # will keep the incoming payload.ret and restore it to that value right
33
+ # before finishing its execution. "pushr" will not do that, it will leave
34
+ # the payload.ret as is, that is, set to the value that was just pushed to
35
+ # the array.
36
+
37
+ names %w[ push pushr ]
5
38
 
6
39
  def pre_execute
7
40
 
@@ -11,13 +44,29 @@ class Flor::Pro::Push < Flor::Procedure
11
44
 
12
45
  def receive_non_att
13
46
 
14
- @node['arr'] ||= payload['ret']
47
+ if ! @node['arr']
48
+ @node['arr'] = payload['ret']
49
+ else
50
+ @node['to_push'] = payload['ret']
51
+ end
15
52
 
16
53
  super
17
54
  end
18
55
 
19
56
  def receive_last
20
57
 
58
+ push(@node.has_key?('to_push') ? @node['to_push'] : node_payload_ret)
59
+
60
+ payload['ret'] = node_payload_ret \
61
+ unless tree[0] == 'pushr'
62
+
63
+ wrap_reply
64
+ end
65
+
66
+ protected
67
+
68
+ def push(val)
69
+
21
70
  arr = @node['arr']
22
71
 
23
72
  if arr.is_a?(String)
@@ -29,17 +78,7 @@ class Flor::Pro::Push < Flor::Procedure
29
78
  "cannot push to given target (#{arr.class})", self
30
79
  ) unless arr.respond_to?(:push)
31
80
 
32
- val =
33
- unkeyed_children.size > 1 ?
34
- payload['ret'] :
35
- node_payload_ret
36
-
37
81
  arr.push(val)
38
-
39
- payload['ret'] = node_payload_ret \
40
- unless tree[0] == 'pushr'
41
-
42
- wrap_reply
43
82
  end
44
83
  end
45
84
 
@@ -0,0 +1,69 @@
1
+
2
+ class Flor::Pro::Range < Flor::Procedure
3
+ #
4
+ # "range" is a procedure to generate ranges of integers.
5
+ #
6
+ # ```
7
+ # # range {end}
8
+ # # range {start} {end}
9
+ # # range {start} {end} {step}
10
+ # range 0 #--> []
11
+ # range 4 #--> [ 0, 1, 2, 3 ]
12
+ # range 4 7 #--> [ 4, 5, 6 ]
13
+ # range 4 14 2 #--> [ 4, 6, 8, 10, 12 ]
14
+ # range (-4) #--> [ 0, -1, -2, -3 ]
15
+ # range 9 1 (-2) #--> [ 9, 7, 5, 3 ] ]
16
+ # ```
17
+ #
18
+ # ```
19
+ # range from: 9 to: 1 by: -2
20
+ # # or
21
+ # range start: 9 end: 1 step: -2
22
+ # #
23
+ # # are also accepted
24
+ # ```
25
+
26
+ name 'range'
27
+
28
+ def pre_execute
29
+
30
+ @node['rets'] = []
31
+ @node['atts'] = []
32
+
33
+ unatt_unkeyed_children
34
+ end
35
+
36
+ def receive_last
37
+
38
+ rets = @node['rets'].select { |r| r.is_a?(Numeric) }.collect(&:to_i)
39
+
40
+ sta = rets[1] ? rets[0] : 0
41
+ edn = rets[1] || rets[0] || 0
42
+ ste = rets[2] || ((sta > edn) ? -1 : 1)
43
+
44
+ asta = att('start') || att('from')
45
+ aedn = att('end') || att('to')
46
+ aste = att('step') || att('by') || att('inc')
47
+
48
+ sta = asta if asta
49
+ edn = aedn if aedn
50
+ ste = aste if aste
51
+
52
+ fail ArgumentError.new("#{@node['heat0']} step is 0") if ste == 0
53
+
54
+ #payload['ret'] = (sta..edn - 1).step(ste).to_a
55
+ # doesn't accept negative steps
56
+
57
+ payload['ret'] = []
58
+ cur = sta
59
+ #
60
+ loop do
61
+ break if (ste < 0) ? (cur <= edn) : (cur >= edn)
62
+ payload['ret'] << cur
63
+ cur = cur + ste
64
+ end
65
+
66
+ super
67
+ end
68
+ end
69
+