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