Loading

Paste #pu3ahzqbr

  1. diff --git a/nml/actions/real_sprite.py b/nml/actions/real_sprite.py
  2. --- a/nml/actions/real_sprite.py
  3. +++ b/nml/actions/real_sprite.py
  4. @@ -160,9 +160,12 @@ class RealSprite(object):
  5.  
  6.      @ivar flags: Cropping/warning flags.
  7.      @type flags: L{expression.ConstantNumeric}
  8. +
  9. +    @ivar poslist: Position of creation of the sprite, if available.
  10. +    @type poslist: C{list} of L{Position}
  11.      """
  12.  
  13. -    def __init__(self, param_list = None, label = None):
  14. +    def __init__(self, param_list = None, label = None, pos = None):
  15.          self.param_list = param_list
  16.          self.label = label
  17.          self.is_empty = False
  18. @@ -170,6 +173,8 @@ class RealSprite(object):
  19.          self.ypos = None
  20.          self.xsize = None
  21.          self.ysize = None
  22. +        self.poslist = []
  23. +        if pos is not None: self.poslist.append(pos)
  24.  
  25.      def debug_print(self, indentation):
  26.          generic.print_dbg(indentation, 'Real sprite, parameters:')
  27. @@ -182,8 +187,8 @@ class RealSprite(object):
  28.              labels[self.label.value] = 0
  29.          return labels, 1
  30.  
  31. -    def expand(self, default_file, default_mask_file, id_dict):
  32. -        return [parse_real_sprite(self, default_file, default_mask_file, id_dict)]
  33. +    def expand(self, default_file, default_mask_file, poslist, id_dict):
  34. +        return [parse_real_sprite(self, default_file, default_mask_file, poslist, id_dict)]
  35.  
  36.      def check_sprite_size(self):
  37.          generic.check_range(self.xpos.value,  0, 0x7fffFFFF,   "Real sprite paramater 'xpos'", self.xpos.pos)
  38. @@ -249,6 +254,13 @@ class RealSprite(object):
  39.          return (rgb_file, rgb_rect, mask_file, mask_rect, do_crop)
  40.  
  41.  class SpriteAction(base_action.BaseAction):
  42. +    """
  43. +    @ivar sprite_num: Number of the sprite, or C{None} if not decided yet.
  44. +    @type sprite_num: C{int} or C{None}
  45. +
  46. +    @ivar last: Whether this sprite action is the last of a series.
  47. +    @type last: C{bool}
  48. +    """
  49.      def __init__(self):
  50.          self.sprite_num = None
  51.          self.last = False
  52. @@ -275,9 +287,13 @@ class RealSpriteAction(SpriteAction):
  53.          if self.last: file.newline()
  54.  
  55.  class RecolourSprite(object):
  56. -    def __init__(self, mapping, label = None):
  57. +    def __init__(self, mapping, label = None, poslist = None):
  58.          self.mapping = mapping
  59.          self.label = label
  60. +        if poslist is None:
  61. +            self.poslist = []
  62. +        else:
  63. +            self.poslist = poslist
  64.  
  65.      def debug_print(self, indentation):
  66.          generic.print_dbg(indentation, 'Recolour sprite, mapping:')
  67. @@ -290,7 +306,7 @@ class RecolourSprite(object):
  68.              labels[self.label.value] = 0
  69.          return labels, 1
  70.  
  71. -    def expand(self, default_file, default_mask_file, id_dict):
  72. +    def expand(self, default_file, default_mask_file, poslist, id_dict):
  73.          # create new struct, needed for template expansion
  74.          new_mapping = []
  75.          for old_assignment in self.mapping:
  76. @@ -299,7 +315,7 @@ class RecolourSprite(object):
  77.              to_min_value = old_assignment.value.min.reduce_constant([id_dict])
  78.              to_max_value = None if old_assignment.value.max is None else old_assignment.value.max.reduce_constant([id_dict])
  79.              new_mapping.append(assignment.Assignment(assignment.Range(from_min_value, from_max_value), assignment.Range(to_min_value, to_max_value), old_assignment.pos))
  80. -        return [RecolourSprite(new_mapping)]
  81. +        return [RecolourSprite(new_mapping, poslist = poslist)]
  82.  
  83.      def __str__(self):
  84.          ret = "" if self.label is None else str(self.label) + ": "
  85. @@ -372,7 +388,7 @@ class TemplateUsage(object):
  86.              labels[self.label.value] = 0
  87.          return labels, offset
  88.  
  89. -    def expand(self, default_file, default_mask_file, parameters):
  90. +    def expand(self, default_file, default_mask_file, poslist, parameters):
  91.          if self.name.value not in sprite_template_map:
  92.              raise generic.ScriptError("Encountered unknown template identifier: " + self.name.value, self.name.pos)
  93.          template = sprite_template_map[self.name.value]
  94. @@ -385,13 +401,13 @@ class TemplateUsage(object):
  95.                  raise generic.ScriptError("Template parameters should be compile-time constants", param.pos)
  96.              param_dict[template.param_list[i].value] = param.value
  97.  
  98. -        return parse_sprite_list(template.sprite_list, default_file, default_mask_file, param_dict)
  99. +        return parse_sprite_list(template.sprite_list, default_file, default_mask_file, poslist + [self.pos], param_dict)
  100.  
  101.      def __str__(self):
  102.          return "{}({})".format(self.name, ", ".join(str(param) for param in self.param_list))
  103.  
  104.  
  105. -def parse_real_sprite(sprite, default_file, default_mask_file, id_dict):
  106. +def parse_real_sprite(sprite, default_file, default_mask_file, poslist, id_dict):
  107.      # check the number of parameters
  108.      num_param = len(sprite.param_list)
  109.      if num_param == 0:
  110. @@ -402,6 +418,7 @@ def parse_real_sprite(sprite, default_fi
  111.  
  112.      # create new sprite struct, needed for template expansion
  113.      new_sprite = RealSprite()
  114. +    new_sprite.poslist = poslist + sprite.poslist
  115.  
  116.      param_offset = 0
  117.  
  118. @@ -474,10 +491,10 @@ def parse_real_sprite(sprite, default_fi
  119.  
  120.  sprite_template_map = {}
  121.  
  122. -def parse_sprite_list(sprite_list, default_file, default_mask_file, parameters = {}):
  123. +def parse_sprite_list(sprite_list, default_file, default_mask_file, poslist, parameters = {}):
  124.      real_sprite_list = []
  125.      for sprite in sprite_list:
  126. -        real_sprite_list.extend(sprite.expand(default_file, default_mask_file, parameters))
  127. +        real_sprite_list.extend(sprite.expand(default_file, default_mask_file, poslist, parameters))
  128.      return real_sprite_list
  129.  
  130.  def parse_sprite_data(sprite_container):
  131. @@ -493,8 +510,8 @@ def parse_sprite_data(sprite_container):
  132.      first = True
  133.  
  134.      for sprite_data in all_sprite_data:
  135. -        sprite_list, default_file, default_mask_file, zoom_level, bit_depth = sprite_data
  136. -        new_sprite_list = parse_sprite_list(sprite_list, default_file, default_mask_file)
  137. +        sprite_list, default_file, default_mask_file, pos, zoom_level, bit_depth = sprite_data
  138. +        new_sprite_list = parse_sprite_list(sprite_list, default_file, default_mask_file, [pos])
  139.          if not first and len(new_sprite_list) != len(action_list):
  140.              msg = "Expected {:d} alternative sprites for {} '{}', got {:d}."
  141.              msg = msg.format(len(action_list), sprite_container.block_type, sprite_container.block_name.value, len(new_sprite_list))
  142. diff --git a/nml/ast/sprite_container.py b/nml/ast/sprite_container.py
  143. --- a/nml/ast/sprite_container.py
  144. +++ b/nml/ast/sprite_container.py
  145. @@ -28,7 +28,7 @@ class SpriteContainer(object):
  146.      @type block_name: L{Identifier}, or C{None} if N/A
  147.  
  148.      @ivar sprite_data: Mapping of (zoom level, bit-depth) to (sprite list, default file)
  149. -    @type sprite_data: C{dict} that maps (C{tuple} of (C{int}, C{int})) to (C{tuple} of (C{list} of (L{RealSprite}, L{RecolourSprite} or L{TemplateUsage}), L{StringLiteral} or C{None}))
  150. +    @type sprite_data: C{dict} that maps (C{tuple} of (C{int}, C{int})) to (C{tuple} of (C{list} of (L{RealSprite}, L{RecolourSprite} or L{TemplateUsage}), L{StringLiteral} or C{None}, L{Position}))
  151.      """
  152.      sprite_blocks = {}
  153.  
  154. @@ -50,14 +50,17 @@ class SpriteContainer(object):
  155.                     "level / bit depth combination. This data will be overridden.")
  156.              msg = msg.format(self.block_type, self.block_name.value)
  157.              generic.print_warning(msg, pos)
  158. -        self.sprite_data[key] = (sprite_list, default_file, default_mask_file)
  159. +        self.sprite_data[key] = (sprite_list, default_file, default_mask_file, pos)
  160.  
  161.      def get_all_sprite_data(self):
  162.          """
  163. -        Get all sprite data as a list of 5-tuples (sprite_list, default_file, default_mask_file, zoom_level, bit_depth)
  164. -        Sorting makes sure that the order is consistent, and that the normal zoom, 8bpp sprites appear first
  165. +        Get all sprite data.
  166. +        Sorting makes sure that the order is consistent, and that the normal zoom, 8bpp sprites appear first.
  167. +
  168. +        @return: List of 6-tuples (sprite_list, default_file, default_mask_file, position, zoom_level, bit_depth).
  169. +        @rtype:  C{list} of C{tuple} of (C{list} of (L{RealSprite}, L{RecolourSprite} or L{TemplateUsage}), L{StringLiteral} or C{None}, L{Position}, C{int}, C{int})
  170.          """
  171. -        return [(self.sprite_data[key][0], self.sprite_data[key][1], self.sprite_data[key][2], key[0], key[1]) for key in sorted(self.sprite_data)]
  172. +        return [val + key for key, val in sorted(self.sprite_data.items())]
  173.  
  174.      @classmethod
  175.      def resolve_sprite_block(cls, block_name):
  176. diff --git a/nml/generic.py b/nml/generic.py
  177. --- a/nml/generic.py
  178. +++ b/nml/generic.py
  179. @@ -139,8 +139,10 @@ class ImageFilePosition(Position):
  180.      """
  181.      Generic (not position-dependant) error with an image file
  182.      """
  183. -    def __init__(self, filename):
  184. -        Position.__init__(self, filename, [])
  185. +    def __init__(self, filename, pos = None):
  186. +        poslist = []
  187. +        if pos is not None: poslist.append(pos)
  188. +        Position.__init__(self, filename, poslist)
  189.  
  190.      def __str__(self):
  191.          return 'Image file "{}"'.format(self.filename)
  192. @@ -181,8 +183,8 @@ class RangeError(ScriptError):
  193.          ScriptError.__init__(self, name + " out of range " + str(min_value) + ".." + str(max_value) + ", encountered " + str(value), pos)
  194.  
  195.  class ImageError(ScriptError):
  196. -    def __init__(self, value, filename):
  197. -        ScriptError.__init__(self, value, ImageFilePosition(filename))
  198. +    def __init__(self, value, filename, pos = None):
  199. +        ScriptError.__init__(self, value, ImageFilePosition(filename, pos))
  200.  
  201.  class OnlyOnceError(ScriptError):
  202.      """
  203. diff --git a/nml/parser.py b/nml/parser.py
  204. --- a/nml/parser.py
  205. +++ b/nml/parser.py
  206. @@ -451,9 +451,9 @@ class NMLParser(object):
  207.          '''real_sprite : LBRACKET expression_list RBRACKET
  208.                         | ID COLON LBRACKET expression_list RBRACKET'''
  209.          if len(t) == 4:
  210. -            t[0] = real_sprite.RealSprite(t[2])
  211. +            t[0] = real_sprite.RealSprite(param_list = t[2], pos = t.lineno(1))
  212.          else:
  213. -            t[0] = real_sprite.RealSprite(t[4], t[1])
  214. +            t[0] = real_sprite.RealSprite(param_list = t[4], label = t[1], pos = t.lineno(1))
  215.  
  216.      def p_recolour_assignment_list(self, t):
  217.          '''recolour_assignment_list :
  218. diff --git a/nml/spriteencoder.py b/nml/spriteencoder.py
  219. --- a/nml/spriteencoder.py
  220. +++ b/nml/spriteencoder.py
  221. @@ -263,14 +263,16 @@ class SpriteEncoder(object):
  222.          if filename_32bpp is not None:
  223.              im = self.open_image_file(filename_32bpp.value)
  224.              if im.mode not in ("RGB", "RGBA"):
  225. -                raise generic.ImageError("32bpp image is not a full colour RGB(A) image.", filename_32bpp.value)
  226. +                pos = build_position(sprite_info.poslist)
  227. +                raise generic.ImageError("32bpp image is not a full colour RGB(A) image.", filename_32bpp.value, pos)
  228.              info_byte |= INFO_RGB
  229.              if im.mode == "RGBA":
  230.                  info_byte |= INFO_ALPHA
  231.  
  232.              (im_width, im_height) = im.size
  233.              if x < 0 or y < 0 or x + size_x > im_width or y + size_y > im_height:
  234. -                raise generic.ScriptError("Read beyond bounds of image file '{}'".format(filename_32bpp.value), filename_32bpp.pos)
  235. +                pos = build_position(sprite_info.poslist)
  236. +                raise generic.ScriptError("Read beyond bounds of image file '{}'".format(filename_32bpp.value), pos)
  237.              sprite = im.crop((x, y, x + size_x, y + size_y))
  238.              rgb_sprite_data = sprite.tostring()
  239.  
  240. @@ -281,13 +283,15 @@ class SpriteEncoder(object):
  241.          if filename_8bpp is not None:
  242.              mask_im = self.open_image_file(filename_8bpp.value)
  243.              if mask_im.mode != "P":
  244. -                raise generic.ImageError("8bpp image does not have a palette", filename_8bpp.value)
  245. +                pos = build_position(sprite_info.poslist)
  246. +                raise generic.ImageError("8bpp image does not have a palette", filename_8bpp.value, pos)
  247.              im_mask_pal = palette.validate_palette(mask_im, filename_8bpp.value)
  248.              info_byte |= INFO_PAL
  249.  
  250.              (im_width, im_height) = mask_im.size
  251.              if mask_x < 0 or mask_y < 0 or mask_x + size_x > im_width or mask_y + size_y > im_height:
  252. -                raise generic.ScriptError("Read beyond bounds of image file '{}'".format(filename_8bpp.value), filename_8bpp.pos)
  253. +                pos = build_position(sprite_info.poslist)
  254. +                raise generic.ScriptError("Read beyond bounds of image file '{}'".format(filename_8bpp.value), pos)
  255.              mask_sprite = mask_im.crop((mask_x, mask_y, mask_x + size_x, mask_y + size_y))
  256.  
  257.              mask_sprite_data = self.palconvert(mask_sprite.tostring(), im_mask_pal)
  258. @@ -497,3 +501,21 @@ class SpriteEncoder(object):
  259.          else:
  260.              return sprite_str
  261.  
  262. +def build_position(poslist):
  263. +    """
  264. +    Construct a L{Position} object that expands to positions in the template instantiation stack.
  265. +
  266. +    @param poslist: Sequence of positions to report. First entry is the innermost template,
  267. +                    last entry is the nml statement that started it all.
  268. +    @type  poslist: C{list} of L{Position}
  269. +
  270. +    @return: Position to attach to an error.
  271. +    @rtype:  L{Position}
  272. +    """
  273. +    if poslist is None or len(poslist) == 0:
  274. +        return None
  275. +    if len(poslist) == 1:
  276. +        return poslist[0]
  277. +    pos = poslist[-1]
  278. +    pos.includes = pos.includes + poslist[:-1]
  279. +    return pos

Comments