ruby-next 0.0.1.26 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +201 -11
  5. data/bin/parse +19 -0
  6. data/bin/ruby-next +16 -0
  7. data/bin/transform +18 -0
  8. data/lib/ruby-next/cli.rb +55 -0
  9. data/lib/ruby-next/commands/base.rb +42 -0
  10. data/lib/ruby-next/commands/nextify.rb +113 -0
  11. data/lib/ruby-next/core/array/difference_union_intersection.rb +31 -0
  12. data/lib/ruby-next/core/enumerable/filter.rb +23 -0
  13. data/lib/ruby-next/core/enumerable/filter_map.rb +50 -0
  14. data/lib/ruby-next/core/enumerable/tally.rb +28 -0
  15. data/lib/ruby-next/core/hash/merge.rb +16 -0
  16. data/lib/ruby-next/core/kernel/then.rb +12 -0
  17. data/lib/ruby-next/core/pattern_matching.rb +37 -0
  18. data/lib/ruby-next/core/proc/compose.rb +21 -0
  19. data/lib/ruby-next/core/runtime.rb +10 -0
  20. data/lib/ruby-next/core.rb +28 -0
  21. data/lib/ruby-next/language/parser.rb +23 -0
  22. data/lib/ruby-next/language/rewriters/args_forward.rb +57 -0
  23. data/lib/ruby-next/language/rewriters/base.rb +87 -0
  24. data/lib/ruby-next/language/rewriters/endless_range.rb +60 -0
  25. data/lib/ruby-next/language/rewriters/method_reference.rb +27 -0
  26. data/lib/ruby-next/language/rewriters/numbered_params.rb +41 -0
  27. data/lib/ruby-next/language/rewriters/pattern_matching.rb +464 -0
  28. data/lib/ruby-next/language/runtime.rb +149 -0
  29. data/lib/ruby-next/language/setup.rb +43 -0
  30. data/lib/ruby-next/language/unparser.rb +23 -0
  31. data/lib/ruby-next/language.rb +100 -0
  32. data/lib/ruby-next/utils.rb +39 -0
  33. data/lib/ruby-next/version.rb +5 -0
  34. data/lib/ruby-next.rb +37 -0
  35. data/lib/uby-next.rb +66 -0
  36. metadata +69 -7
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ unless [].respond_to?(:tally)
4
+ module RubyNext
5
+ module Core
6
+ module EnumerableTally
7
+ def tally
8
+ each_with_object({}) do |v, acc|
9
+ acc[v] ||= 0
10
+ acc[v] += 1
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ RubyNext.module_eval do
18
+ refine Enumerable do
19
+ include RubyNext::Core::EnumerableTally
20
+ end
21
+
22
+ # Refine Array seprately, 'cause refining modules is vulnerable to prepend:
23
+ # - https://bugs.ruby-lang.org/issues/13446
24
+ refine Array do
25
+ include RubyNext::Core::EnumerableTally
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ unless {}.method(:merge).arity == -1
4
+ RubyNext.module_eval do
5
+ refine Hash do
6
+ def merge(*others)
7
+ return super if others.size == 1
8
+ return dup if others.size == 0
9
+
10
+ merge(others.shift).tap do |new_h|
11
+ others.each { |h| new_h.merge!(h) }
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ unless Object.new.respond_to?(:then)
4
+ RubyNext.module_eval do
5
+ # Refine object, 'cause refining modules (Kernel) is vulnerable to prepend:
6
+ # - https://bugs.ruby-lang.org/issues/13446
7
+ # - Rails added `Kernel.prepend` in 6.1: https://github.com/rails/rails/commit/3124007bd674dcdc9c3b5c6b2964dfb7a1a0733c
8
+ refine Object do
9
+ alias then yield_self
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ unless defined?(NoMatchingPatternError)
4
+ class NoMatchingPatternError < RuntimeError
5
+ end
6
+ end
7
+
8
+ # Add `#deconstruct` and `#deconstruct_keys` to core classes
9
+ unless [].respond_to?(:deconstruct)
10
+ RubyNext.module_eval do
11
+ refine Array do
12
+ def deconstruct
13
+ self
14
+ end
15
+ end
16
+
17
+ refine Struct do
18
+ alias deconstruct to_a
19
+ end
20
+ end
21
+ end
22
+
23
+ unless {}.respond_to?(:deconstruct_keys)
24
+ RubyNext.module_eval do
25
+ refine Hash do
26
+ def deconstruct_keys(_)
27
+ self
28
+ end
29
+ end
30
+
31
+ refine Struct do
32
+ def deconstruct_keys(_)
33
+ to_h
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Style/LambdaCall
4
+ unless proc {}.respond_to?(:<<)
5
+ RubyNext.module_eval do
6
+ refine Proc do
7
+ def <<(other)
8
+ raise TypeError, "callable object is expected" unless other.respond_to?(:call)
9
+ this = self
10
+ proc { |*args, &block| this.(other.(*args, &block)) }
11
+ end
12
+
13
+ def >>(other)
14
+ raise TypeError, "callable object is expected" unless other.respond_to?(:call)
15
+ this = self
16
+ proc { |*args, &block| other.(this.(*args, &block)) }
17
+ end
18
+ end
19
+ end
20
+ end
21
+ # rubocop:enable Style/LambdaCall
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Extend `Language.transform` to inject `using RubyNext` to every file
4
+ RubyNext::Language.singleton_class.prepend(Module.new do
5
+ def transform(contents, **hargs)
6
+ # We cannot activate refinements in eval
7
+ RubyNext::Core.inject!(contents) unless hargs[:eval]
8
+ super(contents, **hargs)
9
+ end
10
+ end)
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Core
5
+ class << self
6
+ # Inject `using RubyNext` at the top of the source code
7
+ def inject!(contents)
8
+ contents.sub!(/^(\s*[^#\s].*)/, 'using RubyNext;\1')
9
+ contents
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ require_relative "core/kernel/then"
16
+
17
+ require_relative "core/proc/compose"
18
+
19
+ require_relative "core/enumerable/tally"
20
+ require_relative "core/enumerable/filter"
21
+ require_relative "core/enumerable/filter_map"
22
+
23
+ require_relative "core/array/difference_union_intersection"
24
+
25
+ require_relative "core/hash/merge"
26
+
27
+ # Core extensions required for pattern matching
28
+ require_relative "core/pattern_matching"
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby-next/utils"
4
+
5
+ require "parser/ruby27"
6
+ RubyNext::Language::Parser = Parser::Ruby27
7
+
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
13
+
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)
21
+ end
22
+ end
23
+ end)
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class ArgsForward < Base
7
+ SYNTAX_PROBE = "obj = Object.new; def obj.foo(...) super(...); end"
8
+ MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
9
+
10
+ REST = :__rest__
11
+ BLOCK = :__block__
12
+
13
+ def on_forward_args(node)
14
+ context.track! self
15
+
16
+ node.updated(
17
+ :args,
18
+ [
19
+ s(:restarg, REST),
20
+ s(:blockarg, BLOCK)
21
+ ]
22
+ )
23
+ end
24
+
25
+ def on_send(node)
26
+ return unless node.children[2]&.type == :forwarded_args
27
+
28
+ node.updated(
29
+ nil,
30
+ [
31
+ *node.children[0..1],
32
+ *forwarded_args
33
+ ]
34
+ )
35
+ end
36
+
37
+ def on_super(node)
38
+ return unless node.children[0]&.type == :forwarded_args
39
+
40
+ node.updated(
41
+ nil,
42
+ forwarded_args
43
+ )
44
+ end
45
+
46
+ private
47
+
48
+ def forwarded_args
49
+ [
50
+ s(:splat, s(:lvar, REST)),
51
+ s(:block_pass, s(:lvar, BLOCK))
52
+ ]
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ using RubyNext
4
+
5
+ module RubyNext
6
+ module Language
7
+ module Rewriters
8
+ class Base < ::Parser::TreeRewriter
9
+ class LocalsTracker
10
+ attr_reader :stacks
11
+
12
+ def initialize
13
+ @stacks = []
14
+ end
15
+
16
+ def with(**locals)
17
+ stacks << locals
18
+ yield.tap { stacks.pop }
19
+ end
20
+
21
+ def [](name, suffix = nil)
22
+ fetch(name).then do |name|
23
+ next name unless suffix
24
+ :"#{name}#{suffix}__"
25
+ end
26
+ end
27
+
28
+ def key?(name)
29
+ !!fetch(name) { false }
30
+ end
31
+
32
+ def fetch(name)
33
+ ind = -1
34
+
35
+ loop do
36
+ break stacks[ind][name] if stacks[ind].key?(name)
37
+ ind -= 1
38
+ break if stacks[ind].nil?
39
+ end.then do |name|
40
+ next name unless name.nil?
41
+
42
+ return yield if block_given?
43
+ raise ArgumentError, "Local var not found in scope: #{name}"
44
+ end
45
+ end
46
+ end
47
+
48
+ class << self
49
+ # Returns true if the syntax is supported
50
+ # by the current Ruby (performs syntax check, not version check)
51
+ def unsupported_syntax?
52
+ save_verbose, $VERBOSE = $VERBOSE, nil
53
+ eval_mid = Kernel.respond_to?(:eval_without_ruby_next) ? :eval_without_ruby_next : :eval
54
+ Kernel.send eval_mid, self::SYNTAX_PROBE
55
+ false
56
+ rescue SyntaxError, NameError
57
+ true
58
+ ensure
59
+ $VERBOSE = save_verbose
60
+ end
61
+
62
+ # Returns true if the syntax is supported
63
+ # by the specified version
64
+ def unsupported_version?(version)
65
+ self::MIN_SUPPORTED_VERSION > version
66
+ end
67
+ end
68
+
69
+ attr_reader :locals
70
+
71
+ def initialize(context)
72
+ @context = context
73
+ @locals = LocalsTracker.new
74
+ super()
75
+ end
76
+
77
+ def s(type, *children)
78
+ ::Parser::AST::Node.new(type, children)
79
+ end
80
+
81
+ private
82
+
83
+ attr_reader :context
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class EndlessRange < Base
7
+ SYNTAX_PROBE = "[0, 1][1..]"
8
+ MIN_SUPPORTED_VERSION = Gem::Version.new("2.6.0")
9
+
10
+ def on_index(node)
11
+ @current_index = node
12
+ new_index = process(node.children.last)
13
+ return unless new_index != node.children.last
14
+
15
+ node.updated(
16
+ nil,
17
+ [
18
+ node.children.first,
19
+ new_index
20
+ ]
21
+ )
22
+ end
23
+
24
+ def on_erange(node)
25
+ return unless node.children.last.nil?
26
+
27
+ context.track! self
28
+
29
+ new_end =
30
+ if index_arg?(node)
31
+ s(:int, -1)
32
+ else
33
+ s(:const,
34
+ s(:const,
35
+ s(:cbase), :Float),
36
+ :INFINITY)
37
+ end
38
+
39
+ node.updated(
40
+ :irange,
41
+ [
42
+ node.children.first,
43
+ new_end
44
+ ]
45
+ )
46
+ end
47
+
48
+ alias_method :on_irange, :on_erange
49
+
50
+ private
51
+
52
+ attr_reader :current_index
53
+
54
+ def index_arg?(node)
55
+ current_index&.children&.include?(node)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class MethodReference < Base
7
+ SYNTAX_PROBE = "Language.:transform"
8
+ MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
9
+
10
+ def on_meth_ref(node)
11
+ context.track! self
12
+
13
+ receiver, mid = *node.children
14
+
15
+ node.updated(
16
+ :send,
17
+ [
18
+ receiver,
19
+ :method,
20
+ s(:sym, mid)
21
+ ]
22
+ )
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ using RubyNext
4
+
5
+ module RubyNext
6
+ module Language
7
+ module Rewriters
8
+ class NumberedParams < Base
9
+ SYNTAX_PROBE = "proc { _1 }.call(1)"
10
+ MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
11
+
12
+ def on_numblock(node)
13
+ context.track! self
14
+
15
+ proc_or_lambda, num, *rest = *node.children
16
+
17
+ node.updated(
18
+ :block,
19
+ [
20
+ proc_or_lambda,
21
+ proc_args(num),
22
+ *rest
23
+ ]
24
+ )
25
+ end
26
+
27
+ private
28
+
29
+ def proc_args(n)
30
+ return s(:args, s(:procarg0, :_1)) if n == 1
31
+
32
+ (1..n).map do |numero|
33
+ s(:arg, :"_#{numero}")
34
+ end.then do |args|
35
+ s(:args, *args)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end