#!/usr/bin/env ruby

################################################################################
# SCRIPT      : SHAcho {Pa,Pi,Pu,Pe,Po}co Assembler
# VERSION     : 1.006
# LAST UPDATE : 2009/04/21(Tue)
# AUTHOR      : Yoshiki Saito (shacho)
# AFFILIATION : Amano Laboratory, Department of Computer Science,
#             :   Open and Environmental Systems, Keio University, Japan
################################################################################



$def_param_filename   = 'poco.rb'
$def_dst_filename     = './prg.bin'
$def_pc_incr          = 2
$def_line_bit_num     = 16
$def_head_filename    = nil
$def_tail_filename    = nil
$def_with_src         = true
$def_bit_num          = 16
$def_signed           = 'signed'

$debug_mode           = false
$comment_mark         = '//'
$verilog_comment_mark = '//'
$dont_care_char       = '0'


################################################################
##                                                            ##
##                      DON'T EDIT BELOW                      ##
##                                                            ##
################################################################

BIT_NUM        = 0
SIGN           = 1
BRANCH         = 2

ASM_FORMAT     = 0
BIN_FORMAT     = 1
REGSTR         = 2
REGEXP         = 3
VAR_ORDER      = 4
BIN_REGSTR     = 5
BIN_REGEXP     = 6
BIN_VAR_ORDER  = 7


$bin_regexp      = /^[01]+$/
$arch            = nil

$label_regstr           = '[a-zA-Z]\w*'
$op_regstr              = '[a-zA-Z]\w*'
$var_name_regstr        = '[a-zA-Z]\w*'
$branch_addr_regstr     = '^[+\-]?\d+$'
$label_regexp           = Regexp.new($label_regstr      )
$op_regexp              = Regexp.new($op_regstr         )
$var_name_regexp        = Regexp.new($var_name_regstr   )
$branch_addr_regexp     = Regexp.new($branch_addr_regstr)

$analyze_label_regstr   = '^(?:(' + $label_regstr + ')\s*:)?'
$analyze_comment_regstr = '(?:' + $comment_mark + '(.*))?$'
$analyze_op_regstr      = '^(' + $op_regstr + ')'
$analyze_label_regexp   = Regexp.new($analyze_label_regstr  )
$analyze_comment_regexp = Regexp.new($analyze_comment_regstr)
$analyze_op_regexp      = Regexp.new($analyze_op_regstr     )


$scr_name  = 'SHAcho {Pa,Pi,Pu,Pe,Po}co Assembler'
$version   = '1.008'
$author    = 'Yoshiki Saito'
$mail_addr = 'shacho__${atmark}__am.ics.keio.ac.jp'


ERROR   = 'ERROR!! : '
WARNING = 'WARNING : '
BUG     = 'BUG     : '
INFO    = 'INFO    : '
DEBUG   = 'DEBUG   : '
EMPTY   = '        : '
USAGE   = 'USAGE   : '
SCRIPT  = 'SCRIPT  : '
AUTHOR  = 'AUTHOR  : '
HISTORY = 'HISTORY : '
LOG     = 'LOG     : '
REPORT  = 'REPORT  : '

USAGE_MSG = USAGE + $0 + " [OPTION(s)] {SRC_FILE,ASCII(s),NUM(s)}"                                                            + "\n" \
          + EMPTY + "  --arch PARAM_FILE  : Set parameter file name to PARAM_FILE."                                           + "\n" \
          + EMPTY + "  -o DST_FILE        : Set output file name to DST_FILE."                                                + "\n" \
          + EMPTY + "                     :   Default name is ENV['SHAPA_DEFOUT'] or 'a.out'."                                + "\n" \
          + EMPTY + "  -m                 : Generate mnemonic file from binary file"                                          + "\n" \
          + EMPTY + "                     :   Maybe useful for debugging??"                                                   + "\n" \
          + EMPTY + "                     :   Be careful if the name of the binary file is 'a.out' and -o option is not set." + "\n" \
          + EMPTY + "                     :   The binary file will be overwritten."                                           + "\n" \
          + EMPTY + "  -h HEAD_FILE       : Concat HEAD_FILE at the begining of the output file."                             + "\n" \
          + EMPTY + "                     :   Maybe useful for SFL??."                                                        + "\n" \
          + EMPTY + "  -t TAIL_FILE       : Concat TAIL_FILE at the end of the output file."                                  + "\n" \
          + EMPTY + "                     :   Maybe useful for SFL??."                                                        + "\n" \
          + EMPTY + "  --no_src           : Generate binary file or mnemonic file with no source code commented out."         + "\n" \
          + EMPTY + "  -n NUM(s)          : Translate NUM(s) to binary, oct, decimal and hex."                                + "\n" \
          + EMPTY + "                     :   NUM can be binary, oct, decimal or hex."                                        + "\n" \
          + EMPTY + "                     :   Binary, Oct, and Hex starts from '0b', '0' and '0x', respectively."             + "\n" \
          + EMPTY + "                     :   No assembling will be done,"                                                    + "\n" \
          + EMPTY + "                     :   and option '--arch', '-o', '-m', '-a', '--a2b', '--a2o', '--a2d', '--a2h'"      + "\n" \
          + EMPTY + "                     :   will be ignored."                                                               + "\n" \
          + EMPTY + "  -b BIT_NUM         : Change default bit_num to BIT_NUM."                                               + "\n" \
          + EMPTY + "                     :   Used with '-n' option for translating values"                                   + "\n" \
          + EMPTY + "                     :   to bin, oct, dec or hex."                                                       + "\n" \
          + EMPTY + "  -s signed/unsigned : Set default singed/unsigned."                                                     + "\n" \
          + EMPTY + "                     :   Used with '-n' option for translating values"                                   + "\n" \
          + EMPTY + "                     :   to bin, oct, dec or hex."                                                       + "\n" \
          + EMPTY + "                     :   Default setting is '" + (($def_signed == 'signed')? 'signed':'unsigned') + "'." + "\n" \
          + EMPTY + "  -a ASCII(s)        : Translate ASCII(s) to binary, oct, decimal, hex."                                 + "\n" \
          + EMPTY + "                     :   No assembling will be done,"                                                    + "\n" \
          + EMPTY + "                     :   and option '--arch', '-o', '-m' will be ignored."                               + "\n" \
          + EMPTY + "  --a2b ASCII(s)     : Translate ASCII(s) to binary."                                                    + "\n" \
          + EMPTY + "                     :   No assembling will be done,"                                                    + "\n" \
          + EMPTY + "                     :   and option '--arch', '-o', '-m' will be ignored."                               + "\n" \
          + EMPTY + "  --a2o ASCII(s)     : Translate ASCII(s) to oct."                                                       + "\n" \
          + EMPTY + "                     :   No assembling will be done,"                                                    + "\n" \
          + EMPTY + "                     :   and option '--arch', '-o', '-m' will be ignored."                               + "\n" \
          + EMPTY + "  --a2d ASCII(s)     : Translate ASCII(s) to decimal."                                                   + "\n" \
          + EMPTY + "                     :   No assembling will be done,"                                                    + "\n" \
          + EMPTY + "                     :   and option '--arch', '-o', '-m' will be ignored."                               + "\n" \
          + EMPTY + "  --a2h ASCII(s)     : Translate ASCII(s) to hex."                                                       + "\n" \
          + EMPTY + "                     :   No assembling will be done,"                                                    + "\n" \
          + EMPTY + "                     :   and option '--arch', '-o', '-m' will be ignored."                               + "\n" \
          + EMPTY + "  --debug            : Set debug mode on."                                                               + "\n" \
          + EMPTY + "  --history          : Display history (bug reports, etc)"                                               + "\n" \
          + EMPTY + "  --help             : Display this message, and do nothing."                                            + "\n" \
          + EMPTY + "  --version          : Display version, and do nothing."


def msg(msg_type, msg, *msgs)
  msg_str = Array.new
  msg_str << msg_type + msg.to_s
  until msgs.empty?
    msg_str << EMPTY + msgs.shift.to_s
  end
  return msg_str.join("\n")
end


def error_msg(msg, *msgs)
  warn msg(ERROR, msg, *msgs)
end


def warning_msg(msg, *msgs)
  warn msg(WARNING, msg, *msgs)
end


def bug_msg(msg, *msgs)
  abort msg(BUG, msg, *msgs)
end


def info_msg(msg, *msgs)
  print msg(INFO, msg, *msgs)
end


def debug_msg(msg, *msgs)
  print msg(DEBUG, msg, *msgs)
end


def log_msg(msg, *msgs)
  print msg(LOG, msg, *msgs)
end


def report_msg(msg, *msgs)
  print msg(REPORT, msg, *msgs)
end


def usage_msg
  warn ''
  warn SCRIPT + $scr_name + ' Ver.' + $version
  warn AUTHOR + $author
  warn ''
  warn USAGE_MSG
  warn ''
end


def version_msg
  warn ''
  warn SCRIPT + $scr_name + ' Ver.' + $version
  warn AUTHOR + $author
  warn ''
  warn INFO  + 'If you found any bugs, please inform it to Hunga-san with your source file.'
  warn EMPTY + '  or inform me by sending an e-mail(' + $mail_addr + ') by the end of 2009.'
  warn ''
end


def history_msg
  warn HISTORY + '2008/12/29(Mon) : Numeric done.'
  warn EMPTY   + '2008/12/29(Mon) : ASCII done.'
  warn EMPTY   + '2008/12/29(Mon) : Arch done.'
  warn EMPTY   + '2008/12/30(Tue) : Assemble done.'
  warn EMPTY   + '2008/01/04(Sun) : Mnemonic done.'
  warn EMPTY   + '2009/01/04(Sun) : Makefile done.'
  warn EMPTY   + '2009/01/04(Sun) : Bugs removed from getopts and numeric library'
  warn EMPTY   + '                :   (non-option args in cmdline starts with hyphen, such as negative val)'
  warn EMPTY   + '2009/01/05(Mon) : Mnemo done.'
  warn EMPTY   + '2009/01/06(Tue) : Concatinator done.'
  warn EMPTY   + '2009/01/31(Sat) : Bugs removed from replace_labels function.'
  warn EMPTY   + '2009/02/28(Sat) : README done.'
  warn EMPTY   + '2009/04/12(Sun) : Checker head/tail filename bug removed.'
  warn EMPTY   + '2009/04/14(Tue) : $def_dst_filename changed from "a.out" to "prg.bin".'
  warn EMPTY   + '2009/04/14(Tue) : First release. (-> version 1.001)'
  warn EMPTY   + '2009/04/14(Tue) : Several kinds of bugs, caused by applying different.'
  warn EMPTY   + '                :   version of ruby libraries in hlab and Yagami ITC, removed.'
  warn EMPTY   + '                :   (-> version 1.002 -> 1.003 -> 1.004)'
  warn EMPTY   + '2009/04/14(Tue) : Symbolic linked file was not acceptable, and now free.'
  warn EMPTY   + '2009/04/21(Tue) : Option "--history" was proveded. (-> version 1.006)'
  warn EMPTY   + '2009/05/12(Tue) : Instructions LB, LBU, SB added to poco.rb parameter file (-> version 1.007)'
  warn EMPTY   + '2009/05/19(Tue) : Instructions RTI and EINT added to poco.rb parameter file (-> version 1.008)'
end


def empty_msg1(msg, *msgs)
  print msg(EMPTY, msg, *msgs)
end


def empty_msg2(msg, *msgs)
  warn msg(EMPTY, msg, *msgs)
end


# check if FILENAME is readable and is readable
def is_readable(filename)
  return ((File.readable? filename) and (not test(?d, filename)))
end


def get_lines_of(filename)
  if not is_readable(filename)
    return nil
  else
    lines = Array.new
    open(filename, 'r') do |src|
      src.each do |line|
        lines << line.chomp
      end
    end
    return lines
  end
end


IMPROPER = 0
PROPER   = 1

EMPTY_LINE      = 0
UNKNOWN_OPECODE = 1
UNKNOWN_LABEL   = 2
IMPROPER_FORMAT = 3
DEF_FORMAT      = 4


class Info
  attr_reader :definition
  def initialize(definition, type)
    @definition = definition
    @type       = type
  end
  
  
  def is_proper
    return (@type == PROPER)
  end
  
  
  def is_improper
    return (@type == IMPROPER)
  end
end



def num_opts(opts)
  def_opts = {
    :signed           => 'signed',
    :bit_num          => 16,
    :warn_msg         => false,
    :err_msg          => false,
    :err_overflow     => false,
    :err_neg_unsigned => false
  }
  opts = def_opts.merge(opts)
  
  if (opts[:signed] != 'signed') and (opts[:signed] != 'unsigned')
    opts[:signed] = def_opts[:signed]
  end
  if (not opts[:bit_num].is_a? Integer) or (opts[:bit_num] < 2)
    opts[:bit_num] = def_opts[:bit_num]
  end
  if (opts[:warn_msg] != true) and (opts[:warn_msg] != false)
    opts[:warn_msg] = def_opts[:warn_msg]
  end
  if (opts[:err_msg] != true) and (opts[:err_msg] != false)
    opts[:err_msg] = def_opts[:err_msg]
  end
  if (opts[:err_overflow] != true) and (opts[:err_overflow] != false)
    opts[:err_overflow] = def_opts[:err_overflow]
  end
  if (opts[:err_neg_unsigned] != true) and (opts[:err_neg_unsigned] != false)
    opts[:err_neg_unsigned] = def_opts[:err_neg_unsigned]
  end
  
  return opts[:signed], opts[:bit_num], opts[:warn_msg], opts[:err_msg], opts[:err_overflow], opts[:err_neg_unsigned]
end


def to_bin(val_str, opts = {})
  signed, bit_num, warn_msg, err_msg, err_overflow, err_neg_unsigned = num_opts(opts)
  
  case val_str
  when /^0b[01]+$/
    radix = 2
  when /^0[0-7]+$/
    radix = 8
  when /^[+\-]?\d+$/
    radix = 10
  when /^0x[\da-fA-F]+$/
    radix = 16
  else
    if err_msg
      error_msg 'Improper value found: ' + val_str
    elsif warn_msg
      warning_msg 'Improper value found: ' + val_str
    end
    return 'err_improper_value'
  end
  
  if err_neg_unsigned and (radix == 10) and (val_str =~ /^\-/) and (signed == 'unsigned')
    if err_msg
      error_msg 'Negative value found while translating unsigned value: ' + val_str
    elsif warn_msg
      warning_msg 'Negative value found while translating unsigned value: ' + val_str
    end
    return 'err_neg_unsigned'
  end
  
  bin_str = sprintf('%0' + bit_num.to_s + 'b', val_str)
  if bin_str =~ /\.\./                                          # yagami itc
    bin_str.sub!(/^.*\.\./, '')                                 # yagami itc
    bin_str = bin_str.rjust(bit_num)                            # yagami itc
    bin_str =~ /^(\s*)(0|1)/                                    # yagami itc
    space_len = $1.length                                       # yagami itc
    msb_val = $2                                                # yagami itc
    bin_str.sub!(' ' * space_len, msb_val * space_len)          # yagami itc
  end                                                           # yagami itc
  
  overflow = false
  if bin_str.length > bit_num
    overflow = true
  end
  if (signed == 'signed') and (radix == 10) and (val_str !~ /^-/) and (bin_str =~ /^1/)
    overflow = true
  end
  if overflow
    if err_msg
      warning_msg 'Bit overflowed: ' + val_str + ' -> ' + bin_str
    elsif warn_msg
      warning_msg 'Bit overflowed: ' + val_str + ' -> ' + bin_str
    end
    if err_overflow
      return 'err_bit_overflow'
    end
  end
  
  return bin_str
end


def to_oct(val_str, opts = {})
  signed, bit_num, warn_msg, err_msg, err_overflow, err_neg_unsigned = num_opts(opts)
  bin_str = to_bin(val_str, :signed => signed, :bit_num => bit_num, :warn_msg => warn_msg, :err_msg => err_msg, :err_overflow => err_overflow)
  if bin_str =~ /^err_/
    return bin_str
  end
  bin_str  = '0b' + bin_str
  oct_str  = sprintf('%0' + (bit_num / 3.0).ceil.to_s + 'o', bin_str)
  
  return oct_str
end


def to_dec(val_str, opts = {})
  signed, bit_num, warn_msg, err_msg, err_overflow, err_neg_unsigned = num_opts(opts)
  bin_str = to_bin(val_str, :signed => signed, :bit_num => bit_num, :warn_msg => warn_msg, :err_msg => err_msg, :err_overflow => err_overflow)
  if bin_str =~ /^err_/
    return bin_str
  end
  bin_str = '0b' + bin_str
  
  if (signed == 'signed') and (bin_str =~ /^0b1/)
    bin_min1_str = sprintf('%0' + bit_num.to_s + 'b', (bin_str.oct - 1))
    inv_bin_str  = bin_min1_str.gsub(/1/, '2')
    inv_bin_str.gsub!(/0/, '1')
    inv_bin_str = '0b' + inv_bin_str.gsub(/2/, '0')
    dec_str      = sprintf('-%d', inv_bin_str)
  else
    dec_str = sprintf('%d', bin_str)
  end
  
  return dec_str
end


def to_hex(val_str, opts = {})
  signed, bit_num, warn_msg, err_msg, err_overflow, err_neg_unsigned = num_opts(opts)
  bin_str = to_bin(val_str, :signed => signed, :bit_num => bit_num)
  if bin_str =~ /^err_/
    return bin_str
  end
  bin_str = '0b' + bin_str
  hex_str = sprintf('%0' + (bit_num / 4.0).ceil.to_s + 'X', bin_str)
  
  return hex_str
end


def show_bin_oct_dec_hex(num_ary, bit_num, signed)
  if num_ary.size == 0
    return
  end
  bin_ary = Array.new
  oct_ary = Array.new
  dec_ary = Array.new
  hex_ary = Array.new
  
  #### TRANSLATE ####
  num_ary.each do |num|
    bin_ary << to_bin(num, :bit_num => bit_num, :signed => signed, :warn_msg => false, :err_msg => false, :err_overflow => false, :err_neg_unsigned => false)
    oct_ary << to_oct(num, :bit_num => bit_num, :signed => signed, :warn_msg => false, :err_msg => false, :err_overflow => false, :err_neg_unsigned => false)
    dec_ary << to_dec(num, :bit_num => bit_num, :signed => signed, :warn_msg => false, :err_msg => false, :err_overflow => false, :err_neg_unsigned => false)
    hex_ary << to_hex(num, :bit_num => bit_num, :signed => signed, :warn_msg => false, :err_msg => false, :err_overflow => false, :err_neg_unsigned => false)
  end
  
  #### CHANGE ERROR TO NIL ####
  bin_ary.each_index do |i|
    if bin_ary[i] == 'err_improper_value'
      bin_ary[i] = nil
      oct_ary[i] = nil
      dec_ary[i] = nil
      hex_ary[i] = nil
    end
  end
  
  #### SET OUTPUT FORMAT ####
  num_str_length = 'num'.length
  bin_str_length = 'bin'.length
  oct_str_length = 'oct'.length
  dec_str_length = 'dec'.length
  hex_str_length = 'hex'.length
  
  num_ary.each do |num_str|
    if num_str.length > num_str_length
      num_str_length = num_str.length
    end
  end
  bin_ary.each do |bin_str|
    if bin_str == nil
      next
    end
    if bin_str.length > bin_str_length
      bin_str_length = bin_str.length
    end
  end
  oct_ary.each do |oct_str|
    if oct_str == nil
      next
    end
    if oct_str.length > oct_str_length
      oct_str_length = oct_str.length
    end
  end
  dec_ary.each do |dec_str|
    if dec_str == nil
      next
    end
    if dec_str.length > dec_str_length
      dec_str_length = dec_str.length
    end
  end
  hex_ary.each do |hex_str|
    if hex_str == nil
      next
    end
    if hex_str.length > hex_str_length
      hex_str_length = hex_str.length
    end
  end
  
  num_format = '%' + num_str_length.to_s + 's'
  bin_format = '%' + bin_str_length.to_s + 's'
  oct_format = '%' + oct_str_length.to_s + 's'
  dec_format = '%' + dec_str_length.to_s + 's'
  hex_format = '%' + hex_str_length.to_s + 's'
  str_format = [num_format, bin_format, oct_format, dec_format, hex_format].join('  ')
  header     = ['num'.ljust(num_str_length + 1), 'bin'.ljust(bin_str_length + 1),
                'oct'.ljust(oct_str_length + 1), 'dec'.ljust(dec_str_length + 1),
                'hex'.ljust(hex_str_length)].join(' ')
  hor_line   = '-' * (header.length) + "\n"
  
  #### OUTPUT ####
  print "\n" + '[' + bit_num.to_s + 'bit]' + "\n"
  print hor_line
  print header + "\n"
  print hor_line
  num_ary.each_index do |i|
    print sprintf(str_format, num_ary[i], bin_ary[i], oct_ary[i], dec_ary[i], hex_ary[i])
    if bin_ary[i] == nil
      print '    Improper value!!'
    else
      warn_str = Array.new
      if bin_ary[i].length > bit_num
        warn_str << 'Overflowed!!'
      end
      if (signed == 'unsigned') and (num_ary[i] =~ /^\-/)
        warn_str << 'Not unsigned value!!'
      end
      print '    ' + warn_str.join(', ')
    end
    print "\n"
  end
  print hor_line + "\n"
end


def str2num_ary(str)
  num_ary = Array.new
  str.length.times do |i|
    num_ary << str[i]
  end
  return num_ary
end


def show_ascii2num(token_ary, opts = {})
  if token_ary.empty?
    return
  end
  
  def_opts = {:bin => true, :oct => true, :dec => true, :hex => true}
  opts = def_opts.merge(opts)
  
  num_ary = Array.new
  bin_ary = Array.new
  oct_ary = Array.new
  dec_ary = Array.new
  hex_ary = Array.new
  token_ary.each do |token|
    num_ary << str2num_ary(token)
    bin_str = Array.new
    oct_str = Array.new
    dec_str = Array.new
    hex_str = Array.new
    num_ary.last.each do |num|
      bin_str << sprintf('%08b', num)
      oct_str << sprintf('%03o', num)
      dec_str << sprintf('%d',   num)
      hex_str << sprintf('%02x', num)
    end
    bin_ary << bin_str.join(' ')
    oct_ary << oct_str.join(' ')
    dec_ary << dec_str.join(' ')
    hex_ary << hex_str.join(' ')
  end
  
  token_ary.each do |token|
    bin = bin_ary.shift
    hor_line = '-' * (7 + bin.length) + "\n"
    print '[' + token + ']' + "\n"
    print hor_line
    print 'radix  ascii code' + "\n"
    print hor_line
    if opts[:bin]
      print 'bin    ' + bin           + "\n"
    end
    if opts[:oct]
      print 'oct    ' + oct_ary.shift + "\n"
    end
    if opts[:dec]
      print 'dec    ' + dec_ary.shift + "\n"
    end
    if opts[:hex]
      print 'hex    ' + hex_ary.shift + "\n"
    end
    print hor_line + "\n"
  end
end


def getopts(single_opts, *long_opts)
  $opts = Hash.new
  $improper_opts = Array.new
  
  single_opt_ary = available_single_opts(single_opts)
  string_opt_ary = available_string_opts(long_opts)
  char_opt_ary   = available_char_opts(long_opts)
  optimize_available_opts(single_opt_ary, char_opt_ary)
  
  register_string_opt(string_opt_ary)
  register_char_opt(char_opt_ary)
  register_single_opt(single_opt_ary)
  
  return (not ARGV.include? /^-/)
end


def optimize_available_opts(single_opt_ary, char_opt_ary)
  single_opt_ary.delete_if do |opt|
    char_opt_ary.include? opt
  end
end


def available_single_opts(single_opts)
  if single_opts == nil or single_opts == ''
    return Array.new
  end
  
  single_opt_ary = Array.new
  improper_single_opt_ary = Array.new
  single_opts.each_byte do |opt|
    opt = opt.chr
    if opt =~ /^[a-zA-Z]$/
      single_opt_ary << opt
    else
      improper_single_opt_ary << opt
    end
  end
  
  if improper_single_opt_ary.size > 0
    raise 'Tried to register following improper single opts: ' + improper_single_opt_ary.join(', ')
  end
  
  return single_opt_ary
end


def available_char_opts(long_opts)
  if long_opts.empty?
    return Array.new
  end
  
  char_opt_ary = Hash.new
  long_opts.delete_if do |opt|
    if opt =~ /^[a-zA-Z](:)?$/
      param = ($1 == ':')
      opt.sub!(/:$/, '')
      char_opt_ary[opt] = param
      true
    else
      false
    end
  end
  
  return char_opt_ary
end


def available_string_opts(long_opts)
  if long_opts.empty?
    return Array.new
  end
  
  string_opt_ary = Hash.new
  long_opts.delete_if do |opt|
    if opt =~ /^[a-zA-Z][\w\-]+(:)?$/
      param = ($1 == ':')
      opt.sub!(/:$/, '')
      string_opt_ary[opt] = param
      true
    else
      false
    end
  end
  
  return string_opt_ary
end


def register_single_opt(single_opt_ary)
  if single_opt_ary.empty?
    return
  end
  
  i = 0
  while i < ARGV.size
    if ARGV[i] =~ /^-\w/
      opt = ARGV[i].sub(/^-/, '')
      
      j = 0
      while j < opt.length
        opt_char = opt[j].chr
        if single_opt_ary.include? opt_char
          $opts[opt_char] = true
          opt.sub!(opt_char, '')
        else
          j += 1
        end
      end
      ARGV[i] = '-' + opt
    end
    i += 1
  end
  
  ARGV.delete_if do |arg|
    arg == '-'
  end
  
  return
end


def register_char_opt(char_opt_ary)
  if char_opt_ary.empty?
    return
  end
  
  i = 0
  while i < ARGV.size
    if ARGV[i] =~ /^-\w/
      opts = ARGV[i].sub(/^-/, '')
      j = 0
      while j < opts.length
        opts_j = opts[j].chr
        case char_opt_ary[opts_j]
        when true
          opts =~ /#{opts_j}(.+)$/
          param = $1
          if param == nil
            param = ARGV[i + 1]
            ARGV.delete_at(i + 1)
          end
          if param == nil
            break
          end
          $opts[opts_j] = param
          opts.sub!(/#{opts_j}.*$/, '')
        when false
          $opts[opts_j] = true
          opts.sub!(opts_j, '')
        else
          j += 1
        end
      end
      
      ARGV[i] = '-' + opts
    end
    i += 1
  end
  
  ARGV.delete_if do |arg|
    arg == '-'
  end
  
  return
end


def register_string_opt(string_opt_ary)
  if string_opt_ary.empty?
    return
  end
  
  i = 0
  while i < ARGV.size
    if ARGV[i] =~ /^--/
      opt = ARGV[i].sub(/^--/, '')
      case string_opt_ary[opt]
      when true
        if ARGV[i + 1] != nil
          $opts[opt] = ARGV[i + 1]
          ARGV.delete_at(i)
          ARGV.delete_at(i)
        else
          i += 1
        end
      when false
        $opts[opt] = true
        ARGV.delete_at(i)
      else
        i += 1
      end
    else
      i += 1
    end
  end
  
  return
end


# available options
#   --help             : display version and usage.
#   --version          : display version and usage.
#   --history          : display history (bug reports, etc)
#   --debug            : set debug mode on.
#   
#   --arch             : set architecture file name.
#   -o FILENAME        : set output file name.
#   -m                 : translate binary file to mnemonic,
#                      :   and output to the file if output file name is set with '-o' option, or to stdout if not.
#   -h HEAD_FILE       : concat HEAD_FILE at the begining of the dst file.
#   -t TAIL_FILE       : concat TAIL_FILE at the end of the dst file.
#   --no_src           : generate bin_file with no source code, commented out.
#   -b BIT_NUM         : change default bit_num to BIT_NUM.
#   -s signed/unsigned : set default signed/unsigned.
#   -n NUMs            : translate NUM to binary, oct, decimal and hex.
#   -a ASCII(s)        : translate ASCII(s) to binary, oct, decimal and hex.
#   --a2b ASCII(s)     : translate ASCII(s) to binary.
#   --a2o ASCII(s)     : translate ASCII(s) to oct.
#   --a2d ASCII(s)     : translate ASCII(s) to decimal.
#   --a2h ASCII(s)     : translate ASCII(s) to hex.


def read_cmdline
  getopts('amn', 'arch:', 'o:', 'b:', 'h:', 't:', 'no_src', 's:', 'a2b', 'a2o', 'a2d', 'a2h', 'help', 'version', 'history', 'debug')
  improper_opts = ARGV.dup
  improper_opts.delete_if do |opt|
    (opt !~ /^-/) or (opt =~ /^-\d+$/)
  end
  
  if improper_opts.size > 0
    error_msg('Improper options found in cmdline.', improper_opts.join(', '));
    abort
  end
  
  if $opts['debug']
    $debug_mode = true
  end
  
  if $opts['help']
    usage_msg
    abort
  end
  if $opts['version']
    version_msg
    abort
  end
  if $opts['history']
    history_msg
    abort
  end
  
  if $opts['b']
    if $opts['b'] !~ /^\d+$/ or $opts['b'].to_i < 2
      warning_msg 'Ignoring option \'-b\', since ' + $opts['b'] + ' is not an integer bigger than or equal to 2.'
    else
      $def_bit_num = $opts['b'].to_i
    end
  end
  
  if $opts['s']
    case $opts['s']
    when 'signed', 'unsigned'
      $def_signed = $opts['s']
    else
      warning_msg 'Ignoring option \'-s\', since signed/unsigned is required as its parameter, not \'' + $opts['s'] + '\'.'
    end
  end
  
  if $opts['h']
    $def_head_filename = $opts['h']
  end
  if $opts['t']
    $def_tail_filename = $opts['t']
  end
  
  if $opts['no_src']
    $def_with_src = false
  end
  
  if $opts['arch']
    param_filename = $opts['arch']
  else
    param_filename = $def_param_filename
  end
  
  if $opts['o']
    dst_filename = $opts['o']
  elsif ENV.has_key? 'SHAPA_DEFOUT'
    dst_filename = ENV['SHAPA_DEFOUT']
  else
    dst_filename = $def_dst_filename
  end
  
  execution_mode = 'assemble'
  if $opts['m']
    execution_mode = 'to_mnemonic'
  end
  if $opts['a'] or $opts['a2b'] or $opts['a2o'] or $opts['a2d'] or $opts['a2h']
    execution_mode = 'ascii_to_num'
  end
  if $opts['n']
    execution_mode = 'numerical'
  end
  
  if $debug_mode
    info_msg "Executing shapa with debug mode\n"
  end
  case execution_mode
  when 'numerical'
    info_msg "Executing shapa with numerical mode\n"
  when 'ascii_to_num'
    info_msg "Executing shapa with ascii_to_num mode\n"
  when 'assemble'
    info_msg "Executing shapa with assemble mode\n"
    if ARGV.empty?
      error_msg 'Source file name missing in cmdline.'
      abort
    end
    src_filename = ARGV.shift
    if not ARGV.empty?
      warning_msg 'Extra argument(s) found in cmdline, and was/were ignored', ARGV.join(', ')
    end
  when 'to_mnemonic'
    info_msg "Executing shapa with to_mnemonic mode\n"
    if ARGV.empty?
      error_msg 'Binary file name missing in cmdline.'
      abort
    end
    src_filename = ARGV.shift
    if not ARGV.empty?
      warning_msg 'Extra argument(s) found in cmdline, and was/were ignored', ARGV.join(', ')
    end
  else
    error_msg 'Unknown execution mode'
    abort
  end
  
  return execution_mode, param_filename, src_filename, dst_filename
end


def check_global_vars
  if $debug_mode
    debug_msg 'Checking global variables' + "\n"
  end
  
  ######## CHECK GLOBAL VARIABLES ########
  improper_global_vars = false
  
  if not defined? $def_param_filename
    error_msg 'Global variable "$def_param_filename" not defined.', 'It must be a String with at least one alphabet.'
    improper_global_vars = true
  elsif not (($def_param_filename.is_a? String) and ($def_param_filename.length > 0)) # '.'
    error_msg 'Global variable "$def_param_filename" must be a String with at least one char.', $def_param_filename.inspect
    improper_global_vars = true
  end
  if not defined? $def_dst_filename
    error_msg 'Global variable "$def_dst_filename" not defined.', 'It must be a String with at least one alphabet.'
    improper_global_vars = true
  elsif not (($def_dst_filename.is_a? String) and ($def_dst_filename.length > 0)) # '.'
    error_msg 'Global variable "$def_dst_filename" must be a String with at least one char.', $def_dst_filename.inspect
    improper_global_vars = true
  end
  if not defined? $def_pc_incr
    error_msg 'Global variable "$def_pc_incr" not defined.', 'It must be an Integer.'
    improper_global_vars = true
  elsif not $def_pc_incr.is_a? Integer
    error_msg 'Gloabl variable "$def_pc_incr" must be an Integer.', $def_pc_incr.inspect
    improper_global_vars = true
  end
  if not defined? $def_line_bit_num
    error_msg 'Global variable "$def_line_bit_num" not defined.', 'It must be an Integer greater than 0.'
    improper_global_vars = true
  elsif not (($def_line_bit_num.is_a? Integer) and ($def_line_bit_num > 0))
    error_msg 'Global variable "$def_line_bit_num" must be an Integer greater than 0.', $def_line_bit_num.inspect
    improper_global_vars = true
  end
  if not defined? $def_head_filename
    error_msg 'Global variable "$def_head_filename" not defined.', 'It must be nil or a String with at least one alphabet.'
    improper_global_vars = true
  elsif not (($def_head_filename == nil) or (($def_head_filename.is_a? String) and ($def_head_filename.length > 0))) # '.'
    error_msg 'Global variable "$def_head_filename" must be nil or a String.', $def_head_filename.inspect
    improper_global_vars = true
  end
  if not defined? $def_tail_filename
    error_msg 'Global variable "$def_tail_filename" not defined.', 'It must be nil or a String with at least one alphabet.'
    improper_global_vars = true
  elsif not (($def_tail_filename == nil) or (($def_tail_filename.is_a? String) and ($def_tail_filename.length > 0))) # '.'
    error_msg 'Global variable "$def_tail_filname" must be nil or a String.', $def_tail_filename.inspect
    improper_global_vars = true
  end
  if not defined? $def_with_src
    error_msg 'Global variable "$def_with_src" not defined.', 'It must be true or false.'
    improper_global_vars = true
  elsif not (($def_with_src == true) or ($def_with_src == false))
    error_msg 'Global variable "$def_with_src" must be true or false.', $def_with_src.inspect
    improper_global_vars = true
  end
  if not defined? $def_bit_num
    error_msg 'Global variable "$def_bit_num" not defined.', 'It must be an Integer greater than 2.'
    improper_global_vars = true
  elsif not (($def_bit_num.is_a? Integer) and ($def_bit_num > 2))
    error_msg 'Global variable "$def_bit_num" must be an Integer greater than 0.', $def_bit_num.inspect
    improper_global_vars = true
  end
  if not defined? $def_signed
    error_msg 'Global variable "$def_signed" not defined.', 'It must be "signed" or "unsigned".'
    improper_global_vars = true
  elsif not (($def_signed == 'signed') or ($def_signed == 'unsigned'))
    error_msg 'Global variable "$def_signed" must be "signed" or "unsigned".', $def_signed.inspect
    improper_global_vars = true
  end
  if not defined? $debug_mode
    error_msg 'Global variable "$debug_mode" not defined.', 'It must be true or false.'
    improper_global_vars = true
  elsif not (($debug_mode == true) or ($debug_mode == false))
    error_msg 'Global variable "$debug_mode" must be true or false.', $debug_mode.inspect
    improper_global_vars = true
  end
  if not defined? $comment_mark
    error_msg 'Global variable "$comment_mark" not defined.', 'It must be a String with at least one char.'
    improper_global_vars = true
  elsif not (($comment_mark.is_a? String) and ($comment_mark.length > 0))
    error_msg 'Global variable "$comment_mark" msut be a String with at least one char.', $comment_mark.inspect
    improper_global_vars = true
  end
  if not defined? $verilog_comment_mark
    error_msg 'Global variable "$verilog_comment_mark" not defined.', 'It must be a String with at least one char.'
    improper_global_vars = true
  elsif not (($verilog_comment_mark.is_a? String) and ($verilog_comment_mark.length > 0))
    error_msg 'Global variable "$verilog_comment_mark" must be a String with at least one char.', $verilog_comment_mark.inspect
    improper_global_vars = true
  end
  if not defined? $dont_care_char
    error_msg 'Global variable "$dont_care_char" not defined.', 'It must be 0 or 1.'
    improper_global_vars = true
  elsif not (($dont_care_char.is_a? String) and ($dont_care_char =~ /^[01]$/))
    error_msg 'Global variable "$dont_care_char" must be 0 or 1.', $dont_care_char.inspect
    improper_global_vars = true
  end
  
  if improper_global_vars
    exit
  end
  if $debug_mode
    debug_msg 'Checking global variables... done' + "\n"
  end
end


def check_arch_params
  info_msg 'Checking parameters for arch ' + $arch.name.inspect + "\n"
  
  ################
  improper_arch_params = false
  
  if not defined? $arch
    error_msg 'Global variable "$arch" not defined.', 'It must be an Arch.'
    improper_arch_params = true
  elsif not ($arch.is_a? Arch)
    error_msg 'Global variable "$arch" msut be an Arch.', $arch.inspect
    improper_arch_params = true
  end
  if not defined? $arch.name
    error_msg '$arch.name not defined.', 'It must be a String.'
    improper_arch_params = true
  elsif not (($arch.name.is_a? String) and ($arch.name.length > 0))
    error_msg '$arch.name must be a String with at least one char.', $arch.name.inspect
    improper_arch_params = true
  end
  if not defined? $arch.pc_incr
    error_msg '$arch.pc_incr not defined.', 'It must be a Integer.'
    improper_arch_params = true
  elsif not ($arch.pc_incr.is_a? Integer)
    error_msg '$arch.pc_incr must be an Integer.', $arch.name.inspect
    improper_arch_params = true
  end
  if not defined? $arch.var
    error_msg '$arch.var not defined.', 'It must be a Hash.'
    improper_arch_params = true
  elsif not ($arch.var.is_a? Hash)
    error_msg '$arch.var must be a Hash', $arch.name.inspect
    improper_arch_params = true
  end
  if not defined? $arch.op
    error_msg '$arch.op not defined.', 'It must be a Hash.'
    improper_arch_params = true
  elsif not ($arch.op.is_a? Hash)
    error_msg '$arch.op msut be a Hash', $arch.op.inspect
    improper_arch_params = true
  end
  if not defined? $arch.line_bit_num
    error_msg '$arch.line_bit_num not defined.', 'It must be an Integer greater than 0.'
    improper_arch_params = true
  elsif not (($arch.line_bit_num.is_a? Integer) and ($arch.line_bit_num > 0))
    error_msg '$arch.line_bit_num must be an Integer greater than 0.', $arch.name.inspect
    improper_arch_params = true
  end
  
  if improper_arch_params
    exit
  end
  
  ################
  
  $arch.var.each_key do |var_name|
    if not ((var_name.is_a? String) and (var_name.length > 0))
      error_msg 'All the ' + $arch.name.inspect + '.var name must be a String with at least one char.'
      improper_arch_params = true
      break
    end
  end
  
  $arch.var.each_value do |format|
    if not (format.is_a? Array)
      error_msg $arch.name.inspect + '.var must be an Array.'
      improper_arch_params = true
      break
    end
    if not ((format[BIT_NUM].is_a? Integer) and (format[BIT_NUM] > 0))
      error_msg $arch.name.inspect + '.var[BIT_NUM(==0)] must be an Integer greater than 0.'
      improper_arch_params = true
      break
    end
    if not (format[SIGN].is_a? String)
      error_msg $arch.name.inspect + '.var[SIGN(==1)] must be \'signed\', \'unsigned\', \'signed/label\' or \'unsigned/label\'.'
      improper_arch_params = true
      break
    else
      case format[SIGN]
      when 'signed', 'unsigned', 'signed/label', 'unsigned/label'
      else
        error_msg $arch.name.inspect + '.var[SIGN(==1)] must be \'signed\', \'unsigned\', \'signed/label\' or \'unsigned/label\'.'
        improper_arch_params = true
        break
      end
    end
    if not ((format[BRANCH].is_a? String) or (format[BRANCH] == nil))
      error_msg $arch.name.inspect + '.var[BRANCH(==2)] msut be \'absolute\' or \'relative\'.'
      improper_arch_params = true
      break
    else
      case format[BRANCH]
      when 'absolute', 'relative', nil
      else
        error_msg $arch.name.inspect + '.var[BRANCH] must be \'absolute\' or \'relative\' or nil, but not ' + format[BRANCH].to_s + '.'
        improper_arch_params = true
      end
    end
  end
  
  $arch.op.each_key do |var_name|
    if not ((var_name.is_a? String) and (var_name.length > 0))
      error_msg $arch.name.inspect + '.op name must be a String with at least one char.'
      improper_arch_params = true
      break
    end
  end
  $arch.op.each_value do |format|
    if not (format.is_a? Array)
      error_msg $arch.name.inspect + '.op msut be an Array.'
      improper_arch_params = true
      break
    end
    if not (format[ASM_FORMAT].is_a? String)
      error_msg $arch.name.inspect + '.op[ASM_FORMAT(==0)] must be a String.'
      improper_arch_params = true
      break
    end
    if not (format[BIN_FORMAT].is_a? String)
      error_msg $arch.name.inspect + '.op[BIN_FORMAT(==1)] must be a String.'
      improper_arch_params = true
      break
    end
  end
  
  if improper_arch_params
    exit
  end
  
  ################
  
  $arch.op.each do |op_name, format|
    asm_format = format[ASM_FORMAT].dup
    bin_format = format[BIN_FORMAT].dup
    while asm_format =~ /\$\{(#{$var_name_regstr})\}/
      var_name = $1
      asm_format.sub!(/\$\{(#{var_name})\}/, '')
      if not $arch.var.has_key? var_name
        error_msg 'Undefined variable "' + var_name + '" found in ' + $arch.name.inspect + '.op["' + op_name + '"][ASM_FORMAT(==0)].'
        improper_arch_params = true
      end
    end
    while bin_format =~ /\$\{(#{$var_name_regstr})\}/
      var_name = $1
      bin_format.sub!(/\$\{(#{var_name})\}/, '')
      if not $arch.var.has_key? var_name
        error_msg 'Undefined variable "' + var_name + '" found in ' + $arch.name.inspect + '.op["' + op_name + '"][BIN_FORMAT(==1)].'
        improper_arch_params = true
      end
    end
  end
  
  if improper_arch_params
    exit
  end
  
  ################
  
  $arch.op.each_value do |format|
    asm_format = format[ASM_FORMAT].dup
    bin_format = format[BIN_FORMAT].dup
    asm_vars = Array.new
    bin_vars = Array.new
    while asm_format =~ /\$\{(#{$var_name_regstr})\}/
      var_name = $1
      asm_vars << var_name
      asm_format.sub!(/\$\{(#{var_name})\}/, '')
    end
    while bin_format =~ /\$\{(#{$var_name_regstr})\}/
      var_name = $1
      bin_vars << var_name
      bin_format.sub!(/\$\{(#{var_name})\}/, '')
    end
    if asm_vars.size != asm_vars.uniq.size
      error_msg 'A variable cannot be used more than once in each ' + $arch.name.inspect + '.op[ASM_FORMAT(==0)].'
      improper_arch_params = true
    end
    if bin_vars.size != bin_vars.uniq.size
      error_msg 'A variable cannot be used more than once in each ' + $arch.name.inspect + '.op[BIN_FORMAAT(==1)].'
      improper_arch_params = true
    end
    asm_vars.sort!
    bin_vars.sort!
    if not asm_vars == bin_vars
      error_msg 'Mismatch between variables in ' + $arch.name.inspect + '.op[ASM_FORMAT(==0)] and ' + $arch.name.inspect + '.op[BIN_FORMAT(==1)].'
      improper_arch_params = true
    end
  end
  
  
  # check bit_num
  line_bit_nums = Array.new
  $arch.op.each_value do |format|
    bin_format = format[BIN_FORMAT].dup
    while bin_format =~ /\$\{(#{$var_name_regstr})\}/
      var_name = $1
      bin_format.sub!(/\$\{(#{var_name})\}/, '0' * $arch.var[var_name][BIT_NUM])
    end
    bin_format.gsub!('_', '')
    bin_format.gsub!(/x/, '0')
    line_bit_nums << bin_format.length
  end
  line_bit_nums.uniq!
  if line_bit_nums.size > 1
    error_msg 'Bit nums of ' + $arch.name.inspect + '.op[BIN_FORMAT(==1)] differs.'
    improper_arch_params = true
  end
  
  $arch.op.each do |op, format|
    asm_format = format[ASM_FORMAT].dup
    asm_format.gsub!(/\$\{(#{$var_name_regstr})\}/, '')
    asm_format.gsub!('\w', '')
    asm_format.gsub!('\s', '')
    asm_format.gsub!('\d', '')
    asm_format.gsub!('\W', '')
    asm_format.gsub!('\S', '')
    asm_format.gsub!('\D', '')
    asm_format.gsub!('\\\\', '')
    asm_format.gsub!('\(', '')
    asm_format.gsub!('\)', '')
    asm_format.gsub!('\[', '')
    asm_format.gsub!('\]', '')
    asm_format.gsub!('\{', '')
    asm_format.gsub!('\}', '')
    
    if asm_format.include?('(') or asm_format.include?(')') or
        asm_format.include?('[') or asm_format.include?(']') or
        asm_format.include?('{') or asm_format.include?('}') or 
        asm_format.include?('^') or asm_format.include?('$') or 
        asm_format =~ /^\^/      or asm_format =~ /\$$/
      error_msg $arch.name.inspect + '.op["' + op + '"][ASM_FORMAT(==0)] has regexp special mark(s) without backslash.'
      improper_arch_params = true
    end
  end
  
  if improper_arch_params
    exit
  end
  info_msg 'Checking parameters for arch ' + $arch.name.inspect + "... done\n"
end


PC      = 0
LINE_NO = 1


class SrcFile
  ######## INITIALIZE ########
  def initialize(src_filename)
    @name   = src_filename
    @lines  = Array.new
    @labels = Hash.new
    if not is_readable(@name)
      error_msg 'Source file "' + @name + '" not found or unreadable'
      abort
    end
  end
  
  
  ######## READ SOURCE ########
  def read_src
    set_new_lines
    analyze
    set_pc
    register_labels
    debug_output_label_list
    replace_labels
    debug_output_lines
  end
  
  
  def set_new_lines
    if $debug_mode
      debug_msg 'Reading src file "' + @name + '"' + "\n"
    end
    
    line_no = 1
    open(@name, 'r') do |src|
      src.each do |line|
        @lines << SrcLine.new(line_no, line.chomp)
        line_no += 1
      end
    end
  end
  
  
  def analyze
    if $debug_mode
      debug_msg 'Analyzing each line' + "\n"
    end
    
    @lines.each do |line|
      line.analyze
    end
  end
  
  
  def set_pc
    if $debug_mode
      debug_msg 'Setting program counter for each opecode' + "\n"
    end
    
    pc = 0
    @lines.each do |line|
      if not line.op.is_a? Info
        line.pc = pc
        pc += $arch.pc_incr
      end
    end
  end
  
  
  def register_labels
    if $debug_mode
      debug_msg 'Registering labels in label list' + "\n"
    end
    
    dup_labels = Array.new
    @lines.each do |line|
      if line.label == nil
        next
      end
      if line.op == nil
        warn_msg 'Label at line ' + line.no.to_s + ' was ignored, since no opecode found in the line'
        next
      end
      if @labels.has_key? line.label
        dup_labels << line.label
      end
      @labels[line.label] = [line.pc, line.no]
    end
    
    if dup_labels.empty?
      return
    end
    
    dup_labels.uniq!
    warning_msg 'Duplicated label(s) found:'
    dup_labels.each do |label_name|
      empty_msg2 '  "' + label_name + '" refers pc ' + @labels[label_name][PC].to_s
    end
  end
  
  
  def replace_labels
    if $debug_mode
      debug_msg 'Replacing label(s) with program counter(s)' + "\n"
    end
    
    unknown_label_info = Array.new
    @lines.each do |line|
      line.replace_labels(@labels, unknown_label_info)
    end
    if unknown_label_info.size > 0
      error_msg 'Unknown label(s) found: ', '  ' + unknown_label_info.join(', ')
    end
  end
  
  
  ######## ASSEMBLE ########
  def assemble
    info_msg 'Assembling...' + "\n"
    
    @lines.each do |line|
      line.assemble
    end
    debug_output_bin
    
    info_msg 'Assembling... done' + "\n"
  end
  
  
  ######## OUTPUT ERRORS ########
  def output_errors(dst_filename)
    if $debug_mode
      debug_msg 'Outputting error(s)' + "\n"
    end
    
    error_lines = Array.new
    @lines.each do |line|
      if line.syntax_error
        error_lines << line.no
      end
    end
    
    if error_lines.size > 0
      error_msg  'Syntax error(s) found in following lines:', '  ' + error_lines.join(', ')
      empty_msg2 'See output file "' + dst_filename + '" and find "E" for the error(s)'
    end
  end
  
  
  ######## OUTPUT BIN ########
  def output_bin(dst_filename)
    info_msg 'Outputting result to "' + dst_filename + "\"\n"
    
    bin_length = 0
    pc_length  = 0
    @lines.each do |line|
      if bin_length < line.bin.length
        bin_length = line.bin.length
      end
      if pc_length < line.pc.to_s.length
        pc_length = line.pc.to_s.length
      end
    end
    
    output_str_ary = Array.new
    if $def_head_filename != nil
      output_str_ary << get_lines_of($def_head_filename)
      if output_str_ary.last == nil
        warning_msg 'Header file "' + $def_head_filename + '" unreadable and option "-h" was ignored.'
      end
    end
    if $def_with_src
      @lines.each do |line|
        if line.pc.is_a? Integer
          output_str_ary << line.bin.ljust(bin_length) + '  ' + $verilog_comment_mark + ' ' + line.pc.to_s.ljust(pc_length) + ' : ' + line.str
        else
          output_str_ary << line.bin.ljust(bin_length) + '  ' + $verilog_comment_mark + ' ' + '-'.ljust(pc_length)          + ' : ' + line.str
        end
      end
    else
      @lines.each do |line|
        output_str_ary << line.bin
      end
    end
    if $def_tail_filename != nil
      output_str_ary << get_lines_of($def_tail_filename)
      if output_str_ary.last == nil
        warning_msg 'Tail file "' + $def_tail_filename + '" unreadable and option "-t" was ignored.'
      end
    end
    output_str_ary.flatten!
    output_str_ary.delete(nil)
    
    open(dst_filename, 'w') do |dst|
      dst.print output_str_ary.join("\n") + "\n"
    end
    
    info_msg 'Outputting result to "' + dst_filename + '"... done' + "\n"
  end
  
  
  ######## FOR DEBUG ########
  def debug_output_lines
    if not $debug_mode
      return
    end
    
    line_no_length = 'no'     .length
    pc_length      = 'pc'     .length
    str_length     = 'str'    .length
    label_length   = 'label'  .length
    op_length      = 'op'     .length
    operand_length = 'operand'.length
    comment_length = 'comment'.length
    syntax_length  = 'syntax' .length
    
    @lines.each do |line|
      if line_no_length < line.no.inspect.length
        line_no_length = line.no.inspect.length
      end
      if pc_length < line.pc.inspect.length
        pc_length = line.pc.inspect.length
      end
      if str_length < line.str.inspect.length
        str_length = line.str.inspect.length
      end
      if label_length < line.label.inspect.length
        label_length = line.label.inspect.length
      end
      if op_length < line.op.inspect.length
        op_length = line.op.inspect.length
      end
      if operand_length < line.operand.inspect.length
        operand_length = line.operand.inspect.length
      end
      if comment_length < line.comment.inspect.length
        comment_length = line.comment.inspect.length
      end
    end
    hor_line = '-' * (line_no_length + pc_length + str_length + label_length + op_length + operand_length + comment_length + syntax_length + 14) + "\n"
    
    debug_msg '######## LINES ##################' + "\n"
    empty_msg1 hor_line
    empty_msg1 'no'.ljust(line_no_length) + '  ' + 'pc'.ljust(pc_length) + '  ' + 'str'.ljust(str_length) + '  ' + 'label'.ljust(label_length) + '  ' +
                    'op'.ljust(op_length) + '  ' + 'operand'.ljust(operand_length) + '  ' + 'comment'.ljust(comment_length) + '  ' + 'syntax' + "\n"
    empty_msg1 hor_line
    @lines.each do |line|
      output_str = ''
      output_str += line.no                          .inspect.ljust(line_no_length) + '  '
      output_str += line.pc                          .inspect.ljust(pc_length     ) + '  '
      output_str += line.str                         .inspect.ljust(str_length    ) + '  '
      output_str += line.label                       .inspect.ljust(label_length  ) + '  '
      output_str += line.op                          .inspect.ljust(op_length     ) + '  '
      output_str += line.operand                     .inspect.ljust(operand_length) + '  '
      output_str += line.comment                     .inspect.ljust(comment_length) + '  '
      output_str += ((line.syntax_error)? 'error':'').                              + "\n"
      empty_msg1 output_str
    end
    empty_msg1 hor_line + "\n"
  end
  
  
  def debug_output_label_list
    if not $debug_mode
      return
    end
    
    label_name_length = 'label_name'.length
    pc_length         = 'pc'.length
    line_no_length    = 'line_no'.length
    
    @labels.each do |label_name, label_attr|
      if label_name_length < label_name.length
        label_name_length = label_name.length
      end
      if pc_length < label_attr[PC].inspect.length
        pc_length = label_attr[PC].inspect.length
      end
      if line_no_length < label_attr[LINE_NO].inspect.length
        line_no_length = label_attr[LINE_NO].inspect.length
      end
    end
    hor_line = '-' * (label_name_length + pc_length + line_no_length + 4) + "\n"
    
    debug_msg '######## LABEL LIST #############' + "\n"
    empty_msg1 hor_line
    empty_msg1 'label_name'.ljust(label_name_length) + '  ' + 'pc'.ljust(pc_length) + '  ' + 'line_no' + "\n"
    empty_msg1 hor_line
    @labels.each do |label_name, label_attr|
      output_str = ''
      output_str += label_name                 .ljust(label_name_length) + '  '
      output_str += label_attr[PC]     .inspect.ljust(pc_length        ) + '  '
      output_str += label_attr[LINE_NO].inspect.ljust(line_no_length   ) + "\n"
      empty_msg1 output_str
    end
    empty_msg1 hor_line + "\n"
  end
  
  
  def debug_output_bin
    if not $debug_mode
      return
    end
    
    line_no_length = 'no'.length
    pc_length      = 'pc'.length
    str_length     = 'str'.length
    bin_length     = 'bin'.length
    
    @lines.each do |line|
      if line_no_length < line.no.inspect.length
        line_no_length = line.no.inspect.length
      end
      if pc_length < line.pc.inspect.length
        pc_length = line.pc.inspect.length
      end
      if str_length < line.str.inspect.length
        str_length = line.str.inspect.length
      end
      if bin_length < line.bin.inspect.length
        bin_length = line.bin.inspect.length
      end
    end
    hor_line = '-' * (line_no_length + pc_length + str_length + bin_length + 6) + "\n"
    
    debug_msg '######## ASSEMBLE RESULT ########' + "\n"
    empty_msg1 hor_line
    empty_msg1 'no'.ljust(line_no_length) + '  ' + 'pc'.ljust(pc_length) + '  ' + 'str'.ljust(str_length) + '  ' + 'bin' + "\n"
    empty_msg1 hor_line
    @lines.each do |line|
      output_str = ''
      output_str += line.no .inspect.ljust(line_no_length ) + '  '
      output_str += line.pc .inspect.ljust(pc_length      ) + '  '
      output_str += line.str.inspect.ljust(str_length     ) + '  '
      output_str += line.bin.inspect.                  + "\n"
      empty_msg1 output_str
    end
    empty_msg1 hor_line + "\n"
  end
end



class SrcLine
  attr_reader   :no, :str, :label, :comment, :op, :operand, :bin, :syntax_error
  attr_accessor :pc
  
  ######## INITIALIZE ########
  def initialize(no, str)
    @no      = no
    @pc      = nil
    @str     = str
    
    @label   = nil
    @comment = ''
    @op      = nil
    @operand = nil
    @format  = nil
    
    @bin     = ''
    @syntax_error = false
  end
  
  
  ######## READ SOURCE ########
  def analyze
    str = @str.dup
    
    str =~ $analyze_label_regexp
    @label = $1
    str.sub!($analyze_label_regexp, '')
    str.sub!(/^\s*/, '')
    
    str =~ $analyze_comment_regexp
    @comment = $1
    str.sub!($analyze_comment_regexp, '')
    str.sub!(/\s*$/, '')
    
    str =~ /^(\S*)/
    first_token = $1
    case first_token
    when ""
      @op = Info.new(EMPTY_LINE, PROPER)
      return
    when $op_regexp
      @op = first_token
      str.sub!($analyze_op_regexp, '')
      str.sub!(/^\s*/, '')
      @format = $arch.op[@op]
    else
      @op = Info.new(UNKNOWN_OPECODE, IMPROPER)
      @syntax_error = true
      return
    end
    if not $arch.op.has_key? @op
      @op = Info.new(UNKNOWN_OPECODE, IMPROPER)
      @syntax_error = true
      return
    end
    
    str =~ @format[REGEXP]
    if Regexp.last_match == nil
      @operand = Info.new(IMPROPER_FORMAT, IMPROPER)
      @syntax_error = true
    else
      @operand = Regexp.last_match.captures
    end
  end
  
  
  def replace_labels(label_list, unknown_label_info)
    if @syntax_error
      return
    end
    if ((@op.is_a? Info) and (@op.definition == EMPTY_LINE)) or (@operand.size == 0)
      return
    end
    
    @format[VAR_ORDER].each_index do |i|
      var_name = @format[VAR_ORDER][i]
      if ($arch.var[var_name][SIGN] == 'signed/label') or ($arch.var[var_name][SIGN] == 'unsigned/label')
        if @operand[i] !~ $branch_addr_regexp
          if not label_list.has_key? @operand[i]
            unknown_label_info << @operand[i] + '(line ' + @no.to_s + ')'
            @operand[i] = Info.new(UNKNOWN_LABEL, IMPROPER)
            @syntax_error = true
          else
            case $arch.var[var_name][BRANCH]
            when 'absolute'
              @operand[i] = $arch.absolute_branch_address(@pc, label_list[@operand[i]][PC]).to_s
            when 'relative'
              @operand[i] = $arch.relative_branch_address(@pc, label_list[@operand[i]][PC]).to_s
            else
              bug_msg 'Unknown branch attribute "' + $arch.var[var_name][BRANCH] + '"'
            end
          end
        end
      end
    end
  end
  
  
  ######## ASSEMBLE ########
  def assemble
    if (@op.is_a? Info) and @op.is_improper
      @bin = 'E' * $arch.line_bit_num
      return
    end
    
    if (@op.is_a? Info) and (@op.definition == EMPTY_LINE)
      @bin = ''
      return
    end
    
    bin_str = @format[BIN_FORMAT].dup
    if (@operand.is_a? Info) and (@operand.is_improper)
      while bin_str =~ /\$\{(#{$var_name_regstr})\}/
        var_name = $1
        bit_num  = $arch.var[var_name][BIT_NUM]
        bin_str.sub!(/\$\{(#{var_name})\}/, 'E' * bit_num)
      end
    else
      while bin_str =~ /\$\{(#{$var_name_regstr})\}/
        var_name = $1
        order    = @format[VAR_ORDER].index(var_name)
        sign     = $arch.var[var_name][SIGN]
        bit_num  = $arch.var[var_name][BIT_NUM]
        
        case sign
        when 'signed/label'
          sign = 'signed'
        when 'unsigned/label'
          sign = 'unsigned'
        end
        
        if (@operand[order].is_a? Info) and (@operand[order].is_improper)
          bin_str.sub!(/\$\{#{$var_name_regstr}\}/, 'E' * bit_num)
          next
        end
        
        val = to_bin(@operand[order], :signed => sign, :bit_num => bit_num, :err_msg => false,
                                       :warn_msg => false, :err_overflow => true, :err_neg_unsigned => true)
        case val
        when /^err_/
          val = 'E' * bit_num
          bin_str.sub!(/\$\{#{var_name}\}/, val)
          @syntax_error = true
        when $bin_regexp
          bin_str.sub!(/\$\{#{var_name}\}/, val)
        else
          bug_msg 'Unexpected value was returned from function "to_bin": ' + val.inspect
        end
      end
      
      bin_str.gsub!(/x/, $dont_care_char)
    end
    
    @bin = bin_str
  end
end


class BinaryFile
  ######## INITIALIZE ########
  def initialize(bin_filename)
    @name      = bin_filename
    @bin_lines = Array.new
    @bit_num   = 0
    if not is_readable(@name)
      error_msg 'Binary file "' + @name + '" not found or unreadable'
      abort
    end
  end
  
  
  ######## READ SOURCE ########
  def read_src
    set_new_bin_lines
    check_bin_line_length
    analyze
    set_pc
    debug_output_binaries
  end
  
  
  def set_new_bin_lines
    if $debug_mode
      debug_msg 'Reading bin file "' + @name + '"' + "\n"
    end
    
    line_no = 1
    open(@name, 'r') do |src|
      src.each do |bin_line|
        @bin_lines << BinLine.new(line_no, bin_line.chomp)
        line_no += 1
      end
    end
    
    @bin_lines.each do |bin_line|
      if bin_line.syntax_error
        exit
      end
    end
  end
  
  
  def check_bin_line_length
    if $debug_mode
      debug_msg 'Checking length of each binary line' + "\n"
    end
    
    bit_nums = Array.new
    @bin_lines.each do |bin_line|
      bit_nums << bin_line.length
    end
    bit_nums.uniq!
    bit_nums.delete_if do |bit_num|
      bit_num.is_a? Info
    end
    if bit_nums.size > 1
      error_msg 'Bit num of binary lines differs'
      exit
    end
    @bit_num = bit_nums.shift
  end
  
  
  def analyze
    if $debug_mode
      debug_msg 'Analyzing each line' + "\n"
    end
    
    @bin_lines.each do |bin_line|
      bin_line.analyze
    end
  end
  
  
  def set_pc
    if $debug_mode
      debug_msg 'Setting program counter for each binary line' + "\n"
    end
    
    pc = 0
    @bin_lines.each do |bin_line|
      if not bin_line.op.is_a? Info
        bin_line.pc = pc
        pc += $arch.pc_incr
      end
    end
  end
  
  
  ######## TO MNEMONIC ########
  def to_mnemonic
    info_msg 'Translating to mnemonic...' + "\n"
    
    @bin_lines.each do |bin_line|
      bin_line.to_mnemonic
    end
    debug_output_mnemo
  end
  
  
  ######## OUTPUT ERRORS ########
  def output_errors(dst_filename)
    if $debug_mode
      debug_msg 'Outputting error(s)' + "\n"
    end
    
    error_lines = Array.new
    @bin_lines.each do |bin_line|
      if bin_line.syntax_error
        error_lines << bin_line.no
      end
    end
    
    if error_lines.size > 0
      error_msg 'Binary error(s) found in following lines:', '  ' + error_lines.join(', ')
    end
  end
  
  
  ######## OUTPUT MNEMO ########
  def output_mnemo(dst_filename)
    info_msg 'Outputting result to "' + dst_filename + "\"\n"
    
    asm_length = 0
    pc_length  = 0
    @bin_lines.each do |bin_line|
      if asm_length < bin_line.asm.length
        asm_length = bin_line.asm.length
      end
      if pc_length < bin_line.pc.to_s.length
        pc_length = bin_line.pc.to_s.length
      end
    end
    
    output_str_ary = Array.new
    if $def_head_filename != nil
      output_str_ary << get_lines_of($def_head_filename)
      if output_str_ary.last == nil
        warning_msg 'Header file "' + $def_head_filename + '" unreadable and option "-h" was ignored.'
      end
    end
    if $def_with_src
      @bin_lines.each do |bin_line|
        if bin_line.pc.is_a? Integer
        output_str_ary << bin_line.asm.ljust(asm_length) + '  ' + $comment_mark + ' ' + bin_line.pc.to_s.ljust(pc_length) + ' : ' + bin_line.str
        else
          output_str_ary << bin_line.asm.ljust(asm_length) + '  ' + $comment_mark + ' ' + '-'.ljust(pc_length) + ' : ' + bin_line.str
        end
      end
    else
      @bin_lines.each do |bin_line|
        output_str_ary << bin_line
      end
    end
    if $def_tail_filename != nil
      output_str_ary << get_lines_of($def_tail_filename)
      if output_str_ary.last == nil
        warning_msg 'Tail file "' + $def_tail_filename + '" unreadable and option "-t" was ignored.'
      end
    end
    
    open(dst_filename, 'w') do |dst|
      dst.print output_str_ary.join("\n") + "\n"
    end
  end
  
  
  ######## FOR DEBUG ########
  def debug_output_binaries
    if not $debug_mode
      return
    end
    
    line_no_length = 'no'     .length
    str_length     = 'str'    .length
    bin_str_length = 'binary' .length
    bit_num_length = 'bit_num'.length
    op_length      = 'opecode'.length
    operand_length = 'operand'.length
    comment_length = 'comment'.length
    syntax_length  = 'syntax' .length
    
    @bin_lines.each do |bin_line|
      if line_no_length < bin_line.no.inspect.length
        line_no_length = bin_line.no.inspect.length
      end
      if str_length < bin_line.str.inspect.length
        str_length = bin_line.str.inspect.length
      end
      if bin_str_length < bin_line.bin_str.inspect.length
        bin_str_length = bin_line.bin_str.inspect.length
      end
      if bit_num_length < bin_line.length.inspect.length
        bit_num_length = bin_line.length.inspect.length
      end
      if op_length < bin_line.op.inspect.length
        op_length = bin_line.op.inspect.length
      end
      if operand_length < bin_line.operand.inspect.length
        operand_length = bin_line.operand.inspect.length
      end
      if comment_length < bin_line.comment.inspect.length
        comment_length = bin_line.comment.inspect.length
      end
    end
    hor_line = '-' * (line_no_length + str_length + bin_str_length + bit_num_length + op_length + operand_length + comment_length + syntax_length + 14) + "\n"
    
    debug_msg '######## BINARY LINES ###########' + "\n"
    empty_msg1 hor_line
    empty_msg1 'no'.ljust(line_no_length) + '  ' + 'str'.ljust(str_length) + '  ' + 'binary'.ljust(bin_str_length) + '  ' + 'bit_num'.ljust(bit_num_length) + '  ' +
                    'opecode'.ljust(op_length) + '  ' + 'operand'.ljust(operand_length) + '  ' + 'comment'.ljust(comment_length) + '  ' + 'syntax' + "\n"
    empty_msg1 hor_line
    @bin_lines.each do |bin_line|
      output_str = ''
      output_str += bin_line.no                          .inspect.ljust(line_no_length) + '  '
      output_str += bin_line.str                         .inspect.ljust(str_length    ) + '  '
      output_str += bin_line.bin_str                     .inspect.ljust(bin_str_length) + '  '
      output_str += bin_line.length                      .inspect.ljust(bit_num_length) + '  '
      output_str += bin_line.op                          .inspect.ljust(op_length     ) + '  '
      output_str += bin_line.operand                     .inspect.ljust(operand_length) + '  '
      output_str += bin_line.comment                     .inspect.ljust(comment_length) + '  '
      output_str += ((bin_line.syntax_error)? 'error':'').                              + "\n"
      empty_msg1 output_str
    end
    empty_msg1 hor_line + "\n"
  end
  
  
  def debug_output_mnemo
    if not $debug_mode
      return
    end
    
    line_no_length = 'no' .length
    asm_length     = 'asm'.length
    @bin_lines.each do |bin_line|
      if line_no_length < bin_line.no.inspect.length
        line_no_length = bin_line.no.inspect.length
      end
      if asm_length < bin_line.asm.inspect.length
        asm_length = bin_line.asm.inspect.length
      end
    end
    hor_line = '-' * (line_no_length + asm_length + 2) + "\n"
    
    debug_msg '######## TO MNEMONIC RESULT #####' + "\n"
    empty_msg1 hor_line
    empty_msg1 'no'.ljust(line_no_length) + '  ' + 'asm' + "\n"
    empty_msg1 hor_line
    @bin_lines.each do |bin_line|
      output_str = ''
      output_str += bin_line.no .inspect.ljust(line_no_length) + '  '
      output_str += bin_line.asm.inspect.ljust(asm_length    ) + "\n"
      empty_msg1 output_str
    end
    empty_msg1 hor_line + "\n"
  end
end


class BinLine
  attr_reader :no, :str, :bin_str, :length, :op, :operand, :comment, :syntax_error, :asm
  attr_accessor :pc
  
  ######## INITIALIZE ########
  def initialize(no, str)
    @no      = no
    @pc      = nil
    @str     = str
    @bin_str = nil
    @length  = 0
    @op      = nil
    @operand = nil
    @format  = nil
    @comment = ''
    @syntax_error = false
    @asm     = ''
    
    extract_bin_str
    count_length
  end
  
  
  def extract_bin_str
    str = @str.dup
    str =~ $analyze_comment_regexp
    @comment = $1
    str.sub!($analyze_comment_regexp, '')
    str.gsub!(/\s/, '')
    str.gsub!('_', '')
    @bin_str = str
  end
  
  def count_length
    case @bin_str
    when ''
      @length = Info.new(EMPTY_LINE, PROPER)
    when /^[01]+$/
      @length = @bin_str.length
    else
      error_msg 'Line ' + @no.to_s + ' is not a binary ("' + @str + '")'
      @syntax_error = true
    end
  end
  
  
  ######## READ BIN FILE ########
  def analyze
    if @syntax_error
      return
    end
    if @bin_str == ''
      @op = Info.new(EMPTY_LINE, PROPER)
      return
    end
    
    @op = nil
    $arch.op.each do |op, format|
      if @bin_str =~ format[BIN_REGEXP]
        @op     = op
        @format = format
      end
    end
    if @op == nil
      @op = Info.new(IMPROPER_FORMAT, IMPROPER)
      @syntax_error = true
      return
    end
    
    @bin_str =~ @format[BIN_REGEXP]
    if Regexp.last_match == nil
      @operand = Info.new(IMPROPER_FORMAT, IMPROPER)
      @syntax_error = true
      return
    else
      @operand = Regexp.last_match.captures
    end
    
    @operand.each_index do |i|
      @operand[i] = '0b' + @operand[i]
    end
  end
  
  
  ######## TO MNEMONIC ########
  def to_mnemonic
    if (@op.is_a? Info) and @op.is_improper
      @asm = 'ERROR!!'
      return
    end
    
    if (@op.is_a? Info) and (@op.definition == EMPTY_LINE)
      @asm = ''
      return
    end
    
    asm_str = @format[ASM_FORMAT].dup
    while asm_str =~ /\$\{(#{$var_name_regstr})\}/
      var_name = $1
      order    = @format[BIN_VAR_ORDER].index(var_name)
      sign     = $arch.var[var_name][SIGN]
      bit_num  = $arch.var[var_name][BIT_NUM]
      
      case sign
      when 'signed/label'
        sign = 'signed'
      when 'unsigned/label'
        sign = 'unsigned'
      end
      
      val = to_dec(@operand[order], :signed => sign, :bit_num => bit_num, :err_msg => false,
                   :warn_msg => false, :err_overflow => true, :err_neg_unsigned => true)
      case val
      when /^err_/
        val = 'ERROR!!'
        asm_str.sub!(/\$\{#{var_name}\}/, val)
        @syntax_error = true
      when /^[+\-]?\d+$/
        asm_str.sub!(/\$\{#{var_name}\}/, val)
      else
        bug_msg 'Unexpected value was returned from function "to_dec": ' + val.inspect
      end
    end
    
    @asm = @op + ' ' + asm_str
  end
end


class Arch
  attr_reader :name, :var, :op, :pc_incr, :line_bit_num
  
  def initialize(name)
    @name         = name
    @var          = nil
    @op           = nil
    @pc_incr      = $def_pc_incr
    @line_bit_num = $def_line_bit_num
  end
  
  
  def absolute_branch_address(cur_pc, dst_pc)
    return dst_pc
  end
  
  
  def relative_branch_address(cur_pc, dst_pc)
    return (dst_pc - cur_pc - 1) * @pc_incr
  end
  
  
  def set_attr
    if $debug_mode
      debug_msg 'Analyzing parameters for "' + @name + '"' + "\n"
    end
    
    set_line_bit_num
    set_format
    check_bin_regstr_duplication
    debug_output_var
    debug_output_asm_op
    debug_output_mnemo_op
  end
  
  
  def set_line_bit_num
    @op.each do |op, format|
      bin_format = format[BIN_FORMAT].dup
      while bin_format =~ /\$\{(#{$var_name_regstr})\}/
        var_name = $1
        bin_format.sub!(/\$\{(#{var_name})\}/, '0' * $arch.var[var_name][BIT_NUM])
      end
      bin_format.gsub!(/_/, '')
      bin_format.gsub!(/x/, '0')
      @line_bit_num = bin_format.length
      break
    end
  end
  
  
  def set_format
    @op.each do |op, format|
      format[VAR_ORDER] = Array.new
      regstr = format[ASM_FORMAT].dup
      while regstr =~ /\$\{(#{$var_name_regstr})\}/
        var_name = $1
        sign = @var[var_name][SIGN]
        case sign
        when 'signed'
          regstr.sub!(/\$\{#{var_name}\}/, '([+\-]?\d+)')
        when 'unsigned'
          regstr.sub!(/\$\{#{var_name}\}/, '(\d+)')
        when 'signed/label'
          regstr.sub!(/\$\{#{var_name}\}/, '((?:[+\-]?\d+)|(?:' + $label_regstr + '))')
        when 'unsigned/label'
          regstr.sub!(/\$\{#{var_name}\}/, '((?:\d+)|(?:' + $label_regstr + '))')
        end
        format[VAR_ORDER] << var_name
      end
      format[REGSTR] = regstr
      format[REGEXP] = Regexp.new(regstr)
      
      format[BIN_VAR_ORDER] = Array.new
      bin_regstr = format[BIN_FORMAT].dup
      while bin_regstr =~ /\$\{(#{$var_name_regstr})\}/
        var_name = $1
        bit_num = @var[var_name][BIT_NUM]
        bin_regstr.sub!(/\$\{#{$var_name_regstr}\}/, '(' + '[01]' * bit_num + ')')
        format[BIN_VAR_ORDER] << var_name
      end
      bin_regstr.gsub!('x', '[01]')
      bin_regstr.gsub!('_', '')
      format[BIN_REGSTR] = bin_regstr
      format[BIN_REGEXP] = Regexp.new(bin_regstr)
    end
    
    @op.default = Info.new(DEF_FORMAT, PROPER)
  end
  
  
  def check_bin_regstr_duplication
    duplicated_regstr = false
    @op.each do |op_base, format_base|
      @op.each do |op_comp, format_comp|
        if op_base == op_comp
          next
        end
        if format_base[BIN_REGSTR] == format_comp[BIN_REGSTR]
          error_msg 'All of the ' + @name + '.op[BIN_FORMAT(==1)] must be unique, but "' + op_base + '" and "' + op_comp + '" are the same'
          duplicated_regstr = true
        end
      end
    end
    
    if duplicated_regstr
      exit
    end
  end
  
  
  ######## ANALYZE ########
  def is_opecode(op)
    return @op.include?(op)
  end
  
  
  ######## MODIFY ASM_FORMAT ########
  def fix_asm_format
    @op.each_key do |op_name|
      @op[op_name][ASM_FORMAT].gsub!('\s*,',  ',' )
      @op[op_name][ASM_FORMAT].gsub!('\(\s*', '\(')
      @op[op_name][ASM_FORMAT].gsub!('\s*\)', '\)')
      @op[op_name][ASM_FORMAT].gsub!('\[\s*', '\[')
      @op[op_name][ASM_FORMAT].gsub!('\s*\]', '\]')
      @op[op_name][ASM_FORMAT].gsub!('\{\s*', '\{')
      @op[op_name][ASM_FORMAT].gsub!('\s*\}', '\}')
      @op[op_name][ASM_FORMAT].gsub!('\s*',   ' ' )
      @op[op_name][ASM_FORMAT].gsub!('\s+',   ' ' )
      @op[op_name][ASM_FORMAT].gsub!('\(',    '(' )
      @op[op_name][ASM_FORMAT].gsub!('\)',    ')' )
      @op[op_name][ASM_FORMAT].gsub!('\{',    '{' )
      @op[op_name][ASM_FORMAT].gsub!('\}',    '}' )
      @op[op_name][ASM_FORMAT].gsub!('\[',    '[' )
      @op[op_name][ASM_FORMAT].gsub!('\]',    ']' )
    end
  end
  
  
  ######## FOR DEBUG ########
  def debug_output_var
    if not $debug_mode
      return
    end
    
    var_name_length = 'var_name'         .length
    bit_length      = 'bit'              .length
    sign_length     = 'unsigned or label'.length
    branch_length   = 'absolute'         .length
    @var.each do |var_name, format|
      if var_name_length < var_name.length
        var_name_length = var_name.length
      end
      if bit_length < format[BIT_NUM].inspect.length
        bit_length = format[BIT_NUM].inspect.length
      end
    end
    hor_line = '-' * (var_name_length + bit_length + sign_length + branch_length + 6) + "\n"
    
    debug_msg '######## ARCH VARS ##############' + "\n"
    empty_msg1 hor_line
    empty_msg1 'var_name'.ljust(var_name_length) + '  ' + 'bit'.ljust(bit_length) + '  ' + 'sign'.ljust(sign_length) + '  ' + 'branch' + "\n"
    empty_msg1 hor_line
    @var.each do |var_name, format|
      output_str = ''
      output_str += var_name.ljust(var_name_length)         + '  '
      output_str += format[BIT_NUM].inspect.ljust(bit_length) + '  '
      case format[SIGN]
      when 'signed', 'unsigned'
        output_str += format[SIGN].ljust(sign_length) + '  '
      when 'signed/label', 'unsigned/label'
        output_str += format[SIGN].sub('/', ' or ').ljust(sign_length) + '  '
      else
        bug_msg 'Unknown sign attribute "' + format[SIGN].inspect + '" was set for "' + var_name + '"'
      end
      case format[BRANCH]
      when 'absolute', 'relative'
        output_str += format[BRANCH]
      when nil
        output_str += '-'
      else
        bug_msg 'Unknown branch attribute "' + format[BRANCH].inspect + '" was set for "' + var_name + '"'
      end
      empty_msg1 output_str + "\n"
    end
    empty_msg1 hor_line + "\n"
  end
  
  
  def debug_output_asm_op
    if not $debug_mode
      return
    end
    
    op_length            = 'op'        .length
    asm_format_length    = 'asm'       .length
    var_order_length     = 'var'       .length
    regexp_length        = 'regexp'    .length
    bin_format_length    = 'bin_output'.length
    
    @op.each do |op, format|
      if op_length < op.inspect.length
        op_length = op.inspect.length
      end
      if asm_format_length < format[ASM_FORMAT].inspect.length
        asm_format_length = format[ASM_FORMAT].inspect.length
      end
      if var_order_length < format[VAR_ORDER].inspect.length
        var_order_length = format[VAR_ORDER].inspect.length
      end
      if regexp_length < format[REGEXP].inspect.length
        regexp_length = format[REGEXP].inspect.length
      end
      if bin_format_length < format[BIN_FORMAT].inspect.length
        bin_format_length = format[BIN_FORMAT].inspect.length
      end
    end
    hor_line = '-' * (op_length + asm_format_length + regexp_length + var_order_length + bin_format_length + 8) + "\n"
    
    debug_msg '######## ARCH ASM OPECODE #######' + "\n"
    empty_msg1 hor_line
    empty_msg1 'op'.ljust(op_length) + '  ' + 'asm'.ljust(asm_format_length) + '  ' + 'var'.ljust(var_order_length) + '  ' +
                    'regexp'.ljust(regexp_length) + '  ' + 'bin_output' + "\n"
    empty_msg1 hor_line
    @op.each do |op, format|
      output_str = ''
      output_str += op                   .inspect.ljust(op_length        ) + '  '
      output_str += format[ASM_FORMAT   ].inspect.ljust(asm_format_length) + '  '
      output_str += format[VAR_ORDER    ].inspect.ljust(var_order_length ) + '  '
      output_str += format[REGEXP       ].inspect.ljust(regexp_length    ) + '  '
      output_str += format[BIN_FORMAT   ].inspect.ljust(bin_format_length) + "\n"
      empty_msg1 output_str
    end
    empty_msg1 hor_line + "\n"
  end
  
  
  def debug_output_mnemo_op
    if not $debug_mode
      return
    end
    
    op_length            = 'op'        .length
    bin_regexp_length    = 'bin_regexp'.length
    bin_format_length    = 'bin'       .length
    bin_var_order_length = 'bin_var'   .length
    asm_format_length    = 'asm'       .length
    
    @op.each do |op, format|
      if op_length < op.inspect.length
        op_length = op.inspect.length
      end
      if bin_regexp_length < format[BIN_REGEXP].inspect.length
        bin_regexp_length = format[BIN_REGEXP].inspect.length
      end

      if asm_format_length < format[ASM_FORMAT].inspect.length
        asm_format_length = format[ASM_FORMAT].inspect.length
      end
      if bin_format_length < format[BIN_FORMAT].inspect.length
        bin_format_length = format[BIN_FORMAT].inspect.length
      end
      if bin_var_order_length < format[BIN_VAR_ORDER].inspect.length
        bin_var_order_length = format[BIN_VAR_ORDER].inspect.length
      end
    end
    hor_line = '-' * (op_length + bin_regexp_length + asm_format_length + bin_format_length + bin_var_order_length + 8) + "\n"
    
    debug_msg '######## ARCH MNEMO OPECODE #####' + "\n"
    empty_msg1 hor_line
    empty_msg1 'op'.ljust(op_length) + '  ' + 'bin_regexp'.ljust(bin_regexp_length) + '  ' + 'asm'.ljust(asm_format_length) + '  ' + 
                    'bin'.ljust(bin_format_length) + '  ' + 'bin_var' + "\n"
    empty_msg1 hor_line
    @op.each do |op, format|
      output_str = ''
      output_str += op                   .inspect.ljust(op_length        ) + '  '
      output_str += format[BIN_REGEXP   ].inspect.ljust(bin_regexp_length) + '  '
      output_str += format[ASM_FORMAT   ].inspect.ljust(asm_format_length) + '  '
      output_str += format[BIN_FORMAT   ].inspect.ljust(bin_format_length) + '  '
      output_str += format[BIN_VAR_ORDER].inspect                          + "\n"
      empty_msg1 output_str
    end
    empty_msg1 hor_line + "\n"
  end
end


check_global_vars
execution_mode, param_filename, src_filename, dst_filename = read_cmdline

case execution_mode
when 'numerical'
  show_bin_oct_dec_hex(ARGV, $def_bit_num, $def_signed)
  exit
when 'ascii_to_num'
  if $opts['a']
    show_ascii2num(ARGV, :bin => true, :oct => true, :dec => true, :hex => true)
  else
    show_ascii2num(ARGV, :bin => $opts['a2b'], :oct => $opts['a2o'], :dec => $opts['a2d'], :hex => $opts['a2h'])
  end
  exit
end

load File.dirname($0) + '/' + param_filename
check_arch_params
$arch.set_attr

case execution_mode
when 'assemble'
  src = SrcFile.new(src_filename)
  src.read_src
  src.assemble
  src.output_errors(dst_filename)
  src.output_bin(dst_filename)
when 'to_mnemonic'
  $arch.fix_asm_format
  mnemo = BinaryFile.new(src_filename)
  mnemo.read_src
  mnemo.to_mnemonic
  mnemo.output_errors(dst_filename)
  mnemo.output_mnemo(dst_filename)
else
  bug_msg 'Unknown execution mode "' + execution_mode + '" was set'
end

info_msg 'Done' + "\n"
