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