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         } catch (Exception e) {
366         }
367 
368         if (!in_pattern && s[0] != '#') {
369             string end = ";";
370             if (auto v = "end" in attrs) {
371                 end = *v;
372             }
373             s ~= end;
374         }
375     }
376 
377     return s;
378 }
379 
380 /** Affected by attribute end.
381  * stmt ~ end
382  *    <recursive>
383  */
384 class Stmt(T) : T {
385     private string headline;
386 
387     /// Content of the statement.
388     this(string headline) {
389         this.headline = headline;
390     }
391 
392     override string renderIndent(int parent_level, int level) {
393         string r = stmt_append_end(headline, attrs);
394 
395         if ("noindent" !in attrs) {
396             r = indent(r, parent_level, level);
397         }
398 
399         return r;
400     }
401 }
402 
403 /** Affected by attribute begin, end, noindent.
404  * headline ~ begin
405  *     <recursive>
406  * end
407  * noindent affects post_recursive. If set no indention there.
408  * r.length > 0 catches the case when begin or end is empty string. Used in switch/case.
409  */
410 class Suite(T) : T {
411     private string headline;
412 
413     /// Content of the suite/block.
414     this(string headline) {
415         this.headline = headline;
416     }
417 
418     override string renderIndent(int parent_level, int level) {
419         import std.ascii : newline;
420 
421         string r = headline ~ " {" ~ newline;
422         if (auto v = "begin" in attrs) {
423             r = headline ~ *v;
424         }
425 
426         if (r.length > 0 && !("noindent" in attrs)) {
427             r = indent(r, parent_level, level);
428         }
429         return r;
430     }
431 
432     override string renderPostRecursive(int parent_level, int level) {
433         string r = "}";
434         if (auto v = "end" in attrs) {
435             r = *v;
436         }
437 
438         if (r.length > 0 && "noindent" !in attrs) {
439             r = indent(r, parent_level, level);
440         }
441         return r;
442     }
443 }
444 
445 /// An expressioin in C.
446 struct E {
447 @safe pure:
448     import std.conv : to;
449 
450     private string content;
451 
452     /// Content of the expression.
453     this(string content) nothrow pure {
454         this.content = content;
455     }
456 
457     /// Convert argument via std.conv.to!string.
458     this(T)(T content) nothrow pure {
459         this.content = to!string(content);
460     }
461 
462     /// Concatenate two expressions with ".".
463     this(E lhs, string rhs) nothrow pure {
464         this.content = lhs.content ~ "." ~ rhs;
465     }
466 
467     /// ditto
468     auto e(string lhs) nothrow pure const {
469         return E(content ~ "." ~ lhs);
470     }
471 
472     /// ditto
473     auto e(E lhs) nothrow pure const {
474         return E(content ~ "." ~ lhs.content);
475     }
476 
477     /// Represent the semantic function call.
478     auto opCall(T...)(auto ref T value) pure const {
479         return E(content ~ "(" ~ paramsToString(value) ~ ")");
480     }
481 
482     // implicit
483     @property string toString() pure const nothrow {
484         return content;
485     }
486 
487     alias toString this;
488 
489     /// String representation of the content. Explicit cast.
490     T opCast(T : string)() pure const nothrow {
491         return content;
492     }
493 
494     /// Preprend the textual representation of the operator to the content.
495     auto opUnary(string op)() pure nothrow const {
496         static if (op == "+" || op == "-" || op == "*" || op == "++" || op == "--") {
497             return E(mixin("\"" ~ op ~ "\"~content"));
498         } else {
499             static assert(0, "Operator " ~ op ~ " not implemented");
500         }
501     }
502 
503     /** Represent the semantic meaning of binary operators.
504      *
505      * ~ is special cased but OK for it doesn't exist in C/C++.
506      */
507     auto opBinary(string op, T)(in T rhs) pure nothrow const {
508         static if (op == "+" || op == "-" || op == "*" || op == "/" || op == "%" || op == "&") {
509             return E(mixin("content~\" " ~ op ~ " \"~to!string(rhs)"));
510         } else static if (op == "~" && is(T == E)) {
511             return E(content ~ " " ~ rhs.content);
512         } else static if (op == "~") {
513             return E(content = content ~ to!string(rhs));
514         } else {
515             static assert(0, "Operator " ~ op ~ " not implemented");
516         }
517     }
518 
519     /** Reconstruct the semantic "=" as affecting the content.
520      *
521      * Example:
522      *   E("int x") = E(1) -> "x = 1"
523      */
524     auto opAssign(T)(T rhs) pure nothrow {
525         this.content ~= " = " ~ to!string(rhs);
526         return this;
527     }
528 }
529 
530 /** Code structure for generation of a C header.
531  *
532  * The content is structed as:
533  *  doc
534  *      header
535  *          ifdef_guardbegin
536  *              content
537  *          ifdef_guard end
538  *
539  * Note that the indent is suppressed.
540  */
541 struct CHModule {
542     /// Document root.
543     CModule doc;
544     /// Usually a copyright header.
545     CModule header;
546     /// Main code content.
547     CModule content;
548 
549     /**
550      * Params:
551      *   ifdef_guard = guard statement.
552      */
553     this(string ifdef_guard) {
554         // Must suppress indentation to generate what is expected by the user.
555         doc = new CModule;
556         with (doc) {
557             // doc is a container of the modules so should not affect indent.
558             // header, content and footer is containers so should not affect indent.
559             // ifndef guard usually never affect indent.
560             suppressIndent(1);
561             header = base;
562             header.suppressIndent(1);
563             with (IFNDEF(ifdef_guard)) {
564                 define(ifdef_guard);
565                 content = base;
566                 content.suppressIndent(1);
567             }
568         }
569     }
570 
571     /// Render the content as a string.
572     string render() {
573         return doc.render();
574     }
575 }
576 
577 @("Test of statements")
578 unittest {
579     string expect = "    77;
580     break;
581     continue;
582     return 5;
583     return long_value;
584     goto foo;
585     bar:
586     #define foobar
587     #define smurf 1
588 ";
589 
590     auto x = new CModule();
591 
592     with (x) {
593         stmt(E(77));
594         break_;
595         continue_;
596         return_(E(5));
597         return_("long_value");
598         goto_("foo");
599         label("bar");
600         define("foobar");
601         define("smurf", E(1));
602     }
603 
604     auto rval = x.render();
605     assert(rval == expect, rval);
606 }
607 
608 @("Test of preprocess statements")
609 unittest {
610     string expect = "    #if foo
611     inside;
612     if {
613         deep inside;
614     }
615     #endif // foo
616     #ifdef bar
617     inside;
618     #endif // bar
619     #ifndef foobar
620     inside;
621     #elif wee
622     inside;
623     #else
624     inside;
625     #endif // foobar
626 ";
627 
628     auto x = new CModule();
629 
630     with (x) {
631         with (IF("foo")) {
632             stmt("inside");
633             with (suite("if")) {
634                 stmt("deep inside");
635             }
636         }
637         with (IFDEF("bar")) {
638             stmt("inside");
639         }
640         with (IFNDEF("foobar")) {
641             stmt("inside");
642             ELIF("wee");
643             stmt("inside");
644             ELSE();
645             stmt("inside");
646         }
647     }
648 
649     auto rval = x.render();
650     assert(rval == expect, rval);
651 }
652 
653 @("Test of suites")
654 unittest {
655     string expect = "
656     foo {
657     }
658     if (foo) {
659     }
660     else if (bar) {
661     }
662     else {
663     }
664     for (x; y; z) {
665     }
666     while (x) {
667     }
668     do {
669     } while (x);
670     switch (x) {
671     }
672     case y:
673         foo;
674     default:
675         foobar;
676     int foobar(int x) {
677     }
678     int fun(int y);
679 ";
680 
681     auto x = new CModule();
682     with (x) {
683         sep();
684         suite("foo");
685         if_("foo");
686         else_if("bar");
687         else_;
688         for_("x", "y", "z");
689         while_("x");
690         do_while("x");
691         switch_("x");
692         with (case_("y")) {
693             stmt("foo");
694         }
695         with (default_) {
696             stmt("foobar");
697         }
698         func_body("int", "foobar", "int x");
699         func("int", "fun", "int y");
700     }
701 
702     auto rval = x.render;
703     assert(rval == expect, rval);
704 }
705 
706 @("Test of complicated switch")
707 unittest {
708     string expect = "
709     switch (x) {
710         case 0:
711             return 5;
712             break;
713         case 1:
714             return 3;
715             break;
716         default:
717             return -1;
718     }
719 ";
720 
721     auto x = new CModule();
722     with (x) {
723         sep();
724         with (switch_("x")) {
725             with (case_(E(0))) {
726                 return_(E(5));
727                 break_;
728             }
729             with (case_(E(1))) {
730                 return_(E(3));
731                 break_;
732             }
733             with (default_) {
734                 return_(E(-1));
735             }
736         }
737     }
738 
739     auto rval = x.render;
740     assert(rval == expect, rval);
741 }
742 
743 @("Test of empty CSuite")
744 unittest {
745     auto x = new Suite!CModule("test");
746     assert(x.render == "test {\n}", x.render);
747 }
748 
749 @("Test of stmt_append_end")
750 unittest {
751     string[string] attrs;
752     string stmt = "some_line";
753     string result = stmt_append_end(stmt, attrs);
754     assert(stmt ~ ";" == result, result);
755 
756     result = stmt_append_end(stmt ~ ";", attrs);
757     assert(stmt ~ ";" == result, result);
758 
759     attrs["end"] = "{";
760     result = stmt_append_end(stmt, attrs);
761     assert(stmt ~ "{" == result, result);
762 }
763 
764 @("Test of CSuite with formatting")
765 unittest {
766     auto x = new Suite!CModule("if (x > 5)");
767     assert(x.render() == "if (x > 5) {\n}", x.render);
768 }
769 
770 @("Test of CSuite with simple text")
771 unittest {
772     // also test that text(..) do NOT add a linebreak
773     auto x = new Suite!CModule("foo");
774     with (x) {
775         text("bar");
776     }
777     assert(x.render() == "foo {\nbar}", x.render);
778 }
779 
780 @("Test of CSuite with simple text and changed begin")
781 unittest {
782     auto x = new Suite!CModule("foo");
783     with (x[$.begin = "_:_"]) {
784         text("bar");
785     }
786     assert(x.render() == "foo_:_bar}", x.render);
787 }
788 
789 @("Test of CSuite with simple text and changed end")
790 unittest {
791     auto x = new Suite!CModule("foo");
792     with (x[$.end = "_:_"]) {
793         text("bar");
794     }
795     assert(x.render() == "foo {\nbar_:_", x.render);
796 }
797 
798 @("Test of nested CSuite")
799 unittest {
800     auto x = new Suite!CModule("foo");
801     with (x) {
802         text("bar");
803         sep();
804         with (suite("smurf")) {
805             comment("bar");
806         }
807     }
808     assert(x.render() == "foo {
809 bar
810     smurf {
811         // bar
812     }
813 }", x.render);
814 }
815 
816 @("Test of text in CModule with guard")
817 unittest {
818     auto hdr = CHModule("somefile_hpp");
819 
820     with (hdr.header) {
821         text("header text");
822         sep();
823         comment("header comment");
824     }
825     with (hdr.content) {
826         text("content text");
827         sep();
828         comment("content comment");
829     }
830 
831     assert(hdr.render == "header text
832 // header comment
833 #ifndef somefile_hpp
834 #define somefile_hpp
835 content text
836 // content comment
837 #endif // somefile_hpp
838 ", hdr.render);
839 }
840 
841 @("Test of Expression. Type conversion")
842 unittest {
843     import std.conv : to;
844 
845     string implicit = E("foo")(77);
846     assert("foo(77)" == implicit, implicit);
847 
848     auto explicit = cast(string) E("foo")(77);
849     assert("foo(77)" == explicit, explicit);
850 
851     auto to_string = to!string(E("foo")(77));
852     assert("foo(77)" == to_string, to_string);
853 }
854 
855 @("Test of Expression")
856 unittest {
857     string expect = "foo
858 foo(77)
859 77 + 3
860 77 - 3
861 44 - 3 + 7
862 (44 - 3 + 7)
863 foo(42 + 43)
864 int x = 7
865 ";
866     auto x = new CModule();
867     x.suppressIndent(1);
868 
869     x.text("foo");
870     x.sep;
871     x.text(E("foo")(77));
872     x.sep;
873     x.text(E(77) + 3);
874     x.sep;
875     x.text(E(77) - 3);
876     x.sep;
877     x.text(E(44) - E(3) + E(7));
878     x.sep;
879     x.text(E()(E(44) - E(3) + E(7)));
880     x.sep;
881     x.text(E("foo")(E(42) + 43));
882     x.sep;
883     x.text(E("int x") = 7);
884     x.sep;
885 
886     auto rval = x.render;
887     assert(rval == expect, rval);
888 }
889 
890 @("Test of indent")
891 unittest {
892     string expect = "    L2 1 {
893         L3 1.1 {
894         }
895         L3 1.2 {
896             L4 1.2.1 {
897             }
898         }
899     }
900 ";
901 
902     auto x = new CModule();
903 
904     with (x) {
905         with (suite("L2 1")) {
906             suite("L3 1.1");
907             with (suite("L3 1.2")) {
908                 suite("L4 1.2.1");
909             }
910         }
911     }
912 
913     auto rval = x.render();
914     assert(rval == expect, rval);
915 }
916 
917 @("Test of single suppressing of indent")
918 unittest {
919     string expect = "L1 1 {
920 L1 1.1 {
921 }
922 L1 1.2 {
923     L2 1.2.1 {
924     }
925 }
926 }
927 ";
928 
929     auto x = new CModule();
930 
931     with (x) {
932         suppressIndent(1);
933         with (suite("L1 1")) {
934             suite("L1 1.1");
935             with (suite("L1 1.2")) {
936                 suite("L2 1.2.1");
937             }
938         }
939     }
940 
941     auto rval = x.render();
942     assert(rval == expect, rval);
943 }
944 
945 @("Test of nested suppressing of indent")
946 unittest {
947     string expect = "L1 1 {
948 L1 1.1 {
949 }
950 L1 1.2 {
951 L1 1.2.1 {
952     L2 1.2.1.1 {
953     }
954 }
955 }
956 }
957 ";
958 
959     auto x = new CModule();
960 
961     with (x) {
962         suppressIndent(1);
963         // suppressing L1 1 to be on the same level as x
964         // affects L1 1 and the first level of children
965         with (suite("L1 1")) {
966             suite("L1 1.1"); // suppressed
967             with (suite("L1 1.2")) {
968                 suppressIndent(1);
969                 with (suite("L1 1.2.1")) { // suppressed
970                     suite("L2 1.2.1.1");
971                 }
972             }
973         }
974     }
975 
976     auto rval = x.render();
977     assert(rval == expect, rval);
978 }
979 
980 @("shall be an expression assignment")
981 unittest {
982     auto expect = "    a = p;
983 ";
984 
985     auto m = new CModule;
986     auto e = E("a");
987     e = E("p");
988     m.stmt(e);
989 
990     assert(expect == m.render, m.render);
991 }
992 
993 @("shall be a return with and without value")
994 unittest {
995     auto expect = "    return;
996     return a;
997 ";
998 
999     auto m = new CModule;
1000     m.return_();
1001     m.return_("a");
1002 
1003     assert(expect == m.render, m.render);
1004 }
1005 
1006 @("shall be a C enum definition")
1007 unittest {
1008     auto expect = "    enum {
1009     }
1010     enum A {
1011     };
1012     enum B {
1013         L0,
1014         L1 = 2,
1015     }
1016 ";
1017 
1018     auto m = new CModule;
1019     m.enum_;
1020     m.enum_("A");
1021     with (m.enum_("B")) {
1022         enum_const("L0");
1023         enum_const(E("L1") = E("2"));
1024     }
1025 }
1026 
1027 @("shall be an extern var")
1028 unittest {
1029     auto expect = `    extern var;
1030 `;
1031     auto m = new CModule;
1032     m.extern_decl("var");
1033 
1034     assert(expect == m.render, m.render);
1035 }
1036 
1037 @("shall be an extern C function")
1038 unittest {
1039     auto expect = `    extern "C"     void f();
1040 `;
1041     auto m = new CModule;
1042     m.extern_("C").func("void", "f");
1043 
1044     assert(expect == m.render, m.render);
1045 }