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 }