1 /**
2  * Provides common functions during language conversion.
3  */
4 module codebuilder.structure;
5 
6 import std.algorithm;
7 import std.conv;
8 import std.range;
9 import std.regex;
10 
11 /**
12  * Specifies what string will be prepended for each indentation level.
13  */
14 string indentation = "\t";
15 unittest {
16 	auto code = CodeBuilder(1);
17 	code.modifyLine = false;
18 	scope(exit) indentation = "\t";
19 	indentation = "    ";
20 	code.put("Hello");
21 	assert(code.finalize().startsWith("    "));
22 }
23 
24 /**
25  * Converts a numeric to an indent string
26  */
27 string indented(int indentCount) {
28     assert(indentCount > -1, "Indentation can never be less than 0.");
29 	if(__ctfe)
30 		return to!(string)(repeat("    ", indentCount).join.array);
31 	else
32 		return to!(string)(repeat(indentation, indentCount).join.array);
33 } unittest {
34 	static assert(indented(2) == "        ");
35 	assert(indented(2) == "\t\t");
36 }
37 
38 /**
39  * Specifies how indentation should be applied to this line.
40  *
41  * open - specifies that this opens a block and should indent
42  * future lines.
43  *
44  * close - specifies that this closes a block, itself and
45  * future lines should indent one less.
46  *
47  * none - perform not modification to the indentation.
48  */
49 enum Indent { none, open, close = 4 }
50 
51 /**
52  * An output range that provides extended functionality for
53  * constructing a well formatted string of code.
54  *
55  * CodeBuilder has three main operations.
56  *
57  * $(UL
58  *    $(LI put - Applies string directly to desired output)
59  *    $(LI push - Stacks string for later)
60  *    $(LI build - Constructs a sequence of strings)
61  * )
62  *
63  * $(H1 put)
64  *
65  * The main operation for placing code into the buffer. This will sequentially
66  * place code into the buffer. Similar operations are provided for the other
67  * operations.
68  *
69  * $(D rawPut) will place the string without indentation added.
70  *
71  * One can place the current indentation level without code by calling put("");
72  *
73  * $(H1 push)
74  *
75  * This places code onto a stack. $(B pop) can be used to put the code into the
76  * buffer.
77  *
78  * $(H1 build)
79  *
80  * Building code in a sequence can be pushed onto the stack, or saved to be put
81  * into the buffer later.
82  */
83 struct CodeBuilder {
84 	struct Operation {
85 		string text;
86 		Indent indent;
87 		bool raw;
88 		string file;
89 		int line;
90 	}
91 	private Operation[] upper;
92 	private Operation[][] lower;
93 
94 	int indentCount;
95 	bool modifyLine = true;
96 
97 	/**
98 	 */
99 	static CodeBuilder opCall(int indentedCount) {
100 		CodeBuilder b;
101 		b.indentCount = indentedCount;
102 		return b;
103 	}
104 
105 	/**
106 	 * Places str into the buffer.
107 	 *
108 	 * put("text", Indent.open); will indent "text" at the current level and
109 	 * future code will be indented one level.
110 	 *
111 	 * put("other", Indent.close); will indent "other" at one less the current
112 	 * indentation.
113 	 *
114 	 * put("}{", Indent.close | Indent.open) will indent "}{" at one less the
115 	 * current indentation and continue indentation for future code.
116 	 *
117 	 * rawPut provides the same operations but does not include the current
118 	 * indentation level.
119 	 *
120 	 * To place indentation but no other code use put("");
121 	 *
122 	 * To reduce indentation without inserting code use put(Indent.close);
123 	 */
124 	void put(string str, Indent indent = Indent.none, string f = __FILE__, int l = __LINE__) {
125 		version(ModifyLine) if(modifyLine)
126 			upper ~= Operation("#line " ~ l.to!string ~ " \"" ~ f ~ "\"\n", Indent.none, true);
127 		upper ~= Operation(str, indent);
128 	} unittest {
129 		/// Line numbers are added when using put
130 		auto code = CodeBuilder(0);
131 
132 		mixin(`#line 0 "fake.file"
133 			  code.put("line");`);
134 
135 		auto ans = code.finalize();
136 
137 		assert(ans == "#line 0 \"fake.file\"\nline", ans);
138 	}
139 
140 
141 	/// ditto
142 	void rawPut(string str, Indent indent = Indent.none, string f = __FILE__, int l = __LINE__) {
143 		upper ~= Operation(str, indent, true);
144 	} unittest {
145 		/// Raw input does not insert indents
146 		auto code = CodeBuilder(1);
147 
148 		code.rawPut("No indentation");
149 
150 		auto ans = code.finalize();
151 
152 		assert(ans == "No indentation", ans);
153 	} unittest {
154 		/// Call finalization twice
155 		auto code = CodeBuilder(1);
156 		code.modifyLine = false;
157 
158 		code.rawPut("No indentation\n");
159 		code.put("{\n", Indent.open);
160 
161 		code.finalize();
162 		auto ans = code.finalize();
163 
164 		assert(ans == "No indentation\n\t{\n", ans);
165 	}
166 
167 	/// ditto
168 	void put(Indent indent) {
169 		assert(!(indent & Indent.close & Indent.open), "No-op indent");
170 		upper ~= Operation(null, indent);
171 	} unittest {
172 		auto code = CodeBuilder(0);
173 		code.modifyLine = false;
174 		code.put("No Indent");
175 		code.put(Indent.open);
176 		code.put("Indented");
177 		assert(!code.finalize().startsWith(indentation));
178 		assert(code.finalize() == "No Indent\tIndented", code.finalize());
179    }
180 
181 	/**
182 	 */
183 	void put(CodeBuilder build) {
184 		build.finalize();
185 		upper ~= build.upper;
186 	} unittest {
187 		// CodeBuilders can be added to each other
188 		auto code = CodeBuilder(5);
189 		code.modifyLine = false;
190 		code.put("a\n", Indent.open);
191 		code.put("b\n");
192 		code.put("c\n", Indent.close);
193 		auto code2 = CodeBuilder(0);
194 		code2.modifyLine = false;
195 		code2.put("{\n", Indent.open);
196 		code2.push("}\n", Indent.close);
197 		code2.put(code);
198 		assert(code2.finalize() == "{\n\ta\n\t\tb\n\tc\n}\n", code2.finalize());
199 	}
200 
201 	/**
202 	 * Places str onto a stack that can latter be popped into
203 	 * the current buffer.
204 	 *
205 	 * See put for specifics.
206 	 */
207 	void push(string str, Indent indent = Indent.close, string f = __FILE__, int l = __LINE__) {
208 		lower ~= [Operation(str, indent, false, f, l)];
209 	} unittest {
210 		auto code = CodeBuilder(0);
211 		code.modifyLine = false;
212 		code.push("}\n");
213 		code.push("b\n", Indent.none);
214 		code.push("{\n", Indent.open);
215 		assert(code.finalize() == "{\n\tb\n}\n", code.finalize());
216 	}
217 
218 	/// ditto
219 	void push(Indent indent, string f = __FILE__, int l = __LINE__) {
220 		lower ~= [Operation(null, indent, false, f, l)];
221 	} unittest {
222 		auto code = CodeBuilder(0);
223 		code.modifyLine = false;
224 		code.push("b\n", Indent.none);
225 		code.push(Indent.open);
226 		code.push("{\n", Indent.none);
227 		assert(code.finalize() == "{\n\tb\n", code.finalize());
228 	}
229 
230 	/// ditto
231 	void rawPush(string str = null, Indent indent = Indent.close, string f = __FILE__, int l = __LINE__) {
232 		lower ~= [Operation(str, indent, true, f, l)];
233 	} unittest {
234 		auto code = CodeBuilder(5);
235 		code.modifyLine = false;
236 		code.rawPush("b\n", Indent.none);
237 		code.push("{\n", Indent.open);
238 		assert(code.finalize() == "\t\t\t\t\t{\nb\n", code.finalize());
239 	} unittest {
240 		auto code = CodeBuilder(0);
241 		code.modifyLine = false;
242 		code.put("{\n", Indent.open);
243 		if(true)
244 			code.rawPush();
245 		else
246 			code.push("b\n", Indent.none);
247 		code.pop();
248 		assert(code.finalize() == "{\n", code.finalize());
249 	}
250 
251 
252 	/**
253 	 * Places the top stack item into the buffer.
254 	 */
255 	void pop() {
256 		assert(!lower.empty(), "Can't pop empty buffer");
257 
258 		foreach(op; lower.back())
259 			if(op.raw)
260 				rawPut(op.text, op.indent, op.file, op.line);
261 			else
262 				put(op.text, op.indent, op.file, op.line);
263 
264 		lower.popBack();
265 		if(!__ctfe) assumeSafeAppend(lower);
266 	} unittest {
267 		auto code = CodeBuilder(0);
268 		code.modifyLine = false;
269 		code.put("{\n", Indent.open);
270 		code.push("}\n");
271 		code.pop();
272 		code.put("b\n");
273 		assert(code.finalize() == "{\n}\nb\n", code.finalize());
274 	}
275 
276 	/**
277 	 */
278 	void push(CodeBuilder build) {
279 		build.finalize();
280 		lower ~= build.upper;
281 	} unittest {
282 		// CodeBuilders can be added to each other
283 		auto code = CodeBuilder(5);
284 		code.modifyLine = false;
285 		code.put("a\n", Indent.open);
286 		code.put("b\n");
287 		code.put("c\n", Indent.close);
288 		auto code2 = CodeBuilder(0);
289 		code2.modifyLine = false;
290 		code2.put("{\n", Indent.open);
291 		code2.push("}\n", Indent.close);
292 		code2.push(code);
293 		assert(code2.finalize() == "{\n\ta\n\t\tb\n\tc\n}\n", code2.finalize());
294 	}
295 
296 	/**
297 	 * Returns the buffer, applying an code remaining on the stack.
298 	 */
299 	string finalize() {
300 		while(!lower.empty)
301 			pop();
302 		string ans;
303 		int indentCount = indentCount;
304 		foreach(op; upper.save) {
305 			if(op.indent & Indent.close) indentCount--;
306 			if(!op.raw)
307 				ans ~= indented(indentCount);
308 			ans ~= op.text;
309 			if(op.indent & Indent.open) indentCount++;
310 		}
311 		return ans;
312 	}
313 }
314 
315 unittest {
316 	// Compiletime capable
317 	string run() {
318 		auto code = CodeBuilder(1);
319 		code.modifyLine = false;
320 		code.put("Hello");
321 		code.rawPut(" World");
322 		return code.finalize();
323 	}
324 
325 	static assert(run() == "    Hello World", run());
326 }
327 
328 /// Example usage
329 unittest {
330 	auto indentCount = 0;
331 	auto code = CodeBuilder(indentCount);
332 	code.put("void main() {\n", Indent.open);
333 	code.push("}\n");
334 	code.put("int a = 5;\n");
335 	code.put("multiply(a);\n");
336 	code.pop();
337 
338 	code.put("\nvoid multiply(int v) {\n", Indent.open);
339 	code.push("}\n");
340 	code.put("try {\n", Indent.open);
341 	auto catchblock = CodeBuilder(1);
342 	catchblock.put("} catch(Exception e) {\n", Indent.close | Indent.open);
343 	catchblock.put("import std.stdio;\n");
344 	catchblock.put("writeln(`Exception is bad but I don't care.`);\n");
345 	catchblock.put("}\n", Indent.close);
346 	code.push(catchblock);
347 
348 	code.put("return v * ");
349 	code.rawPut(76.to!string ~ ";\n");
350 }