1 /** 2 * Temple (C) Dylan Knutson, 2013, distributed under the: 3 * Boost Software License - Version 1.0 - August 17th, 2003 4 * 5 * Permission is hereby granted, free of charge, to any person or organization 6 * obtaining a copy of the software and accompanying documentation covered by 7 * this license (the "Software") to use, reproduce, display, distribute, 8 * execute, and transmit the Software, and to prepare derivative works of the 9 * Software, and to permit third-parties to whom the Software is furnished to 10 * do so, all subject to the following: 11 * 12 * The copyright notices in the Software and this entire statement, including 13 * the above license grant, this restriction and the following disclaimer, 14 * must be included in all copies of the Software, in whole or in part, and 15 * all derivative works of the Software, unless such copies or derivative 16 * works are solely in the form of machine-executable object code generated by 17 * a source language processor. 18 */ 19 20 module temple.func_string_gen; 21 22 private import 23 temple, 24 temple.util, 25 temple.delims, 26 std.conv, 27 std.string, 28 std.array, 29 std.exception, 30 std.uni, 31 std.algorithm; 32 33 /** 34 * Stack and generator for unique temporary variable names 35 */ 36 private struct TempBufferNameStack 37 { 38 private: 39 const string base; 40 uint counter = 0; 41 string[] stack; 42 43 public: 44 this(string base) 45 { 46 this.base = base; 47 } 48 49 /** 50 * getNew 51 * Gets a new unique buffer variable name 52 */ 53 string pushNew() 54 { 55 auto name = base ~ counter.to!string; 56 counter++; 57 stack ~= name; 58 return name; 59 } 60 61 /** 62 * Pops the topmost unique variable name off the stack 63 */ 64 string pop() 65 { 66 auto ret = stack[$-1]; 67 stack.length--; 68 return ret; 69 } 70 71 /** 72 * Checks if there are any names to pop off 73 */ 74 bool empty() 75 { 76 return !stack.length; 77 } 78 } 79 80 /** 81 * Represents a unit of code that makes up the template function 82 */ 83 private struct FuncPart { 84 enum Type { 85 StrLit, // String literal appended to the buffer 86 Expr, // Runtime computed expression appended to the buffer 87 Stmt, // Any arbitrary statement/declaration making up the function 88 Line, // #line directive 89 } 90 91 Type type; 92 string value; 93 uint indent; 94 } 95 96 /** 97 * __temple_gen_temple_func_string 98 * Generates the function string to be mixed into a template which renders 99 * a temple file. 100 */ 101 package string __temple_gen_temple_func_string( 102 string temple_str, in string temple_name, in string filter_ident = "") 103 { 104 // Output function string being composed 105 FuncPart[] func_parts; 106 107 // Indendation level for a line being pushed 108 uint indent_level = 0; 109 110 // Current line number in the temple_str being scanned 111 size_t line_number = 0; 112 113 // Generates temporary variable names and keeps them on a stack 114 auto temp_var_names = TempBufferNameStack("__temple_capture_var_"); 115 116 // Content removed from the head of temple_str is put on the tail of this 117 string prev_temple_str = ""; 118 119 /* ----- func_parts appending functions ----- */ 120 void push_expr(string expr) { 121 func_parts ~= FuncPart(FuncPart.Type.Expr, expr, indent_level); 122 } 123 124 void push_stmt(string stmt) 125 { 126 func_parts ~= FuncPart(FuncPart.Type.Stmt, stmt ~ '\n', indent_level); 127 } 128 129 void push_string_literal(string str) 130 { 131 func_parts ~= FuncPart(FuncPart.Type.StrLit, str, indent_level); 132 } 133 134 void push_linenum() 135 { 136 func_parts ~= FuncPart( 137 FuncPart.Type.Line, 138 `#line %d "%s"`.format(line_number+1, temple_name) ~ "\n", 139 indent_level); 140 } 141 /* ----------------------------------------- */ 142 143 void indent() { indent_level++; } 144 void outdent() { indent_level--; } 145 146 // Tracks if the block that the parser has just 147 // finished processing should be printed (e.g., is 148 // it the block who's contents are assigned to the last tmp_buffer_var) 149 bool[] printStartBlockTracker; 150 void sawBlockStart(bool will_be_printed) 151 { 152 printStartBlockTracker ~= will_be_printed; 153 } 154 bool sawBlockEnd() 155 { 156 auto will_be_printed = printStartBlockTracker[$-1]; 157 printStartBlockTracker.length--; 158 return will_be_printed; 159 } 160 161 // Generate the function signature, taking into account if it has a 162 // FilterParam to use 163 push_stmt(build_function_head(filter_ident)); 164 165 indent(); 166 if(filter_ident.length) 167 { 168 push_stmt(`with(%s)`.format(filter_ident)); 169 } 170 push_stmt(`with(__temple_context) {`); 171 indent(); 172 173 // Keeps infinite loops from outright crashing the compiler 174 // The limit should be set to some arbitrary large number 175 uint safeswitch = 0; 176 177 while(temple_str.length) 178 { 179 // This imposes the limiatation of a max of 10_000 delimers parsed for 180 // a template function. Probably will never ever hit this in a single 181 // template file without running out of compiler memory 182 if(safeswitch++ > 10_000) 183 { 184 assert(false, "nesting level too deep; throwing saftey switch: \n" ~ temple_str); 185 } 186 187 DelimPos!(OpenDelim)* oDelimPos = temple_str.nextDelim(OpenDelims); 188 189 if(oDelimPos is null) 190 { 191 //No more delims; append the rest as a string 192 push_linenum(); 193 push_string_literal(temple_str); 194 prev_temple_str.munchHeadOf(temple_str, temple_str.length); 195 } 196 else 197 { 198 immutable OpenDelim oDelim = oDelimPos.delim; 199 immutable CloseDelim cDelim = OpenToClose[oDelim]; 200 201 if(oDelimPos.pos == 0) 202 { 203 if(oDelim.isShort()) 204 { 205 if(!prev_temple_str.validBeforeShort()) 206 { 207 // Chars before % weren't all whitespace, assume it's part of a 208 // string literal. 209 push_linenum(); 210 push_string_literal(temple_str[0..oDelim.toString().length]); 211 prev_temple_str.munchHeadOf(temple_str, oDelim.toString().length); 212 continue; 213 } 214 } 215 216 // If we made it this far, we've got valid open/close delims 217 DelimPos!(CloseDelim)* cDelimPos = temple_str.nextDelim([cDelim]); 218 if(cDelimPos is null) 219 { 220 if(oDelim.isShort()) 221 { 222 // don't require a short close delim at the end of the template 223 temple_str ~= cDelim.toString(); 224 cDelimPos = enforce(temple_str.nextDelim([cDelim])); 225 } 226 else 227 { 228 assert(false, "Missing close delimer: " ~ cDelim.toString()); 229 } 230 } 231 232 // Made it this far, we've got the position of the close delimer. 233 push_linenum(); 234 235 // Get a slice to the content between the delimers 236 immutable string inbetween_delims = 237 temple_str[oDelim.toString().length .. cDelimPos.pos]; 238 239 // track block starts 240 immutable bool is_block_start = inbetween_delims.isBlockStart(); 241 immutable bool is_block_end = inbetween_delims.isBlockEnd(); 242 243 // Invariant 244 assert(!(is_block_start && is_block_end), "Internal bug: " ~ inbetween_delims); 245 246 if(is_block_start) 247 { 248 sawBlockStart(oDelim.isStr()); 249 } 250 251 if(oDelim.isStr()) 252 { 253 // Check if this is a block; in that case, put the block's 254 // contents into a temporary variable, then render that 255 // variable after the block close delim 256 257 // The line would look like: 258 // <%= capture(() { %> 259 // <% }); %> 260 // so look for something like "){" or ") {" at the end 261 262 if(is_block_start) 263 { 264 string tmp_name = temp_var_names.pushNew(); 265 push_stmt(`auto %s = %s`.format(tmp_name, inbetween_delims)); 266 indent(); 267 } 268 else 269 { 270 push_expr(inbetween_delims); 271 272 if(cDelim == CloseDelim.CloseShort) 273 { 274 push_stmt(`__temple_buff_filtered_put("\n");`); 275 } 276 } 277 } 278 else 279 { 280 // It's just raw code, push it into the function body 281 push_stmt(inbetween_delims); 282 283 // Check if the code looks like the ending to a block; 284 // e.g. for block: 285 // <%= capture(() { %> 286 // <% }, "foo"); %>` 287 // look for it starting with }<something>); 288 // If it does, output the last tmp buffer var on the stack 289 if(is_block_end && !temp_var_names.empty) 290 { 291 292 // the block at this level should be printed 293 if(sawBlockEnd()) 294 { 295 outdent(); 296 push_stmt(`__temple_context.put(%s);`.format(temp_var_names.pop())); 297 } 298 } 299 } 300 301 // remove up to the closing delimer 302 prev_temple_str.munchHeadOf( 303 temple_str, 304 cDelimPos.pos + cDelim.toString().length); 305 } 306 else 307 { 308 // Move ahead to the next open delimer, rendering 309 // everything between here and there as a string literal 310 push_linenum(); 311 immutable delim_pos = oDelimPos.pos; 312 push_string_literal(temple_str[0..delim_pos]); 313 prev_temple_str.munchHeadOf(temple_str, delim_pos); 314 } 315 } 316 317 // count the number of newlines in the previous part of the template; 318 // that's the current line number 319 line_number = prev_temple_str.count('\n'); 320 } 321 322 outdent(); 323 push_stmt("}"); 324 outdent(); 325 push_stmt("}"); 326 327 328 return buildFromParts(func_parts); 329 } 330 331 private: 332 333 string build_function_head(string filter_ident) { 334 string ret = ""; 335 336 string function_type_params = 337 filter_ident.length ? 338 "(%s)".format(filter_ident) : 339 "" ; 340 341 ret ~= ( 342 `static void TempleFunc%s(TempleContext __temple_context) {` 343 .format(function_type_params)); 344 345 // This isn't just an overload of __temple_buff_filtered_put because D doesn't allow 346 // overloading of nested functions 347 ret ~= ` 348 // Ensure that __temple_context is never null 349 assert(__temple_context); 350 351 void __temple_put_expr(T)(T expr) { 352 353 // TempleInputStream should never be passed through 354 // a filter; it should be directly appended to the stream 355 static if(is(typeof(expr) == TempleInputStream)) 356 { 357 expr.into(__temple_context.sink); 358 } 359 360 // But other content should be filtered 361 else 362 { 363 __temple_buff_filtered_put(expr); 364 } 365 } 366 367 deprecated auto renderWith(string __temple_file)(TempleContext tc = null) 368 { 369 return render_with!__temple_file(tc); 370 } 371 TempleInputStream render(string __temple_file)() { 372 return render_with!__temple_file(__temple_context); 373 } 374 `; 375 376 // Is the template using a filter? 377 if(filter_ident.length) 378 { 379 ret ~= ` 380 /// Run 'thing' through the Filter's templeFilter static 381 void __temple_buff_filtered_put(T)(T thing) 382 { 383 static if(__traits(compiles, __fp__.templeFilter(__temple_context.sink, thing))) 384 { 385 pragma(msg, "Deprecated: templeFilter on filters is deprecated; please use temple_filter"); 386 __fp__.templeFilter(__temple_context.sink, thing); 387 } 388 else static if(__traits(compiles, __fp__.templeFilter(thing))) 389 { 390 pragma(msg, "Deprecated: templeFilter on filters is deprecated; please use temple_filter"); 391 __temple_context.put(__fp__.templeFilter(thing)); 392 } 393 else static if(__traits(compiles, __fp__.temple_filter(__temple_context.sink, thing))) { 394 __fp__.temple_filter(__temple_context.sink, thing); 395 } 396 else static if(__traits(compiles, __fp__.temple_filter(thing))) 397 { 398 __temple_context.put(__fp__.temple_filter(thing)); 399 } 400 else { 401 // Fall back to templeFilter returning a string 402 static assert(false, "Filter does not have a case that accepts a " ~ T.stringof); 403 } 404 } 405 406 /// with filter, render subtemplate with an explicit context (which defaults to null) 407 TempleInputStream render_with(string __temple_file)(TempleContext tc = null) 408 { 409 return TempleInputStream(delegate(ref TempleOutputStream s) { 410 auto nested = compile_temple_file!(__temple_file, __fp__)(); 411 nested.render(s, tc); 412 }); 413 } 414 `.replace("__fp__", filter_ident); 415 } 416 else 417 { 418 // No filter means just directly append the thing to the 419 // buffer, converting it to a string if needed 420 ret ~= ` 421 void __temple_buff_filtered_put(T)(T thing) 422 { 423 __temple_context.put(std.conv.to!string(thing)); 424 } 425 426 /// without filter, render subtemplate with an explicit context (which defaults to null) 427 TempleInputStream render_with(string __temple_file)(TempleContext tc = null) 428 { 429 return TempleInputStream(delegate(ref TempleOutputStream s) { 430 auto nested = compile_temple_file!(__temple_file)(); 431 nested.render(s, tc); 432 }); 433 } 434 `; 435 } 436 437 return ret; 438 } 439 440 string buildFromParts(in FuncPart[] parts) { 441 string func_str = ""; 442 443 foreach(index, immutable part; parts) { 444 string indent() { 445 string ret = ""; 446 for(int i = 0; i < part.indent; i++) 447 ret ~= '\t'; 448 return ret; 449 } 450 451 func_str ~= indent(); 452 453 final switch(part.type) 454 with(FuncPart.Type) { 455 case Stmt: 456 case Line: 457 func_str ~= part.value; 458 break; 459 460 case Expr: 461 func_str ~= "__temple_put_expr(" ~ part.value.strip ~ ");\n"; 462 break; 463 464 case StrLit: 465 if(index > 1 && (index + 2) < parts.length) { 466 // look ahead/behind 2 because the generator inserts 467 // #line annotations after each statement/expr/literal 468 immutable prev_type = parts[index-2].type; 469 immutable next_type = parts[index+2].type; 470 471 // if the previous and next parts are statements, and this part is all 472 // whitespace, skip inserting it into the template 473 if( 474 prev_type == FuncPart.Type.Stmt && 475 next_type == FuncPart.Type.Stmt && 476 part.value.all!((chr) => chr.isWhite())) 477 { 478 break; 479 } 480 } 481 482 func_str ~= `__temple_context.put("` ~ part.value.replace("\n", "\\n").escapeQuotes() ~ "\");\n"; 483 break; 484 } 485 } 486 487 return func_str; 488 }