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