<reponame>kjcolley7/PEGASUS<gh_stars>1-10
from .geometry import *
from .isa import *
from .ast import *


class Instruction(object):
	def __init__(self, cc="AL", toggle_flags=False, write_flags=None):
		self.cc = cc
		self.operands = dict()
		self.pc = None
		self.prefixes = []
		self.xc_prefix = [p8(PREFIX_XC)] if CC_MAP[cc] >= 8 else []
		
		if write_flags is not None:
			assert(toggle_flags == False)
			
			will_write_flags = True
			if cc not in ("AL", "SP"):
				will_write_flags = False
			
			if write_flags != will_write_flags:
				toggle_flags = True
		
		self.toggle_flags = toggle_flags
		if toggle_flags:
			self.prefixes.append(p8(PREFIX_TF))
	
	def get_prefix_bytes(self):
		return self.prefixes + self.xc_prefix
	
	def got_operands(self):
		pass
	
	def set_operands(self, **kwargs):
		self.operands = kwargs
		self.got_operands()
		return self
	
	def get_mnemonic(self):
		mnem = self.__class__.__name__
		if mnem.startswith("Insn"):
			mnem = mnem[4:]
		if self.toggle_flags:
			mnem += "F"
		return mnem
	
	def get_opcode(self):
		return self.OPCODE
	
	def make_opbyte(self):
		return p8(((CC_MAP[self.cc] & 7) << 5) | (self.get_opcode() & 0x1F))
	
	def __repr__(self):
		argstr = repr(self.cc)
		if self.operands:
			argstr += ", " + ", ".join("%s=%r" % (k, v) for k, v in self.operands.items())
		return "%s(%s)" % (self.get_mnemonic(), argstr)

class InsnRxy(Instruction):
	def got_operands(self):
		self.rx = self.operands["Rx"]
		self.vy = self.operands["Vy"]
		
		if "Rd" in self.operands:
			self.rd = self.operands["Rd"]
			
			# DR prefix byte needed?
			if self.rd != self.rx:
				self.prefixes.append(p8(PREFIX_DR(self.rd)))
		else:
			self.rd = None
	
	def has_imm(self):
		return not isinstance(self.vy, RegExpr)
	
	def __len__(self):
		# Register prefixes, opcode byte, regpair byte
		size = len(self.get_prefix_bytes()) + 1 + 1
		if self.has_imm():
			# Imm16
			size += 2
		return size
	
	def assemble(self, context):
		# Encode prefixes
		ret = self.get_prefix_bytes()
		
		# Encode opcode byte
		ret.append(self.make_opbyte())
		
		# Encode regpair byte
		if self.has_imm():
			ry = RegExpr("DPC")
		else:
			ry = self.vy
		ret.append(p8((self.rx.num << 4) | ry.num))
		
		# Encode Imm16 (if present)
		if self.has_imm():
			ret.append(p16(self.vy.value(context)))
		return b"".join(ret)

class InsnAddOrSub(InsnRxy):
	def __init__(self, cc="AL", toggle_flags=False, write_flags=None):
		InsnRxy.__init__(self, cc, toggle_flags, write_flags)
		self._inc = None
		self._is_inc = None
	
	def _make_inc(self):
		operands = {}
		if self.rd is not None:
			operands["Rd"] = self.rd
		operands["Rx"] = self.rx
		operands["SImm4"] = self.vy if self.OPCODE == 0x00 else NegExpr(self.vy)
		self._inc = InsnINC(self.cc, self.toggle_flags).set_operands(**operands)
		self._is_inc = True
	
	def is_inc(self):
		if self._is_inc is None:
			self._is_inc = False
			try:
				imm = self.vy.value()
				if (-8 <= imm <= -1) or (1 <= imm <= 8):
					self._make_inc()
					self._is_inc = True
			except AttributeError:
				pass
			except TypeError:
				pass
		
		return self._is_inc
	
	def _wrap_inc(method):
		def _proxy_inc(self, *args, **kwargs):
			if self.is_inc():
				return getattr(self._inc, method)(*args, **kwargs)
			else:
				return getattr(InsnRxy, method)(self, *args, **kwargs)
		
		return _proxy_inc
	
	__len__ = _wrap_inc("__len__")
	assemble = _wrap_inc("assemble")
	get_mnemonic = _wrap_inc("get_mnemnoic")
	__repr__ = _wrap_inc("__repr__")

class InsnADD(InsnAddOrSub):
	OPCODE = 0x00

class InsnSUB(InsnAddOrSub):
	OPCODE = 0x01

class InsnMLU(InsnRxy):
	OPCODE = 0x02

class InsnMLS(InsnRxy):
	OPCODE = 0x03

class InsnDVU(InsnRxy):
	OPCODE = 0x04

class InsnDVS(InsnRxy):
	OPCODE = 0x05

class InsnXOR(InsnRxy):
	OPCODE = 0x06

class InsnAND(InsnRxy):
	OPCODE = 0x07

class InsnORR(InsnRxy):
	OPCODE = 0x08

class InsnSHL(InsnRxy):
	OPCODE = 0x09

class InsnSRU(InsnRxy):
	OPCODE = 0x0A

class InsnSRS(InsnRxy):
	OPCODE = 0x0B

class InsnMOV(InsnRxy):
	OPCODE = 0x0C

class InsnCMP(InsnRxy):
	OPCODE = 0x0D

class InsnLDW(InsnRxy):
	OPCODE = 0x10

class InsnSTW(InsnRxy):
	OPCODE = 0x11

class InsnLDB(InsnRxy):
	OPCODE = 0x12

class InsnSTB(InsnRxy):
	OPCODE = 0x13
	
	def got_operands(self):
		self.rx = self.operands["Rx"]
		self.v8 = self.operands["V8"]
	
	def has_imm(self):
		return not isinstance(self.v8, RegExpr)
	
	def __len__(self):
		# Register prefixes, opcode byte, regpair byte
		size = len(self.get_prefix_bytes()) + 1 + 1
		if self.has_imm():
			# Imm8
			size += 1
		return size
	
	def assemble(self, context):
		# Encode prefixes
		ret = self.get_prefix_bytes()
		
		# Encode opcode byte
		ret.append(self.make_opbyte())
		
		# Encode Rx:V8 byte
		if self.has_imm():
			ry = RegExpr("DPC")
		else:
			ry = self.v8
		ret.append(p8((self.rx.num << 4) | ry.num))
		
		# Encode Imm8 byte (if present)
		if self.has_imm():
			# Truncate value if too large
			immval = self.v8.value(context) & 0xFF
			ret.append(p8(immval))
		return b"".join(ret)

class InsnBRA(InsnRxy):
	OPCODE = 0x14
	
	def got_operands(self):
		self.operands["Rx"] = self.operands.get("Rx", RegExpr("DPC"))
		InsnRxy.got_operands(self)

class InsnBRR(Instruction):
	OPCODE = 0x15
	
	def got_operands(self):
		if "Imm16" in self.operands:
			self.imm16 = NumExpr(self.operands["Imm16"])
		else:
			label = self.operands["Label"]
			
			# Target is label - @PC@
			curpc = LabelExpr("PC@")
			self.imm16 = SubExpr(label, curpc)
		
		Instruction.got_operands(self)
	
	def __len__(self):
		# Register prefixes, opcode byte, Imm16
		return len(self.get_prefix_bytes()) + 1 + 2
	
	def assemble(self, context):
		# Encode prefixes
		ret = self.get_prefix_bytes()
		
		# Encode opcode byte
		ret.append(self.make_opbyte())
		
		# Encode Imm16
		ret.append(p16(self.imm16.value(context)))
		return b"".join(ret)

class InsnFCA(InsnBRA):
	OPCODE = 0x16

class InsnFCR(InsnBRR):
	OPCODE = 0x17

class InsnRDB(Instruction):
	OPCODE = 0x18
	
	def got_operands(self):
		self.rx = self.operands["Rx"]
		self.port = self.operands.get("Port", NumExpr(0))
	
	def __len__(self):
		# Register prefixes, opcode byte, Rx:Port byte
		return len(self.get_prefix_bytes()) + 1 + 1
	
	def assemble(self, context):
		# Encode prefixes
		ret = self.get_prefix_bytes()
		
		# Encode opcode byte
		ret.append(self.make_opbyte())
		
		# Encode Rx:Port byte
		port = self.port.value(context)
		if port < 0 or port >= 16:
			raise RangeError("Port number in RDB out of range 0-15: %d" % port)
		ret.append(p8((self.rx.num << 4) | port))
		return b"".join(ret)

class InsnWRB(Instruction):
	OPCODE = 0x19
	
	def got_operands(self):
		self.port = self.operands.get("Port", NumExpr(0))
		self.v8 = self.operands["V8"]
	
	def has_imm(self):
		return not isinstance(self.v8, RegExpr)
	
	def __len__(self):
		# Register prefixes, opcode byte, Port:V8 byte
		size = len(self.get_prefix_bytes()) + 1 + 1
		if self.has_imm():
			# Imm8
			size += 1
		return size
	
	def assemble(self, context):
		# Encode prefixes
		ret = self.get_prefix_bytes()
		
		# Encode opcode byte
		ret.append(self.make_opbyte())
		
		# Encode Port:Ry byte
		port = self.port.value(context)
		if port < 0 or port >= 16:
			raise RangeError("Port number in RDB out of range [0, 15]: %d" % port)
		if self.has_imm():
			ry = RegExpr("DPC")
		else:
			ry = self.v8
		ret.append(p8((port << 4) | ry.num))
		
		# Encode Imm8 (if present)
		if self.has_imm():
			ret.append(p8(self.v8.value(context)))
		return b"".join(ret)

class InsnRegset(Instruction):
	def got_operands(self):
		self.regset = self.operands["Regs16"]
		
		if "Rd" in self.operands:
			self.rd = self.operands["Rd"]
			self.prefixes.append(p8(PREFIX_DR(self.rd)))
	
	def get_regs16(self):
		regs16 = 0
		for r in self.regset:
			regs16 |= 1 << r.num
		return regs16
	
	def __len__(self):
		# Register prefixes, opcode byte, Regs16
		return len(self.get_prefix_bytes()) + 1 + 2
	
	def assemble(self, context):
		# Encode prefixes
		ret = self.get_prefix_bytes()
		
		# Encode opcode byte
		ret.append(self.make_opbyte())
		
		# Encode Regs16
		ret.append(p16(self.get_regs16()))
		return b"".join(ret)

class InsnPSH(InsnRegset):
	OPCODE = 0x1A

class InsnPOP(InsnRegset):
	OPCODE = 0x1B

class InsnINC(Instruction):
	OPCODE = 0x1C
	
	def got_operands(self):
		self.rx = self.operands["Rx"]
		self.simm4 = self.operands.get("SImm4", NumExpr(1))
		
		if "Rd" in self.operands:
			self.rd = self.operands["Rd"]
			
			# DR prefix byte needed?
			if self.rd != self.rx:
				self.prefixes.append(p8(PREFIX_DR(self.rd)))
		else:
			self.rd = None
	
	def __len__(self):
		# Register prefixes, opcode byte, Rx:SImm4 byte
		return len(self.get_prefix_bytes()) + 1 + 1
	
	def assemble(self, context):
		# Encode prefixes
		ret = self.get_prefix_bytes()
		
		# Encode opcode byte
		ret.append(self.make_opbyte())
		
		# Compute value of SImm4 constexpr
		val = self.simm4.value(context)
		
		# Check range of SImm4
		if val < -8 or val > 8:
			raise RangeError("SImm4 value in INC out of range [-8, 8]: %d" % val)
		
		# INC Rx, 0 cannot be encoded
		if val == 0:
			nop = InsnNOP().assemble(context)
			assert(len(nop) == 1)
			
			if self.rd is None:
				# Encode "INC Rx, 0" as NOPs
				return nop * len(self)
			else:
				# Encode "INC Rd, Rx, 0" as "MOV Rd, Rx" padded with trailing NOPs as necessary
				mov = InsnMOV(self.cc)
				mov.set_operands(Rx=self.rd, Vy=self.rx)
				return mov.assemble(context).ljust(len(self), nop)
		
		# Encode Rx:SImm4 byte
		if val > 0:
			val -= 1
		ret.append(p8((self.rx.num << 4) | (val & 0x0F)))
		return b"".join(ret)

class InsnBare(Instruction):
	def __len__(self):
		# Register prefixes, opcode byte
		return len(self.get_prefix_bytes()) + 1
	
	def assemble(self, context):
		# Encode prefixes
		ret = self.get_prefix_bytes()
		
		# Encode opcode byte
		ret.append(self.make_opbyte())
		return b"".join(ret)

class InsnBPT(InsnBare):
	OPCODE = 0x1D

class InsnHLT(InsnBare):
	OPCODE = 0x1E

class InsnNOP(InsnBare):
	OPCODE = 0x1F

class PseudoInstruction(Instruction):
	def __init__(self, cc="AL", toggle_flags=False):
		Instruction.__init__(self, cc, toggle_flags)
		self.insns = None
	
	def get_instructions(self):
		if self.insns is None:
			self.insns = self.real_instructions()
		return self.insns
	
	def __len__(self):
		return sum(len(insn) for insn in self.get_instructions())
	
	def assemble(self, context):
		# Encode each real instruction and concatenate their encoded results
		return b"".join(insn.assemble(context) for insn in self.get_instructions())

class InsnRET(PseudoInstruction):
	def real_instructions(self):
		# Replace "RET.cc" with "BRA.cc RD, RA"
		return [InsnBRA(self.cc).set_operands(Rx=RegExpr("RD"), Vy=RegExpr("RA"))]

class InsnDEC(PseudoInstruction):
	def got_operands(self):
		# Replace "DEC.cc Rx, value" with "INC.cc Rx, -value"
		self.operands["SImm4"] = NegExpr(self.operands.get("SImm4", NumExpr(1)))
	
	def real_instructions(self):
		return [InsnINC(self.cc, self.toggle_flags).set_operands(**self.operands)]

class InsnNEG(PseudoInstruction):
	def got_operands(self):
		self.rx = self.operands["Rx"]
	
	def real_instructions(self):
		# Replace "NEG.cc Rx" with "SUB.cc Rx, ZERO, Rx"
		return [
			InsnSUB(self.cc, self.toggle_flags)
			.set_operands(Rd=self.rx, Rx=RegExpr("ZERO"), Vy=self.rx)
		]

class InsnINV(PseudoInstruction):
	def got_operands(self):
		self.rx = self.operands["Rx"]
	
	def real_instructions(self):
		# Replace "INV.cc Rx" with "XOR.cc Rx, -1"
		return [InsnXOR(self.cc, self.toggle_flags).set_operands(Rx=self.rx, Vy=NumExpr(-1))]

class InsnADR(PseudoInstruction):
	def got_operands(self):
		self.rx = self.operands["Rx"]
		self.target = self.operands["Label"]
	
	def __len__(self):
		# Length of this instruction: optional XC byte, DR byte, opcode byte, regpair byte, Imm16
		return len(self.get_prefix_bytes()) + 1 + 1 + 1 + 2
	
	def real_instructions(self):
		# Length of this instruction: optional XC byte, DR byte, opcode byte, regpair byte, Imm16
		curlen = NumExpr(len(self.get_prefix_bytes()) + 1 + 1 + 1 + 2)
		
		# Replace "ADR.cc Rx, target" with "ADD.cc Rx, PC, target - @PC@"
		curpc = LabelExpr("PC@")
		vy = SubExpr(self.target, curpc)
		return [
			InsnADD(self.cc, self.toggle_flags)
			.set_operands(Rd=self.rx, Rx=RegExpr("PC"), Vy=vy)
		]

class InsnSWP(PseudoInstruction):
	def got_operands(self):
		self.ra = self.operands["Ra"]
		self.rb = self.operands["Rb"]
	
	def real_instructions(self):
		# Replace "SWP.cc Ra, Rb" with "XOR.cc Ra, Rb; XOR.cc Rb, Ra; XOR.cc Ra, Rb"
		kwargs = {}
		if self.cc != "AL":
			kwargs["write_flags"] = False
		
		xor_ab1 = InsnXOR(self.cc, **kwargs).set_operands(Rx=self.ra, Vy=self.rb)
		xor_ba = InsnXOR(self.cc, **kwargs).set_operands(Rx=self.rb, Vy=self.ra)
		xor_ab2 = InsnXOR(self.cc, self.toggle_flags).set_operands(Rx=self.ra, Vy=self.rb)
		return [xor_ab1, xor_ba, xor_ab2]

class PseudoAddOrSubWithCarry(PseudoInstruction):
	def __init__(self, cc="AL", toggle_flags=False, is_add=None):
		PseudoInstruction.__init__(self, cc, toggle_flags)
		self.is_add = is_add
	
	def got_operands(self):
		self.ra = self.operands["Rx"]
		self.vb = self.operands["Vy"]
		self.rd = self.operands.get("Rd", self.ra)
	
	def real_instructions(self):
		incdec = InsnINC if self.IS_ADD else InsnDEC
		addsub = InsnADD if self.IS_ADD else InsnSUB
		
		if self.ra == self.rd:
			"""
			ADC Ra, Vb:
				INC.CS Ra
				ADD Ra, Vb
			
			ADC.cc Ra, Vb:
				BRR.!cc @.after
				ADC Ra, Vb
				
			@.after:
			"""
			incdec_cs = incdec("CS").set_operands(Rx=self.ra)
			addsub_ab = addsub().set_operands(Rx=self.ra, Vy=self.vb)
			
			ret = []
			if self.cc != "AL":
				jump_amount = len(incdec_cs) + len(addsub_ab)
				brr_ncc = InsnBRR(CONDITION_INVERSES[self.cc]).set_operands(Imm16=jump_amount)
				ret.append(brr_ncc)
			
			ret.append(incdec_cs)
			ret.append(addsub_ab)
			return ret
		else:
			"""
			ADC Rd, Ra, Vb:
				INC.CS Rd, ZERO, 1
				ADD Rd, Ra
				ADD Rd, Vb
			
			SBC Rd, Ra, Vb:
				DEC.CS Rd, ZERO, 1
				ADD Rd, Ra
				SUB Rd, Vb
			
			ADC.cc Rd, Ra, Vb:
				BRR.!cc PC, @.after - @PC@
				ADC Rd, Ra, Vb
			
			@.after:
			"""
			incdec_cs = incdec("CS").set_operands(Rd=self.rd, Rx=RegExpr("ZERO"))
			add_da = InsnADD().set_operands(Rx=self.rd, Vy=self.ra)
			addsub_db = addsub().set_operands(Rx=self.rd, Vy=self.vb)
			
			ret = []
			if self.cc != "AL":
				jump_amount = len(incdec_cs) + len(add_da) + len(addsub_db)
				brr_after = InsnBRR(CONDITION_INVERSES[self.cc]).set_operands(Imm16=jump_amount)
				ret.append(brr_after)
			
			ret.append(incdec_cs)
			ret.append(add_da)
			ret.append(addsub_db)
			return ret

class InsnADC(PseudoAddOrSubWithCarry):
	IS_ADD = True

class InsnSBC(PseudoAddOrSubWithCarry):
	IS_ADD = False


INSNS = [
	# Real instructions
	InsnADD, InsnSUB, InsnMLU, InsnMLS, InsnDVU, InsnDVS, InsnXOR, InsnAND,
	InsnORR, InsnSHL, InsnSRU, InsnSRS, InsnMOV, InsnCMP,
	InsnLDW, InsnSTW, InsnLDB, InsnSTB, InsnBRA, InsnBRR, InsnFCA, InsnFCR,
	InsnRDB, InsnWRB, InsnPSH, InsnPOP, InsnINC, InsnBPT, InsnHLT, InsnNOP,
	
	# Pseudo instructions
	InsnRET, InsnDEC, InsnNEG, InsnINV, InsnADR, InsnSWP, InsnADC, InsnSBC
]

INSN_MAP = {
	x.__name__[4:]: x for x in INSNS
}

from .geometry import *
from .isa import *
from .ast import *


class Instruction(object):
	def __init__(self, cc="AL", toggle_flags=False, write_flags=None):
		self.cc = cc
		self.operands = dict()
		self.pc = None
		self.prefixes = []
		self.xc_prefix = [p8(PREFIX_XC)] if CC_MAP[cc] >= 8 else []
		
		if write_flags is not None:
			assert(toggle_flags == False)
			
			will_write_flags = True
			if cc not in ("AL", "SP"):
				will_write_flags = False
			
			if write_flags != will_write_flags:
				toggle_flags = True
		
		self.toggle_flags = toggle_flags
		if toggle_flags:
			self.prefixes.append(p8(PREFIX_TF))
	
	def get_prefix_bytes(self):
		return self.prefixes + self.xc_prefix
	
	def got_operands(self):
		pass
	
	def set_operands(self, **kwargs):
		self.operands = kwargs
		self.got_operands()
		return self
	
	def get_mnemonic(self):
		mnem = self.__class__.__name__
		if mnem.startswith("Insn"):
			mnem = mnem[4:]
		if self.toggle_flags:
			mnem += "F"
		return mnem
	
	def get_opcode(self):
		return self.OPCODE
	
	def make_opbyte(self):
		return p8(((CC_MAP[self.cc] & 7) << 5) | (self.get_opcode() & 0x1F))
	
	def __repr__(self):
		argstr = repr(self.cc)
		if self.operands:
			argstr += ", " + ", ".join("%s=%r" % (k, v) for k, v in self.operands.items())
		return "%s(%s)" % (self.get_mnemonic(), argstr)

class InsnRxy(Instruction):
	def got_operands(self):
		self.rx = self.operands["Rx"]
		self.vy = self.operands["Vy"]
		
		if "Rd" in self.operands:
			self.rd = self.operands["Rd"]
			
			# DR prefix byte needed?
			if self.rd != self.rx:
				self.prefixes.append(p8(PREFIX_DR(self.rd)))
		else:
			self.rd = None
	
	def has_imm(self):
		return not isinstance(self.vy, RegExpr)
	
	def __len__(self):
		# Register prefixes, opcode byte, regpair byte
		size = len(self.get_prefix_bytes()) + 1 + 1
		if self.has_imm():
			# Imm16
			size += 2
		return size
	
	def assemble(self, context):
		# Encode prefixes
		ret = self.get_prefix_bytes()
		
		# Encode opcode byte
		ret.append(self.make_opbyte())
		
		# Encode regpair byte
		if self.has_imm():
			ry = RegExpr("DPC")
		else:
			ry = self.vy
		ret.append(p8((self.rx.num << 4) | ry.num))
		
		# Encode Imm16 (if present)
		if self.has_imm():
			ret.append(p16(self.vy.value(context)))
		return b"".join(ret)

class InsnAddOrSub(InsnRxy):
	def __init__(self, cc="AL", toggle_flags=False, write_flags=None):
		InsnRxy.__init__(self, cc, toggle_flags, write_flags)
		self._inc = None
		self._is_inc = None
	
	def _make_inc(self):
		operands = {}
		if self.rd is not None:
			operands["Rd"] = self.rd
		operands["Rx"] = self.rx
		operands["SImm4"] = self.vy if self.OPCODE == 0x00 else NegExpr(self.vy)
		self._inc = InsnINC(self.cc, self.toggle_flags).set_operands(**operands)
		self._is_inc = True
	
	def is_inc(self):
		if self._is_inc is None:
			self._is_inc = False
			try:
				imm = self.vy.value()
				if (-8 <= imm <= -1) or (1 <= imm <= 8):
					self._make_inc()
					self._is_inc = True
			except AttributeError:
				pass
			except TypeError:
				pass
		
		return self._is_inc
	
	def _wrap_inc(method):
		def _proxy_inc(self, *args, **kwargs):
			if self.is_inc():
				return getattr(self._inc, method)(*args, **kwargs)
			else:
				return getattr(InsnRxy, method)(self, *args, **kwargs)
		
		return _proxy_inc
	
	__len__ = _wrap_inc("__len__")
	assemble = _wrap_inc("assemble")
	get_mnemonic = _wrap_inc("get_mnemnoic")
	__repr__ = _wrap_inc("__repr__")

class InsnADD(InsnAddOrSub):
	OPCODE = 0x00

class InsnSUB(InsnAddOrSub):
	OPCODE = 0x01

class InsnMLU(InsnRxy):
	OPCODE = 0x02

class InsnMLS(InsnRxy):
	OPCODE = 0x03

class InsnDVU(InsnRxy):
	OPCODE = 0x04

class InsnDVS(InsnRxy):
	OPCODE = 0x05

class InsnXOR(InsnRxy):
	OPCODE = 0x06

class InsnAND(InsnRxy):
	OPCODE = 0x07

class InsnORR(InsnRxy):
	OPCODE = 0x08

class InsnSHL(InsnRxy):
	OPCODE = 0x09

class InsnSRU(InsnRxy):
	OPCODE = 0x0A

class InsnSRS(InsnRxy):
	OPCODE = 0x0B

class InsnMOV(InsnRxy):
	OPCODE = 0x0C

class InsnCMP(InsnRxy):
	OPCODE = 0x0D

class InsnLDW(InsnRxy):
	OPCODE = 0x10

class InsnSTW(InsnRxy):
	OPCODE = 0x11

class InsnLDB(InsnRxy):
	OPCODE = 0x12

class InsnSTB(InsnRxy):
	OPCODE = 0x13
	
	def got_operands(self):
		self.rx = self.operands["Rx"]
		self.v8 = self.operands["V8"]
	
	def has_imm(self):
		return not isinstance(self.v8, RegExpr)
	
	def __len__(self):
		# Register prefixes, opcode byte, regpair byte
		size = len(self.get_prefix_bytes()) + 1 + 1
		if self.has_imm():
			# Imm8
			size += 1
		return size
	
	def assemble(self, context):
		# Encode prefixes
		ret = self.get_prefix_bytes()
		
		# Encode opcode byte
		ret.append(self.make_opbyte())
		
		# Encode Rx:V8 byte
		if self.has_imm():
			ry = RegExpr("DPC")
		else:
			ry = self.v8
		ret.append(p8((self.rx.num << 4) | ry.num))
		
		# Encode Imm8 byte (if present)
		if self.has_imm():
			# Truncate value if too large
			immval = self.v8.value(context) & 0xFF
			ret.append(p8(immval))
		return b"".join(ret)

class InsnBRA(InsnRxy):
	OPCODE = 0x14
	
	def got_operands(self):
		self.operands["Rx"] = self.operands.get("Rx", RegExpr("DPC"))
		InsnRxy.got_operands(self)

class InsnBRR(Instruction):
	OPCODE = 0x15
	
	def got_operands(self):
		if "Imm16" in self.operands:
			self.imm16 = NumExpr(self.operands["Imm16"])
		else:
			label = self.operands["Label"]
			
			# Target is label - @PC@
			curpc = LabelExpr("PC@")
			self.imm16 = SubExpr(label, curpc)
		
		Instruction.got_operands(self)
	
	def __len__(self):
		# Register prefixes, opcode byte, Imm16
		return len(self.get_prefix_bytes()) + 1 + 2
	
	def assemble(self, context):
		# Encode prefixes
		ret = self.get_prefix_bytes()
		
		# Encode opcode byte
		ret.append(self.make_opbyte())
		
		# Encode Imm16
		ret.append(p16(self.imm16.value(context)))
		return b"".join(ret)

class InsnFCA(InsnBRA):
	OPCODE = 0x16

class InsnFCR(InsnBRR):
	OPCODE = 0x17

class InsnRDB(Instruction):
	OPCODE = 0x18
	
	def got_operands(self):
		self.rx = self.operands["Rx"]
		self.port = self.operands.get("Port", NumExpr(0))
	
	def __len__(self):
		# Register prefixes, opcode byte, Rx:Port byte
		return len(self.get_prefix_bytes()) + 1 + 1
	
	def assemble(self, context):
		# Encode prefixes
		ret = self.get_prefix_bytes()
		
		# Encode opcode byte
		ret.append(self.make_opbyte())
		
		# Encode Rx:Port byte
		port = self.port.value(context)
		if port < 0 or port >= 16:
			raise RangeError("Port number in RDB out of range 0-15: %d" % port)
		ret.append(p8((self.rx.num << 4) | port))
		return b"".join(ret)

class InsnWRB(Instruction):
	OPCODE = 0x19
	
	def got_operands(self):
		self.port = self.operands.get("Port", NumExpr(0))
		self.v8 = self.operands["V8"]
	
	def has_imm(self):
		return not isinstance(self.v8, RegExpr)
	
	def __len__(self):
		# Register prefixes, opcode byte, Port:V8 byte
		size = len(self.get_prefix_bytes()) + 1 + 1
		if self.has_imm():
			# Imm8
			size += 1
		return size
	
	def assemble(self, context):
		# Encode prefixes
		ret = self.get_prefix_bytes()
		
		# Encode opcode byte
		ret.append(self.make_opbyte())
		
		# Encode Port:Ry byte
		port = self.port.value(context)
		if port < 0 or port >= 16:
			raise RangeError("Port number in RDB out of range [0, 15]: %d" % port)
		if self.has_imm():
			ry = RegExpr("DPC")
		else:
			ry = self.v8
		ret.append(p8((port << 4) | ry.num))
		
		# Encode Imm8 (if present)
		if self.has_imm():
			ret.append(p8(self.v8.value(context)))
		return b"".join(ret)

class InsnRegset(Instruction):
	def got_operands(self):
		self.regset = self.operands["Regs16"]
		
		if "Rd" in self.operands:
			self.rd = self.operands["Rd"]
			self.prefixes.append(p8(PREFIX_DR(self.rd)))
	
	def get_regs16(self):
		regs16 = 0
		for r in self.regset:
			regs16 |= 1 << r.num
		return regs16
	
	def __len__(self):
		# Register prefixes, opcode byte, Regs16
		return len(self.get_prefix_bytes()) + 1 + 2
	
	def assemble(self, context):
		# Encode prefixes
		ret = self.get_prefix_bytes()
		
		# Encode opcode byte
		ret.append(self.make_opbyte())
		
		# Encode Regs16
		ret.append(p16(self.get_regs16()))
		return b"".join(ret)

class InsnPSH(InsnRegset):
	OPCODE = 0x1A

class InsnPOP(InsnRegset):
	OPCODE = 0x1B

class InsnINC(Instruction):
	OPCODE = 0x1C
	
	def got_operands(self):
		self.rx = self.operands["Rx"]
		self.simm4 = self.operands.get("SImm4", NumExpr(1))
		
		if "Rd" in self.operands:
			self.rd = self.operands["Rd"]
			
			# DR prefix byte needed?
			if self.rd != self.rx:
				self.prefixes.append(p8(PREFIX_DR(self.rd)))
		else:
			self.rd = None
	
	def __len__(self):
		# Register prefixes, opcode byte, Rx:SImm4 byte
		return len(self.get_prefix_bytes()) + 1 + 1
	
	def assemble(self, context):
		# Encode prefixes
		ret = self.get_prefix_bytes()
		
		# Encode opcode byte
		ret.append(self.make_opbyte())
		
		# Compute value of SImm4 constexpr
		val = self.simm4.value(context)
		
		# Check range of SImm4
		if val < -8 or val > 8:
			raise RangeError("SImm4 value in INC out of range [-8, 8]: %d" % val)
		
		# INC Rx, 0 cannot be encoded
		if val == 0:
			nop = InsnNOP().assemble(context)
			assert(len(nop) == 1)
			
			if self.rd is None:
				# Encode "INC Rx, 0" as NOPs
				return nop * len(self)
			else:
				# Encode "INC Rd, Rx, 0" as "MOV Rd, Rx" padded with trailing NOPs as necessary
				mov = InsnMOV(self.cc)
				mov.set_operands(Rx=self.rd, Vy=self.rx)
				return mov.assemble(context).ljust(len(self), nop)
		
		# Encode Rx:SImm4 byte
		if val > 0:
			val -= 1
		ret.append(p8((self.rx.num << 4) | (val & 0x0F)))
		return b"".join(ret)

class InsnBare(Instruction):
	def __len__(self):
		# Register prefixes, opcode byte
		return len(self.get_prefix_bytes()) + 1
	
	def assemble(self, context):
		# Encode prefixes
		ret = self.get_prefix_bytes()
		
		# Encode opcode byte
		ret.append(self.make_opbyte())
		return b"".join(ret)

class InsnBPT(InsnBare):
	OPCODE = 0x1D

class InsnHLT(InsnBare):
	OPCODE = 0x1E

class InsnNOP(InsnBare):
	OPCODE = 0x1F

class PseudoInstruction(Instruction):
	def __init__(self, cc="AL", toggle_flags=False):
		Instruction.__init__(self, cc, toggle_flags)
		self.insns = None
	
	def get_instructions(self):
		if self.insns is None:
			self.insns = self.real_instructions()
		return self.insns
	
	def __len__(self):
		return sum(len(insn) for insn in self.get_instructions())
	
	def assemble(self, context):
		# Encode each real instruction and concatenate their encoded results
		return b"".join(insn.assemble(context) for insn in self.get_instructions())

class InsnRET(PseudoInstruction):
	def real_instructions(self):
		# Replace "RET.cc" with "BRA.cc RD, RA"
		return [InsnBRA(self.cc).set_operands(Rx=RegExpr("RD"), Vy=RegExpr("RA"))]

class InsnDEC(PseudoInstruction):
	def got_operands(self):
		# Replace "DEC.cc Rx, value" with "INC.cc Rx, -value"
		self.operands["SImm4"] = NegExpr(self.operands.get("SImm4", NumExpr(1)))
	
	def real_instructions(self):
		return [InsnINC(self.cc, self.toggle_flags).set_operands(**self.operands)]

class InsnNEG(PseudoInstruction):
	def got_operands(self):
		self.rx = self.operands["Rx"]
	
	def real_instructions(self):
		# Replace "NEG.cc Rx" with "SUB.cc Rx, ZERO, Rx"
		return [
			InsnSUB(self.cc, self.toggle_flags)
			.set_operands(Rd=self.rx, Rx=RegExpr("ZERO"), Vy=self.rx)
		]

class InsnINV(PseudoInstruction):
	def got_operands(self):
		self.rx = self.operands["Rx"]
	
	def real_instructions(self):
		# Replace "INV.cc Rx" with "XOR.cc Rx, -1"
		return [InsnXOR(self.cc, self.toggle_flags).set_operands(Rx=self.rx, Vy=NumExpr(-1))]

class InsnADR(PseudoInstruction):
	def got_operands(self):
		self.rx = self.operands["Rx"]
		self.target = self.operands["Label"]
	
	def __len__(self):
		# Length of this instruction: optional XC byte, DR byte, opcode byte, regpair byte, Imm16
		return len(self.get_prefix_bytes()) + 1 + 1 + 1 + 2
	
	def real_instructions(self):
		# Length of this instruction: optional XC byte, DR byte, opcode byte, regpair byte, Imm16
		curlen = NumExpr(len(self.get_prefix_bytes()) + 1 + 1 + 1 + 2)
		
		# Replace "ADR.cc Rx, target" with "ADD.cc Rx, PC, target - @PC@"
		curpc = LabelExpr("PC@")
		vy = SubExpr(self.target, curpc)
		return [
			InsnADD(self.cc, self.toggle_flags)
			.set_operands(Rd=self.rx, Rx=RegExpr("PC"), Vy=vy)
		]

class InsnSWP(PseudoInstruction):
	def got_operands(self):
		self.ra = self.operands["Ra"]
		self.rb = self.operands["Rb"]
	
	def real_instructions(self):
		# Replace "SWP.cc Ra, Rb" with "XOR.cc Ra, Rb; XOR.cc Rb, Ra; XOR.cc Ra, Rb"
		kwargs = {}
		if self.cc != "AL":
			kwargs["write_flags"] = False
		
		xor_ab1 = InsnXOR(self.cc, **kwargs).set_operands(Rx=self.ra, Vy=self.rb)
		xor_ba = InsnXOR(self.cc, **kwargs).set_operands(Rx=self.rb, Vy=self.ra)
		xor_ab2 = InsnXOR(self.cc, self.toggle_flags).set_operands(Rx=self.ra, Vy=self.rb)
		return [xor_ab1, xor_ba, xor_ab2]

class PseudoAddOrSubWithCarry(PseudoInstruction):
	def __init__(self, cc="AL", toggle_flags=False, is_add=None):
		PseudoInstruction.__init__(self, cc, toggle_flags)
		self.is_add = is_add
	
	def got_operands(self):
		self.ra = self.operands["Rx"]
		self.vb = self.operands["Vy"]
		self.rd = self.operands.get("Rd", self.ra)
	
	def real_instructions(self):
		incdec = InsnINC if self.IS_ADD else InsnDEC
		addsub = InsnADD if self.IS_ADD else InsnSUB
		
		if self.ra == self.rd:
			"""
			ADC Ra, Vb:
				INC.CS Ra
				ADD Ra, Vb
			
			ADC.cc Ra, Vb:
				BRR.!cc @.after
				ADC Ra, Vb
				
			@.after:
			"""
			incdec_cs = incdec("CS").set_operands(Rx=self.ra)
			addsub_ab = addsub().set_operands(Rx=self.ra, Vy=self.vb)
			
			ret = []
			if self.cc != "AL":
				jump_amount = len(incdec_cs) + len(addsub_ab)
				brr_ncc = InsnBRR(CONDITION_INVERSES[self.cc]).set_operands(Imm16=jump_amount)
				ret.append(brr_ncc)
			
			ret.append(incdec_cs)
			ret.append(addsub_ab)
			return ret
		else:
			"""
			ADC Rd, Ra, Vb:
				INC.CS Rd, ZERO, 1
				ADD Rd, Ra
				ADD Rd, Vb
			
			SBC Rd, Ra, Vb:
				DEC.CS Rd, ZERO, 1
				ADD Rd, Ra
				SUB Rd, Vb
			
			ADC.cc Rd, Ra, Vb:
				BRR.!cc PC, @.after - @PC@
				ADC Rd, Ra, Vb
			
			@.after:
			"""
			incdec_cs = incdec("CS").set_operands(Rd=self.rd, Rx=RegExpr("ZERO"))
			add_da = InsnADD().set_operands(Rx=self.rd, Vy=self.ra)
			addsub_db = addsub().set_operands(Rx=self.rd, Vy=self.vb)
			
			ret = []
			if self.cc != "AL":
				jump_amount = len(incdec_cs) + len(add_da) + len(addsub_db)
				brr_after = InsnBRR(CONDITION_INVERSES[self.cc]).set_operands(Imm16=jump_amount)
				ret.append(brr_after)
			
			ret.append(incdec_cs)
			ret.append(add_da)
			ret.append(addsub_db)
			return ret

class InsnADC(PseudoAddOrSubWithCarry):
	IS_ADD = True

class InsnSBC(PseudoAddOrSubWithCarry):
	IS_ADD = False


INSNS = [
	# Real instructions
	InsnADD, InsnSUB, InsnMLU, InsnMLS, InsnDVU, InsnDVS, InsnXOR, InsnAND,
	InsnORR, InsnSHL, InsnSRU, InsnSRS, InsnMOV, InsnCMP,
	InsnLDW, InsnSTW, InsnLDB, InsnSTB, InsnBRA, InsnBRR, InsnFCA, InsnFCR,
	InsnRDB, InsnWRB, InsnPSH, InsnPOP, InsnINC, InsnBPT, InsnHLT, InsnNOP,
	
	# Pseudo instructions
	InsnRET, InsnDEC, InsnNEG, InsnINV, InsnADR, InsnSWP, InsnADC, InsnSBC
]

INSN_MAP = {
	x.__name__[4:]: x for x in INSNS
}
# DR prefix byte needed? # Register prefixes, opcode byte, regpair byte # Imm16 # Encode prefixes # Encode opcode byte # Encode regpair byte # Encode Imm16 (if present) # Register prefixes, opcode byte, regpair byte # Imm8 # Encode prefixes # Encode opcode byte # Encode Rx:V8 byte # Encode Imm8 byte (if present) # Truncate value if too large # Target is label - @PC@ # Register prefixes, opcode byte, Imm16 # Encode prefixes # Encode opcode byte # Encode Imm16 # Register prefixes, opcode byte, Rx:Port byte # Encode prefixes # Encode opcode byte # Encode Rx:Port byte # Register prefixes, opcode byte, Port:V8 byte # Imm8 # Encode prefixes # Encode opcode byte # Encode Port:Ry byte # Encode Imm8 (if present) # Register prefixes, opcode byte, Regs16 # Encode prefixes # Encode opcode byte # Encode Regs16 # DR prefix byte needed? # Register prefixes, opcode byte, Rx:SImm4 byte # Encode prefixes # Encode opcode byte # Compute value of SImm4 constexpr # Check range of SImm4 # INC Rx, 0 cannot be encoded # Encode "INC Rx, 0" as NOPs # Encode "INC Rd, Rx, 0" as "MOV Rd, Rx" padded with trailing NOPs as necessary # Encode Rx:SImm4 byte # Register prefixes, opcode byte # Encode prefixes # Encode opcode byte # Encode each real instruction and concatenate their encoded results # Replace "RET.cc" with "BRA.cc RD, RA" # Replace "DEC.cc Rx, value" with "INC.cc Rx, -value" # Replace "NEG.cc Rx" with "SUB.cc Rx, ZERO, Rx" # Replace "INV.cc Rx" with "XOR.cc Rx, -1" # Length of this instruction: optional XC byte, DR byte, opcode byte, regpair byte, Imm16 # Length of this instruction: optional XC byte, DR byte, opcode byte, regpair byte, Imm16 # Replace "ADR.cc Rx, target" with "ADD.cc Rx, PC, target - @PC@" # Replace "SWP.cc Ra, Rb" with "XOR.cc Ra, Rb; XOR.cc Rb, Ra; XOR.cc Ra, Rb" ADC Ra, Vb: 				INC.CS Ra 				ADD Ra, Vb 			 			ADC.cc Ra, Vb: 				BRR.!cc @.after 				ADC Ra, Vb 				 			@.after: ADC Rd, Ra, Vb: 				INC.CS Rd, ZERO, 1 				ADD Rd, Ra 				ADD Rd, Vb 			 			SBC Rd, Ra, Vb: 				DEC.CS Rd, ZERO, 1 				ADD Rd, Ra 				SUB Rd, Vb 			 			ADC.cc Rd, Ra, Vb: 				BRR.!cc PC, @.after - @PC@ 				ADC Rd, Ra, Vb 			 			@.after: # Real instructions # Pseudo instructions