Home | History | Annotate | Download | only in generators
      1 #!/usr/bin/env python
      2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 """Nodes for PPAPI IDL AST"""
      7 
      8 #
      9 # IDL Node
     10 #
     11 # IDL Node defines the IDLAttribute and IDLNode objects which are constructed
     12 # by the parser as it processes the various 'productions'.  The IDLAttribute
     13 # objects are assigned to the IDLNode's property dictionary instead of being
     14 # applied as children of The IDLNodes, so they do not exist in the final tree.
     15 # The AST of IDLNodes is the output from the parsing state and will be used
     16 # as the source data by the various generators.
     17 #
     18 
     19 import sys
     20 
     21 from idl_log import ErrOut, InfoOut, WarnOut
     22 from idl_propertynode import IDLPropertyNode
     23 from idl_release import IDLRelease, IDLReleaseMap
     24 
     25 
     26 # IDLAttribute
     27 #
     28 # A temporary object used by the parsing process to hold an Extended Attribute
     29 # which will be passed as a child to a standard IDLNode.
     30 #
     31 class IDLAttribute(object):
     32   def __init__(self, name, value):
     33     self.cls = 'ExtAttribute'
     34     self.name = name
     35     self.value = value
     36 
     37   def __str__(self):
     38     return '%s=%s' % (self.name, self.value)
     39 
     40 #
     41 # IDLNode
     42 #
     43 # This class implements the AST tree, providing the associations between
     44 # parents and children.  It also contains a namepsace and propertynode to
     45 # allow for look-ups.  IDLNode is derived from IDLRelease, so it is
     46 # version aware.
     47 #
     48 class IDLNode(IDLRelease):
     49 
     50   # Set of object IDLNode types which have a name and belong in the namespace.
     51   NamedSet = set(['Enum', 'EnumItem', 'File', 'Function', 'Interface',
     52                   'Member', 'Param', 'Struct', 'Type', 'Typedef'])
     53 
     54   def __init__(self, cls, filename, lineno, pos, children=None):
     55     # Initialize with no starting or ending Version
     56     IDLRelease.__init__(self, None, None)
     57 
     58     self.cls = cls
     59     self.lineno = lineno
     60     self.pos = pos
     61     self._filename = filename
     62     self._deps = {}
     63     self.errors = 0
     64     self.namespace = None
     65     self.typelist = None
     66     self.parent = None
     67     self._property_node = IDLPropertyNode()
     68     self._unique_releases = None
     69 
     70     # A list of unique releases for this node
     71     self.releases = None
     72 
     73     # A map from any release, to the first unique release
     74     self.first_release = None
     75 
     76     # self._children is a list of children ordered as defined
     77     self._children = []
     78     # Process the passed in list of children, placing ExtAttributes into the
     79     # property dictionary, and nodes into the local child list in order.  In
     80     # addition, add nodes to the namespace if the class is in the NamedSet.
     81     if children:
     82       for child in children:
     83         if child.cls == 'ExtAttribute':
     84           self.SetProperty(child.name, child.value)
     85         else:
     86           self.AddChild(child)
     87 
     88   def __str__(self):
     89     name = self.GetName()
     90     if name is None:
     91       name = ''
     92     return '%s(%s)' % (self.cls, name)
     93 
     94   def Location(self):
     95     """Return a file and line number for where this node was defined."""
     96     return '%s(%d)' % (self._filename, self.lineno)
     97 
     98   def Error(self, msg):
     99     """Log an error for this object."""
    100     self.errors += 1
    101     ErrOut.LogLine(self._filename, self.lineno, 0, ' %s %s' %
    102                    (str(self), msg))
    103     filenode = self.GetProperty('FILE')
    104     if filenode:
    105       errcnt = filenode.GetProperty('ERRORS')
    106       if not errcnt:
    107         errcnt = 0
    108       filenode.SetProperty('ERRORS', errcnt + 1)
    109 
    110   def Warning(self, msg):
    111     """Log a warning for this object."""
    112     WarnOut.LogLine(self._filename, self.lineno, 0, ' %s %s' %
    113                     (str(self), msg))
    114 
    115   def GetName(self):
    116     return self.GetProperty('NAME')
    117 
    118   def Dump(self, depth=0, comments=False, out=sys.stdout):
    119     """Dump this object and its children"""
    120     if self.cls in ['Comment', 'Copyright']:
    121       is_comment = True
    122     else:
    123       is_comment = False
    124 
    125     # Skip this node if it's a comment, and we are not printing comments
    126     if not comments and is_comment:
    127       return
    128 
    129     tab = ''.rjust(depth * 2)
    130     if is_comment:
    131       out.write('%sComment\n' % tab)
    132       for line in self.GetName().split('\n'):
    133         out.write('%s  "%s"\n' % (tab, line))
    134     else:
    135       ver = IDLRelease.__str__(self)
    136       if self.releases:
    137         release_list = ': ' + ' '.join(self.releases)
    138       else:
    139         release_list = ': undefined'
    140       out.write('%s%s%s%s\n' % (tab, self, ver, release_list))
    141     if self.typelist:
    142       out.write('%s  Typelist: %s\n' % (tab, self.typelist.GetReleases()[0]))
    143     properties = self._property_node.GetPropertyList()
    144     if properties:
    145       out.write('%s  Properties\n' % tab)
    146       for p in properties:
    147         if is_comment and p == 'NAME':
    148           # Skip printing the name for comments, since we printed above already
    149           continue
    150         out.write('%s    %s : %s\n' % (tab, p, self.GetProperty(p)))
    151     for child in self._children:
    152       child.Dump(depth+1, comments=comments, out=out)
    153 
    154   def IsA(self, *typelist):
    155     """Check if node is of a given type."""
    156     return self.cls in typelist
    157 
    158   def GetListOf(self, *keys):
    159     """Get a list of objects for the given key(s)."""
    160     out = []
    161     for child in self._children:
    162       if child.cls in keys:
    163         out.append(child)
    164     return out
    165 
    166   def GetOneOf(self, *keys):
    167     """Get an object for the given key(s)."""
    168     out = self.GetListOf(*keys)
    169     if out:
    170       return out[0]
    171     return None
    172 
    173   def SetParent(self, parent):
    174     self._property_node.AddParent(parent)
    175     self.parent = parent
    176 
    177   def AddChild(self, node):
    178     node.SetParent(self)
    179     self._children.append(node)
    180 
    181   # Get a list of all children
    182   def GetChildren(self):
    183     return self._children
    184 
    185   def GetType(self, release):
    186     if not self.typelist:
    187       return None
    188     return self.typelist.FindRelease(release)
    189 
    190   def GetDeps(self, release, visited=None):
    191     visited = visited or set()
    192 
    193     # If this release is not valid for this object, then done.
    194     if not self.IsRelease(release) or self.IsA('Comment', 'Copyright'):
    195       return set([])
    196 
    197     # If we have cached the info for this release, return the cached value
    198     deps = self._deps.get(release, None)
    199     if deps is not None:
    200       return deps
    201 
    202     # If we are already visited, then return
    203     if self in visited:
    204       return set([self])
    205 
    206     # Otherwise, build the dependency list
    207     visited |= set([self])
    208     deps = set([self])
    209 
    210     # Get child deps
    211     for child in self.GetChildren():
    212       deps |= child.GetDeps(release, visited)
    213       visited |= set(deps)
    214 
    215     # Get type deps
    216     typeref = self.GetType(release)
    217     if typeref:
    218       deps |= typeref.GetDeps(release, visited)
    219 
    220     self._deps[release] = deps
    221     return deps
    222 
    223   def GetVersion(self, release):
    224     filenode = self.GetProperty('FILE')
    225     if not filenode:
    226       return None
    227     return filenode.release_map.GetVersion(release)
    228 
    229   def GetUniqueReleases(self, releases):
    230     """Return the unique set of first releases corresponding to input
    231 
    232     Since we are returning the corresponding 'first' version for a
    233     release, we may return a release version prior to the one in the list."""
    234     my_min, my_max = self.GetMinMax(releases)
    235     if my_min > releases[-1] or my_max < releases[0]:
    236       return []
    237 
    238     out = set()
    239     for rel in releases:
    240       remapped = self.first_release[rel]
    241       if not remapped:
    242         continue
    243       out |= set([remapped])
    244 
    245     # Cache the most recent set of unique_releases
    246     self._unique_releases = sorted(out)
    247     return self._unique_releases
    248 
    249   def LastRelease(self, release):
    250     # Get the most recent release from the most recently generated set of
    251     # cached unique releases.
    252     if self._unique_releases and self._unique_releases[-1] > release:
    253       return False
    254     return True
    255 
    256   def GetRelease(self, version):
    257     filenode = self.GetProperty('FILE')
    258     if not filenode:
    259       return None
    260     return filenode.release_map.GetRelease(version)
    261 
    262   def _GetReleaseList(self, releases, visited=None):
    263     visited = visited or set()
    264     if not self.releases:
    265       # If we are unversionable, then return first available release
    266       if self.IsA('Comment', 'Copyright', 'Label'):
    267         self.releases = []
    268         return self.releases
    269 
    270       # Generate the first and if deprecated within this subset, the
    271       # last release for this node
    272       my_min, my_max = self.GetMinMax(releases)
    273 
    274       if my_max != releases[-1]:
    275         my_releases = set([my_min, my_max])
    276       else:
    277         my_releases = set([my_min])
    278 
    279       r = self.GetRelease(self.GetProperty('version'))
    280       if not r in my_releases:
    281         my_releases |= set([r])
    282 
    283       # Break cycle if we reference ourselves
    284       if self in visited:
    285         return [my_min]
    286 
    287       visited |= set([self])
    288 
    289       # Files inherit all their releases from items in the file
    290       if self.IsA('AST', 'File'):
    291         my_releases = set()
    292 
    293       # Visit all children
    294       child_releases = set()
    295 
    296       # Exclude sibling results from parent visited set
    297       cur_visits = visited
    298 
    299       for child in self._children:
    300         child_releases |= set(child._GetReleaseList(releases, cur_visits))
    301         visited |= set(child_releases)
    302 
    303       # Visit my type
    304       type_releases = set()
    305       if self.typelist:
    306         type_list = self.typelist.GetReleases()
    307         for typenode in type_list:
    308           type_releases |= set(typenode._GetReleaseList(releases, cur_visits))
    309 
    310         type_release_list = sorted(type_releases)
    311         if my_min < type_release_list[0]:
    312           type_node = type_list[0]
    313           self.Error('requires %s in %s which is undefined at %s.' % (
    314               type_node, type_node._filename, my_min))
    315 
    316       for rel in child_releases | type_releases:
    317         if rel >= my_min and rel <= my_max:
    318           my_releases |= set([rel])
    319 
    320       self.releases = sorted(my_releases)
    321     return self.releases
    322 
    323   def BuildReleaseMap(self, releases):
    324     unique_list = self._GetReleaseList(releases)
    325     _, my_max = self.GetMinMax(releases)
    326 
    327     self.first_release = {}
    328     last_rel = None
    329     for rel in releases:
    330       if rel in unique_list:
    331         last_rel = rel
    332       self.first_release[rel] = last_rel
    333       if rel == my_max:
    334         last_rel = None
    335 
    336   def SetProperty(self, name, val):
    337     self._property_node.SetProperty(name, val)
    338 
    339   def GetProperty(self, name):
    340     return self._property_node.GetProperty(name)
    341 
    342   def GetPropertyLocal(self, name):
    343     return self._property_node.GetPropertyLocal(name)
    344 
    345   def NodeIsDevOnly(self):
    346     """Returns true iff a node is only in dev channel."""
    347     return self.GetProperty('dev_version') and not self.GetProperty('version')
    348 
    349   def DevInterfaceMatchesStable(self, release):
    350     """Returns true if an interface has an equivalent stable version."""
    351     assert(self.IsA('Interface'))
    352     for child in self.GetListOf('Member'):
    353       unique = child.GetUniqueReleases([release])
    354       if not unique or not child.InReleases([release]):
    355         continue
    356       if child.NodeIsDevOnly():
    357         return False
    358     return True
    359 
    360 
    361 #
    362 # IDLFile
    363 #
    364 # A specialized version of IDLNode which tracks errors and warnings.
    365 #
    366 class IDLFile(IDLNode):
    367   def __init__(self, name, children, errors=0):
    368     attrs = [IDLAttribute('NAME', name),
    369              IDLAttribute('ERRORS', errors)]
    370     if not children:
    371       children = []
    372     IDLNode.__init__(self, 'File', name, 1, 0, attrs + children)
    373     # TODO(teravest): Why do we set release map like this here? This looks
    374     # suspicious...
    375     self.release_map = IDLReleaseMap([('M13', 1.0, 'stable')])
    376 
    377 
    378 #
    379 # Tests
    380 #
    381 def StringTest():
    382   errors = 0
    383   name_str = 'MyName'
    384   text_str = 'MyNode(%s)' % name_str
    385   name_node = IDLAttribute('NAME', name_str)
    386   node = IDLNode('MyNode', 'no file', 1, 0, [name_node])
    387   if node.GetName() != name_str:
    388     ErrOut.Log('GetName returned >%s< not >%s<' % (node.GetName(), name_str))
    389     errors += 1
    390   if node.GetProperty('NAME') != name_str:
    391     ErrOut.Log('Failed to get name property.')
    392     errors += 1
    393   if str(node) != text_str:
    394     ErrOut.Log('str() returned >%s< not >%s<' % (str(node), text_str))
    395     errors += 1
    396   if not errors:
    397     InfoOut.Log('Passed StringTest')
    398   return errors
    399 
    400 
    401 def ChildTest():
    402   errors = 0
    403   child = IDLNode('child', 'no file', 1, 0)
    404   parent = IDLNode('parent', 'no file', 1, 0, [child])
    405 
    406   if child.parent != parent:
    407     ErrOut.Log('Failed to connect parent.')
    408     errors += 1
    409 
    410   if [child] != parent.GetChildren():
    411     ErrOut.Log('Failed GetChildren.')
    412     errors += 1
    413 
    414   if child != parent.GetOneOf('child'):
    415     ErrOut.Log('Failed GetOneOf(child)')
    416     errors += 1
    417 
    418   if parent.GetOneOf('bogus'):
    419     ErrOut.Log('Failed GetOneOf(bogus)')
    420     errors += 1
    421 
    422   if not parent.IsA('parent'):
    423     ErrOut.Log('Expecting parent type')
    424     errors += 1
    425 
    426   parent = IDLNode('parent', 'no file', 1, 0, [child, child])
    427   if [child, child] != parent.GetChildren():
    428     ErrOut.Log('Failed GetChildren2.')
    429     errors += 1
    430 
    431   if not errors:
    432     InfoOut.Log('Passed ChildTest')
    433   return errors
    434 
    435 
    436 def Main():
    437   errors = StringTest()
    438   errors += ChildTest()
    439 
    440   if errors:
    441     ErrOut.Log('IDLNode failed with %d errors.' % errors)
    442     return  -1
    443   return 0
    444 
    445 if __name__ == '__main__':
    446   sys.exit(Main())
    447 
    448