Ejemplo n.º 1
0
Archivo: action0.py Proyecto: spnda/nml
def get_snowlinetable_action(snowline_table):
    assert len(snowline_table) == 12 * 32

    action6.free_parameters.save()
    action_list = []
    tmp_param_map = {}  # Cache for tmp parameters
    act6 = action6.Action6()

    act0, offset = create_action0(0x08, expression.ConstantNumeric(0), act6,
                                  action_list)
    act0.num_ids = 1
    offset += 1  # Skip property number

    data_table = []
    idx = 0
    while idx < len(snowline_table):
        val = snowline_table[idx]
        if isinstance(val, expression.ConstantNumeric):
            data_table.append(val.value)
            idx += 1
            continue

        if idx + 3 >= len(snowline_table):
            tmp_param, tmp_param_actions = actionD.get_tmp_parameter(val)
            tmp_param_map[val] = tmp_param
            act6.modify_bytes(tmp_param, 1, offset + idx)
            action_list.extend(tmp_param_actions)
            data_table.append(0)
            idx += 1
            continue

        # Merge the next 4 values together in a single parameter.
        val2 = nmlop.SHIFT_LEFT(snowline_table[idx + 1], 8)
        val3 = nmlop.SHIFT_LEFT(snowline_table[idx + 2], 16)
        val4 = nmlop.SHIFT_LEFT(snowline_table[idx + 3], 24)
        expr = nmlop.OR(val, val2)
        expr = nmlop.OR(expr, val3)
        expr = nmlop.OR(expr, val4)
        expr = expr.reduce()

        # Cache lookup, saves some ActionDs
        if expr in tmp_param_map:
            tmp_param, tmp_param_actions = tmp_param_map[expr], []
        else:
            tmp_param, tmp_param_actions = actionD.get_tmp_parameter(expr)
            tmp_param_map[expr] = tmp_param

        act6.modify_bytes(tmp_param, 4, offset + idx)
        action_list.extend(tmp_param_actions)
        data_table.extend([0, 0, 0, 0])
        idx += 4

    act0.prop_list.append(
        ByteListProp(0x10, "".join([chr(x) for x in data_table])))
    if len(act6.modifications) > 0:
        action_list.append(act6)
    action_list.append(act0)
    action6.free_parameters.restore()
    return action_list
Ejemplo n.º 2
0
 def reduce(self, id_dicts=None, unknown_id_fatal=True):
     ret = ConstantNumeric(0, self.pos)
     for orig_expr in self.values:
         val = orig_expr.reduce(id_dicts)
         if val.type() != Type.INTEGER:
             raise generic.ScriptError(
                 "Parameters of 'bitmask' must be integers.", orig_expr.pos)
         if isinstance(val, ConstantNumeric) and val.value >= 32:
             raise generic.ScriptError(
                 "Parameters of 'bitmask' cannot be greater than 31",
                 orig_expr.pos)
         val = nmlop.SHIFT_LEFT(1, val)
         ret = nmlop.OR(ret, val)
     return ret.reduce()
Ejemplo n.º 3
0
def nearest_house_matching_criterion(name, args, pos, info):
    # nearest_house_matching_criterion(radius, criterion)
    # parameter is radius | (criterion << 6)
    if len(args) != 2:
        raise generic.ScriptError("{}() requires 2 arguments, encountered {:d}".format(name, len(args)), pos)
    if isinstance(args[0], expression.ConstantNumeric):
        generic.check_range(args[0].value, 1, 63, "{}()-parameter 1 'radius'".format(name), pos)
    if isinstance(args[1], expression.ConstantNumeric) and args[1].value not in (0, 1, 2):
        raise generic.ScriptError("Invalid value for {}()-parameter 2 'criterion'".format(name), pos)

    radius = nmlop.AND(args[0], 0x3F, pos)
    criterion = nmlop.AND(args[1], 0x03, pos)
    criterion = nmlop.MUL(criterion, 0x40)
    retval = nmlop.OR(criterion, radius).reduce()
    return (retval, [])
Ejemplo n.º 4
0
def builtin_vehicle_curv_info(name, args, pos):
    """
    vehicle_curv_info(prev_cur, cur_next) builtin function

    @return Value to use with vehicle var curv_info
    """
    if len(args) != 2:
        raise generic.ScriptError(name + "() must have 2 parameters", pos)

    for arg in args:
        if isinstance(arg, ConstantNumeric):
            generic.check_range(arg.value, -2, 2, "Argument of '{}'".format(name), arg.pos)

    args = [nmlop.AND(arg, 0xF, pos) for arg in args]
    cur_next = nmlop.SHIFT_LEFT(args[1], 8)
    return nmlop.OR(args[0], cur_next)
Ejemplo n.º 5
0
def cargo_accepted_nearby(name, args, pos, info):
    # cargo_accepted_nearby(cargo[, xoffset, yoffset])
    if len(args) not in (1, 3):
        raise generic.ScriptError("{}() requires 1 or 3 arguments, encountered {:d}".format(name, len(args)), pos)

    if len(args) > 1:
        offsets = args[1:3]
        for i, offs in enumerate(offsets[:]):
            if isinstance(offs, expression.ConstantNumeric):
                generic.check_range(offs.value, -128, 127, "{}-parameter {:d} '{}offset'".format(name, i + 1, "x" if i == 0 else "y"), pos)
            offsets[i] = nmlop.AND(offs, 0xFF, pos).reduce()
        # Register 0x100 should be set to xoffset | (yoffset << 8)
        reg100 = nmlop.OR(nmlop.MUL(offsets[1], 256, pos), offsets[0]).reduce()
    else:
        reg100 = expression.ConstantNumeric(0, pos)

    return (args[0], [(0x100, reg100)])
Ejemplo n.º 6
0
def builtin_relative_coord(name, args, pos):
    """
    relative_coord(x, y) builtin function.

    @return Coordinates in 0xYYXX format.
    """
    if len(args) != 2:
        raise generic.ScriptError(name + "() must have x and y coordinates as parameters", pos)

    if isinstance(args[0], ConstantNumeric):
        generic.check_range(args[0].value, 0, 255, "Argument of '{}'".format(name), args[0].pos)
    if isinstance(args[1], ConstantNumeric):
        generic.check_range(args[1].value, 0, 255, "Argument of '{}'".format(name), args[1].pos)

    x_coord = nmlop.AND(args[0], 0xFF)
    y_coord = nmlop.AND(args[1], 0xFF)
    # Shift Y to its position.
    y_coord = nmlop.SHIFT_LEFT(y_coord, 8)

    return nmlop.OR(x_coord, y_coord, pos)
Ejemplo n.º 7
0
def transform_bin_op(assignment):
    op = assignment.value.op
    expr1 = assignment.value.expr1
    expr2 = assignment.value.expr2
    extra_actions = []

    if op == nmlop.CMP_GE:
        expr1, expr2 = expr2, expr1
        op = nmlop.CMP_LE

    if op == nmlop.CMP_LE:
        extra_actions.extend(parse_actionD(ParameterAssignment(assignment.param, nmlop.SUB(expr1, expr2))))
        op = nmlop.CMP_LT
        expr1 = assignment.param
        expr2 = expression.ConstantNumeric(1)

    if op == nmlop.CMP_GT:
        expr1, expr2 = expr2, expr1
        op = nmlop.CMP_LT

    if op == nmlop.CMP_LT:
        extra_actions.extend(parse_actionD(ParameterAssignment(assignment.param, nmlop.SUB(expr1, expr2))))
        op = nmlop.SHIFTU_LEFT  # shift left by negative number = shift right
        expr1 = assignment.param
        expr2 = expression.ConstantNumeric(-31)

    elif op == nmlop.CMP_NEQ:
        extra_actions.extend(parse_actionD(ParameterAssignment(assignment.param, nmlop.SUB(expr1, expr2))))
        op = nmlop.DIV
        # We rely here on the (ondocumented) behavior of both OpenTTD and TTDPatch
        # that expr/0==expr. What we do is compute A/A, which will result in 1 if
        # A != 0 and in 0 if A == 0
        expr1 = assignment.param
        expr2 = assignment.param

    elif op == nmlop.CMP_EQ:
        # We compute A==B by doing not(A - B) which will result in a value != 0
        # if A is equal to B
        extra_actions.extend(parse_actionD(ParameterAssignment(assignment.param, nmlop.SUB(expr1, expr2))))
        # Clamp the value to 0/1, see above for details
        extra_actions.extend(
            parse_actionD(ParameterAssignment(assignment.param, nmlop.DIV(assignment.param, assignment.param)))
        )
        op = nmlop.SUB
        expr1 = expression.ConstantNumeric(1)
        expr2 = assignment.param

    if op == nmlop.SHIFT_RIGHT or op == nmlop.SHIFTU_RIGHT:
        if isinstance(expr2, expression.ConstantNumeric):
            expr2.value *= -1
        else:
            expr2 = nmlop.SUB(0, expr2)
        op = nmlop.SHIFT_LEFT if op == nmlop.SHIFT_RIGHT else nmlop.SHIFTU_LEFT

    elif op == nmlop.XOR:
        # a ^ b ==> (a | b) - (a & b)
        expr1 = parse_subexpression(expr1, extra_actions)
        expr2 = parse_subexpression(expr2, extra_actions)
        tmp_param1, tmp_action_list1 = get_tmp_parameter(nmlop.OR(expr1, expr2))
        tmp_param2, tmp_action_list2 = get_tmp_parameter(nmlop.AND(expr1, expr2))
        extra_actions.extend(tmp_action_list1)
        extra_actions.extend(tmp_action_list2)
        expr1 = expression.Parameter(expression.ConstantNumeric(tmp_param1))
        expr2 = expression.Parameter(expression.ConstantNumeric(tmp_param2))
        op = nmlop.SUB

    return op, expr1, expr2, extra_actions
Ejemplo n.º 8
0
def parse_graphics_block_single_id(graphics_block,
                                   feature,
                                   id,
                                   is_livery_override,
                                   house_tile=None,
                                   house_north_tile_id=None):
    action6.free_parameters.save()
    prepend_action_list = []
    action_list = []
    act6 = action6.Action6()
    act3 = create_action3(feature, id, action_list, act6, is_livery_override)

    cargo_gfx = {}
    seen_callbacks = set()
    callbacks = []
    livery_override = None  # Used for rotor graphics

    for graphics in graphics_block.graphics_list:
        cargo_id = graphics.cargo_id
        if isinstance(cargo_id, expression.Identifier):
            cb_name = cargo_id.value
            cb_table = action3_callbacks.callbacks[feature]
            if cb_name in cb_table:
                if cb_name in seen_callbacks:
                    raise generic.ScriptError(
                        "Callback '{}' is defined multiple times.".format(
                            cb_name), cargo_id.pos)
                seen_callbacks.add(cb_name)

                info_list = cb_table[cb_name]
                if not isinstance(info_list, list):
                    info_list = [info_list]

                for info in info_list:
                    if "deprecate_message" in info:
                        generic.print_warning(generic.Warning.DEPRECATION,
                                              info["deprecate_message"],
                                              cargo_id.pos)
                    if house_tile is not None and "tiles" in info and house_tile not in info[
                            "tiles"]:
                        continue

                    if info["type"] == "cargo":
                        # Not a callback, but an alias for a certain cargo type
                        if info["num"] in cargo_gfx:
                            raise generic.ScriptError(
                                "Graphics for '{}' are defined multiple times."
                                .format(cb_name), cargo_id.pos)
                        cargo_gfx[info["num"]] = graphics.result.value
                    elif info["type"] == "cb":
                        callbacks.append((info, graphics.result.value))
                    elif info["type"] == "override":
                        assert livery_override is None
                        livery_override = graphics.result.value
                    else:
                        raise AssertionError()
                continue

        # Not a callback, so it must be a 'normal' cargo (vehicles/stations only)
        cargo_id = cargo_id.reduce_constant(global_constants.const_list)
        # Raise the error only now, to let the 'unknown identifier' take precedence
        if feature >= 5:
            raise generic.ScriptError(
                "Associating graphics with a specific cargo is possible only for vehicles and stations.",
                cargo_id.pos)
        if cargo_id.value in cargo_gfx:
            raise generic.ScriptError(
                "Graphics for cargo {:d} are defined multiple times.".format(
                    cargo_id.value), cargo_id.pos)
        cargo_gfx[cargo_id.value] = graphics.result.value

    if graphics_block.default_graphics is not None:
        if "default" not in action3_callbacks.callbacks[feature]:
            raise generic.ScriptError(
                "Default graphics may not be defined for this feature (0x{:02X})."
                .format(feature),
                graphics_block.default_graphics.pos,
            )
        if None in cargo_gfx:
            raise generic.ScriptError("Default graphics are defined twice.",
                                      graphics_block.default_graphics.pos)
        cargo_gfx[None] = graphics_block.default_graphics.value

    # An in-between varaction2 is always needed for houses
    if len(callbacks) != 0 or feature == 0x07:
        cb_flags = 0
        # Determine the default value
        if None not in cargo_gfx:
            cargo_gfx[None] = expression.SpriteGroupRef(
                expression.Identifier("CB_FAILED", None), [], None)
        default_val = cargo_gfx[None]

        cb_mapping = {}
        cb_buy_mapping = {}
        # Special case for vehicle cb 36, maps var10 values to spritegroups
        cb36_mapping = {}
        cb36_buy_mapping = {}
        # Sspecial case for industry production CB, maps var18 values to spritegroups
        prod_cb_mapping = {}

        for cb_info, gfx in callbacks:
            if "flag_bit" in cb_info:
                # Set a bit in the CB flags property
                cb_flags |= 1 << cb_info["flag_bit"]

            value_function = cb_info.get("value_function", None)
            mapping_val = (gfx, value_function)

            # See action3_callbacks for info on possible values
            purchase = cb_info.get("purchase", 0)
            if isinstance(purchase, str):
                # Not in purchase list, if separate purchase CB is set
                purchase = 0 if purchase in seen_callbacks else 1
            # Explicit purchase CBs will need a purchase cargo, even if not needed for graphics
            if purchase == 2 and 0xFF not in cargo_gfx:
                cargo_gfx[0xFF] = default_val

            num = cb_info["num"]
            if num == 0x36:
                if purchase != 2:
                    cb36_mapping[cb_info["var10"]] = mapping_val
                if purchase != 0:
                    cb36_buy_mapping[cb_info["var10"]] = mapping_val
            elif feature == 0x0A and num == 0x00:
                # Industry production CB
                assert purchase == 0
                prod_cb_mapping[cb_info["var18"]] = mapping_val
            else:
                if purchase != 2:
                    cb_mapping[num] = mapping_val
                if purchase != 0:
                    cb_buy_mapping[num] = mapping_val

        if cb_flags != 0:
            prepend_action_list.extend(
                action0.get_callback_flags_actions(feature, id, cb_flags))

        # Handle CB 36
        if len(cb36_mapping) != 0:
            expr = expression.Variable(expression.ConstantNumeric(0x10),
                                       mask=expression.ConstantNumeric(0xFF))
            actions, cb36_ref = create_cb_choice_varaction2(
                feature, expr, cb36_mapping, default_val, graphics_block.pos)
            prepend_action_list.extend(actions)
            cb_mapping[0x36] = (cb36_ref, None)
        if len(cb36_buy_mapping) != 0:
            expr = expression.Variable(expression.ConstantNumeric(0x10),
                                       mask=expression.ConstantNumeric(0xFF))
            actions, cb36_ref = create_cb_choice_varaction2(
                feature, expr, cb36_buy_mapping, default_val,
                graphics_block.pos)
            prepend_action_list.extend(actions)
            cb_buy_mapping[0x36] = (cb36_ref, None)
        if len(prod_cb_mapping) != 0:
            expr = expression.Variable(expression.ConstantNumeric(0x18),
                                       mask=expression.ConstantNumeric(0xFF))
            actions, cb_ref = create_cb_choice_varaction2(
                feature, expr, prod_cb_mapping, default_val,
                graphics_block.pos)
            prepend_action_list.extend(actions)
            cb_mapping[0x00] = (cb_ref, None)

        for cargo in sorted(cargo_gfx, key=lambda x: -1 if x is None else x):
            mapping = cb_buy_mapping if cargo == 0xFF else cb_mapping
            if len(mapping) == 0 and feature != 0x07:
                # No callbacks here, so move along
                # Except for houses, where we need to store some stuff in a register
                continue
            if cargo_gfx[cargo] != default_val:
                # There are cargo-specific graphics, be sure to handle those
                # Unhandled callbacks should chain to the default, though
                mapping = mapping.copy()
                mapping[0x00] = (cargo_gfx[cargo], None)

            expr = expression.Variable(expression.ConstantNumeric(0x0C),
                                       mask=expression.ConstantNumeric(0xFFFF))
            if feature == 0x07:
                # Store relative x/y, item id (of the north tile) and house tile (HOUSE_TILE_XX constant) in register FF
                # Format: 0xIIHHYYXX: II: item ID, HH: house tile, YY: relative y, XX: relative x
                lowbytes_dict = {
                    "n": 0x000000,
                    "e": 0x010100,
                    "w": 0x020001,
                    "s": 0x030101,
                }
                lowbytes = expression.ConstantNumeric(
                    lowbytes_dict[house_tile])
                highbyte = nmlop.SHIFT_LEFT(house_north_tile_id, 24)
                register_FF = nmlop.OR(lowbytes, highbyte).reduce()
                register_FF = nmlop.STO_TMP(register_FF, 0xFF)
                expr = nmlop.VAL2(register_FF, expr)

                if len(mapping) == 0:
                    # mapping must not be empty
                    mapping[0x00] = (default_val, None)

            actions, cb_ref = create_cb_choice_varaction2(
                feature, expr, mapping, default_val, graphics_block.pos)
            prepend_action_list.extend(actions)
            cargo_gfx[cargo] = cb_ref

    # Make sure to sort to make the order well-defined
    offset = 7 if feature <= 3 else 5
    for cargo_id in sorted(cg for cg in cargo_gfx if cg is not None):
        result, comment = action2var.parse_result(cargo_gfx[cargo_id],
                                                  action_list, act6,
                                                  offset + 1, act3, None, 0x89)
        act3.cid_mappings.append((cargo_id, result, comment))
        offset += 3

    if None in cargo_gfx:
        result, comment = action2var.parse_result(cargo_gfx[None], action_list,
                                                  act6, offset, act3, None,
                                                  0x89)
        act3.def_cid = result
        act3.default_comment = comment
    else:
        act3.def_cid = None
        act3.default_comment = ""

    if livery_override is not None:
        act6livery = action6.Action6()
        # Add any extra actions before the main action3 (TTDP requirement)
        act3livery = create_action3(feature, id, action_list, act6livery, True)
        offset = 7 if feature <= 3 else 5
        result, comment = action2var.parse_result(livery_override, action_list,
                                                  act6livery, offset,
                                                  act3livery, None, 0x89)
        act3livery.def_cid = result
        act3livery.default_comment = comment

    if len(act6.modifications) > 0:
        action_list.append(act6)
    action_list.append(act3)

    if livery_override is not None:
        if len(act6livery.modifications) > 0:
            action_list.append(act6livery)
        action_list.append(act3livery)  # lgtm[py/uninitialized-local-variable]
    action6.free_parameters.restore()

    return prepend_action_list + action_list
Ejemplo n.º 9
0
def parse_result(value,
                 action_list,
                 act6,
                 offset,
                 parent_action,
                 none_result,
                 var_range,
                 repeat_result=1):
    """
    Parse a result (another switch or CB result) in a switch block.

    @param value: Value to parse
    @type value: L{Expression}

    @param action_list: List to append any extra actions to
    @type action_list: C{list} of L{BaseAction}

    @param act6: Action6 to add any modifications to
    @type act6: L{Action6}

    @param offset: Current offset to use for action6
    @type offset: C{int}

    @param parent_action: Reference to the action of which this is a result
    @type parent_action: L{BaseAction}

    @param none_result: Result to use to return the computed value
    @type none_result: L{Expression}

    @param var_range: Variable range to use for variables in the expression
    @type var_range: C{int}

    @param repeat_result: Repeat any action6 modifying of the next sprite this many times.
    @type repeat_result: C{int}

    @return: A tuple of two values:
                - The value to use as return value
                - Comment to add to this value
    @rtype: C{tuple} of (L{ConstantNumeric} or L{SpriteGroupRef}), C{str}
    """
    if value is None:
        comment = "return;"
        assert none_result is not None
        if isinstance(none_result, expression.SpriteGroupRef):
            result = parse_sg_ref_result(none_result, action_list,
                                         parent_action, var_range)
        else:
            result = none_result
    elif isinstance(value, expression.SpriteGroupRef):
        result = parse_sg_ref_result(value, action_list, parent_action,
                                     var_range)
        comment = result.name.value + ";"
    elif isinstance(value, expression.ConstantNumeric):
        comment = "return {:d};".format(value.value)
        result = value
        if not (-16384 <= value.value <= 32767):
            msg = (
                "Callback results are limited to -16384..16383 (when the result is a signed number)"
                " or 0..32767 (unsigned), encountered {:d}.").format(
                    value.value)
            raise generic.ScriptError(msg, value.pos)

    elif isinstance(value, expression.String):
        comment = "return {};".format(str(value))
        str_id, actions = action4.get_string_action4s(0, 0xD0, value)
        action_list.extend(actions)
        result = expression.ConstantNumeric(str_id - 0xD000 + 0x8000)
    elif value.supported_by_actionD(False):
        tmp_param, tmp_param_actions = actionD.get_tmp_parameter(
            nmlop.OR(value, 0x8000).reduce())
        comment = "return param[{:d}];".format(tmp_param)
        action_list.extend(tmp_param_actions)
        for i in range(repeat_result):
            act6.modify_bytes(tmp_param, 2, offset + 2 * i)
        result = expression.ConstantNumeric(0)
    else:
        global return_action_id
        extra_actions, result = create_return_action(
            value, parent_action.feature,
            "@return_action_{:d}".format(return_action_id), var_range)
        return_action_id += 1
        action2.add_ref(result, parent_action)
        action_list.extend(extra_actions)
        comment = "return {}".format(value)
    return (result, comment)
Ejemplo n.º 10
0
    def reduce(self, id_dicts=None, unknown_id_fatal=True):
        # Reducing a BinOp expression is done in several phases:
        # - Reduce both subexpressions.
        # - If both subexpressions are constant, compute the result and return it.
        # - If the operator allows it and the second expression is more complex than
        #   the first one swap them.
        # - If the operation is a no-op, delete it.
        # - Variables (as used in action2var) can have some computations attached to
        #   them, do that if possible.
        # - Try to merge multiple additions/subtractions with constant numbers

        # - Reduce both subexpressions.
        expr1 = self.expr1.reduce(id_dicts)
        expr2 = self.expr2.reduce(id_dicts)

        # Make sure the combination of operands / operator is valid
        if self.op.validate_func is not None:
            self.op.validate_func(expr1, expr2, self.pos)

        # - If both subexpressions are constant, compute the result and return it.
        if (
            isinstance(expr1, ConstantNumeric)
            and isinstance(expr2, ConstantNumeric)
            and self.op.compiletime_func is not None
        ):
            return ConstantNumeric(self.op.compiletime_func(expr1.value, expr2.value), self.pos)

        if isinstance(expr1, StringLiteral) and isinstance(expr2, StringLiteral):
            assert self.op == nmlop.ADD
            return StringLiteral(expr1.value + expr2.value, expr1.pos)

        if (
            isinstance(expr1, (ConstantNumeric, ConstantFloat))
            and isinstance(expr2, (ConstantNumeric, ConstantFloat))
            and self.op.compiletime_func is not None
        ):
            return ConstantFloat(self.op.compiletime_func(expr1.value, expr2.value), self.pos)

        # - If the operator allows it and the second expression is more complex than
        #   the first one swap them.
        op = self.op
        if op.commutative or op in (nmlop.CMP_LT, nmlop.CMP_GT):
            prio1 = self.get_priority(expr1)
            prio2 = self.get_priority(expr2)
            if prio2 < prio1:
                expr1, expr2 = expr2, expr1
                if op == nmlop.CMP_LT:
                    op = nmlop.CMP_GT
                elif op == nmlop.CMP_GT:
                    op = nmlop.CMP_LT

        # - If the operation is a no-op, delete it.
        if op == nmlop.AND and isinstance(expr2, ConstantNumeric) and (expr2.value == -1 or expr2.value == 0xFFFFFFFF):
            return expr1

        if op in (nmlop.DIV, nmlop.DIVU, nmlop.MUL) and isinstance(expr2, ConstantNumeric) and expr2.value == 1:
            return expr1

        if op in (nmlop.ADD, nmlop.SUB) and isinstance(expr2, ConstantNumeric) and expr2.value == 0:
            return expr1

        # - Variables (as used in action2var) can have some computations attached to
        #   them, do that if possible.
        if isinstance(expr1, Variable) and expr2.supported_by_actionD(False):
            # An action2 Variable has some special fields (mask, add, div and mod) that can be used
            # to perform some operations on the value. These operations are faster than a normal
            # advanced varaction2 operator so we try to use them whenever we can.
            if op == nmlop.AND and expr1.add is None:
                expr1.mask = nmlop.AND(expr1.mask, expr2, self.pos).reduce(id_dicts)
                return expr1
            if op == nmlop.ADD and expr1.div is None and expr1.mod is None:
                if expr1.add is None:
                    expr1.add = expr2
                else:
                    expr1.add = nmlop.ADD(expr1.add, expr2, self.pos).reduce(id_dicts)
                return expr1
            if op == nmlop.SUB and expr1.div is None and expr1.mod is None:
                if expr1.add is None:
                    expr1.add = ConstantNumeric(0)
                expr1.add = nmlop.SUB(expr1.add, expr2, self.pos).reduce(id_dicts)
                return expr1
            # The div and mod fields cannot be used at the same time. Also whenever either of those
            # two are used the add field has to be set, so we change it to zero when it's not yet set.
            if op == nmlop.DIV and expr1.div is None and expr1.mod is None:
                if expr1.add is None:
                    expr1.add = ConstantNumeric(0)
                expr1.div = expr2
                return expr1
            if op == nmlop.MOD and expr1.div is None and expr1.mod is None:
                if expr1.add is None:
                    expr1.add = ConstantNumeric(0)
                expr1.mod = expr2
                return expr1
            # Since we have a lot of nml-variables that are in fact only the high bits of an nfo
            # variable it can happen that we want to shift back the variable to the left.
            # Don't use any extra opcodes but just reduce the shift-right in that case.
            if (
                op == nmlop.SHIFT_LEFT
                and isinstance(expr2, ConstantNumeric)
                and expr1.add is None
                and expr2.value < expr1.shift.value
            ):
                expr1.shift.value -= expr2.value
                expr1.mask = nmlop.SHIFT_LEFT(expr1.mask, expr2).reduce()
                return expr1

        # - Try to merge multiple additions/subtractions with constant numbers
        if (
            op in (nmlop.ADD, nmlop.SUB)
            and isinstance(expr2, ConstantNumeric)
            and isinstance(expr1, BinOp)
            and expr1.op in (nmlop.ADD, nmlop.SUB)
            and isinstance(expr1.expr2, ConstantNumeric)
        ):
            val = expr2.value if op == nmlop.ADD else -expr2.value
            if expr1.op == nmlop.ADD:
                return nmlop.ADD(expr1.expr1, (expr1.expr2.value + val), self.pos).reduce()
            if expr1.op == nmlop.SUB:
                return nmlop.SUB(expr1.expr1, (expr1.expr2.value - val), self.pos).reduce()

        if op == nmlop.OR and isinstance(expr1, Boolean) and isinstance(expr2, Boolean):
            return Boolean(nmlop.OR(expr1.expr, expr2.expr, self.pos)).reduce(id_dicts)

        return BinOp(op, expr1, expr2, self.pos)