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