diff --git a/.gitignore b/.gitignore index 05ea37876..5d4d35fa8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ nml/__version__.py nml.egg-info/* nml_lz77* regression/*output*/* +.python-version diff --git a/nml/actions/action0.py b/nml/actions/action0.py index be0249100..0c8e2bde9 100644 --- a/nml/actions/action0.py +++ b/nml/actions/action0.py @@ -221,7 +221,7 @@ def print_stats(): for feature in used_ids: used = feature.get_num_allocated() if used > 0 and feature.dynamic_allocation: - generic.print_info("{} items: {}/{}".format(feature.name, used, feature.get_max_allocated())) + generic.print_info(f"{feature.name} items: {used}/{feature.get_max_allocated()}") def mark_id_used(feature, id, num_ids): @@ -263,8 +263,7 @@ def check_id_range(feature, id, num_ids, pos): # Check that IDs are valid and in range. if not blk_alloc.in_range(id, num_ids): - msg = "Item ID must be in range 0..{:d}, encountered {:d}..{:d}." - msg = msg.format(blk_alloc.last, id, id + num_ids - 1) + msg = f"Item ID must be in range 0..{blk_alloc.last}, encountered {id}..{id + num_ids - 1}." raise generic.ScriptError(msg, pos) # ID already defined, but with the same size: OK @@ -279,18 +278,15 @@ def check_id_range(feature, id, num_ids, pos): if blk_alloc.get_size(id) is not None: # ID already defined with a different size: error. - raise generic.ScriptError( - "Item with ID {:d} has already been defined, but with a different size.".format(id), pos - ) + raise generic.ScriptError(f"Item with ID {id} has already been defined, but with a different size.", pos) if blk_alloc.is_address_free(id): # First item id free -> any of the additional tile ids must be blocked. - msg = "This multi-tile house requires that item IDs {:d}..{:d} are free, but they are not." - msg = msg.format(id, id + num_ids - 1) + msg = f"This multi-tile house requires that item IDs {id}..{id + num_ids - 1} are free, but they are not." raise generic.ScriptError(msg, pos) # ID already defined as part of a multi-tile house. - raise generic.ScriptError("Item ID {:d} has already used as part of a multi-tile house.".format(id), pos) + raise generic.ScriptError(f"Item ID {id} has already used as part of a multi-tile house.", pos) def get_free_id(feature, num_ids, pos): @@ -310,8 +306,7 @@ def get_free_id(feature, num_ids, pos): addr = blk_alloc.find_unused(num_ids) if addr is None: - msg = "Unable to allocate ID for item, no more free IDs available (maximum is {:d})" - msg = msg.format(blk_alloc.last) + msg = f"Unable to allocate ID for item, no more free IDs available (maximum is {blk_alloc.last})" raise generic.ScriptError(msg, pos) blk_alloc.mark_used(addr, num_ids) @@ -449,9 +444,8 @@ def get_property_info_list(feature, name): assert feature in range(0, len(properties)) # guaranteed by item if properties[feature] is None: raise generic.ScriptError( - "Setting properties for feature '{}' is not possible, no properties are defined.".format( - general.feature_name(feature) - ), + f"Setting properties for feature '{general.feature_name(feature)}' is not possible," + " no properties are defined.", name.pos, ) @@ -615,13 +609,11 @@ def apply_threshold(value): # This can be used to set a label (=string of length 4) to the value of a parameter. if not isinstance(value, expression.StringLiteral): raise generic.ScriptError( - "Value for property {:d} must be a string literal".format(prop_info["num"]), value.pos + f"Value for property {prop_info['num']} must be a string literal", value.pos ) if grfstrings.get_string_size(value.value, False, True) != prop_info["string_literal"]: raise generic.ScriptError( - "Value for property {:d} must be of length {:d}".format( - prop_info["num"], prop_info["string_literal"] - ), + f"Value for property {prop_info['num']} must be of length {prop_info['string_literal']}", value.pos, ) @@ -635,7 +627,7 @@ def apply_threshold(value): elif isinstance(value, expression.String): if "string" not in prop_info: raise generic.ScriptError( - "String used as value for non-string property: " + str(prop_info["num"]), value.pos + f"String used as value for non-string property: {prop_info['num']}", value.pos ) string_range = apply_threshold(prop_info["string"]) stringid, string_actions = action4.get_string_action4s(feature, string_range, value, id) @@ -683,14 +675,14 @@ def validate_prop_info_list(prop_info_list, pos_list, feature): if info in prop_info: generic.print_warning( generic.Warning.GENERIC, - "Property '{}' should be set before all other properties and graphics.".format(prop_name), + f"Property '{prop_name}' should be set before all other properties and graphics.", pos, ) break for info in prop_info: if "required" in info and info not in prop_info_list: generic.print_error( - "Property '{}' is not set. Item will be invalid.".format(prop_name), + f"Property '{prop_name}' is not set. Item will be invalid.", pos_list[-1], ) @@ -1027,7 +1019,7 @@ def get_disable_actions(disable): """ feature = disable.feature.value if feature not in disable_info: - raise generic.ScriptError("disable_item() is not available for feature {:d}.".format(feature), disable.pos) + raise generic.ScriptError(f"disable_item() is not available for feature {feature}.", disable.pos) if disable.first_id is None: # No ids set -> disable all assert disable.last_id is None diff --git a/nml/actions/action0properties.py b/nml/actions/action0properties.py index 8e91963b0..79bbe9c1a 100644 --- a/nml/actions/action0properties.py +++ b/nml/actions/action0properties.py @@ -15,7 +15,7 @@ import itertools -from nml import generic, nmlop, global_constants +from nml import generic, global_constants, nmlop from nml.expression import ( AcceptCargo, Array, @@ -43,7 +43,7 @@ def write(self, file): @param file: The outputfile we have to write to. @type file: L{SpriteOutputBase} """ - raise NotImplementedError("write is not implemented in {!r}".format(type(self))) + raise NotImplementedError(f"write is not implemented in {type(self)!r}") def get_size(self): """ @@ -53,7 +53,7 @@ def get_size(self): @return: The size of this property in bytes. @rtype: C{int} """ - raise NotImplementedError("get_size is not implemented in {!r}".format(type(self))) + raise NotImplementedError(f"get_size is not implemented in {type(self)!r}") class Action0Property(BaseAction0Property): @@ -261,9 +261,7 @@ def cargo_list(value, max_num_cargos): @type prop_size: C{int} """ if not isinstance(value, Array) or len(value.values) > max_num_cargos: - raise generic.ScriptError( - "Cargo list must be an array with no more than {:d} values".format(max_num_cargos), value.pos - ) + raise generic.ScriptError(f"Cargo list must be an array with no more than {max_num_cargos} values", value.pos) cargoes = value.values + [ConstantNumeric(0xFF, value.pos) for _ in range(max_num_cargos - len(value.values))] ret = None @@ -419,7 +417,7 @@ def single_or_list(prop_name, single_num, multiple_num, value): if len(value.values) == 1: return [Action0Property(single_num, value.values[0].reduce_constant(), 1)] return [VariableByteListProp(multiple_num, [[type.reduce_constant().value for type in value.values]])] - raise generic.ScriptError("'{}' must be a constant or an array of constants".format(prop_name)) + raise generic.ScriptError(f"'{prop_name}' must be a constant or an array of constants") # @@ -785,10 +783,8 @@ def station_layouts(value): length = len(layout.values[0].values) number = len(layout.values) if (length, number) in layouts: - generic.print_warning( - generic.Warning.GENERIC, "Redefinition of layout {}x{}".format(length, number), layout.pos - ) - layouts[(length, number)] = [] + generic.print_warning(generic.Warning.GENERIC, f"Redefinition of layout {length}x{number}", layout.pos) + layouts[length, number] = [] for platform in layout.values: if not isinstance(platform, Array) or len(platform.values) == 0: raise generic.ScriptError("A platform must be an array of tile types") @@ -798,10 +794,10 @@ def station_layouts(value): if type.reduce_constant().value % 2 != 0: generic.print_warning( generic.Warning.GENERIC, - "Invalid tile {} in layout {}x{}".format(type, length, number), + f"Invalid tile {type} in layout {length}x{number}", type.pos, ) - layouts[(length, number)].append( + layouts[length, number].append( [nmlop.AND(type, 0xFE).reduce_constant().value for type in platform.values] ) return [StationLayoutProp(0x0E, layouts)] @@ -1127,7 +1123,7 @@ def industry_layouts(value): layouts = [] for name in value.values: if name.value not in tilelayout_names: - raise generic.ScriptError("Unknown layout name '{}'".format(name.value), name.pos) + raise generic.ScriptError(f"Unknown layout name '{name.value}'", name.pos) layouts.append(tilelayout_names[name.value]) return [IndustryLayoutProp(layouts)] @@ -1443,7 +1439,7 @@ def airport_layouts(value): layouts = [] for name in value.values: if name.value not in tilelayout_names: - raise generic.ScriptError("Unknown layout name '{}'".format(name.value), name.pos) + raise generic.ScriptError(f"Unknown layout name '{name.value}'", name.pos) layout = tilelayout_names[name.value] if "rotation" not in layout.properties: raise generic.ScriptError("Airport layouts must have the 'rotation' property", layout.pos) diff --git a/nml/actions/action1.py b/nml/actions/action1.py index fcdf472eb..b439602c7 100644 --- a/nml/actions/action1.py +++ b/nml/actions/action1.py @@ -13,7 +13,7 @@ with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" -from nml.actions import base_action, real_sprite, action2 +from nml.actions import action2, base_action, real_sprite class Action1(base_action.BaseAction): @@ -231,10 +231,13 @@ def get_action1_index(spriteset, feature): @rtype: C{int} """ assert feature in spriteset_collections + act1_idx = None for spriteset_collection in spriteset_collections[feature]: if spriteset in spriteset_collection.spritesets: - return spriteset_collection.get_index(spriteset) - assert False + act1_idx = spriteset_collection.get_index(spriteset) + break + assert act1_idx is not None + return act1_idx def make_cb_failure_action1(feature): diff --git a/nml/actions/action11.py b/nml/actions/action11.py index 6cf698d79..e977fb092 100644 --- a/nml/actions/action11.py +++ b/nml/actions/action11.py @@ -1,3 +1,7 @@ +""" +Action 11 support classes (sounds). +""" + __license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -13,9 +17,6 @@ with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" -""" -Action 11 support classes (sounds). -""" import os from nml import generic @@ -45,12 +46,10 @@ def __init__(self, fname, pos): def prepare_output(self, sprite_num): if not os.access(self.fname, os.R_OK): - raise generic.ScriptError("Sound file '{}' does not exist.".format(self.fname), self.pos) + raise generic.ScriptError(f"Sound file '{self.fname}' does not exist.", self.pos) size = os.path.getsize(self.fname) if size == 0: - raise generic.ScriptError( - "Expected sound file '{}' to have a non-zero length.".format(self.fname), self.pos - ) + raise generic.ScriptError(f"Expected sound file '{self.fname}' to have a non-zero length.", self.pos) def write(self, file): file.print_named_filedata(self.fname) @@ -102,7 +101,7 @@ def print_stats(): """ if len(registered_sounds) > 0: # Currently NML does not optimise the order of sound effects. So we assume NUM_ANIMATION_SOUNDS as the maximum. - generic.print_info("Sound effects: {}/{}".format(len(registered_sounds), NUM_ANIMATION_SOUNDS)) + generic.print_info(f"Sound effects: {len(registered_sounds)}/{NUM_ANIMATION_SOUNDS}") def add_sound(args, pos): diff --git a/nml/actions/action14.py b/nml/actions/action14.py index 1d5a45a08..3cc7ab5fe 100644 --- a/nml/actions/action14.py +++ b/nml/actions/action14.py @@ -87,7 +87,7 @@ def get_size(self): @return: The size (in bytes) of this node. """ - raise NotImplementedError("get_size must be implemented in Action14Node-subclass {!r}".format(type(self))) + raise NotImplementedError(f"get_size must be implemented in Action14Node-subclass {type(self)!r}") def write(self, file): """ @@ -95,7 +95,7 @@ def write(self, file): @param file: The file to write the output to. """ - raise NotImplementedError("write must be implemented in Action14Node-subclass {!r}".format(type(self))) + raise NotImplementedError(f"write must be implemented in Action14Node-subclass {type(self)!r}") def write_type_id(self, file): file.print_string(self.type_string, False, True) @@ -266,7 +266,7 @@ def param_desc_actions(root, params): if min_val > max_val or def_val < min_val or def_val > max_val: generic.print_warning( generic.Warning.GENERIC, - "Limits for GRF parameter {} are incoherent, ignoring.".format(param_num), + f"Limits for GRF parameter {param_num} are incoherent, ignoring.", ) min_val = 0 max_val = 0xFFFFFFFF diff --git a/nml/actions/action2.py b/nml/actions/action2.py index 0ccdf2950..92b869b3f 100644 --- a/nml/actions/action2.py +++ b/nml/actions/action2.py @@ -44,15 +44,11 @@ def print_stats(): """ if spritegroup_stats[0] > 0: generic.print_info( - "Concurrent spritegroups: {}/{} ({})".format( - spritegroup_stats[0], total_action2_ids, str(spritegroup_stats[1]) - ) + f"Concurrent spritegroups: {spritegroup_stats[0]}/{total_action2_ids} ({spritegroup_stats[1]})" ) if a2register_stats[0] > 0: generic.print_info( - "Concurrent Action2 registers: {}/{} ({})".format( - a2register_stats[0], total_tmp_locations, str(a2register_stats[1]) - ) + f"Concurrent Action2 registers: {a2register_stats[0]}/{total_tmp_locations} ({a2register_stats[1]})" ) @@ -117,7 +113,7 @@ def prepare_output(self, sprite_num): ) def write_sprite_start(self, file, size, extra_comment=None): - assert self.num_refs == 0, "Action2 reference counting has {:d} dangling references.".format(self.num_refs) + assert self.num_refs == 0, f"Action2 reference counting has {self.num_refs} dangling references." file.comment("Name: " + self.name) if extra_comment: for c in extra_comment: @@ -298,10 +294,8 @@ def __init__(self): Instead, call initialize(...). """ raise NotImplementedError( - ( - "__init__ must be implemented in ASTSpriteGroup-subclass {!r}," - " initialize(..) should be called instead" - ).format(type(self)) + f"__init__ must be implemented in ASTSpriteGroup-subclass {type(self)!r}," + " initialize(..) should be called instead" ) def initialize(self, name=None, feature=None, num_params=0): @@ -389,7 +383,7 @@ def prepare_act2_output(self): assert self.name is not None generic.print_warning( generic.Warning.OPTIMISATION, - "Block '{}' is not referenced, ignoring.".format(self.name.value), + f"Block '{self.name.value}' is not referenced, ignoring.", self.pos, ) @@ -433,7 +427,7 @@ def collect_references(self): @rtype: C{iterable} of L{SpriteGroupRef} """ raise NotImplementedError( - "collect_references must be implemented in ASTSpriteGroup-subclass {!r}".format(type(self)) + f"collect_references must be implemented in ASTSpriteGroup-subclass {type(self)!r}" ) def set_action2(self, action2, feature): @@ -492,16 +486,14 @@ def _add_reference(self, target_ref): # Passing parameters is not possible here if len(target_ref.param_list) != 0: raise generic.ScriptError( - "Passing parameters to '{}' is only possible from a spritelayout.".format( - target_ref.name.value - ), + f"Passing parameters to '{target_ref.name.value}' is only possible from a spritelayout.", target_ref.pos, ) self.used_sprite_sets.append(target) else: if len(target_ref.param_list) != target.num_params: - msg = "'{}' expects {:d} parameters, encountered {:d}." + msg = "'{}' expects {} parameters, encountered {}." msg = msg.format(target_ref.name.value, target.num_params, len(target_ref.param_list)) raise generic.ScriptError(msg, target_ref.pos) @@ -543,7 +535,7 @@ def register_spritegroup(spritegroup): """ name = spritegroup.name.value if name in spritegroup_list: - raise generic.ScriptError("Block with name '{}' has already been defined".format(name), spritegroup.pos) + raise generic.ScriptError(f"Block with name '{name}' has already been defined", spritegroup.pos) spritegroup_list[name] = spritegroup global_constants.spritegroups[name] = name @@ -559,5 +551,5 @@ def resolve_spritegroup(name): @rtype: L{ASTSpriteGroup} """ if name.value not in spritegroup_list: - raise generic.ScriptError("Unknown identifier encountered: '{}'".format(name.value), name.pos) + raise generic.ScriptError(f"Unknown identifier encountered: '{name.value}'", name.pos) return spritegroup_list[name.value] diff --git a/nml/actions/action2layout.py b/nml/actions/action2layout.py index 1371e6340..e632d1000 100644 --- a/nml/actions/action2layout.py +++ b/nml/actions/action2layout.py @@ -39,7 +39,7 @@ def resolve_tmp_storage(self): def write(self, file): size = self.layout.get_size() - regs = ["{} : register {:X}".format(reg.name, reg.register) for reg in self.param_registers] + regs = [f"{reg.name} : register {reg.register:X}" for reg in self.param_registers] action2.Action2.write_sprite_start(self, file, size, regs) self.layout.write(file) file.end_sprite() @@ -231,9 +231,9 @@ def set_param(self, name, value): name = name.value if name not in self.params: - raise generic.ScriptError("Unknown sprite parameter '{}'".format(name), value.pos) + raise generic.ScriptError(f"Unknown sprite parameter '{name}'", value.pos) if self.is_set(name): - raise generic.ScriptError("Sprite parameter '{}' can be set only once per sprite.".format(name), value.pos) + raise generic.ScriptError(f"Sprite parameter '{name}' can be set only once per sprite.", value.pos) self.params[name]["value"] = self.params[name]["validator"](name, value) self.params[name]["is_set"] = True @@ -371,9 +371,7 @@ def _validate_bounding_box(self, name, value): else: assert name in ("xextent", "yextent", "zextent") if not isinstance(value, expression.ConstantNumeric): - raise generic.ScriptError( - "Value of '{}' must be a compile-time constant number.".format(name), value.pos - ) + raise generic.ScriptError(f"Value of '{name}' must be a compile-time constant number.", value.pos) generic.check_range(value.value, 0, 255, name, value.pos) return value # Value must be written to a register @@ -472,9 +470,7 @@ def process(self, spritelayout, feature, param_map, actions, layout_registers, v for type, pos, param_list in layout_sprite_list: if type.value not in layout_sprite_types: raise generic.ScriptError( - "Invalid sprite type '{}' encountered. Expected 'ground', 'building', or 'childsprite'.".format( - type.value - ), + f"Invalid sprite type '{type.value}' encountered. Expected 'ground', 'building', or 'childsprite'.", type.pos, ) sprite = Action2LayoutSprite(feature, layout_sprite_types[type.value], layout_registers, pos, param_map) @@ -535,9 +531,7 @@ def get_layout_action2s(spritelayout, feature): actions = [] if feature not in action2.features_sprite_layout: - raise generic.ScriptError( - "Sprite layouts are not supported for feature '{}'.".format(general.feature_name(feature)) - ) + raise generic.ScriptError(f"Sprite layouts are not supported for feature '{general.feature_name(feature)}'.") # Allocate registers param_map = {} @@ -563,7 +557,7 @@ def get_layout_action2s(spritelayout, feature): layout_action = Action2Layout( feature, - spritelayout.name.value + " - feature {:02X}".format(feature), + spritelayout.name.value + f" - feature {feature:02X}", spritelayout.pos, layout, param_registers, @@ -587,7 +581,7 @@ def get_layout_action2s(spritelayout, feature): actions.append(extra_act6) varaction2 = action2var.Action2Var( - feature, "{}@registers - feature {:02X}".format(spritelayout.name.value, feature), spritelayout.pos, 0x89 + feature, f"{spritelayout.name.value}@registers - feature {feature:02X}", spritelayout.pos, 0x89 ) varaction2.var_list = varact2parser.var_list ref = expression.SpriteGroupRef(spritelayout.name, [], None, layout_action) @@ -627,7 +621,7 @@ def make_empty_layout_action2(feature, pos): layout = ParsedSpriteLayout() layout.ground_sprite = Action2LayoutSprite(feature, Action2LayoutSpriteType.GROUND) layout.ground_sprite.set_param(expression.Identifier("sprite"), expression.ConstantNumeric(0)) - return Action2Layout(feature, "@CB_FAILED_LAYOUT{:02X}".format(feature), pos, layout, []) + return Action2Layout(feature, f"@CB_FAILED_LAYOUT{feature:02X}", pos, layout, []) class StationSpriteset(expression.Expression): @@ -660,7 +654,7 @@ def append_mapping(self, mapping, feature, actions, default, custom_spritesets): actions.extend(action1.add_to_action1([spriteset], feature, None)) real_action2 = action2real.make_simple_real_action2( feature, - spriteset.name.value + " - feature {:02X}".format(feature), + spriteset.name.value + f" - feature {feature:02X}", None, action1.get_action1_index(spriteset, feature), ) @@ -686,9 +680,9 @@ def parse_station_layouts(feature, id, layouts): def parse_spriteset(name, args, pos, info): if info: if len(args) < 1: - raise generic.ScriptError("'{}' expects 1 or 2 parameters".format(name), pos) + raise generic.ScriptError(f"'{name}' expects 1 or 2 parameters", pos) if not isinstance(args[0], expression.ConstantNumeric): - raise generic.ScriptError("First parameter for '{}' must be a constant".format(name), pos) + raise generic.ScriptError(f"First parameter for '{name}' must be a constant", pos) return var10map.translate(args[0].value, args[1:], pos) return StationSpriteset(None, args, info, pos) @@ -751,7 +745,7 @@ def parse_spriteset(name, args, pos, info): actions.append(extra_act6) varaction2 = action2var.Action2Var( - feature, "Station Layout@registers - Id {:02X}".format(id.value), None, 0x89, param_registers + feature, f"Station Layout@registers - Id {id.value:02X}", None, 0x89, param_registers ) varaction2.var_list = varact2parser.var_list varaction2.default_result = expression.ConstantNumeric(0) diff --git a/nml/actions/action2production.py b/nml/actions/action2production.py index 07f7388a7..2ddaebf8d 100644 --- a/nml/actions/action2production.py +++ b/nml/actions/action2production.py @@ -111,7 +111,7 @@ def finish_production_actions(produce, prod_action, action_list, varact2parser): produce.set_action2(prod_action, 0x0A) else: # Create intermediate varaction2 - varaction2 = action2var.Action2Var(0x0A, "{}@registers".format(produce.name.value), produce.pos, 0x89) + varaction2 = action2var.Action2Var(0x0A, f"{produce.name.value}@registers", produce.pos, 0x89) varaction2.var_list = varact2parser.var_list action_list.extend(varact2parser.extra_actions) extra_act6 = action6.Action6() @@ -185,7 +185,7 @@ def get_production_v2_actions(produce): def resolve_cargoitem(item): cargolabel = item.name.value if cargolabel not in global_constants.cargo_numbers: - raise generic.ScriptError("Cargo label {0} not found in your cargo table".format(cargolabel), produce.pos) + raise generic.ScriptError(f"Cargo label {cargolabel} not found in your cargo table", produce.pos) cargoindex = global_constants.cargo_numbers[cargolabel] valueregister = resolve_prodcb_register(item.value, varact2parser) return (cargoindex, valueregister) diff --git a/nml/actions/action2random.py b/nml/actions/action2random.py index 04343778f..ff855dc89 100644 --- a/nml/actions/action2random.py +++ b/nml/actions/action2random.py @@ -126,7 +126,7 @@ def parse_randomswitch_type(random_switch): # Validate type name / param combination if type_str not in random_types[feature_val]: raise generic.ScriptError( - "Invalid combination for random_switch feature {:d} and type '{}'. ".format(feature_val, type_str), type_pos + f"Invalid combination for random_switch feature {feature_val} and type '{type_str}'. ", type_pos ) type_info = random_types[feature_val][type_str] @@ -135,14 +135,14 @@ def parse_randomswitch_type(random_switch): # No param given if type_info["param"] == 1: raise generic.ScriptError( - "Value '{}' for random_switch parameter 2 'type' requires a parameter.".format(type_str), type_pos + f"Value '{type_str}' for random_switch parameter 2 'type' requires a parameter.", type_pos ) count = None else: # Param given if type_info["param"] == 0: raise generic.ScriptError( - "Value '{}' for random_switch parameter 2 'type' should not have a parameter.".format(type_str), + f"Value '{type_str}' for random_switch parameter 2 'type' should not have a parameter.", type_pos, ) if ( @@ -157,7 +157,7 @@ def parse_randomswitch_type(random_switch): if random_switch.triggers.value != 0 and not type_info["triggers"]: raise generic.ScriptError( - "Triggers may not be set for random_switch feature {:d} and type '{}'. ".format(feature_val, type_str), + f"Triggers may not be set for random_switch feature {feature_val} and type '{type_str}'. ", type_pos, ) @@ -215,16 +215,16 @@ def parse_randomswitch_dependencies(random_switch, start_bit, bits_available, nr if act2_to_copy is not None: if act2_to_copy.randbit != act2.randbit: msg = ( - "random_switch '{}' cannot be dependent on both '{}' and '{}'" - " as these are independent of each other." - ).format(random_switch.name.value, act2_to_copy.name, act2.name) + f"random_switch '{random_switch.name.value}' cannot be dependent on" + f" both '{act2_to_copy.name}' and '{act2.name}' as these are independent of each other." + ) raise generic.ScriptError(msg, random_switch.pos) if act2_to_copy.nrand != act2.nrand: msg = ( - "random_switch '{}' cannot be dependent on both '{}' and '{}'" - " as they don't use the same amount of random data." - ).format(random_switch.name.value, act2_to_copy.name, act2.name) + f"random_switch '{random_switch.name.value}' cannot be dependent on" + f" both '{act2_to_copy.name}' and '{act2.name}' as they don't use the same amount of random data." + ) raise generic.ScriptError(msg, random_switch.pos) else: act2_to_copy = act2 @@ -254,8 +254,8 @@ def parse_randomswitch_dependencies(random_switch, start_bit, bits_available, nr if possible_mask & (required_mask << randbit) != (required_mask << randbit): msg = ( "Combination of dependence on and independence from" - " random_switches is not possible for random_switch '{}'." - ).format(random_switch.name.value) + f" random_switches is not possible for random_switch '{random_switch.name.value}'." + ) raise generic.ScriptError(msg, random_switch.pos) else: # find a suitable randbit @@ -292,9 +292,9 @@ def parse_randomswitch(random_switch): nrand <<= 1 # Verify that enough random data is available - if min(1 << bits_available, 0x80) < nrand: - msg = "The maximum sum of all random_switch probabilities is {:d}, encountered {:d}." - msg = msg.format(min(1 << bits_available, 0x80), total_prob) + max_rand_switch_sum = min(1 << bits_available, 0x80) + if max_rand_switch_sum < nrand: + msg = f"The maximum sum of all random_switch probabilities is {max_rand_switch_sum}, encountered {total_prob}." raise generic.ScriptError(msg, random_switch.pos) randbit, nrand = parse_randomswitch_dependencies(random_switch, start_bit, bits_available, nrand) @@ -344,7 +344,7 @@ def parse_randomswitch(random_switch): res_prob, ) offset += res_prob * 2 - comment = "({:d}/{:d}) -> ({:d}/{:d}): ".format(choice.probability.value, total_prob, res_prob, nrand) + comment + comment = f"({choice.probability.value}/{total_prob}) -> ({res_prob}/{nrand}): " + comment random_action2.choices.append(RandomAction2Choice(result, res_prob, comment)) if len(act6.modifications) > 0: @@ -355,9 +355,7 @@ def parse_randomswitch(random_switch): random_switch.set_action2(random_action2, feature) else: # Create intermediate varaction2 to compute parameter for type 0x84 - varaction2 = action2var.Action2Var( - feature, "{}@registers".format(random_switch.name.value), random_switch.pos, 0x89 - ) + varaction2 = action2var.Action2Var(feature, f"{random_switch.name.value}@registers", random_switch.pos, 0x89) varact2parser = action2var.Varaction2Parser(feature) varact2parser.parse_expr(count_expr) varaction2.var_list = varact2parser.var_list diff --git a/nml/actions/action2real.py b/nml/actions/action2real.py index 8f73650b1..1c26ec933 100644 --- a/nml/actions/action2real.py +++ b/nml/actions/action2real.py @@ -46,9 +46,7 @@ def get_real_action2s(spritegroup, feature): if feature not in action2.features_sprite_group: raise generic.ScriptError( - "Sprite groups that combine sprite sets are not supported for feature '{}'.".format( - general.feature_name(feature) - ), + f"Sprite groups that combine sprite sets are not supported for feature '{general.feature_name(feature)}'.", spritegroup.pos, ) @@ -58,7 +56,7 @@ def get_real_action2s(spritegroup, feature): spriteset_list.extend([action2.resolve_spritegroup(sg_ref.name) for sg_ref in view.spriteset_list]) if feature == 0x04: if view.name.value not in ["little", "lots"]: - raise generic.ScriptError("Unexpected '{}' (list of) sprite set(s).".format(view.name), view.pos) + raise generic.ScriptError(f"Unexpected '{view.name}' (list of) sprite set(s).", view.pos) view.name.value = "loading" if view.name.value == "lots" else "loaded" actions.extend(action1.add_to_action1(spriteset_list, feature, spritegroup.pos)) @@ -71,9 +69,9 @@ def get_real_action2s(spritegroup, feature): raise generic.ScriptError("Expected at least a 'lots' (list of) sprite set(s).", spritegroup.pos) elif feature in (0x05, 0x0B, 0x0D, 0x10): msg = ( - "Sprite groups for feature {:02X} will not be supported in the future, as they are no longer needed." - " Directly refer to sprite sets instead." - ).format(feature) + f"Sprite groups for feature {feature:02X} will not be supported in the future," + " as they are no longer needed. Directly refer to sprite sets instead." + ) generic.print_warning(generic.Warning.GENERIC, msg, spritegroup.pos) if view_names != ["default"]: raise generic.ScriptError("Expected only a 'default' (list of) sprite set(s).", spritegroup.pos) @@ -97,7 +95,7 @@ def get_real_action2s(spritegroup, feature): actions.append( Action2Real( feature, - spritegroup.name.value + " - feature {:02X}".format(feature), + spritegroup.name.value + f" - feature {feature:02X}", spritegroup.pos, loaded_list, loading_list, @@ -146,7 +144,7 @@ def create_spriteset_actions(spritegroup): for feature in spritegroup.feature_set: if len(spritegroup.used_sprite_sets) != 0 and feature not in action2.features_sprite_group: raise generic.ScriptError( - "Directly referring to sprite sets is not possible for feature {:02X}".format(feature), spritegroup.pos + f"Directly referring to sprite sets is not possible for feature {feature:02X}", spritegroup.pos ) for spriteset in spritegroup.used_sprite_sets: if spriteset.has_action2(feature): @@ -155,7 +153,7 @@ def create_spriteset_actions(spritegroup): real_action2 = make_simple_real_action2( feature, - spriteset.name.value + " - feature {:02X}".format(feature), + spriteset.name.value + f" - feature {feature:02X}", spritegroup.pos, action1.get_action1_index(spriteset, feature), ) diff --git a/nml/actions/action2var.py b/nml/actions/action2var.py index 128e6237e..b3777f697 100644 --- a/nml/actions/action2var.py +++ b/nml/actions/action2var.py @@ -15,7 +15,7 @@ from nml import expression, generic, global_constants, nmlop from nml.actions import action2, action2real, action2var_variables, action4, action6, actionD -from nml.ast import general, switch, spriteblock +from nml.ast import general, spriteblock, switch class Action2Var(action2.Action2): @@ -97,7 +97,7 @@ def write(self, file): else: size += var.get_size() - regs = ["{} : register {:X}".format(reg.name, reg.register) for reg in self.param_registers] + regs = [f"{reg.name} : register {reg.register:X}" for reg in self.param_registers] self.write_sprite_start(file, size, regs) file.print_bytex(self.type_byte) file.newline() @@ -198,11 +198,9 @@ class VarAction2ProcCallVar(VarAction2Var): def __init__(self, sg_ref): if not sg_ref.act2: if not isinstance(action2.resolve_spritegroup(sg_ref.name), (switch.Switch, switch.RandomSwitch)): - raise generic.ScriptError( - "Block with name '{}' is not a valid procedure".format(sg_ref.name), sg_ref.pos - ) + raise generic.ScriptError(f"Block with name '{sg_ref.name}' is not a valid procedure", sg_ref.pos) if not sg_ref.is_procedure: - raise generic.ScriptError("Unexpected identifier encountered: '{}'".format(sg_ref.name), sg_ref.pos) + raise generic.ScriptError(f"Unexpected identifier encountered: '{sg_ref.name}'", sg_ref.pos) VarAction2Var.__init__(self, 0x7E, 0, 0, comment=str(sg_ref)) # Reference to the called action2 self.sg_ref = sg_ref @@ -475,7 +473,7 @@ def preprocess_storageop(self, expr): assert isinstance(expr, expression.StorageOp) if expr.info["perm"] and not self.var_scope.has_persistent_storage: raise generic.ScriptError( - "Persistent storage is not supported for feature '{}'".format(self.var_scope.name), + f"Persistent storage is not supported for feature '{self.var_scope.name}'", expr.pos, ) @@ -576,7 +574,7 @@ def parse_binop(self, expr): self.in_store_op = True if ( - isinstance(expr.expr2, (expression.ConstantNumeric, expression.Variable)) + isinstance(expr.expr2, (expression.ConstantNumeric, expression.Variable)) # noqa:SIM101 # keep related types together or isinstance(expr.expr2, (VarAction2LoadTempVar, VarAction2LoadCallParam)) or (isinstance(expr.expr2, expression.Parameter) and isinstance(expr.expr2.num, expression.ConstantNumeric)) or (isinstance(expr.expr2, expression.StorageOp) and expr.expr2.name == "LOAD_TEMP") @@ -844,7 +842,7 @@ def create_ternary_action(guard, expr_true, expr_false, action_list, feature, va act6 = action6.Action6() global return_action_id - name = "@ternary_action_{:d}".format(return_action_id) + name = f"@ternary_action_{return_action_id}" varaction2 = Action2Var(feature, name, guard.pos, var_range) return_action_id += 1 @@ -960,9 +958,7 @@ def get_failed_cb_result(feature, action_list, parent_action, pos): # Normal action2 act1_actions, act1_index = action1.make_cb_failure_action1(feature) action_list.extend(act1_actions) - act2 = action2real.make_simple_real_action2( - feature, "@CB_FAILED_REAL{:02X}".format(feature), pos, act1_index - ) + act2 = action2real.make_simple_real_action2(feature, f"@CB_FAILED_REAL{feature:02X}", pos, act1_index) action_list.append(act2) # Create varaction2, to choose between returning graphics and 0, depending on CB @@ -971,7 +967,7 @@ def get_failed_cb_result(feature, action_list, parent_action, pos): expression.Variable(expression.ConstantNumeric(0x0C), mask=expression.ConstantNumeric(0xFFFF)) ) - varaction2 = Action2Var(feature, "@CB_FAILED{:02X}".format(feature), pos, 0x89) + varaction2 = Action2Var(feature, f"@CB_FAILED{feature:02X}", pos, 0x89) varaction2.var_list = varact2parser.var_list varaction2.ranges.append( @@ -1019,10 +1015,9 @@ def parse_sg_ref_result(result, action_list, parent_action, var_range): target = action2.resolve_spritegroup(result.name) if not result.act2 else None if parent_action.feature not in action2.features_sprite_layout and isinstance(target, spriteblock.SpriteLayout): + parent_name = general.feature_name(parent_action.feature) raise generic.ScriptError( - "SpriteLayout '{}' is not a valid return value for feature '{}'".format( - result.name, general.feature_name(parent_action.feature) - ), + f"SpriteLayout '{result.name}' is not a valid return value for feature '{parent_name}'", result.pos, ) @@ -1052,7 +1047,7 @@ def parse_sg_ref_result(result, action_list, parent_action, var_range): action_list.append(extra_act6) global return_action_id - name = "@return_action_{:d}".format(return_action_id) + name = f"@return_action_{return_action_id}" varaction2 = Action2Var(parent_action.feature, name, result.pos, var_range) return_action_id += 1 varaction2.var_list = varact2parser.var_list @@ -1119,23 +1114,23 @@ def parse_result(value, action_list, act6, offset, parent_action, none_result, v 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) + comment = f"return {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) + f" or 0..32767 (unsigned), encountered {value.value}." + ) raise generic.ScriptError(msg, value.pos) elif isinstance(value, expression.String): - comment = "return {};".format(str(value)) + comment = f"return {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) + comment = f"return param[{tmp_param}];" action_list.extend(tmp_param_actions) for i in range(repeat_result): act6.modify_bytes(tmp_param, 2, offset + 2 * i) @@ -1143,12 +1138,12 @@ def parse_result(value, action_list, act6, offset, parent_action, none_result, v else: global return_action_id extra_actions, result = create_return_action( - value, parent_action.feature, "@return_action_{:d}".format(return_action_id), var_range + value, parent_action.feature, f"@return_action_{return_action_id}", var_range ) return_action_id += 1 action2.add_ref(result, parent_action) action_list.extend(extra_actions) - comment = "return {}".format(value) + comment = f"return {value}" return (result, comment) diff --git a/nml/actions/action2var_variables.py b/nml/actions/action2var_variables.py index 0a30dc615..0c9218dbd 100644 --- a/nml/actions/action2var_variables.py +++ b/nml/actions/action2var_variables.py @@ -15,6 +15,7 @@ from nml import expression, generic, nmlop + def default_60xvar(name, args, pos, info): """ Function to convert arguments into a variable parameter. @@ -38,46 +39,53 @@ def default_60xvar(name, args, pos, info): @rtype: C{tuple} of (L{Expression}, C{list} C{tuple} of (C{int}, L{Expression})) """ if len(args) != 1: - raise generic.ScriptError("'{}'() requires one argument, encountered {:d}".format(name, len(args)), pos) + raise generic.ScriptError(f"'{name}'() requires one argument, encountered {len(args)}", pos) return (args[0], []) # Some commonly used functions that apply some modification to the raw variable value # To pass extra parameters, lambda calculus may be used + def value_sign_extend(var, info): - #r = (x ^ m) - m; with m being (1 << (num_bits -1)) + # 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 value_mul_div(mul, div): return lambda var, info: nmlop.DIV(nmlop.MUL(var, mul), div) + def value_add_constant(const): return lambda var, info: nmlop.ADD(var, const) + def value_equals(const): return lambda var, info: nmlop.CMP_EQ(var, const) # Commonly used functions to let a variable accept an (x, y)-offset as parameters + 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) + raise generic.ScriptError(f"'{name}'() requires 2 arguments, encountered {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) + generic.check_range(arg.value, min, max, f"Argument of '{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(), [] ) + # Make sure to reduce the result + return (param.reduce(), []) + def signed_tile_offset(name, args, pos, info): return tile_offset(name, args, pos, info, -8, 7) + def unsigned_tile_offset(name, args, pos, info): return tile_offset(name, args, pos, info, 0, 15) @@ -85,6 +93,7 @@ def unsigned_tile_offset(name, args, pos, info): # Global variables, usable for all features # + varact2_globalvars = { 'current_month' : {'var': 0x02, 'start': 0, 'size': 8}, 'current_day_of_month' : {'var': 0x02, 'start': 8, 'size': 5}, @@ -114,10 +123,10 @@ def unsigned_tile_offset(name, args, pos, info): 'grfid' : {'var': 0x25, 'start': 0, 'size': 32}, 'position_in_consist' : {'var': 0x40, 'start': 0, 'size': 8}, 'position_in_consist_from_end' : {'var': 0x40, 'start': 8, 'size': 8}, - 'num_vehs_in_consist' : {'var': 0x40, 'start': 16, 'size': 8, 'value_function': value_add_constant(1)}, # Zero-based, add 1 to make sane + 'num_vehs_in_consist' : {'var': 0x40, 'start': 16, 'size': 8, 'value_function': value_add_constant(1)}, # Zero-based, add 1 to make sane 'position_in_vehid_chain' : {'var': 0x41, 'start': 0, 'size': 8}, 'position_in_vehid_chain_from_end' : {'var': 0x41, 'start': 8, 'size': 8}, - 'num_vehs_in_vehid_chain' : {'var': 0x41, 'start': 16, 'size': 8}, # One-based, already sane + '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}, @@ -173,8 +182,8 @@ def unsigned_tile_offset(name, args, pos, info): varact2vars_trains = { **varact2vars_vehicles, - #0x4786 / 0x10000 is an approximation of 3.5790976, the conversion factor - #for train speed + # 0x4786 / 0x10000 is an approximation of 3.5790976, the conversion factor + # for train speed 'max_speed' : {'var': 0x98, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x4786, 0x10000)}, 'current_speed' : {'var': 0xB4, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x4786, 0x10000)}, 'current_railtype' : {'var': 0x4A, 'start': 0, 'size': 8}, @@ -184,8 +193,8 @@ def unsigned_tile_offset(name, args, pos, info): varact2vars_roadvehs = { **varact2vars_vehicles, - #0x23C3 / 0x10000 is an approximation of 7.1581952, the conversion factor - #for road vehicle speed + # 0x23C3 / 0x10000 is an approximation of 7.1581952, the conversion factor + # for road vehicle speed 'max_speed' : {'var': 0x98, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x23C3, 0x10000)}, 'current_speed' : {'var': 0xB4, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x23C3, 0x10000)}, 'current_roadtype' : {'var': 0x4A, 'start': 0, 'size': 8}, @@ -196,8 +205,8 @@ def unsigned_tile_offset(name, args, pos, info): varact2vars_ships = { **varact2vars_vehicles, - #0x23C3 / 0x10000 is an approximation of 7.1581952, the conversion factor - #for ship speed + # 0x23C3 / 0x10000 is an approximation of 7.1581952, the conversion factor + # for ship speed 'max_speed' : {'var': 0x98, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x23C3, 0x10000)}, 'current_speed' : {'var': 0xB4, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x23C3, 0x10000)}, 'current_max_speed' : {'var': 0x4C, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x23C3, 0x10000)}, @@ -206,9 +215,9 @@ def unsigned_tile_offset(name, args, pos, info): varact2vars_aircraft = { **varact2vars_vehicles, - #0x3939 / 0x1000 is an approximation of 0.279617, the conversion factor - #Note that the denominator has one less zero here! - #for aircraft speed + # 0x3939 / 0x1000 is an approximation of 0.279617, the conversion factor + # Note that the denominator has one less zero here! + # for aircraft speed 'max_speed' : {'var': 0x98, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x3939, 0x1000)}, 'current_speed' : {'var': 0xB4, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x3939, 0x1000)}, 'current_max_speed' : {'var': 0x4C, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x3939, 0x1000)}, @@ -216,51 +225,56 @@ def unsigned_tile_offset(name, args, pos, info): 'vehicle_is_in_depot' : {'var': 0xE6, 'start': 0, 'size': 8, 'value_function': value_equals(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) + raise generic.ScriptError(f"{name}() requires one argument, encountered {len(args)}", pos) if isinstance(args[0], expression.ConstantNumeric): - generic.check_range(args[0].value, -128, 127, "parameter of {}()".format(name), pos) + generic.check_range(args[0].value, -128, 127, f"parameter of {name}()", pos) ret = nmlop.AND(args[0], 0xFF, pos).reduce() return (ret, []) + def vehicle_railtype(name, args, pos, info): return (expression.functioncall.builtin_resolve_typelabel(name, args, pos, table_name="railtype"), []) + def vehicle_roadtype(name, args, pos, info): return (expression.functioncall.builtin_resolve_typelabel(name, args, pos, table_name="roadtype"), []) + def vehicle_tramtype(name, args, pos, info): return (expression.functioncall.builtin_resolve_typelabel(name, args, pos, table_name="tramtype"), []) + varact2vars60x_vehicles = { 'count_veh_id' : {'var': 0x60, 'start': 0, 'size': 8}, - 'other_veh_curv_info' : {'var': 0x62, 'start': 0, 'size': 4, 'param_function':signed_byte_parameter, 'value_function':value_sign_extend}, - 'other_veh_is_hidden' : {'var': 0x62, 'start': 7, 'size': 1, 'param_function':signed_byte_parameter}, - 'other_veh_x_offset' : {'var': 0x62, 'start': 8, 'size': 8, 'param_function':signed_byte_parameter, 'value_function':value_sign_extend}, - 'other_veh_y_offset' : {'var': 0x62, 'start': 16, 'size': 8, 'param_function':signed_byte_parameter, 'value_function':value_sign_extend}, - 'other_veh_z_offset' : {'var': 0x62, 'start': 24, 'size': 8, 'param_function':signed_byte_parameter, 'value_function':value_sign_extend}, + 'other_veh_curv_info' : {'var': 0x62, 'start': 0, 'size': 4, 'param_function': signed_byte_parameter, 'value_function': value_sign_extend}, + 'other_veh_is_hidden' : {'var': 0x62, 'start': 7, 'size': 1, 'param_function': signed_byte_parameter}, + 'other_veh_x_offset' : {'var': 0x62, 'start': 8, 'size': 8, 'param_function': signed_byte_parameter, 'value_function': value_sign_extend}, + 'other_veh_y_offset' : {'var': 0x62, 'start': 16, 'size': 8, 'param_function': signed_byte_parameter, 'value_function': value_sign_extend}, + 'other_veh_z_offset' : {'var': 0x62, 'start': 24, 'size': 8, 'param_function': signed_byte_parameter, 'value_function': value_sign_extend}, } varact2vars60x_trains = { **varact2vars60x_vehicles, # Var 0x63 bit 0 is only useful when testing multiple bits at once. On its own it is already covered by railtype_available(). - 'tile_supports_railtype' : {'var': 0x63, 'start': 1, 'size': 1, 'param_function':vehicle_railtype}, - 'tile_powers_railtype' : {'var': 0x63, 'start': 2, 'size': 1, 'param_function':vehicle_railtype}, - 'tile_is_railtype' : {'var': 0x63, 'start': 3, 'size': 1, 'param_function':vehicle_railtype}, + 'tile_supports_railtype' : {'var': 0x63, 'start': 1, 'size': 1, 'param_function': vehicle_railtype}, + 'tile_powers_railtype' : {'var': 0x63, 'start': 2, 'size': 1, 'param_function': vehicle_railtype}, + 'tile_is_railtype' : {'var': 0x63, 'start': 3, 'size': 1, 'param_function': vehicle_railtype}, } varact2vars60x_roadvehs = { **varact2vars60x_vehicles, # Var 0x63 bit 0 is only useful when testing multiple bits at once. On its own it is already covered by road/tramtype_available(). - 'tile_supports_roadtype' : {'var': 0x63, 'start': 1, 'size': 1, 'param_function':vehicle_roadtype}, - 'tile_supports_tramtype' : {'var': 0x63, 'start': 1, 'size': 1, 'param_function':vehicle_tramtype}, - 'tile_powers_roadtype' : {'var': 0x63, 'start': 2, 'size': 1, 'param_function':vehicle_roadtype}, - 'tile_powers_tramtype' : {'var': 0x63, 'start': 2, 'size': 1, 'param_function':vehicle_tramtype}, - 'tile_is_roadtype' : {'var': 0x63, 'start': 3, 'size': 1, 'param_function':vehicle_roadtype}, - 'tile_is_tramtype' : {'var': 0x63, 'start': 3, 'size': 1, 'param_function':vehicle_tramtype}, + 'tile_supports_roadtype' : {'var': 0x63, 'start': 1, 'size': 1, 'param_function': vehicle_roadtype}, + 'tile_supports_tramtype' : {'var': 0x63, 'start': 1, 'size': 1, 'param_function': vehicle_tramtype}, + 'tile_powers_roadtype' : {'var': 0x63, 'start': 2, 'size': 1, 'param_function': vehicle_roadtype}, + 'tile_powers_tramtype' : {'var': 0x63, 'start': 2, 'size': 1, 'param_function': vehicle_tramtype}, + 'tile_is_roadtype' : {'var': 0x63, 'start': 3, 'size': 1, 'param_function': vehicle_roadtype}, + 'tile_is_tramtype' : {'var': 0x63, 'start': 3, 'size': 1, 'param_function': vehicle_tramtype}, } # @@ -271,7 +285,7 @@ def vehicle_tramtype(name, args, pos, info): varact2vars_base_stations = { 'random_bits_station' : {'var': 0x5F, 'start': 8, 'size': 16}, # Var 48 doesn't work with newcargos, do not use - 'had_vehicle_of_type' : {'var': 0x8A, 'start': 1, 'size': 5}, # Only read bits 1-5 + 'had_vehicle_of_type' : {'var': 0x8A, 'start': 1, 'size': 5}, # Only read bits 1-5 'is_waypoint' : {'var': 0x8A, 'start': 6, 'size': 1}, 'facilities' : {'var': 0xF0, 'start': 0, 'size': 8}, 'airport_type' : {'var': 0xF1, 'start': 0, 'size': 8}, @@ -324,17 +338,19 @@ def vehicle_tramtype(name, args, pos, info): (2, False) : 0x49, } + def platform_info_param(name, args, pos, info): if len(args) != 1: - raise generic.ScriptError("'{}'() requires one argument, encountered {:d}".format(name, len(args)), pos) + raise generic.ScriptError(f"'{name}'() requires one argument, encountered {len(args)}", pos) if (not isinstance(args[0], expression.ConstantNumeric)) or args[0].value not in (0, 1, 2): - raise generic.ScriptError("Invalid argument for '{}'(), must be one of PLATFORM_SAME_XXX.".format(name), pos) + raise generic.ScriptError(f"Invalid argument for '{name}'(), must be one of PLATFORM_SAME_XXX.", pos) is_middle = 'middle' in info and info['middle'] if is_middle and args[0].value == 2: - raise generic.ScriptError("Invalid argument for '{}'(), PLATFORM_SAME_DIRECTION is not supported here.".format(name), pos) + raise generic.ScriptError(f"Invalid argument for '{name}'(), PLATFORM_SAME_DIRECTION is not supported here.", pos) # Temporarily store variable number in the param, this will be fixed in the value_function - return (expression.ConstantNumeric(mapping_platform_param[(args[0].value, is_middle)]), []) + return (expression.ConstantNumeric(mapping_platform_param[args[0].value, is_middle]), []) + def platform_info_fix_var(var, info): # Variable to use was temporarily stored in the param @@ -343,6 +359,7 @@ def platform_info_fix_var(var, info): var.param = None return var + varact2vars60x_stations = { **varact2vars60x_base_stations, 'nearby_tile_animation_frame' : {'var': 0x66, 'start': 0, 'size': 32, 'param_function': signed_tile_offset}, @@ -368,9 +385,9 @@ def platform_info_fix_var(var, info): 'platform_position_from_end' : {'var': 0x00, 'start': 4, 'size': 4, 'param_function': platform_info_param, 'value_function': platform_info_fix_var}, 'platform_number_from_start' : {'var': 0x00, 'start': 8, 'size': 4, 'param_function': platform_info_param, 'value_function': platform_info_fix_var}, 'platform_number_from_end' : {'var': 0x00, 'start': 12, 'size': 4, 'param_function': platform_info_param, 'value_function': platform_info_fix_var}, - 'platform_position_from_middle' : {'var': 0x00, 'start': 0, 'size': 4, 'param_function': platform_info_param, 'middle': True, # 'middle' is used by platform_info_param + 'platform_position_from_middle' : {'var': 0x00, 'start': 0, 'size': 4, 'param_function': platform_info_param, 'middle': True, # 'middle' is used by platform_info_param 'value_function': lambda var, info: value_sign_extend(platform_info_fix_var(var, info), info)}, - 'platform_number_from_middle' : {'var': 0x00, 'start': 4, 'size': 4, 'param_function': platform_info_param, 'middle': True, # 'middle' is used by platform_info_param + 'platform_number_from_middle' : {'var': 0x00, 'start': 4, 'size': 4, 'param_function': platform_info_param, 'middle': True, # 'middle' is used by platform_info_param 'value_function': lambda var, info: value_sign_extend(platform_info_fix_var(var, info), info)}, } @@ -394,6 +411,7 @@ def platform_info_fix_var(var, info): # Houses (feature 0x07) # + def house_same_class(var, info): # Just using var 44 fails for non-north house tiles, as these have no class # Therefore work around it using var 61 @@ -413,8 +431,8 @@ def house_same_class(var, info): 'terrain_type' : {'var': 0x43, 'start': 0, 'size': 8}, 'same_house_count_town' : {'var': 0x44, 'start': 0, 'size': 8}, 'same_house_count_map' : {'var': 0x44, 'start': 8, 'size': 8}, - 'same_class_count_town' : {'var': 0xFF, 'start': 16, 'size': 8, 'value_function': house_same_class}, # 'var' is unused - 'same_class_count_map' : {'var': 0xFF, 'start': 24, 'size': 8, 'value_function': house_same_class}, # 'var' is unused + 'same_class_count_town' : {'var': 0xFF, 'start': 16, 'size': 8, 'value_function': house_same_class}, # 'var' is unused + 'same_class_count_map' : {'var': 0xFF, 'start': 24, 'size': 8, 'value_function': house_same_class}, # 'var' is unused 'generating_town' : {'var': 0x45, 'start': 0, 'size': 1}, 'animation_frame' : {'var': 0x46, 'start': 0, 'size': 8}, 'x_coordinate' : {'var': 0x47, 'start': 0, 'size': 16}, @@ -427,16 +445,17 @@ def house_same_class(var, info): 'house_type_id' : {'var': 0x7D, 'start': 24, 'size': 8, 'param': 0xFF}, } + 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) + raise generic.ScriptError(f"{name}() requires 1 or 3 arguments, encountered {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) + generic.check_range(offs.value, -128, 127, f"{name}-parameter {i + 1} '{'x' if i == 0 else 'y'}offset'", 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() @@ -445,15 +464,16 @@ def cargo_accepted_nearby(name, args, pos, info): return (args[0], [(0x100, reg100)]) + 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) + raise generic.ScriptError(f"{name}() requires 2 arguments, encountered {len(args)}", pos) if isinstance(args[0], expression.ConstantNumeric): - generic.check_range(args[0].value, 1, 63, "{}()-parameter 1 'radius'".format(name), pos) + generic.check_range(args[0].value, 1, 63, f"{name}()-parameter 1 'radius'", 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) + raise generic.ScriptError(f"Invalid value for {name}()-parameter 2 'criterion'", pos) radius = nmlop.AND(args[0], 0x3F, pos) criterion = nmlop.AND(args[1], 0x03, pos) @@ -461,6 +481,7 @@ def nearest_house_matching_criterion(name, args, pos, info): retval = nmlop.OR(criterion, radius).reduce() return (retval, []) + varact2vars60x_houses = { 'old_house_count_town' : {'var': 0x60, 'start': 0, 'size': 8}, 'old_house_count_map' : {'var': 0x60, 'start': 8, 'size': 8}, @@ -558,9 +579,10 @@ def nearest_house_matching_criterion(name, args, pos, info): 'last_accept_date' : {'var': 0xB4, 'start': 0, 'size': 16, 'value_function': value_add_constant(701265)}, } + def industry_count(name, args, pos, info): if len(args) < 1 or len(args) > 2: - raise generic.ScriptError("'{}'() requires between 1 and 2 argument(s), encountered {:d}".format(name, len(args)), pos) + raise generic.ScriptError(f"'{name}'() requires between 1 and 2 argument(s), encountered {len(args)}", pos) grfid = expression.ConstantNumeric(0xFFFFFFFF) if len(args) == 1 else args[1] extra_params = [(0x100, grfid)] @@ -570,29 +592,32 @@ def industry_count(name, args, pos, info): 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) + raise generic.ScriptError(f"'{name}'() requires between 2 and 3 argument(s), encountered {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()) ) + extra_params.append((0x100, grfid)) + extra_params.append((0x101, nmlop.AND(args[1], 0xFF).reduce())) return (args[0], extra_params) + def industry_town_count(name, args, pos, info): if len(args) < 1 or len(args) > 2: - raise generic.ScriptError("'{}'() requires between 1 and 2 argument(s), encountered {:d}".format(name, len(args)), pos) + raise generic.ScriptError(f"'{name}'() requires between 1 and 2 argument(s), encountered {len(args)}", pos) grfid = expression.ConstantNumeric(0xFFFFFFFF) if len(args) == 1 else args[1] extra_params = [] - extra_params.append( (0x100, grfid) ) - extra_params.append( (0x101, expression.ConstantNumeric(0x0100)) ) + extra_params.append((0x100, grfid)) + extra_params.append((0x101, expression.ConstantNumeric(0x0100))) return (args[0], extra_params) + def industry_cargotype(name, args, pos, info): return (expression.functioncall.builtin_resolve_typelabel(name, args, pos, table_name="cargotype"), []) + varact2vars60x_industries = { 'nearby_tile_industry_tile_id' : {'var': 0x60, 'start': 0, 'size': 16, 'param_function': unsigned_tile_offset}, 'nearby_tile_random_bits' : {'var': 0x61, 'start': 0, 'size': 8, 'param_function': unsigned_tile_offset}, @@ -803,17 +828,17 @@ def industry_cargotype(name, args, pos, info): 'has_road' : {'var': 0x43, 'start': 0, 'size': 32, 'value_function': lambda var, info: nmlop.CMP_NEQ(var, 0xFFFFFFFF)}, 'has_tram' : {'var': 0x44, 'start': 0, 'size': 32, 'value_function': lambda var, info: nmlop.CMP_NEQ(var, 0xFFFFFFFF)}, - 'road_type' : {'var': 0x43, 'start': 0, 'size': 8}, # The roadtype of this tile - 'tram_type' : {'var': 0x44, 'start': 0, 'size': 8}, # The tramtype of this tile + 'road_type' : {'var': 0x43, 'start': 0, 'size': 8}, # The roadtype of this tile + 'tram_type' : {'var': 0x44, 'start': 0, 'size': 8}, # The tramtype of this tile 'town_manhattan_dist' : {'var': 0x45, 'start': 0, 'size': 16}, 'town_zone' : {'var': 0x45, 'start': 16, 'size': 8}, 'town_euclidean_dist' : {'var': 0x46, 'start': 0, 'size': 32}, - 'company_num' : {'var': 0x47, 'start': 0, 'size': 8}, # 0..14 company number - 'company_type' : {'var': 0x47, 'start': 16, 'size': 2}, # PLAYERTYPE_HUMAN, PLAYERTYPE_AI etc. - 'company_colour1' : {'var': 0x47, 'start': 24, 'size': 4}, # COLOUR_XXX. See https://newgrf-specs.tt-wiki.net/wiki/NML:List_of_default_colour_translation_palettes#Company_colour_helper_functions - 'company_colour2' : {'var': 0x47, 'start': 28, 'size': 4}, # Same as above + 'company_num' : {'var': 0x47, 'start': 0, 'size': 8}, # 0..14 company number + 'company_type' : {'var': 0x47, 'start': 16, 'size': 2}, # PLAYERTYPE_HUMAN, PLAYERTYPE_AI etc. + 'company_colour1' : {'var': 0x47, 'start': 24, 'size': 4}, # COLOUR_XXX. See https://newgrf-specs.tt-wiki.net/wiki/NML:List_of_default_colour_translation_palettes#Company_colour_helper_functions + 'company_colour2' : {'var': 0x47, 'start': 28, 'size': 4}, # Same as above 'animation_frame' : {'var': 0x49, 'start': 0, 'size': 8}, @@ -848,6 +873,7 @@ def industry_cargotype(name, args, pos, info): 'nearby_tile_road_stop_id' : {'var': 0x6B, 'start': 0, 'size': 16, 'param_function': signed_tile_offset}, } + class VarAct2Scope: def __init__(self, name, vars_normal, vars_60x, has_persistent_storage=False): self.name = name diff --git a/nml/actions/action3.py b/nml/actions/action3.py index 8fcb599fe..3e2ae9248 100644 --- a/nml/actions/action3.py +++ b/nml/actions/action3.py @@ -129,7 +129,7 @@ def create_intermediate_varaction2(feature, varact2parser, mapping, default, pos for mod in varact2parser.mods: act6.modify_bytes(mod.param, mod.size, mod.offset + 4) - name = expression.Identifier("@action3_{:d}".format(action2_id)) + name = expression.Identifier(f"@action3_{action2_id}") action2_id += 1 varaction2 = action2var.Action2Var(feature, name.value, pos, 0x89) varaction2.var_list = varact2parser.var_list @@ -319,7 +319,7 @@ def parse_graphics_block_single_id( 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) + raise generic.ScriptError(f"Callback '{cb_name}' is defined multiple times.", cargo_id.pos) seen_callbacks.add(cb_name) info_list = cb_table[cb_name] @@ -336,7 +336,7 @@ def parse_graphics_block_single_id( # 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 + f"Graphics for '{cb_name}' are defined multiple times.", cargo_id.pos ) cargo_gfx[info["num"]] = graphics.result.value elif info["type"] == "cb": @@ -358,7 +358,7 @@ def parse_graphics_block_single_id( layouts = (var10map, registers_ref) else: raise generic.ScriptError( - "'{}' must be an array of even length, or the ID of a station".format(cb_name), + f"'{cb_name}' must be an array of even length, or the ID of a station", cargo_id.pos, ) prepend_action_list.extend(actions) @@ -369,7 +369,7 @@ def parse_graphics_block_single_id( or len(graphics.result.value.values) > 6 ): raise generic.ScriptError( - "'{}' must be an array of at most 6 elements".format(cb_name), cargo_id.pos + f"'{cb_name}' must be an array of at most 6 elements", cargo_id.pos ) custom_spritesets = graphics.result.value.values elif info["type"] == "prepare_layout": @@ -394,15 +394,13 @@ def parse_graphics_block_single_id( "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 - ) + raise generic.ScriptError(f"Graphics for cargo {cargo_id.value} are defined multiple times.", 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), + f"Default graphics may not be defined for this feature (0x{feature:02X}).", graphics_block.default_graphics.pos, ) if None in cargo_gfx: @@ -429,7 +427,7 @@ def parse_graphics_block_single_id( elif prepare_layout: if not isinstance(prepare_layout, expression.SpriteGroupRef): actions, prepare_layout = action2var.create_return_action( - prepare_layout, feature, "Station Layout@prepare - Id {:02X}".format(id.value), 0x89 + prepare_layout, feature, f"Station Layout@prepare - Id {id.value:02X}", 0x89 ) prepend_action_list.extend(actions) varact2parser.parse(prepare_layout) diff --git a/nml/actions/action3_callbacks.py b/nml/actions/action3_callbacks.py index e88ec4bf8..c11025857 100644 --- a/nml/actions/action3_callbacks.py +++ b/nml/actions/action3_callbacks.py @@ -29,12 +29,12 @@ general_vehicle_cbs = { 'default' : {'type': 'cargo', 'num': None}, 'purchase' : {'type': 'cargo', 'num': 0xFF}, - 'random_trigger' : {'type': 'cb', 'num': 0x01}, # Almost undocumented, but really necessary! + 'random_trigger' : {'type': 'cb', 'num': 0x01}, # Almost undocumented, but really necessary! 'loading_speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x07}, 'cargo_subtype_text' : {'type': 'cb', 'num': 0x19, 'flag_bit': 5}, 'additional_text' : {'type': 'cb', 'num': 0x23, 'purchase': 2}, - 'colour_mapping' : {'type': 'cb', 'num': 0x2D, 'flag_bit':6, 'purchase': 'purchase_colour_mapping'}, - 'purchase_colour_mapping' : {'type': 'cb', 'num': 0x2D, 'flag_bit':6, 'purchase': 2}, + 'colour_mapping' : {'type': 'cb', 'num': 0x2D, 'flag_bit': 6, 'purchase': 'purchase_colour_mapping'}, + 'purchase_colour_mapping' : {'type': 'cb', 'num': 0x2D, 'flag_bit': 6, 'purchase': 2}, 'start_stop' : {'type': 'cb', 'num': 0x31}, 'every_32_days' : {'type': 'cb', 'num': 0x32}, 'sound_effect' : {'type': 'cb', 'num': 0x33, 'flag_bit': 7}, @@ -43,19 +43,21 @@ 'refit' : {'type': 'cb', 'num': 0x163, "purchase": 2}, } + # Function to convert vehicle length to the actual property value, which is (8 - length) def vehicle_length(value): return nmlop.SUB(8, value) + # Trains callbacks[0x00] = { 'visual_effect_and_powered' : {'type': 'cb', 'num': 0x10, 'flag_bit': 0}, 'effect_spawn_model_and_powered' : {'type': 'cb', 'num': 0x10, 'flag_bit': 0}, 'length' : {'type': 'cb', 'num': 0x36, 'var10': 0x21, 'value_function': vehicle_length}, - 'cargo_capacity' : [ {'type': 'cb', 'num': 0x15, 'flag_bit': 3}, + 'cargo_capacity' : [{'type': 'cb', 'num': 0x15, 'flag_bit': 3}, {'type': 'cb', 'num': 0x36, 'var10': 0x14, 'purchase': 'purchase_cargo_capacity'}], 'purchase_cargo_capacity' : {'type': 'cb', 'num': 0x36, 'var10': 0x14, 'purchase': 2}, - 'articulated_part' : {'type': 'cb', 'num': 0x16, 'flag_bit': 4, 'purchase': 1}, # Don't add separate purchase CB here + 'articulated_part' : {'type': 'cb', 'num': 0x16, 'flag_bit': 4, 'purchase': 1}, # Don't add separate purchase CB here 'can_attach_wagon' : {'type': 'cb', 'num': 0x1D}, 'speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x09, 'purchase': 'purchase_speed'}, 'purchase_speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x09, 'purchase': 2}, @@ -81,10 +83,10 @@ def vehicle_length(value): 'visual_effect' : {'type': 'cb', 'num': 0x10, 'flag_bit': 0}, 'effect_spawn_model' : {'type': 'cb', 'num': 0x10, 'flag_bit': 0}, 'length' : {'type': 'cb', 'num': 0x36, 'var10': 0x23, 'value_function': vehicle_length}, - 'cargo_capacity' : [ {'type': 'cb', 'num': 0x15, 'flag_bit': 3}, + 'cargo_capacity' : [{'type': 'cb', 'num': 0x15, 'flag_bit': 3}, {'type': 'cb', 'num': 0x36, 'var10': 0x0F, 'purchase': 'purchase_cargo_capacity'}], 'purchase_cargo_capacity' : {'type': 'cb', 'num': 0x36, 'var10': 0x0F, 'purchase': 2}, - 'articulated_part' : {'type': 'cb', 'num': 0x16, 'flag_bit': 4, 'purchase': 1}, # Don't add separate purchase CB here + 'articulated_part' : {'type': 'cb', 'num': 0x16, 'flag_bit': 4, 'purchase': 1}, # Don't add separate purchase CB here 'running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x09, 'purchase': 'purchase_running_cost_factor'}, 'purchase_running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x09, 'purchase': 2}, 'cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x11, 'purchase': 2}, @@ -105,7 +107,7 @@ def vehicle_length(value): callbacks[0x02] = { 'visual_effect' : {'type': 'cb', 'num': 0x10, 'flag_bit': 0}, 'effect_spawn_model' : {'type': 'cb', 'num': 0x10, 'flag_bit': 0}, - 'cargo_capacity' : [ {'type': 'cb', 'num': 0x15, 'flag_bit': 3}, + 'cargo_capacity' : [{'type': 'cb', 'num': 0x15, 'flag_bit': 3}, {'type': 'cb', 'num': 0x36, 'var10': 0x0D, 'purchase': 'purchase_cargo_capacity'}], 'purchase_cargo_capacity' : {'type': 'cb', 'num': 0x36, 'var10': 0x0D, 'purchase': 2}, 'cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0A, 'purchase': 2}, @@ -120,7 +122,7 @@ def vehicle_length(value): # Aircraft callbacks[0x03] = { - 'passenger_capacity' : [ {'type': 'cb', 'num': 0x15, 'flag_bit': 3}, + 'passenger_capacity' : [{'type': 'cb', 'num': 0x15, 'flag_bit': 3}, {'type': 'cb', 'num': 0x36, 'var10': 0x0F, 'purchase': 'purchase_passenger_capacity'}], 'purchase_passenger_capacity' : {'type': 'cb', 'num': 0x36, 'var10': 0x0F, 'purchase': 2}, 'cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0B, 'purchase': 2}, @@ -197,8 +199,8 @@ def vehicle_length(value): 'anim_control' : {'type': 'cb', 'num': 0x25}, 'anim_next_frame' : {'type': 'cb', 'num': 0x26, 'flag_bit': 0}, 'anim_speed' : {'type': 'cb', 'num': 0x27, 'flag_bit': 1}, - 'cargo_amount_accept' : {'type': 'cb', 'num': 0x2B, 'flag_bit': 2}, # Should work like the industry CB, i.e. call multiple times - 'cargo_type_accept' : {'type': 'cb', 'num': 0x2C, 'flag_bit': 3}, # Should work like the industry CB, i.e. call multiple times + 'cargo_amount_accept' : {'type': 'cb', 'num': 0x2B, 'flag_bit': 2}, # Should work like the industry CB, i.e. call multiple times + 'cargo_type_accept' : {'type': 'cb', 'num': 0x2C, 'flag_bit': 3}, # Should work like the industry CB, i.e. call multiple times 'tile_check' : {'type': 'cb', 'num': 0x2F, 'flag_bit': 4}, 'foundations' : {'type': 'cb', 'num': 0x30, 'flag_bit': 5}, 'autoslope' : {'type': 'cb', 'num': 0x3C, 'flag_bit': 6}, @@ -210,7 +212,7 @@ def vehicle_length(value): 'construction_probability' : {'type': 'cb', 'num': 0x22, 'flag_bit': 0}, 'produce_cargo_arrival' : {'type': 'cb', 'num': 0x00, 'flag_bit': 1, 'var18': 0}, 'produce_256_ticks' : {'type': 'cb', 'num': 0x00, 'flag_bit': 2, 'var18': 1}, - 'location_check' : {'type': 'cb', 'num': 0x28, 'flag_bit': 3}, # We need a way to access all those special variables + 'location_check' : {'type': 'cb', 'num': 0x28, 'flag_bit': 3}, # We need a way to access all those special variables 'random_prod_change' : {'type': 'cb', 'num': 0x29, 'flag_bit': 4}, 'monthly_prod_change' : {'type': 'cb', 'num': 0x35, 'flag_bit': 5}, 'cargo_subtype_display' : {'type': 'cb', 'num': 0x37, 'flag_bit': 6}, diff --git a/nml/actions/action4.py b/nml/actions/action4.py index 77c65e067..67078b242 100644 --- a/nml/actions/action4.py +++ b/nml/actions/action4.py @@ -101,11 +101,11 @@ def print_stats(): """ Print statistics about used ids. """ - for t, l in string_ranges.items(): - if l["random_id"]: - num_used = l["total"] - len(l["ids"]) + for str_range, attrs in string_ranges.items(): + if attrs["random_id"]: + num_used = attrs["total"] - len(attrs["ids"]) if num_used > 0: - generic.print_info("{:02X}xx strings: {}/{}".format(t, num_used, l["total"])) + generic.print_info("{:02X}xx strings: {}/{}".format(str_range, num_used, attrs["total"])) def get_global_string_actions(): @@ -179,16 +179,15 @@ def get_string_action4s(feature, string_range, string, id=None): # ID is allocated randomly, we will output the actions later write_action4s = False if (feature, string) in used_strings[string_range]: - id_val = used_strings[string_range][(feature, string)] + id_val = used_strings[string_range][feature, string] else: try: id_val = string_ranges[string_range]["ids"].pop() - used_strings[string_range][(feature, string)] = id_val + used_strings[string_range][feature, string] = id_val except IndexError: raise generic.ScriptError( - "Unable to allocate ID for string, no more free IDs available (maximum is {:d})".format( - string_ranges[string_range]["total"] - ), + "Unable to allocate ID for string, no more free IDs available" + f" (maximum is {string_ranges[string_range]['total']})", string.pos, ) else: diff --git a/nml/actions/action5.py b/nml/actions/action5.py index 4c16955d1..f2f8288ac 100644 --- a/nml/actions/action5.py +++ b/nml/actions/action5.py @@ -100,30 +100,34 @@ def parse_action5(replaces): if block_type == Action5BlockType.FIXED: if num_sprites < num_required: - msg = "Invalid sprite count for sprite replacement type '{}', expected {:d}, got {:d}" - msg = msg.format(replaces.type, num_required, num_sprites) + msg = ( + f"Invalid sprite count for sprite replacement type '{replaces.type}'," + f" expected {num_required}, got {num_sprites}" + ) raise generic.ScriptError(msg, replaces.pos) elif num_sprites > num_required: msg = ( - "Too many sprites specified for sprite replacement type '{}'," - " expected {:d}, got {:d}, extra sprites may be ignored" - ).format(replaces.type, num_required, num_sprites) + f"Too many sprites specified for sprite replacement type '{replaces.type}'," + f" expected {num_required}, got {num_sprites}, extra sprites may be ignored" + ) generic.print_warning(generic.Warning.GENERIC, msg, replaces.pos) if replaces.offset != 0: - msg = "replacenew parameter 'offset' must be zero for sprite replacement type '{}'".format(replaces.type) + msg = f"replacenew parameter 'offset' must be zero for sprite replacement type '{replaces.type}'" raise generic.ScriptError(msg, replaces.pos) elif block_type == Action5BlockType.ANY: if replaces.offset != 0: - msg = "replacenew parameter 'offset' must be zero for sprite replacement type '{}'".format(replaces.type) + msg = f"replacenew parameter 'offset' must be zero for sprite replacement type '{replaces.type}'" raise generic.ScriptError(msg, replaces.pos) elif block_type == Action5BlockType.OFFSET: if num_sprites + replaces.offset > num_required: - msg = "Exceeding the limit of {:d} sprites for sprite replacement type '{}', extra sprites may be ignored" - msg = msg.format(num_required, replaces.type) + msg = ( + f"Exceeding the limit of {num_required} sprites for sprite replacement type '{replaces.type}'," + " extra sprites may be ignored" + ) generic.print_warning(generic.Warning.GENERIC, msg, replaces.pos) if replaces.offset != 0 or num_sprites != num_required: diff --git a/nml/actions/action6.py b/nml/actions/action6.py index 0f23a0750..2729b4f32 100644 --- a/nml/actions/action6.py +++ b/nml/actions/action6.py @@ -28,11 +28,9 @@ def print_stats(): Print statistics about used ids. """ if free_parameters.stats[0] > 0: - generic.print_info( - "Concurrent ActionD registers: {}/{} ({})".format( - free_parameters.stats[0], free_parameters.total_amount, str(free_parameters.stats[1]) - ) - ) + used = free_parameters.stats[0] + total = free_parameters.total_amount + generic.print_info(f"Concurrent ActionD registers: {used}/{total} ({free_parameters.stats[1]})") class Action6(base_action.BaseAction): diff --git a/nml/actions/action7.py b/nml/actions/action7.py index 44dbf9a28..5c38f937f 100644 --- a/nml/actions/action7.py +++ b/nml/actions/action7.py @@ -29,9 +29,7 @@ def print_stats(): """ if free_labels.stats[0] > 0: generic.print_info( - "Concurrent Action10 labels: {}/{} ({})".format( - free_labels.stats[0], free_labels.total_amount, str(free_labels.stats[1]) - ) + f"Concurrent Action10 labels: {free_labels.stats[0]}/{free_labels.total_amount} ({free_labels.stats[1]})" ) @@ -88,7 +86,7 @@ def op_to_cond_op(op): if op == nmlop.CMP_LE: return (5, r"\7>") # Not reached - raise ValueError("Unexpected operator '{}'".format(op.token)) + raise ValueError(f"Unexpected operator '{op.token}'") def parse_conditional(expr): diff --git a/nml/actions/actionD.py b/nml/actions/actionD.py index 8a8a889f4..cbcdb0cc5 100644 --- a/nml/actions/actionD.py +++ b/nml/actions/actionD.py @@ -54,10 +54,10 @@ def write(self, file): size += 4 # print the statement for easier debugging - str1 = "param[{}]".format(self.param1) if self.param1.value != 0xFF else str(self.data) - str2 = "param[{}]".format(self.param2) if self.param2.value != 0xFF else str(self.data) + str1 = f"param[{self.param1}]" if self.param1.value != 0xFF else str(self.data) + str2 = f"param[{self.param2}]" if self.param2.value != 0xFF else str(self.data) str_total = self.op.to_string(str1, str2) if self.op != nmlop.ASSIGN else str1 - file.comment("param[{}] = {}".format(self.target, str_total)) + file.comment(f"param[{self.target}] = {str_total}") file.start_sprite(size) file.print_bytex(0x0D) @@ -97,7 +97,7 @@ def register_names(self): if global_constants.identifier_refcount[self.param.value] == 0: generic.print_warning( generic.Warning.OPTIMISATION, - "Named parameter '{}' is not referenced, ignoring.".format(self.param.value), + f"Named parameter '{self.param.value}' is not referenced, ignoring.", self.param.pos, ) return @@ -110,7 +110,7 @@ def pre_process(self): if isinstance(self.param, expression.SpecialParameter): if not self.param.can_assign(): raise generic.ScriptError( - "Trying to assign a value to the read-only variable '{}'".format(self.param.name), self.param.pos + f"Trying to assign a value to the read-only variable '{self.param.name}'", self.param.pos ) elif isinstance(self.param, expression.Identifier): return @@ -126,7 +126,7 @@ def get_action_list(self): return parse_actionD(self) def __str__(self): - return "{} = {};\n".format(self.param, self.value) + return f"{self.param} = {self.value};\n" # prevent evaluating common sub-expressions multiple times diff --git a/nml/actions/actionF.py b/nml/actions/actionF.py index 790a92b4c..7800f676f 100644 --- a/nml/actions/actionF.py +++ b/nml/actions/actionF.py @@ -1,3 +1,7 @@ +""" +Code for storing and generating action F +""" + __license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -13,9 +17,6 @@ with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" -""" -Code for storing and generating action F -""" from nml import expression, generic, grfstrings from nml.actions import base_action @@ -64,7 +65,7 @@ def print_stats(): """ num_used = total_numbers - len(free_numbers) if num_used > 0: - generic.print_info("Town names: {}/{}".format(num_used, total_numbers)) + generic.print_info(f"Town names: {num_used}/{total_numbers}") class ActionF(base_action.BaseAction): @@ -115,9 +116,7 @@ def prepare_output(self, sprite_num): self.id_number = get_free_id() if isinstance(self.name, expression.Identifier): if self.name.value in named_numbers: - raise generic.ScriptError( - 'Cannot define town name "{}", it is already in use'.format(self.name), self.pos - ) + raise generic.ScriptError(f'Cannot define town name "{self.name}", it is already in use', self.pos) named_numbers[self.name.value] = self.id_number # Add name to the set 'safe' names. else: numbered_numbers.add(self.id_number) # Add number to the set of 'safe' numbers. @@ -157,7 +156,7 @@ def update_block(block, startbit): if startbit > 32: raise generic.ScriptError( - "Not enough random bits for the town name generation ({:d} needed, 32 available)".format(startbit), + f"Not enough random bits for the town name generation ({startbit} needed, 32 available)", self.pos, ) @@ -172,7 +171,7 @@ def update_block(block, startbit): self.style_names.sort() if len(self.style_names) == 0: raise generic.ScriptError( - 'Style "{}" defined, but no translations found for it'.format(self.style_name.name.value), self.pos + f'Style "{self.style_name.name.value}" defined, but no translations found for it', self.pos ) else: self.style_names = [] diff --git a/nml/actions/base_action.py b/nml/actions/base_action.py index 774e75548..582b7d604 100644 --- a/nml/actions/base_action.py +++ b/nml/actions/base_action.py @@ -33,7 +33,7 @@ def write(self, file): @param file: The outputfile to write the data to. @type file: L{SpriteOutputBase} """ - raise NotImplementedError("write is not implemented in {!r}".format(type(self))) + raise NotImplementedError(f"write is not implemented in {type(self)!r}") def skip_action7(self): """ diff --git a/nml/actions/real_sprite.py b/nml/actions/real_sprite.py index 5cb016965..bc212a89a 100644 --- a/nml/actions/real_sprite.py +++ b/nml/actions/real_sprite.py @@ -13,12 +13,6 @@ with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" -try: - from PIL import Image -except ImportError: - # Image is required only when using graphics - pass - from nml import expression, generic from nml.actions import base_action from nml.ast import assignment @@ -211,6 +205,9 @@ def validate_size(self): Check if xpos/ypos/xsize/ysize are already set and if not, set them to 0,0,image_width,image_height. """ + # Image is required only when using graphics, existence already checked by entrypoint + from PIL import Image + if self.xpos is None: with Image.open(generic.find_file(self.file.value)) as im: self.xpos = expression.ConstantNumeric(0) @@ -283,8 +280,10 @@ def __init__(self): def prepare_output(self, sprite_num): if self.sprite_num is not None and self.sprite_num.value != sprite_num: - msg = "Sprite number {:d} given in base_graphics-block, but it doesn't match output sprite number {:d}" - msg = msg.format(self.sprite_num.value, sprite_num) + msg = ( + f"Sprite number {self.sprite_num.value} given in base_graphics-block," + f" but it doesn't match output sprite number {sprite_num}" + ) raise generic.ScriptError(msg) @@ -317,7 +316,7 @@ def __init__(self, mapping, label=None, poslist=None): def debug_print(self, indentation): generic.print_dbg(indentation, "Recolour sprite, mapping:") for recolour in self.mapping: - generic.print_dbg(indentation + 2, "{}: {};".format(recolour.name, recolour.value)) + generic.print_dbg(indentation + 2, f"{recolour.name}: {recolour.value};") def get_labels(self): labels = {} @@ -352,7 +351,7 @@ def __str__(self): ret = "" if self.label is None else str(self.label) + ": " ret += "recolour_sprite {\n" for recolour in self.mapping: - ret += "{}: {};".format(recolour.name, recolour.value) + ret += f"{recolour.name}: {recolour.value};" ret += "}" return ret @@ -382,10 +381,7 @@ def prepare_output(self, sprite_num): val += i colour_mapping[idx] = val for i in range(256): - if i in colour_mapping: - colour = colour_mapping[i] - else: - colour = i + colour = colour_mapping.get(i, i) self.output_table.append(colour) def write(self, file): @@ -428,7 +424,7 @@ def get_labels(self): if self.label is not None: if self.label.value in labels: raise generic.ScriptError( - "Duplicate label encountered; '{}' already exists.".format(self.label.value), self.pos + f"Duplicate label encountered; '{self.label.value}' already exists.", self.pos ) labels[self.label.value] = 0 return labels, offset @@ -492,14 +488,14 @@ def parse_real_sprite(sprite, default_file, default_mask_file, poslist, id_dict) new_sprite.xrel.value, -0x8000, 0x7FFF, - "Real sprite parameter {:d} 'xrel'".format(param_offset + 1), + f"Real sprite parameter {param_offset + 1} 'xrel'", new_sprite.xrel.pos, ) generic.check_range( new_sprite.yrel.value, -0x8000, 0x7FFF, - "Real sprite parameter {:d} 'yrel'".format(param_offset + 2), + f"Real sprite parameter {param_offset + 2} 'yrel'", new_sprite.yrel.pos, ) param_offset += 2 @@ -520,7 +516,7 @@ def parse_real_sprite(sprite, default_file, default_mask_file, poslist, id_dict) param_offset += 1 if not isinstance(new_sprite.file, expression.StringLiteral): raise generic.ScriptError( - "Real sprite parameter {:d} 'file' should be a string literal".format(param_offset + 1), + f"Real sprite parameter {param_offset + 1} 'file' should be a string literal", new_sprite.file.pos, ) @@ -537,7 +533,7 @@ def parse_real_sprite(sprite, default_file, default_mask_file, poslist, id_dict) if isinstance(mask, expression.Array): if not (0 <= len(mask.values) <= 3): raise generic.ScriptError( - "Real sprite mask should be an array with 0 to 3 values, encountered {:d}".format(len(mask.values)), + f"Real sprite mask should be an array with 0 to 3 values, encountered {len(mask.values)}", mask.pos, ) if len(mask.values) == 0: @@ -561,15 +557,13 @@ def parse_real_sprite(sprite, default_file, default_mask_file, poslist, id_dict) new_sprite.mask_file = mask.reduce([id_dict]) if not isinstance(new_sprite.mask_file, expression.StringLiteral): raise generic.ScriptError( - "Real sprite parameter {:d} 'mask' should be an array or string literal".format(param_offset + 1), + f"Real sprite parameter {param_offset + 1} 'mask' should be an array or string literal", new_sprite.file.pos, ) if num_param > param_offset: raise generic.ScriptError( - "Real sprite has too many parameters, the last {:d} parameter(s) cannot be parsed.".format( - num_param - param_offset - ), + f"Real sprite has too many parameters, the last {num_param - param_offset} parameter(s) cannot be parsed.", sprite.param_list[param_offset].pos, ) @@ -602,9 +596,11 @@ def parse_sprite_data(sprite_container): sprite_list, default_file, default_mask_file, pos, zoom_level, bit_depth = sprite_data new_sprite_list = parse_sprite_list(sprite_list, default_file, default_mask_file, [pos]) if not first and len(new_sprite_list) != len(action_list): - msg = "Expected {:d} alternative sprites for {} '{}', got {:d}." - msg = msg.format( - len(action_list), sprite_container.block_type, sprite_container.block_name.value, len(new_sprite_list) + sprite_type = sprite_container.block_type + sprite_name = sprite_container.block_name.value + msg = ( + f"Expected {len(action_list)} alternative sprites for {sprite_type} '{sprite_name}'," + f"got {len(new_sprite_list)}." ) raise generic.ScriptError(msg, sprite_container.pos) diff --git a/nml/ast/alt_sprites.py b/nml/ast/alt_sprites.py index a07d17c17..f363e96f6 100644 --- a/nml/ast/alt_sprites.py +++ b/nml/ast/alt_sprites.py @@ -131,6 +131,6 @@ def __str__(self): params.append(self.mask_file) ret = "alternative_sprites({}) {{\n".format(", ".join(str(p) for p in params)) for sprite in self.sprite_list: - ret += "\t{}\n".format(sprite) + ret += f"\t{sprite}\n" ret += "}\n" return ret diff --git a/nml/ast/assignment.py b/nml/ast/assignment.py index 99e631099..db9817350 100644 --- a/nml/ast/assignment.py +++ b/nml/ast/assignment.py @@ -46,7 +46,7 @@ def debug_print(self, indentation): self.value.debug_print(indentation + 4) def __str__(self): - return "{}: {};".format(self.name, self.value) + return f"{self.name}: {self.value};" class UnitAssignment(Assignment): @@ -73,7 +73,7 @@ def __str__(self): if self.unit is None: return Assignment.__str__(self) else: - return "{}: {} {};".format(self.name, self.value, self.unit.name) + return f"{self.name}: {self.value} {self.unit.name};" class Range: @@ -96,4 +96,4 @@ def __init__(self, min, max): def __str__(self): if self.max is None: return str(self.min) - return "{} .. {}".format(self.min, self.max) + return f"{self.min} .. {self.max}" diff --git a/nml/ast/base_graphics.py b/nml/ast/base_graphics.py index bb8f5771c..a004a603e 100644 --- a/nml/ast/base_graphics.py +++ b/nml/ast/base_graphics.py @@ -39,9 +39,7 @@ def __init__(self, param_list, sprite_list, name, pos): num_params = len(param_list) if not (0 <= num_params <= 2): - raise generic.ScriptError( - "base_graphics-block requires 0 to 2 parameters, encountered {:d}".format(num_params), pos - ) + raise generic.ScriptError(f"base_graphics-block requires 0 to 2 parameters, encountered {num_params}", pos) if num_params >= 2: self.sprite_num = param_list[0].reduce_constant() else: @@ -82,6 +80,6 @@ def __str__(self): params.append(self.image_file) ret = "base_graphics {}({}) {{\n".format(name, ", ".join(str(param) for param in params)) for sprite in self.sprite_list: - ret += "\t{}\n".format(sprite) + ret += f"\t{sprite}\n" ret += "}\n" return ret diff --git a/nml/ast/base_statement.py b/nml/ast/base_statement.py index 01a9f5c4c..5129451d6 100644 --- a/nml/ast/base_statement.py +++ b/nml/ast/base_statement.py @@ -60,20 +60,18 @@ def validate(self, scope_list): """ seen_item = False for scope in scope_list: - if scope.list_type == BaseStatementList.LIST_TYPE_SKIP: + if scope.list_type == BaseStatementList.LIST_TYPE_SKIP: # noqa: SIM102 # match block if not self.bs_skipable: - raise generic.ScriptError( - "{} may not appear inside a conditional block.".format(self.bs_name), self.pos - ) - if scope.list_type == BaseStatementList.LIST_TYPE_LOOP: + raise generic.ScriptError(f"{self.bs_name} may not appear inside a conditional block.", self.pos) + if scope.list_type == BaseStatementList.LIST_TYPE_LOOP: # noqa: SIM102 # match block if not self.bs_loopable: - raise generic.ScriptError("{} may not appear inside a loop.".format(self.bs_name), self.pos) + raise generic.ScriptError(f"{self.bs_name} may not appear inside a loop.", self.pos) if scope.list_type == BaseStatementList.LIST_TYPE_ITEM: seen_item = True if not self.bs_in_item: - raise generic.ScriptError("{} may not appear inside an item block.".format(self.bs_name), self.pos) + raise generic.ScriptError(f"{self.bs_name} may not appear inside an item block.", self.pos) if not (seen_item or self.bs_out_item): - raise generic.ScriptError("{} must appear inside an item block.".format(self.bs_name), self.pos) + raise generic.ScriptError(f"{self.bs_name} must appear inside an item block.", self.pos) def register_names(self): """ @@ -95,7 +93,7 @@ def debug_print(self, indentation): @param indentation: Print all lines with at least C{indentation} spaces @type indentation: C{int} """ - raise NotImplementedError("debug_print must be implemented in BaseStatement-subclass {!r}".format(type(self))) + raise NotImplementedError(f"debug_print must be implemented in BaseStatement-subclass {type(self)!r}") def get_action_list(self): """ @@ -104,9 +102,7 @@ def get_action_list(self): @return: A list of action @rtype: C{list} of L{BaseAction} """ - raise NotImplementedError( - "get_action_list must be implemented in BaseStatement-subclass {!r}".format(type(self)) - ) + raise NotImplementedError(f"get_action_list must be implemented in BaseStatement-subclass {type(self)!r}") def __str__(self): """ @@ -115,7 +111,7 @@ def __str__(self): @return: An NML string representing this action @rtype: C{str} """ - raise NotImplementedError("__str__ must be implemented in BaseStatement-subclass {!r}".format(type(self))) + raise NotImplementedError(f"__str__ must be implemented in BaseStatement-subclass {type(self)!r}") class BaseStatementList(BaseStatement): diff --git a/nml/ast/basecost.py b/nml/ast/basecost.py index 286938af6..c001a2e72 100644 --- a/nml/ast/basecost.py +++ b/nml/ast/basecost.py @@ -55,7 +55,7 @@ def pre_process(self): new_costs.extend(tmp_list) else: raise generic.ScriptError( - "Unrecognized base cost identifier '{}' encountered".format(cost.name.value), cost.name.pos + f"Unrecognized base cost identifier '{cost.name.value}' encountered", cost.name.pos ) else: cost.name = cost.name.reduce() @@ -75,7 +75,7 @@ def get_action_list(self): def __str__(self): ret = "basecost {\n" for cost in self.costs: - ret += "\t{}: {};\n".format(cost.name, cost.value) + ret += f"\t{cost.name}: {cost.value};\n" ret += "}\n" return ret diff --git a/nml/ast/cargotable.py b/nml/ast/cargotable.py index c18dd43bf..665f832d2 100644 --- a/nml/ast/cargotable.py +++ b/nml/ast/cargotable.py @@ -34,7 +34,7 @@ def register_names(self): if self.cargo_list[i].value in global_constants.cargo_numbers: generic.print_warning( generic.Warning.GENERIC, - "Duplicate entry in cargo table: {}".format(self.cargo_list[i].value), + f"Duplicate entry in cargo table: {self.cargo_list[i].value}", cargo.pos, ) else: diff --git a/nml/ast/conditional.py b/nml/ast/conditional.py index c4465bd60..d5e56687c 100644 --- a/nml/ast/conditional.py +++ b/nml/ast/conditional.py @@ -79,7 +79,7 @@ def debug_print(self, indentation): def __str__(self): ret = "" if self.expr is not None: - ret += "if ({})".format(self.expr) + ret += f"if ({self.expr})" ret += " {\n" ret += base_statement.BaseStatementList.__str__(self) ret += "}\n" diff --git a/nml/ast/constant.py b/nml/ast/constant.py index d7e15dbf1..59bcbc3b7 100644 --- a/nml/ast/constant.py +++ b/nml/ast/constant.py @@ -27,7 +27,7 @@ def register_names(self): if not isinstance(self.name, expression.Identifier): raise generic.ScriptError("Constant name should be an identifier", self.name.pos) if self.name.value in global_constants.constant_numbers: - raise generic.ScriptError("Redefinition of constant '{}'.".format(self.name.value), self.name.pos) + raise generic.ScriptError(f"Redefinition of constant '{self.name.value}'.", self.name.pos) global_constants.constant_numbers[self.name.value] = self.value.reduce_constant( global_constants.const_list ).value @@ -44,4 +44,4 @@ def get_action_list(self): return [] def __str__(self): - return "const {} = {};".format(self.name, self.value) + return f"const {self.name} = {self.value};" diff --git a/nml/ast/disable_item.py b/nml/ast/disable_item.py index f2edf10c5..fa544b483 100644 --- a/nml/ast/disable_item.py +++ b/nml/ast/disable_item.py @@ -36,7 +36,7 @@ def __init__(self, param_list, pos): base_statement.BaseStatement.__init__(self, "disable_item()", pos) if not (1 <= len(param_list) <= 3): raise generic.ScriptError( - "disable_item() requires between 1 and 3 parameters, encountered {:d}.".format(len(param_list)), pos + f"disable_item() requires between 1 and 3 parameters, encountered {len(param_list)}.", pos ) self.feature = general.parse_feature(param_list[0]) self.first_id = param_list[1] if len(param_list) > 1 else None @@ -66,7 +66,7 @@ def __str__(self): ret += ", " + str(self.first_id) if self.last_id is not None: ret += ", " + str(self.last_id) - return "disable_item({});\n".format(ret) + return f"disable_item({ret});\n" def get_action_list(self): return action0.get_disable_actions(self) diff --git a/nml/ast/error.py b/nml/ast/error.py index 42cb86880..f28a39599 100644 --- a/nml/ast/error.py +++ b/nml/ast/error.py @@ -89,12 +89,12 @@ def __str__(self): if self.severity.value == actionB.error_severity[s]: sev = s break - res = "error({}, {}".format(sev, self.msg) + res = f"error({sev}, {self.msg}" if self.data is not None: - res += ", {}".format(self.data) + res += f", {self.data}" if len(self.params) > 0: - res += ", {}".format(self.params[0]) + res += f", {self.params[0]}" if len(self.params) > 1: - res += ", {}".format(self.params[1]) + res += f", {self.params[1]}" res += ");\n" return res diff --git a/nml/ast/font.py b/nml/ast/font.py index 12a3b642c..56ad57dcf 100644 --- a/nml/ast/font.py +++ b/nml/ast/font.py @@ -74,6 +74,6 @@ def __str__(self): params.append(self.image_file) ret = "font_glyph {}({}) {{\n".format(name, ", ".join(str(param) for param in params)) for sprite in self.sprite_list: - ret += "\t{}\n".format(sprite) + ret += f"\t{sprite}\n" ret += "}\n" return ret diff --git a/nml/ast/general.py b/nml/ast/general.py index 93656c637..6ac74ea9a 100644 --- a/nml/ast/general.py +++ b/nml/ast/general.py @@ -55,7 +55,7 @@ def feature_name(feature): for name, num in feature_ids.items(): if num == feature: return name - raise AssertionError("Invalid feature number '{}'.".format(feature)) + raise AssertionError(f"Invalid feature number '{feature}'.") def parse_feature(expr): @@ -70,7 +70,7 @@ def parse_feature(expr): """ expr = expr.reduce_constant([feature_ids]) if expr.value not in feature_ids.values(): - raise generic.ScriptError("Invalid feature '{:02X}' encountered.".format(expr.value), expr.pos) + raise generic.ScriptError(f"Invalid feature '{expr.value:02X}' encountered.", expr.pos) return expr diff --git a/nml/ast/grf.py b/nml/ast/grf.py index 3440b1fd4..91e7076ad 100644 --- a/nml/ast/grf.py +++ b/nml/ast/grf.py @@ -33,7 +33,7 @@ def print_stats(): Print statistics about used ids. """ if param_stats[0] > 0: - generic.print_info("GRF parameter registers: {}/{}".format(param_stats[0], param_stats[1])) + generic.print_info(f"GRF parameter registers: {param_stats[0]}/{param_stats[1]}") def set_palette_used(pal): @@ -193,13 +193,13 @@ def get_action_list(self): def __str__(self): ret = "grf {\n" - ret += "\tgrfid: {};\n".format(str(self.grfid)) - ret += "\tname: {};\n".format(str(self.name)) - ret += "\tdesc: {};\n".format(str(self.desc)) + ret += f"\tgrfid: {self.grfid};\n" + ret += f"\tname: {self.name};\n" + ret += f"\tdesc: {self.desc};\n" if self.url is not None: - ret += "\turl: {};\n".format(self.url) - ret += "\tversion: {};\n".format(self.version) - ret += "\tmin_compatible_version: {};\n".format(self.min_compatible_version) + ret += f"\turl: {self.url};\n" + ret += f"\tversion: {self.version};\n" + ret += f"\tmin_compatible_version: {self.min_compatible_version};\n" for param in self.params: ret += str(param) ret += "}\n" @@ -225,15 +225,15 @@ def pre_process(self): self.set_property(set_val.name.value, set_val.value) def __str__(self): - ret = "\t\t{} {{\n".format(self.name) + ret = f"\t\t{self.name} {{\n" for val in self.value_list: if val.name.value == "names": ret += "\t\t\tnames: {\n" for name in val.value.values: - ret += "\t\t\t\t{}: {};\n".format(name.name, name.value) + ret += f"\t\t\t\t{name.name}: {name.value};\n" ret += "\t\t\t};\n" else: - ret += "\t\t\t{}: {};\n".format(val.name, val.value) + ret += f"\t\t\t{val.name}: {val.value};\n" ret += "\t\t}\n" return ret @@ -352,9 +352,7 @@ def pre_process(self, num): setting.bit_num = expression.ConstantNumeric(ParameterDescription.free_bits[self.num.value].pop(0)) else: if setting.bit_num.value not in ParameterDescription.free_bits[self.num.value]: - raise generic.ScriptError( - "Bit {} is already used".format(setting.bit_num.value), setting.name.pos - ) + raise generic.ScriptError(f"Bit {setting.bit_num.value} is already used", setting.name.pos) ParameterDescription.free_bits[self.num.value].remove(setting.bit_num.value) global_constants.misc_grf_bits[setting.name.value] = { "param": self.num.value, diff --git a/nml/ast/item.py b/nml/ast/item.py index 87cc32901..042e308e8 100644 --- a/nml/ast/item.py +++ b/nml/ast/item.py @@ -44,9 +44,7 @@ def __init__(self, params, body, pos): ) if not (1 <= len(params) <= 4): - raise generic.ScriptError( - "Item block requires between 1 and 4 parameters, found {:d}.".format(len(params)), self.pos - ) + raise generic.ScriptError(f"Item block requires between 1 and 4 parameters, found {len(params)}.", self.pos) self.feature = general.parse_feature(params[0]) if self.feature.value in (0x08, 0x0C, 0x0E): raise generic.ScriptError("Defining item blocks for this feature is not allowed.", self.pos) @@ -76,9 +74,10 @@ def register_names(self): if self.id is not None and existing_id.value != self.id.value: raise generic.ScriptError( ( - "Duplicate item with name '{}'." - " This item has already been assigned to id {:d}, cannot reassign to id {:d}" - ).format(self.name.value, existing_id.value, self.id.value), + f"Duplicate item with name '{self.name.value}'." + f" This item has already been assigned to id {existing_id.value}," + f" cannot reassign to id {self.id.value}" + ), self.pos, ) self.id = existing_id @@ -112,13 +111,13 @@ def get_action_list(self): return base_statement.BaseStatementList.get_action_list(self) def __str__(self): - ret = "item({:d}".format(self.feature.value) + ret = f"item({self.feature.value}" if self.name is not None: - ret += ", {}".format(self.name) + ret += f", {self.name}" ret += ", {}".format(str(self.id) if self.id is not None else "-1") if self.size is not None: sizes = ["1X1", None, "2X1", "1X2", "2X2"] - ret += ", HOUSE_SIZE_{}".format(sizes[self.size.value]) + ret += f", HOUSE_SIZE_{sizes[self.size.value]}" ret += ") {\n" ret += base_statement.BaseStatementList.__str__(self) ret += "}\n" @@ -158,7 +157,7 @@ def debug_print(self, indentation): def __str__(self): unit = "" if self.unit is None else " " + str(self.unit) - return "\t{}: {}{};".format(self.name, self.value, unit) + return f"\t{self.name}: {self.value}{unit};" class PropertyBlock(base_statement.BaseStatement): @@ -189,7 +188,7 @@ def get_action_list(self): def __str__(self): ret = "property {\n" for prop in self.prop_list: - ret += "{}\n".format(prop) + ret += f"{prop}\n" ret += "}\n" return ret @@ -215,11 +214,11 @@ def get_action_list(self): return action3.parse_graphics_block(self.graphics_block, item_feature, wagon_id, item_size, True) def __str__(self): - ret = "livery_override({}) {{\n".format(self.wagon_id) + ret = f"livery_override({self.wagon_id}) {{\n" for graphics in self.graphics_block.graphics_list: - ret += "\t{}\n".format(graphics) + ret += f"\t{graphics}\n" if self.graphics_block.default_graphics is not None: - ret += "\t{}\n".format(self.graphics_block.default_graphics) + ret += f"\t{self.graphics_block.default_graphics}\n" ret += "}\n" return ret @@ -274,9 +273,9 @@ def get_action_list(self): def __str__(self): ret = "graphics {\n" for graphics in self.graphics_list: - ret += "\t{}\n".format(graphics) + ret += f"\t{graphics}\n" if self.default_graphics is not None: - ret += "\t{}\n".format(self.default_graphics) + ret += f"\t{self.default_graphics}\n" ret += "}\n" return ret @@ -303,4 +302,4 @@ def debug_print(self, indentation): self.result.debug_print(indentation + 2) def __str__(self): - return "{}: {}".format(self.cargo_id, self.result) + return f"{self.cargo_id}: {self.result}" diff --git a/nml/ast/loop.py b/nml/ast/loop.py index cf2e861db..6a55d668a 100644 --- a/nml/ast/loop.py +++ b/nml/ast/loop.py @@ -48,7 +48,7 @@ def get_action_list(self): return action7.parse_loop_block(self) def __str__(self): - ret = "while({}) {{\n".format(self.expr) + ret = f"while({self.expr}) {{\n" ret += base_statement.BaseStatementList.__str__(self) ret += "}\n" return ret diff --git a/nml/ast/produce.py b/nml/ast/produce.py index 880927fa6..9f89d5f3e 100644 --- a/nml/ast/produce.py +++ b/nml/ast/produce.py @@ -35,7 +35,7 @@ def __init__(self, param_list, pos): base_statement.BaseStatement.__init__(self, "produce-block", pos, False, False) if not (6 <= len(param_list) <= 7): raise generic.ScriptError( - "produce-block requires 6 or 7 parameters, encountered {:d}".format(len(param_list)), self.pos + f"produce-block requires 6 or 7 parameters, encountered {len(param_list)}", self.pos ) name = param_list[0] if not isinstance(name, expression.Identifier): @@ -48,7 +48,7 @@ def __init__(self, param_list, pos): def pre_process(self): generic.print_warning( generic.Warning.DEPRECATION, - "Consider using the new produce() syntax for '{}'".format(self.name), + f"Consider using the new produce() syntax for '{self.name}'", self.name.pos, ) var_scope = action2var.get_scope(0x0A) @@ -128,7 +128,7 @@ def collect_references(self): return all_refs def __str__(self): - return "produce({0}, [{1}], [{2}], {3})\n".format( + return "produce({}, [{}], [{}], {})\n".format( str(self.name), " ".join(str(x) for x in self.subtract_in), " ".join(str(x) for x in self.add_out), @@ -138,10 +138,10 @@ def __str__(self): def debug_print(self, indentation): generic.print_dbg(indentation, "Produce, name =", self.name) for cargopair in self.subtract_in: - generic.print_dbg(indentation + 2, "Subtract from input {0}:".format(cargopair.name.value)) + generic.print_dbg(indentation + 2, f"Subtract from input {cargopair.name.value}:") cargopair.value.debug_print(indentation + 4) for cargopair in self.add_out: - generic.print_dbg(indentation + 2, "Add to output {0}:".format(cargopair.name.value)) + generic.print_dbg(indentation + 2, f"Add to output {cargopair.name.value}:") cargopair.value.debug_print(indentation + 4) generic.print_dbg(indentation + 2, "Again:") self.again.debug_print(indentation + 4) diff --git a/nml/ast/replace.py b/nml/ast/replace.py index 71c723f4b..ac9f5dcc3 100644 --- a/nml/ast/replace.py +++ b/nml/ast/replace.py @@ -81,9 +81,9 @@ def get_action_list(self): def __str__(self): name = str(self.block_name) if self.block_name is not None else "" def_file = "" if self.image_file is None else ", " + str(self.image_file) - ret = "replace {}({}{}) {{\n".format(name, self.start_id, def_file) + ret = f"replace {name}({self.start_id}{def_file}) {{\n" for sprite in self.sprite_list: - ret += "\t{}\n".format(sprite) + ret += f"\t{sprite}\n" ret += "}\n" return ret @@ -164,6 +164,6 @@ def __str__(self): params.append(self.offset) ret = "replacenew {}({}) {{\n".format(name, ", ".join(str(param) for param in params)) for sprite in self.sprite_list: - ret += "\t{}\n".format(sprite) + ret += f"\t{sprite}\n" ret += "}\n" return ret diff --git a/nml/ast/snowline.py b/nml/ast/snowline.py index 95e771fee..43778ef21 100644 --- a/nml/ast/snowline.py +++ b/nml/ast/snowline.py @@ -41,7 +41,7 @@ def __init__(self, line_type, height_data, pos): self.date_heights = height_data def debug_print(self, indentation): - generic.print_dbg(indentation, "Snowline (type={})".format(self.type)) + generic.print_dbg(indentation, f"Snowline (type={self.type})") for dh in self.date_heights: dh.debug_print(indentation + 2) diff --git a/nml/ast/sort_vehicles.py b/nml/ast/sort_vehicles.py index 35d6c744b..3d3ed23aa 100644 --- a/nml/ast/sort_vehicles.py +++ b/nml/ast/sort_vehicles.py @@ -32,9 +32,7 @@ class SortVehicles(base_statement.BaseStatement): def __init__(self, params, pos): base_statement.BaseStatement.__init__(self, "sort-block", pos) if len(params) != 2: - raise generic.ScriptError( - "Sort-block requires exactly two parameters, got {:d}".format(len(params)), self.pos - ) + raise generic.ScriptError(f"Sort-block requires exactly two parameters, got {len(params)}", self.pos) self.feature = general.parse_feature(params[0]) self.vehid_list = params[1] @@ -57,4 +55,4 @@ def get_action_list(self): return action0.parse_sort_block(self.feature.value, self.vehid_list.values) def __str__(self): - return "sort({:d}, {});\n".format(self.feature.value, self.vehid_list) + return f"sort({self.feature.value}, {self.vehid_list});\n" diff --git a/nml/ast/sprite_container.py b/nml/ast/sprite_container.py index 0f91dc1a1..9b1c85af6 100644 --- a/nml/ast/sprite_container.py +++ b/nml/ast/sprite_container.py @@ -43,9 +43,7 @@ def __init__(self, block_type, block_name): self.sprite_data = {} if block_name is not None: if block_name.value in SpriteContainer.sprite_blocks: - raise generic.ScriptError( - "Block with name '{}' is already defined.".format(block_name.value), block_name.pos - ) + raise generic.ScriptError(f"Block with name '{block_name.value}' is already defined.", block_name.pos) SpriteContainer.sprite_blocks[block_name.value] = self def add_sprite_data(self, sprite_list, default_file, pos, zoom_level=0, bit_depth=8, default_mask_file=None): @@ -80,6 +78,4 @@ def get_all_sprite_data(self): def resolve_sprite_block(cls, block_name): if block_name.value in cls.sprite_blocks: return cls.sprite_blocks[block_name.value] - raise generic.ScriptError( - "Undeclared block identifier '{}' encountered".format(block_name.value), block_name.pos - ) + raise generic.ScriptError(f"Undeclared block identifier '{block_name.value}' encountered", block_name.pos) diff --git a/nml/ast/spriteblock.py b/nml/ast/spriteblock.py index ad106c402..2081170bc 100644 --- a/nml/ast/spriteblock.py +++ b/nml/ast/spriteblock.py @@ -31,9 +31,7 @@ def pre_process(self): for sprite in self.sprite_list: if isinstance(sprite, real_sprite.TemplateUsage): if sprite.name.value == self.name.value: - raise generic.ScriptError( - "Sprite template '{}' includes itself.".format(sprite.name.value), self.pos - ) + raise generic.ScriptError(f"Sprite template '{sprite.name.value}' includes itself.", self.pos) elif sprite.name.value not in real_sprite.sprite_template_map: raise generic.ScriptError( "Encountered unknown template identifier: " + sprite.name.value, sprite.pos @@ -43,9 +41,8 @@ def pre_process(self): real_sprite.sprite_template_map[self.name.value] = self else: raise generic.ScriptError( - "Template named '{}' is already defined, first definition at {}".format( - self.name.value, real_sprite.sprite_template_map[self.name.value].pos - ), + f"Template named '{self.name.value}' is already defined," + f" first definition at {real_sprite.sprite_template_map[self.name.value].pos}", self.pos, ) @@ -56,7 +53,7 @@ def get_labels(self): sprite_labels, num_sprites = sprite.get_labels() for lbl, lbl_offset in sprite_labels.items(): if lbl in labels: - raise generic.ScriptError("Duplicate label encountered; '{}' already exists.".format(lbl), self.pos) + raise generic.ScriptError(f"Duplicate label encountered; '{lbl}' already exists.", self.pos) labels[lbl] = lbl_offset + offset offset += num_sprites return labels, offset @@ -76,7 +73,7 @@ def get_action_list(self): def __str__(self): ret = "template {}({}) {{\n".format(str(self.name), ", ".join([str(param) for param in self.param_list])) for sprite in self.sprite_list: - ret += "\t{}\n".format(sprite) + ret += f"\t{sprite}\n" ret += "}\n" return ret @@ -151,7 +148,7 @@ def pre_process(self): sprite_labels, num_sprites = sprite.get_labels() for lbl, lbl_offset in sprite_labels.items(): if lbl in self.labels: - raise generic.ScriptError("Duplicate label encountered; '{}' already exists.".format(lbl), self.pos) + raise generic.ScriptError(f"Duplicate label encountered; '{lbl}' already exists.", self.pos) self.labels[lbl] = lbl_offset + offset offset += num_sprites @@ -184,7 +181,7 @@ def __str__(self): params.append(self.mask_file) ret = "spriteset({}) {{\n".format(", ".join(str(p) for p in params)) for sprite in self.sprite_list: - ret += "\t{}\n".format(str(sprite)) + ret += f"\t{sprite}\n" ret += "}\n" return ret @@ -219,9 +216,9 @@ def get_action_list(self): return action_list def __str__(self): - ret = "spritegroup {} {{\n".format(self.name) + ret = f"spritegroup {self.name} {{\n" for spriteview in self.spriteview_list: - ret += "\t{}\n".format(spriteview) + ret += f"\t{spriteview}\n" ret += "}\n" return ret @@ -275,7 +272,7 @@ def pre_process(self): if not isinstance(param, expression.Identifier): raise generic.ScriptError("spritelayout parameter names must be identifiers.", param.pos) if param.value in seen_names: - raise generic.ScriptError("Duplicate parameter name '{}' encountered.".format(param.value), param.pos) + raise generic.ScriptError(f"Duplicate parameter name '{param.value}' encountered.", param.pos) seen_names.add(param.value) spritelayout_base_class.pre_process(self) diff --git a/nml/ast/switch.py b/nml/ast/switch.py index 0bacd0b5c..e376b328d 100644 --- a/nml/ast/switch.py +++ b/nml/ast/switch.py @@ -38,7 +38,7 @@ def __init__(self, param_list, body, pos): self.var_range = var_ranges[param_list[1].value] else: raise generic.ScriptError( - "Unrecognized value for switch parameter 2 'variable range': '{}'".format(param_list[1].value), + f"Unrecognized value for switch parameter 2 'variable range': '{param_list[1].value}'", param_list[1].pos, ) if not isinstance(param_list[2], expression.Identifier): @@ -58,7 +58,7 @@ def pre_process(self): if not isinstance(param, expression.Identifier): raise generic.ScriptError("switch parameter names must be identifiers.", param.pos) if param.value in seen_names: - raise generic.ScriptError("Duplicate parameter name '{}' encountered.".format(param.value), param.pos) + raise generic.ScriptError(f"Duplicate parameter name '{param.value}' encountered.", param.pos) seen_names.add(param.value) feature = next(iter(self.feature_set)) @@ -112,7 +112,7 @@ def optimise(self): if self.optimised: generic.print_warning( generic.Warning.OPTIMISATION, - "Block '{}' returns a constant, optimising.".format(self.name.value), + f"Block '{self.name.value}' returns a constant, optimising.", self.pos, ) return self.optimised is not self @@ -129,15 +129,12 @@ def collect_references(self): def is_read_only(self): for result in [r.result for r in self.body.ranges] + [self.body.default]: - if result is not None and result.value is not None: - if not result.value.is_read_only(): - return False + if result is not None and result.value is not None and not result.value.is_read_only(): + return False return self.expr.is_read_only() def debug_print(self, indentation): - generic.print_dbg( - indentation, "Switch, Feature = {:d}, name = {}".format(next(iter(self.feature_set)), self.name.value) - ) + generic.print_dbg(indentation, f"Switch, Feature = {next(iter(self.feature_set))}, name = {self.name.value}") if self.param_list: generic.print_dbg(indentation + 2, "Parameters:") @@ -158,8 +155,8 @@ def get_action_list(self): def __str__(self): var_range = "SELF" if self.var_range == 0x89 else "PARENT" params = "" if not self.param_list else "{}, ".format(", ".join(str(x) for x in self.param_list)) - return "switch({}, {}, {}, {}{}) {{\n{}}}\n".format( - next(iter(self.feature_set)), var_range, self.name, params, self.expr, self.body + return ( + f"switch({next(iter(self.feature_set))}, {var_range}, {self.name}, {params}{self.expr}) {{\n{self.body}}}\n" ) @@ -213,9 +210,9 @@ def debug_print(self, indentation): self.default.debug_print(indentation + 2) def __str__(self): - ret = "".join("\t{}\n".format(r) for r in self.ranges) + ret = "".join(f"\t{r}\n" for r in self.ranges) if self.default is not None: - ret += "\t{}\n".format(str(self.default)) + ret += f"\t{self.default}\n" return ret @@ -287,18 +284,16 @@ def __str__(self): assert self.is_return return "return;" elif self.is_return: - return "return {};".format(self.value) + return f"return {self.value};" else: - return "{};".format(self.value) + return f"{self.value};" class RandomSwitch(switch_base_class): def __init__(self, param_list, choices, pos): base_statement.BaseStatement.__init__(self, "random_switch-block", pos, False, False) if not (3 <= len(param_list) <= 4): - raise generic.ScriptError( - "random_switch requires 3 or 4 parameters, encountered {:d}".format(len(param_list)), pos - ) + raise generic.ScriptError(f"random_switch requires 3 or 4 parameters, encountered {len(param_list)}", pos) # feature feature = general.parse_feature(param_list[0]).value @@ -373,7 +368,7 @@ def pre_process(self): spritegroup = action2.resolve_spritegroup(dep_list[i].name) if not isinstance(spritegroup, RandomSwitch): raise generic.ScriptError( - "Value of (in)dependent '{}' should refer to a random_switch.".format(dep_list[i].name.value), + f"Value of (in)dependent '{dep_list[i].name.value}' should refer to a random_switch.", dep_list[i].pos, ) @@ -401,7 +396,7 @@ def optimise(self): ): generic.print_warning( generic.Warning.OPTIMISATION, - "Block '{}' returns a constant, optimising.".format(self.name.value), + f"Block '{self.name.value}' returns a constant, optimising.", self.pos, ) self.optimised = optimised @@ -417,10 +412,7 @@ def collect_references(self): return all_refs def is_read_only(self): - for choice in self.choices: - if not choice.result.value.is_read_only(): - return False - return True + return all(choice.result.value.is_read_only() for choice in self.choices) def debug_print(self, indentation): generic.print_dbg(indentation, "Random") @@ -448,13 +440,11 @@ def get_action_list(self): return [] def __str__(self): - ret = "random_switch({}, {}, {}, {}) {{\n".format( - str(next(iter(self.feature_set))), str(self.type), str(self.name), str(self.triggers) - ) + ret = f"random_switch({next(iter(self.feature_set))}, {self.type}, {self.name}, {self.triggers}) {{\n" for dep in self.dependent: - ret += "dependent: {};\n".format(dep) + ret += f"dependent: {dep};\n" for indep in self.independent: - ret += "independent: {};\n".format(indep) + ret += f"independent: {indep};\n" for choice in self.choices: ret += str(choice) + "\n" ret += "}\n" @@ -495,4 +485,4 @@ def debug_print(self, indentation): self.result.debug_print(indentation + 2) def __str__(self): - return "{}: {}".format(self.probability, self.result) + return f"{self.probability}: {self.result}" diff --git a/nml/ast/tilelayout.py b/nml/ast/tilelayout.py index 0b59c3405..37ffdb6b5 100644 --- a/nml/ast/tilelayout.py +++ b/nml/ast/tilelayout.py @@ -50,7 +50,7 @@ def pre_process(self): if isinstance(tileprop, assignment.Assignment): name = tileprop.name.value if name in self.properties: - raise generic.ScriptError("Duplicate property {} in tile layout".format(name), tileprop.name.pos) + raise generic.ScriptError(f"Duplicate property {name} in tile layout", tileprop.name.pos) self.properties[name] = tileprop.value.reduce_constant(global_constants.const_list) else: assert isinstance(tileprop, LayoutTile) @@ -61,15 +61,13 @@ def pre_process(self): tile = expression.ConstantNumeric(0xFF) self.tile_list.append(LayoutTile(x, y, tile)) if self.name in action0properties.tilelayout_names: - raise generic.ScriptError( - "A tile layout with name '{}' has already been defined.".format(self.name), self.pos - ) + raise generic.ScriptError(f"A tile layout with name '{self.name}' has already been defined.", self.pos) action0properties.tilelayout_names[self.name] = self def debug_print(self, indentation): generic.print_dbg(indentation, "TileLayout") for tile in self.tile_list: - generic.print_dbg(indentation + 2, "At {:d},{:d}:".format(tile.x, tile.y)) + generic.print_dbg(indentation + 2, f"At {tile.x},{tile.y}:") tile.tiletype.debug_print(indentation + 4) def get_action_list(self): @@ -101,9 +99,7 @@ def write(self, file): tile_id = global_constants.item_names[tile.tiletype.value].id if not isinstance(tile_id, expression.ConstantNumeric): raise generic.ScriptError( - "Tile '{}' cannot be used in a tilelayout, as its ID is not a constant.".format( - tile.tiletype.value - ), + f"Tile '{tile.tiletype.value}' cannot be used in a tilelayout, as its ID is not a constant.", tile.tiletype.pos, ) file.print_wordx(tile_id.value) @@ -133,4 +129,4 @@ def __init__(self, x, y, tiletype): self.tiletype = tiletype def __str__(self): - return "{}, {}: {};".format(self.x, self.y, self.tiletype) + return f"{self.x}, {self.y}: {self.tiletype};" diff --git a/nml/ast/townnames.py b/nml/ast/townnames.py index 3370fd713..1df864174 100644 --- a/nml/ast/townnames.py +++ b/nml/ast/townnames.py @@ -95,13 +95,13 @@ def pre_process(self): raise generic.ScriptError("ID must be a number between 0 and 0x7f (inclusive)", self.pos) if self.id_number not in actionF.free_numbers: - raise generic.ScriptError("town names ID 0x{:x} is already used.".format(self.id_number), self.pos) + raise generic.ScriptError(f"town names ID 0x{self.id_number:x} is already used.", self.pos) actionF.free_numbers.remove(self.id_number) def __str__(self): ret = "town_names" if self.name is not None: - ret += "({})".format(self.name) + ret += f"({self.name})" ret += "{{\n{}}}\n".format("".join(str(x) for x in self.param_list)) return ret @@ -156,9 +156,7 @@ def make_actions(self): if len(self.pieces) == 0: raise generic.ScriptError("Expected names and/or town_name references in the part.", self.pos) if len(self.pieces) > 255: - raise generic.ScriptError( - "Too many values in a part, found {:d}, maximum is 255".format(len(self.pieces)), self.pos - ) + raise generic.ScriptError(f"Too many values in a part, found {len(self.pieces)}, maximum is 255", self.pos) return actFs, self @@ -218,7 +216,7 @@ def move_pieces(self): # Assign to action F actFs = [] for _prob, _idx, sub in finished_actions: - actF_name = expression.Identifier("**townname #{:d}**".format(townname_serial), None) + actF_name = expression.Identifier(f"**townname #{townname_serial}**", None) townname_serial = townname_serial + 1 town_part = TownNamesPart(sub, self.pos) town_part.set_numbits(num_bits) @@ -268,7 +266,7 @@ def set_numbits(self, numbits): def debug_print(self, indentation): total = sum(piece.probability.value for piece in self.pieces) - generic.print_dbg(indentation, "Town names part (total {:d})".format(total)) + generic.print_dbg(indentation, f"Town names part (total {total})") for piece in self.pieces: piece.debug_print(indentation + 2, total) @@ -322,7 +320,7 @@ def debug_print(self, indentation): self.value.debug_print(indentation + 4) def __str__(self): - return "{}: {};\n".format(self.key, self.value) + return f"{self.key}: {self.value};\n" class TownNamesEntryDefinition: @@ -369,15 +367,15 @@ def debug_print(self, indentation, total): if isinstance(self.def_number, expression.Identifier): name_text = "name '" + self.def_number.value + "'" else: - name_text = "number 0x{:x}".format(self.def_number.value) + name_text = f"number 0x{self.def_number.value:x}" generic.print_dbg( indentation, - "Insert town_name ID {} with probability {:d}/{:d}".format(name_text, self.probability.value, total), + f"Insert town_name ID {name_text} with probability {self.probability.value}/{total}", ) def __str__(self): - return "town_names({}, {:d}),".format(str(self.def_number), self.probability.value) + return f"town_names({self.def_number}, {self.probability.value})," def get_length(self): return 2 @@ -392,16 +390,14 @@ def resolve_townname_id(self): self.number = actionF.named_numbers.get(self.def_number.value) if self.number is None: raise generic.ScriptError( - 'Town names name "{}" is not defined or points to a next town_names node'.format( - self.def_number.value - ), + f'Town names name "{self.def_number.value}" is not defined or points to a next town_names node', self.pos, ) else: self.number = self.def_number.value if self.number not in actionF.numbered_numbers: raise generic.ScriptError( - 'Town names number "{}" is not defined or points to a next town_names node'.format(self.number), + f'Town names number "{self.number}" is not defined or points to a next town_names node', self.pos, ) return self.number @@ -439,12 +435,10 @@ def pre_process(self): raise generic.ScriptError("Probability out of range (must be between 0 and 0x7f inclusive).", self.pos) def debug_print(self, indentation, total): - generic.print_dbg( - indentation, "Text {} with probability {:d}/{:d}".format(self.text.value, self.probability.value, total) - ) + generic.print_dbg(indentation, f"Text {self.text.value} with probability {self.probability.value}/{total}") def __str__(self): - return "text({}, {:d}),".format(self.text, self.probability.value) + return f"text({self.text}, {self.probability.value})," def get_length(self): return 1 + grfstrings.get_string_size(self.text.value) # probability, text diff --git a/nml/editors/extract_tables.py b/nml/editors/extract_tables.py index 528b7429c..1bc35f916 100644 --- a/nml/editors/extract_tables.py +++ b/nml/editors/extract_tables.py @@ -71,7 +71,7 @@ variables = set() for d in var_tables: - for key in d.keys(): + for key in d: variables.add(key) prop_tables = [ @@ -97,7 +97,7 @@ properties = set() for d in prop_tables: - for key in d.keys(): + for key in d: properties.add(key) dummy = action2layout.Action2LayoutSprite(None, None) @@ -126,7 +126,7 @@ callbacks = set() for d in cb_tables: - for key in d.keys(): + for key in d: callbacks.add(key) # No easy way to get action14 stuff @@ -169,7 +169,7 @@ constant_names = set() for d in const_tables: - for key in d.keys(): + for key in d: constant_names.add(key) act5_names = set(action5.action5_table.keys()) diff --git a/nml/editors/kate.py b/nml/editors/kate.py index 454720062..3b7f718bc 100644 --- a/nml/editors/kate.py +++ b/nml/editors/kate.py @@ -163,19 +163,19 @@ def write_file(fname): with open(fname, "w") as file: file.write(header_text) for word in extract_tables.keywords: - file.write(" {} \n".format(word)) + file.write(f" {word} \n") file.write(feature_text) for word in extract_tables.features: - file.write(" {} \n".format(word)) + file.write(f" {word} \n") file.write(builtin_text) for word in extract_tables.functions: - file.write(" {} \n".format(word)) + file.write(f" {word} \n") file.write(constant_text) for word in extract_tables.callback_names_table: - file.write(" {} \n".format(word)) + file.write(f" {word} \n") file.write(tail_text) diff --git a/nml/editors/visualstudiocode.py b/nml/editors/visualstudiocode.py index 3c9ad2191..af61505d4 100644 --- a/nml/editors/visualstudiocode.py +++ b/nml/editors/visualstudiocode.py @@ -12,6 +12,7 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import json + from nml.editors import extract_tables text1 = """\ diff --git a/nml/expression/__init__.py b/nml/expression/__init__.py index 4cef1ef81..ffbe7d6a7 100644 --- a/nml/expression/__init__.py +++ b/nml/expression/__init__.py @@ -15,6 +15,7 @@ import re +from .abs_op import AbsOp from .array import Array from .base_expression import ConstantFloat, ConstantNumeric, Expression, Type from .bin_not import BinNot, Not @@ -33,10 +34,41 @@ from .string import String from .string_literal import StringLiteral from .ternaryop import TernaryOp -from .abs_op import AbsOp from .variable import Variable -is_valid_id = re.compile("[a-zA-Z_][a-zA-Z0-9_]{3}$") +__all__ = [ + "AbsOp", + "AcceptCargo", + "Array", + "BinNot", + "BinOp", + "BitMask", + "Boolean", + "ConstantFloat", + "ConstantNumeric", + "Expression", + "FunctionCall", + "FunctionPtr", + "GRMOp", + "Identifier", + "Not", + "OtherGRFParameter", + "Parameter", + "PatchVariable", + "ProduceCargo", + "SpecialCheck", + "SpecialParameter", + "SpriteGroupRef", + "StorageOp", + "String", + "StringLiteral", + "TernaryOp", + "Type", + "Variable", + "parse_string_to_dword", +] + +is_valid_id = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]{3}$") def identifier_to_print(name): @@ -49,4 +81,4 @@ def identifier_to_print(name): """ if is_valid_id.match(name): return name - return '"{}"'.format(name) + return f'"{name}"' diff --git a/nml/expression/base_expression.py b/nml/expression/base_expression.py index b6580ad28..b2cee5895 100644 --- a/nml/expression/base_expression.py +++ b/nml/expression/base_expression.py @@ -46,7 +46,7 @@ def debug_print(self, indentation): @param indentation: Indent all printed lines with at least C{indentation} spaces. """ - raise NotImplementedError("debug_print must be implemented in expression-subclass {!r}".format(type(self))) + raise NotImplementedError(f"debug_print must be implemented in expression-subclass {type(self)!r}") def __str__(self): """ @@ -54,7 +54,7 @@ def __str__(self): @return: A string representation of this expression. """ - raise NotImplementedError("__str__ must be implemented in expression-subclass {!r}".format(type(self))) + raise NotImplementedError(f"__str__ must be implemented in expression-subclass {type(self)!r}") def reduce(self, id_dicts=None, unknown_id_fatal=True): """ @@ -67,7 +67,7 @@ def reduce(self, id_dicts=None, unknown_id_fatal=True): @return: A deep copy of this expression simplified as much as possible. """ - raise NotImplementedError("reduce must be implemented in expression-subclass {!r}".format(type(self))) + raise NotImplementedError(f"reduce must be implemented in expression-subclass {type(self)!r}") def reduce_constant(self, id_dicts=None): """ diff --git a/nml/expression/binop.py b/nml/expression/binop.py index fe83c0a45..9c0601cf0 100644 --- a/nml/expression/binop.py +++ b/nml/expression/binop.py @@ -225,9 +225,9 @@ def reduce(self, id_dicts=None, unknown_id_fatal=True): def supported_by_action2(self, raise_error): if not self.op.act2_supports: - token = " '{}'".format(self.op.token) if self.op.token else "" + token = f" '{self.op.token}'" if self.op.token else "" if raise_error: - raise generic.ScriptError("Operator{} not supported in a switch-block".format(token), self.pos) + raise generic.ScriptError(f"Operator{token} not supported in a switch-block", self.pos) return False return self.expr1.supported_by_action2(raise_error) and self.expr2.supported_by_action2(raise_error) @@ -240,8 +240,8 @@ def supported_by_actionD(self, raise_error): raise generic.ScriptError("STORE_TEMP is only available in switch-blocks.", self.pos) # default case - token = " '{}'".format(self.op.token) if self.op.token else "" - raise generic.ScriptError("Operator{} not supported in parameter assignment".format(token), self.pos) + token = f" '{self.op.token}'" if self.op.token else "" + raise generic.ScriptError(f"Operator{token} not supported in parameter assignment", self.pos) return False return self.expr1.supported_by_actionD(raise_error) and self.expr2.supported_by_actionD(raise_error) diff --git a/nml/expression/boolean.py b/nml/expression/boolean.py index e80f3712a..1d45b26cc 100644 --- a/nml/expression/boolean.py +++ b/nml/expression/boolean.py @@ -60,4 +60,4 @@ def is_boolean(self): return True def __str__(self): - return "!!({})".format(self.expr) + return f"!!({self.expr})" diff --git a/nml/expression/cargo.py b/nml/expression/cargo.py index 4530609b7..b1ce15e54 100644 --- a/nml/expression/cargo.py +++ b/nml/expression/cargo.py @@ -33,13 +33,13 @@ def cargolabel(self): def debug_print(self, indentation): if self.value is None: - generic.print_dbg(indentation, "{0} cargo {1}".format(self._debugname, self.cargolabel())) + generic.print_dbg(indentation, f"{self._debugname} cargo {self.cargolabel()}") else: - generic.print_dbg(indentation, "{0} cargo {1} with result:".format(self._debugname, self.cargolabel())) + generic.print_dbg(indentation, f"{self._debugname} cargo {self.cargolabel()} with result:") self.value.debug_print(indentation + 2) def __str__(self): - return "{0}({1}, {2})".format(self._fnname, self.cargolabel(), str(self.value)) + return f"{self._fnname}({self.cargolabel()}, {self.value})" def reduce(self, id_dicts=None, unknown_id_fatal=True): return self diff --git a/nml/expression/functioncall.py b/nml/expression/functioncall.py index c8a4ec4af..51a0ecc73 100644 --- a/nml/expression/functioncall.py +++ b/nml/expression/functioncall.py @@ -21,13 +21,13 @@ from nml import generic, global_constants, nmlop from . import identifier +from .abs_op import AbsOp from .base_expression import ConstantFloat, ConstantNumeric, Expression, Type from .bitmask import BitMask from .cargo import AcceptCargo, ProduceCargo from .parameter import parse_string_to_dword from .storage_op import StorageOp from .string_literal import StringLiteral -from .abs_op import AbsOp class FunctionCall(Expression): @@ -64,12 +64,10 @@ def reduce(self, id_dicts=None, unknown_id_fatal=True): func_ptr.is_procedure = True return func_ptr if func_ptr.type() != Type.FUNCTION_PTR: - raise generic.ScriptError( - "'{}' is defined, but it is not a function.".format(self.name.value), self.pos - ) + raise generic.ScriptError(f"'{self.name.value}' is defined, but it is not a function.", self.pos) return func_ptr.call(params) if unknown_id_fatal: - raise generic.ScriptError("'{}' is not defined as a function.".format(self.name.value), self.pos) + raise generic.ScriptError(f"'{self.name.value}' is not defined as a function.", self.pos) return FunctionCall(self.name, params, self.pos) @@ -231,7 +229,7 @@ def builtin_date(name, args, pos): 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)): + if month >= 3 and (year.value % 4 == 0) and (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) @@ -259,7 +257,7 @@ def builtin_day_of_year(name, args, pos): # Mapping of month to number of days in that month. number_days = {1: 31, 2: 28, 3: 31, 4: 30, 5: 31, 6: 30, 7: 31, 8: 31, 9: 30, 10: 31, 11: 30, 12: 31} if day.value < 1 or day.value > number_days[month.value]: - raise generic.ScriptError("Day should be value between 1 and {:d}.".format(number_days[month.value]), day.pos) + raise generic.ScriptError(f"Day should be value between 1 and {number_days[month.value]}.", day.pos) return ConstantNumeric(datetime.date(1, month.value, day.value).toordinal(), pos) @@ -376,7 +374,7 @@ def builtin_typelabel_available(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have exactly 1 parameter", pos) label = args[0].reduce() - return SpecialCheck(op, 0, (0, 1), parse_string_to_dword(label), "{}({})".format(name, label), pos=args[0].pos) + return SpecialCheck(op, 0, (0, 1), parse_string_to_dword(label), f"{name}({label})", pos=args[0].pos) @builtins("grf_current_status", "grf_future_status", "grf_order_behind") @@ -396,12 +394,12 @@ def builtin_grf_status(name, args, pos): if len(args) == 1: grfid = args[0].reduce() mask = None - string = "{}({})".format(name, grfid) + string = f"{name}({grfid})" varsize = 4 elif len(args) == 2: grfid = args[0].reduce() mask = parse_string_to_dword(args[1].reduce()) - string = "{}({}, {})".format(name, grfid, mask) + string = f"{name}({grfid}, {mask})" varsize = 8 else: raise generic.ScriptError(name + "() must have 1 or 2 parameters", pos) @@ -421,7 +419,7 @@ def builtin_visual_effect_and_powered(name, args, pos): """ arg_len = 2 if name == "visual_effect" else 3 if len(args) != arg_len: - raise generic.ScriptError(name + "() must have {:d} parameters".format(arg_len), pos) + raise generic.ScriptError(name + f"() must have {arg_len} parameters", pos) effect = args[0].reduce_constant(global_constants.const_list).value offset = nmlop.ADD(args[1], 8).reduce_constant().value generic.check_range(offset, 0, 0x0F, "offset in function " + name, pos) @@ -494,7 +492,7 @@ def builtin_resolve_typelabel(name, args, pos, table_name=None): raise generic.ScriptError(name + "() must have 1 parameter", pos) if not isinstance(args[0], StringLiteral) or args[0].value not in table: raise generic.ScriptError( - "Parameter for {}() must be a string literal that is also in your {} table".format(name, table_name), pos + f"Parameter for {name}() must be a string literal that is also in your {table_name} table", pos ) return ConstantNumeric(table[args[0].value]) @@ -504,7 +502,7 @@ def builtin_reserve_sprites(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) count = args[0].reduce_constant() - return GRMOp(nmlop.GRM_RESERVE, 0x08, count.value, lambda x: "{}({:d})".format(name, count.value), pos) + return GRMOp(nmlop.GRM_RESERVE, 0x08, count.value, lambda x: f"{name}({count.value})", pos) @builtin @@ -631,9 +629,9 @@ def builtin_relative_coord(name, args, pos): 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) + generic.check_range(args[0].value, 0, 255, f"Argument of '{name}'", args[0].pos) if isinstance(args[1], ConstantNumeric): - generic.check_range(args[1].value, 0, 255, "Argument of '{}'".format(name), args[1].pos) + generic.check_range(args[1].value, 0, 255, f"Argument of '{name}'", args[1].pos) x_coord = nmlop.AND(args[0], 0xFF) y_coord = nmlop.AND(args[1], 0xFF) @@ -679,7 +677,7 @@ def builtin_slope_to_sprite_offset(name, args, pos): 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) + generic.check_range(args[0].value, 0, 15, f"Argument of '{name}'", args[0].pos) # step 1: ((slope >= 0) & (slope <= 14)) * slope # This handles all non-steep slopes @@ -705,7 +703,7 @@ def builtin_palette_1cc(name, args, pos): 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) + generic.check_range(args[0].value, 0, 15, f"Argument of '{name}'", args[0].pos) return nmlop.ADD(args[0], 775, pos) @@ -722,7 +720,7 @@ def builtin_palette_2cc(name, args, pos): for i in range(0, 2): if isinstance(args[i], ConstantNumeric): - generic.check_range(args[i].value, 0, 15, "Argument of '{}'".format(name), args[i].pos) + generic.check_range(args[i].value, 0, 15, f"Argument of '{name}'", args[i].pos) col2 = nmlop.MUL(args[1], 16, pos) col12 = nmlop.ADD(col2, args[0]) @@ -744,7 +742,7 @@ def builtin_vehicle_curv_info(name, args, pos): for arg in args: if isinstance(arg, ConstantNumeric): - generic.check_range(arg.value, -2, 2, "Argument of '{}'".format(name), arg.pos) + generic.check_range(arg.value, -2, 2, f"Argument of '{name}'", arg.pos) args = [nmlop.AND(arg, 0xF, pos) for arg in args] cur_next = nmlop.SHIFT_LEFT(args[1], 8) @@ -771,7 +769,7 @@ def builtin_format_string(name, args, pos): arg = arg.reduce() if not isinstance(arg, (StringLiteral, ConstantFloat, ConstantNumeric)): raise generic.ScriptError( - name + "() parameter {:d} is not a constant number of literal string".format(i + 1), arg.pos + name + f"() parameter {i + 1} is not a constant number of literal string", arg.pos ) format_args.append(arg.value) @@ -779,7 +777,7 @@ def builtin_format_string(name, args, pos): result = format.value % tuple(format_args) return StringLiteral(result, pos) except Exception as ex: - raise generic.ScriptError("Invalid combination of format / arguments for {}: {}".format(name, str(ex)), pos) + raise generic.ScriptError(f"Invalid combination of format / arguments for {name}: {ex}", pos) # } diff --git a/nml/expression/functionptr.py b/nml/expression/functionptr.py index 71596d1c4..e607aec25 100644 --- a/nml/expression/functionptr.py +++ b/nml/expression/functionptr.py @@ -51,7 +51,7 @@ def __str__(self): def reduce(self, id_dicts=None, unknown_id_fatal=True): raise generic.ScriptError( - "'{}' is a function and should be called using the function call syntax.".format(str(self.name)), + f"'{self.name}' is a function and should be called using the function call syntax.", self.name.pos, ) diff --git a/nml/expression/identifier.py b/nml/expression/identifier.py index 426bdb4b9..c020706dc 100644 --- a/nml/expression/identifier.py +++ b/nml/expression/identifier.py @@ -80,7 +80,7 @@ def reduce(self, id_dicts=None, unknown_id_fatal=True, search_func_ptr=False): def supported_by_actionD(self, raise_error): if raise_error: - raise generic.ScriptError("Unknown identifier '{}'".format(self.value), self.pos) + raise generic.ScriptError(f"Unknown identifier '{self.value}'", self.pos) return False diff --git a/nml/expression/parameter.py b/nml/expression/parameter.py index 265b46849..c8ef2933b 100644 --- a/nml/expression/parameter.py +++ b/nml/expression/parameter.py @@ -35,7 +35,7 @@ def debug_print(self, indentation): self.num.debug_print(indentation + 2) def __str__(self): - return "param[{}]".format(self.num) + return f"param[{self.num}]" def reduce(self, id_dicts=None, unknown_id_fatal=True): num = self.num.reduce(id_dicts) @@ -76,7 +76,7 @@ def debug_print(self, indentation): self.num.debug_print(indentation + 2) def __str__(self): - return "param[{}, {}]".format(self.grfid, self.num) + return f"param[{self.grfid}, {self.num}]" def reduce(self, id_dicts=None, unknown_id_fatal=True): grfid = self.grfid.reduce(id_dicts) diff --git a/nml/expression/patch_variable.py b/nml/expression/patch_variable.py index 33f9d50b2..b0eeeac19 100644 --- a/nml/expression/patch_variable.py +++ b/nml/expression/patch_variable.py @@ -34,7 +34,7 @@ def debug_print(self, indentation): generic.print_dbg(indentation, "PatchVariable:", self.num) def __str__(self): - return "PatchVariable({:d})".format(self.num) + return f"PatchVariable({self.num})" def reduce(self, id_dicts=None, unknown_id_fatal=True): return self diff --git a/nml/expression/special_parameter.py b/nml/expression/special_parameter.py index 038edb9f4..0b5ebe0bb 100644 --- a/nml/expression/special_parameter.py +++ b/nml/expression/special_parameter.py @@ -60,7 +60,7 @@ def __init__(self, name, info, write_func, read_func, is_bool, pos=None): self.is_bool = is_bool def debug_print(self, indentation): - generic.print_dbg(indentation, "Special parameter '{}'".format(self.name)) + generic.print_dbg(indentation, f"Special parameter '{self.name}'") def __str__(self): return self.name diff --git a/nml/expression/spritegroup_ref.py b/nml/expression/spritegroup_ref.py index 834726f92..7835551b8 100644 --- a/nml/expression/spritegroup_ref.py +++ b/nml/expression/spritegroup_ref.py @@ -75,7 +75,7 @@ def get_action2_id(self, feature): try: spritegroup = action2.resolve_spritegroup(self.name) except generic.ScriptError: - raise AssertionError("Illegal action2 reference '{}' encountered.".format(self.name.value)) + raise AssertionError(f"Illegal action2 reference '{self.name.value}' encountered.") return spritegroup.get_action2(feature).id diff --git a/nml/expression/storage_op.py b/nml/expression/storage_op.py index 00f957ced..7393db551 100644 --- a/nml/expression/storage_op.py +++ b/nml/expression/storage_op.py @@ -57,10 +57,8 @@ def __init__(self, name, args, pos=None): if self.info["grfid"]: arg_len += (arg_len[0] + 1,) if len(args) not in arg_len: - argstr = "{:d}".format(arg_len[0]) if len(arg_len) == 1 else "{}..{}".format(arg_len[0], arg_len[1]) - raise generic.ScriptError( - "{} requires {} argument(s), encountered {:d}".format(name, argstr, len(args)), pos - ) + argstr = f"{arg_len[0]}" if len(arg_len) == 1 else f"{arg_len[0]}..{arg_len[1]}" + raise generic.ScriptError(f"{name} requires {argstr} argument(s), encountered {len(args)}", pos) i = 0 if self.info["store"]: @@ -114,7 +112,7 @@ def reduce(self, id_dicts=None, unknown_id_fatal=True): raise generic.ProcCallSyntaxError(register.name, register.pos) raise generic.ScriptError("Register to access must be an integer.", register.pos) if isinstance(register, ConstantNumeric) and register.value > self.info["max"]: - raise generic.ScriptError("Maximum register for {} is {:d}".format(self.name, self.info["max"]), self.pos) + raise generic.ScriptError(f"Maximum register for {self.name} is {self.info['max']}", self.pos) if isinstance(register, ConstantNumeric) and register.value in self.info["reserved"]: raise generic.ScriptError( "Temporary registers from 128 to 255 are reserved for NML's internal calculations.", self.pos @@ -134,7 +132,7 @@ def supported_by_action2(self, raise_error): def supported_by_actionD(self, raise_error): if raise_error: - raise generic.ScriptError("{}() may only be used inside switch-blocks".format(self.name), self.pos) + raise generic.ScriptError(f"{self.name}() may only be used inside switch-blocks", self.pos) return False def collect_references(self): diff --git a/nml/expression/string_literal.py b/nml/expression/string_literal.py index 8d3dbeb62..ab69bbd8a 100644 --- a/nml/expression/string_literal.py +++ b/nml/expression/string_literal.py @@ -31,10 +31,10 @@ def __init__(self, value, pos): self.value = value def debug_print(self, indentation): - generic.print_dbg(indentation, 'String literal: "{}"'.format(self.value)) + generic.print_dbg(indentation, f'String literal: "{self.value}"') def __str__(self): - return '"{}"'.format(self.value) + return f'"{self.value}"' def write(self, file, size): assert grfstrings.get_string_size(self.value, False, True) == size diff --git a/nml/expression/ternaryop.py b/nml/expression/ternaryop.py index 2c704fa54..beea59418 100644 --- a/nml/expression/ternaryop.py +++ b/nml/expression/ternaryop.py @@ -92,4 +92,4 @@ def __hash__(self): return hash((self.guard, self.expr1, self.expr2)) def __str__(self): - return "({} ? {} : {})".format(str(self.guard), str(self.expr1), str(self.expr2)) + return f"({self.guard} ? {self.expr1} : {self.expr2})" diff --git a/nml/expression/variable.py b/nml/expression/variable.py index fb8c871d8..8b8979167 100644 --- a/nml/expression/variable.py +++ b/nml/expression/variable.py @@ -45,17 +45,17 @@ def debug_print(self, indentation): extra_param.debug_print(indentation + 4) def __str__(self): - num = "0x{:02X}".format(self.num.value) if isinstance(self.num, ConstantNumeric) else str(self.num) - ret = "var[{}, {}, {}".format(num, self.shift, self.mask) + num = f"0x{self.num.value:02X}" if isinstance(self.num, ConstantNumeric) else str(self.num) + ret = f"var[{num}, {self.shift}, {self.mask}" if self.param is not None: - ret += ", {}".format(self.param) + ret += f", {self.param}" ret += "]" if self.add is not None: - ret = "({} + {})".format(ret, self.add) + ret = f"({ret} + {self.add})" if self.div is not None: - ret = "({} / {})".format(ret, self.div) + ret = f"({ret} / {self.div})" if self.mod is not None: - ret = "({} % {})".format(ret, self.mod) + ret = f"({ret} % {self.mod})" return ret def reduce(self, id_dicts=None, unknown_id_fatal=True): diff --git a/nml/generic.py b/nml/generic.py index a39180bce..78640130d 100644 --- a/nml/generic.py +++ b/nml/generic.py @@ -171,7 +171,7 @@ def __init__(self, filename, line_start, includes=None): self.line_start = line_start def __str__(self): - return '"{}", line {:d}'.format(self.filename, self.line_start) + return f'"{self.filename}", line {self.line_start}' class PixelPosition(Position): @@ -191,7 +191,7 @@ def __init__(self, filename, xpos, ypos): self.ypos = ypos def __str__(self): - return '"{}" at [x: {:d}, y: {:d}]'.format(self.filename, self.xpos, self.ypos) + return f'"{self.filename}" at [x: {self.xpos}, y: {self.ypos}]' class ImageFilePosition(Position): @@ -206,7 +206,7 @@ def __init__(self, filename, pos=None): Position.__init__(self, filename, poslist) def __str__(self): - return 'Image file "{}"'.format(self.filename) + return f'Image file "{self.filename}"' class LanguageFilePosition(Position): @@ -218,7 +218,7 @@ def __init__(self, filename): Position.__init__(self, filename, []) def __str__(self): - return 'Language file "{}"'.format(self.filename) + return f'Language file "{self.filename}"' class ScriptError(Exception): @@ -254,7 +254,7 @@ def __init__(self, value, min_value, max_value, name, pos=None): class ProcCallSyntaxError(ScriptError): def __init__(self, name, pos=None): - ScriptError.__init__(self, "Missing '()' after '{}'.".format(name), pos) + ScriptError.__init__(self, f"Missing '()' after '{name}'.", pos) class ImageError(ScriptError): @@ -275,7 +275,7 @@ def __init__(self, typestr, pos=None): @param pos: Position of the error, if provided. @type pos: C{None} or L{Position} """ - ScriptError.__init__(self, "A grf may contain only one {}.".format(typestr), pos) + ScriptError.__init__(self, f"A grf may contain only one {typestr}.", pos) class OnlyOnce: @@ -369,7 +369,7 @@ def clear_progress(): hide_progress() if (progress_message is not None) and (verbosity_level >= VERBOSITY_TIMING): - print("{} {:.1f} s".format(progress_message, time.process_time() - progress_start_time)) + print(f"{progress_message} {time.process_time() - progress_start_time:.1f} s") progress_message = None progress_start_time = None @@ -526,29 +526,25 @@ def find_file(filepath): matches = [entry for entry in entries if lcomp == entry.lower()] if len(matches) == 0: - raise ScriptError( - 'Path "{}" does not exist (even after case conversions)'.format(os.path.join(path, comp)) - ) + raise ScriptError(f'Path "{os.path.join(path, comp)}" does not exist (even after case conversions)') elif len(matches) > 1: raise ScriptError( - 'Path "{}" is not unique (case conversion gave {:d} solutions)'.format( - os.path.join(path, comp), len(matches) - ) + f'Path "{os.path.join(path, comp)}" is not unique (case conversion gave {len(matches)} solutions)' ) if matches[0] != comp: given_path = os.path.join(path, comp) real_path = os.path.join(path, matches[0]) msg = ( - 'Path "{}" at the file system does not match path "{}" given in the input' + f'Path "{real_path}" at the file system does not match path "{given_path}" given in the input' " (case mismatch in the last component)" - ).format(real_path, given_path) + ) print_warning(Warning.GENERIC, msg) elif os.access(path, os.X_OK): # Path is only accessible, cannot inspect the file system. matches = [comp] else: - raise ScriptError('Path "{}" does not exist or is not accessible'.format(path)) + raise ScriptError(f'Path "{path}" does not exist or is not accessible') path = os.path.join(path, matches[0]) if len(components) > 0: @@ -613,6 +609,6 @@ def open_cache_file(sources, extension, mode): if "w" in mode: print_warning( Warning.GENERIC, - "Can't create cache file {}. Check permissions, or use --cache-dir or --no-cache.".format(path), + f"Can't create cache file {path}. Check permissions, or use --cache-dir or --no-cache.", ) raise diff --git a/nml/global_constants.py b/nml/global_constants.py index 7bf9f38c4..62e7d2b5e 100644 --- a/nml/global_constants.py +++ b/nml/global_constants.py @@ -19,7 +19,7 @@ def constant_number(name, info, pos): if isinstance(info, str): generic.print_warning( - generic.Warning.DEPRECATION, "'{}' is deprecated, consider using '{}' instead".format(name, info), pos + generic.Warning.DEPRECATION, f"'{name}' is deprecated, consider using '{info}' instead", pos ) info = constant_numbers[info] return expression.ConstantNumeric(info, pos) @@ -1222,7 +1222,7 @@ def signextend(param, info): def global_param_write(info, expr, pos): - if not ("writable" in info and info["writable"]): + if not (info.get("writable")): raise generic.ScriptError("Target parameter is not writable.", pos) return expression.Parameter(expression.ConstantNumeric(info["num"]), pos), expr @@ -1369,7 +1369,7 @@ def patch_variable(name, info, pos): def config_flag_read(bit, pos): - return expression.SpecialCheck((0x01, r"\70"), 0x85, (0, 1), bit, "PatchFlag({})".format(bit), varsize=1, pos=pos) + return expression.SpecialCheck((0x01, r"\70"), 0x85, (0, 1), bit, f"PatchFlag({bit})", varsize=1, pos=pos) def config_flag(name, info, pos): @@ -1422,7 +1422,7 @@ def setting_from_info(name, info, pos): def item_to_id(name, item, pos): if not isinstance(item.id, expression.ConstantNumeric): - raise generic.ScriptError("Referencing item '{}' with a non-constant id is not possible.".format(name), pos) + raise generic.ScriptError(f"Referencing item '{name}' with a non-constant id is not possible.", pos) return expression.ConstantNumeric(item.id.value, pos) @@ -1503,10 +1503,10 @@ def print_stats(): """ if len(cargo_numbers) > 0: # Ids FE and FF have special meanings in Action3, so we do not consider them valid ids. - generic.print_info("Cargo translation table: {}/{}".format(len(cargo_numbers), 0xFE)) + generic.print_info(f"Cargo translation table: {len(cargo_numbers)}/{0xFE}") if not is_default_railtype_table: - generic.print_info("Railtype translation table: {}/{}".format(len(railtype_table), 0x100)) + generic.print_info(f"Railtype translation table: {len(railtype_table)}/{0x100}") if not is_default_roadtype_table: - generic.print_info("Roadtype translation table: {}/{}".format(len(roadtype_table), 0x100)) + generic.print_info(f"Roadtype translation table: {len(roadtype_table)}/{0x100}") if not is_default_tramtype_table: - generic.print_info("Tramtype translation table: {}/{}".format(len(tramtype_table), 0x100)) + generic.print_info(f"Tramtype translation table: {len(tramtype_table)}/{0x100}") diff --git a/nml/grfstrings.py b/nml/grfstrings.py index 3a67a013a..1d796602e 100644 --- a/nml/grfstrings.py +++ b/nml/grfstrings.py @@ -43,7 +43,7 @@ def validate_string(string): @type string: L{expression.String} """ if string.name.value not in default_lang.strings: - raise generic.ScriptError('Unknown string "{}"'.format(string.name.value), string.pos) + raise generic.ScriptError(f'Unknown string "{string.name.value}"', string.pos) def is_ascii_string(string): @@ -179,7 +179,7 @@ def com_parse_comma(val, lang_id): def com_parse_hex(val, lang_id): val = val.reduce_constant() - return "0x{:X}".format(val.value) + return f"0x{val.value:X}" def com_parse_string(val, lang_id): @@ -190,7 +190,7 @@ def com_parse_string(val, lang_id): if isinstance(val, nml.expression.String): # Check that the string exists if val.name.value not in default_lang.strings: - raise generic.ScriptError('Substring "{}" does not exist'.format(val.name.value), val.pos) + raise generic.ScriptError(f'Substring "{val.name.value}" does not exist', val.pos) return get_translation(val, lang_id) return val.value @@ -298,7 +298,7 @@ def read_extra_commands(custom_tags_file): # Failed to open custom_tags.txt, ignore this return line_no = 0 - with open(generic.find_file(custom_tags_file), "r", encoding="utf-8") as fh: + with open(generic.find_file(custom_tags_file), encoding="utf-8") as fh: for line in fh: line_no += 1 line = line.strip() @@ -389,9 +389,8 @@ def validate_arguments(self, lang): raise generic.ScriptError("Using {P} without a ##plural pragma", self.pos) if len(self.arguments) != lang.get_num_plurals(): raise generic.ScriptError( - "Invalid number of arguments to plural command, expected {:d} but got {:d}".format( - lang.get_num_plurals(), len(self.arguments) - ), + "Invalid number of arguments to plural command," + f" expected {lang.get_num_plurals()} but got {len(self.arguments)}", self.pos, ) elif self.name == "G": @@ -399,9 +398,8 @@ def validate_arguments(self, lang): raise generic.ScriptError("Using {G} without a ##gender pragma", self.pos) if len(self.arguments) != len(lang.genders): raise generic.ScriptError( - "Invalid number of arguments to gender command, expected {:d} but got {:d}".format( - len(lang.genders), len(self.arguments) - ), + "Invalid number of arguments to gender command," + f" expected {len(lang.genders)} but got {len(self.arguments)}", self.pos, ) elif self.name == "G=": @@ -409,13 +407,11 @@ def validate_arguments(self, lang): raise generic.ScriptError("Using {G=} without a ##gender pragma", self.pos) if len(self.arguments) != 1: raise generic.ScriptError( - "Invalid number of arguments to set-gender command, expected {:d} but got {:d}".format( - 1, len(self.arguments) - ), + f"Invalid number of arguments to set-gender command, expected 1 but got {len(self.arguments)}", self.pos, ) elif len(self.arguments) != 0: - raise generic.ScriptError('Unexpected arguments to command "{}"'.format(self.name), self.pos) + raise generic.ScriptError(f'Unexpected arguments to command "{self.name}"', self.pos) def parse_string(self, str_type, lang, wanted_lang_id, prev_command, stack, static_args): """ @@ -452,7 +448,7 @@ def parse_string(self, str_type, lang, wanted_lang_id, prev_command, stack, stat if self.str_pos < len(static_args): if "parse" not in commands[self.name]: raise generic.ScriptError( - "Provided a static argument for string command '{}' which is invalid".format(self.name), + f"Provided a static argument for string command '{self.name}' which is invalid", self.pos, ) # Parse commands using the wanted (not current) lang id, so translations are used if present @@ -460,7 +456,7 @@ def parse_string(self, str_type, lang, wanted_lang_id, prev_command, stack, stat prefix = "" suffix = "" if self.case: - prefix += STRING_SELECT_CASE[str_type] + "\\{:02X}".format(self.case) + prefix += STRING_SELECT_CASE[str_type] + f"\\{self.case:02X}" if stack_pos + self_size > 8: raise generic.ScriptError( "Trying to read an argument from the stack without reading the arguments before", self.pos @@ -503,29 +499,29 @@ def parse_string(self, str_type, lang, wanted_lang_id, prev_command, stack, stat if self.name == "P": if offset < 0: return self.arguments[lang.static_plural_form(static_args[offset]) - 1] - ret = BEGIN_PLURAL_CHOICE_LIST[str_type] + "\\{:02X}".format(0x80 + offset) + ret = BEGIN_PLURAL_CHOICE_LIST[str_type] + f"\\{0x80 + offset:02X}" for idx, arg in enumerate(self.arguments): if idx == len(self.arguments) - 1: ret += CHOICE_LIST_DEFAULT[str_type] else: - ret += CHOICE_LIST_ITEM[str_type] + "\\{:02X}".format(idx + 1) + ret += CHOICE_LIST_ITEM[str_type] + f"\\{idx + 1:02X}" ret += arg ret += CHOICE_LIST_END[str_type] return ret if self.name == "G": if offset < 0: return self.arguments[lang.static_gender(static_args[offset]) - 1] - ret = BEGIN_GENDER_CHOICE_LIST[str_type] + "\\{:02X}".format(0x80 + offset) + ret = BEGIN_GENDER_CHOICE_LIST[str_type] + f"\\{0x80 + offset:02X}" for idx, arg in enumerate(self.arguments): if idx == len(self.arguments) - 1: ret += CHOICE_LIST_DEFAULT[str_type] else: - ret += CHOICE_LIST_ITEM[str_type] + "\\{:02X}".format(idx + 1) + ret += CHOICE_LIST_ITEM[str_type] + f"\\{idx + 1:02X}" ret += arg ret += CHOICE_LIST_END[str_type] return ret # Not reached - raise ValueError("Unexpected string command '{}'".format(self.name)) + raise ValueError(f"Unexpected string command '{self.name}'") def get_type(self): if self.name in commands: @@ -653,34 +649,34 @@ def __init__(self, string, lang, pos): if end < len(string) and string[end] == "=": command_name += "=" if command_name not in commands and command_name not in special_commands: - raise generic.ScriptError('Undefined command "{}"'.format(command_name), pos) + raise generic.ScriptError(f'Undefined command "{command_name}"', pos) if command_name in commands and "deprecated" in commands[command_name]: generic.print_warning( generic.Warning.DEPRECATION, - "String code '{}' has been deprecated and will be removed soon".format(command_name), + f"String code '{command_name}' has been deprecated and will be removed soon", pos, ) del commands[command_name]["deprecated"] # command = StringCommand(command_name, cmd_pos, pos) if end >= len(string): - raise generic.ScriptError("Missing '}}' from command \"{}\"".format(string[start:]), pos) + raise generic.ScriptError(f"Missing '}}' from command \"{string[start:]}\"", pos) if string[end] == ".": if command_name not in commands or "allow_case" not in commands[command_name]: - raise generic.ScriptError('Command "{}" can\'t have a case'.format(command_name), pos) + raise generic.ScriptError(f'Command "{command_name}" can\'t have a case', pos) case_start = end + 1 end = case_start while end < len(string) and string[end] not in "} ": end += 1 case = string[case_start:end] if lang.cases is None or case not in lang.cases: - raise generic.ScriptError('Invalid case-name "{}"'.format(case), pos) + raise generic.ScriptError(f'Invalid case-name "{case}"', pos) command.case = lang.cases[case] if string[end] != "}": arg_start = end + 1 end = string.find("}", end + 1) if end == -1 or not command.set_arguments(string[arg_start:end]): - raise generic.ScriptError("Missing '}}' from command \"{}\"".format(string[start:]), pos) + raise generic.ScriptError(f"Missing '}}' from command \"{string[start:]}\"", pos) command.validate_arguments(lang) if command_name == "G=" and self.components: raise generic.ScriptError("Set-gender command {G=} must be at the start of the string", pos) @@ -694,7 +690,7 @@ def __init__(self, string, lang, pos): ): self.gender = self.components[0].arguments[0] if self.gender not in lang.genders: - raise generic.ScriptError("Invalid gender name '{}'".format(self.gender), pos) + raise generic.ScriptError(f"Invalid gender name '{self.gender}'", pos) self.components.pop(0) else: self.gender = None @@ -729,9 +725,8 @@ def remove_non_default_commands(self): i = 0 while i < len(self.components): comp = self.components[i] - if isinstance(comp, StringCommand): - if comp.name == "P" or comp.name == "G": - self.components[i] = comp.arguments[-1] if comp.arguments else "" + if isinstance(comp, StringCommand) and comp.name in ("P", "G"): + self.components[i] = comp.arguments[-1] if comp.arguments else "" i += 1 def parse_string(self, str_type, lang, wanted_lang_id, static_args): @@ -766,7 +761,7 @@ def get_command_sizes(self): sizes_list = [] for idx in range(len(sizes)): if idx not in sizes: - raise generic.ScriptError("String argument {:d} is not used".format(idx), self.pos) + raise generic.ScriptError(f"String argument {idx} is not used", self.pos) sizes_list.append(sizes[idx]) return sizes_list @@ -992,16 +987,14 @@ def get_string(self, string, lang_id): str_type = self.strings[string_id].get_type() parsed_string = "" if self.strings[string_id].gender is not None: - parsed_string += SET_STRING_GENDER[str_type] + "\\{:02X}".format( - self.genders[self.strings[string_id].gender] - ) + parsed_string += SET_STRING_GENDER[str_type] + f"\\{self.genders[self.strings[string_id].gender]:02X}" if len(self.strings[string_id].cases) > 0: parsed_string += BEGIN_CASE_CHOICE_LIST[str_type] for case_name, case_string in sorted(self.strings[string_id].cases.items()): case_id = self.cases[case_name] parsed_string += ( CHOICE_LIST_ITEM[str_type] - + "\\{:02X}".format(case_id) + + f"\\{case_id:02X}" + case_string.parse_string(str_type, self, lang_id, string.params) ) parsed_string += CHOICE_LIST_DEFAULT[str_type] @@ -1023,7 +1016,7 @@ def handle_grflangid(self, data, pos): try: value = int(lang_text, 16) except ValueError: - raise generic.ScriptError("Invalid grflangid {!r}".format(lang_text), pos) + raise generic.ScriptError(f"Invalid grflangid {lang_text!r}", pos) if value < 0 or value >= 0x7F: raise generic.ScriptError("Invalid grflangid", pos) self.langid = value @@ -1073,7 +1066,7 @@ def handle_map_gender(self, data, pos): if len(genders) != 2: raise generic.ScriptError("Invalid ##map_gender line", pos) if genders[0] not in self.genders: - raise generic.ScriptError("Trying to map non-existing gender '{}'".format(genders[0]), pos) + raise generic.ScriptError(f"Trying to map non-existing gender '{genders[0]}'", pos) self.gender_map[genders[0]].append(genders[1]) def handle_case(self, data, pos): @@ -1103,7 +1096,7 @@ def handle_map_case(self, data, pos): if len(cases) != 2: raise generic.ScriptError("Invalid ##map_case line", pos) if cases[0] not in self.cases: - raise generic.ScriptError("Trying to map non-existing case '{}'".format(cases[0]), pos) + raise generic.ScriptError(f"Trying to map non-existing case '{cases[0]}'", pos) self.case_map[cases[0]].append(cases[1]) def handle_text(self, string, case, value, pos): @@ -1119,11 +1112,11 @@ def handle_text(self, string, case, value, pos): @param value: Value of the string. @type value: C{str} """ - if not re.match("[A-Za-z_0-9]+$", string): - raise generic.ScriptError('Invalid string name "{}"'.format(string), pos) + if not re.match(r"[A-Za-z_0-9]+$", string): + raise generic.ScriptError(f'Invalid string name "{string}"', pos) if string in self.strings and case is None: - raise generic.ScriptError('String name "{}" is used multiple times'.format(string), pos) + raise generic.ScriptError(f'String name "{string}" is used multiple times', pos) if self.default: self.strings[string] = NewGRFString(value, self, pos) @@ -1131,7 +1124,7 @@ def handle_text(self, string, case, value, pos): else: if string not in default_lang.strings: generic.print_warning( - generic.Warning.GENERIC, 'String name "{}" does not exist in master file'.format(string), pos + generic.Warning.GENERIC, f'String name "{string}" does not exist in master file', pos ) return @@ -1139,7 +1132,7 @@ def handle_text(self, string, case, value, pos): if not default_lang.strings[string].match_commands(newgrf_string): generic.print_warning( generic.Warning.GENERIC, - 'String commands don\'t match with master file "{}"'.format(DEFAULT_LANGNAME), + f'String commands don\'t match with master file "{DEFAULT_LANGNAME}"', pos, ) return @@ -1151,10 +1144,10 @@ def handle_text(self, string, case, value, pos): generic.print_warning(generic.Warning.GENERIC, "String with case used before the base string", pos) return if self.cases is None or case not in self.cases: - generic.print_warning(generic.Warning.GENERIC, 'Invalid case name "{}"'.format(case), pos) + generic.print_warning(generic.Warning.GENERIC, f'Invalid case name "{case}"', pos) return if case in self.strings[string].cases: - raise generic.ScriptError('String name "{}.{}" is used multiple times'.format(string, case), pos) + raise generic.ScriptError(f'String name "{string}.{case}" is used multiple times', pos) if newgrf_string.gender: generic.print_warning( generic.Warning.GENERIC, "Case-strings can't set the gender, only the base string can", pos @@ -1167,7 +1160,7 @@ def handle_string(self, line, pos): # Silently ignore empty lines. return - elif len(line) > 2 and line[:2] == "##" and not line[:3] == "###": + elif len(line) > 2 and line[:2] == "##" and line[:3] != "###": # "##pragma" line, call relevant handler. if self.default: # Default language ignores all pragmas. @@ -1230,7 +1223,7 @@ def parse_file(filename, default): """ lang = Language(False) try: - with open(generic.find_file(filename), "r", encoding="utf-8") as fh: + with open(generic.find_file(filename), encoding="utf-8") as fh: for idx, line in enumerate(fh): pos = generic.LinePosition(filename, idx + 1) line = line.rstrip("\n\r").lstrip("\ufeff") @@ -1260,9 +1253,7 @@ def parse_file(filename, default): else: for lng in langs: if lng[0] == lang.langid: - msg = "Language file has the same ##grflangid (with number {:d}) as another language file".format( - lang.langid - ) + msg = f"Language file has the same ##grflangid (with number {lang.langid}) as another language file" raise generic.ScriptError(msg, generic.LanguageFilePosition(filename)) langs.append((lang.langid, lang)) @@ -1286,7 +1277,7 @@ def read_lang_files(lang_dir, default_lang_file): if not os.path.exists(lang_dir + os.sep + default_lang_file): generic.print_warning( generic.Warning.GENERIC, - 'Default language file "{}" doesn\'t exist'.format(os.path.join(lang_dir, default_lang_file)), + f'Default language file "{os.path.join(lang_dir, default_lang_file)}" doesn\'t exist', ) return parse_file(lang_dir + os.sep + default_lang_file, True) @@ -1301,4 +1292,4 @@ def list_unused_strings(): for string in default_lang.strings: if string in Language.used_strings: continue - generic.print_warning(generic.Warning.GENERIC, 'String "{}" is unused'.format(string)) + generic.print_warning(generic.Warning.GENERIC, f'String "{string}" is unused') diff --git a/nml/main.py b/nml/main.py index afd2435b2..800e8b3b9 100644 --- a/nml/main.py +++ b/nml/main.py @@ -68,7 +68,7 @@ def parse_cli(argv): @return: Options, and input filename (if not supplied, use C{sys.stdin}). @rtype: C{tuple} (C{Object}, C{str} or C{None}) """ - usage = "Usage: %prog [options] \n" "Where is the nml file to parse" + usage = "Usage: %prog [options] \nWhere is the nml file to parse" opt_parser = optparse.OptionParser(usage=usage, version=version_info.get_cli_version()) opt_parser.set_defaults( @@ -194,9 +194,7 @@ def parse_cli(argv): type="int", dest="verbosity", metavar="", - help="Set the verbosity level for informational output. [default: %default, max: {}]".format( - generic.VERBOSITY_MAX - ), + help=f"Set the verbosity level for informational output. [default: %default, max: {generic.VERBOSITY_MAX}]", ) opt_parser.add_option( "-D", "--debug-parser", action="store_true", dest="debug_parser", help="Enable debug mode for parser." @@ -252,7 +250,7 @@ def parse_cli(argv): else: input_filename = args[0] if not os.access(input_filename, os.R_OK): - raise generic.ScriptError('Input file "{}" does not exist'.format(input_filename)) + raise generic.ScriptError(f'Input file "{input_filename}" does not exist') return opts, input_filename @@ -307,7 +305,7 @@ def main(argv): if input_filename is None: input = sys.stdin else: - input = codecs.open(generic.find_file(input_filename), "r", "utf-8") + input = codecs.open(generic.find_file(input_filename), "r", "utf-8") # noqa: SIM115 # explicit handle usage # Only append an output grf name, if no output is given, also not implicitly via -M if not opts.outputfile_given and not outputs: opts.grf_filename = filename_output_from_input(input_filename, ".grf") @@ -337,7 +335,7 @@ def main(argv): elif outext == ".dep": outputs.append(output_dep.OutputDEP(output, opts.grf_filename)) else: - generic.print_error("Unknown output format {}".format(outext)) + generic.print_error(f"Unknown output format {outext}") sys.exit(2) ret = nml( @@ -413,7 +411,7 @@ def nml( try: script = inputfile.read() except UnicodeDecodeError as ex: - raise generic.ScriptError("Input file is not utf-8 encoded: {}".format(ex)) + raise generic.ScriptError(f"Input file is not utf-8 encoded: {ex}") # Strip a possible BOM script = script.lstrip(str(codecs.BOM_UTF8, "utf-8")) @@ -539,7 +537,7 @@ def nml( if im.mode != "P": continue pal = palette.validate_palette(im, f) - except IOError as ex: + except OSError as ex: raise generic.ImageError(str(ex), f) if ( @@ -547,9 +545,7 @@ def nml( and pal != forced_palette and not (forced_palette == "DEFAULT" and pal == "LEGACY") ): - raise generic.ImageError( - "Image has '{}' palette, but you forced the '{}' palette".format(pal, used_palette), f - ) + raise generic.ImageError(f"Image has '{pal}' palette, but you forced the '{used_palette}' palette", f) if used_palette == "ANY": used_palette = pal @@ -558,7 +554,7 @@ def nml( used_palette = "DEFAULT" else: raise generic.ImageError( - "Image has '{}' palette, but \"{}\" has the '{}' palette".format(pal, last_file, used_palette), + f"Image has '{pal}' palette, but \"{last_file}\" has the '{used_palette}' palette", f, ) last_file = f @@ -661,7 +657,7 @@ def run(): # User mode: print user friendly error message. ex_msg = str(ex) if len(ex_msg) > 0: - ex_msg = '"{}"'.format(ex_msg) + ex_msg = f'"{ex_msg}"' traceback = sys.exc_info()[2] # Walk through the traceback object until we get to the point where the exception happened. @@ -680,7 +676,7 @@ def run(): "version": version, "msg": ex_msg, "cli": sys.argv, - "loc": 'File "{}", line {:d}, in {}'.format(filename, lineno, name), + "loc": f'File "{filename}", line {lineno}, in {name}', } msg = ( diff --git a/nml/nmlop.py b/nml/nmlop.py index 7d9a9f4f6..39ebf4c7e 100644 --- a/nml/nmlop.py +++ b/nml/nmlop.py @@ -98,9 +98,9 @@ def to_string(self, expr1, expr2): @rtype: C{str} """ if self.prefix_text is not None: - return "{}({}, {})".format(self.prefix_text, expr1, expr2) + return f"{self.prefix_text}({expr1}, {expr2})" else: # Infix notation. - return "({} {} {})".format(expr1, self.token, expr2) + return f"({expr1} {self.token} {expr2})" def __call__(self, expr1, expr2, pos=None): return binop.BinOp(self, expr1, expr2, pos) diff --git a/nml/output_base.py b/nml/output_base.py index 8f1cf2b63..bc7637755 100644 --- a/nml/output_base.py +++ b/nml/output_base.py @@ -1,3 +1,7 @@ +""" +Abstract base classes that implements common functionality for output classes +""" + __license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -13,9 +17,6 @@ with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" -""" -Abstract base classes that implements common functionality for output classes -""" import array import io @@ -47,7 +48,7 @@ def open(self): """ Open the output file. Data gets stored in-memory. """ - raise NotImplementedError("Implement me in {}".format(type(self))) + raise NotImplementedError(f"Implement me in {type(self)}") def open_file(self): """ @@ -56,7 +57,7 @@ def open_file(self): @return: File handle of the opened file. @rtype: C{file} """ - raise NotImplementedError("Implement me in {}".format(type(self))) + raise NotImplementedError(f"Implement me in {type(self)}") def assemble_file(self, real_file): """ @@ -214,7 +215,7 @@ def print_bytex(self, byte, pretty_print=None): @param byte: Value to output. @type byte: C{int} """ - raise NotImplementedError("Implement print_bytex() in {}".format(type(self))) + raise NotImplementedError(f"Implement print_bytex() in {type(self)}") def print_wordx(self, byte): """ @@ -223,7 +224,7 @@ def print_wordx(self, byte): @param byte: Value to output. @type byte: C{int} """ - raise NotImplementedError("Implement print_wordx() in {}".format(type(self))) + raise NotImplementedError(f"Implement print_wordx() in {type(self)}") def print_dwordx(self, byte): """ @@ -232,7 +233,7 @@ def print_dwordx(self, byte): @param byte: Value to output. @type byte: C{int} """ - raise NotImplementedError("Implement print_dwordx() in {}".format(type(self))) + raise NotImplementedError(f"Implement print_dwordx() in {type(self)}") def newline(self, msg="", prefix="\t"): """ @@ -245,7 +246,7 @@ def newline(self, msg="", prefix="\t"): @param prefix: Additional white space in front of the comment. @type prefix: C{str} """ - raise NotImplementedError("Implement newline() in {}".format(type(self))) + raise NotImplementedError(f"Implement newline() in {type(self)}") def comment(self, msg): """ @@ -256,7 +257,7 @@ def comment(self, msg): @note: Only use if no bytes have been written to the current line. """ - raise NotImplementedError("Implement comment() in {}".format(type(self))) + raise NotImplementedError(f"Implement comment() in {type(self)}") def start_sprite(self, expected_size, is_real_sprite=False): """ @@ -283,8 +284,8 @@ def end_sprite(self): assert self.in_sprite self.in_sprite = False self.newline() - assert self.expected_count == self.byte_count, "Expected {:d} bytes to be written to sprite, got {:d}".format( - self.expected_count, self.byte_count + assert self.expected_count == self.byte_count, ( + f"Expected {self.expected_count} bytes to be written to sprite, got {self.byte_count}" ) diff --git a/nml/output_grf.py b/nml/output_grf.py index 2312f3255..2595a9fcd 100644 --- a/nml/output_grf.py +++ b/nml/output_grf.py @@ -13,6 +13,7 @@ with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" +import contextlib import hashlib import os @@ -35,11 +36,8 @@ def open_file(self): # Remove / unlink the file, most useful for linux systems # See also issue #4165 # If the file happens to be in use or non-existent, ignore - try: + with contextlib.suppress(OSError): os.unlink(self.filename) - except OSError: - # Ignore - pass return open(self.filename, "wb") def get_md5(self): diff --git a/nml/output_nfo.py b/nml/output_nfo.py index bcb8ba786..ae136d5c7 100644 --- a/nml/output_nfo.py +++ b/nml/output_nfo.py @@ -43,7 +43,7 @@ def open(self): self.file = io.StringIO() def open_file(self): - handle = open(self.filename, "w", encoding="utf-8") + handle = open(self.filename, "w", encoding="utf-8") # noqa: SIM115 # explicit handle usage handle.write( "// Automatically generated by GRFCODEC. Do not modify!\n" "// (Info version 32)\n" @@ -68,23 +68,23 @@ def print_bytex(self, value, pretty_print=None): if pretty_print is not None: self.file.write(pretty_print + " ") return - self.file.write("{:02X} ".format(value)) + self.file.write(f"{value:02X} ") def print_word(self, value): value = self.prepare_word(value) - self.file.write("\\w{:d} ".format(value)) + self.file.write(f"\\w{value:d} ") def print_wordx(self, value): value = self.prepare_word(value) - self.file.write("\\wx{:04X} ".format(value)) + self.file.write(f"\\wx{value:04X} ") def print_dword(self, value): value = self.prepare_dword(value) - self.file.write("\\d{:d} ".format(value)) + self.file.write(f"\\d{value:d} ") def print_dwordx(self, value): value = self.prepare_dword(value) - self.file.write("\\dx{:08X} ".format(value)) + self.file.write(f"\\dx{value:08X} ") def print_string(self, value, final_zero=True, force_ascii=False): assert self.in_sprite diff --git a/nml/parser.py b/nml/parser.py index efabf0ec6..d654c997e 100644 --- a/nml/parser.py +++ b/nml/parser.py @@ -13,8 +13,6 @@ with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" -from nml.ply import yacc - from nml import expression, generic, nmlop, tokens, unit from nml.actions import actionD, real_sprite from nml.ast import ( @@ -45,6 +43,7 @@ townnames, tracktypetable, ) +from nml.ply import yacc class NMLParser: @@ -93,7 +92,7 @@ def p_error(self, t): if t is None: raise generic.ScriptError("Syntax error, unexpected end-of-file") else: - raise generic.ScriptError('Syntax error, unexpected token "{}"'.format(t.value), t.lineno) + raise generic.ScriptError(f'Syntax error, unexpected token "{t.value}"', t.lineno) # # Main script blocks diff --git a/nml/spritecache.py b/nml/spritecache.py index b65d3ba87..a16e43a8c 100644 --- a/nml/spritecache.py +++ b/nml/spritecache.py @@ -156,7 +156,7 @@ def read_cache(self): except json.JSONDecodeError: generic.print_warning( generic.Warning.GENERIC, - "{} contains invalid data, ignoring.".format(index_file_name) + f"{index_file_name} contains invalid data, ignoring." + " Please remove the file and file a bug report if this warning keeps appearing", ) self.cached_sprites = {} @@ -252,7 +252,7 @@ def read_cache(self): except Exception: generic.print_warning( generic.Warning.GENERIC, - "{} contains invalid data, ignoring.".format(index_file_name) + f"{index_file_name} contains invalid data, ignoring." + " Please remove the file and file a bug report if this warning keeps appearing", ) self.cached_sprites = {} # Clear cache @@ -312,9 +312,10 @@ def write_cache(self): index_output = json.JSONEncoder(sort_keys=True).encode(index_data) try: - with generic.open_cache_file(self.sources, ".cache", "wb") as cache_file, generic.open_cache_file( - self.sources, ".cacheindex", "w" - ) as index_file: + with ( + generic.open_cache_file(self.sources, ".cache", "wb") as cache_file, + generic.open_cache_file(self.sources, ".cacheindex", "w") as index_file, + ): index_file.write(index_output) sprite_data.tofile(cache_file) except OSError: diff --git a/nml/spriteencoder.py b/nml/spriteencoder.py index 71a488141..28558c41d 100644 --- a/nml/spriteencoder.py +++ b/nml/spriteencoder.py @@ -18,12 +18,6 @@ from nml import generic, lz77, palette, spritecache from nml.actions import real_sprite -try: - from PIL import Image -except ImportError: - # Image is required only when using graphics - pass - # Some constants for the 'info' byte INFO_RGB = 1 INFO_ALPHA = 2 @@ -116,9 +110,7 @@ def open(self, sprite_files): for sprite_info in sprite_list: count_sprites += 1 - generic.print_progress( - "Encoding {}/{}: {}".format(count_sprites, num_sprites, source_name), incremental=True - ) + generic.print_progress(f"Encoding {count_sprites}/{num_sprites}: {source_name}", incremental=True) cache_key = sprite_info.get_cache_key(self.crop_sprites) cache_item = local_cache.get_item(cache_key, self.palette) @@ -208,9 +200,8 @@ def get(self, sprite_info): alpha = pixel_stats.get("alpha", 0) if alpha > 0 and (sprite_info.flags.value & real_sprite.FLAG_NOALPHA) != 0: warnings.append( - "{}: {:d} of {:d} pixels ({:d}%) are semi-transparent, but NOALPHA is in flags".format( - str(image_32_pos), alpha, total, alpha * 100 // total - ) + f"{image_32_pos}: {alpha} of {total} pixels ({alpha * 100 // total}%)" + " are semi-transparent, but NOALPHA is in flags" ) if cache_key[2] is not None: @@ -219,15 +210,13 @@ def get(self, sprite_info): anim = pixel_stats.get("anim", 0) if white > 0 and (sprite_info.flags.value & real_sprite.FLAG_WHITE) == 0: warnings.append( - "{}: {:d} of {:d} pixels ({:d}%) are pure white, but WHITE isn't in flags".format( - str(image_8_pos), white, total, white * 100 // total - ) + f"{image_8_pos}: {white} of {total} pixels ({white * 100 // total}%)" + " are pure white, but WHITE isn't in flags" ) if anim > 0 and (sprite_info.flags.value & real_sprite.FLAG_ANIM) == 0: warnings.append( - "{}: {:d} of {:d} pixels ({:d}%) are animated, but ANIM isn't in flags".format( - str(image_8_pos), anim, total, anim * 100 // total - ) + f"{image_8_pos}: {anim} of {total} pixels ({anim * 100 // total}%)" + " are animated, but ANIM isn't in flags" ) return (size_x, size_y, xoffset, yoffset, compressed_data, info_byte, crop_rect, warnings) @@ -242,6 +231,9 @@ def open_image_file(self, filename): @return: Image file @rtype: L{Image} """ + # Image is required only when using graphics, existence already checked by entrypoint + from PIL import Image + if filename in self.cached_image_files: im = self.cached_image_files[filename] else: @@ -300,12 +292,12 @@ def encode_sprite(self, sprite_info): (im_width, im_height) = im.size if x < 0 or y < 0 or x + size_x > im_width or y + size_y > im_height: pos = generic.build_position(sprite_info.poslist) - raise generic.ScriptError("Read beyond bounds of image file '{}'".format(filename_32bpp.value), pos) + raise generic.ScriptError(f"Read beyond bounds of image file '{filename_32bpp.value}'", pos) try: sprite = im.crop((x, y, x + size_x, y + size_y)) except OSError: pos = generic.build_position(sprite_info.poslist) - raise generic.ImageError("Failed to crop 32bpp {} image".format(im.format), filename_32bpp.value, pos) + raise generic.ImageError(f"Failed to crop 32bpp {im.format} image", filename_32bpp.value, pos) rgb_sprite_data = sprite.tobytes() if (info_byte & INFO_ALPHA) != 0: @@ -323,14 +315,12 @@ def encode_sprite(self, sprite_info): (im_width, im_height) = mask_im.size if mask_x < 0 or mask_y < 0 or mask_x + size_x > im_width or mask_y + size_y > im_height: pos = generic.build_position(sprite_info.poslist) - raise generic.ScriptError("Read beyond bounds of image file '{}'".format(filename_8bpp.value), pos) + raise generic.ScriptError(f"Read beyond bounds of image file '{filename_8bpp.value}'", pos) try: mask_sprite = mask_im.crop((mask_x, mask_y, mask_x + size_x, mask_y + size_y)) except OSError: pos = generic.build_position(sprite_info.poslist) - raise generic.ImageError( - "Failed to crop 8bpp {} image".format(mask_im.format), filename_8bpp.value, pos - ) + raise generic.ImageError(f"Failed to crop 8bpp {mask_im.format} image", filename_8bpp.value, pos) mask_sprite_data = self.palconvert(mask_sprite.tobytes(), im_mask_pal) # Check for white pixels; those that cause "artefacts" when shading diff --git a/nml/tokens.py b/nml/tokens.py index 42e0b0c5e..00481e7f3 100644 --- a/nml/tokens.py +++ b/nml/tokens.py @@ -16,9 +16,8 @@ import re import sys -from nml.ply import lex - from nml import expression, generic +from nml.ply import lex # fmt: off reserved = { @@ -248,13 +247,11 @@ def t_newline(self, t): self.increment_lines(len(t.value)) def t_error(self, t): - print( - ( - "Illegal character '{}' (character code 0x{:02X}) at {}, column {:d}".format( - t.value[0], ord(t.value[0]), t.lexer.lineno, self.find_column(t) - ) - ) - ) + line = t.lexer.lineno + col = self.find_column(t) + char = t.value[0] + hex_ord = f"0x{ord(char):02X}" + print(f"Illegal character '{char}' (character code {hex_ord}) at {line}, column {col}") sys.exit(1) def build(self): diff --git a/nml/version_info.py b/nml/version_info.py index b9e9bc69c..17034a9a5 100644 --- a/nml/version_info.py +++ b/nml/version_info.py @@ -64,17 +64,17 @@ def get_cli_version(): result = get_nml_version() + "\n\n" nmlc_path = os.path.abspath(sys.argv[0]) - result += "nmlc: {}\n".format(nmlc_path) + result += f"nmlc: {nmlc_path}\n" from nml import lz77 lz77_ver = "C (native)" if lz77.is_native else "Python" - result += "LZ77 implementation: {}\n\n".format(lz77_ver) + result += f"LZ77 implementation: {lz77_ver}\n\n" result += "Library versions encountered:\n" for lib, lib_ver in get_lib_versions().items(): - result += " {}: {}\n".format(lib, lib_ver) + result += f" {lib}: {lib_ver}\n" - result += "\nPython: {}\n".format(sys.executable) - result += "version {}".format(sys.version) + result += f"\nPython: {sys.executable}\n" + result += f"version {sys.version}" return result diff --git a/nml/version_update.py b/nml/version_update.py index 5599e5a33..950aa4a82 100644 --- a/nml/version_update.py +++ b/nml/version_update.py @@ -49,7 +49,7 @@ def get_git_version(): release = describe[1] == "0" changeset = describe[2] except OSError as e: - print("Git checkout found but cannot determine its version. Error({0}): {1}".format(e.errno, e.strerror)) + print(f"Git checkout found but cannot determine its version. Error({e.errno}): {e.strerror}") return None except subprocess.CalledProcessError as e: print("Git checkout found but cannot determine its version. Error: ", e) @@ -93,8 +93,8 @@ def get_and_write_version(): path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) with open(os.path.join(path, "nml", "__version__.py"), "w") as file: file.write("# this file is autogenerated by setup.py\n") - file.write('version = "{}"\n'.format(version)) + file.write(f'version = "{version}"\n') return version.split()[0] - except IOError: + except OSError: print("Version file NOT written") return None diff --git a/pyproject.toml b/pyproject.toml index 71b7f39d6..18f2bb7e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,81 @@ +[project] +name = "nml" +dynamic = ["version"] +description = "An OpenTTD NewGRF compiler for the nml language" +readme = "README.md" +license = "GPL-2.0+" +license-files = ["LICENSE"] +authors = [ + {name = "NML Development Team", email = "info@openttd.org"}, +] +classifiers=[ + "Environment :: Console", + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Topic :: Software Development :: Compilers", +] + +requires-python = ">=3.9" +dependencies = [ + "Pillow>=3.4", +] + +[project.urls] +Repository = "https://github.com/OpenTTD/nml" +Issues = "https://github.com/OpenTTD/nml/issues" +Changelog = "https://github.com/OpenTTD/nml/blob/master/docs/changelog.txt" + +[project.scripts] +nmlc = "nml.main:run" + [build-system] -requires = ["setuptools", "ply"] +requires = ["setuptools"] + +[tool.setuptools.package-dir] +nml = "nml" [tool.black] line-length = 120 -exclude = 'action2var_variables.py|action3_callbacks.py|ply|setup.py' +exclude = 'action2var_variables.py|action3_callbacks.py|ply' + +[tool.ruff] +line-length = 120 +exclude = [ + "nml/ply/*", +] + +[tool.ruff.lint] +select = [ + "E", # pycodestyle + "F", # Pyflakes + "UP", # pyupgrade + "B", # flake8-bugbear + "SIM", # flake8-simplify + "I", # isort +] + +ignore = [ + "B904", # use of nested exceptions + "SIM108", # rewrite as ternary +] + +# Files with tables of data, not conducive to being linted or formatted +[tool.ruff.lint.per-file-ignores] +"nml/palette.py" = ["E203", "E241"] +"nml/grfstrings.py" = ["E203", "E241"] +"nml/global_constants.py" = ["E203", "E241"] +"nml/actions/real_sprite.py" = ["E203", "E241"] +"nml/actions/action2var_variables.py" = ["E203", "E241", "E501"] +"nml/actions/action3_callbacks.py" = ["E203", "E241", "E501"] + +[tool.ruff.format] +exclude = [ + "nml/actions/action2var_variables.py", + "nml/actions/action3_callbacks.py", +] diff --git a/setup.py b/setup.py old mode 100755 new mode 100644 index 3f3d0d11f..f515b54a8 --- a/setup.py +++ b/setup.py @@ -1,9 +1,4 @@ -#!/usr/bin/env python3 - -from setuptools import Distribution, Extension, find_packages, setup - -import contextlib -import os +from setuptools import Extension, setup try: # Update the version by querying git if possible. @@ -17,42 +12,8 @@ NML_VERSION = version_info.get_nml_version() -default_dist = Distribution() -default_build_py = default_dist.get_command_class('build_py') -default_clean = default_dist.get_command_class('clean') - setup( name="nml", version=NML_VERSION, - packages=find_packages(), - description="An OpenTTD NewGRF compiler for the nml language", - long_description=( - "A tool to compile NewGRFs for OpenTTD from nml files" - "NML is a meta-language that aims to be a lot simpler to" - " learn and use than nfo used traditionally to write NewGRFs." - ), - license="GPL-2.0+", - classifiers=[ - "Development Status :: 2 - Pre-Alpha", - "Environment :: Console", - "Intended Audience :: Developers", - "License :: OSI Approved :: GNU General Public License (GPL)", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.14", - "Topic :: Software Development :: Compilers", - ], - url="https://github.com/OpenTTD/nml", - author="NML Development Team", - author_email="info@openttd.org", - entry_points={"console_scripts": ["nmlc = nml.main:run"]}, ext_modules=[Extension("nml_lz77", ["nml/_lz77.c"], optional=True)], - python_requires=">=3.10", - install_requires=[ - "Pillow>=3.4", - ], )