char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Sigbjørn Skjæretjinja : support dot property integer literals (#28817)ae9afff8d

char/aliceai-t5-moe
40.4 KiB1021 linesraw
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 parts
28    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 handling
54value 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 formatting
78            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 operators
116    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 operators
125    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 values
157    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 operations
186    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 operations
227    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 repetition
261    // 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 membership
278    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 object
289    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 automatically
343        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 filter
380    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 preserved
389    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 parts
477    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 variables
484
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 stats
502    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 builtins
610        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 parts
643    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 parameter
710                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 parameter
716                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 execution
755
756        bind_parameters(name, this->args, args, macro_ctx);
757
758        // execute macro body
759        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 execution
783
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 execution
786
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.index
847        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.prop
853        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 123
864
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 below
870    }
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_string
907                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 arguments
945    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 callee
952    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 jinja