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 +4 -4
- data/CHANGELOG.md +24 -0
- data/README.md +100 -27
- data/bin/parse +1 -1
- data/bin/transform +4 -1
- data/lib/ruby-next/commands/nextify.rb +5 -0
- data/lib/ruby-next/core/enumerator/produce.rb +22 -0
- data/lib/ruby-next/core/runtime.rb +3 -3
- data/lib/ruby-next/core.rb +7 -1
- data/lib/ruby-next/language/bootsnap.rb +26 -0
- data/lib/ruby-next/language/eval.rb +64 -0
- data/lib/ruby-next/language/parser.rb +17 -16
- data/lib/ruby-next/language/rewriters/base.rb +18 -0
- data/lib/ruby-next/language/rewriters/method_reference.rb +4 -0
- data/lib/ruby-next/language/rewriters/numbered_params.rb +1 -1
- data/lib/ruby-next/language/rewriters/pattern_matching.rb +50 -6
- data/lib/ruby-next/language/runtime.rb +8 -61
- data/lib/ruby-next/language/unparser.rb +0 -15
- data/lib/ruby-next/language.rb +25 -8
- data/lib/ruby-next/utils.rb +4 -2
- data/lib/ruby-next/version.rb +1 -1
- data/lib/uby-next.rb +1 -1
- metadata +38 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 96fbef2a02f92547dcc39e30ac93fbe59ff98118a797dce1ca017816030e3353
|
4
|
+
data.tar.gz: dd6e45bd8dabbf84ea832f2faa0365b20d4bdc62b9d915fba07cca5a92274a40
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
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
|
70
|
+
Ruby Next transpiler relies on two libraries: [parser][] and [unparser][].
|
64
71
|
|
65
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
80
|
+
# gemspec
|
81
|
+
spec.add_dependency "ruby-next"
|
87
82
|
|
88
|
-
|
83
|
+
# or install globally
|
84
|
+
gem install ruby-next
|
85
|
+
```
|
89
86
|
|
90
|
-
|
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
|
-
|
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
|
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
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)
|
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)
|
data/lib/ruby-next/core.rb
CHANGED
@@ -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.
|
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
|
-
|
9
|
-
|
10
|
-
Parser::Builders::Default
|
11
|
-
|
12
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
@@ -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 = :
|
39
|
-
MATCHEE_ARR = :
|
40
|
-
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
|
-
|
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:
|
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
|
data/lib/ruby-next/language.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
gem "parser", "
|
4
|
-
gem "unparser", "
|
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,
|
65
|
-
|
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
|
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
|
data/lib/ruby-next/utils.rb
CHANGED
@@ -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(
|
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
|
data/lib/ruby-next/version.rb
CHANGED
data/lib/uby-next.rb
CHANGED
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.
|
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:
|
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.
|
20
|
-
type: :
|
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.
|
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.
|
34
|
-
type: :
|
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.
|
41
|
-
description: "\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:
|