1651 using CharIterator = std::string::const_iterator;
1653 std::shared_ptr<std::string> template_str;
1654 CharIterator start, end, it;
1657 Parser(
const std::shared_ptr<std::string>& template_str,
const Options & options) : template_str(template_str), options(options) {
1658 if (!template_str)
throw std::runtime_error(
"Template string is null");
1659 start = it = this->template_str->begin();
1660 end = this->template_str->end();
1665 while (it != end && std::isspace(*it)) ++it;
1670 std::unique_ptr<std::string> parseString() {
1671 auto doParse = [&](
char quote) -> std::unique_ptr<std::string> {
1672 if (it == end || *it != quote)
return nullptr;
1674 bool escape =
false;
1675 for (++it; it != end; ++it) {
1679 case 'n': result +=
'\n';
break;
1680 case 'r': result +=
'\r';
break;
1681 case 't': result +=
'\t';
break;
1682 case 'b': result +=
'\b';
break;
1683 case 'f': result +=
'\f';
break;
1684 case '\\': result +=
'\\';
break;
1693 }
else if (*it ==
'\\') {
1695 }
else if (*it == quote) {
1697 return std::make_unique<std::string>(std::move(result));
1706 if (it == end)
return nullptr;
1707 if (*it ==
'"')
return doParse(
'"');
1708 if (*it ==
'\'')
return doParse(
'\'');
1712 json parseNumber(CharIterator& it,
const CharIterator& end) {
1716 bool hasDecimal =
false;
1717 bool hasExponent =
false;
1719 if (it != end && (*it ==
'-' || *it ==
'+')) ++it;
1722 if (std::isdigit(*it)) {
1724 }
else if (*it ==
'.') {
1725 if (hasDecimal)
throw std::runtime_error(
"Multiple decimal points");
1728 }
else if (it != start && (*it ==
'e' || *it ==
'E')) {
1729 if (hasExponent)
throw std::runtime_error(
"Multiple exponents");
1741 std::string str(start, it);
1743 return json::parse(str);
1744 }
catch (json::parse_error& e) {
1745 throw std::runtime_error(
"Failed to parse number: '" + str +
"' (" + std::string(e.what()) +
")");
1751 std::shared_ptr<Value> parseConstant() {
1754 if (it == end)
return nullptr;
1755 if (*it ==
'"' || *it ==
'\'') {
1756 auto str = parseString();
1757 if (str)
return std::make_shared<Value>(*str);
1759 static std::regex prim_tok(R
"(true\b|True\b|false\b|False\b|None\b)");
1760 auto token = consumeToken(prim_tok);
1761 if (!token.empty()) {
1762 if (token ==
"true" || token ==
"True")
return std::make_shared<Value>(
true);
1763 if (token ==
"false" || token ==
"False")
return std::make_shared<Value>(
false);
1764 if (token ==
"None")
return std::make_shared<Value>(
nullptr);
1765 throw std::runtime_error(
"Unknown constant token: " + token);
1768 auto number = parseNumber(it, end);
1769 if (!number.is_null())
return std::make_shared<Value>(number);
1775 class expression_parsing_error :
public std::runtime_error {
1776 const CharIterator it;
1778 expression_parsing_error(
const std::string & message,
const CharIterator & it)
1779 : std::runtime_error(message), it(it) {}
1780 size_t get_pos(
const CharIterator & begin)
const {
1781 return std::distance(begin, it);
1785 bool peekSymbols(
const std::vector<std::string> & symbols)
const {
1786 for (
const auto & symbol : symbols) {
1787 if (std::distance(it, end) >= (int64_t) symbol.size() && std::string(it, it + symbol.size()) == symbol) {
1796 consumeSpaces(space_handling);
1798 if (std::regex_search(it, end, match, regex) && match.position() == 0) {
1799 it += match[0].length();
1800 std::vector<std::string> ret;
1801 for (
size_t i = 0, n = match.size(); i < n; ++i) {
1802 ret.push_back(match[i].str());
1811 consumeSpaces(space_handling);
1813 if (std::regex_search(it, end, match, regex) && match.position() == 0) {
1814 it += match[0].length();
1815 return match[0].str();
1823 consumeSpaces(space_handling);
1824 if (std::distance(it, end) >= (int64_t) token.size() && std::string(it, it + token.size()) == token) {
1832 std::shared_ptr<Expression> parseExpression(
bool allow_if_expr =
true) {
1833 auto left = parseLogicalOr();
1834 if (it == end)
return left;
1836 if (!allow_if_expr)
return left;
1838 static std::regex if_tok(R
"(if\b)");
1839 if (consumeToken(if_tok).empty()) {
1843 auto location = get_location();
1844 auto [condition, else_expr] = parseIfExpression();
1845 return std::make_shared<IfExpr>(location, std::move(condition), std::move(left), std::move(else_expr));
1849 return {template_str, (size_t) std::distance(start, it)};
1852 std::pair<std::shared_ptr<Expression>, std::shared_ptr<Expression>> parseIfExpression() {
1853 auto condition = parseLogicalOr();
1854 if (!condition)
throw std::runtime_error(
"Expected condition expression");
1856 static std::regex else_tok(R
"(else\b)");
1857 std::shared_ptr<Expression> else_expr;
1858 if (!consumeToken(else_tok).empty()) {
1859 else_expr = parseExpression();
1860 if (!else_expr)
throw std::runtime_error(
"Expected 'else' expression");
1862 return std::pair(std::move(condition), std::move(else_expr));
1865 std::shared_ptr<Expression> parseLogicalOr() {
1866 auto left = parseLogicalAnd();
1867 if (!left)
throw std::runtime_error(
"Expected left side of 'logical or' expression");
1869 static std::regex or_tok(R
"(or\b)");
1870 auto location = get_location();
1871 while (!consumeToken(or_tok).empty()) {
1872 auto right = parseLogicalAnd();
1873 if (!right)
throw std::runtime_error(
"Expected right side of 'or' expression");
1874 left = std::make_shared<BinaryOpExpr>(location, std::move(left), std::move(right),
BinaryOpExpr::Op::Or);
1879 std::shared_ptr<Expression> parseLogicalNot() {
1880 static std::regex not_tok(R
"(not\b)");
1881 auto location = get_location();
1883 if (!consumeToken(not_tok).empty()) {
1884 auto sub = parseLogicalNot();
1885 if (!sub)
throw std::runtime_error(
"Expected expression after 'not' keyword");
1888 return parseLogicalCompare();
1891 std::shared_ptr<Expression> parseLogicalAnd() {
1892 auto left = parseLogicalNot();
1893 if (!left)
throw std::runtime_error(
"Expected left side of 'logical and' expression");
1895 static std::regex and_tok(R
"(and\b)");
1896 auto location = get_location();
1897 while (!consumeToken(and_tok).empty()) {
1898 auto right = parseLogicalNot();
1899 if (!right)
throw std::runtime_error(
"Expected right side of 'and' expression");
1900 left = std::make_shared<BinaryOpExpr>(location, std::move(left), std::move(right),
BinaryOpExpr::Op::And);
1905 std::shared_ptr<Expression> parseLogicalCompare() {
1906 auto left = parseStringConcat();
1907 if (!left)
throw std::runtime_error(
"Expected left side of 'logical compare' expression");
1909 static std::regex compare_tok(R
"(==|!=|<=?|>=?|in\b|is\b|not\s+in\b)");
1910 static std::regex not_tok(R
"(not\b)");
1912 while (!(op_str = consumeToken(compare_tok)).empty()) {
1913 auto location = get_location();
1914 if (op_str ==
"is") {
1915 auto negated = !consumeToken(not_tok).empty();
1917 auto identifier = parseIdentifier();
1918 if (!identifier)
throw std::runtime_error(
"Expected identifier after 'is' keyword");
1920 return std::make_shared<BinaryOpExpr>(
1922 std::move(left), std::move(identifier),
1925 auto right = parseStringConcat();
1926 if (!right)
throw std::runtime_error(
"Expected right side of 'logical compare' expression");
1936 else throw std::runtime_error(
"Unknown comparison operator: " + op_str);
1937 left = std::make_shared<BinaryOpExpr>(get_location(), std::move(left), std::move(right), op);
1944 if (consumeToken(
"(").empty())
throw std::runtime_error(
"Expected opening parenthesis in param list");
1949 if (!consumeToken(
")").empty()) {
1952 auto expr = parseExpression();
1953 if (!expr)
throw std::runtime_error(
"Expected expression in call args");
1955 if (
auto ident =
dynamic_cast<VariableExpr*
>(expr.get())) {
1956 if (!consumeToken(
"=").empty()) {
1957 auto value = parseExpression();
1958 if (!value)
throw std::runtime_error(
"Expected expression in for named arg");
1959 result.emplace_back(ident->get_name(), std::move(value));
1961 result.emplace_back(ident->get_name(),
nullptr);
1964 result.emplace_back(std::string(), std::move(expr));
1966 if (consumeToken(
",").empty()) {
1967 if (consumeToken(
")").empty()) {
1968 throw std::runtime_error(
"Expected closing parenthesis in call args");
1973 throw std::runtime_error(
"Expected closing parenthesis in call args");
1978 if (consumeToken(
"(").empty())
throw std::runtime_error(
"Expected opening parenthesis in call args");
1983 if (!consumeToken(
")").empty()) {
1986 auto expr = parseExpression();
1987 if (!expr)
throw std::runtime_error(
"Expected expression in call args");
1989 if (
auto ident =
dynamic_cast<VariableExpr*
>(expr.get())) {
1990 if (!consumeToken(
"=").empty()) {
1991 auto value = parseExpression();
1992 if (!value)
throw std::runtime_error(
"Expected expression in for named arg");
1993 result.
kwargs.emplace_back(ident->get_name(), std::move(value));
1995 result.
args.emplace_back(std::move(expr));
1998 result.
args.emplace_back(std::move(expr));
2000 if (consumeToken(
",").empty()) {
2001 if (consumeToken(
")").empty()) {
2002 throw std::runtime_error(
"Expected closing parenthesis in call args");
2007 throw std::runtime_error(
"Expected closing parenthesis in call args");
2010 std::shared_ptr<VariableExpr> parseIdentifier() {
2011 static std::regex ident_regex(R
"((?!(?:not|is|and|or|del)\b)[a-zA-Z_]\w*)");
2012 auto location = get_location();
2013 auto ident = consumeToken(ident_regex);
2016 return std::make_shared<VariableExpr>(location, ident);
2019 std::shared_ptr<Expression> parseStringConcat() {
2020 auto left = parseMathPow();
2021 if (!left)
throw std::runtime_error(
"Expected left side of 'string concat' expression");
2023 static std::regex concat_tok(R
"(~(?!\}))");
2024 if (!consumeToken(concat_tok).empty()) {
2025 auto right = parseLogicalAnd();
2026 if (!right)
throw std::runtime_error(
"Expected right side of 'string concat' expression");
2032 std::shared_ptr<Expression> parseMathPow() {
2033 auto left = parseMathPlusMinus();
2034 if (!left)
throw std::runtime_error(
"Expected left side of 'math pow' expression");
2036 while (!consumeToken(
"**").empty()) {
2037 auto right = parseMathPlusMinus();
2038 if (!right)
throw std::runtime_error(
"Expected right side of 'math pow' expression");
2039 left = std::make_shared<BinaryOpExpr>(get_location(), std::move(left), std::move(right),
BinaryOpExpr::Op::MulMul);
2044 std::shared_ptr<Expression> parseMathPlusMinus() {
2045 static std::regex plus_minus_tok(R
"(\+|-(?![}%#]\}))");
2047 auto left = parseMathMulDiv();
2048 if (!left)
throw std::runtime_error(
"Expected left side of 'math plus/minus' expression");
2050 while (!(op_str = consumeToken(plus_minus_tok)).empty()) {
2051 auto right = parseMathMulDiv();
2052 if (!right)
throw std::runtime_error(
"Expected right side of 'math plus/minus' expression");
2054 left = std::make_shared<BinaryOpExpr>(get_location(), std::move(left), std::move(right), op);
2059 std::shared_ptr<Expression> parseMathMulDiv() {
2060 auto left = parseMathUnaryPlusMinus();
2061 if (!left)
throw std::runtime_error(
"Expected left side of 'math mul/div' expression");
2063 static std::regex mul_div_tok(R
"(\*\*?|//?|%(?!\}))");
2065 while (!(op_str = consumeToken(mul_div_tok)).empty()) {
2066 auto right = parseMathUnaryPlusMinus();
2067 if (!right)
throw std::runtime_error(
"Expected right side of 'math mul/div' expression");
2073 left = std::make_shared<BinaryOpExpr>(get_location(), std::move(left), std::move(right), op);
2076 if (!consumeToken(
"|").empty()) {
2077 auto expr = parseMathMulDiv();
2078 if (
auto filter =
dynamic_cast<FilterExpr*
>(expr.get())) {
2079 filter->prepend(std::move(left));
2082 std::vector<std::shared_ptr<Expression>> parts;
2083 parts.emplace_back(std::move(left));
2084 parts.emplace_back(std::move(expr));
2085 return std::make_shared<FilterExpr>(get_location(), std::move(parts));
2091 std::shared_ptr<Expression> call_func(
const std::string & name,
ArgumentsExpression && args)
const {
2092 return std::make_shared<CallExpr>(get_location(), std::make_shared<VariableExpr>(get_location(), name), std::move(args));
2095 std::shared_ptr<Expression> parseMathUnaryPlusMinus() {
2096 static std::regex unary_plus_minus_tok(R
"(\+|-(?![}%#]\}))");
2097 auto op_str = consumeToken(unary_plus_minus_tok);
2098 auto expr = parseExpansion();
2099 if (!expr)
throw std::runtime_error(
"Expected expr of 'unary plus/minus/expansion' expression");
2101 if (!op_str.empty()) {
2103 return std::make_shared<UnaryOpExpr>(get_location(), std::move(expr), op);
2108 std::shared_ptr<Expression> parseExpansion() {
2109 static std::regex expansion_tok(R
"(\*\*?)");
2110 auto op_str = consumeToken(expansion_tok);
2111 auto expr = parseValueExpression();
2112 if (op_str.empty())
return expr;
2113 if (!expr)
throw std::runtime_error(
"Expected expr of 'expansion' expression");
2117 std::shared_ptr<Expression> parseValueExpression() {
2118 auto parseValue = [&]() -> std::shared_ptr<Expression> {
2119 auto location = get_location();
2120 auto constant = parseConstant();
2121 if (constant)
return std::make_shared<LiteralExpr>(location, *constant);
2123 static std::regex null_regex(R
"(null\b)");
2124 if (!consumeToken(null_regex).empty())
return std::make_shared<LiteralExpr>(location,
Value());
2126 auto identifier = parseIdentifier();
2127 if (identifier)
return identifier;
2129 auto braced = parseBracedExpressionOrArray();
2130 if (braced)
return braced;
2132 auto array = parseArray();
2133 if (array)
return array;
2135 auto dictionary = parseDictionary();
2136 if (dictionary)
return dictionary;
2138 throw std::runtime_error(
"Expected value expression");
2141 auto value = parseValue();
2143 while (it != end && consumeSpaces() && peekSymbols({
"[",
"." })) {
2144 if (!consumeToken(
"[").empty()) {
2145 std::shared_ptr<Expression> index;
2146 auto slice_loc = get_location();
2147 std::shared_ptr<Expression> start, end, step;
2148 bool has_first_colon =
false, has_second_colon =
false;
2150 if (!peekSymbols({
":" })) {
2151 start = parseExpression();
2154 if (!consumeToken(
":").empty()) {
2155 has_first_colon =
true;
2156 if (!peekSymbols({
":",
"]" })) {
2157 end = parseExpression();
2159 if (!consumeToken(
":").empty()) {
2160 has_second_colon =
true;
2161 if (!peekSymbols({
"]" })) {
2162 step = parseExpression();
2167 if ((has_first_colon || has_second_colon)) {
2168 index = std::make_shared<SliceExpr>(slice_loc, std::move(start), std::move(end), std::move(step));
2170 index = std::move(start);
2172 if (!index)
throw std::runtime_error(
"Empty index in subscript");
2173 if (consumeToken(
"]").empty())
throw std::runtime_error(
"Expected closing bracket in subscript");
2175 value = std::make_shared<SubscriptExpr>(value->location, std::move(value), std::move(index));
2176 }
else if (!consumeToken(
".").empty()) {
2177 auto identifier = parseIdentifier();
2178 if (!identifier)
throw std::runtime_error(
"Expected identifier in subscript");
2181 if (peekSymbols({
"(" })) {
2182 auto callParams = parseCallArgs();
2183 value = std::make_shared<MethodCallExpr>(identifier->location, std::move(value), std::move(identifier), std::move(callParams));
2185 auto key = std::make_shared<LiteralExpr>(identifier->location,
Value(identifier->get_name()));
2186 value = std::make_shared<SubscriptExpr>(identifier->location, std::move(value), std::move(key));
2192 if (peekSymbols({
"(" })) {
2193 auto location = get_location();
2194 auto callParams = parseCallArgs();
2195 value = std::make_shared<CallExpr>(location, std::move(value), std::move(callParams));
2200 std::shared_ptr<Expression> parseBracedExpressionOrArray() {
2201 if (consumeToken(
"(").empty())
return nullptr;
2203 auto expr = parseExpression();
2204 if (!expr)
throw std::runtime_error(
"Expected expression in braced expression");
2206 if (!consumeToken(
")").empty()) {
2210 std::vector<std::shared_ptr<Expression>> tuple;
2211 tuple.emplace_back(std::move(expr));
2214 if (consumeToken(
",").empty())
throw std::runtime_error(
"Expected comma in tuple");
2215 auto next = parseExpression();
2216 if (!next)
throw std::runtime_error(
"Expected expression in tuple");
2217 tuple.push_back(std::move(next));
2219 if (!consumeToken(
")").empty()) {
2220 return std::make_shared<ArrayExpr>(get_location(), std::move(tuple));
2223 throw std::runtime_error(
"Expected closing parenthesis");
2226 std::shared_ptr<Expression> parseArray() {
2227 if (consumeToken(
"[").empty())
return nullptr;
2229 std::vector<std::shared_ptr<Expression>> elements;
2230 if (!consumeToken(
"]").empty()) {
2231 return std::make_shared<ArrayExpr>(get_location(), std::move(elements));
2233 auto first_expr = parseExpression();
2234 if (!first_expr)
throw std::runtime_error(
"Expected first expression in array");
2235 elements.push_back(std::move(first_expr));
2238 if (!consumeToken(
",").empty()) {
2239 auto expr = parseExpression();
2240 if (!expr)
throw std::runtime_error(
"Expected expression in array");
2241 elements.push_back(std::move(expr));
2242 }
else if (!consumeToken(
"]").empty()) {
2243 return std::make_shared<ArrayExpr>(get_location(), std::move(elements));
2245 throw std::runtime_error(
"Expected comma or closing bracket in array");
2248 throw std::runtime_error(
"Expected closing bracket");
2251 std::shared_ptr<Expression> parseDictionary() {
2252 if (consumeToken(
"{").empty())
return nullptr;
2254 std::vector<std::pair<std::shared_ptr<Expression>, std::shared_ptr<Expression>>> elements;
2255 if (!consumeToken(
"}").empty()) {
2256 return std::make_shared<DictExpr>(get_location(), std::move(elements));
2259 auto parseKeyValuePair = [&]() {
2260 auto key = parseExpression();
2261 if (!key)
throw std::runtime_error(
"Expected key in dictionary");
2262 if (consumeToken(
":").empty())
throw std::runtime_error(
"Expected colon betweek key & value in dictionary");
2263 auto value = parseExpression();
2264 if (!value)
throw std::runtime_error(
"Expected value in dictionary");
2265 elements.emplace_back(std::pair(std::move(key), std::move(value)));
2268 parseKeyValuePair();
2271 if (!consumeToken(
",").empty()) {
2272 parseKeyValuePair();
2273 }
else if (!consumeToken(
"}").empty()) {
2274 return std::make_shared<DictExpr>(get_location(), std::move(elements));
2276 throw std::runtime_error(
"Expected comma or closing brace in dictionary");
2279 throw std::runtime_error(
"Expected closing brace");
2293 using TemplateTokenVector = std::vector<std::unique_ptr<TemplateToken>>;
2294 using TemplateTokenIterator = TemplateTokenVector::const_iterator;
2296 std::vector<std::string> parseVarNames() {
2297 static std::regex varnames_regex(R
"(((?:\w+)(?:\s*,\s*(?:\w+))*)\s*)");
2299 std::vector<std::string> group;
2300 if ((group = consumeTokenGroups(varnames_regex)).empty())
throw std::runtime_error(
"Expected variable names");
2301 std::vector<std::string> varnames;
2302 std::istringstream iss(group[1]);
2303 std::string varname;
2304 while (std::getline(iss, varname,
',')) {
2305 varnames.push_back(
strip(varname));
2310 std::runtime_error unexpected(
const TemplateToken & token)
const {
2314 std::runtime_error unterminated(
const TemplateToken & token)
const {
2319 TemplateTokenVector tokenize() {
2320 static std::regex comment_tok(R
"(\{#([-~]?)([\s\S]*?)([-~]?)#\})");
2321 static std::regex expr_open_regex(R
"(\{\{([-~])?)");
2322 static std::regex block_open_regex(R
"(^\{%([-~])?\s*)");
2323 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)");
2324 static std::regex non_text_open_regex(R
"(\{\{|\{%|\{#)");
2325 static std::regex expr_close_regex(R
"(\s*([-~])?\}\})");
2326 static std::regex block_close_regex(R
"(\s*([-~])?%\})");
2328 TemplateTokenVector tokens;
2329 std::vector<std::string> group;
2335 auto location = get_location();
2338 auto pre_space = parsePreSpace(group[1]);
2339 auto content = group[2];
2340 auto post_space = parsePostSpace(group[3]);
2341 tokens.push_back(std::make_unique<CommentTemplateToken>(location, pre_space, post_space, content));
2343 auto pre_space = parsePreSpace(group[1]);
2344 auto expr = parseExpression();
2346 if ((group = consumeTokenGroups(expr_close_regex)).empty()) {
2347 throw std::runtime_error(
"Expected closing expression tag");
2350 auto post_space = parsePostSpace(group[1]);
2351 tokens.push_back(std::make_unique<ExpressionTemplateToken>(location, pre_space, post_space, std::move(expr)));
2352 }
else if (!(group = consumeTokenGroups(block_open_regex,
SpaceHandling::Keep)).empty()) {
2353 auto pre_space = parsePreSpace(group[1]);
2355 std::string keyword;
2358 if ((group = consumeTokenGroups(block_close_regex)).empty())
throw std::runtime_error(
"Expected closing block tag");
2359 return parsePostSpace(group[1]);
2362 if ((keyword = consumeToken(block_keyword_tok)).empty())
throw std::runtime_error(
"Expected block keyword");
2364 if (keyword ==
"if") {
2365 auto condition = parseExpression();
2366 if (!condition)
throw std::runtime_error(
"Expected condition in if block");
2368 auto post_space = parseBlockClose();
2369 tokens.push_back(std::make_unique<IfTemplateToken>(location, pre_space, post_space, std::move(condition)));
2370 }
else if (keyword ==
"elif") {
2371 auto condition = parseExpression();
2372 if (!condition)
throw std::runtime_error(
"Expected condition in elif block");
2374 auto post_space = parseBlockClose();
2375 tokens.push_back(std::make_unique<ElifTemplateToken>(location, pre_space, post_space, std::move(condition)));
2376 }
else if (keyword ==
"else") {
2377 auto post_space = parseBlockClose();
2378 tokens.push_back(std::make_unique<ElseTemplateToken>(location, pre_space, post_space));
2379 }
else if (keyword ==
"endif") {
2380 auto post_space = parseBlockClose();
2381 tokens.push_back(std::make_unique<EndIfTemplateToken>(location, pre_space, post_space));
2382 }
else if (keyword ==
"for") {
2383 static std::regex recursive_tok(R
"(recursive\b)");
2384 static std::regex if_tok(R
"(if\b)");
2386 auto varnames = parseVarNames();
2387 static std::regex in_tok(R
"(in\b)");
2388 if (consumeToken(in_tok).empty())
throw std::runtime_error(
"Expected 'in' keyword in for block");
2389 auto iterable = parseExpression(
false);
2390 if (!iterable)
throw std::runtime_error(
"Expected iterable in for block");
2392 std::shared_ptr<Expression> condition;
2393 if (!consumeToken(if_tok).empty()) {
2394 condition = parseExpression();
2396 auto recursive = !consumeToken(recursive_tok).empty();
2398 auto post_space = parseBlockClose();
2399 tokens.push_back(std::make_unique<ForTemplateToken>(location, pre_space, post_space, std::move(varnames), std::move(iterable), std::move(condition), recursive));
2400 }
else if (keyword ==
"endfor") {
2401 auto post_space = parseBlockClose();
2402 tokens.push_back(std::make_unique<EndForTemplateToken>(location, pre_space, post_space));
2403 }
else if (keyword ==
"generation") {
2404 auto post_space = parseBlockClose();
2405 tokens.push_back(std::make_unique<GenerationTemplateToken>(location, pre_space, post_space));
2406 }
else if (keyword ==
"endgeneration") {
2407 auto post_space = parseBlockClose();
2408 tokens.push_back(std::make_unique<EndGenerationTemplateToken>(location, pre_space, post_space));
2409 }
else if (keyword ==
"set") {
2410 static std::regex namespaced_var_regex(R
"((\w+)\s*\.\s*(\w+))");
2413 std::vector<std::string> var_names;
2414 std::shared_ptr<Expression> value;
2415 if (!(group = consumeTokenGroups(namespaced_var_regex)).empty()) {
2417 var_names.push_back(group[2]);
2419 if (consumeToken(
"=").empty())
throw std::runtime_error(
"Expected equals sign in set block");
2421 value = parseExpression();
2422 if (!value)
throw std::runtime_error(
"Expected value in set block");
2424 var_names = parseVarNames();
2426 if (!consumeToken(
"=").empty()) {
2427 value = parseExpression();
2428 if (!value)
throw std::runtime_error(
"Expected value in set block");
2431 auto post_space = parseBlockClose();
2432 tokens.push_back(std::make_unique<SetTemplateToken>(location, pre_space, post_space, ns, var_names, std::move(value)));
2433 }
else if (keyword ==
"endset") {
2434 auto post_space = parseBlockClose();
2435 tokens.push_back(std::make_unique<EndSetTemplateToken>(location, pre_space, post_space));
2436 }
else if (keyword ==
"macro") {
2437 auto macroname = parseIdentifier();
2438 if (!macroname)
throw std::runtime_error(
"Expected macro name in macro block");
2439 auto params = parseParameters();
2441 auto post_space = parseBlockClose();
2442 tokens.push_back(std::make_unique<MacroTemplateToken>(location, pre_space, post_space, std::move(macroname), std::move(params)));
2443 }
else if (keyword ==
"endmacro") {
2444 auto post_space = parseBlockClose();
2445 tokens.push_back(std::make_unique<EndMacroTemplateToken>(location, pre_space, post_space));
2446 }
else if (keyword ==
"filter") {
2447 auto filter = parseExpression();
2448 if (!filter)
throw std::runtime_error(
"Expected expression in filter block");
2450 auto post_space = parseBlockClose();
2451 tokens.push_back(std::make_unique<FilterTemplateToken>(location, pre_space, post_space, std::move(filter)));
2452 }
else if (keyword ==
"endfilter") {
2453 auto post_space = parseBlockClose();
2454 tokens.push_back(std::make_unique<EndFilterTemplateToken>(location, pre_space, post_space));
2455 }
else if (keyword ==
"break" || keyword ==
"continue") {
2456 auto post_space = parseBlockClose();
2459 throw std::runtime_error(
"Unexpected block: " + keyword);
2461 }
else if (std::regex_search(it, end, match, non_text_open_regex)) {
2462 if (!match.position()) {
2463 if (match[0] !=
"{#")
2464 throw std::runtime_error(
"Internal error: Expected a comment");
2465 throw std::runtime_error(
"Missing end of comment tag");
2467 auto text_end = it + match.position();
2468 text = std::string(it, text_end);
2472 text = std::string(it, end);
2478 }
catch (
const std::exception & e) {
2479 throw std::runtime_error(e.what() +
error_location_suffix(*template_str, std::distance(start, it)));
2483 std::shared_ptr<TemplateNode> parseTemplate(
2484 const TemplateTokenIterator & begin,
2485 TemplateTokenIterator & it,
2486 const TemplateTokenIterator & end,
2487 bool fully =
false)
const {
2488 std::vector<std::shared_ptr<TemplateNode>> children;
2490 const auto start = it;
2491 const auto & token = *(it++);
2493 std::vector<std::pair<std::shared_ptr<Expression>, std::shared_ptr<TemplateNode>>> cascade;
2494 cascade.emplace_back(std::move(if_token->condition), parseTemplate(begin, it, end));
2498 cascade.emplace_back(std::move(elif_token->condition), parseTemplate(begin, it, end));
2502 cascade.emplace_back(
nullptr, parseTemplate(begin, ++it, end));
2505 throw unterminated(**start);
2507 children.emplace_back(std::make_shared<IfNode>(token->location, std::move(cascade)));
2508 }
else if (
auto for_token =
dynamic_cast<ForTemplateToken*
>(token.get())) {
2509 auto body = parseTemplate(begin, it, end);
2510 auto else_body = std::shared_ptr<TemplateNode>();
2512 else_body = parseTemplate(begin, ++it, end);
2515 throw unterminated(**start);
2517 children.emplace_back(std::make_shared<ForNode>(token->location, std::move(for_token->var_names), std::move(for_token->iterable), std::move(for_token->condition), std::move(body), for_token->recursive, std::move(else_body)));
2519 auto body = parseTemplate(begin, it, end);
2521 throw unterminated(**start);
2524 children.emplace_back(std::move(body));
2529 auto text = text_token->text;
2531 static std::regex trailing_space_regex(R
"(\s+$)");
2532 text = std::regex_replace(text, trailing_space_regex, "");
2534 auto i = text.size();
2535 while (i > 0 && (text[i - 1] ==
' ' || text[i - 1] ==
'\t')) i--;
2536 if ((i == 0 && (it - 1) == begin) || (i > 0 && text[i - 1] ==
'\n')) {
2541 static std::regex leading_space_regex(R
"(^\s+)");
2542 text = std::regex_replace(text, leading_space_regex, "");
2544 if (!text.empty() && text[0] ==
'\n') {
2549 auto i = text.size();
2550 if (i > 0 && text[i - 1] ==
'\n') {
2552 if (i > 0 && text[i - 1] ==
'\r') i--;
2556 children.emplace_back(std::make_shared<TextNode>(token->location, text));
2558 children.emplace_back(std::make_shared<ExpressionNode>(token->location, std::move(expr_token->expr)));
2559 }
else if (
auto set_token =
dynamic_cast<SetTemplateToken*
>(token.get())) {
2560 if (set_token->value) {
2561 children.emplace_back(std::make_shared<SetNode>(token->location, set_token->ns, set_token->var_names, std::move(set_token->value)));
2563 auto value_template = parseTemplate(begin, it, end);
2565 throw unterminated(**start);
2567 if (!set_token->ns.empty())
throw std::runtime_error(
"Namespaced set not supported in set with template value");
2568 if (set_token->var_names.size() != 1)
throw std::runtime_error(
"Structural assignment not supported in set with template value");
2569 auto & name = set_token->var_names[0];
2570 children.emplace_back(std::make_shared<SetTemplateNode>(token->location, name, std::move(value_template)));
2573 auto body = parseTemplate(begin, it, end);
2575 throw unterminated(**start);
2577 children.emplace_back(std::make_shared<MacroNode>(token->location, std::move(macro_token->name), std::move(macro_token->params), std::move(body)));
2579 auto body = parseTemplate(begin, it, end);
2581 throw unterminated(**start);
2583 children.emplace_back(std::make_shared<FilterNode>(token->location, std::move(filter_token->filter), std::move(body)));
2587 children.emplace_back(std::make_shared<LoopControlNode>(token->location, ctrl_token->control_type));
2599 throw unexpected(**(it-1));
2602 if (fully && it != end) {
2603 throw unexpected(**it);
2605 if (children.empty()) {
2606 return std::make_shared<TextNode>(
Location { template_str, 0 }, std::string());
2607 }
else if (children.size() == 1) {
2608 return std::move(children[0]);
2610 return std::make_shared<SequenceNode>(children[0]->location(), std::move(children));
2616 static std::shared_ptr<TemplateNode>
parse(
const std::string& template_str,
const Options & options) {
2618 auto tokens = parser.tokenize();
2619 TemplateTokenIterator begin = tokens.begin();
2621 TemplateTokenIterator end = tokens.end();
2622 return parser.parseTemplate(begin, it, end,
true);
2658 throw std::runtime_error(args.at(
"message").get<std::string>());
2661 return Value(args.at(
"value").dump(args.get<
int64_t>(
"indent", -1),
true));
2665 if (args.contains(
"object")) {
2666 auto &
obj = args.
at(
"object");
2668 throw std::runtime_error(
"Can only get item pairs from a mapping");
2677 auto items = args.
at(
"items");
2678 if (!
items.
is_array())
throw std::runtime_error(
"object is not a list");
2683 auto & text = args.at(
"text");
2684 return text.is_null() ? text :
Value(
strip(text.get<std::string>()));
2688 auto text = args.at(
"text");
2689 if (text.is_null())
return text;
2691 auto str = text.
get<std::string>();
2692 std::transform(
str.begin(),
str.end(), std::back_inserter(
res),
fn);
2700 auto & value = args.
args[0];
2702 bool boolean =
false;
2703 if (args.
args.size() == 3) {
2704 boolean = args.args[2].get<bool>();
2706 Value bv = args.get_named(
"boolean");
2707 if (!bv.is_null()) {
2708 boolean = bv.get<bool>();
2713 auto escape =
simple_function(
"escape", {
"text" }, [](
const std::shared_ptr<Context> &, Value & args) {
2714 return Value(
html_escape(args.at(
"text").get<std::string>()));
2716 globals.
set(
"e", escape);
2717 globals.set(
"escape", escape);
2718 globals.set(
"joiner",
simple_function(
"joiner", {
"sep" }, [](
const std::shared_ptr<Context> &, Value & args) {
2719 auto sep = args.get<std::string>(
"sep",
"");
2720 auto first = std::make_shared<bool>(
true);
2721 return simple_function(
"", {}, [sep, first](
const std::shared_ptr<Context> &,
const Value &) -> Value {
2728 return Value(
html_escape(args.at(
"text").get<std::string>()));
2730 globals.set(
"count",
simple_function(
"count", {
"items" }, [](
const std::shared_ptr<Context> &, Value & args) {
2731 return Value((int64_t) args.at(
"items").size());
2733 globals.set(
"dictsort",
simple_function(
"dictsort", {
"value" }, [](
const std::shared_ptr<Context> &, Value & args) {
2734 if (args.size() != 1)
throw std::runtime_error(
"dictsort expects exactly 1 argument (TODO: fix implementation)");
2735 auto & value = args.at(
"value");
2736 auto keys = value.keys();
2737 std::sort(keys.begin(), keys.end());
2739 for (
auto & key : keys) {
2744 globals.set(
"join",
simple_function(
"join", {
"items",
"d" }, [](
const std::shared_ptr<Context> &, Value & args) {
2745 auto do_join = [](Value & items,
const std::string & sep) {
2746 if (!items.is_array())
throw std::runtime_error(
"object is not iterable: " + items.dump());
2747 std::ostringstream oss;
2749 for (
size_t i = 0, n = items.size(); i < n; ++i) {
2750 if (first) first =
false;
2752 oss << items.at(i).to_str();
2754 return Value(oss.str());
2756 auto sep = args.get<std::string>(
"d",
"");
2757 if (args.contains(
"items")) {
2758 auto & items = args.at(
"items");
2759 return do_join(items, sep);
2761 return simple_function(
"", {
"items"}, [sep, do_join](
const std::shared_ptr<Context> &, Value & args) {
2762 auto & items = args.at(
"items");
2763 if (!items.to_bool() || !items.is_array())
throw std::runtime_error(
"join expects an array for items, got: " + items.dump());
2764 return do_join(items, sep);
2768 globals.set(
"namespace",
Value::callable([=](
const std::shared_ptr<Context> &, ArgumentsValue & args) {
2770 args.expectArgs(
"namespace", {0, 0}, {0, (std::numeric_limits<size_t>::max)()});
2771 for (
auto & [name, value] : args.kwargs) {
2772 ns.set(name, value);
2776 auto equalto =
simple_function(
"equalto", {
"expected",
"actual" }, [](
const std::shared_ptr<Context> &, Value & args) -> Value {
2777 return args.at(
"actual") == args.at(
"expected");
2779 globals.
set(
"equalto", equalto);
2780 globals.set(
"==", equalto);
2781 globals.set(
"length",
simple_function(
"length", {
"items" }, [](
const std::shared_ptr<Context> &, Value & args) -> Value {
2782 auto & items = args.at(
"items");
2783 return (int64_t) items.size();
2785 globals.set(
"safe",
simple_function(
"safe", {
"value" }, [](
const std::shared_ptr<Context> &, Value & args) -> Value {
2786 return args.at(
"value").to_str();
2788 globals.set(
"string",
simple_function(
"string", {
"value" }, [](
const std::shared_ptr<Context> &, Value & args) -> Value {
2789 return args.at(
"value").to_str();
2791 globals.set(
"int",
simple_function(
"int", {
"value" }, [](
const std::shared_ptr<Context> &, Value & args) -> Value {
2792 return args.at(
"value").to_int();
2794 globals.set(
"list",
simple_function(
"list", {
"items" }, [](
const std::shared_ptr<Context> &, Value & args) -> Value {
2795 auto & items = args.at(
"items");
2796 if (!items.is_array())
throw std::runtime_error(
"object is not iterable");
2799 globals.set(
"in",
simple_function(
"in", {
"item",
"items" }, [](
const std::shared_ptr<Context> &, Value & args) -> Value {
2800 return in(args.at(
"item"), args.at(
"items"));
2802 globals.set(
"unique",
simple_function(
"unique", {
"items" }, [](
const std::shared_ptr<Context> &, Value & args) -> Value {
2803 auto & items = args.at(
"items");
2804 if (!items.is_array())
throw std::runtime_error(
"object is not iterable");
2805 std::unordered_set<Value> seen;
2807 for (
size_t i = 0, n = items.size(); i < n; i++) {
2808 auto pair = seen.insert(items.at(i));
2810 result.push_back(items.at(i));
2815 auto make_filter = [](
const Value & filter, Value & extra_args) -> Value {
2816 return simple_function(
"", {
"value" }, [=](
const std::shared_ptr<Context> & context, Value & args) {
2817 auto & value = args.at(
"value");
2818 ArgumentsValue actual_args;
2819 actual_args.args.emplace_back(value);
2820 for (
size_t i = 0, n = extra_args.size(); i < n; i++) {
2821 actual_args.args.emplace_back(extra_args.at(i));
2823 return filter.call(context, actual_args);
2826 auto select_or_reject = [make_filter](
bool is_select) {
2827 return Value::callable([=](
const std::shared_ptr<Context> & context, ArgumentsValue & args) {
2828 args.expectArgs(is_select ?
"select" :
"reject", {2, (std::numeric_limits<size_t>::max)()}, {0, 0});
2829 auto & items = args.args[0];
2830 if (items.is_null()) {
2833 if (!items.is_array()) {
2834 throw std::runtime_error(
"object is not iterable: " + items.dump());
2837 auto filter_fn = context->get(args.args[1]);
2838 if (filter_fn.is_null()) {
2839 throw std::runtime_error(
"Undefined filter: " + args.args[1].dump());
2843 for (
size_t i = 2, n = args.args.size(); i < n; i++) {
2844 filter_args.push_back(args.args[i]);
2846 auto filter = make_filter(filter_fn, filter_args);
2849 for (
size_t i = 0, n = items.size(); i < n; i++) {
2850 auto & item = items.at(i);
2851 ArgumentsValue filter_args;
2852 filter_args.args.emplace_back(item);
2853 auto pred_res = filter.call(context, filter_args);
2854 if (pred_res.to_bool() == (is_select ? true :
false)) {
2855 res.push_back(item);
2861 globals.
set(
"select", select_or_reject(
true));
2862 globals.set(
"reject", select_or_reject(
false));
2863 globals.set(
"map",
Value::callable([=](
const std::shared_ptr<Context> & context, ArgumentsValue & args) {
2865 if (args.args.size() == 1 &&
2866 ((args.has_named(
"attribute") && args.kwargs.size() == 1) || (args.has_named(
"default") && args.kwargs.size() == 2))) {
2867 auto & items = args.args[0];
2868 auto attr_name = args.get_named(
"attribute");
2869 auto default_value = args.get_named(
"default");
2870 for (
size_t i = 0, n = items.size(); i < n; i++) {
2871 auto & item = items.at(i);
2872 auto attr = item.get(attr_name);
2873 res.push_back(attr.is_null() ? default_value : attr);
2875 }
else if (args.kwargs.empty() && args.args.size() >= 2) {
2876 auto fn = context->get(args.args[1]);
2877 if (fn.is_null())
throw std::runtime_error(
"Undefined filter: " + args.args[1].dump());
2878 ArgumentsValue filter_args { {Value()}, {} };
2879 for (
size_t i = 2, n = args.args.size(); i < n; i++) {
2880 filter_args.args.emplace_back(args.args[i]);
2882 for (
size_t i = 0, n = args.args[0].size(); i < n; i++) {
2883 auto & item = args.args[0].at(i);
2884 filter_args.args[0] = item;
2885 res.push_back(fn.call(context, filter_args));
2888 throw std::runtime_error(
"Invalid or unsupported arguments for map");
2892 globals.set(
"indent",
simple_function(
"indent", {
"text",
"indent",
"first" }, [](
const std::shared_ptr<Context> &, Value & args) {
2893 auto text = args.at(
"text").get<std::string>();
2894 auto first = args.get<
bool>(
"first",
false);
2896 std::string indent(args.get<int64_t>(
"indent", 0),
' ');
2897 std::istringstream iss(text);
2899 auto is_first =
true;
2900 while (std::getline(iss, line,
'\n')) {
2901 auto needs_indent = !is_first || first;
2902 if (is_first) is_first =
false;
2904 if (needs_indent) out += indent;
2907 if (!text.empty() && text.back() ==
'\n') out +=
"\n";
2910 auto select_or_reject_attr = [](
bool is_select) {
2911 return Value::callable([=](
const std::shared_ptr<Context> & context, ArgumentsValue & args) {
2912 args.expectArgs(is_select ?
"selectattr" :
"rejectattr", {2, (std::numeric_limits<size_t>::max)()}, {0, 0});
2913 auto & items = args.args[0];
2914 if (items.is_null())
2916 if (!items.is_array())
throw std::runtime_error(
"object is not iterable: " + items.dump());
2917 auto attr_name = args.args[1].
get<std::string>();
2919 bool has_test =
false;
2921 ArgumentsValue test_args {{Value()}, {}};
2922 if (args.args.size() >= 3) {
2924 test_fn = context->
get(args.args[2]);
2925 if (test_fn.is_null())
throw std::runtime_error(
"Undefined test: " + args.args[2].dump());
2926 for (
size_t i = 3, n = args.args.size(); i < n; i++) {
2927 test_args.args.emplace_back(args.args[i]);
2929 test_args.kwargs = args.kwargs;
2933 for (
size_t i = 0, n = items.size(); i < n; i++) {
2934 auto & item = items.at(i);
2935 auto attr = item.get(attr_name);
2937 test_args.args[0] = attr;
2938 if (test_fn.call(context, test_args).to_bool() == (is_select ? true :
false)) {
2939 res.push_back(item);
2942 res.push_back(attr);
2948 globals.
set(
"selectattr", select_or_reject_attr(
true));
2949 globals.set(
"rejectattr", select_or_reject_attr(
false));
2950 globals.set(
"range",
Value::callable([=](
const std::shared_ptr<Context> &, ArgumentsValue & args) {
2951 std::vector<int64_t> startEndStep(3);
2952 std::vector<bool> param_set(3);
2953 if (args.args.size() == 1) {
2954 startEndStep[1] = args.args[0].get<int64_t>();
2955 param_set[1] =
true;
2957 for (
size_t i = 0; i < args.args.size(); i++) {
2958 auto & arg = args.args[i];
2959 auto v = arg.get<int64_t>();
2960 startEndStep[i] = v;
2961 param_set[i] =
true;
2964 for (
auto & [name, value] : args.kwargs) {
2966 if (name ==
"start") {
2968 }
else if (name ==
"end") {
2970 }
else if (name ==
"step") {
2973 throw std::runtime_error(
"Unknown argument " + name +
" for function range");
2977 throw std::runtime_error(
"Duplicate argument " + name +
" for function range");
2979 startEndStep[i] = value.get<int64_t>();
2980 param_set[i] =
true;
2982 if (!param_set[1]) {
2983 throw std::runtime_error(
"Missing required argument 'end' for function range");
2985 int64_t start = param_set[0] ? startEndStep[0] : 0;
2986 int64_t end = startEndStep[1];
2987 int64_t step = param_set[2] ? startEndStep[2] : 1;
2991 for (int64_t i = start; i < end; i += step) {
2992 res.push_back(Value(i));
2995 for (int64_t i = start; i > end; i += step) {
2996 res.push_back(Value(i));
3002 return std::make_shared<Context>(std::move(globals));