CoolFace
Modelpublic

Felipe97/llama-cpp-compiled

sourceHugging Faceupdated 3d agoView on Hugging Face
0likes1.1kdownloads
runtime.cpp1022 linesDownload Raw Back to jinja
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