"""
Common functions for operations.
"""
import re
from copy import deepcopy
from .data_structures import DataTemplate
from .exceptions import OperationParsingError
from .language_defs import MODULE_CONTROL, STEPS
from . import token_utils
from . import bitdef
[docs]def assemble_instruction(instruction_bitdefs, flags_bitdefs, control_steps):
"""
Create templates for all steps to form a complete instruction.
Args:
instruction_bitdefs (list(str)): List of the bitdefs that make
up the instruction byte.
flags_bitdefs: list(str): List of the bitdefs that make up the
flags for this instruction.
control_steps: list(list(str): List of list of bitdefs that
make up the control signals for each step.
Returns:
list(DataTemplate): All the steps for this instruction.
Raises:
ValueError: If too many steps were provided.
"""
num_steps = len(control_steps)
if num_steps > 6:
msg = (
"{num_steps} control steps were passed, "
"the maxiumum is 6.".format(num_steps=num_steps)
)
raise ValueError(msg)
templates = []
instruction_bitdef = bitdef.merge(instruction_bitdefs)
flags_bitdef = bitdef.merge(flags_bitdefs)
for index, current_step_controls in enumerate(control_steps, start=2):
step_bitdef = STEPS[index]
address_bitdef = bitdef.merge(
[
instruction_bitdef,
flags_bitdef,
step_bitdef
]
)
# If this is the last step, add a step reset
if index == num_steps + 1:
current_step_controls.append(MODULE_CONTROL["CU"]["STEP_RESET"])
control_bitdef = bitdef.merge(current_step_controls)
control_bitdef = bitdef.fill(control_bitdef, "0")
template = DataTemplate(
address_range=address_bitdef, data=control_bitdef
)
templates.append(template)
return templates
[docs]def add_quotes_to_strings(strings):
"""
Add double quotes strings in a list then join with commas.
Args:
strings (list(str)): List of strings to add parentheses to.
Returns:
str: The strings with quotes added and joined with commas.
"""
quote_strings = []
for _string in strings:
quote_strings.append("\"{string}\"".format(string=_string))
pretty_strings = ", ".join(quote_strings)
return pretty_strings
[docs]def match_and_parse_line(line, opcode, signatures=None):
"""
Examine assembly code to see if it is valid and parse the arguments.
This is a common function used by most of the assembly operations.
Args:
line (str): The line of assembly code.
opcode (str): The opcode this line is being tested to match.
signatures (list(list(dict)), optional): Data structure that
defines the different combinations of arguments. See
:func:`~.get_arg_def_template` for more details.
Returns:
(bool, list(dict)): Whether or not the line matched, and if it
did, the parsed arguments.
Raises:
OperationParsingError: If multiple op_args defs matched. Or
if no op_args defs matched if the opcode matched (i.e. the
arguments weren't valid for that assembly operation).
"""
if signatures is None:
signatures = []
line_tokens = token_utils.get_tokens_from_line(line)
# Return early if there are no tokens
if not line_tokens:
return False, []
# Return early if the opcode doesn't match
line_opcode = line_tokens[0]
if line_opcode != opcode:
return False, []
# Return early if this op code has no args
line_args = line_tokens[1:]
if not line_args and not signatures:
return True, []
match = False
for signature in signatures:
args_are_correct, parsed_args = match_and_parse_args(
line_args, signature
)
if args_are_correct:
if match:
msg = (
"Args matched multiple arg possibilities."
)
raise OperationParsingError(msg)
else:
match = True
ret_args = parsed_args
if not match:
poss_args_list = generate_possible_signatures_list(signatures)
poss_args_quotes_list = [
add_quotes_to_strings(poss_args) for poss_args in poss_args_list
]
pretty_possible_args = "\n".join(poss_args_quotes_list)
msg = (
"Incorrect arguments specified for the {opcode} "
"operation:\n\n{pretty_args}\n\nThe possible arguments "
"are:\n\n{pretty_possible_args}.".format(
opcode=opcode,
pretty_args=add_quotes_to_strings(line_args),
pretty_possible_args=pretty_possible_args,
)
)
raise OperationParsingError(msg)
return True, ret_args
[docs]def generate_possible_signatures_list(signatures):
"""
Create a readable list of all possible signatures.
Args:
signatures (list(list(dict))): Data structure that defines
the different combinations of arguments. See
:func:`~.get_arg_def_template` for more details.
Returns:
list(str): All possible argument combinations.
"""
arg_possibilities = []
for signature in signatures:
args = []
for arg_def in signature:
arg = ""
if arg_def["value_type"] == "module_name":
arg = arg_def["value"]
if arg_def["value_type"] == "constant":
arg = "<constant>"
if arg_def["is_memory_location"]:
arg = token_utils.represent_as_memory_index(arg)
args.append(arg)
arg_possibilities.append(args)
return arg_possibilities
[docs]def match_and_parse_args(line_args, signature):
"""
Parse assembly operation args if they match the definition.
Take arguments supplied for the assembly operation and see if they
match this arguments definition.
Args:
line_args: (list(str)): The arguments supplied for this assembly
operation.
signature (list(dict)): Definition of a set of arguments. See
:func:`~.get_arg_def_template` for more details.
Returns:
(bool, list(dict)): Whether or not the arguments matched, and if
they did, the parsed values.
Raises:
OperationParsingError: If a single argument managed to match
different kinds of argument definitions.
"""
if len(line_args) != len(signature):
return False, []
parsed_args = []
for line_arg, arg_def in zip(line_args, signature):
num_matches = 0
# If the argument is a plain module name
if (arg_def["value_type"] == "module_name"
and not arg_def["is_memory_location"]
and not token_utils.is_memory_index(line_arg)
and line_arg == arg_def["value"]):
parsed_arg = deepcopy(arg_def)
parsed_args.append(parsed_arg)
num_matches += 1
# If the argument is a module name indexing memory
if (arg_def["value_type"] == "module_name"
and arg_def["is_memory_location"]
and token_utils.is_memory_index(line_arg)):
memory_position = token_utils.extract_memory_position(line_arg)
if memory_position == arg_def["value"]:
parsed_arg = deepcopy(arg_def)
parsed_args.append(parsed_arg)
num_matches += 1
# If the argument is a plain constant
if (arg_def["value_type"] == "constant"
and not arg_def["is_memory_location"]
and not token_utils.is_memory_index(line_arg)
and token_utils.is_constant(line_arg)):
parsed_arg = deepcopy(arg_def)
parsed_arg["value"] = line_arg
parsed_args.append(parsed_arg)
num_matches += 1
# If the argument is a constant indexing memory
if (arg_def["value_type"] == "constant"
and arg_def["is_memory_location"]
and token_utils.is_memory_index(line_arg)):
memory_position = token_utils.extract_memory_position(line_arg)
if token_utils.is_constant(memory_position):
parsed_arg = deepcopy(arg_def)
parsed_arg["value"] = memory_position
parsed_args.append(parsed_arg)
num_matches += 1
# If this argument didn't match, then these args don't match the
# defs
if num_matches == 0:
return False, []
# If there was more than one match something strange has
# happened, bail.
if num_matches > 1:
msg = (
"The argument '{line_arg} matched more than one "
"argument type".format(line_arg=line_arg)
)
raise OperationParsingError(msg)
# If the for loop completes successfully, we've matched all the
# args.
return True, parsed_args