Source code for eight_bit_computer.assembly_validity

"""
Validity checks on the processed assembly lines
"""

from .exceptions import AssemblyError

ERROR_TEMPLATE = "Error processing line {line_no} ({line}): {details}"


[docs]def check_structure_validity(asm_line_infos, variable_start_offset): """ Check the processed assembly lines for consistency/correctness. Args: asm_line_infos (list(dict)): List of dictionaries (conforming to :func:`~.get_assembly_line_template`) with information about all the lines in the assembly file. """ check_multiple_label_defs(asm_line_infos) check_multiple_label_assignment(asm_line_infos) check_undefined_label_ref(asm_line_infos) check_multiple_variable_def(asm_line_infos) check_num_variables(asm_line_infos, variable_start_offset) check_num_instruction_bytes(asm_line_infos)
[docs]def check_multiple_label_defs(asm_line_infos): """ Check if the same label been defined more than once. Args: asm_line_infos (list(dict)): List of dictionaries (conforming to :func:`~.get_assembly_line_template`) with information about all the lines in the assembly file. Raises: AssemblyError: If the same label been defined more than once. """ labels = set() label_lines = {} for asm_line_info in asm_line_infos: if asm_line_info["defines_label"]: label = asm_line_info["defined_label"] if label in labels: details = ( "The label: \"{label}\" has already been defined on " "line {prev_line}.".format( line_no=asm_line_info["line_no"], label=label, prev_line=label_lines[label], ) ) msg = ERROR_TEMPLATE.format( line_no=asm_line_info["line_no"], line=asm_line_info["raw"], details=details, ) raise AssemblyError(msg) else: labels.add(label) label_lines[label] = asm_line_info["line_no"]
[docs]def check_multiple_label_assignment(asm_line_infos): """ Check if a single line been assigned more than one label. Args: asm_line_infos (list(dict)): List of dictionaries (conforming to :func:`~.get_assembly_line_template`) with information about all the lines in the assembly file. Raises: AssemblyError: If a single line been assigned more than one label. """ label_queued = False last_label = "" for asm_line_info in asm_line_infos: if label_queued and asm_line_info["defines_label"]: details = ( "There is already a label ({label}) queued for " "assignment to the next instruction.".format( label=last_label ) ) msg = ERROR_TEMPLATE.format( line_no=asm_line_info["line_no"], line=asm_line_info["raw"], details=details, ) raise AssemblyError(msg) if asm_line_info["defines_label"]: label_queued = True last_label = asm_line_info["defined_label"] if asm_line_info["has_machine_code"] and label_queued: label_queued = False
[docs]def check_undefined_label_ref(asm_line_infos): """ Check if an operation is using a label that hasn't been defined. Args: asm_line_infos (list(dict)): List of dictionaries (conforming to :func:`~.get_assembly_line_template`) with information about all the lines in the assembly file. Raises: AssemblyError: If an operation is using a label that hasn't been defined. """ # Gather labels labels = [] for asm_line_info in asm_line_infos: if asm_line_info["defines_label"]: labels.append(asm_line_info["defined_label"]) for asm_line_info in asm_line_infos: if asm_line_info["has_machine_code"]: for mc_byte_info in asm_line_info["mc_bytes"]: if (mc_byte_info["byte_type"] == "constant" and mc_byte_info["constant_type"] == "label" and mc_byte_info["constant"] not in labels): details = ( "This line is referencing a label ({label}) " "that has not been defined.".format( label=mc_byte_info["constant"] ) ) msg = ERROR_TEMPLATE.format( line_no=asm_line_info["line_no"], line=asm_line_info["raw"], details=details, ) raise AssemblyError(msg)
[docs]def check_multiple_variable_def(asm_line_infos): """ Has the same variable been defined multiple times. Args: asm_line_infos (list(dict)): List of dictionaries (conforming to :func:`~.get_assembly_line_template`) with information about all the lines in the assembly file. Raises: AssemblyError: If a variable has been defined more than once. """ variables = set() variable_lines = {} for asm_line_info in asm_line_infos: if asm_line_info["defines_variable"]: variable = asm_line_info["defined_variable"] if variable in variables: details = ( "The variable: \"{variable}\" has already been defined on " "line {prev_line}.".format( variable=variable, prev_line=variable_lines[variable], ) ) msg = ERROR_TEMPLATE.format( line_no=asm_line_info["line_no"], line=asm_line_info["raw"], details=details, ) raise AssemblyError(msg) else: variables.add(variable) variable_lines[variable] = asm_line_info["line_no"]
[docs]def check_num_variables(asm_line_infos, variable_start_offset): """ Check there are more variables defined than will fit in data mem. There are 255 bytes of data memory available and the start offset may eat into this. Args: asm_line_infos (list(dict)): List of dictionaries (conforming to :func:`~.get_assembly_line_template`) with information about all the lines in the assembly file. variable_start_offset (int): How far in memory to offset when defining the first variable. Raises: AssemblyError: If there are more variables defined than will fit in data memory. """ variables = [] for asm_line_info in asm_line_infos: # Check for defined variable if asm_line_info["defines_variable"]: variable = asm_line_info["defined_variable"] if variable not in variables: variables.append(asm_line_info["defined_variable"]) if len(variables) + variable_start_offset > 255: break # Check for used variables if asm_line_info["has_machine_code"]: for mc_byte_info in asm_line_info["mc_bytes"]: if (mc_byte_info["byte_type"] == "constant" and mc_byte_info["constant_type"] == "variable" and mc_byte_info["constant"] not in variables): variables.append(mc_byte_info["constant"]) if len(variables) + variable_start_offset > 255: break if len(variables) + variable_start_offset > 255: details = ( "No more data memory is available for the declaration of " "variable: \"{variable}\". The max is 255 and a variable " "start offset of {variable_start_offset} was used.".format( variable=variable, variable_start_offset=variable_start_offset, ) ) msg = ERROR_TEMPLATE.format( line_no=asm_line_info["line_no"], line=asm_line_info["raw"], details=details, ) raise AssemblyError(msg)
[docs]def check_num_instruction_bytes(assembly_lines): """ Check there aren't too many instruction_bytes. Args: asm_line_infos (list(dict)): List of dictionaries (conforming to :func:`~.get_assembly_line_template`) with information about all the lines in the assembly file. Raises: AssemblyError: If there are more instruction bytes than will fit in program memory. """ bad_line_no = -1 num_mc_bytes = 0 for assembly_line in assembly_lines: if assembly_line["has_machine_code"]: for mc_byte in assembly_line["mc_bytes"]: num_mc_bytes += 1 if num_mc_bytes > 255 and bad_line_no < 0: bad_line_no = assembly_line["line_no"] if num_mc_bytes > 255: details = ( "This operation brought the total number of machine " "code bytes above 255. The complete assembly code has " "resulted in {num_mc_bytes} bytes of machine code.".format( num_mc_bytes=num_mc_bytes, ) ) msg = ERROR_TEMPLATE.format( line_no=bad_line_no, line=assembly_line["raw"], details=details, ) raise AssemblyError(msg)