Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / tools / json_schema_compiler / cpp_type_generator.py
blob944dba9f1d88e203e60e4d06d37d0d9c4f990187
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.
5 from code import Code
6 from model import PropertyType
7 import cpp_util
8 from json_parse import OrderedDict
9 import schema_util
11 class _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
20 def GetSortKey(self):
21 return '%s.%s' % (self.type_.namespace.name, self.type_.name)
24 class 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
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()
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()
50 def GetEnumValue(self, type_, enum_value):
51 """Gets the enum value of the given model.Property of the given type.
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
65 def GetCppType(self, type_, is_ptr=False, is_in_container=False):
66 """Translates a model.Property or model.Type into its C++ type.
68 If REF types from different namespaces are referenced, will resolve
69 using self._schema_loader.
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).
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::vector<char>'
116 else:
117 raise NotImplementedError('Cannot get type of %s' % type_.property_type)
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)
128 return cpp_type
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))
136 def GenerateForwardDeclarations(self):
137 """Returns the forward declarations for self._default_namespace.
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
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
159 def GenerateIncludes(self, include_soft=False):
160 """Returns the #include lines for self._default_namespace.
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
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.
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)]
181 def FollowRef(self, type_):
182 """Follows $ref link of types to resolve the concrete type a ref refers to.
184 If the property passed in is not of type PropertyType.REF, it will be
185 returned unchanged.
187 if type_.property_type != PropertyType.REF:
188 return type_
189 return self.FollowRef(self._FindType(type_.ref_type))
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.
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)
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)
224 return dependency_namespaces
226 def _TypeDependencies(self, type_, hard=False):
227 """Gets all the type dependencies of a property.
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
247 def GeneratePropertyValues(self, prop, line, nodoc=False):
248 """Generates the Code to display all value-containing properties.
250 c = Code()
251 if not nodoc:
252 c.Comment(prop.description)
254 if prop.value is not None:
255 cpp_type = self.GetCppType(prop.type_)
256 cpp_value = prop.value
257 if cpp_type == 'std::string':
258 cpp_value = '"%s"' % cpp_type
259 c.Append(line % {
260 "type": cpp_type,
261 "name": prop.name,
262 "value": cpp_value
264 else:
265 has_child_code = False
266 c.Sblock('namespace %s {' % prop.name)
267 for child_property in prop.type_.properties.values():
268 child_code = self.GeneratePropertyValues(child_property,
269 line,
270 nodoc=nodoc)
271 if child_code:
272 has_child_code = True
273 c.Concat(child_code)
274 c.Eblock('} // namespace %s' % prop.name)
275 if not has_child_code:
276 c = None
277 return c