#    This file is part of Metasm, the Ruby assembly manipulation suite
#    Copyright (C) 2006-2009 Yoann GUILLOT
#
#    Licence is LGPL, see LICENCE in the top-level directory


require 'metasm/cpu/arm/opcodes'
require 'metasm/encode'

module Metasm
class ARM
	def encode_instr_op(program, instr, op)
		base = op.bin
		set_field = lambda { |f, v|
			v = v.reduce if v.kind_of?(Expression)
			case f
			when :i8_12
				base = Expression[base, :|, [[v, :&, 0xf], :|, [[v, :<<, 4], :&, 0xf00]]]
				next
			when :stype; v = [:lsl, :lsr, :asr, :ror].index(v)
			when :u; v = [:-, :+].index(v)
			end
			base = Expression[base, :|, [[v, :&, @fields_mask[f]], :<<, @fields_shift[f]]]
		}

		val, mask, shift = 0, 0, 0

		if op.props[:cond]
			coff = op.props[:cond_name_off] || op.name.length
			cd = instr.opname[coff, 2]
			cdi = %w[eq ne cs cc mi pl vs vc hi ls ge lt gt le al].index(cd) || 14	# default = al
			set_field[:cond, cdi]
		end

		op.args.zip(instr.args).each { |sym, arg|
			case sym
			when :rd, :rs, :rn, :rm; set_field[sym, arg.i]
			when :rm_rs
				set_field[:rm, arg.i]
				set_field[:stype, arg.stype]
				set_field[:rs, arg.shift.i]
			when :rm_is
				set_field[:rm, arg.i]
				set_field[:stype, arg.stype]
				set_field[:shifti, arg.shift]
			when :mem_rn_rm, :mem_rn_rms, :mem_rn_i8_12, :mem_rn_i12
				set_field[:rn, arg.base.i]
				case sym
				when :mem_rn_rm
					set_field[:rm, arg.offset.i]
				when :mem_rn_rms
					set_field[:rm, arg.offset.i]
					set_field[:stype, arg.offset.stype]
					set_field[:rs, arg.offset.shift.i]
				when :mem_rn_i8_12
					set_field[:i8_12, arg.offset]
				when :mem_rn_i12
					set_field[:i12, arg.offset]
				end
				# TODO set_field[:u] etc
			when :reglist
				set_field[sym, arg.list.inject(0) { |rl, r| rl | (1 << r.i) }]
			when :i8_r
				b = arg.reduce & 0xffffffff
				r = (0..15).find {
					next true if b < 0x100
					b = ((b << 2) & 0xffff_ffff) | ((b >> 30) & 3)
					false
				}
				raise EncodeError, "Invalid constant" if not r
				set_field[:i8, b]
				set_field[:rotate, r]
			when :i12, :i24
				val, mask, shift = arg, @fields_mask[sym], @fields_shift[sym]
			end
		}

		if op.args[-1] == :i24
			# convert label name for branch to relative offset
			label = program.new_label('l_'+op.name)
			target = val
			target = target.rexpr if target.kind_of?(Expression) and target.op == :+ and not target.lexpr
			val = Expression[[target, :-, [label, :+, 8]], :>>, 2]

			EncodedData.new('', :export => { label => 0 }) <<
			Expression[base, :|, [[val, :<<, shift], :&, mask]].encode(:u32, @endianness)
		else
			Expression[base, :|, [[val, :<<, shift], :&, mask]].encode(:u32, @endianness)
		end
	end
end
end
