idl_node.py revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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 19import hashlib 20import sys 21 22from idl_log import ErrOut, InfoOut, WarnOut 23from idl_propertynode import IDLPropertyNode 24from idl_namespace import IDLNamespace 25from idl_release import IDLRelease, IDLReleaseMap 26 27 28# IDLAttribute 29# 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. 32# 33class IDLAttribute(object): 34 def __init__(self, name, value): 35 self.cls = 'ExtAttribute' 36 self.name = name 37 self.value = value 38 39 def __str__(self): 40 return '%s=%s' % (self.name, self.value) 41 42# 43# IDLNode 44# 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 48# version aware. 49# 50class IDLNode(IDLRelease): 51 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']) 55 56 show_versions = False 57 def __init__(self, cls, filename, lineno, pos, children=None): 58 # Initialize with no starting or ending Version 59 IDLRelease.__init__(self, None, None) 60 61 self.cls = cls 62 self.lineno = lineno 63 self.pos = pos 64 self.filename = filename 65 self.filenode = None 66 self.hashes = {} 67 self.deps = {} 68 self.errors = 0 69 self.namespace = None 70 self.typelist = None 71 self.parent = None 72 self.property_node = IDLPropertyNode() 73 74 # A list of unique releases for this node 75 self.releases = None 76 77 # A map from any release, to the first unique release 78 self.first_release = None 79 80 # self.children is a list of children ordered as defined 81 self.children = [] 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) 89 else: 90 self.AddChild(child) 91 92# 93# String related functions 94# 95# 96 97 # Return a string representation of this node 98 def __str__(self): 99 name = self.GetName() 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) 104 105 # Return file and line number for where node was defined 106 def Location(self): 107 return '%s(%d)' % (self.filename, self.lineno) 108 109 # Log an error for this object 110 def Error(self, msg): 111 self.errors += 1 112 ErrOut.LogLine(self.filename, self.lineno, 0, ' %s %s' % 113 (str(self), msg)) 114 if self.filenode: 115 errcnt = self.filenode.GetProperty('ERRORS', 0) 116 self.filenode.SetProperty('ERRORS', errcnt + 1) 117 118 # Log a warning for this object 119 def Warning(self, msg): 120 WarnOut.LogLine(self.filename, self.lineno, 0, ' %s %s' % 121 (str(self), msg)) 122 123 def GetName(self): 124 return self.GetProperty('NAME') 125 126 def GetNameVersion(self): 127 name = self.GetProperty('NAME', default='') 128 ver = IDLRelease.__str__(self) 129 return '%s%s' % (name, ver) 130 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']: 134 is_comment = True 135 else: 136 is_comment = False 137 138 # Skip this node if it's a comment, and we are not printing comments 139 if not comments and is_comment: return 140 141 tab = ''.rjust(depth * 2) 142 if is_comment: 143 out.write('%sComment\n' % tab) 144 for line in self.GetName().split('\n'): 145 out.write('%s "%s"\n' % (tab, line)) 146 else: 147 ver = IDLRelease.__str__(self) 148 if self.releases: 149 release_list = ': ' + ' '.join(self.releases) 150 else: 151 release_list = ': undefined' 152 out.write('%s%s%s%s\n' % (tab, self, ver, release_list)) 153 if self.typelist: 154 out.write('%s Typelist: %s\n' % (tab, self.typelist.GetReleases()[0])) 155 properties = self.property_node.GetPropertyList() 156 if properties: 157 out.write('%s Properties\n' % tab) 158 for p in properties: 159 if is_comment and p == 'NAME': 160 # Skip printing the name for comments, since we printed above already 161 continue 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) 165 166# 167# Search related functions 168# 169 # Check if node is of a given type 170 def IsA(self, *typelist): 171 if self.cls in typelist: return True 172 return False 173 174 # Get a list of objects for this key 175 def GetListOf(self, *keys): 176 out = [] 177 for child in self.children: 178 if child.cls in keys: out.append(child) 179 return out 180 181 def GetOneOf(self, *keys): 182 out = self.GetListOf(*keys) 183 if out: return out[0] 184 return None 185 186 def SetParent(self, parent): 187 self.property_node.AddParent(parent) 188 self.parent = parent 189 190 def AddChild(self, node): 191 node.SetParent(self) 192 self.children.append(node) 193 194 # Get a list of all children 195 def GetChildren(self): 196 return self.children 197 198 # Get a list of all children of a given version 199 def GetChildrenVersion(self, version): 200 out = [] 201 for child in self.children: 202 if child.IsVersion(version): out.append(child) 203 return out 204 205 # Get a list of all children in a given range 206 def GetChildrenRange(self, vmin, vmax): 207 out = [] 208 for child in self.children: 209 if child.IsRange(vmin, vmax): out.append(child) 210 return out 211 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) 216 return node 217 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) 222 return nodes 223 224 def GetType(self, release): 225 if not self.typelist: return None 226 return self.typelist.FindRelease(release) 227 228 def GetHash(self, release): 229 hashval = self.hashes.get(release, None) 230 if hashval is 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) 237 if typeref: 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): 242 continue 243 hashval.update( child.GetHash(release) ) 244 self.hashes[release] = hashval 245 return hashval.hexdigest() 246 247 def GetDeps(self, release, visited=None): 248 visited = visited or set() 249 250 # If this release is not valid for this object, then done. 251 if not self.IsRelease(release) or self.IsA('Comment', 'Copyright'): 252 return set([]) 253 254 # If we have cached the info for this release, return the cached value 255 deps = self.deps.get(release, None) 256 if deps is not None: 257 return deps 258 259 # If we are already visited, then return 260 if self in visited: 261 return set([self]) 262 263 # Otherwise, build the dependency list 264 visited |= set([self]) 265 deps = set([self]) 266 267 # Get child deps 268 for child in self.GetChildren(): 269 deps |= child.GetDeps(release, visited) 270 visited |= set(deps) 271 272 # Get type deps 273 typeref = self.GetType(release) 274 if typeref: 275 deps |= typeref.GetDeps(release, visited) 276 277 self.deps[release] = deps 278 return deps 279 280 def GetVersion(self, release): 281 filenode = self.GetProperty('FILE') 282 if not filenode: 283 return None 284 return filenode.release_map.GetVersion(release) 285 286 def GetUniqueReleases(self, releases): 287 """Return the unique set of first releases corresponding to input 288 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]: 293 return [] 294 295 out = set() 296 for rel in releases: 297 remapped = self.first_release[rel] 298 if not remapped: continue 299 out |= set([remapped]) 300 out = sorted(out) 301 return out 302 303 304 def GetRelease(self, version): 305 filenode = self.GetProperty('FILE') 306 if not filenode: 307 return None 308 return filenode.release_map.GetRelease(version) 309 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'): 319 continue 320 my_releases |= child.GetReleases(releases) 321 self.releases = my_releases 322 return self.releases 323 324 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'): 330 self.releases = [] 331 return self.releases 332 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) 336 337 if my_max != releases[-1]: 338 my_releases = set([my_min, my_max]) 339 else: 340 my_releases = set([my_min]) 341 342 # Break cycle if we reference ourselves 343 if self in visited: 344 return [my_min] 345 346 visited |= set([self]) 347 348 # Files inherit all their releases from items in the file 349 if self.IsA('AST', 'File'): 350 my_releases = set() 351 352 # Visit all children 353 child_releases = set() 354 355 # Exclude sibling results from parent visited set 356 cur_visits = visited 357 358 for child in self.children: 359 child_releases |= set(child._GetReleaseList(releases, cur_visits)) 360 visited |= set(child_releases) 361 362 # Visit my type 363 type_releases = set() 364 if self.typelist: 365 type_list = self.typelist.GetReleases() 366 for typenode in type_list: 367 type_releases |= set(typenode._GetReleaseList(releases, cur_visits)) 368 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)) 374 375 for rel in child_releases | type_releases: 376 if rel >= my_min and rel <= my_max: 377 my_releases |= set([rel]) 378 379 self.releases = sorted(my_releases) 380 return self.releases 381 382 def GetReleaseList(self): 383 return self.releases 384 385 def BuildReleaseMap(self, releases): 386 unique_list = self._GetReleaseList(releases) 387 my_min, my_max = self.GetMinMax(releases) 388 389 self.first_release = {} 390 last_rel = None 391 for rel in releases: 392 if rel in unique_list: 393 last_rel = rel 394 self.first_release[rel] = last_rel 395 if rel == my_max: 396 last_rel = None 397 398 def SetProperty(self, name, val): 399 self.property_node.SetProperty(name, val) 400 401 def GetProperty(self, name, default=None): 402 return self.property_node.GetProperty(name, default) 403 404 def Traverse(self, data, func): 405 func(self, data) 406 for child in self.children: 407 child.Traverse(data, func) 408 409 410# 411# IDLFile 412# 413# A specialized version of IDLNode which tracks errors and warnings. 414# 415class 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)]) 422 423 424# 425# Tests 426# 427def StringTest(): 428 errors = 0 429 name_str = 'MyName' 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)) 435 errors += 1 436 if node.GetProperty('NAME') != name_str: 437 ErrOut.Log('Failed to get name property.') 438 errors += 1 439 if str(node) != text_str: 440 ErrOut.Log('str() returned >%s< not >%s<' % (str(node), text_str)) 441 errors += 1 442 if not errors: InfoOut.Log('Passed StringTest') 443 return errors 444 445 446def ChildTest(): 447 errors = 0 448 child = IDLNode('child', 'no file', 1, 0) 449 parent = IDLNode('parent', 'no file', 1, 0, [child]) 450 451 if child.parent != parent: 452 ErrOut.Log('Failed to connect parent.') 453 errors += 1 454 455 if [child] != parent.GetChildren(): 456 ErrOut.Log('Failed GetChildren.') 457 errors += 1 458 459 if child != parent.GetOneOf('child'): 460 ErrOut.Log('Failed GetOneOf(child)') 461 errors += 1 462 463 if parent.GetOneOf('bogus'): 464 ErrOut.Log('Failed GetOneOf(bogus)') 465 errors += 1 466 467 if not parent.IsA('parent'): 468 ErrOut.Log('Expecting parent type') 469 errors += 1 470 471 parent = IDLNode('parent', 'no file', 1, 0, [child, child]) 472 if [child, child] != parent.GetChildren(): 473 ErrOut.Log('Failed GetChildren2.') 474 errors += 1 475 476 if not errors: InfoOut.Log('Passed ChildTest') 477 return errors 478 479 480def Main(): 481 errors = StringTest() 482 errors += ChildTest() 483 484 if errors: 485 ErrOut.Log('IDLNode failed with %d errors.' % errors) 486 return -1 487 return 0 488 489if __name__ == '__main__': 490 sys.exit(Main()) 491 492