#!/usr/bin/env ruby
#
#
#

require 'pp'
require 'erb'

class InsnsDef
  class InsnInfo
    
    def initialize name, opes, pops, rets, comm, body,
                   orig = self, defopes = [], type = nil,
                   nsc = [], psc = [[], []]

      @name = name
      @opes = opes # [[type, name], ...]
      @pops = pops # [[type, name], ...]
      @rets = rets # [[type, name], ...]
      @comm = comm # {:c => category, :e => en desc, :j => ja desc}
      @body = body # '...'

      @orig = orig
      @defopes = defopes
      @type   = type

      @nextsc = nsc
      @pushsc = psc
      @sc     = []
      @unifs  = []
      @is_sc  = false
    end

    def add_sc sci
      @sc << sci
      sci.set_sc
    end
    
    attr_reader :name, :opes, :pops, :rets
    attr_reader :body, :comm
    attr_reader :nextsc, :pushsc
    attr_reader :orig, :defopes, :type
    attr_reader :sc
    attr_reader :unifs
    attr_reader :is_sc

    def set_sc
      @is_sc = true
    end
    
    def add_unif insns
      @unifs << insns
    end

    def inspect
      "#<InsnInfo:#{@name}>"
    end
  end
  
  def initialize file, optopfile, uniffile
    @insns    = []
    @insn_map = {}
    
    load_insns_def       file
    load_opt_operand_def optopfile
    load_insn_unification_def uniffile
    make_stackcaching_insns if $opts['OPT_STACK_CACHING']
  end

  attr_reader :insns
  attr_reader :insn_map
  
  SKIP_COMMENT_PATTERN = Regexp.compile(Regexp.escape('/** ##skip'))

  include Enumerable
  def each
    @insns.each{|insn|
      yield insn
    }
  end

  def add_insn insn
    @insns << insn
    @insn_map[insn.name] = insn
  end
  
  def make_insn name, opes, pops, rets, comm, body
    add_insn InsnInfo.new(name, opes, pops, rets, comm, body)
  end


  # str -> [[type, var], ...]
  def parse_vars line
    raise unless /\((.*)\)/ =~ line
    vars = $1.split(',')
    vars.map!{|v|
      if /\s*(\S+)\s+(\S+)\s*/ =~ v
        type = $1
        var  = $2
      elsif /\s*\.\.\.\s*/ =~ v
        type = var  = '...'
      else
        raise
      end
      [type, var]
    }
    vars
  end

  def parse_comment comm
    c = 'others'
    j = ''
    e = ''
    comm.each{|line|
      case line
      when /@c (.+)/
        c = $1
      when /@e (.+)/
        e = $1
      when /@e\s*$/
        e = ''
      when /@j (.+)$/
        j = $1
      when /@j\s*$/
        j = ''
      end
    }
    { :c => c,
      :e => e,
      :j => j,
    }
  end
  
  def load_insns_def file
    body = insn = opes = pops = rets = nil
    comment = ''
    
    open(file){|f|
      while line = f.gets
        line.chomp!
        case line

        when SKIP_COMMENT_PATTERN
          while line = f.gets.chomp
            if /\s+\*\/$/ =~ line
              break
            end
          end

        # collect instruction comment
        when /^\/\*\*$/
          while line = f.gets
            if /\s+\*\/\s*$/ =~ line
              break
            else
              comment << line
            end
          end

        # start instruction body
        when /^DEFINE_INSN$/
          insn = f.gets.chomp
          opes = parse_vars(f.gets.chomp)
          pops = parse_vars(f.gets.chomp).reverse
          rets = parse_vars(f.gets.chomp).reverse
          comment = parse_comment(comment)
          insn_in = true
          body    = ''
          
          raise unless /^\{$/ =~ f.gets.chomp
        
        # end instruction body
        when /^\}/
          if insn_in
            make_insn insn, opes, pops, rets, comment, body
            insn_in = false
            comment = ''
          end
          
          # 
        else
          if insn_in
            body << line + "\n"
          end
        end
      end
    }
  end
  
  ## opt op
  def load_opt_operand_def file
    open(file){|f| f.each{|line|
      line = line.gsub(/\#.*/, '').strip
      next  if line.length == 0
      break if /__END__/ =~ line
      /(\S+)\s+(.+)/ =~ line
      insn = $1
      opts = $2
      add_opt_operand insn, opts.split(/,/).map{|e| e.strip}
    }}
  end
  
  def label_escape label
    label.gsub(/\(/, '_O_').
          gsub(/\)/, '_C_').
          gsub(/\*/, '_WC_')
  end

  def add_opt_operand insn_name, opts
    insn = @insn_map[insn_name]
    opes = insn.opes

    if opes.size != opts.size
      raise "operand size mismatcvh for #{insn.name} (#{opts.size})"
    end
    
    ninsn = insn.name + '_OP_' + opts.map{|e| label_escape(e)}.join('_')
    nopes = []
    defv  = []
    
    opts.each_with_index{|e, i|
      if e == '*'
        nopes << opes[i]
      end
      defv  << [opes[i], e]
    }

    make_insn_operand_optimiized(insn, ninsn, nopes, defv)
  end
  
  def make_insn_operand_optimiized orig_insn, name, opes, defopes
    comm = orig_insn.comm.dup
    comm[:c] = 'optimize'
    add_insn InsnInfo.new(name, opes, orig_insn.pops, orig_insn.rets, comm, orig_insn.body,
                          orig_insn, defopes)
  end


  ## insn unif
  def load_insn_unification_def file
    open(file){|f| f.each{|line|
      line = line.gsub(/\#.*/, '').strip
      next  if line.length == 0
      break if /__END__/ =~ line
      make_unified_insns line.split.map{|e|
        raise "unknown insn: #{e}" unless @insn_map[e]
        @insn_map[e]
      }
    }}
  end

  def mk_private_val vals, i, redef
    vals.dup.map{|v|
      v = v.dup
      if v[0] != '...'
        redef[v[1]] = 1
        v[1] = "#{v[1]}_#{i}"
      end
      v
    }
  end
  
  def make_unified_insns insns
    names = []
    opes = []
    pops = []
    rets = []
    comm = {
      :c => 'optimize',
      :e => 'unified insn',
      :j => 'unified insn',
    }
    body = ''
    passed = []
    
    insns.each_with_index{|insn, i|
      names << insn.name
      
      redef_vars = {}
      opes.concat mk_private_val(insn.opes, i, redef_vars)
      pops.concat mk_private_val(insn.pops, i, redef_vars)
      rets.concat mk_private_val(insn.rets, i, redef_vars)
      
      body += redef_vars.keys.map{|v| "#define #{v} #{v}_#{i}"}.join("\n") +
              "\n" + insn.body +
              redef_vars.keys.map{|v| "#undef  #{v}"}.join("\n") + "\n"
    }

    add_insn insn = InsnInfo.new("UNIFIED_" + names.join('_'),
                                 opes, pops, rets.reverse, comm, body)
    insns[0].add_unif [insn, insns]
  end
  
  
  ## sc
  SPECIAL_INSN_FOR_SC_AFTER = {
    /\Asend/      => [:a],
    /\Aend/       => [:a],
    /\Ayield/     => [:a],
    /\Aclassdef/  => [:a],
    /\Amoduledef/ => [:a],
  }
  FROM_SC = [[], [:a], [:b], [:a, :b], [:b, :a]]
  
  def make_stackcaching_insns
    pops = rets = nil
    
    @insns.dup.each{|insn|
      opops = insn.pops
      orets = insn.rets
      oopes = insn.opes
      ocomm = insn.comm

      after = nil
      SPECIAL_INSN_FOR_SC_AFTER.any?{|k, v|
        if k =~ insn.name
          after = v
          break
        end
      }
      
      insns = []
      FROM_SC.each{|from|
        name, pops, rets, pushs1, pushs2, nextsc =
          *calc_stack(insn, from, after, opops, orets)

        #
        make_insn_sc(insn, name, oopes, pops, rets, [pushs1, pushs2], nextsc)
      }
    }
    # exit! 1
  end

  def make_insn_sc orig_insn, name, opes, pops, rets, pushs, nextsc
    comm = orig_insn.comm.dup
    comm[:c] = 'optimize(sc)'
    
    scinsn = InsnInfo.new(name, opes, pops, rets, comm, orig_insn.body,
                          orig_insn, orig_insn.defopes, :sc, nextsc, pushs)
    add_insn scinsn
    #
    orig_insn.add_sc scinsn
  end

  def complement_name st
    "#{st[0] ? st[0] : 'x'}#{st[1] ? st[1] : 'x'}"
  end

  def add_stack_value st
    len = st.length
    if len == 0
      st[0] = :a
      [nil, :a]
    elsif len == 1
      if st[0] == :a
        st[1] = :b
      else
        st[1] = :a
      end
      [nil, st[1]]
    else
      st[0], st[1] = st[1], st[0]
      [st[1], st[1]]
    end
  end

  def calc_stack insn, ofrom, oafter, opops, orets
    from = ofrom.dup; pops = opops.dup; rets = orets.dup
    pushs_before = []
    pushs= []
    
    pops.each_with_index{|e, i|
      if e[0] == '...'
        pushs_before = from
        from = []
      end
      r = from.pop
      break unless r
      pops[i] = pops[i].dup << r
    }

    if oafter
      from = oafter
      from.each_with_index{|r, i|
        rets[i] = rets[i].dup << r if rets[i]
      }
    else
      rets = rets.reverse
      rets.each_with_index{|e, i|
        break if e[0] == '...'
        pushed, r = add_stack_value from
        rets[i] = rets[i].dup << r
        pushs << pushed if pushed
      }
    end
    
    if false # insn == 'swap'
      p ofrom
      p pops
      p rets
      p pushs_before
      p pushs
    end
    
    ret = ["#{insn.name}_SC_#{complement_name(ofrom)}_#{complement_name(from)}",
            pops, rets, pushs_before, pushs, from]
    #p ret
    ret
  end

  ###################################################################
  # vm.inc
  def make_header_prepare_stack insn
    ret = []
    push_ba = insn.pushsc
    raise "unsupport" if push_ba[0].size > 0 && push_ba[1].size > 0
    
    push_ba.each{|pushs|
      pushs.each{|r|
        ret << "  PUSH(SCREG(#{r}));"
      }
    }
    ret.join("\n") + "\n"
  end
  
  def make_header_operands insn
    vars = insn.opes
    n = 0
    ops = []
    
    vars.each_with_index{|(type, var), i|
      if type == '...'
        break
      end

      ops << "  #{type} #{var} = (#{type})GET_OPERAND(#{i+1});"
      n   += 1
    }
    @opn = n
    
    # reverse or not?
    # ops.join
    ops.reverse.join("\n") + "\n"
  end
  
  def make_header_default_operands insn
    ret = []
    vars = insn.defopes
    
    vars.each{|e|
      next if e[1] == '*'
      ret << "#define #{e[0][1]} #{e[1]}"
      # "  const #{e[0][0]} #{e[0][1]} = #{e[1]};\n"
    }
    ret.join("\n") + "\n"
  end

  def make_footer_default_operands insn
    ret = []
    vars = insn.defopes
    
    vars.each{|e|
      next if e[1] == '*'
      ret << "#undef #{e[0][1]}\n"
    }
    ret.join("\n") + "\n"
  end
  
  def make_header_stack_pops insn
    n = 0
    pops = []
    
    vars = insn.pops
    
    vars.each_with_index{|iter, i|
      type, var, r = *iter
      
      if type == '...'
        break
      end
      if r
        pops << "  #{type} #{var} = SCREG(#{r});"
      else
        pops << "  #{type} #{var} = TOPN(#{n});"
        n += 1
      end
    }
    @popn = n
    
    # reverse or not?
    pops.reverse.join("\n") + "\n"
  end

  def make_header_stack_val insn
    ret = []

    vars = insn.opes + insn.pops + insn.defopes.map{|e| e[0]}

    insn.rets.each{|var|
      if vars.all?{|e| e[1] != var[1]} && var[1] != '...'
        ret << "  #{var[0]} #{var[1]};"
      end
    }
    ret.join("\n") + "\n"
  end

  def make_footer_stack_val insn
    ret = []
    insn.rets.reverse_each{|v|
      if v[1] == '...'
        break
      end
      if v[2]
        ret << "  SCREG(#{v[2]}) = #{v[1]};"
      else
        ret << "  PUSH(#{v[1]});"
      end
    }
    ret.join("\n") + "\n"
  end
  
  def make_header insn
    ret  = "\nINSN_ENTRY(#{insn.name}){\n"
    ret += "  /* prepare stack status */\n"                 if $verbatim
    ret += make_header_prepare_stack insn
    ret += "{\n"
    ret += "  /* declare stack push val */\n"               if $verbatim
    ret += make_header_stack_val  insn
    ret += "  /* declare and initialize default opes */\n"  if $verbatim
    ret += make_header_default_operands insn
    ret += "  /* declare and get from iseq */\n"            if $verbatim
    ret += make_header_operands   insn
    ret += "  /* declare and pop from stack */\n"           if $verbatim
    ret += make_header_stack_pops insn
    
    ret += "  /* for debug */\n"                            if $verbatim
    ret += "  DEBUG_ENTER_INSN(\"#{insn.name}\");\n"
    ret += "  USAGE_ANALYSIS_INSN(BIN(#{insn.name}));\n"
    
    ret += "  /* management */\n"                           if $verbatim
    ret += "  ADD_PC(1+#{@opn});\n"
    ret += "  POPN(#{@popn});\n" if @popn > 0
    ret += "  #define CURRENT_INSN_#{insn.name} 1\n"
    ret += "  #define INSN_IS_SC()     #{insn.sc ? 0 : 1}\n"
    ret += "  #define INSN_LABEL(lab)  LABEL_#{insn.name}_##lab\n"

    ret += "  #define LABEL_IS_SC(lab) LABEL_##lab##_###{insn.sc.size == 0 ? 't' : 'f'}\n"
    insn.opes.each_with_index{|op, i|
      ret += "  USAGE_ANALYSIS_OPERAND(BIN(#{insn.name}), #{i}, #{op[1]});\n"
    }
    ret += "{\n"
    
  end

  def make_footer insn
    ret  = ''
    ret  = "  /* push stack val */\n"                       if $verbatim
    ret += make_footer_stack_val insn
    # debug info

    # epilogue
    ret += make_footer_default_operands insn
    ret += "#undef CURRENT_INSN_#{insn.name}\n"
    ret += "#undef INSN_IS_SC\n"
    ret += "#undef INSN_LABEL\n"
    ret += "#undef LABEL_IS_SC\n"
    ret += "  END_INSN(#{insn.name});\n}}\n"
    ret += "}\n"
  end

  def make_insn_def insn
    ret  = make_header insn
    ret += insn.body
    ret += make_footer insn
  end
  
  # vm.inc
  def vm_inc
    vm_body = ''

    @insns.each{|insn|
    #@insns.sort.each{|insn|
      vm_body << "\n"
      vm_body << make_insn_def(insn)
    }
    
    ERB.new(File.read(File.join($srcdir, '/tmpl/vm.inc.tmpl'))).result(binding)
  end


  ###################################################################
  # vmtc.inc

  def vmtc_inc
    insns_table = ''
    insns_end_table = ''
    
    @insns.each{|insn|
      insns_table << "  &&LABEL(#{insn.name}),\n"
    }
    @insns.each{|insn|
      insns_end_table << "  &&ELABEL(#{insn.name}),\n"
    }
    
    ERB.new(File.read($srcdir + '/tmpl/vmtc.inc.tmpl')).result(binding)
  end


  ###################################################################
  # insns_info.inc
  
  def op2typesig op
    case op
    when /^OFFSET/
      "TS_OFFSET"
    when /^ulong/
      "TS_ULONG"
    when /^VALUE/
      "TS_VALUE"
    when /^ID/
      "TS_ID"
    when /GENTRY/
      "TS_GENTRY"
    when /^IC/
      "TS_IC"
    when /^\.\.\./
      "TS_VARIABLE"
    else
      raise "unknown op type: #{op}"
    end
  end

  # insns_info.inc
  def insns_info_inc
    # insn_names
    insn_names = ''
    @insns.each{|insn|
      insn_names << "  \"#{insn.name}\",\n"
    }

    # operands info
    operands_info = ''
    operands_num_info = ''
    @insns.each{|insn|
      opes = insn.opes
      operands_info << '  '
      if opes.size == 0
        operands_info << '"",' + "\n"
      else
        opes.each{|type, var|
          operands_info << (op2typesig(type) + ' ')
        }
        operands_info << ",\n"
      end

      num = opes.size + 1
      operands_num_info << "  #{num},\n"
    }

    # stack num
    stack_num_info = ''
    @insns.each{|insn|
      num = insn.rets.size
      stack_num_info << "  #{num},\n"
    }
    
    ERB.new(File.read($srcdir + '/tmpl/insns_info.inc.tmpl')).result(binding)
  end


  ###################################################################
  # insns.inc
  def insns_inc
    insns = ''
    i=0
    @insns.each{|insn|
      insns << "  %-30s = %d,\n" % ["BIN(#{insn.name})", i]
      i+=1
    }
    ERB.new(File.read($srcdir + '/tmpl/insns.inc.tmpl')).result(binding)
  end


  ###################################################################
  # minsns.inc
  def minsns_inc
    defs = ''
    i=0
    @insns.each{|insn|
      defs << "  rb_define_const(mYarvInsns, %-30s, INT2FIX(%d));\n" %
              ["\"I#{insn.name}\"", i]
      i+=1
    }
    ERB.new(File.read($srcdir + '/tmpl/minsns.inc.tmpl')).result(binding)
  end


  ###################################################################
  # optinsn.inc
  def val_as_type op
    type = op[0][0]
    val  = op[1]

    case type
    when /^long/
      "INT2FIX(#{val})"
    when /^ulong/
      "INT2FIX(#{val})"
    when /^VALUE/
      val
    when /^ID/
      "INT2FIX(#{val})"
    when /GENTRY/
      raise
    when /^\.\.\./
      raise
    else
      raise
    end
    
  end
  
  # optinsn.inc
  def optinsn_inc
    rule = ''
    opt_insns_map = Hash.new{|h, k| h[k] = []}
    
    @insns.each{|insn|
      next if insn.defopes.size == 0
      next if insn.type         == :sc

      originsn = insn.orig
      opt_insns_map[originsn] << insn
    }
    
    opt_insns_map.each{|originsn, optinsns|
      rule += "case BIN(#{originsn.name}):\n"
      
      optinsns.sort_by{|opti|
        opti.defopes.find_all{|e| e[1] == '*'}.size
      }.each{|opti|
        rule += "  if(\n"
        i = 0
        rule += '      ' + opti.defopes.map{|opinfo|
          i += 1
          next if opinfo[1] == '*'
          ["GET_OPERAND(#{i-1}) == #{val_as_type(opinfo)}\n"]
        }.compact.join('  &&  ')
        rule += "  ){\n"
          idx = 0
          n = 0
          opti.defopes.each{|opinfo|
            if opinfo[1] == '*'
              idx += 1
            else
              rule += "  rb_ary_delete_at(insnobj->operands, #{idx});\n"
            end
            n += 1
          }
          rule += "  self = new_insn(BIN(#{opti.name}), insnobj->line_no, insnobj->operands);\n"
        rule += "  break;}\n"
      }
      rule += "break;\n";
    }
    ERB.new(File.read($srcdir + '/tmpl/optinsn.inc.tmpl')).result(binding)
  end

  
  ###################################################################
  # optunifs.inc
  def optunifs_unc
    unif_insns_each = ''
    unif_insns      = ''
    unif_insns_data = []
    
    insns = find_all{|insn| !insn.is_sc}
    insns.each{|insn|
      size = insn.unifs.size
      if size > 0
        insn.unifs.each_with_index{|unif, i|
          
          uni_insn, uni_insns = *unif
          uni_insns = uni_insns[1..-1]
          unif_insns_each << "static int UNIFIED_#{insn.name}_#{i}[] = {" +
                             "  BIN(#{uni_insn.name}), #{uni_insns.size + 2}, \n  " +
                             uni_insns.map{|e| "BIN(#{e.name})"}.join(", ") + "};\n"
        }
      else
        
      end
      if size > 0
        unif_insns << "static int *UNIFIED_#{insn.name}[] = {(int *)#{size+1}, \n"
        unif_insns << (0...size).map{|e| "  UNIFIED_#{insn.name}_#{e}"}.join(",\n") + "};\n"
        unif_insns_data << "  UNIFIED_#{insn.name}"
      else
        unif_insns_data << "  0"
      end
    }
    unif_insns_data = "static int **unified_insns_data[] = {\n" +
                      unif_insns_data.join(",\n") + "};\n"
    ERB.new(File.read($srcdir + '/tmpl/optunifs.inc.tmpl')).result(binding)
  end

  ###################################################################
  # opt_sc.inc
  def opt_sc_inc
    sc_insn_info = []
    @insns.each{|insn|
      insns = insn.sc
      if insns.size > 0
        insns = ['SC_ERROR'] + insns.map{|e| "    BIN(#{e.name})"}
      else
        insns = Array.new(6){'SC_ERROR'}
      end
      sc_insn_info << "  {\n#{insns.join(",\n")}}"
    }
    sc_insn_info = sc_insn_info.join(",\n")
    
    sc_insn_next = @insns.map{|insn|
      "  SCS_#{complement_name(insn.nextsc).upcase}"
    }.join(",\n")
    ERB.new(File.read($srcdir + '/tmpl/opt_sc.inc.tmpl')).result(binding)
  end

  ###################################################################
  # yasmdata.rb
  def yasmdata_rb
    insn_id2no = ''
    @insns.each_with_index{|insn, i|
      insn_id2no << "        :#{insn.name} => #{i},\n"
    }
    ERB.new(File.read($srcdir + '/tmpl/yasmdata.rb.tmpl')).result(binding)
  end
  
  ###################################################################
  # yarvarch.*
  def desc lang
    d = ''
    i = 0
    cat = nil
    @insns.each{|insn|
      seq    = insn.opes.map{|t,v| v}.join(' ')
      before = insn.pops.reverse.map{|t,v| v}.join(' ')
      after  = insn.rets.reverse.map{|t,v| v}.join(' ')

      if cat != insn.comm[:c]
        d << "** #{insn.comm[:c]}\n\n"
      end
      
      d << "*** #{insn.name}\n"
      d << "\n"
      d << insn.comm[lang] + "\n\n"
      d << ":instruction sequence: 0x%02x #{seq}\n" % i
      d << ":stack: #{before} => #{after}\n\n"
      i+=1
    }
    d
  end
  
  def desc_ja
    d = desc :j
    ERB.new(File.read($srcdir + '/tmpl/yarvarch.ja')).result(binding)
  end

  def desc_en
    d = desc :e
    ERB.new(File.read($srcdir + '/tmpl/yarvarch.en')).result(binding)
  end
  
  Files = { # codes
    'vm.inc'         => :vm_inc,
    'vmtc.inc'       => :vmtc_inc,
    'insns.inc'      => :insns_inc,
    'insns_info.inc' => :insns_info_inc,
    # 'minsns.inc'     => :minsns_inc,
    'optinsn.inc'    => :optinsn_inc,
    'optunifs.inc'   => :optunifs_unc,
    'opt_sc.inc'     => :opt_sc_inc,
    'yasmdata.rb'    => :yasmdata_rb,
  }
  
  def self.make_sources insns_def, opopt_def, unif_def, args = []
    insns = InsnsDef.new(insns_def, opopt_def, unif_def)
    
    args = Files.keys if args.empty?
    
    args.each{|fn|
      s = Files[fn]

      open(fn, 'w'){|f|
        f.puts(insns.__send__(s))
      }
    }
  end
end




##############################################
files = []
$opts = {}
$srcdir = '.'
insns_def = 'insns.def'
opope_def = 'opt_operand.def'
unif_def  = 'opt_insn_unif.def'

ARGV.each{|e|
  case e
  when /\A\-D(\w+)/
    $opts[$1] = true
  when /\A--srcdir=(.+)/
    $srcdir = $1
  when /\A--insnsdef=(.+)/
    insns_def = $1
  when /\A--opt-operanddef=(.+)/
    opope_def = $1
  when /\A--opt-insnunifdef=(.+)/
    unif_def  = $1
  when /\A-/
    # ignore
  else
    files << e
  end
}

$insns_def = File.join($srcdir, insns_def)
$opope_def = File.join($srcdir, opope_def)
$unif_def  = File.join($srcdir, unif_def)

def insns_def_new
  InsnsDef.new $insns_def, $opope_def, $unif_def
end

if $0 == __FILE__
  InsnsDef.make_sources $insns_def, $opope_def, $unif_def, files
end

