LCOV - code coverage report
Current view: top level - src/tools/kdb/gen - mustache.hpp (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 341 452 75.4 %
Date: 2019-09-12 12:28:41 Functions: 45 54 83.3 %

          Line data    Source code
       1             : #pragma GCC system_header // ignore warnings
       2             : 
       3             : /*
       4             :  * Boost Software License - Version 1.0
       5             :  *
       6             :  * Mustache v4.0
       7             :  * Copyright 2015-2018 Kevin Wojniak
       8             :  *
       9             :  * Permission is hereby granted, free of charge, to any person or organization
      10             :  * obtaining a copy of the software and accompanying documentation covered by
      11             :  * this license (the "Software") to use, reproduce, display, distribute,
      12             :  * execute, and transmit the Software, and to prepare derivative works of the
      13             :  * Software, and to permit third-parties to whom the Software is furnished to
      14             :  * do so, all subject to the following:
      15             :  *
      16             :  * The copyright notices in the Software and this entire statement, including
      17             :  * the above license grant, this restriction and the following disclaimer,
      18             :  * must be included in all copies of the Software, in whole or in part, and
      19             :  * all derivative works of the Software, unless such copies or derivative
      20             :  * works are solely in the form of machine-executable object code generated by
      21             :  * a source language processor.
      22             :  *
      23             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      24             :  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      25             :  * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
      26             :  * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
      27             :  * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
      28             :  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      29             :  * DEALINGS IN THE SOFTWARE.
      30             :  */
      31             : 
      32             : #ifndef KAINJOW_MUSTACHE_HPP
      33             : #define KAINJOW_MUSTACHE_HPP
      34             : 
      35             : #include <cassert>
      36             : #include <cctype>
      37             : #include <functional>
      38             : #include <iostream>
      39             : #include <memory>
      40             : #include <sstream>
      41             : #include <unordered_map>
      42             : #include <vector>
      43             : 
      44             : namespace kainjow
      45             : {
      46             : namespace mustache
      47             : {
      48             : 
      49             : template <typename string_type>
      50       10516 : string_type trim (const string_type & s)
      51             : {
      52       10516 :         auto it = s.begin ();
      53       47406 :         while (it != s.end () && std::isspace (*it))
      54             :         {
      55        5286 :                 it++;
      56             :         }
      57             :         auto rit = s.rbegin ();
      58       52596 :         while (rit.base () != it && std::isspace (*rit))
      59             :         {
      60       10524 :                 rit++;
      61             :         }
      62       42064 :         return { it, rit.base () };
      63             : }
      64             : 
      65             : template <typename string_type>
      66           0 : string_type html_escape (const string_type & s)
      67             : {
      68           0 :         string_type ret;
      69           0 :         ret.reserve (s.size () * 2);
      70           0 :         for (const auto ch : s)
      71             :         {
      72           0 :                 switch (ch)
      73             :                 {
      74             :                 case '&':
      75           0 :                         ret.append ({ '&', 'a', 'm', 'p', ';' });
      76             :                         break;
      77             :                 case '<':
      78           0 :                         ret.append ({ '&', 'l', 't', ';' });
      79             :                         break;
      80             :                 case '>':
      81           0 :                         ret.append ({ '&', 'g', 't', ';' });
      82             :                         break;
      83             :                 case '\"':
      84           0 :                         ret.append ({ '&', 'q', 'u', 'o', 't', ';' });
      85             :                         break;
      86             :                 case '\'':
      87           0 :                         ret.append ({ '&', 'a', 'p', 'o', 's', ';' });
      88             :                         break;
      89             :                 default:
      90           0 :                         ret.append (1, ch);
      91             :                         break;
      92             :                 }
      93             :         }
      94           0 :         return ret;
      95             : }
      96             : 
      97             : template <typename string_type>
      98          27 : std::vector<string_type> split (const string_type & s, typename string_type::value_type delim)
      99             : {
     100          27 :         std::vector<string_type> elems;
     101          54 :         std::basic_stringstream<typename string_type::value_type> ss (s);
     102          27 :         string_type item;
     103          60 :         while (std::getline (ss, item, delim))
     104             :         {
     105           3 :                 elems.push_back (item);
     106             :         }
     107          27 :         return elems;
     108             : }
     109             : 
     110             : template <typename string_type>
     111             : class basic_renderer
     112             : {
     113             : public:
     114             :         using type1 = std::function<string_type (const string_type &)>;
     115             :         using type2 = std::function<string_type (const string_type &, bool escaped)>;
     116             : 
     117             :         string_type operator() (const string_type & text) const
     118             :         {
     119             :                 return type1_ (text);
     120             :         }
     121             : 
     122             :         string_type operator() (const string_type & text, bool escaped) const
     123             :         {
     124             :                 return type2_ (text, escaped);
     125             :         }
     126             : 
     127             : private:
     128           0 :         basic_renderer (const type1 & t1, const type2 & t2) : type1_ (t1), type2_ (t2)
     129             :         {
     130             :         }
     131             : 
     132             :         const type1 & type1_;
     133             :         const type2 & type2_;
     134             : 
     135             :         template <typename StringType>
     136             :         friend class basic_mustache;
     137             : };
     138             : 
     139             : template <typename string_type>
     140           0 : class basic_lambda_t
     141             : {
     142             : public:
     143             :         using type1 = std::function<string_type (const string_type &)>;
     144             :         using type2 = std::function<string_type (const string_type &, const basic_renderer<string_type> & render)>;
     145             : 
     146             :         basic_lambda_t (const type1 & t) : type1_ (new type1 (t))
     147             :         {
     148             :         }
     149             :         basic_lambda_t (const type2 & t) : type2_ (new type2 (t))
     150             :         {
     151             :         }
     152             : 
     153             :         bool is_type1 () const
     154             :         {
     155             :                 return static_cast<bool> (type1_);
     156             :         }
     157             :         bool is_type2 () const
     158             :         {
     159             :                 return static_cast<bool> (type2_);
     160             :         }
     161             : 
     162             :         const type1 & type1_value () const
     163             :         {
     164           0 :                 return *type1_;
     165             :         }
     166             :         const type2 & type2_value () const
     167             :         {
     168           0 :                 return *type2_;
     169             :         }
     170             : 
     171             :         // Copying
     172           0 :         basic_lambda_t (const basic_lambda_t & l)
     173           0 :         {
     174           0 :                 if (l.type1_)
     175             :                 {
     176           0 :                         type1_.reset (new type1 (*l.type1_));
     177             :                 }
     178           0 :                 else if (l.type2_)
     179             :                 {
     180           0 :                         type2_.reset (new type2 (*l.type2_));
     181             :                 }
     182           0 :         }
     183             : 
     184             :         string_type operator() (const string_type & text) const
     185             :         {
     186             :                 return (*type1_) (text);
     187             :         }
     188             : 
     189             :         string_type operator() (const string_type & text, const basic_renderer<string_type> & render) const
     190             :         {
     191             :                 return (*type2_) (text, render);
     192             :         }
     193             : 
     194             : private:
     195             :         std::unique_ptr<type1> type1_;
     196             :         std::unique_ptr<type2> type2_;
     197             : };
     198             : 
     199             : template <typename string_type>
     200             : class basic_data;
     201             : template <typename string_type>
     202             : using basic_object = std::unordered_map<string_type, basic_data<string_type>>;
     203             : template <typename string_type>
     204             : using basic_list = std::vector<basic_data<string_type>>;
     205             : template <typename string_type>
     206             : using basic_partial = std::function<string_type ()>;
     207             : template <typename string_type>
     208             : using basic_lambda = typename basic_lambda_t<string_type>::type1;
     209             : template <typename string_type>
     210             : using basic_lambda2 = typename basic_lambda_t<string_type>::type2;
     211             : 
     212             : template <typename string_type>
     213       39381 : class basic_data
     214             : {
     215             : public:
     216             :         enum class type
     217             :         {
     218             :                 object,
     219             :                 string,
     220             :                 list,
     221             :                 bool_true,
     222             :                 bool_false,
     223             :                 partial,
     224             :                 lambda,
     225             :                 lambda2,
     226             :                 invalid,
     227             :         };
     228             : 
     229             :         // Construction
     230         612 :         basic_data () : basic_data (type::object)
     231             :         {
     232             :         }
     233        6804 :         basic_data (const string_type & string) : type_{ type::string }
     234             :         {
     235        3402 :                 str_.reset (new string_type (string));
     236        1134 :         }
     237         264 :         basic_data (const typename string_type::value_type * string) : type_{ type::string }
     238             :         {
     239         176 :                 str_.reset (new string_type (string));
     240          44 :         }
     241        1356 :         basic_data (const basic_object<string_type> & obj) : type_{ type::object }
     242             :         {
     243         678 :                 obj_.reset (new basic_object<string_type> (obj));
     244         226 :         }
     245         966 :         basic_data (const basic_list<string_type> & l) : type_{ type::list }
     246             :         {
     247         322 :                 list_.reset (new basic_list<string_type> (l));
     248         161 :         }
     249        3672 :         basic_data (type t) : type_{ t }
     250             :         {
     251         612 :                 switch (type_)
     252             :                 {
     253             :                 case type::object:
     254        1224 :                         obj_.reset (new basic_object<string_type>);
     255             :                         break;
     256             :                 case type::string:
     257           0 :                         str_.reset (new string_type);
     258             :                         break;
     259             :                 case type::list:
     260           0 :                         list_.reset (new basic_list<string_type>);
     261             :                         break;
     262             :                 default:
     263             :                         break;
     264             :                 }
     265         612 :         }
     266             :         basic_data (const string_type & name, const basic_data & var) : basic_data{}
     267             :         {
     268             :                 set (name, var);
     269             :         }
     270        1872 :         basic_data (const basic_partial<string_type> & p) : type_{ type::partial }
     271             :         {
     272         624 :                 partial_.reset (new basic_partial<string_type> (p));
     273         312 :         }
     274             :         basic_data (const basic_lambda<string_type> & l) : type_{ type::lambda }
     275             :         {
     276             :                 lambda_.reset (new basic_lambda_t<string_type> (l));
     277             :         }
     278             :         basic_data (const basic_lambda2<string_type> & l) : type_{ type::lambda2 }
     279             :         {
     280             :                 lambda_.reset (new basic_lambda_t<string_type> (l));
     281             :         }
     282             :         basic_data (const basic_lambda_t<string_type> & l)
     283             :         {
     284             :                 if (l.is_type1 ())
     285             :                 {
     286             :                         type_ = type::lambda;
     287             :                 }
     288             :                 else if (l.is_type2 ())
     289             :                 {
     290             :                         type_ = type::lambda2;
     291             :                 }
     292             :                 lambda_.reset (new basic_lambda_t<string_type> (l));
     293             :         }
     294        2820 :         basic_data (bool b) : type_{ b ? type::bool_true : type::bool_false }
     295             :         {
     296             :         }
     297             : 
     298             :         // Copying
     299       60852 :         basic_data (const basic_data & dat) : type_ (dat.type_)
     300             :         {
     301       20284 :                 if (dat.obj_)
     302             :                 {
     303        2116 :                         obj_.reset (new basic_object<string_type> (*dat.obj_));
     304             :                 }
     305       18168 :                 else if (dat.str_)
     306             :                 {
     307       18273 :                         str_.reset (new string_type (*dat.str_));
     308             :                 }
     309        5986 :                 else if (dat.list_)
     310             :                 {
     311         814 :                         list_.reset (new basic_list<string_type> (*dat.list_));
     312             :                 }
     313        5172 :                 else if (dat.partial_)
     314             :                 {
     315           0 :                         partial_.reset (new basic_partial<string_type> (*dat.partial_));
     316             :                 }
     317        5172 :                 else if (dat.lambda_)
     318             :                 {
     319           0 :                         lambda_.reset (new basic_lambda_t<string_type> (*dat.lambda_));
     320             :                 }
     321       10142 :         }
     322             : 
     323             :         // Move
     324         156 :         basic_data (basic_data && dat) : type_{ dat.type_ }
     325             :         {
     326          52 :                 if (dat.obj_)
     327             :                 {
     328          26 :                         obj_ = std::move (dat.obj_);
     329             :                 }
     330           0 :                 else if (dat.str_)
     331             :                 {
     332           0 :                         str_ = std::move (dat.str_);
     333             :                 }
     334           0 :                 else if (dat.list_)
     335             :                 {
     336           0 :                         list_ = std::move (dat.list_);
     337             :                 }
     338           0 :                 else if (dat.partial_)
     339             :                 {
     340           0 :                         partial_ = std::move (dat.partial_);
     341             :                 }
     342           0 :                 else if (dat.lambda_)
     343             :                 {
     344           0 :                         lambda_ = std::move (dat.lambda_);
     345             :                 }
     346          26 :                 dat.type_ = type::invalid;
     347          26 :         }
     348         652 :         basic_data & operator= (basic_data && dat)
     349             :         {
     350         652 :                 if (this != &dat)
     351             :                 {
     352        1304 :                         obj_.reset ();
     353        1304 :                         str_.reset ();
     354        1304 :                         list_.reset ();
     355        1304 :                         partial_.reset ();
     356        1304 :                         lambda_.reset ();
     357        1304 :                         if (dat.obj_)
     358             :                         {
     359          24 :                                 obj_ = std::move (dat.obj_);
     360             :                         }
     361        1256 :                         else if (dat.str_)
     362             :                         {
     363         166 :                                 str_ = std::move (dat.str_);
     364             :                         }
     365         924 :                         else if (dat.list_)
     366             :                         {
     367          94 :                                 list_ = std::move (dat.list_);
     368             :                         }
     369         736 :                         else if (dat.partial_)
     370             :                         {
     371         312 :                                 partial_ = std::move (dat.partial_);
     372             :                         }
     373         112 :                         else if (dat.lambda_)
     374             :                         {
     375           0 :                                 lambda_ = std::move (dat.lambda_);
     376             :                         }
     377         652 :                         type_ = dat.type_;
     378         652 :                         dat.type_ = type::invalid;
     379             :                 }
     380         652 :                 return *this;
     381             :         }
     382             : 
     383             :         // Type info
     384             :         bool is_object () const
     385             :         {
     386             :                 return type_ == type::object;
     387             :         }
     388             :         bool is_string () const
     389             :         {
     390             :                 return type_ == type::string;
     391             :         }
     392             :         bool is_list () const
     393             :         {
     394             :                 return type_ == type::list;
     395             :         }
     396             :         bool is_bool () const
     397             :         {
     398             :                 return is_true () || is_false ();
     399             :         }
     400             :         bool is_true () const
     401             :         {
     402             :                 return type_ == type::bool_true;
     403             :         }
     404             :         bool is_false () const
     405             :         {
     406             :                 return type_ == type::bool_false;
     407             :         }
     408             :         bool is_partial () const
     409             :         {
     410             :                 return type_ == type::partial;
     411             :         }
     412             :         bool is_lambda () const
     413             :         {
     414             :                 return type_ == type::lambda;
     415             :         }
     416             :         bool is_lambda2 () const
     417             :         {
     418             :                 return type_ == type::lambda2;
     419             :         }
     420             :         bool is_invalid () const
     421             :         {
     422             :                 return type_ == type::invalid;
     423             :         }
     424             : 
     425             :         // Object data
     426             :         bool is_empty_object () const
     427             :         {
     428             :                 return is_object () && obj_->empty ();
     429             :         }
     430             :         bool is_non_empty_object () const
     431             :         {
     432             :                 return is_object () && !obj_->empty ();
     433             :         }
     434             :         void set (const string_type & name, const basic_data & var)
     435             :         {
     436             :                 if (is_object ())
     437             :                 {
     438             :                         auto it = obj_->find (name);
     439             :                         if (it != obj_->end ())
     440             :                         {
     441             :                                 obj_->erase (it);
     442             :                         }
     443             :                         obj_->insert (std::pair<string_type, basic_data>{ name, var });
     444             :                 }
     445             :         }
     446             :         const basic_data * get (const string_type & name) const
     447             :         {
     448        4653 :                 if (!is_object ())
     449             :                 {
     450             :                         return nullptr;
     451             :                 }
     452       10785 :                 const auto & it = obj_->find (name);
     453       10785 :                 if (it == obj_->end ())
     454             :                 {
     455             :                         return nullptr;
     456             :                 }
     457        2309 :                 return &it->second;
     458             :         }
     459             : 
     460             :         // List data
     461             :         void push_back (const basic_data & var)
     462             :         {
     463             :                 if (is_list ())
     464             :                 {
     465             :                         list_->push_back (var);
     466             :                 }
     467             :         }
     468             :         const basic_list<string_type> & list_value () const
     469             :         {
     470         212 :                 return *list_;
     471             :         }
     472             :         bool is_empty_list () const
     473             :         {
     474         849 :                 return is_list () && list_->empty ();
     475             :         }
     476             :         bool is_non_empty_list () const
     477             :         {
     478         567 :                 return is_list () && !list_->empty ();
     479             :         }
     480             :         basic_data & operator<< (const basic_data & data)
     481             :         {
     482             :                 push_back (data);
     483             :                 return *this;
     484             :         }
     485             : 
     486             :         // String data
     487             :         const string_type & string_value () const
     488             :         {
     489        3188 :                 return *str_;
     490             :         }
     491             : 
     492             :         basic_data & operator[] (const string_type & key)
     493             :         {
     494        1014 :                 return (*obj_)[key];
     495             :         }
     496             : 
     497             :         const basic_partial<string_type> & partial_value () const
     498             :         {
     499         142 :                 return (*partial_);
     500             :         }
     501             : 
     502             :         const basic_lambda<string_type> & lambda_value () const
     503             :         {
     504           0 :                 return lambda_->type1_value ();
     505             :         }
     506             : 
     507             :         const basic_lambda2<string_type> & lambda2_value () const
     508             :         {
     509           0 :                 return lambda_->type2_value ();
     510             :         }
     511             : 
     512             : private:
     513             :         type type_;
     514             :         std::unique_ptr<basic_object<string_type>> obj_;
     515             :         std::unique_ptr<string_type> str_;
     516             :         std::unique_ptr<basic_list<string_type>> list_;
     517             :         std::unique_ptr<basic_partial<string_type>> partial_;
     518             :         std::unique_ptr<basic_lambda_t<string_type>> lambda_;
     519             : };
     520             : 
     521             : template <typename string_type>
     522        3786 : class delimiter_set
     523             : {
     524             : public:
     525             :         string_type begin;
     526             :         string_type end;
     527         357 :         delimiter_set () : begin (default_begin), end (default_end)
     528             :         {
     529         119 :         }
     530         436 :         bool is_default () const
     531             :         {
     532         436 :                 return begin == default_begin && end == default_end;
     533             :         }
     534             :         static const string_type default_begin;
     535             :         static const string_type default_end;
     536             : };
     537             : 
     538             : template <typename string_type>
     539       14328 : const string_type delimiter_set<string_type>::default_begin (2, '{');
     540             : template <typename string_type>
     541       14328 : const string_type delimiter_set<string_type>::default_end (2, '}');
     542             : 
     543             : template <typename string_type>
     544             : class basic_context
     545             : {
     546             : public:
     547             :         virtual void push (const basic_data<string_type> * data) = 0;
     548             :         virtual void pop () = 0;
     549             : 
     550             :         virtual const basic_data<string_type> * get (const string_type & name) const = 0;
     551             :         virtual const basic_data<string_type> * get_partial (const string_type & name) const = 0;
     552             : };
     553             : 
     554             : template <typename string_type>
     555         238 : class context : public basic_context<string_type>
     556             : {
     557             : public:
     558          24 :         context (const basic_data<string_type> * data)
     559          48 :         {
     560          24 :                 push (data);
     561          24 :         }
     562             : 
     563          95 :         context ()
     564         190 :         {
     565             :         }
     566             : 
     567         500 :         virtual void push (const basic_data<string_type> * data) override
     568             :         {
     569        1572 :                 items_.insert (items_.begin (), data);
     570         500 :         }
     571             : 
     572         500 :         virtual void pop () override
     573             :         {
     574        2000 :                 items_.erase (items_.begin ());
     575         500 :         }
     576             : 
     577        2734 :         virtual const basic_data<string_type> * get (const string_type & name) const override
     578             :         {
     579             :                 // process {{.}} name
     580        2736 :                 if (name.size () == 1 && name.at (0) == '.')
     581             :                 {
     582           4 :                         return items_.front ();
     583             :                 }
     584        2732 :                 if (name.find ('.') == string_type::npos)
     585             :                 {
     586             :                         // process normal name without having to split which is slower
     587       13258 :                         for (const auto & item : items_)
     588             :                         {
     589        9136 :                                 const auto var = item->get (name);
     590        4568 :                                 if (var)
     591             :                                 {
     592             :                                         return var;
     593             :                                 }
     594             :                         }
     595             :                         return nullptr;
     596             :                 }
     597             :                 // process x.y-like name
     598           0 :                 const auto names = split (name, '.');
     599           0 :                 for (const auto & item : items_)
     600             :                 {
     601           0 :                         auto var = item;
     602           0 :                         for (const auto & n : names)
     603             :                         {
     604           0 :                                 var = var->get (n);
     605           0 :                                 if (!var)
     606             :                                 {
     607             :                                         break;
     608             :                                 }
     609             :                         }
     610           0 :                         if (var)
     611             :                         {
     612             :                                 return var;
     613             :                         }
     614             :                 }
     615             :                 return nullptr;
     616             :         }
     617             : 
     618          71 :         virtual const basic_data<string_type> * get_partial (const string_type & name) const override
     619             :         {
     620         298 :                 for (const auto & item : items_)
     621             :                 {
     622         170 :                         const auto var = item->get (name);
     623          85 :                         if (var)
     624             :                         {
     625             :                                 return var;
     626             :                         }
     627             :                 }
     628             :                 return nullptr;
     629             :         }
     630             : 
     631             :         context (const context &) = delete;
     632             :         context & operator= (const context &) = delete;
     633             : 
     634             : private:
     635             :         std::vector<const basic_data<string_type> *> items_;
     636             : };
     637             : 
     638             : template <typename string_type>
     639         476 : class line_buffer_state
     640             : {
     641             : public:
     642             :         string_type data;
     643             :         bool contained_section_tag = false;
     644             : 
     645             :         bool is_empty_or_contains_only_whitespace () const
     646             :         {
     647        5577 :                 for (const auto ch : data)
     648             :                 {
     649             :                         // don't look at newlines
     650        1049 :                         if (ch != ' ' && ch != '\t')
     651             :                         {
     652             :                                 return false;
     653             :                         }
     654             :                 }
     655             :                 return true;
     656             :         }
     657             : 
     658             :         void clear ()
     659             :         {
     660       14574 :                 data.clear ();
     661        7287 :                 contained_section_tag = false;
     662             :         }
     663             : };
     664             : 
     665             : template <typename string_type>
     666         238 : class context_internal
     667             : {
     668             : public:
     669             :         basic_context<string_type> & ctx;
     670             :         delimiter_set<string_type> delim_set;
     671             :         line_buffer_state<string_type> line_buffer;
     672             : 
     673         238 :         context_internal (basic_context<string_type> & a_ctx) : ctx (a_ctx)
     674             :         {
     675             :         }
     676             : };
     677             : 
     678             : enum class tag_type
     679             : {
     680             :         text,
     681             :         variable,
     682             :         unescaped_variable,
     683             :         section_begin,
     684             :         section_end,
     685             :         section_begin_inverted,
     686             :         comment,
     687             :         partial,
     688             :         set_delimiter,
     689             : };
     690             : 
     691             : template <typename string_type>
     692     2147352 : class mstch_tag /* gcc doesn't allow "tag tag;" so rename the class :( */
     693             : {
     694             : public:
     695             :         string_type name;
     696             :         tag_type type = tag_type::text;
     697             :         std::shared_ptr<string_type> section_text;
     698             :         std::shared_ptr<delimiter_set<string_type>> delim_set;
     699             :         bool is_section_begin () const
     700             :         {
     701       82336 :                 return type == tag_type::section_begin || type == tag_type::section_begin_inverted;
     702             :         }
     703             :         bool is_section_end () const
     704             :         {
     705             :                 return type == tag_type::section_end;
     706             :         }
     707             : };
     708             : 
     709             : template <typename string_type>
     710             : class context_pusher
     711             : {
     712             : public:
     713         500 :         context_pusher (context_internal<string_type> & ctx, const basic_data<string_type> * data) : ctx_ (ctx)
     714             :         {
     715         500 :                 ctx.ctx.push (data);
     716             :         }
     717             :         ~context_pusher ()
     718             :         {
     719         500 :                 ctx_.ctx.pop ();
     720         500 :         }
     721             :         context_pusher (const context_pusher &) = delete;
     722             :         context_pusher & operator= (const context_pusher &) = delete;
     723             : 
     724             : private:
     725             :         context_internal<string_type> & ctx_;
     726             : };
     727             : 
     728             : template <typename string_type>
     729     1142350 : class component
     730             : {
     731             : private:
     732             :         using string_size_type = typename string_type::size_type;
     733             : 
     734             : public:
     735             :         string_type text;
     736             :         mstch_tag<string_type> tag;
     737             :         std::vector<component> children;
     738             :         string_size_type position = string_type::npos;
     739             : 
     740             :         enum class walk_control
     741             :         {
     742             :                 walk, // "continue" is reserved :/
     743             :                 stop,
     744             :                 skip,
     745             :         };
     746             :         using walk_callback = std::function<walk_control (component &)>;
     747             : 
     748        5722 :         component ()
     749       22888 :         {
     750             :         }
     751      289348 :         component (const string_type & t, string_size_type p) : text (t), position (p)
     752             :         {
     753       72337 :         }
     754             : 
     755             :         bool is_text () const
     756             :         {
     757             :                 return tag.type == tag_type::text;
     758             :         }
     759             : 
     760       56743 :         bool is_newline () const
     761             :         {
     762      116698 :                 return is_text () && ((text.size () == 2 && text[0] == '\r' && text[1] == '\n') ||
     763      165663 :                                       (text.size () == 1 && (text[0] == '\n' || text[0] == '\r')));
     764             :         }
     765             : 
     766             :         bool is_non_newline_whitespace () const
     767             :         {
     768             :                 return is_text () && !is_newline () && text.size () == 1 && (text[0] == ' ' || text[0] == '\t');
     769             :         }
     770             : 
     771             :         void walk_children (const walk_callback & callback)
     772             :         {
     773       95653 :                 for (auto & child : children)
     774             :                 {
     775       92097 :                         if (child.walk (callback) != walk_control::walk)
     776             :                         {
     777             :                                 break;
     778             :                         }
     779             :                 }
     780             :         }
     781             : 
     782             : private:
     783      136734 :         walk_control walk (const walk_callback & callback)
     784             :         {
     785      136734 :                 walk_control control{ callback (*this) };
     786      136734 :                 if (control == walk_control::stop)
     787             :                 {
     788             :                         return control;
     789             :                 }
     790      136734 :                 else if (control == walk_control::skip)
     791             :                 {
     792             :                         return walk_control::walk;
     793             :                 }
     794      586885 :                 for (auto & child : children)
     795             :                 {
     796       44637 :                         control = child.walk (callback);
     797       44637 :                         if (control == walk_control::stop)
     798             :                         {
     799             :                                 return control;
     800             :                         }
     801             :                 }
     802             :                 return control;
     803             :         }
     804             : };
     805             : 
     806             : template <typename string_type>
     807             : class parser
     808             : {
     809             : public:
     810             :         parser (const string_type & input, context_internal<string_type> & ctx, component<string_type> & root_component,
     811             :                 string_type & error_message)
     812             :         {
     813          95 :                 parse (input, ctx, root_component, error_message);
     814             :         }
     815             : 
     816             : private:
     817          95 :         void parse (const string_type & input, context_internal<string_type> & ctx, component<string_type> & root_component,
     818             :                     string_type & error_message) const
     819             :         {
     820             :                 using string_size_type = typename string_type::size_type;
     821             :                 using streamstring = std::basic_ostringstream<typename string_type::value_type>;
     822             : 
     823         475 :                 const string_type brace_delimiter_end_unescaped (3, '}');
     824          95 :                 const string_size_type input_size{ input.size () };
     825             : 
     826          95 :                 bool current_delimiter_is_brace{ ctx.delim_set.is_default () };
     827             : 
     828         380 :                 std::vector<component<string_type> *> sections{ &root_component };
     829         190 :                 std::vector<string_size_type> section_starts;
     830         190 :                 string_type current_text;
     831          95 :                 string_size_type current_text_position = -1;
     832             : 
     833          95 :                 current_text.reserve (input_size);
     834             : 
     835      164999 :                 const auto process_current_text = [&current_text, &current_text_position, &sections]() {
     836       49079 :                         if (!current_text.empty ())
     837             :                         {
     838       57960 :                                 const component<string_type> comp{ current_text, current_text_position };
     839       28980 :                                 sections.back ()->children.push_back (comp);
     840       28980 :                                 current_text.clear ();
     841       28980 :                                 current_text_position = -1;
     842             :                         }
     843       49174 :                 };
     844             : 
     845             :                 const std::vector<string_type> whitespace{
     846             :                         string_type (1, '\r') + string_type (1, '\n'),
     847             :                         string_type (1, '\n'),
     848             :                         string_type (1, '\r'),
     849             :                         string_type (1, ' '),
     850             :                         string_type (1, '\t'),
     851        2375 :                 };
     852             : 
     853          95 :                 for (string_size_type input_position = 0; input_position != input_size;)
     854             :                 {
     855      210328 :                         bool parse_tag = false;
     856             : 
     857      210328 :                         if (input.compare (input_position, ctx.delim_set.begin.size (), ctx.delim_set.begin) == 0)
     858             :                         {
     859        5627 :                                 process_current_text ();
     860             : 
     861             :                                 // Tag start delimiter
     862             :                                 parse_tag = true;
     863             :                         }
     864             :                         else
     865             :                         {
     866      204701 :                                 bool parsed_whitespace = false;
     867     1741620 :                                 for (const auto & whitespace_text : whitespace)
     868             :                                 {
     869      966173 :                                         if (input.compare (input_position, whitespace_text.size (), whitespace_text) == 0)
     870             :                                         {
     871       43357 :                                                 process_current_text ();
     872             : 
     873       86714 :                                                 const component<string_type> comp{ whitespace_text, input_position };
     874       43357 :                                                 sections.back ()->children.push_back (comp);
     875       43357 :                                                 input_position += whitespace_text.size ();
     876             : 
     877       43357 :                                                 parsed_whitespace = true;
     878             :                                                 break;
     879             :                                         }
     880             :                                 }
     881             : 
     882      204701 :                                 if (!parsed_whitespace)
     883             :                                 {
     884      161344 :                                         if (current_text.empty ())
     885             :                                         {
     886       28980 :                                                 current_text_position = input_position;
     887             :                                         }
     888      484032 :                                         current_text.append (1, input[input_position]);
     889      161344 :                                         input_position++;
     890             :                                 }
     891             :                         }
     892             : 
     893      210328 :                         if (!parse_tag)
     894             :                         {
     895      204701 :                                 continue;
     896             :                         }
     897             : 
     898             :                         // Find the next tag start delimiter
     899        5627 :                         const string_size_type tag_location_start = input_position;
     900             : 
     901             :                         // Find the next tag end delimiter
     902        5627 :                         string_size_type tag_contents_location{ tag_location_start + ctx.delim_set.begin.size () };
     903        6647 :                         const bool tag_is_unescaped_var{ current_delimiter_is_brace && tag_location_start != (input_size - 2) &&
     904        7667 :                                                          input.at (tag_contents_location) == ctx.delim_set.begin.at (0) };
     905        5627 :                         const string_type & current_tag_delimiter_end{ tag_is_unescaped_var ? brace_delimiter_end_unescaped :
     906        5627 :                                                                                               ctx.delim_set.end };
     907        5627 :                         const auto current_tag_delimiter_end_size = current_tag_delimiter_end.size ();
     908        5627 :                         if (tag_is_unescaped_var)
     909             :                         {
     910         498 :                                 ++tag_contents_location;
     911             :                         }
     912        5627 :                         const string_size_type tag_location_end{ input.find (current_tag_delimiter_end, tag_contents_location) };
     913        5627 :                         if (tag_location_end == string_type::npos)
     914             :                         {
     915           0 :                                 streamstring ss;
     916           0 :                                 ss << "Unclosed tag at " << tag_location_start;
     917           0 :                                 error_message.assign (ss.str ());
     918             :                                 return;
     919             :                         }
     920             : 
     921             :                         // Parse tag
     922             :                         const string_type tag_contents{ trim (
     923       16881 :                                 string_type{ input, tag_contents_location, tag_location_end - tag_contents_location }) };
     924       11254 :                         component<string_type> comp;
     925       11254 :                         if (!tag_contents.empty () && tag_contents[0] == '=')
     926             :                         {
     927         341 :                                 if (!parse_set_delimiter_tag (tag_contents, ctx.delim_set))
     928             :                                 {
     929           0 :                                         streamstring ss;
     930           0 :                                         ss << "Invalid set delimiter tag at " << tag_location_start;
     931           0 :                                         error_message.assign (ss.str ());
     932             :                                         return;
     933             :                                 }
     934         341 :                                 current_delimiter_is_brace = ctx.delim_set.is_default ();
     935         341 :                                 comp.tag.type = tag_type::set_delimiter;
     936         341 :                                 comp.tag.delim_set.reset (new delimiter_set<string_type> (ctx.delim_set));
     937             :                         }
     938        5627 :                         if (comp.tag.type != tag_type::set_delimiter)
     939             :                         {
     940        5286 :                                 parse_tag_contents (tag_is_unescaped_var, tag_contents, comp.tag);
     941             :                         }
     942        5627 :                         comp.position = tag_location_start;
     943        5627 :                         sections.back ()->children.push_back (comp);
     944             : 
     945             :                         // Start next search after this tag
     946        5627 :                         input_position = tag_location_end + current_tag_delimiter_end_size;
     947             : 
     948             :                         // Push or pop sections
     949       11254 :                         if (comp.tag.is_section_begin ())
     950             :                         {
     951        3765 :                                 sections.push_back (&sections.back ()->children.back ());
     952        1255 :                                 section_starts.push_back (input_position);
     953             :                         }
     954        4372 :                         else if (comp.tag.is_section_end ())
     955             :                         {
     956        1255 :                                 if (sections.size () == 1)
     957             :                                 {
     958           0 :                                         streamstring ss;
     959           0 :                                         ss << "Unopened section \"" << comp.tag.name << "\" at " << comp.position;
     960           0 :                                         error_message.assign (ss.str ());
     961             :                                         return;
     962             :                                 }
     963        3765 :                                 sections.back ()->tag.section_text.reset (new string_type (
     964        1255 :                                         input.substr (section_starts.back (), tag_location_start - section_starts.back ())));
     965        1255 :                                 sections.pop_back ();
     966        1255 :                                 section_starts.pop_back ();
     967             :                         }
     968             :                 }
     969             : 
     970          95 :                 process_current_text ();
     971             : 
     972             :                 // Check for sections without an ending tag
     973         380 :                 root_component.walk_children ([&error_message](component<string_type> & comp) ->
     974       76709 :                                               typename component<string_type>::walk_control {
     975      153418 :                                                       if (!comp.tag.is_section_begin ())
     976             :                                                       {
     977             :                                                               return component<string_type>::walk_control::walk;
     978             :                                                       }
     979        5020 :                                                       if (comp.children.empty () || !comp.children.back ().tag.is_section_end () ||
     980        3765 :                                                           comp.children.back ().tag.name != comp.tag.name)
     981             :                                                       {
     982           0 :                                                               streamstring ss;
     983           0 :                                                               ss << "Unclosed section \"" << comp.tag.name << "\" at " << comp.position;
     984           0 :                                                               error_message.assign (ss.str ());
     985             :                                                               return component<string_type>::walk_control::stop;
     986             :                                                       }
     987        1255 :                                                       comp.children.pop_back (); // remove now useless end section component
     988        1255 :                                                       return component<string_type>::walk_control::walk;
     989             :                                               });
     990          95 :                 if (!error_message.empty ())
     991             :                 {
     992             :                         return;
     993             :                 }
     994             :         }
     995             : 
     996         682 :         bool is_set_delimiter_valid (const string_type & delimiter) const
     997             :         {
     998             :                 // "Custom delimiters may not contain whitespace or the equals sign."
     999        4520 :                 for (const auto ch : delimiter)
    1000             :                 {
    1001        1792 :                         if (ch == '=' || std::isspace (ch))
    1002             :                         {
    1003             :                                 return false;
    1004             :                         }
    1005             :                 }
    1006             :                 return true;
    1007             :         }
    1008             : 
    1009         341 :         bool parse_set_delimiter_tag (const string_type & contents, delimiter_set<string_type> & delimiter_set) const
    1010             :         {
    1011             :                 // Smallest legal tag is "=X X="
    1012         341 :                 if (contents.size () < 5)
    1013             :                 {
    1014             :                         return false;
    1015             :                 }
    1016         341 :                 if (contents.back () != '=')
    1017             :                 {
    1018             :                         return false;
    1019             :                 }
    1020         682 :                 const auto contents_substr = trim (contents.substr (1, contents.size () - 2));
    1021         341 :                 const auto spacepos = contents_substr.find (' ');
    1022         341 :                 if (spacepos == string_type::npos)
    1023             :                 {
    1024             :                         return false;
    1025             :                 }
    1026         341 :                 const auto nonspace = contents_substr.find_first_not_of (' ', spacepos + 1);
    1027             :                 assert (nonspace != string_type::npos);
    1028         341 :                 const string_type begin = contents_substr.substr (0, spacepos);
    1029         682 :                 const string_type end = contents_substr.substr (nonspace, contents_substr.size () - nonspace);
    1030         341 :                 if (!is_set_delimiter_valid (begin) || !is_set_delimiter_valid (end))
    1031             :                 {
    1032             :                         return false;
    1033             :                 }
    1034         682 :                 delimiter_set.begin = begin;
    1035         341 :                 delimiter_set.end = end;
    1036             :                 return true;
    1037             :         }
    1038             : 
    1039        5286 :         void parse_tag_contents (bool is_unescaped_var, const string_type & contents, mstch_tag<string_type> & tag) const
    1040             :         {
    1041        5286 :                 if (is_unescaped_var)
    1042             :                 {
    1043         498 :                         tag.type = tag_type::unescaped_variable;
    1044         498 :                         tag.name = contents;
    1045             :                 }
    1046        4788 :                 else if (contents.empty ())
    1047             :                 {
    1048           0 :                         tag.type = tag_type::variable;
    1049           0 :                         tag.name.clear ();
    1050             :                 }
    1051             :                 else
    1052             :                 {
    1053        4788 :                         switch (contents.at (0))
    1054             :                         {
    1055             :                         case '#':
    1056         872 :                                 tag.type = tag_type::section_begin;
    1057         872 :                                 break;
    1058             :                         case '^':
    1059         383 :                                 tag.type = tag_type::section_begin_inverted;
    1060         383 :                                 break;
    1061             :                         case '/':
    1062        1255 :                                 tag.type = tag_type::section_end;
    1063        1255 :                                 break;
    1064             :                         case '>':
    1065          96 :                                 tag.type = tag_type::partial;
    1066          96 :                                 break;
    1067             :                         case '&':
    1068        1894 :                                 tag.type = tag_type::unescaped_variable;
    1069        1894 :                                 break;
    1070             :                         case '!':
    1071          48 :                                 tag.type = tag_type::comment;
    1072          48 :                                 break;
    1073             :                         default:
    1074         240 :                                 tag.type = tag_type::variable;
    1075         240 :                                 break;
    1076             :                         }
    1077        4788 :                         if (tag.type == tag_type::variable)
    1078             :                         {
    1079         240 :                                 tag.name = contents;
    1080             :                         }
    1081             :                         else
    1082             :                         {
    1083        9096 :                                 string_type name{ contents };
    1084       13644 :                                 name.erase (name.begin ());
    1085        9096 :                                 tag.name = trim (name);
    1086             :                         }
    1087             :                 }
    1088        5286 :         }
    1089             : };
    1090             : 
    1091             : template <typename StringType>
    1092         285 : class basic_mustache
    1093             : {
    1094             : public:
    1095             :         using string_type = StringType;
    1096             : 
    1097         190 :         basic_mustache (const string_type & input) : basic_mustache ()
    1098             :         {
    1099         190 :                 context<string_type> ctx;
    1100         190 :                 context_internal<string_type> context{ ctx };
    1101         190 :                 parser<string_type> parser{ input, context, root_component_, error_message_ };
    1102          95 :         }
    1103             : 
    1104             :         bool is_valid () const
    1105             :         {
    1106         522 :                 return error_message_.empty ();
    1107             :         }
    1108             : 
    1109             :         const string_type & error_message () const
    1110             :         {
    1111           0 :                 return error_message_;
    1112             :         }
    1113             : 
    1114             :         using escape_handler = std::function<string_type (const string_type &)>;
    1115             :         void set_custom_escape (const escape_handler & escape_fn)
    1116             :         {
    1117          95 :                 escape_ = escape_fn;
    1118             :         }
    1119             : 
    1120             :         template <typename stream_type>
    1121          24 :         stream_type & render (const basic_data<string_type> & data, stream_type & stream)
    1122             :         {
    1123       24850 :                 render (data, [&stream](const string_type & str) { stream << str; });
    1124          24 :                 return stream;
    1125             :         }
    1126             : 
    1127             :         string_type render (const basic_data<string_type> & data)
    1128             :         {
    1129             :                 std::basic_ostringstream<typename string_type::value_type> ss;
    1130             :                 return render (data, ss).str ();
    1131             :         }
    1132             : 
    1133             :         template <typename stream_type>
    1134             :         stream_type & render (basic_context<string_type> & ctx, stream_type & stream)
    1135             :         {
    1136             :                 context_internal<string_type> context{ ctx };
    1137             :                 render ([&stream](const string_type & str) { stream << str; }, context);
    1138             :                 return stream;
    1139             :         }
    1140             : 
    1141             :         string_type render (basic_context<string_type> & ctx)
    1142             :         {
    1143             :                 std::basic_ostringstream<typename string_type::value_type> ss;
    1144             :                 return render (ctx, ss).str ();
    1145             :         }
    1146             : 
    1147             :         using render_handler = std::function<void(const string_type &)>;
    1148          24 :         void render (const basic_data<string_type> & data, const render_handler & handler)
    1149             :         {
    1150          24 :                 if (!is_valid ())
    1151             :                 {
    1152           0 :                         return;
    1153             :                 }
    1154          48 :                 context<string_type> ctx{ &data };
    1155          48 :                 context_internal<string_type> context{ ctx };
    1156          24 :                 render (handler, context);
    1157             :         }
    1158             : 
    1159             : private:
    1160             :         using string_size_type = typename string_type::size_type;
    1161             : 
    1162         380 :         basic_mustache () : escape_ (html_escape<string_type>)
    1163             :         {
    1164             :         }
    1165             : 
    1166           0 :         basic_mustache (const string_type & input, context_internal<string_type> & ctx) : basic_mustache ()
    1167             :         {
    1168           0 :                 parser<string_type> parser{ input, ctx, root_component_, error_message_ };
    1169           0 :         }
    1170             : 
    1171           0 :         string_type render (context_internal<string_type> & ctx)
    1172             :         {
    1173           0 :                 std::basic_ostringstream<typename string_type::value_type> ss;
    1174           0 :                 render ([&ss](const string_type & str) { ss << str; }, ctx);
    1175           0 :                 return ss.str ();
    1176             :         }
    1177             : 
    1178          95 :         void render (const render_handler & handler, context_internal<string_type> & ctx)
    1179             :         {
    1180         285 :                 root_component_.walk_children (
    1181             :                         [&handler, &ctx, this](component<string_type> & comp) ->
    1182       32072 :                         typename component<string_type>::walk_control { return render_component (handler, ctx, comp); });
    1183             :                 // process the last line
    1184          95 :                 render_current_line (handler, ctx, nullptr);
    1185          95 :         }
    1186             : 
    1187        7287 :         void render_current_line (const render_handler & handler, context_internal<string_type> & ctx,
    1188             :                                   const component<string_type> * comp) const
    1189             :         {
    1190             :                 // We're at the end of a line, so check the line buffer state to see
    1191             :                 // if the line had tags in it, and also if the line is now empty or
    1192             :                 // contains whitespace only. if this situation is true, skip the line.
    1193        7287 :                 bool output = true;
    1194        8448 :                 if (ctx.line_buffer.contained_section_tag && ctx.line_buffer.is_empty_or_contains_only_whitespace ())
    1195             :                 {
    1196        1045 :                         output = false;
    1197             :                 }
    1198        7287 :                 if (output)
    1199             :                 {
    1200       12484 :                         handler (ctx.line_buffer.data);
    1201        6242 :                         if (comp)
    1202             :                         {
    1203        6147 :                                 handler (comp->text);
    1204             :                         }
    1205             :                 }
    1206       14574 :                 ctx.line_buffer.clear ();
    1207        7287 :         }
    1208             : 
    1209             :         void render_result (context_internal<string_type> & ctx, const string_type & text) const
    1210             :         {
    1211      102226 :                 ctx.line_buffer.data.append (text);
    1212             :         }
    1213             : 
    1214       60025 :         typename component<string_type>::walk_control render_component (const render_handler & handler, context_internal<string_type> & ctx,
    1215             :                                                                         component<string_type> & comp)
    1216             :         {
    1217       60025 :                 if (comp.is_text ())
    1218             :                 {
    1219       56743 :                         if (comp.is_newline ())
    1220             :                         {
    1221        7192 :                                 render_current_line (handler, ctx, &comp);
    1222             :                         }
    1223             :                         else
    1224             :                         {
    1225       49551 :                                 render_result (ctx, comp.text);
    1226             :                         }
    1227             :                         return component<string_type>::walk_control::walk;
    1228             :                 }
    1229             : 
    1230        3282 :                 const mstch_tag<string_type> & tag{ comp.tag };
    1231        3282 :                 const basic_data<string_type> * var = nullptr;
    1232        3282 :                 switch (tag.type)
    1233             :                 {
    1234             :                 case tag_type::variable:
    1235             :                 case tag_type::unescaped_variable:
    1236        1562 :                         if ((var = ctx.ctx.get (tag.name)) != nullptr)
    1237             :                         {
    1238        1562 :                                 if (!render_variable (handler, var, ctx, tag.type == tag_type::variable))
    1239             :                                 {
    1240             :                                         return component<string_type>::walk_control::stop;
    1241             :                                 }
    1242             :                         }
    1243             :                         break;
    1244             :                 case tag_type::section_begin:
    1245         822 :                         if ((var = ctx.ctx.get (tag.name)) != nullptr)
    1246             :                         {
    1247         527 :                                 if (var->is_lambda () || var->is_lambda2 ())
    1248             :                                 {
    1249           0 :                                         if (!render_lambda (handler, var, ctx, render_lambda_escape::optional, *comp.tag.section_text,
    1250             :                                                             true))
    1251             :                                         {
    1252             :                                                 return component<string_type>::walk_control::stop;
    1253             :                                         }
    1254             :                                 }
    1255         902 :                                 else if (!var->is_false () && !var->is_empty_list ())
    1256             :                                 {
    1257         284 :                                         render_section (handler, ctx, comp, var);
    1258             :                                 }
    1259             :                         }
    1260             :                         return component<string_type>::walk_control::skip;
    1261             :                 case tag_type::section_begin_inverted:
    1262         430 :                         if ((var = ctx.ctx.get (tag.name)) == nullptr || var->is_false () || var->is_empty_list ())
    1263             :                         {
    1264         270 :                                 render_section (handler, ctx, comp, var);
    1265             :                         }
    1266             :                         return component<string_type>::walk_control::skip;
    1267             :                 case tag_type::partial:
    1268          71 :                         if ((var = ctx.ctx.get_partial (tag.name)) != nullptr && (var->is_partial () || var->is_string ()))
    1269             :                         {
    1270         284 :                                 const auto & partial_result = var->is_partial () ? var->partial_value () () : var->string_value ();
    1271         142 :                                 basic_mustache tmpl{ partial_result };
    1272         142 :                                 tmpl.set_custom_escape (escape_);
    1273          71 :                                 if (!tmpl.is_valid ())
    1274             :                                 {
    1275           0 :                                         error_message_ = tmpl.error_message ();
    1276             :                                 }
    1277             :                                 else
    1278             :                                 {
    1279          71 :                                         tmpl.render (handler, ctx);
    1280          71 :                                         if (!tmpl.is_valid ())
    1281             :                                         {
    1282           0 :                                                 error_message_ = tmpl.error_message ();
    1283             :                                         }
    1284             :                                 }
    1285          71 :                                 if (!tmpl.is_valid ())
    1286             :                                 {
    1287           0 :                                         return component<string_type>::walk_control::stop;
    1288             :                                 }
    1289             :                         }
    1290             :                         break;
    1291             :                 case tag_type::set_delimiter:
    1292         461 :                         ctx.delim_set = *comp.tag.delim_set;
    1293             :                         break;
    1294             :                 default:
    1295             :                         break;
    1296             :                 }
    1297             : 
    1298             :                 return component<string_type>::walk_control::walk;
    1299             :         }
    1300             : 
    1301             :         enum class render_lambda_escape
    1302             :         {
    1303             :                 escape,
    1304             :                 unescape,
    1305             :                 optional,
    1306             :         };
    1307             : 
    1308           0 :         bool render_lambda (const render_handler & handler, const basic_data<string_type> * var, context_internal<string_type> & ctx,
    1309             :                             render_lambda_escape escape, const string_type & text, bool parse_with_same_context)
    1310             :         {
    1311             :                 const typename basic_renderer<string_type>::type2 render2 = [this, &ctx, parse_with_same_context,
    1312           0 :                                                                              escape](const string_type & text, bool escaped) {
    1313           0 :                         const auto process_template = [this, &ctx, escape, escaped](basic_mustache & tmpl) -> string_type {
    1314           0 :                                 if (!tmpl.is_valid ())
    1315             :                                 {
    1316           0 :                                         error_message_ = tmpl.error_message ();
    1317             :                                         return {};
    1318             :                                 }
    1319           0 :                                 const string_type str{ tmpl.render (ctx) };
    1320           0 :                                 if (!tmpl.is_valid ())
    1321             :                                 {
    1322           0 :                                         error_message_ = tmpl.error_message ();
    1323             :                                         return {};
    1324             :                                 }
    1325           0 :                                 bool do_escape = false;
    1326           0 :                                 switch (escape)
    1327             :                                 {
    1328             :                                 case render_lambda_escape::escape:
    1329           0 :                                         do_escape = true;
    1330           0 :                                         break;
    1331             :                                 case render_lambda_escape::unescape:
    1332             :                                         do_escape = false;
    1333             :                                         break;
    1334             :                                 case render_lambda_escape::optional:
    1335           0 :                                         do_escape = escaped;
    1336           0 :                                         break;
    1337             :                                 }
    1338           0 :                                 return do_escape ? escape_ (str) : str;
    1339           0 :                         };
    1340           0 :                         if (parse_with_same_context)
    1341             :                         {
    1342           0 :                                 basic_mustache tmpl{ text, ctx };
    1343           0 :                                 tmpl.set_custom_escape (escape_);
    1344           0 :                                 return process_template (tmpl);
    1345             :                         }
    1346           0 :                         basic_mustache tmpl{ text };
    1347           0 :                         tmpl.set_custom_escape (escape_);
    1348           0 :                         return process_template (tmpl);
    1349           0 :                 };
    1350           0 :                 const typename basic_renderer<string_type>::type1 render = [&render2](const string_type & text) {
    1351             :                         return render2 (text, false);
    1352           0 :                 };
    1353           0 :                 if (var->is_lambda2 ())
    1354             :                 {
    1355           0 :                         const basic_renderer<string_type> renderer{ render, render2 };
    1356           0 :                         render_result (ctx, var->lambda2_value () (text, renderer));
    1357             :                 }
    1358             :                 else
    1359             :                 {
    1360           0 :                         render_current_line (handler, ctx, nullptr);
    1361           0 :                         render_result (ctx, render (var->lambda_value () (text)));
    1362             :                 }
    1363           0 :                 return error_message_.empty ();
    1364             :         }
    1365             : 
    1366        1562 :         bool render_variable (const render_handler & handler, const basic_data<string_type> * var, context_internal<string_type> & ctx,
    1367             :                               bool escaped)
    1368             :         {
    1369        1562 :                 if (var->is_string ())
    1370             :                 {
    1371        1562 :                         const auto & varstr = var->string_value ();
    1372        6248 :                         render_result (ctx, escaped ? escape_ (varstr) : varstr);
    1373             :                 }
    1374           0 :                 else if (var->is_lambda ())
    1375             :                 {
    1376           0 :                         const render_lambda_escape escape_opt = escaped ? render_lambda_escape::escape : render_lambda_escape::unescape;
    1377           0 :                         return render_lambda (handler, var, ctx, escape_opt, {}, false);
    1378             :                 }
    1379           0 :                 else if (var->is_lambda2 ())
    1380             :                 {
    1381             :                         using streamstring = std::basic_ostringstream<typename string_type::value_type>;
    1382           0 :                         streamstring ss;
    1383           0 :                         ss << "Lambda with render argument is not allowed for regular variables";
    1384           0 :                         error_message_ = ss.str ();
    1385             :                         return false;
    1386             :                 }
    1387             :                 return true;
    1388             :         }
    1389             : 
    1390         554 :         void render_section (const render_handler & handler, context_internal<string_type> & ctx, component<string_type> & incomp,
    1391             :                              const basic_data<string_type> * var)
    1392             :         {
    1393             :                 const auto callback = [&handler, &ctx, this](component<string_type> & comp) -> typename component<string_type>::walk_control
    1394       27953 :                 {
    1395       27953 :                         return render_component (handler, ctx, comp);
    1396         554 :                 };
    1397         909 :                 if (var && var->is_non_empty_list ())
    1398             :                 {
    1399         675 :                         for (const auto & item : var->list_value ())
    1400             :                         {
    1401             :                                 // account for the section begin tag
    1402         251 :                                 ctx.line_buffer.contained_section_tag = true;
    1403             : 
    1404         502 :                                 const context_pusher<string_type> ctxpusher{ ctx, &item };
    1405         753 :                                 incomp.walk_children (callback);
    1406             : 
    1407             :                                 // ctx may have been cleared. account for the section end tag
    1408         251 :                                 ctx.line_buffer.contained_section_tag = true;
    1409             :                         }
    1410             :                 }
    1411         448 :                 else if (var)
    1412             :                 {
    1413             :                         // account for the section begin tag
    1414         249 :                         ctx.line_buffer.contained_section_tag = true;
    1415             : 
    1416         498 :                         const context_pusher<string_type> ctxpusher{ ctx, var };
    1417         747 :                         incomp.walk_children (callback);
    1418             : 
    1419             :                         // ctx may have been cleared. account for the section end tag
    1420         249 :                         ctx.line_buffer.contained_section_tag = true;
    1421             :                 }
    1422             :                 else
    1423             :                 {
    1424             :                         // account for the section begin tag
    1425         199 :                         ctx.line_buffer.contained_section_tag = true;
    1426             : 
    1427         597 :                         incomp.walk_children (callback);
    1428             : 
    1429             :                         // ctx may have been cleared. account for the section end tag
    1430         199 :                         ctx.line_buffer.contained_section_tag = true;
    1431             :                 }
    1432         554 :         }
    1433             : 
    1434             : private:
    1435             :         string_type error_message_;
    1436             :         component<string_type> root_component_;
    1437             :         escape_handler escape_;
    1438             : };
    1439             : 
    1440             : using mustache = basic_mustache<std::string>;
    1441             : using data = basic_data<mustache::string_type>;
    1442             : using object = basic_object<mustache::string_type>;
    1443             : using list = basic_list<mustache::string_type>;
    1444             : using partial = basic_partial<mustache::string_type>;
    1445             : using renderer = basic_renderer<mustache::string_type>;
    1446             : using lambda = basic_lambda<mustache::string_type>;
    1447             : using lambda2 = basic_lambda2<mustache::string_type>;
    1448             : using lambda_t = basic_lambda_t<mustache::string_type>;
    1449             : 
    1450             : using mustachew = basic_mustache<std::wstring>;
    1451             : using dataw = basic_data<mustachew::string_type>;
    1452             : 
    1453             : } // namespace mustache
    1454             : } // namespace kainjow
    1455             : 
    1456             : #endif // KAINJOW_MUSTACHE_HPP

Generated by: LCOV version 1.13