flor 0.13.0 → 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +8 -0
- data/lib/flor.rb +1 -1
- data/lib/flor/core.rb +7 -0
- data/lib/flor/core/executor.rb +43 -8
- data/lib/flor/core/node.rb +84 -54
- data/lib/flor/core/procedure.rb +36 -19
- data/lib/flor/core/texecutor.rb +4 -1
- data/lib/flor/deep.rb +43 -51
- data/lib/flor/dollar.rb +2 -3
- data/lib/flor/flor.rb +38 -9
- data/lib/flor/parser.rb +123 -72
- data/lib/flor/pcore/_arr.rb +15 -0
- data/lib/flor/pcore/_atom.rb +6 -5
- data/lib/flor/pcore/_att.rb +9 -4
- data/lib/flor/pcore/_obj.rb +28 -34
- data/lib/flor/pcore/_pat_.rb +77 -0
- data/lib/flor/pcore/_pat_arr.rb +131 -0
- data/lib/flor/pcore/_pat_guard.rb +70 -0
- data/lib/flor/pcore/_pat_obj.rb +143 -0
- data/lib/flor/pcore/_pat_or.rb +46 -0
- data/lib/flor/pcore/_val.rb +20 -0
- data/lib/flor/pcore/arith.rb +7 -1
- data/lib/flor/pcore/case.rb +46 -69
- data/lib/flor/pcore/cursor.rb +24 -24
- data/lib/flor/pcore/define.rb +5 -1
- data/lib/flor/pcore/do_return.rb +32 -0
- data/lib/flor/pcore/length.rb +18 -0
- data/lib/flor/pcore/logo.rb +47 -0
- data/lib/flor/pcore/map.rb +12 -10
- data/lib/flor/pcore/match.rb +276 -0
- data/lib/flor/pcore/not.rb +13 -0
- data/lib/flor/pcore/push.rb +52 -13
- data/lib/flor/pcore/range.rb +69 -0
- data/lib/flor/pcore/set.rb +82 -24
- data/lib/flor/unit/hook.rb +28 -0
- data/lib/flor/unit/hooker.rb +5 -0
- data/lib/flor/unit/loader.rb +4 -1
- data/lib/flor/unit/storage.rb +5 -3
- data/lib/flor/unit/waiter.rb +12 -2
- data/lib/flor/unit/wlist.rb +4 -3
- data/match.md +22 -0
- metadata +15 -5
- data/lib/flor/pcore/_happly.rb +0 -49
- data/lib/flor/pcore/val.rb +0 -16
- 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
|
+
|
data/lib/flor/pcore/push.rb
CHANGED
@@ -1,7 +1,40 @@
|
|
1
1
|
|
2
2
|
class Flor::Pro::Push < Flor::Procedure
|
3
|
-
|
4
|
-
|
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']
|
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
|
+
|