Source code for easier68k.core.opcodes.dc

from ...core.opcodes.opcode import Opcode
from ...core.enum.op_size import OpSize
from ...core.util import opcode_util
from ...simulator.m68k import M68K
from ..util.parsing import parse_literal
from itertools import zip_longest
import math


[docs]class DC(Opcode): valid_sizes = [OpSize.BYTE, OpSize.WORD, OpSize.LONG] QUOTE_DELIMETER = "'" def __init__(self, values: list, size=OpSize.WORD): assert size in DC.valid_sizes self.size = size assert len(values) > 0 self.values = values
[docs] def assemble(self) -> bytearray: """ Assembles this opcode into hex to be inserted into memory :return: The hex version of this opcode """ tr = '' for val in self.values: tr += '{0:08b}'.format(val) return bytearray.fromhex(hex(int(tr, 2))[2:]) # Convert to a bytearray
[docs] def execute(self, simulator: M68K): """ Executes this command in a simulator :param simulator: The simulator to execute the command on :return: Nothing """ # DC does not implement execute because it is processed in the # assembly stage and effectively removed pass
def __str__(self): return "DC command: Size {}, items: {}".format(self.size, self.values)
[docs] @classmethod def command_matches(cls, command: str) -> bool: """ Checks whether a command string is an instance of this command type :param command: The command string to check (e.g. 'MOVE.B', 'LEA', etc.) :return: Whether the string is an instance of this command type """ return opcode_util.command_matches(command, 'DC')
[docs] @classmethod def get_word_length(cls, command: str, parameters: str) -> int: """ Gets the final length of a command in memory in words NOTE: for DC.B, this will round UP to make it a full word, even though it won't use the full memory >>> DC.get_word_length('DC.B', '$0A, $0B') 1 >>> DC.get_word_length('DC.W', '$0A, $0B') 2 >>> DC.get_word_length('DC.L', '$0A, $0B') 4 >>> DC.get_word_length('DC.B', '\\'Hai!\\'') 2 >>> DC.get_word_length('DC.W', '\\'Hai!\\'') 2 >>> DC.get_word_length('DC.L', '\\'Hai!\\'') 4 :param command: The command itself (e.g. 'MOVE.B', 'LEA', etc.) :param parameters: The parameters after the command (such as the source and destination of a move) :return: The length of the command in memory (in words) """ valid, issues = cls.is_valid(command, parameters) assert valid, 'Invalid command' # We're good without asserts from here on out parts = command.split('.') # Split the command by period to get the size of the command if len(parts) == 1: # Use the default size size = OpSize.WORD else: size = OpSize.parse(parts[1]) length = 0 in_quote = False current_param = '' is_string = False lookahead_iter = zip_longest(parameters, parameters[1:]) for c, c_next in lookahead_iter: if c == ',' and not in_quote: # End of this parameter (and not a comma in a quote) if current_param: if is_string: length += len(current_param) * 0.5 if len(current_param) % 2 != 0: length += 1 else: if size == OpSize.BYTE: length += 0.5 elif size == OpSize.WORD: length += 1 elif size == OpSize.LONG: length += 2 in_quote = False current_param = '' is_string = False continue if c == DC.QUOTE_DELIMETER: # This is the start or end of a quote (not an escaped apostrophe) if not in_quote: in_quote = True is_string = True continue else: if c_next == DC.QUOTE_DELIMETER: # This is an escaped apostrophe in a string literal current_param += DC.QUOTE_DELIMETER next(lookahead_iter) # Skip the other apostrophe so we don't double-process it continue # This is the end of a quote (not an escaped apostrophe) in_quote = False continue current_param += c if current_param: if is_string: length += len(current_param) * 0.5 if len(current_param) % 2 != 0: length += 1 else: if size == OpSize.BYTE: length += 0.5 elif size == OpSize.WORD: length += 1 elif size == OpSize.LONG: length += 2 length = math.ceil(length) if size == OpSize.LONG and length % 4 != 0: length = length + (length % 4) return length
[docs] @classmethod def is_valid(cls, command: str, parameters: str) -> (bool, list): """ Tests whether the given command is valid >>> DC.is_valid('DC.B', '\\'Hello, world!\\'')[0] True >>> DC.is_valid('DC.B', '\\'Hello\\'\\' world!\\'')[0] True >>> DC.is_valid('DC.W', '\\'Hello\\', \\' world!\\'')[0] True >>> DC.is_valid('DC.W', '$0A, $0B')[0] True >>> DC.is_valid('DC.L', '$0A')[0] True >>> DC.is_valid('DC.B', '')[0] False >>> DC.is_valid('DC.W', '\\'Hey!\\', $0A')[0] True >>> DC.is_valid('DC.G', '$0A')[0] False :param command: The command itself (e.g. 'MOVE.B', 'LEA', etc.) :param parameters: The parameters after the command (such as the source and destination of a move) :return: Whether the given command is valid and a list of issues/warnings encountered """ issues = [] try: assert opcode_util.check_valid_command(command, 'DC', valid_sizes=DC.valid_sizes), 'Command invalid' param_count = 0 in_quote = False current_param = '' is_string = False lookahead_iter = zip_longest(parameters, parameters[1:]) for c, c_next in lookahead_iter: if c == ',' and not in_quote: # End of this parameter (and not a comma in a quote) if current_param: if not is_string: # Try parsing this value for a literal (if it's a string it's almost certainly fine) assert parse_literal(current_param.strip()) is not None, 'Error parsing literal' param_count += 1 in_quote = False current_param = '' is_string = False continue if c == DC.QUOTE_DELIMETER: # This is the start or end of a quote (not an escaped apostrophe) if not in_quote: assert not current_param, 'Expected comma between two string literals' in_quote = True is_string = True continue else: if c_next == DC.QUOTE_DELIMETER: # This is an escaped apostrophe in a string literal current_param += DC.QUOTE_DELIMETER next(lookahead_iter) # Skip the other apostrophe so we don't double-process it continue # This is the end of a quote (not an escaped apostrophe) in_quote = False continue if c != ' ' or (c == ' ' and in_quote): # Don't add spaces unless we're in a quote current_param += c assert not in_quote, 'Expected apostrophe to end quote, got end of line instead' if current_param: if not is_string: # Make the value the right hex length assert parse_literal(current_param.strip()) is not None, 'Error parsing literal' param_count += 1 assert param_count > 0, 'Must have at least one parameter' except AssertionError as e: issues.append((e.args[0], 'ERROR')) return False, issues return True, issues
[docs] @classmethod def from_binary(cls, data: bytearray): return None, 0
[docs] @classmethod def from_str(cls, command: str, parameters: str): """ Parses a command string into an instance of the opcode class >>> test0 = DC.from_str("DC.B", "$0A, $0B") >>> test0.size <OpSize.BYTE: 1> >>> test0.values [10, 11] >>> test1 = DC.from_str("DC.B", "\\'Hai!\\'") >>> test1.size <OpSize.BYTE: 1> >>> test1.values [72, 97, 105, 33] >>> test2 = DC.from_str("DC.L", "\\'Hai\\'") >>> test2.size <OpSize.LONG: 4> >>> test2.values [72, 97, 105, 0] >>> test3 = DC.from_str("DC.L", "\\'Hai\\', $AB") >>> test3.size <OpSize.LONG: 4> >>> test3.values [72, 97, 105, 0, 0, 0, 0, 171] >>> test4 = DC.from_str("DC.W", "\\'Hai\\', $AB") >>> test4.size <OpSize.WORD: 2> >>> test4.values [72, 97, 105, 0, 0, 171] :param command: The command itself (e.g. 'MOVE.B', 'LEA', etc.) :param parameters: The parameters after the command (such as the source and destination of a move) """ valid, issues = cls.is_valid(command, parameters) assert valid, 'Invalid command' # We're good without asserts from here on out parts = command.split('.') # Split the command by period to get the size of the command if len(parts) == 1: # Use the default size size = OpSize.WORD else: size = OpSize.parse(parts[1]) params = [] in_quote = False current_param = '' is_string = False lookahead_iter = zip_longest(parameters, parameters[1:]) for c, c_next in lookahead_iter: if c == ',' and not in_quote: # End of this parameter (and not a comma in a quote) if current_param: if is_string: temp_params = [] for char in current_param: temp_params.append(ord(char)) # Right pad the string with zeroes if size == OpSize.LONG: if len(temp_params) % 4 != 0: temp_params.extend(0 for _ in range(4 - (len(temp_params) % 4))) if size == OpSize.WORD: if len(temp_params) % 2 != 0: temp_params.append(0) params.extend(temp_params) else: # Make the value the right hex length val = parse_literal(current_param.strip()) hexed = hex(val)[2:].upper() if size == OpSize.LONG: if len(hexed) % 8 != 0: hexed = ('0' * (8 - (len(hexed) % 8))) + hexed if size == OpSize.WORD: if len(hexed) % 4 != 0: hexed = ('0' * (4 - (len(hexed) % 4))) + hexed if size == OpSize.BYTE: if len(hexed) % 2 != 0: hexed = '0' + hexed # Length is now for sure even for i in range(0, len(hexed), 2): params.append(int(hexed[i:i+2], 16)) in_quote = False current_param = '' is_string = False continue if c == DC.QUOTE_DELIMETER: # This is the start or end of a quote (not an escaped apostrophe) if not in_quote: in_quote = True is_string = True continue else: if c_next == DC.QUOTE_DELIMETER: # This is an escaped delimeter in a string literal current_param += DC.QUOTE_DELIMETER next(lookahead_iter) # Skip the other delimeter so we don't double-process it continue # This is the end of a quote (not an escaped apostrophe) in_quote = False continue current_param += c if current_param: if is_string: temp_params = [] for char in current_param: temp_params.append(ord(char)) # Right pad the string with zeroes if size == OpSize.LONG: if len(temp_params) % 4 != 0: temp_params.extend(0 for _ in range(4 - (len(temp_params) % 4))) if size == OpSize.WORD: if len(temp_params) % 2 != 0: temp_params.append(0) params.extend(temp_params) else: # Make the value the right hex length val = parse_literal(current_param.strip()) hexed = hex(val)[2:].upper() if size == OpSize.LONG: if len(hexed) % 8 != 0: hexed = ('0' * (8 - (len(hexed) % 8))) + hexed if size == OpSize.WORD: if len(hexed) % 4 != 0: hexed = ('0' * (4 - (len(hexed) % 4))) + hexed if size == OpSize.BYTE: if len(hexed) % 2 != 0: hexed = '0' + hexed # Length is now for sure even for i in range(0, len(hexed), 2): params.append(int(hexed[i:i + 2], 16)) return cls(params, size)
[docs] @classmethod def disassemble_instruction(cls, data: bytes) -> Opcode: """ Disassembles the instuction into an instance of the DC class """ # DC is a directive for the assembler, so it has no representaiton # as bytes pass