cpp_type_generator.py revision 5821806d5e7f356e8fa4b058a389a808ea183019
1# Copyright (c) 2012 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5from code import Code 6from model import PropertyType 7import any_helper 8import cpp_util 9import operator 10import schema_util 11 12class CppTypeGenerator(object): 13 """Manages the types of properties and provides utilities for getting the 14 C++ type out of a model.Property 15 """ 16 def __init__(self, root_namespace, namespace=None, cpp_namespace=None): 17 """Creates a cpp_type_generator. The given root_namespace should be of the 18 format extensions::api::sub. The generator will generate code suitable for 19 use in the given namespace. 20 """ 21 self._type_namespaces = {} 22 self._root_namespace = root_namespace.split('::') 23 self._cpp_namespaces = {} 24 if namespace and cpp_namespace: 25 self._namespace = namespace 26 self.AddNamespace(namespace, cpp_namespace) 27 28 def AddNamespace(self, namespace, cpp_namespace): 29 """Maps a model.Namespace to its C++ namespace name. All mappings are 30 beneath the root namespace. 31 """ 32 for type_ in namespace.types: 33 if type_ in self._type_namespaces: 34 raise ValueError('Type %s is declared in both %s and %s' % 35 (type_, namespace.name, self._type_namespaces[type_].name)) 36 self._type_namespaces[type_] = namespace 37 self._cpp_namespaces[namespace] = cpp_namespace 38 39 def ExpandParams(self, params): 40 """Returns the given parameters with PropertyType.CHOICES parameters 41 expanded so that each choice is a separate parameter. 42 """ 43 expanded = [] 44 for param in params: 45 if param.type_ == PropertyType.CHOICES: 46 for choice in param.choices.values(): 47 expanded.append(choice) 48 else: 49 expanded.append(param) 50 return expanded 51 52 def GetAllPossibleParameterLists(self, params): 53 """Returns all possible parameter lists for the given set of parameters. 54 Every combination of arguments passed to any of the PropertyType.CHOICES 55 parameters will have a corresponding parameter list returned here. 56 """ 57 if not params: 58 return [[]] 59 partial_parameter_lists = self.GetAllPossibleParameterLists(params[1:]) 60 return [[param] + partial_list 61 for param in self.ExpandParams(params[:1]) 62 for partial_list in partial_parameter_lists] 63 64 def GetCppNamespaceName(self, namespace): 65 """Gets the mapped C++ namespace name for the given namespace relative to 66 the root namespace. 67 """ 68 return self._cpp_namespaces[namespace] 69 70 def GetRootNamespaceStart(self): 71 """Get opening root namespace declarations. 72 """ 73 c = Code() 74 for namespace in self._root_namespace: 75 c.Append('namespace %s {' % namespace) 76 return c 77 78 def GetRootNamespaceEnd(self): 79 """Get closing root namespace declarations. 80 """ 81 c = Code() 82 for namespace in reversed(self._root_namespace): 83 c.Append('} // %s' % namespace) 84 return c 85 86 def GetNamespaceStart(self): 87 """Get opening self._namespace namespace declaration. 88 """ 89 return Code().Append('namespace %s {' % 90 self.GetCppNamespaceName(self._namespace)) 91 92 def GetNamespaceEnd(self): 93 """Get closing self._namespace namespace declaration. 94 """ 95 return Code().Append('} // %s' % 96 self.GetCppNamespaceName(self._namespace)) 97 98 def GetEnumNoneValue(self, prop): 99 """Gets the enum value in the given model.Property indicating no value has 100 been set. 101 """ 102 return '%s_NONE' % self.GetReferencedProperty(prop).unix_name.upper() 103 104 def GetEnumValue(self, prop, enum_value): 105 """Gets the enum value of the given model.Property of the given type. 106 107 e.g VAR_STRING 108 """ 109 return '%s_%s' % (self.GetReferencedProperty(prop).unix_name.upper(), 110 cpp_util.Classname(enum_value.upper())) 111 112 def GetChoicesEnumType(self, prop): 113 """Gets the type of the enum for the given model.Property. 114 115 e.g VarType 116 """ 117 return cpp_util.Classname(prop.name) + 'Type' 118 119 def GetType(self, prop, pad_for_generics=False, wrap_optional=False): 120 return self._GetTypeHelper(prop, pad_for_generics, wrap_optional) 121 122 def GetCompiledType(self, prop, pad_for_generics=False, wrap_optional=False): 123 return self._GetTypeHelper(prop, pad_for_generics, wrap_optional, 124 use_compiled_type=True) 125 126 def _GetTypeHelper(self, prop, pad_for_generics=False, wrap_optional=False, 127 use_compiled_type=False): 128 """Translates a model.Property into its C++ type. 129 130 If REF types from different namespaces are referenced, will resolve 131 using self._type_namespaces. 132 133 Use pad_for_generics when using as a generic to avoid operator ambiguity. 134 135 Use wrap_optional to wrap the type in a scoped_ptr<T> if the Property is 136 optional. 137 138 Use use_compiled_type when converting from prop.type_ to prop.compiled_type. 139 """ 140 cpp_type = None 141 type_ = prop.type_ if not use_compiled_type else prop.compiled_type 142 143 if type_ == PropertyType.REF: 144 dependency_namespace = self._ResolveTypeNamespace(prop.ref_type) 145 if not dependency_namespace: 146 raise KeyError('Cannot find referenced type: %s' % prop.ref_type) 147 if self._namespace != dependency_namespace: 148 cpp_type = '%s::%s' % (self._cpp_namespaces[dependency_namespace], 149 schema_util.StripSchemaNamespace(prop.ref_type)) 150 else: 151 cpp_type = schema_util.StripSchemaNamespace(prop.ref_type) 152 elif type_ == PropertyType.BOOLEAN: 153 cpp_type = 'bool' 154 elif type_ == PropertyType.INTEGER: 155 cpp_type = 'int' 156 elif type_ == PropertyType.INT64: 157 cpp_type = 'int64' 158 elif type_ == PropertyType.DOUBLE: 159 cpp_type = 'double' 160 elif type_ == PropertyType.STRING: 161 cpp_type = 'std::string' 162 elif type_ == PropertyType.ENUM: 163 cpp_type = cpp_util.Classname(prop.name) 164 elif type_ == PropertyType.ADDITIONAL_PROPERTIES: 165 cpp_type = 'base::DictionaryValue' 166 elif type_ == PropertyType.ANY: 167 cpp_type = any_helper.ANY_CLASS 168 elif type_ == PropertyType.OBJECT: 169 cpp_type = cpp_util.Classname(prop.name) 170 elif type_ == PropertyType.FUNCTION: 171 # Functions come into the json schema compiler as empty objects. We can 172 # record these as empty DictionaryValue's so that we know if the function 173 # was passed in or not. 174 cpp_type = 'base::DictionaryValue' 175 elif type_ == PropertyType.ARRAY: 176 item_type = prop.item_type 177 if item_type.type_ == PropertyType.REF: 178 item_type = self.GetReferencedProperty(item_type) 179 if item_type.type_ in ( 180 PropertyType.REF, PropertyType.ANY, PropertyType.OBJECT): 181 cpp_type = 'std::vector<linked_ptr<%s> > ' 182 else: 183 cpp_type = 'std::vector<%s> ' 184 cpp_type = cpp_type % self.GetType( 185 prop.item_type, pad_for_generics=True) 186 elif type_ == PropertyType.BINARY: 187 cpp_type = 'std::string' 188 else: 189 raise NotImplementedError(type_) 190 191 # Enums aren't wrapped because C++ won't allow it. Optional enums have a 192 # NONE value generated instead. 193 if wrap_optional and prop.optional and not self.IsEnumOrEnumRef(prop): 194 cpp_type = 'scoped_ptr<%s> ' % cpp_type 195 if pad_for_generics: 196 return cpp_type 197 return cpp_type.strip() 198 199 def GenerateForwardDeclarations(self): 200 """Returns the forward declarations for self._namespace. 201 202 Use after GetRootNamespaceStart. Assumes all namespaces are relative to 203 self._root_namespace. 204 """ 205 c = Code() 206 namespace_type_dependencies = self._NamespaceTypeDependencies() 207 for namespace in sorted(namespace_type_dependencies.keys(), 208 key=operator.attrgetter('name')): 209 c.Append('namespace %s {' % namespace.name) 210 for type_ in sorted(namespace_type_dependencies[namespace], 211 key=schema_util.StripSchemaNamespace): 212 type_name = schema_util.StripSchemaNamespace(type_) 213 if namespace.types[type_].type_ == PropertyType.STRING: 214 c.Append('typedef std::string %s;' % type_name) 215 elif namespace.types[type_].type_ == PropertyType.ARRAY: 216 c.Append('typedef std::vector<%(item_type)s> %(name)s;') 217 c.Substitute({ 218 'name': type_name, 219 'item_type': self.GetType(namespace.types[type_].item_type, 220 wrap_optional=True)}) 221 # Enums cannot be forward declared. 222 elif namespace.types[type_].type_ != PropertyType.ENUM: 223 c.Append('struct %s;' % type_name) 224 c.Append('}') 225 c.Concat(self.GetNamespaceStart()) 226 for (name, type_) in self._namespace.types.items(): 227 if not type_.functions and type_.type_ == PropertyType.OBJECT: 228 c.Append('struct %s;' % schema_util.StripSchemaNamespace(name)) 229 c.Concat(self.GetNamespaceEnd()) 230 return c 231 232 def GenerateIncludes(self): 233 """Returns the #include lines for self._namespace. 234 """ 235 c = Code() 236 for header in sorted( 237 ['%s/%s.h' % (dependency.source_file_dir, 238 self._cpp_namespaces[dependency]) 239 for dependency in self._NamespaceTypeDependencies().keys()]): 240 c.Append('#include "%s"' % header) 241 c.Append('#include "base/string_number_conversions.h"') 242 243 if self._namespace.events: 244 c.Append('#include "base/json/json_writer.h"') 245 return c 246 247 def _ResolveTypeNamespace(self, ref_type): 248 """Resolves a type, which must be explicitly qualified, to its enclosing 249 namespace. 250 """ 251 if ref_type in self._type_namespaces: 252 return self._type_namespaces[ref_type] 253 raise KeyError('Cannot resolve type: %s. Maybe it needs a namespace prefix ' 254 'if it comes from another namespace?' % ref_type) 255 return None 256 257 def GetReferencedProperty(self, prop): 258 """Returns the property a property of type REF is referring to. 259 260 If the property passed in is not of type PropertyType.REF, it will be 261 returned unchanged. 262 """ 263 if prop.type_ != PropertyType.REF: 264 return prop 265 return self._ResolveTypeNamespace(prop.ref_type).types.get(prop.ref_type, 266 None) 267 268 def IsEnumOrEnumRef(self, prop): 269 """Returns true if the property is an ENUM or a reference to an ENUM. 270 """ 271 return self.GetReferencedProperty(prop).type_ == PropertyType.ENUM 272 273 def IsEnumRef(self, prop): 274 """Returns true if the property is a reference to an ENUM. 275 """ 276 return (prop.type_ == PropertyType.REF and 277 self.GetReferencedProperty(prop).type_ == PropertyType.ENUM) 278 279 def _NamespaceTypeDependencies(self): 280 """Returns a dict containing a mapping of model.Namespace to the C++ type 281 of type dependencies for self._namespace. 282 """ 283 dependencies = set() 284 for function in self._namespace.functions.values(): 285 for param in function.params: 286 dependencies |= self._PropertyTypeDependencies(param) 287 if function.callback: 288 for param in function.callback.params: 289 dependencies |= self._PropertyTypeDependencies(param) 290 for type_ in self._namespace.types.values(): 291 for prop in type_.properties.values(): 292 dependencies |= self._PropertyTypeDependencies(prop) 293 for event in self._namespace.events.values(): 294 for param in event.params: 295 dependencies |= self._PropertyTypeDependencies(param) 296 297 dependency_namespaces = dict() 298 for dependency in dependencies: 299 namespace = self._ResolveTypeNamespace(dependency) 300 if namespace != self._namespace: 301 dependency_namespaces.setdefault(namespace, []) 302 dependency_namespaces[namespace].append(dependency) 303 return dependency_namespaces 304 305 def _PropertyTypeDependencies(self, prop): 306 """Recursively gets all the type dependencies of a property. 307 """ 308 deps = set() 309 if prop: 310 if prop.type_ == PropertyType.REF: 311 deps.add(prop.ref_type) 312 elif prop.type_ == PropertyType.ARRAY: 313 deps = self._PropertyTypeDependencies(prop.item_type) 314 elif prop.type_ == PropertyType.OBJECT: 315 for p in prop.properties.values(): 316 deps |= self._PropertyTypeDependencies(p) 317 return deps 318 319 def GeneratePropertyValues(self, property, line, nodoc=False): 320 """Generates the Code to display all value-containing properties. 321 """ 322 c = Code() 323 if not nodoc: 324 c.Comment(property.description) 325 326 if property.has_value: 327 c.Append(line % { 328 "type": self._GetPrimitiveType(property.type_), 329 "name": property.name, 330 "value": property.value 331 }) 332 else: 333 has_child_code = False 334 c.Sblock('namespace %s {' % property.name) 335 for child_property in property.properties.values(): 336 child_code = self.GeneratePropertyValues( 337 child_property, 338 line, 339 nodoc=nodoc) 340 if child_code: 341 has_child_code = True 342 c.Concat(child_code) 343 c.Eblock('} // namespace %s' % property.name) 344 if not has_child_code: 345 c = None 346 return c 347 348 def _GetPrimitiveType(self, type_): 349 """Like |GetType| but only accepts and returns C++ primitive types. 350 """ 351 if type_ == PropertyType.BOOLEAN: 352 return 'bool' 353 elif type_ == PropertyType.INTEGER: 354 return 'int' 355 elif type_ == PropertyType.DOUBLE: 356 return 'double' 357 elif type_ == PropertyType.STRING: 358 return 'char*' 359 raise Exception(type_ + ' is not primitive') 360