Пример #1
0
    def preprocess_binop(self, expr):
        """
        Several nml operators are not directly support by nfo so we have to work
        around that by implementing those operators in terms of others.

        @return: A pre-processed version of the expression.
        @rtype:  L{Expression}
        """
        assert isinstance(expr, expression.BinOp)
        if expr.op == nmlop.CMP_LT:
            # return value is 0, 1 or 2, we want to map 0 to 1 and the others to 0
            expr = nmlop.VACT2_CMP(expr.expr1, expr.expr2)
            # reduce the problem to 0/1
            expr = nmlop.MIN(expr, 1)
            # and invert the result
            expr = nmlop.XOR(expr, 1)
        elif expr.op == nmlop.CMP_GT:
            # return value is 0, 1 or 2, we want to map 2 to 1 and the others to 0
            expr = nmlop.VACT2_CMP(expr.expr1, expr.expr2)
            # subtract one
            expr = nmlop.SUB(expr, 1)
            # map -1 and 0 to 0
            expr = nmlop.MAX(expr, 0)
        elif expr.op == nmlop.CMP_LE:
            # return value is 0, 1 or 2, we want to map 2 to 0 and the others to 1
            expr = nmlop.VACT2_CMP(expr.expr1, expr.expr2)
            # swap 0 and 2
            expr = nmlop.XOR(expr, 2)
            # map 1/2 to 1
            expr = nmlop.MIN(expr, 1)
        elif expr.op == nmlop.CMP_GE:
            # return value is 0, 1 or 2, we want to map 1/2 to 1
            expr = nmlop.VACT2_CMP(expr.expr1, expr.expr2)
            expr = nmlop.MIN(expr, 1)
        elif expr.op == nmlop.CMP_EQ:
            # return value is 0, 1 or 2, we want to map 1 to 1, other to 0
            expr = nmlop.VACT2_CMP(expr.expr1, expr.expr2)
            expr = nmlop.AND(expr, 1)
        elif expr.op == nmlop.CMP_NEQ:
            # same as CMP_EQ but invert the result
            expr = nmlop.VACT2_CMP(expr.expr1, expr.expr2)
            expr = nmlop.AND(expr, 1)
            expr = nmlop.XOR(expr, 1)

        elif expr.op == nmlop.HASBIT:
            # hasbit(x, n) ==> (x >> n) & 1
            expr = nmlop.SHIFTU_RIGHT(expr.expr1, expr.expr2)
            expr = nmlop.AND(expr, 1)
        elif expr.op == nmlop.NOTHASBIT:
            # !hasbit(x, n) ==> ((x >> n) & 1) ^ 1
            expr = nmlop.SHIFTU_RIGHT(expr.expr1, expr.expr2)
            expr = nmlop.AND(expr, 1)
            expr = nmlop.XOR(expr, 1)

        return expr.reduce()
Пример #2
0
def tile_offset(name, args, pos, info, min, max):
    if len(args) != 2:
        raise generic.ScriptError("'{}'() requires 2 arguments, encountered {:d}".format(name, len(args)), pos)
    for arg in args:
        if isinstance(arg, expression.ConstantNumeric):
            generic.check_range(arg.value, min, max, "Argument of '{}'".format(name), arg.pos)

    x = nmlop.AND(args[0], 0xF)
    y = nmlop.AND(args[1], 0xF)
    # Shift y left by four
    y = nmlop.SHIFT_LEFT(y, 4)
    param = nmlop.ADD(x, y)
    #Make sure to reduce the result
    return ( param.reduce(), [] )
Пример #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, [])
Пример #4
0
def builtin_slope_to_sprite_offset(name, args, pos):
    """
    builtin function slope_to_sprite_offset(slope)

    @return sprite offset to use
    """
    if len(args) != 1:
        raise generic.ScriptError(name + "() must have 1 parameter", pos)

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

    # step 1: ((slope >= 0) & (slope <= 14)) * slope
    # This handles all non-steep slopes
    expr = nmlop.AND(nmlop.CMP_LE(args[0], 14, pos),
                     nmlop.CMP_GE(args[0], 0, pos))
    expr = nmlop.MUL(expr, args[0])
    # Now handle the steep slopes separately
    # So add (slope == SLOPE_XX) * offset_of_SLOPE_XX for each steep slope
    steep_slopes = [(23, 16), (27, 17), (29, 15), (30, 18)]
    for slope, offset in steep_slopes:
        to_add = nmlop.MUL(nmlop.CMP_EQ(args[0], slope, pos), offset)
        expr = nmlop.ADD(expr, to_add)
    return expr
Пример #5
0
def signed_byte_parameter(name, args, pos, info):
    # Convert to a signed byte by AND-ing with 0xFF
    if len(args) != 1:
        raise generic.ScriptError("{}() requires one argument, encountered {:d}".format(name, len(args)), pos)
    if isinstance(args[0], expression.ConstantNumeric):

        generic.check_range(args[0].value, -128, 127, "parameter of {}()".format(name), pos)
    ret = nmlop.AND(args[0], 0xFF, pos).reduce()
    return (ret, [])
Пример #6
0
def industry_layout_count(name, args, pos, info):
    if len(args) < 2 or len(args) > 3:
        raise generic.ScriptError("'{}'() requires between 2 and 3 argument(s), encountered {:d}".format(name, len(args)), pos)

    grfid = expression.ConstantNumeric(0xFFFFFFFF) if len(args) == 2 else args[2]

    extra_params = []
    extra_params.append( (0x100, grfid) )
    extra_params.append( (0x101, nmlop.AND(args[1], 0xFF).reduce()) )
    return (args[0], extra_params)
Пример #7
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)
Пример #8
0
def builtin_num_corners_raised(name, args, pos):
    """
    num_corners_raised(slope) builtin function.
    slope is a 5-bit value

    @return Number of raised corners in a slope (4 for steep slopes)
    """
    if len(args) != 1:
        raise generic.ScriptError(name + "() must have 1 parameter", pos)

    slope = args[0]
    # The returned value is ((slope x 0x8421) & 0x11111) % 0xF
    # Explanation in steps: (numbers in binary)
    # - Masking constrains the slope to 5 bits, just to be sure (a|bcde)
    # - Multiplication creates 4 copies of those bits (abcd|eabc|deab|cdea|bcde)
    # - And-masking leaves only the lowest bit in each nibble (000d|000c|000b|000a|000e)
    # - The modulus operation adds one to the output for each set bit
    # - We now have the count of bits in the slope, which is wat we want. yay!
    slope = nmlop.AND(slope, 0x1F, pos)
    slope = nmlop.MUL(slope, 0x8421)
    slope = nmlop.AND(slope, 0x11111)
    return nmlop.MOD(slope, 0xF)
Пример #9
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)
Пример #10
0
def builtin_getbits(name, args, pos):
    """
    getbits(value, first, amount) builtin function.

    @return Extract C{amount} bits starting at C{first} from C{value},
            that is (C{value} >> C{first}) & (1 << C{amount} - 1)
    """
    if len(args) != 3:
        raise generic.ScriptError(name + "() must have exactly three parameters", pos)

    # getbits(value, first, amount) = (value >> first) & ((0xFFFFFFFF << amount) ^ 0xFFFFFFFF)
    part1 = nmlop.SHIFTU_RIGHT(args[0], args[1], pos)
    part2 = nmlop.SHIFT_LEFT(0xFFFFFFFF, args[2], pos)
    part3 = nmlop.XOR(part2, 0xFFFFFFFF, pos)

    return nmlop.AND(part1, part3, pos)
Пример #11
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)])
Пример #12
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
Пример #13
0
 'num_vehs_in_vehid_chain'          : {'var': 0x41, 'start': 16, 'size':  8}, # One-based, already sane
 'cargo_classes_in_consist'         : {'var': 0x42, 'start':  0, 'size':  8},
 'most_common_cargo_type'           : {'var': 0x42, 'start':  8, 'size':  8},
 'most_common_cargo_subtype'        : {'var': 0x42, 'start': 16, 'size':  8},
 'bitmask_consist_info'             : {'var': 0x42, 'start': 24, 'size':  8},
 'company_num'                      : {'var': 0x43, 'start':  0, 'size':  8},
 'company_type'                     : {'var': 0x43, 'start': 16, 'size':  2},
 'company_colour1'                  : {'var': 0x43, 'start': 24, 'size':  4},
 'company_colour2'                  : {'var': 0x43, 'start': 28, 'size':  4},
 'aircraft_height'                  : {'var': 0x44, 'start':  8, 'size':  8},
 'airport_type'                     : {'var': 0x44, 'start':  0, 'size':  8},
 'curv_info_prev_cur'               : {'var': 0x45, 'start':  0, 'size':  4, 'value_function': value_sign_extend},
 'curv_info_cur_next'               : {'var': 0x45, 'start':  8, 'size':  4, 'value_function': value_sign_extend},
 'curv_info_prev_next'              : {'var': 0x45, 'start': 16, 'size':  4, 'value_function': value_sign_extend},
 'curv_info'                        : {'var': 0x45, 'start':  0, 'size': 12,
         'value_function': lambda var, info: nmlop.AND(var, 0x0F0F).reduce()},
 'motion_counter'                   : {'var': 0x46, 'start':  8, 'size': 24},
 'cargo_type_in_veh'                : {'var': 0x47, 'start':  0, 'size':  8},
 'cargo_unit_weight'                : {'var': 0x47, 'start':  8, 'size':  8},
 'cargo_classes'                    : {'var': 0x47, 'start': 16, 'size': 16},
 'vehicle_is_available'             : {'var': 0x48, 'start':  0, 'size':  1},
 'vehicle_is_testing'               : {'var': 0x48, 'start':  1, 'size':  1},
 'vehicle_is_offered'               : {'var': 0x48, 'start':  2, 'size':  1},
 'build_year'                       : {'var': 0x49, 'start':  0, 'size': 32},
 'vehicle_is_potentially_powered'   : {'var': 0x4A, 'start':  8, 'size':  1},
 'tile_has_catenary'                : {'var': 0x4A, 'start':  9, 'size':  1},
 'date_of_last_service'             : {'var': 0x4B, 'start':  0, 'size': 32},
 'position_in_articulated_veh'          : {'var': 0x4D, 'start':  0, 'size':  8},
 'position_in_articulated_veh_from_end' : {'var': 0x4D, 'start':  8, 'size':  8},
 'waiting_triggers'                 : {'var': 0x5F, 'start':  0, 'size':  8},
 'random_bits'                      : {'var': 0x5F, 'start':  8, 'size':  8},
Пример #14
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)