1 // Copyright © 2017 Rémi Thebault
2 /++
3  +  Wayland scanner for D.
4  +  Generation of client protocol code.
5  +/
6 module wayland.scanner.client;
7 
8 import wayland.scanner;
9 import wayland.scanner.common;
10 
11 import arsd.dom;
12 
13 import std.algorithm;
14 import std.exception;
15 import std.format;
16 import std.range;
17 
18 
19 alias Interface = wayland.scanner.common.Interface;
20 
21 class ClientFactory : Factory
22 {
23     override Protocol makeProtocol(Element el)
24     {
25         return new ClientProtocol(el);
26     }
27     override Interface makeInterface(Element el, Protocol protocol)
28     {
29         return new ClientInterface(el, protocol);
30     }
31     override Message makeMessage(Element el, Interface iface)
32     {
33         return new ClientMessage(el, iface);
34     }
35     override Arg makeArg(Element el)
36     {
37         return new ClientArg(el);
38     }
39 }
40 
41 class ClientArg : Arg
42 {
43     this(Element el)
44     {
45         super(el);
46     }
47 
48     override @property string dType() const
49     {
50         final switch(type) {
51             case ArgType.Int:
52             case ArgType.UInt:
53             case ArgType.Fixed:
54             case ArgType.String:
55             case ArgType.NewId:
56             case ArgType.Array:
57             case ArgType.Fd:
58                 return Arg.dType;
59             case ArgType.Object:
60                 if (iface.length)
61                     return ifaceDName(iface);
62                 else
63                     return "WlProxy";
64         }
65     }
66 
67     override @property string cCastExpr() const
68     {
69         final switch(type) {
70             case ArgType.Int:
71             case ArgType.UInt:
72             case ArgType.Fixed:
73             case ArgType.String:
74             case ArgType.NewId:
75             case ArgType.Array:
76             case ArgType.Fd:
77                 return Arg.cCastExpr;
78             case ArgType.Object:
79                 return format("%s.proxy", paramName);
80         }
81     }
82 
83     @property string reqCType() const
84     {
85         final switch(type) {
86             case ArgType.Int:
87             case ArgType.UInt:
88             case ArgType.Fixed:
89             case ArgType.String:
90             case ArgType.NewId:
91             case ArgType.Array:
92             case ArgType.Fd:
93                 return Arg.cType;
94             case ArgType.Object:
95                 return "wl_proxy*";
96         }
97     }
98 
99     @property string evCType() const
100     {
101         final switch(type) {
102             case ArgType.Int:
103             case ArgType.UInt:
104             case ArgType.Fixed:
105             case ArgType.String:
106             case ArgType.NewId:
107             case ArgType.Array:
108             case ArgType.Fd:
109                 return Arg.cType;
110             case ArgType.Object:
111                 if (iface.empty) return "void*";
112                 else return "wl_proxy*";
113         }
114     }
115 
116     override string dCastExpr(string parentIface) const
117     {
118         final switch(type) {
119             case ArgType.Int:
120             case ArgType.UInt:
121             case ArgType.Fixed:
122             case ArgType.String:
123             case ArgType.NewId:
124             case ArgType.Array:
125             case ArgType.Fd:
126                 return Arg.dCastExpr(parentIface);
127             case ArgType.Object:
128                 auto expr = format("WlProxy.get(%s)", paramName);
129                 if (iface)
130                     return format("cast(%s)%s", ifaceDName(iface), expr);
131                 else
132                     return expr;
133         }
134     }
135 }
136 
137 
138 class ClientMessage : Message
139 {
140     this (Element el, Interface iface)
141     {
142         super(el, iface);
143     }
144 
145     @property auto clArgs()
146     {
147         return args.map!(a => cast(ClientArg)a);
148     }
149 
150     @property string reqRetStr()
151     {
152         final switch (reqType)
153         {
154         case ReqType.newObj:
155             return ifaceDName(reqRet.iface);
156         case ReqType.dynObj:
157             return "WlProxy";
158         case ReqType.void_:
159             return "void";
160         }
161     }
162 
163     void writeRequestCode(SourceFile sf)
164     {
165         final switch(reqType)
166         {
167         case ReqType.void_:
168             writeVoidReqDefinitionCode(sf);
169             break;
170         case ReqType.newObj:
171             writeNewObjReqDefinitionCode(sf);
172             break;
173         case ReqType.dynObj:
174             writeDynObjReqDefinitionCode(sf);
175             break;
176         }
177     }
178 
179     void writeEventDgAlias(SourceFile sf)
180     {
181         sf.writeln("/// Event delegate signature of %s.%s.", ifaceDName(ifaceName), dHandlerName);
182         string[] rtArgs = [
183             format("%s %s", ifaceDName(ifaceName), camelName(ifaceName))
184         ];
185         foreach(a; args)
186         {
187             rtArgs ~= format("%s %s", a.dType, a.paramName);
188         }
189         writeDelegateAlias(sf, dEvDgType, "void", rtArgs);
190     }
191 
192     void writeEventDgAccessor(SourceFile sf)
193     {
194         description.writeCode(sf);
195         sf.writeln("@property void %s(%s dg)", dHandlerName, dEvDgType);
196         sf.bracedBlock!({
197             sf.writeln("_%s = dg;", dHandlerName);
198         });
199     }
200 
201     void writeVoidReqDefinitionCode(SourceFile sf)
202     {
203         string[] rtArgs;
204         string[] exprs = [
205             "proxy", opCodeName
206         ];
207         foreach(arg; args)
208         {
209             rtArgs ~= (arg.dType ~ " " ~ arg.paramName);
210             exprs ~= arg.cCastExpr;
211         }
212         string[] postStmt = isDtor ? ["super.destroyNotify();"] : [];
213 
214         description.writeCode(sf);
215         sf.writeFnSig("void", dMethodName, rtArgs);
216         sf.writeFnBody([], "wl_proxy_marshal", exprs, postStmt);
217     }
218 
219     void writeNewObjReqDefinitionCode(SourceFile sf)
220     {
221         string[] rtArgs;
222         string[] exprs = [
223             "proxy", opCodeName, format("%s.iface.native", ifaceDName(reqRet.iface))
224         ];
225         foreach(arg; args)
226         {
227             if (arg is reqRet)
228             {
229                 exprs ~= "null";
230             }
231             else
232             {
233                 rtArgs ~= format("%s %s", arg.dType, arg.paramName);
234                 exprs ~= arg.cCastExpr;
235             }
236         }
237         description.writeCode(sf);
238         sf.writeFnSig(reqRetStr, dMethodName, rtArgs);
239         sf.writeFnBody([],
240             "auto _pp = wl_proxy_marshal_constructor", exprs,
241             [   "if (!_pp) return null;",
242                 "auto _p = WlProxy.get(_pp);",
243                 format("if (_p) return cast(%s)_p;", reqRetStr),
244                 format("return new %s(_pp);", reqRetStr)    ]
245         );
246     }
247 
248     void writeDynObjReqDefinitionCode(SourceFile sf)
249     {
250         string[] rtArgs;
251         string[] exprs = [
252             "proxy", opCodeName, "iface.native", "ver"
253         ];
254         foreach(arg; args)
255         {
256             if (arg is reqRet)
257             {
258                 rtArgs ~= [ "immutable(WlProxyInterface) iface", "uint ver" ];
259                 exprs ~= [ "iface.native.name", "ver" ];
260             }
261             else
262             {
263                 rtArgs ~= format("%s %s", arg.dType, arg.paramName);
264                 exprs ~= arg.cCastExpr;
265             }
266         }
267         description.writeCode(sf);
268         sf.writeFnSig(reqRetStr, dMethodName, rtArgs);
269         sf.writeFnBody([],
270             "auto _pp = wl_proxy_marshal_constructor_versioned", exprs,
271             [   "if (!_pp) return null;",
272                 "auto _p = WlProxy.get(_pp);",
273                 "if (_p) return _p;",
274                 "return iface.makeProxy(_pp);"  ]
275         );
276     }
277 
278     void writePrivListenerSig(SourceFile sf)
279     {
280         enum fstLine = "void function(";
281         immutable lstEol = format(") %s;", cEvName);
282 
283         immutable indent = ' '.repeat.take(fstLine.length).array();
284         sf.writeln("%svoid* data,", fstLine);
285         auto eol = args.empty ? lstEol : ",";
286         sf.writeln("%swl_proxy* proxy%s", indent, eol);
287         foreach(i, arg; enumerate(clArgs))
288         {
289             eol = i == args.length-1 ? lstEol : ",";
290             sf.writeln("%s%s %s%s", indent, arg.evCType, arg.paramName, eol);
291         }
292     }
293 
294     void writePrivListenerStub(SourceFile sf)
295     {
296         immutable fstLine = format("void wl_d_on_%s_%s(", ifaceName, name);
297         immutable indent = ' '.repeat.take(fstLine.length).array();
298         sf.writeln("%svoid* data,", fstLine);
299         auto eol = args.empty ? ")" : ",";
300         sf.writeln("%swl_proxy* proxy%s", indent, eol);
301         foreach(i, arg; enumerate(clArgs))
302         {
303             eol = i == args.length-1 ? ")" : ",";
304             sf.writeln("%s%s %s%s", indent, arg.evCType, arg.paramName, eol);
305         }
306         sf.bracedBlock!({
307             sf.writeln("nothrowFnWrapper!({");
308             sf.indentedBlock!({
309                 sf.writeln("auto _p = WlProxy.get(proxy);");
310                 sf.writeln("assert(_p, \"listener stub without proxy\");");
311                 sf.writeln("auto _i = cast(%s)_p;", ifaceDName(ifaceName));
312                 sf.writeln("assert(_i, \"listener stub proxy is not %s\");", ifaceDName(ifaceName));
313                 sf.writeln("if (_i._%s)", dHandlerName);
314                 sf.bracedBlock!({
315                     string sep = args.length ? ", " : "";
316                     sf.write("_i._%s(_i%s", dHandlerName, sep);
317                     foreach (i, arg; args)
318                     {
319                         sep = (i == args.length-1) ? "" : ", ";
320                         sf.write("%s%s", arg.dCastExpr(ifaceName), sep);
321                     }
322                     sf.writeln(");");
323                 });
324 
325             });
326             sf.writeln("});");
327         });
328     }
329 }
330 
331 
332 class ClientInterface : Interface
333 {
334     this (Element el, Protocol protocol)
335     {
336         super(el, protocol);
337     }
338 
339     @property auto clRequests()
340     {
341         return requests.map!(r => cast(ClientMessage)r);
342     }
343 
344     @property auto clEvents()
345     {
346         return events.map!(r => cast(ClientMessage)r);
347     }
348 
349     @property string globalNativeListenerName()
350     {
351         return format("wl_d_%s_listener", name);
352     }
353 
354     override void writeCode(SourceFile sf)
355     {
356         description.writeCode(sf);
357 
358         sf.writeln("final class %s : %s", dName,
359             name == "wl_display" ?
360                 "WlDisplayBase" :
361                 "WlProxy");
362         sf.bracedBlock!(
363         {
364             writeVersion(sf);
365             sf.writeln();
366             sf.writeln("/// Build a %s from a native object.", dName);
367             sf.writeln(name == "wl_display" ?
368                 "package(wayland) this(wl_display* native)" :
369                 "private this(wl_proxy* native)"
370             );
371             sf.bracedBlock!({
372                 sf.writeln("super(native);");
373                 if (writeEvents)
374                 {
375                     sf.writeln(
376                         "wl_proxy_add_listener(proxy, cast(void_func_t*)&%s, null);",
377                         globalNativeListenerName
378                     );
379                 }
380             });
381             sf.writeln();
382             sf.writeln("/// Interface object that creates %s objects.", dName);
383             sf.writeln("static @property immutable(WlProxyInterface) iface()");
384             sf.bracedBlock!({
385                 sf.writeln("return %sIface;", camelName(name));
386             });
387             writeConstants(sf);
388             if (writeEvents)
389             {
390                 sf.writeln();
391                 foreach(msg; clEvents)
392                 {
393                     msg.writeEventDgAlias(sf);
394                 }
395             }
396             foreach (en; enums)
397             {
398                 sf.writeln();
399                 en.writeCode(sf);
400             }
401             writeDtorCode(sf);
402             foreach (msg; clRequests)
403             {
404                 sf.writeln();
405                 msg.writeRequestCode(sf);
406             }
407             if (writeEvents)
408             {
409                 foreach(msg; clEvents)
410                 {
411                     sf.writeln();
412                     msg.writeEventDgAccessor(sf);
413                 }
414 
415                 sf.writeln();
416                 foreach(msg; events)
417                 {
418                     sf.writeln("private %s _%s;", msg.dEvDgType, msg.dHandlerName);
419                 }
420             }
421         });
422     }
423 
424     void writeConstants(SourceFile sf)
425     {
426         if (requests.length)
427         {
428             sf.writeln();
429             foreach(i, msg; requests)
430             {
431                 sf.writeln("/// Op-code of %s.%s.", dName, msg.dMethodName);
432                 sf.writeln("enum %s = %d;", msg.opCodeName, i);
433             }
434             sf.writeln();
435             foreach(msg; requests)
436             {
437                 sf.writeln(
438                     "/// Version of %s protocol introducing %s.%s.",
439                     protocol.name, dName, msg.dMethodName
440                 );
441                 sf.writeln("enum %sSinceVersion = %d;", camelName(msg.name), msg.since);
442             }
443         }
444         if (events.length)
445         {
446             sf.writeln();
447             foreach(msg; events)
448             {
449                 sf.writeln(
450                     "/// %s protocol version introducing %s.%s.",
451                     protocol.name, dName, msg.dHandlerName
452                 );
453                 sf.writeln("enum %sSinceVersion = %d;", msg.dHandlerName, msg.since);
454             }
455         }
456     }
457 
458     void writeDtorCode(SourceFile sf)
459     {
460         immutable hasDtor = requests.canFind!(rq => rq.isDtor);
461         immutable hasDestroy = requests.canFind!(rq => rq.name == "destroy");
462 
463         enforce(!hasDestroy || hasDtor);
464 
465         if (!hasDestroy && name != "wl_display")
466         {
467             sf.writeln();
468             sf.writeln("/// Destroy this %s object.", dName);
469             sf.writeln("void destroy()");
470             sf.bracedBlock!({
471                 sf.writeln("wl_proxy_destroy(proxy);");
472                 sf.writeln("super.destroyNotify();");
473             });
474         }
475     }
476 
477     void writePrivListener(SourceFile sf)
478     {
479         if (!writeEvents) return;
480 
481         sf.writeln("struct %s_listener", name);
482         sf.bracedBlock!({
483             foreach(ev; clEvents)
484             {
485                 ev.writePrivListenerSig(sf);
486             }
487         });
488 
489         sf.writeln();
490         immutable fstLine = format("__gshared %s = %s_listener (", globalNativeListenerName, name);
491         immutable indent = ' '.repeat(fstLine.length).array();
492         foreach (i, ev; events)
493         {
494             sf.writeln("%s&wl_d_on_%s_%s%s", (i == 0 ? fstLine : indent),
495                                         name, ev.name,
496                                         (i == events.length-1) ? ");" : ",");
497         }
498     }
499 
500     void writePrivListenerStubs(SourceFile sf)
501     {
502         if (!writeEvents) return;
503 
504         foreach(ev; clEvents)
505         {
506             sf.writeln();
507             ev.writePrivListenerStub(sf);
508         }
509     }
510 }
511 
512 
513 
514 class ClientProtocol : Protocol
515 {
516     this(Element el)
517     {
518         super(el);
519     }
520 
521     @property auto clIfaces()
522     {
523         return ifaces.map!(iface => cast(ClientInterface)iface);
524     }
525 
526     override void writeCode(SourceFile sf, in Options opt)
527     {
528         writeHeader(sf, opt);
529         if (name == "wayland") sf.writeln("import wayland.client.core;");
530         else sf.writeln("import wayland.client;");
531         sf.writeln("import wayland.native.client;");
532         sf.writeln("import wayland.native.util;");
533         sf.writeln("import wayland.util;");
534         sf.writeln();
535         sf.writeln("import std.exception : enforce;");
536         sf.writeln("import std.string : fromStringz, toStringz;");
537         sf.writeln();
538 
539         foreach(iface; ifaces)
540         {
541             iface.writeCode(sf);
542             sf.writeln();
543         }
544 
545         // writing private code
546         sf.writeln("private:");
547         sf.writeln();
548 
549         writePrivIfaces(sf);
550         sf.writeln();
551 
552         sf.writeln("extern(C) nothrow");
553         sf.bracedBlock!({
554             foreach(i, iface; enumerate(clIfaces))
555             {
556                 if (i != 0) sf.writeln();
557                 iface.writePrivListener(sf);
558                 iface.writePrivListenerStubs(sf);
559             }
560         });
561     }
562 
563     override void writePrivIfaces(SourceFile sf)
564     {
565         foreach(iface; ifaces)
566         {
567             sf.writeln("immutable WlProxyInterface %sIface;", camelName(iface.name));
568         }
569         foreach (iface; ifaces)
570         {
571             sf.writeln();
572             sf.writeln("immutable final class %sIface : WlProxyInterface",
573                     titleCamelName(iface.name));
574             sf.bracedBlock!({
575                 sf.writeln("this(immutable wl_interface* native)");
576                 sf.bracedBlock!({
577                     sf.writeln("super(native);");
578                 });
579                 sf.writeln("override WlProxy makeProxy(wl_proxy* proxy) immutable");
580                 sf.bracedBlock!({
581                     if (iface.name == "wl_display")
582                     {
583                         sf.writeln("return new WlDisplay(cast(wl_display*)proxy);");
584                     }
585                     else
586                         sf.writeln("return new %s(proxy);", iface.dName);
587                 });
588             });
589         }
590         writeNativeIfaces(sf);
591     }
592 
593     override void writeNativeIfacesAssignment(SourceFile sf)
594     {
595         foreach (iface; ifaces)
596         {
597             sf.writeln("%sIface = new immutable %sIface( &wl_ifaces[%s] );",
598                     camelName(iface.name),
599                     titleCamelName(iface.name),
600                     indexSymbol(iface.name));
601         }
602     }
603 
604 }