Example #1
0
def modulo(c, m):
    """Modulo statement. For instance:
        al %= 6
    """
    dst = Operand(c, m.group(1))
    src = Operand(c, m.group(2))

    if src.value == 0:
        raise ValueError('Cannot perform modulo 0')

    if src.value < 0:
        raise ValueError('Negative modulo not implemented')

    if src.value is not None and is_power_of_2(src.value):
        # AND optimization: modulo is a power of 2
        # We only need to mask with 111… until its power.
        # For instance:
        #   1000 (8) - 1 = 111 (7)
        #
        # So n % 8, e.g., 100101, is as simple as masking with 111 -> 101
        mask = src.value - 1
        c.add_code(f'and {dst}, {mask}')
        return

    perform_divide(c, dst, src, result_in8='ah', result_in16='dx')
Example #2
0
def if_(c, m):
    """If control flow statement. For instance:
        if ax < 5 {
            ; code
        }

        if cx is even {
            ; code
        }
    """
    a = Operand(c, m.group(1))
    comparision = m.group(2)
    b = Operand(c, m.group(3))

    even_odd = m.group(4)
    label = c.get_uid(m.group(5))

    if even_odd is None:
        c.add_code([f'cmp {a}, {b}', f'{ctoi[comparision]} {label}'])
    else:
        # If even, then bit is 0, then jump to skip if not zero
        # If  odd, then bit is 1, then jump to skip if zero
        jump = 'jnz' if even_odd == 'even' else 'jz'
        c.add_code([f'test {a}, 1', f'{jump} {label}'])

    c.add_pending_code(f'{label}:')
Example #3
0
def while_(c, m):
    """While control flow statement. For instance:
        while bx < 4 {
            ; code
        }

        To force at least one iteration
        while dx > 7 or once {
            ; code
        }
    """
    a = Operand(c, m.group(1))
    comparision = m.group(2)
    b = Operand(c, m.group(3))

    label = c.get_uid(m.group(5))
    labelstart = label + '_s'
    labelend = label + '_e'

    if m.group(4) is None:
        # 'or once' is not present, we might not need to enter the loop
        c.add_code([f'cmp {a}, {b}', f'{ctoi[comparision]} {labelend}'])
    c.add_code(f'{labelstart}:')

    # Reenter the loop if condition is met
    c.add_pending_code(
        [f'cmp {a}, {b}', f'{cti[comparision]} {labelstart}', f'{labelend}:'])
Example #4
0
def setcursor(c, m):
    """Sets the cursor on screen. For instance:
        setcursor(8, 20)
    """
    # TODO Possibly add support for inlining
    setc = define_set_cursor(c)

    row, col = Operand.parseint(m.group(1)), Operand.parseint(m.group(2))
    
    # Assertion
    if row is not None and not 0 <= row < 256:
        raise ValueError('The row for setcursor must be between 0 and 255')

    if col is not None and not 0 <= col < 256:
        raise ValueError('The column for setcursor must be between 0 and 255')

    # Assertion passed, save the previous value
    c.add_code(f'push dx')

    # Special case: both integer values known
    if row is not None and col is not None:
        c.add_code(f'mov dx, {(row << 8) | col}')
    else:
        # Need to move manually
        helperassign(c, 'dh', m.group(1))
        helperassign(c, 'dl', m.group(2))

    c.add_code([
        f'call {setc.name}',
        f'pop dx'
    ])
def check_for_arrows(image, equation, ncp, ind):
    x0, y0, h0, w0, x, y, w, h, x1, y1, w1, h1 = get_coords_of_adjacent(
        ncp, ind)

    if math.fabs(x - x0) < 0.4 * w and math.fabs((x0 + w0) -
                                                 (x + w)) < 0.4 * w and y0 < y:
        obj = Operand(x=x,
                      y=y,
                      top=Operand(x=x0,
                                  y=y0,
                                  image=image[y0:y0 + h0, x0:x0 + w0]),
                      image=image[y:y + h, x:x + w])
        equation.add_operand(obj)
        return True
    elif math.fabs(x - x1) < 0.4 * w and math.fabs(
        (x1 + w1) - (x + w)) < 0.4 * w and y1 < y:
        obj = Operand(x=x,
                      y=y,
                      top=Operand(x=x1,
                                  y=y1,
                                  image=image[y1:y1 + h0, x1:x1 + w1]),
                      image=image[y:y + h, x:x + w])
        equation.add_operand(obj)
        return True
    else:
        equation.add_operand(Operand(x=x, y=y, image=image[y:y + h, x:x + w]))
        return False
Example #6
0
def divide(c, m):
    """Division statement. For instance:
        al /= 4
    """
    dst = Operand(c, m.group(1))
    src = Operand(c, m.group(2))


    if src.value == 0:
        raise ValueError('Cannot divide by 0')


    if src.value and abs(src.value) == 1:
        # One optimization: do nothing or negate
        if src.value < 0:
            c.add_code(f'neg {dst}')
        return


    if src.value is not None:
        # Bit shift optimization: divisor is a small multiple of 2
        # http://zsmith.co/intel_d.html#div or
        # http://zsmith.co/intel_n.html#neg and http://zsmith.co/intel_s.html#sar
        if dst.is_reg or dst.size == 8:
            limit = 80
        else:
            limit = 150

        if dst.is_reg:
            if src.value < 0:
                cost = 3 + 8 + 4 * (-src.value)
            else:
                cost = 8 + 4 * src.value

        if dst.is_mem:
            if src.value < 0:
                cost = 16 + 20 + 4 * (-src.value)
            else:
                cost = 20 + 4 * src.value

        # Now we know our limit, beyond which division is easier
        # and the cost per shift, so we know which values we can use
        if cost < limit:
            # The cost of negating and shifting is less, so we'll do that
            amount = limit // cost
            powers = [2**i for i in range(1, amount + 1)]
            if abs(src.value) in powers:
                # We have a power of two, so perform we can use bit shift
                if src.value < 0:
                    c.add_code(f'neg {dst}')
                    src.value = -src.value

                count = int(src.value ** 0.5)
                c.add_code(f'sar {dst}, {count}')
                return

    perform_divide(c, dst, src,
                   result_in8='al',
                   result_in16='ax')
 def __init__(self,
              inputPipe=None,
              config={},
              simulation=False,
              dataManager=None):
     self.traders = []
     self.config = config
     self.input = inputPipe
     self.simulation = simulation
     self.zero = Operand(d=0)
     self.one = Operand(d=1)
     self.dataManager = dataManager
     self.initialize()
Example #8
0
    def get_operands(self, csv):
        """Converts the possibly comma separated values to a
            list of operands (inmediate, registers and variables
        """
        if isinstance(csv, list):
            return [
                v if isinstance(v, Operand) else Operand(self, v) for v in csv
            ]

        if isinstance(csv, Operand):
            return [csv]

        return [Operand(self, v) for v in Operand.get_csv(csv)]
 def get_operation(self, arg):
     if (type(arg) is str):
         #it is an operation
         operation = create_operation(data=json.loads(arg),
                                      dataline=self.dataManager)
         return operation
     elif (type(arg) is float or type(arg) is int):
         #return the integer or float as it is
         return Operand(d=arg)
     elif (type(arg) is list):
         return Operand(d=0)
     elif (arg is None):
         #return 0
         return Operand(d=0)
 def run(self, data):
     d = {
         'price': data['lastPrice'],
         'time': data['time'],
         'token': data['token'],
         'lastPrice': data['lastPrice']  # TODO change to common as price
     }
     if (data.get('isCandle')):
         d.update({
             'open': data['open'],
             'high': data['high'],
             'low': data['low'],
             'close': data['close'],
             'interval': data['interval'],
             'isCandle': data['isCandle']
         })
     #TODO change this as soon as you can !important
     true = Operand(d=True)
     begin = (self.begin_on.update(d) == true).d
     end = (self.end_on.update(d) == true).d
     r = self.update(d)
     if (begin and not end):
         if (self.phase == 'sleeping'):
             self.begin(d)
             self.phase = 'running'
         if (data.get('noTrade')):
             return None
         return r
     if (begin and end and self.phase == 'running'):
         self.end(d)
         self.phase = 'sleeping'
     return None
Example #11
0
def dualoperands(c, m):
    """Operations with two operands statement. For instance:
        ax |= bx  ; Bitwise OR
        dx ^= cx  ; Bitwise XOR
        ax &= bx  ; Bitwise AND
        dx += cx  ; Integer addition
        ax -= bx  ; Integer subtraction
    """
    op = m.group(2)
    dst = Operand(c, m.group(1))
    src = Operand(c, m.group(3))

    if src.value == 1 and op in translation1:
        c.add_code(f'{translation1[op]} {dst}')
    else:
        helperoperate(c, translation[op], dst, src)
Example #12
0
    def __init__(self, name, vartype, value, vector_size=None):
        self.name = name

        # Variable offset in memory, set by the compiler state
        self.offset = None
        self.length = 1

        # 'vector_size' should be None for non-vector types
        self.is_vector = vector_size is not None
        if self.is_vector:
            if vector_size < 1:
                raise ValueError('Vector size must be ≥ 1')

            self.value = Operand.get_csv(value)
            self.length = vector_size
            # One value is OK with any vector size, 'dup()' is assumed
            if len(self.value) != 1 and self.length != len(self.value):
                raise ValueError(
                    f'Specified vector length ({self.length}) and supplied '
                    f'values count ({len(self.value)}) do not match')
        else:
            # Not a vector, so save its single value
            self.value = value.strip()

        # Name and vector handled, now define the type
        self.type = vartype

        # Type defined, now determine its code
        self.is_constant = vartype == 'const'
        if self.is_constant:
            if self.is_vector:
                raise ValueError('Cannot define a constant vector')

            self.typecode = None
        else:
            if vartype == 'byte':
                self.typecode = 'DB'
                self.size = 8

            elif vartype == 'short':
                self.typecode = 'DW'
                self.size = 16

            elif vartype == 'string':
                if self.is_vector:
                    raise ValueError('Cannot define a vector of strings')

                self.typecode = 'DB'
                self.size = 8
                self.value, self.length = self.escape_string(value)

            else:
                raise ValueError(f'Unknown variable type "{vartype}"')
Example #13
0
def repeat(c, m):
    """Repeat control flow statement. For instance:
        repeat 10 with cx {
            ; code
        }
    """
    count = Operand(c, m.group(1))
    used = Operand(c, m.group(2))
    label = c.get_uid(m.group(3))
    labelstart = label + '_s'
    labelend = label + '_e'

    # Initialize our loop counter
    helperassign(c, used, count)

    # Sanity check, if 0 don't enter the loop unless we know it's not 0
    # This won't optimize away the case where the value is 0
    if count.value is None or count.value <= 0:  # count unknown or zero
        c.add_code([
            f'test {used}, {used}',
            f'jz {labelend}'
        ])

    # Add the start label so we can jump here
    c.add_code(f'{labelstart}:')

    if used.code == 'cx':
        # We can use 'loop' if using 'cx'
        c.add_pending_code([
            f'loop {labelstart}',
            f'{labelend}:'
        ])
    else:
        c.add_pending_code([
            f'dec {used}',
            f'jnz {labelstart}',
            f'{labelend}:'
        ])
Example #14
0
    def __init__(self, name, params, returns=None, mangle=True):
        # Convert a comma separated list to normal parameters if required
        self.params = Operand.get_csv(params)

        # Name mangling, add as many underscores as parameter count
        if mangle:
            self.name = self.mangle(name, len(self.params))
        else:
            self.name = name

        # Where the value is returned
        self.returns = returns

        # Used to store the code of the function
        self.code = []
Example #15
0
def putchar(c, m):
    """Puts a character on the screen. For instance:
        put char 'L'
        put digit cx
    """
    # TODO Possibly add support for not inlining
    src = Operand(c, m.group(2))

    c.add_code('push ax')

    helperassign(c, 'al' if src.size == 8 else 'ax', src)
    if m.group(1) == 'digit':
        c.add_code("add al, '0'")

    c.add_code([
        'mov ah, 14',
        'int 10h',
        'pop ax'
    ])
Example #16
0
def variable(c, m):
    """Variable or constant definition. For instance:
        byte little = 42
        short big = 1234

        byte[5] vector1
        byte[] vector2 = 1, 2, 3, 4, 5

        const VALUE = 7
    """
    # TODO Perform more checks to ensure the value is correct
    vartype = m.group(1)
    vector_size = m.group(2)
    name = m.group(3)
    value = m.group(4)
    if not value:
        value = '?'

    if vector_size is None:
        # Single item variable
        c.add_variable(Variable(name=name, vartype=vartype, value=value))
    else:
        # We have a vector, get the comma-separated values
        values = Operand.get_csv(value)

        # Determine its size (remove '[]' by slicing) if given
        vector_size = vector_size[1:-1].strip()
        if vector_size:
            vector_size = int(vector_size)
        else:
            if value == '?':
                raise ValueError('A list of values must be supplied when '
                                 'no vector size is specified')
            vector_size = len(values)

        c.add_variable(
            Variable(name=name,
                     vartype=vartype,
                     value=values,
                     vector_size=vector_size))
Example #17
0
def multiply(c, m):
    """Multiplication statement. For instance:
        al *= 7
    """
    dst = Operand(c, m.group(1))
    src = Operand(c, m.group(2))

    if src.value == 0:
        # Zero optimization: second product is 0
        if dst.is_mem:
            c.add_code(f'and {dst}, 0')
        else:
            c.add_code(f'xor {dst}, {dst}')
        return

    if src.value and abs(src.value) == 1:
        # One optimization: do nothing or negate
        if src.value < 0:
            c.add_code(f'neg {dst}')
        return

    if src.value is not None:
        # Bit shift optimization: second product is a small multiple of 2
        # http://zsmith.co/intel_m.html#mul or
        # http://zsmith.co/intel_n.html#neg and http://zsmith.co/intel_s.html#sal
        if dst.is_reg or dst.size == 8:
            limit = 70
        else:
            limit = 124

        if dst.is_reg:
            if src.value < 0:
                cost = 3 + 8 + 4 * (-src.value)
            else:
                cost = 8 + 4 * src.value

        if dst.is_mem:
            if src.value < 0:
                cost = 16 + 20 + 4 * (-src.value)
            else:
                cost = 20 + 4 * src.value

        # Now we know our limit, beyond which multiplication is easier
        # and the cost per shift, so we know which values we can use
        if cost < limit:
            # The cost of negating and shifting is less, so we'll do that
            amount = limit // cost
            powers = [2**i for i in range(1, amount + 1)]
            if abs(src.value) in powers:
                # We have a power of two, so perform we can use bit shift
                if src.value < 0:
                    c.add_code(f'neg {dst}')
                    src.value = -src.value

                count = int(src.value**0.5)
                c.add_code(f'sal {dst}, {count}')
                return

    # We're likely going to need to save some temporary variables
    # No 'push' or 'pop' are used because 'ah *= 3', for instance, destroys 'al'
    # 'al' itself can be pushed to the stack, but a temporary variable can hold
    tmps = TmpVariables(c)

    # We will define 'dst, src, fa1, fa2' as:
    #   dst *= src
    #   fa1 *= fa2

    large_multiply = max(dst.size, src.size) > 8

    if large_multiply:
        # 16-bits mode, so we use 'ax' as the first factor
        fa1 = 'ax'

        # The second factor cannot be an inmediate value neither 'ax'
        if src.code[0] == 'a' and src.code[-1] in 'xhl' \
                or src.value is not None:
            fa2 = 'dx'
        else:
            fa2 = src.code

        # Determine which registers we need to save
        saves = []

        # al    *= 260
        # ^ dst
        #
        # ax    *= 260
        # ^ used
        #
        # dst ≠ used, used gets saved

        # Special case where we want to use al/ah as destination
        # We're using ax for multiplication, so we can't save the whole
        if dst[0] == 'a' and dst[-1] in 'hl':
            saves.append('al' if dst.code == 'ah' else 'ah')
        elif dst.code != fa1:
            # Save fa1 unless it's the destination
            saves.append(fa1)

        # Save fa2 unless it's the destination
        if dst.code != fa2:
            saves.append(fa2)

        # Save the used registers
        tmps.save_all(saves)

        # Load the factors into their correct location
        helperassign(c, [fa1, fa2], [dst, src])

        # Perform the multiplication
        c.add_code(f'mul {fa2}')

        # Move the result, 'ax', to wherever it should be
        helperassign(c, dst, 'ax')

        # Restore the used registers
        tmps.restore_all()

    else:
        # 8-bits mode, so we use 'al' as the first factor
        fa1 = 'al'

        # The second factor cannot be an inmediate value neither 'al'
        fa2 = src.code if src.value is None and src.code != 'al' else 'ah'

        # Determine which registers we need to save
        saves = []

        # Save fa1 unless it's the destination
        if dst.code != fa1:
            saves.append(fa1)

        # Save fa2 if we moved it (thus it's not the same)
        # unless it's be overrode if it is the destination
        if src.code != fa2 and dst.code != fa2:
            saves.append(fa2)

        # Save the used registers
        tmps.save_all(saves)

        # Load the factors into their correct location
        helperassign(c, [fa1, fa2], [dst, src])

        # Perform the multiplication
        c.add_code(f'mul {fa2}')

        # Move the result, 'al', to wherever it should be
        helperassign(c, dst, 'al')

        # Restore the used registers
        tmps.restore_all()
Example #18
0
def printf(c, m):
    """Prints a string with optional formatted values. For instance:
        printf "I'm %d years old, you %d" % ax, bx
        printf stringVariable
    """
    # TODO Possibly add support for not inlining
    #      Inlining really shines when there are actually formatted values:
    #
    #      If inlining:
    #           5 instructions; push/pop ax/dx, mov ah 9
    #        + 2n (where n is the number of strings, lea + int)
    #
    #      If not inlining (the first 7 are only written once, ran many times):
    #          7n instructions; function definition, push/pop ax, mov ah 9, int
    #        +  2 instructions; push/pop dx
    #        + 2n (where n is the number of strings, lea + int)
    #
    # Retrieve the captured values
    string = m.group(1)
    args = c.get_operands(m.group(2))
    var = m.group(3)

    # Save the registers used for calling the print interruption
    c.add_code(['push ax', 'push dx'])

    # string/args and var are mutually exclusive
    if var:
        op = Operand(c, var, assert_vector_access=False)
        if op.is_mem:
            # Using a variable, load and call the interruption
            if op.type == 'string':
                c.add_code(f'lea dx, {op}')
            else:
                load_integer(c, op.code)

            c.add_code([f'mov ah, 9', f'int 21h'])

        elif op.is_reg:
            # Using a register, which means we need to load an integer
            load_integer(c, op)
            c.add_code([f'mov ah, 9', f'int 21h'])

        else:
            # Assume inmediate integer value
            c.add_code(f'mov ah, 9')
            define_and_print(c, f'"{op.name}"')

    else:
        # Captured a string, with possibly formatted arguments
        #
        # Since 'ax' and 'dx' are needed for printing, we need to save them.
        # We could use the stack, however, we would need to push as many times
        # as we encountered one of the offending registers. Also, the stack
        # doesn't support the 8-bit versions of these registers, so we simply
        # use a temporary variable.
        tmps = TmpVariables(c)

        for a in args:
            if is_ax_or_dx_variant(a):
                tmps.save(a)

        # After we saved the values we may later need, set up the print function
        c.add_code('mov ah, 9')

        # Now, we have the right string and the arguments used
        if args:
            # 1. Find %X's
            substrings = re.split(r'%[sd]', string)
            arg_types = re.findall(r'%[sd]', string)

            # 2. Print: substring, formatted, substring, formatted, etc.
            for i in range(len(substrings)):
                if i > 0:
                    # Format the arguments, skipping 1 normal string (thus -1)
                    op = args[i - 1]
                    format_type = arg_types[i - 1]

                    # Load the argument into 'dx' depending on its type
                    #
                    # This assumes that the user entered the right type
                    # TODO Don't assume that the user entered the right type
                    if format_type == '%s':
                        c.add_code(f'lea dx, {op}')
                    elif format_type == '%d':
                        if op in tmps:
                            # Special, this was saved previously
                            load_integer(c, tmps[op])
                            c.add_code('mov ah, 9')
                        else:
                            load_integer(c, op)
                    else:
                        raise ValueError(
                            f'Regex should not have allowed {format_type}')

                    # Call the interruption to print the argument
                    c.add_code(f'int 21h')

                # Argument formatted, print the next part of the string
                define_and_print(c, substrings[i])
        else:
            # No arguments at all, a define and load will do
            define_and_print(c, string)

    # All done, restore the original dx/ax values
    c.add_code(['pop dx', 'pop ax'])
Example #19
0
def divmod_(c, m):
    """Division & Modulo statement. For instance:
        ax, dx = divmod bx, 7

        After this operation takes place, the values will be:
        ax = bx / 7  ; (integer division)
        dx = bx % 7  ; (bx modulo 7)
    """
    a = Operand.parseint(m.group(3))
    b = Operand.parseint(m.group(4))
    if a is not None and b is not None:
        # Both are integers, we can optimize this away
        # and just plug the right values in
        a, b = divmod(a, b)
        helperassign(c, m.group(1), a)
        helperassign(c, m.group(2), b)
    else:
        # Need to perform the operation
        quotient = m.group(1)
        remainder = m.group(2)
        dividend = m.group(3)
        divisor = m.group(4)
        # TODO Support for AH/AL (8 bits division mode)

        # Only AX and DX will be affected, so unless we're
        # assigning a result to them, they need to be pushed
        push_ax = quotient != 'ax' and remainder != 'ax'
        push_dx = quotient != 'dx' and remainder != 'dx'
        if push_ax:
            c.add_code('push ax')
        if push_dx:
            c.add_code('push dx')

        push_divisor = False
        # Determine the divisor to be used, it cannot be 'dx' since
        # it has to be zero because it's taken into account when dividing,
        # and it cannot be 'ax' because it's where the dividend is
        #
        # The divisor cannot be an inmediate value either ('b' not None)
        if divisor in ['ax', 'dx'] or b is not None:
            # Pick either the quotient or remainder (will be overwritten later)
            if quotient not in ['ax', 'dx']:
                used_divisor = quotient
            elif remainder not in ['ax', 'dx']:
                used_divisor = remainder
            else:
                # No luck, use 'bx' for instance (we need to save it)
                used_divisor = 'bx'
                push_divisor = True
                c.add_code('push bx')

            # Update whatever register we used
            helperassign(c, used_divisor, divisor)
        else:
            # We have the right value for the divisor, no move required
            used_divisor = divisor

        # Divisor set up, neither in 'ax' nor 'dx' so it's OK
        # Now set up the dividend, which must always be in 'ax'
        helperassign(c, 'ax', dividend)

        # Everything set, perform the division
        c.add_code([
            f'xor dx, dx',  # Upper bits are considered!
            f'div {used_divisor}'
        ])

        if push_divisor:
            # Division done, restore our temporary register
            c.add_code('pop bx')

        # Now move the result if required
        # Special case, 'ax' and 'dx are swapped
        if quotient == 'dx' and remainder == 'ax':
            c.add_code(['push ax', 'mov ax, dx', 'pop dx'])
        else:
            # Although helperassign would take care of these, we can
            # slightly optimize away the otherwise involved push/pop
            #
            # TODO Actually, perhaps helper assign could note this and
            # swap the order of assignment
            if quotient == 'dx':
                # Special case, we need to move remainder, in 'dx', first
                helperassign(c, [remainder, quotient], ['dx', 'ax'])
            else:
                # Normal case, first 'ax', then 'dx'
                helperassign(c, [quotient, remainder], ['ax', 'dx'])

        if push_dx:
            c.add_code('pop dx')
        if push_ax:
            c.add_code('pop ax')
Example #20
0
def helperoperate(c, op, dst, src):
    """Helper operation with support to operate on any value
        (memory or not) of the form 'instruction dst, src'
    """
    if not isinstance(dst, Operand):
        dst = Operand(c, dst)

    if not isinstance(src, Operand):
        src = Operand(c, src)

    # Sanity check: destination cannot be an inmediate value
    if dst.value is not None:
        raise ValueError(
            f'Cannot {op} "{src.name}" to the inmediate value "{dst.name}"')

    # If the source has an integer value, make sure it fits on destination
    if src.value is not None:
        if src.size > dst.size:
            raise ValueError(f'"{src.name}" is too big to fit in "{dst.name}"')

        # It's an okay inmediate value, simply operate and early exit
        c.add_code(f'{op} {dst}, {src}')
        return

    # Special case where both are memory, we need to use a temporary
    # register ('ax' for instance); recursive calls to helperoperate()
    # will then take care of the cases where operating with memory + 'ax'
    if dst.is_mem and src.is_mem:
        # # # [Case memory to memory]
        # TODO Perhaps we could use a temporary variable instead of the
        #      stack to pick either AL or AX (which is better suited)
        c.add_code('push ax')
        helperassign(c, 'ax', src)
        helperoperate(c, op, dst, 'ax')
        c.add_code('pop ax')
        return

    # Now that we know the size, and that not both are memory,
    # we can get our hands dirty
    if dst.size == src.size:
        # # # [Case same size]
        # Both sizes are the same, we can directly operate (unless 'mov')
        if op != 'mov' or dst != src:
            c.add_code(f'{op} {dst}, {src}')

    elif dst.size < src.size:
        # The destination is smaller, we have to ignore the high part
        if src.is_mem or src.low is None:
            # # # [Case large memory/register to small register]
            # The source is memory, then the destination is a register
            #
            # We need an auxiliary register because we don't want to
            # touch the other part (either of r'[HL]'), and we can't
            # pop a masked value not to lose the rest
            #
            # This is also the case when the source is not memory, but
            # the register doesn't support accessing r'[HL]'
            #
            # Use either 'dx' or 'ax' as auxiliar register (arbitrary
            # as long as it doesn't match the one we want to move to)
            aux = 'dx' if dst[0] == 'a' else 'ax'
            c.add_code(f'push {aux}')
            helperassign(c, aux, src)
            helperoperate(c, op, dst, f'{aux[0]}l')
            c.add_code(f'pop {aux}')
        else:
            # # # [Case large register to small register/memory]
            # We know that we have acces to the low part since it was
            # checked above, otherwise we would have needed that
            # auxiliary to be able to pick only the low part
            helperoperate(c, op, dst, src.low)

    else:
        # The destination is larger, we need to mask the high part.
        # The only case where this is possible is on r'[ABCD][HL]',
        # so we already know that we have access to the 'X' version
        # unless the source is memory
        if src.is_mem:
            # # # [Case small memory to large register]
            # Check if the register supports accessing to the r'[HL]'
            # Otherwise we need to use a temporary register such as 'ax'
            if dst.low is None:
                c.add_code(f'push ax')
                c.add_code(f'xor ah, ah')
                helperassign(c, 'al', src)
                helperoperate(c, op, dst, 'ax')
                c.add_code(f'pop ax')
            else:
                c.add_code(f'xor {dst.high}, {dst.high}')
                helperoperate(c, op, dst.low, src)
        elif dst.is_mem:
            # # # [Case small register to large memory]
            # All the 8 bit registers support accessing to 'X', so we
            # can just mask away the high part, move it and restore
            c.add_code(f'push {src.full}')

            # We might need to move the high to the low part before masking
            if src[-1] == 'h':
                helperassign(c, src.low, src.high)
            c.add_code(f'xor {src.high}, {src.high}')

            helperoperate(c, op, dst, src.full)
            c.add_code(f'pop {src.full}')
        else:
            # # # [Case small register to large register]
            if src[-1] == 'l':
                # We're using the low part, we can directly copy
                # and then mask the high part with AND or XOR
                helperoperate(c, op, dst, src.full)
                if dst.high is None:
                    c.add_code(f'and {dst}, 0xff')
                else:
                    c.add_code(f'xor {dst.high}, {dst.high}')
            else:
                # We want to assign the src = YH, but it's 8-bits
                if dst.low is None:
                    # No support to move the 8-bits directly, we need
                    # to save the value, move it, mask it, and move it
                    c.add_code(f'push {src.full}')
                    helperassign(c, src.low, src)
                    c.add_code(f'xor {src}, {src}')
                    helperoperate(c, op, dst, src.full)
                    c.add_code(f'pop {src.full}')
                else:
                    # Destination supports 8-bits access so we can
                    # directly move those and mask away the high part
                    helperoperate(c, op, dst.low, src)
                    c.add_code(f'xor {dst.high}, {dst.high}')