1 /** 2 * Copyright © DiamondMVC 2018 3 * License: MIT (https://github.com/DiamondMVC/Diamond/blob/master/LICENSE) 4 * Author: Jacob Jensen (bausshf) 5 */ 6 module diamond.templates.parser; 7 8 import std.conv : to; 9 import std.algorithm : count; 10 import std..string : indexOf; 11 12 import diamond.templates.grammar; 13 import diamond.templates.contentmode; 14 import diamond.templates.characterincludemode; 15 import diamond.templates.part; 16 17 private 18 { 19 // HACK: to make AA's with classes work at compile-time. 20 @property grammars() 21 { 22 Grammar[char] grammars; 23 grammars['['] = new Grammar( 24 "metadata", '[', ']', 25 ContentMode.metaContent, CharacterIncludeMode.none, false, false 26 ); 27 28 grammars['<'] = new Grammar( 29 "placeHolder", '<', '>', 30 ContentMode.appendContentPlaceHolder, CharacterIncludeMode.none, false, false 31 ); 32 33 grammars['{'] = new Grammar( 34 "code", '{', '}', 35 ContentMode.mixinContent, CharacterIncludeMode.none, false, false 36 ); 37 38 grammars[':'] = new Grammar( 39 "expression", ':', '\n', 40 ContentMode.mixinContent, CharacterIncludeMode.none, false, true 41 ); 42 43 grammars['='] = new Grammar( 44 "expressionValue", '=', ';', 45 ContentMode.appendContent, CharacterIncludeMode.none, false, true 46 ); 47 48 grammars['('] = new Grammar( 49 "escapedValue", '(', ')', 50 ContentMode.appendContent, CharacterIncludeMode.none, false, false 51 ); 52 53 grammars['$'] = new Grammar( 54 "expressionEscaped", '$', ';', 55 ContentMode.appendContent, CharacterIncludeMode.none, false, false, 56 '=' // Character that must follow the first character after @ 57 ); 58 59 grammars['*'] = new Grammar( 60 "comment", '*', '*', 61 ContentMode.discardContent, CharacterIncludeMode.none, false, true 62 ); 63 64 grammars['!'] = new Grammar( 65 "section", '!', ':', 66 ContentMode.discardContent, CharacterIncludeMode.none, false, true 67 ); 68 69 import diamond.extensions; 70 mixin ExtensionEmit!(ExtensionType.customGrammar, q{ 71 Grammar[char] customGrammars = {{extensionEntry}}.createGrammars(); 72 73 if (customGrammars) 74 { 75 foreach (key,value; customGrammars) 76 { 77 grammars[key] = value; 78 } 79 } 80 }); 81 emitExtension(); 82 83 return grammars; 84 } 85 } 86 87 /** 88 * Parses a diamond template. 89 * Params: 90 * content = The content of the diamond template to parse. 91 * Returns: 92 * An associative array of arrays holding the section's template parts. 93 */ 94 auto parseTemplate(string content) 95 { 96 Part[][string] parts; 97 98 auto current = new Part; 99 size_t curlyBracketCount = 0; 100 size_t squareBracketcount = 0; 101 size_t parenthesisCount = 0; 102 string currentSection = ""; 103 104 foreach (ref i; 0 .. content.length) 105 { 106 auto beforeChar = i > 0 ? content[i - 1] : '\0'; 107 auto currentChar = content[i]; 108 auto afterChar = i < (content.length - 1) ? content[i + 1] : '\0'; 109 auto beforeSecondaryChar = i > 1 ? content[i - 2] : '\0'; 110 111 if (currentChar == '@' && !current.currentGrammar) 112 { 113 if (current._content && current._content.length && afterChar != '.') 114 { 115 parts[currentSection] ~= current; 116 current = new Part; 117 } 118 119 if (afterChar != '@' && afterChar != '.') 120 { 121 auto grammar = grammars.get(afterChar, null); 122 123 if (grammar && beforeChar != '@') 124 { 125 current.currentGrammar = grammar; 126 127 if (afterChar == ':') 128 { 129 auto searchSource = content[i .. $]; 130 searchSource = searchSource[0 .. searchSource.indexOf('\n')]; 131 132 curlyBracketCount += searchSource.count!(c => c == '{'); 133 squareBracketcount += searchSource.count!(c => c == '['); 134 parenthesisCount += searchSource.count!(c => c == '('); 135 136 curlyBracketCount -= searchSource.count!(c => c == '}'); 137 squareBracketcount -= searchSource.count!(c => c == ']'); 138 parenthesisCount -= searchSource.count!(c => c == ')'); 139 } 140 } 141 else 142 { 143 current._content ~= currentChar; 144 } 145 } 146 else if (afterChar == '.') 147 { 148 current._content ~= currentChar; 149 } 150 } 151 else 152 { 153 if (current.currentGrammar) 154 { 155 if (current.currentGrammar.mandatoryStartCharacter != '\0' && 156 beforeSecondaryChar == '@' && 157 beforeChar == current.currentGrammar.startCharacter && 158 currentChar == current.currentGrammar.mandatoryStartCharacter) 159 { 160 continue; 161 } 162 163 if (currentChar == current.currentGrammar.startCharacter && 164 (!current.currentGrammar.ignoreDepth || !current.isStart()) 165 ) 166 { 167 current.increaseSeekIndex(); 168 169 if (current.isStart()) 170 { 171 continue; 172 } 173 } 174 else if (currentChar == current.currentGrammar.endCharacter) 175 { 176 current.decreaseSeekIndex(); 177 } 178 } 179 180 if (current.isEnd(currentChar)) 181 { 182 switch (current.currentGrammar.characterIncludeMode) 183 { 184 case CharacterIncludeMode.start: 185 current._content = 186 to!string(current.currentGrammar.startCharacter) 187 ~ current.content; 188 break; 189 190 case CharacterIncludeMode.end: 191 current._content ~= current.currentGrammar.endCharacter; 192 break; 193 194 case CharacterIncludeMode.both: 195 current._content = 196 to!string(current.currentGrammar.startCharacter) ~ 197 current.content ~ to!string(current.currentGrammar.endCharacter); 198 break; 199 200 default: break; 201 } 202 203 if (current.currentGrammar && 204 current.currentGrammar.includeStatementCharacter) 205 { 206 current._content = "@" ~ current.content; 207 } 208 209 if (current._currentGrammar.name == "section") 210 { 211 import std..string : strip; 212 213 auto sectionName = current.content ? current.content.strip() : ""; 214 215 currentSection = sectionName; 216 } 217 else 218 { 219 parts[currentSection] ~= current; 220 } 221 222 current = new Part; 223 } 224 else 225 { 226 // TODO: Simplify this ... 227 if (curlyBracketCount && currentChar == '}') 228 { 229 curlyBracketCount--; 230 231 parts[currentSection] ~= current; 232 233 current = new Part; 234 current.currentGrammar = grammars.get('{', null); 235 current._content = "}"; 236 237 if (afterChar == ';') 238 { 239 current._content ~= ";"; 240 i++; 241 } 242 243 parts[currentSection] ~= current; 244 245 current = new Part; 246 } 247 else if (squareBracketcount && currentChar == ']') 248 { 249 squareBracketcount--; 250 251 parts[currentSection] ~= current; 252 253 current = new Part; 254 current.currentGrammar = grammars.get('{', null); 255 current._content = "]"; 256 257 if (afterChar == ';') 258 { 259 current._content ~= ";"; 260 i++; 261 } 262 263 parts[currentSection] ~= current; 264 265 current = new Part; 266 } 267 else if (parenthesisCount && currentChar == ')') 268 { 269 parenthesisCount--; 270 271 parts[currentSection] ~= current; 272 273 current = new Part; 274 current.currentGrammar = grammars.get('{', null); 275 current._content = ")"; 276 277 if (afterChar == ';') 278 { 279 current._content ~= ";"; 280 i++; 281 } 282 283 parts[currentSection] ~= current; 284 285 current = new Part; 286 } 287 else 288 { 289 current._content ~= currentChar; 290 } 291 } 292 } 293 } 294 295 parts[currentSection] ~= current; 296 297 return parts; 298 }