summaryrefslogtreecommitdiffstats
path: root/xpcom/idl-parser/xpidl/jsonxpt.py
blob: 342188f6451b1022684792c6252b791c8f5c899e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
#!/usr/bin/env python
# jsonxpt.py - Generate json XPT typelib files from IDL.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""Generate a json XPT typelib for an IDL file"""

import itertools
import json

from xpidl import xpidl

# A map of xpidl.py types to xpt enum variants
TypeMap = {
    # builtins
    "boolean": "TD_BOOL",
    "void": "TD_VOID",
    "int16_t": "TD_INT16",
    "int32_t": "TD_INT32",
    "int64_t": "TD_INT64",
    "uint8_t": "TD_UINT8",
    "uint16_t": "TD_UINT16",
    "uint32_t": "TD_UINT32",
    "uint64_t": "TD_UINT64",
    "octet": "TD_UINT8",
    "short": "TD_INT16",
    "long": "TD_INT32",
    "long long": "TD_INT64",
    "unsigned short": "TD_UINT16",
    "unsigned long": "TD_UINT32",
    "unsigned long long": "TD_UINT64",
    "float": "TD_FLOAT",
    "double": "TD_DOUBLE",
    "char": "TD_CHAR",
    "string": "TD_PSTRING",
    "wchar": "TD_WCHAR",
    "wstring": "TD_PWSTRING",
    # special types
    "nsid": "TD_NSID",
    "astring": "TD_ASTRING",
    "utf8string": "TD_UTF8STRING",
    "cstring": "TD_CSTRING",
    "jsval": "TD_JSVAL",
    "promise": "TD_PROMISE",
}


def flags(*flags):
    return [flag for flag, cond in flags if cond]


def get_type(type, calltype, iid_is=None, size_is=None):
    while isinstance(type, xpidl.Typedef):
        type = type.realtype

    if isinstance(type, xpidl.Builtin):
        ret = {"tag": TypeMap[type.name]}
        if type.name in ["string", "wstring"] and size_is is not None:
            ret["tag"] += "_SIZE_IS"
            ret["size_is"] = size_is
        return ret

    if isinstance(type, xpidl.Array):
        # NB: For a Array<T> we pass down the iid_is to get the type of T.
        #     This allows Arrays of InterfaceIs types to work.
        return {
            "tag": "TD_ARRAY",
            "element": get_type(type.type, calltype, iid_is),
        }

    if isinstance(type, xpidl.LegacyArray):
        # NB: For a Legacy [array] T we pass down iid_is to get the type of T.
        #     This allows [array] of InterfaceIs types to work.
        return {
            "tag": "TD_LEGACY_ARRAY",
            "size_is": size_is,
            "element": get_type(type.type, calltype, iid_is),
        }

    if isinstance(type, xpidl.Interface) or isinstance(type, xpidl.Forward):
        return {
            "tag": "TD_INTERFACE_TYPE",
            "name": type.name,
        }

    if isinstance(type, xpidl.WebIDL):
        return {
            "tag": "TD_DOMOBJECT",
            "name": type.name,
            "native": type.native,
            "headerFile": type.headerFile,
        }

    if isinstance(type, xpidl.Native):
        if type.specialtype == "nsid" and type.isPtr(calltype):
            return {"tag": "TD_NSIDPTR"}
        elif type.specialtype:
            return {"tag": TypeMap[type.specialtype]}
        elif iid_is is not None:
            return {
                "tag": "TD_INTERFACE_IS_TYPE",
                "iid_is": iid_is,
            }
        else:
            return {"tag": "TD_VOID"}

    if isinstance(type, xpidl.CEnum):
        # As far as XPConnect is concerned, cenums are just unsigned integers.
        return {"tag": "TD_UINT%d" % type.width}

    raise Exception("Unknown type!")


def mk_param(type, in_=0, out=0, optional=0):
    return {
        "type": type,
        "flags": flags(
            ("in", in_),
            ("out", out),
            ("optional", optional),
        ),
    }


def mk_method(method, params, getter=0, setter=0, optargc=0, hasretval=0, symbol=0):
    return {
        "name": method.name,
        # NOTE: We don't include any return value information here, as we'll
        # never call the methods if they're marked notxpcom, and all xpcom
        # methods return the same type (nsresult).
        # XXX: If we ever use these files for other purposes than xptcodegen we
        # may want to write that info.
        "params": params,
        "flags": flags(
            ("getter", getter),
            ("setter", setter),
            ("hidden", method.noscript or method.notxpcom),
            ("optargc", optargc),
            ("jscontext", method.implicit_jscontext),
            ("hasretval", hasretval),
            ("symbol", method.symbol),
        ),
    }


def attr_param_idx(p, m, attr):
    attr_val = getattr(p, attr, None)
    if not attr_val:
        return None
    for i, param in enumerate(m.params):
        if param.name == attr_val:
            return i
    raise Exception(f"Need parameter named '{attr_val}' for attribute '{attr}'")


def build_interface(iface):
    if iface.namemap is None:
        raise Exception("Interface was not resolved.")

    assert (
        iface.attributes.scriptable
    ), "Don't generate XPT info for non-scriptable interfaces"

    # State used while building an interface
    consts = []
    methods = []

    def build_const(c):
        consts.append(
            {
                "name": c.name,
                "type": get_type(c.basetype, ""),
                "value": c.getValue(),  # All of our consts are numbers
            }
        )

    def build_cenum(b):
        for var in b.variants:
            consts.append(
                {
                    "name": var.name,
                    "type": get_type(b, "in"),
                    "value": var.value,
                }
            )

    def build_method(m):
        params = []
        for p in m.params:
            params.append(
                mk_param(
                    get_type(
                        p.realtype,
                        p.paramtype,
                        iid_is=attr_param_idx(p, m, "iid_is"),
                        size_is=attr_param_idx(p, m, "size_is"),
                    ),
                    in_=p.paramtype.count("in"),
                    out=p.paramtype.count("out"),
                    optional=p.optional,
                )
            )

        hasretval = len(m.params) > 0 and m.params[-1].retval
        if not m.notxpcom and m.realtype.name != "void":
            hasretval = True
            params.append(mk_param(get_type(m.realtype, "out"), out=1))

        methods.append(
            mk_method(m, params, optargc=m.optional_argc, hasretval=hasretval)
        )

    def build_attr(a):
        assert a.realtype.name != "void"
        # Write the getter
        getter_params = []
        if not a.notxpcom:
            getter_params.append(mk_param(get_type(a.realtype, "out"), out=1))

        methods.append(mk_method(a, getter_params, getter=1, hasretval=1))

        # And maybe the setter
        if not a.readonly:
            param = mk_param(get_type(a.realtype, "in"), in_=1)
            methods.append(mk_method(a, [param], setter=1))

    for member in iface.members:
        if isinstance(member, xpidl.ConstMember):
            build_const(member)
        elif isinstance(member, xpidl.Attribute):
            build_attr(member)
        elif isinstance(member, xpidl.Method):
            build_method(member)
        elif isinstance(member, xpidl.CEnum):
            build_cenum(member)
        elif isinstance(member, xpidl.CDATA):
            pass
        else:
            raise Exception("Unexpected interface member: %s" % member)

    return {
        "name": iface.name,
        "uuid": iface.attributes.uuid,
        "methods": methods,
        "consts": consts,
        "parent": iface.base,
        "flags": flags(
            ("function", iface.attributes.function),
            ("builtinclass", iface.attributes.builtinclass),
            ("main_process_only", iface.attributes.main_process_scriptable_only),
        ),
    }


# These functions are the public interface of this module. They are very simple
# functions, but are exported so that if we need to do something more
# complex in them in the future we can.


def build_typelib(idl):
    """Given a parsed IDL file, generate and return the typelib"""
    return [
        build_interface(p)
        for p in idl.productions
        if p.kind == "interface" and p.attributes.scriptable
    ]


def link(typelibs):
    """Link a list of typelibs together into a single typelib"""
    linked = list(itertools.chain.from_iterable(typelibs))
    assert len(set(iface["name"] for iface in linked)) == len(
        linked
    ), "Multiple typelibs containing the same interface were linked together"
    return linked


def write(typelib, fd):
    """Write typelib into fd"""
    json.dump(typelib, fd, indent=2, sort_keys=True)