char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
ae9afff8d
char/aliceai-t5-moe
1#include "lexer.h" 2#include "runtime.h" 3#include "value.h" 4#include "utils.h" 5 6#include <string> 7#include <vector> 8#include <memory> 9#include <cmath> 10 11#define FILENAME "jinja-runtime" 12 13bool g_jinja_debug = false; 14 15namespace jinja { 16 17void enable_debug (bool enable ) { 18g_jinja_debug = enable ; 19} 20 21static value_string exec_statements (const statements & stmts ,context & ctx ) { 22auto result = mk_val < value_array > (); 23for (const auto & stmt :stmts ) { 24JJ_DEBUG ("Executing statement of type %s" ,stmt -> type ().c_str ()); 25result -> push_back (stmt -> execute (ctx )); 26 } 27// convert to string parts 28value_string str = mk_val < value_string > (); 29gather_string_parts_recursive (result ,str ); 30return str ; 31} 32 33static std::string get_line_col (const std::string & source ,size_t pos ) { 34size_t line = 1 ; 35size_t col = 1 ; 36for (size_t i = 0 ;i < pos && i < source .size ();i ++ ) { 37if (source [i ]== '\n' ) { 38line ++ ; 39col = 1 ; 40 }else { 41col ++ ; 42 } 43 } 44return "line " + std::to_string (line )+ ", column " + std::to_string (col ); 45} 46 47static void ensure_key_type_allowed (const value & val ) { 48if (!val -> is_hashable ()) { 49throw std::runtime_error ("Type: " + val -> type ()+ " is not allowed as object key" ); 50 } 51} 52 53// execute with error handling 54value statement::execute (context & ctx ) { 55try { 56return execute_impl (ctx ); 57 }catch (const continue_statement::signal & /* ex */ ) { 58throw ; 59 }catch (const break_statement::signal & /* ex */ ) { 60throw ; 61 }catch (const rethrown_exception & /* ex */ ) { 62throw ; 63 }catch (const not_implemented_exception & /* ex */ ) { 64throw ; 65 }catch (const std::exception & e ) { 66const std::string & source = * ctx .src ; 67if (source .empty ()) { 68 std::ostringstream oss ; 69oss <<"\nError executing " <<type () <<" at position " <<pos <<": " <<e .what (); 70throw rethrown_exception (oss .str ()); 71 }else { 72 std::ostringstream oss ; 73oss <<"\n------------\n" ; 74oss <<"While executing " <<type () <<" at " <<get_line_col (source ,pos ) <<" in source:\n" ; 75oss <<peak_source (source ,pos ) <<"\n" ; 76oss <<"Error: " <<e .what (); 77// throw as another exception to avoid repeated formatting 78throw rethrown_exception (oss .str ()); 79 } 80 } 81} 82 83value identifier::execute_impl (context & ctx ) { 84auto it = ctx .get_val (val ); 85auto builtins = global_builtins (); 86if (!it -> is_undefined ()) { 87if (ctx .is_get_stats ) { 88 value_t::stats_t::mark_used (it ); 89 } 90JJ_DEBUG ("Identifier '%s' found, type = %s" ,val .c_str (),it -> type ().c_str ()); 91return it ; 92 }else if (builtins .find (val )!= builtins .end ()) { 93JJ_DEBUG ("Identifier '%s' found in builtins" ,val .c_str ()); 94return mk_val < value_func > (val ,builtins .at (val )); 95 }else { 96JJ_DEBUG ("Identifier '%s' not found, returning undefined" ,val .c_str ()); 97return mk_val < value_undefined > (val ); 98 } 99} 100 101value object_literal::execute_impl (context & ctx ) { 102auto obj = mk_val < value_object > (); 103for (const auto & pair :val ) { 104value key = pair .first -> execute (ctx ); 105value val = pair .second -> execute (ctx ); 106JJ_DEBUG ("Object literal: setting key '%s' with value type %s" ,key -> as_string ().str ().c_str (),val -> type ().c_str ()); 107obj -> insert (key ,val ); 108 } 109return obj ; 110} 111 112value binary_expression::execute_impl (context & ctx ) { 113value left_val = left -> execute (ctx ); 114 115// Logical operators 116if (op .value == "and" ) { 117JJ_DEBUG ("Executing logical test: %s AND %s" ,left -> type ().c_str (),right -> type ().c_str ()); 118return left_val -> as_bool () ?right -> execute (ctx ) : std::move (left_val ); 119 }else if (op .value == "or" ) { 120JJ_DEBUG ("Executing logical test: %s OR %s" ,left -> type ().c_str (),right -> type ().c_str ()); 121return left_val -> as_bool () ? std::move (left_val ) :right -> execute (ctx ); 122 } 123 124// Equality operators 125value right_val = right -> execute (ctx ); 126JJ_DEBUG ("Executing binary expression %s '%s' %s" ,left_val -> type ().c_str (),op .value .c_str (),right_val -> type ().c_str ()); 127if (op .value == "==" ) { 128return mk_val < value_bool > (* left_val == * right_val ); 129 }else if (op .value == "!=" ) { 130return mk_val < value_bool > (!(* left_val == * right_val )); 131 } 132 133auto workaround_concat_null_with_str = [& ](value & res )-> bool { 134bool is_left_null = left_val -> is_none ()|| left_val -> is_undefined (); 135bool is_right_null = right_val -> is_none ()|| right_val -> is_undefined (); 136bool is_left_str = is_val < value_string > (left_val ); 137bool is_right_str = is_val < value_string > (right_val ); 138if ((is_left_null && is_right_str )|| (is_right_null && is_left_str )) { 139JJ_DEBUG ("%s" ,"Workaround: treating null/undefined as empty string for string concatenation" ); 140string left_str = is_left_null ?string () :left_val -> as_string (); 141string right_str = is_right_null ?string () :right_val -> as_string (); 142auto output = left_str .append (right_str ); 143res = mk_val < value_string > (std::move (output )); 144return true; 145 } 146return false; 147 }; 148 149auto test_is_in = [& ]()-> bool { 150func_args args (ctx ); 151args .push_back (left_val ); 152args .push_back (right_val ); 153return global_builtins ().at ("test_is_in" )(args )-> as_bool (); 154 }; 155 156// Handle undefined and null values 157if (is_val < value_undefined > (left_val )|| is_val < value_undefined > (right_val )) { 158if (is_val < value_undefined > (right_val )&& (op .value == "in" || op .value == "not in" )) { 159// Special case: `anything in undefined` is `false` and `anything not in undefined` is `true` 160return mk_val < value_bool > (op .value == "not in" ); 161 } 162if (op .value == "+" || op .value == "~" ) { 163value res = mk_val < value_undefined > (); 164if (workaround_concat_null_with_str (res )) { 165return res ; 166 } 167 } 168throw std::runtime_error ("Cannot perform operation " + op .value + " on undefined values" ); 169 }else if (is_val < value_none > (left_val )|| is_val < value_none > (right_val )) { 170if (!is_val < value_none > (right_val )&& (op .value == "in" || op .value == "not in" )) { 171// case: none in {'low': 1} 172// A null left operand is looked up like any other value. 173bool member = test_is_in (); 174return mk_val < value_bool > (op .value == "in" ?member : !member ); 175 } 176if (op .value == "+" || op .value == "~" ) { 177value res = mk_val < value_undefined > (); 178if (workaround_concat_null_with_str (res )) { 179return res ; 180 } 181 } 182throw std::runtime_error ("Cannot perform operation on null values" ); 183 } 184 185// Float operations 186if ((is_val < value_int > (left_val )|| is_val < value_float > (left_val ))&& 187 (is_val < value_int > (right_val )|| is_val < value_float > (right_val ))) { 188double a = left_val -> as_float (); 189double b = right_val -> as_float (); 190if (op .value == "+" || op .value == "-" || op .value == "*" ) { 191double res = (op .value == "+" ) ?a + b : (op .value == "-" ) ?a - b :a * b ; 192JJ_DEBUG ("Arithmetic operation: %f %s %f = %f" ,a ,op .value .c_str (),b ,res ); 193bool is_float = is_val < value_float > (left_val )|| is_val < value_float > (right_val ); 194if (is_float ) { 195return mk_val < value_float > (res ); 196 }else { 197return mk_val < value_int > (static_cast < int64_t > (res )); 198 } 199 }else if (op .value == "/" ) { 200JJ_DEBUG ("Division operation: %f / %f" ,a ,b ); 201return mk_val < value_float > (a /b ); 202 }else if (op .value == "%" ) { 203double rem = std::fmod (a ,b ); 204JJ_DEBUG ("Modulo operation: %f %% %f = %f" ,a ,b ,rem ); 205bool is_float = is_val < value_float > (left_val )|| is_val < value_float > (right_val ); 206if (is_float ) { 207return mk_val < value_float > (rem ); 208 }else { 209return mk_val < value_int > (static_cast < int64_t > (rem )); 210 } 211 }else if (op .value == "<" ) { 212JJ_DEBUG ("Comparison operation: %f < %f is %d" ,a ,b ,a < b ); 213return mk_val < value_bool > (a < b ); 214 }else if (op .value == ">" ) { 215JJ_DEBUG ("Comparison operation: %f > %f is %d" ,a ,b ,a > b ); 216return mk_val < value_bool > (a > b ); 217 }else if (op .value == ">=" ) { 218JJ_DEBUG ("Comparison operation: %f >= %f is %d" ,a ,b ,a >=b ); 219return mk_val < value_bool > (a >=b ); 220 }else if (op .value == "<=" ) { 221JJ_DEBUG ("Comparison operation: %f <= %f is %d" ,a ,b ,a <=b ); 222return mk_val < value_bool > (a <=b ); 223 } 224 } 225 226// Array operations 227if (is_val < value_array > (left_val )&& is_val < value_array > (right_val )) { 228if (op .value == "+" ) { 229auto & left_arr = left_val -> as_array (); 230auto & right_arr = right_val -> as_array (); 231auto result = mk_val < value_array > (); 232for (const auto & item :left_arr ) { 233result -> push_back (item ); 234 } 235for (const auto & item :right_arr ) { 236result -> push_back (item ); 237 } 238return result ; 239 } 240 }else if (is_val < value_array > (right_val )) { 241// case: 1 in [0, 1, 2] 242bool member = test_is_in (); 243if (op .value == "in" ) { 244return mk_val < value_bool > (member ); 245 }else if (op .value == "not in" ) { 246return mk_val < value_bool > (!member ); 247 } 248 } 249 250// String concatenation with ~ and + 251if ((is_val < value_string > (left_val )|| is_val < value_string > (right_val ))&& 252 (op .value == "~" || op .value == "+" )) { 253JJ_DEBUG ("String concatenation with %s operator" ,op .value .c_str ()); 254auto output = left_val -> as_string ().append (right_val -> as_string ()); 255auto res = mk_val < value_string > (); 256res -> val_str = std::move (output ); 257return res ; 258 } 259 260// Python-style string repetition 261// TODO: support array/tuple repetition (e.g., [1, 2] * 3 → [1, 2, 1, 2, 1, 2]) 262if (op .value == "*" && 263 ((is_val < value_string > (left_val )&& is_val < value_int > (right_val ))|| 264 (is_val < value_int > (left_val )&& is_val < value_string > (right_val )))) { 265const auto & str = is_val < value_string > (left_val ) ?left_val -> as_string () :right_val -> as_string (); 266const int64_t repeat = is_val < value_int > (right_val ) ?right_val -> as_int () :left_val -> as_int (); 267auto res = mk_val < value_string > (); 268if (repeat <=0 ) { 269return res ; 270 } 271for (int64_t i = 0 ;i < repeat ;++ i ) { 272res -> val_str .append (str ); 273 } 274return res ; 275 } 276 277// String membership 278if (is_val < value_string > (left_val )&& is_val < value_string > (right_val )) { 279// case: "a" in "abc" 280bool member = test_is_in (); 281if (op .value == "in" ) { 282return mk_val < value_bool > (member ); 283 }else if (op .value == "not in" ) { 284return mk_val < value_bool > (!member ); 285 } 286 } 287 288// Value key in object 289if (is_val < value_object > (right_val )) { 290// case: key in {key: value} 291bool member = test_is_in (); 292if (op .value == "in" ) { 293return mk_val < value_bool > (member ); 294 }else if (op .value == "not in" ) { 295return mk_val < value_bool > (!member ); 296 } 297 } 298 299throw std::runtime_error ("Unknown operator \"" + op .value + "\" between " + left_val -> type ()+ " and " + right_val -> type ()); 300} 301 302static value try_builtin_func (context & ctx ,const std::string & name ,value & input ,bool undef_on_missing = false) { 303JJ_DEBUG ("Trying built-in function '%s' for type %s" ,name .c_str (),input -> type ().c_str ()); 304if (ctx .is_get_stats ) { 305 value_t::stats_t::mark_used (input ); 306input -> stats .ops .insert (name ); 307 } 308auto builtins = input -> get_builtins (); 309auto it = builtins .find (name ); 310if (it != builtins .end ()) { 311JJ_DEBUG ("Binding built-in '%s'" ,name .c_str ()); 312return mk_val < value_func > (name ,it -> second ,input ); 313 } 314if (undef_on_missing ) { 315return mk_val < value_undefined > (name ); 316 } 317throw std::runtime_error ("Unknown (built-in) filter '" + name + "' for type " + input -> type ()); 318} 319 320value filter_expression::execute_impl (context & ctx ) { 321value input = operand ?operand -> execute (ctx ) :val ; 322 323JJ_DEBUG ("Applying filter to %s" ,input -> type ().c_str ()); 324 325auto set_filter_alias = [](auto & filter_id ) { 326if (filter_id == "count" ) { 327filter_id = "length" ; 328 }else if (filter_id == "d" ) { 329filter_id = "default" ; 330 }else if (filter_id == "e" ) { 331filter_id = "escape" ; 332 }else if (filter_id == "trim" ) { 333filter_id = "strip" ; 334 } 335 }; 336 337if (is_stmt < identifier > (filter )) { 338auto filter_id = cast_stmt < identifier > (filter )-> val ; 339 340set_filter_alias (filter_id ); 341JJ_DEBUG ("Applying filter '%s' to %s" ,filter_id .c_str (),input -> type ().c_str ()); 342// TODO: Refactor filters so this coercion can be done automatically 343if (!input -> is_undefined ()&& !is_val < value_string > (input )&& ( 344filter_id == "capitalize" || 345filter_id == "lower" || 346filter_id == "replace" || 347filter_id == "strip" || 348filter_id == "title" || 349filter_id == "upper" || 350filter_id == "wordcount" 351 )) { 352JJ_DEBUG ("Coercing %s to String for '%s' filter" ,input -> type ().c_str (),filter_id .c_str ()); 353input = mk_val < value_string > (input -> as_string ()); 354 } 355return try_builtin_func (ctx ,filter_id ,input )-> invoke (func_args (ctx )); 356 357 }else if (is_stmt < call_expression > (filter )) { 358auto call = cast_stmt < call_expression > (filter ); 359if (!is_stmt < identifier > (call -> callee )) { 360throw std::runtime_error ("Filter callee must be an identifier" ); 361 } 362auto filter_id = cast_stmt < identifier > (call -> callee )-> val ; 363 364set_filter_alias (filter_id ); 365JJ_DEBUG ("Applying filter '%s' with arguments to %s" ,filter_id .c_str (),input -> type ().c_str ()); 366func_args args (ctx ); 367for (const auto & arg_expr :call -> args ) { 368args .push_back (arg_expr -> execute (ctx )); 369 } 370 371return try_builtin_func (ctx ,filter_id ,input )-> invoke (args ); 372 373 }else { 374throw std::runtime_error ("Invalid filter expression" ); 375 } 376} 377 378value filter_statement::execute_impl (context & ctx ) { 379// eval body as string, then apply filter 380auto body_val = exec_statements (body ,ctx ); 381value_string parts = mk_val < value_string > (); 382gather_string_parts_recursive (body_val ,parts ); 383 384JJ_DEBUG ("FilterStatement: applying filter to body string of length %zu" ,parts -> val_str .length ()); 385filter_expression filter_expr (std::move (parts ), std::move (filter )); 386value out = filter_expr .execute (ctx ); 387 388// this node can be reused later, make sure filter is preserved 389this -> filter = std::move (filter_expr .filter ); 390return out ; 391} 392 393value test_expression::execute_impl (context & ctx ) { 394// NOTE: "value is something" translates to function call "test_is_something(value)" 395const auto & builtins = global_builtins (); 396 397 std::string test_id ; 398value input = operand -> execute (ctx ); 399 400func_args args (ctx ); 401args .push_back (input ); 402 403if (is_stmt < identifier > (test )) { 404test_id = cast_stmt < identifier > (test )-> val ; 405 }else if (is_stmt < call_expression > (test )) { 406auto call = cast_stmt < call_expression > (test ); 407if (!is_stmt < identifier > (call -> callee )) { 408throw std::runtime_error ("Test callee must be an identifier" ); 409 } 410test_id = cast_stmt < identifier > (call -> callee )-> val ; 411 412JJ_DEBUG ("Applying test '%s' with arguments to %s" ,test_id .c_str (),input -> type ().c_str ()); 413for (const auto & arg_expr :call -> args ) { 414args .push_back (arg_expr -> execute (ctx )); 415 } 416 417 }else { 418throw std::runtime_error ("Invalid test expression" ); 419 } 420 421const std::string test_name = "test_is_" + test_id ; 422auto it = builtins .find (test_name ); 423JJ_DEBUG ("Test expression %s '%s' %s (using function '%s')" ,operand -> type ().c_str (),test_id .c_str (),negate ?"(negate)" :"" ,test_name .c_str ()); 424if (it == builtins .end ()) { 425throw std::runtime_error ("Unknown test '" + test_id + "'" ); 426 } 427 428if (ctx .is_get_stats ) { 429 value_t::stats_t::mark_used (input ); 430input -> stats .ops .insert (test_name ); 431 } 432 433auto res = it -> second (args ); 434 435if (negate ) { 436return mk_val < value_bool > (!res -> as_bool ()); 437 }else { 438return res ; 439 } 440} 441 442value unary_expression::execute_impl (context & ctx ) { 443value operand_val = argument -> execute (ctx ); 444JJ_DEBUG ("Executing unary expression with operator '%s'" ,op .value .c_str ()); 445 446if (op .value == "not" ) { 447return mk_val < value_bool > (!operand_val -> as_bool ()); 448 }else if (op .value == "-" ) { 449if (is_val < value_int > (operand_val )) { 450return mk_val < value_int > (- operand_val -> as_int ()); 451 }else if (is_val < value_float > (operand_val )) { 452return mk_val < value_float > (- operand_val -> as_float ()); 453 }else { 454throw std::runtime_error ("Unary - operator requires numeric operand" ); 455 } 456 } 457 458throw std::runtime_error ("Unknown unary operator '" + op .value + "'" ); 459} 460 461value if_statement::execute_impl (context & ctx ) { 462value test_val = test -> execute (ctx ); 463 464auto out = mk_val < value_array > (); 465if (test_val -> as_bool ()) { 466for (auto & stmt :body ) { 467JJ_DEBUG ("IF --> Executing THEN body, current block: %s" ,stmt -> type ().c_str ()); 468out -> push_back (stmt -> execute (ctx )); 469 } 470 }else { 471for (auto & stmt :alternate ) { 472JJ_DEBUG ("IF --> Executing ELSE body, current block: %s" ,stmt -> type ().c_str ()); 473out -> push_back (stmt -> execute (ctx )); 474 } 475 } 476// convert to string parts 477value_string str = mk_val < value_string > (); 478gather_string_parts_recursive (out ,str ); 479return str ; 480} 481 482value for_statement::execute_impl (context & ctx ) { 483context scope (ctx );// new scope for loop variables 484 485 jinja::select_expression * select_expr = cast_stmt < select_expression > (iterable ); 486statement_ptr test_expr_nullptr ; 487 488statement_ptr & iter_expr = [& ]()-> statement_ptr & { 489auto tmp = cast_stmt < select_expression > (iterable ); 490return tmp ?tmp -> lhs :iterable ; 491 }(); 492statement_ptr & test_expr = [& ]()-> statement_ptr & { 493auto tmp = cast_stmt < select_expression > (iterable ); 494return tmp ?tmp -> test :test_expr_nullptr ; 495 }(); 496 497JJ_DEBUG ("Executing for statement, iterable type: %s" ,iter_expr -> type ().c_str ()); 498 499value iterable_val = iter_expr -> execute (scope ); 500 501// mark the variable being iterated as used for stats 502if (ctx .is_get_stats ) { 503 value_t::stats_t::mark_used (iterable_val ); 504iterable_val -> stats .ops .insert ("array_access" ); 505 } 506 507if (iterable_val -> is_undefined ()) { 508JJ_DEBUG ("%s" ,"For loop iterable is undefined, skipping loop" ); 509iterable_val = mk_val < value_array > (); 510 } 511 512if (!is_val < value_array > (iterable_val )&& !is_val < value_object > (iterable_val )) { 513throw std::runtime_error ("Expected iterable or object type in for loop: got " + iterable_val -> type ()); 514 } 515 516 std::vector < value > items ; 517if (is_val < value_object > (iterable_val )) { 518JJ_DEBUG ("%s" ,"For loop over object keys" ); 519auto & obj = iterable_val -> as_ordered_object (); 520for (auto & p :obj ) { 521auto tuple = mk_val < value_tuple > (p ); 522items .push_back (std::move (tuple )); 523 } 524if (ctx .is_get_stats ) { 525 value_t::stats_t::mark_used (iterable_val ); 526iterable_val -> stats .ops .insert ("object_access" ); 527 } 528 }else { 529JJ_DEBUG ("%s" ,"For loop over array items" ); 530auto & arr = iterable_val -> as_array (); 531for (const auto & item :arr ) { 532items .push_back (item ); 533 } 534if (ctx .is_get_stats ) { 535 value_t::stats_t::mark_used (iterable_val ); 536iterable_val -> stats .ops .insert ("array_access" ); 537 } 538 } 539 540 std::vector < std::function < void (context & )>> scope_update_fns ; 541 542 std::vector < value > filtered_items ; 543for (size_t i = 0 ;i < items .size ();++ i ) { 544context loop_scope (scope ); 545 546value current = items [i ]; 547 548 std::function < void (context & )> scope_update_fn = [](context & ) {/* no-op */ }; 549if (is_stmt < identifier > (loopvar )) { 550auto id = cast_stmt < identifier > (loopvar )-> val ; 551 552if (is_val < value_object > (iterable_val )) { 553// case example: {% for key in dict %} 554current = items [i ]-> as_array ()[0 ]; 555scope_update_fn = [id ,& items ,i ](context & ctx ) { 556ctx .set_val (id ,items [i ]-> as_array ()[0 ]); 557 }; 558 }else { 559// case example: {% for item in list %} 560scope_update_fn = [id ,& items ,i ](context & ctx ) { 561ctx .set_val (id ,items [i ]); 562 }; 563 } 564 565 }else if (is_stmt < tuple_literal > (loopvar )) { 566// case example: {% for key, value in dict %} 567auto tuple = cast_stmt < tuple_literal > (loopvar ); 568if (!is_val < value_array > (current )) { 569throw std::runtime_error ("Cannot unpack non-iterable type: " + current -> type ()); 570 } 571auto & c_arr = current -> as_array (); 572if (tuple -> val .size ()!= c_arr .size ()) { 573throw std::runtime_error (std::string ("Too " )+ (tuple -> val .size ()> c_arr .size () ?"few" :"many" )+ " items to unpack" ); 574 } 575scope_update_fn = [tuple ,& items ,i ](context & ctx ) { 576auto & c_arr = items [i ]-> as_array (); 577for (size_t j = 0 ;j < tuple -> val .size ();++ j ) { 578if (!is_stmt < identifier > (tuple -> val [j ])) { 579throw std::runtime_error ("Cannot unpack non-identifier type: " + tuple -> val [j ]-> type ()); 580 } 581auto id = cast_stmt < identifier > (tuple -> val [j ])-> val ; 582ctx .set_val (id ,c_arr [j ]); 583 } 584 }; 585 586 }else { 587throw std::runtime_error ("Invalid loop variable(s): " + loopvar -> type ()); 588 } 589 590if (select_expr && test_expr ) { 591scope_update_fn (loop_scope ); 592value test_val = test_expr -> execute (loop_scope ); 593if (!test_val -> as_bool ()) { 594continue ; 595 } 596 } 597JJ_DEBUG ("For loop: adding item type %s at index %zu" ,current -> type ().c_str (),i ); 598filtered_items .push_back (current ); 599scope_update_fns .push_back (scope_update_fn ); 600 } 601JJ_DEBUG ("For loop: %zu items after filtering" ,filtered_items .size ()); 602 603auto result = mk_val < value_array > (); 604 605bool noIteration = true; 606for (size_t i = 0 ;i < filtered_items .size ();i ++ ) { 607JJ_DEBUG ("For loop iteration %zu/%zu" ,i + 1 ,filtered_items .size ()); 608value_object loop_obj = mk_val < value_object > (); 609loop_obj -> has_builtins = false;// loop object has no builtins 610loop_obj -> insert ("index" ,mk_val < value_int > (i + 1 )); 611loop_obj -> insert ("index0" ,mk_val < value_int > (i )); 612loop_obj -> insert ("revindex" ,mk_val < value_int > (filtered_items .size ()- i )); 613loop_obj -> insert ("revindex0" ,mk_val < value_int > (filtered_items .size ()- i - 1 )); 614loop_obj -> insert ("first" ,mk_val < value_bool > (i == 0 )); 615loop_obj -> insert ("last" ,mk_val < value_bool > (i == filtered_items .size ()- 1 )); 616loop_obj -> insert ("length" ,mk_val < value_int > (filtered_items .size ())); 617loop_obj -> insert ("previtem" ,i > 0 ?filtered_items [i - 1 ] :mk_val < value_undefined > ("previtem" )); 618loop_obj -> insert ("nextitem" ,i < filtered_items .size ()- 1 ?filtered_items [i + 1 ] :mk_val < value_undefined > ("nextitem" )); 619scope .set_val ("loop" ,loop_obj ); 620scope_update_fns [i ](scope ); 621try { 622for (auto & stmt :body ) { 623value val = stmt -> execute (scope ); 624result -> push_back (val ); 625 } 626 }catch (const continue_statement::signal & ) { 627continue ; 628 }catch (const break_statement::signal & ) { 629break ; 630 } 631noIteration = false; 632 } 633 634JJ_DEBUG ("For loop complete, total iterations: %zu" ,filtered_items .size ()); 635if (noIteration ) { 636for (auto & stmt :default_block ) { 637value val = stmt -> execute (ctx ); 638result -> push_back (val ); 639 } 640 } 641 642// convert to string parts 643value_string str = mk_val < value_string > (); 644gather_string_parts_recursive (result ,str ); 645return str ; 646} 647 648value set_statement::execute_impl (context & ctx ) { 649auto rhs = val ?val -> execute (ctx ) :exec_statements (body ,ctx ); 650 651if (is_stmt < identifier > (assignee )) { 652// case: {% set my_var = value %} 653auto var_name = cast_stmt < identifier > (assignee )-> val ; 654JJ_DEBUG ("Setting global variable '%s' with value type %s" ,var_name .c_str (),rhs -> type ().c_str ()); 655ctx .set_val (var_name ,rhs ); 656 657 }else if (is_stmt < tuple_literal > (assignee )) { 658// case: {% set a, b = value %} 659auto tuple = cast_stmt < tuple_literal > (assignee ); 660if (!is_val < value_array > (rhs )) { 661throw std::runtime_error ("Cannot unpack non-iterable type in set: " + rhs -> type ()); 662 } 663auto & arr = rhs -> as_array (); 664if (arr .size ()!= tuple -> val .size ()) { 665throw std::runtime_error (std::string ("Too " )+ (tuple -> val .size ()> arr .size () ?"few" :"many" )+ " items to unpack in set" ); 666 } 667for (size_t i = 0 ;i < tuple -> val .size ();++ i ) { 668auto & elem = tuple -> val [i ]; 669if (!is_stmt < identifier > (elem )) { 670throw std::runtime_error ("Cannot unpack to non-identifier in set: " + elem -> type ()); 671 } 672auto var_name = cast_stmt < identifier > (elem )-> val ; 673ctx .set_val (var_name ,arr [i ]); 674 } 675 676 }else if (is_stmt < member_expression > (assignee )) { 677// case: {% set ns.my_var = value %} 678auto member = cast_stmt < member_expression > (assignee ); 679if (member -> computed ) { 680throw std::runtime_error ("Cannot assign to computed member" ); 681 } 682if (!is_stmt < identifier > (member -> property )) { 683throw std::runtime_error ("Cannot assign to member with non-identifier property" ); 684 } 685auto prop_name = cast_stmt < identifier > (member -> property )-> val ; 686 687value object = member -> object -> execute (ctx ); 688if (!is_val < value_object > (object )) { 689throw std::runtime_error ("Cannot assign to member of non-object" ); 690 } 691auto obj_ptr = cast_val < value_object > (object ); 692JJ_DEBUG ("Setting object property '%s' with value type %s" ,prop_name .c_str (),rhs -> type ().c_str ()); 693obj_ptr -> insert (prop_name ,rhs ); 694 695 }else { 696throw std::runtime_error ("Invalid LHS inside assignment expression: " + assignee -> type ()); 697 } 698return mk_val < value_undefined > (); 699} 700 701static inline void bind_parameters (const std::string & name ,const statements & this_args ,const func_args & args ,context & ctx ) { 702const size_t expected_count = this_args .size (); 703const size_t input_count = args .count (); 704 705JJ_DEBUG ("Invoking '%s' with %zu input arguments (expected %zu)" ,name .c_str (),input_count ,expected_count ); 706for (size_t i = 0 ;i < expected_count ;++ i ) { 707if (i < input_count ) { 708if (is_stmt < identifier > (this_args [i ])) { 709// normal parameter 710 std::string param_name = cast_stmt < identifier > (this_args [i ])-> val ; 711value param_value = args .get_kwarg_or_pos (param_name ,i ); 712JJ_DEBUG (" Binding parameter '%s' to argument of type %s" ,param_name .c_str (),param_value -> type ().c_str ()); 713ctx .set_val (param_name ,param_value ); 714 }else if (is_stmt < keyword_argument_expression > (this_args [i ])) { 715// default argument used as normal parameter 716auto kwarg = cast_stmt < keyword_argument_expression > (this_args [i ]); 717if (!is_stmt < identifier > (kwarg -> key )) { 718throw std::runtime_error ("Keyword argument key must be an identifier in '" + name + "'" ); 719 } 720 std::string param_name = cast_stmt < identifier > (kwarg -> key )-> val ; 721value param_value = args .get_kwarg_or_pos (param_name ,i ); 722JJ_DEBUG (" Binding parameter '%s' to argument of type %s" ,param_name .c_str (),param_value -> type ().c_str ()); 723ctx .set_val (param_name ,param_value ); 724 }else { 725throw std::runtime_error ("Invalid parameter type in '" + name + "'" ); 726 } 727 }else { 728auto & default_arg = this_args [i ]; 729if (is_stmt < keyword_argument_expression > (default_arg )) { 730auto kwarg = cast_stmt < keyword_argument_expression > (default_arg ); 731if (!is_stmt < identifier > (kwarg -> key )) { 732throw std::runtime_error ("Keyword argument key must be an identifier in '" + name + "'" ); 733 } 734 std::string param_name = cast_stmt < identifier > (kwarg -> key )-> val ; 735JJ_DEBUG (" Binding parameter '%s' to default argument of type %s" ,param_name .c_str (),kwarg -> val -> type ().c_str ()); 736ctx .set_val (param_name ,kwarg -> val -> execute (args .ctx )); 737 }else { 738throw std::runtime_error ("Not enough arguments provided to '" + name + "'" ); 739 } 740//std::string param_name = cast_stmt<identifier>(default_args[i])->val; 741//JJ_DEBUG(" Binding parameter '%s' to default", param_name.c_str()); 742//ctx.var[param_name] = default_args[i]->execute(ctx); 743 } 744 } 745} 746 747value macro_statement::execute_impl (context & ctx ) { 748if (!is_stmt < identifier > (this -> name )) { 749throw std::runtime_error ("Macro name must be an identifier" ); 750 } 751 std::string name = cast_stmt < identifier > (this -> name )-> val ; 752 753const func_handler func = [this ,name ](const func_args & args )-> value { 754context macro_ctx (args .ctx );// new scope for macro execution 755 756bind_parameters (name ,this -> args ,args ,macro_ctx ); 757 758// execute macro body 759JJ_DEBUG ("Executing macro '%s' body with %zu statements" ,name .c_str (),this -> body .size ()); 760auto res = exec_statements (this -> body ,macro_ctx ); 761JJ_DEBUG ("Macro '%s' execution complete, result: %s" ,name .c_str (),res -> val_str .str ().c_str ()); 762return res ; 763 }; 764 765JJ_DEBUG ("Defining macro '%s' with %zu parameters" ,name .c_str (),args .size ()); 766ctx .set_val (name ,mk_val < value_func > (name ,func )); 767return mk_val < value_undefined > (); 768} 769 770value call_statement::execute_impl (context & ctx ) { 771auto call_expr = cast_stmt < call_expression > (this -> call ); 772if (!call_expr ) { 773throw std::runtime_error ("Call statement requires a valid call expression" ); 774 } 775 776value callee_val = call_expr -> callee -> execute (ctx ); 777if (!is_val < value_func > (callee_val )) { 778throw std::runtime_error ("Callee is not a function: got " + callee_val -> type ()); 779 } 780auto * callee_func = cast_val < value_func > (callee_val ); 781 782context caller_ctx (ctx );// new scope for caller execution 783 784const func_handler func = [this ,caller_ctx = std::move (caller_ctx )](const func_args & args )-> value { 785context block_ctx (caller_ctx );// new scope for block execution 786 787bind_parameters ("caller" ,this -> caller_args ,args ,block_ctx ); 788 789JJ_DEBUG ("Executing call body with %zu statements" ,this -> body .size ()); 790auto res = exec_statements (this -> body ,block_ctx ); 791JJ_DEBUG ("Call body execution complete, result: %s" ,res -> val_str .str ().c_str ()); 792return res ; 793 }; 794 795context call_ctx (ctx ); 796call_ctx .set_val ("caller" ,mk_val < value_func > ("caller" ,func )); 797 798func_args args (call_ctx ); 799 800for (const auto & arg_expr :call_expr -> args ) { 801auto arg_val = arg_expr -> execute (ctx ); 802JJ_DEBUG (" Argument type: %s" ,arg_val -> type ().c_str ()); 803args .push_back (arg_val ); 804 } 805 806JJ_DEBUG ("Calling macro '%s' with %zu arguments" ,callee_func -> name .c_str (),args .count ()); 807return callee_func -> invoke (args ); 808} 809 810value member_expression::execute_impl (context & ctx ) { 811value object = this -> object -> execute (ctx ); 812 813value property ; 814if (this -> computed ) { 815// syntax: obj[expr] 816JJ_DEBUG ("Member expression, computing property type %s" ,this -> property -> type ().c_str ()); 817 818int64_t arr_size = 0 ; 819if (is_val < value_array > (object )) { 820arr_size = object -> as_array ().size (); 821 }else if (is_val < value_string > (object )) { 822arr_size = object -> as_string ().length (); 823 } 824 825if (is_stmt < slice_expression > (this -> property )) { 826auto s = cast_stmt < slice_expression > (this -> property ); 827value step_val = s -> step_expr ?s -> step_expr -> execute (ctx ) :mk_val < value_int > (1 ); 828value start_val = s -> start_expr ?s -> start_expr -> execute (ctx ) : (step_val -> as_int ()< 0 ?mk_val < value_int > (arr_size - 1 ) :mk_val < value_int > (0 )); 829value stop_val = s -> stop_expr ?s -> stop_expr -> execute (ctx ) : (step_val -> as_int ()< 0 ?mk_val < value_int > (-1 ) :mk_val < value_int > (arr_size )); 830 831// translate to function call: obj.slice(start, stop, step) 832JJ_DEBUG ("Member expression is a slice: start %s, stop %s, step %s" , 833start_val -> as_repr ().c_str (), 834stop_val -> as_repr ().c_str (), 835step_val -> as_repr ().c_str ()); 836auto slice_func = try_builtin_func (ctx ,"slice" ,object ); 837func_args args (ctx ); 838args .push_back (start_val ); 839args .push_back (stop_val ); 840args .push_back (step_val ); 841return slice_func -> invoke (args ); 842 }else { 843property = this -> property -> execute (ctx ); 844 } 845 }else if (is_stmt < integer_literal > (this -> property )) { 846// syntax: obj.index 847property = mk_val < value_int > (cast_stmt < integer_literal > (this -> property )-> val ); 848if (property -> as_int ()< 0 ) { 849throw std::runtime_error ("Static member property cannot be negative" ); 850 } 851 }else { 852// syntax: obj.prop 853if (!is_stmt < identifier > (this -> property )) { 854throw std::runtime_error ("Static member property must be an identifier" ); 855 } 856property = mk_val < value_string > (cast_stmt < identifier > (this -> property )-> val ); 857 std::string prop = property -> as_string ().str (); 858JJ_DEBUG ("Member expression, object type %s, static property '%s'" ,object -> type ().c_str (),prop .c_str ()); 859 860// behavior of jinja2: obj having prop as a built-in function AND 'prop', as an object key, 861// then obj.prop returns the built-in function, not the property value. 862// while obj['prop'] returns the property value. 863// example: {"obj": {"items": 123}} -> obj.items is the built-in function, obj['items'] is 123 864 865value val = try_builtin_func (ctx ,prop ,object , true); 866if (!is_val < value_undefined > (val )) { 867return val ; 868 } 869// else, fallthrough to normal property access below 870 } 871 872JJ_DEBUG ("Member expression on object type %s, property type %s" ,object -> type ().c_str (),property -> type ().c_str ()); 873value val = mk_val < value_undefined > ("object_property" ); 874 875if (property -> is_undefined ()) { 876JJ_DEBUG ("%s" ,"Member expression property is undefined, returning undefined" ); 877return val ; 878 } 879 880ensure_key_type_allowed (property ); 881 882if (is_val < value_undefined > (object )) { 883JJ_DEBUG ("%s" ,"Accessing property on undefined object, returning undefined" ); 884return val ; 885 886 }else if (is_val < value_object > (object )) { 887auto key = property -> as_string ().str (); 888val = object -> at (property ,val ); 889if (is_val < value_undefined > (val )) { 890val = try_builtin_func (ctx ,key ,object , true); 891 } 892JJ_DEBUG ("Accessed property '%s' value, got type: %s" ,key .c_str (),val -> type ().c_str ()); 893 894 }else if (is_val < value_array > (object )|| is_val < value_string > (object )) { 895if (is_val < value_int > (property )) { 896int64_t index = property -> as_int (); 897JJ_DEBUG ("Accessing %s index %d" ,object -> type ().c_str (), (int )index ); 898if (is_val < value_array > (object )) { 899auto & arr = object -> as_array (); 900if (index < 0 ) { 901index += static_cast < int64_t > (arr .size ()); 902 } 903if (index >=0 && index < static_cast < int64_t > (arr .size ())) { 904val = arr [index ]; 905 } 906 }else {// value_string 907auto str = object -> as_string ().str (); 908if (index >=0 && index < static_cast < int64_t > (str .size ())) { 909val = mk_val < value_string > (std::string (1 ,str [index ])); 910 } 911 } 912 913 }else if (is_val < value_string > (property )) { 914auto key = property -> as_string ().str (); 915JJ_DEBUG ("Accessing %s built-in '%s'" ,is_val < value_array > (object ) ?"array" :"string" ,key .c_str ()); 916val = try_builtin_func (ctx ,key ,object , true); 917 918 }else { 919throw std::runtime_error ("Cannot access property with non-string/non-number: got " + property -> type ()); 920 } 921 }else { 922if (!is_val < value_string > (property )) { 923throw std::runtime_error ("Cannot access property with non-string: got " + property -> type ()); 924 } 925auto key = property -> as_string ().str (); 926val = try_builtin_func (ctx ,key ,object , true); 927 } 928 929if (ctx .is_get_stats && val && object && property ) { 930 value_t::stats_t::mark_used (val ); 931 value_t::stats_t::mark_used (object ); 932 value_t::stats_t::mark_used (property ); 933if (is_val < value_int > (property )) { 934object -> stats .ops .insert ("array_access" ); 935 }else if (is_val < value_string > (property )) { 936object -> stats .ops .insert ("object_access" ); 937 } 938 } 939 940return val ; 941} 942 943value call_expression::execute_impl (context & ctx ) { 944// gather arguments 945func_args args (ctx ); 946for (auto & arg_stmt :this -> args ) { 947auto arg_val = arg_stmt -> execute (ctx ); 948JJ_DEBUG (" Argument type: %s" ,arg_val -> type ().c_str ()); 949args .push_back (arg_val ); 950 } 951// execute callee 952value callee_val = callee -> execute (ctx ); 953if (!is_val < value_func > (callee_val )) { 954throw std::runtime_error ("Callee is not a function: got " + callee_val -> type ()); 955 } 956auto * callee_func = cast_val < value_func > (callee_val ); 957JJ_DEBUG ("Calling function '%s' with %zu arguments" ,callee_func -> name .c_str (),args .count ()); 958return callee_func -> invoke (args ); 959} 960 961value keyword_argument_expression::execute_impl (context & ctx ) { 962if (!is_stmt < identifier > (key )) { 963throw std::runtime_error ("Keyword argument key must be identifiers" ); 964 } 965 966 std::string k = cast_stmt < identifier > (key )-> val ; 967JJ_DEBUG ("Keyword argument expression key: %s, value: %s" ,k .c_str (),val -> type ().c_str ()); 968 969value v = val -> execute (ctx ); 970JJ_DEBUG ("Keyword argument value executed, type: %s" ,v -> type ().c_str ()); 971 972return mk_val < value_kwarg > (k ,v ); 973} 974 975std::string runtime::debug_dump_program (const program & prog ,const std::string & src ) { 976 std::ostringstream oss ; 977size_t lvl = 0 ; 978context ctx ; 979ctx .src .reset (new std::string (src )); 980 981auto indent = [](size_t lvl )-> std::string { 982return std::string (lvl * 2 ,' ' ); 983 }; 984 985ctx .visitor = [& ](bool is_leaf ,statement * node , std::vector < visitor_pair > children ) { 986oss <<indent (lvl ) <<node -> type () <<":\n" ; 987lvl ++ ; 988if (is_leaf ) { 989const auto & pos = node -> pos ; 990oss <<indent (lvl ) <<"(leaf) at " <<get_line_col (src ,pos ) <<" in source:\n" ; 991 std::string snippet = peak_source (src ,pos ); 992string_replace_all (snippet ,"\n" ,"\n" + indent (lvl )); 993oss <<indent (lvl ) <<snippet <<"\n" ; 994 }else { 995for (auto & [label ,children_vec ] :children ) { 996oss <<indent (lvl ) <<label <<":\n" ; 997lvl ++ ; 998if (children_vec .empty ()) { 999oss <<indent (lvl ) <<"<empty>\n\n" ; 1000 }else { 1001for (auto * child :children_vec ) { 1002if (!child ) { 1003continue ; 1004 } 1005child -> visit (ctx ); 1006 } 1007 } 1008lvl -- ; 1009 } 1010 } 1011lvl -- ; 1012 }; 1013 1014for (const auto & stmt :prog .body ) { 1015stmt -> visit (ctx ); 1016 } 1017 1018return oss .str (); 1019} 1020 1021}// namespace jinja