@@ -693,7 +693,7 @@ enum SpaceHandling { Keep, Strip, StripSpaces, StripNewline };
693693
694694class TemplateToken {
695695public:
696- enum class Type { Text, Expression, If, Else, Elif, EndIf, For, EndFor, Generation, EndGeneration, Set, EndSet, Comment, Macro, EndMacro, Filter, EndFilter };
696+ enum class Type { Text, Expression, If, Else, Elif, EndIf, For, EndFor, Generation, EndGeneration, Set, EndSet, Comment, Macro, EndMacro, Filter, EndFilter, Break, Continue };
697697
698698 static std::string typeToString (Type t) {
699699 switch (t) {
@@ -714,6 +714,8 @@ class TemplateToken {
714714 case Type::EndFilter: return " endfilter" ;
715715 case Type::Generation: return " generation" ;
716716 case Type::EndGeneration: return " endgeneration" ;
717+ case Type::Break: return " break" ;
718+ case Type::Continue: return " continue" ;
717719 }
718720 return " Unknown" ;
719721 }
@@ -815,6 +817,22 @@ struct CommentTemplateToken : public TemplateToken {
815817 CommentTemplateToken (const Location & location, SpaceHandling pre , SpaceHandling post , const std::string& t) : TemplateToken(Type::Comment, location, pre , post ), text(t) {}
816818};
817819
820+ enum class LoopControlType { Break, Continue };
821+
822+ class LoopControlException : public std ::runtime_error {
823+ public:
824+ LoopControlType control_type;
825+ LoopControlException (const std::string & message, LoopControlType control_type) : std::runtime_error(message), control_type(control_type) {}
826+ LoopControlException (LoopControlType control_type)
827+ : std::runtime_error((std::ostringstream() << (control_type == LoopControlType::Continue ? " continue" : " break" ) << " outside of a loop" ).str()),
828+ control_type (control_type) {}
829+ };
830+
831+ struct LoopControlTemplateToken : public TemplateToken {
832+ LoopControlType control_type;
833+ LoopControlTemplateToken (const Location & location, SpaceHandling pre , SpaceHandling post , LoopControlType control_type) : TemplateToken(Type::Break, location, pre , post ), control_type(control_type) {}
834+ };
835+
818836class TemplateNode {
819837 Location location_;
820838protected:
@@ -825,6 +843,12 @@ class TemplateNode {
825843 void render (std::ostringstream & out, const std::shared_ptr<Context> & context) const {
826844 try {
827845 do_render (out, context);
846+ } catch (const LoopControlException & e) {
847+ // TODO: make stack creation lazy. Only needed if it was thrown outside of a loop.
848+ std::ostringstream err;
849+ err << e.what ();
850+ if (location_.source ) err << error_location_suffix (*location_.source , location_.pos );
851+ throw LoopControlException (err.str (), e.control_type );
828852 } catch (const std::exception & e) {
829853 std::ostringstream err;
830854 err << e.what ();
@@ -897,6 +921,15 @@ class IfNode : public TemplateNode {
897921 }
898922};
899923
924+ class LoopControlNode : public TemplateNode {
925+ LoopControlType control_type_;
926+ public:
927+ LoopControlNode (const Location & location, LoopControlType control_type) : TemplateNode(location), control_type_(control_type) {}
928+ void do_render (std::ostringstream &, const std::shared_ptr<Context> &) const override {
929+ throw LoopControlException (control_type_);
930+ }
931+ };
932+
900933class ForNode : public TemplateNode {
901934 std::vector<std::string> var_names;
902935 std::shared_ptr<Expression> iterable;
@@ -961,7 +994,12 @@ class ForNode : public TemplateNode {
961994 loop.set (" last" , i == (n - 1 ));
962995 loop.set (" previtem" , i > 0 ? filtered_items.at (i - 1 ) : Value ());
963996 loop.set (" nextitem" , i < n - 1 ? filtered_items.at (i + 1 ) : Value ());
964- body->render (out, loop_context);
997+ try {
998+ body->render (out, loop_context);
999+ } catch (const LoopControlException & e) {
1000+ if (e.control_type == LoopControlType::Break) break ;
1001+ if (e.control_type == LoopControlType::Continue) continue ;
1002+ }
9651003 }
9661004 }
9671005 };
@@ -2159,7 +2197,7 @@ class Parser {
21592197 static std::regex comment_tok (R"( \{#([-~]?)(.*?)([-~]?)#\})" );
21602198 static std::regex expr_open_regex (R"( \{\{([-~])?)" );
21612199 static std::regex block_open_regex (R"( ^\{%([-~])?[\s\n\r]*)" );
2162- static std::regex block_keyword_tok (R"( (if|else|elif|endif|for|endfor|generation|endgeneration|set|endset|block|endblock|macro|endmacro|filter|endfilter)\b)" );
2200+ static std::regex block_keyword_tok (R"( (if|else|elif|endif|for|endfor|generation|endgeneration|set|endset|block|endblock|macro|endmacro|filter|endfilter|break|continue )\b)" );
21632201 static std::regex non_text_open_regex (R"( \{\{|\{%|\{#)" );
21642202 static std::regex expr_close_regex (R"( [\s\n\r]*([-~])?\}\})" );
21652203 static std::regex block_close_regex (R"( [\s\n\r]*([-~])?%\})" );
@@ -2291,6 +2329,9 @@ class Parser {
22912329 } else if (keyword == " endfilter" ) {
22922330 auto post_space = parseBlockClose ();
22932331 tokens.push_back (std::make_unique<EndFilterTemplateToken>(location, pre_space, post_space));
2332+ } else if (keyword == " break" || keyword == " continue" ) {
2333+ auto post_space = parseBlockClose ();
2334+ tokens.push_back (std::make_unique<LoopControlTemplateToken>(location, pre_space, post_space, keyword == " break" ? LoopControlType::Break : LoopControlType::Continue));
22942335 } else {
22952336 throw std::runtime_error (" Unexpected block: " + keyword);
22962337 }
@@ -2414,6 +2455,8 @@ class Parser {
24142455 children.emplace_back (std::make_shared<FilterNode>(token->location , std::move (filter_token->filter ), std::move (body)));
24152456 } else if (dynamic_cast <CommentTemplateToken*>(token.get ())) {
24162457 // Ignore comments
2458+ } else if (auto ctrl_token = dynamic_cast <LoopControlTemplateToken*>(token.get ())) {
2459+ children.emplace_back (std::make_shared<LoopControlNode>(token->location , ctrl_token->control_type ));
24172460 } else if (dynamic_cast <EndForTemplateToken*>(token.get ())
24182461 || dynamic_cast <EndSetTemplateToken*>(token.get ())
24192462 || dynamic_cast <EndMacroTemplateToken*>(token.get ())
0 commit comments