| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202 |
- #include "runtime.h"
- #include "value.h"
- // for converting from JSON to jinja values
- #include <nlohmann/json.hpp>
- #include <string>
- #include <cctype>
- #include <vector>
- #include <optional>
- #include <algorithm>
- #define FILENAME "jinja-value"
- namespace jinja {
- // func_args method implementations
- 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;
- }
- /**
- * Function that mimics Python's array slicing.
- */
- 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 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<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 (is_val<value_int>(val_indent)) {
- indent = static_cast<int>(val_indent->as_int());
- }
- if (val_ascii->as_bool()) { // undefined == false
- throw not_implemented_exception("tojson ensure_ascii=true not implemented");
- }
- if (val_sort->as_bool()) { // undefined == false
- 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 attr_name = args.get_pos(1)->as_string().str();
- auto out = mk_val<value_array>();
- value val_default = mk_val<value_undefined>();
- if (args.count() == 2) {
- // example: array | selectattr("active")
- 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(attr_name, 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) {
- // example: array | selectattr("equalto", "text")
- // translated to: test_is_equalto(item, "text")
- 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); // current object
- test_args.push_back(test_val); // extra argument
- 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) {
- // example: array | selectattr("status", "equalto", "active")
- // translated to: test_is_equalto(item.status, "active")
- 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(attr_name, val_default);
- func_args test_args(args.ctx);
- test_args.push_back(attr_val); // attribute value
- test_args.push_back(extra_arg); // extra argument
- 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(); // undefined == false
- 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();
- // get current time
- // TODO: make sure this is the same behavior as Python's strftime
- 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},
- // tests
- {"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>},
- {"test_is_sequence", test_type_fn<value_array, value_string>},
- {"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_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 {
- // Check if an object points to the same memory address as another object
- (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();
- // FIXME: Support non-specified delimiter (split on consecutive (no leading or trailing) whitespace)
- 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();
- // FIXME: Support non-specified delimiter (split on consecutive (no leading or trailing) whitespace)
- 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 {
- // no-op
- 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); // undefined == false
- 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 {
- // no-op for now
- args.ensure_vals<value_string>();
- return args.get_pos(0);
- }},
- {"tojson", tojson},
- {"indent", [](const func_args &) -> value {
- throw not_implemented_exception("String indent builtin not implemented");
- }},
- {"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");
- }},
- };
- 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 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(args.get_pos(0)->as_array(), start, stop, step);
- auto res = mk_val<value_array>();
- res->val_arr = std::move(arr);
- return res;
- }},
- {"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 val_attribute = args.get_kwarg_or_pos("attribute", 2);
- if (!val_attribute->is_undefined()) {
- throw not_implemented_exception("array attribute join not implemented");
- }
- const auto & arr = args.get_pos(0)->as_array();
- std::string delim = is_val<value_string>(val_delim) ? val_delim->as_string().str() : "";
- std::string result;
- for (size_t i = 0; i < arr.size(); ++i) {
- if (!is_val<value_string>(arr[i]) && !is_val<value_int>(arr[i]) && !is_val<value_float>(arr[i])) {
- throw raised_exception("join() can only join arrays of strings or numerics");
- }
- result += arr[i]->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>();
- auto str = mk_val<value_string>();
- gather_string_parts_recursive(args.get_pos(0), str);
- return str;
- }},
- {"tojson", tojson},
- {"map", [](const func_args & args) -> value {
- args.ensure_count(2, 3);
- if (!is_val<value_array>(args.get_pos(0))) {
- throw raised_exception("map: first argument must be an array");
- }
- value attribute = args.get_kwarg_or_pos("attribute", 1);
- if (is_val<value_int>(attribute)) {
- throw not_implemented_exception("map: integer attribute not implemented");
- }
- if (!is_val<value_string>(attribute)) {
- throw raised_exception("map: attribute must be string or integer");
- }
- std::string attr_name = attribute->as_string().str();
- value default_val = args.get_kwarg("default", mk_val<value_undefined>());
- auto out = mk_val<value_array>();
- auto arr = args.get_pos(0)->as_array();
- for (const auto & item : arr) {
- if (!is_val<value_object>(item)) {
- throw raised_exception("map: item is not an object");
- }
- value attr_val = item->at(attr_name, default_val);
- out->push_back(attr_val);
- }
- return 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));
- // need to use const_cast here to modify the array
- 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));
- // need to use const_cast here to modify the array
- 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, 3);
- if (!is_val<value_array>(args.get_pos(0))) {
- throw raised_exception("sort: first argument must be an array");
- }
- bool reverse = args.get_kwarg("reverse", mk_val<value_undefined>())->as_bool();
- value attribute = args.get_kwarg("attribute", mk_val<value_undefined>());
- std::string attr = attribute->is_undefined() ? "" : attribute->as_string().str();
- std::vector<value> arr = cast_val<value_array>(args.get_pos(0))->as_array(); // copy
- 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 (!is_val<value_object>(a) || !is_val<value_object>(b)) {
- throw raised_exception("sort: items are not objects");
- }
- val_a = attr.empty() ? a : a->at(attr);
- val_b = attr.empty() ? b : b->at(attr);
- }
- if (reverse) {
- return value_compare(val_a, val_b, value_compare_op::gt);
- } else {
- return !value_compare(val_a, val_b, value_compare_op::gt);
- }
- });
- return mk_val<value_array>(arr);
- }},
- {"reverse", [](const func_args & args) -> value {
- args.ensure_vals<value_array>();
- std::vector<value> arr = cast_val<value_array>(args.get_pos(0))->as_array(); // copy
- std::reverse(arr.begin(), arr.end());
- return mk_val<value_array>(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 {
- static const func_builtins builtins = {
- // {"default", default_value}, // cause issue with gpt-oss
- {"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 auto & obj = args.get_pos(0)->as_object();
- std::string key = args.get_pos(1)->as_string().str();
- auto it = obj.find(key);
- if (it != obj.end()) {
- return it->second;
- } else {
- return default_val;
- }
- }},
- {"keys", [](const func_args & args) -> value {
- args.ensure_vals<value_object>();
- const auto & obj = args.get_pos(0)->as_object();
- auto result = mk_val<value_array>();
- for (const auto & pair : obj) {
- result->push_back(mk_val<value_string>(pair.first));
- }
- return result;
- }},
- {"values", [](const func_args & args) -> value {
- args.ensure_vals<value_object>();
- const auto & obj = args.get_pos(0)->as_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_object();
- auto result = mk_val<value_array>();
- for (const auto & pair : obj) {
- auto item = mk_val<value_array>();
- item->push_back(mk_val<value_string>(pair.first));
- item->push_back(pair.second);
- result->push_back(std::move(item));
- }
- return result;
- }},
- {"tojson", tojson},
- {"string", tojson},
- {"length", [](const func_args & args) -> value {
- args.ensure_vals<value_object>();
- const auto & obj = args.get_pos(0)->as_object();
- return mk_val<value_int>(static_cast<int64_t>(obj.size()));
- }},
- {"tojson", [](const func_args & args) -> value {
- args.ensure_vals<value_object>();
- // use global to_json
- 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);
- // FIXME: sorting is case sensitive
- //const bool case_sensitive = val_case->as_bool(); // undefined == false
- const bool reverse = val_reverse->as_bool(); // undefined == false
- if (!val_by->is_undefined()) {
- throw not_implemented_exception("dictsort by key not implemented");
- }
- if (reverse) {
- throw not_implemented_exception("dictsort reverse not implemented");
- }
- value_t::map obj = val_input->val_obj; // copy
- std::sort(obj.ordered.begin(), obj.ordered.end(), [&](const auto & a, const auto & b) {
- return a.first < b.first;
- });
- auto result = mk_val<value_object>();
- result->val_obj = std::move(obj);
- 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},
- };
- return builtins;
- }
- const func_builtins & value_undefined_t::get_builtins() const {
- static const func_builtins builtins = {
- {"default", default_value},
- {"tojson", [](const func_args & args) -> value {
- args.ensure_vals<value_undefined>();
- return mk_val<value_string>("null");
- }},
- };
- 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");
- }
- }
- // compare operator for value_t
- bool value_compare(const value & a, const value & b, value_compare_op op) {
- auto cmp = [&]() {
- // compare numeric types
- 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 (...) {}
- }
- // compare string and number
- // TODO: not sure if this is the right behavior
- 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 (...) {}
- }
- // compare boolean simple
- 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");
- }
- }
- // compare by 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) {
- // printf("global_from_json: %s\n" , json_obj.dump(2).c_str());
- 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->val_obj.ordered; // IMPORTANT: need to keep exact order
- oss << "{";
- if (!obj.empty()) {
- oss << newline();
- size_t i = 0;
- for (const auto & pair : obj) {
- oss << indent_str() << (indent > 0 ? std::string(indent, ' ') : "");
- oss << "\"" << pair.first << "\"" << 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();
- }
- } // namespace jinja
|