@@ -693,7 +693,7 @@ enum SpaceHandling { Keep, Strip, StripSpaces, StripNewline };
693
693
694
694
class TemplateToken {
695
695
public:
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 };
697
697
698
698
static std::string typeToString (Type t) {
699
699
switch (t) {
@@ -714,6 +714,8 @@ class TemplateToken {
714
714
case Type::EndFilter: return " endfilter" ;
715
715
case Type::Generation: return " generation" ;
716
716
case Type::EndGeneration: return " endgeneration" ;
717
+ case Type::Break: return " break" ;
718
+ case Type::Continue: return " continue" ;
717
719
}
718
720
return " Unknown" ;
719
721
}
@@ -815,6 +817,22 @@ struct CommentTemplateToken : public TemplateToken {
815
817
CommentTemplateToken (const Location & location, SpaceHandling pre, SpaceHandling post, const std::string& t) : TemplateToken(Type::Comment, location, pre, post), text(t) {}
816
818
};
817
819
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
+
818
836
class TemplateNode {
819
837
Location location_;
820
838
protected:
@@ -825,6 +843,12 @@ class TemplateNode {
825
843
void render (std::ostringstream & out, const std::shared_ptr<Context> & context) const {
826
844
try {
827
845
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 );
828
852
} catch (const std::exception & e) {
829
853
std::ostringstream err;
830
854
err << e.what ();
@@ -897,6 +921,15 @@ class IfNode : public TemplateNode {
897
921
}
898
922
};
899
923
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
+
900
933
class ForNode : public TemplateNode {
901
934
std::vector<std::string> var_names;
902
935
std::shared_ptr<Expression> iterable;
@@ -961,7 +994,12 @@ class ForNode : public TemplateNode {
961
994
loop.set (" last" , i == (n - 1 ));
962
995
loop.set (" previtem" , i > 0 ? filtered_items.at (i - 1 ) : Value ());
963
996
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
+ }
965
1003
}
966
1004
}
967
1005
};
@@ -2159,7 +2197,7 @@ class Parser {
2159
2197
static std::regex comment_tok (R"( \{#([-~]?)(.*?)([-~]?)#\})" );
2160
2198
static std::regex expr_open_regex (R"( \{\{([-~])?)" );
2161
2199
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)" );
2163
2201
static std::regex non_text_open_regex (R"( \{\{|\{%|\{#)" );
2164
2202
static std::regex expr_close_regex (R"( [\s\n\r]*([-~])?\}\})" );
2165
2203
static std::regex block_close_regex (R"( [\s\n\r]*([-~])?%\})" );
@@ -2291,6 +2329,9 @@ class Parser {
2291
2329
} else if (keyword == " endfilter" ) {
2292
2330
auto post_space = parseBlockClose ();
2293
2331
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));
2294
2335
} else {
2295
2336
throw std::runtime_error (" Unexpected block: " + keyword);
2296
2337
}
@@ -2414,6 +2455,8 @@ class Parser {
2414
2455
children.emplace_back (std::make_shared<FilterNode>(token->location , std::move (filter_token->filter ), std::move (body)));
2415
2456
} else if (dynamic_cast <CommentTemplateToken*>(token.get ())) {
2416
2457
// 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 ));
2417
2460
} else if (dynamic_cast <EndForTemplateToken*>(token.get ())
2418
2461
|| dynamic_cast <EndSetTemplateToken*>(token.get ())
2419
2462
|| dynamic_cast <EndMacroTemplateToken*>(token.get ())
0 commit comments