class Riml::AST_Rewriter

Attributes

ast[RW]
classes[R]
options[RW]

Public Class Methods

new(ast = nil, classes = nil, class_dependency_graph = nil) click to toggle source
# File lib/riml/ast_rewriter.rb, line 14
def initialize(ast = nil, classes = nil, class_dependency_graph = nil)
  @ast = ast
  @classes = classes
  @class_dependency_graph = class_dependency_graph
  # only the top-most rewriter has these properties and defaults
  if self.instance_of?(Riml::AST_Rewriter)
    @classes ||= ClassMap.new
    # AST_Rewriter shares options with Parser. Parser set AST_Rewriter's
    # options before call to `rewrite`.
    @options = nil
    # Keeps track of filenames with their rewritten ASTs, to prevent rewriting
    # the same AST more than once.
    @rewritten_included_and_sourced_files = {}
    # Keeps track of which filenames included/sourced which.
    # ex: { nil => ["main.riml"], "main.riml" => ["lib1.riml", "lib2.riml"],
    # "lib1.riml" => [], "lib2.riml" => [] }
    @included_and_sourced_file_refs = Hash.new { |h, k| h[k] = [] }
    @class_dependency_graph ||= ClassDependencyGraph.new
    @resolving_class_dependencies = nil
  end
end

Public Instance Methods

add_SID_function!() click to toggle source

:h <SID>

# File lib/riml/ast_rewriter.rb, line 289
def add_SID_function!
  fchild = ast.nodes.first
  return false if DefNode === fchild && fchild.name == 'SID' && fchild.scope_modifier == 's:'
  fn = DefNode.new('!', nil, 's:', 'SID', [], nil, Nodes.new([
      IfNode.new(
        CallNode.new(nil, 'exists', [StringNode.new('s:SID_VALUE', :s)]),
        Nodes.new([
          ReturnNode.new(GetVariableNode.new('s:', 'SID_VALUE'))
        ])
      ),
      AssignNode.new(
        '=',
        GetVariableNode.new('s:', 'SID_VALUE'),
        CallNode.new(nil, 'matchstr', [
        CallNode.new(nil, 'expand', [StringNode.new('<sfile>', :s)]),
        StringNode.new('<SNR>\zs\d\+\ze_SID$', :s)
      ]
      )),
      ReturnNode.new(GetVariableNode.new('s:', 'SID_VALUE'))
    ])
  )
  fn.parent = ast.nodes
  establish_parents(fn)
  ast.nodes.unshift fn
end
add_SID_function?(filename) click to toggle source

Add SID function if this is the main file and it has defined classes, or if it included other files and any one of those other files defined classes.

# File lib/riml/ast_rewriter.rb, line 272
def add_SID_function?(filename)
  return true if ast.children.grep(ClassDefinitionNode).any?
  included_files = @included_and_sourced_file_refs[filename]
  while included_files.any?
    incs = []
    included_files.each do |included_file|
      if (ast = @rewritten_included_and_sourced_files[included_file])
        return true if ast.children.grep(ClassDefinitionNode).any?
      end
      incs.concat @included_and_sourced_file_refs[included_file]
    end
    included_files = incs
  end
  false
end
do_establish_parents(node) click to toggle source
# File lib/riml/ast_rewriter.rb, line 79
def do_establish_parents(node)
  node.children.each do |child|
    child.parent_node = node if Visitable === child
  end if Visitable === node
end
do_rewrite_on_match(node) click to toggle source
# File lib/riml/ast_rewriter.rb, line 89
def do_rewrite_on_match(node)
  replace node if match?(node)
end
establish_parents(node) click to toggle source
# File lib/riml/ast_rewriter.rb, line 74
def establish_parents(node)
  Walker.walk_node(node, method(:do_establish_parents))
end
Also aliased as: reestablish_parents
max_recursion_lvl() click to toggle source

recurse until no more children

# File lib/riml/ast_rewriter.rb, line 266
def max_recursion_lvl
  -1
end
reestablish_parents(node)
Alias for: establish_parents
reorder_includes_based_on_class_dependencies!() click to toggle source
# File lib/riml/ast_rewriter.rb, line 132
def reorder_includes_based_on_class_dependencies!
  global_included_filename_order = @class_dependency_graph.filename_order
  asts = [ast]
  while (ast = asts.shift)
    include_nodes =
      ast.children.grep(RimlFileCommandNode).select do |n|
        n.name == 'riml_include'
      end
    included_filenames = include_nodes.map { |n| n.arguments.map(&:value) }.flatten
    new_order_filenames = global_included_filename_order & included_filenames
    add_to_head = included_filenames - new_order_filenames
    new_order_filenames = add_to_head + new_order_filenames
    include_nodes.each do |node|
      node.arguments.each do |arg|
        if (included_file_ast = @included_ASTs_by_include_file[arg.value])
          asts << included_file_ast
        end
        if new_order_filenames.first
          arg.value = new_order_filenames.shift
        else
          # for now, just to be cautious
          raise "Internal error in AST rewriting process. Please report bug!"
        end
      end
    end
  end
end
resolve_class_dependencies!(filename) click to toggle source
# File lib/riml/ast_rewriter.rb, line 98
def resolve_class_dependencies!(filename)
  if @resolving_class_dependencies.nil?
    start_resolving = @resolving_class_dependencies = true
    @included_ASTs_by_include_file = {}
  end
  old_ast = ast
  RegisterClassDependencies.new(ast, classes, @class_dependency_graph, filename).rewrite_on_match
  ast.children.grep(RimlFileCommandNode).each do |node|
    next unless node.name == 'riml_include'
    node.each_existing_file! do |file, fullpath|
      if filename && @included_and_sourced_file_refs[file].include?(filename)
        msg = "#{filename.inspect} can't include #{file.inspect}, as " \
              " #{file.inspect} already included #{filename.inspect}"
        error = IncludeFileLoop.new(msg, node)
        raise error
      elsif filename == file
        error = UserArgumentError.new("#{file.inspect} can't include itself", node)
        raise error
      end
      @included_and_sourced_file_refs[filename] << file
      riml_src = File.read(fullpath)
      Parser.new.tap { |p| p.options = @options }.parse(riml_src, self, file, true)
      @included_ASTs_by_include_file[file] = Parser.ast_cache[file]
    end
  end
ensure
  self.ast = old_ast
  if start_resolving == true
    @resolving_class_dependencies = false
    @included_and_sourced_file_refs.clear
    reorder_includes_based_on_class_dependencies!
  end
end
resolve_class_dependencies?() click to toggle source
# File lib/riml/ast_rewriter.rb, line 93
def resolve_class_dependencies?
  @resolving_class_dependencies = false if @options[:include_reordering] != true
  @resolving_class_dependencies != false
end
rewrite(filename = nil, included = false) click to toggle source
# File lib/riml/ast_rewriter.rb, line 36
def rewrite(filename = nil, included = false)
  if filename && (rewritten_ast = Riml.rewritten_ast_cache[filename])
    return rewritten_ast
  end

  establish_parents(ast)
  if @options && @options[:allow_undefined_global_classes] && !@classes.has_global_import?
    @classes.globbed_imports.unshift(ImportedClass.new('*'))
  end
  class_imports = RegisterImportedClasses.new(ast, classes)
  class_imports.rewrite_on_match
  if resolve_class_dependencies?
    resolve_class_dependencies!(filename)
    return if @resolving_class_dependencies == true
  end
  class_registry = RegisterDefinedClasses.new(ast, classes)
  class_registry.rewrite_on_match
  rewrite_included_and_sourced_files!(filename)
  if filename && !included && add_SID_function?(filename)
    add_SID_function!
  end
  rewriters = [
    StrictEqualsComparisonOperator.new(ast, classes),
    VarEqualsComparisonOperator.new(ast, classes),
    ClassDefinitionToFunctions.new(ast, classes),
    ObjectInstantiationToCall.new(ast, classes),
    CallToExplicitCall.new(ast, classes),
    DefaultParamToIfNode.new(ast, classes),
    DeserializeVarAssignment.new(ast, classes),
    TopLevelDefMethodToDef.new(ast, classes),
    SplatsToCallFunctionInCallingContext.new(ast, classes)
  ]
  rewriters.each do |rewriter|
    rewriter.rewrite_on_match
  end
  ast
end
rewrite_included_and_sourced_files!(filename) click to toggle source

We need to rewrite the included/sourced files before anything else. This is in order to keep track of any classes defined in the included and sourced files (and files included/sourced in those, etc…). We keep a cache of rewritten asts because the included/sourced files are parsed more than once. They’re parsed first in this step, plus whenever the compiler visits a ‘riml_include’/‘riml_source’ node in order to compile it on the spot.

# File lib/riml/ast_rewriter.rb, line 209
def rewrite_included_and_sourced_files!(filename)
  old_ast = ast
  ast.children.grep(RimlFileCommandNode).each do |node|
    action = node.name == 'riml_include' ? 'include' : 'source'

    node.each_existing_file! do |file, fullpath|
      if filename && @included_and_sourced_file_refs[file].include?(filename)
        msg = "#{filename.inspect} can't #{action} #{file.inspect}, as " \
              " #{file.inspect} already included/sourced #{filename.inspect}"
        # IncludeFileLoop/SourceFileLoop
        error = Riml.const_get("#{action.capitalize}FileLoop").new(msg, node)
        raise error
      elsif filename == file
        error = UserArgumentError.new("#{file.inspect} can't #{action} itself", node)
        raise error
      end
      @included_and_sourced_file_refs[filename] << file
      # recursively parse included files with this ast_rewriter in order
      # to pick up any classes that are defined there
      rewritten_ast = nil
      watch_for_class_pickup do
        rewritten_ast = Riml.rewritten_ast_cache.fetch(file) do
          riml_src = File.read(fullpath)
          Parser.new.tap { |p| p.options = @options }.
            parse(riml_src, self, file, action == 'include')
        end
      end
      @rewritten_included_and_sourced_files[file] ||= rewritten_ast
    end
  end
ensure
  self.ast = old_ast
end
rewrite_on_match(node = ast) click to toggle source
# File lib/riml/ast_rewriter.rb, line 85
def rewrite_on_match(node = ast)
  Walker.walk_node(node, method(:do_rewrite_on_match), max_recursion_lvl)
end
watch_for_class_pickup() { || ... } click to toggle source
# File lib/riml/ast_rewriter.rb, line 243
def watch_for_class_pickup
  before_class_names = @classes.class_names
  ast = yield
  after_class_names = @classes.class_names
  diff_class_names = after_class_names - before_class_names
  class_diff = diff_class_names.inject({}) do |hash, class_name|
    hash[class_name] = @classes[class_name]
    hash
  end
  # no classes were picked up, it could be that the cache was hit. Let's
  # register the cached classes for this ast, if there are any
  if class_diff.empty?
    real_diff = Riml.rewritten_ast_cache.fetch_classes_registered(ast)
    real_diff.each do |k,v|
      @classes[k] = v unless @classes.safe_fetch(k)
    end
  # new classes were picked up, let's save them with this ast as the key
  else
    Riml.rewritten_ast_cache.save_classes_registered(ast, class_diff)
  end
end