#!/usr/bin/env jruby

require 'optparse'
require 'jruby'

opts = {}
options = {
  :print_source => false,
  :print_sexp   => false,
  :print_ast    => true,
  :print_ir     => false,
  :print_pass   => nil,
  :pretty_ir    => false,
  :dot_format   => false
}

OptionParser.new do |opts|
  opts.banner = "Usage: #{$0} [options]"

  opts.on('-d', '--dot', 'Display as dot data') do |h|
    options[:dot_format] = true
  end
  
  opts.on('-h', '--help', 'Display this help') do |h|
    puts opts
    exit true
  end

  opts.on('-i', '--ir', 'Dump all IR passes without executing') do |h|
    options[:print_ir] = true
  end

  opts.on('-p', '--pass passes_list', 'Dump IR after running a pass') do |pass|
    options[:print_pass] = pass
  end

  opts.on('-f', '--formatted-ir', 'Pretty printer for IR (without CFG)') do |f|
    options[:pretty_ir] = f
  end
  
  opts.on('-s', '--sexp', 'Display the S-Expression for the AST') do |t|
    options[:print_sexp] = true
  end
  
  opts.on('--source', 'Display the source') do |s|
    options[:print_source] = true
  end
  
  opts.on('--no-ast', 'Do not print out the AST for this (only useful with -s)') do |a|
    options[:print_ast] = false
  end
  
  opts.on('-e exp', '--expression') do |e|
    options[:expression] = e
  end
  
end.parse!

if ARGV.length > 1
  abort "You may only specify one script (see --help)"
elsif ARGV.length == 1
  if options[:expression]
    abort "-e and a script is not a valid combination (see --help)"
  end
  options[:expression] = File.read(ARGV.shift)
elsif ! options.has_key?(:expression)
  abort "No script specified (see --help)"
end

if options[:print_ir] && options[:print_pass]
  abort "-p and -i is not valid. Use only one of them (see --help)"
end

$indent_string = "  "

def indexes(string, lindex, rindex)
  lindex = string.index("(", lindex) if lindex != nil
  rindex = string.index(")", rindex) if rindex != nil
  return lindex, rindex
end

def indent(string)
  depth = -1

  lindex, rindex = indexes(string, 0, 0)

  while (lindex != nil || rindex != nil)
    if (lindex != nil && lindex < rindex)
      depth += 1
      string[lindex, 1] = "\n#{$indent_string * depth}"
    else
      depth -= 1
      string[rindex, 1] = "\n"
    end

    lindex, rindex = indexes(string, lindex, rindex)
  end
  string.gsub(/,\s*$/, '').squeeze("\n")
end

if options[:print_source]
  puts "Source:"
  puts options[:expression]
  puts
end

module DotGraph
  def self.dot_label(node)
    extra = case node
            when org.jruby.ast.StrNode then
              ": '#{node.value}'"
            when org.jruby.ast.FixnumNode then
              ": #{node.value}"
            when org.jruby.ast.FloatNode then
              ": #{node.value}"
            when org.jruby.ast.types.INameNode then
              ": #{node.name}"
            else
              ""
            end

    "#{short_name(node)}#{extra}"
  end

  def self.short_name(node)
    node.class.name.split("::")[-1].gsub("Node", "")
  end

  def self.dot_node_def(node)
    %Q{#{node.hash} [label="#{dot_label(node)}"];\n}
  end

  def self.dot(defs, parent)
    defs[parent.hash] = dot_node_def(parent)

    "".tap do |str|
      parent.child_nodes.each do |child|
        str << "#{parent.hash} -> #{child.hash};\n"
        str << dot(defs, child)
      end
    end
  end

  def self.print_graph(root_node)
    defs = {}
    graph_section = DotGraph.dot(defs, root_node)
    puts "digraph AST {"
    puts defs.values.join('')
    puts graph_section
    puts "}"
  end
end

root = JRuby.parse_result(options[:expression])

if options[:print_ast]
  if options[:dot_format]
    DotGraph.print_graph(root)
  else
    print "AST:"
    puts indent(root.get_ast.to_string) 
    puts
  end
end

def print_passes_on(scope, passes)
  if !scope.kind_of? org.jruby.ir.IRClosure
    passes.each { |pass| pass.run(scope) } 
  end

  scope.lexical_scopes.each do |child_scope|
    child_scope.prepare_for_compilation
    print_passes_on(child_scope, passes)
  end
end

def print_pass_on(scope, pass)
  if !scope.kind_of? org.jruby.ir.IRClosure
    pass.run(scope)
  end
  
  scope.lexical_scopes.each do |child_scope|
    print_pass_on(child_scope, pass)
  end
end

def ir_setup(root)
  runtime = JRuby::runtime
  manager = runtime.ir_manager

  JRuby::IR.compiler_debug = true


  builder = org.jruby.ir.builder.IRBuilder

  scope = builder.build_root(manager, root).scope
  scope.prepare_for_compilation
  passes = manager.get_compiler_passes(scope)
  [scope, passes]
end

module IRPrettyPrinter
  def self.pretty_ir(scope, indent="")
    i = 0
    pretty_str = scope.instrs.map do |instr|
      f_str = "%s%3i\s\s%s" % [indent, i, instr]
      i += 1
      f_str 
    end
    pretty_str = [indent + scope.to_s] + pretty_str
    scope.lexical_scopes.each do |lex_scope|
      pretty_str += pretty_ir(lex_scope, indent + "\s" * 4)
    end
    pretty_str
  end

  def self.print_ir(scope)
    instrs = pretty_ir(scope)
    instrs.each do |instr|
      puts instr
    end
  end
end

if options[:pretty_ir]
  scope, passes = ir_setup(root)
  puts "IR:"
  IRPrettyPrinter.print_ir(scope)
end

if options[:print_pass]
  scope, passes = ir_setup(root)
  pass_name = options[:print_pass]
  pass = passes.find do |p|
    p.java_class.to_s.include?(pass_name.to_s)
  end
  print_pass_on(scope, pass)
end

if options[:print_ir]
  scope, passes = ir_setup(root)

  print_passes_on(scope, passes)
end

if options[:print_sexp]
  puts "SEXP:"
  puts org.jruby.ast.util.SexpMaker.create(root) 
end
