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 }