1 /// Written in the D programming language.
2 /// Date: 2015, Joakim Brännström
3 /// License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
4 /// Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 module dsrcgen.c;
6 
7 import std.typecons : Flag, Yes, No;
8 
9 public import dsrcgen.base;
10 
11 @safe:
12 
13 ///TODO: change to c-comment and make a separate for c++.
14 /** Affected by attribute begin
15  * begin ~ comment
16  */
17 class Comment : BaseModule {
18     mixin Attrs;
19 
20     private string contents;
21 
22     /// Create a one liner comment.
23     this(string contents) {
24         this.contents = contents;
25     }
26 
27     ///
28     override string renderIndent(int parent_level, int level) {
29         if ("begin" in attrs) {
30             return indent(attrs["begin"] ~ contents, parent_level, level);
31         }
32 
33         return indent("// " ~ contents, parent_level, level);
34     }
35 }
36 
37 /// Mixin of methods for creating semantic C content.
38 mixin template CModuleX(T) {
39     mixin Attrs;
40 
41     /** Access to self.
42      *
43      * Useful in with-statements.
44      */
45     T _() {
46         return this;
47     }
48 
49     Comment comment(string comment) {
50         auto e = new Comment(comment);
51         append(e);
52         e.sep;
53         return e;
54     }
55 
56     Text!T text(string content) {
57         auto e = new Text!T(content);
58         append(e);
59         return e;
60     }
61 
62     T base() {
63         auto e = new T;
64         append(e);
65         return e;
66     }
67 
68     // Statements
69     Stmt!T stmt(string stmt_, Flag!"addSep" separator = Yes.addSep) {
70         auto e = new Stmt!T(stmt_);
71         append(e);
72         if (separator) {
73             sep();
74         }
75         return e;
76     }
77 
78     auto break_() {
79         return stmt("break");
80     }
81 
82     auto call(string name, string params) {
83         import std.format : format;
84 
85         auto e = stmt(format("%s(%s)", name, params));
86         return e;
87     }
88 
89     auto call(T...)(string name, auto ref T args) {
90         import std.format : format;
91 
92         string params = this.paramsToString(args);
93 
94         auto e = stmt(format("%s(%s)", name, params));
95         return e;
96     }
97 
98     auto continue_() {
99         return stmt("continue");
100     }
101 
102     auto return_() {
103         return stmt("return");
104     }
105 
106     auto return_(string expr) {
107         return stmt("return " ~ expr);
108     }
109 
110     auto goto_(string name) {
111         import std.format : format;
112 
113         return stmt(format("goto %s", name));
114     }
115 
116     auto label(string name) {
117         import std.format : format;
118 
119         return stmt(format("%s:", name));
120     }
121 
122     auto define(string name) {
123         import std.format : format;
124 
125         auto e = stmt(format("#define %s", name));
126         e[$.end = ""];
127         return e;
128     }
129 
130     auto define(string name, string value) {
131         import std.format : format;
132 
133         // may need to replace \n with \\\n
134         auto e = stmt(format("#define %s %s", name, value));
135         e[$.end = ""];
136         return e;
137     }
138 
139     auto extern_decl(string value) {
140         import std.format : format;
141 
142         return stmt(format(`extern %s`, value));
143     }
144 
145     /// Wrap the supplied module in an extern statement.
146     auto extern_(string kind = null) {
147         import std.format : format;
148 
149         T e;
150 
151         if (kind.length == 0) {
152             e = stmt("extern ", No.addSep);
153         } else {
154             e = stmt(format(`extern "%s" `, kind), No.addSep);
155         }
156 
157         e.suppressIndent(1);
158         e[$.end = ""];
159         return e;
160     }
161 
162     auto include(string filename) {
163         import std.format : format;
164 
165         string f = filename;
166         string incl;
167 
168         if (f.length > 1 && f[0] == '<') {
169             incl = format("#include %s", f);
170         } else {
171             incl = format(`#include "%s"`, f);
172         }
173 
174         auto e = stmt(incl)[$.end = ""];
175         return e;
176     }
177 
178     // Suites
179     Suite!T suite(string headline, Flag!"addSep" separator = Yes.addSep) {
180         auto e = new Suite!T(headline);
181         append(e);
182         if (separator) {
183             sep();
184         }
185         return e;
186     }
187 
188     auto struct_(string name) {
189         auto e = suite("struct " ~ name)[$.end = "};"];
190         return e;
191     }
192 
193     auto if_(string cond) {
194         import std.format : format;
195 
196         return suite(format("if (%s)", cond));
197     }
198 
199     auto else_if(string cond) {
200         import std.format : format;
201 
202         return suite(format("else if (%s)", cond));
203     }
204 
205     auto else_() {
206         return suite("else");
207     }
208 
209     auto enum_() {
210         return suite("enum")[$.end = "};"];
211     }
212 
213     auto enum_(string identifier) {
214         return suite("enum " ~ identifier)[$.end = "};"];
215     }
216 
217     auto enum_const(string name) {
218         return stmt(name)[$.end = ","];
219     }
220 
221     auto for_(string init, string cond, string next) {
222         import std.format : format;
223 
224         return suite(format("for (%s; %s; %s)", init, cond, next));
225     }
226 
227     auto while_(string cond) {
228         import std.format : format;
229 
230         return suite(format("while (%s)", cond));
231     }
232 
233     auto do_while(string cond) {
234         import std.format : format;
235 
236         auto e = suite("do");
237         e[$.end = format("} while (%s);", cond)];
238         return e;
239     }
240 
241     auto switch_(string cond) {
242         import std.format : format;
243 
244         return suite(format("switch (%s)", cond));
245     }
246 
247     auto case_(string val) {
248         import std.format : format;
249 
250         auto e = suite(format("case %s:", val), No.addSep)[$.begin = "", $.end = ""];
251         e.sep;
252         return e;
253     }
254 
255     auto default_() {
256         auto e = suite("default:", No.addSep)[$.begin = "", $.end = ""];
257         e.sep;
258         return e;
259     }
260 
261     auto func(string return_type, string name) {
262         import std.format : format;
263 
264         auto e = stmt(format("%s %s()", return_type, name));
265         return e;
266     }
267 
268     auto func(T...)(string return_type, string name, auto ref T args) {
269         import std.format : format;
270 
271         string params = paramsToString(args);
272 
273         auto e = stmt(format("%s %s(%s)", return_type, name, params));
274         return e;
275     }
276 
277     auto func_body(string return_type, string name) {
278         import std.format : format;
279 
280         auto e = suite(format("%s %s()", return_type, name));
281         return e;
282     }
283 
284     auto func_body(T...)(string return_type, string name, auto ref T args) {
285         import std.format : format;
286 
287         string params = paramsToString(args);
288 
289         auto e = suite(format("%s %s(%s)", return_type, name, params));
290         return e;
291     }
292 
293     auto IF(string name) {
294         auto e = suite("#if " ~ name);
295         e[$.begin = "", $.end = "#endif // " ~ name];
296         e.sep;
297         e.suppressIndent(1);
298         return e;
299     }
300 
301     auto IFDEF(string name) {
302         import std.format : format;
303 
304         auto e = suite(format("#ifdef %s", name));
305         e[$.begin = "", $.end = "#endif // " ~ name];
306         e.sep;
307         e.suppressIndent(1);
308         return e;
309     }
310 
311     auto IFNDEF(string name) {
312         auto e = suite("#ifndef " ~ name);
313         e[$.begin = "", $.end = "#endif // " ~ name];
314         e.sep;
315         e.suppressIndent(1);
316         return e;
317     }
318 
319     auto ELIF(string cond) {
320         auto e = stmt("#elif " ~ cond);
321         return e;
322     }
323 
324     auto ELSE() {
325         auto e = stmt("#else");
326         return e;
327     }
328 }
329 
330 string paramsToString(T...)(auto ref T args) {
331     import std.conv : to;
332 
333     string params;
334     if (args.length >= 1) {
335         params = to!string(args[0]);
336     }
337     if (args.length >= 2) {
338         foreach (v; args[1 .. $]) {
339             params ~= ", " ~ to!string(v);
340         }
341     }
342     return params;
343 }
344 
345 /// Represent a semantic item in C source.
346 class CModule : BaseModule {
347     mixin CModuleX!(CModule);
348 }
349 
350 private string stmt_append_end(string s, ref const string[string] attrs) pure nothrow {
351     import std.algorithm : among;
352 
353     //TODO too much null checking, refactor.
354 
355     if (s.length == 0) {
356         string end = ";";
357         if (auto v = "end" in attrs) {
358             end = *v;
359         }
360         s ~= end;
361     } else {
362         bool in_pattern = false;
363         try {
364             in_pattern = s[$ - 1].among(';', ':', ',', '{') != 0;
365         }
366         catch (Exception e) {
367         }
368 
369         if (!in_pattern && s[0] != '#') {
370             string end = ";";
371             if (auto v = "end" in attrs) {
372                 end = *v;
373             }
374             s ~= end;
375         }
376     }
377 
378     return s;
379 }
380 
381 /** Affected by attribute end.
382  * stmt ~ end
383  *    <recursive>
384  */
385 class Stmt(T) : T {
386     private string headline;
387 
388     /// Content of the statement.
389     this(string headline) {
390         this.headline = headline;
391     }
392 
393     override string renderIndent(int parent_level, int level) {
394         string r = stmt_append_end(headline, attrs);
395 
396         if ("noindent" !in attrs) {
397             r = indent(r, parent_level, level);
398         }
399 
400         return r;
401     }
402 }
403 
404 /** Affected by attribute begin, end, noindent.
405  * headline ~ begin
406  *     <recursive>
407  * end
408  * noindent affects post_recursive. If set no indention there.
409  * r.length > 0 catches the case when begin or end is empty string. Used in switch/case.
410  */
411 class Suite(T) : T {
412     private string headline;
413 
414     /// Content of the suite/block.
415     this(string headline) {
416         this.headline = headline;
417     }
418 
419     override string renderIndent(int parent_level, int level) {
420         import std.ascii : newline;
421 
422         string r = headline ~ " {" ~ newline;
423         if (auto v = "begin" in attrs) {
424             r = headline ~ *v;
425         }
426 
427         if (r.length > 0 && !("noindent" in attrs)) {
428             r = indent(r, parent_level, level);
429         }
430         return r;
431     }
432 
433     override string renderPostRecursive(int parent_level, int level) {
434         string r = "}";
435         if (auto v = "end" in attrs) {
436             r = *v;
437         }
438 
439         if (r.length > 0 && "noindent" !in attrs) {
440             r = indent(r, parent_level, level);
441         }
442         return r;
443     }
444 }
445 
446 /// An expressioin in C.
447 struct E {
448 @safe pure:
449     import std.conv : to;
450 
451     private string content;
452 
453     /// Content of the expression.
454     this(string content) nothrow pure {
455         this.content = content;
456     }
457 
458     /// Convert argument via std.conv.to!string.
459     this(T)(T content) nothrow pure {
460         this.content = to!string(content);
461     }
462 
463     /// Concatenate two expressions with ".".
464     this(E lhs, string rhs) nothrow pure {
465         this.content = lhs.content ~ "." ~ rhs;
466     }
467 
468     /// ditto
469     auto e(string lhs) nothrow pure const {
470         return E(content ~ "." ~ lhs);
471     }
472 
473     /// ditto
474     auto e(E lhs) nothrow pure const {
475         return E(content ~ "." ~ lhs.content);
476     }
477 
478     /// Represent the semantic function call.
479     auto opCall(T...)(auto ref T value) pure const {
480         return E(content ~ "(" ~ paramsToString(value) ~ ")");
481     }
482 
483     // implicit
484     @property string toString() pure const nothrow {
485         return content;
486     }
487 
488     alias toString this;
489 
490     /// String representation of the content. Explicit cast.
491     T opCast(T : string)() pure const nothrow {
492         return content;
493     }
494 
495     /// Preprend the textual representation of the operator to the content.
496     auto opUnary(string op)() pure nothrow const {
497         static if (op == "+" || op == "-" || op == "*" || op == "++" || op == "--") {
498             return E(mixin("\"" ~ op ~ "\"~content"));
499         } else {
500             static assert(0, "Operator " ~ op ~ " not implemented");
501         }
502     }
503 
504     /** Represent the semantic meaning of binary operators.
505      *
506      * ~ is special cased but OK for it doesn't exist in C/C++.
507      */
508     auto opBinary(string op, T)(in T rhs) pure nothrow const {
509         static if (op == "+" || op == "-" || op == "*" || op == "/" || op == "%" || op == "&") {
510             return E(mixin("content~\" " ~ op ~ " \"~to!string(rhs)"));
511         } else static if (op == "~" && is(T == E)) {
512             return E(content ~ " " ~ rhs.content);
513         } else static if (op == "~") {
514             return E(content = content ~ to!string(rhs));
515         } else {
516             static assert(0, "Operator " ~ op ~ " not implemented");
517         }
518     }
519 
520     /** Reconstruct the semantic "=" as affecting the content.
521      *
522      * Example:
523      *   E("int x") = E(1) -> "x = 1"
524      */
525     auto opAssign(T)(T rhs) pure nothrow {
526         this.content ~= " = " ~ to!string(rhs);
527         return this;
528     }
529 }
530 
531 /** Code structure for generation of a C header.
532  *
533  * The content is structed as:
534  *  doc
535  *      header
536  *          ifdef_guardbegin
537  *              content
538  *          ifdef_guard end
539  *
540  * Note that the indent is suppressed.
541  */
542 struct CHModule {
543     /// Document root.
544     CModule doc;
545     /// Usually a copyright header.
546     CModule header;
547     /// Main code content.
548     CModule content;
549 
550     /**
551      * Params:
552      *   ifdef_guard = guard statement.
553      */
554     this(string ifdef_guard) {
555         // Must suppress indentation to generate what is expected by the user.
556         doc = new CModule;
557         with (doc) {
558             // doc is a container of the modules so should not affect indent.
559             // header, content and footer is containers so should not affect indent.
560             // ifndef guard usually never affect indent.
561             suppressIndent(1);
562             header = base;
563             header.suppressIndent(1);
564             with (IFNDEF(ifdef_guard)) {
565                 define(ifdef_guard);
566                 content = base;
567                 content.suppressIndent(1);
568             }
569         }
570     }
571 
572     /// Render the content as a string.
573     string render() {
574         return doc.render();
575     }
576 }
577 
578 @("Test of statements")
579 unittest {
580     string expect = "    77;
581     break;
582     continue;
583     return 5;
584     return long_value;
585     goto foo;
586     bar:
587     #define foobar
588     #define smurf 1
589 ";
590 
591     auto x = new CModule();
592 
593     with (x) {
594         stmt(E(77));
595         break_;
596         continue_;
597         return_(E(5));
598         return_("long_value");
599         goto_("foo");
600         label("bar");
601         define("foobar");
602         define("smurf", E(1));
603     }
604 
605     auto rval = x.render();
606     assert(rval == expect, rval);
607 }
608 
609 @("Test of preprocess statements")
610 unittest {
611     string expect = "    #if foo
612     inside;
613     if {
614         deep inside;
615     }
616     #endif // foo
617     #ifdef bar
618     inside;
619     #endif // bar
620     #ifndef foobar
621     inside;
622     #elif wee
623     inside;
624     #else
625     inside;
626     #endif // foobar
627 ";
628 
629     auto x = new CModule();
630 
631     with (x) {
632         with (IF("foo")) {
633             stmt("inside");
634             with (suite("if")) {
635                 stmt("deep inside");
636             }
637         }
638         with (IFDEF("bar")) {
639             stmt("inside");
640         }
641         with (IFNDEF("foobar")) {
642             stmt("inside");
643             ELIF("wee");
644             stmt("inside");
645             ELSE();
646             stmt("inside");
647         }
648     }
649 
650     auto rval = x.render();
651     assert(rval == expect, rval);
652 }
653 
654 @("Test of suites")
655 unittest {
656     string expect = "
657     foo {
658     }
659     if (foo) {
660     }
661     else if (bar) {
662     }
663     else {
664     }
665     for (x; y; z) {
666     }
667     while (x) {
668     }
669     do {
670     } while (x);
671     switch (x) {
672     }
673     case y:
674         foo;
675     default:
676         foobar;
677     int foobar(int x) {
678     }
679     int fun(int y);
680 ";
681 
682     auto x = new CModule();
683     with (x) {
684         sep();
685         suite("foo");
686         if_("foo");
687         else_if("bar");
688         else_;
689         for_("x", "y", "z");
690         while_("x");
691         do_while("x");
692         switch_("x");
693         with (case_("y")) {
694             stmt("foo");
695         }
696         with (default_) {
697             stmt("foobar");
698         }
699         func_body("int", "foobar", "int x");
700         func("int", "fun", "int y");
701     }
702 
703     auto rval = x.render;
704     assert(rval == expect, rval);
705 }
706 
707 @("Test of complicated switch")
708 unittest {
709     string expect = "
710     switch (x) {
711         case 0:
712             return 5;
713             break;
714         case 1:
715             return 3;
716             break;
717         default:
718             return -1;
719     }
720 ";
721 
722     auto x = new CModule();
723     with (x) {
724         sep();
725         with (switch_("x")) {
726             with (case_(E(0))) {
727                 return_(E(5));
728                 break_;
729             }
730             with (case_(E(1))) {
731                 return_(E(3));
732                 break_;
733             }
734             with (default_) {
735                 return_(E(-1));
736             }
737         }
738     }
739 
740     auto rval = x.render;
741     assert(rval == expect, rval);
742 }
743 
744 @("Test of empty CSuite")
745 unittest {
746     auto x = new Suite!CModule("test");
747     assert(x.render == "test {\n}", x.render);
748 }
749 
750 @("Test of stmt_append_end")
751 unittest {
752     string[string] attrs;
753     string stmt = "some_line";
754     string result = stmt_append_end(stmt, attrs);
755     assert(stmt ~ ";" == result, result);
756 
757     result = stmt_append_end(stmt ~ ";", attrs);
758     assert(stmt ~ ";" == result, result);
759 
760     attrs["end"] = "{";
761     result = stmt_append_end(stmt, attrs);
762     assert(stmt ~ "{" == result, result);
763 }
764 
765 @("Test of CSuite with formatting")
766 unittest {
767     auto x = new Suite!CModule("if (x > 5)");
768     assert(x.render() == "if (x > 5) {\n}", x.render);
769 }
770 
771 @("Test of CSuite with simple text")
772 unittest {
773     // also test that text(..) do NOT add a linebreak
774     auto x = new Suite!CModule("foo");
775     with (x) {
776         text("bar");
777     }
778     assert(x.render() == "foo {\nbar}", x.render);
779 }
780 
781 @("Test of CSuite with simple text and changed begin")
782 unittest {
783     auto x = new Suite!CModule("foo");
784     with (x[$.begin = "_:_"]) {
785         text("bar");
786     }
787     assert(x.render() == "foo_:_bar}", x.render);
788 }
789 
790 @("Test of CSuite with simple text and changed end")
791 unittest {
792     auto x = new Suite!CModule("foo");
793     with (x[$.end = "_:_"]) {
794         text("bar");
795     }
796     assert(x.render() == "foo {\nbar_:_", x.render);
797 }
798 
799 @("Test of nested CSuite")
800 unittest {
801     auto x = new Suite!CModule("foo");
802     with (x) {
803         text("bar");
804         sep();
805         with (suite("smurf")) {
806             comment("bar");
807         }
808     }
809     assert(x.render() == "foo {
810 bar
811     smurf {
812         // bar
813     }
814 }", x.render);
815 }
816 
817 @("Test of text in CModule with guard")
818 unittest {
819     auto hdr = CHModule("somefile_hpp");
820 
821     with (hdr.header) {
822         text("header text");
823         sep();
824         comment("header comment");
825     }
826     with (hdr.content) {
827         text("content text");
828         sep();
829         comment("content comment");
830     }
831 
832     assert(hdr.render == "header text
833 // header comment
834 #ifndef somefile_hpp
835 #define somefile_hpp
836 content text
837 // content comment
838 #endif // somefile_hpp
839 ", hdr.render);
840 }
841 
842 @("Test of Expression. Type conversion")
843 unittest {
844     import std.conv : to;
845 
846     string implicit = E("foo")(77);
847     assert("foo(77)" == implicit, implicit);
848 
849     auto explicit = cast(string) E("foo")(77);
850     assert("foo(77)" == explicit, explicit);
851 
852     auto to_string = to!string(E("foo")(77));
853     assert("foo(77)" == to_string, to_string);
854 }
855 
856 @("Test of Expression")
857 unittest {
858     string expect = "foo
859 foo(77)
860 77 + 3
861 77 - 3
862 44 - 3 + 7
863 (44 - 3 + 7)
864 foo(42 + 43)
865 int x = 7
866 ";
867     auto x = new CModule();
868     x.suppressIndent(1);
869 
870     x.text("foo");
871     x.sep;
872     x.text(E("foo")(77));
873     x.sep;
874     x.text(E(77) + 3);
875     x.sep;
876     x.text(E(77) - 3);
877     x.sep;
878     x.text(E(44) - E(3) + E(7));
879     x.sep;
880     x.text(E()(E(44) - E(3) + E(7)));
881     x.sep;
882     x.text(E("foo")(E(42) + 43));
883     x.sep;
884     x.text(E("int x") = 7);
885     x.sep;
886 
887     auto rval = x.render;
888     assert(rval == expect, rval);
889 }
890 
891 @("Test of indent")
892 unittest {
893     string expect = "    L2 1 {
894         L3 1.1 {
895         }
896         L3 1.2 {
897             L4 1.2.1 {
898             }
899         }
900     }
901 ";
902 
903     auto x = new CModule();
904 
905     with (x) {
906         with (suite("L2 1")) {
907             suite("L3 1.1");
908             with (suite("L3 1.2")) {
909                 suite("L4 1.2.1");
910             }
911         }
912     }
913 
914     auto rval = x.render();
915     assert(rval == expect, rval);
916 }
917 
918 @("Test of single suppressing of indent")
919 unittest {
920     string expect = "L1 1 {
921 L1 1.1 {
922 }
923 L1 1.2 {
924     L2 1.2.1 {
925     }
926 }
927 }
928 ";
929 
930     auto x = new CModule();
931 
932     with (x) {
933         suppressIndent(1);
934         with (suite("L1 1")) {
935             suite("L1 1.1");
936             with (suite("L1 1.2")) {
937                 suite("L2 1.2.1");
938             }
939         }
940     }
941 
942     auto rval = x.render();
943     assert(rval == expect, rval);
944 }
945 
946 @("Test of nested suppressing of indent")
947 unittest {
948     string expect = "L1 1 {
949 L1 1.1 {
950 }
951 L1 1.2 {
952 L1 1.2.1 {
953     L2 1.2.1.1 {
954     }
955 }
956 }
957 }
958 ";
959 
960     auto x = new CModule();
961 
962     with (x) {
963         suppressIndent(1);
964         // suppressing L1 1 to be on the same level as x
965         // affects L1 1 and the first level of children
966         with (suite("L1 1")) {
967             suite("L1 1.1"); // suppressed
968             with (suite("L1 1.2")) {
969                 suppressIndent(1);
970                 with (suite("L1 1.2.1")) { // suppressed
971                     suite("L2 1.2.1.1");
972                 }
973             }
974         }
975     }
976 
977     auto rval = x.render();
978     assert(rval == expect, rval);
979 }
980 
981 @("shall be an expression assignment")
982 unittest {
983     auto expect = "    a = p;
984 ";
985 
986     auto m = new CModule;
987     auto e = E("a");
988     e = E("p");
989     m.stmt(e);
990 
991     assert(expect == m.render, m.render);
992 }
993 
994 @("shall be a return with and without value")
995 unittest {
996     auto expect = "    return;
997     return a;
998 ";
999 
1000     auto m = new CModule;
1001     m.return_();
1002     m.return_("a");
1003 
1004     assert(expect == m.render, m.render);
1005 }
1006 
1007 @("shall be a C enum definition")
1008 unittest {
1009     auto expect = "    enum {
1010     }
1011     enum A {
1012     };
1013     enum B {
1014         L0,
1015         L1 = 2,
1016     }
1017 ";
1018 
1019     auto m = new CModule;
1020     m.enum_;
1021     m.enum_("A");
1022     with (m.enum_("B")) {
1023         enum_const("L0");
1024         enum_const(E("L1") = E("2"));
1025     }
1026 }
1027 
1028 @("shall be an extern var")
1029 unittest {
1030     auto expect = `    extern var;
1031 `;
1032     auto m = new CModule;
1033     m.extern_decl("var");
1034 
1035     assert(expect == m.render, m.render);
1036 }
1037 
1038 @("shall be an extern C function")
1039 unittest {
1040     auto expect = `    extern "C"     void f();
1041 `;
1042     auto m = new CModule;
1043     m.extern_("C").func("void", "f");
1044 
1045     assert(expect == m.render, m.render);
1046 }