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