Source code for easier68k.core.models.list_file

import json
import re

from ..enum.srecordtype import SRecordType

"""
List File

Represents the output from the assembler that contains all of the instructions and where in the
destination memory they should end up.
"""
MAX_MEMORY_LOCATION = 16777216  # 2^24

[docs]class ListFile: """ Represents assembled instructions and their locations in memory """ def __init__(self): """ Constructor """ # the keys of data must be strings so that they will work with JSON # but when working with it, integers are a lot cleaner and make more sense # so all of the interfaces that work with it are going to use ints # but internally it will use strings self.data = {} self.symbols = {} self.starting_execution_address = 0
[docs] def set_starting_execution_address(self, location: int): """ Sets the starting execution address :param location: :return: """ assert 0 <= location <= MAX_MEMORY_LOCATION, 'The starting execution address must be within the bounds [0, 2^24]!' self.starting_execution_address = location
[docs] def get_starting_execution_address(self): """ Gets the starting execution address :return: """ return self.starting_execution_address
[docs] def insert_data(self, location: int, data: str): """ Inserts the data at the given location into the list file This data should be a string of hexadecimal data :param location: :param data: :return: """ assert location >= 0, 'Location is invalid!' assert location < MAX_MEMORY_LOCATION, 'Location is beyond possible bounds!' # ensure that the data is valid # loop through every 2 or 1 characters for s in re.findall('..?', data): # try to convert to an int of base 16 # just to ensure that it is valid int(data, 16) self.data[str(location)] = data
[docs] def insert_data_at_symbol(self, name: str, data: str): """ Inserts the data at the location for the given symbol :param name: :param data: :return: """ self.insert_data(self.get_symbol_location(name), data)
[docs] def clear_location(self, location: int): """ Clears the data at the given location :param location: :return: """ assert location >= 0, 'Location is invalid!' assert location < MAX_MEMORY_LOCATION, 'Location is beyond possible bounds!' assert str(location) in self.data, 'Location not defined in data!' self.data.pop(str(location), None)
[docs] def define_symbol(self, name: str, location: int): """ Defines a label and it's associated location :param name: :param location: :return: """ assert location >= 0, 'Location is invalid!' assert location < MAX_MEMORY_LOCATION, 'Location is beyond possible bounds!' # check that the symbol name is a single word assert re.match(r'^(([A-z])+([A-z]*[0-9]*))\w$', name), 'Symbol name was not a single word!' self.symbols[name] = location
[docs] def clear_symbol(self, name: str): """ Clears a label :param name: :return: """ self.symbols.pop(name, None)
[docs] def get_symbol_location(self, name: str) -> int: """ Gets the associated location for a label :param name: :return: the location associated to the label, if it exists """ assert name in self.symbols return self.symbols[name]
[docs] def get_symbol_data(self, name: str) -> str: """ Get the data for the given label Only works for the start of data This is not for reading in the middle of a set of data :param name: :return: """ assert name in self.symbols, 'Symbol key was not in the labels dictionary' return self.get_starting_data(self.get_symbol_location(name))
[docs] def get_starting_data(self, location: int) -> int: """ Gets the data starting at the given location :param location: :return: """ assert location >= 0, 'Location is invalid!' assert location < MAX_MEMORY_LOCATION, 'Location is beyond possible bounds!' assert str(location) in self.data, 'Location data not defined!' return self.data[str(location)]
[docs] def to_json(self) -> str: """ Dumps the current object into a JSON string :return: """ ret = {} ret['data'] = self.data ret['symbols'] = self.symbols ret['startingExecutionAddress'] = self.starting_execution_address return json.dumps(ret, sort_keys=True)
[docs] def load_from_json(self, json_str: str): """ Populates this object from a json str :param json_str: :return: """ loaded = json.loads(json_str) self.symbols = loaded['symbols'] self.data = loaded['data'] self.starting_execution_address = loaded['startingExecutionAddress']
[docs] def read_s_record_filename(self, filepath: str): """ Read the S record at the given file path, builds the content of this list file from it :param filepath: {str} Path to an S record :return: None """ with open(filepath, 'r') as f: for line in f: # process the line in the file self.__process_s_record_line(line)
def __process_s_record_line(self, line: str): """ Process a single line of the S record This is defined here: http://www.easy68k.com/easy68ksrecord.htm :param line: {str} a single line of an S record file :return: None """ line = line.replace('\r', '').replace('\n', '') # type of record record_type = SRecordType.parse(line[:2]) # count of remaining character pairs in the record count = int(line[2:4], 16) # address 2 3 or 4 bytes as hex address_val_len = 0 if record_type is SRecordType.S0: # address field is unused address_val_len = 4 elif record_type is SRecordType.S1: # 2 bytes address_val_len = 4 elif record_type is SRecordType.S2: # 3 bytes address_val_len = 6 elif record_type is SRecordType.S3: # 4 bytes address_val_len = 8 elif record_type is SRecordType.S5: # the address field is interpreted as a 2 byte value # and contains the counts of S1 2 and 3 records prev # transmitted address_val_len = 4 elif record_type is SRecordType.S7: # termination record # contains the starting execution address # as 4 bytes address_val_len = 8 elif record_type is SRecordType.S8: # termination record # contains the starting execution address # as 3 bytes address_val_len = 6 elif record_type is SRecordType.S9: # termination record # contains the starting execution address # as 2 bytes address_val_len = 4 # get the address for the data address = int(line[4:4+address_val_len], 16) # get the data that should be between 0-64 characters data = line[4+address_val_len:-2] # the last two characters as a hexadecimal value # with the least significant byte of the ones complement of the sum # of the byte values represented by the pairs of characters # making up the count, address and data pairs # for now, I don't care about the checksum checksum = line[-2:] if record_type in [SRecordType.S1, SRecordType.S2, SRecordType.S3]: self.insert_data(address, data) if record_type in [SRecordType.S7, SRecordType.S8, SRecordType.S9]: self.starting_execution_address = address def __eq__(self, other) -> bool: """ Equals operator :param other: :return: """ return self.symbols == other.symbols and self.data == other.data and self.starting_execution_address == other.starting_execution_address def __ne__(self, other) -> bool: """ Not equals operator :param other: :return: """ return self.symbols != other.symbols or self.data != other.data