1 /** 2 * Copyright © DiamondMVC 2019 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 "expressionEscaped", '=', ';', 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 "expressionValue", '$', ';', 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 100 size_t curlyBracketCount = 0; 101 size_t squareBracketcount = 0; 102 size_t parenthesisCount = 0; 103 string currentSection = ""; 104 105 foreach (ref i; 0 .. content.length) 106 { 107 auto beforeChar = i > 0 ? content[i - 1] : '\0'; 108 auto currentChar = content[i]; 109 auto afterChar = i < (content.length - 1) ? content[i + 1] : '\0'; 110 auto beforeSecondaryChar = i > 1 ? content[i - 2] : '\0'; 111 112 if (currentChar == '@' && !current.currentGrammar) 113 { 114 if (current._content && current._content.length && afterChar != '.') 115 { 116 parts[currentSection] ~= current; 117 current = new Part; 118 } 119 120 if (afterChar != '@' && afterChar != '.') 121 { 122 auto grammar = grammars.get(afterChar, null); 123 124 if (grammar && beforeChar != '@') 125 { 126 current.currentGrammar = grammar; 127 128 if (afterChar == ':') 129 { 130 auto searchSource = content[i .. $]; 131 searchSource = searchSource[0 .. searchSource.indexOf('\n')]; 132 133 curlyBracketCount += searchSource.count!(c => c == '{'); 134 squareBracketcount += searchSource.count!(c => c == '['); 135 parenthesisCount += searchSource.count!(c => c == '('); 136 137 curlyBracketCount -= searchSource.count!(c => c == '}'); 138 squareBracketcount -= searchSource.count!(c => c == ']'); 139 parenthesisCount -= searchSource.count!(c => c == ')'); 140 } 141 } 142 else 143 { 144 current._content ~= currentChar; 145 } 146 } 147 else if (afterChar == '.') 148 { 149 current._content ~= currentChar; 150 } 151 } 152 else 153 { 154 if (current.currentGrammar) 155 { 156 if 157 ( 158 current.currentGrammar.mandatoryStartCharacter != '\0' && 159 beforeSecondaryChar == '@' && 160 beforeChar == current.currentGrammar.startCharacter && 161 currentChar == current.currentGrammar.mandatoryStartCharacter 162 ) 163 { 164 continue; 165 } 166 167 if 168 ( 169 currentChar == current.currentGrammar.startCharacter && 170 (!current.currentGrammar.ignoreDepth || !current.isStart()) 171 ) 172 { 173 current.increaseSeekIndex(); 174 175 if (current.isStart()) 176 { 177 continue; 178 } 179 } 180 else if (currentChar == current.currentGrammar.endCharacter) 181 { 182 current.decreaseSeekIndex(); 183 } 184 } 185 186 if (current.isEnd(currentChar)) 187 { 188 switch (current.currentGrammar.characterIncludeMode) 189 { 190 case CharacterIncludeMode.start: 191 current._content = 192 to!string(current.currentGrammar.startCharacter) 193 ~ current.content; 194 break; 195 196 case CharacterIncludeMode.end: 197 current._content ~= current.currentGrammar.endCharacter; 198 break; 199 200 case CharacterIncludeMode.both: 201 current._content = 202 to!string(current.currentGrammar.startCharacter) ~ 203 current.content ~ to!string(current.currentGrammar.endCharacter); 204 break; 205 206 default: break; 207 } 208 209 if (current.currentGrammar && 210 current.currentGrammar.includeStatementCharacter) 211 { 212 current._content = "@" ~ current.content; 213 } 214 215 if (current._currentGrammar.name == "section") 216 { 217 import std..string : strip; 218 219 auto sectionName = current.content ? current.content.strip() : ""; 220 221 currentSection = sectionName; 222 } 223 else 224 { 225 parts[currentSection] ~= current; 226 } 227 228 current = new Part; 229 } 230 else 231 { 232 // TODO: Simplify this ... 233 if (curlyBracketCount && currentChar == '}') 234 { 235 curlyBracketCount--; 236 237 parts[currentSection] ~= current; 238 239 current = new Part; 240 current.currentGrammar = grammars.get('{', null); 241 current._content = "}"; 242 243 if (afterChar == ';') 244 { 245 current._content ~= ";"; 246 i++; 247 } 248 249 parts[currentSection] ~= current; 250 251 current = new Part; 252 } 253 else if (squareBracketcount && currentChar == ']') 254 { 255 squareBracketcount--; 256 257 parts[currentSection] ~= current; 258 259 current = new Part; 260 current.currentGrammar = grammars.get('{', null); 261 current._content = "]"; 262 263 if (afterChar == ';') 264 { 265 current._content ~= ";"; 266 i++; 267 } 268 269 parts[currentSection] ~= current; 270 271 current = new Part; 272 } 273 else if (parenthesisCount && currentChar == ')') 274 { 275 parenthesisCount--; 276 277 parts[currentSection] ~= current; 278 279 current = new Part; 280 current.currentGrammar = grammars.get('{', null); 281 current._content = ")"; 282 283 if (afterChar == ';') 284 { 285 current._content ~= ";"; 286 i++; 287 } 288 289 parts[currentSection] ~= current; 290 291 current = new Part; 292 } 293 else 294 { 295 current._content ~= currentChar; 296 } 297 } 298 } 299 } 300 301 parts[currentSection] ~= current; 302 303 return parts; 304 }