Loading

Paste #pcqgqlx2y

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

Comments