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 cargo_profit_value(value): # In NFO, calculation is (amount * price_factor * cb_result) / 8192 # Units of the NML price factor differ by about 41.12, i.e. 1 NML unit = 41 NFO units # That'd make the formula (amount * price_factor * cb_result) / (8192 / 41) # This is almost (error 0.01%) equivalent to the following, which is what this calculation does # (amount * price_factor * (cb_result * 329 / 256)) / 256 # This allows us to report a factor of 256 in the documentation, which makes a lot more sense than 199.804... # Not doing the division here would improve accuracy, but limits the range of the return value too much value = nmlop.MUL(value, 329) return nmlop.DIV(value, 256)
def parse_property_value(prop_info, value, unit=None, size_bit=None): """ Parse a single property value / unit To determine the value that is to be used in nfo @param prop_info: A dictionary with property information @type prop_info: C{dict} @param value: Value of the property @type value: L{Expression} @param unit: Unit of the property value (e.g. km/h) @type unit: L{Unit} or C{None} @param size_bit: Bit that indicates the size of a multitile house Set iff the item is a house @type size_bit: C{int} or C{None} @return: List of values to actually use (in nfo) for the property @rtype: L{Expression} """ # Change value to use, except when the 'nfo' unit is used if unit is None or unit.type != "nfo": # Save the original value to test conversion against it org_value = value # Multiply by property-specific conversion factor mul, div = 1, 1 if "unit_conversion" in prop_info: mul = prop_info["unit_conversion"] if isinstance(mul, tuple): mul, div = mul # Divide by conversion factor specified by unit if unit is not None: if "unit_type" not in prop_info or unit.type != prop_info[ "unit_type"]: raise generic.ScriptError("Invalid unit for property", value.pos) unit_mul, unit_div = unit.convert, 1 if isinstance(unit_mul, tuple): unit_mul, unit_div = unit_mul mul *= unit_div div *= unit_mul # Factor out common factors gcd = generic.greatest_common_divisor(mul, div) mul //= gcd div //= gcd if isinstance(value, (expression.ConstantNumeric, expression.ConstantFloat)): # Even if mul == div == 1, we have to round floats and adjust value value = expression.ConstantNumeric( int(float(value.value) * mul / div + 0.5), value.pos) if unit is not None and "adjust_value" in prop_info: value = adjust_value(value, org_value, unit, prop_info["adjust_value"]) elif mul != div: # Compute (value * mul + div/2) / div value = nmlop.MUL(value, mul) value = nmlop.ADD(value, int(div / 2)) value = nmlop.DIV(value, div) elif isinstance(value, expression.ConstantFloat): # Round floats to ints value = expression.ConstantNumeric(int(value.value + 0.5), value.pos) # Apply value_function if it exists if "value_function" in prop_info: value = prop_info["value_function"](value) # Make multitile houses work if size_bit is not None: num_ids = house_sizes[size_bit] assert "multitile_function" in prop_info ret = prop_info["multitile_function"](value, num_ids, size_bit) assert len(ret) == num_ids return ret else: return [value]
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 value_mul_div(mul, div): return lambda var, info: nmlop.DIV(nmlop.MUL(var, mul), div)
def compute_table(snowline): """ Compute the table with snowline height for each day of the year. @param snowline: Snowline definition. @type snowline: L{Snowline} @return: Table of 12*32 entries with snowline heights. @rtype: C{str} """ day_table = [None] * 365 # Height at each day, starting at day 0 for dh in snowline.date_heights: doy = dh.name.reduce() if not isinstance(doy, expression.ConstantNumeric): raise generic.ScriptError( "Day of year is not a compile-time constant", doy.pos) if doy.value < 1 or doy.value > 365: raise generic.ScriptError( "Day of the year must be between 1 and 365", doy.pos) height = dh.value.reduce() if isinstance(height, expression.ConstantNumeric) and height.value < 0: raise generic.ScriptError("Height must be at least 0", height.pos) if dh.unit is None: if isinstance(height, expression.ConstantNumeric) and height.value > 255: raise generic.ScriptError("Height must be at most 255", height.pos) else: unit = dh.unit if unit.type != "snowline": raise generic.ScriptError( 'Expected a snowline percentage ("snow%")', height.pos) if isinstance(height, expression.ConstantNumeric) and height.value > 100: raise generic.ScriptError("Height must be at most 100 snow%", height.pos) mul, div = unit.convert, 1 if isinstance(mul, tuple): mul, div = mul # Factor out common factors gcd = generic.greatest_common_divisor(mul, div) mul //= gcd div //= gcd if isinstance( height, (expression.ConstantNumeric, expression.ConstantFloat)): # Even if mul == div == 1, we have to round floats and adjust value height = expression.ConstantNumeric( int(float(height.value) * mul / div + 0.5), height.pos) elif mul != div: # Compute (value * mul + div/2) / div height = nmlop.MUL(height, mul) height = nmlop.ADD(height, int(div / 2)) height = nmlop.DIV(height, div) # For 'linear' snow-line, only accept integer constants. if snowline.type != "equal" and not isinstance( height, expression.ConstantNumeric): raise generic.ScriptError("Height is not a compile-time constant", height.pos) day_table[doy.value - 1] = height # Find first specified point. start = 0 while start < 365 and day_table[start] is None: start = start + 1 if start == 365: raise generic.ScriptError("No heights given for the snowline table", snowline.pos) first_point = start while True: # Find second point from start end = start + 1 if end == 365: end = 0 while end != first_point and day_table[end] is None: end = end + 1 if end == 365: end = 0 # Fill the days between start and end (exclusive both border values) startvalue = day_table[start] endvalue = day_table[end] unwrapped_end = end if end < start: unwrapped_end += 365 if snowline.type == "equal": for day in range(start + 1, unwrapped_end): if day >= 365: day -= 365 day_table[day] = startvalue else: assert snowline.type == "linear" if start != end: dhd = float(endvalue.value - startvalue.value) / float(unwrapped_end - start) else: assert startvalue.value == endvalue.value dhd = 0 for day in range(start + 1, unwrapped_end): uday = day if uday >= 365: uday -= 365 height = startvalue.value + int(round(dhd * (day - start))) day_table[uday] = expression.ConstantNumeric(height) if end == first_point: # All days done break start = end table = [None] * (12 * 32) for dy in range(365): today = datetime.date.fromordinal(dy + 1) if day_table[dy]: expr = day_table[dy].reduce() else: expr = None table[(today.month - 1) * 32 + today.day - 1] = expr for idx, d in enumerate(table): if d is None: table[idx] = table[idx - 1] # Second loop is needed because we need make sure the first item is also set. for idx, d in enumerate(table): if d is None: table[idx] = table[idx - 1] return table