Line data Source code
1 : /**
2 : Copyright (c) 2024 Stappler LLC <admin@stappler.dev>
3 :
4 : Permission is hereby granted, free of charge, to any person obtaining a copy
5 : of this software and associated documentation files (the "Software"), to deal
6 : in the Software without restriction, including without limitation the rights
7 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 : copies of the Software, and to permit persons to whom the Software is
9 : furnished to do so, subject to the following conditions:
10 :
11 : The above copyright notice and this permission notice shall be included in
12 : all copies or substantial portions of the Software.
13 :
14 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 : THE SOFTWARE.
21 : **/
22 :
23 : #include "SPPugLexer.h"
24 : #include "SPPugExpression.h"
25 :
26 : namespace STAPPLER_VERSIONIZED stappler::pug {
27 :
28 525 : Lexer::Lexer(const StringView &str, const OutStream &err)
29 525 : : content(str), root(Token::Root, content) {
30 525 : success = perform(err);
31 525 : }
32 :
33 525 : bool Lexer::perform(const OutStream &OutStream) {
34 525 : return parseToken(OutStream, root);
35 : }
36 :
37 20775 : static uint32_t checkIndent(uint32_t &indent, StringView &str) {
38 20775 : auto indentStr = str.readChars<StringView::Chars<'\t', ' '>>();
39 20775 : if (!indentStr.empty()) {
40 13775 : if (indent == maxOf<uint32_t>()) {
41 1425 : if (indentStr.is('\t')) {
42 1400 : indent = 0;
43 : } else {
44 25 : indentStr = indentStr.readUntil<StringView::Chars<'\t'>>();
45 25 : indent = indentStr.size();
46 : }
47 1425 : return 1;
48 12350 : } else if (indent == 0) {
49 12300 : auto indentStrTmp = indentStr.readChars<StringView::Chars<'\t'>>();
50 12300 : if (!indentStr.empty()) {
51 25 : return maxOf<uint32_t>();
52 : }
53 12275 : return indentStrTmp.size();
54 : } else {
55 50 : auto indentStrTmp = indentStr.readChars<StringView::Chars<' '>>();
56 50 : if (!indentStr.empty()) {
57 0 : return maxOf<uint32_t>();
58 : }
59 50 : return indentStrTmp.size() / indent;
60 : }
61 : }
62 7000 : return 0;
63 : }
64 :
65 : using TagWordFilter = StringView::Compose<StringView::CharGroup<CharGroupId::Alphanumeric>, StringView::Chars<'-', '_'>>;
66 : using AttrWordFilter = StringView::Compose<StringView::CharGroup<CharGroupId::Alphanumeric>, StringView::Chars<'@', '-', '_', ':', '(', ')', '.'>>;
67 : using SpacingFilter = StringView::Chars<' ', '\t'>;
68 : using NewLineFilter = StringView::Chars<'\n', '\r'>;
69 :
70 525 : bool Lexer::parseToken(const OutStream &OutStream, Token &tok) {
71 525 : StringView r = tok.data;
72 :
73 525 : Token *currentTok = &tok;
74 :
75 : std::array<Token *, 32> stack;
76 :
77 21450 : while (!r.empty()) {
78 20975 : StringView tmp(r);
79 20975 : auto indent = indentLevel;
80 20975 : bool followTag = false;
81 20975 : if (r.is(':')) {
82 200 : ++ r;
83 200 : r.skipChars<SpacingFilter>();
84 200 : followTag = true;
85 : } else {
86 20775 : indent = checkIndent(indentStep, r);
87 20775 : if (indent == 0 && !r.is<NewLineFilter>()) {
88 5975 : indentStep = maxOf<uint32_t>();
89 14800 : } else if (indent == maxOf<uint32_t>() && !r.is<NewLineFilter>() && !r.empty()) {
90 25 : return onError(OutStream, r, "Mixed tab and spaces indentations");
91 : }
92 : }
93 :
94 20950 : if (!r.is<NewLineFilter>() && !r.empty()) {
95 19875 : if (indent == indentLevel) {
96 : // do nothing
97 8650 : } else if (indent == indentLevel + 1) {
98 5825 : if (currentTok->tail) {
99 5825 : stack[indentLevel] = currentTok;
100 5825 : currentTok = currentTok->tail;
101 5825 : indentLevel = indent;
102 : } else {
103 0 : return false; // invalid indentation
104 : }
105 2825 : } else if (indent < indentLevel) {
106 2800 : currentTok = stack[indent];
107 2800 : indentLevel = indent;
108 : } else {
109 25 : return onError(OutStream, tmp, "Wrong indentation markup");
110 : }
111 :
112 19850 : if (auto line = readLine(OutStream, tmp, r, currentTok)) {
113 19850 : if (followTag) {
114 200 : currentTok->tail->addChild(line);
115 200 : currentTok = line;
116 200 : stack[indentLevel] = currentTok;
117 : } else {
118 19650 : currentTok->addChild(line);
119 : }
120 : } else {
121 0 : if (!r.is<StringView::Chars<'\n', '\r'>>() && !r.empty()) {
122 0 : return false;
123 : }
124 : }
125 : }
126 20925 : if (!r.is(':')) {
127 20725 : if (followTag) {
128 200 : currentTok = indentLevel > 0 ? stack[indentLevel - 1]->tail : root.tail;
129 : }
130 20725 : r.skipUntil<StringView::Chars<'\n'>>();
131 20725 : if (r.is('\n')) {
132 20600 : ++ r;
133 : }
134 : }
135 : }
136 475 : return true;
137 : }
138 :
139 2800 : bool Lexer::readAttributes(const OutStream &OutStream, Token *data, StringView &r) const {
140 2800 : auto attrs = new Token{Token::TagAttrList, r};
141 :
142 4450 : auto readAttrName = [] (StringView &r) -> Token * {
143 4450 : auto ret = new Token{Token::AttrName, r};
144 4450 : if (r.is('\'')) {
145 25 : ++ r;
146 75 : while (!r.empty() && !r.is('\'')) {
147 50 : auto str = r.readUntil<StringView::Chars<'\'', '\\'>>();
148 50 : if (r.is('\\')) {
149 25 : if (!str.empty()) {
150 25 : ret->addChild(new Token{Token::PlainText, str});
151 : }
152 25 : ++ r;
153 25 : if (!r.empty()) {
154 25 : ret->addChild(new Token{Token::PlainText, StringView(r, 1)});
155 25 : ++ r;
156 : }
157 25 : } else if (r.is('\'') && !str.empty()) {
158 25 : ret->addChild(new Token{Token::PlainText, str});
159 : }
160 : }
161 25 : if (r.is('\'')) {
162 25 : ++ r;
163 : } else {
164 0 : return nullptr;
165 : }
166 4425 : } else if (r.is('"')) {
167 25 : ++ r;
168 75 : while (!r.empty() && !r.is('"')) {
169 50 : auto str = r.readUntil<StringView::Chars<'"', '\\'>>();
170 50 : if (r.is('\\')) {
171 25 : if (!str.empty()) {
172 25 : ret->addChild(new Token{Token::PlainText, str});
173 : }
174 25 : ++ r;
175 25 : if (!r.empty()) {
176 25 : ret->addChild(new Token{Token::PlainText, StringView(r, 1)});
177 25 : ++ r;
178 : }
179 25 : } else if (r.is('"') && !str.empty()) {
180 25 : ret->addChild(new Token{Token::PlainText, str});
181 : }
182 : }
183 25 : if (r.is('"')) {
184 25 : ++ r;
185 : } else {
186 0 : return nullptr;
187 : }
188 : } else {
189 4400 : StringView tmp2(r);
190 4400 : auto str = tmp2.readChars<AttrWordFilter>();
191 4400 : if (!str.empty()) {
192 4400 : if (tmp2.empty() || (!tmp2.is('=') && !tmp2.is('!'))) {
193 50 : if (str.back() == ')') {
194 25 : str = StringView(str, str.size() - 1);
195 : }
196 : }
197 4400 : r += str.size();
198 4400 : ret->addChild(new Token{Token::PlainText, str});
199 : } else {
200 0 : return nullptr;
201 : }
202 : }
203 4450 : ret->data = StringView(ret->data, ret->data.size() - r.size());
204 4450 : return ret;
205 : };
206 :
207 2800 : r.skipChars<SpacingFilter, NewLineFilter>();
208 7250 : while (!r.is<NewLineFilter>() && !r.is(')') && !r.empty()) {
209 4450 : auto tok = new Token{Token::AttrPairEscaped, r};
210 4450 : auto name = readAttrName(r);
211 4450 : if (!name) {
212 0 : return onError(OutStream, r, StringView("Invalid attribute name"));
213 : }
214 :
215 4450 : if (r.is("!=")) {
216 50 : tok->type = Token::AttrPairUnescaped;
217 50 : r += 2;
218 4400 : } else if (r.is('=')) {
219 4350 : tok->type = Token::AttrPairEscaped;
220 4350 : ++ r;
221 50 : } else if (r.is<CharGroupId::WhiteSpace>() || r.is(',') || r.is(')')) {
222 50 : tok->addChild(name);
223 50 : tok->data = StringView(tok->data, tok->data.size() - r.size());
224 50 : r.skipChars<SpacingFilter, NewLineFilter>();
225 50 : if (r.is(',')) { ++ r; }
226 50 : r.skipChars<SpacingFilter, NewLineFilter>();
227 50 : attrs->addChild(tok);
228 50 : continue;
229 : } else {
230 0 : return onError(OutStream, r, StringView("Invalid attribute operator"));
231 : }
232 :
233 4400 : tok->addChild(name);
234 :
235 4400 : auto valTok = new Token{Token::AttrValue, r};
236 4400 : if (!readOutputExpression(valTok, r)) {
237 0 : return onError(OutStream, r, StringView("Invalid attribute value"));
238 : }
239 :
240 4400 : valTok->data = StringView(valTok->data, valTok->data.size() - r.size());
241 :
242 4400 : tok->addChild(valTok);
243 4400 : tok->data = StringView(tok->data, tok->data.size() - r.size());
244 :
245 4400 : attrs->addChild(tok);
246 :
247 4400 : r.skipChars<SpacingFilter, NewLineFilter>();
248 4400 : if (r.is(',')) { ++ r; }
249 4400 : r.skipChars<SpacingFilter, NewLineFilter>();
250 : }
251 :
252 2800 : if (!r.is(')')) {
253 0 : return onError(OutStream, r, StringView("Invalid attribute list"));
254 : } else {
255 2800 : attrs->data = StringView(attrs->data, attrs->data.size() - r.size());
256 2800 : ++ r;
257 : }
258 :
259 2800 : data->addChild(attrs);
260 2800 : data->data = StringView(data->data, data->data.size() - r.size());
261 :
262 2800 : return true;
263 : }
264 :
265 4400 : bool Lexer::readOutputExpression(Token *valTok, StringView &r) const {
266 4400 : if (auto expr = Expression::parse(r, Expression::Options::getDefaultInline())) {
267 4400 : valTok->expression = expr;
268 4400 : return true;
269 : }
270 :
271 0 : return false;
272 : }
273 :
274 7975 : bool Lexer::readTagInfo(const OutStream &OutStream, Token *data, StringView &r, bool interpolated) const {
275 14225 : while (r.is('.') || r.is('#') || r.is('(') || r.is('/') || r.is('=') || r.is('!') || r.is('&') || r.is(':')) {
276 9000 : if (r.is(':')) {
277 125 : return true;
278 : }
279 :
280 8875 : auto c = r[0]; ++ r;
281 8875 : switch (c) {
282 3075 : case '.': {
283 3075 : auto word = r.readChars<TagWordFilter>();
284 3075 : if (!word.empty()) {
285 2925 : data->addChild(new Token{Token::TagClassNote, word});
286 150 : } else if (r.is<NewLineFilter>()) {
287 150 : data->addChild(new Token{Token::TagTrailingDot, StringView()});
288 : }
289 3075 : break;
290 : }
291 475 : case '#': data->addChild(new Token{Token::TagIdNote, r.readChars<TagWordFilter>()}); break;
292 2800 : case '(':
293 2800 : if (!readAttributes(OutStream, data, r)) {
294 0 : return false; // wrong attribute format
295 : }
296 2800 : break;
297 50 : case '&':
298 50 : if (r.is("attributes")) {
299 50 : r += "attributes("_len;
300 50 : auto tmp = r;
301 50 : if (auto expr = Expression::parse(r, Expression::Options::getDefaultInline())) {
302 50 : if (r.is(')')) {
303 50 : ++ r;
304 50 : data->addChild(new Token{Token::TagAttrExpr, StringView(tmp, tmp.size() - r.size()), expr});
305 : } else {
306 0 : return onError(OutStream, r, "Invalid expression in &attributes");
307 : }
308 : } else {
309 0 : return onError(OutStream, r, "Invalid expression in &attributes");
310 : }
311 : } else {
312 0 : return onError(OutStream, r, "Unknown expression in tag");
313 : }
314 50 : break;
315 25 : case '/': data->addChild(new Token{Token::TagTrailingSlash, StringView()}); break;
316 2375 : case '=': data->addChild(new Token{Token::TagTrailingEq, StringView()}); break;
317 75 : case '!':
318 75 : if (r.is('=')) {
319 75 : ++ r;
320 75 : data->addChild(new Token{Token::TagTrailingNEq, StringView()});
321 : }
322 75 : break;
323 0 : default:
324 0 : break;
325 : }
326 :
327 8875 : if (data->tail->type == Token::TagTrailingSlash || data->tail->type == Token::TagTrailingDot) {
328 175 : r.skipChars<SpacingFilter>();
329 175 : if (!r.is<NewLineFilter>()) {
330 0 : return onError(OutStream, r, "Data after endline tag");
331 : }
332 175 : break;
333 8700 : } else if (data->tail->type == Token::TagTrailingEq || data->tail->type == Token::TagTrailingNEq) {
334 2450 : r.skipChars<SpacingFilter>();
335 2450 : if (r.is<NewLineFilter>() || (interpolated && r.is(']'))) {
336 0 : return true;
337 : }
338 2450 : auto tmp = r;
339 2450 : if (auto expr = Expression::parse(r, Expression::Options::getDefaultInline())) {
340 2450 : r.skipChars<SpacingFilter>();
341 2450 : if (r.is<NewLineFilter>() || r.empty() || (interpolated && r.is(']'))) {
342 2450 : data->addChild(new Token{data->tail->type == Token::TagTrailingEq ? Token::OutputEscaped : Token::OutputUnescaped,
343 2450 : StringView(tmp, tmp.size() - r.size()), expr});
344 2450 : return true;
345 : }
346 : }
347 0 : return onError(OutStream, r, "Invalid expression in tag attribute output block");
348 : }
349 : }
350 :
351 5400 : r.skipChars<SpacingFilter>();
352 5400 : if (!r.is<NewLineFilter>()) {
353 725 : return readPlainTextInterpolation(OutStream, data, r, interpolated);
354 : }
355 4675 : return true;
356 : };
357 :
358 3150 : bool Lexer::readCode(Token *data, StringView &r) const {
359 3150 : r.skipChars<SpacingFilter>();
360 6300 : while (!r.empty() && !r.is<NewLineFilter>()) {
361 3150 : auto tmp = r;
362 3150 : if (auto expr = Expression::parse(r, Expression::Options::getDefaultInline())) {
363 3150 : r.skipChars<SpacingFilter>();
364 3150 : if (r.is<NewLineFilter>() || r.is(';')) {
365 3150 : if (r.is(';')) { ++ r; }
366 : } else {
367 0 : return false;
368 : }
369 3150 : data->addChild(new Token{Token::Code, StringView(tmp, tmp.size() - r.size()), expr});
370 : } else {
371 0 : return false;
372 : }
373 : }
374 3150 : return true;
375 : }
376 :
377 25 : bool Lexer::readCodeBlock(Token *data, StringView &r) const {
378 25 : auto newlineTok = StringView(r).readChars<StringView::Chars<'\n', '\r', ' ', '\t'>>();
379 :
380 50 : while (r.is(';') || r.is(newlineTok)) {
381 25 : r += newlineTok.size();
382 :
383 25 : auto tmp = r;
384 25 : if (auto expr = Expression::parse(r, Expression::Options::getWithNewlineToken(newlineTok))) {
385 25 : r.skipChars<SpacingFilter>();
386 25 : if (!r.is<NewLineFilter>() && !r.is(';')) {
387 0 : return false;
388 : }
389 25 : data->addChild(new Token{Token::Code, StringView(tmp, tmp.size() - r.size()), expr});
390 : } else {
391 0 : return false;
392 : }
393 : }
394 :
395 25 : return true;
396 : }
397 :
398 3350 : bool Lexer::readPlainTextInterpolation(const OutStream &OutStream, Token *data, StringView &r, bool interpolated) const {
399 3350 : auto line = interpolated ? r : r.readUntil<NewLineFilter>();
400 3350 : StringView tmp(line);
401 :
402 3350 : StringView buf;
403 3725 : auto flushBuffer = [&] () {
404 3725 : if (!buf.empty()) {
405 3575 : data->addChild(new Token{Token::PlainText, buf});
406 3575 : buf.clear();
407 : }
408 3725 : };
409 :
410 6900 : auto appendBuffer = [&] (const StringView &str) {
411 6900 : if (buf.empty()) {
412 3575 : buf = str;
413 3325 : } else if (buf.data() + buf.size() == str.data()) {
414 3300 : buf = StringView(buf.data(), buf.size() + str.size());
415 : } else {
416 25 : flushBuffer();
417 25 : buf = str;
418 : }
419 6900 : };
420 :
421 6975 : while (!line.empty() && (!interpolated || !line.is(']'))) {
422 3625 : if (interpolated) {
423 0 : appendBuffer(line.readUntil<StringView::Chars<'\\', '#', '!', ']'>>());
424 : } else {
425 3625 : appendBuffer(line.readUntil<StringView::Chars<'\\', '#', '!'>>());
426 : }
427 3625 : if (line.is('\\')) {
428 50 : auto tmp = line;
429 50 : ++ line;
430 50 : if (line.is("#{") || line.is("#[") || line.is("!{")) {
431 25 : appendBuffer(StringView(line, 2));
432 25 : line += 2;
433 : } else {
434 25 : ++ line;
435 25 : appendBuffer(StringView(tmp, 2));
436 : }
437 3575 : } else if (line.is("#{")) {
438 150 : flushBuffer();
439 150 : line += 2;
440 :
441 150 : auto tmp = line;
442 150 : if (auto expr = Expression::parse(line, Expression::Options::getDefaultInline())) {
443 150 : data->addChild(new Token{Token::OutputEscaped, StringView(tmp, tmp.size() - line.size()), expr});
444 150 : if (line.is('}')) {
445 150 : ++ line;
446 : } else {
447 0 : return onError(OutStream, tmp, "Invalid interpolation expression");
448 : }
449 : }
450 3425 : } else if (line.is("#[")) {
451 175 : flushBuffer();
452 175 : line += 2;
453 :
454 175 : auto word = line.readChars<TagWordFilter>();
455 175 : line.skipChars<SpacingFilter>();
456 175 : auto retData = new Token{Token::LineData, tmp};
457 175 : retData->addChild(new Token{Token::Tag, word});
458 175 : if (!readTagInfo(OutStream, retData, line, true)) {
459 0 : return false;
460 : }
461 175 : if (line.is(']')) {
462 175 : ++ line;
463 175 : data->addChild(retData);
464 : } else {
465 0 : return onError(OutStream, word, "Invalid tag interpolation expression");
466 : }
467 3250 : } else if (line.is("!{")) {
468 25 : flushBuffer();
469 25 : line += 2;
470 :
471 25 : auto tmp = line;
472 25 : if (auto expr = Expression::parse(line, Expression::Options::getDefaultInline())) {
473 25 : data->addChild(new Token{Token::OutputUnescaped, StringView(tmp, tmp.size() - line.size()), expr});
474 25 : if (line.is('}')) {
475 25 : ++ line;
476 : } else {
477 0 : return onError(OutStream, tmp, "Invalid interpolation expression");
478 : }
479 : }
480 3225 : } else if (interpolated && line.is(']')) {
481 : // no action
482 : } else {
483 3225 : appendBuffer(StringView(line, 1));
484 3225 : ++ line;
485 : }
486 : }
487 :
488 3350 : flushBuffer();
489 :
490 3350 : if (interpolated) {
491 0 : r = line;
492 : }
493 3350 : return true;
494 : }
495 :
496 15275 : static Token *Lexer_completeLine(Token * retData, const StringView &line, StringView &r) {
497 15275 : retData->data = StringView(retData->data, retData->data.size() - r.size());
498 15275 : auto retTok = new Token(Token::Line, StringView(line, line.size() - r.size()));
499 15275 : retTok->addChild(retData);
500 15275 : return retTok;
501 : };
502 :
503 19850 : Token *Lexer::readLine(const OutStream &OutStream, const StringView &line, StringView &r, Token *rootLine) {
504 19850 : if (rootLine && rootLine->child) {
505 17100 : switch (rootLine->child->type) {
506 325 : case Token::LineComment:
507 : case Token::LineDot:
508 325 : return readPlainLine(OutStream, line, r);
509 : break;
510 7275 : case Token::LineData:
511 7275 : if (rootLine->child->tail && rootLine->child->tail->type == Token::TagTrailingDot) {
512 250 : return readPlainLine(OutStream, line, r);
513 : } else {
514 7025 : return readCommonLine(OutStream, line, r);
515 : }
516 : break;
517 100 : case Token::LinePlainText:
518 100 : return readPlainLine(OutStream, line, r);
519 : break;
520 9400 : default:
521 9400 : break;
522 : }
523 : }
524 :
525 12150 : return readCommonLine(OutStream, line, r);
526 : }
527 :
528 675 : Token *Lexer::readPlainLine(const OutStream &OutStream, const StringView &line, StringView &r) {
529 675 : auto retData = new Token{Token::LinePlainText, r};
530 675 : r.skipChars<SpacingFilter>();
531 675 : if (!r.is<NewLineFilter>()) {
532 675 : if (!readPlainTextInterpolation(OutStream, retData, r)) {
533 0 : return nullptr;
534 : }
535 : }
536 675 : return Lexer_completeLine(retData, line, r);
537 : }
538 :
539 19175 : Token *Lexer::readCommonLine(const OutStream &OutStream, const StringView &line, StringView &r) {
540 19175 : StringView tmp(r);
541 19175 : if (r.is("//")) {
542 325 : bool isHtml = false;
543 325 : auto retData = new Token{Token::LineComment, tmp};
544 325 : if (r.is("//-")) {
545 175 : retData->addChild(new Token(Token::CommentTemplate, StringView(r, 3)));
546 175 : r += 3;
547 : } else {
548 150 : isHtml = true;
549 150 : retData->addChild(new Token(Token::CommentHtml, StringView(r, 2)));
550 150 : r += 2;
551 : }
552 :
553 325 : if (!r.is<NewLineFilter>()) {
554 175 : if (isHtml) {
555 75 : if (!readPlainTextInterpolation(OutStream, retData, r)) {
556 0 : return nullptr;
557 : }
558 : } else {
559 100 : retData->addChild(new Token{Token::PlainText, r.readUntil<NewLineFilter>()});
560 : }
561 : }
562 :
563 325 : return Lexer_completeLine(retData, line, r);
564 18850 : } else if (r.is<StringView::CharGroup<CharGroupId::Latin>>()) {
565 12075 : return readKeywordLine(OutStream, line, r);
566 6775 : } else if (r.is('.') || r.is('#') || r.is('(') || r.is('&')) {
567 1350 : StringView t(r, 1, 1);
568 1350 : if (t.is<TagWordFilter>()) {
569 1325 : auto retData = new Token{Token::LineData, tmp};
570 1325 : retData->addChild(new Token{Token::Tag, r.readChars<TagWordFilter>()});
571 1325 : if (!readTagInfo(OutStream, retData, r)) {
572 1350 : return nullptr;
573 : }
574 1325 : return Lexer_completeLine(retData, line, r);
575 25 : } else if (r.is('.') && t.is<NewLineFilter>()) {
576 25 : auto retData = new Token{Token::LineDot, tmp};
577 25 : retData->addChild(new Token{Token::TagTrailingDot, StringView(r, 1)});
578 25 : ++ r;
579 25 : return Lexer_completeLine(retData, line, r);
580 : }
581 5425 : } else if (r.is('|')) {
582 1650 : auto retData = new Token{Token::LinePiped, tmp};
583 1650 : retData->addChild(new Token{Token::PipeMark, StringView()});
584 1650 : ++ r; r.skipChars<SpacingFilter>();
585 1650 : if (!r.is<NewLineFilter>()) {
586 300 : if (!readPlainTextInterpolation(OutStream, retData, r)) {
587 0 : return nullptr;
588 : }
589 : }
590 1650 : return Lexer_completeLine(retData, line, r);
591 3775 : } else if (r.is('=') || r.is("!=")) {
592 50 : auto retData = new Token{Token::LineOut, tmp};
593 50 : Token * exprToken = nullptr;
594 50 : if (r.is('=')) {
595 25 : ++ r; exprToken = new Token{Token::OutputEscaped, StringView()};
596 : } else {
597 25 : r += 2; exprToken = new Token{Token::OutputUnescaped, StringView()};
598 : }
599 50 : r.skipChars<SpacingFilter>();
600 :
601 50 : if (!r.is<NewLineFilter>()) {
602 50 : auto tmp = r;
603 50 : if (auto expr = Expression::parse(r, Expression::Options::getDefaultInline())) {
604 50 : r.skipChars<SpacingFilter>();
605 50 : if (!r.is<NewLineFilter>() && !r.empty()) {
606 0 : onError(OutStream, r, "Invalid expression after output expression block"); return nullptr;
607 : } else {
608 50 : exprToken->data = StringView(tmp, tmp.size() - r.size());
609 50 : exprToken->expression = expr;
610 50 : retData->addChild(exprToken);
611 : }
612 : } else {
613 0 : onError(OutStream, r, "Invalid expression in output block"); return nullptr;
614 : }
615 : }
616 50 : return Lexer_completeLine(retData, line, r);
617 3725 : } else if (r.is('-')) {
618 3175 : ++ r;
619 3175 : if (!r.is<NewLineFilter>()) {
620 3150 : auto retData = new Token{Token::LineCode, tmp};
621 3150 : if (readCode(retData, r)) {
622 3150 : return Lexer_completeLine(retData, line, r);
623 : } else {
624 0 : onError(OutStream, r, "Fail to read line of code"); return nullptr;
625 : }
626 : } else {
627 25 : auto retData = new Token{Token::LineCodeBlock, tmp};
628 25 : if (readCodeBlock(retData, r)) {
629 25 : return Lexer_completeLine(retData, line, r);
630 : } else {
631 0 : onError(OutStream, r, "Fail to read block of code"); return nullptr;
632 : }
633 : }
634 550 : } else if (r.is('+')) {
635 450 : ++ r;
636 450 : auto retData = new Token{Token::MixinCall, tmp};
637 450 : r.skipChars<SpacingFilter>();
638 :
639 450 : auto name = r.readChars<TagWordFilter>();
640 450 : if (name.empty()) {
641 0 : onError(OutStream, r, "Invalid mixin name"); return nullptr;
642 : }
643 :
644 450 : retData->data = name;
645 :
646 450 : if (r.is('(')) {
647 350 : auto tmp = r;
648 350 : if (auto expr = Expression::parse(r, Expression::Options::getDefaultInline())) {
649 350 : auto exprToken = new Token{Token::MixinArgs, tmp};
650 350 : r.skipChars<SpacingFilter>();
651 350 : if (!r.is<NewLineFilter>() && !r.empty()) {
652 0 : onError(OutStream, r, "Invalid expression after mixin call block"); return nullptr;
653 : } else {
654 350 : exprToken->data = StringView(tmp, tmp.size() - r.size());
655 350 : exprToken->expression = expr;
656 350 : retData->addChild(exprToken);
657 : }
658 : } else {
659 0 : onError(OutStream, r, "Invalid expression in mixin call block"); return nullptr;
660 : }
661 : }
662 :
663 450 : auto retTok = new Token(Token::Line, StringView(line, line.size() - r.size()));
664 450 : retTok->addChild(retData);
665 450 : return retTok;
666 100 : } else if (r.is('<')) {
667 100 : auto retData = new Token{Token::LinePlainText, r.readUntil<NewLineFilter>()};
668 100 : auto retTok = new Token(Token::Line, StringView(line, line.size() - r.size()));
669 100 : retTok->addChild(retData);
670 100 : return retTok;
671 0 : } else if (r.is<NewLineFilter>() || r.empty()) {
672 0 : return nullptr;
673 : }
674 :
675 0 : onError(OutStream, r, "Fail to recognize line type"); return nullptr;
676 : }
677 :
678 12075 : Token *Lexer::readKeywordLine(const OutStream &OutStream, const StringView &line, StringView &r) {
679 12075 : StringView tmp(r);
680 :
681 2075 : auto readKeywordExpression = [&, this] (StringView &r, Token::Type t) -> Token * {
682 2075 : if (auto expr = Expression::parse(r, Expression::Options::getDefaultInline())) {
683 2075 : auto retData = new Token{t, StringView(tmp, tmp.size() - r.size())};
684 2075 : retData->expression = expr;
685 2075 : if (t == Token::ControlMixin) {
686 150 : if (!((retData->expression->op == Expression::Call && retData->expression->left->isToken)
687 25 : || (retData->expression->op == Expression::NoOp && retData->expression->isToken))) {
688 0 : onError(OutStream, r, "Invalid mixin definition"); return nullptr;
689 : }
690 : }
691 2075 : return retData;
692 : } else {
693 0 : onError(OutStream, r, "Invalid expression in control statement"); return nullptr;
694 : }
695 12075 : };
696 :
697 12075 : auto word = r.readChars<TagWordFilter>();
698 12075 : bool hasSpacing = false;
699 12075 : if (r.is<SpacingFilter>()) {
700 5325 : hasSpacing = true;
701 5325 : r.skipChars<SpacingFilter>();
702 5325 : if (word == "include") {
703 475 : auto target = r.readUntil<NewLineFilter>();
704 475 : target.trimChars<SpacingFilter>();
705 475 : if (!target.empty()) {
706 475 : return new Token{Token::Include, target};
707 : } else {
708 0 : return nullptr;
709 : }
710 4850 : } else if (word == "mixin") {
711 150 : return readKeywordExpression(r, Token::ControlMixin);
712 4700 : } else if (word == "doctype") {
713 725 : auto target = r.readUntil<NewLineFilter>();
714 725 : target.trimChars<SpacingFilter>();
715 725 : if (!target.empty()) {
716 725 : return new Token{Token::Doctype, target};
717 : } else {
718 0 : return nullptr;
719 : }
720 3975 : } else if (word == "case") {
721 75 : return readKeywordExpression(r, Token::ControlCase);
722 3900 : } else if (word == "when") {
723 200 : return readKeywordExpression(r, Token::ControlWhen);
724 3700 : } else if (word == "if") {
725 1450 : return readKeywordExpression(r, Token::ControlIf);
726 2250 : } else if (word == "unless") {
727 25 : return readKeywordExpression(r, Token::ControlUnless);
728 2225 : } else if (word == "elseif") {
729 25 : return readKeywordExpression(r, Token::ControlElseIf);
730 2200 : } else if (word == "while") {
731 25 : return readKeywordExpression(r, Token::ControlWhile);
732 2175 : } else if (word == "else" && r.is("if")) {
733 125 : r += 2;
734 125 : if (r.is<SpacingFilter>()) {
735 125 : r.skipChars<SpacingFilter>();
736 125 : return readKeywordExpression(r, Token::ControlElseIf);
737 : } else {
738 0 : onError(OutStream, r, "Invalid expression in 'else if' statement"); return nullptr;
739 : }
740 2050 : } else if (word == "each" || word == "for") {
741 475 : StringView var1 = r.readChars<TagWordFilter>();
742 475 : StringView var2;
743 475 : if (r.is<SpacingFilter>() || r.is(',')) {
744 475 : r.skipChars<SpacingFilter>();
745 475 : if (r.is(',')) {
746 75 : ++ r;
747 75 : r.skipChars<SpacingFilter>();
748 75 : var2 = r.readChars<TagWordFilter>();
749 75 : if (!r.is<SpacingFilter>()) {
750 0 : onError(OutStream, r, "Invalid variable expression in 'each' statement"); return nullptr;
751 : }
752 75 : r.skipChars<SpacingFilter>();
753 : }
754 :
755 475 : if (!var1.empty() && r.is("in")) {
756 475 : r += 2;
757 475 : if (r.is<SpacingFilter>()) {
758 475 : r.skipChars<SpacingFilter>();
759 475 : if (auto expr = Expression::parse(r, Expression::Options::getDefaultInline())) {
760 475 : if (!var2.empty()) {
761 75 : auto retData = new Token{Token::ControlEachPair, StringView(tmp, tmp.size() - r.size())};
762 75 : retData->addChild(new Token(Token::ControlEachVariable, var1));
763 75 : retData->addChild(new Token(Token::ControlEachVariable, var2));
764 75 : retData->expression = expr;
765 75 : return retData;
766 : } else {
767 400 : auto retData = new Token{Token::ControlEach, StringView(tmp, tmp.size() - r.size())};
768 400 : retData->addChild(new Token(Token::ControlEachVariable, var1));
769 400 : retData->expression = expr;
770 400 : return retData;
771 : }
772 : } else {
773 0 : onError(OutStream, r, "Invalid expression in 'each' statement"); return nullptr;
774 : }
775 : }
776 : }
777 : }
778 0 : onError(OutStream, r, "Invalid 'each' statement"); return nullptr;
779 : }
780 : }
781 :
782 8325 : if (word == "default") {
783 75 : if (r.is(':') || r.is<NewLineFilter>()) {
784 75 : return new Token{Token::ControlDefault, StringView(tmp, tmp.size() - r.size())};
785 : } else {
786 0 : onError(OutStream, r, "Invalid 'default' line"); return nullptr;
787 : }
788 8250 : } else if (word == "else") {
789 200 : if (r.is(':') || r.is<NewLineFilter>()) {
790 200 : return new Token{Token::ControlElse, StringView(tmp, tmp.size() - r.size())};
791 : } else {
792 0 : onError(OutStream, r, "Invalid 'else' line"); return nullptr;
793 : }
794 : }
795 :
796 8050 : auto retData = new Token{Token::LineData, tmp};
797 8050 : retData->addChild(new Token{Token::Tag, word});
798 8050 : if (!hasSpacing) {
799 6475 : if (!readTagInfo(OutStream, retData, r)) {
800 0 : return nullptr;
801 : }
802 : } else {
803 1575 : if (!r.is<NewLineFilter>()) {
804 1575 : if (!readPlainTextInterpolation(OutStream, retData, r, false)) {
805 0 : return nullptr;
806 : }
807 : }
808 : }
809 8050 : return Lexer_completeLine(retData, line, r);
810 : }
811 :
812 50 : bool Lexer::onError(const OutStream &OutStream, const StringView &pos, const StringView &str) const {
813 50 : StringStream tmpOut;
814 50 : std::ostream *out = &std::cout;
815 :
816 50 : if (OutStream) {
817 50 : out = &tmpOut;
818 : }
819 :
820 50 : StringView r(content.data(), content.size() - pos.size());
821 :
822 50 : auto numDigits = [] (auto number) -> size_t {
823 50 : size_t digits = 0;
824 : if (number < 0) digits = 1; // remove this line if '-' counts as a digit
825 100 : while (number) {
826 50 : number /= 10;
827 50 : digits++;
828 : }
829 50 : return digits;
830 : };
831 :
832 50 : const char *ptr = r.data();
833 50 : size_t line = 1;
834 :
835 250 : while (!r.empty()) {
836 200 : r.skipUntil<StringView::Chars<'\n'>>();
837 200 : if (r.is('\n')) {
838 175 : ++ line;
839 175 : ++ r;
840 175 : ptr = r.data();
841 : }
842 : }
843 :
844 50 : r = StringView(ptr, content.size() - (ptr - content.data()));
845 50 : r = r.readUntil<StringView::Chars<'\n'>>();
846 :
847 50 : auto nd = numDigits(line) + 5;
848 :
849 50 : *out << "-> " << line << ": " << r << "\n";
850 :
851 350 : for (size_t i = 0; i < nd; ++ i) {
852 300 : *out << ' ';
853 : }
854 :
855 50 : r = StringView(r, pos.data() - r.data());
856 150 : while (!r.empty()) {
857 100 : r.skipUntil<SpacingFilter>();
858 100 : if (r.is<SpacingFilter>()) {
859 100 : *out << r[0];
860 100 : ++ r;
861 : }
862 : }
863 :
864 50 : *out << "^\n";
865 50 : *out << "Lexer error: " << str << "\n";
866 :
867 50 : if (OutStream) {
868 50 : OutStream(tmpOut.weak());
869 : }
870 :
871 50 : return false;
872 50 : }
873 :
874 : }
|