1 module webidl.binding.generator;
2 
3 import std.stdio;
4 import webidl.grammar;
5 import pegged.grammar : ParseTree;
6 
7 import std.array : appender, array, Appender;
8 import std.algorithm;
9 import std.range : chain, enumerate;
10 import std.conv : text, to;
11 import std.range : zip, only, retro;
12 import std.typecons : Flag, No, Yes;
13 import openmethods;
14 
15 enum is32Bit = true;
16 
17 enum dKeywords = ["abstract","alias","align","asm","assert","auto","body","bool","break","byte","case","cast","catch","cdouble","cent","cfloat","char","class","const","continue","creal","dchar","debug","default","delegate","delete","deprecated","do","double","else","enum","export","extern","false","final","finally","float","for","foreach","foreach_reverse","function","goto","idouble","if","ifloat","immutable","import","in","inout","int","interface","invariant","ireal","is","lazy","long","macro","mixin","module","new","nothrow","null","out","override","package","pragma","private","protected","public","pure","real","ref","return","scope","shared","short","static","struct","super","switch","synchronized","template","this","throw","true","try","typedef","typeid","typeof","ubyte","ucent","uint","ulong","union","unittest","ushort","version","void","wchar","while","with","__FILE__","__FILE_FULL_PATH__","__MODULE__","__LINE__","__FUNCTION__","__PRETTY_FUNCTION__","__gshared","__traits","__vector","__parameters","__DATE__","__EOF__","__TIME__","__TIMESTAMP__","__VENDOR__","__VERSION__"];
18 
19 enum jsKeywords = ["default", "arguments"];
20 mixin(registerMethods);
21 
22 enum FunctionType { Function = 1, Attribute = 2, Static = 4, OpIndex = 8, OpIndexAssign = 16, OpDispatch = 32, Getter = 64, Setter = 128, Deleter = 256, Includes = 512, Partial = 1024, DictionaryConstructor = 2048, ExposedConstructor = 4096 };
23 
24 struct Argument {
25   string name;
26   ParseTree type;
27   ParseTree default_;
28   ParseTree argRest;
29   bool templated = false;
30 }
31 
32 struct JsExportFunction {
33   string parentTypeName;
34   string name;
35   Argument[] args;
36   ParseTree result;
37   FunctionType type;
38   string manglePostfix;
39 }
40 
41 struct DBindingFunction {
42   string parentTypeName;
43   string name;
44   Argument[] args;
45   ParseTree result;
46   FunctionType type;
47   string manglePostfix;
48   string baseType;
49   string customName;
50   string handle;
51 }
52 
53 struct DImportFunction {
54   string parentTypeName;
55   string name;
56   Argument[] args;
57   ParseTree result;
58   FunctionType type;
59   string manglePostfix;
60 }
61 
62 interface Node {
63   void toString(scope void delegate(const(char)[]) sink);
64 }
65 
66 void indentToString(scope void delegate(const(char)[]) sink, Node[] children) {
67   foreach(child; children) {
68     bool begin = true;
69     child.toString((const(char)[] line){
70         if (!line)
71           return;
72         if (begin)
73           sink("  ");
74         sink(line);
75         begin = line[$-1] == '\n';
76       });
77     if (!begin)
78       sink("\n");
79   }
80 }
81 class ModuleNode : Node {
82   Module module_;
83   Node[] children;
84   this(Module module_, Node[] children) {
85     this.module_ = module_;
86     this.children = children;
87   }
88   void toString(scope void delegate(const(char)[]) sink) {
89     sink("Module ");
90     sink(module_.name);
91     sink("\n");
92     sink.indentToString(children);
93   }
94 }
95 class ConstNode : Node {
96   string type;
97   string name;
98   string value;
99   this(string type, string name, string value) {
100     this.type = type;
101     this.name = name;
102     this.value = value;
103   }
104   void toString(scope void delegate(const(char)[]) sink) {
105     sink(name);
106   }
107 }
108 
109 class StructNode : Node {
110   string name;
111   ParseTree baseType;
112   Node[] children;
113   string[] functions;
114   Flag!"isStatic" isStatic;
115   this(string name, ParseTree baseType , Node[] children, string[] functions, Flag!"isStatic" isStatic = No.isStatic) {
116     this(name, baseType, children, isStatic);
117     this.functions = functions;
118   }
119   this(string name, ParseTree baseType , Node[] children, Flag!"isStatic" isStatic = No.isStatic) {
120     this.name = name;
121     this.baseType = baseType;
122     this.children = children;
123     this.isStatic = isStatic;
124   }
125   void toString(scope void delegate(const(char)[]) sink) {
126     sink("Struct ");
127     sink(name);
128     sink("\n");
129     sink.indentToString(children);
130   }
131   string getHandleSymbol() {
132     if (baseType != ParseTree.init)
133       return "_parent";
134     return "handle";
135   }
136 }
137 
138 void toDBinding(virtual!Node node, Semantics semantics, IndentedStringAppender* a);
139 void toDBinding(virtual!Node node, StructNode parent, Semantics semantics, IndentedStringAppender* a);
140 void toJsExport(virtual!Node node, Semantics semantics, string[] filter, IndentedStringAppender* a);
141 void toJsExport(virtual!Node node, StructNode parent, Semantics semantics, string[] filter, IndentedStringAppender* a);
142 void toDImport(virtual!Node node, Semantics semantics, IndentedStringAppender* a);
143 void toDImport(virtual!Node node, StructNode parent, Semantics semantics, IndentedStringAppender* a);
144 
145 @method void _toDBinding(Node node, Semantics semantics, IndentedStringAppender* a) {}
146 @method void _toDBinding(ModuleNode node, Semantics semantics, IndentedStringAppender* a) {
147   node.children.each!(c => toDBinding(c, semantics, a));
148 }
149 @method void _toJsExport(Node node, Semantics semantics, string[] filter, IndentedStringAppender* a) {}
150 @method void _toJsExport(ModuleNode node, Semantics semantics, string[] filter, IndentedStringAppender* a) {
151   node.children.each!(c => toJsExport(c, semantics, filter, a));
152 }
153 @method void _toJsExport(StructNode node, Semantics semantics, string[] filter, IndentedStringAppender* a) {
154   import std.algorithm : canFind;
155   bool[string] names;
156   foreach(child; node.children) {
157     auto fun = cast(FunctionNode)child;
158     if (fun) {
159       string name = mangleJsName(node, fun);
160       if (auto p = name in names)
161         continue;
162       if (filter.length > 0 && !filter.canFind(name))
163         continue;
164       names[name] = true;
165     }
166     toJsExport(child, node, semantics, filter, a);
167   }
168 }
169 @method void _toDImport(Node node, Semantics semantics, IndentedStringAppender* a) {}
170 @method void _toDImport(ModuleNode node, Semantics semantics, IndentedStringAppender* a) {
171   node.children.each!(c => toDImport(c, semantics, a));
172 }
173 @method void _toDImport(StructNode node, Semantics semantics, IndentedStringAppender* a) {
174   bool[string] names;
175   foreach(child; node.children) {
176     auto fun = cast(FunctionNode)child;
177     if (fun) {
178       string name = mangleJsName(node, fun);
179       if (auto p = name in names)
180         continue;
181       names[name] = true;
182     }
183     toDImport(child, node, semantics, a);
184   }
185 }
186 
187 @method void _toDBinding(StructNode node, Semantics semantics, IndentedStringAppender* a) {
188   a.putLn(["struct ", node.name.friendlyName, " {"]);
189   a.indent();
190   a.putLn("nothrow:");
191   if (node.isStatic == Yes.isStatic) {
192     a.putLn("static:");
193   } else if (node.baseType != ParseTree.init) {
194     auto p = node.baseType.matches[0] in semantics.types;
195     if (p is null) {
196       writeln("Error: Cannot find definition of type ", node.baseType.matches[0], ".");
197     } else {
198       a.put(["spasm.bindings.", p.module_.name, "."]);
199     }
200     a.put(node.baseType.matches[0].friendlyName);
201     a.putLn(" _parent;");
202     a.putLn("alias _parent this;");
203     a.putLn("this(Handle h) {");
204     a.indent();
205     a.putLn(["_parent = .", node.baseType.matches[0].friendlyName,"(h);"]);
206     a.undent();
207     a.putLn("}");
208   } else {
209     a.putLn("JsHandle handle;");
210     a.putLn("alias handle this;");
211     a.putLn("this(Handle h) {");
212     a.indent();
213     a.putLn("this.handle = JsHandle(h);");
214     a.undent();
215     a.putLn("}");
216   }
217   node.children.each!(c => toDBinding(c, node, semantics, a));
218   a.undent();
219   a.putLn("}");
220 }
221 @method void _toDImport(MixinNode node, Semantics semantics, IndentedStringAppender* a) {
222   auto dummyParent = new StructNode(node.name, ParseTree.init, node.children);
223   bool[string] names;
224   foreach(child; node.children) {
225     auto fun = cast(FunctionNode)child;
226     if (fun) {
227       string name = mangleJsName(dummyParent, fun);
228       if (auto p = name in names)
229         continue;
230       names[name] = true;
231     }
232     toDImport(child, dummyParent, semantics, a);
233   }
234 }
235 
236 @method void _toDBinding(Node node, StructNode parent, Semantics semantics, IndentedStringAppender* a) {
237   // default od nothing
238 }
239 @method void _toJsExport(Node node, StructNode parent, Semantics semantics, string[] filter, IndentedStringAppender* a) {
240   // default od nothing
241 }
242 @method void _toDImport(Node node, StructNode parent, Semantics semantics, IndentedStringAppender* a) {
243   // default od nothing
244 }
245 @method void _toDBinding(ConstNode node, StructNode parent, Semantics semantics, IndentedStringAppender* a) {
246   a.putLn(["enum ", node.type, " ", node.name, " = ", node.value, ";"]);
247 }
248 
249 class StructIncludesNode : Node {
250   string name;
251   string baseType;
252   Node[] children;
253   this(string baseType, string name, Node[] children) {
254     this.name = name;
255     this.baseType = baseType;
256     this.children = children;
257   }
258   void toString(scope void delegate(const(char)[]) sink) {
259     sink("StructIncludes ");
260     sink(name);
261     sink("\n");
262     sink.indentToString(children);
263   }
264 }
265 
266 class MixinNode : Node {
267   string name;
268   Node[] children;
269   this(string name, Node[] children) {
270     this.name = name;
271     this.children = children;
272   }
273   void toString(scope void delegate(const(char)[]) sink) {
274     sink("MixinNode ");
275     sink(name);
276     sink("\n");
277     sink.indentToString(children);
278   }
279 }
280 @method void _toDBinding(StructIncludesNode node, StructNode parent, Semantics semantics, IndentedStringAppender* a) {
281   auto dummyParent = new StructNode(node.name, parent.baseType, node.children, parent.functions);
282   node.children.each!(c => toDBinding(c, dummyParent, semantics, a));
283 }
284 @method void _toJsExport(MixinNode node, Semantics semantics, string[] filter, IndentedStringAppender* a) {
285   import std.algorithm : canFind;
286   auto dummyParent = new StructNode(node.name, ParseTree.init, node.children);
287   bool[string] names;
288   foreach(child; node.children) {
289     auto fun = cast(FunctionNode)child;
290     if (fun) {
291       string name = mangleJsName(dummyParent, fun);
292       if (auto p = name in names)
293         continue;
294       if (filter.length > 0 && !filter.canFind(name))
295         continue;
296       names[name] = true;
297     }
298     toJsExport(child, dummyParent, semantics, filter, a);
299   }
300 }
301 
302 @method void _toDImport(StructIncludesNode node, StructNode parent, Semantics semantics, IndentedStringAppender* a) {
303   // auto dummyParent = new StructNode(node.name, ParseTree.init, node.children);
304   // node.children.each!(c => toDImport(c, dummyParent, semantics, a));
305 }
306 
307 class FunctionNode : Node {
308   string name;
309   Argument[] args;
310   ParseTree result;
311   FunctionType type;
312   string manglePostfix;
313   string baseType;
314   string customName;
315   this(string name, Argument[] args, ParseTree result, FunctionType type, string manglePostfix, string baseType, string customName) {
316     this.name = name;
317     this.args = args;
318     this.result = result;
319     this.type = type;
320     this.manglePostfix = manglePostfix;
321     this.baseType = baseType;
322     this.customName = customName;
323   }
324   void toString(scope void delegate(const(char)[]) sink) {
325     sink("Function ");
326     if (customName.length > 0)
327       sink(customName);
328     else
329       sink(name);
330     if (manglePostfix.length) {
331       sink("_");
332       sink(manglePostfix);
333     }
334     sink("(");
335     sink(args.map!(a => a.name).joiner(", ").text());
336     sink(")");
337   }
338 }
339 
340 @method void _toDBinding(FunctionNode node, StructNode parent, Semantics semantics, IndentedStringAppender* a) {
341   auto tmp = DBindingFunction(parent.name, node.name, node.args, node.result, node.type, node.manglePostfix, node.baseType, node.customName, parent.getHandleSymbol());
342   if (parent.isStatic == Yes.isStatic)
343     tmp.type |= FunctionType.Static;
344   semantics.dump(tmp, a, parent.functions);
345   // TODO: use parent.functions to avoid local shadowing
346 }
347 @method void _toJsExport(FunctionNode node, StructNode parent, Semantics semantics, string[] filter, IndentedStringAppender* a) {
348   if (node.type & (FunctionType.OpDispatch) || node.type & (FunctionType.DictionaryConstructor))
349     return;
350   auto tmp = JsExportFunction(parent.name, node.customName != "" ? node.customName : node.name, node.args, node.result, node.type, node.manglePostfix);
351   if (parent.isStatic == Yes.isStatic)
352     tmp.type |= FunctionType.Static;
353   auto context = Context(semantics);
354   context.dump(tmp, a);
355 }
356 @method void _toDImport(FunctionNode node, StructNode parent, Semantics semantics, IndentedStringAppender* a) {
357   if (node.type & (FunctionType.OpDispatch) || node.type & (FunctionType.DictionaryConstructor))
358     return;
359   auto tmp = DImportFunction(parent.name, node.customName != "" ? node.customName : node.name, node.args, node.result, node.type, node.manglePostfix);
360   if (parent.isStatic == Yes.isStatic)
361     tmp.type |= FunctionType.Static;
362   semantics.dump(tmp, a);
363 }
364 
365 class ExposedConstructorNode : FunctionNode {
366   this(string name, Argument[] args, ParseTree result, string baseType, string manglePostfix) {
367     super(name, args, result, FunctionType.ExposedConstructor, manglePostfix, baseType, "");
368   }
369   override void toString(scope void delegate(const(char)[]) sink) {
370     sink("ExposedConstructor ");
371     sink(name);
372     if (manglePostfix) {
373       sink("_");
374       sink(manglePostfix);
375     }
376     sink("(");
377     sink(args.map!(a => a.name).joiner(", ").text());
378     sink(")");
379   }
380 }
381 @method void _toDBinding(ExposedConstructorNode node, StructNode parent, Semantics semantics, IndentedStringAppender* a) {
382   if (parent.name != node.baseType)
383     return;
384   auto tmp = DBindingFunction(parent.name, node.name, node.args, node.result, FunctionType.ExposedConstructor, node.manglePostfix, "", "", parent.getHandleSymbol());
385   semantics.dump(tmp, a, parent.functions);
386 }
387 @method void _toJsExport(ExposedConstructorNode node, StructNode parent, Semantics semantics, string[] filter, IndentedStringAppender* a) {
388   if (parent.name != node.baseType)
389     return;
390   string mangledName = mangleName(parent.name, node.name, node.manglePostfix);
391   if (filter.length > 0 && !filter.canFind(mangledName))
392     return;
393   auto tmp = JsExportFunction(parent.name, node.name, node.args, node.result, FunctionType.ExposedConstructor, node.manglePostfix);
394   auto context = Context(semantics);
395   context.dump(tmp, a);
396 }
397 @method void _toDImport(ExposedConstructorNode node, StructNode parent, Semantics semantics, IndentedStringAppender* a) {
398   if (parent.name != node.baseType)
399     return;
400   auto tmp = DImportFunction(parent.name, node.name, node.args, node.result, FunctionType.ExposedConstructor, node.manglePostfix);
401   semantics.dump(tmp, a);
402 }
403 
404 class TypedefNode : Node {
405   string name;
406   string def;
407   ParseTree rhs;
408   this(string n, string d, ParseTree rhs) {
409     name = n;
410     def = d;
411     this.rhs = rhs;
412   }
413   void toString(scope void delegate(const(char)[]) sink) {
414     sink("Typedef ");
415     sink(name);
416     sink(" = ");
417     sink(def);
418   }
419 }
420 @method void _toDBinding(TypedefNode node, Semantics semantics, IndentedStringAppender* a) {
421   a.putLn(["alias ", node.name, " = ", node.def, ";"]);
422 }
423 
424 class EnumNode : Node {
425   string name;
426   string content;
427   this(string n, string c) {
428     name = n;
429     content = c;
430   }
431   void toString(scope void delegate(const(char)[]) sink) {
432     sink("Enum ");
433     sink(name);
434   }
435 }
436 
437 @method void _toDBinding(EnumNode node, Semantics semantics, IndentedStringAppender* a) {
438   a.putLn(["enum ", node.name, " {"]);
439   a.indent();
440   a.putLn(node.content);
441   a.undent();
442   a.putLn("}");
443 }
444 
445 class MaplikeNode : Node {
446   ParseTree keyType;
447   ParseTree valueType;
448   this(ParseTree keyType, ParseTree valueType) {
449     this.keyType = keyType;
450     this.valueType = valueType;
451   }
452   void toString(scope void delegate(const(char)[]) sink) {
453     sink("Maplike");
454   }
455 }
456 
457 @method void _toDBinding(MaplikeNode node, StructNode parent, Semantics semantics, IndentedStringAppender* a) {
458   auto context = Context(semantics);
459   string keyType = node.keyType.generateDType(context);
460   string valueQualifiers = (semantics.isPrimitive(node.valueType) || semantics.isNullable(node.valueType)) ? "" : "scope ref ";
461   string valueType = node.valueType.generateDType(context);
462   string mangleKeyType = node.keyType.generateDImports(Context(semantics));
463   string mangleValueType = node.valueType.generateDImports(Context(semantics));
464   string manglePrefix = "Maplike_" ~ mangleKeyType ~ "_" ~ mangleValueType ~ "_";
465   a.putLn("uint size() {");
466   a.putLn(["  return ", manglePrefix, "size(this.handle);"]);
467   a.putLn("}");
468   a.putLn("void clear() {");
469   a.putLn(["  ", manglePrefix, "clear(this.handle);"]);
470   a.putLn("}");
471   a.putLn(["void delete_(",keyType," key) {"]);
472   a.putLn(["  ", manglePrefix, "delete(this.handle, key);"]);
473   a.putLn("}");
474   a.putLn(["Iterator!(ArrayPair!(",keyType,", ",valueType,")) entries() {"]);
475   a.putLn(["  return Iterator!(ArrayPair!(",keyType,", ",valueType,"))(", manglePrefix, "entries(this.handle));"]);
476   a.putLn("}");
477   a.putLn(["extern(C) void forEach(void delegate(",keyType,", Handle, Handle) callback) {"]);
478   a.putLn(["  ", manglePrefix, "forEach(this.handle, callback);"]);
479   a.putLn("}");
480   a.putLn(["",valueType," get(",keyType," key) {"]);
481   a.putLn(["  return ",valueType,"(", manglePrefix, "get(this.handle, key));"]);
482   a.putLn("}");
483   a.putLn(["bool has(",keyType," key) {"]);
484   a.putLn(["  return ", manglePrefix, "has(this.handle, key);"]);
485   a.putLn("}");
486   a.putLn(["Iterator!(",keyType,") keys() {"]);
487   a.putLn(["  return Iterator!(",keyType,")(", manglePrefix, "keys(this.handle));"]);
488   a.putLn("}");
489   a.putLn(["void set(",keyType," key, ", valueQualifiers, valueType," value) {"]);
490   a.putLn(["  ", manglePrefix, "set(this.handle, key, value.handle);"]);
491   a.putLn("}");
492   a.putLn(["Iterator!(",valueType,") values() {"]);
493   a.putLn(["  return Iterator!(",valueType,")(", manglePrefix, "values(this.handle));"]);
494   a.putLn("}");
495 }
496 @method void _toDImport(MaplikeNode node, StructNode parent, Semantics semantics, IndentedStringAppender* a) {
497   auto context = Context(semantics);
498   string keyType = node.keyType.generateDType(context);
499   string valueType = node.valueType.generateDType(context);
500   string mangleKeyType = node.keyType.generateDImports(Context(semantics));
501   string mangleValueType = node.valueType.generateDImports(Context(semantics));
502   string manglePrefix = "Maplike_" ~ mangleKeyType ~ "_" ~ mangleValueType ~ "_";
503   a.putLn(["extern (C) uint ", manglePrefix, "size(Handle);"]);
504   a.putLn(["extern (C) void ", manglePrefix, "clear(Handle);"]);
505   a.putLn(["extern (C) void ", manglePrefix, "delete(Handle, ", mangleKeyType," key);"]);
506   a.putLn(["extern (C) Handle ", manglePrefix, "entries(Handle);"]);
507   a.putLn(["extern (C) void ", manglePrefix, "forEach(Handle, void delegate(", keyType,", Handle, Handle));"]);
508   a.putLn(["extern (C) ", valueType, " ", manglePrefix, "get(Handle, ", mangleKeyType,");"]);
509   a.putLn(["extern (C) bool ", manglePrefix, "has(Handle, ", mangleKeyType,");"]);
510   a.putLn(["extern (C) Handle ", manglePrefix, "keys(Handle);"]);
511   a.putLn(["extern (C) void ", manglePrefix, "set(Handle, ", mangleKeyType, " key, ", mangleValueType, " value);"]);
512   a.putLn(["extern (C) Handle ", manglePrefix, "values(Handle);"]);
513 }
514 
515 class CallbackNode : Node {
516   string name;
517   ParseTree result;
518   Argument[] args;
519   this(string name, ParseTree result, Argument[] args) {
520     this.name = name;
521     this.result = result;
522     this.args = args;
523   }
524   void toString(scope void delegate(const(char)[]) sink) {
525     sink("Callback ");
526     sink(name);
527   }
528 }
529 
530 @method void _toDBinding(CallbackNode node, Semantics semantics, IndentedStringAppender* a) {
531   a.put(["alias ", node.name, " = "]);
532   if (node.result.matches[0] == "void") {
533     a.put("void");
534   } else
535     node.result.generateDType(a, Context(semantics));
536   auto types = node.args.map!(arg => arg.type).map!(type => type.generateDType(Context(semantics))).joiner(", ").text;
537   a.putLn([" delegate(", types, ");"]);
538 }
539 
540 void dumpJsArgument(Appender)(ref Semantics semantics, Argument arg, ref Appender a) {
541   if (semantics.isNullable(arg.type)) {
542     a.put(arg.name.friendlyJsName);
543     a.put("Defined ? ");
544   }
545   if (semantics.isTypedef(arg.type) && semantics.isCallback(arg.type)) {
546     auto name = getTypeName(arg.type);
547     auto aliased = semantics.getAliasedType(name);
548     auto argument = Argument(arg.name, aliased.stripNullable);
549     semantics.dumpJsArgument(argument, a);
550   } else if (semantics.isUnion(arg.type) || semantics.isEnum(arg.type)) {
551     a.put("spasm_decode_");
552     arg.type.mangleTypeJsImpl(a, semantics, MangleTypeJsContext(true));
553     a.put("(");
554     a.put(arg.name.friendlyJsName);
555     a.put(")");
556   } else if (semantics.isStringType(arg.type)) {
557     a.put(["spasm_decode_string(",arg.name.friendlyJsName,"Len, ",arg.name.friendlyJsName,"Ptr)"]);
558   } else if (semantics.isCallback(arg.type)){
559     auto signature = getTypeName(arg.type) in semantics.types;
560     auto argList = semantics.getArgumentList(signature.tree);
561     auto arguments = extractArguments(argList);
562     auto types = extractTypes(argList);
563     string base = arg.name.friendlyJsName;
564     a.put([ "(", arguments.joiner(", ").text, ")=>{"]);
565     size_t offset = 0;
566     zip(arguments, types).enumerate.each!((t) {
567         auto index = t.index;
568         auto arg = t.value[0];
569         auto type = t.value[1];
570         if (semantics.isStringType(type) || semantics.isUnion(type)
571             || semantics.isNullable(type) || semantics.isEnum(type)) {
572           a.put(["spasm_encode_"]);
573           if (type.name == "WebIDL.TypeWithExtendedAttributes")
574             type.children[1].mangleTypeJs(a, semantics);
575           else
576             type.mangleTypeJs(a, semantics);
577           a.put(["(", offset.to!string, ", ", arg, ");"]);
578           offset += semantics.getSizeOf(type);
579         } else if (!semantics.isPrimitive(type)) {
580           a.put(["encode_handle(", offset.to!string, ", ", arg, ");"]);
581           offset += semantics.getSizeOf(type);
582         }
583       });
584     auto resultPtr = offset;
585     bool needsClose = false;
586     auto result = signature.tree.children[1];
587     bool hasResult = result.matches[0] != "void";
588     bool resultIsPassedViaFirstArgument = hasResult && (semantics.isUnion(result) || semantics.isEnum(result) || semantics.isNullable(result) || semantics.isAny(result));
589     if (hasResult && !resultIsPassedViaFirstArgument)
590       a.put("return ");
591 
592     a.put(["spasm_indirect_function_get(", base, "Ptr)("]);
593     if (resultIsPassedViaFirstArgument)
594       a.put([resultPtr.to!string, ", "]);
595     a.put([base, "Ctx"]);
596     offset = 0;
597     zip(arguments, types).enumerate.each!((t) {
598         a.put(", ");
599         auto index = t.index;
600         auto arg = t.value[0];
601         auto type = t.value[1];
602         if (semantics.isStringType(type) || semantics.isUnion(type)
603             || semantics.isNullable(type) || semantics.isEnum(type)
604             || !semantics.isPrimitive(type)) {
605           a.put(offset.to!string);
606           offset += semantics.getSizeOf(type);
607         } else
608           a.put(arg);
609       });
610     a.put(")");
611     if (resultIsPassedViaFirstArgument) {
612       a.put("; return ");
613       if (semantics.isUnion(result) || semantics.isEnum(result) || semantics.isNullable(result)) {
614         a.put("spasm_decode_");
615         result.mangleTypeJsImpl(a, semantics, MangleTypeJsContext(true));
616         a.put(["(", resultPtr.to!string, ")"]);
617       } else if (semantics.isAny(result)) {
618         a.put(["spasm_decode_Handle(", resultPtr.to!string, ")"]);
619       }
620     }
621     a.put("}");
622   } else if (semantics.isPrimitive(arg.type)) {
623     a.put(arg.name.friendlyJsName);
624   } else {
625     a.put(["objects[",arg.name.friendlyJsName,"]"]);
626   }
627   if (semantics.isNullable(arg.type)) {
628     a.put(" : undefined");
629   }
630 }
631 
632 void dumpJsArguments(Appender)(ref Semantics semantics, Argument[] args, ref Appender a) {
633   if (args.length == 0)
634     return;
635   foreach(arg; args[0..$-1]) {
636     semantics.dumpJsArgument(arg, a);
637     a.put(", ");
638   }
639   semantics.dumpJsArgument(args[$-1], a);
640 }
641 
642 void dump(Appender)(ref Context context, JsExportFunction item, ref Appender a) {
643   auto semantics = context.semantics;
644   a.put(mangleName(item.parentTypeName,item.name,item.manglePostfix));
645   a.put(": (");
646   bool rawResult = item.result != ParseTree.init && semantics.isRawResultType(item.result);
647   if (rawResult)
648     a.put("rawResult");
649   if (!(item.type & FunctionType.Static)) {
650     if (rawResult)
651       a.put(", ");
652     a.put("ctx");
653   }
654   if ((rawResult || (!(item.type & FunctionType.Static))) && item.args.length > 0)
655     a.put(", ");
656   item.args.enumerate.each!((e){
657       auto arg = e.value;
658       if (e.index > 0)
659         a.put(", ");
660       a.put(arg.name.friendlyJsName);
661       if (semantics.isNullable(arg.type))
662         a.put(["Defined, ", arg.name.friendlyJsName]);
663       if (semantics.isCallback(arg.type))
664         a.put(["Ctx, ", arg.name.friendlyJsName, "Ptr"]);
665       else if (semantics.isStringType(arg.type))
666         a.put(["Len, ", arg.name.friendlyJsName, "Ptr"]);
667     });
668   a.putLn(") => {");
669   a.indent();
670   a.putLn("setupMemory();");
671   bool returns = item.result != ParseTree.init && item.result.matches[0] != "void";
672   bool needsClose = false;
673   if (returns) {
674     if (!rawResult)
675       a.put("return ");
676     if (semantics.isStringType(item.result) || semantics.isUnion(item.result) || semantics.isNullable(item.result) || semantics.isEnum(item.result)) {
677       a.put("spasm_encode_");
678       if (item.result.name == "WebIDL.TypeWithExtendedAttributes")
679         item.result.children[1].mangleTypeJs(a, semantics);
680       else
681         item.result.mangleTypeJs(a, semantics);
682       needsClose = true;
683       if (rawResult)
684         a.put("(rawResult, ");
685       else
686         a.put("(");
687     } else if (!semantics.isPrimitive(item.result)) {
688       a.put(["addObject("]);
689       needsClose = true;
690     }
691   }
692   if (item.type & FunctionType.Deleter)
693     a.put("delete ");
694   if (item.type & FunctionType.Static)
695     a.put(item.parentTypeName);
696   else {
697     if (item.type & FunctionType.ExposedConstructor)
698       a.put("new ");
699     a.put("objects[ctx]");
700   }
701   if (item.type & (FunctionType.Getter | FunctionType.Setter | FunctionType.Deleter)) {
702     a.put("[");
703     semantics.dumpJsArgument(item.args[0], a);
704     a.put("]");
705     if (item.type & FunctionType.Setter) {
706       a.put(" = ");
707       semantics.dumpJsArgument(item.args[1], a);
708     }
709   } else {
710     a.put(".");
711     a.put(item.name);
712     if (item.type & FunctionType.Attribute) {
713       if (!returns) {
714         a.put(" = ");
715         semantics.dumpJsArgument(item.args[0], a);
716       }
717     } else {
718       a.put("(");
719       semantics.dumpJsArguments(item.args, a);
720       a.put(")");
721     }
722   }
723   if (needsClose)
724     a.put(")");
725   a.putLn(";");
726   a.undent();
727   a.putLn("},");
728 }
729 
730 void dump(Appender)(ref Semantics semantics, DImportFunction item, ref Appender a) {
731   auto context = Context(semantics);
732   a.put("extern (C) ");
733   if (item.result == ParseTree.init || item.result.matches[0] == "void")
734     a.put("void");
735   else {
736     if (!semantics.isPrimitive(item.result) && !semantics.isUnion(item.result) && !semantics.isNullable(item.result)) {
737       a.put("Handle");
738     } else {
739       item.result.generateDType(*a, context);
740     }
741   }
742   a.put(" ");
743   a.put(mangleName(item.parentTypeName,item.name,item.manglePostfix));
744   a.put("(");
745   if (!(item.type & FunctionType.Static)) {
746     a.put("Handle");
747     if (item.args.length > 0)
748       a.put(", ");
749   }
750   if (item.args.length > 0) {
751     item.args.map!(arg => arg.type).array.putWithDelimiter!(generateDImports)(", ", *a, context);
752   }
753   a.putLn(");");
754 }
755 
756 auto getTemplatedTypeName(size_t idx) {
757   import std.conv : to;
758   return "T"~idx.to!string;
759 }
760 auto getSymbolInfo(string symbol) {
761   struct SymbolInfo {
762     string module_;
763     string name;
764   }
765   import std.algorithm : splitter;
766   import std.demangle;
767   import std.range : drop;
768   import std.typecons : tuple;
769   auto parts = demangle("_D"~symbol[1..$]~"v")[5..$].splitter(".").drop(2).array();
770   return SymbolInfo(parts[0], parts[1]);
771 }
772 string generateJsGlobalBindings(IR ir, string[] jsExportFilters, ref IndentedStringAppender app) {
773   auto generatePromiseThenBindings(IR ir, string symbol, string mangled, ref IndentedStringAppender app) {
774     auto getDecoder(string mangled) {
775       if (mangled == "Aya") {
776         return "decode_string";
777       } else if (mangled == "handle" || mangled[0] == 'S') {
778         return "decode_handle";
779       } else if (mangled == "v")
780         return "void";
781       else if (mangled[0] == 'E') {
782         auto info = getSymbolInfo(mangled);
783         return "spasm_decode_" ~ info.name;
784       } else {
785         if (mangled.startsWith("S8optional")) {
786           throw new Error("Promise!T.then is not yet implemented for Optional!T.");
787         }
788         auto info = getSymbolInfo(mangled);
789         if (ir.semantics.isTypedef(info.name)) {
790           throw new Error("Promise!T.then is not yet implemented for Typedefs.");
791         }
792         if (mangled.canFind("sumtype")) {
793           throw new Error("Promise!T.then is not yet implemented for SumType!Ts.");
794         }
795       }
796       return "";
797     }
798     auto getEncoder(string mangled) {
799       if (mangled == "Aya") {
800         return "encode_string";
801       } else if (mangled == "handle" || mangled[0] == 'S') {
802         return "encode_handle";
803       } else if (mangled == "v")
804         return "void";
805       else if (mangled[0] == 'E') {
806         auto info = getSymbolInfo(mangled);
807         return "spasm_encode_" ~ info.name;
808       } else {
809         if (mangled.startsWith("S8optional")) {
810           throw new Error("Promise!T.then is not yet implemented for Optional!T.");
811         }
812         auto info = getSymbolInfo(mangled);
813         if (ir.semantics.isTypedef(info.name)) {
814           throw new Error("Promise!T.then is not yet implemented for Typedefs.");
815         }
816         if (mangled.canFind("sumtype")) {
817           throw new Error("Promise!T.then is not yet implemented for SumType!Ts.");
818         }
819       }
820       return "";
821     }
822     import std.ascii : isDigit;
823     auto len = mangled.until!(a => !a.isDigit).to!int;
824     auto prefixLen = mangled.countUntil!(a => !a.isDigit);
825     len += prefixLen+1;
826     auto argEncoder = getEncoder(mangled[prefixLen+1..len]);
827     auto resultDecoder = getDecoder(mangled[len..$]);
828     bool returns = resultDecoder != "void" && resultDecoder != "";
829     app.putLn(["promise_then_", mangled,": (handle, ctx, ptr) => {"]);
830     app.indent();
831     app.putLn("return addObject(objects[handle].then((r)=>{");
832     app.indent();
833     if (argEncoder != "" && argEncoder != "void")
834       app.putLn([argEncoder, "(0,r);"]);
835     app.put("spasm_indirect_function_get(ptr)(");
836     if (returns) {
837       app.put("512, ");
838     }
839     if (argEncoder == "void") {
840       app.putLn("ctx);");
841     } else if (argEncoder != "") {
842       app.putLn("ctx, 0);");
843     } else {
844       app.putLn("ctx, r);");
845     }
846     if (returns) {
847       app.putLn(["return ", resultDecoder, "(512);"]);
848     }
849     app.undent();
850     app.putLn("}));");
851     app.undent();
852     app.putLn("},");
853   }
854   auto mappings = ["promise_then_": &generatePromiseThenBindings];
855   foreach(filter; jsExportFilters) {
856     foreach(key, func; mappings) {
857       if (filter.startsWith(key))
858         func(ir, filter, filter[commonPrefix(filter,key).length .. $], app);
859     }
860   }
861   return app.data;
862 }
863 void dump(Appender)(ref Semantics semantics, DBindingFunction item, ref Appender a, string[] locals) {
864   // if (item.result != ParseTree.init) {
865   //   item.result.generateDType(a, Context(semantics));
866   //   a.put(" ");
867   // } else
868   //   a.put("void ");
869   if (item.type & FunctionType.DictionaryConstructor) {
870     a.putLn("static auto create() {");
871     a.indent();
872     a.putLn(["return ", item.parentTypeName, "(spasm_add__object());"]);
873     a.undent();
874     a.putLn("}");
875     return;
876   }
877   bool returns = item.result != ParseTree.init && item.result.matches[0] != "void";
878   if (returns) a.put("auto "); else a.put("void ");
879   void putFuncName() {
880     switch (item.type & (FunctionType.OpIndex | FunctionType.OpDispatch | FunctionType.OpIndexAssign)) {
881     case FunctionType.OpIndex:
882       a.put("opIndex");
883       break;
884     case FunctionType.OpDispatch:
885       a.put("opDispatch");
886       break;
887     case FunctionType.OpIndexAssign:
888       a.put("opIndexAssign");
889       break;
890     default:
891       a.put(item.name.friendlyName);
892       break;
893     }
894   }
895   putFuncName();
896   auto templArgs = item.args.filter!(a => a.templated).array();
897   auto runArgs = item.args.filter!(a => !a.templated).array();
898   auto anyOrOptArgs = item.args.enumerate.filter!(a => semantics.isAny(a.value.type) || semantics.isNullable(a.value.type)).array();
899   auto anys = anyOrOptArgs.filter!(a => semantics.isAny(a.value.type)).array();
900   auto optArgs = anyOrOptArgs.filter!(a => semantics.isNullable(a.value.type)).array();
901   if (templArgs.length > 0) {
902     assert(anys.length == 0);
903     a.put("(");
904     semantics.dumpDParameters(templArgs, a, locals);
905     a.put(")");
906   } else if (anyOrOptArgs.length > 0) {
907     a.put("(");
908     foreach(anyOrOpt; anyOrOptArgs[0..$-1]) {
909       a.put(getTemplatedTypeName(anyOrOpt.index));
910       a.put(", ");
911     }
912     a.put(getTemplatedTypeName(anyOrOptArgs[$-1].index));
913     a.put(")");
914   } else {
915     a.put("()");
916   }
917   a.put("(");
918   if (item.type & FunctionType.OpIndexAssign) {
919     assert(runArgs.length > 1);
920     semantics.dumpDParameter(runArgs[$-1], a, locals, 0);
921     a.put(", ");
922     semantics.dumpDParameters(runArgs[0..$-1], a, locals);
923   } else
924     semantics.dumpDParameters(runArgs, a, locals);
925   a.put(") ");
926   if (optArgs.length > 0) {
927     a.put("if (");
928     foreach(idx, opt; optArgs) {
929       a.put("isTOrPointer!(");
930       a.put(getTemplatedTypeName(opt.index));
931       a.put(", ");
932       opt.value.type.stripNullable.generateDType(a, Context(semantics).withLocals(locals));
933       a.put(")");
934       if (idx+1 < optArgs.length)
935         a.put(" && ");
936     }
937     a.put(") ");
938   }
939   a.putLn("{");
940   a.indent();
941   foreach(any; anys) {
942     a.putLn(["Handle _handle_", any.value.name, " = getOrCreateHandle(", any.value.name.friendlyName, ");"]);
943   }
944   bool needDropHandle = anys.length != 0;
945   if (returns) {
946     if (needDropHandle)
947       a.put("auto result = ");
948     else
949       a.put("return ");
950     if (!semantics.isPrimitive(item.result) && !semantics.isUnion(item.result) && !semantics.isNullable(item.result)) {
951       if (item.type == FunctionType.ExposedConstructor)
952         a.put([".",item.name]);
953       else
954         item.result.generateDType(a, Context(semantics).withLocals(locals));
955       a.put("(");
956     }
957   }
958   a.put(mangleName(item.parentTypeName, item.customName.length > 0 ? item.customName : item.name,item.manglePostfix));
959   a.put("(");
960   if (!(item.type & FunctionType.Static)) {
961     a.put(["this.", item.handle]);
962     if (item.args.length > 0)
963       a.put(", ");
964   }
965   semantics.dumpDJsArguments(item.args, a);
966   if (returns) {
967     if (!semantics.isPrimitive(item.result) && !semantics.isUnion(item.result) && !semantics.isNullable(item.result)) {
968       a.put(")");
969     }
970   }
971   a.putLn(");");
972   foreach(any; anys) {
973     a.putLn(["dropHandle!(T", any.index.to!string, ")(_handle_", any.value.name, ");"]);
974   }
975   if (returns && needDropHandle)
976     a.putLn("return result;");
977   a.undent();
978   a.putLn("}");
979 }
980 void dumpDParameters(Appender)(ref Semantics semantics, Argument[] args, ref Appender a, string[] locals) {
981   if (args.length == 0)
982     return;
983   foreach(i, arg; args[0..$-1]) {
984     semantics.dumpDParameter(arg, a, locals, i);
985     a.put(", ");
986   }
987   semantics.dumpDParameter(args[$-1], a, locals, args.length-1);
988 }
989 void dumpDParameter(Appender)(ref Semantics semantics, Argument arg, ref Appender a, string[] locals, size_t idx) {
990   if (semantics.isAny(arg.type)) {
991     a.put("scope auto ref ");
992     a.put(getTemplatedTypeName(idx));
993   } else {
994     if (semantics.isNullable(arg.type)) {
995       a.put("scope auto ref Optional!(");
996       a.put(getTemplatedTypeName(idx));
997       a.put(")");
998     } else {
999       if (!semantics.isPrimitive(arg.type))
1000         a.put("scope ref ");
1001       arg.type.generateDType(a, Context(semantics).withLocals(locals));
1002     }
1003   }
1004   a.put(" ");
1005   a.putCamelCase(arg.name.friendlyName);
1006   if (arg.default_.matches.length > 1) {
1007     a.put(" ");
1008     if (arg.default_.children[0].matches[0] == "null") {
1009       if (semantics.isNullable(arg.type)) {
1010         a.put("/* = no!(");
1011         arg.type.generateDType(a, Context(semantics).withSkipOptional.withLocals(locals).setDParameter);
1012         a.put(") */");
1013         return;
1014       }
1015     }
1016     arg.default_.generateDType(a, Context(semantics).withLocals(locals).setDParameter);
1017   }
1018 }
1019 
1020 void dumpDJsArguments(Appender)(ref Semantics semantics, Argument[] args, ref Appender a) {
1021   if (args.length == 0)
1022     return;
1023   foreach(arg; args[0..$-1]) {
1024     semantics.dumpDJsArgument(arg, a);
1025     a.put(", ");
1026   }
1027   semantics.dumpDJsArgument(args[$-1], a);
1028 }
1029 
1030 void dumpDJsArgument(Appender)(ref Semantics semantics, Argument arg, ref Appender a) {
1031   if (semantics.isAny(arg.type)) {
1032     a.put("_handle_");
1033     a.put(arg.name.friendlyName);
1034     return;
1035   }
1036   bool optional = semantics.isNullable(arg.type);
1037   if (optional)
1038     a.put("!");
1039   a.put(arg.name.friendlyName);
1040   if (optional) {
1041     if (!semantics.isUnion(arg.type)) {
1042       a.put([".empty, ", arg.name.friendlyName]);
1043       a.put(".front");
1044     } else {
1045       a.put([".empty, *", arg.name.friendlyName]);
1046       a.put(".frontRef");
1047     }
1048   }
1049   if (!semantics.isPrimitive(arg.type) && !semantics.isUnion(arg.type)) {
1050     auto s = arg.type.matches[0] in semantics.types;
1051     if (s !is null) {
1052       if (s.tree.children[1].matches.length > 1)
1053         a.put("._parent");
1054       else
1055         a.put(".handle");
1056     } else
1057       a.put(".handle");
1058   }
1059 }
1060 
1061 bool isSequence(Semantics semantics, ParseTree tree) {
1062   if (tree.name == "WebIDL.ReturnType") {
1063     if (tree.matches[0] == "void")
1064       return false;
1065     return semantics.isSequence(tree.children[0]);
1066   }
1067   if (tree.name == "WebIDL.Type") {
1068     if (tree.children[0].name == "WebIDL.SingleType")
1069       return false;
1070     return tree.children[0].children[0].children[0].name == "WebIDL.SequenceType";
1071   }
1072   if (tree.name == "WebIDL.UnionMemberType")
1073     return tree.children[1].children[0].name == "WebIDL.SequenceType";
1074   if (tree.name == "WebIDL.NonAnyType")
1075     return tree.children[0].name == "WebIDL.SequenceType";
1076   return false;
1077 }
1078 
1079 bool isAny(ref Semantics semantics, ParseTree tree) {
1080   return tree.matches[0] == "any";
1081 }
1082 bool isDKeyword(string s) {
1083   import std.algorithm : canFind;
1084   return dKeywords.canFind(s);
1085 }
1086 bool isJsKeyword(string s) {
1087   import std.algorithm : canFind;
1088   return jsKeywords.canFind(s);
1089 }
1090 
1091 string friendlyJsName(string s) {
1092   import std.ascii;
1093   import std.conv : text;
1094   import std.utf : byChar;
1095   if (s.length == 0)
1096     return s;
1097   if (s.isJsKeyword)
1098     return s~"_";
1099   return s;
1100 }
1101 
1102 string friendlyName(string s) {
1103   import std.ascii;
1104   import std.conv : text;
1105   import std.utf : byChar;
1106   if (s.length == 0)
1107     return s;
1108   if (s.isDKeyword)
1109     return s~"_";
1110   string clean = s.byChar.map!(c => c.isAlphaNum ? c : '_').text;
1111   if (!clean[0].isAlpha && clean[0] != '_')
1112     return '_'~clean;
1113   return clean;
1114 }
1115 
1116 struct IndentedStringAppender {
1117   import std.array : Appender;
1118   import std.algorithm : each;
1119   bool beginLine = true;
1120   Appender!string appender;
1121   int i = 0;
1122   void put(char c) {
1123     putIndent();
1124     appender.put(c);
1125   }
1126   void put(string s) {
1127     putIndent();
1128     appender.put(s);
1129   }
1130   void put(string[] ss) {
1131     putIndent();
1132     ss.each!(s => appender.put(s));
1133   }
1134   void putLn(char c) {
1135     put(c);
1136     appender.put("\n");
1137     beginLine = true;
1138   }
1139   void putLn(string s) {
1140     put(s);
1141     appender.put("\n");
1142     beginLine = true;
1143   }
1144   void putLn(string[] ss) {
1145     put(ss);
1146     appender.put("\n");
1147     beginLine = true;
1148   }
1149   void putIndent() {
1150     if (!beginLine)
1151       return;
1152     beginLine = false;
1153     import std.range : repeat;
1154     import std.algorithm : copy;
1155     ' '.repeat(i*2).copy(appender);
1156   }
1157   void indent() {
1158     i++;
1159   }
1160   void undent() {
1161     import std.algorithm : max;
1162     i = max(0, i-1);
1163   }
1164   auto data() {
1165     return appender.data;
1166   }
1167 }
1168 
1169 struct Context {
1170   Semantics semantics;
1171   ParseTree extendedAttributeList;
1172   ParseTree partial;
1173   ParseTree includes;
1174   bool readonly = false;
1175   bool primitiveType = false;
1176   bool sumType = false;
1177   bool optional = false;
1178   bool returnType = false;
1179   bool isIncludes = false;
1180   bool skipOptional = false;
1181   bool dParameter = false;
1182   string typeName;
1183   string customName;
1184   string[] locals;
1185 }
1186 
1187 auto withLocals(Context c, string[] locals) {
1188   c.locals = locals;
1189   return c;
1190 }
1191 
1192 auto setDParameter(Context c) {
1193   c.dParameter = true;
1194   return c;
1195 }
1196 
1197 auto withSkipOptional(Context c) {
1198   c.skipOptional = true;
1199   return c;
1200 }
1201 
1202 bool isEnum(ref Context context, ParseTree tree) {
1203   return context.semantics.isEnum(tree);
1204 }
1205 bool isEnum(ref Semantics semantics, ParseTree tree) {
1206   if (tree.name == "WebIDL.TypeWithExtendedAttributes" || tree.name == "WebIDL.UnionMemberType")
1207     return semantics.isEnum(tree.children[1]);
1208   return semantics.isEnum(tree.matches[0]);
1209 }
1210 
1211 bool isEnum(ref Semantics semantics, string typeName) {
1212   if (auto p = typeName in semantics.types) {
1213     return p.tree.name == "WebIDL.Enum";
1214   }
1215   return false;
1216 }
1217 bool isNullableTypedef(ref Semantics semantics, ParseTree tree) {
1218   if (tree.name == "WebIDL.ReturnType") {
1219     if (tree.matches[0] == "void")
1220       return false;
1221     return semantics.isNullableTypedef(tree.children[0]);
1222   }
1223   if (tree.name == "WebIDL.TypeWithExtendedAttributes")
1224     return semantics.isNullableTypedef(tree.children[1]);
1225   assert(tree.name == "WebIDL.Type" || tree.name == "WebIDL.UnionMemberType");
1226   if (tree.name == "WebIDL.UnionMemberType" && tree.children[0].name == "WebIDL.UnionType")
1227     return false;
1228   string typeName = tree.getTypeName();
1229   if (!semantics.isTypedef(typeName))
1230     return false;
1231   if (tree.matches[$-1] == "?")
1232     return true;
1233   return false;
1234 }
1235 
1236 bool isTypedef(ref Context context, ParseTree tree) {
1237   return context.semantics.isTypedef(tree);
1238 }
1239 
1240 bool isTypedef(ref Semantics semantics, ParseTree tree) {
1241   string typeName = tree.getTypeName();
1242   return semantics.isTypedef(typeName);
1243 }
1244 
1245 bool isTypedef(ref Context context, string typeName) {
1246   return context.semantics.isTypedef(typeName);
1247 }
1248 bool isTypedef(ref Semantics semantics, string typeName) {
1249   if (auto p = typeName in semantics.types) {
1250     return p.tree.name == "WebIDL.Typedef";
1251   }
1252   return false;
1253 }
1254 bool isCallback(ref Context context, string typeName) {
1255   return context.semantics.isCallback(typeName);
1256 }
1257 bool isCallback(ref Semantics semantics, ParseTree tree) {
1258   if (tree.name == "WebIDL.TypeWithExtendedAttributes")
1259     return semantics.isCallback(tree.children[1].matches[0]);
1260   return semantics.isCallback(tree.children[0].matches[0]);
1261 }
1262 bool isCallback(ref Semantics semantics, string typeName) {
1263   if (semantics.isTypedef(typeName)) {
1264     auto aliased = semantics.getAliasedType(typeName);
1265     return semantics.isCallback(aliased);
1266   }
1267   if (auto p = typeName in semantics.types) {
1268     return p.tree.name == "WebIDL.CallbackRest";
1269   }
1270   return false;
1271 }
1272 
1273 bool isPartial(ref Context context) {
1274   return context.partial.matches.length > 0;
1275 }
1276 
1277 void putCamelCase(Appender)(ref Appender a, string s) {
1278   import std.algorithm : until;
1279   import std.uni : isUpper, isLower, asLowerCase;
1280   import std.conv : text;
1281   if (s.length == 0)
1282     return;
1283   if (s[0].isLower) {
1284     a.put(s);
1285     return;
1286   }
1287   import std.string : toLower;
1288   auto head = s.until!(isLower).asLowerCase.text;
1289   if (head.length == 1) {
1290     a.put(head);
1291     a.put(s[head.length .. $]);
1292     return;
1293   }
1294   auto tail = s[head.length-1 .. $];
1295   a.put(head[0..$-1]);
1296   a.put(tail);
1297 }
1298 
1299 string toCamelCase(string s) {
1300   auto app = appender!string;
1301   app.putCamelCase(s);
1302   return app.data;
1303 }
1304 
1305 string mangleJsName(StructNode node, FunctionNode fun) {
1306   return mangleName(node.name, fun.customName != "" ? fun.customName : fun.name, fun.manglePostfix);
1307 }
1308 
1309 string mangleName(string typeName, string name, string appendix = "") {
1310   import std.ascii : toLower, toUpper;
1311   import std.array : appender;
1312   auto app = appender!string;
1313   app.put(typeName);
1314   app.put("_");
1315   app.put(name);
1316   if (appendix.length > 0) {
1317     app.put("_");
1318     app.put(appendix);
1319   }
1320   return app.data;
1321 }
1322 
1323 bool isNullable(ref Semantics semantics, ParseTree tree) {
1324   if (tree.name == "WebIDL.ReturnType") {
1325     if (tree.matches[0] == "void")
1326       return false;
1327     return semantics.isNullable(tree.children[0]);
1328   }
1329   if (tree.name == "WebIDL.TypeWithExtendedAttributes")
1330     return semantics.isNullable(tree.children[1]);
1331   if (tree.name == "WebIDL.UnionMemberType")
1332     return tree.matches[$-1] == "?";
1333   if (tree.name == "WebIDL.InterfaceRest")
1334     return false;
1335   assert(tree.name == "WebIDL.Type");
1336   if (tree.matches[$-1] == "?")
1337     return true;
1338   string typeName = tree.getTypeName();
1339   if (semantics.isTypedef(typeName)) {
1340     return semantics.isNullable(semantics.getAliasedType(typeName));
1341   }
1342   return false;
1343 }
1344 
1345 bool isRawResultType(ref Semantics semantics, ParseTree tree) {
1346   if (tree.name == "WebIDL.ReturnType") {
1347     if (tree.matches[0] == "void")
1348       return false;
1349     return semantics.isRawResultType(tree.children[0]);
1350   }
1351   if (tree.name == "WebIDL.TypeWithExtendedAttributes")
1352     return semantics.isRawResultType(tree.children[1]);
1353   if (tree.name == "WebIDL.InterfaceRest")
1354     return false;
1355   assert(tree.name == "WebIDL.Type");
1356   return semantics.isNullable(tree) ||
1357     semantics.isStringType(tree) || semantics.isUnion(tree);
1358 }
1359 
1360 bool isStringType(ref Semantics semantics, ParseTree tree) {
1361   if (tree.name == "WebIDL.ReturnType") {
1362     if (tree.matches[0] == "void")
1363       return false;
1364     return semantics.isStringType(tree.children[0]);
1365   }
1366   if (tree.name == "WebIDL.TypeWithExtendedAttributes")
1367     return semantics.isStringType(tree.children[1]);
1368   if (tree.name == "WebIDL.InterfaceRest")
1369     return false;
1370   assert(tree.name == "WebIDL.Type");
1371   string typeName = tree.getTypeName();
1372   if (semantics.isTypedef(typeName)) {
1373     return semantics.isStringType(semantics.getAliasedType(typeName));
1374   }
1375   if (tree.children[0].name != "WebIDL.SingleType")
1376     return false;
1377   if (tree.children[0].matches[0] == "any")
1378     return false;
1379   if (tree.children[0].children[0].children[0].name == "WebIDL.StringType")
1380     return true;
1381   return false;
1382 }
1383 bool isUnion(ref Context context, ParseTree tree) {
1384   return context.semantics.isUnion(tree);
1385 }
1386 
1387 bool isUnion(ref Semantics semantics, ParseTree tree) {
1388   if (tree.name == "WebIDL.ReturnType") {
1389     if (tree.matches[0] == "void")
1390       return false;
1391     return semantics.isUnion(tree.children[0]);
1392   }
1393   if (tree.name == "WebIDL.TypeWithExtendedAttributes")
1394     return semantics.isUnion(tree.children[1]);
1395   if (tree.name == "WebIDL.InterfaceRest")
1396     return false;
1397   assert(tree.name == "WebIDL.Type" || tree.name == "WebIDL.UnionMemberType");
1398   if (tree.children[0].name == "WebIDL.UnionType")
1399     return true;
1400   string typeName = tree.getTypeName();
1401   if (semantics.isTypedef(typeName)) {
1402     return semantics.isUnion(semantics.getAliasedType(typeName));
1403   }
1404   return false;
1405 }
1406 auto getAliasedType(ref Context context, string typeName) {
1407   assert(typeName in context.semantics.types);
1408   assert(context.semantics.types[typeName].tree.name == "WebIDL.Typedef");
1409   return context.semantics.types[typeName].tree.children[0];
1410 }
1411 
1412 auto getAliasedType(ref Semantics semantics, string typeName) {
1413   assert(typeName in semantics.types);
1414   assert(semantics.types[typeName].tree.name == "WebIDL.Typedef");
1415   return semantics.types[typeName].tree.children[0];
1416 }
1417 
1418 bool isPrimitive(Context context, ParseTree tree) {
1419   return context.semantics.isPrimitive(tree);
1420 }
1421 
1422 bool isPrimitive(ref Semantics semantics, ParseTree tree) {
1423   if (tree.name == "WebIDL.ReturnType") {
1424     if (tree.matches[0] == "void")
1425       return false;
1426     return semantics.isPrimitive(tree.children[0]);
1427   }
1428   if (tree.name == "WebIDL.TypeWithExtendedAttributes")
1429     return semantics.isPrimitive(tree.children[1]);
1430   string typeName = tree.getTypeName();
1431   if (tree.name == "WebIDL.UnionMemberType") {
1432     if (tree.children[1].name != "WebIDL.NonAnyType")
1433       return false;
1434     return tree.children[1].children[0].name == "WebIDL.PrimitiveType";
1435   }
1436   if (tree.name == "WebIDL.InterfaceRest")
1437     return false;
1438   assert(tree.name == "WebIDL.Type");
1439   if (semantics.isEnum(typeName) || semantics.isCallback(typeName))
1440     return true;
1441   if (semantics.isTypedef(typeName)) {
1442     return semantics.isPrimitive(semantics.getAliasedType(typeName));
1443   }
1444   if (tree.children[0].name != "WebIDL.SingleType")
1445     return false;
1446   if (tree.children[0].matches[0] == "any")
1447     return false;
1448   if (semantics.isStringType(tree))
1449     return true;
1450   if (tree.children[0].children[0].name != "WebIDL.NonAnyType")
1451     return false;
1452   return tree.children[0].children[0].children[0].name == "WebIDL.PrimitiveType";
1453 }
1454 
1455 string getTypeName(ParseTree tree) {
1456   if (tree.name == "WebIDL.ReturnType") {
1457     assert(tree.matches[0] != "void");
1458     return tree.children[0].getTypeName();
1459   }
1460   if (tree.name == "WebIDL.InterfaceRest") {
1461     return tree.matches[0];
1462   }
1463   assert(tree.name == "WebIDL.TypeWithExtendedAttributes" || tree.name == "WebIDL.Type" || tree.name == "WebIDL.UnionMemberType");
1464   if (tree.name == "WebIDL.UnionMemberType") {
1465     assert(tree.children[1].name == "WebIDL.NonAnyType");
1466     return tree.children[1].matches[0];
1467   }
1468   if (tree.name == "WebIDL.TypeWithExtendedAttributes")
1469     return tree.children[1].matches[0];
1470   return tree.matches[0];
1471 }
1472 
1473 auto isEmpty(string s) {
1474   return s == "";
1475 }
1476 auto isEmpty(string[] matches) {
1477   import std.algorithm : all;
1478   return matches.length == 0 || matches.all!(m => m.isEmpty);
1479 }
1480 
1481 template putWithDelimiter(alias Generator)
1482 {
1483   void putWithDelimiter(Appender, Ts...)(ParseTree[] children, string delimiter,ref Appender a, Ts args) {
1484     import std.algorithm : each, filter;
1485     import std.array : array;
1486     auto nonEmpty = children.filter!(c => !c.matches.isEmpty).array;
1487     // need to filter first
1488     if (nonEmpty.length > 0) {
1489       nonEmpty[0..$-1].each!((c){if (c.matches.isEmpty) return; Generator(c, a, args);a.put(delimiter);});
1490       Generator(nonEmpty[$-1], a, args);
1491     }
1492   }
1493 }
1494 
1495 auto extractArgument(ParseTree tree) {
1496   assert(tree.name == "WebIDL.Argument");
1497   auto argRest = tree.children[$-1];
1498   if (argRest.matches[0] == "optional") {
1499     return argRest.children[1].matches[0];
1500   } else {
1501     return argRest.children[2].matches[0];
1502   }
1503 }
1504 auto extractDefault(ParseTree tree) {
1505   assert(tree.name == "WebIDL.Argument");
1506   auto argRest = tree.children[$-1];
1507   string typeName;
1508   if (argRest.matches[0] == "optional") {
1509     return argRest.children[2];
1510   } else {
1511     return ParseTree.init;
1512   }
1513 }
1514 auto extractType(ParseTree tree) {
1515   assert(tree.name == "WebIDL.Argument");
1516   auto argRest = tree.children[$-1];
1517   string typeName;
1518   if (argRest.matches[0] == "optional") {
1519     return argRest.children[0].children[1];
1520   } else {
1521     return argRest.children[0];
1522   }
1523 }
1524 
1525 string extractTypeName(ParseTree tree) {
1526   if (tree.name == "WebIDL.Argument") {
1527     auto argRest = tree.children[$-1];
1528     if (argRest.matches[0] == "optional") {
1529       return extractTypeName(argRest.children[0].children[1]);
1530     }
1531     return extractTypeName(argRest.children[0]);
1532   }
1533   assert(tree.name == "WebIDL.Type");
1534   string typeName = tree.matches[0];
1535   if (typeName == "any")
1536     return "Any";
1537   if (typeName == "DOMString")
1538     return "string";
1539   if (typeName == "boolean")
1540     return "bool";
1541   return typeName;
1542 }
1543 auto extractArguments(ParseTree tree) {
1544   import std.algorithm : map;
1545   assert(tree.name == "WebIDL.ArgumentList");
1546   return tree.children.map!(c => c.extractArgument);
1547 }
1548 auto extractArgumentRests(ParseTree tree) {
1549   assert(tree.name == "WebIDL.ArgumentList");
1550   return tree.children.map!(c => c.children[1]);
1551 }
1552 auto extractDefaults(ParseTree tree) {
1553   import std.algorithm : map;
1554   assert(tree.name == "WebIDL.ArgumentList");
1555   return tree.children.map!(c => c.extractDefault);
1556 }
1557 auto extractTypes(ParseTree tree) {
1558   import std.algorithm : map;
1559   assert(tree.name == "WebIDL.ArgumentList");
1560   return tree.children.map!(c => c.extractType);
1561 }
1562 auto extractTypeNames(ParseTree tree) {
1563   import std.algorithm : map;
1564   assert(tree.name == "WebIDL.ArgumentList");
1565   return tree.children.map!(c => c.extractTypeName);
1566 }
1567 ParseTree getArgumentList(ref Semantics semantics, ParseTree tree) {
1568   auto p = tree.matches[0] in semantics.types;
1569   assert(p != null);
1570   assert(p.tree.name == "WebIDL.CallbackRest");
1571   return p.tree.children[2];
1572 }
1573 auto getType(ref Semantics semantics, ParseTree tree) {
1574   return semantics.getType(tree.matches[0]);
1575 }
1576 auto getType(ref Semantics semantics, string name) {
1577   auto p = name in semantics.types;
1578   if (p is null) {
1579     writeln("Failed to find "~name);
1580     return ParseTree.init;
1581   }
1582   return p.tree;
1583 }
1584 auto getType(ref Context context, string name) {
1585   return context.semantics.getType(name);
1586 }
1587 auto getMatchingPartials(ref Semantics semantics, string name) {
1588   auto isInterface = (Type p) => p.tree.children[0].children[0].name == "WebIDL.PartialInterfaceOrPartialMixin";
1589   auto matches = (Type p) => p.tree.children[0].children[0].children[0].children[0].matches[0] == name;
1590   return semantics.partials.filter!(p => isInterface(p) && matches(p)).map!(t => t.tree).array();
1591 }
1592 
1593 auto getMatchingPartials(ref Context context, string name) {
1594   return context.semantics.getMatchingPartials(name);
1595 }
1596 
1597 uint getSizeOf(ref Semantics semantics, ParseTree tree) {
1598   switch(tree.name) {
1599     case "WebIDL.IntegerType":
1600       if (tree.matches[0] == "long") {
1601         if (tree.matches.length > 1 && !is32Bit)
1602           return 8;
1603         return 4;
1604       }
1605       return 2;
1606   case "WebIDL.StringType":
1607     return 8;
1608   case "WebIDL.FloatType":
1609     if (tree.matches[0] == "float")
1610       return 4;
1611     return 8;
1612   case "WebIDL.PrimitiveType":
1613     if (tree.children.length == 0) {
1614       if (tree.matches[0] == "boolean")
1615         return 4;
1616       return 1;
1617     } else
1618       goto default;
1619   case "WebIDL.SingleType":
1620     if (tree.matches[0] == "any")
1621       return 4;
1622     goto default;
1623   case "WebIDL.Identifier":
1624   case "WebIDL.SequenceType":
1625   case "WebIDL.Enum":
1626   case "WebIDL.RecordType":
1627   case "WebIDL.PromiseType":
1628   case "WebIDL.BufferRelatedType":
1629     return 4;
1630   case "WebIDL.UnionType":
1631     return 4 + tree.children.map!(c => semantics.getSizeOf(c)).maxElement;
1632   case "WebIDL.NonAnyType":
1633     if (tree.matches[0] == "object" || tree.matches[0] == "symbol" || tree.matches[0] == "Error" || tree.matches[0] == "FrozenArray") {
1634       return 4 + semantics.getSizeOf(tree.children[$-1]);
1635     }
1636   goto default;
1637   case "WebIDL.Null":
1638     return tree.matches[0] == "?" ? 4 : 0;
1639   default:
1640     return tree.children.map!(c => semantics.getSizeOf(c)).sum;
1641   }
1642 }
1643 string mangleTypeJs(ParseTree tree, ref Semantics semantics) {
1644   auto app = appender!string;
1645   tree.mangleTypeJs(app, semantics);
1646   return app.data;
1647 }
1648 string mangleTypeJs(ParseTree tree, ref Semantics semantics, MangleTypeJsContext context) {
1649   auto app = appender!string;
1650   tree.mangleTypeJsImpl(app, semantics, context);
1651   return app.data;
1652 }
1653 
1654 struct MangleTypeJsContext {
1655   bool skipOptional;
1656   bool inUnion = false;
1657 }
1658 void mangleTypeJs(Appender)(ParseTree tree, ref Appender a, ref Semantics semantics) {
1659   MangleTypeJsContext context;
1660   tree.mangleTypeJsImpl(a, semantics, context);
1661 }
1662 void mangleTypeJsImpl(Appender)(ParseTree tree, ref Appender a, ref Semantics semantics, MangleTypeJsContext context) {
1663   switch (tree.name) {
1664   case "WebIDL.CallbackRest":
1665     a.put("callback_");
1666     tree.children[1].mangleTypeJsImpl(a, semantics, context);
1667     if (tree.children[2].children.length > 0) {
1668       a.put("_");
1669       tree.children[2].mangleTypeJsImpl(a, semantics, context);
1670     }
1671     break;
1672   case "WebIDL.ArgumentList":
1673     tree.children.putWithDelimiter!(mangleTypeJsImpl)("_", a, semantics, context);
1674     break;
1675   case "WebIDL.Argument":
1676     auto type = tree.extractType;
1677     if (!semantics.isPrimitive(type) && !semantics.isUnion(type)) {
1678       a.put("Handle");
1679       break;
1680     }
1681     tree.children[1].mangleTypeJsImpl(a, semantics, context);
1682     break;
1683   case "WebIDL.UnsignedIntegerType":
1684     if (tree.matches[0] == "unsigned")
1685       a.put("u");
1686     tree.children[0].mangleTypeJsImpl(a, semantics, context);
1687     break;
1688   case "WebIDL.IntegerType":
1689     if (tree.matches[0] == "long") {
1690       if (tree.matches.length > 1)
1691         a.put(is32Bit ? "int" : "long");
1692       else
1693         a.put("int");
1694     } else
1695       a.put(tree.matches[0]);
1696     break;
1697   case "WebIDL.RecordType":
1698     a.put("record");
1699     // tree.children.putWithDelimiter!(mangleTypeJsImpl)("_", a, semantics, context);
1700     break;
1701   case "WebIDL.SequenceType":
1702     if (!context.skipOptional && tree.matches[$-1] == "?")
1703       a.put("optional_");
1704     a.put("sequence");
1705     // tree.children[0].mangleTypeJsImpl(a, semantics, context);
1706     break;
1707   case "WebIDL.SingleType":
1708     if (tree.matches[0] == "any") {
1709       a.put("Handle");
1710     } else if (tree.matches[0] == "void") {
1711       a.put("void");
1712     } else {
1713       tree.children[0].mangleTypeJsImpl(a, semantics, context);
1714     }
1715     break;
1716   case "WebIDL.Type":
1717     string typeName = getTypeName(tree);
1718     if (semantics.isTypedef(typeName)) {
1719       if (tree.matches[$-1] == "?") {
1720         if (context.skipOptional)
1721           context.skipOptional = false;
1722         else
1723           a.put("optional_");
1724       }
1725       auto aliasMangled = semantics.getAliasedType(typeName).mangleTypeJs(semantics, context);
1726       if (aliasMangled.length < typeName.length)
1727         a.put(aliasMangled);
1728       else
1729         a.put(typeName);
1730       return;
1731     }
1732     if (tree.children.length == 2 && tree.children[$-1].matches[0] == "?") {
1733       if (context.skipOptional)
1734         context.skipOptional = false;
1735       else
1736         a.put("optional_");
1737     }
1738     tree.children[0].mangleTypeJsImpl(a, semantics, context);
1739     break;
1740   case "WebIDL.UnionType":
1741     a.put("union");
1742     a.put(tree.children.length.to!string);
1743     a.put("_");
1744     context.inUnion = true;
1745     tree.children.putWithDelimiter!(mangleTypeJsImpl)("_", a, semantics, context);
1746     break;
1747   case "WebIDL.UnionMemberType":
1748     if (tree.children[1].name == "WebIDL.NonAnyType")
1749       tree.children[1].mangleTypeJsImpl(a, semantics, context);
1750     else {
1751       tree.children[0].mangleTypeJsImpl(a, semantics, context);
1752       if (tree.children[$-1].matches[0] == "?")
1753         a.put("_Null");
1754     }
1755     break;
1756   case "WebIDL.NonAnyType":
1757     if (tree.children.length > 1 && tree.children[$-1].matches[0] == "?") {
1758       if (context.skipOptional)
1759         context.skipOptional = false;
1760       else
1761         a.put("optional_");
1762     }
1763     if (tree.children[0].name == "WebIDL.Null")
1764       a.put(tree.matches[0]); // Maybe return here?
1765     if (tree.matches[0] == "FrozenArray")
1766       assert(false);
1767     tree.children[0].mangleTypeJsImpl(a, semantics, context);
1768     break;
1769   case "WebIDL.FloatType":
1770     a.put(tree.matches[0]);
1771     break;
1772   case "WebIDL.TypeWithExtendedAttributes":
1773     tree.children[1].mangleTypeJsImpl(a, semantics, context);
1774     break;
1775   case "WebIDL.Identifier":
1776     auto typeName = tree.matches[0];
1777     if (!context.inUnion && !semantics.isEnum(tree)) {
1778       a.put("Handle");
1779       return;
1780     }
1781     // TODO: could be nullable typedef
1782     if (semantics.isTypedef(typeName)) {
1783       auto aliasMangled = semantics.getAliasedType(typeName).mangleTypeJs(semantics, context);
1784       if (aliasMangled.length < tree.matches[0].length) {
1785         a.put(aliasMangled);
1786         return;
1787       }
1788     }
1789     a.put(tree.matches[0]);
1790     break;
1791   case "WebIDL.StringType":
1792     a.put("string");
1793     break;
1794   case "WebIDL.ArgumentRest":
1795     tree.children[0].mangleTypeJsImpl(a, semantics, context);
1796     break;
1797   case "WebIDL.PrimitiveType":
1798     if (tree.children.length == 1) {
1799       tree.children[0].mangleTypeJsImpl(a, semantics, context);
1800     } else {
1801       switch (tree.matches[0]) {
1802       case "byte": a.put("byte"); break;
1803       case "octet": a.put("ubyte"); break;
1804       case "boolean": a.put("bool"); break;
1805       default: a.put(tree.matches[0]); break;
1806       }
1807     }
1808     break;
1809   default:
1810     tree.children.each!(c => c.mangleTypeJsImpl(a, semantics, context));
1811   }
1812 }
1813 
1814 string generateDImports(ParseTree tree, Context context) {
1815   auto app = IndentedStringAppender();
1816   tree.generateDImports(app, context);
1817   return app.data;
1818 }
1819 void generateDImports(Appender)(ParseTree tree, ref Appender a, Context context) {
1820   import std.algorithm : each, joiner, map;
1821   import std.range : chain;
1822   import std.conv : text;
1823   switch (tree.name) {
1824   case "WebIDL.InterfaceRest":
1825   case "WebIDL.InterfaceMembers":
1826   case "WebIDL.InterfaceMember":
1827   case "WebIDL.ReadOnlyMember":
1828   case "WebIDL.AttributeRest":
1829     break;
1830   case "WebIDL.TypeWithExtendedAttributes":
1831     tree.children[1].generateDImports(a, context);
1832     break;
1833   case "WebIDL.NonAnyType":
1834     bool optional = !context.skipOptional && (context.optional || tree.children[$-1].name == "WebIDL.Null" && tree.children[$-1].matches[0] == "?");
1835     if (optional) {
1836       if (context.returnType)
1837         a.put("Optional!(");
1838       else
1839         a.put("bool, ");
1840     }
1841     if (!(optional && context.returnType) && !context.primitiveType && !context.sumType && !(context.returnType && tree.children[0].name == "WebIDL.SequenceType"))
1842       a.put("Handle");
1843     else
1844       tree.children.each!(c => c.generateDType(a, context));
1845     if (optional && context.returnType)
1846       a.put(")");
1847     break;
1848   case "WebIDL.SingleType":
1849     if (tree.matches[0] == "any") {
1850       a.put("Handle");
1851     } else
1852       tree.children[0].generateDImports(a, context);
1853     break;
1854   case "WebIDL.PrimitiveType":
1855     if (tree.children.length == 0) {
1856       switch (tree.matches[0]) {
1857       case "byte": a.put("byte"); break;
1858       case "octet": a.put("ubyte"); break;
1859       case "boolean": a.put("bool"); break;
1860       default: a.put(tree.matches[0]); break;
1861       }
1862     } else
1863       tree.children[0].generateDImports(a, context);
1864     break;
1865   case "WebIDL.StringType":
1866     a.put("string");
1867     break;
1868   case "WebIDL.ArgumentName":
1869   case "WebIDL.AttributeName":
1870     break;
1871   case "WebIDL.UnrestrictedFloatType":
1872     // TODO: handle unrestricted
1873     tree.children[0].generateDImports(a, context);
1874     break;
1875   case "WebIDL.UnsignedIntegerType":
1876     if (tree.matches[0] == "unsigned")
1877       a.put("u");
1878     tree.children[0].generateDType(a, context);
1879     break;
1880   case "WebIDL.IntegerType":
1881     if (tree.matches[0] == "long") {
1882       if (tree.matches.length > 1)
1883         a.put(is32Bit ? "int" : "long");
1884       else
1885         a.put("int");
1886     } else
1887       a.put(tree.matches[0]);
1888     break;
1889   case "WebIDL.ExtendedAttributeList":
1890     break;
1891   case "WebIDL.FloatType":
1892   case "WebIDL.Identifier":
1893     a.put(tree.matches[0]);
1894     break;
1895   case "WebIDL.SpecialOperation":
1896   case "WebIDL.RegularOperation":
1897     break;
1898   case "WebIDL.Type":
1899     context.optional = false;
1900     if (context.isUnion(tree)) {
1901       bool optional = tree.matches[$-1] == "?";
1902       context.optional = optional;
1903       if (optional) {
1904         if (context.returnType)
1905           a.put("Optional!(");
1906         else
1907           a.put("bool, ");
1908       }
1909       a.put("scope ref ");
1910       if (!context.isTypedef(tree)) {
1911         a.put("SumType!(");
1912       }
1913       context.sumType = true;
1914       tree.children[0].children.putWithDelimiter!(generateDImports)(", ", a, context.withSkipOptional);
1915       if (optional && context.returnType)
1916         a.put(")");
1917       if (!context.isTypedef(tree))
1918         a.put(")");
1919       break;
1920     } else if (context.isTypedef(tree) && context.semantics.isNullable(tree)) {
1921       auto typeName = tree.getTypeName();
1922       auto aliasedType = context.semantics.getAliasedType(typeName);
1923       aliasedType.generateDImports(a, context);
1924     } else {
1925       context.primitiveType = context.isPrimitive(tree);
1926       tree.children[0].generateDImports(a, context);
1927     }
1928     break;
1929   case "WebIDL.UnionMemberType":
1930     context.optional = false;
1931     if (!context.isTypedef(tree) && context.isUnion(tree)) {
1932       bool optional = tree.matches[$-1] == "?";
1933       context.optional = optional;
1934       if (optional) {
1935         if (context.returnType)
1936           a.put("Optional!(");
1937         else
1938           a.put("bool, ");
1939       }
1940       a.put("SumType!(");
1941       tree.children[0].children.putWithDelimiter!(generateDImports)(", ", a, context);
1942       if (optional && context.returnType)
1943         a.put(")");
1944       a.put(")");
1945       break;
1946     } else
1947       tree.children.each!(c => c.generateDImports(a, context));
1948     break;
1949   case "WebIDL.IncludesStatement":
1950     break;
1951   case "WebIDL.ReturnType":
1952     context.returnType = true;
1953     if (tree.children.length > 0) {
1954       if (context.isPrimitive(tree.children[0]) || context.isUnion(tree.children[0]) || tree.matches[$-1] == "?")
1955         tree.children[0].generateDImports(a, context);
1956       else if (tree.matches[0] != "void")
1957         a.put("Handle");
1958       else
1959         a.put("void");
1960     }
1961     else
1962       a.put(tree.matches[0]);
1963     break;
1964   case "WebIDL.OperationRest":
1965   case "WebIDL.Enum":
1966   case "WebIDL.Dictionary":
1967   case "WebIDL.DictionaryMember":
1968   case "WebIDL.Iterable":
1969   case "WebIDL.Typedef":
1970   case "WebIDL.SetlikeRest":
1971   case "WebIDL.MaplikeRest":
1972   case "WebIDL.CallbackRest":
1973   case "WebIDL.MixinRest":
1974     break;
1975   case "WebIDL.SequenceType":
1976     a.put("Handle");
1977     break;
1978   case "WebIDL.MixinMember":
1979   case "WebIDL.Const":
1980   case "WebIDL.Partial":
1981     break;
1982   case "WebIDL.PromiseType":
1983     a.put("Promise!(");
1984     tree.children[0].generateDImports(a, context);
1985     a.put(")");
1986     break;
1987   case "WebIDL.PartialInterfaceRest":
1988     tree.children[1].generateDImports(a, context);
1989     break;
1990   default:
1991     tree.children.each!(c => generateDImports(c, a, context));
1992   }
1993 }
1994 
1995 string orNone(string s) {
1996   if (s.length == 0)
1997     return "none";
1998   return s;
1999 }
2000 
2001 auto createOptionalOverloads(FunctionNode func) {
2002   static bool notOptional(ref Argument arg) {
2003     return arg.argRest.matches[0] != "optional";
2004   }
2005   return chain([func],func.args.retro.until!(notOptional).enumerate.map!((t){
2006         return new FunctionNode(func.name, func.args[0..$-(t.index+1)], func.result, func.type, func.manglePostfix~t.index.to!string, func.baseType, func.customName);
2007       }));
2008 }
2009 
2010 IR toIr(ref Module module_) {
2011   auto app = appender!(Node[]);
2012   module_.iterate!(toIr)(app, Context(module_.semantics));
2013   return new IR([new ModuleNode(module_, app.data)], module_.semantics);
2014 }
2015 IR toIr(ref Semantics semantics) {
2016   auto app = appender!(ModuleNode[]);
2017   foreach(module_; semantics.modules) {
2018     auto mApp = appender!(Node[]);
2019     module_.iterate!(toIr)(mApp, Context(semantics));
2020     app.put(new ModuleNode(module_, mApp.data));
2021   }
2022   return new IR(app.data, semantics);
2023 }
2024 
2025 void toIr(Appender)(ParseTree tree, ref Appender a, Context context) {
2026   switch (tree.name) {
2027   case "WebIDL.Namespace":
2028     // TODO: get matching partials
2029     auto app = appender!(Node[]);
2030     tree.children[1..$].each!(c => toIr(c, app, context));
2031     a.put(new StructNode(tree.children[0].matches[0], ParseTree.init, app.data, Yes.isStatic));
2032     break;
2033   case "WebIDL.InterfaceRest":
2034     ParseTree baseType;
2035     if (tree.children[1].children.length > 0 && tree.children[1].children[0].matches.length != 0)
2036       baseType = tree.children[1].children[0];
2037     auto app = appender!(Node[]);
2038     tree.children[2..$].each!(c => toIr(c, app, context));
2039     a.put(new StructNode(tree.children[0].matches[0], baseType, app.data));
2040     if (context.extendedAttributeList != ParseTree.init) {
2041       auto constructors = context.extendedAttributeList.children.filter!(attr => attr.matches[0] == "Constructor").array();
2042       auto exposeds = context.extendedAttributeList.children.filter!(attr => attr.matches[0] == "Exposed").map!(e => e.children[0].children[1].children).joiner();
2043       bool overloads = constructors.length > 1;
2044       foreach(constructor; constructors) {
2045         Argument[] args;
2046         string manglePostfix;
2047         if (constructor.children[0].children.length > 1) {
2048           auto argList = constructor.children[0].children[1];
2049           args = zip(argList.extractArguments,argList.extractTypes,argList.extractDefaults,argList.extractArgumentRests).map!(a=>Argument(a[0],a[1],a[2],a[3])).array();
2050         }
2051         if (overloads) {
2052           manglePostfix = "_" ~ args.map!(arg => arg.type.mangleTypeJs(context.semantics)).joiner("_").text;
2053         }
2054         auto name = tree.children[0].matches[0];
2055         auto result = context.semantics.getType(tree.children[0].matches[0]);
2056         foreach(exposed; exposeds) {
2057           auto baseTypeName = exposed.matches[0];
2058           a.put(new ExposedConstructorNode(name, args, result, baseTypeName, manglePostfix));
2059         }
2060       }
2061     }
2062     break;
2063   case "WebIDL.IncludesStatement":
2064     context.isIncludes = true;
2065     context.includes = tree;
2066     context.typeName = tree.children[1].matches[0];
2067     ParseTree mixinRest = context.getType(context.typeName);
2068     auto partials = context.getMatchingPartials(context.typeName);
2069     auto app = appender!(Node[]);
2070     mixinRest.children[1].toIr(app, context);
2071     partials.each!((c){
2072         if (c.children[0].children[0].children[0].name == "WebIDL.MixinRest")
2073           toIr(c.children[0].children[0].children[0].children[1], app, context);
2074         else
2075           toIr(c.children[0], app, context);
2076       });
2077     a.put(new StructIncludesNode(tree.children[0].matches[0], tree.children[1].matches[0], app.data));
2078     break;
2079   case "WebIDL.Iterable":
2080     // a.putLn("// TODO: add iterable");
2081     break;
2082   case "WebIDL.MixinRest":
2083     context.typeName = tree.children[0].matches[0];
2084     auto partials = context.getMatchingPartials(context.typeName);
2085     auto app = appender!(Node[]);
2086     tree.children[1].toIr(app, context);
2087     partials.each!(c => toIr(c.children[0].children[0].children[0].children[1], app, context));
2088     a.put(new MixinNode(tree.children[0].matches[0], app.data));
2089     break;
2090   case "WebIDL.Const":
2091     a.put(new ConstNode(tree.children[0].generateDType(context),
2092                         tree.children[1].generateDType(context),
2093                         tree.children[2].generateDType(context)));
2094     break;
2095   case "WebIDL.InterfaceMember":
2096     context.extendedAttributeList = tree.children[0];
2097     tree.children[1].toIr(a, context);
2098     break;
2099   case "WebIDL.ReadOnlyMember":
2100     context.readonly = true;
2101     tree.children[0].toIr(a, context);
2102     break;
2103   case "WebIDL.MixinMember":
2104     if (tree.children[0].name == "WebIDL.ReadOnly") {
2105       if (tree.children[0].matches[0] == "readonly") {
2106         context.readonly = true;
2107       }
2108       tree.children[1].toIr(a, context);
2109     } else
2110       tree.children[0].toIr(a, context);
2111     break;
2112   case "WebIDL.AttributeRest":
2113     if (context.isIncludes) {
2114       auto name = tree.children[1].matches[0];
2115       auto baseName = context.includes.children[0].matches[0];
2116       auto attrType = tree.children[0];
2117       auto attrArg = Argument(name, attrType);
2118 
2119       if (!context.readonly)
2120         a.put(new FunctionNode( name, [attrArg], ParseTree.init, FunctionType.Attribute | FunctionType.Includes, "Set", baseName, ""));
2121       a.put(new FunctionNode( name, [], attrType, FunctionType.Attribute | FunctionType.Includes, "Get", baseName, ""));
2122       break;
2123     }
2124     if (context.isPartial) {
2125       auto name = tree.children[1].matches[0];
2126       auto baseName = context.partial.children[0].children[0].matches[0];
2127       auto attrType = tree.children[0];
2128       auto attrArg = Argument(name, attrType);
2129 
2130       if (!context.readonly)
2131         a.put(new FunctionNode( name, [attrArg], ParseTree.init, FunctionType.Attribute | FunctionType.Partial, "Set", baseName, ""));
2132       a.put(new FunctionNode( name, [], attrType, FunctionType.Attribute | FunctionType.Partial, "Get", baseName, ""));
2133       break;
2134     }
2135     auto name = tree.children[1].matches[0];
2136     auto attrType = tree.children[0];
2137     if (!context.readonly)
2138       a.put(new FunctionNode( name, [Argument(name, attrType)], ParseTree.init, FunctionType.Attribute, "Set", "", ""));
2139     a.put(new FunctionNode( name, [], attrType, FunctionType.Attribute, "Get", "", ""));
2140     break;
2141   case "WebIDL.ExtendedAttributeList":
2142     context.extendedAttributeList = tree;
2143     break;
2144   case "WebIDL.SpecialOperation":
2145     if (tree.children[1].children[1].children[0].matches[0] != "") {
2146       // context.customName = tree.children[0].matches[0];
2147       tree.children[1].toIr(a, context);
2148       switch(tree.matches[0]) {
2149       case "getter":
2150         // (cast(FunctionNode)a.data[$-1]).type |= FunctionType.Getter;
2151         (cast(FunctionNode)a.data[$-1]).manglePostfix = tree.children[0].matches[0];
2152         break;
2153       case "setter":
2154         // (cast(FunctionNode)a.data[$-1]).type |= FunctionType.Getter;
2155         (cast(FunctionNode)a.data[$-1]).manglePostfix = tree.children[0].matches[0];
2156         break;
2157       default: break;
2158       }
2159       break;
2160     }
2161     auto rest = tree.children[1].children[1];
2162     auto args = zip(rest.children[1].extractArguments,rest.children[1].extractTypes, rest.children[1].extractArgumentRests).map!(a=>Argument(a[0],a[1],ParseTree.init,a[2])).array();
2163     auto result = tree.children[1].children[0];
2164     switch(tree.matches[0]) {
2165     case "getter":
2166       assert(args.length == 1);
2167       a.put(new FunctionNode( "getter", args, result, FunctionType.OpIndex | FunctionType.Getter, "", "", ""));
2168       args = args.dup();
2169       args[0].templated = true;
2170       a.put(new FunctionNode( "getter", args, result, FunctionType.OpDispatch | FunctionType.Getter, "", "" ,""));
2171       break;
2172     case "setter":
2173       assert(args.length == 2);
2174       a.put(new FunctionNode( "setter", args, ParseTree.init, FunctionType.OpIndexAssign | FunctionType.Setter, "", "", ""));
2175       args = args.dup();
2176       args[0].templated = true;
2177       a.put(new FunctionNode( "setter", args, ParseTree.init, FunctionType.OpDispatch | FunctionType.Setter, "", "", ""));
2178       break;
2179     case "deleter":
2180       a.put(new FunctionNode( "remove", args, ParseTree.init, FunctionType.Function | FunctionType.Deleter, "", "", "deleter"));
2181       break;
2182     default: assert(0);
2183     }
2184     break;
2185   case "WebIDL.RegularOperation":
2186     auto rest = tree.children[1];
2187     auto args = zip(rest.children[1].extractArguments,rest.children[1].extractTypes,rest.children[1].extractDefaults,rest.children[1].extractArgumentRests).map!(a=>Argument(a[0],a[1],a[2],a[3])).array();
2188     auto result = tree.children[0];
2189     if (context.isIncludes) {
2190       auto name = tree.children[1].matches[0];
2191       auto baseName = context.includes.children[0].matches[0];
2192       createOptionalOverloads(new FunctionNode( name, args, result, FunctionType.Function | FunctionType.Includes, "", baseName, context.customName)).copy(a);
2193       break;
2194     }
2195     if (context.isPartial) {
2196       auto name = rest.children[0].matches[0];
2197       auto baseName = context.partial.children[0].children[0].matches[0];
2198       createOptionalOverloads(new FunctionNode( name, args, result, FunctionType.Function | FunctionType.Partial, "", baseName, context.customName)).copy(a);
2199       break;
2200     }
2201     auto name = rest.children[0].matches[0];
2202     createOptionalOverloads(new FunctionNode( name, args, result, FunctionType.Function, "", "", context.customName)).copy(a);
2203     break;
2204   case "WebIDL.Enum":
2205     a.put(new EnumNode(tree.children[0].matches[0], tree.children[1].children.map!(c => c.matches[0][1..$-1].orNone.friendlyName).joiner(",\n  ").text));
2206     break;
2207   case "WebIDL.Dictionary":
2208     ParseTree baseType;
2209     if (tree.children[1].children.length > 0 && tree.children[1].children[0].matches.length != 0)
2210       baseType = tree.children[1].children[0];
2211     auto app = appender!(Node[]);
2212     Argument[] args;
2213     app.put(new FunctionNode("create", args, ParseTree.init, FunctionType.DictionaryConstructor, "", "", ""));
2214     tree.children[2].toIr(app, context);
2215     a.put(new StructNode(tree.children[0].matches[0], baseType, app.data));
2216     break;
2217   case "WebIDL.DictionaryMember":
2218     context.extendedAttributeList = tree.children[0];
2219     tree.children[1].toIr(a, context);
2220     break;
2221   case "WebIDL.MaplikeRest":
2222     a.put(new MaplikeNode(tree.children[0], tree.children[1]));
2223     break;
2224   case "WebIDL.DictionaryMemberRest":
2225     auto name = tree.children[1].matches[0];
2226     auto paramType = tree.children[0];
2227     a.put(new FunctionNode(name, [Argument(name, paramType)], ParseTree.init, FunctionType.Attribute, "Set", "", ""));
2228     a.put(new FunctionNode(name, [], paramType, FunctionType.Attribute, "Get", "", ""));
2229     break;
2230   case "WebIDL.Typedef":
2231     a.put(new TypedefNode(tree.children[1].matches[0], tree.children[0].generateDType(context), tree.children[0]));
2232     break;
2233   case "WebIDL.Partial":
2234     context.partial = tree;
2235     if (tree.children[0].children[0].name == "WebIDL.PartialInterfaceOrPartialMixin") {
2236       context.typeName = tree.children[0].children[0].children[0].children[0].matches[0];
2237       auto baseType = context.getType(context.typeName);
2238       if (baseType.name == "WebIDL.MixinRest")
2239         return;
2240     } else if (tree.children[0].children[0].name == "WebIDL.PartialDictionary")
2241       context.typeName = tree.children[0].children[0].children[0].matches[0];
2242     tree.children[0].toIr(a, context);
2243     break;
2244   case "WebIDL.PartialInterfaceRest":
2245     tree.children[1].toIr(a, context);
2246     break;
2247   case "WebIDL.CallbackRest":
2248     auto argList = tree.children[2];
2249     auto args = zip(argList.extractArguments,argList.extractTypes,argList.extractDefaults,argList.extractArgumentRests).map!(a=>Argument(a[0],a[1],a[2],a[3])).array();
2250 
2251     a.put(new CallbackNode(tree.children[0].matches[0], tree.children[1], args));
2252     break;
2253   default:
2254     tree.children.each!(c => toIr(c, a, context));
2255     return;
2256   }
2257 }
2258 
2259 auto collectFunctions(IR ir, string[] filter) {
2260   import std.algorithm : canFind;
2261   auto app = appender!(FunctionNode[]);
2262   void recurse(Node node, string parentName) {
2263     if (auto fun = cast(FunctionNode)node) {
2264       string name = mangleName(parentName.length > 0 ? parentName : fun.baseType, fun.name, fun.manglePostfix);
2265       if (filter.length == 0 || filter.canFind(name)) {
2266         app.put(fun);
2267       }
2268     } else if (auto structNode = cast(StructNode)node) {
2269       foreach(child; structNode.children)
2270         recurse(child, structNode.name);
2271     } else if (auto includesNode = cast(StructIncludesNode)node) {
2272       foreach(child; includesNode.children)
2273         recurse(child, includesNode.name);
2274     } else if (auto moduleNode = cast(ModuleNode)node) {
2275       foreach(child; moduleNode.children)
2276         recurse(child, "");
2277     }
2278   }
2279   ir.nodes.each!(node => recurse(node, ""));
2280   return app.data;
2281 }
2282 auto collectCallbacks(alias pred)(IR ir) {
2283   import std.algorithm : canFind;
2284   auto app = appender!(CallbackNode[]);
2285   void recurse(Node node) {
2286     if (auto cb = cast(CallbackNode)node) {
2287       if (pred(cb)) {
2288         app.put(cb);
2289       }
2290     } else if (auto moduleNode = cast(ModuleNode)node) {
2291       foreach(child; moduleNode.children)
2292         recurse(child);
2293     }
2294   }
2295   ir.nodes.each!(node => recurse(node));
2296   return app.data;
2297 }
2298 auto collectUsedCallbackNames(IR ir, FunctionNode[] funcs) {
2299   return funcs.map!(f => f.args).joiner.map!(a => a.type).filter!(t => ir.semantics.isCallback(t)).map!((c){
2300       if (ir.semantics.isTypedef(c))
2301         return ir.semantics.getAliasedType(c.getTypeName());
2302       return c;
2303     }).map!(t => t.getTypeName()).array().sort.uniq;
2304 }
2305 void generateDecodedTypes(IR ir, ref Appender!(ParseTree[]) a, string[] filter) {
2306   auto semantics = ir.semantics;
2307   auto funcs = collectFunctions(ir, filter);
2308   auto callbackNames = collectUsedCallbackNames(ir, funcs);
2309   auto cbs = collectCallbacks!(c => callbackNames.canFind(c.name))(ir);
2310   foreach(fun; funcs) {
2311     foreach(arg; fun.args) {
2312       if (semantics.isNullableTypedef(arg.type)) {
2313         a.put(arg.type.stripNullable);
2314       } else if (semantics.isUnion(arg.type) || semantics.isEnum(arg.type)) {
2315         a.put(arg.type);
2316       }
2317     }
2318   }
2319   foreach(cb; cbs) {
2320     if (semantics.isUnion(cb.result) || semantics.isEnum(cb.result) || semantics.isAny(cb.result))
2321       a.put(cb.result);
2322   }
2323 }
2324 void generateEncodedTypes(IR ir, ref Appender!(ParseTree[]) a, string[] filter) {
2325   auto semantics = ir.semantics;
2326   auto funcs = collectFunctions(ir, filter);
2327   foreach(fun; funcs) {
2328     if (fun.result != ParseTree.init && fun.result.matches[0] != "void") {
2329       if (semantics.isStringType(fun.result) || semantics.isUnion(fun.result) || semantics.isNullable(fun.result) || semantics.isEnum(fun.result)) {
2330         a.put(fun.result);
2331       }
2332     }
2333     foreach(arg; fun.args) {
2334       if (semantics.isCallback(arg.type.matches[0])){
2335         auto argList = semantics.getArgumentList(arg.type);
2336         auto types = extractTypes(argList);
2337         foreach(type; types) {
2338           if (semantics.isStringType(type) || semantics.isUnion(type)
2339               || semantics.isNullable(type) || semantics.isEnum(type))
2340             a.put(type);
2341         }
2342       }
2343     }
2344   }
2345 }
2346 
2347 auto stripNullable(const ParseTree tree) {
2348   ParseTree clone = tree.dup();
2349   if (clone.children.length == 0)
2350     return clone;
2351   switch (clone.name) {
2352   case "WebIDL.ExtendedAttributeList": return clone;
2353   case "WebIDL.CallbackRest": return clone;
2354   case "WebIDL.Dictionary": return clone;
2355   case "WebIDL.CallbackOrInterfaceOrMixin": return clone;
2356   case "WebIDL.PartialDefinition": return clone;
2357   default:
2358   }
2359   if (clone.children[$-1].name == "WebIDL.Null") {
2360     clone.children = clone.children.dup;
2361     clone.children[$-1].matches = [""];
2362     clone.matches[$-1] = "";
2363     return clone;
2364   } else if (clone.matches[$-1] == "?") {
2365     clone.matches = clone.matches.dup[0..$-1];
2366   }
2367   clone.children = clone.children.map!(c => c.stripNullable).array;
2368   return clone;
2369 }
2370 
2371 void generateJsDecoder(Decoder)(Decoder decoder, ref IndentedStringAppender a, ref Semantics semantics, bool isVar) {
2372   a.put("spasm_decode_");
2373   a.put(decoder.mangled);
2374   if (isVar) {
2375     a.put(" = ");
2376   } else {
2377     a.put(": ");
2378   }
2379   if (semantics.isPrimitive(decoder.tree)) {
2380     switch (decoder.mangled) {
2381     case "uint":
2382     case "bool":
2383       a.put("getUInt");
2384       return;
2385     case "double":
2386       a.put("getDouble");
2387       return;
2388     default:
2389     }
2390   }
2391   if (decoder.mangled == "Handle" || decoder.mangled == "object" || decoder.mangled == "sequence" || decoder.mangled == "object" || decoder.mangled == "record") {
2392     a.put("decode_handle");
2393     return;
2394   }
2395   a.putLn("(ptr)=>{");
2396   a.indent();
2397   // enum
2398   // optional!T
2399   // sumType!Ts
2400   // typedef to T
2401   if (semantics.isNullableTypedef(decoder.tree)) {
2402     string typeName = decoder.tree.getTypeName();
2403     auto aliasedType = semantics.getAliasedType(typeName);
2404     uint structSize = semantics.getSizeOf(aliasedType);
2405     a.putLn(["if (getBool(ptr+", structSize.to!string, ")) {"]);
2406     a.indent();
2407     auto typedefMangled = aliasedType.mangleTypeJs(semantics);
2408     a.putLn(["return spasm_decode_",typedefMangled,"(ptr);"]);
2409     a.undent();
2410     a.putLn("}");
2411   } else if (semantics.isTypedef(decoder.tree)) {
2412     string typeName = decoder.tree.getTypeName();
2413     auto aliasedType = semantics.getAliasedType(typeName);
2414     auto typedefMangled = aliasedType.mangleTypeJs(semantics);
2415     a.putLn(["return spasm_decode_",typedefMangled,"(ptr);"]);
2416   } else if (semantics.isNullable(decoder.tree)) {
2417     auto baseType = decoder.tree.stripNullable;
2418     uint structSize = semantics.getSizeOf(baseType);
2419     a.putLn(["if (getBool(ptr+", structSize.to!string, ")) {"]);
2420     a.indent();
2421     auto typedefMangled = baseType.mangleTypeJs(semantics);
2422     a.putLn(["return spasm_decode_",typedefMangled,"(ptr);"]);
2423     a.undent();
2424     a.putLn("}");
2425   } else if (semantics.isEnum(decoder.tree)) {
2426     string typeName = decoder.tree.getTypeName();
2427     auto aliasedType = (typeName in semantics.types).tree;
2428     a.putLn(["const vals = [",aliasedType.children[1].children.map!(c => c.matches[0]).joiner(", ").text,"];"]);
2429     a.putLn("return vals[ptr];");
2430   } else if (semantics.isUnion(decoder.tree)) {
2431     void outputChild(Child)(Child c, ref Semantics semantics) {
2432       a.putLn(["if (getUInt(ptr) == ", c.index.to!string, ") {"]);
2433       a.indent();
2434       a.putLn(["return spasm_decode_",c.value.mangleTypeJs(semantics),"(ptr+4);"]);
2435       a.undent();
2436       a.put("}");
2437     }
2438     auto children = semantics.getUnionChildren(decoder.tree).enumerate;
2439     if (children.length > 0) {
2440       children[0..$-1].each!((c){
2441         outputChild(c, semantics);
2442         a.put(" else ");
2443       });
2444       outputChild(semantics.getUnionChildren(decoder.tree).enumerate[$-1], semantics);
2445     }
2446     a.putLn("");
2447   } else if (semantics.isSequence(decoder.tree)) {
2448     a.putLn("// sequence");
2449     assert(false, "not implemented");
2450   } else if (semantics.isPrimitive(decoder.tree)) {
2451     switch (decoder.mangled) {
2452     case "ulong":
2453       a.putLn("return getUInt(ptr) + (getUInt(ptr+4) << 32);");
2454       break;
2455     default:
2456       a.putLn("// primitive");
2457       a.putLn(["// ", decoder.tree.name]);
2458       a.putLn(decoder.tree.toString);
2459       break;
2460     }
2461     assert(false, "not implemented");
2462   } else {
2463     a.putLn(["// ", decoder.tree.name]);
2464     a.putLn("// other");
2465     assert(false, "not implemented");
2466   }
2467   // where T can be any of the above
2468   // and Ts two or more of the set including the above and the following:
2469   // - any primitive (double, bool, int; unsigned/signed; etc.)
2470   // - a JsHandle
2471   a.undent();
2472   a.put("}");
2473 }
2474 void generateJsEncoder(Encoder)(Encoder encoder, ref IndentedStringAppender a, ref Semantics semantics, bool isVar) {
2475   a.put("spasm_encode_");
2476   a.put(encoder.mangled);
2477   if (isVar) {
2478     a.put(" = ");
2479   } else {
2480     a.put(": ");
2481   }
2482   if (semantics.isPrimitive(encoder.tree)) {
2483     switch (encoder.mangled) {
2484     case "uint":
2485     case "bool":
2486       a.put("setUInt");
2487       return;
2488     case "double":
2489       a.put("setDouble");
2490       return;
2491     default:
2492     }
2493   }
2494   if (encoder.mangled == "Handle" || encoder.mangled == "object" || encoder.mangled == "sequence" || encoder.mangled == "object" || encoder.mangled == "record") {
2495     a.put("encode_handle");
2496     return;
2497   }
2498   a.putLn("(ptr, val)=>{");
2499   a.indent();
2500   // enum
2501   // optional!T
2502   // sumType!Ts
2503   // typedef to T
2504   if (semantics.isNullableTypedef(encoder.tree)) {
2505     string typeName = encoder.tree.getTypeName();
2506     auto aliasedType = semantics.getAliasedType(typeName);
2507     uint structSize = semantics.getSizeOf(aliasedType);
2508     a.putLn(["if (setBool(ptr+", structSize.to!string, ", isDefined(val))) {"]);
2509     a.indent();
2510     auto typedefMangled = aliasedType.mangleTypeJs(semantics);
2511     a.putLn(["spasm_encode_",typedefMangled,"(ptr, val);"]);
2512     a.undent();
2513     a.putLn("}");
2514   } else if (semantics.isTypedef(encoder.tree)) {
2515     string typeName = encoder.tree.getTypeName();
2516     auto aliasedType = semantics.getAliasedType(typeName);
2517     auto typedefMangled = aliasedType.mangleTypeJs(semantics);
2518     a.putLn(["spasm_encode_",typedefMangled,"(ptr, val);"]);
2519   } else if (semantics.isNullable(encoder.tree)) {
2520     auto baseType = encoder.tree.stripNullable;
2521     uint structSize = semantics.getSizeOf(baseType);
2522     a.putLn(["if (setBool(ptr+", structSize.to!string, ", isDefined(val))) {"]);
2523     a.indent();
2524     auto typedefMangled = baseType.mangleTypeJs(semantics);
2525     a.putLn(["spasm_encode_",typedefMangled,"(ptr, val);"]);
2526     a.undent();
2527     a.putLn("}");
2528   } else if (semantics.isEnum(encoder.tree)) {
2529     string typeName = encoder.tree.getTypeName();
2530     auto aliasedType = (typeName in semantics.types).tree;
2531     a.putLn(["const vals = [",aliasedType.children[1].children.map!(c => c.matches[0]).joiner(", ").text,"];"]);
2532     a.putLn("setInt(ptr, vals.indexOf(val))");
2533   } else if (semantics.isUnion(encoder.tree)) {
2534     void outputChild(Child)(Child c, ref Semantics semantics) {
2535         a.putLn(["if (val instanceof ",c.value.getTypeName,") {"]);
2536         a.indent();
2537         a.putLn(["setUInt(ptr, ",c.index.to!string ,");"]);
2538         a.putLn(["spasm_encode_",c.value.mangleTypeJs(semantics),"(ptr+4, val);"]);
2539         a.undent();
2540         a.put("}");
2541     }
2542     auto children = semantics.getUnionChildren(encoder.tree).enumerate;
2543     if (children.length > 0) {
2544       children[0..$-1].each!((c){
2545         outputChild(c, semantics);
2546         a.put(" else ");
2547       });
2548       outputChild(semantics.getUnionChildren(encoder.tree).enumerate[$-1], semantics);
2549     }
2550     a.putLn("");
2551   } else if (semantics.isSequence(encoder.tree)) {
2552     a.putLn("// sequence");
2553     assert(false, "not implemented");
2554   } else if (semantics.isPrimitive(encoder.tree)) {
2555     switch (encoder.mangled) {
2556     case "ulong":
2557       a.putLn("setUInt(ptr, val & 0xffffffff);");
2558       a.putLn("setUInt(ptr+4, val >> 32);");
2559       break;
2560     default:
2561       a.putLn("// primitive");
2562       a.putLn(["// ", encoder.tree.name]);
2563       a.putLn(encoder.tree.toString);
2564       break;
2565     }
2566     assert(false, "not implemented");
2567   } else {
2568     a.putLn(["// ", encoder.tree.name]);
2569     a.putLn("// other");
2570     assert(false, "not implemented");
2571   }
2572   // where T can be any of the above
2573   // and Ts two or more of the set including the above and the following:
2574   // - any primitive (double, bool, int; unsigned/signed; etc.)
2575   // - a JsHandle
2576   a.undent();
2577   a.put("}");
2578 }
2579 
2580 
2581 void iterate(alias fun, Appender, Args...)(ref Module module_, ref Appender app, Args args) {
2582   foreach (key; module_.types.keys.array.sort) {
2583     auto type = module_.types[key];
2584     static if (Args.length > 0)
2585       args[0].extendedAttributeList = type.attributes;
2586     fun(type.tree, app, args);
2587   }
2588   foreach (namespace; module_.namespaces.dup.schwartzSort!(i => i.tree.name)) { 
2589     fun(namespace.tree, app, args);
2590   }
2591   foreach (partialType; module_.partials.dup.schwartzSort!(i => i.tree.name)) {
2592     static if (Args.length > 0)
2593       args[0].extendedAttributeList = partialType.attributes;
2594     fun(partialType.tree, app, args);
2595   }
2596   foreach (mixinType; module_.mixins.dup.schwartzSort!(i => i.tree.name)) {
2597     fun(mixinType.tree, app, args);
2598   }
2599 }
2600 
2601 ParseTree[] getUnionChildren(ref Semantics semantics, ParseTree tree) {
2602   if (tree.name == "WebIDL.ReturnType")
2603     return semantics.getUnionChildren(tree.children[0].children[0]);
2604   if (tree.name == "WebIDL.TypeWithExtendedAttributes")
2605     return semantics.getUnionChildren(tree.children[1].children[0]);
2606   if (tree.name == "WebIDL.Type") {
2607     assert(tree.children[0].name == "WebIDL.UnionType");
2608     return semantics.getUnionChildren(tree.children[0]);
2609   }
2610   assert(tree.name == "WebIDL.UnionType");
2611 
2612   // check each child's first child, if that is a UnionType (or via Typedef), we need to concat types
2613   auto children = appender!(ParseTree[]);
2614   foreach (child; tree.children) {
2615     auto member = child.children[0];
2616     if (member.name == "WebIDL.UnionType") {
2617       semantics.getUnionChildren(member).copy(children);
2618     } else if (semantics.isTypedef(child)) {
2619       string typeName = child.getTypeName();
2620       auto aliasedType = semantics.getAliasedType(typeName);
2621       if (semantics.isUnion(aliasedType)) {
2622         semantics.getUnionChildren(aliasedType).copy(children);
2623       } else
2624         children.put(child);
2625     } else
2626       children.put(child);
2627   }
2628   return children.data;
2629 }
2630 struct TypeEncoder {
2631   string mangled;
2632   ParseTree tree;
2633   bool external;
2634 }
2635 struct TypeDecoder {
2636   string mangled;
2637   ParseTree tree;
2638 }
2639 TypeDecoder[] generateDecodedTypes(IR ir, string[] filter) {
2640   auto semantics = ir.semantics;
2641   auto app = appender!(ParseTree[]);
2642   ir.generateDecodedTypes(app, filter);
2643   auto decodedTypes = app.data.map!(t => TypeDecoder(t.mangleTypeJs(semantics),t)).array.sort!((a,b){return a.mangled < b.mangled;}).uniq!((a, b){return a.mangled == b.mangled;}).array;
2644 
2645   auto decoders = appender!(TypeDecoder[]);
2646   decodedTypes.copy(decoders);
2647 
2648   ulong start = 0, end = decoders.data.length;
2649   while (start != end) {
2650     foreach(decoder; decoders.data[start..end].dup) {
2651       if (semantics.isNullableTypedef(decoder.tree)) {
2652         string typeName = decoder.tree.getTypeName();
2653         auto aliasedType = semantics.getAliasedType(typeName);
2654         auto typedefMangled = aliasedType.mangleTypeJs(semantics);
2655         decoders.put(TypeDecoder(typedefMangled, aliasedType));
2656       } else if (semantics.isTypedef(decoder.tree)) {
2657         string typeName = decoder.tree.getTypeName();
2658         auto aliasedType = semantics.getAliasedType(typeName);
2659         auto typedefMangled = aliasedType.mangleTypeJs(semantics);
2660         decoders.put(TypeDecoder(typedefMangled, aliasedType));
2661       } else if (semantics.isNullable(decoder.tree)) {
2662         ParseTree clone = decoder.tree;
2663         auto baseType = clone.stripNullable;
2664         auto typedefMangled = baseType.mangleTypeJs(semantics);
2665         decoders.put(TypeDecoder(typedefMangled, baseType));
2666       } else if (semantics.isUnion(decoder.tree)) {
2667         foreach (child; semantics.getUnionChildren(decoder.tree)) {
2668           auto typedefMangled = child.mangleTypeJs(semantics);
2669           decoders.put(TypeDecoder(typedefMangled, child));
2670         }
2671       }
2672     }
2673     start = end;
2674     end = decoders.data.length;
2675   }
2676   return decoders.data;
2677 }
2678 
2679 TypeEncoder[] generateEncodedTypes(IR ir, string[] filter) {
2680   auto semantics = ir.semantics;
2681   auto app = appender!(ParseTree[]);
2682   ir.generateEncodedTypes(app, filter);
2683   auto encodedTypes = app.data.map!(t => TypeEncoder(t.mangleTypeJs(semantics),t,true)).array.sort!((a,b){return a.mangled < b.mangled;}).uniq!((a, b){return a.mangled == b.mangled;});
2684 
2685   auto encoders = appender!(TypeEncoder[]);
2686   encodedTypes.copy(encoders);
2687 
2688   ulong start = 0, end = encoders.data.length;
2689   while (start != end) {
2690     foreach(encoder; encoders.data[start..end].dup) {
2691       if (semantics.isNullableTypedef(encoder.tree)) {
2692         string typeName = encoder.tree.getTypeName();
2693         auto aliasedType = semantics.getAliasedType(typeName);
2694         auto typedefMangled = aliasedType.mangleTypeJs(semantics);
2695         encoders.put(TypeEncoder(typedefMangled, aliasedType, false));
2696       } else if (semantics.isTypedef(encoder.tree)) {
2697         string typeName = encoder.tree.getTypeName();
2698         auto aliasedType = semantics.getAliasedType(typeName);
2699         auto typedefMangled = aliasedType.mangleTypeJs(semantics);
2700         encoders.put(TypeEncoder(typedefMangled, aliasedType, false));
2701       } else if (semantics.isNullable(encoder.tree)) {
2702         ParseTree clone = encoder.tree;
2703         auto baseType = clone.stripNullable;
2704         auto typedefMangled = baseType.mangleTypeJs(semantics);
2705         encoders.put(TypeEncoder(typedefMangled, baseType, false));
2706       } else if (semantics.isUnion(encoder.tree)) {
2707         foreach (child; semantics.getUnionChildren(encoder.tree)) {
2708           auto typedefMangled = child.mangleTypeJs(semantics);
2709           encoders.put(TypeEncoder(typedefMangled, child, false));
2710         }
2711       }
2712     }
2713     start = end;
2714     end = encoders.data.length;
2715   }
2716   return encoders.data;
2717 }
2718 
2719 auto getImports(IR ir, Module module_) {
2720   import std.format : format;
2721   import std.typecons : tuple, Tuple;
2722   alias Item = Tuple!(Type,"type",string,"name");
2723   auto app = appender!(Item[]);
2724   auto semantics = ir.semantics;
2725   void extractTypes(alias sink)(Semantics semantics, ParseTree tree, Appender!(Item[]) app) {
2726     if (tree.name == "WebIDL.Typedef") {
2727       sink(tree.children[1].matches[0], semantics, app);
2728     } else if (tree.name == "WebIDL.NonAnyType" && tree.children[0].name == "WebIDL.Identifier") {
2729       sink(tree.matches[0], semantics, app);
2730     } else if (tree.name == "WebIDL.Inheritance") {
2731       if (tree.children.length > 0) {
2732         sink(tree.children[0].matches[0], semantics, app);
2733       }
2734     }
2735     else
2736       tree.children.each!(c => extractTypes!(sink)(semantics, c, app));
2737   }
2738   static void sink(string name, Semantics semantics, Appender!(Item[]) app) {
2739     if (name == "void")
2740       return;
2741     if (auto p = name in semantics.types) {
2742       if (semantics.isTypedef(name)) {
2743         auto baseType = semantics.getAliasedType(name);
2744         auto str = generateDType(baseType, Context(semantics));
2745         if (str.length < name.length) {
2746           if (auto q = str in semantics.types) {
2747             app.put(tuple!("type", "name")(*q, str));
2748             return;
2749           }
2750         }
2751       }
2752       app.put(tuple!("type","name")(*p, name));
2753     } else {
2754       writeln("Cannot find ", name);
2755     }
2756   }
2757   void recurse(Node node) {
2758     if (cast(FunctionNode)node) {
2759       extractTypes!sink(semantics, (cast(FunctionNode)node).result, app);
2760       (cast(FunctionNode)node).args.each!(arg => extractTypes!sink(semantics, arg.type, app));
2761     } else if (cast(TypedefNode)node) {
2762       extractTypes!sink(semantics, (cast(TypedefNode)node).rhs, app);
2763     } else if (cast(CallbackNode)node) {
2764       extractTypes!sink(semantics, (cast(CallbackNode)node).result, app);
2765       (cast(CallbackNode)node).args.each!(arg => extractTypes!sink(semantics, arg.type, app));
2766     } else if (cast(StructNode)node) {
2767       auto baseType = (cast(StructNode)node).baseType;
2768       if (baseType != ParseTree.init && baseType.matches[0].length > 0)
2769         sink(baseType.matches[0], semantics, app);
2770       (cast(StructNode)node).children.each!(node => recurse(node));
2771     } else if (cast(StructIncludesNode)node) {
2772       auto name = (cast(StructIncludesNode)node).name;
2773       sink(name, semantics, app);
2774       (cast(StructIncludesNode)node).children.each!(node => recurse(node));
2775     } else if (cast(ModuleNode)node)
2776       (cast (ModuleNode)node).children.each!(node => recurse(node));
2777     else if (cast(MixinNode)node) {
2778       (cast(MixinNode)node).children.each!(node => recurse(node));
2779     } else if (auto cons = cast(ExposedConstructorNode)node) {
2780       sink(cons.name, semantics, app);
2781     }
2782   }
2783   ir.nodes.filter!(n => n.module_ is module_).each!((node){
2784       node.children.each!(c => recurse(c));
2785     });
2786   return app.data.filter!(t => t.type.module_ !is module_).map!(t => t.type.module_.name).map!(n => format("import spasm.bindings.%s;",n)).array.sort.uniq.array;
2787   // return app.data.schwartzSort!(a => a.name).uniq!((a,b){return a.name == b.name;}).filter!(t => t.type.module_ !is module_).map!(t => format("import spasm.bindings.%s : %s;", t.type.module_.name,t.name)).array;
2788 }
2789 
2790 class IR {
2791   ModuleNode[] nodes;
2792   StructNode[string] structs;
2793   Semantics semantics;
2794   this(ModuleNode[] nodes, Semantics semantics) {
2795     this.nodes = nodes;
2796     this.semantics = semantics;
2797     nodes.each!(mod => mod.children.map!(n => cast(StructNode)n).filter!(n => n !is null).each!((n){
2798           structs[n.name] = n;
2799         }));
2800     this.resolvePartialsAndIncludes();
2801     this.mangleJsOverloads(semantics);
2802     this.moveExposedConstructors();
2803     this.collectMethods();
2804   }
2805 }
2806 
2807 auto collectMethods(IR ir) {
2808   ir.structs.values.each!((s){
2809       foreach(child; s.children) {
2810         if (auto constructor = cast(ExposedConstructorNode)child) {
2811           s.functions ~= constructor.name;
2812         } else if (auto includes = cast(StructIncludesNode)child) {
2813           foreach(includesChild; includes.children) {
2814             if (auto func = cast(FunctionNode)includesChild) {
2815               s.functions ~= func.name;
2816             }
2817           }
2818         } else if (auto func = cast(FunctionNode)child) {
2819           s.functions ~= func.name;
2820         }
2821       }
2822     });
2823 }
2824 template skipType(T) {
2825   auto skipType(Range)(auto ref Range range) {
2826     return range.filter!(i => (cast(T)i) is null);
2827   }
2828 }
2829 template mapType(T) {
2830   auto mapType(Range)(auto ref Range range) {
2831     return range.map!(n => cast(T)n).filter!(n => n !is null);
2832   }
2833 }
2834 auto resolvePartialsAndIncludes(IR ir) {
2835   ir.nodes.each!(mod => mod.children.skipType!(ExposedConstructorNode).mapType!(FunctionNode).filter!(n => n.baseType.length > 0).each!((n){
2836         if (auto p = n.baseType in ir.structs)
2837           p.children ~= n;
2838         else
2839           writeln("Error: Type ", n.baseType, " is unknown: ");
2840       }));
2841   ir.nodes.each!(mod => mod.children.mapType!(StructIncludesNode).each!((n){
2842         if (auto p = n.baseType in ir.structs) {
2843           p.children ~= n;
2844         }
2845         else
2846           writeln("Error: Type ", n.baseType, " is unknown");
2847       }));
2848 }
2849 auto moveExposedConstructors(IR ir) {
2850   foreach(mod; ir.nodes) {
2851     mod.children.mapType!(ExposedConstructorNode).each!((constructor){
2852       if (auto p = constructor.baseType in ir.structs) {
2853         p.children ~= constructor;
2854       } else
2855         writeln("Error: cannot find type ", constructor.baseType," to exposed constructor ",constructor.name, " on.");
2856       });
2857   }
2858   foreach(mod; ir.nodes) {
2859     mod.children = std.algorithm.remove!(n => cast(ExposedConstructorNode)n)(mod.children);
2860   }
2861 }
2862 auto mangleJsOverloads(IR ir, Semantics semantics) {
2863   void handleSet(Node[] nodes) {
2864     auto funcs = nodes.mapType!(FunctionNode).array;
2865     nodes.mapType!(StructIncludesNode).each!(i => handleSet(i.children));
2866     auto overloadGroups = funcs.schwartzSort!((a){
2867         if (a.customName.length > 0)
2868           return a.customName ~ a.manglePostfix;
2869         return a.name ~ a.manglePostfix;
2870       }).groupBy.map!(g => g.array).filter!(g => g.length > 1);
2871     foreach(group; overloadGroups) {
2872       foreach(fun; group) {
2873         fun.manglePostfix ~= "_" ~ fun.args.map!(arg => arg.type.mangleTypeJs(semantics)).joiner("_").text;
2874       }
2875     }
2876   }
2877   import std.algorithm : schwartzSort;
2878   foreach(item; ir.structs.byValue) {
2879     handleSet(item.children);
2880   }
2881   foreach(item; ir.nodes.map!(n => n.children).joiner) {
2882     auto mixinNode = cast(MixinNode)item;
2883     if (mixinNode)
2884       handleSet(mixinNode.children);
2885   }
2886 }
2887 string generateDBindings(IR ir, Module module_) {
2888   auto app = IndentedStringAppender();
2889   auto context = Context(module_.semantics);
2890   ir.nodes.filter!(mod => mod.module_ is module_).each!(n => n.toDBinding(module_.semantics, &app));
2891   return app.data;
2892 }
2893 
2894 string generateDImports(IR ir, Module module_) {
2895   auto app = IndentedStringAppender();
2896   ir.nodes.filter!(mod => mod.module_ is module_).each!(n => n.toDImport(module_.semantics, &app));
2897   if (app.data.length > 0)
2898     return app.data[0 .. $ - 1]; // remove last newline
2899   return app.data;
2900 }
2901 
2902 string generateSingleJsBinding(IR ir, string[] filtered = []) {
2903   import std.algorithm : map, filter, joiner, each, sort, uniq, cmp;
2904   import std.array : array;
2905   import std.conv : text;
2906   import std.typecons : tuple;
2907   auto semantics = ir.semantics;
2908   auto app = IndentedStringAppender();
2909   app.putLn("// File is autogenerated with `dub spasm:webidl -- --bindgen`");
2910   app.putLn("import {spasm as spa, encoders as encoder, decoders as decoder} from '../modules/spasm.js';");
2911   app.putLn("let spasm = spa;");
2912   app.putLn("let memory = {};");
2913   app.putLn("const objects = spasm.objects;");
2914   app.putLn("const addObject = spasm.addObject;");
2915   app.putLn("const setupMemory = () => {");
2916   app.putLn("    let buffer = spasm.memory.buffer;");
2917   app.putLn("    if (memory.buffer == buffer)");
2918   app.putLn("        return;");
2919   app.putLn("    memory.buffer = buffer;");
2920   app.putLn("    memory.heapi32s = new Int32Array(buffer)");
2921   app.putLn("    memory.heapi32u = new Uint32Array(buffer)");
2922   app.putLn("    memory.heapi16s = new Int16Array(buffer)");
2923   app.putLn("    memory.heapi16u = new Uint16Array(buffer)");
2924   app.putLn("    memory.heapi8s = new Int8Array(buffer)");
2925   app.putLn("    memory.heapi8u = new Uint8Array(buffer)");
2926   app.putLn("    memory.heapf32 = new Float32Array(buffer)");
2927   app.putLn("    memory.heapf64 = new Float64Array(buffer)");
2928   app.putLn("}");
2929 
2930   auto decodedTypes = ir.generateDecodedTypes(filtered).sort!((a,b){return a.mangled < b.mangled;}).uniq!((a, b){return a.mangled == b.mangled;});
2931   auto encodedTypes = ir.generateEncodedTypes(filtered).sort!((a,b){return a.mangled < b.mangled;}).uniq!((a, b){return a.mangled == b.mangled;});
2932   app.putLn("const setBool = (ptr, val) => (memory.heapi32u[ptr/4] = +val),");
2933   app.putLn("      setInt = (ptr, val) => (memory.heapi32s[ptr/4] = val),");
2934   app.putLn("      setUInt = (ptr, val) => (memory.heapi32u[ptr/4] = val),");
2935   app.putLn("      setShort = (ptr, val) => (memory.heapi16s[ptr/2] = val),");
2936   app.putLn("      setUShort = (ptr, val) => (memory.heapi16u[ptr/2] = val),");
2937   app.putLn("      setByte = (ptr, val) => (memory.heapi8s[ptr] = val),");
2938   app.putLn("      setUByte = (ptr, val) => (memory.heapi8u[ptr] = val),");
2939   app.putLn("      setFloat = (ptr, val) => (memory.heapf32[ptr/4] = val),");
2940   app.putLn("      setDouble = (ptr, val) => (memory.heapf64[ptr/8] = val),");
2941   app.putLn("      getBool = (ptr) => memory.heapi32u[ptr/4],");
2942   app.putLn("      getInt = (ptr) => memory.heapi32s[ptr/4],");
2943   app.putLn("      getUInt = (ptr) => memory.heapi32u[ptr/4],");
2944   app.putLn("      getShort = (ptr) => memory.heapi16s[ptr/2],");
2945   app.putLn("      getUShort = (ptr) => memory.heapi16u[ptr/2],");
2946   app.putLn("      getByte = (ptr) => memory.heapi8s[ptr],");
2947   app.putLn("      getUByte = (ptr) => memory.heapi8u[ptr],");
2948   app.putLn("      getFloat = (ptr) => memory.heapf32[ptr/4],");
2949   app.putLn("      getDouble = (ptr) => memory.heapf64[ptr/8],");
2950   app.putLn("      isDefined = (val) => (val != undefined && val != null),");
2951   app.putLn("      encode_handle = (ptr, val) => { setUInt(ptr, addObject(val)); },");
2952   app.putLn("      decode_handle = (ptr) => { return objects[getUInt(ptr)]; },");
2953   app.putLn("      spasm_encode_string = encoder.string,");
2954   app.putLn("      spasm_decode_string = decoder.string,");
2955   app.put("      spasm_indirect_function_get = (ptr)=>spasm.instance.exports.__indirect_function_table.get(ptr)");
2956   app.indent();
2957   foreach(encoder; encodedTypes.filter!(e => e.mangled != "string")) {
2958     app.putLn(",");
2959     encoder.generateJsEncoder(app, semantics, true);
2960   }
2961   foreach(decoder; decodedTypes.filter!(e => e.mangled != "string")) {
2962     app.putLn(",");
2963     decoder.generateJsDecoder(app, semantics, true);
2964   }
2965   app.putLn(";");
2966   app.undent();
2967   app.putLn("export let jsExports = {");
2968   app.indent();
2969   app.putLn("env: {");
2970   app.indent();
2971 
2972   auto pos = app.data.length;
2973   ir.nodes.each!(n => n.toJsExport(semantics, filtered, &app));
2974   ir.generateJsGlobalBindings(filtered, app);
2975 
2976   app.undent();
2977   app.putLn("}");
2978   app.undent();
2979   app.put("}");
2980   return app.data;
2981 }
2982 
2983 class Type {
2984   ParseTree tree;
2985   ParseTree attributes;
2986   Module module_;
2987   this(ParseTree t, ParseTree a, Module m) {
2988     tree = t;
2989     attributes = a;
2990     module_ = m;
2991   }
2992 }
2993 
2994 class Module {
2995   string name;
2996   Type[string] types;
2997   Type[] partials;
2998   Type[] mixins;
2999   Type[] namespaces;
3000   Semantics semantics;
3001   this(string n, Semantics semantics) {
3002     this.name = n;
3003     this.semantics = semantics;
3004   }
3005 }
3006 
3007 class Semantics {
3008   Type[string] types;
3009   Type[] partials;
3010   Type[] mixins;
3011   Type[] namespaces;
3012   Module[string] modules;
3013   Module analyse(string module_, ParseTree tree) {
3014     import std.range : chunks;
3015     assert(tree.name == "WebIDL");
3016     auto m = new Module(module_, this);
3017     foreach(chunk; tree.children[0].children.chunks(2)) {
3018       assert(chunk.length == 2);
3019       analyse(m, chunk[1], chunk[0]);
3020     }
3021     modules[module_] = m;
3022     foreach (n; m.namespaces)
3023       namespaces ~= n;
3024     foreach(p; m.partials)
3025       partials ~= p;
3026     foreach(mix; m.mixins)
3027       mixins ~= mix;
3028     foreach(k,v; m.types)
3029       types[k] = v;
3030     return m;
3031   }
3032   void dumpTypes() {
3033     import std.format;
3034     writefln("%(%s\n%)",types.keys.map!((key){return format("%s.%s", types[key].module_.name, key);}).array.sort);
3035   }
3036   private void analyse(Module module_, ParseTree tree, ParseTree attributes) {
3037     switch (tree.name) {
3038     case "WebIDL.IncludesStatement":
3039       module_.mixins ~= new Type(tree, ParseTree.init, module_);
3040       break;
3041     case "WebIDL.Dictionary":
3042     case "WebIDL.InterfaceRest":
3043     case "WebIDL.Enum":
3044     case "WebIDL.CallbackRest":
3045     case "WebIDL.MixinRest":
3046       string name = tree.children[0].matches[0];
3047     if (auto p = name in types) {
3048       writefln("Warning: duplicated entry for %s", name);
3049       writefln("A in %s: %s",(*p).module_.name,(*p).tree.input[(*p).tree.begin .. (*p).tree.end]);
3050       writefln("B in %s: %s",module_.name,tree.input[tree.begin .. tree.end]);
3051     }
3052     module_.types[name] = new Type(tree, attributes, module_);
3053     break;
3054     case "WebIDL.Partial":
3055       module_.partials ~= new Type(tree, attributes, module_);
3056       break;
3057     case "WebIDL.Typedef":
3058       string name = tree.children[1].matches[0];
3059       module_.types[name] = new Type(tree, attributes, module_);
3060       break;
3061     case "WebIDL.Namespace":
3062       module_.namespaces ~= new Type(tree, attributes, module_);
3063       break;
3064     default:
3065       tree.children.each!(c => analyse(module_, c, attributes));
3066     }
3067   }
3068 }
3069 string generateDType(ParseTree tree, Context context) {
3070   auto app = IndentedStringAppender();
3071   tree.generateDType(app, context);
3072   return app.data;
3073 }
3074 void generateDType(Appender)(ParseTree tree, ref Appender a, Context context) {
3075   switch (tree.name) {
3076   case "WebIDL.InterfaceRest":
3077   case "WebIDL.IncludesStatement":
3078   case "WebIDL.Iterable":
3079   case "WebIDL.MixinRest":
3080   case "WebIDL.Const":
3081   case "WebIDL.InterfaceMember":
3082   case "WebIDL.ReadOnlyMember":
3083   case "WebIDL.MixinMember":
3084   case "WebIDL.AttributeRest":
3085   case "WebIDL.ExtendedAttributeList":
3086   case "WebIDL.SpecialOperation":
3087   case "WebIDL.RegularOperation":
3088   case "WebIDL.ArgumentList":
3089   case "WebIDL.ArgumentName":
3090   case "WebIDL.ArgumentRest":
3091   case "WebIDL.Enum":
3092   case "WebIDL.Dictionary":
3093   case "WebIDL.DictionaryMember":
3094   case "WebIDL.SetlikeRest":
3095   case "WebIDL.MaplikeRest":
3096   case "WebIDL.DictionaryMemberRest":
3097   case "WebIDL.Typedef":
3098   case "WebIDL.Partial":
3099   case "WebIDL.PartialInterfaceRest":
3100   case "WebIDL.CallbackRest":
3101     break;
3102   case "WebIDL.SequenceType":
3103     if (tree.children[$-1].matches[0] == "?")
3104       a.put("Optional!(Sequence!(");
3105     else
3106       a.put("Sequence!(");
3107     tree.children[0].generateDType(a, context);
3108     a.put(")");
3109     if (tree.children[$-1].matches[0] == "?")
3110       a.put(")");
3111     break;
3112   case "WebIDL.TypeWithExtendedAttributes":
3113     context.extendedAttributeList = tree.children[0];
3114     tree.children[1].generateDType(a, context);
3115     break;
3116   case "WebIDL.StringType":
3117     a.put("string");
3118     break;
3119   case "WebIDL.SingleType":
3120     if (tree.matches[0] == "any")
3121       a.put("Any");
3122     else
3123       tree.children[0].generateDType(a, context);
3124     break;
3125   case "WebIDL.NonAnyType":
3126     bool optional = !context.skipOptional && (context.optional || tree.children[$-1].name == "WebIDL.Null" && tree.children[$-1].matches[0] == "?");
3127     if (optional) {
3128       a.put("Optional!(");
3129     }
3130     switch (tree.matches[0]) {
3131     case "object":
3132       a.put("JsObject");
3133       break;
3134     case "symbol":
3135       a.put("Symbol");
3136       break;
3137     case "Error":
3138       a.put("Error");
3139       break;
3140     case "FrozenArray":
3141       a.put("FrozenArray!(");
3142       tree.children[$-2].generateDType(a, context);
3143       a.put(")");
3144       break;
3145     default:
3146       tree.children.each!(c => c.generateDType(a, context));
3147     }
3148     if (optional) {
3149       a.put(")");
3150     }
3151     break;
3152   case "WebIDL.PrimitiveType":
3153     if (tree.children.length == 0) {
3154       switch (tree.matches[0]) {
3155       case "byte": a.put("byte"); break;
3156       case "octet": a.put("ubyte"); break;
3157       case "boolean": a.put("bool"); break;
3158       default: a.put(tree.matches[0]); break;
3159       }
3160     } else
3161       tree.children[0].generateDType(a, context);
3162     break;
3163   case "WebIDL.UnrestrictedFloatType":
3164     // TODO: handle unrestricted
3165     tree.children[0].generateDType(a, context);
3166     break;
3167   case "WebIDL.UnsignedIntegerType":
3168     if (tree.matches[0] == "unsigned")
3169       a.put("u");
3170     tree.children[0].generateDType(a, context);
3171     break;
3172   case "WebIDL.IntegerType":
3173     if (tree.matches[0] == "long") {
3174       if (tree.matches.length > 1)
3175         a.put(is32Bit ? "int" : "long");
3176       else
3177         a.put("int");
3178     } else
3179       a.put(tree.matches[0]);
3180     break;
3181   case "WebIDL.FloatType":
3182   case "WebIDL.Identifier":
3183     string typeName = tree.matches[0];
3184     if (context.isTypedef(typeName)) {
3185       auto aliasedType = context.getAliasedType(typeName);
3186       IndentedStringAppender app;
3187       aliasedType.generateDType(app, context);
3188       if (app.data.length < tree.matches[0].length)
3189         return a.put(app.data);
3190     }
3191     if (context.locals.canFind(tree.matches[0]))
3192       a.put(".");
3193     a.put(tree.matches[0]);
3194     break;
3195   case "WebIDL.ReturnType":
3196     if (tree.children.length > 0)
3197       tree.children[0].generateDType(a, context);
3198     else
3199       a.put(tree.matches[0]);
3200     break;
3201   case "WebIDL.Default":
3202     if (tree.children.length == 0)
3203       return;
3204     a.put("/* = ");
3205     tree.children[0].generateDType(a, context);
3206     a.put(" */");
3207     break;
3208   case "WebIDL.DefaultValue":
3209     if (tree.children.length == 0) {
3210       a.put("[]");
3211       return;
3212     }
3213     tree.children[0].generateDType(a, context);
3214     break;
3215   case "WebIDL.ConstValue":
3216     if (tree.children.length == 0) {
3217       a.put(tree.matches);
3218       break;
3219     }
3220     tree.children[0].generateDType(a, context);
3221     break;
3222   case "WebIDL.BooleanLiteral":
3223   case "WebIDL.Integer":
3224     a.put(tree.matches);
3225     break;
3226   case "WebIDL.Float":
3227     a.put(tree.matches[0]);
3228     break;
3229   case "WebIDL.FloatLiteral":
3230     if (tree.children.length == 0) {
3231       switch (tree.matches[0]) {
3232       case "-Infinity": a.put("-float.infinity"); break;
3233       case "Infinity": a.put("float.infinity"); break;
3234       case "NaN": a.put("float.nan"); break;
3235       default: assert(false);
3236       }
3237       break;
3238     }
3239     tree.children[0].generateDType(a, context);
3240     break;
3241   case "WebIDL.String":
3242     a.put(tree.matches);
3243     break;
3244   case "WebIDL.RecordType":
3245     a.put("Record!(");
3246     tree.children.putWithDelimiter!(generateDType)(", ", a, context);
3247     a.put(")");
3248     break;
3249   case "WebIDL.PromiseType":
3250     a.put("Promise!(");
3251     tree.children[0].generateDType(a, context);
3252     a.put(")");
3253     break;
3254   case "WebIDL.Type":
3255     context.optional = false;
3256     if (tree.children[0].name == "WebIDL.UnionType") {
3257       bool optional = !context.skipOptional && tree.children[1].matches[0] == "?";
3258       context.optional = optional;
3259       if (optional) {
3260         a.put("Optional!(");
3261       }
3262       a.put("SumType!(");
3263       tree.children[0].children.putWithDelimiter!(generateDType)(", ", a, context);
3264       if (optional)
3265         a.put(")");
3266       a.put(")");
3267       break;
3268     } else
3269       tree.children[0].generateDType(a, context);
3270     break;
3271   case "WebIDL.UnionMemberType":
3272     context.optional = false;
3273     if (tree.children[0].name == "WebIDL.UnionType") {
3274       bool optional = !context.skipOptional && tree.matches[$-1] == "?";
3275       context.optional = optional;
3276       if (optional) {
3277         a.put("Optional!(");
3278       }
3279       a.put("SumType!(");
3280       tree.children[0].children.putWithDelimiter!(generateDType)(", ", a, context);
3281       if (optional)
3282         a.put(")");
3283       a.put(")");
3284       break;
3285     } else
3286       tree.children.each!(c => c.generateDType(a, context));
3287     break;
3288   default:
3289     tree.children.each!(c => generateDType(c, a, context));
3290   }
3291 }