#!/usr/bin/env jruby

require 'optparse'

opts = {}
options = {
  :print_source => false,
  :print_sexp   => false,
  :print_ast    => true,
  :print_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('-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

require 'jruby'

$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

def 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 short_name(node)
  node.class.name.split("::")[-1].gsub("Node", "")
end

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

def 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 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|
    print_passes_on(child_scope, passes)
  end
end

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

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

if options[:print_ast]
  if options[:dot_format]
    defs = {}
    graph_section = dot(defs, root)
    puts "digraph AST {"
    puts defs.values.join('')
    puts graph_section
    puts "}"
  else
    print "AST:"
    puts indent(root.to_string) 
    puts
  end
end

if options[:print_ir]
  require 'jruby'
  runtime = JRuby::runtime
  manager = runtime.ir_manager

  manager.dry_run = true
  JRuby::IR.compiler_debug = true


  builder = runtime.is1_9 ? org.jruby.ir.IRBuilder19 : org.jruby.ir.IRBuilder

  scope = builder.new(manager).build_root(root)
  passes = manager.get_compiler_passes(scope)

  print_passes_on(scope, passes)
end

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