Module: RubyNext::Language

Defined in:
lib/ruby-next/language.rb,
lib/ruby-next/language/eval.rb,
lib/ruby-next/language/setup.rb,
lib/ruby-next/language/parser.rb,
lib/ruby-next/language/runtime.rb,
lib/ruby-next/language/rewriters/base.rb,
lib/ruby-next/language/rewriters/text.rb,
lib/ruby-next/language/paco_parsers/base.rb,
lib/ruby-next/language/rewriters/2.4/dir.rb,
lib/ruby-next/language/rewriters/abstract.rb,
lib/ruby-next/language/paco_parsers/comments.rb,
lib/ruby-next/language/rewriters/edge/it_param.rb,
lib/ruby-next/language/rewriters/3.0/in_pattern.rb,
lib/ruby-next/language/rewriters/2.7/args_forward.rb,
lib/ruby-next/language/rewriters/3.0/find_pattern.rb,
lib/ruby-next/language/rewriters/2.6/endless_range.rb,
lib/ruby-next/language/paco_parsers/string_literals.rb,
lib/ruby-next/language/rewriters/3.0/endless_method.rb,
lib/ruby-next/language/rewriters/3.1/shorthand_hash.rb,
lib/ruby-next/language/rewriters/2.1/required_kwargs.rb,
lib/ruby-next/language/rewriters/2.3/safe_navigation.rb,
lib/ruby-next/language/rewriters/2.7/numbered_params.rb,
lib/ruby-next/language/rewriters/3.1/anonymous_block.rb,
lib/ruby-next/language/rewriters/2.1/numeric_literals.rb,
lib/ruby-next/language/rewriters/2.3/squiggly_heredoc.rb,
lib/ruby-next/language/rewriters/2.7/pattern_matching.rb,
lib/ruby-next/language/rewriters/3.1/pin_vars_pattern.rb,
lib/ruby-next/language/rewriters/3.2/anonymous_restargs.rb,
lib/ruby-next/language/rewriters/2.5/rescue_within_block.rb,
lib/ruby-next/language/rewriters/3.0/args_forward_leading.rb,
lib/ruby-next/language/rewriters/proposed/method_reference.rb,
lib/ruby-next/language/rewriters/3.1/endless_method_command.rb,
lib/ruby-next/language/rewriters/proposed/bind_vars_pattern.rb,
lib/ruby-next/language/rewriters/3.1/refinement_import_methods.rb,
lib/ruby-next/language/rewriters/3.1/oneline_pattern_parensless.rb

Overview

Language module contains tools to transpile newer Ruby syntax into an older one.

It works the following way:

- Takes a Ruby source code as input
- Generates the AST using the edge parser (via the `parser` gem)
- Pass this AST through the list of processors (one feature = one processor)
- Each processor may modify the AST
- Generates a transpiled source code from the transformed AST (via the `unparser` gem)

Defined Under Namespace

Modules: BuilderExt, ClassEval, Eval, GemTranspiler, InstanceEval, KernelEval, PacoParsers, Rewriters, Runtime Classes: Builder, TransformContext

Constant Summary collapse

RewriterNotFoundError =
Class.new(StandardError)
MODES =
%i[rewrite ast].freeze

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.exclude_patternsObject

Returns the value of attribute exclude_patterns.



69
70
71
# File 'lib/ruby-next/language.rb', line 69

def exclude_patterns
  @exclude_patterns
end

.include_patternsObject

Returns the value of attribute include_patterns.



68
69
70
# File 'lib/ruby-next/language.rb', line 68

def include_patterns
  @include_patterns
end

.modeObject

Returns the value of attribute mode.



82
83
84
# File 'lib/ruby-next/language.rb', line 82

def mode
  @mode
end

.parser_classObject

Returns the value of attribute parser_class.



37
38
39
# File 'lib/ruby-next/language/parser.rb', line 37

def parser_class
  @parser_class
end

.parser_syntax_errorsObject

Returns the value of attribute parser_syntax_errors.



37
38
39
# File 'lib/ruby-next/language/parser.rb', line 37

def parser_syntax_errors
  @parser_syntax_errors
end

.rewritersObject

Returns the value of attribute rewriters.



76
77
78
# File 'lib/ruby-next/language.rb', line 76

def rewriters
  @rewriters
end

.strategyObject

Returns the value of attribute strategy.



78
79
80
# File 'lib/ruby-next/language.rb', line 78

def strategy
  @strategy
end

.watch_dirsObject



71
72
73
74
# File 'lib/ruby-next/language.rb', line 71

def watch_dirs
  warn "[DEPRECATED] Use `RubyNext::Language.include_patterns` instead of `RubyNext::Language.watch_dirs`"
  @watch_dirs
end

Class Method Details

.ast?Boolean

Returns:

  • (Boolean)


93
94
95
# File 'lib/ruby-next/language.rb', line 93

def ast?
  mode == :ast
end

.current_rewritersObject

Rewriters required for the current version



149
150
151
# File 'lib/ruby-next/language.rb', line 149

def current_rewriters
  @current_rewriters ||= rewriters.select(&:unsupported_syntax?)
end

.parse(source, file = "(string)") ⇒ Object



47
48
49
50
51
52
53
54
55
# File 'lib/ruby-next/language/parser.rb', line 47

def parse(source, file = "(string)")
  buffer = ::Parser::Source::Buffer.new(file).tap do |buffer|
    buffer.source = source
  end

  parser.parse(buffer)
rescue *parser_syntax_errors => e
  raise ::SyntaxError, e.message, e.backtrace
end

.parse_with_comments(source, file = "(string)") ⇒ Object



57
58
59
60
61
62
63
64
65
# File 'lib/ruby-next/language/parser.rb', line 57

def parse_with_comments(source, file = "(string)")
  buffer = ::Parser::Source::Buffer.new(file).tap do |buffer|
    buffer.source = source
  end

  parser.parse_with_comments(buffer)
rescue *parser_syntax_errors => e
  raise ::SyntaxError, e.message, e.backtrace
end

.parserObject



39
40
41
42
43
44
45
# File 'lib/ruby-next/language/parser.rb', line 39

def parser
  parser_class.new(Builder.new).tap do |prs|
    prs.diagnostics.tap do |diagnostics|
      diagnostics.all_errors_are_fatal = true
    end
  end
end

.rewrite?Boolean

Returns:

  • (Boolean)


89
90
91
# File 'lib/ruby-next/language.rb', line 89

def rewrite?
  mode == :rewrite?
end

.runtime!Object



97
98
99
100
101
# File 'lib/ruby-next/language.rb', line 97

def runtime!
  require "ruby-next/language/rewriters/runtime"

  @runtime = true
end

.runtime?Boolean

Returns:

  • (Boolean)


103
104
105
# File 'lib/ruby-next/language.rb', line 103

def runtime?
  @runtime
end

.select_rewriters(*names) ⇒ Object

This method guarantees that rewriters will be returned in order they defined in Language module



154
155
156
157
158
159
160
161
# File 'lib/ruby-next/language.rb', line 154

def select_rewriters(*names)
  rewriters_delta = names - rewriters.map { |rewriter| rewriter::NAME }
  if rewriters_delta.any?
    raise RewriterNotFoundError, "Rewriters not found: #{rewriters_delta.join(",")}"
  end

  rewriters.select { |rewriter| names.include?(rewriter::NAME) }
end

.setup_gem_load_path(lib_dir = "lib", rbnext_dir: RUBY_NEXT_DIR, transpile: false) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/ruby-next/language/setup.rb', line 34

def setup_gem_load_path(lib_dir = "lib", rbnext_dir: RUBY_NEXT_DIR, transpile: false)
  called_from = caller_locations(1, 1).first.path
  dirname = File.realpath(File.dirname(called_from))

  loop do
    basename = File.basename(dirname)
    raise "Couldn't find gem's load dir: #{lib_dir}" if basename == dirname

    break if basename == lib_dir

    dirname = File.dirname(basename)
  end

  dirname = File.realpath(dirname)

  return if Language.runtime? && Language.target_dir?(dirname)

  next_dirname = File.join(dirname, rbnext_dir)

  GemTranspiler.maybe_transpile(File.dirname(dirname), lib_dir, next_dirname) if transpile

  current_index = $LOAD_PATH.find_index do |load_path|
    pn = Pathname.new(load_path)
    pn.exist? && pn.realpath.to_s == dirname
  end

  raise "Gem's lib is not in the $LOAD_PATH: #{dirname}" if current_index.nil?

  version = RubyNext.next_ruby_version

  loop do
    break unless version

    version_dir = File.join(next_dirname, version.segments[0..1].join("."))

    if File.exist?(version_dir)
      $LOAD_PATH.insert current_index, version_dir
      current_index += 1
    end

    version = RubyNext.next_ruby_version(version)
  end
end

.target_dir?(dirname) ⇒ Boolean

Returns:

  • (Boolean)


135
136
137
138
139
140
141
# File 'lib/ruby-next/language.rb', line 135

def target_dir?(dirname)
  # fnmatch? requires a file name, not a folder
  fname = File.join(dirname, "x.rb")

  include_patterns.any? { |pattern| File.fnmatch?(pattern, fname) } &&
    exclude_patterns.none? { |pattern| File.fnmatch?(pattern, fname) }
end

.transform(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/ruby-next/language.rb', line 107

def transform(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new)
  text_rewriters, ast_rewriters = rewriters.partition(&:text?)

  retried = 0
  new_source = text_rewrite(source, rewriters: text_rewriters, using: using, context: context)

  begin
    new_source =
      if mode == :rewrite
        rewrite(new_source, rewriters: ast_rewriters, using: using, context: context)
      else
        regenerate(new_source, rewriters: ast_rewriters, using: using, context: context)
      end
  rescue Unparser::UnknownNodeError => err
    RubyNext.warn "Ruby Next fallbacks to \"rewrite\" transpiling mode since the version of Unparser you use doesn't support some syntax yet: #{err.message}.\n" \
      "Try upgrading the Unparser or set transpiling mode to \"rewrite\" in case you use some edge or experimental syntax."
    self.mode = :rewrite
    retried += 1
    retry unless retried > 1
    raise
  end

  return new_source unless RubyNext::Core.refine?
  return new_source unless using && context.use_ruby_next?

  Core.inject! new_source.dup
end

.transformable?(path) ⇒ Boolean

Returns:

  • (Boolean)


143
144
145
146
# File 'lib/ruby-next/language.rb', line 143

def transformable?(path)
  include_patterns.any? { |pattern| File.fnmatch?(pattern, path) } &&
    exclude_patterns.none? { |pattern| File.fnmatch?(pattern, path) }
end

Instance Method Details

#runtime?Boolean

Returns:

  • (Boolean)


29
30
31
# File 'lib/ruby-next/language/setup.rb', line 29

def runtime?
  false
end