gather_string_parts_recursive(val, parts);
// join consecutive parts with the same type
auto & p = parts->val_str.parts;
- for (size_t i = 1; i < p.size(); ) {
- if (p[i].is_input == p[i - 1].is_input) {
- p[i - 1].val += p[i].val;
- p.erase(p.begin() + i);
+ if (p.empty()) {
+ return parts;
+ }
+ size_t w = 0;
+ for (size_t r = 1; r < p.size(); r++) {
+ if (p[w].is_input == p[r].is_input) {
+ p[w].val += p[r].val;
} else {
- i++;
+ w++;
+ if (w != r) {
+ // the guard is needed, self-move leaves the string in an unspecified state
+ p[w] = std::move(p[r]);
+ }
}
}
+ p.resize(w + 1);
return parts;
}
static void test_object_methods(testing & t);
static void test_hasher(testing & t);
static void test_stats(testing & t);
+static void test_string_parts(testing & t);
static void test_fuzzing(testing & t);
static bool g_python_mode = false;
if (!g_python_mode) {
t.test("hasher", test_hasher);
t.test("stats", test_stats);
+ t.test("string parts", test_string_parts);
t.test("fuzzing", test_fuzzing);
}
});
}
+static void test_string_parts(testing & t) {
+ static auto render = [](const std::string & tmpl, const json & vars) -> jinja::string {
+ jinja::lexer lexer;
+ auto lexer_res = lexer.tokenize(tmpl);
+
+ jinja::program ast = jinja::parse_from_tokens(lexer_res);
+
+ jinja::context ctx(tmpl);
+ jinja::global_from_json(ctx, vars, true);
+
+ jinja::runtime runtime(ctx);
+ return runtime.gather_string_parts(runtime.execute(ast))->as_string();
+ };
+
+ t.test("merge joins only the neighbours with the same type", [](testing & t) {
+ // "AB" comes from the input and merges, "-" comes from the template and must not
+ jinja::string res = render("{{ val.a }}{{ val.b }}-{{ val.c }}",
+ json{{"val", json{{"a", "A"}, {"b", "B"}, {"c", "C"}}}});
+
+ if (t.assert_true("3 parts after the merge", res.parts.size() == 3)) {
+ t.assert_true("part 0 is the merged input", res.parts[0].val == "AB" && res.parts[0].is_input);
+ t.assert_true("part 1 is from the template", res.parts[1].val == "-" && !res.parts[1].is_input);
+ t.assert_true("part 2 is input", res.parts[2].val == "C" && res.parts[2].is_input);
+ } else {
+ t.log("parts: " + std::to_string(res.parts.size()) + ", rendered: " + json(res.str()).dump());
+ }
+ });
+
+}
+
static void test_template_cpp(testing & t, const std::string & name, const std::string & tmpl, const json & vars, const std::string & expect) {
t.test(name, [&tmpl, &vars, &expect](testing & t) {
jinja::lexer lexer;