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