1 // Copyright © 2017-2021 Rémi Thebault
2 /++
3 + Wayland scanner for D.
4 + Generation of common protocol code.
5 +/
6 module wayland.scanner.common;
7
8 import wayland.scanner;
9
10 import arsd.dom;
11
12 import std.algorithm;
13 import std.array;
14 import std.conv;
15 import std.exception;
16 import std.format;
17 import std.range;
18 import std.stdio;
19 import std.typecons;
20 import std.uni;
21
22 interface Factory
23 {
24 Protocol makeProtocol(Element el);
25 Interface makeInterface(Element el, Protocol protocol);
26 Message makeMessage(Element el, Interface iface);
27 Arg makeArg(Element el);
28 }
29
30 Factory fact;
31
32
33 void writeMultilineParenthesis(SourceFile sf, in string before,
34 in string[] args, in string after)
35 {
36 immutable indent = ' '.repeat(before.length+1).array();
37 sf.write("%s(", before);
38 foreach (i, a; args)
39 {
40 if (i != 0)
41 {
42 sf.writeln(",");
43 sf.write(indent);
44 }
45 sf.write(a);
46 }
47 sf.writeln(")%s", after);
48 }
49
50 void writeDelegateAlias(SourceFile sf, string name, string ret, string[] rtArgs)
51 {
52 immutable fstLine = format("alias %s = %s delegate(", name, ret);
53 immutable indent = ' '.repeat(fstLine.length).array();
54 sf.write(fstLine);
55 foreach(i, rta; rtArgs)
56 {
57 if (i != 0)
58 {
59 sf.writeln(",");
60 sf.write(indent);
61 }
62 sf.write(rta);
63 }
64 sf.writeln(");");
65 }
66
67 void writeFnSigRaw(SourceFile sf, string qualifiers, string ret, string name,
68 string[] rtArgs, string[] ctArgs=[], string constraint="")
69 {
70 immutable ctSig = ctArgs.length ? format("!(%(%s, %))", ctArgs) : "";
71 immutable qual = qualifiers.length ? format("%s ", qualifiers) : "";
72 immutable fstLine = format("%s%s %s%s(", qual, ret, name, ctSig);
73 immutable indent = ' '.repeat(fstLine.length).array();
74 sf.write(fstLine);
75 foreach (i, rta; rtArgs)
76 {
77 if (i != 0)
78 {
79 sf.writeln(",");
80 sf.write(indent);
81 }
82 sf.write(rta);
83 }
84 sf.write(")");
85 if (constraint.length)
86 {
87 sf.write("if (%s)", constraint);
88 }
89 }
90
91 void writeFnSig(SourceFile sf, string ret, string name, string[] rtArgs,
92 string[] ctArgs=[], string constraint="")
93 {
94 writeFnSigRaw(sf, "", ret, name, rtArgs, ctArgs, constraint);
95 sf.writeln();
96 }
97
98 void writeFnExpr(SourceFile sf, string stmt, string[] exprs)
99 {
100 sf.writeln("%s(", stmt);
101 sf.indentedBlock!({
102 immutable ic = sf.indentChars;
103 int width = ic;
104 foreach (i, e; exprs)
105 {
106 if (i != 0)
107 {
108 sf.write(",");
109 width += 1;
110 }
111 width += e.length;
112 if (width > wrapWidth)
113 {
114 sf.writeln();
115 width = ic;
116 }
117 else if (i != 0)
118 {
119 sf.write(" ");
120 width += 1;
121 }
122 sf.write(e);
123 }
124 });
125 sf.writeln();
126 sf.writeln(");");
127 }
128
129 void writeFnPointer(SourceFile sf, string name, string ret, string[] args)
130 {
131 writeMultilineParenthesis(
132 sf, format("%s function", ret), args, format(" %s;", name)
133 );
134 }
135
136 void writeFnBody(SourceFile sf, string[] preStmts, string mainStmt,
137 string[] exprs, string[] postStmt)
138 {
139 sf.bracedBlock!({
140 foreach (ps; preStmts)
141 {
142 sf.writeln(ps);
143 }
144 writeFnExpr(sf, mainStmt, exprs);
145 foreach (ps; postStmt)
146 {
147 sf.writeln(ps);
148 }
149 });
150 }
151
152 enum ArgType
153 {
154 Int, UInt, Fixed, String, Object, NewId, Array, Fd
155 }
156
157 @property bool isNullable(in ArgType at) pure
158 {
159 switch (at)
160 {
161 case ArgType.String:
162 case ArgType.Object:
163 case ArgType.NewId:
164 case ArgType.Array:
165 return true;
166 default:
167 return false;
168 }
169 }
170
171
172 class Description
173 {
174 string summary;
175 string text;
176
177 this (Element parentEl)
178 {
179 foreach (el; parentEl.getElementsByTagName("description"))
180 {
181 summary = el.getAttribute("summary");
182 text = el.getElText();
183 break;
184 }
185 }
186
187 @property bool empty() const
188 {
189 return summary.empty && text.empty;
190 }
191
192 void writeCode(SourceFile sf)
193 {
194 if (empty) return;
195 if (text.empty)
196 {
197 enforce(!summary.canFind('\n'), "Summary should be a 1 liner.");
198 sf.writeln("/// %s", summary);
199 }
200 else if (summary.empty)
201 {
202 sf.writeDoc("%s", text);
203 }
204 else
205 {
206 sf.writeDoc("%s\n\n%s", summary, text);
207 }
208 }
209 }
210
211
212 class EnumEntry
213 {
214 string ifaceName;
215 string enumName;
216 string name;
217 string value;
218 string summary;
219
220 this (Element el, string ifaceName, string enumName)
221 {
222 assert(el.tagName == "entry");
223 this.ifaceName = ifaceName;
224 this.enumName = enumName;
225 name = el.getAttribute("name");
226 value = el.getAttribute("value");
227 summary = el.getAttribute("summary");
228
229 enforce(!value.empty, "enum entries without value aren't supported");
230 }
231
232 void writeCode(SourceFile sf)
233 {
234 if (summary.length)
235 {
236 sf.writeln("/// %s", summary);
237 }
238 sf.writeln(
239 "%s = %s,", validDName(camelName(name)), value
240 );
241 }
242 }
243
244
245 class Enum
246 {
247 string name;
248 string ifaceName;
249 Description description;
250 EnumEntry[] entries;
251
252 this (Element el, string ifaceName)
253 {
254 assert(el.tagName == "enum");
255 name = el.getAttribute("name");
256 this.ifaceName = ifaceName;
257 description = new Description(el);
258 foreach (entryEl; el.getElementsByTagName("entry"))
259 {
260 entries ~= new EnumEntry(entryEl, ifaceName, name);
261 }
262 }
263
264 @property dName() const
265 {
266 return titleCamelName(name);
267 }
268
269 bool entriesHaveDoc() const
270 {
271 return entries.any!(e => !e.summary.empty);
272 }
273
274 void writeCode(SourceFile sf)
275 {
276 description.writeCode(sf);
277 sf.writeln("enum %s : uint", dName);
278 sf.bracedBlock!({
279 foreach(entry; entries)
280 {
281 entry.writeCode(sf);
282 }
283 });
284 }
285 }
286
287
288
289 abstract class Arg
290 {
291 string name;
292 string summary;
293 string iface;
294 ArgType type;
295 string enumName;
296 bool nullable;
297
298 this (Element el)
299 {
300 assert(el.tagName == "arg");
301 name = el.getAttribute("name");
302 summary = el.getAttribute("summary");
303 iface = el.getAttribute("interface");
304 enumName = el.getAttribute("enum");
305 switch (el.getAttribute("type"))
306 {
307 case "int":
308 type = ArgType.Int;
309 break;
310 case "uint":
311 type = ArgType.UInt;
312 break;
313 case "fixed":
314 type = ArgType.Fixed;
315 break;
316 case "string":
317 type = ArgType.String;
318 break;
319 case "object":
320 type = ArgType.Object;
321 break;
322 case "new_id":
323 type = ArgType.NewId;
324 break;
325 case "array":
326 type = ArgType.Array;
327 break;
328 case "fd":
329 type = ArgType.Fd;
330 break;
331 default:
332 throw new Exception("unknown type: "~el.getAttribute("type"));
333 }
334 immutable allowNull = el.getAttribute("allow-null");
335 enforce (!allowNull.length || (allowNull == "true" || allowNull == "false"));
336 nullable = allowNull == "true";
337 enforce(!nullable || isNullable(type));
338 }
339
340 abstract @property string dType() const
341 {
342 final switch(type) {
343 case ArgType.Int:
344 return "int";
345 case ArgType.UInt:
346 if (enumName.empty) return "uint";
347 else return qualfiedTypeName(enumName);
348 case ArgType.Fixed:
349 return "WlFixed";
350 case ArgType.String:
351 return "string";
352 case ArgType.NewId:
353 assert(false, "must be implemented in subclasses");
354 case ArgType.Array:
355 return "wl_array*"; // ?? let's check this later
356 case ArgType.Fd:
357 return "int";
358 case ArgType.Object:
359 assert(false, "must be implemented in subclasses");
360 }
361 }
362
363 abstract @property string cType() const
364 {
365 final switch(type) {
366 case ArgType.Int:
367 return "int";
368 case ArgType.UInt:
369 return "uint";
370 case ArgType.Fixed:
371 return "wl_fixed_t";
372 case ArgType.String:
373 return "const(char)*";
374 case ArgType.Object:
375 assert(false, "unimplemented");
376 case ArgType.NewId:
377 assert(false, "must be implemented in subclasses");
378 case ArgType.Array:
379 return "wl_array*";
380 case ArgType.Fd:
381 return "int";
382 }
383 }
384
385
386 @property string paramName() const
387 {
388 return validDName(camelName(name));
389 }
390
391 @property string cCastExpr() const
392 {
393 final switch(type) {
394 case ArgType.Int:
395 return paramName;
396 case ArgType.UInt:
397 return paramName;
398 case ArgType.Fixed:
399 return format("%s.raw", paramName);
400 case ArgType.String:
401 return format("toStringz(%s)", paramName);
402 case ArgType.NewId:
403 return paramName;
404 case ArgType.Array:
405 return paramName;
406 case ArgType.Fd:
407 return paramName;
408 case ArgType.Object:
409 assert(false, "must be implemented in subclasses");
410 }
411 }
412
413 string dCastExpr(string parentIface) const
414 {
415 final switch(type) {
416 case ArgType.Int:
417 return paramName;
418 case ArgType.UInt:
419 if (enumName.empty) return paramName;
420 else
421 {
422 immutable en = (parentIface.empty || enumName.canFind('.')) ?
423 enumName : format("%s.%s", parentIface, enumName);
424 return format("cast(%s)%s", qualfiedTypeName(en), paramName);
425 }
426 case ArgType.Fixed:
427 return format("WlFixed(%s)", paramName);
428 case ArgType.String:
429 return format("fromStringz(%s).idup", paramName);
430 case ArgType.Object:
431 assert(false, "unimplemented");
432 case ArgType.NewId:
433 return paramName;
434 case ArgType.Array:
435 return paramName;
436 case ArgType.Fd:
437 return paramName;
438 }
439 }
440 }
441
442 abstract class Message
443 {
444 enum Type
445 {
446 event,
447 request,
448 }
449
450 enum ReqType
451 {
452 void_,
453 newObj,
454 dynObj,
455 }
456
457 string name;
458 Interface iface;
459 int since = 1;
460 bool isDtor;
461 Description description;
462 Arg[] args;
463
464 string[] argIfaceTypes;
465 size_t ifaceTypeIndex;
466 Type type;
467
468 ReqType reqType;
469 Arg reqRet;
470
471 this (Element el, Interface iface)
472 {
473 assert(el.tagName == "request" || el.tagName == "event");
474 this.iface = iface;
475 name = el.getAttribute("name");
476 if (el.hasAttribute("since"))
477 {
478 since = el.getAttribute("since").to!int;
479 }
480 isDtor = (el.getAttribute("type") == "destructor");
481 description = new Description(el);
482 foreach (argEl; el.getElementsByTagName("arg"))
483 {
484 args ~= fact.makeArg(argEl);
485 }
486
487 argIfaceTypes = args
488 .filter!(a => a.type == ArgType.NewId || a.type == ArgType.Object)
489 .map!(a => a.iface)
490 .filter!(iface => iface.length != 0)
491 .array();
492
493 type = el.tagName == "event" ? Type.event : Type.request;
494
495 if (type == Type.request)
496 {
497 auto crr = reqReturn;
498 reqRet = crr[0];
499 reqType = crr[1];
500 }
501 }
502
503 @property string ifaceName()
504 {
505 return iface.name;
506 }
507
508 @property Tuple!(Arg, ReqType) reqReturn()
509 {
510 Arg ret;
511 ReqType rt;
512 foreach (arg; args)
513 {
514 if (arg.type == ArgType.NewId)
515 {
516 enforce(!ret, "more than 1 new-id for a request!");
517 ret = arg;
518 rt = arg.iface.length ? ReqType.newObj : ReqType.dynObj;
519 }
520 }
521 if (!ret)
522 {
523 rt = ReqType.void_;
524 }
525 return tuple(ret, rt);
526 }
527
528 @property bool ifaceTypesAllNull() const
529 {
530 return argIfaceTypes.empty;
531 }
532
533 @property size_t nullIfaceTypeLength() const
534 {
535 return argIfaceTypes.empty ? args.length : 0;
536 }
537
538 @property string dMethodName()
539 {
540 return validDName(camelName(name));
541 }
542
543 @property string opCodeName()
544 {
545 return format("%sOpCode", camelName(name));
546 }
547
548 @property string onOpCodeName()
549 {
550 return format("on%sOpCode", titleCamelName(name));
551 }
552
553 @property string dHandlerName()
554 {
555 return "on" ~ titleCamelName(name);
556 }
557
558 @property string dEvDgType()
559 {
560 return "On" ~ titleCamelName(name) ~ "EventDg";
561 }
562
563 @property string cEvName()
564 {
565 return validDName(name);
566 }
567
568 @property string signature() const
569 {
570 string sig;
571 if (since > 1)
572 {
573 sig ~= since.to!string;
574 }
575 foreach(arg; args)
576 {
577 if (arg.nullable) sig ~= '?';
578 final switch(arg.type)
579 {
580 case ArgType.Int:
581 sig ~= 'i';
582 break;
583 case ArgType.NewId:
584 if (arg.iface.empty)
585 sig ~= "su";
586 sig ~= "n";
587 break;
588 case ArgType.UInt:
589 sig ~= "u";
590 break;
591 case ArgType.Fixed:
592 sig ~= "f";
593 break;
594 case ArgType.String:
595 sig ~= "s";
596 break;
597 case ArgType.Object:
598 sig ~= "o";
599 break;
600 case ArgType.Array:
601 sig ~= "a";
602 break;
603 case ArgType.Fd:
604 sig ~= "h";
605 break;
606 }
607 }
608 return sig;
609 }
610
611 void writePrivIfaceMsg(SourceFile sf)
612 {
613 sf.writeln(
614 "wl_message(\"%s\", \"%s\", &msgTypes[%d]),",
615 name, signature, ifaceTypeIndex
616 );
617 }
618 }
619
620
621 abstract class Interface
622 {
623 Protocol protocol;
624 string name;
625 string ver;
626 Description description;
627 Message[] requests;
628 Message[] events;
629 Enum[] enums;
630
631 // indicate that is created by server rather than by client request.
632 // protocal eventually set to false after all interfaces are parsed
633 bool isGlobal = true;
634
635
636 private static Interface[string] ifaceMap;
637
638 static Interface get(string name)
639 {
640 auto i = name in ifaceMap;
641 if (i) return *i;
642 else return null;
643 }
644
645 this (Element el, Protocol protocol)
646 {
647 assert(el.tagName == "interface");
648 this.protocol = protocol;
649 name = el.getAttribute("name");
650 ver = el.getAttribute("version");
651 description = new Description(el);
652 foreach (rqEl; el.getElementsByTagName("request"))
653 {
654 requests ~= fact.makeMessage(rqEl, this);
655 }
656 foreach (evEl; el.getElementsByTagName("event"))
657 {
658 events ~= fact.makeMessage(evEl, this);
659 }
660 foreach (enEl; el.getElementsByTagName("enum"))
661 {
662 enums ~= new Enum(enEl, name);
663 }
664 ifaceMap[name] = this;
665 }
666
667 abstract void writeCode(SourceFile sf);
668
669 @property string dName() const
670 {
671 return ifaceDName(name);
672 }
673
674 @property bool writeEvents()
675 {
676 return events.length && name != "wl_display";
677 }
678
679 @property size_t nullIfaceTypeLength()
680 {
681 size_t max =0;
682 foreach (tl; chain(requests, events)
683 .map!(msg => msg.nullIfaceTypeLength))
684 {
685 if (tl > max) max = tl;
686 }
687 return max;
688 }
689
690 void writeVersion(SourceFile sf)
691 {
692 sf.writeln("/// Version of %s.%s", protocol.name, name);
693 sf.writeln("enum ver = %s;", ver);
694 }
695
696 void writePrivIfaceMsgs(SourceFile sf, Message[] msgs, in string suffix)
697 {
698 if (msgs.empty) return;
699
700 sf.writeln("auto %s_%s = [", name, suffix);
701 sf.indentedBlock!({
702 foreach(msg; msgs)
703 {
704 msg.writePrivIfaceMsg(sf);
705 }
706 });
707 sf.writeln("];");
708 }
709
710 void writePrivIfacePopulate(SourceFile sf)
711 {
712 writePrivIfaceMsgs(sf, requests, "requests");
713 writePrivIfaceMsgs(sf, events, "events");
714 immutable memb = format("ifaces[%s]", indexSymbol(name));
715 sf.writeln(`%s.name = "%s";`, memb, name);
716 sf.writeln("%s.version_ = %s;", memb, ver);
717
718 if (requests.length)
719 {
720 sf.writeln("%s.method_count = %d;", memb, requests.length);
721 sf.writeln("%s.methods = %s_requests.ptr;", memb, name);
722 }
723 if (events.length)
724 {
725 sf.writeln("%s.event_count = %d;", memb, events.length);
726 sf.writeln("%s.events = %s_events.ptr;", memb, name);
727 }
728 }
729 }
730
731
732 abstract class Protocol
733 {
734 string name;
735 string copyright;
736 Interface[] ifaces;
737
738 this(Element el)
739 {
740 enforce(el.tagName == "protocol");
741 name = el.getAttribute("name");
742 foreach (cr; el.getElementsByTagName("copyright"))
743 {
744 copyright = cr.getElText();
745 break;
746 }
747 Interface[string] ifaceMap;
748 foreach (ifEl; el.getElementsByTagName("interface"))
749 {
750 auto iface = fact.makeInterface(ifEl, this);
751 ifaceMap[iface.name] = iface;
752 ifaces ~= iface;
753 }
754
755 foreach(iface; ifaces)
756 {
757 foreach(req; iface.requests)
758 {
759 foreach(arg; req.args)
760 {
761 if (arg.type == ArgType.NewId)
762 {
763 auto ifp = arg.iface in ifaceMap;
764 if (ifp) ifp.isGlobal = false;
765 }
766 }
767 }
768 }
769 }
770
771 abstract void writeCode(SourceFile sf, in Options opt);
772
773 abstract void writePrivIfaces(SourceFile sf);
774
775 abstract void writeNativeIfacesAssignment(SourceFile sf);
776
777 bool isLocalIface(string name)
778 {
779 return ifaces.map!(iface => iface.name).canFind(name);
780 }
781
782 void writeHeader(SourceFile sf, in Options opt)
783 {
784 import std.path : baseName;
785 sf.writeDoc(
786 "Module generated by wayland:scanner-%s for %s protocol\n" ~
787 " xml protocol: %s\n" ~
788 " generated code: %s",
789 scannerVersion, name,
790 (opt.inFile.length ? baseName(opt.inFile) : "stdin"),
791 opt.code.to!string
792 );
793 sf.writeln("module %s;", opt.moduleName);
794 sf.writeComment(
795 "Protocol copyright:\n\n%s", copyright
796 );
797 sf.writeComment(
798 "Bindings copyright:\n\n%s", bindingsCopyright
799 );
800 }
801
802 void writeNativeIfaces(SourceFile sf)
803 {
804 sf.writeln();
805 sf.writeln("immutable wl_interface[] wl_ifaces;");
806 sf.writeln();
807 foreach (i, iface; ifaces)
808 {
809 sf.writeln("enum %s = %d;", indexSymbol(iface.name), i);
810 }
811 sf.writeln();
812 sf.writeln("shared static this()");
813 sf.bracedBlock!({
814 sf.writeln("auto ifaces = new wl_interface[%d];", ifaces.length);
815 sf.writeln();
816 writePrivMsgTypes(sf);
817 sf.writeln();
818 foreach (iface; ifaces)
819 {
820 iface.writePrivIfacePopulate(sf);
821 sf.writeln();
822 }
823 sf.writeln("import std.exception : assumeUnique;");
824 sf.writeln("wl_ifaces = assumeUnique(ifaces);");
825 sf.writeln();
826 writeNativeIfacesAssignment(sf);
827 });
828 }
829
830 void writePrivMsgTypes(SourceFile sf)
831 {
832 size_t max =0;
833 foreach (l; ifaces.map!(iface => iface.nullIfaceTypeLength))
834 {
835 if (l > max) max = l;
836 }
837
838 immutable nullLength = max;
839 size_t typeIndex = 0;
840
841 sf.writeln("auto msgTypes = [");
842 sf.indentedBlock!({
843 foreach(i; 0..nullLength)
844 {
845 sf.writeln("null,");
846 }
847 foreach (iface; ifaces)
848 {
849 foreach(msg; chain(iface.requests, iface.events))
850 {
851 if (msg.ifaceTypesAllNull)
852 {
853 msg.ifaceTypeIndex = 0;
854 continue;
855 }
856 msg.ifaceTypeIndex = nullLength + typeIndex;
857 typeIndex += msg.args.length;
858 foreach (arg; msg.args)
859 {
860 if (!arg.iface.empty &&
861 (arg.type == ArgType.NewId ||
862 arg.type == ArgType.Object))
863 {
864 if (isLocalIface(arg.iface))
865 sf.writeln("&ifaces[%s],", indexSymbol(arg.iface));
866 else
867 sf.writeln("cast(wl_interface*)%s.iface.native,", ifaceDName(arg.iface));
868 }
869 else
870 {
871 sf.writeln("null,");
872 }
873 }
874
875 }
876 }
877 });
878 sf.writeln("];");
879 }
880 }
881
882
883 // for private wl_interface array
884 string indexSymbol(in string name) pure
885 {
886 return camelName(name, "index");
887 }
888
889 string ifaceDName(in string name) pure
890 {
891 return titleCamelName(name);
892 }
893
894 string validDName(in string name) pure
895 {
896 switch (name)
897 {
898 case "class": return "class_";
899 case "default": return "default_";
900 case "interface": return "iface";
901 case "version": return "version_";
902 default:
903 {
904 if (name[0] >= '0' && name[0] <= '9') return "_" ~ name;
905 else return name;
906 }
907 }
908 }
909
910 string getElText(Element el)
911 {
912 string fulltxt;
913 foreach (child; el.children)
914 {
915 if (child.nodeType == NodeType.Text)
916 {
917 fulltxt ~= child.nodeValue;
918 }
919 }
920
921 string[] lines;
922 string offset;
923 bool offsetdone = false;
924 foreach (l; fulltxt.split('\n'))
925 {
926 immutable bool allwhite = l.all!isWhite;
927 if (!offsetdone && allwhite) continue;
928
929 if (!offsetdone && !allwhite)
930 {
931 offsetdone = true;
932 offset = l
933 .until!(c => !c.isWhite)
934 .to!string;
935 }
936
937 if (l.startsWith(offset))
938 {
939 l = l[offset.length .. $];
940 }
941
942 lines ~= l;
943 }
944
945 foreach_reverse(l; lines) {
946 if (l.all!isWhite) {
947 lines = lines[0..$-1];
948 }
949 else break;
950 }
951
952 return lines.join("\n");
953 }
954
955
956 class SourceFile
957 {
958 File _output;
959 int _indentLev = 0;
960 bool _indentNext = true;
961
962 invariant()
963 {
964 assert(_indentLev >= 0);
965 }
966
967 this(File output)
968 {
969 _output = output;
970 }
971
972 @property File output()
973 {
974 return _output;
975 }
976
977 @property int indentLev() const
978 {
979 return _indentLev;
980 }
981
982 @property int indentChars() const
983 {
984 return _indentLev * charsPerIndent;
985 }
986
987 void indent()
988 {
989 _indentLev += 1;
990 }
991
992 void unindent()
993 {
994 _indentLev -= 1;
995 }
996
997 void write(Args...)(string codeFmt, Args args)
998 {
999 immutable code = format(codeFmt, args);
1000 immutable iStr = indentStr(_indentLev);
1001 if (_indentNext) _output.write(iStr);
1002 _output.write(code.replace("\n", "\n"~iStr));
1003 _indentNext = false;
1004 }
1005
1006 void writeln()
1007 {
1008 _output.writeln();
1009 _indentNext = true;
1010 }
1011
1012 /++
1013 + writes indented code and adds a final '\n'
1014 +/
1015 void writeln(Args...)(string codeFmt, Args args)
1016 {
1017 immutable code = format(codeFmt, args);
1018 immutable iStr = indentStr(_indentLev);
1019 if (_indentNext) _output.write(iStr);
1020 _output.writeln(code.replace("\n", "\n"~iStr));
1021 _indentNext = true;
1022 }
1023
1024 void writeComment(Args...)(string textFmt, Args args)
1025 {
1026 immutable text = format(textFmt, args);
1027 immutable indStr = indentStr(_indentLev);
1028 _output.writeln(indStr, "/+");
1029 foreach (l; text.split("\n")) {
1030 if (l.empty) _output.writeln(indStr, " +");
1031 else _output.writeln(indStr, " + ", l);
1032 }
1033 _output.writeln(indStr, " +/");
1034 }
1035
1036 void writeDoc(Args...)(string textFmt, Args args)
1037 {
1038 immutable text = format(textFmt, args);
1039 immutable indStr = indentStr(_indentLev);
1040 _output.writeln(indStr, "/++");
1041 foreach (l; text.split("\n")) {
1042 if (l.empty) {
1043 _output.writeln(indStr, " +");
1044 }
1045 else {
1046 _output.write(indStr, " + ");
1047 foreach (dchar c; l) {
1048 switch (c) {
1049 case '(': _output.write("$(LPAREN)"); break;
1050 case ')': _output.write("$(RPAREN)"); break;
1051 case '<': _output.write("<"); break;
1052 case '>': _output.write(">"); break;
1053 case '&': _output.write("&"); break;
1054 default: _output.write(c); break;
1055 }
1056 }
1057 _output.writeln();
1058 }
1059 }
1060 _output.writeln(indStr, " +/");
1061 }
1062 }
1063
1064
1065 void bracedBlock(alias writeF)(SourceFile sf)
1066 {
1067 sf.writeln("{");
1068 sf.indent();
1069 writeF();
1070 sf.unindent();
1071 sf.writeln("}");
1072 }
1073
1074 void indentedBlock(alias writeF)(SourceFile sf)
1075 {
1076 sf.indent();
1077 writeF();
1078 sf.unindent();
1079 }
1080
1081
1082 /// Build a camel name from components
1083 string buildCamelName(in string comp, in bool tit) pure
1084 {
1085 string name;
1086 bool cap = tit;
1087 foreach(char c; comp.toLower())
1088 {
1089 if (c != '_')
1090 {
1091 name ~= cap ? c.toUpper() : c;
1092 cap = false;
1093 }
1094 else
1095 {
1096 cap = true;
1097 }
1098 }
1099 return name;
1100 }
1101
1102 string camelName(in string comp) pure
1103 {
1104 return buildCamelName(comp, false);
1105 }
1106
1107 string titleCamelName(in string comp) pure
1108 {
1109 return buildCamelName(comp, true);
1110 }
1111
1112 string camelName(in string[] comps...) pure
1113 {
1114 string name = buildCamelName(comps[0], false);
1115 foreach (c; comps[1 .. $])
1116 {
1117 name ~= buildCamelName(c, true);
1118 }
1119 return name;
1120 }
1121
1122 string titleCamelName(in string[] comps...) pure
1123 {
1124 string name = buildCamelName(comps[0], true);
1125 foreach (c; comps[1 .. $])
1126 {
1127 name ~= buildCamelName(c, true);
1128 }
1129 return name;
1130 }
1131
1132 enum charsPerIndent = 4;
1133 enum wrapWidth = 80;
1134
1135 string indentStr(int indent) pure
1136 {
1137 return ' '.repeat(indent * charsPerIndent).array();
1138 }
1139
1140 string qualfiedTypeName(in string name) pure
1141 {
1142 return name
1143 .splitter(".")
1144 .map!(part => titleCamelName(part))
1145 .join(".");
1146 }
1147
1148 string splitLinesForWidth(string input, in string suffix, in string indent,
1149 in size_t width=wrapWidth) pure
1150 {
1151 string res;
1152 size_t w;
1153 foreach(i, word; input.split(" "))
1154 {
1155 if (i != 0)
1156 {
1157 string spacer = " ";
1158 if (w + word.length + suffix.length >= width)
1159 {
1160 spacer = suffix ~ "\n" ~ indent;
1161 w = indent.length;
1162 }
1163 res ~= spacer;
1164 }
1165 res ~= word;
1166 w += word.length;
1167 }
1168 return res;
1169 }