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"""Generates JavaScript source files from a mojom.Module."""
6
7import mojom.generate.generator as generator
8import mojom.generate.module as mojom
9import mojom.generate.pack as pack
10from mojom.generate.template_expander import UseJinja
11
12_kind_to_javascript_default_value = {
13  mojom.BOOL:                  "false",
14  mojom.INT8:                  "0",
15  mojom.UINT8:                 "0",
16  mojom.INT16:                 "0",
17  mojom.UINT16:                "0",
18  mojom.INT32:                 "0",
19  mojom.UINT32:                "0",
20  mojom.FLOAT:                 "0",
21  mojom.HANDLE:                "null",
22  mojom.DCPIPE:                "null",
23  mojom.DPPIPE:                "null",
24  mojom.MSGPIPE:               "null",
25  mojom.SHAREDBUFFER:          "null",
26  mojom.NULLABLE_HANDLE:       "null",
27  mojom.NULLABLE_DCPIPE:       "null",
28  mojom.NULLABLE_DPPIPE:       "null",
29  mojom.NULLABLE_MSGPIPE:      "null",
30  mojom.NULLABLE_SHAREDBUFFER: "null",
31  mojom.INT64:                 "0",
32  mojom.UINT64:                "0",
33  mojom.DOUBLE:                "0",
34  mojom.STRING:                "null",
35  mojom.NULLABLE_STRING:       "null"
36}
37
38
39def JavaScriptType(kind):
40  if kind.imported_from:
41    return kind.imported_from["unique_name"] + "." + kind.name
42  return kind.name
43
44
45def JavaScriptDefaultValue(field):
46  if field.default:
47    if mojom.IsStructKind(field.kind):
48      assert field.default == "default"
49      return "new %s()" % JavaScriptType(field.kind)
50    return ExpressionToText(field.default)
51  if field.kind in mojom.PRIMITIVES:
52    return _kind_to_javascript_default_value[field.kind]
53  if mojom.IsStructKind(field.kind):
54    return "null"
55  if mojom.IsUnionKind(field.kind):
56    return "null"
57  if mojom.IsArrayKind(field.kind):
58    return "null"
59  if mojom.IsMapKind(field.kind):
60    return "null"
61  if mojom.IsInterfaceKind(field.kind) or \
62     mojom.IsInterfaceRequestKind(field.kind):
63    return _kind_to_javascript_default_value[mojom.MSGPIPE]
64  if mojom.IsAssociatedKind(field.kind):
65    return "null"
66  if mojom.IsEnumKind(field.kind):
67    return "0"
68  raise Exception("No valid default: %s" % field)
69
70
71def JavaScriptPayloadSize(packed):
72  packed_fields = packed.packed_fields
73  if not packed_fields:
74    return 0
75  last_field = packed_fields[-1]
76  offset = last_field.offset + last_field.size
77  pad = pack.GetPad(offset, 8)
78  return offset + pad
79
80
81_kind_to_codec_type = {
82  mojom.BOOL:                  "codec.Uint8",
83  mojom.INT8:                  "codec.Int8",
84  mojom.UINT8:                 "codec.Uint8",
85  mojom.INT16:                 "codec.Int16",
86  mojom.UINT16:                "codec.Uint16",
87  mojom.INT32:                 "codec.Int32",
88  mojom.UINT32:                "codec.Uint32",
89  mojom.FLOAT:                 "codec.Float",
90  mojom.HANDLE:                "codec.Handle",
91  mojom.DCPIPE:                "codec.Handle",
92  mojom.DPPIPE:                "codec.Handle",
93  mojom.MSGPIPE:               "codec.Handle",
94  mojom.SHAREDBUFFER:          "codec.Handle",
95  mojom.NULLABLE_HANDLE:       "codec.NullableHandle",
96  mojom.NULLABLE_DCPIPE:       "codec.NullableHandle",
97  mojom.NULLABLE_DPPIPE:       "codec.NullableHandle",
98  mojom.NULLABLE_MSGPIPE:      "codec.NullableHandle",
99  mojom.NULLABLE_SHAREDBUFFER: "codec.NullableHandle",
100  mojom.INT64:                 "codec.Int64",
101  mojom.UINT64:                "codec.Uint64",
102  mojom.DOUBLE:                "codec.Double",
103  mojom.STRING:                "codec.String",
104  mojom.NULLABLE_STRING:       "codec.NullableString",
105}
106
107
108def CodecType(kind):
109  if kind in mojom.PRIMITIVES:
110    return _kind_to_codec_type[kind]
111  if mojom.IsStructKind(kind):
112    pointer_type = "NullablePointerTo" if mojom.IsNullableKind(kind) \
113        else "PointerTo"
114    return "new codec.%s(%s)" % (pointer_type, JavaScriptType(kind))
115  if mojom.IsUnionKind(kind):
116    return JavaScriptType(kind)
117  if mojom.IsArrayKind(kind):
118    array_type = "NullableArrayOf" if mojom.IsNullableKind(kind) else "ArrayOf"
119    array_length = "" if kind.length is None else ", %d" % kind.length
120    element_type = ElementCodecType(kind.kind)
121    return "new codec.%s(%s%s)" % (array_type, element_type, array_length)
122  if mojom.IsInterfaceKind(kind):
123    return "codec.%s" % ("NullableInterface" if mojom.IsNullableKind(kind)
124        else "Interface")
125  if mojom.IsInterfaceRequestKind(kind):
126    return CodecType(mojom.MSGPIPE)
127  if mojom.IsAssociatedInterfaceKind(kind):
128    return "codec.AssociatedInterfaceNotSupported"
129  if mojom.IsAssociatedInterfaceRequestKind(kind):
130    return "codec.AssociatedInterfaceRequestNotSupported"
131  if mojom.IsEnumKind(kind):
132    return _kind_to_codec_type[mojom.INT32]
133  if mojom.IsMapKind(kind):
134    map_type = "NullableMapOf" if mojom.IsNullableKind(kind) else "MapOf"
135    key_type = ElementCodecType(kind.key_kind)
136    value_type = ElementCodecType(kind.value_kind)
137    return "new codec.%s(%s, %s)" % (map_type, key_type, value_type)
138  raise Exception("No codec type for %s" % kind)
139
140
141def ElementCodecType(kind):
142  return "codec.PackedBool" if mojom.IsBoolKind(kind) else CodecType(kind)
143
144def JavaScriptDecodeSnippet(kind):
145  if (kind in mojom.PRIMITIVES or mojom.IsUnionKind(kind) or
146      mojom.IsInterfaceKind(kind) or mojom.IsAssociatedKind(kind)):
147    return "decodeStruct(%s)" % CodecType(kind)
148  if mojom.IsStructKind(kind):
149    return "decodeStructPointer(%s)" % JavaScriptType(kind)
150  if mojom.IsMapKind(kind):
151    return "decodeMapPointer(%s, %s)" % \
152        (ElementCodecType(kind.key_kind), ElementCodecType(kind.value_kind))
153  if mojom.IsArrayKind(kind) and mojom.IsBoolKind(kind.kind):
154    return "decodeArrayPointer(codec.PackedBool)"
155  if mojom.IsArrayKind(kind):
156    return "decodeArrayPointer(%s)" % CodecType(kind.kind)
157  if mojom.IsUnionKind(kind):
158    return "decodeUnion(%s)" % CodecType(kind)
159  if mojom.IsInterfaceRequestKind(kind):
160    return JavaScriptDecodeSnippet(mojom.MSGPIPE)
161  if mojom.IsEnumKind(kind):
162    return JavaScriptDecodeSnippet(mojom.INT32)
163  raise Exception("No decode snippet for %s" % kind)
164
165
166def JavaScriptEncodeSnippet(kind):
167  if (kind in mojom.PRIMITIVES or mojom.IsUnionKind(kind) or
168      mojom.IsInterfaceKind(kind) or mojom.IsAssociatedKind(kind)):
169    return "encodeStruct(%s, " % CodecType(kind)
170  if mojom.IsUnionKind(kind):
171    return "encodeStruct(%s, " % JavaScriptType(kind)
172  if mojom.IsStructKind(kind):
173    return "encodeStructPointer(%s, " % JavaScriptType(kind)
174  if mojom.IsMapKind(kind):
175    return "encodeMapPointer(%s, %s, " % \
176        (ElementCodecType(kind.key_kind), ElementCodecType(kind.value_kind))
177  if mojom.IsArrayKind(kind) and mojom.IsBoolKind(kind.kind):
178    return "encodeArrayPointer(codec.PackedBool, ";
179  if mojom.IsArrayKind(kind):
180    return "encodeArrayPointer(%s, " % CodecType(kind.kind)
181  if mojom.IsInterfaceRequestKind(kind):
182    return JavaScriptEncodeSnippet(mojom.MSGPIPE)
183  if mojom.IsEnumKind(kind):
184    return JavaScriptEncodeSnippet(mojom.INT32)
185  raise Exception("No encode snippet for %s" % kind)
186
187
188def JavaScriptUnionDecodeSnippet(kind):
189  if mojom.IsUnionKind(kind):
190    return "decodeStructPointer(%s)" % JavaScriptType(kind)
191  return JavaScriptDecodeSnippet(kind)
192
193
194def JavaScriptUnionEncodeSnippet(kind):
195  if mojom.IsUnionKind(kind):
196    return "encodeStructPointer(%s, " % JavaScriptType(kind)
197  return JavaScriptEncodeSnippet(kind)
198
199
200def JavaScriptFieldOffset(packed_field):
201  return "offset + codec.kStructHeaderSize + %s" % packed_field.offset
202
203
204def JavaScriptNullableParam(field):
205  return "true" if mojom.IsNullableKind(field.kind) else "false"
206
207
208def GetArrayExpectedDimensionSizes(kind):
209  expected_dimension_sizes = []
210  while mojom.IsArrayKind(kind):
211    expected_dimension_sizes.append(generator.ExpectedArraySize(kind) or 0)
212    kind = kind.kind
213  # Strings are serialized as variable-length arrays.
214  if (mojom.IsStringKind(kind)):
215    expected_dimension_sizes.append(0)
216  return expected_dimension_sizes
217
218
219def JavaScriptValidateArrayParams(field):
220  nullable = JavaScriptNullableParam(field)
221  element_kind = field.kind.kind
222  element_size = pack.PackedField.GetSizeForKind(element_kind)
223  expected_dimension_sizes = GetArrayExpectedDimensionSizes(
224      field.kind)
225  element_type = ElementCodecType(element_kind)
226  return "%s, %s, %s, %s, 0" % \
227      (element_size, element_type, nullable,
228       expected_dimension_sizes)
229
230
231def JavaScriptValidateStructParams(field):
232  nullable = JavaScriptNullableParam(field)
233  struct_type = JavaScriptType(field.kind)
234  return "%s, %s" % (struct_type, nullable)
235
236def JavaScriptValidateUnionParams(field):
237  nullable = JavaScriptNullableParam(field)
238  union_type = JavaScriptType(field.kind)
239  return "%s, %s" % (union_type, nullable)
240
241def JavaScriptValidateMapParams(field):
242  nullable = JavaScriptNullableParam(field)
243  keys_type = ElementCodecType(field.kind.key_kind)
244  values_kind = field.kind.value_kind;
245  values_type = ElementCodecType(values_kind)
246  values_nullable = "true" if mojom.IsNullableKind(values_kind) else "false"
247  return "%s, %s, %s, %s" % \
248      (nullable, keys_type, values_type, values_nullable)
249
250
251def JavaScriptValidateStringParams(field):
252  nullable = JavaScriptNullableParam(field)
253  return "%s" % (nullable)
254
255
256def JavaScriptValidateHandleParams(field):
257  nullable = JavaScriptNullableParam(field)
258  return "%s" % (nullable)
259
260def JavaScriptValidateInterfaceParams(field):
261  return JavaScriptValidateHandleParams(field)
262
263def JavaScriptProxyMethodParameterValue(parameter):
264  name = parameter.name;
265  if (mojom.IsInterfaceKind(parameter.kind)):
266   type = JavaScriptType(parameter.kind)
267   return "core.isHandle(%s) ? %s : connection.bindImpl" \
268       "(%s, %s)" % (name, name, name, type)
269  if (mojom.IsInterfaceRequestKind(parameter.kind)):
270   type = JavaScriptType(parameter.kind.kind)
271   return "core.isHandle(%s) ? %s : connection.bindProxy" \
272       "(%s, %s)" % (name, name, name, type)
273  return name;
274
275
276def JavaScriptStubMethodParameterValue(parameter):
277  name = parameter.name;
278  if (mojom.IsInterfaceKind(parameter.kind)):
279   type = JavaScriptType(parameter.kind)
280   return "connection.bindHandleToProxy(%s, %s)" % (name, type)
281  if (mojom.IsInterfaceRequestKind(parameter.kind)):
282   type = JavaScriptType(parameter.kind.kind)
283   return "connection.bindHandleToStub(%s, %s)" % (name, type)
284  return name;
285
286
287def TranslateConstants(token):
288  if isinstance(token, (mojom.EnumValue, mojom.NamedValue)):
289    # Both variable and enum constants are constructed like:
290    # NamespaceUid.Struct[.Enum].CONSTANT_NAME
291    name = []
292    if token.imported_from:
293      name.append(token.imported_from["unique_name"])
294    if token.parent_kind:
295      name.append(token.parent_kind.name)
296    if isinstance(token, mojom.EnumValue):
297      name.append(token.enum.name)
298    name.append(token.name)
299    return ".".join(name)
300
301  if isinstance(token, mojom.BuiltinValue):
302    if token.value == "double.INFINITY" or token.value == "float.INFINITY":
303      return "Infinity";
304    if token.value == "double.NEGATIVE_INFINITY" or \
305       token.value == "float.NEGATIVE_INFINITY":
306      return "-Infinity";
307    if token.value == "double.NAN" or token.value == "float.NAN":
308      return "NaN";
309
310  return token
311
312
313def ExpressionToText(value):
314  return TranslateConstants(value)
315
316def IsArrayPointerField(field):
317  return mojom.IsArrayKind(field.kind)
318
319def IsStringPointerField(field):
320  return mojom.IsStringKind(field.kind)
321
322def IsStructPointerField(field):
323  return mojom.IsStructKind(field.kind)
324
325def IsMapPointerField(field):
326  return mojom.IsMapKind(field.kind)
327
328def IsHandleField(field):
329  return mojom.IsAnyHandleKind(field.kind)
330
331def IsInterfaceField(field):
332  return mojom.IsInterfaceKind(field.kind)
333
334def IsUnionField(field):
335  return mojom.IsUnionKind(field.kind)
336
337
338class Generator(generator.Generator):
339
340  js_filters = {
341    "default_value": JavaScriptDefaultValue,
342    "payload_size": JavaScriptPayloadSize,
343    "decode_snippet": JavaScriptDecodeSnippet,
344    "encode_snippet": JavaScriptEncodeSnippet,
345    "union_decode_snippet": JavaScriptUnionDecodeSnippet,
346    "union_encode_snippet": JavaScriptUnionEncodeSnippet,
347    "expression_to_text": ExpressionToText,
348    "field_offset": JavaScriptFieldOffset,
349    "has_callbacks": mojom.HasCallbacks,
350    "is_array_pointer_field": IsArrayPointerField,
351    "is_map_pointer_field": IsMapPointerField,
352    "is_struct_pointer_field": IsStructPointerField,
353    "is_string_pointer_field": IsStringPointerField,
354    "is_union_field": IsUnionField,
355    "is_handle_field": IsHandleField,
356    "is_interface_field": IsInterfaceField,
357    "js_type": JavaScriptType,
358    "js_proxy_method_parameter_value": JavaScriptProxyMethodParameterValue,
359    "js_stub_method_parameter_value": JavaScriptStubMethodParameterValue,
360    "stylize_method": generator.StudlyCapsToCamel,
361    "validate_array_params": JavaScriptValidateArrayParams,
362    "validate_handle_params": JavaScriptValidateHandleParams,
363    "validate_interface_params": JavaScriptValidateInterfaceParams,
364    "validate_map_params": JavaScriptValidateMapParams,
365    "validate_string_params": JavaScriptValidateStringParams,
366    "validate_struct_params": JavaScriptValidateStructParams,
367    "validate_union_params": JavaScriptValidateUnionParams,
368  }
369
370  def GetParameters(self):
371    return {
372      "namespace": self.module.namespace,
373      "imports": self.GetImports(),
374      "kinds": self.module.kinds,
375      "enums": self.module.enums,
376      "module": self.module,
377      "structs": self.GetStructs() + self.GetStructsFromMethods(),
378      "unions": self.GetUnions(),
379      "interfaces": self.GetInterfaces(),
380      "imported_interfaces": self.GetImportedInterfaces(),
381    }
382
383  @staticmethod
384  def GetTemplatePrefix():
385    return "js_templates"
386
387  @classmethod
388  def GetFilters(cls):
389    return cls.js_filters
390
391  @UseJinja("module.amd.tmpl")
392  def GenerateAMDModule(self):
393    return self.GetParameters()
394
395  def GenerateFiles(self, args):
396    if self.variant:
397      raise Exception("Variants not supported in JavaScript bindings.")
398
399    self.Write(self.GenerateAMDModule(),
400        self.MatchMojomFilePath("%s.js" % self.module.name))
401
402  def GetImports(self):
403    used_names = set()
404    for each_import in self.module.imports:
405      simple_name = each_import["module_name"].split(".")[0]
406
407      # Since each import is assigned a variable in JS, they need to have unique
408      # names.
409      unique_name = simple_name
410      counter = 0
411      while unique_name in used_names:
412        counter += 1
413        unique_name = simple_name + str(counter)
414
415      used_names.add(unique_name)
416      each_import["unique_name"] = unique_name + "$"
417      counter += 1
418    return self.module.imports
419
420  def GetImportedInterfaces(self):
421    interface_to_import = {};
422    for each_import in self.module.imports:
423      for each_interface in each_import["module"].interfaces:
424        name = each_interface.name
425        interface_to_import[name] = each_import["unique_name"] + "." + name
426    return interface_to_import;
427
428