1 /**
2 Copyright: Copyright (c) 2015-2016, Joakim Brännström. All rights reserved.
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 
6 Basic support structure for enabling semantic representation in D of languages.
7 Assuming that submodules need:
8  - indentation.
9  - key/value store.
10  - recursing rendering.
11 */
12 module dsrcgen.base;
13 
14 @safe:
15 
16 private struct KV {
17     string k;
18     string v;
19 
20     this(T)(string k, T v) {
21         import std.conv : to;
22 
23         this.k = k;
24         this.v = to!string(v);
25     }
26 }
27 
28 package struct AttrSetter {
29     template opDispatch(string name) {
30         @property auto opDispatch(T)(T v) {
31             static if (name.length > 1 && name[$ - 1] == '_') {
32                 return KV(name[0 .. $ - 1], v);
33             } else {
34                 return KV(name, v);
35             }
36         }
37     }
38 }
39 
40 public:
41 
42 /// Methods for setting attributes inside slice operator via $.x.
43 mixin template Attrs() {
44     import std..string;
45 
46     public string[string] attrs;
47 
48     auto opIndex(T...)(T kvs) {
49         foreach (kv; kvs) {
50             attrs[kv.k] = kv.v;
51         }
52         return this;
53     }
54 
55     auto opDollar(int dim)() {
56         return AttrSetter();
57     }
58 }
59 
60 /** Interface for rendering functionality.
61  *
62  * After the semantic representation is finished the BaseElement interface is
63  * used to recursively render the representation.
64  */
65 interface BaseElement {
66     /// Recursively render the modules.
67     string render() pure;
68 
69     /// Query the module for an indented string representation.
70     string renderIndent(int parent_level, int level) pure;
71 
72     /// Query the module for a concatenated string of the childrens representation.
73     string renderRecursive(int parent_level, int level) pure;
74 
75     /// Query the module for post recursive data.
76     string renderPostRecursive(int parent_level, int level) pure;
77 }
78 
79 /// Raw text representation without indentation.
80 class Text(T) : T {
81     private string contents;
82 
83     /// Use content as is.
84     this(string contents) pure {
85         this.contents = contents;
86     }
87 
88     /// Render content without indentation.
89     override string renderIndent(int parent_level, int level) pure {
90         return contents;
91     }
92 }
93 
94 /** An empty node.
95  *
96  * Not affected by indentation.
97  *
98  * Useful to inject other nodes under it.
99  */
100 class Empty(T) : T {
101 }
102 
103 /** Common functionality for modules.
104  *
105  * Support indentation.
106  * Children structure.
107  * Recursive rendering of content + children.
108  * Line separation independent of accidental sep().
109  *
110  * TODO refactor, lessen the coupling by moving functionality to pure, free functions.
111  * TODO refactor, use a GC-less allocator like Array.
112  * TODO refactor, toString to support the range versions with a sink.
113  */
114 class BaseModule : BaseElement {
115 
116     /// Empty with defaults.
117     this() pure nothrow @nogc {
118     }
119 
120     /** Module with a indent different fro default
121      * Params:
122      *  indent_width = number of whitespaces to use as indent for each level
123      */
124     this(int indent_width) pure nothrow @nogc {
125         this.indent_width = indent_width;
126     }
127 
128     /** Set indent suppression from this point and all children.
129      *
130      * Number of levels to suppress indent of children.
131      * Propagated to leafs.
132      *
133      * It can't suppress indent to lower than parent.
134      * To suppress further use suppressThisIndent.
135      *
136      * Params:
137      *  levels = nr of indentation levels to suppress
138      */
139     void suppressIndent(int levels) pure nothrow @nogc {
140         this.suppress_child_indent = levels;
141     }
142 
143     /** Suppress indentation by also affecting the level propagated from the parent.
144      *
145      * Params:
146      *  levels = nr of indentation levels to suppress
147      */
148     void suppressThisIndent(int levels) pure nothrow @nogc {
149         this.suppress_indent = levels;
150     }
151 
152     /// Sets the width of the indentation
153     void setIndentation(int ind) pure nothrow @nogc {
154         this.indent_width = ind;
155     }
156 
157     /// Clear the node of childrens.
158     BaseModule reset() pure nothrow {
159         children.length = 0;
160         return this;
161     }
162 
163     /// Separate with at most count empty lines.
164     void sep(int count = 1) pure nothrow {
165         import std.ascii : newline;
166 
167         count -= sep_lines;
168         if (count <= 0)
169             return;
170         foreach (i; 0 .. count) {
171             children ~= new Text!(typeof(this))(newline);
172         }
173 
174         sep_lines += count;
175     }
176 
177     /** Insert element at the front.
178      *
179      * Resets line separation.
180      *
181      * TODO change to insertFront to be consistent with std.container API.
182      */
183     void prepend(BaseElement e) pure nothrow {
184         children = e ~ children;
185         sep_lines = 0;
186     }
187 
188     /** Insert element at the back.
189      *
190      * Resets line separation.
191      *
192      * TODO change to insertBack to be consistent with std.container API.
193      */
194     void append(BaseElement e) pure nothrow {
195         children ~= e;
196         sep_lines = 0;
197     }
198 
199     /** Remove all children and clear line separation.
200      *
201      * Resets line separation.
202      * Why?
203      * Conceptually restarts the container so no children then nothing to
204      * separate with empty lines.
205      */
206     void clearChildren() pure nothrow {
207         children.length = 0;
208         sep_lines = 0;
209     }
210 
211     /** Render content with an indentation that takes the parent in
212      * consideration.
213      */
214     string indent(string s, int parent_level, int level) const pure nothrow {
215         import std.algorithm : max;
216 
217         level = max(0, parent_level, level);
218 
219         char filler = ' ';
220         // use for debug purpose
221         //filler = cast(char) ('0' + level);
222 
223         char[] indent;
224         indent.length = indent_width * level;
225         indent[] = filler;
226 
227         return indent.idup ~ s;
228     }
229 
230     override string renderIndent(int parent_level, int level) pure {
231         return "";
232     }
233 
234     override string renderRecursive(int parent_level, int level) pure {
235         import std.algorithm : max;
236 
237         level -= suppress_indent;
238         string s = renderIndent(parent_level, level);
239 
240         // suppressing is intented to affects children. The current leaf is
241         // intented according to the parent or propagated level.
242         int child_level = level - suppress_child_indent;
243         foreach (e; children) {
244             // lock indent to the level of the parent. it allows a suppression of many levels of children.
245             s ~= e.renderRecursive(max(parent_level, level), child_level + 1);
246         }
247         s ~= renderPostRecursive(parent_level, level);
248 
249         return s;
250     }
251 
252     override string renderPostRecursive(int parent_level, int level) pure {
253         return "";
254     }
255 
256     override string render() pure {
257         return renderRecursive(0 - suppress_child_indent, 0 - suppress_child_indent);
258     }
259 
260 private:
261     int indent_width = 4;
262     int suppress_indent;
263     int suppress_child_indent;
264 
265     BaseElement[] children;
266     int sep_lines;
267 }
268 
269 /// Suppress the indentation one level.
270 T noIndent(T)(T m) if (is(T == class)) {
271     m.suppressIndent(1);
272     return m;
273 }