ruby-next 0.0.1.26 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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