def parse_abs_op(assignment): assert isinstance(assignment.value, expression.AbsOp) actions = parse_actionD(ParameterAssignment(assignment.param, assignment.value.expr)) cond_block = conditional.Conditional( nmlop.CMP_LT(assignment.value.expr, 0), [ParameterAssignment(assignment.param, nmlop.SUB(0, assignment.value.expr))], None, ) actions.extend(conditional.ConditionalList([cond_block]).get_action_list()) return actions
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()
def builtin_date(name, args, pos): """ date(year, month, day) builtin function. @return Days since 1 jan 1 of the given date. """ days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] if len(args) != 3: raise generic.ScriptError("date() requires exactly 3 arguments", pos) identifier.ignore_all_invalid_ids = True year = args[0].reduce(global_constants.const_list) identifier.ignore_all_invalid_ids = False try: month = args[1].reduce_constant().value day = args[2].reduce_constant().value except generic.ConstError: raise generic.ScriptError( "Month and day parameters of date() should be compile-time constants", pos) generic.check_range(month, 1, 12, "month", args[1].pos) generic.check_range(day, 1, days_in_month[month - 1], "day", args[2].pos) if not isinstance(year, ConstantNumeric): if month != 1 or day != 1: raise generic.ScriptError( "when the year parameter of date() is not a compile time constant month and day should be 1", pos) # num_days = year*365 + year/4 - year/100 + year/400 part1 = nmlop.MUL(year, 365) part2 = nmlop.DIV(year, 4) part3 = nmlop.DIV(year, 100) part4 = nmlop.DIV(year, 400) res = nmlop.ADD(part1, part2) res = nmlop.SUB(res, part3) res = nmlop.ADD(res, part4) return res generic.check_range(year.value, 0, 5000000, "year", year.pos) day_in_year = 0 for i in range(month - 1): day_in_year += days_in_month[i] day_in_year += day if month >= 3 and (year.value % 4 == 0) and ((not year.value % 100 == 0) or (year.value % 400 == 0)): day_in_year += 1 return ConstantNumeric( year.value * 365 + calendar.leapdays(0, year.value) + day_in_year - 1, pos)
def parse_actionD(assignment): assignment.value.supported_by_actionD(True) if isinstance(assignment.param, expression.SpecialParameter): assignment.param, assignment.value = assignment.param.to_assignment(assignment.value) elif isinstance(assignment.param, expression.Identifier): if global_constants.identifier_refcount[assignment.param.value] == 0: # Named parameter is not referenced, ignoring return [] assignment.param = expression.Parameter( expression.ConstantNumeric(global_constants.named_parameters[assignment.param.value]), assignment.param.pos ) assert isinstance(assignment.param, expression.Parameter) if isinstance(assignment.value, expression.SpecialParameter): assignment.value = assignment.value.to_reading() if isinstance(assignment.value, expression.TernaryOp): return parse_ternary_op(assignment) if isinstance(assignment.value, expression.AbsOp): return parse_abs_op(assignment) if isinstance(assignment.value, expression.SpecialCheck): return parse_special_check(assignment) if isinstance(assignment.value, expression.GRMOp): return parse_grm(assignment) if isinstance(assignment.value, expression.BinOp): op = assignment.value.op if op == nmlop.HASBIT or op == nmlop.NOTHASBIT: return parse_hasbit(assignment) elif op == nmlop.MIN or op == nmlop.MAX: return parse_min_max(assignment) if isinstance(assignment.value, expression.Boolean): return parse_boolean(assignment) if isinstance(assignment.value, expression.Not): expr = nmlop.SUB(1, assignment.value.expr) assignment = ParameterAssignment(assignment.param, expr) if isinstance(assignment.value, expression.BinNot): expr = nmlop.SUB(0xFFFFFFFF, assignment.value.expr) assignment = ParameterAssignment(assignment.param, expr) action6.free_parameters.save() action_list = [] act6 = action6.Action6() assert isinstance(assignment.param, expression.Parameter) target = assignment.param.num if isinstance(target, expression.Parameter) and isinstance(target.num, expression.ConstantNumeric): act6.modify_bytes(target.num.value, 1, 1) target = expression.ConstantNumeric(0) elif not isinstance(target, expression.ConstantNumeric): tmp_param, tmp_param_actions = get_tmp_parameter(target) act6.modify_bytes(tmp_param, 1, 1) target = expression.ConstantNumeric(0) action_list.extend(tmp_param_actions) data = None # print assignment.value if isinstance(assignment.value, expression.ConstantNumeric): op = nmlop.ASSIGN param1 = expression.ConstantNumeric(0xFF) param2 = expression.ConstantNumeric(0) data = assignment.value elif isinstance(assignment.value, expression.Parameter): if isinstance(assignment.value.num, expression.ConstantNumeric): op = nmlop.ASSIGN param1 = assignment.value.num else: tmp_param, tmp_param_actions = get_tmp_parameter(assignment.value.num) act6.modify_bytes(tmp_param, 1, 3) action_list.extend(tmp_param_actions) op = nmlop.ASSIGN param1 = expression.ConstantNumeric(0) param2 = expression.ConstantNumeric(0) elif isinstance(assignment.value, expression.OtherGRFParameter): op = nmlop.ASSIGN if isinstance(assignment.value.num, expression.ConstantNumeric): param1 = assignment.value.num else: tmp_param, tmp_param_actions = get_tmp_parameter(assignment.value.num) act6.modify_bytes(tmp_param, 1, 3) action_list.extend(tmp_param_actions) param1 = expression.ConstantNumeric(0) param2 = expression.ConstantNumeric(0xFE) data = expression.ConstantNumeric(expression.parse_string_to_dword(assignment.value.grfid)) elif isinstance(assignment.value, expression.PatchVariable): op = nmlop.ASSIGN param1 = expression.ConstantNumeric(assignment.value.num) param2 = expression.ConstantNumeric(0xFE) data = expression.ConstantNumeric(0xFFFF) elif isinstance(assignment.value, expression.BinOp): op, expr1, expr2, extra_actions = transform_bin_op(assignment) action_list.extend(extra_actions) if isinstance(expr1, expression.ConstantNumeric): param1 = expression.ConstantNumeric(0xFF) data = expr1 elif isinstance(expr1, expression.Parameter) and isinstance(expr1.num, expression.ConstantNumeric): param1 = expr1.num else: tmp_param, tmp_param_actions = get_tmp_parameter(expr1) action_list.extend(tmp_param_actions) param1 = expression.ConstantNumeric(tmp_param) # We can use the data only for one for the parameters. # If the first parameter uses "data" we need a temp parameter for this one if isinstance(expr2, expression.ConstantNumeric) and data is None: param2 = expression.ConstantNumeric(0xFF) data = expr2 elif isinstance(expr2, expression.Parameter) and isinstance(expr2.num, expression.ConstantNumeric): param2 = expr2.num else: tmp_param, tmp_param_actions = get_tmp_parameter(expr2) action_list.extend(tmp_param_actions) param2 = expression.ConstantNumeric(tmp_param) else: raise generic.ScriptError("Invalid expression in argument assignment", assignment.value.pos) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(ActionD(target, param1, op, param2, data)) action6.free_parameters.restore() return action_list
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
def builtin_abs(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) guard = nmlop.CMP_LT(args[0], 0) return TernaryOp(guard, nmlop.SUB(0, args[0]), args[0], args[0].pos).reduce()
def p_unary_minus(self, t): "expression : MINUS expression" t[0] = nmlop.SUB(0, t[2], t.lineno(1))
def value_sign_extend(var, info): #r = (x ^ m) - m; with m being (1 << (num_bits -1)) m = expression.ConstantNumeric(1 << (info['size'] - 1)) return nmlop.SUB(nmlop.XOR(var, m), m)
def vehicle_length(value): return nmlop.SUB(8, value)
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)