Skip to content

Commit

Permalink
Add: 'const' keyword to allow users to define new constants (#302)
Browse files Browse the repository at this point in the history
* Add: 'const' keyword to allow users to define new constants

* Fix: delay item 'id' validation to allow use of user-defined constants

* Fix: delay disable_item 'id' validations to allow use of user-defined constants

* Codechange: Use "const" in 030_house regression test
  • Loading branch information
glx22 authored Jul 10, 2024
1 parent dcbfdc7 commit 3739dd4
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 89 deletions.
47 changes: 47 additions & 0 deletions nml/ast/constant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
__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
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
NML is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with NML; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA."""

from nml import expression, generic, global_constants
from nml.ast import base_statement


class Constant(base_statement.BaseStatement):
def __init__(self, name, value):
base_statement.BaseStatement.__init__(self, "constant", name.pos, False, False)
self.name = name
self.value = value

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)
global_constants.constant_numbers[self.name.value] = self.value.reduce_constant(
global_constants.const_list
).value

def debug_print(self, indentation):
generic.print_dbg(indentation, "Constant")
generic.print_dbg(indentation + 2, "Name:")
self.name.debug_print(indentation + 4)

generic.print_dbg(indentation + 2, "Value:")
self.value.debug_print(indentation + 4)

def get_action_list(self):
return []

def __str__(self):
return "const {} = {};".format(self.name, self.value)
17 changes: 8 additions & 9 deletions nml/ast/disable_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,17 @@ def __init__(self, param_list, pos):
"disable_item() requires between 1 and 3 parameters, encountered {:d}.".format(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
self.last_id = param_list[2] if len(param_list) > 2 else None

if len(param_list) > 1:
self.first_id = param_list[1].reduce_constant(global_constants.const_list)
else:
self.first_id = None
def pre_process(self):
if self.first_id is not None:
self.first_id = self.first_id.reduce_constant(global_constants.const_list)

if len(param_list) > 2:
self.last_id = param_list[2].reduce_constant(global_constants.const_list)
if self.last_id is not None:
self.last_id = self.last_id.reduce_constant(global_constants.const_list)
if self.last_id.value < self.first_id.value:
raise generic.ScriptError("Last id to disable may not be lower than the first id.", pos)
else:
self.last_id = None
raise generic.ScriptError("Last id to disable may not be lower than the first id.", self.last_id.pos)

def debug_print(self, indentation):
generic.print_dbg(indentation, "Disable items, feature=" + str(self.feature.value))
Expand Down
10 changes: 6 additions & 4 deletions nml/ast/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,7 @@ def __init__(self, params, body, pos):
if self.feature.value in (0x08, 0x0C, 0x0E):
raise generic.ScriptError("Defining item blocks for this feature is not allowed.", self.pos)
self.name = params[1] if len(params) >= 2 else None

self.id = params[2].reduce_constant(global_constants.const_list) if len(params) >= 3 else None
if isinstance(self.id, expression.ConstantNumeric) and self.id.value == -1:
self.id = None # id == -1 means default
self.id = params[2] if len(params) >= 3 else None

if len(params) >= 4:
if self.feature.value != 0x07:
Expand All @@ -66,6 +63,11 @@ def __init__(self, params, body, pos):
self.size = None

def register_names(self):
if self.id is not None:
self.id = self.id.reduce_constant(global_constants.const_list)
if isinstance(self.id, expression.ConstantNumeric) and self.id.value == -1:
self.id = None # id == -1 means default

if self.name:
if not isinstance(self.name, expression.Identifier):
raise generic.ScriptError("Item parameter 2 'name' should be an identifier", self.pos)
Expand Down
8 changes: 7 additions & 1 deletion nml/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
basecost,
cargotable,
conditional,
constant,
deactivate,
disable_item,
error,
Expand Down Expand Up @@ -153,7 +154,8 @@ def p_main_block(self, t):
| snowline
| engine_override
| sort_vehicles
| basecost"""
| basecost
| constant"""
t[0] = t[1]

#
Expand Down Expand Up @@ -839,3 +841,7 @@ def p_tilelayout_item_tile(self, t):
def p_tilelayout_item_prop(self, t):
"tilelayout_item : assignment"
t[0] = t[1]

def p_constant(self, t):
"constant : CONST expression EQ expression SEMICOLON"
t[0] = constant.Constant(t[2], t[4])
1 change: 1 addition & 0 deletions nml/tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"recolour_sprite": "RECOLOUR_SPRITE",
"engine_override": "ENGINE_OVERRIDE",
"sort": "SORT_VEHICLES",
"const": "CONST"
}
# fmt: on

Expand Down
4 changes: 2 additions & 2 deletions regression/030_house.nml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ spriteset(brewery_spriteset_building) {
tmpl_building_sprite(150, 60, 91, -60, "brewery_snow.png")
}

default_palette = PALETTE_USE_DEFAULT;
/* Generic sprite layout that is used for all tiles of the building. Parmaeters:
const default_palette = PALETTE_USE_DEFAULT;
/* Generic sprite layout that is used for all tiles of the building. Parameters:
* - building_sprite: offset in the brewery_spriteset_building spriteset to use. -1 to skip building sprite.
* - with_smoke: Show smoke above the tile (also depends on animation state) */
spritelayout brewery_sprite_layout(building_sprite, with_smoke) {
Expand Down
Binary file modified regression/expected/030_house.grf
Binary file not shown.
Loading

0 comments on commit 3739dd4

Please sign in to comment.