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