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