LCOV - code coverage report
Current view: top level - extra/webserver/pug - SPPugLexer.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 513 569 90.2 %
Date: 2024-05-12 00:16:13 Functions: 21 21 100.0 %

          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             : }

Generated by: LCOV version 1.14