#! /usr/bin/ruby

# This script finds 'eval' and its variants in the given ruby source file and
# provides file and line number for each usage.

# global variables used in this script
$__gen_code__ = Hash.new
$__source_files__ = Array.new

#
# looks for the first line of the given string (which should have been
# generated by calling 'caller' method)
#
def __find_caller__(str)
  i_caller = str[0]
  if i_caller =~ /([^:]+):([0-9]+)(:.*)?/
    file = File.expand_path($1)
    line = $2.to_i
    if not $__source_files__.include?(file)
      $__source_files__.push(file)
    end
    [file, line]
  else
    puts i_caller
    puts "[Internal Error] __find_caller__ is called inappropriately." 
    exit(1)
  end
end

#
# captures the original Kernel#eval and overrides it
#
module Kernel
  $__eval__ = method(:eval)
  
  #
  # saves the to-be-evaluated string and calls the original Kernel#eval
  #
  def eval(s, *rest)
    file, line = __find_caller__(caller())
    $__gen_code__[[file, line]] = {
      :binding => nil, 
      :kind => :eval, 
      :code => s
    }
    $__eval__.call(s, *rest)
  end
end

#
# captures the original Object#instance_eval and overrides it
#
class Object
  $__instance_eval__ = instance_method(:instance_eval)

  #
  # saves the to-be-evaluated string and calls current object's instance_eval()
  #
  def instance_eval(*args, &block)
    file, line = __find_caller__(caller())
    $__gen_code__[[file, line]] = {
      :binding => self, 
      :kind => :instance_eval, 
      :code => args[0]
    }
    if block_given?
      $__instance_eval__.bind(self).call(&block)
    else
      $__instance_eval__.bind(self).call(*args)
    end
  end
end

#
# captures the original Module#module_eval and overrides it
#
class Module
  $__module_eval__ = instance_method(:module_eval)

  #
  # saves the to-be-evaluated string and calls current module's module_eval()
  #
  def module_eval(*args, &block)
    file, line = __find_caller__(caller())
    $__gen_code__[[file, line]] = {
      :binding => self, 
      :kind => :module_eval, 
      :code => args[0]
    }
    if block_given?
      $__module_eval__.bind(self).call(&block)
    else
      $__module_eval__.bind(self).call(*args)
    end
  end
end

#
# captures the original Module#class_eval and overrides it
#
class Module
  $__class_eval__ = instance_method(:class_eval)

  #
  # saves the to-be-evaluted string and calls current class' class_eval()
  #
  def class_eval(*args, &block)
    file, line = __find_caller__(caller())
    $__gen_code__[[file, line]] = {
      :binding => self, 
      :kind => :class_eval, 
      :code => args[0]
    }
    if block_given?
      $__class_eval__.bind(self).call(&block)
    else
      $__class_eval__.bind(self).call(*args)
    end
  end
end

#
# prints the generated code
#
def __print_gen_code__(target_file) 
  target_file.puts "### evaluated strings"
  $__gen_code__.each_pair { |k,v|
    target_file.printf "##? %s:%s:%s:%s\n", 
        k[0], 
        k[1],
        v[:kind], 
        v[:binding]
    if v[:code]
      target_file.puts v[:code]
    end
    target_file.printf "##? EOE\n"
  }
  target_file.printf "### total %s evals (including ones with a block).\n",
    $__gen_code__.length
  target_file.printf "##? EOF\n"
end

#
# replaces an eval call in the given line of code
#
def __replace_eval__(line, gen_code)
  # class_eval
  if line =~ 
      /(.*)((class_eval)|(module_eval)|(instance_eval)|(eval)) ?\(?"(.*)"\)? ?(.*)/
    printf "%s%s {%s} %s\n", $1, $2, gen_code[:code], $8
  end
end

#
# re-finds evals and replace the string with generated code
# (this is for testing only)
#
def __replace_evals__()
  $__source_files__.each {|file|
    lines = File.new(file).readlines 
    lines.each_index {|n|
      # check if the line has any eval(s)
      key = [file, n + 1]
      gen_code = $__gen_code__[key]
      # puts gen_code[:code] if gen_code
      if gen_code
        __replace_eval__(lines[n], gen_code)
      else
        puts lines[n]
      end
    }
  }
end

#
# top level
#

source_file = ARGV[0]
if not source_file 
  print "Usage: file_eval.rb <source file>\n"
  exit(1)
end

# suck in the source file!
require source_file

target_file = File.new(source_file + ".evals", "w")
# __print_gen_code__(target_file)
__print_gen_code__($stdout)
# __replace_evals__()
target_file.close
