From dce955c68db3cfc700cae11743ad966036faa64f Mon Sep 17 00:00:00 2001 From: Vas Crabb Date: Sun, 22 Jul 2018 03:22:31 +1000 Subject: rendlay: allow user variables and repetition in layouts, also add a few more predefined variables --- scripts/build/complay.py | 442 ++++++++++++++++++++++++++++++----------------- 1 file changed, 280 insertions(+), 162 deletions(-) (limited to 'scripts/build/complay.py') diff --git a/scripts/build/complay.py b/scripts/build/complay.py index 9932c604cb4..8ac24bb005a 100644 --- a/scripts/build/complay.py +++ b/scripts/build/complay.py @@ -93,7 +93,8 @@ class XmlError(Exception): class LayoutChecker(Minifyer): BADTAGPATTERN = re.compile('[^abcdefghijklmnopqrstuvwxyz0123456789_.:^$]') - VARPATTERN = re.compile('^~scr(0|[1-9][0-9]*)(native[xy]aspect|width|height)~$') + VARPATTERN = re.compile('^.*~[0-9A-Za-z_]+~.*$') + FLOATCHARS = re.compile('^.*[.eE].*$') SHAPES = frozenset(('disk', 'led14seg', 'led14segsc', 'led16seg', 'led16segsc', 'led7seg', 'led8seg_gts1', 'rect')) OBJECTS = frozenset(('backdrop', 'bezel', 'cpanel', 'marquee', 'overlay')) @@ -106,8 +107,6 @@ class LayoutChecker(Minifyer): self.views = { } self.referenced_elements = { } self.referenced_groups = { } - self.have_bounds = [ ] - self.have_color = [ ] def formatLocation(self): return '%s:%d:%d' % (self.locator.getSystemId(), self.locator.getLineNumber(), self.locator.getColumnNumber()) @@ -116,36 +115,125 @@ class LayoutChecker(Minifyer): self.errors += 1 sys.stderr.write('error: %s: %s\n' % (self.formatLocation(), msg)) - def checkBoundsDimension(self, attrs, name): - if name in attrs: - try: - return float(attrs[name]) - except: - if not self.VARPATTERN.match(attrs[name]): - self.handleError('Element bounds attribute %s "%s" is not numeric' % (name, attrs[name])) - return None + def checkIntAttribute(self, name, attrs, key, default): + if key not in attrs: + return default + val = attrs[key] + if self.VARPATTERN.match(val): + return None + base = 10 + offs = 0 + if (len(val) >= 1) and ('$' == val[0]): + base = 16 + offs = 1 + elif (len(val) >= 2) and ('0' == val[0]) and (('x' == val[1]) or ('X' == val[1])): + base = 16 + offs = 2 + elif (len(val) >= 1) and ('#' == val[0]): + offs = 1 + try: + return int(val[offs:], base) + except: + self.handleError('Element %s attribute %s "%s" is not an integer' % (name, key, val)) + return None + + def checkFloatAttribute(self, name, attrs, key, default): + if key not in attrs: + return default + val = attrs[key] + if self.VARPATTERN.match(val): + return None + try: + return float(val) + except: + self.handleError('Element %s attribute %s "%s" is not a floating point number' % (name, key, val)) + return None + + def checkNumericAttribute(self, name, attrs, key, default): + if key not in attrs: + return default + val = attrs[key] + if self.VARPATTERN.match(val): + return None + base = 0 + offs = 0 + try: + if (len(val) >= 1) and ('$' == val[0]): + base = 16 + offs = 1 + elif (len(val) >= 2) and ('0' == val[0]) and (('x' == val[1]) or ('X' == val[1])): + base = 16 + offs = 2 + elif (len(val) >= 1) and ('#' == val[0]): + base = 10 + offs = 1 + elif self.FLOATCHARS.match(val): + return float(val) + return int(val[offs:], base) + except: + self.handleError('Element %s attribute %s "%s" is not a number' % (name, key, val)) + return None + + def checkParameter(self, attrs): + if 'name' not in attrs: + self.handleError('Element param missing attribute name') + else: + name = attrs['name'] + self.checkNumericAttribute('param', attrs, 'increment', None) + lshift = self.checkIntAttribute('param', attrs, 'lshift', None) + if (lshift is not None) and (0 > lshift): + self.handleError('Element param attribute lshift "%s" is negative', (attrs['lshift'], )) + rshift = self.checkIntAttribute('param', attrs, 'rshift', None) + if (rshift is not None) and (0 > rshift): + self.handleError('Element param attribute rshift "%s" is negative', (attrs['rshift'], )) + if self.repeat_depth and self.repeat_depth[-1]: + if 'start' in attrs: + if 'value' in attrs: + self.handleError('Element param has both start and value attributes') + if 'name' in attrs: + if name not in self.variable_scopes[-1]: + self.variable_scopes[-1][name] = True + elif not self.VARPATTERN.match(name): + self.handleError('Incrementing parameter "%s" redefined', (name, )) + else: + if 'value' not in attrs: + self.handleError('Element param missing attribute value') + if ('increment' in attrs) or ('lshift' in attrs) or ('rshift' in attrs): + self.handleError('Element param has increment/lshift/rshift attribute(s) without start attribute') + if 'name' in attrs: + if not self.variable_scopes[-1].get(name, False): + self.variable_scopes[-1][name] = False + elif not self.VARPATTERN.match(name): + self.handleError('Incrementing parameter "%s" redefined', (name, )) + else: + if ('start' in attrs) or ('increment' in attrs) or ('lshift' in attrs) or ('rshift' in attrs): + self.handleError('Element param with start/increment/lshift/rshift attribute(s) not in repeat scope') + if 'value' not in attrs: + self.handleError('Element param missing attribute value') + if 'name' in attrs: + self.variable_scopes[-1][attrs['name']] = False def checkBounds(self, attrs): if self.have_bounds[-1]: self.handleError('Duplicate element bounds') else: self.have_bounds[-1] = True - left = self.checkBoundsDimension(attrs, 'left') - top = self.checkBoundsDimension(attrs, 'top') - right = self.checkBoundsDimension(attrs, 'right') - bottom = self.checkBoundsDimension(attrs, 'bottom') - x = self.checkBoundsDimension(attrs, 'bottom') - y = self.checkBoundsDimension(attrs, 'bottom') - width = self.checkBoundsDimension(attrs, 'width') - height = self.checkBoundsDimension(attrs, 'height') + left = self.checkFloatAttribute('bounds', attrs, 'left', 0.0) + top = self.checkFloatAttribute('bounds', attrs, 'top', 0.0) + right = self.checkFloatAttribute('bounds', attrs, 'right', 1.0) + bottom = self.checkFloatAttribute('bounds', attrs, 'bottom', 1.0) + x = self.checkFloatAttribute('bounds', attrs, 'x', 0.0) + y = self.checkFloatAttribute('bounds', attrs, 'y', 0.0) + width = self.checkFloatAttribute('bounds', attrs, 'width', 1.0) + height = self.checkFloatAttribute('bounds', attrs, 'height', 1.0) if (left is not None) and (right is not None) and (left > right): self.handleError('Element bounds attribute left "%s" is greater than attribute right "%s"' % ( - attrs['left'], - attrs['right'])) + attrs.get('left', 0.0), + attrs.get('right', 1.0))) if (top is not None) and (bottom is not None) and (top > bottom): self.handleError('Element bounds attribute top "%s" is greater than attribute bottom "%s"' % ( - attrs['top'], - attrs['bottom'])) + attrs.get('top', 0.0), + attrs.get('bottom', 1.0))) if (width is not None) and (0.0 > width): self.handleError('Element bounds attribute width "%s" is negative' % (attrs['width'], )) if (height is not None) and (0.0 > height): @@ -155,16 +243,12 @@ class LayoutChecker(Minifyer): has_ltrb = ('left' in attrs) or ('top' in attrs) or ('right' in attrs) or ('bottom' in attrs) has_origin_size = ('x' in attrs) or ('y' in attrs) or ('width' in attrs) or ('height' in attrs) if has_ltrb and has_origin_size: - self.handleError('Element bounds has both left/top/right/bottom and origin/size') + self.handleError('Element bounds has both left/top/right/bottom and origin/size attributes') def checkColorChannel(self, attrs, name): - if name in attrs: - try: - channel = float(attrs[name]) - if (0.0 > channel) or (1.0 < channel): - self.handleError('Element color attribute %s "%s" outside valid range 0.0-1.0' % (name, attrs[name])) - except: - self.handleError('Element color attribute %s "%s" is not numeric' % (name, attrs[name])) + channel = self.checkFloatAttribute('color', attrs, name, None) + if (channel is not None) and ((0.0 > channel) or (1.0 < channel)): + self.handleError('Element color attribute %s "%s" outside valid range 0.0-1.0' % (name, attrs[name])) def checkTag(self, tag, element, attr): if '' == tag: @@ -177,7 +261,118 @@ class LayoutChecker(Minifyer): if tag.find('::') >= 0: self.handleError('Element %s attribute %s "%s" contains double separator' % (element, attr, tag)) - def checkGroupViewItem(self, name, attrs): + def rootStartHandler(self, name, attrs): + if 'mamelayout' != name: + self.ignored_depth = 1 + self.handleError('Expected root element mamelayout but found %s' % (name, )) + else: + if 'version' not in attrs: + self.handleError('Element mamelayout missing attribute version') + else: + try: + long(attrs['version']) + except: + self.handleError('Element mamelayout attribute version "%s" is not an integer' % (attrs['version'], )) + self.variable_scopes.append({ }) + self.handlers.append((self.layoutStartHandler, self.layoutEndHandler)) + + def rootEndHandler(self, name, attrs): + pass # should be unreachable + + def layoutStartHandler(self, name, attrs): + if 'element' == name: + if 'name' not in attrs: + self.handleError('Element element missing attribute name') + else: + if attrs['name'] not in self.elements: + self.elements[attrs['name']] = self.formatLocation() + elif not self.VARPATTERN.match(attrs['name']): + self.handleError('Element element has duplicate name (previous %s)' % (self.elements[attrs['name']], )) + self.handlers.append((self.elementStartHandler, self.elementEndHandler)) + elif 'group' == name: + if 'name' not in attrs: + self.handleError('Element group missing attribute name') + else: + if attrs['name'] not in self.groups: + self.groups[attrs['name']] = self.formatLocation() + elif not self.VARPATTERN.match(attrs['name']): + self.handleError('Element group has duplicate name (previous %s)' % (self.groups[attrs['name']], )) + self.handlers.append((self.groupViewStartHandler, self.groupViewEndHandler)) + self.variable_scopes.append({ }) + self.repeat_depth.append(0) + self.have_bounds.append(False) + elif 'view' == name: + if 'name' not in attrs: + self.handleError('Element view missing attribute name') + else: + if attrs['name'] not in self.views: + self.views[attrs['name']] = self.formatLocation() + elif not self.VARPATTERN.match(attrs['name']): + self.handleError('Element view has duplicate name (previous %s)' % (self.views[attrs['name']], )) + self.handlers.append((self.groupViewStartHandler, self.groupViewEndHandler)) + self.variable_scopes.append({ }) + self.repeat_depth.append(0) + self.have_bounds.append(False) + elif 'param' == name: + self.checkParameter(attrs) + self.ignored_depth = 1 + elif 'script' == name: + self.ignored_depth = 1 + else: + self.handleError('Encountered unexpected element %s' % (name, )) + self.ignored_depth = 1 + + def layoutEndHandler(self, name): + for element in self.referenced_elements: + if (element not in self.elements) and (not self.VARPATTERN.match(element)): + self.handleError('Element "%s" not found (first referenced at %s)' % (element, self.referenced_elements[element])) + for group in self.referenced_groups: + if (group not in self.groups) and (not self.VARPATTERN.match(group)): + self.handleError('Group "%s" not found (first referenced at %s)' % (group, self.referenced_groups[group])) + self.variable_scopes.pop() + self.handlers.pop() + + def elementStartHandler(self, name, attrs): + if name in self.SHAPES: + self.handlers.append((self.shapeStartHandler, self.shapeEndHandler)) + self.have_bounds.append(False) + self.have_color.append(False) + elif 'text' == name: + if 'string' not in attrs: + self.handleError('Element bounds missing attribute string') + if 'align' in attrs: + align = self.checkIntAttribute(name, attrs, 'align', None) + if (align is not None) and ((0 > align) or (2 < align)): + self.handleError('Element text attribute align "%s" not in valid range 0-2' % (attrs['align'], )) + self.handlers.append((self.shapeStartHandler, self.shapeEndHandler)) + self.have_bounds.append(False) + self.have_color.append(False) + else: + self.ignored_depth = 1 + + def elementEndHandler(self, name): + self.handlers.pop() + + def shapeStartHandler(self, name, attrs): + if 'bounds' == name: + self.checkBounds(attrs) + elif 'color' == name: + if self.have_color[-1]: + self.handleError('Duplicate bounds element') + else: + self.have_color[-1] = True + self.checkColorChannel(attrs, 'red') + self.checkColorChannel(attrs, 'green') + self.checkColorChannel(attrs, 'blue') + self.checkColorChannel(attrs, 'alpha') + self.ignored_depth = 1 + + def shapeEndHandler(self, name): + self.have_bounds.pop() + self.have_color.pop() + self.handlers.pop() + + def groupViewStartHandler(self, name, attrs): if name in self.OBJECTS: if 'element' not in attrs: self.handleError('Element %s missing attribute element', (name, )) @@ -187,21 +382,14 @@ class LayoutChecker(Minifyer): if 'inputmask' not in attrs: self.handleError('Element %s has inputtag without inputmask attribute' % (name, )) self.checkTag(attrs['inputtag'], name, 'inputtag') - if 'inputmask' in attrs: - try: - int(attrs['inputmask'], 0) - except: - self.handleError('Element %s attribute inputmask "%s" is not an integer' % (name, attrs['inputmask'])) - self.in_object = True + self.checkIntAttribute(name, attrs, 'inputmask', None) + self.handlers.append((self.objectStartHandler, self.objectEndHandler)) self.have_bounds.append(False) elif 'screen' == name: if 'index' in attrs: - try: - index = long(attrs['index'], 0) - if 0 > index: - self.handleError('Element screen attribute index "%s" is negative' % (attrs['index'], )) - except: - self.handleError('Element screen attribute index "%s" is not an integer' % (attrs['index'], )) + index = self.checkIntAttribute(name, attrs, 'index', None) + if (index is not None) and (0 > index): + self.handleError('Element screen attribute index "%s" is negative' % (attrs['index'], )) if 'tag' in attrs: self.handleError('Element screen has both index and tag attributes') if 'tag' in attrs: @@ -209,34 +397,65 @@ class LayoutChecker(Minifyer): self.checkTag(tag, name, 'tag') if self.BADTAGPATTERN.search(tag): self.handleError('Element screen attribute tag "%s" contains invalid characters' % (tag, )) - self.in_object = True + self.handlers.append((self.objectStartHandler, self.objectEndHandler)) self.have_bounds.append(False) elif 'group' == name: if 'ref' not in attrs: self.handleError('Element group missing attribute ref') elif attrs['ref'] not in self.referenced_groups: self.referenced_groups[attrs['ref']] = self.formatLocation() - self.in_object = True + self.handlers.append((self.objectStartHandler, self.objectEndHandler)) self.have_bounds.append(False) + elif 'repeat' == name: + if 'count' not in attrs: + self.handleError('Element repeat missing attribute count') + else: + count = self.checkIntAttribute(name, attrs, 'count', None) + if (count is not None) and (0 >= count): + self.handleError('Element repeat attribute count "%s" is negative' % (attrs['count'], )) + self.variable_scopes.append({ }) + self.repeat_depth[-1] += 1 + elif 'param' == name: + self.checkParameter(attrs) + self.ignored_depth = 1 elif 'bounds' == name: self.checkBounds(attrs) + if self.repeat_depth[-1]: + self.handleError('Element bounds inside repeat') self.ignored_depth = 1 else: self.handleError('Encountered unexpected element %s' % (name, )) self.ignored_depth = 1 + def groupViewEndHandler(self, name): + self.variable_scopes.pop() + if self.repeat_depth[-1]: + self.repeat_depth[-1] -= 1 + else: + self.repeat_depth.pop() + self.have_bounds.pop() + self.handlers.pop() + + def objectStartHandler(self, name, attrs): + if 'bounds' == name: + self.checkBounds(attrs) + self.ignored_depth = 1 + + def objectEndHandler(self, name): + self.have_bounds.pop() + self.handlers.pop() + def setDocumentLocator(self, locator): self.locator = locator super(LayoutChecker, self).setDocumentLocator(locator) def startDocument(self): - self.in_layout = False - self.in_element = False - self.in_group = False - self.in_view = False - self.in_shape = False - self.in_object = False + self.handlers = [(self.rootStartHandler, self.rootEndHandler)] self.ignored_depth = 0 + self.variable_scopes = [ ] + self.repeat_depth = [ ] + self.have_bounds = [ ] + self.have_color = [ ] super(LayoutChecker, self).startDocument() def endDocument(self): @@ -246,127 +465,26 @@ class LayoutChecker(Minifyer): self.views.clear() self.referenced_elements.clear() self.referenced_groups.clear() - del self.have_bounds[:] - del self.have_color[:] + del self.handlers + del self.ignored_depth + del self.variable_scopes + del self.repeat_depth + del self.have_bounds + del self.have_color super(LayoutChecker, self).endDocument() def startElement(self, name, attrs): if 0 < self.ignored_depth: self.ignored_depth += 1 - elif not self.in_layout: - if 'mamelayout' != name: - self.ignored_depth = 1 - self.handleError('Expected root element mamelayout but found %s' % (name, )) - else: - if 'version' not in attrs: - self.handleError('Element mamelayout missing attribute version') - else: - try: - long(attrs['version']) - except: - self.handleError('Element mamelayout attribute version "%s" is not an integer' % (attrs['version'], )) - self.in_layout = True - elif self.in_object: - if 'bounds' == name: - self.checkBounds(attrs) - self.ignored_depth = 1 - elif self.in_shape: - if 'bounds' == name: - self.checkBounds(attrs) - elif 'color' == name: - if self.have_color[-1]: - self.handleError('Duplicate bounds element') - else: - self.have_color[-1] = True - self.checkColorChannel(attrs, 'red') - self.checkColorChannel(attrs, 'green') - self.checkColorChannel(attrs, 'blue') - self.checkColorChannel(attrs, 'alpha') - self.ignored_depth = 1 - elif self.in_element: - if name in self.SHAPES: - self.in_shape = True - self.have_bounds.append(False) - self.have_color.append(False) - elif 'text' == name: - if 'string' not in attrs: - self.handleError('Element bounds missing attribute string') - if 'align' in attrs: - try: - align = long(attrs['align']) - if (0 > align) or (2 < align): - self.handleError('Element text attribute align "%s" not in valid range 0-2' % (attrs['align'], )) - except: - self.handleError('Element text attribute align "%s" is not an integer' % (attrs['align'], )) - self.in_shape = True - self.have_bounds.append(False) - self.have_color.append(False) - else: - self.ignored_depth = 1 - elif self.in_group or self.in_view: - self.checkGroupViewItem(name, attrs) - elif 'element' == name: - if 'name' not in attrs: - self.handleError('Element element missing attribute name') - else: - if attrs['name'] in self.elements: - self.handleError('Element element has duplicate name (previous %s)' % (self.elements[attrs['name']], )) - else: - self.elements[attrs['name']] = self.formatLocation() - self.in_element = True - elif 'group' == name: - if 'name' not in attrs: - self.handleError('Element group missing attribute name') - else: - if attrs['name'] in self.groups: - self.handleError('Element group has duplicate name (previous %s)' % (self.groups[attrs['name']], )) - else: - self.groups[attrs['name']] = self.formatLocation() - self.in_group = True - self.have_bounds.append(False) - elif 'view' == name: - if 'name' not in attrs: - self.handleError('Element view missing attribute name') - else: - if attrs['name'] in self.views: - self.handleError('Element view has duplicate name (previous %s)' % (self.views[attrs['name']], )) - else: - self.views[attrs['name']] = self.formatLocation() - self.in_view = True - self.have_bounds.append(False) - elif 'script' == name: - self.ignored_depth = 1 else: - self.handleError('Encountered unexpected element %s' % (name, )) - self.ignored_depth = 1 + self.handlers[-1][0](name, attrs) super(LayoutChecker, self).startElement(name, attrs) def endElement(self, name): if 0 < self.ignored_depth: self.ignored_depth -= 1 - elif self.in_object: - self.in_object = False - self.have_bounds.pop() - elif self.in_shape: - self.in_shape = False - self.have_bounds.pop() - self.have_color.pop() - elif self.in_element: - self.in_element = False - elif self.in_group: - self.in_group = False - self.have_bounds.pop() - elif self.in_view: - self.in_view = False - self.have_bounds.pop() - elif self.in_layout: - for element in self.referenced_elements: - if element not in self.elements: - self.handleError('Element "%s" not found (first referenced at %s)' % (element, self.referenced_elements[element])) - for group in self.referenced_groups: - if group not in self.groups: - self.handleError('Group "%s" not found (first referenced at %s)' % (group, self.referenced_groups[group])) - self.in_layout = False + else: + self.handlers[-1][1](name) super(LayoutChecker, self).endElement(name) -- cgit v1.2.3-70-g09d2