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.temple, 24 temple.util, 25 temple.delims, 26 std.conv, 27 std.string, 28 std.array, 29 std.exception; 30 31 /** 32 * Stack and generator for unique temporary variable names 33 */ 34 private static struct TempBufferNameStack 35 { 36 private: 37 const string base; 38 uint counter = 0; 39 string[] stack; 40 41 public: 42 this(string base) 43 { 44 this.base = base; 45 } 46 47 /** 48 * getNew 49 * Gets a new unique buffer variable name 50 */ 51 string pushNew() 52 { 53 auto name = base ~ counter.to!string; 54 counter++; 55 stack ~= name; 56 return name; 57 } 58 59 /** 60 * Pops the topmost unique variable name off the stack 61 */ 62 string pop() 63 { 64 auto ret = stack[$-1]; 65 stack.length--; 66 return ret; 67 } 68 69 /** 70 * Checks if there are any names to pop off 71 */ 72 bool empty() 73 { 74 return !stack.length; 75 } 76 } 77 78 /** 79 * __temple_gen_temple_func_string 80 * Generates the function string to be mixed into a template which renders 81 * a temple file. 82 */ 83 package string __temple_gen_temple_func_string( 84 string temple_str, string temple_name, string filter_ident = "") 85 { 86 // Output function string being composed 87 auto function_str = ""; 88 89 // Indendation level for a line being pushed 90 auto indent_level = 0; 91 92 // Current line number in the temple_str being scanned 93 size_t line_number = 0; 94 95 void push_line(string[] stmts...) 96 { 97 foreach(i; 0..indent_level) 98 { 99 function_str ~= '\t'; 100 } 101 foreach(stmt; stmts) 102 { 103 function_str ~= stmt; 104 } 105 function_str ~= '\n'; 106 } 107 108 void push_linenum() 109 { 110 push_line(`#line ` ~ (line_number + 1).to!string ~ ` "` ~ temple_name ~ `"`); 111 } 112 113 void push_string_literal(string str) 114 { 115 if(str.length == 0) 116 return; 117 118 push_line(`__temple_buff.put("` ~ str.escapeQuotes() ~ `");`); 119 } 120 121 void indent() { indent_level++; } 122 void outdent() { indent_level--; } 123 124 125 auto temp_var_names = TempBufferNameStack("__temple_capture_var_"); 126 127 // Tracks if the block that the parser has just 128 // finished processing should be printed (e.g., is 129 // it the block who's contents are assigned to the last tmp_buffer_var) 130 bool[] printStartBlockTracker; 131 void sawBlockStart(bool will_be_printed) 132 { 133 printStartBlockTracker ~= will_be_printed; 134 } 135 bool sawBlockEnd() 136 { 137 auto will_be_printed = printStartBlockTracker[$-1]; 138 printStartBlockTracker.length--; 139 return will_be_printed; 140 } 141 142 string function_type_params = ""; 143 if(filter_ident.length) 144 { 145 function_type_params = "(%s)".format(filter_ident); 146 } 147 push_line(`static void TempleFunc%s(OutputStream __temple_buff, TempleContext __temple_context = null) {`.format(function_type_params)); 148 149 // This isn't just an overload of __templeBuffFilteredPut because D doesn't allow 150 // overloading of nested functions 151 push_line(q{ 152 void __templeBuffPutStream(AppenderOutputStream os) 153 { 154 __temple_buff.put(os.data); 155 } 156 157 }); 158 159 push_line(q{ 160 /// Calls renderWith, with the current Temple context 161 AppenderOutputStream render(string __temple_file)() 162 { 163 return renderWith!__temple_file(__temple_context); 164 } 165 }); 166 167 // Is the template using a filter? 168 if(filter_ident.length) 169 { 170 push_line(q{ 171 /// Run 'thing' through the Filter's templeFilter static 172 void __templeBuffFilteredPut(T)(T thing) 173 { 174 static if(__traits(compiles, __fp__.templeFilter(cast(OutputStream) __temple_buff, thing))) { 175 // The filter defines a method that takes an OutputBuffer, 176 // prefer that to appending an entire string 177 __fp__.templeFilter(__temple_buff, thing); 178 } 179 else { 180 // Fall back to templeFilter returning a string 181 __temple_buff.put( __fp__.templeFilter(thing) ); 182 } 183 } 184 185 /// Renders a subtemplate here with an explicitly defined context 186 /// By default, the context is null, so a blank context will be 187 /// used to render the nested template 188 AppenderOutputStream renderWith(string __temple_file)(TempleContext __ctx = null) 189 { 190 alias __temple_render_func = TempleFile!(__temple_file, __fp__); 191 return __temple_context.__templeRenderWith(&__temple_render_func, __ctx); 192 } 193 194 }.replace("__fp__", filter_ident)); 195 } 196 else 197 { 198 // No filter means just directly append the thing to the 199 // buffer, converting it to a string if needed 200 push_line(q{ 201 void __templeBuffFilteredPut(T)(T thing) 202 { 203 __temple_buff.put(.std.conv.to!string(thing)); 204 } 205 206 /// Same as the renderWith when a filter is given, just 207 /// without the filter 208 AppenderOutputStream renderWith(string __temple_file)(TempleContext __ctx = null) 209 { 210 alias __temple_render_func = TempleFile!__temple_file; 211 return __temple_context.__templeRenderWith(&__temple_render_func, __ctx); 212 } 213 }); 214 } 215 216 push_line(q{ 217 // Ensure that __temple_context is never null 218 if(__temple_context is null) 219 { 220 __temple_context = new TempleContext(); 221 } 222 223 // A stack of the current temple buffers, used to render nested 224 // templates with 225 OutputStream[] __temple_buffers; 226 void __pushBuff(OutputStream __new_buff) 227 { 228 __temple_buffers ~= __temple_buff; 229 __temple_buff = __new_buff; 230 } 231 232 void __popBuff() 233 { 234 __temple_buff = __temple_buffers[$-1]; 235 __temple_buffers.length--; 236 } 237 238 // Push this template's hooks to the current context 239 __temple_context.__templePushHooks(&__pushBuff, &__popBuff); 240 scope(exit) { __temple_context.__templePopHooks(); } 241 }); 242 243 indent(); 244 if(filter_ident.length) 245 { 246 push_line(`with(%s)`.format(filter_ident)); 247 } 248 push_line(`with(__temple_context) {`); 249 indent(); 250 251 // Keeps infinite loops from outright crashing the compiler 252 // The limit should be set to some arbitrary large number 253 uint safeswitch = 0; 254 255 string prevTempl = ""; 256 257 while(temple_str.length) 258 { 259 // This imposes the limiatation of a max of 10_000 delimers parsed for 260 // a template function. Probably will never ever hit this in a single 261 // template file without running out of compiler memory 262 if(safeswitch++ > 10_000) 263 { 264 assert(false, "nesting level too deep; throwing saftey switch: \n" ~ temple_str); 265 } 266 267 DelimPos!(OpenDelim)* oDelimPos = temple_str.nextDelim(OpenDelims); 268 269 if(oDelimPos is null) 270 { 271 //No more delims; append the rest as a string 272 push_linenum(); 273 push_string_literal(temple_str); 274 prevTempl.munchHeadOf(temple_str, temple_str.length); 275 } 276 else 277 { 278 immutable OpenDelim oDelim = oDelimPos.delim; 279 immutable CloseDelim cDelim = OpenToClose[oDelim]; 280 281 if(oDelimPos.pos == 0) 282 { 283 if(oDelim.isShort()) 284 { 285 if(!prevTempl.validBeforeShort()) 286 { 287 // Chars before % weren't all whitespace, assume it's part of a 288 // string literal. 289 push_linenum(); 290 push_string_literal(temple_str[0..oDelim.toString().length]); 291 prevTempl.munchHeadOf(temple_str, oDelim.toString().length); 292 continue; 293 } 294 } 295 296 // If we made it this far, we've got valid open/close delims 297 auto cDelimPos = temple_str.nextDelim([cDelim]); 298 if(cDelimPos is null) 299 { 300 if(oDelim.isShort()) 301 { 302 // don't require a short close delim at the end of the template 303 temple_str ~= cDelim.toString(); 304 cDelimPos = enforce(temple_str.nextDelim([cDelim])); 305 } 306 else 307 { 308 assert(false, "Missing close delimer: " ~ cDelim.toString()); 309 } 310 } 311 312 // Made it this far, we've got the position of the close delimer. 313 push_linenum(); 314 315 // Get a slice to the content between the delimers 316 immutable string inbetween_delims = 317 temple_str[oDelim.toString().length .. cDelimPos.pos]; 318 319 // track block starts 320 immutable bool is_block_start = inbetween_delims.isBlockStart(); 321 immutable bool is_block_end = inbetween_delims.isBlockEnd(); 322 323 // Invariant 324 assert(!(is_block_start && is_block_end), "Internal bug: " ~ inbetween_delims); 325 326 if(is_block_start) 327 { 328 sawBlockStart(oDelim.isStr()); 329 } 330 331 if(oDelim.isStr()) 332 { 333 // Check if this is a block; in that case, put the block's 334 // contents into a temporary variable, then render that 335 // variable after the block close delim 336 337 // The line would look like: 338 // <%= capture(() { %> 339 // <% }); %> 340 // so look for something like "){" or ") {" at the end 341 342 if(is_block_start) 343 { 344 string tmp_name = temp_var_names.pushNew(); 345 push_line(`auto %s = %s`.format(tmp_name, inbetween_delims)); 346 indent(); 347 } 348 else 349 { 350 push_line(q{ 351 // AppenderOuputStream should never be passed through 352 // a filter; it should be directly appended to the stream 353 static if(is(typeof(__expr__) == AppenderOutputStream)) 354 { 355 __templeBuffPutStream(__expr__); 356 } 357 358 // But other content should be filtered 359 else 360 { 361 __templeBuffFilteredPut(__expr__); 362 } 363 }.replace("__expr__", inbetween_delims)); 364 365 if(cDelim == CloseDelim.CloseShort) 366 { 367 push_line(`__templeBuffFilteredPut("\n");`); 368 } 369 } 370 } 371 else 372 { 373 // It's just raw code, push it into the function body 374 push_line(inbetween_delims); 375 376 // Check if the code looks like the ending to a block; 377 // e.g. for block: 378 // <%= capture(() { %> 379 // <% }, "foo"); %>` 380 // look for it starting with }<something>); 381 // If it does, output the last tmp buffer var on the stack 382 if(is_block_end && !temp_var_names.empty) 383 { 384 385 // the block at this level should be printed 386 if(sawBlockEnd()) 387 { 388 outdent(); 389 push_line(`__temple_buff.put(%s);`.format(temp_var_names.pop())); 390 } 391 } 392 } 393 394 // remove up to the closing delimer 395 prevTempl.munchHeadOf( 396 temple_str, 397 cDelimPos.pos + cDelim.toString().length); 398 } 399 else 400 { 401 // Move ahead to the next open delimer, rendering 402 // everything between here and there as a string literal 403 push_linenum(); 404 immutable delim_pos = oDelimPos.pos; 405 push_string_literal(temple_str[0..delim_pos]); 406 prevTempl.munchHeadOf(temple_str, delim_pos); 407 } 408 } 409 410 // count the number of newlines in the previous part of the template; 411 // that's the current line number 412 line_number = prevTempl.count('\n'); 413 } 414 415 outdent(); 416 push_line("}"); 417 outdent(); 418 push_line("}"); 419 420 return function_str; 421 }