#!ruby

class IRArgument
  attr_accessor :type, :name, :variadic

  def initialize(name, type)
    @type = type
    @name = name
    @variadic = type.end_with?("[]")
    if @variadic
      @type = @type.split("[]")[0]
    end
  end
end

class OP
  attr_accessor :name, :def, :use, :arg, :variadic
  attr_accessor :trans, :opcode, :side_effect, :terminator
  attr_accessor :frame, :type
  def initialize(name, has_def, has_use, trans, side_effect,
                 terminator, frame, arg, type, opcode)
    @name = name
    @def = has_def == ":def"
    @use = has_use == ":use"
    @trans = trans == ":trans"
    @arg = []
    @variadic = false
    @opcode = opcode
    @side_effect = side_effect
    @terminator = terminator
    @frame = frame
    @type = type
    parse_arg(arg)
  end

  def parse_arg(arg)
    arg.split(",").map(&:strip).map { |e|
      a = IRArgument.new(*e.split(":"))
      @arg.push(a)
      if a.variadic == true
        @variadic = true
      end
    }
  end
end

irs = []

open(ARGV[0]) { |file|
  i = 0
  puts "// This file is auto generated by generate_lir_def.rb"
  puts "// DO NOT TOUCH!"
  puts ""
  puts "// If you want to fix something, you must edit 'lir.def'"

  while l = file.gets
    if /^([a-zA-Z0-9_]+)/ =~ l
      /^([a-zA-Z0-9_]+) *(:def)? *(:use)? *(:trans)? *(:effect)? *(:terminator)? *(:frame)? *\((.*)\) *:([a-zA-Z]+)$/ =~ l
      ir = OP.new($1, $2, $3, $4, $5, $6, $7, $8, $9, i)
      irs.push(ir)
      i += 1
    end
  end
}

def define_struct(ir)
  puts "#define OPCODE_I#{ir.name} #{ir.opcode}\n"
  puts "#define LIR_USE_#{ir.name} #{ir.def ? 1 : 0}"
  puts "#define LIR_DEF_#{ir.name} #{ir.use ? 1 : 0}"
  puts "#define LIR_SIDE_EFFECT_#{ir.name} #{ir.side_effect ? 1 : 0}"
  puts "#define LIR_IS_TERMINATOR_#{ir.name} #{ir.terminator ? 1 : 0}"
  puts "#define LIR_PUSH_FRAME_#{ir.name} #{ir.frame ? 1 : 0}"
  puts "#define LIR_IS_GUARD_INST_#{ir.name} #{ir.name.start_with?("Guard") ? 1 : 0}"
  puts "#define LIR_TYPE_#{ir.name} LIR_TYPE_#{ir.type}"
  puts "typedef struct I#{ir.name} {\n"
  puts "  lir_inst_t base;"
  ir.arg.each{|e|
    type = e.type
    name = e.name
    if e.variadic
      name = name + "[0]"
    end
    puts "  #{type} #{name};\n"
  }
  puts "} I" + ir.name + ";\n\n"
end

def emit_ir(ir)
  print "static lir_t Emit_#{ir.name}(lir_builder_t *builder"
  ir.arg.each{|e|
    type = e.type
    name = e.name
    if e.variadic
      name = name + "[]"
    end
    print ", #{type} #{name}"
  }

  puts ")\n{\n"
  if ir.variadic
    puts "  I#{ir.name} *ir = LIR_NEWINST_N(I#{ir.name}, argc);\n"
  else
    puts "  I#{ir.name} *ir = LIR_NEWINST(I#{ir.name});\n"
  end

  ir.arg.each{|e|
    if e.variadic || e.type == "LirPtr"
      puts "  int i;"
    end
  }

  ir.arg.each{|e|
    if e.variadic || e.type == "LirPtr"
      puts "  for(i = 0; i < argc; i++) {"
      puts "    ir->#{e.name}[i] = #{e.name}[i];\n"
      puts "  }"
    else
      puts "  ir->#{e.name} = #{e.name};\n"
    end
  }

  if ir.variadic
    puts "  return ADD_INST_N(builder, ir, argc);\n"
  else
    puts "  return ADD_INST(builder, ir);\n"
  end

  puts "}\n"
end

def dump_ir(ir)
  print "static void Dump_#{ir.name}(lir_inst_t *Inst)\n"
  puts "{\n"
  puts "#if DUMP_LIR > 0"
  puts "  I#{ir.name} *ir = (I#{ir.name} *)Inst;\n"

  ir.arg.each{|e|
    if e.variadic || e.type == "LirPtr"
      puts "  int i_#{e.name};"
    end
  }

  print "  fprintf(stderr, \"  \" FMT_ID \" u=%d %d %d %d #{ir.name}\","
  print " lir_getid(&ir->base), "
  print " ir->base.user ? jit_list_size(ir->base.user) : 0, "
  print "(ir->base.flag & LIR_FLAG_UNTAGED) ? 1 : 0,"
  print "(ir->base.flag & LIR_FLAG_INVARIANT) ? 1 : 0,"
  print "(ir->base.flag & LIR_FLAG_TRACE_EXIT) ? 1 : 0"
  puts ");"

  ir.arg.each{|e|
    t = e.type
    n = e.name
    puts "  fprintf(stderr, \" #{n}:\");\n"
    if e.variadic || e.type == "LirPtr"
      puts "  for(i_#{n} = 0; i_#{n} < ir->argc; i_#{n}++) {"
      if e.type == "LirPtr"
        puts "    #{t} val = &ir->#{n}[i_#{n}];"
      else
        puts "    #{t} val = ir->#{n}[i_#{n}];"
      end
      puts "    fprintf(stderr, \" \" FMT(#{t}), DATA(#{t}, val));\n"
      puts "  }"
    else
      puts "  fprintf(stderr, FMT(#{t}), DATA(#{t}, ir->#{n}));\n"
    end
  }

  puts "  fprintf(stderr, \"\\n\");"
  puts "#endif /*DUMP_LIR > 0*/"
  puts "}\n"
end

def emit_get_next(ir)
  puts "static lir_t *GetNext_#{ir.name}(lir_inst_t *Inst, int idx)"
  puts "{"
  argc = 0
  ir.arg.each{|e|
    if e.type == "LirPtr" || (e.variadic && e.type = "lir_t")
      argc += 1
    elsif e.type == "lir_t"
      argc += 1
    end
  }
  if argc > 0
    puts "  I#{ir.name} *ir = (I#{ir.name} *)Inst;\n"
  end

  i = 0
  puts "  switch(idx) {"
  ir.arg.each{|e|
    t = e.type
    n = e.name
    if e.type == "LirPtr" || (e.variadic && e.type = "lir_t")
      puts "  default:"
      puts "    if (0 <= idx - #{i} && idx - #{i} < ir->argc) {"
      puts "      return &ir->#{n}[idx - #{i}];"
      puts "    }"
      i += 1
    elsif e.type == "lir_t"
      puts "  case #{i}:"
      puts "    return &ir->#{n};"
      i += 1
    end
  }
  puts "  }"
  puts "  return NULL;"
  puts "}\n"
end

irs.each{|ir|
  define_struct ir
  emit_ir ir
  dump_ir ir
  emit_get_next ir
}

puts "#define LIR_MAX_OPCODE #{irs.length}\n"
puts "#define LIR_EACH(OP) \\"
irs.each{|ir|
  puts "  OP(#{ir.name})\\"
}
puts ""

inlineop = {}
open(ARGV[1]) { |file|
  while l = file.gets
    if /^static inline VALUE rb_jit_exec_I([a-zA-Z0-9_]+) *\((.*)\)$/ =~ l
      inlineop[$1.to_s] = $1.to_s
    end
  end
}

puts "static lir_folder_t const_fold_funcs[] = {"
irs.each{|ir|
  if inlineop[ir.name]
    puts "(lir_folder_t) rb_jit_exec_I#{ir.name},"
  else
    puts "NULL,"
  end
}
puts "};"
