cpp_type_generator.py revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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      if self._default_namespace is ref_type.namespace:
100        cpp_type = ref_type.name
101      else:
102        cpp_type = '%s::%s' % (ref_type.namespace.unix_name, ref_type.name)
103    elif type_.property_type == PropertyType.BOOLEAN:
104      cpp_type = 'bool'
105    elif type_.property_type == PropertyType.INTEGER:
106      cpp_type = 'int'
107    elif type_.property_type == PropertyType.INT64:
108      cpp_type = 'int64'
109    elif type_.property_type == PropertyType.DOUBLE:
110      cpp_type = 'double'
111    elif type_.property_type == PropertyType.STRING:
112      cpp_type = 'std::string'
113    elif type_.property_type == PropertyType.ENUM:
114      cpp_type = cpp_util.Classname(type_.name)
115    elif type_.property_type == PropertyType.ANY:
116      cpp_type = 'base::Value'
117    elif (type_.property_type == PropertyType.OBJECT or
118          type_.property_type == PropertyType.CHOICES):
119      cpp_type = cpp_util.Classname(type_.name)
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