This commit is contained in:
ochafik 2024-12-07 02:15:51 +00:00
parent 1fd5f1af08
commit 5d0033f57a

View file

@ -20,12 +20,6 @@
using json = nlohmann::ordered_json; using json = nlohmann::ordered_json;
/* Backport make_unique from C++14. */
template <class T, class... Args>
typename std::unique_ptr<T> nonstd_make_unique(Args &&...args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
namespace minja { namespace minja {
class Context; class Context;
@ -36,42 +30,13 @@ struct Options {
bool keep_trailing_newline; // don't remove last newline bool keep_trailing_newline; // don't remove last newline
}; };
struct ArgumentsValue;
/* Values that behave roughly like in Python. */ /* Values that behave roughly like in Python. */
class Value : public std::enable_shared_from_this<Value> { class Value : public std::enable_shared_from_this<Value> {
public: public:
struct Arguments { using CallableType = std::function<Value(const std::shared_ptr<Context> &, ArgumentsValue &)>;
std::vector<Value> args; using FilterType = std::function<Value(const std::shared_ptr<Context> &, ArgumentsValue &)>;
std::vector<std::pair<std::string, Value>> kwargs;
bool has_named(const std::string & name) {
for (const auto & p : kwargs) {
if (p.first == name) return true;
}
return false;
}
Value get_named(const std::string & name) {
for (const auto & p : kwargs) {
if (p.first == name) return p.second;
}
return Value();
}
bool empty() {
return args.empty() && kwargs.empty();
}
void expectArgs(const std::string & method_name, const std::pair<size_t, size_t> & pos_count, const std::pair<size_t, size_t> & kw_count) {
if (args.size() < pos_count.first || args.size() > pos_count.second || kwargs.size() < kw_count.first || kwargs.size() > kw_count.second) {
std::ostringstream out;
out << method_name << " must have between " << pos_count.first << " and " << pos_count.second << " positional arguments and between " << kw_count.first << " and " << kw_count.second << " keyword arguments";
throw std::runtime_error(out.str());
}
}
};
using CallableType = std::function<Value(const std::shared_ptr<Context> &, Arguments &)>;
using FilterType = std::function<Value(const std::shared_ptr<Context> &, Arguments &)>;
private: private:
using ObjectType = nlohmann::ordered_map<json, Value>; // Only contains primitive keys using ObjectType = nlohmann::ordered_map<json, Value>; // Only contains primitive keys
@ -246,7 +211,7 @@ public:
if (!key.is_hashable()) throw std::runtime_error("Unashable type: " + dump()); if (!key.is_hashable()) throw std::runtime_error("Unashable type: " + dump());
(*object_)[key.primitive_] = value; (*object_)[key.primitive_] = value;
} }
Value call(const std::shared_ptr<Context> & context, Value::Arguments & args) const { Value call(const std::shared_ptr<Context> & context, ArgumentsValue & args) const {
if (!callable_) throw std::runtime_error("Value is not callable: " + dump()); if (!callable_) throw std::runtime_error("Value is not callable: " + dump());
return (*callable_)(context, args); return (*callable_)(context, args);
} }
@ -305,6 +270,20 @@ public:
return true; return true;
} }
int64_t to_int() const {
if (is_null()) return 0;
if (is_boolean()) return get<bool>() ? 1 : 0;
if (is_number()) return static_cast<int64_t>(get<double>());
if (is_string()) {
try {
return std::stol(get<std::string>());
} catch (const std::exception &) {
return 0;
}
}
return 0;
}
bool operator<(const Value & other) const { bool operator<(const Value & other) const {
if (is_null()) if (is_null())
throw std::runtime_error("Undefined value or reference"); throw std::runtime_error("Undefined value or reference");
@ -433,12 +412,18 @@ public:
return dump(); return dump();
} }
Value operator+(const Value& rhs) const { Value operator+(const Value& rhs) const {
if (is_string() || rhs.is_string()) if (is_string() || rhs.is_string()) {
return to_str() + rhs.to_str(); return to_str() + rhs.to_str();
else if (is_number_integer() && rhs.is_number_integer()) } else if (is_number_integer() && rhs.is_number_integer()) {
return get<int64_t>() + rhs.get<int64_t>(); return get<int64_t>() + rhs.get<int64_t>();
else } else if (is_array() && rhs.is_array()) {
auto res = Value::array();
for (const auto& item : *array_) res.push_back(item);
for (const auto& item : *rhs.array_) res.push_back(item);
return res;
} else {
return get<double>() + rhs.get<double>(); return get<double>() + rhs.get<double>();
}
} }
Value operator-(const Value& rhs) const { Value operator-(const Value& rhs) const {
if (is_number_integer() && rhs.is_number_integer()) if (is_number_integer() && rhs.is_number_integer())
@ -449,7 +434,7 @@ public:
Value operator*(const Value& rhs) const { Value operator*(const Value& rhs) const {
if (is_string() && rhs.is_number_integer()) { if (is_string() && rhs.is_number_integer()) {
std::ostringstream out; std::ostringstream out;
for (int i = 0, n = rhs.get<int64_t>(); i < n; ++i) { for (int64_t i = 0, n = rhs.get<int64_t>(); i < n; ++i) {
out << to_str(); out << to_str();
} }
return out.str(); return out.str();
@ -470,6 +455,37 @@ public:
} }
}; };
struct ArgumentsValue {
std::vector<Value> args;
std::vector<std::pair<std::string, Value>> kwargs;
bool has_named(const std::string & name) {
for (const auto & p : kwargs) {
if (p.first == name) return true;
}
return false;
}
Value get_named(const std::string & name) {
for (const auto & [key, value] : kwargs) {
if (key == name) return value;
}
return Value();
}
bool empty() {
return args.empty() && kwargs.empty();
}
void expectArgs(const std::string & method_name, const std::pair<size_t, size_t> & pos_count, const std::pair<size_t, size_t> & kw_count) {
if (args.size() < pos_count.first || args.size() > pos_count.second || kwargs.size() < kw_count.first || kwargs.size() > kw_count.second) {
std::ostringstream out;
out << method_name << " must have between " << pos_count.first << " and " << pos_count.second << " positional arguments and between " << kw_count.first << " and " << kw_count.second << " keyword arguments";
throw std::runtime_error(out.str());
}
}
};
template <> template <>
inline json Value::get<json>() const { inline json Value::get<json>() const {
if (is_primitive()) return primitive_; if (is_primitive()) return primitive_;
@ -483,13 +499,11 @@ inline json Value::get<json>() const {
} }
if (object_) { if (object_) {
json res = json::object(); json res = json::object();
for (const auto& item : *object_) { for (const auto& [key, value] : *object_) {
const auto & key = item.first;
auto json_value = item.second.get<json>();
if (key.is_string()) { if (key.is_string()) {
res[key.get<std::string>()] = json_value; res[key.get<std::string>()] = value.get<json>();
} else if (key.is_primitive()) { } else if (key.is_primitive()) {
res[key.dump()] = json_value; res[key.dump()] = value.get<json>();
} else { } else {
throw std::runtime_error("Invalid key type for conversion to JSON: " + key.dump()); throw std::runtime_error("Invalid key type for conversion to JSON: " + key.dump());
} }
@ -587,30 +601,6 @@ class Expression {
protected: protected:
virtual Value do_evaluate(const std::shared_ptr<Context> & context) const = 0; virtual Value do_evaluate(const std::shared_ptr<Context> & context) const = 0;
public: public:
struct Arguments {
std::vector<std::shared_ptr<Expression>> args;
std::vector<std::pair<std::string, std::shared_ptr<Expression>>> kwargs;
void expectArgs(const std::string & method_name, const std::pair<size_t, size_t> & pos_count, const std::pair<size_t, size_t> & kw_count) const {
if (args.size() < pos_count.first || args.size() > pos_count.second || kwargs.size() < kw_count.first || kwargs.size() > kw_count.second) {
std::ostringstream out;
out << method_name << " must have between " << pos_count.first << " and " << pos_count.second << " positional arguments and between " << kw_count.first << " and " << kw_count.second << " keyword arguments";
throw std::runtime_error(out.str());
}
}
Value::Arguments evaluate(const std::shared_ptr<Context> & context) const {
Value::Arguments vargs;
for (const auto& arg : this->args) {
vargs.args.push_back(arg->evaluate(context));
}
for (const auto& arg : this->kwargs) {
vargs.kwargs.push_back({arg.first, arg.second->evaluate(context)});
}
return vargs;
}
};
using Parameters = std::vector<std::pair<std::string, std::shared_ptr<Expression>>>; using Parameters = std::vector<std::pair<std::string, std::shared_ptr<Expression>>>;
Location location; Location location;
@ -662,7 +652,7 @@ enum SpaceHandling { Keep, Strip, StripSpaces, StripNewline };
class TemplateToken { class TemplateToken {
public: public:
enum class Type { Text, Expression, If, Else, Elif, EndIf, For, EndFor, Set, EndSet, Comment, Macro, EndMacro }; enum class Type { Text, Expression, If, Else, Elif, EndIf, For, EndFor, Set, EndSet, Comment, Macro, EndMacro, Filter, EndFilter };
static std::string typeToString(Type t) { static std::string typeToString(Type t) {
switch (t) { switch (t) {
@ -679,6 +669,8 @@ public:
case Type::Comment: return "comment"; case Type::Comment: return "comment";
case Type::Macro: return "macro"; case Type::Macro: return "macro";
case Type::EndMacro: return "endmacro"; case Type::EndMacro: return "endmacro";
case Type::Filter: return "filter";
case Type::EndFilter: return "endfilter";
} }
return "Unknown"; return "Unknown";
} }
@ -731,6 +723,16 @@ struct EndMacroTemplateToken : public TemplateToken {
EndMacroTemplateToken(const Location & location, SpaceHandling pre, SpaceHandling post) : TemplateToken(Type::EndMacro, location, pre, post) {} EndMacroTemplateToken(const Location & location, SpaceHandling pre, SpaceHandling post) : TemplateToken(Type::EndMacro, location, pre, post) {}
}; };
struct FilterTemplateToken : public TemplateToken {
std::shared_ptr<Expression> filter;
FilterTemplateToken(const Location & location, SpaceHandling pre, SpaceHandling post, std::shared_ptr<Expression> && filter)
: TemplateToken(Type::Filter, location, pre, post), filter(std::move(filter)) {}
};
struct EndFilterTemplateToken : public TemplateToken {
EndFilterTemplateToken(const Location & location, SpaceHandling pre, SpaceHandling post) : TemplateToken(Type::EndFilter, location, pre, post) {}
};
struct ForTemplateToken : public TemplateToken { struct ForTemplateToken : public TemplateToken {
std::vector<std::string> var_names; std::vector<std::string> var_names;
std::shared_ptr<Expression> iterable; std::shared_ptr<Expression> iterable;
@ -886,7 +888,7 @@ public:
loop.set("length", (int64_t) filtered_items.size()); loop.set("length", (int64_t) filtered_items.size());
size_t cycle_index = 0; size_t cycle_index = 0;
loop.set("cycle", Value::callable([&](const std::shared_ptr<Context> &, Value::Arguments & args) { loop.set("cycle", Value::callable([&](const std::shared_ptr<Context> &, ArgumentsValue & args) {
if (args.args.empty() || !args.kwargs.empty()) { if (args.args.empty() || !args.kwargs.empty()) {
throw std::runtime_error("cycle() expects at least 1 positional argument and no named arg"); throw std::runtime_error("cycle() expects at least 1 positional argument and no named arg");
} }
@ -914,7 +916,7 @@ public:
}; };
if (recursive) { if (recursive) {
loop_function = [&](const std::shared_ptr<Context> &, Value::Arguments & args) { loop_function = [&](const std::shared_ptr<Context> &, ArgumentsValue & args) {
if (args.args.size() != 1 || !args.kwargs.empty() || !args.args[0].is_array()) { if (args.args.size() != 1 || !args.kwargs.empty() || !args.args[0].is_array()) {
throw std::runtime_error("loop() expects exactly 1 positional iterable argument"); throw std::runtime_error("loop() expects exactly 1 positional iterable argument");
} }
@ -946,7 +948,7 @@ public:
void do_render(std::ostringstream &, const std::shared_ptr<Context> & macro_context) const override { void do_render(std::ostringstream &, const std::shared_ptr<Context> & macro_context) const override {
if (!name) throw std::runtime_error("MacroNode.name is null"); if (!name) throw std::runtime_error("MacroNode.name is null");
if (!body) throw std::runtime_error("MacroNode.body is null"); if (!body) throw std::runtime_error("MacroNode.body is null");
auto callable = Value::callable([&](const std::shared_ptr<Context> & context, Value::Arguments & args) { auto callable = Value::callable([&](const std::shared_ptr<Context> & context, ArgumentsValue & args) {
auto call_context = macro_context; auto call_context = macro_context;
std::vector<bool> param_set(params.size(), false); std::vector<bool> param_set(params.size(), false);
for (size_t i = 0, n = args.args.size(); i < n; i++) { for (size_t i = 0, n = args.args.size(); i < n; i++) {
@ -956,13 +958,11 @@ public:
auto & param_name = params[i].first; auto & param_name = params[i].first;
call_context->set(param_name, arg); call_context->set(param_name, arg);
} }
for (size_t i = 0, n = args.kwargs.size(); i < n; i++) { for (auto & [arg_name, value] : args.kwargs) {
auto & arg = args.kwargs[i];
auto & arg_name = arg.first;
auto it = named_param_positions.find(arg_name); auto it = named_param_positions.find(arg_name);
if (it == named_param_positions.end()) throw std::runtime_error("Unknown parameter name for macro " + name->get_name() + ": " + arg_name); if (it == named_param_positions.end()) throw std::runtime_error("Unknown parameter name for macro " + name->get_name() + ": " + arg_name);
call_context->set(arg_name, arg.second); call_context->set(arg_name, value);
param_set[it->second] = true; param_set[it->second] = true;
} }
// Set default values for parameters that were not passed // Set default values for parameters that were not passed
@ -978,6 +978,29 @@ public:
} }
}; };
class FilterNode : public TemplateNode {
std::shared_ptr<Expression> filter;
std::shared_ptr<TemplateNode> body;
public:
FilterNode(const Location & location, std::shared_ptr<Expression> && f, std::shared_ptr<TemplateNode> && b)
: TemplateNode(location), filter(std::move(f)), body(std::move(b)) {}
void do_render(std::ostringstream & out, const std::shared_ptr<Context> & context) const override {
if (!filter) throw std::runtime_error("FilterNode.filter is null");
if (!body) throw std::runtime_error("FilterNode.body is null");
auto filter_value = filter->evaluate(context);
if (!filter_value.is_callable()) {
throw std::runtime_error("Filter must be a callable: " + filter_value.dump());
}
std::string rendered_body = body->render(context);
ArgumentsValue filter_args = {{Value(rendered_body)}, {}};
auto result = filter_value.call(context, filter_args);
out << result.to_str();
}
};
class SetNode : public TemplateNode { class SetNode : public TemplateNode {
std::string ns; std::string ns;
std::vector<std::string> var_names; std::vector<std::string> var_names;
@ -1065,10 +1088,10 @@ public:
: Expression(location), elements(std::move(e)) {} : Expression(location), elements(std::move(e)) {}
Value do_evaluate(const std::shared_ptr<Context> & context) const override { Value do_evaluate(const std::shared_ptr<Context> & context) const override {
auto result = Value::object(); auto result = Value::object();
for (const auto& e : elements) { for (const auto& [key, value] : elements) {
if (!e.first) throw std::runtime_error("Dict key is null"); if (!key) throw std::runtime_error("Dict key is null");
if (!e.second) throw std::runtime_error("Dict value is null"); if (!value) throw std::runtime_error("Dict value is null");
result.set(e.first->evaluate(context), e.second->evaluate(context)); result.set(key->evaluate(context), value->evaluate(context));
} }
return result; return result;
} }
@ -1128,11 +1151,9 @@ public:
class UnaryOpExpr : public Expression { class UnaryOpExpr : public Expression {
public: public:
enum class Op { Plus, Minus, LogicalNot }; enum class Op { Plus, Minus, LogicalNot, Expansion, ExpansionDict };
private:
std::shared_ptr<Expression> expr; std::shared_ptr<Expression> expr;
Op op; Op op;
public:
UnaryOpExpr(const Location & location, std::shared_ptr<Expression> && e, Op o) UnaryOpExpr(const Location & location, std::shared_ptr<Expression> && e, Op o)
: Expression(location), expr(std::move(e)), op(o) {} : Expression(location), expr(std::move(e)), op(o) {}
Value do_evaluate(const std::shared_ptr<Context> & context) const override { Value do_evaluate(const std::shared_ptr<Context> & context) const override {
@ -1142,6 +1163,10 @@ public:
case Op::Plus: return e; case Op::Plus: return e;
case Op::Minus: return -e; case Op::Minus: return -e;
case Op::LogicalNot: return !e.to_bool(); case Op::LogicalNot: return !e.to_bool();
case Op::Expansion:
case Op::ExpansionDict:
throw std::runtime_error("Expansion operator is only supported in function calls and collections");
} }
throw std::runtime_error("Unknown unary operator"); throw std::runtime_error("Unknown unary operator");
} }
@ -1217,7 +1242,7 @@ public:
}; };
if (l.is_callable()) { if (l.is_callable()) {
return Value::callable([l, do_eval](const std::shared_ptr<Context> & context, Value::Arguments & args) { return Value::callable([l, do_eval](const std::shared_ptr<Context> & context, ArgumentsValue & args) {
auto ll = l.call(context, args); auto ll = l.call(context, args);
return do_eval(ll); //args[0].second); return do_eval(ll); //args[0].second);
}); });
@ -1227,6 +1252,43 @@ public:
} }
}; };
struct ArgumentsExpression {
std::vector<std::shared_ptr<Expression>> args;
std::vector<std::pair<std::string, std::shared_ptr<Expression>>> kwargs;
ArgumentsValue evaluate(const std::shared_ptr<Context> & context) const {
ArgumentsValue vargs;
for (const auto& arg : this->args) {
if (auto un_expr = std::dynamic_pointer_cast<UnaryOpExpr>(arg)) {
if (un_expr->op == UnaryOpExpr::Op::Expansion) {
auto array = un_expr->expr->evaluate(context);
if (!array.is_array()) {
throw std::runtime_error("Expansion operator only supported on arrays");
}
array.for_each([&](Value & value) {
vargs.args.push_back(value);
});
continue;
} else if (un_expr->op == UnaryOpExpr::Op::ExpansionDict) {
auto dict = un_expr->expr->evaluate(context);
if (!dict.is_object()) {
throw std::runtime_error("ExpansionDict operator only supported on objects");
}
dict.for_each([&](const Value & key) {
vargs.kwargs.push_back({key.get<std::string>(), dict.at(key)});
});
continue;
}
}
vargs.args.push_back(arg->evaluate(context));
}
for (const auto& [name, value] : this->kwargs) {
vargs.kwargs.push_back({name, value->evaluate(context)});
}
return vargs;
}
};
static std::string strip(const std::string & s) { static std::string strip(const std::string & s) {
static std::regex trailing_spaces_regex("^\\s+|\\s+$"); static std::regex trailing_spaces_regex("^\\s+|\\s+$");
return std::regex_replace(s, trailing_spaces_regex, ""); return std::regex_replace(s, trailing_spaces_regex, "");
@ -1251,64 +1313,64 @@ static std::string html_escape(const std::string & s) {
class MethodCallExpr : public Expression { class MethodCallExpr : public Expression {
std::shared_ptr<Expression> object; std::shared_ptr<Expression> object;
std::shared_ptr<VariableExpr> method; std::shared_ptr<VariableExpr> method;
Expression::Arguments args; ArgumentsExpression args;
public: public:
MethodCallExpr(const Location & location, std::shared_ptr<Expression> && obj, std::shared_ptr<VariableExpr> && m, Expression::Arguments && a) MethodCallExpr(const Location & location, std::shared_ptr<Expression> && obj, std::shared_ptr<VariableExpr> && m, ArgumentsExpression && a)
: Expression(location), object(std::move(obj)), method(std::move(m)), args(std::move(a)) {} : Expression(location), object(std::move(obj)), method(std::move(m)), args(std::move(a)) {}
Value do_evaluate(const std::shared_ptr<Context> & context) const override { Value do_evaluate(const std::shared_ptr<Context> & context) const override {
if (!object) throw std::runtime_error("MethodCallExpr.object is null"); if (!object) throw std::runtime_error("MethodCallExpr.object is null");
if (!method) throw std::runtime_error("MethodCallExpr.method is null"); if (!method) throw std::runtime_error("MethodCallExpr.method is null");
auto obj = object->evaluate(context); auto obj = object->evaluate(context);
auto vargs = args.evaluate(context);
if (obj.is_null()) { if (obj.is_null()) {
throw std::runtime_error("Trying to call method '" + method->get_name() + "' on null"); throw std::runtime_error("Trying to call method '" + method->get_name() + "' on null");
} }
if (obj.is_array()) { if (obj.is_array()) {
if (method->get_name() == "append") { if (method->get_name() == "append") {
args.expectArgs("append method", {1, 1}, {0, 0}); vargs.expectArgs("append method", {1, 1}, {0, 0});
obj.push_back(args.args[0]->evaluate(context)); obj.push_back(vargs.args[0]);
return Value(); return Value();
} else if (method->get_name() == "insert") { } else if (method->get_name() == "insert") {
args.expectArgs("insert method", {2, 2}, {0, 0}); vargs.expectArgs("insert method", {2, 2}, {0, 0});
auto index = args.args[0]->evaluate(context).get<int64_t>(); auto index = vargs.args[0].get<int64_t>();
if (index < 0 || index > (int64_t) obj.size()) throw std::runtime_error("Index out of range for insert method"); if (index < 0 || index > (int64_t) obj.size()) throw std::runtime_error("Index out of range for insert method");
obj.insert(index, args.args[1]->evaluate(context)); obj.insert(index, vargs.args[1]);
return Value(); return Value();
} }
} else if (obj.is_object()) { } else if (obj.is_object()) {
if (method->get_name() == "items") { if (method->get_name() == "items") {
args.expectArgs("items method", {0, 0}, {0, 0}); vargs.expectArgs("items method", {0, 0}, {0, 0});
auto result = Value::array(); auto result = Value::array();
for (const auto& key : obj.keys()) { for (const auto& key : obj.keys()) {
result.push_back(Value::array({key, obj.at(key)})); result.push_back(Value::array({key, obj.at(key)}));
} }
return result; return result;
} else if (method->get_name() == "get") { } else if (method->get_name() == "get") {
args.expectArgs("get method", {1, 2}, {0, 0}); vargs.expectArgs("get method", {1, 2}, {0, 0});
auto key = args.args[0]->evaluate(context); auto key = vargs.args[0];
if (args.args.size() == 1) { if (vargs.args.size() == 1) {
return obj.contains(key) ? obj.at(key) : Value(); return obj.contains(key) ? obj.at(key) : Value();
} else { } else {
return obj.contains(key) ? obj.at(key) : args.args[1]->evaluate(context); return obj.contains(key) ? obj.at(key) : vargs.args[1];
} }
} else if (obj.contains(method->get_name())) { } else if (obj.contains(method->get_name())) {
auto callable = obj.at(method->get_name()); auto callable = obj.at(method->get_name());
if (!callable.is_callable()) { if (!callable.is_callable()) {
throw std::runtime_error("Property '" + method->get_name() + "' is not callable"); throw std::runtime_error("Property '" + method->get_name() + "' is not callable");
} }
Value::Arguments vargs = args.evaluate(context);
return callable.call(context, vargs); return callable.call(context, vargs);
} }
} else if (obj.is_string()) { } else if (obj.is_string()) {
auto str = obj.get<std::string>(); auto str = obj.get<std::string>();
if (method->get_name() == "strip") { if (method->get_name() == "strip") {
args.expectArgs("strip method", {0, 0}, {0, 0}); vargs.expectArgs("strip method", {0, 0}, {0, 0});
return Value(strip(str)); return Value(strip(str));
} else if (method->get_name() == "endswith") { } else if (method->get_name() == "endswith") {
args.expectArgs("endswith method", {1, 1}, {0, 0}); vargs.expectArgs("endswith method", {1, 1}, {0, 0});
auto suffix = args.args[0]->evaluate(context).get<std::string>(); auto suffix = vargs.args[0].get<std::string>();
return suffix.length() <= str.length() && std::equal(suffix.rbegin(), suffix.rend(), str.rbegin()); return suffix.length() <= str.length() && std::equal(suffix.rbegin(), suffix.rend(), str.rbegin());
} else if (method->get_name() == "title") { } else if (method->get_name() == "title") {
args.expectArgs("title method", {0, 0}, {0, 0}); vargs.expectArgs("title method", {0, 0}, {0, 0});
auto res = str; auto res = str;
for (size_t i = 0, n = res.size(); i < n; ++i) { for (size_t i = 0, n = res.size(); i < n; ++i) {
if (i == 0 || std::isspace(res[i - 1])) res[i] = std::toupper(res[i]); if (i == 0 || std::isspace(res[i - 1])) res[i] = std::toupper(res[i]);
@ -1324,8 +1386,8 @@ public:
class CallExpr : public Expression { class CallExpr : public Expression {
public: public:
std::shared_ptr<Expression> object; std::shared_ptr<Expression> object;
Expression::Arguments args; ArgumentsExpression args;
CallExpr(const Location & location, std::shared_ptr<Expression> && obj, Expression::Arguments && a) CallExpr(const Location & location, std::shared_ptr<Expression> && obj, ArgumentsExpression && a)
: Expression(location), object(std::move(obj)), args(std::move(a)) {} : Expression(location), object(std::move(obj)), args(std::move(a)) {}
Value do_evaluate(const std::shared_ptr<Context> & context) const override { Value do_evaluate(const std::shared_ptr<Context> & context) const override {
if (!object) throw std::runtime_error("CallExpr.object is null"); if (!object) throw std::runtime_error("CallExpr.object is null");
@ -1354,12 +1416,12 @@ public:
} else { } else {
if (auto ce = dynamic_cast<CallExpr*>(part.get())) { if (auto ce = dynamic_cast<CallExpr*>(part.get())) {
auto target = ce->object->evaluate(context); auto target = ce->object->evaluate(context);
Value::Arguments args = ce->args.evaluate(context); ArgumentsValue args = ce->args.evaluate(context);
args.args.insert(args.args.begin(), result); args.args.insert(args.args.begin(), result);
result = target.call(context, args); result = target.call(context, args);
} else { } else {
auto callable = part->evaluate(context); auto callable = part->evaluate(context);
Value::Arguments args; ArgumentsValue args;
args.args.insert(args.args.begin(), result); args.args.insert(args.args.begin(), result);
result = callable.call(context, args); result = callable.call(context, args);
} }
@ -1421,7 +1483,7 @@ private:
escape = true; escape = true;
} else if (*it == quote) { } else if (*it == quote) {
++it; ++it;
return nonstd_make_unique<std::string>(std::move(result)); return std::make_unique<std::string>(std::move(result));
} else { } else {
result += *it; result += *it;
} }
@ -1568,8 +1630,8 @@ private:
} }
auto location = get_location(); auto location = get_location();
auto if_expr = parseIfExpression(); auto [condition, else_expr] = parseIfExpression();
return std::make_shared<IfExpr>(location, std::move(if_expr.first), std::move(left), std::move(if_expr.second)); return std::make_shared<IfExpr>(location, std::move(condition), std::move(left), std::move(else_expr));
} }
Location get_location() const { Location get_location() const {
@ -1586,7 +1648,7 @@ private:
else_expr = parseExpression(); else_expr = parseExpression();
if (!else_expr) throw std::runtime_error("Expected 'else' expression"); if (!else_expr) throw std::runtime_error("Expected 'else' expression");
} }
return std::make_pair(std::move(condition), std::move(else_expr)); return std::pair(std::move(condition), std::move(else_expr));
} }
std::shared_ptr<Expression> parseLogicalOr() { std::shared_ptr<Expression> parseLogicalOr() {
@ -1700,11 +1762,11 @@ private:
throw std::runtime_error("Expected closing parenthesis in call args"); throw std::runtime_error("Expected closing parenthesis in call args");
} }
Expression::Arguments parseCallArgs() { ArgumentsExpression parseCallArgs() {
consumeSpaces(); consumeSpaces();
if (consumeToken("(").empty()) throw std::runtime_error("Expected opening parenthesis in call args"); if (consumeToken("(").empty()) throw std::runtime_error("Expected opening parenthesis in call args");
Expression::Arguments result; ArgumentsExpression result;
while (it != end) { while (it != end) {
if (!consumeToken(")").empty()) { if (!consumeToken(")").empty()) {
@ -1815,15 +1877,15 @@ private:
return left; return left;
} }
std::shared_ptr<Expression> call_func(const std::string & name, Expression::Arguments && args) const { std::shared_ptr<Expression> call_func(const std::string & name, ArgumentsExpression && args) const {
return std::make_shared<CallExpr>(get_location(), std::make_shared<VariableExpr>(get_location(), name), std::move(args)); return std::make_shared<CallExpr>(get_location(), std::make_shared<VariableExpr>(get_location(), name), std::move(args));
} }
std::shared_ptr<Expression> parseMathUnaryPlusMinus() { std::shared_ptr<Expression> parseMathUnaryPlusMinus() {
static std::regex unary_plus_minus_tok(R"(\+|-(?![}%#]\}))"); static std::regex unary_plus_minus_tok(R"(\+|-(?![}%#]\}))");
auto op_str = consumeToken(unary_plus_minus_tok); auto op_str = consumeToken(unary_plus_minus_tok);
auto expr = parseValueExpression(); auto expr = parseExpansion();
if (!expr) throw std::runtime_error("Expected expr of 'unary plus/minus' expression"); if (!expr) throw std::runtime_error("Expected expr of 'unary plus/minus/expansion' expression");
if (!op_str.empty()) { if (!op_str.empty()) {
auto op = op_str == "+" ? UnaryOpExpr::Op::Plus : UnaryOpExpr::Op::Minus; auto op = op_str == "+" ? UnaryOpExpr::Op::Plus : UnaryOpExpr::Op::Minus;
@ -1832,6 +1894,15 @@ private:
return expr; return expr;
} }
std::shared_ptr<Expression> parseExpansion() {
static std::regex expansion_tok(R"(\*\*?)");
auto op_str = consumeToken(expansion_tok);
auto expr = parseValueExpression();
if (op_str.empty()) return expr;
if (!expr) throw std::runtime_error("Expected expr of 'expansion' expression");
return std::make_shared<UnaryOpExpr>(get_location(), std::move(expr), op_str == "*" ? UnaryOpExpr::Op::Expansion : UnaryOpExpr::Op::ExpansionDict);
}
std::shared_ptr<Expression> parseValueExpression() { std::shared_ptr<Expression> parseValueExpression() {
auto parseValue = [&]() -> std::shared_ptr<Expression> { auto parseValue = [&]() -> std::shared_ptr<Expression> {
auto location = get_location(); auto location = get_location();
@ -1971,7 +2042,7 @@ private:
if (consumeToken(":").empty()) throw std::runtime_error("Expected colon betweek key & value in dictionary"); if (consumeToken(":").empty()) throw std::runtime_error("Expected colon betweek key & value in dictionary");
auto value = parseExpression(); auto value = parseExpression();
if (!value) throw std::runtime_error("Expected value in dictionary"); if (!value) throw std::runtime_error("Expected value in dictionary");
elements.emplace_back(std::make_pair(std::move(key), std::move(value))); elements.emplace_back(std::pair(std::move(key), std::move(value)));
}; };
parseKeyValuePair(); parseKeyValuePair();
@ -2029,7 +2100,7 @@ private:
static std::regex comment_tok(R"(\{#([-~]?)(.*?)([-~]?)#\})"); static std::regex comment_tok(R"(\{#([-~]?)(.*?)([-~]?)#\})");
static std::regex expr_open_regex(R"(\{\{([-~])?)"); static std::regex expr_open_regex(R"(\{\{([-~])?)");
static std::regex block_open_regex(R"(^\{%([-~])?[\s\n\r]*)"); static std::regex block_open_regex(R"(^\{%([-~])?[\s\n\r]*)");
static std::regex block_keyword_tok(R"((if|else|elif|endif|for|endfor|set|endset|block|endblock|macro|endmacro)\b)"); static std::regex block_keyword_tok(R"((if|else|elif|endif|for|endfor|set|endset|block|endblock|macro|endmacro|filter|endfilter)\b)");
static std::regex text_regex(R"([\s\S\n\r]*?($|(?=\{\{|\{%|\{#)))"); static std::regex text_regex(R"([\s\S\n\r]*?($|(?=\{\{|\{%|\{#)))");
static std::regex expr_close_regex(R"([\s\n\r]*([-~])?\}\})"); static std::regex expr_close_regex(R"([\s\n\r]*([-~])?\}\})");
static std::regex block_close_regex(R"([\s\n\r]*([-~])?%\})"); static std::regex block_close_regex(R"([\s\n\r]*([-~])?%\})");
@ -2046,7 +2117,7 @@ private:
auto pre_space = parsePreSpace(group[1]); auto pre_space = parsePreSpace(group[1]);
auto content = group[2]; auto content = group[2];
auto post_space = parsePostSpace(group[3]); auto post_space = parsePostSpace(group[3]);
tokens.push_back(nonstd_make_unique<CommentTemplateToken>(location, pre_space, post_space, content)); tokens.push_back(std::make_unique<CommentTemplateToken>(location, pre_space, post_space, content));
} else if (!(group = consumeTokenGroups(expr_open_regex, SpaceHandling::Keep)).empty()) { } else if (!(group = consumeTokenGroups(expr_open_regex, SpaceHandling::Keep)).empty()) {
auto pre_space = parsePreSpace(group[1]); auto pre_space = parsePreSpace(group[1]);
auto expr = parseExpression(); auto expr = parseExpression();
@ -2056,7 +2127,7 @@ private:
} }
auto post_space = parsePostSpace(group[1]); auto post_space = parsePostSpace(group[1]);
tokens.push_back(nonstd_make_unique<ExpressionTemplateToken>(location, pre_space, post_space, std::move(expr))); tokens.push_back(std::make_unique<ExpressionTemplateToken>(location, pre_space, post_space, std::move(expr)));
} else if (!(group = consumeTokenGroups(block_open_regex, SpaceHandling::Keep)).empty()) { } else if (!(group = consumeTokenGroups(block_open_regex, SpaceHandling::Keep)).empty()) {
auto pre_space = parsePreSpace(group[1]); auto pre_space = parsePreSpace(group[1]);
@ -2074,19 +2145,19 @@ private:
if (!condition) throw std::runtime_error("Expected condition in if block"); if (!condition) throw std::runtime_error("Expected condition in if block");
auto post_space = parseBlockClose(); auto post_space = parseBlockClose();
tokens.push_back(nonstd_make_unique<IfTemplateToken>(location, pre_space, post_space, std::move(condition))); tokens.push_back(std::make_unique<IfTemplateToken>(location, pre_space, post_space, std::move(condition)));
} else if (keyword == "elif") { } else if (keyword == "elif") {
auto condition = parseExpression(); auto condition = parseExpression();
if (!condition) throw std::runtime_error("Expected condition in elif block"); if (!condition) throw std::runtime_error("Expected condition in elif block");
auto post_space = parseBlockClose(); auto post_space = parseBlockClose();
tokens.push_back(nonstd_make_unique<ElifTemplateToken>(location, pre_space, post_space, std::move(condition))); tokens.push_back(std::make_unique<ElifTemplateToken>(location, pre_space, post_space, std::move(condition)));
} else if (keyword == "else") { } else if (keyword == "else") {
auto post_space = parseBlockClose(); auto post_space = parseBlockClose();
tokens.push_back(nonstd_make_unique<ElseTemplateToken>(location, pre_space, post_space)); tokens.push_back(std::make_unique<ElseTemplateToken>(location, pre_space, post_space));
} else if (keyword == "endif") { } else if (keyword == "endif") {
auto post_space = parseBlockClose(); auto post_space = parseBlockClose();
tokens.push_back(nonstd_make_unique<EndIfTemplateToken>(location, pre_space, post_space)); tokens.push_back(std::make_unique<EndIfTemplateToken>(location, pre_space, post_space));
} else if (keyword == "for") { } else if (keyword == "for") {
static std::regex recursive_tok(R"(recursive\b)"); static std::regex recursive_tok(R"(recursive\b)");
static std::regex if_tok(R"(if\b)"); static std::regex if_tok(R"(if\b)");
@ -2104,10 +2175,10 @@ private:
auto recursive = !consumeToken(recursive_tok).empty(); auto recursive = !consumeToken(recursive_tok).empty();
auto post_space = parseBlockClose(); auto post_space = parseBlockClose();
tokens.push_back(nonstd_make_unique<ForTemplateToken>(location, pre_space, post_space, std::move(varnames), std::move(iterable), std::move(condition), recursive)); tokens.push_back(std::make_unique<ForTemplateToken>(location, pre_space, post_space, std::move(varnames), std::move(iterable), std::move(condition), recursive));
} else if (keyword == "endfor") { } else if (keyword == "endfor") {
auto post_space = parseBlockClose(); auto post_space = parseBlockClose();
tokens.push_back(nonstd_make_unique<EndForTemplateToken>(location, pre_space, post_space)); tokens.push_back(std::make_unique<EndForTemplateToken>(location, pre_space, post_space));
} else if (keyword == "set") { } else if (keyword == "set") {
static std::regex namespaced_var_regex(R"((\w+)[\s\n\r]*\.[\s\n\r]*(\w+))"); static std::regex namespaced_var_regex(R"((\w+)[\s\n\r]*\.[\s\n\r]*(\w+))");
@ -2131,25 +2202,34 @@ private:
} }
} }
auto post_space = parseBlockClose(); auto post_space = parseBlockClose();
tokens.push_back(nonstd_make_unique<SetTemplateToken>(location, pre_space, post_space, ns, var_names, std::move(value))); tokens.push_back(std::make_unique<SetTemplateToken>(location, pre_space, post_space, ns, var_names, std::move(value)));
} else if (keyword == "endset") { } else if (keyword == "endset") {
auto post_space = parseBlockClose(); auto post_space = parseBlockClose();
tokens.push_back(nonstd_make_unique<EndSetTemplateToken>(location, pre_space, post_space)); tokens.push_back(std::make_unique<EndSetTemplateToken>(location, pre_space, post_space));
} else if (keyword == "macro") { } else if (keyword == "macro") {
auto macroname = parseIdentifier(); auto macroname = parseIdentifier();
if (!macroname) throw std::runtime_error("Expected macro name in macro block"); if (!macroname) throw std::runtime_error("Expected macro name in macro block");
auto params = parseParameters(); auto params = parseParameters();
auto post_space = parseBlockClose(); auto post_space = parseBlockClose();
tokens.push_back(nonstd_make_unique<MacroTemplateToken>(location, pre_space, post_space, std::move(macroname), std::move(params))); tokens.push_back(std::make_unique<MacroTemplateToken>(location, pre_space, post_space, std::move(macroname), std::move(params)));
} else if (keyword == "endmacro") { } else if (keyword == "endmacro") {
auto post_space = parseBlockClose(); auto post_space = parseBlockClose();
tokens.push_back(nonstd_make_unique<EndMacroTemplateToken>(location, pre_space, post_space)); tokens.push_back(std::make_unique<EndMacroTemplateToken>(location, pre_space, post_space));
} else if (keyword == "filter") {
auto filter = parseExpression();
if (!filter) throw std::runtime_error("Expected expression in filter block");
auto post_space = parseBlockClose();
tokens.push_back(std::make_unique<FilterTemplateToken>(location, pre_space, post_space, std::move(filter)));
} else if (keyword == "endfilter") {
auto post_space = parseBlockClose();
tokens.push_back(std::make_unique<EndFilterTemplateToken>(location, pre_space, post_space));
} else { } else {
throw std::runtime_error("Unexpected block: " + keyword); throw std::runtime_error("Unexpected block: " + keyword);
} }
} else if (!(text = consumeToken(text_regex, SpaceHandling::Keep)).empty()) { } else if (!(text = consumeToken(text_regex, SpaceHandling::Keep)).empty()) {
tokens.push_back(nonstd_make_unique<TextTemplateToken>(location, SpaceHandling::Keep, SpaceHandling::Keep, text)); tokens.push_back(std::make_unique<TextTemplateToken>(location, SpaceHandling::Keep, SpaceHandling::Keep, text));
} else { } else {
if (it != end) throw std::runtime_error("Unexpected character"); if (it != end) throw std::runtime_error("Unexpected character");
} }
@ -2241,11 +2321,18 @@ private:
throw unterminated(**start); throw unterminated(**start);
} }
children.emplace_back(std::make_shared<MacroNode>(token->location, std::move(macro_token->name), std::move(macro_token->params), std::move(body))); children.emplace_back(std::make_shared<MacroNode>(token->location, std::move(macro_token->name), std::move(macro_token->params), std::move(body)));
} else if (auto filter_token = dynamic_cast<FilterTemplateToken*>(token.get())) {
auto body = parseTemplate(begin, it, end);
if (it == end || (*(it++))->type != TemplateToken::Type::EndFilter) {
throw unterminated(**start);
}
children.emplace_back(std::make_shared<FilterNode>(token->location, std::move(filter_token->filter), std::move(body)));
} else if (dynamic_cast<CommentTemplateToken*>(token.get())) { } else if (dynamic_cast<CommentTemplateToken*>(token.get())) {
// Ignore comments // Ignore comments
} else if (dynamic_cast<EndForTemplateToken*>(token.get()) } else if (dynamic_cast<EndForTemplateToken*>(token.get())
|| dynamic_cast<EndSetTemplateToken*>(token.get()) || dynamic_cast<EndSetTemplateToken*>(token.get())
|| dynamic_cast<EndMacroTemplateToken*>(token.get()) || dynamic_cast<EndMacroTemplateToken*>(token.get())
|| dynamic_cast<EndFilterTemplateToken*>(token.get())
|| dynamic_cast<EndIfTemplateToken*>(token.get()) || dynamic_cast<EndIfTemplateToken*>(token.get())
|| dynamic_cast<ElseTemplateToken*>(token.get()) || dynamic_cast<ElseTemplateToken*>(token.get())
|| dynamic_cast<ElifTemplateToken*>(token.get())) { || dynamic_cast<ElifTemplateToken*>(token.get())) {
@ -2283,7 +2370,7 @@ static Value simple_function(const std::string & fn_name, const std::vector<std:
std::map<std::string, size_t> named_positions; std::map<std::string, size_t> named_positions;
for (size_t i = 0, n = params.size(); i < n; i++) named_positions[params[i]] = i; for (size_t i = 0, n = params.size(); i < n; i++) named_positions[params[i]] = i;
return Value::callable([=](const std::shared_ptr<Context> & context, Value::Arguments & args) -> Value { return Value::callable([=](const std::shared_ptr<Context> & context, ArgumentsValue & args) -> Value {
auto args_obj = Value::object(); auto args_obj = Value::object();
std::vector<bool> provided_args(params.size()); std::vector<bool> provided_args(params.size());
for (size_t i = 0, n = args.args.size(); i < n; i++) { for (size_t i = 0, n = args.args.size(); i < n; i++) {
@ -2295,14 +2382,13 @@ static Value simple_function(const std::string & fn_name, const std::vector<std:
throw std::runtime_error("Too many positional params for " + fn_name); throw std::runtime_error("Too many positional params for " + fn_name);
} }
} }
for (size_t i = 0, n = args.kwargs.size(); i < n; i++) { for (auto & [name, value] : args.kwargs) {
auto & arg = args.kwargs[i]; auto named_pos_it = named_positions.find(name);
auto named_pos_it = named_positions.find(arg.first);
if (named_pos_it == named_positions.end()) { if (named_pos_it == named_positions.end()) {
throw std::runtime_error("Unknown argument " + arg.first + " for function " + fn_name); throw std::runtime_error("Unknown argument " + name + " for function " + fn_name);
} }
provided_args[named_pos_it->second] = true; provided_args[named_pos_it->second] = true;
args_obj.set(arg.first, arg.second); args_obj.set(name, value);
} }
return fn(context, args_obj); return fn(context, args_obj);
}); });
@ -2344,6 +2430,29 @@ inline std::shared_ptr<Context> Context::builtins() {
auto & text = args.at("text"); auto & text = args.at("text");
return text.is_null() ? text : Value(strip(text.get<std::string>())); return text.is_null() ? text : Value(strip(text.get<std::string>()));
})); }));
globals.set("lower", simple_function("lower", { "text" }, [](const std::shared_ptr<Context> &, Value & args) {
auto text = args.at("text");
if (text.is_null()) return text;
std::string res;
auto str = text.get<std::string>();
std::transform(str.begin(), str.end(), std::back_inserter(res), ::tolower);
return Value(res);
}));
globals.set("default", Value::callable([=](const std::shared_ptr<Context> &, ArgumentsValue & args) {
args.expectArgs("default", {2, 3}, {0, 1});
auto & value = args.args[0];
auto & default_value = args.args[1];
bool boolean = false;
if (args.args.size() == 3) {
boolean = args.args[2].get<bool>();
} else {
Value bv = args.get_named("boolean");
if (!bv.is_null()) {
boolean = bv.get<bool>();
}
}
return boolean ? (value.to_bool() ? value : default_value) : value.is_null() ? default_value : value;
}));
auto escape = simple_function("escape", { "text" }, [](const std::shared_ptr<Context> &, Value & args) { auto escape = simple_function("escape", { "text" }, [](const std::shared_ptr<Context> &, Value & args) {
return Value(html_escape(args.at("text").get<std::string>())); return Value(html_escape(args.at("text").get<std::string>()));
}); });
@ -2398,11 +2507,11 @@ inline std::shared_ptr<Context> Context::builtins() {
}); });
} }
})); }));
globals.set("namespace", Value::callable([=](const std::shared_ptr<Context> &, Value::Arguments & args) { globals.set("namespace", Value::callable([=](const std::shared_ptr<Context> &, ArgumentsValue & args) {
auto ns = Value::object(); auto ns = Value::object();
args.expectArgs("namespace", {0, 0}, {0, std::numeric_limits<size_t>::max()}); args.expectArgs("namespace", {0, 0}, {0, std::numeric_limits<size_t>::max()});
for (auto & arg : args.kwargs) { for (auto & [name, value] : args.kwargs) {
ns.set(arg.first, arg.second); ns.set(name, value);
} }
return ns; return ns;
})); }));
@ -2419,8 +2528,10 @@ inline std::shared_ptr<Context> Context::builtins() {
return args.at("value"); return args.at("value");
})); }));
globals.set("string", simple_function("string", { "value" }, [](const std::shared_ptr<Context> &, Value & args) -> Value { globals.set("string", simple_function("string", { "value" }, [](const std::shared_ptr<Context> &, Value & args) -> Value {
auto & items = args.at("value"); return args.at("value").to_str();
return items.to_str(); }));
globals.set("int", simple_function("int", { "value" }, [](const std::shared_ptr<Context> &, Value & args) -> Value {
return args.at("value").to_int();
})); }));
globals.set("list", simple_function("list", { "items" }, [](const std::shared_ptr<Context> &, Value & args) -> Value { globals.set("list", simple_function("list", { "items" }, [](const std::shared_ptr<Context> &, Value & args) -> Value {
auto & items = args.at("items"); auto & items = args.at("items");
@ -2443,7 +2554,7 @@ inline std::shared_ptr<Context> Context::builtins() {
auto make_filter = [](const Value & filter, Value & extra_args) -> Value { auto make_filter = [](const Value & filter, Value & extra_args) -> Value {
return simple_function("", { "value" }, [=](const std::shared_ptr<Context> & context, Value & args) { return simple_function("", { "value" }, [=](const std::shared_ptr<Context> & context, Value & args) {
auto & value = args.at("value"); auto & value = args.at("value");
Value::Arguments actual_args; ArgumentsValue actual_args;
actual_args.args.emplace_back(value); actual_args.args.emplace_back(value);
for (size_t i = 0, n = extra_args.size(); i < n; i++) { for (size_t i = 0, n = extra_args.size(); i < n; i++) {
actual_args.args.emplace_back(extra_args.at(i)); actual_args.args.emplace_back(extra_args.at(i));
@ -2452,7 +2563,7 @@ inline std::shared_ptr<Context> Context::builtins() {
}); });
}; };
// https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.reject // https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.reject
globals.set("reject", Value::callable([=](const std::shared_ptr<Context> & context, Value::Arguments & args) { globals.set("reject", Value::callable([=](const std::shared_ptr<Context> & context, ArgumentsValue & args) {
args.expectArgs("reject", {2, std::numeric_limits<size_t>::max()}, {0, 0}); args.expectArgs("reject", {2, std::numeric_limits<size_t>::max()}, {0, 0});
auto & items = args.args[0]; auto & items = args.args[0];
auto filter_fn = context->get(args.args[1]); auto filter_fn = context->get(args.args[1]);
@ -2467,7 +2578,7 @@ inline std::shared_ptr<Context> Context::builtins() {
auto res = Value::array(); auto res = Value::array();
for (size_t i = 0, n = items.size(); i < n; i++) { for (size_t i = 0, n = items.size(); i < n; i++) {
auto & item = items.at(i); auto & item = items.at(i);
Value::Arguments filter_args; ArgumentsValue filter_args;
filter_args.args.emplace_back(item); filter_args.args.emplace_back(item);
auto pred_res = filter.call(context, filter_args); auto pred_res = filter.call(context, filter_args);
if (!pred_res.to_bool()) { if (!pred_res.to_bool()) {
@ -2476,7 +2587,7 @@ inline std::shared_ptr<Context> Context::builtins() {
} }
return res; return res;
})); }));
globals.set("map", Value::callable([=](const std::shared_ptr<Context> & context, Value::Arguments & args) { globals.set("map", Value::callable([=](const std::shared_ptr<Context> & context, ArgumentsValue & args) {
auto res = Value::array(); auto res = Value::array();
if (args.args.size() == 1 && if (args.args.size() == 1 &&
((args.has_named("attribute") && args.kwargs.size() == 1) || (args.has_named("default") && args.kwargs.size() == 2))) { ((args.has_named("attribute") && args.kwargs.size() == 1) || (args.has_named("default") && args.kwargs.size() == 2))) {
@ -2491,7 +2602,7 @@ inline std::shared_ptr<Context> Context::builtins() {
} else if (args.kwargs.empty() && args.args.size() >= 2) { } else if (args.kwargs.empty() && args.args.size() >= 2) {
auto fn = context->get(args.args[1]); auto fn = context->get(args.args[1]);
if (fn.is_null()) throw std::runtime_error("Undefined filter: " + args.args[1].dump()); if (fn.is_null()) throw std::runtime_error("Undefined filter: " + args.args[1].dump());
Value::Arguments filter_args { {Value()}, {} }; ArgumentsValue filter_args { {Value()}, {} };
for (size_t i = 2, n = args.args.size(); i < n; i++) { for (size_t i = 2, n = args.args.size(); i < n; i++) {
filter_args.args.emplace_back(args.args[i]); filter_args.args.emplace_back(args.args[i]);
} }
@ -2523,7 +2634,7 @@ inline std::shared_ptr<Context> Context::builtins() {
if (!text.empty() && text.back() == '\n') out += "\n"; if (!text.empty() && text.back() == '\n') out += "\n";
return out; return out;
})); }));
globals.set("selectattr", Value::callable([=](const std::shared_ptr<Context> & context, Value::Arguments & args) { globals.set("selectattr", Value::callable([=](const std::shared_ptr<Context> & context, ArgumentsValue & args) {
args.expectArgs("selectattr", {2, std::numeric_limits<size_t>::max()}, {0, 0}); args.expectArgs("selectattr", {2, std::numeric_limits<size_t>::max()}, {0, 0});
auto & items = args.args[0]; auto & items = args.args[0];
if (items.is_null()) if (items.is_null())
@ -2532,7 +2643,7 @@ inline std::shared_ptr<Context> Context::builtins() {
bool has_test = false; bool has_test = false;
Value test_fn; Value test_fn;
Value::Arguments test_args {{Value()}, {}}; ArgumentsValue test_args {{Value()}, {}};
if (args.args.size() >= 3) { if (args.args.size() >= 3) {
has_test = true; has_test = true;
test_fn = context->get(args.args[2]); test_fn = context->get(args.args[2]);
@ -2558,7 +2669,7 @@ inline std::shared_ptr<Context> Context::builtins() {
} }
return res; return res;
})); }));
globals.set("range", Value::callable([=](const std::shared_ptr<Context> &, Value::Arguments & args) { globals.set("range", Value::callable([=](const std::shared_ptr<Context> &, ArgumentsValue & args) {
std::vector<int64_t> startEndStep(3); std::vector<int64_t> startEndStep(3);
std::vector<bool> param_set(3); std::vector<bool> param_set(3);
if (args.args.size() == 1) { if (args.args.size() == 1) {
@ -2572,17 +2683,17 @@ inline std::shared_ptr<Context> Context::builtins() {
param_set[i] = true; param_set[i] = true;
} }
} }
for (auto & arg : args.kwargs) { for (auto & [name, value] : args.kwargs) {
size_t i; size_t i;
if (arg.first == "start") i = 0; if (name == "start") i = 0;
else if (arg.first == "end") i = 1; else if (name == "end") i = 1;
else if (arg.first == "step") i = 2; else if (name == "step") i = 2;
else throw std::runtime_error("Unknown argument " + arg.first + " for function range"); else throw std::runtime_error("Unknown argument " + name + " for function range");
if (param_set[i]) { if (param_set[i]) {
throw std::runtime_error("Duplicate argument " + arg.first + " for function range"); throw std::runtime_error("Duplicate argument " + name + " for function range");
} }
startEndStep[i] = arg.second.get<int64_t>(); startEndStep[i] = value.get<int64_t>();
param_set[i] = true; param_set[i] = true;
} }
if (!param_set[1]) { if (!param_set[1]) {