| OLD | NEW |
| (Empty) | |
| 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 hashlib |
| 20 import sys |
| 21 |
| 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 |
| 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 # |
| 33 class 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 # |
| 50 class 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 # |
| 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)]) |
| 422 |
| 423 |
| 424 # |
| 425 # Tests |
| 426 # |
| 427 def 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 |
| 446 def 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 |
| 480 def 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 |
| 489 if __name__ == '__main__': |
| 490 sys.exit(Main()) |
| 491 |
| OLD | NEW |