cpp_type_generator.py revision 5821806d5e7f356e8fa4b058a389a808ea183019
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 any_helper
8import cpp_util
9import operator
10import schema_util
11
12class CppTypeGenerator(object):
13  """Manages the types of properties and provides utilities for getting the
14  C++ type out of a model.Property
15  """
16  def __init__(self, root_namespace, namespace=None, cpp_namespace=None):
17    """Creates a cpp_type_generator. The given root_namespace should be of the
18    format extensions::api::sub. The generator will generate code suitable for
19    use in the given namespace.
20    """
21    self._type_namespaces = {}
22    self._root_namespace = root_namespace.split('::')
23    self._cpp_namespaces = {}
24    if namespace and cpp_namespace:
25      self._namespace = namespace
26      self.AddNamespace(namespace, cpp_namespace)
27
28  def AddNamespace(self, namespace, cpp_namespace):
29    """Maps a model.Namespace to its C++ namespace name. All mappings are
30    beneath the root namespace.
31    """
32    for type_ in namespace.types:
33      if type_ in self._type_namespaces:
34        raise ValueError('Type %s is declared in both %s and %s' %
35            (type_, namespace.name, self._type_namespaces[type_].name))
36      self._type_namespaces[type_] = namespace
37    self._cpp_namespaces[namespace] = cpp_namespace
38
39  def ExpandParams(self, params):
40    """Returns the given parameters with PropertyType.CHOICES parameters
41    expanded so that each choice is a separate parameter.
42    """
43    expanded = []
44    for param in params:
45      if param.type_ == PropertyType.CHOICES:
46        for choice in param.choices.values():
47          expanded.append(choice)
48      else:
49        expanded.append(param)
50    return expanded
51
52  def GetAllPossibleParameterLists(self, params):
53    """Returns all possible parameter lists for the given set of parameters.
54    Every combination of arguments passed to any of the PropertyType.CHOICES
55    parameters will have a corresponding parameter list returned here.
56    """
57    if not params:
58      return [[]]
59    partial_parameter_lists = self.GetAllPossibleParameterLists(params[1:])
60    return [[param] + partial_list
61            for param in self.ExpandParams(params[:1])
62            for partial_list in partial_parameter_lists]
63
64  def GetCppNamespaceName(self, namespace):
65    """Gets the mapped C++ namespace name for the given namespace relative to
66    the root namespace.
67    """
68    return self._cpp_namespaces[namespace]
69
70  def GetRootNamespaceStart(self):
71    """Get opening root namespace declarations.
72    """
73    c = Code()
74    for namespace in self._root_namespace:
75      c.Append('namespace %s {' % namespace)
76    return c
77
78  def GetRootNamespaceEnd(self):
79    """Get closing root namespace declarations.
80    """
81    c = Code()
82    for namespace in reversed(self._root_namespace):
83      c.Append('}  // %s' % namespace)
84    return c
85
86  def GetNamespaceStart(self):
87    """Get opening self._namespace namespace declaration.
88    """
89    return Code().Append('namespace %s {' %
90        self.GetCppNamespaceName(self._namespace))
91
92  def GetNamespaceEnd(self):
93    """Get closing self._namespace namespace declaration.
94    """
95    return Code().Append('}  // %s' %
96        self.GetCppNamespaceName(self._namespace))
97
98  def GetEnumNoneValue(self, prop):
99    """Gets the enum value in the given model.Property indicating no value has
100    been set.
101    """
102    return '%s_NONE' % self.GetReferencedProperty(prop).unix_name.upper()
103
104  def GetEnumValue(self, prop, enum_value):
105    """Gets the enum value of the given model.Property of the given type.
106
107    e.g VAR_STRING
108    """
109    return '%s_%s' % (self.GetReferencedProperty(prop).unix_name.upper(),
110                      cpp_util.Classname(enum_value.upper()))
111
112  def GetChoicesEnumType(self, prop):
113    """Gets the type of the enum for the given model.Property.
114
115    e.g VarType
116    """
117    return cpp_util.Classname(prop.name) + 'Type'
118
119  def GetType(self, prop, pad_for_generics=False, wrap_optional=False):
120    return self._GetTypeHelper(prop, pad_for_generics, wrap_optional)
121
122  def GetCompiledType(self, prop, pad_for_generics=False, wrap_optional=False):
123    return self._GetTypeHelper(prop, pad_for_generics, wrap_optional,
124                               use_compiled_type=True)
125
126  def _GetTypeHelper(self, prop, pad_for_generics=False, wrap_optional=False,
127                     use_compiled_type=False):
128    """Translates a model.Property into its C++ type.
129
130    If REF types from different namespaces are referenced, will resolve
131    using self._type_namespaces.
132
133    Use pad_for_generics when using as a generic to avoid operator ambiguity.
134
135    Use wrap_optional to wrap the type in a scoped_ptr<T> if the Property is
136    optional.
137
138    Use use_compiled_type when converting from prop.type_ to prop.compiled_type.
139    """
140    cpp_type = None
141    type_ = prop.type_ if not use_compiled_type else prop.compiled_type
142
143    if type_ == PropertyType.REF:
144      dependency_namespace = self._ResolveTypeNamespace(prop.ref_type)
145      if not dependency_namespace:
146        raise KeyError('Cannot find referenced type: %s' % prop.ref_type)
147      if self._namespace != dependency_namespace:
148        cpp_type = '%s::%s' % (self._cpp_namespaces[dependency_namespace],
149            schema_util.StripSchemaNamespace(prop.ref_type))
150      else:
151        cpp_type = schema_util.StripSchemaNamespace(prop.ref_type)
152    elif type_ == PropertyType.BOOLEAN:
153      cpp_type = 'bool'
154    elif type_ == PropertyType.INTEGER:
155      cpp_type = 'int'
156    elif type_ == PropertyType.INT64:
157      cpp_type = 'int64'
158    elif type_ == PropertyType.DOUBLE:
159      cpp_type = 'double'
160    elif type_ == PropertyType.STRING:
161      cpp_type = 'std::string'
162    elif type_ == PropertyType.ENUM:
163      cpp_type = cpp_util.Classname(prop.name)
164    elif type_ == PropertyType.ADDITIONAL_PROPERTIES:
165      cpp_type = 'base::DictionaryValue'
166    elif type_ == PropertyType.ANY:
167      cpp_type = any_helper.ANY_CLASS
168    elif type_ == PropertyType.OBJECT:
169      cpp_type = cpp_util.Classname(prop.name)
170    elif type_ == PropertyType.FUNCTION:
171      # Functions come into the json schema compiler as empty objects. We can
172      # record these as empty DictionaryValue's so that we know if the function
173      # was passed in or not.
174      cpp_type = 'base::DictionaryValue'
175    elif type_ == PropertyType.ARRAY:
176      item_type = prop.item_type
177      if item_type.type_ == PropertyType.REF:
178        item_type = self.GetReferencedProperty(item_type)
179      if item_type.type_ in (
180          PropertyType.REF, PropertyType.ANY, PropertyType.OBJECT):
181        cpp_type = 'std::vector<linked_ptr<%s> > '
182      else:
183        cpp_type = 'std::vector<%s> '
184      cpp_type = cpp_type % self.GetType(
185          prop.item_type, pad_for_generics=True)
186    elif type_ == PropertyType.BINARY:
187      cpp_type = 'std::string'
188    else:
189      raise NotImplementedError(type_)
190
191    # Enums aren't wrapped because C++ won't allow it. Optional enums have a
192    # NONE value generated instead.
193    if wrap_optional and prop.optional and not self.IsEnumOrEnumRef(prop):
194      cpp_type = 'scoped_ptr<%s> ' % cpp_type
195    if pad_for_generics:
196      return cpp_type
197    return cpp_type.strip()
198
199  def GenerateForwardDeclarations(self):
200    """Returns the forward declarations for self._namespace.
201
202    Use after GetRootNamespaceStart. Assumes all namespaces are relative to
203    self._root_namespace.
204    """
205    c = Code()
206    namespace_type_dependencies = self._NamespaceTypeDependencies()
207    for namespace in sorted(namespace_type_dependencies.keys(),
208                            key=operator.attrgetter('name')):
209      c.Append('namespace %s {' % namespace.name)
210      for type_ in sorted(namespace_type_dependencies[namespace],
211                          key=schema_util.StripSchemaNamespace):
212        type_name = schema_util.StripSchemaNamespace(type_)
213        if namespace.types[type_].type_ == PropertyType.STRING:
214          c.Append('typedef std::string %s;' % type_name)
215        elif namespace.types[type_].type_ == PropertyType.ARRAY:
216          c.Append('typedef std::vector<%(item_type)s> %(name)s;')
217          c.Substitute({
218            'name': type_name,
219            'item_type': self.GetType(namespace.types[type_].item_type,
220                                      wrap_optional=True)})
221        # Enums cannot be forward declared.
222        elif namespace.types[type_].type_ != PropertyType.ENUM:
223          c.Append('struct %s;' % type_name)
224      c.Append('}')
225    c.Concat(self.GetNamespaceStart())
226    for (name, type_) in self._namespace.types.items():
227      if not type_.functions and type_.type_ == PropertyType.OBJECT:
228        c.Append('struct %s;' % schema_util.StripSchemaNamespace(name))
229    c.Concat(self.GetNamespaceEnd())
230    return c
231
232  def GenerateIncludes(self):
233    """Returns the #include lines for self._namespace.
234    """
235    c = Code()
236    for header in sorted(
237        ['%s/%s.h' % (dependency.source_file_dir,
238                      self._cpp_namespaces[dependency])
239         for dependency in self._NamespaceTypeDependencies().keys()]):
240      c.Append('#include "%s"' % header)
241    c.Append('#include "base/string_number_conversions.h"')
242
243    if self._namespace.events:
244      c.Append('#include "base/json/json_writer.h"')
245    return c
246
247  def _ResolveTypeNamespace(self, ref_type):
248    """Resolves a type, which must be explicitly qualified, to its enclosing
249    namespace.
250    """
251    if ref_type in self._type_namespaces:
252      return self._type_namespaces[ref_type]
253    raise KeyError('Cannot resolve type: %s. Maybe it needs a namespace prefix '
254                   'if it comes from another namespace?' % ref_type)
255    return None
256
257  def GetReferencedProperty(self, prop):
258    """Returns the property a property of type REF is referring to.
259
260    If the property passed in is not of type PropertyType.REF, it will be
261    returned unchanged.
262    """
263    if prop.type_ != PropertyType.REF:
264      return prop
265    return self._ResolveTypeNamespace(prop.ref_type).types.get(prop.ref_type,
266        None)
267
268  def IsEnumOrEnumRef(self, prop):
269    """Returns true if the property is an ENUM or a reference to an ENUM.
270    """
271    return self.GetReferencedProperty(prop).type_ == PropertyType.ENUM
272
273  def IsEnumRef(self, prop):
274    """Returns true if the property is a reference to an ENUM.
275    """
276    return (prop.type_ == PropertyType.REF and
277            self.GetReferencedProperty(prop).type_ == PropertyType.ENUM)
278
279  def _NamespaceTypeDependencies(self):
280    """Returns a dict containing a mapping of model.Namespace to the C++ type
281    of type dependencies for self._namespace.
282    """
283    dependencies = set()
284    for function in self._namespace.functions.values():
285      for param in function.params:
286        dependencies |= self._PropertyTypeDependencies(param)
287      if function.callback:
288        for param in function.callback.params:
289          dependencies |= self._PropertyTypeDependencies(param)
290    for type_ in self._namespace.types.values():
291      for prop in type_.properties.values():
292        dependencies |= self._PropertyTypeDependencies(prop)
293    for event in self._namespace.events.values():
294      for param in event.params:
295        dependencies |= self._PropertyTypeDependencies(param)
296
297    dependency_namespaces = dict()
298    for dependency in dependencies:
299      namespace = self._ResolveTypeNamespace(dependency)
300      if namespace != self._namespace:
301        dependency_namespaces.setdefault(namespace, [])
302        dependency_namespaces[namespace].append(dependency)
303    return dependency_namespaces
304
305  def _PropertyTypeDependencies(self, prop):
306    """Recursively gets all the type dependencies of a property.
307    """
308    deps = set()
309    if prop:
310      if prop.type_ == PropertyType.REF:
311        deps.add(prop.ref_type)
312      elif prop.type_ == PropertyType.ARRAY:
313        deps = self._PropertyTypeDependencies(prop.item_type)
314      elif prop.type_ == PropertyType.OBJECT:
315        for p in prop.properties.values():
316          deps |= self._PropertyTypeDependencies(p)
317    return deps
318
319  def GeneratePropertyValues(self, property, line, nodoc=False):
320    """Generates the Code to display all value-containing properties.
321    """
322    c = Code()
323    if not nodoc:
324      c.Comment(property.description)
325
326    if property.has_value:
327      c.Append(line % {
328          "type": self._GetPrimitiveType(property.type_),
329          "name": property.name,
330          "value": property.value
331        })
332    else:
333      has_child_code = False
334      c.Sblock('namespace %s {' % property.name)
335      for child_property in property.properties.values():
336        child_code = self.GeneratePropertyValues(
337            child_property,
338            line,
339            nodoc=nodoc)
340        if child_code:
341          has_child_code = True
342          c.Concat(child_code)
343      c.Eblock('}  // namespace %s' % property.name)
344      if not has_child_code:
345        c = None
346    return c
347
348  def _GetPrimitiveType(self, type_):
349    """Like |GetType| but only accepts and returns C++ primitive types.
350    """
351    if type_ == PropertyType.BOOLEAN:
352      return 'bool'
353    elif type_ == PropertyType.INTEGER:
354      return 'int'
355    elif type_ == PropertyType.DOUBLE:
356      return 'double'
357    elif type_ == PropertyType.STRING:
358      return 'char*'
359    raise Exception(type_ + ' is not primitive')
360