ruby-next 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '04759bfe463ed9def346c1d880fbf3ba7f465be6dac80ac47022543c4be67796'
4
- data.tar.gz: a437b160eb2cc3418361d14c962f86faef73f3319c1c6faf71c70bb605de460c
3
+ metadata.gz: 96fbef2a02f92547dcc39e30ac93fbe59ff98118a797dce1ca017816030e3353
4
+ data.tar.gz: dd6e45bd8dabbf84ea832f2faa0365b20d4bdc62b9d915fba07cca5a92274a40
5
5
  SHA512:
6
- metadata.gz: bddaa30d7cd6000d3de03eab8e3b8372e119e3b1f73cb2a78820ab289dcf2a6e88976852012340263ab113b1bd2256e9d22ffaaae80e36b2eee34f88f2953e6c
7
- data.tar.gz: a98075ae6d03f4126e2ac2c537d3f86bda82a57e4c446b702e1738fde8c2682442c5731280205ce35e13e96053059a952dd54ce5262456f6d963b9e5dfe815f0
6
+ metadata.gz: d9755fc420eea44ebc477466dba52015c524c769c57b1d85da809d46061693a07059de89e7021b9d18e443fac8ffce504289ec486bc74baf00c3cab43534a338
7
+ data.tar.gz: 1830041a93b1b0ee7409a950ed75dfc90bdc56894ba8b140ebf1c45d18d86981a030799ec52f0ad7e442f3f12f9f060260c52ebfb4d6c00087a4c5d0131e6ecd
data/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.2.0 (2020-01-13) 🎄
6
+
7
+ - Add `Enumerator.produce`. ([@palkan][])
8
+
9
+ - Add Bootsnap integration. ([@palkan][])
10
+
11
+ Add `require "ruby-next/language/bootsnap"` after setting up Bootsnap
12
+ to transpile the files on load (and cache the resulted iseq via Bootsnap as usually).
13
+
14
+ - Do not patch `eval` and friends when using runtime mode. ([@palkan][])
15
+
16
+ Eval support should be enabled explicitly via the `RubyNext::Language::Eval` refinement, 'cause we cannot handle all the edge cases easily (e.g., the usage caller's binding locals).
17
+
18
+ - Revoke method reference support. ([@palkan][])
19
+
20
+ You can still use this feature by enabling it explicitly (see Readme).
21
+
22
+ - Support in modifier. ([@palkan][])
23
+
24
+ ```ruby
25
+ {a:1, b: 2} in {a:, **}
26
+ p a #=> 1
27
+ ```
28
+
5
29
  ## 0.1.0 (2019-11-18)
6
30
 
7
31
  - Support hash pattern in array and vice versa. ([@palkan][])
data/README.md CHANGED
@@ -5,18 +5,22 @@
5
5
 
6
6
  > Make all Rubies quack like edge Ruby!
7
7
 
8
- Ruby Next is a tool for supporting modern/edge CRuby 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][].
8
+ Ruby Next is a collection of **polyfills** and a **transpiler** 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
9
 
10
10
  Who might be interested in Ruby Next?
11
11
 
12
12
  - **Ruby gems maintainers** who want to write code using the latest Ruby version but still support older ones.
13
13
  - **Application developers** who want to give new features a try without waiting for the final release (or, more often, for the first patch).
14
- - **Users of non-MRI implementations** such as [mruby][], [JRuby][], [TruffleRuby][], [Opal][], [Artichoke][], [Prism][].
14
+ - **Users of non-MRI implementations** such as [mruby][], [JRuby][], [TruffleRuby][], [Opal][], [RubyMotion][], [Artichoke][], [Prism][].
15
15
 
16
16
  Ruby Next also aims to help the community to assess new, _experimental_, MRI features by making it easier to play with them.
17
17
  That's why Ruby Next implements the `trunk` features as fast as possible.
18
18
 
19
- _⚡️ The project is in early alpha stage and looking for the first adopters. Main functionality has been implemented (see [the list][features]) but APIs could change in the future._
19
+ _⚡️ The project is in a **beta** phase. That means that the main functionality has been implemented (see [the list][features]) and APIs shouldn't change a lot in the nearest future. On the other hand, the number of users/projects is not enough to say we're "production-ready". So, can't wait to hear your feedback 🙂_
20
+
21
+ ## Links
22
+
23
+ - [Ruby Next: Make old Rubies quack like a new one](https://noti.st/palkan/j3i2Dr/ruby-next-make-old-rubies-quack-like-a-new-one) (RubyConf 2019)
20
24
 
21
25
  ## Overview
22
26
 
@@ -29,6 +33,7 @@ Language is responsible for **transpiling** edge Ruby syntax into older versions
29
33
  programmatically or via CLI. It also could be done in runtime.
30
34
 
31
35
  Currently, Ruby Next supports Ruby versions 2.5+ (including JRuby 9.2.8+).
36
+ Please, [open an issue](https://github.com/ruby-next/ruby-next/issues/new/choose) if you would like us to support older Ruby versions.
32
37
 
33
38
  ## Using only polyfills
34
39
 
@@ -36,12 +41,14 @@ First, install a gem:
36
41
 
37
42
  ```ruby
38
43
  # Gemfile
39
- gem "ruby-next"
44
+ gem "ruby-next-core"
40
45
 
41
46
  # gemspec
42
- spec.add_dependency "ruby-next"
47
+ spec.add_dependency "ruby-next-core"
43
48
  ```
44
49
 
50
+ **NOTE:** we use the different _distribution_ gem, `ruby-next-core`, to provide zero-dependency, polyfills-only version.
51
+
45
52
  Then, all you need is to load the Ruby Next:
46
53
 
47
54
  ```ruby
@@ -60,34 +67,24 @@ Ruby Next only refines core classes if necessary; thus, this line wouldn't have
60
67
 
61
68
  ## Transpiling, or using edge Ruby syntax features
62
69
 
63
- Ruby Next relies on its own version of the [parser][] gem hosted on Github Package Registry. That makes the installation process a bit more complicated than usual.
70
+ Ruby Next transpiler relies on two libraries: [parser][] and [unparser][].
64
71
 
65
- [**The list of supported syntax features.**][features_syntax]
72
+ **NOTE:** The "official" parser gem only supports the latest stable Ruby version, while Ruby Next aims to support edge and experimental Ruby features. To enable them, you should use our version of Parser (see [instructions](#using-ruby-next-parser) below).
66
73
 
67
- ### Installing with Bundler
68
-
69
- First, configure your bundler to access GPR:
70
-
71
- ```sh
72
- bundle config --local https://rubygems.pkg.github.com/ruby-next USERNAME:ACCESS_TOKEN
73
- ```
74
-
75
- Then, add to your Gemfile:
74
+ Installation:
76
75
 
77
76
  ```ruby
78
- source "https://rubygems.pkg.github.com/ruby-next" do
79
- gem "parser", "2.6.3.102"
80
- end
81
-
82
- gem "unparser", "~> 0.4.5"
77
+ # Gemfile
83
78
  gem "ruby-next"
84
- ```
85
79
 
86
- **NOTE:** we don't add `parser` and `unparser` to the gem's runtime deps, 'cause they're not necessary if you only need polyfills.
80
+ # gemspec
81
+ spec.add_dependency "ruby-next"
87
82
 
88
- ### Installing globally via `gem`
83
+ # or install globally
84
+ gem install ruby-next
85
+ ```
89
86
 
90
- Currently unavailable due to the limitations of Github Package Registry. See [issue](https://github.community/t5/GitHub-Actions/Can-t-install-Ruby-gem-after-publishing-to-Github-Package/m-p/35192/thread-id/2126).
87
+ [**The list of supported syntax features.**][features_syntax]
91
88
 
92
89
  ### Integrating into a gem development
93
90
 
@@ -161,14 +158,38 @@ You can also automatically inject `using RubyNext` to every\* loaded file by als
161
158
 
162
159
  Since the runtime mode requires Kernel monkey-patching, it should be used carefully. For example, we use it in Ruby Next tests—works perfectly. But think twice before enabling it in production.
163
160
 
164
- We plan to add [Bootsnap][] integration in the future, which would allow us to avoid monkey-patching (by relying on the bullet-proofed Bootsnap's one 😉).
161
+ Consider using [Bootsnap](#using-with-bootsnap) integration, 'cause its monkey-patching has been bullet-proofed 😉.
165
162
 
166
163
  \* Ruby Next doesn't hijack every required file but _watches_ only the configured directories: `./app/`, `./lib/`, `./spec/`, `./test/` (relative to the `pwd`). You can configure the watch dirs:
167
164
 
168
165
  ```ruby
169
- RubyNext::Language::Runtime.watch_dirs << "path/to/other/dir"
166
+ RubyNext::Language.watch_dirs << "path/to/other/dir"
167
+ ```
168
+
169
+ ### Eval & similar
170
+
171
+ By default, we do not hijack `Kernel.eval` and similar methods due to some limitations (e.g., there is no easy and efficient way to access the caller's scope, or _binding_, and some evaluations relies on local variables).
172
+
173
+ If you want to support transpiling in `eval`-like methods, opt-in explicitly by activating the refinement:
174
+
175
+ ```ruby
176
+ using RubyNext::Language::Eval
177
+ ```
178
+
179
+ ## Using with Bootsnap
180
+
181
+ [Bootsnap][] is a great tool to speed-up your application load and it's included into the default Rails Gemfile. It patches Ruby mechanism of loading source files to make it possible to cache the intermediate representation (_iseq_).
182
+
183
+ Ruby Next provides a specific integration which allows to add a transpiling step to this process, thus making the transpiler overhead as small as possible, because the cached and **already transpiled** version is used if no changes were made.
184
+
185
+ To enable this integration, add the following line after the `require "bootsnap/setup"`:
186
+
187
+ ```ruby
188
+ require "ruby-next/language/bootsnap"
170
189
  ```
171
190
 
191
+ **NOTE:** there is no way to invalidate the cache when you upgrade Ruby Next (e.g., due to the bug fixes), so you should do this manually.
192
+
172
193
  ## `uby-next`
173
194
 
174
195
  You can also enable runtime mode by requiring `uby-next` while running a Ruby executable:
@@ -183,6 +204,55 @@ RUBYOPT="-ruby-next" ruby my_ruby_script.rb
183
204
  ruby -ruby-next -e "puts [2, 4, 5].tally"
184
205
  ```
185
206
 
207
+ ## Unofficial/experimental features
208
+
209
+ Ruby Next also provides support for some features not-yet-merged into Ruby master (or reverted).
210
+
211
+ These features require a [custom parser](#using-ruby-next-parser).
212
+
213
+ Currenly, the only such feature is the [_method reference_ operator](https://bugs.ruby-lang.org/issues/13581):
214
+
215
+ - Add `--enable-method-reference` option to `nextify` command when using CLI.
216
+ - OR add it programmatically when using a runtime mode (see [example](https://github.com/ruby-next/ruby-next/blob/master/default.mspec)).
217
+ - OR set `RUBY_NEXT_ENABLE_METHOD_REFERENCE=1` environment variable (works with CLI as well).
218
+
219
+ ## Using Ruby Next parser
220
+
221
+ ### Prerequisites
222
+
223
+ Our own version of [parser][next_parser] gem is hosted on Github Package Registry. That makes the installation process a bit more complicated than usual.
224
+
225
+ You must obtain an access token to use it. See the [GPR docs](https://help.github.com/en/github/managing-packages-with-github-package-registry/configuring-rubygems-for-use-with-github-package-registry#authenticating-to-github-package-registry).
226
+
227
+ ### Installing with Bundler
228
+
229
+ First, configure your bundler to access GPR:
230
+
231
+ ```sh
232
+ bundle config --local https://rubygems.pkg.github.com/ruby-next USERNAME:ACCESS_TOKEN
233
+ ```
234
+
235
+ Then, add to your Gemfile:
236
+
237
+ ```ruby
238
+ source "https://rubygems.pkg.github.com/ruby-next" do
239
+ gem "parser", "2.7.0.100"
240
+ end
241
+
242
+ gem "ruby-next"
243
+ ```
244
+
245
+ **NOTE:** we don't add `parser` and `unparser` to the gem's runtime deps, 'cause they're not necessary if you only need polyfills.
246
+
247
+ ### Installing globally via `gem`
248
+
249
+ You can install `ruby-next` globally by running the following commands:
250
+
251
+ ```sh
252
+ gem install parser -v "2.7.0.100" --source "https://USERNAME:ACCESS_TOKEN@rubygems.pkg.github.com/ruby-next"
253
+ gem install ruby-next
254
+ ```
255
+
186
256
  ## Contributing
187
257
 
188
258
  Bug reports and pull requests are welcome on GitHub at [https://github.com/ruby-next/ruby-next](ttps://github.com/ruby-next/ruby-next).
@@ -200,7 +270,10 @@ The gem is available as open source under the terms of the [MIT License](https:/
200
270
  [JRuby]: https://www.jruby.org
201
271
  [TruffleRuby]: https://github.com/oracle/truffleruby
202
272
  [Opal]: https://opalrb.com
273
+ [RubyMotion]: http://www.rubymotion.com
203
274
  [Artichoke]: https://github.com/artichoke/artichoke
204
275
  [Prism]: https://github.com/prism-rb/prism
205
276
  [parser]: https://github.com/whitequark/parser
277
+ [unparser]: https://github.com/mbj/unparser
278
+ [next_parser]: https://github.com/ruby-next/parser
206
279
  [Bootsnap]: https://github.com/Shopify/bootsnap
data/bin/parse CHANGED
@@ -15,5 +15,5 @@ contents =
15
15
  ARGV[0]
16
16
  end
17
17
 
18
- ast = RubyNext::Language::Parser.parse(contents)
18
+ ast = RubyNext::Language.parse(contents)
19
19
  puts ast
data/bin/transform CHANGED
@@ -2,12 +2,15 @@
2
2
 
3
3
  lib = File.expand_path("../../lib", __FILE__)
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
- $VERBOSE = nil
6
5
 
7
6
  require "bundler/setup"
8
7
 
9
8
  require "ruby-next/language"
10
9
 
10
+ # optional parsers
11
+ require "ruby-next/language/rewriters/method_reference"
12
+ RubyNext::Language.rewriters << RubyNext::Language::Rewriters::MethodReference
13
+
11
14
  contents =
12
15
  if File.exist?(ARGV[0])
13
16
  File.read(ARGV[0])
@@ -35,6 +35,11 @@ module RubyNext
35
35
  opts.on("--single-version", "Only create one version of a file (for the earliest Ruby version)") do
36
36
  @single_version = true
37
37
  end
38
+
39
+ opts.on("--enable-method-reference", "Enable reverted method reference syntax (requires custom parser)") do
40
+ require "ruby-next/language/rewriters/method_reference"
41
+ Language.rewriters << Language::Rewriters::MethodReference
42
+ end
38
43
  end
39
44
 
40
45
  @lib_path = args[0]
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ unless Enumerator.respond_to?(:produce)
4
+ RubyNext.module_eval do
5
+ refine Enumerator.singleton_class do
6
+ # Based on https://github.com/zverok/enumerator_generate
7
+ def produce(*rest, &block)
8
+ raise ArgumentError, "wrong number of arguments (given #{rest.size}, expected 0..1)" if rest.size > 1
9
+ raise ArgumentError, "No block given" unless block
10
+
11
+ Enumerator.new(Float::INFINITY) do |y|
12
+ val = rest.empty? ? yield() : rest.pop
13
+
14
+ loop do
15
+ y << val
16
+ val = yield(val)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -2,9 +2,9 @@
2
2
 
3
3
  # Extend `Language.transform` to inject `using RubyNext` to every file
4
4
  RubyNext::Language.singleton_class.prepend(Module.new do
5
- def transform(contents, **hargs)
5
+ def transform(contents, using: true, **hargs)
6
6
  # We cannot activate refinements in eval
7
- RubyNext::Core.inject!(contents) unless hargs[:eval]
8
- super(contents, **hargs)
7
+ new_contents = RubyNext::Core.inject!(contents) if using
8
+ super(new_contents || contents, using: using, **hargs)
9
9
  end
10
10
  end)
@@ -5,7 +5,11 @@ module RubyNext
5
5
  class << self
6
6
  # Inject `using RubyNext` at the top of the source code
7
7
  def inject!(contents)
8
- contents.sub!(/^(\s*[^#\s].*)/, 'using RubyNext;\1')
8
+ if contents.frozen?
9
+ contents = contents.sub(/^(\s*[^#\s].*)/, 'using RubyNext;\1')
10
+ else
11
+ contents.sub!(/^(\s*[^#\s].*)/, 'using RubyNext;\1')
12
+ end
9
13
  contents
10
14
  end
11
15
  end
@@ -20,6 +24,8 @@ require_relative "core/enumerable/tally"
20
24
  require_relative "core/enumerable/filter"
21
25
  require_relative "core/enumerable/filter_map"
22
26
 
27
+ require_relative "core/enumerator/produce"
28
+
23
29
  require_relative "core/array/difference_union_intersection"
24
30
 
25
31
  require_relative "core/hash/merge"
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby-next"
4
+ require "ruby-next/utils"
5
+ require "ruby-next/language"
6
+
7
+ # Patch bootsnap to transform source code.
8
+ # Based on https://github.com/kddeisz/preval/blob/master/lib/preval.rb
9
+ load_iseq = RubyVM::InstructionSequence.method(:load_iseq)
10
+
11
+ if load_iseq.source_location[0].include?("/bootsnap/")
12
+ Bootsnap::CompileCache::ISeq.singleton_class.prepend(
13
+ Module.new do
14
+ def input_to_storage(source, path)
15
+ return super unless RubyNext::Language.transformable?(path)
16
+ source = RubyNext::Language.transform(source, rewriters: RubyNext::Language.current_rewriters)
17
+
18
+ $stdout.puts ::RubyNext::Utils.source_with_lines(source, path) if ENV["RUBY_NEXT_DEBUG"] == "1"
19
+
20
+ RubyVM::InstructionSequence.compile(source, path, path).to_binary
21
+ rescue SyntaxError
22
+ raise Bootsnap::CompileCache::Uncompilable, "syntax error"
23
+ end
24
+ end
25
+ )
26
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module KernelEval
6
+ refine Kernel do
7
+ def eval(source, bind = nil, *args)
8
+ new_source = ::RubyNext::Language::Runtime.transform(
9
+ source,
10
+ using: bind&.receiver == TOPLEVEL_BINDING.receiver || bind&.receiver&.is_a?(Module)
11
+ )
12
+ $stdout.puts ::RubyNext::Utils.source_with_lines(new_source, "(#{caller_locations(1, 1).first})") if ENV["RUBY_NEXT_DEBUG"] == "1"
13
+ super new_source, bind, *args
14
+ end
15
+ end
16
+ end
17
+
18
+ module InstanceEval # :nodoc:
19
+ refine Object do
20
+ def instance_eval(*args, &block)
21
+ return super(*args, &block) if block_given?
22
+
23
+ source = args.shift
24
+ new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
25
+ $stdout.puts ::RubyNext::Utils.source_with_lines(new_source, "(#{caller_locations(1, 1).first})") if ENV["RUBY_NEXT_DEBUG"] == "1"
26
+ super new_source, *args
27
+ end
28
+ end
29
+ end
30
+
31
+ module ClassEval
32
+ refine Module do
33
+ def module_eval(*args, &block)
34
+ return super(*args, &block) if block_given?
35
+
36
+ source = args.shift
37
+ new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
38
+ $stdout.puts ::RubyNext::Utils.source_with_lines(new_source, "(#{caller_locations(1, 1).first})") if ENV["RUBY_NEXT_DEBUG"] == "1"
39
+ super new_source, *args
40
+ end
41
+
42
+ def class_eval(*args, &block)
43
+ return super(*args, &block) if block_given?
44
+
45
+ source = args.shift
46
+ new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
47
+ $stdout.puts ::RubyNext::Utils.source_with_lines(new_source, "(#{caller_locations(1, 1).first})") if ENV["RUBY_NEXT_DEBUG"] == "1"
48
+ super new_source, *args
49
+ end
50
+ end
51
+ end
52
+
53
+ # Refinements for `eval`-like methods.
54
+ # Transpiling eval is only possible if we do not use local from the binding,
55
+ # because we cannot access the binding of caller (without non-production ready hacks).
56
+ #
57
+ # This module is meant mainly for testing purposes.
58
+ module Eval
59
+ include InstanceEval
60
+ include ClassEval
61
+ include KernelEval
62
+ end
63
+ end
64
+ end
@@ -1,23 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "ruby-next/utils"
4
-
5
3
  require "parser/ruby27"
6
- RubyNext::Language::Parser = Parser::Ruby27
7
4
 
8
- # See https://github.com/whitequark/parser/#usage
9
- Parser::Builders::Default.emit_lambda = true
10
- Parser::Builders::Default.emit_procarg0 = true
11
- Parser::Builders::Default.emit_encoding = true
12
- Parser::Builders::Default.emit_index = true
5
+ module RubyNext
6
+ module Language
7
+ class Builder < ::Parser::Builders::Default
8
+ modernize
9
+ end
10
+
11
+ class << self
12
+ def parser
13
+ ::Parser::Ruby27.new(Builder.new)
14
+ end
13
15
 
14
- Parser::Builders::Default.prepend(Module.new do
15
- def match_hash_var_from_str(begin_t, strings, end_t)
16
- super.tap do
17
- string = strings[0]
18
- next unless string.type == :str
19
- name, = *string
20
- @parser.static_env.declare(name)
16
+ def parse(source, file = "(string)")
17
+ buffer = ::Parser::Source::Buffer.new(file).tap do |buffer|
18
+ buffer.source = source
19
+ end
20
+ parser.parse(buffer)
21
+ end
21
22
  end
22
23
  end
23
- end)
24
+ end
@@ -5,6 +5,14 @@ using RubyNext
5
5
  module RubyNext
6
6
  module Language
7
7
  module Rewriters
8
+ CUSTOM_PARSER_REQUIRED = <<~MSG
9
+ The %s feature is not a part of the latest stable Ruby release
10
+ and is not supported by your Parser gem version.
11
+
12
+ Use RubyNext's parser to use it: https://github.com/ruby-next/parser
13
+
14
+ MSG
15
+
8
16
  class Base < ::Parser::TreeRewriter
9
17
  class LocalsTracker
10
18
  attr_reader :stacks
@@ -64,6 +72,16 @@ module RubyNext
64
72
  def unsupported_version?(version)
65
73
  self::MIN_SUPPORTED_VERSION > version
66
74
  end
75
+
76
+ private
77
+
78
+ def transform(source)
79
+ Language.transform(source, rewriters: [self], using: false)
80
+ end
81
+
82
+ def warn_custom_parser_required_for(feature)
83
+ warn(CUSTOM_PARSER_REQUIRED % feature)
84
+ end
67
85
  end
68
86
 
69
87
  attr_reader :locals
@@ -21,6 +21,10 @@ module RubyNext
21
21
  ]
22
22
  )
23
23
  end
24
+
25
+ unless transform(SYNTAX_PROBE) == "Language.method(:transform)"
26
+ warn_custom_parser_required_for("method reference")
27
+ end
24
28
  end
25
29
  end
26
30
  end
@@ -27,7 +27,7 @@ module RubyNext
27
27
  private
28
28
 
29
29
  def proc_args(n)
30
- return s(:args, s(:procarg0, :_1)) if n == 1
30
+ return s(:args, s(:procarg0, s(:arg, :_1))) if n == 1
31
31
 
32
32
  (1..n).map do |numero|
33
33
  s(:arg, :"_#{numero}")
@@ -35,13 +35,12 @@ module RubyNext
35
35
  SYNTAX_PROBE = "case 0; in 0; true; else; 1; end"
36
36
  MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
37
37
 
38
- MATCHEE = :__matchee__
39
- MATCHEE_ARR = :__matchee_arr__
40
- MATCHEE_HASH = :__matchee_hash__
38
+ MATCHEE = :__m__
39
+ MATCHEE_ARR = :__m_arr__
40
+ MATCHEE_HASH = :__m_hash__
41
41
 
42
42
  def on_case_match(node)
43
43
  context.track! self
44
- context.use_ruby_next!
45
44
 
46
45
  @deconstructed = []
47
46
 
@@ -64,6 +63,39 @@ module RubyNext
64
63
  )
65
64
  end
66
65
 
66
+ def on_in_match(node)
67
+ context.track! self
68
+
69
+ @deconstructed = []
70
+
71
+ matchee =
72
+ s(:lvasgn, MATCHEE, node.children[0])
73
+
74
+ pattern =
75
+ locals.with(
76
+ matchee: MATCHEE,
77
+ arr: MATCHEE_ARR,
78
+ hash: MATCHEE_HASH
79
+ ) do
80
+ send(
81
+ :"#{node.children[1].type}_clause",
82
+ node.children[1]
83
+ ).then do |node|
84
+ s(:or,
85
+ node,
86
+ no_matching_pattern)
87
+ end
88
+ end
89
+
90
+ node.updated(
91
+ :and,
92
+ [
93
+ matchee,
94
+ pattern
95
+ ]
96
+ )
97
+ end
98
+
67
99
  private
68
100
 
69
101
  def build_if_clause(node, rest)
@@ -172,6 +204,8 @@ module RubyNext
172
204
  # only deconstruct once per case
173
205
  return if deconstructed.include?(locals[:arr])
174
206
 
207
+ context.use_ruby_next!
208
+
175
209
  right = s(:send, matchee, :deconstruct)
176
210
 
177
211
  deconstructed << locals[:arr]
@@ -276,7 +310,7 @@ module RubyNext
276
310
  # Optimization: avoid hash modifications when not needed
277
311
  # (we use #dup and #delete when "reading" values when **rest is present
278
312
  # to assign the rest of the hash copy to it)
279
- @hash_match_rest = node.children.any? { |child| child.type == :match_rest }
313
+ @hash_match_rest = node.children.any? { |child| child.type == :match_rest || child.type == :match_nil_pattern }
280
314
  keys = hash_pattern_keys(node.children)
281
315
 
282
316
  deconstruct_keys_node(keys, matchee).then do |dnode|
@@ -299,7 +333,9 @@ module RubyNext
299
333
  return s(:nil) if children.empty?
300
334
 
301
335
  children.filter_map do |child|
302
- return s(:nil) if child.type == :match_rest
336
+ # Skip ** without var
337
+ next if child.type == :match_rest && child.children.empty?
338
+ return s(:nil) if child.type == :match_rest || child.type == :match_nil_pattern
303
339
 
304
340
  send("#{child.type}_hash_key", child)
305
341
  end.then { |keys| s(:array, *keys) }
@@ -325,6 +361,8 @@ module RubyNext
325
361
  # Create a copy of the original hash if already deconstructed
326
362
  return hash_dup if deconstructed.include?(locals[:hash])
327
363
 
364
+ context.use_ruby_next!
365
+
328
366
  deconstructed << locals[:hash]
329
367
 
330
368
  right = s(:send,
@@ -398,6 +436,12 @@ module RubyNext
398
436
  match_var_clause(node, hash_value_at(key)))
399
437
  end
400
438
 
439
+ def match_nil_pattern_hash_element(node, _key = nil)
440
+ s(:send,
441
+ s(:lvar, locals[:hash]),
442
+ :empty?)
443
+ end
444
+
401
445
  def match_rest_hash_element(node, _key = nil)
402
446
  # case {}; in **; end
403
447
  return if node.children.empty?
@@ -5,55 +5,42 @@ require "pathname"
5
5
  require "ruby-next"
6
6
  require "ruby-next/utils"
7
7
  require "ruby-next/language"
8
+ require "ruby-next/language/eval"
8
9
 
9
10
  using RubyNext
10
11
 
11
12
  module RubyNext
12
13
  module Language
13
14
  # Module responsible for runtime transformations
14
- module Runtime
15
- # Apply only rewriters required for the current version
16
- REWRITERS = RubyNext::Language.rewriters.select(&:unsupported_syntax?)
17
15
 
16
+ module Runtime
18
17
  class << self
19
18
  include Utils
20
19
 
21
- attr_reader :watch_dirs
22
-
23
20
  def load(path, wrap: false)
24
21
  raise "RubyNext cannot handle `load(smth, wrap: true)`" if wrap
25
22
 
26
23
  contents = File.read(path)
27
24
  new_contents = transform contents
28
25
 
29
- puts source_with_lines(new_contents) if ENV["RUBY_NEXT_DEBUG"] == "1"
26
+ $stdout.puts source_with_lines(new_contents, path) if ENV["RUBY_NEXT_DEBUG"] == "1"
30
27
 
31
28
  TOPLEVEL_BINDING.eval(new_contents, path)
32
29
  true
33
30
  end
34
31
 
35
32
  def transform(contents, **options)
36
- Language.transform(contents, rewriters: REWRITERS, **options)
37
- end
38
-
39
- def transformable?(path)
40
- watch_dirs.any? { |dir| path.start_with?(dir) }
33
+ Language.transform(contents, rewriters: Language.current_rewriters, **options)
41
34
  end
42
35
 
43
36
  def feature_path(path)
44
37
  path = resolve_feature_path(path)
45
38
  return if path.nil?
46
39
  return if File.extname(path) != ".rb"
47
- return unless transformable?(path)
40
+ return unless Language.transformable?(path)
48
41
  path
49
42
  end
50
-
51
- private
52
-
53
- attr_writer :watch_dirs
54
43
  end
55
-
56
- self.watch_dirs = %w[app lib spec test].map { |path| File.join(Dir.pwd, path) }
57
44
  end
58
45
  end
59
46
  end
@@ -69,11 +56,13 @@ module Kernel
69
56
 
70
57
  return false if $LOADED_FEATURES.include?(realpath)
71
58
 
59
+ $LOADED_FEATURES << realpath
60
+
72
61
  RubyNext::Language::Runtime.load(realpath)
73
62
 
74
- $LOADED_FEATURES << realpath
75
63
  true
76
64
  rescue => e
65
+ $LOADED_FEATURES.delete realpath
77
66
  warn "RubyNext failed to require '#{path}': #{e.message}"
78
67
  require_without_ruby_next(path)
79
68
  end
@@ -104,46 +93,4 @@ module Kernel
104
93
  warn "RubyNext failed to load '#{path}': #{e.message}"
105
94
  load_without_ruby_next(path)
106
95
  end
107
-
108
- alias_method :eval_without_ruby_next, :eval
109
- def eval(source, *args)
110
- new_source = RubyNext::Language::Runtime.transform(source, eval: true)
111
- eval_without_ruby_next new_source, *args
112
- end
113
- end
114
-
115
- # Patch BasicObject to hijack instance_eval
116
- class BasicObject
117
- alias_method :instance_eval_without_ruby_next, :instance_eval
118
-
119
- def instance_eval(*args, &block)
120
- return instance_eval_without_ruby_next(*args, &block) if block_given?
121
-
122
- source = args.shift
123
- new_source = ::RubyNext::Language::Runtime.transform(source, eval: true)
124
- instance_eval_without_ruby_next new_source, *args
125
- end
126
- end
127
-
128
- # Patch Module to hijack class_eval/module_eval
129
- class Module
130
- alias_method :module_eval_without_ruby_next, :module_eval
131
-
132
- def module_eval(*args, &block)
133
- return module_eval_without_ruby_next(*args, &block) if block_given?
134
-
135
- source = args.shift
136
- new_source = ::RubyNext::Language::Runtime.transform(source, eval: true)
137
- module_eval_without_ruby_next new_source, *args
138
- end
139
-
140
- alias_method :class_eval_without_ruby_next, :class_eval
141
-
142
- def class_eval(*args, &block)
143
- return class_eval_without_ruby_next(*args, &block) if block_given?
144
-
145
- source = args.shift
146
- new_source = ::RubyNext::Language::Runtime.transform(source, eval: true)
147
- class_eval_without_ruby_next new_source, *args
148
- end
149
96
  end
@@ -6,18 +6,3 @@ require "parser/current"
6
6
  $VERBOSE = save_verbose
7
7
 
8
8
  require "unparser"
9
-
10
- # Unparser patches
11
-
12
- # Unparser doesn't support endless ranges
13
- # Source: https://github.com/mbj/unparser/blob/a4f959d58b660ef0630659efa5882fc20936eb18/lib/unparser/emitter/literal/range.rb
14
- # TODO: propose a PR
15
- class Unparser::Emitter::Literal::Range
16
- private
17
-
18
- def dispatch
19
- visit(begin_node)
20
- write(TOKENS.fetch(node.type))
21
- visit(end_node) unless end_node.nil?
22
- end
23
- end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- gem "parser", "~> 2.6.3.102"
4
- gem "unparser", "~> 0.4.5"
3
+ gem "parser", ">= 2.7.0.0"
4
+ gem "unparser", ">= 0.4.7"
5
5
 
6
6
  require "set"
7
7
 
@@ -60,9 +60,10 @@ module RubyNext
60
60
 
61
61
  class << self
62
62
  attr_accessor :rewriters
63
+ attr_reader :watch_dirs
63
64
 
64
- def transform(source, rewriters: self.rewriters, eval: false, context: TransformContext.new)
65
- Parser.parse(source).then do |ast|
65
+ def transform(source, rewriters: self.rewriters, using: true, context: TransformContext.new)
66
+ parse(source).then do |ast|
66
67
  rewriters.inject(ast) do |tree, rewriter|
67
68
  rewriter.new(context).process(tree)
68
69
  end.then do |new_ast|
@@ -70,15 +71,29 @@ module RubyNext
70
71
 
71
72
  Unparser.unparse(new_ast)
72
73
  end.then do |source|
73
- next source if eval || !context.use_ruby_next?
74
+ next source unless using && context.use_ruby_next?
74
75
 
75
76
  Core.inject! source.dup
76
77
  end
77
78
  end
78
79
  end
80
+
81
+ def transformable?(path)
82
+ watch_dirs.any? { |dir| path.start_with?(dir) }
83
+ end
84
+
85
+ # Rewriters required for the current version
86
+ def current_rewriters
87
+ @current_rewriters ||= rewriters.select(&:unsupported_syntax?)
88
+ end
89
+
90
+ private
91
+
92
+ attr_writer :watch_dirs
79
93
  end
80
94
 
81
95
  self.rewriters = []
96
+ self.watch_dirs = %w[app lib spec test].map { |path| File.join(Dir.pwd, path) }
82
97
 
83
98
  require "ruby-next/language/rewriters/base"
84
99
 
@@ -88,13 +103,15 @@ module RubyNext
88
103
  require "ruby-next/language/rewriters/pattern_matching"
89
104
  rewriters << Rewriters::PatternMatching
90
105
 
91
- require "ruby-next/language/rewriters/method_reference"
92
- rewriters << Rewriters::MethodReference
93
-
94
106
  require "ruby-next/language/rewriters/args_forward"
95
107
  rewriters << Rewriters::ArgsForward
96
108
 
97
109
  require "ruby-next/language/rewriters/numbered_params"
98
110
  rewriters << Rewriters::NumberedParams
111
+
112
+ if ENV["RUBY_NEXT_ENABLE_METHOD_REFERENCE"] == "1"
113
+ require "ruby-next/language/rewriters/method_reference"
114
+ RubyNext::Language.rewriters << RubyNext::Language::Rewriters::MethodReference
115
+ end
99
116
  end
100
117
  end
@@ -25,9 +25,11 @@ module RubyNext
25
25
  end
26
26
  end
27
27
 
28
- def source_with_lines(source)
28
+ def source_with_lines(source, path)
29
29
  source.lines.map.with_index do |line, i|
30
- "#{i.to_s.rjust(3)}: #{line}"
30
+ "#{(i + 1).to_s.rjust(4)}: #{line}"
31
+ end.tap do |lines|
32
+ lines.unshift " 0: # source: #{path}"
31
33
  end
32
34
  end
33
35
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyNext
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/uby-next.rb CHANGED
@@ -5,7 +5,7 @@ require "ruby-next/core/runtime"
5
5
 
6
6
  using RubyNext
7
7
 
8
- RubyNext::Language::Runtime.watch_dirs << Dir.pwd
8
+ RubyNext::Language.watch_dirs << Dir.pwd
9
9
 
10
10
  require "stringio"
11
11
 
metadata CHANGED
@@ -1,44 +1,60 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-next
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.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: 2019-11-18 00:00:00.000000000 Z
11
+ date: 2020-01-13 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ruby-next-core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: parser
15
29
  requirement: !ruby/object:Gem::Requirement
16
30
  requirements:
17
- - - "~>"
31
+ - - ">="
18
32
  - !ruby/object:Gem::Version
19
- version: 2.6.3.102
20
- type: :development
33
+ version: 2.7.0.0
34
+ type: :runtime
21
35
  prerelease: false
22
36
  version_requirements: !ruby/object:Gem::Requirement
23
37
  requirements:
24
- - - "~>"
38
+ - - ">="
25
39
  - !ruby/object:Gem::Version
26
- version: 2.6.3.102
40
+ version: 2.7.0.0
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: unparser
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
- - - "~>"
45
+ - - ">="
32
46
  - !ruby/object:Gem::Version
33
- version: 0.4.5
34
- type: :development
47
+ version: 0.4.7
48
+ type: :runtime
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
- - - "~>"
52
+ - - ">="
39
53
  - !ruby/object:Gem::Version
40
- version: 0.4.5
41
- description: "\n Coming soon.\n "
54
+ version: 0.4.7
55
+ description: "\n Ruby Next is a collection of polyfills and a transpiler for supporting
56
+ latest and upcoming edge CRuby features\n in older versions and alternative implementations
57
+ (such as mruby, JRuby, Opal, Artichoke, RubyMotion, etc.).\n "
42
58
  email:
43
59
  - dementiev.vm@gmail.com
44
60
  executables:
@@ -61,12 +77,15 @@ files:
61
77
  - lib/ruby-next/core/enumerable/filter.rb
62
78
  - lib/ruby-next/core/enumerable/filter_map.rb
63
79
  - lib/ruby-next/core/enumerable/tally.rb
80
+ - lib/ruby-next/core/enumerator/produce.rb
64
81
  - lib/ruby-next/core/hash/merge.rb
65
82
  - lib/ruby-next/core/kernel/then.rb
66
83
  - lib/ruby-next/core/pattern_matching.rb
67
84
  - lib/ruby-next/core/proc/compose.rb
68
85
  - lib/ruby-next/core/runtime.rb
69
86
  - lib/ruby-next/language.rb
87
+ - lib/ruby-next/language/bootsnap.rb
88
+ - lib/ruby-next/language/eval.rb
70
89
  - lib/ruby-next/language/parser.rb
71
90
  - lib/ruby-next/language/rewriters/args_forward.rb
72
91
  - lib/ruby-next/language/rewriters/base.rb
@@ -83,7 +102,12 @@ files:
83
102
  homepage: http://github.com/palkan/ruby-next
84
103
  licenses:
85
104
  - MIT
86
- metadata: {}
105
+ metadata:
106
+ bug_tracker_uri: http://github.com/ruby-next/ruby-next/issues
107
+ changelog_uri: https://github.com/ruby-next/ruby-next/blob/master/CHANGELOG.md
108
+ documentation_uri: http://github.com/ruby-next/ruby-next/blob/master/README.md
109
+ homepage_uri: http://github.com/ruby-next/ruby-next
110
+ source_code_uri: http://github.com/ruby-next/ruby-next
87
111
  post_install_message:
88
112
  rdoc_options: []
89
113
  require_paths: