flor 0.13.0 → 0.14.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 (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
+