Skip to content

Commit f44a858

Browse files
ochafikmglambda
authored andcommitted
1 parent 9f529a2 commit f44a858

File tree

1 file changed

+46
-3
lines changed

1 file changed

+46
-3
lines changed

common/minja.hpp

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,7 @@ enum SpaceHandling { Keep, Strip, StripSpaces, StripNewline };
693693

694694
class TemplateToken {
695695
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 };
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+
818836
class TemplateNode {
819837
Location location_;
820838
protected:
@@ -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+
900933
class 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

Comments
 (0)