OLD | NEW |
| (Empty) |
1 # Copyright 2015 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 '''Generates Go source files from a mojom.Module.''' | |
6 | |
7 from itertools import chain | |
8 import os | |
9 import re | |
10 | |
11 from mojom.generate.template_expander import UseJinja | |
12 | |
13 import mojom.generate.generator as generator | |
14 import mojom.generate.module as mojom | |
15 import mojom.generate.pack as pack | |
16 | |
17 class KindInfo(object): | |
18 def __init__(self, go_type, encode_suffix, decode_suffix, bit_size): | |
19 self.go_type = go_type | |
20 self.encode_suffix = encode_suffix | |
21 self.decode_suffix = decode_suffix | |
22 self.bit_size = bit_size | |
23 | |
24 _kind_infos = { | |
25 mojom.BOOL: KindInfo('bool', 'Bool', 'Bool', 1), | |
26 mojom.INT8: KindInfo('int8', 'Int8', 'Int8', 8), | |
27 mojom.UINT8: KindInfo('uint8', 'Uint8', 'Uint8', 8), | |
28 mojom.INT16: KindInfo('int16', 'Int16', 'Int16', 16), | |
29 mojom.UINT16: KindInfo('uint16', 'Uint16', 'Uint16', 16), | |
30 mojom.INT32: KindInfo('int32', 'Int32', 'Int32', 32), | |
31 mojom.UINT32: KindInfo('uint32', 'Uint32', 'Uint32', 32), | |
32 mojom.FLOAT: KindInfo('float32', 'Float32', 'Float32', 32), | |
33 mojom.HANDLE: KindInfo( | |
34 'system.Handle', 'Handle', 'Handle', 32), | |
35 mojom.DCPIPE: KindInfo( | |
36 'system.ConsumerHandle', 'Handle', 'ConsumerHandle', 32), | |
37 mojom.DPPIPE: KindInfo( | |
38 'system.ProducerHandle', 'Handle', 'ProducerHandle', 32), | |
39 mojom.MSGPIPE: KindInfo( | |
40 'system.MessagePipeHandle', 'Handle', 'MessagePipeHandle', 32), | |
41 mojom.SHAREDBUFFER: KindInfo( | |
42 'system.SharedBufferHandle', 'Handle', 'SharedBufferHandle', 32), | |
43 mojom.NULLABLE_HANDLE: KindInfo( | |
44 'system.Handle', 'Handle', 'Handle', 32), | |
45 mojom.NULLABLE_DCPIPE: KindInfo( | |
46 'system.ConsumerHandle', 'Handle', 'ConsumerHandle', 32), | |
47 mojom.NULLABLE_DPPIPE: KindInfo( | |
48 'system.ProducerHandle', 'Handle', 'ProducerHandle', 32), | |
49 mojom.NULLABLE_MSGPIPE: KindInfo( | |
50 'system.MessagePipeHandle', 'Handle', 'MessagePipeHandle', 32), | |
51 mojom.NULLABLE_SHAREDBUFFER: KindInfo( | |
52 'system.SharedBufferHandle', 'Handle', 'SharedBufferHandle', 32), | |
53 mojom.INT64: KindInfo('int64', 'Int64', 'Int64', 64), | |
54 mojom.UINT64: KindInfo('uint64', 'Uint64', 'Uint64', 64), | |
55 mojom.DOUBLE: KindInfo('float64', 'Float64', 'Float64', 64), | |
56 mojom.STRING: KindInfo('string', 'String', 'String', 64), | |
57 mojom.NULLABLE_STRING: KindInfo('string', 'String', 'String', 64), | |
58 } | |
59 | |
60 | |
61 # The mojom_types.mojom and service_describer.mojom files are special because | |
62 # they are used to generate mojom Type's and ServiceDescription implementations. | |
63 _service_describer_pkg_short = "service_describer" | |
64 _service_describer_pkg = "mojo/public/interfaces/bindings/%s" % \ | |
65 _service_describer_pkg_short | |
66 _mojom_types_pkg_short = "mojom_types" | |
67 _mojom_types_pkg = "mojo/public/interfaces/bindings/%s" % _mojom_types_pkg_short | |
68 | |
69 def GetBitSize(kind): | |
70 if isinstance(kind, (mojom.Union)): | |
71 return 128 | |
72 if isinstance(kind, (mojom.Array, mojom.Map, mojom.Struct, mojom.Interface)): | |
73 return 64 | |
74 if mojom.IsUnionKind(kind): | |
75 return 2*64 | |
76 if isinstance(kind, (mojom.InterfaceRequest)): | |
77 kind = mojom.MSGPIPE | |
78 if isinstance(kind, mojom.Enum): | |
79 kind = mojom.INT32 | |
80 return _kind_infos[kind].bit_size | |
81 | |
82 # Returns go type corresponding to provided kind. If |nullable| is true | |
83 # and kind is nullable adds an '*' to type (example: ?string -> *string). | |
84 def GetGoType(kind, nullable = True): | |
85 if nullable and mojom.IsNullableKind(kind) and not mojom.IsUnionKind(kind): | |
86 return '*%s' % GetNonNullableGoType(kind) | |
87 return GetNonNullableGoType(kind) | |
88 | |
89 # Returns go type corresponding to provided kind. Ignores nullability of | |
90 # top-level kind. | |
91 def GetNonNullableGoType(kind): | |
92 if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): | |
93 return '%s' % GetFullName(kind) | |
94 if mojom.IsArrayKind(kind): | |
95 if kind.length: | |
96 return '[%s]%s' % (kind.length, GetGoType(kind.kind)) | |
97 return '[]%s' % GetGoType(kind.kind) | |
98 if mojom.IsMapKind(kind): | |
99 return 'map[%s]%s' % (GetGoType(kind.key_kind), GetGoType(kind.value_kind)) | |
100 if mojom.IsInterfaceKind(kind): | |
101 return '%s_Pointer' % GetFullName(kind) | |
102 if mojom.IsInterfaceRequestKind(kind): | |
103 return '%s_Request' % GetFullName(kind.kind) | |
104 if mojom.IsEnumKind(kind): | |
105 return GetNameForNestedElement(kind) | |
106 return _kind_infos[kind].go_type | |
107 | |
108 def IsPointer(kind): | |
109 return mojom.IsObjectKind(kind) and not mojom.IsUnionKind(kind) | |
110 | |
111 # Splits name to lower-cased parts used for camel-casing | |
112 # (example: HTTPEntry2FooBar -> ['http', 'entry2', 'foo', 'bar']). | |
113 def NameToComponent(name): | |
114 # insert '_' between anything and a Title name (e.g, HTTPEntry2FooBar -> | |
115 # HTTP_Entry2_FooBar) | |
116 name = re.sub('([^_])([A-Z][^A-Z0-9_]+)', r'\1_\2', name) | |
117 # insert '_' between non upper and start of upper blocks (e.g., | |
118 # HTTP_Entry2_FooBar -> HTTP_Entry2_Foo_Bar) | |
119 name = re.sub('([^A-Z_])([A-Z])', r'\1_\2', name) | |
120 return [x.lower() for x in name.split('_')] | |
121 | |
122 def UpperCamelCase(name): | |
123 return ''.join([x.capitalize() for x in NameToComponent(name)]) | |
124 | |
125 # Formats a name. If |exported| is true makes name camel-cased with first | |
126 # letter capital, otherwise does no camel-casing and makes first letter | |
127 # lower-cased (which is used for making internal names more readable). | |
128 def FormatName(name, exported=True): | |
129 if exported: | |
130 return UpperCamelCase(name) | |
131 # Leave '_' symbols for unexported names. | |
132 return name[0].lower() + name[1:] | |
133 | |
134 # Returns full name of an imported element. | |
135 # If the |element| is not imported returns formatted name of it. | |
136 # |element| should have attr 'name'. |exported| argument is used to make | |
137 # |FormatName()| calls only. | |
138 def GetFullName(element, exported=True): | |
139 return GetQualifiedName( | |
140 element.name, GetPackageNameForElement(element), exported) | |
141 | |
142 def GetUnqualifiedNameForElement(element, exported=True): | |
143 return FormatName(element.name, exported) | |
144 | |
145 # Returns a name for nested elements like enum field or constant. | |
146 # The returned name consists of camel-cased parts separated by '_'. | |
147 def GetNameForNestedElement(element): | |
148 if element.parent_kind: | |
149 return "%s_%s" % (GetNameForElement(element.parent_kind), | |
150 FormatName(element.name)) | |
151 return GetFullName(element) | |
152 | |
153 def GetNameForElement(element, exported=True): | |
154 if (mojom.IsInterfaceKind(element) or mojom.IsStructKind(element) | |
155 or mojom.IsUnionKind(element)): | |
156 return GetFullName(element, exported) | |
157 if isinstance(element, (mojom.EnumField, | |
158 mojom.Field, | |
159 mojom.Method, | |
160 mojom.Parameter)): | |
161 element_name = (element.go_name if hasattr(element, "go_name") | |
162 else element.name) | |
163 return FormatName(element_name, exported) | |
164 if isinstance(element, (mojom.Enum, | |
165 mojom.Constant, | |
166 mojom.ConstantValue)): | |
167 return GetNameForNestedElement(element) | |
168 raise Exception('Unexpected element: %s' % element) | |
169 | |
170 def ExpressionToText(token): | |
171 if isinstance(token, mojom.EnumValue): | |
172 return "%s_%s" % (GetNameForNestedElement(token.enum), | |
173 FormatName(token.name, True)) | |
174 if isinstance(token, mojom.ConstantValue): | |
175 return GetNameForNestedElement(token) | |
176 if isinstance(token, mojom.Constant): | |
177 return ExpressionToText(token.value) | |
178 return token | |
179 | |
180 def DecodeSuffix(kind): | |
181 if mojom.IsEnumKind(kind): | |
182 return DecodeSuffix(mojom.INT32) | |
183 if mojom.IsInterfaceKind(kind): | |
184 return 'Interface' | |
185 if mojom.IsInterfaceRequestKind(kind): | |
186 return DecodeSuffix(mojom.MSGPIPE) | |
187 return _kind_infos[kind].decode_suffix | |
188 | |
189 def EncodeSuffix(kind): | |
190 if mojom.IsEnumKind(kind): | |
191 return EncodeSuffix(mojom.INT32) | |
192 if mojom.IsInterfaceKind(kind): | |
193 return 'Interface' | |
194 if mojom.IsInterfaceRequestKind(kind): | |
195 return EncodeSuffix(mojom.MSGPIPE) | |
196 return _kind_infos[kind].encode_suffix | |
197 | |
198 def GetPackageName(module): | |
199 return module.name.split('.')[0] | |
200 | |
201 def GetPackageNameForElement(element): | |
202 if not hasattr(element, 'imported_from') or not element.imported_from: | |
203 return '' | |
204 return element.imported_from.get('go_name', '') | |
205 | |
206 def GetTypeKeyForElement(element): | |
207 if not hasattr(element, 'type_key') or not element.type_key: | |
208 return '' | |
209 return element.type_key | |
210 | |
211 def GetQualifiedName(name, package=None, exported=True): | |
212 if not package: | |
213 return FormatName(name, exported) | |
214 return '%s.%s' % (package, FormatName(name, exported)) | |
215 | |
216 def GetPackagePath(module): | |
217 name = module.name.split('.')[0] | |
218 return '/'.join(module.path.split('/')[:-1] + [name]) | |
219 | |
220 def GetAllConstants(module): | |
221 data = [module] + module.structs + module.interfaces | |
222 constants = [x.constants for x in data] | |
223 return [i for i in chain.from_iterable(constants)] | |
224 | |
225 def GetAllEnums(module): | |
226 data = [module] + module.structs + module.interfaces | |
227 enums = [x.enums for x in data] | |
228 return [i for i in chain.from_iterable(enums)] | |
229 | |
230 def AddImport(imports, mojom_imports, module, element): | |
231 """Adds an import required to use the provided element. | |
232 | |
233 The required import is stored in the imports parameter. | |
234 The corresponding mojom import is stored in the mojom_imports parameter. | |
235 Each import is also updated to include a 'go_name' entry. The 'go_name' entry | |
236 is the name by which the imported module will be referred to in the generated | |
237 code. Because the import dictionary is accessible from the element's | |
238 imported_from field this allows us to generate the qualified name for the | |
239 element. | |
240 | |
241 Args: | |
242 imports: {dict<str, str>} The key is the path to the import and the value | |
243 is the go name. | |
244 mojom_imports: {dict<str, str>} The key is the path to the import and the | |
245 value is the go name. | |
246 module: {module.Module} the module being processed. | |
247 element: {module.Kind} the element whose import is to be tracked. | |
248 """ | |
249 if not isinstance(element, mojom.Kind): | |
250 return | |
251 | |
252 if mojom.IsArrayKind(element) or mojom.IsInterfaceRequestKind(element): | |
253 AddImport(imports, mojom_imports, module, element.kind) | |
254 return | |
255 if mojom.IsMapKind(element): | |
256 AddImport(imports, mojom_imports, module, element.key_kind) | |
257 AddImport(imports, mojom_imports, module, element.value_kind) | |
258 return | |
259 if mojom.IsAnyHandleKind(element): | |
260 imports['mojo/public/go/system'] = 'system' | |
261 return | |
262 | |
263 if not hasattr(element, 'imported_from') or not element.imported_from: | |
264 return | |
265 imported = element.imported_from | |
266 if GetPackagePath(imported['module']) == GetPackagePath(module): | |
267 return | |
268 path = GetPackagePath(imported['module']) | |
269 if path in imports: | |
270 return | |
271 name = GetPackageName(imported['module']) | |
272 while name in imports.values(): # This avoids repeated names. | |
273 name += '_' | |
274 imported['go_name'] = name | |
275 imports[path] = name | |
276 mojom_imports[path] = name | |
277 | |
278 class Generator(generator.Generator): | |
279 go_filters = { | |
280 'array': lambda kind: mojom.Array(kind), | |
281 'bit_size': GetBitSize, | |
282 'decode_suffix': DecodeSuffix, | |
283 'encode_suffix': EncodeSuffix, | |
284 'go_type': GetGoType, | |
285 'expression_to_text': ExpressionToText, | |
286 'has_response': lambda method: method.response_parameters is not None, | |
287 'is_array': mojom.IsArrayKind, | |
288 'is_enum': mojom.IsEnumKind, | |
289 'is_handle': mojom.IsAnyHandleKind, | |
290 'is_interface': mojom.IsInterfaceKind, | |
291 'is_interface_request': mojom.IsInterfaceRequestKind, | |
292 'is_map': mojom.IsMapKind, | |
293 'is_none_or_empty': lambda array: array is None or len(array) == 0, | |
294 'is_nullable': mojom.IsNullableKind, | |
295 'is_pointer': IsPointer, | |
296 'is_object': mojom.IsObjectKind, | |
297 'is_struct': mojom.IsStructKind, | |
298 'is_union': mojom.IsUnionKind, | |
299 'qualified': GetQualifiedName, | |
300 'mojom_type_key' : GetTypeKeyForElement, | |
301 'name': GetNameForElement, | |
302 'unqualified_name': GetUnqualifiedNameForElement, | |
303 'package': GetPackageNameForElement, | |
304 'tab_indent': lambda s, size = 1: ('\n' + '\t' * size).join(s.splitlines()) | |
305 } | |
306 | |
307 # If set to True, then mojom type information will be generated. | |
308 should_gen_mojom_types = False | |
309 | |
310 def GetParameters(self): | |
311 package = GetPackageName(self.module) | |
312 imports, mojom_imports = self.GetImports() | |
313 return { | |
314 'enums': GetAllEnums(self.module), | |
315 'imports': imports, | |
316 'interfaces': self.GetInterfaces(), | |
317 'mojom_imports': mojom_imports, | |
318 'package': package, | |
319 'structs': self.GetStructs(), | |
320 'descpkg': '%s.' % _service_describer_pkg_short \ | |
321 if package != _service_describer_pkg_short else '', | |
322 'typepkg': '%s.' % _mojom_types_pkg_short \ | |
323 if package != _mojom_types_pkg_short else '', | |
324 'unions': self.GetUnions(), | |
325 } | |
326 | |
327 @UseJinja('go_templates/source.tmpl', filters=go_filters) | |
328 def GenerateSource(self): | |
329 return self.GetParameters() | |
330 | |
331 def GenerateFiles(self, args): | |
332 self.should_gen_mojom_types = "--generate_type_info" in args | |
333 | |
334 self.Write(self.GenerateSource(), os.path.join("go", "src", | |
335 GetPackagePath(self.module), "%s.go" % self.module.name)) | |
336 | |
337 def GetJinjaParameters(self): | |
338 return { | |
339 'lstrip_blocks': True, | |
340 'trim_blocks': True, | |
341 } | |
342 | |
343 def GetGlobals(self): | |
344 return { | |
345 'namespace': self.module.namespace, | |
346 'module': self.module, | |
347 'should_gen_mojom_types': self.should_gen_mojom_types, | |
348 } | |
349 | |
350 def GetImports(self): | |
351 """Gets the current module's imports. | |
352 | |
353 Returns: | |
354 tuple(dict<str, str>, dict<str, str>) | |
355 The first element of the tuple is a dictionary mapping import paths to | |
356 import names. | |
357 The second element is a dictionary mapping import paths to import names | |
358 only for imported mojom files. | |
359 """ | |
360 imports = {} | |
361 mojom_imports = {} | |
362 # Imports are referred to by the imported_from field of imported kinds. | |
363 # Imported kinds can only be referred to in structs, constants, enums, | |
364 # unions and interfaces. | |
365 all_structs = list(self.module.structs) | |
366 for i in self.module.interfaces: | |
367 for method in i.methods: | |
368 all_structs.append(self._GetStructFromMethod(method)) | |
369 if method.response_parameters: | |
370 all_structs.append(self._GetResponseStructFromMethod(method)) | |
371 | |
372 if (len(all_structs) > 0 or len(self.module.interfaces) > 0 | |
373 or len(self.module.unions) > 0): | |
374 imports['fmt'] = 'fmt' | |
375 imports['mojo/public/go/bindings'] = 'bindings' | |
376 if len(self.module.interfaces) > 0: | |
377 imports['mojo/public/go/system'] = 'system' | |
378 if len(all_structs) > 0: | |
379 imports['sort'] = 'sort' | |
380 | |
381 for union in self.module.unions: | |
382 for field in union.fields: | |
383 AddImport(imports, mojom_imports, self.module, field.kind) | |
384 | |
385 for struct in all_structs: | |
386 for field in struct.fields: | |
387 AddImport(imports, mojom_imports, self.module, field.kind) | |
388 # TODO(rogulenko): add these after generating constants and struct defaults. | |
389 # if field.default: | |
390 # AddImport(imports, mojom_imports, self.module, field.default) | |
391 | |
392 for enum in GetAllEnums(self.module): | |
393 for field in enum.fields: | |
394 if field.value: | |
395 AddImport(imports, mojom_imports, self.module, field.value) | |
396 | |
397 # Mojom Type generation requires additional imports. | |
398 defInterface = len(self.module.interfaces) > 0 | |
399 defOtherType = len(self.module.unions) + len(all_structs) + \ | |
400 len(GetAllEnums(self.module)) > 0 | |
401 | |
402 if self.should_gen_mojom_types: | |
403 imports['bytes'] = 'bytes' | |
404 imports['compress/gzip'] = 'gzip' | |
405 imports['encoding/base64'] = 'base64' | |
406 imports['fmt'] = 'fmt' | |
407 imports['io/ioutil'] = 'ioutil' | |
408 imports['mojo/public/go/bindings'] = 'bindings' | |
409 | |
410 if GetPackageName(self.module) != _mojom_types_pkg_short: | |
411 if defInterface: | |
412 # Each Interface has a service description that uses this. | |
413 imports[_mojom_types_pkg] = _mojom_types_pkg_short | |
414 if defOtherType and self.should_gen_mojom_types: | |
415 # This import is needed only if generating mojom type definitions. | |
416 imports[_mojom_types_pkg] = _mojom_types_pkg_short | |
417 | |
418 if GetPackageName(self.module) != _service_describer_pkg_short and \ | |
419 defInterface: | |
420 # Each Interface has a service description that uses this. | |
421 imports[_service_describer_pkg] = _service_describer_pkg_short | |
422 | |
423 # TODO(rogulenko): add these after generating constants and struct defaults. | |
424 # for constant in GetAllConstants(self.module): | |
425 # AddImport(imports, mojom_imports, self.module, constant.value) | |
426 | |
427 return imports, mojom_imports | |
428 | |
429 # Overrides the implementation from the base class in order to customize the | |
430 # struct and field names. Since the Python objects representing the struct | |
431 # and fields are shared by all language generators we don't want to actually | |
432 # modify the |name| property. Instead we add a |go_name| property. | |
433 def _GetStructFromMethod(self, method): | |
434 self._AddStructComputedData(False, method.param_struct) | |
435 # Only generate the go_names if they have not already been generated. | |
436 if not hasattr(method.param_struct, "go_name"): | |
437 method.param_struct.go_name = "%s_%s_Params" % ( | |
438 GetNameForElement(method.interface), GetNameForElement(method)) | |
439 for field in method.param_struct.fields: | |
440 field.go_name = "in%s" % GetNameForElement(field) | |
441 return method.param_struct | |
442 | |
443 # Overrides the implementation from the base class in order to customize the | |
444 # struct and field names. Since the Python objects representing the struct | |
445 # and fields are shared by all language generators we don't want to actually | |
446 # modify the |name| property. Instead we add a |go_name| property. | |
447 def _GetResponseStructFromMethod(self, method): | |
448 self._AddStructComputedData(False, method.response_param_struct) | |
449 if not hasattr(method.response_param_struct, "go_name"): | |
450 # Only generate the go_names if they have not already been generated. | |
451 method.response_param_struct.go_name = "%s_%s_ResponseParams" % ( | |
452 GetNameForElement(method.interface), GetNameForElement(method)) | |
453 for field in method.response_param_struct.fields: | |
454 field.go_name = "out%s" % GetNameForElement(field) | |
455 return method.response_param_struct | |
OLD | NEW |