class Maccro::Rule

Attributes

after[R]
before[R]
matcher[R]
name[R]
safe_reference[R]
under[R]

Public Class Methods

find_placeholder_code_ranges(ast, name) click to toggle source
# File lib/maccro/rule.rb, line 72
def self.find_placeholder_code_ranges(ast, name)
  return [] unless ast.is_a? RubyVM::AbstractSyntaxTree::Node

  if ast.type == :VCALL && ast.children.first.to_s == name
    return [CodeRange.from_node(ast)]
  end

  ranges = []
  ast.children.each do |c|
    rs = find_placeholder_code_ranges(c, name)
    ranges.concat(rs) unless rs.empty?
  end
  ranges.sort
end
new(name, before, after, under: nil, safe_reference: false) click to toggle source
# File lib/maccro/rule.rb, line 9
def initialize(name, before, after, under: nil, safe_reference: false)
  @name = name
  @before = before
  @after = after
  @under = under
  @safe_reference = safe_reference

  # TODO: check all placeholder in @after exist in @before
  # (placeholders in @before are not required to exist in @after, because it may be removed)
  # TODO: check $TARGET exists in under just once

  # TODO: implement a matcher in @before matches in multi times

  @matcher = DSL.matcher(before)
  @pruner = under && DSL.matcher(under) || nil
end

Public Instance Methods

after_code(replace_pairs) click to toggle source
# File lib/maccro/rule.rb, line 87
def after_code(replace_pairs) # name => snippet
  code = @after.dup
  ast = CodeUtil.parse_to_ast(@after)
  code_range_to_name = {}
  replace_pairs.each_key do |name|
    self.class.find_placeholder_code_ranges(ast, name).each do |r|
      code_range_to_name[r] = name
    end
  end
  # reverse is not to break code position for unprocessed code ranges
  code_range_to_name.keys.sort.reverse.each do |code_range|
    name = code_range_to_name[code_range]
    snippet = replace_pairs[name]
    range = CodeUtil.code_range_to_range(@after.dup, code_range)
    code[range] = snippet
  end
  code
end
dig_match(matches, ast) click to toggle source
# File lib/maccro/rule.rb, line 56
def dig_match(matches, ast)
  if @matcher.match?(ast)
    placeholders = {}
    @matcher.capture(ast, placeholders)
    matches << Match.new(rule: self, placeholders: placeholders, range: ast.to_code_range)
  elsif ast.respond_to?(:children)
    ast.children.each do |c|
      dig_match(matches, c)
    end
  elsif ast.respond_to?(:each)
    ast.each do |i|
      dig_match(matches, i)
    end
  end
end
dig_prune(matches, ast) click to toggle source
# File lib/maccro/rule.rb, line 40
def dig_prune(matches, ast)
  if @pruner.match?(ast)
    placeholders = {}
    @pruner.capture(ast, placeholders)
    dig_match(matches, placeholders[:__target__])
  elsif ast.respond_to?(:children)
    ast.children.each do |c|
      dig_prune(matches, c)
    end
  elsif ast.respond_to?(:each)
    ast.each do |i|
      dig_prune(matches, i)
    end
  end
end
match(ast) click to toggle source
# File lib/maccro/rule.rb, line 26
def match(ast)
  matches = []

  if @pruner
    dig_prune(matches, ast)
  else
    dig_match(matches, ast)
  end

  return nil if matches.empty?

  Matched.new(matches)
end