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"""
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.
22 from idl_log
import ErrOut
, InfoOut
, WarnOut
23 from idl_propertynode
import IDLPropertyNode
24 from idl_namespace
import IDLNamespace
25 from idl_release
import IDLRelease
, IDLReleaseMap
30 # A temporary object used by the parsing process to hold an Extended Attribute
31 # which will be passed as a child to a standard IDLNode.
33 class IDLAttribute(object):
34 def __init__(self
, name
, value
):
35 self
.cls
= 'ExtAttribute'
40 return '%s=%s' % (self
.name
, self
.value
)
45 # This class implements the AST tree, providing the associations between
46 # parents and children. It also contains a namepsace and propertynode to
47 # allow for look-ups. IDLNode is derived from IDLRelease, so it is
50 class IDLNode(IDLRelease
):
52 # Set of object IDLNode types which have a name and belong in the namespace.
53 NamedSet
= set(['Enum', 'EnumItem', 'File', 'Function', 'Interface',
54 'Member', 'Param', 'Struct', 'Type', 'Typedef'])
57 def __init__(self
, cls
, filename
, lineno
, pos
, children
=None):
58 # Initialize with no starting or ending Version
59 IDLRelease
.__init
__(self
, None, None)
64 self
.filename
= filename
72 self
.property_node
= IDLPropertyNode()
74 # A list of unique releases for this node
77 # A map from any release, to the first unique release
78 self
.first_release
= None
80 # self.children is a list of children ordered as defined
82 # Process the passed in list of children, placing ExtAttributes into the
83 # property dictionary, and nodes into the local child list in order. In
84 # addition, add nodes to the namespace if the class is in the NamedSet.
85 if not children
: children
= []
86 for child
in children
:
87 if child
.cls
== 'ExtAttribute':
88 self
.SetProperty(child
.name
, child
.value
)
93 # String related functions
97 # Return a string representation of this node
100 ver
= IDLRelease
.__str
__(self
)
101 if name
is None: name
= ''
102 if not IDLNode
.show_versions
: ver
= ''
103 return '%s(%s%s)' % (self
.cls
, name
, ver
)
105 # Return file and line number for where node was defined
107 return '%s(%d)' % (self
.filename
, self
.lineno
)
109 # Log an error for this object
110 def Error(self
, msg
):
112 ErrOut
.LogLine(self
.filename
, self
.lineno
, 0, ' %s %s' %
115 errcnt
= self
.filenode
.GetProperty('ERRORS', 0)
116 self
.filenode
.SetProperty('ERRORS', errcnt
+ 1)
118 # Log a warning for this object
119 def Warning(self
, msg
):
120 WarnOut
.LogLine(self
.filename
, self
.lineno
, 0, ' %s %s' %
124 return self
.GetProperty('NAME')
126 def GetNameVersion(self
):
127 name
= self
.GetProperty('NAME', default
='')
128 ver
= IDLRelease
.__str
__(self
)
129 return '%s%s' % (name
, ver
)
131 # Dump this object and its children
132 def Dump(self
, depth
=0, comments
=False, out
=sys
.stdout
):
133 if self
.cls
in ['Comment', 'Copyright']:
138 # Skip this node if it's a comment, and we are not printing comments
139 if not comments
and is_comment
: return
141 tab
= ''.rjust(depth
* 2)
143 out
.write('%sComment\n' % tab
)
144 for line
in self
.GetName().split('\n'):
145 out
.write('%s "%s"\n' % (tab
, line
))
147 ver
= IDLRelease
.__str
__(self
)
149 release_list
= ': ' + ' '.join(self
.releases
)
151 release_list
= ': undefined'
152 out
.write('%s%s%s%s\n' % (tab
, self
, ver
, release_list
))
154 out
.write('%s Typelist: %s\n' % (tab
, self
.typelist
.GetReleases()[0]))
155 properties
= self
.property_node
.GetPropertyList()
157 out
.write('%s Properties\n' % tab
)
159 if is_comment
and p
== 'NAME':
160 # Skip printing the name for comments, since we printed above already
162 out
.write('%s %s : %s\n' % (tab
, p
, self
.GetProperty(p
)))
163 for child
in self
.children
:
164 child
.Dump(depth
+1, comments
=comments
, out
=out
)
167 # Search related functions
169 # Check if node is of a given type
170 def IsA(self
, *typelist
):
171 if self
.cls
in typelist
: return True
174 # Get a list of objects for this key
175 def GetListOf(self
, *keys
):
177 for child
in self
.children
:
178 if child
.cls
in keys
: out
.append(child
)
181 def GetOneOf(self
, *keys
):
182 out
= self
.GetListOf(*keys
)
183 if out
: return out
[0]
186 def SetParent(self
, parent
):
187 self
.property_node
.AddParent(parent
)
190 def AddChild(self
, node
):
192 self
.children
.append(node
)
194 # Get a list of all children
195 def GetChildren(self
):
198 # Get a list of all children of a given version
199 def GetChildrenVersion(self
, version
):
201 for child
in self
.children
:
202 if child
.IsVersion(version
): out
.append(child
)
205 # Get a list of all children in a given range
206 def GetChildrenRange(self
, vmin
, vmax
):
208 for child
in self
.children
:
209 if child
.IsRange(vmin
, vmax
): out
.append(child
)
212 def FindVersion(self
, name
, version
):
213 node
= self
.namespace
.FindNode(name
, version
)
214 if not node
and self
.parent
:
215 node
= self
.parent
.FindVersion(name
, version
)
218 def FindRange(self
, name
, vmin
, vmax
):
219 nodes
= self
.namespace
.FindNodes(name
, vmin
, vmax
)
220 if not nodes
and self
.parent
:
221 nodes
= self
.parent
.FindVersion(name
, vmin
, vmax
)
224 def GetType(self
, release
):
225 if not self
.typelist
: return None
226 return self
.typelist
.FindRelease(release
)
228 def GetHash(self
, release
):
229 hashval
= self
.hashes
.get(release
, None)
231 hashval
= hashlib
.sha1()
232 hashval
.update(self
.cls
)
233 for key
in self
.property_node
.GetPropertyList():
234 val
= self
.GetProperty(key
)
235 hashval
.update('%s=%s' % (key
, str(val
)))
236 typeref
= self
.GetType(release
)
238 hashval
.update(typeref
.GetHash(release
))
239 for child
in self
.GetChildren():
240 if child
.IsA('Copyright', 'Comment', 'Label'): continue
241 if not child
.IsRelease(release
):
243 hashval
.update( child
.GetHash(release
) )
244 self
.hashes
[release
] = hashval
245 return hashval
.hexdigest()
247 def GetDeps(self
, release
, visited
=None):
248 visited
= visited
or set()
250 # If this release is not valid for this object, then done.
251 if not self
.IsRelease(release
) or self
.IsA('Comment', 'Copyright'):
254 # If we have cached the info for this release, return the cached value
255 deps
= self
.deps
.get(release
, None)
259 # If we are already visited, then return
263 # Otherwise, build the dependency list
264 visited |
= set([self
])
268 for child
in self
.GetChildren():
269 deps |
= child
.GetDeps(release
, visited
)
273 typeref
= self
.GetType(release
)
275 deps |
= typeref
.GetDeps(release
, visited
)
277 self
.deps
[release
] = deps
280 def GetVersion(self
, release
):
281 filenode
= self
.GetProperty('FILE')
284 return filenode
.release_map
.GetVersion(release
)
286 def GetUniqueReleases(self
, releases
):
287 """Return the unique set of first releases corresponding to input
289 Since we are returning the corresponding 'first' version for a
290 release, we may return a release version prior to the one in the list."""
291 my_min
, my_max
= self
.GetMinMax(releases
)
292 if my_min
> releases
[-1] or my_max
< releases
[0]:
297 remapped
= self
.first_release
[rel
]
298 if not remapped
: continue
299 out |
= set([remapped
])
304 def GetRelease(self
, version
):
305 filenode
= self
.GetProperty('FILE')
308 return filenode
.release_map
.GetRelease(version
)
310 def _GetReleases(self
, releases
):
311 if not self
.releases
:
312 my_min
, my_max
= self
.GetMinMax(releases
)
313 my_releases
= [my_min
]
314 if my_max
!= releases
[-1]:
315 my_releases
.append(my_max
)
316 my_releases
= set(my_releases
)
317 for child
in self
.GetChildren():
318 if child
.IsA('Copyright', 'Comment', 'Label'):
320 my_releases |
= child
.GetReleases(releases
)
321 self
.releases
= my_releases
325 def _GetReleaseList(self
, releases
, visited
=None):
326 visited
= visited
or set()
327 if not self
.releases
:
328 # If we are unversionable, then return first available release
329 if self
.IsA('Comment', 'Copyright', 'Label'):
333 # Generate the first and if deprecated within this subset, the
334 # last release for this node
335 my_min
, my_max
= self
.GetMinMax(releases
)
337 if my_max
!= releases
[-1]:
338 my_releases
= set([my_min
, my_max
])
340 my_releases
= set([my_min
])
342 # Break cycle if we reference ourselves
346 visited |
= set([self
])
348 # Files inherit all their releases from items in the file
349 if self
.IsA('AST', 'File'):
353 child_releases
= set()
355 # Exclude sibling results from parent visited set
358 for child
in self
.children
:
359 child_releases |
= set(child
._GetReleaseList
(releases
, cur_visits
))
360 visited |
= set(child_releases
)
363 type_releases
= set()
365 type_list
= self
.typelist
.GetReleases()
366 for typenode
in type_list
:
367 type_releases |
= set(typenode
._GetReleaseList
(releases
, cur_visits
))
369 type_release_list
= sorted(type_releases
)
370 if my_min
< type_release_list
[0]:
371 type_node
= type_list
[0]
372 self
.Error('requires %s in %s which is undefined at %s.' % (
373 type_node
, type_node
.filename
, my_min
))
375 for rel
in child_releases | type_releases
:
376 if rel
>= my_min
and rel
<= my_max
:
377 my_releases |
= set([rel
])
379 self
.releases
= sorted(my_releases
)
382 def GetReleaseList(self
):
385 def BuildReleaseMap(self
, releases
):
386 unique_list
= self
._GetReleaseList
(releases
)
387 my_min
, my_max
= self
.GetMinMax(releases
)
389 self
.first_release
= {}
392 if rel
in unique_list
:
394 self
.first_release
[rel
] = last_rel
398 def SetProperty(self
, name
, val
):
399 self
.property_node
.SetProperty(name
, val
)
401 def GetProperty(self
, name
, default
=None):
402 return self
.property_node
.GetProperty(name
, default
)
404 def Traverse(self
, data
, func
):
406 for child
in self
.children
:
407 child
.Traverse(data
, func
)
413 # A specialized version of IDLNode which tracks errors and warnings.
415 class IDLFile(IDLNode
):
416 def __init__(self
, name
, children
, errors
=0):
417 attrs
= [IDLAttribute('NAME', name
),
418 IDLAttribute('ERRORS', errors
)]
419 if not children
: children
= []
420 IDLNode
.__init
__(self
, 'File', name
, 1, 0, attrs
+ children
)
421 self
.release_map
= IDLReleaseMap([('M13', 1.0)])
430 text_str
= 'MyNode(%s)' % name_str
431 name_node
= IDLAttribute('NAME', name_str
)
432 node
= IDLNode('MyNode', 'no file', 1, 0, [name_node
])
433 if node
.GetName() != name_str
:
434 ErrOut
.Log('GetName returned >%s< not >%s<' % (node
.GetName(), name_str
))
436 if node
.GetProperty('NAME') != name_str
:
437 ErrOut
.Log('Failed to get name property.')
439 if str(node
) != text_str
:
440 ErrOut
.Log('str() returned >%s< not >%s<' % (str(node
), text_str
))
442 if not errors
: InfoOut
.Log('Passed StringTest')
448 child
= IDLNode('child', 'no file', 1, 0)
449 parent
= IDLNode('parent', 'no file', 1, 0, [child
])
451 if child
.parent
!= parent
:
452 ErrOut
.Log('Failed to connect parent.')
455 if [child
] != parent
.GetChildren():
456 ErrOut
.Log('Failed GetChildren.')
459 if child
!= parent
.GetOneOf('child'):
460 ErrOut
.Log('Failed GetOneOf(child)')
463 if parent
.GetOneOf('bogus'):
464 ErrOut
.Log('Failed GetOneOf(bogus)')
467 if not parent
.IsA('parent'):
468 ErrOut
.Log('Expecting parent type')
471 parent
= IDLNode('parent', 'no file', 1, 0, [child
, child
])
472 if [child
, child
] != parent
.GetChildren():
473 ErrOut
.Log('Failed GetChildren2.')
476 if not errors
: InfoOut
.Log('Passed ChildTest')
481 errors
= StringTest()
482 errors
+= ChildTest()
485 ErrOut
.Log('IDLNode failed with %d errors.' % errors
)
489 if __name__
== '__main__':