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