char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Xuan-Son Nguyencommon: add json.h abstraction (#27511)d9f918d2d

char/aliceai-t5-moe
64.6 KiB1586 linesraw
1#include "runtime.h"
2#include "unicode.h"
3#include "value.h"
4
5// for converting from JSON to jinja values
6#include "json.h"
7
8#include <sstream>
9#include <string>
10#include <cctype>
11#include <vector>
12#include <optional>
13#include <algorithm>
14
15#define FILENAME "jinja-value"
16
17namespace jinja {
18
19// func_args method implementations
20
21value func_args::get_kwarg(const std::string & key, value default_val) const {
22    for (const auto & arg : args) {
23        if (is_val<value_kwarg>(arg)) {
24            auto * kwarg = cast_val<value_kwarg>(arg);
25            if (kwarg->key == key) {
26                return kwarg->val;
27            }
28        }
29    }
30    return default_val;
31}
32
33value func_args::get_kwarg_or_pos(const std::string & key, size_t pos) const {
34    value val = get_kwarg(key, mk_val<value_undefined>());
35
36    if (val->is_undefined() && pos < count() && !is_val<value_kwarg>(args[pos])) {
37        return args[pos];
38    }
39
40    return val;
41}
42
43value func_args::get_pos(size_t pos) const {
44    if (count() > pos) {
45        return args[pos];
46    }
47    throw raised_exception("Function '" + func_name + "' expected at least " + std::to_string(pos + 1) + " arguments, got " + std::to_string(count()));
48}
49
50value func_args::get_pos(size_t pos, value default_val) const {
51    if (count() > pos) {
52        return args[pos];
53    }
54    return default_val;
55}
56
57void func_args::push_back(const value & val) {
58    args.push_back(val);
59}
60
61void func_args::push_front(const value & val) {
62    args.insert(args.begin(), val);
63}
64
65const std::vector<value> & func_args::get_args() const {
66    return args;
67}
68
69/**
70 * Function that mimics Python's array slicing.
71 */
72template<typename T>
73static T slice(const T & array, int64_t start, int64_t stop, int64_t step = 1) {
74    int64_t len = static_cast<int64_t>(array.size());
75    int64_t direction = (step > 0) ? 1 : ((step < 0) ? -1 : 0);
76    int64_t start_val = 0;
77    int64_t stop_val = 0;
78    if (direction >= 0) {
79        start_val = start;
80        if (start_val < 0) {
81            start_val = std::max(len + start_val, (int64_t)0);
82        } else {
83            start_val = std::min(start_val, len);
84        }
85
86        stop_val = stop;
87        if (stop_val < 0) {
88            stop_val = std::max(len + stop_val, (int64_t)0);
89        } else {
90            stop_val = std::min(stop_val, len);
91        }
92    } else {
93        start_val = start;
94        if (start_val < 0) {
95            start_val = std::max(len + start_val, (int64_t)0);
96        } else {
97            start_val = std::min(start_val, len - 1);
98        }
99
100        stop_val = stop;
101        if (stop_val < -1) {
102            stop_val = std::max(len + stop_val, (int64_t)-1);
103        } else {
104            stop_val = std::min(stop_val, len - 1);
105        }
106    }
107    T result;
108    if (direction == 0) {
109        return result;
110    }
111    for (int64_t i = start_val; direction * i < direction * stop_val; i += step) {
112        if (i >= 0 && i < len) {
113            result.push_back(array[static_cast<size_t>(i)]);
114        }
115    }
116    return result;
117}
118
119template<typename T>
120static value empty_value_fn(const func_args &) {
121    if constexpr (std::is_same_v<T, value_int>) {
122        return mk_val<T>(0);
123    } else if constexpr (std::is_same_v<T, value_float>) {
124        return mk_val<T>(0.0);
125    } else if constexpr (std::is_same_v<T, value_bool>) {
126        return mk_val<T>(false);
127    } else {
128        return mk_val<T>();
129    }
130}
131template<typename T>
132static value test_type_fn(const func_args & args) {
133    args.ensure_count(1);
134    bool is_type = is_val<T>(args.get_pos(0));
135    JJ_DEBUG("test_type_fn: type=%s result=%d", typeid(T).name(), is_type ? 1 : 0);
136    return mk_val<value_bool>(is_type);
137}
138template<typename T, typename U>
139static value test_type_fn(const func_args & args) {
140    args.ensure_count(1);
141    bool is_type = is_val<T>(args.get_pos(0)) || is_val<U>(args.get_pos(0));
142    JJ_DEBUG("test_type_fn: type=%s or %s result=%d", typeid(T).name(), typeid(U).name(), is_type ? 1 : 0);
143    return mk_val<value_bool>(is_type);
144}
145template<typename T, typename U, typename V>
146static value test_type_fn(const func_args & args) {
147    args.ensure_count(1);
148    bool is_type = is_val<T>(args.get_pos(0)) || is_val<U>(args.get_pos(0)) || is_val<V>(args.get_pos(0));
149    JJ_DEBUG("test_type_fn: type=%s, %s or %s result=%d", typeid(T).name(), typeid(U).name(), typeid(V).name(), is_type ? 1 : 0);
150    return mk_val<value_bool>(is_type);
151}
152template<value_compare_op op>
153static value test_compare_fn(const func_args & args) {
154    args.ensure_count(2, 2);
155    return mk_val<value_bool>(value_compare(args.get_pos(0), args.get_pos(1), op));
156}
157
158static void append_codepoint_as_ascii_json_escape(std::string & out, uint32_t codepoint) {
159    auto append_u16 = [&out](uint32_t value) {
160        char buf[8];
161        snprintf(buf, sizeof(buf), "\\u%04x", static_cast<unsigned int>(value));
162        out += buf;
163    };
164
165    if (codepoint <= 0xFFFF) {
166        append_u16(codepoint);
167        return;
168    }
169
170    codepoint -= 0x10000;
171    append_u16(0xD800 + ((codepoint >> 10) & 0x3FF));
172    append_u16(0xDC00 + (codepoint & 0x3FF));
173}
174
175static std::string json_ensure_ascii_preserving_format(const std::string & json_str) {
176    std::string output;
177    output.reserve(json_str.size());
178
179    bool in_string = false;
180    bool escaped = false;
181
182    for (size_t pos = 0; pos < json_str.size();) {
183        const char ch = json_str[pos];
184        if (!in_string) {
185            output.push_back(ch);
186            if (ch == '"') {
187                in_string = true;
188            }
189            ++pos;
190            continue;
191        }
192
193        if (escaped) {
194            output.push_back(ch);
195            escaped = false;
196            ++pos;
197            continue;
198        }
199
200        if (ch == '\\') {
201            output.push_back(ch);
202            escaped = true;
203            ++pos;
204            continue;
205        }
206
207        if (ch == '"') {
208            output.push_back(ch);
209            in_string = false;
210            ++pos;
211            continue;
212        }
213
214        const unsigned char uch = static_cast<unsigned char>(ch);
215        if (uch < 0x80) {
216            output.push_back(ch);
217            ++pos;
218            continue;
219        }
220
221        auto parsed = common_parse_utf8_codepoint(json_str, pos);
222        if (parsed.status != utf8_parse_result::SUCCESS) {
223            output += "\\ufffd";
224            ++pos;
225            continue;
226        }
227
228        append_codepoint_as_ascii_json_escape(output, parsed.codepoint);
229        pos += parsed.bytes_consumed;
230    }
231
232    return output;
233}
234
235static value tojson(const func_args & args) {
236    args.ensure_count(1, 5);
237    value val_ascii      = args.get_kwarg_or_pos("ensure_ascii", 1);
238    value val_indent     = args.get_kwarg_or_pos("indent",       2);
239    value val_separators = args.get_kwarg_or_pos("separators",   3);
240    value val_sort       = args.get_kwarg_or_pos("sort_keys",    4);
241    int indent = -1;
242    if (args.ctx.is_get_stats) {
243        // mark as used (recursively) for stats
244        auto val_input = args.get_pos(0);
245        value_t::stats_t::mark_used(const_cast<value&>(val_input), true);
246    }
247    if (is_val<value_int>(val_indent)) {
248        indent = static_cast<int>(val_indent->as_int());
249    }
250    if (val_sort->as_bool()) { // undefined == false
251        throw not_implemented_exception("tojson sort_keys=true not implemented");
252    }
253    const bool ensure_ascii = val_ascii->as_bool(); // undefined == false
254    auto separators = (is_val<value_array>(val_separators) ? val_separators : mk_val<value_array>())->as_array();
255    std::string item_sep = separators.size() > 0 ? separators[0]->as_string().str() : (indent < 0 ? ", " : ",");
256    std::string key_sep = separators.size() > 1 ? separators[1]->as_string().str() : ": ";
257    std::string json_str = value_to_json(args.get_pos(0), indent, item_sep, key_sep);
258    if (ensure_ascii) {
259        json_str = json_ensure_ascii_preserving_format(json_str);
260    }
261    return mk_val<value_string>(json_str);
262}
263
264template<bool is_reject>
265static value selectattr(const func_args & args) {
266    args.ensure_count(2, 4);
267    args.ensure_vals<value_array, value_string, value_string, value_string>(true, true, false, false);
268
269    auto arr = args.get_pos(0)->as_array();
270    auto attribute = args.get_pos(1);
271    auto out = mk_val<value_array>();
272    value val_default = mk_val<value_undefined>();
273
274    if (args.count() == 2) {
275        // example: array | selectattr("active")
276        for (const auto & item : arr) {
277            if (!is_val<value_object>(item)) {
278                throw raised_exception("selectattr: item is not an object");
279            }
280            value attr_val = item->at(attribute, val_default);
281            bool is_selected = attr_val->as_bool();
282            if constexpr (is_reject) is_selected = !is_selected;
283            if (is_selected) out->push_back(item);
284        }
285        return out;
286
287    } else if (args.count() == 3) {
288        // example: array | selectattr("equalto", "text")
289        // translated to: test_is_equalto(item, "text")
290        std::string test_name = args.get_pos(1)->as_string().str();
291        value test_val = args.get_pos(2);
292        auto & builtins = global_builtins();
293        auto it = builtins.find("test_is_" + test_name);
294        if (it == builtins.end()) {
295            throw raised_exception("selectattr: unknown test '" + test_name + "'");
296        }
297        auto test_fn = it->second;
298        for (const auto & item : arr) {
299            func_args test_args(args.ctx);
300            test_args.push_back(item); // current object
301            test_args.push_back(test_val); // extra argument
302            value test_result = test_fn(test_args);
303            bool is_selected = test_result->as_bool();
304            if constexpr (is_reject) is_selected = !is_selected;
305            if (is_selected) out->push_back(item);
306        }
307        return out;
308
309    } else if (args.count() == 4) {
310        // example: array | selectattr("status", "equalto", "active")
311        // translated to: test_is_equalto(item.status, "active")
312        std::string test_name = args.get_pos(2)->as_string().str();
313        auto extra_arg = args.get_pos(3);
314        auto & builtins = global_builtins();
315        auto it = builtins.find("test_is_" + test_name);
316        if (it == builtins.end()) {
317            throw raised_exception("selectattr: unknown test '" + test_name + "'");
318        }
319        auto test_fn = it->second;
320        for (const auto & item : arr) {
321            if (!is_val<value_object>(item)) {
322                throw raised_exception("selectattr: item is not an object");
323            }
324            value attr_val = item->at(attribute, val_default);
325            func_args test_args(args.ctx);
326            test_args.push_back(attr_val); // attribute value
327            test_args.push_back(extra_arg); // extra argument
328            value test_result = test_fn(test_args);
329            bool is_selected = test_result->as_bool();
330            if constexpr (is_reject) is_selected = !is_selected;
331            if (is_selected) out->push_back(item);
332        }
333        return out;
334    } else {
335        throw raised_exception("selectattr: invalid number of arguments");
336    }
337
338    return out;
339}
340
341static value default_value(const func_args & args) {
342    args.ensure_count(2, 3);
343    value val_check = args.get_kwarg_or_pos("boolean", 2);
344    bool check_bool = val_check->as_bool(); // undefined == false
345    bool no_value = check_bool
346        ? (!args.get_pos(0)->as_bool())
347        : (args.get_pos(0)->is_undefined() || args.get_pos(0)->is_none());
348    return no_value ? args.get_pos(1) : args.get_pos(0);
349}
350
351const func_builtins & global_builtins() {
352    static const func_builtins builtins = {
353        {"raise_exception", [](const func_args & args) -> value {
354            args.ensure_vals<value_string>();
355            std::string msg = args.get_pos(0)->as_string().str();
356            throw raised_exception("Jinja Exception: " + msg);
357        }},
358        {"namespace", [](const func_args & args) -> value {
359            auto out = mk_val<value_object>();
360            for (const auto & arg : args.get_args()) {
361                if (!is_val<value_kwarg>(arg)) {
362                    throw raised_exception("namespace() arguments must be kwargs");
363                }
364                auto kwarg = cast_val<value_kwarg>(arg);
365                JJ_DEBUG("namespace: adding key '%s'", kwarg->key.c_str());
366                out->insert(kwarg->key, kwarg->val);
367            }
368            return out;
369        }},
370        {"strftime_now", [](const func_args & args) -> value {
371            args.ensure_vals<value_string>();
372            std::string format = args.get_pos(0)->as_string().str();
373            // get current time
374            // TODO: make sure this is the same behavior as Python's strftime
375            char buf[100];
376            if (std::strftime(buf, sizeof(buf), format.c_str(), std::localtime(&args.ctx.current_time))) {
377                return mk_val<value_string>(std::string(buf));
378            } else {
379                throw raised_exception("strftime_now: failed to format time");
380            }
381        }},
382        {"range", [](const func_args & args) -> value {
383            args.ensure_count(1, 3);
384            args.ensure_vals<value_int, value_int, value_int>(true, false, false);
385
386            auto arg0 = args.get_pos(0);
387            auto arg1 = args.get_pos(1, mk_val<value_undefined>());
388            auto arg2 = args.get_pos(2, mk_val<value_undefined>());
389
390            int64_t start, stop, step;
391            if (args.count() == 1) {
392                start = 0;
393                stop = arg0->as_int();
394                step = 1;
395            } else if (args.count() == 2) {
396                start = arg0->as_int();
397                stop = arg1->as_int();
398                step = 1;
399            } else {
400                start = arg0->as_int();
401                stop = arg1->as_int();
402                step = arg2->as_int();
403            }
404
405            auto out = mk_val<value_array>();
406            if (step == 0) {
407                throw raised_exception("range() step argument must not be zero");
408            }
409            if (step > 0) {
410                for (int64_t i = start; i < stop; i += step) {
411                    out->push_back(mk_val<value_int>(i));
412                }
413            } else {
414                for (int64_t i = start; i > stop; i += step) {
415                    out->push_back(mk_val<value_int>(i));
416                }
417            }
418            return out;
419        }},
420        {"tojson", tojson},
421
422        // tests
423        {"test_is_boolean", test_type_fn<value_bool>},
424        {"test_is_callable", test_type_fn<value_func>},
425        {"test_is_odd", [](const func_args & args) -> value {
426            args.ensure_vals<value_int>();
427            int64_t val = args.get_pos(0)->as_int();
428            return mk_val<value_bool>(val % 2 != 0);
429        }},
430        {"test_is_even", [](const func_args & args) -> value {
431            args.ensure_vals<value_int>();
432            int64_t val = args.get_pos(0)->as_int();
433            return mk_val<value_bool>(val % 2 == 0);
434        }},
435        {"test_is_false", [](const func_args & args) -> value {
436            args.ensure_count(1);
437            bool val = is_val<value_bool>(args.get_pos(0)) && !args.get_pos(0)->as_bool();
438            return mk_val<value_bool>(val);
439        }},
440        {"test_is_true", [](const func_args & args) -> value {
441            args.ensure_count(1);
442            bool val = is_val<value_bool>(args.get_pos(0)) && args.get_pos(0)->as_bool();
443            return mk_val<value_bool>(val);
444        }},
445        {"test_is_divisibleby", [](const func_args & args) -> value {
446            args.ensure_vals<value_int, value_int>();
447            bool res = args.get_pos(0)->val_int % args.get_pos(1)->val_int == 0;
448            return mk_val<value_bool>(res);
449        }},
450        {"test_is_string", test_type_fn<value_string>},
451        {"test_is_integer", test_type_fn<value_int>},
452        {"test_is_float", test_type_fn<value_float>},
453        {"test_is_number", test_type_fn<value_int, value_float>},
454        {"test_is_iterable", test_type_fn<value_array, value_string, value_undefined>},
455        {"test_is_sequence", test_type_fn<value_array, value_string, value_undefined>},
456        {"test_is_mapping", test_type_fn<value_object>},
457        {"test_is_lower", [](const func_args & args) -> value {
458            args.ensure_vals<value_string>();
459            return mk_val<value_bool>(args.get_pos(0)->val_str.is_lowercase());
460        }},
461        {"test_is_upper", [](const func_args & args) -> value {
462            args.ensure_vals<value_string>();
463            return mk_val<value_bool>(args.get_pos(0)->val_str.is_uppercase());
464        }},
465        {"test_is_none", test_type_fn<value_none>},
466        {"test_is_defined", [](const func_args & args) -> value {
467            args.ensure_count(1);
468            bool res = !args.get_pos(0)->is_undefined();
469            JJ_DEBUG("test_is_defined: result=%d", res ? 1 : 0);
470            return mk_val<value_bool>(res);
471        }},
472        {"test_is_undefined", test_type_fn<value_undefined>},
473        {"test_is_eq", test_compare_fn<value_compare_op::eq>},
474        {"test_is_equalto", test_compare_fn<value_compare_op::eq>},
475        {"test_is_ge", test_compare_fn<value_compare_op::ge>},
476        {"test_is_gt", test_compare_fn<value_compare_op::gt>},
477        {"test_is_greaterthan", test_compare_fn<value_compare_op::gt>},
478        {"test_is_lt", test_compare_fn<value_compare_op::lt>},
479        {"test_is_lessthan", test_compare_fn<value_compare_op::lt>},
480        {"test_is_ne", test_compare_fn<value_compare_op::ne>},
481        {"test_is_in", [](const func_args & args) -> value {
482            args.ensure_count(2);
483            auto needle   = args.get_pos(0);
484            auto haystack = args.get_pos(1);
485            if (is_val<value_undefined>(haystack)) {
486                return mk_val<value_bool>(false);
487            }
488            if (is_val<value_array>(haystack)) {
489                for (const auto & item : haystack->as_array()) {
490                    if (*needle == *item) {
491                        return mk_val<value_bool>(true);
492                    }
493                }
494                return mk_val<value_bool>(false);
495            }
496            if (is_val<value_string>(haystack)) {
497                if (!is_val<value_string>(needle)) {
498                    throw raised_exception("'in' test expects args[1] as string when args[0] is string, got args[1] as " + needle->type());
499                }
500                return mk_val<value_bool>(
501                    haystack->as_string().str().find(needle->as_string().str()) != std::string::npos);
502            }
503            if (is_val<value_object>(haystack)) {
504                return mk_val<value_bool>(haystack->has_key(needle));
505            }
506            throw raised_exception("'in' test expects iterable as first argument, got " + haystack->type());
507        }},
508        {"test_is_test", [](const func_args & args) -> value {
509            args.ensure_vals<value_string>();
510            auto & builtins = global_builtins();
511            std::string test_name = args.get_pos(0)->val_str.str();
512            auto it = builtins.find("test_is_" + test_name);
513            bool res = it != builtins.end();
514            return mk_val<value_bool>(res);
515        }},
516        {"test_is_sameas", [](const func_args & args) -> value {
517            // Check if an object points to the same memory address as another object
518            (void)args;
519            throw not_implemented_exception("sameas test not implemented");
520        }},
521        {"test_is_escaped", [](const func_args & args) -> value {
522            (void)args;
523            throw not_implemented_exception("escaped test not implemented");
524        }},
525        {"test_is_filter", [](const func_args & args) -> value {
526            (void)args;
527            throw not_implemented_exception("filter test not implemented");
528        }},
529    };
530    return builtins;
531}
532
533
534const func_builtins & value_int_t::get_builtins() const {
535    static const func_builtins builtins = {
536        {"default", default_value},
537        {"abs", [](const func_args & args) -> value {
538            args.ensure_vals<value_int>();
539            int64_t val = args.get_pos(0)->as_int();
540            return mk_val<value_int>(val < 0 ? -val : val);
541        }},
542        {"int", [](const func_args & args) -> value {
543            args.ensure_vals<value_int>();
544            return mk_val<value_int>(args.get_pos(0)->as_int());
545        }},
546        {"float", [](const func_args & args) -> value {
547            args.ensure_vals<value_int>();
548            double val = static_cast<double>(args.get_pos(0)->as_int());
549            return mk_val<value_float>(val);
550        }},
551        {"safe", tojson},
552        {"string", tojson},
553        {"tojson", tojson},
554    };
555    return builtins;
556}
557
558
559const func_builtins & value_float_t::get_builtins() const {
560    static const func_builtins builtins = {
561        {"default", default_value},
562        {"abs", [](const func_args & args) -> value {
563            args.ensure_vals<value_float>();
564            double val = args.get_pos(0)->as_float();
565            return mk_val<value_float>(val < 0.0 ? -val : val);
566        }},
567        {"int", [](const func_args & args) -> value {
568            args.ensure_vals<value_float>();
569            int64_t val = static_cast<int64_t>(args.get_pos(0)->as_float());
570            return mk_val<value_int>(val);
571        }},
572        {"float", [](const func_args & args) -> value {
573            args.ensure_vals<value_float>();
574            return mk_val<value_float>(args.get_pos(0)->as_float());
575        }},
576        {"safe", tojson},
577        {"string", tojson},
578        {"tojson", tojson},
579    };
580    return builtins;
581}
582
583static bool string_startswith(const std::string & str, const std::string & prefix) {
584    if (str.length() < prefix.length()) return false;
585    return str.compare(0, prefix.length(), prefix) == 0;
586}
587
588static bool string_endswith(const std::string & str, const std::string & suffix) {
589    if (str.length() < suffix.length()) return false;
590    return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0;
591}
592
593[[noreturn]] static value string_join_not_implemented(const func_args &) {
594    throw not_implemented_exception("String join builtin not implemented");
595}
596
597const func_builtins & value_string_t::get_builtins() const {
598    static const func_builtins builtins = {
599        {"default", default_value},
600        {"upper", [](const func_args & args) -> value {
601            args.ensure_vals<value_string>();
602            jinja::string str = args.get_pos(0)->as_string().uppercase();
603            return mk_val<value_string>(str);
604        }},
605        {"lower", [](const func_args & args) -> value {
606            args.ensure_vals<value_string>();
607            jinja::string str = args.get_pos(0)->as_string().lowercase();
608            return mk_val<value_string>(str);
609        }},
610        {"strip", [](const func_args & args) -> value {
611            value val_input = args.get_pos(0);
612            if (!is_val<value_string>(val_input)) {
613                throw raised_exception("strip() first argument must be a string");
614            }
615            value val_chars = args.get_kwarg_or_pos("chars", 1);
616            if (val_chars->is_undefined()) {
617                return mk_val<value_string>(args.get_pos(0)->as_string().strip(true, true));
618            } else {
619                return mk_val<value_string>(args.get_pos(0)->as_string().strip(true, true, val_chars->as_string().str()));
620            }
621        }},
622        {"rstrip", [](const func_args & args) -> value {
623            args.ensure_vals<value_string>();
624            value val_chars = args.get_kwarg_or_pos("chars", 1);
625            if (val_chars->is_undefined()) {
626                return mk_val<value_string>(args.get_pos(0)->as_string().strip(false, true));
627            } else {
628                return mk_val<value_string>(args.get_pos(0)->as_string().strip(false, true, val_chars->as_string().str()));
629            }
630        }},
631        {"lstrip", [](const func_args & args) -> value {
632            args.ensure_vals<value_string>();
633            value val_chars = args.get_kwarg_or_pos("chars", 1);
634            if (val_chars->is_undefined()) {
635                return mk_val<value_string>(args.get_pos(0)->as_string().strip(true, false));
636            } else {
637                return mk_val<value_string>(args.get_pos(0)->as_string().strip(true, false, val_chars->as_string().str()));
638            }
639        }},
640        {"title", [](const func_args & args) -> value {
641            args.ensure_vals<value_string>();
642            jinja::string str = args.get_pos(0)->as_string().titlecase();
643            return mk_val<value_string>(str);
644        }},
645        {"capitalize", [](const func_args & args) -> value {
646            args.ensure_vals<value_string>();
647            jinja::string str = args.get_pos(0)->as_string().capitalize();
648            return mk_val<value_string>(str);
649        }},
650        {"length", [](const func_args & args) -> value {
651            args.ensure_vals<value_string>();
652            jinja::string str = args.get_pos(0)->as_string();
653            return mk_val<value_int>(str.length());
654        }},
655        {"startswith", [](const func_args & args) -> value {
656            args.ensure_vals<value_string, value_string>();
657            std::string str = args.get_pos(0)->as_string().str();
658            std::string prefix = args.get_pos(1)->as_string().str();
659            return mk_val<value_bool>(string_startswith(str, prefix));
660        }},
661        {"endswith", [](const func_args & args) -> value {
662            args.ensure_vals<value_string, value_string>();
663            std::string str = args.get_pos(0)->as_string().str();
664            std::string suffix = args.get_pos(1)->as_string().str();
665            return mk_val<value_bool>(string_endswith(str, suffix));
666        }},
667        {"split", [](const func_args & args) -> value {
668            args.ensure_count(1, 3);
669            value val_input = args.get_pos(0);
670            if (!is_val<value_string>(val_input)) {
671                throw raised_exception("split() first argument must be a string");
672            }
673            std::string str = val_input->as_string().str();
674            // FIXME: Support non-specified delimiter (split on consecutive (no leading or trailing) whitespace)
675            std::string delim = (args.count() > 1) ? args.get_pos(1)->as_string().str() : " ";
676            if (delim.empty()) {
677                throw raised_exception("empty separator");
678            }
679            int64_t maxsplit = (args.count() > 2) ? args.get_pos(2)->as_int() : -1;
680            auto result = mk_val<value_array>();
681            size_t pos = 0;
682            std::string token;
683            while ((pos = str.find(delim)) != std::string::npos && maxsplit != 0) {
684                token = str.substr(0, pos);
685                result->push_back(mk_val<value_string>(token));
686                str.erase(0, pos + delim.length());
687                --maxsplit;
688            }
689            auto res = mk_val<value_string>(str);
690            res->val_str.mark_input_based_on(args.get_pos(0)->val_str);
691            result->push_back(std::move(res));
692            return result;
693        }},
694        {"rsplit", [](const func_args & args) -> value {
695            args.ensure_count(1, 3);
696            value val_input = args.get_pos(0);
697            if (!is_val<value_string>(val_input)) {
698                throw raised_exception("rsplit() first argument must be a string");
699            }
700            std::string str = val_input->as_string().str();
701            // FIXME: Support non-specified delimiter (split on consecutive (no leading or trailing) whitespace)
702            std::string delim = (args.count() > 1) ? args.get_pos(1)->as_string().str() : " ";
703            if (delim.empty()) {
704                throw raised_exception("empty separator");
705            }
706            int64_t maxsplit = (args.count() > 2) ? args.get_pos(2)->as_int() : -1;
707            auto result = mk_val<value_array>();
708            size_t pos = 0;
709            std::string token;
710            while ((pos = str.rfind(delim)) != std::string::npos && maxsplit != 0) {
711                token = str.substr(pos + delim.length());
712                result->push_back(mk_val<value_string>(token));
713                str.erase(pos);
714                --maxsplit;
715            }
716            auto res = mk_val<value_string>(str);
717            res->val_str.mark_input_based_on(args.get_pos(0)->val_str);
718            result->push_back(std::move(res));
719            result->reverse();
720            return result;
721        }},
722        {"replace", [](const func_args & args) -> value {
723            args.ensure_vals<value_string, value_string, value_string, value_int>(true, true, true, false);
724            std::string str = args.get_pos(0)->as_string().str();
725            std::string old_str = args.get_pos(1)->as_string().str();
726            std::string new_str = args.get_pos(2)->as_string().str();
727            int64_t count = args.count() > 3 ? args.get_pos(3)->as_int() : -1;
728            if (count > 0) {
729                throw not_implemented_exception("String replace with count argument not implemented");
730            }
731            if (old_str != new_str) {
732                size_t pos = 0;
733                if (old_str.empty()) {
734                    std::string new_res;
735                    new_res.reserve(str.length() + new_str.length() * (str.length() + 1));
736                    new_res += new_str;
737                    for (const char c : str) {
738                        new_res.push_back(c);
739                        new_res += new_str;
740                    }
741                    str = new_res;
742                } else {
743                    while ((pos = str.find(old_str, pos)) != std::string::npos) {
744                        str.replace(pos, old_str.length(), new_str);
745                        pos += new_str.length();
746                    }
747                }
748            }
749            auto res = mk_val<value_string>(str);
750            res->val_str.mark_input_based_on(args.get_pos(0)->val_str);
751            return res;
752        }},
753        {"format", [](const func_args & args) -> value {
754            value val_input = args.get_pos(0);
755            if (!is_val<value_string>(val_input)) {
756                throw raised_exception("format() first argument must be a string");
757            }
758            const jinja::string & fmt = val_input->as_string();
759            const bool fmt_is_input = fmt.all_parts_are_input();
760
761            const std::string str = fmt.str();
762            jinja::string result;
763            std::string literal;
764            auto flush_literal = [&]() {
765                if (!literal.empty()) {
766                    result.parts.push_back({fmt_is_input, literal});
767                    literal.clear();
768                }
769            };
770
771            size_t arg_idx = 1; // positional args follow the format string
772            for (size_t i = 0; i < str.size(); ++i) {
773                if (str[i] != '{') {
774                    literal += str[i];
775                    continue;
776                }
777                if (i + 1 >= str.size() || str[i + 1] != '}') {
778                    throw not_implemented_exception("format() only supports simple '{}' placeholders");
779                }
780                ++i;
781                flush_literal();
782                const jinja::string arg_str = args.get_pos(arg_idx++)->as_string();
783                result.parts.insert(result.parts.end(), arg_str.parts.begin(), arg_str.parts.end());
784            }
785            flush_literal();
786            return mk_val<value_string>(result);
787        }},
788        {"int", [](const func_args & args) -> value {
789            value val_input   = args.get_pos(0);
790            value val_default = args.get_kwarg_or_pos("default", 1);
791            value val_base    = args.get_kwarg_or_pos("base",    2);
792            const int base = val_base->is_undefined() ? 10 : val_base->as_int();
793            if (base != 0 && (base < 2 || base > 36)) {
794                // an out-of-range base makes std::stoi fail fast on the MSVC CRT instead of throwing
795                throw raised_exception("int() base must be 0 or between 2 and 36");
796            }
797            if (is_val<value_string>(val_input) == false) {
798                throw raised_exception("int() first argument must be a string");
799            }
800            std::string str = val_input->as_string().str();
801            try {
802                return mk_val<value_int>(std::stoi(str, nullptr, base));
803            } catch (...) {
804                return mk_val<value_int>(val_default->is_undefined() ? 0 : val_default->as_int());
805            }
806        }},
807        {"float", [](const func_args & args) -> value {
808            args.ensure_vals<value_string>();
809            value val_default = args.get_kwarg_or_pos("default", 1);
810            std::string str = args.get_pos(0)->as_string().str();
811            try {
812                return mk_val<value_float>(std::stod(str));
813            } catch (...) {
814                return mk_val<value_float>(val_default->is_undefined() ? 0.0 : val_default->as_float());
815            }
816        }},
817        {"string", [](const func_args & args) -> value {
818            // no-op
819            args.ensure_vals<value_string>();
820            return mk_val<value_string>(args.get_pos(0)->as_string());
821        }},
822        {"default", [](const func_args & args) -> value {
823            value input = args.get_pos(0);
824            if (!is_val<value_string>(input)) {
825                throw raised_exception("default() first argument must be a string");
826            }
827            value default_val = mk_val<value_string>("");
828            if (args.count() > 1 && !args.get_pos(1)->is_undefined()) {
829                default_val = args.get_pos(1);
830            }
831            value boolean_val = args.get_kwarg_or_pos("boolean", 2); // undefined == false
832            if (input->is_undefined() || (boolean_val->as_bool() && !input->as_bool())) {
833                return default_val;
834            } else {
835                return input;
836            }
837        }},
838        {"slice", [](const func_args & args) -> value {
839            args.ensure_count(1, 4);
840            args.ensure_vals<value_string, value_int, value_int, value_int>(true, true, false, false);
841
842            auto arg0 = args.get_pos(1);
843            auto arg1 = args.get_pos(2, mk_val<value_undefined>());
844            auto arg2 = args.get_pos(3, mk_val<value_undefined>());
845
846            int64_t start, stop, step;
847            if (args.count() == 1) {
848                start = 0;
849                stop = arg0->as_int();
850                step = 1;
851            } else if (args.count() == 2) {
852                start = arg0->as_int();
853                stop = arg1->as_int();
854                step = 1;
855            } else {
856                start = arg0->as_int();
857                stop = arg1->as_int();
858                step = arg2->as_int();
859            }
860            if (step == 0) {
861                throw raised_exception("slice step cannot be zero");
862            }
863            auto input = args.get_pos(0);
864            auto sliced = slice(input->as_string().str(), start, stop, step);
865            auto res = mk_val<value_string>(sliced);
866            res->val_str.mark_input_based_on(input->as_string());
867            return res;
868        }},
869        {"safe", [](const func_args & args) -> value {
870            // no-op for now
871            args.ensure_vals<value_string>();
872            return args.get_pos(0);
873        }},
874        {"tojson", tojson},
875        {"indent", [](const func_args &args) -> value {
876            args.ensure_count(1, 4);
877            value val_input  = args.get_pos(0);
878            value val_width  = args.get_kwarg_or_pos("width", 1);
879            const bool first = args.get_kwarg_or_pos("first", 2)->as_bool(); // undefined == false
880            const bool blank = args.get_kwarg_or_pos("blank", 3)->as_bool(); // undefined == false
881            if (!is_val<value_string>(val_input)) {
882                throw raised_exception("indent() first argument must be a string");
883            }
884            std::string indent;
885            if (is_val<value_int>(val_width)) {
886                indent.assign(val_width->as_int(), ' ');
887            } else if (is_val<value_string>(val_width)) {
888                indent = val_width->as_string().str();
889            } else {
890                indent = "    ";
891            }
892            std::string indented;
893            std::string input = val_input->as_string().str();
894            std::istringstream iss = std::istringstream(input);
895            std::string line;
896            while (std::getline(iss, line)) {
897                if (!indented.empty()) {
898                    indented.push_back('\n');
899                }
900                if ((indented.empty() ? first : (!line.empty() || blank))) {
901                    indented += indent;
902                }
903                indented += line;
904            }
905            if (!input.empty() && input.back() == '\n') {
906                indented.push_back('\n');
907                if (blank) {
908                    indented += indent;
909                }
910            }
911
912            auto res = mk_val<value_string>(indented);
913            res->val_str.mark_input_based_on(val_input->as_string());
914            return res;
915        }},
916        {"join", string_join_not_implemented},
917    };
918    return builtins;
919}
920
921
922const func_builtins & value_bool_t::get_builtins() const {
923    static const func_handler tostring = [](const func_args & args) -> value {
924        args.ensure_vals<value_bool>();
925        bool val = args.get_pos(0)->as_bool();
926        return mk_val<value_string>(val ? "True" : "False");
927    };
928    static const func_builtins builtins = {
929        {"default", default_value},
930        {"int", [](const func_args & args) -> value {
931            args.ensure_vals<value_bool>();
932            bool val = args.get_pos(0)->as_bool();
933            return mk_val<value_int>(val ? 1 : 0);
934        }},
935        {"float", [](const func_args & args) -> value {
936            args.ensure_vals<value_bool>();
937            bool val = args.get_pos(0)->as_bool();
938            return mk_val<value_float>(val ? 1.0 : 0.0);
939        }},
940        {"safe", tostring},
941        {"string", tostring},
942        {"tojson", tojson},
943    };
944    return builtins;
945}
946
947[[noreturn]] static value array_unique_not_implemented(const func_args &) {
948    throw not_implemented_exception("Array unique builtin not implemented");
949}
950
951const func_builtins & value_array_t::get_builtins() const {
952    static const func_builtins builtins = {
953        {"default", default_value},
954        {"list", [](const func_args & args) -> value {
955            args.ensure_vals<value_array>();
956            const auto & arr = args.get_pos(0)->as_array();
957            auto result = mk_val<value_array>();
958            for (const auto& v : arr) {
959                result->push_back(v);
960            }
961            return result;
962        }},
963        {"first", [](const func_args & args) -> value {
964            args.ensure_vals<value_array>();
965            const auto & arr = args.get_pos(0)->as_array();
966            if (arr.empty()) {
967                return mk_val<value_undefined>();
968            }
969            return arr[0];
970        }},
971        {"last", [](const func_args & args) -> value {
972            args.ensure_vals<value_array>();
973            const auto & arr = args.get_pos(0)->as_array();
974            if (arr.empty()) {
975                return mk_val<value_undefined>();
976            }
977            return arr[arr.size() - 1];
978        }},
979        {"length", [](const func_args & args) -> value {
980            args.ensure_vals<value_array>();
981            const auto & arr = args.get_pos(0)->as_array();
982            return mk_val<value_int>(static_cast<int64_t>(arr.size()));
983        }},
984        {"slice", [](const func_args & args) -> value {
985            args.ensure_count(1, 4);
986            args.ensure_vals<value_array, value_int, value_int, value_int>(true, true, false, false);
987
988            auto val  = args.get_pos(0);
989            auto arg0 = args.get_pos(1);
990            auto arg1 = args.get_pos(2, mk_val<value_undefined>());
991            auto arg2 = args.get_pos(3, mk_val<value_undefined>());
992
993            int64_t start, stop, step;
994            if (args.count() == 1) {
995                start = 0;
996                stop = arg0->as_int();
997                step = 1;
998            } else if (args.count() == 2) {
999                start = arg0->as_int();
1000                stop = arg1->as_int();
1001                step = 1;
1002            } else {
1003                start = arg0->as_int();
1004                stop = arg1->as_int();
1005                step = arg2->as_int();
1006            }
1007            if (step == 0) {
1008                throw raised_exception("slice step cannot be zero");
1009            }
1010            auto arr = slice(val->as_array(), start, stop, step);
1011            return is_val<value_tuple>(val) ? mk_val<value_tuple>(std::move(arr)) : mk_val<value_array>(std::move(arr));
1012        }},
1013        {"selectattr", selectattr<false>},
1014        {"select", selectattr<false>},
1015        {"rejectattr", selectattr<true>},
1016        {"reject", selectattr<true>},
1017        {"join", [](const func_args & args) -> value {
1018            args.ensure_count(1, 3);
1019            if (!is_val<value_array>(args.get_pos(0))) {
1020                throw raised_exception("join() first argument must be an array");
1021            }
1022            value val_delim = args.get_kwarg_or_pos("d",         1);
1023            value attribute = args.get_kwarg_or_pos("attribute", 2);
1024            const auto & arr = args.get_pos(0)->as_array();
1025            const bool attr_is_int = is_val<value_int>(attribute);
1026            if (!attribute->is_undefined() && !is_val<value_string>(attribute) && !attr_is_int) {
1027                throw raised_exception("join() attribute must be string or integer");
1028            }
1029            const int64_t attr_int = attr_is_int ? attribute->as_int() : 0;
1030            const std::string delim = val_delim->is_undefined() ? "" : val_delim->as_string().str();
1031            std::string result;
1032            for (size_t i = 0; i < arr.size(); ++i) {
1033                value val_arr = arr[i];
1034                if (!attribute->is_undefined()) {
1035                    if (attr_is_int && is_val<value_array>(val_arr)) {
1036                        val_arr = val_arr->at(attr_int);
1037                    } else if (!attr_is_int && is_val<value_object>(val_arr)) {
1038                        val_arr = val_arr->at(attribute);
1039                    }
1040                }
1041                if (!is_val<value_string>(val_arr) && !is_val<value_int>(val_arr) && !is_val<value_float>(val_arr)) {
1042                    throw raised_exception("join() can only join arrays of strings or numerics");
1043                }
1044                result += val_arr->as_string().str();
1045                if (i < arr.size() - 1) {
1046                    result += delim;
1047                }
1048            }
1049            return mk_val<value_string>(result);
1050        }},
1051        {"string", [](const func_args & args) -> value {
1052            args.ensure_vals<value_array>();
1053            if (args.ctx.is_get_stats) {
1054                // mark as used (recursively) for stats
1055                auto val_input = args.get_pos(0);
1056                value_t::stats_t::mark_used(const_cast<value&>(val_input), true);
1057            }
1058            return mk_val<value_string>(args.get_pos(0)->as_string());
1059        }},
1060        {"tojson", tojson},
1061        {"map", [](const func_args & args) -> value {
1062            args.ensure_count(2);
1063            if (!is_val<value_array>(args.get_pos(0))) {
1064                throw raised_exception("map: first argument must be an array");
1065            }
1066            if (!is_val<value_kwarg>(args.get_args().at(1))) {
1067                throw not_implemented_exception("map: filter-mapping not implemented");
1068            }
1069            value val       = args.get_pos(0);
1070            value attribute = args.get_kwarg_or_pos("attribute", 1);
1071            const bool attr_is_int = is_val<value_int>(attribute);
1072            if (!is_val<value_string>(attribute) && !attr_is_int) {
1073                throw raised_exception("map: attribute must be string or integer");
1074            }
1075            const int64_t attr_int = attr_is_int ? attribute->as_int() : 0;
1076            value default_val = args.get_kwarg("default", mk_val<value_undefined>());
1077            auto out = mk_val<value_array>();
1078            auto arr = val->as_array();
1079            for (const auto & item : arr) {
1080                value attr_val;
1081                if (attr_is_int) {
1082                    attr_val = is_val<value_array>(item) ? item->at(attr_int, default_val) : default_val;
1083                } else {
1084                    attr_val = is_val<value_object>(item) ? item->at(attribute, default_val) : default_val;
1085                }
1086                out->push_back(attr_val);
1087            }
1088            return is_val<value_tuple>(val) ? mk_val<value_tuple>(std::move(out->as_array())) : out;
1089        }},
1090        {"append", [](const func_args & args) -> value {
1091            args.ensure_count(2);
1092            if (!is_val<value_array>(args.get_pos(0))) {
1093                throw raised_exception("append: first argument must be an array");
1094            }
1095            const value_array_t * arr = cast_val<value_array>(args.get_pos(0));
1096            // need to use const_cast here to modify the array
1097            value_array_t * arr_editable = const_cast<value_array_t *>(arr);
1098            arr_editable->push_back(args.get_pos(1));
1099            return args.get_pos(0);
1100        }},
1101        {"pop", [](const func_args & args) -> value {
1102            args.ensure_count(1, 2);
1103            args.ensure_vals<value_array, value_int>(true, false);
1104            int64_t index = args.count() == 2 ? args.get_pos(1)->as_int() : -1;
1105            const value_array_t * arr = cast_val<value_array>(args.get_pos(0));
1106            // need to use const_cast here to modify the array
1107            value_array_t * arr_editable = const_cast<value_array_t *>(arr);
1108            return arr_editable->pop_at(index);
1109        }},
1110        {"sort", [](const func_args & args) -> value {
1111            args.ensure_count(1, 4);
1112            if (!is_val<value_array>(args.get_pos(0))) {
1113                throw raised_exception("sort: first argument must be an array");
1114            }
1115            value val         = args.get_pos(0);
1116            value val_reverse = args.get_kwarg_or_pos("reverse",        1);
1117            value val_case    = args.get_kwarg_or_pos("case_sensitive", 2);
1118            value attribute   = args.get_kwarg_or_pos("attribute",      3);
1119            // FIXME: sorting is currently always case sensitive
1120            //const bool case_sensitive = val_case->as_bool(); // undefined == false
1121            const bool reverse = val_reverse->as_bool(); // undefined == false
1122            const bool attr_is_int = is_val<value_int>(attribute);
1123            const int64_t attr_int = attr_is_int ? attribute->as_int() : 0;
1124            std::vector<value> arr = val->as_array(); // copy
1125            std::sort(arr.begin(), arr.end(),[&](const value & a, const value & b) {
1126                value val_a = a;
1127                value val_b = b;
1128                if (!attribute->is_undefined()) {
1129                    if (attr_is_int && is_val<value_array>(a) && is_val<value_array>(b)) {
1130                        val_a = a->at(attr_int);
1131                        val_b = b->at(attr_int);
1132                    } else if (!attr_is_int && is_val<value_object>(a) && is_val<value_object>(b)) {
1133                        val_a = a->at(attribute);
1134                        val_b = b->at(attribute);
1135                    } else {
1136                        throw raised_exception("sort: unsupported object attribute comparison between " + a->type() + " and " + b->type());
1137                    }
1138                }
1139                return value_compare(val_a, val_b, reverse ? value_compare_op::gt : value_compare_op::lt);
1140            });
1141            return is_val<value_tuple>(val) ? mk_val<value_tuple>(std::move(arr)) : mk_val<value_array>(std::move(arr));
1142        }},
1143        {"reverse", [](const func_args & args) -> value {
1144            args.ensure_vals<value_array>();
1145            value val = args.get_pos(0);
1146            std::vector<value> arr = val->as_array(); // copy
1147            std::reverse(arr.begin(), arr.end());
1148            return is_val<value_tuple>(val) ? mk_val<value_tuple>(std::move(arr)) : mk_val<value_array>(std::move(arr));
1149        }},
1150        {"min", [](const func_args & args) -> value {
1151            args.ensure_count(1, 4);
1152            args.ensure_vals<value_array>();
1153            value val_case    = args.get_kwarg_or_pos("case_sensitive", 1);
1154            value attribute   = args.get_kwarg_or_pos("attribute",      2);
1155            if (!attribute->is_undefined()) {
1156                throw not_implemented_exception("min: attribute not implemented");
1157            }
1158            // FIXME: min is currently always case sensitive
1159            (void) val_case;
1160            const auto & arr = args.get_pos(0)->as_array();
1161            if (arr.empty()) {
1162                return mk_val<value_undefined>();
1163            }
1164            value result = arr[0];
1165            for (size_t i = 1; i < arr.size(); ++i) {
1166                if (value_compare(arr[i], result, value_compare_op::lt)) {
1167                    result = arr[i];
1168                }
1169            }
1170            return result;
1171        }},
1172        {"max", [](const func_args & args) -> value {
1173            args.ensure_count(1, 4);
1174            args.ensure_vals<value_array>();
1175            value val_case    = args.get_kwarg_or_pos("case_sensitive", 1);
1176            value attribute   = args.get_kwarg_or_pos("attribute",      2);
1177            if (!attribute->is_undefined()) {
1178                throw not_implemented_exception("max: attribute not implemented");
1179            }
1180            // FIXME: max is currently always case sensitive
1181            (void) val_case;
1182            const auto & arr = args.get_pos(0)->as_array();
1183            if (arr.empty()) {
1184                return mk_val<value_undefined>();
1185            }
1186            value result = arr[0];
1187            for (size_t i = 1; i < arr.size(); ++i) {
1188                if (value_compare(arr[i], result, value_compare_op::gt)) {
1189                    result = arr[i];
1190                }
1191            }
1192            return result;
1193        }},
1194        {"unique", array_unique_not_implemented},
1195    };
1196    return builtins;
1197}
1198
1199[[noreturn]] static value object_join_not_implemented(const func_args &) {
1200    throw not_implemented_exception("object join not implemented");
1201}
1202
1203const func_builtins & value_object_t::get_builtins() const {
1204    if (!has_builtins) {
1205        static const func_builtins no_builtins = {};
1206        return no_builtins;
1207    }
1208
1209    static const func_builtins builtins = {
1210        // {"default", default_value}, // cause issue with gpt-oss
1211        {"get", [](const func_args & args) -> value {
1212            args.ensure_count(2, 3);
1213            if (!is_val<value_object>(args.get_pos(0))) {
1214                throw raised_exception("get: first argument must be an object");
1215            }
1216            if (!is_val<value_string>(args.get_pos(1))) {
1217                throw raised_exception("get: second argument must be a string (key)");
1218            }
1219            value default_val = mk_val<value_none>();
1220            if (args.count() == 3) {
1221                default_val = args.get_pos(2);
1222            }
1223            const value obj = args.get_pos(0);
1224            const value key = args.get_pos(1);
1225            return obj->at(key, default_val);
1226        }},
1227        {"keys", [](const func_args & args) -> value {
1228            args.ensure_vals<value_object>();
1229            const auto & obj = args.get_pos(0)->as_ordered_object();
1230            auto result = mk_val<value_array>();
1231            for (const auto & pair : obj) {
1232                result->push_back(pair.first);
1233            }
1234            return result;
1235        }},
1236        {"values", [](const func_args & args) -> value {
1237            args.ensure_vals<value_object>();
1238            const auto & obj = args.get_pos(0)->as_ordered_object();
1239            auto result = mk_val<value_array>();
1240            for (const auto & pair : obj) {
1241                result->push_back(pair.second);
1242            }
1243            return result;
1244        }},
1245        {"items", [](const func_args & args) -> value {
1246            args.ensure_vals<value_object>();
1247            const auto & obj = args.get_pos(0)->as_ordered_object();
1248            auto result = mk_val<value_array>();
1249            for (const auto & pair : obj) {
1250                auto item = mk_val<value_tuple>(pair);
1251                result->push_back(std::move(item));
1252            }
1253            return result;
1254        }},
1255        {"tojson", tojson},
1256        {"string", [](const func_args & args) -> value {
1257            args.ensure_vals<value_object>();
1258            if (args.ctx.is_get_stats) {
1259                // mark as used (recursively) for stats
1260                auto val_input = args.get_pos(0);
1261                value_t::stats_t::mark_used(const_cast<value&>(val_input), true);
1262            }
1263            return mk_val<value_string>(args.get_pos(0)->as_string());
1264        }},
1265        {"length", [](const func_args & args) -> value {
1266            args.ensure_vals<value_object>();
1267            const auto & obj = args.get_pos(0)->as_ordered_object();
1268            return mk_val<value_int>(static_cast<int64_t>(obj.size()));
1269        }},
1270        {"tojson", [](const func_args & args) -> value {
1271            args.ensure_vals<value_object>();
1272            // use global to_json
1273            return global_builtins().at("tojson")(args);
1274        }},
1275        {"dictsort", [](const func_args & args) -> value {
1276            value val_input   = args.get_pos(0);
1277            value val_case    = args.get_kwarg_or_pos("case_sensitive", 1);
1278            value val_by      = args.get_kwarg_or_pos("by",             2);
1279            value val_reverse = args.get_kwarg_or_pos("reverse",        3);
1280            // FIXME: sorting is currently always case sensitive
1281            //const bool case_sensitive = val_case->as_bool(); // undefined == false
1282            const bool reverse = val_reverse->as_bool(); // undefined == false
1283            const bool by_value = is_val<value_string>(val_by) && val_by->as_string().str() == "value" ? true : false;
1284            auto result = mk_val<value_object>(val_input); // copy
1285            std::sort(result->val_obj.begin(), result->val_obj.end(), [&](const auto & a, const auto & b) {
1286                if (by_value) {
1287                    return value_compare(a.second, b.second, reverse ? value_compare_op::gt : value_compare_op::lt);
1288                } else {
1289                    return value_compare(a.first, b.first, reverse ? value_compare_op::gt : value_compare_op::lt);
1290                }
1291            });
1292            return result;
1293        }},
1294        {"join", object_join_not_implemented},
1295    };
1296    return builtins;
1297}
1298
1299const func_builtins & value_none_t::get_builtins() const {
1300    static const func_handler tostring = [](const func_args &) -> value {
1301        return mk_val<value_string>("None");
1302    };
1303    static const func_builtins builtins = {
1304        {"default", default_value},
1305        {"tojson", tojson},
1306        {"string", tostring},
1307        {"safe", tostring},
1308        {"items", empty_value_fn<value_array>},
1309        {"map", empty_value_fn<value_array>},
1310        {"reject", empty_value_fn<value_array>},
1311        {"rejectattr", empty_value_fn<value_array>},
1312        {"select", empty_value_fn<value_array>},
1313        {"selectattr", empty_value_fn<value_array>},
1314        {"unique", empty_value_fn<value_array>},
1315    };
1316    return builtins;
1317}
1318
1319
1320const func_builtins & value_undefined_t::get_builtins() const {
1321    static const func_builtins builtins = {
1322        {"default", default_value},
1323        {"capitalize", empty_value_fn<value_string>},
1324        {"first", empty_value_fn<value_undefined>},
1325        {"items", empty_value_fn<value_array>},
1326        {"join", empty_value_fn<value_string>},
1327        {"last", empty_value_fn<value_undefined>},
1328        {"length", empty_value_fn<value_int>},
1329        {"list", empty_value_fn<value_array>},
1330        {"lower", empty_value_fn<value_string>},
1331        {"map", empty_value_fn<value_array>},
1332        {"max", empty_value_fn<value_undefined>},
1333        {"min", empty_value_fn<value_undefined>},
1334        {"reject", empty_value_fn<value_array>},
1335        {"rejectattr", empty_value_fn<value_array>},
1336        {"replace", empty_value_fn<value_string>},
1337        {"reverse", empty_value_fn<value_array>},
1338        {"safe", empty_value_fn<value_string>},
1339        {"select", empty_value_fn<value_array>},
1340        {"selectattr", empty_value_fn<value_array>},
1341        {"sort", empty_value_fn<value_array>},
1342        {"string", empty_value_fn<value_string>},
1343        {"strip", empty_value_fn<value_string>},
1344        {"sum", empty_value_fn<value_int>},
1345        {"title", empty_value_fn<value_string>},
1346        {"truncate", empty_value_fn<value_string>},
1347        {"unique", empty_value_fn<value_array>},
1348        {"upper", empty_value_fn<value_string>},
1349        {"wordcount", empty_value_fn<value_int>},
1350    };
1351    return builtins;
1352}
1353
1354
1355//////////////////////////////////
1356
1357
1358static value from_json(const common_json & j, bool mark_input) {
1359    if (j.is_null()) {
1360        return mk_val<value_none>();
1361    } else if (j.is_boolean()) {
1362        return mk_val<value_bool>(j.get<bool>());
1363    } else if (j.is_number_integer()) {
1364        return mk_val<value_int>(j.get<int64_t>());
1365    } else if (j.is_number_float()) {
1366        return mk_val<value_float>(j.get<double>());
1367    } else if (j.is_string()) {
1368        auto str = mk_val<value_string>(j.get<std::string>());
1369        if (mark_input) {
1370            str->mark_input();
1371        }
1372        return str;
1373    } else if (j.is_array()) {
1374        auto arr = mk_val<value_array>();
1375        for (const auto & item : j) {
1376            arr->push_back(from_json(item, mark_input));
1377        }
1378        return arr;
1379    } else if (j.is_object()) {
1380        auto obj = mk_val<value_object>();
1381        for (auto it = j.begin(); it != j.end(); ++it) {
1382            obj->insert(it.key(), from_json(it.value(), mark_input));
1383        }
1384        return obj;
1385    } else {
1386        throw std::runtime_error("Unsupported JSON value type");
1387    }
1388}
1389
1390// compare operator for value_t
1391bool value_compare(const value & a, const value & b, value_compare_op op) {
1392    auto cmp = [&]() {
1393        // compare numeric types
1394        if ((is_val<value_int>(a) || is_val<value_float>(a)) &&
1395            (is_val<value_int>(b) || is_val<value_float>(b))){
1396            try {
1397                if (op == value_compare_op::eq) {
1398                    return a->as_float() == b->as_float();
1399                } else if (op == value_compare_op::ge) {
1400                    return a->as_float() >= b->as_float();
1401                } else if (op == value_compare_op::gt) {
1402                    return a->as_float() > b->as_float();
1403                } else if (op == value_compare_op::lt) {
1404                    return a->as_float() < b->as_float();
1405                } else if (op == value_compare_op::ne) {
1406                    return a->as_float() != b->as_float();
1407                } else {
1408                    throw std::runtime_error("Unsupported comparison operator for numeric types");
1409                }
1410            } catch (...) {}
1411        }
1412        // compare string and number
1413        // TODO: not sure if this is the right behavior
1414        if ((is_val<value_string>(b) && (is_val<value_int>(a) || is_val<value_float>(a))) ||
1415            (is_val<value_string>(a) && (is_val<value_int>(b) || is_val<value_float>(b))) ||
1416            (is_val<value_string>(a) && is_val<value_string>(b))) {
1417            try {
1418                if (op == value_compare_op::eq) {
1419                    return a->as_string().str() == b->as_string().str();
1420                } else if (op == value_compare_op::ge) {
1421                    return a->as_string().str() >= b->as_string().str();
1422                } else if (op == value_compare_op::gt) {
1423                    return a->as_string().str() > b->as_string().str();
1424                } else if (op == value_compare_op::lt) {
1425                    return a->as_string().str() < b->as_string().str();
1426                } else if (op == value_compare_op::ne) {
1427                    return a->as_string().str() != b->as_string().str();
1428                } else {
1429                    throw std::runtime_error("Unsupported comparison operator for string/number types");
1430                }
1431            } catch (...) {}
1432        }
1433        // compare boolean simple
1434        if (is_val<value_bool>(a) && is_val<value_bool>(b)) {
1435            if (op == value_compare_op::eq) {
1436                return a->as_bool() == b->as_bool();
1437            } else if (op == value_compare_op::ne) {
1438                return a->as_bool() != b->as_bool();
1439            } else {
1440                throw std::runtime_error("Unsupported comparison operator for bool type");
1441            }
1442        }
1443        // compare by type
1444        if (a->type() != b->type()) {
1445            return false;
1446        }
1447        return false;
1448    };
1449    auto result = cmp();
1450    JJ_DEBUG("Comparing types: %s and %s result=%d", a->type().c_str(), b->type().c_str(), result);
1451    return result;
1452}
1453
1454template<>
1455void global_from_json(context & ctx, const common_json & json_obj, bool mark_input) {
1456    // printf("global_from_json: %s\n" , json_obj.dump(2).c_str());
1457    if (json_obj.is_null() || !json_obj.is_object()) {
1458        throw std::runtime_error("global_from_json: input JSON value must be an object");
1459    }
1460    for (auto it = json_obj.begin(); it != json_obj.end(); ++it) {
1461        JJ_DEBUG("global_from_json: setting key '%s'", it.key().c_str());
1462        ctx.set_val(it.key(), from_json(it.value(), mark_input));
1463    }
1464}
1465
1466// recursively convert value to JSON string
1467// TODO: avoid circular references
1468static void value_to_json_internal(std::ostringstream & oss, const value & val, int curr_lvl, int indent, const std::string_view item_sep, const std::string_view key_sep) {
1469    auto indent_str = [indent, curr_lvl]() -> std::string {
1470        return (indent > 0) ? std::string(curr_lvl * indent, ' ') : "";
1471    };
1472    auto newline = [indent]() -> std::string {
1473        return (indent >= 0) ? "\n" : "";
1474    };
1475
1476    if (is_val<value_none>(val) || val->is_undefined()) {
1477        oss << "null";
1478    } else if (is_val<value_bool>(val)) {
1479        oss << (val->as_bool() ? "true" : "false");
1480    } else if (is_val<value_int>(val)) {
1481        oss << val->as_int();
1482    } else if (is_val<value_float>(val)) {
1483        oss << val->as_float();
1484    } else if (is_val<value_string>(val)) {
1485        oss << "\"";
1486        for (char c : val->as_string().str()) {
1487            switch (c) {
1488                case '"': oss << "\\\""; break;
1489                case '\\': oss << "\\\\"; break;
1490                case '\b': oss << "\\b"; break;
1491                case '\f': oss << "\\f"; break;
1492                case '\n': oss << "\\n"; break;
1493                case '\r': oss << "\\r"; break;
1494                case '\t': oss << "\\t"; break;
1495                default:
1496                    if (static_cast<unsigned char>(c) < 0x20) {
1497                        char buf[7];
1498                        snprintf(buf, sizeof(buf), "\\u%04x", static_cast<unsigned char>(c));
1499                        oss << buf;
1500                    } else {
1501                        oss << c;
1502                    }
1503            }
1504        }
1505        oss << "\"";
1506    } else if (is_val<value_array>(val)) {
1507        const auto & arr = val->as_array();
1508        oss << "[";
1509        if (!arr.empty()) {
1510            oss << newline();
1511            for (size_t i = 0; i < arr.size(); ++i) {
1512                oss << indent_str() << (indent > 0 ? std::string(indent, ' ') : "");
1513                value_to_json_internal(oss, arr[i], curr_lvl + 1, indent, item_sep, key_sep);
1514                if (i < arr.size() - 1) {
1515                    oss << item_sep;
1516                }
1517                oss << newline();
1518            }
1519            oss << indent_str();
1520        }
1521        oss << "]";
1522    } else if (is_val<value_object>(val)) {
1523        const auto & obj = val->as_ordered_object(); // IMPORTANT: need to keep exact order
1524        oss << "{";
1525        if (!obj.empty()) {
1526            oss << newline();
1527            size_t i = 0;
1528            for (const auto & pair : obj) {
1529                oss << indent_str() << (indent > 0 ? std::string(indent, ' ') : "");
1530                value_to_json_internal(oss, mk_val<value_string>(pair.first->as_string().str()), curr_lvl + 1, indent, item_sep, key_sep);
1531                oss << key_sep;
1532                value_to_json_internal(oss, pair.second, curr_lvl + 1, indent, item_sep, key_sep);
1533                if (i < obj.size() - 1) {
1534                    oss << item_sep;
1535                }
1536                oss << newline();
1537                ++i;
1538            }
1539            oss << indent_str();
1540        }
1541        oss << "}";
1542    } else {
1543        oss << "null";
1544    }
1545}
1546
1547std::string value_to_json(const value & val, int indent, const std::string_view item_sep, const std::string_view key_sep) {
1548    std::ostringstream oss;
1549    value_to_json_internal(oss, val, 0, indent, item_sep, key_sep);
1550    JJ_DEBUG("value_to_json: result=%s", oss.str().c_str());
1551    return oss.str();
1552}
1553
1554// TODO: avoid circular references
1555std::string value_to_string_repr(const value & val) {
1556    if (is_val<value_string>(val)) {
1557        const std::string val_str = val->as_string().str();
1558
1559        if (val_str.find('\'') != std::string::npos) {
1560            return value_to_json(val);
1561        } else {
1562            return "'" + val_str + "'";
1563        }
1564    } else {
1565        return val->as_repr();
1566    }
1567}
1568
1569// stats utility
1570void value_t::stats_t::mark_used(value & val, bool deep) {
1571    val->stats.used = true;
1572    if (deep) {
1573        if (is_val<value_array>(val)) {
1574            for (auto & item : val->val_arr) {
1575                mark_used(item, deep);
1576            }
1577        } else if (is_val<value_object>(val)) {
1578            for (auto & pair : val->val_obj) {
1579                mark_used(pair.first, deep);
1580                mark_used(pair.second, deep);
1581            }
1582        }
1583    }
1584}
1585
1586} // namespace jinja