#!/usr/local/bin/ruby
# Z80 アセンブラ

$KCODE = 'NONE'			# マルチバイト文字コード指定なし

# ラベルだけの行（:なし）でエラーが出る

require 'optparse'

REG_IHL = 6				# (HL)
REG_A = 7				# A

REG_HL = 2				# HL

# ラベルに使える文字
RE_1 = /[A-Za-z_\#@\$]/o			# 先頭に使える文字
RE_2 = /[A-Za-z_0-9\#@\$]/o		# ２文字目以降に使える文字
RE_LABEL = /#{RE_1}#{RE_2}*/o

# オペランドに使える文字
RE_OPERAND = /[\w_$()+\-*\/,\#@'! \t]+/o

# 8 bit register
Reg8 = {'B'=>0, 'C'=>1, 'D'=>2, 'E'=>3, 'H'=>4, 'L'=>5, '(HL)'=>6, 'A'=>7}

# 16 bit register
Reg16_SP = {'BC'=>0, 'DE'=>1, 'HL'=>2, 'SP'=>3}

# 16 bit register
Reg16_AF = {'BC'=>0, 'DE'=>1, 'HL'=>2, 'AF'=>3}

# 16bit register indirect
Reg16Indirect = {'(BC)'=>0, '(DE)'=>1, '(HL)'=>2}

# AF or AF'
RegAF = {'AF'=>0, "AF'"=>1}

# condition
Cond = {'NZ'=>0, 'Z'=>1, 'NC'=>2, 'C'=>3}

# condition2
Cond2 = {'NZ'=>0, 'Z'=>1, 'NC'=>2, 'C'=>3, 'PO'=>4, 'PE'=>5, 'P'=>6, 'M'=>7}

# index register
IndexReg = {'IX'=>0, 'IY'=>1}

# I or R
RegIR = {'I'=>0, 'R'=>1}

# this is label?
def label?(val)
	val.kind_of?(String)
end

# オペランドをカンマで分割
def divide_operand(operand)
	re = /[\w_$()+\-*\/'! \t]+/o
	if /(#{re})(?:,(#{re}))?/o =~ operand
		opr1 = $1 && $1.strip
		opr2 = $2 && $2.strip
		return opr1, opr2
	end
	return nil, nil
end

# 符号あり 8bit の範囲外？
def out_of_signed_char?(ofs)
	ofs < -128 || ofs > 127
end

# 数値を得る
def getnum(val)
	if val.kind_of?(String)
		# 文字列だった場合： LD A,'0' とか
		x = 0
		val.each_byte {|ch| x = (x << 8) + ch}
		val = x
	end
	val
end

###############################################################################

# 式の計算
# 再帰下降法

def to_bin(str)
	val = 0
	str.each_byte {|ch| val = (val << 1) + (ch - ?0)}
	val
end

class EvalExp
	#==== 式の評価
	#symbol_table ::	シンボルテーブル（ハッシュ）
	#str ::				評価する式の文字列
	# 式の値を返す
	def eval(symbol_table, str)
		@symbol_table = symbol_table
		@str = str

		get_token
		val = eval_plusminus
		return val, @str
	end

private

	#==== トークン切り出し
	def get_token
		@val = nil
		case @str.lstrip
		when /^(\d[0-9A-Fa-f]*)(H|h)/o			# 0ABCDH
			@token = :number
			@val = $1.hex
		when /^([01]+)(B|b)/o					# 1010B
			@token = :number
			@val = to_bin $1
		when /^0(?:x|X)([0-9A-Fa-f]+)/o			# 0x1234
			@token = :number
			@val = $1.hex
		when /^0(?:b|B)([01]+)/o				# 0b0101
			@token = :number
			@val = to_bin $1
		when /^(\d+)/o							# 1234
			@token = :number
			@val = $1.to_i
		when /^'(.*?)'/o						# '...'
			@token = :string
			@val = $1
		when /^\+/o								# +
			@token = :plus
		when /^-/o								# -
			@token = :minus
		when /^\*/o								# *
			@token = :mul
		when /^\//o								# /
			@token = :div
		when /^%/o								# %
			@token = :mod
		when /^(#{RE_LABEL})/o					# シンボル
			@token = :symbol
			@val = $1
		when /^$/o								# 終わり
			@token = :end
		else
			@token = :illegal
			return
		end

		@str = $'
	end

	#==== ＋－の評価
	def eval_plusminus
		val = eval_muldiv
		if val
			while @token == :plus || @token == :minus
				tok = @token
				get_token
				v2 = eval_muldiv
				break unless v2

				if tok == :plus
					val = getnum(val) + getnum(v2)
				else
					val = getnum(val) - getnum(v2)
				end
			end
		end
		val
	end

	#==== ＊／の評価
	def eval_muldiv
		val = eval_factor
		if val
			while @token == :mul || @token == :div
				tok = @token
				get_token
				v2 = eval_factor
				break unless v2

				case tok
				when :mul
					val = getnum(val) * getnum(v2)
				when :div
					val = getnum(val) / getnum(v2)
				when :mod
					val = getnum(val) % getnum(v2)
				end
			end
		end
		val
	end

	#==== 因子
	def eval_factor
		str = @str
		val = @val

		case @token
		when :number, :string
			get_token
			val
		when :symbol
			get_token
			if @symbol_table.has_key? val
				@symbol_table[val]
			else
				raise "undefined symbol '#{val}'"
			end
		when :plus, :minus
			tok = @token
			get_token

			val = eval_factor
			if val
				val = -val	if tok == :minus
			end
			val
		else
			raise "illegal exp '#{str}'"
		end
	end
end


###############################################################################

# １パス目ではまだ登場してないラベルを先に参照する場合があるので、
# その場合は適当な値を返す
# ２パス目では定義されてないラベルを参照した場合エラーを発生させる

#=== ラベルテーブル
class LabelTable < Hash
	def set_pass(pass)
		@pass = pass
	end

	def has_key?(key)
		r = super
		if !r
			if @pass == 1
				r = true
			end
		end
		r
	end
end


###############################################################################

#=== アセンブラ
class Z80As
	#==== 結果をダンプ
	def dump
		p @label_table
	end

	#==== ソースファイルをアセンブルする
	#戻り値：	false = ファイル読み込み失敗
	#			0 = アセンブル成功
	#			> 0 = エラーの個数
	def assemble(srcfn, opt)
		unless File.exist? srcfn
			$stdout.puts "#{srcfn}: File not found"
			return false
		end

		@label_table = LabelTable.new(0)
		@srcfn = srcfn
		@error_num = 0

		assemble_pass( 1 )
		if @error_num == 0
			@f_out = open(opt[:outfn], "wb")
			unless @f_out
				error "Can't open output file (#{opt[:outfn]})"
				return false
			end
			if opt[:listfn]
				@f_list = open(opt[:listfn], "w")
				unless @f_list
					error "Can't open lst file (#{opt[:listfn]})"
					return false
				end
			end

			assemble_pass( 2 )

			@f_out.close
			@f_list.close	if @f_list
		end

		return @error_num
	end

private

	#==== エラー
	def error(msg)
		$stdout.puts "#{@lineno}: #{msg}"
		@error_num += 1
	end

	#==== 文法エラー
	def syntax_error(line)
		error "syntax error: #{line}"
	end

	#==== ラベル定義
	def define_label(name, val)
		@label_table[name] = val
	end

	#==== ラベル参照
	def refer_label(name)
		@label_table[name]
	end

	#==== リスト用に出力
	def dump_list(adr, codes, line)
		if @f_list
			if adr
				@f_list.printf "%04X: ", adr
			else
				@f_list.print "    : "
			end

			if codes
				codes.each do |c|
					@f_list.printf "%02X", c
				end
			end
			(4 - (codes ? codes.size : 0)).times { @f_list.print "  " }
			@f_list.print "\t", line, "\n"
		end
	end

	#==== パスごとの処理
	def assemble_pass(pass)
		@pass = pass
		@label_table.set_pass(pass)

		f = open(@srcfn)
		if !f
			error "Can't open file"
		else
			@adr = 0
			@lineno = 0

			re_label = RE_LABEL
			re_opecode = /[A-Za-z]+/o
			re_operand = RE_OPERAND

			while line = f.gets
				@lineno += 1
				@label_table['$'] = @adr

				line.chomp!
				unless /^(?:(?:(#{re_label})(?::\s*|\s+)|\s+)(?:(#{re_opecode})(?:\s+(#{re_operand}))?)?)?(?:\s*;(.*))?$/o =~ line
					error "illegal line"
				else
					label = $1
					opecode = $2
					operand = $3 && $3.strip
					comment = $4

					codes = dispatch_opecode label, opecode, operand, line

					if pass == 2
						# コード出力
						@f_out.write codes.pack("C*")	if codes

						# リスト出力
						dump_list(@adr, codes, line)
					end

					if codes
						@adr += codes.size
					end
				end
			end

			if @f_list
				@f_list.print "\nTotalBytes: #{@adr}\n"
			end

			f.close
		end
	end

	#==== オペコードによって処理する
	def dispatch_opecode(label, opecode, operand, line)
		uopc = opecode && opecode.upcase
		if label
			if uopc == 'EQU'
				val = parse_immediate?(operand)
				define_label label, val
				return nil
			else
				define_label label, @adr
			end
		end
		return if !opecode

		op1 = @opecode1_table[uopc]
		if op1
			if !operand || operand == ''
				return op1
			else
				syntax_error line
			end
		else
			opfunctbl = @opecode_table[uopc]
			if opfunctbl
				res = dispatch_functbl opfunctbl, opecode, operand
				if res
					return res
				else
					syntax_error line
				end
			else
				syntax_error line
			end
		end
		nil
	end

	#==== オペコードテーブルによって分岐
	def dispatch_functbl(opfunctbl, opecode, operand)
		opfunctbl.each do |tbl|
			sopr1 = tbl[0]
			sopr2 = tbl[1]
			proc = tbl[2]

			arg = nil

			if !sopr2
				if !sopr1
					if !operand || operand == ''
						arg = []
					end
				elsif operand
					vopr1 = is_operand_type? sopr1, operand.upcase, operand
					if vopr1
						arg = [vopr1]
					end
				end
			else
				if operand && /([^,]+),(.*)/o =~ operand
					operand1 = $1.strip
					operand2 = $2.strip
					uoperand1 = operand1.upcase
					uoperand2 = operand2.upcase
					vopr1 = is_operand_type? sopr1, uoperand1, operand1
					if vopr1
						vopr2 = is_operand_type? sopr2, uoperand2, operand2
						if vopr2
							arg = [vopr1, vopr2]
						end
					end
				end
			end

			if arg
				tbl = {
					nil			=> '',
					:reg8		=> '_r',
					:regA		=> '_A',
					:reg16_SP	=> '_rr',
					:reg16_AF	=> '_rr',
					:ireg16		=> '_ireg16',
					:regAF		=> '_AF',
					:regSP		=> '_SP',
					:regDE		=> '_DE',
					:regHL		=> '_HL',
					:regIX		=> '_IX',
					:reg_iHL_	=> '_iHL',
					:reg_iIX_	=> '_iIX',
					:reg_iSP_	=> '_iSP',
					:reg_iC_	=> '_iC',
					:regIR		=> '_IR',
					:cond		=> '_c',
					:cond2		=> '_c',
					:immediate	=> '_n',
					:iadr		=> '_iadr',
					:any		=> '',
				}

				mfunc = opecode.upcase + tbl[sopr1] + tbl[sopr2]
p mfunc
				return proc.call(*arg)
			end
		end
		nil
	end


	#==== 直値かどうかの判定がゆるいので、テーブルのあとのほうにすること
	def parse_immediate?(str)
		if /^\(.*\)$/ =~ str		# 間接アドレッシングなので違う（注：(a*b)+(c/d) のような式を誤判定する、がとりあえず）
			nil
		else
			# すでにレジスタやレジスタ間接は処理されてるはず、なので直値
			begin
				val, s = @evalexp.eval @label_table, str
				if s != ''
					error "illegal exp '#{s}'"
				end
				val
			rescue => excep
				error excep
				[]
			end
		end
	end

	# インデックスレジスタ間接？
	def is_index_register_indirect?(str)
		if %r!^\(I(X|Y)\s*([+\-*/].*)?\)$!io =~ str
			xy = $1
			ofs = $2

			reg = xy == 'X' ? 0 : 1
			return [reg, 0]	if !ofs

			begin
				ofs, s = @evalexp.eval @label_table, ofs
				return [reg, ofs]
			rescue => excep
				error excep
				return []
			end
		else
			nil
		end	
	end


	#==== オペランドが指定の種類か？
	def is_operand_type?(type, str, orgstr)
		case type
		when :reg8				# ８ビットレジスタ？
			Reg8[str]
		when :regA				# ８ビットレジスタ？
			str == 'A'
		when :reg16_SP			# １６ビットレジスタ？
			Reg16_SP[str]
		when :reg16_AF			# １６ビットレジスタ？
			Reg16_AF[str]
		when :ireg16			# １６ビットレジスタ間接？
			Reg16Indirect[str]
		when :immediate			# 直値？
			val = getnum(parse_immediate?(orgstr))
			if val == []			# エラー（シンボル未定義など）
				val = 0
			end
			val
		when :regAF				# AF or AF'？
			RegAF[str]
		when :regHL				# HL？
			str == 'HL'
		when :cond				# condition?
			Cond[str]
		when :cond2				# condition2?
			Cond2[str]
		when :reg_iSP_			# (SP)?
			str == '(SP)'
		when :regDE				# DE?
			str == 'DE'
		when :reg_iHL_			# (HL)?
			str == '(HL)'
		when :regSP				# SP?
			str == 'SP'
		when :regIX				# IX or IY?
			IndexReg[str]
		when :reg_iIX_			# (IX+a) or (IY+a)?
			val = is_index_register_indirect? orgstr
			if val == []			# エラー（シンボル未定義など）
				val = 0
			end
			val
		when :reg_iC_			# (C)?
			str == '(C)'
		when :regIR				# I or R?
			RegIR[str]
		when :iadr				# (nn) アドレス間接？
			if /^\((.+)\)$/o =~ orgstr
				val = parse_immediate? $1
				if val == []			# エラー（シンボル未定義など）
					val = 0
				end
				val
			end
		when :any				# なんでも
			orgstr
		else
			nil
		end
	end

	#==== コンストラクタ
	def initialize
		#@note:
		#	:immediate は直値かどうかの判定がゆるいので、テーブルのあとのほうにすること
		#	:iadr も
		@opecode_table = {
			'LD' => [
				[:reg8,		:reg8,			Proc.new {|*arg| parseLD_r_r *arg}],
				[:regA,		:ireg16,		Proc.new {|*arg| parseLD_A_ireg16 *arg}],
				[:ireg16,	:regA,			Proc.new {|*arg| parseLD_ireg16_A *arg}],
				[:regSP,	:regHL,			Proc.new {|*arg| [0xf9]}],
				[:reg8,		:reg_iIX_,		Proc.new {|*arg| parseLD_r_iIX *arg}],
				[:reg_iIX_,	:reg8,			Proc.new {|*arg| parseLD_iIX_r *arg}],
				[:regSP,	:regIX,			Proc.new {|dst, src| [0xdd + src * 0x20, 0xf9]}],
				[:regIR,	:regA,			Proc.new {|*arg| parseLD_IR_A *arg}],
				[:regA,		:regIR,			Proc.new {|*arg| parseLD_A_IR *arg}],

				[:reg8,		:immediate,		Proc.new {|*arg| parseLD_r_n *arg}],
				[:reg16_SP,	:immediate,		Proc.new {|*arg| parseLD_rr_nn *arg}],
				[:regIX,	:immediate,		Proc.new {|*arg| parseLD_IX_nn *arg}],
				[:reg_iIX_,	:immediate,		Proc.new {|*arg| parseLD_iIX_n *arg}],

				[:regA,		:iadr,			Proc.new {|*arg| parseLD_A_iadr *arg}],
				[:regHL,	:iadr,			Proc.new {|*arg| parseLD_HL_iadr *arg}],
				[:regIX,	:iadr,			Proc.new {|*arg| parseLD_IX_iadr *arg}],
				[:reg16_SP,	:iadr,			Proc.new {|*arg| parseLD_rr_iadr *arg}],

				[:iadr,		:regA,			Proc.new {|*arg| parseLD_iadr_A *arg}],
				[:iadr,		:regHL,			Proc.new {|*arg| parseLD_iadr_HL *arg}],
				[:iadr,		:regIX,			Proc.new {|*arg| parseLD_iadr_IX *arg}],
				[:iadr,		:reg16_SP,		Proc.new {|*arg| parseLD_iadr_rr *arg}],
			],
			'ADD' => [
				[:regA,		:reg8,			Proc.new {|*arg| parseADD_A_r *arg}],
				[:regHL,	:reg16_SP,		Proc.new {|*arg| parseADD_HL_rr *arg}],
				[:regIX,	:reg16_SP,		Proc.new {|*arg| parseADD_IX_rr *arg}],
				[:regIX,	:regIX,			Proc.new {|*arg| parseADD_IX_IX *arg}],
				[:regA,		:reg_iIX_,		Proc.new {|*arg| parseADD_A_iIX *arg}],
				[:regA,		:immediate,		Proc.new {|*arg| parseADD_A_n *arg}],
			],
			'ADC' => [
				[:regA,		:reg8,			Proc.new {|*arg| parseADC_A_r *arg}],

				[:regA,		:reg_iIX_,		Proc.new {|*arg| parseADC_A_iIX *arg}],
				[:regHL,	:reg16_SP,		Proc.new {|*arg| parseADC_HL_rr *arg}],
				[:regA,		:immediate,		Proc.new {|*arg| parseADC_A_n *arg}],
			],
			'SUB' => [
				[:reg8,		nil,			Proc.new {|*arg| parseSUB_r *arg}],
				[:reg_iIX_,	nil,			Proc.new {|*arg| parseSUB_iIX *arg}],
				[:immediate,nil,			Proc.new {|*arg| parseSUB_n *arg}],
			],
			'SBC' => [
				[:regA,		:reg8,			Proc.new {|*arg| parseSBC_A_r *arg}],
				[:regA,		:reg_iIX_,		Proc.new {|*arg| parseSBC_A_iIX *arg}],
				[:regHL,	:reg16_SP,		Proc.new {|*arg| parseSBC_HL_rr *arg}],
				[:regA,		:immediate,		Proc.new {|*arg| parseSBC_A_n *arg}],
			],
			'AND' => [
				[:reg8,		nil,			Proc.new {|*arg| parseAND_r *arg}],
				[:reg_iIX_,	nil,			Proc.new {|*arg| parseAND_iIX *arg}],
				[:immediate,nil,			Proc.new {|*arg| parseAND_n *arg}],
			],
			'XOR' => [
				[:reg8,		nil,			Proc.new {|*arg| parseXOR_r *arg}],
				[:reg_iIX_,	nil,			Proc.new {|*arg| parseXOR_iIX *arg}],
				[:immediate,nil,			Proc.new {|*arg| parseXOR_n *arg}],
			],
			'OR' => [
				[:reg8,		nil,			Proc.new {|*arg| parseOR_r *arg}],
				[:reg_iIX_,	nil,			Proc.new {|*arg| parseOR_iIX *arg}],
				[:immediate,nil,			Proc.new {|*arg| parseOR_n *arg}],
			],
			'CP' => [
				[:reg8,		nil,			Proc.new {|*arg| parseCP_r *arg}],
				[:reg_iIX_,	nil,			Proc.new {|*arg| parseCP_iIX *arg}],
				[:immediate,nil,			Proc.new {|*arg| parseCP_n *arg}],
			],
			'INC' => [
				[:reg8,		nil,			Proc.new {|*arg| parseINC_r *arg}],
				[:reg16_SP,	nil,			Proc.new {|*arg| parseINC_rr *arg}],

				[:regIX,	nil,			Proc.new {|*arg| parseINC_IX *arg}],
				[:reg_iIX_,	nil,			Proc.new {|*arg| parseINC_iIX *arg}],
			],
			'DEC' => [
				[:reg8,		nil,			Proc.new {|*arg| parseDEC_r *arg}],
				[:reg16_SP,	nil,			Proc.new {|*arg| parseDEC_rr *arg}],

				[:regIX,	nil,			Proc.new {|*arg| parseDEC_IX *arg}],
				[:reg_iIX_,	nil,			Proc.new {|*arg| parseDEC_iIX *arg}],
			],
			'EX' => [
				[:regAF,	:regAF,			Proc.new {|*arg| [0x08]}],
				[:reg_iSP_,	:regHL,			Proc.new {|*arg| [0xe3]}],
				[:regDE,	:regHL,			Proc.new {|*arg| [0xeb]}],

				[:reg_iSP_,	:regIX,			Proc.new {|dst, src| [0xdd + src * 0x20, 0xe3]}],
			],
			'JR' => [
				[:cond,		:immediate,		Proc.new {|*arg| parseJR_n *arg}],
				[:immediate,nil,			Proc.new {|*arg| parseJR_n *arg}],
			],
			'JP' => [
				[:reg_iHL_,	nil,			Proc.new {|*arg| [0xe9]}],
				[:reg_iIX_,	nil,			Proc.new {|*arg| parseJP_iIX *arg}],
				[:cond2,	:immediate,		Proc.new {|*arg| parseJP_nn *arg}],
				[:immediate,nil,			Proc.new {|*arg| parseJP_nn *arg}],
			],
			'DJNZ' => [
				[:immediate,nil,			Proc.new {|*arg| parseDJNZ_n *arg}],
			],
			'PUSH' => [
				[:reg16_AF,	nil,			Proc.new {|*arg| parsePUSH_rr *arg}],
				[:regIX,	nil,			Proc.new {|src| [0xdd + src * 0x20, 0xe5]}],
			],
			'POP' => [
				[:reg16_AF,	nil,			Proc.new {|*arg| parsePOP_rr *arg}],
				[:regIX,	nil,			Proc.new {|dst| [0xdd + dst * 0x20, 0xe1]}],
			],
			'CALL' => [
				[:cond2,	:immediate,		Proc.new {|*arg| parseCALL_nn *arg}],
				[:immediate,nil,			Proc.new {|*arg| parseCALL_nn *arg}],
			],
			'RET' => [
				[:cond2,	nil,			Proc.new {|*arg| parseRET *arg}],
				[nil,		nil,			Proc.new {|*arg| parseRET *arg}],
			],
			'RST' => [
				[:immediate,nil,			Proc.new {|*arg| parseRST_n *arg}],
			],
			'OUT' => [
				[:reg_iC_,	:reg8,			Proc.new {|*arg| parseOUT_iC_r *arg}],
				[:iadr,		:regA,			Proc.new {|*arg| parseOUT_iadr_A *arg}],
			],
			'IN' => [
				[:reg8,		:reg_iC_,		Proc.new {|*arg| parseIN_r_iC *arg}],
				[:regA,		:iadr,			Proc.new {|*arg| parseIN_A_iadr *arg}],
			],

			# CB xx
			'RLC' => [
				[:reg8,		nil,			Proc.new {|dst| [0xcb, 0x00 + dst]}],
				[:reg_iIX_,	nil,			Proc.new {|dst| [0xdd + dst[0] * 0x20, 0xcb, dst[1], 0x06]}],
			],
			'RRC' => [
				[:reg8,		nil,			Proc.new {|dst| [0xcb, 0x08 + dst]}],
				[:reg_iIX_,	nil,			Proc.new {|dst| [0xdd + dst[0] * 0x20, 0xcb, dst[1], 0x0e]}],
			],
			'RL' => [
				[:reg8,		nil,			Proc.new {|dst| [0xcb, 0x10 + dst]}],
				[:reg_iIX_,	nil,			Proc.new {|dst| [0xdd + dst[0] * 0x20, 0xcb, dst[1], 0x16]}],
			],
			'RR' => [
				[:reg8,		nil,			Proc.new {|dst| [0xcb, 0x18 + dst]}],
				[:reg_iIX_,	nil,			Proc.new {|dst| [0xdd + dst[0] * 0x20, 0xcb, dst[1], 0x1e]}],
			],
			'SLA' => [
				[:reg8,		nil,			Proc.new {|dst| [0xcb, 0x20 + dst]}],
				[:reg_iIX_,	nil,			Proc.new {|dst| [0xdd + dst[0] * 0x20, 0xcb, dst[1], 0x26]}],
			],
			'SRA' => [
				[:reg8,		nil,			Proc.new {|dst| [0xcb, 0x28 + dst]}],
				[:reg_iIX_,	nil,			Proc.new {|dst| [0xdd + dst[0] * 0x20, 0xcb, dst[1], 0x2e]}],
			],
			'SRL' => [
				[:reg8,		nil,			Proc.new {|dst| [0xcb, 0x38 + dst]}],
				[:reg_iIX_,	nil,			Proc.new {|dst| [0xdd + dst[0] * 0x20, 0xcb, dst[1], 0x3e]}],
			],
			'BIT' => [
				[:immediate,:reg8,			Proc.new {|*arg| parseBIT_n_A *arg}],
				[:immediate,:reg_iIX_,		Proc.new {|*arg| parseBIT_n_iIX *arg}],
			],
			'RES' => [
				[:immediate,:reg8,			Proc.new {|*arg| parseRES_n_A *arg}],
				[:immediate,:reg_iIX_,		Proc.new {|*arg| parseRES_n_iIX *arg}],
			],
			'SET' => [
				[:immediate,:reg8,			Proc.new {|*arg| parseSET_n_A *arg}],
				[:immediate,:reg_iIX_,		Proc.new {|*arg| parseSET_n_iIX *arg}],
			],
			'IM' => [
				[:immediate,nil,			Proc.new {|*arg| parseIM_n *arg}],
			],

			'DB' => [
				[:any,		nil,			Proc.new {|*arg| parseDB *arg}],
			],
			'DW' => [
				[:any,		nil,			Proc.new {|*arg| parseDW *arg}],
			],
			'DS' => [
				[:any,		nil,			Proc.new {|*arg| parseDS *arg}],
			],
		}
		@opecode1_table = {
			'NOP' =>	[0x00],
			'RLCA' =>	[0x07],
			'RRCA' =>	[0x0f],
			'RLA' =>	[0x17],
			'RRA' =>	[0x1f],
			'DAA' =>	[0x27],
			'CPL' =>	[0x2f],
			'SCF' =>	[0x37],
			'CCF' =>	[0x3f],
			'HALT' =>	[0x76],
			'EXX' =>	[0xd9],
			'DI' =>		[0xf3],
			'EI' =>		[0xfb],

			'NEG' =>	[0xed, 0x44],
			'RETN' =>	[0xed, 0x45],
			'RETI' =>	[0xed, 0x4d],
			'RRD' =>	[0xed, 0x67],
			'RLD' =>	[0xed, 0x6f],
			'LDI' =>	[0xed, 0xa0],
			'CPI' =>	[0xed, 0xa1],
			'INI' =>	[0xed, 0xa2],
			'OUTI' =>	[0xed, 0xa3],
			'LDD' =>	[0xed, 0xa8],
			'CPD' =>	[0xed, 0xa9],
			'IND' =>	[0xed, 0xaa],
			'OUTD' =>	[0xed, 0xab],
			'LDIR' =>	[0xed, 0xb0],
			'CPIR' =>	[0xed, 0xb1],
			'INIR' =>	[0xed, 0xb2],
			'OTIR' =>	[0xed, 0xb3],
			'LDDR' =>	[0xed, 0xb8],
			'CPDR' =>	[0xed, 0xb9],
			'INDR' =>	[0xed, 0xba],
			'OTDR' =>	[0xed, 0xbb],
		}
		@evalexp = EvalExp.new
	end


	######### 命令ごとの定義

	# LD reg8, reg8
	def parseLD_r_r(dst, src)
		if dst != REG_IHL || src != REG_IHL
			return [0x40 + dst * 8 + src]
		end
	end

	# LD reg8, n
	def parseLD_r_n(dst, src)
		return [0x06 + dst * 8, src & 255]
	end

	# LD A, (rr)
	def parseLD_A_ireg16(dst, src)
		return [0x0a + src * 16]
	end

	# LD (rr), A
	def parseLD_ireg16_A(dst, src)
		return [0x02 + dst * 16]
	end

	# LD reg16, immediate
	def parseLD_rr_nn(dst, src)
		return [0x01 + dst * 16, src & 255, (src >> 8) & 255]
	end

	# LD (adr), HL
	def parseLD_iadr_HL(dst, src)
		return [0x22, dst & 255, (dst >> 8) & 255]
	end

	# LD HL, (adr)
	def parseLD_HL_iadr(dst, src)
		return [0x2a, src & 255, (src >> 8) & 255]
	end

	# LD (adr), A
	def parseLD_iadr_A(dst, src)
		return [0x32, dst & 255, (dst >> 8) & 255]
	end

	# LD A, (adr)
	def parseLD_A_iadr(dst, src)
		return [0x3a, src & 255, (src >> 8) & 255]
	end

	# ADD A, reg8
	def parseADD_A_r(dst, src)
		return [0x80 + src]
	end

	# ADD A, n
	def parseADD_A_n(dst, src)
		return [0xc6, src & 255]
	end

	# ADC A, reg8
	def parseADC_A_r(dst, src)
		return [0x88 + src]
	end

	# ADC A, n
	def parseADC_A_n(dst, src)
		return [0xce, src & 255]
	end

	# SUB reg8
	def parseSUB_r(src)
		return [0x90 + src]
	end

	# SUB n
	def parseSUB_n(src)
		return [0xd6, src & 255]
	end

	# SBC A, reg8
	def parseSBC_A_r(dst, src)
		return [0x98 + src]
	end

	# SBC A, n
	def parseSBC_A_n(dst, src)
		return [0xde, src & 255]
	end

	# AND reg8
	def parseAND_r(src)
		return [0xa0 + src]
	end

	# AND n
	def parseAND_n(src)
		return [0xe6, src & 255]
	end

	# XOR reg8
	def parseXOR_r(src)
		return [0xa8 + src]
	end

	# XOR n
	def parseXOR_n(src)
		return [0xee, src & 255]
	end

	# OR reg8
	def parseOR_r(src)
		return [0xb0 + src]
	end

	# OR n
	def parseOR_n(src)
		return [0xf6, src & 255]
	end

	# CP reg8
	def parseCP_r(src)
		return [0xb8 + src]
	end

	# CP n
	def parseCP_n(src)
		return [0xfe, src & 255]
	end

	# ADD HL, reg16
	def parseADD_HL_rr(dst, src)
		return [0x09 + src * 16]
	end

	# INC reg8
	def parseINC_r(dst)
		return [0x04 + dst * 8]
	end

	# INC reg16
	def parseINC_rr(dst)
		return [0x03 + dst * 16]
	end

	# DEC reg8
	def parseDEC_r(dst)
		return [0x05 + dst * 8]
	end

	# DEC reg16
	def parseDEC_rr(dst)
		return [0x0b + dst * 16]
	end

	# JR d or JR cond,d
	def parseJR_n(param1, param2=nil)
		if param2
			cond = param1
			ofs = param2 - (@adr + 2)
			op = 0x20 + cond * 8
		else
			ofs = param1 - (@adr + 2)
			op = 0x18
		end

		if out_of_signed_char? ofs
			error "offset out of range (-128 .. +127)"
			[]
		else
			[op, ofs & 255]
		end
	end

	# DJNZ d
	def parseDJNZ_n(dst)
		ofs = dst - (@adr + 2)
		return [0x10, ofs & 255]
	end

	# JP d or JP cond,d
	def parseJP_nn(param1, param2=nil)
		if param2
			cond = param1
			dst = param2
			return [0xc2 + cond * 8, dst & 255, (dst >> 8) & 255]
		else
			dst = param1
			return [0xc3, dst & 255, (dst >> 8) & 255]
		end
	end

	# PUSH reg16
	def parsePUSH_rr(dst)
		return [0xc5 + dst * 16]
	end

	# POP reg16
	def parsePOP_rr(dst)
		return [0xc1 + dst * 16]
	end

	# CALL d or CALL cond,d
	def parseCALL_nn(param1, param2=nil)
		if param2
			cond = param1
			dst = param2
			return [0xc4 + cond * 8, dst & 255, (dst >> 8) & 255]
		else
			dst = param1
			return [0xcd, dst & 255, (dst >> 8) & 255]
		end
	end

	# RET or RET cond
	def parseRET(cond=nil)
		if cond
			return [0xc0 + cond * 8]
		else
			return [0xc9]
		end
	end

	# RST n
	def parseRST_n(adr)
		if (adr & ~0x38) != 0
			error "illegal address: #{"%x" % adr}"
			[]
		else
			return [0xc7 + (adr & 0x38)]
		end
	end

	# OUT (n), A
	def parseOUT_iadr_A(dst, src)
		return [0xd3, dst & 255]
	end

	# IN A, (n)
	def parseIN_A_iadr(dst, src)
		return [0xdb, src & 255]
	end

	# BIT n, A
	def parseBIT_n_A(dst, src)
		if dst < 0 || dst > 7
			error "illegal bit: #{dst}"
			return []
		end
		return [0xcb, 0x40 + (dst & 7) * 8 + src]
	end

	# BIT n, (IX+a)
	def parseBIT_n_iIX(dst, src)
		if dst < 0 || dst > 7
			error "illegal bit: #{dst}"
			return []
		end

		ix = src[0]
		ofs = src[1]

		if out_of_signed_char? ofs
			error "offset out of range (-128 .. +127)"
			return []
		else
			return [0xdd + ix * 0x20, 0xcb, ofs, 0x46 + (dst & 7) * 8]
		end
	end

	# RES n, A
	def parseRES_n_A(dst, src)
		if dst < 0 || dst > 7
			error "illegal bit: #{dst}"
			return []
		end
		return [0xcb, 0x80 + (dst & 7) * 8 + src]
	end

	# RES n, (IX+a)
	def parseRES_n_iIX(dst, src)
		if dst < 0 || dst > 7
			error "illegal bit: #{dst}"
			return []
		end

		ix = src[0]
		ofs = src[1]
		if out_of_signed_char? ofs
			error "offset out of range (-128 .. +127)"
			[]
		else
			return [0xdd + ix * 0x20, 0xcb, ofs, 0x86 + (dst & 7) * 8]
		end
	end

	# SET n, A
	def parseSET_n_A(dst, src)
		if dst < 0 || dst > 7
			error "illegal bit: #{dst}"
			return []
		end
		return [0xcb, 0xc0 + (dst & 7) * 8 + src]
	end

	# SET n, (IX+a)
	def parseSET_n_iIX(dst, src)
		if dst < 0 || dst > 7
			error "illegal bit: #{dst}"
			return []
		end

		ix = src[0]
		ofs = src[1]
		if out_of_signed_char? ofs
			error "offset out of range (-128 .. +127)"
			[]
		else
			return [0xdd + ix * 0x20, 0xcb, ofs, 0xc6 + (dst & 7) * 8]
		end
	end



	# DD, ED, FD

	# LD IX, nn
	def parseLD_IX_nn(dst, src)
		return [0xdd + dst * 0x20, 0x21, src & 255, (src >> 8) & 255]
	end

	# LD (nn), IX
	def parseLD_iadr_IX(dst, src)
		return [0xdd + src * 0x20, 0x22, dst & 255, (dst >> 8) & 255]
	end

	# LD IX, (nn)
	def parseLD_IX_iadr(dst, src)
		return [0xdd + dst * 0x20, 0x2a, src & 255, (src >> 8) & 255]
	end

	# LD (IX+a), n
	def parseLD_iIX_n(dst, src)
		ix = dst[0]
		ofs = dst[1]
		if out_of_signed_char? ofs
			error "offset out of range (-128 .. +127)"
			[]
		else
			return [0xdd + ix * 0x20, 0x36, ofs, src & 255]
		end
	end

	# LD r, (IX+a)
	def parseLD_r_iIX(dst, src)
		ix = src[0]
		ofs = src[1]
		if out_of_signed_char? ofs
			error "offset out of range (-128 .. +127)"
			[]
		else
			return [0xdd + ix * 0x20, 0x46 + dst * 8, ofs]
		end
	end

	# LD (IX+a), r
	def parseLD_iIX_r(dst, src)
		ix = dst[0]
		ofs = dst[1]
		if out_of_signed_char? ofs
			error "offset out of range (-128 .. +127)"
			[]
		else
			return [0xdd + ix * 0x20, 0x70 + src, ofs]
		end
	end

	# LD (nn), rr
	def parseLD_iadr_rr(dst, src)
		if src != REG_HL
			return [0xed, 0x43 + src * 16, dst & 255, (dst >> 8) & 255]
		end
	end

	# LD rr, (nn)
	def parseLD_rr_iadr(dst, src)
		if src != REG_HL
			return [0xed, 0x4b + dst * 16, src & 255, (src >> 8) & 255]
		end
	end

	# LD I or R, A
	def parseLD_IR_A(dst, src)
		return [0xed, 0x47 + dst * 8]
	end

	# LD A, I or R
	def parseLD_A_IR(dst, src)
		return [0xed, 0x57 + src * 8]
	end

	# ADD IX, reg16
	def parseADD_IX_rr(dst, src)
		if src != REG_HL
			return [0xdd + dst * 0x20, 0x09 + src * 16]
		end
	end

	# ADD IX, IX
	def parseADD_IX_IX(dst, src)
		if dst == src
			return [0xdd + dst * 0x20, 0x29]
		end
	end

	# ADD A, (IX+a)
	def parseADD_A_iIX(dst, src)
		ix = src[0]
		ofs = src[1]
		if out_of_signed_char? ofs
			error "offset out of range (-128 .. +127)"
			[]
		else
			return [0xdd + ix * 0x20, 0x86, ofs]
		end
	end

	# ADC A, (IX+a)
	def parseADC_A_iIX(dst, src)
		ix = src[0]
		ofs = src[1]
		if out_of_signed_char? ofs
			error "offset out of range (-128 .. +127)"
			[]
		else
			return [0xdd + ix * 0x20, 0x8e, ofs]
		end
	end

	# ADC HL, rr
	def parseADC_HL_rr(dst, src)
		return [0xed, 0x4a + src * 0x10]
	end

	# SUB (IX+a)
	def parseSUB_iIX(src)
		ix = src[0]
		ofs = src[1]
		if out_of_signed_char? ofs
			error "offset out of range (-128 .. +127)"
			[]
		else
			return [0xdd + ix * 0x20, 0x96, ofs]
		end
	end

	# SBC A, (IX+a)
	def parseSBC_A_iIX(dst, src)
		ix = src[0]
		ofs = src[1]
		if out_of_signed_char? ofs
			error "offset out of range (-128 .. +127)"
			[]
		else
			return [0xdd + ix * 0x20, 0x9e, ofs]
		end
	end

	# AND (IX+a)
	def parseAND_iIX(src)
		ix = src[0]
		ofs = src[1]
		if out_of_signed_char? ofs
			error "offset out of range (-128 .. +127)"
			[]
		else
			return [0xdd + ix * 0x20, 0xa6, ofs]
		end
	end

	# XOR (IX+a)
	def parseXOR_iIX(src)
		ix = src[0]
		ofs = src[1]
		if out_of_signed_char? ofs
			error "offset out of range (-128 .. +127)"
			[]
		else
			return [0xdd + ix * 0x20, 0xae, ofs]
		end
	end

	# OR (IX+a)
	def parseOR_iIX(src)
		ix = src[0]
		ofs = src[1]
		if out_of_signed_char? ofs
			error "offset out of range (-128 .. +127)"
			[]
		else
			return [0xdd + ix * 0x20, 0xb6, ofs]
		end
	end

	# CP (IX+a)
	def parseCP_iIX(src)
		ix = src[0]
		ofs = src[1]
		if out_of_signed_char? ofs
			error "offset out of range (-128 .. +127)"
			[]
		else
			return [0xdd + ix * 0x20, 0xbe, ofs]
		end
	end

	# INC IX
	def parseINC_IX(dst)
		return [0xdd + dst * 0x20, 0x23]
	end

	# INC (IX+a)
	def parseINC_iIX(dst)
		ix = dst[0]
		ofs = dst[1]
		if out_of_signed_char? ofs
			error "offset out of range (-128 .. +127)"
			[]
		else
			return [0xdd + ix * 0x20, 0x34, ofs]
		end
	end

	# DEC IX
	def parseDEC_IX(dst)
		return [0xdd + dst * 0x20, 0x2b]
	end

	# DEC (IX+a)
	def parseDEC_iIX(dst)
		ix = dst[0]
		ofs = dst[1]
		if out_of_signed_char? ofs
			error "offset out of range (-128 .. +127)"
			[]
		else
			return [0xdd + ix * 0x20, 0x35, ofs]
		end
	end

	# JP (IX)
	def parseJP_iIX(dst)
		ix = dst[0]
		ofs = dst[1]
		if ofs == 0
			return [0xdd + ix * 0x20, 0xe9]
		end
	end

	# OUT (C), r
	def parseOUT_iC_r(dst, src)
		return [0xed, 0x41 + src * 8]
	end

	# IN B, (C)
	def parseIN_r_iC(dst, src)
		return [0xed, 0x40 + dst * 8]
	end

	# SBC HL, rr
	def parseSBC_HL_rr(dst, src)
		return [0xed, 0x42 + src * 0x10]
	end

	# IM n
	def parseIM_n(dst)
		code = case dst
		when 0
			0x46
		when 1
			0x56
		when 2
			0x5e
		else
			return nil
		end
		return [0xed, code]
	end


	def parseDB(str)
		begin
			res = []
			loop do
#				if /^\s*'(.*?)'/o =~ str || /^\s*"(.*?)"/o =~ str
#					$1.each_byte {|ch| res << (ch & 255)}
#					str = $'
#				else
					r, s = @evalexp.eval @label_table, str
					str = s

					if r.kind_of?(String)
						r.each_byte {|ch| res << (ch & 255)}
					else
						res << (r & 255) if r
					end
#				end

				case str
				when /^\s*,/o
					str = $'
				when /^\s*$/o
					break
				else
					syntax_error str
				end
			end
			res
		rescue => excep
			error excep
			[]
		end
	end

	def parseDW(str)
		begin
			res = []
			loop do
				r, s = @evalexp.eval @label_table, str

				if !r.kind_of?(Numeric)
					error "number expected: #{str}"
					break
				end

				res.concat [r & 255, (r >> 8) & 255]

				str = s
				case str
				when /^\s*,/o
					str = $'
				when /^\s*$/o
					break
				else
					syntax_error str
				end
			end
			res
		rescue => excep
			error excep
			[]
		end
	end

	def parseDS(str)
		begin
			res = []
			loop do
				r, s = @evalexp.eval @label_table, str

				if !r.kind_of?(Numeric)
					error "number expected: #{str}"
					break
				end

				res.concat [0] * r

				str = s
				case str
				when /^\s*,/o
					str = $'
				when /^\s*$/o
					break
				else
					syntax_error str
					break
				end
			end
			res
		rescue => excep
			error excep
			[]
		end
	end
end


###############################################################################

# エントリ

opt = {
	# :listfn = リストファイルを出力する場合、ファイル名
	# :outfn = オブジェクトファイルの出力名
}

begin
	o = OptionParser.new
	o.banner = "Z80 assembler by Ruby\nusage:\t[option] srcfile\noption:"
	o.on('-l', 'output list file (.lst)') {|listfn| opt[:listfn] = true}
	o.on('-o outfn', 'set output filename (default: .com)') {|fn| opt[:outfn] = fn}
	o.parse!(ARGV)
end

srcfn = ARGV.shift
if !srcfn
	$stdout.puts o.help
	exit 1
end

dir = File.dirname(srcfn)
dir = '' if dir == '/'
basename = File.basename(srcfn, '.*')
if opt[:listfn]
	opt[:listfn] = "#{dir}/#{basename}.lst"
end
if !opt[:outfn]
	opt[:outfn] = "#{dir}/#{basename}.z8b"
end

z80as = Z80As.new
r = z80as.assemble srcfn, opt
if !r
	exit 1
else
	err_num = r
	if err_num > 0
		$stdout.puts "#{err_num} error(s)"
		exit 1
	else
		exit 0
	end
end
