-
Notifications
You must be signed in to change notification settings - Fork 39
/
idl2domjs
executable file
·494 lines (436 loc) · 15.7 KB
/
idl2domjs
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
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
#!/usr/local/bin/node
/*
* idl2domjs: convert WebIDL interfaces into JavaScript source for dom.js
*
* Reads WebIDL from the file specified on the command line and writes
* JavaScript code to standard output. The output code is idiosyncratic
* to the dom.js project. In particular, the code it generates uses functions
* defined in ../src/idl.js
*
* This program makes no attempt to handle every nuance of WebIDL.
* I'll add features as I need them to translate particular DOM interfaces.
*
* This script uses a fork of darobin's JavaScript WebIDL parser:
* https://github.com/darobin/webidl.js and assumes that you've cloned
* the forked webidl.js repo into the top-level directory of this repo:
*
* cd dom.js; # cd to this repo
* git clone git@github.com:davidflanagan/webidl.js.git
*
* This program treats the [Callback] attribute like the [NoInterfaceObject]
* attribute and does not create an interface for it. So for DOM Core,
* there is no EventListener. Also, [Callback] interfaces
* need a different kind of conversion than other interfaces: they're not
* unwrapped, we just have to check that they are functions or objects.
*
* XXX Still need to handle exceptions
*
* XXX: I can probably automatically generate some simple tests from the
* IDL file as well. For each interface I, output a testI() function that
* takes an instance of the interface and verifies simple things like
* instanceof, that it has all of the attributes, operations and constants.
* For attributes, it could read the attribute and test that the value
* was the correct type. So testDocument() would call testElement() on the
* value of the documentElement property? And testElement() would chain to
* testNode(), since that is the superclass?
*
* Not sure how valuable these tests would be if they're generated by
* the same code that generates the classes....
*/
var infile = process.argv[2];
var out = console.log.bind(console);
if (!infile) {
console.error("No input file specified");
process.exit(1);
}
var sourceidl = require("fs").readFileSync(infile,"utf-8");
var webidl = require("../webidl.js/node/WebIDLParser.js")
var parsedidl;
try {
parsedidl = webidl.Parser.parse(sourceidl);
}
catch(e) {
console.log(e.name, ":", e.line, ":", e.column, ": ", e.message);
process.exit();
}
out("//");
out("// DO NOT EDIT.");
out("// This file was generated by idl2domjs from %s", infile);
out("//");
out();
var callbackTypes = {}; // A set of names of [Callback] interfaces
var arrayTypes = {}; // A set of array types we have to create
var dictionaryTypes = {} // A set of dictionary types
// First, loop through the interfaces, looking for callbacks and dictionaries
for(var i = 0; i < parsedidl.length; i++) {
var item = parsedidl[i];
if (item.type === "interface") {
var attrs = getExtAttrs(item);
if ("Callback" in attrs) {
callbackTypes[item.name] = true;
}
}
else if (item.type === "dictionary") {
dictionaryTypes[item.name] = item;
}
}
// Loop again, looking for, and fixing up implements declarations
for(var i = 0; i < parsedidl.length; i++) {
var item = parsedidl[i];
if (item.type === "implements") {
var targetName = item.target;
var sourceName = item.implements;
var target = null, source = null;
parsedidl.forEach(function(x) {
if (x.type !== "interface") return;
if (x.name === targetName) target = x;
if (x.name === sourceName) source = x;
});
if (target === null || source === null) {
console.warn("Skipping %s implements %s: \n\t" +
"source and/or target interface is unknown.",
item.target, item["implements"]);
}
else {
var copy = JSON.parse(JSON.stringify(source.members));
copy.forEach(function(x) { target.members.push(x); });
}
}
}
// Now loop through the interfaces, outputing definitions for them all
for(var i = 0; i < parsedidl.length; i++) {
var item = parsedidl[i];
if (item.type === "interface") {
outputInterface(item);
}
else if (item.type === "dictionary") {
outputDictionary(item);
}
else if (item.type === "implements") {
// Do nothing: we already handled implements declarations
}
else {
console.warn("Skipping %s %s", item.type, item.name);
}
}
// Now that we've output code for each of the IDL interfaces, we need
// to output classes for any array types that the IDL used.
// XXX: probably for sequences, too, if any APIs use them
for(var type in arrayTypes) {
out();
out('defineLazyProperty(idl, "%s", function() {', type);
out(' return new IDLInterface({');
out(' name: "%s",', type);
out(' proxyFactory: %sProxy,', type);
out(' });')
out('});')
}
var current_interface; // For error messages
function outputInterface(idl) {
// Don't output anything for Callback and NoInterfaceObject interfaces
var attrs = getExtAttrs(idl);
if ("Callback" in attrs || "NoInterfaceObject" in attrs) return;
current_interface = idl;
out();
out("//");
out("// Interface %s", idl.name);
out("//");
out();
var members = idl.members;
// Are there any constants in this interface?
var constants = members.some(function(m) { return m.type === "const" })
var prefix = ' '; // 12 spaces for pretty-printing
// If there are constants, define them "globally" (within the closure)
// so that they can be shared by the idl class and the implementation
// class and can be used without prefixes.
// XXX: this will break if dom core or html5 defines two constants
// with the same name in different interfaces.
if (constants) {
out("// Constants defined by %s", idl.name);
members.forEach(function(m) {
if (m.type === "const") {
// XXX what if the constant value needs to be quoted?
// I suppose that technically I should check the type of
// the constant.
out("const %s = %s;", m.name, m.value);
}
});
out();
}
out('defineLazyProperty(global, "' + idl.name + '", function() {');
out(' return idl.' + idl.name + '.publicInterface;');
out('}, true);');
out();
out('defineLazyProperty(idl, "' + idl.name + '", function() {');
out(' return new IDLInterface({');
out(' name: "' + idl.name + '",');
if (idl.inheritance) {
out(' superclass: idl.' + idl.inheritance[0] + ',');
}
// See if this interface has a constructor
// XXX: eventually I'll have to handle multiple constructor and
// named constructors as well. For now, just enough for event constructors.
if ("Constructor" in attrs) {
var c = attrs.Constructor;
out(' constructor: function %s%s{',
idl.name,
arglist(c," "));
out(' return wrap(new impl.%s(%s),',
idl.name,
invokeargs(c, prefix + " "));
out(' idl.%s);', idl.name);
out(' },');
}
// If the interface needs a proxy (because it has an indexed getter, e.g.)
// then specify that
if (needsProxy(idl)) {
out(' proxyFactory: %sProxy,', idl.name);
}
// If the interface defines constants
if (constants) {
out(' constants: {');
members.forEach(function(m) {
if (m.type === "const") {
// Output name twice because we declared the name
// as a const above.
out("%s%s: %s,", prefix, m.name, m.name);
}
});
out(' },');
}
out(' members: {');
members.forEach(function(m) {
// Check for overloaded members
if (m.name !== "") { // getters, setters, etc., have no name
var n = members.filter(function(x) {
return x.name === m.name;
}).length;
if (n > 1) {
console.warn("Skipping overloaded member %s.%s",
idl.name, m.name);
return;
}
}
if (m.type === "attribute")
outputAttribute(m, prefix);
else if (m.type === "operation") {
outputMethod(m, prefix);
}
});
out(' },');
out(' });');
out('});');
}
/*
* WebIDL dictionary types don't have any public representation, and
* the JS representation of dictionary instances are ordinary objects.
* So to output a dictionary we don't need to export anything to the global
* object. But we do need to create a conversion function that converts
* an object passed in to a DOM API (like the Event()) constructor to a
* type-checked legal object that we can pass to the implementation of that
* API
*/
function outputDictionary(d) {
current_interface = d;
out();
out("//");
out("// Dictionary %s", d.name);
out("//");
out();
// Looks like a constructor, but we're using it as a conversion func
out("function %s(o) {", d.name);
out(" var rv = O.create(%s.prototype);", d.name);
outputDictionaryMemberConversions(d);
out(" return rv;");
out("}");
out("function Optional%s(o) {", d.name);
out(" return (o === undefined) ? undefined : %s(o);", d.name);
out("}");
if (d.inheritance)
out("%s.prototype = O.create(%s.prototype);",
d.name, d.inheritance);
else
out("%s.prototype = {};", d.name);
for(var i = 0; i < d.members.length; i++) {
if (d.members[i].defaultValue) {
out("%s.prototype.%s = %s;",
d.name,
d.members[i].name,
d.members[i].defaultValue);
}
}
}
function outputDictionaryMemberConversions(d) {
if (d.inheritance) {
var s = dictionaryTypes[d.inheritance];
if (!s) {
console.warn("Unknown dictionary superclass %s", d.inheritance);
}
else {
outputDictionaryMemberConversions(s);
}
}
for(var i = 0; i < d.members.length; i++) {
var m = d.members[i];
out(" if ('%s' in o) rv['%s'] = %s;",
m.name,
m.name,
convert(m, "o['" + m.name + "']"));
}
}
function needsProxy(idl) {
// If any of the members of this interface are getters, then
// we need a proxy.
return idl.members.some(function(m) {
return (m.type === "operation") &&
(m.getter || m.setter || m.deleter || m.creator || m.caller);
});
}
function outputMethod(m, prefix) {
// Don't output special operation methods if they are omittable or if
// they don't have a name.
if (m.getter || m.setter || m.deleter || m.creator || m.caller) {
if (m.name === "" || m.omittable) return;
}
// Except for stringifiers: if a stringifier doesn't have a name,
// then name it toString.
// XXX What to do if a stringifier does have a name?
if (m.stringifier) {
if (m.name === "")
m.name = "toString";
else {
console.warn("Skipping named stringifier %s in %s",
m.name, current_interface.name);
return;
}
}
out("%s%s: function %s%s{", // intentionally missing spaces
prefix, m.name, m.name, arglist(m, prefix));
var invocation = "unwrap(this)." + m.name + "(" +
invokeargs(m, prefix + " ") + ")";
if (isInterfaceType(m.idlType)) {
out("%s let rv = %s;", prefix, invocation);
out("%s return wrap(rv, idl.%s);",
prefix, typeName(m.idlType));
}
else {
out("%s return %s;", prefix, invocation);
}
out("%s},", prefix);
out();
}
// Return a comma-separated arg list for method m
function arglist(m, prefix) {
if (m.arguments.length == 0)
return "() ";
else if (m.arguments.length == 1)
return "(" + m.arguments[0].name + ") ";
else {
var indent = "\n " + prefix;
var args = m.arguments.map(function(a) { return a.name });
var list = args.join("," + indent)
return "(" + indent + list + ")\n" + prefix;
}
}
// Convert the args and return a list suitable for an invocation
function invokeargs(m, prefix) {
var rv = m.arguments.
map(function(a) { return convert(a, a.name)}).
join(",\n" + prefix);
if (m.arguments.length > 1) rv = "\n" + prefix + rv;
return rv;
}
function outputAttribute(a, prefix) {
out("%sget %s() {", prefix, a.name);
if (isInterfaceType(a.idlType))
out("%s return wrap(unwrap(this).%s, idl.%s);",
prefix, a.name, typeName(a.idlType));
else
out("%s return unwrap(this).%s;", prefix, a.name);
out("%s},", prefix);
if (!a.readonly) {
// XXX output the setter method here
out("%sset %s(newval) {", prefix, a.name);
out("%s unwrap(this).%s = %s;",
prefix, a.name, convert(a, "newval"));
out("%s},", prefix);
}
out();
}
// XXX: interfaces with more than one Constructor attribute will have
// to do something different here. Currently we'll only see the last one.
function getExtAttrs(a) {
var rv = {};
if (a.extAttrs) a.extAttrs.forEach(function(o) { rv[o.name] = o; });
return rv;
}
// Return a string that invokes a conversion function on v, like Boolean(v)
// A is an attribute or a method argument or a dictionary member
function convert(a, v) {
// a is an attribute record or an argument record
var t = a.idlType || a.type; // idlType for attributes, type for arguments
if (t.sequence || t.array) {
console.warn("Don't know now to convert sequence or array of %s in %s",
t.idlType, current_interface.name)
return v;
}
var c;
if (callbackTypes[t.idlType])
c = "toCallback";
else if (isDictionaryType(t))
c = t.idlType;
else if (isInterfaceType(t))
c = "unwrap";
else if (t.idlType === "DOMString")
c = "String";
else if (t.idlType === "unsigned long")
c = "toULong";
else if (t.idlType === "long")
c = "toLong";
else if (t.idlType === "double")
c = "Number";
else if (t.idlType === "boolean")
c = "Boolean";
else if (t.idlType === "object")
c = "Object";
else if (t.idlType === "any")
c = "";
else
throw new Error("Need a converter function for: " + t.idlType);
if (t.nullable) c += "OrNull";
if (a.optional) c = "Optional" + c;
// XXX:
// WebIDL may be making this the default and using
// TreatNullAs=String for the current default behavior.
var attrs = getExtAttrs(a);
if (t.idlType === "DOMString" &&
attrs.TreatNullAs &&
attrs.TreatNullAs.value === "EmptyString")
c += "OrEmpty";
if (c)
return c + "(" + v + ")";
else
return v;
}
function typeName(t) {
var s = t.idlType;
if (t.array) {
s += "Array";
// Remember that we've seen this type so we can output an
// IDL interface for it later.
arrayTypes[s] = true;
}
if (t.sequence) s+= "Sequence";
return s;
}
function isDictionaryType(t) {
return t.idlType && dictionaryTypes[t.idlType];
}
function isInterfaceType(t) {
// WebIDL primitive types are all lower case or DOMString
// We'll just assume that anything else is an interface type
if (t === "void") // Annoyingly, void is just a string
return false;
return (t.idlType !== "DOMString" &&
t.idlType !== t.idlType.toLowerCase())
}