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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0456426952d07bcec1db2c70d256177bd8d1503c8edc7e667e2af62bf5f03ae3'
4
- data.tar.gz: 4528e483af6cd8cdbf7b613454a0051abd5aaad4ec6cd02c727690d64fbfd848
3
+ metadata.gz: 18e3905f1324ff7061b59c61aaabd4faf91ff5bcbe2e2896ceda6f2514427b71
4
+ data.tar.gz: f87cad7947307e13f3da37624eff499f62ae1b8398f902107c8cc7179ee6a5be
5
5
  SHA512:
6
- metadata.gz: a25d4e8c619b0644f4bcb0f411d0f0c86d1aa7c343ab0e55b42c4d8adad10b2c5c7072ee13f019c9da29223774f6c34c353f58b64f32caf91e60040ec1b029a2
7
- data.tar.gz: 02e6e9bdc4379b2542fe6802160f2946ac9d151878bea3f51bcd27da749f7e0bb2cf2891aa0ddf88f5a8221185d8ab9e1bdab4e639de6d69c2a44409e734626c
6
+ metadata.gz: 4c2530ece05aa6e2bb8e0437f38f5d198a9a081039101ec03c836a01162a92685c5dc6dc2d90f1608c1466002b1afcca3fdf9ddb36774ff1b47a2d438f7cb0ae
7
+ data.tar.gz: cf6f93b2f8f869354e594ee8e5932acd96ecd78201223a6630f74d6fc13423d220459d9524a81266342f23913304f41a30c3face46be1291022e09b2f15caee3
@@ -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 multile `-e` in `uby-next`. ([@palkan][])
77
+ - Handle multiple `-e` in `uby-next`. ([@palkan][])
71
78
 
72
79
  ## 0.1.0 (2019-11-16)
73
80
 
@@ -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 dependendices (if these dependencies are not using refinements 🙂)
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, or using edge Ruby syntax features
96
+ ## Transpiling
97
+
98
+ Ruby Next allows you to transpile\* edge Ruby syntax to older versions.
84
99
 
85
- Ruby Next transpiler relies on two libraries: [parser][] and [unparser][].
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
- ### Integrating into a gem development
121
+ ### Transpiler modes
107
122
 
108
- We recommend _pre-transpiling_ source code to work with older versions before releasing it.
123
+ Ruby Next currently provides two different modes of generating transpiled code: _AST_ and _rewrite_.
109
124
 
110
- This is how you can do that with Ruby Next:
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
- - Write source code using the modern/edge Ruby syntax.
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
- - Generate transpiled code by calling `ruby-next nextify ./lib` (e.g., before releasing or pushing to VCS).
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
- 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.
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
- **NOTE:** Do not edit these files manually, either run linters/type checkers/whatever against these files.
133
+ You can change the transpiler mode:
119
134
 
120
- - Add the following code to your gem's _entrypoint_ (the file that is required first and contains other `require`-s):
121
-
122
- ```ruby
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 usecase is to backport the APIs required by pattern matching. You can do this, for example,
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
- ## Runtime mode
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
- ## Unofficial/experimental features
307
+ ## Proposed and edge features
274
308
 
275
- Ruby Next also provides support for some features not-yet-merged into Ruby master (or reverted).
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
- Currenly, the only such feature is the [_method reference_ operator](https://bugs.ruby-lang.org/issues/13581):
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
@@ -132,7 +132,7 @@ module RubyNext
132
132
  end
133
133
 
134
134
  # Use refinements by default
135
- self.strategy = :refine
135
+ self.strategy = ENV.fetch("RUBY_NEXT_CORE_STRATEGY", "refine").to_sym
136
136
  end
137
137
  end
138
138
 
@@ -63,14 +63,41 @@ module RubyNext
63
63
  attr_accessor :rewriters
64
64
  attr_reader :watch_dirs
65
65
 
66
- def transform(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new)
67
- parse(source).then do |ast|
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
@@ -36,6 +36,8 @@ module RubyNext
36
36
  :INFINITY)
37
37
  end
38
38
 
39
+ replace(node.loc.expression, "#{node.children.first.loc.expression.source}..#{unparse(new_end)}")
40
+
39
41
  node.updated(
40
42
  :irange,
41
43
  [
@@ -12,6 +12,13 @@ module RubyNext
12
12
 
13
13
  receiver, mid = *node.children
14
14
 
15
+ replace(
16
+ node.children.first.loc.expression.end.join(
17
+ node.loc.expression.end
18
+ ),
19
+ ".method(:#{mid})"
20
+ )
21
+
15
22
  node.updated(
16
23
  :send,
17
24
  [
@@ -12,14 +12,20 @@ module RubyNext
12
12
  def on_numblock(node)
13
13
  context.track! self
14
14
 
15
- proc_or_lambda, num, *rest = *node.children
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
- *rest
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
- build_if_clause(node.children[1], node.children[2..-1])
243
+ build_case_when(node.children[1..-1])
244
244
  end
245
245
 
246
- # remove unused predicate assignments and truthy expressions
247
- patterns = predicates.process(patterns)
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, patterns
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 build_if_clause(node, rest)
294
- if node&.type == :in_pattern
295
- predicates.reset!
296
- build_in_pattern(node, rest)
297
- else
298
- raise "Unexpected else in the middle of case ... in" if rest && rest.size > 0
299
- # else clause must be present
300
- (process(node) || no_matching_pattern).then do |else_node|
301
- next else_node unless else_node.type == :empty_else
302
- s(:empty)
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 build_in_pattern(clause, rest)
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
- if rest && rest.size > 0
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
- TOPLEVEL_BINDING.eval(new_contents, path)
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
@@ -19,6 +19,8 @@ module RubyNext
19
19
  dirname = File.dirname(basename)
20
20
  end
21
21
 
22
+ dirname = File.realpath(dirname)
23
+
22
24
  current_index = $LOAD_PATH.index(dirname)
23
25
 
24
26
  raise "Gem's lib is not in the $LOAD_PATH: #{dirname}" if current_index.nil?
@@ -20,7 +20,7 @@ module RubyNext
20
20
 
21
21
  $LOAD_PATH.find do |lp|
22
22
  lpath = File.join(lp, path)
23
- return lpath if File.file?(lpath)
23
+ return File.realpath(lpath) if File.file?(lpath)
24
24
  end
25
25
  end
26
26
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyNext
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.0"
5
5
  end
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.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-09 00:00:00.000000000 Z
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.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.0
26
+ version: 2.7.0.5
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: unparser
29
29
  requirement: !ruby/object:Gem::Requirement