1 module mood.compiler;
2 
3 import std.stdio;
4 import std..string;
5 import std.conv;
6 
7 import mood.node;
8 import mood.parser;
9 
10 import std.traits: fullyQualifiedName, isBuiltinType, isAssociativeArray, isPointer, PointerTarget, TemplateArgsOf,
11                     TemplateOf, isArray, KeyType, ValueType;
12 import std.range.primitives: ElementType;
13 
14 import vibe.http.server: HTTPServerRequest, HTTPServerResponse;
15 
16 // works, but do not trust. Look more into later.
17 private immutable bool shrink = false;
18 
19 /**
20  * Struct that represents a single node in a document. 
21  *   
22  * DocumentNode is used to compress the number of overall nodes that have to be processed on page load. Currently the comment field is unused, though it may be used in the future.
23 */
24 private struct DocumentNode
25 {
26     bool code = false;      /// True if the section is a code section. Used in the server to determine output ordering.
27     bool comment = false;   /// True if the section is a comment. Reserved for potential future use.
28     string content = "";    /// The string content of the section.
29 }
30 
31 
32 /**
33  * A struct that represents a compiled webpage
34  *
35  * Returned by compile, and contains an entrypoint function to run all of the code that is on the webpage. Currently codeSections is unused, but in the future will be used for error checking.
36 */
37 struct Document
38 {
39     DocumentNode[] nodes = [];                                                                          /// The individual document nodes that make a page.
40     uint codeSections = 0;                                                                              /// The number of code sections
41 }
42 
43 /**
44  * Params:
45  *  file = The file to import and parse
46  * Returns: Parsed file
47 */
48 private Node[] importAndParse(string file)()
49 {
50     enum tokens = tokenizeDHTML(import(file));
51     enum nodes = parseDHTML(tokens);
52     return nodes;
53 }
54 
55 /**
56  * Resolve any includes that are in a parsed file.
57  *
58  * iterates over all of the parsed nodes and if there is a node that is an include tag i.e. <include:file.html/>, then it will load the file, and try and insert it into the webpage.
59  * Note: This will fail if two or more includes rely on each other. If you get an error along the lines of too many levels of CTFE recursion, then you may have one file that includes itself.
60  * 
61  * Params:
62  *  nodes = The nodes of the current webpage that are to be parsed over.
63  * Returns: The resulting webpage with all includes inserted into it.
64 */
65 Node[] link(const Node[] nodes)()
66 {
67     Node[] result;
68     static foreach(node; nodes)
69     {
70         static if (node.tagType == TagType.Tag && node.content.length >= 9 && node.content[0..8] == "include:")
71         {
72             static if (node.content[$-1] != '/')
73                 static assert(0, "Compilation error: Malformed include statement. Missing \"/\"?");
74             result ~= link!(importAndParse!(node.content[8..$-1])());
75         }
76         else
77             result ~= node;
78     }
79     return result;
80 }
81 
82 /**
83  * Compiles a set of nodes into a Document.
84  *
85  * Takes a set of parsed, and linked nodes, then turns it into an optimized webpage by creating the entrypoint function, of the app, and shortening the normal html content into as few nodes as possible.
86  *
87  * Params:
88  *  nodes = The webpage nodes.
89  * Returns: Compiled Document that represents the webpage.
90 */
91 Document compile(const Node[] nodes)()
92 {
93     Document doc; // resulting document
94     doc.nodes ~= DocumentNode.init; // start off with a blank document node
95     static foreach(node; nodes)
96     {
97         // continue not work well in static foreach
98         static if (shrink && node.nodeType == NodeType.Content && node.content.strip.length == 0) {}
99         else
100         {
101             // add a new section if we hit a code section
102             static if (node.tagType == TagType.Code)
103             {
104                 doc.codeSections++;
105                 doc.nodes ~= DocumentNode(true, false, node.content);
106                 doc.nodes ~= DocumentNode.init;
107             }
108             else static if (node.tagType == TagType.Insert)
109             {
110                 doc.codeSections++;
111                 doc.nodes ~= DocumentNode(true, false, node.content);
112                 doc.nodes ~= DocumentNode.init;
113             }
114             else
115                 doc.nodes[$-1].content ~= node.original; // otherwise add the string contents
116         }
117     }
118 
119     return doc;
120 }
121 
122 
123 /// A block of code that is inserted into the beginning of every webpage program so that it can have output functionality.
124 immutable string outputCodeStub = `import _stub = std.conv: text;
125 void output(T...)(T Args)
126 {
127     foreach(arg; Args)
128     {
129         outputStream[$-1] ~= _stub.text(arg); 
130     }
131 }
132 `;
133 
134 /**
135  * Converts template parameters to function parameters.
136  *
137  * Used to convert template parameters into the function parameters that are passed to the executable function on page load.
138  * 
139  * Params:
140  *  params = The params that are to be converted.
141  * Returns: The resulting code from the conversion.
142 */
143 string extendParameters(params...)()
144 {
145     string code = "";
146     static foreach(i, p; params)
147     {
148         code ~= ", " ~ typeof(params[i]).stringof ~ " " ~ __traits(identifier, params[i]);
149     }
150     return code;
151 }
152 
153 /**
154  * Creates the program source for a webpage.
155  *
156  * Creates the source code for a webpage by taking in all the nodes of the webpage and determining if its a code section or not.
157  *
158  * Params:
159  *  nodes = The nodes of the webpage.
160  * Returns: Source code of the program that is mixin'd
161 */
162 string createProgram(const Node[] nodes, params...)()
163 {
164     string code = "(ref string[] outputStream, HTTPServerRequest req, HTTPServerResponse res" ~ extendParameters!params ~ "){ outputStream = [\"\"];\n" ~ outputCodeStub;
165 
166     foreach(node; nodes)
167 	{
168         if (node.tagType == TagType.Code)
169             code ~= node.content ~ "\n outputStream ~= \"\";\n";
170         else if (node.tagType == TagType.Insert)
171             code ~= "output(" ~ node.content ~ ");\n outputStream ~= \"\";\n";
172 	}
173 
174     code ~= "\n}";
175     return code;
176 }
177 
178 /**
179  * Creates executable program for a webpage
180  * 
181  * Takes in a set of nodes and parameters and creates the executable function that is used to run code on a webpage
182  * 
183  * Params:
184  *  __nodes = The nodes of the webpage itself
185  * __params = the parameters that are passed to the webpage through the render function.
186  * Returns: automatically deduced function that is ran on page load.
187 */
188 auto compileProgram(const Node[] __nodes, __params...)()
189 {
190     mixin(getImportList!__params);
191 
192     return mixin(createProgram!(__nodes, __params));
193 }
194 
195 /**
196  * Translates a fully qualified name into a selective import statement.
197  * 
198  */
199 private string getImportStatementFromFQN(string fqn)
200 {
201 	// remove the junk from the fqn
202 	uint idx = cast(uint)fqn.indexOf('(');
203 	uint idx2 = cast(uint)fqn.indexOf('.');
204 
205 	if (idx != -1 && idx2 != -1 && idx < idx2)
206 		fqn = fqn[idx+1..$];
207 
208 	// idx = idx == -1 ? 0u : idx;
209 	// fqn = fqn[idx+1..$];
210 
211 	char[] delimiters = [')', '(', '!', '[', ']'];
212 
213 
214 	idx = cast(uint)fqn.length;
215 	foreach(d; delimiters)
216 	{
217 		auto i = fqn.indexOf(d);
218 		if (i != -1 && i < idx)
219 			idx = cast(uint)i;
220 	}
221 
222 	fqn = fqn[0..idx];
223 	idx = cast(uint)fqn.lastIndexOf('.');
224 	return "import " ~ fqn[0..idx] ~ " : " ~ fqn[idx+1..$] ~ ";";
225 }
226 
227 /**
228  * Returns a list of Fully Qualified Names from a type symbol, T
229 */
230 private string[] getFQNSFromSymbol(T)()
231 {
232 	static if (is(T == void) == true)
233 		return [];
234 	else
235 	{
236 		// branching types
237 		static if (isAssociativeArray!T == true) // associative array (hash map)
238 		{
239 			return getFQNSFromSymbol!(KeyType!T) ~ getFQNSFromSymbol!(ValueType!T);
240 		}
241 		else static if (!is(TemplateOf!T == void))
242 		{
243 			string[] fqns = [ fullyQualifiedName!(TemplateOf!T) ];
244 			static foreach(T; TemplateArgsOf!T)
245 			{
246 				fqns ~= getFQNSFromSymbol!(TemplateOf!T);
247 			}
248 			return fqns;
249 		}
250 		// non-branching types
251 		else static if (isPointer!T == false && isArray!T == false && isBuiltinType!T == false) // raw type
252 			return [ fullyQualifiedName!T ];
253 		else static if (isPointer!T == true && isBuiltinType!T == false) // pointer type
254 			return getFQNSFromSymbol!(PointerTarget!T);
255 		else
256 			return getFQNSFromSymbol!(ElementType!T); // range type, so return the next lower type
257 	}
258 }
259 
260 /**
261  * Returns a mixin-able import list from a list of params.
262  */
263 string getImportList(params...)()
264 {
265 	string buffer;
266 	string[] fqns;
267 	static foreach(param; params)
268 	{
269 		fqns = getFQNSFromSymbol!(typeof(param));
270 		foreach(fqn; fqns)
271 		{
272 			buffer ~= getImportStatementFromFQN(fqn) ~ "\n";
273 		}
274 	}
275 	return buffer;
276 }
277 
278 unittest
279 {
280 	import std.typecons: Tuple;
281 	
282 	struct S
283 	{
284 		string s;
285 		int i;
286 	}
287 
288 	struct F
289 	{
290 		S s;
291 	}
292 
293 	struct G(T)
294 	{
295 		T t;
296 	}
297 
298 	struct H(T, K)
299 	{
300 		T t;
301 		K k;
302 	}
303 
304     void foo(params...)()
305     {
306         pragma(msg, "IMPORT LIST:");
307         pragma(msg, getImportList!params);
308         pragma(msg, "END");
309     }
310 
311     immutable S[][][][F[]]* cannot;
312 	S[][][][F[]]* can;
313 	pragma(msg, fullyQualifiedName!(typeof(cannot)));
314 	pragma(msg, (G!S).stringof);
315 	pragma(msg, "isBuiltinType!void: " ~ (isBuiltinType!void).text);
316 	pragma(msg, "isBuiltinType!void*: " ~ (isBuiltinType!(void*)).text);
317 	pragma(msg, "ElementType!(void*): " ~ (ElementType!(void*)).stringof);
318 	pragma(msg, "isBuiltinType!(typeof(cannot)): " ~ (isBuiltinType!(typeof(cannot))).text);
319 	pragma(msg, "isBuiltinType!S: " ~ (isBuiltinType!S).text);
320 	pragma(msg, "isPointer!(typeof(cannot)): " ~ (isPointer!(typeof(cannot))).text); 
321 	pragma(msg, "isAssociativeArray!(typeof(cannot)): " ~ (isAssociativeArray!(typeof(cannot))).text); 
322 	pragma(msg, "isBuiltinType!(string[S]): " ~ (isBuiltinType!(string[S])).text);
323 	pragma(msg, "isBuiltinType!(string[string]: " ~ (isBuiltinType!(string[string])).text);
324 	pragma(msg, "isBuiltinType!(S[]): " ~ (isBuiltinType!(S[])).text);
325 	pragma(msg, "isBuiltinType!(S*): " ~ (isBuiltinType!(S*)).text);
326 	pragma(msg, "isPointer!S: " ~ (isPointer!S).text);
327 	pragma(msg, "isArray!S: " ~ (isArray!S).text);
328 	pragma(msg, "ElementType!void: " ~ (ElementType!void).stringof);
329 	pragma(msg, "is(S == void): " ~ is(S == void).text);
330 	pragma(msg, "ElementType!(S*): " ~ (ElementType!(S*)).stringof);
331 	pragma(msg, "isBuiltinType!(immutable(S)): " ~ (isBuiltinType!(immutable(S))).text);
332 	pragma(msg, "TemplateOf!S: " ~ (TemplateOf!S).stringof);
333 	pragma(msg, "ElementType!(G!F): " ~ (ElementType!(G!F)).stringof);
334 
335 	pragma(msg, "S: " ~ (getFQNSFromSymbol!S).text);
336 	pragma(msg, "S[]: " ~ getFQNSFromSymbol!(S[]).text);
337 	pragma(msg, "S*: " ~ getFQNSFromSymbol!(S*).text);
338 	pragma(msg, "immutable(S[string]): " ~ (getFQNSFromSymbol!(immutable(S[string]))).text);
339 	pragma(msg, typeof(cannot).stringof ~ ": " ~ getFQNSFromSymbol!(typeof(cannot)).text);
340 	pragma(msg, "G!(typeof(cannot)): " ~ getFQNSFromSymbol!(G!(typeof(cannot))).text);
341 	pragma(msg, "H!(S, G!(F)): " ~ getFQNSFromSymbol!(H!(S, G!(F))).text);
342 	pragma(msg, "Tuple!(S, F): " ~ (getFQNSFromSymbol!(Tuple!(S, F))).text);
343 
344 	enum manySymbols0 = getFQNSFromSymbol!(G!(typeof(cannot)));
345 	pragma(msg, "manySymbols0:");
346 	static foreach(fqn; manySymbols0)
347 	{
348 		pragma(msg, "\t" ~ getImportStatementFromFQN(fqn));
349 	}
350 	enum manySumbols1 = getFQNSFromSymbol!(H!(S, G!(F)));
351 	pragma(msg, "manySymbols1:");
352 	static foreach(fqn; manySumbols1)
353 	{
354 		pragma(msg, "\t" ~ getImportStatementFromFQN(fqn));
355 	}
356 	enum manySymbols2 = getFQNSFromSymbol!(Tuple!(S, F));
357 	pragma(msg, "manySymbols2:");
358 	static foreach(fqn; manySymbols2)
359 	{
360 		pragma(msg, "\t" ~ getImportStatementFromFQN(fqn));
361 	}
362 	enum manySymbols3 = getFQNSFromSymbol!(S);
363 	pragma(msg, "manySymbols3:");
364 	static foreach(fqn; manySymbols3)
365 	{
366 		pragma(msg, "\t" ~ getImportStatementFromFQN(fqn));
367 	}
368 	enum manySymbols4 = getFQNSFromSymbol!(S[]);
369 	pragma(msg, "manySymbols4:");
370 	static foreach(fqn; manySymbols4)
371 	{
372 		pragma(msg, "\t" ~ getImportStatementFromFQN(fqn));
373 	}
374 
375 	foo!(cannot, can);
376 }