cpp_type_generator.py revision c5cede9ae108bb15f6b7a8aea21c7e1fefa2834c
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 cpp_util 8from json_parse import OrderedDict 9import schema_util 10 11class _TypeDependency(object): 12 """Contains information about a dependency a namespace has on a type: the 13 type's model, and whether that dependency is "hard" meaning that it cannot be 14 forward declared. 15 """ 16 def __init__(self, type_, hard=False): 17 self.type_ = type_ 18 self.hard = hard 19 20 def GetSortKey(self): 21 return '%s.%s' % (self.type_.namespace.name, self.type_.name) 22 23 24class CppTypeGenerator(object): 25 """Manages the types of properties and provides utilities for getting the 26 C++ type out of a model.Property 27 """ 28 def __init__(self, model, schema_loader, default_namespace=None): 29 """Creates a cpp_type_generator. The given root_namespace should be of the 30 format extensions::api::sub. The generator will generate code suitable for 31 use in the given model's namespace. 32 """ 33 self._default_namespace = default_namespace 34 if self._default_namespace is None: 35 self._default_namespace = model.namespaces.values()[0] 36 self._schema_loader = schema_loader 37 38 def GetCppNamespaceName(self, namespace): 39 """Gets the mapped C++ namespace name for the given namespace relative to 40 the root namespace. 41 """ 42 return namespace.unix_name 43 44 def GetNamespaceStart(self): 45 """Get opening self._default_namespace namespace declaration. 46 """ 47 return Code().Append('namespace %s {' % 48 self.GetCppNamespaceName(self._default_namespace)) 49 50 def GetNamespaceEnd(self): 51 """Get closing self._default_namespace namespace declaration. 52 """ 53 return Code().Append('} // %s' % 54 self.GetCppNamespaceName(self._default_namespace)) 55 56 def GetEnumNoneValue(self, type_): 57 """Gets the enum value in the given model.Property indicating no value has 58 been set. 59 """ 60 return '%s_NONE' % self.FollowRef(type_).unix_name.upper() 61 62 def GetEnumLastValue(self, type_): 63 """Gets the enum value in the given model.Property indicating the last value 64 for the type. 65 """ 66 return '%s_LAST' % self.FollowRef(type_).unix_name.upper() 67 68 def GetEnumValue(self, type_, enum_value): 69 """Gets the enum value of the given model.Property of the given type. 70 71 e.g VAR_STRING 72 """ 73 value = cpp_util.Classname(enum_value.name.upper()) 74 if not type_.cpp_omit_enum_type: 75 value = '%s_%s' % (self.FollowRef(type_).unix_name.upper(), value) 76 # To avoid collisions with built-in OS_* preprocessor definitions, we add a 77 # trailing slash to enum names that start with OS_. 78 if value.startswith("OS_"): 79 value += "_" 80 return value 81 82 def GetCppType(self, type_, is_ptr=False, is_in_container=False): 83 """Translates a model.Property or model.Type into its C++ type. 84 85 If REF types from different namespaces are referenced, will resolve 86 using self._schema_loader. 87 88 Use |is_ptr| if the type is optional. This will wrap the type in a 89 scoped_ptr if possible (it is not possible to wrap an enum). 90 91 Use |is_in_container| if the type is appearing in a collection, e.g. a 92 std::vector or std::map. This will wrap it in the correct type with spacing. 93 """ 94 cpp_type = None 95 if type_.property_type == PropertyType.REF: 96 ref_type = self._FindType(type_.ref_type) 97 if ref_type is None: 98 raise KeyError('Cannot find referenced type: %s' % type_.ref_type) 99 cpp_type = self.GetCppType(ref_type) 100 elif type_.property_type == PropertyType.BOOLEAN: 101 cpp_type = 'bool' 102 elif type_.property_type == PropertyType.INTEGER: 103 cpp_type = 'int' 104 elif type_.property_type == PropertyType.INT64: 105 cpp_type = 'int64' 106 elif type_.property_type == PropertyType.DOUBLE: 107 cpp_type = 'double' 108 elif type_.property_type == PropertyType.STRING: 109 cpp_type = 'std::string' 110 elif type_.property_type in (PropertyType.ENUM, 111 PropertyType.OBJECT, 112 PropertyType.CHOICES): 113 if self._default_namespace is type_.namespace: 114 cpp_type = cpp_util.Classname(type_.name) 115 else: 116 cpp_type = '%s::%s' % (type_.namespace.unix_name, 117 cpp_util.Classname(type_.name)) 118 elif type_.property_type == PropertyType.ANY: 119 cpp_type = 'base::Value' 120 elif type_.property_type == PropertyType.FUNCTION: 121 # Functions come into the json schema compiler as empty objects. We can 122 # record these as empty DictionaryValues so that we know if the function 123 # was passed in or not. 124 cpp_type = 'base::DictionaryValue' 125 elif type_.property_type == PropertyType.ARRAY: 126 item_cpp_type = self.GetCppType(type_.item_type, is_in_container=True) 127 cpp_type = 'std::vector<%s>' % cpp_util.PadForGenerics(item_cpp_type) 128 elif type_.property_type == PropertyType.BINARY: 129 cpp_type = 'std::string' 130 else: 131 raise NotImplementedError('Cannot get type of %s' % type_.property_type) 132 133 # HACK: optional ENUM is represented elsewhere with a _NONE value, so it 134 # never needs to be wrapped in pointer shenanigans. 135 # TODO(kalman): change this - but it's an exceedingly far-reaching change. 136 if not self.FollowRef(type_).property_type == PropertyType.ENUM: 137 if is_in_container and (is_ptr or not self.IsCopyable(type_)): 138 cpp_type = 'linked_ptr<%s>' % cpp_util.PadForGenerics(cpp_type) 139 elif is_ptr: 140 cpp_type = 'scoped_ptr<%s>' % cpp_util.PadForGenerics(cpp_type) 141 142 return cpp_type 143 144 def IsCopyable(self, type_): 145 return not (self.FollowRef(type_).property_type in (PropertyType.ANY, 146 PropertyType.ARRAY, 147 PropertyType.OBJECT, 148 PropertyType.CHOICES)) 149 150 def GenerateForwardDeclarations(self): 151 """Returns the forward declarations for self._default_namespace. 152 """ 153 c = Code() 154 155 for namespace, dependencies in self._NamespaceTypeDependencies().items(): 156 c.Append('namespace %s {' % namespace.unix_name) 157 for dependency in dependencies: 158 # No point forward-declaring hard dependencies. 159 if dependency.hard: 160 continue 161 # Add more ways to forward declare things as necessary. 162 if dependency.type_.property_type in (PropertyType.CHOICES, 163 PropertyType.OBJECT): 164 c.Append('struct %s;' % dependency.type_.name) 165 c.Append('}') 166 167 return c 168 169 def GenerateIncludes(self, include_soft=False): 170 """Returns the #include lines for self._default_namespace. 171 """ 172 c = Code() 173 for namespace, dependencies in self._NamespaceTypeDependencies().items(): 174 for dependency in dependencies: 175 if dependency.hard or include_soft: 176 c.Append('#include "%s/%s.h"' % (namespace.source_file_dir, 177 namespace.unix_name)) 178 return c 179 180 def _FindType(self, full_name): 181 """Finds the model.Type with name |qualified_name|. If it's not from 182 |self._default_namespace| then it needs to be qualified. 183 """ 184 namespace = self._schema_loader.ResolveType(full_name, 185 self._default_namespace) 186 if namespace is None: 187 raise KeyError('Cannot resolve type %s. Maybe it needs a prefix ' 188 'if it comes from another namespace?' % full_name) 189 return namespace.types[schema_util.StripNamespace(full_name)] 190 191 def FollowRef(self, type_): 192 """Follows $ref link of types to resolve the concrete type a ref refers to. 193 194 If the property passed in is not of type PropertyType.REF, it will be 195 returned unchanged. 196 """ 197 if type_.property_type != PropertyType.REF: 198 return type_ 199 return self.FollowRef(self._FindType(type_.ref_type)) 200 201 def _NamespaceTypeDependencies(self): 202 """Returns a dict ordered by namespace name containing a mapping of 203 model.Namespace to every _TypeDependency for |self._default_namespace|, 204 sorted by the type's name. 205 """ 206 dependencies = set() 207 for function in self._default_namespace.functions.values(): 208 for param in function.params: 209 dependencies |= self._TypeDependencies(param.type_, 210 hard=not param.optional) 211 if function.callback: 212 for param in function.callback.params: 213 dependencies |= self._TypeDependencies(param.type_, 214 hard=not param.optional) 215 for type_ in self._default_namespace.types.values(): 216 for prop in type_.properties.values(): 217 dependencies |= self._TypeDependencies(prop.type_, 218 hard=not prop.optional) 219 for event in self._default_namespace.events.values(): 220 for param in event.params: 221 dependencies |= self._TypeDependencies(param.type_, 222 hard=not param.optional) 223 224 # Make sure that the dependencies are returned in alphabetical order. 225 dependency_namespaces = OrderedDict() 226 for dependency in sorted(dependencies, key=_TypeDependency.GetSortKey): 227 namespace = dependency.type_.namespace 228 if namespace is self._default_namespace: 229 continue 230 if namespace not in dependency_namespaces: 231 dependency_namespaces[namespace] = [] 232 dependency_namespaces[namespace].append(dependency) 233 234 return dependency_namespaces 235 236 def _TypeDependencies(self, type_, hard=False): 237 """Gets all the type dependencies of a property. 238 """ 239 deps = set() 240 if type_.property_type == PropertyType.REF: 241 deps.add(_TypeDependency(self._FindType(type_.ref_type), hard=hard)) 242 elif type_.property_type == PropertyType.ARRAY: 243 # Non-copyable types are not hard because they are wrapped in linked_ptrs 244 # when generated. Otherwise they're typedefs, so they're hard (though we 245 # could generate those typedefs in every dependent namespace, but that 246 # seems weird). 247 deps = self._TypeDependencies(type_.item_type, 248 hard=self.IsCopyable(type_.item_type)) 249 elif type_.property_type == PropertyType.CHOICES: 250 for type_ in type_.choices: 251 deps |= self._TypeDependencies(type_, hard=self.IsCopyable(type_)) 252 elif type_.property_type == PropertyType.OBJECT: 253 for p in type_.properties.values(): 254 deps |= self._TypeDependencies(p.type_, hard=not p.optional) 255 return deps 256 257 def GeneratePropertyValues(self, property, line, nodoc=False): 258 """Generates the Code to display all value-containing properties. 259 """ 260 c = Code() 261 if not nodoc: 262 c.Comment(property.description) 263 264 if property.value is not None: 265 c.Append(line % { 266 "type": self.GetCppType(property.type_), 267 "name": property.name, 268 "value": property.value 269 }) 270 else: 271 has_child_code = False 272 c.Sblock('namespace %s {' % property.name) 273 for child_property in property.type_.properties.values(): 274 child_code = self.GeneratePropertyValues(child_property, 275 line, 276 nodoc=nodoc) 277 if child_code: 278 has_child_code = True 279 c.Concat(child_code) 280 c.Eblock('} // namespace %s' % property.name) 281 if not has_child_code: 282 c = None 283 return c 284