module RubyNext::Language
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)
Constants
- MODES
- RewriterNotFoundError
Attributes
mode[R]
rewriters[RW]
strategy[RW]
watch_dirs[RW]
Public Class Methods
ast?()
click to toggle source
# File lib/ruby-next/language.rb, line 83 def ast? mode == :ast end
current_rewriters()
click to toggle source
Rewriters
required for the current version
# File lib/ruby-next/language.rb, line 151 def current_rewriters @current_rewriters ||= rewriters.select(&:unsupported_syntax?) end
mode=(val)
click to toggle source
# File lib/ruby-next/language.rb, line 74 def mode=(val) raise ArgumentError, "Unknown mode: #{val}. Available: #{MODES.join(",")}" unless MODES.include?(val) @mode = val end
parse(source, file = "(string)")
click to toggle source
# File lib/ruby-next/language/parser.rb, line 36 def parse(source, file = "(string)") buffer = ::Parser::Source::Buffer.new(file).tap do |buffer| buffer.source = source end parser.parse(buffer) rescue ::Parser::SyntaxError => e raise ::SyntaxError, e.message end
parse_with_comments(source, file = "(string)")
click to toggle source
# File lib/ruby-next/language/parser.rb, line 46 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::SyntaxError => e raise ::SyntaxError, e.message end
parser()
click to toggle source
# File lib/ruby-next/language/parser.rb, line 28 def parser ::Parser::RubyNext.new(Builder.new).tap do |prs| prs.diagnostics.tap do |diagnostics| diagnostics.all_errors_are_fatal = true end end end
regenerate(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new)
click to toggle source
# File lib/ruby-next/language.rb, line 112 def regenerate(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new) parse_with_comments(source).then do |(ast, comments)| rewriters.inject(ast) do |tree, rewriter| rewriter.new(context).process(tree) end.then do |new_ast| next source unless context.dirty? Unparser.unparse(new_ast, comments) end.then do |source| next source unless RubyNext::Core.refine? next source unless using && context.use_ruby_next? Core.inject! source.dup end end end
rewrite(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new)
click to toggle source
# File lib/ruby-next/language.rb, line 129 def rewrite(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new) rewriters.inject(source) do |src, rewriter| buffer = Parser::Source::Buffer.new("<dynamic>") buffer.source = src rewriter.new(context).rewrite(buffer, parse(src)) end.then do |new_source| next source unless context.dirty? new_source end.then do |source| next source unless RubyNext::Core.refine? next source unless using && context.use_ruby_next? Core.inject! source.dup end end
rewrite?()
click to toggle source
# File lib/ruby-next/language.rb, line 79 def rewrite? mode == :rewrite? end
runtime!()
click to toggle source
# File lib/ruby-next/language.rb, line 87 def runtime! require "ruby-next/language/rewriters/runtime" @runtime = true end
runtime?()
click to toggle source
# File lib/ruby-next/language.rb, line 93 def runtime? @runtime end
select_rewriters(*names)
click to toggle source
This method guarantees that rewriters will be returned in order they defined in Language
module
# File lib/ruby-next/language.rb, line 156 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)
click to toggle source
# File lib/ruby-next/language/setup.rb, line 31 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.watch_dirs.include?(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| Pathname.new(load_path).cleanpath.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
transform(*args, **kwargs)
click to toggle source
# File lib/ruby-next/language.rb, line 97 def transform(*args, **kwargs) if mode == :rewrite rewrite(*args, **kwargs) else regenerate(*args, **kwargs) end rescue Unparser::UnknownNodeError if Gem::Version.new(::RubyNext.current_ruby_version) >= Gem::Version.new("3.0.0") RubyNext.warn "Ruby Next fallbacks to \"rewrite\" transpiling mode since Unparser doesn't support 3.0+ AST yet.\n" \ "See https://github.com/mbj/unparser/issues/168" self.mode = :rewrite end rewrite(*args, **kwargs) end
transformable?(path)
click to toggle source
# File lib/ruby-next/language.rb, line 146 def transformable?(path) watch_dirs.any? { |dir| path.start_with?(dir) } end