
Newer pylint warns when files have trailing blank lines. Trim them. Bug: 866772 Change-Id: I20739c2086c1fac0a802c07e9f4baa5f8bbf4291 Reviewed-on: https://chromium-review.googlesource.com/1148000 Reviewed-by: Brad Nelson <bradnelson@chromium.org> Commit-Queue: Mike Frysinger <vapier@chromium.org> Cr-Commit-Position: refs/heads/master@{#577582}
447 lines
13 KiB
Python
Executable File
447 lines
13 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
"""Nodes for PPAPI IDL AST"""
|
|
|
|
#
|
|
# IDL Node
|
|
#
|
|
# IDL Node defines the IDLAttribute and IDLNode objects which are constructed
|
|
# by the parser as it processes the various 'productions'. The IDLAttribute
|
|
# objects are assigned to the IDLNode's property dictionary instead of being
|
|
# applied as children of The IDLNodes, so they do not exist in the final tree.
|
|
# The AST of IDLNodes is the output from the parsing state and will be used
|
|
# as the source data by the various generators.
|
|
#
|
|
|
|
import sys
|
|
|
|
from idl_log import ErrOut, InfoOut, WarnOut
|
|
from idl_propertynode import IDLPropertyNode
|
|
from idl_release import IDLRelease, IDLReleaseMap
|
|
|
|
|
|
# IDLAttribute
|
|
#
|
|
# A temporary object used by the parsing process to hold an Extended Attribute
|
|
# which will be passed as a child to a standard IDLNode.
|
|
#
|
|
class IDLAttribute(object):
|
|
def __init__(self, name, value):
|
|
self.cls = 'ExtAttribute'
|
|
self.name = name
|
|
self.value = value
|
|
|
|
def __str__(self):
|
|
return '%s=%s' % (self.name, self.value)
|
|
|
|
#
|
|
# IDLNode
|
|
#
|
|
# This class implements the AST tree, providing the associations between
|
|
# parents and children. It also contains a namepsace and propertynode to
|
|
# allow for look-ups. IDLNode is derived from IDLRelease, so it is
|
|
# version aware.
|
|
#
|
|
class IDLNode(IDLRelease):
|
|
|
|
# Set of object IDLNode types which have a name and belong in the namespace.
|
|
NamedSet = set(['Enum', 'EnumItem', 'File', 'Function', 'Interface',
|
|
'Member', 'Param', 'Struct', 'Type', 'Typedef'])
|
|
|
|
def __init__(self, cls, filename, lineno, pos, children=None):
|
|
# Initialize with no starting or ending Version
|
|
IDLRelease.__init__(self, None, None)
|
|
|
|
self.cls = cls
|
|
self.lineno = lineno
|
|
self.pos = pos
|
|
self._filename = filename
|
|
self._deps = {}
|
|
self.errors = 0
|
|
self.namespace = None
|
|
self.typelist = None
|
|
self.parent = None
|
|
self._property_node = IDLPropertyNode()
|
|
self._unique_releases = None
|
|
|
|
# A list of unique releases for this node
|
|
self.releases = None
|
|
|
|
# A map from any release, to the first unique release
|
|
self.first_release = None
|
|
|
|
# self._children is a list of children ordered as defined
|
|
self._children = []
|
|
# Process the passed in list of children, placing ExtAttributes into the
|
|
# property dictionary, and nodes into the local child list in order. In
|
|
# addition, add nodes to the namespace if the class is in the NamedSet.
|
|
if children:
|
|
for child in children:
|
|
if child.cls == 'ExtAttribute':
|
|
self.SetProperty(child.name, child.value)
|
|
else:
|
|
self.AddChild(child)
|
|
|
|
def __str__(self):
|
|
name = self.GetName()
|
|
if name is None:
|
|
name = ''
|
|
return '%s(%s)' % (self.cls, name)
|
|
|
|
def Location(self):
|
|
"""Return a file and line number for where this node was defined."""
|
|
return '%s(%d)' % (self._filename, self.lineno)
|
|
|
|
def Error(self, msg):
|
|
"""Log an error for this object."""
|
|
self.errors += 1
|
|
ErrOut.LogLine(self._filename, self.lineno, 0, ' %s %s' %
|
|
(str(self), msg))
|
|
filenode = self.GetProperty('FILE')
|
|
if filenode:
|
|
errcnt = filenode.GetProperty('ERRORS')
|
|
if not errcnt:
|
|
errcnt = 0
|
|
filenode.SetProperty('ERRORS', errcnt + 1)
|
|
|
|
def Warning(self, msg):
|
|
"""Log a warning for this object."""
|
|
WarnOut.LogLine(self._filename, self.lineno, 0, ' %s %s' %
|
|
(str(self), msg))
|
|
|
|
def GetName(self):
|
|
return self.GetProperty('NAME')
|
|
|
|
def Dump(self, depth=0, comments=False, out=sys.stdout):
|
|
"""Dump this object and its children"""
|
|
if self.cls in ['Comment', 'Copyright']:
|
|
is_comment = True
|
|
else:
|
|
is_comment = False
|
|
|
|
# Skip this node if it's a comment, and we are not printing comments
|
|
if not comments and is_comment:
|
|
return
|
|
|
|
tab = ''.rjust(depth * 2)
|
|
if is_comment:
|
|
out.write('%sComment\n' % tab)
|
|
for line in self.GetName().split('\n'):
|
|
out.write('%s "%s"\n' % (tab, line))
|
|
else:
|
|
ver = IDLRelease.__str__(self)
|
|
if self.releases:
|
|
release_list = ': ' + ' '.join(self.releases)
|
|
else:
|
|
release_list = ': undefined'
|
|
out.write('%s%s%s%s\n' % (tab, self, ver, release_list))
|
|
if self.typelist:
|
|
out.write('%s Typelist: %s\n' % (tab, self.typelist.GetReleases()[0]))
|
|
properties = self._property_node.GetPropertyList()
|
|
if properties:
|
|
out.write('%s Properties\n' % tab)
|
|
for p in properties:
|
|
if is_comment and p == 'NAME':
|
|
# Skip printing the name for comments, since we printed above already
|
|
continue
|
|
out.write('%s %s : %s\n' % (tab, p, self.GetProperty(p)))
|
|
for child in self._children:
|
|
child.Dump(depth+1, comments=comments, out=out)
|
|
|
|
def IsA(self, *typelist):
|
|
"""Check if node is of a given type."""
|
|
return self.cls in typelist
|
|
|
|
def GetListOf(self, *keys):
|
|
"""Get a list of objects for the given key(s)."""
|
|
out = []
|
|
for child in self._children:
|
|
if child.cls in keys:
|
|
out.append(child)
|
|
return out
|
|
|
|
def GetOneOf(self, *keys):
|
|
"""Get an object for the given key(s)."""
|
|
out = self.GetListOf(*keys)
|
|
if out:
|
|
return out[0]
|
|
return None
|
|
|
|
def SetParent(self, parent):
|
|
self._property_node.AddParent(parent)
|
|
self.parent = parent
|
|
|
|
def AddChild(self, node):
|
|
node.SetParent(self)
|
|
self._children.append(node)
|
|
|
|
# Get a list of all children
|
|
def GetChildren(self):
|
|
return self._children
|
|
|
|
def GetType(self, release):
|
|
if not self.typelist:
|
|
return None
|
|
return self.typelist.FindRelease(release)
|
|
|
|
def GetDeps(self, release, visited=None):
|
|
visited = visited or set()
|
|
|
|
# If this release is not valid for this object, then done.
|
|
if not self.IsRelease(release) or self.IsA('Comment', 'Copyright'):
|
|
return set([])
|
|
|
|
# If we have cached the info for this release, return the cached value
|
|
deps = self._deps.get(release, None)
|
|
if deps is not None:
|
|
return deps
|
|
|
|
# If we are already visited, then return
|
|
if self in visited:
|
|
return set([self])
|
|
|
|
# Otherwise, build the dependency list
|
|
visited |= set([self])
|
|
deps = set([self])
|
|
|
|
# Get child deps
|
|
for child in self.GetChildren():
|
|
deps |= child.GetDeps(release, visited)
|
|
visited |= set(deps)
|
|
|
|
# Get type deps
|
|
typeref = self.GetType(release)
|
|
if typeref:
|
|
deps |= typeref.GetDeps(release, visited)
|
|
|
|
self._deps[release] = deps
|
|
return deps
|
|
|
|
def GetVersion(self, release):
|
|
filenode = self.GetProperty('FILE')
|
|
if not filenode:
|
|
return None
|
|
return filenode.release_map.GetVersion(release)
|
|
|
|
def GetUniqueReleases(self, releases):
|
|
"""Return the unique set of first releases corresponding to input
|
|
|
|
Since we are returning the corresponding 'first' version for a
|
|
release, we may return a release version prior to the one in the list."""
|
|
my_min, my_max = self.GetMinMax(releases)
|
|
if my_min > releases[-1] or my_max < releases[0]:
|
|
return []
|
|
|
|
out = set()
|
|
for rel in releases:
|
|
remapped = self.first_release[rel]
|
|
if not remapped:
|
|
continue
|
|
out |= set([remapped])
|
|
|
|
# Cache the most recent set of unique_releases
|
|
self._unique_releases = sorted(out)
|
|
return self._unique_releases
|
|
|
|
def LastRelease(self, release):
|
|
# Get the most recent release from the most recently generated set of
|
|
# cached unique releases.
|
|
if self._unique_releases and self._unique_releases[-1] > release:
|
|
return False
|
|
return True
|
|
|
|
def GetRelease(self, version):
|
|
filenode = self.GetProperty('FILE')
|
|
if not filenode:
|
|
return None
|
|
return filenode.release_map.GetRelease(version)
|
|
|
|
def _GetReleaseList(self, releases, visited=None):
|
|
visited = visited or set()
|
|
if not self.releases:
|
|
# If we are unversionable, then return first available release
|
|
if self.IsA('Comment', 'Copyright', 'Label'):
|
|
self.releases = []
|
|
return self.releases
|
|
|
|
# Generate the first and if deprecated within this subset, the
|
|
# last release for this node
|
|
my_min, my_max = self.GetMinMax(releases)
|
|
|
|
if my_max != releases[-1]:
|
|
my_releases = set([my_min, my_max])
|
|
else:
|
|
my_releases = set([my_min])
|
|
|
|
r = self.GetRelease(self.GetProperty('version'))
|
|
if not r in my_releases:
|
|
my_releases |= set([r])
|
|
|
|
# Break cycle if we reference ourselves
|
|
if self in visited:
|
|
return [my_min]
|
|
|
|
visited |= set([self])
|
|
|
|
# Files inherit all their releases from items in the file
|
|
if self.IsA('AST', 'File'):
|
|
my_releases = set()
|
|
|
|
# Visit all children
|
|
child_releases = set()
|
|
|
|
# Exclude sibling results from parent visited set
|
|
cur_visits = visited
|
|
|
|
for child in self._children:
|
|
child_releases |= set(child._GetReleaseList(releases, cur_visits))
|
|
visited |= set(child_releases)
|
|
|
|
# Visit my type
|
|
type_releases = set()
|
|
if self.typelist:
|
|
type_list = self.typelist.GetReleases()
|
|
for typenode in type_list:
|
|
type_releases |= set(typenode._GetReleaseList(releases, cur_visits))
|
|
|
|
type_release_list = sorted(type_releases)
|
|
if my_min < type_release_list[0]:
|
|
type_node = type_list[0]
|
|
self.Error('requires %s in %s which is undefined at %s.' % (
|
|
type_node, type_node._filename, my_min))
|
|
|
|
for rel in child_releases | type_releases:
|
|
if rel >= my_min and rel <= my_max:
|
|
my_releases |= set([rel])
|
|
|
|
self.releases = sorted(my_releases)
|
|
return self.releases
|
|
|
|
def BuildReleaseMap(self, releases):
|
|
unique_list = self._GetReleaseList(releases)
|
|
_, my_max = self.GetMinMax(releases)
|
|
|
|
self.first_release = {}
|
|
last_rel = None
|
|
for rel in releases:
|
|
if rel in unique_list:
|
|
last_rel = rel
|
|
self.first_release[rel] = last_rel
|
|
if rel == my_max:
|
|
last_rel = None
|
|
|
|
def SetProperty(self, name, val):
|
|
self._property_node.SetProperty(name, val)
|
|
|
|
def GetProperty(self, name):
|
|
return self._property_node.GetProperty(name)
|
|
|
|
def GetPropertyLocal(self, name):
|
|
return self._property_node.GetPropertyLocal(name)
|
|
|
|
def NodeIsDevOnly(self):
|
|
"""Returns true iff a node is only in dev channel."""
|
|
return self.GetProperty('dev_version') and not self.GetProperty('version')
|
|
|
|
def DevInterfaceMatchesStable(self, release):
|
|
"""Returns true if an interface has an equivalent stable version."""
|
|
assert(self.IsA('Interface'))
|
|
for child in self.GetListOf('Member'):
|
|
unique = child.GetUniqueReleases([release])
|
|
if not unique or not child.InReleases([release]):
|
|
continue
|
|
if child.NodeIsDevOnly():
|
|
return False
|
|
return True
|
|
|
|
|
|
#
|
|
# IDLFile
|
|
#
|
|
# A specialized version of IDLNode which tracks errors and warnings.
|
|
#
|
|
class IDLFile(IDLNode):
|
|
def __init__(self, name, children, errors=0):
|
|
attrs = [IDLAttribute('NAME', name),
|
|
IDLAttribute('ERRORS', errors)]
|
|
if not children:
|
|
children = []
|
|
IDLNode.__init__(self, 'File', name, 1, 0, attrs + children)
|
|
# TODO(teravest): Why do we set release map like this here? This looks
|
|
# suspicious...
|
|
self.release_map = IDLReleaseMap([('M13', 1.0, 'stable')])
|
|
|
|
|
|
#
|
|
# Tests
|
|
#
|
|
def StringTest():
|
|
errors = 0
|
|
name_str = 'MyName'
|
|
text_str = 'MyNode(%s)' % name_str
|
|
name_node = IDLAttribute('NAME', name_str)
|
|
node = IDLNode('MyNode', 'no file', 1, 0, [name_node])
|
|
if node.GetName() != name_str:
|
|
ErrOut.Log('GetName returned >%s< not >%s<' % (node.GetName(), name_str))
|
|
errors += 1
|
|
if node.GetProperty('NAME') != name_str:
|
|
ErrOut.Log('Failed to get name property.')
|
|
errors += 1
|
|
if str(node) != text_str:
|
|
ErrOut.Log('str() returned >%s< not >%s<' % (str(node), text_str))
|
|
errors += 1
|
|
if not errors:
|
|
InfoOut.Log('Passed StringTest')
|
|
return errors
|
|
|
|
|
|
def ChildTest():
|
|
errors = 0
|
|
child = IDLNode('child', 'no file', 1, 0)
|
|
parent = IDLNode('parent', 'no file', 1, 0, [child])
|
|
|
|
if child.parent != parent:
|
|
ErrOut.Log('Failed to connect parent.')
|
|
errors += 1
|
|
|
|
if [child] != parent.GetChildren():
|
|
ErrOut.Log('Failed GetChildren.')
|
|
errors += 1
|
|
|
|
if child != parent.GetOneOf('child'):
|
|
ErrOut.Log('Failed GetOneOf(child)')
|
|
errors += 1
|
|
|
|
if parent.GetOneOf('bogus'):
|
|
ErrOut.Log('Failed GetOneOf(bogus)')
|
|
errors += 1
|
|
|
|
if not parent.IsA('parent'):
|
|
ErrOut.Log('Expecting parent type')
|
|
errors += 1
|
|
|
|
parent = IDLNode('parent', 'no file', 1, 0, [child, child])
|
|
if [child, child] != parent.GetChildren():
|
|
ErrOut.Log('Failed GetChildren2.')
|
|
errors += 1
|
|
|
|
if not errors:
|
|
InfoOut.Log('Passed ChildTest')
|
|
return errors
|
|
|
|
|
|
def Main():
|
|
errors = StringTest()
|
|
errors += ChildTest()
|
|
|
|
if errors:
|
|
ErrOut.Log('IDLNode failed with %d errors.' % errors)
|
|
return -1
|
|
return 0
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(Main())
|