class CodeWeb::CodeParser

Constants

SPACES

Attributes

debug[RW]
exit_on_error[RW]
file_count[RW]
method_cache[RW]
verbose[RW]

Public Class Methods

new() click to toggle source
# File lib/code_web/code_parser.rb, line 17
def initialize
  @cur_method=[]
  @indent = 0
  @file_count = 0
  @exit_on_error = false
  @method_cache = CodeWeb::MethodCache.new
end

Public Instance Methods

collapse_ast(ast, max=20) click to toggle source

TODO: add collapse_ast

this one creates the true classes, not the string versions
(so don't add double quotes, or do 'nil')
# File lib/code_web/code_parser.rb, line 111
def collapse_ast(ast, max=20)
  if ast.is_a?(Sexp)
    if static?
      ast = ast.gsub(Sexp.new(:self), Sexp.new(:const, self_name))
    else
      ast = ast.gsub(Sexp.new(:call, Sexp.new(:self),:class), Sexp.new(:const, self_name))
    end
    case ast.node_type
    when :hash #name, value, name, value, ...
      if ast[1].is_a?(Sexp) && (ast[1].node_type == :kwsplat || ast[1].node_type == :lit)
        ast[1..-1].map { |i| collapse_ast(i) }
      else
        Hash[*ast[1..-1].map { |i| collapse_ast(i) }]
      end
    when :array
      ast[1..-1].map {|node| collapse_ast(node)}
    when :lit, :lvar, :const, :str, :ivar, :cvar
      ast[1]
    when :true
      true
    when :false
      false
    when :nil
      nil
    when :self
      ast[0]
    when :call
      if ast[2] == :[]
        "#{method_name_from_ast(ast[1..1]).join('.')}[#{collapse_ast(ast[3])}]"
      else
        "#{method_name_from_ast(ast[1..2]).join('.')}#{'(...)' if ast.length > 3}"
      end
    when :evstr
      "#"+"{#{collapse_asts(ast[1..-1]).join}}"
    when :colon2
      "#{method_name_from_ast(ast[1..-1]).join('::')}"
    when :dot2
      "#{collapse_ast(ast[1])}..#{collapse_ast(ast[2])}"
    when :colon3
      "::#{collapse_asts(ast[1..-1]).join}"
    when :[]
      "[#{collapse_asts(ast[1..-1]).join}]"
    when :dstr
      "#{collapse_asts(ast[1..-1]).join}"
    #backref?
    else
      if max > 0
        ast.map {|node| collapse_ast(node, max-1)}
      else
        "#{ast.node_type}[]"
      end
    end
  elsif ast.nil?
    nil
  else
    ast
  end
end
collapse_asts(ast, max=20) click to toggle source
# File lib/code_web/code_parser.rb, line 170
def collapse_asts(ast, max=20)
  ast.map {|node| collapse_ast(node)}
end
debug?() click to toggle source
# File lib/code_web/code_parser.rb, line 14
def debug? ; @debug ; end
handle_method_call(ast, is_yield=false) click to toggle source
# File lib/code_web/code_parser.rb, line 93
def handle_method_call(ast, is_yield=false)
  method_name = method_name_from_ast(ast[1..2])
  args = ast[3..-1].map {|arg| collapse_ast(arg,1)}

  mc = MethodCall.new(ast.file, ast.line, method_name, args, is_yield)
  method_cache << mc
  puts mc.to_s(spaces) if debug? # && method_cache.detect?(mc)
end
method_name_from_ast(ast) click to toggle source
# File lib/code_web/code_parser.rb, line 102
def method_name_from_ast(ast)
  ast.map { |node|
    collapse_ast(node)
  }.compact
end
parse(file_name, file_data=nil, required_string=nil) click to toggle source
# File lib/code_web/code_parser.rb, line 174
def parse(file_name, file_data=nil, required_string=nil)
  #may make more sense to get this into cli (and an option for absolute path)
  file_name = File.realpath(file_name)
  file_data ||= File.binread(file_name)
  begin
    if required_string.nil? || file_data.include?(required_string)
      in_context file_name do
        traverse RubyParser.new.process(file_data, file_name)
      end
    end
    @file_count += 1
  rescue => e
    if defined?(Pry)
      binding.pry
    elsif defined?(Byebug)
      byebug
    end
    STDERR.puts("#{e}: [#{file_data.size}] #{file_name}")
  end
end
traverse(ast, has_yield=false) click to toggle source
# File lib/code_web/code_parser.rb, line 25
def traverse(ast, has_yield=false)
  puts "#{spaces}||#{collapse_ast(ast,1)}||" if verbose?
  puts src if ast.nil?
  case ast.node_type
  #dstr = define string ("abc#{here}"),
  #evstr evaluate string (#{HERE})
  #attrasgn = attribute assignment
  when :block, :lambda, :if, :ensure, :rescue, :case, :when, :begin,
       :while, :until, :defined, :resbody, :match2, :match3, :dot2, :dot3,
       :dstr, :evstr, :dsym, :dregx, :hash, :array, :return, :and, :or,
       :next, :to_ary, :splat, :block_pass, :until, :yield,
       /asgn/, :ivar, :arglist, :args, :kwarg, :kwargs, :kwsplat, :zsuper, :not, #statements[]
       :super, :xstr, :for, :until, :dxstr, :lit,
  #these end up being no-ops:
       :lvar, :const, :str, :nil, :gvar, :back_ref,
       :true, :false, :colon2, :colon3, :next, :alias,
       :nth_ref, :sclass, :cvdecl, :break, :retry, :undef,
  #random
       :svalue, :cvar
    traverse_nodes(ast, 1..-1)
  when :self
    traverse_nodes(ast, 1..-1)
  when :module, #name, statements[]
       :class #name, parent, statements[]
    in_context ast[1], true, true do
      traverse_nodes(ast, 2..-1)
    end
  when :cdecl, #name, statements[]
       :defn #name, args[], call[]
    in_context ast[1], true do
      traverse_nodes(ast, 2..-1)
    end
  when :defs #self[], name, args[], call[] # static method
    in_context ast[2], :static do
      traverse_nodes(ast, 2..-1)
    end
  when :iter #call[], yield_args[], yield_{block|call}[]
    traverse(ast[1], :has_yield)
    in_context 'yield', true do
      traverse_nodes(ast, 2..-1)
    end
  when :call, :safe_call # object, statement? || const symbol, args
    handle_method_call(ast, has_yield)
    traverse_nodes(ast, 1..-1)
  else
    STDERR.puts "#{src}\n  unknown node: #{ast.node_type} #{collapse_ast(ast,1)}"
    if exit_on_error
      if defined?(Pry)
        binding.pry
      elsif defined?(Byebug)
        byebug
      end
      raise "error"
    end
    traverse_nodes(ast, 1..-1)
  end
end
traverse_nodes(ast, *ranges) click to toggle source
# File lib/code_web/code_parser.rb, line 83
def traverse_nodes(ast, *ranges)
  ranges = [0..-1] if ranges.empty?
  ranges.each do |range|
    ast[range].each do |node|
      should_call = node.is_a?(Sexp)
      traverse(node) if should_call
    end
  end
end
verbose?() click to toggle source
# File lib/code_web/code_parser.rb, line 13
def verbose? ; @verbose ; end

Private Instance Methods

in_context(name, indent=false, class_def=false) { || ... } click to toggle source

mark the context of the method call. optionally indents output as well @param name [String] name of the block - file, module, class, method, 'yield' @param indent [boolean] (false) indent this block (pass :static if this is a static method) @param class_def [boolean] (false) true if this is a class definition

# File lib/code_web/code_parser.rb, line 218
def in_context name, indent=false, class_def=false
  name = collapse_ast(name) #split("::").last
  @cur_method << [ name, class_def, indent == :static]
  puts ">> #{'self.' if static?}#{src}" if debug? && indent
  @indent += 1 if indent
  ret = yield
  @indent -= 1 if indent
  @cur_method.pop
  ret
end
self_name() click to toggle source

return nil if we haven't hit a class yet

# File lib/code_web/code_parser.rb, line 203
def self_name
  #cm[1] == true for module/class definitions
  node=@cur_method.select {|cm| cm[1] == true}.last
  node.first unless node.nil?
end
spaces() click to toggle source

print appropriate # of spaces

# File lib/code_web/code_parser.rb, line 230
def spaces
  SPACES[@indent]
end
src() click to toggle source

where in the source are we?

# File lib/code_web/code_parser.rb, line 198
def src
  "#{@cur_method.first.first} | #{@cur_method.map(&:first)[1..-1].join('.')}"
end
static?() click to toggle source
# File lib/code_web/code_parser.rb, line 209
def static?
  @cur_method.last.last
end