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