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 sys 20 21from idl_log import ErrOut, InfoOut, WarnOut 22from idl_propertynode import IDLPropertyNode 23from idl_release import IDLRelease, IDLReleaseMap 24 25 26# IDLAttribute 27# 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. 30# 31class IDLAttribute(object): 32 def __init__(self, name, value): 33 self.cls = 'ExtAttribute' 34 self.name = name 35 self.value = value 36 37 def __str__(self): 38 return '%s=%s' % (self.name, self.value) 39 40# 41# IDLNode 42# 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. 47# 48class IDLNode(IDLRelease): 49 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']) 53 54 def __init__(self, cls, filename, lineno, pos, children=None): 55 # Initialize with no starting or ending Version 56 IDLRelease.__init__(self, None, None) 57 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 69 70 # A list of unique releases for this node 71 self.releases = None 72 73 # A map from any release, to the first unique release 74 self.first_release = None 75 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) 87 88 def __str__(self): 89 name = self.GetName() 90 if name is None: 91 name = '' 92 return '%s(%s)' % (self.cls, name) 93 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) 97 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) 109 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)) 114 115 def GetName(self): 116 return self.GetProperty('NAME') 117 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 124 125 # Skip this node if it's a comment, and we are not printing comments 126 if not comments and is_comment: 127 return 128 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) 153 154 def IsA(self, *typelist): 155 """Check if node is of a given type.""" 156 return self.cls in typelist 157 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 165 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 172 173 def SetParent(self, parent): 174 self._property_node.AddParent(parent) 175 self.parent = parent 176 177 def AddChild(self, node): 178 node.SetParent(self) 179 self._children.append(node) 180 181 # Get a list of all children 182 def GetChildren(self): 183 return self._children 184 185 def GetType(self, release): 186 if not self.typelist: 187 return None 188 return self.typelist.FindRelease(release) 189 190 def GetDeps(self, release, visited=None): 191 visited = visited or set() 192 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([]) 196 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 201 202 # If we are already visited, then return 203 if self in visited: 204 return set([self]) 205 206 # Otherwise, build the dependency list 207 visited |= set([self]) 208 deps = set([self]) 209 210 # Get child deps 211 for child in self.GetChildren(): 212 deps |= child.GetDeps(release, visited) 213 visited |= set(deps) 214 215 # Get type deps 216 typeref = self.GetType(release) 217 if typeref: 218 deps |= typeref.GetDeps(release, visited) 219 220 self._deps[release] = deps 221 return deps 222 223 def GetVersion(self, release): 224 filenode = self.GetProperty('FILE') 225 if not filenode: 226 return None 227 return filenode.release_map.GetVersion(release) 228 229 def GetUniqueReleases(self, releases): 230 """Return the unique set of first releases corresponding to input 231 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 [] 237 238 out = set() 239 for rel in releases: 240 remapped = self.first_release[rel] 241 if not remapped: 242 continue 243 out |= set([remapped]) 244 245 # Cache the most recent set of unique_releases 246 self._unique_releases = sorted(out) 247 return self._unique_releases 248 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 255 256 def GetRelease(self, version): 257 filenode = self.GetProperty('FILE') 258 if not filenode: 259 return None 260 return filenode.release_map.GetRelease(version) 261 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 269 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) 273 274 if my_max != releases[-1]: 275 my_releases = set([my_min, my_max]) 276 else: 277 my_releases = set([my_min]) 278 279 r = self.GetRelease(self.GetProperty('version')) 280 if not r in my_releases: 281 my_releases |= set([r]) 282 283 # Break cycle if we reference ourselves 284 if self in visited: 285 return [my_min] 286 287 visited |= set([self]) 288 289 # Files inherit all their releases from items in the file 290 if self.IsA('AST', 'File'): 291 my_releases = set() 292 293 # Visit all children 294 child_releases = set() 295 296 # Exclude sibling results from parent visited set 297 cur_visits = visited 298 299 for child in self._children: 300 child_releases |= set(child._GetReleaseList(releases, cur_visits)) 301 visited |= set(child_releases) 302 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)) 309 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)) 315 316 for rel in child_releases | type_releases: 317 if rel >= my_min and rel <= my_max: 318 my_releases |= set([rel]) 319 320 self.releases = sorted(my_releases) 321 return self.releases 322 323 def BuildReleaseMap(self, releases): 324 unique_list = self._GetReleaseList(releases) 325 _, my_max = self.GetMinMax(releases) 326 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 335 336 def SetProperty(self, name, val): 337 self._property_node.SetProperty(name, val) 338 339 def GetProperty(self, name): 340 return self._property_node.GetProperty(name) 341 342 def GetPropertyLocal(self, name): 343 return self._property_node.GetPropertyLocal(name) 344 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') 348 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 359 360 361# 362# IDLFile 363# 364# A specialized version of IDLNode which tracks errors and warnings. 365# 366class 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')]) 376 377 378# 379# Tests 380# 381def 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 399 400 401def ChildTest(): 402 errors = 0 403 child = IDLNode('child', 'no file', 1, 0) 404 parent = IDLNode('parent', 'no file', 1, 0, [child]) 405 406 if child.parent != parent: 407 ErrOut.Log('Failed to connect parent.') 408 errors += 1 409 410 if [child] != parent.GetChildren(): 411 ErrOut.Log('Failed GetChildren.') 412 errors += 1 413 414 if child != parent.GetOneOf('child'): 415 ErrOut.Log('Failed GetOneOf(child)') 416 errors += 1 417 418 if parent.GetOneOf('bogus'): 419 ErrOut.Log('Failed GetOneOf(bogus)') 420 errors += 1 421 422 if not parent.IsA('parent'): 423 ErrOut.Log('Expecting parent type') 424 errors += 1 425 426 parent = IDLNode('parent', 'no file', 1, 0, [child, child]) 427 if [child, child] != parent.GetChildren(): 428 ErrOut.Log('Failed GetChildren2.') 429 errors += 1 430 431 if not errors: 432 InfoOut.Log('Passed ChildTest') 433 return errors 434 435 436def Main(): 437 errors = StringTest() 438 errors += ChildTest() 439 440 if errors: 441 ErrOut.Log('IDLNode failed with %d errors.' % errors) 442 return -1 443 return 0 444 445if __name__ == '__main__': 446 sys.exit(Main()) 447 448