minja: sync @ 916c181c0d
This commit is contained in:
parent
1fd5f1af08
commit
5d0033f57a
1 changed files with 271 additions and 160 deletions
431
common/minja.hpp
431
common/minja.hpp
|
@ -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,13 +412,19 @@ 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())
|
||||||
return get<int64_t>() - rhs.get<int64_t>();
|
return get<int64_t>() - rhs.get<int64_t>();
|
||||||
|
@ -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]) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue