ruby-next-core 0.4.0 → 0.5.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 +8 -1
- data/LICENSE.txt +1 -1
- data/README.md +74 -37
- data/lib/ruby-next/commands/nextify.rb +7 -0
- data/lib/ruby-next/core.rb +1 -1
- data/lib/ruby-next/language.rb +56 -9
- data/lib/ruby-next/language/parser.rb +9 -0
- data/lib/ruby-next/language/rewriters/args_forward.rb +6 -0
- data/lib/ruby-next/language/rewriters/base.rb +21 -0
- data/lib/ruby-next/language/rewriters/endless_range.rb +2 -0
- data/lib/ruby-next/language/rewriters/method_reference.rb +7 -0
- data/lib/ruby-next/language/rewriters/numbered_params.rb +8 -2
- data/lib/ruby-next/language/rewriters/pattern_matching.rb +59 -21
- data/lib/ruby-next/language/runtime.rb +20 -1
- data/lib/ruby-next/language/setup.rb +2 -0
- data/lib/ruby-next/utils.rb +1 -1
- data/lib/ruby-next/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 18e3905f1324ff7061b59c61aaabd4faf91ff5bcbe2e2896ceda6f2514427b71
|
4
|
+
data.tar.gz: f87cad7947307e13f3da37624eff499f62ae1b8398f902107c8cc7179ee6a5be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4c2530ece05aa6e2bb8e0437f38f5d198a9a081039101ec03c836a01162a92685c5dc6dc2d90f1608c1466002b1afcca3fdf9ddb36774ff1b47a2d438f7cb0ae
|
7
|
+
data.tar.gz: cf6f93b2f8f869354e594ee8e5932acd96ecd78201223a6630f74d6fc13423d220459d9524a81266342f23913304f41a30c3face46be1291022e09b2f15caee3
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,13 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 0.5.0 (2020-03-20)
|
6
|
+
|
7
|
+
- Add `rewrite` transpiler mode. ([@palkan][])
|
8
|
+
|
9
|
+
Add support for rewriting the source code instead of rebuilding it from scratch to
|
10
|
+
preserve the original layout and improve the debugging experience.
|
11
|
+
|
5
12
|
## 0.4.0 (2020-03-09)
|
6
13
|
|
7
14
|
- Optimize pattern matching transpiled code. ([@palkan][])
|
@@ -67,7 +74,7 @@ p a #=> 1
|
|
67
74
|
|
68
75
|
- Support hash pattern in array and vice versa. ([@palkan][])
|
69
76
|
|
70
|
-
- Handle
|
77
|
+
- Handle multiple `-e` in `uby-next`. ([@palkan][])
|
71
78
|
|
72
79
|
## 0.1.0 (2019-11-16)
|
73
80
|
|
data/LICENSE.txt
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
The MIT License (MIT)
|
2
2
|
|
3
|
-
Copyright (c) 2019 Vladimir Dementyev
|
3
|
+
Copyright (c) 2019-2020 Vladimir Dementyev
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
@@ -24,6 +24,19 @@ That's why Ruby Next implements the `master` features as fast as possible.
|
|
24
24
|
|
25
25
|
- [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)
|
26
26
|
|
27
|
+
## Table of contents
|
28
|
+
|
29
|
+
- [Overview](#overview)
|
30
|
+
- [Polyfills](#using-only-polyfills)
|
31
|
+
- [Transpiling](#transpiling)
|
32
|
+
- [Modes](#transpiler-modes)
|
33
|
+
- [CLI](#cli)
|
34
|
+
- [Using in gems](#integrating-into-a-gem-development)
|
35
|
+
- [Runtime usage](#runtime-usage)
|
36
|
+
- [Bootsnap integration](#using-with-bootsnap)
|
37
|
+
- [`ruby -ruby-next`](#uby-next)
|
38
|
+
- [Proposed & edge features](#proposed-and-edge-features)
|
39
|
+
|
27
40
|
## Overview
|
28
41
|
|
29
42
|
Ruby Next consists of two parts: **core** and **language**.
|
@@ -75,14 +88,16 @@ require "ruby-next/core_ext"
|
|
75
88
|
The following _rule of thumb_ is recommended when choosing between refinements and monkey-patches:
|
76
89
|
|
77
90
|
- Use refinements for libraries development (to avoid conflicts with others code)
|
78
|
-
- Using core extensions could be considered for application development (no need to think about `using RubyNext`); this approach could potentially lead to conflicts with
|
91
|
+
- Using core extensions could be considered for application development (no need to think about `using RubyNext`); this approach could potentially lead to conflicts with dependencies (if these dependencies are not using refinements 🙂)
|
79
92
|
- Use core extensions if refinements are not supported by your platform
|
80
93
|
|
81
94
|
[**The list of supported APIs.**][features_core]
|
82
95
|
|
83
|
-
## Transpiling
|
96
|
+
## Transpiling
|
97
|
+
|
98
|
+
Ruby Next allows you to transpile\* edge Ruby syntax to older versions.
|
84
99
|
|
85
|
-
|
100
|
+
Transpiler relies on two libraries: [parser][] and [unparser][].
|
86
101
|
|
87
102
|
**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).
|
88
103
|
|
@@ -103,40 +118,23 @@ gem install ruby-next
|
|
103
118
|
|
104
119
|
[**The list of supported syntax features.**][features_syntax]
|
105
120
|
|
106
|
-
###
|
121
|
+
### Transpiler modes
|
107
122
|
|
108
|
-
|
123
|
+
Ruby Next currently provides two different modes of generating transpiled code: _AST_ and _rewrite_.
|
109
124
|
|
110
|
-
|
125
|
+
In the AST mode, we parse the source code into AST, modifies this AST and **generate a new code from AST** (using [unparser][unparser]). Thus, the transpiled code being identical in terms of functionality has a different formatting.
|
111
126
|
|
112
|
-
|
127
|
+
In the rewrite mode, we apply changes to the source code itself, thus, keeping the original formatting of the unaffected code (in a similar way to RuboCop's autocorrect feature).
|
113
128
|
|
114
|
-
|
129
|
+
By default, we use the AST mode. That could likely change in the future when we collect enough feedback on the rewrite mode and fix potential bugs.
|
115
130
|
|
116
|
-
|
131
|
+
The main benefit of the rewrite mode is that it preserves the original code line numbers and layout, which is especially useful in debugging.
|
117
132
|
|
118
|
-
|
133
|
+
You can change the transpiler mode:
|
119
134
|
|
120
|
-
-
|
121
|
-
|
122
|
-
|
123
|
-
require "ruby-next/language/setup"
|
124
|
-
|
125
|
-
RubyNext::Language.setup_gem_load_path
|
126
|
-
```
|
127
|
-
|
128
|
-
The `setup_gem_load_path` does the following:
|
129
|
-
|
130
|
-
- Resolves the current ruby version.
|
131
|
-
- Checks whether there are directories corresponding to the current and earlier\* Ruby versions within the `.rbnext` folder.
|
132
|
-
- Add the path to this directory to the `$LOAD_PATH` before the path to the gem's directory.
|
133
|
-
|
134
|
-
That's why need an _entrypoint_: all the subsequent `require` calls will load the transpiled files instead of the original ones
|
135
|
-
due to the way feature resolving works in Ruby (scanning the `$LOAD_PATH` and halting as soon as the matching file is found).
|
136
|
-
|
137
|
-
**NOTE:** `require_relative` should be avoided due to the way we _hijack_ the features loading mechanism.
|
138
|
-
|
139
|
-
\* Ruby Next avoids storing duplicates; instead, only the code for the earlier version is created and is assumed to be used with other versions. For example, if the transpiled code is the same for Ruby 2.5 and Ruby 2.6, only the `.rbnext/2.7/path/to/file.rb` is kept. That's why multiple entries are added to the `$LOAD_PATH` (`.rbnext/2.6` and `.rbnext/2.7` in the specified order for Ruby 2.5 and only `.rbnext/2.7` for Ruby 2.6).
|
135
|
+
- From code by setting `RubyNext::Language.mode = :ast` or `RubyNext::Language.mode = :rewrite`.
|
136
|
+
- Via environmental variable `RUBY_NEXT_TRANSPILE_MODE=rewrite`.
|
137
|
+
- Via CLI option ([see below](#cli)).
|
140
138
|
|
141
139
|
## CLI
|
142
140
|
|
@@ -155,6 +153,7 @@ Usage: ruby-next nextify DIRECTORY_OR_FILE [options]
|
|
155
153
|
--min-version=VERSION Specify the minimum Ruby version to support
|
156
154
|
--single-version Only create one version of a file (for the earliest Ruby version)
|
157
155
|
--enable-method-reference Enable reverted method reference syntax (requires custom parser)
|
156
|
+
--transpile-mode=MODE Transpiler mode (ast or rewrite). Default: ast
|
158
157
|
--[no-]refine Do not inject `using RubyNext`
|
159
158
|
-h, --help Print help
|
160
159
|
-V Turn on verbose mode
|
@@ -191,7 +190,7 @@ Usage: ruby-next core_ext [options]
|
|
191
190
|
-V Turn on verbose mode
|
192
191
|
```
|
193
192
|
|
194
|
-
The most common
|
193
|
+
The most common use-case is to backport the APIs required by pattern matching. You can do this, for example,
|
195
194
|
by including only monkey-patches containing the `"deconstruct"` in their names:
|
196
195
|
|
197
196
|
```sh
|
@@ -215,7 +214,42 @@ $ ruby-next core_ext -l --name=filter --name=deconstruct
|
|
215
214
|
- StructDeconstruct
|
216
215
|
```
|
217
216
|
|
218
|
-
|
217
|
+
### Integrating into a gem development
|
218
|
+
|
219
|
+
We recommend _pre-transpiling_ source code to work with older versions before releasing it.
|
220
|
+
|
221
|
+
This is how you can do that with Ruby Next:
|
222
|
+
|
223
|
+
- Write source code using the modern/edge Ruby syntax.
|
224
|
+
|
225
|
+
- Generate transpiled code by calling `ruby-next nextify ./lib` (e.g., before releasing or pushing to VCS).
|
226
|
+
|
227
|
+
This will produce `lib/.rbnext` folder containing the transpiled files, `lib/.rbnext/2.6`, `lib/.rbnext/2.7`. The version in the path indicates which Ruby version is required for the original functionality. Only the source files containing new syntax are added to this folder.
|
228
|
+
|
229
|
+
**NOTE:** Do not edit these files manually, either run linters/type checkers/whatever against these files.
|
230
|
+
|
231
|
+
- Add the following code to your gem's _entrypoint_ (the file that is required first and contains other `require`-s):
|
232
|
+
|
233
|
+
```ruby
|
234
|
+
require "ruby-next/language/setup"
|
235
|
+
|
236
|
+
RubyNext::Language.setup_gem_load_path
|
237
|
+
```
|
238
|
+
|
239
|
+
The `setup_gem_load_path` does the following:
|
240
|
+
|
241
|
+
- Resolves the current ruby version.
|
242
|
+
- Checks whether there are directories corresponding to the current and earlier\* Ruby versions within the `.rbnext` folder.
|
243
|
+
- Add the path to this directory to the `$LOAD_PATH` before the path to the gem's directory.
|
244
|
+
|
245
|
+
That's why need an _entrypoint_: all the subsequent `require` calls will load the transpiled files instead of the original ones
|
246
|
+
due to the way feature resolving works in Ruby (scanning the `$LOAD_PATH` and halting as soon as the matching file is found).
|
247
|
+
|
248
|
+
**NOTE:** `require_relative` should be avoided due to the way we _hijack_ the features loading mechanism.
|
249
|
+
|
250
|
+
\* Ruby Next avoids storing duplicates; instead, only the code for the earlier version is created and is assumed to be used with other versions. For example, if the transpiled code is the same for Ruby 2.5 and Ruby 2.6, only the `.rbnext/2.7/path/to/file.rb` is kept. That's why multiple entries are added to the `$LOAD_PATH` (`.rbnext/2.6` and `.rbnext/2.7` in the specified order for Ruby 2.5 and only `.rbnext/2.7` for Ruby 2.6).
|
251
|
+
|
252
|
+
## Runtime usage
|
219
253
|
|
220
254
|
It is also possible to transpile Ruby source code in run-time via Ruby Next.
|
221
255
|
|
@@ -270,20 +304,23 @@ RUBYOPT="-ruby-next" ruby my_ruby_script.rb
|
|
270
304
|
ruby -ruby-next -e "puts [2, 4, 5].tally"
|
271
305
|
```
|
272
306
|
|
273
|
-
##
|
307
|
+
## Proposed and edge features
|
274
308
|
|
275
|
-
Ruby Next
|
309
|
+
Ruby Next aims to bring edge and proposed features to Ruby community before they (hopefully) reach an official Ruby release.
|
310
|
+
This includes:
|
311
|
+
|
312
|
+
- Features already merged to [master](https://github.com/ruby/ruby) (_edge_)
|
313
|
+
- Features proposed in [Ruby bug tracker](https://bugs.ruby-lang.org/) (_proposed_)
|
314
|
+
- Features once merged to master but got reverted.
|
276
315
|
|
277
316
|
These features require a [custom parser](#using-ruby-next-parser).
|
278
317
|
|
279
|
-
|
318
|
+
Currently, the only such feature is the [_method reference_ operator](https://bugs.ruby-lang.org/issues/13581):
|
280
319
|
|
281
320
|
- Add `--enable-method-reference` option to `nextify` command when using CLI.
|
282
321
|
- OR add it programmatically when using a runtime mode (see [example](https://github.com/ruby-next/ruby-next/blob/master/default.mspec)).
|
283
322
|
- OR set `RUBY_NEXT_ENABLE_METHOD_REFERENCE=1` environment variable (works with CLI as well).
|
284
323
|
|
285
|
-
## Using Ruby Next parser
|
286
|
-
|
287
324
|
### Prerequisites
|
288
325
|
|
289
326
|
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.
|
@@ -43,6 +43,13 @@ module RubyNext
|
|
43
43
|
Language.rewriters << Language::Rewriters::MethodReference
|
44
44
|
end
|
45
45
|
|
46
|
+
opts.on(
|
47
|
+
"--transpile-mode=MODE",
|
48
|
+
"Transpiler mode (ast or rewrite). Default: ast"
|
49
|
+
) do |val|
|
50
|
+
Language.mode = val.to_sym
|
51
|
+
end
|
52
|
+
|
46
53
|
opts.on("--[no-]refine", "Do not inject `using RubyNext`") do |val|
|
47
54
|
Core.strategy = :core_ext unless val
|
48
55
|
end
|
data/lib/ruby-next/core.rb
CHANGED
data/lib/ruby-next/language.rb
CHANGED
@@ -63,14 +63,41 @@ module RubyNext
|
|
63
63
|
attr_accessor :rewriters
|
64
64
|
attr_reader :watch_dirs
|
65
65
|
|
66
|
-
|
67
|
-
|
66
|
+
attr_accessor :strategy
|
67
|
+
|
68
|
+
MODES = %i[rewrite ast].freeze
|
69
|
+
|
70
|
+
attr_reader :mode
|
71
|
+
|
72
|
+
def mode=(val)
|
73
|
+
raise ArgumentError, "Unknown mode: #{val}. Available: #{MODES.join(",")}" unless MODES.include?(val)
|
74
|
+
@mode = val
|
75
|
+
end
|
76
|
+
|
77
|
+
def rewrite?
|
78
|
+
mode == :rewrite?
|
79
|
+
end
|
80
|
+
|
81
|
+
def ast?
|
82
|
+
mode == :ast
|
83
|
+
end
|
84
|
+
|
85
|
+
def transform(*args, **kwargs)
|
86
|
+
if mode == :rewrite
|
87
|
+
rewrite(*args, **kwargs)
|
88
|
+
else
|
89
|
+
regenerate(*args, **kwargs)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def regenerate(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new)
|
94
|
+
parse_with_comments(source).then do |(ast, comments)|
|
68
95
|
rewriters.inject(ast) do |tree, rewriter|
|
69
96
|
rewriter.new(context).process(tree)
|
70
97
|
end.then do |new_ast|
|
71
98
|
next source unless context.dirty?
|
72
99
|
|
73
|
-
Unparser.unparse(new_ast)
|
100
|
+
Unparser.unparse(new_ast, comments)
|
74
101
|
end.then do |source|
|
75
102
|
next source unless RubyNext::Core.refine?
|
76
103
|
next source unless using && context.use_ruby_next?
|
@@ -80,6 +107,23 @@ module RubyNext
|
|
80
107
|
end
|
81
108
|
end
|
82
109
|
|
110
|
+
def rewrite(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new)
|
111
|
+
rewriters.inject(source) do |src, rewriter|
|
112
|
+
buffer = Parser::Source::Buffer.new("<dynamic>")
|
113
|
+
buffer.source = src
|
114
|
+
|
115
|
+
rewriter.new(context).rewrite(buffer, parse(src))
|
116
|
+
end.then do |new_source|
|
117
|
+
next source unless context.dirty?
|
118
|
+
new_source
|
119
|
+
end.then do |source|
|
120
|
+
next source unless RubyNext::Core.refine?
|
121
|
+
next source unless using && context.use_ruby_next?
|
122
|
+
|
123
|
+
Core.inject! source.dup
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
83
127
|
def transformable?(path)
|
84
128
|
watch_dirs.any? { |dir| path.start_with?(dir) }
|
85
129
|
end
|
@@ -96,21 +140,24 @@ module RubyNext
|
|
96
140
|
|
97
141
|
self.rewriters = []
|
98
142
|
self.watch_dirs = %w[app lib spec test].map { |path| File.join(Dir.pwd, path) }
|
143
|
+
self.mode = ENV.fetch("RUBY_NEXT_TRANSPILE_MODE", "ast").to_sym
|
99
144
|
|
100
145
|
require "ruby-next/language/rewriters/base"
|
101
146
|
|
102
|
-
require "ruby-next/language/rewriters/endless_range"
|
103
|
-
rewriters << Rewriters::EndlessRange
|
104
|
-
|
105
|
-
require "ruby-next/language/rewriters/pattern_matching"
|
106
|
-
rewriters << Rewriters::PatternMatching
|
107
|
-
|
108
147
|
require "ruby-next/language/rewriters/args_forward"
|
109
148
|
rewriters << Rewriters::ArgsForward
|
110
149
|
|
111
150
|
require "ruby-next/language/rewriters/numbered_params"
|
112
151
|
rewriters << Rewriters::NumberedParams
|
113
152
|
|
153
|
+
require "ruby-next/language/rewriters/pattern_matching"
|
154
|
+
rewriters << Rewriters::PatternMatching
|
155
|
+
|
156
|
+
# Put endless range in the end, 'cause Parser fails to parse it in
|
157
|
+
# pattern matching
|
158
|
+
require "ruby-next/language/rewriters/endless_range"
|
159
|
+
rewriters << Rewriters::EndlessRange
|
160
|
+
|
114
161
|
if ENV["RUBY_NEXT_ENABLE_METHOD_REFERENCE"] == "1"
|
115
162
|
require "ruby-next/language/rewriters/method_reference"
|
116
163
|
RubyNext::Language.rewriters << RubyNext::Language::Rewriters::MethodReference
|
@@ -21,8 +21,17 @@ module RubyNext
|
|
21
21
|
buffer = ::Parser::Source::Buffer.new(file).tap do |buffer|
|
22
22
|
buffer.source = source
|
23
23
|
end
|
24
|
+
|
24
25
|
parser.parse(buffer)
|
25
26
|
end
|
27
|
+
|
28
|
+
def parse_with_comments(source, file = "(string)")
|
29
|
+
buffer = ::Parser::Source::Buffer.new(file).tap do |buffer|
|
30
|
+
buffer.source = source
|
31
|
+
end
|
32
|
+
|
33
|
+
parser.parse_with_comments(buffer)
|
34
|
+
end
|
26
35
|
end
|
27
36
|
end
|
28
37
|
end
|
@@ -13,6 +13,8 @@ module RubyNext
|
|
13
13
|
def on_forward_args(node)
|
14
14
|
context.track! self
|
15
15
|
|
16
|
+
replace(node.loc.expression, "(*#{REST}, &#{BLOCK})")
|
17
|
+
|
16
18
|
node.updated(
|
17
19
|
:args,
|
18
20
|
[
|
@@ -25,6 +27,8 @@ module RubyNext
|
|
25
27
|
def on_send(node)
|
26
28
|
return unless node.children[2]&.type == :forwarded_args
|
27
29
|
|
30
|
+
replace(node.children[2].loc.expression, "*#{REST}, &#{BLOCK}")
|
31
|
+
|
28
32
|
node.updated(
|
29
33
|
nil,
|
30
34
|
[
|
@@ -37,6 +41,8 @@ module RubyNext
|
|
37
41
|
def on_super(node)
|
38
42
|
return unless node.children[0]&.type == :forwarded_args
|
39
43
|
|
44
|
+
replace(node.children[0].loc.expression, "*#{REST}, &#{BLOCK}")
|
45
|
+
|
40
46
|
node.updated(
|
41
47
|
nil,
|
42
48
|
forwarded_args
|
@@ -98,6 +98,27 @@ module RubyNext
|
|
98
98
|
|
99
99
|
private
|
100
100
|
|
101
|
+
def replace(range, ast)
|
102
|
+
@source_rewriter&.replace(range, unparse(ast))
|
103
|
+
end
|
104
|
+
|
105
|
+
def remove(range)
|
106
|
+
@source_rewriter&.remove(range)
|
107
|
+
end
|
108
|
+
|
109
|
+
def insert_after(range, ast)
|
110
|
+
@source_rewriter&.insert_after(range, unparse(ast))
|
111
|
+
end
|
112
|
+
|
113
|
+
def insert_before(range, ast)
|
114
|
+
@source_rewriter&.insert_before(range, unparse(ast))
|
115
|
+
end
|
116
|
+
|
117
|
+
def unparse(ast)
|
118
|
+
return ast if ast.is_a?(String)
|
119
|
+
Unparser.unparse(ast)
|
120
|
+
end
|
121
|
+
|
101
122
|
attr_reader :context
|
102
123
|
end
|
103
124
|
end
|
@@ -12,14 +12,20 @@ module RubyNext
|
|
12
12
|
def on_numblock(node)
|
13
13
|
context.track! self
|
14
14
|
|
15
|
-
proc_or_lambda, num,
|
15
|
+
proc_or_lambda, num, body = *node.children
|
16
|
+
|
17
|
+
if proc_or_lambda.type == :lambda
|
18
|
+
insert_before(node.loc.begin, "(#{unparse(proc_args(num))})")
|
19
|
+
else
|
20
|
+
insert_after(node.loc.begin, " |#{unparse(proc_args(num))}|")
|
21
|
+
end
|
16
22
|
|
17
23
|
node.updated(
|
18
24
|
:block,
|
19
25
|
[
|
20
26
|
proc_or_lambda,
|
21
27
|
proc_args(num),
|
22
|
-
|
28
|
+
body
|
23
29
|
]
|
24
30
|
)
|
25
31
|
end
|
@@ -240,16 +240,17 @@ module RubyNext
|
|
240
240
|
arr: MATCHEE_ARR,
|
241
241
|
hash: MATCHEE_HASH
|
242
242
|
) do
|
243
|
-
|
243
|
+
build_case_when(node.children[1..-1])
|
244
244
|
end
|
245
245
|
|
246
|
-
|
247
|
-
|
246
|
+
case_clause = predicates.process(s(:case, *patterns))
|
247
|
+
|
248
|
+
rewrite_case_in! node, matchee_ast, case_clause
|
248
249
|
|
249
250
|
node.updated(
|
250
251
|
:begin,
|
251
252
|
[
|
252
|
-
matchee_ast,
|
253
|
+
matchee_ast, case_clause
|
253
254
|
]
|
254
255
|
)
|
255
256
|
end
|
@@ -285,26 +286,67 @@ module RubyNext
|
|
285
286
|
matchee,
|
286
287
|
pattern
|
287
288
|
]
|
288
|
-
)
|
289
|
+
).tap do |new_node|
|
290
|
+
replace(node.loc.expression, new_node)
|
291
|
+
end
|
289
292
|
end
|
290
293
|
|
291
294
|
private
|
292
295
|
|
293
|
-
def
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
296
|
+
def rewrite_case_in!(node, matchee, new_node)
|
297
|
+
replace(node.loc.keyword, "case; when (#{unparse(matchee)}) && false")
|
298
|
+
remove(node.children[0].loc.expression)
|
299
|
+
|
300
|
+
node.children[1..-1].each.with_index do |clause, i|
|
301
|
+
if clause&.type == :in_pattern
|
302
|
+
# handle multiline clauses differently
|
303
|
+
if clause.loc.last_line > clause.children[0].loc.last_line + 1
|
304
|
+
height = clause.loc.last_line - clause.children[0].loc.last_line
|
305
|
+
padding = "\n" * height
|
306
|
+
body_indent = " " * clause.children[2].loc.column
|
307
|
+
replace(
|
308
|
+
clause.loc.expression,
|
309
|
+
"when #{unparse(new_node.children[i].children[0])}" \
|
310
|
+
"#{padding}" \
|
311
|
+
"#{body_indent}#{clause.children[2].loc.expression.source}"
|
312
|
+
)
|
313
|
+
else
|
314
|
+
replace(
|
315
|
+
clause.loc.keyword.end.join(clause.children[0].loc.expression.end),
|
316
|
+
new_node.children[i].children[0]
|
317
|
+
)
|
318
|
+
remove(clause.children[1].loc.expression) if clause.children[1]
|
319
|
+
replace(clause.loc.keyword, "when ")
|
320
|
+
end
|
321
|
+
elsif clause.nil?
|
322
|
+
insert_after(node.children[-2].loc.expression, "; else; #{unparse(new_node.children.last)}")
|
303
323
|
end
|
304
324
|
end
|
305
325
|
end
|
306
326
|
|
307
|
-
def
|
327
|
+
def build_case_when(nodes)
|
328
|
+
else_clause = nil
|
329
|
+
clauses = []
|
330
|
+
|
331
|
+
nodes.each do |clause|
|
332
|
+
if clause&.type == :in_pattern
|
333
|
+
clauses << build_when_clause(clause)
|
334
|
+
else
|
335
|
+
else_clause = process(clause)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
else_clause = (else_clause || no_matching_pattern).then do |node|
|
340
|
+
next node unless node.type == :empty_else
|
341
|
+
s(:empty)
|
342
|
+
end
|
343
|
+
|
344
|
+
clauses << else_clause
|
345
|
+
clauses
|
346
|
+
end
|
347
|
+
|
348
|
+
def build_when_clause(clause)
|
349
|
+
predicates.reset!
|
308
350
|
[
|
309
351
|
with_guard(
|
310
352
|
send(
|
@@ -315,11 +357,7 @@ module RubyNext
|
|
315
357
|
),
|
316
358
|
process(clause.children[2] || s(:nil)) # expression
|
317
359
|
].then do |children|
|
318
|
-
|
319
|
-
children << build_if_clause(rest.first, rest[1..-1])
|
320
|
-
end
|
321
|
-
|
322
|
-
s(:if, *children)
|
360
|
+
s(:when, *children)
|
323
361
|
end
|
324
362
|
end
|
325
363
|
|
@@ -24,7 +24,7 @@ module RubyNext
|
|
24
24
|
|
25
25
|
$stdout.puts source_with_lines(new_contents, path) if ENV["RUBY_NEXT_DEBUG"] == "1"
|
26
26
|
|
27
|
-
|
27
|
+
evaluate(new_contents, path)
|
28
28
|
true
|
29
29
|
end
|
30
30
|
|
@@ -39,6 +39,25 @@ module RubyNext
|
|
39
39
|
return unless Language.transformable?(path)
|
40
40
|
path
|
41
41
|
end
|
42
|
+
|
43
|
+
if defined?(JRUBY_VERSION)
|
44
|
+
def evaluate(code, filepath)
|
45
|
+
new_toplevel.eval(code, filepath)
|
46
|
+
end
|
47
|
+
|
48
|
+
def new_toplevel
|
49
|
+
# Create new "toplevel" binding to avoid lexical scope re-use
|
50
|
+
# (aka "leaking refinements")
|
51
|
+
eval "proc{binding}.call", TOPLEVEL_BINDING, __FILE__, __LINE__
|
52
|
+
end
|
53
|
+
else
|
54
|
+
def evaluate(code, filepath)
|
55
|
+
# This is workaround to solve the "leaking refinements" problem in MRI
|
56
|
+
RubyVM::InstructionSequence.compile(code, filepath).then do |iseq|
|
57
|
+
iseq.eval
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
42
61
|
end
|
43
62
|
end
|
44
63
|
end
|
data/lib/ruby-next/utils.rb
CHANGED
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.5.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-03-
|
11
|
+
date: 2020-03-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: parser
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 2.7.0.
|
19
|
+
version: 2.7.0.5
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 2.7.0.
|
26
|
+
version: 2.7.0.5
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: unparser
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|