ruby-next-core 0.8.0 → 0.10.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +47 -0
- data/README.md +85 -11
- data/bin/transform +9 -1
- data/lib/.rbnext/2.3/ruby-next/commands/core_ext.rb +167 -0
- data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +198 -0
- data/lib/.rbnext/2.3/ruby-next/language/eval.rb +66 -0
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +121 -0
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/endless_range.rb +63 -0
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/pattern_matching.rb +944 -0
- data/lib/.rbnext/2.3/ruby-next/utils.rb +65 -0
- data/lib/ruby-next.rb +8 -5
- data/lib/ruby-next/cli.rb +2 -2
- data/lib/ruby-next/commands/core_ext.rb +2 -2
- data/lib/ruby-next/commands/nextify.rb +64 -22
- data/lib/ruby-next/core.rb +39 -22
- data/lib/ruby-next/core/array/deconstruct.rb +9 -9
- data/lib/ruby-next/core/array/difference_union_intersection.rb +12 -12
- data/lib/ruby-next/core/constants/no_matching_pattern_error.rb +3 -3
- data/lib/ruby-next/core/enumerable/filter.rb +8 -8
- data/lib/ruby-next/core/enumerable/filter_map.rb +25 -25
- data/lib/ruby-next/core/enumerable/tally.rb +7 -7
- data/lib/ruby-next/core/enumerator/produce.rb +12 -12
- data/lib/ruby-next/core/hash/deconstruct_keys.rb +9 -9
- data/lib/ruby-next/core/hash/except.rb +11 -0
- data/lib/ruby-next/core/hash/merge.rb +8 -8
- data/lib/ruby-next/core/kernel/then.rb +2 -2
- data/lib/ruby-next/core/proc/compose.rb +11 -11
- data/lib/ruby-next/core/string/split.rb +6 -6
- data/lib/ruby-next/core/struct/deconstruct.rb +2 -2
- data/lib/ruby-next/core/struct/deconstruct_keys.rb +17 -17
- data/lib/ruby-next/core/symbol/end_with.rb +4 -4
- data/lib/ruby-next/core/symbol/start_with.rb +4 -4
- data/lib/ruby-next/core/time/ceil.rb +6 -5
- data/lib/ruby-next/core/time/floor.rb +4 -4
- data/lib/ruby-next/core/unboundmethod/bind_call.rb +4 -4
- data/lib/ruby-next/core_ext.rb +2 -2
- data/lib/ruby-next/language.rb +31 -5
- data/lib/ruby-next/language/eval.rb +10 -8
- data/lib/ruby-next/language/proposed.rb +3 -0
- data/lib/ruby-next/language/rewriters/args_forward.rb +24 -20
- data/lib/ruby-next/language/rewriters/base.rb +2 -2
- data/lib/ruby-next/language/rewriters/endless_method.rb +26 -3
- data/lib/ruby-next/language/rewriters/endless_range.rb +1 -0
- data/lib/ruby-next/language/rewriters/find_pattern.rb +44 -0
- data/lib/ruby-next/language/rewriters/method_reference.rb +2 -1
- data/lib/ruby-next/language/rewriters/numbered_params.rb +1 -0
- data/lib/ruby-next/language/rewriters/pattern_matching.rb +105 -13
- data/lib/ruby-next/language/rewriters/right_hand_assignment.rb +2 -1
- data/lib/ruby-next/language/rewriters/runtime.rb +6 -0
- data/lib/ruby-next/language/rewriters/runtime/dir.rb +32 -0
- data/lib/ruby-next/language/rewriters/safe_navigation.rb +87 -0
- data/lib/ruby-next/language/rewriters/shorthand_hash.rb +47 -0
- data/lib/ruby-next/language/rewriters/squiggly_heredoc.rb +36 -0
- data/lib/ruby-next/language/runtime.rb +3 -2
- data/lib/ruby-next/logging.rb +1 -1
- data/lib/ruby-next/rubocop.rb +15 -9
- data/lib/ruby-next/setup_self.rb +22 -0
- data/lib/ruby-next/utils.rb +30 -0
- data/lib/ruby-next/version.rb +1 -1
- data/lib/uby-next.rb +8 -4
- metadata +22 -7
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyNext
|
4
|
+
module Utils
|
5
|
+
module_function
|
6
|
+
|
7
|
+
if $LOAD_PATH.respond_to?(:resolve_feature_path)
|
8
|
+
def resolve_feature_path(feature)
|
9
|
+
((!$LOAD_PATH.resolve_feature_path(feature).nil?) || nil) && $LOAD_PATH.resolve_feature_path(feature).last
|
10
|
+
rescue LoadError
|
11
|
+
end
|
12
|
+
else
|
13
|
+
def resolve_feature_path(path)
|
14
|
+
if File.file?(relative = File.expand_path(path))
|
15
|
+
path = relative
|
16
|
+
end
|
17
|
+
|
18
|
+
path = "#{path}.rb" if File.extname(path).empty?
|
19
|
+
|
20
|
+
return path if Pathname.new(path).absolute?
|
21
|
+
|
22
|
+
$LOAD_PATH.find do |lp|
|
23
|
+
lpath = File.join(lp, path)
|
24
|
+
return File.realpath(lpath) if File.file?(lpath)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def source_with_lines(source, path)
|
30
|
+
source.lines.map.with_index do |line, i|
|
31
|
+
"#{(i + 1).to_s.rjust(4)}: #{line}"
|
32
|
+
end.tap do |lines|
|
33
|
+
lines.unshift " 0: # source: #{path}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns true if modules refinement is supported in current version
|
38
|
+
def refine_modules?
|
39
|
+
@refine_modules ||=
|
40
|
+
begin
|
41
|
+
# Make sure that including modules within refinements works
|
42
|
+
# See https://github.com/oracle/truffleruby/issues/2026
|
43
|
+
eval <<-RUBY, TOPLEVEL_BINDING, __FILE__, __LINE__ + 1
|
44
|
+
module RubyNext::Utils::A; end
|
45
|
+
class RubyNext::Utils::B
|
46
|
+
include RubyNext::Utils::A
|
47
|
+
end
|
48
|
+
using(Module.new do
|
49
|
+
refine RubyNext::Utils::A do
|
50
|
+
include(Module.new do
|
51
|
+
def i_am_refinement
|
52
|
+
"yes, you are!"
|
53
|
+
end
|
54
|
+
end)
|
55
|
+
end
|
56
|
+
end)
|
57
|
+
RubyNext::Utils::B.new.i_am_refinement
|
58
|
+
RUBY
|
59
|
+
true
|
60
|
+
rescue TypeError, NoMethodError
|
61
|
+
false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/ruby-next.rb
CHANGED
@@ -4,17 +4,18 @@ require "ruby-next/version"
|
|
4
4
|
|
5
5
|
module RubyNext
|
6
6
|
# Mininum Ruby version supported by RubyNext
|
7
|
-
MIN_SUPPORTED_VERSION = Gem::Version.new("2.
|
7
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("2.2.0")
|
8
8
|
|
9
9
|
# Where to store transpiled files (relative from the project LOAD_PATH, usually `lib/`)
|
10
10
|
RUBY_NEXT_DIR = ".rbnext"
|
11
11
|
|
12
12
|
# Defines last minor version for every major version
|
13
13
|
LAST_MINOR_VERSIONS = {
|
14
|
-
2 => 8
|
14
|
+
2 => 8, # 2.8 is required for backward compatibility: some gems already uses it
|
15
|
+
3 => 0
|
15
16
|
}.freeze
|
16
17
|
|
17
|
-
LATEST_VERSION = [
|
18
|
+
LATEST_VERSION = [3, 0].freeze
|
18
19
|
|
19
20
|
class << self
|
20
21
|
def next_version(version = RUBY_VERSION)
|
@@ -33,6 +34,8 @@ module RubyNext
|
|
33
34
|
end
|
34
35
|
end
|
35
36
|
|
36
|
-
|
37
|
-
|
37
|
+
require "ruby-next/setup_self"
|
38
|
+
require "ruby-next/core"
|
39
|
+
require "ruby-next/core_ext" if RubyNext::Core.core_ext?
|
40
|
+
require "ruby-next/logging"
|
38
41
|
end
|
data/lib/ruby-next/cli.rb
CHANGED
@@ -61,7 +61,7 @@ module RubyNext
|
|
61
61
|
def maybe_print_help
|
62
62
|
return unless @print_help
|
63
63
|
|
64
|
-
|
64
|
+
$stdout.puts optparser.help
|
65
65
|
exit 0
|
66
66
|
end
|
67
67
|
|
@@ -85,7 +85,7 @@ module RubyNext
|
|
85
85
|
opts.banner = "Usage: ruby-next COMMAND [options]"
|
86
86
|
|
87
87
|
opts.on("-v", "--version", "Print version") do
|
88
|
-
|
88
|
+
$stdout.puts RubyNext::VERSION
|
89
89
|
exit 0
|
90
90
|
end
|
91
91
|
|
@@ -69,7 +69,7 @@ module RubyNext
|
|
69
69
|
RubyNext::Core.patches.extensions
|
70
70
|
.values
|
71
71
|
.flatten
|
72
|
-
.
|
72
|
+
.select do |patch|
|
73
73
|
next if min_version && Gem::Version.new(patch.version) <= min_version
|
74
74
|
next if filter && !filter.match?(patch.name)
|
75
75
|
true
|
@@ -160,7 +160,7 @@ module RubyNext
|
|
160
160
|
# remove empty lines
|
161
161
|
new_src.gsub!(/^\s+$/, "")
|
162
162
|
# remove traling blank lines
|
163
|
-
new_src.
|
163
|
+
new_src.delete_suffix!("\n")
|
164
164
|
new_src
|
165
165
|
end
|
166
166
|
end
|
@@ -10,12 +10,16 @@ module RubyNext
|
|
10
10
|
class Nextify < Base
|
11
11
|
using RubyNext
|
12
12
|
|
13
|
-
attr_reader :lib_path, :paths, :out_path, :min_version, :single_version
|
13
|
+
attr_reader :lib_path, :paths, :out_path, :min_version, :single_version, :specified_rewriters
|
14
14
|
|
15
15
|
def run
|
16
16
|
log "RubyNext core strategy: #{RubyNext::Core.strategy}"
|
17
17
|
log "RubyNext transpile mode: #{RubyNext::Language.mode}"
|
18
18
|
|
19
|
+
remove_rbnext!
|
20
|
+
|
21
|
+
@min_version ||= MIN_SUPPORTED_VERSION
|
22
|
+
|
19
23
|
paths.each do |path|
|
20
24
|
contents = File.read(path)
|
21
25
|
transpile path, contents
|
@@ -24,7 +28,8 @@ module RubyNext
|
|
24
28
|
|
25
29
|
def parse!(args)
|
26
30
|
print_help = false
|
27
|
-
|
31
|
+
print_rewriters = false
|
32
|
+
rewriter_names = []
|
28
33
|
@single_version = false
|
29
34
|
|
30
35
|
optparser = base_parser do |opts|
|
@@ -61,6 +66,14 @@ module RubyNext
|
|
61
66
|
Core.strategy = :core_ext unless val
|
62
67
|
end
|
63
68
|
|
69
|
+
opts.on("--list-rewriters", "List available rewriters") do |val|
|
70
|
+
print_rewriters = true
|
71
|
+
end
|
72
|
+
|
73
|
+
opts.on("--rewrite=REWRITERS...", "Specify particular Ruby features to rewrite") do |val|
|
74
|
+
rewriter_names << val
|
75
|
+
end
|
76
|
+
|
64
77
|
opts.on("-h", "--help", "Print help") do
|
65
78
|
print_help = true
|
66
79
|
end
|
@@ -75,12 +88,35 @@ module RubyNext
|
|
75
88
|
exit 0
|
76
89
|
end
|
77
90
|
|
91
|
+
if print_rewriters
|
92
|
+
Language.rewriters.each do |rewriter|
|
93
|
+
$stdout.puts "#{rewriter::NAME} (\"#{rewriter::SYNTAX_PROBE}\")"
|
94
|
+
end
|
95
|
+
exit 0
|
96
|
+
end
|
97
|
+
|
78
98
|
unless lib_path&.then(&File.method(:exist?))
|
79
99
|
$stdout.puts "Path not found: #{lib_path}"
|
80
100
|
$stdout.puts optparser.help
|
81
101
|
exit 2
|
82
102
|
end
|
83
103
|
|
104
|
+
if rewriter_names.any? && min_version
|
105
|
+
$stdout.puts "--rewrite cannot be used with --min-version simultaneously"
|
106
|
+
exit 2
|
107
|
+
end
|
108
|
+
|
109
|
+
@specified_rewriters =
|
110
|
+
if rewriter_names.any?
|
111
|
+
begin
|
112
|
+
Language.select_rewriters(*rewriter_names)
|
113
|
+
rescue Language::RewriterNotFoundError => error
|
114
|
+
$stdout.puts error.message
|
115
|
+
$stdout.puts "Try --list-rewriters to see list of available rewriters"
|
116
|
+
exit 2
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
84
120
|
@paths =
|
85
121
|
if File.directory?(lib_path)
|
86
122
|
Dir[File.join(lib_path, "**/*.rb")]
|
@@ -94,16 +130,11 @@ module RubyNext
|
|
94
130
|
private
|
95
131
|
|
96
132
|
def transpile(path, contents, version: min_version)
|
97
|
-
rewriters = Language.rewriters.select { |rw| rw.unsupported_version?(version) }
|
133
|
+
rewriters = specified_rewriters || Language.rewriters.select { |rw| rw.unsupported_version?(version) }
|
98
134
|
|
99
135
|
context = Language::TransformContext.new
|
100
136
|
|
101
|
-
new_contents =
|
102
|
-
if Gem::Version.new(version) >= Gem::Version.new("2.7.0") && !defined?(Unparser::Emitter::CaseMatch)
|
103
|
-
Language.rewrite contents, context: context, rewriters: rewriters
|
104
|
-
else
|
105
|
-
Language.transform contents, context: context, rewriters: rewriters
|
106
|
-
end
|
137
|
+
new_contents = Language.transform contents, context: context, rewriters: rewriters
|
107
138
|
|
108
139
|
return unless context.dirty?
|
109
140
|
|
@@ -120,25 +151,17 @@ module RubyNext
|
|
120
151
|
end
|
121
152
|
|
122
153
|
def save(contents, path, version)
|
123
|
-
return $stdout.puts(contents) if
|
154
|
+
return $stdout.puts(contents) if stdout?
|
124
155
|
|
125
156
|
paths = [Pathname.new(path).relative_path_from(Pathname.new(lib_path))]
|
126
157
|
|
127
158
|
paths.unshift(version.segments[0..1].join(".")) unless single_version?
|
128
159
|
|
129
160
|
next_path =
|
130
|
-
if
|
131
|
-
|
132
|
-
out_path
|
133
|
-
else
|
134
|
-
File.join(out_path, *paths)
|
135
|
-
end
|
161
|
+
if next_dir_path.end_with?(".rb")
|
162
|
+
out_path
|
136
163
|
else
|
137
|
-
File.join(
|
138
|
-
lib_path,
|
139
|
-
RUBY_NEXT_DIR,
|
140
|
-
*paths
|
141
|
-
)
|
164
|
+
File.join(next_dir_path, *paths)
|
142
165
|
end
|
143
166
|
|
144
167
|
unless CLI.dry_run?
|
@@ -150,7 +173,26 @@ module RubyNext
|
|
150
173
|
log "Generated: #{next_path}"
|
151
174
|
end
|
152
175
|
|
153
|
-
|
176
|
+
def remove_rbnext!
|
177
|
+
return if CLI.dry_run? || stdout?
|
178
|
+
|
179
|
+
return unless File.directory?(next_dir_path)
|
180
|
+
|
181
|
+
log "Remove old files: #{next_dir_path}"
|
182
|
+
FileUtils.rm_r(next_dir_path)
|
183
|
+
end
|
184
|
+
|
185
|
+
def next_dir_path
|
186
|
+
@next_dir_path ||= (out_path || File.join(lib_path, RUBY_NEXT_DIR))
|
187
|
+
end
|
188
|
+
|
189
|
+
def stdout?
|
190
|
+
out_path == "stdout"
|
191
|
+
end
|
192
|
+
|
193
|
+
def single_version?
|
194
|
+
single_version || specified_rewriters
|
195
|
+
end
|
154
196
|
end
|
155
197
|
end
|
156
198
|
end
|
data/lib/ruby-next/core.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
require "set"
|
4
4
|
|
5
|
+
require "ruby-next/utils"
|
6
|
+
|
5
7
|
module RubyNext
|
6
8
|
module Core
|
7
9
|
# Patch contains the extension implementation
|
@@ -23,7 +25,7 @@ module RubyNext
|
|
23
25
|
@supported = supported.nil? ? mod.method_defined?(method_name) : supported
|
24
26
|
# define whether running Ruby has a native implementation for this method
|
25
27
|
# for that, we check the source_location (which is nil for C defined methods)
|
26
|
-
@native = native.nil? ? (supported? && mod.instance_method(method_name).source_location
|
28
|
+
@native = native.nil? ? (supported? && native_location?(mod.instance_method(method_name).source_location)) : native
|
27
29
|
end
|
28
30
|
@singleton = singleton
|
29
31
|
@refineables = Array(refineable)
|
@@ -73,6 +75,10 @@ module RubyNext
|
|
73
75
|
|
74
76
|
[trace_location.absolute_path, trace_location.lineno + 2]
|
75
77
|
end
|
78
|
+
|
79
|
+
def native_location?(location)
|
80
|
+
location.nil? || location.first.match?(/(<internal:|resource:\/truffleruby\/core)/)
|
81
|
+
end
|
76
82
|
end
|
77
83
|
|
78
84
|
# Registry for patches
|
@@ -95,7 +101,7 @@ module RubyNext
|
|
95
101
|
end
|
96
102
|
|
97
103
|
class << self
|
98
|
-
STRATEGIES = %i[refine core_ext].freeze
|
104
|
+
STRATEGIES = %i[refine core_ext backports].freeze
|
99
105
|
|
100
106
|
attr_reader :strategy
|
101
107
|
|
@@ -109,7 +115,11 @@ module RubyNext
|
|
109
115
|
end
|
110
116
|
|
111
117
|
def core_ext?
|
112
|
-
strategy == :core_ext
|
118
|
+
strategy == :core_ext || strategy == :backports
|
119
|
+
end
|
120
|
+
|
121
|
+
def backports?
|
122
|
+
strategy == :backports
|
113
123
|
end
|
114
124
|
|
115
125
|
def patch(*args, **kwargs, &block)
|
@@ -136,29 +146,31 @@ module RubyNext
|
|
136
146
|
end
|
137
147
|
end
|
138
148
|
|
139
|
-
|
149
|
+
require "backports/2.5" if RubyNext::Core.backports?
|
150
|
+
|
151
|
+
require "ruby-next/core/kernel/then"
|
140
152
|
|
141
|
-
|
153
|
+
require "ruby-next/core/proc/compose"
|
142
154
|
|
143
|
-
|
144
|
-
|
145
|
-
|
155
|
+
require "ruby-next/core/enumerable/tally"
|
156
|
+
require "ruby-next/core/enumerable/filter"
|
157
|
+
require "ruby-next/core/enumerable/filter_map"
|
146
158
|
|
147
|
-
|
159
|
+
require "ruby-next/core/enumerator/produce"
|
148
160
|
|
149
|
-
|
161
|
+
require "ruby-next/core/array/difference_union_intersection"
|
150
162
|
|
151
|
-
|
163
|
+
require "ruby-next/core/hash/merge"
|
152
164
|
|
153
|
-
|
165
|
+
require "ruby-next/core/string/split"
|
154
166
|
|
155
|
-
|
156
|
-
|
167
|
+
require "ruby-next/core/symbol/start_with"
|
168
|
+
require "ruby-next/core/symbol/end_with"
|
157
169
|
|
158
|
-
|
170
|
+
require "ruby-next/core/unboundmethod/bind_call"
|
159
171
|
|
160
|
-
|
161
|
-
|
172
|
+
require "ruby-next/core/time/floor"
|
173
|
+
require "ruby-next/core/time/ceil"
|
162
174
|
|
163
175
|
# Core extensions required for pattern matching
|
164
176
|
# Required for pattern matching with refinements
|
@@ -167,15 +179,20 @@ unless defined?(NoMatchingPatternError)
|
|
167
179
|
end
|
168
180
|
end
|
169
181
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
182
|
+
require "ruby-next/core/constants/no_matching_pattern_error"
|
183
|
+
require "ruby-next/core/array/deconstruct"
|
184
|
+
require "ruby-next/core/hash/deconstruct_keys"
|
185
|
+
require "ruby-next/core/struct/deconstruct"
|
186
|
+
require "ruby-next/core/struct/deconstruct_keys"
|
187
|
+
|
188
|
+
require "ruby-next/core/hash/except"
|
175
189
|
|
176
190
|
# Generate refinements
|
177
191
|
RubyNext.module_eval do
|
178
192
|
RubyNext::Core.patches.refined.each do |mod, patches|
|
193
|
+
# Only refine modules when supported
|
194
|
+
next unless mod.is_a?(Class) || RubyNext::Utils.refine_modules?
|
195
|
+
|
179
196
|
refine mod do
|
180
197
|
patches.each do |patch|
|
181
198
|
module_eval(patch.body, *patch.location)
|
@@ -1,21 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
RubyNext::Core.patch Array, method: :deconstruct, version: "2.7" do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
<<-RUBY
|
5
|
+
def deconstruct
|
6
|
+
self
|
7
|
+
end
|
8
8
|
RUBY
|
9
9
|
end
|
10
10
|
|
11
11
|
# We need to hack `respond_to?` in Ruby 2.5, since it's not working with refinements
|
12
12
|
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.6")
|
13
13
|
RubyNext::Core.patch refineable: Array, name: "ArrayRespondToDeconstruct", method: :deconstruct, version: "2.7" do
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
<<-RUBY
|
15
|
+
def respond_to?(mid, *)
|
16
|
+
return true if mid == :deconstruct
|
17
|
+
super
|
18
|
+
end
|
19
19
|
RUBY
|
20
20
|
end
|
21
21
|
end
|
@@ -1,25 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
RubyNext::Core.patch Array, method: :union, version: "2.6" do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
<<-RUBY
|
5
|
+
def union(*others)
|
6
|
+
others.reduce(Array.new(self).uniq) { |acc, arr| acc | arr }
|
7
|
+
end
|
8
8
|
RUBY
|
9
9
|
end
|
10
10
|
|
11
11
|
RubyNext::Core.patch Array, method: :difference, version: "2.6" do
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
<<-RUBY
|
13
|
+
def difference(*others)
|
14
|
+
others.reduce(Array.new(self)) { |acc, arr| acc - arr }
|
15
|
+
end
|
16
16
|
RUBY
|
17
17
|
end
|
18
18
|
|
19
19
|
RubyNext::Core.patch Array, method: :intersection, version: "2.7" do
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
<<-RUBY
|
21
|
+
def intersection(*others)
|
22
|
+
others.reduce(Array.new(self)) { |acc, arr| acc & arr }
|
23
|
+
end
|
24
24
|
RUBY
|
25
25
|
end
|