Fix DisownOpener and related tests.
[chromium-blink-merge.git] / ppapi / generators / idl_node.py
blob55b24d19b0113350b25960feec64fa5c4fefe8ff
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.
6 """Nodes for PPAPI IDL AST"""
9 # IDL Node
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.
19 import sys
21 from idl_log import ErrOut, InfoOut, WarnOut
22 from idl_propertynode import IDLPropertyNode
23 from idl_release import IDLRelease, IDLReleaseMap
26 # IDLAttribute
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.
31 class IDLAttribute(object):
32 def __init__(self, name, value):
33 self.cls = 'ExtAttribute'
34 self.name = name
35 self.value = value
37 def __str__(self):
38 return '%s=%s' % (self.name, self.value)
41 # IDLNode
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.
48 class IDLNode(IDLRelease):
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'])
54 def __init__(self, cls, filename, lineno, pos, children=None):
55 # Initialize with no starting or ending Version
56 IDLRelease.__init__(self, None, None)
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
70 # A list of unique releases for this node
71 self.releases = None
73 # A map from any release, to the first unique release
74 self.first_release = None
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)
88 def __str__(self):
89 name = self.GetName()
90 if name is None:
91 name = ''
92 return '%s(%s)' % (self.cls, name)
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)
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)
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))
115 def GetName(self):
116 return self.GetProperty('NAME')
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
125 # Skip this node if it's a comment, and we are not printing comments
126 if not comments and is_comment:
127 return
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)
154 def IsA(self, *typelist):
155 """Check if node is of a given type."""
156 return self.cls in typelist
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
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
173 def SetParent(self, parent):
174 self._property_node.AddParent(parent)
175 self.parent = parent
177 def AddChild(self, node):
178 node.SetParent(self)
179 self._children.append(node)
181 # Get a list of all children
182 def GetChildren(self):
183 return self._children
185 def GetType(self, release):
186 if not self.typelist:
187 return None
188 return self.typelist.FindRelease(release)
190 def GetDeps(self, release, visited=None):
191 visited = visited or set()
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([])
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
202 # If we are already visited, then return
203 if self in visited:
204 return set([self])
206 # Otherwise, build the dependency list
207 visited |= set([self])
208 deps = set([self])
210 # Get child deps
211 for child in self.GetChildren():
212 deps |= child.GetDeps(release, visited)
213 visited |= set(deps)
215 # Get type deps
216 typeref = self.GetType(release)
217 if typeref:
218 deps |= typeref.GetDeps(release, visited)
220 self._deps[release] = deps
221 return deps
223 def GetVersion(self, release):
224 filenode = self.GetProperty('FILE')
225 if not filenode:
226 return None
227 return filenode.release_map.GetVersion(release)
229 def GetUniqueReleases(self, releases):
230 """Return the unique set of first releases corresponding to input
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 []
238 out = set()
239 for rel in releases:
240 remapped = self.first_release[rel]
241 if not remapped:
242 continue
243 out |= set([remapped])
245 # Cache the most recent set of unique_releases
246 self._unique_releases = sorted(out)
247 return self._unique_releases
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
256 def GetRelease(self, version):
257 filenode = self.GetProperty('FILE')
258 if not filenode:
259 return None
260 return filenode.release_map.GetRelease(version)
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
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)
274 if my_max != releases[-1]:
275 my_releases = set([my_min, my_max])
276 else:
277 my_releases = set([my_min])
279 r = self.GetRelease(self.GetProperty('version'))
280 if not r in my_releases:
281 my_releases |= set([r])
283 # Break cycle if we reference ourselves
284 if self in visited:
285 return [my_min]
287 visited |= set([self])
289 # Files inherit all their releases from items in the file
290 if self.IsA('AST', 'File'):
291 my_releases = set()
293 # Visit all children
294 child_releases = set()
296 # Exclude sibling results from parent visited set
297 cur_visits = visited
299 for child in self._children:
300 child_releases |= set(child._GetReleaseList(releases, cur_visits))
301 visited |= set(child_releases)
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))
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))
316 for rel in child_releases | type_releases:
317 if rel >= my_min and rel <= my_max:
318 my_releases |= set([rel])
320 self.releases = sorted(my_releases)
321 return self.releases
323 def BuildReleaseMap(self, releases):
324 unique_list = self._GetReleaseList(releases)
325 _, my_max = self.GetMinMax(releases)
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
336 def SetProperty(self, name, val):
337 self._property_node.SetProperty(name, val)
339 def GetProperty(self, name):
340 return self._property_node.GetProperty(name)
342 def GetPropertyLocal(self, name):
343 return self._property_node.GetPropertyLocal(name)
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')
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
362 # IDLFile
364 # A specialized version of IDLNode which tracks errors and warnings.
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')])
379 # Tests
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
401 def ChildTest():
402 errors = 0
403 child = IDLNode('child', 'no file', 1, 0)
404 parent = IDLNode('parent', 'no file', 1, 0, [child])
406 if child.parent != parent:
407 ErrOut.Log('Failed to connect parent.')
408 errors += 1
410 if [child] != parent.GetChildren():
411 ErrOut.Log('Failed GetChildren.')
412 errors += 1
414 if child != parent.GetOneOf('child'):
415 ErrOut.Log('Failed GetOneOf(child)')
416 errors += 1
418 if parent.GetOneOf('bogus'):
419 ErrOut.Log('Failed GetOneOf(bogus)')
420 errors += 1
422 if not parent.IsA('parent'):
423 ErrOut.Log('Expecting parent type')
424 errors += 1
426 parent = IDLNode('parent', 'no file', 1, 0, [child, child])
427 if [child, child] != parent.GetChildren():
428 ErrOut.Log('Failed GetChildren2.')
429 errors += 1
431 if not errors:
432 InfoOut.Log('Passed ChildTest')
433 return errors
436 def Main():
437 errors = StringTest()
438 errors += ChildTest()
440 if errors:
441 ErrOut.Log('IDLNode failed with %d errors.' % errors)
442 return -1
443 return 0
445 if __name__ == '__main__':
446 sys.exit(Main())