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.IsAnyArrayKind(field.kind):
56    return "null"
57  if mojom.IsInterfaceKind(field.kind) or \
58     mojom.IsInterfaceRequestKind(field.kind):
59    return _kind_to_javascript_default_value[mojom.MSGPIPE]
60  if mojom.IsEnumKind(field.kind):
61    return "0"
62
63
64def JavaScriptPayloadSize(packed):
65  packed_fields = packed.packed_fields
66  if not packed_fields:
67    return 0
68  last_field = packed_fields[-1]
69  offset = last_field.offset + last_field.size
70  pad = pack.GetPad(offset, 8)
71  return offset + pad
72
73
74_kind_to_codec_type = {
75  mojom.BOOL:                  "codec.Uint8",
76  mojom.INT8:                  "codec.Int8",
77  mojom.UINT8:                 "codec.Uint8",
78  mojom.INT16:                 "codec.Int16",
79  mojom.UINT16:                "codec.Uint16",
80  mojom.INT32:                 "codec.Int32",
81  mojom.UINT32:                "codec.Uint32",
82  mojom.FLOAT:                 "codec.Float",
83  mojom.HANDLE:                "codec.Handle",
84  mojom.DCPIPE:                "codec.Handle",
85  mojom.DPPIPE:                "codec.Handle",
86  mojom.MSGPIPE:               "codec.Handle",
87  mojom.SHAREDBUFFER:          "codec.Handle",
88  mojom.NULLABLE_HANDLE:       "codec.NullableHandle",
89  mojom.NULLABLE_DCPIPE:       "codec.NullableHandle",
90  mojom.NULLABLE_DPPIPE:       "codec.NullableHandle",
91  mojom.NULLABLE_MSGPIPE:      "codec.NullableHandle",
92  mojom.NULLABLE_SHAREDBUFFER: "codec.NullableHandle",
93  mojom.INT64:                 "codec.Int64",
94  mojom.UINT64:                "codec.Uint64",
95  mojom.DOUBLE:                "codec.Double",
96  mojom.STRING:                "codec.String",
97  mojom.NULLABLE_STRING:       "codec.NullableString",
98}
99
100
101def CodecType(kind):
102  if kind in mojom.PRIMITIVES:
103    return _kind_to_codec_type[kind]
104  if mojom.IsStructKind(kind):
105    pointer_type = "NullablePointerTo" if mojom.IsNullableKind(kind) \
106        else "PointerTo"
107    return "new codec.%s(%s)" % (pointer_type, JavaScriptType(kind))
108  if mojom.IsAnyArrayKind(kind):
109    array_type = "NullableArrayOf" if mojom.IsNullableKind(kind) else "ArrayOf"
110    element_type = "codec.PackedBool" if mojom.IsBoolKind(kind.kind) \
111        else CodecType(kind.kind)
112    return "new codec.%s(%s)" % (array_type, element_type)
113  if mojom.IsInterfaceKind(kind) or mojom.IsInterfaceRequestKind(kind):
114    return CodecType(mojom.MSGPIPE)
115  if mojom.IsEnumKind(kind):
116    return _kind_to_codec_type[mojom.INT32]
117  return kind
118
119
120def JavaScriptDecodeSnippet(kind):
121  if kind in mojom.PRIMITIVES:
122    return "decodeStruct(%s)" % CodecType(kind)
123  if mojom.IsStructKind(kind):
124    return "decodeStructPointer(%s)" % JavaScriptType(kind)
125  if mojom.IsAnyArrayKind(kind) and mojom.IsBoolKind(kind.kind):
126    return "decodeArrayPointer(codec.PackedBool)"
127  if mojom.IsAnyArrayKind(kind):
128    return "decodeArrayPointer(%s)" % CodecType(kind.kind)
129  if mojom.IsInterfaceKind(kind) or mojom.IsInterfaceRequestKind(kind):
130    return JavaScriptDecodeSnippet(mojom.MSGPIPE)
131  if mojom.IsEnumKind(kind):
132    return JavaScriptDecodeSnippet(mojom.INT32)
133
134
135def JavaScriptEncodeSnippet(kind):
136  if kind in mojom.PRIMITIVES:
137    return "encodeStruct(%s, " % CodecType(kind)
138  if mojom.IsStructKind(kind):
139    return "encodeStructPointer(%s, " % JavaScriptType(kind)
140  if mojom.IsAnyArrayKind(kind) and mojom.IsBoolKind(kind.kind):
141    return "encodeArrayPointer(codec.PackedBool, ";
142  if mojom.IsAnyArrayKind(kind):
143    return "encodeArrayPointer(%s, " % CodecType(kind.kind)
144  if mojom.IsInterfaceKind(kind) or mojom.IsInterfaceRequestKind(kind):
145    return JavaScriptEncodeSnippet(mojom.MSGPIPE)
146  if mojom.IsEnumKind(kind):
147    return JavaScriptEncodeSnippet(mojom.INT32)
148
149
150def JavaScriptFieldOffset(packed_field):
151  return "offset + codec.kStructHeaderSize + %s" % packed_field.offset
152
153
154def JavaScriptNullableParam(packed_field):
155  return "true" if mojom.IsNullableKind(packed_field.field.kind) else "false"
156
157
158def JavaScriptValidateArrayParams(packed_field):
159  nullable = JavaScriptNullableParam(packed_field)
160  field_offset = JavaScriptFieldOffset(packed_field)
161  element_kind = packed_field.field.kind.kind
162  element_size = pack.PackedField.GetSizeForKind(element_kind)
163  element_count = generator.ExpectedArraySize(packed_field.field.kind)
164  element_type = "codec.PackedBool" if mojom.IsBoolKind(element_kind) \
165      else CodecType(element_kind)
166  return "%s, %s, %s, %s, %s" % \
167      (field_offset, element_size, element_count, element_type, nullable)
168
169
170def JavaScriptValidateStructParams(packed_field):
171  nullable = JavaScriptNullableParam(packed_field)
172  field_offset = JavaScriptFieldOffset(packed_field)
173  struct_type = JavaScriptType(packed_field.field.kind)
174  return "%s, %s, %s" % (field_offset, struct_type, nullable)
175
176
177def JavaScriptValidateStringParams(packed_field):
178  nullable = JavaScriptNullableParam(packed_field)
179  return "%s, %s" % (JavaScriptFieldOffset(packed_field), nullable)
180
181
182def JavaScriptValidateHandleParams(packed_field):
183  nullable = JavaScriptNullableParam(packed_field)
184  field_offset = JavaScriptFieldOffset(packed_field)
185  return "%s, %s" % (field_offset, nullable)
186
187
188def TranslateConstants(token):
189  if isinstance(token, (mojom.EnumValue, mojom.NamedValue)):
190    # Both variable and enum constants are constructed like:
191    # NamespaceUid.Struct[.Enum].CONSTANT_NAME
192    name = []
193    if token.imported_from:
194      name.append(token.imported_from["unique_name"])
195    if token.parent_kind:
196      name.append(token.parent_kind.name)
197    if isinstance(token, mojom.EnumValue):
198      name.append(token.enum.name)
199    name.append(token.name)
200    return ".".join(name)
201
202  if isinstance(token, mojom.BuiltinValue):
203    if token.value == "double.INFINITY" or token.value == "float.INFINITY":
204      return "Infinity";
205    if token.value == "double.NEGATIVE_INFINITY" or \
206       token.value == "float.NEGATIVE_INFINITY":
207      return "-Infinity";
208    if token.value == "double.NAN" or token.value == "float.NAN":
209      return "NaN";
210
211  return token
212
213
214def ExpressionToText(value):
215  return TranslateConstants(value)
216
217
218def IsArrayPointerField(field):
219  return mojom.IsAnyArrayKind(field.kind)
220
221def IsStringPointerField(field):
222  return mojom.IsStringKind(field.kind)
223
224def IsStructPointerField(field):
225  return mojom.IsStructKind(field.kind)
226
227def IsHandleField(field):
228  return mojom.IsAnyHandleKind(field.kind)
229
230
231class Generator(generator.Generator):
232
233  js_filters = {
234    "default_value": JavaScriptDefaultValue,
235    "payload_size": JavaScriptPayloadSize,
236    "decode_snippet": JavaScriptDecodeSnippet,
237    "encode_snippet": JavaScriptEncodeSnippet,
238    "expression_to_text": ExpressionToText,
239    "field_offset": JavaScriptFieldOffset,
240    "has_callbacks": mojom.HasCallbacks,
241    "is_array_pointer_field": IsArrayPointerField,
242    "is_struct_pointer_field": IsStructPointerField,
243    "is_string_pointer_field": IsStringPointerField,
244    "is_handle_field": IsHandleField,
245    "js_type": JavaScriptType,
246    "stylize_method": generator.StudlyCapsToCamel,
247    "validate_array_params": JavaScriptValidateArrayParams,
248    "validate_handle_params": JavaScriptValidateHandleParams,
249    "validate_string_params": JavaScriptValidateStringParams,
250    "validate_struct_params": JavaScriptValidateStructParams,
251  }
252
253  @UseJinja("js_templates/module.js.tmpl", filters=js_filters)
254  def GenerateJsModule(self):
255    return {
256      "namespace": self.module.namespace,
257      "imports": self.GetImports(),
258      "kinds": self.module.kinds,
259      "enums": self.module.enums,
260      "module": self.module,
261      "structs": self.GetStructs() + self.GetStructsFromMethods(),
262      "interfaces": self.module.interfaces,
263    }
264
265  def GenerateFiles(self, args):
266    self.Write(self.GenerateJsModule(), "%s.js" % self.module.name)
267
268  def GetImports(self):
269    # Since each import is assigned a variable in JS, they need to have unique
270    # names.
271    counter = 1
272    for each in self.module.imports:
273      each["unique_name"] = "import" + str(counter)
274      counter += 1
275    return self.module.imports
276