class Flog

def add_to_score name, score = OTHER_SCORES[name]

def add_to_score name, score = OTHER_SCORES[name]
  return if option[:methods] and method_stack.empty?
  @calls[signature][name] += score * @multiplier
end

def average

def average
  return 0 if calls.size == 0
  total_score / calls.size
end

def calculate

def calculate
  each_by_score threshold do |class_method, score, call_list|
    klass = class_method.scan(/.+(?=#|::)/).first
    method_scores[klass] << [class_method, score]
    scores[klass] += score
  end
end

def calculate_total_scores

def calculate_total_scores
  return if @totals
  @total_score = 0
  @totals = Hash.new(0)
  calls.each do |meth, tally|
    score = score_method(tally)
    @totals[meth] = score
    @total_score += score
  end
end

def dsl_name? args

def dsl_name? args
  return false unless args and not args.empty?
  first_arg, = args
  first_arg = first_arg[1] if first_arg.sexp_type == :hash
  type, value, * = first_arg
  value if [:lit, :str].include? type
end

def each_by_score max = nil

def each_by_score max = nil
  current = 0
  calls.sort_by { |k,v| -totals[k] }.each do |class_method, call_list|
    score = totals[class_method]
    yield class_method, score, call_list
    current += score
    break if max and current >= max
  end
end

def flog(*files)

def flog(*files)
  files.each do |file|
    next unless file == "-" or File.readable? file
    ruby = file == "-" ? $stdin.read : File.binread(file)
    flog_ruby ruby, file
  end
  calculate_total_scores
end

def flog_ruby ruby, file="-", timeout = 10

def flog_ruby ruby, file="-", timeout = 10
  flog_ruby! ruby, file, timeout
rescue Timeout::Error
  warn "TIMEOUT parsing #{file}. Skipping."
rescue RubyParser::SyntaxError, Racc::ParseError => e
  q = option[:quiet]
  if e.inspect =~ /<\%|%\>/ or ruby =~ /<\%|%\>/ then
    return if q
    warn "#{e.inspect} at #{e.backtrace.first(5).join(", ")}"
    warn "\n...stupid lemmings and their bad erb templates... skipping"
  else
    warn "ERROR: parsing ruby file #{file}" unless q
    unless option[:continue] then
      warn "ERROR! Aborting. You may want to run with --continue."
      raise e
    end
    return if q
    warn "%s: %s at:\n  %s" % [e.class, e.message.strip,
                               e.backtrace.first(5).join("\n  ")]
  end
end

def flog_ruby! ruby, file="-", timeout = 10

def flog_ruby! ruby, file="-", timeout = 10
  @parser = (option[:parser] || RubyParser).new
  warn "** flogging #{file}" if option[:verbose]
  ast = @parser.process ruby, file, timeout
  return unless ast
  mass[file] = ast.mass
  process ast
end

def initialize option = {}

def initialize option = {}
  super()
  @option              = option
  @mass                = {}
  @parser              = nil
  @threshold           = option[:threshold] || DEFAULT_THRESHOLD
  self.auto_shift_type = true
  self.reset
end

def max_method

def max_method
  totals.max_by { |_, score| score }
end

def max_score

def max_score
  max_method.last
end

def no_method # :nodoc:

:nodoc:
def no_method # :nodoc:
  @@no_method
end

def penalize_by bonus

def penalize_by bonus
  @multiplier += bonus
  yield
  @multiplier -= bonus
end

def process_alias(exp)

:stopdoc:
def process_alias(exp)
  process exp.shift
  process exp.shift
  add_to_score :alias
  s()
end

def process_and(exp)

def process_and(exp)
  add_to_score :branch
  penalize_by 0.1 do
    process exp.shift # lhs
    process exp.shift # rhs
  end
  s()
end

def process_attrasgn(exp)

def process_attrasgn(exp)
  add_to_score :assignment
  process exp.shift # lhs
  exp.shift # name
  process_until_empty exp # rhs
  s()
end

def process_block(exp)

def process_block(exp)
  penalize_by 0.1 do
    process_until_empty exp
  end
  s()
end

def process_block_pass(exp)

def process_block_pass(exp)
  arg = exp.shift
  add_to_score :block_pass
  return s() unless arg
  case arg.sexp_type
  when :lvar, :dvar, :ivar, :cvar, :self, :const, :colon2, :nil then # f(&b)
    # do nothing
  when :lit then                                                     # f(&:b)
    # do nothing -- this now costs about the same as a block
  when :call then                                                    # f(&x.b)
    # do nothing -- I don't like the indirection, but that's already scored
  when :lasgn then                                                   # f(&l=b)
    add_to_score :to_proc_lasgn
  when :iter, :dsym, :dstr, :hash, *BRANCHING then                   # below
    # f(&proc { ... })
    # f(&"#{...}")
    # f(&:"#{...}")
    # f(&if ... then ... end") and all other branching forms
    # f(&{ a: 42 })  WHY?!?
    add_to_score :to_proc_icky!
  else
    raise({:block_pass_even_ickier! => arg}.inspect)
  end
  process arg
  s()
end

def process_call(exp)

def process_call(exp)
  penalize_by 0.2 do
    process exp.shift # recv
  end
  name = exp.shift
  penalize_by 0.2 do
    process_until_empty exp
  end
  add_to_score name, SCORES[name]
  s()
end

def process_case(exp)

def process_case(exp)
  add_to_score :branch
  process exp.shift # recv
  penalize_by 0.1 do
    process_until_empty exp
  end
  s()
end

def process_class(exp)

def process_class(exp)
  super do
    penalize_by 1.0 do
      process exp.shift # superclass expression
    end
    process_until_empty exp
  end
end

def process_dasgn_curr(exp) # FIX: remove

FIX: remove
def process_dasgn_curr(exp) # FIX: remove
  add_to_score :assignment
  exp.shift # name
  process exp.shift # assigment, if any
  s()
end

def process_else(exp)

TODO: it's not clear to me whether this can be generated at all.
def process_else(exp)
  add_to_score :branch
  penalize_by 0.1 do
    process_until_empty exp
  end
  s()
end

def process_if(exp)

def process_if(exp)
  add_to_score :branch
  process exp.shift # cond
  penalize_by 0.1 do
    process exp.shift # true
    process exp.shift # false
  end
  s()
end

def process_iter(exp)

def process_iter(exp)
  context = (self.context - [:class, :module, :scope])
  context = context.uniq.sort_by { |s| s.to_s }
  exp.delete 0 # { || ... } has 0 in arg slot
  if context == [:block, :iter] or context == [:iter] then
    recv, = exp
    # DSL w/ names. eg task :name do ... end
    #   looks like s(:call, nil, :task, s(:lit, :name))
    #           or s(:call, nil, :task, s(:str, "name"))
    #           or s(:call, nil, :task, s(:hash, s(:lit, :name) ...))
    t, r, m, *a = recv
    if t == :call and r == nil and submsg = dsl_name?(a) then
      m = "#{m}(#{submsg})" if m and [String, Symbol].include?(submsg.class)
      in_klass m do                             # :task/namespace
        in_method submsg, exp.file, exp.line, exp.line_max do # :name
          process_until_empty exp
        end
      end
      return s()
    end
  end
  add_to_score :block_call
  process exp.shift # no penalty for LHS
  penalize_by 0.1 do
    process_until_empty exp
  end
  s()
end

def process_lit(exp)

def process_lit(exp)
  value = exp.shift
  case value
  when 0, -1 then
    # ignore those because they're used as array indicies instead of
    # first/last
  when Integer, Float, Rational, Complex then
    add_to_score :magic_number unless context[1] == :cdecl
  when Symbol, Regexp, Range then
    # do nothing
  else
    raise "Unhandled lit: #{value.inspect}:#{value.class}"
  end
  s()
end

def process_masgn(exp)

def process_masgn(exp)
  add_to_score :assignment
  exp.map! { |s| Sexp === s ? s : s(:lasgn, s) }
  process_until_empty exp
  s()
end

def process_safe_call(exp)

def process_safe_call(exp)
  penalize_by 0.3 do
    process exp.shift # recv
  end
  name = exp.shift
  penalize_by 0.2 do
    process_until_empty exp
  end
  add_to_score name, SCORES[name]
  s()
end

def process_sclass(exp)

def process_sclass(exp)
  super do
    penalize_by 0.5 do
      process exp.shift # recv
      process_until_empty exp
    end
  end
  add_to_score :sclass
  s()
end

def process_super(exp)

def process_super(exp)
  add_to_score :super
  process_until_empty exp
  s()
end

def process_while(exp)

def process_while(exp)
  add_to_score :branch
  penalize_by 0.1 do
    process exp.shift # cond
    process exp.shift # body
  end
  exp.shift # pre/post
  s()
end

def process_yield(exp)

def process_yield(exp)
  add_to_score :yield
  process_until_empty exp
  s()
end

def reset

def reset
  @totals           = @total_score = nil
  @multiplier       = 1.0
  @calls            = Hash.new { |h,k| h[k] = Hash.new 0 }
  @method_scores    = Hash.new { |h,k| h[k] = [] }
  @scores           = Hash.new 0
  method_locations.clear
end

def score_method(tally)

def score_method(tally)
  a, b, c = 0, 0, 0
  tally.each do |cat, score|
    case cat
    when :assignment then a += score
    when :branch, :block_call then b += score
    else                  c += score
    end
  end
  Math.sqrt(a*a + b*b + c*c)
end

def threshold

def threshold
  option[:all] ? nil : total_score * @threshold
end