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.
- 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
|
+
|