ruby-next-core 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -1
- data/README.md +11 -7
- data/lib/ruby-next/commands/core_ext.rb +1 -1
- data/lib/ruby-next/core.rb +3 -0
- data/lib/ruby-next/core/struct/deconstruct_keys.rb +0 -2
- data/lib/ruby-next/core/symbol/end_with.rb +9 -0
- data/lib/ruby-next/core/symbol/start_with.rb +9 -0
- data/lib/ruby-next/language/rewriters/pattern_matching.rb +352 -79
- data/lib/ruby-next/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0456426952d07bcec1db2c70d256177bd8d1503c8edc7e667e2af62bf5f03ae3'
|
4
|
+
data.tar.gz: 4528e483af6cd8cdbf7b613454a0051abd5aaad4ec6cd02c727690d64fbfd848
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a25d4e8c619b0644f4bcb0f411d0f0c86d1aa7c343ab0e55b42c4d8adad10b2c5c7072ee13f019c9da29223774f6c34c353f58b64f32caf91e60040ec1b029a2
|
7
|
+
data.tar.gz: 02e6e9bdc4379b2542fe6802160f2946ac9d151878bea3f51bcd27da749f7e0bb2cf2891aa0ddf88f5a8221185d8ab9e1bdab4e639de6d69c2a44409e734626c
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,17 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 0.4.0 (2020-03-09)
|
6
|
+
|
7
|
+
- Optimize pattern matching transpiled code. ([@palkan][])
|
8
|
+
|
9
|
+
For array patterns, transpiled code is ~1.5-2x faster than native.
|
10
|
+
For hash patterns it's about the same.
|
11
|
+
|
12
|
+
- Pattern matching is 100% compatible with RubySpec. ([@palkan][])
|
13
|
+
|
14
|
+
- Add `Symbol#start_with?/end_with?`. ([@palkan][])
|
15
|
+
|
5
16
|
## 0.3.0 (2020-02-14) 💕
|
6
17
|
|
7
18
|
- Add `Time#floor` and `Time#ceil`. ([@palkan][])
|
@@ -48,7 +59,7 @@ You can still use this feature by enabling it explicitly (see Readme).
|
|
48
59
|
- Support in modifier. ([@palkan][])
|
49
60
|
|
50
61
|
```ruby
|
51
|
-
{a:1, b: 2} in {a:, **}
|
62
|
+
{a: 1, b: 2} in {a:, **}
|
52
63
|
p a #=> 1
|
53
64
|
```
|
54
65
|
|
data/README.md
CHANGED
@@ -3,9 +3,10 @@
|
|
3
3
|
|
4
4
|
# Ruby Next
|
5
5
|
|
6
|
-
|
6
|
+
<img align="right" height="184"
|
7
|
+
title="Ruby Next logo" src="./assets/images/logo.svg">
|
7
8
|
|
8
|
-
Ruby Next is a
|
9
|
+
Ruby Next is a **transpiler** and a collection of **polyfills** for supporting the latest and upcoming Ruby features (APIs and syntax) in older versions and alternative implementations. For example, you can use pattern matching and `Kernel#then` in Ruby 2.5 or [mruby][].
|
9
10
|
|
10
11
|
Who might be interested in Ruby Next?
|
11
12
|
|
@@ -14,9 +15,10 @@ Who might be interested in Ruby Next?
|
|
14
15
|
- **Users of non-MRI implementations** such as [mruby][], [JRuby][], [TruffleRuby][], [Opal][], [RubyMotion][], [Artichoke][], [Prism][].
|
15
16
|
|
16
17
|
Ruby Next also aims to help the community to assess new, _experimental_, MRI features by making it easier to play with them.
|
17
|
-
That's why Ruby Next implements the `
|
18
|
+
That's why Ruby Next implements the `master` features as fast as possible.
|
18
19
|
|
19
|
-
|
20
|
+
<a href="https://evilmartians.com/?utm_source=ruby-next">
|
21
|
+
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
|
20
22
|
|
21
23
|
## Links
|
22
24
|
|
@@ -92,7 +94,9 @@ gem "ruby-next"
|
|
92
94
|
|
93
95
|
# gemspec
|
94
96
|
spec.add_dependency "ruby-next"
|
97
|
+
```
|
95
98
|
|
99
|
+
```sh
|
96
100
|
# or install globally
|
97
101
|
gem install ruby-next
|
98
102
|
```
|
@@ -164,7 +168,7 @@ The behaviour depends on whether you transpile a single file or a directory:
|
|
164
168
|
|
165
169
|
```sh
|
166
170
|
$ ruby-next nextify my_ruby.rb -o my_ruby_next.rb -V
|
167
|
-
|
171
|
+
Ruby Next core strategy: refine
|
168
172
|
Generated: my_ruby_next.rb
|
169
173
|
```
|
170
174
|
|
@@ -298,7 +302,7 @@ Then, add to your Gemfile:
|
|
298
302
|
|
299
303
|
```ruby
|
300
304
|
source "https://rubygems.pkg.github.com/ruby-next" do
|
301
|
-
gem "parser", "2.7.0.100"
|
305
|
+
gem "parser", "~> 2.7.0.100", "< 2.7.1"
|
302
306
|
end
|
303
307
|
|
304
308
|
gem "ruby-next"
|
@@ -311,7 +315,7 @@ gem "ruby-next"
|
|
311
315
|
You can install `ruby-next` globally by running the following commands:
|
312
316
|
|
313
317
|
```sh
|
314
|
-
gem install parser -v "2.7.0.100" --source "https://USERNAME:ACCESS_TOKEN@rubygems.pkg.github.com/ruby-next"
|
318
|
+
gem install parser -v "~> 2.7.0.100" -v "< 2.7.1" --source "https://USERNAME:ACCESS_TOKEN@rubygems.pkg.github.com/ruby-next"
|
315
319
|
gem install ruby-next
|
316
320
|
```
|
317
321
|
|
data/lib/ruby-next/core.rb
CHANGED
@@ -152,6 +152,9 @@ require_relative "core/hash/merge"
|
|
152
152
|
|
153
153
|
require_relative "core/string/split"
|
154
154
|
|
155
|
+
require_relative "core/symbol/start_with"
|
156
|
+
require_relative "core/symbol/end_with"
|
157
|
+
|
155
158
|
require_relative "core/unboundmethod/bind_call"
|
156
159
|
|
157
160
|
require_relative "core/time/floor"
|
@@ -8,8 +8,6 @@ RubyNext::Core.patch Struct, method: :deconstruct_keys, version: "2.7" do
|
|
8
8
|
|
9
9
|
return to_h unless keys
|
10
10
|
|
11
|
-
return {} if size < keys.size
|
12
|
-
|
13
11
|
keys.each_with_object({}) do |k, acc|
|
14
12
|
# if k is Symbol and not a member of a Struct return {}
|
15
13
|
next if (Symbol === k || String === k) && !members.include?(k.to_sym)
|
@@ -31,6 +31,190 @@ module RubyNext
|
|
31
31
|
end
|
32
32
|
end)
|
33
33
|
|
34
|
+
# We can memoize structural predicates to avoid double calculation.
|
35
|
+
#
|
36
|
+
# For example, consider the following case and the corresponding predicate chains:
|
37
|
+
#
|
38
|
+
# case val
|
39
|
+
# in [:ok, 200] #=> [:respond_to_deconstruct, :deconstruct_type, :arr_size_is_2]
|
40
|
+
# in [:created, 201] #=> [:respond_to_deconstruct, :deconstruct_type, :arr_size_is_2]
|
41
|
+
# in [401 | 403] #=> [:respond_to_deconstruct, :deconstruct_type, :arr_size_is_1]
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# We can minimize the number of predicate calls by storing the intermediate values (prefixed with `p_`) and using them
|
45
|
+
# in the subsequent calls:
|
46
|
+
#
|
47
|
+
# case val
|
48
|
+
# in [:ok, 200] #=> [:respond_to_deconstruct, :deconstruct_type, :arr_size_is_2]
|
49
|
+
# in [:created, 201] #=> [:p_deconstructed, :p_arr_size_2]
|
50
|
+
# in [401 | 403] #=> [:p_deconstructed, :arr_size_is_1]
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# This way we mimic a naive decision tree algorithim.
|
54
|
+
module Predicates
|
55
|
+
class Processor < ::Parser::TreeRewriter
|
56
|
+
attr_reader :predicates
|
57
|
+
|
58
|
+
def initialize(predicates)
|
59
|
+
@predicates = predicates
|
60
|
+
super()
|
61
|
+
end
|
62
|
+
|
63
|
+
def on_lvasgn(node)
|
64
|
+
lvar, val = *node.children
|
65
|
+
if predicates.store[lvar] == false
|
66
|
+
process(val)
|
67
|
+
else
|
68
|
+
node
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def on_and(node)
|
73
|
+
left, right = *node.children
|
74
|
+
|
75
|
+
if truthy(left)
|
76
|
+
process(right)
|
77
|
+
elsif truthy(right)
|
78
|
+
process(left)
|
79
|
+
else
|
80
|
+
node.updated(
|
81
|
+
:and,
|
82
|
+
[
|
83
|
+
process(left),
|
84
|
+
process(right)
|
85
|
+
]
|
86
|
+
)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def truthy(node)
|
93
|
+
return false unless node.is_a?(::Parser::AST::Node)
|
94
|
+
return true if node.type == :true
|
95
|
+
return false if node.children.empty?
|
96
|
+
|
97
|
+
node.children.all? { |child| truthy(child) }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class Base
|
102
|
+
attr_reader :store, :predicates_by_path, :count, :terminated, :current_path
|
103
|
+
alias terminated? terminated
|
104
|
+
|
105
|
+
def initialize
|
106
|
+
# total number of predicates
|
107
|
+
@count = 0
|
108
|
+
# cache of all predicates by path
|
109
|
+
@predicates_by_path = {}
|
110
|
+
# all predicates and their dirty state
|
111
|
+
@store = {}
|
112
|
+
|
113
|
+
@current_path = []
|
114
|
+
end
|
115
|
+
|
116
|
+
def reset!
|
117
|
+
@current_path = []
|
118
|
+
@terminated = false
|
119
|
+
end
|
120
|
+
|
121
|
+
def push(path)
|
122
|
+
current_path << path
|
123
|
+
end
|
124
|
+
|
125
|
+
def pop
|
126
|
+
current_path.pop
|
127
|
+
end
|
128
|
+
|
129
|
+
def terminate!
|
130
|
+
@terminated = true
|
131
|
+
end
|
132
|
+
|
133
|
+
def predicate_clause(name, node)
|
134
|
+
if pred?(name)
|
135
|
+
read_pred(name)
|
136
|
+
else
|
137
|
+
write_pred(name, node)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def pred?(name)
|
142
|
+
predicates_by_path.key?(current_path + [name])
|
143
|
+
end
|
144
|
+
|
145
|
+
def read_pred(name)
|
146
|
+
lvar = predicates_by_path.fetch(current_path + [name])
|
147
|
+
# mark as used
|
148
|
+
store[lvar] = true
|
149
|
+
s(:lvar, lvar)
|
150
|
+
end
|
151
|
+
|
152
|
+
def write_pred(name, node)
|
153
|
+
return node if terminated?
|
154
|
+
@count += 1
|
155
|
+
lvar = :"__p_#{count}__"
|
156
|
+
predicates_by_path[current_path + [name]] = lvar
|
157
|
+
store[lvar] = false
|
158
|
+
|
159
|
+
s(:lvasgn,
|
160
|
+
lvar,
|
161
|
+
node)
|
162
|
+
end
|
163
|
+
|
164
|
+
def process(ast)
|
165
|
+
Processor.new(self).process(ast)
|
166
|
+
end
|
167
|
+
|
168
|
+
private
|
169
|
+
|
170
|
+
def s(type, *children)
|
171
|
+
::Parser::AST::Node.new(type, children)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# rubocop:disable Style/MethodMissingSuper
|
176
|
+
# rubocop:disable Style/MissingRespondToMissing
|
177
|
+
class Noop < Base
|
178
|
+
# Return node itself, no memoization
|
179
|
+
def method_missing(mid, node, *)
|
180
|
+
node
|
181
|
+
end
|
182
|
+
end
|
183
|
+
# rubocop:enable Style/MethodMissingSuper
|
184
|
+
# rubocop:enable Style/MissingRespondToMissing
|
185
|
+
|
186
|
+
class CaseIn < Base
|
187
|
+
def const(node, const)
|
188
|
+
node
|
189
|
+
end
|
190
|
+
|
191
|
+
def respond_to_deconstruct(node)
|
192
|
+
predicate_clause(:respond_to_deconstruct, node)
|
193
|
+
end
|
194
|
+
|
195
|
+
def array_size(node, size)
|
196
|
+
predicate_clause(:"array_size_#{size}", node)
|
197
|
+
end
|
198
|
+
|
199
|
+
def array_deconstructed(node)
|
200
|
+
predicate_clause(:array_deconstructed, node)
|
201
|
+
end
|
202
|
+
|
203
|
+
def hash_deconstructed(node, keys)
|
204
|
+
predicate_clause(:"hash_deconstructed_#{keys.join("_p_")}", node)
|
205
|
+
end
|
206
|
+
|
207
|
+
def respond_to_deconstruct_keys(node)
|
208
|
+
predicate_clause(:respond_to_deconstruct_keys, node)
|
209
|
+
end
|
210
|
+
|
211
|
+
def hash_key(node, key)
|
212
|
+
key = key.children.first if key.is_a?(::Parser::AST::Node)
|
213
|
+
predicate_clause(:"hash_key_#{key}", node)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
34
218
|
class PatternMatching < Base
|
35
219
|
SYNTAX_PROBE = "case 0; in 0; true; else; 1; end"
|
36
220
|
MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
|
@@ -39,15 +223,19 @@ module RubyNext
|
|
39
223
|
MATCHEE_ARR = :__m_arr__
|
40
224
|
MATCHEE_HASH = :__m_hash__
|
41
225
|
|
226
|
+
ALTERNATION_MARKER = :__alt__
|
227
|
+
CURRENT_HASH_KEY = :__chk__
|
228
|
+
|
42
229
|
def on_case_match(node)
|
43
230
|
context.track! self
|
44
231
|
|
45
|
-
@
|
232
|
+
@deconstructed_keys = {}
|
233
|
+
@predicates = Predicates::CaseIn.new
|
46
234
|
|
47
235
|
matchee_ast =
|
48
236
|
s(:lvasgn, MATCHEE, node.children[0])
|
49
237
|
|
50
|
-
|
238
|
+
patterns = locals.with(
|
51
239
|
matchee: MATCHEE,
|
52
240
|
arr: MATCHEE_ARR,
|
53
241
|
hash: MATCHEE_HASH
|
@@ -55,10 +243,13 @@ module RubyNext
|
|
55
243
|
build_if_clause(node.children[1], node.children[2..-1])
|
56
244
|
end
|
57
245
|
|
246
|
+
# remove unused predicate assignments and truthy expressions
|
247
|
+
patterns = predicates.process(patterns)
|
248
|
+
|
58
249
|
node.updated(
|
59
250
|
:begin,
|
60
251
|
[
|
61
|
-
matchee_ast,
|
252
|
+
matchee_ast, patterns
|
62
253
|
]
|
63
254
|
)
|
64
255
|
end
|
@@ -66,6 +257,9 @@ module RubyNext
|
|
66
257
|
def on_in_match(node)
|
67
258
|
context.track! self
|
68
259
|
|
260
|
+
@deconstructed_keys = {}
|
261
|
+
@predicates = Predicates::Noop.new
|
262
|
+
|
69
263
|
matchee =
|
70
264
|
s(:lvasgn, MATCHEE, node.children[0])
|
71
265
|
|
@@ -98,11 +292,15 @@ module RubyNext
|
|
98
292
|
|
99
293
|
def build_if_clause(node, rest)
|
100
294
|
if node&.type == :in_pattern
|
295
|
+
predicates.reset!
|
101
296
|
build_in_pattern(node, rest)
|
102
297
|
else
|
103
298
|
raise "Unexpected else in the middle of case ... in" if rest && rest.size > 0
|
104
299
|
# else clause must be present
|
105
|
-
node || no_matching_pattern
|
300
|
+
(process(node) || no_matching_pattern).then do |else_node|
|
301
|
+
next else_node unless else_node.type == :empty_else
|
302
|
+
s(:empty)
|
303
|
+
end
|
106
304
|
end
|
107
305
|
end
|
108
306
|
|
@@ -115,7 +313,7 @@ module RubyNext
|
|
115
313
|
),
|
116
314
|
clause.children[1] # guard
|
117
315
|
),
|
118
|
-
clause.children[2] || s(:nil) # expression
|
316
|
+
process(clause.children[2] || s(:nil)) # expression
|
119
317
|
].then do |children|
|
120
318
|
if rest && rest.size > 0
|
121
319
|
children << build_if_clause(rest.first, rest[1..-1])
|
@@ -125,10 +323,10 @@ module RubyNext
|
|
125
323
|
end
|
126
324
|
end
|
127
325
|
|
128
|
-
def const_pattern_clause(node)
|
326
|
+
def const_pattern_clause(node, right = s(:lvar, locals[:matchee]))
|
129
327
|
const, pattern = *node.children
|
130
328
|
|
131
|
-
case_eq_clause(const).then do |node|
|
329
|
+
predicates.const(case_eq_clause(const, right), const).then do |node|
|
132
330
|
next node if pattern.nil?
|
133
331
|
|
134
332
|
s(:and,
|
@@ -138,37 +336,58 @@ module RubyNext
|
|
138
336
|
end
|
139
337
|
|
140
338
|
def match_alt_clause(node)
|
141
|
-
children =
|
142
|
-
|
339
|
+
children = locals.with(ALTERNATION_MARKER => true) do
|
340
|
+
node.children.map.with_index do |child, i|
|
341
|
+
predicates.terminate! if i == 1
|
342
|
+
send :"#{child.type}_clause", child
|
343
|
+
end
|
143
344
|
end
|
144
345
|
s(:or, *children)
|
145
346
|
end
|
146
347
|
|
147
348
|
def match_as_clause(node, right = s(:lvar, locals[:matchee]))
|
148
349
|
s(:and,
|
149
|
-
|
350
|
+
send(:"#{node.children[0].type}_clause", node.children[0], right),
|
150
351
|
match_var_clause(node.children[1], right))
|
151
352
|
end
|
152
353
|
|
153
354
|
def match_var_clause(node, left = s(:lvar, locals[:matchee]))
|
355
|
+
return s(:true) if node.children[0] == :_
|
356
|
+
|
357
|
+
check_match_var_alternation! node.children[0]
|
358
|
+
|
154
359
|
s(:or,
|
155
360
|
s(:lvasgn, node.children[0], left),
|
156
|
-
s(:true))
|
361
|
+
s(:true))
|
157
362
|
end
|
158
363
|
|
159
|
-
def pin_clause(node)
|
160
|
-
|
364
|
+
def pin_clause(node, right = s(:lvar, locals[:matchee]))
|
365
|
+
predicates.terminate!
|
366
|
+
case_eq_clause node.children[0], right
|
161
367
|
end
|
162
368
|
|
163
369
|
def case_eq_clause(node, right = s(:lvar, locals[:matchee]))
|
370
|
+
predicates.terminate!
|
164
371
|
s(:send,
|
165
|
-
node, :===, right)
|
372
|
+
process(node), :===, right)
|
166
373
|
end
|
167
374
|
|
168
375
|
#=========== ARRAY PATTERN (START) ===============
|
169
376
|
|
170
377
|
def array_pattern_clause(node, matchee = s(:lvar, locals[:matchee]))
|
171
378
|
deconstruct_node(matchee).then do |dnode|
|
379
|
+
size_check = nil
|
380
|
+
# if there is no rest or tail, match the size first
|
381
|
+
unless node.type == :array_pattern_with_tail || node.children.any? { |n| n.type == :match_rest }
|
382
|
+
size_check = predicates.array_size(
|
383
|
+
s(:send,
|
384
|
+
node.children.size.to_ast_node,
|
385
|
+
:==,
|
386
|
+
s(:send, s(:lvar, locals[:arr]), :size)),
|
387
|
+
node.children.size
|
388
|
+
)
|
389
|
+
end
|
390
|
+
|
172
391
|
right =
|
173
392
|
if node.children.empty?
|
174
393
|
case_eq_clause(s(:array), s(:lvar, locals[:arr]))
|
@@ -176,49 +395,36 @@ module RubyNext
|
|
176
395
|
array_element(0, *node.children)
|
177
396
|
end
|
178
397
|
|
179
|
-
|
180
|
-
next right if dnode.nil?
|
181
|
-
|
182
|
-
# if there is no rest or tail, match the size first
|
183
|
-
unless node.type == :array_pattern_with_tail || node.children.any? { |n| n.type == :match_rest }
|
184
|
-
right =
|
185
|
-
s(:and,
|
186
|
-
s(:send,
|
187
|
-
node.children.size.to_ast_node,
|
188
|
-
:==,
|
189
|
-
s(:send, s(:lvar, locals[:arr]), :size)),
|
190
|
-
right)
|
191
|
-
end
|
398
|
+
right = s(:and, size_check, right) if size_check
|
192
399
|
|
193
400
|
s(:and,
|
194
401
|
dnode,
|
195
402
|
right)
|
196
|
-
end.then do |right|
|
197
|
-
s(:and,
|
198
|
-
respond_to_check(matchee, :deconstruct),
|
199
|
-
right)
|
200
403
|
end
|
201
404
|
end
|
202
405
|
|
203
406
|
alias array_pattern_with_tail_clause array_pattern_clause
|
204
407
|
|
205
408
|
def deconstruct_node(matchee)
|
206
|
-
# only deconstruct once per case
|
207
|
-
return if deconstructed&.include?(locals[:arr])
|
208
|
-
|
209
409
|
context.use_ruby_next!
|
210
410
|
|
411
|
+
# we do not memoize respond_to_check for arrays, 'cause
|
412
|
+
# we can memoize is together with #deconstruct result
|
413
|
+
respond_check = respond_to_check(matchee, :deconstruct)
|
211
414
|
right = s(:send, matchee, :deconstruct)
|
212
415
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
416
|
+
predicates.array_deconstructed(
|
417
|
+
s(:and,
|
418
|
+
respond_check,
|
419
|
+
s(:and,
|
420
|
+
s(:or,
|
421
|
+
s(:lvasgn, locals[:arr], right),
|
422
|
+
s(:true)),
|
423
|
+
s(:or,
|
424
|
+
s(:send,
|
425
|
+
s(:const, nil, :Array), :===, s(:lvar, locals[:arr])),
|
426
|
+
raise_error(:TypeError, "#deconstruct must return Array"))))
|
427
|
+
)
|
222
428
|
end
|
223
429
|
|
224
430
|
def array_element(index, head, *tail)
|
@@ -237,6 +443,7 @@ module RubyNext
|
|
237
443
|
child = node.children[0]
|
238
444
|
rest = arr_rest_items(index, tail.size).then do |r|
|
239
445
|
next r unless child
|
446
|
+
|
240
447
|
match_var_clause(
|
241
448
|
child,
|
242
449
|
r
|
@@ -263,14 +470,16 @@ module RubyNext
|
|
263
470
|
def array_pattern_array_element(node, index)
|
264
471
|
element = arr_item_at(index)
|
265
472
|
locals.with(arr: locals[:arr, index]) do
|
266
|
-
|
473
|
+
predicates.push :"i#{index}"
|
474
|
+
array_pattern_clause(node, element).tap { predicates.pop }
|
267
475
|
end
|
268
476
|
end
|
269
477
|
|
270
478
|
def hash_pattern_array_element(node, index)
|
271
479
|
element = arr_item_at(index)
|
272
480
|
locals.with(hash: locals[:arr, index]) do
|
273
|
-
|
481
|
+
predicates.push :"i#{index}"
|
482
|
+
hash_pattern_clause(node, element).tap { predicates.pop }
|
274
483
|
end
|
275
484
|
end
|
276
485
|
|
@@ -318,29 +527,42 @@ module RubyNext
|
|
318
527
|
# (we use #dup and #delete when "reading" values when **rest is present
|
319
528
|
# to assign the rest of the hash copy to it)
|
320
529
|
@hash_match_rest = node.children.any? { |child| child.type == :match_rest || child.type == :match_nil_pattern }
|
321
|
-
keys =
|
530
|
+
keys = hash_pattern_destruction_keys(node.children)
|
531
|
+
|
532
|
+
specified_key_names = hash_pattern_keys(node.children)
|
322
533
|
|
323
534
|
deconstruct_keys_node(keys, matchee).then do |dnode|
|
324
535
|
right =
|
325
536
|
if node.children.empty?
|
326
537
|
case_eq_clause(s(:hash), s(:lvar, locals[:hash]))
|
327
|
-
|
538
|
+
elsif specified_key_names.empty?
|
328
539
|
hash_element(*node.children)
|
540
|
+
else
|
541
|
+
s(:and,
|
542
|
+
having_hash_keys(specified_key_names),
|
543
|
+
hash_element(*node.children))
|
329
544
|
end
|
330
545
|
|
546
|
+
predicates.pop
|
547
|
+
|
331
548
|
next dnode if right.nil?
|
332
549
|
|
333
550
|
s(:and,
|
334
551
|
dnode,
|
335
552
|
right)
|
336
|
-
end.then do |right|
|
337
|
-
s(:and,
|
338
|
-
respond_to_check(matchee, :deconstruct_keys),
|
339
|
-
right)
|
340
553
|
end
|
341
554
|
end
|
342
555
|
|
343
556
|
def hash_pattern_keys(children)
|
557
|
+
children.filter_map do |child|
|
558
|
+
# Skip ** without var
|
559
|
+
next if child.type == :match_rest || child.type == :match_nil_pattern
|
560
|
+
|
561
|
+
send("#{child.type}_hash_key", child)
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
def hash_pattern_destruction_keys(children)
|
344
566
|
return s(:nil) if children.empty?
|
345
567
|
|
346
568
|
children.filter_map do |child|
|
@@ -357,51 +579,73 @@ module RubyNext
|
|
357
579
|
end
|
358
580
|
|
359
581
|
def match_var_hash_key(node)
|
582
|
+
check_match_var_alternation! node.children[0]
|
583
|
+
|
360
584
|
s(:sym, node.children[0])
|
361
585
|
end
|
362
586
|
|
363
587
|
def deconstruct_keys_node(keys, matchee = s(:lvar, locals[:matchee]))
|
364
|
-
#
|
588
|
+
# Use original hash returned by #deconstruct_keys if not **rest matching,
|
589
|
+
# 'cause it remains immutable
|
590
|
+
deconstruct_name = @hash_match_rest ? locals[:hash, :src] : locals[:hash]
|
591
|
+
|
592
|
+
# Duplicate the source hash when matching **rest, 'cause we mutate it
|
365
593
|
hash_dup =
|
366
594
|
if @hash_match_rest
|
367
595
|
s(:lvasgn, locals[:hash], s(:send, s(:lvar, locals[:hash, :src]), :dup))
|
368
596
|
else
|
369
|
-
s(:
|
597
|
+
s(:true)
|
370
598
|
end
|
371
599
|
|
372
|
-
# Create a copy of the original hash if already deconstructed
|
373
|
-
# FIXME: need better algorithm to handle different key sets
|
374
|
-
# return hash_dup if deconstructed&.include?(locals[:hash])
|
375
|
-
|
376
600
|
context.use_ruby_next!
|
377
601
|
|
378
|
-
|
602
|
+
respond_to_checked = predicates.pred?(:respond_to_deconstruct_keys)
|
603
|
+
respond_check = predicates.respond_to_deconstruct_keys(respond_to_check(matchee, :deconstruct_keys))
|
379
604
|
|
380
|
-
|
381
|
-
|
605
|
+
key_names = keys.children.map { |node| node.children.last }
|
606
|
+
predicates.push locals[:hash]
|
607
|
+
|
608
|
+
s(:lvasgn, deconstruct_name,
|
609
|
+
s(:send,
|
610
|
+
matchee, :deconstruct_keys, keys)).then do |dnode|
|
611
|
+
next dnode if respond_to_checked
|
612
|
+
|
613
|
+
s(:and,
|
614
|
+
respond_check,
|
615
|
+
s(:and,
|
616
|
+
s(:or,
|
617
|
+
dnode,
|
618
|
+
s(:true)),
|
619
|
+
s(:or,
|
620
|
+
s(:send,
|
621
|
+
s(:const, nil, :Hash), :===, s(:lvar, deconstruct_name)),
|
622
|
+
raise_error(:TypeError, "#deconstruct_keys must return Hash"))))
|
623
|
+
end.then do |dnode|
|
624
|
+
predicates.hash_deconstructed(dnode, key_names)
|
625
|
+
end.then do |dnode|
|
626
|
+
next dnode unless @hash_match_rest
|
382
627
|
|
383
|
-
s(:and,
|
384
|
-
s(:or,
|
385
|
-
s(:lvasgn, locals[:hash, :src], right),
|
386
|
-
s(:true)), # rubocop:disable Lint/BooleanSymbol
|
387
628
|
s(:and,
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
hash_dup))
|
629
|
+
dnode,
|
630
|
+
hash_dup)
|
631
|
+
end
|
392
632
|
end
|
393
633
|
|
394
634
|
def hash_pattern_hash_element(node, key)
|
395
635
|
element = hash_value_at(key)
|
396
|
-
|
397
|
-
|
636
|
+
key_index = deconstructed_key(key)
|
637
|
+
locals.with(hash: locals[:hash, key_index]) do
|
638
|
+
predicates.push :"k#{key_index}"
|
639
|
+
hash_pattern_clause(node, element).tap { predicates.pop }
|
398
640
|
end
|
399
641
|
end
|
400
642
|
|
401
643
|
def array_pattern_hash_element(node, key)
|
402
644
|
element = hash_value_at(key)
|
403
|
-
|
404
|
-
|
645
|
+
key_index = deconstructed_key(key)
|
646
|
+
locals.with(arr: locals[:hash, key_index]) do
|
647
|
+
predicates.push :"k#{key_index}"
|
648
|
+
array_pattern_clause(node, element).tap { predicates.pop }
|
405
649
|
end
|
406
650
|
end
|
407
651
|
|
@@ -436,16 +680,17 @@ module RubyNext
|
|
436
680
|
s(:and,
|
437
681
|
s(:or,
|
438
682
|
element_node,
|
439
|
-
s(:true)),
|
683
|
+
s(:true)),
|
440
684
|
s(:or, *children))
|
441
685
|
end
|
442
686
|
|
687
|
+
def match_as_hash_element(node, key)
|
688
|
+
match_as_clause(node, hash_value_at(key))
|
689
|
+
end
|
690
|
+
|
443
691
|
def match_var_hash_element(node, key = nil)
|
444
692
|
key ||= node.children[0]
|
445
|
-
|
446
|
-
s(:and,
|
447
|
-
hash_has_key(key),
|
448
|
-
match_var_clause(node, hash_value_at(key)))
|
693
|
+
match_var_clause(node, hash_value_at(key))
|
449
694
|
end
|
450
695
|
|
451
696
|
def match_nil_pattern_hash_element(node, _key = nil)
|
@@ -489,6 +734,17 @@ module RubyNext
|
|
489
734
|
key.to_ast_node)
|
490
735
|
end
|
491
736
|
|
737
|
+
def having_hash_keys(keys, hash = s(:lvar, locals[:hash]))
|
738
|
+
key = keys.shift
|
739
|
+
node = predicates.hash_key(hash_has_key(key, hash), key)
|
740
|
+
|
741
|
+
keys.reduce(node) do |res, key|
|
742
|
+
s(:and,
|
743
|
+
res,
|
744
|
+
predicates.hash_key(hash_has_key(key, hash), key))
|
745
|
+
end
|
746
|
+
end
|
747
|
+
|
492
748
|
#=========== HASH PATTERN (END) ===============
|
493
749
|
|
494
750
|
def with_guard(node, guard)
|
@@ -516,6 +772,7 @@ module RubyNext
|
|
516
772
|
msg.to_ast_node)
|
517
773
|
end
|
518
774
|
|
775
|
+
# Add respond_to? check
|
519
776
|
def respond_to_check(node, mid)
|
520
777
|
s(:send, node, :respond_to?, mid.to_ast_node)
|
521
778
|
end
|
@@ -526,7 +783,7 @@ module RubyNext
|
|
526
783
|
end
|
527
784
|
|
528
785
|
def method_missing(mid, *args, &block)
|
529
|
-
return case_eq_clause(args
|
786
|
+
return case_eq_clause(*args) if mid.match?(/_clause$/)
|
530
787
|
return case_eq_array_element(*args) if mid.match?(/_array_element$/)
|
531
788
|
return case_eq_hash_element(*args) if mid.match?(/_hash_element$/)
|
532
789
|
super
|
@@ -534,7 +791,23 @@ module RubyNext
|
|
534
791
|
|
535
792
|
private
|
536
793
|
|
537
|
-
attr_reader :
|
794
|
+
attr_reader :deconstructed_keys, :predicates
|
795
|
+
|
796
|
+
# Raise SyntaxError if match-var is used within alternation
|
797
|
+
# https://github.com/ruby/ruby/blob/672213ef1ca2b71312084057e27580b340438796/compile.c#L5900
|
798
|
+
def check_match_var_alternation!(name)
|
799
|
+
return unless locals.key?(ALTERNATION_MARKER)
|
800
|
+
|
801
|
+
return if name.start_with?("_")
|
802
|
+
|
803
|
+
raise ::SyntaxError, "illegal variable in alternative pattern (#{name})"
|
804
|
+
end
|
805
|
+
|
806
|
+
def deconstructed_key(key)
|
807
|
+
return deconstructed_keys[key] if deconstructed_keys.key?(key)
|
808
|
+
|
809
|
+
deconstructed_keys[key] = :"k#{deconstructed_keys.size}"
|
810
|
+
end
|
538
811
|
end
|
539
812
|
end
|
540
813
|
end
|
data/lib/ruby-next/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-next-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vladimir Dementyev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-03-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: parser
|
@@ -76,6 +76,8 @@ files:
|
|
76
76
|
- lib/ruby-next/core/string/split.rb
|
77
77
|
- lib/ruby-next/core/struct/deconstruct.rb
|
78
78
|
- lib/ruby-next/core/struct/deconstruct_keys.rb
|
79
|
+
- lib/ruby-next/core/symbol/end_with.rb
|
80
|
+
- lib/ruby-next/core/symbol/start_with.rb
|
79
81
|
- lib/ruby-next/core/time/ceil.rb
|
80
82
|
- lib/ruby-next/core/time/floor.rb
|
81
83
|
- lib/ruby-next/core/unboundmethod/bind_call.rb
|