| | #include "runtime.h"
|
| | #include "value.h"
|
| |
|
| |
|
| | #include <nlohmann/json.hpp>
|
| |
|
| | #include <sstream>
|
| | #include <string>
|
| | #include <cctype>
|
| | #include <vector>
|
| | #include <optional>
|
| | #include <algorithm>
|
| |
|
| | #define FILENAME "jinja-value"
|
| |
|
| | namespace jinja {
|
| |
|
| |
|
| |
|
| | value func_args::get_kwarg(const std::string & key, value default_val) const {
|
| | for (const auto & arg : args) {
|
| | if (is_val<value_kwarg>(arg)) {
|
| | auto * kwarg = cast_val<value_kwarg>(arg);
|
| | if (kwarg->key == key) {
|
| | return kwarg->val;
|
| | }
|
| | }
|
| | }
|
| | return default_val;
|
| | }
|
| |
|
| | value func_args::get_kwarg_or_pos(const std::string & key, size_t pos) const {
|
| | value val = get_kwarg(key, mk_val<value_undefined>());
|
| |
|
| | if (val->is_undefined() && pos < count() && !is_val<value_kwarg>(args[pos])) {
|
| | return args[pos];
|
| | }
|
| |
|
| | return val;
|
| | }
|
| |
|
| | value func_args::get_pos(size_t pos) const {
|
| | if (count() > pos) {
|
| | return args[pos];
|
| | }
|
| | throw raised_exception("Function '" + func_name + "' expected at least " + std::to_string(pos + 1) + " arguments, got " + std::to_string(count()));
|
| | }
|
| |
|
| | value func_args::get_pos(size_t pos, value default_val) const {
|
| | if (count() > pos) {
|
| | return args[pos];
|
| | }
|
| | return default_val;
|
| | }
|
| |
|
| | void func_args::push_back(const value & val) {
|
| | args.push_back(val);
|
| | }
|
| |
|
| | void func_args::push_front(const value & val) {
|
| | args.insert(args.begin(), val);
|
| | }
|
| |
|
| | const std::vector<value> & func_args::get_args() const {
|
| | return args;
|
| | }
|
| |
|
| | |
| | |
| |
|
| | template<typename T>
|
| | static T slice(const T & array, int64_t start, int64_t stop, int64_t step = 1) {
|
| | int64_t len = static_cast<int64_t>(array.size());
|
| | int64_t direction = (step > 0) ? 1 : ((step < 0) ? -1 : 0);
|
| | int64_t start_val = 0;
|
| | int64_t stop_val = 0;
|
| | if (direction >= 0) {
|
| | start_val = start;
|
| | if (start_val < 0) {
|
| | start_val = std::max(len + start_val, (int64_t)0);
|
| | } else {
|
| | start_val = std::min(start_val, len);
|
| | }
|
| |
|
| | stop_val = stop;
|
| | if (stop_val < 0) {
|
| | stop_val = std::max(len + stop_val, (int64_t)0);
|
| | } else {
|
| | stop_val = std::min(stop_val, len);
|
| | }
|
| | } else {
|
| | start_val = len - 1;
|
| | if (start_val < 0) {
|
| | start_val = std::max(len + start_val, (int64_t)-1);
|
| | } else {
|
| | start_val = std::min(start_val, len - 1);
|
| | }
|
| |
|
| | stop_val = -1;
|
| | if (stop_val < -1) {
|
| | stop_val = std::max(len + stop_val, (int64_t)-1);
|
| | } else {
|
| | stop_val = std::min(stop_val, len - 1);
|
| | }
|
| | }
|
| | T result;
|
| | if (direction == 0) {
|
| | return result;
|
| | }
|
| | for (int64_t i = start_val; direction * i < direction * stop_val; i += step) {
|
| | if (i >= 0 && i < len) {
|
| | result.push_back(array[static_cast<size_t>(i)]);
|
| | }
|
| | }
|
| | return result;
|
| | }
|
| |
|
| | template<typename T>
|
| | static value empty_value_fn(const func_args &) {
|
| | if constexpr (std::is_same_v<T, value_int>) {
|
| | return mk_val<T>(0);
|
| | } else if constexpr (std::is_same_v<T, value_float>) {
|
| | return mk_val<T>(0.0);
|
| | } else if constexpr (std::is_same_v<T, value_bool>) {
|
| | return mk_val<T>(false);
|
| | } else {
|
| | return mk_val<T>();
|
| | }
|
| | }
|
| | template<typename T>
|
| | static value test_type_fn(const func_args & args) {
|
| | args.ensure_count(1);
|
| | bool is_type = is_val<T>(args.get_pos(0));
|
| | JJ_DEBUG("test_type_fn: type=%s result=%d", typeid(T).name(), is_type ? 1 : 0);
|
| | return mk_val<value_bool>(is_type);
|
| | }
|
| | template<typename T, typename U>
|
| | static value test_type_fn(const func_args & args) {
|
| | args.ensure_count(1);
|
| | bool is_type = is_val<T>(args.get_pos(0)) || is_val<U>(args.get_pos(0));
|
| | JJ_DEBUG("test_type_fn: type=%s or %s result=%d", typeid(T).name(), typeid(U).name(), is_type ? 1 : 0);
|
| | return mk_val<value_bool>(is_type);
|
| | }
|
| | template<typename T, typename U, typename V>
|
| | static value test_type_fn(const func_args & args) {
|
| | args.ensure_count(1);
|
| | bool is_type = is_val<T>(args.get_pos(0)) || is_val<U>(args.get_pos(0)) || is_val<V>(args.get_pos(0));
|
| | 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);
|
| | return mk_val<value_bool>(is_type);
|
| | }
|
| | template<value_compare_op op>
|
| | static value test_compare_fn(const func_args & args) {
|
| | args.ensure_count(2, 2);
|
| | return mk_val<value_bool>(value_compare(args.get_pos(0), args.get_pos(1), op));
|
| | }
|
| |
|
| | static value tojson(const func_args & args) {
|
| | args.ensure_count(1, 5);
|
| | value val_ascii = args.get_kwarg_or_pos("ensure_ascii", 1);
|
| | value val_indent = args.get_kwarg_or_pos("indent", 2);
|
| | value val_separators = args.get_kwarg_or_pos("separators", 3);
|
| | value val_sort = args.get_kwarg_or_pos("sort_keys", 4);
|
| | int indent = -1;
|
| | if (args.ctx.is_get_stats) {
|
| |
|
| | auto val_input = args.get_pos(0);
|
| | value_t::stats_t::mark_used(const_cast<value&>(val_input), true);
|
| | }
|
| | if (is_val<value_int>(val_indent)) {
|
| | indent = static_cast<int>(val_indent->as_int());
|
| | }
|
| | if (val_ascii->as_bool()) {
|
| | throw not_implemented_exception("tojson ensure_ascii=true not implemented");
|
| | }
|
| | if (val_sort->as_bool()) {
|
| | throw not_implemented_exception("tojson sort_keys=true not implemented");
|
| | }
|
| | auto separators = (is_val<value_array>(val_separators) ? val_separators : mk_val<value_array>())->as_array();
|
| | std::string item_sep = separators.size() > 0 ? separators[0]->as_string().str() : (indent < 0 ? ", " : ",");
|
| | std::string key_sep = separators.size() > 1 ? separators[1]->as_string().str() : ": ";
|
| | std::string json_str = value_to_json(args.get_pos(0), indent, item_sep, key_sep);
|
| | return mk_val<value_string>(json_str);
|
| | }
|
| |
|
| | template<bool is_reject>
|
| | static value selectattr(const func_args & args) {
|
| | args.ensure_count(2, 4);
|
| | args.ensure_vals<value_array, value_string, value_string, value_string>(true, true, false, false);
|
| |
|
| | auto arr = args.get_pos(0)->as_array();
|
| | auto attribute = args.get_pos(1);
|
| | auto out = mk_val<value_array>();
|
| | value val_default = mk_val<value_undefined>();
|
| |
|
| | if (args.count() == 2) {
|
| |
|
| | for (const auto & item : arr) {
|
| | if (!is_val<value_object>(item)) {
|
| | throw raised_exception("selectattr: item is not an object");
|
| | }
|
| | value attr_val = item->at(attribute, val_default);
|
| | bool is_selected = attr_val->as_bool();
|
| | if constexpr (is_reject) is_selected = !is_selected;
|
| | if (is_selected) out->push_back(item);
|
| | }
|
| | return out;
|
| |
|
| | } else if (args.count() == 3) {
|
| |
|
| |
|
| | std::string test_name = args.get_pos(1)->as_string().str();
|
| | value test_val = args.get_pos(2);
|
| | auto & builtins = global_builtins();
|
| | auto it = builtins.find("test_is_" + test_name);
|
| | if (it == builtins.end()) {
|
| | throw raised_exception("selectattr: unknown test '" + test_name + "'");
|
| | }
|
| | auto test_fn = it->second;
|
| | for (const auto & item : arr) {
|
| | func_args test_args(args.ctx);
|
| | test_args.push_back(item);
|
| | test_args.push_back(test_val);
|
| | value test_result = test_fn(test_args);
|
| | bool is_selected = test_result->as_bool();
|
| | if constexpr (is_reject) is_selected = !is_selected;
|
| | if (is_selected) out->push_back(item);
|
| | }
|
| | return out;
|
| |
|
| | } else if (args.count() == 4) {
|
| |
|
| |
|
| | std::string test_name = args.get_pos(2)->as_string().str();
|
| | auto extra_arg = args.get_pos(3);
|
| | auto & builtins = global_builtins();
|
| | auto it = builtins.find("test_is_" + test_name);
|
| | if (it == builtins.end()) {
|
| | throw raised_exception("selectattr: unknown test '" + test_name + "'");
|
| | }
|
| | auto test_fn = it->second;
|
| | for (const auto & item : arr) {
|
| | if (!is_val<value_object>(item)) {
|
| | throw raised_exception("selectattr: item is not an object");
|
| | }
|
| | value attr_val = item->at(attribute, val_default);
|
| | func_args test_args(args.ctx);
|
| | test_args.push_back(attr_val);
|
| | test_args.push_back(extra_arg);
|
| | value test_result = test_fn(test_args);
|
| | bool is_selected = test_result->as_bool();
|
| | if constexpr (is_reject) is_selected = !is_selected;
|
| | if (is_selected) out->push_back(item);
|
| | }
|
| | return out;
|
| | } else {
|
| | throw raised_exception("selectattr: invalid number of arguments");
|
| | }
|
| |
|
| | return out;
|
| | }
|
| |
|
| | static value default_value(const func_args & args) {
|
| | args.ensure_count(2, 3);
|
| | value val_check = args.get_kwarg_or_pos("boolean", 2);
|
| | bool check_bool = val_check->as_bool();
|
| | bool no_value = check_bool
|
| | ? (!args.get_pos(0)->as_bool())
|
| | : (args.get_pos(0)->is_undefined() || args.get_pos(0)->is_none());
|
| | return no_value ? args.get_pos(1) : args.get_pos(0);
|
| | }
|
| |
|
| | const func_builtins & global_builtins() {
|
| | static const func_builtins builtins = {
|
| | {"raise_exception", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_string>();
|
| | std::string msg = args.get_pos(0)->as_string().str();
|
| | throw raised_exception("Jinja Exception: " + msg);
|
| | }},
|
| | {"namespace", [](const func_args & args) -> value {
|
| | auto out = mk_val<value_object>();
|
| | for (const auto & arg : args.get_args()) {
|
| | if (!is_val<value_kwarg>(arg)) {
|
| | throw raised_exception("namespace() arguments must be kwargs");
|
| | }
|
| | auto kwarg = cast_val<value_kwarg>(arg);
|
| | JJ_DEBUG("namespace: adding key '%s'", kwarg->key.c_str());
|
| | out->insert(kwarg->key, kwarg->val);
|
| | }
|
| | return out;
|
| | }},
|
| | {"strftime_now", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_string>();
|
| | std::string format = args.get_pos(0)->as_string().str();
|
| |
|
| |
|
| | char buf[100];
|
| | if (std::strftime(buf, sizeof(buf), format.c_str(), std::localtime(&args.ctx.current_time))) {
|
| | return mk_val<value_string>(std::string(buf));
|
| | } else {
|
| | throw raised_exception("strftime_now: failed to format time");
|
| | }
|
| | }},
|
| | {"range", [](const func_args & args) -> value {
|
| | args.ensure_count(1, 3);
|
| | args.ensure_vals<value_int, value_int, value_int>(true, false, false);
|
| |
|
| | auto arg0 = args.get_pos(0);
|
| | auto arg1 = args.get_pos(1, mk_val<value_undefined>());
|
| | auto arg2 = args.get_pos(2, mk_val<value_undefined>());
|
| |
|
| | int64_t start, stop, step;
|
| | if (args.count() == 1) {
|
| | start = 0;
|
| | stop = arg0->as_int();
|
| | step = 1;
|
| | } else if (args.count() == 2) {
|
| | start = arg0->as_int();
|
| | stop = arg1->as_int();
|
| | step = 1;
|
| | } else {
|
| | start = arg0->as_int();
|
| | stop = arg1->as_int();
|
| | step = arg2->as_int();
|
| | }
|
| |
|
| | auto out = mk_val<value_array>();
|
| | if (step == 0) {
|
| | throw raised_exception("range() step argument must not be zero");
|
| | }
|
| | if (step > 0) {
|
| | for (int64_t i = start; i < stop; i += step) {
|
| | out->push_back(mk_val<value_int>(i));
|
| | }
|
| | } else {
|
| | for (int64_t i = start; i > stop; i += step) {
|
| | out->push_back(mk_val<value_int>(i));
|
| | }
|
| | }
|
| | return out;
|
| | }},
|
| | {"tojson", tojson},
|
| |
|
| |
|
| | {"test_is_boolean", test_type_fn<value_bool>},
|
| | {"test_is_callable", test_type_fn<value_func>},
|
| | {"test_is_odd", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_int>();
|
| | int64_t val = args.get_pos(0)->as_int();
|
| | return mk_val<value_bool>(val % 2 != 0);
|
| | }},
|
| | {"test_is_even", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_int>();
|
| | int64_t val = args.get_pos(0)->as_int();
|
| | return mk_val<value_bool>(val % 2 == 0);
|
| | }},
|
| | {"test_is_false", [](const func_args & args) -> value {
|
| | args.ensure_count(1);
|
| | bool val = is_val<value_bool>(args.get_pos(0)) && !args.get_pos(0)->as_bool();
|
| | return mk_val<value_bool>(val);
|
| | }},
|
| | {"test_is_true", [](const func_args & args) -> value {
|
| | args.ensure_count(1);
|
| | bool val = is_val<value_bool>(args.get_pos(0)) && args.get_pos(0)->as_bool();
|
| | return mk_val<value_bool>(val);
|
| | }},
|
| | {"test_is_divisibleby", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_int, value_int>();
|
| | bool res = args.get_pos(0)->val_int % args.get_pos(1)->val_int == 0;
|
| | return mk_val<value_bool>(res);
|
| | }},
|
| | {"test_is_string", test_type_fn<value_string>},
|
| | {"test_is_integer", test_type_fn<value_int>},
|
| | {"test_is_float", test_type_fn<value_float>},
|
| | {"test_is_number", test_type_fn<value_int, value_float>},
|
| | {"test_is_iterable", test_type_fn<value_array, value_string, value_undefined>},
|
| | {"test_is_sequence", test_type_fn<value_array, value_string, value_undefined>},
|
| | {"test_is_mapping", test_type_fn<value_object>},
|
| | {"test_is_lower", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_string>();
|
| | return mk_val<value_bool>(args.get_pos(0)->val_str.is_lowercase());
|
| | }},
|
| | {"test_is_upper", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_string>();
|
| | return mk_val<value_bool>(args.get_pos(0)->val_str.is_uppercase());
|
| | }},
|
| | {"test_is_none", test_type_fn<value_none>},
|
| | {"test_is_defined", [](const func_args & args) -> value {
|
| | args.ensure_count(1);
|
| | bool res = !args.get_pos(0)->is_undefined();
|
| | JJ_DEBUG("test_is_defined: result=%d", res ? 1 : 0);
|
| | return mk_val<value_bool>(res);
|
| | }},
|
| | {"test_is_undefined", test_type_fn<value_undefined>},
|
| | {"test_is_eq", test_compare_fn<value_compare_op::eq>},
|
| | {"test_is_equalto", test_compare_fn<value_compare_op::eq>},
|
| | {"test_is_ge", test_compare_fn<value_compare_op::ge>},
|
| | {"test_is_gt", test_compare_fn<value_compare_op::gt>},
|
| | {"test_is_greaterthan", test_compare_fn<value_compare_op::gt>},
|
| | {"test_is_lt", test_compare_fn<value_compare_op::lt>},
|
| | {"test_is_lessthan", test_compare_fn<value_compare_op::lt>},
|
| | {"test_is_ne", test_compare_fn<value_compare_op::ne>},
|
| | {"test_is_in", [](const func_args & args) -> value {
|
| | args.ensure_count(2);
|
| | auto needle = args.get_pos(0);
|
| | auto haystack = args.get_pos(1);
|
| | if (is_val<value_undefined>(haystack)) {
|
| | return mk_val<value_bool>(false);
|
| | }
|
| | if (is_val<value_array>(haystack)) {
|
| | for (const auto & item : haystack->as_array()) {
|
| | if (*needle == *item) {
|
| | return mk_val<value_bool>(true);
|
| | }
|
| | }
|
| | return mk_val<value_bool>(false);
|
| | }
|
| | if (is_val<value_string>(haystack)) {
|
| | if (!is_val<value_string>(needle)) {
|
| | throw raised_exception("'in' test expects args[1] as string when args[0] is string, got args[1] as " + needle->type());
|
| | }
|
| | return mk_val<value_bool>(
|
| | haystack->as_string().str().find(needle->as_string().str()) != std::string::npos);
|
| | }
|
| | if (is_val<value_object>(haystack)) {
|
| | return mk_val<value_bool>(haystack->has_key(needle));
|
| | }
|
| | throw raised_exception("'in' test expects iterable as first argument, got " + haystack->type());
|
| | }},
|
| | {"test_is_test", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_string>();
|
| | auto & builtins = global_builtins();
|
| | std::string test_name = args.get_pos(0)->val_str.str();
|
| | auto it = builtins.find("test_is_" + test_name);
|
| | bool res = it != builtins.end();
|
| | return mk_val<value_bool>(res);
|
| | }},
|
| | {"test_is_sameas", [](const func_args & args) -> value {
|
| |
|
| | (void)args;
|
| | throw not_implemented_exception("sameas test not implemented");
|
| | }},
|
| | {"test_is_escaped", [](const func_args & args) -> value {
|
| | (void)args;
|
| | throw not_implemented_exception("escaped test not implemented");
|
| | }},
|
| | {"test_is_filter", [](const func_args & args) -> value {
|
| | (void)args;
|
| | throw not_implemented_exception("filter test not implemented");
|
| | }},
|
| | };
|
| | return builtins;
|
| | }
|
| |
|
| |
|
| | const func_builtins & value_int_t::get_builtins() const {
|
| | static const func_builtins builtins = {
|
| | {"default", default_value},
|
| | {"abs", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_int>();
|
| | int64_t val = args.get_pos(0)->as_int();
|
| | return mk_val<value_int>(val < 0 ? -val : val);
|
| | }},
|
| | {"float", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_int>();
|
| | double val = static_cast<double>(args.get_pos(0)->as_int());
|
| | return mk_val<value_float>(val);
|
| | }},
|
| | {"tojson", tojson},
|
| | {"string", tojson},
|
| | };
|
| | return builtins;
|
| | }
|
| |
|
| |
|
| | const func_builtins & value_float_t::get_builtins() const {
|
| | static const func_builtins builtins = {
|
| | {"default", default_value},
|
| | {"abs", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_float>();
|
| | double val = args.get_pos(0)->as_float();
|
| | return mk_val<value_float>(val < 0.0 ? -val : val);
|
| | }},
|
| | {"int", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_float>();
|
| | int64_t val = static_cast<int64_t>(args.get_pos(0)->as_float());
|
| | return mk_val<value_int>(val);
|
| | }},
|
| | {"tojson", tojson},
|
| | {"string", tojson},
|
| | };
|
| | return builtins;
|
| | }
|
| |
|
| | static bool string_startswith(const std::string & str, const std::string & prefix) {
|
| | if (str.length() < prefix.length()) return false;
|
| | return str.compare(0, prefix.length(), prefix) == 0;
|
| | }
|
| |
|
| | static bool string_endswith(const std::string & str, const std::string & suffix) {
|
| | if (str.length() < suffix.length()) return false;
|
| | return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0;
|
| | }
|
| |
|
| | const func_builtins & value_string_t::get_builtins() const {
|
| | static const func_builtins builtins = {
|
| | {"default", default_value},
|
| | {"upper", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_string>();
|
| | jinja::string str = args.get_pos(0)->as_string().uppercase();
|
| | return mk_val<value_string>(str);
|
| | }},
|
| | {"lower", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_string>();
|
| | jinja::string str = args.get_pos(0)->as_string().lowercase();
|
| | return mk_val<value_string>(str);
|
| | }},
|
| | {"strip", [](const func_args & args) -> value {
|
| | value val_input = args.get_pos(0);
|
| | if (!is_val<value_string>(val_input)) {
|
| | throw raised_exception("strip() first argument must be a string");
|
| | }
|
| | value val_chars = args.get_kwarg_or_pos("chars", 1);
|
| | if (val_chars->is_undefined()) {
|
| | return mk_val<value_string>(args.get_pos(0)->as_string().strip(true, true));
|
| | } else {
|
| | return mk_val<value_string>(args.get_pos(0)->as_string().strip(true, true, val_chars->as_string().str()));
|
| | }
|
| | }},
|
| | {"rstrip", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_string>();
|
| | value val_chars = args.get_kwarg_or_pos("chars", 1);
|
| | if (val_chars->is_undefined()) {
|
| | return mk_val<value_string>(args.get_pos(0)->as_string().strip(false, true));
|
| | } else {
|
| | return mk_val<value_string>(args.get_pos(0)->as_string().strip(false, true, val_chars->as_string().str()));
|
| | }
|
| | }},
|
| | {"lstrip", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_string>();
|
| | value val_chars = args.get_kwarg_or_pos("chars", 1);
|
| | if (val_chars->is_undefined()) {
|
| | return mk_val<value_string>(args.get_pos(0)->as_string().strip(true, false));
|
| | } else {
|
| | return mk_val<value_string>(args.get_pos(0)->as_string().strip(true, false, val_chars->as_string().str()));
|
| | }
|
| | }},
|
| | {"title", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_string>();
|
| | jinja::string str = args.get_pos(0)->as_string().titlecase();
|
| | return mk_val<value_string>(str);
|
| | }},
|
| | {"capitalize", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_string>();
|
| | jinja::string str = args.get_pos(0)->as_string().capitalize();
|
| | return mk_val<value_string>(str);
|
| | }},
|
| | {"length", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_string>();
|
| | jinja::string str = args.get_pos(0)->as_string();
|
| | return mk_val<value_int>(str.length());
|
| | }},
|
| | {"startswith", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_string, value_string>();
|
| | std::string str = args.get_pos(0)->as_string().str();
|
| | std::string prefix = args.get_pos(1)->as_string().str();
|
| | return mk_val<value_bool>(string_startswith(str, prefix));
|
| | }},
|
| | {"endswith", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_string, value_string>();
|
| | std::string str = args.get_pos(0)->as_string().str();
|
| | std::string suffix = args.get_pos(1)->as_string().str();
|
| | return mk_val<value_bool>(string_endswith(str, suffix));
|
| | }},
|
| | {"split", [](const func_args & args) -> value {
|
| | args.ensure_count(1, 3);
|
| | value val_input = args.get_pos(0);
|
| | if (!is_val<value_string>(val_input)) {
|
| | throw raised_exception("split() first argument must be a string");
|
| | }
|
| | std::string str = val_input->as_string().str();
|
| |
|
| | std::string delim = (args.count() > 1) ? args.get_pos(1)->as_string().str() : " ";
|
| | int64_t maxsplit = (args.count() > 2) ? args.get_pos(2)->as_int() : -1;
|
| | auto result = mk_val<value_array>();
|
| | size_t pos = 0;
|
| | std::string token;
|
| | while ((pos = str.find(delim)) != std::string::npos && maxsplit != 0) {
|
| | token = str.substr(0, pos);
|
| | result->push_back(mk_val<value_string>(token));
|
| | str.erase(0, pos + delim.length());
|
| | --maxsplit;
|
| | }
|
| | auto res = mk_val<value_string>(str);
|
| | res->val_str.mark_input_based_on(args.get_pos(0)->val_str);
|
| | result->push_back(std::move(res));
|
| | return result;
|
| | }},
|
| | {"rsplit", [](const func_args & args) -> value {
|
| | args.ensure_count(1, 3);
|
| | value val_input = args.get_pos(0);
|
| | if (!is_val<value_string>(val_input)) {
|
| | throw raised_exception("rsplit() first argument must be a string");
|
| | }
|
| | std::string str = val_input->as_string().str();
|
| |
|
| | std::string delim = (args.count() > 1) ? args.get_pos(1)->as_string().str() : " ";
|
| | int64_t maxsplit = (args.count() > 2) ? args.get_pos(2)->as_int() : -1;
|
| | auto result = mk_val<value_array>();
|
| | size_t pos = 0;
|
| | std::string token;
|
| | while ((pos = str.rfind(delim)) != std::string::npos && maxsplit != 0) {
|
| | token = str.substr(pos + delim.length());
|
| | result->push_back(mk_val<value_string>(token));
|
| | str.erase(pos);
|
| | --maxsplit;
|
| | }
|
| | auto res = mk_val<value_string>(str);
|
| | res->val_str.mark_input_based_on(args.get_pos(0)->val_str);
|
| | result->push_back(std::move(res));
|
| | result->reverse();
|
| | return result;
|
| | }},
|
| | {"replace", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_string, value_string, value_string, value_int>(true, true, true, false);
|
| | std::string str = args.get_pos(0)->as_string().str();
|
| | std::string old_str = args.get_pos(1)->as_string().str();
|
| | std::string new_str = args.get_pos(2)->as_string().str();
|
| | int64_t count = args.count() > 3 ? args.get_pos(3)->as_int() : -1;
|
| | if (count > 0) {
|
| | throw not_implemented_exception("String replace with count argument not implemented");
|
| | }
|
| | size_t pos = 0;
|
| | while ((pos = str.find(old_str, pos)) != std::string::npos) {
|
| | str.replace(pos, old_str.length(), new_str);
|
| | pos += new_str.length();
|
| | }
|
| | auto res = mk_val<value_string>(str);
|
| | res->val_str.mark_input_based_on(args.get_pos(0)->val_str);
|
| | return res;
|
| | }},
|
| | {"int", [](const func_args & args) -> value {
|
| | value val_input = args.get_pos(0);
|
| | value val_default = args.get_kwarg_or_pos("default", 1);
|
| | value val_base = args.get_kwarg_or_pos("base", 2);
|
| | const int base = val_base->is_undefined() ? 10 : val_base->as_int();
|
| | if (is_val<value_string>(val_input) == false) {
|
| | throw raised_exception("int() first argument must be a string");
|
| | }
|
| | std::string str = val_input->as_string().str();
|
| | try {
|
| | return mk_val<value_int>(std::stoi(str, nullptr, base));
|
| | } catch (...) {
|
| | return mk_val<value_int>(val_default->is_undefined() ? 0 : val_default->as_int());
|
| | }
|
| | }},
|
| | {"float", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_string>();
|
| | value val_default = args.get_kwarg_or_pos("default", 1);
|
| | std::string str = args.get_pos(0)->as_string().str();
|
| | try {
|
| | return mk_val<value_float>(std::stod(str));
|
| | } catch (...) {
|
| | return mk_val<value_float>(val_default->is_undefined() ? 0.0 : val_default->as_float());
|
| | }
|
| | }},
|
| | {"string", [](const func_args & args) -> value {
|
| |
|
| | args.ensure_vals<value_string>();
|
| | return mk_val<value_string>(args.get_pos(0)->as_string());
|
| | }},
|
| | {"default", [](const func_args & args) -> value {
|
| | value input = args.get_pos(0);
|
| | if (!is_val<value_string>(input)) {
|
| | throw raised_exception("default() first argument must be a string");
|
| | }
|
| | value default_val = mk_val<value_string>("");
|
| | if (args.count() > 1 && !args.get_pos(1)->is_undefined()) {
|
| | default_val = args.get_pos(1);
|
| | }
|
| | value boolean_val = args.get_kwarg_or_pos("boolean", 2);
|
| | if (input->is_undefined() || (boolean_val->as_bool() && !input->as_bool())) {
|
| | return default_val;
|
| | } else {
|
| | return input;
|
| | }
|
| | }},
|
| | {"slice", [](const func_args & args) -> value {
|
| | args.ensure_count(1, 4);
|
| | args.ensure_vals<value_string, value_int, value_int, value_int>(true, true, false, false);
|
| |
|
| | auto arg0 = args.get_pos(1);
|
| | auto arg1 = args.get_pos(2, mk_val<value_undefined>());
|
| | auto arg2 = args.get_pos(3, mk_val<value_undefined>());
|
| |
|
| | int64_t start, stop, step;
|
| | if (args.count() == 1) {
|
| | start = 0;
|
| | stop = arg0->as_int();
|
| | step = 1;
|
| | } else if (args.count() == 2) {
|
| | start = arg0->as_int();
|
| | stop = arg1->as_int();
|
| | step = 1;
|
| | } else {
|
| | start = arg0->as_int();
|
| | stop = arg1->as_int();
|
| | step = arg2->as_int();
|
| | }
|
| | if (step == 0) {
|
| | throw raised_exception("slice step cannot be zero");
|
| | }
|
| | auto input = args.get_pos(0);
|
| | auto sliced = slice(input->as_string().str(), start, stop, step);
|
| | auto res = mk_val<value_string>(sliced);
|
| | res->val_str.mark_input_based_on(input->as_string());
|
| | return res;
|
| | }},
|
| | {"safe", [](const func_args & args) -> value {
|
| |
|
| | args.ensure_vals<value_string>();
|
| | return args.get_pos(0);
|
| | }},
|
| | {"tojson", tojson},
|
| | {"indent", [](const func_args &args) -> value {
|
| | args.ensure_count(1, 4);
|
| | value val_input = args.get_pos(0);
|
| | value val_width = args.get_kwarg_or_pos("width", 1);
|
| | const bool first = args.get_kwarg_or_pos("first", 2)->as_bool();
|
| | const bool blank = args.get_kwarg_or_pos("blank", 3)->as_bool();
|
| | if (!is_val<value_string>(val_input)) {
|
| | throw raised_exception("indent() first argument must be a string");
|
| | }
|
| | std::string indent;
|
| | if (is_val<value_int>(val_width)) {
|
| | indent.assign(val_width->as_int(), ' ');
|
| | } else if (is_val<value_string>(val_width)) {
|
| | indent = val_width->as_string().str();
|
| | } else {
|
| | indent = " ";
|
| | }
|
| | std::string indented;
|
| | std::string input = val_input->as_string().str();
|
| | std::istringstream iss = std::istringstream(input);
|
| | std::string line;
|
| | while (std::getline(iss, line)) {
|
| | if (!indented.empty()) {
|
| | indented.push_back('\n');
|
| | }
|
| | if ((indented.empty() ? first : (!line.empty() || blank))) {
|
| | indented += indent;
|
| | }
|
| | indented += line;
|
| | }
|
| | if (!input.empty() && input.back() == '\n') {
|
| | indented.push_back('\n');
|
| | if (blank) {
|
| | indented += indent;
|
| | }
|
| | }
|
| |
|
| | auto res = mk_val<value_string>(indented);
|
| | res->val_str.mark_input_based_on(val_input->as_string());
|
| | return res;
|
| | }},
|
| | {"join", [](const func_args &) -> value {
|
| | throw not_implemented_exception("String join builtin not implemented");
|
| | }},
|
| | };
|
| | return builtins;
|
| | }
|
| |
|
| |
|
| | const func_builtins & value_bool_t::get_builtins() const {
|
| | static const func_builtins builtins = {
|
| | {"default", default_value},
|
| | {"int", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_bool>();
|
| | bool val = args.get_pos(0)->as_bool();
|
| | return mk_val<value_int>(val ? 1 : 0);
|
| | }},
|
| | {"float", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_bool>();
|
| | bool val = args.get_pos(0)->as_bool();
|
| | return mk_val<value_float>(val ? 1.0 : 0.0);
|
| | }},
|
| | {"string", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_bool>();
|
| | bool val = args.get_pos(0)->as_bool();
|
| | return mk_val<value_string>(val ? "True" : "False");
|
| | }},
|
| | {"tojson", tojson},
|
| | };
|
| | return builtins;
|
| | }
|
| |
|
| |
|
| | const func_builtins & value_array_t::get_builtins() const {
|
| | static const func_builtins builtins = {
|
| | {"default", default_value},
|
| | {"list", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_array>();
|
| | const auto & arr = args.get_pos(0)->as_array();
|
| | auto result = mk_val<value_array>();
|
| | for (const auto& v : arr) {
|
| | result->push_back(v);
|
| | }
|
| | return result;
|
| | }},
|
| | {"first", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_array>();
|
| | const auto & arr = args.get_pos(0)->as_array();
|
| | if (arr.empty()) {
|
| | return mk_val<value_undefined>();
|
| | }
|
| | return arr[0];
|
| | }},
|
| | {"last", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_array>();
|
| | const auto & arr = args.get_pos(0)->as_array();
|
| | if (arr.empty()) {
|
| | return mk_val<value_undefined>();
|
| | }
|
| | return arr[arr.size() - 1];
|
| | }},
|
| | {"length", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_array>();
|
| | const auto & arr = args.get_pos(0)->as_array();
|
| | return mk_val<value_int>(static_cast<int64_t>(arr.size()));
|
| | }},
|
| | {"slice", [](const func_args & args) -> value {
|
| | args.ensure_count(1, 4);
|
| | args.ensure_vals<value_array, value_int, value_int, value_int>(true, true, false, false);
|
| |
|
| | auto val = args.get_pos(0);
|
| | auto arg0 = args.get_pos(1);
|
| | auto arg1 = args.get_pos(2, mk_val<value_undefined>());
|
| | auto arg2 = args.get_pos(3, mk_val<value_undefined>());
|
| |
|
| | int64_t start, stop, step;
|
| | if (args.count() == 1) {
|
| | start = 0;
|
| | stop = arg0->as_int();
|
| | step = 1;
|
| | } else if (args.count() == 2) {
|
| | start = arg0->as_int();
|
| | stop = arg1->as_int();
|
| | step = 1;
|
| | } else {
|
| | start = arg0->as_int();
|
| | stop = arg1->as_int();
|
| | step = arg2->as_int();
|
| | }
|
| | if (step == 0) {
|
| | throw raised_exception("slice step cannot be zero");
|
| | }
|
| | auto arr = slice(val->as_array(), start, stop, step);
|
| | return is_val<value_tuple>(val) ? mk_val<value_tuple>(std::move(arr)) : mk_val<value_array>(std::move(arr));
|
| | }},
|
| | {"selectattr", selectattr<false>},
|
| | {"select", selectattr<false>},
|
| | {"rejectattr", selectattr<true>},
|
| | {"reject", selectattr<true>},
|
| | {"join", [](const func_args & args) -> value {
|
| | args.ensure_count(1, 3);
|
| | if (!is_val<value_array>(args.get_pos(0))) {
|
| | throw raised_exception("join() first argument must be an array");
|
| | }
|
| | value val_delim = args.get_kwarg_or_pos("d", 1);
|
| | value attribute = args.get_kwarg_or_pos("attribute", 2);
|
| | const auto & arr = args.get_pos(0)->as_array();
|
| | const bool attr_is_int = is_val<value_int>(attribute);
|
| | if (!attribute->is_undefined() && !is_val<value_string>(attribute) && !attr_is_int) {
|
| | throw raised_exception("join() attribute must be string or integer");
|
| | }
|
| | const int64_t attr_int = attr_is_int ? attribute->as_int() : 0;
|
| | const std::string delim = val_delim->is_undefined() ? "" : val_delim->as_string().str();
|
| | std::string result;
|
| | for (size_t i = 0; i < arr.size(); ++i) {
|
| | value val_arr = arr[i];
|
| | if (!attribute->is_undefined()) {
|
| | if (attr_is_int && is_val<value_array>(val_arr)) {
|
| | val_arr = val_arr->at(attr_int);
|
| | } else if (!attr_is_int && is_val<value_object>(val_arr)) {
|
| | val_arr = val_arr->at(attribute);
|
| | }
|
| | }
|
| | if (!is_val<value_string>(val_arr) && !is_val<value_int>(val_arr) && !is_val<value_float>(val_arr)) {
|
| | throw raised_exception("join() can only join arrays of strings or numerics");
|
| | }
|
| | result += val_arr->as_string().str();
|
| | if (i < arr.size() - 1) {
|
| | result += delim;
|
| | }
|
| | }
|
| | return mk_val<value_string>(result);
|
| | }},
|
| | {"string", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_array>();
|
| | if (args.ctx.is_get_stats) {
|
| |
|
| | auto val_input = args.get_pos(0);
|
| | value_t::stats_t::mark_used(const_cast<value&>(val_input), true);
|
| | }
|
| | return mk_val<value_string>(args.get_pos(0)->as_string());
|
| | }},
|
| | {"tojson", tojson},
|
| | {"map", [](const func_args & args) -> value {
|
| | args.ensure_count(2);
|
| | if (!is_val<value_array>(args.get_pos(0))) {
|
| | throw raised_exception("map: first argument must be an array");
|
| | }
|
| | if (!is_val<value_kwarg>(args.get_args().at(1))) {
|
| | throw not_implemented_exception("map: filter-mapping not implemented");
|
| | }
|
| | value val = args.get_pos(0);
|
| | value attribute = args.get_kwarg_or_pos("attribute", 1);
|
| | const bool attr_is_int = is_val<value_int>(attribute);
|
| | if (!is_val<value_string>(attribute) && !attr_is_int) {
|
| | throw raised_exception("map: attribute must be string or integer");
|
| | }
|
| | const int64_t attr_int = attr_is_int ? attribute->as_int() : 0;
|
| | value default_val = args.get_kwarg("default", mk_val<value_undefined>());
|
| | auto out = mk_val<value_array>();
|
| | auto arr = val->as_array();
|
| | for (const auto & item : arr) {
|
| | value attr_val;
|
| | if (attr_is_int) {
|
| | attr_val = is_val<value_array>(item) ? item->at(attr_int, default_val) : default_val;
|
| | } else {
|
| | attr_val = is_val<value_object>(item) ? item->at(attribute, default_val) : default_val;
|
| | }
|
| | out->push_back(attr_val);
|
| | }
|
| | return is_val<value_tuple>(val) ? mk_val<value_tuple>(std::move(out->as_array())) : out;
|
| | }},
|
| | {"append", [](const func_args & args) -> value {
|
| | args.ensure_count(2);
|
| | if (!is_val<value_array>(args.get_pos(0))) {
|
| | throw raised_exception("append: first argument must be an array");
|
| | }
|
| | const value_array_t * arr = cast_val<value_array>(args.get_pos(0));
|
| |
|
| | value_array_t * arr_editable = const_cast<value_array_t *>(arr);
|
| | arr_editable->push_back(args.get_pos(1));
|
| | return args.get_pos(0);
|
| | }},
|
| | {"pop", [](const func_args & args) -> value {
|
| | args.ensure_count(1, 2);
|
| | args.ensure_vals<value_array, value_int>(true, false);
|
| | int64_t index = args.count() == 2 ? args.get_pos(1)->as_int() : -1;
|
| | const value_array_t * arr = cast_val<value_array>(args.get_pos(0));
|
| |
|
| | value_array_t * arr_editable = const_cast<value_array_t *>(arr);
|
| | return arr_editable->pop_at(index);
|
| | }},
|
| | {"sort", [](const func_args & args) -> value {
|
| | args.ensure_count(1, 4);
|
| | if (!is_val<value_array>(args.get_pos(0))) {
|
| | throw raised_exception("sort: first argument must be an array");
|
| | }
|
| | value val = args.get_pos(0);
|
| | value val_reverse = args.get_kwarg_or_pos("reverse", 1);
|
| | value val_case = args.get_kwarg_or_pos("case_sensitive", 2);
|
| | value attribute = args.get_kwarg_or_pos("attribute", 3);
|
| |
|
| |
|
| | const bool reverse = val_reverse->as_bool();
|
| | const bool attr_is_int = is_val<value_int>(attribute);
|
| | const int64_t attr_int = attr_is_int ? attribute->as_int() : 0;
|
| | std::vector<value> arr = val->as_array();
|
| | std::sort(arr.begin(), arr.end(),[&](const value & a, const value & b) {
|
| | value val_a = a;
|
| | value val_b = b;
|
| | if (!attribute->is_undefined()) {
|
| | if (attr_is_int && is_val<value_array>(a) && is_val<value_array>(b)) {
|
| | val_a = a->at(attr_int);
|
| | val_b = b->at(attr_int);
|
| | } else if (!attr_is_int && is_val<value_object>(a) && is_val<value_object>(b)) {
|
| | val_a = a->at(attribute);
|
| | val_b = b->at(attribute);
|
| | } else {
|
| | throw raised_exception("sort: unsupported object attribute comparison between " + a->type() + " and " + b->type());
|
| | }
|
| | }
|
| | return value_compare(val_a, val_b, reverse ? value_compare_op::gt : value_compare_op::lt);
|
| | });
|
| | return is_val<value_tuple>(val) ? mk_val<value_tuple>(std::move(arr)) : mk_val<value_array>(std::move(arr));
|
| | }},
|
| | {"reverse", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_array>();
|
| | value val = args.get_pos(0);
|
| | std::vector<value> arr = val->as_array();
|
| | std::reverse(arr.begin(), arr.end());
|
| | return is_val<value_tuple>(val) ? mk_val<value_tuple>(std::move(arr)) : mk_val<value_array>(std::move(arr));
|
| | }},
|
| | {"unique", [](const func_args &) -> value {
|
| | throw not_implemented_exception("Array unique builtin not implemented");
|
| | }},
|
| | };
|
| | return builtins;
|
| | }
|
| |
|
| |
|
| | const func_builtins & value_object_t::get_builtins() const {
|
| | if (!has_builtins) {
|
| | static const func_builtins no_builtins = {};
|
| | return no_builtins;
|
| | }
|
| |
|
| | static const func_builtins builtins = {
|
| |
|
| | {"get", [](const func_args & args) -> value {
|
| | args.ensure_count(2, 3);
|
| | if (!is_val<value_object>(args.get_pos(0))) {
|
| | throw raised_exception("get: first argument must be an object");
|
| | }
|
| | if (!is_val<value_string>(args.get_pos(1))) {
|
| | throw raised_exception("get: second argument must be a string (key)");
|
| | }
|
| | value default_val = mk_val<value_none>();
|
| | if (args.count() == 3) {
|
| | default_val = args.get_pos(2);
|
| | }
|
| | const value obj = args.get_pos(0);
|
| | const value key = args.get_pos(1);
|
| | return obj->at(key, default_val);
|
| | }},
|
| | {"keys", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_object>();
|
| | const auto & obj = args.get_pos(0)->as_ordered_object();
|
| | auto result = mk_val<value_array>();
|
| | for (const auto & pair : obj) {
|
| | result->push_back(pair.first);
|
| | }
|
| | return result;
|
| | }},
|
| | {"values", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_object>();
|
| | const auto & obj = args.get_pos(0)->as_ordered_object();
|
| | auto result = mk_val<value_array>();
|
| | for (const auto & pair : obj) {
|
| | result->push_back(pair.second);
|
| | }
|
| | return result;
|
| | }},
|
| | {"items", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_object>();
|
| | const auto & obj = args.get_pos(0)->as_ordered_object();
|
| | auto result = mk_val<value_array>();
|
| | for (const auto & pair : obj) {
|
| | auto item = mk_val<value_tuple>(pair);
|
| | result->push_back(std::move(item));
|
| | }
|
| | return result;
|
| | }},
|
| | {"tojson", tojson},
|
| | {"string", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_object>();
|
| | if (args.ctx.is_get_stats) {
|
| |
|
| | auto val_input = args.get_pos(0);
|
| | value_t::stats_t::mark_used(const_cast<value&>(val_input), true);
|
| | }
|
| | return mk_val<value_string>(args.get_pos(0)->as_string());
|
| | }},
|
| | {"length", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_object>();
|
| | const auto & obj = args.get_pos(0)->as_ordered_object();
|
| | return mk_val<value_int>(static_cast<int64_t>(obj.size()));
|
| | }},
|
| | {"tojson", [](const func_args & args) -> value {
|
| | args.ensure_vals<value_object>();
|
| |
|
| | return global_builtins().at("tojson")(args);
|
| | }},
|
| | {"dictsort", [](const func_args & args) -> value {
|
| | value val_input = args.get_pos(0);
|
| | value val_case = args.get_kwarg_or_pos("case_sensitive", 1);
|
| | value val_by = args.get_kwarg_or_pos("by", 2);
|
| | value val_reverse = args.get_kwarg_or_pos("reverse", 3);
|
| |
|
| |
|
| | const bool reverse = val_reverse->as_bool();
|
| | const bool by_value = is_val<value_string>(val_by) && val_by->as_string().str() == "value" ? true : false;
|
| | auto result = mk_val<value_object>(val_input);
|
| | std::sort(result->val_obj.begin(), result->val_obj.end(), [&](const auto & a, const auto & b) {
|
| | if (by_value) {
|
| | return value_compare(a.second, b.second, reverse ? value_compare_op::gt : value_compare_op::lt);
|
| | } else {
|
| | return value_compare(a.first, b.first, reverse ? value_compare_op::gt : value_compare_op::lt);
|
| | }
|
| | });
|
| | return result;
|
| | }},
|
| | {"join", [](const func_args &) -> value {
|
| | throw not_implemented_exception("object join not implemented");
|
| | }},
|
| | };
|
| | return builtins;
|
| | }
|
| |
|
| | const func_builtins & value_none_t::get_builtins() const {
|
| | static const func_builtins builtins = {
|
| | {"default", default_value},
|
| | {"tojson", tojson},
|
| | {"string", [](const func_args &) -> value {
|
| | return mk_val<value_string>("None");
|
| | }},
|
| | {"safe", [](const func_args &) -> value {
|
| | return mk_val<value_string>("None");
|
| | }},
|
| | {"strip", [](const func_args &) -> value {
|
| | return mk_val<value_string>("None");
|
| | }},
|
| | {"items", empty_value_fn<value_array>},
|
| | {"map", empty_value_fn<value_array>},
|
| | {"reject", empty_value_fn<value_array>},
|
| | {"rejectattr", empty_value_fn<value_array>},
|
| | {"select", empty_value_fn<value_array>},
|
| | {"selectattr", empty_value_fn<value_array>},
|
| | {"unique", empty_value_fn<value_array>},
|
| | };
|
| | return builtins;
|
| | }
|
| |
|
| |
|
| | const func_builtins & value_undefined_t::get_builtins() const {
|
| | static const func_builtins builtins = {
|
| | {"default", default_value},
|
| | {"capitalize", empty_value_fn<value_string>},
|
| | {"first", empty_value_fn<value_undefined>},
|
| | {"items", empty_value_fn<value_array>},
|
| | {"join", empty_value_fn<value_string>},
|
| | {"last", empty_value_fn<value_undefined>},
|
| | {"length", empty_value_fn<value_int>},
|
| | {"list", empty_value_fn<value_array>},
|
| | {"lower", empty_value_fn<value_string>},
|
| | {"map", empty_value_fn<value_array>},
|
| | {"max", empty_value_fn<value_undefined>},
|
| | {"min", empty_value_fn<value_undefined>},
|
| | {"reject", empty_value_fn<value_array>},
|
| | {"rejectattr", empty_value_fn<value_array>},
|
| | {"replace", empty_value_fn<value_string>},
|
| | {"reverse", empty_value_fn<value_array>},
|
| | {"safe", empty_value_fn<value_string>},
|
| | {"select", empty_value_fn<value_array>},
|
| | {"selectattr", empty_value_fn<value_array>},
|
| | {"sort", empty_value_fn<value_array>},
|
| | {"string", empty_value_fn<value_string>},
|
| | {"strip", empty_value_fn<value_string>},
|
| | {"sum", empty_value_fn<value_int>},
|
| | {"title", empty_value_fn<value_string>},
|
| | {"truncate", empty_value_fn<value_string>},
|
| | {"unique", empty_value_fn<value_array>},
|
| | {"upper", empty_value_fn<value_string>},
|
| | {"wordcount", empty_value_fn<value_int>},
|
| | };
|
| | return builtins;
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | static value from_json(const nlohmann::ordered_json & j, bool mark_input) {
|
| | if (j.is_null()) {
|
| | return mk_val<value_none>();
|
| | } else if (j.is_boolean()) {
|
| | return mk_val<value_bool>(j.get<bool>());
|
| | } else if (j.is_number_integer()) {
|
| | return mk_val<value_int>(j.get<int64_t>());
|
| | } else if (j.is_number_float()) {
|
| | return mk_val<value_float>(j.get<double>());
|
| | } else if (j.is_string()) {
|
| | auto str = mk_val<value_string>(j.get<std::string>());
|
| | if (mark_input) {
|
| | str->mark_input();
|
| | }
|
| | return str;
|
| | } else if (j.is_array()) {
|
| | auto arr = mk_val<value_array>();
|
| | for (const auto & item : j) {
|
| | arr->push_back(from_json(item, mark_input));
|
| | }
|
| | return arr;
|
| | } else if (j.is_object()) {
|
| | auto obj = mk_val<value_object>();
|
| | for (auto it = j.begin(); it != j.end(); ++it) {
|
| | obj->insert(it.key(), from_json(it.value(), mark_input));
|
| | }
|
| | return obj;
|
| | } else {
|
| | throw std::runtime_error("Unsupported JSON value type");
|
| | }
|
| | }
|
| |
|
| |
|
| | bool value_compare(const value & a, const value & b, value_compare_op op) {
|
| | auto cmp = [&]() {
|
| |
|
| | if ((is_val<value_int>(a) || is_val<value_float>(a)) &&
|
| | (is_val<value_int>(b) || is_val<value_float>(b))){
|
| | try {
|
| | if (op == value_compare_op::eq) {
|
| | return a->as_float() == b->as_float();
|
| | } else if (op == value_compare_op::ge) {
|
| | return a->as_float() >= b->as_float();
|
| | } else if (op == value_compare_op::gt) {
|
| | return a->as_float() > b->as_float();
|
| | } else if (op == value_compare_op::lt) {
|
| | return a->as_float() < b->as_float();
|
| | } else if (op == value_compare_op::ne) {
|
| | return a->as_float() != b->as_float();
|
| | } else {
|
| | throw std::runtime_error("Unsupported comparison operator for numeric types");
|
| | }
|
| | } catch (...) {}
|
| | }
|
| |
|
| |
|
| | if ((is_val<value_string>(b) && (is_val<value_int>(a) || is_val<value_float>(a))) ||
|
| | (is_val<value_string>(a) && (is_val<value_int>(b) || is_val<value_float>(b))) ||
|
| | (is_val<value_string>(a) && is_val<value_string>(b))) {
|
| | try {
|
| | if (op == value_compare_op::eq) {
|
| | return a->as_string().str() == b->as_string().str();
|
| | } else if (op == value_compare_op::ge) {
|
| | return a->as_string().str() >= b->as_string().str();
|
| | } else if (op == value_compare_op::gt) {
|
| | return a->as_string().str() > b->as_string().str();
|
| | } else if (op == value_compare_op::lt) {
|
| | return a->as_string().str() < b->as_string().str();
|
| | } else if (op == value_compare_op::ne) {
|
| | return a->as_string().str() != b->as_string().str();
|
| | } else {
|
| | throw std::runtime_error("Unsupported comparison operator for string/number types");
|
| | }
|
| | } catch (...) {}
|
| | }
|
| |
|
| | if (is_val<value_bool>(a) && is_val<value_bool>(b)) {
|
| | if (op == value_compare_op::eq) {
|
| | return a->as_bool() == b->as_bool();
|
| | } else if (op == value_compare_op::ne) {
|
| | return a->as_bool() != b->as_bool();
|
| | } else {
|
| | throw std::runtime_error("Unsupported comparison operator for bool type");
|
| | }
|
| | }
|
| |
|
| | if (a->type() != b->type()) {
|
| | return false;
|
| | }
|
| | return false;
|
| | };
|
| | auto result = cmp();
|
| | JJ_DEBUG("Comparing types: %s and %s result=%d", a->type().c_str(), b->type().c_str(), result);
|
| | return result;
|
| | }
|
| |
|
| | template<>
|
| | void global_from_json(context & ctx, const nlohmann::ordered_json & json_obj, bool mark_input) {
|
| |
|
| | if (json_obj.is_null() || !json_obj.is_object()) {
|
| | throw std::runtime_error("global_from_json: input JSON value must be an object");
|
| | }
|
| | for (auto it = json_obj.begin(); it != json_obj.end(); ++it) {
|
| | JJ_DEBUG("global_from_json: setting key '%s'", it.key().c_str());
|
| | ctx.set_val(it.key(), from_json(it.value(), mark_input));
|
| | }
|
| | }
|
| |
|
| |
|
| |
|
| | static 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) {
|
| | auto indent_str = [indent, curr_lvl]() -> std::string {
|
| | return (indent > 0) ? std::string(curr_lvl * indent, ' ') : "";
|
| | };
|
| | auto newline = [indent]() -> std::string {
|
| | return (indent >= 0) ? "\n" : "";
|
| | };
|
| |
|
| | if (is_val<value_none>(val) || val->is_undefined()) {
|
| | oss << "null";
|
| | } else if (is_val<value_bool>(val)) {
|
| | oss << (val->as_bool() ? "true" : "false");
|
| | } else if (is_val<value_int>(val)) {
|
| | oss << val->as_int();
|
| | } else if (is_val<value_float>(val)) {
|
| | oss << val->as_float();
|
| | } else if (is_val<value_string>(val)) {
|
| | oss << "\"";
|
| | for (char c : val->as_string().str()) {
|
| | switch (c) {
|
| | case '"': oss << "\\\""; break;
|
| | case '\\': oss << "\\\\"; break;
|
| | case '\b': oss << "\\b"; break;
|
| | case '\f': oss << "\\f"; break;
|
| | case '\n': oss << "\\n"; break;
|
| | case '\r': oss << "\\r"; break;
|
| | case '\t': oss << "\\t"; break;
|
| | default:
|
| | if (static_cast<unsigned char>(c) < 0x20) {
|
| | char buf[7];
|
| | snprintf(buf, sizeof(buf), "\\u%04x", static_cast<unsigned char>(c));
|
| | oss << buf;
|
| | } else {
|
| | oss << c;
|
| | }
|
| | }
|
| | }
|
| | oss << "\"";
|
| | } else if (is_val<value_array>(val)) {
|
| | const auto & arr = val->as_array();
|
| | oss << "[";
|
| | if (!arr.empty()) {
|
| | oss << newline();
|
| | for (size_t i = 0; i < arr.size(); ++i) {
|
| | oss << indent_str() << (indent > 0 ? std::string(indent, ' ') : "");
|
| | value_to_json_internal(oss, arr[i], curr_lvl + 1, indent, item_sep, key_sep);
|
| | if (i < arr.size() - 1) {
|
| | oss << item_sep;
|
| | }
|
| | oss << newline();
|
| | }
|
| | oss << indent_str();
|
| | }
|
| | oss << "]";
|
| | } else if (is_val<value_object>(val)) {
|
| | const auto & obj = val->as_ordered_object();
|
| | oss << "{";
|
| | if (!obj.empty()) {
|
| | oss << newline();
|
| | size_t i = 0;
|
| | for (const auto & pair : obj) {
|
| | oss << indent_str() << (indent > 0 ? std::string(indent, ' ') : "");
|
| | value_to_json_internal(oss, mk_val<value_string>(pair.first->as_string().str()), curr_lvl + 1, indent, item_sep, key_sep);
|
| | oss << key_sep;
|
| | value_to_json_internal(oss, pair.second, curr_lvl + 1, indent, item_sep, key_sep);
|
| | if (i < obj.size() - 1) {
|
| | oss << item_sep;
|
| | }
|
| | oss << newline();
|
| | ++i;
|
| | }
|
| | oss << indent_str();
|
| | }
|
| | oss << "}";
|
| | } else {
|
| | oss << "null";
|
| | }
|
| | }
|
| |
|
| | std::string value_to_json(const value & val, int indent, const std::string_view item_sep, const std::string_view key_sep) {
|
| | std::ostringstream oss;
|
| | value_to_json_internal(oss, val, 0, indent, item_sep, key_sep);
|
| | JJ_DEBUG("value_to_json: result=%s", oss.str().c_str());
|
| | return oss.str();
|
| | }
|
| |
|
| |
|
| | std::string value_to_string_repr(const value & val) {
|
| | if (is_val<value_string>(val)) {
|
| | const std::string val_str = val->as_string().str();
|
| |
|
| | if (val_str.find('\'') != std::string::npos) {
|
| | return value_to_json(val);
|
| | } else {
|
| | return "'" + val_str + "'";
|
| | }
|
| | } else {
|
| | return val->as_repr();
|
| | }
|
| | }
|
| |
|
| |
|
| | void value_t::stats_t::mark_used(value & val, bool deep) {
|
| | val->stats.used = true;
|
| | if (deep) {
|
| | if (is_val<value_array>(val)) {
|
| | for (auto & item : val->val_arr) {
|
| | mark_used(item, deep);
|
| | }
|
| | } else if (is_val<value_object>(val)) {
|
| | for (auto & pair : val->val_obj) {
|
| | mark_used(pair.first, deep);
|
| | mark_used(pair.second, deep);
|
| | }
|
| | }
|
| | }
|
| | }
|
| |
|
| | }
|
| |
|