Skip to content

Commit efdfa8e

Browse files
committed
refact(builtins): refactor built-ins to use add_function and support named arguments
1 parent 3720701 commit efdfa8e

2 files changed

Lines changed: 66 additions & 81 deletions

File tree

jinja.hpp

Lines changed: 66 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@
4343
namespace jinja {
4444

4545
using json = nlohmann::json;
46-
using UserFunction = std::function<json(const std::vector<json>&)>;
46+
using json = nlohmann::json;
47+
using Argument = std::pair<std::string, json>;
48+
using UserFunction = std::function<json(const std::vector<Argument>&)>;
4749

4850
/**
4951
* @brief A lightweight, C++11 compatible Jinja2 template renderer.
@@ -97,6 +99,8 @@ class Template {
9799
private:
98100
struct Impl;
99101
std::unique_ptr<Impl> m_impl;
102+
103+
void register_builtins();
100104
};
101105

102106
} // namespace jinja
@@ -1121,8 +1125,8 @@ struct TestExpr : Expr {
11211125
result = val.is_boolean() && val.get<bool>() == false;
11221126
}
11231127
// TODO: other tests
1124-
1125-
if (is_not) return !result;
1128+
if (is_not) result = !result;
1129+
JINJA_LOG("TestExpr: " << expr->dump() << " is " << (is_not ? "not " : "") << test_name << " -> " << (result ? "TRUE" : "FALSE"));
11261130
return result;
11271131
}
11281132
std::string dump() const override { return "(" + expr->dump() + " is " + (is_not ? "not " : "") + test_name + ")"; }
@@ -1202,9 +1206,9 @@ struct MacroNode : Node {
12021206

12031207
inline json CallExpr::evaluate(Context& context) {
12041208
if (auto func = context.get_function(func_name)) {
1205-
std::vector<json> arg_vals;
1209+
std::vector<Argument> arg_vals;
12061210
for (auto& arg : args) {
1207-
arg_vals.push_back(arg.second->evaluate(context));
1211+
arg_vals.push_back({arg.first, arg.second->evaluate(context)});
12081212
}
12091213
return func(arg_vals);
12101214
}
@@ -1229,69 +1233,9 @@ inline json CallExpr::evaluate(Context& context) {
12291233
}
12301234
context.pop_scope();
12311235
return out;
1232-
} else if (func_name == "namespace") {
1233-
json ns = json::object();
1234-
for (auto& arg : args) {
1235-
if (!arg.first.empty()) {
1236-
ns[arg.first] = arg.second->evaluate(context);
1237-
}
1238-
}
1239-
return ns;
1240-
} else if (func_name == "range") {
1241-
long start=0, stop=0, step=1;
1242-
if (args.size() == 1) {
1243-
stop = args[0].second->evaluate(context).get<long>();
1244-
} else if (args.size() == 2) {
1245-
start = args[0].second->evaluate(context).get<long>();
1246-
stop = args[1].second->evaluate(context).get<long>();
1247-
} else if (args.size() >= 3) {
1248-
start = args[0].second->evaluate(context).get<long>();
1249-
stop = args[1].second->evaluate(context).get<long>();
1250-
step = args[2].second->evaluate(context).get<long>();
1251-
}
1252-
json arr = json::array();
1253-
if (step > 0) {
1254-
for (long i = start; i < stop; i += step) arr.push_back(i);
1255-
} else if (step < 0) {
1256-
for (long i = start; i > stop; i += step) arr.push_back(i);
1257-
}
1258-
return arr;
1259-
}
1260-
1261-
Macro* macro = context.get_macro(func_name);
1262-
if (macro) {
1263-
json scope = json::object();
1264-
size_t i = 0;
1265-
for (const auto& call_arg : args) {
1266-
if (!call_arg.first.empty()) {
1267-
scope[call_arg.first] = call_arg.second->evaluate(context);
1268-
} else {
1269-
if (i < macro->args.size()) {
1270-
scope[macro->args[i]] = call_arg.second->evaluate(context);
1271-
i++;
1272-
}
1273-
}
1274-
}
1275-
context.push_scope(scope);
1276-
std::string out;
1277-
for (const auto& node : macro->body) {
1278-
node->render(context, out);
1279-
}
1280-
context.pop_scope();
1281-
return out;
1282-
} else if (func_name == "strftime_now") {
1283-
std::string format = "%Y-%m-%d";
1284-
if (args.size() > 0) {
1285-
format = args[0].second->evaluate(context).get<std::string>();
1286-
}
1287-
std::time_t t = std::time(nullptr);
1288-
std::tm tm = *std::localtime(&t);
1289-
std::stringstream ss;
1290-
ss << std::put_time(&tm, format.c_str());
1291-
return ss.str();
1292-
}
1236+
}
12931237

1294-
return "";
1238+
return "";
12951239
}
12961240

12971241
struct TextNode : Node {
@@ -1460,7 +1404,7 @@ struct IfNode : Node {
14601404

14611405
void render(Context& context, std::string& out) override {
14621406
bool res = is_truthy(condition->evaluate(context));
1463-
JINJA_LOG("Render If: Condition evaluated to " << (res ? "TRUE" : "FALSE"));
1407+
JINJA_LOG("Render If: (" << condition->dump() << ") evaluated to " << (res ? "TRUE" : "FALSE"));
14641408
if (res) {
14651409
for (const auto& node : true_body) node->render(context, out);
14661410
} else {
@@ -2092,9 +2036,60 @@ inline Template::Template(const std::string& template_str, const json& default_c
20922036
: m_impl(make_unique<Impl>()) {
20932037
m_impl->template_str = template_str;
20942038
m_impl->default_context = default_context;
2039+
2040+
register_builtins();
2041+
20952042
m_impl->parse();
20962043
}
20972044

2045+
inline void Template::register_builtins() {
2046+
// Built-in: range
2047+
add_function("range", [](const std::vector<Argument>& args) -> json {
2048+
long start=0, stop=0, step=1;
2049+
if (args.size() == 1) {
2050+
stop = args[0].second.get<long>();
2051+
} else if (args.size() == 2) {
2052+
start = args[0].second.get<long>();
2053+
stop = args[1].second.get<long>();
2054+
} else if (args.size() >= 3) {
2055+
start = args[0].second.get<long>();
2056+
stop = args[1].second.get<long>();
2057+
step = args[2].second.get<long>();
2058+
}
2059+
json arr = json::array();
2060+
if (step > 0) {
2061+
for (long i = start; i < stop; i += step) arr.push_back(i);
2062+
} else if (step < 0) {
2063+
for (long i = start; i > stop; i += step) arr.push_back(i);
2064+
}
2065+
return arr;
2066+
});
2067+
2068+
// Built-in: namespace
2069+
add_function("namespace", [](const std::vector<Argument>& args) -> json {
2070+
json ns = json::object();
2071+
for (const auto& arg : args) {
2072+
if (!arg.first.empty()) {
2073+
ns[arg.first] = arg.second;
2074+
}
2075+
}
2076+
return ns;
2077+
});
2078+
2079+
// Built-in: strftime_now
2080+
add_function("strftime_now", [](const std::vector<Argument>& args) -> json {
2081+
std::string format = "%Y-%m-%d";
2082+
if (args.size() > 0) {
2083+
format = args[0].second.get<std::string>();
2084+
}
2085+
std::time_t t = std::time(nullptr);
2086+
std::tm tm = *std::localtime(&t);
2087+
std::stringstream ss;
2088+
ss << std::put_time(&tm, format.c_str());
2089+
return ss.str();
2090+
});
2091+
}
2092+
20982093
inline Template::~Template() = default;
20992094

21002095
inline Template::Template(Template&& other) noexcept = default;
@@ -2115,6 +2110,9 @@ inline std::string Template::render(const json& context) const {
21152110

21162111
inline void Template::add_function(const std::string& name, UserFunction func) {
21172112
m_impl->functions[name] = std::move(func);
2113+
if (!m_impl->default_context.contains(name)) {
2114+
m_impl->default_context[name] = "<function " + name + ">";
2115+
}
21182116
}
21192117

21202118
inline std::string Template::apply_chat_template(

tests/test_main.cpp

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
using json = nlohmann::json;
1414

15-
#if 1
1615
namespace Color {
1716
const std::string RESET = "\033[0m";
1817
const std::string RED = "\033[31m";
@@ -23,18 +22,6 @@ namespace Color {
2322
const std::string BOLD = "\033[1m";
2423
const std::string GREY = "\033[90m";
2524
}
26-
#else
27-
namespace Color {
28-
const std::string RESET = "";
29-
const std::string RED = "";
30-
const std::string GREEN = "";
31-
const std::string YELLOW = "";
32-
const std::string BLUE = "";
33-
const std::string CYAN = "";
34-
const std::string BOLD = "";
35-
const std::string GREY = "";
36-
}
37-
#endif
3825

3926
std::string load_file(const std::string& path) {
4027
std::ifstream f(path);

0 commit comments

Comments
 (0)