Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(167)

Side by Side Diff: tools/json_schema_compiler/ppapi_generator.py

Issue 101483003: Add a Pepper IDL generator to the JSON schema compiler. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Created 7 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 # Copyright 2013 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
5 import collections
6 import datetime
7 import os.path
8 import sys
9
10 import code
11 import cpp_util
12 import model
13
14 try:
15 import jinja2
16 except ImportError:
17 jinja2 = None
18 jinja2_error = sys.exc_info()
19
20
21 class _PpapiGeneratorBase(object):
22 """A base class for ppapi generators.
23
24 Implementations should set TEMPLATE_NAME to a string containing the name of
25 the template file without its extension. The template will be rendered with
26 the following symbols available:
27 name: A string containing the name of the namespace.
28 enums: A list of enums within the namespace.
29 types: A list of types within the namespace, sorted such that no element
30 depends on an earlier element.
31 events: A dict of events within the namespace.
32 functions: A dict of functions within the namespace.
33 year: An int containing the current year.
34 source_file: The name of the input file.
35 dev: '_dev' if this a dev API; empty otherwise.
36 dev_path: '/dev' if this a dev API; empty otherwise.
37 """
38
39 def __init__(self, namespace, is_dev):
40 self.is_dev = is_dev
41 self._namespace = namespace
42 self._required_types = {}
43 self._array_types = set()
44 self._optional_types = set()
45 self._optional_array_types = set()
46 self._dependencies = collections.OrderedDict()
47 self._types = []
48 self._enums = []
49
50 if not jinja2:
51 raise jinja2_error
52 self.jinja_environment = jinja2.Environment(
53 loader=jinja2.FileSystemLoader(os.path.join(os.path.dirname(__file__),
54 'templates', 'ppapi')))
55 self._SetupFilters()
56 self._ResolveTypeDependencies()
57
58 def _SetupFilters(self):
59 self.jinja_environment.filters.update({
60 'ppapi_type': self.ToPpapiType,
61 'classname': cpp_util.Classname,
62 'dev_suffix': self.AddDevSuffix,
63 'enum_value': self.EnumValueName,
64 'return_type': self.GetFunctionReturnType,
65 'format_param_type': self.FormatParamType,
66 'needs_optional': self.NeedsOptional,
67 'needs_array': self.NeedsArray,
68 'needs_optional_array': self.NeedsOptionalArray,
69 'has_array_outs': self.HasArrayOuts,
70 })
71
72 def Render(self, template_name, values):
73 c = code.Code()
yzshen1 2013/12/10 21:43:02 nit: maybe consider a more descriptive variable na
Sam McNally 2013/12/11 08:02:38 Done.
74 template = self.jinja_environment.get_template(
75 '%s.template' % template_name)
76 c.Append(template.render(values))
77 return c
78
79 def Generate(self):
80 """Generates a Code object for a single namespace."""
81 return self.Render(self.TEMPLATE_NAME, {
82 'name': self._namespace.name,
83 'enums': self._enums,
84 'types': self._types,
85 'events': self._namespace.events,
86 'functions': self._namespace.functions,
87 'year': datetime.date.today().year,
yzshen1 2013/12/10 21:43:02 nit: Please note that when we modify an existing f
Sam McNally 2013/12/11 08:02:38 Added a TODO. As long as the first generated file
yzshen1 2013/12/11 22:55:35 Indeed!
88 'source_file': self._namespace.source_file,
89 'dev': '_dev' if self.is_dev else '',
90 'dev_path': '/dev' if self.is_dev else '',
91 })
92
93 def _ResolveTypeDependencies(self):
94 """Calculates the transitive closure of the types in _required_types.
95
96 Returns a tuple containing the list of struct types and the list of enum
97 types. The list of struct types is ordered such that no type depends on a
98 type later in the list.
99
100 """
101 if self._namespace.functions:
102 for function in self._namespace.functions.itervalues():
103 self._FindFunctionDependencies(function)
104
105 if self._namespace.events:
106 for event in self._namespace.events.itervalues():
107 self._FindFunctionDependencies(event)
108 resolved_types = set()
109 while resolved_types < set(self._required_types):
110 for typename in sorted(set(self._required_types) - resolved_types):
111 type_ = self._required_types[typename]
112 self._dependencies.setdefault(typename, set())
113 for member in type_.properties.itervalues():
114 self._RegisterDependency(member, self._NameComponents(type_))
115 resolved_types.add(typename)
116 while self._dependencies:
117 for name, deps in self._dependencies.items():
118 if not deps:
119 if (self._required_types[name].property_type ==
120 model.PropertyType.ENUM):
121 self._enums.append(self._required_types[name])
122 else:
123 self._types.append(self._required_types[name])
124 for deps in self._dependencies.itervalues():
125 deps.discard(name)
126 del self._dependencies[name]
127 break
128 else:
129 raise ValueError('Circular dependency %s' % self._dependencies)
130
131 def _FindFunctionDependencies(self, function):
132 for param in function.params:
133 self._RegisterDependency(param, None)
134 if function.callback:
135 for param in function.callback.params:
136 self._RegisterDependency(param, None)
137 if function.returns:
138 self._RegisterTypeDependency(function.returns, None, False, False)
139
140 def _RegisterDependency(self, member, depender):
141 self._RegisterTypeDependency(member.type_, depender, member.optional, False)
142
143 def _RegisterTypeDependency(self, type_, depender, optional, array):
yzshen1 2013/12/10 21:43:02 I noticed that you use 'type_' in quite some place
Sam McNally 2013/12/11 08:02:38 Yes. This is to avoid clashing with the global "ty
yzshen1 2013/12/11 22:55:35 (I don't know.) Is it a common practice to use "ty
Sam McNally 2013/12/11 23:02:21 type_ is used in the rest of the JSON schema compi
144 if type_.property_type == model.PropertyType.ARRAY:
145 self._RegisterTypeDependency(type_.item_type, depender, optional, True)
146 elif type_.property_type == model.PropertyType.REF:
147 self._RegisterTypeDependency(self._namespace.types[type_.ref_type],
148 depender, optional, array)
149 elif type_.property_type in (model.PropertyType.OBJECT,
150 model.PropertyType.ENUM):
151 name_components = self._NameComponents(type_)
152 self._required_types[name_components] = type_
153 if depender:
154 self._dependencies.setdefault(depender, set()).add(
155 name_components)
156 if array:
157 self._array_types.add(name_components)
158 if optional:
159 self._optional_array_types.add(name_components)
160 elif optional:
161 self._optional_types.add(name_components)
162
163 @staticmethod
164 def _NameComponents(entity, include_namespace=False):
165 """Returns a tuple of the fully-qualified name of an entity."""
166 names = []
167 while entity:
168 if (not isinstance(entity, model.Type) or
169 entity.property_type != model.PropertyType.ARRAY):
170 names.append(entity.name)
171 entity = entity.parent
172 if include_namespace:
173 return tuple(reversed(names))
174 return tuple(reversed(names[:-1]))
175
176 def ToPpapiType(self, type_, array=False, optional=False):
177 """Returns a string containing the name of the Pepper C type for |type_|.
178
179 If array is True, returns the name of an array of |type_|. If optional is
180 True, returns the name of an optional |type_|. If both array and optional
181 are True, returns the name of an optional array of |type_|.
182 """
183 if isinstance(type_, model.Function) or type_.property_type in (
184 model.PropertyType.OBJECT, model.PropertyType.ENUM):
185 return self._FormatPpapiTypeName(
186 array, optional, '_'.join(
187 cpp_util.Classname(s) for s in self._NameComponents(
188 type_, include_namespace=True)),
189 '_Dev' if self.is_dev else '')
190 elif type_.property_type == model.PropertyType.REF:
191 return self.ToPpapiType(self._namespace.types[type_.ref_type],
192 optional=optional, array=array)
193 elif type_.property_type == model.PropertyType.ARRAY:
194 return self.ToPpapiType(type_.item_type, array=True,
195 optional=optional)
196 elif type_.property_type == model.PropertyType.STRING and not array:
197 return 'PP_Var'
198 elif array or optional:
199 if type_.property_type in self._PPAPI_COMPOUND_PRIMITIVE_TYPE_MAP:
200 return self._FormatPpapiTypeName(
201 array, optional,
202 self._PPAPI_COMPOUND_PRIMITIVE_TYPE_MAP[type_.property_type], '')
203 return self._PPAPI_PRIMITIVE_TYPE_MAP.get(type_.property_type, 'PP_Var')
204
205 _PPAPI_PRIMITIVE_TYPE_MAP = {
206 model.PropertyType.BOOLEAN: 'PP_Bool',
207 model.PropertyType.DOUBLE: 'double_t',
208 model.PropertyType.INT64: 'int64_t',
209 model.PropertyType.INTEGER: 'int32_t',
210 }
211 _PPAPI_COMPOUND_PRIMITIVE_TYPE_MAP = {
212 model.PropertyType.BOOLEAN: 'Bool',
213 model.PropertyType.DOUBLE: 'Double',
214 model.PropertyType.INT64: 'Int64',
215 model.PropertyType.INTEGER: 'Int32',
216 model.PropertyType.STRING: 'String',
217 }
218
219 @staticmethod
220 def _FormatPpapiTypeName(array, optional, *args):
221 if array:
222 if optional:
223 return 'PP_Optional_%s_Array%s' % args
224 return 'PP_%s_Array%s' % args
225 if optional:
226 return 'PP_Optional_%s%s' % args
227 return 'PP_%s%s' % args
228
229 def NeedsOptional(self, type_):
230 """Returns True if an optional |type_| is required."""
231 return self._NameComponents(type_) in self._optional_types
232
233 def NeedsArray(self, type_):
234 """Returns True if an array of |type_| is required."""
235 return self._NameComponents(type_) in self._array_types
236
237 def NeedsOptionalArray(self, type_):
238 """Returns True if an optional array of |type_| is required."""
239 return self._NameComponents(type_) in self._optional_array_types
240
241 def FormatParamType(self, param):
242 """Formats the type of a parameter or property."""
243 return self.ToPpapiType(param.type_, optional=param.optional)
244
245 def AddDevSuffix(self, name):
246 """Returns |name| with a _Dev suffix if a dev API is being generated."""
247 if self.is_dev:
248 name = '%s_Dev' % name
249 return name
250
251 @staticmethod
252 def GetFunctionReturnType(function):
253 return 'int32_t' if function.callback or function.returns else 'void'
254
255 def EnumValueName(self, enum_value, enum_type):
256 """Returns a string containing the name for an enum value."""
257 return '%s_%s' % (self.ToPpapiType(enum_type).upper(),
258 enum_value.name.upper())
259
260 def _IsOrContainsArray(self, type_):
261 if type_.property_type == model.PropertyType.ARRAY:
262 return True
263 elif type_.property_type == model.PropertyType.REF:
264 return self._IsOrContainsArray(self._namespace.types[type_.ref_type])
265 elif type_.property_type == model.PropertyType.OBJECT:
266 for param in type_.properties.itervalues():
267 if self._IsOrContainsArray(param.type_):
268 return True
269 return False
270
271 def HasArrayOuts(self, function):
272 """Returns True if the function produces any arrays as outputs.
273
274 This includes arrays that are properties of other objects.
275 """
276 if function.callback:
277 for param in function.callback.params:
278 if self._IsOrContainsArray(param.type_):
279 return True
280 return function.returns and self._IsOrContainsArray(function.returns)
281
282
283 class _IdlGenerator(_PpapiGeneratorBase):
284 TEMPLATE_NAME = 'idl'
285
286
287 class _GeneratorWrapper(object):
288 def __init__(self, generator_factory, is_dev):
289 self._generator_factory = generator_factory
290 self._is_dev = is_dev
291
292 def Generate(self, namespace):
293 return self._generator_factory(namespace, self._is_dev).Generate()
294
295
296 class PpapiGenerator(object):
297 def __init__(self, is_dev):
298 self.idl_generator = _GeneratorWrapper(_IdlGenerator, is_dev)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698